@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,139 @@
1
+ # Text
2
+
3
+ Typography primitive. Use instead of raw `<h1>`–`<h6>`, `<p>`, `<span>` for any visible text.
4
+
5
+ ```tsx
6
+ import { Text } from '@devalok/shilp-sutra/ui/text'
7
+ ```
8
+
9
+ ## When to use
10
+
11
+ - Any visible text: headings, body copy, captions, section labels, inline microcopy.
12
+ - Inline code spans inside body text? Pair with `<Code>`.
13
+ - Polymorphic — `<Text as="span">`, `<Text as="div">`, etc. — to demote semantics while keeping visual weight.
14
+ - Server-safe — renders in RSC trees without `'use client'`.
15
+
16
+ ## Variants
17
+
18
+ | Variant | Default element | Use |
19
+ |---|---|---|
20
+ | `heading-2xl` | `h1` | Page title / hero. |
21
+ | `heading-xl` | `h2` | Section title. |
22
+ | `heading-lg` | `h3` | Sub-section title. |
23
+ | `heading-md` | `h4` | Card title (used by `<CardTitle>`). |
24
+ | `heading-sm` | `h5` | Compact sub-heading. |
25
+ | `heading-xs` | `h6` | Smallest heading. |
26
+ | `body-lg` | `p` | Lead paragraphs. |
27
+ | `body-md` (default) | `p` | Standard body. |
28
+ | `body-sm` | `p` | Dense body — captions inside cards, table cells. |
29
+ | `body-xs` | `p` | Footnotes, fine print. |
30
+ | `label-lg` / `md` / `sm` / `xs` | `span` | UPPERCASE section labels, eyebrows. |
31
+ | `label-plain-lg` / `md` / `sm` / `xs` | `span` | Mixed-case labels (form labels, inline UI text). |
32
+ | `caption` | `span` | Image / chart captions. |
33
+ | `overline` | `span` | UPPERCASE marketing eyebrow. |
34
+ | `code` | `code` | Inline monospace code. |
35
+
36
+ `label-*` and `overline` variants are automatically uppercase. `label-plain-*` keeps mixed case.
37
+
38
+ ## Props
39
+
40
+ | Prop | Type | Notes |
41
+ |---|---|---|
42
+ | `variant` | See variants table | Default `body-md`. |
43
+ | `as` | `ElementType` | Override the auto-selected HTML element. |
44
+ | `className` | `string` | For color / alignment overrides. Use semantic tokens only. |
45
+
46
+ Plus all standard HTML attributes for whatever element it renders.
47
+
48
+ ## Examples
49
+
50
+ **Page heading + body:**
51
+ ```tsx
52
+ <Stack gap="ds-04">
53
+ <Text variant="heading-2xl">Projects</Text>
54
+ <Text variant="body-md" className="text-fg-muted">
55
+ A workspace for everything you ship.
56
+ </Text>
57
+ </Stack>
58
+ ```
59
+
60
+ **Section label + heading pair:**
61
+ ```tsx
62
+ <Stack gap="ds-02">
63
+ <Text variant="label-sm" className="text-fg-muted">REVENUE</Text>
64
+ <Text variant="heading-xl">$2.4M</Text>
65
+ <Text variant="body-sm" className="text-fg-muted">+18% YoY</Text>
66
+ </Stack>
67
+ ```
68
+
69
+ **Visual demotion via `as`:**
70
+ ```tsx
71
+ {/* h2-sized heading rendered as a div — useful when the element already has a heading ancestor */}
72
+ <Card>
73
+ <CardHeader>
74
+ <CardTitle>Activity</CardTitle> {/* h4 */}
75
+ <Text variant="heading-xl" as="div">Weekly summary</Text>
76
+ </CardHeader>
77
+ </Card>
78
+ ```
79
+
80
+ **Inline span inside a paragraph:**
81
+ ```tsx
82
+ <Text>
83
+ Press <Text as="kbd" variant="code">⌘ K</Text> to open the command palette.
84
+ </Text>
85
+ ```
86
+
87
+ **Caption under an image:**
88
+ ```tsx
89
+ <Stack gap="ds-02">
90
+ <img src={chart} alt="" />
91
+ <Text variant="caption" className="text-fg-muted">
92
+ Figure 1. Active users by region.
93
+ </Text>
94
+ </Stack>
95
+ ```
96
+
97
+ **Form field label (label-plain variant):**
98
+ ```tsx
99
+ <Stack gap="ds-02">
100
+ <Text variant="label-plain-sm" as="label" htmlFor="email">Email</Text>
101
+ <Input id="email" type="email" />
102
+ </Stack>
103
+ ```
104
+
105
+ Use `<Label>` for form fields when wired with FormField — `Text` is for non-form labels.
106
+
107
+ **Body with inline code:**
108
+ ```tsx
109
+ <Text>
110
+ Call <Code>onSubmit</Code> with the form values, or pass <Code>asChild</Code> to merge with a child element.
111
+ </Text>
112
+ ```
113
+
114
+ **Truncated single line:**
115
+ ```tsx
116
+ <Text variant="body-sm" className="truncate max-w-[200px]">
117
+ {longUserName}
118
+ </Text>
119
+ ```
120
+
121
+ ## Composability
122
+
123
+ - **Server-safe.** No client hooks. Use freely in RSC trees.
124
+ - **No context cascade** — pure typography primitive. Variants don't propagate through children.
125
+ - **Underpins other components.** `<CardTitle>`, `<Alert>`'s title, `<PageHeader>`, `<EmptyState>`, `<SectionHeader>` all render Text internally with specific variants. Don't wrap another Text inside them.
126
+ - **`as` overrides element only, not variant.** Visual weight stays. Use to demote semantics when you have a heading ancestor.
127
+
128
+ See `foundations/typography.md` for the full type scale, line-height tokens, font stack.
129
+
130
+ ## Rules
131
+
132
+ - Use Text for every visible text element. Don't write raw `<h1>` / `<p>` / `<span>` with manual classes.
133
+ - Pick variant by semantic intent, not visual size. `heading-xl` is an `<h2>` — use it for section headings, not because you want big text in a body paragraph (use `as="div"` for that).
134
+ - Don't wrap a `<Text>` inside `<CardTitle>` / `<Alert>` title — they already render Text internally.
135
+ - `label-*` and `overline` variants are automatically uppercase. Don't add `uppercase` class.
136
+ - For form labels paired with controls, use `<Label htmlFor>` from `/ui/label`. `Text variant="label-plain-*"` is for non-form labels.
137
+ - Color overrides go through semantic tokens (`text-fg-muted`, `text-fg`, `text-fg-subtle`). Never raw Tailwind palette utilities.
138
+ - Don't use Text inside another semantic heading — it produces nested headings that break screen-reader navigation.
139
+ - For inline code, use `<Code>`. `Text variant="code"` works but Code is the dedicated primitive.
@@ -0,0 +1,193 @@
1
+ # Toast
2
+
3
+ Transient floating notification. Imperative API — call from event handlers, never render JSX.
4
+
5
+ ```tsx
6
+ import { toast } from '@devalok/shilp-sutra/ui/toast'
7
+ import { Toaster } from '@devalok/shilp-sutra/ui/toaster'
8
+ ```
9
+
10
+ ## When to use
11
+
12
+ - Confirmation after an action (saved, copied, sent).
13
+ - Non-blocking error feedback (failed to save — try again).
14
+ - Async progress with success/failure resolution — use `toast.promise`.
15
+ - File upload progress — use `toast.upload`.
16
+ - Inline in-flow message that must stay visible? Use `<Alert>`.
17
+ - Page-level strip (cookie banner, account warning)? Use `<Banner>`.
18
+ - Modal interruption? Use `<Dialog>`.
19
+
20
+ ## Setup
21
+
22
+ Mount `<Toaster />` **once** at the app root. Every `toast.*` call routes to this single container.
23
+
24
+ ```tsx
25
+ // app/layout.tsx (Next.js) or src/App.tsx
26
+ import { Toaster } from '@devalok/shilp-sutra/ui/toaster'
27
+
28
+ export default function RootLayout({ children }) {
29
+ return (
30
+ <html>
31
+ <body>
32
+ {children}
33
+ <Toaster />
34
+ </body>
35
+ </html>
36
+ )
37
+ }
38
+ ```
39
+
40
+ Without a mounted Toaster, every `toast.*` call is a silent no-op.
41
+
42
+ ## API
43
+
44
+ ```ts
45
+ toast('Plain message') // no icon, no accent
46
+ toast.message('Same as plain') // alias
47
+ toast.success('Saved!') // green accent + check
48
+ toast.error('Failed', { description }) // red accent + X (assertive a11y)
49
+ toast.warning('Disk low') // yellow accent + triangle
50
+ toast.info('New version available') // blue accent + info
51
+ toast.loading('Saving...') // spinner, duration: Infinity
52
+ toast.promise(asyncFn, { loading, success, error }) // one toast, three states
53
+ toast.undo('Item deleted', { onUndo, duration? }) // 8s default, Undo button
54
+ toast.upload({ files, id?, onRetry?, onRemove? }) // per-file progress
55
+ toast.custom((id) => <MyComponent />, options) // escape hatch
56
+ toast.dismiss(id?) // specific or all
57
+ ```
58
+
59
+ ## Options (every method)
60
+
61
+ | Option | Type | Notes |
62
+ |---|---|---|
63
+ | `id` | `string` | Stable id — pass the same id to update / dismiss. |
64
+ | `description` | `ReactNode` | Subline under the main message. |
65
+ | `action` | `{ label, onClick }` | Right-aligned action button. |
66
+ | `cancel` | `{ label, onClick }` | Right-aligned dismiss button. |
67
+ | `duration` | `number` (ms) | Default `5000`. `Infinity` for loading toasts. |
68
+
69
+ ## Toaster props
70
+
71
+ | Prop | Type | Default | Notes |
72
+ |---|---|---|---|
73
+ | `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | `'bottom-right'` | Global default. |
74
+ | `closeButton` | `boolean` | `false` | Show X on every toast. |
75
+ | `duration` | `number` (ms) | `5000` | Global default — overridable per toast. |
76
+ | `hotkey` | `string[]` | `['altKey', 'KeyT']` | Keyboard shortcut to focus the toast region. |
77
+ | `visibleToasts` | `number` | `3` | Max stacked toasts; older ones move to a "+N" stack. |
78
+
79
+ ## Examples
80
+
81
+ **Confirmation after save:**
82
+ ```tsx
83
+ async function handleSave() {
84
+ await api.save(data)
85
+ toast.success('Changes saved')
86
+ }
87
+ ```
88
+
89
+ **Error with description:**
90
+ ```tsx
91
+ toast.error('Upload failed', {
92
+ description: 'File is larger than 10 MB.',
93
+ })
94
+ ```
95
+
96
+ **Async with three states (`toast.promise`):**
97
+ ```tsx
98
+ toast.promise(
99
+ api.publishPost(post),
100
+ {
101
+ loading: 'Publishing post...',
102
+ success: 'Published',
103
+ error: (err) => `Failed: ${err.message}`,
104
+ }
105
+ )
106
+ ```
107
+
108
+ One toast, transitions loading → success / error. No manual `.loading()` + `.success()` choreography.
109
+
110
+ **Undo pattern (soft-delete):**
111
+ ```tsx
112
+ function deleteTask(task) {
113
+ const snapshot = task
114
+ setTasks((prev) => prev.filter((t) => t.id !== task.id))
115
+ toast.undo('Task deleted', {
116
+ onUndo: () => setTasks((prev) => [...prev, snapshot]),
117
+ })
118
+ }
119
+ ```
120
+
121
+ Default 8s duration — gives the user time to react.
122
+
123
+ **File upload progress:**
124
+ ```tsx
125
+ const id = 'upload-' + Date.now()
126
+
127
+ toast.upload({
128
+ id,
129
+ files: [
130
+ { id: 'f1', name: 'doc.pdf', size: 1_200_000, status: 'uploading', progress: 0 },
131
+ ],
132
+ })
133
+
134
+ xhr.onprogress = (e) => {
135
+ toast.upload({
136
+ id,
137
+ files: [{ id: 'f1', name: 'doc.pdf', size: 1_200_000, status: 'uploading', progress: (e.loaded / e.total) * 100 }],
138
+ })
139
+ }
140
+
141
+ xhr.onload = () => {
142
+ toast.upload({
143
+ id,
144
+ files: [{ id: 'f1', name: 'doc.pdf', size: 1_200_000, status: 'complete', progress: 100 }],
145
+ })
146
+ }
147
+ ```
148
+
149
+ Passing the same `id` updates the existing toast in place — no flicker.
150
+
151
+ **Custom action:**
152
+ ```tsx
153
+ toast('Project archived', {
154
+ action: { label: 'Open', onClick: () => router.push(`/archive/${id}`) },
155
+ })
156
+ ```
157
+
158
+ **Loading + manual resolution:**
159
+ ```tsx
160
+ const id = toast.loading('Compiling...')
161
+ try {
162
+ await compile()
163
+ toast.success('Compiled', { id }) // replaces the loading toast
164
+ } catch (err) {
165
+ toast.error('Compile failed', { id, description: err.message })
166
+ }
167
+ ```
168
+
169
+ ## Accessibility
170
+
171
+ - `toast.error` uses `aria-live="assertive"` — screen readers interrupt the current announcement.
172
+ - Other variants use `aria-live="polite"` — announced after current speech.
173
+ - `<Toaster hotkey={['altKey', 'KeyT']}>` lets keyboard users jump to the toast region.
174
+
175
+ ## Composability
176
+
177
+ - **One Toaster, many toasts:** Mount once in the root layout. Don't mount per-route — toasts will disappear on navigation.
178
+ - **Imperative only:** No JSX render path. Call `toast.*` from event handlers, async flows, error boundaries.
179
+ - **z-toast (top layer):** Sits above Dialog, Popover, everything. Don't wrap Toaster in a stacking context.
180
+ - **SSR:** Toaster is marked `'use client'` — renders nothing on the server, hydrates on mount.
181
+
182
+ See `foundations/motion.md` for the spring enter/exit, `foundations/color.md` for accent-bar colors.
183
+
184
+ ## Rules
185
+
186
+ - Mount `<Toaster />` exactly once, at the app root. Multiple Toasters double-render every toast.
187
+ - Never call `toast()` in render. Always from a handler, effect, or async function.
188
+ - Don't use the removed `useToast()` hook or `toast({ title, color })` object syntax — both were removed in 0.18.
189
+ - Use `toast.promise` for async flows — manual loading → success choreography is error-prone.
190
+ - For undo affordances, use `toast.undo` — gives the consistent 8s duration plus the styled button.
191
+ - Don't use Toast for content the user must read — they auto-dismiss. Use Alert or Banner instead.
192
+ - Pass a stable `id` when you want to update / replace a toast — without an id, repeated calls stack.
193
+ - `toast.error` is assertive — don't use it for soft warnings. Use `toast.warning`.
@@ -0,0 +1,128 @@
1
+ # Color
2
+
3
+ The kit ships an OKLCH 12-step ramp per color family (`1`=app-bg, `9`=solid/accent, `12`=hi-contrast text) plus semantic role tokens that map onto those steps. **Use the semantic role.** Reach for the numeric step only when authoring custom states.
4
+
5
+ ## Philosophy
6
+
7
+ - ~90% of surfaces should be neutral. Accent is for the primary CTA and ~1 emphasis per screen.
8
+ - Never write hex / rgb / hsl. Never use Tailwind's stock palette utilities (`text-zinc-*`, `bg-blue-*`). They don't dark-mode-flip, they aren't themable.
9
+ - Status color (error / success / warning / info) is reserved for state communication, not for decoration.
10
+
11
+ ## Decision tree — "what color do I use?"
12
+
13
+ ```
14
+ Surface (background of a region)?
15
+ → page bg → bg-surface-base
16
+ → card / widget / panel → bg-surface-raised
17
+ → sidebar / topbar → bg-surface-sunken
18
+ → dialog / popover / dropdown / input → bg-surface-overlay
19
+ → tooltip → bg-surface-inverted
20
+ → disabled control → bg-surface-disabled
21
+
22
+ Text on a surface?
23
+ → primary body / heading → text-fg
24
+ → secondary / metadata → text-fg-muted
25
+ → tertiary / hint → text-fg-subtle
26
+ → on inverted bg → text-surface-inverted-fg
27
+ → on accent-9 / solid → text-accent-fg
28
+ → on error / success / warning / info solid → text-{error|success|warning|info}-fg
29
+
30
+ Border?
31
+ → standard hairline → border-surface-border
32
+ → emphasized → border-surface-border-strong
33
+ → subtle divider → border-surface-border-subtle
34
+ → state (form invalid) → border-error-7 (or use Input state="error" instead)
35
+
36
+ Status?
37
+ → error → bg-error-3 / text-error-11 / border-error-7
38
+ → success → bg-success-3 / text-success-11 / border-success-7
39
+ → warning → bg-warning-3 / text-warning-11 / border-warning-7
40
+ → info → bg-info-3 / text-info-11 / border-info-7
41
+
42
+ Brand emphasis?
43
+ → primary CTA → use <Button> default (no className needed)
44
+ → small accent tint → bg-accent-3 / text-accent-11
45
+ → link → text-link (auto-handles hover + visited)
46
+ ```
47
+
48
+ ## Semantic surface tokens
49
+
50
+ | Token | Use |
51
+ |---|---|
52
+ | `surface-base` | Page background. The "back wall" of the app. |
53
+ | `surface-raised` | Cards, widgets, panels, anything that sits **on** the page. |
54
+ | `surface-raised-hover` | Hover state on a `surface-raised` element. |
55
+ | `surface-raised-active` | Pressed / active state. |
56
+ | `surface-sunken` | Shell chrome (sidebar, topbar), board columns, segmented-control tracks. |
57
+ | `surface-overlay` | Floating layers — dialogs, popovers, dropdowns, inputs, toasts. |
58
+ | `surface-inverted` | Tooltips, inverted badges. Pair with `surface-inverted-fg`. |
59
+ | `surface-disabled` | Disabled controls. Pair with `surface-fg-disabled`. |
60
+
61
+ Available as: `bg-surface-*`, `text-surface-*`, `border-surface-*`.
62
+
63
+ See `foundations/surfaces.md` for elevation rules and the shadow pairing matrix.
64
+
65
+ ## Foreground / text tokens
66
+
67
+ | Token | Use |
68
+ |---|---|
69
+ | `text-fg` | Primary body + headings. |
70
+ | `text-fg-muted` | Secondary content, metadata, helper text. |
71
+ | `text-fg-subtle` | Tertiary content, placeholder text. |
72
+ | `text-surface-inverted-fg` | Text on `surface-inverted`. |
73
+ | `text-accent-fg` | Text on `bg-accent-9` (solid brand). |
74
+ | `text-{error\|success\|warning\|info}-fg` | Text on the solid color background. Brand-swap safe. |
75
+ | `text-link` | Anchor color (auto-hover + visited). |
76
+
77
+ ## Border tokens
78
+
79
+ | Token | Use |
80
+ |---|---|
81
+ | `border-surface-border` | Default 1px line. |
82
+ | `border-surface-border-strong` | Emphasized line — section dividers, table headers. |
83
+ | `border-surface-border-subtle` | Hairline, lower contrast than default. |
84
+
85
+ ## Accent ramp (12 steps)
86
+
87
+ Available as `accent-1` … `accent-12`. Apply via `bg-accent-N`, `text-accent-N`, `border-accent-N`. Steps map to roles:
88
+
89
+ | Step | Role |
90
+ |---|---|
91
+ | 1 | App background tint |
92
+ | 2 | Subtle background |
93
+ | 3 | Component background (e.g. soft Button rest) |
94
+ | 4 | Hover bg |
95
+ | 5 | Active / pressed bg |
96
+ | 6 | Border subtle |
97
+ | 7 | Border default |
98
+ | 8 | Border strong |
99
+ | 9 | Solid fill (primary CTA, badge solid) |
100
+ | 10 | Solid hover |
101
+ | 11 | Lo-contrast text |
102
+ | 12 | Hi-contrast text |
103
+
104
+ The same 12-step pattern repeats for `error-*`, `success-*`, `warning-*`, `info-*` (subset: 2, 3, 4, 5, 6, 7, 9, 10, 11).
105
+
106
+ ## Theming — never hardcode
107
+
108
+ Consumers swap the accent by overriding `--color-accent-1` through `--color-accent-12` in a `:root { }` block placed **after** the kit's CSS import. Dark mode is derived algorithmically — no separate dark overrides needed.
109
+
110
+ ```css
111
+ /* In consumer's global.css, after @import "@devalok/shilp-sutra/css"; */
112
+ :root {
113
+ --color-accent-9: oklch(0.55 0.18 230); /* swap brand to blue */
114
+ /* … the kit will compute related steps via OKLCH curves */
115
+ }
116
+ ```
117
+
118
+ ## Rules
119
+
120
+ - **Never** `bg-white` / `bg-black` / `bg-zinc-*` / `bg-pink-*`. Use semantic tokens.
121
+ - **Never** put a color directly on a Button via className. Set `color="error"` (or whichever).
122
+ - **Never** combine `border-*` and `shadow-*` tokens — shadow tokens contain a ring layer.
123
+ - **Never** use `text-fg` on `bg-accent-9`. Use `text-accent-fg` (brand-swap safe).
124
+ - **Never** invent step numbers. Steps 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12 exist. Step 8 exists only on accent / neutral.
125
+
126
+ ## Forced-colors / Windows high-contrast
127
+
128
+ The kit auto-remaps every semantic color to system keywords (`Canvas`, `CanvasText`, `Highlight`, `LinkText`, `GrayText`) under `@media (forced-colors: active)`. As long as you use semantic tokens, high-contrast mode just works. Hardcoded hex breaks it.
@@ -0,0 +1,81 @@
1
+ # Dark mode
2
+
3
+ `.dark` class on `<html>` or `<body>` flips every token. No theme provider, no JS reshuffle.
4
+
5
+ ## How it works
6
+
7
+ The kit declares a custom variant: `@custom-variant dark (&:where(.dark *))`. Identical semantics to Tailwind's old `darkMode: 'class'`. Adding `.dark` higher in the DOM cascades every semantic token to its dark counterpart.
8
+
9
+ Dark tokens are **algorithmically derived** from the OKLCH primitives, not hand-tuned hex overrides. This means:
10
+
11
+ - Surfaces *lighten* with elevation in dark mode (opposite of light mode). `surface-overlay` is brighter than `surface-base` when `.dark` is active.
12
+ - Brand swaps (consumer overrides `--color-accent-9`) produce a coherent dark palette without extra work.
13
+ - Status colors stay legible — solid backgrounds darken slightly (L=0.63→0.54 in dark mode) to maintain WCAG AA.
14
+
15
+ ## Wiring the toggle
16
+
17
+ ```tsx
18
+ import { useColorMode } from '@devalok/shilp-sutra/hooks/use-color-mode'
19
+
20
+ function ThemeToggle() {
21
+ const { mode, resolvedMode, setMode } = useColorMode()
22
+ // mode: 'light' | 'dark' | 'system'
23
+ // resolvedMode: 'light' | 'dark' (after resolving 'system')
24
+
25
+ return (
26
+ <Button
27
+ variant="soft"
28
+ onClick={() => setMode(resolvedMode === 'dark' ? 'light' : 'dark')}
29
+ >
30
+ <Icon icon={resolvedMode === 'dark' ? IconSun : IconMoon} />
31
+ {resolvedMode === 'dark' ? 'Light' : 'Dark'}
32
+ </Button>
33
+ )
34
+ }
35
+ ```
36
+
37
+ `useColorMode()` handles:
38
+ - Persisting to `localStorage` (`shilp-sutra-color-mode`)
39
+ - Listening to `prefers-color-scheme` when mode is `'system'`
40
+ - Syncing across browser tabs
41
+ - Adding/removing `.dark` on `<html>`
42
+
43
+ ## What you have to do per-component
44
+
45
+ Nothing. Semantic tokens flip automatically. As long as you use `bg-surface-raised` not `bg-white`, dark mode works.
46
+
47
+ The only exception: **images, illustrations, decorative SVGs**. These don't flip. Author them as theme-aware:
48
+
49
+ ```tsx
50
+ {resolvedMode === 'dark' ? <img src="/logo-dark.svg" /> : <img src="/logo-light.svg" />}
51
+ ```
52
+
53
+ Or use a single CSS-variable-driven SVG (`fill="currentColor"` + `text-fg`).
54
+
55
+ ## Forced-colors (Windows high-contrast)
56
+
57
+ Independent of `.dark`. Activated by the OS. Every semantic color remaps to system keywords (`Canvas`, `CanvasText`, `Highlight`, `LinkText`, `GrayText`) under `@media (forced-colors: active)`. No work needed if you use semantic tokens.
58
+
59
+ ## Initial load — avoid the flash
60
+
61
+ Set the mode **before** React hydrates. Insert this script tag in `<head>` (Next.js: `app/layout.tsx`):
62
+
63
+ ```html
64
+ <script>
65
+ (function () {
66
+ var m = localStorage.getItem('shilp-sutra-color-mode') || 'system';
67
+ var dark = m === 'dark' || (m === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
68
+ if (dark) document.documentElement.classList.add('dark');
69
+ })();
70
+ </script>
71
+ ```
72
+
73
+ Without this, light mode flashes on first paint when the user prefers dark.
74
+
75
+ ## Rules
76
+
77
+ - **Never** hardcode hex / Tailwind palette colors. They don't dark-mode flip.
78
+ - **Never** write a `dark:bg-*` override on a semantic token — the token already handles dark mode.
79
+ - **For images**, use conditional rendering off `resolvedMode`.
80
+ - **Wire the head script** to prevent the FOUC on first paint.
81
+ - Default to `'system'` mode unless the product is explicitly light-only or dark-only.
@@ -0,0 +1,107 @@
1
+ # Icons
2
+
3
+ The kit uses **Tabler Icons** exclusively. Custom icon libraries are not supported.
4
+
5
+ ## Install
6
+
7
+ `@tabler/icons-react` is declared as a peer dependency:
8
+
9
+ ```bash
10
+ npm i @tabler/icons-react
11
+ ```
12
+
13
+ ## Import + use
14
+
15
+ ```tsx
16
+ import { Icon } from '@devalok/shilp-sutra/ui/icon'
17
+ import { IconHome, IconUser, IconSettings } from '@tabler/icons-react'
18
+
19
+ <Icon icon={IconHome} />
20
+ <Icon icon={IconUser} size={20} />
21
+ <Icon icon={IconSettings} className="text-fg-muted" />
22
+ ```
23
+
24
+ Or pass the component directly to a prop that accepts `IconInput`:
25
+
26
+ ```tsx
27
+ <Button startIcon={IconPlus}>Add</Button>
28
+ <Badge icon={IconCheck}>Verified</Badge>
29
+ ```
30
+
31
+ ## `IconInput` (universal icon prop type)
32
+
33
+ Every icon-accepting prop across the kit (`startIcon`, `endIcon`, `icon`, `leftIcon`, `rightIcon`) takes `IconInput`. Four shapes work interchangeably:
34
+
35
+ ```tsx
36
+ // 1. Component reference (Tabler component)
37
+ <Button startIcon={IconPlus} />
38
+
39
+ // 2. Rendered <Icon> element
40
+ <Button startIcon={<Icon icon={IconPlus} />} />
41
+
42
+ // 3. Rendered Tabler element directly
43
+ <Button startIcon={<IconPlus />} />
44
+
45
+ // 4. Any custom node (svg, span with bg)
46
+ <Button startIcon={<MyCustomIcon />} />
47
+ ```
48
+
49
+ Prefer shape 1 (component ref) — the kit applies the right size and color via context.
50
+
51
+ ## Sizing via `IconProvider`
52
+
53
+ Don't add `className="h-4 w-4"` on every Icon. Wrap a subtree:
54
+
55
+ ```tsx
56
+ import { IconProvider } from '@devalok/shilp-sutra/ui/icon-context'
57
+
58
+ <IconProvider size={16}>
59
+ <NavSection>
60
+ <Icon icon={IconHome} />
61
+ <Icon icon={IconUser} />
62
+ {/* all icons here = 16px */}
63
+ </NavSection>
64
+ </IconProvider>
65
+ ```
66
+
67
+ Override per-icon with `<Icon icon={...} size={24} />`.
68
+
69
+ Default sizes by component context:
70
+
71
+ | Context | Default size |
72
+ |---|---|
73
+ | Button (md) | 16 |
74
+ | Button (sm / xs) | 14 |
75
+ | Button (lg / xl) | 18 / 20 |
76
+ | Input (any size) | 16 |
77
+ | Badge | 12 |
78
+ | Standalone | 16 (recommended) |
79
+
80
+ ## Color
81
+
82
+ Icons inherit `currentColor` by default. To color one:
83
+
84
+ ```tsx
85
+ <Icon icon={IconAlertTriangle} className="text-warning-11" />
86
+ <Icon icon={IconCheck} className="text-success-11" />
87
+ ```
88
+
89
+ Inside a `<Button>` or `<Badge>`, color is inherited from the button/badge — don't override.
90
+
91
+ ## Multi-color / branded icons (OAuth, etc.)
92
+
93
+ OAuthButton ships brand glyphs for 13 providers. To use a brand's official multi-color SVG, pass `icon` to override:
94
+
95
+ ```tsx
96
+ <OAuthButton provider="google" icon={<GoogleColorSVG />} />
97
+ ```
98
+
99
+ For custom branded icons elsewhere, use raw `<svg>` — `<Icon>` is for Tabler outline glyphs.
100
+
101
+ ## Rules
102
+
103
+ - **Tabler only** — no lucide-react, no heroicons, no mui-icons. The kit is sized for Tabler's stroke weight.
104
+ - **Use `<Icon icon={...} />`** — don't render Tabler components directly when inside a kit component that accepts `IconInput`.
105
+ - **Don't size with className** (`h-4 w-4`) — use `size` prop or `IconProvider`.
106
+ - **Don't color icons inside Button / Badge** — they inherit from the parent. Color only on standalone icons.
107
+ - **Don't pass a string** (`"check"`) — `IconInput` excludes strings/numbers. Always a component or element.