@xmesh/system-design 0.0.1

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 (175) hide show
  1. package/README.md +472 -0
  2. package/assets/brand-lockup-dark.svg +9 -0
  3. package/assets/brand-lockup-light.svg +9 -0
  4. package/assets/brand-mark.svg +9 -0
  5. package/colors_and_type.css +11 -0
  6. package/dist/lit/components/alert/index.css +201 -0
  7. package/dist/lit/components/alert/index.d.ts +25 -0
  8. package/dist/lit/components/alert/index.js +191 -0
  9. package/dist/lit/components/app-bar/index.css +80 -0
  10. package/dist/lit/components/app-bar/index.d.ts +19 -0
  11. package/dist/lit/components/app-bar/index.js +120 -0
  12. package/dist/lit/components/artifact/index.css +166 -0
  13. package/dist/lit/components/artifact/index.d.ts +37 -0
  14. package/dist/lit/components/artifact/index.js +294 -0
  15. package/dist/lit/components/autocomplete/index.css +171 -0
  16. package/dist/lit/components/autocomplete/index.d.ts +47 -0
  17. package/dist/lit/components/autocomplete/index.js +404 -0
  18. package/dist/lit/components/avatar/index.css +62 -0
  19. package/dist/lit/components/avatar/index.d.ts +19 -0
  20. package/dist/lit/components/avatar/index.js +112 -0
  21. package/dist/lit/components/avatar-group/index.css +60 -0
  22. package/dist/lit/components/avatar-group/index.d.ts +19 -0
  23. package/dist/lit/components/avatar-group/index.js +97 -0
  24. package/dist/lit/components/badge/index.css +72 -0
  25. package/dist/lit/components/badge/index.d.ts +18 -0
  26. package/dist/lit/components/badge/index.js +115 -0
  27. package/dist/lit/components/brand-mark/index.css +109 -0
  28. package/dist/lit/components/brand-mark/index.d.ts +24 -0
  29. package/dist/lit/components/brand-mark/index.js +116 -0
  30. package/dist/lit/components/breadcrumbs/index.css +91 -0
  31. package/dist/lit/components/breadcrumbs/index.d.ts +19 -0
  32. package/dist/lit/components/breadcrumbs/index.js +104 -0
  33. package/dist/lit/components/bubble/index.css +182 -0
  34. package/dist/lit/components/bubble/index.d.ts +72 -0
  35. package/dist/lit/components/bubble/index.js +617 -0
  36. package/dist/lit/components/button/index.css +342 -0
  37. package/dist/lit/components/button/index.d.ts +32 -0
  38. package/dist/lit/components/button/index.js +202 -0
  39. package/dist/lit/components/card/index.css +99 -0
  40. package/dist/lit/components/card/index.d.ts +20 -0
  41. package/dist/lit/components/card/index.js +133 -0
  42. package/dist/lit/components/chat/index.css +292 -0
  43. package/dist/lit/components/chat/index.d.ts +74 -0
  44. package/dist/lit/components/chat/index.js +589 -0
  45. package/dist/lit/components/checkbox/index.css +126 -0
  46. package/dist/lit/components/checkbox/index.d.ts +21 -0
  47. package/dist/lit/components/checkbox/index.js +138 -0
  48. package/dist/lit/components/chip/index.css +145 -0
  49. package/dist/lit/components/chip/index.d.ts +30 -0
  50. package/dist/lit/components/chip/index.js +230 -0
  51. package/dist/lit/components/chip-group/index.css +19 -0
  52. package/dist/lit/components/chip-group/index.d.ts +24 -0
  53. package/dist/lit/components/chip-group/index.js +171 -0
  54. package/dist/lit/components/code/index.css +42 -0
  55. package/dist/lit/components/code/index.d.ts +12 -0
  56. package/dist/lit/components/code/index.js +68 -0
  57. package/dist/lit/components/composer/index.css +548 -0
  58. package/dist/lit/components/composer/index.d.ts +67 -0
  59. package/dist/lit/components/composer/index.js +713 -0
  60. package/dist/lit/components/data-table/index.css +166 -0
  61. package/dist/lit/components/data-table/index.d.ts +55 -0
  62. package/dist/lit/components/data-table/index.js +390 -0
  63. package/dist/lit/components/dialog/index.css +124 -0
  64. package/dist/lit/components/dialog/index.d.ts +24 -0
  65. package/dist/lit/components/dialog/index.js +199 -0
  66. package/dist/lit/components/divider/index.css +27 -0
  67. package/dist/lit/components/divider/index.d.ts +13 -0
  68. package/dist/lit/components/divider/index.js +67 -0
  69. package/dist/lit/components/empty-state/index.css +69 -0
  70. package/dist/lit/components/empty-state/index.d.ts +21 -0
  71. package/dist/lit/components/empty-state/index.js +123 -0
  72. package/dist/lit/components/expansion-panel/index.css +120 -0
  73. package/dist/lit/components/expansion-panel/index.d.ts +22 -0
  74. package/dist/lit/components/expansion-panel/index.js +174 -0
  75. package/dist/lit/components/field/index.css +223 -0
  76. package/dist/lit/components/field/index.d.ts +106 -0
  77. package/dist/lit/components/field/index.js +388 -0
  78. package/dist/lit/components/file-input/index.css +257 -0
  79. package/dist/lit/components/file-input/index.d.ts +30 -0
  80. package/dist/lit/components/file-input/index.js +298 -0
  81. package/dist/lit/components/form/index.css +29 -0
  82. package/dist/lit/components/form/index.d.ts +38 -0
  83. package/dist/lit/components/form/index.js +192 -0
  84. package/dist/lit/components/grid/index.css +53 -0
  85. package/dist/lit/components/grid/index.d.ts +14 -0
  86. package/dist/lit/components/grid/index.js +82 -0
  87. package/dist/lit/components/kbd/index.css +35 -0
  88. package/dist/lit/components/kbd/index.d.ts +11 -0
  89. package/dist/lit/components/kbd/index.js +43 -0
  90. package/dist/lit/components/list/index.css +15 -0
  91. package/dist/lit/components/list/index.d.ts +28 -0
  92. package/dist/lit/components/list/index.js +188 -0
  93. package/dist/lit/components/list-item/index.css +119 -0
  94. package/dist/lit/components/list-item/index.d.ts +20 -0
  95. package/dist/lit/components/list-item/index.js +127 -0
  96. package/dist/lit/components/menu/index.css +94 -0
  97. package/dist/lit/components/menu/index.d.ts +47 -0
  98. package/dist/lit/components/menu/index.js +386 -0
  99. package/dist/lit/components/navigation-drawer/index.css +114 -0
  100. package/dist/lit/components/navigation-drawer/index.d.ts +29 -0
  101. package/dist/lit/components/navigation-drawer/index.js +218 -0
  102. package/dist/lit/components/overlay/index.css +171 -0
  103. package/dist/lit/components/overlay/index.d.ts +65 -0
  104. package/dist/lit/components/overlay/index.js +566 -0
  105. package/dist/lit/components/pagination/index.css +102 -0
  106. package/dist/lit/components/pagination/index.d.ts +22 -0
  107. package/dist/lit/components/pagination/index.js +184 -0
  108. package/dist/lit/components/primitives/index.css +504 -0
  109. package/dist/lit/components/primitives/index.d.ts +25 -0
  110. package/dist/lit/components/primitives/index.js +283 -0
  111. package/dist/lit/components/progress/index.css +143 -0
  112. package/dist/lit/components/progress/index.d.ts +23 -0
  113. package/dist/lit/components/progress/index.js +180 -0
  114. package/dist/lit/components/radio-group/index.css +178 -0
  115. package/dist/lit/components/radio-group/index.d.ts +35 -0
  116. package/dist/lit/components/radio-group/index.js +292 -0
  117. package/dist/lit/components/select/index.css +151 -0
  118. package/dist/lit/components/select/index.d.ts +50 -0
  119. package/dist/lit/components/select/index.js +390 -0
  120. package/dist/lit/components/sidebar-item/index.css +133 -0
  121. package/dist/lit/components/sidebar-item/index.d.ts +20 -0
  122. package/dist/lit/components/sidebar-item/index.js +105 -0
  123. package/dist/lit/components/skeleton/index.css +81 -0
  124. package/dist/lit/components/skeleton/index.d.ts +19 -0
  125. package/dist/lit/components/skeleton/index.js +119 -0
  126. package/dist/lit/components/slider/index.css +171 -0
  127. package/dist/lit/components/slider/index.d.ts +36 -0
  128. package/dist/lit/components/slider/index.js +302 -0
  129. package/dist/lit/components/snackbar/index.css +279 -0
  130. package/dist/lit/components/snackbar/index.d.ts +33 -0
  131. package/dist/lit/components/snackbar/index.js +195 -0
  132. package/dist/lit/components/stack/index.css +41 -0
  133. package/dist/lit/components/stack/index.d.ts +20 -0
  134. package/dist/lit/components/stack/index.js +103 -0
  135. package/dist/lit/components/switch/index.css +126 -0
  136. package/dist/lit/components/switch/index.d.ts +17 -0
  137. package/dist/lit/components/switch/index.js +116 -0
  138. package/dist/lit/components/table/index.css +85 -0
  139. package/dist/lit/components/table/index.d.ts +25 -0
  140. package/dist/lit/components/table/index.js +139 -0
  141. package/dist/lit/components/tabs/index.css +116 -0
  142. package/dist/lit/components/tabs/index.d.ts +49 -0
  143. package/dist/lit/components/tabs/index.js +320 -0
  144. package/dist/lit/components/text-field/index.css +90 -0
  145. package/dist/lit/components/text-field/index.d.ts +17 -0
  146. package/dist/lit/components/text-field/index.js +101 -0
  147. package/dist/lit/components/textarea/index.css +55 -0
  148. package/dist/lit/components/textarea/index.d.ts +26 -0
  149. package/dist/lit/components/textarea/index.js +124 -0
  150. package/dist/lit/components/tooltip/index.css +37 -0
  151. package/dist/lit/components/tooltip/index.d.ts +31 -0
  152. package/dist/lit/components/tooltip/index.js +196 -0
  153. package/dist/lit/components/validation/index.css +386 -0
  154. package/dist/lit/components/validation/index.d.ts +45 -0
  155. package/dist/lit/components/validation/index.js +318 -0
  156. package/dist/lit/index.d.ts +50 -0
  157. package/dist/lit/index.js +59 -0
  158. package/package.json +81 -0
  159. package/styles/README.md +346 -0
  160. package/styles/_elevation.css +24 -0
  161. package/styles/_fonts.css +6 -0
  162. package/styles/_layout.css +37 -0
  163. package/styles/_primitives.css +154 -0
  164. package/styles/_scroll.css +75 -0
  165. package/styles/_semantic.css +146 -0
  166. package/styles/_space.css +61 -0
  167. package/styles/_type.css +139 -0
  168. package/styles/_xmesh-extensions.css +232 -0
  169. package/styles/index.css +44 -0
  170. package/styles/md3/_color.css +102 -0
  171. package/styles/md3/_elevation.css +26 -0
  172. package/styles/md3/_motion.css +35 -0
  173. package/styles/md3/_shape.css +22 -0
  174. package/styles/md3/_state.css +22 -0
  175. package/styles/md3/_type.css +111 -0
@@ -0,0 +1,302 @@
1
+ /*
2
+ slider/index.ts — <xm-slider>, a draggable range slider on XmField (Story 2.8).
3
+
4
+ Extends XmField for the chrome (label / helper / disabled / form association /
5
+ focus) and renders a track + active fill + thumb. The bordered text-field
6
+ control box is wrong for a slider, so this overrides render() for a
7
+ label-row / track-row / helper-row layout, but reuses the base's state +
8
+ form-association + ARIA hooks (AD-7).
9
+
10
+ Value contract (AD-6 / AD-8a): uncontrolled-first. The live numeric value is
11
+ owned internally and mirrored into the inherited string `value` (+
12
+ ElementInternals) so xm-form sees a stable serialization; the events carry the
13
+ real number. During drag / keyboard stepping it emits `input` (per movement);
14
+ on commit (pointerup / blur after keyboard) it emits `change` — both with
15
+ detail.value: number.
16
+
17
+ Keyboard (slider APG, AD-9a): ←/↓ step down, →/↑ step up, Home → min, End →
18
+ max, PageDown/PageUp step by a larger increment. Value is clamped to [min,max]
19
+ and snapped to step.
20
+
21
+ Active fill + thumb are the coral accent (--md-sys-color-primary); the inactive
22
+ track + thumb size + track height are --xm-slider-* extensions (AD-10). Coral =
23
+ the filled value, never severity (AD-11).
24
+
25
+ Shadow DOM. Lit is a bare `import` (peer dep).
26
+ */
27
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
28
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
29
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
30
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
31
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
32
+ };
33
+ import { html, nothing } from "lit";
34
+ import { customElement, property, query } from "lit/decorators.js";
35
+ import { XmField } from "../field/index.js";
36
+ const SLIDER_CSS = new URL("../slider/index.css", import.meta.url).href;
37
+ const PRIMITIVES_CSS = new URL("../primitives/index.css", import.meta.url).href;
38
+ let XmSlider = class XmSlider extends XmField {
39
+ constructor() {
40
+ super(...arguments);
41
+ this.min = 0;
42
+ this.max = 100;
43
+ this.step = 1;
44
+ /** Show the current numeric value as a label beside the track. */
45
+ this.showValue = false;
46
+ /** Optional unit appended to the displayed value (e.g. "%", "px"). */
47
+ this.unit = "";
48
+ this._dragging = false;
49
+ this._onPointerDown = (e) => {
50
+ if (this.nonInteractive)
51
+ return;
52
+ e.preventDefault();
53
+ this._dragging = true;
54
+ e.target.setPointerCapture?.(e.pointerId);
55
+ this._setValue(this._valueFromClientX(e.clientX), true);
56
+ this._focusThumb();
57
+ };
58
+ this._onPointerMove = (e) => {
59
+ if (!this._dragging)
60
+ return;
61
+ this._setValue(this._valueFromClientX(e.clientX), true);
62
+ };
63
+ this._onPointerUp = (e) => {
64
+ if (!this._dragging)
65
+ return;
66
+ this._dragging = false;
67
+ e.target.releasePointerCapture?.(e.pointerId);
68
+ this._commit();
69
+ };
70
+ // ── Keyboard (slider APG) ───────────────────────────────────────────
71
+ this._onKeydown = (e) => {
72
+ if (this.nonInteractive)
73
+ return;
74
+ const big = Math.max(this.step, (this.max - this.min) / 10);
75
+ let handled = true;
76
+ switch (e.key) {
77
+ case "ArrowLeft":
78
+ case "ArrowDown":
79
+ this._setValue(this._num - this.step, true);
80
+ break;
81
+ case "ArrowRight":
82
+ case "ArrowUp":
83
+ this._setValue(this._num + this.step, true);
84
+ break;
85
+ case "PageDown":
86
+ this._setValue(this._num - big, true);
87
+ break;
88
+ case "PageUp":
89
+ this._setValue(this._num + big, true);
90
+ break;
91
+ case "Home":
92
+ this._setValue(this.min, true);
93
+ break;
94
+ case "End":
95
+ this._setValue(this.max, true);
96
+ break;
97
+ default:
98
+ handled = false;
99
+ }
100
+ if (handled)
101
+ e.preventDefault();
102
+ };
103
+ this._onBlur = () => {
104
+ if (!this._dragging)
105
+ this._commit();
106
+ };
107
+ }
108
+ connectedCallback() {
109
+ super.connectedCallback();
110
+ // Seed the numeric value from the uncontrolled `value` attribute, snapping
111
+ // into range. If unset, default to min.
112
+ const seed = this.initialValue === "" ? this.min : Number(this.initialValue);
113
+ this._value = String(this._clampSnap(Number.isFinite(seed) ? seed : this.min));
114
+ this.internals.setFormValue(this._value);
115
+ }
116
+ /** Typed live read; the inherited string `value` stays in sync. */
117
+ get valueAsNumber() {
118
+ return Number(this._value);
119
+ }
120
+ get _num() {
121
+ const n = Number(this._value);
122
+ return Number.isFinite(n) ? n : this.min;
123
+ }
124
+ get _fraction() {
125
+ const span = this.max - this.min;
126
+ if (span <= 0)
127
+ return 0;
128
+ return (this._num - this.min) / span;
129
+ }
130
+ _clampSnap(v) {
131
+ const clamped = Math.min(this.max, Math.max(this.min, v));
132
+ if (this.step <= 0)
133
+ return clamped;
134
+ const steps = Math.round((clamped - this.min) / this.step);
135
+ const snapped = this.min + steps * this.step;
136
+ // Avoid binary-float drift in the displayed/emitted number.
137
+ const decimals = (String(this.step).split(".")[1] ?? "").length;
138
+ return Number(Math.min(this.max, Math.max(this.min, snapped)).toFixed(decimals));
139
+ }
140
+ _setValue(v, emitInput) {
141
+ const next = this._clampSnap(v);
142
+ if (next === this._num && !emitInput)
143
+ return;
144
+ this._value = String(next);
145
+ this.internals.setFormValue(this._value);
146
+ if (emitInput) {
147
+ this.dispatchEvent(new CustomEvent("input", {
148
+ bubbles: true,
149
+ composed: true,
150
+ detail: { value: next },
151
+ }));
152
+ }
153
+ }
154
+ _commit() {
155
+ this.dispatchEvent(new CustomEvent("change", {
156
+ bubbles: true,
157
+ composed: true,
158
+ detail: { value: this._num },
159
+ }));
160
+ }
161
+ // ── Pointer drag ────────────────────────────────────────────────────
162
+ _valueFromClientX(clientX) {
163
+ const track = this._track;
164
+ if (!track)
165
+ return this._num;
166
+ const rect = track.getBoundingClientRect();
167
+ if (rect.width <= 0)
168
+ return this._num;
169
+ const frac = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
170
+ return this.min + frac * (this.max - this.min);
171
+ }
172
+ _focusThumb() {
173
+ const thumb = this.renderRoot.querySelector(".slider__thumb");
174
+ thumb?.focus();
175
+ }
176
+ updated(changed) {
177
+ super.updated?.(changed);
178
+ // Re-snap if min/max/step change after seed so the value stays valid.
179
+ if (changed.has("min") || changed.has("max") || changed.has("step")) {
180
+ const re = this._clampSnap(this._num);
181
+ if (re !== this._num) {
182
+ this._value = String(re);
183
+ this.internals.setFormValue(this._value);
184
+ }
185
+ }
186
+ }
187
+ get _valueText() {
188
+ return this.unit ? `${this._num} ${this.unit}` : String(this._num);
189
+ }
190
+ render() {
191
+ const cls = [
192
+ "slider",
193
+ this.effectiveDisabled ? "slider--disabled" : "",
194
+ this.isError ? "slider--error" : "",
195
+ ]
196
+ .filter(Boolean)
197
+ .join(" ");
198
+ const helperText = this.isError ? this.error : this.helper;
199
+ const ctrl = this.controlAria;
200
+ const pct = `${(this._fraction * 100).toFixed(2)}%`;
201
+ return html `
202
+ <link rel="stylesheet" href="${PRIMITIVES_CSS}" />
203
+ <link rel="stylesheet" href="${SLIDER_CSS}" />
204
+ <style>
205
+ :host {
206
+ display: block;
207
+ }
208
+ :host([hidden]) {
209
+ display: none;
210
+ }
211
+ </style>
212
+ <div class="${cls}">
213
+ ${this.label
214
+ ? html `<div class="slider__label-row">
215
+ <label class="slider__label" id="${ctrl.id}-label">
216
+ <span class="slider__label-text">${this.label}</span>
217
+ ${this.required
218
+ ? html `<span class="slider__required" aria-hidden="true"
219
+ >*</span
220
+ >`
221
+ : nothing}
222
+ </label>
223
+ ${this.showValue
224
+ ? html `<span class="slider__value">${this._valueText}</span>`
225
+ : nothing}
226
+ </div>`
227
+ : this.showValue
228
+ ? html `<div class="slider__label-row">
229
+ <span class="slider__value">${this._valueText}</span>
230
+ </div>`
231
+ : nothing}
232
+
233
+ <div
234
+ class="slider__track"
235
+ @pointerdown=${this._onPointerDown}
236
+ @pointermove=${this._onPointerMove}
237
+ @pointerup=${this._onPointerUp}
238
+ >
239
+ <span class="slider__rail" aria-hidden="true"></span>
240
+ <span class="slider__fill" style="width:${pct}" aria-hidden="true"></span>
241
+ <span
242
+ class="slider__thumb"
243
+ id="${ctrl.id}"
244
+ role="slider"
245
+ tabindex="${this.effectiveDisabled ? -1 : 0}"
246
+ aria-valuemin="${this.min}"
247
+ aria-valuemax="${this.max}"
248
+ aria-valuenow="${this._num}"
249
+ aria-valuetext="${this.unit ? this._valueText : nothing}"
250
+ aria-labelledby="${this.label ? `${ctrl.id}-label` : nothing}"
251
+ aria-label="${this.label ? nothing : "Value"}"
252
+ aria-describedby="${helperText ? ctrl.describedBy : nothing}"
253
+ aria-invalid="${ctrl.invalid ?? nothing}"
254
+ aria-disabled="${this.effectiveDisabled ? "true" : nothing}"
255
+ style="left:${pct}"
256
+ @keydown=${this._onKeydown}
257
+ @blur=${this._onBlur}
258
+ ></span>
259
+ </div>
260
+
261
+ ${helperText
262
+ ? html `<div
263
+ class="slider__message ${this.isError
264
+ ? "slider__message--error"
265
+ : "slider__message--helper"}"
266
+ id="${ctrl.describedBy}"
267
+ role=${this.isError ? "alert" : nothing}
268
+ >
269
+ ${this.isError
270
+ ? html `<span class="slider__error-icon" aria-hidden="true">
271
+ <xm-warn-icon size="14"></xm-warn-icon>
272
+ </span>`
273
+ : nothing}
274
+ <span class="slider__message-text">${helperText}</span>
275
+ </div>`
276
+ : nothing}
277
+ </div>
278
+ `;
279
+ }
280
+ };
281
+ __decorate([
282
+ property({ type: Number })
283
+ ], XmSlider.prototype, "min", void 0);
284
+ __decorate([
285
+ property({ type: Number })
286
+ ], XmSlider.prototype, "max", void 0);
287
+ __decorate([
288
+ property({ type: Number })
289
+ ], XmSlider.prototype, "step", void 0);
290
+ __decorate([
291
+ property({ type: Boolean, attribute: "show-value" })
292
+ ], XmSlider.prototype, "showValue", void 0);
293
+ __decorate([
294
+ property({ type: String })
295
+ ], XmSlider.prototype, "unit", void 0);
296
+ __decorate([
297
+ query(".slider__track")
298
+ ], XmSlider.prototype, "_track", void 0);
299
+ XmSlider = __decorate([
300
+ customElement("xm-slider")
301
+ ], XmSlider);
302
+ export { XmSlider };
@@ -0,0 +1,279 @@
1
+ /* ============================================
2
+ Snackbar — backend error surface
3
+
4
+ Styles for <Snackbar /> (components/snackbar/index.jsx). Lives at the bottom
5
+ of the chat pane, above the composer. The host owns placement (an
6
+ absolute container inside .chat-main); this rule set styles the card
7
+ itself, its severity variants, the dim backdrop overlay, and the entry
8
+ animation. The card is neutral; the gradient overlay uses the
9
+ neutral `--xm-snackbar-scrim*` tokens (no severity hue).
10
+
11
+ Note: the .failmode-* picker classes used by chat-shell.jsx are NOT
12
+ part of this component — they live in ../styles.css as prototype
13
+ chrome.
14
+ ============================================ */
15
+
16
+ .snackbar-stack {
17
+ position: absolute;
18
+ inset: 0;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: flex-end;
23
+ gap: var(--s-2);
24
+ pointer-events: none;
25
+ padding: 0 var(--s-6) 116px; /* sits clearly above the chat-shell__composer */
26
+ z-index: 40;
27
+ }
28
+ .snackbar-stack > * { pointer-events: auto; }
29
+
30
+ /* Dim backdrop behind the snackbar so the error reads as a focused
31
+ modal-ish surface, not a passive notification. Sits below the snackbar
32
+ in the same stack and fades in with the same easing.
33
+
34
+ We do NOT use backdrop-filter here — a full-pane blur is expensive on
35
+ every paint, and the chat behind contains a virtualized scroll list, so
36
+ it would regress scrolling. Instead we use a denser overlay color
37
+ (theme-aware) plus a faint vertical gradient so the snackbar still feels
38
+ "lifted" without any GPU filter work. */
39
+ .snackbar-overlay {
40
+ position: absolute;
41
+ inset: 0;
42
+ background:
43
+ linear-gradient(
44
+ to top,
45
+ var(--xm-snackbar-scrim-strong) 0%,
46
+ var(--xm-snackbar-scrim) 60%,
47
+ var(--xm-snackbar-scrim-soft) 100%
48
+ );
49
+ animation: snackbar-overlay-in var(--md-sys-motion-duration-medium1) cubic-bezier(0.32, 0.72, 0.24, 1) both;
50
+ pointer-events: auto; /* clicks land on the overlay, not the chat behind */
51
+ }
52
+ @keyframes snackbar-overlay-in {
53
+ from { opacity: 0; }
54
+ to { opacity: 1; }
55
+ }
56
+
57
+ .snackbar {
58
+ /* Component-local vars — single neutral surface for all severities.
59
+ Severity is communicated by the icon and body copy, not by hue. */
60
+ /* The card rides the surface family (--snk-bg = surface-container-high),
61
+ so ink must come from the on-surface family, not inverse-on-surface —
62
+ pairing inverse ink (a dark ink) on this dark surface reads dark-on-dark
63
+ and fails AA (POLICIES 7a). --snk-accent stays the brightest on-surface
64
+ tone so the reversed primary button keeps high contrast. */
65
+ --snk-bg: var(--md-sys-color-surface-container-high);
66
+ --snk-border: var(--md-sys-color-outline-variant);
67
+ --snk-accent: var(--md-sys-color-on-surface);
68
+ --snk-ink: var(--md-sys-color-on-surface);
69
+ --snk-ink-soft: var(--md-sys-color-on-surface-variant);
70
+
71
+ position: relative;
72
+ display: grid;
73
+ grid-template-columns: auto 1fr auto auto;
74
+ align-items: center;
75
+ gap: var(--s-3-5);
76
+ width: 100%;
77
+ max-width: 620px;
78
+ padding: var(--s-3-5) var(--s-3-5) var(--s-3-5) var(--s-4);
79
+ background: var(--snk-bg);
80
+ border: 1px solid var(--snk-border);
81
+ border-radius: var(--md-sys-shape-corner-medium);
82
+ box-shadow: var(--xm-elevation-snackbar);
83
+ color: var(--snk-ink);
84
+ font-family: var(--md-sys-typescale-body-large-font);
85
+ animation: snackbar-in 280ms cubic-bezier(0.32, 0.72, 0.24, 1) both;
86
+ }
87
+ .snackbar--static { animation: none; }
88
+
89
+ @keyframes snackbar-in {
90
+ from {
91
+ opacity: 0;
92
+ transform: translateY(8px) scale(0.985);
93
+ }
94
+ to {
95
+ opacity: 1;
96
+ transform: translateY(0) scale(1);
97
+ }
98
+ }
99
+
100
+ /* Severity variants — class hooks kept for JSX/markup compatibility, but
101
+ they no longer swap colors. The icon and body copy carry the severity. */
102
+
103
+ .snackbar__icon {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ width: 36px;
108
+ height: 36px;
109
+ border-radius: 10px;
110
+ /* Soft on-card highlight chip: stronger contrast in dark, softer in light. */
111
+ background: color-mix(in oklch, var(--snk-accent) 18%, transparent);
112
+ color: var(--snk-accent);
113
+ flex-shrink: 0;
114
+ }
115
+
116
+ .snackbar__body {
117
+ min-width: 0;
118
+ display: flex;
119
+ flex-direction: column;
120
+ gap: 3px;
121
+ }
122
+ .snackbar__head {
123
+ display: inline-flex;
124
+ align-items: center;
125
+ gap: var(--s-2);
126
+ font-size: 13.5px;
127
+ font-weight: 600;
128
+ letter-spacing: -0.005em;
129
+ color: var(--snk-ink);
130
+ }
131
+ .snackbar__code {
132
+ font-family: var(--xm-typescale-mono-font);
133
+ font-size: 10.5px;
134
+ font-weight: 700;
135
+ letter-spacing: 0.04em;
136
+ padding: 0;
137
+ background: transparent;
138
+ color: var(--snk-accent);
139
+ flex-shrink: 0;
140
+ }
141
+ .snackbar__title {
142
+ white-space: nowrap;
143
+ overflow: hidden;
144
+ text-overflow: ellipsis;
145
+ }
146
+ .snackbar__msg {
147
+ font-size: 13px;
148
+ color: var(--snk-ink-soft);
149
+ line-height: 1.45;
150
+ text-wrap: pretty;
151
+ }
152
+ .snackbar__meta {
153
+ display: inline-flex;
154
+ align-items: baseline;
155
+ gap: var(--s-1-5);
156
+ margin-top: var(--s-1);
157
+ font-size: 11px;
158
+ }
159
+ .snackbar__meta-label {
160
+ text-transform: uppercase;
161
+ letter-spacing: 0.08em;
162
+ color: var(--snk-ink-soft);
163
+ font-weight: 500;
164
+ opacity: 0.8;
165
+ }
166
+ .snackbar__meta-id {
167
+ font-family: var(--xm-typescale-mono-font);
168
+ color: var(--snk-ink);
169
+ background: transparent;
170
+ border: 0;
171
+ padding: 0;
172
+ font-size: 11px;
173
+ }
174
+
175
+ .snackbar__actions {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ gap: var(--s-1-5);
179
+ flex-shrink: 0;
180
+ }
181
+ .snackbar__btn {
182
+ appearance: none;
183
+ border: 1px solid transparent;
184
+ background: transparent;
185
+ font: inherit;
186
+ font-size: 12.5px;
187
+ font-weight: 600;
188
+ padding: 7px 11px;
189
+ border-radius: 8px;
190
+ cursor: pointer;
191
+ display: inline-flex;
192
+ align-items: center;
193
+ gap: var(--s-1-5);
194
+ transition:
195
+ background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
196
+ border-color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
197
+ color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
198
+ white-space: nowrap;
199
+ color: var(--snk-ink);
200
+ }
201
+ .snackbar__btn--primary {
202
+ background: var(--snk-accent);
203
+ color: var(--snk-bg); /* high-contrast inverse against the accent */
204
+ border-color: var(--snk-accent);
205
+ }
206
+ .snackbar__btn--primary:hover {
207
+ background: color-mix(in oklab, var(--snk-bg) var(--md-sys-state-hover-state-layer-opacity), var(--snk-accent));
208
+ }
209
+ .snackbar__btn--primary:active { transform: translateY(1px); }
210
+ .snackbar__btn--primary svg { opacity: 0.9; }
211
+
212
+ .snackbar__btn--ghost {
213
+ color: var(--snk-ink-soft);
214
+ border-color: transparent;
215
+ }
216
+ .snackbar__btn--ghost:hover {
217
+ color: var(--snk-ink);
218
+ background: color-mix(in oklch, var(--snk-accent) 14%, transparent);
219
+ }
220
+
221
+ .snackbar__close {
222
+ appearance: none;
223
+ border: none;
224
+ background: transparent;
225
+ color: var(--snk-ink-soft);
226
+ width: 28px;
227
+ height: 28px;
228
+ border-radius: 6px;
229
+ cursor: pointer;
230
+ display: inline-flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ transition:
234
+ background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
235
+ color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
236
+ flex-shrink: 0;
237
+ }
238
+ .snackbar__close:hover {
239
+ background: color-mix(in oklch, var(--snk-accent) 14%, transparent);
240
+ color: var(--snk-ink);
241
+ }
242
+
243
+ /* When sitting in the bare canvas (state cards on the design surface), the
244
+ card needs a touch more contrast against the warm canvas background. */
245
+ .snackbar-card-frame {
246
+ background: var(--md-sys-color-surface);
247
+ padding: 30px var(--s-7);
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ width: 100%;
252
+ height: 100%;
253
+ position: relative;
254
+ }
255
+ .snackbar-card-frame .frame-label {
256
+ position: absolute;
257
+ top: var(--s-4);
258
+ left: var(--s-6);
259
+ margin: 0;
260
+ }
261
+ .snackbar-card-frame .snackbar {
262
+ max-width: 580px;
263
+ }
264
+
265
+ /* Compact variant: when the chat shell is narrow, drop the right-side actions
266
+ below the body so nothing gets cut off. The 640px literal mirrors
267
+ --xm-breakpoint-md (styles/_layout.css) — @media can't read the token
268
+ (docs/adr/0001), so the literal is the source-of-truth's echo. */
269
+ @media (max-width: 640px) {
270
+ .snackbar {
271
+ grid-template-columns: auto 1fr auto;
272
+ grid-template-rows: auto auto;
273
+ row-gap: var(--s-2);
274
+ }
275
+ .snackbar__actions {
276
+ grid-column: 1 / -2;
277
+ justify-content: flex-end;
278
+ }
279
+ }
@@ -0,0 +1,33 @@
1
+ import { LitElement } from "lit";
2
+ import type { TemplateResult } from "lit";
3
+ export type SnackKind = "auth" | "server" | "generic";
4
+ export interface SnackPreset {
5
+ kind: SnackKind;
6
+ code: string;
7
+ title: string;
8
+ message: string;
9
+ primaryLabel: string;
10
+ secondaryLabel: string;
11
+ }
12
+ export declare const SNACK_PRESETS: Record<SnackKind, SnackPreset>;
13
+ declare class XmSnackbar extends LitElement {
14
+ kind: SnackKind;
15
+ code: string;
16
+ title: string;
17
+ message: string;
18
+ primaryLabel: string;
19
+ secondaryLabel: string;
20
+ showDismiss: boolean;
21
+ static: boolean;
22
+ createRenderRoot(): HTMLElement | DocumentFragment;
23
+ connectedCallback(): void;
24
+ private _onPrimary;
25
+ private _onDismiss;
26
+ render(): TemplateResult;
27
+ }
28
+ declare global {
29
+ interface HTMLElementTagNameMap {
30
+ "xm-snackbar": XmSnackbar;
31
+ }
32
+ }
33
+ export {};