@devalok/shilp-sutra 0.56.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +198 -0
- package/dist/_chunks/badge-group.js +75 -174
- package/dist/_chunks/badge-group.js.map +1 -1
- package/dist/_chunks/chat.js +270 -253
- package/dist/_chunks/chat.js.map +1 -1
- package/dist/_chunks/document-preview.js +1 -1
- package/dist/_chunks/document-preview.js.map +1 -1
- package/dist/_chunks/emoji-suggestion.js +1 -1
- package/dist/_chunks/emoji-suggestion.js.map +1 -1
- package/dist/_chunks/image-preview.js +71 -70
- package/dist/_chunks/image-preview.js.map +1 -1
- package/dist/_chunks/mention-suggestion.js +2 -2
- package/dist/_chunks/mention-suggestion.js.map +1 -1
- package/dist/_chunks/motion-preference.js +15 -0
- package/dist/_chunks/motion-preference.js.map +1 -0
- package/dist/_chunks/motion-provider.js +1 -1
- package/dist/_chunks/motion-provider.js.map +1 -1
- package/dist/_chunks/oauth-button.js +1 -1
- package/dist/_chunks/oauth-button.js.map +1 -1
- package/dist/_chunks/popover.js +50 -49
- package/dist/_chunks/popover.js.map +1 -1
- package/dist/_chunks/primitives-internal.js +29 -29
- package/dist/_chunks/primitives-internal.js.map +1 -1
- package/dist/_chunks/shared.js +48 -47
- package/dist/_chunks/shared.js.map +1 -1
- package/dist/_chunks/success.js +36 -36
- package/dist/_chunks/success.js.map +1 -1
- package/dist/_chunks/tree-view.js +1 -1
- package/dist/_chunks/tree-view.js.map +1 -1
- package/dist/_chunks/use-calendar.js +4 -4
- package/dist/_chunks/use-calendar.js.map +1 -1
- package/dist/ai/block-renderer.js +8 -8
- package/dist/ai/blocks/text.js +1 -1
- package/dist/ai/blocks/text.js.map +1 -1
- package/dist/ai/command-bar.js +67 -67
- package/dist/ai/command-bar.js.map +1 -1
- package/dist/ai/conversation.js +35 -35
- package/dist/ai/conversation.js.map +1 -1
- package/dist/ai/index.js +4 -4
- package/dist/composed/activity-feed.d.ts.map +1 -1
- package/dist/composed/activity-feed.js +109 -108
- package/dist/composed/activity-feed.js.map +1 -1
- package/dist/composed/avatar-group.d.ts +7 -2
- package/dist/composed/avatar-group.d.ts.map +1 -1
- package/dist/composed/avatar-group.js +3 -3
- package/dist/composed/avatar-group.js.map +1 -1
- package/dist/composed/command-palette.js +137 -137
- package/dist/composed/command-palette.js.map +1 -1
- package/dist/composed/content-card.js +2 -2
- package/dist/composed/content-card.js.map +1 -1
- package/dist/composed/date-picker/index.js +9 -9
- package/dist/composed/date-picker/index.js.map +1 -1
- package/dist/composed/diff.d.ts +23 -2
- package/dist/composed/diff.d.ts.map +1 -1
- package/dist/composed/diff.js +341 -260
- package/dist/composed/diff.js.map +1 -1
- package/dist/composed/emoji-picker.js +3 -3
- package/dist/composed/emoji-picker.js.map +1 -1
- package/dist/composed/empty-state.js +4 -4
- package/dist/composed/error-boundary.js +2 -2
- package/dist/composed/error-boundary.js.map +1 -1
- package/dist/composed/file-preview/image-preview.d.ts.map +1 -1
- package/dist/composed/file-preview/shared.d.ts.map +1 -1
- package/dist/composed/file-preview/video-preview.d.ts.map +1 -1
- package/dist/composed/file-preview.js +152 -151
- package/dist/composed/file-preview.js.map +1 -1
- package/dist/composed/filter-bar.d.ts.map +1 -1
- package/dist/composed/filter-bar.js +53 -52
- package/dist/composed/filter-bar.js.map +1 -1
- package/dist/composed/form-section.d.ts.map +1 -1
- package/dist/composed/form-section.js +38 -37
- package/dist/composed/form-section.js.map +1 -1
- package/dist/composed/inline-edit.js +1 -1
- package/dist/composed/inline-edit.js.map +1 -1
- package/dist/composed/loading-skeleton.js +3 -3
- package/dist/composed/loading-skeleton.js.map +1 -1
- package/dist/composed/master-detail.d.ts.map +1 -1
- package/dist/composed/master-detail.js +1 -1
- package/dist/composed/master-detail.js.map +1 -1
- package/dist/composed/member-picker.js +1 -1
- package/dist/composed/member-picker.js.map +1 -1
- package/dist/composed/multi-select-popover.d.ts.map +1 -1
- package/dist/composed/multi-select-popover.js +109 -108
- package/dist/composed/multi-select-popover.js.map +1 -1
- package/dist/composed/page-skeletons.js +4 -4
- package/dist/composed/page-skeletons.js.map +1 -1
- package/dist/composed/responsive-modal.js +1 -1
- package/dist/composed/responsive-modal.js.map +1 -1
- package/dist/composed/rich-chat-input/attachment-strip.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/reply-banner.d.ts.map +1 -1
- package/dist/composed/rich-chat-input/schedule-send.d.ts.map +1 -1
- package/dist/composed/rich-chat-input.d.ts.map +1 -1
- package/dist/composed/rich-chat-input.js +473 -472
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.js +6 -6
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/dist/composed/schedule-view.js +5 -5
- package/dist/composed/schedule-view.js.map +1 -1
- package/dist/composed/status-badge.d.ts.map +1 -1
- package/dist/composed/status-badge.js +29 -28
- package/dist/composed/status-badge.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -3
- package/dist/hooks/use-container-size.d.ts +18 -0
- package/dist/hooks/use-container-size.d.ts.map +1 -0
- package/dist/hooks/use-container-size.js +28 -0
- package/dist/hooks/use-container-size.js.map +1 -0
- package/dist/motion/index.d.ts +1 -0
- package/dist/motion/index.d.ts.map +1 -1
- package/dist/motion/index.js +2 -15
- package/dist/motion/motion-preference.d.ts +36 -0
- package/dist/motion/motion-preference.d.ts.map +1 -0
- package/dist/motion/motion-provider.d.ts +2 -1
- package/dist/motion/motion-provider.d.ts.map +1 -1
- package/dist/motion/primitives-index.js +103 -102
- package/dist/motion/primitives-index.js.map +1 -1
- package/dist/motion/primitives.d.ts.map +1 -1
- package/dist/shell/app-shell.d.ts +101 -0
- package/dist/shell/app-shell.d.ts.map +1 -0
- package/dist/shell/app-shell.js +76 -0
- package/dist/shell/app-shell.js.map +1 -0
- package/dist/shell/bottom-navbar.js +21 -21
- package/dist/shell/bottom-navbar.js.map +1 -1
- package/dist/shell/index.d.ts +2 -1
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +6 -5
- package/dist/shell/notification-center.d.ts +17 -0
- package/dist/shell/notification-center.d.ts.map +1 -1
- package/dist/shell/notification-center.js +110 -105
- package/dist/shell/notification-center.js.map +1 -1
- package/dist/shell/notification-preferences.js +1 -1
- package/dist/shell/notification-preferences.js.map +1 -1
- package/dist/shell/top-bar.js +7 -7
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/tokens/generate-scale.d.ts +49 -0
- package/dist/tokens/generate-scale.d.ts.map +1 -1
- package/dist/tokens/index.css +1 -0
- package/dist/tokens/palettes.css +292 -0
- package/dist/tokens/primitives.css +29 -26
- package/dist/tokens/semantic.css +170 -47
- package/dist/tokens/shilp-sutra.css +1 -0
- package/dist/tokens/utilities.css +10 -7
- package/dist/tokens/variants.css +1 -1
- package/dist/ui/accordion.d.ts.map +1 -1
- package/dist/ui/accordion.js +27 -26
- package/dist/ui/accordion.js.map +1 -1
- package/dist/ui/alert-dialog.d.ts.map +1 -1
- package/dist/ui/alert-dialog.js +60 -59
- package/dist/ui/alert-dialog.js.map +1 -1
- package/dist/ui/alert.d.ts.map +1 -1
- package/dist/ui/alert.js +55 -113
- package/dist/ui/alert.js.map +1 -1
- package/dist/ui/autocomplete.js +80 -80
- package/dist/ui/autocomplete.js.map +1 -1
- package/dist/ui/avatar.d.ts.map +1 -1
- package/dist/ui/avatar.js +71 -70
- package/dist/ui/avatar.js.map +1 -1
- package/dist/ui/badge-indicator.d.ts +4 -8
- package/dist/ui/badge-indicator.d.ts.map +1 -1
- package/dist/ui/badge-indicator.js +3 -8
- package/dist/ui/badge-indicator.js.map +1 -1
- package/dist/ui/badge.d.ts +55 -34
- package/dist/ui/badge.d.ts.map +1 -1
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +43 -41
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/button-group.d.ts.map +1 -1
- package/dist/ui/button-group.js +3 -23
- package/dist/ui/button-group.js.map +1 -1
- package/dist/ui/button.d.ts +10 -1
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +79 -212
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.d.ts.map +1 -1
- package/dist/ui/card.js +78 -75
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/charts/radar-chart.d.ts.map +1 -1
- package/dist/ui/charts/radar-chart.js +1 -1
- package/dist/ui/charts/radar-chart.js.map +1 -1
- package/dist/ui/chat/date-separator.d.ts +8 -1
- package/dist/ui/chat/date-separator.d.ts.map +1 -1
- package/dist/ui/chat/message-list.d.ts.map +1 -1
- package/dist/ui/chat/message.d.ts.map +1 -1
- package/dist/ui/chat/typing-indicator.d.ts.map +1 -1
- package/dist/ui/checkbox.js +1 -1
- package/dist/ui/checkbox.js.map +1 -1
- package/dist/ui/code.js +2 -2
- package/dist/ui/code.js.map +1 -1
- package/dist/ui/color-input.d.ts.map +1 -1
- package/dist/ui/color-input.js +151 -150
- package/dist/ui/color-input.js.map +1 -1
- package/dist/ui/combobox.d.ts +14 -0
- package/dist/ui/combobox.d.ts.map +1 -1
- package/dist/ui/combobox.js +156 -152
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/context-menu.d.ts.map +1 -1
- package/dist/ui/context-menu.js +70 -69
- package/dist/ui/context-menu.js.map +1 -1
- package/dist/ui/data-table-body.d.ts +3 -1
- package/dist/ui/data-table-body.d.ts.map +1 -1
- package/dist/ui/data-table-body.js +93 -92
- package/dist/ui/data-table-body.js.map +1 -1
- package/dist/ui/data-table-bulk-actions.d.ts +7 -1
- package/dist/ui/data-table-bulk-actions.d.ts.map +1 -1
- package/dist/ui/data-table-bulk-actions.js +14 -8
- package/dist/ui/data-table-bulk-actions.js.map +1 -1
- package/dist/ui/data-table-card.d.ts.map +1 -1
- package/dist/ui/data-table-card.js +66 -47
- package/dist/ui/data-table-card.js.map +1 -1
- package/dist/ui/data-table-context.d.ts +4 -0
- package/dist/ui/data-table-context.d.ts.map +1 -1
- package/dist/ui/data-table-context.js +2 -2
- package/dist/ui/data-table-context.js.map +1 -1
- package/dist/ui/data-table-header.d.ts.map +1 -1
- package/dist/ui/data-table-header.js +50 -46
- package/dist/ui/data-table-header.js.map +1 -1
- package/dist/ui/data-table-pagination.js +3 -3
- package/dist/ui/data-table-pagination.js.map +1 -1
- package/dist/ui/data-table-toolbar.d.ts +11 -1
- package/dist/ui/data-table-toolbar.d.ts.map +1 -1
- package/dist/ui/data-table-toolbar.js +7 -7
- package/dist/ui/data-table-toolbar.js.map +1 -1
- package/dist/ui/data-table.d.ts +52 -5
- package/dist/ui/data-table.d.ts.map +1 -1
- package/dist/ui/data-table.js +181 -169
- package/dist/ui/data-table.js.map +1 -1
- package/dist/ui/dialog.d.ts.map +1 -1
- package/dist/ui/dialog.js +62 -61
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dot.d.ts.map +1 -1
- package/dist/ui/dot.js +10 -41
- package/dist/ui/dot.js.map +1 -1
- package/dist/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/ui/dropdown-menu.js +79 -78
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload.js +117 -116
- package/dist/ui/file-upload.js.map +1 -1
- package/dist/ui/form.d.ts.map +1 -1
- package/dist/ui/form.js +27 -26
- package/dist/ui/form.js.map +1 -1
- package/dist/ui/hover-card.d.ts.map +1 -1
- package/dist/ui/hover-card.js +26 -25
- package/dist/ui/hover-card.js.map +1 -1
- package/dist/ui/icon.js +7 -7
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +3 -3
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input-otp.js +1 -1
- package/dist/ui/input-otp.js.map +1 -1
- package/dist/ui/input.js +8 -8
- package/dist/ui/input.js.map +1 -1
- package/dist/ui/menubar.d.ts.map +1 -1
- package/dist/ui/menubar.js +73 -72
- package/dist/ui/menubar.js.map +1 -1
- package/dist/ui/navigation-menu.d.ts.map +1 -1
- package/dist/ui/navigation-menu.js +80 -79
- package/dist/ui/navigation-menu.js.map +1 -1
- package/dist/ui/number-input.js +3 -3
- package/dist/ui/number-input.js.map +1 -1
- package/dist/ui/pagination.js +1 -1
- package/dist/ui/pagination.js.map +1 -1
- package/dist/ui/popover.d.ts.map +1 -1
- package/dist/ui/progress-ring.js +2 -2
- package/dist/ui/progress-ring.js.map +1 -1
- package/dist/ui/progress.d.ts.map +1 -1
- package/dist/ui/progress.js +82 -74
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/radio.d.ts.map +1 -1
- package/dist/ui/radio.js +32 -31
- package/dist/ui/radio.js.map +1 -1
- package/dist/ui/search-input.d.ts.map +1 -1
- package/dist/ui/search-input.js +28 -27
- package/dist/ui/search-input.js.map +1 -1
- package/dist/ui/select.d.ts.map +1 -1
- package/dist/ui/select.js +67 -66
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sheet.js +1 -1
- package/dist/ui/sheet.js.map +1 -1
- package/dist/ui/sidebar.d.ts.map +1 -1
- package/dist/ui/sidebar.js +14 -14
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/slider.d.ts +63 -9
- package/dist/ui/slider.d.ts.map +1 -1
- package/dist/ui/slider.js +75 -33
- package/dist/ui/slider.js.map +1 -1
- package/dist/ui/split-button.d.ts +8 -2
- package/dist/ui/split-button.d.ts.map +1 -1
- package/dist/ui/split-button.js +49 -95
- package/dist/ui/split-button.js.map +1 -1
- package/dist/ui/stat-card.d.ts.map +1 -1
- package/dist/ui/stat-card.js +89 -88
- package/dist/ui/stat-card.js.map +1 -1
- package/dist/ui/stat-flash.js +16 -16
- package/dist/ui/stepper.d.ts.map +1 -1
- package/dist/ui/stepper.js +63 -62
- package/dist/ui/stepper.js.map +1 -1
- package/dist/ui/surface.d.ts.map +1 -1
- package/dist/ui/surface.js +2 -2
- package/dist/ui/surface.js.map +1 -1
- package/dist/ui/switch.js +1 -1
- package/dist/ui/switch.js.map +1 -1
- package/dist/ui/table.js +3 -3
- package/dist/ui/table.js.map +1 -1
- package/dist/ui/tabs.d.ts.map +1 -1
- package/dist/ui/tabs.js +68 -67
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/textarea.js +5 -5
- package/dist/ui/textarea.js.map +1 -1
- package/dist/ui/toast.d.ts.map +1 -1
- package/dist/ui/toast.js +196 -195
- package/dist/ui/toast.js.map +1 -1
- package/dist/ui/toggle.d.ts.map +1 -1
- package/dist/ui/toggle.js +26 -24
- package/dist/ui/toggle.js.map +1 -1
- package/dist/ui/tooltip.d.ts.map +1 -1
- package/dist/ui/tooltip.js +37 -36
- package/dist/ui/tooltip.js.map +1 -1
- package/dist/ui/tree-view/tree-item.d.ts.map +1 -1
- package/docs/components/composed/avatar-group.md +4 -4
- package/docs/components/composed/diff.md +10 -2
- package/docs/components/composed/schedule-view.md +1 -1
- package/docs/components/shell/app-shell.md +132 -0
- package/docs/components/ui/avatar.md +1 -1
- package/docs/components/ui/button.md +1 -1
- package/docs/components/ui/chat.md +7 -1
- package/docs/components/ui/color-swatch.md +1 -1
- package/docs/components/ui/data-table-toolbar.md +6 -2
- package/docs/components/ui/data-table.md +41 -7
- package/docs/components/ui/dot.md +1 -1
- package/docs/components/ui/progress.md +1 -0
- package/docs/components/ui/surface.md +3 -3
- package/docs/components/ui/table.md +2 -2
- package/docs/recipes/customize-brand.md +58 -0
- package/docs/recipes/install-astro.md +1 -0
- package/docs/recipes/install-next-app-router.md +1 -0
- package/docs/recipes/install-next-pages.md +22 -1
- package/docs/recipes/install-remix.md +1 -0
- package/docs/recipes/install-tanstack-start.md +1 -0
- package/docs/recipes/install-vite.md +2 -0
- package/docs/recipes/server-components.md +7 -4
- package/docs/recipes/troubleshoot.md +1 -0
- package/llms.txt +2 -1
- package/make-kit/Guidelines.md +2 -2
- package/make-kit/components/button.md +1 -1
- package/make-kit/components/card.md +6 -6
- package/make-kit/components/surface.md +4 -4
- package/make-kit/foundations/color.md +4 -4
- package/make-kit/foundations/dark-mode.md +1 -1
- package/make-kit/foundations/radius.md +1 -1
- package/make-kit/foundations/surfaces.md +9 -9
- package/mcp-manifest.json +358 -35
- package/package.json +8 -4
- package/skill/SKILL.md +12 -6
- package/skill/references/components.md +2 -1
- package/skill/references/customize-brand.md +58 -0
- package/skill/references/server-components.md +7 -4
- package/skill/references/setup-astro.md +1 -0
- package/skill/references/setup-next-app-router.md +1 -0
- package/skill/references/setup-next-pages.md +22 -1
- package/skill/references/setup-remix.md +1 -0
- package/skill/references/setup-tanstack-start.md +1 -0
- package/skill/references/setup-vite.md +2 -0
- package/skill/references/troubleshoot.md +1 -0
- package/dist/motion/check-motion-provider.d.ts +0 -2
- package/dist/motion/check-motion-provider.d.ts.map +0 -1
- package/dist/motion/index.js.map +0 -1
- package/dist/primitives/react-toast.d.ts +0 -120
- package/dist/ui/charts/_internal/scales.d.ts +0 -5
- package/dist/ui/charts/_internal/scales.d.ts.map +0 -1
- package/dist/ui/lib/slot.d.ts +0 -8
- package/dist/ui/lib/slot.d.ts.map +0 -1
|
@@ -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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"master-detail.d.ts","sourceRoot":"","sources":["../../src/composed/master-detail.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAEf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC9D,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAA;CAChD;AAED,UAAU,qBAAsB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AAC/E,UAAU,uBAAwB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AACjF,UAAU,yBAA0B,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACvF,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA6CD,iBAAS,gBAAgB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,qBAAqB,4BA2D1G;AAED,iBAAS,kBAAkB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,4BA4BrF;
|
|
1
|
+
{"version":3,"file":"master-detail.d.ts","sourceRoot":"","sources":["../../src/composed/master-detail.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAEf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC9D,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IAC/B,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAA;CAChD;AAED,UAAU,qBAAsB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AAC/E,UAAU,uBAAwB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AACjF,UAAU,yBAA0B,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACvF,8EAA8E;IAC9E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA6CD,iBAAS,gBAAgB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,qBAAqB,4BA2D1G;AAED,iBAAS,kBAAkB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,uBAAuB,4BA4BrF;AAgDD,iBAAS,gBAAgB,CAAC,EACxB,QAAQ,EACR,eAAsB,EACtB,QAAQ,EACR,KAAe,EACf,MAAM,EACN,WAAqB,EACrB,UAAiB,EACjB,UAAU,EACV,UAAU,EACV,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,iBAAiB,qBAmCnB;AAED,QAAA,MAAM,YAAY;;;;CAIhB,CAAA;AAEF,OAAO,EAAE,YAAY,EAAE,CAAA;AACvB,YAAY,EAAE,yBAAyB,EAAE,CAAA"}
|
|
@@ -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-
|
|
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-4 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 // accent-4, not accent-2: in dark the pale steps sit BELOW the panel\n // while the grey hover sits above it, so a merely hovered row read\n // stronger than the selected one (1.03:1 vs 1.30:1) and in the\n // opposite direction.\n isActive && 'bg-accent-4 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-
|
|
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-
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi-select-popover.d.ts","sourceRoot":"","sources":["../../src/composed/multi-select-popover.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"multi-select-popover.d.ts","sourceRoot":"","sources":["../../src/composed/multi-select-popover.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAiB9B,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACtG,8DAA8D;IAC9D,KAAK,CAAC,EAAE,eAAe,EAAE,CAAA;IACzB,yCAAyC;IACzC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC3B,kCAAkC;IAClC,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,oCAAoC;IACpC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACtC,2BAA2B;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IACxD,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAA;IAC1E,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,yCAAyC;IACzC,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;IAClC,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,sBAAsB;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAMD,QAAA,MAAM,kBAAkB,gGAmPvB,CAAA;AAGD,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -1,92 +1,93 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { springs as e } from "../ui/lib/motion.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
3
|
+
import { MotionPreference as t } from "../_chunks/motion-preference.js";
|
|
4
|
+
import { cn as n } from "../ui/lib/utils.js";
|
|
5
|
+
import { Spinner as r } from "../ui/spinner.js";
|
|
6
|
+
import { Icon as i } from "../ui/icon.js";
|
|
7
|
+
import { Popover as a, PopoverContent as o, PopoverTrigger as s } from "../_chunks/popover.js";
|
|
8
|
+
import * as c from "react";
|
|
9
|
+
import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
10
|
+
import { IconCheck as f, IconSearch as p } from "@tabler/icons-react";
|
|
11
|
+
import { motion as m } from "framer-motion";
|
|
11
12
|
//#region src/composed/multi-select-popover.tsx
|
|
12
|
-
var
|
|
13
|
-
let [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}, [
|
|
17
|
-
if (!(!
|
|
18
|
-
if (!
|
|
19
|
-
|
|
13
|
+
var h = c.forwardRef(({ items: h, groups: g, value: _, onValueChange: v, searchPlaceholder: y = "Search...", onSearch: b, searchDebounce: x = 300, renderItem: S, emptyMessage: C = "No results found", maxSelections: w, align: T = "start", width: E, children: D, className: O, ...k }, A) => {
|
|
14
|
+
let [j, M] = c.useState(!1), [N, P] = c.useState(""), [F, I] = c.useState(null), [L, R] = c.useState(!1), [z, B] = c.useState(-1), V = c.useRef(void 0), H = c.useRef(null);
|
|
15
|
+
c.useEffect(() => {
|
|
16
|
+
j || (P(""), I(null), B(-1));
|
|
17
|
+
}, [j]), c.useEffect(() => {
|
|
18
|
+
if (!(!b || !j)) {
|
|
19
|
+
if (!N.trim()) {
|
|
20
|
+
I(null), R(!1);
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
},
|
|
23
|
+
return R(!0), clearTimeout(V.current), V.current = setTimeout(() => {
|
|
24
|
+
b(N).then((e) => I(e)).catch(() => I([])).finally(() => R(!1));
|
|
25
|
+
}, x), () => clearTimeout(V.current);
|
|
25
26
|
}
|
|
26
27
|
}, [
|
|
27
|
-
|
|
28
|
-
y,
|
|
28
|
+
N,
|
|
29
29
|
b,
|
|
30
|
-
|
|
30
|
+
x,
|
|
31
|
+
j
|
|
31
32
|
]);
|
|
32
|
-
let
|
|
33
|
-
m,
|
|
33
|
+
let U = c.useMemo(() => F || (g ? g.flatMap((e) => e.items) : h ?? []), [
|
|
34
34
|
h,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
g,
|
|
36
|
+
F
|
|
37
|
+
]), W = c.useMemo(() => {
|
|
38
|
+
if (b) return U;
|
|
39
|
+
let e = N.toLowerCase();
|
|
40
|
+
return e ? U.filter((t) => t.label.toLowerCase().includes(e)) : U;
|
|
40
41
|
}, [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
]),
|
|
45
|
-
if (!
|
|
46
|
-
let e =
|
|
47
|
-
return
|
|
42
|
+
U,
|
|
43
|
+
N,
|
|
44
|
+
b
|
|
45
|
+
]), G = c.useMemo(() => {
|
|
46
|
+
if (!g || b || F) return null;
|
|
47
|
+
let e = N.toLowerCase();
|
|
48
|
+
return g.map((t) => ({
|
|
48
49
|
...t,
|
|
49
50
|
items: e ? t.items.filter((t) => t.label.toLowerCase().includes(e)) : t.items
|
|
50
51
|
})).filter((e) => e.items.length > 0);
|
|
51
52
|
}, [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
g,
|
|
54
|
+
N,
|
|
55
|
+
b,
|
|
56
|
+
F
|
|
56
57
|
]);
|
|
57
|
-
function G(e) {
|
|
58
|
-
g.includes(e) ? _(g.filter((t) => t !== e)) : C && g.length >= C ? _([...g.slice(1), e]) : _([...g, e]);
|
|
59
|
-
}
|
|
60
|
-
s.useEffect(() => {
|
|
61
|
-
z(-1);
|
|
62
|
-
}, [M]), s.useEffect(() => {
|
|
63
|
-
if (R < 0) return;
|
|
64
|
-
let e = V.current;
|
|
65
|
-
e && e.querySelectorAll("[data-multiselect-item]")[R]?.scrollIntoView({ block: "nearest" });
|
|
66
|
-
}, [R]);
|
|
67
58
|
function K(e) {
|
|
68
|
-
|
|
59
|
+
_.includes(e) ? v(_.filter((t) => t !== e)) : w && _.length >= w ? v([..._.slice(1), e]) : v([..._, e]);
|
|
60
|
+
}
|
|
61
|
+
c.useEffect(() => {
|
|
62
|
+
B(-1);
|
|
63
|
+
}, [N]), c.useEffect(() => {
|
|
64
|
+
if (z < 0) return;
|
|
65
|
+
let e = H.current;
|
|
66
|
+
e && e.querySelectorAll("[data-multiselect-item]")[z]?.scrollIntoView({ block: "nearest" });
|
|
67
|
+
}, [z]);
|
|
68
|
+
function q(e) {
|
|
69
|
+
let t = W.length;
|
|
69
70
|
if (t !== 0) {
|
|
70
|
-
if (e.key === "ArrowDown") e.preventDefault(),
|
|
71
|
-
else if (e.key === "ArrowUp") e.preventDefault(),
|
|
72
|
-
else if (e.key === "Enter" &&
|
|
71
|
+
if (e.key === "ArrowDown") e.preventDefault(), B((e) => e < t - 1 ? e + 1 : 0);
|
|
72
|
+
else if (e.key === "ArrowUp") e.preventDefault(), B((e) => e <= 0 ? t - 1 : e - 1);
|
|
73
|
+
else if (e.key === "Enter" && z >= 0) {
|
|
73
74
|
e.preventDefault();
|
|
74
|
-
let t =
|
|
75
|
-
t && !t.disabled &&
|
|
75
|
+
let t = W[z];
|
|
76
|
+
t && !t.disabled && K(t.id);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
let
|
|
80
|
-
function
|
|
81
|
-
let
|
|
82
|
-
return /* @__PURE__ */ u(
|
|
83
|
-
id: `msp-item-${
|
|
80
|
+
let J = 0;
|
|
81
|
+
function Y(r) {
|
|
82
|
+
let a = _.includes(r.id), o = J++, s = o === z;
|
|
83
|
+
return /* @__PURE__ */ u(t, { children: /* @__PURE__ */ d(m.button, {
|
|
84
|
+
id: `msp-item-${o}`,
|
|
84
85
|
type: "button",
|
|
85
86
|
role: "option",
|
|
86
|
-
"aria-selected":
|
|
87
|
-
disabled:
|
|
87
|
+
"aria-selected": a,
|
|
88
|
+
disabled: r.disabled,
|
|
88
89
|
"data-multiselect-item": "",
|
|
89
|
-
"data-focused":
|
|
90
|
+
"data-focused": s ? "" : void 0,
|
|
90
91
|
initial: {
|
|
91
92
|
opacity: 0,
|
|
92
93
|
x: -8
|
|
@@ -97,90 +98,90 @@ var m = s.forwardRef(({ items: m, groups: h, value: g, onValueChange: _, searchP
|
|
|
97
98
|
},
|
|
98
99
|
transition: {
|
|
99
100
|
...e.snappy,
|
|
100
|
-
delay:
|
|
101
|
+
delay: o * .02
|
|
101
102
|
},
|
|
102
|
-
onClick: () =>
|
|
103
|
-
className:
|
|
104
|
-
children: [
|
|
103
|
+
onClick: () => K(r.id),
|
|
104
|
+
className: n("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", a && "bg-accent-4 text-accent-11", s && "bg-surface-panel-hover"),
|
|
105
|
+
children: [S ? /* @__PURE__ */ u("span", {
|
|
105
106
|
className: "flex-1 min-w-0",
|
|
106
|
-
children:
|
|
107
|
-
}) : /* @__PURE__ */
|
|
108
|
-
src:
|
|
107
|
+
children: S(r, a)
|
|
108
|
+
}) : /* @__PURE__ */ d(l, { children: [r.image && /* @__PURE__ */ u("img", {
|
|
109
|
+
src: r.image,
|
|
109
110
|
alt: "",
|
|
110
111
|
className: "h-ico-md w-ico-md rounded-pill object-cover shrink-0"
|
|
111
|
-
}), /* @__PURE__ */
|
|
112
|
+
}), /* @__PURE__ */ d("span", {
|
|
112
113
|
className: "flex-1 min-w-0",
|
|
113
|
-
children: [/* @__PURE__ */
|
|
114
|
+
children: [/* @__PURE__ */ u("span", {
|
|
114
115
|
className: "block truncate text-body-md font-body text-surface-fg",
|
|
115
|
-
children:
|
|
116
|
-
}),
|
|
116
|
+
children: r.label
|
|
117
|
+
}), r.description && /* @__PURE__ */ u("span", {
|
|
117
118
|
className: "block truncate text-caption text-surface-fg-subtle",
|
|
118
|
-
children:
|
|
119
|
+
children: r.description
|
|
119
120
|
})]
|
|
120
|
-
})] }),
|
|
121
|
+
})] }), a && /* @__PURE__ */ u(m.span, {
|
|
121
122
|
initial: { scale: 0 },
|
|
122
123
|
animate: { scale: 1 },
|
|
123
124
|
transition: e.snappy,
|
|
124
125
|
className: "inline-flex shrink-0",
|
|
125
|
-
children: /* @__PURE__ */
|
|
126
|
-
icon:
|
|
126
|
+
children: /* @__PURE__ */ u(i, {
|
|
127
|
+
icon: f,
|
|
127
128
|
size: "sm",
|
|
128
129
|
className: "text-accent-11"
|
|
129
130
|
})
|
|
130
131
|
})]
|
|
131
|
-
},
|
|
132
|
+
}, r.id) });
|
|
132
133
|
}
|
|
133
|
-
return /* @__PURE__ */
|
|
134
|
-
open:
|
|
135
|
-
onOpenChange:
|
|
134
|
+
return /* @__PURE__ */ d(a, {
|
|
135
|
+
open: j,
|
|
136
|
+
onOpenChange: M,
|
|
136
137
|
modal: !1,
|
|
137
|
-
children: [/* @__PURE__ */
|
|
138
|
+
children: [/* @__PURE__ */ u(s, {
|
|
138
139
|
asChild: !0,
|
|
139
|
-
children:
|
|
140
|
-
}), /* @__PURE__ */
|
|
141
|
-
ref:
|
|
142
|
-
...
|
|
143
|
-
className:
|
|
144
|
-
style: { width:
|
|
145
|
-
align:
|
|
140
|
+
children: D
|
|
141
|
+
}), /* @__PURE__ */ d(o, {
|
|
142
|
+
ref: A,
|
|
143
|
+
...k,
|
|
144
|
+
className: n("border-surface-border-strong bg-surface-overlay p-0", O),
|
|
145
|
+
style: { width: E ?? 240 },
|
|
146
|
+
align: T,
|
|
146
147
|
sideOffset: 4,
|
|
147
|
-
children: [/* @__PURE__ */
|
|
148
|
+
children: [/* @__PURE__ */ d("div", {
|
|
148
149
|
className: "flex items-center gap-ds-03 border-b border-surface-border-strong px-ds-04 py-ds-03",
|
|
149
150
|
children: [
|
|
150
|
-
/* @__PURE__ */
|
|
151
|
-
icon:
|
|
151
|
+
/* @__PURE__ */ u(i, {
|
|
152
|
+
icon: p,
|
|
152
153
|
size: "sm",
|
|
153
154
|
stroke: "light",
|
|
154
155
|
className: "shrink-0 text-surface-fg-subtle"
|
|
155
156
|
}),
|
|
156
|
-
/* @__PURE__ */
|
|
157
|
+
/* @__PURE__ */ u("input", {
|
|
157
158
|
type: "text",
|
|
158
|
-
placeholder:
|
|
159
|
-
value:
|
|
160
|
-
onChange: (e) =>
|
|
161
|
-
onKeyDown:
|
|
159
|
+
placeholder: y,
|
|
160
|
+
value: N,
|
|
161
|
+
onChange: (e) => P(e.target.value),
|
|
162
|
+
onKeyDown: q,
|
|
162
163
|
"aria-label": "Search",
|
|
163
|
-
"aria-activedescendant":
|
|
164
|
+
"aria-activedescendant": z >= 0 ? `msp-item-${z}` : void 0,
|
|
164
165
|
className: "w-full bg-transparent text-body-md font-body text-surface-fg placeholder:text-surface-fg-subtle outline-hidden"
|
|
165
166
|
}),
|
|
166
|
-
|
|
167
|
+
L && /* @__PURE__ */ u(r, { size: "sm" })
|
|
167
168
|
]
|
|
168
|
-
}), /* @__PURE__ */
|
|
169
|
-
ref:
|
|
169
|
+
}), /* @__PURE__ */ d("div", {
|
|
170
|
+
ref: H,
|
|
170
171
|
role: "listbox",
|
|
171
172
|
"aria-multiselectable": "true",
|
|
172
173
|
className: "max-h-[240px] overflow-y-auto py-ds-02",
|
|
173
|
-
children: [
|
|
174
|
+
children: [G ? G.map((e) => /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("div", {
|
|
174
175
|
className: "px-ds-04 py-ds-02 text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle",
|
|
175
176
|
children: e.label
|
|
176
|
-
}), e.items.map(
|
|
177
|
+
}), e.items.map(Y)] }, e.label)) : W.map(Y), !L && W.length === 0 && /* @__PURE__ */ u("p", {
|
|
177
178
|
className: "px-ds-04 py-ds-05 text-center text-body-sm font-body text-surface-fg-subtle",
|
|
178
|
-
children:
|
|
179
|
+
children: C
|
|
179
180
|
})]
|
|
180
181
|
})]
|
|
181
182
|
})]
|
|
182
183
|
});
|
|
183
184
|
});
|
|
184
|
-
|
|
185
|
+
h.displayName = "MultiSelectPopover";
|
|
185
186
|
//#endregion
|
|
186
|
-
export {
|
|
187
|
+
export { h as MultiSelectPopover };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi-select-popover.js","names":[],"sources":["../../src/composed/multi-select-popover.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck, IconSearch } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '../ui/popover'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface MultiSelectItem {\n id: string\n label: string\n image?: string\n description?: string\n disabled?: boolean\n}\n\nexport interface MultiSelectGroup {\n label: string\n items: MultiSelectItem[]\n}\n\nexport interface MultiSelectPopoverProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onSelect'> {\n /** Flat list of items (use `groups` for grouped rendering) */\n items?: MultiSelectItem[]\n /** Grouped items with section headers */\n groups?: MultiSelectGroup[]\n /** Currently selected item IDs */\n value: string[]\n /** Called when selection changes */\n onValueChange: (ids: string[]) => void\n /** @default 'Search...' */\n searchPlaceholder?: string\n /** Async search — replaces local filter with server results */\n onSearch?: (query: string) => Promise<MultiSelectItem[]>\n /** Debounce for async search in ms @default 300 */\n searchDebounce?: number\n /** Custom item renderer */\n renderItem?: (item: MultiSelectItem, selected: boolean) => React.ReactNode\n /** Message when no items match filter @default 'No results found' */\n emptyMessage?: string\n /** Maximum number of selections */\n maxSelections?: number\n /** Popover alignment @default 'start' */\n align?: 'start' | 'center' | 'end'\n /** Popover width */\n width?: string | number\n /** Trigger element */\n children: React.ReactNode\n}\n\n// ============================================================\n// MultiSelectPopover\n// ============================================================\n\nconst MultiSelectPopover = React.forwardRef<HTMLDivElement, MultiSelectPopoverProps>(\n (\n {\n items,\n groups,\n value,\n onValueChange,\n searchPlaceholder = 'Search...',\n onSearch,\n searchDebounce = 300,\n renderItem,\n emptyMessage = 'No results found',\n maxSelections,\n align = 'start',\n width,\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const [open, setOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [asyncItems, setAsyncItems] = React.useState<MultiSelectItem[] | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [focusedIndex, setFocusedIndex] = React.useState(-1)\n const debounceRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const listRef = React.useRef<HTMLDivElement>(null)\n\n // Reset search and focus when popover closes\n React.useEffect(() => {\n if (!open) {\n setSearch('')\n setAsyncItems(null)\n setFocusedIndex(-1)\n }\n }, [open])\n\n // Async search\n React.useEffect(() => {\n if (!onSearch || !open) return\n if (!search.trim()) {\n setAsyncItems(null)\n setLoading(false)\n return\n }\n setLoading(true)\n clearTimeout(debounceRef.current)\n debounceRef.current = setTimeout(() => {\n onSearch(search)\n .then((results) => setAsyncItems(results))\n .catch(() => setAsyncItems([]))\n .finally(() => setLoading(false))\n }, searchDebounce)\n return () => clearTimeout(debounceRef.current)\n }, [search, onSearch, searchDebounce, open])\n\n // Resolve visible items\n const allItems = React.useMemo(() => {\n if (asyncItems) return asyncItems\n if (groups) return groups.flatMap((g) => g.items)\n return items ?? []\n }, [items, groups, asyncItems])\n\n const filteredItems = React.useMemo(() => {\n if (onSearch) return allItems // async search handles its own filtering\n const q = search.toLowerCase()\n if (!q) return allItems\n return allItems.filter((item) => item.label.toLowerCase().includes(q))\n }, [allItems, search, onSearch])\n\n const filteredGroups = React.useMemo(() => {\n if (!groups || onSearch || asyncItems) return null\n const q = search.toLowerCase()\n return groups\n .map((g) => ({\n ...g,\n items: q ? g.items.filter((item) => item.label.toLowerCase().includes(q)) : g.items,\n }))\n .filter((g) => g.items.length > 0)\n }, [groups, search, onSearch, asyncItems])\n\n function toggle(id: string) {\n const isSelected = value.includes(id)\n if (isSelected) {\n onValueChange(value.filter((v) => v !== id))\n } else {\n if (maxSelections && value.length >= maxSelections) {\n // Replace: drop the first selected item to make room for the new one\n onValueChange([...value.slice(1), id])\n } else {\n onValueChange([...value, id])\n }\n }\n }\n\n // Reset focused index when search text changes\n \n React.useEffect(() => { setFocusedIndex(-1) }, [search])\n\n // Scroll focused item into view\n React.useEffect(() => {\n if (focusedIndex < 0) return\n const list = listRef.current\n if (!list) return\n const items = list.querySelectorAll('[data-multiselect-item]')\n items[focusedIndex]?.scrollIntoView({ block: 'nearest' })\n }, [focusedIndex])\n\n function handleSearchKeyDown(e: React.KeyboardEvent) {\n const count = filteredItems.length\n if (count === 0) return\n\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev < count - 1 ? prev + 1 : 0))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev <= 0 ? count - 1 : prev - 1))\n } else if (e.key === 'Enter' && focusedIndex >= 0) {\n e.preventDefault()\n const item = filteredItems[focusedIndex]\n if (item && !item.disabled) toggle(item.id)\n }\n }\n\n let itemCounter = 0\n\n function renderItemRow(item: MultiSelectItem) {\n const isSelected = value.includes(item.id)\n const index = itemCounter++\n const isFocused = index === focusedIndex\n return (\n <motion.button\n key={item.id}\n id={`msp-item-${index}`}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n disabled={item.disabled}\n data-multiselect-item=\"\"\n data-focused={isFocused ? '' : undefined}\n initial={{ opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={{ ...springs.snappy, delay: index * 0.02 }}\n onClick={() => toggle(item.id)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-raised-hover',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n isSelected && 'bg-accent-2 text-accent-11',\n isFocused && 'bg-surface-raised-hover',\n )}\n >\n {renderItem ? (\n <span className=\"flex-1 min-w-0\">{renderItem(item, isSelected)}</span>\n ) : (\n <>\n {item.image && (\n <img\n src={item.image}\n alt=\"\"\n className=\"h-ico-md w-ico-md rounded-pill object-cover shrink-0\"\n />\n )}\n <span className=\"flex-1 min-w-0\">\n <span className=\"block truncate text-body-md font-body text-surface-fg\">{item.label}</span>\n {item.description && (\n <span className=\"block truncate text-caption text-surface-fg-subtle\">{item.description}</span>\n )}\n </span>\n </>\n )}\n {isSelected && (\n <motion.span\n initial={{ scale: 0 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex shrink-0\"\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"text-accent-11\" />\n </motion.span>\n )}\n </motion.button>\n )\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen} modal={false}>\n <PopoverTrigger asChild>{children}</PopoverTrigger>\n <PopoverContent\n ref={ref}\n {...props}\n className={cn(\n 'border-surface-border-strong bg-surface-overlay p-0',\n className,\n )}\n style={{ width: width ?? 240 }}\n align={align}\n sideOffset={4}\n >\n {/* Search */}\n <div className=\"flex items-center gap-ds-03 border-b border-surface-border-strong px-ds-04 py-ds-03\">\n <Icon icon={IconSearch} size=\"sm\" stroke=\"light\" className=\"shrink-0 text-surface-fg-subtle\" />\n <input\n type=\"text\"\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={handleSearchKeyDown}\n aria-label=\"Search\"\n aria-activedescendant={focusedIndex >= 0 ? `msp-item-${focusedIndex}` : undefined}\n className=\"w-full bg-transparent text-body-md font-body text-surface-fg placeholder:text-surface-fg-subtle outline-hidden\"\n />\n {loading && <Spinner size=\"sm\" />}\n </div>\n\n {/* Items */}\n <div ref={listRef} role=\"listbox\" aria-multiselectable=\"true\" className=\"max-h-[240px] overflow-y-auto py-ds-02\">\n {filteredGroups\n ? filteredGroups.map((group) => (\n <div key={group.label}>\n <div className=\"px-ds-04 py-ds-02 text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {group.label}\n </div>\n {group.items.map(renderItemRow)}\n </div>\n ))\n : filteredItems.map(renderItemRow)}\n {!loading && filteredItems.length === 0 && (\n <p className=\"px-ds-04 py-ds-05 text-center text-body-sm font-body text-surface-fg-subtle\">\n {emptyMessage}\n </p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n )\n },\n)\nMultiSelectPopover.displayName = 'MultiSelectPopover'\n\nexport { MultiSelectPopover }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"multi-select-popover.js","names":[],"sources":["../../src/composed/multi-select-popover.tsx"],"sourcesContent":["'use client'\n\nimport { IconCheck, IconSearch } from '@tabler/icons-react'\nimport { motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { MotionPreference } from '../motion/motion-preference'\nimport { Icon } from '../ui/icon'\nimport { springs } from '../ui/lib/motion'\nimport { cn } from '../ui/lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '../ui/popover'\nimport { Spinner } from '../ui/spinner'\n\n// ============================================================\n// Types\n// ============================================================\n\nexport interface MultiSelectItem {\n id: string\n label: string\n image?: string\n description?: string\n disabled?: boolean\n}\n\nexport interface MultiSelectGroup {\n label: string\n items: MultiSelectItem[]\n}\n\nexport interface MultiSelectPopoverProps extends Omit<React.ComponentPropsWithoutRef<'div'>, 'onSelect'> {\n /** Flat list of items (use `groups` for grouped rendering) */\n items?: MultiSelectItem[]\n /** Grouped items with section headers */\n groups?: MultiSelectGroup[]\n /** Currently selected item IDs */\n value: string[]\n /** Called when selection changes */\n onValueChange: (ids: string[]) => void\n /** @default 'Search...' */\n searchPlaceholder?: string\n /** Async search — replaces local filter with server results */\n onSearch?: (query: string) => Promise<MultiSelectItem[]>\n /** Debounce for async search in ms @default 300 */\n searchDebounce?: number\n /** Custom item renderer */\n renderItem?: (item: MultiSelectItem, selected: boolean) => React.ReactNode\n /** Message when no items match filter @default 'No results found' */\n emptyMessage?: string\n /** Maximum number of selections */\n maxSelections?: number\n /** Popover alignment @default 'start' */\n align?: 'start' | 'center' | 'end'\n /** Popover width */\n width?: string | number\n /** Trigger element */\n children: React.ReactNode\n}\n\n// ============================================================\n// MultiSelectPopover\n// ============================================================\n\nconst MultiSelectPopover = React.forwardRef<HTMLDivElement, MultiSelectPopoverProps>(\n (\n {\n items,\n groups,\n value,\n onValueChange,\n searchPlaceholder = 'Search...',\n onSearch,\n searchDebounce = 300,\n renderItem,\n emptyMessage = 'No results found',\n maxSelections,\n align = 'start',\n width,\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const [open, setOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [asyncItems, setAsyncItems] = React.useState<MultiSelectItem[] | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [focusedIndex, setFocusedIndex] = React.useState(-1)\n const debounceRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const listRef = React.useRef<HTMLDivElement>(null)\n\n // Reset search and focus when popover closes\n React.useEffect(() => {\n if (!open) {\n setSearch('')\n setAsyncItems(null)\n setFocusedIndex(-1)\n }\n }, [open])\n\n // Async search\n React.useEffect(() => {\n if (!onSearch || !open) return\n if (!search.trim()) {\n setAsyncItems(null)\n setLoading(false)\n return\n }\n setLoading(true)\n clearTimeout(debounceRef.current)\n debounceRef.current = setTimeout(() => {\n onSearch(search)\n .then((results) => setAsyncItems(results))\n .catch(() => setAsyncItems([]))\n .finally(() => setLoading(false))\n }, searchDebounce)\n return () => clearTimeout(debounceRef.current)\n }, [search, onSearch, searchDebounce, open])\n\n // Resolve visible items\n const allItems = React.useMemo(() => {\n if (asyncItems) return asyncItems\n if (groups) return groups.flatMap((g) => g.items)\n return items ?? []\n }, [items, groups, asyncItems])\n\n const filteredItems = React.useMemo(() => {\n if (onSearch) return allItems // async search handles its own filtering\n const q = search.toLowerCase()\n if (!q) return allItems\n return allItems.filter((item) => item.label.toLowerCase().includes(q))\n }, [allItems, search, onSearch])\n\n const filteredGroups = React.useMemo(() => {\n if (!groups || onSearch || asyncItems) return null\n const q = search.toLowerCase()\n return groups\n .map((g) => ({\n ...g,\n items: q ? g.items.filter((item) => item.label.toLowerCase().includes(q)) : g.items,\n }))\n .filter((g) => g.items.length > 0)\n }, [groups, search, onSearch, asyncItems])\n\n function toggle(id: string) {\n const isSelected = value.includes(id)\n if (isSelected) {\n onValueChange(value.filter((v) => v !== id))\n } else {\n if (maxSelections && value.length >= maxSelections) {\n // Replace: drop the first selected item to make room for the new one\n onValueChange([...value.slice(1), id])\n } else {\n onValueChange([...value, id])\n }\n }\n }\n\n // Reset focused index when search text changes\n \n React.useEffect(() => { setFocusedIndex(-1) }, [search])\n\n // Scroll focused item into view\n React.useEffect(() => {\n if (focusedIndex < 0) return\n const list = listRef.current\n if (!list) return\n const items = list.querySelectorAll('[data-multiselect-item]')\n items[focusedIndex]?.scrollIntoView({ block: 'nearest' })\n }, [focusedIndex])\n\n function handleSearchKeyDown(e: React.KeyboardEvent) {\n const count = filteredItems.length\n if (count === 0) return\n\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev < count - 1 ? prev + 1 : 0))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setFocusedIndex((prev) => (prev <= 0 ? count - 1 : prev - 1))\n } else if (e.key === 'Enter' && focusedIndex >= 0) {\n e.preventDefault()\n const item = filteredItems[focusedIndex]\n if (item && !item.disabled) toggle(item.id)\n }\n }\n\n let itemCounter = 0\n\n function renderItemRow(item: MultiSelectItem) {\n const isSelected = value.includes(item.id)\n const index = itemCounter++\n const isFocused = index === focusedIndex\n return (\n <MotionPreference>\n <motion.button\n key={item.id}\n id={`msp-item-${index}`}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n disabled={item.disabled}\n data-multiselect-item=\"\"\n data-focused={isFocused ? '' : undefined}\n initial={{ opacity: 0, x: -8 }}\n animate={{ opacity: 1, x: 0 }}\n transition={{ ...springs.snappy, delay: index * 0.02 }}\n onClick={() => toggle(item.id)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left transition-colors duration-fast-01 ease-productive-standard',\n 'hover:bg-surface-panel-hover',\n 'disabled:opacity-action-disabled disabled:cursor-not-allowed',\n // accent-4 so the selection stays above the focus ring's\n // panel-hover fill in dark; accent-2 sat below the panel.\n isSelected && 'bg-accent-4 text-accent-11',\n isFocused && 'bg-surface-panel-hover',\n )}\n >\n {renderItem ? (\n <span className=\"flex-1 min-w-0\">{renderItem(item, isSelected)}</span>\n ) : (\n <>\n {item.image && (\n <img\n src={item.image}\n alt=\"\"\n className=\"h-ico-md w-ico-md rounded-pill object-cover shrink-0\"\n />\n )}\n <span className=\"flex-1 min-w-0\">\n <span className=\"block truncate text-body-md font-body text-surface-fg\">{item.label}</span>\n {item.description && (\n <span className=\"block truncate text-caption text-surface-fg-subtle\">{item.description}</span>\n )}\n </span>\n </>\n )}\n {isSelected && (\n <motion.span\n initial={{ scale: 0 }}\n animate={{ scale: 1 }}\n transition={springs.snappy}\n className=\"inline-flex shrink-0\"\n >\n <Icon icon={IconCheck} size=\"sm\" className=\"text-accent-11\" />\n </motion.span>\n )}\n </motion.button>\n </MotionPreference>\n )\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen} modal={false}>\n <PopoverTrigger asChild>{children}</PopoverTrigger>\n <PopoverContent\n ref={ref}\n {...props}\n className={cn(\n 'border-surface-border-strong bg-surface-overlay p-0',\n className,\n )}\n style={{ width: width ?? 240 }}\n align={align}\n sideOffset={4}\n >\n {/* Search */}\n <div className=\"flex items-center gap-ds-03 border-b border-surface-border-strong px-ds-04 py-ds-03\">\n <Icon icon={IconSearch} size=\"sm\" stroke=\"light\" className=\"shrink-0 text-surface-fg-subtle\" />\n <input\n type=\"text\"\n placeholder={searchPlaceholder}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={handleSearchKeyDown}\n aria-label=\"Search\"\n aria-activedescendant={focusedIndex >= 0 ? `msp-item-${focusedIndex}` : undefined}\n className=\"w-full bg-transparent text-body-md font-body text-surface-fg placeholder:text-surface-fg-subtle outline-hidden\"\n />\n {loading && <Spinner size=\"sm\" />}\n </div>\n\n {/* Items */}\n <div ref={listRef} role=\"listbox\" aria-multiselectable=\"true\" className=\"max-h-[240px] overflow-y-auto py-ds-02\">\n {filteredGroups\n ? filteredGroups.map((group) => (\n <div key={group.label}>\n <div className=\"px-ds-04 py-ds-02 text-label-xs font-semibold uppercase tracking-wider text-surface-fg-subtle\">\n {group.label}\n </div>\n {group.items.map(renderItemRow)}\n </div>\n ))\n : filteredItems.map(renderItemRow)}\n {!loading && filteredItems.length === 0 && (\n <p className=\"px-ds-04 py-ds-05 text-center text-body-sm font-body text-surface-fg-subtle\">\n {emptyMessage}\n </p>\n )}\n </div>\n </PopoverContent>\n </Popover>\n )\n },\n)\nMultiSelectPopover.displayName = 'MultiSelectPopover'\n\nexport { MultiSelectPopover }\n"],"mappings":"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"}
|
|
@@ -27,7 +27,7 @@ var s = n.forwardRef(function({ className: n, label: s, ...c }, l) {
|
|
|
27
27
|
/* @__PURE__ */ r("div", {
|
|
28
28
|
className: "grid grid-cols-1 gap-ds-05 sm:grid-cols-2 lg:grid-cols-4",
|
|
29
29
|
children: Array.from({ length: 4 }).map((e, n) => /* @__PURE__ */ i("div", {
|
|
30
|
-
className: "flex flex-col gap-ds-04 rounded-surface border border-card bg-surface-
|
|
30
|
+
className: "flex flex-col gap-ds-04 rounded-surface border border-card bg-surface-panel p-ds-05b",
|
|
31
31
|
children: [
|
|
32
32
|
/* @__PURE__ */ i("div", {
|
|
33
33
|
className: "flex items-center justify-between",
|
|
@@ -39,7 +39,7 @@ var s = n.forwardRef(function({ className: n, label: s, ...c }, l) {
|
|
|
39
39
|
}, `stat-${n}`))
|
|
40
40
|
}),
|
|
41
41
|
/* @__PURE__ */ i("div", {
|
|
42
|
-
className: "rounded-surface border border-card bg-surface-
|
|
42
|
+
className: "rounded-surface border border-card bg-surface-panel p-ds-06",
|
|
43
43
|
children: [
|
|
44
44
|
/* @__PURE__ */ i("div", {
|
|
45
45
|
className: "mb-ds-06 flex items-center justify-between",
|
|
@@ -91,7 +91,7 @@ var c = n.forwardRef(function({ className: n, label: s, ...c }, l) {
|
|
|
91
91
|
/* @__PURE__ */ r("div", {
|
|
92
92
|
className: "grid grid-cols-1 gap-ds-05 sm:grid-cols-2 lg:grid-cols-3",
|
|
93
93
|
children: Array.from({ length: 6 }).map((e, n) => /* @__PURE__ */ i("div", {
|
|
94
|
-
className: "flex flex-col gap-ds-05 rounded-surface border border-card bg-surface-
|
|
94
|
+
className: "flex flex-col gap-ds-05 rounded-surface border border-card bg-surface-panel p-ds-05b",
|
|
95
95
|
children: [
|
|
96
96
|
/* @__PURE__ */ i("div", {
|
|
97
97
|
className: "flex items-center justify-between",
|
|
@@ -124,7 +124,7 @@ var l = n.forwardRef(function({ className: n, label: s, ...c }, l) {
|
|
|
124
124
|
ref: l,
|
|
125
125
|
...a(s),
|
|
126
126
|
...c,
|
|
127
|
-
className: e("flex h-full flex-col gap-0 rounded-surface border border-card bg-surface-
|
|
127
|
+
className: e("flex h-full flex-col gap-0 rounded-surface border border-card bg-surface-panel", n),
|
|
128
128
|
children: [
|
|
129
129
|
/* @__PURE__ */ r(o, { label: s }),
|
|
130
130
|
/* @__PURE__ */ i("div", {
|