@repobit/dex-system-design 1.0.1 → 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.
Files changed (136) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +43 -12
  3. package/package.json +4 -2
  4. package/schemas/component-manifest.json +207 -49
  5. package/schemas/manifest-omissions.json +2 -0
  6. package/src/components/accordion/accordion.css.js +12 -1
  7. package/src/components/agent-endpoints/agent-endpoints.js +1 -1
  8. package/src/components/alert/alert.css.js +22 -1
  9. package/src/components/anchor/anchor-nav.css.js +6 -1
  10. package/src/components/annotation/annotation.css.js +6 -1
  11. package/src/components/architecture.stories.js +6 -4
  12. package/src/components/avatar/avatar.js +37 -11
  13. package/src/components/avatar/avatar.stories.js +10 -6
  14. package/src/components/awards/awards-flex.css.js +31 -129
  15. package/src/components/awards/awards-flex.js +66 -116
  16. package/src/components/awards/awards-flex.stories.js +8 -6
  17. package/src/components/back/back.css.js +6 -1
  18. package/src/components/back/back.js +24 -12
  19. package/src/components/back/back.stories.js +3 -3
  20. package/src/components/badge/badge.js +6 -3
  21. package/src/components/badge/badge.stories.js +13 -9
  22. package/src/components/bento-grid/bento-grid.js +1 -1
  23. package/src/components/blockquote/blockquote.css.js +12 -3
  24. package/src/components/blockquote/blockquote.js +12 -5
  25. package/src/components/blockquote/blockquote.stories.js +6 -4
  26. package/src/components/button/button.css.js +46 -25
  27. package/src/components/button/button.js +44 -43
  28. package/src/components/button/button.stories.js +2 -2
  29. package/src/components/caption/caption.css.js +10 -0
  30. package/src/components/caption/caption.stories.js +4 -4
  31. package/src/components/carousel/carousel.css.js +19 -116
  32. package/src/components/carousel/carousel.js +70 -74
  33. package/src/components/carousel/carousel.stories.js +9 -8
  34. package/src/components/checkbox/checkbox.css.js +6 -1
  35. package/src/components/code/code.css.js +6 -1
  36. package/src/components/code/code.js +48 -7
  37. package/src/components/code/code.stories.js +7 -4
  38. package/src/components/divider/divider-horizontal.js +11 -3
  39. package/src/components/divider/divider-vertical.js +12 -5
  40. package/src/components/divider/divider.css.js +4 -1
  41. package/src/components/divider/divider.stories.js +6 -6
  42. package/src/components/dropdown/dropdown.js +67 -13
  43. package/src/components/dropdown/dropdown.stories.js +15 -14
  44. package/src/components/fieldset/fieldset.stories.js +41 -16
  45. package/src/components/footer/footer-nav-menu.css.js +6 -1
  46. package/src/components/footer/footer.css.js +6 -1
  47. package/src/components/header/header-code.js +1 -1
  48. package/src/components/header/header.css.js +9 -4
  49. package/src/components/heading/heading.stories.js +64 -31
  50. package/src/components/highlight/highlight.js +74 -34
  51. package/src/components/highlight/highlight.stories.js +5 -4
  52. package/src/components/icons/icon-element.js +49 -6
  53. package/src/components/icons/icons.stories.js +7 -0
  54. package/src/components/image/image.css.js +9 -98
  55. package/src/components/image/image.js +7 -2
  56. package/src/components/input/custom-form.stories.js +14 -19
  57. package/src/components/input/input-clipboard.css.js +6 -1
  58. package/src/components/input/input-clipboard.js +56 -8
  59. package/src/components/input/input.js +19 -2
  60. package/src/components/label/label.js +87 -1
  61. package/src/components/label/label.stories.js +21 -12
  62. package/src/components/link/link.css.js +52 -3
  63. package/src/components/link/link.js +52 -17
  64. package/src/components/link/link.stories.js +114 -3
  65. package/src/components/list/list-item/list-item.css.js +4 -6
  66. package/src/components/list/list-item/list-item.js +53 -5
  67. package/src/components/list/list-item/list-item.stories.js +22 -0
  68. package/src/components/list/list.js +27 -0
  69. package/src/components/modal/modal.css.js +6 -1
  70. package/src/components/pagination-dots/pagination-dots.css.js +161 -0
  71. package/src/components/pagination-dots/pagination-dots.js +202 -0
  72. package/src/components/pagination-dots/pagination-dots.stories.js +171 -0
  73. package/src/components/paragraph/paragraph.css.js +37 -1
  74. package/src/components/paragraph/paragraph.js +19 -2
  75. package/src/components/paragraph/paragraph.stories.js +40 -6
  76. package/src/components/picture/picture.css.js +8 -67
  77. package/src/components/picture/picture.js +15 -8
  78. package/src/components/pricing-cards/pricing-card.css.js +12 -2
  79. package/src/components/pricing-cards/pricing-card.js +4 -4
  80. package/src/components/progress/progress.js +24 -4
  81. package/src/components/progress/progress.stories.js +8 -6
  82. package/src/components/radio/radio.css.js +6 -1
  83. package/src/components/radio/radio.js +48 -3
  84. package/src/components/radio/radio.stories.js +1 -1
  85. package/src/components/shared/form-field.css.js +30 -4
  86. package/src/components/shared/token-scales.js +16 -0
  87. package/src/components/spinner/spinner.css.js +31 -4
  88. package/src/components/spinner/spinner.js +26 -4
  89. package/src/components/spinner/spinner.stories.js +10 -7
  90. package/src/components/switch/switch.css.js +21 -3
  91. package/src/components/switch/switch.js +8 -1
  92. package/src/components/switch/switch.stories.js +2 -1
  93. package/src/components/tab-list/tab-list.css.js +6 -1
  94. package/src/components/terminal/terminal-cursor.js +11 -0
  95. package/src/components/terminal/terminal-message.js +1 -1
  96. package/src/components/termsOfUse/terms.css.js +6 -1
  97. package/src/components/textarea/textarea.stories.js +23 -0
  98. package/src/components/tooltip/tooltip.css.js +24 -1
  99. package/src/components/tooltip/tooltip.js +135 -5
  100. package/src/components/tooltip/tooltip.stories.js +20 -14
  101. package/src/components/trust-panel/trust-panel.js +2 -2
  102. package/src/components/video/video.js +38 -3
  103. package/src/components/video/video.stories.js +10 -5
  104. package/src/components/video-player/video-player.css.js +6 -1
  105. package/src/tokens/elevation.stories.js +2 -3
  106. package/src/tokens/focus.stories.js +81 -15
  107. package/src/tokens/motion.stories.js +0 -6
  108. package/src/tokens/tokens.css +174 -97
  109. package/src/tokens/tokens.js +174 -97
  110. package/src/tokens/vertical-rhythm.stories.js +1 -1
  111. package/src/stories/Button.js +0 -20
  112. package/src/stories/Button.stories.js +0 -46
  113. package/src/stories/Configure.mdx +0 -364
  114. package/src/stories/Header.js +0 -45
  115. package/src/stories/Header.stories.js +0 -24
  116. package/src/stories/Page.js +0 -62
  117. package/src/stories/Page.stories.js +0 -20
  118. package/src/stories/assets/accessibility.png +0 -0
  119. package/src/stories/assets/accessibility.svg +0 -1
  120. package/src/stories/assets/addon-library.png +0 -0
  121. package/src/stories/assets/assets.png +0 -0
  122. package/src/stories/assets/avif-test-image.avif +0 -0
  123. package/src/stories/assets/context.png +0 -0
  124. package/src/stories/assets/discord.svg +0 -1
  125. package/src/stories/assets/docs.png +0 -0
  126. package/src/stories/assets/figma-plugin.png +0 -0
  127. package/src/stories/assets/github.svg +0 -1
  128. package/src/stories/assets/share.png +0 -0
  129. package/src/stories/assets/styling.png +0 -0
  130. package/src/stories/assets/testing.png +0 -0
  131. package/src/stories/assets/theming.png +0 -0
  132. package/src/stories/assets/tutorials.svg +0 -1
  133. package/src/stories/assets/youtube.svg +0 -1
  134. package/src/stories/button.css +0 -30
  135. package/src/stories/header.css +0 -32
  136. package/src/stories/page.css +0 -68
@@ -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
- const GROUP_SIZE = 3; // how many quote cards are visible per carousel page
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
- _paused : { state: true },
31
- _userStopped : { state: true },
32
- _reduceMotion: { state: true }
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._paused = false;
49
- /* Hover/focus pause is transient. A user who presses Stop means it, and
50
- must not have it undone by the pointer leaving the carousel — so the two
51
- are separate pieces of state. */
52
- this._userStopped = false;
53
- this._reduceMotion = false;
54
- this._timer = null;
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
- /* WCAG 2.2.2: content that moves on its own must be pausable, and under
60
- prefers-reduced-motion it should not start at all. Watched rather than
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._motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
64
- this._reduceMotion = this._motionQuery.matches;
65
- this._onMotionChange = () => { this._reduceMotion = this._motionQuery.matches; };
66
- this._motionQuery.addEventListener("change", this._onMotionChange);
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._motionQuery?.removeEventListener("change", this._onMotionChange);
74
- this._stopAutoplay();
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("_paused") ||
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 pages of GROUP_SIZE for the carousel
112
+ // chunk badges into slides of _groupSize for the carousel
97
113
  const groups = [];
98
- for (let i = 0; i < this.badges.length; i += GROUP_SIZE) {
99
- groups.push(this.badges.slice(i, i + GROUP_SIZE));
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
- _maybeStartAutoplay() {
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
- * The pause/stop control WCAG 2.2.2 requires of anything that starts moving
207
- * on its own and keeps going. Hovering or focusing the carousel already
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
- <div class="bd-awardsflex-controls">
232
- <div class="bd-awardsflex-dots" role="tablist" aria-label="Slide navigation">
233
- ${this._groups.map(
234
- (_, i) => html`
235
- <button
236
- class="bd-awardsflex-dot ${i === this._slide ? "active" : ""}"
237
- role="tab"
238
- aria-selected="${i === this._slide}"
239
- aria-label="Go to page ${i + 1}"
240
- @click=${() => this._goTo(i)}
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._onMouseEnter}
256
- @mouseleave=${this._onMouseLeave}
257
- @focusin=${this._onMouseEnter}
258
- @focusout=${this._onMouseLeave}
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 page. With 6
26
- badges that's 2 pages, with 9 badges 3 pages, and so on — the carousel slides a full page
27
- of 3 cards at a time.
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"\` — paginate 3 cards at a time |
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 at a time (layout=\"quotes\" only)",
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+ pages (3 cards per page)
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
- outline: none;
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
- if (this.href) {
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
- return html`
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, clicking navigates to that URL via \`window.location.href\`
24
- - If \`href\` is empty, clicking calls \`window.history.back()\`
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 `/`. Clicking navigates to the home page via `window.location.href`.'
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="16" color="currentColor"></bd-individual-icon>`;
80
+ return html`<bd-individual-icon size="sm" color="currentColor"></bd-individual-icon>`;
78
81
  case 'family':
79
- return html`<bd-family-icon size="16" color="currentColor"></bd-family-icon>`;
82
+ return html`<bd-family-icon size="sm" color="currentColor"></bd-family-icon>`;
80
83
  default:
81
84
  return '';
82
85
  }
@@ -1,7 +1,7 @@
1
1
  import { html } from 'lit';
2
2
  import './badge.js';
3
3
 
4
- const TONES = ['default', 'primary', 'success', 'warning', 'error', 'info'];
4
+ const TONES = ['neutral', 'brand', 'success', 'warning', 'error', 'info'];
5
5
  const SIZES = ['sm', 'md', 'lg'];
6
6
  const VARIANTS = ['shade', 'fill'];
7
7
 
@@ -35,10 +35,10 @@ matched to the token that already carries it.
35
35
  ### Usage
36
36
  \`\`\`html
37
37
  <bd-badge tone="success" variant="shade">Save 44%</bd-badge>
38
- <bd-badge tone="primary" variant="fill" size="sm">New</bd-badge>
38
+ <bd-badge tone="brand" variant="fill" size="sm">New</bd-badge>
39
39
  <bd-badge mode="count" tone="error" variant="fill">3</bd-badge>
40
40
  <bd-badge mode="dot" tone="warning" variant="fill"></bd-badge>
41
- <bd-badge tone="primary" variant="fill" icon="family">Family Pack</bd-badge>
41
+ <bd-badge tone="brand" variant="fill" icon="family">Family Pack</bd-badge>
42
42
  \`\`\`
43
43
 
44
44
  ### Props
@@ -46,7 +46,7 @@ matched to the token that already carries it.
46
46
  |---|---|---|
47
47
  | \`mode\` | \`text\`, \`dot\`, \`count\` | \`text\` |
48
48
  | \`size\` | \`sm\`, \`md\`, \`lg\` | \`md\` |
49
- | \`tone\` | \`default\`, \`primary\`, \`success\`, \`warning\`, \`error\`, \`info\` | \`default\` |
49
+ | \`tone\` | \`neutral\`, \`brand\`, \`success\`, \`warning\`, \`error\`, \`info\` | \`neutral\` |
50
50
  | \`variant\` | \`shade\`, \`fill\` | \`shade\` |
51
51
  | \`icon\` | \`individual\`, \`family\`, \`''\` | \`''\` |
52
52
  | \`label\` | any string — accessible name for \`mode="dot"\` | tone name |
@@ -107,7 +107,7 @@ matched to the token that already carries it.
107
107
  control : { type: 'select' },
108
108
  options : TONES,
109
109
  description: 'Semantic colour pairing.',
110
- table : { type: { summary: 'string' }, defaultValue: { summary: 'default' }, category: 'Appearance' }
110
+ table : { type: { summary: 'string' }, defaultValue: { summary: 'neutral' }, category: 'Appearance' }
111
111
  },
112
112
  variant: {
113
113
  control : { type: 'inline-radio' },
@@ -135,7 +135,7 @@ matched to the token that already carries it.
135
135
  args: {
136
136
  mode : 'text',
137
137
  size : 'md',
138
- tone : 'default',
138
+ tone : 'neutral',
139
139
  variant: 'shade',
140
140
  icon : '',
141
141
  label : '',
@@ -172,7 +172,7 @@ export const Sizes = {
172
172
  render : () => html`
173
173
  ${VARIANTS.map(variant => html`
174
174
  ${row(variant, SIZES.map(size => html`
175
- <bd-badge size="${size}" tone="primary" variant="${variant}">${size}</bd-badge>
175
+ <bd-badge size="${size}" tone="brand" variant="${variant}">${size}</bd-badge>
176
176
  `))}
177
177
  `)}
178
178
  `
@@ -212,7 +212,7 @@ export const WithIcon = {
212
212
  <bd-badge tone="success" icon="family">Family Pack</bd-badge>
213
213
  `)}
214
214
  ${row('fill', html`
215
- <bd-badge variant="fill" tone="primary" icon="individual">Individual</bd-badge>
215
+ <bd-badge variant="fill" tone="brand" icon="individual">Individual</bd-badge>
216
216
  <bd-badge variant="fill" tone="success" icon="family">Family Pack</bd-badge>
217
217
  `)}
218
218
  `
@@ -220,11 +220,15 @@ export const WithIcon = {
220
220
 
221
221
  export const Legacy = {
222
222
  name : 'Legacy variants',
223
- parameters: { docs: { description: { story: 'Pre-`ds-badge` markup still renders, mapped onto the new axes.' } } },
223
+ parameters: { docs: { description: { story: 'Pre-`ds-badge` markup still renders, mapped onto the new axes: the old variants, and the two retired tone names, `default` (now `neutral`) and `primary` (now `brand`). Write the new names; these are here so a page still using the old ones is shown to work.' } } },
224
224
  render : () => html`
225
225
  ${row('mapped', html`
226
226
  <bd-badge variant="bd-light-blue">bd-light-blue</bd-badge>
227
227
  <bd-badge variant="bd-light-green">bd-light-green</bd-badge>
228
228
  `)}
229
+ ${row('tones', html`
230
+ <bd-badge tone="default">tone="default" → neutral</bd-badge>
231
+ <bd-badge tone="primary">tone="primary" → brand</bd-badge>
232
+ `)}
229
233
  `
230
234
  };
@@ -220,7 +220,7 @@ class BentoGrid extends LitElement {
220
220
  <div class="bd-bentogrid-header">
221
221
  ${this.eyebrow
222
222
  ? html`
223
- <bd-p variant="regular" class="bd-bentogrid-eyebrow">
223
+ <bd-p variant="eyebrow" class="bd-bentogrid-eyebrow">
224
224
  <slot name="eyebrow">${this.eyebrow}</slot>
225
225
  </bd-p>
226
226
  `
@@ -4,8 +4,17 @@ import { css } from 'lit';
4
4
  export const blockquoteCSS = css`
5
5
  :host { display: block; }
6
6
 
7
+ /* .quote is the outer element: the <figure> when there is an attribution,
8
+ the bare <blockquote> when there is not. The bar and the type live on it,
9
+ so the caption inherits the quotation's family and leading the way the
10
+ <footer> did when it sat inside the <blockquote> -- the rhythm the note
11
+ below is about. */
12
+ figure,
7
13
  blockquote {
8
14
  margin: 0;
15
+ }
16
+
17
+ .quote {
9
18
  padding-left: var(--spacing-16);
10
19
  border-left: var(--border-width-4) solid var(--color-border-brand);
11
20
  font-family: var(--typography-body-large-fontFamily);
@@ -15,7 +24,7 @@ export const blockquoteCSS = css`
15
24
  color: var(--color-text-primary);
16
25
  }
17
26
 
18
- footer {
27
+ figcaption {
19
28
  margin-top: var(--spacing-8);
20
29
  font-size: var(--typography-caption-default-fontSize);
21
30
  /* no role: two roles are each a near-miss, from opposite directions, and
@@ -24,8 +33,8 @@ export const blockquoteCSS = css`
24
33
  .
25
34
  label-small matches this text's size and weight exactly -- but adopting
26
35
  it would add letterSpacing-wide, which has no job on mixed-case sentence
27
- text because nothing here crowds, and would replace the 1.625 leading
28
- this inherits from the quote it attributes with 1.5. That inherited
36
+ text because nothing here crowds, and would replace the leading this
37
+ inherits from the quote it attributes with 1.5. That inherited
29
38
  rhythm is the one thing in this rule that means something: the credit
30
39
  line sits on the same measure as the quotation it belongs to, where 1.5
31
40
  merely comes attached to the role.
@@ -17,12 +17,19 @@ export class BdBlockquote extends LitElement {
17
17
  this.attribution = '';
18
18
  }
19
19
 
20
+ /* WHERE THE ATTRIBUTION GOES.
21
+ It used to be a <footer> inside the <blockquote>, and everything inside a
22
+ blockquote is the quotation, so the name of the person quoted was read as
23
+ part of what they said. The HTML spec's pattern puts the credit outside
24
+ the quote: a <figure> holding the <blockquote> and a <figcaption>. With
25
+ no attribution there is no caption, so the bare <blockquote> renders, as
26
+ before -- a figure with nothing to name it would only add an empty
27
+ "figure" for a screen reader to step through. */
20
28
  render() {
21
- return html`
22
- <blockquote cite=${this.cite || nothing}>
23
- <slot></slot>${this.attribution ? html`<footer>\u2014 ${this.attribution}</footer>` : nothing}
24
- </blockquote>
25
- `;
29
+ const quote = html`<blockquote class=${this.attribution ? nothing : 'quote'} cite=${this.cite || nothing}><slot></slot></blockquote>`;
30
+ return this.attribution
31
+ ? html`<figure class="quote">${quote}<figcaption>\u2014 ${this.attribution}</figcaption></figure>`
32
+ : quote;
26
33
  }
27
34
  }
28
35
 
@@ -28,9 +28,11 @@ optional attribution.
28
28
  | \`cite\` | URL of the source | \`''\` |
29
29
 
30
30
  ### Notes
31
- \`attribution\` renders as a \`<footer>\` inside the \`<blockquote>\`, prefixed with
32
- an em dash. That is the pairing the HTML spec recommends for a credit that
33
- belongs to the quote itself.
31
+ \`attribution\` renders as a \`<figcaption>\`, prefixed with an em dash, beside
32
+ the \`<blockquote>\` inside a \`<figure>\`. That is the HTML spec's pattern for
33
+ crediting a quotation: everything inside a \`<blockquote>\` is the quote, so the
34
+ name of the person quoted belongs outside it. Without an attribution there is
35
+ no figure, only the \`<blockquote>\`.
34
36
 
35
37
  \`cite\` maps to the native \`cite\` attribute. It is **metadata only** — browsers
36
38
  do not render it and it is not a link, so if the source should be visible or
@@ -42,7 +44,7 @@ clickable, put it in \`attribution\` or in the slotted content.
42
44
  argTypes: {
43
45
  attribution: {
44
46
  control : 'text',
45
- description: 'Who said it. Renders as a footer inside the blockquote, prefixed with an em dash.',
47
+ description: 'Who said it. Renders as a figcaption beside the blockquote in a figure, prefixed with an em dash.',
46
48
  table : { type: { summary: 'string' }, defaultValue: { summary: "" }, category: 'Content' }
47
49
  },
48
50
  cite: {