@devalok/shilp-sutra 0.40.1 → 0.42.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/AGENTS.md +23 -1
- package/BREAKING.json +66 -0
- package/BREAKING.schema.json +184 -0
- package/MIGRATION.md +16 -0
- package/docs/recipes/install-next-app-router.md +54 -14
- package/docs/recipes/upgrading.md +19 -0
- package/llms-full.txt +1 -1
- package/llms-quick.txt +3 -1
- package/llms.txt +1 -0
- package/make-kit/Guidelines.md +71 -0
- package/make-kit/components/badge.md +162 -0
- package/make-kit/components/button.md +125 -0
- package/make-kit/components/card.md +147 -0
- package/make-kit/components/dialog.md +167 -0
- package/make-kit/components/dropdown-menu.md +205 -0
- package/make-kit/components/form.md +189 -0
- package/make-kit/components/icon.md +152 -0
- package/make-kit/components/input.md +154 -0
- package/make-kit/components/overview.md +308 -0
- package/make-kit/components/popover.md +201 -0
- package/make-kit/components/select.md +148 -0
- package/make-kit/components/stack.md +165 -0
- package/make-kit/components/table.md +215 -0
- package/make-kit/components/tabs.md +162 -0
- package/make-kit/components/text.md +139 -0
- package/make-kit/components/toast.md +193 -0
- package/make-kit/foundations/color.md +128 -0
- package/make-kit/foundations/dark-mode.md +81 -0
- package/make-kit/foundations/icons.md +107 -0
- package/make-kit/foundations/motion.md +134 -0
- package/make-kit/foundations/radius.md +78 -0
- package/make-kit/foundations/spacing.md +110 -0
- package/make-kit/foundations/surfaces.md +121 -0
- package/make-kit/foundations/typography.md +120 -0
- package/make-kit/setup.md +130 -0
- package/package.json +9 -2
- package/skill/SKILL.md +3 -3
- package/skill/references/components-full.md +1 -1
- package/skill/references/components.md +1 -0
- package/skill/references/setup-next-app-router.md +54 -14
- package/skill/references/upgrading.md +19 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Input
|
|
2
|
+
|
|
3
|
+
Single-line text entry. Use instead of `<input type="text" | "email" | "url" | "tel" | "search" | "password">`.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Input } from '@devalok/shilp-sutra/ui/input'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- Any single-line text capture: email, URL, search, name, password, phone.
|
|
12
|
+
- Multi-line input? Use `<Textarea>`.
|
|
13
|
+
- Number-only with steppers? Use `<NumberInput>`.
|
|
14
|
+
- One-time-code / PIN entry? Use `<InputOTP>`.
|
|
15
|
+
- Searchable list with selection? Use `<Combobox>` or `<Autocomplete>`.
|
|
16
|
+
|
|
17
|
+
Wrap in a `<FormField>` for automatic a11y wiring (state, `aria-describedby`, `aria-invalid`, `aria-required`).
|
|
18
|
+
|
|
19
|
+
## Sizes
|
|
20
|
+
|
|
21
|
+
| Size | Pixel height | When |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `xs` | 28 | Toolbar / inline filters. |
|
|
24
|
+
| `sm` | 32 | Dense forms. |
|
|
25
|
+
| `md` (default) | 40 | Standard. |
|
|
26
|
+
| `lg` | 48 | Marketing / spacious layouts. |
|
|
27
|
+
|
|
28
|
+
All sizes use 14 px text — sizes scale height + padding, not font.
|
|
29
|
+
|
|
30
|
+
## States
|
|
31
|
+
|
|
32
|
+
| State | Visual | Use |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `default` (default) | Subtle border. | Resting. |
|
|
35
|
+
| `error` | Red border + `aria-invalid="true"`. | Validation failure. |
|
|
36
|
+
| `warning` | Amber border. | Soft caution (caps lock on, weak password). |
|
|
37
|
+
| `success` | Green border. | Inline confirmation. |
|
|
38
|
+
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
| Prop | Type | Notes |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `size` | `'xs'\|'sm'\|'md'\|'lg'` | Default `md`. Excludes the HTML native `size` attribute. |
|
|
44
|
+
| `state` | `'default'\|'error'\|'warning'\|'success'` | Inherits from `<FormField>` context if present. |
|
|
45
|
+
| `startSection` | `ReactNode` | Leading slot — icon (React element) or label string. |
|
|
46
|
+
| `endSection` | `ReactNode` | Trailing slot — icon or label. |
|
|
47
|
+
| `startSectionType` | `'icon'\|'label'` | Auto-inferred from content type. Override only when needed. |
|
|
48
|
+
| `endSectionType` | `'icon'\|'label'` | Auto-inferred. |
|
|
49
|
+
| `startSectionClickable` | `boolean` | Enables pointer events on the section (e.g. clear button in slot). |
|
|
50
|
+
| `endSectionClickable` | `boolean` | Same for trailing slot. |
|
|
51
|
+
| `wrapperClassName` | `string` | Classes on the wrapper div (border, bg, ring live here). |
|
|
52
|
+
| `className` | `string` | Classes on the raw `<input>` element (transparent — text only). |
|
|
53
|
+
|
|
54
|
+
Plus all standard HTML input attributes except the native `size` (renamed to mean visual sizing).
|
|
55
|
+
|
|
56
|
+
## Section type inference
|
|
57
|
+
|
|
58
|
+
| Content | Renders as |
|
|
59
|
+
|---|---|
|
|
60
|
+
| React element (`<Icon icon={IconMail} />`) | `'icon'` — fixed-width centered cell. |
|
|
61
|
+
| String (`"https://"`, `".00"`) | `'label'` — tinted bg + border separator. |
|
|
62
|
+
|
|
63
|
+
Override via `startSectionType` / `endSectionType` when the inference is wrong.
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
**Standard, with leading icon:**
|
|
68
|
+
```tsx
|
|
69
|
+
<Input
|
|
70
|
+
type="email"
|
|
71
|
+
placeholder="you@example.com"
|
|
72
|
+
startSection={<Icon icon={IconMail} />}
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**URL prefix (label section):**
|
|
77
|
+
```tsx
|
|
78
|
+
<Input
|
|
79
|
+
startSection="https://"
|
|
80
|
+
placeholder="example.com"
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Currency with prefix icon + suffix label:**
|
|
85
|
+
```tsx
|
|
86
|
+
<Input
|
|
87
|
+
startSection={<Icon icon={IconCurrencyDollar} />}
|
|
88
|
+
endSection=".00"
|
|
89
|
+
placeholder="0"
|
|
90
|
+
inputMode="decimal"
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Clearable search (clickable trailing slot):**
|
|
95
|
+
```tsx
|
|
96
|
+
<Input
|
|
97
|
+
placeholder="Search projects"
|
|
98
|
+
startSection={<Icon icon={IconSearch} />}
|
|
99
|
+
endSection={
|
|
100
|
+
<button type="button" onClick={() => setValue('')} aria-label="Clear">
|
|
101
|
+
<Icon icon={IconX} />
|
|
102
|
+
</button>
|
|
103
|
+
}
|
|
104
|
+
endSectionClickable
|
|
105
|
+
value={value}
|
|
106
|
+
onChange={(e) => setValue(e.target.value)}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Inside a FormField (auto-wired a11y):**
|
|
111
|
+
```tsx
|
|
112
|
+
<FormField state="error">
|
|
113
|
+
<Label htmlFor="email" required>Email</Label>
|
|
114
|
+
<Input id="email" type="email" />
|
|
115
|
+
<FormHelperText>Enter a valid work email.</FormHelperText>
|
|
116
|
+
</FormField>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The Input picks up `state="error"`, `aria-describedby` (linked to FormHelperText), `aria-invalid`, and `aria-required` from FormField context automatically.
|
|
120
|
+
|
|
121
|
+
**Password with reveal toggle:**
|
|
122
|
+
```tsx
|
|
123
|
+
const [visible, setVisible] = useState(false)
|
|
124
|
+
|
|
125
|
+
<Input
|
|
126
|
+
type={visible ? 'text' : 'password'}
|
|
127
|
+
placeholder="Password"
|
|
128
|
+
endSection={
|
|
129
|
+
<button type="button" onClick={() => setVisible((v) => !v)} aria-label={visible ? 'Hide password' : 'Show password'}>
|
|
130
|
+
<Icon icon={visible ? IconEyeOff : IconEye} />
|
|
131
|
+
</button>
|
|
132
|
+
}
|
|
133
|
+
endSectionClickable
|
|
134
|
+
/>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Composability
|
|
138
|
+
|
|
139
|
+
- **FormField:** Inside a `<FormField>`, Input auto-reads `state`, `aria-describedby`, `aria-invalid`, `aria-required` from context. Explicit props override.
|
|
140
|
+
- **IconProvider:** Icons in `startSection` / `endSection` are auto-sized via the input's `size`. Don't pass `size` on the nested `<Icon>`.
|
|
141
|
+
- **Container-first architecture:** Border, background, and focus ring live on the wrapper div, not the input element. Style overrides go through `wrapperClassName`, not `className`. The raw `<input>` is transparent.
|
|
142
|
+
|
|
143
|
+
See `foundations/color.md` for state-color tokens, `foundations/icons.md` for the IconProvider cascade.
|
|
144
|
+
|
|
145
|
+
## Rules
|
|
146
|
+
|
|
147
|
+
- Pair every Input with a `<Label htmlFor="x" />` and matching `<Input id="x" />`. FormField does NOT auto-wire labels.
|
|
148
|
+
- Use `wrapperClassName` for border / bg / ring overrides. `className` only changes the raw input text styling.
|
|
149
|
+
- Don't pass `size` on `<Icon>` inside a section — IconProvider sets it.
|
|
150
|
+
- Don't use the HTML native `size` attribute — it's excluded. Use CSS width or the size prop.
|
|
151
|
+
- For interactive sections (clear button, password toggle), set `startSectionClickable` / `endSectionClickable`. Without it, the slot is `pointer-events-none`.
|
|
152
|
+
- For status / validation, set `state` — it sets `aria-invalid` automatically when `state="error"`.
|
|
153
|
+
- Don't use the removed `startIcon` / `endIcon` props — they were removed in 0.38. Use `startSection` / `endSection`.
|
|
154
|
+
- Prefer FormField wrapping for any input with a label + helper — manual a11y wiring is error-prone.
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Components — catalog & decision trees
|
|
2
|
+
|
|
3
|
+
Catalog of every major component. Use this file to pick the right component before reaching for raw HTML. Per-component deep guides live alongside this file.
|
|
4
|
+
|
|
5
|
+
## Decision tree — actions
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
User wants to commit / cancel / submit?
|
|
9
|
+
→ primary CTA (1 per region) → <Button variant="solid" color="accent">
|
|
10
|
+
→ secondary action → <Button variant="soft"> (NOT outline — see preference)
|
|
11
|
+
→ tertiary / dismissive → <Button variant="ghost">
|
|
12
|
+
→ destructive action → <Button variant="solid" color="error">
|
|
13
|
+
→ link-styled action → <Button variant="link">
|
|
14
|
+
|
|
15
|
+
Action has a side-menu / overflow?
|
|
16
|
+
→ <SplitButton> — primary + adjacent dropdown trigger.
|
|
17
|
+
|
|
18
|
+
Action is icon-only?
|
|
19
|
+
→ <IconButton> — same as Button, square aspect, aria-label required.
|
|
20
|
+
|
|
21
|
+
Group of related actions?
|
|
22
|
+
→ <ButtonGroup> — attached or not. Propagates disabled / size.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Decision tree — input
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Single-line text?
|
|
29
|
+
→ <Input> — type="text"/"email"/"password"/"number"/"tel"/"url"/"search"
|
|
30
|
+
→ For search specifically: <SearchInput> (has clear button + icon)
|
|
31
|
+
→ For OTP code: <InputOTP>
|
|
32
|
+
|
|
33
|
+
Multi-line text?
|
|
34
|
+
→ <Textarea>
|
|
35
|
+
|
|
36
|
+
Pick one from a list?
|
|
37
|
+
→ static list ≤7 items → <Select>
|
|
38
|
+
→ static list >7 items → <Combobox> (searchable)
|
|
39
|
+
→ async / large list → <Autocomplete>
|
|
40
|
+
→ enum of 2–4 options → <SegmentedControl> (always-visible) or <Tabs> (if scoping a section)
|
|
41
|
+
|
|
42
|
+
Pick many?
|
|
43
|
+
→ list with checkboxes → <Checkbox> per item
|
|
44
|
+
→ compact pills → <ToggleGroup type="multiple">
|
|
45
|
+
→ searchable → <Combobox multiple>
|
|
46
|
+
|
|
47
|
+
Boolean?
|
|
48
|
+
→ <Switch> (settings-style on/off) — instant action
|
|
49
|
+
→ <Checkbox> (form field) — confirmed-on-submit
|
|
50
|
+
→ <Toggle> (button-like toggled state)
|
|
51
|
+
|
|
52
|
+
Range / scalar?
|
|
53
|
+
→ <Slider> (single or multi-thumb range)
|
|
54
|
+
→ <NumberInput> (precise number with steppers)
|
|
55
|
+
|
|
56
|
+
Date / time?
|
|
57
|
+
→ <DatePicker> (single date)
|
|
58
|
+
→ <DateRangePicker> (range)
|
|
59
|
+
→ <DateTimePicker> (date + time)
|
|
60
|
+
|
|
61
|
+
Color?
|
|
62
|
+
→ <ColorInput> (popover swatch + manual hex)
|
|
63
|
+
→ <ColorSwatch> (display-only)
|
|
64
|
+
|
|
65
|
+
File?
|
|
66
|
+
→ <FileUpload> (drag-drop + click)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Decision tree — feedback
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Permanent inline message?
|
|
73
|
+
→ <Alert> — page-level notice (info / success / warning / error)
|
|
74
|
+
→ <Banner> — full-bleed page banner (system status, announcements)
|
|
75
|
+
→ <InfoBlock> — within a form / card (helper context)
|
|
76
|
+
|
|
77
|
+
Status of an item?
|
|
78
|
+
→ <Badge> — pill label (status / tag)
|
|
79
|
+
→ <StatusBadge> — colored dot + label (discriminated union: status="online"/"away"/...)
|
|
80
|
+
→ <StatusDot> — just the colored dot
|
|
81
|
+
|
|
82
|
+
Temporary notification?
|
|
83
|
+
→ toast.success / error / warning / info (imperative) — must have <Toaster /> mounted
|
|
84
|
+
|
|
85
|
+
Loading / pending?
|
|
86
|
+
→ <Spinner> — generic
|
|
87
|
+
→ <ProgressRing> — determinate ring
|
|
88
|
+
→ <Progress> — linear bar
|
|
89
|
+
→ <Skeleton> — content placeholder shimmer
|
|
90
|
+
→ <PageSkeletons.*> — pre-built page-level skeletons
|
|
91
|
+
→ <GlobalLoading> — app-wide loading overlay
|
|
92
|
+
|
|
93
|
+
Empty state?
|
|
94
|
+
→ <EmptyState> — icon + heading + body + action
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Decision tree — overlays
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Confirm a destructive action? → <AlertDialog> or <ConfirmDialog>
|
|
101
|
+
Show a form / detailed flow? → <Dialog> (auto fullScreen on mobile)
|
|
102
|
+
Show a side panel? → <Sheet> (auto bottom-drawer on mobile)
|
|
103
|
+
Show a small floating popup on click? → <Popover>
|
|
104
|
+
Show a small floating popup on hover? → <HoverCard>
|
|
105
|
+
Show a label on hover? → <Tooltip>
|
|
106
|
+
Show a menu from a button? → <DropdownMenu>
|
|
107
|
+
Show a menu from right-click? → <ContextMenu>
|
|
108
|
+
Show a menu in a toolbar? → <Menubar>
|
|
109
|
+
Show a command palette (cmd+K)? → <AppCommandPalette> (shell) or <CommandPalette> (composed)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Decision tree — navigation
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
Top of every page?
|
|
116
|
+
→ <TopBar> (shell)
|
|
117
|
+
|
|
118
|
+
Side nav (product)?
|
|
119
|
+
→ <AppSidebar> (shell) — collapsible, with sections, footer
|
|
120
|
+
|
|
121
|
+
Tabs within a page?
|
|
122
|
+
→ <Tabs> — horizontal default
|
|
123
|
+
→ <Tabs orientation="vertical"> — side-nav within a card
|
|
124
|
+
|
|
125
|
+
Pages within a flow?
|
|
126
|
+
→ <Stepper> — multi-step wizard, optional clickable
|
|
127
|
+
|
|
128
|
+
Breadcrumb trail?
|
|
129
|
+
→ <Breadcrumb>
|
|
130
|
+
|
|
131
|
+
Pagination?
|
|
132
|
+
→ <Pagination>
|
|
133
|
+
|
|
134
|
+
Mobile bottom nav?
|
|
135
|
+
→ <BottomNavbar>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Decision tree — layout
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
Page wrapper?
|
|
142
|
+
→ <Container size="..."> — max-width constraints (sm / md / lg / xl / 2xl / full)
|
|
143
|
+
|
|
144
|
+
Vertical / horizontal stack?
|
|
145
|
+
→ <Stack direction="col|row" gap="ds-*">
|
|
146
|
+
|
|
147
|
+
A card / panel?
|
|
148
|
+
→ <Card> — surface-raised + shadow-raised
|
|
149
|
+
→ <Card variant="..."> — tinted by color prop
|
|
150
|
+
|
|
151
|
+
A grid?
|
|
152
|
+
→ use native CSS grid utilities (grid grid-cols-12 gap-ds-05) — no kit primitive for this
|
|
153
|
+
|
|
154
|
+
Aspect-ratio box?
|
|
155
|
+
→ <AspectRatio ratio={16/9}>
|
|
156
|
+
|
|
157
|
+
Divider?
|
|
158
|
+
→ <Separator> (horizontal default; orientation="vertical")
|
|
159
|
+
|
|
160
|
+
Collapsible section?
|
|
161
|
+
→ <Collapsible> (single)
|
|
162
|
+
→ <Accordion> (group, single-open or multi-open)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Decision tree — data display
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
Tabular data?
|
|
169
|
+
→ <Table> — simple, hand-roll rows/cells
|
|
170
|
+
→ <DataTable> — full-featured (sort, paginate, select, filter, export, density, mobile-card)
|
|
171
|
+
|
|
172
|
+
Key-value display?
|
|
173
|
+
→ <StatCard> — single big stat
|
|
174
|
+
→ 4 StatCards in a row → use <Stack direction="row" gap="ds-05">
|
|
175
|
+
|
|
176
|
+
Activity / event log?
|
|
177
|
+
→ <ActivityFeed> — vertical timeline with dots
|
|
178
|
+
|
|
179
|
+
User avatars?
|
|
180
|
+
→ <Avatar> — single
|
|
181
|
+
→ <AvatarGroup> — stack with overflow
|
|
182
|
+
|
|
183
|
+
A list of selectable members?
|
|
184
|
+
→ <MemberPicker>
|
|
185
|
+
|
|
186
|
+
A chart?
|
|
187
|
+
→ BarChart / LineChart / AreaChart / PieChart / RadarChart / GaugeChart / Sparkline (from /ui/charts)
|
|
188
|
+
|
|
189
|
+
Code?
|
|
190
|
+
→ <Code> inline
|
|
191
|
+
→ <MarkdownViewer> for rendered markdown
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Component catalog by category
|
|
195
|
+
|
|
196
|
+
### Actions
|
|
197
|
+
| Component | Subpath | Purpose |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| Button | `/ui/button` | Primary action element. |
|
|
200
|
+
| IconButton | `/ui/icon-button` | Icon-only square button. |
|
|
201
|
+
| ButtonGroup | `/ui/button-group` | Visually attached group. |
|
|
202
|
+
| SplitButton | `/ui/split-button` | Action + dropdown. |
|
|
203
|
+
| OAuthButton | `/ui/oauth-button` | Brand-aware social login button. |
|
|
204
|
+
| Toggle | `/ui/toggle` | Toggled-state button. |
|
|
205
|
+
| ToggleGroup | `/ui/toggle-group` | Group of toggles (single / multiple). |
|
|
206
|
+
| Link | `/ui/link` | Themed anchor. |
|
|
207
|
+
|
|
208
|
+
### Inputs
|
|
209
|
+
| Component | Subpath |
|
|
210
|
+
|---|---|
|
|
211
|
+
| Input | `/ui/input` |
|
|
212
|
+
| Textarea | `/ui/textarea` |
|
|
213
|
+
| Select | `/ui/select` |
|
|
214
|
+
| Combobox | `/ui/combobox` |
|
|
215
|
+
| Autocomplete | `/ui/autocomplete` |
|
|
216
|
+
| SearchInput | `/ui/search-input` |
|
|
217
|
+
| NumberInput | `/ui/number-input` |
|
|
218
|
+
| InputOTP | `/ui/input-otp` |
|
|
219
|
+
| Checkbox | `/ui/checkbox` |
|
|
220
|
+
| Radio | `/ui/radio` |
|
|
221
|
+
| Switch | `/ui/switch` |
|
|
222
|
+
| Slider | `/ui/slider` |
|
|
223
|
+
| FileUpload | `/ui/file-upload` |
|
|
224
|
+
| ColorInput | `/ui/color-input` |
|
|
225
|
+
| ColorSwatch | `/ui/color-swatch` |
|
|
226
|
+
| DatePicker | `/composed/date-picker` |
|
|
227
|
+
| Label | `/ui/label` |
|
|
228
|
+
| Form / FormField | `/ui/form` |
|
|
229
|
+
| SegmentedControl | `/ui/segmented-control` |
|
|
230
|
+
|
|
231
|
+
### Overlays
|
|
232
|
+
| Component | Subpath |
|
|
233
|
+
|---|---|
|
|
234
|
+
| Dialog | `/ui/dialog` |
|
|
235
|
+
| AlertDialog | `/ui/alert-dialog` |
|
|
236
|
+
| Sheet | `/ui/sheet` |
|
|
237
|
+
| Popover | `/ui/popover` |
|
|
238
|
+
| HoverCard | `/ui/hover-card` |
|
|
239
|
+
| Tooltip | `/ui/tooltip` |
|
|
240
|
+
| DropdownMenu | `/ui/dropdown-menu` |
|
|
241
|
+
| ContextMenu | `/ui/context-menu` |
|
|
242
|
+
| Menubar | `/ui/menubar` |
|
|
243
|
+
|
|
244
|
+
### Feedback
|
|
245
|
+
| Component | Subpath |
|
|
246
|
+
|---|---|
|
|
247
|
+
| Alert | `/ui/alert` |
|
|
248
|
+
| Banner | `/ui/banner` |
|
|
249
|
+
| Toast / Toaster | `/ui/toast`, `/ui/toaster` |
|
|
250
|
+
| Spinner | `/ui/spinner` |
|
|
251
|
+
| Progress | `/ui/progress` |
|
|
252
|
+
| ProgressRing | `/ui/progress-ring` |
|
|
253
|
+
| Skeleton | `/ui/skeleton` |
|
|
254
|
+
| Badge | `/ui/badge` |
|
|
255
|
+
| StatusBadge | `/composed/status-badge` |
|
|
256
|
+
| StatusDot | `/ui/status-dot` |
|
|
257
|
+
| EmptyState | `/composed/empty-state` |
|
|
258
|
+
|
|
259
|
+
### Layout
|
|
260
|
+
| Component | Subpath |
|
|
261
|
+
|---|---|
|
|
262
|
+
| Container | `/ui/container` |
|
|
263
|
+
| Stack | `/ui/stack` |
|
|
264
|
+
| Card | `/ui/card` |
|
|
265
|
+
| AspectRatio | `/ui/aspect-ratio` |
|
|
266
|
+
| Separator | `/ui/separator` |
|
|
267
|
+
| Accordion | `/ui/accordion` |
|
|
268
|
+
| Collapsible | `/ui/collapsible` |
|
|
269
|
+
|
|
270
|
+
### Navigation
|
|
271
|
+
| Component | Subpath |
|
|
272
|
+
|---|---|
|
|
273
|
+
| Tabs | `/ui/tabs` |
|
|
274
|
+
| Breadcrumb | `/ui/breadcrumb` |
|
|
275
|
+
| Pagination | `/ui/pagination` |
|
|
276
|
+
| Stepper | `/ui/stepper` |
|
|
277
|
+
| NavigationMenu | `/ui/navigation-menu` |
|
|
278
|
+
| Sidebar (AppSidebar) | `/shell/sidebar` |
|
|
279
|
+
| TopBar | `/shell/top-bar` |
|
|
280
|
+
| BottomNavbar | `/shell/bottom-navbar` |
|
|
281
|
+
|
|
282
|
+
### Data display
|
|
283
|
+
| Component | Subpath |
|
|
284
|
+
|---|---|
|
|
285
|
+
| Table | `/ui/table` |
|
|
286
|
+
| DataTable | `/ui/data-table` |
|
|
287
|
+
| StatCard | `/ui/stat-card` |
|
|
288
|
+
| ActivityFeed | `/composed/activity-feed` |
|
|
289
|
+
| Avatar | `/ui/avatar` |
|
|
290
|
+
| AvatarGroup | `/composed/avatar-group` |
|
|
291
|
+
| Text | `/ui/text` |
|
|
292
|
+
| Code | `/ui/code` |
|
|
293
|
+
| Charts (bar / line / area / pie / radar / gauge / sparkline) | `/ui/charts/*` |
|
|
294
|
+
|
|
295
|
+
## Common props across components
|
|
296
|
+
|
|
297
|
+
| Prop | Type | Where |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| `className` | string | All. Use sparingly. |
|
|
300
|
+
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | Button, Input, Select, Card, Alert, Badge, Tabs, Checkbox, Radio, Slider, etc. Not all sizes exist on every component. |
|
|
301
|
+
| `color` | `'accent' \| 'error' \| 'success' \| 'warning' \| 'info' \| 'neutral'` | Button, Badge, Card, Tabs (subset), Alert. |
|
|
302
|
+
| `variant` | varies | Button (`solid|soft|outline|ghost|link`), Card, Alert, Badge, Select, etc. |
|
|
303
|
+
| `disabled` | boolean | All interactive. Propagates from ButtonGroup. |
|
|
304
|
+
| `loading` | boolean | Button, IconButton, OAuthButton, async-aware components. |
|
|
305
|
+
|
|
306
|
+
## Per-component guides
|
|
307
|
+
|
|
308
|
+
See `components/{button|card|input|dialog|badge|select|tabs|toast|form|table|dropdown-menu|popover|text|stack|icon}.md` for prop tables, examples, and rules.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Popover
|
|
2
|
+
|
|
3
|
+
Click-triggered floating panel for interactive content anchored to a trigger.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import {
|
|
7
|
+
Popover,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
PopoverContent,
|
|
10
|
+
PopoverAnchor,
|
|
11
|
+
} from '@devalok/shilp-sutra/ui/popover'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- Interactive panel anchored to a button: filter form, date picker, color picker, share popover.
|
|
17
|
+
- A list of actions? Use `<DropdownMenu>` — keyboard model differs (arrow nav vs. Tab nav).
|
|
18
|
+
- Inert hover label (tooltip)? Use `<Tooltip>`.
|
|
19
|
+
- Rich hover preview (user card, link preview)? Use `<HoverCard>`.
|
|
20
|
+
- Critical full-attention interaction? Use `<Dialog>`.
|
|
21
|
+
|
|
22
|
+
On mobile, Popover auto-promotes to a bottom drawer when it would overflow the viewport — don't rebuild this manually.
|
|
23
|
+
|
|
24
|
+
## Compound shape
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Popover (root — open, onOpenChange, defaultOpen, modal)
|
|
28
|
+
PopoverTrigger ← asChild around your button
|
|
29
|
+
PopoverAnchor ← optional, decouples trigger from positioning origin
|
|
30
|
+
PopoverContent ← portalled, accepts side/align/sideOffset
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Root state props (Radix passthrough)
|
|
34
|
+
|
|
35
|
+
| Prop | Type | Notes |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `open` | `boolean` | Controlled. |
|
|
38
|
+
| `onOpenChange` | `(open: boolean) => void` | |
|
|
39
|
+
| `defaultOpen` | `boolean` | Uncontrolled. |
|
|
40
|
+
| `modal` | `boolean` | Default `false`. Set `true` for backdrop + focus trap (rare — usually use Dialog). |
|
|
41
|
+
|
|
42
|
+
## PopoverContent positioning
|
|
43
|
+
|
|
44
|
+
| Prop | Type | Notes |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `side` | `'top'\|'right'\|'bottom'\|'left'` | Preferred side. Floats to opposite side if it would overflow. |
|
|
47
|
+
| `align` | `'start'\|'center'\|'end'` | Alignment relative to trigger. |
|
|
48
|
+
| `sideOffset` | `number` (px) | Gap between trigger and content. |
|
|
49
|
+
| `collisionPadding` | `number \| { top, right, bottom, left }` | Distance from viewport edges before flipping. |
|
|
50
|
+
|
|
51
|
+
All Floating-UI positioning options pass through via Radix.
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
**Filter form:**
|
|
56
|
+
```tsx
|
|
57
|
+
<Popover>
|
|
58
|
+
<PopoverTrigger asChild>
|
|
59
|
+
<Button variant="soft" startIcon={IconFilter}>Filters</Button>
|
|
60
|
+
</PopoverTrigger>
|
|
61
|
+
<PopoverContent align="start">
|
|
62
|
+
<Stack gap="ds-04">
|
|
63
|
+
<FormField>
|
|
64
|
+
<Label htmlFor="status">Status</Label>
|
|
65
|
+
<Select value={status} onValueChange={setStatus}>
|
|
66
|
+
<SelectTrigger id="status">
|
|
67
|
+
<SelectValue />
|
|
68
|
+
</SelectTrigger>
|
|
69
|
+
<SelectContent>
|
|
70
|
+
<SelectItem value="all">All</SelectItem>
|
|
71
|
+
<SelectItem value="active">Active</SelectItem>
|
|
72
|
+
<SelectItem value="archived">Archived</SelectItem>
|
|
73
|
+
</SelectContent>
|
|
74
|
+
</Select>
|
|
75
|
+
</FormField>
|
|
76
|
+
<Stack direction="horizontal" gap="ds-03" justify="end">
|
|
77
|
+
<Button variant="ghost" size="sm" onClick={reset}>Clear</Button>
|
|
78
|
+
<Button size="sm" onClick={apply}>Apply</Button>
|
|
79
|
+
</Stack>
|
|
80
|
+
</Stack>
|
|
81
|
+
</PopoverContent>
|
|
82
|
+
</Popover>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Date picker:**
|
|
86
|
+
```tsx
|
|
87
|
+
<Popover>
|
|
88
|
+
<PopoverTrigger asChild>
|
|
89
|
+
<Button variant="soft" startIcon={IconCalendar}>
|
|
90
|
+
{date ? formatDate(date) : 'Pick a date'}
|
|
91
|
+
</Button>
|
|
92
|
+
</PopoverTrigger>
|
|
93
|
+
<PopoverContent>
|
|
94
|
+
<Calendar value={date} onChange={(d) => { setDate(d); setOpen(false) }} />
|
|
95
|
+
</PopoverContent>
|
|
96
|
+
</Popover>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Share popover with copy link:**
|
|
100
|
+
```tsx
|
|
101
|
+
<Popover>
|
|
102
|
+
<PopoverTrigger asChild>
|
|
103
|
+
<Button variant="soft" startIcon={IconShare}>Share</Button>
|
|
104
|
+
</PopoverTrigger>
|
|
105
|
+
<PopoverContent side="bottom" align="end">
|
|
106
|
+
<Stack gap="ds-03">
|
|
107
|
+
<Text variant="label-sm">Share link</Text>
|
|
108
|
+
<Stack direction="horizontal" gap="ds-02">
|
|
109
|
+
<Input value={shareUrl} readOnly className="flex-1" />
|
|
110
|
+
<IconButton
|
|
111
|
+
icon={<Icon icon={copied ? IconCheck : IconCopy} />}
|
|
112
|
+
variant="soft"
|
|
113
|
+
aria-label="Copy link"
|
|
114
|
+
onClick={() => { navigator.clipboard.writeText(shareUrl); setCopied(true) }}
|
|
115
|
+
/>
|
|
116
|
+
</Stack>
|
|
117
|
+
</Stack>
|
|
118
|
+
</PopoverContent>
|
|
119
|
+
</Popover>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Decoupled anchor (trigger ≠ positioning origin):**
|
|
123
|
+
```tsx
|
|
124
|
+
<Popover>
|
|
125
|
+
<PopoverAnchor>
|
|
126
|
+
<Card>
|
|
127
|
+
<CardHeader>
|
|
128
|
+
<CardTitle>Project</CardTitle>
|
|
129
|
+
<PopoverTrigger asChild>
|
|
130
|
+
<IconButton icon={<Icon icon={IconInfo} />} variant="ghost" size="sm" aria-label="About" />
|
|
131
|
+
</PopoverTrigger>
|
|
132
|
+
</CardHeader>
|
|
133
|
+
</Card>
|
|
134
|
+
</PopoverAnchor>
|
|
135
|
+
<PopoverContent side="top">
|
|
136
|
+
Popover positions relative to the whole Card, not the small info button.
|
|
137
|
+
</PopoverContent>
|
|
138
|
+
</Popover>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Inside a Dialog (nested overlays work correctly):**
|
|
142
|
+
```tsx
|
|
143
|
+
<Dialog>
|
|
144
|
+
<DialogContent>
|
|
145
|
+
<DialogHeader>
|
|
146
|
+
<DialogTitle>Schedule meeting</DialogTitle>
|
|
147
|
+
</DialogHeader>
|
|
148
|
+
<Popover>
|
|
149
|
+
<PopoverTrigger asChild>
|
|
150
|
+
<Button variant="soft">Pick a time</Button>
|
|
151
|
+
</PopoverTrigger>
|
|
152
|
+
<PopoverContent>
|
|
153
|
+
<TimePicker value={time} onChange={setTime} />
|
|
154
|
+
</PopoverContent>
|
|
155
|
+
</Popover>
|
|
156
|
+
</DialogContent>
|
|
157
|
+
</Dialog>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Popover uses `z-popover` (1400), above `z-dialog`. Nesting works without z-index fights.
|
|
161
|
+
|
|
162
|
+
**Controlled with custom close behavior:**
|
|
163
|
+
```tsx
|
|
164
|
+
const [open, setOpen] = useState(false)
|
|
165
|
+
|
|
166
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
167
|
+
<PopoverTrigger asChild>
|
|
168
|
+
<Button>Open</Button>
|
|
169
|
+
</PopoverTrigger>
|
|
170
|
+
<PopoverContent
|
|
171
|
+
onInteractOutside={(e) => {
|
|
172
|
+
if (hasUnsavedChanges) {
|
|
173
|
+
e.preventDefault()
|
|
174
|
+
confirmClose().then((ok) => ok && setOpen(false))
|
|
175
|
+
}
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<UnsavedForm />
|
|
179
|
+
</PopoverContent>
|
|
180
|
+
</Popover>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Composability
|
|
184
|
+
|
|
185
|
+
- **Built on Radix Popover** — every standard Radix prop passes through.
|
|
186
|
+
- **Portal rendering:** PopoverContent portals to body. CSS `overflow: hidden` / `transform` on ancestors don't clip it.
|
|
187
|
+
- **z-popover (1400):** Above Dialog (`z-dialog`). Nested popovers inside dialogs stack correctly.
|
|
188
|
+
- **PopoverAnchor:** Decouples the visual anchor from the interactive trigger. Useful when the trigger is small (icon button) but the popover should position relative to a larger surrounding element.
|
|
189
|
+
|
|
190
|
+
See `foundations/surfaces.md` for overlay surface, `foundations/motion.md` for the spring open animation, `foundations/spacing.md` for internal popover padding.
|
|
191
|
+
|
|
192
|
+
## Rules
|
|
193
|
+
|
|
194
|
+
- Use Popover for interactive content (forms, pickers). Use DropdownMenu for action lists — different keyboard model.
|
|
195
|
+
- `<PopoverTrigger asChild>` around any focusable element — usually a Button or IconButton.
|
|
196
|
+
- Set `modal={true}` only when the popover blocks interaction with the rest of the page. Default `false` is correct for filters / pickers.
|
|
197
|
+
- Set explicit `side` + `align` for predictable positioning. Defaults bottom-center, but designs often want `align="end"` for right-anchored triggers.
|
|
198
|
+
- Don't use Popover for tooltips — Tooltip is for inert hover labels. Popover requires explicit click.
|
|
199
|
+
- Don't nest Popover inside Popover. Use a single Popover with stepped content, or close the first before opening the second.
|
|
200
|
+
- For complex form flows that need full attention, switch to Dialog. Popovers shouldn't host multi-step wizards.
|
|
201
|
+
- The internal padding inside `PopoverContent` is preset — don't add wrapper divs with extra padding. Use `<Stack gap>` to space content.
|