@devalok/shilp-sutra 0.37.1 → 0.38.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 (63) hide show
  1. package/MIGRATION.md +67 -0
  2. package/dist/_chunks/tiptap.js +977 -968
  3. package/dist/_chunks/tiptap.js.map +1 -1
  4. package/dist/composed/index.d.ts +0 -2
  5. package/dist/composed/index.d.ts.map +1 -1
  6. package/dist/composed/index.js +5 -6
  7. package/dist/hooks/index.d.ts +2 -2
  8. package/dist/hooks/index.d.ts.map +1 -1
  9. package/dist/hooks/index.js +0 -1
  10. package/dist/ui/alert.d.ts +1 -2
  11. package/dist/ui/alert.d.ts.map +1 -1
  12. package/dist/ui/alert.js +1 -27
  13. package/dist/ui/alert.js.map +1 -1
  14. package/dist/ui/banner.d.ts +3 -5
  15. package/dist/ui/banner.d.ts.map +1 -1
  16. package/dist/ui/banner.js +13 -13
  17. package/dist/ui/banner.js.map +1 -1
  18. package/dist/ui/index.d.ts +1 -1
  19. package/dist/ui/index.d.ts.map +1 -1
  20. package/dist/ui/index.js +2 -2
  21. package/dist/ui/index.js.map +1 -1
  22. package/dist/ui/input.d.ts +1 -9
  23. package/dist/ui/input.d.ts.map +1 -1
  24. package/dist/ui/input.js +26 -27
  25. package/dist/ui/input.js.map +1 -1
  26. package/dist/ui/segmented-control.d.ts +1 -1
  27. package/dist/ui/segmented-control.d.ts.map +1 -1
  28. package/dist/ui/segmented-control.js +2 -4
  29. package/dist/ui/segmented-control.js.map +1 -1
  30. package/docs/components/_header.md +1 -1
  31. package/docs/components/ui/alert.md +4 -1
  32. package/docs/components/ui/banner.md +3 -1
  33. package/docs/components/ui/input.md +4 -2
  34. package/docs/components/ui/segmented-control.md +13 -6
  35. package/docs/recipes/customize-brand.md +216 -0
  36. package/docs/recipes/index.md +51 -0
  37. package/docs/recipes/install-astro.md +178 -0
  38. package/docs/recipes/install-next-app-router.md +230 -0
  39. package/docs/recipes/install-next-pages.md +123 -0
  40. package/docs/recipes/install-remix.md +171 -0
  41. package/docs/recipes/install-tanstack-start.md +143 -0
  42. package/docs/recipes/install-vite.md +170 -0
  43. package/docs/recipes/server-components.md +209 -0
  44. package/docs/recipes/troubleshoot.md +217 -0
  45. package/llms-full.txt +26 -53
  46. package/llms.txt +33 -16
  47. package/package.json +45 -35
  48. package/dist/composed/responsive-overlay.d.ts +0 -23
  49. package/dist/composed/responsive-overlay.d.ts.map +0 -1
  50. package/dist/composed/responsive-overlay.js +0 -40
  51. package/dist/composed/responsive-overlay.js.map +0 -1
  52. package/dist/hooks/use-toast.d.ts +0 -17
  53. package/dist/hooks/use-toast.d.ts.map +0 -1
  54. package/dist/hooks/use-toast.js +0 -3
  55. package/dist/tailwind/index.cjs +0 -41
  56. package/dist/tailwind/index.d.ts +0 -2
  57. package/dist/tailwind/index.d.ts.map +0 -1
  58. package/dist/tailwind/index.js +0 -2
  59. package/dist/tailwind/preset.d.ts +0 -25
  60. package/dist/tailwind/preset.d.ts.map +0 -1
  61. package/dist/tailwind/preset.js +0 -17
  62. package/dist/tailwind/preset.js.map +0 -1
  63. package/docs/components/composed/responsive-overlay.md +0 -41
package/dist/ui/input.js CHANGED
@@ -33,30 +33,30 @@ var s = t([
33
33
  sm: "sm",
34
34
  md: "md",
35
35
  lg: "lg"
36
- }, u = i.forwardRef(({ className: t, type: i, state: u, size: d = "md", startSection: f, endSection: p, startSectionClickable: m, endSectionClickable: h, startSectionType: g, endSectionType: _, startIcon: v, endIcon: y, wrapperClassName: b, ...x }, S) => {
37
- let C = d ?? "md", w = r(), T = u ?? (w.state === "helper" ? void 0 : w.state), E = x["aria-describedby"] ?? w.helperTextId, D = x["aria-required"] ?? w.required, O = x.id ?? w.inputId, k = f ?? v, A = p ?? y, j = !!k, M = !!A, N = g ?? (typeof k == "string" ? "label" : "icon"), P = _ ?? (typeof A == "string" ? "label" : "icon"), F = {
36
+ }, u = i.forwardRef(({ className: t, type: i, state: u, size: d = "md", startSection: f, endSection: p, startSectionClickable: m, endSectionClickable: h, startSectionType: g, endSectionType: _, wrapperClassName: v, ...y }, b) => {
37
+ let x = d ?? "md", S = r(), C = u ?? (S.state === "helper" ? void 0 : S.state), w = y["aria-describedby"] ?? S.helperTextId, T = y["aria-required"] ?? S.required, E = y.id ?? S.inputId, D = !!f, O = !!p, k = g ?? (typeof f == "string" ? "label" : "icon"), A = _ ?? (typeof p == "string" ? "label" : "icon"), j = {
38
38
  xs: "px-ds-02",
39
39
  sm: "px-ds-02",
40
40
  md: "px-ds-03",
41
41
  lg: "px-ds-03"
42
- }, I = {
42
+ }, M = {
43
43
  xs: "text-ds-sm",
44
44
  sm: "text-ds-sm",
45
45
  md: "text-ds-sm",
46
46
  lg: "text-ds-md"
47
47
  };
48
48
  return /* @__PURE__ */ o("div", {
49
- className: n(s({ size: C }), T === "error" && "border-error-7 focus-within:ring-error-7", T === "warning" && "border-warning-7 focus-within:ring-warning-7", T === "success" && "border-success-7 focus-within:ring-success-7", b),
49
+ className: n(s({ size: x }), C === "error" && "border-error-7 focus-within:ring-error-7", C === "warning" && "border-warning-7 focus-within:ring-warning-7", C === "success" && "border-success-7 focus-within:ring-success-7", v),
50
50
  children: [
51
- j && N === "label" && /* @__PURE__ */ a("span", {
52
- className: n("flex items-center shrink-0 select-none self-stretch", "border-r border-surface-border", "bg-surface-raised text-surface-fg-muted", "rounded-l-[inherit]", F[C], I[C], !m && "pointer-events-none"),
53
- children: k
51
+ D && k === "label" && /* @__PURE__ */ a("span", {
52
+ className: n("flex items-center shrink-0 select-none self-stretch", "border-r border-surface-border", "bg-surface-raised text-surface-fg-muted", "rounded-l-[inherit]", j[x], M[x], !m && "pointer-events-none"),
53
+ children: f
54
54
  }),
55
- j && N === "icon" && /* @__PURE__ */ a(e, {
56
- size: l[C],
55
+ D && k === "icon" && /* @__PURE__ */ a(e, {
56
+ size: l[x],
57
57
  children: /* @__PURE__ */ a("span", {
58
- className: n("flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted", c[C], !m && "pointer-events-none"),
59
- children: k
58
+ className: n("flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted", c[x], !m && "pointer-events-none"),
59
+ children: f
60
60
  })
61
61
  }),
62
62
  /* @__PURE__ */ a("input", {
@@ -66,29 +66,28 @@ var s = t([
66
66
  sm: "px-ds-03",
67
67
  md: "px-ds-03",
68
68
  lg: "px-ds-04"
69
- }[C], j && N === "icon" && "pl-0", M && P === "icon" && "pr-0", t),
70
- "aria-invalid": T === "error" || void 0,
71
- "aria-describedby": E,
72
- "aria-required": D || void 0,
73
- ref: S,
74
- ...x,
75
- id: O
69
+ }[x], D && k === "icon" && "pl-0", O && A === "icon" && "pr-0", t),
70
+ "aria-invalid": C === "error" || void 0,
71
+ "aria-describedby": w,
72
+ "aria-required": T || void 0,
73
+ ref: b,
74
+ ...y,
75
+ id: E
76
76
  }),
77
- M && P === "label" && /* @__PURE__ */ a("span", {
78
- className: n("flex items-center shrink-0 select-none self-stretch", "border-l border-surface-border", "bg-surface-raised text-surface-fg-muted", "rounded-r-[inherit]", F[C], I[C], !h && "pointer-events-none"),
79
- children: A
77
+ O && A === "label" && /* @__PURE__ */ a("span", {
78
+ className: n("flex items-center shrink-0 select-none self-stretch", "border-l border-surface-border", "bg-surface-raised text-surface-fg-muted", "rounded-r-[inherit]", j[x], M[x], !h && "pointer-events-none"),
79
+ children: p
80
80
  }),
81
- M && P === "icon" && /* @__PURE__ */ a(e, {
82
- size: l[C],
81
+ O && A === "icon" && /* @__PURE__ */ a(e, {
82
+ size: l[x],
83
83
  children: /* @__PURE__ */ a("span", {
84
- className: n("flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted", c[C], !h && "pointer-events-none"),
85
- children: A
84
+ className: n("flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted", c[x], !h && "pointer-events-none"),
85
+ children: p
86
86
  })
87
87
  })
88
88
  ]
89
89
  });
90
90
  });
91
91
  u.displayName = "Input";
92
- var d = s;
93
92
  //#endregion
94
- export { u as Input, d as inputVariants, s as inputWrapperVariants };
93
+ export { u as Input, s as inputWrapperVariants };
@@ -1 +1 @@
1
- {"version":3,"file":"input.js","names":[],"sources":["../../src/ui/input.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { IconProvider, type IconSize } from './icon-context'\nimport { cn } from './lib/utils'\n\nexport type InputState = 'default' | 'error' | 'warning' | 'success'\n\nconst inputWrapperVariants = cva(\n [\n 'relative flex items-center w-full font-sans',\n 'bg-surface-raised-hover text-surface-fg',\n 'border border-surface-border-strong rounded-ds-md',\n 'hover:bg-surface-raised-active',\n 'transition-[color,background-color,border-color,box-shadow] duration-fast-02 ease-productive-standard',\n 'focus-within:outline-hidden focus-within:ring-2 focus-within:ring-accent-9 focus-within:ring-offset-2 focus-within:border-surface-border',\n 'has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-action-disabled',\n 'has-[:read-only]:bg-surface-raised has-[:read-only]:cursor-default',\n ],\n {\n variants: {\n size: {\n xs: 'h-ds-xs-plus text-ds-sm',\n sm: 'h-ds-sm text-ds-sm',\n md: 'h-ds-md text-ds-md',\n lg: 'h-ds-lg rounded-ds-lg text-ds-md',\n },\n },\n defaultVariants: { size: 'md' },\n },\n)\n\nconst sectionWidthMap: Record<string, string> = {\n xs: 'w-[26px]',\n sm: 'w-[30px]',\n md: 'w-[38px]',\n lg: 'w-[46px]',\n}\n\nconst iconSizeMap: Record<string, IconSize> = {\n xs: 'xs',\n sm: 'sm',\n md: 'md',\n lg: 'lg',\n}\n\n/**\n * Props for Input — a single-line text field with container-first architecture,\n * section-based icons with automatic padding, per-size scaling, and validation state coloring.\n *\n * **Sizes:** `xs` (28px) | `sm` (32px) | `md` (40px, default) | `lg` (48px)\n * HTML's native `size` attribute is excluded — use CSS width instead.\n *\n * **Validation states:** `state=\"error\"` colors the border red and sets `aria-invalid`.\n * Use with `<FormField>` to show helper text below the input.\n *\n * **Section-based icons:** `startSection` and `endSection` accept any React node.\n * Icons are auto-sized via `IconProvider`. Sections are `pointer-events-none` by default;\n * use `startSectionClickable`/`endSectionClickable` for interactive sections.\n *\n * **className** targets the `<input>` element (backward compatible).\n * **wrapperClassName** targets the wrapper div for border/bg/ring overrides.\n *\n * @example\n * // Basic email field with placeholder:\n * <Input type=\"email\" placeholder=\"you@example.com\" />\n *\n * @example\n * // Search input with a leading icon:\n * <Input size=\"md\" startSection={<IconSearch />} placeholder=\"Search projects...\" />\n *\n * @example\n * // Validated error state (pair with FormField for label + helper text):\n * <Input state=\"error\" value={email} onChange={handleChange} />\n *\n * @example\n * // Read-only field (shows a muted background, non-editable):\n * <Input readOnly value=\"https://devalok.com/api/key/abc123\" endSection={<IconCopy />} />\n */\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,\n VariantProps<typeof inputWrapperVariants> {\n state?: InputState\n startSection?: React.ReactNode\n endSection?: React.ReactNode\n startSectionClickable?: boolean\n endSectionClickable?: boolean\n /** Section display type. `'icon'` = fixed-width centered cell (default for ReactElements).\n * `'label'` = tinted background with border separator (default for strings). */\n startSectionType?: 'icon' | 'label'\n /** Section display type. `'icon'` = fixed-width centered cell (default for ReactElements).\n * `'label'` = tinted background with border separator (default for strings). */\n endSectionType?: 'icon' | 'label'\n /** Classes for the wrapper div (border, bg, ring). */\n wrapperClassName?: string\n /** @deprecated Use startSection */\n startIcon?: React.ReactNode\n /** @deprecated Use endSection */\n endIcon?: React.ReactNode\n}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n type,\n state: stateProp,\n size: sizeProp = 'md',\n startSection,\n endSection,\n startSectionClickable,\n endSectionClickable,\n startSectionType: startSectionTypeProp,\n endSectionType: endSectionTypeProp,\n startIcon,\n endIcon,\n wrapperClassName,\n ...props\n },\n ref,\n ) => {\n const size = sizeProp ?? 'md'\n const fieldCtx = useFormField()\n // Merge FormField context — explicit props always win\n const state =\n stateProp ??\n (fieldCtx.state === 'helper'\n ? undefined\n : (fieldCtx.state as InputState | undefined))\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n // Explicit id wins; otherwise adopt FormField's inputId so <Label htmlFor> resolves.\n const inputId = props.id ?? fieldCtx.inputId\n\n // Backward compat: map deprecated props\n const resolvedStart = startSection ?? startIcon\n const resolvedEnd = endSection ?? endIcon\n\n const hasStart = !!resolvedStart\n const hasEnd = !!resolvedEnd\n\n // Infer section type: string → label, ReactElement → icon\n const startType = startSectionTypeProp ?? (typeof resolvedStart === 'string' ? 'label' : 'icon')\n const endType = endSectionTypeProp ?? (typeof resolvedEnd === 'string' ? 'label' : 'icon')\n\n const labelPaddingMap: Record<string, string> = {\n xs: 'px-ds-02',\n sm: 'px-ds-02',\n md: 'px-ds-03',\n lg: 'px-ds-03',\n }\n\n const labelTextMap: Record<string, string> = {\n xs: 'text-ds-sm',\n sm: 'text-ds-sm',\n md: 'text-ds-sm',\n lg: 'text-ds-md',\n }\n\n const inputPadding: Record<string, string> = {\n xs: 'px-ds-02',\n sm: 'px-ds-03',\n md: 'px-ds-03',\n lg: 'px-ds-04',\n }\n\n return (\n <div\n className={cn(\n inputWrapperVariants({ size }),\n state === 'error' && 'border-error-7 focus-within:ring-error-7',\n state === 'warning' && 'border-warning-7 focus-within:ring-warning-7',\n state === 'success' && 'border-success-7 focus-within:ring-success-7',\n wrapperClassName,\n )}\n >\n {hasStart && startType === 'label' && (\n <span\n className={cn(\n 'flex items-center shrink-0 select-none self-stretch',\n 'border-r border-surface-border',\n 'bg-surface-raised text-surface-fg-muted',\n 'rounded-l-[inherit]',\n labelPaddingMap[size],\n labelTextMap[size],\n !startSectionClickable && 'pointer-events-none',\n )}\n >\n {resolvedStart}\n </span>\n )}\n\n {hasStart && startType === 'icon' && (\n <IconProvider size={iconSizeMap[size]}>\n <span\n className={cn(\n 'flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted',\n sectionWidthMap[size],\n !startSectionClickable && 'pointer-events-none',\n )}\n >\n {resolvedStart}\n </span>\n </IconProvider>\n )}\n\n <input\n type={type}\n className={cn(\n 'flex-1 min-w-0 h-full bg-transparent outline-hidden font-sans',\n 'placeholder:text-surface-fg-subtle',\n 'disabled:cursor-not-allowed',\n 'read-only:cursor-default',\n inputPadding[size],\n // Remove left padding when start icon section is present (icon provides visual space)\n hasStart && startType === 'icon' && 'pl-0',\n // Remove right padding when end icon section is present\n hasEnd && endType === 'icon' && 'pr-0',\n className,\n )}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n ref={ref}\n {...props}\n id={inputId}\n />\n\n {hasEnd && endType === 'label' && (\n <span\n className={cn(\n 'flex items-center shrink-0 select-none self-stretch',\n 'border-l border-surface-border',\n 'bg-surface-raised text-surface-fg-muted',\n 'rounded-r-[inherit]',\n labelPaddingMap[size],\n labelTextMap[size],\n !endSectionClickable && 'pointer-events-none',\n )}\n >\n {resolvedEnd}\n </span>\n )}\n\n {hasEnd && endType === 'icon' && (\n <IconProvider size={iconSizeMap[size]}>\n <span\n className={cn(\n 'flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted',\n sectionWidthMap[size],\n !endSectionClickable && 'pointer-events-none',\n )}\n >\n {resolvedEnd}\n </span>\n </IconProvider>\n )}\n </div>\n )\n },\n)\nInput.displayName = 'Input'\n\n/** @deprecated Use inputWrapperVariants — semantics changed (now targets wrapper, not input) */\nconst inputVariants = inputWrapperVariants\n\nexport { Input, inputVariants, inputWrapperVariants }\n"],"mappings":";;;;;;;;AAWA,IAAM,IAAuB,EAC3B;CACE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EACD;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EACF;CACD,iBAAiB,EAAE,MAAM,MAAM;CAChC,CACF,EAEK,IAA0C;CAC9C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EAEK,IAAwC;CAC5C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EAyDK,IAAQ,EAAM,YAEhB,EACE,cACA,SACA,OAAO,GACP,MAAM,IAAW,MACjB,iBACA,eACA,0BACA,wBACA,kBAAkB,GAClB,gBAAgB,GAChB,cACA,YACA,qBACA,GAAG,KAEL,MACG;CACH,IAAM,IAAO,KAAY,MACnB,IAAW,GAAc,EAEzB,IACJ,MACC,EAAS,UAAU,WAChB,KAAA,IACC,EAAS,QACV,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAElD,IAAU,EAAM,MAAM,EAAS,SAG/B,IAAgB,KAAgB,GAChC,IAAc,KAAc,GAE5B,IAAW,CAAC,CAAC,GACb,IAAS,CAAC,CAAC,GAGX,IAAY,MAAyB,OAAO,KAAkB,WAAW,UAAU,SACnF,IAAU,MAAuB,OAAO,KAAgB,WAAW,UAAU,SAE7E,IAA0C;EAC9C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EAEK,IAAuC;EAC3C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL;AASD,QACE,kBAAC,OAAD;EACE,WAAW,EACT,EAAqB,EAAE,SAAM,CAAC,EAC9B,MAAU,WAAW,4CACrB,MAAU,aAAa,gDACvB,MAAU,aAAa,gDACvB,EACD;YAPH;GASG,KAAY,MAAc,WACzB,kBAAC,QAAD;IACE,WAAW,EACT,uDACA,kCACA,2CACA,uBACA,EAAgB,IAChB,EAAa,IACb,CAAC,KAAyB,sBAC3B;cAEA;IACI,CAAA;GAGR,KAAY,MAAc,UACzB,kBAAC,GAAD;IAAc,MAAM,EAAY;cAC9B,kBAAC,QAAD;KACE,WAAW,EACT,gFACA,EAAgB,IAChB,CAAC,KAAyB,sBAC3B;eAEA;KACI,CAAA;IACM,CAAA;GAGjB,kBAAC,SAAD;IACQ;IACN,WAAW,EACT,iEACA,sCACA,+BACA,4BArDqC;KAC3C,IAAI;KACJ,IAAI;KACJ,IAAI;KACJ,IAAI;KACL,CAiDoB,IAEb,KAAY,MAAc,UAAU,QAEpC,KAAU,MAAY,UAAU,QAChC,EACD;IACD,gBAAc,MAAU,WAAW,KAAA;IACnC,oBAAkB;IAClB,iBAAe,KAAgB,KAAA;IAC1B;IACL,GAAI;IACJ,IAAI;IACJ,CAAA;GAED,KAAU,MAAY,WACrB,kBAAC,QAAD;IACE,WAAW,EACT,uDACA,kCACA,2CACA,uBACA,EAAgB,IAChB,EAAa,IACb,CAAC,KAAuB,sBACzB;cAEA;IACI,CAAA;GAGR,KAAU,MAAY,UACrB,kBAAC,GAAD;IAAc,MAAM,EAAY;cAC9B,kBAAC,QAAD;KACE,WAAW,EACT,gFACA,EAAgB,IAChB,CAAC,KAAuB,sBACzB;eAEA;KACI,CAAA;IACM,CAAA;GAEb;;EAGX;AACD,EAAM,cAAc;AAGpB,IAAM,IAAgB"}
1
+ {"version":3,"file":"input.js","names":[],"sources":["../../src/ui/input.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { IconProvider, type IconSize } from './icon-context'\nimport { cn } from './lib/utils'\n\nexport type InputState = 'default' | 'error' | 'warning' | 'success'\n\nconst inputWrapperVariants = cva(\n [\n 'relative flex items-center w-full font-sans',\n 'bg-surface-raised-hover text-surface-fg',\n 'border border-surface-border-strong rounded-ds-md',\n 'hover:bg-surface-raised-active',\n 'transition-[color,background-color,border-color,box-shadow] duration-fast-02 ease-productive-standard',\n 'focus-within:outline-hidden focus-within:ring-2 focus-within:ring-accent-9 focus-within:ring-offset-2 focus-within:border-surface-border',\n 'has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-action-disabled',\n 'has-[:read-only]:bg-surface-raised has-[:read-only]:cursor-default',\n ],\n {\n variants: {\n size: {\n xs: 'h-ds-xs-plus text-ds-sm',\n sm: 'h-ds-sm text-ds-sm',\n md: 'h-ds-md text-ds-md',\n lg: 'h-ds-lg rounded-ds-lg text-ds-md',\n },\n },\n defaultVariants: { size: 'md' },\n },\n)\n\nconst sectionWidthMap: Record<string, string> = {\n xs: 'w-[26px]',\n sm: 'w-[30px]',\n md: 'w-[38px]',\n lg: 'w-[46px]',\n}\n\nconst iconSizeMap: Record<string, IconSize> = {\n xs: 'xs',\n sm: 'sm',\n md: 'md',\n lg: 'lg',\n}\n\n/**\n * Props for Input — a single-line text field with container-first architecture,\n * section-based icons with automatic padding, per-size scaling, and validation state coloring.\n *\n * **Sizes:** `xs` (28px) | `sm` (32px) | `md` (40px, default) | `lg` (48px)\n * HTML's native `size` attribute is excluded — use CSS width instead.\n *\n * **Validation states:** `state=\"error\"` colors the border red and sets `aria-invalid`.\n * Use with `<FormField>` to show helper text below the input.\n *\n * **Section-based icons:** `startSection` and `endSection` accept any React node.\n * Icons are auto-sized via `IconProvider`. Sections are `pointer-events-none` by default;\n * use `startSectionClickable`/`endSectionClickable` for interactive sections.\n *\n * **className** targets the `<input>` element (backward compatible).\n * **wrapperClassName** targets the wrapper div for border/bg/ring overrides.\n *\n * @example\n * // Basic email field with placeholder:\n * <Input type=\"email\" placeholder=\"you@example.com\" />\n *\n * @example\n * // Search input with a leading icon:\n * <Input size=\"md\" startSection={<IconSearch />} placeholder=\"Search projects...\" />\n *\n * @example\n * // Validated error state (pair with FormField for label + helper text):\n * <Input state=\"error\" value={email} onChange={handleChange} />\n *\n * @example\n * // Read-only field (shows a muted background, non-editable):\n * <Input readOnly value=\"https://devalok.com/api/key/abc123\" endSection={<IconCopy />} />\n */\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,\n VariantProps<typeof inputWrapperVariants> {\n state?: InputState\n startSection?: React.ReactNode\n endSection?: React.ReactNode\n startSectionClickable?: boolean\n endSectionClickable?: boolean\n /** Section display type. `'icon'` = fixed-width centered cell (default for ReactElements).\n * `'label'` = tinted background with border separator (default for strings). */\n startSectionType?: 'icon' | 'label'\n /** Section display type. `'icon'` = fixed-width centered cell (default for ReactElements).\n * `'label'` = tinted background with border separator (default for strings). */\n endSectionType?: 'icon' | 'label'\n /** Classes for the wrapper div (border, bg, ring). */\n wrapperClassName?: string\n}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n type,\n state: stateProp,\n size: sizeProp = 'md',\n startSection,\n endSection,\n startSectionClickable,\n endSectionClickable,\n startSectionType: startSectionTypeProp,\n endSectionType: endSectionTypeProp,\n wrapperClassName,\n ...props\n },\n ref,\n ) => {\n const size = sizeProp ?? 'md'\n const fieldCtx = useFormField()\n // Merge FormField context — explicit props always win\n const state =\n stateProp ??\n (fieldCtx.state === 'helper'\n ? undefined\n : (fieldCtx.state as InputState | undefined))\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n // Explicit id wins; otherwise adopt FormField's inputId so <Label htmlFor> resolves.\n const inputId = props.id ?? fieldCtx.inputId\n\n const hasStart = !!startSection\n const hasEnd = !!endSection\n\n // Infer section type: string → label, ReactElement → icon\n const startType = startSectionTypeProp ?? (typeof startSection === 'string' ? 'label' : 'icon')\n const endType = endSectionTypeProp ?? (typeof endSection === 'string' ? 'label' : 'icon')\n\n const labelPaddingMap: Record<string, string> = {\n xs: 'px-ds-02',\n sm: 'px-ds-02',\n md: 'px-ds-03',\n lg: 'px-ds-03',\n }\n\n const labelTextMap: Record<string, string> = {\n xs: 'text-ds-sm',\n sm: 'text-ds-sm',\n md: 'text-ds-sm',\n lg: 'text-ds-md',\n }\n\n const inputPadding: Record<string, string> = {\n xs: 'px-ds-02',\n sm: 'px-ds-03',\n md: 'px-ds-03',\n lg: 'px-ds-04',\n }\n\n return (\n <div\n className={cn(\n inputWrapperVariants({ size }),\n state === 'error' && 'border-error-7 focus-within:ring-error-7',\n state === 'warning' && 'border-warning-7 focus-within:ring-warning-7',\n state === 'success' && 'border-success-7 focus-within:ring-success-7',\n wrapperClassName,\n )}\n >\n {hasStart && startType === 'label' && (\n <span\n className={cn(\n 'flex items-center shrink-0 select-none self-stretch',\n 'border-r border-surface-border',\n 'bg-surface-raised text-surface-fg-muted',\n 'rounded-l-[inherit]',\n labelPaddingMap[size],\n labelTextMap[size],\n !startSectionClickable && 'pointer-events-none',\n )}\n >\n {startSection}\n </span>\n )}\n\n {hasStart && startType === 'icon' && (\n <IconProvider size={iconSizeMap[size]}>\n <span\n className={cn(\n 'flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted',\n sectionWidthMap[size],\n !startSectionClickable && 'pointer-events-none',\n )}\n >\n {startSection}\n </span>\n </IconProvider>\n )}\n\n <input\n type={type}\n className={cn(\n 'flex-1 min-w-0 h-full bg-transparent outline-hidden font-sans',\n 'placeholder:text-surface-fg-subtle',\n 'disabled:cursor-not-allowed',\n 'read-only:cursor-default',\n inputPadding[size],\n // Remove left padding when start icon section is present (icon provides visual space)\n hasStart && startType === 'icon' && 'pl-0',\n // Remove right padding when end icon section is present\n hasEnd && endType === 'icon' && 'pr-0',\n className,\n )}\n aria-invalid={state === 'error' || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n ref={ref}\n {...props}\n id={inputId}\n />\n\n {hasEnd && endType === 'label' && (\n <span\n className={cn(\n 'flex items-center shrink-0 select-none self-stretch',\n 'border-l border-surface-border',\n 'bg-surface-raised text-surface-fg-muted',\n 'rounded-r-[inherit]',\n labelPaddingMap[size],\n labelTextMap[size],\n !endSectionClickable && 'pointer-events-none',\n )}\n >\n {endSection}\n </span>\n )}\n\n {hasEnd && endType === 'icon' && (\n <IconProvider size={iconSizeMap[size]}>\n <span\n className={cn(\n 'flex items-center justify-center shrink-0 self-stretch text-surface-fg-muted',\n sectionWidthMap[size],\n !endSectionClickable && 'pointer-events-none',\n )}\n >\n {endSection}\n </span>\n </IconProvider>\n )}\n </div>\n )\n },\n)\nInput.displayName = 'Input'\n\nexport { Input, inputWrapperVariants }\n"],"mappings":";;;;;;;;AAWA,IAAM,IAAuB,EAC3B;CACE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EACD;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EACF;CACD,iBAAiB,EAAE,MAAM,MAAM;CAChC,CACF,EAEK,IAA0C;CAC9C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EAEK,IAAwC;CAC5C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EAqDK,IAAQ,EAAM,YAEhB,EACE,cACA,SACA,OAAO,GACP,MAAM,IAAW,MACjB,iBACA,eACA,0BACA,wBACA,kBAAkB,GAClB,gBAAgB,GAChB,qBACA,GAAG,KAEL,MACG;CACH,IAAM,IAAO,KAAY,MACnB,IAAW,GAAc,EAEzB,IACJ,MACC,EAAS,UAAU,WAChB,KAAA,IACC,EAAS,QACV,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAElD,IAAU,EAAM,MAAM,EAAS,SAE/B,IAAW,CAAC,CAAC,GACb,IAAS,CAAC,CAAC,GAGX,IAAY,MAAyB,OAAO,KAAiB,WAAW,UAAU,SAClF,IAAU,MAAuB,OAAO,KAAe,WAAW,UAAU,SAE5E,IAA0C;EAC9C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EAEK,IAAuC;EAC3C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL;AASD,QACE,kBAAC,OAAD;EACE,WAAW,EACT,EAAqB,EAAE,SAAM,CAAC,EAC9B,MAAU,WAAW,4CACrB,MAAU,aAAa,gDACvB,MAAU,aAAa,gDACvB,EACD;YAPH;GASG,KAAY,MAAc,WACzB,kBAAC,QAAD;IACE,WAAW,EACT,uDACA,kCACA,2CACA,uBACA,EAAgB,IAChB,EAAa,IACb,CAAC,KAAyB,sBAC3B;cAEA;IACI,CAAA;GAGR,KAAY,MAAc,UACzB,kBAAC,GAAD;IAAc,MAAM,EAAY;cAC9B,kBAAC,QAAD;KACE,WAAW,EACT,gFACA,EAAgB,IAChB,CAAC,KAAyB,sBAC3B;eAEA;KACI,CAAA;IACM,CAAA;GAGjB,kBAAC,SAAD;IACQ;IACN,WAAW,EACT,iEACA,sCACA,+BACA,4BArDqC;KAC3C,IAAI;KACJ,IAAI;KACJ,IAAI;KACJ,IAAI;KACL,CAiDoB,IAEb,KAAY,MAAc,UAAU,QAEpC,KAAU,MAAY,UAAU,QAChC,EACD;IACD,gBAAc,MAAU,WAAW,KAAA;IACnC,oBAAkB;IAClB,iBAAe,KAAgB,KAAA;IAC1B;IACL,GAAI;IACJ,IAAI;IACJ,CAAA;GAED,KAAU,MAAY,WACrB,kBAAC,QAAD;IACE,WAAW,EACT,uDACA,kCACA,2CACA,uBACA,EAAgB,IAChB,EAAa,IACb,CAAC,KAAuB,sBACzB;cAEA;IACI,CAAA;GAGR,KAAU,MAAY,UACrB,kBAAC,GAAD;IAAc,MAAM,EAAY;cAC9B,kBAAC,QAAD;KACE,WAAW,EACT,gFACA,EAAgB,IAChB,CAAC,KAAuB,sBACzB;eAEA;KACI,CAAA;IACM,CAAA;GAEb;;EAGX;AACD,EAAM,cAAc"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import * as React from 'react';
3
3
  export type SegmentedControlSize = 'sm' | 'md' | 'lg';
4
- export type SegmentedControlVariant = 'default' | 'solid' | /** @deprecated Use `'solid'` instead. */ 'accent';
4
+ export type SegmentedControlVariant = 'default' | 'solid';
5
5
  export interface SegmentedControlOption {
6
6
  id: string;
7
7
  text: string;
@@ -1 +1 @@
1
- {"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../src/ui/segmented-control.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AACrD,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,yCAAyC,CAAC,QAAQ,CAAA;AAE9G,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACnG,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,OAAO,EAAE,sBAAsB,EAAE,CAAA;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAiCD,QAAA,MAAM,gBAAgB,8FA+HrB,CAAA;AAKD,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../src/ui/segmented-control.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AACrD,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,CAAA;AAEzD,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACnG,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,OAAO,EAAE,sBAAsB,EAAE,CAAA;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AA6BD,QAAA,MAAM,gBAAgB,8FA+HrB,CAAA;AAKD,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
@@ -19,12 +19,10 @@ var o = {
19
19
  }
20
20
  }, s = {
21
21
  default: "bg-surface-overlay shadow-raised",
22
- solid: "bg-accent-9",
23
- accent: "bg-accent-9"
22
+ solid: "bg-accent-9"
24
23
  }, c = {
25
24
  default: "text-surface-fg",
26
- solid: "text-accent-fg",
27
- accent: "text-accent-fg"
25
+ solid: "text-accent-fg"
28
26
  }, l = {
29
27
  type: "spring",
30
28
  stiffness: 400,
@@ -1 +1 @@
1
- {"version":3,"file":"segmented-control.js","names":[],"sources":["../../src/ui/segmented-control.tsx"],"sourcesContent":["'use client'\n\nimport { LayoutGroup,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\n/* ── Types ─────────────────────────────────────────────────── */\n\nexport type SegmentedControlSize = 'sm' | 'md' | 'lg'\nexport type SegmentedControlVariant = 'default' | 'solid' | /** @deprecated Use `'solid'` instead. */ 'accent'\n\nexport interface SegmentedControlOption {\n id: string\n text: string\n /** Optional icon component rendered before the text label. */\n icon?: React.ComponentType<{ className?: string }>\n}\n\nexport interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n size?: SegmentedControlSize\n variant?: SegmentedControlVariant\n options: SegmentedControlOption[]\n selectedId: string\n onSelect: (id: string) => void\n disabled?: boolean\n}\n\n/* ── Size config ───────────────────────────────────────────── */\n\nconst sizeConfig = {\n sm: { button: 'h-7 px-ds-04 text-ds-sm', icon: 'h-3.5 w-3.5' },\n md: { button: 'h-8 px-ds-05 text-ds-md', icon: 'h-4 w-4' },\n lg: { button: 'h-10 px-ds-06 text-ds-md', icon: 'h-4 w-4' },\n} as const\n\n/* ── Pill styles per variant ───────────────────────────────── */\n\nconst pillStyles = {\n default: 'bg-surface-overlay shadow-raised',\n solid: 'bg-accent-9',\n /** @deprecated Use `solid` instead. */\n accent: 'bg-accent-9',\n} as const\n\nconst selectedTextStyles = {\n default: 'text-surface-fg',\n solid: 'text-accent-fg',\n /** @deprecated Use `solid` instead. */\n accent: 'text-accent-fg',\n} as const\n\n/* ── Spring config (snappy, minimal overshoot) ─────────────── */\n/* Intentionally softer than springs.snappy (500/30/0.5) for pill slide feel */\n\nconst pillSpring = { type: 'spring' as const, stiffness: 400, damping: 30 }\n\n/* ── SegmentedControl ──────────────────────────────────────── */\n\nconst SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n function SegmentedControl(\n {\n size = 'md',\n variant = 'default',\n options,\n selectedId,\n onSelect,\n disabled = false,\n className,\n ...props\n },\n ref,\n ) {\n const instanceId = React.useId()\n const tablistRef = React.useRef<HTMLDivElement | null>(null)\n\n // Compose refs\n const mergedRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n tablistRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n },\n [ref],\n )\n\n // Keyboard navigation\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return\n\n const currentIndex = options.findIndex((o) => o.id === selectedId)\n let nextIndex = currentIndex\n\n switch (e.key) {\n case 'ArrowLeft':\n e.preventDefault()\n nextIndex = currentIndex > 0 ? currentIndex - 1 : options.length - 1\n break\n case 'ArrowRight':\n e.preventDefault()\n nextIndex = currentIndex < options.length - 1 ? currentIndex + 1 : 0\n break\n case 'Home':\n e.preventDefault()\n nextIndex = 0\n break\n case 'End':\n e.preventDefault()\n nextIndex = options.length - 1\n break\n default:\n return\n }\n\n onSelect(options[nextIndex].id)\n requestAnimationFrame(() => {\n const buttons = tablistRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n buttons?.[nextIndex]?.focus()\n })\n }\n\n const { button: buttonSize, icon: iconSize } = sizeConfig[size]\n\n return (\n <div\n ref={mergedRef}\n role=\"tablist\"\n tabIndex={-1}\n aria-label={props['aria-label'] ?? 'Segmented control'}\n onKeyDown={handleKeyDown}\n className={cn(\n 'inline-flex p-[3px] rounded-ds-lg',\n 'bg-surface-raised-hover border border-surface-border-subtle shadow-inset',\n disabled && 'opacity-action-disabled pointer-events-none',\n className,\n )}\n {...props}\n >\n <LayoutGroup id={instanceId}>\n {options.map((option) => {\n const isSelected = option.id === selectedId\n const OptionIcon = option.icon\n\n return (\n <button\n key={option.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={isSelected}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n onClick={() => onSelect(option.id)}\n className={cn(\n 'relative inline-flex items-center justify-center gap-ds-02 rounded-ds-md',\n 'font-medium transition-colors duration-fast-02 ease-productive-standard',\n 'outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2',\n buttonSize,\n isSelected\n ? selectedTextStyles[variant]\n : 'text-surface-fg-muted hover:text-surface-fg',\n )}\n >\n {/* Sliding pill indicator */}\n {isSelected && (\n <motion.span\n layoutId=\"segment-pill\"\n className={cn(\n 'absolute inset-0 rounded-ds-md pointer-events-none',\n pillStyles[variant],\n )}\n transition={pillSpring}\n />\n )}\n\n {/* Content (above pill via z-index) */}\n {OptionIcon && (\n <OptionIcon className={cn('relative z-[1] shrink-0', iconSize)} />\n )}\n <span className=\"relative z-[1]\">{option.text}</span>\n </button>\n )\n })}\n </LayoutGroup>\n </div>\n )\n },\n)\nSegmentedControl.displayName = 'SegmentedControl'\n\n/* ── Exports ───────────────────────────────────────────────── */\n\nexport { SegmentedControl }\n"],"mappings":";;;;;;AA8BA,IAAM,IAAa;CACjB,IAAI;EAAE,QAAQ;EAA2B,MAAM;EAAe;CAC9D,IAAI;EAAE,QAAQ;EAA2B,MAAM;EAAW;CAC1D,IAAI;EAAE,QAAQ;EAA4B,MAAM;EAAW;CAC5D,EAIK,IAAa;CACjB,SAAS;CACT,OAAO;CAEP,QAAQ;CACT,EAEK,IAAqB;CACzB,SAAS;CACT,OAAO;CAEP,QAAQ;CACT,EAKK,IAAa;CAAE,MAAM;CAAmB,WAAW;CAAK,SAAS;CAAI,EAIrE,IAAmB,EAAM,WAC7B,SACE,EACE,UAAO,MACP,aAAU,WACV,YACA,eACA,aACA,cAAW,IACX,cACA,GAAG,KAEL,GACA;CACA,IAAM,IAAa,EAAM,OAAO,EAC1B,IAAa,EAAM,OAA8B,KAAK,EAGtD,IAAY,EAAM,aACrB,MAAgC;AAE/B,EADA,EAAW,UAAU,GACjB,OAAO,KAAQ,aAAY,EAAI,EAAK,GAC/B,MAAM,EAAsD,UAAU;IAEjF,CAAC,EAAI,CACN,EAGK,KAAiB,MAA2C;AAChE,MAAI,EAAU;EAEd,IAAM,IAAe,EAAQ,WAAW,MAAM,EAAE,OAAO,EAAW,EAC9D,IAAY;AAEhB,UAAQ,EAAE,KAAV;GACE,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY,IAAe,IAAI,IAAe,IAAI,EAAQ,SAAS;AACnE;GACF,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY,IAAe,EAAQ,SAAS,IAAI,IAAe,IAAI;AACnE;GACF,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY;AACZ;GACF,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY,EAAQ,SAAS;AAC7B;GACF,QACE;;AAIJ,EADA,EAAS,EAAQ,GAAW,GAAG,EAC/B,4BAA4B;AAE1B,IADgB,EAAW,SAAS,iBAAoC,iBAAe,IAC7E,IAAY,OAAO;IAC7B;IAGE,EAAE,QAAQ,GAAY,MAAM,MAAa,EAAW;AAE1D,QACE,kBAAC,OAAD;EACE,KAAK;EACL,MAAK;EACL,UAAU;EACV,cAAY,EAAM,iBAAiB;EACnC,WAAW;EACX,WAAW,EACT,qCACA,4EACA,KAAY,+CACZ,EACD;EACD,GAAI;YAEJ,kBAAC,GAAD;GAAa,IAAI;aACd,EAAQ,KAAK,MAAW;IACvB,IAAM,IAAa,EAAO,OAAO,GAC3B,IAAa,EAAO;AAE1B,WACE,kBAAC,UAAD;KAEE,MAAK;KACL,MAAK;KACL,iBAAe;KACf,UAAU,IAAa,IAAI;KACjB;KACV,eAAe,EAAS,EAAO,GAAG;KAClC,WAAW,EACT,4EACA,2EACA,+FACA,GACA,IACI,EAAmB,KACnB,8CACL;eAhBH;MAmBG,KACC,kBAAC,EAAO,MAAR;OACE,UAAS;OACT,WAAW,EACT,sDACA,EAAW,GACZ;OACD,YAAY;OACZ,CAAA;MAIH,KACC,kBAAC,GAAD,EAAY,WAAW,EAAG,2BAA2B,EAAS,EAAI,CAAA;MAEpE,kBAAC,QAAD;OAAM,WAAU;iBAAkB,EAAO;OAAY,CAAA;MAC9C;OAlCF,EAAO,GAkCL;KAEX;GACU,CAAA;EACV,CAAA;EAGX;AACD,EAAiB,cAAc"}
1
+ {"version":3,"file":"segmented-control.js","names":[],"sources":["../../src/ui/segmented-control.tsx"],"sourcesContent":["'use client'\n\nimport { LayoutGroup,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\n/* ── Types ─────────────────────────────────────────────────── */\n\nexport type SegmentedControlSize = 'sm' | 'md' | 'lg'\nexport type SegmentedControlVariant = 'default' | 'solid'\n\nexport interface SegmentedControlOption {\n id: string\n text: string\n /** Optional icon component rendered before the text label. */\n icon?: React.ComponentType<{ className?: string }>\n}\n\nexport interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n size?: SegmentedControlSize\n variant?: SegmentedControlVariant\n options: SegmentedControlOption[]\n selectedId: string\n onSelect: (id: string) => void\n disabled?: boolean\n}\n\n/* ── Size config ───────────────────────────────────────────── */\n\nconst sizeConfig = {\n sm: { button: 'h-7 px-ds-04 text-ds-sm', icon: 'h-3.5 w-3.5' },\n md: { button: 'h-8 px-ds-05 text-ds-md', icon: 'h-4 w-4' },\n lg: { button: 'h-10 px-ds-06 text-ds-md', icon: 'h-4 w-4' },\n} as const\n\n/* ── Pill styles per variant ───────────────────────────────── */\n\nconst pillStyles = {\n default: 'bg-surface-overlay shadow-raised',\n solid: 'bg-accent-9',\n} as const\n\nconst selectedTextStyles = {\n default: 'text-surface-fg',\n solid: 'text-accent-fg',\n} as const\n\n/* ── Spring config (snappy, minimal overshoot) ─────────────── */\n/* Intentionally softer than springs.snappy (500/30/0.5) for pill slide feel */\n\nconst pillSpring = { type: 'spring' as const, stiffness: 400, damping: 30 }\n\n/* ── SegmentedControl ──────────────────────────────────────── */\n\nconst SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n function SegmentedControl(\n {\n size = 'md',\n variant = 'default',\n options,\n selectedId,\n onSelect,\n disabled = false,\n className,\n ...props\n },\n ref,\n ) {\n const instanceId = React.useId()\n const tablistRef = React.useRef<HTMLDivElement | null>(null)\n\n // Compose refs\n const mergedRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n tablistRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n },\n [ref],\n )\n\n // Keyboard navigation\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return\n\n const currentIndex = options.findIndex((o) => o.id === selectedId)\n let nextIndex = currentIndex\n\n switch (e.key) {\n case 'ArrowLeft':\n e.preventDefault()\n nextIndex = currentIndex > 0 ? currentIndex - 1 : options.length - 1\n break\n case 'ArrowRight':\n e.preventDefault()\n nextIndex = currentIndex < options.length - 1 ? currentIndex + 1 : 0\n break\n case 'Home':\n e.preventDefault()\n nextIndex = 0\n break\n case 'End':\n e.preventDefault()\n nextIndex = options.length - 1\n break\n default:\n return\n }\n\n onSelect(options[nextIndex].id)\n requestAnimationFrame(() => {\n const buttons = tablistRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n buttons?.[nextIndex]?.focus()\n })\n }\n\n const { button: buttonSize, icon: iconSize } = sizeConfig[size]\n\n return (\n <div\n ref={mergedRef}\n role=\"tablist\"\n tabIndex={-1}\n aria-label={props['aria-label'] ?? 'Segmented control'}\n onKeyDown={handleKeyDown}\n className={cn(\n 'inline-flex p-[3px] rounded-ds-lg',\n 'bg-surface-raised-hover border border-surface-border-subtle shadow-inset',\n disabled && 'opacity-action-disabled pointer-events-none',\n className,\n )}\n {...props}\n >\n <LayoutGroup id={instanceId}>\n {options.map((option) => {\n const isSelected = option.id === selectedId\n const OptionIcon = option.icon\n\n return (\n <button\n key={option.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={isSelected}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n onClick={() => onSelect(option.id)}\n className={cn(\n 'relative inline-flex items-center justify-center gap-ds-02 rounded-ds-md',\n 'font-medium transition-colors duration-fast-02 ease-productive-standard',\n 'outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2',\n buttonSize,\n isSelected\n ? selectedTextStyles[variant]\n : 'text-surface-fg-muted hover:text-surface-fg',\n )}\n >\n {/* Sliding pill indicator */}\n {isSelected && (\n <motion.span\n layoutId=\"segment-pill\"\n className={cn(\n 'absolute inset-0 rounded-ds-md pointer-events-none',\n pillStyles[variant],\n )}\n transition={pillSpring}\n />\n )}\n\n {/* Content (above pill via z-index) */}\n {OptionIcon && (\n <OptionIcon className={cn('relative z-[1] shrink-0', iconSize)} />\n )}\n <span className=\"relative z-[1]\">{option.text}</span>\n </button>\n )\n })}\n </LayoutGroup>\n </div>\n )\n },\n)\nSegmentedControl.displayName = 'SegmentedControl'\n\n/* ── Exports ───────────────────────────────────────────────── */\n\nexport { SegmentedControl }\n"],"mappings":";;;;;;AA8BA,IAAM,IAAa;CACjB,IAAI;EAAE,QAAQ;EAA2B,MAAM;EAAe;CAC9D,IAAI;EAAE,QAAQ;EAA2B,MAAM;EAAW;CAC1D,IAAI;EAAE,QAAQ;EAA4B,MAAM;EAAW;CAC5D,EAIK,IAAa;CACjB,SAAS;CACT,OAAO;CACR,EAEK,IAAqB;CACzB,SAAS;CACT,OAAO;CACR,EAKK,IAAa;CAAE,MAAM;CAAmB,WAAW;CAAK,SAAS;CAAI,EAIrE,IAAmB,EAAM,WAC7B,SACE,EACE,UAAO,MACP,aAAU,WACV,YACA,eACA,aACA,cAAW,IACX,cACA,GAAG,KAEL,GACA;CACA,IAAM,IAAa,EAAM,OAAO,EAC1B,IAAa,EAAM,OAA8B,KAAK,EAGtD,IAAY,EAAM,aACrB,MAAgC;AAE/B,EADA,EAAW,UAAU,GACjB,OAAO,KAAQ,aAAY,EAAI,EAAK,GAC/B,MAAM,EAAsD,UAAU;IAEjF,CAAC,EAAI,CACN,EAGK,KAAiB,MAA2C;AAChE,MAAI,EAAU;EAEd,IAAM,IAAe,EAAQ,WAAW,MAAM,EAAE,OAAO,EAAW,EAC9D,IAAY;AAEhB,UAAQ,EAAE,KAAV;GACE,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY,IAAe,IAAI,IAAe,IAAI,EAAQ,SAAS;AACnE;GACF,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY,IAAe,EAAQ,SAAS,IAAI,IAAe,IAAI;AACnE;GACF,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY;AACZ;GACF,KAAK;AAEH,IADA,EAAE,gBAAgB,EAClB,IAAY,EAAQ,SAAS;AAC7B;GACF,QACE;;AAIJ,EADA,EAAS,EAAQ,GAAW,GAAG,EAC/B,4BAA4B;AAE1B,IADgB,EAAW,SAAS,iBAAoC,iBAAe,IAC7E,IAAY,OAAO;IAC7B;IAGE,EAAE,QAAQ,GAAY,MAAM,MAAa,EAAW;AAE1D,QACE,kBAAC,OAAD;EACE,KAAK;EACL,MAAK;EACL,UAAU;EACV,cAAY,EAAM,iBAAiB;EACnC,WAAW;EACX,WAAW,EACT,qCACA,4EACA,KAAY,+CACZ,EACD;EACD,GAAI;YAEJ,kBAAC,GAAD;GAAa,IAAI;aACd,EAAQ,KAAK,MAAW;IACvB,IAAM,IAAa,EAAO,OAAO,GAC3B,IAAa,EAAO;AAE1B,WACE,kBAAC,UAAD;KAEE,MAAK;KACL,MAAK;KACL,iBAAe;KACf,UAAU,IAAa,IAAI;KACjB;KACV,eAAe,EAAS,EAAO,GAAG;KAClC,WAAW,EACT,4EACA,2EACA,+FACA,GACA,IACI,EAAmB,KACnB,8CACL;eAhBH;MAmBG,KACC,kBAAC,EAAO,MAAR;OACE,UAAS;OACT,WAAW,EACT,sDACA,EAAW,GACZ;OACD,YAAY;OACZ,CAAA;MAIH,KACC,kBAAC,GAAD,EAAY,WAAW,EAAG,2BAA2B,EAAS,EAAI,CAAA;MAEpE,kBAAC,QAAD;OAAM,WAAU;iBAAkB,EAAO;OAAY,CAAA;MAC9C;OAlCF,EAAO,GAkCL;KAEX;GACU,CAAA;EACV,CAAA;EAGX;AACD,EAAiB,cAAc"}
@@ -39,7 +39,7 @@ pnpm add sonner
39
39
  transpilePackages: ['@devalok/shilp-sutra', '@devalok/shilp-sutra-brand'],
40
40
  ```
41
41
 
42
- **Do NOT** import `@devalok/shilp-sutra/tailwind` in a `tailwind.config.ts`. That export is a deprecated no-op stub and logs a dev-mode `console.warn`. It is scheduled for removal in 0.38.
42
+ **Do NOT** import `@devalok/shilp-sutra/tailwind`. That export was removed in 0.38.0. Use the CSS-first setup (`@import "tailwindcss"; @import "@devalok/shilp-sutra/css";`) — no `tailwind.config.ts` is required.
43
43
 
44
44
  ### Peer dependencies (0.37.0)
45
45
 
@@ -5,7 +5,7 @@
5
5
  - Category: ui
6
6
 
7
7
  ## Props
8
- variant: "subtle" | "solid" | "outline" | "filled" (deprecated alias for "solid")
8
+ variant: "subtle" | "solid" | "outline"
9
9
  color: "info" | "success" | "warning" | "error" | "neutral"
10
10
  size: "sm" | "md" | "lg"
11
11
  title: string (optional)
@@ -36,6 +36,9 @@
36
36
  - Icon is auto-selected by color (info=circle, success=check, warning=triangle, error=alert)
37
37
 
38
38
  ## Changes
39
+ ### v0.38.0
40
+ - **Removed** (BREAKING) `variant="filled"` deprecated alias. Use `variant="solid"`.
41
+
39
42
  ### v0.31.0
40
43
  - **Added** `size` prop: `sm | md | lg`. Default `md` (non-breaking).
41
44
 
@@ -7,7 +7,6 @@
7
7
  ## Props
8
8
  color: "info" | "success" | "warning" | "error" | "neutral"
9
9
  actions: ReactNode (optional action slot for one or more buttons; wraps on narrow viewports)
10
- action: ReactNode (DEPRECATED — use `actions` instead)
11
10
  onDismiss: () => void (optional, shows X button)
12
11
  children: ReactNode (message text)
13
12
 
@@ -34,6 +33,9 @@
34
33
  - `onDismiss` fires after the exit animation completes, not immediately on dismiss button click
35
34
 
36
35
  ## Changes
36
+ ### v0.38.0
37
+ - **Removed** (BREAKING) deprecated singular `action` prop. Use `actions={[...]}` (plural).
38
+
37
39
  ### v0.20.0
38
40
  - **Added** `actions` prop (plural) for multiple action buttons with mobile-friendly flex-wrap
39
41
  - **Deprecated** `action` prop — use `actions` instead (both still work; `actions` takes priority)
@@ -14,8 +14,6 @@
14
14
  startSectionType: 'icon' | 'label' (section display type — auto-inferred from content)
15
15
  endSectionType: 'icon' | 'label' (section display type — auto-inferred from content)
16
16
  wrapperClassName: string (classes for the wrapper div — border, bg, ring)
17
- startIcon: ReactNode (@deprecated — use startSection)
18
- endIcon: ReactNode (@deprecated — use endSection)
19
17
  (plus all standard HTML input attributes except native "size")
20
18
 
21
19
  ## Types
@@ -50,6 +48,10 @@
50
48
  - Section type is auto-inferred: strings default to `'label'` (tinted bg + border), React elements default to `'icon'` (fixed-width centered). Override with `startSectionType`/`endSectionType`.
51
49
 
52
50
  ## Changes
51
+ ### v0.38.0
52
+ - **Removed** (BREAKING) deprecated `startIcon` / `endIcon` props. Use `startSection` / `endSection`.
53
+ - **Removed** (BREAKING) deprecated `inputVariants` export. Use `inputWrapperVariants`.
54
+
53
55
  ### v0.29.0
54
56
  - **Changed** v2 rewrite: container-first architecture with wrapper div holding focus ring
55
57
  - **Added** `xs` size (28px height)
@@ -5,8 +5,8 @@
5
5
  - Category: ui
6
6
 
7
7
  ## Props
8
- size: "sm" | "md" | "lg" (REQUIRED) — also accepts legacy "small" | "medium" | "big"
9
- variant: "filled" | "tonal" (REQUIRED)
8
+ size: "sm" | "md" | "lg"
9
+ variant: "default" | "solid"
10
10
  options: SegmentedControlOption[] (REQUIRED)
11
11
  selectedId: string (REQUIRED)
12
12
  onSelect: (id: string) => void (REQUIRED)
@@ -14,14 +14,18 @@
14
14
 
15
15
  ## Types
16
16
  SegmentedControlOption = { id: string, text: string, icon?: ComponentType<{ className?: string }> }
17
- SegmentedControlSize = 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'big' (legacy aliases)
18
- SegmentedControlVariant = 'filled' | 'tonal'
17
+ SegmentedControlSize = 'sm' | 'md' | 'lg'
18
+ SegmentedControlVariant = 'default' | 'solid'
19
+
20
+ ## Defaults
21
+ size: "md"
22
+ variant: "default"
19
23
 
20
24
  ## Example
21
25
  ```jsx
22
26
  <SegmentedControl
23
27
  size="md"
24
- variant="tonal"
28
+ variant="default"
25
29
  options={[
26
30
  { id: 'list', text: 'List' },
27
31
  { id: 'grid', text: 'Grid' },
@@ -44,11 +48,14 @@
44
48
  - Use Tabs (not SegmentedControl) when you need associated content panels per option
45
49
 
46
50
  ## Changes
51
+ ### v0.38.0
52
+ - **Removed** (BREAKING) deprecated `variant="accent"` alias. Use `variant="solid"`.
53
+
47
54
  ### v0.18.0
48
55
  - **Fixed** `bg-interactive` changed to `bg-accent-9`, `bg-field` changed to `bg-surface-3` (OKLCH migration)
49
56
 
50
57
  ### v0.4.2
51
- - **Changed** (BREAKING) `color` prop renamed to `variant` (values `filled`/`tonal` unchanged)
58
+ - **Changed** (BREAKING) `color` prop renamed to `variant`
52
59
 
53
60
  ### v0.1.1
54
61
  - **Fixed** `tabIndex={0}` changed to `tabIndex={-1}` on tablist wrapper — fixes double-focus keyboard navigation bug
@@ -0,0 +1,216 @@
1
+ # Customize: Brand & Tokens
2
+
3
+ Override colors, radius, fonts, and spacing without forking the package. All customization happens through CSS custom properties — no JS configuration.
4
+
5
+ ## How tokens layer
6
+
7
+ Shilp Sutra's tokens are organized in three tiers:
8
+
9
+ 1. **Primitives** (`primitives.css`) — raw OKLCH palette values. Private. You should not need to touch these.
10
+ 2. **Semantic** (`semantic.css`) — intent-based mappings (`--color-accent-9`, `--color-surface-2`, `--font-sans`). Public, exposed to Tailwind 4's `@theme`.
11
+ 3. **Utility output** — Tailwind generates classes (`bg-accent-9`, `text-surface-fg`, `font-sans`) from the semantic layer.
12
+
13
+ To override a token, redefine it AFTER the design-system import. The cascade does the rest.
14
+
15
+ ```css
16
+ @import "tailwindcss";
17
+ @import "@devalok/shilp-sutra/css";
18
+
19
+ @theme {
20
+ /* Overrides go here */
21
+ --color-accent-9: oklch(0.55 0.22 195);
22
+ }
23
+ ```
24
+
25
+ `@theme` is the Tailwind 4 directive that registers a CSS variable AND auto-generates the matching utility (so `bg-accent-9` picks up the override).
26
+
27
+ ## Common overrides
28
+
29
+ ### Change the accent color
30
+
31
+ The accent scale runs from `--color-accent-1` (lightest) to `--color-accent-12` (darkest). Step 9 is the saturated default-button background, step 11 is on-tint text.
32
+
33
+ To swap to teal:
34
+
35
+ ```css
36
+ @theme {
37
+ --color-accent-1: oklch(0.985 0.005 195);
38
+ --color-accent-2: oklch(0.96 0.012 195);
39
+ --color-accent-3: oklch(0.92 0.025 195);
40
+ --color-accent-4: oklch(0.88 0.04 195);
41
+ --color-accent-5: oklch(0.82 0.07 195);
42
+ --color-accent-6: oklch(0.74 0.10 195);
43
+ --color-accent-7: oklch(0.66 0.14 195);
44
+ --color-accent-8: oklch(0.60 0.17 195);
45
+ --color-accent-9: oklch(0.54 0.22 195);
46
+ --color-accent-10: oklch(0.49 0.21 195);
47
+ --color-accent-11: oklch(0.42 0.16 195);
48
+ --color-accent-12: oklch(0.20 0.05 195);
49
+ }
50
+
51
+ .dark {
52
+ --color-accent-9: oklch(0.62 0.21 195);
53
+ /* ...override remaining steps for dark mode */
54
+ }
55
+ ```
56
+
57
+ For a quick swap without redoing the full scale, only override step 9 (background) and step 11 (foreground). Other steps will still reference the original chroma — visually mismatched but functional.
58
+
59
+ ### Change the radius scale
60
+
61
+ ```css
62
+ @theme {
63
+ --radius-ds-sm: 0.25rem; /* small */
64
+ --radius-ds-md: 0.5rem; /* default */
65
+ --radius-ds-lg: 0.75rem;
66
+ --radius-ds-xl: 1rem;
67
+ --radius: 0.5rem; /* unsuffixed — generates bare `rounded` */
68
+ }
69
+ ```
70
+
71
+ For a flat, sharp brand, set everything to `0`. For a heavily rounded brand, scale up.
72
+
73
+ ### Change fonts
74
+
75
+ The design system uses three font families, each backed by a CSS variable:
76
+
77
+ - `--font-sans` — Inter (body)
78
+ - `--font-display` — Ranade (headings)
79
+ - `--font-mono` — JetBrains Mono (code)
80
+
81
+ To swap with `next/font`:
82
+
83
+ ```tsx
84
+ // app/layout.tsx (Next.js App Router)
85
+ import { Inter, Plus_Jakarta_Sans, JetBrains_Mono } from "next/font/google";
86
+
87
+ const fontSans = Plus_Jakarta_Sans({ subsets: ["latin"], variable: "--font-sans" });
88
+ const fontDisplay = Plus_Jakarta_Sans({ subsets: ["latin"], weight: "700", variable: "--font-display" });
89
+ const fontMono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-mono" });
90
+
91
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
92
+ return (
93
+ <html lang="en" className={`${fontSans.variable} ${fontDisplay.variable} ${fontMono.variable}`}>
94
+ <body>{children}</body>
95
+ </html>
96
+ );
97
+ }
98
+ ```
99
+
100
+ For Vite/Remix/Astro/TanStack, drop the `@font-face` declarations into the global CSS and override the variables:
101
+
102
+ ```css
103
+ @font-face {
104
+ font-family: "Plus Jakarta Sans";
105
+ src: url("/fonts/PlusJakartaSans-Variable.woff2") format("woff2");
106
+ font-weight: 100 900;
107
+ font-display: swap;
108
+ }
109
+
110
+ @theme {
111
+ --font-sans: "Plus Jakarta Sans", system-ui, sans-serif;
112
+ --font-display: "Plus Jakarta Sans", system-ui, sans-serif;
113
+ }
114
+ ```
115
+
116
+ ### Change the spacing scale
117
+
118
+ The DS spacing scale runs `--spacing-ds-00` (0) to `--spacing-ds-12` (highest). Each step roughly doubles. The Tailwind classes are `p-ds-04`, `m-ds-08`, `gap-ds-03`, etc.
119
+
120
+ ```css
121
+ @theme {
122
+ --spacing-ds-00: 0;
123
+ --spacing-ds-01: 0.125rem;
124
+ --spacing-ds-02: 0.25rem;
125
+ --spacing-ds-03: 0.5rem;
126
+ --spacing-ds-04: 0.75rem;
127
+ --spacing-ds-05: 1rem;
128
+ /* etc. */
129
+ }
130
+ ```
131
+
132
+ Most consumers should not touch this. The DS scale is tuned for the typography rhythm; changing one step without re-tuning typography looks visually off.
133
+
134
+ ### Change a single shadow
135
+
136
+ ```css
137
+ @theme {
138
+ --shadow-raised: 0 1px 2px oklch(0 0 0 / 0.06), 0 2px 4px oklch(0 0 0 / 0.04);
139
+ }
140
+ ```
141
+
142
+ Available shadows: `--shadow-raised`, `--shadow-overlay`, `--shadow-floating`, `--shadow-brand`. Each is a multi-layer composite — overriding one without considering its layers usually produces flat-looking shadows.
143
+
144
+ ## Light + dark in lockstep
145
+
146
+ Every override should consider both modes. Pattern:
147
+
148
+ ```css
149
+ @theme {
150
+ --color-accent-9: oklch(0.54 0.22 195);
151
+ --color-accent-fg: oklch(0.99 0 0);
152
+ }
153
+
154
+ /* Dark mode override */
155
+ .dark {
156
+ --color-accent-9: oklch(0.62 0.21 195);
157
+ --color-accent-fg: oklch(0.10 0 0);
158
+ }
159
+ ```
160
+
161
+ `.dark` is a regular class selector — its specificity is higher than `:root`/`@theme` body, so dark-mode overrides win when the class is active.
162
+
163
+ ## Forced colors (Windows high-contrast)
164
+
165
+ If you override semantic colors, the `@media (forced-colors: active)` block in `semantic.css` continues to remap to system keywords. Your override is ignored when the user is in high-contrast mode — this is the correct behavior.
166
+
167
+ If you need a custom forced-colors mapping, override inside `@media (forced-colors: active)`:
168
+
169
+ ```css
170
+ @media (forced-colors: active) {
171
+ :root {
172
+ --color-accent-9: Highlight;
173
+ --color-accent-fg: HighlightText;
174
+ }
175
+ }
176
+ ```
177
+
178
+ ## Per-route theming
179
+
180
+ Apply a class on a wrapper element to scope token overrides:
181
+
182
+ ```css
183
+ .theme-karm {
184
+ --color-accent-9: oklch(0.55 0.22 195);
185
+ }
186
+ .theme-karm.dark,
187
+ .dark .theme-karm {
188
+ --color-accent-9: oklch(0.62 0.21 195);
189
+ }
190
+ ```
191
+
192
+ Then:
193
+
194
+ ```tsx
195
+ <div className="theme-karm">
196
+ <Button>Karm-themed</Button>
197
+ </div>
198
+ ```
199
+
200
+ ## What you cannot override via CSS variables
201
+
202
+ - The component DOM structure (use the `asChild` pattern or wrap the component to insert)
203
+ - The vendored Radix primitives' behavior
204
+ - Animation keyframes (override the `--animate-*` references via `@theme` — but the keyframe definitions themselves are package-private)
205
+
206
+ For deeper changes, fork the source or use the `className` prop with arbitrary Tailwind utilities to layer styling on top.
207
+
208
+ ## Verifying an override
209
+
210
+ Open DevTools, inspect a component, and check the Computed tab for the variable's resolved value. The cascade origin column shows where the value came from — your override should appear there, not the package's default.
211
+
212
+ If the override isn't winning, check:
213
+
214
+ 1. The `@theme` override comes AFTER `@import "@devalok/shilp-sutra/css";` in source order
215
+ 2. There's no `!important` in the package overriding you (there isn't — file an issue if you find one)
216
+ 3. The selector specificity matches the target (`.dark` class is on a parent of the inspected element, etc.)
@@ -0,0 +1,51 @@
1
+ # Recipes
2
+
3
+ Copy-paste-ready setup guides for installing and customizing **`@devalok/shilp-sutra`** in any React project.
4
+
5
+ These files are designed to be read by AI coding agents (Claude Code, Cursor, Copilot, Codex) and humans alike. Every step is concrete: file paths, exact strings, expected outcomes.
6
+
7
+ When the package is installed, recipes ship at:
8
+
9
+ ```
10
+ node_modules/@devalok/shilp-sutra/docs/recipes/
11
+ ```
12
+
13
+ ## Install recipes
14
+
15
+ Pick the recipe that matches your framework. Detection criteria are listed at the top of each file.
16
+
17
+ | Framework | Recipe | When to pick it |
18
+ |---|---|---|
19
+ | Next.js (App Router) | [install-next-app-router.md](./install-next-app-router.md) | `app/` directory exists; using React Server Components |
20
+ | Next.js (Pages Router) | [install-next-pages.md](./install-next-pages.md) | `pages/` directory is the primary router; legacy or pre-13 codebases |
21
+ | Vite + React | [install-vite.md](./install-vite.md) | `vite.config.*` exists; SPA or MPA |
22
+ | Astro | [install-astro.md](./install-astro.md) | `astro.config.*` exists |
23
+ | Remix | [install-remix.md](./install-remix.md) | `remix.config.*` or Remix v2 with Vite |
24
+ | TanStack Start | [install-tanstack-start.md](./install-tanstack-start.md) | `app.config.*` with `@tanstack/start` |
25
+
26
+ If the framework is not listed, start with [install-vite.md](./install-vite.md) and adapt — most React+Tailwind setups follow the same shape.
27
+
28
+ ## Customization recipes
29
+
30
+ | Recipe | What it covers |
31
+ |---|---|
32
+ | [customize-brand.md](./customize-brand.md) | Token override cookbook — change colors, radius, fonts, spacing scale, dark-mode mapping |
33
+ | [server-components.md](./server-components.md) | Per-component RSC-safety matrix; correct import patterns for Server Components |
34
+
35
+ ## Diagnostics
36
+
37
+ | Recipe | What it covers |
38
+ |---|---|
39
+ | [troubleshoot.md](./troubleshoot.md) | Decision tree for the eight most common breakages: Tailwind not detecting tokens, framer-motion duplicates, missing `transpilePackages`, wrong CSS import order, dark mode not toggling, RSC import errors, font 404s, hydration mismatches |
40
+
41
+ ## What every recipe assumes
42
+
43
+ - React `^18 || ^19`
44
+ - Node `>= 18.18` (no `engines.node` declared by us, but Tailwind 4 + Vite 5+ require it)
45
+ - A bundler that supports CSS imports from `node_modules` (every modern React framework does)
46
+
47
+ ## What every recipe excludes
48
+
49
+ - Database/auth/state-management setup — out of scope. Shilp Sutra is presentation-only.
50
+ - IDE configuration — your call.
51
+ - Testing setup — see [packages/core/CONTRIBUTING.md](../../CONTRIBUTING.md) if contributing to the design system itself.