@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.
- package/AGENTS.md +1 -1
- package/README.md +2 -0
- package/dist/index.cjs +34 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14469 -14225
- package/dist/index.js.map +1 -1
- package/dist/types/assets/icons/DashboardActiveIcon.d.ts +1 -1
- package/dist/types/assets/icons/DashboardIcon.d.ts +1 -1
- package/dist/types/assets/icons/DevicesActiveIcon.d.ts +1 -1
- package/dist/types/assets/icons/DevicesIcon.d.ts +1 -1
- package/dist/types/assets/icons/InstallerActiveIcon.d.ts +1 -1
- package/dist/types/assets/icons/InstallerIcon.d.ts +1 -1
- package/dist/types/assets/icons/ManifestActiveIcon.d.ts +1 -1
- package/dist/types/assets/icons/ManifestIcon.d.ts +1 -1
- package/dist/types/assets/icons/SensorActiveIcon.d.ts +1 -1
- package/dist/types/assets/icons/SensorIcon.d.ts +1 -1
- package/dist/types/assets/icons/SiteActiveIcon.d.ts +1 -1
- package/dist/types/assets/icons/SiteIcon.d.ts +1 -1
- package/dist/types/assets/icons/XrayActiveIcon.d.ts +1 -1
- package/dist/types/assets/icons/XrayIcon.d.ts +1 -1
- package/dist/types/assets/logos/DarkLogo.d.ts +1 -1
- package/dist/types/assets/logos/DarkSymbol.d.ts +1 -1
- package/dist/types/assets/logos/PrimaryLogo.d.ts +1 -1
- package/dist/types/assets/logos/PrimarySymbol.d.ts +1 -1
- package/dist/types/assets/logos/WhiteLogo.d.ts +1 -1
- package/dist/types/assets/logos/WhiteSymbol.d.ts +1 -1
- package/dist/types/components/blocks/AppBentoMenu.d.ts +2 -2
- package/dist/types/components/blocks/AppDashboard.d.ts +9 -3
- package/dist/types/components/blocks/AppHeader.d.ts +6 -6
- package/dist/types/components/blocks/AppLocationTreeFilter.d.ts +1 -1
- package/dist/types/components/blocks/AppNotificationsMenu.d.ts +1 -1
- package/dist/types/components/blocks/AppPageTitle.d.ts +1 -1
- package/dist/types/components/blocks/AppProfile.d.ts +1 -1
- package/dist/types/components/blocks/AppWizardStepper.d.ts +61 -0
- package/dist/types/components/blocks/TableManager.d.ts +36 -14
- package/dist/types/components/blocks/backgrounds/AppDashboardBackgroundDots.d.ts +1 -1
- package/dist/types/components/blocks/backgrounds/AppDashboardBackgroundWave.d.ts +1 -1
- package/dist/types/components/blocks/backgrounds/types.d.ts +2 -0
- package/dist/types/components/blocks/index.d.ts +1 -0
- package/dist/types/components/primitives/AppAccordion.d.ts +4 -4
- package/dist/types/components/primitives/AppAlert.d.ts +1 -1
- package/dist/types/components/primitives/AppAlertDialog.d.ts +2 -2
- package/dist/types/components/primitives/AppAspectRatio.d.ts +1 -1
- package/dist/types/components/primitives/AppAvatar.d.ts +3 -3
- package/dist/types/components/primitives/AppBadge.d.ts +1 -1
- package/dist/types/components/primitives/AppBreadcrumb.d.ts +7 -7
- package/dist/types/components/primitives/AppButton.d.ts +1 -1
- package/dist/types/components/primitives/AppButtonGroup.d.ts +1 -1
- package/dist/types/components/primitives/AppCalendar.d.ts +1 -1
- package/dist/types/components/primitives/AppCard.d.ts +7 -7
- package/dist/types/components/primitives/AppCarousel.d.ts +5 -5
- package/dist/types/components/primitives/AppChart.d.ts +3 -3
- package/dist/types/components/primitives/AppCheckbox.d.ts +1 -1
- package/dist/types/components/primitives/AppCollapsible.d.ts +1 -1
- package/dist/types/components/primitives/AppCombobox.d.ts +7 -7
- package/dist/types/components/primitives/AppCommand.d.ts +9 -9
- package/dist/types/components/primitives/AppContextMenu.d.ts +7 -7
- package/dist/types/components/primitives/AppDataTable.d.ts +2 -1
- package/dist/types/components/primitives/AppDatePicker.d.ts +2 -1
- package/dist/types/components/primitives/AppDialog.d.ts +6 -6
- package/dist/types/components/primitives/AppDrawer.d.ts +3 -3
- package/dist/types/components/primitives/AppDropdownMenu.d.ts +10 -10
- package/dist/types/components/primitives/AppEmpty.d.ts +6 -6
- package/dist/types/components/primitives/AppField.d.ts +10 -10
- package/dist/types/components/primitives/AppForm.d.ts +2 -2
- package/dist/types/components/primitives/AppHoverCard.d.ts +3 -3
- package/dist/types/components/primitives/AppInput.d.ts +10 -1
- package/dist/types/components/primitives/AppKbd.d.ts +2 -2
- package/dist/types/components/primitives/AppLabel.d.ts +1 -1
- package/dist/types/components/primitives/AppMenubar.d.ts +6 -6
- package/dist/types/components/primitives/AppNavigationMenu.d.ts +8 -8
- package/dist/types/components/primitives/AppPagination.d.ts +7 -7
- package/dist/types/components/primitives/AppPopover.d.ts +3 -3
- package/dist/types/components/primitives/AppProgress.d.ts +1 -1
- package/dist/types/components/primitives/AppRadioGroup.d.ts +2 -2
- package/dist/types/components/primitives/AppScrollArea.d.ts +2 -2
- package/dist/types/components/primitives/AppSelect.d.ts +7 -7
- package/dist/types/components/primitives/AppSeparator.d.ts +1 -1
- package/dist/types/components/primitives/AppSheet.d.ts +6 -6
- package/dist/types/components/primitives/AppSidebar.d.ts +24 -24
- package/dist/types/components/primitives/AppSkeleton.d.ts +1 -1
- package/dist/types/components/primitives/AppSwitch.d.ts +1 -1
- package/dist/types/components/primitives/AppTable.d.ts +8 -8
- package/dist/types/components/primitives/AppTabs.d.ts +4 -4
- package/dist/types/components/primitives/AppTextarea.d.ts +7 -1
- package/dist/types/components/primitives/AppThemeProvider.d.ts +2 -2
- package/dist/types/components/primitives/AppToaster.d.ts +1 -1
- package/dist/types/components/primitives/AppToggle.d.ts +1 -1
- package/dist/types/components/primitives/AppTooltip.d.ts +4 -4
- package/dist/types/components/primitives/AppTypography.d.ts +1 -1
- package/dist/types/components/ui/alert-dialog.d.ts +2 -2
- package/dist/types/components/ui/badge.d.ts +1 -1
- package/dist/types/components/ui/breadcrumb.d.ts +2 -2
- package/dist/types/components/ui/button.d.ts +5 -2
- package/dist/types/components/ui/calendar.d.ts +1 -1
- package/dist/types/components/ui/card.d.ts +7 -7
- package/dist/types/components/ui/carousel.d.ts +5 -5
- package/dist/types/components/ui/chart.d.ts +4 -4
- package/dist/types/components/ui/combobox.d.ts +7 -7
- package/dist/types/components/ui/command.d.ts +9 -9
- package/dist/types/components/ui/context-menu.d.ts +1 -1
- package/dist/types/components/ui/dialog.d.ts +2 -2
- package/dist/types/components/ui/drawer.d.ts +2 -2
- package/dist/types/components/ui/dropdown-menu.d.ts +1 -1
- package/dist/types/components/ui/form.d.ts +2 -2
- package/dist/types/components/ui/input.d.ts +1 -1
- package/dist/types/components/ui/label.d.ts +1 -1
- package/dist/types/components/ui/menubar.d.ts +16 -16
- package/dist/types/components/ui/pagination.d.ts +5 -5
- package/dist/types/components/ui/scroll-text.d.ts +1 -1
- package/dist/types/components/ui/sheet.d.ts +2 -2
- package/dist/types/components/ui/sidebar.d.ts +23 -23
- package/dist/types/components/ui/skeleton.d.ts +1 -1
- package/dist/types/components/ui/sonner.d.ts +1 -1
- package/dist/types/hooks/use-page-title.d.ts +10 -1
- package/dist/ui-kit.css +1 -1
- package/package.json +29 -19
- package/rules/button.md +1 -0
- package/rules/dashboard.md +96 -4
- package/rules/header.md +23 -2
- package/rules/input.md +34 -2
- package/rules/pagetitle.md +32 -0
- package/rules/sidebar.md +21 -0
- package/rules/table-manager.md +44 -1
- package/rules/textarea.md +23 -1
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
81
|
+
"date-fns": "^4.4.0",
|
|
82
82
|
"embla-carousel": "^8.6.0",
|
|
83
83
|
"embla-carousel-react": "^8.6.0",
|
|
84
|
-
"lucide-react": "^
|
|
85
|
-
"motion": "^12.
|
|
84
|
+
"lucide-react": "^1.17.0",
|
|
85
|
+
"motion": "^12.40.0",
|
|
86
86
|
"radix-ui": "^1.4.3",
|
|
87
|
-
"react-day-picker": "^
|
|
88
|
-
"react-hook-form": "^7.
|
|
87
|
+
"react-day-picker": "^10.0.1",
|
|
88
|
+
"react-hook-form": "^7.77.0",
|
|
89
89
|
"recharts": "^3.8.1",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
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": "^
|
|
102
|
+
"@types/node": "^25",
|
|
103
103
|
"@types/react": "^19",
|
|
104
104
|
"@types/react-dom": "^19",
|
|
105
|
-
"@vitejs/plugin-react-swc": "^
|
|
106
|
-
"eslint": "^
|
|
107
|
-
"react": "
|
|
108
|
-
"react-
|
|
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": "^
|
|
116
|
+
"typescript": "^6",
|
|
117
|
+
"typescript-eslint": "^8.60.1",
|
|
113
118
|
"vite": "^6",
|
|
114
|
-
"vite-plugin-checker": "^0.
|
|
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
|
package/rules/dashboard.md
CHANGED
|
@@ -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
|
|
239
|
+
## AppThemeProvider
|
|
240
|
+
|
|
241
|
+
`AppDashboard` includes `AppThemeProvider` internally — the theme toggle works out of the box with no extra setup required.
|
|
187
242
|
|
|
188
|
-
|
|
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
|
|
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
|
|
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
|
package/rules/pagetitle.md
CHANGED
|
@@ -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 }`.
|
package/rules/table-manager.md
CHANGED
|
@@ -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
|