@webority/theme 0.7.9 → 0.8.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.
- package/dist/webority-theme.css +1 -1
- package/package.json +1 -1
- package/scss/_wui-components.scss +4 -0
- package/scss/wui/_card.scss +20 -1
- package/scss/wui/_controls.scss +33 -3
- package/scss/wui/_data.scss +4 -1
- package/scss/wui/_display.scss +173 -15
- package/scss/wui/_dock.scss +102 -0
- package/scss/wui/_flow.scss +33 -20
- package/scss/wui/_menu-motion.scss +153 -0
- package/scss/wui/_overlays.scss +221 -25
- package/scss/wui/_toolbar.scss +138 -0
- package/scss/wui/_workspace.scss +61 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Webority shared design system — Bootstrap 5.3 SCSS overrides, --wui-* tokens, and shared component CSS. The single source of brand chrome for React portals and Razor sites.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
package/scss/wui/_card.scss
CHANGED
|
@@ -7,6 +7,23 @@
|
|
|
7
7
|
// Part of the wui-components layer — see _wui-components.scss for the load
|
|
8
8
|
// order. Order within the layer is LOAD-BEARING; never reorder the imports.
|
|
9
9
|
|
|
10
|
+
// Default (md) card inset — a uniform 20px on the header, body and footer, driven by one
|
|
11
|
+
// friendly token. A consumer class can retune the padding by setting --wui-card-pad (e.g.
|
|
12
|
+
// `.tight-card { --wui-card-pad: 12px }` with `className`/`class` on the card) without
|
|
13
|
+
// overriding a selector. The padding-scale classes (.card-compact, .card-pad-*) still win —
|
|
14
|
+
// they set `padding` directly on the header/body/footer.
|
|
15
|
+
.card {
|
|
16
|
+
--wui-card-pad: 20px;
|
|
17
|
+
--bs-card-spacer-y: var(--wui-card-pad);
|
|
18
|
+
--bs-card-spacer-x: var(--wui-card-pad);
|
|
19
|
+
--bs-card-cap-padding-y: var(--wui-card-pad);
|
|
20
|
+
--bs-card-cap-padding-x: var(--wui-card-pad);
|
|
21
|
+
// Default card is borderless — a flat surface. The border-STYLE stays solid (Bootstrap's
|
|
22
|
+
// `border: var(--bs-card-border-width) solid …`), so a variant or a consumer class can
|
|
23
|
+
// bring the edge back just by giving it a width (set --bs-card-border-width).
|
|
24
|
+
--bs-card-border-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
// A card header that carries a title on the left and controls on the right — the
|
|
11
28
|
// pattern nearly every panel header uses, so it belongs here rather than being
|
|
12
29
|
// re-flexed with utilities on each one.
|
|
@@ -40,8 +57,10 @@
|
|
|
40
57
|
// ── Variants ──────────────────────────────────────────────────────────────────
|
|
41
58
|
// `default` is the flat surface above. The rest are opt-in visual treatments,
|
|
42
59
|
// chosen by the `variant` prop (React) / attribute (Razor).
|
|
43
|
-
// Flat elevation: no in-page drop shadow — stronger border is the edge cue.
|
|
60
|
+
// Flat elevation: no in-page drop shadow — stronger border is the edge cue. The default card
|
|
61
|
+
// is borderless, so re-assert a width here (this variant's whole cue is that border).
|
|
44
62
|
.card-elevated {
|
|
63
|
+
--bs-card-border-width: 1px;
|
|
45
64
|
border-color: var(--color-border-strong);
|
|
46
65
|
}
|
|
47
66
|
.card-outlined {
|
package/scss/wui/_controls.scss
CHANGED
|
@@ -95,14 +95,29 @@
|
|
|
95
95
|
.wui-check:hover:not(:disabled):not(:checked),
|
|
96
96
|
.wui-radio:hover:not(:disabled):not(:checked) {
|
|
97
97
|
border-color: var(--color-primary);
|
|
98
|
-
box-shadow: 0 0 0
|
|
98
|
+
box-shadow: 0 0 0 2px var(--color-primary-soft);
|
|
99
99
|
}
|
|
100
|
+
// Keyboard focus keeps a restrained brand ring — a keyboard user MUST see where focus is
|
|
101
|
+
// (a11y). :focus-visible matches keyboard/AT focus only, never a plain mouse click.
|
|
100
102
|
.form-check-input:focus-visible,
|
|
101
103
|
.wui-check:focus-visible,
|
|
102
104
|
.wui-radio:focus-visible {
|
|
103
105
|
outline: none;
|
|
104
106
|
border-color: var(--color-primary);
|
|
105
|
-
box-shadow: 0 0 0
|
|
107
|
+
box-shadow: 0 0 0 2px var(--color-primary-soft);
|
|
108
|
+
}
|
|
109
|
+
// A plain MOUSE click must leave NO lingering focus/active glow, ring, border tint or
|
|
110
|
+
// darkening — Bootstrap paints all of them on :focus/:active. Neutralise them for pointer
|
|
111
|
+
// focus + active while the keyboard ring above stays, so a mouse click just toggles cleanly.
|
|
112
|
+
.form-check-input:focus:not(:focus-visible),
|
|
113
|
+
.wui-check:focus:not(:focus-visible),
|
|
114
|
+
.wui-radio:focus:not(:focus-visible),
|
|
115
|
+
.form-check-input:active,
|
|
116
|
+
.wui-check:active,
|
|
117
|
+
.wui-radio:active {
|
|
118
|
+
outline: none;
|
|
119
|
+
box-shadow: none;
|
|
120
|
+
filter: none;
|
|
106
121
|
}
|
|
107
122
|
.form-check-input:checked,
|
|
108
123
|
.wui-check:checked,
|
|
@@ -167,7 +182,7 @@
|
|
|
167
182
|
}
|
|
168
183
|
.form-switch .form-check-input:hover:not(:disabled):not(:checked) {
|
|
169
184
|
border-color: transparent;
|
|
170
|
-
box-shadow: 0 0 0
|
|
185
|
+
box-shadow: 0 0 0 2px var(--color-primary-soft);
|
|
171
186
|
}
|
|
172
187
|
.form-switch .form-check-input:checked {
|
|
173
188
|
border-color: transparent;
|
|
@@ -176,6 +191,12 @@
|
|
|
176
191
|
// Re-assert the knob — the checkbox :checked tick has equal specificity.
|
|
177
192
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='7' fill='white'/%3E%3C/svg%3E");
|
|
178
193
|
}
|
|
194
|
+
// On a mouse click the switch's track border reverts to its resting transparent — Bootstrap's
|
|
195
|
+
// :focus otherwise tints it brand-blue, leaving a coloured outline after the click. The shared
|
|
196
|
+
// :focus:not(:focus-visible) rule already dropped the glow; this drops the border tint too.
|
|
197
|
+
.form-switch .form-check-input:focus:not(:focus-visible) {
|
|
198
|
+
border-color: transparent;
|
|
199
|
+
}
|
|
179
200
|
|
|
180
201
|
// Circular variant (shape="circle") — a compact round on/off button instead of a sliding
|
|
181
202
|
// pill. The track is a square-sized circle; the knob dot stays CENTRED in both states (no
|
|
@@ -194,6 +215,15 @@
|
|
|
194
215
|
.form-switch-circle .form-check-input:checked {
|
|
195
216
|
background-position: center;
|
|
196
217
|
}
|
|
218
|
+
|
|
219
|
+
// Rounded-pill variant (shape="round") — the same sliding pill, but with truly circular
|
|
220
|
+
// ends. The switch is a type=checkbox, so it inherits the checkbox's default
|
|
221
|
+
// `corner-shape: squircle`, which superellipses the pill's ends; reset it to `round` for
|
|
222
|
+
// the fully semicircular track in the reference. Only the corner shape changes — size,
|
|
223
|
+
// knob and slide are the base pill's.
|
|
224
|
+
.form-switch-round .form-check-input {
|
|
225
|
+
corner-shape: round;
|
|
226
|
+
}
|
|
197
227
|
@media (prefers-reduced-motion: reduce) {
|
|
198
228
|
.form-check-input,
|
|
199
229
|
.wui-check,
|
package/scss/wui/_data.scss
CHANGED
|
@@ -249,7 +249,10 @@
|
|
|
249
249
|
// floating 42px header band, airy rows on a hairline rule, and an inset from
|
|
250
250
|
// the card edge. Column geometry is NOT set here.
|
|
251
251
|
.table-wrap:has(> .table-ledger) {
|
|
252
|
-
padding
|
|
252
|
+
// One token drives the whole inset so a consumer can retune the table's padding with a
|
|
253
|
+
// class (set --wui-table-pad) without overriding this selector. Bottom padding is now
|
|
254
|
+
// included (was 0) so the last row isn't flush against the card edge.
|
|
255
|
+
padding: var(--wui-table-pad, 20px);
|
|
253
256
|
}
|
|
254
257
|
.table-ledger {
|
|
255
258
|
border-spacing: 0;
|
package/scss/wui/_display.scss
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
display: flex;
|
|
13
13
|
flex-direction: column;
|
|
14
14
|
padding: 18px 20px;
|
|
15
|
-
border: 1px solid var(--color-border);
|
|
16
15
|
border-radius: var(--radius-lg);
|
|
17
16
|
corner-shape: squircle;
|
|
18
17
|
background: var(--color-surface);
|
|
@@ -119,21 +118,18 @@
|
|
|
119
118
|
margin-top: 2px;
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
// Surface variants — plain (above) is the
|
|
123
|
-
//
|
|
124
|
-
// drawing its own outline.
|
|
121
|
+
// Surface variants — plain (above) is borderless on the surface fill; these just swap the
|
|
122
|
+
// fill so a dashboard can mix a KPI strip with the surrounding card.
|
|
125
123
|
.kpi.kpi-filled {
|
|
126
|
-
border-color: transparent;
|
|
127
124
|
background: var(--color-bg-subtle);
|
|
128
125
|
}
|
|
129
126
|
.kpi.kpi-accent {
|
|
130
|
-
border-color: transparent;
|
|
131
127
|
background: var(--color-primary-soft);
|
|
132
128
|
}
|
|
133
129
|
// Needs-action state — orthogonal to variant, so it composes with plain/filled/accent
|
|
134
|
-
// rather than replacing the surface.
|
|
130
|
+
// rather than replacing the surface. Draws its own outline since the base tile no longer does.
|
|
135
131
|
.kpi.kpi-attention {
|
|
136
|
-
border
|
|
132
|
+
border: 1px solid var(--color-warning);
|
|
137
133
|
background: var(--color-warning-soft);
|
|
138
134
|
}
|
|
139
135
|
|
|
@@ -153,18 +149,64 @@
|
|
|
153
149
|
height: 44px;
|
|
154
150
|
}
|
|
155
151
|
|
|
152
|
+
// Spotlight — an editorial feature tile: the label sits top-left with the icon chip pinned
|
|
153
|
+
// top-right, the value is set in the display face (a product's serif brand face when it
|
|
154
|
+
// overrides --wui-font-display), and an optional caption reads under the number. For the
|
|
155
|
+
// single showcase stat on a marketing/portal surface where the number is the hero.
|
|
156
|
+
.kpi.kpi-spotlight {
|
|
157
|
+
display: grid;
|
|
158
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
159
|
+
column-gap: 16px;
|
|
160
|
+
padding: 22px 24px;
|
|
161
|
+
}
|
|
162
|
+
.kpi.kpi-spotlight .kpi-icon {
|
|
163
|
+
grid-column: 2;
|
|
164
|
+
grid-row: 1;
|
|
165
|
+
align-self: start;
|
|
166
|
+
justify-self: end;
|
|
167
|
+
margin-bottom: 0;
|
|
168
|
+
}
|
|
169
|
+
.kpi.kpi-spotlight .kpi-label,
|
|
170
|
+
.kpi.kpi-spotlight .kpi-value,
|
|
171
|
+
.kpi.kpi-spotlight .kpi-caption,
|
|
172
|
+
.kpi.kpi-spotlight .kpi-delta {
|
|
173
|
+
grid-column: 1;
|
|
174
|
+
}
|
|
175
|
+
.kpi.kpi-spotlight .kpi-label {
|
|
176
|
+
align-self: center;
|
|
177
|
+
text-transform: uppercase;
|
|
178
|
+
letter-spacing: 0.08em;
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
}
|
|
181
|
+
.kpi.kpi-spotlight .kpi-value {
|
|
182
|
+
margin-top: 14px;
|
|
183
|
+
// The editorial serif is this variant's signature (see the reference tile), so the value gets
|
|
184
|
+
// a serif stack directly — not --wui-font-display, which defaults to the body sans and so would
|
|
185
|
+
// never read as serif. A product that wants its own face overrides this one selector.
|
|
186
|
+
font-family: Georgia, 'Times New Roman', 'Noto Serif', serif;
|
|
187
|
+
font-size: 44px;
|
|
188
|
+
line-height: 1;
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
}
|
|
191
|
+
// Caption — the sub-line under the value ("Total sent"). Spotlight-only today, but the class
|
|
192
|
+
// is variant-agnostic so a plain tile can carry one too.
|
|
193
|
+
.kpi-caption {
|
|
194
|
+
margin-top: 8px;
|
|
195
|
+
font-size: 14px;
|
|
196
|
+
font-weight: 400;
|
|
197
|
+
color: var(--color-text-muted);
|
|
198
|
+
}
|
|
199
|
+
|
|
156
200
|
// The whole tile as a link (href). Colour/underline reset so the anchor reads as the
|
|
157
201
|
// card, not as text; the hover lift matches .res-card's tint-and-lift treatment.
|
|
158
202
|
a.kpi {
|
|
159
203
|
text-decoration: none;
|
|
160
204
|
color: inherit;
|
|
161
205
|
transition:
|
|
162
|
-
border-color 0.15s ease,
|
|
163
206
|
box-shadow 0.15s ease,
|
|
164
207
|
transform 0.15s ease;
|
|
165
208
|
}
|
|
166
209
|
a.kpi:hover {
|
|
167
|
-
border-color: var(--color-primary);
|
|
168
210
|
box-shadow: var(--shadow-md);
|
|
169
211
|
transform: translateY(-2px);
|
|
170
212
|
}
|
|
@@ -566,6 +608,9 @@ a.info-row:hover .info-value {
|
|
|
566
608
|
// cannot break into segments when an entry wraps to two lines.
|
|
567
609
|
// ============================================================================
|
|
568
610
|
.timeline {
|
|
611
|
+
// One knob for the whole component: the dot diameter. The rail, halo and
|
|
612
|
+
// glyph all derive from it, so `size` (px) scales everything in proportion.
|
|
613
|
+
--wui-timeline-dot: 24px;
|
|
569
614
|
position: relative;
|
|
570
615
|
margin: 0;
|
|
571
616
|
padding: 0 0 0 6px;
|
|
@@ -575,9 +620,9 @@ a.info-row:hover .info-value {
|
|
|
575
620
|
content: "";
|
|
576
621
|
position: absolute;
|
|
577
622
|
// Inset top and bottom so the rail starts and ends inside the first/last dot.
|
|
578
|
-
top:
|
|
579
|
-
bottom:
|
|
580
|
-
left:
|
|
623
|
+
top: calc(var(--wui-timeline-dot) / 2 - 2px);
|
|
624
|
+
bottom: calc(var(--wui-timeline-dot) / 2 - 2px);
|
|
625
|
+
left: calc(6px + var(--wui-timeline-dot) / 2 - 1px);
|
|
581
626
|
width: 2px;
|
|
582
627
|
background: var(--color-border);
|
|
583
628
|
}
|
|
@@ -594,8 +639,8 @@ a.info-row:hover .info-value {
|
|
|
594
639
|
position: relative;
|
|
595
640
|
z-index: 1;
|
|
596
641
|
flex: none;
|
|
597
|
-
width:
|
|
598
|
-
height:
|
|
642
|
+
width: var(--wui-timeline-dot);
|
|
643
|
+
height: var(--wui-timeline-dot);
|
|
599
644
|
display: inline-grid;
|
|
600
645
|
place-items: center;
|
|
601
646
|
border-radius: 999px;
|
|
@@ -659,6 +704,119 @@ a.info-row:hover .info-value {
|
|
|
659
704
|
white-space: pre-wrap;
|
|
660
705
|
overflow-wrap: anywhere;
|
|
661
706
|
}
|
|
707
|
+
// Plain inline subtext under the title — a muted line (distinct from the boxed
|
|
708
|
+
// .timeline-note). Works in both variants; used heavily by the progress variant.
|
|
709
|
+
.timeline-desc {
|
|
710
|
+
margin-top: 2px;
|
|
711
|
+
font-size: 13px;
|
|
712
|
+
line-height: 1.45;
|
|
713
|
+
color: var(--color-text-muted);
|
|
714
|
+
overflow-wrap: anywhere;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ----------------------------------------------------------------------------
|
|
718
|
+
// Progress variant — a status tracker (Initiated → Submitted → … → Disbursed).
|
|
719
|
+
// Solid status dots (done/current/rejected) or a hollow ring (upcoming); the
|
|
720
|
+
// rail is drawn PER SEGMENT so a completed step's outgoing line reads filled.
|
|
721
|
+
// ----------------------------------------------------------------------------
|
|
722
|
+
.timeline--progress {
|
|
723
|
+
// Halo widths scale with the dot: the coloured ring and the thin blank ring.
|
|
724
|
+
--wui-timeline-halo: calc(var(--wui-timeline-dot) / 4);
|
|
725
|
+
--wui-timeline-halo-thin: calc(var(--wui-timeline-dot) / 12);
|
|
726
|
+
// Room on the left so a status dot's soft halo ring isn't clipped.
|
|
727
|
+
padding-left: calc(var(--wui-timeline-halo) + 1px);
|
|
728
|
+
}
|
|
729
|
+
// No single list rail — each item owns the segment down to the next dot so it
|
|
730
|
+
// can be tinted independently.
|
|
731
|
+
.timeline--progress::before {
|
|
732
|
+
display: none;
|
|
733
|
+
}
|
|
734
|
+
.timeline--progress .timeline-item {
|
|
735
|
+
padding-bottom: 22px;
|
|
736
|
+
}
|
|
737
|
+
// The connector from this dot down into the next one — anchored to the dot's
|
|
738
|
+
// bottom edge and centre, so it stays aligned at any dot size.
|
|
739
|
+
.timeline--progress .timeline-item:not(:last-child)::before {
|
|
740
|
+
content: "";
|
|
741
|
+
position: absolute;
|
|
742
|
+
top: var(--wui-timeline-dot);
|
|
743
|
+
bottom: -2px;
|
|
744
|
+
left: calc(var(--wui-timeline-dot) / 2 - 1px);
|
|
745
|
+
width: 2px;
|
|
746
|
+
background: var(--color-border);
|
|
747
|
+
}
|
|
748
|
+
// The rail fills green only BETWEEN two completed steps, so a lone/last done
|
|
749
|
+
// step doesn't leave a green stub dangling into an incomplete step below it.
|
|
750
|
+
.timeline--progress .timeline-item.is-done:has(+ .timeline-item.is-done)::before {
|
|
751
|
+
background: var(--color-success);
|
|
752
|
+
}
|
|
753
|
+
// Base / upcoming dot: a hollow ring for a not-yet-reached step. Its 2px grey
|
|
754
|
+
// halo is OPAQUE (mixed toward the surface, not transparent) so it masks the
|
|
755
|
+
// rail behind the dot — the connector only shows in the clear gap, never
|
|
756
|
+
// bleeding through the ring's halo.
|
|
757
|
+
.timeline--progress .timeline-dot,
|
|
758
|
+
.timeline--progress .timeline-item.is-upcoming .timeline-dot {
|
|
759
|
+
color: var(--color-text-subtle);
|
|
760
|
+
background: var(--color-surface);
|
|
761
|
+
box-shadow:
|
|
762
|
+
inset 0 0 0 2px var(--color-border),
|
|
763
|
+
0 0 0 var(--wui-timeline-halo-thin) color-mix(in srgb, var(--color-text-subtle) 12%, var(--color-surface));
|
|
764
|
+
}
|
|
765
|
+
// Solid status discs with a white glyph and a soft concentric halo ring in the
|
|
766
|
+
// dot's own hue. The fill is softened a touch (mixed toward the surface) so the
|
|
767
|
+
// states read gentler than the raw brand tones.
|
|
768
|
+
.timeline--progress .timeline-item.is-done .timeline-dot {
|
|
769
|
+
color: var(--color-on-scrim);
|
|
770
|
+
background: color-mix(in srgb, var(--color-success) 82%, var(--color-surface));
|
|
771
|
+
box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-success-soft);
|
|
772
|
+
}
|
|
773
|
+
.timeline--progress .timeline-item.is-current .timeline-dot {
|
|
774
|
+
color: var(--color-on-scrim);
|
|
775
|
+
background: color-mix(in srgb, var(--color-warning) 82%, var(--color-surface));
|
|
776
|
+
box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-warning-soft);
|
|
777
|
+
}
|
|
778
|
+
.timeline--progress .timeline-item.is-rejected .timeline-dot {
|
|
779
|
+
color: var(--color-on-scrim);
|
|
780
|
+
background: color-mix(in srgb, var(--color-danger) 82%, var(--color-surface));
|
|
781
|
+
box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-danger-soft);
|
|
782
|
+
}
|
|
783
|
+
// Blocked / waiting on an external action — informational blue.
|
|
784
|
+
.timeline--progress .timeline-item.is-on-hold .timeline-dot {
|
|
785
|
+
color: var(--color-on-scrim);
|
|
786
|
+
background: color-mix(in srgb, var(--color-info) 82%, var(--color-surface));
|
|
787
|
+
box-shadow: 0 0 0 var(--wui-timeline-halo) var(--color-info-soft);
|
|
788
|
+
}
|
|
789
|
+
// Cancelled / skipped — resolved-but-inactive outcomes read as muted slate,
|
|
790
|
+
// with a soft neutral halo, so they sit visually below the live states.
|
|
791
|
+
.timeline--progress .timeline-item.is-cancelled .timeline-dot,
|
|
792
|
+
.timeline--progress .timeline-item.is-skipped .timeline-dot {
|
|
793
|
+
color: var(--color-on-scrim);
|
|
794
|
+
background: color-mix(in srgb, var(--color-text-subtle) 82%, var(--color-surface));
|
|
795
|
+
box-shadow: 0 0 0 var(--wui-timeline-halo) color-mix(in srgb, var(--color-text-subtle) 16%, transparent);
|
|
796
|
+
}
|
|
797
|
+
// A cancelled step's title is struck through — the plan changed.
|
|
798
|
+
.timeline--progress .timeline-item.is-cancelled .timeline-title {
|
|
799
|
+
text-decoration: line-through;
|
|
800
|
+
color: var(--color-text-muted);
|
|
801
|
+
}
|
|
802
|
+
// Title row: title on the left, the timestamp pinned to the right.
|
|
803
|
+
.timeline--progress .timeline-head {
|
|
804
|
+
display: flex;
|
|
805
|
+
align-items: baseline;
|
|
806
|
+
justify-content: space-between;
|
|
807
|
+
gap: 12px;
|
|
808
|
+
}
|
|
809
|
+
.timeline--progress .timeline-head .timeline-at {
|
|
810
|
+
flex: none;
|
|
811
|
+
font-size: 12.5px;
|
|
812
|
+
color: var(--color-text-subtle);
|
|
813
|
+
white-space: nowrap;
|
|
814
|
+
}
|
|
815
|
+
// An upcoming step greys down — it hasn't happened yet.
|
|
816
|
+
.timeline--progress .timeline-item.is-upcoming .timeline-title {
|
|
817
|
+
color: var(--color-text-muted);
|
|
818
|
+
font-weight: 500;
|
|
819
|
+
}
|
|
662
820
|
|
|
663
821
|
// ============================================================================
|
|
664
822
|
// Feature-comparison table — us vs them. The highlighted column is tinted so
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// ── Floating dock ──
|
|
2
|
+
// AppFloatingDock / <app-floating-dock> / <wui-dock> — a macOS-style pill of icon
|
|
3
|
+
// links. The element writes --wui-dock-mag (1 → max-scale) per item on pointermove;
|
|
4
|
+
// everything visual lives here. With no JS (or coarse pointer / reduced motion /
|
|
5
|
+
// `static`) the var stays 1 and the dock is a plain icon row.
|
|
6
|
+
//
|
|
7
|
+
// Part of the wui-components layer — see _wui-components.scss for load order.
|
|
8
|
+
|
|
9
|
+
wui-dock {
|
|
10
|
+
--dock-size: 44px;
|
|
11
|
+
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: flex-end;
|
|
14
|
+
gap: 10px;
|
|
15
|
+
padding: 10px 14px;
|
|
16
|
+
background: var(--color-surface);
|
|
17
|
+
border: 1px solid var(--color-border);
|
|
18
|
+
border-radius: var(--wui-radius-xl);
|
|
19
|
+
corner-shape: squircle;
|
|
20
|
+
box-shadow: var(--wui-shadow-overlay);
|
|
21
|
+
// Item boxes grow on hover; anchoring the row height to the rest size keeps the
|
|
22
|
+
// pill from pushing surrounding layout while items magnify upward out of it.
|
|
23
|
+
height: calc(var(--dock-size) + 20px);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dock-item {
|
|
27
|
+
position: relative;
|
|
28
|
+
display: grid;
|
|
29
|
+
place-items: center;
|
|
30
|
+
width: calc(var(--dock-size) * var(--wui-dock-mag, 1));
|
|
31
|
+
height: calc(var(--dock-size) * var(--wui-dock-mag, 1));
|
|
32
|
+
border: none;
|
|
33
|
+
border-radius: 50%;
|
|
34
|
+
background: var(--color-bg-muted);
|
|
35
|
+
color: var(--color-text-muted);
|
|
36
|
+
text-decoration: none;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
// Smooths the snap-back when the pointer leaves; during tracking the element
|
|
39
|
+
// updates per frame, so the short transition just softens the steps.
|
|
40
|
+
transition:
|
|
41
|
+
width 120ms var(--wui-ease-ui),
|
|
42
|
+
height 120ms var(--wui-ease-ui),
|
|
43
|
+
background-color 160ms var(--wui-ease-ui),
|
|
44
|
+
color 160ms var(--wui-ease-ui);
|
|
45
|
+
}
|
|
46
|
+
.dock-item:hover,
|
|
47
|
+
.dock-item:focus-visible {
|
|
48
|
+
background: var(--color-primary-soft);
|
|
49
|
+
color: var(--color-primary);
|
|
50
|
+
}
|
|
51
|
+
.dock-item:focus-visible {
|
|
52
|
+
outline: 2px solid var(--color-primary);
|
|
53
|
+
outline-offset: 2px;
|
|
54
|
+
// Keyboard users get no pointer tracking — grow the focused item itself.
|
|
55
|
+
--wui-dock-mag: 1.25;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dock-item-icon {
|
|
59
|
+
display: grid;
|
|
60
|
+
place-items: center;
|
|
61
|
+
font-size: calc(18px * var(--wui-dock-mag, 1));
|
|
62
|
+
line-height: 1;
|
|
63
|
+
}
|
|
64
|
+
// Node icons (SVGs) don't inherit font-size — scale the box instead.
|
|
65
|
+
.dock-item-icon > svg {
|
|
66
|
+
transform: scale(var(--wui-dock-mag, 1));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dock-item-tip {
|
|
70
|
+
position: absolute;
|
|
71
|
+
bottom: calc(100% + 10px);
|
|
72
|
+
left: 50%;
|
|
73
|
+
translate: -50% 4px;
|
|
74
|
+
padding: 4px 10px;
|
|
75
|
+
background: var(--color-surface);
|
|
76
|
+
border: 1px solid var(--color-border);
|
|
77
|
+
border-radius: var(--wui-radius-sm);
|
|
78
|
+
box-shadow: var(--wui-shadow-thumb);
|
|
79
|
+
color: var(--color-text);
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
line-height: 1.35;
|
|
83
|
+
white-space: nowrap;
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
opacity: 0;
|
|
86
|
+
transition:
|
|
87
|
+
opacity 140ms var(--wui-ease-ui),
|
|
88
|
+
translate 140ms var(--wui-ease-ui);
|
|
89
|
+
z-index: var(--wui-z-tooltip);
|
|
90
|
+
}
|
|
91
|
+
.dock-item:hover .dock-item-tip,
|
|
92
|
+
.dock-item:focus-visible .dock-item-tip {
|
|
93
|
+
opacity: 1;
|
|
94
|
+
translate: -50% 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@media (prefers-reduced-motion: reduce) {
|
|
98
|
+
.dock-item,
|
|
99
|
+
.dock-item-tip {
|
|
100
|
+
transition: none;
|
|
101
|
+
}
|
|
102
|
+
}
|
package/scss/wui/_flow.scss
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
// --wui-step-connector the incomplete-rail colour
|
|
23
23
|
// --wui-step-size compact/vertical badge diameter (default 28px)
|
|
24
24
|
// --wui-step-badge-size icons-variant badge diameter (default 56px)
|
|
25
|
-
//
|
|
25
|
+
// Every variant defaults to the brand primary — active, done and rail share one accent
|
|
26
|
+
// (no multi-colour). Colour variety is opt-in: a `.wui-step-tone-*` class or a var override.
|
|
26
27
|
// ============================================================================
|
|
27
28
|
.stepper {
|
|
28
29
|
// Full width so the flex:1 steps spread evenly across the container. Without it,
|
|
@@ -34,6 +35,18 @@
|
|
|
34
35
|
gap: 0;
|
|
35
36
|
padding: 0 8px;
|
|
36
37
|
}
|
|
38
|
+
// A STANDALONE AppStepper gets the brand (primary) accent as a DECLARED default, so a tone
|
|
39
|
+
// class placed on the stepper element itself overrides it. The wizard is excluded because
|
|
40
|
+
// its tone class rides on the wizard HOST (an ancestor of the generated header): declaring
|
|
41
|
+
// the vars on the header would block that inherited override, so the wizard leans on the
|
|
42
|
+
// var fallbacks instead — which now also default to primary. :where() keeps this at ZERO
|
|
43
|
+
// specificity, so a .wui-step-tone-* class or an inline --wui-step-* override still wins.
|
|
44
|
+
:where(.stepper:not(.wizard-stepper)) {
|
|
45
|
+
--wui-step-accent: var(--color-primary);
|
|
46
|
+
--wui-step-accent-fg: var(--color-on-scrim);
|
|
47
|
+
--wui-step-soft: var(--color-primary-soft);
|
|
48
|
+
--wui-step-soft-fg: var(--color-primary);
|
|
49
|
+
}
|
|
37
50
|
.step {
|
|
38
51
|
position: relative;
|
|
39
52
|
flex: 1;
|
|
@@ -188,8 +201,8 @@ button.step:focus-visible .step-circle {
|
|
|
188
201
|
transform 0.2s ease;
|
|
189
202
|
}
|
|
190
203
|
.stepper-journey .step.done .step-circle {
|
|
191
|
-
background: var(--wui-step-soft, var(--color-
|
|
192
|
-
color: var(--wui-step-soft-fg, var(--color-
|
|
204
|
+
background: var(--wui-step-soft, var(--color-primary-soft));
|
|
205
|
+
color: var(--wui-step-soft-fg, var(--color-primary));
|
|
193
206
|
}
|
|
194
207
|
.stepper-journey .step.active .step-circle {
|
|
195
208
|
background: var(--wui-step-accent, var(--color-primary));
|
|
@@ -214,7 +227,7 @@ button.step:focus-visible .step-circle {
|
|
|
214
227
|
color: var(--color-text-subtle);
|
|
215
228
|
}
|
|
216
229
|
.stepper-journey .step.done .step-index {
|
|
217
|
-
color: var(--wui-step-soft-fg, var(--color-
|
|
230
|
+
color: var(--wui-step-soft-fg, var(--color-primary));
|
|
218
231
|
}
|
|
219
232
|
.stepper-journey .step.active .step-index {
|
|
220
233
|
color: var(--wui-step-accent, var(--color-primary));
|
|
@@ -255,7 +268,7 @@ button.step:focus-visible .step-circle {
|
|
|
255
268
|
background: var(--color-border-strong);
|
|
256
269
|
}
|
|
257
270
|
.stepper-journey .step-line.done {
|
|
258
|
-
background: var(--wui-step-accent, var(--color-
|
|
271
|
+
background: var(--wui-step-accent, var(--color-primary));
|
|
259
272
|
}
|
|
260
273
|
// Below xl the four label+title pairs no longer fit side by side, so the bar
|
|
261
274
|
// becomes a vertical list and the connectors go away.
|
|
@@ -348,9 +361,9 @@ button.step:focus-visible .step-circle {
|
|
|
348
361
|
}
|
|
349
362
|
|
|
350
363
|
// .stepper-icons — large icon badges joined by a rail, with a "STEP n" eyebrow above
|
|
351
|
-
// each title. Done
|
|
352
|
-
// badge; pending is a muted badge
|
|
353
|
-
// (a step with no icon falls back to its number).
|
|
364
|
+
// each title. Done gets a soft-accent tint + filled connector; active is a solid-accent
|
|
365
|
+
// badge; pending is a muted badge — all one accent (brand primary by default). Built for
|
|
366
|
+
// icon-per-step flows — each step sets `icon` (a step with no icon falls back to its number).
|
|
354
367
|
// ----------------------------------------------------------------------------
|
|
355
368
|
.stepper-icons {
|
|
356
369
|
align-items: flex-start;
|
|
@@ -371,11 +384,11 @@ button.step:focus-visible .step-circle {
|
|
|
371
384
|
color 0.2s ease,
|
|
372
385
|
transform 0.2s ease;
|
|
373
386
|
}
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
387
|
+
// Like every variant, the icons stepper defaults to the brand primary — a solid-primary
|
|
388
|
+
// active badge, a soft-primary done badge, a primary rail. Every colour routes through the
|
|
389
|
+
// shared --wui-step-* vars, so a `.wui-step-tone-*` class or an override recolours it.
|
|
377
390
|
.stepper-icons .step.active .step-circle {
|
|
378
|
-
background: var(--wui-step-accent, var(--color-
|
|
391
|
+
background: var(--wui-step-accent, var(--color-primary));
|
|
379
392
|
color: var(--wui-step-accent-fg, var(--color-on-scrim));
|
|
380
393
|
// The compact variant rings the active circle; here the solid fill carries it, so
|
|
381
394
|
// drop the ring to match the flat badge look.
|
|
@@ -383,8 +396,8 @@ button.step:focus-visible .step-circle {
|
|
|
383
396
|
}
|
|
384
397
|
.stepper-icons .step.done .step-circle {
|
|
385
398
|
border: none;
|
|
386
|
-
background: var(--wui-step-soft, var(--color-
|
|
387
|
-
color: var(--wui-step-soft-fg, var(--color-
|
|
399
|
+
background: var(--wui-step-soft, var(--color-primary-soft));
|
|
400
|
+
color: var(--wui-step-soft-fg, var(--color-primary));
|
|
388
401
|
}
|
|
389
402
|
.stepper-icons .step.error .step-circle {
|
|
390
403
|
background: var(--color-danger-soft);
|
|
@@ -406,10 +419,10 @@ button.step:focus-visible .step-circle {
|
|
|
406
419
|
color: var(--color-text-subtle);
|
|
407
420
|
}
|
|
408
421
|
.stepper-icons .step.active .step-index {
|
|
409
|
-
color: var(--wui-step-accent, var(--color-
|
|
422
|
+
color: var(--wui-step-accent, var(--color-primary));
|
|
410
423
|
}
|
|
411
424
|
.stepper-icons .step.done .step-index {
|
|
412
|
-
color: var(--wui-step-soft-fg, var(--color-
|
|
425
|
+
color: var(--wui-step-soft-fg, var(--color-primary));
|
|
413
426
|
}
|
|
414
427
|
.stepper-icons .step.error .step-index {
|
|
415
428
|
color: var(--color-danger-text);
|
|
@@ -435,7 +448,7 @@ button.step:focus-visible .step-circle {
|
|
|
435
448
|
background: var(--wui-step-connector, var(--color-border));
|
|
436
449
|
}
|
|
437
450
|
.stepper-icons .step.done + .step::before {
|
|
438
|
-
background: var(--wui-step-accent, var(--color-
|
|
451
|
+
background: var(--wui-step-accent, var(--color-primary));
|
|
439
452
|
}
|
|
440
453
|
// The compact hover ring is calibrated to the 28px badge; the 56px icon badge just
|
|
441
454
|
// lifts on hover instead.
|
|
@@ -793,6 +806,7 @@ button.step:focus-visible .step-circle {
|
|
|
793
806
|
// ============================================================================
|
|
794
807
|
wui-wizard {
|
|
795
808
|
display: block;
|
|
809
|
+
width: 100%;
|
|
796
810
|
}
|
|
797
811
|
// Custom elements don't inherit the UA `[hidden] { display: none }` once we give the
|
|
798
812
|
// tag its own `display`, so the inactive panels need it stated explicitly.
|
|
@@ -819,13 +833,12 @@ wui-wizard-step[hidden] {
|
|
|
819
833
|
}
|
|
820
834
|
.wizard-footer {
|
|
821
835
|
// The sticky Back/Skip/Next/Finish bar — pinned to the bottom of a scrolling flow
|
|
822
|
-
// so the actions (and the Skip on an optional step) are always in reach.
|
|
836
|
+
// so the actions (and the Skip on an optional step) are always in reach. Runs flush on
|
|
837
|
+
// the flow (no divider or fill) — the buttons alone read as the footer.
|
|
823
838
|
position: sticky;
|
|
824
839
|
bottom: 0;
|
|
825
840
|
margin-top: 24px;
|
|
826
841
|
padding: 16px 0;
|
|
827
|
-
border-top: 1px solid var(--color-border);
|
|
828
|
-
background: var(--color-surface);
|
|
829
842
|
z-index: 1;
|
|
830
843
|
}
|
|
831
844
|
.wizard-footer-actions {
|