@codecademy/styleguide 79.1.2 → 79.1.3-alpha.09ab91.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/CHANGELOG.md +4 -0
- package/package.json +2 -2
- package/src/lib/Organisms/Lists & Tables/DataList/CurrentEmployeeTable.tsx +175 -0
- package/src/lib/Organisms/Lists & Tables/DataList/DataList.mdx +12 -0
- package/src/lib/Organisms/Lists & Tables/DataList/DataList.stories.tsx +10 -0
- package/src/lib/Organisms/Lists & Tables/DataList/EmployeeTable.tsx +362 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
### [79.1.3-alpha.09ab91.0](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@79.1.2...@codecademy/styleguide@79.1.3-alpha.09ab91.0) (2026-02-26)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @codecademy/styleguide
|
|
9
|
+
|
|
6
10
|
### [79.1.2](https://github.com/Codecademy/gamut/compare/@codecademy/styleguide@79.1.1...@codecademy/styleguide@79.1.2) (2026-02-12)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @codecademy/styleguide
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecademy/styleguide",
|
|
3
3
|
"description": "Styleguide & Component library for codecademy.com",
|
|
4
|
-
"version": "79.1.
|
|
4
|
+
"version": "79.1.3-alpha.09ab91.0",
|
|
5
5
|
"author": "Codecademy Engineering",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"repository": "git@github.com:Codecademy/gamut.git",
|
|
11
|
-
"gitHead": "
|
|
11
|
+
"gitHead": "014ac90de1bb9d802dc412b4008732d3225e03e9"
|
|
12
12
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { DataList, FlexBox, Text } from '@codecademy/gamut';
|
|
3
|
+
import styled from '@emotion/styled';
|
|
4
|
+
|
|
5
|
+
const StyledDataListWrapper = styled.div`
|
|
6
|
+
width: 100%;
|
|
7
|
+
|
|
8
|
+
thead tr {
|
|
9
|
+
background: var(--color-background-selected) !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
thead th,
|
|
13
|
+
thead th a {
|
|
14
|
+
font-weight: 700;
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const EmployeeCell = ({ name, role }) => (
|
|
19
|
+
<FlexBox flexDirection="column">
|
|
20
|
+
<Text fontWeight={700} variant="p-large">
|
|
21
|
+
{name}
|
|
22
|
+
</Text>
|
|
23
|
+
<Text variant="p-small">{role}</Text>
|
|
24
|
+
</FlexBox>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const columns = [
|
|
28
|
+
{
|
|
29
|
+
header: 'Employee',
|
|
30
|
+
key: 'employee',
|
|
31
|
+
size: 'xl',
|
|
32
|
+
sortable: true,
|
|
33
|
+
type: 'header',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
header: 'Skill health avg',
|
|
37
|
+
key: 'skillHealthAvg',
|
|
38
|
+
size: 'lg',
|
|
39
|
+
sortable: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
header: 'Skill spread',
|
|
43
|
+
key: 'skillSpread',
|
|
44
|
+
size: 'lg',
|
|
45
|
+
sortable: true,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
header: 'Gaps',
|
|
49
|
+
key: 'gaps',
|
|
50
|
+
size: 'md',
|
|
51
|
+
sortable: true,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
header: 'Gaps closed',
|
|
55
|
+
key: 'gapsClosed',
|
|
56
|
+
size: 'md',
|
|
57
|
+
sortable: true,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const rows = [
|
|
62
|
+
{
|
|
63
|
+
id: '1',
|
|
64
|
+
employee: (
|
|
65
|
+
<EmployeeCell name="Joshua Lewis" role="Quality Control Analyst" />
|
|
66
|
+
),
|
|
67
|
+
skillHealthAvg: 'On target',
|
|
68
|
+
skillSpread: '44 skills',
|
|
69
|
+
gaps: 2,
|
|
70
|
+
gapsClosed: '0 +6%',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: '2',
|
|
74
|
+
employee: (
|
|
75
|
+
<EmployeeCell
|
|
76
|
+
name="Kathryn Mallory Murphy-Richards"
|
|
77
|
+
role="Clinical Data Manager"
|
|
78
|
+
/>
|
|
79
|
+
),
|
|
80
|
+
skillHealthAvg: 'Needs improvement',
|
|
81
|
+
skillSpread: '19 skills',
|
|
82
|
+
gaps: 2,
|
|
83
|
+
gapsClosed: '0 +6%',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: '3',
|
|
87
|
+
employee: (
|
|
88
|
+
<EmployeeCell
|
|
89
|
+
name="Cameron Williamson"
|
|
90
|
+
role="Regulatory Affairs Specialist"
|
|
91
|
+
/>
|
|
92
|
+
),
|
|
93
|
+
skillHealthAvg: 'Above target',
|
|
94
|
+
skillSpread: '23 skills',
|
|
95
|
+
gaps: 1,
|
|
96
|
+
gapsClosed: '0 +6%',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: '4',
|
|
100
|
+
employee: (
|
|
101
|
+
<EmployeeCell name="Theresa Webb" role="Pharmacovigilance Scientist" />
|
|
102
|
+
),
|
|
103
|
+
skillHealthAvg: 'Needs improvement',
|
|
104
|
+
skillSpread: '41 skills',
|
|
105
|
+
gaps: 1,
|
|
106
|
+
gapsClosed: '0 +6%',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: '5',
|
|
110
|
+
employee: (
|
|
111
|
+
<EmployeeCell name="Savannah Nguyen" role="Medical Science Liaison" />
|
|
112
|
+
),
|
|
113
|
+
skillHealthAvg: 'On target',
|
|
114
|
+
skillSpread: '18 skills',
|
|
115
|
+
gaps: 0,
|
|
116
|
+
gapsClosed: '0 +6%',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: '6',
|
|
120
|
+
employee: <EmployeeCell name="Jacob Jones" role="Research Scientist" />,
|
|
121
|
+
skillHealthAvg: 'Above target',
|
|
122
|
+
skillSpread: '33 skills',
|
|
123
|
+
gaps: 0,
|
|
124
|
+
gapsClosed: '0 +6%',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: '7',
|
|
128
|
+
employee: (
|
|
129
|
+
<EmployeeCell name="Jane Cooper" role="Clinical Trial Coordinator" />
|
|
130
|
+
),
|
|
131
|
+
skillHealthAvg: 'Critical',
|
|
132
|
+
skillSpread: '40 skills',
|
|
133
|
+
gaps: 4,
|
|
134
|
+
gapsClosed: '0 +6%',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: '8',
|
|
138
|
+
employee: (
|
|
139
|
+
<EmployeeCell name="Joshua Lewis" role="Quality Assurance Manager" />
|
|
140
|
+
),
|
|
141
|
+
skillHealthAvg: 'Needs improvement',
|
|
142
|
+
skillSpread: '29 skills',
|
|
143
|
+
gaps: 3,
|
|
144
|
+
gapsClosed: '0 +6%',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: '9',
|
|
148
|
+
employee: (
|
|
149
|
+
<EmployeeCell
|
|
150
|
+
name="Kathryn Mallory Murphy-Richards"
|
|
151
|
+
role="Formulation Development Scientist"
|
|
152
|
+
/>
|
|
153
|
+
),
|
|
154
|
+
skillHealthAvg: 'Needs improvement',
|
|
155
|
+
skillSpread: '12 skills',
|
|
156
|
+
gaps: 1,
|
|
157
|
+
gapsClosed: '0 +6%',
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
export function CurrentEmployeeTable() {
|
|
162
|
+
return (
|
|
163
|
+
<StyledDataListWrapper>
|
|
164
|
+
<DataList
|
|
165
|
+
header
|
|
166
|
+
id="employees-table"
|
|
167
|
+
idKey="id"
|
|
168
|
+
rows={rows}
|
|
169
|
+
columns={columns}
|
|
170
|
+
spacing="normal"
|
|
171
|
+
onRowSelect={() => {}}
|
|
172
|
+
/>
|
|
173
|
+
</StyledDataListWrapper>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -191,6 +191,18 @@ The example below demonstrates the difference between default container query be
|
|
|
191
191
|
|
|
192
192
|
<Callout text="In most cases, you should use the default container query behavior as it provides better responsive design. Only disable container queries when you have specific layout requirements that conflict with the default behavior." />
|
|
193
193
|
|
|
194
|
+
## Employee table example
|
|
195
|
+
|
|
196
|
+
An example of a DataList used to display employee skill data, with custom cell renderers for health status tags, gap warning indicators, and a row action menu.
|
|
197
|
+
|
|
198
|
+
<Canvas of={DataListStories.EmployeeTableExample} />
|
|
199
|
+
|
|
200
|
+
### Passing React elements directly as row values
|
|
201
|
+
|
|
202
|
+
Row values can be React elements — rendered as-is inside the cell without needing a `render` function on the column. This is useful when the data is already shaped at the call site.
|
|
203
|
+
|
|
204
|
+
<Canvas of={DataListStories.CurrentEmployeeTableExample} />
|
|
205
|
+
|
|
194
206
|
## Playground
|
|
195
207
|
|
|
196
208
|
<Canvas sourceState="shown" of={DataListStories.Default} />
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
simpleColumns,
|
|
13
13
|
simpleRows,
|
|
14
14
|
} from '../examples';
|
|
15
|
+
import { CurrentEmployeeTable } from './CurrentEmployeeTable';
|
|
16
|
+
import { EmployeeTable } from './EmployeeTable';
|
|
15
17
|
|
|
16
18
|
const meta: Meta<typeof DataList> = {
|
|
17
19
|
component: DataList,
|
|
@@ -213,3 +215,11 @@ export const DisableContainerQuery: Story = {
|
|
|
213
215
|
args: {},
|
|
214
216
|
render: () => <DataListDisableContainerQueryExample />,
|
|
215
217
|
};
|
|
218
|
+
|
|
219
|
+
export const EmployeeTableExample: Story = {
|
|
220
|
+
render: () => <EmployeeTable />,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export const CurrentEmployeeTableExample: Story = {
|
|
224
|
+
render: () => <CurrentEmployeeTable />,
|
|
225
|
+
};
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
ColumnConfig,
|
|
5
|
+
DataList,
|
|
6
|
+
FlexBox,
|
|
7
|
+
IconButton,
|
|
8
|
+
Text,
|
|
9
|
+
useLocalQuery,
|
|
10
|
+
} from '@codecademy/gamut';
|
|
11
|
+
import {
|
|
12
|
+
MiniKebabMenuIcon,
|
|
13
|
+
MiniWarningTriangleIcon,
|
|
14
|
+
} from '@codecademy/gamut-icons';
|
|
15
|
+
import { theme } from '@codecademy/gamut-styles';
|
|
16
|
+
import styled from '@emotion/styled';
|
|
17
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
const StyledDataListWrapper = styled.div`
|
|
20
|
+
width: 100%;
|
|
21
|
+
|
|
22
|
+
thead tr {
|
|
23
|
+
background: var(--color-background-selected);
|
|
24
|
+
border-top-left-radius: ${theme.borderRadii.xl};
|
|
25
|
+
border-top-right-radius: ${theme.borderRadii.xl};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
thead th > div {
|
|
29
|
+
align-items: center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
thead th,
|
|
33
|
+
thead th button {
|
|
34
|
+
font-weight: 700 !important;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
tbody tr:last-child {
|
|
38
|
+
border-bottom-left-radius: ${theme.borderRadii.xl};
|
|
39
|
+
border-bottom-right-radius: ${theme.borderRadii.xl};
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
type SkillHealth =
|
|
44
|
+
| 'On target'
|
|
45
|
+
| 'Above target'
|
|
46
|
+
| 'Needs improvement'
|
|
47
|
+
| 'Critical';
|
|
48
|
+
|
|
49
|
+
interface EmployeeRow {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
title: string;
|
|
53
|
+
skillHealthAvg: SkillHealth;
|
|
54
|
+
skillSpread: number;
|
|
55
|
+
gapCount: number;
|
|
56
|
+
criticalGapCount: number;
|
|
57
|
+
gapsClosed: number;
|
|
58
|
+
gapGrowthPct: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const HEALTH_TAG_STYLES: Record<SkillHealth, { bg: string; color: string }> = {
|
|
62
|
+
'On target': { bg: 'background-success', color: 'feedback-success' },
|
|
63
|
+
'Above target': { bg: 'background-success', color: 'feedback-success' },
|
|
64
|
+
// NOTE: `theme.colors` is a global variable that is imported from `@codecademy/gamut-styles`
|
|
65
|
+
// and `yellow-400` is a static color, i.e. it doesn't switch when toggling between light and dark mode.
|
|
66
|
+
// it also does NOT pass a contrast test
|
|
67
|
+
'Needs improvement': {
|
|
68
|
+
bg: 'background-warning',
|
|
69
|
+
color: `${theme.colors['yellow-400']}`,
|
|
70
|
+
},
|
|
71
|
+
Critical: { bg: 'background-error', color: 'feedback-error' },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const EMPLOYEES: EmployeeRow[] = [
|
|
75
|
+
{
|
|
76
|
+
id: '1',
|
|
77
|
+
name: 'Joshua Lewis',
|
|
78
|
+
title: 'Quality Control Analyst',
|
|
79
|
+
skillHealthAvg: 'On target',
|
|
80
|
+
skillSpread: 44,
|
|
81
|
+
gapCount: 0,
|
|
82
|
+
criticalGapCount: 0,
|
|
83
|
+
gapsClosed: 8,
|
|
84
|
+
gapGrowthPct: 8,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: '2',
|
|
88
|
+
name: 'Kathryn Mallory Murphy-Richards',
|
|
89
|
+
title: 'Clinical Data Manager',
|
|
90
|
+
skillHealthAvg: 'Needs improvement',
|
|
91
|
+
skillSpread: 19,
|
|
92
|
+
gapCount: 2,
|
|
93
|
+
criticalGapCount: 0,
|
|
94
|
+
gapsClosed: 8,
|
|
95
|
+
gapGrowthPct: 8,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: '3',
|
|
99
|
+
name: 'Cameron Williamson',
|
|
100
|
+
title: 'Regulatory Affairs Specialist',
|
|
101
|
+
skillHealthAvg: 'Above target',
|
|
102
|
+
skillSpread: 23,
|
|
103
|
+
gapCount: 0,
|
|
104
|
+
criticalGapCount: 0,
|
|
105
|
+
gapsClosed: 8,
|
|
106
|
+
gapGrowthPct: 8,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: '4',
|
|
110
|
+
name: 'Theresa Webb',
|
|
111
|
+
title: 'Pharmacovigilance Scientist',
|
|
112
|
+
skillHealthAvg: 'Needs improvement',
|
|
113
|
+
skillSpread: 41,
|
|
114
|
+
gapCount: 1,
|
|
115
|
+
criticalGapCount: 0,
|
|
116
|
+
gapsClosed: 8,
|
|
117
|
+
gapGrowthPct: 8,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: '5',
|
|
121
|
+
name: 'Savannah Nguyen',
|
|
122
|
+
title: 'Medical Science Liaison',
|
|
123
|
+
skillHealthAvg: 'On target',
|
|
124
|
+
skillSpread: 18,
|
|
125
|
+
gapCount: 0,
|
|
126
|
+
criticalGapCount: 0,
|
|
127
|
+
gapsClosed: 8,
|
|
128
|
+
gapGrowthPct: 8,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: '6',
|
|
132
|
+
name: 'Jacob Jones',
|
|
133
|
+
title: 'Research Scientist',
|
|
134
|
+
skillHealthAvg: 'Above target',
|
|
135
|
+
skillSpread: 33,
|
|
136
|
+
gapCount: 0,
|
|
137
|
+
criticalGapCount: 0,
|
|
138
|
+
gapsClosed: 8,
|
|
139
|
+
gapGrowthPct: 8,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: '7',
|
|
143
|
+
name: 'Jane Cooper',
|
|
144
|
+
title: 'Clinical Trial Coordinator',
|
|
145
|
+
skillHealthAvg: 'Critical',
|
|
146
|
+
skillSpread: 48,
|
|
147
|
+
gapCount: 4,
|
|
148
|
+
criticalGapCount: 1,
|
|
149
|
+
gapsClosed: 8,
|
|
150
|
+
gapGrowthPct: 8,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: '8',
|
|
154
|
+
name: 'Joshua Lewis',
|
|
155
|
+
title: 'Quality Assurance Manager',
|
|
156
|
+
skillHealthAvg: 'Needs improvement',
|
|
157
|
+
skillSpread: 29,
|
|
158
|
+
gapCount: 3,
|
|
159
|
+
criticalGapCount: 0,
|
|
160
|
+
gapsClosed: 8,
|
|
161
|
+
gapGrowthPct: 8,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: '9',
|
|
165
|
+
name: 'Kathryn Mallory Murphy-Richards',
|
|
166
|
+
title: 'Formulation Development Scientist',
|
|
167
|
+
skillHealthAvg: 'Needs improvement',
|
|
168
|
+
skillSpread: 12,
|
|
169
|
+
gapCount: 1,
|
|
170
|
+
criticalGapCount: 0,
|
|
171
|
+
gapsClosed: 8,
|
|
172
|
+
gapGrowthPct: 8,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: '10',
|
|
176
|
+
name: 'Cameron Williamson',
|
|
177
|
+
title: 'Bioinformatician',
|
|
178
|
+
skillHealthAvg: 'Critical',
|
|
179
|
+
skillSpread: 38,
|
|
180
|
+
gapCount: 5,
|
|
181
|
+
criticalGapCount: 2,
|
|
182
|
+
gapsClosed: 8,
|
|
183
|
+
gapGrowthPct: 8,
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
function HealthStatusTag({ status }: { status: SkillHealth }) {
|
|
188
|
+
const { bg, color } = HEALTH_TAG_STYLES[status];
|
|
189
|
+
return (
|
|
190
|
+
<FlexBox alignItems="center" bg={bg} px={8} py={2}>
|
|
191
|
+
<Text color={color} fontSize={14}>
|
|
192
|
+
{status}
|
|
193
|
+
</Text>
|
|
194
|
+
</FlexBox>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function GapTag({
|
|
199
|
+
count,
|
|
200
|
+
severity,
|
|
201
|
+
}: {
|
|
202
|
+
count: number;
|
|
203
|
+
severity: 'warning' | 'critical';
|
|
204
|
+
}) {
|
|
205
|
+
const isWarning = severity === 'warning';
|
|
206
|
+
return (
|
|
207
|
+
<FlexBox
|
|
208
|
+
alignItems="center"
|
|
209
|
+
bg={isWarning ? 'background-warning' : 'background-error'}
|
|
210
|
+
gap={8}
|
|
211
|
+
px={8}
|
|
212
|
+
py={2}
|
|
213
|
+
>
|
|
214
|
+
<MiniWarningTriangleIcon
|
|
215
|
+
color={isWarning ? `${theme.colors['yellow-400']}` : 'feedback-error'}
|
|
216
|
+
size={16}
|
|
217
|
+
/>
|
|
218
|
+
<Text
|
|
219
|
+
color={isWarning ? `${theme.colors['yellow-400']}` : 'feedback-error'}
|
|
220
|
+
fontSize={14}
|
|
221
|
+
>
|
|
222
|
+
{count}
|
|
223
|
+
</Text>
|
|
224
|
+
</FlexBox>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const COLUMNS: ColumnConfig<EmployeeRow>[] = [
|
|
229
|
+
{
|
|
230
|
+
key: 'name',
|
|
231
|
+
header: 'Employee',
|
|
232
|
+
sortable: true,
|
|
233
|
+
size: 'xl',
|
|
234
|
+
type: 'header',
|
|
235
|
+
render: (row) => (
|
|
236
|
+
<FlexBox flexDirection="column" py={12}>
|
|
237
|
+
<Text
|
|
238
|
+
fontFamily="accent"
|
|
239
|
+
fontSize={18}
|
|
240
|
+
fontWeight={700}
|
|
241
|
+
lineHeight="title"
|
|
242
|
+
>
|
|
243
|
+
{row.name}
|
|
244
|
+
</Text>
|
|
245
|
+
<Text fontSize={16}>{row.title}</Text>
|
|
246
|
+
</FlexBox>
|
|
247
|
+
),
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
// InfoTip labels require a custom header row beyond the standard ColumnConfig API
|
|
251
|
+
key: 'skillHealthAvg',
|
|
252
|
+
header: 'Skill health avg',
|
|
253
|
+
sortable: true,
|
|
254
|
+
size: 'lg',
|
|
255
|
+
render: (row) => <HealthStatusTag status={row.skillHealthAvg} />,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
key: 'skillSpread',
|
|
259
|
+
header: 'Skill spread',
|
|
260
|
+
sortable: true,
|
|
261
|
+
size: 'md',
|
|
262
|
+
render: (row) => (
|
|
263
|
+
<FlexBox alignItems="center" gap={8}>
|
|
264
|
+
<Text fontSize={16}>{row.skillSpread}</Text>
|
|
265
|
+
<Text color="text-secondary" fontSize={16}>
|
|
266
|
+
skills
|
|
267
|
+
</Text>
|
|
268
|
+
</FlexBox>
|
|
269
|
+
),
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: 'gapCount',
|
|
273
|
+
header: 'Gaps',
|
|
274
|
+
sortable: true,
|
|
275
|
+
size: 'md',
|
|
276
|
+
render: (row) => (
|
|
277
|
+
<FlexBox alignItems="center" gap={8}>
|
|
278
|
+
{row.gapCount > 0 && <GapTag count={row.gapCount} severity="warning" />}
|
|
279
|
+
{row.criticalGapCount > 0 && (
|
|
280
|
+
<GapTag count={row.criticalGapCount} severity="critical" />
|
|
281
|
+
)}
|
|
282
|
+
</FlexBox>
|
|
283
|
+
),
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
key: 'gapsClosed',
|
|
287
|
+
header: 'Gaps closed',
|
|
288
|
+
sortable: true,
|
|
289
|
+
size: 'md',
|
|
290
|
+
render: (row) => (
|
|
291
|
+
<FlexBox alignItems="center" gap={8}>
|
|
292
|
+
<Text fontSize={16}>{row.gapsClosed}</Text>
|
|
293
|
+
<FlexBox alignItems="center" bg="background-success" px={8} py={2}>
|
|
294
|
+
<Text color="feedback-success" fontSize={16}>
|
|
295
|
+
+{row.gapGrowthPct}%
|
|
296
|
+
</Text>
|
|
297
|
+
</FlexBox>
|
|
298
|
+
</FlexBox>
|
|
299
|
+
),
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
key: 'actions',
|
|
303
|
+
header: 'Actions',
|
|
304
|
+
size: 'content',
|
|
305
|
+
type: 'control',
|
|
306
|
+
render: () => (
|
|
307
|
+
<IconButton
|
|
308
|
+
icon={MiniKebabMenuIcon}
|
|
309
|
+
size="small"
|
|
310
|
+
tip="Show more options"
|
|
311
|
+
/>
|
|
312
|
+
),
|
|
313
|
+
},
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
export function EmployeeTable() {
|
|
317
|
+
const [selectedRows, setSelectedRows] = useState<string[]>([]);
|
|
318
|
+
|
|
319
|
+
const { idKey, query, rows, onQueryChange } = useLocalQuery({
|
|
320
|
+
idKey: 'id',
|
|
321
|
+
rows: EMPLOYEES,
|
|
322
|
+
columns: COLUMNS,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const allIds = useMemo(() => EMPLOYEES.map(({ id }) => id), []);
|
|
326
|
+
|
|
327
|
+
const onRowSelect = useCallback(
|
|
328
|
+
({
|
|
329
|
+
type,
|
|
330
|
+
payload: { toggle, rowId },
|
|
331
|
+
}: {
|
|
332
|
+
type: string;
|
|
333
|
+
payload: { toggle: boolean; rowId: string };
|
|
334
|
+
}) => {
|
|
335
|
+
if (type === 'select') {
|
|
336
|
+
return setSelectedRows((prev) =>
|
|
337
|
+
toggle ? prev.filter((id) => id !== rowId) : [...prev, rowId]
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (type === 'select-all') {
|
|
341
|
+
return setSelectedRows(toggle ? [] : allIds);
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
[allIds]
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<StyledDataListWrapper>
|
|
349
|
+
<DataList
|
|
350
|
+
columns={COLUMNS}
|
|
351
|
+
header
|
|
352
|
+
id="employee-table"
|
|
353
|
+
idKey={idKey}
|
|
354
|
+
query={query}
|
|
355
|
+
rows={rows}
|
|
356
|
+
selected={selectedRows}
|
|
357
|
+
onQueryChange={onQueryChange}
|
|
358
|
+
onRowSelect={onRowSelect}
|
|
359
|
+
/>
|
|
360
|
+
</StyledDataListWrapper>
|
|
361
|
+
);
|
|
362
|
+
}
|