-
Notifications
You must be signed in to change notification settings - Fork 468
fix(ui): User Panel rough in updates #9482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5caec63
e9a1c6d
af9ac32
e3d10fa
3fd96c2
ea35017
c42a87a
20dc863
eb7a34d
9df9ba3
819681b
83ef857
28f2e21
03867ff
16845f5
35cf642
a13a334
a505f3c
874df6c
8adb223
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,13 +7,13 @@ export interface CompositionPiece { | |||||||||||||||
| name: string; | ||||||||||||||||
| /** Route to the piece's page in swingset (e.g. `/components/button`). */ | ||||||||||||||||
| href: string; | ||||||||||||||||
| /** Which Mosaic layer the piece lives in (e.g. `User`, `Components`, `Primitives`). */ | ||||||||||||||||
| /** Which Mosaic layer the piece lives in (e.g. `User Button`, `Components`, `Primitives`). */ | ||||||||||||||||
| layer: string; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Mosaic layers, high → low. Drives the order the composition groups render in. | ||||||||||||||||
| // Matches the sidebar group names. | ||||||||||||||||
| const LAYER_ORDER = ['User', 'Components', 'Styles', 'Primitives']; | ||||||||||||||||
| const LAYER_ORDER = ['User Button', 'User Profile', 'Components', 'Styles', 'Primitives']; | ||||||||||||||||
|
Comment on lines
14
to
+16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Include
Proposed fix-const LAYER_ORDER = ['User Button', 'User Profile', 'Components', 'Styles', 'Primitives'];
+const LAYER_ORDER = ['User Button', 'User Profile', 'Components', 'Styles', 'Primitives', 'Hooks'];📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
|
|
||||||||||||||||
| function layerRank(layer: string): number { | ||||||||||||||||
| const i = LAYER_ORDER.indexOf(layer); | ||||||||||||||||
|
|
||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,11 @@ | ||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import { ChevronRightIcon } from 'lucide-react'; | ||||||||||||||||||||||||
| import Link from 'next/link'; | ||||||||||||||||||||||||
| import { usePathname } from 'next/navigation'; | ||||||||||||||||||||||||
| import * as React from 'react'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; | ||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||
| Sidebar, | ||||||||||||||||||||||||
| SidebarContent, | ||||||||||||||||||||||||
|
|
@@ -15,11 +17,116 @@ import { | |||||||||||||||||||||||
| SidebarMenuButton, | ||||||||||||||||||||||||
| SidebarMenuItem, | ||||||||||||||||||||||||
| SidebarRail, | ||||||||||||||||||||||||
| SidebarSeparator, | ||||||||||||||||||||||||
| } from '@/components/ui/sidebar'; | ||||||||||||||||||||||||
| import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; | ||||||||||||||||||||||||
| import { getSidebarGroups } from '@/lib/registry'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const groups = getSidebarGroups(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const COLLAPSED_BY_DEFAULT = new Set(['Primitives', 'Components', 'Styles', 'Hooks']); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| type SidebarEntry = ReturnType<typeof getSidebarGroups>[number]['components'][number]; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Partitions a group's entries by `meta.navigation.category` into subheaded runs. Category and | ||||||||||||||||||||||||
| // entry order both follow first appearance in the registry; uncategorized entries get no subheading. | ||||||||||||||||||||||||
| function byCategory(components: SidebarEntry[]) { | ||||||||||||||||||||||||
| const categories: { category: string; components: SidebarEntry[] }[] = []; | ||||||||||||||||||||||||
| for (const component of components) { | ||||||||||||||||||||||||
| const category = component.mod.meta.navigation?.category ?? ''; | ||||||||||||||||||||||||
| const bucket = categories.find(c => c.category === category); | ||||||||||||||||||||||||
| if (bucket) { | ||||||||||||||||||||||||
| bucket.components.push(component); | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| categories.push({ category, components: [component] }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| return categories; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function SidebarUsageItem({ usage, href, isActive }: { usage: string; href: string; isActive: boolean }) { | ||||||||||||||||||||||||
| const labelRef = React.useRef<HTMLSpanElement>(null); | ||||||||||||||||||||||||
| const [isTruncated, setIsTruncated] = React.useState(false); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| React.useEffect(() => { | ||||||||||||||||||||||||
| const label = labelRef.current; | ||||||||||||||||||||||||
| if (!label) { | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| const check = () => setIsTruncated(label.scrollWidth > label.clientWidth); | ||||||||||||||||||||||||
| check(); | ||||||||||||||||||||||||
| const observer = new ResizeObserver(check); | ||||||||||||||||||||||||
| observer.observe(label); | ||||||||||||||||||||||||
| return () => observer.disconnect(); | ||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <SidebarMenuItem> | ||||||||||||||||||||||||
| <Tooltip disabled={!isTruncated}> | ||||||||||||||||||||||||
| <TooltipTrigger | ||||||||||||||||||||||||
| delay={300} | ||||||||||||||||||||||||
| render={ | ||||||||||||||||||||||||
| <SidebarMenuButton | ||||||||||||||||||||||||
| className='h-auto py-1 text-xs' | ||||||||||||||||||||||||
| isActive={isActive} | ||||||||||||||||||||||||
| render={<Link href={href} />} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||
| ref={labelRef} | ||||||||||||||||||||||||
| className='truncate font-mono text-[10px] leading-relaxed' | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| {usage} | ||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||
| </SidebarMenuButton> | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| <TooltipContent | ||||||||||||||||||||||||
| side='right' | ||||||||||||||||||||||||
| className='font-mono text-[10px]' | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| {usage} | ||||||||||||||||||||||||
| </TooltipContent> | ||||||||||||||||||||||||
| </Tooltip> | ||||||||||||||||||||||||
| </SidebarMenuItem> | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function SidebarEntryMenu({ | ||||||||||||||||||||||||
| components, | ||||||||||||||||||||||||
| groupSlug, | ||||||||||||||||||||||||
| pathname, | ||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||
| components: SidebarEntry[]; | ||||||||||||||||||||||||
| groupSlug: string; | ||||||||||||||||||||||||
| pathname: string; | ||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <SidebarMenu> | ||||||||||||||||||||||||
| {components.map(({ mod, componentSlug }) => { | ||||||||||||||||||||||||
| const href = `/${groupSlug}/${componentSlug}`; | ||||||||||||||||||||||||
| // How an entry is USED differs by layer, so the label follows the layer rather | ||||||||||||||||||||||||
| // than a guess at the title: hooks are called, atomic styles are a set of | ||||||||||||||||||||||||
| // exports with no single call form worth privileging, and everything else is a | ||||||||||||||||||||||||
| // component rendered as JSX. | ||||||||||||||||||||||||
| const usage = | ||||||||||||||||||||||||
| mod.meta.group === 'Hooks' | ||||||||||||||||||||||||
| ? `${mod.meta.title}()` | ||||||||||||||||||||||||
| : mod.meta.group === 'Styles' | ||||||||||||||||||||||||
| ? mod.meta.title | ||||||||||||||||||||||||
| : `<${mod.meta.title} />`; | ||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <SidebarUsageItem | ||||||||||||||||||||||||
| key={mod.meta.title} | ||||||||||||||||||||||||
| usage={usage} | ||||||||||||||||||||||||
| href={href} | ||||||||||||||||||||||||
| isActive={pathname === href} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||
| </SidebarMenu> | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { | ||||||||||||||||||||||||
| const pathname = usePathname(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -59,45 +166,69 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { | |||||||||||||||||||||||
| </SidebarHeader> | ||||||||||||||||||||||||
| <SidebarContent className='gap-0'> | ||||||||||||||||||||||||
| {groups.map(({ group, groupSlug, components }) => ( | ||||||||||||||||||||||||
| <SidebarGroup | ||||||||||||||||||||||||
| key={group} | ||||||||||||||||||||||||
| className='py-1' | ||||||||||||||||||||||||
| data-section={group} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| <SidebarGroupLabel className='text-sidebar-foreground/50 h-auto px-2 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-wider'> | ||||||||||||||||||||||||
| {group} | ||||||||||||||||||||||||
| </SidebarGroupLabel> | ||||||||||||||||||||||||
| <SidebarGroupContent> | ||||||||||||||||||||||||
| <SidebarMenu> | ||||||||||||||||||||||||
| {components.map(({ mod, componentSlug }) => { | ||||||||||||||||||||||||
| const href = `/${groupSlug}/${componentSlug}`; | ||||||||||||||||||||||||
| // How an entry is USED differs by layer, so the label follows the layer rather | ||||||||||||||||||||||||
| // than a guess at the title: hooks are called, atomic styles are a set of | ||||||||||||||||||||||||
| // exports with no single call form worth privileging, and everything else is a | ||||||||||||||||||||||||
| // component rendered as JSX. | ||||||||||||||||||||||||
| const usage = | ||||||||||||||||||||||||
| mod.meta.group === 'Hooks' | ||||||||||||||||||||||||
| ? `${mod.meta.title}()` | ||||||||||||||||||||||||
| : mod.meta.group === 'Styles' | ||||||||||||||||||||||||
| ? mod.meta.title | ||||||||||||||||||||||||
| : `<${mod.meta.title} />`; | ||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <SidebarMenuItem key={mod.meta.title}> | ||||||||||||||||||||||||
| <SidebarMenuButton | ||||||||||||||||||||||||
| className='h-auto items-start py-1 text-xs leading-relaxed' | ||||||||||||||||||||||||
| isActive={pathname === href} | ||||||||||||||||||||||||
| render={<Link href={href} />} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| <span className='whitespace-normal! break-all font-mono text-[10px] leading-relaxed'> | ||||||||||||||||||||||||
| {usage} | ||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||
| </SidebarMenuButton> | ||||||||||||||||||||||||
| </SidebarMenuItem> | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||
| </SidebarMenu> | ||||||||||||||||||||||||
| </SidebarGroupContent> | ||||||||||||||||||||||||
| </SidebarGroup> | ||||||||||||||||||||||||
| <React.Fragment key={group}> | ||||||||||||||||||||||||
| {group === 'Components' && <SidebarSeparator className='data-horizontal:w-auto my-1' />} | ||||||||||||||||||||||||
| <Collapsible | ||||||||||||||||||||||||
| defaultOpen={!COLLAPSED_BY_DEFAULT.has(group)} | ||||||||||||||||||||||||
| className='group/collapsible' | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
|
Comment on lines
+171
to
+174
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Keep the active group open on initial navigation. On Proposed fix <Collapsible
- defaultOpen={!COLLAPSED_BY_DEFAULT.has(group)}
+ defaultOpen={
+ !COLLAPSED_BY_DEFAULT.has(group) ||
+ components.some(({ componentSlug }) => pathname === `/${groupSlug}/${componentSlug}`)
+ }
className='group/collapsible'
>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| <SidebarGroup | ||||||||||||||||||||||||
| className='py-1' | ||||||||||||||||||||||||
| data-section={group} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| <SidebarGroupLabel | ||||||||||||||||||||||||
| className='text-sidebar-foreground/50 hover:text-sidebar-foreground/80 h-auto w-full px-2 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-wider' | ||||||||||||||||||||||||
| render={<CollapsibleTrigger />} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| {group} | ||||||||||||||||||||||||
| <ChevronRightIcon className='size-3! ml-auto transition-transform group-data-[open]/collapsible:rotate-90' /> | ||||||||||||||||||||||||
| </SidebarGroupLabel> | ||||||||||||||||||||||||
| <CollapsibleContent> | ||||||||||||||||||||||||
| <SidebarGroupContent> | ||||||||||||||||||||||||
| {byCategory(components).map(({ category, components }) => | ||||||||||||||||||||||||
| category ? ( | ||||||||||||||||||||||||
| <Collapsible | ||||||||||||||||||||||||
| key={category} | ||||||||||||||||||||||||
| // Collapsed by default, unless it holds the page being viewed. | ||||||||||||||||||||||||
| defaultOpen={components.some( | ||||||||||||||||||||||||
| ({ componentSlug }) => pathname === `/${groupSlug}/${componentSlug}`, | ||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||
| className='group/category' | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| <CollapsibleTrigger className='text-sidebar-foreground/40 hover:text-sidebar-foreground/70 flex w-full items-center gap-1 px-2 pb-0.5 pt-2 text-[9px] font-semibold uppercase tracking-wider'> | ||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||
| aria-hidden='true' | ||||||||||||||||||||||||
| className='font-mono text-[10px] leading-none' | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| └ | ||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||
| {category} | ||||||||||||||||||||||||
| <ChevronRightIcon className='size-2.5! ml-auto transition-transform group-data-[open]/category:rotate-90' /> | ||||||||||||||||||||||||
| </CollapsibleTrigger> | ||||||||||||||||||||||||
| <CollapsibleContent> | ||||||||||||||||||||||||
| <div className='border-sidebar-border ml-3 border-l pl-1'> | ||||||||||||||||||||||||
| <SidebarEntryMenu | ||||||||||||||||||||||||
| components={components} | ||||||||||||||||||||||||
| groupSlug={groupSlug} | ||||||||||||||||||||||||
| pathname={pathname} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </CollapsibleContent> | ||||||||||||||||||||||||
| </Collapsible> | ||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||
| <SidebarEntryMenu | ||||||||||||||||||||||||
| key={group} | ||||||||||||||||||||||||
| components={components} | ||||||||||||||||||||||||
| groupSlug={groupSlug} | ||||||||||||||||||||||||
| pathname={pathname} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||
| </SidebarGroupContent> | ||||||||||||||||||||||||
| </CollapsibleContent> | ||||||||||||||||||||||||
| </SidebarGroup> | ||||||||||||||||||||||||
| </Collapsible> | ||||||||||||||||||||||||
| </React.Fragment> | ||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||
| </SidebarContent> | ||||||||||||||||||||||||
| <SidebarRail /> | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Add release metadata or remove these empty Changesets.
Each file has empty front matter. It records no package version bump and no changelog entry. Add the affected package, release type, and summary when the change is releasable. Otherwise, remove the empty file.
.changeset/swingset-sidebar-organization.md#L1-L2: add release metadata and a summary, or remove the file..changeset/user-profile-billing-panel.md#L1-L2: add release metadata and a summary, or remove the file..changeset/user-profile-security-panel.md#L1-L2: add release metadata and a summary, or remove the file.As per coding guidelines, use Changesets for version management and changelogs.
📍 Affects 3 files
.changeset/swingset-sidebar-organization.md#L1-L2(this comment).changeset/user-profile-billing-panel.md#L1-L2.changeset/user-profile-security-panel.md#L1-L2🤖 Prompt for AI Agents
Source: Coding guidelines