@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.
Files changed (41) hide show
  1. package/AGENTS.md +23 -1
  2. package/BREAKING.json +66 -0
  3. package/BREAKING.schema.json +184 -0
  4. package/MIGRATION.md +16 -0
  5. package/docs/recipes/install-next-app-router.md +54 -14
  6. package/docs/recipes/upgrading.md +19 -0
  7. package/llms-full.txt +1 -1
  8. package/llms-quick.txt +3 -1
  9. package/llms.txt +1 -0
  10. package/make-kit/Guidelines.md +71 -0
  11. package/make-kit/components/badge.md +162 -0
  12. package/make-kit/components/button.md +125 -0
  13. package/make-kit/components/card.md +147 -0
  14. package/make-kit/components/dialog.md +167 -0
  15. package/make-kit/components/dropdown-menu.md +205 -0
  16. package/make-kit/components/form.md +189 -0
  17. package/make-kit/components/icon.md +152 -0
  18. package/make-kit/components/input.md +154 -0
  19. package/make-kit/components/overview.md +308 -0
  20. package/make-kit/components/popover.md +201 -0
  21. package/make-kit/components/select.md +148 -0
  22. package/make-kit/components/stack.md +165 -0
  23. package/make-kit/components/table.md +215 -0
  24. package/make-kit/components/tabs.md +162 -0
  25. package/make-kit/components/text.md +139 -0
  26. package/make-kit/components/toast.md +193 -0
  27. package/make-kit/foundations/color.md +128 -0
  28. package/make-kit/foundations/dark-mode.md +81 -0
  29. package/make-kit/foundations/icons.md +107 -0
  30. package/make-kit/foundations/motion.md +134 -0
  31. package/make-kit/foundations/radius.md +78 -0
  32. package/make-kit/foundations/spacing.md +110 -0
  33. package/make-kit/foundations/surfaces.md +121 -0
  34. package/make-kit/foundations/typography.md +120 -0
  35. package/make-kit/setup.md +130 -0
  36. package/package.json +9 -2
  37. package/skill/SKILL.md +3 -3
  38. package/skill/references/components-full.md +1 -1
  39. package/skill/references/components.md +1 -0
  40. package/skill/references/setup-next-app-router.md +54 -14
  41. package/skill/references/upgrading.md +19 -0
@@ -0,0 +1,148 @@
1
+ # Select
2
+
3
+ Single-choice picker from a short fixed list. Use instead of `<select>`.
4
+
5
+ ```tsx
6
+ import {
7
+ Select,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ SelectContent,
11
+ SelectGroup,
12
+ SelectLabel,
13
+ SelectItem,
14
+ SelectSeparator,
15
+ } from '@devalok/shilp-sutra/ui/select'
16
+ ```
17
+
18
+ ## When to use
19
+
20
+ - Fixed list under ~15 items, no search needed (status, priority, role).
21
+ - Need typeahead / search across many options? Use `<Combobox>`.
22
+ - Free-text with suggestions? Use `<Autocomplete>`.
23
+ - Multi-select? Use `<MultiSelect>` or `<Combobox multiple>`.
24
+ - Yes/no toggle? Use `<Switch>` or `<RadioGroup>` with 2 options.
25
+
26
+ ## Compound shape
27
+
28
+ ```
29
+ Select (root — value, onValueChange, defaultValue)
30
+ SelectTrigger ← variant / color / size go HERE
31
+ SelectValue (placeholder)
32
+ SelectContent
33
+ SelectGroup (optional)
34
+ SelectLabel ← non-interactive section header
35
+ SelectItem (value) ← REQUIRED value, unique
36
+ SelectSeparator
37
+ ```
38
+
39
+ ## SelectTrigger props
40
+
41
+ | Prop | Type | Notes |
42
+ |---|---|---|
43
+ | `variant` | `'default'\|'outline'\|'ghost'` | Default `default`. |
44
+ | `color` | `'default'\|'error'\|'success'\|'warning'` | Default `default`. `error` sets `aria-invalid`. |
45
+ | `size` | `'xs'\|'sm'\|'md'\|'lg'` | Default `md`. |
46
+
47
+ Styling lives on the **Trigger**, not on `Select` root. Setting `<Select size="lg">` does nothing — TypeScript won't catch it.
48
+
49
+ ## Root state props (Radix passthrough)
50
+
51
+ | Prop | Type | Notes |
52
+ |---|---|---|
53
+ | `value` | `string` | Controlled value. |
54
+ | `onValueChange` | `(value: string) => void` | Fires on select. |
55
+ | `defaultValue` | `string` | Uncontrolled. |
56
+ | `open` | `boolean` | Controlled open state. |
57
+ | `onOpenChange` | `(open: boolean) => void` | |
58
+
59
+ ## Examples
60
+
61
+ **Standard:**
62
+ ```tsx
63
+ <Select onValueChange={setStatus}>
64
+ <SelectTrigger>
65
+ <SelectValue placeholder="Status" />
66
+ </SelectTrigger>
67
+ <SelectContent>
68
+ <SelectItem value="todo">To do</SelectItem>
69
+ <SelectItem value="doing">In progress</SelectItem>
70
+ <SelectItem value="done">Done</SelectItem>
71
+ </SelectContent>
72
+ </Select>
73
+ ```
74
+
75
+ **With grouped items and a separator:**
76
+ ```tsx
77
+ <Select value={assignee} onValueChange={setAssignee}>
78
+ <SelectTrigger size="sm">
79
+ <SelectValue placeholder="Assignee" />
80
+ </SelectTrigger>
81
+ <SelectContent>
82
+ <SelectGroup>
83
+ <SelectLabel>Team</SelectLabel>
84
+ <SelectItem value="alice">Alice</SelectItem>
85
+ <SelectItem value="bob">Bob</SelectItem>
86
+ </SelectGroup>
87
+ <SelectSeparator />
88
+ <SelectGroup>
89
+ <SelectLabel>External</SelectLabel>
90
+ <SelectItem value="contractor-1">Contractor 1</SelectItem>
91
+ </SelectGroup>
92
+ </SelectContent>
93
+ </Select>
94
+ ```
95
+
96
+ **Inside a FormField (manual error wiring):**
97
+ ```tsx
98
+ <FormField state={errors.role ? 'error' : 'helper'}>
99
+ <Label htmlFor="role">Role</Label>
100
+ <Select value={role} onValueChange={setRole}>
101
+ <SelectTrigger id="role" color={errors.role ? 'error' : 'default'}>
102
+ <SelectValue placeholder="Choose a role" />
103
+ </SelectTrigger>
104
+ <SelectContent>
105
+ <SelectItem value="admin">Admin</SelectItem>
106
+ <SelectItem value="member">Member</SelectItem>
107
+ <SelectItem value="viewer">Viewer</SelectItem>
108
+ </SelectContent>
109
+ </Select>
110
+ {errors.role && <FormHelperText>{errors.role}</FormHelperText>}
111
+ </FormField>
112
+ ```
113
+
114
+ Select doesn't auto-consume FormField context (unlike Input / Textarea). Set `color="error"` on `SelectTrigger` manually.
115
+
116
+ **Ghost variant in a toolbar:**
117
+ ```tsx
118
+ <Select value={sort} onValueChange={setSort} defaultValue="recent">
119
+ <SelectTrigger variant="ghost" size="sm">
120
+ <SelectValue />
121
+ </SelectTrigger>
122
+ <SelectContent>
123
+ <SelectItem value="recent">Most recent</SelectItem>
124
+ <SelectItem value="oldest">Oldest</SelectItem>
125
+ <SelectItem value="az">A → Z</SelectItem>
126
+ </SelectContent>
127
+ </Select>
128
+ ```
129
+
130
+ ## Composability
131
+
132
+ - **Radix Select** underneath — `value` / `onValueChange` / `defaultValue` / `open` / `onOpenChange` standard state.
133
+ - **Portal + z-popover (1400):** SelectContent portals to body, stacks above Dialog / Sheet / other overlays.
134
+ - **SelectItem requires a unique `value`** — duplicates produce undefined selection behavior.
135
+ - **FormField integration is manual** — wire `color="error"` on SelectTrigger from your validation state.
136
+
137
+ See `foundations/surfaces.md` for the overlay surface, `foundations/color.md` for state colors.
138
+
139
+ ## Rules
140
+
141
+ - Put `variant` / `color` / `size` on `SelectTrigger`, NOT on `Select` root.
142
+ - Every `SelectItem` needs a unique `value` prop.
143
+ - For lists over ~15 items or when users will scan for a term, switch to `<Combobox>` — Select has no typeahead.
144
+ - Set `color="error"` on `SelectTrigger` for validation failures — Select doesn't auto-consume FormField state.
145
+ - Don't use Select for multi-value capture — use `<MultiSelect>` or `<Combobox multiple>`.
146
+ - Always render `<SelectValue placeholder="..." />` — without it, the trigger renders empty before any selection.
147
+ - Group related items with `<SelectGroup>` + `<SelectLabel>` — flat lists over 8 items get hard to scan.
148
+ - Don't customize the dropdown surface — overlays use `surface-1` per `foundations/surfaces.md`.
@@ -0,0 +1,165 @@
1
+ # Stack
2
+
3
+ Flexbox layout primitive. Use instead of `<div className="flex flex-col gap-4">` everywhere.
4
+
5
+ ```tsx
6
+ import { Stack } from '@devalok/shilp-sutra/ui/stack'
7
+ ```
8
+
9
+ ## When to use
10
+
11
+ - Stacking elements vertically or horizontally with consistent gap.
12
+ - Any time you'd write `flex` + `gap-*` + `items-*` + `justify-*` on a div.
13
+ - Centering + width-capping a page section? Use `<Container>`, then a `<Stack>` inside.
14
+ - Grid layouts (rows × columns)? Use a Tailwind `grid` div directly — Stack is for one-axis layouts.
15
+
16
+ Server-safe — no hydration, no context.
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Notes |
21
+ |---|---|---|
22
+ | `direction` | `'vertical'\|'horizontal'\|'row'\|'column'` | Default `vertical`. `row` = `horizontal`, `column` = `vertical` (aliases). |
23
+ | `gap` | `'ds-01'..'ds-13'` \| `0..13` | Design-system spacing token. Numbers map 1:1 to `ds-0N`. |
24
+ | `align` | `'start'\|'center'\|'end'\|'stretch'\|'baseline'` | Cross-axis alignment (`align-items`). |
25
+ | `justify` | `'start'\|'center'\|'end'\|'between'\|'around'\|'evenly'` | Main-axis alignment (`justify-content`). |
26
+ | `wrap` | `boolean` | Enables `flex-wrap`. |
27
+ | `as` | `ElementType` | Default `'div'`. Polymorphic. |
28
+ | `className` | `string` | For overrides — don't reach for raw `flex-*` utilities. |
29
+
30
+ ## Gap cadence
31
+
32
+ Default to the 3-tier cadence from `foundations/spacing.md`:
33
+
34
+ | Gap | Use |
35
+ |---|---|
36
+ | `ds-03` (8 px) | Related items inside a group (icon + label, button row). |
37
+ | `ds-05` (16 px) | Grouped sections within a card / form. |
38
+ | `ds-07` (32 px) | Page sections / major regions. |
39
+
40
+ Don't reach for every adjacent token. If `ds-04` "feels right," it usually means a parent's padding or the section relationship needs a rethink.
41
+
42
+ ## Examples
43
+
44
+ **Vertical form layout:**
45
+ ```tsx
46
+ <Stack gap="ds-05">
47
+ <FormField>
48
+ <Label htmlFor="name">Name</Label>
49
+ <Input id="name" />
50
+ </FormField>
51
+ <FormField>
52
+ <Label htmlFor="email">Email</Label>
53
+ <Input id="email" />
54
+ </FormField>
55
+ </Stack>
56
+ ```
57
+
58
+ **Horizontal toolbar:**
59
+ ```tsx
60
+ <Stack direction="horizontal" gap="ds-03" align="center">
61
+ <Button variant="soft" startIcon={IconFilter}>Filter</Button>
62
+ <Button variant="soft" startIcon={IconSortDescending}>Sort</Button>
63
+ <Separator orientation="vertical" className="h-6" />
64
+ <Button variant="solid" startIcon={IconPlus}>New</Button>
65
+ </Stack>
66
+ ```
67
+
68
+ **Avatar + label cluster:**
69
+ ```tsx
70
+ <Stack direction="horizontal" gap="ds-03" align="center">
71
+ <Avatar size="sm" src={user.avatar} alt={user.name} />
72
+ <Stack gap="ds-01">
73
+ <Text variant="label-plain-sm">{user.name}</Text>
74
+ <Text variant="body-xs" className="text-fg-muted">{user.role}</Text>
75
+ </Stack>
76
+ </Stack>
77
+ ```
78
+
79
+ **Page-section spacing:**
80
+ ```tsx
81
+ <Container>
82
+ <Stack gap="ds-07">
83
+ <PageHeader title="Projects" description="A workspace for everything you ship." />
84
+ <Stack gap="ds-05">
85
+ <SectionHeader title="Active" />
86
+ <ProjectGrid projects={active} />
87
+ </Stack>
88
+ <Stack gap="ds-05">
89
+ <SectionHeader title="Archived" />
90
+ <ProjectGrid projects={archived} />
91
+ </Stack>
92
+ </Stack>
93
+ </Container>
94
+ ```
95
+
96
+ **Wrap for tag cluster:**
97
+ ```tsx
98
+ <Stack direction="horizontal" gap="ds-02" wrap>
99
+ {tags.map((tag) => <Badge key={tag} color="neutral">{tag}</Badge>)}
100
+ </Stack>
101
+ ```
102
+
103
+ For tag clusters specifically, prefer `<Badge.Group>` — it handles overflow with `+N` automatically.
104
+
105
+ **Justify-between (label + action):**
106
+ ```tsx
107
+ <Stack direction="horizontal" gap="ds-04" align="center" justify="between">
108
+ <Text variant="heading-md">Team</Text>
109
+ <Button variant="soft" startIcon={IconPlus} size="sm">Invite</Button>
110
+ </Stack>
111
+ ```
112
+
113
+ **Polymorphic — semantic list:**
114
+ ```tsx
115
+ <Stack as="ul" gap="ds-03">
116
+ {items.map((item) => (
117
+ <Stack as="li" key={item.id} direction="horizontal" gap="ds-03" align="center">
118
+ <Icon icon={IconCheck} />
119
+ <Text variant="body-sm">{item.label}</Text>
120
+ </Stack>
121
+ ))}
122
+ </Stack>
123
+ ```
124
+
125
+ **Numeric gap shortcut:**
126
+ ```tsx
127
+ <Stack gap={5}> {/* same as gap="ds-05" */}
128
+
129
+ </Stack>
130
+ ```
131
+
132
+ **Inside Card (no extra padding):**
133
+ ```tsx
134
+ <Card>
135
+ <CardContent>
136
+ <Stack gap="ds-04">
137
+ <Text variant="label-sm" className="text-fg-muted">REVENUE</Text>
138
+ <Text variant="heading-xl">$2.4M</Text>
139
+ <Text variant="body-sm" className="text-fg-muted">+18% YoY</Text>
140
+ </Stack>
141
+ </CardContent>
142
+ </Card>
143
+ ```
144
+
145
+ CardContent already supplies the outer padding. Stack just spaces the children.
146
+
147
+ ## Composability
148
+
149
+ - **Server-safe.** Nothing to hydrate. Use in RSC trees.
150
+ - **Polymorphic via `as`** — `<Stack as="ul">`, `<Stack as="section">`, `<Stack as="nav">`. Inherits flex behavior on the chosen element.
151
+ - **Compose with Container:** `<Container><Stack>...</Stack></Container>`. Container centers + caps width; Stack arranges children.
152
+ - **No responsive direction prop** — for `flex-col md:flex-row` use a plain div with Tailwind utilities directly, or render two Stacks with display toggling.
153
+
154
+ See `foundations/spacing.md` for the gap cadence, `foundations/surfaces.md` for layout-on-surfaces context.
155
+
156
+ ## Rules
157
+
158
+ - Default to the `ds-03 / ds-05 / ds-07` cadence. Reach for other tokens only with a deliberate reason.
159
+ - Use Stack everywhere you'd write `flex` + `gap` on a div. Don't mix Stack and raw flex utilities in the same tree.
160
+ - For grid (2D) layouts, use a plain `grid` div. Stack is one-axis only.
161
+ - For responsive direction changes, drop to a plain div with Tailwind responsive flex utilities. Stack doesn't have a responsive direction prop.
162
+ - Don't add padding to a Stack — wrap it in a Container or Card. Padding lives on containers, not layout primitives.
163
+ - For tag clusters with potential overflow, use `<Badge.Group>` instead of `<Stack wrap>` — it handles `+N` collapsing.
164
+ - Numeric `gap={4}` and string `gap="ds-04"` are equivalent. Pick a convention per file and stick with it.
165
+ - `direction="row"` and `direction="column"` are aliases for `horizontal` / `vertical`. Pick one naming pair per codebase.
@@ -0,0 +1,215 @@
1
+ # Table
2
+
3
+ Server-safe semantic wrappers around `<table>`. For static / presentational tables.
4
+
5
+ ```tsx
6
+ import {
7
+ Table,
8
+ TableHeader,
9
+ TableBody,
10
+ TableFooter,
11
+ TableRow,
12
+ TableHead,
13
+ TableCell,
14
+ TableCaption,
15
+ } from '@devalok/shilp-sutra/ui/table'
16
+ ```
17
+
18
+ ## When to use
19
+
20
+ - Static or small data displays where you control every row and cell.
21
+ - Marketing / pricing comparison tables.
22
+ - Documentation tables (API references, prop tables).
23
+ - Server-rendered tables (RSC) — Table and sub-components are server-safe.
24
+ - Need sorting / filtering / pagination / selection / virtualization? Use `<DataTable>` from `@devalok/shilp-sutra/ui/data-table` — out of scope for this guide.
25
+
26
+ ## Compound shape
27
+
28
+ ```
29
+ Table (<table>)
30
+ TableCaption (<caption>) ← optional summary for screen readers
31
+ TableHeader (<thead>)
32
+ TableRow (<tr>)
33
+ TableHead (<th scope="col">)
34
+ TableBody (<tbody>)
35
+ TableRow (<tr>)
36
+ TableCell (<td>)
37
+ TableFooter (<tfoot>)
38
+ TableRow
39
+ TableCell
40
+ ```
41
+
42
+ Each component is a thin semantic wrapper — no props beyond standard HTML attributes plus `className`.
43
+
44
+ ## Examples
45
+
46
+ **Standard:**
47
+ ```tsx
48
+ <Table>
49
+ <TableHeader>
50
+ <TableRow>
51
+ <TableHead>Name</TableHead>
52
+ <TableHead>Status</TableHead>
53
+ <TableHead>Owner</TableHead>
54
+ <TableHead>Updated</TableHead>
55
+ </TableRow>
56
+ </TableHeader>
57
+ <TableBody>
58
+ {projects.map((p) => (
59
+ <TableRow key={p.id}>
60
+ <TableCell>{p.name}</TableCell>
61
+ <TableCell>
62
+ <Badge color={p.status === 'active' ? 'success' : 'neutral'}>
63
+ {p.status}
64
+ </Badge>
65
+ </TableCell>
66
+ <TableCell>
67
+ <Stack direction="horizontal" gap="ds-02" align="center">
68
+ <Avatar size="xs" src={p.owner.avatar} alt={p.owner.name} />
69
+ <Text variant="body-sm">{p.owner.name}</Text>
70
+ </Stack>
71
+ </TableCell>
72
+ <TableCell>
73
+ <Text variant="body-sm" className="text-fg-muted">
74
+ {formatDate(p.updatedAt)}
75
+ </Text>
76
+ </TableCell>
77
+ </TableRow>
78
+ ))}
79
+ </TableBody>
80
+ </Table>
81
+ ```
82
+
83
+ **With caption + footer:**
84
+ ```tsx
85
+ <Table>
86
+ <TableCaption>Q4 revenue by region.</TableCaption>
87
+ <TableHeader>
88
+ <TableRow>
89
+ <TableHead>Region</TableHead>
90
+ <TableHead>Revenue</TableHead>
91
+ </TableRow>
92
+ </TableHeader>
93
+ <TableBody>
94
+ <TableRow><TableCell>NA</TableCell><TableCell>$1.2M</TableCell></TableRow>
95
+ <TableRow><TableCell>EU</TableCell><TableCell>$0.8M</TableCell></TableRow>
96
+ <TableRow><TableCell>APAC</TableCell><TableCell>$0.4M</TableCell></TableRow>
97
+ </TableBody>
98
+ <TableFooter>
99
+ <TableRow>
100
+ <TableCell><Text variant="label-sm">Total</Text></TableCell>
101
+ <TableCell><Text variant="label-sm">$2.4M</Text></TableCell>
102
+ </TableRow>
103
+ </TableFooter>
104
+ </Table>
105
+ ```
106
+
107
+ **Row actions (IconButton in last cell):**
108
+ ```tsx
109
+ <Table>
110
+ <TableHeader>
111
+ <TableRow>
112
+ <TableHead>File</TableHead>
113
+ <TableHead>Size</TableHead>
114
+ <TableHead className="w-12" />
115
+ </TableRow>
116
+ </TableHeader>
117
+ <TableBody>
118
+ {files.map((f) => (
119
+ <TableRow key={f.id}>
120
+ <TableCell>{f.name}</TableCell>
121
+ <TableCell>{formatFileSize(f.size)}</TableCell>
122
+ <TableCell>
123
+ <DropdownMenu>
124
+ <DropdownMenuTrigger asChild>
125
+ <IconButton icon={<Icon icon={IconDots} />} variant="ghost" size="sm" aria-label="Actions" />
126
+ </DropdownMenuTrigger>
127
+ <DropdownMenuContent>
128
+ <DropdownMenuItem onSelect={() => download(f)}>Download</DropdownMenuItem>
129
+ <DropdownMenuSeparator />
130
+ <DropdownMenuItem onSelect={() => remove(f)}>Delete</DropdownMenuItem>
131
+ </DropdownMenuContent>
132
+ </DropdownMenu>
133
+ </TableCell>
134
+ </TableRow>
135
+ ))}
136
+ </TableBody>
137
+ </Table>
138
+ ```
139
+
140
+ **Inside a Card:**
141
+ ```tsx
142
+ <Card>
143
+ <CardHeader>
144
+ <CardTitle>Team members</CardTitle>
145
+ </CardHeader>
146
+ <CardContent>
147
+ <Table>
148
+ <TableHeader>
149
+ <TableRow>
150
+ <TableHead>Member</TableHead>
151
+ <TableHead>Role</TableHead>
152
+ </TableRow>
153
+ </TableHeader>
154
+ <TableBody>
155
+ {members.map((m) => (
156
+ <TableRow key={m.id}>
157
+ <TableCell>{m.name}</TableCell>
158
+ <TableCell><Badge color="neutral">{m.role}</Badge></TableCell>
159
+ </TableRow>
160
+ ))}
161
+ </TableBody>
162
+ </Table>
163
+ </CardContent>
164
+ </Card>
165
+ ```
166
+
167
+ When inside a `<Card>`, the Table's surrounding padding comes from `CardContent`. Don't add extra padding on the Table.
168
+
169
+ **Server-rendered table (RSC):**
170
+ ```tsx
171
+ // app/projects/page.tsx — no 'use client'
172
+ export default async function ProjectsPage() {
173
+ const projects = await db.projects.findMany()
174
+ return (
175
+ <Table>
176
+ <TableHeader>
177
+ <TableRow>
178
+ <TableHead>Name</TableHead>
179
+ <TableHead>Status</TableHead>
180
+ </TableRow>
181
+ </TableHeader>
182
+ <TableBody>
183
+ {projects.map((p) => (
184
+ <TableRow key={p.id}>
185
+ <TableCell>{p.name}</TableCell>
186
+ <TableCell><Badge>{p.status}</Badge></TableCell>
187
+ </TableRow>
188
+ ))}
189
+ </TableBody>
190
+ </Table>
191
+ )
192
+ }
193
+ ```
194
+
195
+ Table + Badge (server-safe via Badge.Group context — verify per use) + Avatar all render in RSC trees. Skip client components.
196
+
197
+ ## Composability
198
+
199
+ - **Server-safe:** Table and its sub-components are pure HTML semantic wrappers. No state, no context. Use in RSC trees without `'use client'`.
200
+ - **TableHead scope:** Headers automatically get `scope="col"` for screen-reader navigation. Don't override it.
201
+ - **Composes with primitives:** Drop `<Badge>`, `<Avatar>`, `<IconButton>`, `<StatusDot>` inside cells. Check each component's server-safety if you need RSC compatibility.
202
+ - **TableCaption:** Renders as HTML `<caption>` — screen readers announce it before content. Use it for any non-trivial table.
203
+
204
+ See `foundations/typography.md` for the body / label variants inside cells, `foundations/surfaces.md` for table-in-card surface guidance.
205
+
206
+ ## Rules
207
+
208
+ - For anything with sorting / filtering / pagination / selection / virtualization, use `<DataTable>` from `/ui/data-table`. Don't rebuild that machinery on bare Table.
209
+ - Always wrap header cells in `<TableHead>` (renders `<th>`). Don't use `<TableCell>` (`<td>`) in headers — breaks screen-reader column scope.
210
+ - Use `<TableCaption>` for any table that's not self-evident. Renders the HTML `<caption>` which screen readers announce.
211
+ - Inside Card, drop the Table directly in `<CardContent>` — don't add wrapper divs that fight the card's padding cascade.
212
+ - For wide tables on mobile, wrap in an `overflow-x-auto` container. Don't try to make a Table responsive via column stacking — switch to a card list on small viewports.
213
+ - Don't style cell text with raw Tailwind palette utilities. Use `text-fg-muted` from `foundations/color.md`.
214
+ - Compose with Badge for status, Avatar for users, IconButton for row actions. Don't invent new primitives per table.
215
+ - Keep TableCell content single-line where possible — multi-line cells make scanning hard. Use `<Stack>` only when each row genuinely needs two lines.
@@ -0,0 +1,162 @@
1
+ # Tabs
2
+
3
+ Switch between sibling views inside a single region. Not for top-level navigation.
4
+
5
+ ```tsx
6
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@devalok/shilp-sutra/ui/tabs'
7
+ ```
8
+
9
+ ## When to use
10
+
11
+ - Sub-sections of a single page / panel that share context (Overview / Activity / Settings on a project).
12
+ - Filtered views over the same dataset (All / Mine / Archived).
13
+ - Need URL-driven routes per tab? Wire `value` / `onValueChange` to router state.
14
+ - Top-level app navigation? Use `<Sidebar>` / `<TopBar>`, not Tabs.
15
+ - Multi-step flows? Use `<Stepper>` or a wizard pattern.
16
+
17
+ ## Compound shape
18
+
19
+ ```
20
+ Tabs (root — value, defaultValue, onValueChange)
21
+ TabsList (variant, size, orientation)
22
+ TabsTrigger (value) ← inherits variant/size/orientation from TabsList
23
+ TabsContent (value) ← rendered inline (not portalled)
24
+ ```
25
+
26
+ ## TabsList props
27
+
28
+ | Prop | Type | Notes |
29
+ |---|---|---|
30
+ | `variant` | `'line'\|'contained'` | Default `line`. |
31
+ | `size` | `'sm'\|'md'\|'lg'` | Default `md`. |
32
+ | `orientation` | `'horizontal'\|'vertical'` | Default `horizontal`. Vertical also changes keyboard nav to ArrowUp/Down. |
33
+ | `color` | `'accent'\|'neutral'` | Affects the line-variant active indicator. |
34
+
35
+ ## Variants
36
+
37
+ | Variant | When |
38
+ |---|---|
39
+ | `line` (default) | Underline active indicator. Most common — pairs with section headings. |
40
+ | `contained` | Pill background per active trigger. Use inside cards or compact toolbars. |
41
+
42
+ ## Root state props (Radix passthrough)
43
+
44
+ | Prop | Type | Notes |
45
+ |---|---|---|
46
+ | `value` | `string` | Controlled active tab. |
47
+ | `defaultValue` | `string` | Uncontrolled initial tab. |
48
+ | `onValueChange` | `(value: string) => void` | Fires on tab change. |
49
+
50
+ ## TabsTrigger / TabsContent
51
+
52
+ Both require a `value: string` prop. The values must match between a trigger and its content.
53
+
54
+ `TabsTrigger` reads `variant` / `size` / `orientation` from `TabsList` via context. Override per-trigger if needed, but normally don't.
55
+
56
+ ## Examples
57
+
58
+ **Standard line tabs:**
59
+ ```tsx
60
+ <Tabs defaultValue="overview">
61
+ <TabsList>
62
+ <TabsTrigger value="overview">Overview</TabsTrigger>
63
+ <TabsTrigger value="activity">Activity</TabsTrigger>
64
+ <TabsTrigger value="settings">Settings</TabsTrigger>
65
+ </TabsList>
66
+ <TabsContent value="overview">
67
+ <ProjectOverview />
68
+ </TabsContent>
69
+ <TabsContent value="activity">
70
+ <ActivityFeed />
71
+ </TabsContent>
72
+ <TabsContent value="settings">
73
+ <ProjectSettings />
74
+ </TabsContent>
75
+ </Tabs>
76
+ ```
77
+
78
+ **Contained variant inside a card:**
79
+ ```tsx
80
+ <Card>
81
+ <CardContent>
82
+ <Tabs defaultValue="day">
83
+ <TabsList variant="contained" size="sm">
84
+ <TabsTrigger value="day">Day</TabsTrigger>
85
+ <TabsTrigger value="week">Week</TabsTrigger>
86
+ <TabsTrigger value="month">Month</TabsTrigger>
87
+ </TabsList>
88
+ <TabsContent value="day"><Chart range="day" /></TabsContent>
89
+ <TabsContent value="week"><Chart range="week" /></TabsContent>
90
+ <TabsContent value="month"><Chart range="month" /></TabsContent>
91
+ </Tabs>
92
+ </CardContent>
93
+ </Card>
94
+ ```
95
+
96
+ **Vertical orientation (settings-style):**
97
+ ```tsx
98
+ <Tabs defaultValue="account" orientation="vertical">
99
+ <Stack direction="horizontal" gap="ds-07" align="start">
100
+ <TabsList orientation="vertical">
101
+ <TabsTrigger value="account">Account</TabsTrigger>
102
+ <TabsTrigger value="billing">Billing</TabsTrigger>
103
+ <TabsTrigger value="notifications">Notifications</TabsTrigger>
104
+ </TabsList>
105
+ <div className="flex-1">
106
+ <TabsContent value="account"><AccountForm /></TabsContent>
107
+ <TabsContent value="billing"><BillingForm /></TabsContent>
108
+ <TabsContent value="notifications"><NotificationsForm /></TabsContent>
109
+ </div>
110
+ </Stack>
111
+ </Tabs>
112
+ ```
113
+
114
+ **With icons + badges:**
115
+ ```tsx
116
+ <Tabs defaultValue="inbox">
117
+ <TabsList>
118
+ <TabsTrigger value="inbox">
119
+ <Icon icon={IconInbox} /> Inbox
120
+ <Badge size="xs" color="accent">12</Badge>
121
+ </TabsTrigger>
122
+ <TabsTrigger value="sent">
123
+ <Icon icon={IconSend} /> Sent
124
+ </TabsTrigger>
125
+ </TabsList>
126
+ <TabsContent value="inbox"><InboxList /></TabsContent>
127
+ <TabsContent value="sent"><SentList /></TabsContent>
128
+ </Tabs>
129
+ ```
130
+
131
+ **Router-driven (Next.js App Router):**
132
+ ```tsx
133
+ 'use client'
134
+ const router = useRouter()
135
+ const pathname = usePathname()
136
+ const tab = pathname.split('/').pop() ?? 'overview'
137
+
138
+ <Tabs value={tab} onValueChange={(v) => router.push(`/projects/${id}/${v}`)}>
139
+ <TabsList>
140
+ <TabsTrigger value="overview">Overview</TabsTrigger>
141
+ <TabsTrigger value="activity">Activity</TabsTrigger>
142
+ </TabsList>
143
+ </Tabs>
144
+ ```
145
+
146
+ ## Composability
147
+
148
+ - **Context cascade:** `TabsList` propagates `variant` / `size` / `orientation` to every child `TabsTrigger`. Don't repeat those props on each trigger.
149
+ - **Inline content:** `TabsContent` renders inline (not portalled). Container-scoped queries in tests work.
150
+ - **Keyboard:** Roving tabindex via Radix — ArrowLeft/Right (horizontal) or ArrowUp/Down (vertical). Home / End jump to first / last. Don't re-implement.
151
+
152
+ See `foundations/spacing.md` for the gap between TabsList and TabsContent, `foundations/icons.md` for icon sizing inside triggers.
153
+
154
+ ## Rules
155
+
156
+ - Put `variant` / `size` / `orientation` on `TabsList`, NOT on `Tabs` root or `TabsTrigger`.
157
+ - Every `TabsTrigger` and `TabsContent` needs a `value` — the values must match.
158
+ - For top-level app navigation use Sidebar / TopBar, not Tabs.
159
+ - Don't stack two Tabs inside the same region — pick one. Nested tabs confuse keyboard nav and section structure.
160
+ - For router-bound tabs, keep `value` controlled — don't mix `defaultValue` with router-driven URLs.
161
+ - For 5+ tabs that overflow on mobile, consider a Select dropdown on small viewports or wrap in a horizontally scrollable container.
162
+ - Icons in TabsTrigger don't auto-size via IconProvider — set explicit `<Icon icon={...} size="sm" />` when the trigger feels off.