added sidebar variant

This commit is contained in:
Nate Kelley 2025-02-27 21:56:52 -07:00
parent 1d934c6cde
commit ba62cb4e25
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
8 changed files with 233 additions and 1 deletions

32
web/package-lock.json generated
View File

@ -19,6 +19,7 @@
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-select": "^2.1.6",
@ -2426,6 +2427,7 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {
@ -5683,6 +5685,36 @@
}
}
},
"node_modules/@radix-ui/react-collapsible": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz",
"integrity": "sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz",

View File

@ -28,6 +28,7 @@
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-select": "^2.1.6",

View File

@ -0,0 +1,11 @@
'use client';
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View File

@ -0,0 +1,11 @@
import React from 'react';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger
} from '../collapsible/CollapsibleBase';
import { type ISidebarGroup } from './interfaces';
export const SidebarGroup: React.FC<ISidebarGroup> = React.memo(({ label, items }) => {
return <></>;
});

View File

@ -0,0 +1,84 @@
import type { Meta, StoryObj } from '@storybook/react';
import { SidebarItem } from './SidebarItem';
import { HouseModern } from '@/components/ui/icons';
import { BusterRoutes } from '@/routes';
const meta: Meta<typeof SidebarItem> = {
title: 'UI/Sidebar/SidebarItem',
component: SidebarItem,
parameters: {
layout: 'centered'
},
argTypes: {
variant: {
control: 'select',
options: ['default', 'emphasized']
},
active: {
control: 'boolean'
},
disabled: {
control: 'boolean'
}
},
tags: ['autodocs'],
decorators: [
(Story) => (
<div className="min-w-[300px]">
<Story />
</div>
)
]
};
export default meta;
type Story = StoryObj<typeof SidebarItem>;
export const Default: Story = {
args: {
label: 'Home',
icon: <HouseModern />,
route: BusterRoutes.APP_ROOT,
id: 'home'
}
};
export const Emphasized: Story = {
args: {
...Default.args,
variant: 'emphasized',
id: 'home-emphasized'
}
};
export const Active: Story = {
args: {
...Default.args,
id: 'home-active',
active: true
}
};
export const Disabled: Story = {
args: {
...Default.args,
disabled: true,
id: 'home-disabled'
}
};
export const LongText: Story = {
args: {
...Default.args,
label:
'This is a very long sidebar item label that should demonstrate text truncation behavior in the component',
id: 'long-text'
},
decorators: [
(Story) => (
<div className="max-w-[200px]">
<Story />
</div>
)
]
};

View File

@ -0,0 +1,64 @@
import React from 'react';
import Link from 'next/link';
import { cn } from '@/lib/classMerge';
import { type ISidebarItem } from './interfaces';
import { cva, VariantProps } from 'class-variance-authority';
const itemVariants = cva(
'flex items-center gap-2.5 rounded px-1.5 py-1.5 text-sm transition-colors',
{
variants: {
variant: {
default: 'hover:bg-item-hover text-text-default',
emphasized: 'shadow bg-background border border-border text-text-default'
},
active: {
true: '',
false: ''
},
disabled: {
true: 'cursor-not-allowed',
false: ''
}
},
compoundVariants: [
{
active: true,
disabled: false,
variant: 'default',
className: 'bg-nav-item-select hover:bg-nav-item-select'
},
{
active: true,
disabled: false,
variant: 'emphasized',
className: 'bg-nav-item-select hover:bg-nav-item-select'
},
{
active: false,
disabled: true,
variant: 'emphasized',
className: 'bg-nav-item-select hover:bg-nav-item-select'
},
{
active: false,
disabled: false,
variant: 'emphasized',
className: 'hover:bg-item-hover '
}
]
}
);
export const SidebarItem: React.FC<ISidebarItem & VariantProps<typeof itemVariants>> = React.memo(
({ label, icon, route, id, disabled = false, active = false, variant = 'default' }) => {
const ItemNode = disabled ? 'div' : Link;
return (
<ItemNode href={route} className={cn(itemVariants({ active, disabled, variant }))}>
<span className="text-icon-size text-icon-color">{icon}</span>
<span className="text-foreground truncate">{label}</span>
</ItemNode>
);
}
);

View File

@ -0,0 +1,29 @@
import { BusterRoutes } from '@/routes';
export interface ISidebarItem {
label: string;
icon: React.ReactNode;
route: BusterRoutes;
id: string;
disabled?: boolean;
active?: boolean;
onRemove?: () => void;
}
export interface ISidebarGroup {
label: string;
items: ISidebarItem[];
}
export interface ISidebarList {
items: ISidebarItem[];
}
type SidebarContent = ISidebarGroup | ISidebarList;
export interface SidebarProps {
header: React.ReactNode;
content: SidebarContent[];
footer?: React.ReactNode;
activeItem: string;
}

View File

@ -24,7 +24,7 @@ export const BusterReactQueryProvider = ({ children }: { children: React.ReactEl
return (
<QueryClientProvider client={queryClient}>
{children}
{/* <ReactQueryDevtoolsPanel /> */}
<ReactQueryDevtoolsPanel />
</QueryClientProvider>
);
};