diff options
| author | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 18:35:09 -0500 |
|---|---|---|
| committer | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 18:35:09 -0500 |
| commit | 4bc03002e17a0d128059c9d6d76c91ba7bac2e8f (patch) | |
| tree | 59687bb0e83a02f07121f8f34820fcbffb786ef6 /src | |
| parent | 61201c28646ccb3be155b84b2a95cbc4502e51e4 (diff) | |
Dark mode
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/globals.css | 2 | ||||
| -rw-r--r-- | src/app/groups/[groupId]/balances-list.tsx | 4 | ||||
| -rw-r--r-- | src/app/layout.tsx | 60 | ||||
| -rw-r--r-- | src/components/theme-provider.tsx | 8 | ||||
| -rw-r--r-- | src/components/theme-toggle.tsx | 39 | ||||
| -rw-r--r-- | src/components/ui/dropdown-menu.tsx | 200 |
6 files changed, 286 insertions, 27 deletions
diff --git a/src/app/globals.css b/src/app/globals.css index a3d838f..bb707bc 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -33,7 +33,7 @@ --card-foreground: 0 0% 95%; --popover: 0 0% 9%; --popover-foreground: 0 0% 95%; - --primary: 142.1 70.6% 45.3%; + --primary: 161 90% 45%; --primary-foreground: 144.9 80.4% 10%; --secondary: 240 3.7% 15.9%; --secondary-foreground: 0 0% 98%; diff --git a/src/app/groups/[groupId]/balances-list.tsx b/src/app/groups/[groupId]/balances-list.tsx index 0e321bb..5e7b81b 100644 --- a/src/app/groups/[groupId]/balances-list.tsx +++ b/src/app/groups/[groupId]/balances-list.tsx @@ -35,8 +35,8 @@ export function BalancesList({ balances, participants, currency }: Props) { className={cn( 'absolute top-1 h-7 z-10', isLeft - ? 'bg-green-200 left-0 rounded-r-lg border border-green-300' - : 'bg-red-200 right-0 rounded-l-lg border border-red-300', + ? 'bg-green-200 dark:bg-green-800 left-0 rounded-r-lg border border-green-300 dark:border-green-700' + : 'bg-red-200 dark:bg-red-800 right-0 rounded-l-lg border border-red-300 dark:border-red-700', )} style={{ width: (Math.abs(balance) / maxBalance) * 100 + '%', diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 799f8c5..8943688 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,6 @@ import { ProgressBar } from '@/components/progress-bar' +import { ThemeProvider } from '@/components/theme-provider' +import { ThemeToggle } from '@/components/theme-toggle' import { Button } from '@/components/ui/button' import { env } from '@/lib/env' import type { Metadata } from 'next' @@ -39,30 +41,40 @@ export default function RootLayout({ children: React.ReactNode }) { return ( - <html lang="en"> - <body className="bg-slate-50"> - <ProgressBar /> - <header className="fixed top-0 left-0 right-0 flex justify-between bg-white bg-opacity-50 p-2 border-b backdrop-blur-sm"> - <Link className="flex items-center gap-2" href="/"> - <h1> - <Image - src="/logo-with-text.png" - className="m-1" - width={(35 * 522) / 180} - height={35} - alt="Spliit" - /> - </h1> - </Link> - <ul role="nav" className="flex items-center text-sm"> - <li> - <Button variant="link" asChild className="-my-3"> - <Link href="/groups">Groups</Link> - </Button> - </li> - </ul> - </header> - <div className="max-w-screen-md mx-auto p-4 pt-20">{children}</div> + <html lang="en" suppressHydrationWarning> + <body className="light:bg-slate-50"> + <ThemeProvider + attribute="class" + defaultTheme="system" + enableSystem + disableTransitionOnChange + > + <ProgressBar /> + <header className="fixed top-0 left-0 right-0 flex justify-between bg-white dark:bg-slate-800 bg-opacity-50 dark:bg-opacity-50 p-2 border-b backdrop-blur-sm"> + <Link className="flex items-center gap-2" href="/"> + <h1> + <Image + src="/logo-with-text.png" + className="m-1" + width={(35 * 522) / 180} + height={35} + alt="Spliit" + /> + </h1> + </Link> + <ul role="nav" className="flex items-center text-sm"> + <li> + <Button variant="link" asChild className="-my-3"> + <Link href="/groups">Groups</Link> + </Button> + </li> + <li> + <ThemeToggle /> + </li> + </ul> + </header> + <div className="max-w-screen-md mx-auto p-4 pt-20">{children}</div> + </ThemeProvider> </body> </html> ) diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx new file mode 100644 index 0000000..cb8c4c3 --- /dev/null +++ b/src/components/theme-provider.tsx @@ -0,0 +1,8 @@ +'use client' + +import { ThemeProvider as NextThemesProvider } from 'next-themes' +import { type ThemeProviderProps } from 'next-themes/dist/types' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return <NextThemesProvider {...props}>{children}</NextThemesProvider> +} diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx new file mode 100644 index 0000000..bf45ffc --- /dev/null +++ b/src/components/theme-toggle.tsx @@ -0,0 +1,39 @@ +'use client' + +import { MoonIcon, SunIcon } from '@radix-ui/react-icons' +import { useTheme } from 'next-themes' + +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' + +export function ThemeToggle() { + const { setTheme } = useTheme() + + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button variant="link" size="icon"> + <SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> + <MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> + <span className="sr-only">Toggle theme</span> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuItem onClick={() => setTheme('light')}> + Light + </DropdownMenuItem> + <DropdownMenuItem onClick={() => setTheme('dark')}> + Dark + </DropdownMenuItem> + <DropdownMenuItem onClick={() => setTheme('system')}> + System + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ) +} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..f69a0d6 --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,200 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + <DropdownMenuPrimitive.SubTrigger + ref={ref} + className={cn( + "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent", + inset && "pl-8", + className + )} + {...props} + > + {children} + <ChevronRight className="ml-auto h-4 w-4" /> + </DropdownMenuPrimitive.SubTrigger> +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> +>(({ className, ...props }, ref) => ( + <DropdownMenuPrimitive.SubContent + ref={ref} + className={cn( + "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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", + className + )} + {...props} + /> +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> +>(({ className, sideOffset = 4, ...props }, ref) => ( + <DropdownMenuPrimitive.Portal> + <DropdownMenuPrimitive.Content + ref={ref} + sideOffset={sideOffset} + className={cn( + "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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", + className + )} + {...props} + /> + </DropdownMenuPrimitive.Portal> +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.Item>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + <DropdownMenuPrimitive.Item + ref={ref} + className={cn( + "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + inset && "pl-8", + className + )} + {...props} + /> +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> +>(({ className, children, checked, ...props }, ref) => ( + <DropdownMenuPrimitive.CheckboxItem + ref={ref} + className={cn( + "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + className + )} + checked={checked} + {...props} + > + <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> + <DropdownMenuPrimitive.ItemIndicator> + <Check className="h-4 w-4" /> + </DropdownMenuPrimitive.ItemIndicator> + </span> + {children} + </DropdownMenuPrimitive.CheckboxItem> +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> +>(({ className, children, ...props }, ref) => ( + <DropdownMenuPrimitive.RadioItem + ref={ref} + className={cn( + "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", + className + )} + {...props} + > + <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> + <DropdownMenuPrimitive.ItemIndicator> + <Circle className="h-2 w-2 fill-current" /> + </DropdownMenuPrimitive.ItemIndicator> + </span> + {children} + </DropdownMenuPrimitive.RadioItem> +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.Label>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + <DropdownMenuPrimitive.Label + ref={ref} + className={cn( + "px-2 py-1.5 text-sm font-semibold", + inset && "pl-8", + className + )} + {...props} + /> +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef<typeof DropdownMenuPrimitive.Separator>, + React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> +>(({ className, ...props }, ref) => ( + <DropdownMenuPrimitive.Separator + ref={ref} + className={cn("-mx-1 my-1 h-px bg-muted", className)} + {...props} + /> +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes<HTMLSpanElement>) => { + return ( + <span + className={cn("ml-auto text-xs tracking-widest opacity-60", className)} + {...props} + /> + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} |
