@czi-sds/mcp 0.20.1 → 0.21.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.
@@ -12,21 +12,47 @@ The component's source code in the SDS codebase can be found [here](https://gith
12
12
  import { Tag } from "@czi-sds/components";
13
13
  ```
14
14
 
15
+ > **Coming from the Tag before SDS 25?** Its API changed: `sdsStyle` is now `"solid"` or `"outline"`, sizes are `"small"` and `"medium"`, and `sdsType` and `hover` are gone. The previous component still ships as TagLegacy, and the table under "Migrating from TagLegacy" maps each old prop to its new one.
16
+
15
17
  ## Code examples
16
18
 
17
19
  ### **Default Tag**
18
20
 
19
- A tag with nothing but a label, next to the same tag asking for the rounded shape the design guidance prefers.
21
+ A tag with nothing but a label: neutral, solid and medium.
20
22
 
21
23
  **Example: DefaultTag**
22
24
 
23
25
  ```tsx
24
- // A label is all a Tag needs. Everything else has a default: the neutral grey
25
- // intent, the small size, and the square shape.
26
+ // A label is all a Tag needs. Everything else has a default: the neutral intent,
27
+ // the solid style and the medium size. A tag is pill-shaped; only an icon-only
28
+ // tag is square.
29
+
30
+ import { Tag } from "@czi-sds/components";
31
+
32
+ function App() {
33
+ return (
34
+ <div className="app">
35
+ <Tag label="Science Design System" />
36
+ </div>
37
+ );
38
+ }
39
+
40
+ export default App;
41
+ ```
42
+
43
+ ### Styles and intents
44
+
45
+ The seven intents, drawn solid and then outlined. A tag left without a color is neutral.
46
+
47
+ **Example: TagStylesAndIntents**
48
+
49
+ ```tsx
50
+ // color carries the tag's intent. Seven are available, and neutral is what a tag
51
+ // falls back to when the prop is left out.
26
52
  //
27
- // Note that the component defaults to square while the design guidance above
28
- // treats rounded as the shape to reach for, so a tag that should match the rest
29
- // of the system asks for it: sdsStyle="rounded".
53
+ // sdsStyle picks how that intent is drawn: solid fills the tag with the intent
54
+ // color and sets the label white, outline tints the fill and draws the label,
55
+ // the icons and a 1px border in the intent color.
30
56
 
31
57
  import {
32
58
  Tag,
@@ -34,9 +60,23 @@ import {
34
60
  getSemanticColors,
35
61
  getSpaces,
36
62
  type CommonThemeProps,
63
+ type SdsTagIntent,
64
+ type SdsTagStyle,
37
65
  } from "@czi-sds/components";
38
66
  import styled from "@emotion/styled";
39
67
 
68
+ const INTENTS: SdsTagIntent[] = [
69
+ "accent",
70
+ "beta",
71
+ "info",
72
+ "negative",
73
+ "neutral",
74
+ "notice",
75
+ "positive",
76
+ ];
77
+
78
+ const STYLES: SdsTagStyle[] = ["solid", "outline"];
79
+
40
80
  const Stack = styled.div<CommonThemeProps>`
41
81
  ${(props) => {
42
82
  const spaces = getSpaces(props);
@@ -49,14 +89,14 @@ const Stack = styled.div<CommonThemeProps>`
49
89
  }}
50
90
  `;
51
91
 
52
- const Group = styled.div<CommonThemeProps>`
92
+ const Row = styled.div<CommonThemeProps>`
53
93
  ${(props) => {
54
94
  const spaces = getSpaces(props);
55
95
 
56
96
  return `
57
- align-items: center;
58
97
  display: flex;
59
- gap: ${spaces?.m}px;
98
+ flex-wrap: wrap;
99
+ gap: ${spaces?.s}px;
60
100
  `;
61
101
  }}
62
102
  `;
@@ -66,11 +106,11 @@ const Caption = styled.p<CommonThemeProps>`
66
106
 
67
107
  ${(props) => {
68
108
  const semanticColors = getSemanticColors(props);
109
+ const spaces = getSpaces(props);
69
110
 
70
111
  return `
71
112
  color: ${semanticColors?.base?.textSecondary};
72
- margin: 0;
73
- min-width: 110px;
113
+ margin: 0 0 ${spaces?.xxs}px;
74
114
  `;
75
115
  }}
76
116
  `;
@@ -79,15 +119,21 @@ function App() {
79
119
  return (
80
120
  <div className="app">
81
121
  <Stack>
82
- <Group>
83
- <Caption>Square</Caption>
84
- <Tag label="Science Design System" />
85
- </Group>
86
-
87
- <Group>
88
- <Caption>Rounded</Caption>
89
- <Tag label="Science Design System" sdsStyle="rounded" />
90
- </Group>
122
+ {STYLES.map((sdsStyle) => (
123
+ <div key={sdsStyle}>
124
+ <Caption>{sdsStyle}</Caption>
125
+ <Row>
126
+ {INTENTS.map((intent) => (
127
+ <Tag
128
+ color={intent}
129
+ key={intent}
130
+ label={intent}
131
+ sdsStyle={sdsStyle}
132
+ />
133
+ ))}
134
+ </Row>
135
+ </div>
136
+ ))}
91
137
  </Stack>
92
138
  </div>
93
139
  );
@@ -96,19 +142,16 @@ function App() {
96
142
  export default App;
97
143
  ```
98
144
 
99
- ### Intents
145
+ ### Sizes
100
146
 
101
- The six intents, drawn as primary and then as secondary. A tag left without a color is neutral.
147
+ The medium and small sizes, each with a label, a label and an icon, and the icon alone.
102
148
 
103
- **Example: TagIntents**
149
+ **Example: TagSizes**
104
150
 
105
151
  ```tsx
106
- // color carries the tag's intent. Six names are available, and neutral is what a
107
- // tag falls back to when the prop is left out.
108
- //
109
- // sdsType picks how that intent is drawn: primary fills the tag with the intent
110
- // color and sets the label white, secondary tints the fill and keeps the label in
111
- // the intent color behind a matching border.
152
+ // size is medium unless it is set to small. A medium tag is 24px tall with a 13px
153
+ // label and 12px icons; a small one is 20px tall with a 10px label and 10px
154
+ // icons. Small suits a dense row, like a navigation bar or a tab's label.
112
155
 
113
156
  import {
114
157
  Tag,
@@ -116,18 +159,9 @@ import {
116
159
  getSemanticColors,
117
160
  getSpaces,
118
161
  type CommonThemeProps,
119
- type SdsTagColorType,
120
162
  } from "@czi-sds/components";
121
163
  import styled from "@emotion/styled";
122
-
123
- const INTENTS: Extract<SdsTagColorType, string>[] = [
124
- "info",
125
- "positive",
126
- "notice",
127
- "negative",
128
- "neutral",
129
- "beta",
130
- ];
164
+ import { StackIcon } from "@phosphor-icons/react";
131
165
 
132
166
  const Stack = styled.div<CommonThemeProps>`
133
167
  ${(props) => {
@@ -141,14 +175,14 @@ const Stack = styled.div<CommonThemeProps>`
141
175
  }}
142
176
  `;
143
177
 
144
- const Row = styled.div<CommonThemeProps>`
178
+ const Group = styled.div<CommonThemeProps>`
145
179
  ${(props) => {
146
180
  const spaces = getSpaces(props);
147
181
 
148
182
  return `
183
+ align-items: center;
149
184
  display: flex;
150
- flex-wrap: wrap;
151
- gap: ${spaces?.s}px;
185
+ gap: ${spaces?.m}px;
152
186
  `;
153
187
  }}
154
188
  `;
@@ -158,11 +192,11 @@ const Caption = styled.p<CommonThemeProps>`
158
192
 
159
193
  ${(props) => {
160
194
  const semanticColors = getSemanticColors(props);
161
- const spaces = getSpaces(props);
162
195
 
163
196
  return `
164
197
  color: ${semanticColors?.base?.textSecondary};
165
- margin: 0 0 ${spaces?.xxs}px;
198
+ margin: 0;
199
+ min-width: 80px;
166
200
  `;
167
201
  }}
168
202
  `;
@@ -171,34 +205,74 @@ function App() {
171
205
  return (
172
206
  <div className="app">
173
207
  <Stack>
174
- <div>
175
- <Caption>Primary</Caption>
176
- <Row>
177
- {INTENTS.map((intent) => (
178
- <Tag
179
- color={intent}
180
- key={intent}
181
- label={intent}
182
- sdsStyle="rounded"
183
- />
184
- ))}
185
- </Row>
186
- </div>
208
+ <Group>
209
+ <Caption>medium</Caption>
210
+ <Tag color="accent" label="Annotated" />
211
+ <Tag
212
+ color="accent"
213
+ icon={<StackIcon weight="fill" />}
214
+ label="Annotated"
215
+ />
216
+ <Tag
217
+ color="accent"
218
+ icon={<StackIcon weight="fill" />}
219
+ iconOnly
220
+ label="Annotated"
221
+ />
222
+ <Tag color="accent" label="Annotated" sdsStyle="outline" />
223
+ <Tag
224
+ color="accent"
225
+ icon={<StackIcon weight="fill" />}
226
+ label="Annotated"
227
+ sdsStyle="outline"
228
+ />
229
+ <Tag
230
+ color="accent"
231
+ icon={<StackIcon weight="fill" />}
232
+ iconOnly
233
+ label="Annotated"
234
+ sdsStyle="outline"
235
+ />
236
+ </Group>
187
237
 
188
- <div>
189
- <Caption>Secondary</Caption>
190
- <Row>
191
- {INTENTS.map((intent) => (
192
- <Tag
193
- color={intent}
194
- key={intent}
195
- label={intent}
196
- sdsStyle="rounded"
197
- sdsType="secondary"
198
- />
199
- ))}
200
- </Row>
201
- </div>
238
+ <Group>
239
+ <Caption>small</Caption>
240
+ <Tag color="accent" label="Annotated" size="small" />
241
+ <Tag
242
+ color="accent"
243
+ icon={<StackIcon weight="fill" />}
244
+ label="Annotated"
245
+ size="small"
246
+ />
247
+ <Tag
248
+ color="accent"
249
+ icon={<StackIcon weight="fill" />}
250
+ iconOnly
251
+ label="Annotated"
252
+ size="small"
253
+ />
254
+ <Tag
255
+ color="accent"
256
+ label="Annotated"
257
+ size="small"
258
+ sdsStyle="outline"
259
+ />
260
+ <Tag
261
+ color="accent"
262
+ icon={<StackIcon weight="fill" />}
263
+ label="Annotated"
264
+ size="small"
265
+ sdsStyle="outline"
266
+ />
267
+ <Tag
268
+ color="accent"
269
+ icon={<StackIcon weight="fill" />}
270
+ iconOnly
271
+ label="Annotated"
272
+ size="small"
273
+ sdsStyle="outline"
274
+ />
275
+ </Group>
202
276
  </Stack>
203
277
  </div>
204
278
  );
@@ -209,28 +283,29 @@ export default App;
209
283
 
210
284
  ### Tag + Icon
211
285
 
212
- A large tag, which has to carry an icon, beside a small one. Each uses an Icon whose own size matches the size the tag will draw it at.
286
+ A leading icon from Phosphor or from `@czi-sds/icons`, and an icon-only tag inside the Tooltip that shows its label.
213
287
 
214
- **Example: TagIcon**
288
+ **Example: TagIcons**
215
289
 
216
290
  ```tsx
217
- // An icon goes on the leading edge of a tag, and the large size requires one:
218
- // TypeScript rejects sdsSize="l" without it.
291
+ // An icon goes on the leading edge of a tag. The tag draws it at its own size,
292
+ // 12px in a medium tag and 10px in a small one, and in its own color, so pass a
293
+ // bare icon: one from Phosphor, or one from @czi-sds/icons for the icons SDS
294
+ // draws itself.
219
295
  //
220
- // The tag draws whatever icon it is given at a fixed size: 12px in a small tag,
221
- // 24px in a large one. Match the Icon's own sdsSize to that so the artwork is
222
- // drawn at the size it was made for: s or xs for a small tag, l for a large one.
223
- // Not every icon exists at both sizes: Virus below is large-only, so asking for it
224
- // at a small size renders nothing and logs an error.
296
+ // iconOnly hides the label and squares the tag off. The label still names the
297
+ // tag for screen readers, but sighted users only see the icon, so an icon-only
298
+ // tag belongs in a Tooltip that shows the label.
225
299
 
226
- import { Icon, Tag } from "@czi-sds/components";
227
- import styled from "@emotion/styled";
228
300
  import {
229
- fontBodyXs,
230
- getSemanticColors,
301
+ Tag,
302
+ Tooltip,
231
303
  getSpaces,
232
304
  type CommonThemeProps,
233
305
  } from "@czi-sds/components";
306
+ import { StackIcon } from "@phosphor-icons/react";
307
+ import { CheckCircleIcon } from "@phosphor-icons/react";
308
+ import styled from "@emotion/styled";
234
309
 
235
310
  const Row = styled.div<CommonThemeProps>`
236
311
  ${(props) => {
@@ -245,16 +320,67 @@ const Row = styled.div<CommonThemeProps>`
245
320
  }}
246
321
  `;
247
322
 
248
- const Caption = styled.p<CommonThemeProps>`
249
- ${fontBodyXs}
323
+ function App() {
324
+ return (
325
+ <div className="app">
326
+ <Row>
327
+ <Tag
328
+ color="accent"
329
+ icon={<StackIcon weight="fill" />}
330
+ label="Generated"
331
+ />
332
+
333
+ <Tag
334
+ color="positive"
335
+ icon={<CheckCircleIcon />}
336
+ label="Verified"
337
+ sdsStyle="outline"
338
+ />
339
+
340
+ <Tooltip placement="top" title="Generated">
341
+ <Tag
342
+ color="accent"
343
+ icon={<StackIcon weight="fill" />}
344
+ iconOnly
345
+ label="Generated"
346
+ />
347
+ </Tooltip>
348
+ </Row>
349
+ </div>
350
+ );
351
+ }
352
+
353
+ export default App;
354
+ ```
355
+
356
+ ### Tag as a link
250
357
 
358
+ A tag with an `href` is a link. The arrow that says so is optional.
359
+
360
+ **Example: TagAsLink**
361
+
362
+ ```tsx
363
+ // href turns a tag into a link: it renders as an <a>, takes a tab stop, and
364
+ // follows its href on click and on Enter. showLinkIcon adds an arrow after the
365
+ // label to say the tag leads somewhere. The arrow is optional; a tag with an
366
+ // href is a link either way.
367
+ //
368
+ // target="_blank" also sets rel="noopener noreferrer". For client-side routing,
369
+ // pass your router's link component as component and the tag renders that
370
+ // instead of a plain anchor.
371
+
372
+ import { Tag, getSpaces, type CommonThemeProps } from "@czi-sds/components";
373
+ import styled from "@emotion/styled";
374
+
375
+ const Row = styled.div<CommonThemeProps>`
251
376
  ${(props) => {
252
- const semanticColors = getSemanticColors(props);
253
377
  const spaces = getSpaces(props);
254
378
 
255
379
  return `
256
- color: ${semanticColors?.base?.textSecondary};
257
- margin: 0 0 ${spaces?.xxs}px;
380
+ align-items: center;
381
+ display: flex;
382
+ flex-wrap: wrap;
383
+ gap: ${spaces?.m}px;
258
384
  `;
259
385
  }}
260
386
  `;
@@ -262,23 +388,29 @@ const Caption = styled.p<CommonThemeProps>`
262
388
  function App() {
263
389
  return (
264
390
  <div className="app">
265
- <Caption>
266
- A large tag with a large icon, and a small tag with a small one
267
- </Caption>
268
391
  <Row>
269
392
  <Tag
270
- color="negative"
271
- icon={<Icon sdsIcon="Virus" sdsSize="l" />}
272
- label="Virus"
273
- sdsSize="l"
274
- sdsStyle="rounded"
393
+ color="info"
394
+ href="https://sds.czi.design"
395
+ label="SDS website"
396
+ showLinkIcon
397
+ target="_blank"
398
+ />
399
+
400
+ <Tag
401
+ color="info"
402
+ href="https://sds.czi.design"
403
+ label="SDS website"
404
+ sdsStyle="outline"
405
+ showLinkIcon
406
+ target="_blank"
275
407
  />
276
408
 
277
409
  <Tag
278
- color="negative"
279
- icon={<Icon sdsIcon="Bacteria" sdsSize="s" />}
280
- label="Bacteria"
281
- sdsStyle="rounded"
410
+ color="info"
411
+ href="https://sds.czi.design"
412
+ label="Without the arrow"
413
+ target="_blank"
282
414
  />
283
415
  </Row>
284
416
  </div>
@@ -288,29 +420,29 @@ function App() {
288
420
  export default App;
289
421
  ```
290
422
 
291
- ### Tag with custom colors
423
+ ### Clickable, static and disabled tags
292
424
 
293
- Colors given as a tuple, for a tag the intents do not cover. The two-value form leaves the icon white, so a tag with an icon wants all three.
425
+ A tag with an `onClick` behaves as a button for both the pointer and the keyboard. A tag without one stays a static label, and `disabled` turns a clickable tag off.
294
426
 
295
- **Example: TagWithCustomColors**
427
+ **Example: InteractiveTag**
296
428
 
297
429
  ```tsx
298
- // color also takes a tuple of CSS colors for a tag the intents do not cover:
299
- // [label, background] or [label, background, icon].
430
+ // A tag with an onClick becomes a button: it takes a tab stop, answers Enter and
431
+ // Space, reports its label as its name, and darkens under the pointer. A tag with
432
+ // neither an onClick nor an href is static: no role, no tab stop and no hover
433
+ // state, so it reads as a label rather than a control.
300
434
  //
301
- // The two-value form does not touch the icon, which stays white and disappears on
302
- // a light background (the first tag below). A tag with an icon wants the
303
- // three-value form, and the contrast of whatever colors you pick is on you, since
304
- // SDS only guarantees it for its own intents.
435
+ // disabled greys a clickable tag out and takes it out of the tab order.
305
436
 
306
- import { Icon, Tag } from "@czi-sds/components";
307
- import styled from "@emotion/styled";
308
437
  import {
438
+ Tag,
309
439
  fontBodyXs,
310
440
  getSemanticColors,
311
441
  getSpaces,
312
442
  type CommonThemeProps,
313
443
  } from "@czi-sds/components";
444
+ import styled from "@emotion/styled";
445
+ import { useState } from "react";
314
446
 
315
447
  const Stack = styled.div<CommonThemeProps>`
316
448
  ${(props) => {
@@ -345,45 +477,58 @@ const Caption = styled.p<CommonThemeProps>`
345
477
  return `
346
478
  color: ${semanticColors?.base?.textSecondary};
347
479
  margin: 0;
348
- min-width: 180px;
480
+ min-width: 150px;
481
+ `;
482
+ }}
483
+ `;
484
+
485
+ const Readout = styled.p<CommonThemeProps>`
486
+ ${fontBodyXs}
487
+
488
+ ${(props) => {
489
+ const semanticColors = getSemanticColors(props);
490
+
491
+ return `
492
+ color: ${semanticColors?.base?.textSecondary};
493
+ margin: 0;
349
494
  `;
350
495
  }}
351
496
  `;
352
497
 
353
498
  function App() {
499
+ const [clicks, setClicks] = useState(0);
500
+
354
501
  return (
355
502
  <div className="app">
356
503
  <Stack>
357
504
  <Group>
358
- <Caption>[label, background]</Caption>
505
+ <Caption>Clickable</Caption>
359
506
  <Tag
360
- color={["#3b2f00", "#ffe6a0"]}
361
- icon={<Icon sdsIcon="Star" sdsSize="l" />}
362
- label="Icon left white"
363
- sdsSize="l"
364
- sdsStyle="rounded"
507
+ color="info"
508
+ label="Filter by species"
509
+ onClick={() => setClicks((count) => count + 1)}
365
510
  />
366
511
  </Group>
367
512
 
368
513
  <Group>
369
- <Caption>[label, background, icon]</Caption>
370
- <Tag
371
- color={["#3b2f00", "#ffe6a0", "#b26f00"]}
372
- icon={<Icon sdsIcon="Star" sdsSize="l" />}
373
- label="Icon colored too"
374
- sdsSize="l"
375
- sdsStyle="rounded"
376
- />
514
+ <Caption>Static</Caption>
515
+ <Tag label="Read-only label" />
377
516
  </Group>
378
517
 
379
518
  <Group>
380
- <Caption>No icon, two values</Caption>
519
+ <Caption>Disabled</Caption>
381
520
  <Tag
382
- color={["#3b2f00", "#ffe6a0"]}
383
- label="Label and background"
384
- sdsStyle="rounded"
521
+ color="info"
522
+ disabled
523
+ label="Filter by tissue"
524
+ onClick={() => setClicks((count) => count + 1)}
385
525
  />
386
526
  </Group>
527
+
528
+ <Readout>
529
+ The clickable tag has been activated {clicks}{" "}
530
+ {clicks === 1 ? "time" : "times"}, by pointer or by keyboard.
531
+ </Readout>
387
532
  </Stack>
388
533
  </div>
389
534
  );
@@ -392,21 +537,20 @@ function App() {
392
537
  export default App;
393
538
  ```
394
539
 
395
- ### Clickable and static tags
540
+ ### Tag with custom colors
396
541
 
397
- A tag with an `onClick` behaves as a button for both the pointer and the keyboard. A tag that only labels something uses `hover={false}` so it stops looking clickable.
542
+ Colors given as a tuple, for a tag the intents do not cover.
398
543
 
399
- **Example: InteractiveTag**
544
+ **Example: TagWithCustomColors**
400
545
 
401
546
  ```tsx
402
- // A tag with an onClick becomes a button: it takes a tab stop, answers Enter and
403
- // Space, and reports its label as its name. Use it for a tag that filters or
404
- // navigates, not for one that only labels something.
547
+ // color also takes a tuple of CSS colors for a tag the intents do not cover:
548
+ // [label, background] or [label, background, icon]. The icon takes the label
549
+ // color when the third value is left out, and an outline tag draws its border
550
+ // from the label color too.
405
551
  //
406
- // Every tag darkens under the pointer and shows a pointer cursor by default,
407
- // including one that does nothing. hover={false} turns that off, and with it all
408
- // pointer events, so a static tag stops looking clickable, but it also stops
409
- // answering the mouse and stops triggering a Tooltip wrapped around it.
552
+ // The contrast of whatever colors you pick is on you, since SDS only guarantees
553
+ // it for its own intents.
410
554
 
411
555
  import {
412
556
  Tag,
@@ -416,7 +560,7 @@ import {
416
560
  type CommonThemeProps,
417
561
  } from "@czi-sds/components";
418
562
  import styled from "@emotion/styled";
419
- import { useState } from "react";
563
+ import { StarIcon } from "@phosphor-icons/react";
420
564
 
421
565
  const Stack = styled.div<CommonThemeProps>`
422
566
  ${(props) => {
@@ -451,49 +595,41 @@ const Caption = styled.p<CommonThemeProps>`
451
595
  return `
452
596
  color: ${semanticColors?.base?.textSecondary};
453
597
  margin: 0;
454
- min-width: 150px;
455
- `;
456
- }}
457
- `;
458
-
459
- const Readout = styled.p<CommonThemeProps>`
460
- ${fontBodyXs}
461
-
462
- ${(props) => {
463
- const semanticColors = getSemanticColors(props);
464
-
465
- return `
466
- color: ${semanticColors?.base?.textSecondary};
467
- margin: 0;
598
+ min-width: 180px;
468
599
  `;
469
600
  }}
470
601
  `;
471
602
 
472
603
  function App() {
473
- const [clicks, setClicks] = useState(0);
474
-
475
604
  return (
476
605
  <div className="app">
477
606
  <Stack>
478
607
  <Group>
479
- <Caption>Clickable</Caption>
608
+ <Caption>[label, background]</Caption>
480
609
  <Tag
481
- color="info"
482
- label="Filter by species"
483
- onClick={() => setClicks((count) => count + 1)}
484
- sdsStyle="rounded"
610
+ color={["#3b2f00", "#ffe6a0"]}
611
+ icon={<StarIcon />}
612
+ label="Icon takes the label color"
485
613
  />
486
614
  </Group>
487
615
 
488
616
  <Group>
489
- <Caption>Static</Caption>
490
- <Tag hover={false} label="Read-only label" sdsStyle="rounded" />
617
+ <Caption>[label, background, icon]</Caption>
618
+ <Tag
619
+ color={["#3b2f00", "#ffe6a0", "#b26f00"]}
620
+ icon={<StarIcon />}
621
+ label="Icon colored on its own"
622
+ />
491
623
  </Group>
492
624
 
493
- <Readout>
494
- The clickable tag has been activated {clicks}{" "}
495
- {clicks === 1 ? "time" : "times"}, by pointer or by keyboard.
496
- </Readout>
625
+ <Group>
626
+ <Caption>Outline</Caption>
627
+ <Tag
628
+ color={["#3b2f00", "#fff8e0"]}
629
+ label="Border from the label"
630
+ sdsStyle="outline"
631
+ />
632
+ </Group>
497
633
  </Stack>
498
634
  </div>
499
635
  );
@@ -509,13 +645,10 @@ What a long label does in a narrow container, and the Tooltip that keeps the tru
509
645
  **Example: TagWithLongLabel**
510
646
 
511
647
  ```tsx
512
- // A tag never wraps: it stretches to its label and then, in a container too narrow
513
- // for it, cuts the label off with an ellipsis. The text that is cut is not
648
+ // A tag never wraps: it stretches to its label and then, in a container too
649
+ // narrow for it, cuts the label off with an ellipsis. The text that is cut is not
514
650
  // available anywhere else, so a tag that can hold a long value belongs in a
515
651
  // Tooltip that carries the whole thing.
516
- //
517
- // The tooltip only works because the tag still receives pointer events. Adding
518
- // hover={false} to quiet the hover styling would silence the tooltip with it.
519
652
 
520
653
  import {
521
654
  Tag,
@@ -544,10 +677,11 @@ const Stack = styled.div<CommonThemeProps>`
544
677
  const NarrowColumn = styled.div<CommonThemeProps>`
545
678
  ${(props) => {
546
679
  const semanticColors = getSemanticColors(props);
680
+ const spaces = getSpaces(props);
547
681
 
548
682
  return `
549
683
  border: 1px dashed ${semanticColors?.base?.divider};
550
- padding: 4px;
684
+ padding: ${spaces?.xxs}px;
551
685
  width: 160px;
552
686
  `;
553
687
  }}
@@ -573,7 +707,7 @@ function App() {
573
707
  <Stack>
574
708
  <div>
575
709
  <Caption>Room for the whole label</Caption>
576
- <Tag label={LABEL} sdsStyle="rounded" />
710
+ <Tag label={LABEL} />
577
711
  </div>
578
712
 
579
713
  <div>
@@ -582,7 +716,7 @@ function App() {
582
716
  </Caption>
583
717
  <NarrowColumn>
584
718
  <Tooltip placement="top" title={LABEL}>
585
- <Tag label={LABEL} sdsStyle="rounded" />
719
+ <Tag label={LABEL} />
586
720
  </Tooltip>
587
721
  </NarrowColumn>
588
722
  </div>
@@ -596,21 +730,21 @@ export default App;
596
730
 
597
731
  ## SDS vs MUI
598
732
 
599
- SDS Tag wraps MUI's Chip and replaces its palette, sizing and shape with SDS ones. SDS also splits the Chip's two jobs in two: Tag labels something, and TagFilter is the tag that can be removed. The differences from MUI are these:
733
+ SDS Tag wraps MUI's Chip and replaces its palette, sizing and shape with SDS ones. SDS also splits the Chip's two jobs in two: Tag labels something, and Chip is the one that can be removed. The differences from MUI are these:
600
734
 
601
- - `sdsStyle`: `"square"` or `"rounded"`. The component defaults to `"square"`, while the design guidance above treats rounded as the shape to reach for, so a tag that should match the rest of the system asks for it explicitly.
735
+ - `sdsStyle`: `"solid"` (default) fills the tag with the intent color and sets the label white; `"outline"` tints the fill and draws the label, the icons and a 1px border in the intent color. It replaces MUI's `variant`, which Tag does not take.
602
736
 
603
- - `sdsType`: `"primary"` (default) fills the tag with the intent color and sets the label white; `"secondary"` tints the fill, keeps the label in the intent color and draws a 1px border in it.
737
+ - `size`: MUI's own prop, but with SDS measurements. `"medium"` (default) is 24px tall with a 13px label and 12px icons, `"small"` is 20px tall with a 10px label and 10px icons.
604
738
 
605
- - `sdsSize`: `"s"` (default) or `"l"`, which is the pair of sizes the design carries rather than MUI's `"small"` and `"medium"`. A large tag must have an icon: TypeScript rejects `sdsSize="l"` without one.
739
+ - `color`: an SDS intent rather than one of MUI's palette names. Seven intents are available, `"accent"` among them, and a tag without the prop is neutral. It also takes a tuple of CSS colors for cases the intents do not cover.
606
740
 
607
- - `color`: an SDS intent rather than one of MUI's palette names, since SDS hands MUI a fixed color of its own and uses this prop for the fill, the label and the icon. Six intents are available and a tag without the prop is neutral. It also takes a tuple of CSS colors for cases the intents do not cover.
741
+ - **The shape follows the content:** a tag with a label is pill-shaped, and an icon-only tag (`iconOnly`) is square with 4px corners. There is no prop for it.
608
742
 
609
- - `hover`: on by default, which draws the hover and pressed states and a pointer cursor on every tag, whether or not anything happens when it is clicked. Setting it to `false` removes those states by taking the tag out of pointer events entirely, which also stops a Tooltip wrapped around it from opening.
743
+ - **Links:** `href` renders the tag as an `<a>`, which MUI's Chip only does when it is also handed `component="a"` and `clickable`. Passing `component` still works, so a router's link component can take the anchor's place. `showLinkIcon` adds Phosphor's ArrowUpRight after the label.
610
744
 
611
- - **Sizing and variant props do nothing:** MUI's `size` and `variant` set their classes but SDS pins the height, padding, fill and border, so a tag looks the same either way. MUI's `clickable` does have an effect, and not a useful one: it makes the tag a tab stop that does nothing. Pass `onClick` instead.
745
+ - **Interactive styling follows behavior:** the hover and pressed states, the pointer cursor and the focus ring appear only on a tag with an `onClick` or an `href`, or one set `clickable`. A tag that only labels something has none of them.
612
746
 
613
- - **onDelete works, but off-brand:** a Tag given `onDelete` does become deletable, drawing MUI's filled-circle Cancel icon rather than the SDS X. Reach for TagFilter when a tag needs to come off.
747
+ - **onDelete works, but Chip is the removable tag:** a Tag given `onDelete` becomes deletable and draws MUI's Cancel icon at the tag's icon size. Reach for Chip when a tag needs to come off.
614
748
 
615
749
  ## MUI Documentation
616
750
 
@@ -618,30 +752,53 @@ Documentation for the underlying MUI component can be found [here](https://mui.c
618
752
 
619
753
  ## Behavior and accessibility
620
754
 
621
- - A plain tag is a div holding its label, with no role and no tab stop, and is read as the text it shows. Giving it an `onClick` turns it into a button: it takes a tab stop, answers Enter and Space, and takes its accessible name from the label.
755
+ - A plain tag is a div holding its label, with no role and no tab stop, and is read as the text it shows. Giving it an `onClick` turns it into a button: it takes a tab stop, answers Enter and Space, and takes its accessible name from the label. Giving it an `href` turns it into a link instead.
622
756
 
623
- - Because `hover` is on by default, a tag that only labels something still darkens under the pointer and shows a pointer cursor, which reads as clickable. `hover={false}` settles that, at the cost of all pointer events, so do not combine it with `onClick`, which would leave the tag reachable by keyboard but dead to the mouse.
757
+ - `disabled` greys a tag out. On a clickable tag or a link it also sets `aria-disabled` and takes the tag out of the tab order; on a static tag it changes nothing but the colors, so say in the label why the thing it describes is unavailable.
624
758
 
625
759
  - Nothing about the intent reaches assistive technology: `color` is color only, so a negative tag needs a label that says what is wrong rather than relying on red.
626
760
 
761
+ - An icon-only tag keeps its label as its accessible name, hidden from view but read by screen readers. Sighted users see only the icon, so wrap the tag in a Tooltip that shows the label.
762
+
763
+ - The link arrow is decorative and hidden from screen readers. A link that opens in a new tab with `target="_blank"` should say so in its label or in text beside it.
764
+
627
765
  - A tag never wraps. It stretches to fit its label and then, in a container too narrow, cuts the label off with an ellipsis, leaving the rest of the text nowhere else to be read. A tag that can hold a long value belongs in a Tooltip carrying the whole string.
628
766
 
629
- - Icons are drawn at a fixed size (12px in a small tag, 24px in a large one) whatever size the Icon itself asks for, so match the Icon's `sdsSize` to the tag: `"s"` or `"xs"` for a small tag, `"l"` for a large one. Some icons exist at only one of those sizes, Star and Virus among them, and asking for one at a size it does not have renders nothing and logs an error.
767
+ - Icons are drawn at the tag's icon size (12px in a medium tag, 10px in a small one) and in its icon color, whatever the icon itself asks for, so pass a bare Phosphor or `@czi-sds/icons` icon. The deprecated SDS Icon still works, as long as the icon exists at a small size.
768
+
769
+ - Contrast is yours to check when you pass custom colors. SDS only guarantees it for its own intents.
770
+
771
+ ## Migrating from TagLegacy
772
+
773
+ The Tag before SDS 25 is still exported as `TagLegacy`, so an app can switch its imports first and migrate each usage later. Its props map to the new Tag like this:
630
774
 
631
- - The two-value form of a custom `color` leaves the icon white rather than coloring it with the label, so it vanishes on a light fill; a tag with an icon wants the three-value form. On a secondary tag, custom colors also draw the border in the fill color, which hides it. Contrast is yours to check. SDS only guarantees it for its own intents.
775
+ | Old prop | Replace with |
776
+ | ---------------------------------- | --------------------------------------------------------------------------------- |
777
+ | `sdsType="primary"` | `sdsStyle="solid"`, the default |
778
+ | `sdsType="secondary"` | `sdsStyle="outline"` |
779
+ | `sdsStyle="rounded"` or `"square"` | Nothing: a tag with a label is always pill-shaped, and an icon-only tag is square |
780
+ | `sdsSize="s"` | `size="small"` |
781
+ | `sdsSize="l"` | `size="medium"`, the default, which no longer needs an icon |
782
+ | `hover={false}` | Nothing: a tag without an `onClick` or an `href` is static |
783
+ | `tagColor` | `color` |
784
+ | `color={[label, background]}` | Unchanged, though the icon now takes the label color rather than staying white |
632
785
 
633
786
  ## Props
634
787
 
635
788
  Any custom SDS props and MUI props required for implementation are found on the table below. See the MUI documentation for additional optional props.
636
789
 
637
- | Name | Type | Default | Description |
638
- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
639
- | `label` | `string` | - (required) | The tag's text, and its accessible name. It is cut off with an ellipsis rather than wrapped when there is not room for it. |
640
- | `color` | `"info"` \| `"positive"` \| `"notice"` \| `"negative"` \| `"neutral"` \| `"beta"` \| `[string, string]` \| `[string, string, string]` | `"neutral"` | The tag's intent, given either as one of the six names or as CSS colors: 1. [label color, background color]: the icon stays white 2. [label color, background color, icon color] |
641
- | `sdsType` | `"primary"` \| `"secondary"` | `"primary"` | primary fills the tag with the intent color and sets the label white. secondary tints the fill and keeps the label and a 1px border in the intent color. |
642
- | `sdsStyle` | `"square"` \| `"rounded"` | `"square"` | The shape of the tag. The design guidance treats rounded as the default for the system, so it is usually worth passing. |
643
- | `sdsSize` | `"s" \| "l"` | `"s"` | The size of the tag: an 11px label with a 12px icon, or a 13px label with a 24px icon. A large tag has to have an icon. |
644
- | `icon` | `JSX.Element` | - | An icon on the leading edge, usually an SDS Icon. It is drawn at the tag's icon size whatever size it asks for, so match its `sdsSize` to the tag. |
645
- | `hover` | `boolean` | `true` | Whether the tag responds to the pointer with a darker fill and a pointer cursor. False removes the tag from pointer events altogether, which also keeps a surrounding Tooltip from opening. |
646
- | `onClick` | `(event) => void` | - | MUI's prop. Makes the tag a button: a tab stop that answers Enter and Space. |
647
- | `onDelete` | `(event) => void` | - | MUI's prop. Adds a delete control, drawn with MUI's Cancel icon rather than the SDS X. Use TagFilter for a removable tag instead. |
790
+ | Name | Type | Default | Description |
791
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
792
+ | `label` | `string` | - (required) | The tag's text, and its accessible name. It is cut off with an ellipsis rather than wrapped, so keep it to a word or two. An icon-only tag still needs it: the text is hidden from view but read by screen readers. |
793
+ | `sdsStyle` | `"solid"` \| `"outline"` | `"solid"` | `solid` fills the tag with the intent color. `outline` tints the fill and draws the label, the icons and a 1px border in the intent color. |
794
+ | `size` | `"small"` \| `"medium"` | `"medium"` | `medium` is 24px tall with a 13px label and 12px icons, `small` is 20px tall with a 10px label and 10px icons. |
795
+ | `color` | `"accent"` \| `"beta"` \| `"info"` \| `"negative"` \| `"neutral"` \| `"notice"` \| `"positive"` \| `[string, string]` \| `[string, string, string]` | `"neutral"` | One of the seven intents, or CSS colors for a tag the intents do not cover: `[label, background]` or `[label, background, icon]`. The icon takes the label color when the third value is left out. |
796
+ | `icon` | `ReactElement` | - | An icon on the leading edge, usually a Phosphor or `@czi-sds/icons` icon. The tag draws it at its own icon size and color. |
797
+ | `iconOnly` | `boolean` | `false` | Draws only the icon, in a square tag. The label is hidden from view but stays the tag's accessible name. Has no effect without an icon. |
798
+ | `showLinkIcon` | `boolean` | `false` | Adds an arrow after the label to say the tag leads somewhere. Usually paired with `href`, though a tag can be a link without it. |
799
+ | `href` | `string` | - | Turns the tag into a link. It renders as an `<a>` unless `component` says otherwise, as with a router's link component. |
800
+ | `target` | `string` | - | Where the link opens. `_blank` also sets `rel="noopener noreferrer"`. |
801
+ | `rel` | `string` | - | The link's relationship to the current page. |
802
+ | `disabled` | `boolean` | `false` | Greys the tag out. On a clickable tag or a link it also takes the tag out of the tab order. |
803
+ | `onClick` | `(event) => void` | - | MUI's prop. Makes the tag a button: a tab stop that answers Enter and Space, with hover and pressed states. |
804
+ | `onDelete` | `(event) => void` | - | MUI's prop. Adds a delete control, drawn with MUI's Cancel icon. Use Chip for a removable tag instead. |