Your calm window into the Atmosphere. morgen.blue
rss atproto
3
fork

Configure Feed

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

refactor(layout): account dropdown + propagate level context

Replace the standalone settings icon with an account dropdown on the
user icon (Settings + Log out). Soften the popover surface to a level-2
border + lighter shadow so it reads as a card-on-card surface. Have
Window and DropdownMenuContent publish their level into LevelContext so
nested controls pick the right contrast tones by intent rather than by
the default value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+62 -20
+6 -2
resources/js/components/ui/dropdown-menu.tsx
··· 1 1 import * as React from "react" 2 2 import { Menu as MenuPrimitive } from "@base-ui/react/menu" 3 3 4 + import { LevelContext } from "@/lib/level-context" 4 5 import { cn } from "@/lib/utils" 5 6 import { HugeiconsIcon } from "@hugeicons/react" 6 7 import { ArrowRight01Icon, Tick02Icon } from "@hugeicons/core-free-icons" ··· 23 24 side = "bottom", 24 25 sideOffset = 4, 25 26 className, 27 + children, 26 28 ...props 27 29 }: MenuPrimitive.Popup.Props & 28 30 Pick< ··· 40 42 > 41 43 <MenuPrimitive.Popup 42 44 data-slot="dropdown-menu-content" 43 - className={cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", className )} 45 + className={cn("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-gray-100 bg-popover p-1 text-popover-foreground shadow-sm duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95 dark:border-gray-700", className )} 44 46 {...props} 45 - /> 47 + > 48 + <LevelContext.Provider value={2}>{children}</LevelContext.Provider> 49 + </MenuPrimitive.Popup> 46 50 </MenuPrimitive.Positioner> 47 51 </MenuPrimitive.Portal> 48 52 )
+43 -8
resources/js/components/window-chrome.tsx
··· 1 1 import { 2 + LogoutSquare01Icon, 2 3 PlusSignIcon, 3 4 Settings03Icon, 4 5 UserCircleIcon, 5 6 } from '@hugeicons/core-free-icons'; 6 7 import { HugeiconsIcon } from '@hugeicons/react'; 7 - import { Link, usePage } from '@inertiajs/react'; 8 + import { Link, router, usePage } from '@inertiajs/react'; 8 9 9 10 import { Button } from '@/components/ui/button'; 11 + import { 12 + DropdownMenu, 13 + DropdownMenuContent, 14 + DropdownMenuItem, 15 + DropdownMenuTrigger, 16 + } from '@/components/ui/dropdown-menu'; 10 17 import { cn } from '@/lib/utils'; 11 - import { consume, create, discover } from '@/routes'; 18 + import { consume, create, discover, logout } from '@/routes'; 19 + import { edit as editAppearance } from '@/routes/appearance'; 12 20 13 21 type Tab = { 14 22 label: string; ··· 24 32 export function WindowChrome() { 25 33 const { url } = usePage(); 26 34 35 + const handleLogout = () => { 36 + router.flushAll(); 37 + router.post(logout().url); 38 + }; 39 + 27 40 return ( 28 41 <header className="flex h-14 shrink-0 items-center justify-between px-20"> 29 42 <nav className="flex items-center gap-6"> ··· 57 70 <Button variant="ghost" size="icon-sm" aria-label="Add source"> 58 71 <HugeiconsIcon icon={PlusSignIcon} className="size-5" /> 59 72 </Button> 60 - <Button variant="ghost" size="icon-sm" aria-label="Profile"> 61 - <HugeiconsIcon icon={UserCircleIcon} className="size-5" /> 62 - </Button> 63 - <Button variant="ghost" size="icon-sm" aria-label="Settings"> 64 - <HugeiconsIcon icon={Settings03Icon} className="size-5" /> 65 - </Button> 73 + <DropdownMenu> 74 + <DropdownMenuTrigger 75 + render={ 76 + <Button 77 + variant="ghost" 78 + size="icon-sm" 79 + aria-label="Account" 80 + /> 81 + } 82 + > 83 + <HugeiconsIcon 84 + icon={UserCircleIcon} 85 + className="size-5" 86 + /> 87 + </DropdownMenuTrigger> 88 + <DropdownMenuContent align="end" className="w-44"> 89 + <DropdownMenuItem 90 + render={<Link href={editAppearance().url} />} 91 + > 92 + <HugeiconsIcon icon={Settings03Icon} /> 93 + Settings 94 + </DropdownMenuItem> 95 + <DropdownMenuItem onClick={handleLogout}> 96 + <HugeiconsIcon icon={LogoutSquare01Icon} /> 97 + Log out 98 + </DropdownMenuItem> 99 + </DropdownMenuContent> 100 + </DropdownMenu> 66 101 </div> 67 102 </header> 68 103 );
+13 -10
resources/js/components/window.tsx
··· 1 1 import type { ReactNode } from 'react'; 2 + import { LevelContext } from '@/lib/level-context'; 2 3 import { cn } from '@/lib/utils'; 3 4 4 5 type WindowVariant = 'plain' | 'sunrise'; ··· 20 21 className, 21 22 }: WindowProps) { 22 23 return ( 23 - <div 24 - data-slot="window" 25 - className={cn( 26 - 'overflow-hidden rounded-tl-[4rem] rounded-tr-[4rem] rounded-br-[0.5rem] rounded-bl-[0.5rem]', 27 - VARIANT_STYLES[variant], 28 - className, 29 - )} 30 - > 31 - {children} 32 - </div> 24 + <LevelContext.Provider value={1}> 25 + <div 26 + data-slot="window" 27 + className={cn( 28 + 'overflow-hidden rounded-tl-[4rem] rounded-tr-[4rem] rounded-br-[0.5rem] rounded-bl-[0.5rem]', 29 + VARIANT_STYLES[variant], 30 + className, 31 + )} 32 + > 33 + {children} 34 + </div> 35 + </LevelContext.Provider> 33 36 ); 34 37 }