Listen to and share the music in the Atmosphere. musicsky.up.railway.app/
nextjs atproto music typescript react
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add theme toggle

enough of light mode burning my eyes at night

+329 -2
+11
package-lock.json
··· 19 19 "lucide-react": "^0.575.0", 20 20 "multiformats": "^13.4.2", 21 21 "next": "16.1.6", 22 + "next-themes": "^0.4.6", 22 23 "radix-ui": "^1.4.3", 23 24 "react": "19.2.3", 24 25 "react-dom": "19.2.3", ··· 9613 9614 "sass": { 9614 9615 "optional": true 9615 9616 } 9617 + } 9618 + }, 9619 + "node_modules/next-themes": { 9620 + "version": "0.4.6", 9621 + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", 9622 + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", 9623 + "license": "MIT", 9624 + "peerDependencies": { 9625 + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", 9626 + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" 9616 9627 } 9617 9628 }, 9618 9629 "node_modules/next/node_modules/postcss": {
+1
package.json
··· 22 22 "lucide-react": "^0.575.0", 23 23 "multiformats": "^13.4.2", 24 24 "next": "16.1.6", 25 + "next-themes": "^0.4.6", 25 26 "radix-ui": "^1.4.3", 26 27 "react": "19.2.3", 27 28 "react-dom": "19.2.3",
+10 -2
src/app/layout.tsx
··· 1 1 import type { Metadata } from "next"; 2 + import { ThemeProvider } from "@/components/theme-provider"; 2 3 import { Geist, Geist_Mono } from "next/font/google"; 3 4 import "./globals.css"; 4 5 ··· 23 24 children: React.ReactNode; 24 25 }>) { 25 26 return ( 26 - <html lang="en"> 27 + <html lang="en" suppressHydrationWarning> 27 28 <body 28 29 className={`${geistSans.variable} ${geistMono.variable} antialiased`} 29 30 > 30 31 <div className="flex min-h-screen items-center justify-center bg-background"> 31 - {children} 32 + <ThemeProvider 33 + attribute="class" 34 + defaultTheme="system" 35 + enableSystem 36 + disableTransitionOnChange 37 + > 38 + {children} 39 + </ThemeProvider> 32 40 </div> 33 41 </body> 34 42 </html>
+11
src/components/theme-provider.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 + 6 + export function ThemeProvider({ 7 + children, 8 + ...props 9 + }: React.ComponentProps<typeof NextThemesProvider>) { 10 + return <NextThemesProvider {...props}>{children}</NextThemesProvider>; 11 + }
+39
src/components/theme-toggle.tsx
··· 1 + "use client"; 2 + 3 + import { Moon, Sun } from "lucide-react"; 4 + import { useTheme } from "next-themes"; 5 + 6 + import { Button } from "@/components/ui/button"; 7 + import { 8 + DropdownMenu, 9 + DropdownMenuContent, 10 + DropdownMenuItem, 11 + DropdownMenuTrigger, 12 + } from "@/components/ui/dropdown-menu"; 13 + 14 + export function ThemeToggle() { 15 + const { setTheme } = useTheme(); 16 + 17 + return ( 18 + <DropdownMenu> 19 + <DropdownMenuTrigger asChild> 20 + <Button variant="outline" size="icon"> 21 + <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" /> 22 + <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" /> 23 + <span className="sr-only">Toggle theme</span> 24 + </Button> 25 + </DropdownMenuTrigger> 26 + <DropdownMenuContent align="end"> 27 + <DropdownMenuItem onClick={() => setTheme("light")}> 28 + Light 29 + </DropdownMenuItem> 30 + <DropdownMenuItem onClick={() => setTheme("dark")}> 31 + Dark 32 + </DropdownMenuItem> 33 + <DropdownMenuItem onClick={() => setTheme("system")}> 34 + System 35 + </DropdownMenuItem> 36 + </DropdownMenuContent> 37 + </DropdownMenu> 38 + ); 39 + }
+257
src/components/ui/dropdown-menu.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; 5 + import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"; 6 + 7 + import { cn } from "@/lib/utils"; 8 + 9 + function DropdownMenu({ 10 + ...props 11 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { 12 + return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />; 13 + } 14 + 15 + function DropdownMenuPortal({ 16 + ...props 17 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { 18 + return ( 19 + <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} /> 20 + ); 21 + } 22 + 23 + function DropdownMenuTrigger({ 24 + ...props 25 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { 26 + return ( 27 + <DropdownMenuPrimitive.Trigger 28 + data-slot="dropdown-menu-trigger" 29 + {...props} 30 + /> 31 + ); 32 + } 33 + 34 + function DropdownMenuContent({ 35 + className, 36 + sideOffset = 4, 37 + ...props 38 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) { 39 + return ( 40 + <DropdownMenuPrimitive.Portal> 41 + <DropdownMenuPrimitive.Content 42 + data-slot="dropdown-menu-content" 43 + sideOffset={sideOffset} 44 + className={cn( 45 + "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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", 46 + className, 47 + )} 48 + {...props} 49 + /> 50 + </DropdownMenuPrimitive.Portal> 51 + ); 52 + } 53 + 54 + function DropdownMenuGroup({ 55 + ...props 56 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { 57 + return ( 58 + <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} /> 59 + ); 60 + } 61 + 62 + function DropdownMenuItem({ 63 + className, 64 + inset, 65 + variant = "default", 66 + ...props 67 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { 68 + inset?: boolean; 69 + variant?: "default" | "destructive"; 70 + }) { 71 + return ( 72 + <DropdownMenuPrimitive.Item 73 + data-slot="dropdown-menu-item" 74 + data-inset={inset} 75 + data-variant={variant} 76 + className={cn( 77 + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 78 + className, 79 + )} 80 + {...props} 81 + /> 82 + ); 83 + } 84 + 85 + function DropdownMenuCheckboxItem({ 86 + className, 87 + children, 88 + checked, 89 + ...props 90 + }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) { 91 + return ( 92 + <DropdownMenuPrimitive.CheckboxItem 93 + data-slot="dropdown-menu-checkbox-item" 94 + className={cn( 95 + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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", 96 + className, 97 + )} 98 + checked={checked} 99 + {...props} 100 + > 101 + <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> 102 + <DropdownMenuPrimitive.ItemIndicator> 103 + <CheckIcon className="size-4" /> 104 + </DropdownMenuPrimitive.ItemIndicator> 105 + </span> 106 + {children} 107 + </DropdownMenuPrimitive.CheckboxItem> 108 + ); 109 + } 110 + 111 + function DropdownMenuRadioGroup({ 112 + ...props 113 + }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { 114 + return ( 115 + <DropdownMenuPrimitive.RadioGroup 116 + data-slot="dropdown-menu-radio-group" 117 + {...props} 118 + /> 119 + ); 120 + } 121 + 122 + function DropdownMenuRadioItem({ 123 + className, 124 + children, 125 + ...props 126 + }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) { 127 + return ( 128 + <DropdownMenuPrimitive.RadioItem 129 + data-slot="dropdown-menu-radio-item" 130 + className={cn( 131 + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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", 132 + className, 133 + )} 134 + {...props} 135 + > 136 + <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> 137 + <DropdownMenuPrimitive.ItemIndicator> 138 + <CircleIcon className="size-2 fill-current" /> 139 + </DropdownMenuPrimitive.ItemIndicator> 140 + </span> 141 + {children} 142 + </DropdownMenuPrimitive.RadioItem> 143 + ); 144 + } 145 + 146 + function DropdownMenuLabel({ 147 + className, 148 + inset, 149 + ...props 150 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { 151 + inset?: boolean; 152 + }) { 153 + return ( 154 + <DropdownMenuPrimitive.Label 155 + data-slot="dropdown-menu-label" 156 + data-inset={inset} 157 + className={cn( 158 + "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", 159 + className, 160 + )} 161 + {...props} 162 + /> 163 + ); 164 + } 165 + 166 + function DropdownMenuSeparator({ 167 + className, 168 + ...props 169 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { 170 + return ( 171 + <DropdownMenuPrimitive.Separator 172 + data-slot="dropdown-menu-separator" 173 + className={cn("bg-border -mx-1 my-1 h-px", className)} 174 + {...props} 175 + /> 176 + ); 177 + } 178 + 179 + function DropdownMenuShortcut({ 180 + className, 181 + ...props 182 + }: React.ComponentProps<"span">) { 183 + return ( 184 + <span 185 + data-slot="dropdown-menu-shortcut" 186 + className={cn( 187 + "text-muted-foreground ml-auto text-xs tracking-widest", 188 + className, 189 + )} 190 + {...props} 191 + /> 192 + ); 193 + } 194 + 195 + function DropdownMenuSub({ 196 + ...props 197 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { 198 + return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />; 199 + } 200 + 201 + function DropdownMenuSubTrigger({ 202 + className, 203 + inset, 204 + children, 205 + ...props 206 + }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { 207 + inset?: boolean; 208 + }) { 209 + return ( 210 + <DropdownMenuPrimitive.SubTrigger 211 + data-slot="dropdown-menu-sub-trigger" 212 + data-inset={inset} 213 + className={cn( 214 + "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 215 + className, 216 + )} 217 + {...props} 218 + > 219 + {children} 220 + <ChevronRightIcon className="ml-auto size-4" /> 221 + </DropdownMenuPrimitive.SubTrigger> 222 + ); 223 + } 224 + 225 + function DropdownMenuSubContent({ 226 + className, 227 + ...props 228 + }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) { 229 + return ( 230 + <DropdownMenuPrimitive.SubContent 231 + data-slot="dropdown-menu-sub-content" 232 + className={cn( 233 + "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 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", 234 + className, 235 + )} 236 + {...props} 237 + /> 238 + ); 239 + } 240 + 241 + export { 242 + DropdownMenu, 243 + DropdownMenuPortal, 244 + DropdownMenuTrigger, 245 + DropdownMenuContent, 246 + DropdownMenuGroup, 247 + DropdownMenuLabel, 248 + DropdownMenuItem, 249 + DropdownMenuCheckboxItem, 250 + DropdownMenuRadioGroup, 251 + DropdownMenuRadioItem, 252 + DropdownMenuSeparator, 253 + DropdownMenuShortcut, 254 + DropdownMenuSub, 255 + DropdownMenuSubTrigger, 256 + DropdownMenuSubContent, 257 + };