@undrr/undrr-mangrove 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +1 -1
  2. package/components/BookCard.js +1 -1
  3. package/components/Breadcrumbs.js +1 -1
  4. package/components/Checkbox.js +1 -1
  5. package/components/Chips.js +1 -1
  6. package/components/CopyButton.js +1 -1
  7. package/components/CtaButton.js +1 -1
  8. package/components/Drawer.js +2 -2
  9. package/components/EmbedContainer.js +1 -1
  10. package/components/Footer.js +1 -1
  11. package/components/FormAction.js +1 -1
  12. package/components/FormErrorSummary.js +1 -1
  13. package/components/FormGroup.js +1 -1
  14. package/components/FullWidth.js +1 -1
  15. package/components/Gallery.js +2 -2
  16. package/components/Hero.js +2 -2
  17. package/components/HighlightBox.js +1 -1
  18. package/components/HorizontalBookCard.js +1 -1
  19. package/components/HorizontalCard.js +1 -1
  20. package/components/IconCard.js +1 -1
  21. package/components/Legend.js +1 -1
  22. package/components/Loader.js +2 -2
  23. package/components/MegaMenu.js +2 -2
  24. package/components/Notice.js +1 -1
  25. package/components/PageHeader.js +1 -1
  26. package/components/Pager.js +1 -1
  27. package/components/ProgressBar.js +2 -2
  28. package/components/QuoteHighlight.js +1 -1
  29. package/components/Radio.js +1 -1
  30. package/components/Range.js +1 -1
  31. package/components/ScrollContainer.js +1 -1
  32. package/components/SegmentedControl.js +1 -1
  33. package/components/Select.js +1 -1
  34. package/components/ServiceNotice.js +1 -1
  35. package/components/ShareButtons.js +1 -1
  36. package/components/ShowMore.js +2 -2
  37. package/components/SkipLink.js +1 -1
  38. package/components/StatsCard.js +1 -1
  39. package/components/SyndicationSearchWidget.js +2 -2
  40. package/components/Tab.js +1 -1
  41. package/components/TextCta.js +1 -1
  42. package/components/TextInput.js +1 -1
  43. package/components/Textarea.js +1 -1
  44. package/components/Tree.js +1 -1
  45. package/components/UserFeedback.js +1 -1
  46. package/components/VerticalCard.js +1 -1
  47. package/components/hydrate.js +2 -2
  48. package/css/style-all.css +41 -42
  49. package/css/style-all.css.map +1 -1
  50. package/css/style-delta.css +41 -42
  51. package/css/style-delta.css.map +1 -1
  52. package/css/style-gutenberg.css +4 -4
  53. package/css/style-gutenberg.css.map +1 -1
  54. package/css/style-irp.css +41 -42
  55. package/css/style-irp.css.map +1 -1
  56. package/css/style-mcr.css +41 -42
  57. package/css/style-mcr.css.map +1 -1
  58. package/css/style-preventionweb.css +41 -42
  59. package/css/style-preventionweb.css.map +1 -1
  60. package/css/style.css +41 -42
  61. package/css/style.css.map +1 -1
  62. package/docs/EDITORIAL-MANUAL.md +1 -1
  63. package/docs/llms-motion.txt +125 -0
  64. package/error-pages/401.html +1 -1
  65. package/error-pages/403.html +1 -1
  66. package/error-pages/404.html +1 -1
  67. package/error-pages/429.html +1 -1
  68. package/error-pages/500.html +1 -1
  69. package/error-pages/502.html +1 -1
  70. package/error-pages/503.html +1 -1
  71. package/error-pages/504.html +1 -1
  72. package/error-pages/5xx.html +1 -1
  73. package/error-pages/challenge.html +1 -1
  74. package/error-pages/managed-challenge.html +1 -1
  75. package/js/cite-this.js +685 -43
  76. package/js/on-this-page-nav.js +80 -1
  77. package/js/show-more.js +149 -21
  78. package/package.json +1 -1
  79. package/scss/Atom/Images/AuthorImage/author-image.scss +9 -2
  80. package/scss/Atom/ReachElement/Details/details.scss +4 -2
  81. package/scss/Components/Cards/Card/card.scss +8 -2
  82. package/scss/Components/Cards/StatsCard/stats-card.scss +8 -1
  83. package/scss/Components/CiteThis/cite-this.scss +162 -9
  84. package/scss/Components/Gallery/gallery.scss +40 -15
  85. package/scss/Components/Hero/hero.scss +165 -0
  86. package/scss/Components/MegaMenu/mega-menu.scss +42 -121
  87. package/scss/Components/Navigation/Drawer/drawer.scss +10 -0
  88. package/scss/Components/OnThisPageNav/on-this-page-nav.scss +108 -3
  89. package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +252 -127
  90. package/scss/Components/Tab/tab.scss +2 -1
  91. package/scss/Patterns/RichContent/rich-content.scss +177 -0
  92. package/scss/Patterns/_shared/pattern-demo.scss +5 -0
  93. package/scss/Utilities/Loader/loader.scss +89 -11
  94. package/scss/Utilities/ShowMore/show-more.scss +6 -0
  95. package/scss/assets/scss/_components.scss +3 -0
  96. package/scss/assets/scss/_foundational.scss +23 -8
  97. package/scss/assets/scss/_mixins.scss +5 -0
  98. package/scss/assets/scss/_variables.scss +1 -1
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Rich content
3
+ *
4
+ * Opt-in semantic defaults for trusted editorial HTML. Classed descendants
5
+ * keep their own presentation; classless structural wrappers pass through.
6
+ *
7
+ * Full documentation:
8
+ * https://github.com/PreventionWeb/undrr-mangrove/blob/main/stories/Patterns/RichContent/RichContent.mdx
9
+ */
10
+
11
+ // The pattern's scroll wrapper is editorial structure, not a component
12
+ // island. Root classes never exclude the root's own content.
13
+ @mixin mg-editorial-element($element) {
14
+ .mg-content
15
+ #{$element}:not(
16
+ :where(
17
+ .mg-content [class]:not(.mg-content__scroll),
18
+ .mg-content [class]:not(.mg-content__scroll) *,
19
+ .mg-content
20
+ :is(
21
+ nav,
22
+ form,
23
+ fieldset,
24
+ button,
25
+ [role="navigation"],
26
+ [role="menu"],
27
+ [role="menubar"],
28
+ [role="tablist"],
29
+ [role="dialog"],
30
+ [role="toolbar"],
31
+ [role="listbox"],
32
+ [role="tree"]
33
+ ),
34
+ .mg-content
35
+ :is(
36
+ nav,
37
+ form,
38
+ fieldset,
39
+ button,
40
+ [role="navigation"],
41
+ [role="menu"],
42
+ [role="menubar"],
43
+ [role="tablist"],
44
+ [role="dialog"],
45
+ [role="toolbar"],
46
+ [role="listbox"],
47
+ [role="tree"]
48
+ )
49
+ *
50
+ )
51
+ ) {
52
+ @content;
53
+ }
54
+ }
55
+
56
+ @include mg-editorial-element(":is(p, ul, ol, dl)") {
57
+ margin-block: 0 var(--mg-spacing-150);
58
+ }
59
+
60
+ @include mg-editorial-element(":is(h1, h2, h3, h4, h5, h6)") {
61
+ margin-block: var(--mg-spacing-200) var(--mg-spacing-100);
62
+ line-height: var(--mg-font-line-height-500);
63
+ }
64
+
65
+ @include mg-editorial-element(":is(ul, ol)") {
66
+ padding-inline: 0;
67
+ padding-inline-start: var(--mg-spacing-250);
68
+ }
69
+
70
+ @include mg-editorial-element("li") {
71
+ padding-inline: 0;
72
+ margin-block-end: var(--mg-spacing-25);
73
+ }
74
+
75
+ @include mg-editorial-element("li > :is(ul, ol)") {
76
+ margin-block: var(--mg-spacing-50) 0;
77
+ }
78
+
79
+ @include mg-editorial-element(
80
+ "blockquote:not(:where(:has(.mg-highlight-box)))"
81
+ ) {
82
+ margin-block: var(--mg-spacing-200);
83
+ margin-inline: 0;
84
+ padding-inline-start: var(--mg-spacing-150);
85
+ border-inline-start: 3px solid rgb(var(--mg-color-neutral-400));
86
+ font-size: var(--mg-font-size-300);
87
+ font-weight: 400;
88
+ line-height: var(--mg-font-line-height-700);
89
+ }
90
+
91
+ @include mg-editorial-element("cite") {
92
+ display: inline;
93
+ margin: 0;
94
+ font-style: italic;
95
+ }
96
+
97
+ @include mg-editorial-element("a:not(:where([role=button]))") {
98
+ text-decoration: underline;
99
+ text-underline-offset: 0.15em;
100
+ }
101
+
102
+ @include mg-editorial-element("hr") {
103
+ margin-block: var(--mg-spacing-200);
104
+ }
105
+
106
+ @include mg-editorial-element("figure") {
107
+ margin-block: var(--mg-spacing-200);
108
+ margin-inline: 0;
109
+ }
110
+
111
+ @include mg-editorial-element("figure > img") {
112
+ display: block;
113
+ max-width: 100%;
114
+ height: auto;
115
+ padding: 0;
116
+ }
117
+
118
+ @include mg-editorial-element(":is(figcaption, caption)") {
119
+ font-size: var(--mg-font-size-250);
120
+ line-height: var(--mg-font-line-height-700);
121
+ text-align: start;
122
+ }
123
+
124
+ @include mg-editorial-element("figcaption") {
125
+ margin-block-start: var(--mg-spacing-50);
126
+ }
127
+
128
+ @include mg-editorial-element("table") {
129
+ width: 100%;
130
+ border-collapse: collapse;
131
+ margin-block: var(--mg-spacing-150);
132
+ }
133
+
134
+ @include mg-editorial-element(":is(th, td)") {
135
+ padding: var(--mg-spacing-75) var(--mg-spacing-100);
136
+ border: 1px solid rgb(var(--mg-color-neutral-400));
137
+ text-align: start;
138
+ vertical-align: top;
139
+ }
140
+
141
+ @include mg-editorial-element("caption") {
142
+ margin-block-end: var(--mg-spacing-50);
143
+ font-weight: 600;
144
+ }
145
+
146
+ @include mg-editorial-element("code:not(:where(pre code))") {
147
+ // Code is prose, not a link: use the readable text/surface pairing rather
148
+ // than interactive ink on its tinted global background in every theme.
149
+ color: rgb(var(--mg-color-text));
150
+ background: rgb(var(--mg-color-neutral-50));
151
+ }
152
+
153
+ @include mg-editorial-element("pre") {
154
+ max-width: 100%;
155
+ margin-block: var(--mg-spacing-150);
156
+ overflow-x: auto;
157
+
158
+ // Match the existing LTR code child so RTL pages can reach both ends.
159
+ direction: ltr;
160
+ unicode-bidi: isolate;
161
+ }
162
+
163
+ @include mg-editorial-element("pre:focus-visible") {
164
+ @include mg-focus-ring-inset;
165
+ }
166
+
167
+ // Only authored overflow regions become keyboard stops. Native table
168
+ // semantics remain on the table; the wrapper supplies scrolling and focus.
169
+ .mg-content .mg-content__scroll {
170
+ max-width: 100%;
171
+ overflow-x: auto;
172
+ margin-block: var(--mg-spacing-150);
173
+
174
+ &:focus-visible {
175
+ @include mg-focus-ring-inset;
176
+ }
177
+ }
@@ -53,3 +53,8 @@
53
53
  .mg-demo-article-header .mg-hero__split-grid {
54
54
  max-inline-size: var(--mg-reading-with-contents-width);
55
55
  }
56
+
57
+ // CollectionPage: space between the page's sections.
58
+ .mg-demo-collection-section {
59
+ margin-block-end: var(--mg-spacing-400);
60
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Loader / Spinner
3
3
  *
4
- * Animated loading spinner for asynchronous states, in a default size for
4
+ * Loading indicator for asynchronous states, in a default size for
5
5
  * pages and regions and a 1em inline size for text, buttons and form fields.
6
6
  *
7
7
  * Full documentation:
@@ -59,16 +59,6 @@
59
59
  width: 1em;
60
60
  }
61
61
 
62
- // Under reduced motion the site-wide reset cuts every animation to 0.01ms
63
- // and one iteration, which would freeze the spinner and make it look as if
64
- // loading has stopped. A slow, steady rotation still says "working" without
65
- // the speed. The !important is needed to outrank the reset's own
66
- // !important, and is scoped to the loader.
67
- @media (prefers-reduced-motion: reduce) {
68
- animation-duration: calc(var(--mg-loader-duration) * 3) !important;
69
- animation-iteration-count: infinite !important;
70
- }
71
-
72
62
  // Forced colours would paint every side of the border in one system colour,
73
63
  // a full ring whose rotation cannot be seen. Opt out and use system colours
74
64
  // for the arc and the track.
@@ -77,6 +67,94 @@
77
67
  border-top-color: CanvasText;
78
68
  forced-color-adjust: none;
79
69
  }
70
+
71
+ @media (prefers-reduced-motion: reduce) {
72
+ // A complete dashed ring is a deliberate pending marker, not a frozen
73
+ // frame of the asymmetric spinner. Its label carries the state meaning.
74
+ animation: none !important;
75
+ transform: none;
76
+ border-style: dashed;
77
+ border-color: rgb(var(--mg-color-interactive));
78
+
79
+ &--inline {
80
+ border-color: currentColor;
81
+ }
82
+
83
+ // The same existing markup works for React and CSS-only consumers. The
84
+ // status label joins normal flow; only the marker keeps the old size.
85
+ &:not([aria-hidden="true"]) {
86
+ display: inline-flex;
87
+ flex-direction: column;
88
+ align-items: center;
89
+ gap: var(--mg-spacing-50);
90
+ width: fit-content;
91
+ max-width: 100%;
92
+ height: auto;
93
+ border: 0;
94
+ border-radius: 0;
95
+ vertical-align: middle;
96
+ text-align: center;
97
+
98
+ &::before {
99
+ content: "";
100
+ display: block;
101
+ flex-shrink: 0;
102
+ box-sizing: border-box;
103
+ width: 40px;
104
+ max-width: 100%;
105
+ aspect-ratio: 1;
106
+ border: 8px dashed rgb(var(--mg-color-interactive));
107
+ border-radius: 50%;
108
+
109
+ @include devicebreak(medium) {
110
+ width: 96px;
111
+ border-width: 14px;
112
+ }
113
+ }
114
+
115
+ > .mg-u-sr-only {
116
+ position: static;
117
+ width: auto;
118
+ min-width: 0;
119
+ max-width: 100%;
120
+ height: auto;
121
+ margin: 0;
122
+ overflow: visible;
123
+ clip-path: none;
124
+ white-space: normal;
125
+ overflow-wrap: anywhere;
126
+ line-height: var(--mg-font-line-height-700);
127
+ }
128
+ }
129
+
130
+ &--inline:not([aria-hidden="true"]) {
131
+ flex-direction: row;
132
+ align-items: baseline;
133
+ text-align: start;
134
+
135
+ &::before {
136
+ width: 1em;
137
+ height: 1em;
138
+ border-width: max(2px, 0.125em);
139
+ border-color: currentColor;
140
+ }
141
+ }
142
+
143
+ @media (forced-colors: active) {
144
+ border-color: CanvasText;
145
+
146
+ &:not([aria-hidden="true"]) {
147
+ // Let the visible label use the user's text colours. Only the
148
+ // decorative marker opts out to keep its dashed outline distinct.
149
+ forced-color-adjust: auto;
150
+
151
+ &::before {
152
+ border-color: CanvasText;
153
+ forced-color-adjust: none;
154
+ }
155
+ }
156
+ }
157
+ }
80
158
  }
81
159
 
82
160
  // * loader end *//
@@ -32,3 +32,9 @@
32
32
  z-index: 2; // button sits above the gradient fade-out overlay — local to show-more context
33
33
  padding: mg-rem(10) mg-rem(40);
34
34
  }
35
+
36
+ // `.mg-button` sets `display`, which beats the `hidden` attribute the
37
+ // threshold option puts on a toggle with nothing to reveal.
38
+ .mg-show-more--button[hidden] {
39
+ display: none;
40
+ }
@@ -97,3 +97,6 @@
97
97
  @import "../../Components/DataViz/Legend/legend";
98
98
  @import "../../Components/Navigation/Drawer/drawer";
99
99
  @import "../../Components/Navigation/Tree/tree";
100
+
101
+ // Opt-in editorial defaults follow component styles; classed islands opt out.
102
+ @import "../../Patterns/RichContent/rich-content";
@@ -191,7 +191,10 @@ a {
191
191
  // Long-form and WYSIWYG content opt into this boundary. Navigation, cards and
192
192
  // button-like links carry their purpose through their component treatment;
193
193
  // prose links do not, so colour alone is insufficient (WCAG 1.4.1).
194
- .mg-content p a {
194
+ // Retain the reach through CMS classes, with low specificity so component
195
+ // links (including paragraph-wrapped buttons) can own their treatment.
196
+ :where(.mg-content p)
197
+ a:not(:where(.mg-button, .mg-icon-button, [role="button"])) {
195
198
  text-decoration: underline;
196
199
  }
197
200
 
@@ -206,9 +209,22 @@ a {
206
209
  }
207
210
  }
208
211
 
209
- [dir="rtl"] {
212
+ // RTL list indentation. The direction is wrapped in :where() so these rules
213
+ // carry exactly the specificity of the LTR `ul`, `ol` and `ul li` rules they
214
+ // mirror. A bare [dir="rtl"] made them outrank a component's own list reset
215
+ // (a class rule), so in RTL every unmarked component list (On this page nav,
216
+ // hero tags, breadcrumbs, pager, tabs and more) picked up the prose indent and
217
+ // sat 9-22px inside its content edge. LTR is untouched: these rules only match
218
+ // under an RTL ancestor, and the custom property is only read here.
219
+ :where([dir="rtl"]) {
210
220
  ul,
211
221
  ol {
222
+ // The item indent below aligns Arabic text with its bullets. A list
223
+ // with no bullets (mg-list-unmarked) sets this to 0 on itself; each
224
+ // list declares its own default, so a bulleted list nested inside an
225
+ // unmarked one keeps its indent.
226
+ --mg-list-item-indent-rtl: #{mg-rem(9)};
227
+
212
228
  direction: rtl;
213
229
  padding-left: 0;
214
230
  padding-right: mg-rem(11);
@@ -219,16 +235,15 @@ a {
219
235
 
220
236
  li {
221
237
  padding-left: 0;
222
- padding-right: mg-rem(9);
223
238
 
224
- @include devicebreak(medium) {
225
- // Keep RTL list item text aligned with bullets at medium
226
- // https://gitlab.com/undrr/web-backlog/-/issues/2444
227
- padding-right: mg-rem(9);
228
- }
239
+ // Keep RTL list item text aligned with bullets
240
+ // https://gitlab.com/undrr/web-backlog/-/issues/2444
241
+ padding-right: var(--mg-list-item-indent-rtl);
229
242
  }
230
243
  }
244
+ }
231
245
 
246
+ [dir="rtl"] {
232
247
  // 900 weight Roboto does not work well with Arabic
233
248
  // https://gitlab.com/undrr/web-backlog/-/issues/1711
234
249
  h1,
@@ -176,6 +176,11 @@
176
176
  // (`tree`, `group`, `menu`, `listbox`, `tablist`): that role already replaces
177
177
  // list semantics, and there is nothing to preserve.
178
178
  @mixin mg-list-unmarked {
179
+ // A list with no bullets has nothing to align its items with, so it opts
180
+ // out of the RTL item indent in _foundational.scss. Read only under
181
+ // [dir="rtl"]; no effect in LTR.
182
+ --mg-list-item-indent-rtl: 0;
183
+
179
184
  list-style: none;
180
185
  list-style-type: "";
181
186
  }
@@ -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/PreventionWeb/undrr-mangrove
7
- * Version: 2.0.1
7
+ * Version: 2.1.0
8
8
  *
9
9
  * ██╗ ██╗███╗ ██╗██████╗ ██████╗ ██████╗
10
10
  * ██║ ██║████╗ ██║██╔══██╗██╔══██╗██╔══██╗