@devalok/shilp-sutra 0.56.0 → 0.57.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 (253) hide show
  1. package/MIGRATION.md +107 -0
  2. package/dist/_chunks/badge-group.js +48 -34
  3. package/dist/_chunks/badge-group.js.map +1 -1
  4. package/dist/_chunks/chat.js +91 -75
  5. package/dist/_chunks/chat.js.map +1 -1
  6. package/dist/_chunks/document-preview.js +1 -1
  7. package/dist/_chunks/document-preview.js.map +1 -1
  8. package/dist/_chunks/emoji-suggestion.js +1 -1
  9. package/dist/_chunks/emoji-suggestion.js.map +1 -1
  10. package/dist/_chunks/mention-suggestion.js +2 -2
  11. package/dist/_chunks/mention-suggestion.js.map +1 -1
  12. package/dist/_chunks/oauth-button.js +1 -1
  13. package/dist/_chunks/oauth-button.js.map +1 -1
  14. package/dist/_chunks/success.js +2 -2
  15. package/dist/_chunks/success.js.map +1 -1
  16. package/dist/_chunks/tree-view.js +1 -1
  17. package/dist/_chunks/tree-view.js.map +1 -1
  18. package/dist/_chunks/use-calendar.js +4 -4
  19. package/dist/_chunks/use-calendar.js.map +1 -1
  20. package/dist/ai/blocks/text.js +1 -1
  21. package/dist/ai/blocks/text.js.map +1 -1
  22. package/dist/ai/command-bar.js +9 -9
  23. package/dist/ai/command-bar.js.map +1 -1
  24. package/dist/ai/conversation.js +2 -2
  25. package/dist/ai/conversation.js.map +1 -1
  26. package/dist/composed/activity-feed.js +1 -1
  27. package/dist/composed/activity-feed.js.map +1 -1
  28. package/dist/composed/avatar-group.d.ts +7 -2
  29. package/dist/composed/avatar-group.d.ts.map +1 -1
  30. package/dist/composed/avatar-group.js +3 -3
  31. package/dist/composed/avatar-group.js.map +1 -1
  32. package/dist/composed/command-palette.js +7 -7
  33. package/dist/composed/command-palette.js.map +1 -1
  34. package/dist/composed/content-card.js +2 -2
  35. package/dist/composed/content-card.js.map +1 -1
  36. package/dist/composed/date-picker/index.js +4 -4
  37. package/dist/composed/date-picker/index.js.map +1 -1
  38. package/dist/composed/diff.d.ts +23 -2
  39. package/dist/composed/diff.d.ts.map +1 -1
  40. package/dist/composed/diff.js +341 -260
  41. package/dist/composed/diff.js.map +1 -1
  42. package/dist/composed/emoji-picker.js +3 -3
  43. package/dist/composed/emoji-picker.js.map +1 -1
  44. package/dist/composed/error-boundary.js +2 -2
  45. package/dist/composed/error-boundary.js.map +1 -1
  46. package/dist/composed/file-preview.js +1 -1
  47. package/dist/composed/file-preview.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 +3 -3
  53. package/dist/composed/loading-skeleton.js.map +1 -1
  54. package/dist/composed/master-detail.js +1 -1
  55. package/dist/composed/master-detail.js.map +1 -1
  56. package/dist/composed/member-picker.js +1 -1
  57. package/dist/composed/member-picker.js.map +1 -1
  58. package/dist/composed/multi-select-popover.js +1 -1
  59. package/dist/composed/multi-select-popover.js.map +1 -1
  60. package/dist/composed/page-skeletons.js +4 -4
  61. package/dist/composed/page-skeletons.js.map +1 -1
  62. package/dist/composed/responsive-modal.js +1 -1
  63. package/dist/composed/responsive-modal.js.map +1 -1
  64. package/dist/composed/rich-chat-input.js +17 -17
  65. package/dist/composed/rich-chat-input.js.map +1 -1
  66. package/dist/composed/rich-text-editor.js +5 -5
  67. package/dist/composed/rich-text-editor.js.map +1 -1
  68. package/dist/composed/schedule-view.js +5 -5
  69. package/dist/composed/schedule-view.js.map +1 -1
  70. package/dist/hooks/index.d.ts +1 -0
  71. package/dist/hooks/index.d.ts.map +1 -1
  72. package/dist/hooks/index.js +4 -3
  73. package/dist/hooks/use-container-size.d.ts +18 -0
  74. package/dist/hooks/use-container-size.d.ts.map +1 -0
  75. package/dist/hooks/use-container-size.js +28 -0
  76. package/dist/hooks/use-container-size.js.map +1 -0
  77. package/dist/shell/app-shell.d.ts +101 -0
  78. package/dist/shell/app-shell.d.ts.map +1 -0
  79. package/dist/shell/app-shell.js +76 -0
  80. package/dist/shell/app-shell.js.map +1 -0
  81. package/dist/shell/bottom-navbar.js +2 -2
  82. package/dist/shell/bottom-navbar.js.map +1 -1
  83. package/dist/shell/index.d.ts +1 -0
  84. package/dist/shell/index.d.ts.map +1 -1
  85. package/dist/shell/index.js +6 -5
  86. package/dist/shell/notification-center.js +4 -4
  87. package/dist/shell/notification-center.js.map +1 -1
  88. package/dist/shell/notification-preferences.js +1 -1
  89. package/dist/shell/notification-preferences.js.map +1 -1
  90. package/dist/shell/top-bar.js +7 -7
  91. package/dist/shell/top-bar.js.map +1 -1
  92. package/dist/tokens/generate-scale.d.ts +49 -0
  93. package/dist/tokens/generate-scale.d.ts.map +1 -1
  94. package/dist/tokens/primitives.css +29 -26
  95. package/dist/tokens/semantic.css +98 -39
  96. package/dist/tokens/variants.css +1 -1
  97. package/dist/ui/accordion.js +1 -1
  98. package/dist/ui/accordion.js.map +1 -1
  99. package/dist/ui/alert-dialog.js +1 -1
  100. package/dist/ui/alert-dialog.js.map +1 -1
  101. package/dist/ui/alert.d.ts.map +1 -1
  102. package/dist/ui/alert.js +7 -7
  103. package/dist/ui/alert.js.map +1 -1
  104. package/dist/ui/avatar.js +3 -3
  105. package/dist/ui/avatar.js.map +1 -1
  106. package/dist/ui/badge-indicator.js +1 -1
  107. package/dist/ui/badge-indicator.js.map +1 -1
  108. package/dist/ui/badge.d.ts +47 -33
  109. package/dist/ui/badge.d.ts.map +1 -1
  110. package/dist/ui/banner.js +1 -1
  111. package/dist/ui/banner.js.map +1 -1
  112. package/dist/ui/button.js +4 -4
  113. package/dist/ui/button.js.map +1 -1
  114. package/dist/ui/card.js +4 -4
  115. package/dist/ui/card.js.map +1 -1
  116. package/dist/ui/chat/date-separator.d.ts +8 -1
  117. package/dist/ui/chat/date-separator.d.ts.map +1 -1
  118. package/dist/ui/checkbox.js +1 -1
  119. package/dist/ui/checkbox.js.map +1 -1
  120. package/dist/ui/code.js +2 -2
  121. package/dist/ui/code.js.map +1 -1
  122. package/dist/ui/color-input.js +3 -3
  123. package/dist/ui/color-input.js.map +1 -1
  124. package/dist/ui/combobox.js +4 -4
  125. package/dist/ui/combobox.js.map +1 -1
  126. package/dist/ui/context-menu.js +4 -4
  127. package/dist/ui/context-menu.js.map +1 -1
  128. package/dist/ui/data-table-body.d.ts +3 -1
  129. package/dist/ui/data-table-body.d.ts.map +1 -1
  130. package/dist/ui/data-table-body.js +93 -92
  131. package/dist/ui/data-table-body.js.map +1 -1
  132. package/dist/ui/data-table-bulk-actions.d.ts +7 -1
  133. package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
  134. package/dist/ui/data-table-bulk-actions.js +14 -8
  135. package/dist/ui/data-table-bulk-actions.js.map +1 -1
  136. package/dist/ui/data-table-card.d.ts.map +1 -1
  137. package/dist/ui/data-table-card.js +66 -47
  138. package/dist/ui/data-table-card.js.map +1 -1
  139. package/dist/ui/data-table-context.d.ts +4 -0
  140. package/dist/ui/data-table-context.d.ts.map +1 -1
  141. package/dist/ui/data-table-context.js.map +1 -1
  142. package/dist/ui/data-table-header.d.ts.map +1 -1
  143. package/dist/ui/data-table-header.js +18 -15
  144. package/dist/ui/data-table-header.js.map +1 -1
  145. package/dist/ui/data-table-pagination.js +3 -3
  146. package/dist/ui/data-table-pagination.js.map +1 -1
  147. package/dist/ui/data-table-toolbar.d.ts +11 -1
  148. package/dist/ui/data-table-toolbar.d.ts.map +1 -1
  149. package/dist/ui/data-table-toolbar.js +7 -7
  150. package/dist/ui/data-table-toolbar.js.map +1 -1
  151. package/dist/ui/data-table.d.ts +52 -5
  152. package/dist/ui/data-table.d.ts.map +1 -1
  153. package/dist/ui/data-table.js +181 -169
  154. package/dist/ui/data-table.js.map +1 -1
  155. package/dist/ui/dialog.js +1 -1
  156. package/dist/ui/dialog.js.map +1 -1
  157. package/dist/ui/dot.js +1 -1
  158. package/dist/ui/dot.js.map +1 -1
  159. package/dist/ui/dropdown-menu.js +4 -4
  160. package/dist/ui/dropdown-menu.js.map +1 -1
  161. package/dist/ui/file-upload.js +3 -3
  162. package/dist/ui/file-upload.js.map +1 -1
  163. package/dist/ui/input-otp.js +1 -1
  164. package/dist/ui/input-otp.js.map +1 -1
  165. package/dist/ui/input.js +8 -8
  166. package/dist/ui/input.js.map +1 -1
  167. package/dist/ui/menubar.js +5 -5
  168. package/dist/ui/menubar.js.map +1 -1
  169. package/dist/ui/navigation-menu.js +1 -1
  170. package/dist/ui/navigation-menu.js.map +1 -1
  171. package/dist/ui/number-input.js +3 -3
  172. package/dist/ui/number-input.js.map +1 -1
  173. package/dist/ui/pagination.js +1 -1
  174. package/dist/ui/pagination.js.map +1 -1
  175. package/dist/ui/progress-ring.js +2 -2
  176. package/dist/ui/progress-ring.js.map +1 -1
  177. package/dist/ui/progress.d.ts.map +1 -1
  178. package/dist/ui/progress.js +16 -12
  179. package/dist/ui/progress.js.map +1 -1
  180. package/dist/ui/radio.js +1 -1
  181. package/dist/ui/radio.js.map +1 -1
  182. package/dist/ui/select.js +5 -5
  183. package/dist/ui/select.js.map +1 -1
  184. package/dist/ui/sheet.js +1 -1
  185. package/dist/ui/sheet.js.map +1 -1
  186. package/dist/ui/sidebar.js +12 -12
  187. package/dist/ui/sidebar.js.map +1 -1
  188. package/dist/ui/slider.js +1 -1
  189. package/dist/ui/slider.js.map +1 -1
  190. package/dist/ui/split-button.js +3 -3
  191. package/dist/ui/split-button.js.map +1 -1
  192. package/dist/ui/stat-card.js +1 -1
  193. package/dist/ui/stat-card.js.map +1 -1
  194. package/dist/ui/stepper.js +2 -2
  195. package/dist/ui/stepper.js.map +1 -1
  196. package/dist/ui/surface.d.ts.map +1 -1
  197. package/dist/ui/surface.js +2 -2
  198. package/dist/ui/surface.js.map +1 -1
  199. package/dist/ui/switch.js +1 -1
  200. package/dist/ui/switch.js.map +1 -1
  201. package/dist/ui/table.js +1 -1
  202. package/dist/ui/table.js.map +1 -1
  203. package/dist/ui/tabs.d.ts.map +1 -1
  204. package/dist/ui/tabs.js +1 -1
  205. package/dist/ui/tabs.js.map +1 -1
  206. package/dist/ui/textarea.js +5 -5
  207. package/dist/ui/textarea.js.map +1 -1
  208. package/dist/ui/toast.js +1 -1
  209. package/dist/ui/toast.js.map +1 -1
  210. package/dist/ui/toggle.js +3 -3
  211. package/dist/ui/toggle.js.map +1 -1
  212. package/docs/components/composed/avatar-group.md +4 -4
  213. package/docs/components/composed/diff.md +10 -2
  214. package/docs/components/composed/schedule-view.md +1 -1
  215. package/docs/components/shell/app-shell.md +132 -0
  216. package/docs/components/ui/avatar.md +1 -1
  217. package/docs/components/ui/button.md +1 -1
  218. package/docs/components/ui/chat.md +7 -1
  219. package/docs/components/ui/color-swatch.md +1 -1
  220. package/docs/components/ui/data-table-toolbar.md +6 -2
  221. package/docs/components/ui/data-table.md +41 -7
  222. package/docs/components/ui/dot.md +1 -1
  223. package/docs/components/ui/progress.md +1 -0
  224. package/docs/components/ui/surface.md +3 -3
  225. package/docs/components/ui/table.md +2 -2
  226. package/docs/recipes/install-astro.md +1 -0
  227. package/docs/recipes/install-next-app-router.md +1 -0
  228. package/docs/recipes/install-remix.md +1 -0
  229. package/docs/recipes/install-tanstack-start.md +1 -0
  230. package/docs/recipes/install-vite.md +1 -0
  231. package/llms.txt +2 -1
  232. package/make-kit/Guidelines.md +2 -2
  233. package/make-kit/components/button.md +1 -1
  234. package/make-kit/components/card.md +6 -6
  235. package/make-kit/components/surface.md +4 -4
  236. package/make-kit/foundations/color.md +4 -4
  237. package/make-kit/foundations/dark-mode.md +1 -1
  238. package/make-kit/foundations/radius.md +1 -1
  239. package/make-kit/foundations/surfaces.md +9 -9
  240. package/mcp-manifest.json +350 -32
  241. package/package.json +6 -3
  242. package/skill/SKILL.md +12 -6
  243. package/skill/references/components.md +2 -1
  244. package/skill/references/setup-astro.md +1 -0
  245. package/skill/references/setup-next-app-router.md +1 -0
  246. package/skill/references/setup-remix.md +1 -0
  247. package/skill/references/setup-tanstack-start.md +1 -0
  248. package/skill/references/setup-vite.md +1 -0
  249. package/dist/primitives/react-toast.d.ts +0 -120
  250. package/dist/ui/charts/_internal/scales.d.ts +0 -5
  251. package/dist/ui/charts/_internal/scales.d.ts.map +0 -1
  252. package/dist/ui/lib/slot.d.ts +0 -8
  253. package/dist/ui/lib/slot.d.ts.map +0 -1
@@ -66,7 +66,7 @@ var h = r.forwardRef(function({ error: o, onReset: s, actions: c, fullPage: l =
66
66
  className: e("flex items-center justify-center p-ds-05", l && "min-h-[60vh]", g),
67
67
  children: /* @__PURE__ */ a("div", {
68
68
  role: "alert",
69
- className: "flex w-full max-w-lg flex-col items-center gap-ds-06 rounded-overlay-lg bg-surface-raised p-ds-07 text-center shadow-raised",
69
+ className: "flex w-full max-w-lg flex-col items-center gap-ds-06 rounded-overlay-lg bg-surface-panel p-ds-07 text-center",
70
70
  children: [
71
71
  /* @__PURE__ */ i("div", {
72
72
  className: e("flex h-ds-lg w-ds-lg items-center justify-center rounded-bubble", C.bgClass),
@@ -104,7 +104,7 @@ var h = r.forwardRef(function({ error: o, onReset: s, actions: c, fullPage: l =
104
104
  })
105
105
  }),
106
106
  S && x && /* @__PURE__ */ a("div", {
107
- className: "w-full overflow-auto rounded-surface border border-card bg-surface-raised p-ds-05 text-left",
107
+ className: "w-full overflow-auto rounded-surface border border-card bg-surface-panel p-ds-05 text-left",
108
108
  children: [/* @__PURE__ */ i("p", {
109
109
  className: "text-body-sm mb-ds-03 font-semibold text-surface-fg",
110
110
  children: "Stack Trace (development only)"
@@ -1 +1 @@
1
- {"version":3,"file":"error-boundary.js","names":[],"sources":["../../src/composed/error-boundary.tsx"],"sourcesContent":["'use client'\n\nimport { IconAlertTriangle, IconBan, IconFileUnknown, IconServerOff } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\n\ndeclare const process: { env: { NODE_ENV?: string } } | undefined\n\nexport interface ErrorDisplayProps extends React.ComponentPropsWithoutRef<'div'> {\n error: unknown\n onReset?: () => void\n /** Custom recovery actions. When set, replaces the default \"Try Again\" button. */\n actions?: React.ReactNode\n /** Center in a full-viewport-height region (error page). @default true */\n fullPage?: boolean\n /** Move focus to the recovery button on mount (set by ErrorBoundary on swap). @default false */\n autoFocusReset?: boolean\n}\n\nfunction getStatusFromError(error: unknown): number | undefined {\n if (\n error &&\n typeof error === 'object' &&\n 'status' in error &&\n typeof (error as Record<string, unknown>).status === 'number'\n ) {\n return (error as Record<string, unknown>).status as number\n }\n return undefined\n}\n\nfunction getMessageFromError(error: unknown): string | undefined {\n if (error instanceof Error) return error.message\n if (error && typeof error === 'object' && 'data' in error) {\n const data = (error as Record<string, unknown>).data\n if (typeof data === 'string') return data\n if (data && typeof data === 'object' && 'message' in data) {\n return String((data as Record<string, unknown>).message)\n }\n }\n if (typeof error === 'string') return error\n return undefined\n}\n\nfunction getStackFromError(error: unknown): string | undefined {\n return error instanceof Error ? error.stack : undefined\n}\n\nfunction isDevEnv() {\n return typeof process !== 'undefined' && !!process.env && process.env.NODE_ENV === 'development'\n}\n\nfunction getErrorConfig(status?: number) {\n switch (status) {\n case 404:\n return { icon: IconFileUnknown, title: 'Page not found', message: 'The page you are looking for does not exist or has been moved.', bgClass: 'bg-accent-2', iconClass: 'text-accent-11' }\n case 403:\n return { icon: IconBan, title: 'Access denied', message: 'You do not have permission to view this page. Contact your administrator if you believe this is a mistake.', bgClass: 'bg-warning-3', iconClass: 'text-warning-11' }\n case 500:\n return { icon: IconServerOff, title: 'Server error', message: 'Something went wrong on our end. Please try again later or contact support if the issue persists.', bgClass: 'bg-error-3', iconClass: 'text-error-11' }\n default:\n return { icon: IconAlertTriangle, title: 'Something went wrong', message: 'An unexpected error occurred. Please try again or go back to the home page.', bgClass: 'bg-accent-2', iconClass: 'text-accent-11' }\n }\n}\n\nconst ErrorDisplay = React.forwardRef<HTMLDivElement, ErrorDisplayProps>(\n function ErrorDisplay({ error, onReset, actions, fullPage = true, autoFocusReset = false, className, ...props }, ref) {\n const status = getStatusFromError(error)\n const rawMessage = getMessageFromError(error)\n const stack = getStackFromError(error)\n const isDev = isDevEnv()\n const errorConfig = getErrorConfig(status)\n const ErrorIcon = errorConfig.icon\n const resetRef = React.useRef<HTMLButtonElement>(null)\n\n // Only surface the raw error message in development — in production it can leak\n // internal detail; users see the friendly, status-mapped copy instead.\n const shownMessage = isDev ? rawMessage || errorConfig.message : errorConfig.message\n\n React.useEffect(() => {\n if (autoFocusReset) resetRef.current?.focus()\n }, [autoFocusReset])\n\n return (\n <div ref={ref} {...props} className={cn('flex items-center justify-center p-ds-05', fullPage && 'min-h-[60vh]', className)}>\n {/* role=alert → assertive live region: AT announces the error on appearance. */}\n <div\n role=\"alert\"\n className=\"flex w-full max-w-lg flex-col items-center gap-ds-06 rounded-overlay-lg bg-surface-raised p-ds-07 text-center shadow-raised\"\n >\n <div className={cn('flex h-ds-lg w-ds-lg items-center justify-center rounded-bubble', errorConfig.bgClass)}>\n <Icon icon={ErrorIcon} size=\"2xl\" className={errorConfig.iconClass} />\n </div>\n\n <div className=\"flex flex-col gap-ds-03\">\n {status && <span className=\"text-body-sm text-surface-fg-subtle\">Error {status}</span>}\n <h2 className=\"text-heading-md font-semibold text-surface-fg\">{errorConfig.title}</h2>\n <p className=\"text-body-lg text-surface-fg-subtle\">{shownMessage}</p>\n </div>\n\n {(actions || onReset) && (\n <div className=\"flex items-center gap-ds-04\">\n {actions ?? (\n <Button ref={resetRef} variant=\"soft\" size=\"md\" onClick={onReset}>\n Try Again\n </Button>\n )}\n </div>\n )}\n\n {isDev && stack && (\n <div className=\"w-full overflow-auto rounded-surface border border-card bg-surface-raised p-ds-05 text-left\">\n <p className=\"text-body-sm mb-ds-03 font-semibold text-surface-fg\">Stack Trace (development only)</p>\n <pre className=\"whitespace-pre-wrap text-body-sm text-surface-fg-subtle\">{stack}</pre>\n </div>\n )}\n </div>\n </div>\n )\n },\n)\nErrorDisplay.displayName = 'ErrorDisplay'\n\ninterface ErrorBoundaryProps {\n children: React.ReactNode\n /** Called when the user clicks \"Try Again\" (after the boundary resets). */\n onReset?: () => void\n /** Called when an error is caught — wire your logger/Sentry here. */\n onError?: (error: unknown, info: React.ErrorInfo) => void\n /** Auto-reset when any value in this array changes (react-error-boundary parity). */\n resetKeys?: unknown[]\n /** Custom fallback — receives the caught error + a reset callback. Defaults to ErrorDisplay. */\n fallback?: (props: { error: unknown; onReset: () => void }) => React.ReactNode\n}\n\ninterface ErrorBoundaryState {\n error: unknown | null\n}\n\nfunction keysChanged(a: unknown[] | undefined, b: unknown[] | undefined) {\n if (!a || !b) return false\n if (a.length !== b.length) return true\n return a.some((v, i) => !Object.is(v, b[i]))\n}\n\nclass ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props)\n this.state = { error: null }\n }\n\n static getDerivedStateFromError(error: unknown): ErrorBoundaryState {\n return { error }\n }\n\n componentDidCatch(error: unknown, info: React.ErrorInfo) {\n this.props.onError?.(error, info)\n }\n\n componentDidUpdate(prev: ErrorBoundaryProps) {\n // Auto-recover when the reset dependencies change.\n if (this.state.error !== null && keysChanged(prev.resetKeys, this.props.resetKeys)) {\n this.setState({ error: null })\n }\n }\n\n private handleReset = () => {\n this.setState({ error: null })\n this.props.onReset?.()\n }\n\n render() {\n if (this.state.error !== null) {\n if (this.props.fallback) {\n return this.props.fallback({ error: this.state.error, onReset: this.handleReset })\n }\n return <ErrorDisplay error={this.state.error} onReset={this.handleReset} autoFocusReset />\n }\n return this.props.children\n }\n}\n\nexport { ErrorBoundary, ErrorDisplay }\nexport type { ErrorBoundaryProps }\n"],"mappings":"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"}
1
+ {"version":3,"file":"error-boundary.js","names":[],"sources":["../../src/composed/error-boundary.tsx"],"sourcesContent":["'use client'\n\nimport { IconAlertTriangle, IconBan, IconFileUnknown, IconServerOff } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\n\ndeclare const process: { env: { NODE_ENV?: string } } | undefined\n\nexport interface ErrorDisplayProps extends React.ComponentPropsWithoutRef<'div'> {\n error: unknown\n onReset?: () => void\n /** Custom recovery actions. When set, replaces the default \"Try Again\" button. */\n actions?: React.ReactNode\n /** Center in a full-viewport-height region (error page). @default true */\n fullPage?: boolean\n /** Move focus to the recovery button on mount (set by ErrorBoundary on swap). @default false */\n autoFocusReset?: boolean\n}\n\nfunction getStatusFromError(error: unknown): number | undefined {\n if (\n error &&\n typeof error === 'object' &&\n 'status' in error &&\n typeof (error as Record<string, unknown>).status === 'number'\n ) {\n return (error as Record<string, unknown>).status as number\n }\n return undefined\n}\n\nfunction getMessageFromError(error: unknown): string | undefined {\n if (error instanceof Error) return error.message\n if (error && typeof error === 'object' && 'data' in error) {\n const data = (error as Record<string, unknown>).data\n if (typeof data === 'string') return data\n if (data && typeof data === 'object' && 'message' in data) {\n return String((data as Record<string, unknown>).message)\n }\n }\n if (typeof error === 'string') return error\n return undefined\n}\n\nfunction getStackFromError(error: unknown): string | undefined {\n return error instanceof Error ? error.stack : undefined\n}\n\nfunction isDevEnv() {\n return typeof process !== 'undefined' && !!process.env && process.env.NODE_ENV === 'development'\n}\n\nfunction getErrorConfig(status?: number) {\n switch (status) {\n case 404:\n return { icon: IconFileUnknown, title: 'Page not found', message: 'The page you are looking for does not exist or has been moved.', bgClass: 'bg-accent-2', iconClass: 'text-accent-11' }\n case 403:\n return { icon: IconBan, title: 'Access denied', message: 'You do not have permission to view this page. Contact your administrator if you believe this is a mistake.', bgClass: 'bg-warning-3', iconClass: 'text-warning-11' }\n case 500:\n return { icon: IconServerOff, title: 'Server error', message: 'Something went wrong on our end. Please try again later or contact support if the issue persists.', bgClass: 'bg-error-3', iconClass: 'text-error-11' }\n default:\n return { icon: IconAlertTriangle, title: 'Something went wrong', message: 'An unexpected error occurred. Please try again or go back to the home page.', bgClass: 'bg-accent-2', iconClass: 'text-accent-11' }\n }\n}\n\nconst ErrorDisplay = React.forwardRef<HTMLDivElement, ErrorDisplayProps>(\n function ErrorDisplay({ error, onReset, actions, fullPage = true, autoFocusReset = false, className, ...props }, ref) {\n const status = getStatusFromError(error)\n const rawMessage = getMessageFromError(error)\n const stack = getStackFromError(error)\n const isDev = isDevEnv()\n const errorConfig = getErrorConfig(status)\n const ErrorIcon = errorConfig.icon\n const resetRef = React.useRef<HTMLButtonElement>(null)\n\n // Only surface the raw error message in development — in production it can leak\n // internal detail; users see the friendly, status-mapped copy instead.\n const shownMessage = isDev ? rawMessage || errorConfig.message : errorConfig.message\n\n React.useEffect(() => {\n if (autoFocusReset) resetRef.current?.focus()\n }, [autoFocusReset])\n\n return (\n <div ref={ref} {...props} className={cn('flex items-center justify-center p-ds-05', fullPage && 'min-h-[60vh]', className)}>\n {/* role=alert → assertive live region: AT announces the error on appearance. */}\n <div\n role=\"alert\"\n className=\"flex w-full max-w-lg flex-col items-center gap-ds-06 rounded-overlay-lg bg-surface-panel p-ds-07 text-center\"\n >\n <div className={cn('flex h-ds-lg w-ds-lg items-center justify-center rounded-bubble', errorConfig.bgClass)}>\n <Icon icon={ErrorIcon} size=\"2xl\" className={errorConfig.iconClass} />\n </div>\n\n <div className=\"flex flex-col gap-ds-03\">\n {status && <span className=\"text-body-sm text-surface-fg-subtle\">Error {status}</span>}\n <h2 className=\"text-heading-md font-semibold text-surface-fg\">{errorConfig.title}</h2>\n <p className=\"text-body-lg text-surface-fg-subtle\">{shownMessage}</p>\n </div>\n\n {(actions || onReset) && (\n <div className=\"flex items-center gap-ds-04\">\n {actions ?? (\n <Button ref={resetRef} variant=\"soft\" size=\"md\" onClick={onReset}>\n Try Again\n </Button>\n )}\n </div>\n )}\n\n {isDev && stack && (\n <div className=\"w-full overflow-auto rounded-surface border border-card bg-surface-panel p-ds-05 text-left\">\n <p className=\"text-body-sm mb-ds-03 font-semibold text-surface-fg\">Stack Trace (development only)</p>\n <pre className=\"whitespace-pre-wrap text-body-sm text-surface-fg-subtle\">{stack}</pre>\n </div>\n )}\n </div>\n </div>\n )\n },\n)\nErrorDisplay.displayName = 'ErrorDisplay'\n\ninterface ErrorBoundaryProps {\n children: React.ReactNode\n /** Called when the user clicks \"Try Again\" (after the boundary resets). */\n onReset?: () => void\n /** Called when an error is caught — wire your logger/Sentry here. */\n onError?: (error: unknown, info: React.ErrorInfo) => void\n /** Auto-reset when any value in this array changes (react-error-boundary parity). */\n resetKeys?: unknown[]\n /** Custom fallback — receives the caught error + a reset callback. Defaults to ErrorDisplay. */\n fallback?: (props: { error: unknown; onReset: () => void }) => React.ReactNode\n}\n\ninterface ErrorBoundaryState {\n error: unknown | null\n}\n\nfunction keysChanged(a: unknown[] | undefined, b: unknown[] | undefined) {\n if (!a || !b) return false\n if (a.length !== b.length) return true\n return a.some((v, i) => !Object.is(v, b[i]))\n}\n\nclass ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props)\n this.state = { error: null }\n }\n\n static getDerivedStateFromError(error: unknown): ErrorBoundaryState {\n return { error }\n }\n\n componentDidCatch(error: unknown, info: React.ErrorInfo) {\n this.props.onError?.(error, info)\n }\n\n componentDidUpdate(prev: ErrorBoundaryProps) {\n // Auto-recover when the reset dependencies change.\n if (this.state.error !== null && keysChanged(prev.resetKeys, this.props.resetKeys)) {\n this.setState({ error: null })\n }\n }\n\n private handleReset = () => {\n this.setState({ error: null })\n this.props.onReset?.()\n }\n\n render() {\n if (this.state.error !== null) {\n if (this.props.fallback) {\n return this.props.fallback({ error: this.state.error, onReset: this.handleReset })\n }\n return <ErrorDisplay error={this.state.error} onReset={this.handleReset} autoFocusReset />\n }\n return this.props.children\n }\n}\n\nexport { ErrorBoundary, ErrorDisplay }\nexport type { ErrorBoundaryProps }\n"],"mappings":";;;;;;;;AAsBA,SAAS,EAAmB,GAAoC;CAC9D,IACE,KACA,OAAO,KAAU,YACjB,YAAY,KACZ,OAAQ,EAAkC,UAAW,UAErD,OAAQ,EAAkC;AAG9C;AAEA,SAAS,EAAoB,GAAoC;CAC/D,IAAI,aAAiB,OAAO,OAAO,EAAM;CACzC,IAAI,KAAS,OAAO,KAAU,YAAY,UAAU,GAAO;EACzD,IAAM,IAAQ,EAAkC;EAChD,IAAI,OAAO,KAAS,UAAU,OAAO;EACrC,IAAI,KAAQ,OAAO,KAAS,YAAY,aAAa,GACnD,OAAO,OAAQ,EAAiC,OAAO;CAE3D;CACA,IAAI,OAAO,KAAU,UAAU,OAAO;AAExC;AAEA,SAAS,EAAkB,GAAoC;CAC7D,OAAO,aAAiB,QAAQ,EAAM,QAAQ,KAAA;AAChD;AAEA,SAAS,IAAW;CAClB,OAAO,OAAO,UAAY,OAAe,CAAC,CAAC,QAAQ,OAAA,QAAA,IAAA,aAAgC;AACrF;AAEA,SAAS,EAAe,GAAiB;CACvC,QAAQ,GAAR;EACE,KAAK,KACH,OAAO;GAAE,MAAM;GAAiB,OAAO;GAAkB,SAAS;GAAkE,SAAS;GAAe,WAAW;EAAiB;EAC1L,KAAK,KACH,OAAO;GAAE,MAAM;GAAS,OAAO;GAAiB,SAAS;GAA8G,SAAS;GAAgB,WAAW;EAAkB;EAC/N,KAAK,KACH,OAAO;GAAE,MAAM;GAAe,OAAO;GAAgB,SAAS;GAAqG,SAAS;GAAc,WAAW;EAAgB;EACvN,SACE,OAAO;GAAE,MAAM;GAAmB,OAAO;GAAwB,SAAS;GAA+E,SAAS;GAAe,WAAW;EAAiB;CACjN;AACF;AAEA,IAAM,IAAe,EAAM,WACzB,SAAsB,EAAE,UAAO,YAAS,YAAS,cAAW,IAAM,oBAAiB,IAAO,cAAW,GAAG,KAAS,GAAK;CACpH,IAAM,IAAS,EAAmB,CAAK,GACjC,IAAa,EAAoB,CAAK,GACtC,IAAQ,EAAkB,CAAK,GAC/B,IAAQ,EAAS,GACjB,IAAc,EAAe,CAAM,GACnC,IAAY,EAAY,MACxB,IAAW,EAAM,OAA0B,IAAI,GAI/C,IAAe,KAAQ,KAAoC,EAAY;CAM7E,OAJA,EAAM,gBAAgB;EACpB,AAAI,KAAgB,EAAS,SAAS,MAAM;CAC9C,GAAG,CAAC,CAAc,CAAC,GAGjB,kBAAC,OAAD;EAAU;EAAK,GAAI;EAAO,WAAW,EAAG,4CAA4C,KAAY,gBAAgB,CAAS;YAEvH,kBAAC,OAAD;GACE,MAAK;GACL,WAAU;aAFZ;IAIE,kBAAC,OAAD;KAAK,WAAW,EAAG,mEAAmE,EAAY,OAAO;eACvG,kBAAC,GAAD;MAAM,MAAM;MAAW,MAAK;MAAM,WAAW,EAAY;KAAY,CAAA;IAClE,CAAA;IAEL,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KAAU,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAsD,UAAO,CAAa;;MACrF,kBAAC,MAAD;OAAI,WAAU;iBAAiD,EAAY;MAAU,CAAA;MACrF,kBAAC,KAAD;OAAG,WAAU;iBAAuC;MAAgB,CAAA;KACjE;;KAEH,KAAW,MACX,kBAAC,OAAD;KAAK,WAAU;eACZ,KACC,kBAAC,GAAD;MAAQ,KAAK;MAAU,SAAQ;MAAO,MAAK;MAAK,SAAS;gBAAS;KAE1D,CAAA;IAEP,CAAA;IAGN,KAAS,KACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAsD;KAAiC,CAAA,GACpG,kBAAC,OAAD;MAAK,WAAU;gBAA2D;KAAW,CAAA,CAClF;;GAEJ;;CACF,CAAA;AAET,CACF;AACA,EAAa,cAAc;AAkB3B,SAAS,EAAY,GAA0B,GAA0B;CAGvE,OAFI,CAAC,KAAK,CAAC,IAAU,KACjB,EAAE,WAAW,EAAE,UACZ,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE,EAAE,CAAC;AAC7C;AAEA,IAAM,IAAN,cAA4B,EAAM,UAAkD;CAClF,YAAY,GAA2B;EAErC,AADA,MAAM,CAAK,GACX,KAAK,QAAQ,EAAE,OAAO,KAAK;CAC7B;CAEA,OAAO,yBAAyB,GAAoC;EAClE,OAAO,EAAE,SAAM;CACjB;CAEA,kBAAkB,GAAgB,GAAuB;EACvD,KAAK,MAAM,UAAU,GAAO,CAAI;CAClC;CAEA,mBAAmB,GAA0B;EAE3C,AAAI,KAAK,MAAM,UAAU,QAAQ,EAAY,EAAK,WAAW,KAAK,MAAM,SAAS,KAC/E,KAAK,SAAS,EAAE,OAAO,KAAK,CAAC;CAEjC;CAEA,oBAA4B;EAE1B,AADA,KAAK,SAAS,EAAE,OAAO,KAAK,CAAC,GAC7B,KAAK,MAAM,UAAU;CACvB;CAEA,SAAS;EAOP,OANI,KAAK,MAAM,UAAU,OAMlB,KAAK,MAAM,WALZ,KAAK,MAAM,WACN,KAAK,MAAM,SAAS;GAAE,OAAO,KAAK,MAAM;GAAO,SAAS,KAAK;EAAY,CAAC,IAE5E,kBAAC,GAAD;GAAc,OAAO,KAAK,MAAM;GAAO,SAAS,KAAK;GAAa,gBAAA;EAAgB,CAAA;CAG7F;AACF"}
@@ -52,7 +52,7 @@ function x({ url: e, fileName: t, onError: n }) {
52
52
  url: e
53
53
  }) : /* @__PURE__ */ m("div", {
54
54
  ref: a,
55
- className: "rounded-surface bg-surface-raised shadow-raised overflow-hidden",
55
+ className: "rounded-surface bg-surface-panel overflow-hidden",
56
56
  tabIndex: -1,
57
57
  children: [
58
58
  /* @__PURE__ */ p("audio", {
@@ -1 +1 @@
1
- {"version":3,"file":"file-preview.js","names":[],"sources":["../../src/composed/file-preview/audio-preview.tsx","../../src/composed/file-preview/embed-preview.tsx","../../src/composed/file-preview/video-preview.tsx","../../src/composed/file-preview.tsx"],"sourcesContent":["'use client'\n\nimport { IconPlayerPause,IconPlayerPlay } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { TruncatedText } from '../../ui/truncated-text'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Audio Preview — Branded mini-player (Spotify/SoundCloud style)\n// ============================================================\n\nexport default function AudioPreview({ url, fileName, onError }: { url: string; fileName?: string; onError?: (msg: string) => void }) {\n const audioRef = React.useRef<HTMLAudioElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [volume, setVolume] = React.useState(1)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Keyboard shortcuts\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const a = audioRef.current\n if (!a) return\n\n switch (e.key) {\n case ' ':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) a.pause(); else a.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n a.currentTime = Math.min(a.duration || 0, a.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n a.currentTime = Math.max(0, a.currentTime - 5)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n a.muted = !prev\n return !prev\n })\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n \n }, [])\n\n function togglePlay() {\n if (!audioRef.current) return\n if (playing) audioRef.current.pause()\n else audioRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!audioRef.current || !duration) return\n audioRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function toggleMute() {\n if (!audioRef.current) return\n const next = !muted\n setMuted(next)\n audioRef.current.muted = next\n }\n\n if (error) return <ErrorFallback message=\"Could not load audio\" url={url} />\n\n return (\n <div ref={containerRef} className=\"rounded-surface bg-surface-raised shadow-raised overflow-hidden\" tabIndex={-1}>\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <audio\n ref={audioRef}\n src={url}\n onTimeUpdate={() => {\n if (!audioRef.current) return\n setCurrentTime(audioRef.current.currentTime)\n if (audioRef.current.duration) setProgress((audioRef.current.currentTime / audioRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(audioRef.current?.duration ?? 0)}\n onEnded={() => setPlaying(false)}\n onError={() => { setError(true); onError?.('Audio failed to load') }}\n />\n\n {/* Progress bar — full width at top (Spotify style) */}\n <div className=\"px-ds-03 pt-ds-02\">\n <MediaSlider\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"light\"\n ariaLabel=\"Audio progress\"\n />\n </div>\n\n {/* Controls */}\n <div className=\"flex items-center gap-ds-04 px-ds-05 py-ds-04\">\n <Button\n variant=\"solid\"\n size=\"icon-sm\"\n onClick={togglePlay}\n aria-label={playing ? 'Pause' : 'Play'}\n title={playing ? 'Pause' : 'Play'}\n className=\"shrink-0\"\n >\n {playing ? (\n <Icon icon={IconPlayerPause} size=\"sm\" />\n ) : (\n <Icon icon={IconPlayerPlay} size=\"sm\" className=\"ml-0.5\" />\n )}\n </Button>\n\n {/* File name + time */}\n <div className=\"flex-1 min-w-0\">\n {fileName && (\n <TruncatedText as=\"p\" mode=\"middle\" className=\"text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n <p className=\"text-caption font-mono text-surface-fg-muted tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </p>\n </div>\n\n {/* Volume */}\n <VolumeControl\n volume={volume}\n muted={muted}\n onVolumeChange={(v) => {\n setVolume(v)\n if (audioRef.current) audioRef.current.volume = v\n if (v > 0 && muted) {\n setMuted(false)\n if (audioRef.current) audioRef.current.muted = false\n }\n }}\n onMuteToggle={toggleMute}\n variant=\"light\"\n />\n </div>\n </div>\n )\n}\n","'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { tweens } from '../../ui/lib/motion'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback } from './shared'\n\n// ============================================================\n// Embed Preview — 16:9 with error timeout\n// ============================================================\n\nexport default function EmbedPreview({ url, embedUrl, onError }: { url: string; embedUrl: string; onError?: (msg: string) => void }) {\n const [loaded, setLoaded] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Timeout — if iframe doesn't load in 15s, show error\n React.useEffect(() => {\n const timer = setTimeout(() => {\n if (!loaded) { setError(true); onError?.('Embed timed out') }\n }, 15000)\n return () => clearTimeout(timer)\n }, [loaded, onError])\n\n if (error) return <ErrorFallback message=\"Could not load embed\" url={url} />\n\n return (\n <div className=\"relative w-full rounded-control overflow-hidden\" style={{ aspectRatio: '16 / 9' }}>\n {!loaded && <Skeleton className=\"absolute inset-0\" />}\n <motion.iframe\n src={embedUrl}\n title=\"Embedded content\"\n onLoad={() => setLoaded(true)}\n onError={() => { setError(true); onError?.('Embed failed to load') }}\n initial={{ opacity: 0 }}\n animate={{ opacity: loaded ? 1 : 0 }}\n transition={tweens.fade}\n className=\"absolute inset-0 h-full w-full border-none\"\n allowFullScreen\n />\n </div>\n )\n}\n","'use client'\n\nimport {\n IconMaximize,\n IconPlayerPause,\n IconPlayerPlay,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from '../../ui/icon'\nimport { springs, tweens } from '../../ui/lib/motion'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Video Preview — Custom player with DS styling\n// ============================================================\n\nconst PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2] as const\n\nexport default function VideoPreview({ url, onError }: { url: string; onError?: (msg: string) => void }) {\n const videoRef = React.useRef<HTMLVideoElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n const [showControls, setShowControls] = React.useState(true)\n const [playbackRate, setPlaybackRate] = React.useState(1)\n // Kept current every render so the mount-time keydown handler (empty deps)\n // reads the live rate instead of the stale initial 1x. See #91.\n const playbackRateRef = React.useRef(playbackRate)\n playbackRateRef.current = playbackRate\n const hideTimer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n React.useEffect(() => {\n return () => clearTimeout(hideTimer.current)\n }, [])\n\n // Keyboard shortcuts — YouTube style\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const v = videoRef.current\n if (!v) return\n\n switch (e.key) {\n case ' ':\n case 'k':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) v.pause(); else v.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 5)\n break\n case 'j':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 10)\n break\n case 'l':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 10)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n v.muted = !prev\n return !prev\n })\n break\n case 'f':\n e.preventDefault()\n v.requestFullscreen?.()\n break\n case '>':\n e.preventDefault()\n cyclePlaybackRate(1)\n break\n case '<':\n e.preventDefault()\n cyclePlaybackRate(-1)\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [])\n\n function cyclePlaybackRate(direction: 1 | -1) {\n const idx = PLAYBACK_RATES.indexOf(playbackRateRef.current as typeof PLAYBACK_RATES[number])\n const nextIdx = Math.max(0, Math.min(PLAYBACK_RATES.length - 1, idx + direction))\n const next = PLAYBACK_RATES[nextIdx]\n setPlaybackRate(next)\n if (videoRef.current) videoRef.current.playbackRate = next\n }\n\n function togglePlay() {\n if (!videoRef.current) return\n if (playing) videoRef.current.pause()\n else videoRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!videoRef.current || !duration) return\n videoRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function handleMouseMove() {\n setShowControls(true)\n clearTimeout(hideTimer.current)\n if (playing) {\n hideTimer.current = setTimeout(() => setShowControls(false), 3000)\n }\n }\n\n if (error) return <ErrorFallback message=\"Could not load video\" url={url} />\n\n return (\n <div\n ref={containerRef}\n className=\"group relative rounded-control bg-black overflow-hidden\"\n onMouseMove={handleMouseMove}\n onMouseLeave={() => playing && setShowControls(false)}\n tabIndex={-1}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <video\n ref={videoRef}\n src={url}\n className=\"max-h-[70vh] w-full\"\n onClick={togglePlay}\n muted={muted}\n onTimeUpdate={() => {\n if (!videoRef.current) return\n setCurrentTime(videoRef.current.currentTime)\n if (videoRef.current.duration) setProgress((videoRef.current.currentTime / videoRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(videoRef.current?.duration ?? 0)}\n onEnded={() => { setPlaying(false); setShowControls(true) }}\n onError={() => { setError(true); onError?.('Video failed to load') }}\n playsInline\n />\n\n {/* Play button overlay — shown when paused */}\n {!playing && (\n <motion.button\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n onClick={togglePlay}\n className=\"absolute inset-0 flex items-center justify-center bg-black/30\"\n aria-label=\"Play video\"\n >\n <div className=\"flex h-14 w-14 items-center justify-center rounded-pill bg-white/90 shadow-floating\">\n <Icon icon={IconPlayerPlay} size=\"xl\" className=\"text-surface-fg ml-0.5\" />\n </div>\n </motion.button>\n )}\n\n {/* Bottom controls — auto-hide after 3s during playback */}\n <AnimatePresence>\n {showControls && duration > 0 && (\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n transition={tweens.fade}\n className=\"absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/80 to-transparent px-ds-04 pb-ds-04 pt-ds-08\"\n >\n {/* Progress bar */}\n <MediaSlider\n className=\"mb-ds-03\"\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"dark\"\n ariaLabel=\"Video progress\"\n />\n\n {/* Controls row */}\n <div className=\"flex items-center gap-ds-03\">\n <button onClick={togglePlay} className=\"text-white hover:text-white/80\" aria-label={playing ? 'Pause' : 'Play'} title={playing ? 'Pause' : 'Play'}>\n {playing ? <Icon icon={IconPlayerPause} size=\"lg\" /> : <Icon icon={IconPlayerPlay} size=\"lg\" />}\n </button>\n <VolumeControl\n volume={muted ? 0 : 1}\n muted={muted}\n onVolumeChange={(v) => {\n if (videoRef.current) videoRef.current.volume = v\n if (v > 0 && muted) { setMuted(false); if (videoRef.current) videoRef.current.muted = false }\n }}\n onMuteToggle={() => { setMuted(!muted); if (videoRef.current) videoRef.current.muted = !muted }}\n variant=\"dark\"\n />\n <span className=\"text-body-sm font-mono text-white/70 tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </span>\n <div className=\"flex-1\" />\n <button\n onClick={() => cyclePlaybackRate(1)}\n className=\"text-body-sm font-mono text-white/70 hover:text-white px-1 rounded-control-inner hover:bg-white/10 transition-colors\"\n aria-label={`Playback speed: ${playbackRate}x`}\n >\n {playbackRate}x\n </button>\n <button\n onClick={() => videoRef.current?.requestFullscreen?.()}\n className=\"text-white hover:text-white/80\"\n aria-label=\"Fullscreen (F)\"\n title=\"Fullscreen\"\n >\n <Icon icon={IconMaximize} size=\"sm\" />\n </button>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )\n}\n","'use client'\n\nimport { IconDownload } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// Sub-components: direct imports for non-peer-dep renderers,\n// React.lazy for renderers that pull in optional peer deps\n// (react-zoom-pan-pinch, react-pdf) to preserve code-splitting.\nconst LazyImagePreview = React.lazy(() => import('./file-preview/image-preview'))\nconst LazyDocumentPreview = React.lazy(() => import('./file-preview/document-preview'))\nimport AudioPreview from './file-preview/audio-preview'\nimport EmbedPreview from './file-preview/embed-preview'\nimport VideoPreview from './file-preview/video-preview'\n\n// ============================================================\n// Types\n// ============================================================\n\ntype FileType = 'image' | 'pdf' | 'video' | 'audio' | 'embed'\n\nexport interface FilePreviewProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n url: string\n /** Auto-detected from URL/mimeType if omitted */\n type?: FileType\n mimeType?: string\n alt?: string\n /** PDF starting page @default 1 */\n initialPage?: number\n /** File name for display */\n fileName?: string\n /** File size for display (e.g. '2.4 MB') */\n fileSize?: string\n /** Called on errors (broken URL, CORS, timeout) */\n onError?: (error: string) => void\n}\n\n// ============================================================\n// Type Detection\n// ============================================================\n\nfunction detectType(url: string, mimeType?: string): FileType {\n if (mimeType) {\n if (mimeType.startsWith('image/')) return 'image'\n if (mimeType === 'application/pdf') return 'pdf'\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n }\n\n const ext = url.split('?')[0].split('.').pop()?.toLowerCase()\n if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext ?? '')) return 'image'\n if (ext === 'pdf') return 'pdf'\n if (['mp4', 'webm', 'ogg', 'mov'].includes(ext ?? '')) return 'video'\n if (['mp3', 'wav', 'ogg', 'aac', 'flac'].includes(ext ?? '')) return 'audio'\n\n if (url.includes('figma.com') || url.includes('loom.com') || url.includes('youtube.com') || url.includes('youtu.be') || url.includes('vimeo.com')) {\n return 'embed'\n }\n\n return 'image'\n}\n\nfunction getEmbedUrl(url: string): string {\n const ytMatch = url.match(/(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/)([^&?]+)/)\n if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`\n\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/)\n if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`\n\n if (url.includes('figma.com')) return `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`\n\n const loomMatch = url.match(/loom\\.com\\/share\\/([^?]+)/)\n if (loomMatch) return `https://www.loom.com/embed/${loomMatch[1]}`\n\n return url\n}\n\n// ============================================================\n// FilePreview — Main component (router)\n// ============================================================\n\nfunction FilePreview({\n url,\n type: typeProp,\n mimeType,\n alt,\n initialPage = 1,\n fileName,\n fileSize,\n onError,\n className,\n ...props\n}: FilePreviewProps) {\n const type = typeProp ?? detectType(url, mimeType)\n const fallback = <Skeleton className=\"h-64 w-full rounded-control\" />\n\n return (\n <div className={cn('flex flex-col gap-ds-03', className)} {...props}>\n {/* File info bar */}\n {(fileName || fileSize) && (\n <div className=\"flex items-center gap-ds-03\">\n {fileName && (\n <TruncatedText mode=\"middle\" className=\"min-w-0 text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n {fileSize && <Badge variant=\"subtle\" size=\"xs\">{fileSize}</Badge>}\n </div>\n )}\n\n {/* Preview */}\n {type === 'image' && (\n <React.Suspense fallback={fallback}>\n <LazyImagePreview url={url} alt={alt} onError={onError} />\n </React.Suspense>\n )}\n {type === 'pdf' && (\n <React.Suspense fallback={fallback}>\n <LazyDocumentPreview url={url} initialPage={initialPage} onError={onError} />\n </React.Suspense>\n )}\n {type === 'video' && <VideoPreview url={url} onError={onError} />}\n {type === 'audio' && <AudioPreview url={url} fileName={fileName} onError={onError} />}\n {type === 'embed' && <EmbedPreview url={url} embedUrl={getEmbedUrl(url)} onError={onError} />}\n\n {/* Download — not shown for audio (it has its own layout) */}\n {type !== 'audio' && (\n <div className=\"flex justify-end\">\n <Button variant=\"ghost\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download</a>\n </Button>\n </div>\n )}\n </div>\n )\n}\n\nexport { FilePreview }\nexport type { FileType }\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-preview.js","names":[],"sources":["../../src/composed/file-preview/audio-preview.tsx","../../src/composed/file-preview/embed-preview.tsx","../../src/composed/file-preview/video-preview.tsx","../../src/composed/file-preview.tsx"],"sourcesContent":["'use client'\n\nimport { IconPlayerPause,IconPlayerPlay } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Button } from '../../ui/button'\nimport { Icon } from '../../ui/icon'\nimport { TruncatedText } from '../../ui/truncated-text'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Audio Preview — Branded mini-player (Spotify/SoundCloud style)\n// ============================================================\n\nexport default function AudioPreview({ url, fileName, onError }: { url: string; fileName?: string; onError?: (msg: string) => void }) {\n const audioRef = React.useRef<HTMLAudioElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [volume, setVolume] = React.useState(1)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Keyboard shortcuts\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const a = audioRef.current\n if (!a) return\n\n switch (e.key) {\n case ' ':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) a.pause(); else a.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n a.currentTime = Math.min(a.duration || 0, a.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n a.currentTime = Math.max(0, a.currentTime - 5)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n a.muted = !prev\n return !prev\n })\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n \n }, [])\n\n function togglePlay() {\n if (!audioRef.current) return\n if (playing) audioRef.current.pause()\n else audioRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!audioRef.current || !duration) return\n audioRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function toggleMute() {\n if (!audioRef.current) return\n const next = !muted\n setMuted(next)\n audioRef.current.muted = next\n }\n\n if (error) return <ErrorFallback message=\"Could not load audio\" url={url} />\n\n return (\n <div ref={containerRef} className=\"rounded-surface bg-surface-panel overflow-hidden\" tabIndex={-1}>\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <audio\n ref={audioRef}\n src={url}\n onTimeUpdate={() => {\n if (!audioRef.current) return\n setCurrentTime(audioRef.current.currentTime)\n if (audioRef.current.duration) setProgress((audioRef.current.currentTime / audioRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(audioRef.current?.duration ?? 0)}\n onEnded={() => setPlaying(false)}\n onError={() => { setError(true); onError?.('Audio failed to load') }}\n />\n\n {/* Progress bar — full width at top (Spotify style) */}\n <div className=\"px-ds-03 pt-ds-02\">\n <MediaSlider\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"light\"\n ariaLabel=\"Audio progress\"\n />\n </div>\n\n {/* Controls */}\n <div className=\"flex items-center gap-ds-04 px-ds-05 py-ds-04\">\n <Button\n variant=\"solid\"\n size=\"icon-sm\"\n onClick={togglePlay}\n aria-label={playing ? 'Pause' : 'Play'}\n title={playing ? 'Pause' : 'Play'}\n className=\"shrink-0\"\n >\n {playing ? (\n <Icon icon={IconPlayerPause} size=\"sm\" />\n ) : (\n <Icon icon={IconPlayerPlay} size=\"sm\" className=\"ml-0.5\" />\n )}\n </Button>\n\n {/* File name + time */}\n <div className=\"flex-1 min-w-0\">\n {fileName && (\n <TruncatedText as=\"p\" mode=\"middle\" className=\"text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n <p className=\"text-caption font-mono text-surface-fg-muted tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </p>\n </div>\n\n {/* Volume */}\n <VolumeControl\n volume={volume}\n muted={muted}\n onVolumeChange={(v) => {\n setVolume(v)\n if (audioRef.current) audioRef.current.volume = v\n if (v > 0 && muted) {\n setMuted(false)\n if (audioRef.current) audioRef.current.muted = false\n }\n }}\n onMuteToggle={toggleMute}\n variant=\"light\"\n />\n </div>\n </div>\n )\n}\n","'use client'\n\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { tweens } from '../../ui/lib/motion'\nimport { Skeleton } from '../../ui/skeleton'\nimport { ErrorFallback } from './shared'\n\n// ============================================================\n// Embed Preview — 16:9 with error timeout\n// ============================================================\n\nexport default function EmbedPreview({ url, embedUrl, onError }: { url: string; embedUrl: string; onError?: (msg: string) => void }) {\n const [loaded, setLoaded] = React.useState(false)\n const [error, setError] = React.useState(false)\n\n // Timeout — if iframe doesn't load in 15s, show error\n React.useEffect(() => {\n const timer = setTimeout(() => {\n if (!loaded) { setError(true); onError?.('Embed timed out') }\n }, 15000)\n return () => clearTimeout(timer)\n }, [loaded, onError])\n\n if (error) return <ErrorFallback message=\"Could not load embed\" url={url} />\n\n return (\n <div className=\"relative w-full rounded-control overflow-hidden\" style={{ aspectRatio: '16 / 9' }}>\n {!loaded && <Skeleton className=\"absolute inset-0\" />}\n <motion.iframe\n src={embedUrl}\n title=\"Embedded content\"\n onLoad={() => setLoaded(true)}\n onError={() => { setError(true); onError?.('Embed failed to load') }}\n initial={{ opacity: 0 }}\n animate={{ opacity: loaded ? 1 : 0 }}\n transition={tweens.fade}\n className=\"absolute inset-0 h-full w-full border-none\"\n allowFullScreen\n />\n </div>\n )\n}\n","'use client'\n\nimport {\n IconMaximize,\n IconPlayerPause,\n IconPlayerPlay,\n} from '@tabler/icons-react'\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from '../../ui/icon'\nimport { springs, tweens } from '../../ui/lib/motion'\nimport { ErrorFallback, formatTime,MediaSlider,VolumeControl } from './shared'\n\n// ============================================================\n// Video Preview — Custom player with DS styling\n// ============================================================\n\nconst PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2] as const\n\nexport default function VideoPreview({ url, onError }: { url: string; onError?: (msg: string) => void }) {\n const videoRef = React.useRef<HTMLVideoElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const [playing, setPlaying] = React.useState(false)\n const [progress, setProgress] = React.useState(0)\n const [duration, setDuration] = React.useState(0)\n const [currentTime, setCurrentTime] = React.useState(0)\n const [muted, setMuted] = React.useState(false)\n const [error, setError] = React.useState(false)\n const [showControls, setShowControls] = React.useState(true)\n const [playbackRate, setPlaybackRate] = React.useState(1)\n // Kept current every render so the mount-time keydown handler (empty deps)\n // reads the live rate instead of the stale initial 1x. See #91.\n const playbackRateRef = React.useRef(playbackRate)\n playbackRateRef.current = playbackRate\n const hideTimer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n React.useEffect(() => {\n return () => clearTimeout(hideTimer.current)\n }, [])\n\n // Keyboard shortcuts — YouTube style\n React.useEffect(() => {\n function handleKeyDown(e: KeyboardEvent) {\n const el = containerRef.current\n if (!el || !el.contains(document.activeElement) && document.activeElement !== el) return\n const v = videoRef.current\n if (!v) return\n\n switch (e.key) {\n case ' ':\n case 'k':\n e.preventDefault()\n setPlaying(prev => {\n if (prev) v.pause(); else v.play()\n return !prev\n })\n break\n case 'ArrowRight':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 5)\n break\n case 'ArrowLeft':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 5)\n break\n case 'j':\n e.preventDefault()\n v.currentTime = Math.max(0, v.currentTime - 10)\n break\n case 'l':\n e.preventDefault()\n v.currentTime = Math.min(v.duration, v.currentTime + 10)\n break\n case 'm':\n e.preventDefault()\n setMuted(prev => {\n v.muted = !prev\n return !prev\n })\n break\n case 'f':\n e.preventDefault()\n v.requestFullscreen?.()\n break\n case '>':\n e.preventDefault()\n cyclePlaybackRate(1)\n break\n case '<':\n e.preventDefault()\n cyclePlaybackRate(-1)\n break\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n }, [])\n\n function cyclePlaybackRate(direction: 1 | -1) {\n const idx = PLAYBACK_RATES.indexOf(playbackRateRef.current as typeof PLAYBACK_RATES[number])\n const nextIdx = Math.max(0, Math.min(PLAYBACK_RATES.length - 1, idx + direction))\n const next = PLAYBACK_RATES[nextIdx]\n setPlaybackRate(next)\n if (videoRef.current) videoRef.current.playbackRate = next\n }\n\n function togglePlay() {\n if (!videoRef.current) return\n if (playing) videoRef.current.pause()\n else videoRef.current.play()\n setPlaying(!playing)\n }\n\n function handleSeek(nextProgress: number) {\n if (!videoRef.current || !duration) return\n videoRef.current.currentTime = (nextProgress / 100) * duration\n }\n\n function handleMouseMove() {\n setShowControls(true)\n clearTimeout(hideTimer.current)\n if (playing) {\n hideTimer.current = setTimeout(() => setShowControls(false), 3000)\n }\n }\n\n if (error) return <ErrorFallback message=\"Could not load video\" url={url} />\n\n return (\n <div\n ref={containerRef}\n className=\"group relative rounded-control bg-black overflow-hidden\"\n onMouseMove={handleMouseMove}\n onMouseLeave={() => playing && setShowControls(false)}\n tabIndex={-1}\n >\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <video\n ref={videoRef}\n src={url}\n className=\"max-h-[70vh] w-full\"\n onClick={togglePlay}\n muted={muted}\n onTimeUpdate={() => {\n if (!videoRef.current) return\n setCurrentTime(videoRef.current.currentTime)\n if (videoRef.current.duration) setProgress((videoRef.current.currentTime / videoRef.current.duration) * 100)\n }}\n onLoadedMetadata={() => setDuration(videoRef.current?.duration ?? 0)}\n onEnded={() => { setPlaying(false); setShowControls(true) }}\n onError={() => { setError(true); onError?.('Video failed to load') }}\n playsInline\n />\n\n {/* Play button overlay — shown when paused */}\n {!playing && (\n <motion.button\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={springs.snappy}\n onClick={togglePlay}\n className=\"absolute inset-0 flex items-center justify-center bg-black/30\"\n aria-label=\"Play video\"\n >\n <div className=\"flex h-14 w-14 items-center justify-center rounded-pill bg-white/90 shadow-floating\">\n <Icon icon={IconPlayerPlay} size=\"xl\" className=\"text-surface-fg ml-0.5\" />\n </div>\n </motion.button>\n )}\n\n {/* Bottom controls — auto-hide after 3s during playback */}\n <AnimatePresence>\n {showControls && duration > 0 && (\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 10 }}\n transition={tweens.fade}\n className=\"absolute bottom-0 left-0 right-0 bg-linear-to-t from-black/80 to-transparent px-ds-04 pb-ds-04 pt-ds-08\"\n >\n {/* Progress bar */}\n <MediaSlider\n className=\"mb-ds-03\"\n value={progress}\n max={100}\n step={0.1}\n onValueChange={handleSeek}\n tone=\"dark\"\n ariaLabel=\"Video progress\"\n />\n\n {/* Controls row */}\n <div className=\"flex items-center gap-ds-03\">\n <button onClick={togglePlay} className=\"text-white hover:text-white/80\" aria-label={playing ? 'Pause' : 'Play'} title={playing ? 'Pause' : 'Play'}>\n {playing ? <Icon icon={IconPlayerPause} size=\"lg\" /> : <Icon icon={IconPlayerPlay} size=\"lg\" />}\n </button>\n <VolumeControl\n volume={muted ? 0 : 1}\n muted={muted}\n onVolumeChange={(v) => {\n if (videoRef.current) videoRef.current.volume = v\n if (v > 0 && muted) { setMuted(false); if (videoRef.current) videoRef.current.muted = false }\n }}\n onMuteToggle={() => { setMuted(!muted); if (videoRef.current) videoRef.current.muted = !muted }}\n variant=\"dark\"\n />\n <span className=\"text-body-sm font-mono text-white/70 tabular-nums\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </span>\n <div className=\"flex-1\" />\n <button\n onClick={() => cyclePlaybackRate(1)}\n className=\"text-body-sm font-mono text-white/70 hover:text-white px-1 rounded-control-inner hover:bg-white/10 transition-colors\"\n aria-label={`Playback speed: ${playbackRate}x`}\n >\n {playbackRate}x\n </button>\n <button\n onClick={() => videoRef.current?.requestFullscreen?.()}\n className=\"text-white hover:text-white/80\"\n aria-label=\"Fullscreen (F)\"\n title=\"Fullscreen\"\n >\n <Icon icon={IconMaximize} size=\"sm\" />\n </button>\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )\n}\n","'use client'\n\nimport { IconDownload } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\nimport { TruncatedText } from '../ui/truncated-text'\n\n// Sub-components: direct imports for non-peer-dep renderers,\n// React.lazy for renderers that pull in optional peer deps\n// (react-zoom-pan-pinch, react-pdf) to preserve code-splitting.\nconst LazyImagePreview = React.lazy(() => import('./file-preview/image-preview'))\nconst LazyDocumentPreview = React.lazy(() => import('./file-preview/document-preview'))\nimport AudioPreview from './file-preview/audio-preview'\nimport EmbedPreview from './file-preview/embed-preview'\nimport VideoPreview from './file-preview/video-preview'\n\n// ============================================================\n// Types\n// ============================================================\n\ntype FileType = 'image' | 'pdf' | 'video' | 'audio' | 'embed'\n\nexport interface FilePreviewProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onError'> {\n url: string\n /** Auto-detected from URL/mimeType if omitted */\n type?: FileType\n mimeType?: string\n alt?: string\n /** PDF starting page @default 1 */\n initialPage?: number\n /** File name for display */\n fileName?: string\n /** File size for display (e.g. '2.4 MB') */\n fileSize?: string\n /** Called on errors (broken URL, CORS, timeout) */\n onError?: (error: string) => void\n}\n\n// ============================================================\n// Type Detection\n// ============================================================\n\nfunction detectType(url: string, mimeType?: string): FileType {\n if (mimeType) {\n if (mimeType.startsWith('image/')) return 'image'\n if (mimeType === 'application/pdf') return 'pdf'\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n }\n\n const ext = url.split('?')[0].split('.').pop()?.toLowerCase()\n if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext ?? '')) return 'image'\n if (ext === 'pdf') return 'pdf'\n if (['mp4', 'webm', 'ogg', 'mov'].includes(ext ?? '')) return 'video'\n if (['mp3', 'wav', 'ogg', 'aac', 'flac'].includes(ext ?? '')) return 'audio'\n\n if (url.includes('figma.com') || url.includes('loom.com') || url.includes('youtube.com') || url.includes('youtu.be') || url.includes('vimeo.com')) {\n return 'embed'\n }\n\n return 'image'\n}\n\nfunction getEmbedUrl(url: string): string {\n const ytMatch = url.match(/(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/)([^&?]+)/)\n if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`\n\n const vimeoMatch = url.match(/vimeo\\.com\\/(\\d+)/)\n if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`\n\n if (url.includes('figma.com')) return `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(url)}`\n\n const loomMatch = url.match(/loom\\.com\\/share\\/([^?]+)/)\n if (loomMatch) return `https://www.loom.com/embed/${loomMatch[1]}`\n\n return url\n}\n\n// ============================================================\n// FilePreview — Main component (router)\n// ============================================================\n\nfunction FilePreview({\n url,\n type: typeProp,\n mimeType,\n alt,\n initialPage = 1,\n fileName,\n fileSize,\n onError,\n className,\n ...props\n}: FilePreviewProps) {\n const type = typeProp ?? detectType(url, mimeType)\n const fallback = <Skeleton className=\"h-64 w-full rounded-control\" />\n\n return (\n <div className={cn('flex flex-col gap-ds-03', className)} {...props}>\n {/* File info bar */}\n {(fileName || fileSize) && (\n <div className=\"flex items-center gap-ds-03\">\n {fileName && (\n <TruncatedText mode=\"middle\" className=\"min-w-0 text-body-sm font-semibold text-surface-fg\">\n {fileName}\n </TruncatedText>\n )}\n {fileSize && <Badge variant=\"subtle\" size=\"xs\">{fileSize}</Badge>}\n </div>\n )}\n\n {/* Preview */}\n {type === 'image' && (\n <React.Suspense fallback={fallback}>\n <LazyImagePreview url={url} alt={alt} onError={onError} />\n </React.Suspense>\n )}\n {type === 'pdf' && (\n <React.Suspense fallback={fallback}>\n <LazyDocumentPreview url={url} initialPage={initialPage} onError={onError} />\n </React.Suspense>\n )}\n {type === 'video' && <VideoPreview url={url} onError={onError} />}\n {type === 'audio' && <AudioPreview url={url} fileName={fileName} onError={onError} />}\n {type === 'embed' && <EmbedPreview url={url} embedUrl={getEmbedUrl(url)} onError={onError} />}\n\n {/* Download — not shown for audio (it has its own layout) */}\n {type !== 'audio' && (\n <div className=\"flex justify-end\">\n <Button variant=\"ghost\" size=\"xs\" startIcon={<Icon icon={IconDownload} />} asChild>\n <a href={url} download target=\"_blank\" rel=\"noopener noreferrer\">Download</a>\n </Button>\n </div>\n )}\n </div>\n )\n}\n\nexport { FilePreview }\nexport type { FileType }\n"],"mappings":"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"}
@@ -70,7 +70,7 @@ function b({ label: i, value: a, onValueChange: o, options: s, className: c }) {
70
70
  searchPlaceholder: `Search ${i.toLowerCase()}...`,
71
71
  children: /* @__PURE__ */ m("button", {
72
72
  type: "button",
73
- className: t("flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-raised-hover text-surface-fg", "hover:bg-surface-raised-active transition-colors duration-fast-01 ease-productive-standard", {
73
+ className: t("flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-panel-hover text-surface-fg", "hover:bg-surface-panel-active transition-colors duration-fast-01 ease-productive-standard", {
74
74
  xs: "h-ds-xs-plus text-body-sm px-ds-02",
75
75
  sm: "h-ds-sm text-body-sm px-ds-03",
76
76
  md: "h-ds-md text-body-md px-ds-04"
@@ -1 +1 @@
1
- {"version":3,"file":"filter-bar.js","names":[],"sources":["../../src/composed/filter-bar.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { SearchInput } from '../ui/search-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { type MultiSelectItem,MultiSelectPopover } from './multi-select-popover'\n\n// ============================================================\n// Context\n// ============================================================\n\ntype FilterBarSize = 'xs' | 'sm' | 'md'\n\nconst FilterBarContext = React.createContext<{ size: FilterBarSize }>({ size: 'sm' })\n\n// ============================================================\n// FilterBar\n// ============================================================\n\n/**\n * A toolbar for filtering lists/tables. Composes a search input, slot-based\n * filter controls (Select, MultiSelect), and an optional \"Clear all\" button.\n * Size is propagated to all child controls via context.\n */\nexport interface FilterBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controlled search input value. Omit `onSearchChange` to hide the search input entirely. */\n searchValue?: string\n /** Called when the search input changes. If not provided, the search input is hidden. */\n onSearchChange?: (value: string) => void\n searchPlaceholder?: string\n /** When provided, a \"Clear all\" button appears that calls this handler. */\n onClearAll?: () => void\n /** Size propagated to all child controls. @default 'sm' */\n size?: FilterBarSize\n}\n\nconst FilterBar = React.forwardRef<HTMLDivElement, FilterBarProps>(({\n searchValue,\n onSearchChange,\n searchPlaceholder = 'Search...',\n onClearAll,\n size = 'sm',\n children,\n className,\n ...props\n}, ref) => {\n return (\n <FilterBarContext.Provider value={{ size }}>\n <div\n ref={ref}\n className={cn('flex flex-wrap items-center gap-ds-03', className)}\n role=\"toolbar\"\n aria-label=\"Filters\"\n {...props}\n >\n {onSearchChange && (\n <SearchInput\n size={size}\n value={searchValue}\n onChange={(e) => onSearchChange(e.target.value)}\n onClear={searchValue ? () => onSearchChange('') : undefined}\n placeholder={searchPlaceholder}\n className=\"w-48\"\n />\n )}\n {children}\n {onClearAll && (\n <Button variant=\"ghost\" size={size} onClick={onClearAll}>\n Clear all\n </Button>\n )}\n </div>\n </FilterBarContext.Provider>\n )\n})\nFilterBar.displayName = 'FilterBar'\n\n// ============================================================\n// FilterSelect\n// ============================================================\n\nexport interface FilterSelectProps {\n label: string\n value: string\n onValueChange: (value: string) => void\n options: { value: string; label: string }[]\n /** Label for the \"all\" option @default 'All' */\n allLabel?: string\n className?: string\n}\n\nfunction FilterSelect({\n label,\n value,\n onValueChange,\n options,\n allLabel = 'All',\n className,\n}: FilterSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const isFiltered = value !== '' && value !== 'all'\n\n return (\n <Select value={value} onValueChange={onValueChange}>\n <SelectTrigger\n size={size}\n className={cn('w-40', isFiltered && 'border-accent-7', className)}\n >\n <SelectValue placeholder={label} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{allLabel}</SelectItem>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\n// ============================================================\n// FilterMultiSelect\n// ============================================================\n\nexport interface FilterMultiSelectProps {\n label: string\n value: string[]\n onValueChange: (values: string[]) => void\n options: { value: string; label: string }[]\n className?: string\n}\n\nfunction FilterMultiSelect({\n label,\n value,\n onValueChange,\n options,\n className,\n}: FilterMultiSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const count = value.length\n\n const items: MultiSelectItem[] = React.useMemo(\n () => options.map((o) => ({ id: o.value, label: o.label })),\n [options],\n )\n\n // Trigger button sized by FilterBar context\n const triggerSizeClasses: Record<string, string> = {\n xs: 'h-ds-xs-plus text-body-sm px-ds-02',\n sm: 'h-ds-sm text-body-sm px-ds-03',\n md: 'h-ds-md text-body-md px-ds-04',\n }\n\n return (\n <MultiSelectPopover\n items={items}\n value={value}\n onValueChange={onValueChange}\n searchPlaceholder={`Search ${label.toLowerCase()}...`}\n >\n <button\n type=\"button\"\n className={cn(\n 'flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-raised-hover text-surface-fg',\n 'hover:bg-surface-raised-active transition-colors duration-fast-01 ease-productive-standard',\n triggerSizeClasses[size],\n count > 0 ? 'border-accent-7' : 'border-surface-border-strong',\n 'w-40',\n className,\n )}\n >\n <span className=\"flex min-w-0 items-center gap-ds-02\">\n <span className=\"truncate\">{label}</span>\n {count > 0 && (\n <motion.span\n key={count}\n initial={{ scale: 0.8 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex\"\n >\n <Badge size=\"xs\" variant=\"solid\" className=\"ml-ds-01\">\n {count}\n </Badge>\n </motion.span>\n )}\n </span>\n <Icon icon={IconChevronDown} size=\"xs\" className=\"opacity-50 shrink-0\" />\n </button>\n </MultiSelectPopover>\n )\n}\n\nexport { FilterBar, FilterMultiSelect,FilterSelect }\n"],"mappings":";;;;;;;;;;;;;;AA2BA,IAAM,IAAmB,EAAM,cAAuC,EAAE,MAAM,KAAK,CAAC,GAuB9E,IAAY,EAAM,YAA4C,EAClE,gBACA,mBACA,uBAAoB,aACpB,eACA,UAAO,MACP,aACA,cACA,GAAG,KACF,MAEC,kBAAC,EAAiB,UAAlB;CAA2B,OAAO,EAAE,QAAK;WACvC,kBAAC,OAAD;EACO;EACL,WAAW,EAAG,yCAAyC,CAAS;EAChE,MAAK;EACL,cAAW;EACX,GAAI;YALN;GAOG,KACC,kBAAC,GAAD;IACQ;IACN,OAAO;IACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;IAC9C,SAAS,UAAoB,EAAe,EAAE,IAAI,KAAA;IAClD,aAAa;IACb,WAAU;GACX,CAAA;GAEF;GACA,KACC,kBAAC,GAAD;IAAQ,SAAQ;IAAc;IAAM,SAAS;cAAY;GAEjD,CAAA;EAEP;;AACoB,CAAA,CAE9B;AACD,EAAU,cAAc;AAgBxB,SAAS,EAAa,EACpB,UACA,UACA,kBACA,YACA,cAAW,OACX,gBACoB;CACpB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB;CAGlD,OACE,kBAAC,GAAD;EAAe;EAAsB;YAArC,CACE,kBAAC,GAAD;GACQ;GACN,WAAW,EAAG,QAND,MAAU,MAAM,MAAU,SAMH,mBAAmB,CAAS;aAEhE,kBAAC,GAAD,EAAa,aAAa,EAAQ,CAAA;EACrB,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAY,OAAM;aAAO;EAAqB,CAAA,GAC7C,EAAQ,KAAK,MACZ,kBAAC,GAAD;GAA0B,OAAO,EAAE;aAChC,EAAE;EACO,GAFK,EAAE,KAEP,CACb,CACY,EAAA,CAAA,CACT;;AAEZ;AAcA,SAAS,EAAkB,EACzB,UACA,UACA,kBACA,YACA,gBACyB;CACzB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB,GAC5C,IAAQ,EAAM;CAcpB,OACE,kBAAC,GAAD;EACS,OAdsB,EAAM,cAC/B,EAAQ,KAAK,OAAO;GAAE,IAAI,EAAE;GAAO,OAAO,EAAE;EAAM,EAAE,GAC1D,CAAC,CAAO,CAYC;EACA;EACQ;EACf,mBAAmB,UAAU,EAAM,YAAY,EAAE;YAEjD,kBAAC,UAAD;GACE,MAAK;GACL,WAAW,EACT,gIACA,8FACA;IAjBN,IAAI;IACJ,IAAI;IACJ,IAAI;GAeE,EAAmB,IACnB,IAAQ,IAAI,oBAAoB,gCAChC,QACA,CACF;aATF,CAWE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAY;IAAY,CAAA,GACvC,IAAQ,KACP,kBAAC,EAAO,MAAR;KAEE,SAAS,EAAE,OAAO,GAAI;KACtB,SAAS,EAAE,OAAO,EAAE;KACpB,YAAY,EAAQ;KACpB,WAAU;eAEV,kBAAC,GAAD;MAAO,MAAK;MAAK,SAAQ;MAAQ,WAAU;gBACxC;KACI,CAAA;IACI,GATN,CASM,CAEX;OACN,kBAAC,GAAD;IAAM,MAAM;IAAiB,MAAK;IAAK,WAAU;GAAuB,CAAA,CAClE;;CACU,CAAA;AAExB"}
1
+ {"version":3,"file":"filter-bar.js","names":[],"sources":["../../src/composed/filter-bar.tsx"],"sourcesContent":["'use client'\n\nimport { IconChevronDown } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport { SearchInput } from '../ui/search-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../ui/select'\nimport { type MultiSelectItem,MultiSelectPopover } from './multi-select-popover'\n\n// ============================================================\n// Context\n// ============================================================\n\ntype FilterBarSize = 'xs' | 'sm' | 'md'\n\nconst FilterBarContext = React.createContext<{ size: FilterBarSize }>({ size: 'sm' })\n\n// ============================================================\n// FilterBar\n// ============================================================\n\n/**\n * A toolbar for filtering lists/tables. Composes a search input, slot-based\n * filter controls (Select, MultiSelect), and an optional \"Clear all\" button.\n * Size is propagated to all child controls via context.\n */\nexport interface FilterBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controlled search input value. Omit `onSearchChange` to hide the search input entirely. */\n searchValue?: string\n /** Called when the search input changes. If not provided, the search input is hidden. */\n onSearchChange?: (value: string) => void\n searchPlaceholder?: string\n /** When provided, a \"Clear all\" button appears that calls this handler. */\n onClearAll?: () => void\n /** Size propagated to all child controls. @default 'sm' */\n size?: FilterBarSize\n}\n\nconst FilterBar = React.forwardRef<HTMLDivElement, FilterBarProps>(({\n searchValue,\n onSearchChange,\n searchPlaceholder = 'Search...',\n onClearAll,\n size = 'sm',\n children,\n className,\n ...props\n}, ref) => {\n return (\n <FilterBarContext.Provider value={{ size }}>\n <div\n ref={ref}\n className={cn('flex flex-wrap items-center gap-ds-03', className)}\n role=\"toolbar\"\n aria-label=\"Filters\"\n {...props}\n >\n {onSearchChange && (\n <SearchInput\n size={size}\n value={searchValue}\n onChange={(e) => onSearchChange(e.target.value)}\n onClear={searchValue ? () => onSearchChange('') : undefined}\n placeholder={searchPlaceholder}\n className=\"w-48\"\n />\n )}\n {children}\n {onClearAll && (\n <Button variant=\"ghost\" size={size} onClick={onClearAll}>\n Clear all\n </Button>\n )}\n </div>\n </FilterBarContext.Provider>\n )\n})\nFilterBar.displayName = 'FilterBar'\n\n// ============================================================\n// FilterSelect\n// ============================================================\n\nexport interface FilterSelectProps {\n label: string\n value: string\n onValueChange: (value: string) => void\n options: { value: string; label: string }[]\n /** Label for the \"all\" option @default 'All' */\n allLabel?: string\n className?: string\n}\n\nfunction FilterSelect({\n label,\n value,\n onValueChange,\n options,\n allLabel = 'All',\n className,\n}: FilterSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const isFiltered = value !== '' && value !== 'all'\n\n return (\n <Select value={value} onValueChange={onValueChange}>\n <SelectTrigger\n size={size}\n className={cn('w-40', isFiltered && 'border-accent-7', className)}\n >\n <SelectValue placeholder={label} />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{allLabel}</SelectItem>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\n// ============================================================\n// FilterMultiSelect\n// ============================================================\n\nexport interface FilterMultiSelectProps {\n label: string\n value: string[]\n onValueChange: (values: string[]) => void\n options: { value: string; label: string }[]\n className?: string\n}\n\nfunction FilterMultiSelect({\n label,\n value,\n onValueChange,\n options,\n className,\n}: FilterMultiSelectProps) {\n const { size } = React.useContext(FilterBarContext)\n const count = value.length\n\n const items: MultiSelectItem[] = React.useMemo(\n () => options.map((o) => ({ id: o.value, label: o.label })),\n [options],\n )\n\n // Trigger button sized by FilterBar context\n const triggerSizeClasses: Record<string, string> = {\n xs: 'h-ds-xs-plus text-body-sm px-ds-02',\n sm: 'h-ds-sm text-body-sm px-ds-03',\n md: 'h-ds-md text-body-md px-ds-04',\n }\n\n return (\n <MultiSelectPopover\n items={items}\n value={value}\n onValueChange={onValueChange}\n searchPlaceholder={`Search ${label.toLowerCase()}...`}\n >\n <button\n type=\"button\"\n className={cn(\n 'flex items-center justify-between gap-ds-02 whitespace-nowrap rounded-control border bg-surface-panel-hover text-surface-fg',\n 'hover:bg-surface-panel-active transition-colors duration-fast-01 ease-productive-standard',\n triggerSizeClasses[size],\n count > 0 ? 'border-accent-7' : 'border-surface-border-strong',\n 'w-40',\n className,\n )}\n >\n <span className=\"flex min-w-0 items-center gap-ds-02\">\n <span className=\"truncate\">{label}</span>\n {count > 0 && (\n <motion.span\n key={count}\n initial={{ scale: 0.8 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex\"\n >\n <Badge size=\"xs\" variant=\"solid\" className=\"ml-ds-01\">\n {count}\n </Badge>\n </motion.span>\n )}\n </span>\n <Icon icon={IconChevronDown} size=\"xs\" className=\"opacity-50 shrink-0\" />\n </button>\n </MultiSelectPopover>\n )\n}\n\nexport { FilterBar, FilterMultiSelect,FilterSelect }\n"],"mappings":";;;;;;;;;;;;;;AA2BA,IAAM,IAAmB,EAAM,cAAuC,EAAE,MAAM,KAAK,CAAC,GAuB9E,IAAY,EAAM,YAA4C,EAClE,gBACA,mBACA,uBAAoB,aACpB,eACA,UAAO,MACP,aACA,cACA,GAAG,KACF,MAEC,kBAAC,EAAiB,UAAlB;CAA2B,OAAO,EAAE,QAAK;WACvC,kBAAC,OAAD;EACO;EACL,WAAW,EAAG,yCAAyC,CAAS;EAChE,MAAK;EACL,cAAW;EACX,GAAI;YALN;GAOG,KACC,kBAAC,GAAD;IACQ;IACN,OAAO;IACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;IAC9C,SAAS,UAAoB,EAAe,EAAE,IAAI,KAAA;IAClD,aAAa;IACb,WAAU;GACX,CAAA;GAEF;GACA,KACC,kBAAC,GAAD;IAAQ,SAAQ;IAAc;IAAM,SAAS;cAAY;GAEjD,CAAA;EAEP;;AACoB,CAAA,CAE9B;AACD,EAAU,cAAc;AAgBxB,SAAS,EAAa,EACpB,UACA,UACA,kBACA,YACA,cAAW,OACX,gBACoB;CACpB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB;CAGlD,OACE,kBAAC,GAAD;EAAe;EAAsB;YAArC,CACE,kBAAC,GAAD;GACQ;GACN,WAAW,EAAG,QAND,MAAU,MAAM,MAAU,SAMH,mBAAmB,CAAS;aAEhE,kBAAC,GAAD,EAAa,aAAa,EAAQ,CAAA;EACrB,CAAA,GACf,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;GAAY,OAAM;aAAO;EAAqB,CAAA,GAC7C,EAAQ,KAAK,MACZ,kBAAC,GAAD;GAA0B,OAAO,EAAE;aAChC,EAAE;EACO,GAFK,EAAE,KAEP,CACb,CACY,EAAA,CAAA,CACT;;AAEZ;AAcA,SAAS,EAAkB,EACzB,UACA,UACA,kBACA,YACA,gBACyB;CACzB,IAAM,EAAE,YAAS,EAAM,WAAW,CAAgB,GAC5C,IAAQ,EAAM;CAcpB,OACE,kBAAC,GAAD;EACS,OAdsB,EAAM,cAC/B,EAAQ,KAAK,OAAO;GAAE,IAAI,EAAE;GAAO,OAAO,EAAE;EAAM,EAAE,GAC1D,CAAC,CAAO,CAYC;EACA;EACQ;EACf,mBAAmB,UAAU,EAAM,YAAY,EAAE;YAEjD,kBAAC,UAAD;GACE,MAAK;GACL,WAAW,EACT,+HACA,6FACA;IAjBN,IAAI;IACJ,IAAI;IACJ,IAAI;GAeE,EAAmB,IACnB,IAAQ,IAAI,oBAAoB,gCAChC,QACA,CACF;aATF,CAWE,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,QAAD;KAAM,WAAU;eAAY;IAAY,CAAA,GACvC,IAAQ,KACP,kBAAC,EAAO,MAAR;KAEE,SAAS,EAAE,OAAO,GAAI;KACtB,SAAS,EAAE,OAAO,EAAE;KACpB,YAAY,EAAQ;KACpB,WAAU;eAEV,kBAAC,GAAD;MAAO,MAAK;MAAK,SAAQ;MAAQ,WAAU;gBACxC;KACI,CAAA;IACI,GATN,CASM,CAEX;OACN,kBAAC,GAAD;IAAM,MAAM;IAAiB,MAAK;IAAK,WAAU;GAAuB,CAAA,CAClE;;CACU,CAAA;AAExB"}
@@ -78,7 +78,7 @@ var s = r.forwardRef(({ value: s, onSave: c, placeholder: l = "Click to edit", t
78
78
  onInput: j,
79
79
  onPaste: M,
80
80
  spellCheck: x,
81
- className: e("font-sans text-surface-fg outline-hidden", u, !d && !x && "cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-raised-hover transition-colors duration-fast-01", x && "rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-raised-hover ring-1 ring-accent-7", E && !x && "text-surface-fg-subtle italic", d && "cursor-default"),
81
+ className: e("font-sans text-surface-fg outline-hidden", u, !d && !x && "cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-panel-hover transition-colors duration-fast-01", x && "rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-panel-hover ring-1 ring-accent-7", E && !x && "text-surface-fg-subtle italic", d && "cursor-default"),
82
82
  "data-placeholder": l,
83
83
  children: s || (x ? "" : l)
84
84
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"inline-edit.js","names":[],"sources":["../../src/composed/inline-edit.tsx"],"sourcesContent":["'use client'\n\nimport { IconPencil } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface InlineEditProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSave'> {\n value: string\n onSave: (newValue: string) => void | Promise<void>\n placeholder?: string\n /** CSS class applied to the editable text (e.g. 'text-heading-xs font-semibold') */\n textClassName?: string\n readOnly?: boolean\n maxLength?: number\n /** External saving state — shows spinner */\n saving?: boolean\n}\n\n// ============================================================\n// InlineEdit — contentEditable approach\n//\n// No mode switch. No input field. The text IS the editor.\n// Click → cursor appears in the text. Type. Enter saves. Escape reverts.\n// Like Notion, Linear, Figma layer names.\n// ============================================================\n\nconst InlineEdit = React.forwardRef<HTMLDivElement, InlineEditProps>(({\n value,\n onSave,\n placeholder = 'Click to edit',\n textClassName,\n readOnly = false,\n maxLength,\n saving: savingProp = false,\n className,\n // aria-label / aria-labelledby apply to the editable textbox (the inner\n // span), not the outer wrapper div. Intercept them from ...props so they\n // land on the role=\"textbox\" element that actually needs the name.\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n}, ref) => {\n const [saving, setSaving] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const editRef = React.useRef<HTMLSpanElement>(null)\n const snapshotRef = React.useRef(value)\n\n const isSaving = savingProp || saving\n const isEmpty = !value\n\n // Sync DOM text when value changes externally (and not focused)\n React.useEffect(() => {\n if (!focused && editRef.current) {\n editRef.current.textContent = value\n }\n }, [value, focused])\n\n function handleFocus() {\n if (readOnly || isSaving) return\n setFocused(true)\n snapshotRef.current = value\n\n // Select all text on focus (like clicking a file name in Finder)\n requestAnimationFrame(() => {\n if (!editRef.current) return\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n })\n }\n\n async function commit() {\n if (!editRef.current) return\n setFocused(false)\n const raw = editRef.current.textContent ?? ''\n const trimmed = raw.trim()\n\n // Enforce maxLength\n const final = maxLength ? trimmed.slice(0, maxLength) : trimmed\n\n // Reset DOM to clean value\n editRef.current.textContent = final || value\n\n if (final === value) return\n const result = onSave(final)\n if (result instanceof Promise) {\n setSaving(true)\n try {\n await result\n } catch {\n // Revert on error\n if (editRef.current) editRef.current.textContent = value\n } finally {\n setSaving(false)\n }\n }\n }\n\n function cancel() {\n if (!editRef.current) return\n editRef.current.textContent = snapshotRef.current\n setFocused(false)\n editRef.current.blur()\n }\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault()\n cancel()\n } else if (e.key === 'Enter') {\n e.preventDefault()\n commit()\n }\n }\n\n function handleInput() {\n if (!editRef.current || !maxLength) return\n const text = editRef.current.textContent ?? ''\n if (text.length > maxLength) {\n editRef.current.textContent = text.slice(0, maxLength)\n // Move cursor to end\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n\n // Prevent paste from inserting rich content\n function handlePaste(e: React.ClipboardEvent) {\n e.preventDefault()\n const text = e.clipboardData.getData('text/plain')\n document.execCommand('insertText', false, text)\n }\n\n return (\n <div ref={ref} className={cn('group inline-flex items-center gap-ds-02', className)} {...props}>\n <span\n ref={editRef}\n role={readOnly ? undefined : 'textbox'}\n contentEditable={!readOnly && !isSaving}\n suppressContentEditableWarning\n tabIndex={readOnly ? undefined : 0}\n // Fall back to the placeholder when no explicit label is supplied —\n // screen readers need _something_ on role=\"textbox\".\n aria-label={readOnly ? undefined : (ariaLabel ?? (ariaLabelledBy ? undefined : placeholder))}\n aria-labelledby={readOnly ? undefined : ariaLabelledBy}\n onFocus={handleFocus}\n onBlur={commit}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n onPaste={handlePaste}\n spellCheck={focused}\n className={cn(\n 'font-sans text-surface-fg outline-hidden',\n textClassName,\n // Idle: subtle hover hint\n !readOnly && !focused && 'cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-raised-hover transition-colors duration-fast-01',\n // Focused: subtle underline to indicate editing\n focused && 'rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-raised-hover ring-1 ring-accent-7',\n // Empty: show placeholder styling\n isEmpty && !focused && 'text-surface-fg-subtle italic',\n // Read-only\n readOnly && 'cursor-default',\n )}\n data-placeholder={placeholder}\n >\n {value || (focused ? '' : placeholder)}\n </span>\n {!readOnly && !focused && !isSaving && (\n <Icon icon={IconPencil} size=\"xs\" className=\"text-surface-fg-subtle opacity-0 group-hover:opacity-100 transition-opacity duration-fast-01 shrink-0\" />\n )}\n {isSaving && <Spinner size=\"sm\" />}\n </div>\n )\n})\nInlineEdit.displayName = 'InlineEdit'\n\nexport { InlineEdit }\n"],"mappings":"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"}
1
+ {"version":3,"file":"inline-edit.js","names":[],"sources":["../../src/composed/inline-edit.tsx"],"sourcesContent":["'use client'\n\nimport { IconPencil } from '@tabler/icons-react'\nimport * as React from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { cn } from '../ui/lib/utils'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface InlineEditProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSave'> {\n value: string\n onSave: (newValue: string) => void | Promise<void>\n placeholder?: string\n /** CSS class applied to the editable text (e.g. 'text-heading-xs font-semibold') */\n textClassName?: string\n readOnly?: boolean\n maxLength?: number\n /** External saving state — shows spinner */\n saving?: boolean\n}\n\n// ============================================================\n// InlineEdit — contentEditable approach\n//\n// No mode switch. No input field. The text IS the editor.\n// Click → cursor appears in the text. Type. Enter saves. Escape reverts.\n// Like Notion, Linear, Figma layer names.\n// ============================================================\n\nconst InlineEdit = React.forwardRef<HTMLDivElement, InlineEditProps>(({\n value,\n onSave,\n placeholder = 'Click to edit',\n textClassName,\n readOnly = false,\n maxLength,\n saving: savingProp = false,\n className,\n // aria-label / aria-labelledby apply to the editable textbox (the inner\n // span), not the outer wrapper div. Intercept them from ...props so they\n // land on the role=\"textbox\" element that actually needs the name.\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n}, ref) => {\n const [saving, setSaving] = React.useState(false)\n const [focused, setFocused] = React.useState(false)\n const editRef = React.useRef<HTMLSpanElement>(null)\n const snapshotRef = React.useRef(value)\n\n const isSaving = savingProp || saving\n const isEmpty = !value\n\n // Sync DOM text when value changes externally (and not focused)\n React.useEffect(() => {\n if (!focused && editRef.current) {\n editRef.current.textContent = value\n }\n }, [value, focused])\n\n function handleFocus() {\n if (readOnly || isSaving) return\n setFocused(true)\n snapshotRef.current = value\n\n // Select all text on focus (like clicking a file name in Finder)\n requestAnimationFrame(() => {\n if (!editRef.current) return\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n })\n }\n\n async function commit() {\n if (!editRef.current) return\n setFocused(false)\n const raw = editRef.current.textContent ?? ''\n const trimmed = raw.trim()\n\n // Enforce maxLength\n const final = maxLength ? trimmed.slice(0, maxLength) : trimmed\n\n // Reset DOM to clean value\n editRef.current.textContent = final || value\n\n if (final === value) return\n const result = onSave(final)\n if (result instanceof Promise) {\n setSaving(true)\n try {\n await result\n } catch {\n // Revert on error\n if (editRef.current) editRef.current.textContent = value\n } finally {\n setSaving(false)\n }\n }\n }\n\n function cancel() {\n if (!editRef.current) return\n editRef.current.textContent = snapshotRef.current\n setFocused(false)\n editRef.current.blur()\n }\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.preventDefault()\n cancel()\n } else if (e.key === 'Enter') {\n e.preventDefault()\n commit()\n }\n }\n\n function handleInput() {\n if (!editRef.current || !maxLength) return\n const text = editRef.current.textContent ?? ''\n if (text.length > maxLength) {\n editRef.current.textContent = text.slice(0, maxLength)\n // Move cursor to end\n const range = document.createRange()\n range.selectNodeContents(editRef.current)\n range.collapse(false)\n const sel = window.getSelection()\n sel?.removeAllRanges()\n sel?.addRange(range)\n }\n }\n\n // Prevent paste from inserting rich content\n function handlePaste(e: React.ClipboardEvent) {\n e.preventDefault()\n const text = e.clipboardData.getData('text/plain')\n document.execCommand('insertText', false, text)\n }\n\n return (\n <div ref={ref} className={cn('group inline-flex items-center gap-ds-02', className)} {...props}>\n <span\n ref={editRef}\n role={readOnly ? undefined : 'textbox'}\n contentEditable={!readOnly && !isSaving}\n suppressContentEditableWarning\n tabIndex={readOnly ? undefined : 0}\n // Fall back to the placeholder when no explicit label is supplied —\n // screen readers need _something_ on role=\"textbox\".\n aria-label={readOnly ? undefined : (ariaLabel ?? (ariaLabelledBy ? undefined : placeholder))}\n aria-labelledby={readOnly ? undefined : ariaLabelledBy}\n onFocus={handleFocus}\n onBlur={commit}\n onKeyDown={handleKeyDown}\n onInput={handleInput}\n onPaste={handlePaste}\n spellCheck={focused}\n className={cn(\n 'font-sans text-surface-fg outline-hidden',\n textClassName,\n // Idle: subtle hover hint\n !readOnly && !focused && 'cursor-text rounded-control-inner -mx-ds-01 px-ds-01 hover:bg-surface-panel-hover transition-colors duration-fast-01',\n // Focused: subtle underline to indicate editing\n focused && 'rounded-control-inner -mx-ds-01 px-ds-01 bg-surface-panel-hover ring-1 ring-accent-7',\n // Empty: show placeholder styling\n isEmpty && !focused && 'text-surface-fg-subtle italic',\n // Read-only\n readOnly && 'cursor-default',\n )}\n data-placeholder={placeholder}\n >\n {value || (focused ? '' : placeholder)}\n </span>\n {!readOnly && !focused && !isSaving && (\n <Icon icon={IconPencil} size=\"xs\" className=\"text-surface-fg-subtle opacity-0 group-hover:opacity-100 transition-opacity duration-fast-01 shrink-0\" />\n )}\n {isSaving && <Spinner size=\"sm\" />}\n </div>\n )\n})\nInlineEdit.displayName = 'InlineEdit'\n\nexport { InlineEdit }\n"],"mappings":"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"}
@@ -22,7 +22,7 @@ var c = n.forwardRef(function({ className: n, label: a, ...c }, l) {
22
22
  ref: l,
23
23
  ...o(a),
24
24
  ...c,
25
- className: e("rounded-surface border border-card bg-surface-raised p-ds-05b", n),
25
+ className: e("rounded-surface border border-card bg-surface-panel p-ds-05b", n),
26
26
  children: [
27
27
  /* @__PURE__ */ r(s, { label: a }),
28
28
  /* @__PURE__ */ i("div", {
@@ -55,7 +55,7 @@ var l = n.forwardRef(function({ rows: n = 5, columns: c = 4, className: l, label
55
55
  children: [
56
56
  /* @__PURE__ */ r(s, { label: u }),
57
57
  /* @__PURE__ */ r("div", {
58
- className: "flex items-center gap-ds-05 border-b border-surface-border-strong bg-surface-raised px-ds-05 py-ds-04",
58
+ className: "flex items-center gap-ds-05 border-b border-surface-border-strong bg-surface-panel px-ds-05 py-ds-04",
59
59
  children: Array.from({ length: m }).map((n, i) => /* @__PURE__ */ r(t, { className: e("h-ds-04", i === 0 ? "w-ds-13" : "flex-1") }, `head-${i}`))
60
60
  }),
61
61
  Array.from({ length: p }).map((n, i) => /* @__PURE__ */ r("div", {
@@ -86,7 +86,7 @@ var u = n.forwardRef(function({ columns: n = 4, cardsPerColumn: c = 3, className
86
86
  ]
87
87
  }), /* @__PURE__ */ r(t, { className: "h-ico-sm w-ico-sm rounded" })]
88
88
  }), Array.from({ length: m }).map((e, a) => /* @__PURE__ */ i("div", {
89
- className: "rounded-surface border border-card bg-surface-raised p-ds-04",
89
+ className: "rounded-surface border border-card bg-surface-panel p-ds-04",
90
90
  children: [/* @__PURE__ */ i("div", {
91
91
  className: "space-y-ds-03",
92
92
  children: [/* @__PURE__ */ r(t, { className: "h-ds-04 w-4/5" }), /* @__PURE__ */ r(t, { className: "h-ds-04 w-3/5" })]
@@ -1 +1 @@
1
- {"version":3,"file":"loading-skeleton.js","names":[],"sources":["../../src/composed/loading-skeleton.tsx"],"sourcesContent":["// @server-safe\nimport * as React from 'react'\n\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\n\n// Non-negative integer count (guards Array.from against NaN / negatives → RangeError).\nconst clamp = (n: number | undefined, fallback: number) =>\n Math.max(0, Math.floor(typeof n === 'number' && !Number.isNaN(n) ? n : fallback))\n\n// role=status + aria-busy so AT announces the loading region (child Skeletons are\n// aria-hidden). sr-only label gives the announcement text.\nfunction statusProps(label?: string) {\n return { role: 'status' as const, 'aria-busy': true, 'aria-label': label ?? 'Loading' }\n}\nfunction SrLabel({ label }: { label?: string }) {\n return <span className=\"sr-only\">{label ?? 'Loading'}…</span>\n}\n\nexport interface CardSkeletonProps extends React.ComponentPropsWithoutRef<'div'> {\n className?: string\n /** Accessible label for the loading region. @default 'Loading' */\n label?: string\n}\n\nconst CardSkeleton = React.forwardRef<HTMLDivElement, CardSkeletonProps>(\n function CardSkeleton({ className, label, ...props }, ref) {\n return (\n <div\n ref={ref}\n {...statusProps(label)}\n {...props}\n className={cn('rounded-surface border border-card bg-surface-raised p-ds-05b', className)}\n >\n <SrLabel label={label} />\n <div className=\"flex items-center justify-between pb-ds-05\">\n <Skeleton className=\"h-ds-05 w-ds-13\" />\n <Skeleton className=\"h-ico-sm w-ico-sm rounded\" />\n </div>\n <div className=\"space-y-ds-04\">\n <Skeleton className=\"h-ds-04 w-full\" />\n <Skeleton className=\"h-ds-04 w-4/5\" />\n <Skeleton className=\"h-ds-04 w-3/5\" />\n </div>\n <div className=\"flex items-center gap-ds-03 pt-ds-05\">\n <Skeleton className=\"h-ds-xs w-ds-xs rounded-pill\" />\n <Skeleton className=\"h-ds-04 w-ds-11\" />\n </div>\n </div>\n )\n },\n)\nCardSkeleton.displayName = 'CardSkeleton'\n\nexport interface TableSkeletonProps extends CardSkeletonProps {\n rows?: number\n columns?: number\n}\n\nconst TableSkeleton = React.forwardRef<HTMLDivElement, TableSkeletonProps>(\n function TableSkeleton({ rows = 5, columns = 4, className, label, ...props }, ref) {\n const r = clamp(rows, 5)\n const c = Math.max(1, clamp(columns, 4))\n return (\n <div\n ref={ref}\n {...statusProps(label)}\n {...props}\n className={cn('overflow-hidden rounded-surface border border-card', className)}\n >\n <SrLabel label={label} />\n {/* Header */}\n <div className=\"flex items-center gap-ds-05 border-b border-surface-border-strong bg-surface-raised px-ds-05 py-ds-04\">\n {Array.from({ length: c }).map((_, i) => (\n <Skeleton key={`head-${i}`} className={cn('h-ds-04', i === 0 ? 'w-ds-13' : 'flex-1')} />\n ))}\n </div>\n {/* Rows */}\n {Array.from({ length: r }).map((_, rowIndex) => (\n <div\n key={`row-${rowIndex}`}\n className={cn(\n 'flex items-center gap-ds-05 px-ds-05 py-ds-04',\n rowIndex < r - 1 && 'border-b border-surface-border-strong',\n )}\n >\n {Array.from({ length: c }).map((_, colIndex) => (\n <Skeleton\n key={`cell-${rowIndex}-${colIndex}`}\n className={cn('h-ds-04', colIndex === 0 ? 'w-ds-13' : 'flex-1')}\n />\n ))}\n </div>\n ))}\n </div>\n )\n },\n)\nTableSkeleton.displayName = 'TableSkeleton'\n\nexport interface BoardSkeletonProps extends CardSkeletonProps {\n columns?: number\n cardsPerColumn?: number\n}\n\nconst BoardSkeleton = React.forwardRef<HTMLDivElement, BoardSkeletonProps>(\n function BoardSkeleton({ columns = 4, cardsPerColumn = 3, className, label, ...props }, ref) {\n const c = clamp(columns, 4)\n const cards = clamp(cardsPerColumn, 3)\n return (\n <div ref={ref} {...statusProps(label)} {...props} className={cn('flex gap-ds-05 overflow-x-auto', className)}>\n <SrLabel label={label} />\n {Array.from({ length: c }).map((_, colIndex) => (\n <div key={`col-${colIndex}`} className=\"flex w-[272px] shrink-0 flex-col gap-ds-03\">\n {/* Column header */}\n <div className=\"flex items-center justify-between px-ds-02 py-ds-03\">\n <div className=\"flex items-center gap-ds-03\">\n <Skeleton className=\"h-ds-04 w-ds-04 rounded\" />\n <Skeleton className=\"h-ds-04 w-ds-11\" />\n <Skeleton className=\"h-ds-05 w-ds-05b rounded-pill\" />\n </div>\n <Skeleton className=\"h-ico-sm w-ico-sm rounded\" />\n </div>\n {/* Column cards */}\n {Array.from({ length: cards }).map((_, cardIndex) => (\n <div\n key={`card-${colIndex}-${cardIndex}`}\n className=\"rounded-surface border border-card bg-surface-raised p-ds-04\"\n >\n <div className=\"space-y-ds-03\">\n <Skeleton className=\"h-ds-04 w-4/5\" />\n <Skeleton className=\"h-ds-04 w-3/5\" />\n </div>\n <div className=\"flex items-center justify-between pt-ds-04\">\n <div className=\"flex items-center gap-ds-02b\">\n <Skeleton className=\"h-ico-md w-ico-md rounded-pill\" />\n <Skeleton className=\"h-ds-03 w-ds-10\" />\n </div>\n <Skeleton className=\"h-ds-05 w-ds-lg rounded-pill\" />\n </div>\n </div>\n ))}\n </div>\n ))}\n </div>\n )\n },\n)\nBoardSkeleton.displayName = 'BoardSkeleton'\n\nexport interface ListSkeletonProps extends CardSkeletonProps {\n rows?: number\n showAvatar?: boolean\n}\n\nconst ListSkeleton = React.forwardRef<HTMLDivElement, ListSkeletonProps>(\n function ListSkeleton({ rows = 6, showAvatar = true, className, label, ...props }, ref) {\n const r = clamp(rows, 6)\n return (\n <div ref={ref} {...statusProps(label)} {...props} className={cn('flex flex-col', className)}>\n <SrLabel label={label} />\n {Array.from({ length: r }).map((_, i) => (\n <div\n key={`list-${i}`}\n className={cn(\n 'flex items-center gap-ds-04 py-ds-04',\n i < r - 1 && 'border-b border-surface-border-strong',\n )}\n >\n {showAvatar && <Skeleton className=\"h-ds-sm w-ds-sm shrink-0 rounded-pill\" />}\n <div className=\"flex flex-1 flex-col gap-ds-02b\">\n <Skeleton className=\"h-ds-04 w-2/5\" />\n <Skeleton className=\"h-ds-03 w-3/5\" />\n </div>\n <Skeleton className=\"h-ds-05b w-ds-09 rounded-pill\" />\n </div>\n ))}\n </div>\n )\n },\n)\nListSkeleton.displayName = 'ListSkeleton'\n\nexport { BoardSkeleton, CardSkeleton, ListSkeleton, TableSkeleton }\n"],"mappings":"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"}
1
+ {"version":3,"file":"loading-skeleton.js","names":[],"sources":["../../src/composed/loading-skeleton.tsx"],"sourcesContent":["// @server-safe\nimport * as React from 'react'\n\nimport { cn } from '../ui/lib/utils'\nimport { Skeleton } from '../ui/skeleton'\n\n// Non-negative integer count (guards Array.from against NaN / negatives → RangeError).\nconst clamp = (n: number | undefined, fallback: number) =>\n Math.max(0, Math.floor(typeof n === 'number' && !Number.isNaN(n) ? n : fallback))\n\n// role=status + aria-busy so AT announces the loading region (child Skeletons are\n// aria-hidden). sr-only label gives the announcement text.\nfunction statusProps(label?: string) {\n return { role: 'status' as const, 'aria-busy': true, 'aria-label': label ?? 'Loading' }\n}\nfunction SrLabel({ label }: { label?: string }) {\n return <span className=\"sr-only\">{label ?? 'Loading'}…</span>\n}\n\nexport interface CardSkeletonProps extends React.ComponentPropsWithoutRef<'div'> {\n className?: string\n /** Accessible label for the loading region. @default 'Loading' */\n label?: string\n}\n\nconst CardSkeleton = React.forwardRef<HTMLDivElement, CardSkeletonProps>(\n function CardSkeleton({ className, label, ...props }, ref) {\n return (\n <div\n ref={ref}\n {...statusProps(label)}\n {...props}\n className={cn('rounded-surface border border-card bg-surface-panel p-ds-05b', className)}\n >\n <SrLabel label={label} />\n <div className=\"flex items-center justify-between pb-ds-05\">\n <Skeleton className=\"h-ds-05 w-ds-13\" />\n <Skeleton className=\"h-ico-sm w-ico-sm rounded\" />\n </div>\n <div className=\"space-y-ds-04\">\n <Skeleton className=\"h-ds-04 w-full\" />\n <Skeleton className=\"h-ds-04 w-4/5\" />\n <Skeleton className=\"h-ds-04 w-3/5\" />\n </div>\n <div className=\"flex items-center gap-ds-03 pt-ds-05\">\n <Skeleton className=\"h-ds-xs w-ds-xs rounded-pill\" />\n <Skeleton className=\"h-ds-04 w-ds-11\" />\n </div>\n </div>\n )\n },\n)\nCardSkeleton.displayName = 'CardSkeleton'\n\nexport interface TableSkeletonProps extends CardSkeletonProps {\n rows?: number\n columns?: number\n}\n\nconst TableSkeleton = React.forwardRef<HTMLDivElement, TableSkeletonProps>(\n function TableSkeleton({ rows = 5, columns = 4, className, label, ...props }, ref) {\n const r = clamp(rows, 5)\n const c = Math.max(1, clamp(columns, 4))\n return (\n <div\n ref={ref}\n {...statusProps(label)}\n {...props}\n className={cn('overflow-hidden rounded-surface border border-card', className)}\n >\n <SrLabel label={label} />\n {/* Header */}\n <div className=\"flex items-center gap-ds-05 border-b border-surface-border-strong bg-surface-panel px-ds-05 py-ds-04\">\n {Array.from({ length: c }).map((_, i) => (\n <Skeleton key={`head-${i}`} className={cn('h-ds-04', i === 0 ? 'w-ds-13' : 'flex-1')} />\n ))}\n </div>\n {/* Rows */}\n {Array.from({ length: r }).map((_, rowIndex) => (\n <div\n key={`row-${rowIndex}`}\n className={cn(\n 'flex items-center gap-ds-05 px-ds-05 py-ds-04',\n rowIndex < r - 1 && 'border-b border-surface-border-strong',\n )}\n >\n {Array.from({ length: c }).map((_, colIndex) => (\n <Skeleton\n key={`cell-${rowIndex}-${colIndex}`}\n className={cn('h-ds-04', colIndex === 0 ? 'w-ds-13' : 'flex-1')}\n />\n ))}\n </div>\n ))}\n </div>\n )\n },\n)\nTableSkeleton.displayName = 'TableSkeleton'\n\nexport interface BoardSkeletonProps extends CardSkeletonProps {\n columns?: number\n cardsPerColumn?: number\n}\n\nconst BoardSkeleton = React.forwardRef<HTMLDivElement, BoardSkeletonProps>(\n function BoardSkeleton({ columns = 4, cardsPerColumn = 3, className, label, ...props }, ref) {\n const c = clamp(columns, 4)\n const cards = clamp(cardsPerColumn, 3)\n return (\n <div ref={ref} {...statusProps(label)} {...props} className={cn('flex gap-ds-05 overflow-x-auto', className)}>\n <SrLabel label={label} />\n {Array.from({ length: c }).map((_, colIndex) => (\n <div key={`col-${colIndex}`} className=\"flex w-[272px] shrink-0 flex-col gap-ds-03\">\n {/* Column header */}\n <div className=\"flex items-center justify-between px-ds-02 py-ds-03\">\n <div className=\"flex items-center gap-ds-03\">\n <Skeleton className=\"h-ds-04 w-ds-04 rounded\" />\n <Skeleton className=\"h-ds-04 w-ds-11\" />\n <Skeleton className=\"h-ds-05 w-ds-05b rounded-pill\" />\n </div>\n <Skeleton className=\"h-ico-sm w-ico-sm rounded\" />\n </div>\n {/* Column cards */}\n {Array.from({ length: cards }).map((_, cardIndex) => (\n <div\n key={`card-${colIndex}-${cardIndex}`}\n className=\"rounded-surface border border-card bg-surface-panel p-ds-04\"\n >\n <div className=\"space-y-ds-03\">\n <Skeleton className=\"h-ds-04 w-4/5\" />\n <Skeleton className=\"h-ds-04 w-3/5\" />\n </div>\n <div className=\"flex items-center justify-between pt-ds-04\">\n <div className=\"flex items-center gap-ds-02b\">\n <Skeleton className=\"h-ico-md w-ico-md rounded-pill\" />\n <Skeleton className=\"h-ds-03 w-ds-10\" />\n </div>\n <Skeleton className=\"h-ds-05 w-ds-lg rounded-pill\" />\n </div>\n </div>\n ))}\n </div>\n ))}\n </div>\n )\n },\n)\nBoardSkeleton.displayName = 'BoardSkeleton'\n\nexport interface ListSkeletonProps extends CardSkeletonProps {\n rows?: number\n showAvatar?: boolean\n}\n\nconst ListSkeleton = React.forwardRef<HTMLDivElement, ListSkeletonProps>(\n function ListSkeleton({ rows = 6, showAvatar = true, className, label, ...props }, ref) {\n const r = clamp(rows, 6)\n return (\n <div ref={ref} {...statusProps(label)} {...props} className={cn('flex flex-col', className)}>\n <SrLabel label={label} />\n {Array.from({ length: r }).map((_, i) => (\n <div\n key={`list-${i}`}\n className={cn(\n 'flex items-center gap-ds-04 py-ds-04',\n i < r - 1 && 'border-b border-surface-border-strong',\n )}\n >\n {showAvatar && <Skeleton className=\"h-ds-sm w-ds-sm shrink-0 rounded-pill\" />}\n <div className=\"flex flex-1 flex-col gap-ds-02b\">\n <Skeleton className=\"h-ds-04 w-2/5\" />\n <Skeleton className=\"h-ds-03 w-3/5\" />\n </div>\n <Skeleton className=\"h-ds-05b w-ds-09 rounded-pill\" />\n </div>\n ))}\n </div>\n )\n },\n)\nListSkeleton.displayName = 'ListSkeleton'\n\nexport { BoardSkeleton, CardSkeleton, ListSkeleton, TableSkeleton }\n"],"mappings":"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"}
@@ -134,7 +134,7 @@ var _ = i.forwardRef(function({ active: e, value: n, children: r, className: o,
134
134
  "data-active": m || void 0,
135
135
  onKeyDown: h,
136
136
  onClick: g,
137
- className: t("flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg", "transition-colors duration-fast-01 ease-productive-standard", "hover:bg-surface-raised-hover", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset", m && "bg-accent-2 text-accent-11 font-medium", o),
137
+ className: t("flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg", "transition-colors duration-fast-01 ease-productive-standard", "hover:bg-surface-panel-hover", "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset", m && "bg-accent-2 text-accent-11 font-medium", o),
138
138
  ...l,
139
139
  children: r
140
140
  });
@@ -1 +1 @@
1
- {"version":3,"file":"master-detail.js","names":[],"sources":["../../src/composed/master-detail.tsx"],"sourcesContent":["'use client'\n\nimport { IconArrowLeft } from '@tabler/icons-react'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n/**\n * A responsive master-detail layout. Desktop: fixed-width list panel + detail\n * panel. Below the breakpoint: stacked view with a back button. The list is an\n * ARIA `listbox` with roving keyboard nav (Arrow/Home/End + Enter/Space).\n *\n * Selection can be **controlled** (`selected`) or **owned** by the component\n * (`defaultSelected` + `onSelect`, deriving each row's active state from its\n * `value` — no hand-wiring `active` + `onClick` per row).\n */\nexport interface MasterDetailProps\n // Replaces the rarely-used native text-selection `onSelect` with our row-select callback.\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n /** Controlled selected item id. `null` = no selection. Omit for uncontrolled. */\n selected?: string | null\n /** Uncontrolled initial selection. */\n defaultSelected?: string | null\n /** Called with a row's `value` when it's chosen (click or Enter/Space). */\n onSelect?: (value: string) => void\n /** Accessible name for the list (the `listbox`). @default 'Items' */\n label?: string\n /** Called when mobile back button is pressed. */\n onBack?: () => void\n /** Master panel width on desktop. @default '280px' */\n masterWidth?: string\n /** Breakpoint below which stacked mode activates. @default 'md' */\n breakpoint?: 'sm' | 'md' | 'lg'\n /** Content to show in the detail pane when nothing is selected. */\n emptyState?: React.ReactNode\n /** Called when the user presses ArrowUp/ArrowDown while the list has focus. */\n onNavigate?: (direction: 'up' | 'down') => void\n}\n\ninterface MasterDetailListProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailDetailProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailListItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Explicit active state. Omit to derive from `value` === the selected id. */\n active?: boolean\n /** Row id — enables derived selection + auto-fires `onSelect` on activate. */\n value?: string\n}\n\nconst breakpoints: Record<string, string> = { sm: '640px', md: '768px', lg: '1024px' }\n\nfunction useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState(false)\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const mql = window.matchMedia(query)\n setMatches(mql.matches)\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n }, [query])\n return matches\n}\n\ninterface MasterDetailContextValue {\n selected: string | null\n onSelect?: (value: string) => void\n label: string\n isMobile: boolean\n onBack?: () => void\n emptyState?: React.ReactNode\n onNavigate?: (direction: 'up' | 'down') => void\n activeIndex: number\n setActiveIndex: (index: number) => void\n registerItemRef: (index: number, el: HTMLButtonElement | null) => void\n}\n\nconst MasterDetailContext = React.createContext<MasterDetailContextValue>({\n selected: null,\n label: 'Items',\n isMobile: false,\n activeIndex: 0,\n setActiveIndex: () => {},\n registerItemRef: () => {},\n})\n\nfunction itemIsActive(child: React.ReactNode, selected: string | null): boolean {\n if (!React.isValidElement(child)) return false\n const p = child.props as { active?: boolean; value?: string }\n return p.active ?? (p.value != null && p.value === selected)\n}\n\nfunction MasterDetailList({ children, className, 'aria-label': ariaLabel, ...props }: MasterDetailListProps) {\n const { selected, label, isMobile, onNavigate, activeIndex, setActiveIndex, registerItemRef } =\n React.useContext(MasterDetailContext)\n const itemRefsLocal = React.useRef<(HTMLButtonElement | null)[]>([])\n const childCount = React.Children.count(children)\n\n // Roving focus starts on the active/selected row (derived from `active` or `value`).\n const activeChildIndex = React.useMemo(() => {\n let idx = -1\n React.Children.forEach(children, (child, i) => {\n if (itemIsActive(child, selected)) idx = i\n })\n return idx\n }, [children, selected])\n React.useEffect(() => {\n if (activeChildIndex >= 0) setActiveIndex(activeChildIndex)\n }, [activeChildIndex, setActiveIndex])\n\n if (isMobile && selected) return null\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n const next = Math.min(activeIndex + 1, childCount - 1)\n setActiveIndex(next); itemRefsLocal.current[next]?.focus(); onNavigate?.('down')\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n const prev = Math.max(activeIndex - 1, 0)\n setActiveIndex(prev); itemRefsLocal.current[prev]?.focus(); onNavigate?.('up')\n } else if (e.key === 'Home') {\n e.preventDefault(); setActiveIndex(0); itemRefsLocal.current[0]?.focus()\n } else if (e.key === 'End') {\n e.preventDefault()\n const last = childCount - 1\n setActiveIndex(last); itemRefsLocal.current[last]?.focus()\n }\n }\n\n return (\n <div\n role=\"listbox\"\n aria-label={ariaLabel ?? label}\n tabIndex={-1}\n className={cn('overflow-y-auto focus-visible:outline-hidden', !isMobile && 'border-e border-surface-border', className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {React.Children.map(children, (child, i) => {\n if (!React.isValidElement(child)) return child\n return React.cloneElement(child as React.ReactElement<any>, {\n ref: (el: HTMLButtonElement | null) => {\n itemRefsLocal.current[i] = el\n registerItemRef(i, el)\n },\n tabIndex: i === activeIndex ? 0 : -1,\n })\n })}\n </div>\n )\n}\n\nfunction MasterDetailDetail({ children, className, ...props }: MasterDetailDetailProps) {\n const { selected, isMobile, onBack, emptyState } = React.useContext(MasterDetailContext)\n const reduced = useReducedMotion()\n if (isMobile && !selected) return null\n\n return (\n // aria-live so AT users are told the detail changed when the selection swaps.\n <div role=\"region\" aria-label=\"Detail\" aria-live=\"polite\" className={cn('flex-1 overflow-y-auto', className)} {...props}>\n {isMobile && onBack && (\n <div className=\"border-b border-surface-border px-ds-04 py-ds-03\">\n <Button variant=\"ghost\" size=\"xs\" onClick={onBack} startIcon={<Icon icon={IconArrowLeft} className=\"rtl:-scale-x-100\" />}>\n Back\n </Button>\n </div>\n )}\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={selected ?? 'empty'}\n initial={isMobile && !reduced ? { x: 20, opacity: 0 } : false}\n animate={{ x: 0, opacity: 1 }}\n exit={isMobile && !reduced ? { x: -20, opacity: 0 } : undefined}\n transition={reduced ? { duration: 0 } : springs.snappy}\n >\n {selected ? children : (emptyState ?? children)}\n </motion.div>\n </AnimatePresence>\n </div>\n )\n}\n\nconst MasterDetailListItem = React.forwardRef<HTMLButtonElement, MasterDetailListItemProps>(\n function MasterDetailListItem({ active, value, children, className, onKeyDown, onClick, ...props }, ref) {\n const { selected, onSelect } = React.useContext(MasterDetailContext)\n const isActive = active ?? (value != null && value === selected)\n\n function handleKeyDown(e: React.KeyboardEvent<HTMLButtonElement>) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n ;(e.currentTarget as HTMLButtonElement).click()\n }\n onKeyDown?.(e)\n }\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n if (value != null) onSelect?.(value)\n onClick?.(e)\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive || undefined}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n className={cn(\n 'flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-raised-hover',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset',\n isActive && 'bg-accent-2 text-accent-11 font-medium',\n className,\n )}\n {...props}\n >\n {children}\n </button>\n )\n },\n)\n\nfunction MasterDetailRoot({\n selected,\n defaultSelected = null,\n onSelect,\n label = 'Items',\n onBack,\n masterWidth = '280px',\n breakpoint = 'md',\n emptyState,\n onNavigate,\n children,\n className,\n style,\n ...props\n}: MasterDetailProps) {\n const isMobile = useMediaQuery(`(max-width: ${breakpoints[breakpoint]})`)\n const [activeIndex, setActiveIndex] = React.useState(0)\n const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([])\n\n // Controlled when `selected` is provided; otherwise own the selection.\n const isControlled = selected !== undefined\n const [internalSelected, setInternalSelected] = React.useState<string | null>(defaultSelected)\n const resolvedSelected = isControlled ? selected ?? null : internalSelected\n\n const handleSelect = React.useCallback(\n (value: string) => {\n if (!isControlled) setInternalSelected(value)\n onSelect?.(value)\n },\n [isControlled, onSelect],\n )\n\n const registerItemRef = React.useCallback((index: number, el: HTMLButtonElement | null) => {\n itemRefs.current[index] = el\n }, [])\n\n return (\n <MasterDetailContext.Provider\n value={{ selected: resolvedSelected, onSelect: handleSelect, label, isMobile, onBack, emptyState, onNavigate, activeIndex, setActiveIndex, registerItemRef }}\n >\n <div\n className={cn('flex h-full', !isMobile && 'grid', className)}\n style={{ ...style, ...(isMobile ? {} : { gridTemplateColumns: `${masterWidth} 1fr` }) }}\n {...props}\n >\n {children}\n </div>\n </MasterDetailContext.Provider>\n )\n}\n\nconst MasterDetail = Object.assign(MasterDetailRoot, {\n List: MasterDetailList,\n Detail: MasterDetailDetail,\n ListItem: MasterDetailListItem,\n})\n\nexport { MasterDetail }\nexport type { MasterDetailListItemProps }\n"],"mappings":"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"}
1
+ {"version":3,"file":"master-detail.js","names":[],"sources":["../../src/composed/master-detail.tsx"],"sourcesContent":["'use client'\n\nimport { IconArrowLeft } from '@tabler/icons-react'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from '../ui/button'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\n\n/**\n * A responsive master-detail layout. Desktop: fixed-width list panel + detail\n * panel. Below the breakpoint: stacked view with a back button. The list is an\n * ARIA `listbox` with roving keyboard nav (Arrow/Home/End + Enter/Space).\n *\n * Selection can be **controlled** (`selected`) or **owned** by the component\n * (`defaultSelected` + `onSelect`, deriving each row's active state from its\n * `value` — no hand-wiring `active` + `onClick` per row).\n */\nexport interface MasterDetailProps\n // Replaces the rarely-used native text-selection `onSelect` with our row-select callback.\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {\n /** Controlled selected item id. `null` = no selection. Omit for uncontrolled. */\n selected?: string | null\n /** Uncontrolled initial selection. */\n defaultSelected?: string | null\n /** Called with a row's `value` when it's chosen (click or Enter/Space). */\n onSelect?: (value: string) => void\n /** Accessible name for the list (the `listbox`). @default 'Items' */\n label?: string\n /** Called when mobile back button is pressed. */\n onBack?: () => void\n /** Master panel width on desktop. @default '280px' */\n masterWidth?: string\n /** Breakpoint below which stacked mode activates. @default 'md' */\n breakpoint?: 'sm' | 'md' | 'lg'\n /** Content to show in the detail pane when nothing is selected. */\n emptyState?: React.ReactNode\n /** Called when the user presses ArrowUp/ArrowDown while the list has focus. */\n onNavigate?: (direction: 'up' | 'down') => void\n}\n\ninterface MasterDetailListProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailDetailProps extends React.HTMLAttributes<HTMLDivElement> {}\ninterface MasterDetailListItemProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Explicit active state. Omit to derive from `value` === the selected id. */\n active?: boolean\n /** Row id — enables derived selection + auto-fires `onSelect` on activate. */\n value?: string\n}\n\nconst breakpoints: Record<string, string> = { sm: '640px', md: '768px', lg: '1024px' }\n\nfunction useMediaQuery(query: string): boolean {\n const [matches, setMatches] = React.useState(false)\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const mql = window.matchMedia(query)\n setMatches(mql.matches)\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n }, [query])\n return matches\n}\n\ninterface MasterDetailContextValue {\n selected: string | null\n onSelect?: (value: string) => void\n label: string\n isMobile: boolean\n onBack?: () => void\n emptyState?: React.ReactNode\n onNavigate?: (direction: 'up' | 'down') => void\n activeIndex: number\n setActiveIndex: (index: number) => void\n registerItemRef: (index: number, el: HTMLButtonElement | null) => void\n}\n\nconst MasterDetailContext = React.createContext<MasterDetailContextValue>({\n selected: null,\n label: 'Items',\n isMobile: false,\n activeIndex: 0,\n setActiveIndex: () => {},\n registerItemRef: () => {},\n})\n\nfunction itemIsActive(child: React.ReactNode, selected: string | null): boolean {\n if (!React.isValidElement(child)) return false\n const p = child.props as { active?: boolean; value?: string }\n return p.active ?? (p.value != null && p.value === selected)\n}\n\nfunction MasterDetailList({ children, className, 'aria-label': ariaLabel, ...props }: MasterDetailListProps) {\n const { selected, label, isMobile, onNavigate, activeIndex, setActiveIndex, registerItemRef } =\n React.useContext(MasterDetailContext)\n const itemRefsLocal = React.useRef<(HTMLButtonElement | null)[]>([])\n const childCount = React.Children.count(children)\n\n // Roving focus starts on the active/selected row (derived from `active` or `value`).\n const activeChildIndex = React.useMemo(() => {\n let idx = -1\n React.Children.forEach(children, (child, i) => {\n if (itemIsActive(child, selected)) idx = i\n })\n return idx\n }, [children, selected])\n React.useEffect(() => {\n if (activeChildIndex >= 0) setActiveIndex(activeChildIndex)\n }, [activeChildIndex, setActiveIndex])\n\n if (isMobile && selected) return null\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n const next = Math.min(activeIndex + 1, childCount - 1)\n setActiveIndex(next); itemRefsLocal.current[next]?.focus(); onNavigate?.('down')\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n const prev = Math.max(activeIndex - 1, 0)\n setActiveIndex(prev); itemRefsLocal.current[prev]?.focus(); onNavigate?.('up')\n } else if (e.key === 'Home') {\n e.preventDefault(); setActiveIndex(0); itemRefsLocal.current[0]?.focus()\n } else if (e.key === 'End') {\n e.preventDefault()\n const last = childCount - 1\n setActiveIndex(last); itemRefsLocal.current[last]?.focus()\n }\n }\n\n return (\n <div\n role=\"listbox\"\n aria-label={ariaLabel ?? label}\n tabIndex={-1}\n className={cn('overflow-y-auto focus-visible:outline-hidden', !isMobile && 'border-e border-surface-border', className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {React.Children.map(children, (child, i) => {\n if (!React.isValidElement(child)) return child\n return React.cloneElement(child as React.ReactElement<any>, {\n ref: (el: HTMLButtonElement | null) => {\n itemRefsLocal.current[i] = el\n registerItemRef(i, el)\n },\n tabIndex: i === activeIndex ? 0 : -1,\n })\n })}\n </div>\n )\n}\n\nfunction MasterDetailDetail({ children, className, ...props }: MasterDetailDetailProps) {\n const { selected, isMobile, onBack, emptyState } = React.useContext(MasterDetailContext)\n const reduced = useReducedMotion()\n if (isMobile && !selected) return null\n\n return (\n // aria-live so AT users are told the detail changed when the selection swaps.\n <div role=\"region\" aria-label=\"Detail\" aria-live=\"polite\" className={cn('flex-1 overflow-y-auto', className)} {...props}>\n {isMobile && onBack && (\n <div className=\"border-b border-surface-border px-ds-04 py-ds-03\">\n <Button variant=\"ghost\" size=\"xs\" onClick={onBack} startIcon={<Icon icon={IconArrowLeft} className=\"rtl:-scale-x-100\" />}>\n Back\n </Button>\n </div>\n )}\n <AnimatePresence mode=\"wait\">\n <motion.div\n key={selected ?? 'empty'}\n initial={isMobile && !reduced ? { x: 20, opacity: 0 } : false}\n animate={{ x: 0, opacity: 1 }}\n exit={isMobile && !reduced ? { x: -20, opacity: 0 } : undefined}\n transition={reduced ? { duration: 0 } : springs.snappy}\n >\n {selected ? children : (emptyState ?? children)}\n </motion.div>\n </AnimatePresence>\n </div>\n )\n}\n\nconst MasterDetailListItem = React.forwardRef<HTMLButtonElement, MasterDetailListItemProps>(\n function MasterDetailListItem({ active, value, children, className, onKeyDown, onClick, ...props }, ref) {\n const { selected, onSelect } = React.useContext(MasterDetailContext)\n const isActive = active ?? (value != null && value === selected)\n\n function handleKeyDown(e: React.KeyboardEvent<HTMLButtonElement>) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n ;(e.currentTarget as HTMLButtonElement).click()\n }\n onKeyDown?.(e)\n }\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n if (value != null) onSelect?.(value)\n onClick?.(e)\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive || undefined}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n className={cn(\n 'flex w-full items-center px-ds-04 py-ds-03 text-left text-body-md font-sans text-surface-fg',\n 'transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-panel-hover',\n 'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent-7 focus-visible:ring-inset',\n isActive && 'bg-accent-2 text-accent-11 font-medium',\n className,\n )}\n {...props}\n >\n {children}\n </button>\n )\n },\n)\n\nfunction MasterDetailRoot({\n selected,\n defaultSelected = null,\n onSelect,\n label = 'Items',\n onBack,\n masterWidth = '280px',\n breakpoint = 'md',\n emptyState,\n onNavigate,\n children,\n className,\n style,\n ...props\n}: MasterDetailProps) {\n const isMobile = useMediaQuery(`(max-width: ${breakpoints[breakpoint]})`)\n const [activeIndex, setActiveIndex] = React.useState(0)\n const itemRefs = React.useRef<(HTMLButtonElement | null)[]>([])\n\n // Controlled when `selected` is provided; otherwise own the selection.\n const isControlled = selected !== undefined\n const [internalSelected, setInternalSelected] = React.useState<string | null>(defaultSelected)\n const resolvedSelected = isControlled ? selected ?? null : internalSelected\n\n const handleSelect = React.useCallback(\n (value: string) => {\n if (!isControlled) setInternalSelected(value)\n onSelect?.(value)\n },\n [isControlled, onSelect],\n )\n\n const registerItemRef = React.useCallback((index: number, el: HTMLButtonElement | null) => {\n itemRefs.current[index] = el\n }, [])\n\n return (\n <MasterDetailContext.Provider\n value={{ selected: resolvedSelected, onSelect: handleSelect, label, isMobile, onBack, emptyState, onNavigate, activeIndex, setActiveIndex, registerItemRef }}\n >\n <div\n className={cn('flex h-full', !isMobile && 'grid', className)}\n style={{ ...style, ...(isMobile ? {} : { gridTemplateColumns: `${masterWidth} 1fr` }) }}\n {...props}\n >\n {children}\n </div>\n </MasterDetailContext.Provider>\n )\n}\n\nconst MasterDetail = Object.assign(MasterDetailRoot, {\n List: MasterDetailList,\n Detail: MasterDetailDetail,\n ListItem: MasterDetailListItem,\n})\n\nexport { MasterDetail }\nexport type { MasterDetailListItemProps }\n"],"mappings":"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"}
@@ -30,7 +30,7 @@ var c = a.forwardRef(({ members: c, selectedIds: l, onSelect: u, children: d, mu
30
30
  src: i.image,
31
31
  alt: i.label
32
32
  }), /* @__PURE__ */ o(t, {
33
- className: "bg-surface-raised-hover text-body-xs font-semibold text-surface-fg",
33
+ className: "bg-surface-panel-hover text-body-xs font-semibold text-surface-fg",
34
34
  children: r(i.label)
35
35
  })]
36
36
  }), /* @__PURE__ */ o("span", {
@@ -1 +1 @@
1
- {"version":3,"file":"member-picker.js","names":[],"sources":["../../src/composed/member-picker.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from '../ui/avatar'\nimport { getInitials } from './lib/string-utils'\nimport { type MultiSelectItem,MultiSelectPopover } from './multi-select-popover'\n\n// ============================================================\n// Types — backward-compatible with original MemberPicker\n// ============================================================\n\nexport interface MemberPickerMember {\n id: string\n name: string\n avatar?: string\n}\n\nexport interface MemberPickerProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onSelect'> {\n members: MemberPickerMember[]\n selectedIds: string[]\n onSelect: (memberId: string) => void\n multiple?: boolean\n placeholder?: string\n children: React.ReactNode\n}\n\n// ============================================================\n// MemberPicker — thin wrapper around MultiSelectPopover\n// ============================================================\n\nconst MemberPicker = React.forwardRef<HTMLDivElement, MemberPickerProps>(\n (\n {\n members,\n selectedIds,\n onSelect,\n children,\n multiple = false,\n placeholder = 'Search members...',\n ...props\n },\n ref,\n ) => {\n const items: MultiSelectItem[] = React.useMemo(\n () => members.map((m) => ({ id: m.id, label: m.name, image: m.avatar })),\n [members],\n )\n\n function handleChange(ids: string[]) {\n // Find the toggled id\n const added = ids.find((id) => !selectedIds.includes(id))\n const removed = selectedIds.find((id) => !ids.includes(id))\n const toggled = added ?? removed\n if (toggled) onSelect(toggled)\n }\n\n return (\n <MultiSelectPopover\n ref={ref}\n items={items}\n value={selectedIds}\n onValueChange={handleChange}\n searchPlaceholder={placeholder}\n maxSelections={multiple ? undefined : 1}\n renderItem={(item) => (\n <div className=\"flex items-center gap-ds-03\">\n <Avatar className=\"h-ico-md w-ico-md\">\n {item.image && <AvatarImage src={item.image} alt={item.label} />}\n <AvatarFallback className=\"bg-surface-raised-hover text-body-xs font-semibold text-surface-fg\">\n {getInitials(item.label)}\n </AvatarFallback>\n </Avatar>\n <span className=\"flex-1 truncate text-body-md font-body text-surface-fg\">\n {item.label}\n </span>\n </div>\n )}\n {...props}\n >\n {children}\n </MultiSelectPopover>\n )\n },\n)\n\nMemberPicker.displayName = 'MemberPicker'\n\nexport { MemberPicker }\n"],"mappings":";;;;;;;AAmCA,IAAM,IAAe,EAAM,YAEvB,EACE,YACA,gBACA,aACA,aACA,cAAW,IACX,iBAAc,qBACd,GAAG,KAEL,MACG;CACH,IAAM,IAA2B,EAAM,cAC/B,EAAQ,KAAK,OAAO;EAAE,IAAI,EAAE;EAAI,OAAO,EAAE;EAAM,OAAO,EAAE;CAAO,EAAE,GACvE,CAAC,CAAO,CACV;CAEA,SAAS,EAAa,GAAe;EAEnC,IAAM,IAAQ,EAAI,MAAM,MAAO,CAAC,EAAY,SAAS,CAAE,CAAC,GAClD,IAAU,EAAY,MAAM,MAAO,CAAC,EAAI,SAAS,CAAE,CAAC,GACpD,IAAU,KAAS;EACzB,AAAI,KAAS,EAAS,CAAO;CAC/B;CAEA,OACE,kBAAC,GAAD;EACO;EACE;EACP,OAAO;EACP,eAAe;EACf,mBAAmB;EACnB,eAAe,IAAW,KAAA,IAAY;EACtC,aAAa,MACX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAQ,WAAU;cAAlB,CACG,EAAK,SAAS,kBAAC,GAAD;KAAa,KAAK,EAAK;KAAO,KAAK,EAAK;IAAQ,CAAA,GAC/D,kBAAC,GAAD;KAAgB,WAAU;eACvB,EAAY,EAAK,KAAK;IACT,CAAA,CACV;OACR,kBAAC,QAAD;IAAM,WAAU;cACb,EAAK;GACF,CAAA,CACH;;EAEP,GAAI;EAEH;CACiB,CAAA;AAExB,CACF;AAEA,EAAa,cAAc"}
1
+ {"version":3,"file":"member-picker.js","names":[],"sources":["../../src/composed/member-picker.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from '../ui/avatar'\nimport { getInitials } from './lib/string-utils'\nimport { type MultiSelectItem,MultiSelectPopover } from './multi-select-popover'\n\n// ============================================================\n// Types — backward-compatible with original MemberPicker\n// ============================================================\n\nexport interface MemberPickerMember {\n id: string\n name: string\n avatar?: string\n}\n\nexport interface MemberPickerProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onSelect'> {\n members: MemberPickerMember[]\n selectedIds: string[]\n onSelect: (memberId: string) => void\n multiple?: boolean\n placeholder?: string\n children: React.ReactNode\n}\n\n// ============================================================\n// MemberPicker — thin wrapper around MultiSelectPopover\n// ============================================================\n\nconst MemberPicker = React.forwardRef<HTMLDivElement, MemberPickerProps>(\n (\n {\n members,\n selectedIds,\n onSelect,\n children,\n multiple = false,\n placeholder = 'Search members...',\n ...props\n },\n ref,\n ) => {\n const items: MultiSelectItem[] = React.useMemo(\n () => members.map((m) => ({ id: m.id, label: m.name, image: m.avatar })),\n [members],\n )\n\n function handleChange(ids: string[]) {\n // Find the toggled id\n const added = ids.find((id) => !selectedIds.includes(id))\n const removed = selectedIds.find((id) => !ids.includes(id))\n const toggled = added ?? removed\n if (toggled) onSelect(toggled)\n }\n\n return (\n <MultiSelectPopover\n ref={ref}\n items={items}\n value={selectedIds}\n onValueChange={handleChange}\n searchPlaceholder={placeholder}\n maxSelections={multiple ? undefined : 1}\n renderItem={(item) => (\n <div className=\"flex items-center gap-ds-03\">\n <Avatar className=\"h-ico-md w-ico-md\">\n {item.image && <AvatarImage src={item.image} alt={item.label} />}\n <AvatarFallback className=\"bg-surface-panel-hover text-body-xs font-semibold text-surface-fg\">\n {getInitials(item.label)}\n </AvatarFallback>\n </Avatar>\n <span className=\"flex-1 truncate text-body-md font-body text-surface-fg\">\n {item.label}\n </span>\n </div>\n )}\n {...props}\n >\n {children}\n </MultiSelectPopover>\n )\n },\n)\n\nMemberPicker.displayName = 'MemberPicker'\n\nexport { MemberPicker }\n"],"mappings":";;;;;;;AAmCA,IAAM,IAAe,EAAM,YAEvB,EACE,YACA,gBACA,aACA,aACA,cAAW,IACX,iBAAc,qBACd,GAAG,KAEL,MACG;CACH,IAAM,IAA2B,EAAM,cAC/B,EAAQ,KAAK,OAAO;EAAE,IAAI,EAAE;EAAI,OAAO,EAAE;EAAM,OAAO,EAAE;CAAO,EAAE,GACvE,CAAC,CAAO,CACV;CAEA,SAAS,EAAa,GAAe;EAEnC,IAAM,IAAQ,EAAI,MAAM,MAAO,CAAC,EAAY,SAAS,CAAE,CAAC,GAClD,IAAU,EAAY,MAAM,MAAO,CAAC,EAAI,SAAS,CAAE,CAAC,GACpD,IAAU,KAAS;EACzB,AAAI,KAAS,EAAS,CAAO;CAC/B;CAEA,OACE,kBAAC,GAAD;EACO;EACE;EACP,OAAO;EACP,eAAe;EACf,mBAAmB;EACnB,eAAe,IAAW,KAAA,IAAY;EACtC,aAAa,MACX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAQ,WAAU;cAAlB,CACG,EAAK,SAAS,kBAAC,GAAD;KAAa,KAAK,EAAK;KAAO,KAAK,EAAK;IAAQ,CAAA,GAC/D,kBAAC,GAAD;KAAgB,WAAU;eACvB,EAAY,EAAK,KAAK;IACT,CAAA,CACV;OACR,kBAAC,QAAD;IAAM,WAAU;cACb,EAAK;GACF,CAAA,CACH;;EAEP,GAAI;EAEH;CACiB,CAAA;AAExB,CACF;AAEA,EAAa,cAAc"}
@@ -100,7 +100,7 @@ var m = s.forwardRef(({ items: m, groups: h, value: g, onValueChange: _, searchP
100
100
  delay: a * .02
101
101
  },
102
102
  onClick: () => G(n.id),
103
- className: t("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard", "hover:bg-surface-raised-hover", "disabled:opacity-action-disabled disabled:cursor-not-allowed", i && "bg-accent-2 text-accent-11", o && "bg-surface-raised-hover"),
103
+ className: t("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard", "hover:bg-surface-panel-hover", "disabled:opacity-action-disabled disabled:cursor-not-allowed", i && "bg-accent-2 text-accent-11", o && "bg-surface-panel-hover"),
104
104
  children: [x ? /* @__PURE__ */ l("span", {
105
105
  className: "flex-1 min-w-0",
106
106
  children: x(n, i)