srcdev-nuxt-components 9.3.2 → 9.3.4
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/.claude/skills/components/display-dialog.md +7 -7
- package/.claude/skills/components/display-toast.md +4 -0
- package/app/components/01.atoms/display-dialog/CONSUMER-STYLING.md +20 -8
- package/app/components/01.atoms/display-dialog/DisplayDialog.vue +17 -13
- package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +33 -33
- package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +6 -6
- package/app/components/01.atoms/toast/CONSUMER-STYLING.md +27 -0
- package/app/components/01.atoms/toast/DisplayToast.vue +4 -1
- package/app/components/01.atoms/toast/DisplayToastProvider.vue +8 -1
- package/package.json +1 -1
|
@@ -14,9 +14,9 @@ open/close state management.
|
|
|
14
14
|
|---|---|---|---|
|
|
15
15
|
| `dataDialogId` | `string` | — | **Required.** Unique identifier rendered as `data-dialog-id` attribute. |
|
|
16
16
|
| `variant` | `'dialog' \| 'modal' \| 'confirm' \| 'alert' \| 'fullscreen'` | `'dialog'` | Controls panel sizing and ARIA role. |
|
|
17
|
-
| `theme` | `SemanticTheme` | `undefined` | Optional header accent — `data-theme` on `.header` only; adds a coloured bottom border and tints the close icon. |
|
|
17
|
+
| `theme` | `SemanticTheme` | `undefined` | Optional header accent — `data-theme` on `.display-dialog-header` only; adds a coloured bottom border and tints the close icon. |
|
|
18
18
|
| `v-model` | `boolean` | — | Controls open/closed state. |
|
|
19
|
-
| `allowContentScroll` | `boolean` | `false` | Enables independent scroll on `.dialog-content`. |
|
|
19
|
+
| `allowContentScroll` | `boolean` | `false` | Enables independent scroll on `.display-dialog-content`. |
|
|
20
20
|
| `lockViewport` | `boolean` | `true` | Adds/removes `lock` class on `<body>` on mount/close. |
|
|
21
21
|
| `justifyDialog` | `'start' \| 'center' \| 'end'` | `'center'` | Horizontal position of panel within overlay. |
|
|
22
22
|
| `alignDialog` | `'start' \| 'center' \| 'end'` | `'center'` | Vertical position of panel within overlay. |
|
|
@@ -27,12 +27,12 @@ open/close state management.
|
|
|
27
27
|
|
|
28
28
|
| Slot | Renders in |
|
|
29
29
|
|---|---|
|
|
30
|
-
| `#dialogTitle` | `.col-left` in the header (hidden when slot is empty) |
|
|
31
|
-
| `#dialogContent` | `.dialog-content` (hidden when slot is empty) |
|
|
32
|
-
| `#actionButtonLeft` | Left side of `.footer` |
|
|
33
|
-
| `#actionButtonRight` | Right side of `.footer` |
|
|
30
|
+
| `#dialogTitle` | `.display-dialog-col-left` in the header (hidden when slot is empty) |
|
|
31
|
+
| `#dialogContent` | `.display-dialog-content` (hidden when slot is empty) |
|
|
32
|
+
| `#actionButtonLeft` | Left side of `.display-dialog-footer` |
|
|
33
|
+
| `#actionButtonRight` | Right side of `.display-dialog-footer` |
|
|
34
34
|
|
|
35
|
-
`.footer` is omitted entirely when neither action button slot is provided.
|
|
35
|
+
`.display-dialog-footer` is omitted entirely when neither action button slot is provided.
|
|
36
36
|
|
|
37
37
|
## Variants
|
|
38
38
|
|
|
@@ -218,6 +218,10 @@ Override tokens via an unscoped style block scoped to a page or layout class:
|
|
|
218
218
|
}
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
+
Stacking order: `--display-toast-z-index` / `--display-toast-provider-z-index`, both default
|
|
222
|
+
`999999` (matches `DisplayDialog`'s own default) — see `CONSUMER-STYLING.md` if a toast is
|
|
223
|
+
appearing behind a consumer's own sticky header.
|
|
224
|
+
|
|
221
225
|
### Masked variant
|
|
222
226
|
|
|
223
227
|
Setting `appearance.masked: true` swaps `AlertContent` for `AlertMaskedContent`, which uses an SVG-based border and a semi-transparent background (`rgba(0,0,0,0.3)`) so page content is faintly visible beneath the toast.
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# DisplayDialog — Consumer Styling Guide
|
|
2
2
|
|
|
3
|
+
## Structural class names (2026-08-22)
|
|
4
|
+
|
|
5
|
+
`DisplayDialog` renders `<dialog>` inline in the component tree, not via `<Teleport>` — so it's a
|
|
6
|
+
genuine descendant of wherever it's used in the page, and any ancestor's CSS selectors reach it.
|
|
7
|
+
Before 2026-08-22 its internal sections used bare generic class names (`.inner`, `.header`,
|
|
8
|
+
`.footer`, `.col-left`, `.col-right`, `.dialog-content`) — a consumer app with its own `.header`/
|
|
9
|
+
`.footer` layout classes (extremely common names) had its unlayered site CSS silently override the
|
|
10
|
+
dialog's own footer layout (button alignment, padding) purely by class-name collision, since
|
|
11
|
+
unlayered CSS always beats `@layer components` regardless of specificity. All six renamed to
|
|
12
|
+
`.display-dialog-*` prefixed variants (`.display-dialog-inner`, `.display-dialog-header`, etc.) —
|
|
13
|
+
update any consumer overrides written against the old names.
|
|
14
|
+
|
|
3
15
|
## Public token API
|
|
4
16
|
|
|
5
17
|
All `--display-dialog-*` tokens are the stable override surface. Because dialogs are site-wide UI
|
|
@@ -75,11 +87,11 @@ on `:root`. This applies to every `DisplayDialog` across the site.
|
|
|
75
87
|
|
|
76
88
|
The `fullscreen` variant bypasses `--_inner-border-radius`, `--_inner-border`, and
|
|
77
89
|
`--_inner-outline` directly (setting them to `0`/`none`). To restyle the fullscreen panel,
|
|
78
|
-
target the private tokens on `.inner.fullscreen`:
|
|
90
|
+
target the private tokens on `.display-dialog-inner.fullscreen`:
|
|
79
91
|
|
|
80
92
|
```css
|
|
81
93
|
.display-dialog {
|
|
82
|
-
.inner {
|
|
94
|
+
.display-dialog-inner {
|
|
83
95
|
&.confirm {
|
|
84
96
|
/* e.g. constrain confirm panel width further */
|
|
85
97
|
max-width: 40rem;
|
|
@@ -101,19 +113,19 @@ target the private tokens on `.inner.fullscreen`:
|
|
|
101
113
|
|
|
102
114
|
## Section targeting
|
|
103
115
|
|
|
104
|
-
Target `.header`, `.dialog-content`, and `.footer` directly to adjust layout within the panel:
|
|
116
|
+
Target `.display-dialog-header`, `.display-dialog-content`, and `.display-dialog-footer` directly to adjust layout within the panel:
|
|
105
117
|
|
|
106
118
|
```css
|
|
107
119
|
.display-dialog {
|
|
108
|
-
.header {
|
|
120
|
+
.display-dialog-header {
|
|
109
121
|
border-bottom: 0.1rem solid var(--brand-border);
|
|
110
122
|
}
|
|
111
123
|
|
|
112
|
-
.dialog-content {
|
|
124
|
+
.display-dialog-content {
|
|
113
125
|
/* content area uses --display-dialog-content-padding */
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
.footer {
|
|
128
|
+
.display-dialog-footer {
|
|
117
129
|
border-top: 0.1rem solid var(--brand-border);
|
|
118
130
|
justify-content: space-between; /* override default flex-end */
|
|
119
131
|
}
|
|
@@ -134,7 +146,7 @@ you can scope overrides to a specific page without affecting the rest of the sit
|
|
|
134
146
|
--display-dialog-inner-border-radius: 0;
|
|
135
147
|
--display-dialog-backdrop-background: rgba(0, 0, 0, 0.8);
|
|
136
148
|
|
|
137
|
-
.footer {
|
|
149
|
+
.display-dialog-footer {
|
|
138
150
|
justify-content: stretch;
|
|
139
151
|
}
|
|
140
152
|
}
|
|
@@ -156,7 +168,7 @@ needs a different look, pass a modifier class and target it alongside `.display-
|
|
|
156
168
|
.display-dialog.danger-dialog {
|
|
157
169
|
--display-dialog-backdrop-background: rgba(180, 0, 0, 0.4);
|
|
158
170
|
|
|
159
|
-
.inner {
|
|
171
|
+
.display-dialog-inner {
|
|
160
172
|
--display-dialog-inner-border: 0.2rem solid var(--color-danger);
|
|
161
173
|
}
|
|
162
174
|
}
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
:click-outside-deactivates="!isAlert"
|
|
17
17
|
@deactivate="closeDialog()"
|
|
18
18
|
>
|
|
19
|
-
<div class="inner" :class="[resolved.variant]">
|
|
20
|
-
<div class="header" :data-theme="resolved.theme">
|
|
21
|
-
<div v-if="hasTitle" :id="dialogTitleId" class="col-left">
|
|
19
|
+
<div class="display-dialog-inner" :class="[resolved.variant]">
|
|
20
|
+
<div class="display-dialog-header" :data-theme="resolved.theme">
|
|
21
|
+
<div v-if="hasTitle" :id="dialogTitleId" class="display-dialog-col-left">
|
|
22
22
|
<slot name="dialogTitle"></slot>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
|
-
<div class="col-right">
|
|
25
|
+
<div class="display-dialog-col-right">
|
|
26
26
|
<button
|
|
27
27
|
data-test-id="display-dialog-header-close"
|
|
28
28
|
class="display-dialog-close"
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
<div
|
|
37
37
|
v-if="hasContent"
|
|
38
|
-
class="dialog-content"
|
|
38
|
+
class="display-dialog-content"
|
|
39
39
|
:class="[{ 'allow-content-scroll': resolved.allowContentScroll }]"
|
|
40
40
|
:tabindex="resolved.allowContentScroll ? 0 : undefined"
|
|
41
41
|
>
|
|
42
42
|
<slot name="dialogContent"></slot>
|
|
43
43
|
</div>
|
|
44
|
-
<div v-if="hasFooter" class="footer">
|
|
44
|
+
<div v-if="hasFooter" class="display-dialog-footer">
|
|
45
45
|
<slot name="actionButtonLeft"></slot>
|
|
46
46
|
<slot name="actionButtonRight"></slot>
|
|
47
47
|
</div>
|
|
@@ -216,7 +216,7 @@ onUnmounted(() => {
|
|
|
216
216
|
align-items: flex-end;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
.inner {
|
|
219
|
+
.display-dialog-inner {
|
|
220
220
|
display: grid;
|
|
221
221
|
grid-template-rows: auto 1fr auto;
|
|
222
222
|
border-radius: var(--_inner-border-radius);
|
|
@@ -232,7 +232,11 @@ onUnmounted(() => {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
&.confirm {
|
|
235
|
-
width: initial
|
|
235
|
+
/* Matches .alert's cap. Plain `width: initial` (== auto) makes this a shrink-to-fit flex
|
|
236
|
+
item with no wrapping constraint, so a long confirm sentence renders as one unbroken
|
|
237
|
+
line and drags the whole dialog (and its footer buttons) out to that width instead of
|
|
238
|
+
wrapping normally — visible once a real confirm message runs more than a few words. */
|
|
239
|
+
width: min(90%, 48rem);
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
&.dialog {
|
|
@@ -252,7 +256,7 @@ onUnmounted(() => {
|
|
|
252
256
|
width: initial;
|
|
253
257
|
}
|
|
254
258
|
|
|
255
|
-
.header {
|
|
259
|
+
.display-dialog-header {
|
|
256
260
|
display: flex;
|
|
257
261
|
align-items: center;
|
|
258
262
|
padding: var(--_header-padding);
|
|
@@ -265,11 +269,11 @@ onUnmounted(() => {
|
|
|
265
269
|
}
|
|
266
270
|
}
|
|
267
271
|
|
|
268
|
-
.col-left {
|
|
272
|
+
.display-dialog-col-left {
|
|
269
273
|
flex: 1;
|
|
270
274
|
}
|
|
271
275
|
|
|
272
|
-
.col-right {
|
|
276
|
+
.display-dialog-col-right {
|
|
273
277
|
margin-inline-start: auto;
|
|
274
278
|
|
|
275
279
|
.display-dialog-close {
|
|
@@ -303,7 +307,7 @@ onUnmounted(() => {
|
|
|
303
307
|
}
|
|
304
308
|
}
|
|
305
309
|
|
|
306
|
-
.dialog-content {
|
|
310
|
+
.display-dialog-content {
|
|
307
311
|
overflow: hidden;
|
|
308
312
|
padding: var(--_content-padding);
|
|
309
313
|
|
|
@@ -315,7 +319,7 @@ onUnmounted(() => {
|
|
|
315
319
|
}
|
|
316
320
|
}
|
|
317
321
|
|
|
318
|
-
.footer {
|
|
322
|
+
.display-dialog-footer {
|
|
319
323
|
display: flex;
|
|
320
324
|
gap: var(--_footer-gap);
|
|
321
325
|
justify-content: flex-end;
|
|
@@ -116,81 +116,81 @@ describe("DisplayDialog", () => {
|
|
|
116
116
|
expect(wrapper.attributes("role")).toBe("alertdialog");
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
// ─── Variant class on .inner ──────────────────────────────────────────────
|
|
119
|
+
// ─── Variant class on .display-dialog-inner ──────────────────────────────────────────────
|
|
120
120
|
|
|
121
|
-
it("applies 'dialog' class to .inner by default", async () => {
|
|
121
|
+
it("applies 'dialog' class to .display-dialog-inner by default", async () => {
|
|
122
122
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
123
123
|
props: { dataDialogId: "test" },
|
|
124
124
|
});
|
|
125
|
-
expect(wrapper.find(".inner").classes()).toContain("dialog");
|
|
125
|
+
expect(wrapper.find(".display-dialog-inner").classes()).toContain("dialog");
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
it.each(["dialog", "modal", "confirm", "alert", "fullscreen"] as const)(
|
|
129
|
-
"applies '%s' class to .inner",
|
|
129
|
+
"applies '%s' class to .display-dialog-inner",
|
|
130
130
|
async (variant) => {
|
|
131
131
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
132
132
|
props: { dataDialogId: "test", variant },
|
|
133
133
|
});
|
|
134
|
-
expect(wrapper.find(".inner").classes()).toContain(variant);
|
|
134
|
+
expect(wrapper.find(".display-dialog-inner").classes()).toContain(variant);
|
|
135
135
|
}
|
|
136
136
|
);
|
|
137
137
|
|
|
138
138
|
// ─── Slots ────────────────────────────────────────────────────────────────
|
|
139
139
|
|
|
140
|
-
it("renders dialogTitle slot in .col-left", async () => {
|
|
140
|
+
it("renders dialogTitle slot in .display-dialog-col-left", async () => {
|
|
141
141
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
142
142
|
props: { dataDialogId: "test" },
|
|
143
143
|
slots: { dialogTitle: "<p>My Title</p>" },
|
|
144
144
|
});
|
|
145
|
-
expect(wrapper.find(".col-left").text()).toBe("My Title");
|
|
145
|
+
expect(wrapper.find(".display-dialog-col-left").text()).toBe("My Title");
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
it("omits .col-left when dialogTitle slot is not provided", async () => {
|
|
148
|
+
it("omits .display-dialog-col-left when dialogTitle slot is not provided", async () => {
|
|
149
149
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
150
150
|
props: { dataDialogId: "test" },
|
|
151
151
|
});
|
|
152
|
-
expect(wrapper.find(".col-left").exists()).toBe(false);
|
|
152
|
+
expect(wrapper.find(".display-dialog-col-left").exists()).toBe(false);
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
it("renders dialogContent slot in .dialog-content", async () => {
|
|
155
|
+
it("renders dialogContent slot in .display-dialog-content", async () => {
|
|
156
156
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
157
157
|
props: { dataDialogId: "test" },
|
|
158
158
|
slots: { dialogContent: "<p>Content</p>" },
|
|
159
159
|
});
|
|
160
|
-
expect(wrapper.find(".dialog-content").text()).toBe("Content");
|
|
160
|
+
expect(wrapper.find(".display-dialog-content").text()).toBe("Content");
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
it("omits .dialog-content when dialogContent slot is not provided", async () => {
|
|
163
|
+
it("omits .display-dialog-content when dialogContent slot is not provided", async () => {
|
|
164
164
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
165
165
|
props: { dataDialogId: "test" },
|
|
166
166
|
});
|
|
167
|
-
expect(wrapper.find(".dialog-content").exists()).toBe(false);
|
|
167
|
+
expect(wrapper.find(".display-dialog-content").exists()).toBe(false);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
it("renders .footer when actionButtonLeft slot is provided", async () => {
|
|
170
|
+
it("renders .display-dialog-footer when actionButtonLeft slot is provided", async () => {
|
|
171
171
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
172
172
|
props: { dataDialogId: "test" },
|
|
173
173
|
slots: { actionButtonLeft: "<button>Cancel</button>" },
|
|
174
174
|
});
|
|
175
|
-
expect(wrapper.find(".footer").exists()).toBe(true);
|
|
175
|
+
expect(wrapper.find(".display-dialog-footer").exists()).toBe(true);
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
it("renders .footer when actionButtonRight slot is provided", async () => {
|
|
178
|
+
it("renders .display-dialog-footer when actionButtonRight slot is provided", async () => {
|
|
179
179
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
180
180
|
props: { dataDialogId: "test" },
|
|
181
181
|
slots: { actionButtonRight: "<button>Confirm</button>" },
|
|
182
182
|
});
|
|
183
|
-
expect(wrapper.find(".footer").exists()).toBe(true);
|
|
183
|
+
expect(wrapper.find(".display-dialog-footer").exists()).toBe(true);
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
-
it("omits .footer when neither action button slot is provided", async () => {
|
|
186
|
+
it("omits .display-dialog-footer when neither action button slot is provided", async () => {
|
|
187
187
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
188
188
|
props: { dataDialogId: "test" },
|
|
189
189
|
});
|
|
190
|
-
expect(wrapper.find(".footer").exists()).toBe(false);
|
|
190
|
+
expect(wrapper.find(".display-dialog-footer").exists()).toBe(false);
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
it("renders both action button slots inside .footer", async () => {
|
|
193
|
+
it("renders both action button slots inside .display-dialog-footer", async () => {
|
|
194
194
|
const wrapper = await mountSuspended(DisplayDialog, {
|
|
195
195
|
props: { dataDialogId: "test" },
|
|
196
196
|
slots: {
|
|
@@ -198,8 +198,8 @@ describe("DisplayDialog", () => {
|
|
|
198
198
|
actionButtonRight: "<button>Confirm</button>",
|
|
199
199
|
},
|
|
200
200
|
});
|
|
201
|
-
expect(wrapper.find(".footer").text()).toContain("Cancel");
|
|
202
|
-
expect(wrapper.find(".footer").text()).toContain("Confirm");
|
|
201
|
+
expect(wrapper.find(".display-dialog-footer").text()).toContain("Cancel");
|
|
202
|
+
expect(wrapper.find(".display-dialog-footer").text()).toContain("Confirm");
|
|
203
203
|
});
|
|
204
204
|
|
|
205
205
|
// ─── allowContentScroll ───────────────────────────────────────────────────
|
|
@@ -209,7 +209,7 @@ describe("DisplayDialog", () => {
|
|
|
209
209
|
props: { dataDialogId: "test" },
|
|
210
210
|
slots: { dialogContent: "<p>Content</p>" },
|
|
211
211
|
});
|
|
212
|
-
expect(wrapper.find(".dialog-content").classes()).not.toContain("allow-content-scroll");
|
|
212
|
+
expect(wrapper.find(".display-dialog-content").classes()).not.toContain("allow-content-scroll");
|
|
213
213
|
});
|
|
214
214
|
|
|
215
215
|
it("applies allow-content-scroll class when allowContentScroll=true", async () => {
|
|
@@ -217,7 +217,7 @@ describe("DisplayDialog", () => {
|
|
|
217
217
|
props: { dataDialogId: "test", allowContentScroll: true },
|
|
218
218
|
slots: { dialogContent: "<p>Content</p>" },
|
|
219
219
|
});
|
|
220
|
-
expect(wrapper.find(".dialog-content").classes()).toContain("allow-content-scroll");
|
|
220
|
+
expect(wrapper.find(".display-dialog-content").classes()).toContain("allow-content-scroll");
|
|
221
221
|
});
|
|
222
222
|
|
|
223
223
|
// ─── Close button ─────────────────────────────────────────────────────────
|
|
@@ -312,12 +312,12 @@ describe("DisplayDialog", () => {
|
|
|
312
312
|
props: { dataDialogId: "test" },
|
|
313
313
|
slots: { dialogContent: "<p>Content</p>" },
|
|
314
314
|
});
|
|
315
|
-
expect(wrapper.find(".inner").classes()).toContain("dialog");
|
|
315
|
+
expect(wrapper.find(".display-dialog-inner").classes()).toContain("dialog");
|
|
316
316
|
expect(wrapper.attributes("align-dialog")).toBe("center");
|
|
317
317
|
expect(wrapper.attributes("justify-dialog")).toBe("center");
|
|
318
318
|
expect(document.body.classList.contains("lock")).toBe(true);
|
|
319
|
-
expect(wrapper.find(".dialog-content").classes()).not.toContain("allow-content-scroll");
|
|
320
|
-
expect(wrapper.find(".header").attributes("data-theme")).toBeUndefined();
|
|
319
|
+
expect(wrapper.find(".display-dialog-content").classes()).not.toContain("allow-content-scroll");
|
|
320
|
+
expect(wrapper.find(".display-dialog-header").attributes("data-theme")).toBeUndefined();
|
|
321
321
|
expect(iconName(wrapper)).toContain("bitcoin-icons:cross-filled");
|
|
322
322
|
});
|
|
323
323
|
|
|
@@ -340,12 +340,12 @@ describe("DisplayDialog", () => {
|
|
|
340
340
|
props: { dataDialogId: "test" },
|
|
341
341
|
slots: { dialogContent: "<p>Content</p>" },
|
|
342
342
|
});
|
|
343
|
-
expect(wrapper.find(".inner").classes()).toContain("modal");
|
|
343
|
+
expect(wrapper.find(".display-dialog-inner").classes()).toContain("modal");
|
|
344
344
|
expect(wrapper.attributes("align-dialog")).toBe("end");
|
|
345
345
|
expect(wrapper.attributes("justify-dialog")).toBe("start");
|
|
346
346
|
expect(document.body.classList.contains("lock")).toBe(false);
|
|
347
|
-
expect(wrapper.find(".dialog-content").classes()).toContain("allow-content-scroll");
|
|
348
|
-
expect(wrapper.find(".header").attributes("data-theme")).toBe("info");
|
|
347
|
+
expect(wrapper.find(".display-dialog-content").classes()).toContain("allow-content-scroll");
|
|
348
|
+
expect(wrapper.find(".display-dialog-header").attributes("data-theme")).toBe("info");
|
|
349
349
|
expect(iconName(wrapper)).toContain("heroicons:x-mark");
|
|
350
350
|
});
|
|
351
351
|
|
|
@@ -377,12 +377,12 @@ describe("DisplayDialog", () => {
|
|
|
377
377
|
},
|
|
378
378
|
slots: { dialogContent: "<p>Content</p>" },
|
|
379
379
|
});
|
|
380
|
-
expect(wrapper.find(".inner").classes()).toContain("fullscreen");
|
|
380
|
+
expect(wrapper.find(".display-dialog-inner").classes()).toContain("fullscreen");
|
|
381
381
|
expect(wrapper.attributes("align-dialog")).toBe("start");
|
|
382
382
|
expect(wrapper.attributes("justify-dialog")).toBe("end");
|
|
383
383
|
expect(document.body.classList.contains("lock")).toBe(true);
|
|
384
|
-
expect(wrapper.find(".dialog-content").classes()).not.toContain("allow-content-scroll");
|
|
385
|
-
expect(wrapper.find(".header").attributes("data-theme")).toBe("error");
|
|
384
|
+
expect(wrapper.find(".display-dialog-content").classes()).not.toContain("allow-content-scroll");
|
|
385
|
+
expect(wrapper.find(".display-dialog-header").attributes("data-theme")).toBe("error");
|
|
386
386
|
expect(iconName(wrapper)).toContain("material-symbols:close");
|
|
387
387
|
});
|
|
388
388
|
|
package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`DisplayDialog > renders correct HTML structure with all slots 1`] = `
|
|
4
4
|
"<dialog class="display-dialog" aria-modal="true" aria-labelledby="v-0-0" align-dialog="center" justify-dialog="center" open="" data-dialog-id="test">
|
|
5
|
-
<div class="inner dialog">
|
|
6
|
-
<div class="header">
|
|
7
|
-
<div id="v-0-0" class="col-left">
|
|
5
|
+
<div class="display-dialog-inner dialog">
|
|
6
|
+
<div class="display-dialog-header">
|
|
7
|
+
<div id="v-0-0" class="display-dialog-col-left">
|
|
8
8
|
<p>Title</p>
|
|
9
9
|
</div>
|
|
10
|
-
<div class="col-right"><button data-test-id="display-dialog-header-close" class="display-dialog-close"><span class="iconify undefinedbitcoin-icons:cross-filled icon"></span><span class="sr-only">Close</span></button></div>
|
|
10
|
+
<div class="display-dialog-col-right"><button data-test-id="display-dialog-header-close" class="display-dialog-close"><span class="iconify undefinedbitcoin-icons:cross-filled icon"></span><span class="sr-only">Close</span></button></div>
|
|
11
11
|
</div>
|
|
12
|
-
<div class="dialog-content">
|
|
12
|
+
<div class="display-dialog-content">
|
|
13
13
|
<p>Content</p>
|
|
14
14
|
</div>
|
|
15
|
-
<div class="footer"><button>Cancel</button><button>Confirm</button></div>
|
|
15
|
+
<div class="display-dialog-footer"><button>Cancel</button><button>Confirm</button></div>
|
|
16
16
|
</div>
|
|
17
17
|
</dialog>"
|
|
18
18
|
`;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# DisplayToast / DisplayToastProvider — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Stacking order
|
|
4
|
+
|
|
5
|
+
```css
|
|
6
|
+
--display-toast-z-index: 999999; /* DisplayToast (standalone) */
|
|
7
|
+
--display-toast-provider-z-index: 999999; /* DisplayToastProvider (queue) */
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Both default to `999999`, matching `DisplayDialog`'s own `--display-dialog-z-index` default — a
|
|
11
|
+
toast is meant to float above ordinary page chrome (headers, sticky nav), so it needs to clear
|
|
12
|
+
whatever z-index a consumer's own layout uses for that. If a toast appears behind your site
|
|
13
|
+
header, your header's z-index is at or above `999999`; either raise the toast's token to match/
|
|
14
|
+
exceed it, or (more robust) lower the header's z-index to something more ordinary — very few
|
|
15
|
+
elements legitimately need to sit above a `dialog`/`alertdialog`.
|
|
16
|
+
|
|
17
|
+
If a `DisplayDialog` and a toast happen to be visible at the same time, the two tie on DOM/paint
|
|
18
|
+
order at equal z-index — an acceptable edge case for a transient notification, not worth resolving
|
|
19
|
+
with a strict priority order between them.
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
/* Global override, e.g. assets/styles/setup/07.components/display-toast.css */
|
|
23
|
+
:root {
|
|
24
|
+
--display-toast-z-index: 1000000;
|
|
25
|
+
--display-toast-provider-z-index: 1000000;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
@@ -228,13 +228,16 @@ onBeforeRouteLeave(() => {
|
|
|
228
228
|
--_toast-gutter: 24px;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/* See the matching comment in DisplayToastProvider.vue — same fix, same rationale. */
|
|
232
|
+
--_z-index: var(--display-toast-z-index, 999999);
|
|
233
|
+
|
|
231
234
|
display: block;
|
|
232
235
|
overflow: hidden;
|
|
233
236
|
position: fixed;
|
|
234
237
|
margin: 0;
|
|
235
238
|
opacity: 0;
|
|
236
239
|
|
|
237
|
-
z-index:
|
|
240
|
+
z-index: var(--_z-index);
|
|
238
241
|
|
|
239
242
|
/* Focus styles for accessibility */
|
|
240
243
|
&:focus {
|
|
@@ -220,11 +220,18 @@ onUnmounted(() => {
|
|
|
220
220
|
--_gutter: 24px;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
/* No public token before 2026-08-22 and hardcoded far below DisplayDialog's own
|
|
224
|
+
--display-dialog-z-index default (999999) — a toast is meant to float above ordinary page
|
|
225
|
+
chrome, but 100 loses to any consumer's sticky header. Matches DisplayDialog's default so
|
|
226
|
+
toasts also clear a modal's own overlay; the two ties on DOM/paint order if both happen to
|
|
227
|
+
be visible at once, which is an acceptable edge case for a transient notification. */
|
|
228
|
+
--_z-index: var(--display-toast-provider-z-index, 999999);
|
|
229
|
+
|
|
223
230
|
position: fixed;
|
|
224
231
|
display: flex;
|
|
225
232
|
flex-direction: column;
|
|
226
233
|
gap: 8px;
|
|
227
|
-
z-index:
|
|
234
|
+
z-index: var(--_z-index);
|
|
228
235
|
|
|
229
236
|
inset-inline: var(--_gutter);
|
|
230
237
|
margin-inline: auto;
|