@undrr/undrr-mangrove 2.0.0-rc.2 → 2.0.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.
- package/README.md +11 -6
- package/components/BookCard.js +1 -1
- package/components/Breadcrumbs.js +2 -2
- package/components/Checkbox.js +1 -1
- package/components/Chips.js +1 -1
- package/components/CopyButton.js +2 -2
- package/components/CtaButton.js +1 -1
- package/components/Drawer.js +1 -1
- package/components/EmbedContainer.js +1 -1
- package/components/Footer.js +1 -1
- package/components/FormAction.js +1 -1
- package/components/FormErrorSummary.js +1 -1
- package/components/FormGroup.js +1 -1
- package/components/FullWidth.js +1 -1
- package/components/Gallery.js +2 -2
- package/components/Hero.js +2 -2
- package/components/HighlightBox.js +1 -1
- package/components/HorizontalBookCard.js +1 -1
- package/components/HorizontalCard.js +1 -1
- package/components/IconCard.js +1 -1
- package/components/Legend.js +1 -1
- package/components/Loader.js +1 -1
- package/components/MegaMenu.js +2 -2
- package/components/Notice.js +2 -2
- package/components/PageHeader.js +2 -2
- package/components/Pager.js +2 -2
- package/components/QuoteHighlight.js +2 -2
- package/components/QuoteHighlight.js.LICENSE.txt +2 -0
- package/components/Radio.js +1 -1
- package/components/Range.js +1 -1
- package/components/ScrollContainer.js +2 -2
- package/components/SegmentedControl.js +9 -0
- package/components/SegmentedControl.js.LICENSE.txt +9 -0
- package/components/Select.js +1 -1
- package/components/ServiceNotice.js +2 -2
- package/components/ShareButtons.js +2 -2
- package/components/ShowMore.js +2 -2
- package/components/SkipLink.js +1 -1
- package/components/StatsCard.js +1 -1
- package/components/SyndicationSearchWidget.js +2 -2
- package/components/Tab.js +2 -2
- package/components/TextCta.js +1 -1
- package/components/TextInput.js +1 -1
- package/components/Textarea.js +1 -1
- package/components/Tree.js +1 -1
- package/components/UserFeedback.js +1 -1
- package/components/VerticalCard.js +1 -1
- package/components/hydrate.js +2 -2
- package/css/style-all.css +60 -40
- package/css/style-all.css.map +1 -1
- package/css/style-delta.css +59 -39
- package/css/style-delta.css.map +1 -1
- package/css/style-gutenberg.css +16 -7
- package/css/style-gutenberg.css.map +1 -1
- package/css/style-irp.css +60 -40
- package/css/style-irp.css.map +1 -1
- package/css/style-mcr.css +59 -39
- package/css/style-mcr.css.map +1 -1
- package/css/style-preventionweb.css +60 -40
- package/css/style-preventionweb.css.map +1 -1
- package/css/style.css +59 -39
- package/css/style.css.map +1 -1
- package/error-pages/401.html +3 -3
- package/error-pages/403.html +3 -3
- package/error-pages/404.html +4 -4
- package/error-pages/429.html +3 -3
- package/error-pages/500.html +6 -6
- package/error-pages/502.html +3 -3
- package/error-pages/503.html +3 -3
- package/error-pages/504.html +3 -3
- package/error-pages/5xx.html +4 -4
- package/error-pages/challenge.html +3 -3
- package/error-pages/managed-challenge.html +3 -3
- package/js/README.md +31 -25
- package/js/copy-button.js +17 -0
- package/js/drawer.js +752 -0
- package/js/hub-header.js +217 -0
- package/js/show-more.js +144 -10
- package/js/switch-pending.js +195 -46
- package/package.json +8 -3
- package/scss/Atom/BaseTypography/Blockquote/blockquote.scss +2 -1
- package/scss/Atom/BaseTypography/Cite/cite.scss +2 -1
- package/scss/Atom/BaseTypography/Code/code.scss +8 -2
- package/scss/Atom/Icons/icons.scss +1 -1
- package/scss/Atom/Images/AuthorImage/author-image.scss +16 -1
- package/scss/Atom/StatusLabel/status-label.scss +331 -8
- package/scss/Atom/Table/table.scss +8 -3
- package/scss/Atom/Tag/tag.scss +20 -2
- package/scss/Components/Breadcrumbs/breadcrumbs.scss +8 -4
- package/scss/Components/Buttons/CtaButton/cta-button.scss +15 -0
- package/scss/Components/Cards/Card/card.scss +37 -10
- package/scss/Components/Cards/StatsCard/stats-card.scss +2 -10
- package/scss/Components/DataViz/Legend/legend.scss +2 -1
- package/scss/Components/ErrorPages/error-pages.scss +8 -0
- package/scss/Components/Forms/SegmentedControl/segmented-control.scss +412 -0
- package/scss/Components/Forms/_form-base.scss +109 -18
- package/scss/Components/Hero/hero.scss +296 -57
- package/scss/Components/MegaMenu/mega-menu.scss +26 -6
- package/scss/Components/OnThisPageNav/on-this-page-nav.scss +3 -1
- package/scss/Components/PageHeader/page-header.scss +13 -28
- package/scss/Components/Pager/pager.scss +3 -1
- package/scss/Components/QuoteHighlight/quote-highlight.scss +6 -1
- package/scss/Components/ScrollContainer/scroll-container.scss +11 -0
- package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +51 -24
- package/scss/Components/Tab/tab.scss +1 -1
- package/scss/Components/TextCta/text-cta.scss +11 -3
- package/scss/Patterns/HubHeader/hub-header.scss +14 -3
- package/scss/Utilities/FullWidth/full-width.scss +14 -1
- package/scss/assets/scss/_components.scss +1 -0
- package/scss/assets/scss/_fonts.scss +11 -0
- package/scss/assets/scss/_foundational.scss +7 -0
- package/scss/assets/scss/_mixins.scss +32 -0
- package/scss/assets/scss/_tokens-data-viz.scss +9 -0
- package/scss/assets/scss/_variables.scss +16 -3
- package/scss/assets/scss/generated/_tokens-irp.scss +20 -4
- package/scss/assets/scss/generated/_tokens-mangrove.scss +6 -0
- package/scss/assets/scss/generated/_tokens-preventionweb.scss +20 -4
- package/js/__tests__/copy-button.test.js +0 -112
- package/js/__tests__/show-more.test.js +0 -80
- package/js/__tests__/table-of-contents.test.js +0 -196
- package/scss/Components/SyndicationSearchWidget/SyndicationSearchWidget.scss +0 -11
- package/scss/Components/TableOfContents/TableOfContents.scss +0 -11
- package/scss/Utilities/FullWidth/FullWidth.scss +0 -11
- package/scss/Utilities/ShowMore/ShowMore.scss +0 -11
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Segmented control
|
|
3
|
+
*
|
|
4
|
+
* Compact segmented presentation of a radio group: one row of adjoining
|
|
5
|
+
* segments for a small, mutually exclusive choice that changes what a single
|
|
6
|
+
* view shows.
|
|
7
|
+
*
|
|
8
|
+
* Full documentation:
|
|
9
|
+
* https://github.com/unisdr/undrr-mangrove/blob/main/stories/Components/Forms/SegmentedControl/SegmentedControl.mdx
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// The control is a presentation layer over a native radio group: a fieldset, a
|
|
13
|
+
// legend and one <input type="radio"> per segment, each followed by its own
|
|
14
|
+
// <label>. Nothing here changes the semantics, so arrow-key roving, the
|
|
15
|
+
// "N of M" announcement, form submission and RTL traversal are the browser's
|
|
16
|
+
// and stay correct. That is the whole reason this is not a tablist
|
|
17
|
+
// (unisdr/undrr-mangrove#1188): a tablist promises tab panels, and a view
|
|
18
|
+
// switcher has none.
|
|
19
|
+
|
|
20
|
+
// WHY THIS IS ITS OWN BLOCK AND NOT A .mg-form-check VARIANT
|
|
21
|
+
|
|
22
|
+
// #1188 asked for a presentation variant of Radio. The semantics are indeed
|
|
23
|
+
// identical, and they are reused — this is the same fieldset/legend/radio/label
|
|
24
|
+
// markup a FormGroup + Radio pairing produces. What is not reused is
|
|
25
|
+
// .mg-form-check, and the reason is that it is a round-dot presentation whose
|
|
26
|
+
// every dimension is the opposite of a segment's. A variant class would have to
|
|
27
|
+
// unset, across five rules in _form-base.scss:
|
|
28
|
+
|
|
29
|
+
// .mg-form-check display:flex, min-height:40px, align-items
|
|
30
|
+
// .mg-form-check__input appearance, border(2px), width/height 24px,
|
|
31
|
+
// margin, cursor, :hover border-color,
|
|
32
|
+
// :focus-visible (mg-focus-ring: outline +
|
|
33
|
+
// box-shadow)
|
|
34
|
+
// ...__input--radio background-color, border-radius:100%,
|
|
35
|
+
// box-shadow (the inset dot)
|
|
36
|
+
// ...__input--radio:checked background-color, box-shadow (the dot again)
|
|
37
|
+
// ...__input--radio:checked:focus-visible box-shadow (dot + band)
|
|
38
|
+
// .mg-form-check__label font-size, padding
|
|
39
|
+
|
|
40
|
+
// That is ~21 declarations to undo, and two of them sit outside the BEM nesting
|
|
41
|
+
// at 0,2,0 and 0,3,0 specifically so they win, so the variant would need
|
|
42
|
+
// selectors at least that heavy just to cancel them. Worse, a segment has to
|
|
43
|
+
// move the focus ring from the input to the label: that is not a declaration a
|
|
44
|
+
// variant can add, it is a mixin's output suppressed on one element and
|
|
45
|
+
// re-emitted on another. Twenty-one unsets and a relocated focus ring is a
|
|
46
|
+
// rewrite wearing a modifier's name, so this is its own block.
|
|
47
|
+
|
|
48
|
+
// WHAT THAT COSTS, AND WHAT IS OWED BACK
|
|
49
|
+
|
|
50
|
+
// The deviation is real, so the couplings are written down rather than left to
|
|
51
|
+
// be discovered:
|
|
52
|
+
|
|
53
|
+
// * ERROR / INVALID STATE. Radio has one (--error, aria-invalid, errorText,
|
|
54
|
+
// .mg-form-error) and so does FormGroup; this control has none, and it
|
|
55
|
+
// cannot borrow FormGroup's by nesting, because a fieldset inside a fieldset
|
|
56
|
+
// announces two group names. A consumer needing "you must choose one" has to
|
|
57
|
+
// hand-roll it — which is exactly what unisdr/undrr-mangrove#1187 reported
|
|
58
|
+
// against .mg-switch, and the trap is worse here because __label sets
|
|
59
|
+
// forced-color-adjust: none below, so a hand-rolled red is painted literally
|
|
60
|
+
// in forced colours. If Radio's validation wiring is ever revisited, this
|
|
61
|
+
// control needs the same pass.
|
|
62
|
+
// * DISABLED COLOUR. neutral-400 / neutral-500 are named directly, matching
|
|
63
|
+
// what .mg-form-check__input--disabled and .mg-form-input--disabled do
|
|
64
|
+
// today. A future --mg-color-form-disabled token would have to be routed
|
|
65
|
+
// here by hand.
|
|
66
|
+
// * HELP TEXT / aria-describedby. Not supported. Use a Radio group in a
|
|
67
|
+
// FormGroup when an option needs one.
|
|
68
|
+
|
|
69
|
+
// Two theming hooks, no more. Everything else is a shared token, so a
|
|
70
|
+
// segmented control follows the button, focus-ring and spacing scales of
|
|
71
|
+
// whichever theme wraps it. Each is read with its default where it is used,
|
|
72
|
+
// not declared on the block, so a value set on the control or on any ancestor
|
|
73
|
+
// (a toolbar, a .mg-theme-* container) reaches it.
|
|
74
|
+
$mg-segmented-min-block-size: var(
|
|
75
|
+
--mg-segmented-control-min-block-size,
|
|
76
|
+
#{mg-rem(44)}
|
|
77
|
+
);
|
|
78
|
+
$mg-segmented-radius: var(
|
|
79
|
+
--mg-segmented-control-radius,
|
|
80
|
+
var(--mg-radius-button)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
.mg-segmented-control {
|
|
84
|
+
border: 0;
|
|
85
|
+
margin: 0 0 var(--mg-spacing-100);
|
|
86
|
+
min-inline-size: 0;
|
|
87
|
+
padding: 0;
|
|
88
|
+
|
|
89
|
+
&__legend {
|
|
90
|
+
@extend %paragraph-font-300;
|
|
91
|
+
|
|
92
|
+
display: block;
|
|
93
|
+
margin-bottom: var(--mg-spacing-25);
|
|
94
|
+
padding: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// inline-flex, so the control is as wide as its labels and does not stretch
|
|
98
|
+
// across the page the way its block-level fieldset would. It is also the
|
|
99
|
+
// positioning ancestor for the visually hidden inputs.
|
|
100
|
+
|
|
101
|
+
// It wraps. A row that cannot fit has two ways to go: shrink the segments
|
|
102
|
+
// until the labels break mid-word, or take a second line. Measured at 320
|
|
103
|
+
// CSS px (WCAG 1.4.10 reflow, which is also 1280 at 400%), shrinking gave
|
|
104
|
+
// "Dep / th" and "Frequen / cy"; wrapping keeps every label whole. Only the
|
|
105
|
+
// first and last segment of the whole control are rounded, so a wrapped row
|
|
106
|
+
// ends square, which reads as "continues below" rather than as a second
|
|
107
|
+
// control. Per-row rounding is not expressible here: CSS cannot address the
|
|
108
|
+
// first or last item of a flex line, so it would take JS, which this
|
|
109
|
+
// component does not have.
|
|
110
|
+
|
|
111
|
+
// Two limits of the wrap, both measured, both deliberate:
|
|
112
|
+
|
|
113
|
+
// * THIS APPLIES TO THE DEFAULT LAYOUT ONLY. --full-width below sets
|
|
114
|
+
// `flex: 1 1 0`, which gives every segment a hypothetical main size of 0,
|
|
115
|
+
// so the line never overflows and the row never wraps — it shrinks
|
|
116
|
+
// instead, and past a certain label length the words do break. Measured
|
|
117
|
+
// at 320 CSS px: "Precipitatio / n", "Vulnerabilit / y", "Displaceme /
|
|
118
|
+
// nt". --full-width is for short labels; a narrow row with long ones
|
|
119
|
+
// should stay on the default layout and wrap.
|
|
120
|
+
// * A WRAPPED ROW STARTS 1px EARLY. The -1px border collapse below applies
|
|
121
|
+
// to every segment after the first, whichever line it lands on, so the
|
|
122
|
+
// segment that begins a second row sits 1px outside the control's
|
|
123
|
+
// inline-start edge (measured x=7 against x=8 for the row above). Rows
|
|
124
|
+
// also do not collapse in the block direction, so the seam between two
|
|
125
|
+
// rows is 2px where the seams within a row are 1px. Both need a
|
|
126
|
+
// line-aware selector CSS does not have.
|
|
127
|
+
&__group {
|
|
128
|
+
display: inline-flex;
|
|
129
|
+
flex-wrap: wrap;
|
|
130
|
+
max-inline-size: 100%;
|
|
131
|
+
position: relative;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Hidden from sight, not from anything else. The input keeps its size, its
|
|
135
|
+
// focus, its role and its place in the tab order; only its painting is
|
|
136
|
+
// clipped away, and the label next to it is what the user sees and presses.
|
|
137
|
+
// Never display: none or visibility: hidden here — both take the radio out
|
|
138
|
+
// of the accessibility tree and out of the keyboard's reach.
|
|
139
|
+
&__input {
|
|
140
|
+
appearance: none;
|
|
141
|
+
border: 0;
|
|
142
|
+
clip-path: inset(50%);
|
|
143
|
+
height: 1px;
|
|
144
|
+
margin: -1px;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
padding: 0;
|
|
147
|
+
position: absolute;
|
|
148
|
+
white-space: nowrap;
|
|
149
|
+
width: 1px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// The segment itself, drawn from the outline button's tokens: same stroke
|
|
153
|
+
// width, same radius, same padding, same type. A consumer who styles this by
|
|
154
|
+
// hand is the drift the issue reported, so nothing here is a bespoke value.
|
|
155
|
+
&__label {
|
|
156
|
+
align-items: center;
|
|
157
|
+
background-color: transparent;
|
|
158
|
+
border: var(--mg-border-width-button) solid
|
|
159
|
+
rgb(var(--mg-color-button-outline-primary));
|
|
160
|
+
color: rgb(var(--mg-color-button-outline-primary));
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
display: inline-flex;
|
|
163
|
+
flex: 0 1 auto;
|
|
164
|
+
font-family: var(--mg-font-family-text);
|
|
165
|
+
font-size: var(--mg-font-size-button);
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
line-height: 1.2;
|
|
169
|
+
min-block-size: $mg-segmented-min-block-size;
|
|
170
|
+
min-inline-size: 0;
|
|
171
|
+
overflow-wrap: anywhere;
|
|
172
|
+
padding: var(--mg-padding-button);
|
|
173
|
+
text-align: center;
|
|
174
|
+
|
|
175
|
+
// Local stacking context only: the focus ring and the selected fill have
|
|
176
|
+
// to sit above the neighbouring segment, whose border this one overlaps.
|
|
177
|
+
// No page-level layer is involved, so a raw integer is correct here.
|
|
178
|
+
position: relative;
|
|
179
|
+
z-index: 0;
|
|
180
|
+
|
|
181
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
182
|
+
transition:
|
|
183
|
+
background-color var(--mg-motion-duration-fast)
|
|
184
|
+
var(--mg-motion-easing-standard),
|
|
185
|
+
border-color var(--mg-motion-duration-fast)
|
|
186
|
+
var(--mg-motion-easing-standard),
|
|
187
|
+
color var(--mg-motion-duration-fast) var(--mg-motion-easing-standard);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Only the ends are rounded, and they are rounded with logical corners, so
|
|
192
|
+
// the row mirrors itself under dir="rtl" with no RTL rule to keep in step.
|
|
193
|
+
&__label:first-of-type {
|
|
194
|
+
border-end-start-radius: $mg-segmented-radius;
|
|
195
|
+
border-start-start-radius: $mg-segmented-radius;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
&__label:last-of-type {
|
|
199
|
+
border-end-end-radius: $mg-segmented-radius;
|
|
200
|
+
border-start-end-radius: $mg-segmented-radius;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Adjoining, not spaced: every segment after the first pulls back over its
|
|
204
|
+
// neighbour's stroke so the row reads as one control with single-width
|
|
205
|
+
// dividers. margin-inline-start flips with direction on its own.
|
|
206
|
+
&__input:not(:first-of-type) + &__label {
|
|
207
|
+
margin-inline-start: calc(-1 * var(--mg-border-width-button));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Full width: the segments share the control's inline size equally. Useful
|
|
211
|
+
// where the control owns a panel header or a mobile row.
|
|
212
|
+
&--full-width &__group {
|
|
213
|
+
display: flex;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
&--full-width &__label {
|
|
217
|
+
flex: 1 1 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// One shared compact size, so a toolbar or a map overlay does not invent its
|
|
221
|
+
// own. It stays above the 24x24 WCAG 2.5.8 minimum; the 44px default is the
|
|
222
|
+
// recommended target and is what an unmodified control gives you.
|
|
223
|
+
&--small {
|
|
224
|
+
--mg-segmented-control-min-block-size: #{mg-rem(36)};
|
|
225
|
+
|
|
226
|
+
.mg-segmented-control__label {
|
|
227
|
+
font-size: var(--mg-font-size-200);
|
|
228
|
+
padding: var(--mg-spacing-50) var(--mg-spacing-100);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// States live outside the BEM nesting, in specificity order: hover (lowest),
|
|
234
|
+
// then selected, then disabled, then focus — so focus is visible on any of
|
|
235
|
+
// them and a disabled segment never lights up.
|
|
236
|
+
|
|
237
|
+
// Hover is written on the LABEL, not on `input:hover + label`. Pointing at a
|
|
238
|
+
// label does not put :hover on the input it is for, so the input-side
|
|
239
|
+
// selector would only ever match the pixel the input is clipped to.
|
|
240
|
+
|
|
241
|
+
// THE HOVER FILL IS A KNOWN CONTRAST PROBLEM IN TWO THEMES, AND #1249 IS THE
|
|
242
|
+
// FIX. --mg-color-button-outline-primary--hover and
|
|
243
|
+
// --mg-color-button-background--hover both resolve to orange-900 (#eb752a) in
|
|
244
|
+
// PreventionWeb and IRP, because both themes set interactive-active equal to
|
|
245
|
+
// interactive and had no tonal step of their own to hover to. Against the white
|
|
246
|
+
// label that is 2.95:1 — under SC 1.4.3 — and it applies to BOTH hover states
|
|
247
|
+
// here, selected and unselected. Measured in a browser on this component:
|
|
248
|
+
|
|
249
|
+
// theme selected rest unselected rest hover fill (either)
|
|
250
|
+
// UNDRR #004f91 8.31 #004f91 8.31 #3372a7 5.11 ok
|
|
251
|
+
// PreventionWeb #0a6969 6.49 #0a6969 6.49 #eb752a 2.95 FAILS
|
|
252
|
+
// IRP #0f78bf 4.71 #0f78bf 4.71 #eb752a 2.95 FAILS
|
|
253
|
+
// MCR2030 #591a61 12.03 #591a61 12.03 #6e2677 9.52 ok
|
|
254
|
+
// DELTA #004f91 8.31 #004f91 8.31 #106cb8 ok
|
|
255
|
+
|
|
256
|
+
// tokens-contract.test.js records this pair as an exception under the names
|
|
257
|
+
// "button label on primary background, hover" and "outline primary button
|
|
258
|
+
// label, hover fill" — but those names are cta-button.scss's, and the suite
|
|
259
|
+
// checks token pairs rather than components, so this control's use of the same
|
|
260
|
+
// pair adds no entry and the suite stays green while a second component ships
|
|
261
|
+
// the failure. The exception is a defect record, not a licence.
|
|
262
|
+
// unisdr/undrr-mangrove#1249 re-points both tokens (PW to teal-800 at 5.50,
|
|
263
|
+
// IRP to blue-800 at 6.02) and deletes those entries; this control then clears
|
|
264
|
+
// 1.4.3 in all five themes with no change of its own.
|
|
265
|
+
|
|
266
|
+
// ==> DO NOT MERGE THIS COMPONENT AHEAD OF #1249. <==
|
|
267
|
+
|
|
268
|
+
// Second, smaller point, present in every theme: hover fills an UNSELECTED
|
|
269
|
+
// segment and puts the white label on it, which is the selected treatment. So
|
|
270
|
+
// while the pointer is on an unselected segment the row shows two filled
|
|
271
|
+
// segments, and in UNDRR the only thing separating them is #004f91 against
|
|
272
|
+
// #3372a7 — 1.62:1, under SC 1.4.11's 3:1 for a state indicator. Hovering the
|
|
273
|
+
// SELECTED segment moves it to that same #3372a7, so it briefly looks like any
|
|
274
|
+
// other hovered segment. A tinted hover that keeps the brand ink would leave
|
|
275
|
+
// "filled" meaning "selected" and nothing else; it is not done here because
|
|
276
|
+
// every candidate tint puts an existing token into a contrast role
|
|
277
|
+
// tokens-contract.test.js does not yet cover, in five themes, and that is a
|
|
278
|
+
// design decision rather than a review one.
|
|
279
|
+
.mg-segmented-control__label:hover {
|
|
280
|
+
background-color: rgb(var(--mg-color-button-outline-primary--hover));
|
|
281
|
+
border-color: rgb(var(--mg-color-button-outline-primary--hover));
|
|
282
|
+
color: rgb(var(--mg-color-button));
|
|
283
|
+
z-index: 1;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Selected takes the filled primary button's pair, which every theme has
|
|
287
|
+
// already contrast-checked for its own brand colour.
|
|
288
|
+
.mg-segmented-control__input:checked + .mg-segmented-control__label {
|
|
289
|
+
background-color: var(--mg-color-button-background);
|
|
290
|
+
border-color: var(--mg-color-button-background);
|
|
291
|
+
color: rgb(var(--mg-color-button));
|
|
292
|
+
z-index: 1;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.mg-segmented-control__input:checked + .mg-segmented-control__label:hover {
|
|
296
|
+
background-color: var(--mg-color-button-background--hover);
|
|
297
|
+
border-color: var(--mg-color-button-background--hover);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Declared after the selected rules, and one class heavier than each of them,
|
|
301
|
+
// so a disabled segment keeps this treatment while the pointer is over it.
|
|
302
|
+
.mg-segmented-control__input:disabled + .mg-segmented-control__label,
|
|
303
|
+
.mg-segmented-control__input:disabled + .mg-segmented-control__label:hover {
|
|
304
|
+
background-color: transparent;
|
|
305
|
+
border-color: rgb(var(--mg-color-neutral-400));
|
|
306
|
+
color: rgb(var(--mg-color-neutral-400));
|
|
307
|
+
cursor: not-allowed;
|
|
308
|
+
z-index: 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.mg-segmented-control__input:disabled:checked + .mg-segmented-control__label,
|
|
312
|
+
.mg-segmented-control__input:disabled:checked
|
|
313
|
+
+ .mg-segmented-control__label:hover {
|
|
314
|
+
background-color: rgb(var(--mg-color-neutral-500));
|
|
315
|
+
border-color: rgb(var(--mg-color-neutral-500));
|
|
316
|
+
color: rgb(var(--mg-color-neutral-0));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// The ring is drawn on the label because the input it belongs to is clipped to
|
|
320
|
+
// a pixel. mg-focus-ring is an outline plus a separator band, and neither is
|
|
321
|
+
// re-declared by any rule above, so a focused segment keeps its ring whether
|
|
322
|
+
// it is selected, hovered or both.
|
|
323
|
+
.mg-segmented-control__input:focus-visible + .mg-segmented-control__label {
|
|
324
|
+
@include mg-focus-ring;
|
|
325
|
+
|
|
326
|
+
z-index: 2;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Forced colours forces every background to Canvas and every foreground to
|
|
330
|
+
// CanvasText, whatever the author wrote — a system colour keyword in an author
|
|
331
|
+
// declaration is forced like any other, which was measured on this component:
|
|
332
|
+
// `background-color: Highlight` on the selected segment came back as Canvas
|
|
333
|
+
// and the row read as three identical, unselected boxes. Selection is the one
|
|
334
|
+
// thing this control has to show, so the segments opt out of forcing and
|
|
335
|
+
// repaint themselves in system colours, exactly as .mg-switch does for its
|
|
336
|
+
// track. Nothing here uses opacity: a faded system colour loses its contrast.
|
|
337
|
+
// Opting out has a cost, and it is the one unisdr/undrr-mangrove#1187 found on
|
|
338
|
+
// the switch: with forcing off, an author colour that still reaches the
|
|
339
|
+
// element is painted literally in a palette that has no such colour. Every
|
|
340
|
+
// selector below therefore carries an extra .mg-segmented-control so that it
|
|
341
|
+
// outranks, or at least ties and follows, the author state rule it replaces —
|
|
342
|
+
// including the :hover variants, which are one class heavier than the state
|
|
343
|
+
// they sit on and would otherwise repaint a segment in brand blue.
|
|
344
|
+
@media (forced-colors: active) {
|
|
345
|
+
.mg-segmented-control .mg-segmented-control__label {
|
|
346
|
+
background-color: Canvas;
|
|
347
|
+
border-color: ButtonText;
|
|
348
|
+
color: ButtonText;
|
|
349
|
+
forced-color-adjust: none;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// The Highlight pair, which is what a selected item looks like in every
|
|
353
|
+
// forced-colours palette.
|
|
354
|
+
.mg-segmented-control
|
|
355
|
+
.mg-segmented-control__input:checked
|
|
356
|
+
+ .mg-segmented-control__label,
|
|
357
|
+
.mg-segmented-control
|
|
358
|
+
.mg-segmented-control__input:checked
|
|
359
|
+
+ .mg-segmented-control__label:hover {
|
|
360
|
+
background-color: Highlight;
|
|
361
|
+
border-color: Highlight;
|
|
362
|
+
color: HighlightText;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.mg-segmented-control
|
|
366
|
+
.mg-segmented-control__input:disabled
|
|
367
|
+
+ .mg-segmented-control__label,
|
|
368
|
+
.mg-segmented-control
|
|
369
|
+
.mg-segmented-control__input:disabled
|
|
370
|
+
+ .mg-segmented-control__label:hover {
|
|
371
|
+
background-color: Canvas;
|
|
372
|
+
border-color: GrayText;
|
|
373
|
+
color: GrayText;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// A disabled selection still has to read as the selection, so it keeps a
|
|
377
|
+
// fill and takes GrayText for it.
|
|
378
|
+
.mg-segmented-control
|
|
379
|
+
.mg-segmented-control__input:disabled:checked
|
|
380
|
+
+ .mg-segmented-control__label,
|
|
381
|
+
.mg-segmented-control
|
|
382
|
+
.mg-segmented-control__input:disabled:checked
|
|
383
|
+
+ .mg-segmented-control__label:hover {
|
|
384
|
+
background-color: GrayText;
|
|
385
|
+
border-color: GrayText;
|
|
386
|
+
color: Canvas;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// forced-color-adjust: none above would otherwise keep mg-focus-ring's white
|
|
390
|
+
// separator band, which is an author colour on a system-coloured segment.
|
|
391
|
+
// The band goes and the ring becomes Highlight, drawn outside the segment,
|
|
392
|
+
// where it is legible against a selected segment's fill as well.
|
|
393
|
+
.mg-segmented-control
|
|
394
|
+
.mg-segmented-control__input:focus-visible
|
|
395
|
+
+ .mg-segmented-control__label {
|
|
396
|
+
box-shadow: none;
|
|
397
|
+
outline: var(--mg-focus-ring-width) solid Highlight;
|
|
398
|
+
outline-offset: var(--mg-focus-ring-offset);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Hover is a colour-only change, and forced colours leaves no second fill to
|
|
402
|
+
// move to. An inner boundary is the affordance that survives;
|
|
403
|
+
// :not(:focus-visible) on the input keeps the focus outline winning when the
|
|
404
|
+
// pointer is over a focused segment, and :not(:disabled) keeps a disabled
|
|
405
|
+
// segment inert.
|
|
406
|
+
.mg-segmented-control
|
|
407
|
+
.mg-segmented-control__input:not(:disabled, :focus-visible)
|
|
408
|
+
+ .mg-segmented-control__label:hover {
|
|
409
|
+
outline: 1px solid ButtonText;
|
|
410
|
+
outline-offset: calc(-1 * var(--mg-border-width-button));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
@@ -297,6 +297,43 @@ $mg-switch-ring-gap: var(
|
|
|
297
297
|
rgb(var(--mg-color-neutral-300))
|
|
298
298
|
);
|
|
299
299
|
|
|
300
|
+
// Error follows the rest of the form: the invalid control takes a red
|
|
301
|
+
// boundary and the message is a sibling .mg-form-error. The track's inset is
|
|
302
|
+
// a transparent border (below), so the error is a border-color change, the
|
|
303
|
+
// same declaration .mg-form-input--error makes. Nothing here is a box-shadow
|
|
304
|
+
// or an outline, so the focus ring's outline and separator band survive it.
|
|
305
|
+
$mg-switch-error: var(
|
|
306
|
+
--mg-switch-track-border-color--error,
|
|
307
|
+
rgb(var(--mg-color-red-900))
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Geometry hooks. --mg-switch-size is the one number most consumers set: the
|
|
311
|
+
// track's block size. The track's inline size, the inset around the thumb, the
|
|
312
|
+
// thumb and the thumb's travel all derive from it, so resizing a switch never
|
|
313
|
+
// means re-declaring the transform -- which is what broke RTL, where the
|
|
314
|
+
// travel is the same distance the other way. See unisdr/undrr-mangrove#1199.
|
|
315
|
+
// Each part can still be set on its own, and every derived value is written
|
|
316
|
+
// once and reused by the LTR rule, the RTL rule and the forced-colours block.
|
|
317
|
+
$mg-switch-size: var(--mg-switch-size, #{mg-rem(24)});
|
|
318
|
+
$mg-switch-track-block: var(--mg-switch-track-block-size, #{$mg-switch-size});
|
|
319
|
+
$mg-switch-track-inline: var(
|
|
320
|
+
--mg-switch-track-inline-size,
|
|
321
|
+
calc(#{$mg-switch-track-block} * 1.75)
|
|
322
|
+
);
|
|
323
|
+
$mg-switch-inset: var(
|
|
324
|
+
--mg-switch-track-inset,
|
|
325
|
+
calc(#{$mg-switch-track-block} / 12)
|
|
326
|
+
);
|
|
327
|
+
$mg-switch-thumb-size: var(
|
|
328
|
+
--mg-switch-thumb-size,
|
|
329
|
+
calc(#{$mg-switch-track-block} - 2 * #{$mg-switch-inset})
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// The thumb crosses the track's content box: the track's inline size less its
|
|
333
|
+
// two insets, less the thumb. Interpolated as a string so the LTR rule, the
|
|
334
|
+
// RTL rule and any test read one expression.
|
|
335
|
+
$mg-switch-travel: "#{$mg-switch-track-inline} - #{$mg-switch-thumb-size} - 2 * #{$mg-switch-inset}";
|
|
336
|
+
|
|
300
337
|
.mg-switch {
|
|
301
338
|
align-items: center;
|
|
302
339
|
cursor: pointer;
|
|
@@ -326,7 +363,7 @@ $mg-switch-ring-gap: var(
|
|
|
326
363
|
background-color: $mg-switch-track-checked;
|
|
327
364
|
|
|
328
365
|
.mg-switch__thumb {
|
|
329
|
-
transform: translateX(mg-
|
|
366
|
+
transform: translateX(calc(#{$mg-switch-travel}));
|
|
330
367
|
}
|
|
331
368
|
}
|
|
332
369
|
|
|
@@ -374,12 +411,14 @@ $mg-switch-ring-gap: var(
|
|
|
374
411
|
);
|
|
375
412
|
cursor: progress;
|
|
376
413
|
|
|
414
|
+
// Both lengths are fractions of the thumb (2px and 4px on a default
|
|
415
|
+
// 20px thumb), so a resized switch keeps the same ring proportions.
|
|
377
416
|
.mg-switch__thumb::after {
|
|
378
|
-
border: mg-
|
|
417
|
+
border: calc(#{$mg-switch-thumb-size} * 0.1) dotted $mg-switch-ring;
|
|
379
418
|
border-radius: 50%;
|
|
380
419
|
box-sizing: border-box;
|
|
381
420
|
content: "";
|
|
382
|
-
inset: mg-
|
|
421
|
+
inset: calc(#{$mg-switch-thumb-size} * 0.2);
|
|
383
422
|
position: absolute;
|
|
384
423
|
}
|
|
385
424
|
|
|
@@ -410,30 +449,37 @@ $mg-switch-ring-gap: var(
|
|
|
410
449
|
cursor: progress;
|
|
411
450
|
}
|
|
412
451
|
|
|
452
|
+
// The inset around the thumb is a transparent border rather than padding:
|
|
453
|
+
// it holds exactly the same box (border-box sizing, same content box, same
|
|
454
|
+
// travel) and gives the error state a border-color to set, the way every
|
|
455
|
+
// other control shows an error. Forced colours colours the same border.
|
|
413
456
|
&__track {
|
|
414
457
|
align-items: center;
|
|
415
458
|
background-color: $mg-switch-track;
|
|
416
|
-
|
|
459
|
+
block-size: $mg-switch-track-block;
|
|
460
|
+
border: $mg-switch-inset solid transparent;
|
|
461
|
+
border-radius: calc(#{$mg-switch-track-block} / 2);
|
|
417
462
|
box-sizing: border-box;
|
|
418
463
|
display: inline-flex;
|
|
419
464
|
flex-shrink: 0;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
465
|
+
inline-size: $mg-switch-track-inline;
|
|
466
|
+
transition:
|
|
467
|
+
background-color var(--mg-motion-duration-fast)
|
|
468
|
+
var(--mg-motion-easing-standard),
|
|
469
|
+
border-color var(--mg-motion-duration-fast)
|
|
470
|
+
var(--mg-motion-easing-standard);
|
|
425
471
|
}
|
|
426
472
|
|
|
427
473
|
&__thumb {
|
|
428
474
|
background-color: $mg-switch-thumb;
|
|
475
|
+
block-size: $mg-switch-thumb-size;
|
|
429
476
|
border-radius: 50%;
|
|
430
477
|
box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
|
|
431
478
|
display: block;
|
|
432
|
-
|
|
479
|
+
inline-size: $mg-switch-thumb-size;
|
|
433
480
|
position: relative;
|
|
434
481
|
transition: transform var(--mg-motion-duration-fast)
|
|
435
482
|
var(--mg-motion-easing-standard);
|
|
436
|
-
width: mg-rem(20);
|
|
437
483
|
}
|
|
438
484
|
|
|
439
485
|
&__label {
|
|
@@ -441,6 +487,17 @@ $mg-switch-ring-gap: var(
|
|
|
441
487
|
font-size: var(--mg-font-size-300);
|
|
442
488
|
}
|
|
443
489
|
|
|
490
|
+
// One shared small size for a toolbar, a table row or a panel header, so
|
|
491
|
+
// each consumer does not invent its own. It sets the size hook, and tightens
|
|
492
|
+
// the row's gap to match the smaller graphic; no length inside the switch is
|
|
493
|
+
// re-declared. The row keeps its 36px min-height, so the target stays large
|
|
494
|
+
// enough even though the graphic is smaller.
|
|
495
|
+
&--small {
|
|
496
|
+
--mg-switch-size: #{mg-rem(18)};
|
|
497
|
+
|
|
498
|
+
gap: var(--mg-spacing-50);
|
|
499
|
+
}
|
|
500
|
+
|
|
444
501
|
@media (prefers-reduced-motion: reduce) {
|
|
445
502
|
&__track,
|
|
446
503
|
&__thumb {
|
|
@@ -449,8 +506,25 @@ $mg-switch-ring-gap: var(
|
|
|
449
506
|
}
|
|
450
507
|
}
|
|
451
508
|
|
|
509
|
+
// Error, in the three shapes the rest of the library already uses: the
|
|
510
|
+
// aria-invalid the other controls set anyway, the --error element modifier
|
|
511
|
+
// they carry, and a block modifier for markup that can only reach the label,
|
|
512
|
+
// as .mg-switch--pending does. Declared after the checked rule, so an invalid
|
|
513
|
+
// switch keeps its red boundary in either position.
|
|
514
|
+
.mg-switch__input[aria-invalid="true"] + .mg-switch__track,
|
|
515
|
+
.mg-switch__input--error + .mg-switch__track,
|
|
516
|
+
.mg-switch--error .mg-switch__track {
|
|
517
|
+
border-color: $mg-switch-error;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// The same distance the other way. Both rules interpolate one expression, so
|
|
521
|
+
// a consumer setting the size hooks gets a correct RTL travel for free and
|
|
522
|
+
// never has a transform of their own to keep in step. The descendant
|
|
523
|
+
// combinator already covers a switch carrying `dir="rtl"` itself, because the
|
|
524
|
+
// label is an ancestor of its own input -- no `.mg-switch[dir="rtl"]` variant
|
|
525
|
+
// is needed, and adding one would only raise this rule above a consumer's.
|
|
452
526
|
[dir="rtl"] .mg-switch__input:checked + .mg-switch__track .mg-switch__thumb {
|
|
453
|
-
transform: translateX(
|
|
527
|
+
transform: translateX(calc(-1 * (#{$mg-switch-travel})));
|
|
454
528
|
}
|
|
455
529
|
|
|
456
530
|
// Forced colours paint every background as Canvas, which erased the track and
|
|
@@ -461,12 +535,12 @@ $mg-switch-ring-gap: var(
|
|
|
461
535
|
@media (forced-colors: active) {
|
|
462
536
|
.mg-switch__track {
|
|
463
537
|
// A border, not an outline, so the focus outline adds to the boundary
|
|
464
|
-
// rather than replacing it. The
|
|
465
|
-
//
|
|
538
|
+
// rather than replacing it. The track already carries its inset as a
|
|
539
|
+
// transparent border, so forced colours only colours it: the box, the
|
|
540
|
+
// thumb and the travel are untouched at any size.
|
|
466
541
|
background-color: Canvas;
|
|
467
|
-
border:
|
|
542
|
+
border-color: CanvasText;
|
|
468
543
|
forced-color-adjust: none;
|
|
469
|
-
padding: 1px;
|
|
470
544
|
}
|
|
471
545
|
|
|
472
546
|
.mg-switch__thumb {
|
|
@@ -547,6 +621,23 @@ $mg-switch-ring-gap: var(
|
|
|
547
621
|
}
|
|
548
622
|
}
|
|
549
623
|
|
|
624
|
+
// The track opts out of forcing, so an author red would be painted
|
|
625
|
+
// unchanged here while everything around it adapts -- the bug reported in
|
|
626
|
+
// unisdr/undrr-mangrove#1187. The boundary goes back to a system colour and
|
|
627
|
+
// the error is carried by a dashed edge instead, which survives a palette
|
|
628
|
+
// with no red in it. The .mg-form-error message carries the rest.
|
|
629
|
+
// Each selector carries an extra .mg-switch, as the disabled rules above do,
|
|
630
|
+
// so that all three reach the checked fill's specificity. Without it the two
|
|
631
|
+
// class markups lost `border-color` to the Highlight fill and kept only
|
|
632
|
+
// `border-style`, which painted a dashed Highlight edge on a Highlight
|
|
633
|
+
// track: an invalid checked switch with no visible boundary at all.
|
|
634
|
+
.mg-switch .mg-switch__input[aria-invalid="true"] + .mg-switch__track,
|
|
635
|
+
.mg-switch .mg-switch__input--error + .mg-switch__track,
|
|
636
|
+
.mg-switch.mg-switch--error .mg-switch__track {
|
|
637
|
+
border-color: CanvasText;
|
|
638
|
+
border-style: dashed;
|
|
639
|
+
}
|
|
640
|
+
|
|
550
641
|
@media (prefers-reduced-motion: no-preference) {
|
|
551
642
|
// Every ring colour selector above is repeated here, so the gap outranks
|
|
552
643
|
// or follows each one; without a gap the spin would be invisible.
|
|
@@ -596,7 +687,7 @@ $mg-switch-ring-gap: var(
|
|
|
596
687
|
// -----------------------------------------------------------------------------
|
|
597
688
|
.mg-form-error-summary {
|
|
598
689
|
background-color: rgb(var(--mg-color-red-50));
|
|
599
|
-
border-
|
|
690
|
+
border-inline-start: 4px solid rgb(var(--mg-color-red-900));
|
|
600
691
|
margin-bottom: var(--mg-spacing-200);
|
|
601
692
|
padding: var(--mg-spacing-150);
|
|
602
693
|
|
|
@@ -617,7 +708,7 @@ $mg-switch-ring-gap: var(
|
|
|
617
708
|
|
|
618
709
|
&__list {
|
|
619
710
|
margin: 0;
|
|
620
|
-
padding-
|
|
711
|
+
padding-inline-start: var(--mg-spacing-100);
|
|
621
712
|
|
|
622
713
|
li {
|
|
623
714
|
margin-bottom: var(--mg-spacing-50);
|