@devalok/shilp-sutra 0.18.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -98,7 +98,7 @@ const te = i(
98
98
  "div",
99
99
  {
100
100
  className: r(
101
- "flex h-full w-[--sidebar-width] flex-col bg-surface-1 text-surface-fg",
101
+ "flex h-full w-[--sidebar-width] flex-col bg-surface-2 text-surface-fg",
102
102
  d
103
103
  ),
104
104
  ref: u,
@@ -110,7 +110,7 @@ const te = i(
110
110
  {
111
111
  "data-sidebar": "sidebar",
112
112
  "data-mobile": "true",
113
- className: "w-[--sidebar-width] bg-surface-1 p-0 text-surface-fg [&>button]:hidden",
113
+ className: "w-[--sidebar-width] bg-surface-2 p-0 text-surface-fg [&>button]:hidden",
114
114
  style: {
115
115
  "--sidebar-width": Q
116
116
  },
@@ -152,7 +152,7 @@ const te = i(
152
152
  "div",
153
153
  {
154
154
  "data-sidebar": "sidebar",
155
- className: "flex h-full w-full flex-col bg-surface-1 group-data-[variant=floating]:rounded-ds-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-surface-border group-data-[variant=floating]:shadow",
155
+ className: "flex h-full w-full flex-col bg-surface-2 group-data-[variant=floating]:rounded-ds-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-surface-border group-data-[variant=floating]:shadow",
156
156
  children: o
157
157
  }
158
158
  )
@@ -202,7 +202,7 @@ const re = i(
202
202
  "hover:after:bg-surface-border-strong absolute inset-y-0 z-raised hidden w-4 -translate-x-1/2 transition-colors ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
203
203
  "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
204
204
  "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
205
- "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-surface-2",
205
+ "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-surface-3",
206
206
  "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
207
207
  "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
208
208
  a
@@ -234,7 +234,7 @@ const de = i(({ className: a, ...e }, t) => /* @__PURE__ */ s(
234
234
  ref: t,
235
235
  "data-sidebar": "input",
236
236
  className: r(
237
- "h-ds-sm w-full bg-surface-1 shadow-none focus-visible:ring-2 focus-visible:ring-accent-9",
237
+ "h-ds-sm w-full bg-surface-2 shadow-none focus-visible:ring-2 focus-visible:ring-accent-9",
238
238
  a
239
239
  ),
240
240
  ...e
@@ -325,7 +325,7 @@ const pe = i(({ className: a, asChild: e = !1, ...t }, d) => /* @__PURE__ */ s(
325
325
  ref: d,
326
326
  "data-sidebar": "group-action",
327
327
  className: r(
328
- "hover:bg-surface-2 absolute right-ds-04 top-ds-04 flex aspect-square w-5 items-center justify-center rounded-ds-md p-0 text-surface-fg outline-none ring-accent-9 transition-transform hover:text-surface-fg focus-visible:ring-2 [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0",
328
+ "hover:bg-surface-3 absolute right-ds-04 top-ds-04 flex aspect-square w-5 items-center justify-center rounded-ds-md p-0 text-surface-fg outline-none ring-accent-9 transition-transform hover:text-surface-fg focus-visible:ring-2 [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0",
329
329
  "after:absolute after:-inset-2 after:md:hidden",
330
330
  "group-data-[collapsible=icon]:hidden",
331
331
  a
@@ -371,12 +371,12 @@ const ge = i(
371
371
  );
372
372
  ge.displayName = "SidebarMenuItem";
373
373
  const he = T(
374
- "peer/menu-button hover:bg-surface-2 active:bg-accent-2 data-[active=true]:bg-accent-2 data-[state=open]:hover:bg-surface-2 flex w-full items-center gap-ds-03 overflow-hidden rounded-ds-md p-ds-03 text-left outline-none ring-accent-9 transition-[width,height,padding] hover:text-surface-fg focus-visible:ring-2 active:text-surface-fg disabled:pointer-events-none disabled:opacity-action-disabled group-has-[[data-sidebar=menu-action]]/menu-item:pr-ds-07 aria-disabled:pointer-events-none aria-disabled:opacity-action-disabled data-[active=true]:font-medium data-[active=true]:text-surface-fg data-[state=open]:hover:text-surface-fg group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-ds-03 [&>span:last-child]:truncate [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0",
374
+ "peer/menu-button hover:bg-surface-3 active:bg-accent-2 data-[active=true]:bg-accent-2 data-[state=open]:hover:bg-surface-3 flex w-full items-center gap-ds-03 overflow-hidden rounded-ds-md p-ds-03 text-left outline-none ring-accent-9 transition-[width,height,padding] hover:text-surface-fg focus-visible:ring-2 active:text-surface-fg disabled:pointer-events-none disabled:opacity-action-disabled group-has-[[data-sidebar=menu-action]]/menu-item:pr-ds-07 aria-disabled:pointer-events-none aria-disabled:opacity-action-disabled data-[active=true]:font-medium data-[active=true]:text-surface-fg data-[state=open]:hover:text-surface-fg group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-ds-03 [&>span:last-child]:truncate [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0",
375
375
  {
376
376
  variants: {
377
377
  variant: {
378
- default: "hover:bg-surface-2 hover:text-surface-fg",
379
- outline: "hover:bg-surface-2 bg-surface-1 shadow-[0_0_0_1px_var(--color-surface-border)] hover:text-surface-fg hover:shadow-[0_0_0_1px_var(--color-surface-border-strong)]"
378
+ default: "hover:bg-surface-3 hover:text-surface-fg",
379
+ outline: "hover:bg-surface-3 bg-surface-2 shadow-[0_0_0_1px_var(--color-surface-border)] hover:text-surface-fg hover:shadow-[0_0_0_1px_var(--color-surface-border-strong)]"
380
380
  },
381
381
  size: {
382
382
  md: "h-ds-sm text-ds-md",
@@ -445,7 +445,7 @@ const xe = i(({ className: a, asChild: e = !1, showOnHover: t = !1, ...d }, o) =
445
445
  ref: o,
446
446
  "data-sidebar": "menu-action",
447
447
  className: r(
448
- "hover:bg-surface-2 absolute right-ds-02 top-ds-02b flex aspect-square w-5 items-center justify-center rounded-ds-md p-0 text-surface-fg outline-none ring-accent-9 transition-transform hover:text-surface-fg focus-visible:ring-2 peer-hover/menu-button:text-surface-fg [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0",
448
+ "hover:bg-surface-3 absolute right-ds-02 top-ds-02b flex aspect-square w-5 items-center justify-center rounded-ds-md p-0 text-surface-fg outline-none ring-accent-9 transition-transform hover:text-surface-fg focus-visible:ring-2 peer-hover/menu-button:text-surface-fg [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0",
449
449
  "after:absolute after:-inset-2 after:md:hidden",
450
450
  "peer-data-[size=sm]/menu-button:top-1",
451
451
  "peer-data-[size=md]/menu-button:top-ds-02b",
@@ -538,7 +538,7 @@ const Me = i(({ asChild: a = !1, size: e = "md", isActive: t, className: d, ...o
538
538
  "data-size": e,
539
539
  "data-active": t,
540
540
  className: r(
541
- "hover:bg-surface-2 active:bg-accent-2 flex h-ds-xs-plus min-w-0 -translate-x-px items-center gap-ds-03 overflow-hidden rounded-ds-md px-ds-03 text-surface-fg outline-none ring-accent-9 hover:text-surface-fg focus-visible:ring-2 active:text-surface-fg disabled:pointer-events-none disabled:opacity-action-disabled aria-disabled:pointer-events-none aria-disabled:opacity-action-disabled [&>span:last-child]:truncate [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0 [&>svg]:text-surface-fg",
541
+ "hover:bg-surface-3 active:bg-accent-2 flex h-ds-xs-plus min-w-0 -translate-x-px items-center gap-ds-03 overflow-hidden rounded-ds-md px-ds-03 text-surface-fg outline-none ring-accent-9 hover:text-surface-fg focus-visible:ring-2 active:text-surface-fg disabled:pointer-events-none disabled:opacity-action-disabled aria-disabled:pointer-events-none aria-disabled:opacity-action-disabled [&>span:last-child]:truncate [&>svg]:h-ico-sm [&>svg]:w-ico-sm [&>svg]:shrink-0 [&>svg]:text-surface-fg",
542
542
  "data-[active=true]:bg-accent-2 data-[active=true]:text-surface-fg",
543
543
  e === "sm" && "text-ds-sm",
544
544
  e === "md" && "text-ds-md",
@@ -42,10 +42,10 @@ Color tokens use OKLCH (perceptually uniform) with 12 functional steps per palet
42
42
  | 2 | Subtle background | Sidebar, card alt |
43
43
  | 3 | Component bg | Input bg, badge bg |
44
44
  | 4 | Component bg hover | Button hover state |
45
- | 5 | Component bg active | Active/pressed state |
46
- | 6 | Border subtle | Dividers, soft borders |
47
- | 7 | Border default | Input borders, card borders |
48
- | 8 | Border strong | Focus rings, emphasis borders |
45
+ | 5 | Border subtle | Semantic `surface-border` in light mode |
46
+ | 6 | Border default | Semantic `surface-border-strong` in light mode |
47
+ | 7 | Border strong | Focus rings, emphasis borders |
48
+ | 8 | Border emphasis | High-contrast outlines |
49
49
  | 9 | Solid / accent | Button bg, primary CTA |
50
50
  | 10 | Solid hover | Button hover bg |
51
51
  | 11 | Low-contrast text | Secondary accent text |
@@ -54,7 +54,9 @@ Color tokens use OKLCH (perceptually uniform) with 12 functional steps per palet
54
54
  Semantic layer:
55
55
  - Accent (swappable): --color-accent-{1-12} + --color-accent-fg
56
56
  - Secondary: --color-secondary-{1-12} + --color-secondary-fg
57
- - Surface: --color-surface-{1-4} + --color-surface-fg / fg-muted / fg-subtle / border
57
+ - Surface: --color-surface-{1-4} + --color-surface-fg / fg-muted / fg-subtle / border / border-strong
58
+ - Border mapping: light mode border=step5, border-strong=step6; dark mode border=step3, border-strong=step4
59
+ - Shell chrome (sidebar, topbar, bottom nav) uses surface-2 for elevation above surface-1 app background
58
60
  - Status: --color-{error,success,warning,info}-{3,7,9,11}
59
61
  - Category: --color-category-{teal,amber,slate,indigo,cyan,orange,emerald}
60
62
 
@@ -6,15 +6,15 @@
6
6
 
7
7
  ## Props
8
8
  items: ActivityItem[] (REQUIRED) — { id, actor?: { name, image? }, action: string|ReactNode, timestamp: Date|string, icon?, color?: 'default'|'success'|'warning'|'error'|'info', detail?: ReactNode }
9
- onLoadMore: () => void — "Load more" button callback
9
+ onLoadMore?: () => void — "Load more" button callback
10
10
  loading: boolean — skeleton shimmer
11
- hasMore: boolean — shows "Load more" button
12
- emptyState: ReactNode — empty state content
11
+ hasMore?: boolean — shows "Load more" button
12
+ emptyState?: ReactNode — empty state content
13
13
  compact: boolean — tighter spacing, no avatars, smaller text
14
14
  maxInitialItems: number — truncate with "Show all (N)" toggle
15
15
 
16
16
  ## Defaults
17
- loading=false, compact=false
17
+ loading=false, compact=false, hasMore=false
18
18
 
19
19
  ## Example
20
20
  ```jsx
@@ -5,7 +5,7 @@
5
5
  - Category: composed
6
6
 
7
7
  ## Props
8
- groups: CommandGroup[] — { label: string, items: CommandItem[] }
8
+ groups?: CommandGroup[] (default: []) — { label: string, items: CommandItem[] }
9
9
  placeholder: string (default: "Search or jump to...")
10
10
  onSearch: (query: string) => void
11
11
  emptyMessage: string (default: "No results found.")
@@ -13,7 +13,7 @@
13
13
  CommandItem shape: { id, label, description?, icon?, shortcut?, onSelect: () => void }
14
14
 
15
15
  ## Defaults
16
- placeholder="Search or jump to...", emptyMessage="No results found."
16
+ placeholder="Search or jump to...", emptyMessage="No results found.", groups=[]
17
17
 
18
18
  ## Example
19
19
  ```jsx
@@ -12,7 +12,7 @@
12
12
  confirmText: string (default: "Confirm")
13
13
  cancelText: string (default: "Cancel")
14
14
  color: "default" | "error" (controls confirm button color)
15
- loading: boolean (default: false, disables buttons and shows spinner)
15
+ loading: boolean (default: false, disables buttons and replaces confirm button text with 'Processing...')
16
16
  onConfirm: () => void | Promise<void> (REQUIRED)
17
17
 
18
18
  ## Defaults
@@ -7,8 +7,8 @@
7
7
  ## Props
8
8
 
9
9
  ### DatePicker
10
- value: Date | null
11
- onChange: (date: Date | null) => void
10
+ value?: Date | null
11
+ onChange?: (date: Date | null) => void
12
12
  placeholder: string (default: "Pick a date")
13
13
  formatStr: string (default: "MMM d, yyyy")
14
14
  minDate: Date
@@ -17,9 +17,9 @@
17
17
  className: string
18
18
 
19
19
  ### DateRangePicker
20
- startDate: Date | null
21
- endDate: Date | null
22
- onChange: (range: { start: Date | null, end: Date | null }) => void
20
+ startDate?: Date | null
21
+ endDate?: Date | null
22
+ onChange?: (range: { start: Date | null, end: Date | null }) => void
23
23
  placeholder: string (default: "Pick a date range")
24
24
  formatStr: string (default: "MMM d, yyyy")
25
25
  minDate: Date
@@ -40,8 +40,8 @@
40
40
  className: string
41
41
 
42
42
  ### TimePicker
43
- value: Date | null (time stored as a Date object)
44
- onChange: (date: Date) => void
43
+ value?: Date | null (time stored as a Date object)
44
+ onChange?: (date: Date) => void
45
45
  format: "12h" | "24h" (default: "12h")
46
46
  minuteStep: number (default: 1)
47
47
  secondStep: number (default: 1)
@@ -52,14 +52,14 @@
52
52
 
53
53
  ### CalendarGrid
54
54
  currentMonth: Date (REQUIRED)
55
- selected: Date | null
56
- rangeStart: Date | null
57
- rangeEnd: Date | null
58
- hoverDate: Date | null
55
+ selected?: Date | null
56
+ rangeStart?: Date | null
57
+ rangeEnd?: Date | null
58
+ hoverDate?: Date | null
59
59
  onSelect: (date: Date) => void (REQUIRED)
60
- onHover: (date: Date | null) => void
60
+ onHover?: (date: Date | null) => void
61
61
  onMonthChange: (date: Date) => void (REQUIRED)
62
- onHeaderClick: () => void
62
+ onHeaderClick?: () => void
63
63
  disabledDates: (date: Date) => boolean
64
64
  minDate: Date
65
65
  maxDate: Date
@@ -8,8 +8,8 @@
8
8
  view: "day" | "week" (REQUIRED)
9
9
  date: Date (REQUIRED — current day or any date in target week)
10
10
  events: ScheduleEvent[] (REQUIRED) — { id, title, start: Date, end: Date, color? }
11
- onEventClick: (event: ScheduleEvent) => void
12
- onSlotClick: (start: Date, end: Date) => void
11
+ onEventClick?: (event: ScheduleEvent) => void
12
+ onSlotClick?: (start: Date, end: Date) => void
13
13
  startHour: number (default: 8)
14
14
  endHour: number (default: 18, exclusive)
15
15
  slotDuration: number (minutes, default: 30)
@@ -15,6 +15,7 @@ Note: StatusBadge was server-safe prior to v0.18.0 but is NO LONGER server-safe
15
15
 
16
16
  ## Defaults
17
17
  size="md", hideDot=false
18
+ When neither status nor color is passed, defaults to status='pending' styling
18
19
 
19
20
  ## Example
20
21
  ```jsx
@@ -35,6 +35,10 @@ BottomNavbarUser: { name: string, role?: string }
35
35
  - Requires LinkProvider for framework-specific link components (e.g., Next.js Link)
36
36
 
37
37
  ## Changes
38
+ ### v0.19.0
39
+ - **Changed** Background elevated from `bg-surface-1` to `bg-surface-2` for visual hierarchy above app background
40
+ - **Changed** "More" menu and interactive items bumped accordingly
41
+
38
42
  ### v0.18.0
39
43
  - **Fixed** Removed incorrect `role="button"` and `tabIndex` from overlay
40
44
 
@@ -6,21 +6,21 @@
6
6
 
7
7
  ## Props
8
8
  notifications?: Notification[]
9
- unreadCount: number (derived from notifications if not provided)
10
- open: boolean (controlled mode)
11
- onOpenChange: (open: boolean) => void
12
- isLoading: boolean
13
- hasMore: boolean
14
- onFetchMore: () => void
15
- onMarkRead: (id: string) => void
16
- onMarkAllRead: () => void
17
- onNavigate: (path: string) => void — called when a notification with a route is clicked
18
- getNotificationRoute: (notification: Notification) => string | null — returns route for a notification; defaults to () => null
19
- footerSlot: ReactNode — content rendered in a sticky footer below the scroll area
20
- emptyState: ReactNode — replaces default empty state UI
21
- headerActions: ReactNode — extra action buttons after "Mark all read"
22
- popoverClassName: string — override default popover dimensions
23
- onDismiss: (id: string) => void — when provided, each notification shows a dismiss button
9
+ unreadCount?: number (derived from notifications if not provided)
10
+ open?: boolean (controlled mode)
11
+ onOpenChange?: (open: boolean) => void
12
+ isLoading?: boolean
13
+ hasMore?: boolean
14
+ onFetchMore?: () => void
15
+ onMarkRead?: (id: string) => void
16
+ onMarkAllRead?: () => void
17
+ onNavigate?: (path: string) => void — called when a notification with a route is clicked
18
+ getNotificationRoute?: (notification: Notification) => string | null — returns route for a notification; defaults to () => null
19
+ footerSlot?: ReactNode — content rendered in a sticky footer below the scroll area
20
+ emptyState?: ReactNode — replaces default empty state UI
21
+ headerActions?: ReactNode — extra action buttons after "Mark all read"
22
+ popoverClassName?: string — override default popover dimensions
23
+ onDismiss?: (id: string) => void — when provided, each notification shows a dismiss button
24
24
 
25
25
  Notification: { id: string, title: string, body?: string | null, tier: 'INFO' | 'IMPORTANT' | 'CRITICAL', isRead: boolean, createdAt: string, entityType?: string | null, entityId?: string | null, projectId?: string | null, project?: { title: string } | null, actions?: NotificationAction[] }
26
26
  NotificationAction: { label: string, variant?: 'primary' | 'default' | 'danger', onClick: (id: string) => void }
@@ -21,7 +21,7 @@ NavItem: { title: string, href: string, icon: ReactNode, exact?: boolean, badge?
21
21
  NavSubItem: { title: string, href: string, icon?: ReactNode, exact?: boolean }
22
22
  NavGroup: { label: string, items: NavItem[], action?: ReactNode }
23
23
  SidebarUser: { name: string, email?: string, image?: string | null, designation?: string, role?: string }
24
- SidebarFooterConfig: { links: Array<{ label: string, href: string }>, version: string | { label: string, href: string }, slot: ReactNode, promo: SidebarPromo }
24
+ SidebarFooterConfig: { links?: Array<{ label: string, href: string }>, version?: string | { label: string, href: string }, slot?: ReactNode, promo?: SidebarPromo }
25
25
  SidebarPromo: { text: string, icon?: ReactNode, action?: { label: string, href?: string, onClick?: () => void }, onDismiss?: () => void }
26
26
 
27
27
  ## Defaults
@@ -57,6 +57,10 @@ SidebarPromo: { text: string, icon?: ReactNode, action?: { label: string, href?:
57
57
  - Badge numbers > 99 display as "99+"
58
58
 
59
59
  ## Changes
60
+ ### v0.19.0
61
+ - **Changed** Background elevated from `bg-surface-1` to `bg-surface-2` for visual hierarchy above app background
62
+ - **Changed** Interactive hover states bumped from `surface-2` to `surface-3`
63
+
60
64
  ### v0.18.0
61
65
  - **Fixed** `bg-interactive-subtle` changed to `bg-accent-2` (OKLCH migration)
62
66
 
@@ -4,16 +4,54 @@
4
4
  - Server-safe: No
5
5
  - Category: shell
6
6
 
7
+ ## Overview
8
+
9
+ Composition-based application top bar. Uses dot-notation subcomponents for flexible layout.
10
+
11
+ ## Subcomponents
12
+
13
+ | Component | Purpose |
14
+ |-----------|---------|
15
+ | `TopBar` | Root — bg-surface-2, border-b, sticky. Auto-switches to grid when Center is present. |
16
+ | `TopBar.Left` | Left zone — sidebar trigger, title, breadcrumbs |
17
+ | `TopBar.Center` | Optional center zone — search bar, tabs. Triggers 3-column grid layout. |
18
+ | `TopBar.Right` | Right zone — action buttons, user menu. Gets ml-auto in flex mode. |
19
+ | `TopBar.Section` | Groups items with configurable gap |
20
+ | `TopBar.IconButton` | Circular icon button with tooltip (bg-surface-3, hover:bg-surface-4) |
21
+ | `TopBar.Title` | Page title heading, hidden on mobile |
22
+ | `TopBar.UserMenu` | Avatar dropdown with color mode toggle, profile, logout |
23
+
7
24
  ## Props
8
- pageTitle?: string (default: "")
9
- user?: TopBarUser | null — { name, email?, image? }
25
+
26
+ ### TopBar (root)
27
+ children: ReactNode
28
+ className?: string
29
+
30
+ ### TopBar.Left / TopBar.Center / TopBar.Right
31
+ children: ReactNode
32
+ className?: string
33
+
34
+ ### TopBar.Section
35
+ gap?: "tight" | "default" | "loose" (default: "default")
36
+ children: ReactNode
37
+ className?: string
38
+
39
+ Gap values: tight = gap-ds-02, default = gap-ds-04, loose = gap-ds-06
40
+
41
+ ### TopBar.IconButton
42
+ icon: ReactNode
43
+ tooltip: string
44
+ ...ButtonHTMLAttributes
45
+
46
+ ### TopBar.Title
47
+ children: ReactNode
48
+ className?: string
49
+
50
+ ### TopBar.UserMenu
51
+ user: TopBarUser — { name, email?, image? }
10
52
  onNavigate?: (path: string) => void
11
53
  onLogout?: () => void
12
- onSearchClick?: () => void
13
- onAiChatClick?: () => void
14
- mobileLogo?: ReactNode
15
- notificationSlot?: ReactNode (render NotificationCenter here)
16
- userMenuItems?: UserMenuItem[] — custom items between Profile and Dark/Light Mode toggle
54
+ userMenuItems?: UserMenuItem[]
17
55
  className?: string
18
56
 
19
57
  TopBarUser: { name: string, email?: string, image?: string | null }
@@ -27,30 +65,69 @@ UserMenuItem fields:
27
65
  - badge — string for count badge, true for dot indicator
28
66
  - disabled — greys out the item
29
67
 
30
- ## Defaults
31
- None
32
-
33
68
  ## Example
69
+
70
+ ### Two-zone (standard)
34
71
  ```jsx
35
- <TopBar
36
- pageTitle="Dashboard"
37
- user={{ name: 'John', email: 'john@example.com' }}
38
- onNavigate={(p) => router.push(p)}
39
- onLogout={handleLogout}
40
- notificationSlot={<NotificationCenter notifications={notifications} />}
41
- userMenuItems={[
42
- { label: 'Changelog', icon: <IconNews />, href: '/changelog', badge: '3' },
43
- { label: 'Shortcuts', icon: <IconKeyboard />, onClick: () => openModal() },
44
- ]}
45
- />
72
+ <TopBar>
73
+ <TopBar.Left>
74
+ <SidebarTrigger />
75
+ <TopBar.Title>Dashboard</TopBar.Title>
76
+ </TopBar.Left>
77
+ <TopBar.Right>
78
+ <TopBar.Section gap="tight">
79
+ <TopBar.IconButton icon={<IconSearch />} tooltip="Search (Ctrl+K)" onClick={openSearch} />
80
+ <NotificationCenter notifications={notifications} />
81
+ <TopBar.IconButton icon={<IconSparkles />} tooltip="AI Chat" onClick={openAI} />
82
+ </TopBar.Section>
83
+ <TopBar.UserMenu
84
+ user={{ name: 'John', email: 'john@example.com' }}
85
+ onNavigate={(p) => router.push(p)}
86
+ onLogout={handleLogout}
87
+ userMenuItems={[
88
+ { label: 'Changelog', icon: <IconNews />, href: '/changelog', badge: '3' },
89
+ ]}
90
+ />
91
+ </TopBar.Right>
92
+ </TopBar>
93
+ ```
94
+
95
+ ### Three-zone (centered search bar)
96
+ ```jsx
97
+ <TopBar>
98
+ <TopBar.Left>
99
+ <SidebarTrigger />
100
+ <TopBar.Title>Dashboard</TopBar.Title>
101
+ </TopBar.Left>
102
+ <TopBar.Center>
103
+ <SearchBarTrigger />
104
+ </TopBar.Center>
105
+ <TopBar.Right>
106
+ <TopBar.Section gap="tight">
107
+ <TopBar.IconButton icon={<IconBell />} tooltip="Notifications" onClick={fn} />
108
+ </TopBar.Section>
109
+ <TopBar.UserMenu user={user} onLogout={logout} />
110
+ </TopBar.Right>
111
+ </TopBar>
46
112
  ```
47
113
 
48
114
  ## Gotchas
49
- - `notificationSlot` is where NotificationCenter should be rendered
50
- - `userMenuItems` are inserted between the Profile link and the Dark/Light Mode toggle in the user dropdown
51
- - Requires LinkProvider for framework-specific navigation
115
+ - Without `TopBar.Center`, layout is flex (two-zone). With it, layout switches to CSS grid `1fr auto 1fr` for true centering.
116
+ - `TopBar.IconButton` renders any number of action buttons no artificial limit. Use responsive hiding (`className="hidden md:flex"`) for mobile.
117
+ - `TopBar.UserMenu` includes Profile link, color mode toggle, and logout automatically. `userMenuItems` are inserted between Profile and the toggle.
118
+ - Requires SidebarProvider wrapper for SidebarTrigger to work.
52
119
 
53
120
  ## Changes
121
+ ### v0.19.0
122
+ - **BREAKING** Rewritten as composition API. Old props-based API removed (`pageTitle`, `onSearchClick`, `onAiChatClick`, `notificationSlot`, `mobileLogo` props).
123
+ - **Added** `TopBar.Left`, `TopBar.Center`, `TopBar.Right` zone components
124
+ - **Added** `TopBar.Section` with `gap` prop (`tight` | `default` | `loose`)
125
+ - **Added** `TopBar.IconButton` — reusable circular icon button with tooltip
126
+ - **Added** `TopBar.Title` — responsive page title (hidden on mobile)
127
+ - **Added** `TopBar.UserMenu` — extracted user dropdown as standalone subcomponent
128
+ - **Added** Auto grid/flex layout detection based on Center zone presence
129
+ - **Changed** Background elevated from `bg-surface-1` to `bg-surface-2`
130
+
54
131
  ### v0.7.0
55
132
  - **Added** `userMenuItems` prop for custom dropdown items
56
133
 
@@ -7,7 +7,7 @@
7
7
  ## Props
8
8
  options: AutocompleteOption[] (REQUIRED) — { value: string, label: string }
9
9
  value: AutocompleteOption | null
10
- onValueChange: (option: AutocompleteOption) => void
10
+ onValueChange?: (option: AutocompleteOption) => void
11
11
  placeholder: string
12
12
  emptyText: string (default: "No options")
13
13
  disabled: boolean
@@ -23,6 +23,7 @@
23
23
  ## Gotchas
24
24
  - Banner is full-width (spans container). Alert is inline.
25
25
  - Renders role="alert" automatically
26
+ - `onDismiss` fires after the exit animation completes, not immediately on dismiss button click
26
27
 
27
28
  ## Changes
28
29
  ### v0.3.1
@@ -30,6 +30,7 @@
30
30
  - MUST use label prop — children are NOT rendered
31
31
  - `<Chip>text</Chip>` is WRONG — use `<Chip label="text" />`
32
32
  - Wrap dynamic chip lists in `<ChipGroup>` for exit animations
33
+ - `color="primary"` will be renamed to `color="brand"` in v1.0 — use `color="primary"` for now
33
34
 
34
35
  ## Changes
35
36
  ### v0.4.2
@@ -7,8 +7,8 @@
7
7
  ## Props
8
8
  options: ComboboxOption[] (REQUIRED) — { value: string, label: string, description?: string, icon?: ReactNode, disabled?: boolean }
9
9
  DISCRIMINATED UNION — type depends on `multiple` flag:
10
- Single (default): multiple?: false, value: string, onValueChange: (value: string) => void
11
- Multiple: multiple: true, value: string[], onValueChange: (value: string[]) => void
10
+ Single (default): multiple?: false, value?: string, onValueChange: (value: string) => void
11
+ Multiple: multiple: true, value?: string[], onValueChange: (value: string[]) => void
12
12
  placeholder: string (default: "Select...")
13
13
  searchPlaceholder: string (default: "Search...")
14
14
  emptyMessage: string (default: "No results found")
@@ -26,7 +26,7 @@
26
26
  - HTML native "size" attribute is excluded — use CSS width instead
27
27
  - state="error" sets aria-invalid automatically
28
28
  - Inside FormField: auto-inherits state, aria-describedby, aria-required from context (explicit props override)
29
- - Resting border is border-subtle (soft); focus ring is ring-1 at 50% opacity (v0.12.0)
29
+ - Resting border is border-subtle (soft); focus ring is `ring-1 ring-accent-7` (v0.12.0)
30
30
  - All sizes (sm, md, lg) use text-ds-md (14px) font — size only affects height and padding (v0.15.0)
31
31
 
32
32
  ## Changes
@@ -10,6 +10,7 @@
10
10
  variant: "filled" | "bare"
11
11
  delay: number (ms — render delay to avoid flicker on fast operations)
12
12
  onComplete: () => void (callback after success/error state transition)
13
+ className: string
13
14
 
14
15
  ## Defaults
15
16
  size: "md"
@@ -43,7 +43,7 @@
43
43
 
44
44
  ## Gotchas
45
45
  - variant goes on TabsList, NOT on individual TabsTrigger (propagates via context)
46
- - DO NOT put variant on TabsTrigger — it inherits from TabsList
46
+ - Normally omit `variant` on TabsTrigger — it inherits from TabsList via context. You CAN set it per-trigger to override.
47
47
 
48
48
  ## Changes
49
49
  ### v0.18.0