@workday/canvas-kit-docs 10.3.6 → 10.3.8
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/dist/mdx/preview-react/_examples/TablesAdvanced.mdx +84 -0
- package/dist/mdx/preview-react/_examples/examples/Table/WithExpandableRows.tsx +219 -0
- package/dist/mdx/preview-react/_examples/examples/Table/WithSelectableRows.tsx +156 -0
- package/dist/mdx/preview-react/_examples/examples/Table/WithSortableColumnHeaders.tsx +213 -0
- package/dist/mdx/preview-react/table/Table.mdx +9 -1
- package/package.json +6 -6
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {Table} from '@workday/canvas-kit-preview-react/table';
|
|
2
|
+
|
|
3
|
+
import SelectableRows from './examples/Table/WithSelectableRows';
|
|
4
|
+
import ExpandableRows from './examples/Table/WithExpandableRows';
|
|
5
|
+
import SortableColumnHeaders from './examples/Table/WithSortableColumnHeaders';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Advanced Table Examples
|
|
9
|
+
|
|
10
|
+
Out of the box, `Table` is a lightweight component with a high degree of flexibility, but not much
|
|
11
|
+
functionality outside of providing a basic table layout. This leaves a gap for developers to
|
|
12
|
+
implement common features, such as selecting rows and sorting columns, on top of `Table` to meet
|
|
13
|
+
their specific application needs. During the development process, it's often unclear how to
|
|
14
|
+
implement these features in an accessible way. In an effort to reduce that gap and increase clarity,
|
|
15
|
+
we've documented several advanced examples below to communicate the accessibility nuances alongside
|
|
16
|
+
tangible code snippets.
|
|
17
|
+
|
|
18
|
+
### Expandable Rows
|
|
19
|
+
|
|
20
|
+
Expandable Rows combines the likes of an accordion with tabular data tables. Column 1 renders icon
|
|
21
|
+
buttons with 2 states, a collapsed and expanded state. A new row that spans the entire width of the
|
|
22
|
+
table is added to the table just after the expanded row.
|
|
23
|
+
|
|
24
|
+
- The `aria-expanded` property is added to the button to communicate this state to screen reader
|
|
25
|
+
users.
|
|
26
|
+
- A Canvas accessible `Tooltip` component is used to assign names to each icon button based on the
|
|
27
|
+
most useful value in the row. In this example, we combined the car make and model together. This
|
|
28
|
+
allows everyone to view the name of the checkboxes by hovering the mouse or focusing with the
|
|
29
|
+
keyboard.
|
|
30
|
+
- The expanded row uses `colspan` to span the entire width of the table and support screen readers.
|
|
31
|
+
This space provides flexibility to show headings, lists, and other structured content for the
|
|
32
|
+
table row above.
|
|
33
|
+
- There is no explicit relationship between a row of cells, and the spanned content below it. The
|
|
34
|
+
spanned content is assumed to belong to the row of cells above it, based on established accordion
|
|
35
|
+
patterns and logical reading order of content rendered to the screen.
|
|
36
|
+
- Outlining hierarchy in a data table is not supported for screen readers in this example.
|
|
37
|
+
|
|
38
|
+
<ExampleCodeBlock code={ExpandableRows} />
|
|
39
|
+
|
|
40
|
+
### Selectable Rows
|
|
41
|
+
|
|
42
|
+
Developed by the Workday accessibility team, using a `Checkbox` labeled "Select All" inside of a table
|
|
43
|
+
column header can be a confusing experience for screen reader users. Screen readers will
|
|
44
|
+
automatically announce the "Select All" column header each time users are reading any cell in
|
|
45
|
+
column 1. For instance, the `Checkbox` in row 4 is decidedly not going to select all of the rows. Here
|
|
46
|
+
is what we did about it:
|
|
47
|
+
|
|
48
|
+
- We intentionally rendered row 1, column 1 as a standard `<td>` element so screen readers won't
|
|
49
|
+
automatically announce "Select All" while reading cells in column 1.
|
|
50
|
+
- Our research found that VoiceOver (MacOS v12.7, Safari v17.1) persistently announce "Select All"
|
|
51
|
+
despite using the `<td>` element because of the optional `<thead>` element in the table. We
|
|
52
|
+
omitted the optional `<thead>` and `<tbody>` elements from this example for that reason.
|
|
53
|
+
- We used Canvas' accessible `Tooltip` component to assign names to each Checkbox based on the most
|
|
54
|
+
useful value in the row, the topping name. This allows everyone to view the name of the checkboxes
|
|
55
|
+
by hovering the mouse or focusing with the keyboard.
|
|
56
|
+
- We rendered the cells in column 2 as the row headers for the table, enabling screen readers to
|
|
57
|
+
automatically announce the topping name even while reading down the Amounts in column 3. When we
|
|
58
|
+
rendered column 1 as row headers, then reading down column 2 (Topping Name) sounded redundant
|
|
59
|
+
because the `Checkbox` names in column 1 are identical to the Topping Name in column 2.
|
|
60
|
+
|
|
61
|
+
<ExampleCodeBlock code={SelectableRows} />
|
|
62
|
+
|
|
63
|
+
### Sortable Column Headers
|
|
64
|
+
|
|
65
|
+
The challenge in this example is, how will we clearly communicate the complexity of the column
|
|
66
|
+
headers to screen readers and keep the tabular data easy to understand? The column headers must
|
|
67
|
+
describe the column name, the current sort state of the column, they are interactive (a button), and
|
|
68
|
+
instructions about how the column will be sorted next. When screen reader users are focused on the
|
|
69
|
+
tabular data cells, instructions about how the column will be sorted next is probably not relevant,
|
|
70
|
+
distracting, and makes it harder to understand the data.
|
|
71
|
+
|
|
72
|
+
- The `aria-sort` property has been initialized to `none` on each of the `<th>` elements; this value
|
|
73
|
+
is updated to `ascending` or `descending` to reflect the current state.
|
|
74
|
+
- A `<button>` element describing the column name is used inside of the `<th>` column header.
|
|
75
|
+
- The `describe` variant of the Canvas `Tooltip` component is applied to the button in the column
|
|
76
|
+
header. This is used to describe how the column will be sorted after interaction and applied to
|
|
77
|
+
the accessible description of the button with the `aria-describedby` property. This way screen
|
|
78
|
+
readers won't read the longer description of the column headers automatically while focusing on
|
|
79
|
+
the tabular data below.
|
|
80
|
+
- The `aria-sort` property may not reliably provide the current sort state of the column to users
|
|
81
|
+
focused on the tabular data in the table. It may be more reliable to use an `aria-label` string on
|
|
82
|
+
the button that describes the column name and sort state together.
|
|
83
|
+
|
|
84
|
+
<ExampleCodeBlock code={SortableColumnHeaders} />
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {StatusIndicator} from '@workday/canvas-kit-preview-react/status-indicator';
|
|
4
|
+
import {Table} from '@workday/canvas-kit-preview-react/table';
|
|
5
|
+
import {TertiaryButton} from '@workday/canvas-kit-react/button';
|
|
6
|
+
import {createComponent, generateUniqueId} from '@workday/canvas-kit-react/common';
|
|
7
|
+
import {Heading, Subtext} from '@workday/canvas-kit-react/text';
|
|
8
|
+
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
|
|
9
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
10
|
+
|
|
11
|
+
import {chevronDownSmallIcon, chevronRightSmallIcon} from '@workday/canvas-system-icons-web';
|
|
12
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
13
|
+
|
|
14
|
+
interface AutoData {
|
|
15
|
+
id: string;
|
|
16
|
+
brand: string;
|
|
17
|
+
model: string;
|
|
18
|
+
year: string;
|
|
19
|
+
price: string;
|
|
20
|
+
engine: string;
|
|
21
|
+
transmission: string;
|
|
22
|
+
horsepower: string;
|
|
23
|
+
torque: string;
|
|
24
|
+
curbWeight: string;
|
|
25
|
+
orderStatus: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const headingID = generateUniqueId();
|
|
29
|
+
const autoData: AutoData[] = [
|
|
30
|
+
{
|
|
31
|
+
id: generateUniqueId(),
|
|
32
|
+
brand: 'Porsche',
|
|
33
|
+
model: '992 911 GT3',
|
|
34
|
+
year: '2022',
|
|
35
|
+
price: 'Starts at $160,000',
|
|
36
|
+
engine: '4.0L Flat 6',
|
|
37
|
+
transmission: 'PDK or 7-Speed Manual',
|
|
38
|
+
horsepower: '502hp',
|
|
39
|
+
torque: '346 lb-ft',
|
|
40
|
+
curbWeight: '3,164 lbs',
|
|
41
|
+
orderStatus: 'Order Placed',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: generateUniqueId(),
|
|
45
|
+
brand: 'BMW',
|
|
46
|
+
model: 'M5 Competition',
|
|
47
|
+
year: '2018',
|
|
48
|
+
price: 'Starts at $105,000',
|
|
49
|
+
engine: 'Twin-Turbo 4.4L V8',
|
|
50
|
+
transmission: 'Automatic',
|
|
51
|
+
horsepower: '627hp',
|
|
52
|
+
torque: '553 lb-ft',
|
|
53
|
+
curbWeight: '4,345 lbs',
|
|
54
|
+
orderStatus: 'Order Fulfilled',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: generateUniqueId(),
|
|
58
|
+
brand: 'Audi',
|
|
59
|
+
model: 'R8',
|
|
60
|
+
year: '2022',
|
|
61
|
+
price: 'Starts at $148,000',
|
|
62
|
+
engine: '5.2L V10',
|
|
63
|
+
transmission: 'Automatic',
|
|
64
|
+
horsepower: '562hp',
|
|
65
|
+
torque: '408 lb-ft',
|
|
66
|
+
curbWeight: '3,594 lbs',
|
|
67
|
+
orderStatus: 'Order Fulfilled',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: generateUniqueId(),
|
|
71
|
+
brand: 'Lotus',
|
|
72
|
+
model: 'Emira',
|
|
73
|
+
year: '2023',
|
|
74
|
+
price: 'Starts at $78,000',
|
|
75
|
+
engine: 'Supercharged 3.5L V6',
|
|
76
|
+
transmission: 'Automatic or 6-Speed Manual',
|
|
77
|
+
horsepower: '400hp',
|
|
78
|
+
torque: '317 lb-ft',
|
|
79
|
+
curbWeight: '3520 lbs',
|
|
80
|
+
orderStatus: 'Shipped: In Transit',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: generateUniqueId(),
|
|
84
|
+
brand: 'Toyota',
|
|
85
|
+
model: 'Supra',
|
|
86
|
+
year: '1998',
|
|
87
|
+
price: '$40,000 - $80,000',
|
|
88
|
+
engine: '3.0L Inline 6',
|
|
89
|
+
transmission: 'Automatic or 6-Speed Manual',
|
|
90
|
+
horsepower: '320hp',
|
|
91
|
+
torque: '315 lb-ft',
|
|
92
|
+
curbWeight: '3,599 lbs',
|
|
93
|
+
orderStatus: 'Delivered',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: generateUniqueId(),
|
|
97
|
+
brand: 'Nissan',
|
|
98
|
+
model: 'Skyline GT-R',
|
|
99
|
+
year: '1994',
|
|
100
|
+
price: '$45,000 - $90,000',
|
|
101
|
+
engine: '2.6L Twin-Turbo Inline 6',
|
|
102
|
+
transmission: '5-Speed Manual',
|
|
103
|
+
horsepower: '276hp',
|
|
104
|
+
torque: '260 lb-ft',
|
|
105
|
+
curbWeight: '3,153 lbs',
|
|
106
|
+
orderStatus: 'Delivered',
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
interface RowProps {
|
|
111
|
+
data: AutoData;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const expandableSectionStyles = createStyles({
|
|
115
|
+
alignItems: 'flex-start',
|
|
116
|
+
display: 'flex',
|
|
117
|
+
gap: system.space.x4,
|
|
118
|
+
padding: system.space.x8,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const expandableHeadingStyles = createStyles({
|
|
122
|
+
margin: 0,
|
|
123
|
+
fontWeight: system.fontWeight.bold,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const expandableListStyles = createStyles({
|
|
127
|
+
listStyle: 'none',
|
|
128
|
+
margin: 0,
|
|
129
|
+
padding: 0,
|
|
130
|
+
gap: system.space.x2,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
function ExpandableRow({data}: RowProps) {
|
|
134
|
+
const [rowExpanded, setRowExpanded] = React.useState(false);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<>
|
|
138
|
+
<Table.Row gridTemplateColumns="4rem repeat(4, 1fr)">
|
|
139
|
+
<Table.Cell>
|
|
140
|
+
<Tooltip title={`${data.brand} ${data.model}`}>
|
|
141
|
+
<TertiaryButton
|
|
142
|
+
size="small"
|
|
143
|
+
icon={rowExpanded ? chevronDownSmallIcon : chevronRightSmallIcon}
|
|
144
|
+
aria-expanded={rowExpanded}
|
|
145
|
+
onClick={() => setRowExpanded(!rowExpanded)}
|
|
146
|
+
/>
|
|
147
|
+
</Tooltip>
|
|
148
|
+
</Table.Cell>
|
|
149
|
+
<Table.Header scope="row">{data.brand}</Table.Header>
|
|
150
|
+
<Table.Cell>{data.model}</Table.Cell>
|
|
151
|
+
<Table.Cell>{data.year}</Table.Cell>
|
|
152
|
+
<Table.Cell>{data.price}</Table.Cell>
|
|
153
|
+
</Table.Row>
|
|
154
|
+
{rowExpanded && (
|
|
155
|
+
<Table.Row>
|
|
156
|
+
<Table.Cell colSpan={5} cs={expandableSectionStyles}>
|
|
157
|
+
<div>
|
|
158
|
+
<Subtext as="h4" size="large" cs={expandableHeadingStyles}>
|
|
159
|
+
Status
|
|
160
|
+
</Subtext>
|
|
161
|
+
<StatusIndicator variant="blue">{data.orderStatus}</StatusIndicator>
|
|
162
|
+
</div>
|
|
163
|
+
<div>
|
|
164
|
+
<Subtext as="h4" size="large" cs={expandableHeadingStyles}>
|
|
165
|
+
Specifications
|
|
166
|
+
</Subtext>
|
|
167
|
+
<ul className={expandableListStyles}>
|
|
168
|
+
<Subtext as="li" size="large">
|
|
169
|
+
Engine: {data.engine}
|
|
170
|
+
</Subtext>
|
|
171
|
+
<Subtext as="li" size="large">
|
|
172
|
+
Transmission: {data.transmission}
|
|
173
|
+
</Subtext>
|
|
174
|
+
<Subtext as="li" size="large">
|
|
175
|
+
Horsepower: {data.horsepower}
|
|
176
|
+
</Subtext>
|
|
177
|
+
<Subtext as="li" size="large">
|
|
178
|
+
Torque: {data.torque}
|
|
179
|
+
</Subtext>
|
|
180
|
+
<Subtext as="li" size="large">
|
|
181
|
+
Curb Weight: {data.curbWeight}
|
|
182
|
+
</Subtext>
|
|
183
|
+
</ul>
|
|
184
|
+
</div>
|
|
185
|
+
</Table.Cell>
|
|
186
|
+
</Table.Row>
|
|
187
|
+
)}
|
|
188
|
+
</>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default createComponent()({
|
|
193
|
+
displayName: 'ExpandableRows',
|
|
194
|
+
Component: () => {
|
|
195
|
+
return (
|
|
196
|
+
<>
|
|
197
|
+
<Heading as="h3" id={headingID} size="small">
|
|
198
|
+
Showroom Inventory
|
|
199
|
+
</Heading>
|
|
200
|
+
<Table aria-labelledby={headingID}>
|
|
201
|
+
<Table.Head>
|
|
202
|
+
<Table.Row gridTemplateColumns="4rem repeat(4, 1fr)">
|
|
203
|
+
<Table.Cell></Table.Cell>
|
|
204
|
+
<Table.Header scope="col">Make</Table.Header>
|
|
205
|
+
<Table.Header scope="col">Model</Table.Header>
|
|
206
|
+
<Table.Header scope="col">Year</Table.Header>
|
|
207
|
+
<Table.Header scope="col">Price</Table.Header>
|
|
208
|
+
</Table.Row>
|
|
209
|
+
</Table.Head>
|
|
210
|
+
<Table.Body>
|
|
211
|
+
{autoData.map(item => (
|
|
212
|
+
<ExpandableRow key={item.id} data={item} />
|
|
213
|
+
))}
|
|
214
|
+
</Table.Body>
|
|
215
|
+
</Table>
|
|
216
|
+
</>
|
|
217
|
+
);
|
|
218
|
+
},
|
|
219
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {Table} from '@workday/canvas-kit-preview-react/table';
|
|
4
|
+
import {Heading} from '@workday/canvas-kit-react/text';
|
|
5
|
+
import {Checkbox} from '@workday/canvas-kit-react/checkbox';
|
|
6
|
+
import {createComponent, generateUniqueId} from '@workday/canvas-kit-react/common';
|
|
7
|
+
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
|
|
8
|
+
import {createStencil, createStyles} from '@workday/canvas-kit-styling';
|
|
9
|
+
import {base} from '@workday/canvas-tokens-web';
|
|
10
|
+
|
|
11
|
+
const selectableRowStencil = createStencil({
|
|
12
|
+
base: {
|
|
13
|
+
gridTemplateColumns: '3.5rem repeat(2, 1fr)',
|
|
14
|
+
transition: 'background-color 200ms',
|
|
15
|
+
},
|
|
16
|
+
modifiers: {
|
|
17
|
+
isSelected: {
|
|
18
|
+
true: {
|
|
19
|
+
backgroundColor: base.blueberry100,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const tableHeaderStyles = createStyles({
|
|
26
|
+
backgroundColor: base.soap100,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const tableCellStyles = createStyles({
|
|
30
|
+
backgroundColor: 'transparent',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
interface SelectableRowProps {
|
|
34
|
+
onSelect?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
35
|
+
rowData: PizzaTopping;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SelectableRow = createComponent('tr')({
|
|
39
|
+
displayName: 'SelectableRow',
|
|
40
|
+
Component: ({onSelect, rowData}: SelectableRowProps) => {
|
|
41
|
+
return (
|
|
42
|
+
<Table.Row cs={selectableRowStencil({isSelected: rowData.checked})}>
|
|
43
|
+
<Table.Cell cs={tableCellStyles}>
|
|
44
|
+
<Tooltip title={rowData.name}>
|
|
45
|
+
<Checkbox checked={rowData.checked} onChange={onSelect} />
|
|
46
|
+
</Tooltip>
|
|
47
|
+
</Table.Cell>
|
|
48
|
+
<Table.Cell cs={tableCellStyles} scope="row">
|
|
49
|
+
{rowData.name}
|
|
50
|
+
</Table.Cell>
|
|
51
|
+
<Table.Cell cs={tableCellStyles}>{rowData.amount}</Table.Cell>
|
|
52
|
+
</Table.Row>
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
interface PizzaTopping {
|
|
58
|
+
name: string;
|
|
59
|
+
amount: string;
|
|
60
|
+
checked: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const pizzaToppingData: PizzaTopping[] = [
|
|
64
|
+
{name: 'Pepperoni', amount: '2.5 oz.', checked: false},
|
|
65
|
+
{name: 'Mozzarella', amount: '5 oz.', checked: false},
|
|
66
|
+
{name: 'Basil', amount: '10 Leaves', checked: false},
|
|
67
|
+
{name: 'Roasted Red Peppers', amount: '3 oz.', checked: false},
|
|
68
|
+
{name: 'Mushrooms', amount: '2 oz.', checked: false},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const headingID = generateUniqueId();
|
|
72
|
+
|
|
73
|
+
type SelectAll = 'checked' | 'indeterminate' | 'unchecked';
|
|
74
|
+
|
|
75
|
+
export default () => {
|
|
76
|
+
const [selectAllState, setSelectAllState] = React.useState<SelectAll>('unchecked');
|
|
77
|
+
const [toppings, setToppings] = React.useState(pizzaToppingData);
|
|
78
|
+
|
|
79
|
+
const handleToppingChange = (name: string) => {
|
|
80
|
+
// Toggle the selected item's checked state and update state
|
|
81
|
+
const updatedToppings = toppings.map(topping => {
|
|
82
|
+
if (topping.name === name) {
|
|
83
|
+
return {...topping, checked: !topping.checked};
|
|
84
|
+
} else {
|
|
85
|
+
return topping;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
setToppings(updatedToppings);
|
|
89
|
+
|
|
90
|
+
// Update the Select All checkbox state
|
|
91
|
+
const selectedToppings = updatedToppings.filter(topping => topping.checked === true);
|
|
92
|
+
// If no toppings are selected, set the Select All checkbox to 'unchecked'
|
|
93
|
+
if (selectedToppings.length === 0) {
|
|
94
|
+
setSelectAllState('unchecked');
|
|
95
|
+
// If all toppings are selected, set the Select All checkbox to 'checked'
|
|
96
|
+
} else if (selectedToppings.length === updatedToppings.length) {
|
|
97
|
+
setSelectAllState('checked');
|
|
98
|
+
// Otherwise, set the Select All checkbox to 'indeterminate'
|
|
99
|
+
} else {
|
|
100
|
+
setSelectAllState('indeterminate');
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleSelectAll = () => {
|
|
105
|
+
// If the Select All checkbox is in a checked or indeterminate state,
|
|
106
|
+
// update it to 'unchecked', and uncheck all topping checkboxes
|
|
107
|
+
if (selectAllState === 'checked' || selectAllState === 'indeterminate') {
|
|
108
|
+
setSelectAllState('unchecked');
|
|
109
|
+
const updatedToppingData = toppings.map(topping => ({...topping, checked: false}));
|
|
110
|
+
setToppings(updatedToppingData);
|
|
111
|
+
}
|
|
112
|
+
// If the Select All checkbox is in an unchecked state,
|
|
113
|
+
// update it to 'checked', and check all topping checkboxes
|
|
114
|
+
if (selectAllState === 'unchecked') {
|
|
115
|
+
setSelectAllState('checked');
|
|
116
|
+
const updatedToppingData = toppings.map(topping => ({...topping, checked: true}));
|
|
117
|
+
setToppings(updatedToppingData);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
<Heading as="h3" id={headingID} size="small">
|
|
124
|
+
Select your pizza toppings
|
|
125
|
+
</Heading>
|
|
126
|
+
<Table aria-labelledby={headingID}>
|
|
127
|
+
<Table.Row gridTemplateColumns="3.5rem repeat(2, 1fr)">
|
|
128
|
+
<Table.Header cs={tableHeaderStyles}>
|
|
129
|
+
<Tooltip title="Select All">
|
|
130
|
+
<Checkbox
|
|
131
|
+
checked={selectAllState === 'checked'}
|
|
132
|
+
indeterminate={selectAllState === 'indeterminate'}
|
|
133
|
+
onChange={handleSelectAll}
|
|
134
|
+
/>
|
|
135
|
+
</Tooltip>
|
|
136
|
+
</Table.Header>
|
|
137
|
+
<Table.Header scope="col" cs={tableHeaderStyles}>
|
|
138
|
+
Toppings
|
|
139
|
+
</Table.Header>
|
|
140
|
+
<Table.Header scope="col" cs={tableHeaderStyles}>
|
|
141
|
+
Amount
|
|
142
|
+
</Table.Header>
|
|
143
|
+
</Table.Row>
|
|
144
|
+
<Table.Body>
|
|
145
|
+
{toppings.map(rowData => (
|
|
146
|
+
<SelectableRow
|
|
147
|
+
key={rowData.name}
|
|
148
|
+
rowData={rowData}
|
|
149
|
+
onSelect={() => handleToppingChange(rowData.name)}
|
|
150
|
+
/>
|
|
151
|
+
))}
|
|
152
|
+
</Table.Body>
|
|
153
|
+
</Table>
|
|
154
|
+
</>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import {createComponent} from '@workday/canvas-kit-react/common';
|
|
4
|
+
import {Table} from '@workday/canvas-kit-preview-react/table';
|
|
5
|
+
import {Tooltip} from '@workday/canvas-kit-react/tooltip';
|
|
6
|
+
import {TertiaryButton} from '@workday/canvas-kit-react/button';
|
|
7
|
+
import {Text} from '@workday/canvas-kit-react/text';
|
|
8
|
+
import {sortDownIcon, sortUpIcon} from '@workday/canvas-system-icons-web';
|
|
9
|
+
import {createStyles} from '@workday/canvas-kit-styling';
|
|
10
|
+
import {system} from '@workday/canvas-tokens-web';
|
|
11
|
+
|
|
12
|
+
interface CountryData {
|
|
13
|
+
country: string;
|
|
14
|
+
capital: string;
|
|
15
|
+
population: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const countryData: CountryData[] = [
|
|
19
|
+
{country: 'Australia', capital: 'Canberra', population: 25690000},
|
|
20
|
+
{country: 'Bahamas', capital: 'Nassau', population: 407906},
|
|
21
|
+
{country: 'Canada', capital: 'Ottawa', population: 38250000},
|
|
22
|
+
{country: 'Fiji', capital: 'Suva', population: 924610},
|
|
23
|
+
{country: 'Ghana', capital: 'Accra', population: 32830000},
|
|
24
|
+
{country: 'Hong Kong', capital: 'City of Victoria', population: 7413000},
|
|
25
|
+
{country: 'India', capital: 'New Delhi', population: 1408000000},
|
|
26
|
+
{country: 'Ireland', capital: 'Dublin', population: 5033000},
|
|
27
|
+
{country: 'Jamaica', capital: 'Kingston', population: 2828000},
|
|
28
|
+
{country: 'Kenya', capital: 'Nairobi', population: 53010000},
|
|
29
|
+
{country: 'Micronesia', capital: 'Palikir', population: 113131},
|
|
30
|
+
{country: 'New Zealand', capital: 'Wellington', population: 5123000},
|
|
31
|
+
{country: 'Philippines', capital: 'Manila', population: 113900000},
|
|
32
|
+
{country: 'Puerto Rico', capital: 'San Juan', population: 3264000},
|
|
33
|
+
{country: 'Samoa', capital: 'Apia', population: 218764},
|
|
34
|
+
{country: 'Singapore', capital: 'Singapore', population: 5454000},
|
|
35
|
+
{country: 'Tanzania', capital: 'Dodoma', population: 63590000},
|
|
36
|
+
{country: 'United Kingdom', capital: 'London', population: 67330000},
|
|
37
|
+
{country: 'United States', capital: 'Washington, D.C.', population: 331900000},
|
|
38
|
+
{country: 'Zimbabwe', capital: 'Harare', population: 15990000},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
type SortOrder = 'ascending' | 'descending' | 'none';
|
|
42
|
+
|
|
43
|
+
interface HeaderRowState {
|
|
44
|
+
column1SortDirection: SortOrder;
|
|
45
|
+
column2SortDirection: SortOrder;
|
|
46
|
+
column3SortDirection: SortOrder;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface HeaderRowAction {
|
|
50
|
+
column: 'Country' | 'Capital' | 'Population';
|
|
51
|
+
payload: CountryData[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const initialHeaderRowState: HeaderRowState = {
|
|
55
|
+
column1SortDirection: 'none',
|
|
56
|
+
column2SortDirection: 'none',
|
|
57
|
+
column3SortDirection: 'none',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Given the current sort order, return the next sort order
|
|
62
|
+
*/
|
|
63
|
+
function getNextSortOrder(sortOrder: SortOrder) {
|
|
64
|
+
return sortOrder === 'ascending' ? 'descending' : 'ascending';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function headerRowReducer(state: HeaderRowState, action: HeaderRowAction): HeaderRowState {
|
|
68
|
+
switch (action.column) {
|
|
69
|
+
case 'Country':
|
|
70
|
+
if (state.column1SortDirection === 'ascending') {
|
|
71
|
+
action.payload.sort((a, b) => b.country.localeCompare(a.country));
|
|
72
|
+
} else {
|
|
73
|
+
action.payload.sort((a, b) => a.country.localeCompare(b.country));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
...initialHeaderRowState,
|
|
78
|
+
column1SortDirection: getNextSortOrder(state.column1SortDirection),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
case 'Capital':
|
|
82
|
+
if (state.column2SortDirection === 'ascending') {
|
|
83
|
+
action.payload.sort((a, b) => b.capital.localeCompare(a.capital));
|
|
84
|
+
} else {
|
|
85
|
+
action.payload.sort((a, b) => a.capital.localeCompare(b.capital));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...initialHeaderRowState,
|
|
90
|
+
column2SortDirection: getNextSortOrder(state.column2SortDirection),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
case 'Population':
|
|
94
|
+
if (state.column3SortDirection === 'ascending') {
|
|
95
|
+
action.payload.sort((a, b) => b.population - a.population);
|
|
96
|
+
} else {
|
|
97
|
+
action.payload.sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
...initialHeaderRowState,
|
|
102
|
+
column3SortDirection: getNextSortOrder(state.column3SortDirection),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
return initialHeaderRowState;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface SortableColumnHeaderProps {
|
|
111
|
+
label: string;
|
|
112
|
+
onSortAction: (label: string) => void;
|
|
113
|
+
children?: React.ReactNode;
|
|
114
|
+
sortOrder: SortOrder;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const getSortIcon = (sortOrder?: SortOrder) => {
|
|
118
|
+
if (sortOrder === 'ascending') {
|
|
119
|
+
return sortUpIcon;
|
|
120
|
+
} else if (sortOrder === 'descending') {
|
|
121
|
+
return sortDownIcon;
|
|
122
|
+
} else {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default createComponent('th')({
|
|
128
|
+
displayName: 'SortableColumnHeader',
|
|
129
|
+
Component: (
|
|
130
|
+
{label, sortOrder, onSortAction, children, ...elemProps}: SortableColumnHeaderProps,
|
|
131
|
+
ref
|
|
132
|
+
) => {
|
|
133
|
+
return (
|
|
134
|
+
<Table.Header ref={ref} scope="col" aria-sort={sortOrder} {...elemProps}>
|
|
135
|
+
<Tooltip type="describe" title={`Sort ${getNextSortOrder(sortOrder)}`}>
|
|
136
|
+
<TertiaryButton
|
|
137
|
+
icon={getSortIcon(sortOrder)}
|
|
138
|
+
iconPosition="end"
|
|
139
|
+
onClick={() => onSortAction(label)}
|
|
140
|
+
>
|
|
141
|
+
{children}
|
|
142
|
+
</TertiaryButton>
|
|
143
|
+
</Tooltip>
|
|
144
|
+
</Table.Header>
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const textStyles = createStyles({
|
|
150
|
+
paddingInlineStart: system.space.x3,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export default () => {
|
|
154
|
+
const [headerRowState, headerRowDispatch] = React.useReducer(
|
|
155
|
+
headerRowReducer,
|
|
156
|
+
initialHeaderRowState
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
function sortColumnHandler(columnName) {
|
|
160
|
+
headerRowDispatch({
|
|
161
|
+
column: columnName,
|
|
162
|
+
payload: countryData,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<Table maxHeight="40rem">
|
|
168
|
+
<Table.Caption>Population Listed by Country (2021)</Table.Caption>
|
|
169
|
+
<Table.Head>
|
|
170
|
+
<Table.Row>
|
|
171
|
+
<SortableColumnHeader
|
|
172
|
+
label="Country"
|
|
173
|
+
sortOrder={headerRowState.column1SortDirection as SortOrder}
|
|
174
|
+
onSortAction={sortColumnHandler}
|
|
175
|
+
>
|
|
176
|
+
Country
|
|
177
|
+
</SortableColumnHeader>
|
|
178
|
+
<SortableColumnHeader
|
|
179
|
+
label="Capital"
|
|
180
|
+
sortOrder={headerRowState.column2SortDirection as SortOrder}
|
|
181
|
+
onSortAction={sortColumnHandler}
|
|
182
|
+
>
|
|
183
|
+
Capital
|
|
184
|
+
</SortableColumnHeader>
|
|
185
|
+
<SortableColumnHeader
|
|
186
|
+
label="Population"
|
|
187
|
+
sortOrder={headerRowState.column3SortDirection as SortOrder}
|
|
188
|
+
onSortAction={sortColumnHandler}
|
|
189
|
+
>
|
|
190
|
+
Population
|
|
191
|
+
</SortableColumnHeader>
|
|
192
|
+
</Table.Row>
|
|
193
|
+
</Table.Head>
|
|
194
|
+
<Table.Body>
|
|
195
|
+
{countryData.map(item => {
|
|
196
|
+
return (
|
|
197
|
+
<Table.Row key={item.country}>
|
|
198
|
+
<Table.Header scope="row">
|
|
199
|
+
<Text cs={textStyles}>{item.country}</Text>
|
|
200
|
+
</Table.Header>
|
|
201
|
+
<Table.Cell>
|
|
202
|
+
<Text cs={textStyles}>{item.capital}</Text>
|
|
203
|
+
</Table.Cell>
|
|
204
|
+
<Table.Cell>
|
|
205
|
+
<Text cs={textStyles}>{item.population.toLocaleString()}</Text>
|
|
206
|
+
</Table.Cell>
|
|
207
|
+
</Table.Row>
|
|
208
|
+
);
|
|
209
|
+
})}
|
|
210
|
+
</Table.Body>
|
|
211
|
+
</Table>
|
|
212
|
+
);
|
|
213
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {SymbolDoc
|
|
1
|
+
import {SymbolDoc} from '@workday/canvas-kit-docs';
|
|
2
2
|
import {Table} from '@workday/canvas-kit-preview-react/table';
|
|
3
3
|
// Examples
|
|
4
4
|
import Basic from './examples/Basic';
|
|
@@ -53,6 +53,14 @@ Users may add styles to the `Table.Header` to render a fixed column. The example
|
|
|
53
53
|
|
|
54
54
|
<ExampleCodeBlock code={FixedColumn} />
|
|
55
55
|
|
|
56
|
+
### Advanced
|
|
57
|
+
|
|
58
|
+
You can also find several advanced Table examples in our Storybook Examples section.
|
|
59
|
+
|
|
60
|
+
- [Expandable Rows](/docs/examples-tables-advanced--expandable-rows)
|
|
61
|
+
- [Selectable Rows ](/docs/examples-tables-advanced--selectable-rows)
|
|
62
|
+
- [Sortable Column Headers](/docs/examples-tables-advanced--sortable-column-headers)
|
|
63
|
+
|
|
56
64
|
## Component API
|
|
57
65
|
|
|
58
66
|
<SymbolDoc name="Table" fileName="/preview-react/" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workday/canvas-kit-docs",
|
|
3
|
-
"version": "10.3.
|
|
3
|
+
"version": "10.3.8",
|
|
4
4
|
"description": "Documentation components of Canvas Kit components",
|
|
5
5
|
"author": "Workday, Inc. (https://www.workday.com)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@emotion/styled": "^11.6.0",
|
|
46
46
|
"@storybook/csf": "0.0.1",
|
|
47
|
-
"@workday/canvas-kit-labs-react": "^10.3.
|
|
48
|
-
"@workday/canvas-kit-preview-react": "^10.3.
|
|
49
|
-
"@workday/canvas-kit-react": "^10.3.
|
|
50
|
-
"@workday/canvas-kit-styling": "^10.3.
|
|
47
|
+
"@workday/canvas-kit-labs-react": "^10.3.8",
|
|
48
|
+
"@workday/canvas-kit-preview-react": "^10.3.8",
|
|
49
|
+
"@workday/canvas-kit-react": "^10.3.8",
|
|
50
|
+
"@workday/canvas-kit-styling": "^10.3.8",
|
|
51
51
|
"@workday/canvas-system-icons-web": "^3.0.0",
|
|
52
52
|
"@workday/canvas-tokens-web": "^1.0.0",
|
|
53
53
|
"markdown-to-jsx": "^6.10.3",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"mkdirp": "^1.0.3",
|
|
60
60
|
"typescript": "4.2"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "90aa5dab1b3136b368ccc8d6e4153e8f1cafc417"
|
|
63
63
|
}
|