@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.
- package/MIGRATION.md +46 -0
- package/dist/_chunks/chat.js +163 -162
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/mention-suggestion.js +34 -32
- package/dist/_chunks/mention-suggestion.js.map +1 -1
- package/dist/_chunks/oauth-button.js +102 -97
- package/dist/_chunks/oauth-button.js.map +1 -1
- package/dist/_chunks/tree-view.js +1 -1
- package/dist/_chunks/tree-view.js.map +1 -1
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/composed/command-palette.js +3 -3
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/content-card.d.ts +6 -0
- package/dist/composed/content-card.d.ts.map +1 -1
- package/dist/composed/content-card.js.map +1 -1
- package/dist/composed/extensions/file-attachment.d.ts.map +1 -1
- package/dist/composed/file-preview/audio-preview.d.ts.map +1 -1
- package/dist/composed/file-preview.d.ts.map +1 -1
- package/dist/composed/file-preview.js +146 -142
- package/dist/composed/file-preview.js.map +1 -1
- package/dist/composed/filter-bar.js +5 -2
- package/dist/composed/filter-bar.js.map +1 -1
- package/dist/composed/master-detail.js +1 -1
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/page-header.js +1 -1
- package/dist/composed/page-header.js.map +1 -1
- package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/audio-player.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
- package/dist/composed/rich-chat-input.js +595 -586
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.d.ts.map +1 -1
- package/dist/composed/rich-text-editor.js +164 -162
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/shell/notification-center.d.ts.map +1 -1
- package/dist/shell/notification-center.js +101 -97
- package/dist/shell/notification-center.js.map +1 -1
- package/dist/shell/sidebar.d.ts.map +1 -1
- package/dist/shell/sidebar.js +137 -122
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/shell/top-bar.d.ts.map +1 -1
- package/dist/shell/top-bar.js +86 -83
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/tokens/semantic.css +1 -0
- package/dist/ui/avatar.js +1 -1
- package/dist/ui/avatar.js.map +1 -1
- package/dist/ui/breadcrumb.js +2 -2
- package/dist/ui/breadcrumb.js.map +1 -1
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.d.ts +82 -7
- package/dist/ui/card.d.ts.map +1 -1
- package/dist/ui/card.js +74 -108
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/chat/message.d.ts.map +1 -1
- package/dist/ui/combobox.js +2 -2
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/dropdown-menu.js +9 -3
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +58 -57
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/menubar.js +9 -3
- package/dist/ui/menubar.js.map +1 -1
- package/dist/ui/oauth-button/oauth-button.d.ts.map +1 -1
- package/dist/ui/select.js +5 -2
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sidebar.js +73 -73
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/stat-card.d.ts +8 -5
- package/dist/ui/stat-card.d.ts.map +1 -1
- package/dist/ui/stat-card.js +168 -150
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/tabs.d.ts.map +1 -1
- package/dist/ui/tabs.js +5 -2
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/truncated-text.d.ts +37 -0
- package/dist/ui/truncated-text.d.ts.map +1 -0
- package/dist/ui/truncated-text.js +53 -0
- package/dist/ui/truncated-text.js.map +1 -0
- package/docs/components/ui/card.md +13 -4
- package/docs/components/ui/stat-card.md +7 -2
- package/docs/components/ui/truncated-text.md +49 -0
- package/llms-full.txt +70 -7
- package/make-kit/components/card.md +14 -4
- package/package.json +6 -1
- package/skill/SKILL.md +1 -1
- package/skill/references/components-full.md +70 -7
package/dist/ui/tabs.js.map
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
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
|
-
- **
|
|
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)
|
|
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
|
-
|
|
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. `
|
|
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.
|
|
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
|
-
|
|
1100
|
-
|
|
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
|
-
- **
|
|
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)
|
|
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
|
-
|
|
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. `
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
**
|
|
116
|
+
**Corner slot — status badge / overflow action:**
|
|
111
117
|
```tsx
|
|
112
|
-
<Card
|
|
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.
|
|
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.
|
|
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
|