@deepseek-ai/dsh-client-ui-primitives 0.1.6-alpha.2 → 0.1.7-alpha.2

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 (62) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +31 -13
  3. package/README.zh.md +31 -13
  4. package/lib/CodeCard.module.css +83 -0
  5. package/lib/ConnectionIndicator.module.css +1 -10
  6. package/lib/DiffBlock.module.css +24 -53
  7. package/lib/HoverCard.module.css +27 -0
  8. package/lib/Menu.module.css +68 -48
  9. package/lib/Modal.module.css +6 -2
  10. package/lib/OnboardingSurface.module.css +1 -2
  11. package/lib/PathLabel.module.css +28 -0
  12. package/lib/ReadBlock.module.css +13 -81
  13. package/lib/RiskConfirmation.module.css +1 -1
  14. package/lib/SegmentedControl.module.css +76 -0
  15. package/lib/SegmentedTabs.module.css +55 -0
  16. package/lib/StateDot.module.css +64 -37
  17. package/lib/TerminalBlock.module.css +13 -6
  18. package/lib/TextShimmer.module.css +27 -0
  19. package/lib/Toast.module.css +31 -2
  20. package/lib/Tooltip.module.css +12 -0
  21. package/lib/index.js +5892 -3142
  22. package/lib/markdown/CodeBlock.module.css +15 -0
  23. package/lib/markdown/MarkdownText.module.css +1 -19
  24. package/lib/settings-form/SettingsForm.module.css +53 -0
  25. package/lib/settings-form/fields.module.css +145 -0
  26. package/lib/types/CodeToolbar.d.ts +30 -0
  27. package/lib/types/ConfigField.d.ts +33 -0
  28. package/lib/types/DiffBlock.d.ts +3 -3
  29. package/lib/types/DisclosureRow.d.ts +4 -1
  30. package/lib/types/HoverCard.d.ts +14 -4
  31. package/lib/types/LinkIcon.d.ts +15 -30
  32. package/lib/types/Menu.d.ts +53 -8
  33. package/lib/types/PathLabel.d.ts +13 -0
  34. package/lib/types/PermissionIcon.d.ts +39 -0
  35. package/lib/types/ReadBlock.d.ts +2 -1
  36. package/lib/types/ReferenceIcon.d.ts +12 -7
  37. package/lib/types/SegmentedControl.d.ts +35 -0
  38. package/lib/types/SegmentedTabs.d.ts +26 -0
  39. package/lib/types/SiteGlyph.d.ts +1 -1
  40. package/lib/types/StateDot.d.ts +6 -4
  41. package/lib/types/TerminalBlock.d.ts +15 -3
  42. package/lib/types/TextShimmer.d.ts +13 -0
  43. package/lib/types/Toast.d.ts +19 -2
  44. package/lib/types/Tooltip.d.ts +13 -2
  45. package/lib/types/code-file-types.d.ts +1 -1
  46. package/lib/types/code-highlighting.d.ts +19 -0
  47. package/lib/types/icons/index.d.ts +376 -195
  48. package/lib/types/icons/props.d.ts +1 -1
  49. package/lib/types/icons/shared-artwork.d.ts +36 -0
  50. package/lib/types/index.d.ts +22 -4
  51. package/lib/types/markdown/CodeBlock.d.ts +6 -1
  52. package/lib/types/markdown/render.d.ts +3 -0
  53. package/lib/types/overlay-top-margin.d.ts +14 -0
  54. package/lib/types/plugin-artwork.d.ts +17 -0
  55. package/lib/types/settings-form/SettingsForm.d.ts +46 -0
  56. package/lib/types/settings-form/fields.d.ts +69 -0
  57. package/lib/types/settings-form/form-model.d.ts +214 -0
  58. package/lib/types/useAnchoredMaxHeight.d.ts +4 -1
  59. package/lib/types/useAnchoredPosition.d.ts +3 -1
  60. package/lib/types/user-text.d.ts +2 -2
  61. package/lib/user-text.module.css +4 -1
  62. package/package.json +5 -3
@@ -3,19 +3,19 @@
3
3
  display: inline-flex;
4
4
  }
5
5
 
6
- /* Dropdown card (figma MenuDropdown 122:9481 / 419:16920): menu surface,
7
- * r12, elevation-prominent (hairline stroke in shadow), 4px inset padding. */
6
+ /* Compact translucent dropdown card shared by feature menus. */
8
7
  .list,
9
8
  .submenu {
10
9
  /* min-widths below are the design's outer card widths — include the pad. */
11
10
  box-sizing: border-box;
12
- padding: 4px;
11
+ padding: 3px;
13
12
  display: flex;
14
13
  flex-direction: column;
15
14
  gap: 0;
16
15
  border: 0;
17
- border-radius: 20px;
16
+ border-radius: 16px;
18
17
  background: var(--dsw-specific-menu);
18
+ backdrop-filter: var(--dsw-menu-backdrop-filter);
19
19
  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
20
20
  box-shadow: var(--dsw-elevation-prominent);
21
21
  /* Elevated surface: the scrollbar thumb takes the l2 elevation tokens. The
@@ -27,13 +27,13 @@
27
27
  --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
28
28
  }
29
29
 
30
- /* Primary card is 218 wide in the design across both hosts. */
30
+ /* Primary cards grow with their content between compact and readable bounds. */
31
31
  .list {
32
32
  position: absolute;
33
33
  top: calc(100% + 4px);
34
34
  left: 0;
35
35
  z-index: 100;
36
- min-width: 218px;
36
+ min-width: 144px;
37
37
  max-width: 360px;
38
38
  }
39
39
 
@@ -60,12 +60,13 @@
60
60
  }
61
61
 
62
62
  /* Viewport fit: the card stops 12px short of the viewport's top/bottom edges
63
- * (24 = 2 × the portal MARGIN in Menu.tsx) and taller content scrolls inside
63
+ * (the portal MARGIN in Menu.tsx; the top side widens to the frame's published
64
+ * top clearance, the macOS window strip) and taller content scrolls inside
64
65
  * .viewport, so a pinned .footer stays visible. Menus with submenu rows skip
65
66
  * this class — the overflow clip would crop the side card, so they rely on
66
67
  * staying short. */
67
68
  .scrollable {
68
- max-height: calc(100vh - 24px);
69
+ max-height: calc(100vh - 12px - max(12px, var(--dsh-frame-top-clearance, 12px)));
69
70
  }
70
71
 
71
72
  .viewport {
@@ -84,8 +85,8 @@
84
85
  flex: none;
85
86
  display: flex;
86
87
  flex-direction: column;
87
- margin-top: 4px;
88
- padding-top: 4px;
88
+ margin-top: 3px;
89
+ padding-top: 3px;
89
90
  border-top: 0.5px solid var(--dsw-alias-border-l2);
90
91
  }
91
92
 
@@ -93,21 +94,20 @@
93
94
  position: relative;
94
95
  }
95
96
 
96
- /* Menu cell (figma .Menu_cell): min-h 40, r10, pad 10/8, 14/22 primary,
97
- * gap 8 between leading icon / label / trailing check. */
97
+ /* Menu cell: compact text, icon, and selection spacing. */
98
98
  .item {
99
99
  display: flex;
100
100
  align-items: center;
101
- gap: 8px;
101
+ gap: 6px;
102
102
  width: 100%;
103
- min-height: 40px;
104
- padding: 8px 10px;
103
+ min-height: 34px;
104
+ padding: 6px 8px;
105
105
  border: none;
106
- border-radius: 10px;
106
+ border-radius: 8px;
107
107
  background: transparent;
108
108
  cursor: pointer;
109
- font-size: 14px;
110
- line-height: 22px;
109
+ font-size: 13px;
110
+ line-height: 20px;
111
111
  color: var(--dsw-alias-label-primary);
112
112
  text-align: left;
113
113
  }
@@ -125,33 +125,33 @@
125
125
  }
126
126
 
127
127
  .denseList .item {
128
- min-height: 34px;
129
- padding-block: 5px;
128
+ min-height: 30px;
129
+ padding-block: 4px;
130
130
  }
131
131
 
132
132
  .denseList .label {
133
- padding-block: 4px;
133
+ padding-block: 3px;
134
134
  }
135
135
 
136
136
  .list.compactList,
137
137
  .submenu.compactList {
138
- min-width: 164px;
138
+ min-width: 156px;
139
139
  padding: 2px;
140
140
  border-radius: 7px;
141
141
  }
142
142
 
143
143
  .compactList .item {
144
- min-height: 26px;
145
- gap: 6px;
146
- padding: 3px 7px;
144
+ min-height: 24px;
145
+ gap: 5px;
146
+ padding: 2px 6px;
147
147
  border-radius: 5px;
148
- font-size: 12px;
149
- line-height: 18px;
148
+ font-size: 11px;
149
+ line-height: 17px;
150
150
  }
151
151
 
152
152
  .compactList .itemIcon {
153
- width: 14px;
154
- height: 14px;
153
+ width: 12px;
154
+ height: 12px;
155
155
  }
156
156
 
157
157
  .compactList .separator {
@@ -159,9 +159,9 @@
159
159
  }
160
160
 
161
161
  .compactList .label {
162
- padding: 4px 7px;
163
- font-size: 11px;
164
- line-height: 16px;
162
+ padding: 3px 6px;
163
+ font-size: 10px;
164
+ line-height: 15px;
165
165
  }
166
166
 
167
167
  .item:disabled {
@@ -172,13 +172,25 @@
172
172
  .itemIcon {
173
173
  display: inline-flex;
174
174
  flex: none;
175
- width: 16px;
176
- height: 16px;
175
+ width: 14px;
176
+ height: 14px;
177
177
  align-items: center;
178
178
  justify-content: center;
179
179
  color: var(--dsw-alias-label-tertiary);
180
180
  }
181
181
 
182
+ .itemIcon svg,
183
+ .check {
184
+ width: 14px;
185
+ height: 14px;
186
+ }
187
+
188
+ .compactList .itemIcon svg,
189
+ .compactList .check {
190
+ width: 12px;
191
+ height: 12px;
192
+ }
193
+
182
194
  .itemLabel {
183
195
  flex: 1;
184
196
  min-width: 0;
@@ -223,30 +235,38 @@
223
235
 
224
236
  /* Heading row: non-interactive small grey text, padding aligned with items. */
225
237
  .label {
226
- padding: 8px 10px;
227
- font-size: 12px;
228
- line-height: 16px;
238
+ padding: 6px 8px;
239
+ font-size: 11px;
240
+ line-height: 15px;
229
241
  color: var(--dsw-alias-label-tertiary);
230
242
  }
231
243
 
232
- /* Separator cell (figma 122:9481): py 4 / px 2 around the hairline. */
244
+ /* Separator cell (figma 122:9481): py 4 / px 2 around the hairline. l2, not
245
+ * l1, for the same reason as .footer: l1 is near-invisible on this surface. */
233
246
  .separator {
234
247
  height: 0.5px;
235
- margin: 4px 2px;
236
- background: var(--dsw-alias-border-l1);
248
+ margin: 3px 2px;
249
+ background: var(--dsw-alias-border-l2);
250
+ }
251
+
252
+ /* A data separator directly followed by a row that starts a group draws one
253
+ * line, not two; a group start on the list's first row draws none. */
254
+ .separator + .separator,
255
+ .separator + .itemWrap > .separator:first-child,
256
+ .itemWrap:first-child > .separator:first-child {
257
+ display: none;
237
258
  }
238
259
 
239
- /* Nested card to the right of the parent row (figma 419:16920).
260
+ /* Nested card to the right of the parent row.
240
261
  * Bottom-aligned with the parent menu card (grows upward): itemWrap sits in
241
- * .list's 4px pad, so bottom: -4px matches the list's outer bottom edge.
242
- * Horizontal: list pad (4px) + 6px card gap = 10px past itemWrap — plain
243
- * `100% + 6px` collapses to ~2px between outer card edges.
262
+ * .list's 3px pad, so bottom: -3px matches the list's outer bottom edge.
263
+ * Horizontal: list pad (3px) + 6px card gap = 9px past itemWrap.
244
264
  * ::before bridges the full gap so the pointer can cross without mouseLeave. */
245
265
  .submenu {
246
266
  position: absolute;
247
267
  top: auto;
248
- bottom: -4px;
249
- left: calc(100% + 10px);
268
+ bottom: -3px;
269
+ left: calc(100% + 9px);
250
270
  z-index: 101;
251
271
  min-width: 163px;
252
272
  }
@@ -256,6 +276,6 @@
256
276
  position: absolute;
257
277
  top: 0;
258
278
  bottom: 0;
259
- left: -10px;
260
- width: 10px;
279
+ left: -9px;
280
+ width: 9px;
261
281
  }
@@ -1,4 +1,8 @@
1
- /* Full-viewport layer (figma Mask + Dialog 451:18655): mask + centered card. */
1
+ /* Full-viewport layer (figma Mask + Dialog 451:18655): mask + centered card.
2
+ 24px of air on every side; the top/bottom widen to the frame's published
3
+ top clearance (the macOS window strip) so a tall card stays clear of it.
4
+ Cards size against this padding box: consumers cap growth with
5
+ `max-height: 100%`, never their own viewport calc. */
2
6
  .root {
3
7
  position: fixed;
4
8
  inset: 0;
@@ -6,7 +10,7 @@
6
10
  display: flex;
7
11
  align-items: center;
8
12
  justify-content: center;
9
- padding: 24px;
13
+ padding: max(24px, var(--dsh-frame-top-clearance, 24px)) 24px;
10
14
  }
11
15
 
12
16
  /* User/spec mask: rgba(0,0,0,0.24) + blur(2px) via --dsw-alias-bg-mask-1 /
@@ -14,8 +14,7 @@
14
14
  top: 80px;
15
15
  bottom: 0px;
16
16
  background: rgba(0, 0, 0, 0.24);
17
- /* Mask-blur */
18
- backdrop-filter: blur(2px);
17
+ backdrop-filter: var(--dsw-mask-blur);
19
18
  }
20
19
 
21
20
  .onboardingStage {
@@ -0,0 +1,28 @@
1
+ /* Auto margin keeps fitting text at the left. On overflow it becomes zero,
2
+ so flex-end preserves the filename suffix at the right edge. */
3
+ .path {
4
+ display: flex;
5
+ flex: 1 1 auto;
6
+ justify-content: flex-end;
7
+ min-width: 0;
8
+ overflow: hidden;
9
+ font-size: 12px;
10
+ white-space: nowrap;
11
+ }
12
+
13
+ .path[data-path-clipped] {
14
+ mask-image: linear-gradient(to right, transparent, black 28px);
15
+ }
16
+
17
+ .text {
18
+ flex: none;
19
+ margin-right: auto;
20
+ }
21
+
22
+ .directory {
23
+ color: var(--dsw-alias-label-tertiary);
24
+ }
25
+
26
+ .name {
27
+ color: var(--dsw-alias-label-primary);
28
+ }
@@ -1,105 +1,37 @@
1
- .block {
2
- --dsl-read-radius: 12px;
3
- --dsl-read-line-height: 22px;
4
- /* Fixed-width gutter column for the line numbers, so the content edge stays
5
- put down the whole window regardless of how wide the numbers grow. */
6
- --dsl-read-gutter: 48px;
7
-
8
- position: relative;
9
- margin: 16px 0;
10
- color: var(--dsw-alias-label-primary);
11
- background: var(--dsw-alias-markdown-code-block);
12
- border-radius: var(--dsl-read-radius);
13
- }
14
-
15
- .banner {
16
- display: flex;
17
- justify-content: space-between;
18
- align-items: center;
19
- gap: 12px;
20
- padding: 9px 14px;
21
- background: var(--dsw-alias-markdown-code-block-banner);
22
- border-top-left-radius: var(--dsl-read-radius);
23
- border-top-right-radius: var(--dsl-read-radius);
24
- }
25
-
26
- .label {
27
- min-width: 0;
28
- overflow: hidden;
29
- text-overflow: ellipsis;
30
- white-space: nowrap;
31
- color: var(--dsw-alias-label-primary);
32
- font-family: var(--ds-font-family-code);
33
- font-size: 12px;
34
- line-height: 18px;
35
- }
36
-
37
- .action {
38
- display: flex;
39
- align-items: center;
40
- flex-shrink: 0;
41
- gap: 12px;
42
- }
43
-
44
- .count {
45
- color: var(--dsw-alias-label-tertiary);
46
- font: var(--dsw-font-xs-13);
47
- }
48
-
49
- .lang {
50
- color: var(--dsw-alias-label-tertiary);
51
- font-family: var(--ds-font-family-code);
52
- font-size: 12px;
53
- line-height: 18px;
54
- }
55
-
56
- .copyButton {
57
- background-color: transparent;
58
- border: none;
59
- padding: 0;
60
- margin: 0;
61
- color: var(--dsw-alias-label-secondary);
62
- cursor: pointer;
63
- font: var(--dsw-font-xs-13);
64
- }
65
-
66
- .body {
67
- padding: 12px 0;
68
- font: var(--dsw-font-markdown-code-block);
69
- overflow-x: auto;
70
- overflow-y: hidden;
71
- }
72
-
73
- /* One row per file line: a fixed gutter column, then the content. No wrapping —
74
- a source line's leading whitespace is meaningful and scrolls sideways. */
75
1
  .line {
76
2
  display: flex;
77
- min-height: var(--dsl-read-line-height);
78
- line-height: var(--dsl-read-line-height);
3
+ min-height: 1lh;
79
4
  white-space: pre;
80
5
  }
81
6
 
82
7
  .gutter {
83
8
  flex: none;
84
9
  width: var(--dsl-read-gutter);
85
- padding-right: 14px;
10
+ margin-right: 12px;
86
11
  text-align: right;
87
12
  color: var(--dsw-alias-label-tertiary);
88
- /* The gutter is chrome, not content: keep it out of a text selection so a
89
- copy of the visible rows carries the source, not the line numbers. */
90
13
  user-select: none;
91
14
  }
92
15
 
93
16
  .content {
17
+ min-width: 0;
94
18
  color: var(--dsw-alias-label-primary);
95
19
  }
96
20
 
21
+ .block[data-code-wrap='true'] .line {
22
+ white-space: pre-wrap;
23
+ }
24
+
25
+ .block[data-code-wrap='true'] .content {
26
+ overflow-wrap: anywhere;
27
+ }
28
+
97
29
  .expand {
98
30
  display: block;
99
31
  width: 100%;
100
- padding: 0 0 0 var(--dsl-read-gutter);
32
+ padding: 0 0 0 calc(var(--dsl-read-gutter) + 12px);
101
33
  border: none;
102
- background-color: transparent;
34
+ background: transparent;
103
35
  color: var(--dsw-alias-label-tertiary);
104
36
  cursor: pointer;
105
37
  font: inherit;
@@ -1,6 +1,6 @@
1
1
  .confirmation {
2
2
  width: min(440px, 100%);
3
- max-height: calc(100vh - 48px);
3
+ max-height: 100%;
4
4
  overflow: hidden;
5
5
  }
6
6
 
@@ -0,0 +1,76 @@
1
+ /* The track is the same translucent fill the hover state uses, so it reads as
2
+ * a place rather than a second button; the one raised part is the indicator,
3
+ * a white pill that slides under the selected segment. Its stroke rides inside
4
+ * the soft elevation shadow (border: 0), the way every elevated surface draws
5
+ * its edge, so the elevation gate keeps it off the alias-border set.
6
+ *
7
+ * Segments are equal-width grid tracks sized to the widest label, which is
8
+ * what lets one indicator be placed arithmetically: the tablist carries the
9
+ * segment count and the selected index as custom properties, and the indicator
10
+ * derives its width and offset from them instead of measuring the DOM. */
11
+ .control {
12
+ position: relative;
13
+ display: inline-grid;
14
+ grid-auto-flow: column;
15
+ grid-auto-columns: 1fr;
16
+ gap: 2px;
17
+ padding: 3px;
18
+ border-radius: 9px;
19
+ background: var(--dsw-alias-interactive-bg-hover);
20
+ }
21
+
22
+ .indicator {
23
+ position: absolute;
24
+ top: 3px;
25
+ left: 3px;
26
+ width: calc((100% - 6px - 2px * (var(--dsh-segment-count) - 1)) / var(--dsh-segment-count));
27
+ height: calc(100% - 6px);
28
+ border: 0;
29
+ border-radius: 7px;
30
+ background: var(--dsw-alias-bg-layer-1);
31
+ box-shadow: var(--dsw-elevation-soft);
32
+ transform: translateX(calc(var(--dsh-segment-index) * (100% + 2px)));
33
+ transition: transform 160ms ease;
34
+ pointer-events: none;
35
+ }
36
+
37
+ .tab {
38
+ box-sizing: border-box;
39
+ position: relative;
40
+ z-index: 1;
41
+ height: 28px;
42
+ padding: 0 16px;
43
+ border: 0;
44
+ border-radius: 7px;
45
+ background: transparent;
46
+ color: var(--dsw-alias-label-secondary);
47
+ font: inherit;
48
+ font-size: 13px;
49
+ line-height: 20px;
50
+ font-weight: 500;
51
+ white-space: nowrap;
52
+ cursor: pointer;
53
+ transition: color 120ms ease;
54
+ }
55
+
56
+ .tab:hover:not(:disabled),
57
+ .tab[aria-selected='true'] {
58
+ color: var(--dsw-alias-label-primary);
59
+ }
60
+
61
+ .tab:disabled {
62
+ cursor: default;
63
+ opacity: 0.4;
64
+ }
65
+
66
+ .tab:focus-visible {
67
+ outline: 2px solid var(--dsw-alias-brand-primary);
68
+ outline-offset: -2px;
69
+ }
70
+
71
+ @media (prefers-reduced-motion: reduce) {
72
+ .indicator,
73
+ .tab {
74
+ transition: none;
75
+ }
76
+ }
@@ -0,0 +1,55 @@
1
+ .tabs {
2
+ position: relative;
3
+ display: grid;
4
+ padding: 4px;
5
+ border-radius: 12px;
6
+ background: var(--dsw-alias-bg-module-platform);
7
+ }
8
+
9
+ .indicator {
10
+ position: absolute;
11
+ inset: 4px auto 4px 4px;
12
+ box-sizing: border-box;
13
+ border: 0.5px solid var(--dsw-alias-border-l3);
14
+ border-radius: 8px;
15
+ background: var(--dsw-alias-bg-layer-3);
16
+ transition: transform 180ms ease;
17
+ pointer-events: none;
18
+ }
19
+
20
+ .tab {
21
+ position: relative;
22
+ justify-content: center;
23
+ height: 34px;
24
+ padding: 0 12px;
25
+ border-radius: 8px;
26
+ font-size: 14px;
27
+ line-height: 20px;
28
+ background: transparent;
29
+ color: var(--dsw-alias-label-secondary);
30
+ transition: color 180ms ease;
31
+ }
32
+
33
+ .tab:hover,
34
+ .tab[aria-selected='true'] {
35
+ background: transparent;
36
+ color: var(--dsw-alias-label-primary);
37
+ }
38
+
39
+ .tab[aria-selected='true'] {
40
+ font-weight: 600;
41
+ }
42
+
43
+ .tab:focus-visible {
44
+ outline: none;
45
+ text-decoration: underline;
46
+ text-decoration-thickness: 1px;
47
+ text-underline-offset: 4px;
48
+ }
49
+
50
+ @media (prefers-reduced-motion: reduce) {
51
+ .indicator,
52
+ .tab {
53
+ transition: none;
54
+ }
55
+ }
@@ -1,28 +1,10 @@
1
- /* Ongoing blue has no alias token (state-business-primary is the 500 step,
2
- * not this 450) — component-level var pinned to the static scale instead. */
3
- .dot,
4
- .matrix {
5
- --dsh-state-ongoing: var(--dsw-static-deepseek-450);
6
- }
7
-
8
- /* Solid states: same-color halo via a 0.10-opacity outer layer (::before)
9
- * with a 6/10-scale solid core. Layer color rides currentColor set per state. */
1
+ /* Solid states keep the 10px layout slot but paint one 6px currentColor core. */
10
2
  .dot {
11
3
  position: relative;
12
4
  display: inline-block;
13
5
  flex: none;
14
6
  }
15
7
 
16
- .dot::before {
17
- content: '';
18
- position: absolute;
19
- inset: 0;
20
- border-radius: 50%;
21
- corner-shape: round;
22
- background: currentColor;
23
- opacity: 0.1;
24
- }
25
-
26
8
  .dot::after {
27
9
  content: '';
28
10
  position: absolute;
@@ -44,30 +26,75 @@
44
26
  color: var(--dsw-alias-state-error-primary);
45
27
  }
46
28
 
47
- /* Idle is the absence of activity, not a fourth outcome: it stays on the
48
- * tertiary label color so it recedes beside the three outcome colors. */
29
+ /* Idle is the absence of activity and uses the theme's neutral state step. */
49
30
  .dot[data-state='idle'] {
50
- color: var(--dsw-alias-label-tertiary);
31
+ color: var(--dsw-alias-state-idle-primary);
51
32
  }
52
33
 
53
- /* Pixel chase: each outer cell holds a discrete brightness step (flat keyframe
54
- * holds, no tweening — the retro feel), peaking when the chase hits it and
55
- * decaying over the next three cells. Phase offsets come from per-rect
56
- * animation-delay (index * -125ms) set inline by the component. */
57
- .matrix {
34
+ /* Ongoing is the sole non-dot state: a quiet full ring sits behind a centered
35
+ * breathing arc while the complete glyph rotates continuously. Both animations
36
+ * share one 1.5s period so pinning their start times aligns them together. */
37
+ .spinner {
58
38
  flex: none;
59
- color: var(--dsh-state-ongoing);
39
+ color: var(--dsw-alias-label-tertiary);
40
+ }
41
+
42
+ .spinnerMotion {
43
+ transform-origin: center;
44
+ animation: dsh-state-dot-spin 1.5s linear infinite;
60
45
  }
61
46
 
62
- .cell {
63
- fill: currentColor;
64
- opacity: 0.15;
65
- animation: dsh-state-dot-chase 1s infinite;
47
+ .spinnerTrack,
48
+ .spinnerArc {
49
+ fill: none;
50
+ stroke: currentColor;
51
+ stroke-width: 2;
52
+ stroke-linecap: round;
66
53
  }
67
54
 
68
- @keyframes dsh-state-dot-chase {
69
- 0%, 12.4% { opacity: 1; }
70
- 12.5%, 24.9% { opacity: 0.6; }
71
- 25%, 37.4% { opacity: 0.35; }
72
- 37.5%, 100% { opacity: 0.15; }
55
+ .spinnerTrack {
56
+ opacity: 0.25;
73
57
  }
58
+
59
+ .spinnerArc {
60
+ stroke-dasharray: 12 150;
61
+ animation: dsh-state-dot-dash 1.5s ease-in-out infinite;
62
+ }
63
+
64
+ @keyframes dsh-state-dot-spin {
65
+ to { transform: rotate(360deg); }
66
+ }
67
+
68
+ @keyframes dsh-state-dot-dash {
69
+ 0% {
70
+ stroke-dasharray: 12 150;
71
+ stroke-dashoffset: 0;
72
+ }
73
+
74
+ 50% {
75
+ stroke-dasharray: 24 150;
76
+ stroke-dashoffset: -6;
77
+ }
78
+
79
+ 100% {
80
+ stroke-dasharray: 12 150;
81
+ stroke-dashoffset: 0;
82
+ }
83
+ }
84
+
85
+ @media (prefers-reduced-motion: reduce) {
86
+ .spinnerMotion,
87
+ .spinnerArc {
88
+ animation: none;
89
+ }
90
+
91
+ .spinnerArc {
92
+ stroke-dasharray: 18 150;
93
+ stroke-dashoffset: -3;
94
+ }
95
+ }
96
+
97
+ .step { display: inline-flex; flex: none; align-items: center; justify-content: center; box-sizing: border-box; border-radius: 50%; corner-shape: round; border: 1.5px solid var(--dsw-alias-label-tertiary); color: var(--dsw-alias-label-primary-foreground); }
98
+ .step[data-state='done'] { border-color: var(--dsw-alias-state-success-primary); background: var(--dsw-alias-state-success-primary); }
99
+ .step[data-state='error'] { border-color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-state-error-primary); }
100
+ .step[data-state='warning'] { border-color: var(--dsw-alias-state-warn-primary); }