@skunkworksai/ui-kit 0.1.18 → 0.1.25-a

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.
Files changed (126) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +2 -0
  3. package/dist/index.cjs +34 -34
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +14469 -14225
  6. package/dist/index.js.map +1 -1
  7. package/dist/types/assets/icons/DashboardActiveIcon.d.ts +1 -1
  8. package/dist/types/assets/icons/DashboardIcon.d.ts +1 -1
  9. package/dist/types/assets/icons/DevicesActiveIcon.d.ts +1 -1
  10. package/dist/types/assets/icons/DevicesIcon.d.ts +1 -1
  11. package/dist/types/assets/icons/InstallerActiveIcon.d.ts +1 -1
  12. package/dist/types/assets/icons/InstallerIcon.d.ts +1 -1
  13. package/dist/types/assets/icons/ManifestActiveIcon.d.ts +1 -1
  14. package/dist/types/assets/icons/ManifestIcon.d.ts +1 -1
  15. package/dist/types/assets/icons/SensorActiveIcon.d.ts +1 -1
  16. package/dist/types/assets/icons/SensorIcon.d.ts +1 -1
  17. package/dist/types/assets/icons/SiteActiveIcon.d.ts +1 -1
  18. package/dist/types/assets/icons/SiteIcon.d.ts +1 -1
  19. package/dist/types/assets/icons/XrayActiveIcon.d.ts +1 -1
  20. package/dist/types/assets/icons/XrayIcon.d.ts +1 -1
  21. package/dist/types/assets/logos/DarkLogo.d.ts +1 -1
  22. package/dist/types/assets/logos/DarkSymbol.d.ts +1 -1
  23. package/dist/types/assets/logos/PrimaryLogo.d.ts +1 -1
  24. package/dist/types/assets/logos/PrimarySymbol.d.ts +1 -1
  25. package/dist/types/assets/logos/WhiteLogo.d.ts +1 -1
  26. package/dist/types/assets/logos/WhiteSymbol.d.ts +1 -1
  27. package/dist/types/components/blocks/AppBentoMenu.d.ts +2 -2
  28. package/dist/types/components/blocks/AppDashboard.d.ts +9 -3
  29. package/dist/types/components/blocks/AppHeader.d.ts +6 -6
  30. package/dist/types/components/blocks/AppLocationTreeFilter.d.ts +1 -1
  31. package/dist/types/components/blocks/AppNotificationsMenu.d.ts +1 -1
  32. package/dist/types/components/blocks/AppPageTitle.d.ts +1 -1
  33. package/dist/types/components/blocks/AppProfile.d.ts +1 -1
  34. package/dist/types/components/blocks/AppWizardStepper.d.ts +61 -0
  35. package/dist/types/components/blocks/TableManager.d.ts +36 -14
  36. package/dist/types/components/blocks/backgrounds/AppDashboardBackgroundDots.d.ts +1 -1
  37. package/dist/types/components/blocks/backgrounds/AppDashboardBackgroundWave.d.ts +1 -1
  38. package/dist/types/components/blocks/backgrounds/types.d.ts +2 -0
  39. package/dist/types/components/blocks/index.d.ts +1 -0
  40. package/dist/types/components/primitives/AppAccordion.d.ts +4 -4
  41. package/dist/types/components/primitives/AppAlert.d.ts +1 -1
  42. package/dist/types/components/primitives/AppAlertDialog.d.ts +2 -2
  43. package/dist/types/components/primitives/AppAspectRatio.d.ts +1 -1
  44. package/dist/types/components/primitives/AppAvatar.d.ts +3 -3
  45. package/dist/types/components/primitives/AppBadge.d.ts +1 -1
  46. package/dist/types/components/primitives/AppBreadcrumb.d.ts +7 -7
  47. package/dist/types/components/primitives/AppButton.d.ts +1 -1
  48. package/dist/types/components/primitives/AppButtonGroup.d.ts +1 -1
  49. package/dist/types/components/primitives/AppCalendar.d.ts +1 -1
  50. package/dist/types/components/primitives/AppCard.d.ts +7 -7
  51. package/dist/types/components/primitives/AppCarousel.d.ts +5 -5
  52. package/dist/types/components/primitives/AppChart.d.ts +3 -3
  53. package/dist/types/components/primitives/AppCheckbox.d.ts +1 -1
  54. package/dist/types/components/primitives/AppCollapsible.d.ts +1 -1
  55. package/dist/types/components/primitives/AppCombobox.d.ts +7 -7
  56. package/dist/types/components/primitives/AppCommand.d.ts +9 -9
  57. package/dist/types/components/primitives/AppContextMenu.d.ts +7 -7
  58. package/dist/types/components/primitives/AppDataTable.d.ts +2 -1
  59. package/dist/types/components/primitives/AppDatePicker.d.ts +2 -1
  60. package/dist/types/components/primitives/AppDialog.d.ts +6 -6
  61. package/dist/types/components/primitives/AppDrawer.d.ts +3 -3
  62. package/dist/types/components/primitives/AppDropdownMenu.d.ts +10 -10
  63. package/dist/types/components/primitives/AppEmpty.d.ts +6 -6
  64. package/dist/types/components/primitives/AppField.d.ts +10 -10
  65. package/dist/types/components/primitives/AppForm.d.ts +2 -2
  66. package/dist/types/components/primitives/AppHoverCard.d.ts +3 -3
  67. package/dist/types/components/primitives/AppInput.d.ts +10 -1
  68. package/dist/types/components/primitives/AppKbd.d.ts +2 -2
  69. package/dist/types/components/primitives/AppLabel.d.ts +1 -1
  70. package/dist/types/components/primitives/AppMenubar.d.ts +6 -6
  71. package/dist/types/components/primitives/AppNavigationMenu.d.ts +8 -8
  72. package/dist/types/components/primitives/AppPagination.d.ts +7 -7
  73. package/dist/types/components/primitives/AppPopover.d.ts +3 -3
  74. package/dist/types/components/primitives/AppProgress.d.ts +1 -1
  75. package/dist/types/components/primitives/AppRadioGroup.d.ts +2 -2
  76. package/dist/types/components/primitives/AppScrollArea.d.ts +2 -2
  77. package/dist/types/components/primitives/AppSelect.d.ts +7 -7
  78. package/dist/types/components/primitives/AppSeparator.d.ts +1 -1
  79. package/dist/types/components/primitives/AppSheet.d.ts +6 -6
  80. package/dist/types/components/primitives/AppSidebar.d.ts +24 -24
  81. package/dist/types/components/primitives/AppSkeleton.d.ts +1 -1
  82. package/dist/types/components/primitives/AppSwitch.d.ts +1 -1
  83. package/dist/types/components/primitives/AppTable.d.ts +8 -8
  84. package/dist/types/components/primitives/AppTabs.d.ts +4 -4
  85. package/dist/types/components/primitives/AppTextarea.d.ts +7 -1
  86. package/dist/types/components/primitives/AppThemeProvider.d.ts +2 -2
  87. package/dist/types/components/primitives/AppToaster.d.ts +1 -1
  88. package/dist/types/components/primitives/AppToggle.d.ts +1 -1
  89. package/dist/types/components/primitives/AppTooltip.d.ts +4 -4
  90. package/dist/types/components/primitives/AppTypography.d.ts +1 -1
  91. package/dist/types/components/ui/alert-dialog.d.ts +2 -2
  92. package/dist/types/components/ui/badge.d.ts +1 -1
  93. package/dist/types/components/ui/breadcrumb.d.ts +2 -2
  94. package/dist/types/components/ui/button.d.ts +5 -2
  95. package/dist/types/components/ui/calendar.d.ts +1 -1
  96. package/dist/types/components/ui/card.d.ts +7 -7
  97. package/dist/types/components/ui/carousel.d.ts +5 -5
  98. package/dist/types/components/ui/chart.d.ts +4 -4
  99. package/dist/types/components/ui/combobox.d.ts +7 -7
  100. package/dist/types/components/ui/command.d.ts +9 -9
  101. package/dist/types/components/ui/context-menu.d.ts +1 -1
  102. package/dist/types/components/ui/dialog.d.ts +2 -2
  103. package/dist/types/components/ui/drawer.d.ts +2 -2
  104. package/dist/types/components/ui/dropdown-menu.d.ts +1 -1
  105. package/dist/types/components/ui/form.d.ts +2 -2
  106. package/dist/types/components/ui/input.d.ts +1 -1
  107. package/dist/types/components/ui/label.d.ts +1 -1
  108. package/dist/types/components/ui/menubar.d.ts +16 -16
  109. package/dist/types/components/ui/pagination.d.ts +5 -5
  110. package/dist/types/components/ui/scroll-text.d.ts +1 -1
  111. package/dist/types/components/ui/sheet.d.ts +2 -2
  112. package/dist/types/components/ui/sidebar.d.ts +23 -23
  113. package/dist/types/components/ui/skeleton.d.ts +1 -1
  114. package/dist/types/components/ui/sonner.d.ts +1 -1
  115. package/dist/types/hooks/use-page-title.d.ts +10 -1
  116. package/dist/ui-kit.css +1 -1
  117. package/package.json +29 -19
  118. package/rules/button.md +1 -0
  119. package/rules/dashboard.md +96 -4
  120. package/rules/header.md +23 -2
  121. package/rules/input.md +34 -2
  122. package/rules/pagetitle.md +32 -0
  123. package/rules/sidebar.md +21 -0
  124. package/rules/table-manager.md +44 -1
  125. package/rules/textarea.md +23 -1
  126. package/rules/wizard-stepper.md +207 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skunkworksai/ui-kit",
3
- "version": "0.1.18",
3
+ "version": "0.1.25-a",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -48,7 +48,7 @@
48
48
  "react-dom": ">=18"
49
49
  },
50
50
  "dependencies": {
51
- "@base-ui/react": "^1.3.0",
51
+ "@base-ui/react": "^1.5.0",
52
52
  "@radix-ui/react-accordion": "^1.2.0",
53
53
  "@radix-ui/react-alert-dialog": "^1.1.0",
54
54
  "@radix-ui/react-aspect-ratio": "^1.1.0",
@@ -62,7 +62,7 @@
62
62
  "@radix-ui/react-label": "^2.1.0",
63
63
  "@radix-ui/react-navigation-menu": "^1.2.0",
64
64
  "@radix-ui/react-popover": "^1.1.0",
65
- "@radix-ui/react-progress": "^1.1.0",
65
+ "@radix-ui/react-progress": "^1.1.8",
66
66
  "@radix-ui/react-radio-group": "^1.1.0",
67
67
  "@radix-ui/react-scroll-area": "^1.1.0",
68
68
  "@radix-ui/react-select": "^2.1.0",
@@ -78,40 +78,50 @@
78
78
  "class-variance-authority": "^0.7.1",
79
79
  "clsx": "^2.1.1",
80
80
  "cmdk": "^1.0.0",
81
- "date-fns": "^4.1.0",
81
+ "date-fns": "^4.4.0",
82
82
  "embla-carousel": "^8.6.0",
83
83
  "embla-carousel-react": "^8.6.0",
84
- "lucide-react": "^0.577.0",
85
- "motion": "^12.38.0",
84
+ "lucide-react": "^1.17.0",
85
+ "motion": "^12.40.0",
86
86
  "radix-ui": "^1.4.3",
87
- "react-day-picker": "^9.4.0",
88
- "react-hook-form": "^7.54.0",
87
+ "react-day-picker": "^10.0.1",
88
+ "react-hook-form": "^7.77.0",
89
89
  "recharts": "^3.8.1",
90
- "shadcn": "^4.1.0",
91
- "sonner": "^1.7.0",
92
- "tailwind-merge": "^3.5.0",
90
+ "sonner": "^2.0.7",
91
+ "tailwind-merge": "^3.6.0",
93
92
  "tw-animate-css": "^1.4.0"
94
93
  },
95
94
  "devDependencies": {
95
+ "@eslint/js": "^10.0.1",
96
96
  "@storybook/addon-a11y": "^8",
97
97
  "@storybook/addon-docs": "^8",
98
98
  "@storybook/addon-onboarding": "^8",
99
99
  "@storybook/react": "^8",
100
100
  "@storybook/react-vite": "^8",
101
101
  "@tailwindcss/vite": "^4",
102
- "@types/node": "^20",
102
+ "@types/node": "^25",
103
103
  "@types/react": "^19",
104
104
  "@types/react-dom": "^19",
105
- "@vitejs/plugin-react-swc": "^3",
106
- "eslint": "^9",
107
- "react": "19.2.4",
108
- "react-dom": "19.2.4",
105
+ "@vitejs/plugin-react-swc": "^4",
106
+ "eslint": "^10",
107
+ "eslint-plugin-react-hooks": "^7.1.1",
108
+ "eslint-plugin-react-refresh": "^0.5.2",
109
+ "globals": "^17.6.0",
110
+ "react": "19.2.7",
111
+ "react-dom": "19.2.7",
112
+ "shadcn": "^4.10.0",
109
113
  "storybook": "^8",
110
114
  "tailwindcss": "^4",
111
115
  "tailwindcss-animate": "^1.0.7",
112
- "typescript": "^5",
116
+ "typescript": "^6",
117
+ "typescript-eslint": "^8.60.1",
113
118
  "vite": "^6",
114
- "vite-plugin-checker": "^0.9"
119
+ "vite-plugin-checker": "^0.14"
115
120
  },
116
- "packageManager": "pnpm@10.32.1"
121
+ "packageManager": "pnpm@10.32.1",
122
+ "pnpm": {
123
+ "overrides": {
124
+ "ws@>=8.0.0 <8.20.1": ">=8.20.1"
125
+ }
126
+ }
117
127
  }
package/rules/button.md CHANGED
@@ -61,6 +61,7 @@ import { AppButton } from "@/components/primitives/AppButton"
61
61
  - ✅ Use the `loading` prop for async actions — do not place `AppSpinner` inside manually
62
62
  - ✅ Use `iconLeft` / `iconRight` for icons — do not wrap icons in extra `<div>` or `<span>`
63
63
  - ✅ Use `variant` for color intent — do not override button color with Tailwind color classes
64
+ - ✅ `AppButton` forwards refs to the underlying `<button>` element — safe to use as `asChild` child of Radix primitives (e.g. `PopoverTrigger`, `TooltipTrigger`)
64
65
  - ❌ Do not use `size="icon"` with text children — icon size is for icon-only buttons
65
66
  - ❌ Do not set `gap` or `flex` on the button — icon spacing is controlled by the size variant
66
67
  - ❌ If a prop you need is missing, stop and inform the design team
@@ -22,6 +22,7 @@ import { AppDashboard } from '@skunkworksai/ui-kit'
22
22
  | `background` | `ReactNode` | `<AppDashboardBackgroundWave />` | Background preset or custom component. Must render gradient. See Background presets section. |
23
23
  | `pageTitle` | `AppPageTitleProps \| false` | omitted | Page title bar below the header. Pass `false` to hide entirely. |
24
24
  | `brand` | `Omit<AppSidebarBrandProps, "className">` | `WhiteLogo` / `WhiteSymbol` | Props forwarded to `AppSidebarBrand`. Use to set a custom logo for expanded and collapsed sidebar states. |
25
+ | `sidebarFooter` | `ReactNode` | `undefined` | Content rendered at the bottom of the sidebar inside `AppSidebarFooter`, before the rail. Omit to render no footer. |
25
26
  | `children` | `ReactNode` | required | Page content. |
26
27
 
27
28
  ## Customising the header
@@ -133,6 +134,58 @@ import { AppDashboardBackgroundWave, AppDashboardBackgroundDots } from '@skunkwo
133
134
  | `AppDashboardBackgroundWave` | Perlin noise wave grid + mouse-follow glow. Default. |
134
135
  | `AppDashboardBackgroundDots` | Uniform dot grid. Dots fade near cursor (closer = lower opacity). |
135
136
 
137
+ ### Background preset props
138
+
139
+ Both presets share the following props in addition to `animated` and `interactive`:
140
+
141
+ #### `AppDashboardBackgroundDots`
142
+
143
+ | Prop | Type | Default | Description |
144
+ |------|------|---------|-------------|
145
+ | `color` | `string` | `--sidebar-ring` | Dot color. Any valid CSS color string: hex, oklch, hsl, or `var(--token)`. |
146
+
147
+ #### `AppDashboardBackgroundWave`
148
+
149
+ | Prop | Type | Default | Description |
150
+ |------|------|---------|-------------|
151
+ | `color` | `string` | `--sidebar-ring` | Wave line color. Any valid CSS color string: hex, oklch, hsl, or `var(--token)`. |
152
+ | `glowColor` | `string` | `--sidebar-primary` / `--sidebar-accent` | Cursor glow color. Replaces both glow layers with a single color mixed at different opacities. |
153
+
154
+ ### Customizing background color
155
+
156
+ ```tsx
157
+ import { AppDashboardBackgroundWave, AppDashboardBackgroundDots } from '@skunkworks-ai/ui-kit'
158
+
159
+ // Wave with custom line + glow color (oklch, hex, hsl, or CSS variable — all valid)
160
+ <AppDashboard background={
161
+ <AppDashboardBackgroundWave
162
+ color="oklch(0.75 0.18 150)"
163
+ glowColor="oklch(0.75 0.18 150)"
164
+ />
165
+ }>
166
+ ...
167
+ </AppDashboard>
168
+
169
+ // Dots with custom dot color
170
+ <AppDashboard background={
171
+ <AppDashboardBackgroundDots color="#ff6b35" />
172
+ }>
173
+ ...
174
+ </AppDashboard>
175
+
176
+ // Using a design token from the consumer's theme
177
+ <AppDashboard background={
178
+ <AppDashboardBackgroundWave
179
+ color="var(--brand-primary)"
180
+ glowColor="var(--brand-primary)"
181
+ />
182
+ }>
183
+ ...
184
+ </AppDashboard>
185
+ ```
186
+
187
+ When `color` is omitted, both presets fall back to the resolved value of `--sidebar-ring`. When `glowColor` is omitted on Wave, the glow uses `--sidebar-primary` and `--sidebar-accent`.
188
+
136
189
  ### Custom background contract
137
190
 
138
191
  Custom backgrounds receive `animated`, `interactive`, and `themeClass` from `AppDashboardBackgroundContext`:
@@ -183,9 +236,11 @@ The canvas animation runs by default. Use `animated` to control it:
183
236
 
184
237
  **When `animated="auto"` (default):** animation runs normally unless the user's OS has `prefers-reduced-motion: reduce` set, in which case the canvas is skipped automatically. No consumer code needed for accessibility compliance.
185
238
 
186
- ## AppThemeProvider requirement
239
+ ## AppThemeProvider
240
+
241
+ `AppDashboard` includes `AppThemeProvider` internally — the theme toggle works out of the box with no extra setup required.
187
242
 
188
- The default header includes a theme toggle that requires `AppThemeProvider` in the tree:
243
+ If you use `AppHeader` standalone (outside `AppDashboard`), wrap your layout with `AppThemeProvider` for the toggle to function:
189
244
 
190
245
  ```tsx
191
246
  import { AppThemeProvider } from '@skunkworksai/ui-kit'
@@ -201,8 +256,6 @@ export default function RootLayout({ children }) {
201
256
  }
202
257
  ```
203
258
 
204
- Without it, the toggle renders but does nothing.
205
-
206
259
  ## Nav types
207
260
 
208
261
  ```ts
@@ -345,6 +398,45 @@ const nav = [
345
398
  </AppDashboard>
346
399
  ```
347
400
 
401
+ ## Sidebar footer
402
+
403
+ Use the `sidebarFooter` prop to render content at the bottom of the sidebar (inside `AppSidebarFooter`, before the rail). The consumer owns the markup — pass any `ReactNode`.
404
+
405
+ ```tsx
406
+ // Wallet-style footer card
407
+ <AppDashboard
408
+ sidebarFooter={
409
+ <div className="rounded-lg bg-sidebar-accent p-3 group-data-[collapsible=icon]:hidden">
410
+ <p className="text-xs font-medium text-sidebar-foreground">Acme Corp</p>
411
+ <p className="text-xs text-sidebar-foreground/60">Free plan · 3 seats</p>
412
+ </div>
413
+ }
414
+ >
415
+ <PageContent />
416
+ </AppDashboard>
417
+ ```
418
+
419
+ Omitting `sidebarFooter` renders no `AppSidebarFooter` element at all — no extra whitespace.
420
+
421
+ ### Auto-hiding on icon collapse
422
+
423
+ When the sidebar collapses to icon mode, content that doesn't make sense at 40 px wide should be hidden. Use the `group-data-[collapsible=icon]:hidden` Tailwind class on any element inside `sidebarFooter`:
424
+
425
+ ```tsx
426
+ // The entire card hides when collapsed; nothing renders in icon mode
427
+ <div className="... group-data-[collapsible=icon]:hidden">
428
+ ...
429
+ </div>
430
+
431
+ // Hide only the text, keep an avatar visible
432
+ <div className="flex items-center gap-2">
433
+ <Avatar className="size-7 shrink-0" />
434
+ <span className="group-data-[collapsible=icon]:hidden text-sm">Kevin Aton</span>
435
+ </div>
436
+ ```
437
+
438
+ `group-data-[collapsible=icon]` matches the `data-collapsible="icon"` attribute set on the sidebar group element by `AppSidebarProvider` when the sidebar is collapsed to icon mode.
439
+
348
440
  ## Rules
349
441
 
350
442
  - **Do** use `AppDashboard` as the layout for every page — do not compose the scaffold manually.
package/rules/header.md CHANGED
@@ -20,7 +20,7 @@ All right-side controls are included by default. Each can be removed or replaced
20
20
  |------|------|---------|-------------|
21
21
  | `searchbar` | `ReactNode` | standard search input | Searchbar slot. Pass `<AppInput />` to customise. `null` removes it entirely. |
22
22
  | `actions` | `ReactNode` | network combobox + bento + theme toggle + avatar | Entire right-side slot. `null` removes all actions. Overrides all other action props. |
23
- | `avatar` | `ReactNode` | `AppAvatar` with fallback `"KA"` | Avatar inside default actions. `null` hides it. Ignored when `actions` is provided. |
23
+ | `avatar` | `ReactNode` | `AppAvatar` with `UserRound` icon | Avatar inside default actions. `null` hides it. Ignored when `actions` is provided. |
24
24
  | `networkSelect` | `ReactNode` | `AppHeaderNetworkCombobox` | Network picker inside default actions. `null` hides it. Pass a custom node to override. Ignored when `actions` is provided. |
25
25
  | `networks` | `NetworkOption[]` | 2 demo entries | List of networks for the built-in combobox. Ignored when `networkSelect` or `actions` is provided. |
26
26
  | `currentNetwork` | `string` | first network in list | Controlled selected network. Omit for uncontrolled. Ignored when `networkSelect` or `actions` is provided. |
@@ -108,7 +108,7 @@ Use these only when building a layout outside `AppDashboard`:
108
108
  |-----------|---------|
109
109
  | `AppHeaderContent` | Inner flex container with padding and gap. |
110
110
  | `AppHeaderTitle` | Optional page label. `shrink-0`, never compresses. |
111
- | `AppHeaderSearchbar` | `flex-1` slot for search input groups. |
111
+ | `AppHeaderSearchbar` | `flex-1 max-w-sm` slot for search input groups. Hidden on mobile (`hidden md:flex`) by default. |
112
112
  | `AppHeaderActions` | `ml-auto` right-side slot for controls. |
113
113
  | `AppHeaderNetworkCombobox` | Standalone network combobox — use inside a custom `actions` slot. |
114
114
 
@@ -163,6 +163,8 @@ Customize via the `header` prop — never import `AppHeader` directly for this:
163
163
 
164
164
  Only needed when building a custom layout that does not use `AppDashboard`:
165
165
 
166
+ > **Note:** `AppThemeProvider` is required for the theme toggle to function. When using `AppHeader` inside `AppDashboard`, this is handled automatically. For standalone usage, wrap your layout with `AppThemeProvider`.
167
+
166
168
  ```tsx
167
169
  import { AppHeader, type NetworkOption } from '@skunkworks/ui'
168
170
 
@@ -197,6 +199,8 @@ With custom slots:
197
199
 
198
200
  The `searchbar` slot accepts any `ReactNode`. Always fill it with an `App*` component — never a raw `<input>`.
199
201
 
202
+ `AppHeaderSearchbar` defaults to `hidden md:flex min-w-0 flex-1 max-w-sm`. The `max-w-sm` (384px) cap prevents searchbar content from stretching full-width when the consumer does not supply an explicit width. To widen, pass `className="max-w-md"` (or larger). Hidden on mobile — the mobile search icon + Sheet handles that path automatically.
203
+
200
204
  ```tsx
201
205
  // ✅ Correct
202
206
  import { AppInput } from '@skunkworksai/ui-kit'
@@ -226,6 +230,23 @@ import { AppInput } from '@skunkworksai/ui-kit'
226
230
  <AppDashboard header={{ searchbar: null }}>
227
231
  ```
228
232
 
233
+ ## Consumer Tailwind setup
234
+
235
+ `AppHeader` uses `md:hidden` to hide the mobile search icon (`AppHeaderSearchbar`) at desktop widths. If the consumer's `@tailwindcss/vite` plugin injects its utilities layer **after** the ui-kit bundle, it overwrites same-named utilities and `md:hidden` stops working.
236
+
237
+ **Required** — add this to the consumer's `app.css` (or equivalent global CSS entry):
238
+
239
+ ```css
240
+ @layer theme, base, components, utilities;
241
+ @import "tailwindcss/utilities" layer(utilities);
242
+ ```
243
+
244
+ This explicitly names the `utilities` layer so both the ui-kit bundle and the consumer's Tailwind output merge into the same layer in document order.
245
+
246
+ **Symptom of missing setup:** the mobile search icon is visible at all breakpoints instead of hiding at `md`.
247
+
248
+ See `rules/tokens.md` for the full consumer setup checklist.
249
+
229
250
  ## Rules
230
251
 
231
252
  - **Do** provide `networks` with real data — the default 2-entry list is a demo placeholder only.
package/rules/input.md CHANGED
@@ -12,10 +12,13 @@ import { AppInput } from "@/components/primitives/AppInput"
12
12
  |------|------|---------|-------------|
13
13
  | `leftAdornment` | `ReactNode` | — | Icon or element rendered inside the left edge of the input |
14
14
  | `rightAdornment` | `ReactNode` | — | Icon or element rendered inside the right edge of the input |
15
+ | `wrapperClassName` | `string` | — | Applied to the outer wrapper `<div>` when adornments are present. Use to override the default `w-full`. Silently ignored when no adornments are set. |
15
16
  | `type` | `string` | `"text"` | HTML input type (text, email, password, number, etc.) |
16
17
  | `placeholder` | `string` | — | Placeholder text |
17
18
  | `disabled` | `boolean` | `false` | Disables the input |
18
- | `className` | `string` | — | Additional Tailwind classes applied to the input element |
19
+ | `className` | `string` | — | Additional Tailwind classes applied to the inner `<input>` element |
20
+
21
+ > `className` always routes to the inner `<input>`. Use `wrapperClassName` to control the outer wrapper size/layout when adornments are in use.
19
22
 
20
23
  > Accepts all standard HTML `<input>` attributes in addition to the props above.
21
24
 
@@ -43,6 +46,33 @@ import { AppInput } from "@/components/primitives/AppInput"
43
46
  />
44
47
  ```
45
48
 
49
+ ### Controlling wrapper width with adornments
50
+
51
+ Without adornments `className` width classes land directly on `<input>`. With adornments you must use `wrapperClassName` — `className` targets the inner element and a width there does not constrain the wrapper.
52
+
53
+ ```tsx
54
+ {/* Fixed width on desktop, full-width on mobile */}
55
+ <AppInput
56
+ leftAdornment={<Search className="size-4" />}
57
+ placeholder="Search..."
58
+ wrapperClassName="w-full md:w-64"
59
+ />
60
+
61
+ {/* Fill remaining space in a flex row */}
62
+ <AppInput
63
+ leftAdornment={<Search className="size-4" />}
64
+ placeholder="Search..."
65
+ wrapperClassName="flex-1"
66
+ />
67
+
68
+ {/* Intrinsic width */}
69
+ <AppInput
70
+ leftAdornment={<Search className="size-4" />}
71
+ placeholder="Search..."
72
+ wrapperClassName="w-auto"
73
+ />
74
+ ```
75
+
46
76
  ### Disabled
47
77
  ```tsx
48
78
  <AppInput disabled placeholder="Not editable" />
@@ -59,8 +89,10 @@ import { AppInput } from "@/components/primitives/AppInput"
59
89
  ## Rules
60
90
  - ✅ Always use `AppInput` — never use a raw `<input>` element
61
91
  - ✅ Use `leftAdornment` / `rightAdornment` for icons inside the input — do not wrap `AppInput` in a relative `<div>` to position them manually
92
+ - ✅ Use `wrapperClassName` to control width/layout when adornments are present — `className` targets the inner `<input>` only
62
93
  - ✅ Pair with `AppLabel` using matching `id` and `htmlFor` — never use a raw `<label>`
63
94
  - ✅ Pair with `AppField` or `AppForm` for full form field layout with error states
64
95
  - ❌ Do not add `pl-*` or `pr-*` padding when using adornments — padding is adjusted automatically
65
- - ❌ Do not set `width` directly on `AppInput` — control width from the parent container
96
+ - ❌ Do not add width classes via `className` when adornments are present they land on `<input>`, not the wrapper; use `wrapperClassName` instead
97
+ - ❌ `wrapperClassName` has no effect without adornments — without `leftAdornment` or `rightAdornment` there is no wrapper element
66
98
  - ❌ If a prop you need is missing, stop and inform the design team
@@ -143,12 +143,44 @@ When `breadcrumbs` is omitted, the component reads `window.location.pathname` on
143
143
  </AppDashboard>
144
144
  ```
145
145
 
146
+ ## usePageTitle — ReactNode prop stability
147
+
148
+ `usePageTitle` compares props by identity, not by value. Primitive props (`title`, `subtitle`, `titleColor`) are compared by value and are safe to pass inline. ReactNode props (`actions`, `breadcrumbs`, `icon`) are compared by **reference** — passing them as inline JSX causes the effect to re-run on every render.
149
+
150
+ **Always memoize ReactNode props:**
151
+
152
+ ```tsx
153
+ // ✅ Correct — stable reference
154
+ const actions = React.useMemo(
155
+ () => <AppButton onClick={handleAdd}>+ Add Site</AppButton>,
156
+ [handleAdd]
157
+ )
158
+ usePageTitle({ title: "Sites", actions })
159
+
160
+ // ✅ Also correct — module-scope constant (never changes)
161
+ const BREADCRUMBS = [
162
+ { label: "Dashboard", href: "/" },
163
+ { label: "Sites" },
164
+ ]
165
+ usePageTitle({ title: "Sites", breadcrumbs: BREADCRUMBS })
166
+
167
+ // ❌ Wrong — new object/JSX every render, causes effect churn
168
+ usePageTitle({
169
+ title: "Sites",
170
+ actions: <AppButton>+ Add Site</AppButton>,
171
+ breadcrumbs: [{ label: "Dashboard", href: "/" }, { label: "Sites" }],
172
+ })
173
+ ```
174
+
175
+ > **Note:** Earlier versions used `JSON.stringify` to compare deps, which crashed when ReactNode props contained circular references (e.g. React elements with `$$typeof`). The current implementation compares individual fields by identity — memoization is now required to avoid redundant effect re-runs rather than crashes.
176
+
146
177
  ## Rules
147
178
 
148
179
  - **Do** use `pageTitle` on `AppDashboard` rather than placing `AppPageTitle` manually inside the page content.
149
180
  - **Do** pass `pageTitle={false}` to explicitly suppress the block — do not conditionally omit it based on state.
150
181
  - **Do** put action buttons in the `actions` slot — do not place them above the title.
151
182
  - **Do** size and style the `icon` yourself — pass `className="size-16 rounded-xl"` on the `<img>` or wrapper. The component does not enforce icon dimensions.
183
+ - **Do** memoize `actions`, `breadcrumbs`, and `icon` when passing to `usePageTitle` — these are compared by identity and inline JSX re-runs the effect every render.
152
184
  - **Don't** hardcode breadcrumbs when auto-generation is correct — only override when the URL structure doesn't match the desired labels.
153
185
  - **Don't** add additional margin-top to page content — `AppPageTitle` already provides `mb-6` spacing.
154
186
  - **Don't** use raw color values for `titleColor` overrides — use the `titleColor` prop, not a `className` on the heading.
package/rules/sidebar.md CHANGED
@@ -209,6 +209,27 @@ No background fill by default or on hover. The **primary brand color** backgroun
209
209
  | :--- | :--- | :--- | :--- |
210
210
  | `showIcon` | `boolean` | `false` | Show a skeleton icon placeholder. |
211
211
 
212
+ ## AppSidebarFooter
213
+
214
+ Renders sticky content at the bottom of the sidebar column, below `AppSidebarContent` and above `AppSidebarRail`.
215
+
216
+ **Used internally by `AppDashboard`:** when you pass the `sidebarFooter` prop to `AppDashboard`, the block wraps your content in `AppSidebarFooter` automatically. You do not need to add `AppSidebarFooter` yourself when using `AppDashboard`.
217
+
218
+ **In custom layouts** (e.g. when composing `AppSidebar` directly without `AppDashboard`), place `AppSidebarFooter` as the last child of `AppSidebar` before `AppSidebarRail`:
219
+
220
+ ```tsx
221
+ <AppSidebar collapsible="icon">
222
+ <AppSidebarHeader>...</AppSidebarHeader>
223
+ <AppSidebarContent>...</AppSidebarContent>
224
+ <AppSidebarFooter>
225
+ <span className="text-xs text-sidebar-foreground/50">v1.0.0</span>
226
+ </AppSidebarFooter>
227
+ <AppSidebarRail />
228
+ </AppSidebar>
229
+ ```
230
+
231
+ Use `group-data-[collapsible=icon]:hidden` on any child element to auto-hide it when the sidebar collapses to icon mode (see `rules/dashboard.md` for examples).
232
+
212
233
  ## useSidebar
213
234
 
214
235
  Returns `{ state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar }`.
@@ -286,6 +286,44 @@ When `headerCell` is provided it fully replaces the header content (text + sort
286
286
  </TableManagerToolbarActions>
287
287
  ```
288
288
 
289
+ ## Props — TableManagerToolbar
290
+
291
+ | Prop | Type | Default | Description |
292
+ |------|------|---------|-------------|
293
+ | `layout` | `"wrap" \| "inline"` | `"wrap"` | Layout mode. See below. |
294
+ | `className` | `string` | — | Extra classes on the toolbar wrapper. |
295
+
296
+ ### `layout` values
297
+
298
+ | Value | Behaviour |
299
+ |-------|-----------|
300
+ | `"wrap"` | `flex-wrap` — children reflow to multiple rows on narrow viewports. Back-compatible default. |
301
+ | `"inline"` | Mobile: search stacks above actions (`flex-col`). Desktop (md+): search + actions share one row with `justify-between`. Pair with `TableManagerSearch wrapperClassName="w-full md:w-64"` so the search does not stretch full-width on desktop. |
302
+
303
+ ### Responsive inline toolbar recipe
304
+
305
+ ```tsx
306
+ <TableManagerToolbar layout="inline">
307
+ <TableManagerSearch wrapperClassName="w-full md:w-64" />
308
+ <TableManagerToolbarActions>
309
+ <TableManagerMultiFilter ... />
310
+ <TableManagerPagination />
311
+ <TableManagerColumnToggle />
312
+ </TableManagerToolbarActions>
313
+ </TableManagerToolbar>
314
+ ```
315
+
316
+ ## Props — TableManagerSearch
317
+
318
+ | Prop | Type | Default | Description |
319
+ |------|------|---------|-------------|
320
+ | `placeholder` | `string` | `"Search..."` | Input placeholder. |
321
+ | `debounceMs` | `number` | `300` | Debounce delay before `onSearchChange` fires. |
322
+ | `wrapperClassName` | `string` | — | Forwarded to `AppInput.wrapperClassName`. Controls the outer wrapper width. Default `w-64`. |
323
+ | `className` | `string` | — | Applied to the inner `<input>` element. |
324
+
325
+ `wrapperClassName` overrides merge with the `w-64` default (later class wins via Tailwind Merge). Pass `wrapperClassName="w-full md:w-64"` for a mobile-first responsive width.
326
+
289
327
  ## Rules
290
328
 
291
329
  - ✅ Always wrap everything in `<TableManager>` before using any sub-component
@@ -295,12 +333,17 @@ When `headerCell` is provided it fully replaces the header content (text + sort
295
333
  - ✅ Implement OR logic in `onFilterChange` when a row can belong to multiple filter values
296
334
  - ✅ Set `type: "date"` on date columns and `type: "text"` on primary text columns so the default sort works correctly
297
335
  - ✅ Use `TableManagerToolbarActions` with `shrink-0` behavior built in — it will not compress when the search input grows
336
+ - ✅ Use `layout="inline"` on `TableManagerToolbar` when search + actions + pagination must coexist on one row at md+
337
+ - ✅ Pair `layout="inline"` with `wrapperClassName="w-full md:w-64"` on `TableManagerSearch` — otherwise search stretches full-width on desktop
298
338
  - ❌ Never import sub-components outside of a `<TableManager>` tree — they read from internal context and will throw
299
339
  - ❌ Never filter data internally in the block — always pass already-filtered data via the `data` prop
300
340
  - ❌ Never add `date-fns` or a date library just for this block — it uses native `Date` methods
301
341
  - ❌ Never use arbitrary CSS selectors (`:last-child`, `[&_th:last-child]`) to align column headers — use `headerAlign` / `headerCell` instead
342
+ - ❌ Do not pass a width class via `TableManagerSearch className` to control the wrapper width — `className` targets the inner `<input>`; use `wrapperClassName` instead
302
343
  - ❌ If a prop you need is missing, stop and inform the design team
303
344
 
304
345
  ## Responsive
305
346
 
306
- `TableManagerToolbar` wraps with `flex-wrap` on small screens. `TableManagerToolbarActions` loses its `ml-auto` below `sm` and flows inline with other toolbar children. Wrapping is automatic — no consumer change needed.
347
+ `TableManagerToolbar layout="wrap"` (default) wraps with `flex-wrap` on small screens. `TableManagerToolbarActions` loses its `ml-auto` below `sm` and flows inline with other toolbar children. Wrapping is automatic — no consumer change needed.
348
+
349
+ `layout="inline"` gives explicit two-tier stacking: search on top on mobile, side-by-side on md+. Use this when `layout="wrap"` produces too many rows on narrow viewports.
package/rules/textarea.md CHANGED
@@ -12,10 +12,21 @@ import { AppTextarea } from "@/components/primitives/AppTextarea"
12
12
  |------|------|---------|-------------|
13
13
  | `placeholder` | `string` | — | Placeholder text |
14
14
  | `disabled` | `boolean` | `false` | Disables the textarea |
15
- | `className` | `string` | — | Additional Tailwind classes |
15
+ | `className` | `string` | — | Additional Tailwind classes. Merged after the built-in bg classes so your overrides always win. |
16
16
 
17
17
  > Accepts all standard HTML `<textarea>` attributes in addition to the props above.
18
18
 
19
+ ## Dark mode background
20
+
21
+ `AppTextarea` applies `bg-transparent dark:bg-input/30` internally so the textarea surface is visible in dark mode, matching `AppInput`'s appearance. You do not need to add background classes for dark mode.
22
+
23
+ `className` is merged last — pass a `bg-*` class to override the default for special cases.
24
+
25
+ ```tsx
26
+ {/* Override background for a specific context */}
27
+ <AppTextarea className="bg-card dark:bg-card" placeholder="Custom bg..." />
28
+ ```
29
+
19
30
  ## Usage
20
31
 
21
32
  ### Basic
@@ -31,8 +42,19 @@ import { AppTextarea } from "@/components/primitives/AppTextarea"
31
42
  </div>
32
43
  ```
33
44
 
45
+ ### Controlled height
46
+ ```tsx
47
+ {/* Fixed height via rows */}
48
+ <AppTextarea rows={6} placeholder="Detailed description..." />
49
+
50
+ {/* Tailwind height class */}
51
+ <AppTextarea className="h-40" placeholder="Notes..." />
52
+ ```
53
+
34
54
  ## Rules
35
55
  - ✅ Always use `AppTextarea` — never use a raw `<textarea>` element
36
56
  - ✅ Use `AppLabel` for accessibility
37
57
  - ✅ Control height via `rows` attribute or Tailwind `h-*` classes
58
+ - ✅ Dark mode background is handled automatically — no `dark:bg-*` needed unless overriding
59
+ - ❌ Do not add `dark:bg-transparent` to fix a dark-mode visibility issue — `AppTextarea` already applies `dark:bg-input/30` for parity with `AppInput`
38
60
  - ❌ If a prop you need is missing, stop and inform the design team