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