@devalok/shilp-sutra 0.42.1 → 0.44.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 (130) hide show
  1. package/MIGRATION.md +78 -0
  2. package/dist/_chunks/chat.js +164 -163
  3. package/dist/_chunks/chat.js.map +1 -1
  4. package/dist/_chunks/mention-suggestion.js +35 -33
  5. package/dist/_chunks/mention-suggestion.js.map +1 -1
  6. package/dist/_chunks/oauth-button.js +102 -97
  7. package/dist/_chunks/oauth-button.js.map +1 -1
  8. package/dist/_chunks/popover.js +1 -1
  9. package/dist/_chunks/popover.js.map +1 -1
  10. package/dist/_chunks/shared.js +1 -1
  11. package/dist/_chunks/shared.js.map +1 -1
  12. package/dist/_chunks/tree-view.js +1 -1
  13. package/dist/_chunks/tree-view.js.map +1 -1
  14. package/dist/ai/command-bar.js +1 -1
  15. package/dist/ai/command-bar.js.map +1 -1
  16. package/dist/composed/bulk-action-bar.js +1 -1
  17. package/dist/composed/bulk-action-bar.js.map +1 -1
  18. package/dist/composed/command-palette.js +4 -4
  19. package/dist/composed/command-palette.js.map +1 -1
  20. package/dist/composed/content-card.d.ts +6 -0
  21. package/dist/composed/content-card.d.ts.map +1 -1
  22. package/dist/composed/content-card.js +1 -1
  23. package/dist/composed/content-card.js.map +1 -1
  24. package/dist/composed/error-boundary.js +1 -1
  25. package/dist/composed/error-boundary.js.map +1 -1
  26. package/dist/composed/extensions/emoji-suggestion.js +1 -1
  27. package/dist/composed/extensions/emoji-suggestion.js.map +1 -1
  28. package/dist/composed/extensions/file-attachment.d.ts.map +1 -1
  29. package/dist/composed/file-preview/audio-preview.d.ts.map +1 -1
  30. package/dist/composed/file-preview.d.ts.map +1 -1
  31. package/dist/composed/file-preview.js +147 -143
  32. package/dist/composed/file-preview.js.map +1 -1
  33. package/dist/composed/filter-bar.js +5 -2
  34. package/dist/composed/filter-bar.js.map +1 -1
  35. package/dist/composed/master-detail.js +1 -1
  36. package/dist/composed/master-detail.js.map +1 -1
  37. package/dist/composed/page-header.js +1 -1
  38. package/dist/composed/page-header.js.map +1 -1
  39. package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
  40. package/dist/composed/rich-chat-input/audio-player.d.ts.map +1 -1
  41. package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
  42. package/dist/composed/rich-chat-input.js +600 -591
  43. package/dist/composed/rich-chat-input.js.map +1 -1
  44. package/dist/composed/rich-text-editor.d.ts.map +1 -1
  45. package/dist/composed/rich-text-editor.js +166 -164
  46. package/dist/composed/rich-text-editor.js.map +1 -1
  47. package/dist/hooks/index.d.ts +2 -2
  48. package/dist/hooks/index.d.ts.map +1 -1
  49. package/dist/shell/notification-center.d.ts.map +1 -1
  50. package/dist/shell/notification-center.js +102 -98
  51. package/dist/shell/notification-center.js.map +1 -1
  52. package/dist/shell/sidebar.d.ts +6 -0
  53. package/dist/shell/sidebar.d.ts.map +1 -1
  54. package/dist/shell/sidebar.js +142 -118
  55. package/dist/shell/sidebar.js.map +1 -1
  56. package/dist/shell/top-bar.d.ts.map +1 -1
  57. package/dist/shell/top-bar.js +87 -84
  58. package/dist/shell/top-bar.js.map +1 -1
  59. package/dist/tokens/semantic.css +9 -2
  60. package/dist/ui/alert-dialog.js +1 -1
  61. package/dist/ui/alert-dialog.js.map +1 -1
  62. package/dist/ui/autocomplete.js +1 -1
  63. package/dist/ui/autocomplete.js.map +1 -1
  64. package/dist/ui/avatar.js +1 -1
  65. package/dist/ui/avatar.js.map +1 -1
  66. package/dist/ui/breadcrumb.js +2 -2
  67. package/dist/ui/breadcrumb.js.map +1 -1
  68. package/dist/ui/button.js.map +1 -1
  69. package/dist/ui/card.d.ts +82 -7
  70. package/dist/ui/card.d.ts.map +1 -1
  71. package/dist/ui/card.js +76 -110
  72. package/dist/ui/card.js.map +1 -1
  73. package/dist/ui/chat/message.d.ts.map +1 -1
  74. package/dist/ui/color-input.js +1 -1
  75. package/dist/ui/color-input.js.map +1 -1
  76. package/dist/ui/combobox.js +3 -3
  77. package/dist/ui/combobox.js.map +1 -1
  78. package/dist/ui/context-menu.js +2 -2
  79. package/dist/ui/context-menu.js.map +1 -1
  80. package/dist/ui/data-table-bulk-actions.js +1 -1
  81. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  82. package/dist/ui/dialog.js +1 -1
  83. package/dist/ui/dialog.js.map +1 -1
  84. package/dist/ui/dropdown-menu.js +11 -5
  85. package/dist/ui/dropdown-menu.js.map +1 -1
  86. package/dist/ui/hover-card.js +1 -1
  87. package/dist/ui/hover-card.js.map +1 -1
  88. package/dist/ui/index.d.ts +3 -1
  89. package/dist/ui/index.d.ts.map +1 -1
  90. package/dist/ui/index.js +58 -56
  91. package/dist/ui/index.js.map +1 -1
  92. package/dist/ui/input-otp.js +1 -1
  93. package/dist/ui/input-otp.js.map +1 -1
  94. package/dist/ui/menubar.js +12 -6
  95. package/dist/ui/menubar.js.map +1 -1
  96. package/dist/ui/navigation-menu.js +1 -1
  97. package/dist/ui/navigation-menu.js.map +1 -1
  98. package/dist/ui/oauth-button/oauth-button.d.ts.map +1 -1
  99. package/dist/ui/select.js +6 -3
  100. package/dist/ui/select.js.map +1 -1
  101. package/dist/ui/sidebar.js +74 -74
  102. package/dist/ui/sidebar.js.map +1 -1
  103. package/dist/ui/split-button.js +1 -1
  104. package/dist/ui/split-button.js.map +1 -1
  105. package/dist/ui/stat-card.d.ts +26 -3
  106. package/dist/ui/stat-card.d.ts.map +1 -1
  107. package/dist/ui/stat-card.js +183 -150
  108. package/dist/ui/stat-card.js.map +1 -1
  109. package/dist/ui/stat-flash.d.ts +46 -0
  110. package/dist/ui/stat-flash.d.ts.map +1 -0
  111. package/dist/ui/stat-flash.js +114 -0
  112. package/dist/ui/stat-flash.js.map +1 -0
  113. package/dist/ui/tabs.d.ts.map +1 -1
  114. package/dist/ui/tabs.js +5 -2
  115. package/dist/ui/tabs.js.map +1 -1
  116. package/dist/ui/toast.js +2 -2
  117. package/dist/ui/toast.js.map +1 -1
  118. package/dist/ui/truncated-text.d.ts +37 -0
  119. package/dist/ui/truncated-text.d.ts.map +1 -0
  120. package/dist/ui/truncated-text.js +53 -0
  121. package/dist/ui/truncated-text.js.map +1 -0
  122. package/docs/components/ui/card.md +13 -4
  123. package/docs/components/ui/stat-card.md +18 -3
  124. package/docs/components/ui/stat-flash.md +44 -0
  125. package/docs/components/ui/truncated-text.md +49 -0
  126. package/llms-full.txt +125 -8
  127. package/make-kit/components/card.md +14 -4
  128. package/package.json +1 -1
  129. package/skill/SKILL.md +1 -1
  130. package/skill/references/components-full.md +125 -8
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ type TruncateMode = 'end' | 'clamp' | 'middle';
4
+ export interface TruncatedTextProps {
5
+ /** The full text. Always the accessible name, even when visually shortened. */
6
+ children: string;
7
+ /**
8
+ * `end` (default) single-line "…end"; `clamp` multi-line (`lines`); `middle`
9
+ * keeps both ends (filenames, emails) — `name…end.pdf`.
10
+ */
11
+ mode?: TruncateMode;
12
+ /** Lines for `mode="clamp"`. @default 2 */
13
+ lines?: number;
14
+ /** Element to render. @default 'span' */
15
+ as?: 'span' | 'p' | 'div';
16
+ /** Show the full text in a tooltip when (and only when) actually truncated. @default true */
17
+ tooltip?: boolean;
18
+ className?: string;
19
+ /** Override the full-text accessible name / tooltip (defaults to `children`). */
20
+ title?: string;
21
+ }
22
+ /**
23
+ * Text that truncates and recovers. CSS handles `end`/`clamp` (full text stays in the
24
+ * DOM, screen-reader safe); `middle` measures + JS-shortens but keeps the full string as
25
+ * the accessible name. A tooltip appears ONLY when the text is actually clipped.
26
+ *
27
+ * Note: for `end`/`middle` in a flex/grid row, the shrinking item still needs `min-w-0`
28
+ * (CSS gotcha) — apply it at the call site.
29
+ *
30
+ * @example
31
+ * <TruncatedText>{user.name}</TruncatedText>
32
+ * <TruncatedText mode="middle">{fileName}</TruncatedText> // report…v2.pdf
33
+ * <TruncatedText mode="clamp" lines={2}>{description}</TruncatedText>
34
+ */
35
+ export declare const TruncatedText: React.ForwardRefExoticComponent<TruncatedTextProps & React.RefAttributes<HTMLElement>>;
36
+ export {};
37
+ //# sourceMappingURL=truncated-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truncated-text.d.ts","sourceRoot":"","sources":["../../src/ui/truncated-text.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,KAAK,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE9C,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yCAAyC;IACzC,EAAE,CAAC,EAAE,MAAM,GAAG,GAAG,GAAG,KAAK,CAAA;IACzB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAYD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,wFA2FzB,CAAA"}
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { cn as e } from "./lib/utils.js";
3
+ import { Tooltip as t, TooltipContent as n, TooltipTrigger as r } from "./tooltip.js";
4
+ import * as i from "react";
5
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
+ //#region src/ui/truncated-text.tsx
7
+ function s(e, t, n = "…") {
8
+ if (t >= e.length || t < 2) return e;
9
+ let r = t - n.length, i = Math.ceil(r / 2), a = Math.floor(r / 2);
10
+ return e.slice(0, i) + n + (a > 0 ? e.slice(-a) : "");
11
+ }
12
+ var c = i.forwardRef(({ children: c, mode: l = "end", lines: u = 2, as: d = "span", tooltip: f = !0, className: p, title: m }, h) => {
13
+ let g = c, [_, v] = i.useState(!1), [y, b] = i.useState(g), x = i.useRef(null), S = /* @__PURE__ */ a(d, {
14
+ ref: i.useCallback((e) => {
15
+ if (typeof h == "function" ? h(e) : h && (h.current = e), x.current?.disconnect(), !e) return;
16
+ let t = () => {
17
+ if (l === "middle") {
18
+ if (e.textContent = g, e.clientWidth === 0 || e.scrollWidth <= e.clientWidth) {
19
+ b(g), v(!1);
20
+ return;
21
+ }
22
+ let t = 2, n = g.length, r = 2;
23
+ for (; t <= n;) {
24
+ let i = t + n >> 1;
25
+ e.textContent = s(g, i), e.scrollWidth <= e.clientWidth ? (r = i, t = i + 1) : n = i - 1;
26
+ }
27
+ b(s(g, r)), v(!0);
28
+ } else v(l === "clamp" ? e.scrollHeight > e.clientHeight + 1 : e.scrollWidth > e.clientWidth + 1);
29
+ };
30
+ t(), x.current = new ResizeObserver(t), x.current.observe(e);
31
+ }, [
32
+ g,
33
+ l,
34
+ u,
35
+ h
36
+ ]),
37
+ className: e(l === "end" && "block truncate", l === "middle" && "block overflow-hidden whitespace-nowrap", l === "clamp" && "overflow-hidden", p),
38
+ style: l === "clamp" ? {
39
+ display: "-webkit-box",
40
+ WebkitBoxOrient: "vertical",
41
+ WebkitLineClamp: u
42
+ } : void 0,
43
+ "aria-label": _ ? m ?? g : void 0,
44
+ children: l === "middle" ? y : g
45
+ });
46
+ return f && _ ? /* @__PURE__ */ o(t, { children: [/* @__PURE__ */ a(r, {
47
+ asChild: !0,
48
+ children: S
49
+ }), /* @__PURE__ */ a(n, { children: m ?? g })] }) : S;
50
+ });
51
+ c.displayName = "TruncatedText";
52
+ //#endregion
53
+ export { c as TruncatedText };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truncated-text.js","names":[],"sources":["../../src/ui/truncated-text.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\nimport { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'\n\ntype TruncateMode = 'end' | 'clamp' | 'middle'\n\nexport interface TruncatedTextProps {\n /** The full text. Always the accessible name, even when visually shortened. */\n children: string\n /**\n * `end` (default) single-line \"…end\"; `clamp` multi-line (`lines`); `middle`\n * keeps both ends (filenames, emails) — `name…end.pdf`.\n */\n mode?: TruncateMode\n /** Lines for `mode=\"clamp\"`. @default 2 */\n lines?: number\n /** Element to render. @default 'span' */\n as?: 'span' | 'p' | 'div'\n /** Show the full text in a tooltip when (and only when) actually truncated. @default true */\n tooltip?: boolean\n className?: string\n /** Override the full-text accessible name / tooltip (defaults to `children`). */\n title?: string\n}\n\n/** Keep `keep` chars total, ellipsis in the middle. */\nfunction truncateMiddle(str: string, keep: number, ellipsis = '…'): string {\n if (keep >= str.length || keep < 2) return str\n const budget = keep - ellipsis.length\n const head = Math.ceil(budget / 2)\n const tail = Math.floor(budget / 2)\n // slice(-0) returns the whole string, so guard the tail explicitly.\n return str.slice(0, head) + ellipsis + (tail > 0 ? str.slice(-tail) : '')\n}\n\n/**\n * Text that truncates and recovers. CSS handles `end`/`clamp` (full text stays in the\n * DOM, screen-reader safe); `middle` measures + JS-shortens but keeps the full string as\n * the accessible name. A tooltip appears ONLY when the text is actually clipped.\n *\n * Note: for `end`/`middle` in a flex/grid row, the shrinking item still needs `min-w-0`\n * (CSS gotcha) — apply it at the call site.\n *\n * @example\n * <TruncatedText>{user.name}</TruncatedText>\n * <TruncatedText mode=\"middle\">{fileName}</TruncatedText> // report…v2.pdf\n * <TruncatedText mode=\"clamp\" lines={2}>{description}</TruncatedText>\n */\nexport const TruncatedText = React.forwardRef<HTMLElement, TruncatedTextProps>(\n ({ children, mode = 'end', lines = 2, as = 'span', tooltip = true, className, title }, forwardedRef) => {\n const full = children\n const [overflowing, setOverflowing] = React.useState(false)\n const [display, setDisplay] = React.useState(full)\n const roRef = React.useRef<ResizeObserver | null>(null)\n\n // Callback ref owns the ResizeObserver so it always tracks the CURRENT node.\n // Wrapping in a Tooltip remounts the element when `overflowing` flips — a one-shot\n // layout effect would leave a stale observer on the detached node (which then fires\n // with size 0 and resets the measurement). Reconnecting here avoids that loop.\n const attachRef = React.useCallback(\n (node: HTMLElement | null) => {\n if (typeof forwardedRef === 'function') forwardedRef(node)\n else if (forwardedRef) (forwardedRef as React.MutableRefObject<HTMLElement | null>).current = node\n\n roRef.current?.disconnect()\n if (!node) return\n\n const measure = () => {\n if (mode === 'middle') {\n node.textContent = full\n if (node.clientWidth === 0 || node.scrollWidth <= node.clientWidth) {\n setDisplay(full)\n setOverflowing(false)\n return\n }\n // Largest middle-truncated string that fits, via binary search.\n let lo = 2\n let hi = full.length\n let best = 2\n while (lo <= hi) {\n const midKeep = (lo + hi) >> 1\n node.textContent = truncateMiddle(full, midKeep)\n if (node.scrollWidth <= node.clientWidth) {\n best = midKeep\n lo = midKeep + 1\n } else {\n hi = midKeep - 1\n }\n }\n setDisplay(truncateMiddle(full, best))\n setOverflowing(true)\n } else if (mode === 'clamp') {\n setOverflowing(node.scrollHeight > node.clientHeight + 1)\n } else {\n setOverflowing(node.scrollWidth > node.clientWidth + 1)\n }\n }\n\n measure()\n roRef.current = new ResizeObserver(measure)\n roRef.current.observe(node)\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [full, mode, lines, forwardedRef],\n )\n\n const Tag = as as React.ElementType\n const content = (\n <Tag\n ref={attachRef}\n className={cn(\n mode === 'end' && 'block truncate',\n mode === 'middle' && 'block overflow-hidden whitespace-nowrap',\n mode === 'clamp' && 'overflow-hidden',\n className,\n )}\n style={\n mode === 'clamp'\n ? ({ display: '-webkit-box', WebkitBoxOrient: 'vertical', WebkitLineClamp: lines } as React.CSSProperties)\n : undefined\n }\n // When clipped, the visible text may be shortened (middle mode) — keep the full\n // string as the accessible name so screen readers always read it in full.\n aria-label={overflowing ? (title ?? full) : undefined}\n >\n {mode === 'middle' ? display : full}\n </Tag>\n )\n\n if (tooltip && overflowing) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{content}</TooltipTrigger>\n <TooltipContent>{title ?? full}</TooltipContent>\n </Tooltip>\n )\n }\n return content\n },\n)\nTruncatedText.displayName = 'TruncatedText'\n"],"mappings":";;;;;;AA6BA,SAAS,EAAe,GAAa,GAAc,IAAW,KAAa;AACzE,KAAI,KAAQ,EAAI,UAAU,IAAO,EAAG,QAAO;CAC3C,IAAM,IAAS,IAAO,EAAS,QACzB,IAAO,KAAK,KAAK,IAAS,EAAE,EAC5B,IAAO,KAAK,MAAM,IAAS,EAAE;AAEnC,QAAO,EAAI,MAAM,GAAG,EAAK,GAAG,KAAY,IAAO,IAAI,EAAI,MAAM,CAAC,EAAK,GAAG;;AAgBxE,IAAa,IAAgB,EAAM,YAChC,EAAE,aAAU,UAAO,OAAO,WAAQ,GAAG,QAAK,QAAQ,aAAU,IAAM,cAAW,YAAS,MAAiB;CACtG,IAAM,IAAO,GACP,CAAC,GAAa,KAAkB,EAAM,SAAS,GAAM,EACrD,CAAC,GAAS,KAAc,EAAM,SAAS,EAAK,EAC5C,IAAQ,EAAM,OAA8B,KAAK,EAsDjD,IACJ,kBAFU,GAEV;EACE,KAlDc,EAAM,aACrB,MAA6B;AAK5B,OAJI,OAAO,KAAiB,aAAY,EAAa,EAAK,GACjD,MAAe,EAA4D,UAAU,IAE9F,EAAM,SAAS,YAAY,EACvB,CAAC,EAAM;GAEX,IAAM,UAAgB;AACpB,QAAI,MAAS,UAAU;AAErB,SADA,EAAK,cAAc,GACf,EAAK,gBAAgB,KAAK,EAAK,eAAe,EAAK,aAAa;AAElE,MADA,EAAW,EAAK,EAChB,EAAe,GAAM;AACrB;;KAGF,IAAI,IAAK,GACL,IAAK,EAAK,QACV,IAAO;AACX,YAAO,KAAM,IAAI;MACf,IAAM,IAAW,IAAK,KAAO;AAE7B,MADA,EAAK,cAAc,EAAe,GAAM,EAAQ,EAC5C,EAAK,eAAe,EAAK,eAC3B,IAAO,GACP,IAAK,IAAU,KAEf,IAAK,IAAU;;AAInB,KADA,EAAW,EAAe,GAAM,EAAK,CAAC,EACtC,EAAe,GAAK;WAEpB,EADS,MAAS,UACH,EAAK,eAAe,EAAK,eAAe,IAExC,EAAK,cAAc,EAAK,cAAc,EAAE;;AAM3D,GAFA,GAAS,EACT,EAAM,UAAU,IAAI,eAAe,EAAQ,EAC3C,EAAM,QAAQ,QAAQ,EAAK;KAG7B;GAAC;GAAM;GAAM;GAAO;GAAa,CAClC;EAMG,WAAW,EACT,MAAS,SAAS,kBAClB,MAAS,YAAY,2CACrB,MAAS,WAAW,mBACpB,EACD;EACD,OACE,MAAS,UACJ;GAAE,SAAS;GAAe,iBAAiB;GAAY,iBAAiB;GAAO,GAChF,KAAA;EAIN,cAAY,IAAe,KAAS,IAAQ,KAAA;YAE3C,MAAS,WAAW,IAAU;EAC3B,CAAA;AAWR,QARI,KAAW,IAEX,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;EAAgB,SAAA;YAAS;EAAyB,CAAA,EAClD,kBAAC,GAAD,EAAA,UAAiB,KAAS,GAAsB,CAAA,CACxC,EAAA,CAAA,GAGP;EAEV;AACD,EAAc,cAAc"}
@@ -9,11 +9,14 @@
9
9
  color: "default" | "accent" | "error" | "success" | "warning" | "info" | "neutral" (border accent color)
10
10
  size: "sm" | "md" | "lg" (padding — propagated to CardHeader/CardContent/CardFooter via context)
11
11
  interactive: boolean (enables hover shadow lift + pointer cursor)
12
- accent: "left" | "top" | "right" | "bottom" (render a colored accent bar on the specified edge)
13
- accentColor: "default" | "accent" | "error" | "success" | "warning" | "info" (color of the accent bar; default maps to accent-9)
12
+
13
+ ### CardAction
14
+ placement: "top-right" (default) | "top-left" | "bottom-right" | "bottom-left"
15
+ tuck: boolean (pull a step toward the corner so an icon button's glyph aligns to the content edge)
14
16
 
15
17
  ## Compound Components
16
18
  Card (root)
19
+ CardAction ← absolutely-positioned corner slot (badge, icon button, menu)
17
20
  CardHeader ← inherits size from Card context
18
21
  CardTitle
19
22
  CardDescription
@@ -37,15 +40,21 @@
37
40
  ## Composability
38
41
  - **Size cascades through context** — Card's `size` prop sets padding on CardHeader, CardContent, and CardFooter via `CardSizeContext`. Don't set padding classes on sub-components directly; override via `className` if needed.
39
42
  - **Not a compound state machine** — Card, CardHeader, CardTitle, etc. are purely structural. No open/close state.
40
- - **Accent bar is independent:** The `accent` / `accentColor` props render a decorative colored edge bar (absolutely positioned, `aria-hidden`). Works alongside `color` (which tints the border) the two can stack for layered emphasis.
43
+ - **Corner slots via `CardAction`:** Pin a badge, icon button, or menu to any corner (`placement`), inset to match the card's content padding. `tuck` pulls an icon button so its glyph (not its padding box) aligns to the content edge. Replaces the removed `accent` decorative bar composition, not a bespoke prop. Card is `relative` to anchor it.
44
+ - **`color` paints the border, never a stacked rail:** `color` tints the 1px edge (semantic accent/error/success/…). The DS never stacks a border + drop shadow or a colored rail on top (make-kit rule #6 — that reads as an AI tell).
41
45
  - **Interactive cards:** Set `interactive={true}` + `onClick` for clickable cards (entire surface becomes the button). Add `aria-label` on the Card root when there's no visible heading. For complex multi-action cards, prefer standard Card with explicit buttons inside.
42
- - **ContentCard (composed)** is a higher-level wrapper with built-in header/footer slots and title/actions use it for list-row-style cards; use Card directly for custom layouts.
46
+ - **ContentCard (composed) is deprecated**compose `Card` + `CardHeader`/`CardContent`/`CardAction` directly instead.
43
47
 
44
48
  ## Gotchas
45
49
  - Use `interactive` prop for clickable cards — adds hover lift and pointer cursor
46
50
  - Don't override CardHeader/CardContent/CardFooter padding via className if you want the size cascade to work — set size on Card instead
47
51
 
48
52
  ## Changes
53
+ ### v0.44.0
54
+ - **BREAKING** Removed `accent` / `accentColor` (the colored edge-bar / left-rail). Use `<CardAction>` for corner content, or `color` for a tinted border. The DS no longer ships a stacked rail (make-kit rule #6).
55
+ - **Added** `<CardAction>` compound — composable corner slot (4 placements, `tuck`).
56
+ - **Changed** Gap-model padding: the container owns vertical `py` + inter-slot `gap`; slots own only horizontal `px`. Adding/removing a slot can't unbalance the bottom edge. Card is now `relative`.
57
+
49
58
  ### v0.31.0
50
59
  - **Added** `color` prop: semantic border color (accent, error, success, warning, info, neutral)
51
60
  - **Added** `size` prop: `sm | md | lg` — padding propagated to sub-components via React context
@@ -11,12 +11,17 @@
11
11
  prefix: string (before value, e.g. "$")
12
12
  suffix: string (after value, e.g. "users")
13
13
  delta: { value: string, direction: "up" | "down" | "neutral" }
14
+ deltaPlacement: "block" | "inline" (block = below value [default]; inline = on the value's baseline)
14
15
  icon: ReactNode | ComponentType<{ className?: string }>
15
16
  loading: boolean (renders skeleton)
16
17
  comparisonLabel: string (shown after delta, e.g. "vs last month")
17
18
  secondaryLabel: string (below main value, e.g. "of $50,000 target")
18
19
  progress: number (0-100, renders thin progress bar below value)
19
- accent: "default" | "success" | "warning" | "error" | "info" (left border color)
20
+ variant: "default" | "elevated" | "outline" | "flat" (default = ring-in-shadow, no border; elevated = stronger shadow; outline = border, no shadow; flat = filled, no edge) — delegated to Card
21
+ accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
22
+ iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
23
+ flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
24
+ flashSpeed: "fast" | "normal" | "slow" (default normal)
20
25
  sparkline: number[] (renders mini SVG line chart)
21
26
  onClick: () => void (makes card clickable with hover state)
22
27
  href: string (makes card a link via LinkContext)
@@ -34,7 +39,8 @@
34
39
  delta={{ value: "+12%", direction: "up" }}
35
40
  comparisonLabel="vs last month"
36
41
  icon={<IconCurrencyDollar />}
37
- accent="success"
42
+ accentStyle="icon"
43
+ flash="up"
38
44
  />
39
45
 
40
46
  <StatCard
@@ -51,7 +57,8 @@
51
57
  - **High-density metric card** — optimized for dashboards. Everything optional except `value`. Mix and match features (delta, sparkline, progress, secondary label, footer) per metric's needs.
52
58
  - **Router integration via href:** Internally uses `LinkContext` to resolve framework-specific Link components (Next.js, react-router). Set `href` in a LinkProvider-wrapped tree to get seamless client-side navigation without custom asChild wiring.
53
59
  - **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
54
- - **Accent bar semantic:** Use `accent` to signal metric health at a glance (success for positive, warning for at-risk, error for over-target). Combine with delta.direction for layered emphasis.
60
+ - **Accent (composable, opt-in):** `accentStyle="icon"` wraps `icon` in an accent chip (`iconFill="soft" | "solid"`); `accentStyle="tint"` applies a subtle accent surface wash + accent value. `variant` picks edge-vs-elevation. No colored rail — the DS never stacks a border + drop shadow (make-kit rule #6). Trend health reads from `delta.direction` (up=green, down=red).
61
+ - **Flash motion (opt-in):** `flash` mounts a toned state glyph (`up`/`down`/`goal`/`record`/`alert`/`live`, or `{ tone, icon }`) that settles to `icon`; `flashSpeed` tunes timing. Reuses the standalone `StatFlash` primitive. Honors `prefers-reduced-motion`.
55
62
  - **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
56
63
  - **Icon auto-sizing:** Accepts `ComponentType<{ className }>` OR `ReactNode`. The component prop (e.g. `icon={IconBolt}`) is preferred — icon is rendered at a consistent size.
57
64
  - **Loading state:** `loading={true}` renders the full card skeleton — use during initial data fetch.
@@ -63,6 +70,14 @@
63
70
  - `sparkline` needs at least 2 data points to render
64
71
 
65
72
  ## Changes
73
+ ### v0.44.0
74
+ - **BREAKING** Renamed `surface` → `variant`, widened to a 4-way scale (`default` | `elevated` | `outline` | `flat`). StatCard now composes `<Card>`, so surface, gap-model padding, and elevation all live in one place. Migration: `surface="raised"` → `variant="default"`, `surface="flat"` → `variant="outline"`.
75
+ - **Added** `deltaPlacement` (`"block"` [default] | `"inline"`) — inline rides the value's baseline for compact dashboards.
76
+
77
+ ### v0.43.0
78
+ - **BREAKING** Removed `accent` (colored left-rail). Use `accentStyle` (`"icon"` | `"tint"`) or rely on `delta` for trend colour.
79
+ - **Added** `surface`, `accentStyle`, `iconFill`, `flash`, `flashSpeed`. New standalone `StatFlash` primitive. Base no longer stacks border + shadow.
80
+
66
81
  ### v0.2.0
67
82
  - **Added** `icon` prop now accepts `React.ComponentType` (e.g., `icon={IconBolt}`) in addition to `ReactNode`
68
83
 
@@ -0,0 +1,44 @@
1
+ # StatFlash
2
+
3
+ - Import: @devalok/shilp-sutra/ui/stat-flash
4
+ - Server-safe: No
5
+ - Category: ui
6
+
7
+ ## Props
8
+ icon: ReactNode | ComponentType<{ className?: string }> (REQUIRED — settled identity glyph)
9
+ flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (REQUIRED — the entrance state)
10
+ fill: "soft" | "solid" (resting chip style; default soft)
11
+ speed: "fast" | "normal" | "slow" (coarse motion preset; default normal)
12
+ holdMs: number (override how long the flash holds before settling)
13
+ settleTransition: Transition (override the icon scale-in transition)
14
+ flashTransition: Transition (override the flash enter/exit fade)
15
+
16
+ ## Defaults
17
+ fill="soft", speed="normal"
18
+
19
+ ## Example
20
+ ```jsx
21
+ // Preset: green up-arrow flashes, then settles to the folder icon
22
+ <StatFlash icon={<IconFolder />} flash="up" />
23
+
24
+ // Explicit tone + glyph
25
+ <StatFlash icon={<IconActivity />} flash={{ tone: "info", icon: <IconBolt /> }} fill="solid" />
26
+
27
+ // Composable speed: coarse preset + fine override
28
+ <StatFlash icon={<IconFolder />} flash="up" speed="fast" holdMs={300} />
29
+ ```
30
+
31
+ ## Composability
32
+ - **State → identity primitive:** the chip mounts showing a transient *state* (a toned glyph — up-arrow on green, check, alert…) then settles into the stable *identity* icon. Used by `StatCard`'s `flash` prop, but standalone too (list rows, badges, toasts).
33
+ - **Tone slot:** six named presets (`up`/`down`/`goal`/`record`/`alert`/`live`) map to DS semantic tones (success/error/warning/info/accent) + a glyph. Pass `{ tone, icon }` for anything else.
34
+ - **Composable motion:** `speed` is a coarse preset built from the DS motion tokens; `holdMs` / `settleTransition` / `flashTransition` each override one slice and fall back to the preset.
35
+ - **Accessible by default:** honors `prefers-reduced-motion` — renders the settled identity directly, no flash. The chip is `aria-hidden` (decorative; the metric's text carries meaning).
36
+
37
+ ## Gotchas
38
+ - `flash` and `icon` are both required — the flash settles *to* the icon.
39
+ - Fine overrides win over `speed`.
40
+ - Decorative only — never the sole carrier of meaning (it's `aria-hidden`).
41
+
42
+ ## Changes
43
+ ### v0.43.0
44
+ - **Added** Initial release. Powers `StatCard`'s `flash` prop.
@@ -0,0 +1,49 @@
1
+ # TruncatedText
2
+
3
+ - Import: @devalok/shilp-sutra/ui/truncated-text
4
+ - Server-safe: No
5
+ - Category: ui
6
+
7
+ ## Props
8
+ children: string (REQUIRED — the full text; always the accessible name, even when shortened)
9
+ mode: "end" | "clamp" | "middle" (end [default] = single-line ellipsis; clamp = multi-line; middle = keep both ends)
10
+ lines: number (lines to show in mode="clamp"; default 2)
11
+ as: "span" | "p" | "div" (element to render; default "span")
12
+ tooltip: boolean (show the full text in a tooltip when — and only when — actually clipped; default true)
13
+ title: string (override the full-text accessible name / tooltip; defaults to children)
14
+ className: string
15
+
16
+ ## Defaults
17
+ mode="end", lines=2, as="span", tooltip=true
18
+
19
+ ## Example
20
+ ```jsx
21
+ <TruncatedText>{user.name}</TruncatedText>
22
+
23
+ // Filenames / emails — keep the extension / domain:
24
+ <TruncatedText mode="middle">{fileName}</TruncatedText> // report…v7.pdf
25
+
26
+ // Multi-line description with a 2-line clamp:
27
+ <TruncatedText as="p" mode="clamp" lines={2}>{description}</TruncatedText>
28
+
29
+ // In a flex row, the shrinking item still needs min-w-0:
30
+ <div className="flex items-center gap-ds-03">
31
+ <Avatar … />
32
+ <TruncatedText className="min-w-0 flex-1">{user.name}</TruncatedText>
33
+ </div>
34
+ ```
35
+
36
+ ## Composability
37
+ - **Truncates AND recovers in one place:** `end`/`clamp` are pure CSS (full text stays in the DOM → screen-reader safe). `middle` measures the element and JS-shortens the visible string but keeps the full string as the accessible name. A tooltip appears only when `scrollWidth`/`scrollHeight` actually exceeds the box (measured via ResizeObserver) — no tooltip noise on text that fits.
38
+ - **`mode="middle"` for identifiers:** filenames (extension), emails (domain), paths, hashes — end-ellipsis destroys the meaningful tail; middle keeps both ends. Middle is not native CSS, so it's measured + recomputed on resize.
39
+ - **The flexbox gotcha:** `truncate` (and `middle`) do nothing on a flex/grid child unless that item has `min-w-0` (or an explicit width). TruncatedText can't set that on its parent — add `min-w-0` at the call site.
40
+ - **Don't truncate everything:** page titles/H1, toast bodies, validation messages, and numbers must show fully (a truncated number is a wrong number). Use this for bounded list/nav/metadata text.
41
+
42
+ ## Gotchas
43
+ - `children` must be a string (it's measured + used as the accessible name) — not arbitrary ReactNode.
44
+ - In a flex row, add `min-w-0` to the TruncatedText (or its column) or nothing truncates.
45
+ - `mode="middle"` clips to the largest middle-truncated string that fits; the last glyph may be sub-pixel-clipped by overflow-hidden — the tooltip + aria-label always carry the full string.
46
+
47
+ ## Changes
48
+ ### v0.44.0
49
+ - **Added** Initial release — end/clamp/middle truncation with overflow-aware tooltip recovery.
package/llms-full.txt CHANGED
@@ -5,7 +5,7 @@
5
5
  > All variant values and props verified from source CVA definitions.
6
6
  >
7
7
  > Package: @devalok/shilp-sutra
8
- > Version: 0.42.1
8
+ > Version: 0.44.0
9
9
  >
10
10
  > **If you are an AI agent reading this file top-to-bottom:** the Setup
11
11
  > section below is authoritative. If any later per-component doc or a
@@ -1096,11 +1096,14 @@ See `Button` docs for the public API.
1096
1096
  color: "default" | "accent" | "error" | "success" | "warning" | "info" | "neutral" (border accent color)
1097
1097
  size: "sm" | "md" | "lg" (padding — propagated to CardHeader/CardContent/CardFooter via context)
1098
1098
  interactive: boolean (enables hover shadow lift + pointer cursor)
1099
- accent: "left" | "top" | "right" | "bottom" (render a colored accent bar on the specified edge)
1100
- accentColor: "default" | "accent" | "error" | "success" | "warning" | "info" (color of the accent bar; default maps to accent-9)
1099
+
1100
+ ### CardAction
1101
+ placement: "top-right" (default) | "top-left" | "bottom-right" | "bottom-left"
1102
+ tuck: boolean (pull a step toward the corner so an icon button's glyph aligns to the content edge)
1101
1103
 
1102
1104
  ## Compound Components
1103
1105
  Card (root)
1106
+ CardAction ← absolutely-positioned corner slot (badge, icon button, menu)
1104
1107
  CardHeader ← inherits size from Card context
1105
1108
  CardTitle
1106
1109
  CardDescription
@@ -1124,15 +1127,21 @@ See `Button` docs for the public API.
1124
1127
  ## Composability
1125
1128
  - **Size cascades through context** — Card's `size` prop sets padding on CardHeader, CardContent, and CardFooter via `CardSizeContext`. Don't set padding classes on sub-components directly; override via `className` if needed.
1126
1129
  - **Not a compound state machine** — Card, CardHeader, CardTitle, etc. are purely structural. No open/close state.
1127
- - **Accent bar is independent:** The `accent` / `accentColor` props render a decorative colored edge bar (absolutely positioned, `aria-hidden`). Works alongside `color` (which tints the border) the two can stack for layered emphasis.
1130
+ - **Corner slots via `CardAction`:** Pin a badge, icon button, or menu to any corner (`placement`), inset to match the card's content padding. `tuck` pulls an icon button so its glyph (not its padding box) aligns to the content edge. Replaces the removed `accent` decorative bar composition, not a bespoke prop. Card is `relative` to anchor it.
1131
+ - **`color` paints the border, never a stacked rail:** `color` tints the 1px edge (semantic accent/error/success/…). The DS never stacks a border + drop shadow or a colored rail on top (make-kit rule #6 — that reads as an AI tell).
1128
1132
  - **Interactive cards:** Set `interactive={true}` + `onClick` for clickable cards (entire surface becomes the button). Add `aria-label` on the Card root when there's no visible heading. For complex multi-action cards, prefer standard Card with explicit buttons inside.
1129
- - **ContentCard (composed)** is a higher-level wrapper with built-in header/footer slots and title/actions use it for list-row-style cards; use Card directly for custom layouts.
1133
+ - **ContentCard (composed) is deprecated**compose `Card` + `CardHeader`/`CardContent`/`CardAction` directly instead.
1130
1134
 
1131
1135
  ## Gotchas
1132
1136
  - Use `interactive` prop for clickable cards — adds hover lift and pointer cursor
1133
1137
  - Don't override CardHeader/CardContent/CardFooter padding via className if you want the size cascade to work — set size on Card instead
1134
1138
 
1135
1139
  ## Changes
1140
+ ### v0.44.0
1141
+ - **BREAKING** Removed `accent` / `accentColor` (the colored edge-bar / left-rail). Use `<CardAction>` for corner content, or `color` for a tinted border. The DS no longer ships a stacked rail (make-kit rule #6).
1142
+ - **Added** `<CardAction>` compound — composable corner slot (4 placements, `tuck`).
1143
+ - **Changed** Gap-model padding: the container owns vertical `py` + inter-slot `gap`; slots own only horizontal `px`. Adding/removing a slot can't unbalance the bottom edge. Card is now `relative`.
1144
+
1136
1145
  ### v0.31.0
1137
1146
  - **Added** `color` prop: semantic border color (accent, error, success, warning, info, neutral)
1138
1147
  - **Added** `size` prop: `sm | md | lg` — padding propagated to sub-components via React context
@@ -4041,12 +4050,17 @@ import { SplitButton } from '@devalok/shilp-sutra'
4041
4050
  prefix: string (before value, e.g. "$")
4042
4051
  suffix: string (after value, e.g. "users")
4043
4052
  delta: { value: string, direction: "up" | "down" | "neutral" }
4053
+ deltaPlacement: "block" | "inline" (block = below value [default]; inline = on the value's baseline)
4044
4054
  icon: ReactNode | ComponentType<{ className?: string }>
4045
4055
  loading: boolean (renders skeleton)
4046
4056
  comparisonLabel: string (shown after delta, e.g. "vs last month")
4047
4057
  secondaryLabel: string (below main value, e.g. "of $50,000 target")
4048
4058
  progress: number (0-100, renders thin progress bar below value)
4049
- accent: "default" | "success" | "warning" | "error" | "info" (left border color)
4059
+ variant: "default" | "elevated" | "outline" | "flat" (default = ring-in-shadow, no border; elevated = stronger shadow; outline = border, no shadow; flat = filled, no edge) — delegated to Card
4060
+ accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
4061
+ iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
4062
+ flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
4063
+ flashSpeed: "fast" | "normal" | "slow" (default normal)
4050
4064
  sparkline: number[] (renders mini SVG line chart)
4051
4065
  onClick: () => void (makes card clickable with hover state)
4052
4066
  href: string (makes card a link via LinkContext)
@@ -4064,7 +4078,8 @@ import { SplitButton } from '@devalok/shilp-sutra'
4064
4078
  delta={{ value: "+12%", direction: "up" }}
4065
4079
  comparisonLabel="vs last month"
4066
4080
  icon={<IconCurrencyDollar />}
4067
- accent="success"
4081
+ accentStyle="icon"
4082
+ flash="up"
4068
4083
  />
4069
4084
 
4070
4085
  <StatCard
@@ -4081,7 +4096,8 @@ import { SplitButton } from '@devalok/shilp-sutra'
4081
4096
  - **High-density metric card** — optimized for dashboards. Everything optional except `value`. Mix and match features (delta, sparkline, progress, secondary label, footer) per metric's needs.
4082
4097
  - **Router integration via href:** Internally uses `LinkContext` to resolve framework-specific Link components (Next.js, react-router). Set `href` in a LinkProvider-wrapped tree to get seamless client-side navigation without custom asChild wiring.
4083
4098
  - **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
4084
- - **Accent bar semantic:** Use `accent` to signal metric health at a glance (success for positive, warning for at-risk, error for over-target). Combine with delta.direction for layered emphasis.
4099
+ - **Accent (composable, opt-in):** `accentStyle="icon"` wraps `icon` in an accent chip (`iconFill="soft" | "solid"`); `accentStyle="tint"` applies a subtle accent surface wash + accent value. `variant` picks edge-vs-elevation. No colored rail — the DS never stacks a border + drop shadow (make-kit rule #6). Trend health reads from `delta.direction` (up=green, down=red).
4100
+ - **Flash motion (opt-in):** `flash` mounts a toned state glyph (`up`/`down`/`goal`/`record`/`alert`/`live`, or `{ tone, icon }`) that settles to `icon`; `flashSpeed` tunes timing. Reuses the standalone `StatFlash` primitive. Honors `prefers-reduced-motion`.
4085
4101
  - **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
4086
4102
  - **Icon auto-sizing:** Accepts `ComponentType<{ className }>` OR `ReactNode`. The component prop (e.g. `icon={IconBolt}`) is preferred — icon is rendered at a consistent size.
4087
4103
  - **Loading state:** `loading={true}` renders the full card skeleton — use during initial data fetch.
@@ -4093,11 +4109,63 @@ import { SplitButton } from '@devalok/shilp-sutra'
4093
4109
  - `sparkline` needs at least 2 data points to render
4094
4110
 
4095
4111
  ## Changes
4112
+ ### v0.44.0
4113
+ - **BREAKING** Renamed `surface` → `variant`, widened to a 4-way scale (`default` | `elevated` | `outline` | `flat`). StatCard now composes `<Card>`, so surface, gap-model padding, and elevation all live in one place. Migration: `surface="raised"` → `variant="default"`, `surface="flat"` → `variant="outline"`.
4114
+ - **Added** `deltaPlacement` (`"block"` [default] | `"inline"`) — inline rides the value's baseline for compact dashboards.
4115
+
4116
+ ### v0.43.0
4117
+ - **BREAKING** Removed `accent` (colored left-rail). Use `accentStyle` (`"icon"` | `"tint"`) or rely on `delta` for trend colour.
4118
+ - **Added** `surface`, `accentStyle`, `iconFill`, `flash`, `flashSpeed`. New standalone `StatFlash` primitive. Base no longer stacks border + shadow.
4119
+
4096
4120
  ### v0.2.0
4097
4121
  - **Added** `icon` prop now accepts `React.ComponentType` (e.g., `icon={IconBolt}`) in addition to `ReactNode`
4098
4122
 
4099
4123
  ### v0.1.0
4100
4124
  - **Added** Initial release
4125
+ # StatFlash
4126
+
4127
+ - Import: @devalok/shilp-sutra/ui/stat-flash
4128
+ - Server-safe: No
4129
+ - Category: ui
4130
+
4131
+ ## Props
4132
+ icon: ReactNode | ComponentType<{ className?: string }> (REQUIRED — settled identity glyph)
4133
+ flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (REQUIRED — the entrance state)
4134
+ fill: "soft" | "solid" (resting chip style; default soft)
4135
+ speed: "fast" | "normal" | "slow" (coarse motion preset; default normal)
4136
+ holdMs: number (override how long the flash holds before settling)
4137
+ settleTransition: Transition (override the icon scale-in transition)
4138
+ flashTransition: Transition (override the flash enter/exit fade)
4139
+
4140
+ ## Defaults
4141
+ fill="soft", speed="normal"
4142
+
4143
+ ## Example
4144
+ ```jsx
4145
+ // Preset: green up-arrow flashes, then settles to the folder icon
4146
+ <StatFlash icon={<IconFolder />} flash="up" />
4147
+
4148
+ // Explicit tone + glyph
4149
+ <StatFlash icon={<IconActivity />} flash={{ tone: "info", icon: <IconBolt /> }} fill="solid" />
4150
+
4151
+ // Composable speed: coarse preset + fine override
4152
+ <StatFlash icon={<IconFolder />} flash="up" speed="fast" holdMs={300} />
4153
+ ```
4154
+
4155
+ ## Composability
4156
+ - **State → identity primitive:** the chip mounts showing a transient *state* (a toned glyph — up-arrow on green, check, alert…) then settles into the stable *identity* icon. Used by `StatCard`'s `flash` prop, but standalone too (list rows, badges, toasts).
4157
+ - **Tone slot:** six named presets (`up`/`down`/`goal`/`record`/`alert`/`live`) map to DS semantic tones (success/error/warning/info/accent) + a glyph. Pass `{ tone, icon }` for anything else.
4158
+ - **Composable motion:** `speed` is a coarse preset built from the DS motion tokens; `holdMs` / `settleTransition` / `flashTransition` each override one slice and fall back to the preset.
4159
+ - **Accessible by default:** honors `prefers-reduced-motion` — renders the settled identity directly, no flash. The chip is `aria-hidden` (decorative; the metric's text carries meaning).
4160
+
4161
+ ## Gotchas
4162
+ - `flash` and `icon` are both required — the flash settles *to* the icon.
4163
+ - Fine overrides win over `speed`.
4164
+ - Decorative only — never the sole carrier of meaning (it's `aria-hidden`).
4165
+
4166
+ ## Changes
4167
+ ### v0.43.0
4168
+ - **Added** Initial release. Powers `StatCard`'s `flash` prop.
4101
4169
  # StatusDot
4102
4170
 
4103
4171
  - Import: @devalok/shilp-sutra/ui/status-dot
@@ -4806,6 +4874,55 @@ export default function RootLayout({ children }) {
4806
4874
 
4807
4875
  ### v0.1.0
4808
4876
  - **Added** Initial release
4877
+ # TruncatedText
4878
+
4879
+ - Import: @devalok/shilp-sutra/ui/truncated-text
4880
+ - Server-safe: No
4881
+ - Category: ui
4882
+
4883
+ ## Props
4884
+ children: string (REQUIRED — the full text; always the accessible name, even when shortened)
4885
+ mode: "end" | "clamp" | "middle" (end [default] = single-line ellipsis; clamp = multi-line; middle = keep both ends)
4886
+ lines: number (lines to show in mode="clamp"; default 2)
4887
+ as: "span" | "p" | "div" (element to render; default "span")
4888
+ tooltip: boolean (show the full text in a tooltip when — and only when — actually clipped; default true)
4889
+ title: string (override the full-text accessible name / tooltip; defaults to children)
4890
+ className: string
4891
+
4892
+ ## Defaults
4893
+ mode="end", lines=2, as="span", tooltip=true
4894
+
4895
+ ## Example
4896
+ ```jsx
4897
+ <TruncatedText>{user.name}</TruncatedText>
4898
+
4899
+ // Filenames / emails — keep the extension / domain:
4900
+ <TruncatedText mode="middle">{fileName}</TruncatedText> // report…v7.pdf
4901
+
4902
+ // Multi-line description with a 2-line clamp:
4903
+ <TruncatedText as="p" mode="clamp" lines={2}>{description}</TruncatedText>
4904
+
4905
+ // In a flex row, the shrinking item still needs min-w-0:
4906
+ <div className="flex items-center gap-ds-03">
4907
+ <Avatar … />
4908
+ <TruncatedText className="min-w-0 flex-1">{user.name}</TruncatedText>
4909
+ </div>
4910
+ ```
4911
+
4912
+ ## Composability
4913
+ - **Truncates AND recovers in one place:** `end`/`clamp` are pure CSS (full text stays in the DOM → screen-reader safe). `middle` measures the element and JS-shortens the visible string but keeps the full string as the accessible name. A tooltip appears only when `scrollWidth`/`scrollHeight` actually exceeds the box (measured via ResizeObserver) — no tooltip noise on text that fits.
4914
+ - **`mode="middle"` for identifiers:** filenames (extension), emails (domain), paths, hashes — end-ellipsis destroys the meaningful tail; middle keeps both ends. Middle is not native CSS, so it's measured + recomputed on resize.
4915
+ - **The flexbox gotcha:** `truncate` (and `middle`) do nothing on a flex/grid child unless that item has `min-w-0` (or an explicit width). TruncatedText can't set that on its parent — add `min-w-0` at the call site.
4916
+ - **Don't truncate everything:** page titles/H1, toast bodies, validation messages, and numbers must show fully (a truncated number is a wrong number). Use this for bounded list/nav/metadata text.
4917
+
4918
+ ## Gotchas
4919
+ - `children` must be a string (it's measured + used as the accessible name) — not arbitrary ReactNode.
4920
+ - In a flex row, add `min-w-0` to the TruncatedText (or its column) or nothing truncates.
4921
+ - `mode="middle"` clips to the largest middle-truncated string that fits; the last glyph may be sub-pixel-clipped by overflow-hidden — the tooltip + aria-label always carry the full string.
4922
+
4923
+ ## Changes
4924
+ ### v0.44.0
4925
+ - **Added** Initial release — end/clamp/middle truncation with overflow-aware tooltip recovery.
4809
4926
  # VisuallyHidden
4810
4927
 
4811
4928
  - Import: @devalok/shilp-sutra/ui/visually-hidden
@@ -5,6 +5,7 @@ The default container for any panel-style content that sits **on** the page.
5
5
  ```tsx
6
6
  import {
7
7
  Card,
8
+ CardAction,
8
9
  CardHeader,
9
10
  CardTitle,
10
11
  CardDescription,
@@ -65,8 +66,13 @@ Card (root)
65
66
  | Prop | Type | Notes |
66
67
  |---|---|---|
67
68
  | `interactive` | `boolean` | Adds hover lift + `cursor-pointer`. Make the whole card clickable. Provide `onClick` and `aria-label`. |
68
- | `accent` | `'left'\|'top'\|'right'\|'bottom'` | Decorative colored bar on the named edge. |
69
- | `accentColor` | `'default'\|'accent'\|'error'\|'success'\|'warning'\|'info'` | Color of the accent bar. |
69
+
70
+ ### CardAction (corner slot)
71
+
72
+ | Prop | Type | Notes |
73
+ |---|---|---|
74
+ | `placement` | `'top-right'` (default) `\| 'top-left' \| 'bottom-right' \| 'bottom-left'` | Which corner. Inset matches the card's content padding. |
75
+ | `tuck` | `boolean` | Pull a step toward the corner so an icon button's glyph (not its padding box) aligns to the content edge. |
70
76
 
71
77
  ## Examples
72
78
 
@@ -107,13 +113,17 @@ Card (root)
107
113
  </Card>
108
114
  ```
109
115
 
110
- **Accent barpipeline state:**
116
+ **Corner slotstatus badge / overflow action:**
111
117
  ```tsx
112
- <Card accent="left" accentColor="success">
118
+ <Card>
119
+ <CardAction><Badge color="success" size="xs">DEPLOYED</Badge></CardAction>
113
120
  <CardHeader>
114
121
  <CardTitle>Deploy succeeded</CardTitle>
115
122
  <CardDescription>main · 2 min ago</CardDescription>
116
123
  </CardHeader>
124
+ <CardAction placement="bottom-right" tuck>
125
+ <Button variant="ghost" size="sm">View logs</Button>
126
+ </CardAction>
117
127
  </Card>
118
128
  ```
119
129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devalok/shilp-sutra",
3
- "version": "0.42.1",
3
+ "version": "0.44.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>",
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.42.1"
6
+ version: "0.44.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