@repobit/dex-system-design 1.0.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/README.md +43 -12
- package/package.json +6 -4
- package/schemas/component-manifest.json +207 -49
- package/schemas/manifest-omissions.json +2 -0
- package/src/components/accordion/accordion.css.js +12 -1
- package/src/components/agent-endpoints/agent-endpoints.js +1 -1
- package/src/components/alert/alert.css.js +22 -1
- package/src/components/anchor/anchor-nav.css.js +6 -1
- package/src/components/annotation/annotation.css.js +6 -1
- package/src/components/architecture.stories.js +6 -4
- package/src/components/avatar/avatar.js +37 -11
- package/src/components/avatar/avatar.stories.js +10 -6
- package/src/components/awards/awards-flex.css.js +31 -129
- package/src/components/awards/awards-flex.js +66 -116
- package/src/components/awards/awards-flex.stories.js +8 -6
- package/src/components/back/back.css.js +6 -1
- package/src/components/back/back.js +24 -12
- package/src/components/back/back.stories.js +3 -3
- package/src/components/badge/badge.js +6 -3
- package/src/components/badge/badge.stories.js +13 -9
- package/src/components/bento-grid/bento-grid.js +1 -1
- package/src/components/blockquote/blockquote.css.js +12 -3
- package/src/components/blockquote/blockquote.js +12 -5
- package/src/components/blockquote/blockquote.stories.js +6 -4
- package/src/components/button/button.css.js +46 -25
- package/src/components/button/button.js +44 -43
- package/src/components/button/button.stories.js +2 -2
- package/src/components/caption/caption.css.js +10 -0
- package/src/components/caption/caption.stories.js +4 -4
- package/src/components/carousel/carousel.css.js +19 -116
- package/src/components/carousel/carousel.js +70 -74
- package/src/components/carousel/carousel.stories.js +9 -8
- package/src/components/checkbox/checkbox.css.js +6 -1
- package/src/components/code/code.css.js +6 -1
- package/src/components/code/code.js +48 -7
- package/src/components/code/code.stories.js +7 -4
- package/src/components/divider/divider-horizontal.js +11 -3
- package/src/components/divider/divider-vertical.js +12 -5
- package/src/components/divider/divider.css.js +4 -1
- package/src/components/divider/divider.stories.js +6 -6
- package/src/components/dropdown/dropdown.js +67 -13
- package/src/components/dropdown/dropdown.stories.js +15 -14
- package/src/components/fieldset/fieldset.stories.js +41 -16
- package/src/components/footer/footer-nav-menu.css.js +6 -1
- package/src/components/footer/footer.css.js +6 -1
- package/src/components/header/header-code.js +1 -1
- package/src/components/header/header.css.js +9 -4
- package/src/components/heading/heading.stories.js +64 -31
- package/src/components/highlight/highlight.js +74 -34
- package/src/components/highlight/highlight.stories.js +5 -4
- package/src/components/icons/icon-element.js +49 -6
- package/src/components/icons/icons.stories.js +7 -0
- package/src/components/image/image.css.js +9 -98
- package/src/components/image/image.js +7 -2
- package/src/components/input/custom-form.stories.js +14 -19
- package/src/components/input/input-clipboard.css.js +6 -1
- package/src/components/input/input-clipboard.js +56 -8
- package/src/components/input/input.js +19 -2
- package/src/components/label/label.js +87 -1
- package/src/components/label/label.stories.js +21 -12
- package/src/components/link/link.css.js +52 -3
- package/src/components/link/link.js +52 -17
- package/src/components/link/link.stories.js +114 -3
- package/src/components/list/list-item/list-item.css.js +4 -6
- package/src/components/list/list-item/list-item.js +53 -5
- package/src/components/list/list-item/list-item.stories.js +22 -0
- package/src/components/list/list.js +27 -0
- package/src/components/modal/modal.css.js +6 -1
- package/src/components/pagination-dots/pagination-dots.css.js +161 -0
- package/src/components/pagination-dots/pagination-dots.js +202 -0
- package/src/components/pagination-dots/pagination-dots.stories.js +171 -0
- package/src/components/paragraph/paragraph.css.js +37 -1
- package/src/components/paragraph/paragraph.js +19 -2
- package/src/components/paragraph/paragraph.stories.js +40 -6
- package/src/components/picture/picture.css.js +8 -67
- package/src/components/picture/picture.js +15 -8
- package/src/components/pricing-cards/pricing-card.css.js +12 -2
- package/src/components/pricing-cards/pricing-card.js +4 -4
- package/src/components/progress/progress.js +24 -4
- package/src/components/progress/progress.stories.js +8 -6
- package/src/components/radio/radio.css.js +6 -1
- package/src/components/radio/radio.js +48 -3
- package/src/components/radio/radio.stories.js +1 -1
- package/src/components/shared/form-field.css.js +30 -4
- package/src/components/shared/token-scales.js +16 -0
- package/src/components/spinner/spinner.css.js +31 -4
- package/src/components/spinner/spinner.js +26 -4
- package/src/components/spinner/spinner.stories.js +10 -7
- package/src/components/switch/switch.css.js +21 -3
- package/src/components/switch/switch.js +8 -1
- package/src/components/switch/switch.stories.js +2 -1
- package/src/components/tab-list/tab-list.css.js +6 -1
- package/src/components/terminal/terminal-cursor.js +11 -0
- package/src/components/terminal/terminal-message.js +1 -1
- package/src/components/termsOfUse/terms.css.js +6 -1
- package/src/components/textarea/textarea.stories.js +23 -0
- package/src/components/tooltip/tooltip.css.js +24 -1
- package/src/components/tooltip/tooltip.js +135 -5
- package/src/components/tooltip/tooltip.stories.js +20 -14
- package/src/components/trust-panel/trust-panel.js +2 -2
- package/src/components/video/video.js +38 -3
- package/src/components/video/video.stories.js +10 -5
- package/src/components/video-player/video-player.css.js +6 -1
- package/src/tokens/elevation.stories.js +2 -3
- package/src/tokens/focus.stories.js +81 -15
- package/src/tokens/motion.stories.js +0 -6
- package/src/tokens/tokens.css +174 -97
- package/src/tokens/tokens.js +174 -97
- package/src/tokens/vertical-rhythm.stories.js +1 -1
- package/src/stories/Button.js +0 -20
- package/src/stories/Button.stories.js +0 -46
- package/src/stories/Configure.mdx +0 -364
- package/src/stories/Header.js +0 -45
- package/src/stories/Header.stories.js +0 -24
- package/src/stories/Page.js +0 -62
- package/src/stories/Page.stories.js +0 -20
- package/src/stories/assets/accessibility.png +0 -0
- package/src/stories/assets/accessibility.svg +0 -1
- package/src/stories/assets/addon-library.png +0 -0
- package/src/stories/assets/assets.png +0 -0
- package/src/stories/assets/avif-test-image.avif +0 -0
- package/src/stories/assets/context.png +0 -0
- package/src/stories/assets/discord.svg +0 -1
- package/src/stories/assets/docs.png +0 -0
- package/src/stories/assets/figma-plugin.png +0 -0
- package/src/stories/assets/github.svg +0 -1
- package/src/stories/assets/share.png +0 -0
- package/src/stories/assets/styling.png +0 -0
- package/src/stories/assets/testing.png +0 -0
- package/src/stories/assets/theming.png +0 -0
- package/src/stories/assets/tutorials.svg +0 -1
- package/src/stories/assets/youtube.svg +0 -1
- package/src/stories/button.css +0 -30
- package/src/stories/header.css +0 -32
- package/src/stories/page.css +0 -68
|
@@ -23,7 +23,7 @@ there is no image.
|
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| \`src\` | image URL — omit to fall back to initials | \`''\` |
|
|
25
25
|
| \`name\` | the person's name | \`''\` |
|
|
26
|
-
| \`size\` | \`
|
|
26
|
+
| \`size\` | \`sm\`, \`md\`, \`lg\` — \`small\` / \`default\` / \`large\` still resolve | \`md\` |
|
|
27
27
|
|
|
28
28
|
### Always pass a name
|
|
29
29
|
\`name\` does two jobs. It is the source of the initials when there is no \`src\`,
|
|
@@ -31,9 +31,13 @@ and it is the accessible name in both cases — the wrapper carries
|
|
|
31
31
|
\`role="img"\` with \`aria-label\` from \`name\`, and the inner image is given an
|
|
32
32
|
empty \`alt\` so it is not announced twice.
|
|
33
33
|
|
|
34
|
-
Without a \`name\` the
|
|
35
|
-
|
|
36
|
-
an image.
|
|
34
|
+
Without a \`name\` the avatar has nothing to say, so it is hidden from assistive
|
|
35
|
+
technology rather than announced — it used to be an image named "Avatar", which
|
|
36
|
+
tells a screen reader user nothing. Pass one even when you have an image.
|
|
37
|
+
|
|
38
|
+
### When the image does not load
|
|
39
|
+
A \`src\` that fails to load falls back to the initials, the same as no \`src\`
|
|
40
|
+
at all. A new \`src\` gets its own chance to load.
|
|
37
41
|
|
|
38
42
|
### How the initials are derived
|
|
39
43
|
The first letter of each whitespace-separated word, capped at two characters
|
|
@@ -57,9 +61,9 @@ first and last.
|
|
|
57
61
|
},
|
|
58
62
|
size: {
|
|
59
63
|
control : { type: 'select' },
|
|
60
|
-
options : ['
|
|
64
|
+
options : ['sm', 'md', 'lg'],
|
|
61
65
|
description: 'Diameter of the avatar.',
|
|
62
|
-
table : { type: { summary: 'string' }, defaultValue: { summary:
|
|
66
|
+
table : { type: { summary: 'string' }, defaultValue: { summary: 'md' }, category: 'Appearance' }
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
};
|
|
@@ -203,136 +203,21 @@ export const awardsFlexCSS = css`
|
|
|
203
203
|
margin: 0;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
/* ===
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
.bd-awardsflex-dots {
|
|
216
|
-
display: flex;
|
|
217
|
-
/* The buttons below carry their own 24px of target, so the space between
|
|
218
|
-
two dots now comes from the boxes rather than from a gap. Adding one on
|
|
219
|
-
top would push them apart without making either easier to hit. */
|
|
220
|
-
gap: 0;
|
|
206
|
+
/* === SLIDE CONTROLS ===
|
|
207
|
+
bd-pagination-dots, which also runs the autoplay clock. This file used to
|
|
208
|
+
draw its own dots and pause button -- a dot row that called itself a
|
|
209
|
+
tablist with no tabpanels, resting dots at neutral-300 (2.10:1 on the
|
|
210
|
+
tint, under the 3:1 a control needs), and a setInterval the dots knew
|
|
211
|
+
nothing about. The atom owns all of that now; the margin is all that is
|
|
212
|
+
left to decide here. */
|
|
213
|
+
bd-pagination-dots {
|
|
221
214
|
margin-top: var(--spacing-8);
|
|
222
215
|
}
|
|
223
216
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
border: var(--border-width-1) solid var(--color-border-default);
|
|
229
|
-
border-radius: var(--radius-full);
|
|
230
|
-
color: var(--color-text-muted);
|
|
231
|
-
font-family: var(--typography-fontFamily-sans);
|
|
232
|
-
/* no role: four roles sit at this size and each fails differently, so the
|
|
233
|
-
size stays a primitive. Note the weight is not declared here at all --
|
|
234
|
-
it inherits 400 -- which is why two of these are about what adopting a
|
|
235
|
-
role would ADD.
|
|
236
|
-
.
|
|
237
|
-
caption-default matches every value exactly, including the colour token:
|
|
238
|
-
xs / 400 / 1.5 / tracking 0 / sans, and its own colour axis is
|
|
239
|
-
--color-text-muted, which is what this rule already declares two lines
|
|
240
|
-
up. It renders as the same pixels. It is declined anyway, because
|
|
241
|
-
vertical-rhythm.stories.js documents it as "text under an image or a
|
|
242
|
-
figure" and this is the visible label of a toggle button. A
|
|
243
|
-
five-axis-plus-colour identity is the most seductive form of choosing a
|
|
244
|
-
role by its value rather than its meaning -- not an exemption from
|
|
245
|
-
CLAUDE.md's rule against that, but the case the rule is for.
|
|
246
|
-
.
|
|
247
|
-
caption-small carries the same wrong meaning and adds
|
|
248
|
-
letterSpacing-wide, which has no job on a five-letter word.
|
|
249
|
-
.
|
|
250
|
-
label-small is the right family -- "a small label: a badge, a tag, a
|
|
251
|
-
field hint" -- but the wrong result: it would declare a weight of 500
|
|
252
|
-
where nothing is declared today, and add wide tracking, making the
|
|
253
|
-
control bolder and 2.2px wider. This control is deliberately recessive:
|
|
254
|
-
muted, 2px of vertical padding, sitting beside the carousel dots as
|
|
255
|
-
chrome whose job is not to compete with the quote cards. Adopting a role
|
|
256
|
-
should not invert that.
|
|
257
|
-
.
|
|
258
|
-
control-label-small is the right meaning -- built for the name of a
|
|
259
|
-
control -- and fails on the leading. See the line below. */
|
|
260
|
-
font-size: var(--typography-fontSize-xs);
|
|
261
|
-
/* no role: and this leading in particular cannot move. The line box is
|
|
262
|
-
18px, which with 2px of padding and a 1px border makes the button
|
|
263
|
-
exactly 24px -- the same 24px as the .bd-awardsflex-dot buttons it shares
|
|
264
|
-
a row with, which are 24 square deliberately for the WCAG 2.5.8 target
|
|
265
|
-
size the comment further down explains.
|
|
266
|
-
.
|
|
267
|
-
control-label-small would take the line box to 15px and the button to
|
|
268
|
-
21px, misaligning it from the dots. Its tight leading exists for controls
|
|
269
|
-
whose height is DERIVED from padding + fontSize x lineHeight, the way
|
|
270
|
-
form-field computes one. This button's height is not derived -- it is
|
|
271
|
-
matched to a sibling. So the constraint is a pixel alignment between two
|
|
272
|
-
controls in one row, and nothing else in this file says so: a later pass
|
|
273
|
-
adopting control-label-small on meaning alone would quietly break the
|
|
274
|
-
row. */
|
|
275
|
-
line-height: var(--typography-lineHeight-normal);
|
|
276
|
-
cursor: pointer;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
.bd-awardsflex-autoplay-toggle:hover {
|
|
280
|
-
color: var(--color-text-primary);
|
|
281
|
-
border-color: var(--color-border-strong);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.bd-awardsflex-autoplay-toggle:focus-visible {
|
|
285
|
-
/* the one focus ring — see Foundations/Focus */
|
|
286
|
-
outline: none;
|
|
287
|
-
box-shadow: var(--focus-outline-primary);
|
|
288
|
-
border-radius: var(--focus-outline-radius);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/* An 8px dot was an 8px target, which is a third of what WCAG 2.5.8 asks and
|
|
292
|
-
close enough to its neighbour that the 24px circles would have overlapped
|
|
293
|
-
as well. The button is the target and the dot is only what you see, so the
|
|
294
|
-
button is 24 square and the dot is drawn inside it at the size it always
|
|
295
|
-
was. Nothing about the component looks bigger; the part you can hit does. */
|
|
296
|
-
.bd-awardsflex-dot {
|
|
297
|
-
width: var(--dimension-24);
|
|
298
|
-
height: var(--dimension-24);
|
|
299
|
-
display: grid;
|
|
300
|
-
place-items: center;
|
|
301
|
-
border: none;
|
|
302
|
-
background: transparent;
|
|
303
|
-
padding: 0;
|
|
304
|
-
cursor: pointer;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
.bd-awardsflex-dot::before {
|
|
308
|
-
content: "";
|
|
309
|
-
width: var(--spacing-8);
|
|
310
|
-
height: var(--spacing-8);
|
|
311
|
-
border-radius: 50%;
|
|
312
|
-
background: var(--color-neutral-300);
|
|
313
|
-
transition: background var(--transition-duration-normal) var(--transition-easing-ease),
|
|
314
|
-
transform var(--transition-duration-normal) var(--transition-easing-ease);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
.bd-awardsflex-dot.active::before {
|
|
318
|
-
/* The current slide's mark, and it takes the indicator role now that the
|
|
319
|
-
role is not named for a shape. --indicator-color IS blue-500, so nothing
|
|
320
|
-
moves; what changes is that this dot and the tab/nav underlines can no
|
|
321
|
-
longer drift apart, which is the reason the role exists.
|
|
322
|
-
.
|
|
323
|
-
The geometry stays local on purpose: an underline slides its width, a dot
|
|
324
|
-
scales, so --indicator-underline-height and its transition are not this
|
|
325
|
-
component's to borrow. */
|
|
326
|
-
background: var(--indicator-color);
|
|
327
|
-
transform: scale(1.2);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
.bd-awardsflex-dot:focus-visible {
|
|
331
|
-
/* the one focus ring — see Foundations/Focus. It follows the target, not
|
|
332
|
-
the dot, so keyboard focus is visible at the size the button really is. */
|
|
333
|
-
outline: none;
|
|
334
|
-
box-shadow: var(--focus-outline-primary);
|
|
335
|
-
border-radius: var(--radius-full);
|
|
217
|
+
@media (prefers-reduced-motion: reduce) {
|
|
218
|
+
.bd-awardsflex-carousel-track {
|
|
219
|
+
transition: none;
|
|
220
|
+
}
|
|
336
221
|
}
|
|
337
222
|
|
|
338
223
|
@media ${unsafeCSS(below('sm'))} {
|
|
@@ -342,12 +227,29 @@ export const awardsFlexCSS = css`
|
|
|
342
227
|
min-height: var(--bd-awardsflex-min-height-mobile, 280px);
|
|
343
228
|
}
|
|
344
229
|
|
|
345
|
-
.
|
|
346
|
-
|
|
230
|
+
/* The static row stacks. The carousel slide does not: below sm it holds
|
|
231
|
+
one card, and in a column the card's 200px flex-basis became its
|
|
232
|
+
height. In the row it fills the width and takes its own height. */
|
|
233
|
+
.bd-awardsflex-quotes-row {
|
|
347
234
|
flex-direction: column;
|
|
348
235
|
align-items: center;
|
|
349
236
|
}
|
|
350
237
|
|
|
238
|
+
/* A stacked card is as tall as what it holds. In the column, the card's
|
|
239
|
+
200px flex-basis became its height and the 160px min-height had nothing
|
|
240
|
+
left to do -- it keeps cards that stand side by side the same height,
|
|
241
|
+
and stacked cards stand side by side with nothing. Measured at 390:
|
|
242
|
+
200px cards whose content ended at 111 and 132. The width is the
|
|
243
|
+
column's, so a short quote no longer hugs its source line.
|
|
244
|
+
.
|
|
245
|
+
The carousel slide keeps the min-height: there it stops the section
|
|
246
|
+
jumping in height from one slide to the next. */
|
|
247
|
+
.bd-awardsflex-quotes-row .bd-awardsflex-quote-card {
|
|
248
|
+
flex: none;
|
|
249
|
+
width: 100%;
|
|
250
|
+
min-height: 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
351
253
|
.bd-awardsflex-quote-card {
|
|
352
254
|
max-width: none;
|
|
353
255
|
}
|
|
@@ -10,11 +10,19 @@ import "../icons/pc-mag-readers-icon.js";
|
|
|
10
10
|
import "../icons/top-product-icon.js";
|
|
11
11
|
import "../heading/heading.js";
|
|
12
12
|
import "../paragraph/paragraph.js";
|
|
13
|
+
import "../pagination-dots/pagination-dots.js";
|
|
13
14
|
import { awardsFlexCSS } from "./awards-flex.css.js";
|
|
14
15
|
import { ensembleCSS } from "../shared/ensemble.css.js";
|
|
16
|
+
import { below } from "../shared/token-scales.js";
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
/* How many quote cards one carousel slide holds: three side by side, and one
|
|
19
|
+
below sm. Three used to stack in a column on a phone, so a slide was about
|
|
20
|
+
1065px tall with every card on screen at once, and the dots paged between
|
|
21
|
+
two such stacks. One card a slide gives the phone a carousel again -- six
|
|
22
|
+
awards are six slides and six dots -- at about half the height. Drawn in the
|
|
23
|
+
foundation Figma file as Awards carousel, breakpoint=mobile. */
|
|
24
|
+
const GROUP_SIZE = 3;
|
|
25
|
+
const GROUP_SIZE_NARROW = 1;
|
|
18
26
|
|
|
19
27
|
export class AwardsFlex extends LitElement {
|
|
20
28
|
static properties = {
|
|
@@ -27,9 +35,10 @@ export class AwardsFlex extends LitElement {
|
|
|
27
35
|
subtext : { type: String },
|
|
28
36
|
badges : { type: Array },
|
|
29
37
|
_slide : { state: true },
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
_groupSize : { state: true },
|
|
39
|
+
_hovered : { state: true },
|
|
40
|
+
_focusWithin : { state: true },
|
|
41
|
+
_live : { state: true }
|
|
33
42
|
};
|
|
34
43
|
|
|
35
44
|
static styles = [tokens, ensembleCSS, awardsFlexCSS];
|
|
@@ -45,33 +54,43 @@ export class AwardsFlex extends LitElement {
|
|
|
45
54
|
this.subtext = "";
|
|
46
55
|
this.badges = [];
|
|
47
56
|
this._slide = 0;
|
|
48
|
-
this.
|
|
49
|
-
/*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
54
|
-
|
|
57
|
+
this._groupSize = GROUP_SIZE;
|
|
58
|
+
/* The pointer or focus on the slides holds the autoplay -- a hold, which
|
|
59
|
+
lifts by itself. Stopping it for good is the atom's: a visitor who picks
|
|
60
|
+
a dot has chosen a slide, and it stays. */
|
|
61
|
+
this._hovered = false;
|
|
62
|
+
this._focusWithin = false;
|
|
63
|
+
/* The slides' live region. Off while they change on their own, so a
|
|
64
|
+
screen reader is not read a new set of quotes every few seconds; polite
|
|
65
|
+
once they are still, so a change the visitor asks for is announced.
|
|
66
|
+
bd-pagination-dots says which, through bd-toggle. */
|
|
67
|
+
this._live = "polite";
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
connectedCallback() {
|
|
58
71
|
super.connectedCallback();
|
|
59
|
-
/*
|
|
60
|
-
|
|
61
|
-
read once, so a reader who changes the setting is honoured live. */
|
|
72
|
+
/* Watched rather than read once: a slide is a layout, and a phone turned
|
|
73
|
+
sideways crosses the breakpoint. */
|
|
62
74
|
if (typeof window !== "undefined" && window.matchMedia) {
|
|
63
|
-
this.
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
75
|
+
this._narrowQuery = window.matchMedia(below("sm"));
|
|
76
|
+
this._onNarrowChange = () => this._setGroupSize(this._narrowQuery.matches ? GROUP_SIZE_NARROW : GROUP_SIZE);
|
|
77
|
+
this._narrowQuery.addEventListener("change", this._onNarrowChange);
|
|
78
|
+
this._onNarrowChange();
|
|
67
79
|
}
|
|
68
|
-
this._maybeStartAutoplay();
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
disconnectedCallback() {
|
|
72
83
|
super.disconnectedCallback();
|
|
73
|
-
this.
|
|
74
|
-
|
|
84
|
+
this._narrowQuery?.removeEventListener("change", this._onNarrowChange);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Changing the slide size keeps the visitor on the slide that holds the card
|
|
88
|
+
they were looking at: its first card, re-paged. */
|
|
89
|
+
_setGroupSize(size) {
|
|
90
|
+
if (size === this._groupSize) return;
|
|
91
|
+
const firstCard = this._slide * this._groupSize;
|
|
92
|
+
this._groupSize = size;
|
|
93
|
+
this._slide = Math.floor(firstCard / size);
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
updated(changed) {
|
|
@@ -80,23 +99,20 @@ export class AwardsFlex extends LitElement {
|
|
|
80
99
|
changed.has("carousel") ||
|
|
81
100
|
changed.has("layout") ||
|
|
82
101
|
changed.has("badges") ||
|
|
83
|
-
changed.has("
|
|
84
|
-
changed.has("_userStopped") ||
|
|
85
|
-
changed.has("_reduceMotion")
|
|
102
|
+
changed.has("_groupSize")
|
|
86
103
|
) {
|
|
87
104
|
// if the badge set shrank, make sure the current slide index is still valid
|
|
88
105
|
if (this._slide >= this._slidesCount) {
|
|
89
106
|
this._slide = 0;
|
|
90
107
|
}
|
|
91
|
-
this._maybeStartAutoplay();
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
110
|
|
|
95
111
|
get _groups() {
|
|
96
|
-
// chunk badges into
|
|
112
|
+
// chunk badges into slides of _groupSize for the carousel
|
|
97
113
|
const groups = [];
|
|
98
|
-
for (let i = 0; i < this.badges.length; i +=
|
|
99
|
-
groups.push(this.badges.slice(i, i +
|
|
114
|
+
for (let i = 0; i < this.badges.length; i += this._groupSize) {
|
|
115
|
+
groups.push(this.badges.slice(i, i + this._groupSize));
|
|
100
116
|
}
|
|
101
117
|
return groups;
|
|
102
118
|
}
|
|
@@ -105,52 +121,11 @@ export class AwardsFlex extends LitElement {
|
|
|
105
121
|
return this.layout === "quotes" ? this._groups.length : 0;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
this._stopAutoplay();
|
|
110
|
-
const canAutoplay =
|
|
111
|
-
this.autoplay &&
|
|
112
|
-
this.carousel &&
|
|
113
|
-
this.layout === "quotes" &&
|
|
114
|
-
this._slidesCount > 1 &&
|
|
115
|
-
!this._paused &&
|
|
116
|
-
!this._userStopped &&
|
|
117
|
-
!this._reduceMotion;
|
|
118
|
-
|
|
119
|
-
if (!canAutoplay) return;
|
|
120
|
-
|
|
121
|
-
this._timer = setInterval(() => {
|
|
122
|
-
this._slide = (this._slide + 1) % this._slidesCount;
|
|
123
|
-
}, Math.max(1000, this.autoplayDelay));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
_stopAutoplay() {
|
|
127
|
-
if (this._timer) {
|
|
128
|
-
clearInterval(this._timer);
|
|
129
|
-
this._timer = null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
_onMouseEnter() {
|
|
134
|
-
if (this.autoplay) this._paused = true;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
_onMouseLeave() {
|
|
138
|
-
if (this.autoplay) this._paused = false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
_toggleAutoplay() {
|
|
142
|
-
this._userStopped = !this._userStopped;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** True when the carousel is actually able to move on its own. */
|
|
124
|
+
/** True when the carousel is able to move on its own. */
|
|
146
125
|
get _autoplayOffered() {
|
|
147
126
|
return this.autoplay && this.carousel && this.layout === "quotes" && this._slidesCount > 1;
|
|
148
127
|
}
|
|
149
128
|
|
|
150
|
-
_goTo(i) {
|
|
151
|
-
this._slide = i;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
129
|
_iconHeight() {
|
|
155
130
|
if (this.layout === "grid") return 64;
|
|
156
131
|
if (this.layout === "quotes") return 56;
|
|
@@ -202,48 +177,22 @@ export class AwardsFlex extends LitElement {
|
|
|
202
177
|
`;
|
|
203
178
|
}
|
|
204
179
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
* paused it, but that is not a mechanism a keyboard or touch user can rely
|
|
209
|
-
* on, and it cannot express "leave it stopped".
|
|
210
|
-
*
|
|
211
|
-
* Its name states the action it performs, which is what a screen reader
|
|
212
|
-
* announces on focus. It sits beside the dots rather than inside them: that
|
|
213
|
-
* container is a tablist and a plain button is not a valid child of one.
|
|
214
|
-
*/
|
|
215
|
-
_renderAutoplayToggle() {
|
|
216
|
-
if (!this._autoplayOffered) return nothing;
|
|
217
|
-
const stopped = this._userStopped || this._reduceMotion;
|
|
218
|
-
return html`
|
|
219
|
-
<button
|
|
220
|
-
type="button"
|
|
221
|
-
class="bd-awardsflex-autoplay-toggle"
|
|
222
|
-
@click=${this._toggleAutoplay}
|
|
223
|
-
aria-label=${stopped ? "Play slideshow" : "Pause slideshow"}
|
|
224
|
-
>${stopped ? "Play" : "Pause"}</button>
|
|
225
|
-
`;
|
|
226
|
-
}
|
|
227
|
-
|
|
180
|
+
/* bd-pagination-dots, which runs the clock too, so the progress in the
|
|
181
|
+
current pill and the change of slide are one thing. WCAG 2.2.2's way to
|
|
182
|
+
stop it is the dots themselves: picking one stops autoplay for good. */
|
|
228
183
|
_renderDots() {
|
|
229
184
|
if (!this.carousel || this._slidesCount < 2) return nothing;
|
|
230
185
|
return html`
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
></button>
|
|
242
|
-
`
|
|
243
|
-
)}
|
|
244
|
-
</div>
|
|
245
|
-
${this._renderAutoplayToggle()}
|
|
246
|
-
</div>
|
|
186
|
+
<bd-pagination-dots
|
|
187
|
+
count=${this._slidesCount}
|
|
188
|
+
current=${this._slide + 1}
|
|
189
|
+
label="Awards"
|
|
190
|
+
?autoplay=${this._autoplayOffered}
|
|
191
|
+
delay=${this.autoplayDelay}
|
|
192
|
+
?held=${this._hovered || this._focusWithin}
|
|
193
|
+
@bd-change=${e => { this._slide = e.detail.current - 1; }}
|
|
194
|
+
@bd-toggle=${e => { this._live = e.detail.moving ? "off" : "polite"; }}
|
|
195
|
+
></bd-pagination-dots>
|
|
247
196
|
`;
|
|
248
197
|
}
|
|
249
198
|
|
|
@@ -252,18 +201,19 @@ export class AwardsFlex extends LitElement {
|
|
|
252
201
|
return html`
|
|
253
202
|
<div
|
|
254
203
|
class="bd-awardsflex-carousel-viewport"
|
|
255
|
-
@mouseenter=${this.
|
|
256
|
-
@mouseleave=${this.
|
|
257
|
-
@focusin=${this.
|
|
258
|
-
@focusout=${this.
|
|
204
|
+
@mouseenter=${() => { this._hovered = true; }}
|
|
205
|
+
@mouseleave=${() => { this._hovered = false; }}
|
|
206
|
+
@focusin=${() => { this._focusWithin = true; }}
|
|
207
|
+
@focusout=${e => { this._focusWithin = e.currentTarget.contains(e.relatedTarget); }}
|
|
259
208
|
>
|
|
260
209
|
<div
|
|
261
210
|
class="bd-awardsflex-carousel-track"
|
|
262
211
|
style="transform: translateX(${offset}%);"
|
|
212
|
+
aria-live=${this._live}
|
|
263
213
|
>
|
|
264
214
|
${this._groups.map(
|
|
265
|
-
(group) => html`
|
|
266
|
-
<div class="bd-awardsflex-carousel-slide">
|
|
215
|
+
(group, i) => html`
|
|
216
|
+
<div class="bd-awardsflex-carousel-slide" ?inert=${i !== this._slide}>
|
|
267
217
|
${group.map((a) => this._renderQuoteCard(a))}
|
|
268
218
|
</div>
|
|
269
219
|
`
|
|
@@ -22,9 +22,11 @@ Badges render real icon components from \`components/icons\` (e.g. \`<bd-pc-mag-
|
|
|
22
22
|
\`<bd-av-comparatives-icon>\`) — pass the icon's slug via the \`icon\` field on each badge
|
|
23
23
|
object, and \`AwardsFlex\` maps it to the matching \`<bd-*-icon>\` custom element internally.
|
|
24
24
|
|
|
25
|
-
When \`layout="quotes"\` and \`carousel\` is set, badges are paginated 3 per
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
When \`layout="quotes"\` and \`carousel\` is set, badges are paginated 3 per slide, and
|
|
26
|
+
1 per slide below the \`sm\` breakpoint (576px). With 6 badges that's 2 slides on a wide
|
|
27
|
+
screen and 6 on a phone — the dots follow the count. The carousel slides a full slide at
|
|
28
|
+
a time; crossing the breakpoint keeps the visitor on the slide that holds the card they
|
|
29
|
+
were looking at.
|
|
28
30
|
|
|
29
31
|
### Usage
|
|
30
32
|
\`\`\`html
|
|
@@ -45,7 +47,7 @@ of 3 cards at a time.
|
|
|
45
47
|
|-------------------|------------------------------------|-------------|----------------------------------------------------------------------|
|
|
46
48
|
| \`background\` | \`"white" \\| "tint"\` | \`"white"\` | Section background |
|
|
47
49
|
| \`layout\` | \`"compact" \\| "grid" \\| "quotes"\` | \`"compact"\` | Badge size/arrangement, or badges paired with quote cards |
|
|
48
|
-
| \`carousel\` | Boolean | \`false\` | Only for \`layout="quotes"\` —
|
|
50
|
+
| \`carousel\` | Boolean | \`false\` | Only for \`layout="quotes"\` — 3 cards a slide, 1 below \`sm\` |
|
|
49
51
|
| \`autoplay\` | Boolean | \`false\` | Auto-advance carousel pages. Requires \`carousel\` + \`layout="quotes"\` and 2+ pages (7+ badges is unnecessary — 2 pages start at 4 badges) |
|
|
50
52
|
| \`autoplay-delay\` | Number (ms) | \`4000\` | Time between auto-advances. Minimum enforced: 1000ms |
|
|
51
53
|
| \`headline\` | String | \`""\` | Optional heading |
|
|
@@ -86,7 +88,7 @@ Autoplay pauses automatically on mouse hover or keyboard focus within the carous
|
|
|
86
88
|
},
|
|
87
89
|
carousel: {
|
|
88
90
|
control : "boolean",
|
|
89
|
-
description: "Paginate quote cards 3
|
|
91
|
+
description: "Paginate quote cards 3 a slide, 1 below sm (layout=\"quotes\" only)",
|
|
90
92
|
table : { category: "Appearance" }
|
|
91
93
|
},
|
|
92
94
|
autoplay: {
|
|
@@ -170,7 +172,7 @@ const threeBadgesWithQuotes = [
|
|
|
170
172
|
}
|
|
171
173
|
];
|
|
172
174
|
|
|
173
|
-
// Six badges with quotes — needed so the carousel has 2+
|
|
175
|
+
// Six badges with quotes — needed so the carousel has 2+ slides (3 a slide, 1 below sm)
|
|
174
176
|
const sixBadgesWithQuotes = [
|
|
175
177
|
...threeBadgesWithQuotes,
|
|
176
178
|
{
|
|
@@ -47,7 +47,12 @@ export default css`
|
|
|
47
47
|
4px for as long as it was focused. That token is the default for
|
|
48
48
|
elements with no radius, which is what Foundations/Focus says. */
|
|
49
49
|
.bd-back:focus-visible {
|
|
50
|
-
|
|
50
|
+
/* forced-colors mode does not paint box-shadow, so the ring
|
|
51
|
+
vanishes there; it does paint an outline, in a system colour. This one
|
|
52
|
+
is transparent everywhere else, and the offset lands it on the ring's
|
|
53
|
+
outer band. */
|
|
54
|
+
outline: var(--border-width-2) solid transparent;
|
|
55
|
+
outline-offset: var(--focus-outline-offset);
|
|
51
56
|
box-shadow: var(--focus-outline-primary);
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -30,27 +30,39 @@ export class BdBack extends LitElement {
|
|
|
30
30
|
this.ariaLabel = null;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Without an href there is no destination to link to, only history. */
|
|
33
34
|
handleClick() {
|
|
34
|
-
|
|
35
|
-
window.location.href = this.href;
|
|
36
|
-
} else {
|
|
37
|
-
window.history.back();
|
|
38
|
-
}
|
|
35
|
+
window.history.back();
|
|
39
36
|
}
|
|
40
37
|
|
|
38
|
+
/* A real link when there is somewhere to go. With href this used to be a
|
|
39
|
+
<button> that set window.location.href on click, so the destination was
|
|
40
|
+
invisible to everything that reads links: no middle-click or
|
|
41
|
+
Cmd/Ctrl-click into a new tab, no "copy link", no status-bar URL, and a
|
|
42
|
+
screen reader announced a button and listed it under none of the page's
|
|
43
|
+
links. Without href it stays a button, because history.back() is an
|
|
44
|
+
action, not a place. */
|
|
41
45
|
render() {
|
|
42
|
-
|
|
46
|
+
const content = html`<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" aria-hidden="true" class="mi-solid mi-arrow-back" viewBox="0 0 24 24">
|
|
47
|
+
<path d="M19 11H7.83l4.88-4.88c.39-.39.39-1.03 0-1.42a.996.996 0 0 0-1.41 0l-6.59 6.59a.996.996 0 0 0 0 1.41l6.59 6.59a.996.996 0 1 0 1.41-1.41L7.83 13H19c.55 0 1-.45 1-1s-.45-1-1-1"/>
|
|
48
|
+
</svg>
|
|
49
|
+
<span class="bd-back-label">${this.label}</span>`;
|
|
50
|
+
|
|
51
|
+
return this.href
|
|
52
|
+
? html`
|
|
53
|
+
<a
|
|
54
|
+
class="bd-back"
|
|
55
|
+
href="${this.href}"
|
|
56
|
+
aria-label="${this.ariaLabel ?? nothing}"
|
|
57
|
+
>${content}</a>
|
|
58
|
+
`
|
|
59
|
+
: html`
|
|
43
60
|
<button
|
|
44
61
|
type="button"
|
|
45
62
|
class="bd-back"
|
|
46
63
|
aria-label="${this.ariaLabel ?? nothing}"
|
|
47
64
|
@click="${this.handleClick}"
|
|
48
|
-
>
|
|
49
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" aria-hidden="true" class="mi-solid mi-arrow-back" viewBox="0 0 24 24">
|
|
50
|
-
<path d="M19 11H7.83l4.88-4.88c.39-.39.39-1.03 0-1.42a.996.996 0 0 0-1.41 0l-6.59 6.59a.996.996 0 0 0 0 1.41l6.59 6.59a.996.996 0 1 0 1.41-1.41L7.83 13H19c.55 0 1-.45 1-1s-.45-1-1-1"/>
|
|
51
|
-
</svg>
|
|
52
|
-
<span class="bd-back-label">${this.label}</span>
|
|
53
|
-
</button>
|
|
65
|
+
>${content}</button>
|
|
54
66
|
`;
|
|
55
67
|
}
|
|
56
68
|
|
|
@@ -20,8 +20,8 @@ export default {
|
|
|
20
20
|
\`\`\`
|
|
21
21
|
|
|
22
22
|
### Behavior
|
|
23
|
-
- If \`href\` is provided,
|
|
24
|
-
- If \`href\` is empty,
|
|
23
|
+
- If \`href\` is provided, it renders a real link to that URL, so it opens in a new tab, can be copied, and is announced as a link
|
|
24
|
+
- If \`href\` is empty, it renders a button that calls \`window.history.back()\`
|
|
25
25
|
- Always renders a left-pointing arrow SVG icon alongside the label
|
|
26
26
|
`
|
|
27
27
|
}
|
|
@@ -69,7 +69,7 @@ export const WithHref = {
|
|
|
69
69
|
parameters: {
|
|
70
70
|
docs: {
|
|
71
71
|
description: {
|
|
72
|
-
story: '`href` is set to
|
|
72
|
+
story: '`href` is set to `/`, so it renders a link to the home page.'
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -71,12 +71,15 @@ export class BdBadge extends LitElement {
|
|
|
71
71
|
if (!this.icon || this.mode === 'dot') return '';
|
|
72
72
|
|
|
73
73
|
// currentColor makes the icon follow the tone's text colour, so there is
|
|
74
|
-
// no per-tone icon colour to keep in step
|
|
74
|
+
// no per-tone icon colour to keep in step.
|
|
75
|
+
// size is a step on the icon scale, not pixels. This said size="16", which
|
|
76
|
+
// is no step, so both icons fell back to the xl they were drawn at and a
|
|
77
|
+
// badge with an icon measured 40px tall beside 23px ones; sm is the 16.
|
|
75
78
|
switch (this.icon) {
|
|
76
79
|
case 'individual':
|
|
77
|
-
return html`<bd-individual-icon size="
|
|
80
|
+
return html`<bd-individual-icon size="sm" color="currentColor"></bd-individual-icon>`;
|
|
78
81
|
case 'family':
|
|
79
|
-
return html`<bd-family-icon size="
|
|
82
|
+
return html`<bd-family-icon size="sm" color="currentColor"></bd-family-icon>`;
|
|
80
83
|
default:
|
|
81
84
|
return '';
|
|
82
85
|
}
|