@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.
- package/LICENSE.md +109 -0
- package/README.md +44 -0
- package/SKILL.md +83 -0
- package/package.json +73 -0
- package/public/llms.txt +57 -0
- package/public/skills.sh +11 -0
- package/src/pages/_layout/componentCatalog.generated.js +4514 -0
- package/src/pages/_layout/componentInspector.js +60 -0
- package/src/pages/_layout/componentManager.js +192 -0
- package/src/pages/_layout/componentRuntimeRecords.js +49 -0
- package/src/pages/_layout/inspector/componentRegistry.js +639 -0
- package/src/pages/_layout/inspector/serialize.js +134 -0
- package/src/pages/_layout/inspector/useInspector.js +293 -0
- package/src/pages/components/accordion/DomAccordion.vue +39 -0
- package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
- package/src/pages/components/button/DomButton.vue +132 -0
- package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
- package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
- package/src/pages/components/dialog/DomDialog.vue +271 -0
- package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
- package/src/pages/components/dialog/DomDialogStack.vue +289 -0
- package/src/pages/components/dialog/useDialogs.js +253 -0
- package/src/pages/components/drawer/DomDrawer.vue +159 -0
- package/src/pages/components/dropdown/DomDropdown.vue +261 -0
- package/src/pages/components/icon-button/DomIconButton.vue +155 -0
- package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
- package/src/pages/components/menu/DomMenu.vue +359 -0
- package/src/pages/components/menu/DomMenuItem.vue +233 -0
- package/src/pages/components/menu/DomMenuLabel.vue +19 -0
- package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
- package/src/pages/components/menu/menuContext.js +11 -0
- package/src/pages/components/menu/menuPlacement.js +26 -0
- package/src/pages/components/menu/submenu.js +58 -0
- package/src/pages/components/pagination/DomPagination.vue +265 -0
- package/src/pages/components/playground/DomRenderer.vue +48 -0
- package/src/pages/components/popover/DomPopover.vue +244 -0
- package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
- package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
- package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
- package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
- package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
- package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
- package/src/pages/components/stepper/DomStepper.vue +386 -0
- package/src/pages/components/tabs/DomTabs.vue +149 -0
- package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
- package/src/pages/components/toast/DomToastItem.vue +52 -0
- package/src/pages/components/toast/DomToastStack.vue +768 -0
- package/src/pages/components/toast/useToasts.js +237 -0
- package/src/pages/components/toolbar/DomToolbar.vue +158 -0
- package/src/pages/components/tooltip/DomTooltip.vue +69 -0
- package/src/pages/components/tooltip/vDomTooltip.js +106 -0
- package/src/pages/components/tree-view/DomTreeView.vue +961 -0
- package/src/pages/components/tree-view/treeSchemas.js +129 -0
- package/src/pages/components/tree-view/treeUtils.js +228 -0
- package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
- package/src/pages/data/_shared/resourceRecord.js +29 -0
- package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
- package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
- package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
- package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
- package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
- package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
- package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
- package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
- package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
- package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
- package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
- package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
- package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
- package/src/pages/data/kanban/DomKanban.vue +1703 -0
- package/src/pages/data/kanban/kanbanApi.js +80 -0
- package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
- package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
- package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
- package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
- package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
- package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
- package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
- package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
- package/src/pages/forms/_shared/options.js +61 -0
- package/src/pages/forms/_shared/useOptionMenu.js +59 -0
- package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
- package/src/pages/forms/calendar/DomCalendar.vue +442 -0
- package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
- package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
- package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
- package/src/pages/forms/color-input/DomColorInput.vue +118 -0
- package/src/pages/forms/combobox/DomCombobox.vue +237 -0
- package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
- package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
- package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
- package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
- package/src/pages/forms/field/DomField.vue +267 -0
- package/src/pages/forms/field/fieldLayout.js +1 -0
- package/src/pages/forms/field/fieldProps.js +114 -0
- package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
- package/src/pages/forms/field/useField.js +343 -0
- package/src/pages/forms/field/validators.js +364 -0
- package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
- package/src/pages/forms/form/DomForm.vue +985 -0
- package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
- package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
- package/src/pages/forms/form/adapters/shared.js +53 -0
- package/src/pages/forms/form/adapters/zod.js +235 -0
- package/src/pages/forms/form/formApi.js +38 -0
- package/src/pages/forms/form/formDefinition.js +392 -0
- package/src/pages/forms/form/schemaAdapter.js +54 -0
- package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
- package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
- package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
- package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
- package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
- package/src/pages/forms/listbox/DomListbox.vue +105 -0
- package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
- package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
- package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
- package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
- package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
- package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
- package/src/pages/forms/password-strength/passwordStrength.js +92 -0
- package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
- package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
- package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
- package/src/pages/forms/select/DomSelect.vue +479 -0
- package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
- package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
- package/src/pages/forms/text-input/DomTextInput.vue +48 -0
- package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
- package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
- package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
- package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
- package/src/pages/forms/time-selector/timeUtils.js +194 -0
- package/src/pages/forms/toggle/DomToggle.vue +158 -0
- package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
- package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
- package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
- package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
- package/src/pages/lib/css/color.js +245 -0
- package/src/pages/lib/headless/accordion.d.ts +7 -0
- package/src/pages/lib/headless/accordion.js +246 -0
- package/src/pages/lib/headless/autocomplete.d.ts +4 -0
- package/src/pages/lib/headless/autocomplete.js +33 -0
- package/src/pages/lib/headless/base.d.ts +25 -0
- package/src/pages/lib/headless/base.js +127 -0
- package/src/pages/lib/headless/checkbox.d.ts +19 -0
- package/src/pages/lib/headless/checkbox.js +99 -0
- package/src/pages/lib/headless/combobox.d.ts +7 -0
- package/src/pages/lib/headless/combobox.js +488 -0
- package/src/pages/lib/headless/dialog.d.ts +23 -0
- package/src/pages/lib/headless/dialog.js +384 -0
- package/src/pages/lib/headless/drawer.d.ts +21 -0
- package/src/pages/lib/headless/drawer.js +682 -0
- package/src/pages/lib/headless/dropdown.d.ts +19 -0
- package/src/pages/lib/headless/dropdown.js +412 -0
- package/src/pages/lib/headless/floating-effect.js +118 -0
- package/src/pages/lib/headless/floating.d.ts +34 -0
- package/src/pages/lib/headless/floating.js +382 -0
- package/src/pages/lib/headless/index.d.ts +19 -0
- package/src/pages/lib/headless/index.js +19 -0
- package/src/pages/lib/headless/listbox.d.ts +19 -0
- package/src/pages/lib/headless/listbox.js +126 -0
- package/src/pages/lib/headless/menu.d.ts +18 -0
- package/src/pages/lib/headless/menu.js +117 -0
- package/src/pages/lib/headless/popover-panel.d.ts +18 -0
- package/src/pages/lib/headless/popover-panel.js +309 -0
- package/src/pages/lib/headless/popover.d.ts +19 -0
- package/src/pages/lib/headless/popover.js +391 -0
- package/src/pages/lib/headless/radio-group.d.ts +19 -0
- package/src/pages/lib/headless/radio-group.js +100 -0
- package/src/pages/lib/headless/tabs.d.ts +18 -0
- package/src/pages/lib/headless/tabs.js +119 -0
- package/src/pages/lib/headless/toast.d.ts +13 -0
- package/src/pages/lib/headless/toast.js +65 -0
- package/src/pages/lib/headless/toggle.d.ts +19 -0
- package/src/pages/lib/headless/toggle.js +162 -0
- package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
- package/src/pages/lib/headless/tooltip-controller.js +549 -0
- package/src/pages/lib/headless/tooltip.d.ts +19 -0
- package/src/pages/lib/headless/tooltip.js +153 -0
- package/src/pages/lib/headless/transitions.d.ts +5 -0
- package/src/pages/lib/headless/transitions.js +89 -0
- package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
- package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
- package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
- package/src/pages/lib/vue/codeHighlighter.js +132 -0
- package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
- package/src/pages/lib/vue/index.d.ts +6806 -0
- package/src/pages/lib/vue/index.js +218 -0
- package/src/pages/lib/vue/useTheme.js +116 -0
- package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
- package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
- package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
- package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
- package/src/pages/mobile/shell/DomAppShell.vue +51 -0
- package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
- package/src/pages/mobile/stack/DomAppStack.vue +711 -0
- package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
- package/src/pages/visual/alert/DomAlert.vue +363 -0
- package/src/pages/visual/avatar/DomAvatar.vue +110 -0
- package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
- package/src/pages/visual/badge/DomBadge.vue +105 -0
- package/src/pages/visual/card/DomCard.vue +65 -0
- package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
- package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
- package/src/pages/visual/progress/DomProgress.vue +212 -0
- package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
- package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
- package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
- package/src/pages/visual/spinner/DomSpinner.vue +122 -0
- package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
- package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
- package/src/pages/visual/text-diff/textDiff.js +300 -0
- package/src/style.css +446 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {string | ((row: Record<string, unknown>, index: number) => unknown)} ResourceFieldResolver
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {string | ((row: Record<string, unknown>, index: number) => string | number)} ResourceKeyResolver
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} ActivityTimelineRecord
|
|
12
|
+
* @property {Record<string, unknown>} row Original application-owned resource row.
|
|
13
|
+
* @property {string|number} rowKey Stable row identity.
|
|
14
|
+
* @property {number} index Source row index.
|
|
15
|
+
* @property {unknown} title Resolved primary label.
|
|
16
|
+
* @property {unknown} description Resolved supporting content.
|
|
17
|
+
* @property {unknown} timestamp Resolved raw timestamp or date label.
|
|
18
|
+
* @property {string} formattedTimestamp Display-ready timestamp.
|
|
19
|
+
* @property {unknown} meta Resolved actor, source, or secondary metadata.
|
|
20
|
+
* @property {unknown} status Resolved workflow status.
|
|
21
|
+
* @property {string} statusLabel Human-readable workflow status.
|
|
22
|
+
* @property {string} tone Normalized semantic tone.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export const activityTimelineProps = {
|
|
26
|
+
rows: {
|
|
27
|
+
type: Array,
|
|
28
|
+
default: () => [],
|
|
29
|
+
_edit: { description: 'Application-owned resource rows in the exact order they should appear.' },
|
|
30
|
+
},
|
|
31
|
+
rowKey: {
|
|
32
|
+
type: [String, Function],
|
|
33
|
+
default: 'id',
|
|
34
|
+
_edit: { typeRef: 'ResourceKeyResolver', description: 'Logical field name or function used to identify each resource row.' },
|
|
35
|
+
},
|
|
36
|
+
titleKey: {
|
|
37
|
+
type: [String, Function],
|
|
38
|
+
default: 'title',
|
|
39
|
+
_edit: { typeRef: 'ResourceFieldResolver', group: 'Fields', description: 'Logical field name or function used for the primary activity label.' },
|
|
40
|
+
},
|
|
41
|
+
descriptionKey: {
|
|
42
|
+
type: [String, Function],
|
|
43
|
+
default: 'description',
|
|
44
|
+
_edit: { typeRef: 'ResourceFieldResolver', group: 'Fields', description: 'Logical field name or function used for supporting activity content.' },
|
|
45
|
+
},
|
|
46
|
+
timestampKey: {
|
|
47
|
+
type: [String, Function],
|
|
48
|
+
default: 'createdAt',
|
|
49
|
+
_edit: { typeRef: 'ResourceFieldResolver', group: 'Fields', description: 'Logical field name or function used for the activity timestamp.' },
|
|
50
|
+
},
|
|
51
|
+
metaKey: {
|
|
52
|
+
type: [String, Function],
|
|
53
|
+
default: 'actor',
|
|
54
|
+
_edit: { typeRef: 'ResourceFieldResolver', group: 'Fields', description: 'Logical field name or function used for actor, source, or secondary metadata.' },
|
|
55
|
+
},
|
|
56
|
+
statusKey: {
|
|
57
|
+
type: [String, Function],
|
|
58
|
+
default: 'status',
|
|
59
|
+
_edit: { typeRef: 'ResourceFieldResolver', group: 'Fields', description: 'Logical field name or function used for an optional status pill.' },
|
|
60
|
+
},
|
|
61
|
+
toneKey: {
|
|
62
|
+
type: [String, Function],
|
|
63
|
+
default: 'tone',
|
|
64
|
+
_edit: { typeRef: 'ResourceFieldResolver', group: 'Fields', description: 'Logical field name or function resolving to a semantic marker and status tone.' },
|
|
65
|
+
},
|
|
66
|
+
formatTimestamp: {
|
|
67
|
+
type: Function,
|
|
68
|
+
default: null,
|
|
69
|
+
_edit: { group: 'Fields', description: 'Optional formatter receiving the raw timestamp, row, and source index.' },
|
|
70
|
+
},
|
|
71
|
+
layout: {
|
|
72
|
+
type: String,
|
|
73
|
+
default: 'stacked',
|
|
74
|
+
_edit: { options: ['stacked', 'split'], group: 'Layout', description: 'Place timestamp metadata above the content or in a separate desktop column.' },
|
|
75
|
+
},
|
|
76
|
+
density: {
|
|
77
|
+
type: String,
|
|
78
|
+
default: 'comfortable',
|
|
79
|
+
_edit: { options: ['compact', 'comfortable'], group: 'Layout', description: 'Spacing and marker scale for the timeline.' },
|
|
80
|
+
},
|
|
81
|
+
loading: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: false,
|
|
84
|
+
_edit: { group: 'State', description: 'Expose busy state and show timeline-shaped skeleton rows when no rows are available.' },
|
|
85
|
+
},
|
|
86
|
+
loadingRows: {
|
|
87
|
+
type: Number,
|
|
88
|
+
default: 4,
|
|
89
|
+
_edit: { group: 'State', description: 'Number of placeholder rows shown during an initial load.' },
|
|
90
|
+
},
|
|
91
|
+
emptyTitle: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: 'No activity yet',
|
|
94
|
+
_edit: { group: 'Labels', description: 'Heading shown when the supplied resource collection is empty.' },
|
|
95
|
+
},
|
|
96
|
+
emptyDescription: {
|
|
97
|
+
type: String,
|
|
98
|
+
default: 'Events will appear here as this resource changes.',
|
|
99
|
+
_edit: { group: 'Labels', description: 'Supporting copy shown for an empty activity collection.' },
|
|
100
|
+
},
|
|
101
|
+
ariaLabel: {
|
|
102
|
+
type: String,
|
|
103
|
+
default: 'Activity timeline',
|
|
104
|
+
_edit: { group: 'Labels', description: 'Accessible label for the ordered activity list.' },
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<script setup>
|
|
110
|
+
import { computed } from 'vue';
|
|
111
|
+
import DomEmptyState from '../../visual/empty-state/DomEmptyState.vue';
|
|
112
|
+
import DomSkeleton from '../../visual/skeleton/DomSkeleton.vue';
|
|
113
|
+
import DomStatusPill from '../../visual/status-pill/DomStatusPill.vue';
|
|
114
|
+
import { resolveResourceKey, resolveResourceValue } from '../_shared/resourceRecord.js';
|
|
115
|
+
|
|
116
|
+
defineOptions({
|
|
117
|
+
__doc: {
|
|
118
|
+
name: 'Activity timeline',
|
|
119
|
+
tag: '<DomActivityTimeline>',
|
|
120
|
+
description: 'Projects application-owned resource rows into an accessible ordered activity trail without owning loading, ordering, querying, or persistence.',
|
|
121
|
+
icon: 'M6 5h.01M6 12h.01M6 19h.01M10 5h8M10 12h8M10 19h8M6 6v5M6 13v5',
|
|
122
|
+
events: [],
|
|
123
|
+
slots: [
|
|
124
|
+
{ name: 'item', payload: 'ActivityTimelineSlotPayload', typeRef: 'ActivityTimelineSlotPayload', description: 'Replace the default title, description, status, and metadata content while retaining timeline structure.' },
|
|
125
|
+
{ name: 'marker', payload: 'ActivityTimelineSlotPayload', typeRef: 'ActivityTimelineSlotPayload', description: 'Replace the semantic-tone marker for one activity row.' },
|
|
126
|
+
{ name: 'opposite', payload: 'ActivityTimelineSlotPayload', typeRef: 'ActivityTimelineSlotPayload', description: 'Replace timestamp and secondary metadata, including the separate column used by split layout.' },
|
|
127
|
+
{ name: 'actions', payload: 'ActivityTimelineSlotPayload', typeRef: 'ActivityTimelineSlotPayload', description: 'Append application-owned controls to the default activity content.' },
|
|
128
|
+
{ name: 'loading', payload: '{ count }', description: 'Replace the initial loading placeholders.' },
|
|
129
|
+
{ name: 'empty', description: 'Replace the empty collection state.' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const props = defineProps(activityTimelineProps);
|
|
135
|
+
|
|
136
|
+
const sourceRows = computed(getSourceRows);
|
|
137
|
+
const records = computed(getActivityRecords);
|
|
138
|
+
const loadingCount = computed(getLoadingCount);
|
|
139
|
+
const listClass = computed(getListClass);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Normalizes the application-owned collection without changing row identity.
|
|
143
|
+
*
|
|
144
|
+
* @returns {Array<Record<string, unknown>>} Safe resource-row collection.
|
|
145
|
+
*/
|
|
146
|
+
function getSourceRows() {
|
|
147
|
+
return Array.isArray(props.rows) ? props.rows : [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Creates presentation records while retaining every original resource row.
|
|
152
|
+
*
|
|
153
|
+
* @returns {Array<ActivityTimelineRecord>} Timeline presentation records.
|
|
154
|
+
*/
|
|
155
|
+
function getActivityRecords() {
|
|
156
|
+
return sourceRows.value.map(createActivityRecord);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Resolves one resource row into the stable slot and default-renderer contract.
|
|
161
|
+
*
|
|
162
|
+
* @param {Record<string, unknown>} row Application-owned resource row.
|
|
163
|
+
* @param {number} index Source row index.
|
|
164
|
+
* @returns {ActivityTimelineRecord} Resolved timeline presentation record.
|
|
165
|
+
*/
|
|
166
|
+
function createActivityRecord(row, index) {
|
|
167
|
+
const timestamp = resolveResourceValue(row, props.timestampKey, index);
|
|
168
|
+
const status = resolveResourceValue(row, props.statusKey, index);
|
|
169
|
+
return {
|
|
170
|
+
row,
|
|
171
|
+
rowKey: resolveResourceKey(row, props.rowKey, index),
|
|
172
|
+
index,
|
|
173
|
+
title: resolveResourceValue(row, props.titleKey, index),
|
|
174
|
+
description: resolveResourceValue(row, props.descriptionKey, index),
|
|
175
|
+
timestamp,
|
|
176
|
+
formattedTimestamp: formatResolvedTimestamp(timestamp, row, index),
|
|
177
|
+
meta: resolveResourceValue(row, props.metaKey, index),
|
|
178
|
+
status,
|
|
179
|
+
statusLabel: labelFromValue(status),
|
|
180
|
+
tone: normalizeTone(resolveResourceValue(row, props.toneKey, index)),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Formats a timestamp through the consumer hook or a safe string fallback.
|
|
186
|
+
*
|
|
187
|
+
* @param {unknown} value Raw timestamp or display label.
|
|
188
|
+
* @param {Record<string, unknown>} row Source resource row.
|
|
189
|
+
* @param {number} index Source row index.
|
|
190
|
+
* @returns {string} Display-ready timestamp.
|
|
191
|
+
*/
|
|
192
|
+
function formatResolvedTimestamp(value, row, index) {
|
|
193
|
+
if (typeof props.formatTimestamp === 'function') {
|
|
194
|
+
const formatted = props.formatTimestamp(value, row, index);
|
|
195
|
+
return formatted === null || formatted === undefined ? '' : String(formatted);
|
|
196
|
+
}
|
|
197
|
+
return value === null || value === undefined ? '' : String(value);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Converts a stored status value into a compact human-readable label.
|
|
202
|
+
*
|
|
203
|
+
* @param {unknown} value Raw status value.
|
|
204
|
+
* @returns {string} Human-readable status label.
|
|
205
|
+
*/
|
|
206
|
+
function labelFromValue(value) {
|
|
207
|
+
if (value === null || value === undefined || value === '') return '';
|
|
208
|
+
return String(value)
|
|
209
|
+
.replace(/[_-]+/g, ' ')
|
|
210
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
211
|
+
.replace(/^\w/, capitalizeFirstLetter);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Uppercases the leading character in a generated status label.
|
|
216
|
+
*
|
|
217
|
+
* @param {string} character Matched leading character.
|
|
218
|
+
* @returns {string} Uppercase character.
|
|
219
|
+
*/
|
|
220
|
+
function capitalizeFirstLetter(character) {
|
|
221
|
+
return character.toUpperCase();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Restricts resource-provided tone values to DOM Studio semantic tones.
|
|
226
|
+
*
|
|
227
|
+
* @param {unknown} value Candidate tone value.
|
|
228
|
+
* @returns {'neutral'|'primary'|'success'|'warning'|'danger'|'info'} Semantic tone.
|
|
229
|
+
*/
|
|
230
|
+
function normalizeTone(value) {
|
|
231
|
+
const tone = String(value || 'neutral');
|
|
232
|
+
return ['neutral', 'primary', 'success', 'warning', 'danger', 'info'].includes(tone)
|
|
233
|
+
? tone
|
|
234
|
+
: 'neutral';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Builds semantic marker classes for one resolved activity tone.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} tone Normalized semantic tone.
|
|
241
|
+
* @returns {string} Tailwind marker classes.
|
|
242
|
+
*/
|
|
243
|
+
function markerClass(tone) {
|
|
244
|
+
return {
|
|
245
|
+
neutral: 'border-border bg-canvas text-muted-fg',
|
|
246
|
+
primary: 'border-primary/35 bg-primary/15 text-primary',
|
|
247
|
+
success: 'border-success/35 bg-success/15 text-success',
|
|
248
|
+
warning: 'border-warning/40 bg-warning/15 text-warning',
|
|
249
|
+
danger: 'border-destructive/35 bg-destructive/15 text-destructive',
|
|
250
|
+
info: 'border-primary/35 bg-primary/15 text-primary',
|
|
251
|
+
}[tone] || 'border-border bg-canvas text-muted-fg';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Clamps initial skeleton count to a small useful range.
|
|
256
|
+
*
|
|
257
|
+
* @returns {number} Number of loading records to render.
|
|
258
|
+
*/
|
|
259
|
+
function getLoadingCount() {
|
|
260
|
+
return Math.min(12, Math.max(1, Number(props.loadingRows) || 1));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Builds layout-aware classes for the ordered list.
|
|
265
|
+
*
|
|
266
|
+
* @returns {Array<string>} Timeline list classes.
|
|
267
|
+
*/
|
|
268
|
+
function getListClass() {
|
|
269
|
+
return [
|
|
270
|
+
'grid',
|
|
271
|
+
props.density === 'compact' ? 'gap-0' : 'gap-1',
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Builds responsive grid classes for one activity row.
|
|
277
|
+
*
|
|
278
|
+
* @returns {string} Activity row grid classes.
|
|
279
|
+
*/
|
|
280
|
+
function itemGridClass() {
|
|
281
|
+
if (props.layout === 'split') {
|
|
282
|
+
return 'grid-cols-[1.5rem_minmax(0,1fr)] md:grid-cols-[9rem_1.5rem_minmax(0,1fr)]';
|
|
283
|
+
}
|
|
284
|
+
return 'grid-cols-[1.5rem_minmax(0,1fr)]';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Positions timestamp metadata without rendering responsive slot duplicates.
|
|
289
|
+
*
|
|
290
|
+
* @returns {string} Opposite-column layout classes.
|
|
291
|
+
*/
|
|
292
|
+
function oppositeClass() {
|
|
293
|
+
const shared = 'col-start-2 row-start-1 mb-1 min-w-0 text-xs text-muted-fg';
|
|
294
|
+
if (props.layout === 'split') {
|
|
295
|
+
return `${shared} md:col-start-1 md:mb-0 md:pr-4 md:text-right`;
|
|
296
|
+
}
|
|
297
|
+
return shared;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Positions the timeline rail across metadata and content rows.
|
|
302
|
+
*
|
|
303
|
+
* @returns {string} Marker-column layout classes.
|
|
304
|
+
*/
|
|
305
|
+
function markerColumnClass() {
|
|
306
|
+
return props.layout === 'split'
|
|
307
|
+
? 'col-start-1 row-span-2 row-start-1 md:col-start-2'
|
|
308
|
+
: 'col-start-1 row-span-2 row-start-1';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Builds spacing classes for the default or slotted activity content.
|
|
313
|
+
*
|
|
314
|
+
* @returns {string} Content spacing classes.
|
|
315
|
+
*/
|
|
316
|
+
function contentClass() {
|
|
317
|
+
const spacing = props.density === 'compact' ? 'pb-4 pl-3' : 'pb-7 pl-4';
|
|
318
|
+
const position = props.layout === 'split'
|
|
319
|
+
? 'col-start-2 row-start-2 md:col-start-3 md:row-start-1'
|
|
320
|
+
: 'col-start-2 row-start-2';
|
|
321
|
+
return `${spacing} ${position}`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Builds marker dimensions that match the selected density.
|
|
326
|
+
*
|
|
327
|
+
* @returns {string} Marker size classes.
|
|
328
|
+
*/
|
|
329
|
+
function markerSizeClass() {
|
|
330
|
+
return props.density === 'compact' ? 'mt-0.5 size-4' : 'mt-0.5 size-5';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Builds connector offsets that continue cleanly into the next row.
|
|
335
|
+
*
|
|
336
|
+
* @returns {string} Connector position classes.
|
|
337
|
+
*/
|
|
338
|
+
function connectorClass() {
|
|
339
|
+
return props.density === 'compact' ? 'top-4 bottom-0' : 'top-5 -bottom-1';
|
|
340
|
+
}
|
|
341
|
+
</script>
|
|
342
|
+
|
|
343
|
+
<template>
|
|
344
|
+
<div :aria-busy="loading ? 'true' : 'false'">
|
|
345
|
+
<slot v-if="loading && !records.length" name="loading" :count="loadingCount">
|
|
346
|
+
<ol class="grid gap-0" :aria-label="ariaLabel">
|
|
347
|
+
<li v-for="index in loadingCount" :key="index" class="grid grid-cols-[1.5rem_minmax(0,1fr)]">
|
|
348
|
+
<div class="relative flex justify-center">
|
|
349
|
+
<span v-if="index < loadingCount" class="absolute top-4 bottom-0 w-px bg-border" aria-hidden="true"></span>
|
|
350
|
+
<DomSkeleton variant="circle" class="relative z-10 mt-0.5" width="1rem" height="1rem" />
|
|
351
|
+
</div>
|
|
352
|
+
<div class="pb-4 pl-3">
|
|
353
|
+
<DomSkeleton variant="text" :lines="2" width="min(100%, 34rem)" />
|
|
354
|
+
</div>
|
|
355
|
+
</li>
|
|
356
|
+
</ol>
|
|
357
|
+
</slot>
|
|
358
|
+
|
|
359
|
+
<slot v-else-if="!records.length" name="empty">
|
|
360
|
+
<DomEmptyState :title="emptyTitle" :description="emptyDescription" size="sm" />
|
|
361
|
+
</slot>
|
|
362
|
+
|
|
363
|
+
<ol v-else :class="listClass" :aria-label="ariaLabel">
|
|
364
|
+
<li
|
|
365
|
+
v-for="(record, index) in records"
|
|
366
|
+
:key="record.rowKey"
|
|
367
|
+
class="relative grid min-w-0"
|
|
368
|
+
:class="itemGridClass()"
|
|
369
|
+
:data-row-key="record.rowKey"
|
|
370
|
+
>
|
|
371
|
+
<div :class="oppositeClass()">
|
|
372
|
+
<slot name="opposite" v-bind="record">
|
|
373
|
+
<span v-if="record.formattedTimestamp" class="font-medium text-canvas-fg">{{ record.formattedTimestamp }}</span>
|
|
374
|
+
<span v-if="record.formattedTimestamp && record.meta" class="mx-1" :class="layout === 'split' ? 'md:hidden' : ''" aria-hidden="true">·</span>
|
|
375
|
+
<span v-if="record.meta" class="truncate" :class="layout === 'split' ? 'md:mt-1 md:block' : ''">{{ record.meta }}</span>
|
|
376
|
+
</slot>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<div class="relative flex justify-center" :class="markerColumnClass()">
|
|
380
|
+
<span
|
|
381
|
+
v-if="index < records.length - 1"
|
|
382
|
+
class="absolute w-px bg-border"
|
|
383
|
+
:class="connectorClass()"
|
|
384
|
+
aria-hidden="true"
|
|
385
|
+
></span>
|
|
386
|
+
<slot name="marker" v-bind="record">
|
|
387
|
+
<span
|
|
388
|
+
class="relative z-10 grid place-items-center rounded-full border"
|
|
389
|
+
:class="[markerSizeClass(), markerClass(record.tone)]"
|
|
390
|
+
aria-hidden="true"
|
|
391
|
+
>
|
|
392
|
+
<span class="size-1.5 rounded-full bg-current"></span>
|
|
393
|
+
</span>
|
|
394
|
+
</slot>
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
<article class="min-w-0" :class="contentClass()">
|
|
398
|
+
<slot name="item" v-bind="record">
|
|
399
|
+
<div class="flex min-w-0 flex-wrap items-start justify-between gap-2">
|
|
400
|
+
<h3 class="min-w-0 flex-1 text-sm font-semibold leading-5 text-canvas-fg">{{ record.title || 'Untitled activity' }}</h3>
|
|
401
|
+
<DomStatusPill
|
|
402
|
+
v-if="record.statusLabel"
|
|
403
|
+
:tone="record.tone"
|
|
404
|
+
:label="record.statusLabel"
|
|
405
|
+
size="sm"
|
|
406
|
+
/>
|
|
407
|
+
</div>
|
|
408
|
+
<p v-if="record.description" class="mt-1 text-sm leading-6 text-muted-fg">{{ record.description }}</p>
|
|
409
|
+
<div v-if="$slots.actions" class="mt-3 flex flex-wrap gap-2">
|
|
410
|
+
<slot name="actions" v-bind="record" />
|
|
411
|
+
</div>
|
|
412
|
+
</slot>
|
|
413
|
+
</article>
|
|
414
|
+
</li>
|
|
415
|
+
</ol>
|
|
416
|
+
</div>
|
|
417
|
+
</template>
|