@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
|
@@ -114,6 +114,10 @@
|
|
|
114
114
|
cursor: grabbing;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
&:focus-visible {
|
|
118
|
+
@include mg-focus-ring-inset;
|
|
119
|
+
}
|
|
120
|
+
|
|
117
121
|
.mg-scroll--mobile & {
|
|
118
122
|
cursor: default;
|
|
119
123
|
|
|
@@ -161,6 +165,13 @@
|
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
&--custom-width {
|
|
168
|
+
// A floor on the scroll box itself, which is what minWidth means. Note
|
|
169
|
+
// that min-width wins over the max-width: 100% above, so a value larger
|
|
170
|
+
// than the parent makes this box overflow its parent by design — that
|
|
171
|
+
// is the point of a minimum. Containing that overflow is the caller's
|
|
172
|
+
// job; pick a value the layout can afford, or give the parent its own
|
|
173
|
+
// overflow. To widen the scrollable track instead, size the items with
|
|
174
|
+
// itemWidth.
|
|
164
175
|
min-width: var(--scroll-min-width, auto);
|
|
165
176
|
}
|
|
166
177
|
|
|
@@ -26,7 +26,12 @@ $search-debug-text: var(--mg-color-yellow-dark);
|
|
|
26
26
|
|
|
27
27
|
// Spacing & sizing (component needs rounded corners, base theme has 0)
|
|
28
28
|
$search-radius: var(--mg-spacing-25);
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
// Clear button target: 36px square, clear of the WCAG 2.5.8 24x24 minimum.
|
|
31
|
+
$search-clear-size: mg-rem(36);
|
|
32
|
+
|
|
33
|
+
// Inline-end room for the clear button: its size plus the offset and a small gap.
|
|
34
|
+
$search-input-padding-x: mg-rem(44);
|
|
30
35
|
|
|
31
36
|
// Screen reader only utility
|
|
32
37
|
.mg-u-sr-only {
|
|
@@ -102,8 +107,8 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
102
107
|
.mg-search__input {
|
|
103
108
|
@extend %mg-form-input-base;
|
|
104
109
|
|
|
105
|
-
// Override
|
|
106
|
-
padding-
|
|
110
|
+
// Override the inline-end padding to leave room for the clear button
|
|
111
|
+
padding-inline-end: $search-input-padding-x;
|
|
107
112
|
transition:
|
|
108
113
|
border-color 0.2s,
|
|
109
114
|
box-shadow 0.2s;
|
|
@@ -115,18 +120,20 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
115
120
|
|
|
116
121
|
.mg-search__clear {
|
|
117
122
|
position: absolute;
|
|
118
|
-
|
|
123
|
+
inset-inline-end: var(--mg-spacing-50);
|
|
119
124
|
top: 50%;
|
|
120
125
|
transform: translateY(-50%);
|
|
121
126
|
display: inline-flex;
|
|
122
127
|
align-items: center;
|
|
123
128
|
justify-content: center;
|
|
124
|
-
width:
|
|
125
|
-
height:
|
|
129
|
+
width: $search-clear-size;
|
|
130
|
+
height: $search-clear-size;
|
|
126
131
|
padding: 0;
|
|
127
132
|
background: none;
|
|
128
133
|
border: none;
|
|
129
|
-
|
|
134
|
+
|
|
135
|
+
// Matches the icon-only button glyph size used by CTA buttons and chips.
|
|
136
|
+
font-size: mg-rem(18);
|
|
130
137
|
color: $search-text;
|
|
131
138
|
cursor: pointer;
|
|
132
139
|
line-height: 1;
|
|
@@ -171,14 +178,16 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
171
178
|
.mg-search__active-filters-label {
|
|
172
179
|
font-weight: 500;
|
|
173
180
|
color: $search-text;
|
|
174
|
-
margin-
|
|
181
|
+
margin-inline-end: var(--mg-spacing-25);
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
.mg-search__active-filters-list {
|
|
178
185
|
display: flex;
|
|
179
186
|
flex-wrap: wrap;
|
|
180
187
|
gap: var(--mg-spacing-50);
|
|
181
|
-
|
|
188
|
+
|
|
189
|
+
@include mg-list-unmarked;
|
|
190
|
+
|
|
182
191
|
margin: 0;
|
|
183
192
|
padding: 0;
|
|
184
193
|
}
|
|
@@ -492,7 +501,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
492
501
|
.mg-search__result-date {
|
|
493
502
|
&::before {
|
|
494
503
|
content: "•";
|
|
495
|
-
margin-
|
|
504
|
+
margin-inline-end: var(--mg-spacing-50);
|
|
496
505
|
color: $search-border;
|
|
497
506
|
}
|
|
498
507
|
}
|
|
@@ -949,7 +958,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
949
958
|
padding: var(--mg-spacing-50) var(--mg-spacing-75);
|
|
950
959
|
font-size: var(--mg-font-size-300);
|
|
951
960
|
color: $search-text;
|
|
952
|
-
text-align:
|
|
961
|
+
text-align: start;
|
|
953
962
|
background-color: rgb(var(--mg-color-neutral-50));
|
|
954
963
|
border: 1px solid rgb(var(--mg-color-neutral-600) / 0.22);
|
|
955
964
|
border-radius: var(--mg-radius-form-input);
|
|
@@ -986,7 +995,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
986
995
|
|
|
987
996
|
.mg-select__trigger-icon {
|
|
988
997
|
flex-shrink: 0;
|
|
989
|
-
margin-
|
|
998
|
+
margin-inline-start: var(--mg-spacing-50);
|
|
990
999
|
color: $search-text-muted;
|
|
991
1000
|
transition: transform 0.2s ease;
|
|
992
1001
|
|
|
@@ -1148,7 +1157,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
1148
1157
|
|
|
1149
1158
|
// Subtype options (indented children under parent content types)
|
|
1150
1159
|
.mg-select__option--subtype {
|
|
1151
|
-
padding-
|
|
1160
|
+
padding-inline-start: var(--mg-spacing-150);
|
|
1152
1161
|
|
|
1153
1162
|
.mg-select__option-label {
|
|
1154
1163
|
font-size: var(--mg-font-size-100);
|
|
@@ -1156,7 +1165,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
1156
1165
|
// Add visual connector
|
|
1157
1166
|
&::before {
|
|
1158
1167
|
content: "└";
|
|
1159
|
-
margin-
|
|
1168
|
+
margin-inline-end: var(--mg-spacing-25);
|
|
1160
1169
|
color: $search-text-muted;
|
|
1161
1170
|
}
|
|
1162
1171
|
}
|
|
@@ -1194,7 +1203,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
1194
1203
|
}
|
|
1195
1204
|
|
|
1196
1205
|
label {
|
|
1197
|
-
padding-
|
|
1206
|
+
padding-inline-start: var(--mg-spacing-25);
|
|
1198
1207
|
font-weight: normal;
|
|
1199
1208
|
text-wrap: balance;
|
|
1200
1209
|
cursor: pointer;
|
|
@@ -1226,7 +1235,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
1226
1235
|
}
|
|
1227
1236
|
|
|
1228
1237
|
// ========================================
|
|
1229
|
-
// Mobile Filter Drawer (
|
|
1238
|
+
// Mobile Filter Drawer (Inline-start Slide-in)
|
|
1230
1239
|
// ========================================
|
|
1231
1240
|
// UX patterns informed by:
|
|
1232
1241
|
// @see https://www.pencilandpaper.io/articles/ux-pattern-analysis-mobile-filters
|
|
@@ -1254,24 +1263,42 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
1254
1263
|
}
|
|
1255
1264
|
}
|
|
1256
1265
|
|
|
1266
|
+
// The panel, the direction it travels and the side its shadow falls on are
|
|
1267
|
+
// all driven by two custom properties flipped under `:dir(rtl)`, the same
|
|
1268
|
+
// shape `.mg-drawer--start` uses in Navigation/Drawer. `inset-inline-start`
|
|
1269
|
+
// on its own would fight the keyframe, which translates on a physical axis:
|
|
1270
|
+
// the resting edge would move to the right in Arabic while the panel still
|
|
1271
|
+
// slid in from the left.
|
|
1257
1272
|
.mg-search__drawer {
|
|
1273
|
+
--mg-search-drawer-offset: -100%;
|
|
1274
|
+
--mg-search-drawer-shadow-offset: 4px;
|
|
1275
|
+
|
|
1258
1276
|
position: fixed;
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
bottom: 0;
|
|
1277
|
+
inset-block: 0;
|
|
1278
|
+
inset-inline-start: 0;
|
|
1262
1279
|
width: min(320px, 85vw); // Max 320px or 85% of viewport
|
|
1263
1280
|
background: #fff;
|
|
1264
|
-
box-shadow:
|
|
1281
|
+
box-shadow: var(--mg-search-drawer-shadow-offset) 0 20px rgb(0 0 0 / 0.15);
|
|
1265
1282
|
display: flex;
|
|
1266
1283
|
flex-direction: column;
|
|
1267
|
-
animation: mg-search-slide-in-
|
|
1284
|
+
animation: mg-search-slide-in-start 0.3s ease-out;
|
|
1268
1285
|
z-index: var(--mg-z-index-drawer);
|
|
1269
1286
|
cursor: default; // Reset cursor inside drawer
|
|
1287
|
+
|
|
1288
|
+
// The shadow points at the page content, so it mirrors with the panel.
|
|
1289
|
+
&:dir(rtl) {
|
|
1290
|
+
--mg-search-drawer-offset: 100%;
|
|
1291
|
+
--mg-search-drawer-shadow-offset: -4px;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1295
|
+
animation: none;
|
|
1296
|
+
}
|
|
1270
1297
|
}
|
|
1271
1298
|
|
|
1272
|
-
@keyframes mg-search-slide-in-
|
|
1299
|
+
@keyframes mg-search-slide-in-start {
|
|
1273
1300
|
from {
|
|
1274
|
-
transform: translateX(-
|
|
1301
|
+
transform: translateX(var(--mg-search-drawer-offset));
|
|
1275
1302
|
}
|
|
1276
1303
|
to {
|
|
1277
1304
|
transform: translateX(0);
|
|
@@ -1518,7 +1545,7 @@ $search-input-padding-x: var(--mg-spacing-250);
|
|
|
1518
1545
|
.mg-search__filter-chip-connector {
|
|
1519
1546
|
display: inline-flex;
|
|
1520
1547
|
align-items: center;
|
|
1521
|
-
margin-
|
|
1548
|
+
margin-inline-end: var(--mg-spacing-25);
|
|
1522
1549
|
color: $search-text-muted;
|
|
1523
1550
|
font-weight: 600;
|
|
1524
1551
|
text-transform: uppercase;
|
|
@@ -147,10 +147,16 @@
|
|
|
147
147
|
background-color: rgb(var(--mg-color-hero));
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// The orange accent carries no text at any size, so this variant no longer
|
|
151
|
+
// paints it flat under an eyebrow, a headline and a 90%-white body. It takes
|
|
152
|
+
// the soft treatment below — a white surface under an 8% wash of the accent,
|
|
153
|
+
// with dark copy — and keeps the accent as a solid leading rule, which no
|
|
154
|
+
// copy sits on. See
|
|
155
|
+
// docs/COLOUR-CONTRAST-METHODOLOGY.md#the-orange-accent-carries-no-text
|
|
150
156
|
&--secondary {
|
|
151
157
|
--mg-cta-accent: var(--mg-color-hero--secondary);
|
|
152
158
|
|
|
153
|
-
|
|
159
|
+
border-inline-start: mg-rem(6) solid rgb(var(--mg-cta-accent));
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
&--tertiary {
|
|
@@ -165,7 +171,8 @@
|
|
|
165
171
|
background-color: rgb(var(--mg-color-hero--quaternary));
|
|
166
172
|
}
|
|
167
173
|
|
|
168
|
-
&--soft
|
|
174
|
+
&--soft,
|
|
175
|
+
&--secondary {
|
|
169
176
|
background-color: rgb(var(--mg-color-neutral-0));
|
|
170
177
|
background-image: linear-gradient(
|
|
171
178
|
rgb(var(--mg-cta-accent) / 0.08),
|
|
@@ -187,7 +194,8 @@
|
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
// Strong surfaces need inverse buttons; soft surfaces use normal buttons.
|
|
190
|
-
|
|
197
|
+
// --secondary is a soft surface too, since the orange accent left its fill.
|
|
198
|
+
&:where(:not(.mg-cta--soft, .mg-cta--secondary)) {
|
|
191
199
|
// Light tinted stroke keeps filled buttons visible on the strong surface.
|
|
192
200
|
--mg-border-color-button-primary: rgb(var(--mg-color-neutral-0) / 0.72);
|
|
193
201
|
--mg-border-color-button-secondary: rgb(var(--mg-color-neutral-0) / 0.72);
|
|
@@ -41,9 +41,18 @@
|
|
|
41
41
|
--mg-hub-header-bar-tint: 0 0 0;
|
|
42
42
|
--mg-hub-header-bar-tint-alpha: 0.14;
|
|
43
43
|
|
|
44
|
+
// hero--secondary is the orange accent, which carries no text at any size —
|
|
45
|
+
// every link and heading here was white on it at 2.65, and the bar tint
|
|
46
|
+
// darkened it nowhere near enough. So the surface stays the theme's own hero
|
|
47
|
+
// colour, which the white ink above is already sized for, and the accent
|
|
48
|
+
// becomes the rule the bar sits on: a line, with no copy over it.
|
|
49
|
+
// See docs/COLOUR-CONTRAST-METHODOLOGY.md#the-orange-accent-carries-no-text
|
|
44
50
|
&--surface-secondary {
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
// Full-bleed, because the bar it sits under is full-bleed, so the rule
|
|
52
|
+
// reads as the edge of the header rather than a line inside it.
|
|
53
|
+
.mg-hub-header__bar {
|
|
54
|
+
border-block-end: mg-rem(4) solid rgb(var(--mg-color-hero--secondary));
|
|
55
|
+
}
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
&--surface-tertiary {
|
|
@@ -233,7 +242,9 @@
|
|
|
233
242
|
overflow-x: auto;
|
|
234
243
|
scrollbar-width: none;
|
|
235
244
|
scroll-padding-inline: var(--mg-spacing-150);
|
|
236
|
-
|
|
245
|
+
|
|
246
|
+
@include mg-list-unmarked;
|
|
247
|
+
|
|
237
248
|
padding: 0;
|
|
238
249
|
margin: 0;
|
|
239
250
|
gap: var(--mg-spacing-50);
|
|
@@ -35,6 +35,19 @@
|
|
|
35
35
|
z-index: var(--mg-z-index-behind); // behind siblings, not behind ancestors
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// RTL keeps the physical override rather than a logical `margin-inline-start`:
|
|
39
|
+
// the pseudo-element is absolutely positioned with both offsets `auto`, so it
|
|
40
|
+
// hangs off its static position, and a flex or grid parent resolves that static
|
|
41
|
+
// position from the inline-end edge in RTL. Reset the inherited `margin-left`
|
|
42
|
+
// here, or both negative margins apply at once and the bleed lands ~13px off
|
|
43
|
+
// centre inside a flex parent such as `mg-mega-topbar`, leaving an unpainted
|
|
44
|
+
// strip at the inline-end edge.
|
|
45
|
+
// `left: 50%` is not a physical property left un-mirrored: the mirrored form
|
|
46
|
+
// (`right: 50%` with `margin-right: -50vw`) was measured and reintroduces the
|
|
47
|
+
// offset this anchor exists to remove. Do not tidy it.
|
|
48
|
+
// See unisdr/undrr-mangrove#1203.
|
|
38
49
|
[dir="rtl"] .mg-container-full-width::before {
|
|
39
|
-
|
|
50
|
+
left: 50%;
|
|
51
|
+
margin-left: -50vw;
|
|
52
|
+
margin-right: 0;
|
|
40
53
|
}
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
@import "../../Components/Forms/Textarea/textarea";
|
|
45
45
|
@import "../../Components/Forms/Checkbox/checkbox";
|
|
46
46
|
@import "../../Components/Forms/Radio/radio";
|
|
47
|
+
@import "../../Components/Forms/SegmentedControl/segmented-control";
|
|
47
48
|
@import "../../Components/Forms/Select/select";
|
|
48
49
|
@import "../../Components/Forms/Range/range";
|
|
49
50
|
@import "../../Components/Breadcrumbs/breadcrumbs";
|
|
@@ -170,3 +170,14 @@ $font-path-noto-sans-arabic: "https://assets.undrr.org/fonts/noto-sans-arabic/v1
|
|
|
170
170
|
|
|
171
171
|
font-family: var(--mg-font-family-text);
|
|
172
172
|
}
|
|
173
|
+
|
|
174
|
+
// Arabic joins letters: Latin tracking must never separate or overlap them.
|
|
175
|
+
// Match the element's inherited language, not an Arabic ancestor, so explicit
|
|
176
|
+
// non-Arabic islands keep their own tracking. Important is intentional: this
|
|
177
|
+
// script constraint must beat later component rules and consumer import order.
|
|
178
|
+
// Include generated labels (for example pre[data-language]::after).
|
|
179
|
+
:lang(ar),
|
|
180
|
+
:lang(ar)::before,
|
|
181
|
+
:lang(ar)::after {
|
|
182
|
+
letter-spacing: normal !important;
|
|
183
|
+
}
|
|
@@ -188,6 +188,13 @@ a {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
// Long-form and WYSIWYG content opt into this boundary. Navigation, cards and
|
|
192
|
+
// button-like links carry their purpose through their component treatment;
|
|
193
|
+
// prose links do not, so colour alone is insufficient (WCAG 1.4.1).
|
|
194
|
+
.mg-content p a {
|
|
195
|
+
text-decoration: underline;
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
@media (prefers-reduced-motion: reduce) {
|
|
192
199
|
*,
|
|
193
200
|
*::before,
|
|
@@ -148,6 +148,38 @@
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// Unmarked list — hide a <ul>/<ol>'s markers without losing its list role.
|
|
152
|
+
|
|
153
|
+
// Safari removes the implicit `list` role from any list whose `list-style` is
|
|
154
|
+
// `none`, so VoiceOver stops announcing "list, N items" for every navigation,
|
|
155
|
+
// pager, legend and menu we style that way (unisdr/undrr-mangrove#1209). An
|
|
156
|
+
// empty string IS a marker, so the role survives, and it renders nothing and
|
|
157
|
+
// reserves no space.
|
|
158
|
+
|
|
159
|
+
// Order matters. The shorthand goes first as the fallback: a browser too old
|
|
160
|
+
// to parse a string `list-style-type` drops that declaration and keeps `none`,
|
|
161
|
+
// behaving exactly as it did before. Never emit `list-style: none` on its own.
|
|
162
|
+
|
|
163
|
+
// This is deliberately a CSS fix rather than `role="list"` in the markup.
|
|
164
|
+
// Consumers hand-write these lists in Drupal templates and static HTML, so
|
|
165
|
+
// only a fix on the class reaches all of them — and `jsx-a11y` flags the
|
|
166
|
+
// redundant role in our own examples. See the "List semantics" rule in
|
|
167
|
+
// docs/REVIEW-CHECKLIST.md.
|
|
168
|
+
|
|
169
|
+
// It belongs on the list items too, wherever an `<li>` rule hides its own
|
|
170
|
+
// marker. `list-style-type` is inherited, so the list's empty string reaches
|
|
171
|
+
// the items — but an item rule setting `none` wins over that inheritance, and
|
|
172
|
+
// it is the item's marker Safari inspects. A bare `list-style: none` on an
|
|
173
|
+
// `<li>` silently undoes the fix on its list.
|
|
174
|
+
|
|
175
|
+
// Do NOT use this on a list that carries a different ARIA role on purpose
|
|
176
|
+
// (`tree`, `group`, `menu`, `listbox`, `tablist`): that role already replaces
|
|
177
|
+
// list semantics, and there is nothing to preserve.
|
|
178
|
+
@mixin mg-list-unmarked {
|
|
179
|
+
list-style: none;
|
|
180
|
+
list-style-type: "";
|
|
181
|
+
}
|
|
182
|
+
|
|
151
183
|
/* mixing end */
|
|
152
184
|
|
|
153
185
|
/* placeholder start */
|
|
@@ -42,6 +42,15 @@
|
|
|
42
42
|
* N=2 ΔE 56.8 N=3 ΔE 19.5 N=4 ΔE 19.5
|
|
43
43
|
* N=5 ΔE 11.1 N=6 ΔE 8.3 N=7 ΔE 3.2
|
|
44
44
|
*
|
|
45
|
+
* The worst pair itself, so a slot change can be checked against the right
|
|
46
|
+
* two colours: N=2 is 1&2 (protan), N=3 and N=4 are 1&3 (deutan), N=5 is 1&5
|
|
47
|
+
* (protan), N=6 is 2&6 (protan), N=7 is 1&7 (protan).
|
|
48
|
+
*
|
|
49
|
+
* METHOD — reproduce these before changing a slot; the steps are not
|
|
50
|
+
* interchangeable and the wrong order gives wrong answers. See
|
|
51
|
+
* docs/COLOUR-CONTRAST-METHODOLOGY.md, "How colour-vision separation is
|
|
52
|
+
* measured", for the full procedure and why each step matters.
|
|
53
|
+
*
|
|
45
54
|
* Read that as: up to 4 series, colour alone is sufficient. At 5 it is
|
|
46
55
|
* marginal. At 6 or 7 colour alone is NOT sufficient — slots 1 and 7 are
|
|
47
56
|
* near-identical to a protanope (ΔE 3.2) — so a chart using more than five
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Storybook: https://mangrove.undrr.org/
|
|
5
5
|
* LLMs context: https://mangrove.undrr.org/llms.txt
|
|
6
6
|
* Repository: https://github.com/unisdr/undrr-mangrove
|
|
7
|
-
* Version: 2.0.0
|
|
7
|
+
* Version: 2.0.0
|
|
8
8
|
*
|
|
9
9
|
* ██╗ ██╗███╗ ██╗██████╗ ██████╗ ██████╗
|
|
10
10
|
* ██║ ██║████╗ ██║██╔══██╗██╔══██╗██╔══██╗
|
|
@@ -303,8 +303,21 @@ $mg-tabs-border-bottom: true !default;
|
|
|
303
303
|
// The dots are decoration, not the message — the label text always names the
|
|
304
304
|
// status, so SC 1.4.1 does not depend on them. They still need a boundary:
|
|
305
305
|
// the pale swatches sit at 1.3-2.2:1 against a white page, so a neutral-500
|
|
306
|
-
// ring (5.74:1 on white) gives every indicator a visible edge
|
|
307
|
-
//
|
|
306
|
+
// ring (5.74:1 on white) gives every indicator a visible edge — the same
|
|
307
|
+
// width on every edge of all seven shapes, including the two drawn with a
|
|
308
|
+
// clip path (unisdr/undrr-mangrove#1208). Set
|
|
309
|
+
// --mg-status-label-indicator-border-width to 0 to drop it, bearing in mind
|
|
310
|
+
// that the ring is the only part of the four pale marks clearing 3:1, and
|
|
311
|
+
// that in forced colours it is also what keeps the base indicator visible.
|
|
312
|
+
// -
|
|
313
|
+
// No theme overrides any of the seven fills, so all five brands render the
|
|
314
|
+
// same status palette. That is deliberate: these are DELTA's editorial
|
|
315
|
+
// workflow colours and the shared service-health pair, not brand identity,
|
|
316
|
+
// and a status that changed colour per site would stop being one status.
|
|
317
|
+
// Do not rebalance the fills for colour-vision separation without reading
|
|
318
|
+
// StatusLabel.mdx, "Why the palette is not rebalanced instead" — it is
|
|
319
|
+
// measured, and the alternatives are worse on contrast, on normal vision, or
|
|
320
|
+
// on both.
|
|
308
321
|
--mg-status-label-gap: var(--mg-spacing-75);
|
|
309
322
|
--mg-status-label-font-size: var(--mg-font-size-300);
|
|
310
323
|
--mg-status-label-color: rgb(var(--mg-color-text));
|
|
@@ -31,11 +31,21 @@
|
|
|
31
31
|
--mg-color-button-background: rgb(15 120 191);
|
|
32
32
|
|
|
33
33
|
// color.button-background--hover
|
|
34
|
-
|
|
34
|
+
// Deeper partner of the IRP interactive blue. Carries the secondary button
|
|
35
|
+
// fill and, since the primary button's hover left the orange accent, that
|
|
36
|
+
// hover too. White on it is 6.02:1 and 71.1 perceptually, so it clears AA
|
|
37
|
+
// either way, and it sits 6.6 dE2000 from the interactive blue —
|
|
38
|
+
// comfortably past the ~2.3 just-noticeable difference, and of the same
|
|
39
|
+
// order as the base theme's own blue-900 to blue-700 hover (12.2).
|
|
40
|
+
--mg-color-button-background--hover: rgb(13 103 163);
|
|
35
41
|
|
|
36
42
|
// color.button-secondary-background
|
|
37
|
-
// Deeper partner of the IRP interactive blue
|
|
38
|
-
//
|
|
43
|
+
// Deeper partner of the IRP interactive blue. Carries the secondary button
|
|
44
|
+
// fill and, since the primary button's hover left the orange accent, that
|
|
45
|
+
// hover too. White on it is 6.02:1 and 71.1 perceptually, so it clears AA
|
|
46
|
+
// either way, and it sits 6.6 dE2000 from the interactive blue —
|
|
47
|
+
// comfortably past the ~2.3 just-noticeable difference, and of the same
|
|
48
|
+
// order as the base theme's own blue-900 to blue-700 hover (12.2).
|
|
39
49
|
--mg-color-button-secondary-background: 13 103 163;
|
|
40
50
|
|
|
41
51
|
// color.button-secondary-background--hover
|
|
@@ -46,7 +56,13 @@
|
|
|
46
56
|
--mg-color-button-outline-primary: 15 120 191;
|
|
47
57
|
|
|
48
58
|
// color.button-outline-primary--hover
|
|
49
|
-
|
|
59
|
+
// Deeper partner of the IRP interactive blue. Carries the secondary button
|
|
60
|
+
// fill and, since the primary button's hover left the orange accent, that
|
|
61
|
+
// hover too. White on it is 6.02:1 and 71.1 perceptually, so it clears AA
|
|
62
|
+
// either way, and it sits 6.6 dE2000 from the interactive blue —
|
|
63
|
+
// comfortably past the ~2.3 just-noticeable difference, and of the same
|
|
64
|
+
// order as the base theme's own blue-900 to blue-700 hover (12.2).
|
|
65
|
+
--mg-color-button-outline-primary--hover: 13 103 163;
|
|
50
66
|
|
|
51
67
|
// color.button-outline-secondary
|
|
52
68
|
--mg-color-button-outline-secondary: var(--mg-color-button-secondary-background);
|
|
@@ -585,6 +585,12 @@ $mg-font-line-height-700: 1.5em;
|
|
|
585
585
|
// hero-gradient.end-opacity
|
|
586
586
|
--mg-hero-gradient-end-opacity: 0.08;
|
|
587
587
|
|
|
588
|
+
// hero-scrim.opacity
|
|
589
|
+
--mg-hero-scrim-opacity: 0.16;
|
|
590
|
+
|
|
591
|
+
// hero-scrim.fade
|
|
592
|
+
--mg-hero-scrim-fade: 10rem;
|
|
593
|
+
|
|
588
594
|
// z-index.behind
|
|
589
595
|
--mg-z-index-behind: -1;
|
|
590
596
|
|
|
@@ -31,11 +31,21 @@
|
|
|
31
31
|
--mg-color-button-background: rgb(10 105 105);
|
|
32
32
|
|
|
33
33
|
// color.button-background--hover
|
|
34
|
-
|
|
34
|
+
// Lighter partner of the brand teal. Carries the secondary button fill and,
|
|
35
|
+
// since the primary button's hover left the orange accent, that hover too.
|
|
36
|
+
// White on it is 5.5:1 and 69.7 perceptually, so it clears AA either way,
|
|
37
|
+
// and it sits 4.2 dE2000 from the brand teal — comfortably past the ~2.3
|
|
38
|
+
// just-noticeable difference, though a shallower step than the base theme's
|
|
39
|
+
// own blue-900 to blue-700 hover (12.2).
|
|
40
|
+
--mg-color-button-background--hover: rgb(12 117 117);
|
|
35
41
|
|
|
36
42
|
// color.button-secondary-background
|
|
37
|
-
// Lighter partner of the brand teal
|
|
38
|
-
//
|
|
43
|
+
// Lighter partner of the brand teal. Carries the secondary button fill and,
|
|
44
|
+
// since the primary button's hover left the orange accent, that hover too.
|
|
45
|
+
// White on it is 5.5:1 and 69.7 perceptually, so it clears AA either way,
|
|
46
|
+
// and it sits 4.2 dE2000 from the brand teal — comfortably past the ~2.3
|
|
47
|
+
// just-noticeable difference, though a shallower step than the base theme's
|
|
48
|
+
// own blue-900 to blue-700 hover (12.2).
|
|
39
49
|
--mg-color-button-secondary-background: 12 117 117;
|
|
40
50
|
|
|
41
51
|
// color.button-secondary-background--hover
|
|
@@ -46,7 +56,13 @@
|
|
|
46
56
|
--mg-color-button-outline-primary: 10 105 105;
|
|
47
57
|
|
|
48
58
|
// color.button-outline-primary--hover
|
|
49
|
-
|
|
59
|
+
// Lighter partner of the brand teal. Carries the secondary button fill and,
|
|
60
|
+
// since the primary button's hover left the orange accent, that hover too.
|
|
61
|
+
// White on it is 5.5:1 and 69.7 perceptually, so it clears AA either way,
|
|
62
|
+
// and it sits 4.2 dE2000 from the brand teal — comfortably past the ~2.3
|
|
63
|
+
// just-noticeable difference, though a shallower step than the base theme's
|
|
64
|
+
// own blue-900 to blue-700 hover (12.2).
|
|
65
|
+
--mg-color-button-outline-primary--hover: 12 117 117;
|
|
50
66
|
|
|
51
67
|
// color.button-outline-secondary
|
|
52
68
|
--mg-color-button-outline-secondary: var(--mg-color-button-secondary-background);
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { mgCopyButton } from '../copy-button';
|
|
2
|
-
|
|
3
|
-
describe('mgCopyButton (Vanilla JS Script)', () => {
|
|
4
|
-
let writeTextMock;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
document.body.innerHTML = '';
|
|
8
|
-
jest.useFakeTimers();
|
|
9
|
-
|
|
10
|
-
writeTextMock = jest.fn().mockResolvedValue(undefined);
|
|
11
|
-
Object.assign(navigator, {
|
|
12
|
-
clipboard: {
|
|
13
|
-
writeText: writeTextMock,
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
jest.useRealTimers();
|
|
20
|
-
jest.restoreAllMocks();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function createButton(attrs = {}) {
|
|
24
|
-
const btn = document.createElement('button');
|
|
25
|
-
btn.type = 'button';
|
|
26
|
-
btn.className =
|
|
27
|
-
'mg-button mg-button-primary mg-button-outline mg-button--icon mg-copy-button';
|
|
28
|
-
btn.setAttribute('data-mg-copy-button', '');
|
|
29
|
-
Object.entries(attrs).forEach(([key, val]) => {
|
|
30
|
-
btn.setAttribute(`data-${key}`, val);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
btn.innerHTML = `
|
|
34
|
-
<span class="mg-icon mg-icon-copy mg-button__icon" aria-hidden="true"></span>
|
|
35
|
-
<span class="mg-copy-button__feedback" role="status" aria-hidden="true">Copied!</span>
|
|
36
|
-
<span class="mg-u-sr-only" aria-live="polite"></span>
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
document.body.appendChild(btn);
|
|
40
|
-
return btn;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
it('initializes buttons and writes to clipboard on click', async () => {
|
|
44
|
-
const btn = createButton({
|
|
45
|
-
'text-to-copy': 'https://preventionweb.net',
|
|
46
|
-
'tooltip-label': 'Copied!',
|
|
47
|
-
'copied-label': 'URL copied to clipboard.',
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
mgCopyButton(btn);
|
|
51
|
-
|
|
52
|
-
// Trigger click
|
|
53
|
-
btn.click();
|
|
54
|
-
await Promise.resolve();
|
|
55
|
-
|
|
56
|
-
expect(writeTextMock).toHaveBeenCalledWith('https://preventionweb.net');
|
|
57
|
-
expect(btn.classList.contains('mg-copy-button--copied')).toBe(true);
|
|
58
|
-
|
|
59
|
-
const feedback = btn.querySelector('.mg-copy-button__feedback');
|
|
60
|
-
expect(
|
|
61
|
-
feedback.classList.contains('mg-copy-button__feedback--visible')
|
|
62
|
-
).toBe(true);
|
|
63
|
-
expect(feedback.textContent).toBe('Copied!');
|
|
64
|
-
|
|
65
|
-
const liveRegion = btn.querySelector('.mg-u-sr-only');
|
|
66
|
-
expect(liveRegion.textContent).toBe('URL copied to clipboard.');
|
|
67
|
-
|
|
68
|
-
// Fast-forward 2 seconds
|
|
69
|
-
jest.advanceTimersByTime(2000);
|
|
70
|
-
|
|
71
|
-
expect(btn.classList.contains('mg-copy-button--copied')).toBe(false);
|
|
72
|
-
expect(
|
|
73
|
-
feedback.classList.contains('mg-copy-button__feedback--visible')
|
|
74
|
-
).toBe(false);
|
|
75
|
-
expect(liveRegion.textContent).toBe('');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('guards against duplicate initialization', () => {
|
|
79
|
-
const btn = createButton({ 'text-to-copy': 'Single' });
|
|
80
|
-
|
|
81
|
-
mgCopyButton(btn);
|
|
82
|
-
expect(btn.dataset.mgCopyButtonInitialized).toBe('true');
|
|
83
|
-
|
|
84
|
-
// Second call does not double-bind
|
|
85
|
-
mgCopyButton(btn);
|
|
86
|
-
expect(btn.dataset.mgCopyButtonInitialized).toBe('true');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('supports initializing via container elements or document', async () => {
|
|
90
|
-
const container = document.createElement('div');
|
|
91
|
-
container.innerHTML = `
|
|
92
|
-
<div class="card">
|
|
93
|
-
<button type="button" class="mg-button mg-copy-button" data-mg-copy-button data-text-to-copy="https://nested.org">
|
|
94
|
-
<span class="mg-icon mg-icon-copy mg-button__icon"></span>
|
|
95
|
-
<span class="mg-copy-button__feedback">Copied!</span>
|
|
96
|
-
<span class="mg-u-sr-only"></span>
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
99
|
-
`;
|
|
100
|
-
document.body.appendChild(container);
|
|
101
|
-
|
|
102
|
-
mgCopyButton(container);
|
|
103
|
-
|
|
104
|
-
const btn = container.querySelector('[data-mg-copy-button]');
|
|
105
|
-
expect(btn.dataset.mgCopyButtonInitialized).toBe('true');
|
|
106
|
-
|
|
107
|
-
btn.click();
|
|
108
|
-
await Promise.resolve();
|
|
109
|
-
|
|
110
|
-
expect(writeTextMock).toHaveBeenCalledWith('https://nested.org');
|
|
111
|
-
});
|
|
112
|
-
});
|