@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,67 @@
1
+ <script setup>
2
+ defineOptions({
3
+ __doc: {
4
+ name: 'App top bar',
5
+ tag: '<DomAppTopBar>',
6
+ description: 'A compact app navigation bar with optional leading and trailing actions.',
7
+ slots: [
8
+ { name: 'leading', description: 'Leading icon button or back control.' },
9
+ { name: 'trailing', description: 'Trailing actions.' },
10
+ ],
11
+ playground: {
12
+ mobileSlot: 'top',
13
+ initial: {
14
+ title: 'Workspace',
15
+ subtitle: 'Capacitor app',
16
+ },
17
+ },
18
+ },
19
+ });
20
+
21
+ defineProps({
22
+ title: {
23
+ type: String,
24
+ default: '',
25
+ _edit: { description: 'Primary title shown in the app bar.' },
26
+ },
27
+ subtitle: {
28
+ type: String,
29
+ default: '',
30
+ _edit: { description: 'Optional supporting text below the title.' },
31
+ },
32
+ border: {
33
+ type: Boolean,
34
+ default: true,
35
+ _edit: { description: 'Show a bottom divider.' },
36
+ },
37
+ large: {
38
+ type: Boolean,
39
+ default: false,
40
+ _edit: { description: 'Use larger title sizing and padding.' },
41
+ },
42
+ });
43
+ </script>
44
+
45
+ <template>
46
+ <header
47
+ class="skin-card px-4"
48
+ :class="[!border && 'border-0', large ? 'py-4' : 'py-3']"
49
+ >
50
+ <div class="flex min-h-11 items-center gap-3">
51
+ <div v-if="$slots.leading" class="flex shrink-0 items-center">
52
+ <slot name="leading" />
53
+ </div>
54
+ <div class="min-w-0 flex-1">
55
+ <h1
56
+ v-if="title"
57
+ class="truncate font-semibold tracking-tight text-canvas-fg"
58
+ :class="large ? 'text-2xl' : 'text-base'"
59
+ >{{ title }}</h1>
60
+ <p v-if="subtitle" class="truncate text-xs text-muted-fg">{{ subtitle }}</p>
61
+ </div>
62
+ <div v-if="$slots.trailing" class="flex shrink-0 items-center gap-1">
63
+ <slot name="trailing" />
64
+ </div>
65
+ </div>
66
+ </header>
67
+ </template>
@@ -0,0 +1,363 @@
1
+ <script setup>
2
+ import { computed, ref, useAttrs } from 'vue';
3
+ import DomIconButton from '../../components/icon-button/DomIconButton.vue';
4
+
5
+ defineOptions({
6
+ inheritAttrs: false,
7
+ __doc: {
8
+ name: 'Alert',
9
+ tag: '<DomAlert>',
10
+ description: 'A persistent inline notice for status messages, policy warnings, empty-flow guidance, and recoverable errors.',
11
+ icon: 'M12 17v-5M12 8h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z',
12
+ playground: {
13
+ initial: {
14
+ tone: 'info',
15
+ variant: 'soft',
16
+ title: 'Information available',
17
+ description: 'Review the latest status details before continuing.',
18
+ },
19
+ },
20
+ slots: [
21
+ { name: 'icon', description: 'Replace the leading status icon. Prefer the icon prop for SVG path data or imported Vue icon components.' },
22
+ { name: 'title', description: 'Replace the title content.' },
23
+ { name: '(default)', description: 'Alert body. Falls back to the description prop.' },
24
+ { name: 'actions', description: 'Action buttons or links aligned below the message.' },
25
+ ],
26
+ events: [
27
+ { name: 'dismiss', payload: 'MouseEvent', description: 'Emitted after a dismissible alert has finished fading away.' },
28
+ ],
29
+ },
30
+ });
31
+
32
+ const props = defineProps({
33
+ as: {
34
+ type: String,
35
+ default: 'div',
36
+ _edit: { description: 'Element to render.' },
37
+ },
38
+ tone: {
39
+ type: String,
40
+ default: 'info',
41
+ _edit: {
42
+ options: ['default', 'info', 'neutral', 'primary', 'success', 'warning', 'danger'],
43
+ description: 'Semantic alert tone.',
44
+ },
45
+ },
46
+ variant: {
47
+ type: String,
48
+ default: 'soft',
49
+ _edit: {
50
+ options: ['soft', 'outline', 'solid', 'toast'],
51
+ description: 'Visual weight.',
52
+ },
53
+ },
54
+ title: {
55
+ type: String,
56
+ default: 'Notice',
57
+ _edit: { description: 'Fallback title when the title slot is empty.' },
58
+ },
59
+ description: {
60
+ type: String,
61
+ default: '',
62
+ _edit: { description: 'Fallback body copy when the default slot is empty.' },
63
+ },
64
+ icon: {
65
+ type: [Boolean, String, Object, Function],
66
+ default: true,
67
+ _edit: {
68
+ component: 'DomIconSelector',
69
+ typeRef: 'boolean | string | Component',
70
+ description: 'Show a tone icon, hide it with false, pass SVG path data, or pass an imported Vue icon component.',
71
+ props: {
72
+ kind: 'icons',
73
+ placeholder: 'Search icons',
74
+ },
75
+ },
76
+ },
77
+ dismissible: {
78
+ type: Boolean,
79
+ default: false,
80
+ _edit: { description: 'Show a close button that fades the alert away when activated.' },
81
+ },
82
+ dismissLabel: {
83
+ type: String,
84
+ default: 'Dismiss alert',
85
+ _edit: { description: 'Accessible label for the optional dismiss button.' },
86
+ },
87
+ role: {
88
+ type: String,
89
+ default: '',
90
+ _edit: { description: 'ARIA role override. Danger and warning default to alert; other tones default to status.' },
91
+ },
92
+ });
93
+ const emit = defineEmits(['dismiss']);
94
+ const attrs = useAttrs();
95
+ const visible = ref(true);
96
+ let dismissEvent = null;
97
+
98
+ const toneIconPaths = {
99
+ default: 'M12 17v-5M12 8h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z',
100
+ info: 'M12 17v-5M12 8h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z',
101
+ neutral: 'M12 17v-5M12 8h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z',
102
+ primary: 'M13 3 4 14h7l-1 7 9-11h-7l1-7Z',
103
+ success: 'M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM8.5 12.5l2.5 2.5 4.5-5',
104
+ warning: 'M12 4l9 16H3L12 4ZM12 9v4M12 17h.01',
105
+ danger: 'M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM15 9l-6 6M9 9l6 6',
106
+ };
107
+
108
+ const toneClasses = {
109
+ default: {
110
+ soft: 'border-border bg-secondary/70 text-canvas-fg',
111
+ outline: 'border-border bg-canvas text-canvas-fg',
112
+ solid: 'border-border bg-secondary text-secondary-fg',
113
+ toast: 'skin-popover border-l-4 border-border text-canvas-fg shadow-md',
114
+ icon: 'text-muted-fg',
115
+ solidIcon: 'text-secondary-fg',
116
+ solidDescription: 'text-secondary-fg/80',
117
+ },
118
+ neutral: {
119
+ soft: 'border-border bg-secondary/70 text-canvas-fg',
120
+ outline: 'border-border bg-canvas text-canvas-fg',
121
+ solid: 'border-border bg-secondary text-secondary-fg',
122
+ toast: 'skin-popover border-l-4 border-border text-canvas-fg shadow-md',
123
+ icon: 'text-muted-fg',
124
+ solidIcon: 'text-secondary-fg',
125
+ solidDescription: 'text-secondary-fg/80',
126
+ },
127
+ primary: {
128
+ soft: 'border-primary/20 bg-primary/10 text-canvas-fg',
129
+ outline: 'border-primary/35 bg-canvas text-canvas-fg',
130
+ solid: 'border-primary bg-primary text-primary-fg',
131
+ toast: 'skin-popover border-l-4 border-primary/35 text-canvas-fg shadow-md',
132
+ icon: 'text-primary',
133
+ solidIcon: 'text-primary-fg',
134
+ solidDescription: 'text-primary-fg/80',
135
+ },
136
+ success: {
137
+ soft: 'border-success/25 bg-success/10 text-canvas-fg',
138
+ outline: 'border-success/35 bg-canvas text-canvas-fg',
139
+ solid: 'border-success bg-success text-success-fg',
140
+ toast: 'skin-popover border-l-4 border-success/35 text-canvas-fg shadow-md',
141
+ icon: 'text-success',
142
+ solidIcon: 'text-success-fg',
143
+ solidDescription: 'text-success-fg/85',
144
+ },
145
+ warning: {
146
+ soft: 'border-warning/30 bg-warning/15 text-canvas-fg',
147
+ outline: 'border-warning/40 bg-canvas text-canvas-fg',
148
+ solid: 'border-warning bg-warning text-warning-fg',
149
+ toast: 'skin-popover border-l-4 border-warning/40 text-canvas-fg shadow-md',
150
+ icon: 'text-warning-fg',
151
+ solidIcon: 'text-warning-fg',
152
+ solidDescription: 'text-warning-fg/75',
153
+ },
154
+ danger: {
155
+ soft: 'border-destructive/25 bg-destructive/10 text-canvas-fg',
156
+ outline: 'border-destructive/40 bg-canvas text-canvas-fg',
157
+ solid: 'border-destructive bg-destructive text-destructive-fg',
158
+ toast: 'skin-popover border-l-4 border-destructive/40 text-canvas-fg shadow-md',
159
+ icon: 'text-destructive',
160
+ solidIcon: 'text-destructive-fg',
161
+ solidDescription: 'text-destructive-fg/85',
162
+ },
163
+ info: {
164
+ soft: 'border-primary/20 bg-primary/10 text-canvas-fg',
165
+ outline: 'border-primary/30 bg-canvas text-canvas-fg',
166
+ solid: 'border-primary bg-primary text-primary-fg',
167
+ toast: 'skin-popover border-l-4 border-primary/30 text-canvas-fg shadow-md',
168
+ icon: 'text-primary',
169
+ solidIcon: 'text-primary-fg',
170
+ solidDescription: 'text-primary-fg/80',
171
+ },
172
+ };
173
+
174
+ const resolvedTone = computed(getResolvedTone);
175
+ const resolvedVariant = computed(getResolvedVariant);
176
+ const classes = computed(getClasses);
177
+ const iconClass = computed(getIconClass);
178
+ const descriptionClass = computed(getDescriptionClass);
179
+ const showIcon = computed(getShowIcon);
180
+ const iconPath = computed(getIconPath);
181
+ const iconComponent = computed(getIconComponent);
182
+ const resolvedRole = computed(getResolvedRole);
183
+
184
+ /**
185
+ * Resolves unknown tones to the general information treatment.
186
+ *
187
+ * @returns {string}
188
+ */
189
+ function getResolvedTone() {
190
+ return toneClasses[props.tone] ? props.tone : 'info';
191
+ }
192
+
193
+ /**
194
+ * Resolves unknown variants to the soft alert treatment.
195
+ *
196
+ * @returns {string}
197
+ */
198
+ function getResolvedVariant() {
199
+ return toneClasses[resolvedTone.value][props.variant] ? props.variant : 'soft';
200
+ }
201
+
202
+ /**
203
+ * Builds the outer alert class list for the active tone and variant.
204
+ *
205
+ * @returns {Array<string>}
206
+ */
207
+ function getClasses() {
208
+ return [
209
+ 'rounded-lg border px-4 py-3 text-sm shadow-sm',
210
+ toneClasses[resolvedTone.value][resolvedVariant.value],
211
+ ];
212
+ }
213
+
214
+ /**
215
+ * Builds the icon class list while preserving solid alert contrast.
216
+ *
217
+ * @returns {Array<string>}
218
+ */
219
+ function getIconClass() {
220
+ const tone = toneClasses[resolvedTone.value];
221
+ return [
222
+ 'mt-0.5 size-5 shrink-0',
223
+ resolvedVariant.value === 'solid' ? tone.solidIcon : tone.icon,
224
+ ];
225
+ }
226
+
227
+ /**
228
+ * Builds the body-copy class list while preserving solid alert contrast.
229
+ *
230
+ * @returns {Array<string>}
231
+ */
232
+ function getDescriptionClass() {
233
+ return [
234
+ 'mt-1 leading-6',
235
+ resolvedVariant.value === 'solid'
236
+ ? toneClasses[resolvedTone.value].solidDescription
237
+ : 'text-muted-fg',
238
+ ];
239
+ }
240
+
241
+ /**
242
+ * Determines whether the leading icon slot or fallback icon should render.
243
+ *
244
+ * @returns {boolean}
245
+ */
246
+ function getShowIcon() {
247
+ return props.icon !== false;
248
+ }
249
+
250
+ /**
251
+ * Resolves SVG path data from an explicit path string or the active tone.
252
+ *
253
+ * @returns {string}
254
+ */
255
+ function getIconPath() {
256
+ if (typeof props.icon === 'string') return props.icon;
257
+ if (iconComponent.value) return '';
258
+ return toneIconPaths[resolvedTone.value] || toneIconPaths.info;
259
+ }
260
+
261
+ /**
262
+ * Resolves imported Vue icon components without treating SVG path strings as component names.
263
+ *
264
+ * @returns {import('vue').Component|null}
265
+ */
266
+ function getIconComponent() {
267
+ if (!props.icon || typeof props.icon === 'boolean' || typeof props.icon === 'string') return null;
268
+ return props.icon;
269
+ }
270
+
271
+ /**
272
+ * Resolves the live-region role from explicit props and the alert tone.
273
+ *
274
+ * @returns {string}
275
+ */
276
+ function getResolvedRole() {
277
+ if (props.role) return props.role;
278
+ return ['danger', 'warning'].includes(resolvedTone.value) ? 'alert' : 'status';
279
+ }
280
+
281
+ /**
282
+ * Starts the dismiss transition while retaining the activation event until the alert has left.
283
+ *
284
+ * @param {MouseEvent} event
285
+ * @returns {void}
286
+ */
287
+ function dismiss(event) {
288
+ if (!props.dismissible || !visible.value) return;
289
+ dismissEvent = event;
290
+ visible.value = false;
291
+ }
292
+
293
+ /**
294
+ * Notifies consumers after the fade completes so parent-owned collections can remove the alert safely.
295
+ *
296
+ * @returns {void}
297
+ */
298
+ function emitDismiss() {
299
+ emit('dismiss', dismissEvent);
300
+ dismissEvent = null;
301
+ }
302
+ </script>
303
+
304
+ <template>
305
+ <Transition name="dom-alert-fade" @after-leave="emitDismiss">
306
+ <component v-if="visible" :is="as" v-bind="attrs" :class="classes" :role="resolvedRole">
307
+ <div class="flex gap-3">
308
+ <slot v-if="showIcon" name="icon">
309
+ <component v-if="iconComponent" :is="iconComponent" :class="iconClass" aria-hidden="true" focusable="false" />
310
+ <svg v-else-if="iconPath" :class="iconClass" viewBox="0 0 24 24" fill="none" aria-hidden="true">
311
+ <path :d="iconPath" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
312
+ </svg>
313
+ </slot>
314
+ <div class="min-w-0 flex-1">
315
+ <p class="font-semibold leading-6">
316
+ <slot name="title">{{ title }}</slot>
317
+ </p>
318
+ <div v-if="$slots.default || description" :class="descriptionClass">
319
+ <slot>{{ description }}</slot>
320
+ </div>
321
+ <div v-if="$slots.actions" class="mt-3 flex flex-wrap gap-2">
322
+ <slot name="actions"></slot>
323
+ </div>
324
+ </div>
325
+ <DomIconButton
326
+ v-if="dismissible"
327
+ class="dom-alert__dismiss -mr-2 -mt-1"
328
+ size="xs"
329
+ :label="dismissLabel"
330
+ icon="M18 6 6 18M6 6l12 12"
331
+ @click="dismiss"
332
+ />
333
+ </div>
334
+ </component>
335
+ </Transition>
336
+ </template>
337
+
338
+ <style scoped>
339
+ .dom-alert-fade-leave-active {
340
+ transition: opacity 180ms ease;
341
+ }
342
+
343
+ .dom-alert-fade-leave-to {
344
+ opacity: 0;
345
+ }
346
+
347
+ .dom-alert__dismiss {
348
+ color: inherit;
349
+ opacity: 0.65;
350
+ }
351
+
352
+ .dom-alert__dismiss:hover {
353
+ background-color: color-mix(in oklab, currentColor 12%, transparent);
354
+ color: inherit;
355
+ opacity: 1;
356
+ }
357
+
358
+ @media (prefers-reduced-motion: reduce) {
359
+ .dom-alert-fade-leave-active {
360
+ transition-duration: 1ms;
361
+ }
362
+ }
363
+ </style>
@@ -0,0 +1,110 @@
1
+ <script setup>
2
+ import { computed, ref, watch } from 'vue';
3
+
4
+ defineOptions({
5
+ __doc: {
6
+ name: 'Avatar',
7
+ tag: '<DomAvatar>',
8
+ description: 'A compact identity primitive for people, teams, workspaces, and accounts.',
9
+ playground: {
10
+ initial: {
11
+ name: 'Mina Patel',
12
+ initials: 'MP',
13
+ src: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&crop=faces&w=96&h=96&q=80',
14
+ size: 'md',
15
+ },
16
+ },
17
+ slots: [
18
+ { name: 'fallback', description: 'Custom fallback content when no image is available.' },
19
+ ],
20
+ },
21
+ });
22
+
23
+ const props = defineProps({
24
+ src: {
25
+ type: String,
26
+ default: '',
27
+ _edit: { description: 'Image URL.' },
28
+ },
29
+ alt: {
30
+ type: String,
31
+ default: '',
32
+ _edit: { description: 'Image alt text. Leave empty when adjacent text already names the person or object.' },
33
+ },
34
+ name: {
35
+ type: String,
36
+ default: '',
37
+ _edit: { description: 'Name used to generate fallback initials.' },
38
+ },
39
+ initials: {
40
+ type: String,
41
+ default: '',
42
+ _edit: { description: 'Explicit fallback initials.' },
43
+ },
44
+ size: {
45
+ type: String,
46
+ default: 'md',
47
+ _edit: { options: ['xs', 'sm', 'md', 'lg', 'xl'], description: 'Avatar size.' },
48
+ },
49
+ shape: {
50
+ type: String,
51
+ default: 'circle',
52
+ _edit: { options: ['circle', 'rounded', 'square'], description: 'Avatar shape.' },
53
+ },
54
+ });
55
+
56
+ const imageFailed = ref(false);
57
+
58
+ watch(() => props.src, () => {
59
+ imageFailed.value = false;
60
+ });
61
+
62
+ const displayInitials = computed(() => {
63
+ if (props.initials) return props.initials.slice(0, 3).toUpperCase();
64
+ return props.name
65
+ .trim()
66
+ .split(/\s+/)
67
+ .filter(Boolean)
68
+ .slice(0, 2)
69
+ .map((part) => part.charAt(0))
70
+ .join('')
71
+ .toUpperCase() || '?';
72
+ });
73
+
74
+ const sizeClasses = {
75
+ xs: 'size-6 text-[10px]',
76
+ sm: 'size-8 text-xs',
77
+ md: 'size-10 text-sm',
78
+ lg: 'size-12 text-base',
79
+ xl: 'size-16 text-lg',
80
+ };
81
+
82
+ const shapeClasses = {
83
+ circle: 'rounded-full',
84
+ rounded: 'rounded-xl',
85
+ square: 'rounded-md',
86
+ };
87
+
88
+ const classes = computed(() => [
89
+ 'relative inline-grid shrink-0 place-items-center overflow-hidden bg-secondary font-semibold text-secondary-fg ring-1 ring-border',
90
+ sizeClasses[props.size] || sizeClasses.md,
91
+ shapeClasses[props.shape] || shapeClasses.circle,
92
+ ]);
93
+
94
+ const showImage = computed(() => props.src && !imageFailed.value);
95
+ </script>
96
+
97
+ <template>
98
+ <span :class="classes">
99
+ <img
100
+ v-if="showImage"
101
+ :src="src"
102
+ :alt="alt"
103
+ class="size-full object-cover"
104
+ @error="imageFailed = true"
105
+ />
106
+ <slot v-else name="fallback">
107
+ <span aria-hidden="true">{{ displayInitials }}</span>
108
+ </slot>
109
+ </span>
110
+ </template>
@@ -0,0 +1,89 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+ import DomAvatar from '../avatar/DomAvatar.vue';
4
+
5
+ defineOptions({
6
+ __doc: {
7
+ name: 'Avatar stack',
8
+ tag: '<DomAvatarStack>',
9
+ description: 'A grouped identity primitive for collaborators, assignees, reviewers, and participants.',
10
+ playground: {
11
+ initial: {
12
+ items: [
13
+ { name: 'Mina Patel', initials: 'MP', src: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
14
+ { name: 'Ari Chen', initials: 'AC', src: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
15
+ { name: 'Noah Kim', initials: 'NK', src: 'https://images.unsplash.com/photo-1519345182560-3f2917c472ef?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
16
+ { name: 'Leah Stone', initials: 'LS', src: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
17
+ { name: 'Theo Grant', initials: 'TG', src: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?auto=format&fit=crop&crop=faces&w=96&h=96&q=80' },
18
+ ],
19
+ max: 4,
20
+ },
21
+ },
22
+ slots: [
23
+ { name: 'avatar', payload: '{ item, index }', description: 'Custom avatar rendering for each visible item.' },
24
+ ],
25
+ },
26
+ });
27
+
28
+ const props = defineProps({
29
+ items: {
30
+ type: Array,
31
+ default: () => [],
32
+ _edit: {
33
+ component: 'DomJsonListInput',
34
+ description: 'People, teams, or workspace records.',
35
+ props: {
36
+ compact: true,
37
+ schema: [
38
+ { key: 'name', label: 'Name', placeholder: 'Ada Lovelace' },
39
+ { key: 'src', label: 'Image URL', type: 'url', placeholder: 'https://...' },
40
+ { key: 'initials', label: 'Initials', placeholder: 'AL' },
41
+ ],
42
+ },
43
+ },
44
+ },
45
+ max: {
46
+ type: Number,
47
+ default: 4,
48
+ _edit: { description: 'Maximum avatars to show before the overflow count.' },
49
+ },
50
+ size: {
51
+ type: String,
52
+ default: 'sm',
53
+ _edit: { options: ['xs', 'sm', 'md', 'lg'], description: 'Avatar size.' },
54
+ },
55
+ label: {
56
+ type: String,
57
+ default: 'Participants',
58
+ _edit: { description: 'Accessible label for the stack.' },
59
+ },
60
+ });
61
+
62
+ const visibleItems = computed(() => props.items.slice(0, Math.max(0, props.max)));
63
+ const overflow = computed(() => Math.max(0, props.items.length - visibleItems.value.length));
64
+ </script>
65
+
66
+ <template>
67
+ <div class="inline-flex items-center -space-x-2" role="group" :aria-label="label">
68
+ <template v-for="(item, index) in visibleItems" :key="item.value || item.name || index">
69
+ <slot name="avatar" :item="item" :index="index">
70
+ <DomAvatar
71
+ :src="item.src || item.avatar"
72
+ :alt="item.alt || ''"
73
+ :name="item.name || item.label"
74
+ :initials="item.initials"
75
+ :size="size"
76
+ class="ring-2 ring-canvas"
77
+ />
78
+ </slot>
79
+ </template>
80
+ <span
81
+ v-if="overflow"
82
+ class="grid shrink-0 place-items-center rounded-full bg-secondary text-xs font-semibold text-secondary-fg ring-2 ring-canvas"
83
+ :class="{ xs: 'size-6 text-[10px]', sm: 'size-8', md: 'size-10', lg: 'size-12' }[size] || 'size-8'"
84
+ :aria-label="`${overflow} more`"
85
+ >
86
+ +{{ overflow }}
87
+ </span>
88
+ </div>
89
+ </template>