@czi-sds/mcp 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +2 -1
  2. package/data/component-docs/Accordion.md +291 -0
  3. package/data/component-docs/Autocomplete.md +525 -0
  4. package/data/component-docs/Banner.md +231 -0
  5. package/data/component-docs/Button.md +224 -0
  6. package/data/component-docs/ButtonDropdown.md +256 -0
  7. package/data/component-docs/ButtonGroup.md +320 -0
  8. package/data/component-docs/ButtonToggle.md +397 -0
  9. package/data/component-docs/Callout.md +268 -0
  10. package/data/component-docs/CellBasic.md +122 -0
  11. package/data/component-docs/CellComponent.md +63 -0
  12. package/data/component-docs/CellHeader.md +128 -0
  13. package/data/component-docs/ComplexFilter.md +294 -0
  14. package/data/component-docs/ContentCard.md +406 -0
  15. package/data/component-docs/Dialog.md +651 -0
  16. package/data/component-docs/Dropdown.md +155 -0
  17. package/data/component-docs/DropdownMenu.md +949 -0
  18. package/data/component-docs/HeatmapChart.md +782 -0
  19. package/data/component-docs/Hero.md +363 -0
  20. package/data/component-docs/Icon.md +283 -0
  21. package/data/component-docs/InputCheckbox.md +205 -0
  22. package/data/component-docs/InputDropdown.md +482 -0
  23. package/data/component-docs/InputRadio.md +173 -0
  24. package/data/component-docs/InputSearch.md +81 -0
  25. package/data/component-docs/InputSlider.md +189 -0
  26. package/data/component-docs/InputText.md +110 -0
  27. package/data/component-docs/InputToggle.md +141 -0
  28. package/data/component-docs/IntentMessage.md +293 -0
  29. package/data/component-docs/Legend.md +442 -0
  30. package/data/component-docs/Link.md +23 -0
  31. package/data/component-docs/List.md +300 -0
  32. package/data/component-docs/LoadingIndicator.md +198 -0
  33. package/data/component-docs/Menu.md +298 -0
  34. package/data/component-docs/MenuItem.md +100 -0
  35. package/data/component-docs/NavigationFooter.md +165 -0
  36. package/data/component-docs/NavigationHeader.md +500 -0
  37. package/data/component-docs/NavigationJumpTo.md +414 -0
  38. package/data/component-docs/Notification.md +288 -0
  39. package/data/component-docs/Pagination.md +67 -0
  40. package/data/component-docs/Panel.md +636 -0
  41. package/data/component-docs/Popover.md +448 -0
  42. package/data/component-docs/PreComposedTable.md +2947 -0
  43. package/data/component-docs/ProteinStructureViewer.md +1044 -0
  44. package/data/component-docs/SegmentedControl.md +396 -0
  45. package/data/component-docs/StackedBarChart.md +475 -0
  46. package/data/component-docs/Table.md +2000 -0
  47. package/data/component-docs/TableHeader.md +53 -0
  48. package/data/component-docs/TableRow.md +74 -0
  49. package/data/component-docs/Tabs.md +455 -0
  50. package/data/component-docs/Tag.md +647 -0
  51. package/data/component-docs/TagFilter.md +131 -0
  52. package/data/component-docs/Tooltip.md +561 -0
  53. package/data/component-docs/TooltipCondensed.md +203 -0
  54. package/data/component-docs/TooltipTable.md +145 -0
  55. package/data/component-docs/index.json +267 -0
  56. package/data/component-list.json +2 -5
  57. package/data/component-props/Accordion.json +31 -11
  58. package/data/component-props/Alert.json +3 -3
  59. package/data/component-props/Autocomplete.json +150 -15
  60. package/data/component-props/Banner.json +31 -17
  61. package/data/component-props/Button.json +45 -20
  62. package/data/component-props/ButtonDropdown.json +36 -17
  63. package/data/component-props/ButtonDropdownLegacy.json +11 -11
  64. package/data/component-props/ButtonGroup.json +32 -11
  65. package/data/component-props/ButtonIcon.json +10 -10
  66. package/data/component-props/ButtonLegacy.json +19 -19
  67. package/data/component-props/ButtonToggle.json +32 -19
  68. package/data/component-props/ButtonToggleLegacy.json +11 -11
  69. package/data/component-props/Callout.json +58 -17
  70. package/data/component-props/CellBasic.json +94 -41
  71. package/data/component-props/CellComponent.json +23 -7
  72. package/data/component-props/CellHeader.json +62 -19
  73. package/data/component-props/ComplexFilter.json +52 -21
  74. package/data/component-props/ContentCard.json +70 -47
  75. package/data/component-props/Dialog.json +47 -14
  76. package/data/component-props/Dropdown.json +68 -31
  77. package/data/component-props/DropdownMenu.json +128 -23
  78. package/data/component-props/HeatmapChart.json +88 -12
  79. package/data/component-props/Hero.json +80 -39
  80. package/data/component-props/Icon.json +20 -9
  81. package/data/component-props/InputCheckbox.json +36 -14
  82. package/data/component-props/InputDropdown.json +75 -27
  83. package/data/component-props/InputRadio.json +31 -13
  84. package/data/component-props/InputSearch.json +41 -18
  85. package/data/component-props/InputSlider.json +46 -19
  86. package/data/component-props/InputText.json +26 -19
  87. package/data/component-props/InputToggle.json +26 -9
  88. package/data/component-props/IntentMessage.json +38 -9
  89. package/data/component-props/Link.json +13 -7
  90. package/data/component-props/LoadingIndicator.json +11 -3
  91. package/data/component-props/MenuItem.json +38 -19
  92. package/data/component-props/MenuSelect.json +8 -8
  93. package/data/component-props/NavigationFooter.json +49 -19
  94. package/data/component-props/NavigationHeader.json +150 -37
  95. package/data/component-props/NavigationJumpTo.json +33 -7
  96. package/data/component-props/Notification.json +39 -21
  97. package/data/component-props/Pagination.json +39 -11
  98. package/data/component-props/Panel.json +62 -9
  99. package/data/component-props/Popover.json +80 -7
  100. package/data/component-props/PreComposedTable.json +99 -27
  101. package/data/component-props/ProteinStructureViewer.json +117 -0
  102. package/data/component-props/SegmentedControl.json +42 -3
  103. package/data/component-props/StackedBarChart.json +116 -42
  104. package/data/component-props/TableRow.json +38 -19
  105. package/data/component-props/Tabs.json +66 -9
  106. package/data/component-props/Tag.json +34 -17
  107. package/data/component-props/TagFilter.json +10 -3
  108. package/data/component-props/Tooltip.json +49 -18
  109. package/data/component-props/TooltipCondensed.json +18 -9
  110. package/data/component-props/TooltipTable.json +17 -7
  111. package/data/resources/{zeroheight-rules.md → documentation-rules.md} +43 -30
  112. package/data/resources/figma-rules.md +2 -2
  113. package/data/tailwind.json +20 -14
  114. package/dist/stdio.js +112 -208
  115. package/package.json +9 -5
  116. package/data/component-props/ButtonDropdownV2.json +0 -34
  117. package/data/component-props/ButtonNew.json +0 -47
  118. package/data/component-props/ButtonToggleV2.json +0 -44
  119. package/data/component-props/ButtonV2.json +0 -43
  120. package/data/zeroheight-cache.json +0 -10259
  121. package/data/zeroheight-exports/Accordion.md +0 -774
  122. package/data/zeroheight-exports/Banner.md +0 -691
  123. package/data/zeroheight-exports/Borders.md +0 -117
  124. package/data/zeroheight-exports/Buttons.md +0 -1854
  125. package/data/zeroheight-exports/Callout.md +0 -931
  126. package/data/zeroheight-exports/Colors.md +0 -1755
  127. package/data/zeroheight-exports/Content-Card.md +0 -309
  128. package/data/zeroheight-exports/Contributing-to-SDS.md +0 -98
  129. package/data/zeroheight-exports/Control-Inputs.md +0 -2818
  130. package/data/zeroheight-exports/Corners.md +0 -104
  131. package/data/zeroheight-exports/Cover-page.md +0 -3
  132. package/data/zeroheight-exports/Dialog.md +0 -1367
  133. package/data/zeroheight-exports/Downloads.md +0 -236
  134. package/data/zeroheight-exports/Drop-Shadows.md +0 -79
  135. package/data/zeroheight-exports/Dropdown-Input.md +0 -1776
  136. package/data/zeroheight-exports/Dropdown-Menus.md +0 -913
  137. package/data/zeroheight-exports/Element-Status-Tracker.md +0 -267
  138. package/data/zeroheight-exports/Field-Inputs.md +0 -1405
  139. package/data/zeroheight-exports/Filters.md +0 -1111
  140. package/data/zeroheight-exports/Getting-Started.md +0 -1157
  141. package/data/zeroheight-exports/Hero.md +0 -414
  142. package/data/zeroheight-exports/Icons.md +0 -2067
  143. package/data/zeroheight-exports/Intent.md +0 -86
  144. package/data/zeroheight-exports/Introduction-to-Bases.md +0 -35
  145. package/data/zeroheight-exports/Introduction-to-Cells.md +0 -35
  146. package/data/zeroheight-exports/Introduction-to-DNA.md +0 -35
  147. package/data/zeroheight-exports/Introduction-to-Genes.md +0 -35
  148. package/data/zeroheight-exports/Introduction.md +0 -115
  149. package/data/zeroheight-exports/Link.md +0 -432
  150. package/data/zeroheight-exports/Lists.md +0 -986
  151. package/data/zeroheight-exports/Loading-Indicator.md +0 -41
  152. package/data/zeroheight-exports/Navigation.md +0 -1190
  153. package/data/zeroheight-exports/Notification.md +0 -1038
  154. package/data/zeroheight-exports/Panel.md +0 -1030
  155. package/data/zeroheight-exports/README.md +0 -122
  156. package/data/zeroheight-exports/Responsive-Design.md +0 -330
  157. package/data/zeroheight-exports/Search-Input.md +0 -91
  158. package/data/zeroheight-exports/Search.md +0 -822
  159. package/data/zeroheight-exports/Segmented-Control.md +0 -609
  160. package/data/zeroheight-exports/Spacing.md +0 -297
  161. package/data/zeroheight-exports/Table.md +0 -5345
  162. package/data/zeroheight-exports/Tabs.md +0 -1324
  163. package/data/zeroheight-exports/Tags.md +0 -1545
  164. package/data/zeroheight-exports/Text-Link.md +0 -268
  165. package/data/zeroheight-exports/Theming.md +0 -362
  166. package/data/zeroheight-exports/Tooltips.md +0 -2015
  167. package/data/zeroheight-exports/Typography.md +0 -800
@@ -0,0 +1,131 @@
1
+ # TagFilter
2
+
3
+ ## Source Code
4
+
5
+ The component's source code in the SDS codebase can be found [here](https://github.com/chanzuckerberg/sci-components/blob/main/packages/components/src/core/TagFilter/index.tsx).
6
+
7
+ ## Import
8
+
9
+ **React TypeScript**
10
+
11
+ ```tsx
12
+ import { TagFilter } from "@czi-sds/components";
13
+ ```
14
+
15
+ ## Code examples
16
+
17
+ ### Default TagFilter
18
+
19
+ A row of applied filters, each removing itself from the page's state through `onDelete`. The row is a named group, which gives the identical "Delete Tag" buttons some context.
20
+
21
+ **Example: DefaultTagFilter**
22
+
23
+ ```tsx
24
+ // TagFilter is the tag that comes off: onDelete is required, and removing the tag
25
+ // from your own state is what makes it disappear. Everything about how it looks is
26
+ // fixed (the accent fill, the square shape, and the small X), so there are no
27
+ // style props to pass.
28
+ //
29
+ // Both the tag and its X are focusable, so each filter is two tab stops: Backspace
30
+ // or Delete on the tag removes it, and so does Enter on the X.
31
+
32
+ import {
33
+ TagFilter,
34
+ fontBodyXs,
35
+ getSemanticColors,
36
+ getSpaces,
37
+ type CommonThemeProps,
38
+ } from "@czi-sds/components";
39
+ import styled from "@emotion/styled";
40
+ import { useState } from "react";
41
+
42
+ const FILTERS = ["Homo sapiens", "Lung", "10x 3' v3", "Healthy"];
43
+
44
+ const Row = styled.div<CommonThemeProps>`
45
+ ${(props) => {
46
+ const spaces = getSpaces(props);
47
+
48
+ return `
49
+ display: flex;
50
+ flex-wrap: wrap;
51
+ gap: ${spaces?.s}px;
52
+ margin-bottom: ${spaces?.m}px;
53
+ min-height: 28px;
54
+ `;
55
+ }}
56
+ `;
57
+
58
+ const Readout = styled.p<CommonThemeProps>`
59
+ ${fontBodyXs}
60
+
61
+ ${(props) => {
62
+ const semanticColors = getSemanticColors(props);
63
+
64
+ return `
65
+ color: ${semanticColors?.base?.textSecondary};
66
+ margin: 0;
67
+ `;
68
+ }}
69
+ `;
70
+
71
+ function App() {
72
+ const [filters, setFilters] = useState(FILTERS);
73
+
74
+ return (
75
+ <div className="app">
76
+ <Row aria-label="Applied filters" role="group">
77
+ {filters.map((filter) => (
78
+ <TagFilter
79
+ key={filter}
80
+ label={filter}
81
+ onDelete={() =>
82
+ setFilters((current) => current.filter((item) => item !== filter))
83
+ }
84
+ />
85
+ ))}
86
+ </Row>
87
+
88
+ <Readout>
89
+ {filters.length > 0
90
+ ? `${filters.length} of ${FILTERS.length} filters applied`
91
+ : "No filters applied"}
92
+ </Readout>
93
+ </div>
94
+ );
95
+ }
96
+
97
+ export default App;
98
+ ```
99
+
100
+ ## SDS vs MUI
101
+
102
+ TagFilter is built on the SDS Tag, and so on MUI's Chip, but it exists for one job: showing which Filters are applied and letting them be taken off again. It has one appearance and no style props:
103
+
104
+ - **onDelete is required.** MUI treats it as the switch that decides whether a chip can be deleted; here the delete control is always there, so the handler has to be. Removing the tag from your own state is what makes it disappear.
105
+
106
+ - **The look is fixed:** the accent fill, the square shape, the small semibold label and the 12px X. Tag's `sdsStyle`, `sdsType`, `sdsSize` and `color` are not part of the type, and MUI's `variant` is not either.
107
+
108
+ - **The X cannot be swapped:** SDS applies its own `deleteIcon` (an SDS Button holding the XMark icon) after your props, so a `deleteIcon` of your own is ignored without a warning.
109
+
110
+ ## MUI Documentation
111
+
112
+ Documentation for the underlying MUI component can be found [here](https://mui.com/material-ui/react-chip/).
113
+
114
+ ## Behavior and accessibility
115
+
116
+ - There are two ways to remove a filter, and both are reachable from the keyboard: the X is a button that answers Enter, and the tag itself answers Backspace and Delete. That also means each filter tag is two tab stops.
117
+
118
+ - The X button is named "Delete Tag" on every filter tag, and the name cannot be changed. The label beside it is read as plain text rather than as part of the button, so a row of filters is worth wrapping in a group with a name of its own, to say what the buttons are removing. The example below uses `role="group"` with an `aria-label`.
119
+
120
+ - The tag carries `role="none presentation"`, so it contributes no semantics of its own. Adding an `onClick` does not change that: the tag becomes clickable to the pointer while still announcing nothing, so leave the clicking to the X.
121
+
122
+ - Like a Tag, a filter tag does not wrap: a long filter value is cut off with an ellipsis. Keep the values short, or shorten them yourself so the part that survives is the part that matters.
123
+
124
+ ## Props
125
+
126
+ Any custom SDS props and MUI props required for implementation are found on the table below. See the MUI documentation for additional optional props.
127
+
128
+ | Name | Type | Default | Description |
129
+ | ---------- | ---------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
130
+ | `label` | `string` | - (required) | The filter's text. Cut off with an ellipsis when there is not room for it. |
131
+ | `onDelete` | `function` | - (required) | Runs when the X is clicked or activated, and when Backspace or Delete is pressed on the tag. Drop the filter from your state here; the component does not remove itself. **Signature:** `function(event: SyntheticEvent) => void` |
@@ -0,0 +1,561 @@
1
+ # Tooltip
2
+
3
+ ## Source Code
4
+
5
+ The component's source code in the SDS codebase can be found [here](https://github.com/chanzuckerberg/sci-components/blob/main/packages/components/src/core/Tooltip/index.tsx).
6
+
7
+ ## Import
8
+
9
+ **React TypeScript**
10
+
11
+ ```tsx
12
+ import { Tooltip } from "@czi-sds/components";
13
+ ```
14
+
15
+ ## Code examples
16
+
17
+ ### **Default Tooltip**
18
+
19
+ A title and a trigger, which is all a tooltip needs. The surface, the arrow, and the placement below the trigger are what the component gives you without being asked.
20
+
21
+ **Example: DefaultTooltip**
22
+
23
+ ```tsx
24
+ // The least a tooltip needs: a title, and something to hang it on. The surface,
25
+ // the arrow and the placement below the trigger all come as standard, so hover or
26
+ // focus the button to see what the component does unasked.
27
+ //
28
+ // The trigger still says what it is. A tooltip adds to a label; it is not one.
29
+
30
+ import { Button, Tooltip } from "@czi-sds/components";
31
+ import styled from "@emotion/styled";
32
+
33
+ const Stage = styled.div`
34
+ align-items: center;
35
+ display: flex;
36
+ justify-content: center;
37
+ min-height: 120px;
38
+ `;
39
+
40
+ function App() {
41
+ return (
42
+ <div className="app">
43
+ <Stage>
44
+ <Tooltip title="Recalculated whenever the filters change.">
45
+ <Button sdsStyle="minimal" sdsType="secondary">
46
+ Sequencing depth
47
+ </Button>
48
+ </Tooltip>
49
+ </Stage>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ export default App;
55
+ ```
56
+
57
+ ### **Tooltip with a subtitle**
58
+
59
+ A second line under the title, in smaller and dimmer text, for a detail that would crowd the title itself.
60
+
61
+ **Example: TooltipWithSubtitle**
62
+
63
+ ```tsx
64
+ // subtitle sits under title in smaller, dimmer text, for a detail like how to use
65
+ // the thing being pointed at. Both are optional, but a tooltip with neither a
66
+ // title, a subtitle, nor a componentSlot renders nothing at all.
67
+ //
68
+ // The trigger needs its own accessible name. An icon-only button gets one from
69
+ // aria-label, and it needs one because the tooltip text is not a substitute.
70
+
71
+ import { Button, Icon, Tooltip } from "@czi-sds/components";
72
+ import styled from "@emotion/styled";
73
+
74
+ const Stage = styled.div`
75
+ align-items: center;
76
+ display: flex;
77
+ justify-content: center;
78
+ min-height: 120px;
79
+ `;
80
+
81
+ function App() {
82
+ return (
83
+ <div className="app">
84
+ <Stage>
85
+ <Tooltip
86
+ placement="top"
87
+ subtitle="Values are recalculated whenever the filters change."
88
+ title="Sequencing depth"
89
+ >
90
+ <Button
91
+ aria-label="About sequencing depth"
92
+ sdsStyle="minimal"
93
+ sdsType="secondary"
94
+ >
95
+ <Icon sdsIcon="InfoCircle" sdsSize="s" />
96
+ </Button>
97
+ </Tooltip>
98
+ </Stage>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ export default App;
104
+ ```
105
+
106
+ ### **Tooltip widths**
107
+
108
+ Both widths side by side. The default caps the tooltip at 250px; the wide one holds 550px of left-aligned text.
109
+
110
+ **Example: TooltipWidths**
111
+
112
+ ```tsx
113
+ // width caps how far a tooltip can stretch before its text wraps. The default stops
114
+ // at 250px and centres the text; "wide" lifts the cap to 550px and switches the text
115
+ // to the left, which is what longer explanations need.
116
+
117
+ import {
118
+ Button,
119
+ Icon,
120
+ Tooltip,
121
+ fontBodyXs,
122
+ getSemanticColors,
123
+ getSpaces,
124
+ type CommonThemeProps,
125
+ } from "@czi-sds/components";
126
+ import styled from "@emotion/styled";
127
+
128
+ const LONG_TEXT =
129
+ "Reads are aligned to the reference genome with minimap2, then filtered to remove duplicates and anything below the mapping quality threshold set for the project.";
130
+
131
+ const Stage = styled.div<CommonThemeProps>`
132
+ ${(props) => {
133
+ const spaces = getSpaces(props);
134
+
135
+ return `
136
+ align-items: center;
137
+ display: flex;
138
+ gap: ${spaces?.xxl}px;
139
+ justify-content: center;
140
+ min-height: 140px;
141
+ `;
142
+ }}
143
+ `;
144
+
145
+ const Labelled = styled.div<CommonThemeProps>`
146
+ ${fontBodyXs}
147
+
148
+ ${(props) => {
149
+ const semanticColors = getSemanticColors(props);
150
+ const spaces = getSpaces(props);
151
+
152
+ return `
153
+ align-items: center;
154
+ color: ${semanticColors?.base?.textSecondary};
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: ${spaces?.xs}px;
158
+ `;
159
+ }}
160
+ `;
161
+
162
+ function App() {
163
+ return (
164
+ <div className="app">
165
+ <Stage>
166
+ <Labelled>
167
+ <Tooltip placement="top" title="Aligned with minimap2">
168
+ <Button
169
+ aria-label="About alignment"
170
+ sdsStyle="minimal"
171
+ sdsType="secondary"
172
+ >
173
+ <Icon sdsIcon="InfoCircle" sdsSize="s" />
174
+ </Button>
175
+ </Tooltip>
176
+ Default width
177
+ </Labelled>
178
+
179
+ <Labelled>
180
+ <Tooltip placement="top" title={LONG_TEXT} width="wide">
181
+ <Button
182
+ aria-label="About the alignment pipeline"
183
+ sdsStyle="minimal"
184
+ sdsType="secondary"
185
+ >
186
+ <Icon sdsIcon="InfoCircle" sdsSize="s" />
187
+ </Button>
188
+ </Tooltip>
189
+ Wide
190
+ </Labelled>
191
+ </Stage>
192
+ </div>
193
+ );
194
+ }
195
+
196
+ export default App;
197
+ ```
198
+
199
+ ### **Placement and the arrow**
200
+
201
+ Four of the twelve placements, and what `arrowOffset` does to the arrow: it sets a position rather than nudging one.
202
+
203
+ **Example: TooltipPlacement**
204
+
205
+ ```tsx
206
+ // placement is MUI's, and all twelve values work: a side, optionally with -start or
207
+ // -end to pin the tooltip to one end of the trigger. A tooltip that would fall off
208
+ // the screen flips to the opposite side on its own.
209
+ //
210
+ // arrowOffset is SDS's, and it is not an offset: the number becomes the arrow's left
211
+ // position inside the tooltip, in pixels from its left edge. Small values move the
212
+ // arrow near the left corner and values past the tooltip's width push it out of
213
+ // sight, so it is only worth reaching for when the arrow has to line up with
214
+ // something narrower than the trigger.
215
+
216
+ import {
217
+ Button,
218
+ Icon,
219
+ Tooltip,
220
+ fontBodyXs,
221
+ getSemanticColors,
222
+ getSpaces,
223
+ type CommonThemeProps,
224
+ type TooltipProps,
225
+ } from "@czi-sds/components";
226
+ import styled from "@emotion/styled";
227
+
228
+ const PLACEMENTS: TooltipProps["placement"][] = [
229
+ "top",
230
+ "right",
231
+ "bottom-start",
232
+ "left-end",
233
+ ];
234
+
235
+ const Stage = styled.div<CommonThemeProps>`
236
+ ${(props) => {
237
+ const spaces = getSpaces(props);
238
+
239
+ return `
240
+ display: flex;
241
+ flex-wrap: wrap;
242
+ gap: ${spaces?.xxl}px;
243
+ justify-content: center;
244
+ padding: ${spaces?.xxl}px 0;
245
+ `;
246
+ }}
247
+ `;
248
+
249
+ const Labelled = styled.div<CommonThemeProps>`
250
+ ${fontBodyXs}
251
+
252
+ ${(props) => {
253
+ const semanticColors = getSemanticColors(props);
254
+ const spaces = getSpaces(props);
255
+
256
+ return `
257
+ align-items: center;
258
+ color: ${semanticColors?.base?.textSecondary};
259
+ display: flex;
260
+ flex-direction: column;
261
+ gap: ${spaces?.xs}px;
262
+ `;
263
+ }}
264
+ `;
265
+
266
+ function App() {
267
+ return (
268
+ <div className="app">
269
+ <Stage>
270
+ {PLACEMENTS.map((placement) => (
271
+ <Labelled key={placement}>
272
+ <Tooltip placement={placement} title={`Placed ${placement}`}>
273
+ <Button
274
+ aria-label={`Tooltip placed ${placement}`}
275
+ sdsStyle="minimal"
276
+ sdsType="secondary"
277
+ >
278
+ <Icon sdsIcon="InfoCircle" sdsSize="s" />
279
+ </Button>
280
+ </Tooltip>
281
+ {placement}
282
+ </Labelled>
283
+ ))}
284
+
285
+ <Labelled>
286
+ <Tooltip
287
+ arrowOffset={12}
288
+ placement="top"
289
+ title="Arrow pinned 12px from the left edge"
290
+ >
291
+ <Button
292
+ aria-label="Tooltip with an offset arrow"
293
+ sdsStyle="minimal"
294
+ sdsType="secondary"
295
+ >
296
+ <Icon sdsIcon="InfoCircle" sdsSize="s" />
297
+ </Button>
298
+ </Tooltip>
299
+ arrowOffset
300
+ </Labelled>
301
+ </Stage>
302
+ </div>
303
+ );
304
+ }
305
+
306
+ export default App;
307
+ ```
308
+
309
+ ### **Tooltip with a component slot**
310
+
311
+ Content that is not a string goes in `componentSlot`, under the title. The pointer can travel into the tooltip, which is what makes the link inside it usable.
312
+
313
+ **Example: TooltipWithComponentSlot**
314
+
315
+ ```tsx
316
+ // componentSlot takes anything React can render and puts it under the title and
317
+ // subtitle, with 12px between them. It is the way to get something other than text
318
+ // into a tooltip (a legend, a thumbnail, a link) because title and subtitle are
319
+ // each wrapped in a paragraph, so a table or a div passed to title produces invalid
320
+ // HTML.
321
+ //
322
+ // A tooltip is interactive: the pointer can travel from the trigger, across the gap,
323
+ // and onto the tooltip without closing it, which is what makes the link below
324
+ // reachable. Content that has to be clicked only works because of that, and it stops
325
+ // working the moment disableInteractive is set.
326
+
327
+ import {
328
+ Button,
329
+ Icon,
330
+ Tooltip,
331
+ focusVisibleA11yStyle,
332
+ fontBodyXs,
333
+ fontLinkXs,
334
+ getSemanticColors,
335
+ getSpaces,
336
+ type CommonThemeProps,
337
+ } from "@czi-sds/components";
338
+ import styled from "@emotion/styled";
339
+
340
+ const Stage = styled.div`
341
+ align-items: center;
342
+ display: flex;
343
+ justify-content: center;
344
+ min-height: 140px;
345
+ `;
346
+
347
+ const Legend = styled.div<CommonThemeProps>`
348
+ ${fontBodyXs}
349
+
350
+ ${(props) => {
351
+ const spaces = getSpaces(props);
352
+
353
+ return `
354
+ display: flex;
355
+ flex-direction: column;
356
+ gap: ${spaces?.xxs}px;
357
+ text-align: left;
358
+ `;
359
+ }}
360
+ `;
361
+
362
+ const Swatch = styled.span<CommonThemeProps>`
363
+ ${(props) => {
364
+ const spaces = getSpaces(props);
365
+
366
+ return `
367
+ align-items: center;
368
+ display: flex;
369
+ gap: ${spaces?.s}px;
370
+
371
+ &::before {
372
+ border-radius: 2px;
373
+ content: "";
374
+ height: 10px;
375
+ width: 10px;
376
+ }
377
+ `;
378
+ }}
379
+ `;
380
+
381
+ const PassingSwatch = styled(Swatch)<CommonThemeProps>`
382
+ ${(props) => `
383
+ &::before {
384
+ background-color: ${getSemanticColors(props)?.positive?.fillPrimary};
385
+ }
386
+ `}
387
+ `;
388
+
389
+ const FailingSwatch = styled(Swatch)<CommonThemeProps>`
390
+ ${(props) => `
391
+ &::before {
392
+ background-color: ${getSemanticColors(props)?.negative?.fillPrimary};
393
+ }
394
+ `}
395
+ `;
396
+
397
+ // The tooltip renders in a portal outside the page's styles, so a link inside one
398
+ // carries the SDS link type style itself.
399
+ const SlotLink = styled.a<CommonThemeProps>`
400
+ ${fontLinkXs}
401
+ ${focusVisibleA11yStyle}
402
+
403
+ ${(props) => {
404
+ const semanticColors = getSemanticColors(props);
405
+
406
+ return `
407
+ color: ${semanticColors?.accent?.textAction};
408
+ text-underline-offset: 2.5px;
409
+
410
+ &:hover {
411
+ color: ${semanticColors?.accent?.textActionHover};
412
+ }
413
+ `;
414
+ }}
415
+ `;
416
+
417
+ function App() {
418
+ return (
419
+ <div className="app">
420
+ <Stage>
421
+ <Tooltip
422
+ componentSlot={
423
+ <Legend>
424
+ <PassingSwatch>Above 30x coverage</PassingSwatch>
425
+ <FailingSwatch>Below the threshold</FailingSwatch>
426
+ <SlotLink href="https://sds.czi.design/009eaf17b">
427
+ Read the full method
428
+ </SlotLink>
429
+ </Legend>
430
+ }
431
+ placement="right"
432
+ textAlign="left"
433
+ title="Coverage"
434
+ >
435
+ <Button
436
+ aria-label="Coverage legend"
437
+ sdsStyle="minimal"
438
+ sdsType="secondary"
439
+ >
440
+ <Icon sdsIcon="InfoCircle" sdsSize="s" />
441
+ </Button>
442
+ </Tooltip>
443
+ </Stage>
444
+ </div>
445
+ );
446
+ }
447
+
448
+ export default App;
449
+ ```
450
+
451
+ ### **Tooltip on a disabled element**
452
+
453
+ A disabled control reports no hovers, so the tooltip goes on a span around it.
454
+
455
+ > **Note for Tooltips on Disabled elements**
456
+ >
457
+ > If the tooltip wraps a disabled component, please make sure to wrap the children in a `<span>` tag. SDS puts `tabIndex={0}` on whatever the tooltip wraps, so that span also becomes a tab stop and the tooltip opens on focus.
458
+ >
459
+ > [https://mui.com/components/tooltips/#disabled-elements](https://mui.com/components/tooltips/#disabled-elements)
460
+
461
+ **Example: TooltipOnDisabledElement**
462
+
463
+ ```tsx
464
+ // A disabled button fires no pointer events, so a tooltip attached straight to one
465
+ // never opens. Wrapping it in a span gives the tooltip an element that still reports
466
+ // hovers.
467
+ //
468
+ // The span works for the keyboard too, without any extra work: SDS puts tabIndex={0}
469
+ // on whatever a tooltip wraps, so the span becomes a tab stop of its own and the
470
+ // tooltip opens when it takes focus. That is the one route left, since the disabled
471
+ // button itself cannot be focused.
472
+
473
+ import { Button, Tooltip } from "@czi-sds/components";
474
+ import styled from "@emotion/styled";
475
+
476
+ const Stage = styled.div`
477
+ align-items: center;
478
+ display: flex;
479
+ justify-content: center;
480
+ min-height: 120px;
481
+ `;
482
+
483
+ function App() {
484
+ return (
485
+ <div className="app">
486
+ <Stage>
487
+ <Tooltip
488
+ placement="top"
489
+ title="Pick at least one sample before exporting"
490
+ >
491
+ <span>
492
+ <Button disabled sdsStyle="solid" sdsType="primary">
493
+ Export
494
+ </Button>
495
+ </span>
496
+ </Tooltip>
497
+ </Stage>
498
+ </div>
499
+ );
500
+ }
501
+
502
+ export default App;
503
+ ```
504
+
505
+ ## SDS vs MUI
506
+
507
+ SDS Tooltip wraps MUI's Tooltip, composes the content out of its own props rather than taking it whole, and pins the parts of MUI's API the design has an opinion about. What it adds and changes:
508
+
509
+ - **One surface, which follows the theme:** a tooltip is light in the light theme and dark in the dark theme, and there is nothing to set. MUI leaves the surface to the theme as well, but SDS pins the shape of it: the radius, the shadow, the hairline outline and the type scale.
510
+
511
+ - `title` and `subtitle`: SDS builds the content itself, wrapping `title` and then `subtitle` in paragraphs of its own, so `title` is not the free-form node MUI's is. `subtitle` is the smaller, dimmer second line. A tooltip with no `title`, `subtitle` or `componentSlot` renders nothing and leaves its child alone.
512
+
513
+ - `componentSlot`: anything React can render, placed under the text with 12px above it when there is text to sit under. This is where non-text content goes, since a table or a div handed to `title` lands inside a paragraph and produces invalid HTML.
514
+
515
+ - `width`: `"default"` caps the tooltip at 250px, `"wide"` at 550px and switches the text to the left. Compare MUI, which has no `width` prop of its own.
516
+
517
+ - `textAlign`: overrides the alignment each width picks for itself: centred at the default width, left when wide.
518
+
519
+ - `arrowOffset`: not an offset but a position: the number becomes the arrow's `left` in pixels, measured from the tooltip's left edge, replacing the one the positioning engine worked out. Values past the tooltip's own width put the arrow out of sight.
520
+
521
+ - **arrow is on:** MUI defaults to no arrow; SDS turns it on for every tooltip. It can still be turned back off with `arrow={false}`, though the design always draws one.
522
+
523
+ - **hasInvertedStyle, inverted and sdsStyle are deprecated and now do nothing.** All three chose between a light and a dark tooltip, back when the choice was the author's to make. Passing any of them still type-checks, so nothing breaks, but the tooltip takes its colours from the theme regardless and a deprecation warning is logged once. They will be removed in a future release: delete them.
524
+
525
+ - **The popper is SDS's:** it carries the arrow's shape and its position for all twelve placements, and it arrives through `PopperComponent`, which SDS keeps as a prop name of its own after MUI moved to `slots.popper`. SDS fills in `slots` last, so a `slots` object of your own is dropped; pass `PopperComponent` instead.
526
+
527
+ ## MUI Documentation
528
+
529
+ Documentation for the underlying MUI component can be found [here](https://mui.com/material-ui/react-tooltip/).
530
+
531
+ ## Behavior and accessibility
532
+
533
+ - A tooltip opens on hover and on keyboard focus, and closes on Escape. SDS gives the trigger `tabIndex={0}` so that focus can reach it even when the element wrapped is not naturally focusable.
534
+
535
+ - While a tooltip is open, MUI points the trigger's `aria-labelledby` at it, so the tooltip text becomes the trigger's name for that moment rather than an extra description. The trigger still needs a name of its own for when the tooltip is closed: an icon-only button needs an `aria-label`, and text in a tooltip is never a substitute for it.
536
+
537
+ - Tooltips are interactive: the pointer can travel from the trigger, across the 14px gap, and onto the tooltip without it closing, which is what makes a link inside one reachable. MUI's `disableInteractive` turns that off, and with it any chance of clicking what the tooltip holds.
538
+
539
+ - A disabled element fires no pointer events, so a tooltip attached to one never opens. Wrap the child in a `<span>` to give the tooltip something that reports hovers. The span inherits the `tabIndex={0}` SDS adds, so it becomes a tab stop and the tooltip still opens on focus. That is the only route left, given that the disabled control cannot take focus itself.
540
+
541
+ - The tooltip renders in a portal at the end of the document, so it is not clipped by whatever it sits inside and does not need room reserved for it.
542
+
543
+ - A tooltip is not a place for anything a person has to have. It cannot be reached on a touch screen without a tap that also does something else, and it is gone the moment the pointer moves.
544
+
545
+ ## Props
546
+
547
+ Any custom SDS props and MUI props required for implementation are found on the table below. See the MUI documentation for additional optional props.
548
+
549
+ | Name | Type | Default | Description |
550
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
551
+ | `title` | `ReactNode` | - | The tooltip's first line, wrapped in a paragraph by SDS. A tooltip with no `title`, `subtitle` or `componentSlot` does not render. |
552
+ | `subtitle` | `ReactNode` | - | A smaller, dimmer second line under the title, for a detail such as how to use what the tooltip points at. |
553
+ | `componentSlot` | `ReactNode` | - | Content below the text, with 12px above it when there is text. The place for anything that is not a string, including TooltipTable. |
554
+ | `width` | `"default" \| "wide"` | `"default"` | Caps the tooltip at 250px or 550px. Wide also left-aligns the text. |
555
+ | `textAlign` | `"left" \| "center" \| "right"` | `"center"`, or `"left"` when wide | Overrides the alignment the width would pick. |
556
+ | `arrow` | `boolean` | `true` | MUI's prop, which SDS turns on for every tooltip. It can be set back to `false`, though the design always draws an arrow. |
557
+ | `arrowOffset` | `number` | - | Sets the arrow's left position, in pixels from the tooltip's left edge, instead of letting it be calculated. Values past the tooltip's width move the arrow out of sight. |
558
+ | `PopperComponent` | `ElementType` | SDS `StyledPopper` | The popper the tooltip renders into, carrying the arrow's shape and placement. SDS maps it to MUI's `slots.popper`, and a `slots` object passed directly is ignored. |
559
+ | `placement` | `"bottom-start"` \| `"bottom"` \| `"bottom-end"` \| `"left-start"` \| `"left"` \| `"left-end"` \| `"right-start"` \| `"right"` \| `"right-end"` \| `"top-start"` \| `"top"` \| `"top-end"` | `"bottom"` | MUI's prop. All twelve values work, and a tooltip that would fall off the screen flips to the opposite side on its own. |
560
+ | `disableInteractive` | `boolean` | `false` | MUI's prop. Stops the tooltip from staying open while the pointer moves onto it, which also puts anything clickable inside it out of reach. |
561
+ | `followCursor` | `boolean` | `false` | MUI's prop, which keeps the tooltip under the pointer as it moves. SDS also reads it, and gives a tooltip that follows the cursor the roomier padding it uses for tables. |