@fr0mpy/component-system 2.2.0 → 3.1.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/README.md +86 -27
- package/bin/cli.js +155 -62
- package/bin/validate-compliance.js +447 -0
- package/bin/validate-config.js +175 -0
- package/index.js +15 -0
- package/package.json +6 -4
- package/templates/agents/component-auditor.md +77 -0
- package/templates/agents/design-token-validator.md +72 -0
- package/templates/agents/harness-scaffolder.md +146 -0
- package/templates/agents/playwright-tester.md +437 -0
- package/templates/agents/style-inspector.md +81 -0
- package/templates/commands/component-harness.md +104 -10
- package/templates/commands/retheme.md +142 -0
- package/templates/commands/save-theme.md +50 -0
- package/templates/commands/setup-styling.md +386 -57
- package/templates/component-recipes/accordion.md +9 -9
- package/templates/component-recipes/alert.md +36 -23
- package/templates/component-recipes/avatar.md +14 -12
- package/templates/component-recipes/badge.md +9 -6
- package/templates/component-recipes/breadcrumb.md +4 -4
- package/templates/component-recipes/button.md +2 -2
- package/templates/component-recipes/checkbox.md +5 -5
- package/templates/component-recipes/collapsible.md +13 -13
- package/templates/component-recipes/combobox.md +2 -2
- package/templates/component-recipes/context-menu.md +11 -11
- package/templates/component-recipes/dialog.md +16 -16
- package/templates/component-recipes/drawer.md +18 -18
- package/templates/component-recipes/dropdown-menu.md +12 -12
- package/templates/component-recipes/hover-card.md +11 -11
- package/templates/component-recipes/label.md +4 -4
- package/templates/component-recipes/modal.md +9 -9
- package/templates/component-recipes/navigation-menu.md +19 -19
- package/templates/component-recipes/popover.md +10 -10
- package/templates/component-recipes/progress.md +10 -8
- package/templates/component-recipes/radio.md +6 -6
- package/templates/component-recipes/select.md +5 -5
- package/templates/component-recipes/separator.md +2 -2
- package/templates/component-recipes/slider.md +2 -2
- package/templates/component-recipes/switch.md +6 -6
- package/templates/component-recipes/table.md +1 -1
- package/templates/component-recipes/tabs.md +6 -6
- package/templates/component-recipes/toast.md +56 -42
- package/templates/component-recipes/toggle-group.md +4 -4
- package/templates/component-recipes/tooltip.md +5 -5
- package/templates/mcp/mcp.json +8 -0
- package/templates/playwright/playwright.config.ts +31 -0
- package/templates/playwright/tests/components.spec.ts +104 -0
- package/templates/skills/react-patterns.md +4 -0
- package/templates/skills/styling.md +141 -52
- package/templates/hooks/triggers.d/react-patterns.json +0 -18
- package/templates/hooks/triggers.d/styling.json +0 -23
|
@@ -2,25 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Structure
|
|
4
4
|
- Container with icon, title, and description
|
|
5
|
-
- Support variants:
|
|
5
|
+
- Support variants: default, success, warning, destructive
|
|
6
6
|
- Optional dismiss button
|
|
7
7
|
- Optional action buttons
|
|
8
8
|
|
|
9
|
+
## IMPORTANT: Color Token Usage
|
|
10
|
+
|
|
11
|
+
**NEVER use hardcoded Tailwind colors.** Use semantic tokens only:
|
|
12
|
+
|
|
13
|
+
| Semantic Token | Purpose |
|
|
14
|
+
|----------------|---------|
|
|
15
|
+
| `bg-success`, `text-success`, `border-success` | Success states |
|
|
16
|
+
| `bg-warning`, `text-warning`, `border-warning` | Warning states |
|
|
17
|
+
| `bg-destructive`, `text-destructive`, `border-destructive` | Error/destructive states |
|
|
18
|
+
| `bg-accent`, `text-accent`, `border-accent` | Info/accent states |
|
|
19
|
+
| `bg-primary`, `text-primary`, `border-primary` | Primary actions |
|
|
20
|
+
| `bg-muted`, `text-muted-foreground` | Muted/secondary text |
|
|
21
|
+
|
|
9
22
|
## Tailwind Classes
|
|
10
23
|
|
|
11
24
|
### Container
|
|
12
25
|
```
|
|
13
|
-
relative w-full
|
|
26
|
+
relative w-full rounded-[USE_CONFIG_RADIUS] border p-4
|
|
14
27
|
[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:h-4 [&>svg]:w-4
|
|
15
28
|
[&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11
|
|
16
29
|
```
|
|
17
30
|
|
|
18
|
-
|
|
31
|
+
**Note:** Replace `rounded-[USE_CONFIG_RADIUS]` with the value from `.claude/styling-config.json` → `tokens.radius` (e.g., `rounded-none`, `rounded-md`, `rounded-lg`)
|
|
32
|
+
|
|
33
|
+
### Variants (Using Semantic Tokens)
|
|
19
34
|
```
|
|
20
35
|
default: bg-background border-border text-foreground [&>svg]:text-foreground
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
warning: bg-yellow-500/10 border-yellow-500/20 text-yellow-600 [&>svg]:text-yellow-600
|
|
36
|
+
success: bg-success/10 border-success/20 text-success [&>svg]:text-success
|
|
37
|
+
warning: bg-warning/10 border-warning/20 text-warning [&>svg]:text-warning
|
|
24
38
|
destructive: bg-destructive/10 border-destructive/20 text-destructive [&>svg]:text-destructive
|
|
25
39
|
```
|
|
26
40
|
|
|
@@ -36,14 +50,14 @@ text-sm [&_p]:leading-relaxed
|
|
|
36
50
|
|
|
37
51
|
### Dismiss Button
|
|
38
52
|
```
|
|
39
|
-
absolute right-2 top-2
|
|
40
|
-
hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-primary
|
|
53
|
+
absolute right-2 top-2 rounded-[USE_CONFIG_RADIUS] p-1 opacity-70 cursor-pointer
|
|
54
|
+
hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary
|
|
41
55
|
```
|
|
42
56
|
|
|
43
57
|
## Props Interface
|
|
44
58
|
```typescript
|
|
45
59
|
interface AlertProps {
|
|
46
|
-
variant?: 'default' | '
|
|
60
|
+
variant?: 'default' | 'success' | 'warning' | 'destructive'
|
|
47
61
|
icon?: React.ReactNode
|
|
48
62
|
title?: string
|
|
49
63
|
onDismiss?: () => void
|
|
@@ -63,7 +77,6 @@ interface AlertDescriptionProps {
|
|
|
63
77
|
|
|
64
78
|
## Icons per Variant
|
|
65
79
|
```
|
|
66
|
-
info: <Info /> or <AlertCircle />
|
|
67
80
|
success: <CheckCircle />
|
|
68
81
|
warning: <AlertTriangle />
|
|
69
82
|
destructive: <XCircle /> or <AlertOctagon />
|
|
@@ -71,33 +84,32 @@ default: <Info /> or custom
|
|
|
71
84
|
```
|
|
72
85
|
|
|
73
86
|
## Do
|
|
74
|
-
-
|
|
75
|
-
-
|
|
87
|
+
- Use semantic color tokens ONLY (bg-success, bg-warning, bg-destructive)
|
|
88
|
+
- Read `.claude/styling-config.json` for radius value
|
|
89
|
+
- Include `cursor-pointer` on dismiss button
|
|
90
|
+
- Use `focus-visible:` not `focus:` for keyboard accessibility
|
|
76
91
|
- Support composable pattern (Alert.Title, Alert.Description)
|
|
77
|
-
- Add dismiss functionality when needed
|
|
78
92
|
|
|
79
93
|
## Don't
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
94
|
+
- Use hardcoded colors (bg-green-500, bg-yellow-500, #XXXXXX)
|
|
95
|
+
- Use non-semantic Tailwind colors
|
|
96
|
+
- Use `focus:` instead of `focus-visible:`
|
|
97
|
+
- Forget `cursor-pointer` on interactive elements
|
|
84
98
|
|
|
85
99
|
## Example
|
|
86
100
|
```tsx
|
|
87
101
|
import { cn } from '@/lib/utils'
|
|
88
|
-
import {
|
|
102
|
+
import { CheckCircle, AlertTriangle, XCircle, X, Info } from 'lucide-react'
|
|
89
103
|
|
|
90
104
|
const alertVariants = {
|
|
91
105
|
default: 'bg-background border-border text-foreground',
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
warning: 'bg-yellow-500/10 border-yellow-500/20 text-yellow-600 [&>svg]:text-yellow-600',
|
|
106
|
+
success: 'bg-success/10 border-success/20 text-success [&>svg]:text-success',
|
|
107
|
+
warning: 'bg-warning/10 border-warning/20 text-warning [&>svg]:text-warning',
|
|
95
108
|
destructive: 'bg-destructive/10 border-destructive/20 text-destructive [&>svg]:text-destructive',
|
|
96
109
|
}
|
|
97
110
|
|
|
98
111
|
const alertIcons = {
|
|
99
112
|
default: Info,
|
|
100
|
-
info: AlertCircle,
|
|
101
113
|
success: CheckCircle,
|
|
102
114
|
warning: AlertTriangle,
|
|
103
115
|
destructive: XCircle,
|
|
@@ -110,6 +122,7 @@ const Alert = ({ variant = 'default', title, onDismiss, className, children, ...
|
|
|
110
122
|
<div
|
|
111
123
|
role="alert"
|
|
112
124
|
className={cn(
|
|
125
|
+
// Base styles - replace rounded-lg with your config's tokens.radius
|
|
113
126
|
'relative w-full rounded-lg border p-4',
|
|
114
127
|
'[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:h-4 [&>svg]:w-4',
|
|
115
128
|
'[&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11',
|
|
@@ -126,7 +139,7 @@ const Alert = ({ variant = 'default', title, onDismiss, className, children, ...
|
|
|
126
139
|
{onDismiss && (
|
|
127
140
|
<button
|
|
128
141
|
onClick={onDismiss}
|
|
129
|
-
className="absolute right-2 top-2 rounded p-1 opacity-70 hover:opacity-100"
|
|
142
|
+
className="absolute right-2 top-2 rounded p-1 opacity-70 cursor-pointer hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
|
130
143
|
>
|
|
131
144
|
<X className="h-4 w-4" />
|
|
132
145
|
</button>
|
|
@@ -35,13 +35,13 @@ flex h-full w-full items-center justify-center rounded-full
|
|
|
35
35
|
bg-muted text-muted-foreground font-medium
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
### Status Indicator
|
|
38
|
+
### Status Indicator (Using Semantic Tokens)
|
|
39
39
|
```
|
|
40
40
|
absolute bottom-0 right-0 h-3 w-3 rounded-full border-2 border-background
|
|
41
|
-
online: bg-
|
|
42
|
-
offline: bg-
|
|
43
|
-
busy: bg-
|
|
44
|
-
away: bg-
|
|
41
|
+
online: bg-success
|
|
42
|
+
offline: bg-muted
|
|
43
|
+
busy: bg-destructive
|
|
44
|
+
away: bg-warning
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
### Avatar Group
|
|
@@ -67,20 +67,21 @@ interface AvatarGroupProps {
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
## Do
|
|
70
|
-
- Use
|
|
70
|
+
- Use Base UI Avatar for image loading states
|
|
71
71
|
- Show fallback immediately while image loads
|
|
72
72
|
- Support both initials and icon fallbacks
|
|
73
73
|
- Use ring for avatar group overlap effect
|
|
74
74
|
|
|
75
75
|
## Don't
|
|
76
|
-
- Hardcode colors
|
|
76
|
+
- Hardcode colors (use bg-success NOT bg-green-500)
|
|
77
|
+
- Use non-semantic Tailwind colors
|
|
77
78
|
- Forget alt text for images
|
|
78
79
|
- Use non-square aspect ratios
|
|
79
80
|
- Skip loading state handling
|
|
80
81
|
|
|
81
82
|
## Example
|
|
82
83
|
```tsx
|
|
83
|
-
import
|
|
84
|
+
import { Avatar as AvatarPrimitive } from '@base-ui/react/avatar'
|
|
84
85
|
import { cn } from '@/lib/utils'
|
|
85
86
|
import { User } from 'lucide-react'
|
|
86
87
|
|
|
@@ -93,11 +94,12 @@ const avatarSizes = {
|
|
|
93
94
|
'2xl': 'h-24 w-24 text-xl',
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
// Using semantic tokens - NO hardcoded colors!
|
|
96
98
|
const statusColors = {
|
|
97
|
-
online: 'bg-
|
|
98
|
-
offline: 'bg-
|
|
99
|
-
busy: 'bg-
|
|
100
|
-
away: 'bg-
|
|
99
|
+
online: 'bg-success',
|
|
100
|
+
offline: 'bg-muted',
|
|
101
|
+
busy: 'bg-destructive',
|
|
102
|
+
away: 'bg-warning',
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
const Avatar = ({ src, alt, fallback, size = 'md', status, className }) => (
|
|
@@ -14,14 +14,14 @@ inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium
|
|
|
14
14
|
transition-colors
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
### Variants
|
|
17
|
+
### Variants (Using Semantic Tokens ONLY)
|
|
18
18
|
```
|
|
19
19
|
default: bg-primary text-primary-foreground
|
|
20
20
|
secondary: bg-secondary text-secondary-foreground
|
|
21
21
|
outline: border border-border text-foreground bg-transparent
|
|
22
22
|
destructive: bg-destructive text-destructive-foreground
|
|
23
|
-
success: bg-
|
|
24
|
-
warning: bg-
|
|
23
|
+
success: bg-success/10 text-success border border-success/20
|
|
24
|
+
warning: bg-warning/10 text-warning border border-warning/20
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### Sizes
|
|
@@ -68,21 +68,24 @@ interface BadgeProps {
|
|
|
68
68
|
## Don't
|
|
69
69
|
- Make badges too large
|
|
70
70
|
- Use for long text content
|
|
71
|
-
- Hardcode colors
|
|
71
|
+
- Hardcode colors (use bg-success NOT bg-green-500)
|
|
72
|
+
- Use non-semantic Tailwind colors
|
|
72
73
|
- Forget hover state for interactive badges
|
|
74
|
+
- Forget `cursor-pointer` on interactive badges
|
|
73
75
|
|
|
74
76
|
## Example
|
|
75
77
|
```tsx
|
|
76
78
|
import { cn } from '@/lib/utils'
|
|
77
79
|
import { X } from 'lucide-react'
|
|
78
80
|
|
|
81
|
+
// Using semantic tokens - NO hardcoded colors!
|
|
79
82
|
const badgeVariants = {
|
|
80
83
|
default: 'bg-primary text-primary-foreground',
|
|
81
84
|
secondary: 'bg-secondary text-secondary-foreground',
|
|
82
85
|
outline: 'border border-border text-foreground bg-transparent',
|
|
83
86
|
destructive: 'bg-destructive text-destructive-foreground',
|
|
84
|
-
success: 'bg-
|
|
85
|
-
warning: 'bg-
|
|
87
|
+
success: 'bg-success/10 text-success border border-success/20',
|
|
88
|
+
warning: 'bg-warning/10 text-warning border border-warning/20',
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
const badgeSizes = {
|
|
@@ -72,7 +72,7 @@ interface BreadcrumbListProps {
|
|
|
72
72
|
|
|
73
73
|
interface BreadcrumbLinkProps {
|
|
74
74
|
href: string
|
|
75
|
-
|
|
75
|
+
render?: React.ReactElement
|
|
76
76
|
children: React.ReactNode
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -110,7 +110,7 @@ interface BreadcrumbEllipsisProps {
|
|
|
110
110
|
## Example
|
|
111
111
|
```tsx
|
|
112
112
|
import { ChevronRight, MoreHorizontal } from 'lucide-react'
|
|
113
|
-
import { Slot } from '@
|
|
113
|
+
import { Slot } from '@base-ui/react/slot'
|
|
114
114
|
import { cn } from '@/lib/utils'
|
|
115
115
|
|
|
116
116
|
const Breadcrumb = ({ children, ...props }) => (
|
|
@@ -133,8 +133,8 @@ const BreadcrumbItem = ({ className, ...props }) => (
|
|
|
133
133
|
<li className={cn('inline-flex items-center gap-1.5', className)} {...props} />
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
-
const BreadcrumbLink = ({
|
|
137
|
-
const Comp =
|
|
136
|
+
const BreadcrumbLink = ({ render, className, ...props }) => {
|
|
137
|
+
const Comp = render ? Slot : 'a'
|
|
138
138
|
return (
|
|
139
139
|
<Comp
|
|
140
140
|
className={cn('transition-colors hover:text-foreground', className)}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- Support variants: primary, secondary, outline, ghost, destructive
|
|
6
6
|
- Support sizes: sm, md, lg
|
|
7
7
|
- Include states: loading, disabled
|
|
8
|
-
- Optionally support `
|
|
8
|
+
- Optionally support `render` for composition (render as link, etc.)
|
|
9
9
|
|
|
10
10
|
## Tailwind Classes
|
|
11
11
|
|
|
@@ -46,7 +46,7 @@ interface ButtonProps {
|
|
|
46
46
|
size?: 'sm' | 'md' | 'lg'
|
|
47
47
|
loading?: boolean
|
|
48
48
|
disabled?: boolean
|
|
49
|
-
|
|
49
|
+
render?: boolean
|
|
50
50
|
children: React.ReactNode
|
|
51
51
|
}
|
|
52
52
|
```
|
|
@@ -14,8 +14,8 @@ peer h-4 w-4 shrink-0 {tokens.radius} border border-border
|
|
|
14
14
|
ring-offset-background
|
|
15
15
|
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
|
|
16
16
|
disabled:cursor-not-allowed disabled:opacity-50
|
|
17
|
-
data-
|
|
18
|
-
data-
|
|
17
|
+
data-checked:bg-primary data-checked:border-primary data-checked:text-primary-foreground
|
|
18
|
+
data-indeterminate:bg-primary data-indeterminate:border-primary
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
### Check Icon
|
|
@@ -70,7 +70,7 @@ interface CheckboxWithLabelProps extends CheckboxProps {
|
|
|
70
70
|
|
|
71
71
|
## Example
|
|
72
72
|
```tsx
|
|
73
|
-
import
|
|
73
|
+
import { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox'
|
|
74
74
|
import { Check, Minus } from 'lucide-react'
|
|
75
75
|
import { cn } from '@/lib/utils'
|
|
76
76
|
|
|
@@ -81,8 +81,8 @@ const Checkbox = ({ className, ...props }) => (
|
|
|
81
81
|
'ring-offset-background',
|
|
82
82
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2',
|
|
83
83
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
84
|
-
'data-
|
|
85
|
-
'data-
|
|
84
|
+
'data-checked:bg-primary data-checked:border-primary data-checked:text-primary-foreground',
|
|
85
|
+
'data-indeterminate:bg-primary data-indeterminate:border-primary data-indeterminate:text-primary-foreground',
|
|
86
86
|
className
|
|
87
87
|
)}
|
|
88
88
|
{...props}
|
|
@@ -15,7 +15,7 @@ w-full
|
|
|
15
15
|
|
|
16
16
|
### Trigger
|
|
17
17
|
```
|
|
18
|
-
flex items-center justify-between w-full [&[data-
|
|
18
|
+
flex items-center justify-between w-full [&[data-open]>svg]:rotate-180
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
### Trigger with default styling
|
|
@@ -32,8 +32,8 @@ h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200
|
|
|
32
32
|
### Content
|
|
33
33
|
```
|
|
34
34
|
overflow-hidden
|
|
35
|
-
data-
|
|
36
|
-
data-
|
|
35
|
+
data-closed:animate-collapsible-up
|
|
36
|
+
data-open:animate-collapsible-down
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### Content Inner (for padding)
|
|
@@ -46,10 +46,10 @@ pt-2 pb-4
|
|
|
46
46
|
keyframes: {
|
|
47
47
|
'collapsible-down': {
|
|
48
48
|
from: { height: '0' },
|
|
49
|
-
to: { height: 'var(--
|
|
49
|
+
to: { height: 'var(--collapsible-panel-height)' },
|
|
50
50
|
},
|
|
51
51
|
'collapsible-up': {
|
|
52
|
-
from: { height: 'var(--
|
|
52
|
+
from: { height: 'var(--collapsible-panel-height)' },
|
|
53
53
|
to: { height: '0' },
|
|
54
54
|
},
|
|
55
55
|
},
|
|
@@ -70,7 +70,7 @@ interface CollapsibleProps {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
interface CollapsibleTriggerProps {
|
|
73
|
-
|
|
73
|
+
render?: React.ReactElement
|
|
74
74
|
children: React.ReactNode
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -89,7 +89,7 @@ interface CollapsibleContentProps {
|
|
|
89
89
|
- Code snippets with preview
|
|
90
90
|
|
|
91
91
|
## Do
|
|
92
|
-
- Use
|
|
92
|
+
- Use Base UI Collapsible for accessibility
|
|
93
93
|
- Animate height smoothly
|
|
94
94
|
- Include visual indicator of state (chevron)
|
|
95
95
|
- Support keyboard toggle (Enter/Space)
|
|
@@ -102,7 +102,7 @@ interface CollapsibleContentProps {
|
|
|
102
102
|
|
|
103
103
|
## Example
|
|
104
104
|
```tsx
|
|
105
|
-
import
|
|
105
|
+
import { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible'
|
|
106
106
|
import { ChevronDown } from 'lucide-react'
|
|
107
107
|
import { cn } from '@/lib/utils'
|
|
108
108
|
|
|
@@ -114,8 +114,8 @@ const CollapsibleContent = ({ className, children, ...props }) => (
|
|
|
114
114
|
<CollapsiblePrimitive.Content
|
|
115
115
|
className={cn(
|
|
116
116
|
'overflow-hidden',
|
|
117
|
-
'data-
|
|
118
|
-
'data-
|
|
117
|
+
'data-closed:animate-collapsible-up',
|
|
118
|
+
'data-open:animate-collapsible-down',
|
|
119
119
|
className
|
|
120
120
|
)}
|
|
121
121
|
{...props}
|
|
@@ -127,7 +127,7 @@ const CollapsibleContent = ({ className, children, ...props }) => (
|
|
|
127
127
|
// Simple usage
|
|
128
128
|
const SimpleCollapsible = ({ title, children }) => (
|
|
129
129
|
<Collapsible>
|
|
130
|
-
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 font-medium hover:underline [&[data-
|
|
130
|
+
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 font-medium hover:underline [&[data-open]>svg]:rotate-180">
|
|
131
131
|
{title}
|
|
132
132
|
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
|
133
133
|
</CollapsibleTrigger>
|
|
@@ -140,7 +140,7 @@ const SimpleCollapsible = ({ title, children }) => (
|
|
|
140
140
|
// With border styling
|
|
141
141
|
const CollapsibleCard = ({ title, defaultOpen = false, children }) => (
|
|
142
142
|
<Collapsible defaultOpen={defaultOpen} className="rounded-lg border border-border">
|
|
143
|
-
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium [&[data-
|
|
143
|
+
<CollapsibleTrigger className="flex items-center justify-between w-full p-4 font-medium [&[data-open]>svg]:rotate-180">
|
|
144
144
|
{title}
|
|
145
145
|
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
|
146
146
|
</CollapsibleTrigger>
|
|
@@ -155,7 +155,7 @@ const CollapsibleCard = ({ title, defaultOpen = false, children }) => (
|
|
|
155
155
|
// Sidebar navigation example
|
|
156
156
|
const SidebarGroup = ({ label, items, defaultOpen = true }) => (
|
|
157
157
|
<Collapsible defaultOpen={defaultOpen} className="space-y-2">
|
|
158
|
-
<CollapsibleTrigger className="flex items-center justify-between w-full px-2 py-1.5 text-sm font-semibold text-muted-foreground hover:text-foreground [&[data-
|
|
158
|
+
<CollapsibleTrigger className="flex items-center justify-between w-full px-2 py-1.5 text-sm font-semibold text-muted-foreground hover:text-foreground [&[data-open]>svg]:rotate-180">
|
|
159
159
|
{label}
|
|
160
160
|
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
|
161
161
|
</CollapsibleTrigger>
|
|
@@ -88,7 +88,7 @@ interface ComboboxProps {
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
## Do
|
|
91
|
-
- Use
|
|
91
|
+
- Use Base UI Combobox or cmdk for accessibility
|
|
92
92
|
- Support type-ahead filtering
|
|
93
93
|
- Clear search on selection (single) or keep (multi)
|
|
94
94
|
- Show loading state while filtering
|
|
@@ -123,7 +123,7 @@ const Combobox = ({
|
|
|
123
123
|
|
|
124
124
|
return (
|
|
125
125
|
<Popover open={open} onOpenChange={setOpen}>
|
|
126
|
-
<PopoverTrigger
|
|
126
|
+
<PopoverTrigger render={<Button variant="outline" role="combobox" aria-expanded={open} className="w-full justify-between" />}>
|
|
127
127
|
<Button
|
|
128
128
|
variant="outline"
|
|
129
129
|
role="combobox"
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
z-50 min-w-[8rem] overflow-hidden {tokens.radius} border border-border
|
|
20
20
|
bg-background p-1 text-foreground {tokens.shadow}
|
|
21
21
|
animate-in fade-in-80
|
|
22
|
-
data-
|
|
23
|
-
data-
|
|
24
|
-
data-
|
|
22
|
+
data-open:animate-in data-closed:animate-out
|
|
23
|
+
data-closed:fade-out-0 data-open:fade-in-0
|
|
24
|
+
data-closed:zoom-out-95 data-open:zoom-in-95
|
|
25
25
|
data-[side=bottom]:slide-in-from-top-2
|
|
26
26
|
data-[side=left]:slide-in-from-right-2
|
|
27
27
|
data-[side=right]:slide-in-from-left-2
|
|
@@ -82,14 +82,14 @@ ml-auto text-xs tracking-widest text-muted-foreground
|
|
|
82
82
|
flex cursor-pointer select-none items-center {tokens.radius} px-2 py-1.5
|
|
83
83
|
text-sm outline-none
|
|
84
84
|
focus:bg-muted
|
|
85
|
-
data-
|
|
85
|
+
data-open:bg-muted
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
### Sub Content
|
|
89
89
|
```
|
|
90
90
|
z-50 min-w-[8rem] overflow-hidden {tokens.radius} border border-border
|
|
91
91
|
bg-background p-1 {tokens.shadow}
|
|
92
|
-
data-
|
|
92
|
+
data-open:animate-in data-closed:animate-out
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
## Props Interface
|
|
@@ -102,7 +102,7 @@ interface ContextMenuProps {
|
|
|
102
102
|
interface ContextMenuTriggerProps {
|
|
103
103
|
children: React.ReactNode
|
|
104
104
|
disabled?: boolean
|
|
105
|
-
|
|
105
|
+
render?: React.ReactElement
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
interface ContextMenuContentProps {
|
|
@@ -122,7 +122,7 @@ interface ContextMenuItemProps {
|
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
## Do
|
|
125
|
-
- Use
|
|
125
|
+
- Use Base UI Menu for accessibility
|
|
126
126
|
- Support all item types (checkbox, radio, sub-menus)
|
|
127
127
|
- Show keyboard shortcuts
|
|
128
128
|
- Handle touch devices (long-press)
|
|
@@ -135,7 +135,7 @@ interface ContextMenuItemProps {
|
|
|
135
135
|
|
|
136
136
|
## Example
|
|
137
137
|
```tsx
|
|
138
|
-
import
|
|
138
|
+
import { Menu as ContextMenuPrimitive } from '@base-ui/react/menu'
|
|
139
139
|
import { Check, ChevronRight, Circle } from 'lucide-react'
|
|
140
140
|
import { cn } from '@/lib/utils'
|
|
141
141
|
|
|
@@ -152,9 +152,9 @@ const ContextMenuContent = ({ className, ...props }) => (
|
|
|
152
152
|
className={cn(
|
|
153
153
|
'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background p-1 shadow-md',
|
|
154
154
|
'animate-in fade-in-80',
|
|
155
|
-
'data-
|
|
156
|
-
'data-
|
|
157
|
-
'data-
|
|
155
|
+
'data-open:animate-in data-closed:animate-out',
|
|
156
|
+
'data-closed:fade-out-0 data-open:fade-in-0',
|
|
157
|
+
'data-closed:zoom-out-95 data-open:zoom-in-95',
|
|
158
158
|
'data-[side=bottom]:slide-in-from-top-2',
|
|
159
159
|
className
|
|
160
160
|
)}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
### Overlay
|
|
12
12
|
```
|
|
13
13
|
fixed inset-0 z-50 bg-black/80
|
|
14
|
-
data-
|
|
15
|
-
data-
|
|
14
|
+
data-open:animate-in data-closed:animate-out
|
|
15
|
+
data-closed:fade-out-0 data-open:fade-in-0
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
### Content
|
|
@@ -20,11 +20,11 @@ data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
|
|
|
20
20
|
fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2
|
|
21
21
|
gap-4 border border-border bg-background p-6 {tokens.shadow} {tokens.radius}
|
|
22
22
|
duration-200
|
|
23
|
-
data-
|
|
24
|
-
data-
|
|
25
|
-
data-
|
|
26
|
-
data-
|
|
27
|
-
data-
|
|
23
|
+
data-open:animate-in data-closed:animate-out
|
|
24
|
+
data-closed:fade-out-0 data-open:fade-in-0
|
|
25
|
+
data-closed:zoom-out-95 data-open:zoom-in-95
|
|
26
|
+
data-closed:slide-out-to-left-1/2 data-closed:slide-out-to-top-[48%]
|
|
27
|
+
data-open:slide-in-from-left-1/2 data-open:slide-in-from-top-[48%]
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
### Header
|
|
@@ -53,7 +53,7 @@ absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background
|
|
|
53
53
|
transition-opacity hover:opacity-100
|
|
54
54
|
focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2
|
|
55
55
|
disabled:pointer-events-none
|
|
56
|
-
data-
|
|
56
|
+
data-open:bg-accent data-open:text-muted-foreground
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
## Alert Dialog Variant (for destructive confirmations)
|
|
@@ -112,7 +112,7 @@ interface DialogDescriptionProps {
|
|
|
112
112
|
|
|
113
113
|
## Example
|
|
114
114
|
```tsx
|
|
115
|
-
import
|
|
115
|
+
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
|
|
116
116
|
import { X } from 'lucide-react'
|
|
117
117
|
import { cn } from '@/lib/utils'
|
|
118
118
|
|
|
@@ -125,8 +125,8 @@ const DialogOverlay = ({ className, ...props }) => (
|
|
|
125
125
|
<DialogPrimitive.Overlay
|
|
126
126
|
className={cn(
|
|
127
127
|
'fixed inset-0 z-50 bg-black/80',
|
|
128
|
-
'data-
|
|
129
|
-
'data-
|
|
128
|
+
'data-open:animate-in data-closed:animate-out',
|
|
129
|
+
'data-closed:fade-out-0 data-open:fade-in-0',
|
|
130
130
|
className
|
|
131
131
|
)}
|
|
132
132
|
{...props}
|
|
@@ -140,9 +140,9 @@ const DialogContent = ({ className, children, ...props }) => (
|
|
|
140
140
|
className={cn(
|
|
141
141
|
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2',
|
|
142
142
|
'gap-4 border border-border bg-background p-6 shadow-lg rounded-lg duration-200',
|
|
143
|
-
'data-
|
|
144
|
-
'data-
|
|
145
|
-
'data-
|
|
143
|
+
'data-open:animate-in data-closed:animate-out',
|
|
144
|
+
'data-closed:fade-out-0 data-open:fade-in-0',
|
|
145
|
+
'data-closed:zoom-out-95 data-open:zoom-in-95',
|
|
146
146
|
className
|
|
147
147
|
)}
|
|
148
148
|
{...props}
|
|
@@ -174,8 +174,8 @@ const DialogDescription = ({ className, ...props }) => (
|
|
|
174
174
|
|
|
175
175
|
// Usage
|
|
176
176
|
<Dialog>
|
|
177
|
-
<DialogTrigger
|
|
178
|
-
|
|
177
|
+
<DialogTrigger render={<Button variant="outline" />}>
|
|
178
|
+
Edit Profile
|
|
179
179
|
</DialogTrigger>
|
|
180
180
|
<DialogContent>
|
|
181
181
|
<DialogHeader>
|