From deaa6dfd74738eae384b4de0a28c60b6af4a4883 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 11:28:49 -0500 Subject: [PATCH 1/9] Add user management components for model and participant selection Implements four new components based on Figma designs: 1. Tooltip - Hover tooltips for UI elements - Simple popover with text - Uses popover theme tokens - Proper ARIA role for accessibility 2. DropdownMenu - Standalone dropdown menus - Menu items with icons and avatars - Secondary icons (check, plus, chevron) - Separator and label support - Search input integration - Hover and active states 3. AvatarGroup - Overlapping user avatars - Negative margin overlap design - Border separation for clarity - Presence indicator support - Size variants (sm, default, lg) - Spacing variants (tight, default, loose) 4. ModelUserManagement - Complete compound component - Model selector button - Add user button - Avatar display (single or group) - User dropdown trigger - Integrates all sub-components Features: - TypeScript with proper prop types - class-variance-authority for variants - Comprehensive Storybook stories - Exported from src/index.ts - Vanilla HTML/CSS examples in vanilla/components/user-management.html - Updated vanilla/index.html with new component links Based on Figma: https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=5077-6876 --- .../AvatarGroup/AvatarGroup.stories.tsx | 129 ++++++++ src/components/AvatarGroup/AvatarGroup.tsx | 102 ++++++ src/components/AvatarGroup/index.ts | 7 + .../DropdownMenu/DropdownMenu.stories.tsx | 259 +++++++++++++++ src/components/DropdownMenu/DropdownMenu.tsx | 186 +++++++++++ src/components/DropdownMenu/index.ts | 11 + .../ModelUserManagement.stories.tsx | 162 ++++++++++ .../ModelUserManagement.tsx | 157 +++++++++ src/components/ModelUserManagement/index.ts | 14 + src/components/Tooltip/Tooltip.stories.tsx | 42 +++ src/components/Tooltip/Tooltip.tsx | 40 +++ src/components/Tooltip/index.ts | 1 + src/index.ts | 4 + vanilla/components/user-management.html | 300 ++++++++++++++++++ vanilla/index.html | 8 +- 15 files changed, 1421 insertions(+), 1 deletion(-) create mode 100644 src/components/AvatarGroup/AvatarGroup.stories.tsx create mode 100644 src/components/AvatarGroup/AvatarGroup.tsx create mode 100644 src/components/AvatarGroup/index.ts create mode 100644 src/components/DropdownMenu/DropdownMenu.stories.tsx create mode 100644 src/components/DropdownMenu/DropdownMenu.tsx create mode 100644 src/components/DropdownMenu/index.ts create mode 100644 src/components/ModelUserManagement/ModelUserManagement.stories.tsx create mode 100644 src/components/ModelUserManagement/ModelUserManagement.tsx create mode 100644 src/components/ModelUserManagement/index.ts create mode 100644 src/components/Tooltip/Tooltip.stories.tsx create mode 100644 src/components/Tooltip/Tooltip.tsx create mode 100644 src/components/Tooltip/index.ts create mode 100644 vanilla/components/user-management.html diff --git a/src/components/AvatarGroup/AvatarGroup.stories.tsx b/src/components/AvatarGroup/AvatarGroup.stories.tsx new file mode 100644 index 0000000..37b70d4 --- /dev/null +++ b/src/components/AvatarGroup/AvatarGroup.stories.tsx @@ -0,0 +1,129 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { AvatarGroup, AvatarGroupItem } from './AvatarGroup'; + +const meta = { + title: 'Components/AvatarGroup', + component: AvatarGroup, + parameters: { + layout: 'centered', + design: { + type: 'figma', + url: 'https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=5069-7627', + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Single: Story = { + render: () => ( + + + + ), +}; + +export const Two: Story = { + render: () => ( + + + + + ), +}; + +export const Three: Story = { + render: () => ( + + + + + + ), +}; + +export const WithPresenceIndicator: Story = { + render: () => ( + + + + ), +}; + +export const TwoWithPresence: Story = { + render: () => ( + + + + + ), +}; + +export const SmallSize: Story = { + render: () => ( + + + + + + ), +}; + +export const LargeSize: Story = { + render: () => ( + + + + + + ), +}; + +export const TightSpacing: Story = { + render: () => ( + + + + + + + ), +}; + +export const LooseSpacing: Story = { + render: () => ( + + + + + + + ), +}; + +export const WithImages: Story = { + render: () => ( + + + + + + ), +}; diff --git a/src/components/AvatarGroup/AvatarGroup.tsx b/src/components/AvatarGroup/AvatarGroup.tsx new file mode 100644 index 0000000..2e71a71 --- /dev/null +++ b/src/components/AvatarGroup/AvatarGroup.tsx @@ -0,0 +1,102 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/Avatar'; + +/** + * AvatarGroup Component + * + * Displays multiple user avatars in an overlapping group. + * Each avatar shows with a border for visual separation. + * Based on Figma component: https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=5069-7627 + * + * @example + * ```tsx + * + * + * + * + * + * ``` + */ + +const avatarGroupVariants = cva('flex items-center', { + variants: { + size: { + sm: '[&>*]:h-4 [&>*]:w-4 [&>*]:text-[10px]', + default: '[&>*]:h-[25px] [&>*]:w-[25px] [&>*]:text-xs', + lg: '[&>*]:h-10 [&>*]:w-10 [&>*]:text-sm', + }, + spacing: { + tight: '[&>*:not(:first-child)]:-ml-2', + default: '[&>*:not(:first-child)]:-ml-1', + loose: '[&>*:not(:first-child)]:-ml-0.5', + }, + }, + defaultVariants: { + size: 'default', + spacing: 'default', + }, +}); + +export interface AvatarGroupProps + extends React.HTMLAttributes, + VariantProps {} + +const AvatarGroup = React.forwardRef( + ({ className, size, spacing, ...props }, ref) => { + return ( +
+ ); + } +); +AvatarGroup.displayName = 'AvatarGroup'; + +export interface AvatarGroupItemProps + extends Omit, 'children'> { + /** + * URL of the avatar image + */ + src?: string; + /** + * Alt text for the avatar image + */ + alt?: string; + /** + * Fallback text (usually initials) + */ + fallback: string; + /** + * Whether to show a presence indicator (green dot) + */ + showPresence?: boolean; +} + +const AvatarGroupItem = React.forwardRef( + ({ className, src, alt, fallback, showPresence, ...props }, ref) => { + return ( +
+ + {src && } + + {fallback} + + + {showPresence && ( + + )} +
+ ); + } +); +AvatarGroupItem.displayName = 'AvatarGroupItem'; + +export { AvatarGroup, AvatarGroupItem, avatarGroupVariants }; diff --git a/src/components/AvatarGroup/index.ts b/src/components/AvatarGroup/index.ts new file mode 100644 index 0000000..2361268 --- /dev/null +++ b/src/components/AvatarGroup/index.ts @@ -0,0 +1,7 @@ +export { + AvatarGroup, + AvatarGroupItem, + avatarGroupVariants, + type AvatarGroupProps, + type AvatarGroupItemProps, +} from './AvatarGroup'; diff --git a/src/components/DropdownMenu/DropdownMenu.stories.tsx b/src/components/DropdownMenu/DropdownMenu.stories.tsx new file mode 100644 index 0000000..1f4f81f --- /dev/null +++ b/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -0,0 +1,259 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { DropdownMenu, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from './DropdownMenu'; +import { Avatar, AvatarFallback } from '@/components/Avatar'; +import { Input } from '@/components/Input'; +import { Users, Edit2 } from 'lucide-react'; + +const meta = { + title: 'Components/DropdownMenu', + component: DropdownMenu, + parameters: { + layout: 'centered', + design: { + type: 'figma', + url: 'https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=5070-8203', + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const AddParticipants: Story = { + render: () => ( + + Add participants + Group + Team member + + ), +}; + +export const SelectModel: Story = { + render: () => ( + + + A +
+ } + checked + > + Claude Opus 4.5 + + + A + + } + > + Claude Sonnet 4.5 + + + G + + } + secondaryIcon="plus" + > + Gemini 3 Pro + + + G + + } + secondaryIcon="plus" + > + Gemini Flash + + + O + + } + secondaryIcon="plus" + > + GPT-5.1 Codex + + + O + + } + secondaryIcon="plus" + > + GPT-5.2 + + + O + + } + secondaryIcon="plus" + > + GPT-5.2 Pro + + + O + + } + secondaryIcon="plus" + > + o4-mini + + + ), +}; + +export const AddGroup: Story = { + render: () => ( + + Add group + +
+ +
+ + Dev team + + + Leadership + + + Prod/Dev + + + Create new group +
+ ), +}; + +export const AddTeamMember: Story = { + render: () => ( + + Add team member + +
+ +
+ + + B + + + } + secondaryIcon="plus" + > + Brittan Berry + + + D + + } + secondaryIcon="plus" + > + David Noël-Romas + + + E + + } + secondaryIcon="plus" + > + Emilio + + + L + + } + secondaryIcon="plus" + > + Louis Amira + + + N + + } + secondaryIcon="plus" + > + Naveen + + + R + + } + secondaryIcon="plus" + > + Rob D + +
+ ), +}; + +export const ViewParticipants: Story = { + render: () => ( + + + L + + } + > + Lucy Cameron + + + B + + } + secondaryIcon="plus" + > + Brian DeJong + + + R + + } + secondaryIcon="plus" + > + Rob Di Marco + + + ), +}; + +export const RemoveUser: Story = { + render: () => ( + + Remove + + ), +}; diff --git a/src/components/DropdownMenu/DropdownMenu.tsx b/src/components/DropdownMenu/DropdownMenu.tsx new file mode 100644 index 0000000..e57f807 --- /dev/null +++ b/src/components/DropdownMenu/DropdownMenu.tsx @@ -0,0 +1,186 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; +import { Icon, Check, Plus, ChevronRight } from '@/components/Icon'; +import type { LucideIcon } from 'lucide-react'; + +/** + * DropdownMenu Component + * + * Displays a menu to the user — such as a set of actions or functions — triggered by a button. + * Based on Figma component: https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=5070-8203 + * + * @see https://ui.shadcn.com/docs/components/dropdown-menu + * + * @example + * ```tsx + * + * Add participants + * Group + * Team member + * + * ``` + */ + +export type DropdownMenuProps = React.HTMLAttributes; + +const DropdownMenu = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +
+ ); + } +); +DropdownMenu.displayName = 'DropdownMenu'; + +const dropdownMenuItemVariants = cva( + 'flex h-8 w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm font-normal leading-5 text-popover-foreground transition-colors', + { + variants: { + variant: { + default: 'hover:bg-muted', + active: 'bg-muted', + }, + disabled: { + true: 'cursor-not-allowed opacity-50', + false: 'cursor-pointer', + }, + }, + defaultVariants: { + variant: 'default', + disabled: false, + }, + } +); + +export interface DropdownMenuItemProps + extends Omit, 'disabled'>, + VariantProps { + /** + * Optional icon to display on the left + */ + icon?: LucideIcon; + /** + * Optional secondary icon to display on the right + */ + secondaryIcon?: LucideIcon | 'check' | 'plus' | 'chevron'; + /** + * Whether to show a checkmark (shorthand for secondaryIcon="check") + */ + checked?: boolean; + /** + * Whether this item has a submenu (shorthand for secondaryIcon="chevron") + */ + hasSubmenu?: boolean; + /** + * Optional avatar element to display instead of icon + */ + avatar?: React.ReactNode; +} + +const DropdownMenuItem = React.forwardRef< + HTMLButtonElement, + DropdownMenuItemProps +>( + ( + { + className, + variant, + disabled = false, + icon, + secondaryIcon, + checked, + hasSubmenu, + avatar, + children, + ...props + }, + ref + ) => { + const isDisabled = Boolean(disabled); + + // Determine secondary icon + let secondaryIconComponent: React.ReactNode = null; + if (checked || secondaryIcon === 'check') { + secondaryIconComponent = ; + } else if (hasSubmenu || secondaryIcon === 'chevron') { + secondaryIconComponent = ; + } else if (secondaryIcon === 'plus') { + secondaryIconComponent = ; + } else if (secondaryIcon && typeof secondaryIcon !== 'string') { + secondaryIconComponent = ; + } + + return ( + + ); + } +); +DropdownMenuItem.displayName = 'DropdownMenuItem'; + +export type DropdownMenuLabelProps = React.HTMLAttributes; + +const DropdownMenuLabel = React.forwardRef< + HTMLDivElement, + DropdownMenuLabelProps +>(({ className, ...props }, ref) => { + return ( +
+ ); +}); +DropdownMenuLabel.displayName = 'DropdownMenuLabel'; + +export type DropdownMenuSeparatorProps = React.HTMLAttributes; + +const DropdownMenuSeparator = React.forwardRef< + HTMLDivElement, + DropdownMenuSeparatorProps +>(({ className, ...props }, ref) => { + return ( +
+ ); +}); +DropdownMenuSeparator.displayName = 'DropdownMenuSeparator'; + +export { + DropdownMenu, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + dropdownMenuItemVariants, +}; diff --git a/src/components/DropdownMenu/index.ts b/src/components/DropdownMenu/index.ts new file mode 100644 index 0000000..fb2f2ac --- /dev/null +++ b/src/components/DropdownMenu/index.ts @@ -0,0 +1,11 @@ +export { + DropdownMenu, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + dropdownMenuItemVariants, + type DropdownMenuProps, + type DropdownMenuItemProps, + type DropdownMenuLabelProps, + type DropdownMenuSeparatorProps, +} from './DropdownMenu'; diff --git a/src/components/ModelUserManagement/ModelUserManagement.stories.tsx b/src/components/ModelUserManagement/ModelUserManagement.stories.tsx new file mode 100644 index 0000000..4b3259d --- /dev/null +++ b/src/components/ModelUserManagement/ModelUserManagement.stories.tsx @@ -0,0 +1,162 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { + ModelUserManagement, + ModelSelector, + UserManagementGroup, + AddUserButton, + UserAvatars, + UserDropdownTrigger, +} from './ModelUserManagement'; +import { AvatarGroupItem } from '@/components/AvatarGroup'; + +const meta = { + title: 'Components/ModelUserManagement', + component: ModelUserManagement, + parameters: { + layout: 'centered', + design: { + type: 'figma', + url: 'https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=5070-8794', + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + Claude Sonnet 4.5 + + + + + + + + + ), +}; + +export const TwoUsers: Story = { + render: () => ( + + Claude Sonnet 4.5 + + + + + + + + + + ), +}; + +export const ThreeUsers: Story = { + render: () => ( + + Claude Sonnet 4.5 + + + + + + + + + + + ), +}; + +export const WithPresenceIndicator: Story = { + render: () => ( + + Claude Sonnet 4.5 + + + + + + + + + ), +}; + +export const TwoUsersWithPresence: Story = { + render: () => ( + + Claude Sonnet 4.5 + + + + + + + + + + ), +}; + +export const DifferentModel: Story = { + render: () => ( + + Claude Opus 4.5 + + + + + + + + + + ), +}; + +export const SingleUserNoPresence: Story = { + render: () => ( + + GPT-5.2 Pro + + + + + + + + + ), +}; + +export const WithCustomHandlers: Story = { + render: () => ( + + alert('Select model')}> + Claude Sonnet 4.5 + + + alert('Add user')} /> + + + + + alert('User options')} /> + + + ), +}; diff --git a/src/components/ModelUserManagement/ModelUserManagement.tsx b/src/components/ModelUserManagement/ModelUserManagement.tsx new file mode 100644 index 0000000..f53e777 --- /dev/null +++ b/src/components/ModelUserManagement/ModelUserManagement.tsx @@ -0,0 +1,157 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/Button'; +import { AvatarGroup } from '@/components/AvatarGroup'; +import { Icon, Plus, ChevronDown } from '@/components/Icon'; + +/** + * ModelUserManagement Component + * + * A compound component for managing model selection and user participants. + * Combines model selector button with user management controls including + * add user button, avatar display, and user dropdown. + * Based on Figma component: https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=5070-8794 + * + * @example + * ```tsx + * + * Claude Sonnet 4.5 + * + * + * + * + * + * + * + * + * + * ``` + */ + +export type ModelUserManagementProps = React.HTMLAttributes; + +const ModelUserManagement = React.forwardRef< + HTMLDivElement, + ModelUserManagementProps +>(({ className, ...props }, ref) => { + return ( +
+ ); +}); +ModelUserManagement.displayName = 'ModelUserManagement'; + +export interface ModelSelectorProps + extends React.ButtonHTMLAttributes {} + +const ModelSelector = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( + + ); + } +); +ModelSelector.displayName = 'ModelSelector'; + +export type UserManagementGroupProps = React.HTMLAttributes; + +const UserManagementGroup = React.forwardRef< + HTMLDivElement, + UserManagementGroupProps +>(({ className, ...props }, ref) => { + return ( +
+ ); +}); +UserManagementGroup.displayName = 'UserManagementGroup'; + +export interface AddUserButtonProps + extends React.ButtonHTMLAttributes {} + +const AddUserButton = React.forwardRef( + ({ className, ...props }, ref) => { + return ( + + ); + } +); +AddUserButton.displayName = 'AddUserButton'; + +export type UserAvatarsProps = React.HTMLAttributes; + +const UserAvatars = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( +
+ {children} +
+ ); + } +); +UserAvatars.displayName = 'UserAvatars'; + +export interface UserDropdownTriggerProps + extends React.ButtonHTMLAttributes {} + +const UserDropdownTrigger = React.forwardRef< + HTMLButtonElement, + UserDropdownTriggerProps +>(({ className, ...props }, ref) => { + return ( + + ); +}); +UserDropdownTrigger.displayName = 'UserDropdownTrigger'; + +export { + ModelUserManagement, + ModelSelector, + UserManagementGroup, + AddUserButton, + UserAvatars, + UserDropdownTrigger, +}; diff --git a/src/components/ModelUserManagement/index.ts b/src/components/ModelUserManagement/index.ts new file mode 100644 index 0000000..2110dc4 --- /dev/null +++ b/src/components/ModelUserManagement/index.ts @@ -0,0 +1,14 @@ +export { + ModelUserManagement, + ModelSelector, + UserManagementGroup, + AddUserButton, + UserAvatars, + UserDropdownTrigger, + type ModelUserManagementProps, + type ModelSelectorProps, + type UserManagementGroupProps, + type AddUserButtonProps, + type UserAvatarsProps, + type UserDropdownTriggerProps, +} from './ModelUserManagement'; diff --git a/src/components/Tooltip/Tooltip.stories.tsx b/src/components/Tooltip/Tooltip.stories.tsx new file mode 100644 index 0000000..915fe41 --- /dev/null +++ b/src/components/Tooltip/Tooltip.stories.tsx @@ -0,0 +1,42 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Tooltip } from './Tooltip'; + +const meta = { + title: 'Components/Tooltip', + component: Tooltip, + parameters: { + layout: 'centered', + design: { + type: 'figma', + url: 'https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=2688-451', + }, + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'Chat participants', + }, +}; + +export const AddParticipants: Story = { + args: { + children: 'Add participants', + }, +}; + +export const SelectModel: Story = { + args: { + children: 'Select model', + }, +}; + +export const LongText: Story = { + args: { + children: 'This is a longer tooltip text that might wrap to multiple lines', + }, +}; diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000..cb5275d --- /dev/null +++ b/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import { cn } from '@/lib/utils'; + +/** + * Tooltip Component + * + * A popup that displays information related to an element when the element + * receives keyboard focus or the mouse hovers over it. + * Based on Figma component: https://www.figma.com/design/nadcKNlrnZUHbbLwm9GdK4?node-id=2688-451 + * + * @see https://ui.shadcn.com/docs/components/tooltip + * + * @example + * ```tsx + * + * Chat participants + * + * ``` + */ + +export type TooltipProps = React.HTMLAttributes; + +const Tooltip = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +
+ ); + } +); +Tooltip.displayName = 'Tooltip'; + +export { Tooltip }; diff --git a/src/components/Tooltip/index.ts b/src/components/Tooltip/index.ts new file mode 100644 index 0000000..e84d44a --- /dev/null +++ b/src/components/Tooltip/index.ts @@ -0,0 +1 @@ +export { Tooltip, type TooltipProps } from './Tooltip'; diff --git a/src/index.ts b/src/index.ts index bd9bdd2..2358540 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export * from './components/Accordion'; export * from './components/Alert'; export * from './components/AlertDialog'; export * from './components/Avatar'; +export * from './components/AvatarGroup'; export * from './components/Badge'; export * from './components/Breadcrumb'; export * from './components/Button'; @@ -12,6 +13,7 @@ export * from './components/Carousel'; export * from './components/Checkbox'; export * from './components/Dialog'; export * from './components/Drawer'; +export * from './components/DropdownMenu'; export * from './components/Header'; export * from './components/HoverCard'; export * from './components/Icon'; @@ -20,6 +22,7 @@ export * from './components/Input'; export * from './components/InputGroup'; export * from './components/Label'; export * from './components/Menubar'; +export * from './components/ModelUserManagement'; export * from './components/NavHeader'; export * from './components/NavSidePanel'; export * from './components/Pagination'; @@ -37,6 +40,7 @@ export * from './components/Tabs'; export * from './components/Textarea'; export * from './components/Toast'; export * from './components/Toggle'; +export * from './components/Tooltip'; // Utilities export { cn } from './lib/utils'; diff --git a/vanilla/components/user-management.html b/vanilla/components/user-management.html new file mode 100644 index 0000000..a9eea1c --- /dev/null +++ b/vanilla/components/user-management.html @@ -0,0 +1,300 @@ + + + + + + User Management Components - ATXP Design System + + + +
+ +
+

User Management Components

+

Model selection and user participant management components

+
+ + +
+

Tooltip

+

A popup that displays information related to an element.

+ +
+ + + + + +
+
+ + +
+

Avatar Group

+

Multiple user avatars displayed in an overlapping group.

+ +
+
+

Single User

+
+
+
+
+ L +
+
+
+
+
+ +
+

Two Users

+
+
+
+
+ L +
+
+
+
+
+
+ B +
+
+
+
+
+ +
+

Three Users

+
+
+
+
+ L +
+
+
+
+
+
+ B +
+
+
+
+
+
+ R +
+
+
+
+
+ +
+

With Presence Indicator

+
+
+
+
+ L +
+
+ +
+
+
+
+
+ + +
+

Dropdown Menu

+

Displays a menu to the user with actions or functions.

+ +
+ + + + + + + + +
+
+ + +
+

Model & User Management

+

Complete model selection and user management interface.

+ +
+
+

Single User

+
+ + + + +
+ + + + +
+
+
+
+
+ L +
+
+
+
+
+ + + +
+
+
+ +
+

Two Users

+
+ + +
+ + +
+
+
+
+
+ L +
+
+
+
+
+
+ B +
+
+ +
+
+
+ + +
+
+
+ +
+

Three Users

+
+ + +
+ + +
+
+
+
+
+ L +
+
+
+
+
+
+ B +
+
+
+
+
+
+ R +
+
+
+
+
+ + +
+
+
+
+
+
+ + diff --git a/vanilla/index.html b/vanilla/index.html index addd161..5a05046 100644 --- a/vanilla/index.html +++ b/vanilla/index.html @@ -434,6 +434,11 @@

Detailed Component Examples

Interactive
Tabs, accordions, progress, skeletons
+ + +
User Management
+
Model selection, tooltips, dropdowns, avatar groups
+
@@ -524,7 +529,8 @@

Welcome to ATXP Desi DialogsFormsUI Elements • - Interactive + Interactive • + User Management

From 34e747c86cddb33f900ca13361327b0c9e5796a2 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 12:09:52 -0500 Subject: [PATCH 2/9] Fix avatar sizes and vertical alignment in user management components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add tailwind-merge to cn utility to properly handle conflicting Tailwind classes - Fix avatar text size in dropdown menus: text-xs (12px) → text-[10px] for 16px avatars - Fix vertical alignment in ModelUserManagement by setting explicit height on UserAvatars - Convert empty interfaces to type aliases to fix TypeScript lint errors This ensures: - Small avatars (16px) in dropdowns render at correct size and don't inherit 40px default - All components in ModelUserManagement are properly vertically centered - Text in small avatars is proportional (10px text in 16px avatars) - Code passes TypeScript linting without empty interface warnings --- package.json | 15 +-- pnpm-lock.yaml | 110 ++++++++++-------- .../DropdownMenu/DropdownMenu.stories.tsx | 34 +++--- .../ModelUserManagement.tsx | 11 +- src/lib/utils.ts | 6 +- 5 files changed, 92 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index 87b9100..4c48ce0 100644 --- a/package.json +++ b/package.json @@ -49,17 +49,17 @@ "homepage": "https://github.com/atxp-dev/design-system#readme", "sideEffects": false, "peerDependencies": { + "lucide-react": "^0.562.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", - "lucide-react": "^0.562.0", "sonner": "^2.0.7" }, "devDependencies": { "@eslint/js": "^9.39.2", - "@storybook/addon-designs": "^11.1.0", - "@storybook/addon-links": "^10.1.10", - "@storybook/react": "^10.1.10", - "@storybook/react-vite": "^10.1.10", + "@storybook/addon-designs": "^11.1.1", + "@storybook/addon-links": "^10.2.0", + "@storybook/react": "^10.2.0", + "@storybook/react-vite": "^10.2.0", "@tailwindcss/cli": "^4.1.18", "@tailwindcss/postcss": "^4.1.18", "@types/react": "^19.2.7", @@ -75,7 +75,7 @@ "postcss": "^8.5.6", "react": "^19.2.3", "react-dom": "^19.2.3", - "storybook": "^10.1.10", + "storybook": "^10.2.0", "tailwindcss": "^4.1.18", "tsup": "^8.5.1", "typescript": "^5.9.3", @@ -90,7 +90,8 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1" + "clsx": "^2.1.1", + "tailwind-merge": "^3.4.0" }, "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f70e5b8..a8f5509 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,22 +41,25 @@ importers: sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 devDependencies: '@eslint/js': specifier: ^9.39.2 version: 9.39.2 '@storybook/addon-designs': - specifier: ^11.1.0 - version: 11.1.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + specifier: ^11.1.1 + version: 11.1.1(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) '@storybook/addon-links': - specifier: ^10.1.10 - version: 10.1.10(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + specifier: ^10.2.0 + version: 10.2.0(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) '@storybook/react': - specifier: ^10.1.10 - version: 10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) + specifier: ^10.2.0 + version: 10.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) '@storybook/react-vite': - specifier: ^10.1.10 - version: 10.1.10(esbuild@0.27.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.5)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) + specifier: ^10.2.0 + version: 10.2.0(esbuild@0.27.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.5)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) '@tailwindcss/cli': specifier: ^4.1.18 version: 4.1.18 @@ -103,8 +106,8 @@ importers: specifier: ^19.2.3 version: 19.2.3(react@19.2.3) storybook: - specifier: ^10.1.10 - version: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^10.2.0 + version: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -1087,8 +1090,8 @@ packages: cpu: [x64] os: [win32] - '@storybook/addon-designs@11.1.0': - resolution: {integrity: sha512-i9lnUJ9x+UwThUpIjgg7QWvadhwmQ1ZuqcrTFe12giqyyYJKM6hdrUEuxGgSOrz3pkmDV/Bypq3G5ehwIDdKiw==} + '@storybook/addon-designs@11.1.1': + resolution: {integrity: sha512-1KAmTzoW/qw4RfR8uft3pBgsdWHoQiMp9rt+nzhFLEBPd1Ru3YiTnVL/JBEiJkGXsQfQxMnAYRRwYgf+HTr4yw==} peerDependencies: '@storybook/addon-docs': ^10.0.0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1102,27 +1105,27 @@ packages: react-dom: optional: true - '@storybook/addon-links@10.1.10': - resolution: {integrity: sha512-SVKFDb14mne16QMGkmOEk+T4NLvCuFJJ1ecebQ01cPiG5gM72LhzYkAro717Aizd6owyMqcWs0Rsfwl09qi5zA==} + '@storybook/addon-links@10.2.0': + resolution: {integrity: sha512-QOZLlcJwK6RkhizxBqDzipfYNqVrQNbWMFLHDcSfdA7suszgelxLyUVK9pC0McMmkpjw14bMH22urLjrjHUOuw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.10 + storybook: ^10.2.0 peerDependenciesMeta: react: optional: true - '@storybook/builder-vite@10.1.10': - resolution: {integrity: sha512-6m6zOyDhHLynv3lvkH70s1YoIkIFPhbpGsBKvHchRLrZLe8hCPeafIFLfZRPoD4yIPwBS6rWbjMsSvBMFlR+ag==} + '@storybook/builder-vite@10.2.0': + resolution: {integrity: sha512-S1+62ipGmQzGPZfcbgNqpbrCezsqkvbhj+MBbQ6VS46b2HcPjm4H8V6FzGly0Ja2pSgu8gT1BQ5N+3yOG8UNTw==} peerDependencies: - storybook: ^10.1.10 + storybook: ^10.2.0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - '@storybook/csf-plugin@10.1.10': - resolution: {integrity: sha512-2dri4TRU8uuj/skmx/ZBw+GnnXf8EZHiMDMeijVRdBQtYFWPeoYzNIrGRpNfbuGpnDP0dcxrqti/TsedoxwFkA==} + '@storybook/csf-plugin@10.2.0': + resolution: {integrity: sha512-Cty+tZ0r1AZhwBBzqI4RyCpMVGt9wHGTtG4YCRUuNgVFO1MnjaFBHKRT+oT7md28+BWYjFz4Qtpge/fcWANJ0w==} peerDependencies: esbuild: '*' rollup: '*' - storybook: ^10.1.10 + storybook: ^10.2.0 vite: '*' webpack: '*' peerDependenciesMeta: @@ -1144,27 +1147,27 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@storybook/react-dom-shim@10.1.10': - resolution: {integrity: sha512-9pmUbEr1MeMHg9TG0c2jVUfHWr2AA86vqZGphY/nT6mbe/rGyWtBl5EnFLrz6WpI8mo3h+Kxs6p2oiuIYieRtw==} + '@storybook/react-dom-shim@10.2.0': + resolution: {integrity: sha512-PEQofiruE6dBGzUQPXZZREbuh1t62uRBWoUPRFNAZi79zddlk7+b9qu08VV9cvf68mwOqqT1+VJ1P+3ClD2ZVw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.10 + storybook: ^10.2.0 - '@storybook/react-vite@10.1.10': - resolution: {integrity: sha512-6kE4/88YuwO07P0DR6caKNDNvCB/VnpimPmj4Jv6qmqrBgnoOOiXHIKyHJD+EjNyrbbwv4ygG01RVEajpjQaDA==} + '@storybook/react-vite@10.2.0': + resolution: {integrity: sha512-tIXRfrA+wREFuA+bIJccMCV1YVFdACENcSnSlnB5Be3m8ynMHukOz6ObX9jI5WsWZnqrk0/eHyiYJyVhpY9rhQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.10 + storybook: ^10.2.0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - '@storybook/react@10.1.10': - resolution: {integrity: sha512-9Rpr8/wX0p5/EaulrxpqrjKjhGaA/Ab9HgxzTqs2Shz0gvMAQHoiRnTEp7RCCkP49ruFYnIp0yGRSovu03LakQ==} + '@storybook/react@10.2.0': + resolution: {integrity: sha512-ciJlh1UGm0GBXQgqrYFeLmiix+KGFB3v37OnAYjGghPS9OP6S99XyshxY/6p0sMOYtS+eWS2gPsOKNXNnLDGYw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.10 + storybook: ^10.2.0 typescript: '>= 4.9.x' peerDependenciesMeta: typescript: @@ -2682,8 +2685,8 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - storybook@10.1.10: - resolution: {integrity: sha512-oK0t0jEogiKKfv5Z1ao4Of99+xWw1TMUGuGRYDQS4kp2yyBsJQEgu7NI7OLYsCDI6gzt5p3RPtl1lqdeVLUi8A==} + storybook@10.2.0: + resolution: {integrity: sha512-fIQnFtpksRRgHR1CO1onGX3djaog4qsW/c5U8arqYTkUEr2TaWpn05mIJDOBoPJFlOdqFrB4Ttv0PZJxV7avhw==} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -2755,6 +2758,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} hasBin: true @@ -3847,28 +3853,28 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.5': optional: true - '@storybook/addon-designs@11.1.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + '@storybook/addon-designs@11.1.1(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: '@figspec/react': 2.0.1(@types/react@19.2.7)(react@19.2.3) - storybook: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) optionalDependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: - '@types/react' - '@storybook/addon-links@10.1.10(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + '@storybook/addon-links@10.2.0(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: '@storybook/global': 5.0.0 - storybook: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) optionalDependencies: react: 19.2.3 - '@storybook/builder-vite@10.1.10(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))': + '@storybook/builder-vite@10.2.0(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: - '@storybook/csf-plugin': 10.1.10(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) + '@storybook/csf-plugin': 10.2.0(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) '@vitest/mocker': 3.2.4(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) - storybook: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) ts-dedent: 2.2.0 vite: 7.3.0(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: @@ -3877,9 +3883,9 @@ snapshots: - rollup - webpack - '@storybook/csf-plugin@10.1.10(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))': + '@storybook/csf-plugin@10.2.0(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: - storybook: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.2 @@ -3893,25 +3899,25 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - '@storybook/react-dom-shim@10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + '@storybook/react-dom-shim@10.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) - storybook: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@storybook/react-vite@10.1.10(esbuild@0.27.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.5)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))': + '@storybook/react-vite@10.2.0(esbuild@0.27.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.5)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.6.3(typescript@5.9.3)(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) '@rollup/pluginutils': 5.3.0(rollup@4.53.5) - '@storybook/builder-vite': 10.1.10(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) - '@storybook/react': 10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) + '@storybook/builder-vite': 10.2.0(esbuild@0.27.2)(rollup@4.53.5)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) + '@storybook/react': 10.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 react: 19.2.3 react-docgen: 8.0.2 react-dom: 19.2.3(react@19.2.3) resolve: 1.22.11 - storybook: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tsconfig-paths: 4.2.0 vite: 7.3.0(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: @@ -3922,14 +3928,14 @@ snapshots: - typescript - webpack - '@storybook/react@10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)': + '@storybook/react@10.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)': dependencies: '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 10.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/react-dom-shim': 10.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) react: 19.2.3 react-docgen: 8.0.2 react-dom: 19.2.3(react@19.2.3) - storybook: 10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -5618,7 +5624,7 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook@10.1.10(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -5729,6 +5735,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@3.4.0: {} + tailwindcss@4.1.18: {} tapable@2.3.0: {} diff --git a/src/components/DropdownMenu/DropdownMenu.stories.tsx b/src/components/DropdownMenu/DropdownMenu.stories.tsx index 1f4f81f..069621b 100644 --- a/src/components/DropdownMenu/DropdownMenu.stories.tsx +++ b/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -36,7 +36,7 @@ export const SelectModel: Story = { - A + A

} checked @@ -47,7 +47,7 @@ export const SelectModel: Story = { variant="active" avatar={
- A + A
} > @@ -56,7 +56,7 @@ export const SelectModel: Story = { - G + G
} secondaryIcon="plus" @@ -66,7 +66,7 @@ export const SelectModel: Story = { - G + G
} secondaryIcon="plus" @@ -76,7 +76,7 @@ export const SelectModel: Story = { - O + O
} secondaryIcon="plus" @@ -86,7 +86,7 @@ export const SelectModel: Story = { - O + O
} secondaryIcon="plus" @@ -96,7 +96,7 @@ export const SelectModel: Story = { - O + O } secondaryIcon="plus" @@ -106,7 +106,7 @@ export const SelectModel: Story = { - O + O } secondaryIcon="plus" @@ -151,7 +151,7 @@ export const AddTeamMember: Story = { - + B @@ -163,7 +163,7 @@ export const AddTeamMember: Story = { - D + D } secondaryIcon="plus" @@ -173,7 +173,7 @@ export const AddTeamMember: Story = { - E + E } secondaryIcon="plus" @@ -183,7 +183,7 @@ export const AddTeamMember: Story = { - L + L } secondaryIcon="plus" @@ -193,7 +193,7 @@ export const AddTeamMember: Story = { - N + N } secondaryIcon="plus" @@ -203,7 +203,7 @@ export const AddTeamMember: Story = { - R + R } secondaryIcon="plus" @@ -220,7 +220,7 @@ export const ViewParticipants: Story = { - L + L } > @@ -229,7 +229,7 @@ export const ViewParticipants: Story = { - B + B } secondaryIcon="plus" @@ -239,7 +239,7 @@ export const ViewParticipants: Story = { - R + R } secondaryIcon="plus" diff --git a/src/components/ModelUserManagement/ModelUserManagement.tsx b/src/components/ModelUserManagement/ModelUserManagement.tsx index f53e777..d25e317 100644 --- a/src/components/ModelUserManagement/ModelUserManagement.tsx +++ b/src/components/ModelUserManagement/ModelUserManagement.tsx @@ -44,8 +44,7 @@ const ModelUserManagement = React.forwardRef< }); ModelUserManagement.displayName = 'ModelUserManagement'; -export interface ModelSelectorProps - extends React.ButtonHTMLAttributes {} +export type ModelSelectorProps = React.ButtonHTMLAttributes; const ModelSelector = React.forwardRef( ({ className, children, ...props }, ref) => { @@ -80,8 +79,7 @@ const UserManagementGroup = React.forwardRef< }); UserManagementGroup.displayName = 'UserManagementGroup'; -export interface AddUserButtonProps - extends React.ButtonHTMLAttributes {} +export type AddUserButtonProps = React.ButtonHTMLAttributes; const AddUserButton = React.forwardRef( ({ className, ...props }, ref) => { @@ -111,7 +109,7 @@ const UserAvatars = React.forwardRef(
( ); UserAvatars.displayName = 'UserAvatars'; -export interface UserDropdownTriggerProps - extends React.ButtonHTMLAttributes {} +export type UserDropdownTriggerProps = React.ButtonHTMLAttributes; const UserDropdownTrigger = React.forwardRef< HTMLButtonElement, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 56c19b2..3de80f0 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,9 +1,11 @@ import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; /** * Utility function to merge class names - * Combines clsx for conditional classes + * Combines clsx for conditional classes with tailwind-merge + * to properly handle conflicting Tailwind classes */ export function cn(...inputs: ClassValue[]) { - return clsx(inputs); + return twMerge(clsx(inputs)); } From 2b305cbdf9d0b2cdf55334df4151837b00d7ef11 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 12:11:02 -0500 Subject: [PATCH 3/9] Storybook upgrade --- .storybook/main.ts | 3 +- .storybook/preview.ts | 23 ++++--- eslint.config.js | 64 +++++++++---------- package.json | 11 ++-- pnpm-lock.yaml | 29 +++++---- .../Accordion/Accordion.stories.tsx | 2 +- src/components/Alert/Alert.stories.tsx | 2 +- .../AlertDialog/AlertDialog.stories.tsx | 2 +- src/components/Avatar/Avatar.stories.tsx | 2 +- .../AvatarGroup/AvatarGroup.stories.tsx | 2 +- src/components/Badge/Badge.stories.tsx | 2 +- .../Breadcrumb/Breadcrumb.stories.tsx | 2 +- src/components/Button/Button.stories.tsx | 2 +- .../ButtonGroup/ButtonGroup.stories.tsx | 2 +- src/components/Card/Card.stories.tsx | 2 +- src/components/Carousel/Carousel.stories.tsx | 2 +- src/components/Checkbox/Checkbox.stories.tsx | 2 +- src/components/Dialog/Dialog.stories.tsx | 2 +- src/components/Drawer/Drawer.stories.tsx | 2 +- .../DropdownMenu/DropdownMenu.stories.tsx | 2 +- src/components/Header/Header.stories.tsx | 2 +- .../HoverCard/HoverCard.stories.tsx | 2 +- src/components/Icon/Icon.stories.tsx | 2 +- .../Indicators/Indicators.stories.tsx | 2 +- src/components/Input/Input.stories.tsx | 2 +- .../InputGroup/InputGroup.stories.tsx | 2 +- src/components/Label/Label.stories.tsx | 2 +- src/components/Menubar/Menubar.stories.tsx | 2 +- .../ModelUserManagement.stories.tsx | 2 +- .../NavHeader/NavHeader.stories.tsx | 2 +- .../NavSidePanel/NavSidePanel.stories.tsx | 2 +- .../Pagination/Pagination.stories.tsx | 2 +- src/components/Popover/Popover.stories.tsx | 2 +- src/components/Progress/Progress.stories.tsx | 2 +- src/components/Radio/Radio.stories.tsx | 2 +- .../Scrollbar/Scrollbar.stories.tsx | 2 +- src/components/Select/Select.stories.tsx | 2 +- .../Separator/Separator.stories.tsx | 2 +- src/components/Sheet/Sheet.stories.tsx | 2 +- src/components/Skeleton/Skeleton.stories.tsx | 2 +- src/components/Slider/Slider.stories.tsx | 2 +- src/components/Switch/Switch.stories.tsx | 2 +- src/components/Table/Table.stories.tsx | 2 +- src/components/Tabs/Tabs.stories.tsx | 2 +- src/components/Textarea/Textarea.stories.tsx | 2 +- src/components/Theme/Theme.stories.tsx | 2 +- src/components/Toast/Toast.stories.tsx | 2 +- src/components/Toggle/Toggle.stories.tsx | 2 +- src/components/Tooltip/Tooltip.stories.tsx | 2 +- 49 files changed, 116 insertions(+), 102 deletions(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index b06b47f..f930b89 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,6 +1,7 @@ +// This file has been automatically migrated to valid ESM format by Storybook. import type { StorybookConfig } from '@storybook/react-vite'; import { mergeConfig } from 'vite'; -import path from 'path'; +import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); diff --git a/.storybook/preview.ts b/.storybook/preview.ts index d53b231..7096dd3 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,4 +1,4 @@ -import type { Preview } from '@storybook/react'; +import type { Preview } from '@storybook/react-vite'; import '../src/styles/globals.css'; const preview: Preview = { @@ -10,23 +10,30 @@ const preview: Preview = { }, }, backgrounds: { - default: 'light', - values: [ - { + options: { + light: { name: 'light', value: '#ffffff', }, - { + + dark: { name: 'dark', value: '#020618', }, - { + + muted: { name: 'muted', value: '#f1f5f9', - }, - ], + } + } }, }, + + initialGlobals: { + backgrounds: { + value: 'light' + } + } }; export default preview; diff --git a/eslint.config.js b/eslint.config.js index ff5b323..75362de 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,3 +1,6 @@ +// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format +import storybook from "eslint-plugin-storybook"; + import js from '@eslint/js'; import typescript from '@typescript-eslint/eslint-plugin'; import typescriptParser from '@typescript-eslint/parser'; @@ -5,40 +8,37 @@ import react from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks'; import globals from 'globals'; -export default [ - js.configs.recommended, - { - files: ['**/*.{ts,tsx}'], - languageOptions: { - parser: typescriptParser, - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - ...globals.browser, - ...globals.es2021, +export default [js.configs.recommended, { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, }, }, - plugins: { - '@typescript-eslint': typescript, - 'react': react, - 'react-hooks': reactHooks, + globals: { + ...globals.browser, + ...globals.es2021, }, - rules: { - ...typescript.configs.recommended.rules, - ...react.configs.recommended.rules, - ...reactHooks.configs.recommended.rules, - 'react/react-in-jsx-scope': 'off', - 'react/prop-types': 'off', - }, - settings: { - react: { - version: 'detect', - }, + }, + plugins: { + '@typescript-eslint': typescript, + 'react': react, + 'react-hooks': reactHooks, + }, + rules: { + ...typescript.configs.recommended.rules, + ...react.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + }, + settings: { + react: { + version: 'detect', }, }, -]; +}, ...storybook.configs["flat/recommended"]]; diff --git a/package.json b/package.json index 4c48ce0..d0b77f0 100644 --- a/package.json +++ b/package.json @@ -49,16 +49,15 @@ "homepage": "https://github.com/atxp-dev/design-system#readme", "sideEffects": false, "peerDependencies": { - "lucide-react": "^0.562.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", + "lucide-react": "^0.562.0", "sonner": "^2.0.7" }, "devDependencies": { "@eslint/js": "^9.39.2", "@storybook/addon-designs": "^11.1.1", "@storybook/addon-links": "^10.2.0", - "@storybook/react": "^10.2.0", "@storybook/react-vite": "^10.2.0", "@tailwindcss/cli": "^4.1.18", "@tailwindcss/postcss": "^4.1.18", @@ -79,7 +78,8 @@ "tailwindcss": "^4.1.18", "tsup": "^8.5.1", "typescript": "^5.9.3", - "vite": "^7.3.0" + "vite": "^7.3.0", + "eslint-plugin-storybook": "10.2.0" }, "dependencies": { "@radix-ui/react-accordion": "^1.2.12", @@ -90,8 +90,7 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "tailwind-merge": "^3.4.0" + "clsx": "^2.1.1" }, "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402" -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8f5509..06c6154 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,9 +41,6 @@ importers: sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - tailwind-merge: - specifier: ^3.4.0 - version: 3.4.0 devDependencies: '@eslint/js': specifier: ^9.39.2 @@ -54,9 +51,6 @@ importers: '@storybook/addon-links': specifier: ^10.2.0 version: 10.2.0(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) - '@storybook/react': - specifier: ^10.2.0 - version: 10.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) '@storybook/react-vite': specifier: ^10.2.0 version: 10.2.0(esbuild@0.27.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.53.5)(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3)(vite@7.3.0(jiti@2.6.1)(lightningcss@1.30.2)) @@ -93,6 +87,9 @@ importers: eslint-plugin-react-hooks: specifier: ^7.0.1 version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-storybook: + specifier: 10.2.0 + version: 10.2.0(eslint@9.39.2(jiti@2.6.1))(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3) globals: specifier: ^16.5.0 version: 16.5.0 @@ -1780,6 +1777,12 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-storybook@10.2.0: + resolution: {integrity: sha512-OtQJ153FOusr8bIMzccjkfMFJEex/3NFx0iXZ+UaeQ0WXearQ+37EGgBay3onkFElyu8AySggq/fdTknPAEvPA==} + peerDependencies: + eslint: '>=8' + storybook: ^10.2.0 + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2758,9 +2761,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwind-merge@3.4.0: - resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} - tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} hasBin: true @@ -4703,6 +4703,15 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-storybook@10.2.0(eslint@9.39.2(jiti@2.6.1))(storybook@10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.9.3): + dependencies: + '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + storybook: 10.2.0(@testing-library/dom@10.4.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - supports-color + - typescript + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -5735,8 +5744,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwind-merge@3.4.0: {} - tailwindcss@4.1.18: {} tapable@2.3.0: {} diff --git a/src/components/Accordion/Accordion.stories.tsx b/src/components/Accordion/Accordion.stories.tsx index 0a8c867..b0ed071 100644 --- a/src/components/Accordion/Accordion.stories.tsx +++ b/src/components/Accordion/Accordion.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { Accordion, AccordionContent, diff --git a/src/components/Alert/Alert.stories.tsx b/src/components/Alert/Alert.stories.tsx index 42fc6cb..145d72b 100644 --- a/src/components/Alert/Alert.stories.tsx +++ b/src/components/Alert/Alert.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Alert, AlertDescription, AlertTitle } from './Alert'; import { Icon, AlertCircle } from '@/components/Icon'; diff --git a/src/components/AlertDialog/AlertDialog.stories.tsx b/src/components/AlertDialog/AlertDialog.stories.tsx index 3665776..0c0f311 100644 --- a/src/components/AlertDialog/AlertDialog.stories.tsx +++ b/src/components/AlertDialog/AlertDialog.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { useState } from 'react'; import { AlertDialog, diff --git a/src/components/Avatar/Avatar.stories.tsx b/src/components/Avatar/Avatar.stories.tsx index 8a6bc60..8d62136 100644 --- a/src/components/Avatar/Avatar.stories.tsx +++ b/src/components/Avatar/Avatar.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Avatar, AvatarFallback, AvatarImage } from './Avatar'; /** diff --git a/src/components/AvatarGroup/AvatarGroup.stories.tsx b/src/components/AvatarGroup/AvatarGroup.stories.tsx index 37b70d4..04da1d0 100644 --- a/src/components/AvatarGroup/AvatarGroup.stories.tsx +++ b/src/components/AvatarGroup/AvatarGroup.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { AvatarGroup, AvatarGroupItem } from './AvatarGroup'; const meta = { diff --git a/src/components/Badge/Badge.stories.tsx b/src/components/Badge/Badge.stories.tsx index 81a970d..aee58b5 100644 --- a/src/components/Badge/Badge.stories.tsx +++ b/src/components/Badge/Badge.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Badge } from './Badge'; /** diff --git a/src/components/Breadcrumb/Breadcrumb.stories.tsx b/src/components/Breadcrumb/Breadcrumb.stories.tsx index a378ebc..6e37bde 100644 --- a/src/components/Breadcrumb/Breadcrumb.stories.tsx +++ b/src/components/Breadcrumb/Breadcrumb.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { Breadcrumb, BreadcrumbList, diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx index aba2383..03426e9 100644 --- a/src/components/Button/Button.stories.tsx +++ b/src/components/Button/Button.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Button } from './Button'; import { Icon, Plus } from '@/components/Icon'; diff --git a/src/components/ButtonGroup/ButtonGroup.stories.tsx b/src/components/ButtonGroup/ButtonGroup.stories.tsx index 59e6c2d..a9d9c86 100644 --- a/src/components/ButtonGroup/ButtonGroup.stories.tsx +++ b/src/components/ButtonGroup/ButtonGroup.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { ButtonGroup } from './ButtonGroup'; import { Button } from '@/components/Button'; import { Icon, Plus, ChevronDown, ExternalLink, Phone } from '@/components/Icon'; diff --git a/src/components/Card/Card.stories.tsx b/src/components/Card/Card.stories.tsx index 9ff8139..eec68ca 100644 --- a/src/components/Card/Card.stories.tsx +++ b/src/components/Card/Card.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { Card, CardContent, diff --git a/src/components/Carousel/Carousel.stories.tsx b/src/components/Carousel/Carousel.stories.tsx index cb36c96..a02f605 100644 --- a/src/components/Carousel/Carousel.stories.tsx +++ b/src/components/Carousel/Carousel.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { Carousel, CarouselContent, diff --git a/src/components/Checkbox/Checkbox.stories.tsx b/src/components/Checkbox/Checkbox.stories.tsx index 1569c0f..2ef80a7 100644 --- a/src/components/Checkbox/Checkbox.stories.tsx +++ b/src/components/Checkbox/Checkbox.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Checkbox } from './Checkbox'; /** diff --git a/src/components/Dialog/Dialog.stories.tsx b/src/components/Dialog/Dialog.stories.tsx index e18c5d3..4b7dff9 100644 --- a/src/components/Dialog/Dialog.stories.tsx +++ b/src/components/Dialog/Dialog.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { useState } from 'react'; import { Dialog, diff --git a/src/components/Drawer/Drawer.stories.tsx b/src/components/Drawer/Drawer.stories.tsx index 3fd9897..663e0d5 100644 --- a/src/components/Drawer/Drawer.stories.tsx +++ b/src/components/Drawer/Drawer.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { useState } from 'react'; import { Drawer, diff --git a/src/components/DropdownMenu/DropdownMenu.stories.tsx b/src/components/DropdownMenu/DropdownMenu.stories.tsx index 069621b..b4c6624 100644 --- a/src/components/DropdownMenu/DropdownMenu.stories.tsx +++ b/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { DropdownMenu, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from './DropdownMenu'; import { Avatar, AvatarFallback } from '@/components/Avatar'; import { Input } from '@/components/Input'; diff --git a/src/components/Header/Header.stories.tsx b/src/components/Header/Header.stories.tsx index cdba040..4eb5f21 100644 --- a/src/components/Header/Header.stories.tsx +++ b/src/components/Header/Header.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { Header, HeaderBreadcrumbs, diff --git a/src/components/HoverCard/HoverCard.stories.tsx b/src/components/HoverCard/HoverCard.stories.tsx index 6eae3fe..9817f7a 100644 --- a/src/components/HoverCard/HoverCard.stories.tsx +++ b/src/components/HoverCard/HoverCard.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { HoverCard, HoverCardHeader, diff --git a/src/components/Icon/Icon.stories.tsx b/src/components/Icon/Icon.stories.tsx index b3976b1..ac28e26 100644 --- a/src/components/Icon/Icon.stories.tsx +++ b/src/components/Icon/Icon.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Icon } from './Icon'; import * as Icons from 'lucide-react'; diff --git a/src/components/Indicators/Indicators.stories.tsx b/src/components/Indicators/Indicators.stories.tsx index 74802e9..ca63f16 100644 --- a/src/components/Indicators/Indicators.stories.tsx +++ b/src/components/Indicators/Indicators.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { IndicatorDot, IndicatorCircle } from './Indicators'; /** diff --git a/src/components/Input/Input.stories.tsx b/src/components/Input/Input.stories.tsx index 8b2e8d3..1f1f20c 100644 --- a/src/components/Input/Input.stories.tsx +++ b/src/components/Input/Input.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Input } from './Input'; import { Icon, Search } from '@/components/Icon'; diff --git a/src/components/InputGroup/InputGroup.stories.tsx b/src/components/InputGroup/InputGroup.stories.tsx index d669ce1..de61b41 100644 --- a/src/components/InputGroup/InputGroup.stories.tsx +++ b/src/components/InputGroup/InputGroup.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { InputGroup, InputGroupLabel, InputGroupHelpText } from './InputGroup'; import { Input } from '@/components/Input'; diff --git a/src/components/Label/Label.stories.tsx b/src/components/Label/Label.stories.tsx index 692a845..201bb81 100644 --- a/src/components/Label/Label.stories.tsx +++ b/src/components/Label/Label.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Label } from './Label'; /** diff --git a/src/components/Menubar/Menubar.stories.tsx b/src/components/Menubar/Menubar.stories.tsx index be9e5ef..30d66af 100644 --- a/src/components/Menubar/Menubar.stories.tsx +++ b/src/components/Menubar/Menubar.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Menubar, MenubarTrigger, diff --git a/src/components/ModelUserManagement/ModelUserManagement.stories.tsx b/src/components/ModelUserManagement/ModelUserManagement.stories.tsx index 4b3259d..fa889e6 100644 --- a/src/components/ModelUserManagement/ModelUserManagement.stories.tsx +++ b/src/components/ModelUserManagement/ModelUserManagement.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { ModelUserManagement, ModelSelector, diff --git a/src/components/NavHeader/NavHeader.stories.tsx b/src/components/NavHeader/NavHeader.stories.tsx index 7fc06f1..68f2aed 100644 --- a/src/components/NavHeader/NavHeader.stories.tsx +++ b/src/components/NavHeader/NavHeader.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { NavHeader, NavHeaderBrand, diff --git a/src/components/NavSidePanel/NavSidePanel.stories.tsx b/src/components/NavSidePanel/NavSidePanel.stories.tsx index 021dbd9..1685b3b 100644 --- a/src/components/NavSidePanel/NavSidePanel.stories.tsx +++ b/src/components/NavSidePanel/NavSidePanel.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { NavSidePanel, NavSidePanelHeader, diff --git a/src/components/Pagination/Pagination.stories.tsx b/src/components/Pagination/Pagination.stories.tsx index 38cfaa8..480dfd9 100644 --- a/src/components/Pagination/Pagination.stories.tsx +++ b/src/components/Pagination/Pagination.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Pagination, PaginationItem, diff --git a/src/components/Popover/Popover.stories.tsx b/src/components/Popover/Popover.stories.tsx index 12bc3ca..115a7e7 100644 --- a/src/components/Popover/Popover.stories.tsx +++ b/src/components/Popover/Popover.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Popover, PopoverHeader, diff --git a/src/components/Progress/Progress.stories.tsx b/src/components/Progress/Progress.stories.tsx index 0b7f0ca..92cef75 100644 --- a/src/components/Progress/Progress.stories.tsx +++ b/src/components/Progress/Progress.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Progress } from './Progress'; import { useState, useEffect } from 'react'; diff --git a/src/components/Radio/Radio.stories.tsx b/src/components/Radio/Radio.stories.tsx index f45dffe..5afd249 100644 --- a/src/components/Radio/Radio.stories.tsx +++ b/src/components/Radio/Radio.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Radio } from './Radio'; /** diff --git a/src/components/Scrollbar/Scrollbar.stories.tsx b/src/components/Scrollbar/Scrollbar.stories.tsx index 10b27a4..68f4640 100644 --- a/src/components/Scrollbar/Scrollbar.stories.tsx +++ b/src/components/Scrollbar/Scrollbar.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; const meta = { title: 'Design System/Scrollbar', diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index 7f934f3..463ce44 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Select, SelectContent, diff --git a/src/components/Separator/Separator.stories.tsx b/src/components/Separator/Separator.stories.tsx index f4fed3d..8cc3968 100644 --- a/src/components/Separator/Separator.stories.tsx +++ b/src/components/Separator/Separator.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Separator } from './Separator'; /** diff --git a/src/components/Sheet/Sheet.stories.tsx b/src/components/Sheet/Sheet.stories.tsx index 56ea78d..c3a6478 100644 --- a/src/components/Sheet/Sheet.stories.tsx +++ b/src/components/Sheet/Sheet.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { useState } from 'react'; import { Sheet, diff --git a/src/components/Skeleton/Skeleton.stories.tsx b/src/components/Skeleton/Skeleton.stories.tsx index a82cce8..2fd3802 100644 --- a/src/components/Skeleton/Skeleton.stories.tsx +++ b/src/components/Skeleton/Skeleton.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Skeleton } from './Skeleton'; /** diff --git a/src/components/Slider/Slider.stories.tsx b/src/components/Slider/Slider.stories.tsx index 85f09b4..a49e44a 100644 --- a/src/components/Slider/Slider.stories.tsx +++ b/src/components/Slider/Slider.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import * as React from 'react'; import { Slider } from './Slider'; diff --git a/src/components/Switch/Switch.stories.tsx b/src/components/Switch/Switch.stories.tsx index 64395b3..efb6daf 100644 --- a/src/components/Switch/Switch.stories.tsx +++ b/src/components/Switch/Switch.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import * as React from 'react'; import { Switch } from './Switch'; diff --git a/src/components/Table/Table.stories.tsx b/src/components/Table/Table.stories.tsx index efe0a78..90bc29c 100644 --- a/src/components/Table/Table.stories.tsx +++ b/src/components/Table/Table.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { Table, TableBody, diff --git a/src/components/Tabs/Tabs.stories.tsx b/src/components/Tabs/Tabs.stories.tsx index c7f84ff..62f1907 100644 --- a/src/components/Tabs/Tabs.stories.tsx +++ b/src/components/Tabs/Tabs.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs'; /** diff --git a/src/components/Textarea/Textarea.stories.tsx b/src/components/Textarea/Textarea.stories.tsx index 7fc45d3..65c21df 100644 --- a/src/components/Textarea/Textarea.stories.tsx +++ b/src/components/Textarea/Textarea.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import * as React from 'react'; import { Textarea } from './Textarea'; diff --git a/src/components/Theme/Theme.stories.tsx b/src/components/Theme/Theme.stories.tsx index 2bf187a..1e5c770 100644 --- a/src/components/Theme/Theme.stories.tsx +++ b/src/components/Theme/Theme.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { ThemeProvider, useTheme, Theme } from '@/lib/theme'; import { Button } from '@/components/Button'; import { Card } from '@/components/Card'; diff --git a/src/components/Toast/Toast.stories.tsx b/src/components/Toast/Toast.stories.tsx index 0274045..c2100d8 100644 --- a/src/components/Toast/Toast.stories.tsx +++ b/src/components/Toast/Toast.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta } from '@storybook/react-vite'; import { Toaster, toast } from './Toast'; import { Button } from '../Button'; diff --git a/src/components/Toggle/Toggle.stories.tsx b/src/components/Toggle/Toggle.stories.tsx index ab40ff2..621e8ab 100644 --- a/src/components/Toggle/Toggle.stories.tsx +++ b/src/components/Toggle/Toggle.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import * as React from 'react'; import { Toggle } from './Toggle'; import { Bold, Italic, Underline, Type } from 'lucide-react'; diff --git a/src/components/Tooltip/Tooltip.stories.tsx b/src/components/Tooltip/Tooltip.stories.tsx index 915fe41..bd32b9a 100644 --- a/src/components/Tooltip/Tooltip.stories.tsx +++ b/src/components/Tooltip/Tooltip.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; import { Tooltip } from './Tooltip'; const meta = { From eff72f2681a6ff3dab75acd3de3bcc4eb96a78d8 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 12:11:58 -0500 Subject: [PATCH 4/9] Add tailwind-merge dependency to package.json This dependency is required for the cn utility function to properly merge conflicting Tailwind classes. Without it, component size overrides don't work correctly (e.g., h-4 w-4 on Avatar doesn't override h-10 w-10). The previous pnpm add command updated node_modules but didn't save to package.json properly. --- package.json | 9 +++++---- pnpm-lock.yaml | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index d0b77f0..8acaec6 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,9 @@ "homepage": "https://github.com/atxp-dev/design-system#readme", "sideEffects": false, "peerDependencies": { + "lucide-react": "^0.562.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", - "lucide-react": "^0.562.0", "sonner": "^2.0.7" }, "devDependencies": { @@ -70,6 +70,7 @@ "eslint": "^9.39.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-storybook": "10.2.0", "globals": "^16.5.0", "postcss": "^8.5.6", "react": "^19.2.3", @@ -78,8 +79,7 @@ "tailwindcss": "^4.1.18", "tsup": "^8.5.1", "typescript": "^5.9.3", - "vite": "^7.3.0", - "eslint-plugin-storybook": "10.2.0" + "vite": "^7.3.0" }, "dependencies": { "@radix-ui/react-accordion": "^1.2.12", @@ -90,7 +90,8 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1" + "clsx": "^2.1.1", + "tailwind-merge": "^3.4.0" }, "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402" } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06c6154..be4ec33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 devDependencies: '@eslint/js': specifier: ^9.39.2 @@ -2761,6 +2764,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + tailwindcss@4.1.18: resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} hasBin: true @@ -5744,6 +5750,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@3.4.0: {} + tailwindcss@4.1.18: {} tapable@2.3.0: {} From 66730449e043d30bb78be07efce437ed543d1142 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 12:13:41 -0500 Subject: [PATCH 5/9] Add border to UserAvatars container for visibility The bg-background container was invisible on light backgrounds. Added border-input to match Figma design and provide visual separation. Also updated vanilla HTML examples to include the border. --- src/components/ModelUserManagement/ModelUserManagement.tsx | 2 +- vanilla/components/user-management.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ModelUserManagement/ModelUserManagement.tsx b/src/components/ModelUserManagement/ModelUserManagement.tsx index d25e317..6de30a8 100644 --- a/src/components/ModelUserManagement/ModelUserManagement.tsx +++ b/src/components/ModelUserManagement/ModelUserManagement.tsx @@ -109,7 +109,7 @@ const UserAvatars = React.forwardRef(
Model & User Management -
+
@@ -222,7 +222,7 @@

Model & User Management

-
+
@@ -261,7 +261,7 @@

Model & User Management

-
+
From a6f47fd764d12be8b6e52f52c26d09eee5367490 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 12:36:43 -0500 Subject: [PATCH 6/9] Standardize all ModelUserManagement component heights to 32px for proper alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ModelSelector: h-8 → h-[32px] - AddUserButton: h-[25px] w-[25px] → h-[32px] w-[32px] - UserAvatars: h-[29px] → h-[32px], p-0.5 pr-1 → px-1 - UserDropdownTrigger: h-[25px] w-[25px] → h-[32px] w-[32px] This ensures all elements in the component align vertically at the same baseline. --- .../ModelUserManagement/ModelUserManagement.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ModelUserManagement/ModelUserManagement.tsx b/src/components/ModelUserManagement/ModelUserManagement.tsx index 6de30a8..94b43a9 100644 --- a/src/components/ModelUserManagement/ModelUserManagement.tsx +++ b/src/components/ModelUserManagement/ModelUserManagement.tsx @@ -53,7 +53,7 @@ const ModelSelector = React.forwardRef( ref={ref} variant="secondary" size="sm" - className={cn('h-8', className)} + className={cn('h-[32px]', className)} {...props} > {children} @@ -89,7 +89,7 @@ const AddUserButton = React.forwardRef( type="button" aria-label="Add user" className={cn( - 'flex h-[25px] w-[25px] items-center justify-center rounded-full bg-muted transition-colors hover:bg-muted/80', + 'flex h-[32px] w-[32px] items-center justify-center rounded-full bg-muted transition-colors hover:bg-muted/80', className )} {...props} @@ -109,12 +109,12 @@ const UserAvatars = React.forwardRef(
- {children} + {children}
); } @@ -133,7 +133,7 @@ const UserDropdownTrigger = React.forwardRef< type="button" aria-label="User options" className={cn( - 'flex h-[25px] w-[25px] items-center justify-center rounded-full transition-colors hover:bg-muted', + 'flex h-[32px] w-[32px] items-center justify-center rounded-full transition-colors hover:bg-muted', className )} {...props} From 89faee9451ca9ec7736807c555d96cf6587b8860 Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 12:44:13 -0500 Subject: [PATCH 7/9] Fix UserDropdownTrigger to render inside UserAvatars container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restructured UserAvatars to detect and separate UserDropdownTrigger from avatar children - UserDropdownTrigger now rendered inside the white rounded container with avatars - Updated UserDropdownTrigger size from 32px to 25px to match Figma design - Changed padding: px-1 → p-0.5 with pr-1 on inner avatar wrapper - Updated all stories to nest UserDropdownTrigger inside UserAvatars This fixes the 'black circle behind avatars' issue - the chevron is now properly integrated into the user management UI. --- .../ModelUserManagement.stories.tsx | 16 ++++++------- .../ModelUserManagement.tsx | 24 +++++++++++++++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/components/ModelUserManagement/ModelUserManagement.stories.tsx b/src/components/ModelUserManagement/ModelUserManagement.stories.tsx index fa889e6..a4afbcc 100644 --- a/src/components/ModelUserManagement/ModelUserManagement.stories.tsx +++ b/src/components/ModelUserManagement/ModelUserManagement.stories.tsx @@ -33,8 +33,8 @@ export const Default: Story = { + - ), @@ -49,8 +49,8 @@ export const TwoUsers: Story = { + - ), @@ -66,8 +66,8 @@ export const ThreeUsers: Story = { + - ), @@ -85,8 +85,8 @@ export const WithPresenceIndicator: Story = { className="bg-orange-100" showPresence /> + - ), @@ -105,8 +105,8 @@ export const TwoUsersWithPresence: Story = { className="bg-green-100" showPresence /> + - ), @@ -121,8 +121,8 @@ export const DifferentModel: Story = { + - ), @@ -136,8 +136,8 @@ export const SingleUserNoPresence: Story = { + - ), @@ -154,8 +154,8 @@ export const WithCustomHandlers: Story = { + alert('User options')} /> - alert('User options')} /> ), diff --git a/src/components/ModelUserManagement/ModelUserManagement.tsx b/src/components/ModelUserManagement/ModelUserManagement.tsx index 94b43a9..65991c9 100644 --- a/src/components/ModelUserManagement/ModelUserManagement.tsx +++ b/src/components/ModelUserManagement/ModelUserManagement.tsx @@ -21,8 +21,8 @@ import { Icon, Plus, ChevronDown } from '@/components/Icon'; * * * + * * - * * * * ``` @@ -105,16 +105,32 @@ export type UserAvatarsProps = React.HTMLAttributes; const UserAvatars = React.forwardRef( ({ className, children, ...props }, ref) => { + // Separate avatar children from dropdown trigger + const childArray = React.Children.toArray(children); + const dropdownTrigger = childArray.find( + (child) => + React.isValidElement(child) && + (child.type as any).displayName === 'UserDropdownTrigger' + ); + const avatarChildren = childArray.filter( + (child) => + !React.isValidElement(child) || + (child.type as any).displayName !== 'UserDropdownTrigger' + ); + return (
- {children} +
+ {avatarChildren} +
+ {dropdownTrigger}
); } @@ -133,7 +149,7 @@ const UserDropdownTrigger = React.forwardRef< type="button" aria-label="User options" className={cn( - 'flex h-[32px] w-[32px] items-center justify-center rounded-full transition-colors hover:bg-muted', + 'flex h-[25px] w-[25px] items-center justify-center rounded-full transition-colors hover:bg-muted/50', className )} {...props} From b526f1c6c4e4c6a8435564f95f8c018d43ed350b Mon Sep 17 00:00:00 2001 From: Rob Di Marco Date: Fri, 23 Jan 2026 12:47:35 -0500 Subject: [PATCH 8/9] Fix TypeScript lint errors: replace 'as any' with proper type guard - Created isDropdownTrigger type guard function that properly checks for displayName - Checks: isValidElement, not string type, has displayName property, equals 'UserDropdownTrigger' - Eliminates @typescript-eslint/no-explicit-any errors --- .../ModelUserManagement.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/components/ModelUserManagement/ModelUserManagement.tsx b/src/components/ModelUserManagement/ModelUserManagement.tsx index 65991c9..f1dd727 100644 --- a/src/components/ModelUserManagement/ModelUserManagement.tsx +++ b/src/components/ModelUserManagement/ModelUserManagement.tsx @@ -105,18 +105,22 @@ export type UserAvatarsProps = React.HTMLAttributes; const UserAvatars = React.forwardRef( ({ className, children, ...props }, ref) => { + // Helper to check if element is UserDropdownTrigger + const isDropdownTrigger = ( + child: React.ReactNode + ): child is React.ReactElement => { + return ( + React.isValidElement(child) && + typeof child.type !== 'string' && + 'displayName' in child.type && + child.type.displayName === 'UserDropdownTrigger' + ); + }; + // Separate avatar children from dropdown trigger const childArray = React.Children.toArray(children); - const dropdownTrigger = childArray.find( - (child) => - React.isValidElement(child) && - (child.type as any).displayName === 'UserDropdownTrigger' - ); - const avatarChildren = childArray.filter( - (child) => - !React.isValidElement(child) || - (child.type as any).displayName !== 'UserDropdownTrigger' - ); + const dropdownTrigger = childArray.find(isDropdownTrigger); + const avatarChildren = childArray.filter((child) => !isDropdownTrigger(child)); return (
Date: Fri, 23 Jan 2026 12:52:40 -0500 Subject: [PATCH 9/9] Fix avatar sizing in AvatarGroup - make them inherit parent size - Changed wrapper div to use h-[inherit] w-[inherit] - Changed Avatar to use h-full w-full instead of default h-10 w-10 - This allows AvatarGroup's size variants to properly control avatar dimensions - Fixes issue where avatars rendered at 40px instead of 25px in ModelUserManagement --- src/components/AvatarGroup/AvatarGroup.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AvatarGroup/AvatarGroup.tsx b/src/components/AvatarGroup/AvatarGroup.tsx index 2e71a71..68f5abf 100644 --- a/src/components/AvatarGroup/AvatarGroup.tsx +++ b/src/components/AvatarGroup/AvatarGroup.tsx @@ -80,8 +80,8 @@ export interface AvatarGroupItemProps const AvatarGroupItem = React.forwardRef( ({ className, src, alt, fallback, showPresence, ...props }, ref) => { return ( -
- +
+ {src && } {fallback}