@adia-ai/web-modules 0.8.35 → 0.8.37
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/CHANGELOG.md +22 -0
- package/README.md +3 -3
- package/agent-admin/admin-roster/admin-roster.js +1 -1
- package/agent-admin/admin-settings/admin-settings.js +1 -1
- package/agent-admin/agent-admin/agent-admin.js +1 -1
- package/billing/billing-overview/billing-overview.a2ui.json +281 -0
- package/billing/billing-overview/billing-overview.class.js +838 -0
- package/billing/billing-overview/billing-overview.css +223 -0
- package/billing/billing-overview/billing-overview.js +16 -0
- package/billing/billing-overview/billing-overview.yaml +441 -0
- package/billing/index.js +21 -0
- package/billing/invoice-detail/invoice-detail.a2ui.json +276 -0
- package/billing/invoice-detail/invoice-detail.class.js +711 -0
- package/billing/invoice-detail/invoice-detail.css +305 -0
- package/billing/invoice-detail/invoice-detail.js +16 -0
- package/billing/invoice-detail/invoice-detail.yaml +411 -0
- package/billing/invoice-history/invoice-history.a2ui.json +315 -0
- package/billing/invoice-history/invoice-history.class.js +672 -0
- package/billing/invoice-history/invoice-history.css +130 -0
- package/billing/invoice-history/invoice-history.js +16 -0
- package/billing/invoice-history/invoice-history.yaml +398 -0
- package/billing/payment-method-form/payment-method-form.a2ui.json +310 -0
- package/billing/payment-method-form/payment-method-form.class.js +886 -0
- package/billing/payment-method-form/payment-method-form.css +149 -0
- package/billing/payment-method-form/payment-method-form.js +16 -0
- package/billing/payment-method-form/payment-method-form.yaml +430 -0
- package/billing/payment-method-list/payment-method-list.a2ui.json +283 -0
- package/billing/payment-method-list/payment-method-list.class.js +705 -0
- package/billing/payment-method-list/payment-method-list.css +128 -0
- package/billing/payment-method-list/payment-method-list.js +16 -0
- package/billing/payment-method-list/payment-method-list.yaml +355 -0
- package/billing/plan-picker/plan-picker.a2ui.json +349 -0
- package/billing/plan-picker/plan-picker.class.js +635 -0
- package/billing/plan-picker/plan-picker.css +227 -0
- package/billing/plan-picker/plan-picker.js +16 -0
- package/billing/plan-picker/plan-picker.yaml +453 -0
- package/chat/README.md +1 -1
- package/chat/chat-composer/chat-composer.js +2 -2
- package/chat/chat-empty/chat-empty.js +2 -2
- package/chat/chat-shell/chat-shell.js +4 -4
- package/chat/chat-sidebar/chat-sidebar.js +2 -2
- package/chat/chat-surfaces/chat-surfaces.js +2 -2
- package/chat/chat-thread/chat-thread.js +2 -2
- package/dashboard/dashboard-layout/dashboard-layout.a2ui.json +254 -0
- package/dashboard/dashboard-layout/dashboard-layout.css +190 -0
- package/dashboard/dashboard-layout/dashboard-layout.js +289 -0
- package/dashboard/dashboard-layout/dashboard-layout.yaml +374 -0
- package/dashboard/date-range-selector/date-range-selector.a2ui.json +236 -0
- package/dashboard/date-range-selector/date-range-selector.css +84 -0
- package/dashboard/date-range-selector/date-range-selector.js +686 -0
- package/dashboard/date-range-selector/date-range-selector.yaml +358 -0
- package/dashboard/index.js +14 -0
- package/dist/everything.min.js +1 -1
- package/editor/editor-canvas/editor-canvas.js +2 -2
- package/editor/editor-shell/editor-shell.js +2 -2
- package/editor/editor-sidebar/editor-sidebar.js +2 -2
- package/editor/editor-toolbar/editor-toolbar.js +2 -2
- package/feedback/confirm-dialog/confirm-dialog.a2ui.json +206 -0
- package/feedback/confirm-dialog/confirm-dialog.css +157 -0
- package/feedback/confirm-dialog/confirm-dialog.js +383 -0
- package/feedback/confirm-dialog/confirm-dialog.yaml +297 -0
- package/feedback/index.js +10 -0
- package/form/form-popover/form-popover.js +1 -1
- package/generative/index.d.ts +1 -1
- package/generative/index.js +2 -2
- package/onboarding/index.js +1 -0
- package/onboarding/onboarding-checklist/onboarding-checklist.a2ui.json +263 -0
- package/onboarding/onboarding-checklist/onboarding-checklist.css +157 -0
- package/onboarding/onboarding-checklist/onboarding-checklist.js +428 -0
- package/onboarding/onboarding-checklist/onboarding-checklist.yaml +236 -0
- package/package.json +99 -6
- package/runtime/a2ui-root/a2ui-root.js +3 -3
- package/runtime/gen-root/gen-root.js +5 -5
- package/settings/index.js +12 -0
- package/settings/integrations-page/integrations-page.a2ui.json +274 -0
- package/settings/integrations-page/integrations-page.css +177 -0
- package/settings/integrations-page/integrations-page.d.ts +77 -0
- package/settings/integrations-page/integrations-page.js +413 -0
- package/settings/integrations-page/integrations-page.yaml +317 -0
- package/settings/notification-preferences/notification-preferences.a2ui.json +245 -0
- package/settings/notification-preferences/notification-preferences.css +204 -0
- package/settings/notification-preferences/notification-preferences.js +622 -0
- package/settings/notification-preferences/notification-preferences.yaml +358 -0
- package/shell/admin-command/admin-command.js +2 -2
- package/shell/admin-shell/admin-shell.js +2 -2
- package/shell/admin-sidebar/admin-sidebar.js +2 -2
- package/shell/embed-shell/embed-shell.js +3 -3
- package/simple/simple-shell/simple-shell.js +1 -1
- package/theme/theme-panel/theme-panel.js +2 -2
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <onboarding-checklist-ui
|
|
3
|
+
* storage-key="acme:onboarding:v1"
|
|
4
|
+
* items='[{"id":"profile","label":"Complete your profile","href":"/settings/profile"}, …]'>
|
|
5
|
+
* <button-ui slot="footer" variant="ghost" text="Hide for now"></button-ui>
|
|
6
|
+
* </onboarding-checklist-ui>
|
|
7
|
+
*
|
|
8
|
+
* Module-tier onboarding checklist — composes <progress-ui> + <list-ui> +
|
|
9
|
+
* <list-item-ui> + <check-ui> + <button-ui>. Data-driven (items prop),
|
|
10
|
+
* persistent (storage-key), auto-dismissing on completion.
|
|
11
|
+
*
|
|
12
|
+
* Per ADR-0027 — composites that programmatically create primitives do
|
|
13
|
+
* NOT auto-import those primitives. The consumer (app shell or page)
|
|
14
|
+
* must explicitly import <progress-ui>, <list-ui>, <check-ui>,
|
|
15
|
+
* <button-ui> in addition to this module.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { UIElement, html } from '@adia-ai/web-components/core/element';
|
|
19
|
+
|
|
20
|
+
class UIOnboardingChecklist extends UIElement {
|
|
21
|
+
static properties = {
|
|
22
|
+
items: { type: Array, default: [] },
|
|
23
|
+
'storage-key': { type: String, default: '', reflect: true },
|
|
24
|
+
storage: { type: String, default: 'local', reflect: true },
|
|
25
|
+
'dismiss-on-complete':{ type: Boolean, default: false, reflect: true },
|
|
26
|
+
collapsible: { type: Boolean, default: true, reflect: true },
|
|
27
|
+
collapsed: { type: Boolean, default: false, reflect: true },
|
|
28
|
+
value: { type: Object, default: {} },
|
|
29
|
+
title: { type: String, default: 'Get started', reflect: true },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Data-driven internal DOM; we render via tagged template literal so
|
|
33
|
+
// the child count can change as `items` changes.
|
|
34
|
+
static template = (host) => host._renderTemplate();
|
|
35
|
+
|
|
36
|
+
// ── lifecycle ────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
#headerId = `obc-h-${Math.random().toString(36).slice(2, 8)}`;
|
|
39
|
+
#liveId = `obc-l-${Math.random().toString(36).slice(2, 8)}`;
|
|
40
|
+
#valueHostControlled = false;
|
|
41
|
+
#lastEmittedComplete = false;
|
|
42
|
+
#boundChange = null;
|
|
43
|
+
#boundClick = null;
|
|
44
|
+
|
|
45
|
+
// ── attribute parsing ────────────────────────────────────────
|
|
46
|
+
//
|
|
47
|
+
// `items` + `value` accept JSON strings via the HTML attribute path
|
|
48
|
+
// (the canonical authoring shape for a2ui-generated markup). The
|
|
49
|
+
// default UIElement `attributeChangedCallback` would assign the
|
|
50
|
+
// string verbatim to the property — breaking the array/object shape.
|
|
51
|
+
// Override to parse JSON before assignment for those two props.
|
|
52
|
+
|
|
53
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
54
|
+
if (name === 'items' || name === 'value') {
|
|
55
|
+
if (newVal == null) {
|
|
56
|
+
this[name] = name === 'items' ? [] : {};
|
|
57
|
+
} else {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(newVal);
|
|
60
|
+
this[name] = parsed;
|
|
61
|
+
} catch {
|
|
62
|
+
this[name] = name === 'items' ? [] : {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
connected() {
|
|
71
|
+
this.setAttribute('role', 'region');
|
|
72
|
+
this.setAttribute('aria-labelledby', this.#headerId);
|
|
73
|
+
|
|
74
|
+
// Late-parse JSON attributes that were present at construct time
|
|
75
|
+
// (UIElement reads the attribute as a String, so we re-parse here).
|
|
76
|
+
if (this.hasAttribute('items') && !Array.isArray(this.items)) {
|
|
77
|
+
try { this.items = JSON.parse(this.getAttribute('items')); }
|
|
78
|
+
catch { this.items = []; }
|
|
79
|
+
}
|
|
80
|
+
if (this.hasAttribute('value') && (typeof this.value !== 'object' || Array.isArray(this.value))) {
|
|
81
|
+
try { this.value = JSON.parse(this.getAttribute('value')); }
|
|
82
|
+
catch { this.value = {}; }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Determine whether host is supplying `value` (host-controlled mode).
|
|
86
|
+
// If host set the property before connect, treat as host-controlled
|
|
87
|
+
// and skip storage hydration.
|
|
88
|
+
this.#valueHostControlled = this.value && Object.keys(this.value).length > 0;
|
|
89
|
+
if (!this.#valueHostControlled) {
|
|
90
|
+
const hydrated = this.#readStorage();
|
|
91
|
+
if (hydrated) this.value = hydrated;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.#boundChange = this.#onChange.bind(this);
|
|
95
|
+
this.#boundClick = this.#onClick.bind(this);
|
|
96
|
+
this.addEventListener('change', this.#boundChange);
|
|
97
|
+
this.addEventListener('click', this.#boundClick);
|
|
98
|
+
|
|
99
|
+
this.#lastEmittedComplete = this.#isAllDone();
|
|
100
|
+
this.#syncCompletionAttrs();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
disconnected() {
|
|
104
|
+
if (this.#boundChange) this.removeEventListener('change', this.#boundChange);
|
|
105
|
+
if (this.#boundClick) this.removeEventListener('click', this.#boundClick);
|
|
106
|
+
this.#boundChange = null;
|
|
107
|
+
this.#boundClick = null;
|
|
108
|
+
|
|
109
|
+
// Persist final state on detach if storage-key is set.
|
|
110
|
+
this.#writeStorage();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── render ───────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
render() {
|
|
116
|
+
// Sync per-item completion attributes after each stamp (the
|
|
117
|
+
// template engine handles primary structure; this keeps reflected
|
|
118
|
+
// attrs + aria correct after items/value changes).
|
|
119
|
+
this.#syncCompletionAttrs();
|
|
120
|
+
this.#announceProgress();
|
|
121
|
+
this.#maybeEmitComplete();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_renderTemplate() {
|
|
125
|
+
const safeItems = Array.isArray(this.items) ? this.items : [];
|
|
126
|
+
const { done, total, percent } = this.#progressSnapshot();
|
|
127
|
+
const collapseLabel = this.collapsed ? 'Expand' : 'Collapse';
|
|
128
|
+
const collapseIcon = this.collapsed ? 'chevron-down' : 'chevron-up';
|
|
129
|
+
const showCollapse = !!this.collapsible;
|
|
130
|
+
const headerTitle = this.title || 'Get started';
|
|
131
|
+
const allDone = total > 0 && done === total;
|
|
132
|
+
|
|
133
|
+
// The body content (progress + items + footer) is hidden via CSS
|
|
134
|
+
// when [collapsed] is set; we keep the nodes mounted so collapsing
|
|
135
|
+
// is a paint-only operation and focus inside the item list is
|
|
136
|
+
// preserved across collapse/expand toggles.
|
|
137
|
+
|
|
138
|
+
return html`
|
|
139
|
+
<div data-part="header">
|
|
140
|
+
<div data-part="title" id=${this.#headerId}>${headerTitle}</div>
|
|
141
|
+
${showCollapse
|
|
142
|
+
? html`<button-ui
|
|
143
|
+
data-part="collapse-toggle"
|
|
144
|
+
variant="ghost"
|
|
145
|
+
size="sm"
|
|
146
|
+
.icon=${collapseIcon}
|
|
147
|
+
aria-expanded=${String(!this.collapsed)}
|
|
148
|
+
aria-controls=${this.#liveId}
|
|
149
|
+
aria-label=${collapseLabel}
|
|
150
|
+
@click=${(e) => this.#onCollapseToggle(e)}
|
|
151
|
+
></button-ui>`
|
|
152
|
+
: null}
|
|
153
|
+
</div>
|
|
154
|
+
<progress-ui
|
|
155
|
+
data-part="progress"
|
|
156
|
+
.value=${percent}
|
|
157
|
+
aria-label="Onboarding progress"
|
|
158
|
+
></progress-ui>
|
|
159
|
+
<div
|
|
160
|
+
data-part="meter"
|
|
161
|
+
id=${this.#liveId}
|
|
162
|
+
aria-live="polite"
|
|
163
|
+
aria-atomic="true"
|
|
164
|
+
>${total > 0
|
|
165
|
+
? (allDone ? 'All steps complete' : `${done} of ${total} steps complete`)
|
|
166
|
+
: ''}</div>
|
|
167
|
+
<list-ui data-part="items">
|
|
168
|
+
${safeItems.map((item) => this.#renderItem(item))}
|
|
169
|
+
</list-ui>
|
|
170
|
+
${allDone && !this['dismiss-on-complete']
|
|
171
|
+
? html`<div data-part="complete">
|
|
172
|
+
<slot name="complete"></slot>
|
|
173
|
+
<button-ui
|
|
174
|
+
data-part="dismiss"
|
|
175
|
+
variant="primary"
|
|
176
|
+
size="sm"
|
|
177
|
+
text="Done"
|
|
178
|
+
@click=${() => this.#dismiss('user')}
|
|
179
|
+
></button-ui>
|
|
180
|
+
</div>`
|
|
181
|
+
: null}
|
|
182
|
+
<div data-part="footer"><slot name="footer"></slot></div>
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#renderItem(item) {
|
|
187
|
+
const id = String(item?.id ?? '');
|
|
188
|
+
const label = String(item?.label ?? '');
|
|
189
|
+
const description = String(item?.description ?? '');
|
|
190
|
+
const href = String(item?.href ?? '');
|
|
191
|
+
const done = this.#isItemDone(item);
|
|
192
|
+
return html`
|
|
193
|
+
<list-item-ui data-item-id=${id} data-done=${String(done)}>
|
|
194
|
+
<check-ui
|
|
195
|
+
slot="icon"
|
|
196
|
+
data-step-check
|
|
197
|
+
data-step-id=${id}
|
|
198
|
+
.checked=${done}
|
|
199
|
+
aria-label=${label}
|
|
200
|
+
></check-ui>
|
|
201
|
+
<span slot="text">${label}</span>
|
|
202
|
+
${description
|
|
203
|
+
? html`<span slot="description">${description}</span>`
|
|
204
|
+
: null}
|
|
205
|
+
${href
|
|
206
|
+
? html`<button-ui
|
|
207
|
+
slot="action"
|
|
208
|
+
data-step-action
|
|
209
|
+
data-step-id=${id}
|
|
210
|
+
data-href=${href}
|
|
211
|
+
variant="ghost"
|
|
212
|
+
icon-trailing="arrow-right"
|
|
213
|
+
text="Open"
|
|
214
|
+
></button-ui>`
|
|
215
|
+
: null}
|
|
216
|
+
</list-item-ui>
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── event delegation ─────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
#onChange(e) {
|
|
223
|
+
const target = e.target;
|
|
224
|
+
if (!target || !target.closest) return;
|
|
225
|
+
const check = target.closest('[data-step-check]');
|
|
226
|
+
if (!check || !this.contains(check)) return;
|
|
227
|
+
|
|
228
|
+
const id = check.getAttribute('data-step-id') || '';
|
|
229
|
+
if (!id) return;
|
|
230
|
+
|
|
231
|
+
const wasDone = this.#isItemDoneById(id);
|
|
232
|
+
const nowDone = !!check.checked;
|
|
233
|
+
if (wasDone === nowDone) return;
|
|
234
|
+
|
|
235
|
+
this.#setItemDone(id, nowDone, { emit: nowDone });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#onClick(e) {
|
|
239
|
+
const target = e.target;
|
|
240
|
+
if (!target || !target.closest) return;
|
|
241
|
+
const action = target.closest('[data-step-action]');
|
|
242
|
+
if (!action || !this.contains(action)) return;
|
|
243
|
+
|
|
244
|
+
const id = action.getAttribute('data-step-id') || '';
|
|
245
|
+
const href = action.getAttribute('data-href') || '';
|
|
246
|
+
if (!id) return;
|
|
247
|
+
|
|
248
|
+
this.dispatchEvent(new CustomEvent('onboarding-step-action', {
|
|
249
|
+
bubbles: true,
|
|
250
|
+
detail: { id, href },
|
|
251
|
+
}));
|
|
252
|
+
// Per spec OD-001 — clicking the CTA does NOT auto-complete.
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#onCollapseToggle(e) {
|
|
256
|
+
e?.stopPropagation?.();
|
|
257
|
+
const next = !this.collapsed;
|
|
258
|
+
this.collapsed = next;
|
|
259
|
+
if (next) {
|
|
260
|
+
this.dispatchEvent(new CustomEvent('onboarding-dismiss', {
|
|
261
|
+
bubbles: true,
|
|
262
|
+
detail: { reason: 'collapsed' },
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── state helpers ────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
#isItemDone(item) {
|
|
270
|
+
if (!item) return false;
|
|
271
|
+
const id = item.id;
|
|
272
|
+
if (id != null && Object.prototype.hasOwnProperty.call(this.value || {}, id)) {
|
|
273
|
+
return !!this.value[id];
|
|
274
|
+
}
|
|
275
|
+
return !!item.done;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
#isItemDoneById(id) {
|
|
279
|
+
const safeItems = Array.isArray(this.items) ? this.items : [];
|
|
280
|
+
const it = safeItems.find((i) => i && String(i.id) === String(id));
|
|
281
|
+
return this.#isItemDone(it);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#setItemDone(id, done, { emit = true } = {}) {
|
|
285
|
+
const next = { ...(this.value || {}), [id]: !!done };
|
|
286
|
+
this.value = next;
|
|
287
|
+
this.#writeStorage();
|
|
288
|
+
if (emit && done) {
|
|
289
|
+
this.dispatchEvent(new CustomEvent('onboarding-step-complete', {
|
|
290
|
+
bubbles: true,
|
|
291
|
+
detail: { id, value: next },
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#progressSnapshot() {
|
|
297
|
+
const safeItems = Array.isArray(this.items) ? this.items : [];
|
|
298
|
+
const total = safeItems.length;
|
|
299
|
+
let done = 0;
|
|
300
|
+
for (const it of safeItems) if (this.#isItemDone(it)) done++;
|
|
301
|
+
const percent = total === 0 ? 0 : Math.round((done / total) * 100);
|
|
302
|
+
return { done, total, percent };
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#isAllDone() {
|
|
306
|
+
const { done, total } = this.#progressSnapshot();
|
|
307
|
+
return total > 0 && done === total;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#syncCompletionAttrs() {
|
|
311
|
+
if (this.#isAllDone()) this.setAttribute('data-complete', '');
|
|
312
|
+
else this.removeAttribute('data-complete');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#maybeEmitComplete() {
|
|
316
|
+
const isDone = this.#isAllDone();
|
|
317
|
+
if (isDone && !this.#lastEmittedComplete) {
|
|
318
|
+
this.#lastEmittedComplete = true;
|
|
319
|
+
this.dispatchEvent(new CustomEvent('onboarding-complete', {
|
|
320
|
+
bubbles: true,
|
|
321
|
+
detail: { value: this.value },
|
|
322
|
+
}));
|
|
323
|
+
// dismiss-on-complete now defaults FALSE — by default the widget stays
|
|
324
|
+
// and surfaces a "Dismiss" button so the user controls when it goes away
|
|
325
|
+
// (per operator: a button rather than the widget silently vanishing).
|
|
326
|
+
// Auto-dismiss is opt-in via the [dismiss-on-complete] attribute.
|
|
327
|
+
if (this['dismiss-on-complete']) this.#dismiss('complete');
|
|
328
|
+
} else if (!isDone) {
|
|
329
|
+
this.#lastEmittedComplete = false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Hide the checklist + announce dismissal. Triggered by the user's Dismiss
|
|
334
|
+
* button on completion, or automatically when [dismiss-on-complete] is set. */
|
|
335
|
+
#dismiss(reason = 'user') {
|
|
336
|
+
this.setAttribute('hidden', '');
|
|
337
|
+
this.dispatchEvent(new CustomEvent('onboarding-dismiss', {
|
|
338
|
+
bubbles: true,
|
|
339
|
+
detail: { reason },
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#announceProgress() {
|
|
344
|
+
// The live region's textContent is set declaratively in the
|
|
345
|
+
// template; nothing else to do here — the polite live region
|
|
346
|
+
// re-reads on text change.
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ── storage ──────────────────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
#storageArea() {
|
|
352
|
+
if (this.#valueHostControlled) return null;
|
|
353
|
+
if (this.storage === 'none') return null;
|
|
354
|
+
if (!this['storage-key']) return null;
|
|
355
|
+
if (typeof window === 'undefined') return null;
|
|
356
|
+
try {
|
|
357
|
+
return this.storage === 'session' ? window.sessionStorage : window.localStorage;
|
|
358
|
+
} catch {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#readStorage() {
|
|
364
|
+
const area = this.#storageArea();
|
|
365
|
+
if (!area) return null;
|
|
366
|
+
try {
|
|
367
|
+
const raw = area.getItem(this['storage-key']);
|
|
368
|
+
if (!raw) return null;
|
|
369
|
+
const parsed = JSON.parse(raw);
|
|
370
|
+
return (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : null;
|
|
371
|
+
} catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#writeStorage() {
|
|
377
|
+
const area = this.#storageArea();
|
|
378
|
+
if (!area) return;
|
|
379
|
+
try {
|
|
380
|
+
area.setItem(this['storage-key'], JSON.stringify(this.value || {}));
|
|
381
|
+
} catch {
|
|
382
|
+
/* quota / privacy mode — silently ignore */
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── public methods ───────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Mark an item as done (idempotent). Fires `onboarding-step-complete`
|
|
390
|
+
* if the item transitions from undone → done.
|
|
391
|
+
*/
|
|
392
|
+
complete(id) {
|
|
393
|
+
if (id == null) return;
|
|
394
|
+
const key = String(id);
|
|
395
|
+
const was = this.#isItemDoneById(key);
|
|
396
|
+
if (was) return;
|
|
397
|
+
this.#setItemDone(key, true, { emit: true });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Reset one item (or all items, when id is omitted) to undone. Wipes
|
|
402
|
+
* the storage entry when resetting all.
|
|
403
|
+
*/
|
|
404
|
+
reset(id) {
|
|
405
|
+
if (id == null) {
|
|
406
|
+
this.value = {};
|
|
407
|
+
const area = this.#storageArea();
|
|
408
|
+
try { area?.removeItem(this['storage-key']); } catch { /* ignore */ }
|
|
409
|
+
this.#lastEmittedComplete = false;
|
|
410
|
+
this.removeAttribute('hidden');
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const key = String(id);
|
|
414
|
+
if (!Object.prototype.hasOwnProperty.call(this.value || {}, key)) return;
|
|
415
|
+
this.#setItemDone(key, false, { emit: false });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Snapshot of progress numbers. */
|
|
419
|
+
progress() {
|
|
420
|
+
return this.#progressSnapshot();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (!customElements.get('onboarding-checklist-ui')) {
|
|
425
|
+
customElements.define('onboarding-checklist-ui', UIOnboardingChecklist);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export { UIOnboardingChecklist };
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: UIOnboardingChecklist
|
|
4
|
+
tag: onboarding-checklist-ui
|
|
5
|
+
status: stable
|
|
6
|
+
component: OnboardingChecklist
|
|
7
|
+
category: feedback
|
|
8
|
+
version: 1
|
|
9
|
+
description: |
|
|
10
|
+
Module-tier onboarding checklist — a progressive setup-step widget
|
|
11
|
+
("Getting started" card seen in Stripe / Linear / Vercel dashboards).
|
|
12
|
+
Composes <progress-ui> (top fill bar), <list-ui> + <list-item-ui>
|
|
13
|
+
(the step rows), <check-ui> (per-item completion checkbox), and
|
|
14
|
+
optional <button-ui> (per-item action CTA).
|
|
15
|
+
|
|
16
|
+
Owns the data model (items: array of {id, label, description, href,
|
|
17
|
+
done}), the persistence contract (storage-key writes a JSON blob to
|
|
18
|
+
localStorage / sessionStorage), the auto-computed progress display,
|
|
19
|
+
and the dismiss-on-complete behavior. Items are data, not slotted
|
|
20
|
+
markup — host wiring uses stable per-item ids, not DOM positions.
|
|
21
|
+
|
|
22
|
+
Use for the "card of optional setup tasks" case; use <step-progress-ui>
|
|
23
|
+
for horizontal multi-screen wizards where the user is locked into
|
|
24
|
+
linear progression.
|
|
25
|
+
|
|
26
|
+
props:
|
|
27
|
+
items:
|
|
28
|
+
description: Step list — each item is { id, label, description?, href?, done? }. Required, non-empty.
|
|
29
|
+
type: array
|
|
30
|
+
default: []
|
|
31
|
+
required: true
|
|
32
|
+
dynamic: true
|
|
33
|
+
storage-key:
|
|
34
|
+
description: localStorage key for per-item completion persistence. Empty string disables storage (ephemeral).
|
|
35
|
+
type: string
|
|
36
|
+
default: ""
|
|
37
|
+
reflect: true
|
|
38
|
+
storage:
|
|
39
|
+
description: Persistence backend when storage-key is set.
|
|
40
|
+
type: string
|
|
41
|
+
default: local
|
|
42
|
+
enum: [local, session, none]
|
|
43
|
+
reflect: true
|
|
44
|
+
dismiss-on-complete:
|
|
45
|
+
description: Auto-hide the widget when all items are done.
|
|
46
|
+
type: boolean
|
|
47
|
+
default: true
|
|
48
|
+
reflect: true
|
|
49
|
+
collapsible:
|
|
50
|
+
description: Show a top-right collapse toggle.
|
|
51
|
+
type: boolean
|
|
52
|
+
default: true
|
|
53
|
+
reflect: true
|
|
54
|
+
collapsed:
|
|
55
|
+
description: Initial / current collapsed state — hides the item list, keeps progress + header visible.
|
|
56
|
+
type: boolean
|
|
57
|
+
default: false
|
|
58
|
+
reflect: true
|
|
59
|
+
dynamic: true
|
|
60
|
+
value:
|
|
61
|
+
description: "Completion map mapping item id to boolean. When set, host owns state and storage attributes are ignored."
|
|
62
|
+
type: object
|
|
63
|
+
default: {}
|
|
64
|
+
dynamic: true
|
|
65
|
+
title:
|
|
66
|
+
description: Header label above the progress bar. Defaults to "Get started".
|
|
67
|
+
type: string
|
|
68
|
+
default: "Get started"
|
|
69
|
+
reflect: true
|
|
70
|
+
|
|
71
|
+
events:
|
|
72
|
+
onboarding-step-complete:
|
|
73
|
+
description: Fired when a step transitions from undone to done.
|
|
74
|
+
detail:
|
|
75
|
+
id:
|
|
76
|
+
type: string
|
|
77
|
+
description: The id of the step that just completed.
|
|
78
|
+
value:
|
|
79
|
+
type: object
|
|
80
|
+
description: Snapshot of the full completion map after the change.
|
|
81
|
+
onboarding-step-action:
|
|
82
|
+
description: Fired when the user clicks an item's action CTA.
|
|
83
|
+
detail:
|
|
84
|
+
id:
|
|
85
|
+
type: string
|
|
86
|
+
description: The id of the step whose CTA was clicked.
|
|
87
|
+
href:
|
|
88
|
+
type: string
|
|
89
|
+
description: The href of the CTA (may be empty when host wires the click elsewhere).
|
|
90
|
+
onboarding-complete:
|
|
91
|
+
description: Fired when all items become done.
|
|
92
|
+
detail:
|
|
93
|
+
value:
|
|
94
|
+
type: object
|
|
95
|
+
description: The completion map at the moment of full completion.
|
|
96
|
+
onboarding-dismiss:
|
|
97
|
+
description: Fired when the widget collapses or auto-dismisses on completion.
|
|
98
|
+
detail:
|
|
99
|
+
reason:
|
|
100
|
+
type: string
|
|
101
|
+
description: Why the widget was dismissed.
|
|
102
|
+
enum: [collapsed, complete]
|
|
103
|
+
|
|
104
|
+
slots:
|
|
105
|
+
header:
|
|
106
|
+
description: Custom title region above the progress bar. Overrides the default [title] attribute label.
|
|
107
|
+
description:
|
|
108
|
+
description: >-
|
|
109
|
+
Optional subtitle / supporting prose rendered beneath the header.
|
|
110
|
+
Renders at body-subtle typography. Use for richer markup than the
|
|
111
|
+
plain [description] attribute string.
|
|
112
|
+
footer:
|
|
113
|
+
description: Optional footer content (e.g. "Skip for now" link / button).
|
|
114
|
+
complete:
|
|
115
|
+
description: Content shown when all items are done and dismiss-on-complete is false.
|
|
116
|
+
|
|
117
|
+
states:
|
|
118
|
+
- name: idle
|
|
119
|
+
description: Some items incomplete; widget visible and expanded.
|
|
120
|
+
- name: collapsed
|
|
121
|
+
description: Item list hidden; header + progress visible.
|
|
122
|
+
attribute: collapsed
|
|
123
|
+
- name: complete
|
|
124
|
+
description: All items done. Behavior depends on dismiss-on-complete.
|
|
125
|
+
attribute: data-complete
|
|
126
|
+
- name: dismissed
|
|
127
|
+
description: Widget removed from layout after collapse or auto-dismiss.
|
|
128
|
+
attribute: hidden
|
|
129
|
+
|
|
130
|
+
traits: []
|
|
131
|
+
|
|
132
|
+
tokens:
|
|
133
|
+
--onboarding-checklist-bg:
|
|
134
|
+
description: Card background. Defaults to elevated canvas tier.
|
|
135
|
+
default: var(--a-canvas-1)
|
|
136
|
+
--onboarding-checklist-fg:
|
|
137
|
+
description: Primary foreground color.
|
|
138
|
+
default: var(--a-fg)
|
|
139
|
+
--onboarding-checklist-fg-muted:
|
|
140
|
+
description: Secondary foreground color for descriptions + meta.
|
|
141
|
+
default: var(--a-fg-muted)
|
|
142
|
+
--onboarding-checklist-radius:
|
|
143
|
+
description: Outer card radius.
|
|
144
|
+
default: var(--a-radius-lg)
|
|
145
|
+
--onboarding-checklist-px:
|
|
146
|
+
description: Inline (left/right) padding.
|
|
147
|
+
default: var(--a-space-4)
|
|
148
|
+
--onboarding-checklist-py:
|
|
149
|
+
description: Block (top/bottom) padding.
|
|
150
|
+
default: var(--a-space-4)
|
|
151
|
+
--onboarding-checklist-gap:
|
|
152
|
+
description: Gap between header / progress / items / footer.
|
|
153
|
+
default: var(--a-space-3)
|
|
154
|
+
--onboarding-checklist-progress-fill:
|
|
155
|
+
description: Progress-bar fill color (forwarded to inner <progress-ui> via --progress-fill).
|
|
156
|
+
default: var(--md-sys-color-primary)
|
|
157
|
+
--onboarding-checklist-shadow:
|
|
158
|
+
description: Card elevation.
|
|
159
|
+
default: var(--a-shadow-sm)
|
|
160
|
+
--onboarding-checklist-border:
|
|
161
|
+
description: Outer border.
|
|
162
|
+
default: 1px solid var(--a-border-subtle)
|
|
163
|
+
|
|
164
|
+
a2ui:
|
|
165
|
+
rules:
|
|
166
|
+
- rule: 'OnboardingChecklist MUST have a non-empty `items` array. Empty items renders an empty card.'
|
|
167
|
+
reason: 'Without steps the widget has nothing to display.'
|
|
168
|
+
- rule: 'Each item MUST have a unique `id` within the list — used as the storage key + event detail.'
|
|
169
|
+
reason: 'Stable per-item identifier; completion cannot be persisted otherwise.'
|
|
170
|
+
- rule: '`storageKey` SHOULD be namespaced per product (e.g. `myapp:onboarding:v1`) to avoid cross-app collisions.'
|
|
171
|
+
reason: 'localStorage is a shared global key-space across the origin.'
|
|
172
|
+
- rule: 'Items SHOULD number 3 to 8. Fewer than 3 is not worth a checklist; more than 8 should split into stages.'
|
|
173
|
+
reason: 'Empirical product-design heuristic — readable progress, no overwhelm.'
|
|
174
|
+
- rule: 'Use for the "card of optional setup tasks" case. For horizontal multi-screen wizards use <step-progress-ui>; for generic to-do lists compose <list-ui> + <check-ui> directly.'
|
|
175
|
+
reason: 'Onboarding-specific semantics (dismiss-on-complete, persistent storage) leak into generic lists.'
|
|
176
|
+
- rule: 'Do not nest two <onboarding-checklist-ui> with the same `storageKey` — they will clobber each other.'
|
|
177
|
+
reason: 'Single storage slot per key.'
|
|
178
|
+
|
|
179
|
+
anti_patterns:
|
|
180
|
+
- wrong: |
|
|
181
|
+
{ "component": "OnboardingChecklist", "items": [] }
|
|
182
|
+
why: |
|
|
183
|
+
Empty checklist — renders an empty card.
|
|
184
|
+
fix: |
|
|
185
|
+
{ "component": "OnboardingChecklist", "items": [{ "id": "x", "label": "Step 1" }] }
|
|
186
|
+
- wrong: |
|
|
187
|
+
{ "component": "OnboardingChecklist", "items": [
|
|
188
|
+
{ "label": "Step A" }, { "label": "Step B" }
|
|
189
|
+
] }
|
|
190
|
+
why: |
|
|
191
|
+
Items missing `id`. The component cannot persist completion
|
|
192
|
+
without stable per-item identifiers.
|
|
193
|
+
fix: |
|
|
194
|
+
{ "component": "OnboardingChecklist", "items": [
|
|
195
|
+
{ "id": "a", "label": "Step A" }, { "id": "b", "label": "Step B" }
|
|
196
|
+
] }
|
|
197
|
+
|
|
198
|
+
examples:
|
|
199
|
+
- name: basic-checklist
|
|
200
|
+
description: Three-step dashboard setup card.
|
|
201
|
+
a2ui: |
|
|
202
|
+
[
|
|
203
|
+
{
|
|
204
|
+
"id": "onb-1",
|
|
205
|
+
"component": "OnboardingChecklist",
|
|
206
|
+
"storageKey": "acme:onboarding:v1",
|
|
207
|
+
"items": [
|
|
208
|
+
{ "id": "profile", "label": "Complete your profile", "description": "Add a photo and bio", "href": "/settings/profile" },
|
|
209
|
+
{ "id": "team", "label": "Invite your team", "description": "Add at least one teammate", "href": "/settings/team" },
|
|
210
|
+
{ "id": "connect", "label": "Connect a data source", "description": "Hook up the first integration", "href": "/integrations" }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
keywords:
|
|
216
|
+
- onboarding
|
|
217
|
+
- checklist
|
|
218
|
+
- getting-started
|
|
219
|
+
- setup
|
|
220
|
+
- steps
|
|
221
|
+
- progress
|
|
222
|
+
- getting started
|
|
223
|
+
|
|
224
|
+
synonyms:
|
|
225
|
+
onboarding: [getting-started, setup, welcome, kickoff]
|
|
226
|
+
checklist: [todo, task-list, steps]
|
|
227
|
+
getting-started: [onboarding, setup, welcome]
|
|
228
|
+
setup: [onboarding, getting-started, configuration]
|
|
229
|
+
|
|
230
|
+
related:
|
|
231
|
+
- Check
|
|
232
|
+
- List
|
|
233
|
+
- ListItem
|
|
234
|
+
- Progress
|
|
235
|
+
- Button
|
|
236
|
+
- StepProgress
|