@devalok/shilp-sutra 0.38.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/MIGRATION.md +118 -0
  2. package/dist/_chunks/badge-group.js +4 -4
  3. package/dist/_chunks/badge-group.js.map +1 -1
  4. package/dist/_chunks/chat.js +14 -14
  5. package/dist/_chunks/chat.js.map +1 -1
  6. package/dist/_chunks/date-picker.js +9 -9
  7. package/dist/_chunks/date-picker.js.map +1 -1
  8. package/dist/_chunks/document-preview.js +2 -2
  9. package/dist/_chunks/document-preview.js.map +1 -1
  10. package/dist/_chunks/file-preview.js +11 -11
  11. package/dist/_chunks/file-preview.js.map +1 -1
  12. package/dist/_chunks/image-preview.js +2 -2
  13. package/dist/_chunks/image-preview.js.map +1 -1
  14. package/dist/_chunks/mention-suggestion.js +5 -5
  15. package/dist/_chunks/mention-suggestion.js.map +1 -1
  16. package/dist/_chunks/popover.js +3 -3
  17. package/dist/_chunks/popover.js.map +1 -1
  18. package/dist/_chunks/rich-chat-input.js +33 -33
  19. package/dist/_chunks/rich-chat-input.js.map +1 -1
  20. package/dist/_chunks/shared.js +5 -5
  21. package/dist/_chunks/shared.js.map +1 -1
  22. package/dist/_chunks/text.js +2 -2
  23. package/dist/_chunks/text.js.map +1 -1
  24. package/dist/_chunks/tree-view.js +1 -1
  25. package/dist/_chunks/tree-view.js.map +1 -1
  26. package/dist/_chunks/use-calendar.js +5 -5
  27. package/dist/_chunks/use-calendar.js.map +1 -1
  28. package/dist/ai/command-bar.js +10 -10
  29. package/dist/ai/command-bar.js.map +1 -1
  30. package/dist/ai/conversation.js +4 -4
  31. package/dist/ai/conversation.js.map +1 -1
  32. package/dist/composed/activity-feed.js +5 -5
  33. package/dist/composed/activity-feed.js.map +1 -1
  34. package/dist/composed/avatar-group.js +4 -4
  35. package/dist/composed/avatar-group.js.map +1 -1
  36. package/dist/composed/bulk-action-bar.js +1 -1
  37. package/dist/composed/bulk-action-bar.js.map +1 -1
  38. package/dist/composed/command-palette.js +5 -5
  39. package/dist/composed/command-palette.js.map +1 -1
  40. package/dist/composed/content-card.js +1 -1
  41. package/dist/composed/content-card.js.map +1 -1
  42. package/dist/composed/emoji-picker.js +4 -4
  43. package/dist/composed/emoji-picker.js.map +1 -1
  44. package/dist/composed/empty-state.js +1 -1
  45. package/dist/composed/empty-state.js.map +1 -1
  46. package/dist/composed/error-boundary.js +3 -3
  47. package/dist/composed/error-boundary.js.map +1 -1
  48. package/dist/composed/filter-bar.js +1 -1
  49. package/dist/composed/filter-bar.js.map +1 -1
  50. package/dist/composed/inline-edit.js +1 -1
  51. package/dist/composed/inline-edit.js.map +1 -1
  52. package/dist/composed/loading-skeleton.js +9 -9
  53. package/dist/composed/loading-skeleton.js.map +1 -1
  54. package/dist/composed/markdown-viewer.js +3 -3
  55. package/dist/composed/markdown-viewer.js.map +1 -1
  56. package/dist/composed/multi-select-popover.js +1 -1
  57. package/dist/composed/multi-select-popover.js.map +1 -1
  58. package/dist/composed/page-header.js +1 -1
  59. package/dist/composed/page-header.js.map +1 -1
  60. package/dist/composed/page-skeletons.js +15 -15
  61. package/dist/composed/page-skeletons.js.map +1 -1
  62. package/dist/composed/priority-indicator.js +3 -3
  63. package/dist/composed/priority-indicator.js.map +1 -1
  64. package/dist/composed/rich-text-editor.js +11 -11
  65. package/dist/composed/rich-text-editor.js.map +1 -1
  66. package/dist/composed/schedule-view.js +3 -3
  67. package/dist/composed/schedule-view.js.map +1 -1
  68. package/dist/composed/status-badge.js +3 -3
  69. package/dist/composed/status-badge.js.map +1 -1
  70. package/dist/shell/bottom-navbar.js +6 -6
  71. package/dist/shell/bottom-navbar.js.map +1 -1
  72. package/dist/shell/notification-center.js +8 -8
  73. package/dist/shell/notification-center.js.map +1 -1
  74. package/dist/shell/notification-preferences.js +1 -1
  75. package/dist/shell/notification-preferences.js.map +1 -1
  76. package/dist/shell/sidebar.js +6 -6
  77. package/dist/shell/sidebar.js.map +1 -1
  78. package/dist/shell/top-bar.js +4 -4
  79. package/dist/shell/top-bar.js.map +1 -1
  80. package/dist/tokens/semantic.css +82 -9
  81. package/dist/ui/accordion.js +1 -1
  82. package/dist/ui/accordion.js.map +1 -1
  83. package/dist/ui/alert-dialog.js +3 -3
  84. package/dist/ui/alert-dialog.js.map +1 -1
  85. package/dist/ui/alert.js +2 -2
  86. package/dist/ui/alert.js.map +1 -1
  87. package/dist/ui/autocomplete.js +2 -2
  88. package/dist/ui/autocomplete.js.map +1 -1
  89. package/dist/ui/avatar.js +11 -11
  90. package/dist/ui/avatar.js.map +1 -1
  91. package/dist/ui/badge-indicator.js +1 -1
  92. package/dist/ui/badge-indicator.js.map +1 -1
  93. package/dist/ui/banner.js +1 -1
  94. package/dist/ui/banner.js.map +1 -1
  95. package/dist/ui/breadcrumb.js +1 -1
  96. package/dist/ui/breadcrumb.js.map +1 -1
  97. package/dist/ui/button.d.ts +2 -2
  98. package/dist/ui/button.js +14 -14
  99. package/dist/ui/button.js.map +1 -1
  100. package/dist/ui/card.js +5 -5
  101. package/dist/ui/card.js.map +1 -1
  102. package/dist/ui/charts/index.js +5 -5
  103. package/dist/ui/charts/index.js.map +1 -1
  104. package/dist/ui/checkbox.js +1 -1
  105. package/dist/ui/checkbox.js.map +1 -1
  106. package/dist/ui/code.js +2 -2
  107. package/dist/ui/code.js.map +1 -1
  108. package/dist/ui/color-input.js +10 -10
  109. package/dist/ui/color-input.js.map +1 -1
  110. package/dist/ui/color-swatch.js +3 -3
  111. package/dist/ui/color-swatch.js.map +1 -1
  112. package/dist/ui/combobox.js +5 -5
  113. package/dist/ui/combobox.js.map +1 -1
  114. package/dist/ui/context-menu.js +6 -6
  115. package/dist/ui/context-menu.js.map +1 -1
  116. package/dist/ui/data-table-body.js +1 -1
  117. package/dist/ui/data-table-body.js.map +1 -1
  118. package/dist/ui/data-table-bulk-actions.js +2 -2
  119. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  120. package/dist/ui/data-table-card.js +2 -2
  121. package/dist/ui/data-table-card.js.map +1 -1
  122. package/dist/ui/data-table-header.js +2 -2
  123. package/dist/ui/data-table-header.js.map +1 -1
  124. package/dist/ui/data-table-pagination.js +3 -3
  125. package/dist/ui/data-table-pagination.js.map +1 -1
  126. package/dist/ui/data-table-toolbar.js +1 -1
  127. package/dist/ui/data-table-toolbar.js.map +1 -1
  128. package/dist/ui/data-table.js +1 -1
  129. package/dist/ui/data-table.js.map +1 -1
  130. package/dist/ui/devalok-grain.d.ts +1 -1
  131. package/dist/ui/devalok-grain.js.map +1 -1
  132. package/dist/ui/dialog.js +2 -2
  133. package/dist/ui/dialog.js.map +1 -1
  134. package/dist/ui/dropdown-menu.js +6 -6
  135. package/dist/ui/dropdown-menu.js.map +1 -1
  136. package/dist/ui/file-upload.js +4 -4
  137. package/dist/ui/file-upload.js.map +1 -1
  138. package/dist/ui/hover-card.js +1 -1
  139. package/dist/ui/hover-card.js.map +1 -1
  140. package/dist/ui/icon-button.js +1 -1
  141. package/dist/ui/icon-button.js.map +1 -1
  142. package/dist/ui/input-otp.js +1 -1
  143. package/dist/ui/input-otp.js.map +1 -1
  144. package/dist/ui/input.js +2 -2
  145. package/dist/ui/input.js.map +1 -1
  146. package/dist/ui/link.js +1 -1
  147. package/dist/ui/link.js.map +1 -1
  148. package/dist/ui/menubar.js +8 -8
  149. package/dist/ui/menubar.js.map +1 -1
  150. package/dist/ui/navigation-menu.js +3 -3
  151. package/dist/ui/navigation-menu.js.map +1 -1
  152. package/dist/ui/number-input.js +3 -3
  153. package/dist/ui/number-input.js.map +1 -1
  154. package/dist/ui/pagination.js +1 -1
  155. package/dist/ui/pagination.js.map +1 -1
  156. package/dist/ui/progress.js +1 -1
  157. package/dist/ui/progress.js.map +1 -1
  158. package/dist/ui/radio.js +1 -1
  159. package/dist/ui/radio.js.map +1 -1
  160. package/dist/ui/segmented-control.js +3 -3
  161. package/dist/ui/segmented-control.js.map +1 -1
  162. package/dist/ui/select.js +3 -3
  163. package/dist/ui/select.js.map +1 -1
  164. package/dist/ui/sheet.js +2 -2
  165. package/dist/ui/sheet.js.map +1 -1
  166. package/dist/ui/sidebar.js +11 -11
  167. package/dist/ui/sidebar.js.map +1 -1
  168. package/dist/ui/skeleton.js +9 -9
  169. package/dist/ui/skeleton.js.map +1 -1
  170. package/dist/ui/slider.js +2 -2
  171. package/dist/ui/slider.js.map +1 -1
  172. package/dist/ui/split-button.js +7 -7
  173. package/dist/ui/split-button.js.map +1 -1
  174. package/dist/ui/stat-card.js +7 -7
  175. package/dist/ui/stat-card.js.map +1 -1
  176. package/dist/ui/status-dot.js +2 -2
  177. package/dist/ui/status-dot.js.map +1 -1
  178. package/dist/ui/stepper.js +2 -2
  179. package/dist/ui/stepper.js.map +1 -1
  180. package/dist/ui/switch.js +2 -2
  181. package/dist/ui/switch.js.map +1 -1
  182. package/dist/ui/tabs.js +3 -3
  183. package/dist/ui/tabs.js.map +1 -1
  184. package/dist/ui/textarea.js +1 -1
  185. package/dist/ui/textarea.js.map +1 -1
  186. package/dist/ui/toast.js +8 -8
  187. package/dist/ui/toast.js.map +1 -1
  188. package/dist/ui/toggle.js +1 -1
  189. package/dist/ui/toggle.js.map +1 -1
  190. package/dist/ui/tooltip.js +1 -1
  191. package/dist/ui/tooltip.js.map +1 -1
  192. package/docs/components/_header.md +90 -1
  193. package/docs/recipes/customize-brand.md +85 -4
  194. package/docs/recipes/index.md +1 -1
  195. package/llms-full.txt +91 -2
  196. package/llms.txt +9 -1
  197. package/package.json +2 -1
  198. package/skill/README.md +99 -0
  199. package/skill/SKILL.md +144 -0
  200. package/skill/install.sh +59 -0
  201. package/skill/references/components-full.md +6997 -0
  202. package/skill/references/components.md +673 -0
  203. package/skill/references/customize-brand.md +299 -0
  204. package/skill/references/server-components.md +211 -0
  205. package/skill/references/setup-astro.md +180 -0
  206. package/skill/references/setup-next-app-router.md +232 -0
  207. package/skill/references/setup-next-pages.md +125 -0
  208. package/skill/references/setup-remix.md +173 -0
  209. package/skill/references/setup-tanstack-start.md +145 -0
  210. package/skill/references/setup-vite.md +172 -0
  211. package/skill/references/troubleshoot.md +219 -0
  212. /package/{LICENSE → skill/LICENSE} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"skeleton.js","names":[],"sources":["../../src/ui/skeleton.tsx"],"sourcesContent":["// @server-safe\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\nconst skeletonVariants = cva('bg-skeleton-base', {\n variants: {\n variant: {\n rectangle: 'rounded-ds-md',\n circle: 'rounded-ds-full aspect-square',\n text: 'rounded-ds-sm h-4 w-full',\n },\n animation: {\n pulse: 'animate-pulse motion-reduce:animate-none',\n shimmer:\n 'bg-[length:200%_100%] bg-linear-to-r from-skeleton-base via-skeleton-shimmer to-skeleton-base animate-skeleton-shimmer [background-attachment:fixed] motion-reduce:animate-none',\n none: '',\n },\n },\n defaultVariants: {\n variant: 'rectangle',\n animation: 'pulse',\n },\n})\n\n/**\n * Props for Skeleton — a loading placeholder with shape variants and two animation styles.\n *\n * **Shape variants:** `rectangle` (default, rounded corners) | `circle` (aspect-square pill) |\n * `text` (full-width slim bar, for text line placeholders)\n *\n * **Animation:** `pulse` (default, opacity fade) | `shimmer` (left-to-right gradient sweep,\n * respects `prefers-reduced-motion`) | `none` (static, for testing or paused states)\n *\n * @example\n * // Text line placeholder (repeatable for a paragraph skeleton):\n * <Skeleton variant=\"text\" />\n * <Skeleton variant=\"text\" className=\"w-3/4\" />\n *\n * @example\n * // Avatar + name card loading state:\n * <div className=\"flex items-center gap-ds-04\">\n * <Skeleton variant=\"circle\" className=\"h-ds-md w-ds-md\" />\n * <div className=\"flex flex-col gap-ds-02 flex-1\">\n * <Skeleton variant=\"text\" className=\"w-1/2\" />\n * <Skeleton variant=\"text\" className=\"w-1/3\" />\n * </div>\n * </div>\n *\n * @example\n * // Card placeholder with shimmer animation:\n * <Skeleton variant=\"rectangle\" animation=\"shimmer\" className=\"h-48 w-full\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface SkeletonProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof skeletonVariants> {}\n\nconst Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(\n ({ className, variant, animation, ...props }, ref) => {\n return (\n <div\n ref={ref}\n aria-hidden=\"true\"\n className={cn(skeletonVariants({ variant, animation }), className)}\n {...props}\n />\n )\n },\n)\nSkeleton.displayName = 'Skeleton'\n\n// ---------------------------------------------------------------------------\n// Animation helper — resolves animation classes for sub-components\n// ---------------------------------------------------------------------------\n\nconst animationClasses = {\n pulse: 'animate-pulse',\n shimmer:\n 'bg-[length:200%_100%] bg-linear-to-r from-skeleton-base via-skeleton-shimmer to-skeleton-base animate-skeleton-shimmer motion-reduce:animate-none',\n none: '',\n} as const\n\ntype SkeletonAnimation = 'pulse' | 'shimmer' | 'none'\n\n// ---------------------------------------------------------------------------\n// SkeletonAvatar\n// ---------------------------------------------------------------------------\n\nconst avatarSizeClasses = {\n sm: 'h-8 w-8',\n md: 'h-10 w-10',\n lg: 'h-12 w-12',\n xl: 'h-16 w-16',\n} as const\n\nexport interface SkeletonAvatarProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'md' | 'lg' | 'xl'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonAvatar = React.forwardRef<HTMLDivElement, SkeletonAvatarProps>(\n ({ className, size = 'md', animation = 'pulse', ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-skeleton-base rounded-ds-full',\n avatarSizeClasses[size],\n animationClasses[animation],\n className,\n )}\n {...props}\n />\n )\n },\n)\nSkeletonAvatar.displayName = 'SkeletonAvatar'\n\n// ---------------------------------------------------------------------------\n// SkeletonText\n// ---------------------------------------------------------------------------\n\nconst lastLineWidthClasses = {\n full: 'w-full',\n 'three-quarter': 'w-3/4',\n half: 'w-1/2',\n} as const\n\nconst textSpacingClasses = {\n sm: 'gap-ds-02',\n md: 'gap-ds-03',\n} as const\n\nexport interface SkeletonTextProps extends React.HTMLAttributes<HTMLDivElement> {\n lines?: number\n /** Last line width — 'full' | 'three-quarter' | 'half' (default: 'three-quarter') */\n lastLineWidth?: 'full' | 'three-quarter' | 'half'\n /** Gap between lines */\n spacing?: 'sm' | 'md'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(\n (\n {\n className,\n lines = 3,\n lastLineWidth = 'three-quarter',\n spacing = 'md',\n animation = 'pulse',\n ...props\n },\n ref,\n ) => {\n const safeLines = Math.max(1, lines ?? 3)\n return (\n <div\n ref={ref}\n className={cn('flex flex-col', textSpacingClasses[spacing], className)}\n {...props}\n >\n {Array.from({ length: safeLines }).map((_, i) => (\n <div\n key={i}\n className={cn(\n 'h-3.5 rounded-ds-sm bg-skeleton-base',\n animationClasses[animation],\n i === safeLines - 1 ? lastLineWidthClasses[lastLineWidth] : 'w-full',\n )}\n />\n ))}\n </div>\n )\n },\n)\nSkeletonText.displayName = 'SkeletonText'\n\n// ---------------------------------------------------------------------------\n// SkeletonButton\n// ---------------------------------------------------------------------------\n\nconst buttonSizeClasses = {\n sm: 'h-ds-sm',\n md: 'h-ds-md',\n lg: 'h-ds-lg',\n} as const\n\nconst buttonWidthClasses = {\n auto: { sm: 'w-24', md: 'w-28', lg: 'w-32' },\n full: { sm: 'w-full', md: 'w-full', lg: 'w-full' },\n} as const\n\nexport interface SkeletonButtonProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'md' | 'lg'\n width?: 'auto' | 'full' | 'icon'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonButton = React.forwardRef<HTMLDivElement, SkeletonButtonProps>(\n ({ className, size = 'md', width = 'auto', animation = 'pulse', ...props }, ref) => {\n const isIcon = width === 'icon'\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-skeleton-base rounded-ds-md',\n buttonSizeClasses[size],\n isIcon ? 'aspect-square' : buttonWidthClasses[width as 'auto' | 'full'][size],\n animationClasses[animation],\n className,\n )}\n {...props}\n />\n )\n },\n)\nSkeletonButton.displayName = 'SkeletonButton'\n\n// ---------------------------------------------------------------------------\n// SkeletonInput\n// ---------------------------------------------------------------------------\n\nconst inputSizeClasses = {\n sm: 'h-ds-sm',\n md: 'h-ds-md',\n lg: 'h-ds-lg',\n} as const\n\nexport interface SkeletonInputProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'md' | 'lg'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonInput = React.forwardRef<HTMLDivElement, SkeletonInputProps>(\n ({ className, size = 'md', animation = 'pulse', ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'w-full bg-skeleton-base rounded-ds-md border border-skeleton-base/30',\n inputSizeClasses[size],\n animationClasses[animation],\n className,\n )}\n {...props}\n />\n )\n },\n)\nSkeletonInput.displayName = 'SkeletonInput'\n\n// ---------------------------------------------------------------------------\n// SkeletonChart\n// ---------------------------------------------------------------------------\n\n/** Deterministic bar heights based on index (percentage of container height) */\nconst BAR_HEIGHTS = [65, 40, 85, 55, 70, 30, 90, 50, 75, 35, 80, 45, 60, 95, 38] as const\n\nexport interface SkeletonChartProps extends React.HTMLAttributes<HTMLDivElement> {\n bars?: number\n height?: string\n animation?: SkeletonAnimation\n}\n\nconst SkeletonChart = React.forwardRef<HTMLDivElement, SkeletonChartProps>(\n ({ className, bars = 7, height = 'h-40', animation = 'pulse', ...props }, ref) => {\n const safeBars = Math.max(1, bars ?? 7)\n return (\n <div\n ref={ref}\n className={cn('flex items-end gap-ds-02', height, className)}\n {...props}\n >\n {Array.from({ length: safeBars }).map((_, i) => {\n const barHeight = BAR_HEIGHTS[i % BAR_HEIGHTS.length]\n return (\n <div\n key={i}\n className={cn(\n 'flex-1 bg-skeleton-base rounded-t-ds-sm',\n animationClasses[animation],\n )}\n style={{ height: `${barHeight}%` }}\n />\n )\n })}\n </div>\n )\n },\n)\nSkeletonChart.displayName = 'SkeletonChart'\n\n// ---------------------------------------------------------------------------\n// SkeletonImage\n// ---------------------------------------------------------------------------\n\nexport interface SkeletonImageProps extends React.HTMLAttributes<HTMLDivElement> {\n width?: string\n height?: string\n animation?: SkeletonAnimation\n}\n\nconst SkeletonImage = React.forwardRef<HTMLDivElement, SkeletonImageProps>(\n ({ className, width = 'w-full', height = 'h-40', animation = 'pulse', ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-skeleton-base rounded-ds-md relative flex items-center justify-center',\n width,\n height,\n animationClasses[animation],\n className,\n )}\n {...props}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-skeleton-shimmer opacity-40\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n </div>\n )\n },\n)\nSkeletonImage.displayName = 'SkeletonImage'\n\n// ---------------------------------------------------------------------------\n// SkeletonGroup\n// ---------------------------------------------------------------------------\n\nexport interface SkeletonGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Accessible label for the loading state */\n label?: string\n}\n\nconst SkeletonGroup = React.forwardRef<HTMLDivElement, SkeletonGroupProps>(\n ({ label = 'Loading', children, ...props }, ref) => {\n return (\n <div ref={ref} role=\"status\" aria-label={label} aria-busy=\"true\" {...props}>\n <span className=\"sr-only\">{label}...</span>\n {children}\n </div>\n )\n },\n)\nSkeletonGroup.displayName = 'SkeletonGroup'\n\nexport {\n Skeleton,\n SkeletonAvatar,\n SkeletonButton,\n SkeletonChart,\n SkeletonGroup,\n SkeletonImage,\n SkeletonInput,\n SkeletonText,\n skeletonVariants,\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"skeleton.js","names":[],"sources":["../../src/ui/skeleton.tsx"],"sourcesContent":["// @server-safe\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\nconst skeletonVariants = cva('bg-skeleton-base', {\n variants: {\n variant: {\n rectangle: 'rounded-control',\n circle: 'rounded-pill aspect-square',\n text: 'rounded-control-inner h-4 w-full',\n },\n animation: {\n pulse: 'animate-pulse motion-reduce:animate-none',\n shimmer:\n 'bg-[length:200%_100%] bg-linear-to-r from-skeleton-base via-skeleton-shimmer to-skeleton-base animate-skeleton-shimmer [background-attachment:fixed] motion-reduce:animate-none',\n none: '',\n },\n },\n defaultVariants: {\n variant: 'rectangle',\n animation: 'pulse',\n },\n})\n\n/**\n * Props for Skeleton — a loading placeholder with shape variants and two animation styles.\n *\n * **Shape variants:** `rectangle` (default, rounded corners) | `circle` (aspect-square pill) |\n * `text` (full-width slim bar, for text line placeholders)\n *\n * **Animation:** `pulse` (default, opacity fade) | `shimmer` (left-to-right gradient sweep,\n * respects `prefers-reduced-motion`) | `none` (static, for testing or paused states)\n *\n * @example\n * // Text line placeholder (repeatable for a paragraph skeleton):\n * <Skeleton variant=\"text\" />\n * <Skeleton variant=\"text\" className=\"w-3/4\" />\n *\n * @example\n * // Avatar + name card loading state:\n * <div className=\"flex items-center gap-ds-04\">\n * <Skeleton variant=\"circle\" className=\"h-ds-md w-ds-md\" />\n * <div className=\"flex flex-col gap-ds-02 flex-1\">\n * <Skeleton variant=\"text\" className=\"w-1/2\" />\n * <Skeleton variant=\"text\" className=\"w-1/3\" />\n * </div>\n * </div>\n *\n * @example\n * // Card placeholder with shimmer animation:\n * <Skeleton variant=\"rectangle\" animation=\"shimmer\" className=\"h-48 w-full\" />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface SkeletonProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof skeletonVariants> {}\n\nconst Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(\n ({ className, variant, animation, ...props }, ref) => {\n return (\n <div\n ref={ref}\n aria-hidden=\"true\"\n className={cn(skeletonVariants({ variant, animation }), className)}\n {...props}\n />\n )\n },\n)\nSkeleton.displayName = 'Skeleton'\n\n// ---------------------------------------------------------------------------\n// Animation helper — resolves animation classes for sub-components\n// ---------------------------------------------------------------------------\n\nconst animationClasses = {\n pulse: 'animate-pulse',\n shimmer:\n 'bg-[length:200%_100%] bg-linear-to-r from-skeleton-base via-skeleton-shimmer to-skeleton-base animate-skeleton-shimmer motion-reduce:animate-none',\n none: '',\n} as const\n\ntype SkeletonAnimation = 'pulse' | 'shimmer' | 'none'\n\n// ---------------------------------------------------------------------------\n// SkeletonAvatar\n// ---------------------------------------------------------------------------\n\nconst avatarSizeClasses = {\n sm: 'h-8 w-8',\n md: 'h-10 w-10',\n lg: 'h-12 w-12',\n xl: 'h-16 w-16',\n} as const\n\nexport interface SkeletonAvatarProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'md' | 'lg' | 'xl'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonAvatar = React.forwardRef<HTMLDivElement, SkeletonAvatarProps>(\n ({ className, size = 'md', animation = 'pulse', ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-skeleton-base rounded-pill',\n avatarSizeClasses[size],\n animationClasses[animation],\n className,\n )}\n {...props}\n />\n )\n },\n)\nSkeletonAvatar.displayName = 'SkeletonAvatar'\n\n// ---------------------------------------------------------------------------\n// SkeletonText\n// ---------------------------------------------------------------------------\n\nconst lastLineWidthClasses = {\n full: 'w-full',\n 'three-quarter': 'w-3/4',\n half: 'w-1/2',\n} as const\n\nconst textSpacingClasses = {\n sm: 'gap-ds-02',\n md: 'gap-ds-03',\n} as const\n\nexport interface SkeletonTextProps extends React.HTMLAttributes<HTMLDivElement> {\n lines?: number\n /** Last line width — 'full' | 'three-quarter' | 'half' (default: 'three-quarter') */\n lastLineWidth?: 'full' | 'three-quarter' | 'half'\n /** Gap between lines */\n spacing?: 'sm' | 'md'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(\n (\n {\n className,\n lines = 3,\n lastLineWidth = 'three-quarter',\n spacing = 'md',\n animation = 'pulse',\n ...props\n },\n ref,\n ) => {\n const safeLines = Math.max(1, lines ?? 3)\n return (\n <div\n ref={ref}\n className={cn('flex flex-col', textSpacingClasses[spacing], className)}\n {...props}\n >\n {Array.from({ length: safeLines }).map((_, i) => (\n <div\n key={i}\n className={cn(\n 'h-3.5 rounded-control-inner bg-skeleton-base',\n animationClasses[animation],\n i === safeLines - 1 ? lastLineWidthClasses[lastLineWidth] : 'w-full',\n )}\n />\n ))}\n </div>\n )\n },\n)\nSkeletonText.displayName = 'SkeletonText'\n\n// ---------------------------------------------------------------------------\n// SkeletonButton\n// ---------------------------------------------------------------------------\n\nconst buttonSizeClasses = {\n sm: 'h-ds-sm',\n md: 'h-ds-md',\n lg: 'h-ds-lg',\n} as const\n\nconst buttonWidthClasses = {\n auto: { sm: 'w-24', md: 'w-28', lg: 'w-32' },\n full: { sm: 'w-full', md: 'w-full', lg: 'w-full' },\n} as const\n\nexport interface SkeletonButtonProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'md' | 'lg'\n width?: 'auto' | 'full' | 'icon'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonButton = React.forwardRef<HTMLDivElement, SkeletonButtonProps>(\n ({ className, size = 'md', width = 'auto', animation = 'pulse', ...props }, ref) => {\n const isIcon = width === 'icon'\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-skeleton-base rounded-control',\n buttonSizeClasses[size],\n isIcon ? 'aspect-square' : buttonWidthClasses[width as 'auto' | 'full'][size],\n animationClasses[animation],\n className,\n )}\n {...props}\n />\n )\n },\n)\nSkeletonButton.displayName = 'SkeletonButton'\n\n// ---------------------------------------------------------------------------\n// SkeletonInput\n// ---------------------------------------------------------------------------\n\nconst inputSizeClasses = {\n sm: 'h-ds-sm',\n md: 'h-ds-md',\n lg: 'h-ds-lg',\n} as const\n\nexport interface SkeletonInputProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'md' | 'lg'\n animation?: SkeletonAnimation\n}\n\nconst SkeletonInput = React.forwardRef<HTMLDivElement, SkeletonInputProps>(\n ({ className, size = 'md', animation = 'pulse', ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'w-full bg-skeleton-base rounded-control border border-skeleton-base/30',\n inputSizeClasses[size],\n animationClasses[animation],\n className,\n )}\n {...props}\n />\n )\n },\n)\nSkeletonInput.displayName = 'SkeletonInput'\n\n// ---------------------------------------------------------------------------\n// SkeletonChart\n// ---------------------------------------------------------------------------\n\n/** Deterministic bar heights based on index (percentage of container height) */\nconst BAR_HEIGHTS = [65, 40, 85, 55, 70, 30, 90, 50, 75, 35, 80, 45, 60, 95, 38] as const\n\nexport interface SkeletonChartProps extends React.HTMLAttributes<HTMLDivElement> {\n bars?: number\n height?: string\n animation?: SkeletonAnimation\n}\n\nconst SkeletonChart = React.forwardRef<HTMLDivElement, SkeletonChartProps>(\n ({ className, bars = 7, height = 'h-40', animation = 'pulse', ...props }, ref) => {\n const safeBars = Math.max(1, bars ?? 7)\n return (\n <div\n ref={ref}\n className={cn('flex items-end gap-ds-02', height, className)}\n {...props}\n >\n {Array.from({ length: safeBars }).map((_, i) => {\n const barHeight = BAR_HEIGHTS[i % BAR_HEIGHTS.length]\n return (\n <div\n key={i}\n className={cn(\n 'flex-1 bg-skeleton-base rounded-t-control-inner',\n animationClasses[animation],\n )}\n style={{ height: `${barHeight}%` }}\n />\n )\n })}\n </div>\n )\n },\n)\nSkeletonChart.displayName = 'SkeletonChart'\n\n// ---------------------------------------------------------------------------\n// SkeletonImage\n// ---------------------------------------------------------------------------\n\nexport interface SkeletonImageProps extends React.HTMLAttributes<HTMLDivElement> {\n width?: string\n height?: string\n animation?: SkeletonAnimation\n}\n\nconst SkeletonImage = React.forwardRef<HTMLDivElement, SkeletonImageProps>(\n ({ className, width = 'w-full', height = 'h-40', animation = 'pulse', ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'bg-skeleton-base rounded-control relative flex items-center justify-center',\n width,\n height,\n animationClasses[animation],\n className,\n )}\n {...props}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-skeleton-shimmer opacity-40\"\n aria-hidden=\"true\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n </div>\n )\n },\n)\nSkeletonImage.displayName = 'SkeletonImage'\n\n// ---------------------------------------------------------------------------\n// SkeletonGroup\n// ---------------------------------------------------------------------------\n\nexport interface SkeletonGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Accessible label for the loading state */\n label?: string\n}\n\nconst SkeletonGroup = React.forwardRef<HTMLDivElement, SkeletonGroupProps>(\n ({ label = 'Loading', children, ...props }, ref) => {\n return (\n <div ref={ref} role=\"status\" aria-label={label} aria-busy=\"true\" {...props}>\n <span className=\"sr-only\">{label}...</span>\n {children}\n </div>\n )\n },\n)\nSkeletonGroup.displayName = 'SkeletonGroup'\n\nexport {\n Skeleton,\n SkeletonAvatar,\n SkeletonButton,\n SkeletonChart,\n SkeletonGroup,\n SkeletonImage,\n SkeletonInput,\n SkeletonText,\n skeletonVariants,\n}\n"],"mappings":"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"}
package/dist/ui/slider.js CHANGED
@@ -6,14 +6,14 @@ import { useFormField as o } from "./form.js";
6
6
  import * as s from "react";
7
7
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
8
8
  //#region src/ui/slider.tsx
9
- var u = i("relative w-full grow overflow-hidden rounded-ds-full bg-surface-raised-hover", {
9
+ var u = i("relative w-full grow overflow-hidden rounded-pill bg-surface-raised-hover", {
10
10
  variants: { size: {
11
11
  sm: "h-[4px]",
12
12
  md: "h-ds-02b",
13
13
  lg: "h-[10px]"
14
14
  } },
15
15
  defaultVariants: { size: "md" }
16
- }), d = i("touch-target block rounded-ds-full bg-surface-overlay shadow-raised transition-[color,transform,box-shadow] duration-fast-02 ease-productive-standard hover:scale-110 hover:shadow-raised-hover active:scale-[1.15] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-action-disabled", {
16
+ }), d = i("touch-target block rounded-pill bg-surface-overlay shadow-raised transition-[color,transform,box-shadow] duration-fast-02 ease-productive-standard hover:scale-110 hover:shadow-raised-hover active:scale-[1.15] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-action-disabled", {
17
17
  variants: {
18
18
  size: {
19
19
  sm: "h-4 w-4 border",
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":[],"sources":["../../src/ui/slider.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@primitives/react-slider'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { cn } from './lib/utils'\n\nconst sliderTrackVariants = cva(\n 'relative w-full grow overflow-hidden rounded-ds-full bg-surface-raised-hover',\n {\n variants: {\n size: {\n sm: 'h-[4px]',\n md: 'h-ds-02b',\n lg: 'h-[10px]',\n },\n },\n defaultVariants: { size: 'md' },\n },\n)\n\nconst sliderThumbVariants = cva(\n 'touch-target block rounded-ds-full bg-surface-overlay shadow-raised transition-[color,transform,box-shadow] duration-fast-02 ease-productive-standard hover:scale-110 hover:shadow-raised-hover active:scale-[1.15] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-action-disabled',\n {\n variants: {\n size: {\n sm: 'h-4 w-4 border',\n md: 'h-6 w-6 border-2',\n lg: 'h-8 w-8 border-[3px]',\n },\n color: {\n accent: 'border-accent-7 focus-visible:ring-accent-9',\n success: 'border-success-7 focus-visible:ring-success-9',\n warning: 'border-warning-7 focus-visible:ring-warning-9',\n error: 'border-error-7 focus-visible:ring-error-9',\n },\n },\n defaultVariants: { size: 'md', color: 'accent' },\n },\n)\n\nconst sliderRangeColorMap: Record<string, string> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n}\n\nexport type SliderSize = NonNullable<VariantProps<typeof sliderTrackVariants>['size']>\nexport type SliderColor = NonNullable<VariantProps<typeof sliderThumbVariants>['color']>\n\nexport interface SliderProps\n extends Omit<React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>, 'color'> {\n /** Size of the slider. Controls track height and thumb diameter. */\n size?: SliderSize\n /** Color of the range fill and thumb border. */\n color?: SliderColor\n}\n\n/**\n * A Radix-powered slider supporting single or multiple thumbs.\n *\n * Pass `defaultValue={[50]}` for a single thumb, or `defaultValue={[25, 75]}`\n * for a range slider with two thumbs. One `<Thumb>` is rendered per value entry.\n *\n * When using multiple thumbs, provide per-thumb labels via the `aria-label` array\n * on the `value`/`defaultValue` entries, or wrap each in a labelled form field.\n *\n * **Sizes:** `sm` | `md` (default) | `lg`\n *\n * **Colors:** `accent` (default) | `success` | `warning` | `error`\n */\nconst Slider = React.forwardRef<\n React.ElementRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(({ className, 'aria-label': ariaLabel, value, defaultValue, size: sizeProp = 'md', color: colorProp = 'accent', ...props }, ref) => {\n const size = sizeProp ?? 'md'\n const color = colorProp ?? 'accent'\n const fieldCtx = useFormField()\n const isError = fieldCtx.state === 'error'\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Determine how many thumbs to render from controlled or default value\n const thumbCount = (value ?? defaultValue ?? [0]).length\n\n return (\n <SliderPrimitive.Root\n ref={ref}\n value={value}\n defaultValue={defaultValue}\n aria-invalid={isError || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n className={cn(\n 'relative flex w-full touch-none select-none items-center',\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track className={sliderTrackVariants({ size })}>\n <SliderPrimitive.Range className={cn('absolute h-full', sliderRangeColorMap[color])} />\n </SliderPrimitive.Track>\n {Array.from({ length: thumbCount }, (_, i) => (\n <SliderPrimitive.Thumb\n key={i}\n aria-label={thumbCount === 1 ? (ariaLabel as string | undefined) : undefined}\n className={sliderThumbVariants({ size, color })}\n />\n ))}\n </SliderPrimitive.Root>\n )\n})\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider, sliderThumbVariants,sliderTrackVariants }\n"],"mappings":";;;;;;;;AASA,IAAM,IAAsB,EAC1B,gFACA;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EACF;CACD,iBAAiB,EAAE,MAAM,MAAM;CAChC,CACF,EAEK,IAAsB,EAC1B,mWACA;CACE,UAAU;EACR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL;EACD,OAAO;GACL,QAAQ;GACR,SAAS;GACT,SAAS;GACT,OAAO;GACR;EACF;CACD,iBAAiB;EAAE,MAAM;EAAM,OAAO;EAAU;CACjD,CACF,EAEK,IAA8C;CAClD,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACR,EA0BK,IAAS,EAAM,YAGlB,EAAE,cAAW,cAAc,GAAW,UAAO,iBAAc,MAAM,IAAW,MAAM,OAAO,IAAY,UAAU,GAAG,KAAS,MAAQ;CACpI,IAAM,IAAO,KAAY,MACnB,IAAQ,KAAa,UACrB,IAAW,GAAc,EACzB,IAAU,EAAS,UAAU,SAC7B,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,KAAc,KAAS,KAAgB,CAAC,EAAE,EAAE;AAElD,QACE,kBAAC,GAAD;EACO;EACE;EACO;EACd,gBAAc,KAAW,KAAA;EACzB,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,WAAW,EACT,4DACA,EACD;EACD,GAAI;YAXN,CAaE,kBAAC,GAAD;GAAuB,WAAW,EAAoB,EAAE,SAAM,CAAC;aAC7D,kBAAC,GAAD,EAAuB,WAAW,EAAG,mBAAmB,EAAoB,GAAO,EAAI,CAAA;GACjE,CAAA,EACvB,MAAM,KAAK,EAAE,QAAQ,GAAY,GAAG,GAAG,MACtC,kBAAC,GAAD;GAEE,cAAY,MAAe,IAAK,IAAmC,KAAA;GACnE,WAAW,EAAoB;IAAE;IAAM;IAAO,CAAC;GAC/C,EAHK,EAGL,CACF,CACmB;;EAEzB;AACF,EAAO,cAAA,EAAmC"}
1
+ {"version":3,"file":"slider.js","names":[],"sources":["../../src/ui/slider.tsx"],"sourcesContent":["'use client'\n\nimport * as SliderPrimitive from '@primitives/react-slider'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\n\nimport { useFormField } from './form'\nimport { cn } from './lib/utils'\n\nconst sliderTrackVariants = cva(\n 'relative w-full grow overflow-hidden rounded-pill bg-surface-raised-hover',\n {\n variants: {\n size: {\n sm: 'h-[4px]',\n md: 'h-ds-02b',\n lg: 'h-[10px]',\n },\n },\n defaultVariants: { size: 'md' },\n },\n)\n\nconst sliderThumbVariants = cva(\n 'touch-target block rounded-pill bg-surface-overlay shadow-raised transition-[color,transform,box-shadow] duration-fast-02 ease-productive-standard hover:scale-110 hover:shadow-raised-hover active:scale-[1.15] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-action-disabled',\n {\n variants: {\n size: {\n sm: 'h-4 w-4 border',\n md: 'h-6 w-6 border-2',\n lg: 'h-8 w-8 border-[3px]',\n },\n color: {\n accent: 'border-accent-7 focus-visible:ring-accent-9',\n success: 'border-success-7 focus-visible:ring-success-9',\n warning: 'border-warning-7 focus-visible:ring-warning-9',\n error: 'border-error-7 focus-visible:ring-error-9',\n },\n },\n defaultVariants: { size: 'md', color: 'accent' },\n },\n)\n\nconst sliderRangeColorMap: Record<string, string> = {\n accent: 'bg-accent-9',\n success: 'bg-success-9',\n warning: 'bg-warning-9',\n error: 'bg-error-9',\n}\n\nexport type SliderSize = NonNullable<VariantProps<typeof sliderTrackVariants>['size']>\nexport type SliderColor = NonNullable<VariantProps<typeof sliderThumbVariants>['color']>\n\nexport interface SliderProps\n extends Omit<React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>, 'color'> {\n /** Size of the slider. Controls track height and thumb diameter. */\n size?: SliderSize\n /** Color of the range fill and thumb border. */\n color?: SliderColor\n}\n\n/**\n * A Radix-powered slider supporting single or multiple thumbs.\n *\n * Pass `defaultValue={[50]}` for a single thumb, or `defaultValue={[25, 75]}`\n * for a range slider with two thumbs. One `<Thumb>` is rendered per value entry.\n *\n * When using multiple thumbs, provide per-thumb labels via the `aria-label` array\n * on the `value`/`defaultValue` entries, or wrap each in a labelled form field.\n *\n * **Sizes:** `sm` | `md` (default) | `lg`\n *\n * **Colors:** `accent` (default) | `success` | `warning` | `error`\n */\nconst Slider = React.forwardRef<\n React.ElementRef<typeof SliderPrimitive.Root>,\n SliderProps\n>(({ className, 'aria-label': ariaLabel, value, defaultValue, size: sizeProp = 'md', color: colorProp = 'accent', ...props }, ref) => {\n const size = sizeProp ?? 'md'\n const color = colorProp ?? 'accent'\n const fieldCtx = useFormField()\n const isError = fieldCtx.state === 'error'\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Determine how many thumbs to render from controlled or default value\n const thumbCount = (value ?? defaultValue ?? [0]).length\n\n return (\n <SliderPrimitive.Root\n ref={ref}\n value={value}\n defaultValue={defaultValue}\n aria-invalid={isError || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n className={cn(\n 'relative flex w-full touch-none select-none items-center',\n className,\n )}\n {...props}\n >\n <SliderPrimitive.Track className={sliderTrackVariants({ size })}>\n <SliderPrimitive.Range className={cn('absolute h-full', sliderRangeColorMap[color])} />\n </SliderPrimitive.Track>\n {Array.from({ length: thumbCount }, (_, i) => (\n <SliderPrimitive.Thumb\n key={i}\n aria-label={thumbCount === 1 ? (ariaLabel as string | undefined) : undefined}\n className={sliderThumbVariants({ size, color })}\n />\n ))}\n </SliderPrimitive.Root>\n )\n})\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider, sliderThumbVariants,sliderTrackVariants }\n"],"mappings":";;;;;;;;AASA,IAAM,IAAsB,EAC1B,6EACA;CACE,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACL,EACF;CACD,iBAAiB,EAAE,MAAM,MAAM;CAChC,CACF,EAEK,IAAsB,EAC1B,gWACA;CACE,UAAU;EACR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACL;EACD,OAAO;GACL,QAAQ;GACR,SAAS;GACT,SAAS;GACT,OAAO;GACR;EACF;CACD,iBAAiB;EAAE,MAAM;EAAM,OAAO;EAAU;CACjD,CACF,EAEK,IAA8C;CAClD,QAAQ;CACR,SAAS;CACT,SAAS;CACT,OAAO;CACR,EA0BK,IAAS,EAAM,YAGlB,EAAE,cAAW,cAAc,GAAW,UAAO,iBAAc,MAAM,IAAW,MAAM,OAAO,IAAY,UAAU,GAAG,KAAS,MAAQ;CACpI,IAAM,IAAO,KAAY,MACnB,IAAQ,KAAa,UACrB,IAAW,GAAc,EACzB,IAAU,EAAS,UAAU,SAC7B,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,KAAc,KAAS,KAAgB,CAAC,EAAE,EAAE;AAElD,QACE,kBAAC,GAAD;EACO;EACE;EACO;EACd,gBAAc,KAAW,KAAA;EACzB,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,WAAW,EACT,4DACA,EACD;EACD,GAAI;YAXN,CAaE,kBAAC,GAAD;GAAuB,WAAW,EAAoB,EAAE,SAAM,CAAC;aAC7D,kBAAC,GAAD,EAAuB,WAAW,EAAG,mBAAmB,EAAoB,GAAO,EAAI,CAAA;GACjE,CAAA,EACvB,MAAM,KAAK,EAAE,QAAQ,GAAY,GAAG,GAAG,MACtC,kBAAC,GAAD;GAEE,cAAY,MAAe,IAAK,IAAmC,KAAA;GACnE,WAAW,EAAoB;IAAE;IAAM;IAAO,CAAC;GAC/C,EAHK,EAGL,CACF,CACmB;;EAEzB;AACF,EAAO,cAAA,EAAmC"}
@@ -57,12 +57,12 @@ var p = {
57
57
  "icon-sm": "px-ds-02",
58
58
  "icon-md": "px-ds-02"
59
59
  }, v = {
60
- xs: "rounded-ds-md",
61
- sm: "rounded-ds-md",
62
- md: "rounded-ds-lg",
63
- "icon-xs": "rounded-ds-sm",
64
- "icon-sm": "rounded-ds-md",
65
- "icon-md": "rounded-ds-md"
60
+ xs: "rounded-control",
61
+ sm: "rounded-control",
62
+ md: "rounded-control",
63
+ "icon-xs": "rounded-control",
64
+ "icon-sm": "rounded-control",
65
+ "icon-md": "rounded-control"
66
66
  };
67
67
  function y(e, t) {
68
68
  let n = {
@@ -224,7 +224,7 @@ var S = s.forwardRef(function({ children: S, onClick: C, dropdownContent: w, ope
224
224
  scale: .95
225
225
  },
226
226
  transition: { duration: a.moderate01 },
227
- className: "rounded-ds-lg border border-surface-border-strong bg-surface-overlay shadow-floating",
227
+ className: "rounded-overlay border border-surface-border-strong bg-surface-overlay shadow-floating",
228
228
  children: w
229
229
  })
230
230
  }) })] });
@@ -1 +1 @@
1
- {"version":3,"file":"split-button.js","names":[],"sources":["../../src/ui/split-button.tsx"],"sourcesContent":["'use client'\n\nimport { autoUpdate, computePosition, flip, offset, type Placement,shift } from '@floating-ui/dom'\nimport { type VariantProps } from 'class-variance-authority'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { buttonVariants } from './button'\nimport { durations } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// ── Types ───────────────────────────────────────────────────────\n\ntype SplitButtonVariant = 'solid' | 'soft' | 'outline'\ntype SplitButtonColor = NonNullable<VariantProps<typeof buttonVariants>['color']>\ntype SplitButtonSize = 'xs' | 'sm' | 'md' | 'icon-xs' | 'icon-sm' | 'icon-md'\n\nexport interface SplitButtonProps {\n /** Primary action content (left side). */\n children: React.ReactNode\n /** Primary click handler (left side). */\n onClick: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Content rendered inside the floating dropdown panel. */\n dropdownContent?: React.ReactNode\n /** Controlled open state. */\n open?: boolean\n /** Called when open state changes. Uncontrolled if omitted. */\n onOpenChange?: (open: boolean) => void\n /** @default 'solid' */\n variant?: SplitButtonVariant\n /** @default 'accent' */\n color?: SplitButtonColor\n /** @default 'md' */\n size?: SplitButtonSize\n /** Disable both halves. */\n disabled?: boolean\n /** aria-label for the primary button. */\n 'aria-label'?: string\n /** aria-label for the dropdown trigger. @default 'More options' */\n dropdownLabel?: string\n /** Custom icon for the dropdown trigger. Defaults to chevron-down. */\n dropdownIcon?: React.ReactNode\n /** Which side the dropdown trigger sits on. @default 'right' */\n triggerSide?: 'left' | 'right'\n /** Preferred placement for the dropdown panel. @default 'top-end' */\n placement?: Placement\n /** Width of the trigger (chevron) half. @default 'auto' */\n triggerWidth?: number | string\n className?: string\n}\n\n// ── Styling maps ────────────────────────────────────────────────\n\nconst dividerColor: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-11/20', error: 'bg-error-11/20', success: 'bg-success-11/20',\n warning: 'bg-warning-11/20', neutral: 'bg-neutral-8/30',\n },\n soft: {\n accent: 'bg-accent-6', error: 'bg-error-6', success: 'bg-success-6',\n warning: 'bg-warning-6', neutral: 'bg-surface-border',\n },\n outline: {\n accent: 'bg-accent-7', error: 'bg-error-7', success: 'bg-success-7',\n warning: 'bg-warning-7', neutral: 'bg-surface-border-strong',\n },\n}\n\nconst heightClass: Record<SplitButtonSize, string> = {\n xs: 'h-ds-xs-plus', sm: 'h-ds-sm', md: 'h-ds-md',\n 'icon-xs': 'h-ds-xs-plus', 'icon-sm': 'h-ds-sm', 'icon-md': 'h-ds-md',\n}\n\nconst textClass: Record<SplitButtonSize, string> = {\n xs: 'text-ds-sm', sm: 'text-ds-sm', md: 'text-ds-md',\n 'icon-xs': 'text-ds-sm', 'icon-sm': 'text-ds-sm', 'icon-md': 'text-ds-md',\n}\n\nconst primaryPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-03 gap-1', sm: 'px-ds-04 gap-1.5', md: 'px-ds-05 gap-2',\n 'icon-xs': 'px-ds-02', 'icon-sm': 'px-ds-03', 'icon-md': 'px-ds-04',\n}\n\nconst triggerPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-02', sm: 'px-ds-02', md: 'px-ds-03',\n 'icon-xs': 'px-ds-01', 'icon-sm': 'px-ds-02', 'icon-md': 'px-ds-02',\n}\n\nconst radiusClass: Record<SplitButtonSize, string> = {\n xs: 'rounded-ds-md', sm: 'rounded-ds-md', md: 'rounded-ds-lg',\n 'icon-xs': 'rounded-ds-sm', 'icon-sm': 'rounded-ds-md', 'icon-md': 'rounded-ds-md',\n}\n\nfunction getHalfClasses(variant: SplitButtonVariant, color: string): string {\n const map: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-9 text-accent-fg hover:bg-accent-10 active:bg-accent-11',\n error: 'bg-error-9 text-error-fg hover:bg-error-10 active:bg-error-11',\n success: 'bg-success-9 text-success-fg hover:bg-success-10 active:bg-success-11',\n warning: 'bg-warning-9 text-warning-fg hover:bg-warning-10 active:bg-warning-11',\n neutral: 'bg-neutral-5 text-surface-fg hover:bg-neutral-7 active:bg-neutral-8',\n },\n soft: {\n accent: 'bg-accent-3 text-accent-11 hover:bg-accent-4 active:bg-accent-5',\n error: 'bg-error-3 text-error-11 hover:bg-error-4 active:bg-error-5',\n success: 'bg-success-3 text-success-11 hover:bg-success-4 active:bg-success-5',\n warning: 'bg-warning-3 text-warning-11 hover:bg-warning-4 active:bg-warning-5',\n neutral: 'bg-surface-raised-hover text-surface-fg-muted hover:bg-surface-raised-active active:bg-neutral-5',\n },\n outline: {\n accent: 'bg-transparent text-accent-11 hover:bg-accent-3 active:bg-accent-4',\n error: 'bg-transparent text-error-11 hover:bg-error-3 active:bg-error-4',\n success: 'bg-transparent text-success-11 hover:bg-success-3 active:bg-success-4',\n warning: 'bg-transparent text-warning-11 hover:bg-warning-3 active:bg-warning-4',\n neutral: 'bg-transparent text-surface-fg hover:bg-surface-raised-hover active:bg-surface-raised-active',\n },\n }\n return map[variant][color] ?? map[variant].accent\n}\n\nfunction getOutlineBorderColor(color: string): string {\n const map: Record<string, string> = {\n accent: 'border-accent-7', error: 'border-error-7', success: 'border-success-7',\n warning: 'border-warning-7', neutral: 'border-surface-border-strong',\n }\n return map[color] ?? map.accent\n}\n\n// ── Chevron SVG ─────────────────────────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" className={className} aria-hidden=\"true\">\n <path d=\"M3 4.5L6 7.5L9 4.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────\n\nconst SplitButton = React.forwardRef<HTMLDivElement, SplitButtonProps>(\n function SplitButton(\n {\n children,\n onClick,\n dropdownContent,\n open: openProp,\n onOpenChange,\n variant = 'solid',\n color = 'accent',\n size = 'md',\n disabled = false,\n 'aria-label': ariaLabel,\n dropdownLabel = 'More options',\n dropdownIcon,\n triggerSide = 'right',\n placement: placementProp = 'top-end',\n triggerWidth,\n className,\n },\n ref,\n ) {\n const [internalOpen, setInternalOpen] = React.useState(false)\n const isControlled = openProp !== undefined\n const isOpen = isControlled ? openProp : internalOpen\n\n const anchorRef = React.useRef<HTMLDivElement>(null)\n const floatingRef = React.useRef<HTMLDivElement>(null)\n const menuId = React.useId()\n\n const setOpen = React.useCallback(\n (value: boolean) => {\n if (onOpenChange) onOpenChange(value)\n if (!isControlled) setInternalOpen(value)\n },\n [onOpenChange, isControlled],\n )\n\n // ── Floating UI positioning ──\n React.useEffect(() => {\n if (!isOpen || !anchorRef.current || !floatingRef.current) return\n\n const anchor = anchorRef.current\n const floating = floatingRef.current\n\n const update = () => {\n computePosition(anchor, floating, {\n placement: placementProp,\n middleware: [\n offset(8),\n flip({ fallbackPlacements: ['bottom-end', 'top-start', 'bottom-start'] }),\n shift({ padding: 8 }),\n ],\n }).then(({ x, y }) => {\n Object.assign(floating.style, { left: `${x}px`, top: `${y}px` })\n })\n }\n\n const cleanup = autoUpdate(anchor, floating, update)\n return cleanup\n }, [isOpen, placementProp])\n\n // ── Close on outside click ──\n React.useEffect(() => {\n if (!isOpen) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node\n if (\n anchorRef.current?.contains(target) ||\n floatingRef.current?.contains(target)\n ) return\n setOpen(false)\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [isOpen, setOpen])\n\n // ── Close on Escape ──\n React.useEffect(() => {\n if (!isOpen) return\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', handleKey)\n return () => document.removeEventListener('keydown', handleKey)\n }, [isOpen, setOpen])\n\n const halfClasses = getHalfClasses(variant, color)\n const divider = dividerColor[variant][color] ?? dividerColor[variant].accent\n\n const triggerStyle = triggerWidth != null\n ? { width: typeof triggerWidth === 'number' ? `${triggerWidth}px` : triggerWidth }\n : undefined\n\n return (\n <>\n {/* Button group */}\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel ?? undefined}\n className={cn('relative inline-flex', className)}\n >\n <div\n ref={anchorRef}\n className={cn(\n 'inline-flex items-stretch overflow-hidden',\n radiusClass[size],\n variant === 'solid' && 'shadow-raised',\n variant === 'outline' && `border ${getOutlineBorderColor(color)}`,\n )}\n >\n {triggerSide === 'left' && (\n <>\n {/* Dropdown trigger (left) */}\n <button\n type=\"button\"\n onClick={() => setOpen(!isOpen)}\n disabled={disabled}\n aria-label={dropdownLabel}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? menuId : undefined}\n className={cn(\n 'inline-flex items-center justify-center select-none',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n triggerPadding[size],\n )}\n style={triggerStyle}\n >\n {dropdownIcon ?? <ChevronDown />}\n </button>\n <div className={cn('w-px self-stretch', divider)} />\n </>\n )}\n\n {/* Primary action */}\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex items-center justify-center font-semibold select-none whitespace-nowrap',\n 'transition-[colors,transform] duration-fast-01 ease-productive-standard',\n 'active:scale-[0.97]',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n textClass[size],\n primaryPadding[size],\n )}\n >\n {children}\n </button>\n\n {triggerSide === 'right' && (\n <>\n <div className={cn('w-px self-stretch', divider)} />\n {/* Dropdown trigger (right) */}\n <button\n type=\"button\"\n onClick={() => setOpen(!isOpen)}\n disabled={disabled}\n aria-label={dropdownLabel}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? menuId : undefined}\n className={cn(\n 'inline-flex items-center justify-center select-none',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n triggerPadding[size],\n )}\n style={triggerStyle}\n >\n {dropdownIcon ?? <ChevronDown />}\n </button>\n </>\n )}\n </div>\n </div>\n\n {/* Floating dropdown panel */}\n <AnimatePresence>\n {isOpen && dropdownContent && (\n <div\n ref={floatingRef}\n id={menuId}\n role=\"menu\"\n className=\"fixed z-popover\"\n style={{ top: 0, left: 0 }}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: durations.moderate01 }}\n className=\"rounded-ds-lg border border-surface-border-strong bg-surface-overlay shadow-floating\"\n >\n {dropdownContent}\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n </>\n )\n },\n)\n\nSplitButton.displayName = 'SplitButton'\n\nexport { SplitButton }\nexport type { SplitButtonColor, SplitButtonSize,SplitButtonVariant }\n"],"mappings":"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"}
1
+ {"version":3,"file":"split-button.js","names":[],"sources":["../../src/ui/split-button.tsx"],"sourcesContent":["'use client'\n\nimport { autoUpdate, computePosition, flip, offset, type Placement,shift } from '@floating-ui/dom'\nimport { type VariantProps } from 'class-variance-authority'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { buttonVariants } from './button'\nimport { durations } from './lib/motion'\nimport { cn } from './lib/utils'\n\n// ── Types ───────────────────────────────────────────────────────\n\ntype SplitButtonVariant = 'solid' | 'soft' | 'outline'\ntype SplitButtonColor = NonNullable<VariantProps<typeof buttonVariants>['color']>\ntype SplitButtonSize = 'xs' | 'sm' | 'md' | 'icon-xs' | 'icon-sm' | 'icon-md'\n\nexport interface SplitButtonProps {\n /** Primary action content (left side). */\n children: React.ReactNode\n /** Primary click handler (left side). */\n onClick: (e: React.MouseEvent<HTMLButtonElement>) => void\n /** Content rendered inside the floating dropdown panel. */\n dropdownContent?: React.ReactNode\n /** Controlled open state. */\n open?: boolean\n /** Called when open state changes. Uncontrolled if omitted. */\n onOpenChange?: (open: boolean) => void\n /** @default 'solid' */\n variant?: SplitButtonVariant\n /** @default 'accent' */\n color?: SplitButtonColor\n /** @default 'md' */\n size?: SplitButtonSize\n /** Disable both halves. */\n disabled?: boolean\n /** aria-label for the primary button. */\n 'aria-label'?: string\n /** aria-label for the dropdown trigger. @default 'More options' */\n dropdownLabel?: string\n /** Custom icon for the dropdown trigger. Defaults to chevron-down. */\n dropdownIcon?: React.ReactNode\n /** Which side the dropdown trigger sits on. @default 'right' */\n triggerSide?: 'left' | 'right'\n /** Preferred placement for the dropdown panel. @default 'top-end' */\n placement?: Placement\n /** Width of the trigger (chevron) half. @default 'auto' */\n triggerWidth?: number | string\n className?: string\n}\n\n// ── Styling maps ────────────────────────────────────────────────\n\nconst dividerColor: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-11/20', error: 'bg-error-11/20', success: 'bg-success-11/20',\n warning: 'bg-warning-11/20', neutral: 'bg-neutral-8/30',\n },\n soft: {\n accent: 'bg-accent-6', error: 'bg-error-6', success: 'bg-success-6',\n warning: 'bg-warning-6', neutral: 'bg-surface-border',\n },\n outline: {\n accent: 'bg-accent-7', error: 'bg-error-7', success: 'bg-success-7',\n warning: 'bg-warning-7', neutral: 'bg-surface-border-strong',\n },\n}\n\nconst heightClass: Record<SplitButtonSize, string> = {\n xs: 'h-ds-xs-plus', sm: 'h-ds-sm', md: 'h-ds-md',\n 'icon-xs': 'h-ds-xs-plus', 'icon-sm': 'h-ds-sm', 'icon-md': 'h-ds-md',\n}\n\nconst textClass: Record<SplitButtonSize, string> = {\n xs: 'text-ds-sm', sm: 'text-ds-sm', md: 'text-ds-md',\n 'icon-xs': 'text-ds-sm', 'icon-sm': 'text-ds-sm', 'icon-md': 'text-ds-md',\n}\n\nconst primaryPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-03 gap-1', sm: 'px-ds-04 gap-1.5', md: 'px-ds-05 gap-2',\n 'icon-xs': 'px-ds-02', 'icon-sm': 'px-ds-03', 'icon-md': 'px-ds-04',\n}\n\nconst triggerPadding: Record<SplitButtonSize, string> = {\n xs: 'px-ds-02', sm: 'px-ds-02', md: 'px-ds-03',\n 'icon-xs': 'px-ds-01', 'icon-sm': 'px-ds-02', 'icon-md': 'px-ds-02',\n}\n\nconst radiusClass: Record<SplitButtonSize, string> = {\n xs: 'rounded-control', sm: 'rounded-control', md: 'rounded-control',\n 'icon-xs': 'rounded-control', 'icon-sm': 'rounded-control', 'icon-md': 'rounded-control',\n}\n\nfunction getHalfClasses(variant: SplitButtonVariant, color: string): string {\n const map: Record<SplitButtonVariant, Record<string, string>> = {\n solid: {\n accent: 'bg-accent-9 text-accent-fg hover:bg-accent-10 active:bg-accent-11',\n error: 'bg-error-9 text-error-fg hover:bg-error-10 active:bg-error-11',\n success: 'bg-success-9 text-success-fg hover:bg-success-10 active:bg-success-11',\n warning: 'bg-warning-9 text-warning-fg hover:bg-warning-10 active:bg-warning-11',\n neutral: 'bg-neutral-5 text-surface-fg hover:bg-neutral-7 active:bg-neutral-8',\n },\n soft: {\n accent: 'bg-accent-3 text-accent-11 hover:bg-accent-4 active:bg-accent-5',\n error: 'bg-error-3 text-error-11 hover:bg-error-4 active:bg-error-5',\n success: 'bg-success-3 text-success-11 hover:bg-success-4 active:bg-success-5',\n warning: 'bg-warning-3 text-warning-11 hover:bg-warning-4 active:bg-warning-5',\n neutral: 'bg-surface-raised-hover text-surface-fg-muted hover:bg-surface-raised-active active:bg-neutral-5',\n },\n outline: {\n accent: 'bg-transparent text-accent-11 hover:bg-accent-3 active:bg-accent-4',\n error: 'bg-transparent text-error-11 hover:bg-error-3 active:bg-error-4',\n success: 'bg-transparent text-success-11 hover:bg-success-3 active:bg-success-4',\n warning: 'bg-transparent text-warning-11 hover:bg-warning-3 active:bg-warning-4',\n neutral: 'bg-transparent text-surface-fg hover:bg-surface-raised-hover active:bg-surface-raised-active',\n },\n }\n return map[variant][color] ?? map[variant].accent\n}\n\nfunction getOutlineBorderColor(color: string): string {\n const map: Record<string, string> = {\n accent: 'border-accent-7', error: 'border-error-7', success: 'border-success-7',\n warning: 'border-warning-7', neutral: 'border-surface-border-strong',\n }\n return map[color] ?? map.accent\n}\n\n// ── Chevron SVG ─────────────────────────────────────────────────\n\nfunction ChevronDown({ className }: { className?: string }) {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" className={className} aria-hidden=\"true\">\n <path d=\"M3 4.5L6 7.5L9 4.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────\n\nconst SplitButton = React.forwardRef<HTMLDivElement, SplitButtonProps>(\n function SplitButton(\n {\n children,\n onClick,\n dropdownContent,\n open: openProp,\n onOpenChange,\n variant = 'solid',\n color = 'accent',\n size = 'md',\n disabled = false,\n 'aria-label': ariaLabel,\n dropdownLabel = 'More options',\n dropdownIcon,\n triggerSide = 'right',\n placement: placementProp = 'top-end',\n triggerWidth,\n className,\n },\n ref,\n ) {\n const [internalOpen, setInternalOpen] = React.useState(false)\n const isControlled = openProp !== undefined\n const isOpen = isControlled ? openProp : internalOpen\n\n const anchorRef = React.useRef<HTMLDivElement>(null)\n const floatingRef = React.useRef<HTMLDivElement>(null)\n const menuId = React.useId()\n\n const setOpen = React.useCallback(\n (value: boolean) => {\n if (onOpenChange) onOpenChange(value)\n if (!isControlled) setInternalOpen(value)\n },\n [onOpenChange, isControlled],\n )\n\n // ── Floating UI positioning ──\n React.useEffect(() => {\n if (!isOpen || !anchorRef.current || !floatingRef.current) return\n\n const anchor = anchorRef.current\n const floating = floatingRef.current\n\n const update = () => {\n computePosition(anchor, floating, {\n placement: placementProp,\n middleware: [\n offset(8),\n flip({ fallbackPlacements: ['bottom-end', 'top-start', 'bottom-start'] }),\n shift({ padding: 8 }),\n ],\n }).then(({ x, y }) => {\n Object.assign(floating.style, { left: `${x}px`, top: `${y}px` })\n })\n }\n\n const cleanup = autoUpdate(anchor, floating, update)\n return cleanup\n }, [isOpen, placementProp])\n\n // ── Close on outside click ──\n React.useEffect(() => {\n if (!isOpen) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node\n if (\n anchorRef.current?.contains(target) ||\n floatingRef.current?.contains(target)\n ) return\n setOpen(false)\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [isOpen, setOpen])\n\n // ── Close on Escape ──\n React.useEffect(() => {\n if (!isOpen) return\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setOpen(false)\n }\n document.addEventListener('keydown', handleKey)\n return () => document.removeEventListener('keydown', handleKey)\n }, [isOpen, setOpen])\n\n const halfClasses = getHalfClasses(variant, color)\n const divider = dividerColor[variant][color] ?? dividerColor[variant].accent\n\n const triggerStyle = triggerWidth != null\n ? { width: typeof triggerWidth === 'number' ? `${triggerWidth}px` : triggerWidth }\n : undefined\n\n return (\n <>\n {/* Button group */}\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel ?? undefined}\n className={cn('relative inline-flex', className)}\n >\n <div\n ref={anchorRef}\n className={cn(\n 'inline-flex items-stretch overflow-hidden',\n radiusClass[size],\n variant === 'solid' && 'shadow-raised',\n variant === 'outline' && `border ${getOutlineBorderColor(color)}`,\n )}\n >\n {triggerSide === 'left' && (\n <>\n {/* Dropdown trigger (left) */}\n <button\n type=\"button\"\n onClick={() => setOpen(!isOpen)}\n disabled={disabled}\n aria-label={dropdownLabel}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? menuId : undefined}\n className={cn(\n 'inline-flex items-center justify-center select-none',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n triggerPadding[size],\n )}\n style={triggerStyle}\n >\n {dropdownIcon ?? <ChevronDown />}\n </button>\n <div className={cn('w-px self-stretch', divider)} />\n </>\n )}\n\n {/* Primary action */}\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex items-center justify-center font-semibold select-none whitespace-nowrap',\n 'transition-[colors,transform] duration-fast-01 ease-productive-standard',\n 'active:scale-[0.97]',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n textClass[size],\n primaryPadding[size],\n )}\n >\n {children}\n </button>\n\n {triggerSide === 'right' && (\n <>\n <div className={cn('w-px self-stretch', divider)} />\n {/* Dropdown trigger (right) */}\n <button\n type=\"button\"\n onClick={() => setOpen(!isOpen)}\n disabled={disabled}\n aria-label={dropdownLabel}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? menuId : undefined}\n className={cn(\n 'inline-flex items-center justify-center select-none',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-inset',\n 'disabled:pointer-events-none disabled:opacity-action-disabled',\n halfClasses,\n heightClass[size],\n triggerPadding[size],\n )}\n style={triggerStyle}\n >\n {dropdownIcon ?? <ChevronDown />}\n </button>\n </>\n )}\n </div>\n </div>\n\n {/* Floating dropdown panel */}\n <AnimatePresence>\n {isOpen && dropdownContent && (\n <div\n ref={floatingRef}\n id={menuId}\n role=\"menu\"\n className=\"fixed z-popover\"\n style={{ top: 0, left: 0 }}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: durations.moderate01 }}\n className=\"rounded-overlay border border-surface-border-strong bg-surface-overlay shadow-floating\"\n >\n {dropdownContent}\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n </>\n )\n },\n)\n\nSplitButton.displayName = 'SplitButton'\n\nexport { SplitButton }\nexport type { SplitButtonColor, SplitButtonSize,SplitButtonVariant }\n"],"mappings":"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"}
@@ -80,14 +80,14 @@ function h({ data: e, colorClass: t }) {
80
80
  function g({ progress: e, label: t }) {
81
81
  let r = Math.max(0, Math.min(100, e)), i = r >= 90 ? "bg-success-9" : r >= 70 ? "bg-warning-9" : "bg-accent-9";
82
82
  return /* @__PURE__ */ s("div", {
83
- className: "h-1 w-full rounded-ds-full bg-surface-raised mt-ds-04",
83
+ className: "h-1 w-full rounded-pill bg-surface-raised mt-ds-04",
84
84
  role: "progressbar",
85
85
  "aria-label": `${t} progress`,
86
86
  "aria-valuenow": r,
87
87
  "aria-valuemin": 0,
88
88
  "aria-valuemax": 100,
89
89
  children: /* @__PURE__ */ s("div", {
90
- className: n("h-full rounded-ds-full transition-[width] duration-moderate-02 ease-productive-standard", i),
90
+ className: n("h-full rounded-pill transition-[width] duration-moderate-02 ease-productive-standard", i),
91
91
  style: { width: `${r}%` }
92
92
  })
93
93
  });
@@ -96,12 +96,12 @@ var _ = a.forwardRef(({ className: a, label: m, title: _, value: v, prefix: y, s
96
96
  let P = i(), F = _ ?? m ?? "", I = !!(k || A), L = I && !M["aria-label"] ? `View ${F}` : M["aria-label"];
97
97
  if (C) return /* @__PURE__ */ c("div", {
98
98
  ref: N,
99
- className: n("rounded-ds-lg border border-surface-border bg-surface-raised shadow-raised p-ds-05b", D && `border-l-[3px] ${p[D]}`, a),
99
+ className: n("rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b", D && `border-l-[3px] ${p[D]}`, a),
100
100
  ...M,
101
101
  children: [
102
- /* @__PURE__ */ s("div", { className: "h-ds-04 w-24 rounded-ds-sm bg-skeleton-base animate-pulse mb-ds-05" }),
103
- /* @__PURE__ */ s("div", { className: "h-ds-sm w-32 rounded-ds-md bg-skeleton-base animate-pulse mb-ds-03" }),
104
- /* @__PURE__ */ s("div", { className: "h-3 w-16 rounded-ds-sm bg-skeleton-base animate-pulse" })
102
+ /* @__PURE__ */ s("div", { className: "h-ds-04 w-24 rounded-control-inner bg-skeleton-base animate-pulse mb-ds-05" }),
103
+ /* @__PURE__ */ s("div", { className: "h-ds-sm w-32 rounded-control bg-skeleton-base animate-pulse mb-ds-03" }),
104
+ /* @__PURE__ */ s("div", { className: "h-3 w-16 rounded-control-inner bg-skeleton-base animate-pulse" })
105
105
  ]
106
106
  });
107
107
  let R = x?.direction === "up" ? d : x?.direction === "down" ? u : l, z = x?.direction === "up" ? "text-success-11" : x?.direction === "down" ? "text-error-11" : "text-surface-fg-muted", B = x?.direction === "up" ? "text-success-11" : x?.direction === "down" ? "text-error-11" : "text-accent-11", V = /* @__PURE__ */ c(o, { children: [
@@ -236,7 +236,7 @@ var _ = a.forwardRef(({ className: a, label: m, title: _, value: v, prefix: y, s
236
236
  },
237
237
  children: j
238
238
  })
239
- ] }), H = n("rounded-ds-lg border border-surface-border bg-surface-raised shadow-raised p-ds-05b", D && `border-l-[3px] ${p[D]}`, I && "cursor-pointer hover:shadow-raised-hover hover:border-surface-border-strong transition-[box-shadow,border-color] duration-fast-02 ease-productive-standard group", a);
239
+ ] }), H = n("rounded-surface border border-surface-border bg-surface-raised shadow-raised p-ds-05b", D && `border-l-[3px] ${p[D]}`, I && "cursor-pointer hover:shadow-raised-hover hover:border-surface-border-strong transition-[box-shadow,border-color] duration-fast-02 ease-productive-standard group", a);
240
240
  return A ? /* @__PURE__ */ s(P, {
241
241
  ref: N,
242
242
  href: A,
@@ -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 { useLink } from './lib/link-context'\nimport { springs, tweens } from './lib/motion'\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?: React.ReactNode | React.ComponentType<{ className?: string }>\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-ds-full 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-ds-full 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-ds-lg 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-ds-sm bg-skeleton-base animate-pulse mb-ds-05\" />\n <div className=\"h-ds-sm w-32 rounded-ds-md bg-skeleton-base animate-pulse mb-ds-03\" />\n <div className=\"h-3 w-16 rounded-ds-sm 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 {typeof icon === 'function'\n ? <Icon icon={icon as React.ForwardRefExoticComponent<any>} size=\"lg\" />\n : icon}\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-ds-lg 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":";;;;;;;;;;AAqFA,IAAM,IAAwE;CAC5E,SAAS;CACT,SAAS;CACT,SAAS;CACT,OAAO;CACP,MAAM;CACP;AAED,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;KAAS;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;EAAwD,MAAK;EAAc,cAAY,GAAG,EAAM;EAAY,iBAAe;EAAS,iBAAe;EAAG,iBAAe;YAClL,kBAAC,OAAD;GACE,WAAW,EAAG,2FAA2F,EAAS;GAClH,OAAO,EAAE,OAAO,GAAG,EAAQ,IAAI;GAC/B,CAAA;EACE,CAAA;;AAIV,IAAM,IAAW,EAAM,YAEnB,EACE,cACA,UACA,UACA,UACA,WACA,WACA,UACA,SACA,YACA,oBACA,mBACA,aACA,WACA,cACA,YACA,SACA,WACA,GAAG,KAEL,MACG;CACH,IAAM,IAAO,GAAS,EAChB,IAAgB,KAAS,KAAS,IAClC,IAAc,CAAC,EAAE,KAAW,IAC5B,IACJ,KAAe,CAAC,EAAM,gBAAgB,QAAQ,MAAkB,EAAM;AAExE,KAAI,EACF,QACE,kBAAC,OAAD;EACO;EACL,WAAW,EACT,uFACA,KAAU,kBAAkB,EAAgB,MAC5C,EACD;EACD,GAAI;YAPN;GASE,kBAAC,OAAD,EAAK,WAAU,sEAAuE,CAAA;GACtF,kBAAC,OAAD,EAAK,WAAU,sEAAuE,CAAA;GACtF,kBAAC,OAAD,EAAK,WAAU,yDAA0D,CAAA;GACrE;;CAIV,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,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,KACC,kBAAC,EAAO,MAAR;KACE,WAAU;KACV,SAAS;MAAE,SAAS;MAAG,OAAO;MAAM;KACpC,SAAS;MAAE,SAAS;MAAG,OAAO;MAAG;KACjC,YAAY,EAAQ;KACpB,eAAY;eAEX,OAAO,KAAS,aACb,kBAAC,GAAD;MAAY;MAA8C,MAAK;MAAO,CAAA,GACtE;KACQ,CAAA,CAEZ;MACF;;EACN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,EAAO,GAAR;IACE,WAAU;IACV,SAAS;KAAE,SAAS;KAAG,GAAG;KAAQ;IAClC,SAAS;KAAE,SAAS;KAAG,GAAG;KAAG;IAC7B,YAAY,EAAQ;cAJtB;KAMG,KACC,kBAAC,QAAD;MAAM,WAAU;gBAAoC;MAAc,CAAA;KAEpE,kBAAC,QAAD;MAAM,WAAU;gBAAgB;MAAa,CAAA;KAC5C,KACC,kBAAC,QAAD;MAAM,WAAU;gBAAoC;MAAc,CAAA;KAE3D;;GACP,CAAA;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,KACC,kBAAC,EAAO,KAAR;GACE,WAAW,EACT,+DACA,EACD;GACD,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;GAC7B,SAAS;IAAE,SAAS;IAAG,GAAG;IAAG;GAC7B,YAAY;IAAE,GAAG,EAAQ;IAAQ,OAAO;IAAK;aAP/C;IASE,kBAAC,EAAO,MAAR;KACE,WAAU;KACV,SAAS;MAAE,SAAS;MAAK,OAAO;MAAK;KACrC,SAAS;MAAE,SAAS;MAAG,OAAO;MAAG;KACjC,YAAY,EAAQ;eAEpB,kBAAC,GAAD;MAAM,MAAM;MAAW,MAAK;MAAO,CAAA;KACvB,CAAA;IACd,kBAAC,QAAD,EAAA,UAAO,EAAM,OAAa,CAAA;IACzB,KACC,kBAAC,QAAD;KAAM,WAAU;eAAsC;KAAuB,CAAA;IAEpE;;EAEd,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,EAGC,IAAc,EAClB,uFACA,KAAU,kBAAkB,EAAgB,MAC5C,KACE,oKACF,EACD;AAiBD,QAfI,IAEA,kBAAC,GAAD;EACO;EACC;EACG;EACT,WAAW,EAAG,GAAa,qBAAqB;EAChD,cAAY;EACZ,GAAK;YAEJ;EACI,CAAA,GAKT,kBAAC,OAAD;EACO;EACL,WAAW;EACF;EACT,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;EACxB,cAAY;EACZ,WACE,KACK,MAA2B;AAC1B,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,GAAS;MAGb,KAAA;EAEN,GAAI;YAEH;EACG,CAAA;EAGX;AAED,EAAS,cAAc"}
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 { useLink } from './lib/link-context'\nimport { springs, tweens } from './lib/motion'\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?: React.ReactNode | React.ComponentType<{ className?: string }>\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 {typeof icon === 'function'\n ? <Icon icon={icon as React.ForwardRefExoticComponent<any>} size=\"lg\" />\n : icon}\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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"}
@@ -44,7 +44,7 @@ var i = {
44
44
  text: "text-ds-sm"
45
45
  }
46
46
  }, l = t.forwardRef(({ status: t, size: l = "md", variant: u = "filled", pulse: d, label: f, labelClassName: p, className: m, "aria-label": h, ...g }, _) => {
47
- let v = d ?? t === "healthy", y = c[l], b = f ? `${f} \u2014 ${t}` : `Status: ${t}`, x = u === "ring" ? e("relative inline-flex rounded-full border-[1.5px]", y.dot, o[t], "bg-transparent") : e("relative inline-flex rounded-full", y.dot, i[t]);
47
+ let v = d ?? t === "healthy", y = c[l], b = f ? `${f} \u2014 ${t}` : `Status: ${t}`, x = u === "ring" ? e("relative inline-flex rounded-pill border-[1.5px]", y.dot, o[t], "bg-transparent") : e("relative inline-flex rounded-pill", y.dot, i[t]);
48
48
  return /* @__PURE__ */ r("span", {
49
49
  ref: _,
50
50
  role: "status",
@@ -55,7 +55,7 @@ var i = {
55
55
  className: "relative inline-flex shrink-0",
56
56
  children: [v && /* @__PURE__ */ n("span", {
57
57
  "data-pulse": "",
58
- className: e("absolute inline-flex rounded-full animate-ping motion-reduce:animate-none", y.pulse, s[t])
58
+ className: e("absolute inline-flex rounded-pill animate-ping motion-reduce:animate-none", y.pulse, s[t])
59
59
  }), /* @__PURE__ */ n("span", { className: x })]
60
60
  }), f && /* @__PURE__ */ n("span", {
61
61
  className: e(y.text, a[t], "font-sans", p),
@@ -1 +1 @@
1
- {"version":3,"file":"status-dot.js","names":[],"sources":["../../src/ui/status-dot.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\ntype Status = 'healthy' | 'warning' | 'critical' | 'neutral' | 'inactive'\n\nconst dotColorMap: Record<Status, string> = {\n healthy: 'bg-success-9',\n warning: 'bg-warning-9',\n critical: 'bg-error-9',\n neutral: 'bg-neutral-8',\n inactive: 'bg-neutral-6',\n}\n\nconst textColorMap: Record<Status, string> = {\n healthy: 'text-success-11',\n warning: 'text-warning-11',\n critical: 'text-error-11',\n neutral: 'text-surface-fg-muted',\n inactive: 'text-surface-fg-subtle',\n}\n\nconst ringBorderColorMap: Record<Status, string> = {\n healthy: 'border-success-9',\n warning: 'border-warning-9',\n critical: 'border-error-9',\n neutral: 'border-neutral-8',\n inactive: 'border-neutral-6',\n}\n\nconst pulseColorMap: Record<Status, string> = {\n healthy: 'bg-success-9/40',\n warning: 'bg-warning-9/40',\n critical: 'bg-error-9/40',\n neutral: 'bg-neutral-8/40',\n inactive: 'bg-neutral-6/40',\n}\n\nconst sizeMap = {\n sm: { dot: 'h-1.5 w-1.5', pulse: 'h-1.5 w-1.5', text: 'text-ds-xs' },\n md: { dot: 'h-2 w-2', pulse: 'h-2 w-2', text: 'text-ds-sm' },\n lg: { dot: 'h-2.5 w-2.5', pulse: 'h-2.5 w-2.5', text: 'text-ds-sm' },\n} as const\n\n/**\n * A semantic health/presence indicator dot with optional pulse animation and inline label.\n *\n * @example\n * <StatusDot status=\"healthy\" />\n * <StatusDot status=\"critical\" label=\"Service down\" pulse />\n * <StatusDot status=\"warning\" size=\"lg\" label=\"Elevated load\" />\n */\nexport interface StatusDotProps extends React.HTMLAttributes<HTMLSpanElement> {\n status: Status\n /** @default 'md' */\n size?: 'sm' | 'md' | 'lg'\n /** @default 'filled' */\n variant?: 'filled' | 'ring'\n /** Pulse animation. Defaults to true for 'healthy', false for others. */\n pulse?: boolean\n /** Inline label text rendered after the dot */\n label?: string\n labelClassName?: string\n}\n\nconst StatusDot = React.forwardRef<HTMLSpanElement, StatusDotProps>(\n ({ status, size = 'md', variant = 'filled', pulse, label, labelClassName, className, 'aria-label': ariaLabelProp, ...props }, ref) => {\n const shouldPulse = pulse ?? status === 'healthy'\n const s = sizeMap[size]\n const autoAriaLabel = label ? `${label} \\u2014 ${status}` : `Status: ${status}`\n\n const dotClasses = variant === 'ring'\n ? cn('relative inline-flex rounded-full border-[1.5px]', s.dot, ringBorderColorMap[status], 'bg-transparent')\n : cn('relative inline-flex rounded-full', s.dot, dotColorMap[status])\n\n return (\n <span\n ref={ref}\n role=\"status\"\n aria-label={ariaLabelProp ?? autoAriaLabel}\n className={cn('inline-flex items-center gap-ds-02', className)}\n {...props}\n >\n <span className=\"relative inline-flex shrink-0\">\n {shouldPulse && (\n <span\n data-pulse=\"\"\n className={cn('absolute inline-flex rounded-full animate-ping motion-reduce:animate-none', s.pulse, pulseColorMap[status])}\n />\n )}\n <span className={dotClasses} />\n </span>\n {label && (\n <span className={cn(s.text, textColorMap[status], 'font-sans', labelClassName)}>\n {label}\n </span>\n )}\n </span>\n )\n },\n)\nStatusDot.displayName = 'StatusDot'\n\nexport { StatusDot }\nexport type { Status as StatusDotStatus }\n"],"mappings":";;;;;AAQA,IAAM,IAAsC;CAC1C,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAAuC;CAC3C,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAA6C;CACjD,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAAwC;CAC5C,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAAU;CACd,IAAI;EAAE,KAAK;EAAe,OAAO;EAAe,MAAM;EAAc;CACpE,IAAI;EAAE,KAAK;EAAW,OAAO;EAAW,MAAM;EAAc;CAC5D,IAAI;EAAE,KAAK;EAAe,OAAO;EAAe,MAAM;EAAc;CACrE,EAuBK,IAAY,EAAM,YACrB,EAAE,WAAQ,UAAO,MAAM,aAAU,UAAU,UAAO,UAAO,mBAAgB,cAAW,cAAc,GAAe,GAAG,KAAS,MAAQ;CACpI,IAAM,IAAc,KAAS,MAAW,WAClC,IAAI,EAAQ,IACZ,IAAgB,IAAQ,GAAG,EAAM,UAAU,MAAW,WAAW,KAEjE,IAAa,MAAY,SAC3B,EAAG,oDAAoD,EAAE,KAAK,EAAmB,IAAS,iBAAiB,GAC3G,EAAG,qCAAqC,EAAE,KAAK,EAAY,GAAQ;AAEvE,QACE,kBAAC,QAAD;EACO;EACL,MAAK;EACL,cAAY,KAAiB;EAC7B,WAAW,EAAG,sCAAsC,EAAU;EAC9D,GAAI;YALN,CAOE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACG,KACC,kBAAC,QAAD;IACE,cAAW;IACX,WAAW,EAAG,6EAA6E,EAAE,OAAO,EAAc,GAAQ;IAC1H,CAAA,EAEJ,kBAAC,QAAD,EAAM,WAAW,GAAc,CAAA,CAC1B;MACN,KACC,kBAAC,QAAD;GAAM,WAAW,EAAG,EAAE,MAAM,EAAa,IAAS,aAAa,EAAe;aAC3E;GACI,CAAA,CAEJ;;EAGZ;AACD,EAAU,cAAc"}
1
+ {"version":3,"file":"status-dot.js","names":[],"sources":["../../src/ui/status-dot.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { cn } from './lib/utils'\n\ntype Status = 'healthy' | 'warning' | 'critical' | 'neutral' | 'inactive'\n\nconst dotColorMap: Record<Status, string> = {\n healthy: 'bg-success-9',\n warning: 'bg-warning-9',\n critical: 'bg-error-9',\n neutral: 'bg-neutral-8',\n inactive: 'bg-neutral-6',\n}\n\nconst textColorMap: Record<Status, string> = {\n healthy: 'text-success-11',\n warning: 'text-warning-11',\n critical: 'text-error-11',\n neutral: 'text-surface-fg-muted',\n inactive: 'text-surface-fg-subtle',\n}\n\nconst ringBorderColorMap: Record<Status, string> = {\n healthy: 'border-success-9',\n warning: 'border-warning-9',\n critical: 'border-error-9',\n neutral: 'border-neutral-8',\n inactive: 'border-neutral-6',\n}\n\nconst pulseColorMap: Record<Status, string> = {\n healthy: 'bg-success-9/40',\n warning: 'bg-warning-9/40',\n critical: 'bg-error-9/40',\n neutral: 'bg-neutral-8/40',\n inactive: 'bg-neutral-6/40',\n}\n\nconst sizeMap = {\n sm: { dot: 'h-1.5 w-1.5', pulse: 'h-1.5 w-1.5', text: 'text-ds-xs' },\n md: { dot: 'h-2 w-2', pulse: 'h-2 w-2', text: 'text-ds-sm' },\n lg: { dot: 'h-2.5 w-2.5', pulse: 'h-2.5 w-2.5', text: 'text-ds-sm' },\n} as const\n\n/**\n * A semantic health/presence indicator dot with optional pulse animation and inline label.\n *\n * @example\n * <StatusDot status=\"healthy\" />\n * <StatusDot status=\"critical\" label=\"Service down\" pulse />\n * <StatusDot status=\"warning\" size=\"lg\" label=\"Elevated load\" />\n */\nexport interface StatusDotProps extends React.HTMLAttributes<HTMLSpanElement> {\n status: Status\n /** @default 'md' */\n size?: 'sm' | 'md' | 'lg'\n /** @default 'filled' */\n variant?: 'filled' | 'ring'\n /** Pulse animation. Defaults to true for 'healthy', false for others. */\n pulse?: boolean\n /** Inline label text rendered after the dot */\n label?: string\n labelClassName?: string\n}\n\nconst StatusDot = React.forwardRef<HTMLSpanElement, StatusDotProps>(\n ({ status, size = 'md', variant = 'filled', pulse, label, labelClassName, className, 'aria-label': ariaLabelProp, ...props }, ref) => {\n const shouldPulse = pulse ?? status === 'healthy'\n const s = sizeMap[size]\n const autoAriaLabel = label ? `${label} \\u2014 ${status}` : `Status: ${status}`\n\n const dotClasses = variant === 'ring'\n ? cn('relative inline-flex rounded-pill border-[1.5px]', s.dot, ringBorderColorMap[status], 'bg-transparent')\n : cn('relative inline-flex rounded-pill', s.dot, dotColorMap[status])\n\n return (\n <span\n ref={ref}\n role=\"status\"\n aria-label={ariaLabelProp ?? autoAriaLabel}\n className={cn('inline-flex items-center gap-ds-02', className)}\n {...props}\n >\n <span className=\"relative inline-flex shrink-0\">\n {shouldPulse && (\n <span\n data-pulse=\"\"\n className={cn('absolute inline-flex rounded-pill animate-ping motion-reduce:animate-none', s.pulse, pulseColorMap[status])}\n />\n )}\n <span className={dotClasses} />\n </span>\n {label && (\n <span className={cn(s.text, textColorMap[status], 'font-sans', labelClassName)}>\n {label}\n </span>\n )}\n </span>\n )\n },\n)\nStatusDot.displayName = 'StatusDot'\n\nexport { StatusDot }\nexport type { Status as StatusDotStatus }\n"],"mappings":";;;;;AAQA,IAAM,IAAsC;CAC1C,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAAuC;CAC3C,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAA6C;CACjD,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAAwC;CAC5C,SAAS;CACT,SAAS;CACT,UAAU;CACV,SAAS;CACT,UAAU;CACX,EAEK,IAAU;CACd,IAAI;EAAE,KAAK;EAAe,OAAO;EAAe,MAAM;EAAc;CACpE,IAAI;EAAE,KAAK;EAAW,OAAO;EAAW,MAAM;EAAc;CAC5D,IAAI;EAAE,KAAK;EAAe,OAAO;EAAe,MAAM;EAAc;CACrE,EAuBK,IAAY,EAAM,YACrB,EAAE,WAAQ,UAAO,MAAM,aAAU,UAAU,UAAO,UAAO,mBAAgB,cAAW,cAAc,GAAe,GAAG,KAAS,MAAQ;CACpI,IAAM,IAAc,KAAS,MAAW,WAClC,IAAI,EAAQ,IACZ,IAAgB,IAAQ,GAAG,EAAM,UAAU,MAAW,WAAW,KAEjE,IAAa,MAAY,SAC3B,EAAG,oDAAoD,EAAE,KAAK,EAAmB,IAAS,iBAAiB,GAC3G,EAAG,qCAAqC,EAAE,KAAK,EAAY,GAAQ;AAEvE,QACE,kBAAC,QAAD;EACO;EACL,MAAK;EACL,cAAY,KAAiB;EAC7B,WAAW,EAAG,sCAAsC,EAAU;EAC9D,GAAI;YALN,CAOE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACG,KACC,kBAAC,QAAD;IACE,cAAW;IACX,WAAW,EAAG,6EAA6E,EAAE,OAAO,EAAc,GAAQ;IAC1H,CAAA,EAEJ,kBAAC,QAAD,EAAM,WAAW,GAAc,CAAA,CAC1B;MACN,KACC,kBAAC,QAAD;GAAM,WAAW,EAAG,EAAE,MAAM,EAAa,IAAS,aAAa,EAAe;aAC3E;GACI,CAAA,CAEJ;;EAGZ;AACD,EAAU,cAAc"}
@@ -40,7 +40,7 @@ var c = n.createContext({
40
40
  });
41
41
  }), u = n.forwardRef(({ label: e, description: o, icon: s, className: l, ...u }, d) => {
42
42
  let { _index: f = 0, ...p } = u, { activeStep: m, orientation: h, stepperId: g, onStepClick: _ } = n.useContext(c), v = f < m ? "completed" : f === m ? "active" : "pending", y = v === "completed" && !!_, b = /* @__PURE__ */ a(r, { children: [/* @__PURE__ */ i("div", {
43
- className: t("relative shrink-0 flex items-center justify-center w-ds-sm h-ds-sm rounded-ds-full text-ds-sm font-semibold", "transition-[background-color,border-color,color] duration-moderate-01 ease-productive-standard", v === "completed" && "bg-accent-9 text-accent-fg", v === "active" && "bg-accent-9 text-accent-fg", v === "pending" && "bg-surface-raised text-surface-fg-subtle border border-surface-border-strong"),
43
+ className: t("relative shrink-0 flex items-center justify-center w-ds-sm h-ds-sm rounded-pill text-ds-sm font-semibold", "transition-[background-color,border-color,color] duration-moderate-01 ease-productive-standard", v === "completed" && "bg-accent-9 text-accent-fg", v === "active" && "bg-accent-9 text-accent-fg", v === "pending" && "bg-surface-raised text-surface-fg-subtle border border-surface-border-strong"),
44
44
  children: s || (v === "completed" ? /* @__PURE__ */ i("svg", {
45
45
  className: "w-ico-sm h-ico-sm",
46
46
  viewBox: "0 0 24 24",
@@ -73,7 +73,7 @@ var c = n.createContext({
73
73
  tabIndex: 0,
74
74
  "aria-label": `Go to step ${f + 1}: ${e}`,
75
75
  onClick: () => _(f),
76
- className: t("flex items-center gap-ds-03 cursor-pointer select-none", "rounded-ds-md px-ds-02 py-ds-01 -mx-ds-02 -my-ds-01", "bg-transparent border-0", "hover:bg-surface-raised-hover active:bg-surface-raised-active", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-8 focus-visible:ring-offset-2", "transition-colors duration-fast-01 ease-productive-standard"),
76
+ className: t("flex items-center gap-ds-03 cursor-pointer select-none", "rounded-control px-ds-02 py-ds-01 -mx-ds-02 -my-ds-01", "bg-transparent border-0", "hover:bg-surface-raised-hover active:bg-surface-raised-active", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-8 focus-visible:ring-offset-2", "transition-colors duration-fast-01 ease-productive-standard"),
77
77
  children: b
78
78
  })
79
79
  }) : /* @__PURE__ */ i("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"stepper.js","names":[],"sources":["../../src/ui/stepper.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs } from './lib/motion'\nimport { cn } from './lib/utils'\n\ntype StepState = 'completed' | 'active' | 'pending'\n\ntype StepperContextValue = {\n activeStep: number\n orientation: 'horizontal' | 'vertical'\n stepperId: string\n onStepClick?: (stepIndex: number) => void\n}\n\nconst StepperContext = React.createContext<StepperContextValue>({\n activeStep: 0,\n orientation: 'horizontal',\n stepperId: '',\n})\n\n/**\n * Props for Stepper — a multi-step progress indicator that renders a connected series of `<Step>`\n * children with connector lines, showing completed/active/pending states automatically.\n *\n * **`activeStep`:** 0-indexed. Step at `activeStep` is \"active\", steps before it are \"completed\",\n * steps after it are \"pending\". Update this value to advance the stepper.\n *\n * **Orientation:** `'horizontal'` (default, left-to-right) | `'vertical'` (top-to-bottom, for sidebars).\n *\n * **Children:** Must be `<Step>` components. The Stepper injects `_index` into each child automatically.\n *\n * @example\n * // Horizontal 3-step form wizard:\n * <Stepper activeStep={currentStep}>\n * <Step label=\"Account\" description=\"Create your credentials\" />\n * <Step label=\"Profile\" description=\"Add your details\" />\n * <Step label=\"Review\" description=\"Confirm and submit\" />\n * </Stepper>\n *\n * @example\n * // Vertical onboarding checklist in a sidebar:\n * <Stepper activeStep={completedSteps} orientation=\"vertical\">\n * <Step label=\"Connect workspace\" />\n * <Step label=\"Invite teammates\" />\n * <Step label=\"Set up billing\" />\n * </Stepper>\n *\n * @example\n * // Clickable completed steps — user can navigate back:\n * <Stepper activeStep={step} onStepClick={(i) => setStep(i)}>\n * <Step label=\"Account\" />\n * <Step label=\"Profile\" />\n * <Step label=\"Review\" />\n * </Stepper>\n * // These are just a few ways — feel free to combine props creatively!\n */\ninterface StepperProps extends React.HTMLAttributes<HTMLDivElement> {\n activeStep: number\n orientation?: 'horizontal' | 'vertical'\n /** Called when a completed step is clicked. Receives the 0-based step index. */\n onStepClick?: (stepIndex: number) => void\n children: React.ReactNode\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n ({ activeStep, orientation = 'horizontal', onStepClick, className, children, ...props }, ref) => {\n const steps = React.Children.toArray(children)\n const stepperId = React.useId()\n const contextValue = React.useMemo(\n () => ({ activeStep, orientation, stepperId, onStepClick }),\n [activeStep, orientation, stepperId, onStepClick],\n )\n return (\n <StepperContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn(\n 'flex gap-ds-02',\n orientation === 'vertical' ? 'flex-col' : 'flex-row items-center',\n className,\n )}\n role=\"list\"\n {...props}\n >\n {steps.map((child, index) => (\n <React.Fragment key={index}>\n {React.isValidElement<StepInternalProps>(child)\n ? React.cloneElement(child, { _index: index })\n : child}\n {index < steps.length - 1 && (\n <div\n className={cn(\n 'relative flex-1 overflow-hidden',\n orientation === 'vertical'\n ? 'ml-ds-04 w-ds-01 min-h-ds-05'\n : 'h-ds-01 min-w-ds-05',\n 'bg-surface-border',\n )}\n aria-hidden=\"true\"\n >\n {/* Filled portion — smooth CSS transition */}\n <div\n className={cn(\n 'absolute inset-0 bg-accent-9 transition-transform duration-moderate-02 ease-productive-standard',\n orientation === 'vertical' ? 'origin-top' : 'origin-left',\n )}\n style={{\n transform: `${orientation === 'vertical' ? 'scaleY' : 'scaleX'}(${index < activeStep ? 1 : 0})`,\n }}\n />\n </div>\n )}\n </React.Fragment>\n ))}\n </div>\n </StepperContext.Provider>\n )\n },\n)\n\n/**\n * Props for Step — a single step within a `<Stepper>`. The visual state (completed/active/pending)\n * is derived from the parent Stepper's `activeStep` and this step's position — you don't set it manually.\n *\n * @example\n * // Basic step with label and description:\n * <Step label=\"Payment\" description=\"Enter your card details\" />\n *\n * @example\n * // Step with a custom icon (overrides the default number/checkmark):\n * <Step label=\"Verified\" icon={<IconShieldCheck className=\"h-ico-sm w-ico-sm\" />} />\n */\ninterface StepProps extends React.HTMLAttributes<HTMLDivElement> {\n label: string\n description?: string\n icon?: React.ReactNode\n}\n\ntype StepInternalProps = StepProps & { _index?: number }\n\nconst Step = React.forwardRef<HTMLDivElement, StepProps>(\n ({ label, description, icon, className, ...props }, ref) => {\n const { _index = 0, ...restProps } = props as StepInternalProps\n const { activeStep, orientation, stepperId, onStepClick } = React.useContext(StepperContext)\n const state: StepState = _index < activeStep ? 'completed' : _index === activeStep ? 'active' : 'pending'\n const isClickable = state === 'completed' && !!onStepClick\n\n const stepContent = (\n <>\n <div\n className={cn(\n 'relative shrink-0 flex items-center justify-center w-ds-sm h-ds-sm rounded-ds-full text-ds-sm font-semibold',\n 'transition-[background-color,border-color,color] duration-moderate-01 ease-productive-standard',\n state === 'completed' && 'bg-accent-9 text-accent-fg',\n state === 'active' && 'bg-accent-9 text-accent-fg',\n state === 'pending' && 'bg-surface-raised text-surface-fg-subtle border border-surface-border-strong',\n )}\n >\n {icon || (state === 'completed' ? (\n <svg\n className=\"w-ico-sm h-ico-sm\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n ) : (\n _index + 1\n ))}\n </div>\n <div className=\"flex flex-col\">\n <span\n className={cn(\n 'text-ds-md font-medium leading-ds-snug',\n state === 'pending'\n ? 'text-surface-fg-subtle'\n : 'text-surface-fg',\n )}\n >\n {label}\n </span>\n {description && (\n <span className=\"text-ds-sm text-surface-fg-muted leading-ds-relaxed\">\n {description}\n </span>\n )}\n </div>\n </>\n )\n\n if (isClickable) {\n return (\n <div\n ref={ref}\n data-step=\"\"\n data-state={state}\n role=\"listitem\"\n className={cn(\n orientation === 'vertical' && 'py-ds-02',\n className,\n )}\n {...restProps}\n >\n <button\n type=\"button\"\n tabIndex={0}\n aria-label={`Go to step ${_index + 1}: ${label}`}\n onClick={() => onStepClick(_index)}\n className={cn(\n 'flex items-center gap-ds-03 cursor-pointer select-none',\n 'rounded-ds-md px-ds-02 py-ds-01 -mx-ds-02 -my-ds-01',\n 'bg-transparent border-0',\n 'hover:bg-surface-raised-hover active:bg-surface-raised-active',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-8 focus-visible:ring-offset-2',\n 'transition-colors duration-fast-01 ease-productive-standard',\n )}\n >\n {stepContent}\n </button>\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n data-step=\"\"\n data-state={state}\n role=\"listitem\"\n aria-current={state === 'active' ? 'step' : undefined}\n aria-label={`Step ${_index + 1}: ${label}, ${state === 'completed' ? 'completed' : state === 'active' ? 'current' : 'upcoming'}`}\n className={cn(\n 'flex items-center gap-ds-03',\n orientation === 'vertical' && 'py-ds-02',\n className,\n )}\n {...restProps}\n >\n {stepContent}\n </div>\n )\n },\n)\n\n/**\n * Props for StepperContent — wraps the content panel for the active step, animating\n * slide transitions when `activeStep` changes. Detects forward vs backward navigation\n * and slides content accordingly.\n *\n * @example\n * <StepperContent activeStep={currentStep}>\n * {currentStep === 0 && <AccountForm />}\n * {currentStep === 1 && <ProfileForm />}\n * {currentStep === 2 && <ReviewPanel />}\n * </StepperContent>\n */\ninterface StepperContentProps {\n activeStep: number\n children: React.ReactNode\n className?: string\n}\n\nconst SLIDE_OFFSET = 40\n\nconst stepContentVariants = {\n enter: (direction: number) => ({\n opacity: 0,\n x: direction * SLIDE_OFFSET,\n }),\n center: {\n opacity: 1,\n x: 0,\n },\n exit: (direction: number) => ({\n opacity: 0,\n x: direction * -SLIDE_OFFSET,\n }),\n}\n\nfunction StepperContent({ activeStep, children, className }: StepperContentProps) {\n const prevStepRef = React.useRef(activeStep)\n const direction = activeStep >= prevStepRef.current ? 1 : -1\n\n React.useEffect(() => {\n prevStepRef.current = activeStep\n }, [activeStep])\n\n return (\n <div className={cn('relative overflow-hidden', className)}>\n <AnimatePresence mode=\"wait\" custom={direction}>\n <motion.div\n key={activeStep}\n custom={direction}\n variants={stepContentVariants}\n initial=\"enter\"\n animate=\"center\"\n exit=\"exit\"\n transition={springs.smooth}\n >\n {children}\n </motion.div>\n </AnimatePresence>\n </div>\n )\n}\n\nStepper.displayName = 'Stepper'\nStep.displayName = 'Step'\nStepperContent.displayName = 'StepperContent'\n\nexport { Step, Stepper, StepperContent, type StepperContentProps,type StepperProps, type StepProps }\n"],"mappings":"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"}
1
+ {"version":3,"file":"stepper.js","names":[],"sources":["../../src/ui/stepper.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { springs } from './lib/motion'\nimport { cn } from './lib/utils'\n\ntype StepState = 'completed' | 'active' | 'pending'\n\ntype StepperContextValue = {\n activeStep: number\n orientation: 'horizontal' | 'vertical'\n stepperId: string\n onStepClick?: (stepIndex: number) => void\n}\n\nconst StepperContext = React.createContext<StepperContextValue>({\n activeStep: 0,\n orientation: 'horizontal',\n stepperId: '',\n})\n\n/**\n * Props for Stepper — a multi-step progress indicator that renders a connected series of `<Step>`\n * children with connector lines, showing completed/active/pending states automatically.\n *\n * **`activeStep`:** 0-indexed. Step at `activeStep` is \"active\", steps before it are \"completed\",\n * steps after it are \"pending\". Update this value to advance the stepper.\n *\n * **Orientation:** `'horizontal'` (default, left-to-right) | `'vertical'` (top-to-bottom, for sidebars).\n *\n * **Children:** Must be `<Step>` components. The Stepper injects `_index` into each child automatically.\n *\n * @example\n * // Horizontal 3-step form wizard:\n * <Stepper activeStep={currentStep}>\n * <Step label=\"Account\" description=\"Create your credentials\" />\n * <Step label=\"Profile\" description=\"Add your details\" />\n * <Step label=\"Review\" description=\"Confirm and submit\" />\n * </Stepper>\n *\n * @example\n * // Vertical onboarding checklist in a sidebar:\n * <Stepper activeStep={completedSteps} orientation=\"vertical\">\n * <Step label=\"Connect workspace\" />\n * <Step label=\"Invite teammates\" />\n * <Step label=\"Set up billing\" />\n * </Stepper>\n *\n * @example\n * // Clickable completed steps — user can navigate back:\n * <Stepper activeStep={step} onStepClick={(i) => setStep(i)}>\n * <Step label=\"Account\" />\n * <Step label=\"Profile\" />\n * <Step label=\"Review\" />\n * </Stepper>\n * // These are just a few ways — feel free to combine props creatively!\n */\ninterface StepperProps extends React.HTMLAttributes<HTMLDivElement> {\n activeStep: number\n orientation?: 'horizontal' | 'vertical'\n /** Called when a completed step is clicked. Receives the 0-based step index. */\n onStepClick?: (stepIndex: number) => void\n children: React.ReactNode\n}\n\nconst Stepper = React.forwardRef<HTMLDivElement, StepperProps>(\n ({ activeStep, orientation = 'horizontal', onStepClick, className, children, ...props }, ref) => {\n const steps = React.Children.toArray(children)\n const stepperId = React.useId()\n const contextValue = React.useMemo(\n () => ({ activeStep, orientation, stepperId, onStepClick }),\n [activeStep, orientation, stepperId, onStepClick],\n )\n return (\n <StepperContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn(\n 'flex gap-ds-02',\n orientation === 'vertical' ? 'flex-col' : 'flex-row items-center',\n className,\n )}\n role=\"list\"\n {...props}\n >\n {steps.map((child, index) => (\n <React.Fragment key={index}>\n {React.isValidElement<StepInternalProps>(child)\n ? React.cloneElement(child, { _index: index })\n : child}\n {index < steps.length - 1 && (\n <div\n className={cn(\n 'relative flex-1 overflow-hidden',\n orientation === 'vertical'\n ? 'ml-ds-04 w-ds-01 min-h-ds-05'\n : 'h-ds-01 min-w-ds-05',\n 'bg-surface-border',\n )}\n aria-hidden=\"true\"\n >\n {/* Filled portion — smooth CSS transition */}\n <div\n className={cn(\n 'absolute inset-0 bg-accent-9 transition-transform duration-moderate-02 ease-productive-standard',\n orientation === 'vertical' ? 'origin-top' : 'origin-left',\n )}\n style={{\n transform: `${orientation === 'vertical' ? 'scaleY' : 'scaleX'}(${index < activeStep ? 1 : 0})`,\n }}\n />\n </div>\n )}\n </React.Fragment>\n ))}\n </div>\n </StepperContext.Provider>\n )\n },\n)\n\n/**\n * Props for Step — a single step within a `<Stepper>`. The visual state (completed/active/pending)\n * is derived from the parent Stepper's `activeStep` and this step's position — you don't set it manually.\n *\n * @example\n * // Basic step with label and description:\n * <Step label=\"Payment\" description=\"Enter your card details\" />\n *\n * @example\n * // Step with a custom icon (overrides the default number/checkmark):\n * <Step label=\"Verified\" icon={<IconShieldCheck className=\"h-ico-sm w-ico-sm\" />} />\n */\ninterface StepProps extends React.HTMLAttributes<HTMLDivElement> {\n label: string\n description?: string\n icon?: React.ReactNode\n}\n\ntype StepInternalProps = StepProps & { _index?: number }\n\nconst Step = React.forwardRef<HTMLDivElement, StepProps>(\n ({ label, description, icon, className, ...props }, ref) => {\n const { _index = 0, ...restProps } = props as StepInternalProps\n const { activeStep, orientation, stepperId, onStepClick } = React.useContext(StepperContext)\n const state: StepState = _index < activeStep ? 'completed' : _index === activeStep ? 'active' : 'pending'\n const isClickable = state === 'completed' && !!onStepClick\n\n const stepContent = (\n <>\n <div\n className={cn(\n 'relative shrink-0 flex items-center justify-center w-ds-sm h-ds-sm rounded-pill text-ds-sm font-semibold',\n 'transition-[background-color,border-color,color] duration-moderate-01 ease-productive-standard',\n state === 'completed' && 'bg-accent-9 text-accent-fg',\n state === 'active' && 'bg-accent-9 text-accent-fg',\n state === 'pending' && 'bg-surface-raised text-surface-fg-subtle border border-surface-border-strong',\n )}\n >\n {icon || (state === 'completed' ? (\n <svg\n className=\"w-ico-sm h-ico-sm\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n ) : (\n _index + 1\n ))}\n </div>\n <div className=\"flex flex-col\">\n <span\n className={cn(\n 'text-ds-md font-medium leading-ds-snug',\n state === 'pending'\n ? 'text-surface-fg-subtle'\n : 'text-surface-fg',\n )}\n >\n {label}\n </span>\n {description && (\n <span className=\"text-ds-sm text-surface-fg-muted leading-ds-relaxed\">\n {description}\n </span>\n )}\n </div>\n </>\n )\n\n if (isClickable) {\n return (\n <div\n ref={ref}\n data-step=\"\"\n data-state={state}\n role=\"listitem\"\n className={cn(\n orientation === 'vertical' && 'py-ds-02',\n className,\n )}\n {...restProps}\n >\n <button\n type=\"button\"\n tabIndex={0}\n aria-label={`Go to step ${_index + 1}: ${label}`}\n onClick={() => onStepClick(_index)}\n className={cn(\n 'flex items-center gap-ds-03 cursor-pointer select-none',\n 'rounded-control px-ds-02 py-ds-01 -mx-ds-02 -my-ds-01',\n 'bg-transparent border-0',\n 'hover:bg-surface-raised-hover active:bg-surface-raised-active',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-8 focus-visible:ring-offset-2',\n 'transition-colors duration-fast-01 ease-productive-standard',\n )}\n >\n {stepContent}\n </button>\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n data-step=\"\"\n data-state={state}\n role=\"listitem\"\n aria-current={state === 'active' ? 'step' : undefined}\n aria-label={`Step ${_index + 1}: ${label}, ${state === 'completed' ? 'completed' : state === 'active' ? 'current' : 'upcoming'}`}\n className={cn(\n 'flex items-center gap-ds-03',\n orientation === 'vertical' && 'py-ds-02',\n className,\n )}\n {...restProps}\n >\n {stepContent}\n </div>\n )\n },\n)\n\n/**\n * Props for StepperContent — wraps the content panel for the active step, animating\n * slide transitions when `activeStep` changes. Detects forward vs backward navigation\n * and slides content accordingly.\n *\n * @example\n * <StepperContent activeStep={currentStep}>\n * {currentStep === 0 && <AccountForm />}\n * {currentStep === 1 && <ProfileForm />}\n * {currentStep === 2 && <ReviewPanel />}\n * </StepperContent>\n */\ninterface StepperContentProps {\n activeStep: number\n children: React.ReactNode\n className?: string\n}\n\nconst SLIDE_OFFSET = 40\n\nconst stepContentVariants = {\n enter: (direction: number) => ({\n opacity: 0,\n x: direction * SLIDE_OFFSET,\n }),\n center: {\n opacity: 1,\n x: 0,\n },\n exit: (direction: number) => ({\n opacity: 0,\n x: direction * -SLIDE_OFFSET,\n }),\n}\n\nfunction StepperContent({ activeStep, children, className }: StepperContentProps) {\n const prevStepRef = React.useRef(activeStep)\n const direction = activeStep >= prevStepRef.current ? 1 : -1\n\n React.useEffect(() => {\n prevStepRef.current = activeStep\n }, [activeStep])\n\n return (\n <div className={cn('relative overflow-hidden', className)}>\n <AnimatePresence mode=\"wait\" custom={direction}>\n <motion.div\n key={activeStep}\n custom={direction}\n variants={stepContentVariants}\n initial=\"enter\"\n animate=\"center\"\n exit=\"exit\"\n transition={springs.smooth}\n >\n {children}\n </motion.div>\n </AnimatePresence>\n </div>\n )\n}\n\nStepper.displayName = 'Stepper'\nStep.displayName = 'Step'\nStepperContent.displayName = 'StepperContent'\n\nexport { Step, Stepper, StepperContent, type StepperContentProps,type StepperProps, type StepProps }\n"],"mappings":"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"}
package/dist/ui/switch.js CHANGED
@@ -32,7 +32,7 @@ var c = {
32
32
  h === void 0 && T(e), _?.(e);
33
33
  }, [h, _]);
34
34
  return /* @__PURE__ */ o(t, {
35
- className: r("touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-ds-full border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active", D, l[p], x && "border-error-7 data-[state=checked]:bg-error-9", u),
35
+ className: r("touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active", D, l[p], x && "border-error-7 data-[state=checked]:bg-error-9", u),
36
36
  checked: h,
37
37
  defaultChecked: g,
38
38
  onCheckedChange: A,
@@ -44,7 +44,7 @@ var c = {
44
44
  children: /* @__PURE__ */ o(e, {
45
45
  asChild: !0,
46
46
  children: /* @__PURE__ */ o(s.span, {
47
- className: r("pointer-events-none flex items-center justify-center rounded-ds-full bg-accent-fg shadow-raised-hover ring-0", O),
47
+ className: r("pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0", O),
48
48
  animate: { x: E ? k : 0 },
49
49
  whileTap: { scale: .85 },
50
50
  transition: n.snappy,
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","names":[],"sources":["../../src/ui/switch.tsx"],"sourcesContent":["'use client'\n\nimport * as SwitchPrimitives from \"@primitives/react-switch\"\nimport { motion } from 'framer-motion'\nimport * as React from \"react\"\n\nimport { useFormField } from './form'\nimport { springs } from './lib/motion'\nimport { cn } from \"./lib/utils\"\n\nconst sizeConfig = {\n sm: { track: 'h-6 w-[38px]', thumb: 'h-5 w-5', travel: 16 },\n md: { track: 'h-6 w-11', thumb: 'h-ico-md w-ico-md', travel: 20 },\n lg: { track: 'h-7 w-[52px]', thumb: 'h-6 w-6', travel: 24 },\n} as const\n\nconst colorMap = {\n accent: 'data-[state=checked]:bg-accent-9',\n success: 'data-[state=checked]:bg-success-9',\n warning: 'data-[state=checked]:bg-warning-9',\n} as const\n\nexport interface SwitchProps extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n color?: 'accent' | 'success' | 'warning'\n thumbIcon?: React.ReactNode\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(({ className, error, size = 'md', color = 'accent', thumbIcon, checked, defaultChecked, onCheckedChange, ...props }, ref) => {\n const fieldCtx = useFormField()\n const isError = error ?? fieldCtx.state === 'error'\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Track checked state internally to drive Framer Motion animation\n const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false)\n const isChecked = checked !== undefined ? checked : internalChecked\n const { track, thumb, travel } = sizeConfig[size]\n\n const handleCheckedChange = React.useCallback(\n (value: boolean) => {\n if (checked === undefined) {\n setInternalChecked(value)\n }\n onCheckedChange?.(value)\n },\n [checked, onCheckedChange],\n )\n\n return (\n <SwitchPrimitives.Root\n className={cn(\n \"touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-ds-full border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active\",\n track,\n colorMap[color],\n isError && \"border-error-7 data-[state=checked]:bg-error-9\",\n className\n )}\n checked={checked}\n defaultChecked={defaultChecked}\n onCheckedChange={handleCheckedChange}\n aria-invalid={isError || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb asChild>\n <motion.span\n className={cn(\n \"pointer-events-none flex items-center justify-center rounded-ds-full bg-accent-fg shadow-raised-hover ring-0\",\n thumb\n )}\n animate={{ x: isChecked ? travel : 0 }}\n whileTap={{ scale: 0.85 }}\n transition={springs.snappy}\n >\n {thumbIcon}\n </motion.span>\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n})\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"],"mappings":";;;;;;;;;AAUA,IAAM,IAAa;CACjB,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;EAAI;CAC3D,IAAI;EAAE,OAAO;EAAY,OAAO;EAAqB,QAAQ;EAAI;CACjE,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;EAAI;CAC5D,EAEK,IAAW;CACf,QAAQ;CACR,SAAS;CACT,SAAS;CACV,EASK,IAAS,EAAM,YAGlB,EAAE,cAAW,UAAO,UAAO,MAAM,WAAQ,UAAU,cAAW,YAAS,mBAAgB,oBAAiB,GAAG,KAAS,MAAQ;CAC7H,IAAM,IAAW,GAAc,EACzB,IAAU,KAAS,EAAS,UAAU,SACtC,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,CAAC,GAAiB,KAAsB,EAAM,SAAS,KAAkB,GAAM,EAC/E,IAAY,MAAY,KAAA,IAAsB,IAAV,GACpC,EAAE,UAAO,UAAO,cAAW,EAAW,IAEtC,IAAsB,EAAM,aAC/B,MAAmB;AAIlB,EAHI,MAAY,KAAA,KACd,EAAmB,EAAM,EAE3B,IAAkB,EAAM;IAE1B,CAAC,GAAS,EAAgB,CAC3B;AAED,QACE,kBAAC,GAAD;EACE,WAAW,EACT,keACA,GACA,EAAS,IACT,KAAW,kDACX,EACD;EACQ;EACO;EAChB,iBAAiB;EACjB,gBAAc,KAAW,KAAA;EACzB,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;EACC;YAEL,kBAAC,GAAD;GAAwB,SAAA;aACtB,kBAAC,EAAO,MAAR;IACE,WAAW,EACT,gHACA,EACD;IACD,SAAS,EAAE,GAAG,IAAY,IAAS,GAAG;IACtC,UAAU,EAAE,OAAO,KAAM;IACzB,YAAY,EAAQ;cAEnB;IACW,CAAA;GACS,CAAA;EACH,CAAA;EAE1B;AACF,EAAO,cAAA,EAAoC"}
1
+ {"version":3,"file":"switch.js","names":[],"sources":["../../src/ui/switch.tsx"],"sourcesContent":["'use client'\n\nimport * as SwitchPrimitives from \"@primitives/react-switch\"\nimport { motion } from 'framer-motion'\nimport * as React from \"react\"\n\nimport { useFormField } from './form'\nimport { springs } from './lib/motion'\nimport { cn } from \"./lib/utils\"\n\nconst sizeConfig = {\n sm: { track: 'h-6 w-[38px]', thumb: 'h-5 w-5', travel: 16 },\n md: { track: 'h-6 w-11', thumb: 'h-ico-md w-ico-md', travel: 20 },\n lg: { track: 'h-7 w-[52px]', thumb: 'h-6 w-6', travel: 24 },\n} as const\n\nconst colorMap = {\n accent: 'data-[state=checked]:bg-accent-9',\n success: 'data-[state=checked]:bg-success-9',\n warning: 'data-[state=checked]:bg-warning-9',\n} as const\n\nexport interface SwitchProps extends React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> {\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n color?: 'accent' | 'success' | 'warning'\n thumbIcon?: React.ReactNode\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(({ className, error, size = 'md', color = 'accent', thumbIcon, checked, defaultChecked, onCheckedChange, ...props }, ref) => {\n const fieldCtx = useFormField()\n const isError = error ?? fieldCtx.state === 'error'\n const ariaDescribedBy = props['aria-describedby'] ?? fieldCtx.helperTextId\n const ariaRequired = props['aria-required'] ?? fieldCtx.required\n\n // Track checked state internally to drive Framer Motion animation\n const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false)\n const isChecked = checked !== undefined ? checked : internalChecked\n const { track, thumb, travel } = sizeConfig[size]\n\n const handleCheckedChange = React.useCallback(\n (value: boolean) => {\n if (checked === undefined) {\n setInternalChecked(value)\n }\n onCheckedChange?.(value)\n },\n [checked, onCheckedChange],\n )\n\n return (\n <SwitchPrimitives.Root\n className={cn(\n \"touch-target peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-2 border-surface-border-strong shadow-raised transition-colors duration-fast-01 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-action-disabled data-[state=checked]:border-transparent data-[state=unchecked]:bg-surface-border-strong data-[state=unchecked]:hover:bg-surface-raised-active\",\n track,\n colorMap[color],\n isError && \"border-error-7 data-[state=checked]:bg-error-9\",\n className\n )}\n checked={checked}\n defaultChecked={defaultChecked}\n onCheckedChange={handleCheckedChange}\n aria-invalid={isError || undefined}\n aria-describedby={ariaDescribedBy}\n aria-required={ariaRequired || undefined}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb asChild>\n <motion.span\n className={cn(\n \"pointer-events-none flex items-center justify-center rounded-pill bg-accent-fg shadow-raised-hover ring-0\",\n thumb\n )}\n animate={{ x: isChecked ? travel : 0 }}\n whileTap={{ scale: 0.85 }}\n transition={springs.snappy}\n >\n {thumbIcon}\n </motion.span>\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n})\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"],"mappings":";;;;;;;;;AAUA,IAAM,IAAa;CACjB,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;EAAI;CAC3D,IAAI;EAAE,OAAO;EAAY,OAAO;EAAqB,QAAQ;EAAI;CACjE,IAAI;EAAE,OAAO;EAAgB,OAAO;EAAW,QAAQ;EAAI;CAC5D,EAEK,IAAW;CACf,QAAQ;CACR,SAAS;CACT,SAAS;CACV,EASK,IAAS,EAAM,YAGlB,EAAE,cAAW,UAAO,UAAO,MAAM,WAAQ,UAAU,cAAW,YAAS,mBAAgB,oBAAiB,GAAG,KAAS,MAAQ;CAC7H,IAAM,IAAW,GAAc,EACzB,IAAU,KAAS,EAAS,UAAU,SACtC,IAAkB,EAAM,uBAAuB,EAAS,cACxD,IAAe,EAAM,oBAAoB,EAAS,UAGlD,CAAC,GAAiB,KAAsB,EAAM,SAAS,KAAkB,GAAM,EAC/E,IAAY,MAAY,KAAA,IAAsB,IAAV,GACpC,EAAE,UAAO,UAAO,cAAW,EAAW,IAEtC,IAAsB,EAAM,aAC/B,MAAmB;AAIlB,EAHI,MAAY,KAAA,KACd,EAAmB,EAAM,EAE3B,IAAkB,EAAM;IAE1B,CAAC,GAAS,EAAgB,CAC3B;AAED,QACE,kBAAC,GAAD;EACE,WAAW,EACT,+dACA,GACA,EAAS,IACT,KAAW,kDACX,EACD;EACQ;EACO;EAChB,iBAAiB;EACjB,gBAAc,KAAW,KAAA;EACzB,oBAAkB;EAClB,iBAAe,KAAgB,KAAA;EAC/B,GAAI;EACC;YAEL,kBAAC,GAAD;GAAwB,SAAA;aACtB,kBAAC,EAAO,MAAR;IACE,WAAW,EACT,6GACA,EACD;IACD,SAAS,EAAE,GAAG,IAAY,IAAS,GAAG;IACtC,UAAU,EAAE,OAAO,KAAM;IACzB,YAAY,EAAQ;cAEnB;IACW,CAAA;GACS,CAAA;EACH,CAAA;EAE1B;AACF,EAAO,cAAA,EAAoC"}