@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,53 @@
1
+ # TableHeader
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/TableHeader/index.tsx).
6
+
7
+ ## Import
8
+
9
+ **React TypeScript**
10
+
11
+ ```tsx
12
+ import { TableHeader } from "@czi-sds/components";
13
+ ```
14
+
15
+ ## Code examples
16
+
17
+ ### **Default TableHeader**
18
+
19
+ This example demonstrates the minimum required props for rendering a TableHeader component.
20
+
21
+ **Example: DefaultTableHeader**
22
+
23
+ ```tsx
24
+ // TableHeader renders the thead and the header row itself, so its children are
25
+ // header cells rather than a tr.
26
+
27
+ import { CellHeader, Table, TableHeader } from "@czi-sds/components";
28
+
29
+ function App() {
30
+ return (
31
+ <div className="app">
32
+ <Table>
33
+ <TableHeader>
34
+ <CellHeader>Header</CellHeader>
35
+ <CellHeader>Header</CellHeader>
36
+ <CellHeader>Header</CellHeader>
37
+ </TableHeader>
38
+ </Table>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ export default App;
44
+ ```
45
+
46
+ ## Props
47
+
48
+ TableHeader renders the `thead` and the header row inside it, so its children are header cells rather than a row. It has two props of its own.
49
+
50
+ | Name | Type | Default | Description |
51
+ | ----------- | ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `children` | `React.ReactNode` | - | Required. The header cells, normally CellHeaders. They are wrapped in a TableRow with hover and tooltips switched off, so do not pass a `tr` yourself. |
53
+ | `filterRow` | `React.ReactNode` | - | A second row rendered below the header row, inside the same `thead`, for per-column filter controls. Pass a `tr` of cells. Supplying it also changes the border on the header row above. PreComposedTable uses this for its column filtering. |
@@ -0,0 +1,74 @@
1
+ # TableRow
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/TableRow/index.tsx).
6
+
7
+ ## Import
8
+
9
+ **React TypeScript**
10
+
11
+ ```tsx
12
+ import { TableRow } from "@czi-sds/components";
13
+ ```
14
+
15
+ ## Code examples
16
+
17
+ ### **Default TableRow**
18
+
19
+ This example demonstrates the minimum required props for rendering a TableRow component.
20
+
21
+ **Example: DefaultTableRow**
22
+
23
+ ```tsx
24
+ import { TableRow, CellBasic } from "@czi-sds/components";
25
+
26
+ function App() {
27
+ return (
28
+ <div className="app">
29
+ <table>
30
+ <tbody>
31
+ <TableRow>
32
+ <CellBasic
33
+ primaryText="Primary Text"
34
+ secondaryText="Secondary Text"
35
+ shouldShowTooltipOnHover={false}
36
+ />
37
+ <CellBasic
38
+ primaryText="Primary Text"
39
+ secondaryText="Secondary Text"
40
+ shouldShowTooltipOnHover={false}
41
+ />
42
+ <CellBasic
43
+ primaryText="Primary Text"
44
+ secondaryText="Secondary Text"
45
+ shouldShowTooltipOnHover={false}
46
+ />
47
+ </TableRow>
48
+ </tbody>
49
+ </table>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ export default App;
55
+ ```
56
+
57
+ ## Props
58
+
59
+ Any custom SDS props and MUI props required for implementation are found on the table below. See the MUI documentation for additional optional props.
60
+
61
+ | Name | Type | Default | Description |
62
+ | -------------------------- | ----------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `children` | `React.ReactNode` | - | Required. The row's cells. |
64
+ | `disabled` | `boolean` | `false` | Grays the row out and stops pointer events inside its CellComponents, so controls in the row cannot be used. Tags and icons in the row are dimmed to match. |
65
+ | `hover` | `boolean` | `true` | Gives the row a hover background, and is also required for its tooltip to appear. TableHeader sets it to `false` for the header row. |
66
+ | `rowHeight` | `number` | - | Caps the row's height, in pixels. |
67
+ | `selected` | `boolean` | `false` | Gives the row's cells the accent surface used for selection. It is styling only; it sets no ARIA state. |
68
+ | `shouldShowTooltipOnHover` | `boolean` | `true` | Wraps the row in a Tooltip, but only while `hover` is also `true`. On its own it shows nothing: a row tooltip needs `tooltipText` as well. |
69
+ | `tooltipText` | `string` | - | The tooltip's title. |
70
+ | `tooltipSubtitle` | `string` | - | A second line below the tooltip's title. |
71
+ | `tooltipProps` | `Partial<TooltipProps>` | - | Passed to the Tooltip, and overrides the defaults the row sets (arrow, dark style). |
72
+ | `useDivider` | `boolean` | `true` | Draws the 1px divider along the bottom of the row. |
73
+
74
+ Anything else you pass, such as `onClick`, `className` or a data attribute, lands on the rendered `tr`. Note that a row tooltip and a cell tooltip will both fire, so turn off `shouldShowTooltipOnHover` on the cells of a row that has its own.
@@ -0,0 +1,455 @@
1
+ # Tabs
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/Tabs/index.tsx).
6
+
7
+ ## Import
8
+
9
+ **React TypeScript**
10
+
11
+ ```tsx
12
+ import { Tab, Tabs } from "@czi-sds/components";
13
+ ```
14
+
15
+ ## Code examples
16
+
17
+ ### Tabs – Large
18
+
19
+ The default size, for the top level of a screen. The selection is held in the page's state and written back from `onChange`, which is what a Tabs group needs to respond to a click.
20
+
21
+ **Example: TabsLarge**
22
+
23
+ ```tsx
24
+ // Tabs is a controlled component: it draws the tab whose value matches the value
25
+ // prop, so the selection has to live in your state and onChange has to write it
26
+ // back. Without that, clicking a tab changes nothing.
27
+ //
28
+ // The large size is the default and belongs at the top level of a screen. The
29
+ // component brings its own spacing, 16px above and 24px below, so the group does
30
+ // not need margins of its own.
31
+
32
+ import { Tab, Tabs } from "@czi-sds/components";
33
+ import { SyntheticEvent, useState } from "react";
34
+
35
+ function App() {
36
+ const [value, setValue] = useState(0);
37
+
38
+ const handleChange = (_event: SyntheticEvent, newValue: unknown) => {
39
+ setValue(newValue as number);
40
+ };
41
+
42
+ return (
43
+ <div className="app">
44
+ <Tabs aria-label="Sample views" onChange={handleChange} value={value}>
45
+ <Tab label="Overview" />
46
+ <Tab label="Samples" />
47
+ <Tab label="Sequencing runs" />
48
+ </Tabs>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export default App;
54
+ ```
55
+
56
+ ### Tabs – Small
57
+
58
+ A group at the small size, which drops the label text to 13px. It is meant for a second layer of tabbing rather than on its own.
59
+
60
+ **Example: TabsSmall**
61
+
62
+ ```tsx
63
+ // The small size drops the label text from 14px to 13px and tightens the spacing
64
+ // around the group to 12px. It is meant for a second layer of tabbing underneath a
65
+ // large group, not on its own. See the two levels example further down.
66
+ //
67
+ // sdsSize reaches every Tab through context, so it is set once on the group. A
68
+ // Tab cannot ask for a different size than the group it sits in.
69
+
70
+ import { Tab, Tabs } from "@czi-sds/components";
71
+ import { SyntheticEvent, useState } from "react";
72
+
73
+ function App() {
74
+ const [value, setValue] = useState(0);
75
+
76
+ const handleChange = (_event: SyntheticEvent, newValue: unknown) => {
77
+ setValue(newValue as number);
78
+ };
79
+
80
+ return (
81
+ <div className="app">
82
+ <Tabs
83
+ aria-label="Sample details"
84
+ onChange={handleChange}
85
+ sdsSize="small"
86
+ value={value}
87
+ >
88
+ <Tab label="Metadata" />
89
+ <Tab label="Quality control" />
90
+ <Tab label="Files" />
91
+ </Tabs>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ export default App;
97
+ ```
98
+
99
+ ### Tabs with count, text, and component
100
+
101
+ `count` takes any node: a number, a count of `0` on an empty tab, and a number paired with a Tag on the last tab. The group is underlined, which separates it from the content below.
102
+
103
+ **Example: TabsWithCountTextAndComponent**
104
+
105
+ ```tsx
106
+ // count sits after a tab's label and says how many items wait on that tab's
107
+ // screen. It takes any node, not just a number, so it can carry text or a whole
108
+ // component. The last tab below pairs a number with a Tag.
109
+ //
110
+ // A count of 0 is drawn rather than dropped, which is what makes an empty tab
111
+ // read as empty rather than as unknown. Whatever count renders becomes part of
112
+ // the tab's accessible name, so it is announced as "Contributors 0".
113
+ //
114
+ // underlined runs a divider under the full width of the group, separating the
115
+ // tabs from the content below them.
116
+
117
+ import {
118
+ Tab,
119
+ Tabs,
120
+ Tag,
121
+ getSpaces,
122
+ type CommonThemeProps,
123
+ } from "@czi-sds/components";
124
+ import styled from "@emotion/styled";
125
+ import { SyntheticEvent, useState } from "react";
126
+
127
+ const CountWithTag = styled.span<CommonThemeProps>`
128
+ ${(props) => {
129
+ const spaces = getSpaces(props);
130
+
131
+ return `
132
+ align-items: center;
133
+ display: inline-flex;
134
+ gap: ${spaces?.s}px;
135
+ `;
136
+ }}
137
+ `;
138
+
139
+ function App() {
140
+ const [value, setValue] = useState(0);
141
+
142
+ const handleChange = (_event: SyntheticEvent, newValue: unknown) => {
143
+ setValue(newValue as number);
144
+ };
145
+
146
+ return (
147
+ <div className="app">
148
+ <Tabs
149
+ aria-label="Project contents"
150
+ onChange={handleChange}
151
+ underlined
152
+ value={value}
153
+ >
154
+ <Tab count={4} label="Samples" />
155
+ <Tab count={0} label="Contributors" />
156
+ <Tab
157
+ count={
158
+ <CountWithTag>
159
+ 14
160
+ <Tag
161
+ color="beta"
162
+ label="BETA"
163
+ sdsStyle="rounded"
164
+ sdsType="secondary"
165
+ />
166
+ </CountWithTag>
167
+ }
168
+ label="Analyses"
169
+ />
170
+ </Tabs>
171
+ </div>
172
+ );
173
+ }
174
+
175
+ export default App;
176
+ ```
177
+
178
+ ### Tabs with panels
179
+
180
+ The wiring between a tab and the content it reveals, which the component leaves to you: matching ids on each Tab and its panel, `role="tabpanel"` on the panel, and only the selected panel rendered.
181
+
182
+ **Example: TabsWithPanels**
183
+
184
+ ```tsx
185
+ // Tabs draws the tab strip; the panel below it is yours to render and to connect.
186
+ // The component sets role="tablist" and role="tab" and moves the tab stop between
187
+ // the tabs, but it does not know about your content, so nothing links a tab to
188
+ // the panel it controls until you pass the ids yourself:
189
+ //
190
+ // Tab: id="…-tab", aria-controls="…-panel"
191
+ // Panel: id="…-panel", role="tabpanel", aria-labelledby="…-tab"
192
+ //
193
+ // Only the selected panel is rendered here, so the tabs are the only way to reach
194
+ // the other content. tabIndex={0} on the panel gives it a tab stop, which lets a
195
+ // keyboard user move from the selected tab straight into what it revealed.
196
+
197
+ import {
198
+ Tab,
199
+ Tabs,
200
+ fontBodyS,
201
+ getSemanticColors,
202
+ getSpaces,
203
+ type CommonThemeProps,
204
+ } from "@czi-sds/components";
205
+ import styled from "@emotion/styled";
206
+ import { SyntheticEvent, useState } from "react";
207
+
208
+ const VIEWS = [
209
+ {
210
+ body: "12 samples collected between March and June, 3 of them flagged for re-sequencing.",
211
+ id: "samples",
212
+ label: "Samples",
213
+ },
214
+ {
215
+ body: "Two runs on the NovaSeq X, both passing the coverage threshold of 30x.",
216
+ id: "runs",
217
+ label: "Sequencing runs",
218
+ },
219
+ {
220
+ body: "A differential expression analysis finished last week and is ready to download.",
221
+ id: "analyses",
222
+ label: "Analyses",
223
+ },
224
+ ];
225
+
226
+ const Panel = styled.div<CommonThemeProps>`
227
+ ${fontBodyS}
228
+
229
+ ${(props) => {
230
+ const semanticColors = getSemanticColors(props);
231
+ const spaces = getSpaces(props);
232
+
233
+ return `
234
+ border: 1px solid ${semanticColors?.base?.divider};
235
+ border-radius: 4px;
236
+ color: ${semanticColors?.base?.textPrimary};
237
+ padding: ${spaces?.l}px;
238
+ `;
239
+ }}
240
+ `;
241
+
242
+ function App() {
243
+ const [value, setValue] = useState(0);
244
+
245
+ const handleChange = (_event: SyntheticEvent, newValue: unknown) => {
246
+ setValue(newValue as number);
247
+ };
248
+
249
+ const view = VIEWS[value];
250
+
251
+ return (
252
+ <div className="app">
253
+ <Tabs
254
+ aria-label="Project sections"
255
+ onChange={handleChange}
256
+ underlined
257
+ value={value}
258
+ >
259
+ {VIEWS.map((item) => (
260
+ <Tab
261
+ aria-controls={`${item.id}-panel`}
262
+ id={`${item.id}-tab`}
263
+ key={item.id}
264
+ label={item.label}
265
+ />
266
+ ))}
267
+ </Tabs>
268
+
269
+ <Panel
270
+ aria-labelledby={`${view.id}-tab`}
271
+ id={`${view.id}-panel`}
272
+ role="tabpanel"
273
+ tabIndex={0}
274
+ >
275
+ {view.body}
276
+ </Panel>
277
+ </div>
278
+ );
279
+ }
280
+
281
+ export default App;
282
+ ```
283
+
284
+ ### A disabled tab
285
+
286
+ A tab whose content is not ready yet. It stays in the strip, greyed out, and both the pointer and the arrow keys pass over it.
287
+
288
+ **Example: TabsWithDisabledTab**
289
+
290
+ ```tsx
291
+ // disabled comes from MUI and greys a tab out. The tab stays in the strip, so it
292
+ // still says that the section exists, but it cannot be clicked and the arrow keys
293
+ // skip past it.
294
+ //
295
+ // A tab is worth disabling when its content is not ready yet rather than not
296
+ // available at all. Otherwise leave it out of the group.
297
+
298
+ import { Tab, Tabs } from "@czi-sds/components";
299
+ import { SyntheticEvent, useState } from "react";
300
+
301
+ function App() {
302
+ const [value, setValue] = useState(0);
303
+
304
+ const handleChange = (_event: SyntheticEvent, newValue: unknown) => {
305
+ setValue(newValue as number);
306
+ };
307
+
308
+ return (
309
+ <div className="app">
310
+ <Tabs aria-label="Analysis steps" onChange={handleChange} value={value}>
311
+ <Tab count={12} label="Samples" />
312
+ <Tab disabled label="Alignment" />
313
+ <Tab count={3} label="Variant calls" />
314
+ </Tabs>
315
+ </div>
316
+ );
317
+ }
318
+
319
+ export default App;
320
+ ```
321
+
322
+ ### Two levels of tabs
323
+
324
+ A large group over a small one, which is the pairing the small size exists for. Each level keeps its own state, and the second level resets when the first one changes.
325
+
326
+ **Example: NestedTabs**
327
+
328
+ ```tsx
329
+ // The small size exists for a second layer of tabbing under a large group, which
330
+ // is the only place the design guidance puts it. Each level is its own Tabs group
331
+ // with its own state, and the second level resets when the first one changes, so a
332
+ // section never opens on a sub-tab that was chosen somewhere else.
333
+ //
334
+ // Each group brings its own spacing (24px under the large one, 12px over the
335
+ // small one), and those margins collapse into a single 24px gap between the two
336
+ // strips, so neither level needs margins added or taken away here.
337
+
338
+ import { Tab, Tabs } from "@czi-sds/components";
339
+ import { SyntheticEvent, useState } from "react";
340
+
341
+ const SECTIONS = [
342
+ { label: "Samples", subTabs: ["All", "Flagged", "Archived"] },
343
+ { label: "Sequencing runs", subTabs: ["Completed", "In progress"] },
344
+ ];
345
+
346
+ function App() {
347
+ const [section, setSection] = useState(0);
348
+ const [subTab, setSubTab] = useState(0);
349
+
350
+ const handleSectionChange = (_event: SyntheticEvent, newValue: unknown) => {
351
+ setSection(newValue as number);
352
+ setSubTab(0);
353
+ };
354
+
355
+ const handleSubTabChange = (_event: SyntheticEvent, newValue: unknown) => {
356
+ setSubTab(newValue as number);
357
+ };
358
+
359
+ return (
360
+ <div className="app">
361
+ <Tabs
362
+ aria-label="Project sections"
363
+ onChange={handleSectionChange}
364
+ underlined
365
+ value={section}
366
+ >
367
+ {SECTIONS.map((item) => (
368
+ <Tab key={item.label} label={item.label} />
369
+ ))}
370
+ </Tabs>
371
+
372
+ <Tabs
373
+ aria-label={`${SECTIONS[section].label} views`}
374
+ onChange={handleSubTabChange}
375
+ sdsSize="small"
376
+ value={subTab}
377
+ >
378
+ {SECTIONS[section].subTabs.map((label) => (
379
+ <Tab key={label} label={label} />
380
+ ))}
381
+ </Tabs>
382
+ </div>
383
+ );
384
+ }
385
+
386
+ export default App;
387
+ ```
388
+
389
+ ## SDS vs MUI
390
+
391
+ SDS Tabs wraps MUI's Tabs, and SDS Tab wraps MUI's Tab. Both are exported from the library and are used together: a Tab has to be a direct child of a Tabs group. The differences from MUI are these:
392
+
393
+ - `sdsSize`: takes `"large"` (default) or `"small"`, and sets both the size of the label text on every tab in the group (14px for large, 13px for small) and the spacing around the group: 16px above and 24px below at the large size, 12px on both sides at the small size. It reaches the tabs through context, so it belongs on the group; a Tab cannot ask for a size of its own.
394
+
395
+ - `underlined`: runs a divider under the full width of the group when `true`, separating the tabs from the content below them. Without it only the selected tab is underlined, in the accent color.
396
+
397
+ - **count on a Tab:** a value drawn after the label, saying how many items wait on that tab's screen. It is a prop on each Tab, not on the group, and it takes any node: a number, a string, or a component such as a Tag. A count of `0` is drawn rather than dropped.
398
+
399
+ - **The label is rendered twice:** each tab carries a second, hidden copy of its label set in semibold, which holds the width the label will need once the tab is selected and its text turns semibold. It is why selecting a tab does not shift the strip. The copy is hidden from assistive technology, but it is in the DOM, so a test looking for a tab's text by content finds two nodes.
400
+
401
+ - **The indicator is fixed:** SDS styles the selected tab's underline itself (2px, in the accent color), so MUI's `indicatorColor` and `textColor` have no visible effect. That styling arrives through `slotProps.indicator`, so passing your own `slotProps` replaces it and leaves the indicator with MUI's default appearance.
402
+
403
+ - **Only a horizontal, full-width strip is supported:** SDS turns off the clipping that MUI's scroller relies on, so `variant="scrollable"` neither clips nor scrolls: too many tabs spill out of a narrow container rather than scrolling inside it. Setting `orientation="vertical"` stacks the tabs but leaves the indicator drawn as a horizontal underline. Keep the group horizontal and keep the number of tabs to what fits.
404
+
405
+ - **Icons are not part of the design:** MUI's `icon` and `iconPosition` props still render, but SDS does not style them, so an SDS tab is a label and an optional count.
406
+
407
+ ## MUI Documentation
408
+
409
+ Documentation for the underlying MUI component can be found [here](https://mui.com/material-ui/react-tabs/).
410
+
411
+ ## Behavior and accessibility
412
+
413
+ - The group is controlled. It draws the tab whose value matches `value`, so the selection has to live in your state and `onChange` has to write it back; without that, clicking a tab changes nothing. A value that matches no tab leaves the group with nothing selected and logs a warning listing the values it accepts. Passing `value={false}` selects nothing on purpose, without the warning.
414
+
415
+ - Tabs are numbered by position unless a Tab is given a `value` of its own, which is worth doing when the strip can change, so the selection does not follow whichever tab happens to sit in that position.
416
+
417
+ - A Tab has to be a direct child of a Tabs group: rendered on its own it throws, and wrapped in an element it renders but never selects, since the group passes its state to its immediate children. Mapping over an array is fine.
418
+
419
+ - The group renders `role="tablist"` with a `role="tab"` button per tab, and keeps a single tab stop: Tab enters the strip on the selected tab, the arrow keys move between tabs and wrap around, Home and End jump to the ends, and Space or Enter selects the focused tab. Selecting on focus alone is available through MUI's `selectionFollowsFocus`, which suits a strip whose panels are already loaded.
420
+
421
+ - Nothing connects a tab to the content it reveals until you do it. Give each Tab an `id` and an `aria-controls` pointing at its panel, and give the panel `role="tabpanel"` and an `aria-labelledby` pointing back at the tab. The panels example below shows the wiring.
422
+
423
+ - `aria-label` on the group lands on the tablist and names the set of tabs. Worth setting, since a screen reader otherwise announces an unnamed tab list.
424
+
425
+ - A tab's accessible name is its label followed by whatever `count` renders, so a count of `0` is announced as part of the name: "Contributors 0". Keep a count short enough to read that way.
426
+
427
+ - A `disabled` tab keeps its place in the strip but cannot be clicked, and the arrow keys skip past it. It says that a section exists but is not ready; a section that does not apply at all is better left out of the group.
428
+
429
+ ## Props
430
+
431
+ Any custom SDS props and MUI props required for implementation are found on the table below. See the MUI documentation for additional optional props.
432
+
433
+ | Name | Type | Default | Description |
434
+ | ----------------------- | ----------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
435
+ | `children` | `React.ReactNode` | - (required) | The tabs in the group, as direct Tab children in the order they appear. |
436
+ | `value` | `any` | - (required) | The selected tab's value: its position in the group, or the value set on the Tab itself. Pass `false` to select nothing. |
437
+ | `onChange` | `(event, value: any) => void` | - | Runs when a tab is selected, with that tab's value. Write it back to `value`, or the group will keep showing the same tab. |
438
+ | `sdsSize` | `"small"` \| `"large"` | `"large"` | The size of the label text on every tab in the group, and the spacing around the group. |
439
+ | `underlined` | `boolean` | `false` | Draws a divider along the full width of the group, below the tabs; the selected tab's indicator sits on top of it. |
440
+ | `aria-label` | `string` | - | Names the tablist. Say what the tabs switch between, and use `aria-labelledby` instead when a visible heading already says it. |
441
+ | `selectionFollowsFocus` | `boolean` | `false` | MUI's prop for selecting a tab as soon as the arrow keys reach it, instead of waiting for Space or Enter. |
442
+
443
+ ### Tab
444
+
445
+ One tab in the group. Beyond the props below it takes MUI's Tab props and the attributes of the button it renders.
446
+
447
+ | Name | Type | Default | Description |
448
+ | --------------- | ----------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
449
+ | `label` | `React.ReactNode` | - | The tab's text, and its accessible name. Rendered twice, once visibly and once hidden in semibold to hold the width the selected state needs. |
450
+ | `count` | `React.ReactNode` | - | A value drawn after the label: a number, a string, or a component. `0` is drawn rather than dropped, and whatever renders joins the tab's accessible name. |
451
+ | `value` | `any` | position in the group | Identifies the tab in the group's `value` and `onChange`. Worth setting when the set of tabs can change. |
452
+ | `disabled` | `boolean` | `false` | Greys the tab out and takes it out of reach of the pointer and the arrow keys, while leaving it in the strip. |
453
+ | `id` | `string` | - | The tab's id, for a panel's `aria-labelledby` to point at. Not generated for you. |
454
+ | `aria-controls` | `string` | - | The id of the panel this tab reveals. |
455
+ | `selected` | `boolean` | - | Set by the group from its value, and used to draw the label in semibold. Not something to pass yourself. |