parent
07eee63e35
commit
9164c71c76
@ -1,83 +1,83 @@ |
||||
import Image from 'next/image'; |
||||
'use client'; |
||||
|
||||
import { useEffect, useState } from 'react'; |
||||
import { DateTime } from 'luxon'; |
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; |
||||
import { Label } from '@/components/ui/label'; |
||||
import { |
||||
Select, |
||||
SelectContent, |
||||
SelectItem, |
||||
SelectTrigger, |
||||
SelectValue, |
||||
} from '@/components/ui/select'; |
||||
|
||||
const TIMEZONE_LIST = ['Asia/Seoul', 'America/New_York', 'Europe/London', 'Asia/Tokyo']; |
||||
|
||||
export default function Home() { |
||||
return ( |
||||
<div className="grid min-h-screen grid-rows-[20px_1fr_20px] items-center justify-items-center gap-16 p-8 pb-20 font-sans sm:p-20"> |
||||
<main className="row-start-2 flex flex-col items-center gap-[32px] sm:items-start"> |
||||
<Image |
||||
className="dark:invert" |
||||
src="/next.svg" |
||||
alt="Next.js logo" |
||||
width={180} |
||||
height={38} |
||||
priority |
||||
/> |
||||
<ol className="list-inside list-decimal text-center font-mono text-sm/6 sm:text-left"> |
||||
<li className="mb-2 tracking-[-.01em]"> |
||||
Get started by editing{' '} |
||||
<code className="rounded bg-black/[.05] px-1 py-0.5 font-mono font-semibold dark:bg-white/[.06]"> |
||||
src/app/page.tsx |
||||
</code> |
||||
. |
||||
</li> |
||||
<li className="tracking-[-.01em]">Save and see your changes instantly.</li> |
||||
</ol> |
||||
const [now, setNow] = useState(DateTime.now()); |
||||
const [timezone, setTimezone] = useState('Asia/Seoul'); |
||||
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row"> |
||||
<a |
||||
className="bg-foreground text-background flex h-10 items-center justify-center gap-2 rounded-full border border-solid border-transparent px-4 text-sm font-medium transition-colors hover:bg-[#383838] sm:h-12 sm:w-auto sm:px-5 sm:text-base dark:hover:bg-[#ccc]" |
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
<Image |
||||
className="dark:invert" |
||||
src="/vercel.svg" |
||||
alt="Vercel logomark" |
||||
width={20} |
||||
height={20} |
||||
/> |
||||
Deploy now |
||||
</a> |
||||
<a |
||||
className="flex h-10 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-4 text-sm font-medium transition-colors hover:border-transparent hover:bg-[#f2f2f2] sm:h-12 sm:w-auto sm:px-5 sm:text-base md:w-[158px] dark:border-white/[.145] dark:hover:bg-[#1a1a1a]" |
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
Read our docs |
||||
</a> |
||||
useEffect(() => { |
||||
const timer = setInterval(() => setNow(DateTime.now()), 10); |
||||
return () => clearInterval(timer); |
||||
}, []); |
||||
|
||||
const localNow = now.setZone(timezone); |
||||
|
||||
return ( |
||||
<main className="flex min-h-screen flex-col items-center justify-center p-6"> |
||||
<Card className="w-full max-w-md"> |
||||
<CardHeader> |
||||
<CardTitle className="text-2xl">DateTime UI Sample</CardTitle> |
||||
</CardHeader> |
||||
<CardContent className="space-y-4"> |
||||
<div className="flex flex-col justify-center"> |
||||
<Label htmlFor="tz">Select timezone</Label> |
||||
<Select value={timezone} onValueChange={setTimezone}> |
||||
<SelectTrigger id="tz" className="mt-1"> |
||||
<SelectValue placeholder="Select timezone" /> |
||||
</SelectTrigger> |
||||
<SelectContent> |
||||
{TIMEZONE_LIST.map((tz) => ( |
||||
<SelectItem key={tz} value={tz}> |
||||
{tz} |
||||
</SelectItem> |
||||
))} |
||||
</SelectContent> |
||||
</Select> |
||||
</div> |
||||
</main> |
||||
<footer className="row-start-3 flex flex-wrap items-center justify-center gap-[24px]"> |
||||
<a |
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
<Image aria-hidden src="/file.svg" alt="File icon" width={16} height={16} /> |
||||
Learn |
||||
</a> |
||||
<a |
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
<Image aria-hidden src="/window.svg" alt="Window icon" width={16} height={16} /> |
||||
Examples |
||||
</a> |
||||
<a |
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
<div className="text-primary font-mono text-lg"> |
||||
Current time: {localNow.toFormat('yyyy-MM-dd HH:mm:ss (ZZZZ)')} |
||||
</div> |
||||
<div className="text-muted-foreground text-xs"> |
||||
(Luxon: realtime rendering, local timezone support) |
||||
</div> |
||||
</CardContent> |
||||
</Card> |
||||
<h1 className="mb-6 text-3xl font-bold">DateTime UI Sample</h1> |
||||
<div className="flex w-full max-w-md flex-col items-center space-y-4 rounded-xl p-6 shadow-lg"> |
||||
<div> |
||||
<label className="mb-2 block font-semibold">Timezone</label> |
||||
<select |
||||
className="rounded border px-3 py-2" |
||||
value={timezone} |
||||
onChange={(e) => setTimezone(e.target.value)} |
||||
> |
||||
<Image aria-hidden src="/globe.svg" alt="Globe icon" width={16} height={16} /> |
||||
Go to nextjs.org → |
||||
</a> |
||||
</footer> |
||||
{TIMEZONE_LIST.map((tz) => ( |
||||
<option key={tz} value={tz}> |
||||
{tz} |
||||
</option> |
||||
))} |
||||
</select> |
||||
</div> |
||||
<div className="text-x font-mono text-teal-600"> |
||||
Current time: {localNow.toFormat('yyyy-MM-dd HH:mm:ss (ZZZZ)')} |
||||
</div> |
||||
<div className="text-xs opacity-60"> |
||||
(Luxon: realtime rendering, local timezone support) |
||||
</div> |
||||
</div> |
||||
</main> |
||||
); |
||||
} |
||||
|
@ -0,0 +1,185 @@ |
||||
"use client" |
||||
|
||||
import * as React from "react" |
||||
import * as SelectPrimitive from "@radix-ui/react-select" |
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" |
||||
|
||||
import { cn } from "@/lib/utils" |
||||
|
||||
function Select({ |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) { |
||||
return <SelectPrimitive.Root data-slot="select" {...props} /> |
||||
} |
||||
|
||||
function SelectGroup({ |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) { |
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} /> |
||||
} |
||||
|
||||
function SelectValue({ |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) { |
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} /> |
||||
} |
||||
|
||||
function SelectTrigger({ |
||||
className, |
||||
size = "default", |
||||
children, |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & { |
||||
size?: "sm" | "default" |
||||
}) { |
||||
return ( |
||||
<SelectPrimitive.Trigger |
||||
data-slot="select-trigger" |
||||
data-size={size} |
||||
className={cn( |
||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", |
||||
className |
||||
)} |
||||
{...props} |
||||
> |
||||
{children} |
||||
<SelectPrimitive.Icon asChild> |
||||
<ChevronDownIcon className="size-4 opacity-50" /> |
||||
</SelectPrimitive.Icon> |
||||
</SelectPrimitive.Trigger> |
||||
) |
||||
} |
||||
|
||||
function SelectContent({ |
||||
className, |
||||
children, |
||||
position = "popper", |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) { |
||||
return ( |
||||
<SelectPrimitive.Portal> |
||||
<SelectPrimitive.Content |
||||
data-slot="select-content" |
||||
className={cn( |
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", |
||||
position === "popper" && |
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", |
||||
className |
||||
)} |
||||
position={position} |
||||
{...props} |
||||
> |
||||
<SelectScrollUpButton /> |
||||
<SelectPrimitive.Viewport |
||||
className={cn( |
||||
"p-1", |
||||
position === "popper" && |
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1" |
||||
)} |
||||
> |
||||
{children} |
||||
</SelectPrimitive.Viewport> |
||||
<SelectScrollDownButton /> |
||||
</SelectPrimitive.Content> |
||||
</SelectPrimitive.Portal> |
||||
) |
||||
} |
||||
|
||||
function SelectLabel({ |
||||
className, |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) { |
||||
return ( |
||||
<SelectPrimitive.Label |
||||
data-slot="select-label" |
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} |
||||
{...props} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
function SelectItem({ |
||||
className, |
||||
children, |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) { |
||||
return ( |
||||
<SelectPrimitive.Item |
||||
data-slot="select-item" |
||||
className={cn( |
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", |
||||
className |
||||
)} |
||||
{...props} |
||||
> |
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center"> |
||||
<SelectPrimitive.ItemIndicator> |
||||
<CheckIcon className="size-4" /> |
||||
</SelectPrimitive.ItemIndicator> |
||||
</span> |
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> |
||||
</SelectPrimitive.Item> |
||||
) |
||||
} |
||||
|
||||
function SelectSeparator({ |
||||
className, |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) { |
||||
return ( |
||||
<SelectPrimitive.Separator |
||||
data-slot="select-separator" |
||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)} |
||||
{...props} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
function SelectScrollUpButton({ |
||||
className, |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { |
||||
return ( |
||||
<SelectPrimitive.ScrollUpButton |
||||
data-slot="select-scroll-up-button" |
||||
className={cn( |
||||
"flex cursor-default items-center justify-center py-1", |
||||
className |
||||
)} |
||||
{...props} |
||||
> |
||||
<ChevronUpIcon className="size-4" /> |
||||
</SelectPrimitive.ScrollUpButton> |
||||
) |
||||
} |
||||
|
||||
function SelectScrollDownButton({ |
||||
className, |
||||
...props |
||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) { |
||||
return ( |
||||
<SelectPrimitive.ScrollDownButton |
||||
data-slot="select-scroll-down-button" |
||||
className={cn( |
||||
"flex cursor-default items-center justify-center py-1", |
||||
className |
||||
)} |
||||
{...props} |
||||
> |
||||
<ChevronDownIcon className="size-4" /> |
||||
</SelectPrimitive.ScrollDownButton> |
||||
) |
||||
} |
||||
|
||||
export { |
||||
Select, |
||||
SelectContent, |
||||
SelectGroup, |
||||
SelectItem, |
||||
SelectLabel, |
||||
SelectScrollDownButton, |
||||
SelectScrollUpButton, |
||||
SelectSeparator, |
||||
SelectTrigger, |
||||
SelectValue, |
||||
} |
Loading…
Reference in new issue