@getdom/studio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/LICENSE.md +109 -0
  2. package/README.md +44 -0
  3. package/SKILL.md +83 -0
  4. package/package.json +73 -0
  5. package/public/llms.txt +57 -0
  6. package/public/skills.sh +11 -0
  7. package/src/pages/_layout/componentCatalog.generated.js +4514 -0
  8. package/src/pages/_layout/componentInspector.js +60 -0
  9. package/src/pages/_layout/componentManager.js +192 -0
  10. package/src/pages/_layout/componentRuntimeRecords.js +49 -0
  11. package/src/pages/_layout/inspector/componentRegistry.js +639 -0
  12. package/src/pages/_layout/inspector/serialize.js +134 -0
  13. package/src/pages/_layout/inspector/useInspector.js +293 -0
  14. package/src/pages/components/accordion/DomAccordion.vue +39 -0
  15. package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
  16. package/src/pages/components/button/DomButton.vue +132 -0
  17. package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
  18. package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
  19. package/src/pages/components/dialog/DomDialog.vue +271 -0
  20. package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
  21. package/src/pages/components/dialog/DomDialogStack.vue +289 -0
  22. package/src/pages/components/dialog/useDialogs.js +253 -0
  23. package/src/pages/components/drawer/DomDrawer.vue +159 -0
  24. package/src/pages/components/dropdown/DomDropdown.vue +261 -0
  25. package/src/pages/components/icon-button/DomIconButton.vue +155 -0
  26. package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
  27. package/src/pages/components/menu/DomMenu.vue +359 -0
  28. package/src/pages/components/menu/DomMenuItem.vue +233 -0
  29. package/src/pages/components/menu/DomMenuLabel.vue +19 -0
  30. package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
  31. package/src/pages/components/menu/menuContext.js +11 -0
  32. package/src/pages/components/menu/menuPlacement.js +26 -0
  33. package/src/pages/components/menu/submenu.js +58 -0
  34. package/src/pages/components/pagination/DomPagination.vue +265 -0
  35. package/src/pages/components/playground/DomRenderer.vue +48 -0
  36. package/src/pages/components/popover/DomPopover.vue +244 -0
  37. package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
  38. package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
  39. package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
  40. package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
  41. package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
  42. package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
  43. package/src/pages/components/stepper/DomStepper.vue +386 -0
  44. package/src/pages/components/tabs/DomTabs.vue +149 -0
  45. package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
  46. package/src/pages/components/toast/DomToastItem.vue +52 -0
  47. package/src/pages/components/toast/DomToastStack.vue +768 -0
  48. package/src/pages/components/toast/useToasts.js +237 -0
  49. package/src/pages/components/toolbar/DomToolbar.vue +158 -0
  50. package/src/pages/components/tooltip/DomTooltip.vue +69 -0
  51. package/src/pages/components/tooltip/vDomTooltip.js +106 -0
  52. package/src/pages/components/tree-view/DomTreeView.vue +961 -0
  53. package/src/pages/components/tree-view/treeSchemas.js +129 -0
  54. package/src/pages/components/tree-view/treeUtils.js +228 -0
  55. package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
  56. package/src/pages/data/_shared/resourceRecord.js +29 -0
  57. package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
  58. package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
  59. package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
  60. package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
  61. package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
  62. package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
  63. package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
  64. package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
  65. package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
  66. package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
  67. package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
  68. package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
  69. package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
  70. package/src/pages/data/kanban/DomKanban.vue +1703 -0
  71. package/src/pages/data/kanban/kanbanApi.js +80 -0
  72. package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
  73. package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
  74. package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
  75. package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
  76. package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
  77. package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
  78. package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
  79. package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
  80. package/src/pages/forms/_shared/options.js +61 -0
  81. package/src/pages/forms/_shared/useOptionMenu.js +59 -0
  82. package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
  83. package/src/pages/forms/calendar/DomCalendar.vue +442 -0
  84. package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
  85. package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
  86. package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
  87. package/src/pages/forms/color-input/DomColorInput.vue +118 -0
  88. package/src/pages/forms/combobox/DomCombobox.vue +237 -0
  89. package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
  90. package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
  91. package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
  92. package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
  93. package/src/pages/forms/field/DomField.vue +267 -0
  94. package/src/pages/forms/field/fieldLayout.js +1 -0
  95. package/src/pages/forms/field/fieldProps.js +114 -0
  96. package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
  97. package/src/pages/forms/field/useField.js +343 -0
  98. package/src/pages/forms/field/validators.js +364 -0
  99. package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
  100. package/src/pages/forms/form/DomForm.vue +985 -0
  101. package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
  102. package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
  103. package/src/pages/forms/form/adapters/shared.js +53 -0
  104. package/src/pages/forms/form/adapters/zod.js +235 -0
  105. package/src/pages/forms/form/formApi.js +38 -0
  106. package/src/pages/forms/form/formDefinition.js +392 -0
  107. package/src/pages/forms/form/schemaAdapter.js +54 -0
  108. package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
  109. package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
  110. package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
  111. package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
  112. package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
  113. package/src/pages/forms/listbox/DomListbox.vue +105 -0
  114. package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
  115. package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
  116. package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
  117. package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
  118. package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
  119. package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
  120. package/src/pages/forms/password-strength/passwordStrength.js +92 -0
  121. package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
  122. package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
  123. package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
  124. package/src/pages/forms/select/DomSelect.vue +479 -0
  125. package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
  126. package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
  127. package/src/pages/forms/text-input/DomTextInput.vue +48 -0
  128. package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
  129. package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
  130. package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
  131. package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
  132. package/src/pages/forms/time-selector/timeUtils.js +194 -0
  133. package/src/pages/forms/toggle/DomToggle.vue +158 -0
  134. package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
  135. package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
  136. package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
  137. package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
  138. package/src/pages/lib/css/color.js +245 -0
  139. package/src/pages/lib/headless/accordion.d.ts +7 -0
  140. package/src/pages/lib/headless/accordion.js +246 -0
  141. package/src/pages/lib/headless/autocomplete.d.ts +4 -0
  142. package/src/pages/lib/headless/autocomplete.js +33 -0
  143. package/src/pages/lib/headless/base.d.ts +25 -0
  144. package/src/pages/lib/headless/base.js +127 -0
  145. package/src/pages/lib/headless/checkbox.d.ts +19 -0
  146. package/src/pages/lib/headless/checkbox.js +99 -0
  147. package/src/pages/lib/headless/combobox.d.ts +7 -0
  148. package/src/pages/lib/headless/combobox.js +488 -0
  149. package/src/pages/lib/headless/dialog.d.ts +23 -0
  150. package/src/pages/lib/headless/dialog.js +384 -0
  151. package/src/pages/lib/headless/drawer.d.ts +21 -0
  152. package/src/pages/lib/headless/drawer.js +682 -0
  153. package/src/pages/lib/headless/dropdown.d.ts +19 -0
  154. package/src/pages/lib/headless/dropdown.js +412 -0
  155. package/src/pages/lib/headless/floating-effect.js +118 -0
  156. package/src/pages/lib/headless/floating.d.ts +34 -0
  157. package/src/pages/lib/headless/floating.js +382 -0
  158. package/src/pages/lib/headless/index.d.ts +19 -0
  159. package/src/pages/lib/headless/index.js +19 -0
  160. package/src/pages/lib/headless/listbox.d.ts +19 -0
  161. package/src/pages/lib/headless/listbox.js +126 -0
  162. package/src/pages/lib/headless/menu.d.ts +18 -0
  163. package/src/pages/lib/headless/menu.js +117 -0
  164. package/src/pages/lib/headless/popover-panel.d.ts +18 -0
  165. package/src/pages/lib/headless/popover-panel.js +309 -0
  166. package/src/pages/lib/headless/popover.d.ts +19 -0
  167. package/src/pages/lib/headless/popover.js +391 -0
  168. package/src/pages/lib/headless/radio-group.d.ts +19 -0
  169. package/src/pages/lib/headless/radio-group.js +100 -0
  170. package/src/pages/lib/headless/tabs.d.ts +18 -0
  171. package/src/pages/lib/headless/tabs.js +119 -0
  172. package/src/pages/lib/headless/toast.d.ts +13 -0
  173. package/src/pages/lib/headless/toast.js +65 -0
  174. package/src/pages/lib/headless/toggle.d.ts +19 -0
  175. package/src/pages/lib/headless/toggle.js +162 -0
  176. package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
  177. package/src/pages/lib/headless/tooltip-controller.js +549 -0
  178. package/src/pages/lib/headless/tooltip.d.ts +19 -0
  179. package/src/pages/lib/headless/tooltip.js +153 -0
  180. package/src/pages/lib/headless/transitions.d.ts +5 -0
  181. package/src/pages/lib/headless/transitions.js +89 -0
  182. package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
  183. package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
  184. package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
  185. package/src/pages/lib/vue/codeHighlighter.js +132 -0
  186. package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
  187. package/src/pages/lib/vue/index.d.ts +6806 -0
  188. package/src/pages/lib/vue/index.js +218 -0
  189. package/src/pages/lib/vue/useTheme.js +116 -0
  190. package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
  191. package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
  192. package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
  193. package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
  194. package/src/pages/mobile/shell/DomAppShell.vue +51 -0
  195. package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
  196. package/src/pages/mobile/stack/DomAppStack.vue +711 -0
  197. package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
  198. package/src/pages/visual/alert/DomAlert.vue +363 -0
  199. package/src/pages/visual/avatar/DomAvatar.vue +110 -0
  200. package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
  201. package/src/pages/visual/badge/DomBadge.vue +105 -0
  202. package/src/pages/visual/card/DomCard.vue +65 -0
  203. package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
  204. package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
  205. package/src/pages/visual/progress/DomProgress.vue +212 -0
  206. package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
  207. package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
  208. package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
  209. package/src/pages/visual/spinner/DomSpinner.vue +122 -0
  210. package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
  211. package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
  212. package/src/pages/visual/text-diff/textDiff.js +300 -0
  213. package/src/style.css +446 -0
@@ -0,0 +1,105 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+
4
+ defineOptions({
5
+ __doc: {
6
+ name: 'Badge',
7
+ tag: '<DomBadge>',
8
+ description: 'A compact visual label for counts, roles, plans, tags, and metadata.',
9
+ playground: {
10
+ initial: { label: 'Scale', tone: 'success', variant: 'soft', size: 'md' },
11
+ },
12
+ slots: [
13
+ { name: '(default)', description: 'Badge text or compact inline content.' },
14
+ ],
15
+ },
16
+ });
17
+
18
+ const props = defineProps({
19
+ as: {
20
+ type: String,
21
+ default: 'span',
22
+ _edit: { description: "Element to render. Use 'RouterLink' or 'button' through wrappers when interaction is needed." },
23
+ },
24
+ label: {
25
+ type: String,
26
+ default: 'Badge',
27
+ _edit: { description: 'Fallback text when no default slot is provided.' },
28
+ },
29
+ tone: {
30
+ type: String,
31
+ default: 'neutral',
32
+ _edit: {
33
+ options: ['neutral', 'primary', 'success', 'warning', 'danger', 'info'],
34
+ description: 'Semantic colour treatment.',
35
+ },
36
+ },
37
+ variant: {
38
+ type: String,
39
+ default: 'soft',
40
+ _edit: {
41
+ options: ['soft', 'solid', 'outline'],
42
+ description: 'Visual weight.',
43
+ },
44
+ },
45
+ size: {
46
+ type: String,
47
+ default: 'md',
48
+ _edit: {
49
+ options: ['sm', 'md', 'lg'],
50
+ description: 'Badge density.',
51
+ },
52
+ },
53
+ });
54
+
55
+ const toneClasses = {
56
+ neutral: {
57
+ soft: 'bg-secondary text-secondary-fg ring-border',
58
+ solid: 'bg-canvas-fg text-canvas ring-transparent',
59
+ outline: 'bg-canvas text-canvas-fg ring-border',
60
+ },
61
+ primary: {
62
+ soft: 'bg-primary/10 text-primary ring-primary/15',
63
+ solid: 'bg-primary text-primary-fg ring-transparent',
64
+ outline: 'bg-canvas text-primary ring-primary/30',
65
+ },
66
+ success: {
67
+ soft: 'bg-success/15 text-success ring-success/20',
68
+ solid: 'bg-success text-success-fg ring-transparent',
69
+ outline: 'bg-canvas text-success ring-success/30',
70
+ },
71
+ warning: {
72
+ soft: 'bg-warning/20 text-warning-fg ring-warning/30',
73
+ solid: 'bg-warning text-warning-fg ring-transparent',
74
+ outline: 'bg-canvas text-warning-fg ring-warning/40',
75
+ },
76
+ danger: {
77
+ soft: 'bg-destructive/15 text-destructive ring-destructive/20',
78
+ solid: 'bg-destructive text-destructive-fg ring-transparent',
79
+ outline: 'bg-canvas text-destructive ring-destructive/30',
80
+ },
81
+ info: {
82
+ soft: 'bg-primary/10 text-canvas-fg ring-primary/15',
83
+ solid: 'bg-primary text-primary-fg ring-transparent',
84
+ outline: 'bg-canvas text-canvas-fg ring-primary/30',
85
+ },
86
+ };
87
+
88
+ const sizeClasses = {
89
+ sm: 'px-2 py-0.5 text-[11px]',
90
+ md: 'px-2.5 py-1 text-xs',
91
+ lg: 'px-3 py-1.5 text-sm',
92
+ };
93
+
94
+ const classes = computed(() => [
95
+ 'inline-flex w-fit shrink-0 items-center gap-1 rounded-full font-medium ring-1 ring-inset',
96
+ toneClasses[props.tone]?.[props.variant] || toneClasses.neutral.soft,
97
+ sizeClasses[props.size] || sizeClasses.md,
98
+ ]);
99
+ </script>
100
+
101
+ <template>
102
+ <component :is="as" :class="classes">
103
+ <slot>{{ label }}</slot>
104
+ </component>
105
+ </template>
@@ -0,0 +1,65 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+
4
+ defineOptions({
5
+ __doc: {
6
+ name: 'Card',
7
+ tag: '<DomCard>',
8
+ description: 'A visual-only frame for grouped content, styled by the skin-card theme recipe.',
9
+ slots: [
10
+ { name: '(default)', description: 'Card content.' },
11
+ ],
12
+ },
13
+ });
14
+
15
+ const props = defineProps({
16
+ as: {
17
+ type: String,
18
+ default: 'div',
19
+ _edit: { description: "The HTML element or component to render. Use 'section', 'article', or a router-link when useful." },
20
+ },
21
+ padding: {
22
+ type: String,
23
+ default: 'p-4 md:p-6 lg:p-8',
24
+ _edit: {
25
+ component: 'DomTextInput',
26
+ description: 'Internal spacing. Defaults to sm on small screens, md from the md breakpoint, and lg from the lg breakpoint. Use none, sm, md, or lg for a fixed size, or provide Tailwind padding utilities.',
27
+ props: {
28
+ placeholder: 'p-4 md:p-6',
29
+ },
30
+ },
31
+ },
32
+ interactive: {
33
+ type: Boolean,
34
+ default: false,
35
+ _edit: { description: 'Add a restrained hover lift for clickable cards.' },
36
+ },
37
+ });
38
+
39
+ const paddingClasses = {
40
+ none: '',
41
+ sm: 'p-4',
42
+ md: 'p-6',
43
+ lg: 'p-8',
44
+ };
45
+
46
+ /**
47
+ * Assemble the card surface, interaction state, and resolved padding classes.
48
+ *
49
+ * Named padding values use the component scale; any other string is treated as
50
+ * Tailwind utilities so callers can include responsive variants.
51
+ *
52
+ * @returns {Array<string|boolean>} Classes bound to the rendered card element.
53
+ */
54
+ const classes = computed(() => [
55
+ 'relative w-full overflow-hidden rounded-2xl skin-card',
56
+ props.interactive && 'transition duration-200 hover:-translate-y-0.5 hover:border-primary/30',
57
+ paddingClasses[props.padding] ?? props.padding,
58
+ ]);
59
+ </script>
60
+
61
+ <template>
62
+ <component :is="as" :class="classes">
63
+ <slot />
64
+ </component>
65
+ </template>
@@ -0,0 +1,99 @@
1
+ <script setup>
2
+ defineOptions({
3
+ __doc: {
4
+ name: 'Card rail',
5
+ tag: '<DomCardRail>',
6
+ description: 'A horizontal rail for composed card groups with native overflow, snap points, and flexible item widths.',
7
+ icon: 'M4 8h5v8H4V8Zm7 0h5v8h-5V8Zm7 0h2v8h-2V8Z',
8
+ slots: [
9
+ { name: 'header', description: 'Section heading, copy, filters, or controls above the rail.' },
10
+ { name: 'actions', description: 'Optional controls aligned with the header.' },
11
+ { name: '(default)', description: 'Rail items. Each direct child becomes fixed-width, snap-aligned content.' },
12
+ ],
13
+ },
14
+ });
15
+
16
+ defineProps({
17
+ as: {
18
+ type: String,
19
+ default: 'section',
20
+ _edit: { description: 'Element to render for the rail wrapper.' },
21
+ },
22
+ ariaLabel: {
23
+ type: String,
24
+ default: 'Scrollable card rail',
25
+ _edit: { description: 'Accessible label for the focusable horizontal scroll region.' },
26
+ },
27
+ });
28
+ </script>
29
+
30
+ <template>
31
+ <component :is="as" class="dom-card-rail min-w-0">
32
+ <div v-if="$slots.header || $slots.actions" class="mb-4 flex min-w-0 flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
33
+ <div v-if="$slots.header" class="min-w-0">
34
+ <slot name="header"></slot>
35
+ </div>
36
+ <div v-if="$slots.actions" class="flex shrink-0 items-center gap-2">
37
+ <slot name="actions"></slot>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="dom-card-rail__viewport" role="region" tabindex="0" :aria-label="ariaLabel">
42
+ <div class="dom-card-rail__track">
43
+ <slot />
44
+ </div>
45
+ </div>
46
+ </component>
47
+ </template>
48
+
49
+ <style scoped>
50
+ .dom-card-rail {
51
+ --dom-card-rail-edge: 0rem;
52
+ --dom-card-rail-gap: 1rem;
53
+ }
54
+
55
+ .dom-card-rail__viewport {
56
+ margin-inline: calc(var(--dom-card-rail-edge) * -1);
57
+ overflow-x: auto;
58
+ overflow-y: hidden;
59
+ overscroll-behavior-x: contain;
60
+ scroll-snap-type: x proximity;
61
+ scrollbar-color: var(--border) transparent;
62
+ scrollbar-width: thin;
63
+ -webkit-overflow-scrolling: touch;
64
+ }
65
+
66
+ .dom-card-rail__viewport:focus-visible {
67
+ border-radius: var(--radius-lg);
68
+ outline: 2px solid var(--ring);
69
+ outline-offset: 3px;
70
+ }
71
+
72
+ .dom-card-rail__viewport::-webkit-scrollbar {
73
+ height: 0.7rem;
74
+ }
75
+
76
+ .dom-card-rail__viewport::-webkit-scrollbar-track {
77
+ background: transparent;
78
+ }
79
+
80
+ .dom-card-rail__viewport::-webkit-scrollbar-thumb {
81
+ background: var(--border);
82
+ border: 0.25rem solid transparent;
83
+ border-radius: 999px;
84
+ background-clip: padding-box;
85
+ }
86
+
87
+ .dom-card-rail__track {
88
+ display: flex;
89
+ gap: var(--dom-card-rail-gap);
90
+ min-width: max-content;
91
+ padding-block-end: 0.75rem;
92
+ padding-inline: var(--dom-card-rail-edge);
93
+ }
94
+
95
+ .dom-card-rail__track :slotted(*) {
96
+ flex: 0 0 auto;
97
+ scroll-snap-align: start;
98
+ }
99
+ </style>
@@ -0,0 +1,88 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+
4
+ defineOptions({
5
+ __doc: {
6
+ name: 'Empty state',
7
+ tag: '<DomEmptyState>',
8
+ description: 'A reusable empty, zero-data, or setup state with icon, copy, and actions.',
9
+ slots: [
10
+ { name: 'icon', description: 'Custom icon or illustration.' },
11
+ { name: 'title', description: 'Custom title markup.' },
12
+ { name: 'description', description: 'Custom helper text.' },
13
+ { name: 'actions', description: 'Primary and secondary actions.' },
14
+ ],
15
+ },
16
+ });
17
+
18
+ const props = defineProps({
19
+ title: {
20
+ type: String,
21
+ default: 'Nothing here yet',
22
+ _edit: { description: 'Primary empty-state message.' },
23
+ },
24
+ description: {
25
+ type: String,
26
+ default: 'Create something new or adjust your filters to continue.',
27
+ _edit: { description: 'Supporting copy.' },
28
+ },
29
+ icon: {
30
+ type: String,
31
+ default: 'M5 12h14M12 5v14',
32
+ _edit: {
33
+ component: 'DomIconSelector',
34
+ description: 'SVG path data for the default 24x24 line icon.',
35
+ props: {
36
+ kind: 'icons',
37
+ clearable: false,
38
+ placeholder: 'Search icons',
39
+ },
40
+ },
41
+ },
42
+ align: {
43
+ type: String,
44
+ default: 'center',
45
+ _edit: { options: ['left', 'center'], description: 'Content alignment.' },
46
+ },
47
+ size: {
48
+ type: String,
49
+ default: 'md',
50
+ _edit: { options: ['sm', 'md', 'lg'], description: 'Spacing scale.' },
51
+ },
52
+ });
53
+
54
+ const classes = computed(() => [
55
+ 'rounded-2xl border border-dashed border-border bg-secondary/30',
56
+ props.align === 'left' ? 'text-left' : 'text-center',
57
+ {
58
+ sm: 'p-5',
59
+ md: 'p-8',
60
+ lg: 'p-10',
61
+ }[props.size] || 'p-8',
62
+ ]);
63
+ </script>
64
+
65
+ <template>
66
+ <div :class="classes">
67
+ <div :class="align === 'left' ? 'items-start' : 'items-center'" class="flex flex-col">
68
+ <slot name="icon">
69
+ <div class="grid size-11 place-items-center rounded-2xl bg-canvas text-muted-fg ring-1 ring-border">
70
+ <svg viewBox="0 0 24 24" class="size-5" fill="none" aria-hidden="true">
71
+ <path :d="icon" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
72
+ </svg>
73
+ </div>
74
+ </slot>
75
+ <div class="mt-4 max-w-md">
76
+ <slot name="title">
77
+ <h3 class="text-base font-semibold tracking-tight text-canvas-fg">{{ title }}</h3>
78
+ </slot>
79
+ <slot name="description">
80
+ <p class="mt-2 text-sm leading-6 text-muted-fg">{{ description }}</p>
81
+ </slot>
82
+ </div>
83
+ <div v-if="$slots.actions" class="mt-5 flex flex-wrap gap-2" :class="align === 'left' ? 'justify-start' : 'justify-center'">
84
+ <slot name="actions" />
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </template>
@@ -0,0 +1,212 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+
4
+ defineOptions({
5
+ __doc: {
6
+ name: 'Progress',
7
+ tag: '<DomProgress>',
8
+ description: 'An accessible progress bar for uploads, imports, background jobs, and completion meters.',
9
+ icon: 'M5 12h14M5 12a3 3 0 0 1 3-3h8a3 3 0 0 1 0 6H8a3 3 0 0 1-3-3Z',
10
+ playground: {
11
+ initial: { value: 68, showValue: true },
12
+ },
13
+ },
14
+ });
15
+
16
+ const props = defineProps({
17
+ value: {
18
+ type: Number,
19
+ default: null,
20
+ _edit: { description: 'Current progress value. Leave empty for indeterminate progress.' },
21
+ },
22
+ max: {
23
+ type: Number,
24
+ default: 100,
25
+ _edit: { description: 'Maximum progress value.' },
26
+ },
27
+ label: {
28
+ type: String,
29
+ default: 'Progress',
30
+ _edit: { description: 'Accessible progress label.' },
31
+ },
32
+ tone: {
33
+ type: String,
34
+ default: 'primary',
35
+ _edit: {
36
+ options: ['primary', 'success', 'warning', 'danger', 'neutral'],
37
+ description: 'Filled bar tone.',
38
+ },
39
+ },
40
+ size: {
41
+ type: String,
42
+ default: 'md',
43
+ _edit: {
44
+ options: ['sm', 'md', 'lg'],
45
+ description: 'Track height.',
46
+ },
47
+ },
48
+ showValue: {
49
+ type: Boolean,
50
+ default: false,
51
+ _edit: { description: 'Show a percentage label beside the title.' },
52
+ },
53
+ showLabel: {
54
+ type: Boolean,
55
+ default: true,
56
+ _edit: { description: 'Show the label above the bar. The label remains accessible when visually hidden.' },
57
+ },
58
+ indeterminate: {
59
+ type: Boolean,
60
+ default: false,
61
+ _edit: { description: 'Show a sweeping loading bar when the total is unknown.' },
62
+ },
63
+ });
64
+
65
+ const isIndeterminate = computed(getIsIndeterminate);
66
+ const safeMax = computed(getSafeMax);
67
+ const clampedValue = computed(getClampedValue);
68
+ const percent = computed(getPercent);
69
+ const barStyle = computed(getBarStyle);
70
+ const trackClass = computed(getTrackClass);
71
+ const barClass = computed(getBarClass);
72
+
73
+ /**
74
+ * Resolves whether progress should use the unknown-total presentation.
75
+ *
76
+ * @returns {boolean} Whether progress is indeterminate.
77
+ */
78
+ function getIsIndeterminate() {
79
+ return props.indeterminate || props.value == null;
80
+ }
81
+
82
+ /**
83
+ * Resolves the maximum progress value while avoiding division by zero.
84
+ *
85
+ * @returns {number} Safe maximum value.
86
+ */
87
+ function getSafeMax() {
88
+ return Math.max(1, Number(props.max) || 100);
89
+ }
90
+
91
+ /**
92
+ * Resolves the current progress value inside the allowed range.
93
+ *
94
+ * @returns {number} Clamped progress value.
95
+ */
96
+ function getClampedValue() {
97
+ const value = Number(props.value);
98
+ if (!Number.isFinite(value)) return 0;
99
+ return Math.min(safeMax.value, Math.max(0, value));
100
+ }
101
+
102
+ /**
103
+ * Converts progress into a percentage.
104
+ *
105
+ * @returns {number} Rounded progress percentage.
106
+ */
107
+ function getPercent() {
108
+ return Math.round((clampedValue.value / safeMax.value) * 100);
109
+ }
110
+
111
+ /**
112
+ * Builds the width and highlight treatment for the active progress bar.
113
+ *
114
+ * @returns {Record<string, string|undefined>} Inline bar style.
115
+ */
116
+ function getBarStyle() {
117
+ return {
118
+ width: isIndeterminate.value ? '30%' : `${percent.value}%`,
119
+ backgroundImage: isIndeterminate.value
120
+ ? 'linear-gradient(90deg, transparent, currentColor, transparent)'
121
+ : undefined,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Builds the track class list for the selected size.
127
+ *
128
+ * @returns {Array<string>} Vue class list.
129
+ */
130
+ function getTrackClass() {
131
+ return [
132
+ 'w-full overflow-hidden rounded-full bg-secondary',
133
+ {
134
+ sm: 'h-1.5',
135
+ md: 'h-2',
136
+ lg: 'h-3',
137
+ }[props.size] || 'h-2',
138
+ ];
139
+ }
140
+
141
+ /**
142
+ * Builds the active bar class list for determinate or indeterminate progress.
143
+ *
144
+ * @returns {Array<string|boolean>} Vue class list.
145
+ */
146
+ function getBarClass() {
147
+ const toneClasses = isIndeterminate.value
148
+ ? {
149
+ primary: 'text-primary',
150
+ success: 'text-success',
151
+ warning: 'text-warning',
152
+ danger: 'text-destructive',
153
+ neutral: 'text-muted-fg',
154
+ }
155
+ : {
156
+ primary: 'bg-primary',
157
+ success: 'bg-success',
158
+ warning: 'bg-warning',
159
+ danger: 'bg-destructive',
160
+ neutral: 'bg-muted-fg',
161
+ };
162
+
163
+ return [
164
+ 'h-full rounded-full transition-[width] duration-300',
165
+ isIndeterminate.value && 'dom-progress-indeterminate',
166
+ toneClasses[props.tone] || toneClasses.primary,
167
+ ];
168
+ }
169
+ </script>
170
+
171
+ <template>
172
+ <div class="w-full">
173
+ <div v-if="(showLabel && label) || showValue" class="mb-2 flex items-center justify-between gap-3 text-sm">
174
+ <span v-if="showLabel && label" class="font-medium text-canvas-fg">{{ label }}</span>
175
+ <span v-if="showValue && !isIndeterminate" class="text-xs font-medium text-muted-fg">{{ percent }}%</span>
176
+ </div>
177
+ <div
178
+ :class="trackClass"
179
+ role="progressbar"
180
+ :aria-label="label"
181
+ :aria-valuemin="0"
182
+ :aria-valuemax="safeMax"
183
+ :aria-valuenow="isIndeterminate ? undefined : clampedValue"
184
+ :aria-valuetext="isIndeterminate ? 'Loading' : `${percent}%`"
185
+ >
186
+ <div :class="barClass" :style="barStyle"></div>
187
+ </div>
188
+ </div>
189
+ </template>
190
+
191
+ <style scoped>
192
+ .dom-progress-indeterminate {
193
+ animation: dom-progress-slide 1.35s ease-in-out infinite;
194
+ }
195
+
196
+ @keyframes dom-progress-slide {
197
+ 0% {
198
+ transform: translateX(-100%);
199
+ }
200
+
201
+ 100% {
202
+ transform: translateX(320%);
203
+ }
204
+ }
205
+
206
+ @media (prefers-reduced-motion: reduce) {
207
+ .dom-progress-indeterminate {
208
+ animation: none;
209
+ transform: translateX(115%);
210
+ }
211
+ }
212
+ </style>