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() { |
export default function Home() { |
||||||
return ( |
const [now, setNow] = useState(DateTime.now()); |
||||||
<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"> |
const [timezone, setTimezone] = useState('Asia/Seoul'); |
||||||
<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> |
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-4 sm:flex-row"> |
useEffect(() => { |
||||||
<a |
const timer = setInterval(() => setNow(DateTime.now()), 10); |
||||||
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]" |
return () => clearInterval(timer); |
||||||
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" |
const localNow = now.setZone(timezone); |
||||||
> |
|
||||||
<Image |
return ( |
||||||
className="dark:invert" |
<main className="flex min-h-screen flex-col items-center justify-center p-6"> |
||||||
src="/vercel.svg" |
<Card className="w-full max-w-md"> |
||||||
alt="Vercel logomark" |
<CardHeader> |
||||||
width={20} |
<CardTitle className="text-2xl">DateTime UI Sample</CardTitle> |
||||||
height={20} |
</CardHeader> |
||||||
/> |
<CardContent className="space-y-4"> |
||||||
Deploy now |
<div className="flex flex-col justify-center"> |
||||||
</a> |
<Label htmlFor="tz">Select timezone</Label> |
||||||
<a |
<Select value={timezone} onValueChange={setTimezone}> |
||||||
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]" |
<SelectTrigger id="tz" className="mt-1"> |
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
<SelectValue placeholder="Select timezone" /> |
||||||
target="_blank" |
</SelectTrigger> |
||||||
rel="noopener noreferrer" |
<SelectContent> |
||||||
> |
{TIMEZONE_LIST.map((tz) => ( |
||||||
Read our docs |
<SelectItem key={tz} value={tz}> |
||||||
</a> |
{tz} |
||||||
|
</SelectItem> |
||||||
|
))} |
||||||
|
</SelectContent> |
||||||
|
</Select> |
||||||
</div> |
</div> |
||||||
</main> |
<div className="text-primary font-mono text-lg"> |
||||||
<footer className="row-start-3 flex flex-wrap items-center justify-center gap-[24px]"> |
Current time: {localNow.toFormat('yyyy-MM-dd HH:mm:ss (ZZZZ)')} |
||||||
<a |
</div> |
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
<div className="text-muted-foreground text-xs"> |
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
(Luxon: realtime rendering, local timezone support) |
||||||
target="_blank" |
</div> |
||||||
rel="noopener noreferrer" |
</CardContent> |
||||||
> |
</Card> |
||||||
<Image aria-hidden src="/file.svg" alt="File icon" width={16} height={16} /> |
<h1 className="mb-6 text-3xl font-bold">DateTime UI Sample</h1> |
||||||
Learn |
<div className="flex w-full max-w-md flex-col items-center space-y-4 rounded-xl p-6 shadow-lg"> |
||||||
</a> |
<div> |
||||||
<a |
<label className="mb-2 block font-semibold">Timezone</label> |
||||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" |
<select |
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" |
className="rounded border px-3 py-2" |
||||||
target="_blank" |
value={timezone} |
||||||
rel="noopener noreferrer" |
onChange={(e) => setTimezone(e.target.value)} |
||||||
> |
|
||||||
<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" |
|
||||||
> |
> |
||||||
<Image aria-hidden src="/globe.svg" alt="Globe icon" width={16} height={16} /> |
{TIMEZONE_LIST.map((tz) => ( |
||||||
Go to nextjs.org → |
<option key={tz} value={tz}> |
||||||
</a> |
{tz} |
||||||
</footer> |
</option> |
||||||
|
))} |
||||||
|
</select> |
||||||
</div> |
</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