@devalok/shilp-sutra 0.50.0 → 0.52.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 (56) hide show
  1. package/AGENTS.md +1 -1
  2. package/MIGRATION.md +12 -0
  3. package/dist/tokens/semantic.css +20 -1
  4. package/dist/ui/button-group.js +2 -0
  5. package/dist/ui/button-group.js.map +1 -1
  6. package/dist/ui/button-processing.d.ts.map +1 -1
  7. package/dist/ui/button-processing.js +1 -0
  8. package/dist/ui/button-processing.js.map +1 -1
  9. package/dist/ui/button.d.ts +3 -3
  10. package/dist/ui/button.d.ts.map +1 -1
  11. package/dist/ui/button.js +26 -0
  12. package/dist/ui/button.js.map +1 -1
  13. package/dist/ui/card.d.ts +5 -4
  14. package/dist/ui/card.d.ts.map +1 -1
  15. package/dist/ui/card.js +1 -1
  16. package/dist/ui/card.js.map +1 -1
  17. package/dist/ui/color-input.d.ts.map +1 -1
  18. package/dist/ui/color-input.js +82 -82
  19. package/dist/ui/color-input.js.map +1 -1
  20. package/dist/ui/icon.d.ts +2 -0
  21. package/dist/ui/icon.d.ts.map +1 -1
  22. package/dist/ui/icon.js +31 -26
  23. package/dist/ui/icon.js.map +1 -1
  24. package/dist/ui/search-input.d.ts.map +1 -1
  25. package/dist/ui/search-input.js +1 -0
  26. package/dist/ui/search-input.js.map +1 -1
  27. package/dist/ui/segmented-control.d.ts +28 -6
  28. package/dist/ui/segmented-control.d.ts.map +1 -1
  29. package/dist/ui/segmented-control.js +61 -43
  30. package/dist/ui/segmented-control.js.map +1 -1
  31. package/dist/ui/split-button.d.ts.map +1 -1
  32. package/dist/ui/split-button.js +7 -0
  33. package/dist/ui/split-button.js.map +1 -1
  34. package/dist/ui/stat-card.d.ts +3 -3
  35. package/dist/ui/stat-card.d.ts.map +1 -1
  36. package/dist/ui/stat-card.js.map +1 -1
  37. package/docs/components/ui/button.md +2 -2
  38. package/docs/components/ui/segmented-control.md +31 -11
  39. package/docs/components/ui/stat-card.md +1 -1
  40. package/docs/recipes/install-remix.md +3 -3
  41. package/docs/recipes/install-vite.md +3 -3
  42. package/docs/recipes/server-components.md +2 -2
  43. package/llms.txt +1 -1
  44. package/make-kit/components/card.md +4 -4
  45. package/make-kit/foundations/color.md +20 -0
  46. package/make-kit/foundations/dark-mode.md +6 -9
  47. package/make-kit/foundations/icons.md +3 -3
  48. package/make-kit/setup.md +4 -4
  49. package/mcp-manifest.json +62 -11
  50. package/package.json +1 -1
  51. package/scripts/welcome.mjs +59 -1
  52. package/skill/SKILL.md +1 -1
  53. package/skill/references/components.md +1 -1
  54. package/skill/references/server-components.md +2 -2
  55. package/skill/references/setup-remix.md +3 -3
  56. package/skill/references/setup-vite.md +3 -3
@@ -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
  )
package/mcp-manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "./mcp-manifest.schema.json",
3
3
  "manifestVersion": "1.2.0",
4
4
  "package": "@devalok/shilp-sutra",
5
- "packageVersion": "0.50.0",
5
+ "packageVersion": "0.52.0",
6
6
  "components": {
7
7
  "accordion": {
8
8
  "displayName": "Accordion",
@@ -1192,6 +1192,7 @@
1192
1192
  "error",
1193
1193
  "success",
1194
1194
  "warning",
1195
+ "info",
1195
1196
  "neutral"
1196
1197
  ]
1197
1198
  },
@@ -1302,6 +1303,7 @@
1302
1303
  "error",
1303
1304
  "success",
1304
1305
  "warning",
1306
+ "info",
1305
1307
  "neutral"
1306
1308
  ]
1307
1309
  },
@@ -5250,7 +5252,7 @@
5250
5252
  "type": {
5251
5253
  "name": "enum",
5252
5254
  "value": [
5253
- "default",
5255
+ "soft",
5254
5256
  "solid"
5255
5257
  ]
5256
5258
  },
@@ -5263,16 +5265,24 @@
5263
5265
  },
5264
5266
  "required": true
5265
5267
  },
5266
- "selectedId": {
5268
+ "value": {
5267
5269
  "type": {
5268
- "name": "string"
5270
+ "name": "object",
5271
+ "raw": "string // controlled"
5269
5272
  },
5270
- "required": true
5273
+ "required": false
5271
5274
  },
5272
- "onSelect": {
5275
+ "defaultValue": {
5276
+ "type": {
5277
+ "name": "object",
5278
+ "raw": "string // uncontrolled initial"
5279
+ },
5280
+ "required": false
5281
+ },
5282
+ "onValueChange": {
5273
5283
  "type": {
5274
5284
  "name": "function",
5275
- "raw": "(id: string) => void (REQUIRED)"
5285
+ "raw": "(id: string) => void"
5276
5286
  },
5277
5287
  "required": false
5278
5288
  },
@@ -5281,6 +5291,26 @@
5281
5291
  "name": "boolean"
5282
5292
  },
5283
5293
  "required": false
5294
+ },
5295
+ "fullWidth": {
5296
+ "type": {
5297
+ "name": "boolean"
5298
+ },
5299
+ "required": false
5300
+ },
5301
+ "selectedId": {
5302
+ "type": {
5303
+ "name": "object",
5304
+ "raw": "string // @deprecated — use value"
5305
+ },
5306
+ "required": false
5307
+ },
5308
+ "onSelect": {
5309
+ "type": {
5310
+ "name": "function",
5311
+ "raw": "(id: string) => void // @deprecated — use onValueChange"
5312
+ },
5313
+ "required": false
5284
5314
  }
5285
5315
  },
5286
5316
  "composition": {
@@ -5288,20 +5318,29 @@
5288
5318
  "**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.",
5289
5319
  "**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\"`.",
5290
5320
  "**Option icons** auto-size based on the `size` prop — don't set explicit icon sizes.",
5291
- "Fully controlled there's no `defaultSelectedId`. Manage state in parent.",
5321
+ "**`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.",
5322
+ "**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.",
5323
+ "**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.",
5324
+ "**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.",
5325
+ "**Touch targets** — each segment has a 44px minimum hit area (via `touch-target`) even though the visual height stays dense.",
5326
+ "**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).",
5292
5327
  "Built from scratch (no Radix primitive) — standard HTML buttons with `role=\"radio\"` + `aria-checked` and roving tabindex."
5293
5328
  ]
5294
5329
  },
5295
5330
  "docPath": "docs/components/ui/segmented-control.md",
5296
5331
  "examples": [
5297
- "<SegmentedControl\n size=\"md\"\n variant=\"default\"\n options={[\n { id: 'list', text: 'List' },\n { id: 'grid', text: 'Grid' },\n ]}\n selectedId={viewMode}\n onSelect={setViewMode}\n/>"
5332
+ "<SegmentedControl\n size=\"md\"\n variant=\"soft\"\n options={[\n { id: 'list', text: 'List' },\n { id: 'grid', text: 'Grid' },\n ]}\n value={viewMode}\n onValueChange={setViewMode}\n/>"
5298
5333
  ],
5299
5334
  "gotchas": [
5300
- "Controlled only — selectedId + onSelect are required",
5335
+ "Controlled (`value`) or uncontrolled (`defaultValue`) `selectedId`/`onSelect` are deprecated aliases",
5301
5336
  "Uses data-driven API (options prop), not compound children",
5302
5337
  "Use Tabs (not SegmentedControl) when you need associated content panels per option"
5303
5338
  ],
5304
5339
  "changes": [
5340
+ {
5341
+ "version": "0.52.0",
5342
+ "summary": "**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`."
5343
+ },
5305
5344
  {
5306
5345
  "version": "0.38.0",
5307
5346
  "summary": "**Removed** (BREAKING) deprecated `variant=\"accent\"` alias. Use `variant=\"solid\"`."
@@ -6418,7 +6457,7 @@
6418
6457
  "variant": {
6419
6458
  "type": {
6420
6459
  "name": "union",
6421
- "raw": "\"default\" | \"elevated\" | \"outline\" | \"flat\" (default = ring-in-shadow, no border; elevated = stronger shadow; outline = border, no shadow; flat = filled, no edge) — delegated to Card"
6460
+ "raw": "\"default\" | \"elevated\" | \"outline\" | \"flat\" (default = tonal border-card hairline, no shadow; elevated = shadow, no border; outline = strong border, no shadow; flat = filled, no edge) — delegated to Card"
6422
6461
  },
6423
6462
  "required": false
6424
6463
  },
@@ -13279,6 +13318,14 @@
13279
13318
  "name": "--color-backdrop",
13280
13319
  "value": "Canvas"
13281
13320
  },
13321
+ {
13322
+ "name": "--color-segment-track",
13323
+ "value": "color-mix(in oklch, oklch(1 0 0) 7%, transparent)"
13324
+ },
13325
+ {
13326
+ "name": "--color-segment-thumb",
13327
+ "value": "var(--neutral-3)"
13328
+ },
13282
13329
  {
13283
13330
  "name": "--color-overlay",
13284
13331
  "value": "Canvas"
@@ -13933,6 +13980,10 @@
13933
13980
  "name": "--shadow-raised-hover",
13934
13981
  "value": "var(--shadow-sm-internal)"
13935
13982
  },
13983
+ {
13984
+ "name": "--shadow-segment",
13985
+ "value": "0 1px 2px -0.5px oklch(var(--shadow-color) / calc(0.10 * var(--shadow-strength))), 0 3px 8px -2px oklch(var(--shadow-color) / calc(0.09 * var(--shadow-strength)))"
13986
+ },
13936
13987
  {
13937
13988
  "name": "--shadow-floating",
13938
13989
  "value": "var(--shadow-md-internal)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devalok/shilp-sutra",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "description": "Devalok Design System — accessible React components, OKLCH design tokens, and Tailwind 4 CSS-first setup. Ships with AI-agent setup recipes.",
5
5
  "license": "MIT",
6
6
  "author": "Devalok Design & Strategy Studios <shilp-sutra@devalok.in>",
@@ -183,7 +183,7 @@ function buildFullBanner(version, prevVersion) {
183
183
  lines.push(row(` ${colour('https://shilp-sutra.devalok.in/themer', DIM)}`))
184
184
  lines.push(colour(EMPTY, PINK_DIM))
185
185
  lines.push(row(` ${colour('▸', PINK)} Wire your AI agent (Claude Code / Cursor / Codex):`))
186
- lines.push(row(` ${colour('connect the live docs MCP https://shilp-sutra.devalok.in/mcp', DIM)}`))
186
+ lines.push(row(` ${colour('live docs MCP added to .mcp.json — approve it to enable', DIM)}`))
187
187
  lines.push(row(` ${colour('(version-exact setup + peer preflight; beats guessing)', DIM)}`))
188
188
  lines.push(row(` ${colour('or copy the skill: cp -r node_modules/@devalok/shilp-sutra/skill \\', DIM)}`))
189
189
  lines.push(row(` ${colour('~/.claude/skills/shilp-sutra', DIM)}`))
@@ -215,6 +215,59 @@ function buildCompactBanner(version, prevVersion) {
215
215
  ].join('\n')
216
216
  }
217
217
 
218
+ // ── MCP auto-discovery ───────────────────────────────────────────────────────
219
+ // Write a project-scoped `.mcp.json` pointing at the hosted docs MCP so an AI
220
+ // coding agent DISCOVERS it right after install. This runs even when stdout is
221
+ // piped (unlike the banner) — a config file is not console noise, and the agent
222
+ // that just ran `install` is exactly who should find it. It is never silent-
223
+ // forced: Claude Code (and peers) still PROMPT the user to approve a project
224
+ // MCP server before enabling it. Safety: additive merge (never clobbers other
225
+ // servers or an existing shilp-sutra entry), skips CI and dev installs, honours
226
+ // opt-out, and a write-once sentinel so a user who deletes it is not re-nagged.
227
+ const SEP = process.platform === 'win32' ? '\\' : '/'
228
+ const MCP_URL = 'https://shilp-sutra.devalok.in/mcp'
229
+
230
+ function tryWriteMcpConfig() {
231
+ try {
232
+ if (process.env.SHILP_SUTRA_NO_WELCOME === '1' || process.env.SHILP_SUTRA_NO_WELCOME === 'true') return
233
+ if (process.env.SHILP_SUTRA_NO_MCP === '1' || process.env.SHILP_SUTRA_NO_MCP === 'true') return
234
+ if (process.env.CI) return // writing agent config into a CI checkout is pointless/unwanted
235
+
236
+ const initCwd = process.env.INIT_CWD
237
+ const cwd = process.cwd()
238
+ const isInsideNodeModules = cwd.includes(`${SEP}node_modules${SEP}`) || cwd.includes('/node_modules/')
239
+ if (!isInsideNodeModules) return // dev install inside the DS repo itself
240
+ if (!initCwd || initCwd === cwd) return // no consumer root → unusual context
241
+
242
+ // Write-once-ever sentinel (survives re-installs; respects user deletion of .mcp.json)
243
+ const parts = PKG_DIR.split(/[/\\]/)
244
+ const nmIdx = parts.lastIndexOf('node_modules')
245
+ const sentinel = nmIdx === -1 ? null : join(parts.slice(0, nmIdx + 1).join(SEP), '.shilp-sutra-mcp-written')
246
+ if (sentinel && existsSync(sentinel)) return
247
+
248
+ const target = join(initCwd, '.mcp.json')
249
+ let config = { mcpServers: {} }
250
+ if (existsSync(target)) {
251
+ try {
252
+ config = JSON.parse(readFileSync(target, 'utf-8'))
253
+ } catch {
254
+ return // existing but unparseable — never clobber a hand-authored config
255
+ }
256
+ if (!config || typeof config !== 'object') return
257
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') config.mcpServers = {}
258
+ if (config.mcpServers['shilp-sutra']) {
259
+ if (sentinel) writeFileSync(sentinel, MCP_URL + '\n')
260
+ return // already declared — leave the consumer's version untouched
261
+ }
262
+ }
263
+ config.mcpServers['shilp-sutra'] = { type: 'http', url: MCP_URL }
264
+ writeFileSync(target, JSON.stringify(config, null, 2) + '\n')
265
+ if (sentinel) writeFileSync(sentinel, MCP_URL + '\n')
266
+ } catch {
267
+ // Never break the consumer install — a failed config write is a no-op.
268
+ }
269
+ }
270
+
218
271
  // ── Main ────────────────────────────────────────────────────────────────────
219
272
  function main() {
220
273
  // --preview / --compact bypass all guards. Used by maintainers + by the
@@ -223,6 +276,11 @@ function main() {
223
276
  const preview = process.argv.includes('--preview')
224
277
  const forceCompact = process.argv.includes('--compact')
225
278
 
279
+ // MCP auto-discovery runs regardless of TTY — an agent-run (piped) install is
280
+ // precisely when the agent should discover the docs MCP. Must come before the
281
+ // TTY skip below, which only governs the human-facing banner.
282
+ if (!preview) tryWriteMcpConfig()
283
+
226
284
  if (!preview) {
227
285
  const skipReason = shouldSkip()
228
286
  if (skipReason) return // silent
package/skill/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: shilp-sutra
3
3
  description: Add, configure, and use components from Devalok's shilp-sutra design system (@devalok/shilp-sutra) — a Tailwind 4 + React 19 + CVA library with 110+ accessible components, OKLCH design tokens, framer-motion animations, and per-component RSC-safe entry points. Use this skill whenever the user mentions shilp-sutra, Devalok, the @devalok npm scope, or asks to install/add/style/theme UI in any React project that already depends on the package — even if they don't name it explicitly. Use it instead of generic shadcn/ui, MUI, or Chakra knowledge when shilp-sutra is in the project. Covers Next.js (App + Pages), Vite, Astro, Remix, TanStack Start setup playbooks; component API and variant reference; brand token customization; Server Component import patterns; and a troubleshoot tree for the thirteen most common breakages.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.50.0"
6
+ version: "0.52.0"
7
7
  author: Devalok Design & Strategy Studios
8
8
  homepage: https://github.com/devalok-design/shilp-sutra
9
9
  npm: https://www.npmjs.com/package/@devalok/shilp-sutra
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @devalok/shilp-sutra
4
4
 
5
- > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.50.0.
5
+ > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.52.0.
6
6
  > Built on the same primitives as shadcn/ui but with DIFFERENT prop APIs — never guess from shadcn knowledge; verify every prop.
7
7
  > 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).
8
8
 
@@ -153,8 +153,8 @@ Both `<Suspense>` and `<LoadingSkeleton>` are server-safe.
153
153
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
154
154
 
155
155
  export default function ServerPage() {
156
- const { mode } = useColorMode(); // breaks
157
- return <div>{mode}</div>;
156
+ const { colorMode } = useColorMode(); // breaks
157
+ return <div>{colorMode}</div>;
158
158
  }
159
159
  ```
160
160
 
@@ -129,10 +129,10 @@ For runtime toggling inside React components, use `useColorMode`:
129
129
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
130
130
 
131
131
  export function ThemeToggle() {
132
- const { mode, toggle } = useColorMode();
132
+ const { colorMode, toggleColorMode } = useColorMode();
133
133
  return (
134
- <button onClick={toggle} aria-label="Toggle theme">
135
- {mode === "dark" ? "☀" : "☾"}
134
+ <button onClick={toggleColorMode} aria-label="Toggle theme">
135
+ {colorMode === "dark" ? "☀" : "☾"}
136
136
  </button>
137
137
  );
138
138
  }
@@ -123,10 +123,10 @@ Wire the runtime hook from anywhere in the app (e.g., a header button):
123
123
  import { useColorMode } from "@devalok/shilp-sutra/hooks/use-color-mode";
124
124
 
125
125
  export function ThemeToggle() {
126
- const { mode, toggle } = useColorMode();
126
+ const { colorMode, toggleColorMode } = useColorMode();
127
127
  return (
128
- <button onClick={toggle} aria-label="Toggle theme">
129
- {mode === "dark" ? "☀" : "☾"}
128
+ <button onClick={toggleColorMode} aria-label="Toggle theme">
129
+ {colorMode === "dark" ? "☀" : "☾"}
130
130
  </button>
131
131
  );
132
132
  }