@adia-ai/web-components 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/components/alert/alert.css +5 -0
  2. package/components/alert/alert.js +4 -2
  3. package/components/button/button.js +4 -1
  4. package/components/chat/chat-input.js +13 -2
  5. package/components/description-list/description-list.js +4 -3
  6. package/components/field/field.css +113 -63
  7. package/components/field/field.js +44 -142
  8. package/components/icon/icon.a2ui.json +1 -1
  9. package/components/icon/icon.css +16 -0
  10. package/components/icon/icon.js +18 -0
  11. package/components/icon/icon.yaml +6 -2
  12. package/components/index.js +1 -0
  13. package/components/input/input.a2ui.json +1 -1
  14. package/components/input/input.css +19 -23
  15. package/components/input/input.js +36 -9
  16. package/components/input/input.yaml +3 -1
  17. package/components/option-card/option-card.a2ui.json +262 -0
  18. package/components/option-card/option-card.css +215 -0
  19. package/components/option-card/option-card.js +158 -0
  20. package/components/option-card/option-card.yaml +234 -0
  21. package/components/rating/rating.a2ui.json +10 -0
  22. package/components/rating/rating.yaml +8 -0
  23. package/components/segment/segment.a2ui.json +5 -0
  24. package/components/segment/segment.css +2 -0
  25. package/components/segment/segment.js +21 -1
  26. package/components/segment/segment.yaml +5 -0
  27. package/components/textarea/textarea.css +1 -1
  28. package/components/textarea/textarea.js +2 -2
  29. package/core/data-stream.js +21 -0
  30. package/core/form.js +5 -0
  31. package/core/index.js +2 -0
  32. package/core/streams-bridge.js +96 -0
  33. package/package.json +1 -1
  34. package/styles/colors/semantics.css +8 -3
  35. package/styles/components.css +1 -0
  36. package/styles/prose.css +3 -7
  37. package/styles/tokens.css +7 -4
@@ -0,0 +1,215 @@
1
+ @scope (option-card-ui) {
2
+ :where(:scope) {
3
+ /* ── Container ── */
4
+ --option-card-padding-block: var(--a-space-3);
5
+ --option-card-padding-inline: var(--a-space-4);
6
+ --option-card-radius: var(--a-radius-md);
7
+ --option-card-bg: var(--a-bg);
8
+ --option-card-border: var(--a-border);
9
+ --option-card-gap-x: var(--a-space-3);
10
+ --option-card-gap-y: var(--a-space-1);
11
+
12
+ /* ── State: hover ── */
13
+ --option-card-bg-hover: var(--a-bg-muted);
14
+ --option-card-border-hover: var(--a-fg-subtle);
15
+
16
+ /* ── State: checked ── */
17
+ --option-card-bg-checked: var(--a-accent-muted);
18
+ --option-card-border-checked: var(--a-accent);
19
+
20
+ /* ── Indicator (CSS radio circle, same size + recipe as <radio-ui>). */
21
+ --option-card-radio-size: var(--a-toggle-size);
22
+ --option-card-radio-bg: var(--a-bg);
23
+ --option-card-radio-border: var(--a-border);
24
+ --option-card-radio-fill: var(--a-accent);
25
+ --option-card-radio-dot: var(--a-accent-fg);
26
+
27
+ /* ── Typography ── */
28
+ --option-card-heading-color: var(--a-fg);
29
+ --option-card-heading-color-checked: var(--a-fg-strong);
30
+ --option-card-heading-weight: var(--a-weight-medium);
31
+ --option-card-heading-size: var(--a-ui-size);
32
+ --option-card-desc-color: var(--a-fg-muted);
33
+ --option-card-desc-size: var(--a-ui-sm);
34
+ --option-card-desc-line-height: 1.4;
35
+ --option-card-icon-color: var(--a-fg-subtle);
36
+ --option-card-icon-color-checked: var(--a-fg-strong);
37
+ --option-card-icon-size: 1.5rem;
38
+
39
+ /* ── State: disabled ── */
40
+ --option-card-disabled-opacity: 0.6;
41
+
42
+ /* ── Focus ── */
43
+ --option-card-focus-ring: var(--a-focus-ring);
44
+
45
+ /* ── Transitions ── */
46
+ --option-card-duration: var(--a-duration-fast);
47
+ --option-card-easing: var(--a-easing);
48
+ }
49
+
50
+ /* ── Base — grid: indicator | (heading + description). Optional icon
51
+ sits between indicator and heading via :has() rules below. ── */
52
+ :scope {
53
+ box-sizing: border-box;
54
+ display: grid;
55
+ grid-template-columns: auto minmax(0, 1fr);
56
+ grid-template-areas:
57
+ "indicator heading"
58
+ "indicator description";
59
+ column-gap: var(--option-card-gap-x);
60
+ row-gap: var(--option-card-gap-y);
61
+ padding: var(--option-card-padding-block) var(--option-card-padding-inline);
62
+ border: 1px solid var(--option-card-border);
63
+ border-radius: var(--option-card-radius);
64
+ background: var(--option-card-bg);
65
+ cursor: pointer;
66
+ user-select: none;
67
+ outline: none;
68
+ transition:
69
+ background var(--option-card-duration) var(--option-card-easing),
70
+ border-color var(--option-card-duration) var(--option-card-easing);
71
+ }
72
+
73
+ /* When an icon slot is present, insert a third column for it
74
+ between indicator and heading. */
75
+ :scope:has(> [slot="icon"]) {
76
+ grid-template-columns: auto auto minmax(0, 1fr);
77
+ grid-template-areas:
78
+ "indicator icon heading"
79
+ "indicator icon description";
80
+ }
81
+ :scope > [slot="icon"] {
82
+ grid-area: icon;
83
+ align-self: start;
84
+ color: var(--option-card-icon-color);
85
+ --a-icon-size: var(--option-card-icon-size);
86
+ transition: color var(--option-card-duration) var(--option-card-easing);
87
+ }
88
+
89
+ /* ── Indicator — pure CSS radio circle, no separate <radio-ui>. */
90
+ :scope::before {
91
+ content: '';
92
+ grid-area: indicator;
93
+ width: var(--option-card-radio-size);
94
+ height: var(--option-card-radio-size);
95
+ border: 1.5px solid var(--option-card-radio-border);
96
+ border-radius: var(--a-radius-full);
97
+ background: var(--option-card-radio-bg);
98
+ align-self: start;
99
+ margin-block-start: 0.125rem;
100
+ flex-shrink: 0;
101
+ transition:
102
+ background var(--option-card-duration) var(--option-card-easing),
103
+ border-color var(--option-card-duration) var(--option-card-easing);
104
+ }
105
+
106
+ /* ── Slots — heading + description ── */
107
+ :scope > [slot="heading"] {
108
+ grid-area: heading;
109
+ color: var(--option-card-heading-color);
110
+ font-weight: var(--option-card-heading-weight);
111
+ font-size: var(--option-card-heading-size);
112
+ transition: color var(--option-card-duration) var(--option-card-easing);
113
+ }
114
+ :scope > [slot="description"] {
115
+ grid-area: description;
116
+ color: var(--option-card-desc-color);
117
+ font-size: var(--option-card-desc-size);
118
+ line-height: var(--option-card-desc-line-height);
119
+ }
120
+
121
+ /* ── Default slot — "spillover" content revealed when checked.
122
+ Aligns with the heading/description column (skips the indicator
123
+ gutter). Common pattern: an "Other" option with a free-text
124
+ textarea, conditional follow-up fields, etc. ── */
125
+ :scope > :not([slot]) {
126
+ grid-column: 2 / -1;
127
+ margin-block-start: var(--a-space-3);
128
+ display: none;
129
+ }
130
+ :scope:has(> [slot="icon"]) > :not([slot]) {
131
+ grid-column: 3 / -1;
132
+ }
133
+ :scope[checked] > :not([slot]) {
134
+ display: block;
135
+ }
136
+
137
+ /* ── State: hover (not checked, not disabled) ── */
138
+ :scope:not([checked]):not([disabled]):hover {
139
+ background: var(--option-card-bg-hover);
140
+ border-color: var(--option-card-border-hover);
141
+ }
142
+
143
+ /* ── State: checked — accent border + tinted bg + filled radio.
144
+ The indicator becomes an accent disc with a centered dot of
145
+ --option-card-radio-dot at 60% of the size, mirroring
146
+ radio-ui's recipe (radio.css:75-78). Done with a radial
147
+ gradient so a single pseudo-element carries both layers. */
148
+ :scope[checked] {
149
+ background: var(--option-card-bg-checked);
150
+ border-color: var(--option-card-border-checked);
151
+ }
152
+ :scope[checked]::before {
153
+ border-color: var(--option-card-radio-fill);
154
+ background:
155
+ radial-gradient(
156
+ circle,
157
+ var(--option-card-radio-dot) 0 30%,
158
+ var(--option-card-radio-fill) 30% 100%
159
+ );
160
+ }
161
+ /* Heading + icon shift to a strong color when checked — gives the
162
+ selected card a clear text-level emphasis on top of the bg/border
163
+ state, so picking is unambiguous beyond the radio dot alone. */
164
+ :scope[checked] > [slot="heading"] {
165
+ color: var(--option-card-heading-color-checked);
166
+ }
167
+ :scope[checked] > [slot="icon"] {
168
+ color: var(--option-card-icon-color-checked);
169
+ }
170
+
171
+ /* ── Layout: tile — icon top-left, indicator top-right, heading +
172
+ description below, all left-aligned. Used for hero pickers
173
+ (data source, role, plan tiles) where the icon is a primary
174
+ brand cue rather than secondary chrome. ── */
175
+ :scope[layout="tile"] {
176
+ grid-template-columns: minmax(0, 1fr) auto;
177
+ grid-template-areas:
178
+ "icon indicator"
179
+ "heading heading"
180
+ "description description";
181
+ column-gap: var(--option-card-gap-x);
182
+ row-gap: var(--option-card-gap-y);
183
+ padding: var(--a-space-4);
184
+ align-items: start;
185
+ }
186
+ :scope[layout="tile"] > [slot="icon"] {
187
+ grid-area: icon;
188
+ justify-self: start;
189
+ align-self: start;
190
+ --option-card-icon-size: 1.75rem;
191
+ }
192
+ :scope[layout="tile"]::before {
193
+ grid-area: indicator;
194
+ align-self: start;
195
+ justify-self: end;
196
+ margin-block-start: 0;
197
+ }
198
+ :scope[layout="tile"] > [slot="heading"] {
199
+ margin-block-start: var(--a-space-2);
200
+ }
201
+ :scope[layout="tile"] > :not([slot]) {
202
+ grid-column: 1 / -1;
203
+ }
204
+
205
+ /* ── State: disabled ── */
206
+ :scope[disabled] {
207
+ cursor: not-allowed;
208
+ opacity: var(--option-card-disabled-opacity);
209
+ }
210
+
211
+ /* ── Focus ── */
212
+ :scope:focus-visible {
213
+ box-shadow: var(--option-card-focus-ring);
214
+ }
215
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * <option-card-ui> — Selectable card with radio semantics.
3
+ *
4
+ * A "rich radio" — single-select-of-N where each option carries a
5
+ * heading, optional description, and optional leading icon. Siblings
6
+ * with the same `name` form a radiogroup. The whole card is the click
7
+ * target; a CSS-rendered radio circle in the top-left signals state.
8
+ *
9
+ * <option-card-ui name="use-case" value="build" checked
10
+ * heading="I'm building a product"
11
+ * description="Spinning up a new project — design, ship, iterate.">
12
+ * </option-card-ui>
13
+ *
14
+ * Rich content via slots:
15
+ *
16
+ * <option-card-ui name="plan" value="pro">
17
+ * <span slot="heading">Pro <badge-ui text="14-day trial"></badge-ui></span>
18
+ * <span slot="description">Unlimited members · advanced charts</span>
19
+ * </option-card-ui>
20
+ *
21
+ * Spillover content via the default slot — any unslotted child shows
22
+ * only when the card is `[checked]`. Used for "Other"-style fields:
23
+ *
24
+ * <option-card-ui name="reason" value="other"
25
+ * heading="Something else">
26
+ * <textarea-ui name="reason-detail" rows="3"></textarea-ui>
27
+ * </option-card-ui>
28
+ *
29
+ * Sibling navigation: arrow keys move focus and selection; Space/Enter
30
+ * select. Form-associated via AdiaFormElement, so `name=value` submits
31
+ * with the parent form when the card is checked.
32
+ */
33
+
34
+ import { AdiaFormElement } from '../../core/form.js';
35
+
36
+ class AdiaOptionCard extends AdiaFormElement {
37
+ static properties = {
38
+ ...AdiaFormElement.properties,
39
+ checked: { type: Boolean, default: false, reflect: true },
40
+ heading: { type: String, default: '', reflect: true },
41
+ description: { type: String, default: '', reflect: true },
42
+ icon: { type: String, default: '', reflect: true },
43
+ layout: { type: String, default: 'default', reflect: true },
44
+ };
45
+
46
+ static template = () => null;
47
+
48
+ connected() {
49
+ super.connected();
50
+ this.setAttribute('role', 'radio');
51
+ this.setAttribute('tabindex', '0');
52
+ this.#ensureLayout();
53
+ this.addEventListener('click', this.#select);
54
+ this.addEventListener('keydown', this.#onKey);
55
+ }
56
+
57
+ disconnected() {
58
+ super.disconnected();
59
+ this.removeEventListener('click', this.#select);
60
+ this.removeEventListener('keydown', this.#onKey);
61
+ }
62
+
63
+ render() {
64
+ this.setAttribute('aria-checked', String(this.checked));
65
+ if (this.disabled) this.setAttribute('aria-disabled', 'true');
66
+ else this.removeAttribute('aria-disabled');
67
+
68
+ // Keep slotted content in sync with attrs (only when slot was
69
+ // auto-stamped from the attr — don't clobber consumer-authored
70
+ // rich content).
71
+ const h = this.querySelector(':scope > [slot="heading"]');
72
+ if (h && h.dataset.fromAttr === 'true') h.textContent = this.heading || '';
73
+ const d = this.querySelector(':scope > [slot="description"]');
74
+ if (d && d.dataset.fromAttr === 'true') d.textContent = this.description || '';
75
+ const i = this.querySelector(':scope > [slot="icon"]');
76
+ if (i && i.dataset.fromAttr === 'true') i.setAttribute('name', this.icon || '');
77
+
78
+ if (this.checked) this.syncValue(this.value || 'on');
79
+ else this.syncValue('');
80
+ }
81
+
82
+ // ── Private ───────────────────────────────────────────────────────
83
+
84
+ /** Stamp heading / description / icon from attributes when the
85
+ * consumer hasn't already provided slotted content. */
86
+ #ensureLayout() {
87
+ if (this.heading && !this.querySelector(':scope > [slot="heading"]')) {
88
+ const el = document.createElement('span');
89
+ el.setAttribute('slot', 'heading');
90
+ el.dataset.fromAttr = 'true';
91
+ el.textContent = this.heading;
92
+ this.appendChild(el);
93
+ }
94
+ if (this.description && !this.querySelector(':scope > [slot="description"]')) {
95
+ const el = document.createElement('span');
96
+ el.setAttribute('slot', 'description');
97
+ el.dataset.fromAttr = 'true';
98
+ el.textContent = this.description;
99
+ this.appendChild(el);
100
+ }
101
+ if (this.icon && !this.querySelector(':scope > [slot="icon"]')) {
102
+ const el = document.createElement('icon-ui');
103
+ el.setAttribute('slot', 'icon');
104
+ el.dataset.fromAttr = 'true';
105
+ el.setAttribute('name', this.icon);
106
+ this.appendChild(el);
107
+ }
108
+ }
109
+
110
+ #select = () => {
111
+ if (this.disabled || this.readonly || this.checked) return;
112
+ const group = this.#group();
113
+ if (group) {
114
+ for (const el of group.querySelectorAll(`option-card-ui[name="${this.name}"]`)) {
115
+ if (el !== this && el.checked) el.checked = false;
116
+ }
117
+ }
118
+ this.checked = true;
119
+ this.dispatchEvent(new Event('change', { bubbles: true }));
120
+ };
121
+
122
+ #onKey = (e) => {
123
+ // Don't intercept keys when focus is in a nested form control
124
+ // (textarea, input, contenteditable). Otherwise Space/Enter would
125
+ // block typing and arrow keys would navigate siblings instead of
126
+ // moving the cursor.
127
+ if (e.target !== this) return;
128
+ if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); this.#select(); return; }
129
+ if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
130
+ e.preventDefault();
131
+ const next = this.#sibling(1);
132
+ if (next) { next.focus(); next.click(); }
133
+ }
134
+ if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
135
+ e.preventDefault();
136
+ const prev = this.#sibling(-1);
137
+ if (prev) { prev.focus(); prev.click(); }
138
+ }
139
+ };
140
+
141
+ /** The radiogroup root — explicit `<fieldset>` / `[role="radiogroup"]`
142
+ * if present, else the immediate parent. */
143
+ #group() {
144
+ return this.closest('fieldset, [role="radiogroup"]') || this.parentElement;
145
+ }
146
+
147
+ #sibling(dir) {
148
+ const group = this.#group();
149
+ const items = [...(group?.querySelectorAll(`option-card-ui[name="${this.name}"]`) || [])];
150
+ const i = items.indexOf(this);
151
+ if (i < 0) return null;
152
+ return items[(i + dir + items.length) % items.length] || null;
153
+ }
154
+ }
155
+
156
+ customElements.define('option-card-ui', AdiaOptionCard);
157
+
158
+ export { AdiaOptionCard };
@@ -0,0 +1,234 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
3
+ name: AdiaOptionCard
4
+ tag: option-card-ui
5
+ component: OptionCard
6
+ category: input
7
+ version: 1
8
+ description: >-
9
+ Selectable card with radio semantics. A "rich radio" — single-select
10
+ one of N where each option carries a heading, optional description,
11
+ and optional leading icon. Siblings sharing a `name` form a
12
+ radiogroup. The whole card is the click target; a CSS-rendered radio
13
+ circle in the top-left signals state. Form-associated, so `name=value`
14
+ submits with the parent form when checked.
15
+ props:
16
+ name:
17
+ description: Form control name. Siblings sharing a name form a radiogroup.
18
+ type: string
19
+ default: ""
20
+ value:
21
+ description: Form value submitted when checked (defaults to `on` if empty).
22
+ type: string
23
+ default: ""
24
+ checked:
25
+ description: Whether this card is currently selected.
26
+ type: boolean
27
+ default: false
28
+ reflect: true
29
+ disabled:
30
+ description: Disables interaction and dims the card.
31
+ type: boolean
32
+ default: false
33
+ reflect: true
34
+ required:
35
+ description: Marks the radiogroup as requiring a selection for form validation.
36
+ type: boolean
37
+ default: false
38
+ reflect: true
39
+ heading:
40
+ description: Heading text. Stamped into a [slot="heading"] span when no slotted heading is provided.
41
+ type: string
42
+ default: ""
43
+ reflect: true
44
+ description:
45
+ description: Description text. Stamped into a [slot="description"] span when no slotted description is provided.
46
+ type: string
47
+ default: ""
48
+ reflect: true
49
+ icon:
50
+ description: Optional Phosphor icon name. Stamped as a leading <icon-ui slot="icon"> when set.
51
+ type: string
52
+ default: ""
53
+ reflect: true
54
+ layout:
55
+ description: >-
56
+ Internal layout. `default` puts the indicator on the left and the icon
57
+ adjacent. `tile` stacks vertically — icon top-left, indicator top-right,
58
+ heading + description below — for hero pickers (source / role / plan
59
+ tiles) where the icon is a primary brand cue.
60
+ type: string
61
+ default: default
62
+ enum: [default, tile]
63
+ reflect: true
64
+ events:
65
+ change:
66
+ description: Fired when this card becomes selected (bubbles).
67
+ slots:
68
+ heading:
69
+ description: Rich heading content. Overrides the `heading` attribute when present.
70
+ description:
71
+ description: Rich description content. Overrides the `description` attribute when present.
72
+ icon:
73
+ description: Custom icon element. Overrides the `icon` attribute when present.
74
+ default:
75
+ description: >-
76
+ Spillover content revealed only when the card is checked — typically a
77
+ follow-up form field (e.g. a textarea on an "Other" option, conditional
78
+ inputs that depend on the selection). Aligns with the heading/description
79
+ column; hidden via `display: none` when not checked.
80
+ states:
81
+ - name: idle
82
+ description: Default, ready for interaction.
83
+ - name: hover
84
+ description: Pointer over a non-checked card.
85
+ selector: ":not([checked]):not([disabled]):hover"
86
+ - name: checked
87
+ description: Selected — accent border, tinted background, filled radio circle.
88
+ attribute: checked
89
+ - name: disabled
90
+ description: Non-interactive; dimmed.
91
+ attribute: disabled
92
+ - name: focused
93
+ description: Keyboard focus ring.
94
+ selector: ":focus-visible"
95
+ traits:
96
+ - focusable
97
+ tokens:
98
+ --option-card-padding-block:
99
+ description: Vertical padding inside the card.
100
+ --option-card-padding-inline:
101
+ description: Horizontal padding inside the card.
102
+ --option-card-radius:
103
+ description: Card corner radius.
104
+ --option-card-bg:
105
+ description: Default background.
106
+ --option-card-border:
107
+ description: Default border color.
108
+ --option-card-gap-x:
109
+ description: Horizontal gap between indicator (and icon) and content.
110
+ --option-card-gap-y:
111
+ description: Vertical gap between heading and description.
112
+ --option-card-bg-hover:
113
+ description: Hover background (non-checked).
114
+ --option-card-border-hover:
115
+ description: Hover border color (non-checked).
116
+ --option-card-bg-checked:
117
+ description: Background when checked.
118
+ --option-card-border-checked:
119
+ description: Border color when checked.
120
+ --option-card-radio-size:
121
+ description: Diameter of the indicator circle.
122
+ --option-card-radio-bg:
123
+ description: Indicator background when not checked.
124
+ --option-card-radio-border:
125
+ description: Indicator border when not checked.
126
+ --option-card-radio-fill:
127
+ description: Indicator fill color when checked.
128
+ --option-card-radio-dot:
129
+ description: Inner dot color when checked.
130
+ --option-card-heading-color:
131
+ description: Heading text color.
132
+ --option-card-heading-color-checked:
133
+ description: Heading text color when the card is checked (defaults to `--a-fg-strong` so the selected card reads with extra emphasis on top of the bg/border state).
134
+ --option-card-heading-weight:
135
+ description: Heading font weight.
136
+ --option-card-heading-size:
137
+ description: Heading font size.
138
+ --option-card-desc-color:
139
+ description: Description text color.
140
+ --option-card-desc-size:
141
+ description: Description font size.
142
+ --option-card-desc-line-height:
143
+ description: Description line height.
144
+ --option-card-icon-color:
145
+ description: Leading icon color when the card is not checked.
146
+ --option-card-icon-color-checked:
147
+ description: Leading icon color when the card is checked.
148
+ --option-card-icon-size:
149
+ description: Leading icon size (sets `--a-icon-size` on the slotted icon-ui).
150
+ --option-card-disabled-opacity:
151
+ description: Opacity multiplier when disabled.
152
+ --option-card-focus-ring:
153
+ description: Focus ring (box-shadow value).
154
+ --option-card-duration:
155
+ description: Transition duration for hover / checked state changes.
156
+ --option-card-easing:
157
+ description: Transition easing.
158
+ a2ui:
159
+ rules: []
160
+ anti_patterns: []
161
+ examples:
162
+ - name: use-case-picker
163
+ description: A four-option pick-one for "what brings you here" — radio-card behavior with heading + description per option.
164
+ a2ui: >-
165
+ [
166
+ {
167
+ "id": "root",
168
+ "component": "Column",
169
+ "gap": "2",
170
+ "children": ["build", "explore", "migrate", "evaluate"]
171
+ },
172
+ {
173
+ "id": "build",
174
+ "component": "OptionCard",
175
+ "name": "use-case",
176
+ "value": "build",
177
+ "checked": true,
178
+ "heading": "I'm building a product",
179
+ "description": "Spinning up a new project — design, ship, iterate."
180
+ },
181
+ {
182
+ "id": "explore",
183
+ "component": "OptionCard",
184
+ "name": "use-case",
185
+ "value": "explore",
186
+ "heading": "I'm exploring the product",
187
+ "description": "Kicking the tires before bringing a team along."
188
+ },
189
+ {
190
+ "id": "migrate",
191
+ "component": "OptionCard",
192
+ "name": "use-case",
193
+ "value": "migrate",
194
+ "heading": "I'm migrating from another tool",
195
+ "description": "Moving an existing workspace and want a smooth port."
196
+ },
197
+ {
198
+ "id": "evaluate",
199
+ "component": "OptionCard",
200
+ "name": "use-case",
201
+ "value": "evaluate",
202
+ "heading": "I'm evaluating for my team",
203
+ "description": "Comparing options and want to dig into specifics."
204
+ }
205
+ ]
206
+ keywords:
207
+ - option
208
+ - card
209
+ - radio
210
+ - select
211
+ - choice
212
+ - picker
213
+ - tier
214
+ - plan
215
+ - onboarding
216
+ - registration
217
+ synonyms:
218
+ radio:
219
+ - option-card
220
+ - radio
221
+ - select
222
+ picker:
223
+ - option-card
224
+ - radio
225
+ - select
226
+ tier:
227
+ - option-card
228
+ - card
229
+ - plan
230
+ related:
231
+ - radio
232
+ - card
233
+ - check
234
+ - segmented
@@ -55,6 +55,16 @@
55
55
  "description": "Current rating value (0..max, supports 0.5 steps when allowHalf)",
56
56
  "type": "number",
57
57
  "default": 0
58
+ },
59
+ "variant": {
60
+ "description": "Color variant — applied via `:scope[variant=...]` selectors in CSS, no JS reflection needed",
61
+ "type": "string",
62
+ "enum": [
63
+ "",
64
+ "accent",
65
+ "warning"
66
+ ],
67
+ "default": ""
58
68
  }
59
69
  },
60
70
  "required": [
@@ -43,6 +43,14 @@ props:
43
43
  description: Current rating value (0..max, supports 0.5 steps when allowHalf)
44
44
  type: number
45
45
  default: 0
46
+ variant:
47
+ description: Color variant — applied via `:scope[variant=...]` selectors in CSS, no JS reflection needed
48
+ type: string
49
+ default: ""
50
+ enum:
51
+ - ""
52
+ - accent
53
+ - warning
46
54
  events:
47
55
  "[object Object]":
48
56
  description: "Fired on [object Object]."
@@ -21,6 +21,11 @@
21
21
  "type": "boolean",
22
22
  "default": false
23
23
  },
24
+ "icon": {
25
+ "description": "Phosphor icon name. Rendered as a leading <icon-ui> child stamped on render.",
26
+ "type": "string",
27
+ "default": ""
28
+ },
24
29
  "selected": {
25
30
  "description": "Whether this segment is currently selected. Managed by the parent Segmented container; don't set multiple siblings selected.",
26
31
  "type": "boolean",
@@ -2,6 +2,7 @@
2
2
  :where(:scope) {
3
3
  /* ── Layout ── */
4
4
  --segment-px: var(--a-ui-px);
5
+ --segment-gap: var(--a-space-1);
5
6
  --segment-radius: calc(var(--a-radius) - 2px);
6
7
 
7
8
  /* ── Typography ── */
@@ -30,6 +31,7 @@
30
31
  justify-content: center;
31
32
  align-self: stretch;
32
33
  min-width: 0;
34
+ gap: var(--segment-gap);
33
35
 
34
36
  padding-inline: var(--segment-px);
35
37