@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.
- package/README.md +43 -1
- package/components/alert/alert.css +5 -0
- package/components/alert/alert.js +4 -2
- package/components/button/button.js +4 -1
- package/components/chart/chart.js +7 -4
- package/components/chat/chat-input.js +13 -2
- package/components/description-list/description-list.js +4 -3
- package/components/field/field.css +113 -63
- package/components/field/field.js +44 -142
- package/components/icon/icon.a2ui.json +1 -1
- package/components/icon/icon.css +16 -0
- package/components/icon/icon.js +18 -0
- package/components/icon/icon.yaml +6 -2
- package/components/index.js +7 -0
- package/components/input/input.a2ui.json +1 -1
- package/components/input/input.css +21 -23
- package/components/input/input.js +36 -9
- package/components/input/input.yaml +3 -1
- package/components/option-card/option-card.a2ui.json +262 -0
- package/components/option-card/option-card.css +215 -0
- package/components/option-card/option-card.js +158 -0
- package/components/option-card/option-card.yaml +234 -0
- package/components/rating/rating.a2ui.json +10 -0
- package/components/rating/rating.yaml +8 -0
- package/components/segment/segment.a2ui.json +5 -0
- package/components/segment/segment.css +2 -0
- package/components/segment/segment.js +21 -1
- package/components/segment/segment.yaml +5 -0
- package/components/textarea/textarea.css +3 -1
- package/components/textarea/textarea.js +2 -2
- package/components/tooltip/tooltip.js +10 -3
- package/core/data-stream.js +486 -0
- package/core/form.js +5 -0
- package/core/index.js +2 -0
- package/core/streams-bridge.js +96 -0
- package/package.json +1 -1
- package/styles/colors/semantics.css +21 -3
- package/styles/components.css +1 -0
- package/styles/prose.css +3 -7
- package/styles/tokens.css +7 -4
- 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(
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
|
269
|
-
if (
|
|
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
|
}
|