@codecademy/gamut 68.6.1-alpha.edab62.0 → 68.6.1-alpha.f6b2ce.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/agent-tools/.cursor-plugin/plugin.json +1 -1
- package/agent-tools/DESIGN.Codecademy.md +466 -413
- package/agent-tools/DESIGN.LXStudio.md +350 -282
- package/agent-tools/DESIGN.Percipio.md +357 -279
- package/agent-tools/commands/gamut-review.md +176 -87
- package/agent-tools/guidelines/components/animations.md +74 -0
- package/agent-tools/guidelines/components/buttons.md +74 -23
- package/agent-tools/guidelines/components/card.md +19 -0
- package/agent-tools/guidelines/components/coachmark.md +21 -0
- package/agent-tools/guidelines/components/data-table.md +79 -0
- package/agent-tools/guidelines/components/forms.md +106 -0
- package/agent-tools/guidelines/components/loading-states.md +17 -0
- package/agent-tools/guidelines/components/menu.md +58 -0
- package/agent-tools/guidelines/components/overview.md +97 -17
- package/agent-tools/guidelines/components/radial-progress.md +13 -0
- package/agent-tools/guidelines/components/select.md +23 -0
- package/agent-tools/guidelines/components/tooltips.md +22 -0
- package/agent-tools/guidelines/components/video.md +29 -0
- package/agent-tools/guidelines/foundations/color.md +140 -58
- package/agent-tools/guidelines/foundations/modes.md +41 -17
- package/agent-tools/guidelines/foundations/spacing.md +78 -37
- package/agent-tools/guidelines/foundations/typography.md +69 -37
- package/agent-tools/guidelines/overview-icons.md +19 -0
- package/agent-tools/guidelines/overview-illustrations.md +7 -0
- package/agent-tools/guidelines/overview-patterns.md +7 -0
- package/agent-tools/guidelines/overview.md +71 -22
- package/agent-tools/guidelines/setup.md +59 -18
- package/agent-tools/rules/accessibility.mdc +22 -13
- package/agent-tools/skills/gamut-accessibility/SKILL.md +97 -112
- package/agent-tools/skills/gamut-color-mode/SKILL.md +91 -41
- package/agent-tools/skills/gamut-components/SKILL.md +46 -0
- package/agent-tools/skills/gamut-forms/SKILL.md +101 -0
- package/agent-tools/skills/gamut-style-utilities/SKILL.md +111 -0
- package/agent-tools/skills/gamut-system-props/SKILL.md +81 -29
- package/agent-tools/skills/gamut-testing/SKILL.md +106 -62
- package/agent-tools/skills/gamut-theming/SKILL.md +36 -86
- package/agent-tools/skills/gamut-typography/SKILL.md +36 -80
- package/bin/commands/plugin/install.mjs +96 -56
- package/bin/commands/plugin/list.mjs +11 -43
- package/bin/commands/plugin/remove.mjs +30 -38
- package/bin/commands/plugin/update.mjs +15 -5
- package/bin/gamut.mjs +17 -13
- package/bin/lib/design.mjs +71 -0
- package/bin/lib/io.mjs +14 -0
- package/package.json +6 -6
- package/bin/lib/figma.mjs +0 -49
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# DataTable & DataList sorting
|
|
2
|
+
|
|
3
|
+
`DataTable` and `DataList` do not sort data automatically. `sortable: true` on a column only renders sort UI. You must implement sorting via `query` and `onQueryChange`, and pass pre-sorted `rows`.
|
|
4
|
+
|
|
5
|
+
## How sorting works
|
|
6
|
+
|
|
7
|
+
1. `sortable: true` — enables sort toggle on the column header. Without `query` / `onQueryChange`, clicks do nothing.
|
|
8
|
+
2. `query` — `Query<Row>` holding current sort (and filter) state. `sort` maps column keys to `'asc' | 'desc'`.
|
|
9
|
+
3. `onQueryChange` — receives `QueryChangeEvent<Row>` when the user clicks a sortable header. Update `query` in your handler.
|
|
10
|
+
4. Sorted rows — sort the `rows` array yourself from `query.sort` before passing to the component.
|
|
11
|
+
|
|
12
|
+
## Required pattern
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { useMemo, useReducer } from 'react';
|
|
16
|
+
import { DataTable } from '@codecademy/gamut';
|
|
17
|
+
import type { Query, QueryChangeEvent } from '@codecademy/gamut';
|
|
18
|
+
|
|
19
|
+
function queryReducer<Row>(
|
|
20
|
+
state: Query<Row>,
|
|
21
|
+
event: QueryChangeEvent<Row>
|
|
22
|
+
): Query<Row> {
|
|
23
|
+
switch (event.type) {
|
|
24
|
+
case 'sort': {
|
|
25
|
+
const { dimension, value } = event.payload;
|
|
26
|
+
if (value === 'none') {
|
|
27
|
+
const { [dimension]: _, ...rest } = state.sort ?? {};
|
|
28
|
+
return { ...state, sort: rest };
|
|
29
|
+
}
|
|
30
|
+
return { ...state, sort: { [dimension]: value } };
|
|
31
|
+
}
|
|
32
|
+
case 'filter': {
|
|
33
|
+
const { dimension, value } = event.payload;
|
|
34
|
+
return { ...state, filter: { ...state.filter, [dimension]: value } };
|
|
35
|
+
}
|
|
36
|
+
case 'reset':
|
|
37
|
+
return {};
|
|
38
|
+
default:
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const [query, onQueryChange] = useReducer(queryReducer, {});
|
|
44
|
+
|
|
45
|
+
const sortedRows = useMemo(() => {
|
|
46
|
+
const sorted = [...rows];
|
|
47
|
+
const sortEntries = Object.entries(query.sort ?? {});
|
|
48
|
+
if (sortEntries.length > 0) {
|
|
49
|
+
const [key, direction] = sortEntries[0];
|
|
50
|
+
sorted.sort((a, b) => {
|
|
51
|
+
const aVal = String(a[key]).toLowerCase();
|
|
52
|
+
const bVal = String(b[key]).toLowerCase();
|
|
53
|
+
if (aVal < bVal) return direction === 'asc' ? -1 : 1;
|
|
54
|
+
if (aVal > bVal) return direction === 'asc' ? 1 : -1;
|
|
55
|
+
return 0;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return sorted;
|
|
59
|
+
}, [query.sort, rows]);
|
|
60
|
+
|
|
61
|
+
<DataTable
|
|
62
|
+
id="my-table"
|
|
63
|
+
idKey="id"
|
|
64
|
+
rows={sortedRows}
|
|
65
|
+
columns={columns}
|
|
66
|
+
query={query}
|
|
67
|
+
onQueryChange={onQueryChange}
|
|
68
|
+
/>;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Rules
|
|
72
|
+
|
|
73
|
+
1. Never use `sortable: true` without `query` and `onQueryChange`.
|
|
74
|
+
2. Always sort rows client-side (or server-side before passing) from `query.sort`.
|
|
75
|
+
3. Handle `'sort'`, `'filter'`, and `'reset'` in the reducer even if you only use sorting.
|
|
76
|
+
4. One active sort column in standard usage — replace the previous sort entry when the user picks a new column.
|
|
77
|
+
5. Import `Query` and `QueryChangeEvent` from `@codecademy/gamut` for type safety.
|
|
78
|
+
|
|
79
|
+
Storybook: [Organisms / DataTable](https://gamut.codecademy.com/?path=/docs-organisms-datatable--docs)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
Gamut provides two form organisms — `GridForm` and `ConnectedForm` — that handle layout, validation, submission state, and field registration. Always use one of these organisms for functional forms. Do not compose forms from individual form atoms when the interface needs submit, validation, reset, or dirty tracking.
|
|
4
|
+
|
|
5
|
+
Related: [`skills/gamut-forms/SKILL.md`](../../skills/gamut-forms/SKILL.md) (accessibility wiring) · [`skills/gamut-accessibility/SKILL.md`](../../skills/gamut-accessibility/SKILL.md) (universal ARIA)
|
|
6
|
+
|
|
7
|
+
## When to use each
|
|
8
|
+
|
|
9
|
+
| Component | Use when |
|
|
10
|
+
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `GridForm` | Declarative, config-driven form. Pass `fields` (and optional `sections`) plus `submit` — layout, labels, validation, and submit button are automatic. Best for settings pages, profile forms, and CRUD dialogs. |
|
|
12
|
+
| `ConnectedForm` | Full layout control with managed form state. Provides `react-hook-form` context; compose with `ConnectedFormGroup` and connected inputs. Best for tabs, custom columns, or content interleaved with fields. |
|
|
13
|
+
|
|
14
|
+
## Rules
|
|
15
|
+
|
|
16
|
+
1. Never use bare form atoms for functional forms. `Input`, `Select`, `Checkbox`, `Radio`, `TextArea`, `FormGroup`, and `FormGroupLabel` are building blocks _inside_ organisms. Use them standalone only when there is no submit step (e.g. live search filter, immediate callback toggle).
|
|
17
|
+
2. Prefer `GridForm` over `ConnectedForm` when the layout fits a grid.
|
|
18
|
+
3. Always provide `defaultValues`. Omitting defaults causes uncontrolled-to-controlled warnings and broken resets.
|
|
19
|
+
4. Use `validation="onChange"` when the submit button should stay disabled until required fields are valid. Default `"onSubmit"` validates only on submit.
|
|
20
|
+
5. `GridForm` sections — use `GridFormSectionProps` (`title`, `as`, `fields`) to group fields under headings in one form, not multiple separate forms.
|
|
21
|
+
6. Import connected inputs from `@codecademy/gamut`. `ConnectedInput`, `ConnectedSelect`, `ConnectedCheckbox`, `ConnectedRadioGroup`, `ConnectedTextArea`, `ConnectedFormGroup`, `SubmitButton`.
|
|
22
|
+
7. `hideLabel: true` on checkbox/radio fields with no `label`. Without it, `FormGroupLabel` renders a stray "(optional)" or "\*" row above the control.
|
|
23
|
+
8. `hideLabel: true` on toggle (`custom` type) fields. `Toggle` renders its own inline label; a `FormGroupLabel` above is redundant.
|
|
24
|
+
|
|
25
|
+
## GridForm example
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { GridForm } from '@codecademy/gamut';
|
|
29
|
+
|
|
30
|
+
<GridForm
|
|
31
|
+
fields={[
|
|
32
|
+
{
|
|
33
|
+
title: 'General',
|
|
34
|
+
as: 'h2',
|
|
35
|
+
variant: 'title-sm',
|
|
36
|
+
fields: [
|
|
37
|
+
{
|
|
38
|
+
name: 'orgName',
|
|
39
|
+
label: 'Organization Name',
|
|
40
|
+
type: 'text',
|
|
41
|
+
size: 6,
|
|
42
|
+
defaultValue: 'Acme Corp',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'emailNotifs',
|
|
46
|
+
description: 'Receive email notifications',
|
|
47
|
+
type: 'checkbox',
|
|
48
|
+
size: 12,
|
|
49
|
+
defaultValue: true,
|
|
50
|
+
hideLabel: true,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
]}
|
|
55
|
+
submit={{ contents: 'Save Settings', size: 12 }}
|
|
56
|
+
onSubmit={(values) => console.log(values)}
|
|
57
|
+
validation="onChange"
|
|
58
|
+
/>;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## ConnectedForm example (`useConnectedForm`)
|
|
62
|
+
|
|
63
|
+
Prefer `useConnectedForm` for custom layouts — it types `ConnectedForm` / `ConnectedFormGroup` from `defaultValues` and spreads `connectedFormProps` (defaults + `validationRules`).
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import {
|
|
67
|
+
ConnectedForm,
|
|
68
|
+
ConnectedFormGroup,
|
|
69
|
+
ConnectedInput,
|
|
70
|
+
SubmitButton,
|
|
71
|
+
useConnectedForm,
|
|
72
|
+
} from '@codecademy/gamut';
|
|
73
|
+
|
|
74
|
+
export const ProfileForm = () => {
|
|
75
|
+
const { ConnectedFormGroup, ConnectedForm, connectedFormProps } =
|
|
76
|
+
useConnectedForm({
|
|
77
|
+
defaultValues: { displayName: '' },
|
|
78
|
+
validationRules: {
|
|
79
|
+
displayName: { required: 'Display name is required' },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<ConnectedForm
|
|
85
|
+
onSubmit={(values) => console.log(values)}
|
|
86
|
+
validation="onChange"
|
|
87
|
+
{...connectedFormProps}
|
|
88
|
+
>
|
|
89
|
+
<ConnectedFormGroup
|
|
90
|
+
name="displayName"
|
|
91
|
+
label="Display Name"
|
|
92
|
+
field={{ component: ConnectedInput }}
|
|
93
|
+
/>
|
|
94
|
+
<SubmitButton>Save</SubmitButton>
|
|
95
|
+
</ConnectedForm>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Quick reference
|
|
101
|
+
|
|
102
|
+
| Layer | Components | When to use directly |
|
|
103
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- |
|
|
104
|
+
| Organisms | `GridForm`, `ConnectedForm` | Always for functional forms |
|
|
105
|
+
| Connected inputs | `ConnectedInput`, `ConnectedSelect`, `ConnectedCheckbox`, `ConnectedRadioGroup`, `ConnectedTextArea`, `ConnectedFormGroup`, `SubmitButton` | Only inside `ConnectedForm` |
|
|
106
|
+
| Atoms | `Input`, `Select`, `Checkbox`, `Radio`, `TextArea`, `FormGroup`, `FormGroupLabel`, `FormError` | Standalone display / live filters only |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Loading states
|
|
2
|
+
|
|
3
|
+
Use `Shimmer` or `Spinner` for loading states. Do not use `FeatureShimmer` as a loading indicator.
|
|
4
|
+
|
|
5
|
+
Storybook: [Atoms / Shimmer](https://gamut.codecademy.com/?path=/docs-atoms-shimmer--docs) · [Spinner](https://gamut.codecademy.com/?path=/docs-atoms-spinner--docs)
|
|
6
|
+
|
|
7
|
+
## Components
|
|
8
|
+
|
|
9
|
+
| Component | Use for |
|
|
10
|
+
| ---------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `Shimmer` | Skeleton placeholders for content being loaded |
|
|
12
|
+
| `Spinner` | Indeterminate spinner for active loading |
|
|
13
|
+
| `FeatureShimmer` | Not a loader. Draws attention to secondary new features on a page. Do not use as a general-purpose loading indicator. |
|
|
14
|
+
|
|
15
|
+
## Rule
|
|
16
|
+
|
|
17
|
+
`Shimmer` and `Spinner` are the only true loading indicators. `FeatureShimmer` is for new-feature surfacing only.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Menu
|
|
2
|
+
|
|
3
|
+
`Menu` and `MenuItem` from `@codecademy/gamut` are stateless — the consumer manages `active` and `disabled` state.
|
|
4
|
+
|
|
5
|
+
Related: [`skills/gamut-accessibility/SKILL.md`](../../skills/gamut-accessibility/SKILL.md)
|
|
6
|
+
|
|
7
|
+
Storybook: [Molecules / Menu](https://gamut.codecademy.com/?path=/docs-molecules-menu--docs)
|
|
8
|
+
|
|
9
|
+
## Variants — always set explicitly
|
|
10
|
+
|
|
11
|
+
Always pass the `variant` prop. Relying on the default produces the wrong variant for persistent navigation.
|
|
12
|
+
|
|
13
|
+
| Variant | Use for | Rendering |
|
|
14
|
+
| --------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
|
15
|
+
| `fixed` | Persistent inline navigation — sidebars, primary nav, footer nav, any menu that does not open/close on interaction. | Must use `as="nav"` for accessible navigation. |
|
|
16
|
+
| `popover` | Temporary surfaces — overflow menus, action menus, kebab menus, dismiss on outside click. | Default popover surface. |
|
|
17
|
+
|
|
18
|
+
### Fixed (sidebar / persistent navigation)
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<Menu variant="fixed" as="nav">
|
|
22
|
+
<MenuItem
|
|
23
|
+
active={activeId === 'overview'}
|
|
24
|
+
onClick={() => setActive('overview')}
|
|
25
|
+
>
|
|
26
|
+
Overview
|
|
27
|
+
</MenuItem>
|
|
28
|
+
<MenuItem
|
|
29
|
+
active={activeId === 'settings'}
|
|
30
|
+
onClick={() => setActive('settings')}
|
|
31
|
+
>
|
|
32
|
+
Settings
|
|
33
|
+
</MenuItem>
|
|
34
|
+
</Menu>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Popover (overflow / action menu)
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<Menu variant="popover">
|
|
41
|
+
<MenuItem onClick={handleEdit}>Edit</MenuItem>
|
|
42
|
+
<MenuItem disabled label="Owner permissions required" onClick={handleDelete}>
|
|
43
|
+
Delete
|
|
44
|
+
</MenuItem>
|
|
45
|
+
</Menu>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## `MenuItem` state
|
|
49
|
+
|
|
50
|
+
- `active: boolean` — currently selected item; drive from your state.
|
|
51
|
+
- `disabled: boolean` — unavailable for interaction.
|
|
52
|
+
- `label: string` — when `disabled`, renders a `ToolTip` explaining why. Provide for any disabled item users might question.
|
|
53
|
+
|
|
54
|
+
## Do not stretch `Menu` in flex layouts
|
|
55
|
+
|
|
56
|
+
`Menu` is a flex column sized to its content. If an ancestor is a flex child that stretches (`flex={1}`, `height: 100%`, `alignSelf: stretch`), the menu expands and items spread apart.
|
|
57
|
+
|
|
58
|
+
Rule: Wrap `Menu` in a block-level container with intrinsic height (`as="nav"` on a fixed menu, `Box`, or `div`). Do not use `FlexBox` as the immediate wrapper, and do not apply stretch on ancestors between `Menu` and the sidebar scroll container.
|
|
@@ -2,43 +2,123 @@
|
|
|
2
2
|
|
|
3
3
|
52 components have Figma ↔ code mappings via Figma Code Connect (`packages/code-connect/`). Live code snippets appear in Figma's inspect panel when you select a component.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Core rules
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Always use existing Gamut components from `packages/gamut/src` rather than one-off equivalents. See the quick reference table below.
|
|
8
|
+
- Apply exact sizing, variant, and props from the source. Never rely on defaults when the design or prompt specifies a value (e.g. `size="small"` on `Input`, `sizeVariant="small"` on `Select`).
|
|
9
|
+
- When unsure, reference `Badge`, `Tag`, or button atoms in `packages/gamut/src`.
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Forms vs. standalone inputs
|
|
12
|
+
|
|
13
|
+
Functional forms — submit/save bundling multiple fields, validation, errors, or dirty tracking — must use `GridForm` or `ConnectedForm`. Read [forms.md](forms.md) before building any functional form.
|
|
14
|
+
|
|
15
|
+
Standalone inputs — live search, real-time filters, single controls that update state immediately — use bare atoms (`Input`, `Select`, `Checkbox`, `Radio`, `TextArea`, `FormGroup`). No organism when there is no submit step.
|
|
16
|
+
|
|
17
|
+
The test: submit/save bundled values → organism. Real-time state with no submit → atom.
|
|
18
|
+
|
|
19
|
+
## Component discovery
|
|
20
|
+
|
|
21
|
+
Before custom markup for any UI pattern:
|
|
22
|
+
|
|
23
|
+
1. Enumerate exports — check `@codecademy/gamut` public API or `index.d.ts`.
|
|
24
|
+
2. Prefer Gamut over raw HTML — e.g. `Menu` for nav, `DataTable` for tables, `Tabs` for tabs. Do not rebuild from `<ul>` / `<motion.div>` when a primitive exists.
|
|
25
|
+
3. Read type definitions for props before custom wrappers.
|
|
26
|
+
4. Never build custom media players — use `Video` ([video.md](video.md)).
|
|
27
|
+
5. When no Gamut component exists, comment: `{/* No Gamut component for [pattern] — custom markup */}`
|
|
28
|
+
|
|
29
|
+
### Quick reference
|
|
30
|
+
|
|
31
|
+
| UI Pattern | Gamut component(s) | Guide |
|
|
32
|
+
| --------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
33
|
+
| Buttons | `FillButton`, `StrokeButton`, `TextButton`, `IconButton`, `CTAButton` | [buttons.md](buttons.md) |
|
|
34
|
+
| Links | `Anchor` | — |
|
|
35
|
+
| Typography | `Text` | [`gamut-typography` skill](../../skills/gamut-typography/SKILL.md) |
|
|
36
|
+
| Markdown | `Markdown` | — |
|
|
37
|
+
| Layout | `Box`, `FlexBox`, `GridBox`, `Layout`, `LayoutGrid` | [spacing.md](../foundations/spacing.md) |
|
|
38
|
+
| Animations | `Rotation`, `ExpandInCollapseOut`, `FadeInSlideOut` | [animations.md](animations.md) |
|
|
39
|
+
| Page containers | `ContentContainer`, `GridContainer` | — |
|
|
40
|
+
| Navigation / menus | `Menu`, `MenuItem`, `MenuSeparator` | [menu.md](menu.md) |
|
|
41
|
+
| Breadcrumbs | `Breadcrumbs` | — |
|
|
42
|
+
| Pagination | `Pagination` | — |
|
|
43
|
+
| Tabs | `Tabs` | [`gamut-accessibility` skill](../../skills/gamut-accessibility/SKILL.md) |
|
|
44
|
+
| Accordions | `Disclosure` | — |
|
|
45
|
+
| Data tables | `DataTable`, `DataList` | [data-table.md](data-table.md) |
|
|
46
|
+
| Lists | `List` | — |
|
|
47
|
+
| Cards | `Card` | [card.md](card.md) |
|
|
48
|
+
| Charts | `BarChart` | — |
|
|
49
|
+
| Modals | `Dialog` (binary confirm/cancel); `Modal` (multi-view, complex) | [`gamut-accessibility` skill](../../skills/gamut-accessibility/SKILL.md) |
|
|
50
|
+
| Drawers / flyouts | `Drawer`, `Flyout` | [`gamut-accessibility` skill](../../skills/gamut-accessibility/SKILL.md) |
|
|
51
|
+
| Overlays / focus trap | `Overlay`, `FocusTrap` | [`gamut-accessibility` skill](../../skills/gamut-accessibility/SKILL.md) |
|
|
52
|
+
| Popovers | `Popover`, `PopoverContainer` | — |
|
|
53
|
+
| Tooltips | `ToolTip`, `InfoTip`, `PreviewTip` | [tooltips.md](tooltips.md) |
|
|
54
|
+
| Onboarding | `Coachmark` | [coachmark.md](coachmark.md) |
|
|
55
|
+
| Alerts / toasts | `Alert`, `Toast`, `Toaster` | — |
|
|
56
|
+
| Badges / tags | `Badge`, `Tag` | — |
|
|
57
|
+
| Progress | `ProgressBar`, `RadialProgress` | [radial-progress.md](radial-progress.md) |
|
|
58
|
+
| Loading | `Shimmer`, `Spinner` | [loading-states.md](loading-states.md) |
|
|
59
|
+
| Video | `Video` | [video.md](video.md) |
|
|
60
|
+
| Forms | `GridForm`, `ConnectedForm` | [forms.md](forms.md) |
|
|
61
|
+
| Standalone inputs | `Input`, `Select`, `Checkbox`, `Radio`, `TextArea`, `FormGroup` | Forms breadcrumb above |
|
|
62
|
+
| Rich select | `SelectDropdown` | [select.md](select.md) |
|
|
63
|
+
| Date | `DatePicker` | [`gamut-forms` skill](../../skills/gamut-forms/SKILL.md) |
|
|
64
|
+
| Toggle (standalone) | `Toggle` | — |
|
|
65
|
+
| Screen reader text | `HiddenText` | — |
|
|
66
|
+
| Skip link | `SkipToContent` | — |
|
|
67
|
+
| Dark / light regions | `ColorMode`, `Background` | [modes.md](../foundations/modes.md) |
|
|
68
|
+
|
|
69
|
+
## Validate variant props
|
|
70
|
+
|
|
71
|
+
`Card`, `Badge`, `Tag`, and `Alert` accept specific `variant` values. Invalid strings (e.g. `"navy-on-white"`) crash `parseToHsl()` at runtime. Inspect prop types in `@codecademy/gamut` before using variant/color props. See [card.md](card.md) for `Card` variants.
|
|
72
|
+
|
|
73
|
+
## Catalog by layer
|
|
74
|
+
|
|
75
|
+
### Atoms
|
|
76
|
+
|
|
77
|
+
Badge, FillButton, StrokeButton, CTAButton, TextButton, IconButton, Card, Checkbox, CodeBlock, Drawer, FlexBox, FormGroup, GridBox, HiddenText, Icon, Input, Label, Loader, Radio, Select, Spinner, Tag, TextArea, Toggle, Tooltip
|
|
78
|
+
|
|
79
|
+
### Molecules
|
|
10
80
|
|
|
11
81
|
Alert, Anchor, Breadcrumbs, Coachmark, Disclosure, GridForm, Markdown, Menu, Modal, Pagination, Popover, ProgressBar, Table, Tabs, Toast, Toaster, Video
|
|
12
82
|
|
|
13
|
-
|
|
83
|
+
### Organisms
|
|
14
84
|
|
|
15
85
|
ContentContainer, GridContainer, Layout, LayoutGrid
|
|
16
86
|
|
|
17
87
|
## Key patterns
|
|
18
88
|
|
|
19
89
|
### Buttons
|
|
20
|
-
|
|
90
|
+
|
|
91
|
+
[buttons.md](buttons.md) — `FillButton` primary, `StrokeButton` secondary.
|
|
92
|
+
|
|
93
|
+
### Forms and accessibility
|
|
94
|
+
|
|
95
|
+
[forms.md](forms.md) · [`gamut-forms` skill](../../skills/gamut-forms/SKILL.md) · [`gamut-accessibility` skill](../../skills/gamut-accessibility/SKILL.md)
|
|
21
96
|
|
|
22
97
|
### Cards
|
|
23
|
-
|
|
24
|
-
|
|
98
|
+
|
|
99
|
+
See [card.md](card.md). Variants include `default`, `white`, `yellow`, `hyper`, `navy`. Confirm against `DESIGN.md` for theme-specific surfaces.
|
|
100
|
+
`FormGroup`, `GridForm`, `ConnectedForm`, tips, dialogs, composite widgets: [`skills/gamut-forms/SKILL.md`](../../skills/gamut-forms/SKILL.md) (forms) · [`skills/gamut-accessibility/SKILL.md`](../../skills/gamut-accessibility/SKILL.md) (overlays, composites, checklists) · Storybook [Meta / Best practices](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page).
|
|
101
|
+
|
|
102
|
+
- Background variants: `default` (ColorMode-responsive), `white`, `yellow`, `beige`, `navy`, `hyper`
|
|
103
|
+
- Shadow variants: `none` (default), `outline`, `patternLeft`, `patternRight`
|
|
25
104
|
- Add `isInteractive` when wrapping in `<Anchor>` — enables hover shadow + `borderRadius: md`
|
|
26
105
|
- Default `borderRadius` is `none`; override with `borderRadius` prop
|
|
27
106
|
|
|
28
|
-
### Color-aware
|
|
29
|
-
|
|
30
|
-
|
|
107
|
+
### Color-aware regions
|
|
108
|
+
|
|
109
|
+
[foundations/modes.md](../foundations/modes.md) — `<ColorMode mode="light|dark|system">`, `<Background bg="…">`.
|
|
31
110
|
|
|
32
111
|
### Alerts
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
112
|
+
|
|
113
|
+
| Variant | Tokens |
|
|
114
|
+
| ------- | ----------------------------------------- |
|
|
115
|
+
| Error | `feedback-error` + `background-error` |
|
|
36
116
|
| Success | `feedback-success` + `background-success` |
|
|
37
117
|
| Warning | `feedback-warning` + `background-warning` |
|
|
38
118
|
|
|
39
119
|
## Global tokens
|
|
40
120
|
|
|
41
|
-
| Token
|
|
42
|
-
|
|
43
|
-
| `headerHeight` | 64px (base), 80px (md+) | Global page header
|
|
44
|
-
| `headerZ`
|
|
121
|
+
| Token | Value | Use |
|
|
122
|
+
| -------------- | ----------------------- | ------------------ |
|
|
123
|
+
| `headerHeight` | 64px (base), 80px (md+) | Global page header |
|
|
124
|
+
| `headerZ` | 15 | Header z-index |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# RadialProgress
|
|
2
|
+
|
|
3
|
+
Storybook: [Molecules / RadialProgress](https://gamut.codecademy.com/?path=/docs-molecules-radialprogress--docs)
|
|
4
|
+
|
|
5
|
+
## Labels as `children` by default
|
|
6
|
+
|
|
7
|
+
`RadialProgress` renders children in a centered overlay inside the ring. Place the label (e.g. percentage text) as a child, not as a sibling below the component, unless the design explicitly places it outside the ring.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
<RadialProgress percent={75}>
|
|
11
|
+
<Text>75%</Text>
|
|
12
|
+
</RadialProgress>
|
|
13
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Select vs. SelectDropdown
|
|
2
|
+
|
|
3
|
+
`Select` is a native select suitable for most dropdown needs. Prefer `Select` over `SelectDropdown` for simple lists.
|
|
4
|
+
|
|
5
|
+
Storybook: [Atoms / Select](https://gamut.codecademy.com/?path=/docs-atoms-select--docs) · [Organisms / SelectDropdown](https://gamut.codecademy.com/?path=/docs-organisms-selectdropdown--docs)
|
|
6
|
+
|
|
7
|
+
## When to use `SelectDropdown`
|
|
8
|
+
|
|
9
|
+
Only when you need:
|
|
10
|
+
|
|
11
|
+
- Searchable/filterable options (`isSearchable`)
|
|
12
|
+
- Multi-select (`multiple`)
|
|
13
|
+
- Option subtitles, right labels, icons, abbreviations
|
|
14
|
+
- Grouped options with dividers and group labels
|
|
15
|
+
- Custom option styling
|
|
16
|
+
|
|
17
|
+
## When to use `Select`
|
|
18
|
+
|
|
19
|
+
Plain option lists with no enrichment — simpler, more performant, and more accessible.
|
|
20
|
+
|
|
21
|
+
## Sizing
|
|
22
|
+
|
|
23
|
+
Apply exact sizing from the design (e.g. `sizeVariant="small"` on `Select`) — do not rely on defaults when the source specifies a value.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ToolTip and InfoTip
|
|
2
|
+
|
|
3
|
+
Related: [`skills/gamut-accessibility/SKILL.md`](../../skills/gamut-accessibility/SKILL.md) (`aria-describedby`, `InfoTip` labeling)
|
|
4
|
+
|
|
5
|
+
Storybook: [Molecules / ToolTip](https://gamut.codecademy.com/?path=/docs-molecules-tips-tooltip--docs) · [InfoTip](https://gamut.codecademy.com/?path=/docs-molecules-tips-infotip--docs)
|
|
6
|
+
|
|
7
|
+
## Use `placement: 'floating'` inside overflow containers
|
|
8
|
+
|
|
9
|
+
`ToolTip` defaults to `placement: 'inline'`, which is clipped by ancestors with `overflow: hidden` (e.g. `DataTable` rows).
|
|
10
|
+
|
|
11
|
+
When an `IconButton` or `ToolTip` is inside a table, card, or clipping container:
|
|
12
|
+
|
|
13
|
+
- `tipProps={{ placement: 'floating' }}` on `IconButton`
|
|
14
|
+
- `placement="floating"` on `ToolTip` directly
|
|
15
|
+
|
|
16
|
+
This renders the tooltip in a floating layer above surrounding content.
|
|
17
|
+
|
|
18
|
+
## Use `alignment="bottom-*"` near the top of the viewport
|
|
19
|
+
|
|
20
|
+
Tips default to opening above the trigger. Near the page top (e.g. in a heading), the tip can clip.
|
|
21
|
+
|
|
22
|
+
Pass `alignment="bottom-right"` or `"bottom-left"` so the tip opens downward.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Video
|
|
2
|
+
|
|
3
|
+
Never build custom media players. Gamut exports `Video` (built on `@vidstack/react`) with controls, poster support, text tracks, and accessibility.
|
|
4
|
+
|
|
5
|
+
Storybook: [Molecules / Video](https://gamut.codecademy.com/?path=/docs-molecules-video--docs)
|
|
6
|
+
|
|
7
|
+
## Rule
|
|
8
|
+
|
|
9
|
+
Before constructing `<video>`, image + play-button overlays, or third-party embed wrappers, use `Video`.
|
|
10
|
+
|
|
11
|
+
## Common props
|
|
12
|
+
|
|
13
|
+
| Prop | Purpose |
|
|
14
|
+
| ------------------ | ------------------- |
|
|
15
|
+
| `videoUrl` | Video source |
|
|
16
|
+
| `placeholderImage` | Poster thumbnail |
|
|
17
|
+
| `videoTitle` | Accessibility title |
|
|
18
|
+
| `controls` | Standard player UI |
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { Video } from '@codecademy/gamut';
|
|
22
|
+
|
|
23
|
+
<Video
|
|
24
|
+
videoUrl="https://example.com/video.mp4"
|
|
25
|
+
placeholderImage="https://example.com/poster.jpg"
|
|
26
|
+
videoTitle="Course introduction"
|
|
27
|
+
controls
|
|
28
|
+
/>;
|
|
29
|
+
```
|