@adia-ai/web-components 0.0.14 → 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 (41) hide show
  1. package/README.md +43 -1
  2. package/components/alert/alert.css +5 -0
  3. package/components/alert/alert.js +4 -2
  4. package/components/button/button.js +4 -1
  5. package/components/chart/chart.js +7 -4
  6. package/components/chat/chat-input.js +13 -2
  7. package/components/description-list/description-list.js +4 -3
  8. package/components/field/field.css +113 -63
  9. package/components/field/field.js +44 -142
  10. package/components/icon/icon.a2ui.json +1 -1
  11. package/components/icon/icon.css +16 -0
  12. package/components/icon/icon.js +18 -0
  13. package/components/icon/icon.yaml +6 -2
  14. package/components/index.js +7 -0
  15. package/components/input/input.a2ui.json +1 -1
  16. package/components/input/input.css +21 -23
  17. package/components/input/input.js +36 -9
  18. package/components/input/input.yaml +3 -1
  19. package/components/option-card/option-card.a2ui.json +262 -0
  20. package/components/option-card/option-card.css +215 -0
  21. package/components/option-card/option-card.js +158 -0
  22. package/components/option-card/option-card.yaml +234 -0
  23. package/components/rating/rating.a2ui.json +10 -0
  24. package/components/rating/rating.yaml +8 -0
  25. package/components/segment/segment.a2ui.json +5 -0
  26. package/components/segment/segment.css +2 -0
  27. package/components/segment/segment.js +21 -1
  28. package/components/segment/segment.yaml +5 -0
  29. package/components/textarea/textarea.css +3 -1
  30. package/components/textarea/textarea.js +2 -2
  31. package/components/tooltip/tooltip.js +10 -3
  32. package/core/data-stream.js +486 -0
  33. package/core/form.js +5 -0
  34. package/core/index.js +2 -0
  35. package/core/streams-bridge.js +96 -0
  36. package/package.json +1 -1
  37. package/styles/colors/semantics.css +21 -3
  38. package/styles/components.css +1 -0
  39. package/styles/prose.css +3 -7
  40. package/styles/tokens.css +7 -4
  41. package/styles/typography.css +6 -1
@@ -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
 
@@ -1,6 +1,9 @@
1
1
  /**
2
- * <segment-ui value="tab1" text="Tab 1"></segment-ui>
2
+ * <segment-ui value="tab1" text="Tab 1" icon="list"></segment-ui>
3
3
  * Child of segmented-ui. Represents one option.
4
+ *
5
+ * `icon=` stamps a leading <icon-ui name="…"> child on render.
6
+ * Mirrors the button-ui pattern.
4
7
  */
5
8
 
6
9
  import { AdiaElement } from '../../core/element.js';
@@ -9,6 +12,7 @@ class AdiaSegment extends AdiaElement {
9
12
  static properties = {
10
13
  value: { type: String, default: '', reflect: true },
11
14
  text: { type: String, default: '', reflect: true },
15
+ icon: { type: String, default: '', reflect: true },
12
16
  disabled: { type: Boolean, default: false, reflect: true },
13
17
  selected: { type: Boolean, default: false, reflect: true },
14
18
  };
@@ -25,6 +29,22 @@ class AdiaSegment extends AdiaElement {
25
29
  this.setAttribute('aria-checked', this.selected ? 'true' : 'false');
26
30
  if (this.disabled) this.setAttribute('aria-disabled', 'true');
27
31
  else this.removeAttribute('aria-disabled');
32
+
33
+ // Stamp/refresh leading icon-ui child when [icon] is set.
34
+ // Mirrors button-ui's pattern (button.js:29) — author code never
35
+ // hand-rolls an icon-ui inside a segment.
36
+ if (this.icon) {
37
+ const existing = this.querySelector(':scope > icon-ui');
38
+ if (!existing || existing.getAttribute('name') !== this.icon) {
39
+ existing?.remove();
40
+ const iconEl = document.createElement('icon-ui');
41
+ iconEl.setAttribute('name', this.icon);
42
+ iconEl.setAttribute('aria-hidden', 'true');
43
+ this.prepend(iconEl);
44
+ }
45
+ } else {
46
+ this.querySelector(':scope > icon-ui')?.remove();
47
+ }
28
48
  }
29
49
  }
30
50
  customElements.define('segment-ui', AdiaSegment);
@@ -13,6 +13,11 @@ props:
13
13
  type: boolean
14
14
  default: false
15
15
  reflect: true
16
+ icon:
17
+ description: Phosphor icon name. Rendered as a leading <icon-ui> child stamped on render.
18
+ type: string
19
+ default: ""
20
+ reflect: true
16
21
  selected:
17
22
  description: Whether this segment is currently selected. Managed by the parent Segmented container; don't set multiple siblings selected.
18
23
  type: boolean
@@ -22,6 +22,7 @@
22
22
  --textarea-easing: var(--a-easing);
23
23
 
24
24
  /* ── State ── */
25
+ --textarea-bg-hover: var(--a-ui-bg-hover);
25
26
  --textarea-fg-hover: var(--a-fg);
26
27
  --textarea-label-fg-focus: var(--a-fg-subtle);
27
28
  --textarea-bg-disabled: var(--a-ui-bg-disabled);
@@ -66,6 +67,7 @@
66
67
  transition: border-color var(--textarea-duration) var(--textarea-easing);
67
68
  }
68
69
  :scope:not([disabled]) [slot="text"]:hover {
70
+ background: var(--textarea-bg-hover);
69
71
  border-color: var(--textarea-border-hover);
70
72
  color: var(--textarea-fg-hover);
71
73
  }
@@ -89,7 +91,7 @@
89
91
 
90
92
  /* Placeholder */
91
93
  [slot="text"][data-empty]::before {
92
- content: attr(aria-placeholder);
94
+ content: attr(data-placeholder);
93
95
  color: var(--textarea-placeholder-fg);
94
96
  pointer-events: none;
95
97
  }
@@ -29,7 +29,7 @@ class AdiaTextarea extends AdiaFormElement {
29
29
  ${this.label ? `<label slot="label" label="${this.label}"></label>` : ''}
30
30
  <div slot="text" contenteditable="plaintext-only" tabindex="0"
31
31
  ${this.value ? '' : 'data-empty=""'}
32
- aria-placeholder="${this.placeholder}"></div>
32
+ data-placeholder="${this.placeholder}"></div>
33
33
  `;
34
34
  }
35
35
 
@@ -47,7 +47,7 @@ class AdiaTextarea extends AdiaFormElement {
47
47
  render() {
48
48
  if (!this.#textEl) return;
49
49
 
50
- this.#textEl.setAttribute('aria-placeholder', this.placeholder);
50
+ this.#textEl.setAttribute('data-placeholder', this.placeholder);
51
51
 
52
52
  if (this.disabled || this.readonly) {
53
53
  this.#textEl.contentEditable = 'false';
@@ -256,6 +256,7 @@ class AdiaTooltip extends AdiaElement {
256
256
  #positionAtPointer(x, y) {
257
257
  if (!this.#popover || x == null || y == null) return;
258
258
  const gap = 12;
259
+ const edgePad = 8;
259
260
  const popover = this.#popover;
260
261
  popover.style.position = 'fixed';
261
262
  popover.style.left = '0';
@@ -263,10 +264,16 @@ class AdiaTooltip extends AdiaElement {
263
264
  /* Force reflow to read offset dimensions now that content changed */
264
265
  const tw = popover.offsetWidth || 0;
265
266
  const th = popover.offsetHeight || 0;
266
- let px = x + gap;
267
+ /* Default: centered horizontally above the cursor, gap px clear of
268
+ the cursor. Clamp horizontally to viewport with edgePad on each
269
+ side (short tooltips near the viewport edge scoot inward rather
270
+ than drifting off-screen). Flip vertically when there's not
271
+ enough room above. */
272
+ let px = x - tw / 2;
267
273
  let py = y - th - gap;
268
- if (px + tw > window.innerWidth) px = x - tw - gap;
269
- if (py < 0) py = y + gap;
274
+ if (px < edgePad) px = edgePad;
275
+ if (px + tw > window.innerWidth - edgePad) px = window.innerWidth - tw - edgePad;
276
+ if (py < edgePad) py = y + gap;
270
277
  popover.style.left = `${px}px`;
271
278
  popover.style.top = `${py}px`;
272
279
  }