@pega/cosmos-react-work 10.0.0-build.4.2 → 10.0.0-build.4.21

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 (79) hide show
  1. package/lib/components/AppAnnouncement/AppAnnouncement.d.ts.map +1 -1
  2. package/lib/components/AppAnnouncement/AppAnnouncement.js +30 -2
  3. package/lib/components/AppAnnouncement/AppAnnouncement.js.map +1 -1
  4. package/lib/components/Article/ArticleRating.d.ts.map +1 -1
  5. package/lib/components/Article/ArticleRating.js +2 -2
  6. package/lib/components/Article/ArticleRating.js.map +1 -1
  7. package/lib/components/ArticleList/ArticleFeedback.d.ts.map +1 -1
  8. package/lib/components/ArticleList/ArticleFeedback.js +2 -2
  9. package/lib/components/ArticleList/ArticleFeedback.js.map +1 -1
  10. package/lib/components/CaseView/CaseHeader/CaseHeader.d.ts.map +1 -1
  11. package/lib/components/CaseView/CaseHeader/CaseHeader.js +1 -1
  12. package/lib/components/CaseView/CaseHeader/CaseHeader.js.map +1 -1
  13. package/lib/components/CaseView/CaseView.styles.d.ts.map +1 -1
  14. package/lib/components/CaseView/CaseView.styles.js +4 -2
  15. package/lib/components/CaseView/CaseView.styles.js.map +1 -1
  16. package/lib/components/CaseView/CaseView.types.d.ts +1 -0
  17. package/lib/components/CaseView/CaseView.types.d.ts.map +1 -1
  18. package/lib/components/CaseView/CaseView.types.js.map +1 -1
  19. package/lib/components/Details/DetailsList.d.ts.map +1 -1
  20. package/lib/components/Details/DetailsList.js +2 -2
  21. package/lib/components/Details/DetailsList.js.map +1 -1
  22. package/lib/components/GenAICoach/ActiveCases.d.ts.map +1 -1
  23. package/lib/components/GenAICoach/ActiveCases.js +2 -1
  24. package/lib/components/GenAICoach/ActiveCases.js.map +1 -1
  25. package/lib/components/GenAICoach/GenAICoach.js +2 -2
  26. package/lib/components/GenAICoach/GenAICoach.js.map +1 -1
  27. package/lib/components/GenAICoach/GenAICoach.styles.d.ts +2 -0
  28. package/lib/components/GenAICoach/GenAICoach.styles.d.ts.map +1 -1
  29. package/lib/components/GenAICoach/GenAICoach.styles.js +11 -2
  30. package/lib/components/GenAICoach/GenAICoach.styles.js.map +1 -1
  31. package/lib/components/GenAICoach/GenAIMessage.d.ts.map +1 -1
  32. package/lib/components/GenAICoach/GenAIMessage.js +15 -2
  33. package/lib/components/GenAICoach/GenAIMessage.js.map +1 -1
  34. package/lib/components/GenAICoach/GenAIMessageFeedback.d.ts.map +1 -1
  35. package/lib/components/GenAICoach/GenAIMessageFeedback.js +2 -2
  36. package/lib/components/GenAICoach/GenAIMessageFeedback.js.map +1 -1
  37. package/lib/components/GenAICoach/Questionnaire.js +2 -2
  38. package/lib/components/GenAICoach/Questionnaire.js.map +1 -1
  39. package/lib/components/InteractionNotification/InteractionNotification.d.ts +1 -1
  40. package/lib/components/InteractionNotification/InteractionNotification.d.ts.map +1 -1
  41. package/lib/components/InteractionNotification/InteractionNotification.js +2 -2
  42. package/lib/components/InteractionNotification/InteractionNotification.js.map +1 -1
  43. package/lib/components/Shortcuts/Shortcuts.d.ts.map +1 -1
  44. package/lib/components/Shortcuts/Shortcuts.js +20 -11
  45. package/lib/components/Shortcuts/Shortcuts.js.map +1 -1
  46. package/lib/components/Shortcuts/Shortcuts.styles.d.ts +16 -3
  47. package/lib/components/Shortcuts/Shortcuts.styles.d.ts.map +1 -1
  48. package/lib/components/Shortcuts/Shortcuts.styles.js +91 -47
  49. package/lib/components/Shortcuts/Shortcuts.styles.js.map +1 -1
  50. package/lib/components/Shortcuts/Shortcuts.types.d.ts +8 -2
  51. package/lib/components/Shortcuts/Shortcuts.types.d.ts.map +1 -1
  52. package/lib/components/Shortcuts/Shortcuts.types.js.map +1 -1
  53. package/lib/components/Stages/Stages.d.ts.map +1 -1
  54. package/lib/components/Stages/Stages.js +59 -39
  55. package/lib/components/Stages/Stages.js.map +1 -1
  56. package/lib/components/Stages/Stages.styles.d.ts +27 -5
  57. package/lib/components/Stages/Stages.styles.d.ts.map +1 -1
  58. package/lib/components/Stages/Stages.styles.js +693 -163
  59. package/lib/components/Stages/Stages.styles.js.map +1 -1
  60. package/lib/components/Stages/Stages.types.d.ts +6 -0
  61. package/lib/components/Stages/Stages.types.d.ts.map +1 -1
  62. package/lib/components/Stages/Stages.types.js.map +1 -1
  63. package/lib/components/Stages/index.d.ts +1 -1
  64. package/lib/components/Stages/index.d.ts.map +1 -1
  65. package/lib/components/Stages/index.js.map +1 -1
  66. package/lib/components/Tags/Tags.d.ts.map +1 -1
  67. package/lib/components/Tags/Tags.js +1 -12
  68. package/lib/components/Tags/Tags.js.map +1 -1
  69. package/lib/components/Tasks/TaskCard.d.ts.map +1 -1
  70. package/lib/components/Tasks/TaskCard.js +5 -2
  71. package/lib/components/Tasks/TaskCard.js.map +1 -1
  72. package/lib/components/Tasks/TaskList.d.ts.map +1 -1
  73. package/lib/components/Tasks/TaskList.js +24 -16
  74. package/lib/components/Tasks/TaskList.js.map +1 -1
  75. package/lib/components/Tasks/Tasks.d.ts +7 -2
  76. package/lib/components/Tasks/Tasks.d.ts.map +1 -1
  77. package/lib/components/Tasks/Tasks.js +7 -5
  78. package/lib/components/Tasks/Tasks.js.map +1 -1
  79. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
- import styled, { css } from 'styled-components';
2
- import { transparentize } from 'polished';
3
- import { calculateFontSize, DateTimeDisplay, defaultThemeProp, Icon, Text, StyledIcon, calculateForegroundColor } from '@pega/cosmos-react-core';
1
+ import styled, { css, keyframes } from 'styled-components';
2
+ import { transparentize, darken } from 'polished';
3
+ import { calculateFontSize, DateTimeDisplay, defaultThemeProp, Icon, Text, StyledIcon, calculateForegroundColor, tryCatch } from '@pega/cosmos-react-core';
4
4
  const stageClipPath = (leftNotch, rightPoint, mirrored = false) => {
5
5
  if (mirrored) {
6
6
  // RTL: mirror the LTR clip-path polygon
@@ -30,6 +30,20 @@ const stageClipPath = (leftNotch, rightPoint, mirrored = false) => {
30
30
  }
31
31
  return `clip-path: polygon(${points.join(', ')});`;
32
32
  };
33
+ const segmentedConfig = {
34
+ width: 'calc(0.8 * var(--chevron-spacing))',
35
+ coarseWidth: 'calc(1.0 * var(--chevron-spacing))'
36
+ };
37
+ const segmentedClipPath = (leftCut, rightCut, mirrored = false) => {
38
+ if (mirrored) {
39
+ const topRight = rightCut ? 'calc(100% - var(--segmented-width)) 0%' : '100% 0%';
40
+ const bottomLeft = leftCut ? 'var(--segmented-width) 100%' : '0% 100%';
41
+ return `clip-path: polygon(0% 0%, ${topRight}, 100% 100%, ${bottomLeft});`;
42
+ }
43
+ const topLeft = leftCut ? 'var(--segmented-width) 0%' : '0% 0%';
44
+ const bottomRight = rightCut ? 'calc(100% - var(--segmented-width)) 100%' : '100% 100%';
45
+ return `clip-path: polygon(${topLeft}, 100% 0%, ${bottomRight}, 0% 100%);`;
46
+ };
33
47
  // === Chevron Configuration ===
34
48
  // Centralizes spacing and border thickness via CSS custom properties set at StyledStages
35
49
  const chevronConfig = {
@@ -50,8 +64,15 @@ const chevronConfig = {
50
64
  // Equal to `width` so the point extends exactly one chevron-width beyond the stage boundary.
51
65
  offsetRight: 'calc(1.445 * var(--chevron-spacing))',
52
66
  // Coarse-pointer equivalents of offsetLeft/offsetRight, scaled to match coarseWidth/coarseSquareSize.
53
- coarseOffsetLeft: 'calc(1.2875 * var(--chevron-spacing))',
54
- coarseOffsetRight: 'calc(1.797 * var(--chevron-spacing))'
67
+ // coarseOffsetLeft must satisfy: coarseSquareSize = 2 × coarseWidth + coarseOffsetLeft
68
+ // (5 = 2 × 1.797 + 1.406) so that ::before and ::after co-locate on coarse devices.
69
+ coarseOffsetLeft: 'calc(1.406 * var(--chevron-spacing))',
70
+ coarseOffsetRight: 'calc(1.797 * var(--chevron-spacing))',
71
+ // Pre-computed flat positions for ::before notch and ::after point.
72
+ notchInsetStart: 'calc(-2.305 * var(--chevron-spacing))',
73
+ // 3.203 = exact alignment (coarseSquareSize - coarseWidth); slightly overdone default clears sub-pixel rounding on 3× displays.
74
+ coarseNotchInsetStart: 'calc(-1 * var(--stages-coarse-notch-correction) * var(--chevron-spacing))',
75
+ pointInsetEnd: '0'
55
76
  };
56
77
  // === Chevron Helpers ===
57
78
  // After team discussion we decided to use logical properties throughout — `inset-block-start`
@@ -81,11 +102,11 @@ const createChevronBaseStyles = (config, borderThicknessVar) => css `
81
102
  }
82
103
 
83
104
  &:not(:nth-of-type(-n + 2))::before {
84
- inset-inline-start: calc(-1 * ${config.width} - ${config.offsetLeft});
105
+ inset-inline-start: ${config.notchInsetStart};
85
106
  }
86
107
 
87
108
  &:not(:nth-last-of-type(-n + 2))::after {
88
- inset-inline-end: calc(-1 * ${config.width} + ${config.offsetRight});
109
+ inset-inline-end: ${config.pointInsetEnd};
89
110
  }
90
111
  `;
91
112
  const createChevronInteractiveStates = (config, borderThicknessVar, includeHover = true) => css `
@@ -137,6 +158,14 @@ export const StyledStagesWrapper = styled.div(({ theme }) => {
137
158
  --stages-wrapper-bottom-radius,
138
159
  ${theme.components.card['border-radius']}
139
160
  );
161
+ -webkit-mask-image: linear-gradient(black, black),
162
+ linear-gradient(
163
+ to right,
164
+ transparent calc(-1 * var(--mask-edge-overhang)),
165
+ black var(--stages-fade-start),
166
+ black calc(100% - var(--stages-fade-end)),
167
+ transparent calc(100% + var(--mask-edge-overhang))
168
+ );
140
169
  mask-image: linear-gradient(black, black),
141
170
  linear-gradient(
142
171
  to right,
@@ -154,6 +183,14 @@ export const StyledStagesWrapper = styled.div(({ theme }) => {
154
183
  mask-repeat: no-repeat, no-repeat;
155
184
 
156
185
  [dir='rtl'] & {
186
+ -webkit-mask-image: linear-gradient(black, black),
187
+ linear-gradient(
188
+ to right,
189
+ transparent calc(-1 * var(--mask-edge-overhang)),
190
+ black var(--stages-fade-end),
191
+ black calc(100% - var(--stages-fade-start)),
192
+ transparent calc(100% + var(--mask-edge-overhang))
193
+ );
157
194
  mask-image: linear-gradient(black, black),
158
195
  linear-gradient(
159
196
  to right,
@@ -170,6 +207,13 @@ export const StyledStagesWrapper = styled.div(({ theme }) => {
170
207
 
171
208
  @media (pointer: coarse) {
172
209
  --stages-fade-width: calc(${theme.base.spacing} * 3);
210
+ -webkit-mask-image: linear-gradient(
211
+ to right,
212
+ transparent calc(-1 * var(--mask-edge-overhang)),
213
+ black var(--stages-fade-start),
214
+ black calc(100% - var(--stages-fade-end)),
215
+ transparent calc(100% + var(--mask-edge-overhang))
216
+ );
173
217
  mask-image: linear-gradient(
174
218
  to right,
175
219
  transparent calc(-1 * var(--mask-edge-overhang)),
@@ -182,6 +226,13 @@ export const StyledStagesWrapper = styled.div(({ theme }) => {
182
226
  mask-repeat: no-repeat;
183
227
 
184
228
  [dir='rtl'] & {
229
+ -webkit-mask-image: linear-gradient(
230
+ to right,
231
+ transparent calc(-1 * var(--mask-edge-overhang)),
232
+ black var(--stages-fade-end),
233
+ black calc(100% - var(--stages-fade-start)),
234
+ transparent calc(100% + var(--mask-edge-overhang))
235
+ );
185
236
  mask-image: linear-gradient(
186
237
  to right,
187
238
  transparent calc(-1 * var(--mask-edge-overhang)),
@@ -195,13 +246,18 @@ export const StyledStagesWrapper = styled.div(({ theme }) => {
195
246
  });
196
247
  StyledStagesWrapper.defaultProps = defaultThemeProp;
197
248
  /**
198
- * Scrollable `<ol>` containing all stage chevron items. Sets the `--chevron-spacing`
199
- * and `--chevron-border-thickness` custom properties consumed by child elements.
249
+ * Scrollable `<ol>` containing all stage chevron items. Sets the `--chevron-spacing`,
250
+ * `--chevron-border-thickness`, and `--segmented-border-thickness` custom properties
251
+ * consumed by child elements.
200
252
  */
201
- export const StyledStages = styled.ol(({ theme }) => {
253
+ export const StyledStages = styled.ol(({ theme, variant }) => {
254
+ const isMilestone = variant === 'milestone';
202
255
  return css `
203
256
  --chevron-spacing: ${theme.base.spacing};
204
257
  --chevron-border-thickness: ${theme.components.button['border-width']};
258
+ --segmented-border-thickness: calc(2 * ${theme.components.button['border-width']});
259
+ --segmented-width: ${segmentedConfig.width};
260
+ background: ${theme.components.card.background};
205
261
  border-radius: ${theme.components.card['border-radius']};
206
262
  display: flex;
207
263
  padding: 0;
@@ -212,6 +268,17 @@ export const StyledStages = styled.ol(({ theme }) => {
212
268
  scrollbar-width: thin;
213
269
  scrollbar-color: transparent transparent;
214
270
 
271
+ ${isMilestone &&
272
+ css `
273
+ --milestone-size: calc(4 * ${theme.base.spacing});
274
+ --milestone-pulse-min: calc(var(--milestone-size) * 0.1);
275
+ --milestone-pulse-start: ${transparentize(0.5, theme.base.palette.interactive)};
276
+ --milestone-pulse-spread: calc(var(--milestone-size) * 0.25);
277
+ --milestone-pulse-end: ${transparentize(0.75, theme.base.palette.interactive)};
278
+ padding-block: ${theme.base.spacing};
279
+ padding-inline: calc(var(--milestone-size) * 0.25);
280
+ `}
281
+
215
282
  &:hover,
216
283
  &:focus-within {
217
284
  scrollbar-color: var(
@@ -226,6 +293,8 @@ export const StyledStages = styled.ol(({ theme }) => {
226
293
  }
227
294
 
228
295
  @media (pointer: coarse) {
296
+ /* 3.203 = exact alignment; slightly overdone default to clear sub-pixel rounding on 3× displays */
297
+ --stages-coarse-notch-correction: 3.35;
229
298
  scrollbar-width: none;
230
299
 
231
300
  &::-webkit-scrollbar {
@@ -299,11 +368,58 @@ export const StyledInnerStage = styled.span(({ theme, ellipsis }) => {
299
368
  `;
300
369
  });
301
370
  StyledInnerStage.defaultProps = defaultThemeProp;
371
+ const stageButtonChevronStyles = (theme) => css `
372
+ padding-inline-start: calc(2 * var(--chevron-spacing));
373
+ padding-inline-end: var(--chevron-spacing);
374
+ width: calc(100% + ${chevronConfig.width});
375
+ --stage-chevron-width: ${chevronConfig.width};
376
+
377
+ ${StyledInnerStage} {
378
+ transform: translateX(calc(-0.25 * var(--chevron-spacing)));
379
+ opacity: ${theme.base.transparency['transparent-2']};
380
+ }
381
+
382
+ ${stageClipPath(true, true)}
383
+
384
+ [dir='rtl'] & ${StyledInnerStage} {
385
+ transform: translateX(calc(0.25 * var(--chevron-spacing)));
386
+ }
387
+
388
+ [dir='rtl'] & {
389
+ ${stageClipPath(true, true, true)}
390
+ }
391
+
392
+ @media (pointer: coarse) {
393
+ padding-inline-start: calc(2.25 * var(--chevron-spacing));
394
+ width: calc(100% + ${chevronConfig.coarseWidth});
395
+ --stage-chevron-width: ${chevronConfig.coarseWidth};
396
+ }
397
+ `;
398
+ const stageButtonSegmentedStyles = (theme) => css `
399
+ width: 100%;
400
+ padding-inline-start: calc(var(--segmented-width) + var(--chevron-spacing));
401
+ padding-inline-end: calc(var(--segmented-width) + var(--chevron-spacing));
402
+
403
+ ${StyledInnerStage} {
404
+ opacity: ${theme.base.transparency['transparent-2']};
405
+ }
406
+
407
+ ${segmentedClipPath(true, true)}
408
+
409
+ [dir='rtl'] & {
410
+ ${segmentedClipPath(true, true, true)}
411
+ }
412
+ `;
413
+ const stageButtonVariants = {
414
+ chevron: stageButtonChevronStyles,
415
+ segmented: stageButtonSegmentedStyles,
416
+ milestone: () => css ``
417
+ };
302
418
  /**
303
419
  * Clickable `<button>` for a single stage. Renders the chevron clip-path shape and
304
420
  * applies status-based background and foreground colours.
305
421
  */
306
- export const StyledStage = styled.button(({ theme: { base: { palette, 'hit-area': hitArea, transparency, 'font-weight': fontWeight }, components: { 'case-view': { stages } } }, status, readOnly }) => {
422
+ export const StyledStage = styled.button(({ theme, theme: { base: { palette, 'hit-area': hitArea, transparency, 'font-weight': fontWeight }, components: { 'case-view': { stages } } }, status, readOnly, variant: shape }) => {
307
423
  const stageForeground = calculateForegroundColor(stages.status[status].background, stages.status[status]['foreground-color']);
308
424
  return css `
309
425
  background: ${stages.status[status].background};
@@ -318,26 +434,10 @@ export const StyledStage = styled.button(({ theme: { base: { palette, 'hit-area'
318
434
  * Adding 0.0625rem (1px at default font size) to min-height compensates so the button fills its <li> container,
319
435
  * keeping the clipped chevron background flush with the top and bottom borders. */
320
436
  min-height: calc(5 * var(--chevron-spacing) + 0.0625rem);
321
- padding-inline-start: calc(2 * var(--chevron-spacing));
322
- padding-inline-end: var(--chevron-spacing);
323
437
  margin-inline-start: 0;
324
438
  margin-inline-end: 0;
325
- width: calc(100% + ${chevronConfig.width});
326
- --stage-chevron-width: ${chevronConfig.width};
327
439
 
328
- ${StyledInnerStage} {
329
- transform: translateX(calc(-0.25 * var(--chevron-spacing)));
330
- opacity: ${transparency['transparent-2']};
331
- }
332
- ${stageClipPath(true, true)}
333
-
334
- [dir='rtl'] & ${StyledInnerStage} {
335
- transform: translateX(calc(0.25 * var(--chevron-spacing)));
336
- }
337
-
338
- [dir='rtl'] & {
339
- ${stageClipPath(true, true, true)}
340
- }
440
+ ${stageButtonVariants[shape](theme)}
341
441
 
342
442
  ${!readOnly &&
343
443
  css `
@@ -376,9 +476,6 @@ export const StyledStage = styled.button(({ theme: { base: { palette, 'hit-area'
376
476
 
377
477
  @media (pointer: coarse) {
378
478
  min-height: ${hitArea['finger-min']};
379
- padding-inline-start: calc(2.25 * var(--chevron-spacing));
380
- width: calc(100% + ${chevronConfig.coarseWidth});
381
- --stage-chevron-width: ${chevronConfig.coarseWidth};
382
479
  }
383
480
  `;
384
481
  });
@@ -400,195 +497,439 @@ export const StyledStageText = styled(Text)(({ theme, isCurrent }) => css `
400
497
  font-weight: ${isCurrent ? theme.base['font-weight'].bold : theme.base['font-weight'].normal};
401
498
  `);
402
499
  StyledStageText.defaultProps = defaultThemeProp;
403
- /**
404
- * `<li>` container for a single stage item. Manages negative-margin chevron overlap,
405
- * pseudo-element positioning, and z-index stacking for interactive states.
406
- */
407
- export const StyledStageContainer = styled.li(({ theme: { base: { palette }, components: { card: { 'border-radius': borderRadius } } }, readOnly }) => {
500
+ const stageContainerChevronStyles = (theme, readOnly) => {
501
+ const includeHover = !readOnly;
408
502
  return css `
409
- display: flex;
410
- position: relative;
411
- margin-inline-start: calc(-1 * ${chevronConfig.width});
503
+ /* Chevron: negative-margin overlap so consecutive stages share a boundary */
504
+ margin-inline-start: calc(-1 * ${chevronConfig.width});
412
505
 
413
- &:nth-of-type(2) {
414
- margin-inline-start: 0;
415
- }
506
+ &:nth-of-type(2) {
507
+ margin-inline-start: 0;
508
+ }
416
509
 
417
- /* Resting z-index: for same-z siblings, DOM order determines paint order.
418
- * All non-last <li>s have z-index:1 so the later (right) <li>'s ::before paints on top
419
- * at internal boundaries; the last <li> has no z-index so its left neighbor's ::after
420
- * wins at the final boundary. On hover/focus/active, the interacting <li> gains z:2/3
421
- * and its own pseudo-element surfaces for that boundary, regardless of which side.
510
+ /* Chevron boundary indicators via ::before (left notch) and ::after (right point) pseudo-elements */
511
+ &:not(:nth-of-type(-n + 2))::before {
512
+ ${createChevronPseudo('calc(2 * var(--chevron-border-thickness))', theme.base.palette['border-line'], chevronConfig.squareSize)}
513
+ /* Elevate ::before above the button so it renders via the same pathway as ::after.
514
+ * Without z-index, ::before (first in DOM) is painted first and then covered by the
515
+ * button, making it only partially visible through the clip-path notch window. This
516
+ * causes thicker interactive-state borders (3×) to look thinner on the left boundary
517
+ * than on the right (::after, which paints last and is always fully visible).
518
+ * The ::before tip's rightmost extent is at --stage-chevron-width from the <li> start,
519
+ * which is before the stage text (padding-inline-start: 2 × spacing), so no content overlap.
422
520
  */
423
- &:not(:nth-last-of-type(-n + 2)) {
424
- z-index: 1;
425
- }
521
+ z-index: 1;
522
+ }
426
523
 
427
- &:has(button:focus-visible),
428
- &:has(button:active) {
429
- z-index: 2;
524
+ &:not(:nth-last-of-type(-n + 2))::after {
525
+ ${createChevronPseudo('calc(2 * var(--chevron-border-thickness))', theme.base.palette['border-line'], chevronConfig.squareSize)}
526
+ }
527
+
528
+ [dir='rtl'] &::before,
529
+ [dir='rtl'] &::after {
530
+ transform: rotateZ(-45deg) skew(-15deg, -15deg);
531
+ }
532
+
533
+ /* Chevron positioning: 45° rotated squares create visual tips via offsets:
534
+ * left ≈ 0.86 × spacing, right ≈ 1.445 × spacing (accounts for skew correction)
535
+ */
536
+ ${createChevronBaseStyles(chevronConfig, 'var(--chevron-border-thickness)')}
537
+ ${createChevronInteractiveStates(chevronConfig, 'var(--chevron-border-thickness)', includeHover)}
538
+
539
+ /* Thicken chevrons to visually match button top/bottom border on any colored state.
540
+ * At 45° rotation, border appears 1/sin(45°) ≈ 0.707× thinner, so 2× border ≈ 1.4× horizontal border weight.
541
+ */
542
+ ${includeHover &&
543
+ css `
544
+ &:has(button:hover)::before,
545
+ &:has(button:hover)::after {
546
+ border-color: ${theme.base.palette['border-line']};
430
547
  }
548
+ `}
431
549
 
432
- ${!readOnly &&
550
+ &:has(button:focus-visible)::before,
551
+ &:has(button:focus-visible)::after {
552
+ border-color: ${theme.base.palette.interactive};
553
+ }
554
+
555
+ &:has(button:active)::before,
556
+ &:has(button:active)::after {
557
+ border-color: ${theme.base.palette.interactive};
558
+ }
559
+
560
+ /* First stage: no left notch, extended width to cover the full left edge */
561
+ &:nth-of-type(2):not(:nth-last-of-type(2)) > ${StyledStage} {
562
+ --stage-chevron-width: ${chevronConfig.width};
563
+ width: calc(100% + (2 * var(--stage-chevron-width)));
564
+ padding-inline-start: calc(var(--chevron-spacing));
565
+ ${stageClipPath(false, true)}
566
+
567
+ ${includeHover &&
433
568
  css `
434
- &:has(button:hover) {
435
- z-index: 3;
569
+ /* Inline-start interactive indicator via inset shadow — avoids border-color bleeding through gradient backgrounds */
570
+ &:hover {
571
+ box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0
572
+ ${theme.base.palette['border-line']};
573
+
574
+ [dir='rtl'] & {
575
+ box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0
576
+ ${theme.base.palette['border-line']};
577
+ }
436
578
  }
437
579
  `}
438
580
 
581
+ &:focus-visible,
582
+ &:active {
583
+ box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0
584
+ ${theme.base.palette.interactive};
585
+
586
+ [dir='rtl'] & {
587
+ box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0
588
+ ${theme.base.palette.interactive};
589
+ }
590
+ }
591
+
439
592
  @media (pointer: coarse) {
440
- margin-inline-start: calc(-1 * ${chevronConfig.coarseWidth});
593
+ --stage-chevron-width: ${chevronConfig.coarseWidth};
594
+ width: calc(100% + (2 * var(--stage-chevron-width)));
441
595
  }
596
+ }
442
597
 
443
- &:not(:nth-of-type(-n + 2))::before {
444
- ${createChevronPseudo('calc(2 * var(--chevron-border-thickness))', palette['border-line'], chevronConfig.squareSize)}
445
- /* Elevate ::before above the button so it renders via the same pathway as ::after.
446
- * Without z-index, ::before (first in DOM) is painted first and then covered by the
447
- * button, making it only partially visible through the clip-path notch window. This
448
- * causes thicker interactive-state borders (3×) to look thinner on the left boundary
449
- * than on the right (::after, which paints last and is always fully visible).
450
- * The ::before tip's rightmost extent is at --stage-chevron-width from the <li> start,
451
- * which is before the stage text (padding-inline-start: 2 × spacing), so no content overlap.
452
- */
453
- z-index: 1;
598
+ /* Last stage: no right point */
599
+ &:nth-last-of-type(2):not(:nth-of-type(2)) > ${StyledStage} {
600
+ --stage-chevron-width: ${chevronConfig.width};
601
+ padding-inline-end: 0;
602
+ width: 100%;
603
+ ${stageClipPath(true, false)}
604
+
605
+ ${includeHover &&
606
+ css `
607
+ /* Inline-end interactive indicator via inset shadow — avoids border-color bleeding through gradient backgrounds */
608
+ &:hover {
609
+ box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0
610
+ ${theme.base.palette['border-line']};
611
+
612
+ [dir='rtl'] & {
613
+ box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0
614
+ ${theme.base.palette['border-line']};
615
+ }
616
+ }
617
+ `}
618
+
619
+ &:focus-visible,
620
+ &:active {
621
+ box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0
622
+ ${theme.base.palette.interactive};
623
+
624
+ [dir='rtl'] & {
625
+ box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0
626
+ ${theme.base.palette.interactive};
627
+ }
454
628
  }
455
629
 
456
- &:not(:nth-last-of-type(-n + 2))::after {
457
- ${createChevronPseudo('calc(2 * var(--chevron-border-thickness))', palette['border-line'], chevronConfig.squareSize)}
630
+ @media (pointer: coarse) {
631
+ --stage-chevron-width: ${chevronConfig.coarseWidth};
458
632
  }
633
+ }
459
634
 
460
- /* Chevron positioning: 45° rotated squares create visual tips via offsets:
461
- * left ≈ 0.86 × spacing, right ≈ 1.445 × spacing (accounts for skew correction)
462
- */
635
+ /* RTL: flip clip-paths for first and last stages */
636
+ [dir='rtl'] &:nth-of-type(2):not(:nth-last-of-type(2)) > ${StyledStage} {
637
+ ${stageClipPath(true, false, true)}
638
+ }
463
639
 
464
- [dir='rtl'] &::before,
465
- [dir='rtl'] &::after {
466
- transform: rotateZ(-45deg) skew(-15deg, -15deg);
467
- }
640
+ [dir='rtl'] &:nth-last-of-type(2):not(:nth-of-type(2)) > ${StyledStage} {
641
+ ${stageClipPath(false, true, true)}
642
+ }
468
643
 
469
- /* Thicken chevrons to visually match button top/bottom border on any colored state.
470
- * At 45° rotation, border appears 1/sin(45°) ≈ 0.707× thinner, so 2× border 1.4× horizontal border weight.
471
- */
472
- ${createChevronBaseStyles(chevronConfig, 'var(--chevron-border-thickness)')}
473
- ${createChevronInteractiveStates(chevronConfig, 'var(--chevron-border-thickness)', !readOnly)}
474
- ${!readOnly &&
644
+ /* Single stage: plain rectangle no clip-path notch, inset shadows on both sides */
645
+ &:nth-of-type(2):nth-last-of-type(2) > ${StyledStage} {
646
+ --stage-chevron-width: ${chevronConfig.width};
647
+ clip-path: none;
648
+ padding-inline-start: calc(var(--chevron-spacing));
649
+
650
+ ${includeHover &&
475
651
  css `
476
- &:has(button:hover)::before,
477
- &:has(button:hover)::after {
478
- border-color: ${palette['border-line']};
652
+ &:hover {
653
+ box-shadow:
654
+ inset calc(2 * var(--chevron-border-thickness)) 0 0 ${theme.base.palette['border-line']},
655
+ inset calc(-2 * var(--chevron-border-thickness)) 0 0
656
+ ${theme.base.palette['border-line']};
479
657
  }
480
658
  `}
481
- &:has(button:focus-visible)::before,
482
- &:has(button:focus-visible)::after {
483
- border-color: ${palette.interactive};
484
- }
485
659
 
486
- &:has(button:active)::before,
487
- &:has(button:active)::after {
488
- border-color: ${palette.interactive};
660
+ &:focus-visible,
661
+ &:active {
662
+ box-shadow:
663
+ inset calc(2 * var(--chevron-border-thickness)) 0 0 ${theme.base.palette.interactive},
664
+ inset calc(-2 * var(--chevron-border-thickness)) 0 0 ${theme.base.palette.interactive};
489
665
  }
490
666
 
491
667
  @media (pointer: coarse) {
492
- &:not(:nth-of-type(-n + 2))::before,
493
- &:not(:nth-last-of-type(-n + 2))::after {
494
- inset-block-start: calc(50% - ${chevronConfig.coarseSquareSize} / 2);
495
- width: ${chevronConfig.coarseSquareSize};
496
- height: ${chevronConfig.coarseSquareSize};
497
- }
668
+ --stage-chevron-width: ${chevronConfig.coarseWidth};
669
+ }
670
+ }
498
671
 
499
- &:not(:nth-of-type(-n + 2))::before {
500
- inset-inline-start: calc(
501
- -1 * ${chevronConfig.coarseWidth} - ${chevronConfig.coarseOffsetLeft}
502
- );
503
- }
672
+ @media (pointer: coarse) {
673
+ margin-inline-start: calc(-1 * ${chevronConfig.coarseWidth});
504
674
 
505
- &:not(:nth-last-of-type(-n + 2))::after {
506
- inset-inline-end: calc(
507
- -1 * ${chevronConfig.coarseWidth} + ${chevronConfig.coarseOffsetRight}
508
- );
509
- }
675
+ &:not(:nth-of-type(-n + 2))::before,
676
+ &:not(:nth-last-of-type(-n + 2))::after {
677
+ inset-block-start: calc(50% - ${chevronConfig.coarseSquareSize} / 2);
678
+ width: ${chevronConfig.coarseSquareSize};
679
+ height: ${chevronConfig.coarseSquareSize};
680
+ border-inline-end-width: calc(2 * var(--chevron-border-thickness));
681
+ border-block-start-width: calc(2 * var(--chevron-border-thickness));
510
682
  }
511
683
 
512
- &:nth-of-type(2) > ${StyledStage} {
513
- --stage-chevron-width: ${chevronConfig.width};
514
- border-start-start-radius: ${borderRadius};
515
- border-end-start-radius: ${borderRadius};
516
- width: calc(100% + (2 * var(--stage-chevron-width)));
517
- padding-inline-start: calc(var(--chevron-spacing));
518
- ${stageClipPath(false, true)}
684
+ &:not(:nth-of-type(-n + 2))::before {
685
+ inset-inline-start: ${chevronConfig.coarseNotchInsetStart};
686
+ }
519
687
 
520
- ${!readOnly &&
688
+ &:not(:nth-last-of-type(-n + 2))::after {
689
+ inset-inline-end: 0;
690
+ }
691
+ }
692
+ `;
693
+ };
694
+ const stageContainerSegmentedStyles = (theme, readOnly) => {
695
+ const includeHover = !readOnly;
696
+ return css `
697
+ --segmented-edge-thickness: var(--chevron-border-thickness);
698
+
699
+ /* Segmented: stages overlap by --segmented-width so parallelogram edges meet flush */
700
+ margin-inline-start: calc(-1 * var(--segmented-width));
701
+
702
+ &:nth-of-type(2) {
703
+ margin-inline-start: 0;
704
+ }
705
+
706
+ /* Left segmented border strip: diagonal clip-path on each stage's inline-start edge (stages 2..N).
707
+ * The strip goes from (segmented-width, 0%) at top to (0%, 100%) at bottom of the pseudo,
708
+ * matching the parallelogram's inline-start clip edge exactly.
709
+ */
710
+ &:not(:nth-of-type(-n + 2))::before {
711
+ content: '';
712
+ position: absolute;
713
+ inset-block: 0;
714
+ inset-inline-start: 0;
715
+ width: calc(var(--segmented-width) + var(--segmented-edge-thickness));
716
+ clip-path: polygon(
717
+ var(--segmented-width) 0%,
718
+ calc(var(--segmented-width) + var(--segmented-edge-thickness)) 0%,
719
+ var(--segmented-edge-thickness) 100%,
720
+ 0% 100%
721
+ );
722
+ background: ${theme.base.palette['border-line']};
723
+ pointer-events: none;
724
+ z-index: 1;
725
+ }
726
+
727
+ /* Right segmented border strip: diagonal clip-path on each stage's inline-end edge (stages 1..N-1). */
728
+ &:not(:nth-last-of-type(-n + 2))::after {
729
+ content: '';
730
+ position: absolute;
731
+ inset-block: 0;
732
+ inset-inline-end: 0;
733
+ width: calc(var(--segmented-width) + var(--segmented-edge-thickness));
734
+ clip-path: polygon(
735
+ var(--segmented-width) 0%,
736
+ calc(var(--segmented-width) + var(--segmented-edge-thickness)) 0%,
737
+ var(--segmented-edge-thickness) 100%,
738
+ 0% 100%
739
+ );
740
+ background: ${theme.base.palette['border-line']};
741
+ pointer-events: none;
742
+ z-index: 2;
743
+ }
744
+
745
+ /* Interactive states: update pseudo background to signal hover/focus/active */
746
+ ${includeHover &&
521
747
  css `
522
- /* Inline-start interactive indicator via inset shadow — avoids border-color bleeding through gradient backgrounds */
523
- &:hover {
524
- box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0
525
- ${palette['border-line']};
748
+ &:has(button:hover) {
749
+ --segmented-edge-thickness: var(--segmented-border-thickness);
750
+ }
526
751
 
527
- [dir='rtl'] & {
528
- box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0
529
- ${palette['border-line']};
530
- }
531
- }
532
- `}
752
+ &:has(button:hover)::before,
753
+ &:has(button:hover)::after {
754
+ background: ${theme.base.palette['border-line']};
755
+ }
756
+ `}
533
757
 
534
- &:focus-visible,
535
- &:active {
536
- box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0 ${palette.interactive};
758
+ &:has(button:focus-visible),
759
+ &:has(button:active) {
760
+ --segmented-edge-thickness: var(--segmented-border-thickness);
761
+ }
762
+
763
+ &:has(button:focus-visible)::before,
764
+ &:has(button:focus-visible)::after,
765
+ &:has(button:active)::before,
766
+ &:has(button:active)::after {
767
+ background: ${theme.base.palette.interactive};
768
+ }
769
+
770
+ /* First stage (no inline-start cut): flat left edge + inset shadow edge indicator */
771
+ &:nth-of-type(2):not(:nth-last-of-type(2)) > ${StyledStage} {
772
+ ${segmentedClipPath(false, true)}
773
+
774
+ ${includeHover &&
775
+ css `
776
+ &:hover {
777
+ box-shadow: inset var(--segmented-border-thickness) 0 0
778
+ ${theme.base.palette['border-line']};
537
779
 
538
780
  [dir='rtl'] & {
539
- box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0 ${palette.interactive};
781
+ box-shadow: inset calc(-1 * var(--segmented-border-thickness)) 0 0
782
+ ${theme.base.palette['border-line']};
540
783
  }
541
784
  }
785
+ `}
786
+
787
+ &:focus-visible,
788
+ &:active {
789
+ box-shadow: inset var(--segmented-border-thickness) 0 0 ${theme.base.palette.interactive};
542
790
 
543
- @media (pointer: coarse) {
544
- --stage-chevron-width: ${chevronConfig.coarseWidth};
545
- width: calc(100% + (2 * var(--stage-chevron-width)));
791
+ [dir='rtl'] & {
792
+ box-shadow: inset calc(-1 * var(--segmented-border-thickness)) 0 0
793
+ ${theme.base.palette.interactive};
546
794
  }
547
795
  }
796
+ }
548
797
 
549
- &:nth-last-of-type(2) > ${StyledStage} {
550
- --stage-chevron-width: ${chevronConfig.width};
551
- border-start-end-radius: ${borderRadius};
552
- border-end-end-radius: ${borderRadius};
553
- padding-inline-end: 0;
554
- width: 100%;
555
- ${stageClipPath(true, false)}
798
+ /* Last stage (no inline-end cut): flat right edge + inset shadow edge indicator */
799
+ &:nth-last-of-type(2):not(:nth-of-type(2)) > ${StyledStage} {
800
+ ${segmentedClipPath(true, false)}
556
801
 
557
- ${!readOnly &&
802
+ ${includeHover &&
558
803
  css `
559
- /* Inline-end interactive indicator via inset shadow — avoids border-color bleeding through gradient backgrounds */
560
- &:hover {
561
- box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0
562
- ${palette['border-line']};
804
+ &:hover {
805
+ box-shadow: inset calc(-1 * var(--segmented-border-thickness)) 0 0
806
+ ${theme.base.palette['border-line']};
563
807
 
564
- [dir='rtl'] & {
565
- box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0
566
- ${palette['border-line']};
567
- }
808
+ [dir='rtl'] & {
809
+ box-shadow: inset var(--segmented-border-thickness) 0 0
810
+ ${theme.base.palette['border-line']};
568
811
  }
569
- `}
812
+ }
813
+ `}
570
814
 
571
- &:focus-visible,
572
- &:active {
573
- box-shadow: inset calc(-2 * var(--chevron-border-thickness)) 0 0 ${palette.interactive};
815
+ &:focus-visible,
816
+ &:active {
817
+ box-shadow: inset calc(-1 * var(--segmented-border-thickness)) 0 0
818
+ ${theme.base.palette.interactive};
574
819
 
575
- [dir='rtl'] & {
576
- box-shadow: inset calc(2 * var(--chevron-border-thickness)) 0 0 ${palette.interactive};
577
- }
820
+ [dir='rtl'] & {
821
+ box-shadow: inset var(--segmented-border-thickness) 0 0 ${theme.base.palette.interactive};
578
822
  }
823
+ }
824
+ }
825
+
826
+ /* RTL: flip clip-paths for first and last stage buttons */
827
+ [dir='rtl'] &:nth-of-type(2):not(:nth-last-of-type(2)) > ${StyledStage} {
828
+ ${segmentedClipPath(true, false, true)}
829
+ }
830
+
831
+ [dir='rtl'] &:nth-last-of-type(2):not(:nth-of-type(2)) > ${StyledStage} {
832
+ ${segmentedClipPath(false, true, true)}
833
+ }
834
+
835
+ /* Single stage: plain rectangle — no diagonal cuts, inset shadows on both sides */
836
+ &:nth-of-type(2):nth-last-of-type(2) > ${StyledStage} {
837
+ clip-path: none;
838
+ padding-inline-start: calc(var(--chevron-spacing));
839
+ padding-inline-end: calc(var(--chevron-spacing));
840
+
841
+ ${includeHover &&
842
+ css `
843
+ &:hover {
844
+ box-shadow:
845
+ inset var(--segmented-border-thickness) 0 0 ${theme.base.palette['border-line']},
846
+ inset calc(-1 * var(--segmented-border-thickness)) 0 0
847
+ ${theme.base.palette['border-line']};
848
+ }
849
+ `}
850
+
851
+ &:focus-visible,
852
+ &:active {
853
+ box-shadow:
854
+ inset var(--segmented-border-thickness) 0 0 ${theme.base.palette.interactive},
855
+ inset calc(-1 * var(--segmented-border-thickness)) 0 0 ${theme.base.palette.interactive};
856
+ }
857
+ }
858
+
859
+ /* RTL: mirror the pseudo clip-paths.
860
+ * In LTR the strip goes top-right → bottom-left (polygon A 0%, A+B 0%, B 100%, 0% 100%).
861
+ * In RTL the pseudo is positioned at the opposite physical edge via logical inset properties,
862
+ * so the strip must go top-left → bottom-right to stay aligned with the flipped slant.
863
+ */
864
+ [dir='rtl'] &:not(:nth-of-type(-n + 2))::before,
865
+ [dir='rtl'] &:not(:nth-last-of-type(-n + 2))::after {
866
+ clip-path: polygon(
867
+ 0% 0%,
868
+ var(--segmented-edge-thickness) 0%,
869
+ calc(var(--segmented-width) + var(--segmented-edge-thickness)) 100%,
870
+ var(--segmented-width) 100%
871
+ );
872
+ }
873
+
874
+ @media (pointer: coarse) {
875
+ /* A single CSS custom property update propagates to all margin and clip-path consumers */
876
+ --segmented-width: ${segmentedConfig.coarseWidth};
877
+ }
878
+ `;
879
+ };
880
+ const stageContainerVariants = {
881
+ chevron: stageContainerChevronStyles,
882
+ segmented: stageContainerSegmentedStyles,
883
+ milestone: () => css ``
884
+ };
885
+ /**
886
+ * `<li>` container for a single stage item. Manages negative-margin chevron overlap,
887
+ * pseudo-element positioning, and z-index stacking for interactive states.
888
+ */
889
+ export const StyledStageContainer = styled.li(({ theme, theme: { components: { card: { 'border-radius': borderRadius } } }, readOnly, variant: shape }) => {
890
+ return css `
891
+ display: flex;
892
+ position: relative;
893
+
894
+ /* Resting z-index: for same-z siblings, DOM order determines paint order.
895
+ * All non-last <li>s have z-index:1 so the later (right) <li>'s ::before paints on top
896
+ * at internal boundaries; the last <li> has no z-index so its left neighbor's ::after
897
+ * wins at the final boundary. On hover/focus/active, the interacting <li> gains z:2/3
898
+ * and its own pseudo-element surfaces for that boundary, regardless of which side.
899
+ */
900
+ &:not(:nth-last-of-type(-n + 2)) {
901
+ z-index: 1;
902
+ }
579
903
 
580
- @media (pointer: coarse) {
581
- --stage-chevron-width: ${chevronConfig.coarseWidth};
904
+ &:has(button:focus-visible),
905
+ &:has(button:active) {
906
+ z-index: 2;
907
+ }
908
+
909
+ ${!readOnly &&
910
+ css `
911
+ &:has(button:hover) {
912
+ z-index: 3;
582
913
  }
914
+ `}
915
+
916
+ /* Corner radii are shared across all shape variants */
917
+ &:nth-of-type(2):not(:nth-last-of-type(2)) > ${StyledStage} {
918
+ border-start-start-radius: ${borderRadius};
919
+ border-end-start-radius: ${borderRadius};
583
920
  }
584
921
 
585
- [dir='rtl'] &:nth-of-type(2) > ${StyledStage} {
586
- ${stageClipPath(true, false, true)}
922
+ &:nth-last-of-type(2):not(:nth-of-type(2)) > ${StyledStage} {
923
+ border-start-end-radius: ${borderRadius};
924
+ border-end-end-radius: ${borderRadius};
587
925
  }
588
926
 
589
- [dir='rtl'] &:nth-last-of-type(2) > ${StyledStage} {
590
- ${stageClipPath(false, true, true)}
927
+ &:nth-of-type(2):nth-last-of-type(2) > ${StyledStage} {
928
+ border-radius: ${borderRadius};
929
+ width: 100%;
591
930
  }
931
+
932
+ ${stageContainerVariants[shape](theme, readOnly)}
592
933
  `;
593
934
  });
594
935
  StyledStageContainer.defaultProps = defaultThemeProp;
@@ -651,6 +992,195 @@ export const StyledStageGlimpse = styled.div(({ theme }) => {
651
992
  `;
652
993
  });
653
994
  StyledStageGlimpse.defaultProps = defaultThemeProp;
995
+ const milestonePulse = keyframes `
996
+ 0%, 100% {
997
+ box-shadow: 0 0 0 var(--milestone-pulse-min) var(--milestone-pulse-start);
998
+ }
999
+ 60% {
1000
+ box-shadow: 0 0 0 var(--milestone-pulse-spread) var(--milestone-pulse-end);
1001
+ }
1002
+ `;
1003
+ export const StyledMilestoneLabel = styled.span(({ theme }) => {
1004
+ return css `
1005
+ color: ${theme.base.palette['foreground-color']};
1006
+ line-height: ${theme.base['line-height']};
1007
+ text-align: center;
1008
+ align-self: stretch;
1009
+ overflow-wrap: break-word;
1010
+ padding-inline: calc(0.25 * ${theme.base.spacing});
1011
+ `;
1012
+ });
1013
+ StyledMilestoneLabel.defaultProps = defaultThemeProp;
1014
+ /**
1015
+ * `<li>` container for a single stage item in the 'milestone' shape variant.
1016
+ * Renders column-flex layout (milestone above, label below) and draws the
1017
+ * horizontal connector as a single line from this milestone's centre to the next
1018
+ * milestone's centre (::after), sized so every connector has an identical length.
1019
+ */
1020
+ export const StyledMilestoneStageContainer = styled.li(({ theme, nextCompleted, status }) => {
1021
+ const { base: { palette: { interactive, 'border-line': borderLine }, spacing } } = theme;
1022
+ const fontSize = calculateFontSize(theme.base['font-size'], theme.base['font-scale']);
1023
+ const connectorFilled = nextCompleted;
1024
+ return css `
1025
+ --connector-height: ${connectorFilled ? '0.25rem' : '0.125rem'};
1026
+ --connector-display: block;
1027
+ --connector-start: 50%;
1028
+ --connector-width: 100%;
1029
+ --connector-bg: ${connectorFilled ? interactive : borderLine};
1030
+ --connector-forced-bg: ${connectorFilled ? 'Highlight' : 'CanvasText'};
1031
+ display: flex;
1032
+ flex-direction: column;
1033
+ align-items: center;
1034
+ flex: 1 0 0;
1035
+ font-size: ${fontSize.xs};
1036
+ font-weight: ${status === 'current'
1037
+ ? theme.base['font-weight']['semi-bold']
1038
+ : theme.base['font-weight'].normal};
1039
+ min-width: 15ch;
1040
+ position: relative;
1041
+ gap: ${spacing};
1042
+ z-index: 1;
1043
+
1044
+ &:nth-of-type(2):not(:nth-last-of-type(2)) {
1045
+ --connector-start: calc(var(--milestone-size) / 2);
1046
+ --connector-width: calc(200% - var(--milestone-size));
1047
+ align-items: flex-start;
1048
+ flex: 0.5 0 calc(var(--milestone-size) / 2);
1049
+ min-width: calc(7.5ch + var(--milestone-size) / 2);
1050
+
1051
+ ${StyledMilestoneLabel} {
1052
+ text-align: start;
1053
+ }
1054
+ }
1055
+
1056
+ &:nth-last-of-type(2):not(:nth-of-type(2)) {
1057
+ --connector-display: none;
1058
+ align-items: flex-end;
1059
+ flex: 0.5 0 calc(var(--milestone-size) / 2);
1060
+ min-width: calc(7.5ch + var(--milestone-size) / 2);
1061
+
1062
+ ${StyledMilestoneLabel} {
1063
+ text-align: end;
1064
+ }
1065
+ }
1066
+
1067
+ &:nth-of-type(2):nth-last-of-type(2) {
1068
+ --connector-display: none;
1069
+ align-items: center;
1070
+ flex: 1 0 0;
1071
+ }
1072
+
1073
+ &::after {
1074
+ content: '';
1075
+ display: var(--connector-display);
1076
+ position: absolute;
1077
+ inset-block-start: calc(var(--milestone-size) / 2 - var(--connector-height) / 2);
1078
+ inset-inline-start: var(--connector-start);
1079
+ width: var(--connector-width);
1080
+ height: var(--connector-height);
1081
+ background: var(--connector-bg);
1082
+ pointer-events: none;
1083
+
1084
+ @media (forced-colors: active) {
1085
+ background: var(--connector-forced-bg);
1086
+ }
1087
+ }
1088
+ `;
1089
+ });
1090
+ StyledMilestoneStageContainer.defaultProps = defaultThemeProp;
1091
+ export const StyledMilestoneButton = styled.button(({ theme, status, readOnly }) => {
1092
+ const { base: { 'hit-area': hitArea, animation: { speed: animationSpeed, timing: { ease: animationTiming } }, shadow: { focus: focusShadow } }, components: { button: { 'border-width': borderWidth }, 'case-view': { stages } } } = theme;
1093
+ const stageForeground = calculateForegroundColor(stages.status[status].background, stages.status[status]['foreground-color']);
1094
+ const fontSize = calculateFontSize(theme.base['font-size'], theme.base['font-scale']);
1095
+ const completedBorderColor = status === 'completed'
1096
+ ? tryCatch(() => darken(0.12, stages.status.completed.background), () => 'transparent')
1097
+ : '';
1098
+ return css `
1099
+ position: relative;
1100
+ z-index: 1;
1101
+ display: flex;
1102
+ align-items: center;
1103
+ justify-content: center;
1104
+ flex-shrink: 0;
1105
+ border-radius: 50%;
1106
+ width: var(--milestone-size);
1107
+ height: var(--milestone-size);
1108
+ background: ${stages.status[status].background};
1109
+ color: ${stageForeground};
1110
+ font-size: ${fontSize.s};
1111
+ font-weight: ${status === 'current'
1112
+ ? theme.base['font-weight']['semi-bold']
1113
+ : theme.base['font-weight'].normal};
1114
+ outline: none;
1115
+ border: none;
1116
+ cursor: ${readOnly ? 'default' : 'pointer'};
1117
+ transition: box-shadow calc(2 * ${animationSpeed}) ${animationTiming};
1118
+
1119
+ ${status === 'completed' &&
1120
+ css `
1121
+ box-shadow: inset 0 0 0 calc(2 * ${borderWidth}) ${completedBorderColor};
1122
+ `}
1123
+
1124
+ &:focus-visible {
1125
+ box-shadow: ${focusShadow};
1126
+
1127
+ @media (forced-colors: active) {
1128
+ outline: 0.125rem solid Highlight;
1129
+ outline-offset: 0.125rem;
1130
+ }
1131
+ }
1132
+
1133
+ ${!readOnly &&
1134
+ css `
1135
+ &:hover {
1136
+ filter: brightness(0.85);
1137
+ }
1138
+
1139
+ &:active {
1140
+ filter: brightness(0.85);
1141
+ }
1142
+ `}
1143
+
1144
+ ${status === 'current' &&
1145
+ !readOnly &&
1146
+ css `
1147
+ &::before {
1148
+ content: '';
1149
+ position: absolute;
1150
+ inset: 0;
1151
+ border-radius: 50%;
1152
+ pointer-events: none;
1153
+ animation: ${milestonePulse} 2s ease-out infinite;
1154
+
1155
+ @media (prefers-reduced-motion: reduce) {
1156
+ animation: none;
1157
+ }
1158
+ }
1159
+ `}
1160
+
1161
+ /* Expanded hit area */
1162
+ &::after {
1163
+ content: '';
1164
+ position: absolute;
1165
+ width: ${hitArea['mouse-min']};
1166
+ height: ${hitArea['mouse-min']};
1167
+ inset-block-start: calc((${hitArea['mouse-min']} - var(--milestone-size)) / -2);
1168
+ inset-inline-start: calc((${hitArea['mouse-min']} - var(--milestone-size)) / -2);
1169
+
1170
+ @media (pointer: coarse) {
1171
+ width: ${hitArea['finger-min']};
1172
+ height: ${hitArea['finger-min']};
1173
+ inset-block-start: calc((${hitArea['finger-min']} - var(--milestone-size)) / -2);
1174
+ inset-inline-start: calc((${hitArea['finger-min']} - var(--milestone-size)) / -2);
1175
+ }
1176
+ }
1177
+
1178
+ @media (forced-colors: active) {
1179
+ border: 0.125rem solid ButtonText;
1180
+ }
1181
+ `;
1182
+ });
1183
+ StyledMilestoneButton.defaultProps = defaultThemeProp;
654
1184
  /**
655
1185
  * Visually hidden `<p>` providing an accessible description for the stages list,
656
1186
  * announced by screen readers but not displayed on screen.