@skunkworksai/ui-kit 0.1.18
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 +250 -0
- package/README.md +429 -0
- package/dist/assets/Service-applets-apps/audience-measurement.png +0 -0
- package/dist/assets/Service-applets-apps/coatro-app.png +0 -0
- package/dist/assets/Service-applets-apps/content-effectiveness.png +0 -0
- package/dist/assets/Service-applets-apps/hop-tracking.png +0 -0
- package/dist/assets/Service-applets-apps/index.ts +13 -0
- package/dist/assets/Service-applets-apps/journeys-app.png +0 -0
- package/dist/assets/Service-applets-apps/people-counting.png +0 -0
- package/dist/assets/Service-applets-apps/petra.png +0 -0
- package/dist/assets/Service-applets-apps/sam-generic.png +0 -0
- package/dist/assets/Service-applets-apps/sami-for-environment.png +0 -0
- package/dist/assets/Service-applets-apps/shelf-engagement.png +0 -0
- package/dist/assets/Service-applets-apps/traffic-management.png +0 -0
- package/dist/assets/Service-applets-apps/viana.png +0 -0
- package/dist/assets/Service-applets-apps/zone-engagement.png +0 -0
- package/dist/assets/icons/Dashboard-active.svg +29 -0
- package/dist/assets/icons/Dashboard.svg +25 -0
- package/dist/assets/icons/DashboardActiveIcon.tsx +35 -0
- package/dist/assets/icons/DashboardIcon.tsx +31 -0
- package/dist/assets/icons/Devices-active.svg +27 -0
- package/dist/assets/icons/Devices.svg +20 -0
- package/dist/assets/icons/DevicesActiveIcon.tsx +33 -0
- package/dist/assets/icons/DevicesIcon.tsx +26 -0
- package/dist/assets/icons/Installer-active.svg +29 -0
- package/dist/assets/icons/Installer.svg +17 -0
- package/dist/assets/icons/InstallerActiveIcon.tsx +35 -0
- package/dist/assets/icons/InstallerIcon.tsx +23 -0
- package/dist/assets/icons/Manifest-active.svg +49 -0
- package/dist/assets/icons/Manifest.svg +49 -0
- package/dist/assets/icons/ManifestActiveIcon.tsx +55 -0
- package/dist/assets/icons/ManifestIcon.tsx +55 -0
- package/dist/assets/icons/Sensor-active.svg +15 -0
- package/dist/assets/icons/Sensor.svg +11 -0
- package/dist/assets/icons/SensorActiveIcon.tsx +21 -0
- package/dist/assets/icons/SensorIcon.tsx +17 -0
- package/dist/assets/icons/Site-active.svg +14 -0
- package/dist/assets/icons/Site.svg +10 -0
- package/dist/assets/icons/SiteActiveIcon.tsx +20 -0
- package/dist/assets/icons/SiteIcon.tsx +16 -0
- package/dist/assets/icons/X-ray-active.svg +199 -0
- package/dist/assets/icons/X-ray.svg +47 -0
- package/dist/assets/icons/XrayActiveIcon.tsx +205 -0
- package/dist/assets/icons/XrayIcon.tsx +53 -0
- package/dist/assets/icons/index.ts +14 -0
- package/dist/assets/logos/DarkLogo.tsx +28 -0
- package/dist/assets/logos/DarkSymbol.tsx +9 -0
- package/dist/assets/logos/PrimaryLogo.tsx +28 -0
- package/dist/assets/logos/PrimarySymbol.tsx +9 -0
- package/dist/assets/logos/WhiteLogo.tsx +28 -0
- package/dist/assets/logos/WhiteSymbol.tsx +9 -0
- package/dist/assets/logos/dark_logo.svg +22 -0
- package/dist/assets/logos/dark_symbol.svg +3 -0
- package/dist/assets/logos/index.ts +6 -0
- package/dist/assets/logos/primary-logo.svg +22 -0
- package/dist/assets/logos/primary_logo.svg +22 -0
- package/dist/assets/logos/primary_symbol.svg +3 -0
- package/dist/assets/logos/white_symbol.svg +3 -0
- package/dist/index.cjs +160 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +45276 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens-source.css +121 -0
- package/dist/types/assets/Service-applets-apps/index.d.ts +13 -0
- package/dist/types/assets/icons/DashboardActiveIcon.d.ts +2 -0
- package/dist/types/assets/icons/DashboardIcon.d.ts +2 -0
- package/dist/types/assets/icons/DevicesActiveIcon.d.ts +2 -0
- package/dist/types/assets/icons/DevicesIcon.d.ts +2 -0
- package/dist/types/assets/icons/InstallerActiveIcon.d.ts +2 -0
- package/dist/types/assets/icons/InstallerIcon.d.ts +2 -0
- package/dist/types/assets/icons/ManifestActiveIcon.d.ts +2 -0
- package/dist/types/assets/icons/ManifestIcon.d.ts +2 -0
- package/dist/types/assets/icons/SensorActiveIcon.d.ts +2 -0
- package/dist/types/assets/icons/SensorIcon.d.ts +2 -0
- package/dist/types/assets/icons/SiteActiveIcon.d.ts +2 -0
- package/dist/types/assets/icons/SiteIcon.d.ts +2 -0
- package/dist/types/assets/icons/XrayActiveIcon.d.ts +2 -0
- package/dist/types/assets/icons/XrayIcon.d.ts +2 -0
- package/dist/types/assets/icons/index.d.ts +14 -0
- package/dist/types/assets/logos/DarkLogo.d.ts +2 -0
- package/dist/types/assets/logos/DarkSymbol.d.ts +2 -0
- package/dist/types/assets/logos/PrimaryLogo.d.ts +2 -0
- package/dist/types/assets/logos/PrimarySymbol.d.ts +2 -0
- package/dist/types/assets/logos/WhiteLogo.d.ts +2 -0
- package/dist/types/assets/logos/WhiteSymbol.d.ts +2 -0
- package/dist/types/assets/logos/index.d.ts +6 -0
- package/dist/types/components/blocks/AppBentoMenu.d.ts +31 -0
- package/dist/types/components/blocks/AppDashboard.d.ts +121 -0
- package/dist/types/components/blocks/AppHeader.d.ts +198 -0
- package/dist/types/components/blocks/AppLocationTreeFilter.d.ts +44 -0
- package/dist/types/components/blocks/AppNotificationsMenu.d.ts +50 -0
- package/dist/types/components/blocks/AppPageTitle.d.ts +86 -0
- package/dist/types/components/blocks/AppProfile.d.ts +53 -0
- package/dist/types/components/blocks/TableManager.d.ts +194 -0
- package/dist/types/components/blocks/backgrounds/AppDashboardBackgroundDots.d.ts +14 -0
- package/dist/types/components/blocks/backgrounds/AppDashboardBackgroundWave.d.ts +14 -0
- package/dist/types/components/blocks/backgrounds/index.d.ts +3 -0
- package/dist/types/components/blocks/backgrounds/types.d.ts +13 -0
- package/dist/types/components/blocks/index.d.ts +9 -0
- package/dist/types/components/primitives/AppAccordion.d.ts +22 -0
- package/dist/types/components/primitives/AppAlert.d.ts +33 -0
- package/dist/types/components/primitives/AppAlertDialog.d.ts +79 -0
- package/dist/types/components/primitives/AppAspectRatio.d.ts +7 -0
- package/dist/types/components/primitives/AppAvatar.d.ts +17 -0
- package/dist/types/components/primitives/AppBadge.d.ts +9 -0
- package/dist/types/components/primitives/AppBreadcrumb.d.ts +45 -0
- package/dist/types/components/primitives/AppButton.d.ts +24 -0
- package/dist/types/components/primitives/AppButtonGroup.d.ts +10 -0
- package/dist/types/components/primitives/AppCalendar.d.ts +51 -0
- package/dist/types/components/primitives/AppCard.d.ts +41 -0
- package/dist/types/components/primitives/AppCarousel.d.ts +26 -0
- package/dist/types/components/primitives/AppChart.d.ts +27 -0
- package/dist/types/components/primitives/AppCheckbox.d.ts +9 -0
- package/dist/types/components/primitives/AppCollapsible.d.ts +20 -0
- package/dist/types/components/primitives/AppCombobox.d.ts +37 -0
- package/dist/types/components/primitives/AppCommand.d.ts +47 -0
- package/dist/types/components/primitives/AppContextMenu.d.ts +82 -0
- package/dist/types/components/primitives/AppDataTable.d.ts +23 -0
- package/dist/types/components/primitives/AppDatePicker.d.ts +26 -0
- package/dist/types/components/primitives/AppDialog.d.ts +40 -0
- package/dist/types/components/primitives/AppDrawer.d.ts +58 -0
- package/dist/types/components/primitives/AppDropdownMenu.d.ts +85 -0
- package/dist/types/components/primitives/AppEmpty.d.ts +35 -0
- package/dist/types/components/primitives/AppField.d.ts +65 -0
- package/dist/types/components/primitives/AppForm.d.ts +58 -0
- package/dist/types/components/primitives/AppHoverCard.d.ts +19 -0
- package/dist/types/components/primitives/AppInput.d.ts +21 -0
- package/dist/types/components/primitives/AppKbd.d.ts +18 -0
- package/dist/types/components/primitives/AppLabel.d.ts +8 -0
- package/dist/types/components/primitives/AppMenubar.d.ts +83 -0
- package/dist/types/components/primitives/AppNavigationMenu.d.ts +51 -0
- package/dist/types/components/primitives/AppPagination.d.ts +46 -0
- package/dist/types/components/primitives/AppPopover.d.ts +20 -0
- package/dist/types/components/primitives/AppProgress.d.ts +9 -0
- package/dist/types/components/primitives/AppRadioGroup.d.ts +15 -0
- package/dist/types/components/primitives/AppScrollArea.d.ts +12 -0
- package/dist/types/components/primitives/AppScrollText.d.ts +7 -0
- package/dist/types/components/primitives/AppSelect.d.ts +46 -0
- package/dist/types/components/primitives/AppSeparator.d.ts +8 -0
- package/dist/types/components/primitives/AppSheet.d.ts +32 -0
- package/dist/types/components/primitives/AppSidebar.d.ts +59 -0
- package/dist/types/components/primitives/AppSkeleton.d.ts +6 -0
- package/dist/types/components/primitives/AppSpinner.d.ts +13 -0
- package/dist/types/components/primitives/AppSwitch.d.ts +8 -0
- package/dist/types/components/primitives/AppTable.d.ts +53 -0
- package/dist/types/components/primitives/AppTabs.d.ts +10 -0
- package/dist/types/components/primitives/AppTextarea.d.ts +9 -0
- package/dist/types/components/primitives/AppThemeProvider.d.ts +46 -0
- package/dist/types/components/primitives/AppToaster.d.ts +7 -0
- package/dist/types/components/primitives/AppToggle.d.ts +9 -0
- package/dist/types/components/primitives/AppToggleGroup.d.ts +18 -0
- package/dist/types/components/primitives/AppTooltip.d.ts +26 -0
- package/dist/types/components/primitives/AppTypography.d.ts +80 -0
- package/dist/types/components/primitives/index.d.ts +57 -0
- package/dist/types/components/ui/accordion.d.ts +7 -0
- package/dist/types/components/ui/alert-dialog.d.ts +20 -0
- package/dist/types/components/ui/alert.d.ts +8 -0
- package/dist/types/components/ui/aspect-ratio.d.ts +4 -0
- package/dist/types/components/ui/avatar.d.ts +6 -0
- package/dist/types/components/ui/badge.d.ts +9 -0
- package/dist/types/components/ui/breadcrumb.d.ts +19 -0
- package/dist/types/components/ui/button.d.ts +13 -0
- package/dist/types/components/ui/calendar.d.ts +8 -0
- package/dist/types/components/ui/card.d.ts +11 -0
- package/dist/types/components/ui/carousel.d.ts +28 -0
- package/dist/types/components/ui/chart.d.ts +48 -0
- package/dist/types/components/ui/checkbox.d.ts +4 -0
- package/dist/types/components/ui/collapsible.d.ts +6 -0
- package/dist/types/components/ui/combobox.d.ts +83 -0
- package/dist/types/components/ui/command.d.ts +82 -0
- package/dist/types/components/ui/context-menu.d.ts +27 -0
- package/dist/types/components/ui/dialog.d.ts +19 -0
- package/dist/types/components/ui/drawer.d.ts +23 -0
- package/dist/types/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/types/components/ui/empty.d.ts +14 -0
- package/dist/types/components/ui/field.d.ts +40 -0
- package/dist/types/components/ui/form.d.ts +22 -0
- package/dist/types/components/ui/hover-card.d.ts +6 -0
- package/dist/types/components/ui/input.d.ts +3 -0
- package/dist/types/components/ui/kbd.d.ts +4 -0
- package/dist/types/components/ui/label.d.ts +4 -0
- package/dist/types/components/ui/menubar.d.ts +30 -0
- package/dist/types/components/ui/navigation-menu.d.ts +12 -0
- package/dist/types/components/ui/pagination.d.ts +28 -0
- package/dist/types/components/ui/popover.d.ts +6 -0
- package/dist/types/components/ui/progress.d.ts +4 -0
- package/dist/types/components/ui/radio-group.d.ts +5 -0
- package/dist/types/components/ui/scroll-area.d.ts +5 -0
- package/dist/types/components/ui/scroll-text.d.ts +6 -0
- package/dist/types/components/ui/select.d.ts +13 -0
- package/dist/types/components/ui/separator.d.ts +4 -0
- package/dist/types/components/ui/sheet.d.ts +25 -0
- package/dist/types/components/ui/sidebar.d.ts +69 -0
- package/dist/types/components/ui/skeleton.d.ts +2 -0
- package/dist/types/components/ui/sonner.d.ts +2 -0
- package/dist/types/components/ui/switch.d.ts +4 -0
- package/dist/types/components/ui/table.d.ts +10 -0
- package/dist/types/components/ui/tabs.d.ts +19 -0
- package/dist/types/components/ui/textarea.d.ts +5 -0
- package/dist/types/components/ui/toggle-group.d.ts +15 -0
- package/dist/types/components/ui/toggle.d.ts +12 -0
- package/dist/types/components/ui/tooltip.d.ts +7 -0
- package/dist/types/hooks/use-mobile.d.ts +13 -0
- package/dist/types/hooks/use-page-title.d.ts +23 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/utils.d.ts +2 -0
- package/dist/ui-kit.css +1 -0
- package/package.json +117 -0
- package/rules/accordion.md +60 -0
- package/rules/alert-dialog.md +62 -0
- package/rules/alert.md +61 -0
- package/rules/aspect-ratio.md +47 -0
- package/rules/avatar.md +52 -0
- package/rules/badge.md +55 -0
- package/rules/bento-menu.md +86 -0
- package/rules/breadcrumb.md +80 -0
- package/rules/button-group.md +58 -0
- package/rules/button.md +66 -0
- package/rules/calendar.md +97 -0
- package/rules/card.md +84 -0
- package/rules/carousel.md +113 -0
- package/rules/chart.md +125 -0
- package/rules/checkbox.md +49 -0
- package/rules/collapsible.md +57 -0
- package/rules/combobox.md +133 -0
- package/rules/command.md +73 -0
- package/rules/context-menu.md +65 -0
- package/rules/dashboard.md +368 -0
- package/rules/data-table.md +91 -0
- package/rules/date-picker.md +65 -0
- package/rules/dialog.md +60 -0
- package/rules/drawer.md +65 -0
- package/rules/dropdown-menu.md +72 -0
- package/rules/empty.md +110 -0
- package/rules/field.md +149 -0
- package/rules/form.md +60 -0
- package/rules/header.md +245 -0
- package/rules/hover-card.md +53 -0
- package/rules/icons.md +141 -0
- package/rules/input.md +66 -0
- package/rules/kbd.md +58 -0
- package/rules/label.md +30 -0
- package/rules/location-tree-filter.md +165 -0
- package/rules/logo.md +58 -0
- package/rules/menubar.md +75 -0
- package/rules/navigation-menu.md +68 -0
- package/rules/notifications-menu.md +137 -0
- package/rules/pagetitle.md +160 -0
- package/rules/pagination.md +71 -0
- package/rules/popover.md +48 -0
- package/rules/profile.md +125 -0
- package/rules/progress.md +41 -0
- package/rules/radio-group.md +44 -0
- package/rules/scroll-area.md +32 -0
- package/rules/scroll-text.md +40 -0
- package/rules/select.md +86 -0
- package/rules/separator.md +43 -0
- package/rules/sheet.md +76 -0
- package/rules/sidebar.md +442 -0
- package/rules/skeleton.md +46 -0
- package/rules/spinner.md +43 -0
- package/rules/switch.md +46 -0
- package/rules/table-manager.md +306 -0
- package/rules/table.md +177 -0
- package/rules/tabs.md +82 -0
- package/rules/text.md +3 -0
- package/rules/textarea.md +38 -0
- package/rules/toaster.md +58 -0
- package/rules/toggle-group.md +50 -0
- package/rules/toggle.md +40 -0
- package/rules/tokens.md +88 -0
- package/rules/tooltip.md +78 -0
- package/rules/typography.md +81 -0
- package/scripts/scaffold.mjs +28 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# AppLocationTreeFilter
|
|
2
|
+
|
|
3
|
+
> Docs: https://ui-kit.vercel.app/docs/blocks/location-tree-filter
|
|
4
|
+
|
|
5
|
+
**This is a block — import from `blocks/`, not `primitives/`.**
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { AppLocationTreeFilter } from "@/components/blocks/AppLocationTreeFilter"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Also available from the barrel:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { AppLocationTreeFilter } from "@/components/blocks"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Hard stop
|
|
18
|
+
|
|
19
|
+
> ❌ **Never compose a location tree filter manually using `AppCollapsible`, raw `<details>`, or any other approach.**
|
|
20
|
+
>
|
|
21
|
+
> `AppLocationTreeFilter` handles the full tree rendering, recursive collapsible structure, search/filter, depth indentation, selection state, and unallocated row. Building this from scratch will produce inconsistent behaviour and visual drift. Use this block.
|
|
22
|
+
|
|
23
|
+
## Types
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
type TreeNode = {
|
|
27
|
+
label: string // Display label and the value emitted when this node is selected
|
|
28
|
+
count: number // Badge count shown alongside the label
|
|
29
|
+
children?: TreeNode[] // If present, renders as a collapsible branch
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type UnallocatedConfig = {
|
|
33
|
+
label?: string // Row label. Defaults to "Unallocated"
|
|
34
|
+
count: number // Badge count
|
|
35
|
+
value?: string // Sentinel value emitted on click. Defaults to "__unallocated__"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
| Prop | Type | Default | Description |
|
|
42
|
+
|------|------|---------|-------------|
|
|
43
|
+
| `data` | `TreeNode[]` | required | Tree data. Top-level nodes with optional nested children. |
|
|
44
|
+
| `selected` | `string \| null` | required | Current selection value. `null` = root/all state. |
|
|
45
|
+
| `onSelect` | `(value: string \| null) => void` | required | Fired when user clicks a node or the unallocated row. |
|
|
46
|
+
| `title` | `string` | `"Filter by Location"` | Label shown above the search input. |
|
|
47
|
+
| `showHelp` | `boolean` | `false` | Shows a help icon next to the title. |
|
|
48
|
+
| `searchPlaceholder` | `string` | `"Search locations..."` | Placeholder for the search input. |
|
|
49
|
+
| `unallocated` | `UnallocatedConfig` | `undefined` | Pinned row above the tree for unallocated records. Omit to hide. |
|
|
50
|
+
| `className` | `string` | `—` | Extra classes on the root container (default width: `w-56`). |
|
|
51
|
+
|
|
52
|
+
## Selection model
|
|
53
|
+
|
|
54
|
+
The block is **uncontrolled for tree state** (collapse/expand), but **controlled for selection** — your component owns `selected`.
|
|
55
|
+
|
|
56
|
+
| `onSelect` emits | Meaning |
|
|
57
|
+
|-----------------|---------|
|
|
58
|
+
| `null` | Top-level branch node clicked — "show all" / root state |
|
|
59
|
+
| `node.label` | A mid-level or leaf node was clicked — filter by that label |
|
|
60
|
+
| `unallocated.value` (default: `"__unallocated__"`) | The unallocated row was clicked |
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
const [selected, setSelected] = React.useState<string | null>(null)
|
|
64
|
+
|
|
65
|
+
<AppLocationTreeFilter
|
|
66
|
+
data={tree}
|
|
67
|
+
selected={selected}
|
|
68
|
+
onSelect={setSelected}
|
|
69
|
+
/>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Search behavior
|
|
73
|
+
|
|
74
|
+
Typing in the search input filters visible tree nodes by label. Branches that contain a matching descendant are automatically expanded. Clearing the search restores the original collapsed state.
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
### Basic
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
const tree: TreeNode[] = [
|
|
82
|
+
{
|
|
83
|
+
label: "All",
|
|
84
|
+
count: 65,
|
|
85
|
+
children: [
|
|
86
|
+
{
|
|
87
|
+
label: "Philippines",
|
|
88
|
+
count: 65,
|
|
89
|
+
children: [
|
|
90
|
+
{ label: "Davao City", count: 1 },
|
|
91
|
+
{ label: "Cagayan de Oro", count: 64 },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
export default function Page() {
|
|
99
|
+
const [selected, setSelected] = React.useState<string | null>(null)
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<AppLocationTreeFilter
|
|
103
|
+
data={tree}
|
|
104
|
+
selected={selected}
|
|
105
|
+
onSelect={setSelected}
|
|
106
|
+
/>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### With unallocated row
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
<AppLocationTreeFilter
|
|
115
|
+
data={tree}
|
|
116
|
+
title="Filter by Location"
|
|
117
|
+
showHelp
|
|
118
|
+
unallocated={{ label: "Unallocated Devices", count: 135 }}
|
|
119
|
+
selected={selected}
|
|
120
|
+
onSelect={setSelected}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### URL search params
|
|
125
|
+
|
|
126
|
+
The `unallocated.value` sentinel (default `"__unallocated__"`) must be encoded when storing in URL params:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
// Writing to URL
|
|
130
|
+
const params = new URLSearchParams()
|
|
131
|
+
if (selected !== null) params.set("location", encodeURIComponent(selected))
|
|
132
|
+
|
|
133
|
+
// Reading from URL
|
|
134
|
+
const raw = params.get("location")
|
|
135
|
+
const selected = raw ? decodeURIComponent(raw) : null
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Custom width
|
|
139
|
+
|
|
140
|
+
Override the default `w-56` via `className`:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<AppLocationTreeFilter
|
|
144
|
+
data={tree}
|
|
145
|
+
selected={selected}
|
|
146
|
+
onSelect={setSelected}
|
|
147
|
+
className="w-64"
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Rules
|
|
152
|
+
|
|
153
|
+
- ✅ Always use `AppLocationTreeFilter` for hierarchical location filtering — never compose manually
|
|
154
|
+
- ✅ Provide a top-level node (e.g. "All") as the root branch — it emits `null` on click and represents the "show all" state
|
|
155
|
+
- ✅ Own the `selected` state in your page or parent component — the block does not manage selection internally
|
|
156
|
+
- ✅ Use `unallocated` whenever records can exist without an assigned location
|
|
157
|
+
- ✅ Set a custom `unallocated.value` if `"__unallocated__"` conflicts with any real location label in your dataset
|
|
158
|
+
- ❌ Never import `AppCollapsible` to build a tree structure — use this block
|
|
159
|
+
- ❌ Never manage tree expand/collapse state externally — the block handles it internally
|
|
160
|
+
- ❌ Never pass a flat list as `data` — the tree structure must be nested via `children`
|
|
161
|
+
- ❌ If a prop you need is missing, stop and inform the design team
|
|
162
|
+
|
|
163
|
+
## Responsive
|
|
164
|
+
|
|
165
|
+
The root container uses `w-full sm:w-56 sm:shrink-0` — on mobile it expands to full width and stacks vertically with surrounding content; on desktop it returns to its fixed `14rem` width and does not shrink. A drawer-based mobile trigger is not implemented — wrap in `AppSheet` at the page level if a collapsible filter panel is needed.
|
package/rules/logo.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Logo Assets
|
|
2
|
+
|
|
3
|
+
Assets ship as raw files in `dist/assets/` (requires `@skunkworksai/ui-kit@>=0.1.5`). They are scaffolded into your project during setup — import from your own files, never from the package directly:
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { PrimaryLogo, DarkLogo, WhiteLogo } from "./assets/logos"
|
|
7
|
+
import { PrimarySymbol, DarkSymbol, WhiteSymbol } from "./assets/logos"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Variants
|
|
11
|
+
|
|
12
|
+
| Component | Fill color | Use on |
|
|
13
|
+
|-----------|-----------|--------|
|
|
14
|
+
| `PrimaryLogo` | Brand blue | Light backgrounds, white surfaces |
|
|
15
|
+
| `DarkLogo` | Dark | Light backgrounds when primary blue is too prominent |
|
|
16
|
+
| `WhiteLogo` | White | Dark backgrounds, colored sidebars, dark-mode |
|
|
17
|
+
| `PrimarySymbol` | Brand blue | Icon-only contexts on light backgrounds |
|
|
18
|
+
| `DarkSymbol` | Dark | Icon-only on light |
|
|
19
|
+
| `WhiteSymbol` | White | Icon-only on dark backgrounds or colored fills |
|
|
20
|
+
|
|
21
|
+
## Default dimensions
|
|
22
|
+
|
|
23
|
+
All logos share the same `265 × 83` viewBox. Pass `width` and `height` to scale proportionally:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<PrimaryLogo width={120} height={38} /> {/* standard header size */}
|
|
27
|
+
<WhiteLogo width={160} height={50} /> {/* larger marketing use */}
|
|
28
|
+
<WhiteSymbol width={32} height={32} /> {/* icon-only mark */}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Light + dark mode pair
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<PrimaryLogo className="block dark:hidden" width={120} height={38} />
|
|
37
|
+
<WhiteLogo className="hidden dark:block" width={120} height={38} />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Symbol only
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<WhiteSymbol width={28} height={28} />
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Rules
|
|
47
|
+
|
|
48
|
+
- ✅ Import from `./assets/logos` — never from `@skunkworksai/ui-kit`
|
|
49
|
+
- ✅ Use `PrimaryLogo` on light backgrounds (docs, marketing pages, cards)
|
|
50
|
+
- ✅ Use `WhiteLogo` or `WhiteSymbol` on dark backgrounds
|
|
51
|
+
- ✅ Support dark mode with `block dark:hidden` / `hidden dark:block` pair
|
|
52
|
+
- ❌ Do not hardcode logo colors with Tailwind color classes — use the correct variant instead
|
|
53
|
+
- ❌ Do not distort the logo — always scale both `width` and `height` proportionally from the `265 × 83` viewBox ratio
|
|
54
|
+
- ❌ If you need a logo variant that doesn't exist, inform the design team — do not create ad-hoc SVGs
|
|
55
|
+
|
|
56
|
+
## On package upgrade
|
|
57
|
+
|
|
58
|
+
Re-run the asset scaffold step and diff `src/assets/logos/` against the new `dist/assets/logos/` if logos changed.
|
package/rules/menubar.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# AppMenubar
|
|
2
|
+
|
|
3
|
+
> Docs: https://ui-kit.vercel.app/docs/components/menubar
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import {
|
|
7
|
+
AppMenubar,
|
|
8
|
+
AppMenubarMenu,
|
|
9
|
+
AppMenubarTrigger,
|
|
10
|
+
AppMenubarContent,
|
|
11
|
+
AppMenubarItem,
|
|
12
|
+
AppMenubarSeparator,
|
|
13
|
+
AppMenubarLabel,
|
|
14
|
+
AppMenubarCheckboxItem,
|
|
15
|
+
AppMenubarRadioGroup,
|
|
16
|
+
AppMenubarRadioItem,
|
|
17
|
+
AppMenubarPortal,
|
|
18
|
+
AppMenubarGroup,
|
|
19
|
+
AppMenubarSub,
|
|
20
|
+
AppMenubarSubContent,
|
|
21
|
+
AppMenubarSubTrigger,
|
|
22
|
+
AppMenubarShortcut,
|
|
23
|
+
} from "@/components/primitives/AppMenubar"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## AppMenubar
|
|
27
|
+
|
|
28
|
+
| Prop | Type | Default | Description |
|
|
29
|
+
| :--- | :--- | :--- | :--- |
|
|
30
|
+
| `defaultValue` | `string` | - | The value of the menu that should be open by default. |
|
|
31
|
+
|
|
32
|
+
## AppMenubarTrigger
|
|
33
|
+
|
|
34
|
+
| Prop | Type | Default | Description |
|
|
35
|
+
| :--- | :--- | :--- | :--- |
|
|
36
|
+
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child. |
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Desktop-style Menubar
|
|
41
|
+
```tsx
|
|
42
|
+
<AppMenubar>
|
|
43
|
+
<AppMenubarMenu>
|
|
44
|
+
<AppMenubarTrigger>File</AppMenubarTrigger>
|
|
45
|
+
<AppMenubarContent>
|
|
46
|
+
<AppMenubarItem>
|
|
47
|
+
New Tab <AppMenubarShortcut>⌘T</AppMenubarShortcut>
|
|
48
|
+
</AppMenubarItem>
|
|
49
|
+
<AppMenubarItem>
|
|
50
|
+
New Window <AppMenubarShortcut>⌘N</AppMenubarShortcut>
|
|
51
|
+
</AppMenubarItem>
|
|
52
|
+
<AppMenubarSeparator />
|
|
53
|
+
<AppMenubarItem>Print</AppMenubarItem>
|
|
54
|
+
</AppMenubarContent>
|
|
55
|
+
</AppMenubarMenu>
|
|
56
|
+
<AppMenubarMenu>
|
|
57
|
+
<AppMenubarTrigger>Edit</AppMenubarTrigger>
|
|
58
|
+
<AppMenubarContent>
|
|
59
|
+
<AppMenubarItem>Undo</AppMenubarItem>
|
|
60
|
+
<AppMenubarItem>Redo</AppMenubarItem>
|
|
61
|
+
</AppMenubarContent>
|
|
62
|
+
</AppMenubarMenu>
|
|
63
|
+
</AppMenubar>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Rules
|
|
67
|
+
|
|
68
|
+
- **Do** use `AppMenubar` for application-wide persistent menus.
|
|
69
|
+
- **Do** use `AppMenubarShortcut` to display keyboard shortcuts.
|
|
70
|
+
- **Don't** use `AppMenubar` for simple navigation; use `AppNavigationMenu` for website headers.
|
|
71
|
+
- If a prop you need is missing, stop and inform the design team.
|
|
72
|
+
|
|
73
|
+
## Responsive
|
|
74
|
+
|
|
75
|
+
`AppMenubar` scrolls horizontally on mobile via an `overflow-x-auto` wrapper with `min-w-max` on the inner bar. A Sheet-based mobile menu is not implemented — compose one at the page level using `AppSheet` if a collapsing menu is required.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# AppNavigationMenu
|
|
2
|
+
|
|
3
|
+
> Docs: https://ui-kit.vercel.app/docs/components/navigation-menu
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import {
|
|
7
|
+
AppNavigationMenu,
|
|
8
|
+
AppNavigationMenuList,
|
|
9
|
+
AppNavigationMenuItem,
|
|
10
|
+
AppNavigationMenuTrigger,
|
|
11
|
+
AppNavigationMenuContent,
|
|
12
|
+
AppNavigationMenuLink,
|
|
13
|
+
AppNavigationMenuIndicator,
|
|
14
|
+
AppNavigationMenuViewport,
|
|
15
|
+
navigationMenuTriggerStyle,
|
|
16
|
+
} from "@/components/primitives/AppNavigationMenu"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## AppNavigationMenu
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
| :--- | :--- | :--- | :--- |
|
|
23
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | The orientation of the menu. |
|
|
24
|
+
|
|
25
|
+
## AppNavigationMenuLink
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Default | Description |
|
|
28
|
+
| :--- | :--- | :--- | :--- |
|
|
29
|
+
| `active` | `boolean` | - | Whether the link is currently active. |
|
|
30
|
+
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child. |
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Standard Navigation Menu
|
|
35
|
+
```tsx
|
|
36
|
+
<AppNavigationMenu>
|
|
37
|
+
<AppNavigationMenuList>
|
|
38
|
+
<AppNavigationMenuItem>
|
|
39
|
+
<AppNavigationMenuTrigger>Components</AppNavigationMenuTrigger>
|
|
40
|
+
<AppNavigationMenuContent>
|
|
41
|
+
<ul className="grid w-[400px] gap-3 p-4">
|
|
42
|
+
<li>
|
|
43
|
+
<AppNavigationMenuLink asChild>
|
|
44
|
+
<a href="/docs/alert">Alert</a>
|
|
45
|
+
</AppNavigationMenuLink>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
48
|
+
</AppNavigationMenuContent>
|
|
49
|
+
</AppNavigationMenuItem>
|
|
50
|
+
<AppNavigationMenuItem>
|
|
51
|
+
<AppNavigationMenuLink className={navigationMenuTriggerStyle()} href="/docs">
|
|
52
|
+
Documentation
|
|
53
|
+
</AppNavigationMenuLink>
|
|
54
|
+
</AppNavigationMenuItem>
|
|
55
|
+
</AppNavigationMenuList>
|
|
56
|
+
</AppNavigationMenu>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Rules
|
|
60
|
+
|
|
61
|
+
- **Do** use `AppNavigationMenu` for primary website navigation.
|
|
62
|
+
- **Do** use `navigationMenuTriggerStyle()` for consistent styling on top-level links.
|
|
63
|
+
- **Don't** nest `AppNavigationMenu` components.
|
|
64
|
+
- If a prop you need is missing, stop and inform the design team.
|
|
65
|
+
|
|
66
|
+
## Responsive
|
|
67
|
+
|
|
68
|
+
`AppNavigationMenuList` scrolls horizontally on mobile via an `overflow-x-auto` wrapper with `min-w-max` on items so they never wrap. A hamburger-to-Sheet pattern is not implemented — if a collapsing nav is needed at mobile widths, build it at the page level using `AppSheet`.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# AppNotificationsMenu
|
|
2
|
+
|
|
3
|
+
## When to use
|
|
4
|
+
|
|
5
|
+
`AppNotificationsMenu` is a bell-icon trigger that opens a popover panel listing notifications. It is rendered automatically inside `AppHeader` — you do not need to import it directly unless building a layout outside `AppDashboard`.
|
|
6
|
+
|
|
7
|
+
To show notifications inside `AppDashboard`, pass notification props via the `header` prop on `AppDashboard`:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<AppDashboard
|
|
11
|
+
header={{
|
|
12
|
+
notifications: myNotifications,
|
|
13
|
+
notificationsTotal: 1433,
|
|
14
|
+
onShowMoreNotifications: handleShowMore,
|
|
15
|
+
onMarkAllNotificationsRead: handleMarkAll,
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
...
|
|
19
|
+
</AppDashboard>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `notifications` | `AppNotification[]` | `[]` | Notification items to display. Component is data-dumb — caller owns the array. |
|
|
27
|
+
| `totalCount` | `number` | `notifications.length` | Total count on the server. Used for "Show More (N of TOTAL)" footer label. |
|
|
28
|
+
| `pageSize` | `number` | `10` | Initial number of visible items. |
|
|
29
|
+
| `onShowMore` | `() => void` | — | Called when "Show More" is clicked. Append more items to `notifications` in the caller. |
|
|
30
|
+
| `onMarkAllAsRead` | `() => void` | — | Called when "Mark all as read" is selected from the meatball menu. |
|
|
31
|
+
| `open` | `boolean` | — | Controlled open state for the popover. |
|
|
32
|
+
| `onOpenChange` | `(open: boolean) => void` | — | Called when the panel open state changes. |
|
|
33
|
+
| `trigger` | `ReactNode` | Bell icon button | Replaces the default bell trigger entirely. |
|
|
34
|
+
| `className` | `string` | — | Extra classes applied to the popover content panel. |
|
|
35
|
+
|
|
36
|
+
## AppNotification type
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
type AppNotification = {
|
|
40
|
+
id: string
|
|
41
|
+
title: string
|
|
42
|
+
description?: string
|
|
43
|
+
/** ISO string or Date. Rendered as relative time ("20d ago"). */
|
|
44
|
+
timestamp: string | Date
|
|
45
|
+
variant?: "info" | "success" | "warning" | "destructive" // default "info"
|
|
46
|
+
/** When false, renders an unread dot before the title. */
|
|
47
|
+
read?: boolean
|
|
48
|
+
/** Override the default variant icon. */
|
|
49
|
+
icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>
|
|
50
|
+
href?: string
|
|
51
|
+
onClick?: () => void
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Variant → icon map
|
|
56
|
+
|
|
57
|
+
| Variant | Icon | Container colors |
|
|
58
|
+
|---------|------|-----------------|
|
|
59
|
+
| `info` | `Info` | `bg-info text-info-foreground` |
|
|
60
|
+
| `success` | `CheckCircle2` | `bg-success text-success-foreground` |
|
|
61
|
+
| `warning` | `AlertTriangle` | `bg-warning text-warning-foreground` |
|
|
62
|
+
| `destructive` | `AlertCircle` | `bg-destructive text-destructive-foreground` |
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### Inside AppDashboard (standard)
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<AppDashboard
|
|
70
|
+
nav={nav}
|
|
71
|
+
header={{
|
|
72
|
+
notifications: items,
|
|
73
|
+
notificationsTotal: 1433,
|
|
74
|
+
onShowMoreNotifications: () => fetchMore().then(more => setItems(prev => [...prev, ...more])),
|
|
75
|
+
onMarkAllNotificationsRead: () => setItems(prev => prev.map(n => ({ ...n, read: true }))),
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<PageContent />
|
|
79
|
+
</AppDashboard>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Hide the bell
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<AppDashboard header={{ showNotifications: false }}>...</AppDashboard>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Custom bell trigger
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<AppNotificationsMenu
|
|
92
|
+
notifications={items}
|
|
93
|
+
trigger={<AppButton variant="outline" size="sm">Alerts</AppButton>}
|
|
94
|
+
/>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Server-paged Show More
|
|
98
|
+
|
|
99
|
+
The component is intentionally dumb about data. "Show More" fires `onShowMore` — caller fetches the next page and appends to `notifications`. The footer auto-hides when `notifications.length >= totalCount`.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
const [items, setItems] = useState<AppNotification[]>(initialPage)
|
|
103
|
+
|
|
104
|
+
<AppNotificationsMenu
|
|
105
|
+
notifications={items}
|
|
106
|
+
totalCount={serverTotal}
|
|
107
|
+
pageSize={10}
|
|
108
|
+
onShowMore={async () => {
|
|
109
|
+
const next = await fetchPage(items.length)
|
|
110
|
+
setItems(prev => [...prev, ...next])
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Default header action order
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
network combobox → bell → bento → theme toggle → avatar
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The bell is positioned between the network combobox and the bento icon. This order is fixed in `AppHeader` default actions and cannot be changed via props (use the `actions` slot override on `AppHeader` to reorder).
|
|
122
|
+
|
|
123
|
+
## Rules
|
|
124
|
+
|
|
125
|
+
- **Do** keep the component data-dumb — never mutate `notifications` inside the component. Caller owns the array.
|
|
126
|
+
- **Do** pass `totalCount` when data is server-paged so the footer shows the correct "N of TOTAL" label.
|
|
127
|
+
- **Do** set `read: false` on new items to render the unread blue dot before the title.
|
|
128
|
+
- **Do** provide an `href` or `onClick` on each row that should be navigable.
|
|
129
|
+
- **Don't** add an unread badge count to the bell trigger — deferred to a future `unreadCount` prop.
|
|
130
|
+
- **Don't** sort or filter notifications inside this component — do it in the caller before passing the array.
|
|
131
|
+
- **Don't** use `AppScrollArea` inside this component — the list uses a plain overflow div; Radix ScrollArea requires a fixed height which conflicts with dynamic list heights.
|
|
132
|
+
|
|
133
|
+
If a prop you need is missing, stop and inform the design team.
|
|
134
|
+
|
|
135
|
+
## Responsive
|
|
136
|
+
|
|
137
|
+
On mobile (<768px) the panel renders as a side `AppDrawer` with the header row showing title, meatball menu, and close button together. On desktop (≥768px) it renders as a floating `AppPopover`. The component uses `useIsMobile` to branch between these two paths internally — the public API is unchanged across all viewport sizes. The bell trigger remains visible at all viewport widths.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# AppPageTitle
|
|
2
|
+
|
|
3
|
+
> **Default page header block** — present on most pages. Suppress it explicitly with `pageTitle={false}`.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { AppPageTitle } from "@/components/blocks/AppPageTitle"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or via `AppDashboard` (recommended):
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { AppDashboard } from "@/components/blocks/AppDashboard"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default | Description |
|
|
20
|
+
|------|------|---------|-------------|
|
|
21
|
+
| `title` | `string` | auto | Page heading, rendered as an `<h1>`. Auto-derived from the last pathname segment when omitted. |
|
|
22
|
+
| `subtitle` | `string` | — | Optional description rendered below the heading. |
|
|
23
|
+
| `breadcrumbs` | `AppPageTitleBreadcrumb[]` | auto | Explicit breadcrumb trail. Auto-generated from `window.location.pathname` when omitted. |
|
|
24
|
+
| `actions` | `ReactNode` | — | Right-side action slot. Accepts any elements — buttons, selects, text, etc. Empty by default. |
|
|
25
|
+
| `icon` | `ReactNode` | — | Optional icon rendered to the left of the title stack. Accepts `<img>`, SVG, or Lucide icons. Omit to hide. |
|
|
26
|
+
| `titleColor` | `"default" \| "primary"` | `"default"` | Controls the h1 color. `"default"` uses `text-foreground`; `"primary"` uses the brand blue. |
|
|
27
|
+
| `hidden` | `boolean` | `false` | Hides the entire block. |
|
|
28
|
+
| `className` | `string` | — | Extra classes on the root element. |
|
|
29
|
+
|
|
30
|
+
### AppPageTitleBreadcrumb
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
type AppPageTitleBreadcrumb = {
|
|
34
|
+
label: string
|
|
35
|
+
href?: string // Omit for the last (current-page) segment
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage via AppDashboard
|
|
40
|
+
|
|
41
|
+
### Default (title + breadcrumbs auto-derived from pathname)
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<AppDashboard nav={nav}>
|
|
45
|
+
<PageContent />
|
|
46
|
+
</AppDashboard>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### With explicit title and subtitle
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<AppDashboard
|
|
53
|
+
nav={nav}
|
|
54
|
+
pageTitle={{ title: "Site", subtitle: "Stay up to date to everything in your network" }}
|
|
55
|
+
>
|
|
56
|
+
<PageContent />
|
|
57
|
+
</AppDashboard>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### With actions
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<AppDashboard
|
|
64
|
+
nav={nav}
|
|
65
|
+
pageTitle={{
|
|
66
|
+
title: "Site",
|
|
67
|
+
subtitle: "Stay up to date to everything in your network",
|
|
68
|
+
actions: <AppButton>+ Add Site</AppButton>,
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<PageContent />
|
|
72
|
+
</AppDashboard>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Hidden
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<AppDashboard nav={nav} pageTitle={false}>
|
|
79
|
+
<PageContent />
|
|
80
|
+
</AppDashboard>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Manual breadcrumbs
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<AppDashboard
|
|
87
|
+
nav={nav}
|
|
88
|
+
pageTitle={{
|
|
89
|
+
title: "Site Details",
|
|
90
|
+
breadcrumbs: [
|
|
91
|
+
{ label: "Manage", href: "/manage" },
|
|
92
|
+
{ label: "Site", href: "/manage/site" },
|
|
93
|
+
{ label: "Site Details" },
|
|
94
|
+
],
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<PageContent />
|
|
98
|
+
</AppDashboard>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Breadcrumb auto-generation
|
|
102
|
+
|
|
103
|
+
When `breadcrumbs` is omitted, the component reads `window.location.pathname` on mount and converts each path segment into a readable label:
|
|
104
|
+
|
|
105
|
+
- Segments are split on `-` and `_`, then title-cased (`site-settings` → `Site Settings`)
|
|
106
|
+
- The final segment has no `href` (current page, non-linked)
|
|
107
|
+
- All preceding segments get `href` values pointing to their cumulative path
|
|
108
|
+
|
|
109
|
+
## With icon
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<AppDashboard
|
|
113
|
+
nav={nav}
|
|
114
|
+
pageTitle={{
|
|
115
|
+
title: "Hi Kevin! Welcome to Skunkworks",
|
|
116
|
+
subtitle: "Stay up to date to everything in your network",
|
|
117
|
+
breadcrumbs: [{ label: "Dashboard" }],
|
|
118
|
+
titleColor: "primary",
|
|
119
|
+
icon: (
|
|
120
|
+
<img
|
|
121
|
+
src="/persistent-icon.png"
|
|
122
|
+
alt="Dashboard"
|
|
123
|
+
className="size-16 rounded-xl"
|
|
124
|
+
/>
|
|
125
|
+
),
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<PageContent />
|
|
129
|
+
</AppDashboard>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## With primary title color
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<AppDashboard
|
|
136
|
+
nav={nav}
|
|
137
|
+
pageTitle={{
|
|
138
|
+
title: "Dashboard",
|
|
139
|
+
titleColor: "primary",
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
<PageContent />
|
|
143
|
+
</AppDashboard>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Rules
|
|
147
|
+
|
|
148
|
+
- **Do** use `pageTitle` on `AppDashboard` rather than placing `AppPageTitle` manually inside the page content.
|
|
149
|
+
- **Do** pass `pageTitle={false}` to explicitly suppress the block — do not conditionally omit it based on state.
|
|
150
|
+
- **Do** put action buttons in the `actions` slot — do not place them above the title.
|
|
151
|
+
- **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.
|
|
152
|
+
- **Don't** hardcode breadcrumbs when auto-generation is correct — only override when the URL structure doesn't match the desired labels.
|
|
153
|
+
- **Don't** add additional margin-top to page content — `AppPageTitle` already provides `mb-6` spacing.
|
|
154
|
+
- **Don't** use raw color values for `titleColor` overrides — use the `titleColor` prop, not a `className` on the heading.
|
|
155
|
+
|
|
156
|
+
If a prop you need is missing, stop and inform the design team.
|
|
157
|
+
|
|
158
|
+
## Responsive
|
|
159
|
+
|
|
160
|
+
On mobile (<768px) the title row stacks vertically (`flex-col`) with actions rendering below the title. On desktop (≥768px) title and actions are inline (`flex-row justify-between`). When a breadcrumb trail has more than 2 segments, middle items are automatically hidden on mobile using `hideOnMobile` — no manual configuration required.
|