@adia-ai/web-components 0.0.27 → 0.0.29

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 (93) hide show
  1. package/README.md +4 -8
  2. package/a2ui/index.js +1 -1
  3. package/components/agent-questions/agent-questions.css +20 -1
  4. package/components/agent-trace/agent-trace.css +17 -12
  5. package/components/badge/badge.js +9 -1
  6. package/components/breadcrumb/breadcrumb.a2ui.json +16 -1
  7. package/components/breadcrumb/breadcrumb.css +27 -0
  8. package/components/breadcrumb/breadcrumb.js +95 -17
  9. package/components/breadcrumb/breadcrumb.yaml +15 -1
  10. package/components/canvas/canvas.js +1 -1
  11. package/components/chart/chart.css +20 -13
  12. package/components/chart/chart.js +49 -17
  13. package/components/chart-legend/chart-legend.css +30 -54
  14. package/components/chart-legend/chart-legend.js +48 -30
  15. package/components/command/command.js +52 -1
  16. package/components/feed/feed-item.yaml +50 -0
  17. package/components/feed/feed.a2ui.json +59 -0
  18. package/components/feed/feed.css +150 -0
  19. package/components/feed/feed.js +385 -0
  20. package/components/feed/feed.yaml +33 -0
  21. package/components/index.js +2 -0
  22. package/components/swatch/swatch.a2ui.json +116 -0
  23. package/components/swatch/swatch.css +141 -0
  24. package/components/swatch/swatch.js +121 -0
  25. package/components/swatch/swatch.yaml +101 -0
  26. package/components/timeline/timeline.css +5 -1
  27. package/components/toast/toast.js +48 -178
  28. package/components/tooltip/tooltip.css +3 -3
  29. package/core/provider.js +1 -0
  30. package/index.css +3 -2
  31. package/index.js +15 -7
  32. package/package.json +1 -5
  33. package/styles/components.css +1 -0
  34. package/patterns/a2ui-root/a2ui-root.a2ui.json +0 -125
  35. package/patterns/a2ui-root/a2ui-root.js +0 -191
  36. package/patterns/a2ui-root/a2ui-root.yaml +0 -87
  37. package/patterns/adia-chat/adia-chat.a2ui.json +0 -149
  38. package/patterns/adia-chat/adia-chat.css +0 -10
  39. package/patterns/adia-chat/adia-chat.js +0 -297
  40. package/patterns/adia-chat/adia-chat.yaml +0 -118
  41. package/patterns/adia-chat/css/adia-chat.empty.css +0 -12
  42. package/patterns/adia-chat/css/adia-chat.layout.css +0 -60
  43. package/patterns/adia-chat/css/adia-chat.markdown.css +0 -74
  44. package/patterns/adia-chat/css/adia-chat.messages.css +0 -87
  45. package/patterns/adia-chat/css/adia-chat.streaming.css +0 -30
  46. package/patterns/adia-chat/css/adia-chat.tokens.css +0 -95
  47. package/patterns/adia-editor/adia-editor.a2ui.json +0 -73
  48. package/patterns/adia-editor/adia-editor.css +0 -6
  49. package/patterns/adia-editor/adia-editor.js +0 -56
  50. package/patterns/adia-editor/adia-editor.yaml +0 -59
  51. package/patterns/adia-editor/css/adia-editor.layout.css +0 -171
  52. package/patterns/adia-editor/css/adia-editor.tokens.css +0 -28
  53. package/patterns/app-nav/app-nav.a2ui.json +0 -89
  54. package/patterns/app-nav/app-nav.css +0 -92
  55. package/patterns/app-nav/app-nav.js +0 -112
  56. package/patterns/app-nav/app-nav.yaml +0 -54
  57. package/patterns/app-nav-group/app-nav-group.a2ui.json +0 -82
  58. package/patterns/app-nav-group/app-nav-group.css +0 -264
  59. package/patterns/app-nav-group/app-nav-group.js +0 -116
  60. package/patterns/app-nav-group/app-nav-group.yaml +0 -59
  61. package/patterns/app-nav-item/app-nav-item.a2ui.json +0 -83
  62. package/patterns/app-nav-item/app-nav-item.css +0 -162
  63. package/patterns/app-nav-item/app-nav-item.js +0 -42
  64. package/patterns/app-nav-item/app-nav-item.yaml +0 -62
  65. package/patterns/app-shell/app-shell.a2ui.json +0 -129
  66. package/patterns/app-shell/app-shell.css +0 -14
  67. package/patterns/app-shell/app-shell.js +0 -251
  68. package/patterns/app-shell/app-shell.yaml +0 -89
  69. package/patterns/app-shell/css/app-shell.collapsed.css +0 -86
  70. package/patterns/app-shell/css/app-shell.helpers.css +0 -42
  71. package/patterns/app-shell/css/app-shell.main.css +0 -172
  72. package/patterns/app-shell/css/app-shell.shell.css +0 -44
  73. package/patterns/app-shell/css/app-shell.sidebar.css +0 -161
  74. package/patterns/app-shell/css/app-shell.templates.css +0 -214
  75. package/patterns/app-shell/css/app-shell.tokens.css +0 -119
  76. package/patterns/gen-ui/gen-ui.a2ui.json +0 -72
  77. package/patterns/gen-ui/gen-ui.css +0 -83
  78. package/patterns/gen-ui/gen-ui.js +0 -136
  79. package/patterns/gen-ui/gen-ui.yaml +0 -43
  80. package/patterns/index.js +0 -11
  81. package/patterns/section-nav/section-nav.a2ui.json +0 -91
  82. package/patterns/section-nav/section-nav.css +0 -60
  83. package/patterns/section-nav/section-nav.js +0 -42
  84. package/patterns/section-nav/section-nav.yaml +0 -58
  85. package/patterns/section-nav-group/section-nav-group.a2ui.json +0 -95
  86. package/patterns/section-nav-group/section-nav-group.css +0 -74
  87. package/patterns/section-nav-group/section-nav-group.js +0 -84
  88. package/patterns/section-nav-group/section-nav-group.yaml +0 -66
  89. package/patterns/section-nav-item/section-nav-item.a2ui.json +0 -97
  90. package/patterns/section-nav-item/section-nav-item.css +0 -106
  91. package/patterns/section-nav-item/section-nav-item.js +0 -66
  92. package/patterns/section-nav-item/section-nav-item.yaml +0 -70
  93. package/styles/layouts/admin.css +0 -7
@@ -48,77 +48,53 @@
48
48
  align-items: flex-start;
49
49
  }
50
50
 
51
- /* Rows both <button> (interactive) and <span> (static) variants */
52
- [data-row] {
53
- all: unset;
54
- box-sizing: border-box;
55
- display: inline-flex;
56
- align-items: center;
57
- gap: var(--chart-legend-row-gap);
58
- padding: var(--chart-legend-py) var(--a-space-1);
59
- border-radius: var(--chart-legend-row-radius);
51
+ /* Rows are <badge-ui> chips. We override badge-ui's default tokens
52
+ to give them a quieter, click-to-toggle appearance — transparent
53
+ bg by default (so they read as inline text not chips), highlighted
54
+ on hover when interactive. The pill chrome (radius, padding,
55
+ font-size, line-height) comes from badge-ui itself. */
56
+ badge-ui[data-row] {
57
+ --badge-bg: transparent;
58
+ --badge-fg: var(--chart-legend-fg);
59
+ --badge-radius: var(--chart-legend-row-radius);
60
+ --badge-px: var(--a-space-1);
61
+ --badge-py: var(--chart-legend-py);
62
+ --badge-gap: var(--chart-legend-row-gap);
63
+ --badge-font-size: var(--chart-legend-font-size);
60
64
  cursor: pointer;
61
- color: inherit;
62
- line-height: 1.2;
63
65
  transition: background var(--a-duration-fast) var(--a-easing),
64
66
  color var(--a-duration-fast) var(--a-easing);
65
67
  }
66
68
 
67
69
  /* Static rows — no interaction */
68
- :scope[static] [data-row] {
70
+ :scope[static] badge-ui[data-row] {
69
71
  cursor: default;
70
72
  }
71
73
 
72
- button[data-row]:hover {
73
- background: var(--chart-legend-row-hover);
74
- color: var(--chart-legend-row-fg-hover);
74
+ badge-ui[data-row][role="button"]:hover {
75
+ --badge-bg: var(--chart-legend-row-hover);
76
+ --badge-fg: var(--chart-legend-row-fg-hover);
75
77
  }
76
78
 
77
- button[data-row]:focus-visible {
79
+ badge-ui[data-row][role="button"]:focus-visible {
78
80
  outline: none;
79
81
  box-shadow: var(--chart-legend-focus-ring);
80
82
  }
81
83
 
82
- /* Inactive (toggled-off) rows — dim the swatch + fade label */
83
- [data-row]:not([data-active]) {
84
- color: var(--chart-legend-fg-inactive);
84
+ /* Inactive (toggled-off) rows — dim the swatch + fade label. Drives
85
+ badge-ui's --badge-fg so the label fades alongside the swatch. */
86
+ badge-ui[data-row]:not([data-active]) {
87
+ --badge-fg: var(--chart-legend-fg-inactive);
85
88
  }
86
- [data-row]:not([data-active]) [data-swatch] {
89
+ badge-ui[data-row]:not([data-active]) swatch-ui {
87
90
  opacity: 0.4;
88
91
  }
89
92
 
90
- /* Swatch dedicated span per row, styled by the legend's [shape] attr.
91
- --swatch-color is set inline per-row from --color-{key} with a
92
- --chart-{slot} fallback; overrides cascade through atomically. */
93
- [data-swatch] {
94
- display: inline-block;
95
- flex-shrink: 0;
96
- background: var(--swatch-color, var(--chart-0));
97
- line-height: 0;
98
- }
99
-
100
- :scope[shape="dot"] [data-swatch] {
101
- width: var(--chart-legend-swatch-size);
102
- height: var(--chart-legend-swatch-size);
103
- border-radius: 50%;
104
- }
105
-
106
- :scope[shape="square"] [data-swatch] {
107
- width: var(--chart-legend-swatch-size);
108
- height: var(--chart-legend-swatch-size);
109
- border-radius: var(--a-radius-sm);
110
- }
111
-
112
- :scope[shape="line"] [data-swatch] {
113
- width: calc(var(--chart-legend-swatch-size) * 1.75);
114
- height: var(--chart-legend-line-w);
115
- border-radius: var(--chart-legend-line-w);
116
- }
117
-
118
- :scope[shape="dashed"] [data-swatch] {
119
- width: calc(var(--chart-legend-swatch-size) * 1.75);
120
- height: 0;
121
- background: transparent;
122
- border-top: var(--chart-legend-line-w) dashed var(--swatch-color, var(--chart-0));
123
- }
93
+ /* Swatch is composed via <swatch-ui shape="…">; per-shape styling
94
+ lives in swatch.css. The legend just hands its [shape] attr through
95
+ and writes --swatch-color inline. Pre-2026-05-01 this file owned a
96
+ `<span data-swatch>` element + dot/square/line/dashed CSS rules,
97
+ which collided with the docs-shell's `[data-swatch]` token-demo
98
+ rule (cascade leak). Composing the primitive moves the styling to
99
+ one place. */
124
100
  }
@@ -14,14 +14,17 @@
14
14
  * override when both are provided.)
15
15
  * items — JSON array of {key, label, slot?, pct?} legend items.
16
16
  * Takes precedence over [for] if both are provided.
17
- * shape — dot | square | line | dashed. Default: dot. Maps to
18
- * badge-ui's icon slot (dot = phosphor 'dot', square =
19
- * 'square-fill', line/dashed = custom CSS swatches).
17
+ * shape — dot | square | line | dashed. Default: dot. The swatch is
18
+ * a `[data-swatch]` span inside each badge-ui row, styled by
19
+ * the legend's [shape] attr. Custom span (rather than badge-
20
+ * ui's [icon] slot) is required because line/dashed shapes
21
+ * aren't representable as icon glyphs.
20
22
  * position — top | bottom | left | right. Layout hint; actual placement
21
23
  * is where the element is placed in the DOM. Drives
22
24
  * flex-direction.
23
- * static — when set, rows render as non-interactive <span>s. Default
24
- * is interactive <button>s that toggle.
25
+ * static — when set, rows render as non-interactive badges (no
26
+ * role/tabindex/handlers). Default is interactive
27
+ * `role="button"` badges that toggle on click + Enter/Space.
25
28
  * on-toggle — hide | opacity. Default hide. Escape hatch per OD-CHART-09.
26
29
  * Reported via the `toggle` event detail; chart-ui reads it
27
30
  * when wired via [for].
@@ -127,45 +130,59 @@ class AdiaChartLegend extends AdiaElement {
127
130
  const slot = item.slot != null ? item.slot : 0;
128
131
  const active = !this.#hiddenKeys.has(key);
129
132
 
130
- const row = this.static
131
- ? document.createElement('span')
132
- : document.createElement('button');
133
-
133
+ /* Each row is a <badge-ui> chip — the canonical AdiaUI primitive for
134
+ legend pills (per badge.yaml's documented "chart-legend" example).
135
+ badge-ui carries the pill chrome (radius, padding, font-size, hover
136
+ affordance), the legend layers shape-specific swatch CSS + the
137
+ click-to-toggle behavior on top. Interactive rows get
138
+ role="button" + tabindex="0" + Enter/Space handlers so they're
139
+ keyboard-operable without a wrapping <button>. */
140
+ const row = document.createElement('badge-ui');
134
141
  row.setAttribute('data-row', '');
135
- row.setAttribute('role', 'listitem');
142
+ row.setAttribute('role', this.static ? 'listitem' : 'button');
143
+ row.setAttribute('text', label);
136
144
  if (key) row.setAttribute('data-key', key);
137
145
  if (active) row.setAttribute('data-active', '');
138
- if (!this.static) row.setAttribute('type', 'button');
139
-
140
- /* Swatch + label. A dedicated `[data-swatch]` span lets shape-specific
141
- CSS handle dot/square/line/dashed variants without fighting badge-
142
- ui's icon slot (which is shadow-DOM-less and doesn't expose an
143
- `icon` partprevious badge-ui compose produced visual artifacts
144
- for line/dashed shapes). Color resolves via --color-{key} with a
145
- chart-{slot} fallback, mirroring chart-ui's per-series CSS var
146
- injection so ancestor overrides cascade through atomically. */
147
- const swatch = document.createElement('span');
148
- swatch.setAttribute('data-swatch', '');
146
+ if (!this.static) {
147
+ row.setAttribute('tabindex', '0');
148
+ row.setAttribute('aria-pressed', active ? 'true' : 'false');
149
+ }
150
+
151
+ /* Swatchcomposed via `<swatch-ui>` with the legend's [shape] attr
152
+ passed through. Pre-2026-05-01 this was a hand-rolled `<span
153
+ data-swatch>` plus per-shape CSS in this file; that drift caused
154
+ a cascade-leak collision with the docs-shell's `[data-swatch]`
155
+ demo rule (token-colors page). Compose the primitive instead and
156
+ the swatch's dot / square / line / dashed variants live in one
157
+ place. Color resolves via --color-{key} with a --a-data-{slot}
158
+ fallback; the legend can render standalone (without a chart-ui
159
+ ancestor) so the fallback must reference the global data palette
160
+ directly, not chart-ui's scoped --chart-{N} alias. */
161
+ const swatchShape = this.shape || 'dot';
162
+ const swatch = document.createElement('swatch-ui');
163
+ swatch.setAttribute('shape', swatchShape);
164
+ swatch.setAttribute('size', 'sm');
149
165
  const swatchColor = key
150
- ? `var(--color-${key}, var(--chart-${slot}))`
151
- : `var(--chart-${slot})`;
166
+ ? `var(--color-${key}, var(--a-data-${slot}))`
167
+ : `var(--a-data-${slot})`;
152
168
  swatch.style.setProperty('--swatch-color', swatchColor);
153
169
  row.appendChild(swatch);
154
170
 
155
- const labelEl = document.createElement('span');
156
- labelEl.setAttribute('data-label', '');
157
- labelEl.textContent = label;
158
- row.appendChild(labelEl);
159
-
160
171
  if (!this.static) {
161
- row.addEventListener('click', (e) => this.#onRowClick(e, key));
172
+ row.addEventListener('click', (e) => this.#onRowToggle(e, key));
173
+ row.addEventListener('keydown', (e) => {
174
+ if (e.key === 'Enter' || e.key === ' ') {
175
+ e.preventDefault();
176
+ this.#onRowToggle(e, key);
177
+ }
178
+ });
162
179
  }
163
180
 
164
181
  this.appendChild(row);
165
182
  }
166
183
  }
167
184
 
168
- #onRowClick(e, key) {
185
+ #onRowToggle(e, key) {
169
186
  if (!key) return;
170
187
  const currentlyActive = !this.#hiddenKeys.has(key);
171
188
  const newActive = !currentlyActive;
@@ -175,6 +192,7 @@ class AdiaChartLegend extends AdiaElement {
175
192
  const row = e.currentTarget;
176
193
  if (newActive) row.setAttribute('data-active', '');
177
194
  else row.removeAttribute('data-active');
195
+ row.setAttribute('aria-pressed', newActive ? 'true' : 'false');
178
196
 
179
197
  this.dispatchEvent(new CustomEvent('toggle', {
180
198
  bubbles: true,
@@ -29,6 +29,7 @@ class AdiaCommand extends AdiaElement {
29
29
  static template = () => null;
30
30
 
31
31
  #items = [];
32
+ #recents = [];
32
33
  #activeIdx = -1;
33
34
  #inputEl = null;
34
35
  #listEl = null;
@@ -37,6 +38,8 @@ class AdiaCommand extends AdiaElement {
37
38
  #bound = false;
38
39
  #itemByEl = new WeakMap();
39
40
 
41
+ static #RECENTS_MAX = 3;
42
+
40
43
  #onListClick = (e) => {
41
44
  const el = e.target instanceof Element ? e.target.closest('[role="option"]:not([aria-disabled])') : null;
42
45
  if (!el) return;
@@ -99,7 +102,15 @@ class AdiaCommand extends AdiaElement {
99
102
  // ── Public API ──
100
103
 
101
104
  get value() { return this.#inputEl?.value || ''; }
102
- set value(v) { if (this.#inputEl) this.#inputEl.value = v; }
105
+ set value(v) {
106
+ if (!this.#inputEl) return;
107
+ const next = v ?? '';
108
+ this.#inputEl.value = next;
109
+ // Keep the rendered list in sync with the visible input — the host
110
+ // commonly clears value on (re)open, and consumers expect to see the
111
+ // full list (plus recents) rather than the previous filter's residue.
112
+ this.#renderItems(next);
113
+ }
103
114
 
104
115
  focus() { this.#inputEl?.focus(); }
105
116
 
@@ -148,6 +159,26 @@ class AdiaCommand extends AdiaElement {
148
159
  const flat = this.#items;
149
160
  let visibleCount = 0;
150
161
 
162
+ // Recents — only when no filter is applied. Resolved against the
163
+ // current item set so a stale recent (item removed via setItems)
164
+ // is silently dropped.
165
+ if (!q && this.#recents.length) {
166
+ const resolved = this.#recents
167
+ .map(v => this.#findItem(v))
168
+ .filter(it => it && !it.disabled);
169
+ if (resolved.length) {
170
+ const groupEl = document.createElement('div');
171
+ groupEl.setAttribute('data-group', '');
172
+ groupEl.setAttribute('data-recent', '');
173
+ groupEl.innerHTML = `<div data-group-label>Recent</div>`;
174
+ for (const item of resolved) {
175
+ groupEl.appendChild(this.#createItemEl(item, visibleCount));
176
+ visibleCount++;
177
+ }
178
+ this.#listEl.appendChild(groupEl);
179
+ }
180
+ }
181
+
151
182
  for (const entry of flat) {
152
183
  if (entry.items) {
153
184
  // Group
@@ -182,6 +213,24 @@ class AdiaCommand extends AdiaElement {
182
213
  if (visibleCount > 0) this.#activate(0);
183
214
  }
184
215
 
216
+ #findItem(value) {
217
+ for (const entry of this.#items) {
218
+ if (entry.items) {
219
+ const hit = entry.items.find(i => i.value === value);
220
+ if (hit) return hit;
221
+ } else if (entry.value === value) {
222
+ return entry;
223
+ }
224
+ }
225
+ return null;
226
+ }
227
+
228
+ #pushRecent(value) {
229
+ if (!value) return;
230
+ this.#recents = [value, ...this.#recents.filter(v => v !== value)]
231
+ .slice(0, AdiaCommand.#RECENTS_MAX);
232
+ }
233
+
185
234
  #createItemEl(item, idx) {
186
235
  const el = document.createElement('div');
187
236
  el.setAttribute('role', 'option');
@@ -235,6 +284,7 @@ class AdiaCommand extends AdiaElement {
235
284
  }
236
285
 
237
286
  #select(item) {
287
+ this.#pushRecent(item.value);
238
288
  this.dispatchEvent(new CustomEvent('select', {
239
289
  bubbles: true,
240
290
  detail: { value: item.value, label: item.label },
@@ -246,6 +296,7 @@ class AdiaCommand extends AdiaElement {
246
296
  if (!active) return;
247
297
  const value = active.dataset.value;
248
298
  const label = active.querySelector('[data-text]')?.textContent || '';
299
+ this.#pushRecent(value);
249
300
  this.dispatchEvent(new CustomEvent('select', {
250
301
  bubbles: true,
251
302
  detail: { value, label },
@@ -0,0 +1,50 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: AdiaFeedItem
3
+ tag: feed-item-ui
4
+ component: FeedItem
5
+ category: feedback
6
+ version: 1
7
+ description: >-
8
+ Atomic feed entry inside a `<feed-ui>` lane. Three dismiss policies
9
+ are inferred from the prop shape — auto-fade (duration > 0 + no
10
+ action), sticky-dismissible (duration null/0), action-required
11
+ (duration null/0 + action; future phase). Posted via
12
+ `AdiaFeed.post()` rather than authored directly.
13
+ props:
14
+ text:
15
+ description: Body copy
16
+ type: string
17
+ default: ""
18
+ heading:
19
+ description: Optional emphasis line above text
20
+ type: string
21
+ default: ""
22
+ icon:
23
+ description: Optional leading icon name
24
+ type: string
25
+ default: ""
26
+ variant:
27
+ description: Semantic variant
28
+ type: string
29
+ default: default
30
+ enum:
31
+ - default
32
+ - info
33
+ - success
34
+ - warning
35
+ - danger
36
+ duration:
37
+ description: Auto-fade timer in ms; null/0 = sticky (requires user input)
38
+ type: number
39
+ default: 4000
40
+ dismissible:
41
+ description: Render an x close button (default true for sticky, false for auto-fade)
42
+ type: boolean
43
+ default: false
44
+ events:
45
+ close:
46
+ description: Fired after the item finishes its exit animation
47
+ slots:
48
+ body:
49
+ description: Default content slot (also accepts the `text` / `heading` props)
50
+ states: {}
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/Feed.json",
4
+ "title": "Feed",
5
+ "description": "Shared top-layer feed channel. Per docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into document.body via Popover API; consumers post via the static API (`AdiaFeed.post()`) or the global 'feed' CustomEvent.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "Feed"
18
+ },
19
+ "max": {
20
+ "description": "Cap on simultaneously visible items per lane",
21
+ "type": "number",
22
+ "default": 5
23
+ },
24
+ "position": {
25
+ "description": "Lane the feed renders into",
26
+ "type": "string",
27
+ "enum": [
28
+ "top-left",
29
+ "top-center",
30
+ "top-right",
31
+ "bottom-left",
32
+ "bottom-center",
33
+ "bottom-right",
34
+ "inline"
35
+ ],
36
+ "default": "bottom-right"
37
+ }
38
+ },
39
+ "required": [
40
+ "component"
41
+ ],
42
+ "unevaluatedProperties": false,
43
+ "x-adiaui": {
44
+ "anti_patterns": [],
45
+ "category": "container",
46
+ "events": {},
47
+ "examples": [],
48
+ "keywords": [],
49
+ "name": "AdiaFeedContainer",
50
+ "related": [],
51
+ "slots": {},
52
+ "states": {},
53
+ "synonyms": {},
54
+ "tag": "feed-ui",
55
+ "tokens": {},
56
+ "traits": [],
57
+ "version": 1
58
+ }
59
+ }
@@ -0,0 +1,150 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ FEED-UI — Shared top-layer feed channel.
3
+ Per docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001).
4
+ ═══════════════════════════════════════════════════════════════ */
5
+
6
+ /* Safari 17.x bug — same as toast: `:scope[data-open]` flavor B
7
+ doesn't reliably restyle on attribute toggling inside @scope.
8
+ Selectors moved out as plain feed-item-ui[data-…] rules. */
9
+ feed-item-ui[data-open] {
10
+ transition: transform var(--feed-item-duration) var(--feed-item-easing),
11
+ opacity var(--feed-item-duration) var(--feed-item-easing);
12
+ transform: translateY(0);
13
+ opacity: 1;
14
+ }
15
+ feed-item-ui[data-closing] {
16
+ transition: transform var(--feed-item-duration) var(--feed-item-easing),
17
+ opacity var(--feed-item-duration) var(--feed-item-easing);
18
+ opacity: 0;
19
+ }
20
+
21
+ /* Container — per-position lane, mounted into document.body via
22
+ Popover API for top-layer placement. */
23
+ @scope (feed-ui) {
24
+ :where(:scope) {
25
+ --feed-gap: var(--a-space-2);
26
+ --feed-padding: var(--a-space-3);
27
+ --feed-max-width: 22rem;
28
+ --feed-offset: var(--a-space-4);
29
+ }
30
+
31
+ :scope {
32
+ position: fixed;
33
+ z-index: 9999;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: var(--feed-gap);
37
+ padding: 0;
38
+ background: transparent;
39
+ border: 0;
40
+ pointer-events: none; /* Items re-enable pointer-events. */
41
+ width: max-content;
42
+ max-width: var(--feed-max-width);
43
+ }
44
+ /* Reset native popover defaults so the lane is invisible until
45
+ items render. */
46
+ :scope[popover] { background: none; color: inherit; }
47
+
48
+ /* Position variants. Default = bottom-right. */
49
+ :scope,
50
+ :scope[position="bottom-right"] {
51
+ bottom: var(--feed-offset); right: var(--feed-offset);
52
+ }
53
+ :scope[position="bottom-left"] {
54
+ bottom: var(--feed-offset); left: var(--feed-offset); right: auto;
55
+ }
56
+ :scope[position="bottom-center"] {
57
+ bottom: var(--feed-offset);
58
+ left: 50%; right: auto;
59
+ transform: translateX(-50%);
60
+ }
61
+ :scope[position="top-right"] {
62
+ top: var(--feed-offset); right: var(--feed-offset); bottom: auto;
63
+ flex-direction: column-reverse;
64
+ }
65
+ :scope[position="top-left"] {
66
+ top: var(--feed-offset); left: var(--feed-offset); right: auto; bottom: auto;
67
+ flex-direction: column-reverse;
68
+ }
69
+ :scope[position="top-center"] {
70
+ top: var(--feed-offset); bottom: auto;
71
+ left: 50%; right: auto;
72
+ transform: translateX(-50%);
73
+ flex-direction: column-reverse;
74
+ }
75
+ :scope[position="inline"] {
76
+ position: relative;
77
+ inset: auto;
78
+ z-index: 1;
79
+ }
80
+ }
81
+
82
+ /* Item — atomic feed entry. */
83
+ @scope (feed-item-ui) {
84
+ :where(:scope) {
85
+ --feed-item-bg: var(--a-bg);
86
+ --feed-item-fg: var(--a-fg);
87
+ --feed-item-border: var(--a-border-subtle);
88
+ --feed-item-radius: var(--a-radius-md);
89
+ --feed-item-px: var(--a-space-3);
90
+ --feed-item-py: var(--a-space-2-5);
91
+ --feed-item-shadow: var(--a-shadow-md);
92
+ --feed-item-icon-size: var(--a-icon-size);
93
+ --feed-item-duration: var(--a-duration);
94
+ --feed-item-easing: var(--a-easing-out);
95
+ --feed-item-gap: var(--a-space-3);
96
+ --feed-item-max-width: var(--a-feed-max-width, 22rem);
97
+ }
98
+ :scope[variant="info"] { --feed-item-fg: var(--a-info-fg); --feed-item-bg: var(--a-info-muted); }
99
+ :scope[variant="success"] { --feed-item-fg: var(--a-success-fg); --feed-item-bg: var(--a-success-muted); }
100
+ :scope[variant="warning"] { --feed-item-fg: var(--a-warning-fg); --feed-item-bg: var(--a-warning-muted); }
101
+ :scope[variant="danger"] { --feed-item-fg: var(--a-danger-fg); --feed-item-bg: var(--a-danger-muted); }
102
+
103
+ :scope {
104
+ box-sizing: border-box;
105
+ display: flex;
106
+ align-items: center;
107
+ gap: var(--feed-item-gap);
108
+ max-width: var(--feed-item-max-width);
109
+ padding: var(--feed-item-py) var(--feed-item-px);
110
+ background: var(--feed-item-bg);
111
+ color: var(--feed-item-fg);
112
+ border: 1px solid var(--feed-item-border);
113
+ border-radius: var(--feed-item-radius);
114
+ box-shadow: var(--feed-item-shadow);
115
+ opacity: 0;
116
+ transform: translateY(0.5rem);
117
+ pointer-events: auto;
118
+ }
119
+ :scope > [slot="body"] {
120
+ flex: 1;
121
+ min-width: 0;
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: 0.125rem;
125
+ }
126
+ :scope > [slot="body"] strong {
127
+ font-weight: var(--a-font-weight-strong, 600);
128
+ }
129
+ :scope > [data-feed-close] {
130
+ flex-shrink: 0;
131
+ }
132
+ }
133
+
134
+ /* Phase 2 max-queue: items above [max] are appended with [data-queued]
135
+ and stay invisible until a visible sibling dismisses (at which point
136
+ `releaseContainerIfEmpty` strips the attribute). Display:none keeps
137
+ them out of the layout AND screen-reader-invisible — items not yet
138
+ shown shouldn't be announced. */
139
+ feed-item-ui[data-queued] {
140
+ display: none;
141
+ }
142
+
143
+ /* Reduced-motion: skip slide-in/out, keep opacity. */
144
+ @media (prefers-reduced-motion: reduce) {
145
+ feed-item-ui[data-open],
146
+ feed-item-ui[data-closing] {
147
+ transition: opacity var(--feed-item-duration) var(--feed-item-easing);
148
+ transform: none !important;
149
+ }
150
+ }