@devalok/shilp-sutra 0.51.0 → 0.53.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 (81) hide show
  1. package/AGENTS.md +1 -1
  2. package/MIGRATION.md +12 -0
  3. package/dist/_chunks/motion-provider.js +7 -6
  4. package/dist/_chunks/motion-provider.js.map +1 -1
  5. package/dist/_chunks/success.js +53 -53
  6. package/dist/ai/command-bar.js +158 -158
  7. package/dist/ai/command-bar.js.map +1 -1
  8. package/dist/ai/conversation.js +5 -5
  9. package/dist/composed/command-palette.js +17 -17
  10. package/dist/composed/command-palette.js.map +1 -1
  11. package/dist/composed/priority-indicator.d.ts +15 -6
  12. package/dist/composed/priority-indicator.d.ts.map +1 -1
  13. package/dist/composed/priority-indicator.js +37 -88
  14. package/dist/composed/priority-indicator.js.map +1 -1
  15. package/dist/composed/schedule-view.d.ts +17 -2
  16. package/dist/composed/schedule-view.d.ts.map +1 -1
  17. package/dist/composed/schedule-view.js +163 -64
  18. package/dist/composed/schedule-view.js.map +1 -1
  19. package/dist/motion/motion-provider.d.ts.map +1 -1
  20. package/dist/shell/bottom-navbar.d.ts +32 -6
  21. package/dist/shell/bottom-navbar.d.ts.map +1 -1
  22. package/dist/shell/bottom-navbar.js +156 -129
  23. package/dist/shell/bottom-navbar.js.map +1 -1
  24. package/dist/tokens/primitives.css +6 -2
  25. package/dist/tokens/semantic.css +20 -1
  26. package/dist/tokens/utilities.css +6 -0
  27. package/dist/ui/autocomplete.d.ts +34 -32
  28. package/dist/ui/autocomplete.d.ts.map +1 -1
  29. package/dist/ui/autocomplete.js +115 -110
  30. package/dist/ui/autocomplete.js.map +1 -1
  31. package/dist/ui/button-group.js +2 -0
  32. package/dist/ui/button-group.js.map +1 -1
  33. package/dist/ui/button-processing.d.ts.map +1 -1
  34. package/dist/ui/button-processing.js +1 -0
  35. package/dist/ui/button-processing.js.map +1 -1
  36. package/dist/ui/button.d.ts +3 -3
  37. package/dist/ui/button.d.ts.map +1 -1
  38. package/dist/ui/button.js +26 -0
  39. package/dist/ui/button.js.map +1 -1
  40. package/dist/ui/color-input.d.ts.map +1 -1
  41. package/dist/ui/color-input.js +82 -82
  42. package/dist/ui/color-input.js.map +1 -1
  43. package/dist/ui/combobox.js +4 -4
  44. package/dist/ui/icon.d.ts +2 -0
  45. package/dist/ui/icon.d.ts.map +1 -1
  46. package/dist/ui/icon.js +31 -26
  47. package/dist/ui/icon.js.map +1 -1
  48. package/dist/ui/index.js +30 -30
  49. package/dist/ui/search-input.d.ts.map +1 -1
  50. package/dist/ui/search-input.js +5 -4
  51. package/dist/ui/search-input.js.map +1 -1
  52. package/dist/ui/segmented-control.d.ts +28 -6
  53. package/dist/ui/segmented-control.d.ts.map +1 -1
  54. package/dist/ui/segmented-control.js +61 -43
  55. package/dist/ui/segmented-control.js.map +1 -1
  56. package/dist/ui/sidebar.js +7 -7
  57. package/dist/ui/split-button.d.ts.map +1 -1
  58. package/dist/ui/split-button.js +7 -0
  59. package/dist/ui/split-button.js.map +1 -1
  60. package/docs/components/composed/priority-indicator.md +22 -11
  61. package/docs/components/composed/schedule-view.md +20 -5
  62. package/docs/components/shell/bottom-navbar.md +24 -5
  63. package/docs/components/ui/autocomplete.md +26 -10
  64. package/docs/components/ui/button.md +2 -2
  65. package/docs/components/ui/segmented-control.md +31 -11
  66. package/docs/recipes/install-remix.md +3 -3
  67. package/docs/recipes/install-vite.md +3 -3
  68. package/docs/recipes/server-components.md +2 -2
  69. package/llms.txt +1 -1
  70. package/make-kit/foundations/color.md +20 -0
  71. package/make-kit/foundations/dark-mode.md +6 -9
  72. package/make-kit/foundations/icons.md +3 -3
  73. package/make-kit/setup.md +4 -4
  74. package/mcp-manifest.json +239 -35
  75. package/package.json +1 -1
  76. package/scripts/welcome.mjs +59 -1
  77. package/skill/SKILL.md +1 -1
  78. package/skill/references/components.md +1 -1
  79. package/skill/references/server-components.md +2 -2
  80. package/skill/references/setup-remix.md +3 -3
  81. package/skill/references/setup-vite.md +3 -3
@@ -6,16 +6,23 @@
6
6
 
7
7
  ## Props
8
8
  currentPath?: string (optional)
9
- user?: BottomNavbarUser | null (optional)
9
+ user?: BottomNavbarUser | null (drives per-item role gating, optional)
10
10
  primaryItems?: BottomNavItem[] (max 4 recommended, optional)
11
- moreItems?: BottomNavItem[] (overflow items in "More" menu, optional)
11
+ moreItems?: BottomNavItem[] (overflow items in "More" sheet, optional)
12
+ indicator?: 'pill' | 'underline' | 'tint' | 'none' (active-item indicator; default 'pill')
13
+ labelVisibility?: 'always' | 'selected'
12
14
  className?: string
13
15
 
14
- BottomNavItem: { title: string, href: string, icon: ReactNode, exact?: boolean, badge?: number }
16
+ BottomNavItem: { title: string, href: string, icon: IconInput, activeIcon?: IconInput, exact?: boolean, badge?: number, roles?: string[], canView?: (user: BottomNavbarUser | null) => boolean }
15
17
  BottomNavbarUser: { name: string, role?: string }
16
18
 
17
19
  ## Defaults
18
- None
20
+ currentPath: '/'
21
+ user: null
22
+ primaryItems: []
23
+ moreItems: []
24
+ indicator: 'pill'
25
+ labelVisibility: 'always'
19
26
 
20
27
  ## Example
21
28
  ```jsx
@@ -37,7 +44,11 @@ BottomNavbarUser: { name: string, role?: string }
37
44
  ```
38
45
  - **Primary vs overflow:** `primaryItems` (max 4) for the always-visible slots; `moreItems` go into a "More" sheet that opens on tap. Don't exceed 4 primary — the bar becomes cramped.
39
46
  - **Router integration via LinkProvider:** Each nav item is rendered using the framework-specific Link component registered in LinkProvider. Without LinkProvider, you get full-page reloads on tap.
40
- - **Badge numbers** cap at 99+ (same as BadgeIndicator pattern).
47
+ - **Badge numbers** cap at 99+ (composes the `Badge` primitive).
48
+ - **Role gating:** each item may declare `roles: string[]` (shown only when `user.role` matches) or a `canView(user)` predicate for arbitrary logic (`canView` wins). Items with neither are always visible. Gating applies to both `primaryItems` and `moreItems`.
49
+ - **Overflow sheet:** the "More" surface is the DS `Sheet` (`side="bottom"`) — it inherits focus trap, scroll lock, return-focus, and `aria-modal`; the trigger is wired with `aria-haspopup`/`aria-controls` automatically.
50
+ - **Indicator (animated):** the active indicator slides to the selected item (shared-element `layoutId`) and fades in on first appearance. Modes: `pill` (default, Material-3 tonal pill behind the icon), `underline` (top accent bar), `tint` (subtle bg on the whole active cell), `none` (no shape — pair with `activeIcon` for the iOS filled-icon look). `labelVisibility="selected"` shows labels only for the active item.
51
+ - **Filled-when-active:** set `activeIcon` on an item (e.g. a Tabler `*Filled` variant) to swap the icon for a filled version while it's the active route; falls back to `icon`.
41
52
  - **Not for desktop:** The viewport-fixed positioning + touch-optimized sizing don't translate well to desktop. Hide behind `md:hidden`.
42
53
 
43
54
  ## Gotchas
@@ -47,6 +58,14 @@ BottomNavbarUser: { name: string, role?: string }
47
58
  - Requires LinkProvider for framework-specific link components (e.g., Next.js Link)
48
59
 
49
60
  ## Changes
61
+ ### v0.53.0
62
+ - **Changed** Overflow "More" menu re-founded on the DS `Sheet` primitive — inherits focus trap, scroll lock, return-focus, `aria-modal`, and trigger↔panel ARIA wiring (was a hand-rolled `role="dialog"` with none of these). Composes `Badge` for notification counts and the Sheet's built-in close (≥ touch target).
63
+ - **Added** Per-item role gating: `roles?: string[]` and `canView?: (user) => boolean` on `BottomNavItem`. The previously-inert `user` prop now drives it.
64
+ - **Added** `activeIcon` per item — a filled/alternate icon shown while active (falls back to `icon`). Tightened the icon lozenge padding so icon-only items read less airy.
65
+ - **Added** `indicator` (default **`pill`** — Material-3; plus `underline`, `tint`, `none`) and `labelVisibility` ('always' | 'selected'). The active indicator animates (slides) between items via a shared-element `layoutId`.
66
+ - **Added** Label truncation + logical (RTL-safe) properties; overflow grid adapts to item count.
67
+ - **Fixed** Notification badge `zoom-in` animation now reduced-motion gated.
68
+
50
69
  ### v0.19.0
51
70
  - **Changed** Background elevated from `bg-surface-1` to `bg-surface-2` for visual hierarchy above app background
52
71
  - **Changed** "More" menu and interactive items bumped accordingly
@@ -6,16 +6,23 @@
6
6
 
7
7
  ## Props
8
8
  options: AutocompleteOption[] (REQUIRED) — { value: string, label: string }
9
- value: AutocompleteOption | null
9
+ value?: AutocompleteOption | null (controlled)
10
+ defaultValue?: AutocompleteOption | null (uncontrolled initial)
10
11
  onValueChange?: (option: AutocompleteOption) => void
11
- placeholder: string
12
- emptyText: string (default: "No options")
13
- disabled: boolean
14
- className: string
15
- id: string
12
+ placeholder?: string
13
+ emptyText?: string (default: "No options")
14
+ disabled?: boolean
15
+ size?: (forwarded to Input)
16
+ state?: 'default' | 'error' | 'warning' | 'success' (forwarded to Input)
17
+ isLoading?: boolean
18
+ loadingText?: string (default: "Loading…")
19
+ renderOption?: (option, query) => ReactNode
20
+ className?: string
21
+ id?: string
16
22
 
17
23
  ## Defaults
18
24
  emptyText="No options"
25
+ loadingText="Loading…"
19
26
 
20
27
  ## Example
21
28
  ```jsx
@@ -28,17 +35,26 @@
28
35
  ```
29
36
 
30
37
  ## Composability
31
- - **Autocomplete vs Combobox:** Autocomplete allows free-text input (users can type anything); Combobox enforces selection from the list. Pick by whether "off-list" values are valid (e.g. city field that accepts typos → Autocomplete; tag picker from a fixed vocabulary → Combobox).
32
- - **Value shape is an object** (`{ value, label }`), not a plain string this preserves label/value decoupling for display-vs-storage.
33
- - **FormField:** Does NOT auto-consume FormField state. Set explicit error styling via className if needed.
38
+ - **Composes `Input`** the field is the DS `Input`, so it inherits `size`, error/`state` painting, read-only, hover, and FormField wiring. Autocomplete owns only the dropdown + behavior.
39
+ - **Autocomplete vs Combobox:** Autocomplete allows free-text input (users can type anything); Combobox enforces selection from the list.
40
+ - **Value shape is an object** (`{ value, label }`), not a plain string. Controlled via `value`, or uncontrolled via `defaultValue`.
41
+ - **FormField:** auto-consumes FormField state (via the composed Input) — inside a FormField, error border + `aria-invalid`/`aria-describedby`/`required` are wired automatically. Pass `state` to override.
42
+ - **Async / "type to search":** set `isLoading` to show a spinner (in the field + the listbox) with `loadingText`.
43
+ - **Matched-text highlight:** the query substring is bolded in each option by default; override the whole row with `renderOption`.
34
44
  - **Portal rendering:** Dropdown portals to body with z-popover (1400) — stacks above Dialog/Sheet.
35
- - **Keyboard:** ArrowDown/Up navigate suggestions, Enter selects, Esc closes. Typeahead is the input's native filtering.
45
+ - **Keyboard:** ArrowDown/Up/Home/End navigate, Enter selects, Esc closes.
36
46
 
37
47
  ## Gotchas
38
48
  - Allows free-text input (no forced selection) — use Combobox for forced selection
39
49
  - value is an object { value, label }, NOT just a string
50
+ - Client-side filtering only (known list). For huge/remote lists, drive `options` yourself with `isLoading` — no built-in virtualization.
40
51
 
41
52
  ## Changes
53
+ ### v0.53.0
54
+ - **Changed** Re-parented onto the DS `Input` primitive — inherits `size`, error/`state` painting, read-only, hover, and FormField auto-consumption (previously re-rolled its own `<input>` and read FormField error but never painted it).
55
+ - **Added** `defaultValue` (uncontrolled), `size`, `state`, `isLoading`/`loadingText` (async), `renderOption`, and matched-substring highlighting.
56
+ - **Fixed** Doc corrected — it DOES auto-consume FormField (via Input). Dropped a keystroke-frequency stagger animation + a dead effect.
57
+
42
58
  ### v0.18.0
43
59
  - **Fixed** Added `useEffect` to sync query when external value changes
44
60
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## Props
8
8
  variant: "solid" | "soft" | "outline" | "ghost" | "link"
9
- color: "accent" | "error" | "success" | "warning" | "neutral"
9
+ color: "accent" | "error" | "success" | "warning" | "info" | "neutral"
10
10
  size: "xs" | "sm" | "md" | "lg" | "compact-xs" | "compact-sm" | "compact-md" | "icon" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg"
11
11
  weight: "semibold" | "normal"
12
12
  shape: "default" | "pill"
@@ -17,7 +17,7 @@
17
17
  fullWidth: boolean
18
18
  asChild: boolean
19
19
  processing: boolean | 'ambient' | 'working' | 'urgent' (marching ants SVG border)
20
- processingColor: 'accent' | 'error' | 'success' | 'warning' | 'neutral' (override animation color)
20
+ processingColor: 'accent' | 'error' | 'success' | 'warning' | 'info' | 'neutral' (override animation color)
21
21
  processingDisabled: boolean (disable button during processing, default: true)
22
22
  onClickAsync: (e: MouseEvent) => Promise<void> (auto loading->success/error->idle, auto-activates processing)
23
23
  asyncFeedbackDuration: number (ms, default 1500)
@@ -6,32 +6,37 @@
6
6
 
7
7
  ## Props
8
8
  size: "sm" | "md" | "lg"
9
- variant: "default" | "solid"
9
+ variant: "soft" | "solid"
10
10
  options: SegmentedControlOption[] (REQUIRED)
11
- selectedId: string (REQUIRED)
12
- onSelect: (id: string) => void (REQUIRED)
11
+ value: string // controlled
12
+ defaultValue: string // uncontrolled initial
13
+ onValueChange: (id: string) => void
13
14
  disabled: boolean
15
+ fullWidth: boolean
16
+ selectedId: string // @deprecated — use value
17
+ onSelect: (id: string) => void // @deprecated — use onValueChange
14
18
 
15
19
  ## Types
16
- SegmentedControlOption = { id: string, text: string, icon?: ComponentType<{ className?: string }> }
20
+ SegmentedControlOption = { id: string, text?: React.ReactNode, icon?: IconInput, ariaLabel?: string }
17
21
  SegmentedControlSize = 'sm' | 'md' | 'lg'
18
- SegmentedControlVariant = 'default' | 'solid'
22
+ SegmentedControlVariant = 'soft' | 'solid'
19
23
 
20
24
  ## Defaults
21
25
  size: "md"
22
- variant: "default"
26
+ variant: "soft"
27
+ fullWidth: false
23
28
 
24
29
  ## Example
25
30
  ```jsx
26
31
  <SegmentedControl
27
32
  size="md"
28
- variant="default"
33
+ variant="soft"
29
34
  options={[
30
35
  { id: 'list', text: 'List' },
31
36
  { id: 'grid', text: 'Grid' },
32
37
  ]}
33
- selectedId={viewMode}
34
- onSelect={setViewMode}
38
+ value={viewMode}
39
+ onValueChange={setViewMode}
35
40
  />
36
41
  ```
37
42
 
@@ -39,15 +44,30 @@
39
44
  - **Data-driven, not compound** — unlike Tabs/ToggleGroup, SegmentedControl takes an `options` array rather than children. This makes it easier to render from a list but harder to customize per-option styling; use Tabs if you need compound children.
40
45
  - **When to use vs Tabs:** SegmentedControl is for mutually-exclusive VIEW-MODE toggles (List/Grid/Kanban) — short labels, no associated content panel. Tabs is for content switching where each tab has a corresponding TabsContent. SegmentedControl renders `role="radiogroup"` with `role="radio"` segments (a panel-less single-select); Tabs renders `role="tablist"`.
41
46
  - **Option icons** auto-size based on the `size` prop — don't set explicit icon sizes.
42
- - Fully controlled there's no `defaultSelectedId`. Manage state in parent.
47
+ - **`fullWidth`** switches segments from content-hug (default) to equal-fill: each segment takes an equal share of the container (a 2-item toggle splits 50/50, a 3-item switcher gives each a third). Use for view switchers and toolbar toggles that should fill their column; leave off for compact inline toolbars.
48
+ - **Visual model:** a rounded-rect track (not a full pill) — a translucent recessed groove with a single soft-shadowed sliding thumb. The track has no border/inset shadow; the thumb carries the only edge. Elevation inverts in dark so the groove stays visible.
49
+ - **Controlled or uncontrolled** — pass `value` + `onValueChange` to control it, or `defaultValue` (optional; falls back to the first option) to let it own state. Matches the Tabs/ToggleGroup vocabulary. `selectedId`/`onSelect` are deprecated aliases that still work.
50
+ - **Option labels accept `ReactNode`** — a segment can hold a count badge or custom node, not just a string. `text` is optional: omit it for an **icon-only** segment and set `ariaLabel` so the segment still has an accessible name.
51
+ - **Touch targets** — each segment has a 44px minimum hit area (via `touch-target`) even though the visual height stays dense.
52
+ - **RTL** — Arrow-key navigation tracks reading order: in a right-to-left context `ArrowLeft` moves to the next option and `ArrowRight` to the previous (detected from the nearest `dir` attribute).
43
53
  - Built from scratch (no Radix primitive) — standard HTML buttons with `role="radio"` + `aria-checked` and roving tabindex.
44
54
 
45
55
  ## Gotchas
46
- - Controlled only — selectedId + onSelect are required
56
+ - Controlled (`value`) or uncontrolled (`defaultValue`) `selectedId`/`onSelect` are deprecated aliases
47
57
  - Uses data-driven API (options prop), not compound children
48
58
  - Use Tabs (not SegmentedControl) when you need associated content panels per option
49
59
 
50
60
  ## Changes
61
+ ### v0.52.0
62
+ - **Changed** Visual rebuild — rounded-rect track (was full pill), translucent recessed track with no border/inset, single ring-less soft-shadow thumb. Dark-mode elevation inverts so the groove stays visible. New tokens: `--color-segment-track`, `--color-segment-thumb`, `--shadow-segment`.
63
+ - **Added** `value` / `defaultValue` / `onValueChange` — canonical controlled+uncontrolled API (aligns with Tabs/ToggleGroup).
64
+ - **Added** `fullWidth` prop — segments split the container equally.
65
+ - **Added** 44px minimum touch targets (`touch-target`), keeping dense visual height.
66
+ - **Added** Crisp bounce-free thumb motion (reduced-motion aware) + `motion-safe` press-scale feedback.
67
+ - **Changed** Option `text` widened from `string` to `ReactNode`, and made optional (omit for icon-only segments).
68
+ - **Added** `ariaLabel` per option for icon-only segments; RTL-aware Arrow-key navigation.
69
+ - **Deprecated** `variant="default"` → `variant="soft"`; `selectedId` → `value`; `onSelect` → `onValueChange`. All old names still accepted as aliases; update call sites.
70
+
51
71
  ### v0.38.0
52
72
  - **Removed** (BREAKING) deprecated `variant="accent"` alias. Use `variant="solid"`.
53
73
 
@@ -127,10 +127,10 @@ For runtime toggling inside React components, use `useColorMode`:
127
127
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
128
128
 
129
129
  export function ThemeToggle() {
130
- const { mode, toggle } = useColorMode();
130
+ const { colorMode, toggleColorMode } = useColorMode();
131
131
  return (
132
- <button onClick={toggle} aria-label="Toggle theme">
133
- {mode === "dark" ? "☀" : "☾"}
132
+ <button onClick={toggleColorMode} aria-label="Toggle theme">
133
+ {colorMode === "dark" ? "☀" : "☾"}
134
134
  </button>
135
135
  );
136
136
  }
@@ -121,10 +121,10 @@ Wire the runtime hook from anywhere in the app (e.g., a header button):
121
121
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
122
122
 
123
123
  export function ThemeToggle() {
124
- const { mode, toggle } = useColorMode();
124
+ const { colorMode, toggleColorMode } = useColorMode();
125
125
  return (
126
- <button onClick={toggle} aria-label="Toggle theme">
127
- {mode === "dark" ? "☀" : "☾"}
126
+ <button onClick={toggleColorMode} aria-label="Toggle theme">
127
+ {colorMode === "dark" ? "☀" : "☾"}
128
128
  </button>
129
129
  );
130
130
  }
@@ -151,8 +151,8 @@ Both `<Suspense>` and `<LoadingSkeleton>` are server-safe.
151
151
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
152
152
 
153
153
  export default function ServerPage() {
154
- const { mode } = useColorMode(); // breaks
155
- return <div>{mode}</div>;
154
+ const { colorMode } = useColorMode(); // breaks
155
+ return <div>{colorMode}</div>;
156
156
  }
157
157
  ```
158
158
 
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @devalok/shilp-sutra
2
2
 
3
- > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.51.0.
3
+ > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.53.0.
4
4
  > Built on the same primitives as shadcn/ui but with DIFFERENT prop APIs — never guess from shadcn knowledge; verify every prop.
5
5
  > This file is a ROUTER: it tells you what exists and where to get details. Do not look for prop tables here — fetch them per component (MCP tool or per-component doc file below).
6
6
 
@@ -103,6 +103,26 @@ Available as `accent-1` … `accent-12`. Apply via `bg-accent-N`, `text-accent-N
103
103
 
104
104
  The same 12-step pattern repeats for `error-*`, `success-*`, `warning-*`, `info-*` (subset: 2, 3, 4, 5, 6, 7, 9, 10, 11).
105
105
 
106
+ ## Component `color` prop — support matrix
107
+
108
+ The `color` prop accepts a different set per component, because each serves a different job. **The shared intent set — safe to pass to all three from one token — is `accent` · `error` · `success` · `warning` · `info` · `neutral`.**
109
+
110
+ | color | Button | Card | Badge |
111
+ |---|:---:|:---:|:---:|
112
+ | `accent` | ✅ | ✅ | ✅ |
113
+ | `error` | ✅ | ✅ | ✅ |
114
+ | `success` | ✅ | ✅ | ✅ |
115
+ | `warning` | ✅ | ✅ | ✅ |
116
+ | `info` | ✅ | ✅ | ✅ |
117
+ | `neutral` | ✅ | ✅ | ✅ |
118
+ | `default` | — | ✅ | ✅ |
119
+ | `teal` `amber` `slate` `indigo` `cyan` `orange` `emerald` | — | — | ✅ |
120
+ | `custom` (`--badge-color`) | — | — | ✅ |
121
+
122
+ - **Button / Card** carry the six **semantic intents** — they communicate *state or emphasis* (a destructive action, a warning panel). That set is aligned across both.
123
+ - **Badge** adds a **category palette** (`teal`, `amber`, `indigo`, …) plus `custom`, because badges label/categorize many peer items where hue is a taxonomy, not a status. Those category hues are intentionally **not** on Button/Card — a "teal primary button" would read as an intent that doesn't exist.
124
+ - To tint a Card + Badge + Button set from one variable, constrain the token to the shared intent set above.
125
+
106
126
  ## Theming — never hardcode
107
127
 
108
128
  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.
@@ -18,17 +18,14 @@ Dark tokens are **algorithmically derived** from the OKLCH primitives, not hand-
18
18
  import { useColorMode } from '@devalok/shilp-sutra/hooks/use-color-mode'
19
19
 
20
20
  function ThemeToggle() {
21
- const { mode, resolvedMode, setMode } = useColorMode()
22
- // mode: 'light' | 'dark' | 'system'
23
- // resolvedMode: 'light' | 'dark' (after resolving 'system')
21
+ const { colorMode, setColorMode, toggleColorMode } = useColorMode()
22
+ // colorMode: 'light' | 'dark' | 'system'
23
+ // setColorMode(next): set an explicit mode · toggleColorMode(): flip light↔dark
24
24
 
25
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'}
26
+ <Button variant="soft" onClick={toggleColorMode}>
27
+ <Icon icon={colorMode === 'dark' ? IconSun : IconMoon} />
28
+ {colorMode === 'dark' ? 'Light' : 'Dark'}
32
29
  </Button>
33
30
  )
34
31
  }
@@ -17,7 +17,7 @@ import { Icon } from '@devalok/shilp-sutra/ui/icon'
17
17
  import { IconHome, IconUser, IconSettings } from '@tabler/icons-react'
18
18
 
19
19
  <Icon icon={IconHome} />
20
- <Icon icon={IconUser} size={20} />
20
+ <Icon icon={IconUser} size="lg" />
21
21
  <Icon icon={IconSettings} className="text-fg-muted" />
22
22
  ```
23
23
 
@@ -55,7 +55,7 @@ Don't add `className="h-4 w-4"` on every Icon. Wrap a subtree:
55
55
  ```tsx
56
56
  import { IconProvider } from '@devalok/shilp-sutra/ui/icon-context'
57
57
 
58
- <IconProvider size={16}>
58
+ <IconProvider size="sm">
59
59
  <NavSection>
60
60
  <Icon icon={IconHome} />
61
61
  <Icon icon={IconUser} />
@@ -64,7 +64,7 @@ import { IconProvider } from '@devalok/shilp-sutra/ui/icon-context'
64
64
  </IconProvider>
65
65
  ```
66
66
 
67
- Override per-icon with `<Icon icon={...} size={24} />`.
67
+ Override per-icon with `<Icon icon={...} size="xl" />`.
68
68
 
69
69
  Default sizes by component context:
70
70
 
package/make-kit/setup.md CHANGED
@@ -53,7 +53,7 @@ import { IconProvider } from '@devalok/shilp-sutra/ui/icon-context'
53
53
  export default function App({ children }) {
54
54
  return (
55
55
  <MotionProvider reducedMotion="user">
56
- <IconProvider size={16}>
56
+ <IconProvider size="sm">
57
57
  {children}
58
58
  <Toaster />
59
59
  </IconProvider>
@@ -63,7 +63,7 @@ export default function App({ children }) {
63
63
  ```
64
64
 
65
65
  - `MotionProvider` — required. `reducedMotion="user"` respects OS preference. Without this provider, motion primitives still work but reduced-motion is ignored.
66
- - `IconProvider` — optional but recommended. Sets default icon size for all `<Icon>` children. Override per-call with `<Icon size={20} />`.
66
+ - `IconProvider` — optional but recommended. Sets default icon size for all `<Icon>` children. Size is a tier (`"xs" | "sm" | "md" | "lg" | "xl" | "2xl"`), not a pixel number. Override per-call with `<Icon size="lg" />`.
67
67
  - `Toaster` — only needed if the app calls `toast(...)`. Mount exactly once.
68
68
 
69
69
  ## Dark mode toggle
@@ -74,9 +74,9 @@ Add the `.dark` class on `<html>` or `<body>` to flip the entire token system to
74
74
  import { useColorMode } from '@devalok/shilp-sutra/hooks/use-color-mode'
75
75
 
76
76
  function ThemeToggle() {
77
- const { mode, setMode } = useColorMode() // 'light' | 'dark' | 'system'
77
+ const { colorMode, setColorMode } = useColorMode() // 'light' | 'dark' | 'system'
78
78
  return (
79
- <Button onClick={() => setMode(mode === 'dark' ? 'light' : 'dark')}>
79
+ <Button onClick={() => setColorMode(colorMode === 'dark' ? 'light' : 'dark')}>
80
80
  Toggle theme
81
81
  </Button>
82
82
  )