feat: implement new ui
This commit is contained in:
parent
4d5af8c62a
commit
6293470332
28 changed files with 1417 additions and 584 deletions
137
components/icons.tsx
Normal file
137
components/icons.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function NextChat({
|
||||
className,
|
||||
inverted,
|
||||
...props
|
||||
}: React.ComponentProps<'svg'> & { inverted?: boolean }) {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn('w-4 h-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`gradient-${id}-1`}
|
||||
x1="10.6889"
|
||||
y1="10.3556"
|
||||
x2="13.8445"
|
||||
y2="14.2667"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={inverted ? 'white' : 'black'} />
|
||||
<stop
|
||||
offset={1}
|
||||
stopColor={inverted ? 'white' : 'black'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id={`gradient-${id}-2`}
|
||||
x1="11.7555"
|
||||
y1="4.8"
|
||||
x2="11.7376"
|
||||
y2="9.50002"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={inverted ? 'white' : 'black'} />
|
||||
<stop
|
||||
offset={1}
|
||||
stopColor={inverted ? 'white' : 'black'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M1 16L2.58314 11.2506C1.83084 9.74642 1.63835 8.02363 2.04013 6.39052C2.4419 4.75741 3.41171 3.32057 4.776 2.33712C6.1403 1.35367 7.81003 0.887808 9.4864 1.02289C11.1628 1.15798 12.7364 1.8852 13.9256 3.07442C15.1148 4.26363 15.842 5.83723 15.9771 7.5136C16.1122 9.18997 15.6463 10.8597 14.6629 12.224C13.6794 13.5883 12.2426 14.5581 10.6095 14.9599C8.97637 15.3616 7.25358 15.1692 5.74942 14.4169L1 16Z"
|
||||
fill={inverted ? 'black' : 'white'}
|
||||
stroke={inverted ? 'black' : 'white'}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<mask
|
||||
id="mask0_91_2047"
|
||||
style={{ maskType: 'alpha' }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={1}
|
||||
y={0}
|
||||
width={16}
|
||||
height={16}
|
||||
>
|
||||
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
||||
</mask>
|
||||
<g mask="url(#mask0_91_2047)">
|
||||
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
||||
<path
|
||||
d="M14.2896 14.0018L7.146 4.8H5.80005V11.1973H6.87681V6.16743L13.4444 14.6529C13.7407 14.4545 14.0231 14.2369 14.2896 14.0018Z"
|
||||
fill={`url(#gradient-${id}-1)`}
|
||||
/>
|
||||
<rect
|
||||
x="11.2222"
|
||||
y="4.8"
|
||||
width="1.06667"
|
||||
height="6.4"
|
||||
fill={`url(#gradient-${id}-2)`}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function OpenAI({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn('w-4 h-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<title>OpenAI icon</title>
|
||||
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Vercel({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
aria-label="Vercel logomark"
|
||||
role="img"
|
||||
viewBox="0 0 74 64"
|
||||
className={cn('w-4 h-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M37.5896 0.25L74.5396 64.25H0.639648L37.5896 0.25Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function GitHub({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
className={cn('w-4 h-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<title>GitHub</title>
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
14
components/tailwind-indicator.tsx
Normal file
14
components/tailwind-indicator.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export function TailwindIndicator() {
|
||||
if (process.env.NODE_ENV === 'production') return null
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-1 left-1 z-50 flex h-6 w-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
|
||||
<div className="block sm:hidden">xs</div>
|
||||
<div className="hidden sm:block md:hidden">sm</div>
|
||||
<div className="hidden md:block lg:hidden">md</div>
|
||||
<div className="hidden lg:block xl:hidden">lg</div>
|
||||
<div className="hidden xl:block 2xl:hidden">xl</div>
|
||||
<div className="hidden 2xl:block">2xl</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ export function ThemeToggle() {
|
|||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
size="sm"
|
||||
className="p-0 leading-4 h-4 font-normal w-full"
|
||||
onClick={() => {
|
||||
startTransition(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react'
|
||||
import { VariantProps, cva } from 'class-variance-authority'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
|
|
@ -21,8 +22,7 @@ const buttonVariants = cva(
|
|||
size: {
|
||||
default: 'h-10 py-2 px-4',
|
||||
sm: 'h-9 px-3 rounded-md',
|
||||
lg: 'h-11 px-8 rounded-md',
|
||||
xs: 'h-6 px-3 rounded-md'
|
||||
lg: 'h-11 px-8 rounded-md'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
|
|
@ -34,12 +34,15 @@ const buttonVariants = cva(
|
|||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {}
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, ...props }, ref) => {
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button'
|
||||
return (
|
||||
<button
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
'use client'
|
||||
|
||||
import { signIn } from '@auth/nextjs/client'
|
||||
|
||||
export function Login() {
|
||||
return (
|
||||
<button
|
||||
className="inline-flex w-full items-center justify-center rounded border border-zinc-800 bg-white h-8 px-4 -my-1.5 text-sm leading-6 tracking-tight text-zinc-900 transition-colors ease-in-out hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onClick={() => signIn('github')}
|
||||
>
|
||||
<span className="font-medium">Login</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
import { useId } from 'react'
|
||||
|
||||
export function NextChatLogo(
|
||||
props: JSX.IntrinsicElements['svg'] & { inverted?: boolean }
|
||||
) {
|
||||
const id = useId()
|
||||
return (
|
||||
<svg
|
||||
width={17}
|
||||
height={17}
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`gradient-${id}-1`}
|
||||
x1="10.6889"
|
||||
y1="10.3556"
|
||||
x2="13.8445"
|
||||
y2="14.2667"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={props.inverted ? 'white' : 'black'} />
|
||||
<stop
|
||||
offset={1}
|
||||
stopColor={props.inverted ? 'white' : 'black'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id={`gradient-${id}-2`}
|
||||
x1="11.7555"
|
||||
y1="4.8"
|
||||
x2="11.7376"
|
||||
y2="9.50002"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={props.inverted ? 'white' : 'black'} />
|
||||
<stop
|
||||
offset={1}
|
||||
stopColor={props.inverted ? 'white' : 'black'}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M1 16L2.58314 11.2506C1.83084 9.74642 1.63835 8.02363 2.04013 6.39052C2.4419 4.75741 3.41171 3.32057 4.776 2.33712C6.1403 1.35367 7.81003 0.887808 9.4864 1.02289C11.1628 1.15798 12.7364 1.8852 13.9256 3.07442C15.1148 4.26363 15.842 5.83723 15.9771 7.5136C16.1122 9.18997 15.6463 10.8597 14.6629 12.224C13.6794 13.5883 12.2426 14.5581 10.6095 14.9599C8.97637 15.3616 7.25358 15.1692 5.74942 14.4169L1 16Z"
|
||||
fill={props.inverted ? 'black' : 'white'}
|
||||
stroke={props.inverted ? 'black' : 'white'}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<mask
|
||||
id="mask0_91_2047"
|
||||
style={{ maskType: 'alpha' }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={1}
|
||||
y={0}
|
||||
width={16}
|
||||
height={16}
|
||||
>
|
||||
<circle cx={9} cy={8} r={8} fill={props.inverted ? 'black' : 'white'} />
|
||||
</mask>
|
||||
<g mask="url(#mask0_91_2047)">
|
||||
<circle cx={9} cy={8} r={8} fill={props.inverted ? 'black' : 'white'} />
|
||||
<path
|
||||
d="M14.2896 14.0018L7.146 4.8H5.80005V11.1973H6.87681V6.16743L13.4444 14.6529C13.7407 14.4545 14.0231 14.2369 14.2896 14.0018Z"
|
||||
fill={`url(#gradient-${id}-1)`}
|
||||
/>
|
||||
<rect
|
||||
x="11.2222"
|
||||
y="4.8"
|
||||
width="1.06667"
|
||||
height="6.4"
|
||||
fill={`url(#gradient-${id}-2)`}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
NextChatLogo.displayName = 'DevGPT-logo'
|
||||
146
components/ui/sheet.tsx
Normal file
146
components/ui/sheet.tsx
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SheetPrimitive from '@radix-ui/react-dialog'
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { X } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger
|
||||
|
||||
const SheetClose = SheetPrimitive.Close
|
||||
|
||||
const SheetPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: SheetPrimitive.DialogPortalProps) => (
|
||||
<SheetPrimitive.Portal
|
||||
className={cn('fixed inset-0 z-50 flex', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SheetPrimitive.Portal>
|
||||
)
|
||||
SheetPortal.displayName = SheetPrimitive.Portal.displayName
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||
|
||||
const sheetVariants = cva(
|
||||
'fixed z-50 scale-100 gap-4 bg-background p-6 opacity-100 shadow-lg border h-full',
|
||||
{
|
||||
variants: {
|
||||
position: {
|
||||
left: 'data-[state=open]:animate-slide-from-left data-[state=closed]:animate-slide-to-left',
|
||||
right: 'animate-in slide-in-from-right h-full duration-300'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
position: 'left'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export interface DialogContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
DialogContentProps
|
||||
>(({ position, className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ position }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col space-y-2 text-center sm:text-left',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetHeader.displayName = 'SheetHeader'
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = 'SheetFooter'
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-lg font-semibold text-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription
|
||||
}
|
||||
24
components/ui/textarea.tsx
Normal file
24
components/ui/textarea.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = "Textarea"
|
||||
|
||||
export { Textarea }
|
||||
30
components/ui/tooltip.tsx
Normal file
30
components/ui/tooltip.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 overflow-hidden rounded-md border bg-popover font-medium px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
'use client'
|
||||
import { ThemeToggle } from '@/components/theme-toggle'
|
||||
import { signOut } from '@auth/nextjs/client'
|
||||
import { type Session } from '@auth/nextjs/types'
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||
import Image from 'next/image'
|
||||
|
||||
export interface UserMenuProps {
|
||||
session: Session
|
||||
}
|
||||
|
||||
export function UserMenu({ session }: UserMenuProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="focus:outline-none">
|
||||
{session.user?.image ? (
|
||||
<Image
|
||||
width={24}
|
||||
height={24}
|
||||
className="h-6 w-6 rounded-full select-none ring-zinc-100/10 ring-1 hover:opacity-80 transition-opacity duration-300"
|
||||
src={session.user?.image ? `${session.user.image}&s=60` : ''}
|
||||
alt={session.user.name ?? 'Avatar'}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gradient-to-r from-cyan-500 to-blue-500 p-2 select-none">
|
||||
<div
|
||||
className="font-medium uppercase text-zinc-100"
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
{session.user?.name ? session.user?.name.slice(0, 2) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="min-w-[200px] bg-white dark:bg-zinc-950 rounded-lg shadow-lg text-zinc-900 dark:text-zinc-400 overflow-hidden border border-transparent dark:border-zinc-700 focus:outline-none relative z-20 py-2"
|
||||
sideOffset={8}
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenu.Item className="py-2 px-3 focus:outline-none">
|
||||
<div className="text-xs font-medium">{session.user?.name}</div>
|
||||
<div className="text-xs text-zinc-500">{session.user?.email}</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-[1px] bg-zinc-100 dark:bg-zinc-800" />
|
||||
<DropdownMenu.Item className="py-2 px-3 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none">
|
||||
<ThemeToggle />
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-[1px] bg-zinc-100 dark:bg-zinc-800" />
|
||||
<DropdownMenu.Item className="py-2 px-3 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none ">
|
||||
<a
|
||||
href="https://vercel.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-between w-full"
|
||||
>
|
||||
<span>Vercel Homepage</span>
|
||||
<span>
|
||||
<svg
|
||||
fill="none"
|
||||
height={16}
|
||||
shapeRendering="geometricPrecision"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width={16}
|
||||
aria-hidden="true"
|
||||
className="mr-1"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" />
|
||||
<path d="M15 3h6v6" />
|
||||
<path d="M10 14L21 3" />
|
||||
</svg>
|
||||
</span>
|
||||
</a>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="py-2 px-3 hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-200 cursor-pointer text-xs focus:outline-none"
|
||||
onClick={() => signOut()}
|
||||
>
|
||||
Log Out
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
UserMenu.displayName = 'UserMenu'
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { cn } from '@/lib/utils'
|
||||
|
||||
export const VercelLogo = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
height={22}
|
||||
viewBox="0 0 235 203"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Vercel Logo"
|
||||
className={cn(className, 'dark:fill-white fill-black')}
|
||||
>
|
||||
<path d="M117.082 0L234.164 202.794H0L117.082 0Z" fill="currentColor" />
|
||||
</svg>
|
||||
)
|
||||
29
components/user-menu.tsx
Normal file
29
components/user-menu.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
'use client'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { signIn } from '@auth/nextjs/client'
|
||||
import { type Session } from '@auth/nextjs/types'
|
||||
|
||||
export interface UserMenuProps {
|
||||
session: Session
|
||||
}
|
||||
|
||||
export function UserMenu({ session }: UserMenuProps) {
|
||||
if (!session) {
|
||||
return (
|
||||
<Button variant="ghost" size="sm" onClick={() => signIn('github')}>
|
||||
Login
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
{session?.user && (
|
||||
<Button variant="link" size="sm">
|
||||
{session.user?.name}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue