@adia-ai/web-modules 0.8.36 → 0.8.38
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 +25 -0
- package/README.md +4 -4
- 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 +2 -2
- 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 +6 -6
- package/chat/chat-sidebar/chat-sidebar.js +2 -2
- package/chat/chat-surfaces/chat-surfaces.js +3 -3
- 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/chat/chat-shell.min.js +1 -1
- package/dist/everything.min.js +121 -102
- 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,227 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
+
<plan-picker-ui> — Form-participating chooser of one of N plans.
|
|
3
|
+
|
|
4
|
+
Composite over <option-card-ui> + <segmented-ui> + <segment-ui> +
|
|
5
|
+
<tag-ui> + <button-ui> + <icon-ui> + <empty-state-ui>. The host is
|
|
6
|
+
a column that lays out the toggle row · plans grid · skeleton ·
|
|
7
|
+
empty-state regions in DOM order. Each plan card is an
|
|
8
|
+
<option-card-ui> decorated with a price block, feature list, and
|
|
9
|
+
CTA via per-region L3 tokens.
|
|
10
|
+
|
|
11
|
+
SPEC: docs/specs/implementation-ready/SPEC-011-plan-picker-in-app-upgrade.md
|
|
12
|
+
Two-block @scope pattern per docs/specs/component-token-contract.md.
|
|
13
|
+
Zero raw color values (repo hard rule); zero raw px ≥ 3 (rem only).
|
|
14
|
+
═══════════════════════════════════════════════════════════════ */
|
|
15
|
+
|
|
16
|
+
/* ── Block 1: TOKEN DECLARATIONS ───────────────────────────────── */
|
|
17
|
+
|
|
18
|
+
plan-picker-ui {
|
|
19
|
+
--plan-picker-bg: transparent;
|
|
20
|
+
--plan-picker-grid-gap: var(--a-space-4);
|
|
21
|
+
--plan-picker-grid-min: 16rem;
|
|
22
|
+
|
|
23
|
+
--plan-picker-card-bg: var(--md-sys-color-neutral-background);
|
|
24
|
+
--plan-picker-card-bg-recommended: var(--md-sys-color-primary-container);
|
|
25
|
+
--plan-picker-card-bg-current: var(--a-bg-muted);
|
|
26
|
+
--plan-picker-card-border: 1px solid var(--md-sys-color-neutral-outline);
|
|
27
|
+
--plan-picker-card-border-recommended: 2px solid var(--md-sys-color-primary);
|
|
28
|
+
--plan-picker-card-radius: var(--a-radius-lg);
|
|
29
|
+
--plan-picker-card-py: var(--a-space-5);
|
|
30
|
+
--plan-picker-card-px: var(--a-space-5);
|
|
31
|
+
--plan-picker-card-shadow-recommended: var(--a-shadow-md);
|
|
32
|
+
|
|
33
|
+
--plan-picker-price-size: var(--a-text-3xl, 1.875rem);
|
|
34
|
+
--plan-picker-price-fg: var(--a-fg-strong, var(--md-sys-color-neutral-on-surface));
|
|
35
|
+
--plan-picker-cycle-fg: var(--a-fg-muted);
|
|
36
|
+
|
|
37
|
+
--plan-picker-features-gap: var(--a-space-2);
|
|
38
|
+
--plan-picker-feature-icon-fg: var(--md-sys-color-primary);
|
|
39
|
+
|
|
40
|
+
--plan-picker-toggle-justify: center;
|
|
41
|
+
|
|
42
|
+
--plan-picker-disabled-opacity: var(--a-opacity-disabled, 0.5);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* ── Block 2: SCOPED LAYOUT ────────────────────────────────────── */
|
|
46
|
+
|
|
47
|
+
@scope (plan-picker-ui) {
|
|
48
|
+
|
|
49
|
+
:scope {
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: var(--a-space-4);
|
|
53
|
+
background: var(--plan-picker-bg);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── Toggle row ── */
|
|
57
|
+
:scope > [data-toggle] {
|
|
58
|
+
display: flex;
|
|
59
|
+
justify-content: var(--plan-picker-toggle-justify);
|
|
60
|
+
}
|
|
61
|
+
:scope > [data-toggle][hidden] {
|
|
62
|
+
display: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ── Plans grid ── */
|
|
66
|
+
:scope > [data-plans] {
|
|
67
|
+
display: grid;
|
|
68
|
+
grid-template-columns: repeat(
|
|
69
|
+
auto-fit,
|
|
70
|
+
minmax(var(--plan-picker-grid-min), 1fr)
|
|
71
|
+
);
|
|
72
|
+
gap: var(--plan-picker-grid-gap);
|
|
73
|
+
align-items: stretch;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* List layout — single-column rows for in-app settings. */
|
|
77
|
+
:scope[layout="list"] > [data-plans] {
|
|
78
|
+
grid-template-columns: 1fr;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ── Per-card cascades into option-card-ui L3 tokens ── */
|
|
82
|
+
:scope > [data-plans] > option-card-ui {
|
|
83
|
+
--option-card-bg: var(--plan-picker-card-bg);
|
|
84
|
+
--option-card-border: var(--plan-picker-card-border);
|
|
85
|
+
--option-card-radius: var(--plan-picker-card-radius);
|
|
86
|
+
--option-card-padding-block: var(--plan-picker-card-py);
|
|
87
|
+
--option-card-padding-inline: var(--plan-picker-card-px);
|
|
88
|
+
box-sizing: border-box;
|
|
89
|
+
/* Equal-height cards across the auto-fit row (the grid sets
|
|
90
|
+
align-items:stretch); option-card[layout="tile"] supplies the flex
|
|
91
|
+
column, so we must NOT override its display here — doing so strands
|
|
92
|
+
the radio indicator in a collapsed 8px grid gutter. */
|
|
93
|
+
height: 100%;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
:scope > [data-plans] > option-card-ui[data-recommended] {
|
|
97
|
+
--option-card-bg: var(--plan-picker-card-bg-recommended);
|
|
98
|
+
--option-card-border: var(--plan-picker-card-border-recommended);
|
|
99
|
+
box-shadow: var(--plan-picker-card-shadow-recommended);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
:scope > [data-plans] > option-card-ui[data-current] {
|
|
103
|
+
--option-card-bg: var(--plan-picker-card-bg-current);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ── Plan body — price + features + CTA, REVEALED ALWAYS.
|
|
107
|
+
option-card-ui hides default-slot children unless [checked],
|
|
108
|
+
but plan cards must show their content regardless. We override
|
|
109
|
+
with the same `:not([slot])` selector option-card uses
|
|
110
|
+
internally, scoped to our [data-plan-body] children. flex:1 lets
|
|
111
|
+
the body absorb the card's free height so the CTA (margin-top:auto
|
|
112
|
+
below) anchors to the bottom edge — CTAs align across a row of
|
|
113
|
+
cards with uneven feature lists. ── */
|
|
114
|
+
:scope > [data-plans] > option-card-ui > [data-plan-body] {
|
|
115
|
+
flex: 1;
|
|
116
|
+
display: flex !important;
|
|
117
|
+
flex-direction: column;
|
|
118
|
+
gap: var(--a-space-3);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Recommended / current pill — a standalone tag at the card's top-left
|
|
122
|
+
(the tile radio sits top-right). order:0 default keeps it ahead of the
|
|
123
|
+
order:2 heading regardless of DOM position. */
|
|
124
|
+
:scope > [data-plans] > option-card-ui > [data-plan-tags] {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-self: start;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: var(--a-space-1);
|
|
129
|
+
}
|
|
130
|
+
:scope > [data-plans] > option-card-ui > [data-plan-tags]:empty {
|
|
131
|
+
display: none;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ── Price row ── */
|
|
135
|
+
:scope [data-price-row] {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: baseline;
|
|
138
|
+
gap: var(--a-space-1);
|
|
139
|
+
}
|
|
140
|
+
:scope [data-price] {
|
|
141
|
+
font-size: var(--plan-picker-price-size);
|
|
142
|
+
color: var(--plan-picker-price-fg);
|
|
143
|
+
font-weight: var(--a-weight-bold, 700);
|
|
144
|
+
line-height: 1;
|
|
145
|
+
}
|
|
146
|
+
:scope [data-cycle] {
|
|
147
|
+
font-size: var(--a-text-sm, 0.875rem);
|
|
148
|
+
color: var(--plan-picker-cycle-fg);
|
|
149
|
+
}
|
|
150
|
+
:scope [data-price-note] {
|
|
151
|
+
font-size: var(--a-text-xs, 0.75rem);
|
|
152
|
+
color: var(--plan-picker-cycle-fg);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ── Features list ── */
|
|
156
|
+
:scope ul[data-features] {
|
|
157
|
+
list-style: none;
|
|
158
|
+
margin: 0;
|
|
159
|
+
padding: 0;
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-direction: column;
|
|
162
|
+
gap: var(--plan-picker-features-gap);
|
|
163
|
+
}
|
|
164
|
+
:scope ul[data-features] > li {
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: flex-start;
|
|
167
|
+
gap: var(--a-space-2);
|
|
168
|
+
/* Override the docs feature-list indent (site.css adds
|
|
169
|
+
`padding-left: 24px` + a ::before checkmark to ul[data-features] li);
|
|
170
|
+
the slotted <icon-ui> IS our bullet, so the row aligns flush-left. */
|
|
171
|
+
padding-inline-start: 0;
|
|
172
|
+
color: var(--md-sys-color-neutral-on-surface);
|
|
173
|
+
font-size: var(--a-text-sm, 0.875rem);
|
|
174
|
+
line-height: 1.4;
|
|
175
|
+
}
|
|
176
|
+
:scope ul[data-features] > li > icon-ui {
|
|
177
|
+
flex-shrink: 0;
|
|
178
|
+
color: var(--plan-picker-feature-icon-fg);
|
|
179
|
+
--a-icon-size: 1rem;
|
|
180
|
+
margin-block-start: 0.125rem;
|
|
181
|
+
}
|
|
182
|
+
/* The feature check is the slotted <icon-ui>. Suppress any inherited
|
|
183
|
+
::before checkmark (the docs admin-page feature-list style adds a green
|
|
184
|
+
"✓" to every ul[data-features] li) so the bullet doesn't double up. */
|
|
185
|
+
:scope ul[data-features] > li::before {
|
|
186
|
+
content: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ── CTA button — full-width, anchored to the body's bottom edge so a
|
|
190
|
+
row of cards keeps their CTAs aligned regardless of feature count. ── */
|
|
191
|
+
:scope button-ui[data-cta] {
|
|
192
|
+
width: 100%;
|
|
193
|
+
margin-block-start: auto;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* ── Empty state ── gated by [empty] attribute.
|
|
197
|
+
Hide [data-empty] when host is NOT empty; do NOT set a display
|
|
198
|
+
value when shown (would clobber empty-state-ui's flex layout). */
|
|
199
|
+
:scope:not([empty]) > [data-empty] {
|
|
200
|
+
display: none;
|
|
201
|
+
}
|
|
202
|
+
:scope[empty] > [data-plans] {
|
|
203
|
+
display: none;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ── Loading skeleton ── */
|
|
207
|
+
:scope > [data-skeleton] {
|
|
208
|
+
display: none;
|
|
209
|
+
}
|
|
210
|
+
:scope[loading] > [data-skeleton] {
|
|
211
|
+
display: grid;
|
|
212
|
+
grid-template-columns: repeat(
|
|
213
|
+
auto-fit,
|
|
214
|
+
minmax(var(--plan-picker-grid-min), 1fr)
|
|
215
|
+
);
|
|
216
|
+
gap: var(--plan-picker-grid-gap);
|
|
217
|
+
}
|
|
218
|
+
:scope[loading] > [data-plans] {
|
|
219
|
+
visibility: hidden;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* ── Disabled state ── */
|
|
223
|
+
:scope[disabled] {
|
|
224
|
+
opacity: var(--plan-picker-disabled-opacity);
|
|
225
|
+
pointer-events: none;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<plan-picker-ui>` — auto-registers the tag on import.
|
|
3
|
+
*
|
|
4
|
+
* For a non-side-effect class import (test isolation, tag override),
|
|
5
|
+
* use the class export from `./plan-picker.class.js`:
|
|
6
|
+
*
|
|
7
|
+
* import { PlanPicker } from '@adia-ai/web-modules/billing/plan-picker/class';
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { PlanPicker } from './plan-picker.class.js';
|
|
11
|
+
|
|
12
|
+
if (!customElements.get('plan-picker-ui')) {
|
|
13
|
+
customElements.define('plan-picker-ui', PlanPicker);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { PlanPicker };
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: PlanPicker
|
|
4
|
+
tag: plan-picker-ui
|
|
5
|
+
status: experimental
|
|
6
|
+
component: PlanPicker
|
|
7
|
+
category: input
|
|
8
|
+
version: 1
|
|
9
|
+
description: |
|
|
10
|
+
A form-participating chooser of one of N billing plans. The canonical
|
|
11
|
+
plan-card grid used by both in-app upgrade modals and marketing
|
|
12
|
+
pricing pages (SPEC-012 composes this primitive as its core), so
|
|
13
|
+
benefit copy, pricing strings, recommendation highlighting, and the
|
|
14
|
+
billing-cycle toggle never drift between surfaces.
|
|
15
|
+
|
|
16
|
+
Each plan record carries a stable id, name, optional tagline + icon,
|
|
17
|
+
a per-cycle price object, a feature list, and optional flags
|
|
18
|
+
(recommended, rank, disabled). One plan may carry recommended:true
|
|
19
|
+
(accent border + tinted bg + shadow); a host-level `current` id
|
|
20
|
+
marks the user's current plan (muted bg, "Current plan" tag, no
|
|
21
|
+
Choose CTA, contextual "Upgrade"/"Downgrade" labels on siblings).
|
|
22
|
+
|
|
23
|
+
Layout is grid by default (marketing surfaces) and `layout="list"`
|
|
24
|
+
for in-app settings panels. The optional `cycle-toggle` boolean
|
|
25
|
+
renders an internal <segmented-ui> that swaps prices between
|
|
26
|
+
monthly and annual cycles in place across every card.
|
|
27
|
+
|
|
28
|
+
Spec: .claude/docs/specs/implementation-ready/SPEC-011-plan-picker-in-app-upgrade.md.
|
|
29
|
+
|
|
30
|
+
# Per ADR-0027 — composites do NOT auto-import the primitives they
|
|
31
|
+
# stamp in connected()/render(). The consumer (or this primitive's
|
|
32
|
+
# .html demo shell) must explicitly side-effect-import each one.
|
|
33
|
+
composes:
|
|
34
|
+
- option-card-ui
|
|
35
|
+
- segmented-ui
|
|
36
|
+
- segment-ui
|
|
37
|
+
- button-ui
|
|
38
|
+
- icon-ui
|
|
39
|
+
- text-ui
|
|
40
|
+
- tag-ui
|
|
41
|
+
- empty-state-ui
|
|
42
|
+
|
|
43
|
+
props:
|
|
44
|
+
plans:
|
|
45
|
+
description: |
|
|
46
|
+
Array of plan records. Source of truth for the rendered cards.
|
|
47
|
+
Shape per record: {id, name, tagline?, prices: {monthly: {amount,
|
|
48
|
+
currency?, unit?, note?}, annual?: {...}}, features?: string[],
|
|
49
|
+
cta?: string, recommended?: boolean, rank?: number, icon?: string,
|
|
50
|
+
disabled?: boolean}. Empty array renders the empty-state.
|
|
51
|
+
type: array
|
|
52
|
+
default: []
|
|
53
|
+
dynamic: true
|
|
54
|
+
required: true
|
|
55
|
+
|
|
56
|
+
value:
|
|
57
|
+
description: |
|
|
58
|
+
Currently selected plan id (form value). Read-write — both the
|
|
59
|
+
form submission key and the [checked] gate on the matching
|
|
60
|
+
<option-card-ui>.
|
|
61
|
+
type: string
|
|
62
|
+
default: ""
|
|
63
|
+
reflect: true
|
|
64
|
+
dynamic: true
|
|
65
|
+
|
|
66
|
+
current:
|
|
67
|
+
description: |
|
|
68
|
+
Id of the user's CURRENT plan. When set, that card renders with
|
|
69
|
+
a muted background, a "Current plan" <tag-ui>, and either no CTA
|
|
70
|
+
or a "Manage" CTA. Other plans receive contextual upgrade /
|
|
71
|
+
downgrade CTA labels derived from rank. Unknown ids fail
|
|
72
|
+
silently (no anchor row).
|
|
73
|
+
type: string
|
|
74
|
+
default: ""
|
|
75
|
+
reflect: true
|
|
76
|
+
dynamic: true
|
|
77
|
+
|
|
78
|
+
name:
|
|
79
|
+
description: |
|
|
80
|
+
Form field name for the selected plan id. Mirrors UIFormElement
|
|
81
|
+
conventions; serialized via ElementInternals.
|
|
82
|
+
type: string
|
|
83
|
+
default: ""
|
|
84
|
+
reflect: true
|
|
85
|
+
|
|
86
|
+
cycle:
|
|
87
|
+
description: |
|
|
88
|
+
Active billing cycle. Drives which `plans[].prices[cycle]` value
|
|
89
|
+
renders across every card simultaneously. Toggle via the in-host
|
|
90
|
+
`cycle-toggle` (when set) or programmatically via setCycle().
|
|
91
|
+
type: string
|
|
92
|
+
default: "monthly"
|
|
93
|
+
enum:
|
|
94
|
+
- monthly
|
|
95
|
+
- annual
|
|
96
|
+
reflect: true
|
|
97
|
+
dynamic: true
|
|
98
|
+
|
|
99
|
+
cycle-toggle:
|
|
100
|
+
description: |
|
|
101
|
+
When set, the primitive renders an internal <segmented-ui> with
|
|
102
|
+
Monthly / Annual options above the plan grid. When unset, the
|
|
103
|
+
consumer drives [cycle] externally (e.g. from a parent toolbar).
|
|
104
|
+
type: boolean
|
|
105
|
+
default: false
|
|
106
|
+
reflect: true
|
|
107
|
+
attribute: cycle-toggle
|
|
108
|
+
|
|
109
|
+
currency:
|
|
110
|
+
description: |
|
|
111
|
+
Default ISO 4217 currency code for price rendering. Overridable
|
|
112
|
+
per-price via plans[].prices[cycle].currency.
|
|
113
|
+
type: string
|
|
114
|
+
default: "USD"
|
|
115
|
+
|
|
116
|
+
locale:
|
|
117
|
+
description: |
|
|
118
|
+
Locale for Intl.NumberFormat (price) and cycle labels. Defaults
|
|
119
|
+
to document.documentElement.lang (or "en-US" when absent).
|
|
120
|
+
type: string
|
|
121
|
+
default: ""
|
|
122
|
+
|
|
123
|
+
layout:
|
|
124
|
+
description: |
|
|
125
|
+
Card layout. `grid` (auto-fit columns, minimum 16rem) for
|
|
126
|
+
marketing surfaces. `list` (single column rows) for in-app
|
|
127
|
+
settings panels.
|
|
128
|
+
type: string
|
|
129
|
+
default: "grid"
|
|
130
|
+
enum:
|
|
131
|
+
- grid
|
|
132
|
+
- list
|
|
133
|
+
reflect: true
|
|
134
|
+
|
|
135
|
+
disabled:
|
|
136
|
+
description: Block all interaction across the picker.
|
|
137
|
+
type: boolean
|
|
138
|
+
default: false
|
|
139
|
+
reflect: true
|
|
140
|
+
|
|
141
|
+
data-stream-src:
|
|
142
|
+
description: |
|
|
143
|
+
Optional endpoint returning the `plans` array (universal
|
|
144
|
+
data-stream-* trait). On successful load, `plans` is replaced
|
|
145
|
+
and a `load` event fires; on failure an `error` event fires.
|
|
146
|
+
type: string
|
|
147
|
+
default: ""
|
|
148
|
+
attribute: data-stream-src
|
|
149
|
+
|
|
150
|
+
events:
|
|
151
|
+
change:
|
|
152
|
+
description: |
|
|
153
|
+
Fired when the selected plan id changes — either via user click
|
|
154
|
+
or programmatic value set. Bubbles.
|
|
155
|
+
detail:
|
|
156
|
+
value:
|
|
157
|
+
type: string
|
|
158
|
+
description: New plan id.
|
|
159
|
+
previous:
|
|
160
|
+
type: string
|
|
161
|
+
description: Previous plan id (empty string when none).
|
|
162
|
+
cycle:
|
|
163
|
+
type: string
|
|
164
|
+
description: Active billing cycle.
|
|
165
|
+
|
|
166
|
+
cycle-change:
|
|
167
|
+
description: |
|
|
168
|
+
Fired when the active billing cycle changes (cycle-toggle
|
|
169
|
+
click or programmatic setCycle). Bubbles.
|
|
170
|
+
detail:
|
|
171
|
+
cycle:
|
|
172
|
+
type: string
|
|
173
|
+
description: New cycle.
|
|
174
|
+
enum:
|
|
175
|
+
- monthly
|
|
176
|
+
- annual
|
|
177
|
+
previous:
|
|
178
|
+
type: string
|
|
179
|
+
description: Previous cycle.
|
|
180
|
+
|
|
181
|
+
select:
|
|
182
|
+
description: |
|
|
183
|
+
Fired when the user activates a plan's CTA button — the
|
|
184
|
+
committing action that consumers wire to a checkout / upgrade
|
|
185
|
+
flow. Distinct from `change` (selection without commit). Bubbles.
|
|
186
|
+
detail:
|
|
187
|
+
value:
|
|
188
|
+
type: string
|
|
189
|
+
description: Plan id being committed.
|
|
190
|
+
cycle:
|
|
191
|
+
type: string
|
|
192
|
+
description: Active billing cycle at commit.
|
|
193
|
+
|
|
194
|
+
load:
|
|
195
|
+
description: |
|
|
196
|
+
Fired when `data-stream-src` finishes a successful fetch and
|
|
197
|
+
`plans` is populated. Bubbles.
|
|
198
|
+
detail:
|
|
199
|
+
count:
|
|
200
|
+
type: integer
|
|
201
|
+
description: Number of plans loaded.
|
|
202
|
+
|
|
203
|
+
error:
|
|
204
|
+
description: |
|
|
205
|
+
Fired when a `data-stream-src` fetch fails OR when a user tries
|
|
206
|
+
to select a `disabled: true` plan. Bubbles.
|
|
207
|
+
detail:
|
|
208
|
+
reason:
|
|
209
|
+
type: string
|
|
210
|
+
description: One of `fetch-failed`, `plan-disabled`.
|
|
211
|
+
|
|
212
|
+
slots:
|
|
213
|
+
header:
|
|
214
|
+
description: |
|
|
215
|
+
Optional content above the plan grid (e.g. a <header> with a
|
|
216
|
+
title and a custom cycle toggle). When absent and
|
|
217
|
+
`cycle-toggle` is set, the primitive stamps its own toggle.
|
|
218
|
+
|
|
219
|
+
footer:
|
|
220
|
+
description: |
|
|
221
|
+
Optional content below the plan grid (compare-plans link, FAQ,
|
|
222
|
+
fine print).
|
|
223
|
+
|
|
224
|
+
empty:
|
|
225
|
+
description: |
|
|
226
|
+
Custom empty-state shown when `plans` is empty. Falls back to a
|
|
227
|
+
default <empty-state-ui> when not provided.
|
|
228
|
+
|
|
229
|
+
states:
|
|
230
|
+
- name: idle
|
|
231
|
+
description: Default — plans rendered, ready for interaction.
|
|
232
|
+
- name: loading
|
|
233
|
+
description: A `data-stream-src` fetch is in flight.
|
|
234
|
+
attribute: loading
|
|
235
|
+
- name: empty
|
|
236
|
+
description: No plans to render.
|
|
237
|
+
attribute: empty
|
|
238
|
+
- name: error
|
|
239
|
+
description: Last `data-stream-src` fetch failed.
|
|
240
|
+
attribute: error
|
|
241
|
+
- name: disabled
|
|
242
|
+
description: All cards non-interactive.
|
|
243
|
+
attribute: disabled
|
|
244
|
+
|
|
245
|
+
traits:
|
|
246
|
+
- focusable
|
|
247
|
+
|
|
248
|
+
tokens:
|
|
249
|
+
--plan-picker-bg:
|
|
250
|
+
description: Host background. Defaults to transparent.
|
|
251
|
+
default: transparent
|
|
252
|
+
--plan-picker-grid-gap:
|
|
253
|
+
description: Gap between plan cards in grid layout.
|
|
254
|
+
default: var(--a-space-4)
|
|
255
|
+
--plan-picker-grid-min:
|
|
256
|
+
description: Minimum column width in auto-fit grid.
|
|
257
|
+
default: 16rem
|
|
258
|
+
--plan-picker-card-bg:
|
|
259
|
+
description: Card background (cascades to option-card --option-card-bg).
|
|
260
|
+
default: var(--a-bg)
|
|
261
|
+
--plan-picker-card-bg-recommended:
|
|
262
|
+
description: Recommended-card background.
|
|
263
|
+
default: var(--md-sys-color-primary-container)
|
|
264
|
+
--plan-picker-card-bg-current:
|
|
265
|
+
description: Current-plan card background.
|
|
266
|
+
default: var(--a-bg-muted)
|
|
267
|
+
--plan-picker-card-border:
|
|
268
|
+
description: Default card border.
|
|
269
|
+
default: 1px solid var(--a-border)
|
|
270
|
+
--plan-picker-card-border-recommended:
|
|
271
|
+
description: Recommended-card accent border.
|
|
272
|
+
default: 2px solid var(--md-sys-color-primary)
|
|
273
|
+
--plan-picker-card-radius:
|
|
274
|
+
description: Card corner radius.
|
|
275
|
+
default: var(--a-radius-lg)
|
|
276
|
+
--plan-picker-card-py:
|
|
277
|
+
description: Card vertical padding.
|
|
278
|
+
default: var(--a-space-5)
|
|
279
|
+
--plan-picker-card-px:
|
|
280
|
+
description: Card horizontal padding.
|
|
281
|
+
default: var(--a-space-5)
|
|
282
|
+
--plan-picker-card-shadow-recommended:
|
|
283
|
+
description: Shadow elevation on the recommended card.
|
|
284
|
+
default: var(--a-shadow-md)
|
|
285
|
+
--plan-picker-price-size:
|
|
286
|
+
description: Price font size.
|
|
287
|
+
default: var(--a-text-3xl, 1.875rem)
|
|
288
|
+
--plan-picker-price-fg:
|
|
289
|
+
description: Price color.
|
|
290
|
+
default: var(--a-fg-strong, var(--a-fg))
|
|
291
|
+
--plan-picker-cycle-fg:
|
|
292
|
+
description: "Per-unit / cycle suffix color (e.g. \"/month\")."
|
|
293
|
+
default: var(--a-fg-muted)
|
|
294
|
+
--plan-picker-features-gap:
|
|
295
|
+
description: Gap between feature list rows.
|
|
296
|
+
default: var(--a-space-2)
|
|
297
|
+
--plan-picker-feature-icon-fg:
|
|
298
|
+
description: Feature-row check icon color.
|
|
299
|
+
default: var(--md-sys-color-primary)
|
|
300
|
+
--plan-picker-toggle-justify:
|
|
301
|
+
description: |
|
|
302
|
+
Justification of the internal cycle toggle row. `center` for
|
|
303
|
+
marketing layouts; `start` for in-app settings panels.
|
|
304
|
+
default: center
|
|
305
|
+
|
|
306
|
+
requiredIcons:
|
|
307
|
+
- check
|
|
308
|
+
- package
|
|
309
|
+
|
|
310
|
+
a2ui:
|
|
311
|
+
rules:
|
|
312
|
+
- >-
|
|
313
|
+
PlanPicker.plans MUST be a non-empty array on commit. Empty
|
|
314
|
+
arrays render the empty-state but cannot be the submission
|
|
315
|
+
state.
|
|
316
|
+
- >-
|
|
317
|
+
At most ONE plans[] record may carry `recommended: true`. Two
|
|
318
|
+
recommended plans are visually ambiguous and defeat the
|
|
319
|
+
treatment's purpose.
|
|
320
|
+
- >-
|
|
321
|
+
`current` MUST match a plans[].id or be empty. Unknown ids
|
|
322
|
+
fail silently — no anchor row renders, no current-state
|
|
323
|
+
contextualization on siblings.
|
|
324
|
+
- >-
|
|
325
|
+
plans[].prices.monthly is REQUIRED on every record. annual is
|
|
326
|
+
optional; if absent and cycle="annual" the card renders the
|
|
327
|
+
monthly price with the annual cycle suffix and no `note`.
|
|
328
|
+
- >-
|
|
329
|
+
`cycle` MUST be `monthly` or `annual`. Other values are
|
|
330
|
+
coerced to `monthly` and a one-shot console.warn fires.
|
|
331
|
+
- >-
|
|
332
|
+
Use `layout="list"` for in-app settings panels where the picker
|
|
333
|
+
sits inside a <card-ui> and width is constrained. Use the
|
|
334
|
+
default `layout="grid"` for marketing pricing pages.
|
|
335
|
+
- >-
|
|
336
|
+
Wire `select` (not `change`) to the actual commit handler —
|
|
337
|
+
change fires on selection without commit (keyboard focus
|
|
338
|
+
traversal, programmatic value-set); select fires when the user
|
|
339
|
+
clicks the CTA inside a card.
|
|
340
|
+
|
|
341
|
+
anti_patterns:
|
|
342
|
+
- wrong: |
|
|
343
|
+
<plan-picker-ui plans='[
|
|
344
|
+
{"id":"p1","name":"Free","price":0},
|
|
345
|
+
{"id":"p2","name":"Pro","price":20}
|
|
346
|
+
]'></plan-picker-ui>
|
|
347
|
+
why: |
|
|
348
|
+
`price` is a top-level shortcut not in the schema. Prices are
|
|
349
|
+
keyed by cycle (monthly / annual). Without a cycle key, the
|
|
350
|
+
cycle toggle has nothing to switch and annual pricing is
|
|
351
|
+
invisible to consumers.
|
|
352
|
+
fix: |
|
|
353
|
+
<plan-picker-ui plans='[
|
|
354
|
+
{"id":"p1","name":"Free","prices":{"monthly":{"amount":0}}},
|
|
355
|
+
{"id":"p2","name":"Pro","prices":{"monthly":{"amount":20}}}
|
|
356
|
+
]'></plan-picker-ui>
|
|
357
|
+
- wrong: |
|
|
358
|
+
<plan-picker-ui plans='[
|
|
359
|
+
{"id":"t","name":"Team","recommended":true,"prices":{"monthly":{"amount":10}}},
|
|
360
|
+
{"id":"p","name":"Pro","recommended":true,"prices":{"monthly":{"amount":20}}}
|
|
361
|
+
]'></plan-picker-ui>
|
|
362
|
+
why: |
|
|
363
|
+
Two recommended plans cancel each other visually — there is no
|
|
364
|
+
"the" recommendation. Pick one.
|
|
365
|
+
fix: |
|
|
366
|
+
<plan-picker-ui plans='[
|
|
367
|
+
{"id":"t","name":"Team","prices":{"monthly":{"amount":10}}},
|
|
368
|
+
{"id":"p","name":"Pro","recommended":true,"prices":{"monthly":{"amount":20}}}
|
|
369
|
+
]'></plan-picker-ui>
|
|
370
|
+
- wrong: |
|
|
371
|
+
A <col-ui> of bespoke <card-ui> plan cards with manual radio
|
|
372
|
+
markup and a per-plan cycle pill.
|
|
373
|
+
why: |
|
|
374
|
+
Reinvents the radiogroup keyboard model and the cycle-toggle
|
|
375
|
+
semantics; pricing strings drift between this surface and the
|
|
376
|
+
marketing pricing page that uses the canonical primitive.
|
|
377
|
+
fix: |
|
|
378
|
+
<plan-picker-ui plans="..." cycle-toggle></plan-picker-ui>
|
|
379
|
+
|
|
380
|
+
examples:
|
|
381
|
+
- name: in-app-upgrade
|
|
382
|
+
description: In-app upgrade modal with three plans + annual default + cycle toggle.
|
|
383
|
+
a2ui: |
|
|
384
|
+
[
|
|
385
|
+
{
|
|
386
|
+
"id": "pp-upgrade",
|
|
387
|
+
"component": "PlanPicker",
|
|
388
|
+
"name": "plan",
|
|
389
|
+
"current": "plan_team",
|
|
390
|
+
"cycle": "annual",
|
|
391
|
+
"cycleToggle": true,
|
|
392
|
+
"plans": [
|
|
393
|
+
{"id":"plan_free","name":"Free","prices":{"monthly":{"amount":0}},"features":["3 projects","Community support"],"rank":1},
|
|
394
|
+
{"id":"plan_team","name":"Team","prices":{"monthly":{"amount":10,"unit":"seat"},"annual":{"amount":8,"unit":"seat","note":"billed annually"}},"features":["Unlimited projects","Email support"],"rank":2},
|
|
395
|
+
{"id":"plan_pro","name":"Pro","prices":{"monthly":{"amount":25,"unit":"seat"},"annual":{"amount":20,"unit":"seat","note":"billed annually"}},"features":["Everything in Team","Priority support"],"recommended":true,"rank":3}
|
|
396
|
+
]
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
- name: settings-list
|
|
400
|
+
description: Settings-page plan picker — single-column list inside a card.
|
|
401
|
+
a2ui: |
|
|
402
|
+
[
|
|
403
|
+
{
|
|
404
|
+
"id": "pp-settings",
|
|
405
|
+
"component": "PlanPicker",
|
|
406
|
+
"name": "plan",
|
|
407
|
+
"current": "plan_team",
|
|
408
|
+
"layout": "list",
|
|
409
|
+
"plans": [
|
|
410
|
+
{"id":"plan_team","name":"Team","prices":{"monthly":{"amount":10,"unit":"seat"}},"features":["Unlimited projects"],"rank":2},
|
|
411
|
+
{"id":"plan_pro","name":"Pro","prices":{"monthly":{"amount":25,"unit":"seat"}},"features":["Everything in Team","Priority support"],"recommended":true,"rank":3}
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
|
|
416
|
+
keywords:
|
|
417
|
+
- plan-picker
|
|
418
|
+
- plans
|
|
419
|
+
- pricing
|
|
420
|
+
- upgrade
|
|
421
|
+
- billing
|
|
422
|
+
- tier
|
|
423
|
+
- tiers
|
|
424
|
+
- subscription
|
|
425
|
+
- subscribe
|
|
426
|
+
- paywall
|
|
427
|
+
- choose-plan
|
|
428
|
+
- select-plan
|
|
429
|
+
- plan-selection
|
|
430
|
+
|
|
431
|
+
synonyms:
|
|
432
|
+
plan:
|
|
433
|
+
- tier
|
|
434
|
+
- subscription
|
|
435
|
+
- package
|
|
436
|
+
pricing:
|
|
437
|
+
- plan
|
|
438
|
+
- price
|
|
439
|
+
- tier
|
|
440
|
+
upgrade:
|
|
441
|
+
- plan-change
|
|
442
|
+
- subscribe
|
|
443
|
+
- choose-plan
|
|
444
|
+
|
|
445
|
+
related:
|
|
446
|
+
- OptionCard
|
|
447
|
+
- Segmented
|
|
448
|
+
- Segment
|
|
449
|
+
- Tag
|
|
450
|
+
- Button
|
|
451
|
+
- Card
|
|
452
|
+
- EmptyState
|
|
453
|
+
- Skeleton
|