@devalok/shilp-sutra 0.43.0 → 0.44.1

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 (90) hide show
  1. package/MIGRATION.md +46 -0
  2. package/dist/_chunks/chat.js +163 -162
  3. package/dist/_chunks/chat.js.map +1 -1
  4. package/dist/_chunks/mention-suggestion.js +34 -32
  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/tree-view.js +1 -1
  9. package/dist/_chunks/tree-view.js.map +1 -1
  10. package/dist/ai/command-bar.js.map +1 -1
  11. package/dist/composed/command-palette.js +3 -3
  12. package/dist/composed/command-palette.js.map +1 -1
  13. package/dist/composed/content-card.d.ts +6 -0
  14. package/dist/composed/content-card.d.ts.map +1 -1
  15. package/dist/composed/content-card.js.map +1 -1
  16. package/dist/composed/extensions/file-attachment.d.ts.map +1 -1
  17. package/dist/composed/file-preview/audio-preview.d.ts.map +1 -1
  18. package/dist/composed/file-preview.d.ts.map +1 -1
  19. package/dist/composed/file-preview.js +146 -142
  20. package/dist/composed/file-preview.js.map +1 -1
  21. package/dist/composed/filter-bar.js +5 -2
  22. package/dist/composed/filter-bar.js.map +1 -1
  23. package/dist/composed/master-detail.js +1 -1
  24. package/dist/composed/master-detail.js.map +1 -1
  25. package/dist/composed/page-header.js +1 -1
  26. package/dist/composed/page-header.js.map +1 -1
  27. package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
  28. package/dist/composed/rich-chat-input/audio-player.d.ts.map +1 -1
  29. package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
  30. package/dist/composed/rich-chat-input.js +595 -586
  31. package/dist/composed/rich-chat-input.js.map +1 -1
  32. package/dist/composed/rich-text-editor.d.ts.map +1 -1
  33. package/dist/composed/rich-text-editor.js +164 -162
  34. package/dist/composed/rich-text-editor.js.map +1 -1
  35. package/dist/hooks/index.d.ts +2 -2
  36. package/dist/hooks/index.d.ts.map +1 -1
  37. package/dist/shell/notification-center.d.ts.map +1 -1
  38. package/dist/shell/notification-center.js +101 -97
  39. package/dist/shell/notification-center.js.map +1 -1
  40. package/dist/shell/sidebar.d.ts.map +1 -1
  41. package/dist/shell/sidebar.js +137 -122
  42. package/dist/shell/sidebar.js.map +1 -1
  43. package/dist/shell/top-bar.d.ts.map +1 -1
  44. package/dist/shell/top-bar.js +86 -83
  45. package/dist/shell/top-bar.js.map +1 -1
  46. package/dist/tokens/semantic.css +1 -0
  47. package/dist/ui/avatar.js +1 -1
  48. package/dist/ui/avatar.js.map +1 -1
  49. package/dist/ui/breadcrumb.js +2 -2
  50. package/dist/ui/breadcrumb.js.map +1 -1
  51. package/dist/ui/button.js.map +1 -1
  52. package/dist/ui/card.d.ts +82 -7
  53. package/dist/ui/card.d.ts.map +1 -1
  54. package/dist/ui/card.js +74 -108
  55. package/dist/ui/card.js.map +1 -1
  56. package/dist/ui/chat/message.d.ts.map +1 -1
  57. package/dist/ui/combobox.js +2 -2
  58. package/dist/ui/combobox.js.map +1 -1
  59. package/dist/ui/dropdown-menu.js +9 -3
  60. package/dist/ui/dropdown-menu.js.map +1 -1
  61. package/dist/ui/index.d.ts +2 -1
  62. package/dist/ui/index.d.ts.map +1 -1
  63. package/dist/ui/index.js +58 -57
  64. package/dist/ui/index.js.map +1 -1
  65. package/dist/ui/menubar.js +9 -3
  66. package/dist/ui/menubar.js.map +1 -1
  67. package/dist/ui/oauth-button/oauth-button.d.ts.map +1 -1
  68. package/dist/ui/select.js +5 -2
  69. package/dist/ui/select.js.map +1 -1
  70. package/dist/ui/sidebar.js +73 -73
  71. package/dist/ui/sidebar.js.map +1 -1
  72. package/dist/ui/stat-card.d.ts +8 -5
  73. package/dist/ui/stat-card.d.ts.map +1 -1
  74. package/dist/ui/stat-card.js +168 -150
  75. package/dist/ui/stat-card.js.map +1 -1
  76. package/dist/ui/tabs.d.ts.map +1 -1
  77. package/dist/ui/tabs.js +5 -2
  78. package/dist/ui/tabs.js.map +1 -1
  79. package/dist/ui/truncated-text.d.ts +37 -0
  80. package/dist/ui/truncated-text.d.ts.map +1 -0
  81. package/dist/ui/truncated-text.js +53 -0
  82. package/dist/ui/truncated-text.js.map +1 -0
  83. package/docs/components/ui/card.md +13 -4
  84. package/docs/components/ui/stat-card.md +7 -2
  85. package/docs/components/ui/truncated-text.md +49 -0
  86. package/llms-full.txt +70 -7
  87. package/make-kit/components/card.md +14 -4
  88. package/package.json +6 -1
  89. package/skill/SKILL.md +1 -1
  90. package/skill/references/components-full.md +70 -7
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.js","names":[],"sources":["../../src/ui/tabs.tsx"],"sourcesContent":["'use client'\n\nimport * as TabsPrimitive from '@primitives/react-tabs'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { LayoutGroup,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\n/* ── Active-value context (drives layoutId indicator) ────── */\ntype TabsOrientation = 'horizontal' | 'vertical'\nconst TabsValueContext = React.createContext<string | undefined>(undefined)\nconst TabsOrientationContext = React.createContext<TabsOrientation>('horizontal')\n\n/**\n * Tabs compound component — accessible tabbed navigation with keyboard support and two visual\n * variants (line underline and contained pill styles).\n *\n * **Parts (in composition order):**\n * - `Tabs` — manages active tab state (this root; takes `defaultValue`, `value`, `onValueChange`)\n * - `TabsList` — tab bar container (takes `variant=\"line\"|\"contained\"`, default `\"line\"`)\n * - `TabsTrigger` — individual tab button (requires `value`; inherits `variant` from TabsList via context)\n * - `TabsContent` — the panel shown when its tab is active (requires `value` matching a TabsTrigger)\n *\n * **Critical behavior:** `variant` set on `TabsList` propagates automatically via React context to\n * all `TabsTrigger` children. You do NOT need to repeat `variant` on each trigger — but you CAN\n * override it per-trigger if needed.\n *\n * The active tab indicator animates between tabs using Framer Motion `layoutId`. Line variant\n * shows a sliding underline; contained variant shows a sliding pill background.\n *\n * @compound\n * @example\n * // Default line variant:\n * <Tabs defaultValue=\"overview\">\n * <TabsList>\n * <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n * <TabsTrigger value=\"activity\">Activity</TabsTrigger>\n * <TabsTrigger value=\"settings\">Settings</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"overview\">Overview content</TabsContent>\n * <TabsContent value=\"activity\">Activity content</TabsContent>\n * <TabsContent value=\"settings\">Settings content</TabsContent>\n * </Tabs>\n *\n * @example\n * // Contained pill variant (controlled):\n * const [tab, setTab] = useState('members')\n * <Tabs value={tab} onValueChange={setTab}>\n * <TabsList variant=\"contained\">\n * <TabsTrigger value=\"members\">Members</TabsTrigger>\n * <TabsTrigger value=\"roles\">Roles</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"members\">Members list here.</TabsContent>\n * <TabsContent value=\"roles\">Roles list here.</TabsContent>\n * </Tabs>\n */\nconst Tabs = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>\n>(({ value: valueProp, defaultValue, onValueChange, orientation, ...props }, ref) => {\n // Track the active value so TabsTrigger can conditionally render the motion indicator.\n // For controlled usage, mirror the prop; for uncontrolled, manage internal state.\n const [activeValue, setActiveValue] = React.useState(valueProp ?? defaultValue ?? '')\n\n // Sync controlled value\n React.useEffect(() => {\n if (valueProp !== undefined) setActiveValue(valueProp)\n }, [valueProp])\n\n const handleValueChange = React.useCallback(\n (newValue: string) => {\n setActiveValue(newValue)\n onValueChange?.(newValue)\n },\n [onValueChange],\n )\n\n const resolvedOrientation: TabsOrientation = orientation ?? 'horizontal'\n\n return (\n <TabsValueContext.Provider value={activeValue}>\n <TabsOrientationContext.Provider value={resolvedOrientation}>\n <TabsPrimitive.Root\n ref={ref}\n value={valueProp}\n defaultValue={valueProp === undefined ? defaultValue : undefined}\n onValueChange={handleValueChange}\n orientation={orientation}\n {...props}\n className={cn(\n resolvedOrientation === 'vertical' && 'flex flex-row gap-ds-05',\n props.className,\n )}\n />\n </TabsOrientationContext.Provider>\n </TabsValueContext.Provider>\n )\n})\nTabs.displayName = 'Tabs'\n\n/** Props for the Tabs root (defaultValue, value, onValueChange, etc.). */\nexport type TabsProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>\n\ntype TabsVariant = 'line' | 'contained'\ntype TabsSize = 'sm' | 'md' | 'lg'\ntype TabsColor = 'accent' | 'neutral'\n\ninterface TabsListContextValue {\n variant: TabsVariant\n size: TabsSize\n color: TabsColor\n layoutId: string\n}\n\nconst TabsListContext = React.createContext<TabsListContextValue>({\n variant: 'line',\n size: 'md',\n color: 'accent',\n layoutId: 'tab-indicator',\n})\n\nconst tabsListVariants = cva('inline-flex items-center', {\n variants: {\n variant: {\n line: 'border-b border-surface-border-strong w-full gap-0',\n contained:\n 'bg-surface-raised p-ds-02 rounded-surface gap-ds-02',\n },\n size: {\n sm: 'h-8',\n md: 'h-10',\n lg: 'h-12',\n },\n orientation: {\n horizontal: '',\n vertical: 'flex-col',\n },\n },\n compoundVariants: [\n // Vertical line: left border instead of bottom, fixed width, auto height\n { variant: 'line', orientation: 'vertical', class: 'border-b-0 border-l border-surface-border-strong w-48 shrink-0 h-auto gap-0' },\n // Vertical contained: column layout, fixed width, auto height\n { variant: 'contained', orientation: 'vertical', class: 'h-auto w-48 shrink-0' },\n ],\n defaultVariants: { variant: 'line', size: 'md', orientation: 'horizontal' },\n})\n\nconst tabsTriggerVariants = cva(\n 'relative inline-flex items-center justify-center gap-ds-02 whitespace-nowrap font-sans font-medium transition-colors duration-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-action-disabled',\n {\n variants: {\n variant: {\n line: [\n '-mb-px',\n 'text-surface-fg-muted hover:text-surface-fg',\n ],\n contained: [\n 'rounded-control',\n 'text-surface-fg-muted hover:text-surface-fg',\n 'data-[state=active]:text-surface-fg',\n ],\n },\n size: {\n sm: 'px-ds-03 py-ds-02 text-ds-xs',\n md: 'px-ds-05 py-ds-03 text-ds-sm',\n lg: 'px-ds-06 py-ds-04 text-ds-md',\n },\n },\n defaultVariants: { variant: 'line', size: 'md' },\n },\n)\n\n/**\n * TabsList — container for tab triggers. Sets `variant` for all child TabsTriggers via context.\n *\n * **Compound structure — variant propagates automatically:**\n * ```tsx\n * <Tabs defaultValue=\"overview\">\n * <TabsList variant=\"contained\">\n * <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n * <TabsTrigger value=\"activity\">Activity</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"overview\">Overview content</TabsContent>\n * <TabsContent value=\"activity\">Activity content</TabsContent>\n * </Tabs>\n * ```\n *\n * `variant` on `TabsList` flows to all `TabsTrigger` children via React context.\n * You do NOT need to repeat `variant` on each `TabsTrigger`.\n */\nexport interface TabsListProps\n extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>,\n VariantProps<typeof tabsListVariants> {\n /** Color axis — affects active tab indicator and text color. @default \"accent\" */\n color?: TabsColor\n}\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n TabsListProps\n>(({ className, variant, size, color, ...props }, ref) => {\n const orientation = React.useContext(TabsOrientationContext)\n const resolvedVariant: TabsVariant = variant ?? 'line'\n const resolvedSize: TabsSize = size ?? 'md'\n const resolvedColor: TabsColor = color ?? 'accent'\n const layoutId = React.useId()\n const contextValue = React.useMemo(\n () => ({\n variant: resolvedVariant,\n size: resolvedSize,\n color: resolvedColor,\n layoutId: `tab-indicator-${layoutId}`,\n }),\n [resolvedVariant, resolvedSize, resolvedColor, layoutId],\n )\n return (\n <TabsListContext.Provider value={contextValue}>\n <LayoutGroup>\n <TabsPrimitive.List\n ref={ref}\n className={cn(tabsListVariants({ variant: resolvedVariant, size: resolvedSize, orientation }), className)}\n {...props}\n />\n </LayoutGroup>\n </TabsListContext.Provider>\n )\n})\nTabsList.displayName = TabsPrimitive.List.displayName\n\nexport interface TabsTriggerProps\n extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>,\n VariantProps<typeof tabsTriggerVariants> {}\n\n/* ── Color classes for active state ────── */\nconst lineActiveColorMap: Record<TabsColor, string> = {\n accent: 'data-[state=active]:text-accent-11',\n neutral: 'data-[state=active]:text-surface-fg',\n}\n\nconst lineIndicatorColorMap: Record<TabsColor, string> = {\n accent: 'bg-accent-9',\n neutral: 'bg-surface-fg',\n}\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, variant: variantProp, children, ...props }, ref) => {\n const listContext = React.useContext(TabsListContext)\n const activeValue = React.useContext(TabsValueContext)\n const orientation = React.useContext(TabsOrientationContext)\n const variant = variantProp ?? listContext.variant\n const size = listContext.size\n const color = listContext.color\n const isActive = props.value === activeValue\n const isVertical = orientation === 'vertical'\n\n return (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n tabsTriggerVariants({ variant, size }),\n variant === 'line' && lineActiveColorMap[color],\n // Vertical: full width triggers, left margin instead of bottom\n isVertical && 'w-full justify-start',\n variant === 'line' && isVertical && '-ml-px -mb-0',\n className,\n )}\n {...props}\n >\n {/* Contained variant: sliding pill background */}\n {variant === 'contained' && isActive && (\n <motion.span\n layoutId={`${listContext.layoutId}-contained`}\n className=\"absolute inset-0 rounded-control bg-surface-overlay shadow-raised\"\n transition={springs.smooth}\n />\n )}\n {/* Content sits above the indicator */}\n <span className=\"relative z-[1] inline-flex items-center gap-ds-02\">{children}</span>\n {/* Line variant: sliding indicator -- bottom underline (horizontal) or left bar (vertical) */}\n {variant === 'line' && isActive && (\n <motion.span\n layoutId={`${listContext.layoutId}-line`}\n className={cn(\n 'absolute',\n isVertical\n ? 'left-0 top-0 bottom-0 w-0.5'\n : 'bottom-0 left-0 right-0 h-0.5',\n lineIndicatorColorMap[color],\n )}\n transition={springs.smooth}\n />\n )}\n </TabsPrimitive.Trigger>\n )\n})\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, children, ...props }, ref) => {\n const orientation = React.useContext(TabsOrientationContext)\n const isVertical = orientation === 'vertical'\n\n return (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n 'ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2',\n isVertical ? 'mt-0 flex-1 min-w-0' : 'mt-ds-05',\n className,\n )}\n {...props}\n >\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n >\n {children}\n </motion.div>\n </TabsPrimitive.Content>\n )\n})\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\n/** Props for TabsContent. The `value` prop must match a TabsTrigger's `value`. */\nexport type TabsContentProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n\nexport { Tabs, TabsContent,TabsList, TabsTrigger }\nexport type { TabsColor, TabsOrientation,TabsSize }\n"],"mappings":"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"}
1
+ {"version":3,"file":"tabs.js","names":[],"sources":["../../src/ui/tabs.tsx"],"sourcesContent":["'use client'\n\nimport * as TabsPrimitive from '@primitives/react-tabs'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { LayoutGroup,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\n\n/* ── Active-value context (drives layoutId indicator) ────── */\ntype TabsOrientation = 'horizontal' | 'vertical'\nconst TabsValueContext = React.createContext<string | undefined>(undefined)\nconst TabsOrientationContext = React.createContext<TabsOrientation>('horizontal')\n\n/**\n * Tabs compound component — accessible tabbed navigation with keyboard support and two visual\n * variants (line underline and contained pill styles).\n *\n * **Parts (in composition order):**\n * - `Tabs` — manages active tab state (this root; takes `defaultValue`, `value`, `onValueChange`)\n * - `TabsList` — tab bar container (takes `variant=\"line\"|\"contained\"`, default `\"line\"`)\n * - `TabsTrigger` — individual tab button (requires `value`; inherits `variant` from TabsList via context)\n * - `TabsContent` — the panel shown when its tab is active (requires `value` matching a TabsTrigger)\n *\n * **Critical behavior:** `variant` set on `TabsList` propagates automatically via React context to\n * all `TabsTrigger` children. You do NOT need to repeat `variant` on each trigger — but you CAN\n * override it per-trigger if needed.\n *\n * The active tab indicator animates between tabs using Framer Motion `layoutId`. Line variant\n * shows a sliding underline; contained variant shows a sliding pill background.\n *\n * @compound\n * @example\n * // Default line variant:\n * <Tabs defaultValue=\"overview\">\n * <TabsList>\n * <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n * <TabsTrigger value=\"activity\">Activity</TabsTrigger>\n * <TabsTrigger value=\"settings\">Settings</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"overview\">Overview content</TabsContent>\n * <TabsContent value=\"activity\">Activity content</TabsContent>\n * <TabsContent value=\"settings\">Settings content</TabsContent>\n * </Tabs>\n *\n * @example\n * // Contained pill variant (controlled):\n * const [tab, setTab] = useState('members')\n * <Tabs value={tab} onValueChange={setTab}>\n * <TabsList variant=\"contained\">\n * <TabsTrigger value=\"members\">Members</TabsTrigger>\n * <TabsTrigger value=\"roles\">Roles</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"members\">Members list here.</TabsContent>\n * <TabsContent value=\"roles\">Roles list here.</TabsContent>\n * </Tabs>\n */\nconst Tabs = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>\n>(({ value: valueProp, defaultValue, onValueChange, orientation, ...props }, ref) => {\n // Track the active value so TabsTrigger can conditionally render the motion indicator.\n // For controlled usage, mirror the prop; for uncontrolled, manage internal state.\n const [activeValue, setActiveValue] = React.useState(valueProp ?? defaultValue ?? '')\n\n // Sync controlled value\n React.useEffect(() => {\n if (valueProp !== undefined) setActiveValue(valueProp)\n }, [valueProp])\n\n const handleValueChange = React.useCallback(\n (newValue: string) => {\n setActiveValue(newValue)\n onValueChange?.(newValue)\n },\n [onValueChange],\n )\n\n const resolvedOrientation: TabsOrientation = orientation ?? 'horizontal'\n\n return (\n <TabsValueContext.Provider value={activeValue}>\n <TabsOrientationContext.Provider value={resolvedOrientation}>\n <TabsPrimitive.Root\n ref={ref}\n value={valueProp}\n defaultValue={valueProp === undefined ? defaultValue : undefined}\n onValueChange={handleValueChange}\n orientation={orientation}\n {...props}\n className={cn(\n resolvedOrientation === 'vertical' && 'flex flex-row gap-ds-05',\n props.className,\n )}\n />\n </TabsOrientationContext.Provider>\n </TabsValueContext.Provider>\n )\n})\nTabs.displayName = 'Tabs'\n\n/** Props for the Tabs root (defaultValue, value, onValueChange, etc.). */\nexport type TabsProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>\n\ntype TabsVariant = 'line' | 'contained'\ntype TabsSize = 'sm' | 'md' | 'lg'\ntype TabsColor = 'accent' | 'neutral'\n\ninterface TabsListContextValue {\n variant: TabsVariant\n size: TabsSize\n color: TabsColor\n layoutId: string\n}\n\nconst TabsListContext = React.createContext<TabsListContextValue>({\n variant: 'line',\n size: 'md',\n color: 'accent',\n layoutId: 'tab-indicator',\n})\n\nconst tabsListVariants = cva('inline-flex items-center', {\n variants: {\n variant: {\n line: 'border-b border-surface-border-strong w-full gap-0',\n contained:\n 'bg-surface-raised p-ds-02 rounded-surface gap-ds-02',\n },\n size: {\n sm: 'h-8',\n md: 'h-10',\n lg: 'h-12',\n },\n orientation: {\n horizontal: '',\n vertical: 'flex-col',\n },\n },\n compoundVariants: [\n // Vertical line: left border instead of bottom, fixed width, auto height\n { variant: 'line', orientation: 'vertical', class: 'border-b-0 border-l border-surface-border-strong w-48 shrink-0 h-auto gap-0' },\n // Vertical contained: column layout, fixed width, auto height\n { variant: 'contained', orientation: 'vertical', class: 'h-auto w-48 shrink-0' },\n ],\n defaultVariants: { variant: 'line', size: 'md', orientation: 'horizontal' },\n})\n\nconst tabsTriggerVariants = cva(\n 'relative inline-flex items-center justify-center gap-ds-02 whitespace-nowrap font-sans font-medium transition-colors duration-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-action-disabled',\n {\n variants: {\n variant: {\n line: [\n '-mb-px',\n 'text-surface-fg-muted hover:text-surface-fg',\n ],\n contained: [\n 'rounded-control',\n 'text-surface-fg-muted hover:text-surface-fg',\n 'data-[state=active]:text-surface-fg',\n ],\n },\n size: {\n sm: 'px-ds-03 py-ds-02 text-ds-xs',\n md: 'px-ds-05 py-ds-03 text-ds-sm',\n lg: 'px-ds-06 py-ds-04 text-ds-md',\n },\n },\n defaultVariants: { variant: 'line', size: 'md' },\n },\n)\n\n/**\n * TabsList — container for tab triggers. Sets `variant` for all child TabsTriggers via context.\n *\n * **Compound structure — variant propagates automatically:**\n * ```tsx\n * <Tabs defaultValue=\"overview\">\n * <TabsList variant=\"contained\">\n * <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n * <TabsTrigger value=\"activity\">Activity</TabsTrigger>\n * </TabsList>\n * <TabsContent value=\"overview\">Overview content</TabsContent>\n * <TabsContent value=\"activity\">Activity content</TabsContent>\n * </Tabs>\n * ```\n *\n * `variant` on `TabsList` flows to all `TabsTrigger` children via React context.\n * You do NOT need to repeat `variant` on each `TabsTrigger`.\n */\nexport interface TabsListProps\n extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>,\n VariantProps<typeof tabsListVariants> {\n /** Color axis — affects active tab indicator and text color. @default \"accent\" */\n color?: TabsColor\n}\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n TabsListProps\n>(({ className, variant, size, color, ...props }, ref) => {\n const orientation = React.useContext(TabsOrientationContext)\n const resolvedVariant: TabsVariant = variant ?? 'line'\n const resolvedSize: TabsSize = size ?? 'md'\n const resolvedColor: TabsColor = color ?? 'accent'\n const layoutId = React.useId()\n const contextValue = React.useMemo(\n () => ({\n variant: resolvedVariant,\n size: resolvedSize,\n color: resolvedColor,\n layoutId: `tab-indicator-${layoutId}`,\n }),\n [resolvedVariant, resolvedSize, resolvedColor, layoutId],\n )\n return (\n <TabsListContext.Provider value={contextValue}>\n <LayoutGroup>\n <TabsPrimitive.List\n ref={ref}\n className={cn(tabsListVariants({ variant: resolvedVariant, size: resolvedSize, orientation }), className)}\n {...props}\n />\n </LayoutGroup>\n </TabsListContext.Provider>\n )\n})\nTabsList.displayName = TabsPrimitive.List.displayName\n\nexport interface TabsTriggerProps\n extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>,\n VariantProps<typeof tabsTriggerVariants> {}\n\n/* ── Color classes for active state ────── */\nconst lineActiveColorMap: Record<TabsColor, string> = {\n accent: 'data-[state=active]:text-accent-11',\n neutral: 'data-[state=active]:text-surface-fg',\n}\n\nconst lineIndicatorColorMap: Record<TabsColor, string> = {\n accent: 'bg-accent-9',\n neutral: 'bg-surface-fg',\n}\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, variant: variantProp, children, ...props }, ref) => {\n const listContext = React.useContext(TabsListContext)\n const activeValue = React.useContext(TabsValueContext)\n const orientation = React.useContext(TabsOrientationContext)\n const variant = variantProp ?? listContext.variant\n const size = listContext.size\n const color = listContext.color\n const isActive = props.value === activeValue\n const isVertical = orientation === 'vertical'\n\n return (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n tabsTriggerVariants({ variant, size }),\n variant === 'line' && lineActiveColorMap[color],\n // Vertical: full width triggers, left margin instead of bottom\n isVertical && 'w-full justify-start',\n variant === 'line' && isVertical && '-ml-px -mb-0',\n className,\n )}\n {...props}\n >\n {/* Contained variant: sliding pill background */}\n {variant === 'contained' && isActive && (\n <motion.span\n layoutId={`${listContext.layoutId}-contained`}\n className=\"absolute inset-0 rounded-control bg-surface-overlay shadow-raised\"\n transition={springs.smooth}\n />\n )}\n {/* Content sits above the indicator */}\n <span className=\"relative z-[1] inline-flex min-w-0 items-center gap-ds-02\">\n <span className=\"truncate\">{children}</span>\n </span>\n {/* Line variant: sliding indicator -- bottom underline (horizontal) or left bar (vertical) */}\n {variant === 'line' && isActive && (\n <motion.span\n layoutId={`${listContext.layoutId}-line`}\n className={cn(\n 'absolute',\n isVertical\n ? 'left-0 top-0 bottom-0 w-0.5'\n : 'bottom-0 left-0 right-0 h-0.5',\n lineIndicatorColorMap[color],\n )}\n transition={springs.smooth}\n />\n )}\n </TabsPrimitive.Trigger>\n )\n})\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, children, ...props }, ref) => {\n const orientation = React.useContext(TabsOrientationContext)\n const isVertical = orientation === 'vertical'\n\n return (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n 'ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2',\n isVertical ? 'mt-0 flex-1 min-w-0' : 'mt-ds-05',\n className,\n )}\n {...props}\n >\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\n >\n {children}\n </motion.div>\n </TabsPrimitive.Content>\n )\n})\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\n/** Props for TabsContent. The `value` prop must match a TabsTrigger's `value`. */\nexport type TabsContentProps = React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n\nexport { Tabs, TabsContent,TabsList, TabsTrigger }\nexport type { TabsColor, TabsOrientation,TabsSize }\n"],"mappings":"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"}
@@ -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,13 @@
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
- surface: "raised" | "flat" (raised = ring-in-shadow, no border [default]; flat = border, no shadow)
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
20
21
  accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
21
22
  iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
22
23
  flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
@@ -56,7 +57,7 @@
56
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.
57
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.
58
59
  - **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
59
- - **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. `surface` 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).
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).
60
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`.
61
62
  - **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
62
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.
@@ -69,6 +70,10 @@
69
70
  - `sparkline` needs at least 2 data points to render
70
71
 
71
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
+
72
77
  ### v0.43.0
73
78
  - **BREAKING** Removed `accent` (colored left-rail). Use `accentStyle` (`"icon"` | `"tint"`) or rely on `delta` for trend colour.
74
79
  - **Added** `surface`, `accentStyle`, `iconFill`, `flash`, `flashSpeed`. New standalone `StatFlash` primitive. Base no longer stacks border + shadow.
@@ -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.43.0
8
+ > Version: 0.44.1
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,13 @@ 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
- surface: "raised" | "flat" (raised = ring-in-shadow, no border [default]; flat = border, no shadow)
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
4050
4060
  accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
4051
4061
  iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
4052
4062
  flash: "up" | "down" | "goal" | "record" | "alert" | "live" | { tone, icon } (opt-in entrance flash; requires icon)
@@ -4086,7 +4096,7 @@ import { SplitButton } from '@devalok/shilp-sutra'
4086
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.
4087
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.
4088
4098
  - **Interactive modes:** `onClick` makes the entire card a button; `href` makes it a link. Mutually exclusive — href wins if both are set.
4089
- - **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. `surface` 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).
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).
4090
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`.
4091
4101
  - **Sparkline:** Pure SVG, lightweight — no chart library. For rich charts use Chart components. Minimum 2 data points.
4092
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.
@@ -4099,6 +4109,10 @@ import { SplitButton } from '@devalok/shilp-sutra'
4099
4109
  - `sparkline` needs at least 2 data points to render
4100
4110
 
4101
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
+
4102
4116
  ### v0.43.0
4103
4117
  - **BREAKING** Removed `accent` (colored left-rail). Use `accentStyle` (`"icon"` | `"tint"`) or rely on `delta` for trend colour.
4104
4118
  - **Added** `surface`, `accentStyle`, `iconFill`, `flash`, `flashSpeed`. New standalone `StatFlash` primitive. Base no longer stacks border + shadow.
@@ -4860,6 +4874,55 @@ export default function RootLayout({ children }) {
4860
4874
 
4861
4875
  ### v0.1.0
4862
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.
4863
4926
  # VisuallyHidden
4864
4927
 
4865
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.43.0",
3
+ "version": "0.44.1",
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>",
@@ -432,6 +432,11 @@
432
432
  "import": "./dist/ui/tooltip.js",
433
433
  "default": "./dist/ui/tooltip.js"
434
434
  },
435
+ "./ui/truncated-text": {
436
+ "types": "./dist/ui/truncated-text.d.ts",
437
+ "import": "./dist/ui/truncated-text.js",
438
+ "default": "./dist/ui/truncated-text.js"
439
+ },
435
440
  "./ui/visually-hidden": {
436
441
  "types": "./dist/ui/visually-hidden.d.ts",
437
442
  "import": "./dist/ui/visually-hidden.js",
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.43.0"
6
+ version: "0.44.1"
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