@devalok/shilp-sutra 0.50.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MIGRATION.md CHANGED
@@ -4,6 +4,12 @@ This page indexes all breaking changes across `@devalok/shilp-sutra` versions. F
4
4
 
5
5
  > **Upgrading from < 0.36?** Start here, then read each intermediate version section. Breaking changes stack — skipping versions means stacking migrations.
6
6
 
7
+ ## v0.51.0 — additive (no migration required)
8
+
9
+ Nothing breaks at the TypeScript level; this release is additive.
10
+
11
+ > Auto-stubbed by `version-packages` so the release audit's MIGRATION-section gate can't block. If any behavioral or visual change needs consumer action, replace this stub with the specifics on the Version Packages PR before publishing.
12
+
7
13
  ## v0.50.0 — additive (no migration required)
8
14
 
9
15
  Nothing breaks at the TypeScript level; this release is additive.
package/dist/ui/card.d.ts CHANGED
@@ -12,10 +12,11 @@ declare const cardVariants: (props?: ({
12
12
  * Props for Card — a general-purpose content container with 4 elevation/style variants and
13
13
  * an optional interactive hover state.
14
14
  *
15
- * **Variants (elevation-led vs border-led — never both):** `default` (ring-in-shadow, no border) |
16
- * `elevated` (stronger shadow-raised-hover, no border) | `outline` (visible border, no shadow) |
17
- * `flat` (filled background, no edge). The shadow tokens carry their own 1px ring, so elevated
18
- * variants need no explicit border (make-kit rule #6).
15
+ * **Variants (tonal, elevation-led, or border-led — never border+shadow together):** `default`
16
+ * (tonal surface shift + `border-card` hairline, no shadow) | `elevated` (shadow-raised-hover,
17
+ * no border use when a card should visibly pop) | `outline` (strong visible border, no shadow) |
18
+ * `flat` (filled background, no edge). The shadow tokens carry their own 1px ring, so `elevated`
19
+ * needs no explicit border (make-kit rule #6).
19
20
  *
20
21
  * **Composition:** Use sub-components `<CardHeader>`, `<CardTitle>`, `<CardDescription>`,
21
22
  * `<CardContent>`, and `<CardFooter>` for consistent internal spacing. Text content must live
@@ -1 +1 @@
1
- {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/ui/card.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAElC,QAAA,MAAM,YAAY;;;;;8EAiDjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,MAAM,WAAW,SACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,EACzD,YAAY,CAAC,OAAO,YAAY,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,YAAY,EAAE,QAAQ,EAAE,CAAA;AAExB,QAAA,MAAM,IAAI,kFAgCT,CAAA;AAGD,QAAA,MAAM,UAAU,6GAYd,CAAA;AAGF,QAAA,MAAM,SAAS,6GASb,CAAA;AAGF,QAAA,MAAM,eAAe,6GASnB,CAAA;AAGF,QAAA,MAAM,WAAW,6GAcf,CAAA;AAGF,QAAA,MAAM,UAAU,6GASd,CAAA;AAGF;;;;;GAKG;AACH,QAAA,MAAM,WAAW,6GASf,CAAA;AAGF,KAAK,aAAa,GAAG,GAAG,GAAG,KAAK,GAAG,QAAQ,GAAG,GAAG,GAAG,KAAK,CAAA;AAczD,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,SAAS,uFAId,CAAA;AAGD,KAAK,mBAAmB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAA;AAWpF,MAAM,WAAW,eAAgB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC3E,4DAA4D;IAC5D,SAAS,CAAC,EAAE,mBAAmB,CAAA;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,UAAU,wFAaf,CAAA;AA0BD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,CAAA;AAClI,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/ui/card.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAElC,QAAA,MAAM,YAAY;;;;;8EAqDjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,MAAM,WAAW,SACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,EACzD,YAAY,CAAC,OAAO,YAAY,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,YAAY,EAAE,QAAQ,EAAE,CAAA;AAExB,QAAA,MAAM,IAAI,kFAgCT,CAAA;AAGD,QAAA,MAAM,UAAU,6GAYd,CAAA;AAGF,QAAA,MAAM,SAAS,6GASb,CAAA;AAGF,QAAA,MAAM,eAAe,6GASnB,CAAA;AAGF,QAAA,MAAM,WAAW,6GAcf,CAAA;AAGF,QAAA,MAAM,UAAU,6GASd,CAAA;AAGF;;;;;GAKG;AACH,QAAA,MAAM,WAAW,6GASf,CAAA;AAGF,KAAK,aAAa,GAAG,GAAG,GAAG,KAAK,GAAG,QAAQ,GAAG,GAAG,GAAG,KAAK,CAAA;AAczD,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,aAAa,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,SAAS,uFAId,CAAA;AAGD,KAAK,mBAAmB,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAA;AAWpF,MAAM,WAAW,eAAgB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC3E,4DAA4D;IAC5D,SAAS,CAAC,EAAE,mBAAmB,CAAA;IAC/B;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,UAAU,wFAaf,CAAA;AA0BD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,CAAA;AAClI,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,CAAA"}
package/dist/ui/card.js CHANGED
@@ -9,7 +9,7 @@ import { motion as o } from "framer-motion";
9
9
  var s = n("relative flex text-surface-fg rounded-surface transition-shadow duration-fast-02 ease-productive-standard", {
10
10
  variants: {
11
11
  variant: {
12
- default: "bg-surface-raised border border-transparent shadow-raised",
12
+ default: "bg-surface-raised border border-card shadow-none",
13
13
  elevated: "bg-surface-raised border border-transparent shadow-raised-hover",
14
14
  outline: "bg-transparent border border-surface-border-strong shadow-none",
15
15
  flat: "bg-surface-raised border-none shadow-none"
@@ -1 +1 @@
1
- {"version":3,"file":"card.js","names":[],"sources":["../../src/ui/card.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { motionProps, springs } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// Bundlers (Vite, Next.js, webpack) define process.env.NODE_ENV; guard for raw ESM.\ndeclare const process: { env: { NODE_ENV?: string } } | undefined\n\ntype CardSize = 'sm' | 'md' | 'lg'\n\nconst cardVariants = cva(\n // Gap model, variable-driven: `size` sets --card-spacing / --card-gap once on the\n // container; the container reads them for its vertical edge (py) and inter-slot\n // rhythm (gap), slots read the same variables for their horizontal inset (px), and\n // CardAction/CardBleed read them for corner offsets / negative margins. One variable\n // pair is the whole size system — override it with a single arbitrary property\n // (`[--card-spacing:var(--spacing-ds-07)]`) and every part follows.\n // No per-slot py, no pt-0, no per-element margins — adding/removing a slot can't\n // unbalance the bottom edge (make-kit rule: shadow ring is the edge, gap is the rhythm).\n // `relative` establishes the positioning context for <CardAction> corner slots.\n 'relative flex text-surface-fg rounded-surface transition-shadow duration-fast-02 ease-productive-standard',\n {\n variants: {\n variant: {\n // Elevation-led: the shadow's own ring is the edge (make-kit rule #6 — no\n // border+shadow double-edge). border-transparent keeps the `color` prop able to\n // paint a deliberate colored edge without a grey one by default.\n default: 'bg-surface-raised border border-transparent shadow-raised',\n elevated: 'bg-surface-raised border border-transparent shadow-raised-hover',\n // Border-led: a visible edge, no shadow.\n outline: 'bg-transparent border border-surface-border-strong shadow-none',\n flat: 'bg-surface-raised border-none shadow-none',\n },\n color: {\n default: '',\n accent: 'border-accent-7',\n error: 'border-error-7',\n success: 'border-success-7',\n warning: 'border-warning-7',\n info: 'border-info-7',\n neutral: '',\n },\n // size only assigns the two variables; the orientation axis decides where\n // they're consumed (container in vertical, <CardSection> in horizontal).\n size: {\n sm: '[--card-spacing:var(--spacing-ds-05)] [--card-gap:var(--spacing-ds-03)]',\n md: '[--card-spacing:var(--spacing-ds-05b)] [--card-gap:var(--spacing-ds-04)]',\n lg: '[--card-spacing:var(--spacing-ds-06)] [--card-gap:var(--spacing-ds-05)]',\n },\n // vertical: the container owns py + gap (the default gap model).\n // horizontal: the container is a padding-less row; a <CardSection> child\n // re-establishes py + gap for the text column while a media pane owns an edge.\n orientation: {\n vertical: 'flex-col py-(--card-spacing) gap-(--card-gap)',\n horizontal: 'flex-row items-stretch',\n },\n },\n defaultVariants: { variant: 'default', color: 'default', size: 'md', orientation: 'vertical' },\n },\n)\n\n/**\n * Props for Card — a general-purpose content container with 4 elevation/style variants and\n * an optional interactive hover state.\n *\n * **Variants (elevation-led vs border-led — never both):** `default` (ring-in-shadow, no border) |\n * `elevated` (stronger shadow-raised-hover, no border) | `outline` (visible border, no shadow) |\n * `flat` (filled background, no edge). The shadow tokens carry their own 1px ring, so elevated\n * variants need no explicit border (make-kit rule #6).\n *\n * **Composition:** Use sub-components `<CardHeader>`, `<CardTitle>`, `<CardDescription>`,\n * `<CardContent>`, and `<CardFooter>` for consistent internal spacing. Text content must live\n * inside a slot — raw text as a direct child gets no horizontal inset (direct children span the\n * full card width by design, which is what makes full-width dividers and bands free).\n *\n * **Spacing:** `size` sets `--card-spacing` / `--card-gap` once; container, slots, `CardAction`,\n * and `CardBleed` all read the same pair. Retune a card with a single override:\n * `className=\"[--card-spacing:var(--spacing-ds-07)]\"`.\n *\n * **Interactive:** Pass `interactive` to enable hover shadow lift and pointer cursor —\n * useful for clickable cards in grids.\n *\n * @example\n * // Standard content card with header and body:\n * <Card>\n * <CardHeader>\n * <CardTitle>Project Alpha</CardTitle>\n * <CardDescription>Last updated 2 hours ago</CardDescription>\n * </CardHeader>\n * <CardContent>\n * <p>Sprint 4 is in progress with 12 open tasks.</p>\n * </CardContent>\n * </Card>\n *\n * @example\n * // Full-bleed cover image + edge-to-edge divider:\n * <Card>\n * <CardBleed side=\"top\"><img src={cover} alt=\"\" /></CardBleed>\n * <CardHeader><CardTitle>Muhurat launch site</CardTitle></CardHeader>\n * <Separator />\n * <CardFooter><Badge color=\"success\">On track</Badge></CardFooter>\n * </Card>\n *\n * @example\n * // Horizontal media card — media pane owns the left edge, CardSection restores rhythm:\n * <Card orientation=\"horizontal\">\n * <div className=\"w-32 shrink-0 rounded-l-surface overflow-hidden\">\n * <img src={thumb} alt=\"\" className=\"h-full w-full object-cover\" />\n * </div>\n * <CardSection>\n * <CardHeader><CardTitle>Field notes</CardTitle></CardHeader>\n * <CardFooter>edited yesterday</CardFooter>\n * </CardSection>\n * </Card>\n *\n * @example\n * // Flat card for a sidebar panel section (no shadow):\n * <Card variant=\"flat\">\n * <CardContent>\n * <p className=\"text-surface-fg-muted text-body-sm\">No recent activity</p>\n * </CardContent>\n * </Card>\n */\nexport interface CardProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n VariantProps<typeof cardVariants> {\n interactive?: boolean\n}\n\nexport type { CardSize }\n\nconst Card = React.forwardRef<HTMLDivElement, CardProps>(\n ({ className, variant, color, size, orientation, interactive, children, ...props }, ref) => {\n if (typeof process !== 'undefined' && process?.env.NODE_ENV !== 'production') {\n warnOnUnwrappedTextChildren(children)\n }\n const classes = cn(\n cardVariants({ variant, color, size, orientation }),\n interactive && 'hover:shadow-raised-hover cursor-pointer transition-shadow duration-fast-02 ease-productive-standard',\n className,\n )\n\n if (interactive) {\n return (\n <motion.div\n ref={ref}\n whileHover={{ y: -3 }}\n whileTap={{ scale: 0.98 }}\n transition={springs.snappy}\n className={classes}\n {...motionProps(props)}\n >\n {children}\n </motion.div>\n )\n }\n\n return (\n <div ref={ref} className={classes} {...props}>\n {children}\n </div>\n )\n },\n)\nCard.displayName = 'Card'\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col gap-ds-02b px-(--card-spacing) [&>:first-child]:mt-0 [&>:last-child]:mb-0',\n className,\n )}\n {...props}\n />\n))\nCardHeader.displayName = 'CardHeader'\n\nconst CardTitle = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('font-sans font-semibold leading-ds-none tracking-normal text-surface-fg', className)}\n {...props}\n />\n))\nCardTitle.displayName = 'CardTitle'\n\nconst CardDescription = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('text-body-md text-surface-fg-muted', className)}\n {...props}\n />\n))\nCardDescription.displayName = 'CardDescription'\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n // Reset first/last child margins so a raw <p>/<h*>'s UA margin can't leak\n // past the slot onto the container's gap-model padding (the bottom-heavy bug).\n 'px-(--card-spacing) [&>:first-child]:mt-0 [&>:last-child]:mb-0',\n className,\n )}\n {...props}\n />\n))\nCardContent.displayName = 'CardContent'\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center gap-ds-03 px-(--card-spacing)', className)}\n {...props}\n />\n))\nCardFooter.displayName = 'CardFooter'\n\n/**\n * A vertical stack that re-establishes the card's padding + gap rhythm — the text column\n * of a `<Card orientation=\"horizontal\">`. Reads the same `--card-spacing` / `--card-gap`\n * variables the container sets, so the horizontal card's content column matches a vertical\n * card of the same `size` exactly.\n */\nconst CardSection = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex min-w-0 flex-1 flex-col py-(--card-spacing) gap-(--card-gap)', className)}\n {...props}\n />\n))\nCardSection.displayName = 'CardSection'\n\ntype CardBleedSide = 'x' | 'top' | 'bottom' | 'y' | 'all'\n\nconst bleedSideClasses: Record<CardBleedSide, string> = {\n // Inside a slot: escape the slot's horizontal inset.\n x: '-mx-(--card-spacing)',\n // As a direct child: escape the container's vertical edge (direct children are\n // already full-width — never add `x` bleed to a direct child, it would overflow).\n top: '-mt-(--card-spacing) rounded-t-surface overflow-hidden',\n bottom: '-mb-(--card-spacing) rounded-b-surface overflow-hidden',\n y: '-my-(--card-spacing) rounded-surface overflow-hidden',\n // Inside a slot, escaping every edge (e.g. an image-only card body).\n all: '-my-(--card-spacing) -mx-(--card-spacing) rounded-surface overflow-hidden',\n}\n\nexport interface CardBleedProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Which card padding to negate. `top`/`bottom`/`y` are for direct children of Card\n * (full-bleed media, edge bands) and inherit the card's corner radius; `x`/`all` are\n * for content INSIDE a slot that needs to escape the slot's horizontal inset.\n * Direct children of Card already span its full width — don't use `x`/`all` there.\n * @default 'x'\n */\n side?: CardBleedSide\n}\n\n/**\n * Escape hatch from the card's padding — the shilp-sutra equivalent of Radix Themes'\n * `Inset` / Polaris `Bleed`. Negates the same `--card-spacing` variable the padding\n * reads, so it stays exact across sizes and overrides.\n *\n * @example\n * // Cover image touching the card's top + side edges:\n * <Card>\n * <CardBleed side=\"top\"><img src={cover} alt=\"\" /></CardBleed>\n * <CardHeader><CardTitle>Project</CardTitle></CardHeader>\n * </Card>\n *\n * @example\n * // A tinted band inside CardContent, running edge-to-edge:\n * <CardContent>\n * <CardBleed><div className=\"bg-accent-2 px-(--card-spacing) py-ds-03\">Heads up…</div></CardBleed>\n * </CardContent>\n */\nconst CardBleed = React.forwardRef<HTMLDivElement, CardBleedProps>(\n ({ className, side = 'x', ...props }, ref) => (\n <div ref={ref} className={cn(bleedSideClasses[side], className)} {...props} />\n ),\n)\nCardBleed.displayName = 'CardBleed'\n\ntype CardActionPlacement = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'\n\n// Corner inset reads --card-spacing so an action's edge lines up with the slot\n// content edge at every size.\nconst cornerPositions: Record<CardActionPlacement, string> = {\n 'top-right': 'top-(--card-spacing) right-(--card-spacing)',\n 'top-left': 'top-(--card-spacing) left-(--card-spacing)',\n 'bottom-right': 'bottom-(--card-spacing) right-(--card-spacing)',\n 'bottom-left': 'bottom-(--card-spacing) left-(--card-spacing)',\n}\n\nexport interface CardActionProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Which corner the action sits in. @default 'top-right' */\n placement?: CardActionPlacement\n /**\n * Pull the action a step toward its corner (`-m-ds-02`) so an icon button's GLYPH —\n * not its padding box — aligns to the content-edge line. Use for ghost/icon buttons\n * placed in a corner; leave off for badges/text where the padding box is the edge.\n */\n tuck?: boolean\n}\n\n/**\n * An absolutely-positioned corner slot for a Card — a menu/icon button, a status badge,\n * an overflow action. The Card is `relative`, so this pins to one of its four corners\n * with an inset that matches the card's content padding.\n *\n * @example\n * <Card>\n * <CardAction><IconButton aria-label=\"More\" icon={<IconDots />} variant=\"ghost\" tuck /></CardAction>\n * <CardHeader><CardTitle>Project Alpha</CardTitle></CardHeader>\n * </Card>\n *\n * @example\n * // Accent status badge in the top-right:\n * <Card>\n * <CardAction><Badge color=\"accent\" size=\"xs\">LIVE</Badge></CardAction>\n * ...\n * </Card>\n */\nconst CardAction = React.forwardRef<HTMLDivElement, CardActionProps>(\n ({ className, placement = 'top-right', tuck, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'absolute z-[1] flex items-center gap-ds-02',\n cornerPositions[placement],\n tuck && '-m-ds-02',\n className,\n )}\n {...props}\n />\n ),\n)\nCardAction.displayName = 'CardAction'\n\n// Text-shaped tags that almost certainly expected a slot's horizontal inset.\n// Structural tags (div, section, img, table, hr…) are legitimate full-width direct\n// children, so they stay silent.\nconst TEXTUAL_TAGS = new Set(['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'label', 'small', 'strong', 'em'])\nlet warnedUnwrappedText = false\n\nfunction warnOnUnwrappedTextChildren(children: React.ReactNode): void {\n if (warnedUnwrappedText) return\n React.Children.forEach(children, (child) => {\n const isBareText = typeof child === 'string' && child.trim() !== ''\n const isTextualTag =\n React.isValidElement(child) &&\n typeof child.type === 'string' &&\n TEXTUAL_TAGS.has(child.type)\n if (isBareText || isTextualTag) {\n warnedUnwrappedText = true\n console.warn(\n '[shilp-sutra] <Card> received text content as a direct child. Direct children span the full card width and get no horizontal inset — wrap text in <CardContent> (or <CardHeader>/<CardFooter>). See the Card docs, \"Composition\".',\n )\n }\n })\n}\n\nexport { Card, CardAction, CardBleed, CardContent, CardDescription, CardFooter, CardHeader, CardSection, CardTitle, cardVariants }\nexport type { CardActionPlacement, CardBleedSide }\n"],"mappings":"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"}
1
+ {"version":3,"file":"card.js","names":[],"sources":["../../src/ui/card.tsx"],"sourcesContent":["'use client'\n\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { motionProps, springs } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// Bundlers (Vite, Next.js, webpack) define process.env.NODE_ENV; guard for raw ESM.\ndeclare const process: { env: { NODE_ENV?: string } } | undefined\n\ntype CardSize = 'sm' | 'md' | 'lg'\n\nconst cardVariants = cva(\n // Gap model, variable-driven: `size` sets --card-spacing / --card-gap once on the\n // container; the container reads them for its vertical edge (py) and inter-slot\n // rhythm (gap), slots read the same variables for their horizontal inset (px), and\n // CardAction/CardBleed read them for corner offsets / negative margins. One variable\n // pair is the whole size system — override it with a single arbitrary property\n // (`[--card-spacing:var(--spacing-ds-07)]`) and every part follows.\n // No per-slot py, no pt-0, no per-element margins — adding/removing a slot can't\n // unbalance the bottom edge (make-kit rule: shadow ring is the edge, gap is the rhythm).\n // `relative` establishes the positioning context for <CardAction> corner slots.\n 'relative flex text-surface-fg rounded-surface transition-shadow duration-fast-02 ease-productive-standard',\n {\n variants: {\n variant: {\n // Tonal (default): depth from a surface-tone shift + a whisper hairline in the\n // surface's own colour (`border-card`), NO shadow — the DS-wide anti-slop edge\n // (Setu tonal-elevation). The `color` prop overrides the hairline colour to paint\n // a deliberate accent/status edge.\n default: 'bg-surface-raised border border-card shadow-none',\n // Elevation-led: the shadow's own ring is the edge (make-kit rule #6 — no\n // border+shadow double-edge). Reach for it when a card should visibly pop\n // (a dragged tile, a spotlight panel). border-transparent keeps `color` paintable.\n elevated: 'bg-surface-raised border border-transparent shadow-raised-hover',\n // Border-led, strong: a firmly visible edge, no shadow.\n outline: 'bg-transparent border border-surface-border-strong shadow-none',\n flat: 'bg-surface-raised border-none shadow-none',\n },\n color: {\n default: '',\n accent: 'border-accent-7',\n error: 'border-error-7',\n success: 'border-success-7',\n warning: 'border-warning-7',\n info: 'border-info-7',\n neutral: '',\n },\n // size only assigns the two variables; the orientation axis decides where\n // they're consumed (container in vertical, <CardSection> in horizontal).\n size: {\n sm: '[--card-spacing:var(--spacing-ds-05)] [--card-gap:var(--spacing-ds-03)]',\n md: '[--card-spacing:var(--spacing-ds-05b)] [--card-gap:var(--spacing-ds-04)]',\n lg: '[--card-spacing:var(--spacing-ds-06)] [--card-gap:var(--spacing-ds-05)]',\n },\n // vertical: the container owns py + gap (the default gap model).\n // horizontal: the container is a padding-less row; a <CardSection> child\n // re-establishes py + gap for the text column while a media pane owns an edge.\n orientation: {\n vertical: 'flex-col py-(--card-spacing) gap-(--card-gap)',\n horizontal: 'flex-row items-stretch',\n },\n },\n defaultVariants: { variant: 'default', color: 'default', size: 'md', orientation: 'vertical' },\n },\n)\n\n/**\n * Props for Card — a general-purpose content container with 4 elevation/style variants and\n * an optional interactive hover state.\n *\n * **Variants (tonal, elevation-led, or border-led — never border+shadow together):** `default`\n * (tonal — surface shift + `border-card` hairline, no shadow) | `elevated` (shadow-raised-hover,\n * no border — use when a card should visibly pop) | `outline` (strong visible border, no shadow) |\n * `flat` (filled background, no edge). The shadow tokens carry their own 1px ring, so `elevated`\n * needs no explicit border (make-kit rule #6).\n *\n * **Composition:** Use sub-components `<CardHeader>`, `<CardTitle>`, `<CardDescription>`,\n * `<CardContent>`, and `<CardFooter>` for consistent internal spacing. Text content must live\n * inside a slot — raw text as a direct child gets no horizontal inset (direct children span the\n * full card width by design, which is what makes full-width dividers and bands free).\n *\n * **Spacing:** `size` sets `--card-spacing` / `--card-gap` once; container, slots, `CardAction`,\n * and `CardBleed` all read the same pair. Retune a card with a single override:\n * `className=\"[--card-spacing:var(--spacing-ds-07)]\"`.\n *\n * **Interactive:** Pass `interactive` to enable hover shadow lift and pointer cursor —\n * useful for clickable cards in grids.\n *\n * @example\n * // Standard content card with header and body:\n * <Card>\n * <CardHeader>\n * <CardTitle>Project Alpha</CardTitle>\n * <CardDescription>Last updated 2 hours ago</CardDescription>\n * </CardHeader>\n * <CardContent>\n * <p>Sprint 4 is in progress with 12 open tasks.</p>\n * </CardContent>\n * </Card>\n *\n * @example\n * // Full-bleed cover image + edge-to-edge divider:\n * <Card>\n * <CardBleed side=\"top\"><img src={cover} alt=\"\" /></CardBleed>\n * <CardHeader><CardTitle>Muhurat launch site</CardTitle></CardHeader>\n * <Separator />\n * <CardFooter><Badge color=\"success\">On track</Badge></CardFooter>\n * </Card>\n *\n * @example\n * // Horizontal media card — media pane owns the left edge, CardSection restores rhythm:\n * <Card orientation=\"horizontal\">\n * <div className=\"w-32 shrink-0 rounded-l-surface overflow-hidden\">\n * <img src={thumb} alt=\"\" className=\"h-full w-full object-cover\" />\n * </div>\n * <CardSection>\n * <CardHeader><CardTitle>Field notes</CardTitle></CardHeader>\n * <CardFooter>edited yesterday</CardFooter>\n * </CardSection>\n * </Card>\n *\n * @example\n * // Flat card for a sidebar panel section (no shadow):\n * <Card variant=\"flat\">\n * <CardContent>\n * <p className=\"text-surface-fg-muted text-body-sm\">No recent activity</p>\n * </CardContent>\n * </Card>\n */\nexport interface CardProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,\n VariantProps<typeof cardVariants> {\n interactive?: boolean\n}\n\nexport type { CardSize }\n\nconst Card = React.forwardRef<HTMLDivElement, CardProps>(\n ({ className, variant, color, size, orientation, interactive, children, ...props }, ref) => {\n if (typeof process !== 'undefined' && process?.env.NODE_ENV !== 'production') {\n warnOnUnwrappedTextChildren(children)\n }\n const classes = cn(\n cardVariants({ variant, color, size, orientation }),\n interactive && 'hover:shadow-raised-hover cursor-pointer transition-shadow duration-fast-02 ease-productive-standard',\n className,\n )\n\n if (interactive) {\n return (\n <motion.div\n ref={ref}\n whileHover={{ y: -3 }}\n whileTap={{ scale: 0.98 }}\n transition={springs.snappy}\n className={classes}\n {...motionProps(props)}\n >\n {children}\n </motion.div>\n )\n }\n\n return (\n <div ref={ref} className={classes} {...props}>\n {children}\n </div>\n )\n },\n)\nCard.displayName = 'Card'\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col gap-ds-02b px-(--card-spacing) [&>:first-child]:mt-0 [&>:last-child]:mb-0',\n className,\n )}\n {...props}\n />\n))\nCardHeader.displayName = 'CardHeader'\n\nconst CardTitle = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('font-sans font-semibold leading-ds-none tracking-normal text-surface-fg', className)}\n {...props}\n />\n))\nCardTitle.displayName = 'CardTitle'\n\nconst CardDescription = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('text-body-md text-surface-fg-muted', className)}\n {...props}\n />\n))\nCardDescription.displayName = 'CardDescription'\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n // Reset first/last child margins so a raw <p>/<h*>'s UA margin can't leak\n // past the slot onto the container's gap-model padding (the bottom-heavy bug).\n 'px-(--card-spacing) [&>:first-child]:mt-0 [&>:last-child]:mb-0',\n className,\n )}\n {...props}\n />\n))\nCardContent.displayName = 'CardContent'\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex items-center gap-ds-03 px-(--card-spacing)', className)}\n {...props}\n />\n))\nCardFooter.displayName = 'CardFooter'\n\n/**\n * A vertical stack that re-establishes the card's padding + gap rhythm — the text column\n * of a `<Card orientation=\"horizontal\">`. Reads the same `--card-spacing` / `--card-gap`\n * variables the container sets, so the horizontal card's content column matches a vertical\n * card of the same `size` exactly.\n */\nconst CardSection = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex min-w-0 flex-1 flex-col py-(--card-spacing) gap-(--card-gap)', className)}\n {...props}\n />\n))\nCardSection.displayName = 'CardSection'\n\ntype CardBleedSide = 'x' | 'top' | 'bottom' | 'y' | 'all'\n\nconst bleedSideClasses: Record<CardBleedSide, string> = {\n // Inside a slot: escape the slot's horizontal inset.\n x: '-mx-(--card-spacing)',\n // As a direct child: escape the container's vertical edge (direct children are\n // already full-width — never add `x` bleed to a direct child, it would overflow).\n top: '-mt-(--card-spacing) rounded-t-surface overflow-hidden',\n bottom: '-mb-(--card-spacing) rounded-b-surface overflow-hidden',\n y: '-my-(--card-spacing) rounded-surface overflow-hidden',\n // Inside a slot, escaping every edge (e.g. an image-only card body).\n all: '-my-(--card-spacing) -mx-(--card-spacing) rounded-surface overflow-hidden',\n}\n\nexport interface CardBleedProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Which card padding to negate. `top`/`bottom`/`y` are for direct children of Card\n * (full-bleed media, edge bands) and inherit the card's corner radius; `x`/`all` are\n * for content INSIDE a slot that needs to escape the slot's horizontal inset.\n * Direct children of Card already span its full width — don't use `x`/`all` there.\n * @default 'x'\n */\n side?: CardBleedSide\n}\n\n/**\n * Escape hatch from the card's padding — the shilp-sutra equivalent of Radix Themes'\n * `Inset` / Polaris `Bleed`. Negates the same `--card-spacing` variable the padding\n * reads, so it stays exact across sizes and overrides.\n *\n * @example\n * // Cover image touching the card's top + side edges:\n * <Card>\n * <CardBleed side=\"top\"><img src={cover} alt=\"\" /></CardBleed>\n * <CardHeader><CardTitle>Project</CardTitle></CardHeader>\n * </Card>\n *\n * @example\n * // A tinted band inside CardContent, running edge-to-edge:\n * <CardContent>\n * <CardBleed><div className=\"bg-accent-2 px-(--card-spacing) py-ds-03\">Heads up…</div></CardBleed>\n * </CardContent>\n */\nconst CardBleed = React.forwardRef<HTMLDivElement, CardBleedProps>(\n ({ className, side = 'x', ...props }, ref) => (\n <div ref={ref} className={cn(bleedSideClasses[side], className)} {...props} />\n ),\n)\nCardBleed.displayName = 'CardBleed'\n\ntype CardActionPlacement = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'\n\n// Corner inset reads --card-spacing so an action's edge lines up with the slot\n// content edge at every size.\nconst cornerPositions: Record<CardActionPlacement, string> = {\n 'top-right': 'top-(--card-spacing) right-(--card-spacing)',\n 'top-left': 'top-(--card-spacing) left-(--card-spacing)',\n 'bottom-right': 'bottom-(--card-spacing) right-(--card-spacing)',\n 'bottom-left': 'bottom-(--card-spacing) left-(--card-spacing)',\n}\n\nexport interface CardActionProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Which corner the action sits in. @default 'top-right' */\n placement?: CardActionPlacement\n /**\n * Pull the action a step toward its corner (`-m-ds-02`) so an icon button's GLYPH —\n * not its padding box — aligns to the content-edge line. Use for ghost/icon buttons\n * placed in a corner; leave off for badges/text where the padding box is the edge.\n */\n tuck?: boolean\n}\n\n/**\n * An absolutely-positioned corner slot for a Card — a menu/icon button, a status badge,\n * an overflow action. The Card is `relative`, so this pins to one of its four corners\n * with an inset that matches the card's content padding.\n *\n * @example\n * <Card>\n * <CardAction><IconButton aria-label=\"More\" icon={<IconDots />} variant=\"ghost\" tuck /></CardAction>\n * <CardHeader><CardTitle>Project Alpha</CardTitle></CardHeader>\n * </Card>\n *\n * @example\n * // Accent status badge in the top-right:\n * <Card>\n * <CardAction><Badge color=\"accent\" size=\"xs\">LIVE</Badge></CardAction>\n * ...\n * </Card>\n */\nconst CardAction = React.forwardRef<HTMLDivElement, CardActionProps>(\n ({ className, placement = 'top-right', tuck, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'absolute z-[1] flex items-center gap-ds-02',\n cornerPositions[placement],\n tuck && '-m-ds-02',\n className,\n )}\n {...props}\n />\n ),\n)\nCardAction.displayName = 'CardAction'\n\n// Text-shaped tags that almost certainly expected a slot's horizontal inset.\n// Structural tags (div, section, img, table, hr…) are legitimate full-width direct\n// children, so they stay silent.\nconst TEXTUAL_TAGS = new Set(['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'label', 'small', 'strong', 'em'])\nlet warnedUnwrappedText = false\n\nfunction warnOnUnwrappedTextChildren(children: React.ReactNode): void {\n if (warnedUnwrappedText) return\n React.Children.forEach(children, (child) => {\n const isBareText = typeof child === 'string' && child.trim() !== ''\n const isTextualTag =\n React.isValidElement(child) &&\n typeof child.type === 'string' &&\n TEXTUAL_TAGS.has(child.type)\n if (isBareText || isTextualTag) {\n warnedUnwrappedText = true\n console.warn(\n '[shilp-sutra] <Card> received text content as a direct child. Direct children span the full card width and get no horizontal inset — wrap text in <CardContent> (or <CardHeader>/<CardFooter>). See the Card docs, \"Composition\".',\n )\n }\n })\n}\n\nexport { Card, CardAction, CardBleed, CardContent, CardDescription, CardFooter, CardHeader, CardSection, CardTitle, cardVariants }\nexport type { CardActionPlacement, CardBleedSide }\n"],"mappings":"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"}
@@ -7,8 +7,8 @@ import * as React from 'react';
7
7
  * Props for StatCard — a dashboard metric tile displaying a label, a large numeric value,
8
8
  * an optional trend delta (with directional arrow icon), and an optional header icon.
9
9
  *
10
- * **Surface:** delegated to `Card` via `variant` — `default` (ring-in-shadow, no border) |
11
- * `elevated` | `outline` (border, no shadow) | `flat`. StatCard composes `<Card>`, so the surface,
10
+ * **Surface:** delegated to `Card` via `variant` — `default` (tonal hairline, no shadow) |
11
+ * `elevated` (shadow) | `outline` (strong border, no shadow) | `flat`. StatCard composes `<Card>`, so the surface,
12
12
  * padding (gap model), and elevation all live in one place.
13
13
  *
14
14
  * **Accent:** `accentStyle="none"` (default) is neutral; `"icon"` wraps `icon` in an accent chip
@@ -75,7 +75,7 @@ export interface StatCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>
75
75
  secondaryLabel?: string;
76
76
  /** Progress toward a target (0-100). Renders a thin progress bar below the value. */
77
77
  progress?: number;
78
- /** Surface variant, delegated to Card: `default` (ring-in-shadow) | `elevated` | `outline` (border, no shadow) | `flat`. @default 'default' */
78
+ /** Surface variant, delegated to Card: `default` (tonal hairline, no shadow) | `elevated` (shadow) | `outline` (strong border, no shadow) | `flat`. @default 'default' */
79
79
  variant?: 'default' | 'elevated' | 'outline' | 'flat';
80
80
  /**
81
81
  * Tile density, delegated to Card's size axis. `sm` tightens padding to 16px and steps
@@ -1 +1 @@
1
- {"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/ui/stat-card.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAiC,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAGrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAOjD,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAY,MAAM,cAAc,CAAA;AAE1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACxF,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;KACrC,CAAA;IACD,kJAAkJ;IAClJ,cAAc,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IACnC,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+IAA+I;IAC/I,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAA;IACrD;;;;OAIG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,yJAAyJ;IACzJ,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IACtC,6HAA6H;IAC7H,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,uGAAuG;IACvG,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IAC/B,iFAAiF;IACjF,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAgGD,QAAA,MAAM,QAAQ,sFAoQb,CAAA;AAID,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/ui/stat-card.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAiC,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAGrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAOjD,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAY,MAAM,cAAc,CAAA;AAE1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACxF,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAA;KACrC,CAAA;IACD,kJAAkJ;IAClJ,cAAc,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IACnC,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0KAA0K;IAC1K,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAA;IACrD;;;;OAIG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,yJAAyJ;IACzJ,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IACtC,6HAA6H;IAC7H,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,uGAAuG;IACvG,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IAC/B,iFAAiF;IACjF,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AAgGD,QAAA,MAAM,QAAQ,sFAoQb,CAAA;AAID,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"stat-card.js","names":[],"sources":["../../src/ui/stat-card.tsx"],"sourcesContent":["'use client'\n\nimport { IconMinus, IconTrendingDown, IconTrendingUp } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Card, CardContent, CardFooter, type CardSize } from './card'\nimport { Icon } from './icon'\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { useLink } from './lib/link-context'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\nimport { Progress, type ProgressColor } from './progress'\nimport { Skeleton } from './skeleton'\nimport { type FlashPreset, type FlashSpec, type FlashSpeed,StatFlash } from './stat-flash'\n\n/**\n * Props for StatCard — a dashboard metric tile displaying a label, a large numeric value,\n * an optional trend delta (with directional arrow icon), and an optional header icon.\n *\n * **Surface:** delegated to `Card` via `variant` — `default` (ring-in-shadow, no border) |\n * `elevated` | `outline` (border, no shadow) | `flat`. StatCard composes `<Card>`, so the surface,\n * padding (gap model), and elevation all live in one place.\n *\n * **Accent:** `accentStyle=\"none\"` (default) is neutral; `\"icon\"` wraps `icon` in an accent chip\n * (`iconFill=\"soft\" | \"solid\"`); `\"tint\"` applies a subtle accent surface wash + accent value.\n * (Replaces the removed `accent` left-rail prop — see CHANGELOG migration.)\n *\n * **Delta direction:** `'up'` renders a green trending-up arrow, `'down'` renders a red\n * trending-down arrow, `'neutral'` renders a grey dash. The `delta.value` is a formatted\n * string (e.g. `\"+8%\"` or `\"−120\"`).\n *\n * **Loading state:** Pass `loading={true}` to render a pulse-skeleton placeholder instead of data.\n *\n * @example\n * // Revenue metric with a positive trend:\n * <StatCard\n * label=\"Monthly Revenue\"\n * value=\"$48,200\"\n * delta={{ value: \"+12%\", direction: \"up\" }}\n * icon={<IconCurrencyDollar />}\n * />\n *\n * @example\n * // Open ticket count with a downward trend (good for support queues):\n * <StatCard\n * label=\"Open Tickets\"\n * value={142}\n * delta={{ value: \"−18\", direction: \"down\" }}\n * />\n *\n * @example\n * // Loading skeleton while data is fetching:\n * <StatCard label=\"Users\" value={0} loading={isFetching} />\n *\n * @example\n * // Simple metric with no trend (stable, neutral):\n * <StatCard\n * label=\"Storage Used\"\n * value=\"4.2 GB\"\n * delta={{ value: \"No change\", direction: \"neutral\" }}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface StatCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {\n /** Heading text for the metric. `title` is an alias for `label`. */\n label?: string\n /** Alias for `label` — use whichever feels natural. */\n title?: string\n value: string | number\n /** Prefix before value, e.g. \"$\" */\n prefix?: string\n /** Suffix after value, e.g. \"users\" */\n suffix?: string\n delta?: {\n value: string\n direction: 'up' | 'down' | 'neutral'\n }\n /** Where the delta sits relative to the value: `block` (below — default) or `inline` (on the value's baseline, to its right). @default 'block' */\n deltaPlacement?: 'block' | 'inline'\n icon?: IconInput\n loading?: boolean\n /** Comparison period label shown after delta, e.g. \"vs last month\" */\n comparisonLabel?: string\n /** Secondary metric below main value, e.g. \"of $50,000 target\" */\n secondaryLabel?: string\n /** Progress toward a target (0-100). Renders a thin progress bar below the value. */\n progress?: number\n /** Surface variant, delegated to Card: `default` (ring-in-shadow) | `elevated` | `outline` (border, no shadow) | `flat`. @default 'default' */\n variant?: 'default' | 'elevated' | 'outline' | 'flat'\n /**\n * Tile density, delegated to Card's size axis. `sm` tightens padding to 16px and steps\n * the value down to `text-heading-md` — use it for dense KPI rows and narrow stat grids\n * (e.g. stat-row's 140px tiles). @default 'md'\n */\n size?: CardSize\n /** How the brand accent reads: `none` (neutral — default), `icon` (accent chip around `icon`), or `tint` (subtle accent surface wash + accent value). */\n accentStyle?: 'none' | 'icon' | 'tint'\n /** Animate value/label in on mount (subtle settle; content stays visible either way, never opacity-gated). @default false */\n reveal?: boolean\n /** When `accentStyle=\"icon\"`: `soft` (tinted chip) or `solid` (filled accent chip). @default 'soft' */\n iconFill?: 'soft' | 'solid'\n /**\n * Opt-in entrance flash: the icon chip mounts showing a transient state (e.g. a green up-arrow)\n * then settles to `icon`. A preset (`'up' | 'down' | 'goal' | 'record' | 'alert' | 'live'`) or an\n * explicit `{ tone, icon }`. Requires `icon`; implies the icon chip. Off by default.\n */\n flash?: FlashPreset | FlashSpec\n /** Speed of the entrance flash: `fast` | `normal` | `slow`. @default 'normal' */\n flashSpeed?: FlashSpeed\n /** Sparkline data points. Renders a mini SVG line chart in the card. */\n sparkline?: number[]\n /** Make the card clickable with hover state */\n onClick?: () => void\n /** Href — makes the card a link (uses the LinkContext Link component) */\n href?: string\n /** Footer content rendered below the card body, e.g. \"View details →\" */\n footer?: React.ReactNode\n}\n\nfunction buildSparklinePath(raw: number[], width: number, height: number): string {\n const data = raw.filter((v) => Number.isFinite(v))\n if (data.length < 2) return ''\n const min = Math.min(...data)\n const max = Math.max(...data)\n const range = max - min || 1\n const stepX = width / (data.length - 1)\n return data\n .map((v, i) => {\n const x = i * stepX\n const y = height - ((v - min) / range) * height\n return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`\n })\n .join(' ')\n}\n\nfunction Sparkline({\n data,\n colorClass,\n}: {\n data: number[]\n colorClass: string\n}) {\n const id = React.useId()\n const pathRef = React.useRef<SVGPathElement>(null)\n const [pathLength, setPathLength] = React.useState(0)\n const svgWidth = 80\n const svgHeight = 32\n const path = buildSparklinePath(data, svgWidth, svgHeight)\n\n React.useEffect(() => {\n if (pathRef.current) {\n setPathLength(pathRef.current.getTotalLength())\n }\n }, [path])\n\n if (!path) return null\n\n // Build area fill path (close to bottom-right then bottom-left)\n const areaPath = `${path} L${svgWidth.toFixed(1)},${svgHeight.toFixed(1)} L0,${svgHeight.toFixed(1)} Z`\n\n return (\n <div className={cn('w-20 h-8', colorClass)} aria-hidden=\"true\">\n <svg\n viewBox={`0 0 ${svgWidth} ${svgHeight}`}\n className=\"w-full h-full overflow-visible\"\n preserveAspectRatio=\"none\"\n >\n <defs>\n <linearGradient id={`sparkline-fill-${id}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity=\"0.1\" />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n <path d={areaPath} fill={`url(#sparkline-fill-${id})`} />\n <path\n ref={pathRef}\n d={path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n opacity: pathLength === 0 ? 0 : 1,\n strokeDasharray: pathLength,\n strokeDashoffset: pathLength,\n animation: pathLength > 0 ? `sparkline-draw-${id.replace(/:/g, '')} 1s ease-out forwards` : 'none',\n }}\n />\n </svg>\n {pathLength > 0 && (\n <style>{`@keyframes sparkline-draw-${id.replace(/:/g, '')} { to { stroke-dashoffset: 0; } }`}</style>\n )}\n </div>\n )\n}\n\nfunction ProgressBar({ progress, label }: { progress: number; label: string }) {\n const clamped = Math.max(0, Math.min(100, progress))\n // StatCard's own thresholds (90 = success, 70 = warning) — deliberately distinct\n // from Progress's autoColor (85/60), so we pass an explicit color.\n const barColor: ProgressColor =\n clamped >= 90 ? 'success' : clamped >= 70 ? 'warning' : 'accent'\n\n return (\n <Progress.Root value={clamped} size=\"sm\">\n <Progress.Track aria-label={`${label} progress`}>\n <Progress.Indicator color={barColor} />\n </Progress.Track>\n </Progress.Root>\n )\n}\n\nconst StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(\n (\n {\n className,\n label,\n title,\n value,\n prefix,\n suffix,\n delta,\n deltaPlacement = 'block',\n icon,\n loading,\n comparisonLabel,\n secondaryLabel,\n progress,\n variant = 'default',\n size = 'md',\n accentStyle = 'none',\n reveal = false,\n iconFill = 'soft',\n flash,\n flashSpeed,\n sparkline,\n onClick,\n href,\n footer,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const resolvedLabel = title ?? label ?? ''\n const isClickable = !!(onClick || href)\n const computedAriaLabel =\n isClickable && !props['aria-label'] ? `View ${resolvedLabel}` : props['aria-label']\n\n if (loading) {\n return (\n <Card ref={ref} variant={variant} size={size} className={className} aria-busy=\"true\" {...props}>\n <CardContent className=\"flex flex-col gap-ds-03\">\n <Skeleton className=\"h-ds-04 w-24 rounded-control-inner\" />\n <Skeleton className=\"h-ds-sm w-32\" />\n <Skeleton className=\"h-3 w-16 rounded-control-inner\" />\n </CardContent>\n </Card>\n )\n }\n\n const DeltaIcon =\n delta?.direction === 'up'\n ? IconTrendingUp\n : delta?.direction === 'down'\n ? IconTrendingDown\n : IconMinus\n\n const deltaColour =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-surface-fg-muted'\n\n const sparklineColor =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-accent-11'\n\n const deltaNode = delta ? (\n <motion.div\n className={cn(\n // Rhythm comes from CardContent's flex gap — no placement margins.\n 'flex items-center gap-ds-02 text-body-sm font-medium',\n deltaColour,\n )}\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ ...springs.smooth, delay: 0.2 }}\n >\n <motion.span\n className=\"inline-flex\"\n initial={{ opacity: 0.5, scale: 1.4 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.smooth}\n >\n <Icon icon={DeltaIcon} size=\"sm\" />\n </motion.span>\n <span>{delta.value}</span>\n {comparisonLabel && (\n <span className=\"text-surface-fg-subtle font-normal\">{comparisonLabel}</span>\n )}\n </motion.div>\n ) : null\n\n // reveal=false → static (content-safe default). reveal=true → subtle settle; opacity NEVER gated.\n const revealProps = reveal\n ? { initial: { y: 8 }, animate: { y: 0 }, transition: springs.smooth }\n : { initial: false as const }\n\n const cardContent = (\n <>\n <div className=\"flex items-center justify-between\">\n <motion.p\n className=\"text-body-md font-medium text-surface-fg-muted\"\n {...revealProps}\n >\n {resolvedLabel}\n </motion.p>\n <div className=\"flex items-center gap-ds-03\">\n {sparkline && sparkline.length >= 2 && (\n <Sparkline data={sparkline} colorClass={sparklineColor} />\n )}\n {icon && flash ? (\n <StatFlash icon={icon} flash={flash} fill={iconFill} speed={flashSpeed} />\n ) : icon ? (\n accentStyle === 'icon' ? (\n <motion.span\n className={cn(\n 'inline-flex items-center justify-center rounded-control p-ds-02',\n iconFill === 'solid'\n ? 'bg-accent-9 text-accent-fg'\n : 'bg-accent-3 text-accent-11',\n )}\n {...revealProps}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"md\">{normalizeIcon(icon, 'md')}</IconProvider>\n </motion.span>\n ) : (\n <motion.span\n className=\"text-surface-fg-muted\"\n {...revealProps}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"lg\">{normalizeIcon(icon, 'lg')}</IconProvider>\n </motion.span>\n )\n ) : null}\n </div>\n </div>\n <div className=\"flex flex-col gap-ds-01\">\n <div className={cn(deltaPlacement === 'inline' && delta && 'flex items-baseline gap-ds-03')}>\n <div className=\"overflow-hidden\">\n <motion.p\n className={cn(\n 'inline-block font-semibold',\n size === 'sm' ? 'text-heading-md' : 'text-heading-lg',\n accentStyle === 'tint' ? 'text-accent-11' : 'text-surface-fg',\n )}\n {...revealProps}\n >\n {prefix && (\n <span className={cn('text-surface-fg-muted', size === 'sm' ? 'text-body-md' : 'text-heading-xs')}>{prefix}</span>\n )}\n <span className=\"tabular-nums\">{value}</span>\n {suffix && (\n <span className={cn('text-surface-fg-muted', size === 'sm' ? 'text-body-md' : 'text-heading-xs')}>{suffix}</span>\n )}\n </motion.p>\n </div>\n {deltaPlacement === 'inline' && deltaNode}\n </div>\n {secondaryLabel && (\n <motion.p\n className=\"text-body-sm text-surface-fg-subtle\"\n {...revealProps}\n >\n {secondaryLabel}\n </motion.p>\n )}\n </div>\n {progress != null && (\n <motion.div\n {...revealProps}\n >\n <ProgressBar progress={progress} label={resolvedLabel} />\n </motion.div>\n )}\n {deltaPlacement === 'block' && deltaNode}\n </>\n )\n\n // accentStyle=\"tint\" → a subtle accent gradient wash over Card's surface.\n const tintClass =\n accentStyle === 'tint' ? 'bg-linear-to-t from-accent-2 to-surface-raised' : undefined\n // Footer sits outside CardContent behind a full-width divider (both are direct\n // children of Card, so the rule spans edge-to-edge — no inset border-t).\n const body = (\n <>\n <CardContent className=\"flex flex-col gap-ds-03\">{cardContent}</CardContent>\n {footer && (\n <>\n <div aria-hidden=\"true\" className=\"h-px w-full bg-surface-border-subtle\" />\n <CardFooter className=\"text-body-sm\">\n <motion.div\n className=\"w-full\"\n initial={false}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.25 }}\n >\n {footer}\n </motion.div>\n </CardFooter>\n </>\n )}\n </>\n )\n\n // href → wrap Card in the framework Link (Card stays the shell; hover-lift inside).\n if (href) {\n return (\n <Link\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n onClick={onClick}\n className=\"block no-underline\"\n aria-label={computedAriaLabel}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n <Card variant={variant} size={size} interactive className={tintClass}>\n {body}\n </Card>\n </Link>\n )\n }\n\n // onClick → interactive Card with button semantics + keyboard activation.\n if (onClick) {\n return (\n <Card\n ref={ref}\n variant={variant}\n size={size}\n interactive\n className={cn(tintClass, className)}\n role=\"button\"\n tabIndex={0}\n aria-label={computedAriaLabel}\n onClick={onClick}\n onKeyDown={(e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onClick()\n }\n }}\n {...props}\n >\n {body}\n </Card>\n )\n }\n\n return (\n <Card ref={ref} variant={variant} size={size} className={cn(tintClass, className)} {...props}>\n {body}\n </Card>\n )\n },\n)\n\nStatCard.displayName = 'StatCard'\n\nexport { StatCard }\n"],"mappings":"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"}
1
+ {"version":3,"file":"stat-card.js","names":[],"sources":["../../src/ui/stat-card.tsx"],"sourcesContent":["'use client'\n\nimport { IconMinus, IconTrendingDown, IconTrendingUp } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Card, CardContent, CardFooter, type CardSize } from './card'\nimport { Icon } from './icon'\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { useLink } from './lib/link-context'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\nimport { Progress, type ProgressColor } from './progress'\nimport { Skeleton } from './skeleton'\nimport { type FlashPreset, type FlashSpec, type FlashSpeed,StatFlash } from './stat-flash'\n\n/**\n * Props for StatCard — a dashboard metric tile displaying a label, a large numeric value,\n * an optional trend delta (with directional arrow icon), and an optional header icon.\n *\n * **Surface:** delegated to `Card` via `variant` — `default` (tonal hairline, no shadow) |\n * `elevated` (shadow) | `outline` (strong border, no shadow) | `flat`. StatCard composes `<Card>`, so the surface,\n * padding (gap model), and elevation all live in one place.\n *\n * **Accent:** `accentStyle=\"none\"` (default) is neutral; `\"icon\"` wraps `icon` in an accent chip\n * (`iconFill=\"soft\" | \"solid\"`); `\"tint\"` applies a subtle accent surface wash + accent value.\n * (Replaces the removed `accent` left-rail prop — see CHANGELOG migration.)\n *\n * **Delta direction:** `'up'` renders a green trending-up arrow, `'down'` renders a red\n * trending-down arrow, `'neutral'` renders a grey dash. The `delta.value` is a formatted\n * string (e.g. `\"+8%\"` or `\"−120\"`).\n *\n * **Loading state:** Pass `loading={true}` to render a pulse-skeleton placeholder instead of data.\n *\n * @example\n * // Revenue metric with a positive trend:\n * <StatCard\n * label=\"Monthly Revenue\"\n * value=\"$48,200\"\n * delta={{ value: \"+12%\", direction: \"up\" }}\n * icon={<IconCurrencyDollar />}\n * />\n *\n * @example\n * // Open ticket count with a downward trend (good for support queues):\n * <StatCard\n * label=\"Open Tickets\"\n * value={142}\n * delta={{ value: \"−18\", direction: \"down\" }}\n * />\n *\n * @example\n * // Loading skeleton while data is fetching:\n * <StatCard label=\"Users\" value={0} loading={isFetching} />\n *\n * @example\n * // Simple metric with no trend (stable, neutral):\n * <StatCard\n * label=\"Storage Used\"\n * value=\"4.2 GB\"\n * delta={{ value: \"No change\", direction: \"neutral\" }}\n * />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface StatCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {\n /** Heading text for the metric. `title` is an alias for `label`. */\n label?: string\n /** Alias for `label` — use whichever feels natural. */\n title?: string\n value: string | number\n /** Prefix before value, e.g. \"$\" */\n prefix?: string\n /** Suffix after value, e.g. \"users\" */\n suffix?: string\n delta?: {\n value: string\n direction: 'up' | 'down' | 'neutral'\n }\n /** Where the delta sits relative to the value: `block` (below — default) or `inline` (on the value's baseline, to its right). @default 'block' */\n deltaPlacement?: 'block' | 'inline'\n icon?: IconInput\n loading?: boolean\n /** Comparison period label shown after delta, e.g. \"vs last month\" */\n comparisonLabel?: string\n /** Secondary metric below main value, e.g. \"of $50,000 target\" */\n secondaryLabel?: string\n /** Progress toward a target (0-100). Renders a thin progress bar below the value. */\n progress?: number\n /** Surface variant, delegated to Card: `default` (tonal hairline, no shadow) | `elevated` (shadow) | `outline` (strong border, no shadow) | `flat`. @default 'default' */\n variant?: 'default' | 'elevated' | 'outline' | 'flat'\n /**\n * Tile density, delegated to Card's size axis. `sm` tightens padding to 16px and steps\n * the value down to `text-heading-md` — use it for dense KPI rows and narrow stat grids\n * (e.g. stat-row's 140px tiles). @default 'md'\n */\n size?: CardSize\n /** How the brand accent reads: `none` (neutral — default), `icon` (accent chip around `icon`), or `tint` (subtle accent surface wash + accent value). */\n accentStyle?: 'none' | 'icon' | 'tint'\n /** Animate value/label in on mount (subtle settle; content stays visible either way, never opacity-gated). @default false */\n reveal?: boolean\n /** When `accentStyle=\"icon\"`: `soft` (tinted chip) or `solid` (filled accent chip). @default 'soft' */\n iconFill?: 'soft' | 'solid'\n /**\n * Opt-in entrance flash: the icon chip mounts showing a transient state (e.g. a green up-arrow)\n * then settles to `icon`. A preset (`'up' | 'down' | 'goal' | 'record' | 'alert' | 'live'`) or an\n * explicit `{ tone, icon }`. Requires `icon`; implies the icon chip. Off by default.\n */\n flash?: FlashPreset | FlashSpec\n /** Speed of the entrance flash: `fast` | `normal` | `slow`. @default 'normal' */\n flashSpeed?: FlashSpeed\n /** Sparkline data points. Renders a mini SVG line chart in the card. */\n sparkline?: number[]\n /** Make the card clickable with hover state */\n onClick?: () => void\n /** Href — makes the card a link (uses the LinkContext Link component) */\n href?: string\n /** Footer content rendered below the card body, e.g. \"View details →\" */\n footer?: React.ReactNode\n}\n\nfunction buildSparklinePath(raw: number[], width: number, height: number): string {\n const data = raw.filter((v) => Number.isFinite(v))\n if (data.length < 2) return ''\n const min = Math.min(...data)\n const max = Math.max(...data)\n const range = max - min || 1\n const stepX = width / (data.length - 1)\n return data\n .map((v, i) => {\n const x = i * stepX\n const y = height - ((v - min) / range) * height\n return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`\n })\n .join(' ')\n}\n\nfunction Sparkline({\n data,\n colorClass,\n}: {\n data: number[]\n colorClass: string\n}) {\n const id = React.useId()\n const pathRef = React.useRef<SVGPathElement>(null)\n const [pathLength, setPathLength] = React.useState(0)\n const svgWidth = 80\n const svgHeight = 32\n const path = buildSparklinePath(data, svgWidth, svgHeight)\n\n React.useEffect(() => {\n if (pathRef.current) {\n setPathLength(pathRef.current.getTotalLength())\n }\n }, [path])\n\n if (!path) return null\n\n // Build area fill path (close to bottom-right then bottom-left)\n const areaPath = `${path} L${svgWidth.toFixed(1)},${svgHeight.toFixed(1)} L0,${svgHeight.toFixed(1)} Z`\n\n return (\n <div className={cn('w-20 h-8', colorClass)} aria-hidden=\"true\">\n <svg\n viewBox={`0 0 ${svgWidth} ${svgHeight}`}\n className=\"w-full h-full overflow-visible\"\n preserveAspectRatio=\"none\"\n >\n <defs>\n <linearGradient id={`sparkline-fill-${id}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"currentColor\" stopOpacity=\"0.1\" />\n <stop offset=\"100%\" stopColor=\"currentColor\" stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n <path d={areaPath} fill={`url(#sparkline-fill-${id})`} />\n <path\n ref={pathRef}\n d={path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n opacity: pathLength === 0 ? 0 : 1,\n strokeDasharray: pathLength,\n strokeDashoffset: pathLength,\n animation: pathLength > 0 ? `sparkline-draw-${id.replace(/:/g, '')} 1s ease-out forwards` : 'none',\n }}\n />\n </svg>\n {pathLength > 0 && (\n <style>{`@keyframes sparkline-draw-${id.replace(/:/g, '')} { to { stroke-dashoffset: 0; } }`}</style>\n )}\n </div>\n )\n}\n\nfunction ProgressBar({ progress, label }: { progress: number; label: string }) {\n const clamped = Math.max(0, Math.min(100, progress))\n // StatCard's own thresholds (90 = success, 70 = warning) — deliberately distinct\n // from Progress's autoColor (85/60), so we pass an explicit color.\n const barColor: ProgressColor =\n clamped >= 90 ? 'success' : clamped >= 70 ? 'warning' : 'accent'\n\n return (\n <Progress.Root value={clamped} size=\"sm\">\n <Progress.Track aria-label={`${label} progress`}>\n <Progress.Indicator color={barColor} />\n </Progress.Track>\n </Progress.Root>\n )\n}\n\nconst StatCard = React.forwardRef<HTMLDivElement, StatCardProps>(\n (\n {\n className,\n label,\n title,\n value,\n prefix,\n suffix,\n delta,\n deltaPlacement = 'block',\n icon,\n loading,\n comparisonLabel,\n secondaryLabel,\n progress,\n variant = 'default',\n size = 'md',\n accentStyle = 'none',\n reveal = false,\n iconFill = 'soft',\n flash,\n flashSpeed,\n sparkline,\n onClick,\n href,\n footer,\n ...props\n },\n ref,\n ) => {\n const Link = useLink()\n const resolvedLabel = title ?? label ?? ''\n const isClickable = !!(onClick || href)\n const computedAriaLabel =\n isClickable && !props['aria-label'] ? `View ${resolvedLabel}` : props['aria-label']\n\n if (loading) {\n return (\n <Card ref={ref} variant={variant} size={size} className={className} aria-busy=\"true\" {...props}>\n <CardContent className=\"flex flex-col gap-ds-03\">\n <Skeleton className=\"h-ds-04 w-24 rounded-control-inner\" />\n <Skeleton className=\"h-ds-sm w-32\" />\n <Skeleton className=\"h-3 w-16 rounded-control-inner\" />\n </CardContent>\n </Card>\n )\n }\n\n const DeltaIcon =\n delta?.direction === 'up'\n ? IconTrendingUp\n : delta?.direction === 'down'\n ? IconTrendingDown\n : IconMinus\n\n const deltaColour =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-surface-fg-muted'\n\n const sparklineColor =\n delta?.direction === 'up'\n ? 'text-success-11'\n : delta?.direction === 'down'\n ? 'text-error-11'\n : 'text-accent-11'\n\n const deltaNode = delta ? (\n <motion.div\n className={cn(\n // Rhythm comes from CardContent's flex gap — no placement margins.\n 'flex items-center gap-ds-02 text-body-sm font-medium',\n deltaColour,\n )}\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ ...springs.smooth, delay: 0.2 }}\n >\n <motion.span\n className=\"inline-flex\"\n initial={{ opacity: 0.5, scale: 1.4 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.smooth}\n >\n <Icon icon={DeltaIcon} size=\"sm\" />\n </motion.span>\n <span>{delta.value}</span>\n {comparisonLabel && (\n <span className=\"text-surface-fg-subtle font-normal\">{comparisonLabel}</span>\n )}\n </motion.div>\n ) : null\n\n // reveal=false → static (content-safe default). reveal=true → subtle settle; opacity NEVER gated.\n const revealProps = reveal\n ? { initial: { y: 8 }, animate: { y: 0 }, transition: springs.smooth }\n : { initial: false as const }\n\n const cardContent = (\n <>\n <div className=\"flex items-center justify-between\">\n <motion.p\n className=\"text-body-md font-medium text-surface-fg-muted\"\n {...revealProps}\n >\n {resolvedLabel}\n </motion.p>\n <div className=\"flex items-center gap-ds-03\">\n {sparkline && sparkline.length >= 2 && (\n <Sparkline data={sparkline} colorClass={sparklineColor} />\n )}\n {icon && flash ? (\n <StatFlash icon={icon} flash={flash} fill={iconFill} speed={flashSpeed} />\n ) : icon ? (\n accentStyle === 'icon' ? (\n <motion.span\n className={cn(\n 'inline-flex items-center justify-center rounded-control p-ds-02',\n iconFill === 'solid'\n ? 'bg-accent-9 text-accent-fg'\n : 'bg-accent-3 text-accent-11',\n )}\n {...revealProps}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"md\">{normalizeIcon(icon, 'md')}</IconProvider>\n </motion.span>\n ) : (\n <motion.span\n className=\"text-surface-fg-muted\"\n {...revealProps}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"lg\">{normalizeIcon(icon, 'lg')}</IconProvider>\n </motion.span>\n )\n ) : null}\n </div>\n </div>\n <div className=\"flex flex-col gap-ds-01\">\n <div className={cn(deltaPlacement === 'inline' && delta && 'flex items-baseline gap-ds-03')}>\n <div className=\"overflow-hidden\">\n <motion.p\n className={cn(\n 'inline-block font-semibold',\n size === 'sm' ? 'text-heading-md' : 'text-heading-lg',\n accentStyle === 'tint' ? 'text-accent-11' : 'text-surface-fg',\n )}\n {...revealProps}\n >\n {prefix && (\n <span className={cn('text-surface-fg-muted', size === 'sm' ? 'text-body-md' : 'text-heading-xs')}>{prefix}</span>\n )}\n <span className=\"tabular-nums\">{value}</span>\n {suffix && (\n <span className={cn('text-surface-fg-muted', size === 'sm' ? 'text-body-md' : 'text-heading-xs')}>{suffix}</span>\n )}\n </motion.p>\n </div>\n {deltaPlacement === 'inline' && deltaNode}\n </div>\n {secondaryLabel && (\n <motion.p\n className=\"text-body-sm text-surface-fg-subtle\"\n {...revealProps}\n >\n {secondaryLabel}\n </motion.p>\n )}\n </div>\n {progress != null && (\n <motion.div\n {...revealProps}\n >\n <ProgressBar progress={progress} label={resolvedLabel} />\n </motion.div>\n )}\n {deltaPlacement === 'block' && deltaNode}\n </>\n )\n\n // accentStyle=\"tint\" → a subtle accent gradient wash over Card's surface.\n const tintClass =\n accentStyle === 'tint' ? 'bg-linear-to-t from-accent-2 to-surface-raised' : undefined\n // Footer sits outside CardContent behind a full-width divider (both are direct\n // children of Card, so the rule spans edge-to-edge — no inset border-t).\n const body = (\n <>\n <CardContent className=\"flex flex-col gap-ds-03\">{cardContent}</CardContent>\n {footer && (\n <>\n <div aria-hidden=\"true\" className=\"h-px w-full bg-surface-border-subtle\" />\n <CardFooter className=\"text-body-sm\">\n <motion.div\n className=\"w-full\"\n initial={false}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.25 }}\n >\n {footer}\n </motion.div>\n </CardFooter>\n </>\n )}\n </>\n )\n\n // href → wrap Card in the framework Link (Card stays the shell; hover-lift inside).\n if (href) {\n return (\n <Link\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n onClick={onClick}\n className=\"block no-underline\"\n aria-label={computedAriaLabel}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n <Card variant={variant} size={size} interactive className={tintClass}>\n {body}\n </Card>\n </Link>\n )\n }\n\n // onClick → interactive Card with button semantics + keyboard activation.\n if (onClick) {\n return (\n <Card\n ref={ref}\n variant={variant}\n size={size}\n interactive\n className={cn(tintClass, className)}\n role=\"button\"\n tabIndex={0}\n aria-label={computedAriaLabel}\n onClick={onClick}\n onKeyDown={(e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onClick()\n }\n }}\n {...props}\n >\n {body}\n </Card>\n )\n }\n\n return (\n <Card ref={ref} variant={variant} size={size} className={cn(tintClass, className)} {...props}>\n {body}\n </Card>\n )\n },\n)\n\nStatCard.displayName = 'StatCard'\n\nexport { StatCard }\n"],"mappings":"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"}
@@ -17,7 +17,7 @@
17
17
  comparisonLabel: string (shown after delta, e.g. "vs last month")
18
18
  secondaryLabel: string (below main value, e.g. "of $50,000 target")
19
19
  progress: number (0-100, renders thin progress bar below value)
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
+ variant: "default" | "elevated" | "outline" | "flat" (default = tonal border-card hairline, no shadow; elevated = shadow, no border; outline = strong border, no shadow; flat = filled, no edge) — delegated to Card
21
21
  size: "sm" | "md" (default) | "lg" — delegated to Card's size axis; sm tightens padding to 16px and steps the value down to text-ds-2xl (use for dense KPI rows / narrow stat grids)
22
22
  accentStyle: "none" | "icon" | "tint" (none [default]; icon = accent chip around icon; tint = accent surface wash + accent value)
23
23
  iconFill: "soft" | "solid" (chip style when accentStyle="icon"; default soft)
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @devalok/shilp-sutra
2
2
 
3
- > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.50.0.
3
+ > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.51.0.
4
4
  > Built on the same primitives as shadcn/ui but with DIFFERENT prop APIs — never guess from shadcn knowledge; verify every prop.
5
5
  > This file is a ROUTER: it tells you what exists and where to get details. Do not look for prop tables here — fetch them per component (MCP tool or per-component doc file below).
6
6
 
@@ -22,15 +22,15 @@ import {
22
22
  - Header / actions / footer are built in as slots (`CardHeader`, `CardAction`, `CardFooter`) — don't reach for a wrapper. (`<ContentCard>` is deprecated; use Card slots.)
23
23
  - Need just a tinted region with no card affordance? Use a `<div className="bg-surface-raised">` (rare; usually Card is right).
24
24
 
25
- Card renders on `surface-raised` with `shadow-raised`. **Never** override its background or border.
25
+ Card renders on `surface-raised`. The `default` variant is tonal — a surface-tone shift plus a whisper hairline (`border-card`), no shadow. **Never** override its background or border.
26
26
 
27
27
  ## Variants
28
28
 
29
29
  | Variant | Use |
30
30
  |---|---|
31
- | `default` (default) | `surface-raised` + `shadow-raised`. Standard card. |
32
- | `elevated` | Slightly stronger shadow. Use for hero / hovered emphasis. |
33
- | `outline` | `surface-raised` + border-only (no shadow). Dense lists where 12 cards stacked together would feel too lifted. |
31
+ | `default` (default) | `surface-raised` + tonal `border-card` hairline, no shadow. Standard card — depth from tone, not a drop shadow. |
32
+ | `elevated` | `shadow-raised-hover`, no border. Use when a card must visibly pop (hero, dragged tile, spotlight panel). |
33
+ | `outline` | `surface-raised` + strong border-only (no shadow). Dense lists where stacked shadowed cards would feel too lifted. |
34
34
  | `flat` | `surface-raised` + no shadow, no border. For cards inside an already-elevated container. |
35
35
 
36
36
  ## Colors
package/mcp-manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "./mcp-manifest.schema.json",
3
3
  "manifestVersion": "1.2.0",
4
4
  "package": "@devalok/shilp-sutra",
5
- "packageVersion": "0.50.0",
5
+ "packageVersion": "0.51.0",
6
6
  "components": {
7
7
  "accordion": {
8
8
  "displayName": "Accordion",
@@ -6418,7 +6418,7 @@
6418
6418
  "variant": {
6419
6419
  "type": {
6420
6420
  "name": "union",
6421
- "raw": "\"default\" | \"elevated\" | \"outline\" | \"flat\" (default = ring-in-shadow, no border; elevated = stronger shadow; outline = border, no shadow; flat = filled, no edge) — delegated to Card"
6421
+ "raw": "\"default\" | \"elevated\" | \"outline\" | \"flat\" (default = tonal border-card hairline, no shadow; elevated = shadow, no border; outline = strong border, no shadow; flat = filled, no edge) — delegated to Card"
6422
6422
  },
6423
6423
  "required": false
6424
6424
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devalok/shilp-sutra",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "Devalok Design System — accessible React components, OKLCH design tokens, and Tailwind 4 CSS-first setup. Ships with AI-agent setup recipes.",
5
5
  "license": "MIT",
6
6
  "author": "Devalok Design & Strategy Studios <shilp-sutra@devalok.in>",
package/skill/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: shilp-sutra
3
3
  description: Add, configure, and use components from Devalok's shilp-sutra design system (@devalok/shilp-sutra) — a Tailwind 4 + React 19 + CVA library with 110+ accessible components, OKLCH design tokens, framer-motion animations, and per-component RSC-safe entry points. Use this skill whenever the user mentions shilp-sutra, Devalok, the @devalok npm scope, or asks to install/add/style/theme UI in any React project that already depends on the package — even if they don't name it explicitly. Use it instead of generic shadcn/ui, MUI, or Chakra knowledge when shilp-sutra is in the project. Covers Next.js (App + Pages), Vite, Astro, Remix, TanStack Start setup playbooks; component API and variant reference; brand token customization; Server Component import patterns; and a troubleshoot tree for the thirteen most common breakages.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.50.0"
6
+ version: "0.51.0"
7
7
  author: Devalok Design & Strategy Studios
8
8
  homepage: https://github.com/devalok-design/shilp-sutra
9
9
  npm: https://www.npmjs.com/package/@devalok/shilp-sutra
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @devalok/shilp-sutra
4
4
 
5
- > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.50.0.
5
+ > Radix UI + Tailwind 4 (CSS-first) + CVA design system for Devalok apps, v0.51.0.
6
6
  > Built on the same primitives as shadcn/ui but with DIFFERENT prop APIs — never guess from shadcn knowledge; verify every prop.
7
7
  > This file is a ROUTER: it tells you what exists and where to get details. Do not look for prop tables here — fetch them per component (MCP tool or per-component doc file below).
8
8