@withmata/blueprints 0.3.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/audit/SKILL.md +4 -4
- package/.claude/skills/blueprint-catalog/SKILL.md +17 -7
- package/.claude/skills/copywrite/SKILL.md +187 -0
- package/.claude/skills/copywrite-landing/SKILL.md +489 -0
- package/.claude/skills/design-system/SKILL.md +970 -0
- package/.claude/skills/new-project/SKILL.md +168 -112
- package/.claude/skills/scaffold-auth/SKILL.md +9 -9
- package/.claude/skills/scaffold-db/SKILL.md +14 -14
- package/.claude/skills/scaffold-env/SKILL.md +4 -4
- package/.claude/skills/scaffold-foundation/SKILL.md +15 -15
- package/.claude/skills/scaffold-tailwind/SKILL.md +17 -3
- package/.claude/skills/scaffold-ui/SKILL.md +155 -36
- package/ENGINEERING.md +2 -2
- package/blueprints/discovery/design-system/BLUEPRINT.md +1479 -0
- package/blueprints/discovery/marketing-copywriting/BLUEPRINT.md +664 -0
- package/blueprints/features/auth-better-auth/BLUEPRINT.md +20 -22
- package/blueprints/features/db-drizzle-postgres/BLUEPRINT.md +12 -12
- package/blueprints/features/db-drizzle-postgres/files/db/src/example-entity.ts +1 -1
- package/blueprints/features/db-drizzle-postgres/files/db/src/scripts/seed.ts +1 -1
- package/blueprints/features/env-t3/BLUEPRINT.md +1 -1
- package/blueprints/features/tailwind-v4/BLUEPRINT.md +9 -2
- package/blueprints/features/tailwind-v4/files/tailwind-config/shared-styles.css +80 -1
- package/blueprints/features/ui-shared-components/BLUEPRINT.md +411 -78
- package/blueprints/features/ui-shared-components/files/ui/components/ui/alert-dialog.tsx +192 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/avatar.tsx +71 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/badge.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/breadcrumb.tsx +122 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/button.tsx +56 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card-select.tsx +72 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/card.tsx +100 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/collapsible.tsx +34 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/combobox.tsx +301 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/dropdown-menu.tsx +264 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/empty-state.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/entity-select.tsx +110 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/field.tsx +237 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/form-field.tsx +217 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input-group.tsx +161 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/input.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/label.tsx +20 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/org-switcher.tsx +114 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/page-header.tsx +45 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pagination.tsx +52 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/pill-select.tsx +151 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/popover.tsx +41 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/search-input.tsx +49 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/select.tsx +205 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/selected-entity-card.tsx +47 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/separator.tsx +25 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/sidebar.tsx +389 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/status-filter.tsx +43 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/tag-input.tsx +131 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/textarea.tsx +18 -0
- package/blueprints/features/ui-shared-components/files/ui/components/ui/user-menu.tsx +149 -0
- package/blueprints/features/ui-shared-components/files/ui/components.json +11 -8
- package/blueprints/features/ui-shared-components/files/ui/package.json +20 -11
- package/blueprints/foundation/monorepo-turbo/BLUEPRINT.md +19 -20
- package/blueprints/foundation/monorepo-turbo/files/root/package.json +1 -1
- package/dist/index.js +27 -10
- package/package.json +1 -1
- package/blueprints/features/tailwind-v4/files/tailwind-config/package.json +0 -20
- package/blueprints/foundation/monorepo-turbo/files/root/pnpm-workspace.yaml +0 -5
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
# UI Shared Components Blueprint — shadcn/ui
|
|
1
|
+
# UI Shared Components Blueprint — Base UI + shadcn/ui Component Library
|
|
2
2
|
|
|
3
3
|
## Tier
|
|
4
4
|
|
|
5
5
|
Feature
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Problem
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Building a UI component library for a TypeScript project requires solving several hard problems at once: accessible headless primitives, a variant system that scales, styling that works across package boundaries, and a component authoring pattern that balances consistency with flexibility. Teams either adopt a full component library and fight its opinions, or build from scratch and lose months on accessibility and API design. This blueprint provides 31 production-ready components built on Base UI headless primitives, a compound component architecture with `data-slot` CSS targeting, CVA-driven variant systems, and full shadcn CLI compatibility for adding more components — all exported as TypeScript source with no build step.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Status
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Complete.
|
|
14
14
|
|
|
15
15
|
## Blueprint Dependencies
|
|
16
16
|
|
|
17
17
|
| Blueprint | Type | Why |
|
|
18
18
|
|-----------|------|-----|
|
|
19
|
-
| `features/tailwind-v4` | required |
|
|
20
|
-
| `foundation/monorepo-turbo` | recommended | Provides workspace structure, TypeScript config, and package conventions. Without it,
|
|
19
|
+
| `features/tailwind-v4` | required | Components depend on design tokens, theme variables, and animation classes from the Tailwind design system. Without it, component styles break or fall back to browser defaults. |
|
|
20
|
+
| `foundation/monorepo-turbo` | recommended | Provides workspace structure, TypeScript config, and package conventions. Without it, components are created as a local directory with direct imports instead of workspace packages. |
|
|
21
21
|
|
|
22
22
|
### If `tailwind-v4` is not installed
|
|
23
23
|
|
|
24
|
-
The scaffold command
|
|
25
|
-
|
|
26
|
-
If declined, scaffold with basic Tailwind defaults and warn about missing design tokens.
|
|
24
|
+
The scaffold command automatically installs the Tailwind design system as a prerequisite — no user prompt needed. The design system (tokens, animations, typography) is a hard requirement for component styling. The scaffold skill runs the tailwind-v4 blueprint inline before proceeding with UI scaffolding.
|
|
27
25
|
|
|
28
26
|
---
|
|
29
27
|
|
|
@@ -31,23 +29,37 @@ If declined, scaffold with basic Tailwind defaults and warn about missing design
|
|
|
31
29
|
|
|
32
30
|
### Core Pattern
|
|
33
31
|
|
|
34
|
-
The UI package is a workspace package (`packages/ui/`) that exports components, utilities, styles, and icons via **path-based exports** in `package.json`. Consuming apps import directly from specific paths (`@scope/ui/components/ui/button`) rather than through barrel re-exports.
|
|
32
|
+
The UI package is a workspace package (`packages/ui/`) that exports 31 base components, utilities, styles, and icons via **path-based exports** in `package.json`. Components are built on **Base UI** (`@base-ui/react`) headless primitives with **CVA** for variant management and the **compound component pattern** for complex UI elements. Every component and sub-component receives a `data-slot` attribute for CSS targeting. Consuming apps import directly from specific paths (`@scope/ui/components/ui/button`) rather than through barrel re-exports.
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
The library ships with a custom **`base-maia` shadcn style** so that `shadcn add` generates Base UI components (not Radix) that are compatible with the existing library.
|
|
37
35
|
|
|
38
36
|
### Key Decisions
|
|
39
37
|
|
|
38
|
+
- **Base UI as primary headless library** (required) — Base UI (`@base-ui/react`) is built by the same team that created Radix, with a modernized API. It uses `data-*` attributes for state instead of Radix's `data-[state=*]` convention, which produces cleaner CSS selectors. It provides first-class render props for polymorphism instead of the `asChild` pattern. Every new component in this blueprint uses Base UI primitives.
|
|
39
|
+
|
|
40
|
+
- **Legacy Radix for popover and collapsible** (current state) — `collapsible.tsx` and `popover.tsx` still use Radix primitives because Base UI equivalents are not yet available or stable. These components work correctly alongside Base UI components. They can be migrated to Base UI when those primitives ship.
|
|
41
|
+
|
|
42
|
+
- **`data-slot` attribute convention** (required) — Every component root and sub-component element gets `data-slot="component-name"`. This enables CSS targeting via `[data-slot=button]` selectors, which are more stable than class-based selectors that break when Tailwind classes change. It also enables parent components to style children without prop drilling.
|
|
43
|
+
|
|
44
|
+
- **Compound component pattern** (required) — Complex components export multiple named parts that consumers compose freely. For example, `Card` exports `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, and `CardFooter`. This gives consumers full control over layout and ordering while keeping each part's styling encapsulated.
|
|
45
|
+
|
|
46
|
+
- **Phosphor Icons** (recommended) — Richer icon set than Lucide with six weights per icon (regular, bold, duotone, fill, light, thin). Tree-shakeable via deep imports (`@phosphor-icons/react/ArrowRight`). Consistent API across all icons. Can be swapped to Lucide or any other library.
|
|
47
|
+
|
|
40
48
|
- **Path-based exports** (required) — Instead of `import { Button } from "@scope/ui"`, use `import { Button } from "@scope/ui/components/ui/button"`. This avoids barrel export issues, enables per-component tree-shaking, and makes dependencies explicit. Package.json uses wildcard exports (`"./components/*": "./components/*.tsx"`).
|
|
41
49
|
|
|
42
50
|
- **Subpath imports with `#` prefix** (required) — Internal imports within the UI package use Node.js subpath imports (`#components/ui/button`). This provides clean paths without TypeScript path aliases and works natively with Node.js module resolution.
|
|
43
51
|
|
|
44
|
-
- **
|
|
52
|
+
- **Blueprint-owned vs user-owned components** (required) — The 31 template components shipped with this blueprint are blueprint-owned. They can be updated during `/scaffold-ui` upgrade mode. Components added via `shadcn add` are user-owned and never touched by upgrades. This separation allows the library to evolve without overwriting user customizations.
|
|
53
|
+
|
|
54
|
+
- **`base-maia` shadcn style** (recommended) — A custom shadcn style that generates Base UI components instead of Radix. Required for `shadcn add` to produce components compatible with the existing library. Configured in `components.json`. Can be changed to `new-york` or `default` if Radix compatibility is preferred.
|
|
45
55
|
|
|
46
56
|
- **No build step** (required) — The UI package exports TypeScript source directly (`.tsx` files). Consuming Next.js apps transpile via their own bundler. This eliminates a build step and enables hot module replacement for components during development.
|
|
47
57
|
|
|
48
|
-
- **`cn()` utility** (required) — Combines `clsx` for conditional classes and `tailwind-merge` for deduplication. Every component
|
|
58
|
+
- **`cn()` utility** (required) — Combines `clsx` for conditional classes and `tailwind-merge` for deduplication. Every component uses `cn()` for className composition. Located at `utils/cn.ts`.
|
|
59
|
+
|
|
60
|
+
- **CVA for variants** (required) — `class-variance-authority` for defining component variants. Re-exported from `utils/cva.ts` for consistency. Button alone has 6 variants x 8 sizes. Badge has 6 variants. Sidebar has multiple CVA-driven sub-component styles.
|
|
49
61
|
|
|
50
|
-
- **
|
|
62
|
+
- **Form system** (recommended) — `Field` (compound form field layout), `FormField` (higher-order field with hint popover system using IntersectionObserver for scroll detection and auto-show on focus), `TagInput` (controlled tag input with chips, max items, duplicate detection), and `PillSelect` (generic single-select pill buttons with search, collapse/expand, deselect) form a cohesive form building system.
|
|
51
63
|
|
|
52
64
|
- **Separate `icons/` directory** (recommended) — SVG icons and brand logos in their own directory with path-based exports (`@scope/ui/icons/Logo`). Keeps component imports clean.
|
|
53
65
|
|
|
@@ -58,19 +70,91 @@ Components use shadcn/ui conventions: Radix UI primitives for accessibility, `cl
|
|
|
58
70
|
## Hard Requirements vs Recommended Defaults
|
|
59
71
|
|
|
60
72
|
**Hard requirements:**
|
|
73
|
+
- Base UI as the headless primitive layer
|
|
74
|
+
- `data-slot` attribute on every component and sub-component
|
|
75
|
+
- Compound component pattern for complex components
|
|
61
76
|
- Path-based exports (no barrel re-exports for components)
|
|
62
77
|
- shadcn/ui CLI compatibility (`components.json`)
|
|
63
78
|
- `cn()` utility using clsx + tailwind-merge
|
|
79
|
+
- CVA for variant management
|
|
64
80
|
- TypeScript source exports (no build step)
|
|
65
81
|
- Styles import chain through tailwind-config
|
|
82
|
+
- `#` subpath imports for internal references
|
|
66
83
|
|
|
67
84
|
**Recommended defaults — ask during scaffolding:**
|
|
68
85
|
|
|
69
86
|
| Choice | Recommended | Alternatives |
|
|
70
87
|
|---|---|---|
|
|
71
|
-
| shadcn style | `"
|
|
72
|
-
| Icon library | `"
|
|
73
|
-
| Include
|
|
88
|
+
| shadcn style | `"base-maia"` | `"new-york"`, `"default"`, or any shadcn style |
|
|
89
|
+
| Icon library | `"phosphor"` | `"lucide"`, `"radix"`, or custom |
|
|
90
|
+
| Include base components | Yes (all 31) | No (skeleton only — empty `components/ui/`) |
|
|
91
|
+
| Include form system | Yes | No (skip `form-field`, `field`, `tag-input`, `pill-select`) |
|
|
92
|
+
| Which consuming apps get globals.css update | Ask during scaffolding | — |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Components
|
|
97
|
+
|
|
98
|
+
### Primitives (no internal UI deps)
|
|
99
|
+
|
|
100
|
+
| Component | File | Headless Primitive | Notes |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| Button | `button.tsx` | Base UI `ButtonPrimitive` | 6 variants (default, destructive, outline, secondary, ghost, link) x 8 sizes. CVA-driven. |
|
|
103
|
+
| Input | `input.tsx` | Base UI `InputPrimitive` | Standard text input with `data-slot="input"`. |
|
|
104
|
+
| Label | `label.tsx` | Native `<label>` | Styled label with `data-slot="label"`. |
|
|
105
|
+
| Textarea | `textarea.tsx` | Native `<textarea>` | Auto-styled textarea with `data-slot="textarea"`. |
|
|
106
|
+
| Badge | `badge.tsx` | Base UI `useRender` | 6 variants. Polymorphic via render prop — can render as links, buttons, etc. |
|
|
107
|
+
| Separator | `separator.tsx` | Base UI `SeparatorPrimitive` | Horizontal/vertical with `data-slot="separator"`. |
|
|
108
|
+
|
|
109
|
+
### Compound Components
|
|
110
|
+
|
|
111
|
+
| Component | File | Headless Primitive | Exports |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| Card | `card.tsx` | None (semantic HTML) | 7 parts: Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter |
|
|
114
|
+
| Alert Dialog | `alert-dialog.tsx` | Base UI `AlertDialogPrimitive` | 12 parts including AlertDialogMedia for icons/illustrations |
|
|
115
|
+
| Select | `select.tsx` | Base UI `SelectPrimitive` | 10 parts with Portal/Positioner, scroll buttons |
|
|
116
|
+
| Combobox | `combobox.tsx` | Base UI `ComboboxPrimitive` | 16 exports with chips support, InputGroup integration |
|
|
117
|
+
| Dropdown Menu | `dropdown-menu.tsx` | Base UI `MenuPrimitive` | 15 exports with sub-menus, checkbox items, radio items |
|
|
118
|
+
| Sidebar | `sidebar.tsx` | Context-based | 14 exports with cookie persistence, CVA variants for width/behavior |
|
|
119
|
+
| Field | `field.tsx` | None (compound layout) | 10 parts: Field, FieldGroup, FieldSet, FieldLabel, FieldDescription, FieldError, FieldSeparator, FieldContent, FieldTitle, FieldLegend |
|
|
120
|
+
| Input Group | `input-group.tsx` | None (compound layout) | 6 parts: InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea |
|
|
121
|
+
|
|
122
|
+
### Navigation/Utility
|
|
123
|
+
|
|
124
|
+
| Component | File | Headless Primitive | Notes |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| Breadcrumb | `breadcrumb.tsx` | Base UI `useRender` | 7 exports. Polymorphic links via render prop. |
|
|
127
|
+
| Collapsible | `collapsible.tsx` | Radix `CollapsiblePrimitive` | Legacy Radix — awaiting Base UI port. |
|
|
128
|
+
| Popover | `popover.tsx` | Radix `PopoverPrimitive` | Legacy Radix — forwardRef pattern. Awaiting Base UI port. |
|
|
129
|
+
|
|
130
|
+
### Composed Form Widgets
|
|
131
|
+
|
|
132
|
+
| Component | File | Deps | Notes |
|
|
133
|
+
|---|---|---|---|
|
|
134
|
+
| Form Field | `form-field.tsx` | Field, Popover | Higher-order FormField with hint popover system. `FieldHint` interface, IntersectionObserver for scroll detection, auto-show hints on focus. |
|
|
135
|
+
| Tag Input | `tag-input.tsx` | Button | Controlled tag input with chips, max items, duplicate detection, counter display. |
|
|
136
|
+
| Pill Select | `pill-select.tsx` | Button, Input | Generic single-select pill buttons with search, collapse/expand, deselect support. |
|
|
137
|
+
| Card Select | `card-select.tsx` | Badge | Grid of selectable option cards with label + description. Configurable columns (2/3/4), disabled state with badge label. |
|
|
138
|
+
| Entity Select | `entity-select.tsx` | Combobox | Generic type-safe combobox with render props for options/selected state, "Create new" action, loading/empty states. |
|
|
139
|
+
| Selected Entity Card | `selected-entity-card.tsx` | Button | Compact card showing a selected entity with remove button and optional edit action. Companion to EntitySelect. |
|
|
140
|
+
|
|
141
|
+
### Data Display
|
|
142
|
+
|
|
143
|
+
| Component | File | Deps | Notes |
|
|
144
|
+
|---|---|---|---|
|
|
145
|
+
| Pagination | `pagination.tsx` | Button | Offset-based prev/next pagination with "Showing X-Y of Z" display. Auto-hides when total ≤ limit. |
|
|
146
|
+
| Empty State | `empty-state.tsx` | Button | Centered layout with icon circle, heading, description, and optional CTA button. For empty lists/tables. |
|
|
147
|
+
| Avatar | `avatar.tsx` | None | Image with initials fallback, error handling. CVA-driven size variants (xs/sm/default/lg/xl). |
|
|
148
|
+
|
|
149
|
+
### App Shell & Layout
|
|
150
|
+
|
|
151
|
+
| Component | File | Deps | Notes |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| User Menu | `user-menu.tsx` | Sidebar, DropdownMenu, Avatar | Sidebar footer user menu with avatar, name/email, dropdown actions (profile, settings, sign out). Adapts to collapsed sidebar state. |
|
|
154
|
+
| Org Switcher | `org-switcher.tsx` | DropdownMenu, Button | Organization switcher dropdown with current org display, switch list with check indicator, and "Create Organization" action with PlusIcon. |
|
|
155
|
+
| Page Header | `page-header.tsx` | Button | Page title + description + action button (defaults to PlusIcon). Standard header for list/entity pages. |
|
|
156
|
+
| Status Filter | `status-filter.tsx` | Button | Pill-style filter tabs toggling active variant. Supports optional count display. Generic for any status enum. |
|
|
157
|
+
| Search Input | `search-input.tsx` | Input | Debounced search input with MagnifyingGlass icon. Configurable delay (default 300ms). |
|
|
74
158
|
|
|
75
159
|
---
|
|
76
160
|
|
|
@@ -81,19 +165,23 @@ Components use shadcn/ui conventions: Radix UI primitives for accessibility, `cl
|
|
|
81
165
|
**packages/ui:**
|
|
82
166
|
| Package | Version | Purpose |
|
|
83
167
|
|---|---|---|
|
|
84
|
-
|
|
|
85
|
-
| `@
|
|
86
|
-
|
|
|
87
|
-
| `
|
|
88
|
-
|
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
168
|
+
| `@base-ui/react` | ^1.0.0 | Headless UI primitives (Button, Input, Select, Combobox, Menu, AlertDialog, Separator, Badge) |
|
|
169
|
+
| `@phosphor-icons/react` | ^2.1.10 | Icon library with 6 weights per icon |
|
|
170
|
+
| `@radix-ui/react-collapsible` | ^1.1.12 | Collapsible primitive (legacy, pending Base UI port) |
|
|
171
|
+
| `@radix-ui/react-popover` | ^1.1.15 | Popover primitive (legacy, pending Base UI port) |
|
|
172
|
+
| `@radix-ui/react-slot` | ^1.2.4 | Polymorphic component support (used by some composed components) |
|
|
173
|
+
| `class-variance-authority` | ^0.7.1 | Component variant management |
|
|
174
|
+
| `clsx` | ^2.1.1 | Conditional className utility |
|
|
175
|
+
| `tailwind-merge` | ^3.4.0 | Smart Tailwind class deduplication |
|
|
176
|
+
| `tw-animate-css` | ^1.4.0 | Animation classes for transitions |
|
|
177
|
+
| `shadcn` | ^3.6.2 | Component generation CLI |
|
|
178
|
+
| `react` | ^19.1.0 | React |
|
|
179
|
+
| `react-dom` | ^19.1.0 | React DOM |
|
|
93
180
|
|
|
94
181
|
**packages/ui (dev):**
|
|
95
182
|
| Package | Version | Purpose |
|
|
96
183
|
|---|---|---|
|
|
184
|
+
| `{{SCOPE}}/tailwind-config` | `workspace:*` | Shared design tokens |
|
|
97
185
|
| `{{SCOPE}}/typescript-config` | `workspace:*` | Shared TypeScript config |
|
|
98
186
|
| `@tailwindcss/postcss` | ^4 | PostCSS plugin |
|
|
99
187
|
| `tailwindcss` | ^4 | Tailwind CSS |
|
|
@@ -132,7 +220,27 @@ Components use shadcn/ui conventions: Radix UI primitives for accessibility, `cl
|
|
|
132
220
|
```
|
|
133
221
|
packages/ui/
|
|
134
222
|
├── components/
|
|
135
|
-
│ └── ui/ #
|
|
223
|
+
│ └── ui/ # 31 base components (blueprint-owned)
|
|
224
|
+
│ ├── button.tsx
|
|
225
|
+
│ ├── input.tsx
|
|
226
|
+
│ ├── label.tsx
|
|
227
|
+
│ ├── textarea.tsx
|
|
228
|
+
│ ├── badge.tsx
|
|
229
|
+
│ ├── separator.tsx
|
|
230
|
+
│ ├── card.tsx
|
|
231
|
+
│ ├── alert-dialog.tsx
|
|
232
|
+
│ ├── select.tsx
|
|
233
|
+
│ ├── combobox.tsx
|
|
234
|
+
│ ├── dropdown-menu.tsx
|
|
235
|
+
│ ├── sidebar.tsx
|
|
236
|
+
│ ├── field.tsx
|
|
237
|
+
│ ├── input-group.tsx
|
|
238
|
+
│ ├── breadcrumb.tsx
|
|
239
|
+
│ ├── collapsible.tsx
|
|
240
|
+
│ ├── popover.tsx
|
|
241
|
+
│ ├── form-field.tsx
|
|
242
|
+
│ ├── tag-input.tsx
|
|
243
|
+
│ └── pill-select.tsx
|
|
136
244
|
├── icons/
|
|
137
245
|
│ └── logos/ # Brand logos (SVG as React components)
|
|
138
246
|
├── styles/
|
|
@@ -140,7 +248,7 @@ packages/ui/
|
|
|
140
248
|
├── utils/
|
|
141
249
|
│ ├── cn.ts # Class merging utility
|
|
142
250
|
│ └── cva.ts # CVA re-export
|
|
143
|
-
├── components.json # shadcn CLI config
|
|
251
|
+
├── components.json # shadcn CLI config (base-maia style)
|
|
144
252
|
├── package.json # Exports, imports, deps
|
|
145
253
|
└── tsconfig.json # Extends shared react-library config
|
|
146
254
|
```
|
|
@@ -162,8 +270,9 @@ The `@source` directive tells Tailwind to scan the UI package for classes. The r
|
|
|
162
270
|
**Usage in components:**
|
|
163
271
|
```typescript
|
|
164
272
|
import { Button } from "{{SCOPE}}/ui/components/ui/button";
|
|
273
|
+
import { Card, CardHeader, CardTitle, CardContent } from "{{SCOPE}}/ui/components/ui/card";
|
|
165
274
|
import { cn } from "{{SCOPE}}/ui/utils/cn";
|
|
166
|
-
import {
|
|
275
|
+
import { ArrowRight } from "@phosphor-icons/react/ArrowRight";
|
|
167
276
|
```
|
|
168
277
|
|
|
169
278
|
---
|
|
@@ -176,20 +285,26 @@ For standalone applications without a monorepo, UI components live alongside the
|
|
|
176
285
|
|
|
177
286
|
| Aspect | Monorepo | Single-Repo |
|
|
178
287
|
|--------|----------|-------------|
|
|
179
|
-
| UI code location | `packages/ui/` (workspace package) | `src/ui/`
|
|
288
|
+
| UI code location | `packages/ui/` (workspace package) | `src/ui/` |
|
|
289
|
+
| Component location | `packages/ui/components/ui/` | `src/ui/components/ui/` |
|
|
180
290
|
| Import pattern | `import { x } from "@scope/ui/components/ui/button"` | `import { x } from "@/ui/components/ui/button"` |
|
|
181
291
|
| Package dependencies | `workspace:*` protocol | Direct — packages at root `package.json` |
|
|
182
292
|
| TypeScript config | Extends `@scope/typescript-config/react-library.json` | Inherits from root `tsconfig.json` |
|
|
183
293
|
| Styles import | `@import "{{SCOPE}}/ui/styles"` in consuming app | Not needed (same bundle) |
|
|
184
294
|
| `@source` directive | Required (cross-package scanning) | Not needed (same project) |
|
|
185
295
|
| shadcn CLI | Run from `packages/ui/` | Run from project root |
|
|
296
|
+
| `package.json` / `tsconfig.json` | `packages/ui/package.json` + `packages/ui/tsconfig.json` | N/A (use root configs) |
|
|
186
297
|
|
|
187
298
|
### What Stays the Same
|
|
188
299
|
|
|
300
|
+
- Base UI headless primitives and `data-slot` convention
|
|
301
|
+
- Compound component pattern and component API shapes
|
|
189
302
|
- Path-based exports pattern (via `package.json` exports or TypeScript paths)
|
|
190
|
-
- `cn()` utility and CVA
|
|
191
|
-
- `components.json` for shadcn CLI
|
|
303
|
+
- `cn()` utility and CVA patterns
|
|
304
|
+
- `components.json` for shadcn CLI (base-maia style)
|
|
192
305
|
- Component file organization (`components/ui/`, `icons/`, `utils/`)
|
|
306
|
+
- All 31 base components — identical source files
|
|
307
|
+
- Phosphor Icons integration
|
|
193
308
|
|
|
194
309
|
---
|
|
195
310
|
|
|
@@ -197,12 +312,48 @@ For standalone applications without a monorepo, UI components live alongside the
|
|
|
197
312
|
|
|
198
313
|
| File | Purpose | Monorepo Target | Single-Repo Target |
|
|
199
314
|
|---|---|---|---|
|
|
200
|
-
|
|
|
315
|
+
| **Config/Utilities** | | | |
|
|
316
|
+
| `files/ui/package.json` | Package config with exports, imports, deps, Base UI + Radix + Phosphor dependencies | `packages/ui/package.json` | N/A (merge deps into root `package.json`) |
|
|
317
|
+
| `files/ui/tsconfig.json` | TypeScript config extending shared react-library base | `packages/ui/tsconfig.json` | N/A (use root `tsconfig.json`) |
|
|
318
|
+
| `files/ui/components.json` | shadcn CLI configuration with `base-maia` style | `packages/ui/components.json` | `components.json` (project root) |
|
|
319
|
+
| `files/ui/styles/globals.css` | UI package styles (imports tailwind-config, animation classes) | `packages/ui/styles/globals.css` | `src/ui/styles/globals.css` |
|
|
201
320
|
| `files/ui/utils/cn.ts` | Class merging utility (clsx + tailwind-merge) | `packages/ui/utils/cn.ts` | `src/ui/utils/cn.ts` |
|
|
202
321
|
| `files/ui/utils/cva.ts` | CVA re-export for component variants | `packages/ui/utils/cva.ts` | `src/ui/utils/cva.ts` |
|
|
203
|
-
|
|
|
204
|
-
| `files/ui/
|
|
205
|
-
| `files/ui/
|
|
322
|
+
| **Primitive Components** | | | |
|
|
323
|
+
| `files/ui/components/ui/button.tsx` | Button with Base UI primitive, 6 variants x 8 sizes, CVA | `packages/ui/components/ui/button.tsx` | `src/ui/components/ui/button.tsx` |
|
|
324
|
+
| `files/ui/components/ui/input.tsx` | Text input with Base UI InputPrimitive | `packages/ui/components/ui/input.tsx` | `src/ui/components/ui/input.tsx` |
|
|
325
|
+
| `files/ui/components/ui/label.tsx` | Form label with data-slot targeting | `packages/ui/components/ui/label.tsx` | `src/ui/components/ui/label.tsx` |
|
|
326
|
+
| `files/ui/components/ui/textarea.tsx` | Textarea with data-slot targeting | `packages/ui/components/ui/textarea.tsx` | `src/ui/components/ui/textarea.tsx` |
|
|
327
|
+
| `files/ui/components/ui/badge.tsx` | Badge with Base UI useRender, 6 variants, polymorphic render prop | `packages/ui/components/ui/badge.tsx` | `src/ui/components/ui/badge.tsx` |
|
|
328
|
+
| `files/ui/components/ui/separator.tsx` | Separator with Base UI SeparatorPrimitive | `packages/ui/components/ui/separator.tsx` | `src/ui/components/ui/separator.tsx` |
|
|
329
|
+
| **Compound Components** | | | |
|
|
330
|
+
| `files/ui/components/ui/card.tsx` | Card with 7 compound parts (Header, Title, Description, Action, Content, Footer) | `packages/ui/components/ui/card.tsx` | `src/ui/components/ui/card.tsx` |
|
|
331
|
+
| `files/ui/components/ui/alert-dialog.tsx` | Alert dialog with Base UI AlertDialogPrimitive, 12 parts including AlertDialogMedia | `packages/ui/components/ui/alert-dialog.tsx` | `src/ui/components/ui/alert-dialog.tsx` |
|
|
332
|
+
| `files/ui/components/ui/select.tsx` | Select with Base UI SelectPrimitive, Portal/Positioner, scroll buttons, 10 parts | `packages/ui/components/ui/select.tsx` | `src/ui/components/ui/select.tsx` |
|
|
333
|
+
| `files/ui/components/ui/combobox.tsx` | Combobox with Base UI ComboboxPrimitive, chips support, InputGroup integration, 16 exports | `packages/ui/components/ui/combobox.tsx` | `src/ui/components/ui/combobox.tsx` |
|
|
334
|
+
| `files/ui/components/ui/dropdown-menu.tsx` | Dropdown menu with Base UI MenuPrimitive, sub-menus, checkbox/radio items, 15 exports | `packages/ui/components/ui/dropdown-menu.tsx` | `src/ui/components/ui/dropdown-menu.tsx` |
|
|
335
|
+
| `files/ui/components/ui/sidebar.tsx` | Sidebar with context-based state, cookie persistence, CVA variants, 14 exports | `packages/ui/components/ui/sidebar.tsx` | `src/ui/components/ui/sidebar.tsx` |
|
|
336
|
+
| `files/ui/components/ui/field.tsx` | Field compound layout with 10 parts (Field, FieldGroup, FieldSet, FieldLabel, etc.) | `packages/ui/components/ui/field.tsx` | `src/ui/components/ui/field.tsx` |
|
|
337
|
+
| `files/ui/components/ui/input-group.tsx` | Input group compound with 6 parts (Addon, Button, Text, Input, Textarea) | `packages/ui/components/ui/input-group.tsx` | `src/ui/components/ui/input-group.tsx` |
|
|
338
|
+
| **Navigation/Utility** | | | |
|
|
339
|
+
| `files/ui/components/ui/breadcrumb.tsx` | Breadcrumb with Base UI useRender for polymorphic links, 7 exports | `packages/ui/components/ui/breadcrumb.tsx` | `src/ui/components/ui/breadcrumb.tsx` |
|
|
340
|
+
| `files/ui/components/ui/collapsible.tsx` | Collapsible with Radix CollapsiblePrimitive (legacy) | `packages/ui/components/ui/collapsible.tsx` | `src/ui/components/ui/collapsible.tsx` |
|
|
341
|
+
| `files/ui/components/ui/popover.tsx` | Popover with Radix PopoverPrimitive, forwardRef pattern (legacy) | `packages/ui/components/ui/popover.tsx` | `src/ui/components/ui/popover.tsx` |
|
|
342
|
+
| **Composed Form Widgets** | | | |
|
|
343
|
+
| `files/ui/components/ui/form-field.tsx` | Higher-order FormField with hint popover system (FieldHint interface, IntersectionObserver scroll detection, auto-show on focus) | `packages/ui/components/ui/form-field.tsx` | `src/ui/components/ui/form-field.tsx` |
|
|
344
|
+
| `files/ui/components/ui/tag-input.tsx` | Controlled tag input with chips, max items, duplicate detection, counter | `packages/ui/components/ui/tag-input.tsx` | `src/ui/components/ui/tag-input.tsx` |
|
|
345
|
+
| `files/ui/components/ui/pill-select.tsx` | Generic single-select pill buttons with search, collapse/expand, deselect | `packages/ui/components/ui/pill-select.tsx` | `src/ui/components/ui/pill-select.tsx` |
|
|
346
|
+
| `files/ui/components/ui/card-select.tsx` | Grid of selectable option cards with label, description, disabled state | `packages/ui/components/ui/card-select.tsx` | `src/ui/components/ui/card-select.tsx` |
|
|
347
|
+
| `files/ui/components/ui/entity-select.tsx` | Generic combobox with render props, "Create new" action, loading/empty states | `packages/ui/components/ui/entity-select.tsx` | `src/ui/components/ui/entity-select.tsx` |
|
|
348
|
+
| `files/ui/components/ui/selected-entity-card.tsx` | Compact selected entity display with remove/edit actions | `packages/ui/components/ui/selected-entity-card.tsx` | `src/ui/components/ui/selected-entity-card.tsx` |
|
|
349
|
+
| `files/ui/components/ui/pagination.tsx` | Offset-based prev/next pagination with count display | `packages/ui/components/ui/pagination.tsx` | `src/ui/components/ui/pagination.tsx` |
|
|
350
|
+
| `files/ui/components/ui/empty-state.tsx` | Centered empty state with icon, heading, description, CTA | `packages/ui/components/ui/empty-state.tsx` | `src/ui/components/ui/empty-state.tsx` |
|
|
351
|
+
| `files/ui/components/ui/avatar.tsx` | Image avatar with initials fallback and size variants | `packages/ui/components/ui/avatar.tsx` | `src/ui/components/ui/avatar.tsx` |
|
|
352
|
+
| `files/ui/components/ui/user-menu.tsx` | Sidebar footer user menu with avatar, dropdown actions, collapsed state | `packages/ui/components/ui/user-menu.tsx` | `src/ui/components/ui/user-menu.tsx` |
|
|
353
|
+
| `files/ui/components/ui/org-switcher.tsx` | Organization switcher dropdown with switch list and create action | `packages/ui/components/ui/org-switcher.tsx` | `src/ui/components/ui/org-switcher.tsx` |
|
|
354
|
+
| `files/ui/components/ui/page-header.tsx` | Page title + description + action button with PlusIcon default | `packages/ui/components/ui/page-header.tsx` | `src/ui/components/ui/page-header.tsx` |
|
|
355
|
+
| `files/ui/components/ui/status-filter.tsx` | Pill-style status filter tabs with count display | `packages/ui/components/ui/status-filter.tsx` | `src/ui/components/ui/status-filter.tsx` |
|
|
356
|
+
| `files/ui/components/ui/search-input.tsx` | Debounced search input with magnifying glass icon | `packages/ui/components/ui/search-input.tsx` | `src/ui/components/ui/search-input.tsx` |
|
|
206
357
|
|
|
207
358
|
---
|
|
208
359
|
|
|
@@ -211,57 +362,73 @@ For standalone applications without a monorepo, UI components live alongside the
|
|
|
211
362
|
### Phase 1: Package Setup
|
|
212
363
|
|
|
213
364
|
1. Create `packages/ui/` directory
|
|
214
|
-
2. Copy `package.json` — replace `{{SCOPE}}`
|
|
215
|
-
3. Copy `tsconfig.json` — replace `{{SCOPE}}`
|
|
216
|
-
4. Copy `components.json`
|
|
365
|
+
2. Copy `files/ui/package.json` — replace `{{SCOPE}}` with project scope
|
|
366
|
+
3. Copy `files/ui/tsconfig.json` — replace `{{SCOPE}}`
|
|
367
|
+
4. Copy `files/ui/components.json`
|
|
217
368
|
|
|
218
369
|
### Phase 2: Utilities and Styles
|
|
219
370
|
|
|
220
371
|
5. Create `utils/` directory
|
|
221
|
-
6. Copy `cn.ts`
|
|
222
|
-
7. Copy `cva.ts`
|
|
372
|
+
6. Copy `files/ui/utils/cn.ts`
|
|
373
|
+
7. Copy `files/ui/utils/cva.ts`
|
|
223
374
|
8. Create `styles/` directory
|
|
224
|
-
9. Copy `globals.css` — replace `{{SCOPE}}`
|
|
375
|
+
9. Copy `files/ui/styles/globals.css` — replace `{{SCOPE}}`
|
|
225
376
|
|
|
226
377
|
### Phase 3: Directory Structure
|
|
227
378
|
|
|
228
|
-
10. Create `components/ui/` directory
|
|
229
|
-
11. Create `icons/` directory (empty — user adds
|
|
230
|
-
12. Create `icons/logos/` directory
|
|
379
|
+
10. Create `components/ui/` directory
|
|
380
|
+
11. Create `icons/` directory (empty — user adds custom icons)
|
|
381
|
+
12. Create `icons/logos/` directory (empty — user adds brand assets)
|
|
382
|
+
|
|
383
|
+
### Phase 4: Base Components
|
|
231
384
|
|
|
232
|
-
|
|
385
|
+
13. Copy all 31 component files from `files/ui/components/ui/` to `packages/ui/components/ui/`:
|
|
386
|
+
- Primitives: `button.tsx`, `input.tsx`, `label.tsx`, `textarea.tsx`, `badge.tsx`, `separator.tsx`
|
|
387
|
+
- Compound: `card.tsx`, `alert-dialog.tsx`, `select.tsx`, `combobox.tsx`, `dropdown-menu.tsx`, `sidebar.tsx`, `field.tsx`, `input-group.tsx`
|
|
388
|
+
- Navigation: `breadcrumb.tsx`, `collapsible.tsx`, `popover.tsx`
|
|
389
|
+
- Form widgets: `form-field.tsx`, `tag-input.tsx`, `pill-select.tsx`, `card-select.tsx`, `entity-select.tsx`, `selected-entity-card.tsx`
|
|
390
|
+
- Data display: `pagination.tsx`, `empty-state.tsx`, `avatar.tsx`
|
|
391
|
+
- App shell: `user-menu.tsx`, `org-switcher.tsx`, `page-header.tsx`, `status-filter.tsx`, `search-input.tsx`
|
|
233
392
|
|
|
234
|
-
|
|
393
|
+
### Phase 5: Consuming App Integration
|
|
394
|
+
|
|
395
|
+
14. Update `apps/web/app/globals.css`:
|
|
235
396
|
- Add `@import "{{SCOPE}}/ui/styles";` after the tailwind-config import
|
|
236
397
|
- Add `@plugin "@tailwindcss/typography";`
|
|
237
398
|
- Add `@source "../../../packages/ui";` (compute relative path based on app location)
|
|
238
|
-
|
|
399
|
+
15. Verify the workspace configuration in root `package.json` includes `packages/*`
|
|
239
400
|
|
|
240
|
-
### Phase
|
|
401
|
+
### Phase 6: Dependency Installation
|
|
241
402
|
|
|
242
|
-
|
|
243
|
-
|
|
403
|
+
16. Run `bun install` to wire workspace dependencies and install Base UI, Phosphor, Radix legacy, CVA, and other packages
|
|
404
|
+
17. Verify no peer dependency warnings
|
|
244
405
|
|
|
245
|
-
### Phase
|
|
406
|
+
### Phase 7: Verification
|
|
246
407
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
408
|
+
18. Run `bun dev` — no CSS/import errors
|
|
409
|
+
19. Import a component in `apps/web/` and verify it renders with correct styles:
|
|
410
|
+
```typescript
|
|
411
|
+
import { Button } from "@scope/ui/components/ui/button";
|
|
412
|
+
```
|
|
413
|
+
20. Verify `data-slot` attributes appear in rendered HTML via browser dev tools
|
|
414
|
+
21. Try adding a shadcn component: `cd packages/ui && bunx shadcn@latest add tooltip` — verify it uses `base-maia` style and produces Base UI output
|
|
250
415
|
|
|
251
416
|
---
|
|
252
417
|
|
|
253
418
|
## Adding Components
|
|
254
419
|
|
|
255
|
-
|
|
420
|
+
### Via shadcn CLI (user-owned)
|
|
421
|
+
|
|
422
|
+
After initial scaffolding, add more shadcn/ui components. The `base-maia` style ensures generated components use Base UI primitives and follow the `data-slot` convention:
|
|
256
423
|
|
|
257
424
|
```bash
|
|
258
425
|
# From packages/ui/
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
426
|
+
bunx shadcn@latest add tooltip
|
|
427
|
+
bunx shadcn@latest add tabs
|
|
428
|
+
bunx shadcn@latest add dialog
|
|
262
429
|
```
|
|
263
430
|
|
|
264
|
-
Components
|
|
431
|
+
Components generated via `shadcn add` are **user-owned** — the blueprint never modifies them during upgrades.
|
|
265
432
|
|
|
266
433
|
### Custom Components
|
|
267
434
|
|
|
@@ -279,7 +446,7 @@ interface FeatureCardProps {
|
|
|
279
446
|
|
|
280
447
|
export function FeatureCard({ title, className, children }: FeatureCardProps) {
|
|
281
448
|
return (
|
|
282
|
-
<div className={cn("rounded-lg border p-6", className)}>
|
|
449
|
+
<div data-slot="feature-card" className={cn("rounded-lg border p-6", className)}>
|
|
283
450
|
<h3 className="text-lg font-semibold">{title}</h3>
|
|
284
451
|
{children}
|
|
285
452
|
</div>
|
|
@@ -291,26 +458,160 @@ Import in consuming apps: `import { FeatureCard } from "@scope/ui/components/fea
|
|
|
291
458
|
|
|
292
459
|
---
|
|
293
460
|
|
|
461
|
+
## Usage Patterns
|
|
462
|
+
|
|
463
|
+
These patterns document **how to compose** the base components into real application features. They are conventions extracted from production usage — not additional components, but recipes for building with the component library.
|
|
464
|
+
|
|
465
|
+
### Icon Conventions
|
|
466
|
+
|
|
467
|
+
- **Create/add actions**: Always use `PlusIcon` before the label: `<Button><PlusIcon className="size-4" />New Item</Button>`
|
|
468
|
+
- **Navigation arrows**: `CaretLeftIcon` / `CaretRightIcon` for pagination, `CaretUpDownIcon` for dropdown triggers
|
|
469
|
+
- **Menu triggers**: `DotsThreeIcon weight="bold"` for action menus on cards/rows
|
|
470
|
+
- **Import style**: Prefer deep imports for tree-shaking: `import { PlusIcon } from "@phosphor-icons/react/Plus"`
|
|
471
|
+
- **Default icon size**: `size-4` (16px) inside buttons and menu items, `size-3.5` for inline indicators
|
|
472
|
+
|
|
473
|
+
### Form Integration with React Hook Form + Zod
|
|
474
|
+
|
|
475
|
+
The recommended form stack is **react-hook-form** + **@hookform/resolvers** + **zod** + **FormField**:
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
import { useForm, Controller } from "react-hook-form";
|
|
479
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
480
|
+
|
|
481
|
+
const { register, control, handleSubmit, formState: { errors } } = useForm({
|
|
482
|
+
resolver: zodResolver(schema),
|
|
483
|
+
defaultValues: { ... },
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**Field connection patterns:**
|
|
488
|
+
- **Simple inputs** — use `register()` directly: `<Input {...register("name")} />`
|
|
489
|
+
- **Custom/controlled fields** — use `Controller`: `<Controller name="field" control={control} render={({ field }) => <CardSelect value={field.value} onChange={field.onChange} />} />`
|
|
490
|
+
- **FormField wrapper** — wraps every field with label, error, description, and optional hint system
|
|
491
|
+
|
|
492
|
+
**Hint system wiring:**
|
|
493
|
+
```tsx
|
|
494
|
+
const [showHints, setShowHints] = useState(true);
|
|
495
|
+
const [activeHintField, setActiveHintField] = useState<string | null>(null);
|
|
496
|
+
|
|
497
|
+
<FormField
|
|
498
|
+
label="Name" name="name" required error={errors.name?.message}
|
|
499
|
+
hint={{ directive: "Be specific", example: "Enterprise SaaS for HR teams" }}
|
|
500
|
+
showHints={showHints} isHintOpen={activeHintField === "name"}
|
|
501
|
+
onHintOpen={(name) => setActiveHintField(name)}
|
|
502
|
+
onHintClose={() => setActiveHintField(null)}
|
|
503
|
+
onToggleHints={() => setShowHints(prev => !prev)}
|
|
504
|
+
>
|
|
505
|
+
<Input {...register("name")} />
|
|
506
|
+
</FormField>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Dialog Form Pattern
|
|
510
|
+
|
|
511
|
+
Dialogs that contain forms follow a consistent structure:
|
|
512
|
+
|
|
513
|
+
**Confirmation dialogs** (archive, delete, restore):
|
|
514
|
+
- `AlertDialog` + `AlertDialogContent size="sm"`
|
|
515
|
+
- `AlertDialogHeader` with `AlertDialogMedia` (icon in circle) + title + description
|
|
516
|
+
- `AlertDialogFooter` with Cancel + destructive/confirm Button
|
|
517
|
+
- Async action with loading state on the confirm button
|
|
518
|
+
|
|
519
|
+
**Multi-step form dialogs** (create entity):
|
|
520
|
+
1. **Choice step**: Two-column grid of outline buttons choosing creation method (manual vs AI). `AlertDialogContent` at `max-w-lg`.
|
|
521
|
+
2. **Form step**: Animated width transition to `max-w-[38rem]`. Scrollable form body with sticky footer. Close button positioned `absolute top-0 right-0`.
|
|
522
|
+
3. On close: reset form, reset mutation state, then `setTimeout(() => setStep("choice"), 500)` for smooth animation.
|
|
523
|
+
|
|
524
|
+
**Single-step form dialogs**: Skip the choice step, go directly to the form. Same scrollable body + sticky footer pattern.
|
|
525
|
+
|
|
526
|
+
### List Page Composition
|
|
527
|
+
|
|
528
|
+
Every entity list page follows this structure:
|
|
529
|
+
|
|
530
|
+
```
|
|
531
|
+
PageHeader (title + description + "New X" button)
|
|
532
|
+
↓
|
|
533
|
+
StatusFilter (active/archived tabs, or full status set)
|
|
534
|
+
↓
|
|
535
|
+
SearchInput + sort Select (optional, for complex lists)
|
|
536
|
+
↓
|
|
537
|
+
Content area:
|
|
538
|
+
- Loading: skeleton grid/table with animate-pulse
|
|
539
|
+
- Empty: EmptyState component
|
|
540
|
+
- Data: grid of EntityCards or DataTable + Pagination
|
|
541
|
+
↓
|
|
542
|
+
Inline dialogs (create, edit, archive, restore)
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Skeleton loading pattern:**
|
|
546
|
+
```tsx
|
|
547
|
+
{isLoading && (
|
|
548
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
549
|
+
{[...Array(6)].map((_, i) => (
|
|
550
|
+
<Card key={i} className="animate-pulse">
|
|
551
|
+
<CardHeader><div className="h-5 bg-muted rounded w-3/4" /></CardHeader>
|
|
552
|
+
<CardContent><div className="h-4 bg-muted rounded w-1/2" /></CardContent>
|
|
553
|
+
</Card>
|
|
554
|
+
))}
|
|
555
|
+
</div>
|
|
556
|
+
)}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Sidebar App Shell
|
|
560
|
+
|
|
561
|
+
The recommended app shell layout:
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
SidebarProvider
|
|
565
|
+
├── Header (sticky top-0, contains OrgSwitcher + Separator + Breadcrumbs)
|
|
566
|
+
├── Sidebar collapsible="icon"
|
|
567
|
+
│ ├── SidebarContent → Navigation (SidebarMenu with SidebarMenuButton)
|
|
568
|
+
│ ├── SidebarFooter → UserMenu
|
|
569
|
+
│ └── SidebarRail (edge toggle)
|
|
570
|
+
└── SidebarInset → main content area
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
- Header height set via `--header-height` CSS variable
|
|
574
|
+
- Sidebar adapts to collapsed state — UserMenu shows only avatar, navigation shows only icons
|
|
575
|
+
- OrgSwitcher lives in the header, not the sidebar — stays visible when sidebar collapses
|
|
576
|
+
|
|
577
|
+
### Entity Management Pattern
|
|
578
|
+
|
|
579
|
+
For CRUD entity pages (audiences, brand voices, CTAs), the pattern is:
|
|
580
|
+
|
|
581
|
+
1. **List page**: `PageHeader` + `StatusFilter` + grid of entity `Card` components with action `DropdownMenu`
|
|
582
|
+
2. **Entity cards**: `Card` + `CardHeader` (name + action menu) + `CardContent` (badges/metadata) + `CardFooter` (timestamp)
|
|
583
|
+
3. **Action menus**: `DropdownMenu` triggered by 3-dot icon button. Edit + Archive (active) or Restore (archived) items. Destructive separator before archive/delete.
|
|
584
|
+
4. **Inline dialogs**: All create/edit/archive/restore dialogs render at the page level, controlled by state. On success, invalidate SWR cache.
|
|
585
|
+
5. **Entity selection in forms**: `EntitySelect` with render props for custom option/selected display. "Create new" action opens an inline dialog, auto-selects the created entity via `setValue()`.
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
294
589
|
## Maintenance Hooks
|
|
295
590
|
|
|
296
591
|
### Component & Style Hooks
|
|
297
592
|
|
|
298
593
|
| When you... | Then do... | Why |
|
|
299
594
|
|---|---|---|
|
|
300
|
-
| Add a shadcn component | Run `
|
|
595
|
+
| Add a shadcn component | Run `bunx shadcn@latest add <name>` from `packages/ui/` | Uses `base-maia` style to generate Base UI components in the correct location |
|
|
596
|
+
| Create a custom component | Add `data-slot="component-name"` to the root element | Maintains the CSS targeting convention across all components |
|
|
301
597
|
| Add a new app that uses UI | Add `@import "{{SCOPE}}/ui/styles"` and `@source` directive to the app's `globals.css` | New apps don't automatically see UI package classes |
|
|
302
598
|
| Add a new export directory | Add it to both `exports` and `imports` in `packages/ui/package.json` | Missing exports = import errors in consuming apps |
|
|
303
599
|
| Modify component variants | Update CVA definitions and ensure `cn()` is used for className composition | Inconsistent class merging = broken styles |
|
|
304
600
|
| Add an icon | Place in `icons/` or `icons/logos/` — export is automatic via wildcard | Follows the path-based export convention |
|
|
601
|
+
| Upgrade base components | Run `/scaffold-ui` in upgrade mode | Updates blueprint-owned components without touching user-owned ones |
|
|
305
602
|
|
|
306
|
-
### Condensed Rules for project
|
|
603
|
+
### Condensed Rules for project maintenance skill
|
|
307
604
|
|
|
308
605
|
```markdown
|
|
309
606
|
### ui-shared-components maintenance
|
|
310
|
-
- Add shadcn components via `
|
|
607
|
+
- Add shadcn components via `bunx shadcn@latest add <name>` from packages/ui/ (uses base-maia style)
|
|
311
608
|
- Use cn() for all className composition — never concatenate class strings manually
|
|
609
|
+
- Use data-slot attribute on every component root element and sub-components
|
|
610
|
+
- Import Phosphor icons from @phosphor-icons/react or @phosphor-icons/react/<IconName>
|
|
611
|
+
- Base UI primitives: import from @base-ui/react/<primitive-name>
|
|
312
612
|
- Internal imports use #prefix (#components/ui/button); cross-package imports use @scope/ui/components/ui/button
|
|
313
|
-
-
|
|
613
|
+
- Base components in components/ui/ are blueprint-owned — update via /scaffold-ui upgrade mode
|
|
614
|
+
- After adding new export directory: add to both exports and imports in packages/ui/package.json
|
|
314
615
|
- After adding a new consuming app: add @import and @source directive to the app's globals.css
|
|
315
616
|
```
|
|
316
617
|
|
|
@@ -318,18 +619,23 @@ Import in consuming apps: `import { FeatureCard } from "@scope/ui/components/fea
|
|
|
318
619
|
|
|
319
620
|
## What's Configurable
|
|
320
621
|
|
|
321
|
-
- shadcn style (`new-york`, `default`, or
|
|
322
|
-
- Icon library (
|
|
323
|
-
-
|
|
324
|
-
-
|
|
622
|
+
- **shadcn style** — `base-maia` (default), `new-york`, `default`, or any shadcn style
|
|
623
|
+
- **Icon library** — Phosphor (default), Lucide, Radix, or custom
|
|
624
|
+
- **Include base components** — Yes (default, all 31 components), No (skeleton only with empty `components/ui/`)
|
|
625
|
+
- **Include form system** — Yes (default), No (skip `form-field`, `field`, `tag-input`, `pill-select`)
|
|
626
|
+
- **Which consuming apps get globals.css update** — Ask during scaffolding
|
|
325
627
|
|
|
326
628
|
## What's Opinionated
|
|
327
629
|
|
|
328
|
-
- **
|
|
329
|
-
-
|
|
330
|
-
- **
|
|
331
|
-
-
|
|
332
|
-
- **
|
|
630
|
+
- **Base UI as headless layer** — Modern API, `data-*` state attributes, render props for polymorphism
|
|
631
|
+
- **`data-slot` attribute convention** — Stable CSS targeting that survives class changes
|
|
632
|
+
- **Compound component pattern** — Named parts over monolithic prop APIs
|
|
633
|
+
- **Phosphor Icons** — Six weights, tree-shakeable, consistent API
|
|
634
|
+
- **Path-based exports** — No barrel re-exports for components
|
|
635
|
+
- **No build step** — Exports TypeScript source, consuming apps transpile
|
|
636
|
+
- **`#` prefix subpath imports** — Node.js native, no path alias configuration
|
|
637
|
+
- **Flat component structure** — `components/ui/` for all components, no nested directories
|
|
638
|
+
- **CVA for all variant systems** — Consistent variant definition pattern across components
|
|
333
639
|
|
|
334
640
|
## Project Context Output
|
|
335
641
|
|
|
@@ -340,9 +646,11 @@ Appends to `.project-context.md` under `## Installed Blueprints`:
|
|
|
340
646
|
blueprint: ui-shared-components
|
|
341
647
|
installed_at: <date>
|
|
342
648
|
choices:
|
|
343
|
-
shadcn_style:
|
|
344
|
-
icon_library:
|
|
649
|
+
shadcn_style: base-maia
|
|
650
|
+
icon_library: phosphor
|
|
345
651
|
include_cva: true
|
|
652
|
+
include_base_components: true
|
|
653
|
+
include_form_system: true
|
|
346
654
|
files_created:
|
|
347
655
|
- packages/ui/package.json
|
|
348
656
|
- packages/ui/tsconfig.json
|
|
@@ -350,9 +658,33 @@ files_created:
|
|
|
350
658
|
- packages/ui/styles/globals.css
|
|
351
659
|
- packages/ui/utils/cn.ts
|
|
352
660
|
- packages/ui/utils/cva.ts
|
|
661
|
+
- packages/ui/components/ui/button.tsx
|
|
662
|
+
- packages/ui/components/ui/input.tsx
|
|
663
|
+
- packages/ui/components/ui/label.tsx
|
|
664
|
+
- packages/ui/components/ui/textarea.tsx
|
|
665
|
+
- packages/ui/components/ui/card.tsx
|
|
666
|
+
- packages/ui/components/ui/badge.tsx
|
|
667
|
+
- packages/ui/components/ui/separator.tsx
|
|
668
|
+
- packages/ui/components/ui/alert-dialog.tsx
|
|
669
|
+
- packages/ui/components/ui/select.tsx
|
|
670
|
+
- packages/ui/components/ui/combobox.tsx
|
|
671
|
+
- packages/ui/components/ui/dropdown-menu.tsx
|
|
672
|
+
- packages/ui/components/ui/breadcrumb.tsx
|
|
673
|
+
- packages/ui/components/ui/collapsible.tsx
|
|
674
|
+
- packages/ui/components/ui/popover.tsx
|
|
675
|
+
- packages/ui/components/ui/sidebar.tsx
|
|
676
|
+
- packages/ui/components/ui/field.tsx
|
|
677
|
+
- packages/ui/components/ui/form-field.tsx
|
|
678
|
+
- packages/ui/components/ui/input-group.tsx
|
|
679
|
+
- packages/ui/components/ui/tag-input.tsx
|
|
680
|
+
- packages/ui/components/ui/pill-select.tsx
|
|
353
681
|
globals_css_updated:
|
|
354
682
|
- apps/web/app/globals.css
|
|
355
683
|
packages_added:
|
|
684
|
+
- "@base-ui/react"
|
|
685
|
+
- "@phosphor-icons/react"
|
|
686
|
+
- "@radix-ui/react-collapsible"
|
|
687
|
+
- "@radix-ui/react-popover"
|
|
356
688
|
- "@radix-ui/react-slot"
|
|
357
689
|
- class-variance-authority
|
|
358
690
|
- clsx
|
|
@@ -365,9 +697,10 @@ packages_added:
|
|
|
365
697
|
|
|
366
698
|
## References
|
|
367
699
|
|
|
700
|
+
- **Base UI:** https://base-ui.com
|
|
701
|
+
- **Phosphor Icons:** https://phosphoricons.com
|
|
368
702
|
- **shadcn/ui Docs:** https://ui.shadcn.com/docs
|
|
369
703
|
- **shadcn/ui Monorepo Guide:** https://ui.shadcn.com/docs/monorepo
|
|
370
704
|
- **Tailwind CSS v4:** https://tailwindcss.com/docs
|
|
371
705
|
- **Class Variance Authority:** https://cva.style/docs
|
|
372
706
|
- **Node.js Subpath Imports:** https://nodejs.org/api/packages.html#subpath-imports
|
|
373
|
-
- **Radix UI Primitives:** https://www.radix-ui.com/primitives
|