@webority/theme 0.24.0 → 0.26.0

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 (42) hide show
  1. package/README.md +16 -2
  2. package/dist/purge-safelist.json +199 -2
  3. package/dist/webority-brand-signal.css +1 -0
  4. package/dist/webority-theme.css +2 -2
  5. package/fonts/sans/OFL.txt +92 -0
  6. package/fonts/sans/WeboritySans-Bold.woff2 +0 -0
  7. package/fonts/sans/WeboritySans-BoldItalic.woff2 +0 -0
  8. package/fonts/sans/WeboritySans-Medium.woff2 +0 -0
  9. package/fonts/sans/WeboritySans-Regular.woff2 +0 -0
  10. package/fonts/sans/WeboritySans-RegularItalic.woff2 +0 -0
  11. package/fonts/sans/WeboritySans-SemiBold.woff2 +0 -0
  12. package/fonts/serif/WeboritySerif-Bold.woff2 +0 -0
  13. package/fonts/serif/WeboritySerif-BoldItalic.woff2 +0 -0
  14. package/fonts/serif/WeboritySerif-DemiBold.woff2 +0 -0
  15. package/fonts/serif/WeboritySerif-ExtraBold.woff2 +0 -0
  16. package/fonts/serif/WeboritySerif-Medium.woff2 +0 -0
  17. package/fonts/serif/WeboritySerif-Regular.woff2 +0 -0
  18. package/fonts/serif/WeboritySerif-RegularItalic.woff2 +0 -0
  19. package/index.scss +2 -1
  20. package/package.json +4 -2
  21. package/scss/_brand-signal.scss +202 -0
  22. package/scss/_fonts.scss +96 -0
  23. package/scss/_tokens.scss +55 -2
  24. package/scss/_variables.scss +2 -1
  25. package/scss/_wui-aliases.scss +21 -0
  26. package/scss/_wui-components.scss +12 -0
  27. package/scss/wui/_calendar.scss +256 -0
  28. package/scss/wui/_chart-card.scss +86 -0
  29. package/scss/wui/_chart.scss +524 -0
  30. package/scss/wui/_context-menu.scss +53 -0
  31. package/scss/wui/_data.scss +8 -6
  32. package/scss/wui/_display.scss +1 -1
  33. package/scss/wui/_image-crop.scss +146 -0
  34. package/scss/wui/_kanban.scss +434 -0
  35. package/scss/wui/_modal-remote.scss +55 -0
  36. package/scss/wui/_overlays.scss +39 -0
  37. package/scss/wui/_rich-text.scss +281 -0
  38. package/scss/wui/_shell-parts.scss +503 -0
  39. package/scss/wui/_shell.scss +11 -0
  40. package/scss/wui/_sortable.scss +135 -0
  41. package/scss/wui/_table-server.scss +40 -0
  42. package/scss/wui/_tree.scss +124 -0
@@ -0,0 +1,146 @@
1
+ // ── Image crop ──
2
+ // <wui-image-crop>: the crop surface AppLogoPicker / <app-logo-picker> shows in a dialog before
3
+ // upload. The image is fitted into the stage; the frame is positioned by the element (inline
4
+ // left/top/width/height) and dims everything outside it with a very large box-shadow, which the
5
+ // stage clips. Every colour is a theme token, so dark mode follows the token flip.
6
+ //
7
+ // Part of the wui-components layer, see _wui-components.scss for load order.
8
+
9
+ wui-image-crop {
10
+ display: block;
11
+ }
12
+
13
+ .wui-crop-stage {
14
+ position: relative;
15
+ height: min(360px, 55vh);
16
+ overflow: hidden;
17
+ border-radius: var(--radius-lg);
18
+ background: var(--color-bg-muted);
19
+ // The frame is dragged with a finger, so the browser must not scroll or zoom the page instead.
20
+ touch-action: none;
21
+ user-select: none;
22
+ cursor: grab;
23
+ }
24
+ .wui-crop-stage:active {
25
+ cursor: grabbing;
26
+ }
27
+ .wui-crop-stage:focus-visible {
28
+ outline: 2px solid var(--color-primary);
29
+ outline-offset: 2px;
30
+ }
31
+
32
+ .wui-crop-img {
33
+ position: absolute;
34
+ max-width: none;
35
+ pointer-events: none;
36
+ }
37
+ .wui-crop-img[hidden],
38
+ .wui-crop-frame[hidden] {
39
+ display: none;
40
+ }
41
+
42
+ .wui-crop-frame {
43
+ position: absolute;
44
+ border: 2px solid #fff;
45
+ box-shadow:
46
+ 0 0 0 9999px rgb(0 0 0 / 55%),
47
+ 0 0 0 1px rgb(0 0 0 / 35%);
48
+ // Thirds guide, so a person lining up a face or a logo has something to hold against.
49
+ background-image:
50
+ linear-gradient(to right, transparent 33.2%, rgb(255 255 255 / 35%) 33.3%, transparent 33.5%, transparent 66.5%, rgb(255 255 255 / 35%) 66.7%, transparent 66.8%),
51
+ linear-gradient(to bottom, transparent 33.2%, rgb(255 255 255 / 35%) 33.3%, transparent 33.5%, transparent 66.5%, rgb(255 255 255 / 35%) 66.7%, transparent 66.8%);
52
+ }
53
+ .wui-crop-frame--round {
54
+ border-radius: 50%;
55
+ background-image: none;
56
+ }
57
+
58
+ // A corner handle is a 44px tap target drawn as a small square at its corner.
59
+ .wui-crop-handle {
60
+ position: absolute;
61
+ width: 44px;
62
+ height: 44px;
63
+ padding: 0;
64
+ border: 0;
65
+ background: transparent;
66
+ cursor: nwse-resize;
67
+ }
68
+ .wui-crop-handle::after {
69
+ content: "";
70
+ position: absolute;
71
+ width: 14px;
72
+ height: 14px;
73
+ border-radius: 3px;
74
+ background: #fff;
75
+ box-shadow: 0 0 0 1px rgb(0 0 0 / 45%);
76
+ }
77
+ .wui-crop-handle:focus-visible::after {
78
+ background: var(--color-primary);
79
+ }
80
+ .wui-crop-handle--nw {
81
+ top: -22px;
82
+ left: -22px;
83
+ }
84
+ .wui-crop-handle--ne {
85
+ top: -22px;
86
+ right: -22px;
87
+ cursor: nesw-resize;
88
+ }
89
+ .wui-crop-handle--sw {
90
+ bottom: -22px;
91
+ left: -22px;
92
+ cursor: nesw-resize;
93
+ }
94
+ .wui-crop-handle--se {
95
+ right: -22px;
96
+ bottom: -22px;
97
+ }
98
+ .wui-crop-handle--nw::after {
99
+ top: 15px;
100
+ left: 15px;
101
+ }
102
+ .wui-crop-handle--ne::after {
103
+ top: 15px;
104
+ right: 15px;
105
+ }
106
+ .wui-crop-handle--sw::after {
107
+ bottom: 15px;
108
+ left: 15px;
109
+ }
110
+ .wui-crop-handle--se::after {
111
+ right: 15px;
112
+ bottom: 15px;
113
+ }
114
+
115
+ .wui-crop-controls {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 12px;
119
+ margin-top: 16px;
120
+ }
121
+ .wui-crop-zoom {
122
+ flex: 1 1 auto;
123
+ min-width: 0;
124
+ margin: 0;
125
+ }
126
+ .wui-crop-zoombtn {
127
+ display: inline-flex;
128
+ flex: 0 0 auto;
129
+ align-items: center;
130
+ justify-content: center;
131
+ width: 36px;
132
+ height: 36px;
133
+ padding: 0;
134
+ border: 1px solid var(--color-border);
135
+ border-radius: 50%;
136
+ background: var(--color-surface);
137
+ color: var(--color-text);
138
+ cursor: pointer;
139
+ }
140
+ .wui-crop-zoombtn:hover {
141
+ border-color: var(--color-border-strong);
142
+ }
143
+ .wui-crop-zoombtn:focus-visible {
144
+ outline: 2px solid var(--color-primary);
145
+ outline-offset: 2px;
146
+ }
@@ -0,0 +1,434 @@
1
+ // ── Kanban board ──
2
+ // AppKanban / <app-kanban> over <wui-kanban>. The library owns the column shell, the card shell
3
+ // and every drag state; card CONTENT is the product's. State lives in data attributes the element
4
+ // writes (data-wui-drop, data-wui-over, data-wui-dragging, data-collapsed, aria-busy), never in
5
+ // classes, so a React re-render never wipes it.
6
+ //
7
+ // Below md the board is one column per screen: a scroll-snapped strip with a column switcher
8
+ // above it. The board scrolls inside itself, so the page never scrolls sideways.
9
+ //
10
+ // Part of the wui-components layer, see _wui-components.scss for load order.
11
+
12
+ .wui-kanban {
13
+ --wui-kanban-column-width: 288px;
14
+ --wui-kanban-collapsed-width: 56px;
15
+ --wui-kanban-gap: 16px;
16
+ display: block;
17
+ position: relative;
18
+ min-width: 0;
19
+ max-width: 100%;
20
+ }
21
+
22
+ .wui-kanban-board {
23
+ display: flex;
24
+ align-items: flex-start;
25
+ gap: var(--wui-kanban-gap);
26
+ overflow-x: auto;
27
+ overscroll-behavior-x: contain;
28
+ padding-bottom: 8px;
29
+ }
30
+
31
+ .wui-kanban-column {
32
+ display: flex;
33
+ flex-direction: column;
34
+ flex: 1 0 var(--wui-kanban-column-width);
35
+ min-width: 0;
36
+ max-width: 100%;
37
+ background: var(--color-bg-subtle);
38
+ border: 1px solid var(--color-border-light);
39
+ border-radius: var(--wui-radius-lg);
40
+ transition:
41
+ background-color 160ms var(--wui-ease-ui),
42
+ border-color 160ms var(--wui-ease-ui);
43
+ }
44
+
45
+ // ── Column head ──
46
+ .wui-kanban-head {
47
+ display: flex;
48
+ flex-wrap: wrap;
49
+ align-items: center;
50
+ gap: 6px 8px;
51
+ padding: 12px 12px 8px;
52
+ }
53
+
54
+ .wui-kanban-dot {
55
+ flex: none;
56
+ width: 10px;
57
+ height: 10px;
58
+ border-radius: 50%;
59
+ background: var(--color-text-subtle);
60
+ }
61
+ .wui-kanban-column[data-tone="primary"] .wui-kanban-dot {
62
+ background: var(--color-primary);
63
+ }
64
+ .wui-kanban-column[data-tone="info"] .wui-kanban-dot {
65
+ background: var(--color-info);
66
+ }
67
+ .wui-kanban-column[data-tone="success"] .wui-kanban-dot {
68
+ background: var(--color-success);
69
+ }
70
+ .wui-kanban-column[data-tone="warning"] .wui-kanban-dot {
71
+ background: var(--color-warning);
72
+ }
73
+ .wui-kanban-column[data-tone="danger"] .wui-kanban-dot {
74
+ background: var(--color-danger);
75
+ }
76
+
77
+ .wui-kanban-title {
78
+ flex: 1 1 auto;
79
+ min-width: 0;
80
+ margin: 0;
81
+ font-size: 0.9375rem;
82
+ font-weight: 600;
83
+ line-height: 1.3;
84
+ color: var(--color-text);
85
+ overflow-wrap: anywhere;
86
+
87
+ a {
88
+ color: inherit;
89
+ text-decoration: none;
90
+ }
91
+ a:hover {
92
+ text-decoration: underline;
93
+ }
94
+ }
95
+
96
+ .wui-kanban-count {
97
+ flex: none;
98
+ min-width: 24px;
99
+ padding: 1px 8px;
100
+ border-radius: 999px;
101
+ background: var(--color-surface);
102
+ border: 1px solid var(--color-border);
103
+ color: var(--color-text-muted);
104
+ font-size: 0.75rem;
105
+ font-weight: 600;
106
+ line-height: 1.5;
107
+ text-align: center;
108
+ font-variant-numeric: tabular-nums;
109
+ }
110
+ .wui-kanban-count:empty {
111
+ display: none;
112
+ }
113
+
114
+ .wui-kanban-collapse {
115
+ display: inline-grid;
116
+ place-items: center;
117
+ flex: none;
118
+ width: 32px;
119
+ height: 32px;
120
+ padding: 0;
121
+ border: 0;
122
+ border-radius: var(--wui-radius);
123
+ background: transparent;
124
+ color: var(--color-text-muted);
125
+ cursor: pointer;
126
+
127
+ svg {
128
+ transition: transform 160ms var(--wui-ease-ui);
129
+ }
130
+ }
131
+ .wui-kanban-collapse:hover {
132
+ background: var(--color-bg-muted);
133
+ color: var(--color-text);
134
+ }
135
+ .wui-kanban-collapse:focus-visible,
136
+ .wui-kanban-switch:focus-visible {
137
+ outline: 2px solid var(--color-primary);
138
+ outline-offset: 2px;
139
+ }
140
+ @media (pointer: coarse) {
141
+ .wui-kanban-collapse {
142
+ width: 44px;
143
+ height: 44px;
144
+ }
145
+ }
146
+
147
+ .wui-kanban-summary {
148
+ flex: 1 0 100%;
149
+ padding-left: 18px;
150
+ color: var(--color-text-muted);
151
+ font-size: 0.8125rem;
152
+ font-weight: 500;
153
+ font-variant-numeric: tabular-nums;
154
+ }
155
+ .wui-kanban-summary:empty {
156
+ display: none;
157
+ }
158
+
159
+ // ── Body, cards, empty state ──
160
+ .wui-kanban-body {
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: 8px;
164
+ min-height: 64px;
165
+ padding: 0 8px 8px;
166
+ }
167
+
168
+ .wui-kanban-empty {
169
+ margin: 0 8px 8px;
170
+ padding: 16px 8px;
171
+ border: 1px dashed var(--color-border);
172
+ border-radius: var(--wui-radius);
173
+ color: var(--color-text-muted);
174
+ font-size: 0.8125rem;
175
+ text-align: center;
176
+ }
177
+ .wui-kanban-card {
178
+ position: relative;
179
+ min-height: 44px;
180
+ padding: 12px;
181
+ background: var(--color-surface);
182
+ border: 1px solid var(--color-border);
183
+ border-radius: var(--wui-radius);
184
+ color: var(--color-text);
185
+ cursor: grab;
186
+ touch-action: manipulation;
187
+ user-select: none;
188
+ -webkit-user-select: none;
189
+ transition:
190
+ box-shadow 160ms var(--wui-ease-ui),
191
+ opacity 160ms var(--wui-ease-ui);
192
+ }
193
+ .wui-kanban-card:hover {
194
+ border-color: var(--color-border-strong);
195
+ }
196
+ .wui-kanban-card:focus-visible {
197
+ outline: 2px solid var(--color-primary);
198
+ outline-offset: 2px;
199
+ }
200
+ .wui-kanban-card[hidden] {
201
+ display: none;
202
+ }
203
+ .wui-kanban[readonly] .wui-kanban-card,
204
+ .wui-kanban-card[data-wui-locked] {
205
+ cursor: default;
206
+ }
207
+ // The card while it is carried: its place stays, as a faint outline.
208
+ .wui-kanban-card[data-wui-dragging] {
209
+ opacity: 0.35;
210
+ border-style: dashed;
211
+ }
212
+ // Picked up by keyboard: the card itself shows it is lifted.
213
+ .wui-kanban-card[data-wui-lifted] {
214
+ box-shadow: var(--wui-shadow-overlay);
215
+ outline: 2px solid var(--color-primary);
216
+ outline-offset: 2px;
217
+ }
218
+ // Saving: the card cannot move again until the server answers.
219
+ .wui-kanban-card[aria-busy="true"] {
220
+ cursor: progress;
221
+ opacity: 0.7;
222
+ }
223
+ .wui-kanban-card[aria-busy="true"]::after {
224
+ content: "";
225
+ position: absolute;
226
+ inset: auto 0 0;
227
+ height: 3px;
228
+ border-radius: 0 0 var(--wui-radius) var(--wui-radius);
229
+ background: linear-gradient(90deg, transparent, var(--color-primary), transparent);
230
+ background-size: 200% 100%;
231
+ animation: wui-kanban-saving 1.1s linear infinite;
232
+ }
233
+ @keyframes wui-kanban-saving {
234
+ from {
235
+ background-position: 100% 0;
236
+ }
237
+ to {
238
+ background-position: -100% 0;
239
+ }
240
+ }
241
+
242
+ .wui-kanban-ghost {
243
+ position: fixed;
244
+ top: 0;
245
+ left: 0;
246
+ z-index: var(--wui-z-tooltip);
247
+ margin: 0;
248
+ pointer-events: none;
249
+ cursor: grabbing;
250
+ box-shadow: var(--wui-shadow-overlay);
251
+ rotate: 2deg;
252
+ }
253
+ .wui-kanban[data-wui-dragging] {
254
+ cursor: grabbing;
255
+ }
256
+
257
+ // ── Drop states, while a card is carried ──
258
+ .wui-kanban-drop-note {
259
+ margin: 0 8px 8px;
260
+ padding: 8px 10px;
261
+ border-radius: var(--wui-radius);
262
+ background: var(--color-surface);
263
+ color: var(--color-text-muted);
264
+ font-size: 0.8125rem;
265
+ line-height: 1.4;
266
+ }
267
+ .wui-kanban-column[data-wui-drop="ok"] {
268
+ border: 1px dashed var(--color-primary);
269
+ }
270
+ .wui-kanban-column[data-wui-drop="ok"][data-wui-over],
271
+ .wui-kanban-column[data-wui-drop="ok"][data-wui-target] {
272
+ background: var(--color-primary-soft);
273
+ border-style: solid;
274
+ }
275
+ .wui-kanban-column[data-wui-drop="ok"] .wui-kanban-drop-note {
276
+ color: var(--color-text);
277
+ }
278
+ // Refused: hatched and locked, with the reason in the note.
279
+ .wui-kanban-column[data-wui-drop="refused"] {
280
+ border-color: var(--color-border);
281
+ background-image: repeating-linear-gradient(
282
+ 135deg,
283
+ var(--color-bg-muted) 0 8px,
284
+ transparent 8px 16px
285
+ );
286
+ cursor: not-allowed;
287
+ }
288
+ .wui-kanban-column[data-wui-drop="refused"] .wui-kanban-body {
289
+ opacity: 0.55;
290
+ }
291
+ .wui-kanban-column[data-wui-drop="refused"] .wui-kanban-drop-note {
292
+ color: var(--color-danger-text);
293
+ border: 1px solid var(--color-danger-soft);
294
+ }
295
+ .wui-kanban-column[data-wui-drop="refused"][data-wui-over],
296
+ .wui-kanban-column[data-wui-drop="refused"][data-wui-target] {
297
+ border-color: var(--color-danger);
298
+ }
299
+ .wui-kanban-column[data-wui-drop="source"][data-wui-target] {
300
+ border: 1px dashed var(--color-border-strong);
301
+ }
302
+
303
+ // ── Collapsed column: a narrow strip that still takes drops ──
304
+ .wui-kanban-column[data-collapsed] {
305
+ flex: 0 0 var(--wui-kanban-collapsed-width);
306
+ align-self: stretch;
307
+
308
+ .wui-kanban-head {
309
+ flex-direction: column;
310
+ flex-wrap: nowrap;
311
+ padding: 10px 6px;
312
+ }
313
+ .wui-kanban-title {
314
+ writing-mode: vertical-rl;
315
+ flex: none;
316
+ white-space: nowrap;
317
+ }
318
+ .wui-kanban-collapse {
319
+ order: -1;
320
+ }
321
+ .wui-kanban-collapse svg {
322
+ transform: rotate(180deg);
323
+ }
324
+ .wui-kanban-summary,
325
+ .wui-kanban-body,
326
+ .wui-kanban-empty {
327
+ display: none;
328
+ }
329
+ .wui-kanban-drop-note {
330
+ margin: 0 4px 8px;
331
+ padding: 6px 4px;
332
+ font-size: 0.75rem;
333
+ writing-mode: vertical-rl;
334
+ }
335
+ }
336
+
337
+ // ── Phone column switcher (hidden from md up) ──
338
+ .wui-kanban-switcher {
339
+ display: none;
340
+ }
341
+
342
+ @media (max-width: 767.98px) {
343
+ .wui-kanban-switcher {
344
+ display: flex;
345
+ gap: 6px;
346
+ margin-bottom: 12px;
347
+ overflow-x: auto;
348
+ scrollbar-width: none;
349
+ }
350
+ .wui-kanban-switch {
351
+ display: inline-flex;
352
+ align-items: center;
353
+ gap: 6px;
354
+ flex: none;
355
+ min-height: 44px;
356
+ padding: 0 14px;
357
+ border: 1px solid var(--color-border);
358
+ border-radius: 999px;
359
+ background: var(--color-surface);
360
+ color: var(--color-text-muted);
361
+ font-size: 0.875rem;
362
+ font-weight: 500;
363
+ white-space: nowrap;
364
+ cursor: pointer;
365
+ }
366
+ // A long column title shortens rather than widening its pill.
367
+ .wui-kanban-switch-title {
368
+ max-width: 14ch;
369
+ overflow: hidden;
370
+ text-overflow: ellipsis;
371
+ white-space: nowrap;
372
+ }
373
+ .wui-kanban-switch[aria-current="true"] {
374
+ border-color: var(--color-primary);
375
+ background: var(--color-primary-soft);
376
+ color: var(--color-text);
377
+ }
378
+
379
+ .wui-kanban-board {
380
+ gap: 12px;
381
+ scroll-snap-type: x mandatory;
382
+ scrollbar-width: none;
383
+ }
384
+ .wui-kanban-board::-webkit-scrollbar,
385
+ .wui-kanban-switcher::-webkit-scrollbar {
386
+ display: none;
387
+ }
388
+ // Collapse is a wide-screen space saver: on a phone every column is full width.
389
+ .wui-kanban-column,
390
+ .wui-kanban-column[data-collapsed] {
391
+ flex: 0 0 100%;
392
+ scroll-snap-align: start;
393
+ align-self: flex-start;
394
+
395
+ .wui-kanban-head {
396
+ flex-direction: row;
397
+ flex-wrap: wrap;
398
+ padding: 12px 12px 8px;
399
+ }
400
+ .wui-kanban-title {
401
+ writing-mode: horizontal-tb;
402
+ flex: 1 1 auto;
403
+ white-space: normal;
404
+ }
405
+ .wui-kanban-collapse {
406
+ display: none;
407
+ }
408
+ .wui-kanban-summary {
409
+ display: block;
410
+ }
411
+ .wui-kanban-body {
412
+ display: flex;
413
+ }
414
+ .wui-kanban-empty:not([hidden]) {
415
+ display: block;
416
+ }
417
+ .wui-kanban-drop-note {
418
+ writing-mode: horizontal-tb;
419
+ }
420
+ }
421
+ .wui-kanban-summary:empty {
422
+ display: none;
423
+ }
424
+ }
425
+
426
+ @media (prefers-reduced-motion: reduce) {
427
+ .wui-kanban-ghost {
428
+ rotate: none;
429
+ }
430
+ .wui-kanban-card[aria-busy="true"]::after {
431
+ animation: none;
432
+ background: var(--color-primary);
433
+ }
434
+ }
@@ -0,0 +1,55 @@
1
+ // Server-loaded modal: loading skeleton, error state and the submit-failure alert. The fullscreen
2
+ // size lives with the modal itself in _overlays.scss, since a page modal uses it too.
3
+ //
4
+ // Part of the wui-components layer; imported after its base partial (see _wui-components.scss).
5
+
6
+ // The loading card: three shimmer lines in the body while the fragment is on its way. The
7
+ // `.skeleton` shimmer is the shared utility; this only shapes the lines.
8
+ .wui-modal-loading {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 12px;
12
+ padding: 4px 0 8px;
13
+ }
14
+ .wui-modal-skeleton {
15
+ height: 14px;
16
+ border-radius: var(--radius-sm);
17
+ }
18
+ .wui-modal-skeleton--short {
19
+ width: 60%;
20
+ }
21
+
22
+ // The error card: a centred message with Close and Try again, in place of the content that did
23
+ // not load. The dialog stays open so the user decides what happens next.
24
+ .wui-modal-state {
25
+ display: flex;
26
+ flex-direction: column;
27
+ align-items: center;
28
+ gap: 8px;
29
+ padding: 16px 8px 8px;
30
+ text-align: center;
31
+ }
32
+ .wui-modal-state-title {
33
+ margin: 0;
34
+ font-size: 16px;
35
+ font-weight: 600;
36
+ color: var(--color-text);
37
+ }
38
+ .wui-modal-state-text {
39
+ margin: 0;
40
+ max-width: 40ch;
41
+ font-size: 14px;
42
+ color: var(--color-text-muted);
43
+ }
44
+ .wui-modal-state-actions {
45
+ display: flex;
46
+ flex-wrap: wrap;
47
+ justify-content: center;
48
+ gap: 10px;
49
+ margin-top: 12px;
50
+ }
51
+
52
+ // A failed submit keeps the form and says so above it.
53
+ .wui-modal-submit-error {
54
+ margin-bottom: 16px;
55
+ }
@@ -296,6 +296,45 @@
296
296
  }
297
297
  }
298
298
 
299
+ // Fullscreen size: the card is the whole viewport. Both surfaces put `wui-modal-fullscreen` on the
300
+ // `.modal` shell (Razor's size="fullscreen", React's size="fullscreen"); neither sets a max-width.
301
+ // Header and footer are flex: none, so they stay pinned while the body scrolls between them, and
302
+ // they pad past a notch or home indicator. `100dvh` follows the phone's collapsing toolbar; `100vh`
303
+ // is the fallback for a browser without dynamic viewport units.
304
+ // 100vw, not the shell's 100%: the document reserves a stable scrollbar gutter, which a fixed box's
305
+ // 100% leaves uncovered, so a fullscreen card would stop short of the right edge.
306
+ .modal.wui-modal-shell.wui-modal-fullscreen {
307
+ width: 100vw;
308
+ padding: 0;
309
+ }
310
+ .modal.wui-modal-shell.wui-modal-fullscreen .modal-dialog.wui-rb-dialog {
311
+ width: 100%;
312
+ height: 100%;
313
+ align-items: stretch;
314
+ }
315
+ .wui-modal-fullscreen .wui-modal {
316
+ width: 100vw;
317
+ max-width: none;
318
+ height: 100vh;
319
+ height: 100dvh;
320
+ max-height: none;
321
+ border-radius: 0;
322
+ }
323
+ .wui-modal-fullscreen .wui-modal-header {
324
+ padding-top: max(16px, env(safe-area-inset-top));
325
+ padding-left: max(20px, env(safe-area-inset-left));
326
+ padding-right: max(20px, env(safe-area-inset-right));
327
+ }
328
+ .wui-modal-fullscreen .wui-modal-body {
329
+ padding-left: max(22px, env(safe-area-inset-left));
330
+ padding-right: max(22px, env(safe-area-inset-right));
331
+ }
332
+ .wui-modal-fullscreen .wui-modal-footer {
333
+ padding-bottom: max(16px, env(safe-area-inset-bottom));
334
+ padding-left: max(22px, env(safe-area-inset-left));
335
+ padding-right: max(22px, env(safe-area-inset-right));
336
+ }
337
+
299
338
  // ── Confirm dialog ──
300
339
  // Confirmation dialog — centred status icon + title + message, two equal
301
340
  // side-by-side CTAs. Runs headerless (Modal hideHeader), so the body