@czi-sds/mcp 0.15.0 → 0.17.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.
- package/README.md +2 -1
- package/data/component-docs/Accordion.md +291 -0
- package/data/component-docs/Autocomplete.md +525 -0
- package/data/component-docs/Banner.md +231 -0
- package/data/component-docs/Button.md +224 -0
- package/data/component-docs/ButtonDropdown.md +256 -0
- package/data/component-docs/ButtonGroup.md +320 -0
- package/data/component-docs/ButtonToggle.md +397 -0
- package/data/component-docs/Callout.md +268 -0
- package/data/component-docs/CellBasic.md +122 -0
- package/data/component-docs/CellComponent.md +63 -0
- package/data/component-docs/CellHeader.md +128 -0
- package/data/component-docs/ComplexFilter.md +294 -0
- package/data/component-docs/ContentCard.md +406 -0
- package/data/component-docs/Dialog.md +651 -0
- package/data/component-docs/Dropdown.md +155 -0
- package/data/component-docs/DropdownMenu.md +949 -0
- package/data/component-docs/HeatmapChart.md +782 -0
- package/data/component-docs/Hero.md +363 -0
- package/data/component-docs/Icon.md +283 -0
- package/data/component-docs/InputCheckbox.md +205 -0
- package/data/component-docs/InputDropdown.md +482 -0
- package/data/component-docs/InputRadio.md +173 -0
- package/data/component-docs/InputSearch.md +81 -0
- package/data/component-docs/InputSlider.md +189 -0
- package/data/component-docs/InputText.md +110 -0
- package/data/component-docs/InputToggle.md +141 -0
- package/data/component-docs/IntentMessage.md +293 -0
- package/data/component-docs/Legend.md +442 -0
- package/data/component-docs/Link.md +23 -0
- package/data/component-docs/List.md +300 -0
- package/data/component-docs/LoadingIndicator.md +198 -0
- package/data/component-docs/Menu.md +298 -0
- package/data/component-docs/MenuItem.md +100 -0
- package/data/component-docs/NavigationFooter.md +165 -0
- package/data/component-docs/NavigationHeader.md +500 -0
- package/data/component-docs/NavigationJumpTo.md +414 -0
- package/data/component-docs/Notification.md +288 -0
- package/data/component-docs/Pagination.md +67 -0
- package/data/component-docs/Panel.md +636 -0
- package/data/component-docs/Popover.md +448 -0
- package/data/component-docs/PreComposedTable.md +2947 -0
- package/data/component-docs/SegmentedControl.md +396 -0
- package/data/component-docs/StackedBarChart.md +475 -0
- package/data/component-docs/Table.md +2000 -0
- package/data/component-docs/TableHeader.md +53 -0
- package/data/component-docs/TableRow.md +74 -0
- package/data/component-docs/Tabs.md +455 -0
- package/data/component-docs/Tag.md +647 -0
- package/data/component-docs/TagFilter.md +131 -0
- package/data/component-docs/Tooltip.md +561 -0
- package/data/component-docs/TooltipCondensed.md +203 -0
- package/data/component-docs/TooltipTable.md +145 -0
- package/data/component-docs/index.json +262 -0
- package/data/component-list.json +2 -5
- package/data/component-props/Accordion.json +31 -11
- package/data/component-props/Alert.json +3 -3
- package/data/component-props/Autocomplete.json +150 -15
- package/data/component-props/Banner.json +31 -17
- package/data/component-props/Button.json +45 -20
- package/data/component-props/ButtonDropdown.json +36 -17
- package/data/component-props/ButtonDropdownLegacy.json +11 -11
- package/data/component-props/ButtonGroup.json +32 -11
- package/data/component-props/ButtonIcon.json +10 -10
- package/data/component-props/ButtonLegacy.json +19 -19
- package/data/component-props/ButtonToggle.json +32 -19
- package/data/component-props/ButtonToggleLegacy.json +11 -11
- package/data/component-props/Callout.json +58 -17
- package/data/component-props/CellBasic.json +94 -41
- package/data/component-props/CellComponent.json +23 -7
- package/data/component-props/CellHeader.json +62 -19
- package/data/component-props/ComplexFilter.json +52 -21
- package/data/component-props/ContentCard.json +70 -47
- package/data/component-props/Dialog.json +47 -14
- package/data/component-props/Dropdown.json +67 -31
- package/data/component-props/DropdownMenu.json +128 -23
- package/data/component-props/HeatmapChart.json +88 -12
- package/data/component-props/Hero.json +80 -39
- package/data/component-props/Icon.json +20 -9
- package/data/component-props/InputCheckbox.json +36 -14
- package/data/component-props/InputDropdown.json +75 -27
- package/data/component-props/InputRadio.json +31 -13
- package/data/component-props/InputSearch.json +41 -18
- package/data/component-props/InputSlider.json +46 -19
- package/data/component-props/InputText.json +26 -19
- package/data/component-props/InputToggle.json +26 -9
- package/data/component-props/IntentMessage.json +38 -9
- package/data/component-props/Link.json +13 -7
- package/data/component-props/LoadingIndicator.json +11 -3
- package/data/component-props/MenuItem.json +38 -19
- package/data/component-props/MenuSelect.json +8 -8
- package/data/component-props/NavigationFooter.json +49 -19
- package/data/component-props/NavigationHeader.json +150 -37
- package/data/component-props/NavigationJumpTo.json +33 -7
- package/data/component-props/Notification.json +39 -21
- package/data/component-props/Pagination.json +39 -11
- package/data/component-props/Panel.json +62 -9
- package/data/component-props/Popover.json +80 -7
- package/data/component-props/PreComposedTable.json +99 -27
- package/data/component-props/SegmentedControl.json +42 -3
- package/data/component-props/StackedBarChart.json +116 -42
- package/data/component-props/TableRow.json +38 -19
- package/data/component-props/Tabs.json +66 -9
- package/data/component-props/Tag.json +34 -17
- package/data/component-props/TagFilter.json +10 -3
- package/data/component-props/Tooltip.json +49 -18
- package/data/component-props/TooltipCondensed.json +18 -9
- package/data/component-props/TooltipTable.json +17 -7
- package/data/resources/{zeroheight-rules.md → documentation-rules.md} +43 -30
- package/data/resources/figma-rules.md +2 -2
- package/dist/stdio.js +483 -780
- package/package.json +12 -8
- package/data/component-props/ButtonDropdownV2.json +0 -34
- package/data/component-props/ButtonNew.json +0 -47
- package/data/component-props/ButtonToggleV2.json +0 -44
- package/data/component-props/ButtonV2.json +0 -43
- package/data/zeroheight-cache.json +0 -10259
- package/data/zeroheight-exports/Accordion.md +0 -774
- package/data/zeroheight-exports/Banner.md +0 -691
- package/data/zeroheight-exports/Borders.md +0 -117
- package/data/zeroheight-exports/Buttons.md +0 -1854
- package/data/zeroheight-exports/Callout.md +0 -931
- package/data/zeroheight-exports/Colors.md +0 -1755
- package/data/zeroheight-exports/Content-Card.md +0 -309
- package/data/zeroheight-exports/Contributing-to-SDS.md +0 -98
- package/data/zeroheight-exports/Control-Inputs.md +0 -2818
- package/data/zeroheight-exports/Corners.md +0 -104
- package/data/zeroheight-exports/Cover-page.md +0 -3
- package/data/zeroheight-exports/Dialog.md +0 -1367
- package/data/zeroheight-exports/Downloads.md +0 -236
- package/data/zeroheight-exports/Drop-Shadows.md +0 -79
- package/data/zeroheight-exports/Dropdown-Input.md +0 -1776
- package/data/zeroheight-exports/Dropdown-Menus.md +0 -913
- package/data/zeroheight-exports/Element-Status-Tracker.md +0 -267
- package/data/zeroheight-exports/Field-Inputs.md +0 -1405
- package/data/zeroheight-exports/Filters.md +0 -1111
- package/data/zeroheight-exports/Getting-Started.md +0 -1157
- package/data/zeroheight-exports/Hero.md +0 -414
- package/data/zeroheight-exports/Icons.md +0 -2067
- package/data/zeroheight-exports/Intent.md +0 -86
- package/data/zeroheight-exports/Introduction-to-Bases.md +0 -35
- package/data/zeroheight-exports/Introduction-to-Cells.md +0 -35
- package/data/zeroheight-exports/Introduction-to-DNA.md +0 -35
- package/data/zeroheight-exports/Introduction-to-Genes.md +0 -35
- package/data/zeroheight-exports/Introduction.md +0 -115
- package/data/zeroheight-exports/Link.md +0 -432
- package/data/zeroheight-exports/Lists.md +0 -986
- package/data/zeroheight-exports/Loading-Indicator.md +0 -41
- package/data/zeroheight-exports/Navigation.md +0 -1190
- package/data/zeroheight-exports/Notification.md +0 -1038
- package/data/zeroheight-exports/Panel.md +0 -1030
- package/data/zeroheight-exports/README.md +0 -122
- package/data/zeroheight-exports/Responsive-Design.md +0 -330
- package/data/zeroheight-exports/Search-Input.md +0 -91
- package/data/zeroheight-exports/Search.md +0 -822
- package/data/zeroheight-exports/Segmented-Control.md +0 -609
- package/data/zeroheight-exports/Spacing.md +0 -297
- package/data/zeroheight-exports/Table.md +0 -5345
- package/data/zeroheight-exports/Tabs.md +0 -1324
- package/data/zeroheight-exports/Tags.md +0 -1545
- package/data/zeroheight-exports/Text-Link.md +0 -268
- package/data/zeroheight-exports/Theming.md +0 -362
- package/data/zeroheight-exports/Tooltips.md +0 -2015
- package/data/zeroheight-exports/Typography.md +0 -800
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# Legend
|
|
2
|
+
|
|
3
|
+
A wrapping row of colored swatches and labels, for naming the parts of a chart and, optionally, filtering it.
|
|
4
|
+
|
|
5
|
+
## Source Code
|
|
6
|
+
|
|
7
|
+
The component's source code in the SDS codebase can be found [here](https://github.com/chanzuckerberg/sci-components/tree/main/packages/components/src/core/Legend).
|
|
8
|
+
|
|
9
|
+
## Import
|
|
10
|
+
|
|
11
|
+
**React TypeScript**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Legend } from "@czi-sds/components";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Code examples
|
|
18
|
+
|
|
19
|
+
### **Default Legend**
|
|
20
|
+
|
|
21
|
+
The minimum: a list of names, each with a color. Nothing here is interactive, which is how the legend reads when it is only a key.
|
|
22
|
+
|
|
23
|
+
**Example: DefaultLegend**
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// A legend on its own is a key: a swatch and a name for each part of whatever it
|
|
27
|
+
// sits next to. Items are drawn in the order they are given, and the row wraps
|
|
28
|
+
// to as many lines as the container needs, so the container is what decides how
|
|
29
|
+
// wide the legend is.
|
|
30
|
+
//
|
|
31
|
+
// Nothing here is interactive. Items are still buttons for the sake of the
|
|
32
|
+
// legends that are, so a click lands on one and does nothing.
|
|
33
|
+
|
|
34
|
+
import { Legend } from "@czi-sds/components";
|
|
35
|
+
|
|
36
|
+
const ITEMS = [
|
|
37
|
+
{ color: "#0B6CCC", name: "H. sapiens" },
|
|
38
|
+
{ color: "#3E8F3E", name: "M. musculus" },
|
|
39
|
+
{ color: "#9A54C1", name: "D. rerio" },
|
|
40
|
+
{ color: "#C9721A", name: "M. mulatta" },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function App() {
|
|
44
|
+
return (
|
|
45
|
+
<div className="app">
|
|
46
|
+
<div style={{ maxWidth: "400px" }}>
|
|
47
|
+
<Legend items={ITEMS} />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default App;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Values and colors
|
|
57
|
+
|
|
58
|
+
Values turned on, and the two ways to color the swatches: a palette passed as `colors`, and a color on each item. The last item is disabled, which is how a catch-all category sits in the legend without behaving like the rest.
|
|
59
|
+
|
|
60
|
+
**Example: LegendValuesAndColors**
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Two ways to color a legend, and what showValues does to it.
|
|
64
|
+
//
|
|
65
|
+
// The colors prop is a palette for the set as a whole, applied by index. It is
|
|
66
|
+
// what generateDiscreteColors produces, and it has to be regenerated when the
|
|
67
|
+
// theme changes, since the generator reverses its ramp for dark mode. A color on
|
|
68
|
+
// the item itself is the opposite case: a category that has to look the same
|
|
69
|
+
// everywhere it appears.
|
|
70
|
+
//
|
|
71
|
+
// Values are formatted for you when they are numbers, and printed as given when
|
|
72
|
+
// they are strings, which is how a percentage or a unit gets in. The last item
|
|
73
|
+
// on the right is disabled: it is drawn like the others but takes no hover, no
|
|
74
|
+
// click, and does not dim when its neighbours are selected, which is what a
|
|
75
|
+
// catch-all category usually wants.
|
|
76
|
+
|
|
77
|
+
import {
|
|
78
|
+
Legend,
|
|
79
|
+
fontBodyXs,
|
|
80
|
+
generateDiscreteColors,
|
|
81
|
+
getMode,
|
|
82
|
+
getSemanticColors,
|
|
83
|
+
getSpaces,
|
|
84
|
+
type CommonThemeProps,
|
|
85
|
+
type SDSTheme,
|
|
86
|
+
} from "@czi-sds/components";
|
|
87
|
+
import styled from "@emotion/styled";
|
|
88
|
+
import { useTheme } from "@mui/material";
|
|
89
|
+
|
|
90
|
+
const COUNTS = [
|
|
91
|
+
{ name: "H. sapiens", value: 3212 },
|
|
92
|
+
{ name: "M. musculus", value: 130 },
|
|
93
|
+
{ name: "C. jacchus", value: 89 },
|
|
94
|
+
{ name: "D. rerio", value: 65 },
|
|
95
|
+
{ name: "M. mulatta", value: 45 },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const SHARES = [
|
|
99
|
+
{ color: "#0B6CCC", name: "Passed", value: "68%" },
|
|
100
|
+
{ color: "#F5C700", name: "Flagged", value: "19%" },
|
|
101
|
+
{ color: "#C41E3A", name: "Failed", value: "8%" },
|
|
102
|
+
{ disabled: true, name: "Not run", value: "5%" },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const Row = styled.div<CommonThemeProps>`
|
|
106
|
+
${(props) => {
|
|
107
|
+
const spaces = getSpaces(props);
|
|
108
|
+
|
|
109
|
+
return `
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-wrap: wrap;
|
|
112
|
+
gap: ${spaces?.xxl}px;
|
|
113
|
+
`;
|
|
114
|
+
}}
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
const Caption = styled.p<CommonThemeProps>`
|
|
118
|
+
${fontBodyXs}
|
|
119
|
+
|
|
120
|
+
${(props) => {
|
|
121
|
+
const semanticColors = getSemanticColors(props);
|
|
122
|
+
const spaces = getSpaces(props);
|
|
123
|
+
|
|
124
|
+
return `
|
|
125
|
+
color: ${semanticColors?.base?.textSecondary};
|
|
126
|
+
margin: 0 0 ${spaces?.s}px;
|
|
127
|
+
`;
|
|
128
|
+
}}
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
function App() {
|
|
132
|
+
const theme = useTheme() as SDSTheme;
|
|
133
|
+
const colors = generateDiscreteColors(COUNTS.length, {
|
|
134
|
+
isDarkMode: getMode({ theme }) === "dark",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className="app">
|
|
139
|
+
<Row>
|
|
140
|
+
<div style={{ maxWidth: "320px" }}>
|
|
141
|
+
<Caption>A generated palette, with counts</Caption>
|
|
142
|
+
<Legend colors={colors} items={COUNTS} showValues />
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div style={{ maxWidth: "320px" }}>
|
|
146
|
+
<Caption>A color on each item, with shares</Caption>
|
|
147
|
+
<Legend items={SHARES} showValues />
|
|
148
|
+
</div>
|
|
149
|
+
</Row>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default App;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Selection
|
|
158
|
+
|
|
159
|
+
Selection lives in the parent, so the same state can filter a list beside the legend. The buttons are not just a convenience: clicking an item is pointer-only, so they are how a keyboard reaches the selection at all.
|
|
160
|
+
|
|
161
|
+
**Example: SelectableLegend**
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
// Selection is controlled: the legend draws whatever selectedIndices holds and
|
|
165
|
+
// asks the parent to change it through onSelectionChange, which is called with
|
|
166
|
+
// the clicked index toggled in or out. Because the state lives in the parent,
|
|
167
|
+
// the same selection can drive something else on the page. Here, that is the
|
|
168
|
+
// list of datasets beside it.
|
|
169
|
+
//
|
|
170
|
+
// Selected items keep a filled background and their swatch at full opacity,
|
|
171
|
+
// while everything unselected fades. Hovering wins over that for as long as the
|
|
172
|
+
// pointer is on the row.
|
|
173
|
+
//
|
|
174
|
+
// Clicking an item is pointer-only: items are buttons and take focus, but they
|
|
175
|
+
// do not respond to Enter or Space. The buttons below are what let a keyboard
|
|
176
|
+
// change the selection at all.
|
|
177
|
+
|
|
178
|
+
import { useState } from "react";
|
|
179
|
+
import {
|
|
180
|
+
Button,
|
|
181
|
+
Legend,
|
|
182
|
+
fontBodyXs,
|
|
183
|
+
getSemanticColors,
|
|
184
|
+
getSpaces,
|
|
185
|
+
type CommonThemeProps,
|
|
186
|
+
} from "@czi-sds/components";
|
|
187
|
+
import styled from "@emotion/styled";
|
|
188
|
+
|
|
189
|
+
const ITEMS = [
|
|
190
|
+
{ color: "#0B6CCC", name: "Transcriptomic", value: 117 },
|
|
191
|
+
{ color: "#3E8F3E", name: "Prosthetics", value: 130 },
|
|
192
|
+
{ color: "#9A54C1", name: "Epigenomics", value: 100 },
|
|
193
|
+
{ color: "#C9721A", name: "Imaging", value: 61 },
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
const Stack = styled.div<CommonThemeProps>`
|
|
197
|
+
${(props) => {
|
|
198
|
+
const spaces = getSpaces(props);
|
|
199
|
+
|
|
200
|
+
return `
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
gap: ${spaces?.l}px;
|
|
204
|
+
max-width: 420px;
|
|
205
|
+
`;
|
|
206
|
+
}}
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
const Controls = styled.div<CommonThemeProps>`
|
|
210
|
+
${(props) => {
|
|
211
|
+
const spaces = getSpaces(props);
|
|
212
|
+
|
|
213
|
+
return `
|
|
214
|
+
display: flex;
|
|
215
|
+
gap: ${spaces?.s}px;
|
|
216
|
+
`;
|
|
217
|
+
}}
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
const Caption = styled.p<CommonThemeProps>`
|
|
221
|
+
${fontBodyXs}
|
|
222
|
+
|
|
223
|
+
${(props) => {
|
|
224
|
+
const semanticColors = getSemanticColors(props);
|
|
225
|
+
|
|
226
|
+
return `
|
|
227
|
+
color: ${semanticColors?.base?.textSecondary};
|
|
228
|
+
margin: 0;
|
|
229
|
+
`;
|
|
230
|
+
}}
|
|
231
|
+
`;
|
|
232
|
+
|
|
233
|
+
function App() {
|
|
234
|
+
const [selectedIndices, setSelectedIndices] = useState<number[]>([1]);
|
|
235
|
+
|
|
236
|
+
const summary = selectedIndices.length
|
|
237
|
+
? selectedIndices.map((index) => ITEMS[index].name).join(", ")
|
|
238
|
+
: "Nothing selected: showing all modalities.";
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div className="app">
|
|
242
|
+
<Stack>
|
|
243
|
+
<Legend
|
|
244
|
+
items={ITEMS}
|
|
245
|
+
onSelectionChange={setSelectedIndices}
|
|
246
|
+
selectedIndices={selectedIndices}
|
|
247
|
+
showValues
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
<Controls>
|
|
251
|
+
<Button
|
|
252
|
+
onClick={() => setSelectedIndices(ITEMS.map((_, index) => index))}
|
|
253
|
+
sdsStyle="outline"
|
|
254
|
+
sdsType="primary"
|
|
255
|
+
>
|
|
256
|
+
Select all
|
|
257
|
+
</Button>
|
|
258
|
+
<Button
|
|
259
|
+
disabled={selectedIndices.length === 0}
|
|
260
|
+
onClick={() => setSelectedIndices([])}
|
|
261
|
+
sdsStyle="minimal"
|
|
262
|
+
sdsType="secondary"
|
|
263
|
+
>
|
|
264
|
+
Clear
|
|
265
|
+
</Button>
|
|
266
|
+
</Controls>
|
|
267
|
+
|
|
268
|
+
<Caption>{summary}</Caption>
|
|
269
|
+
</Stack>
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export default App;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Hover synced with a chart
|
|
278
|
+
|
|
279
|
+
`hoveredIndex` and the hover callbacks wired in both directions, so pointing at a bar highlights its legend item and pointing at a legend item highlights the bar.
|
|
280
|
+
|
|
281
|
+
**Example: LegendWithChartHover**
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
// The legend describes a chart it knows nothing about, so keeping the two in
|
|
285
|
+
// step is the parent's job, and it takes both halves of the hover API.
|
|
286
|
+
//
|
|
287
|
+
// Pointing at a bar sets hoveredIndex, which highlights the matching legend
|
|
288
|
+
// item. Pointing at a legend item fires onItemMouseEnter, which the parent turns
|
|
289
|
+
// into a highlight on the bar. hoveredIndex only ever adds a highlight (the
|
|
290
|
+
// legend still tracks its own hover, and null means "nothing from outside"
|
|
291
|
+
// rather than "clear it"), so the two never fight over the same item.
|
|
292
|
+
//
|
|
293
|
+
// The bar here is four divs sized by flex-grow, deliberately plain: it stands in
|
|
294
|
+
// for whatever chart the legend is a key to. For a real stacked bar, reach for
|
|
295
|
+
// StackedBarChart from @czi-sds/data-viz, which renders this legend itself.
|
|
296
|
+
|
|
297
|
+
import { useState } from "react";
|
|
298
|
+
import {
|
|
299
|
+
Legend,
|
|
300
|
+
getCorners,
|
|
301
|
+
getSpaces,
|
|
302
|
+
type CommonThemeProps,
|
|
303
|
+
} from "@czi-sds/components";
|
|
304
|
+
import styled from "@emotion/styled";
|
|
305
|
+
|
|
306
|
+
const ITEMS = [
|
|
307
|
+
{ color: "#0B6CCC", name: "Transcriptomic", value: 117 },
|
|
308
|
+
{ color: "#3E8F3E", name: "Prosthetics", value: 130 },
|
|
309
|
+
{ color: "#9A54C1", name: "Epigenomics", value: 100 },
|
|
310
|
+
{ color: "#C9721A", name: "Imaging", value: 61 },
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
const TOTAL = ITEMS.reduce((sum, item) => sum + item.value, 0);
|
|
314
|
+
|
|
315
|
+
const Stack = styled.div<CommonThemeProps>`
|
|
316
|
+
${(props) => {
|
|
317
|
+
const spaces = getSpaces(props);
|
|
318
|
+
|
|
319
|
+
return `
|
|
320
|
+
display: flex;
|
|
321
|
+
flex-direction: column;
|
|
322
|
+
gap: ${spaces?.m}px;
|
|
323
|
+
max-width: 420px;
|
|
324
|
+
`;
|
|
325
|
+
}}
|
|
326
|
+
`;
|
|
327
|
+
|
|
328
|
+
const Bar = styled.div<CommonThemeProps>`
|
|
329
|
+
${(props) => {
|
|
330
|
+
const corners = getCorners(props);
|
|
331
|
+
const spaces = getSpaces(props);
|
|
332
|
+
|
|
333
|
+
return `
|
|
334
|
+
display: flex;
|
|
335
|
+
gap: ${spaces?.xxxs}px;
|
|
336
|
+
height: 16px;
|
|
337
|
+
overflow: hidden;
|
|
338
|
+
border-radius: ${corners?.s}px;
|
|
339
|
+
`;
|
|
340
|
+
}}
|
|
341
|
+
`;
|
|
342
|
+
|
|
343
|
+
const Segment = styled.div`
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
transition: opacity 0.2s ease-in-out;
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
function App() {
|
|
349
|
+
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div className="app">
|
|
353
|
+
<Stack>
|
|
354
|
+
<Bar>
|
|
355
|
+
{ITEMS.map((item, index) => (
|
|
356
|
+
<Segment
|
|
357
|
+
key={item.name}
|
|
358
|
+
onMouseEnter={() => setHoveredIndex(index)}
|
|
359
|
+
onMouseLeave={() => setHoveredIndex(null)}
|
|
360
|
+
style={{
|
|
361
|
+
backgroundColor: item.color,
|
|
362
|
+
flexGrow: item.value / TOTAL,
|
|
363
|
+
opacity:
|
|
364
|
+
hoveredIndex === null || hoveredIndex === index ? 1 : 0.2,
|
|
365
|
+
}}
|
|
366
|
+
/>
|
|
367
|
+
))}
|
|
368
|
+
</Bar>
|
|
369
|
+
|
|
370
|
+
<Legend
|
|
371
|
+
hoveredIndex={hoveredIndex}
|
|
372
|
+
items={ITEMS}
|
|
373
|
+
onItemMouseEnter={(_, index) => setHoveredIndex(index)}
|
|
374
|
+
onItemMouseLeave={() => setHoveredIndex(null)}
|
|
375
|
+
showValues
|
|
376
|
+
/>
|
|
377
|
+
</Stack>
|
|
378
|
+
</div>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export default App;
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## How it is built
|
|
386
|
+
|
|
387
|
+
Legend has no MUI base. It is a flex row of divs that wraps, where each item is a swatch, a label, and an optional value, styled from SDS spacing and semantic colors. It draws nothing but itself: it holds no chart, and it is up to the page to keep the legend and whatever it describes in the same order and the same colors.
|
|
388
|
+
|
|
389
|
+
It is also what StackedBarChart renders below its bar. Reach for Legend directly when the chart is your own (an ECharts plot, a map, a set of swatches in a table) and you want its key to match the rest of the system.
|
|
390
|
+
|
|
391
|
+
## Items and colors
|
|
392
|
+
|
|
393
|
+
`items` is the whole content of the legend, drawn in the order given. Each item needs a `name` and may carry a `value`, a `color`, and `disabled`. An item's index is its identity everywhere else in the API: selection, hover, and every callback are indices into this array. Items are therefore positional and names need not be unique.
|
|
394
|
+
|
|
395
|
+
A swatch takes the first color it finds: the entry at its index in the `colors` prop, then the item's own `color`, then a grey fallback from the theme. The `colors` prop is for a palette computed for the whole set at once, which is what `generateDiscreteColors` produces and what needs regenerating when the theme changes; a color on the item is for a category that must always look the same. A short colors array is fine, since each index falls through on its own.
|
|
396
|
+
|
|
397
|
+
Values are only drawn when `showValues` is on and the item has one. Numbers go through `toLocaleString`, so 3212 reads as "3,212"; strings are printed as given, which is the way to show a percentage, a unit, or a range.
|
|
398
|
+
|
|
399
|
+
## Selection and hover
|
|
400
|
+
|
|
401
|
+
Selection is controlled. The legend draws whatever `selectedIndices` holds and never changes it: clicking an item calls `onSelectionChange` with that index toggled in or out, and the parent decides what to do with it. Without the callback the legend is inert: clicks still reach `onItemClick`, but nothing is ever selected.
|
|
402
|
+
|
|
403
|
+
Once anything is selected, the swatches of unselected items drop to 20% opacity. Hovering overrides that while the pointer is down the row: the hovered item and the selected ones stay solid, everything else dims. Labels never dim, so the legend stays readable as a list whatever is highlighted.
|
|
404
|
+
|
|
405
|
+
`hoveredIndex` lets a chart highlight a legend item as the pointer moves over the matching mark, and `onItemMouseEnter` / `onItemMouseLeave` drive the same thing in the other direction. It only ever adds a highlight: the legend keeps its own hover state, and passing `null` is the same as not passing the prop at all rather than a way to clear one.
|
|
406
|
+
|
|
407
|
+
## Behavior and accessibility
|
|
408
|
+
|
|
409
|
+
- Each item is a `role="button"` with a tab stop and an accessible name built from its label and value, and it reports its selected state through `aria-pressed`. It does not respond to Enter or Space, though, so a keyboard reaches the legend but cannot select from it. Where selection matters, give the page a keyboard path of its own, as the selection example below does with buttons.
|
|
410
|
+
|
|
411
|
+
- Every item is a button whether or not anything listens, so a legend used purely as a key still invites a click that does nothing. That is worth weighing against the alternative of drawing the key by hand.
|
|
412
|
+
|
|
413
|
+
- Color is the only thing tying an item to the mark it names. Where the marks are close in hue, or the chart is read by someone who cannot separate them, the values in the legend are what make it interpretable, which is a reason to turn `showValues` on rather than leave it to the chart.
|
|
414
|
+
|
|
415
|
+
- A disabled item takes no pointer events at all: no hover, no click, no dimming when its neighbours are selected. It is drawn exactly like the others, so anything a reader should know about it has to be in its name: "Other (17)", "Remaining". It also keeps its tab stop out of the tab order.
|
|
416
|
+
|
|
417
|
+
- The legend wraps to as many rows as it needs and has no width of its own, so it takes the width of its container. Constrain that container to keep it the same width as the chart it belongs to.
|
|
418
|
+
|
|
419
|
+
## Props
|
|
420
|
+
|
|
421
|
+
The legend spreads any remaining props onto its root div, so standard HTML attributes such as `className`, `id`, and `data-testid` work as usual.
|
|
422
|
+
|
|
423
|
+
| Name | Type | Default | Description |
|
|
424
|
+
| ------------------- | ------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
425
|
+
| `items` | `LegendItemData[]` | - (required) | The items, drawn in order. Their indices are what selection, hover, and the callbacks refer to. See the table below for the shape of an item. |
|
|
426
|
+
| `colors` | `string[]` | - | A palette applied by index, taking priority over the color on an item. Entries past the end of the array fall back to the item's color, then to grey. |
|
|
427
|
+
| `showValues` | `boolean` | `false` | Draws each item's value beside its name. Items without a value are unaffected. |
|
|
428
|
+
| `selectedIndices` | `number[]` | `[]` | Indices of the selected items. The legend is controlled: it draws this and never changes it. |
|
|
429
|
+
| `onSelectionChange` | `function` | - | `(selectedIndices: number[]) => void`. Called with the next selection when an item is clicked, with that index toggled. Without it, clicking selects nothing. |
|
|
430
|
+
| `hoveredIndex` | `number \| null` | - | Highlights an item from outside, for syncing with a chart. It adds to the legend's own hover rather than replacing it, and `null` reads as no external highlight. |
|
|
431
|
+
| `onItemClick` | `function` | - | `(item: LegendItemData, index: number) => void`. Fires on a click, alongside any selection change rather than instead of it. |
|
|
432
|
+
| `onItemMouseEnter` | `function` | - | `(item: LegendItemData, index: number) => void`. Called when the pointer enters an item. The legend already dims its own swatches, so this is for driving something outside it, such as a highlight on the chart. |
|
|
433
|
+
| `onItemMouseLeave` | `function` | - | `(item: LegendItemData, index: number) => void`. Called when the pointer leaves an item. |
|
|
434
|
+
|
|
435
|
+
### LegendItemData
|
|
436
|
+
|
|
437
|
+
| Name | Type | Default | Description |
|
|
438
|
+
| ---------- | ------------------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
439
|
+
| `name` | `string` | - (required) | The item's label, and the start of its accessible name. Names need not be unique, since items are addressed by index. |
|
|
440
|
+
| `value` | `number \| string` | - | Shown after the name when `showValues` is on. Numbers are grouped by locale; a string is printed as given, for a unit or a percentage. |
|
|
441
|
+
| `color` | `string` | theme grey | Any CSS color for the swatch. The `colors` prop overrides it, and without either the swatch is drawn in the theme's secondary ornament grey. |
|
|
442
|
+
| `disabled` | `boolean` | `false` | Takes the item out of every interaction and out of the tab order, and keeps its swatch at full opacity while others dim. |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Deprecated: the Link component
|
|
2
|
+
|
|
3
|
+
> **Do not use this in new code.** The **Link** component still ships, and its story sits under **Deprecated** in this Storybook. It is documented here only so that existing usages can be migrated.
|
|
4
|
+
|
|
5
|
+
It wrapped MUI's Link, offered two sizes and a bold weight, and underlined only on hover in its default style. Its source is [here](https://github.com/chanzuckerberg/sci-components/tree/main/packages/components/src/core/Link) and the underlying [MUI documentation](https://mui.com/material-ui/react-link/) still applies to the props it passed through.
|
|
6
|
+
|
|
7
|
+
## Migrating away from it
|
|
8
|
+
|
|
9
|
+
| Old prop | Replace with |
|
|
10
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `sdsSize="s"` | `fontLinkS` |
|
|
12
|
+
| `sdsSize="xs"` | `fontLinkXs` |
|
|
13
|
+
| `fontWeight="bold"` | The semibold mixin for the size, such as `fontLinkSemiboldS`. The component's `"bold"` was `600`, which SDS calls semibold. |
|
|
14
|
+
| `sdsStyle="default"` | The link type style plus the accent text action colors. Note the difference: the component only underlined on hover, whereas the type style is underlined in every state. |
|
|
15
|
+
| `sdsStyle="dashed"` | `color: inherit` with `text-decoration: underline dashed`, going solid on hover, as in the last example above. |
|
|
16
|
+
|
|
17
|
+
## Props
|
|
18
|
+
|
|
19
|
+
| Name | Type | Default | Description |
|
|
20
|
+
| ------------ | ------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `sdsStyle` | `"default"` \| `"dashed"` | `"default"` | `"default"` underlines on hover, focus, and press only; `"dashed"` is underlined dashed at rest and solid on interaction. |
|
|
22
|
+
| `sdsSize` | `"xs" \| "s"` | `"s"` | The font size, taken from the body scale rather than the link one. |
|
|
23
|
+
| `fontWeight` | `"normal"` \| `"bold"` | `"normal"` | Maps to `font-weight` `400` and `600`. |
|