@devalok/shilp-sutra 0.42.1 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +78 -0
- package/dist/_chunks/chat.js +164 -163
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/mention-suggestion.js +35 -33
- 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/popover.js +1 -1
- package/dist/_chunks/popover.js.map +1 -1
- package/dist/_chunks/shared.js +1 -1
- package/dist/_chunks/shared.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 +1 -1
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/composed/bulk-action-bar.js +1 -1
- package/dist/composed/bulk-action-bar.js.map +1 -1
- package/dist/composed/command-palette.js +4 -4
- 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 +1 -1
- package/dist/composed/content-card.js.map +1 -1
- package/dist/composed/error-boundary.js +1 -1
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/extensions/emoji-suggestion.js +1 -1
- package/dist/composed/extensions/emoji-suggestion.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 +147 -143
- 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 +600 -591
- 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 +166 -164
- 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 +102 -98
- package/dist/shell/notification-center.js.map +1 -1
- package/dist/shell/sidebar.d.ts +6 -0
- package/dist/shell/sidebar.d.ts.map +1 -1
- package/dist/shell/sidebar.js +142 -118
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/shell/top-bar.d.ts.map +1 -1
- package/dist/shell/top-bar.js +87 -84
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/tokens/semantic.css +9 -2
- package/dist/ui/alert-dialog.js +1 -1
- package/dist/ui/alert-dialog.js.map +1 -1
- package/dist/ui/autocomplete.js +1 -1
- package/dist/ui/autocomplete.js.map +1 -1
- 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 +76 -110
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/chat/message.d.ts.map +1 -1
- package/dist/ui/color-input.js +1 -1
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/combobox.js +3 -3
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/context-menu.js +2 -2
- package/dist/ui/context-menu.js.map +1 -1
- package/dist/ui/data-table-bulk-actions.js +1 -1
- package/dist/ui/data-table-bulk-actions.js.map +1 -1
- package/dist/ui/dialog.js +1 -1
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dropdown-menu.js +11 -5
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/hover-card.js +1 -1
- package/dist/ui/hover-card.js.map +1 -1
- package/dist/ui/index.d.ts +3 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +58 -56
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input-otp.js +1 -1
- package/dist/ui/input-otp.js.map +1 -1
- package/dist/ui/menubar.js +12 -6
- package/dist/ui/menubar.js.map +1 -1
- package/dist/ui/navigation-menu.js +1 -1
- package/dist/ui/navigation-menu.js.map +1 -1
- package/dist/ui/oauth-button/oauth-button.d.ts.map +1 -1
- package/dist/ui/select.js +6 -3
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sidebar.js +74 -74
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/split-button.js +1 -1
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/stat-card.d.ts +26 -3
- package/dist/ui/stat-card.d.ts.map +1 -1
- package/dist/ui/stat-card.js +183 -150
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/stat-flash.d.ts +46 -0
- package/dist/ui/stat-flash.d.ts.map +1 -0
- package/dist/ui/stat-flash.js +114 -0
- package/dist/ui/stat-flash.js.map +1 -0
- 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/toast.js +2 -2
- package/dist/ui/toast.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 +18 -3
- package/docs/components/ui/stat-flash.md +44 -0
- package/docs/components/ui/truncated-text.md +49 -0
- package/llms-full.txt +125 -8
- package/make-kit/components/card.md +14 -4
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
- package/skill/references/components-full.md +125 -8
package/dist/ui/stat-card.js.map
CHANGED
|
@@ -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 { 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'\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 * **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 React.HTMLAttributes<HTMLDivElement> {\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 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 /** Color accent for the left border decoration */\n accent?: 'default' | 'success' | 'warning' | 'error' | 'info'\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\nconst accentBorderMap: Record<NonNullable<StatCardProps['accent']>, string> = {\n default: 'border-l-accent-9',\n success: 'border-l-success-9',\n warning: 'border-l-warning-9',\n error: 'border-l-error-9',\n info: 'border-l-info-9',\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 const barColor =\n clamped >= 90 ? 'bg-success-9' : clamped >= 70 ? 'bg-warning-9' : 'bg-accent-9'\n\n return (\n <div className=\"h-1 w-full rounded-pill bg-surface-raised mt-ds-04\" role=\"progressbar\" aria-label={`${label} progress`} aria-valuenow={clamped} aria-valuemin={0} aria-valuemax={100}>\n <div\n className={cn('h-full rounded-pill transition-[width] duration-moderate-02 ease-productive-standard', barColor)}\n style={{ width: `${clamped}%` }}\n />\n </div>\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 icon,\n loading,\n comparisonLabel,\n secondaryLabel,\n progress,\n accent,\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 <div\n ref={ref}\n className={cn(\n 'rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b',\n accent && `border-l-[3px] ${accentBorderMap[accent]}`,\n className,\n )}\n {...props}\n >\n <div className=\"h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse mb-ds-05\" />\n <div className=\"h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse mb-ds-03\" />\n <div className=\"h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse\" />\n </div>\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 cardContent = (\n <>\n <div className=\"flex items-center justify-between mb-ds-04\">\n <motion.p\n className=\"text-ds-md font-medium text-surface-fg-muted\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\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 && (\n <motion.span\n className=\"text-surface-fg-muted\"\n initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n aria-hidden=\"true\"\n >\n <IconProvider size=\"lg\">{normalizeIcon(icon, 'lg')}</IconProvider>\n </motion.span>\n )}\n </div>\n </div>\n <div className=\"overflow-hidden\">\n <motion.p\n className=\"inline-block text-ds-3xl font-semibold text-surface-fg\"\n initial={{ opacity: 0, y: '100%' }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.smooth}\n >\n {prefix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{prefix}</span>\n )}\n <span className=\"tabular-nums\">{value}</span>\n {suffix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{suffix}</span>\n )}\n </motion.p>\n </div>\n {secondaryLabel && (\n <motion.p\n className=\"text-ds-sm text-surface-fg-subtle mt-ds-01\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.1 }}\n >\n {secondaryLabel}\n </motion.p>\n )}\n {progress != null && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.15 }}\n >\n <ProgressBar progress={progress} label={resolvedLabel} />\n </motion.div>\n )}\n {delta && (\n <motion.div\n className={cn(\n 'mt-ds-03 flex items-center gap-ds-02 text-ds-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.bouncy}\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 )}\n {footer && (\n <motion.div\n className=\"mt-ds-04 pt-ds-04 border-t border-surface-border text-ds-sm\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.25 }}\n >\n {footer}\n </motion.div>\n )}\n </>\n )\n\n const cardClasses = cn(\n 'rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b',\n accent && `border-l-[3px] ${accentBorderMap[accent]}`,\n isClickable &&\n 'cursor-pointer hover:shadow-raised-hover hover:border-surface-border-strong transition-[box-shadow,border-color] duration-fast-02 ease-productive-standard group',\n className,\n )\n\n if (href) {\n return (\n <Link\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n onClick={onClick}\n className={cn(cardClasses, 'block no-underline')}\n aria-label={computedAriaLabel}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n {cardContent}\n </Link>\n )\n }\n\n return (\n <div\n ref={ref}\n className={cardClasses}\n onClick={onClick}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n aria-label={computedAriaLabel}\n onKeyDown={\n onClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onClick()\n }\n }\n : undefined\n }\n {...props}\n >\n {cardContent}\n </div>\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 } 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 { 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 /** 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 /** 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 const barColor =\n clamped >= 90 ? 'bg-success-9' : clamped >= 70 ? 'bg-warning-9' : 'bg-accent-9'\n\n return (\n <div className=\"h-1 w-full rounded-pill bg-surface-raised mt-ds-04\" role=\"progressbar\" aria-label={`${label} progress`} aria-valuenow={clamped} aria-valuemin={0} aria-valuemax={100}>\n <div\n className={cn('h-full rounded-pill transition-[width] duration-moderate-02 ease-productive-standard', barColor)}\n style={{ width: `${clamped}%` }}\n />\n </div>\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 accentStyle = 'none',\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} className={className} {...props}>\n <CardContent>\n <div className=\"h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse mb-ds-05\" />\n <div className=\"h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse mb-ds-03\" />\n <div className=\"h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse\" />\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 'flex items-center gap-ds-02 text-ds-sm font-medium',\n deltaColour,\n // block placement owns the gap to the value; inline rides the value's baseline row.\n deltaPlacement === 'block' && 'mt-ds-03',\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.bouncy}\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 const cardContent = (\n <>\n <div className=\"flex items-center justify-between mb-ds-04\">\n <motion.p\n className=\"text-ds-md font-medium text-surface-fg-muted\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={tweens.fade}\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 initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\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 initial={{ opacity: 0, scale: 0.96 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\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={cn(deltaPlacement === 'inline' && delta && 'flex items-baseline gap-ds-03')}>\n <div className=\"overflow-hidden\">\n <motion.p\n className={cn(\n 'inline-block text-ds-3xl font-semibold',\n accentStyle === 'tint' ? 'text-accent-11' : 'text-surface-fg',\n )}\n initial={{ opacity: 0, y: '100%' }}\n animate={{ opacity: 1, y: 0 }}\n transition={springs.smooth}\n >\n {prefix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{prefix}</span>\n )}\n <span className=\"tabular-nums\">{value}</span>\n {suffix && (\n <span className=\"text-surface-fg-muted text-ds-lg\">{suffix}</span>\n )}\n </motion.p>\n </div>\n {deltaPlacement === 'inline' && deltaNode}\n </div>\n {secondaryLabel && (\n <motion.p\n className=\"text-ds-sm text-surface-fg-subtle mt-ds-01\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.1 }}\n >\n {secondaryLabel}\n </motion.p>\n )}\n {progress != null && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.15 }}\n >\n <ProgressBar progress={progress} label={resolvedLabel} />\n </motion.div>\n )}\n {deltaPlacement === 'block' && deltaNode}\n {footer && (\n <motion.div\n className=\"mt-ds-04 pt-ds-04 border-t border-surface-border text-ds-sm\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n transition={{ ...tweens.fade, delay: 0.25 }}\n >\n {footer}\n </motion.div>\n )}\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 const body = <CardContent>{cardContent}</CardContent>\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} 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 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} className={cn(tintClass, className)} {...props}>\n {body}\n </Card>\n )\n },\n)\n\nStatCard.displayName = 'StatCard'\n\nexport { StatCard }\n"],"mappings":";;;;;;;;;;;;;;AAgHA,SAAS,EAAmB,GAAe,GAAe,GAAwB;CAChF,IAAM,IAAO,EAAI,QAAQ,MAAM,OAAO,SAAS,EAAE,CAAC;AAClD,KAAI,EAAK,SAAS,EAAG,QAAO;CAC5B,IAAM,IAAM,KAAK,IAAI,GAAG,EAAK,EAEvB,IADM,KAAK,IAAI,GAAG,EAAK,GACT,KAAO,GACrB,IAAQ,KAAS,EAAK,SAAS;AACrC,QAAO,EACJ,KAAK,GAAG,MAAM;EACb,IAAM,IAAI,IAAI,GACR,IAAI,KAAW,IAAI,KAAO,IAAS;AACzC,SAAO,GAAG,MAAM,IAAI,MAAM,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE;GAC5D,CACD,KAAK,IAAI;;AAGd,SAAS,EAAU,EACjB,SACA,iBAIC;CACD,IAAM,IAAK,EAAM,OAAO,EAClB,IAAU,EAAM,OAAuB,KAAK,EAC5C,CAAC,GAAY,KAAiB,EAAM,SAAS,EAAE,EAG/C,IAAO,EAAmB,GAAM,IAAU,GAAU;AAQ1D,KANA,EAAM,gBAAgB;AACpB,EAAI,EAAQ,WACV,EAAc,EAAQ,QAAQ,gBAAgB,CAAC;IAEhD,CAAC,EAAK,CAAC,EAEN,CAAC,EAAM,QAAO;CAGlB,IAAM,IAAW,GAAG,EAAK,IAAI,IAAS,QAAQ,EAAE,CAAC,GAAG,IAAU,QAAQ,EAAE,CAAC,MAAM,IAAU,QAAQ,EAAE,CAAC;AAEpG,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,YAAY,EAAW;EAAE,eAAY;YAAxD,CACE,kBAAC,OAAD;GACE,SAAS;GACT,WAAU;GACV,qBAAoB;aAHtB;IAKE,kBAAC,QAAD,EAAA,UACE,kBAAC,kBAAD;KAAgB,IAAI,kBAAkB;KAAM,IAAG;KAAI,IAAG;KAAI,IAAG;KAAI,IAAG;eAApE,CACE,kBAAC,QAAD;MAAM,QAAO;MAAK,WAAU;MAAe,aAAY;MAAQ,CAAA,EAC/D,kBAAC,QAAD;MAAM,QAAO;MAAO,WAAU;MAAe,aAAY;MAAM,CAAA,CAChD;QACZ,CAAA;IACP,kBAAC,QAAD;KAAM,GAAG;KAAU,MAAM,uBAAuB,EAAG;KAAM,CAAA;IACzD,kBAAC,QAAD;KACE,KAAK;KACL,GAAG;KACH,MAAK;KACL,QAAO;KACP,aAAY;KACZ,eAAc;KACd,gBAAe;KACf,OAAO;MACL,SAAS,MAAe,IAAI,IAAI;MAChC,iBAAiB;MACjB,kBAAkB;MAClB,WAAW,IAAa,IAAI,kBAAkB,EAAG,QAAQ,MAAM,GAAG,CAAC,yBAAyB;MAC7F;KACD,CAAA;IACE;MACL,IAAa,KACZ,kBAAC,SAAD,EAAA,UAAQ,6BAA6B,EAAG,QAAQ,MAAM,GAAG,CAAC,oCAA2C,CAAA,CAEnG;;;AAIV,SAAS,EAAY,EAAE,aAAU,YAA8C;CAC7E,IAAM,IAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,EAAS,CAAC,EAC9C,IACJ,KAAW,KAAK,iBAAiB,KAAW,KAAK,iBAAiB;AAEpE,QACE,kBAAC,OAAD;EAAK,WAAU;EAAqD,MAAK;EAAc,cAAY,GAAG,EAAM;EAAY,iBAAe;EAAS,iBAAe;EAAG,iBAAe;YAC/K,kBAAC,OAAD;GACE,WAAW,EAAG,wFAAwF,EAAS;GAC/G,OAAO,EAAE,OAAO,GAAG,EAAQ,IAAI;GAC/B,CAAA;EACE,CAAA;;AAIV,IAAM,IAAW,EAAM,YAEnB,EACE,cACA,UACA,UACA,UACA,WACA,WACA,UACA,oBAAiB,SACjB,SACA,YACA,oBACA,mBACA,aACA,aAAU,WACV,iBAAc,QACd,cAAW,QACX,UACA,eACA,cACA,YACA,SACA,WACA,GAAG,KAEL,MACG;CACH,IAAM,IAAO,GAAS,EAChB,IAAgB,KAAS,KAAS,IAElC,KADiB,KAAW,MAEjB,CAAC,EAAM,gBAAgB,QAAQ,MAAkB,EAAM;AAExE,KAAI,EACF,QACE,kBAAC,GAAD;EAAW;EAAc;EAAoB;EAAW,GAAI;YAC1D,kBAAC,GAAD,EAAA,UAAA;GACE,kBAAC,OAAD,EAAK,WAAU,8EAA+E,CAAA;GAC9F,kBAAC,OAAD,EAAK,WAAU,wEAAyE,CAAA;GACxF,kBAAC,OAAD,EAAK,WAAU,iEAAkE,CAAA;GACrE,EAAA,CAAA;EACT,CAAA;CAIX,IAAM,IACJ,GAAO,cAAc,OACjB,IACA,GAAO,cAAc,SACnB,IACA,GAEF,IACJ,GAAO,cAAc,OACjB,oBACA,GAAO,cAAc,SACnB,kBACA,yBAEF,IACJ,GAAO,cAAc,OACjB,oBACA,GAAO,cAAc,SACnB,kBACA,kBAEF,IAAY,IAChB,kBAAC,EAAO,KAAR;EACE,WAAW,EACT,sDACA,GAEA,MAAmB,WAAW,WAC/B;EACD,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,YAAY;GAAE,GAAG,EAAQ;GAAQ,OAAO;GAAK;YAT/C;GAWE,kBAAC,EAAO,MAAR;IACE,WAAU;IACV,SAAS;KAAE,SAAS;KAAK,OAAO;KAAK;IACrC,SAAS;KAAE,SAAS;KAAG,OAAO;KAAG;IACjC,YAAY,EAAQ;cAEpB,kBAAC,GAAD;KAAM,MAAM;KAAW,MAAK;KAAO,CAAA;IACvB,CAAA;GACd,kBAAC,QAAD,EAAA,UAAO,EAAM,OAAa,CAAA;GACzB,KACC,kBAAC,QAAD;IAAM,WAAU;cAAsC;IAAuB,CAAA;GAEpE;MACX,MAEE,IACJ,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,EAAO,GAAR;IACE,WAAU;IACV,SAAS,EAAE,SAAS,GAAG;IACvB,SAAS,EAAE,SAAS,GAAG;IACvB,YAAY,EAAO;cAElB;IACQ,CAAA,EACX,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KAAa,EAAU,UAAU,KAChC,kBAAC,GAAD;KAAW,MAAM;KAAW,YAAY;KAAkB,CAAA,EAE3D,KAAQ,IACP,kBAAC,GAAD;KAAiB;KAAa;KAAO,MAAM;KAAU,OAAO;KAAc,CAAA,GACxE,IACF,MAAgB,SACd,kBAAC,EAAO,MAAR;KACE,WAAW,EACT,mEACA,MAAa,UACT,+BACA,6BACL;KACD,SAAS;MAAE,SAAS;MAAG,OAAO;MAAM;KACpC,SAAS;MAAE,SAAS;MAAG,OAAO;MAAG;KACjC,YAAY,EAAQ;KACpB,eAAY;eAEZ,kBAAC,GAAD;MAAc,MAAK;gBAAM,EAAc,GAAM,KAAK;MAAgB,CAAA;KACtD,CAAA,GAEd,kBAAC,EAAO,MAAR;KACE,WAAU;KACV,SAAS;MAAE,SAAS;MAAG,OAAO;MAAM;KACpC,SAAS;MAAE,SAAS;MAAG,OAAO;MAAG;KACjC,YAAY,EAAQ;KACpB,eAAY;eAEZ,kBAAC,GAAD;MAAc,MAAK;gBAAM,EAAc,GAAM,KAAK;MAAgB,CAAA;KACtD,CAAA,GAEd,KACA;MACF;;EACN,kBAAC,OAAD;GAAK,WAAW,EAAG,MAAmB,YAAY,KAAS,gCAAgC;aAA3F,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,EAAO,GAAR;KACE,WAAW,EACT,0CACA,MAAgB,SAAS,mBAAmB,kBAC7C;KACD,SAAS;MAAE,SAAS;MAAG,GAAG;MAAQ;KAClC,SAAS;MAAE,SAAS;MAAG,GAAG;MAAG;KAC7B,YAAY,EAAQ;eAPtB;MASG,KACC,kBAAC,QAAD;OAAM,WAAU;iBAAoC;OAAc,CAAA;MAEpE,kBAAC,QAAD;OAAM,WAAU;iBAAgB;OAAa,CAAA;MAC5C,KACC,kBAAC,QAAD;OAAM,WAAU;iBAAoC;OAAc,CAAA;MAE3D;;IACP,CAAA,EACL,MAAmB,YAAY,EAC5B;;EACL,KACC,kBAAC,EAAO,GAAR;GACE,WAAU;GACV,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,YAAY;IAAE,GAAG,EAAO;IAAM,OAAO;IAAK;aAEzC;GACQ,CAAA;EAEZ,KAAY,QACX,kBAAC,EAAO,KAAR;GACE,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,YAAY;IAAE,GAAG,EAAO;IAAM,OAAO;IAAM;aAE3C,kBAAC,GAAD;IAAuB;IAAU,OAAO;IAAiB,CAAA;GAC9C,CAAA;EAEd,MAAmB,WAAW;EAC9B,KACC,kBAAC,EAAO,KAAR;GACE,WAAU;GACV,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,YAAY;IAAE,GAAG,EAAO;IAAM,OAAO;IAAM;aAE1C;GACU,CAAA;EAEd,EAAA,CAAA,EAIC,IACJ,MAAgB,SAAS,mDAAmD,KAAA,GACxE,IAAO,kBAAC,GAAD,EAAA,UAAc,GAA0B,CAAA;AA6CrD,QA1CI,IAEA,kBAAC,GAAD;EACO;EACC;EACG;EACT,WAAU;EACV,cAAY;EACZ,GAAK;YAEL,kBAAC,GAAD;GAAe;GAAS,aAAA;GAAY,WAAW;aAC5C;GACI,CAAA;EACF,CAAA,GAKP,IAEA,kBAAC,GAAD;EACO;EACI;EACT,aAAA;EACA,WAAW,EAAG,GAAW,EAAU;EACnC,MAAK;EACL,UAAU;EACV,cAAY;EACH;EACT,YAAY,MAA2B;AACrC,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,GAAS;;EAGb,GAAI;YAEH;EACI,CAAA,GAKT,kBAAC,GAAD;EAAW;EAAc;EAAS,WAAW,EAAG,GAAW,EAAU;EAAE,GAAI;YACxE;EACI,CAAA;EAGZ;AAED,EAAS,cAAc"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Transition } from 'framer-motion';
|
|
3
|
+
import { IconInput } from './lib/icon-input';
|
|
4
|
+
export type FlashTone = 'success' | 'error' | 'warning' | 'info' | 'accent';
|
|
5
|
+
/** Named entrance states. Each maps to a tone + glyph. */
|
|
6
|
+
export type FlashPreset = 'up' | 'down' | 'goal' | 'record' | 'alert' | 'live';
|
|
7
|
+
/** Explicit flash: any tone + any glyph. */
|
|
8
|
+
export interface FlashSpec {
|
|
9
|
+
tone: FlashTone;
|
|
10
|
+
icon: IconInput;
|
|
11
|
+
}
|
|
12
|
+
/** Composable speed — coarse presets built from the DS motion tokens. */
|
|
13
|
+
export type FlashSpeed = 'fast' | 'normal' | 'slow';
|
|
14
|
+
export interface StatFlashProps {
|
|
15
|
+
/** Settled identity glyph, shown after the flash resolves. */
|
|
16
|
+
icon: IconInput;
|
|
17
|
+
/** The entrance flash — a named preset or an explicit `{ tone, icon }`. */
|
|
18
|
+
flash: FlashPreset | FlashSpec;
|
|
19
|
+
/** Resting chip style once settled. @default 'soft' */
|
|
20
|
+
fill?: 'soft' | 'solid';
|
|
21
|
+
/** Coarse speed preset (hold + settle + fade), built from DS motion tokens. @default 'normal' */
|
|
22
|
+
speed?: FlashSpeed;
|
|
23
|
+
/** Override how long the flash holds before settling, in ms. Wins over `speed`. */
|
|
24
|
+
holdMs?: number;
|
|
25
|
+
/** Override the settle (icon scale-in) transition. Wins over `speed`. */
|
|
26
|
+
settleTransition?: Transition;
|
|
27
|
+
/** Override the flash enter/exit fade transition. Wins over `speed`. */
|
|
28
|
+
flashTransition?: Transition;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* StatFlash — a chip that mounts showing a transient **state** (a toned glyph: an up-arrow on
|
|
32
|
+
* green, a check, an alert…) then settles into the metric's stable **identity** icon. The pattern
|
|
33
|
+
* is *state → identity*: communicate what just happened, then resolve to what the metric is.
|
|
34
|
+
*
|
|
35
|
+
* Used by `StatCard` via its `flash` prop, but standalone too (list rows, badges, toasts).
|
|
36
|
+
* Honors `prefers-reduced-motion` — renders the settled identity directly, no flash.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* <StatFlash icon={<IconFolder />} flash="up" /> // green up-arrow → folder
|
|
40
|
+
* <StatFlash icon={<IconBolt />} flash={{ tone: 'info', icon: <IconActivity /> }} />
|
|
41
|
+
*/
|
|
42
|
+
export declare function StatFlash({ icon, flash, fill, speed, holdMs, settleTransition, flashTransition, }: StatFlashProps): import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
export declare namespace StatFlash {
|
|
44
|
+
var displayName: string;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=stat-flash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stat-flash.d.ts","sourceRoot":"","sources":["../../src/ui/stat-flash.tsx"],"names":[],"mappings":"AAUA,OAAO,EAA2B,KAAK,UAAU,EAAoB,MAAM,eAAe,CAAA;AAI1F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAKjD,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAE3E,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAE9E,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,SAAS,CAAA;CAChB;AA2BD,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAQnD,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,IAAI,EAAE,SAAS,CAAA;IACf,2EAA2E;IAC3E,KAAK,EAAE,WAAW,GAAG,SAAS,CAAA;IAC9B,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,iGAAiG;IACjG,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,UAAU,CAAA;IAC7B,wEAAwE;IACxE,eAAe,CAAC,EAAE,UAAU,CAAA;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,IAAa,EACb,KAAgB,EAChB,MAAM,EACN,gBAAgB,EAChB,eAAe,GAChB,EAAE,cAAc,2CA0DhB;yBAlEe,SAAS"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { IconProvider as e } from "./icon-context.js";
|
|
3
|
+
import { springs as t, tweens as n } from "./lib/motion.js";
|
|
4
|
+
import { cn as r } from "./lib/utils.js";
|
|
5
|
+
import { t as i } from "../_chunks/normalize-icon.js";
|
|
6
|
+
import * as a from "react";
|
|
7
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
8
|
+
import { IconAlertTriangle as c, IconArrowDown as l, IconArrowUp as u, IconBolt as d, IconCheck as f, IconStar as p } from "@tabler/icons-react";
|
|
9
|
+
import { AnimatePresence as m, motion as h, useReducedMotion as g } from "framer-motion";
|
|
10
|
+
//#region src/ui/stat-flash.tsx
|
|
11
|
+
var _ = {
|
|
12
|
+
up: {
|
|
13
|
+
tone: "success",
|
|
14
|
+
Glyph: u
|
|
15
|
+
},
|
|
16
|
+
down: {
|
|
17
|
+
tone: "error",
|
|
18
|
+
Glyph: l
|
|
19
|
+
},
|
|
20
|
+
goal: {
|
|
21
|
+
tone: "success",
|
|
22
|
+
Glyph: f
|
|
23
|
+
},
|
|
24
|
+
record: {
|
|
25
|
+
tone: "accent",
|
|
26
|
+
Glyph: p
|
|
27
|
+
},
|
|
28
|
+
alert: {
|
|
29
|
+
tone: "warning",
|
|
30
|
+
Glyph: c
|
|
31
|
+
},
|
|
32
|
+
live: {
|
|
33
|
+
tone: "info",
|
|
34
|
+
Glyph: d
|
|
35
|
+
}
|
|
36
|
+
}, v = {
|
|
37
|
+
success: "bg-success-9",
|
|
38
|
+
error: "bg-error-9",
|
|
39
|
+
warning: "bg-warning-9",
|
|
40
|
+
info: "bg-info-9",
|
|
41
|
+
accent: "bg-accent-9"
|
|
42
|
+
}, y = {
|
|
43
|
+
success: "text-success-fg",
|
|
44
|
+
error: "text-error-fg",
|
|
45
|
+
warning: "text-warning-fg",
|
|
46
|
+
info: "text-info-fg",
|
|
47
|
+
accent: "text-accent-fg"
|
|
48
|
+
}, b = {
|
|
49
|
+
fast: {
|
|
50
|
+
holdMs: 450,
|
|
51
|
+
settle: t.snappy,
|
|
52
|
+
fade: n.fade
|
|
53
|
+
},
|
|
54
|
+
normal: {
|
|
55
|
+
holdMs: 650,
|
|
56
|
+
settle: t.snappy,
|
|
57
|
+
fade: n.fade
|
|
58
|
+
},
|
|
59
|
+
slow: {
|
|
60
|
+
holdMs: 950,
|
|
61
|
+
settle: t.gentle,
|
|
62
|
+
fade: n.elegant
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function x({ icon: t, flash: n, fill: c = "soft", speed: l = "normal", holdMs: u, settleTransition: d, flashTransition: f }) {
|
|
66
|
+
let p = g(), x = b[l], C = u ?? x.holdMs, w = d ?? x.settle, T = f ?? x.fade, E = typeof n == "string" ? {
|
|
67
|
+
tone: _[n].tone,
|
|
68
|
+
icon: /* @__PURE__ */ o(S, { preset: n })
|
|
69
|
+
} : n, [D, O] = a.useState(!1);
|
|
70
|
+
return a.useEffect(() => {
|
|
71
|
+
if (p) {
|
|
72
|
+
O(!0);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let e = setTimeout(() => O(!0), C);
|
|
76
|
+
return () => clearTimeout(e);
|
|
77
|
+
}, [p, C]), /* @__PURE__ */ s("span", {
|
|
78
|
+
className: r("relative inline-flex items-center justify-center overflow-hidden rounded-control p-ds-02", c === "solid" ? "bg-accent-9 text-accent-fg" : "bg-accent-3 text-accent-11"),
|
|
79
|
+
"aria-hidden": "true",
|
|
80
|
+
children: [/* @__PURE__ */ o(h.span, {
|
|
81
|
+
className: "inline-flex",
|
|
82
|
+
initial: p ? !1 : {
|
|
83
|
+
opacity: 0,
|
|
84
|
+
scale: .55
|
|
85
|
+
},
|
|
86
|
+
animate: {
|
|
87
|
+
opacity: +!!D,
|
|
88
|
+
scale: D ? 1 : .55
|
|
89
|
+
},
|
|
90
|
+
transition: w,
|
|
91
|
+
children: /* @__PURE__ */ o(e, {
|
|
92
|
+
size: "md",
|
|
93
|
+
children: i(t, "md")
|
|
94
|
+
})
|
|
95
|
+
}), /* @__PURE__ */ o(m, { children: !D && /* @__PURE__ */ o(h.span, {
|
|
96
|
+
className: r("absolute inset-0 inline-flex items-center justify-center", v[E.tone], y[E.tone]),
|
|
97
|
+
initial: { opacity: 0 },
|
|
98
|
+
animate: { opacity: 1 },
|
|
99
|
+
exit: { opacity: 0 },
|
|
100
|
+
transition: T,
|
|
101
|
+
children: /* @__PURE__ */ o(e, {
|
|
102
|
+
size: "md",
|
|
103
|
+
children: i(E.icon, "md")
|
|
104
|
+
})
|
|
105
|
+
}) })]
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function S({ preset: e }) {
|
|
109
|
+
let { Glyph: t } = _[e];
|
|
110
|
+
return /* @__PURE__ */ o(t, {});
|
|
111
|
+
}
|
|
112
|
+
x.displayName = "StatFlash";
|
|
113
|
+
//#endregion
|
|
114
|
+
export { x as StatFlash };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stat-flash.js","names":[],"sources":["../../src/ui/stat-flash.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertTriangle,\n IconArrowDown,\n IconArrowUp,\n IconBolt,\n IconCheck,\n IconStar,\n} from '@tabler/icons-react'\nimport { AnimatePresence, motion, type Transition, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { IconProvider } from './icon-context'\nimport type { IconInput } from './lib/icon-input'\nimport { springs, tweens } from './lib/motion'\nimport { normalizeIcon } from './lib/normalize-icon'\nimport { cn } from './lib/utils'\n\nexport type FlashTone = 'success' | 'error' | 'warning' | 'info' | 'accent'\n\n/** Named entrance states. Each maps to a tone + glyph. */\nexport type FlashPreset = 'up' | 'down' | 'goal' | 'record' | 'alert' | 'live'\n\n/** Explicit flash: any tone + any glyph. */\nexport interface FlashSpec {\n tone: FlashTone\n icon: IconInput\n}\n\nconst FLASH_PRESETS: Record<FlashPreset, { tone: FlashTone; Glyph: React.ComponentType }> = {\n up: { tone: 'success', Glyph: IconArrowUp },\n down: { tone: 'error', Glyph: IconArrowDown },\n goal: { tone: 'success', Glyph: IconCheck },\n record: { tone: 'accent', Glyph: IconStar },\n alert: { tone: 'warning', Glyph: IconAlertTriangle },\n live: { tone: 'info', Glyph: IconBolt },\n}\n\nconst toneBg: Record<FlashTone, string> = {\n success: 'bg-success-9',\n error: 'bg-error-9',\n warning: 'bg-warning-9',\n info: 'bg-info-9',\n accent: 'bg-accent-9',\n}\n\nconst toneFg: Record<FlashTone, string> = {\n success: 'text-success-fg',\n error: 'text-error-fg',\n warning: 'text-warning-fg',\n info: 'text-info-fg',\n accent: 'text-accent-fg',\n}\n\n/** Composable speed — coarse presets built from the DS motion tokens. */\nexport type FlashSpeed = 'fast' | 'normal' | 'slow'\n\nconst SPEEDS: Record<FlashSpeed, { holdMs: number; settle: Transition; fade: Transition }> = {\n fast: { holdMs: 450, settle: springs.snappy, fade: tweens.fade },\n normal: { holdMs: 650, settle: springs.snappy, fade: tweens.fade },\n slow: { holdMs: 950, settle: springs.gentle, fade: tweens.elegant },\n}\n\nexport interface StatFlashProps {\n /** Settled identity glyph, shown after the flash resolves. */\n icon: IconInput\n /** The entrance flash — a named preset or an explicit `{ tone, icon }`. */\n flash: FlashPreset | FlashSpec\n /** Resting chip style once settled. @default 'soft' */\n fill?: 'soft' | 'solid'\n /** Coarse speed preset (hold + settle + fade), built from DS motion tokens. @default 'normal' */\n speed?: FlashSpeed\n /** Override how long the flash holds before settling, in ms. Wins over `speed`. */\n holdMs?: number\n /** Override the settle (icon scale-in) transition. Wins over `speed`. */\n settleTransition?: Transition\n /** Override the flash enter/exit fade transition. Wins over `speed`. */\n flashTransition?: Transition\n}\n\n/**\n * StatFlash — a chip that mounts showing a transient **state** (a toned glyph: an up-arrow on\n * green, a check, an alert…) then settles into the metric's stable **identity** icon. The pattern\n * is *state → identity*: communicate what just happened, then resolve to what the metric is.\n *\n * Used by `StatCard` via its `flash` prop, but standalone too (list rows, badges, toasts).\n * Honors `prefers-reduced-motion` — renders the settled identity directly, no flash.\n *\n * @example\n * <StatFlash icon={<IconFolder />} flash=\"up\" /> // green up-arrow → folder\n * <StatFlash icon={<IconBolt />} flash={{ tone: 'info', icon: <IconActivity /> }} />\n */\nexport function StatFlash({\n icon,\n flash,\n fill = 'soft',\n speed = 'normal',\n holdMs,\n settleTransition,\n flashTransition,\n}: StatFlashProps) {\n const prefersReduced = useReducedMotion()\n const preset = SPEEDS[speed]\n const hold = holdMs ?? preset.holdMs\n const settleT = settleTransition ?? preset.settle\n const fadeT = flashTransition ?? preset.fade\n\n const resolved =\n typeof flash === 'string'\n ? { tone: FLASH_PRESETS[flash].tone, icon: <FlashPresetGlyph preset={flash} /> }\n : flash\n\n const [settled, setSettled] = React.useState(false)\n\n React.useEffect(() => {\n if (prefersReduced) {\n setSettled(true)\n return\n }\n const t = setTimeout(() => setSettled(true), hold)\n return () => clearTimeout(t)\n }, [prefersReduced, hold])\n\n return (\n <span\n className={cn(\n 'relative inline-flex items-center justify-center overflow-hidden rounded-control p-ds-02',\n fill === 'solid' ? 'bg-accent-9 text-accent-fg' : 'bg-accent-3 text-accent-11',\n )}\n aria-hidden=\"true\"\n >\n <motion.span\n className=\"inline-flex\"\n initial={prefersReduced ? false : { opacity: 0, scale: 0.55 }}\n animate={{ opacity: settled ? 1 : 0, scale: settled ? 1 : 0.55 }}\n transition={settleT}\n >\n <IconProvider size=\"md\">{normalizeIcon(icon, 'md')}</IconProvider>\n </motion.span>\n <AnimatePresence>\n {!settled && (\n <motion.span\n className={cn(\n 'absolute inset-0 inline-flex items-center justify-center',\n toneBg[resolved.tone],\n toneFg[resolved.tone],\n )}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={fadeT}\n >\n <IconProvider size=\"md\">{normalizeIcon(resolved.icon, 'md')}</IconProvider>\n </motion.span>\n )}\n </AnimatePresence>\n </span>\n )\n}\n\nfunction FlashPresetGlyph({ preset }: { preset: FlashPreset }) {\n const { Glyph } = FLASH_PRESETS[preset]\n return <Glyph />\n}\n\nStatFlash.displayName = 'StatFlash'\n"],"mappings":";;;;;;;;;;AA8BA,IAAM,IAAsF;CAC1F,IAAI;EAAE,MAAM;EAAW,OAAO;EAAa;CAC3C,MAAM;EAAE,MAAM;EAAS,OAAO;EAAe;CAC7C,MAAM;EAAE,MAAM;EAAW,OAAO;EAAW;CAC3C,QAAQ;EAAE,MAAM;EAAU,OAAO;EAAU;CAC3C,OAAO;EAAE,MAAM;EAAW,OAAO;EAAmB;CACpD,MAAM;EAAE,MAAM;EAAQ,OAAO;EAAU;CACxC,EAEK,IAAoC;CACxC,SAAS;CACT,OAAO;CACP,SAAS;CACT,MAAM;CACN,QAAQ;CACT,EAEK,IAAoC;CACxC,SAAS;CACT,OAAO;CACP,SAAS;CACT,MAAM;CACN,QAAQ;CACT,EAKK,IAAuF;CAC3F,MAAM;EAAE,QAAQ;EAAK,QAAQ,EAAQ;EAAQ,MAAM,EAAO;EAAM;CAChE,QAAQ;EAAE,QAAQ;EAAK,QAAQ,EAAQ;EAAQ,MAAM,EAAO;EAAM;CAClE,MAAM;EAAE,QAAQ;EAAK,QAAQ,EAAQ;EAAQ,MAAM,EAAO;EAAS;CACpE;AA+BD,SAAgB,EAAU,EACxB,SACA,UACA,UAAO,QACP,WAAQ,UACR,WACA,qBACA,sBACiB;CACjB,IAAM,IAAiB,GAAkB,EACnC,IAAS,EAAO,IAChB,IAAO,KAAU,EAAO,QACxB,IAAU,KAAoB,EAAO,QACrC,IAAQ,KAAmB,EAAO,MAElC,IACJ,OAAO,KAAU,WACb;EAAE,MAAM,EAAc,GAAO;EAAM,MAAM,kBAAC,GAAD,EAAkB,QAAQ,GAAS,CAAA;EAAE,GAC9E,GAEA,CAAC,GAAS,KAAc,EAAM,SAAS,GAAM;AAWnD,QATA,EAAM,gBAAgB;AACpB,MAAI,GAAgB;AAClB,KAAW,GAAK;AAChB;;EAEF,IAAM,IAAI,iBAAiB,EAAW,GAAK,EAAE,EAAK;AAClD,eAAa,aAAa,EAAE;IAC3B,CAAC,GAAgB,EAAK,CAAC,EAGxB,kBAAC,QAAD;EACE,WAAW,EACT,4FACA,MAAS,UAAU,+BAA+B,6BACnD;EACD,eAAY;YALd,CAOE,kBAAC,EAAO,MAAR;GACE,WAAU;GACV,SAAS,IAAiB,KAAQ;IAAE,SAAS;IAAG,OAAO;IAAM;GAC7D,SAAS;IAAE,SAAS;IAAiB,OAAO,IAAU,IAAI;IAAM;GAChE,YAAY;aAEZ,kBAAC,GAAD;IAAc,MAAK;cAAM,EAAc,GAAM,KAAK;IAAgB,CAAA;GACtD,CAAA,EACd,kBAAC,GAAD,EAAA,UACG,CAAC,KACA,kBAAC,EAAO,MAAR;GACE,WAAW,EACT,4DACA,EAAO,EAAS,OAChB,EAAO,EAAS,MACjB;GACD,SAAS,EAAE,SAAS,GAAG;GACvB,SAAS,EAAE,SAAS,GAAG;GACvB,MAAM,EAAE,SAAS,GAAG;GACpB,YAAY;aAEZ,kBAAC,GAAD;IAAc,MAAK;cAAM,EAAc,EAAS,MAAM,KAAK;IAAgB,CAAA;GAC/D,CAAA,EAEA,CAAA,CACb;;;AAIX,SAAS,EAAiB,EAAE,aAAmC;CAC7D,IAAM,EAAE,aAAU,EAAc;AAChC,QAAO,kBAAC,GAAD,EAAS,CAAA;;AAGlB,EAAU,cAAc"}
|
package/dist/ui/tabs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/ui/tabs.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,KAAK,eAAe,GAAG,YAAY,GAAG,UAAU,CAAA;AAIhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,QAAA,MAAM,IAAI,mJAyCR,CAAA;AAGF,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAA;AAGjF,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAClC,KAAK,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAgBrC,QAAA,MAAM,gBAAgB;;;;8EAwBpB,CAAA;AAEF,QAAA,MAAM,mBAAmB;;;8EAuBxB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,aACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,EAC/D,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC,kFAAkF;IAClF,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,QAAA,MAAM,QAAQ,sFA6BZ,CAAA;AAGF,MAAM,WAAW,gBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,EAClE,YAAY,CAAC,OAAO,mBAAmB,CAAC;CAAG;AAa/C,QAAA,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/ui/tabs.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,KAAK,eAAe,GAAG,YAAY,GAAG,UAAU,CAAA;AAIhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,QAAA,MAAM,IAAI,mJAyCR,CAAA;AAGF,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAA;AAGjF,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAClC,KAAK,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAgBrC,QAAA,MAAM,gBAAgB;;;;8EAwBpB,CAAA;AAEF,QAAA,MAAM,mBAAmB;;;8EAuBxB,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,aACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,EAC/D,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC,kFAAkF;IAClF,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,QAAA,MAAM,QAAQ,sFA6BZ,CAAA;AAGF,MAAM,WAAW,gBACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,EAClE,YAAY,CAAC,OAAO,mBAAmB,CAAC;CAAG;AAa/C,QAAA,MAAM,WAAW,4FAsDf,CAAA;AAGF,QAAA,MAAM,WAAW,0JA0Bf,CAAA;AAGF,kFAAkF;AAClF,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,CAAA;AAE3F,OAAO,EAAE,IAAI,EAAE,WAAW,EAAC,QAAQ,EAAE,WAAW,EAAE,CAAA;AAClD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAC,QAAQ,EAAE,CAAA"}
|
package/dist/ui/tabs.js
CHANGED
|
@@ -135,8 +135,11 @@ var b = {
|
|
|
135
135
|
transition: i.smooth
|
|
136
136
|
}),
|
|
137
137
|
/* @__PURE__ */ l("span", {
|
|
138
|
-
className: "relative z-[1] inline-flex items-center gap-ds-02",
|
|
139
|
-
children:
|
|
138
|
+
className: "relative z-[1] inline-flex min-w-0 items-center gap-ds-02",
|
|
139
|
+
children: /* @__PURE__ */ l("span", {
|
|
140
|
+
className: "truncate",
|
|
141
|
+
children: r
|
|
142
|
+
})
|
|
140
143
|
}),
|
|
141
144
|
y === "line" && w && /* @__PURE__ */ l(f.span, {
|
|
142
145
|
layoutId: `${d.layoutId}-line`,
|
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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"}
|
package/dist/ui/toast.js
CHANGED
|
@@ -87,7 +87,7 @@ function k({ type: a, title: o, description: u, action: d, cancel: f, duration:
|
|
|
87
87
|
role: j ? "alert" : "status",
|
|
88
88
|
"aria-live": j ? "assertive" : "polite",
|
|
89
89
|
"aria-atomic": "true",
|
|
90
|
-
className: "group relative flex w-full overflow-hidden rounded-overlay-sm
|
|
90
|
+
className: "group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating",
|
|
91
91
|
onMouseEnter: () => _(!0),
|
|
92
92
|
onMouseLeave: () => _(!1),
|
|
93
93
|
onFocusCapture: () => y(!0),
|
|
@@ -370,7 +370,7 @@ function I({ files: r, onRetry: a, onRemove: o, selfDismissId: d }) {
|
|
|
370
370
|
role: j ? "alert" : "status",
|
|
371
371
|
"aria-live": j ? "assertive" : "polite",
|
|
372
372
|
"aria-label": "File uploads",
|
|
373
|
-
className: "group relative flex w-full overflow-hidden rounded-overlay-sm
|
|
373
|
+
className: "group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating",
|
|
374
374
|
onMouseEnter: () => m(!0),
|
|
375
375
|
onMouseLeave: () => m(!1),
|
|
376
376
|
onFocusCapture: () => g(!0),
|
package/dist/ui/toast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":[],"sources":["../../src/ui/toast.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertCircle,\n IconAlertTriangle,\n IconCheck,\n IconCircleCheck,\n IconCircleX,\n IconFile,\n IconInfoCircle,\n IconPhoto,\n IconRefresh,\n IconUpload,\n IconX,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { type ExternalToast,toast as sonnerToast } from 'sonner'\n\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Progress } from './progress'\nimport { Spinner } from './spinner'\nimport { assertToasterMounted } from './toast-registry'\nimport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n\n/* ---------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------ */\n\nconst DEFAULT_DURATION = 5000\nconst UNDO_DURATION = 8000\nconst UPLOAD_COMPLETE_DELAY = 3000\n\n/* ---------------------------------------------------------------------------\n * Accent bar & icon config per toast type\n * ------------------------------------------------------------------------ */\n\nconst TOAST_TYPE_CONFIG: Record<\n ToastType,\n {\n accentClass: string\n iconClass: string\n icon: React.ForwardRefExoticComponent<any> | null\n timerBarClass: string\n }\n> = {\n message: {\n accentClass: '',\n iconClass: '',\n icon: null,\n timerBarClass: 'bg-surface-border',\n },\n success: {\n accentClass: 'bg-success-9',\n iconClass: 'text-success-11',\n icon: IconCircleCheck,\n timerBarClass: 'bg-success-9',\n },\n error: {\n accentClass: 'bg-error-9',\n iconClass: 'text-error-11',\n icon: IconCircleX,\n timerBarClass: 'bg-error-9',\n },\n warning: {\n accentClass: 'bg-warning-9',\n iconClass: 'text-warning-11',\n icon: IconAlertTriangle,\n timerBarClass: 'bg-warning-9',\n },\n info: {\n accentClass: 'bg-info-9',\n iconClass: 'text-info-11',\n icon: IconInfoCircle,\n timerBarClass: 'bg-info-9',\n },\n loading: {\n accentClass: 'bg-accent-9',\n iconClass: 'text-accent-11',\n icon: null,\n timerBarClass: 'bg-accent-9',\n },\n}\n\n/* ---------------------------------------------------------------------------\n * Timer bar — shows auto-dismiss countdown\n * ------------------------------------------------------------------------ */\n\nfunction TimerBar({\n duration,\n type,\n paused,\n}: {\n duration: number\n type: ToastType\n paused: boolean\n}) {\n const config = TOAST_TYPE_CONFIG[type]\n return (\n <div className=\"absolute bottom-0 left-0 right-0 h-[2px] overflow-hidden\">\n <div\n className={cn(\n 'h-full w-full origin-left opacity-30',\n config.timerBarClass,\n 'motion-safe:animate-timer-bar',\n )}\n style={{\n animationDuration: `${duration}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }}\n />\n </div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Base toast UI — used by all typed toasts\n * ------------------------------------------------------------------------ */\n\nfunction ToastContent({\n type,\n title,\n description,\n action,\n cancel,\n duration = DEFAULT_DURATION,\n showTimerBar = true,\n selfDismissId,\n}: {\n type: ToastType\n title?: React.ReactNode\n description?: React.ReactNode\n action?: ToastActionOptions\n cancel?: ToastActionOptions\n duration?: number\n showTimerBar?: boolean\n /** When set, this component manages its own dismiss timer (hover-aware). */\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const config = TOAST_TYPE_CONFIG[type]\n const StatusIcon = config.icon\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(duration)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || duration === Infinity) return\n if (paused) {\n // Pause: save remaining time\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n // Start/resume timer\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, duration, paused])\n\n // Error toasts announce assertively so screen readers interrupt;\n // warnings stay polite but atomically re-read the message.\n const isUrgent = type === 'error'\n return (\n <motion.div\n layout=\"position\"\n role={isUrgent ? 'alert' : 'status'}\n aria-live={isUrgent ? 'assertive' : 'polite'}\n aria-atomic=\"true\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm border border-surface-border-strong bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n {config.accentClass && (\n <div\n className={cn('w-1 shrink-0 rounded-l-overlay-sm', config.accentClass)}\n />\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 items-start gap-ds-03 p-ds-04\">\n {/* Status icon — animates between spinner ↔ typed icon */}\n <AnimatePresence mode=\"wait\">\n {type === 'loading' ? (\n <motion.div\n key=\"loading\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n className=\"mt-0.5 shrink-0\"\n >\n <Spinner size=\"sm\" className=\"h-4 w-4\" />\n </motion.div>\n ) : StatusIcon ? (\n <motion.div\n key={type}\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n className=\"mt-0.5 shrink-0\"\n >\n <Icon icon={StatusIcon} size=\"sm\" className={config.iconClass} />\n </motion.div>\n ) : null}\n </AnimatePresence>\n\n {/* Text */}\n <div className=\"min-w-0 flex-1\">\n {title && (\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {title}\n </p>\n )}\n {description && (\n <p\n className={cn(\n 'text-ds-sm text-surface-fg-muted',\n title && 'mt-0.5',\n )}\n >\n {description}\n </p>\n )}\n\n {/* Action / cancel buttons */}\n {(action || cancel) && (\n <div className=\"mt-ds-03 flex items-center gap-ds-03\">\n {action && (\n <button\n type=\"button\"\n onClick={action.onClick}\n className=\"text-ds-sm font-medium text-accent-11 underline-offset-2 hover:underline hover:bg-surface-raised-hover rounded-control-inner px-ds-02 py-ds-01 transition-[color,background-color] duration-fast-01 ease-productive-standard focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {action.label}\n </button>\n )}\n {cancel && (\n <button\n type=\"button\"\n onClick={cancel.onClick}\n className=\"text-ds-sm text-surface-fg-muted hover:text-surface-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {cancel.label}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n\n {/* Timer bar */}\n {showTimerBar && type !== 'loading' && (\n <TimerBar duration={duration} type={type} paused={paused} />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Upload toast UI\n * ------------------------------------------------------------------------ */\n\nconst IMAGE_EXTENSIONS = new Set([\n 'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif',\n])\n\nfunction isImageFile(file: UploadFile): boolean {\n if (file.previewUrl) return true\n const ext = file.name.split('.').pop()?.toLowerCase() ?? ''\n return IMAGE_EXTENSIONS.has(ext)\n}\n\nfunction getProgressColor(\n status: UploadFile['status'],\n): 'default' | 'success' | 'error' {\n switch (status) {\n case 'complete':\n return 'success'\n case 'error':\n return 'error'\n default:\n return 'default'\n }\n}\n\nfunction getProgressValue(file: UploadFile): number | undefined {\n switch (file.status) {\n case 'pending':\n return 0\n case 'uploading':\n return file.progress ?? 0\n case 'processing':\n return undefined\n case 'complete':\n return 100\n case 'error':\n return file.progress ?? 0\n }\n}\n\nfunction formatFileSize(bytes: number): string {\n if (!bytes || bytes < 0 || !Number.isFinite(bytes)) return '0 B'\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`\n}\n\nfunction UploadFileRow({\n file,\n onRetry,\n onRemove,\n}: {\n file: UploadFile\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n}) {\n const isTerminal = file.status === 'complete' || file.status === 'error'\n const isUploading =\n file.status === 'uploading' || file.status === 'processing'\n\n return (\n <motion.div\n layout\n className=\"flex items-center gap-ds-02 py-1\"\n initial={false}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, x: -20, height: 0 }}\n transition={springs.snappy}\n >\n {/* File icon — animates between states */}\n <div className=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <AnimatePresence mode=\"wait\">\n {file.status === 'complete' ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"xs\" className=\"text-success-11\" />\n </motion.div>\n ) : file.status === 'error' ? (\n <motion.div\n key=\"error\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Icon icon={IconAlertCircle} size=\"xs\" className=\"text-error-11\" />\n </motion.div>\n ) : file.status === 'processing' ? (\n <motion.div\n key=\"processing\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Spinner size=\"sm\" className=\"h-3.5 w-3.5\" />\n </motion.div>\n ) : isImageFile(file) ? (\n <motion.div\n key=\"image\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconPhoto} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"file\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconFile} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n\n {/* Filename */}\n <span className=\"min-w-0 flex-1 truncate text-ds-xs text-surface-fg\">\n {file.name}\n </span>\n\n {/* Progress bar or status */}\n {isUploading ? (\n <div className=\"flex w-16 items-center gap-1\">\n <Progress\n size=\"sm\"\n color={getProgressColor(file.status)}\n value={getProgressValue(file)}\n className=\"flex-1\"\n />\n {file.progress !== undefined && (\n <span className=\"shrink-0 text-ds-xs tabular-nums text-surface-fg-muted\">\n {file.progress}%\n </span>\n )}\n </div>\n ) : file.status === 'complete' ? (\n <span className=\"text-ds-xs text-success-11\">Done</span>\n ) : file.status === 'error' ? (\n <span className=\"max-w-[60px] truncate text-ds-xs text-error-11\">\n {file.error || 'Failed'}\n </span>\n ) : (\n <span className=\"text-ds-xs text-surface-fg-muted\">\n {formatFileSize(file.size)}\n </span>\n )}\n\n {/* Actions */}\n {file.status === 'error' && onRetry && (\n <button\n type=\"button\"\n onClick={() => onRetry(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Retry ${file.name}`}\n title=\"Retry\"\n >\n <IconRefresh className=\"h-3.5 w-3.5\" />\n </button>\n )}\n {!isTerminal && onRemove && (\n <button\n type=\"button\"\n onClick={() => onRemove(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Cancel ${file.name}`}\n title=\"Remove\"\n >\n <IconX className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </motion.div>\n )\n}\n\nfunction UploadToastContent({\n files,\n onRetry,\n onRemove,\n selfDismissId,\n}: {\n files: UploadFile[]\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const completeCount = files.filter((f) => f.status === 'complete').length\n const errorCount = files.filter((f) => f.status === 'error').length\n const allTerminal = files.every(\n (f) => f.status === 'complete' || f.status === 'error',\n )\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(UPLOAD_COMPLETE_DELAY)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || !allTerminal) return\n if (paused) {\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, allTerminal, paused])\n\n const accentClass = allTerminal\n ? errorCount > 0\n ? 'bg-error-7'\n : 'bg-success-7'\n : 'bg-accent-9'\n\n const timerBarType: ToastType = allTerminal\n ? errorCount > 0\n ? 'error'\n : 'success'\n : 'loading'\n\n const uploadUrgent = allTerminal && errorCount > 0\n return (\n <motion.div\n layout\n role={uploadUrgent ? 'alert' : 'status'}\n aria-live={uploadUrgent ? 'assertive' : 'polite'}\n aria-label=\"File uploads\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm border border-surface-border-strong bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n <div className={cn('w-1 shrink-0 rounded-l-overlay-sm', accentClass)} />\n\n {/* Content */}\n <div className=\"min-w-0 flex-1 p-ds-04\">\n {/* Header — icon morphs between upload ↔ result */}\n <div className=\"flex items-center gap-ds-02\">\n <AnimatePresence mode=\"wait\">\n {allTerminal ? (\n errorCount > 0 ? (\n <motion.div\n key=\"error-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconAlertCircle} size=\"sm\" className=\"shrink-0 text-error-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"success-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"shrink-0 text-success-11\" />\n </motion.div>\n )\n ) : (\n <motion.div\n key=\"upload-icon\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"sm\" className=\"shrink-0 text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {allTerminal\n ? errorCount > 0\n ? `${completeCount} of ${files.length} uploaded`\n : `${files.length} file${files.length > 1 ? 's' : ''} uploaded`\n : `Uploading ${files.length} file${files.length > 1 ? 's' : ''}`}\n </p>\n </div>\n {!allTerminal && (\n <p className=\"mt-0.5 text-ds-xs text-surface-fg-muted\">\n {completeCount} of {files.length} complete\n {errorCount > 0 && ` · ${errorCount} failed`}\n </p>\n )}\n\n {/* File list */}\n <div className=\"mt-ds-02 max-h-[140px] overflow-y-auto\">\n <AnimatePresence>\n {files.map((file) => (\n <UploadFileRow\n key={file.id}\n file={file}\n onRetry={onRetry}\n onRemove={onRemove}\n />\n ))}\n </AnimatePresence>\n </div>\n\n {/* Screen reader announcement */}\n <div className=\"sr-only\" aria-live=\"polite\">\n {completeCount} of {files.length} files uploaded\n {errorCount > 0 && `, ${errorCount} failed`}\n </div>\n </div>\n\n {/* Timer bar — only after all terminal */}\n {allTerminal && (\n <TimerBar\n duration={UPLOAD_COMPLETE_DELAY}\n type={timerBarType}\n paused={false}\n />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Toast API — wraps Sonner with our styled UI\n * ------------------------------------------------------------------------ */\n\nfunction createSonnerOptions(\n options?: ToastOptions,\n duration?: number,\n): ExternalToast {\n return {\n id: options?.id,\n duration: options?.duration ?? duration ?? DEFAULT_DURATION,\n unstyled: true,\n classNames: { toast: 'w-full' },\n }\n}\n\nfunction createTypedToast(\n type: ToastType,\n message: string | React.ReactNode,\n options?: ToastOptions,\n) {\n assertToasterMounted()\n const duration = options?.duration ?? DEFAULT_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n description={options?.description}\n action={options?.action}\n cancel={options?.cancel}\n duration={duration}\n />\n ),\n createSonnerOptions(options, duration),\n )\n}\n\n/**\n * Toast API — imperative toast notifications with Sonner rendering engine.\n *\n * Mount `<Toaster />` once at your root layout. Then call `toast()` from anywhere.\n *\n * @example\n * toast('Something happened')\n * toast.success('Saved!')\n * toast.error('Failed to save')\n * toast.promise(saveFn(), { loading: 'Saving...', success: 'Done!', error: 'Failed' })\n * toast.undo('Deleted', { onUndo: () => restore() })\n * toast.upload({ files, onRetry, onRemove })\n */\nfunction toast(message: string | React.ReactNode, options?: ToastOptions) {\n return createTypedToast('message', message, options)\n}\n\ntoast.message = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('message', message, options)\n}\n\ntoast.success = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('success', message, options)\n}\n\ntoast.error = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('error', message, options)\n}\n\ntoast.warning = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('warning', message, options)\n}\n\ntoast.info = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('info', message, options)\n}\n\ntoast.loading = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('loading', message, {\n ...options,\n duration: options?.duration ?? Infinity,\n })\n}\n\ntoast.promise = <T,>(\n promise: Promise<T>,\n options: {\n loading: string | React.ReactNode\n success: string | React.ReactNode | ((data: T) => string | React.ReactNode)\n error:\n | string\n | React.ReactNode\n | ((error: unknown) => string | React.ReactNode)\n },\n) => {\n const id = sonnerToast.custom(\n () => (\n <ToastContent type=\"loading\" title={options.loading} showTimerBar={false} />\n ),\n { duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n\n const replaceWith = (type: ToastType, message: React.ReactNode) => {\n // Sonner doesn't reset its auto-dismiss timer when updating a custom toast\n // in-place from duration: Infinity, so ToastContent manages its own\n // hover-aware dismiss timer via selfDismissId.\n sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n duration={DEFAULT_DURATION}\n selfDismissId={id}\n />\n ),\n { id, duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n }\n\n promise\n .then((data) => {\n const message =\n typeof options.success === 'function'\n ? options.success(data)\n : options.success\n replaceWith('success', message)\n })\n .catch((error) => {\n const message =\n typeof options.error === 'function'\n ? options.error(error)\n : options.error\n replaceWith('error', message)\n })\n\n return id\n}\n\ntoast.undo = (message: string | React.ReactNode, options: ToastUndoOptions) => {\n const duration = options.duration ?? UNDO_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type=\"message\"\n title={message}\n description={options.description}\n action={{ label: 'Undo', onClick: options.onUndo }}\n duration={duration}\n />\n ),\n { duration, unstyled: true, classNames: { toast: 'w-full' } },\n )\n}\n\ntoast.upload = (options: ToastUploadOptions) => {\n // Pre-determine the id so we can pass it to UploadToastContent for self-dismiss.\n const toastId = options.id ?? String(Date.now())\n\n sonnerToast.custom(\n () => (\n <UploadToastContent\n files={options.files}\n onRetry={options.onRetry}\n onRemove={options.onRemove}\n selfDismissId={toastId}\n />\n ),\n {\n id: toastId,\n duration: Infinity,\n unstyled: true,\n classNames: { toast: 'w-full' },\n },\n )\n\n return toastId\n}\n\ntoast.custom = (\n render: (id: string | number) => React.ReactElement,\n options?: ExternalToast,\n) => {\n return sonnerToast.custom(render, {\n ...options,\n unstyled: true,\n classNames: { toast: 'w-full', ...(options?.classNames as Record<string, string>) },\n })\n}\n\ntoast.dismiss = (id?: string | number) => {\n if (id !== undefined) {\n sonnerToast.dismiss(id)\n } else {\n sonnerToast.dismiss()\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------ */\n\nexport type ToastProps = ToastOptions\n\nexport {\n formatFileSize,\n TimerBar,\n toast,\n ToastContent,\n UploadFileRow,\n UploadToastContent,\n}\nexport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toast.js","names":[],"sources":["../../src/ui/toast.tsx"],"sourcesContent":["'use client'\n\nimport {\n IconAlertCircle,\n IconAlertTriangle,\n IconCheck,\n IconCircleCheck,\n IconCircleX,\n IconFile,\n IconInfoCircle,\n IconPhoto,\n IconRefresh,\n IconUpload,\n IconX,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { type ExternalToast,toast as sonnerToast } from 'sonner'\n\nimport { Icon } from './icon'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Progress } from './progress'\nimport { Spinner } from './spinner'\nimport { assertToasterMounted } from './toast-registry'\nimport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n\n/* ---------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------ */\n\nconst DEFAULT_DURATION = 5000\nconst UNDO_DURATION = 8000\nconst UPLOAD_COMPLETE_DELAY = 3000\n\n/* ---------------------------------------------------------------------------\n * Accent bar & icon config per toast type\n * ------------------------------------------------------------------------ */\n\nconst TOAST_TYPE_CONFIG: Record<\n ToastType,\n {\n accentClass: string\n iconClass: string\n icon: React.ForwardRefExoticComponent<any> | null\n timerBarClass: string\n }\n> = {\n message: {\n accentClass: '',\n iconClass: '',\n icon: null,\n timerBarClass: 'bg-surface-border',\n },\n success: {\n accentClass: 'bg-success-9',\n iconClass: 'text-success-11',\n icon: IconCircleCheck,\n timerBarClass: 'bg-success-9',\n },\n error: {\n accentClass: 'bg-error-9',\n iconClass: 'text-error-11',\n icon: IconCircleX,\n timerBarClass: 'bg-error-9',\n },\n warning: {\n accentClass: 'bg-warning-9',\n iconClass: 'text-warning-11',\n icon: IconAlertTriangle,\n timerBarClass: 'bg-warning-9',\n },\n info: {\n accentClass: 'bg-info-9',\n iconClass: 'text-info-11',\n icon: IconInfoCircle,\n timerBarClass: 'bg-info-9',\n },\n loading: {\n accentClass: 'bg-accent-9',\n iconClass: 'text-accent-11',\n icon: null,\n timerBarClass: 'bg-accent-9',\n },\n}\n\n/* ---------------------------------------------------------------------------\n * Timer bar — shows auto-dismiss countdown\n * ------------------------------------------------------------------------ */\n\nfunction TimerBar({\n duration,\n type,\n paused,\n}: {\n duration: number\n type: ToastType\n paused: boolean\n}) {\n const config = TOAST_TYPE_CONFIG[type]\n return (\n <div className=\"absolute bottom-0 left-0 right-0 h-[2px] overflow-hidden\">\n <div\n className={cn(\n 'h-full w-full origin-left opacity-30',\n config.timerBarClass,\n 'motion-safe:animate-timer-bar',\n )}\n style={{\n animationDuration: `${duration}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }}\n />\n </div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Base toast UI — used by all typed toasts\n * ------------------------------------------------------------------------ */\n\nfunction ToastContent({\n type,\n title,\n description,\n action,\n cancel,\n duration = DEFAULT_DURATION,\n showTimerBar = true,\n selfDismissId,\n}: {\n type: ToastType\n title?: React.ReactNode\n description?: React.ReactNode\n action?: ToastActionOptions\n cancel?: ToastActionOptions\n duration?: number\n showTimerBar?: boolean\n /** When set, this component manages its own dismiss timer (hover-aware). */\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const config = TOAST_TYPE_CONFIG[type]\n const StatusIcon = config.icon\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(duration)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || duration === Infinity) return\n if (paused) {\n // Pause: save remaining time\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n // Start/resume timer\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, duration, paused])\n\n // Error toasts announce assertively so screen readers interrupt;\n // warnings stay polite but atomically re-read the message.\n const isUrgent = type === 'error'\n return (\n <motion.div\n layout=\"position\"\n role={isUrgent ? 'alert' : 'status'}\n aria-live={isUrgent ? 'assertive' : 'polite'}\n aria-atomic=\"true\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n {config.accentClass && (\n <div\n className={cn('w-1 shrink-0 rounded-l-overlay-sm', config.accentClass)}\n />\n )}\n\n {/* Content */}\n <div className=\"flex min-w-0 flex-1 items-start gap-ds-03 p-ds-04\">\n {/* Status icon — animates between spinner ↔ typed icon */}\n <AnimatePresence mode=\"wait\">\n {type === 'loading' ? (\n <motion.div\n key=\"loading\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n className=\"mt-0.5 shrink-0\"\n >\n <Spinner size=\"sm\" className=\"h-4 w-4\" />\n </motion.div>\n ) : StatusIcon ? (\n <motion.div\n key={type}\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n className=\"mt-0.5 shrink-0\"\n >\n <Icon icon={StatusIcon} size=\"sm\" className={config.iconClass} />\n </motion.div>\n ) : null}\n </AnimatePresence>\n\n {/* Text */}\n <div className=\"min-w-0 flex-1\">\n {title && (\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {title}\n </p>\n )}\n {description && (\n <p\n className={cn(\n 'text-ds-sm text-surface-fg-muted',\n title && 'mt-0.5',\n )}\n >\n {description}\n </p>\n )}\n\n {/* Action / cancel buttons */}\n {(action || cancel) && (\n <div className=\"mt-ds-03 flex items-center gap-ds-03\">\n {action && (\n <button\n type=\"button\"\n onClick={action.onClick}\n className=\"text-ds-sm font-medium text-accent-11 underline-offset-2 hover:underline hover:bg-surface-raised-hover rounded-control-inner px-ds-02 py-ds-01 transition-[color,background-color] duration-fast-01 ease-productive-standard focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {action.label}\n </button>\n )}\n {cancel && (\n <button\n type=\"button\"\n onClick={cancel.onClick}\n className=\"text-ds-sm text-surface-fg-muted hover:text-surface-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:rounded-control-inner\"\n >\n {cancel.label}\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n\n {/* Timer bar */}\n {showTimerBar && type !== 'loading' && (\n <TimerBar duration={duration} type={type} paused={paused} />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Upload toast UI\n * ------------------------------------------------------------------------ */\n\nconst IMAGE_EXTENSIONS = new Set([\n 'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif',\n])\n\nfunction isImageFile(file: UploadFile): boolean {\n if (file.previewUrl) return true\n const ext = file.name.split('.').pop()?.toLowerCase() ?? ''\n return IMAGE_EXTENSIONS.has(ext)\n}\n\nfunction getProgressColor(\n status: UploadFile['status'],\n): 'default' | 'success' | 'error' {\n switch (status) {\n case 'complete':\n return 'success'\n case 'error':\n return 'error'\n default:\n return 'default'\n }\n}\n\nfunction getProgressValue(file: UploadFile): number | undefined {\n switch (file.status) {\n case 'pending':\n return 0\n case 'uploading':\n return file.progress ?? 0\n case 'processing':\n return undefined\n case 'complete':\n return 100\n case 'error':\n return file.progress ?? 0\n }\n}\n\nfunction formatFileSize(bytes: number): string {\n if (!bytes || bytes < 0 || !Number.isFinite(bytes)) return '0 B'\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`\n}\n\nfunction UploadFileRow({\n file,\n onRetry,\n onRemove,\n}: {\n file: UploadFile\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n}) {\n const isTerminal = file.status === 'complete' || file.status === 'error'\n const isUploading =\n file.status === 'uploading' || file.status === 'processing'\n\n return (\n <motion.div\n layout\n className=\"flex items-center gap-ds-02 py-1\"\n initial={false}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, x: -20, height: 0 }}\n transition={springs.snappy}\n >\n {/* File icon — animates between states */}\n <div className=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <AnimatePresence mode=\"wait\">\n {file.status === 'complete' ? (\n <motion.div\n key=\"complete\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"xs\" className=\"text-success-11\" />\n </motion.div>\n ) : file.status === 'error' ? (\n <motion.div\n key=\"error\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={tweens.fade}\n >\n <Icon icon={IconAlertCircle} size=\"xs\" className=\"text-error-11\" />\n </motion.div>\n ) : file.status === 'processing' ? (\n <motion.div\n key=\"processing\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Spinner size=\"sm\" className=\"h-3.5 w-3.5\" />\n </motion.div>\n ) : isImageFile(file) ? (\n <motion.div\n key=\"image\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconPhoto} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"file\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconFile} size=\"xs\" className=\"text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n\n {/* Filename */}\n <span className=\"min-w-0 flex-1 truncate text-ds-xs text-surface-fg\">\n {file.name}\n </span>\n\n {/* Progress bar or status */}\n {isUploading ? (\n <div className=\"flex w-16 items-center gap-1\">\n <Progress\n size=\"sm\"\n color={getProgressColor(file.status)}\n value={getProgressValue(file)}\n className=\"flex-1\"\n />\n {file.progress !== undefined && (\n <span className=\"shrink-0 text-ds-xs tabular-nums text-surface-fg-muted\">\n {file.progress}%\n </span>\n )}\n </div>\n ) : file.status === 'complete' ? (\n <span className=\"text-ds-xs text-success-11\">Done</span>\n ) : file.status === 'error' ? (\n <span className=\"max-w-[60px] truncate text-ds-xs text-error-11\">\n {file.error || 'Failed'}\n </span>\n ) : (\n <span className=\"text-ds-xs text-surface-fg-muted\">\n {formatFileSize(file.size)}\n </span>\n )}\n\n {/* Actions */}\n {file.status === 'error' && onRetry && (\n <button\n type=\"button\"\n onClick={() => onRetry(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Retry ${file.name}`}\n title=\"Retry\"\n >\n <IconRefresh className=\"h-3.5 w-3.5\" />\n </button>\n )}\n {!isTerminal && onRemove && (\n <button\n type=\"button\"\n onClick={() => onRemove(file.id)}\n className=\"touch-target flex min-h-6 min-w-6 items-center justify-center rounded-control-inner text-surface-fg-muted hover:text-surface-fg\"\n aria-label={`Cancel ${file.name}`}\n title=\"Remove\"\n >\n <IconX className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </motion.div>\n )\n}\n\nfunction UploadToastContent({\n files,\n onRetry,\n onRemove,\n selfDismissId,\n}: {\n files: UploadFile[]\n onRetry?: (fileId: string) => void\n onRemove?: (fileId: string) => void\n selfDismissId?: string | number\n}) {\n const [hovered, setHovered] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const paused = hovered || focused\n const completeCount = files.filter((f) => f.status === 'complete').length\n const errorCount = files.filter((f) => f.status === 'error').length\n const allTerminal = files.every(\n (f) => f.status === 'complete' || f.status === 'error',\n )\n\n // Self-managed dismiss timer that pauses on hover or keyboard focus\n const remainingRef = React.useRef(UPLOAD_COMPLETE_DELAY)\n const startTimeRef = React.useRef(0)\n\n React.useEffect(() => {\n if (selfDismissId == null || !allTerminal) return\n if (paused) {\n const elapsed = Date.now() - startTimeRef.current\n remainingRef.current = Math.max(0, remainingRef.current - elapsed)\n return\n }\n startTimeRef.current = Date.now()\n const timer = setTimeout(() => {\n sonnerToast.dismiss(selfDismissId)\n }, remainingRef.current)\n return () => clearTimeout(timer)\n }, [selfDismissId, allTerminal, paused])\n\n const accentClass = allTerminal\n ? errorCount > 0\n ? 'bg-error-7'\n : 'bg-success-7'\n : 'bg-accent-9'\n\n const timerBarType: ToastType = allTerminal\n ? errorCount > 0\n ? 'error'\n : 'success'\n : 'loading'\n\n const uploadUrgent = allTerminal && errorCount > 0\n return (\n <motion.div\n layout\n role={uploadUrgent ? 'alert' : 'status'}\n aria-live={uploadUrgent ? 'assertive' : 'polite'}\n aria-label=\"File uploads\"\n className=\"group relative flex w-full overflow-hidden rounded-overlay-sm bg-surface-overlay shadow-floating\"\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onFocusCapture={() => setFocused(true)}\n onBlurCapture={() => setFocused(false)}\n transition={springs.smooth}\n >\n {/* Left accent bar */}\n <div className={cn('w-1 shrink-0 rounded-l-overlay-sm', accentClass)} />\n\n {/* Content */}\n <div className=\"min-w-0 flex-1 p-ds-04\">\n {/* Header — icon morphs between upload ↔ result */}\n <div className=\"flex items-center gap-ds-02\">\n <AnimatePresence mode=\"wait\">\n {allTerminal ? (\n errorCount > 0 ? (\n <motion.div\n key=\"error-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconAlertCircle} size=\"sm\" className=\"shrink-0 text-error-11\" />\n </motion.div>\n ) : (\n <motion.div\n key=\"success-icon\"\n initial={{ opacity: 0, scale: 0.5 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.5 }}\n transition={springs.bouncy}\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"shrink-0 text-success-11\" />\n </motion.div>\n )\n ) : (\n <motion.div\n key=\"upload-icon\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n >\n <Icon icon={IconUpload} size=\"sm\" className=\"shrink-0 text-surface-fg-muted\" />\n </motion.div>\n )}\n </AnimatePresence>\n <p className=\"text-ds-md font-semibold text-surface-fg\">\n {allTerminal\n ? errorCount > 0\n ? `${completeCount} of ${files.length} uploaded`\n : `${files.length} file${files.length > 1 ? 's' : ''} uploaded`\n : `Uploading ${files.length} file${files.length > 1 ? 's' : ''}`}\n </p>\n </div>\n {!allTerminal && (\n <p className=\"mt-0.5 text-ds-xs text-surface-fg-muted\">\n {completeCount} of {files.length} complete\n {errorCount > 0 && ` · ${errorCount} failed`}\n </p>\n )}\n\n {/* File list */}\n <div className=\"mt-ds-02 max-h-[140px] overflow-y-auto\">\n <AnimatePresence>\n {files.map((file) => (\n <UploadFileRow\n key={file.id}\n file={file}\n onRetry={onRetry}\n onRemove={onRemove}\n />\n ))}\n </AnimatePresence>\n </div>\n\n {/* Screen reader announcement */}\n <div className=\"sr-only\" aria-live=\"polite\">\n {completeCount} of {files.length} files uploaded\n {errorCount > 0 && `, ${errorCount} failed`}\n </div>\n </div>\n\n {/* Timer bar — only after all terminal */}\n {allTerminal && (\n <TimerBar\n duration={UPLOAD_COMPLETE_DELAY}\n type={timerBarType}\n paused={false}\n />\n )}\n </motion.div>\n )\n}\n\n/* ---------------------------------------------------------------------------\n * Toast API — wraps Sonner with our styled UI\n * ------------------------------------------------------------------------ */\n\nfunction createSonnerOptions(\n options?: ToastOptions,\n duration?: number,\n): ExternalToast {\n return {\n id: options?.id,\n duration: options?.duration ?? duration ?? DEFAULT_DURATION,\n unstyled: true,\n classNames: { toast: 'w-full' },\n }\n}\n\nfunction createTypedToast(\n type: ToastType,\n message: string | React.ReactNode,\n options?: ToastOptions,\n) {\n assertToasterMounted()\n const duration = options?.duration ?? DEFAULT_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n description={options?.description}\n action={options?.action}\n cancel={options?.cancel}\n duration={duration}\n />\n ),\n createSonnerOptions(options, duration),\n )\n}\n\n/**\n * Toast API — imperative toast notifications with Sonner rendering engine.\n *\n * Mount `<Toaster />` once at your root layout. Then call `toast()` from anywhere.\n *\n * @example\n * toast('Something happened')\n * toast.success('Saved!')\n * toast.error('Failed to save')\n * toast.promise(saveFn(), { loading: 'Saving...', success: 'Done!', error: 'Failed' })\n * toast.undo('Deleted', { onUndo: () => restore() })\n * toast.upload({ files, onRetry, onRemove })\n */\nfunction toast(message: string | React.ReactNode, options?: ToastOptions) {\n return createTypedToast('message', message, options)\n}\n\ntoast.message = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('message', message, options)\n}\n\ntoast.success = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('success', message, options)\n}\n\ntoast.error = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('error', message, options)\n}\n\ntoast.warning = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('warning', message, options)\n}\n\ntoast.info = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('info', message, options)\n}\n\ntoast.loading = (\n message: string | React.ReactNode,\n options?: ToastOptions,\n) => {\n return createTypedToast('loading', message, {\n ...options,\n duration: options?.duration ?? Infinity,\n })\n}\n\ntoast.promise = <T,>(\n promise: Promise<T>,\n options: {\n loading: string | React.ReactNode\n success: string | React.ReactNode | ((data: T) => string | React.ReactNode)\n error:\n | string\n | React.ReactNode\n | ((error: unknown) => string | React.ReactNode)\n },\n) => {\n const id = sonnerToast.custom(\n () => (\n <ToastContent type=\"loading\" title={options.loading} showTimerBar={false} />\n ),\n { duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n\n const replaceWith = (type: ToastType, message: React.ReactNode) => {\n // Sonner doesn't reset its auto-dismiss timer when updating a custom toast\n // in-place from duration: Infinity, so ToastContent manages its own\n // hover-aware dismiss timer via selfDismissId.\n sonnerToast.custom(\n () => (\n <ToastContent\n type={type}\n title={message}\n duration={DEFAULT_DURATION}\n selfDismissId={id}\n />\n ),\n { id, duration: Infinity, unstyled: true, classNames: { toast: 'w-full' } },\n )\n }\n\n promise\n .then((data) => {\n const message =\n typeof options.success === 'function'\n ? options.success(data)\n : options.success\n replaceWith('success', message)\n })\n .catch((error) => {\n const message =\n typeof options.error === 'function'\n ? options.error(error)\n : options.error\n replaceWith('error', message)\n })\n\n return id\n}\n\ntoast.undo = (message: string | React.ReactNode, options: ToastUndoOptions) => {\n const duration = options.duration ?? UNDO_DURATION\n return sonnerToast.custom(\n () => (\n <ToastContent\n type=\"message\"\n title={message}\n description={options.description}\n action={{ label: 'Undo', onClick: options.onUndo }}\n duration={duration}\n />\n ),\n { duration, unstyled: true, classNames: { toast: 'w-full' } },\n )\n}\n\ntoast.upload = (options: ToastUploadOptions) => {\n // Pre-determine the id so we can pass it to UploadToastContent for self-dismiss.\n const toastId = options.id ?? String(Date.now())\n\n sonnerToast.custom(\n () => (\n <UploadToastContent\n files={options.files}\n onRetry={options.onRetry}\n onRemove={options.onRemove}\n selfDismissId={toastId}\n />\n ),\n {\n id: toastId,\n duration: Infinity,\n unstyled: true,\n classNames: { toast: 'w-full' },\n },\n )\n\n return toastId\n}\n\ntoast.custom = (\n render: (id: string | number) => React.ReactElement,\n options?: ExternalToast,\n) => {\n return sonnerToast.custom(render, {\n ...options,\n unstyled: true,\n classNames: { toast: 'w-full', ...(options?.classNames as Record<string, string>) },\n })\n}\n\ntoast.dismiss = (id?: string | number) => {\n if (id !== undefined) {\n sonnerToast.dismiss(id)\n } else {\n sonnerToast.dismiss()\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------ */\n\nexport type ToastProps = ToastOptions\n\nexport {\n formatFileSize,\n TimerBar,\n toast,\n ToastContent,\n UploadFileRow,\n UploadToastContent,\n}\nexport type {\n ToastActionOptions,\n ToastOptions,\n ToastType,\n ToastUndoOptions,\n ToastUploadOptions,\n UploadFile,\n} from './toast-types'\n"],"mappings":"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"}
|