@codecademy/gamut 68.6.1-alpha.e6c390.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 +239 -191
- package/agent-tools/DESIGN.LXStudio.md +236 -184
- package/agent-tools/DESIGN.Percipio.md +232 -182
- package/agent-tools/DESIGN.md +1 -1
- 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 +39 -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 +69 -23
- 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 +79 -29
- 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 +70 -26
- package/agent-tools/skills/gamut-testing/SKILL.md +106 -62
- package/agent-tools/skills/gamut-theming/SKILL.md +34 -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
|
@@ -1,48 +1,46 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gamut-testing
|
|
3
|
-
description:
|
|
3
|
+
description: Unit tests for Gamut UI — setupRtl, MockGamutProvider, no jest.mock of @codecademy/gamut. When invoked, see commands/gamut-review.md Check 5 for test guardrails.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Gamut Testing
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
---
|
|
8
|
+
## Read first
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
When this skill applies, skim [`commands/gamut-review.md`](../../commands/gamut-review.md) Check 5 (test setup) for blocking patterns before changing tests.
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Source: `@codecademy/gamut-tests` — [`index.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-tests/src/index.tsx)
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
---
|
|
17
15
|
|
|
18
16
|
---
|
|
19
17
|
|
|
20
|
-
## What `MockGamutProvider` does
|
|
18
|
+
## What `MockGamutProvider` does (under `setupRtl`)
|
|
21
19
|
|
|
22
|
-
`MockGamutProvider`
|
|
20
|
+
`MockGamutProvider` forwards to `GamutProvider` with:
|
|
23
21
|
|
|
24
|
-
- `useCache={false}` —
|
|
25
|
-
- `useGlobals={false}` —
|
|
26
|
-
- `theme={
|
|
22
|
+
- `useCache={false}` — stable Emotion output across tests
|
|
23
|
+
- `useGlobals={false}` — no global Reboot/Typography bleed between files
|
|
24
|
+
- `theme={theme}` — full token theme for styled components
|
|
25
|
+
- Optional `useLogicalProperties` — forwarded for logical vs physical CSS in variance
|
|
27
26
|
|
|
28
|
-
You
|
|
27
|
+
You normally do not import `MockGamutProvider` for plain component tests; `setupRtl` already wraps the component under test once. Import it inside a harness when the SUT needs a non-default provider flag or extra wrappers (see below).
|
|
29
28
|
|
|
30
29
|
---
|
|
31
30
|
|
|
32
31
|
## Decision guide
|
|
33
32
|
|
|
34
|
-
| Scenario
|
|
35
|
-
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
33
|
+
| Scenario | Prefer |
|
|
34
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
35
|
+
| Default unit test for a Gamut (or app) component | `setupRtl(Component, defaultProps)` once per file / describe |
|
|
36
|
+
| Vary `useLogicalProperties` across cases | Harness that accepts `useLogicalProperties` and wraps `MockGamutProvider`, then `setupRtl(Harness, defaults)`; pass overrides per `it` / `describe.each` |
|
|
37
|
+
| Need `ColorMode` (or other context) around the SUT | Harness with `<ColorMode>` inside the tree, then `setupRtl(Harness)` — no need for raw `render` unless you are testing the provider itself |
|
|
38
|
+
| `dir` / RTL behavior (e.g. mirrored layout, `useElementDir`) | Keep using `setupRtl` for the component; set `document.documentElement.setAttribute('dir', 'rtl' \| 'ltr')` (and scroll/viewport stubs if needed) in `beforeEach` / `afterEach`; reset `dir` after tests so suites do not leak |
|
|
39
|
+
| Storybook-only mock, chromatic-style wrapper, or non-RTL harness | `MockGamutProvider` (± `ColorMode`) in the exported wrapper component |
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
## `setupRtl` —
|
|
44
|
-
|
|
45
|
-
`setupRtl` from `@codecademy/gamut-tests` wraps `setupRtl` from `component-test-setup` with `MockGamutProvider` automatically. You do not need to add `MockGamutProvider` yourself.
|
|
43
|
+
## `setupRtl` — primary pattern
|
|
46
44
|
|
|
47
45
|
```tsx
|
|
48
46
|
import { setupRtl } from '@codecademy/gamut-tests';
|
|
@@ -66,13 +64,21 @@ it('accepts prop overrides', () => {
|
|
|
66
64
|
```
|
|
67
65
|
|
|
68
66
|
`renderView` returns `{ view, props, update }`:
|
|
69
|
-
|
|
70
|
-
- `
|
|
71
|
-
- `
|
|
67
|
+
|
|
68
|
+
- `view` — RTL `RenderResult` (`getByRole`, `getByLabelText`, `getByText`, …)
|
|
69
|
+
- `props` — resolved props (handy for `jest.fn()` assertions)
|
|
70
|
+
- `update` — re-render with new props without remounting
|
|
71
|
+
|
|
72
|
+
### Query and interaction habits (RTL)
|
|
73
|
+
|
|
74
|
+
- Prefer `getByRole`, `getByLabelText`, and accessible names over CSS selectors or snapshotting class strings unless you are explicitly testing styling.
|
|
75
|
+
- Prefer `@testing-library/user-event` over `fireEvent` when simulating real input (import `userEvent` from `@testing-library/user-event` in current major versions).
|
|
72
76
|
|
|
73
77
|
### Accessing mock functions via `props`
|
|
74
78
|
|
|
75
79
|
```tsx
|
|
80
|
+
import userEvent from '@testing-library/user-event';
|
|
81
|
+
|
|
76
82
|
it('calls onClick when clicked', async () => {
|
|
77
83
|
const { view, props } = renderView();
|
|
78
84
|
await userEvent.click(view.getByRole('button'));
|
|
@@ -82,50 +88,84 @@ it('calls onClick when clicked', async () => {
|
|
|
82
88
|
|
|
83
89
|
---
|
|
84
90
|
|
|
85
|
-
## `
|
|
91
|
+
## Harness + `setupRtl` when the wrapper is not default
|
|
92
|
+
|
|
93
|
+
`setupRtl` always wraps with `MockGamutProvider` with default props. To vary `useLogicalProperties`, add `ColorMode`, or compose other providers, define a small harness and pass `setupRtl` that harness — still one `renderView` factory, still `props` / `update` ergonomics.
|
|
86
94
|
|
|
87
|
-
|
|
95
|
+
### Varying `useLogicalProperties` (logical vs physical CSS)
|
|
88
96
|
|
|
89
97
|
```tsx
|
|
90
|
-
import { MockGamutProvider } from '@codecademy/gamut-tests';
|
|
91
|
-
import { render } from '@testing-library/react';
|
|
92
|
-
|
|
93
|
-
it('uses logical properties when enabled', () => {
|
|
94
|
-
const { container } = render(
|
|
95
|
-
<MockGamutProvider useLogicalProperties>
|
|
96
|
-
<MyComponent width="200px" />
|
|
97
|
-
</MockGamutProvider>
|
|
98
|
-
);
|
|
99
|
-
// assert on inlineSize rather than width
|
|
100
|
-
});
|
|
101
|
-
```
|
|
98
|
+
import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests';
|
|
102
99
|
|
|
103
|
-
|
|
100
|
+
import { MyComponent } from '../MyComponent';
|
|
101
|
+
|
|
102
|
+
type HarnessProps = React.ComponentProps<typeof MyComponent> & {
|
|
103
|
+
useLogicalProperties?: boolean;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const MyHarness = ({ useLogicalProperties, ...rest }: HarnessProps) => (
|
|
107
|
+
<MockGamutProvider useLogicalProperties={useLogicalProperties}>
|
|
108
|
+
<MyComponent {...rest} />
|
|
109
|
+
</MockGamutProvider>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const renderView = setupRtl(MyHarness, { width: '200px' });
|
|
104
113
|
|
|
105
|
-
```tsx
|
|
106
114
|
describe.each([
|
|
107
|
-
{ useLogicalProperties: true, widthProp: 'inlineSize' },
|
|
108
|
-
{ useLogicalProperties: false, widthProp: 'width' },
|
|
115
|
+
{ useLogicalProperties: true as const, widthProp: 'inlineSize' as const },
|
|
116
|
+
{ useLogicalProperties: false as const, widthProp: 'width' as const },
|
|
109
117
|
])(
|
|
110
118
|
'useLogicalProperties=$useLogicalProperties',
|
|
111
119
|
({ useLogicalProperties, widthProp }) => {
|
|
112
120
|
it(`uses ${widthProp}`, () => {
|
|
113
|
-
const {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
118
|
-
expect(container.firstChild).toHaveStyle({ [widthProp]: '200px' });
|
|
121
|
+
const { view } = renderView({ useLogicalProperties });
|
|
122
|
+
expect(view.getByTestId('my-component-root')).toHaveStyle({
|
|
123
|
+
[widthProp]: '200px',
|
|
124
|
+
});
|
|
119
125
|
});
|
|
120
126
|
}
|
|
121
127
|
);
|
|
122
128
|
```
|
|
123
129
|
|
|
130
|
+
The outer `setupRtl` wrapper adds a default `MockGamutProvider`; the harness’s inner `MockGamutProvider` sets `useLogicalProperties` for the subtree under test (nested `GamutProvider` / theme is the nearest one Emotion and variance see).
|
|
131
|
+
|
|
132
|
+
### `ColorMode` without abandoning `setupRtl`
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { ColorMode } from '@codecademy/gamut-styles';
|
|
136
|
+
import { setupRtl } from '@codecademy/gamut-tests';
|
|
137
|
+
|
|
138
|
+
const DarkHarness = (props: React.ComponentProps<typeof MyComponent>) => (
|
|
139
|
+
<ColorMode mode="dark">
|
|
140
|
+
<MyComponent {...props} />
|
|
141
|
+
</ColorMode>
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const renderDark = setupRtl(DarkHarness, { title: 'Hi' });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Use `MockGamutProvider` only inside the harness if you also need a non-default Gamut flag and `ColorMode` in the same tree; otherwise `setupRtl(DarkHarness)` is enough.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Raw `render` + `MockGamutProvider` — rare
|
|
152
|
+
|
|
153
|
+
Reserve `render` from `@testing-library/react` + manual `MockGamutProvider` for cases where a harness would be more obscure than a single inline tree (e.g. highly dynamic one-off trees). If the same wrapper appears more than once, switch to a harness + `setupRtl`.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## RTL / `dir` and document-level behavior
|
|
158
|
+
|
|
159
|
+
Some components (e.g. overlays that call `useElementDir`) resolve direction from `document.documentElement` when there is no real target node. For those tests:
|
|
160
|
+
|
|
161
|
+
- Set `document.documentElement.setAttribute('dir', 'rtl')` (or `'ltr'`) around the scenario, `unmount` between LTR and RTL assertions when re-rendering, and restore `dir` in `afterEach` so other tests start clean.
|
|
162
|
+
- Combine with the harness pattern above when `useLogicalProperties` affects which longhand wins (`left` vs `insetInlineStart`, etc.).
|
|
163
|
+
|
|
124
164
|
---
|
|
125
165
|
|
|
126
166
|
## Emotion style assertions
|
|
127
167
|
|
|
128
|
-
Install `@emotion/jest` matchers
|
|
168
|
+
Install `@emotion/jest` matchers if you absolutely need to enable CSS-in-JS assertions:
|
|
129
169
|
|
|
130
170
|
```tsx
|
|
131
171
|
import { matchers } from '@emotion/jest';
|
|
@@ -140,7 +180,7 @@ expect(element).toHaveStyle({ borderRadius: '2px' });
|
|
|
140
180
|
expect(element).toHaveStyleRule('padding', '1rem');
|
|
141
181
|
```
|
|
142
182
|
|
|
143
|
-
Use `theme` from `@codecademy/gamut-styles`
|
|
183
|
+
Use `theme` from `@codecademy/gamut-styles` instead of hardcoding token strings:
|
|
144
184
|
|
|
145
185
|
```tsx
|
|
146
186
|
import { theme } from '@codecademy/gamut-styles';
|
|
@@ -150,15 +190,17 @@ expect(element).toHaveStyle({ columnGap: theme.spacing[40] });
|
|
|
150
190
|
|
|
151
191
|
---
|
|
152
192
|
|
|
153
|
-
## Visual test wrappers
|
|
193
|
+
## Visual test wrappers and Storybook
|
|
154
194
|
|
|
155
|
-
|
|
195
|
+
Exported mocks and stories may wrap with `MockGamutProvider` and `ColorMode` explicitly (no `setupRtl` in Storybook):
|
|
156
196
|
|
|
157
197
|
```tsx
|
|
158
198
|
import { MockGamutProvider } from '@codecademy/gamut-tests';
|
|
159
199
|
import { ColorMode } from '@codecademy/gamut-styles';
|
|
160
200
|
|
|
161
|
-
export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (
|
|
201
|
+
export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (
|
|
202
|
+
props
|
|
203
|
+
) => (
|
|
162
204
|
<MockGamutProvider>
|
|
163
205
|
<ColorMode mode="light">
|
|
164
206
|
<MyComponent {...props} />
|
|
@@ -171,11 +213,13 @@ export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (pr
|
|
|
171
213
|
|
|
172
214
|
## Common anti-patterns
|
|
173
215
|
|
|
174
|
-
| Anti-pattern
|
|
175
|
-
|
|
176
|
-
| `jest.mock('@codecademy/gamut', () => ({ ... }))`
|
|
177
|
-
| `jest.mock('@codecademy/gamut-styles', ...)`
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
216
|
+
| Anti-pattern | Fix |
|
|
217
|
+
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
218
|
+
| `jest.mock('@codecademy/gamut', () => ({ ... }))` | Remove; use `setupRtl` (or harness + `setupRtl`) |
|
|
219
|
+
| `jest.mock('@codecademy/gamut-styles', ...)` | Remove; `MockGamutProvider` / `setupRtl` supplies theme |
|
|
220
|
+
| `GamutProvider` in test files | Use `MockGamutProvider` only when building a harness or story; default tests go through `setupRtl` |
|
|
221
|
+
| `import { setupRtl } from 'component-test-setup'` in Gamut / apps | Import `setupRtl` from `@codecademy/gamut-tests` so `MockGamutProvider` is applied |
|
|
222
|
+
| Repeated `render(<MockGamutProvider>…` | Harness + `setupRtl`, or a shared `renderView` factory |
|
|
223
|
+
| One `setupRtl` call per `it` | Define `renderView` once outside `describe`, call it inside each `it` |
|
|
224
|
+
| Asserting raw CSS strings for tokens | Use `theme` from `@codecademy/gamut-styles` |
|
|
225
|
+
| Leaking `dir="rtl"` between tests | Reset `document.documentElement` in `afterEach` |
|
|
@@ -1,115 +1,63 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gamut-theming
|
|
3
|
-
description:
|
|
3
|
+
description: Gamut themes, GamutProvider, theme.d.ts, CreatingThemes. When invoked, read guidelines/setup.md and repo DESIGN.md first. Not for css/variant/states (see gamut-style-utilities).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Gamut Theming
|
|
7
7
|
|
|
8
|
+
## Read first
|
|
9
|
+
|
|
10
|
+
When this skill applies, read before writing code:
|
|
11
|
+
|
|
12
|
+
- [`guidelines/setup.md`](../../guidelines/setup.md)
|
|
13
|
+
- Root `DESIGN.md` in the app repo (product tokens and patterns)
|
|
14
|
+
|
|
8
15
|
Source: `@codecademy/gamut-styles`
|
|
9
16
|
|
|
17
|
+
See also: [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) (`css`, `variant`, `states`, `StyleProps`, `useTheme` escape hatch). [`gamut-color-mode`](../gamut-color-mode/SKILL.md) (semantic color, `<ColorMode>`, `<Background>`). [`gamut-system-props`](../gamut-system-props/SKILL.md) (`system.*`, responsive `Box` props).
|
|
18
|
+
|
|
10
19
|
## Overview
|
|
11
20
|
|
|
12
|
-
Gamut uses Emotion's theme system.
|
|
21
|
+
Gamut uses Emotion's theme system. Themes are org-specific token bundles (colors, typography, spacing, etc.). The active theme is set at the app root with `<GamutProvider theme={...}>`; child styled components read tokens through Emotion context.
|
|
22
|
+
|
|
23
|
+
For authoring component styles (`css`, `variant`, `states`, system props, ColorMode), use the skills linked above and the styleguide [Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx).
|
|
24
|
+
|
|
25
|
+
## Infer theme from the repo
|
|
26
|
+
|
|
27
|
+
Do not hardcode a product theme in generic guidance. In an app repo:
|
|
13
28
|
|
|
14
|
-
|
|
29
|
+
1. Read root `DESIGN.md` (installed via `gamut plugin install --theme <name>`) for semantic tokens, fonts, and product patterns.
|
|
30
|
+
2. Confirm `<GamutProvider theme={...}>` matches that product (`coreTheme`, `percipioTheme`, `lxStudioTheme`, etc.).
|
|
31
|
+
3. Use Storybook Foundations / Theme stories for the active product when verifying hex ↔ semantic mappings.
|
|
15
32
|
|
|
16
33
|
## Available themes
|
|
17
34
|
|
|
18
35
|
| Theme | Used for |
|
|
19
36
|
| --------- | ---------------------------------------------------------- |
|
|
20
|
-
| Core | Codecademy default
|
|
37
|
+
| Core | Codecademy default |
|
|
21
38
|
| Admin | Codecademy admin tools |
|
|
22
39
|
| Platform | Codecademy learning environment / shared platform surfaces |
|
|
23
40
|
| LX Studio | Learning Experience Studio |
|
|
24
41
|
| Percipio | Skillsoft Percipio platform |
|
|
25
42
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Accessing tokens in styled components
|
|
29
|
-
|
|
30
|
-
### Via `css()` utility (recommended for static styles)
|
|
31
|
-
|
|
32
|
-
```tsx
|
|
33
|
-
import { css } from '@codecademy/gamut-styles';
|
|
34
|
-
import styled from '@emotion/styled';
|
|
35
|
-
|
|
36
|
-
// Static color token
|
|
37
|
-
const Box = styled.div(css({ bg: 'navy-400', p: 4 }));
|
|
38
|
-
|
|
39
|
-
// Semantic color alias (adapts to color mode and theme)
|
|
40
|
-
const Text = styled.div(css({ color: 'primary', p: 4 }));
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Via `theme` prop (for dynamic styles)
|
|
44
|
-
|
|
45
|
-
Every Emotion styled component receives `theme` as a prop:
|
|
46
|
-
|
|
47
|
-
```tsx
|
|
48
|
-
import styled from '@emotion/styled';
|
|
49
|
-
|
|
50
|
-
const DynamicBox = styled.div`
|
|
51
|
-
color: ${({ theme }) => theme.colors.blue};
|
|
52
|
-
font-size: ${({ theme }) => theme.fontSize[16]};
|
|
53
|
-
`;
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Via imported `theme` object (outside styled components)
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
import { css as emotionCss } from '@emotion/react';
|
|
60
|
-
import { theme } from '@codecademy/gamut-styles';
|
|
61
|
-
|
|
62
|
-
// For use in plain CSS-in-JS outside of styled components
|
|
63
|
-
const myStyles = emotionCss`
|
|
64
|
-
font-size: ${theme.fontSize[14]};
|
|
65
|
-
color: ${theme.colors['navy-400']};
|
|
66
|
-
`;
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Via `useTheme()` hook
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
import { useTheme } from '@emotion/react';
|
|
73
|
-
|
|
74
|
-
const MyComponent = () => {
|
|
75
|
-
const theme = useTheme();
|
|
76
|
-
return <div style={{ color: theme.colors.primary }} />;
|
|
77
|
-
};
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## System props as the primary token API
|
|
81
|
-
|
|
82
|
-
For most styling needs, use **system props** (see the `gamut-system-props` skill) rather than accessing the theme directly. System props are the idiomatic way to use design tokens in Gamut components:
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
import { variance } from '@codecademy/variance';
|
|
86
|
-
import { system } from '@codecademy/gamut-styles';
|
|
87
|
-
|
|
88
|
-
const Card = styled.div(
|
|
89
|
-
variance.compose(system.layout, system.space, system.color)
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// token scale values are validated at the type level
|
|
93
|
-
<Card bg="navy-400" p={16} width="100%" />;
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Theme-aware vs. color-mode-aware
|
|
43
|
+
Product-level import names and `theme.d.ts` patterns live in [setup.md](../../guidelines/setup.md).
|
|
97
44
|
|
|
98
|
-
|
|
99
|
-
| ---------------------------------------------- | ------------------------------------------ |
|
|
100
|
-
| Tokens change per theme (colors, sizes, fonts) | Access via `theme.*` or system props |
|
|
101
|
-
| Colors change per light/dark mode | Use semantic color aliases + `<ColorMode>` |
|
|
102
|
-
| Background contrast | Use `<Background>` from ColorMode |
|
|
45
|
+
## Theme vs color mode vs style API
|
|
103
46
|
|
|
104
|
-
|
|
47
|
+
| Concern | Where to read |
|
|
48
|
+
| ------------------------------------------------------- | -------------------------------------------------- |
|
|
49
|
+
| Which `theme` object to pass to `GamutProvider` | This skill + [setup.md](../../guidelines/setup.md) |
|
|
50
|
+
| Light / dark semantic colors, `ColorMode`, `Background` | `gamut-color-mode` |
|
|
51
|
+
| `css` / `variant` / `states`, `useTheme` for non-CSS JS | `gamut-style-utilities` |
|
|
52
|
+
| Composed `system.*` props on styled primitives / `Box` | `gamut-system-props` |
|
|
105
53
|
|
|
106
54
|
## Creating a new theme
|
|
107
55
|
|
|
108
|
-
See `CreatingThemes.mdx` in the styleguide (`packages/styleguide/src/lib/Foundations/Theme/CreatingThemes.mdx`)
|
|
56
|
+
See `CreatingThemes.mdx` in the styleguide (`packages/styleguide/src/lib/Foundations/Theme/CreatingThemes.mdx`). Themes are defined in `@codecademy/gamut-styles` and must extend the base theme shape with all required token keys.
|
|
109
57
|
|
|
110
58
|
## Key principles
|
|
111
59
|
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
60
|
+
- Pick the correct theme export for the product (`coreTheme`, `adminTheme`, `platformTheme`, `lxStudioTheme`, `percipioTheme`, etc.) so tokens and fonts match `DESIGN.md` and design intent.
|
|
61
|
+
- Align `theme.d.ts` / `Theme extends …` with the same theme interface you pass to `GamutProvider` (see [setup.md](../../guidelines/setup.md)).
|
|
62
|
+
- Components stay portable across themes when they use token and semantic aliases rather than one-off hex; authoring rules live in `gamut-style-utilities` and `gamut-color-mode`.
|
|
63
|
+
- `GamutProvider` wires theme, color mode, and logical-properties settings at the root; individual components should not hard-code which org theme is active.
|
|
@@ -1,123 +1,79 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gamut-typography
|
|
3
|
-
description:
|
|
3
|
+
description: Gamut UI text — headlines, body, fontSize, lineHeight, fontWeight title. When invoked, read guidelines/foundations/typography.md and DESIGN.md for the active theme.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Gamut Typography
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Read first
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
When this skill applies, read [`guidelines/foundations/typography.md`](../../guidelines/foundations/typography.md) before writing code. Confirm stacks and `fontWeight.title` against root `DESIGN.md`.
|
|
11
11
|
|
|
12
|
-
Codecademy
|
|
12
|
+
Implementation source of truth: [`packages/gamut-styles/src/variables/typography.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variables/typography.ts) and themes under [`packages/gamut-styles/src/themes`](https://github.com/Codecademy/gamut/tree/main/packages/gamut-styles/src/themes).
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Scope by theme
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- Emphasis within body copy (Italic — **not** Bold)
|
|
16
|
+
| Themes | Fonts | `fontWeight.title` |
|
|
17
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
|
18
|
+
| Core, Admin, Platform | `base` → Apercu stack; `accent` → Suisse + Apercu stack | 700 |
|
|
19
|
+
| Percipio, LX Studio | `base` → Skillsoft Text; `accent` → Skillsoft Sans; Percipio `monospace` → Roboto Mono; LX `monospace` matches Core stack per theme file | 500 |
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
- Do not use Bold to emphasize text within a Regular-weight paragraph. Use Italic instead.
|
|
24
|
-
- Set with generous line-height for body text: **150–175%** of the type size (e.g. 16px type → 24–28px line-height).
|
|
25
|
-
- Headlines should use **100–110%** line-height to appear intentional and grouped.
|
|
26
|
-
- Text should be **left-aligned** by default.
|
|
27
|
-
|
|
28
|
-
### Suisse Intl Mono (`fontFamily: "accent"`)
|
|
29
|
-
|
|
30
|
-
Monospace accent typeface. Use sparingly for:
|
|
31
|
-
- Code snippets and inline code
|
|
32
|
-
- Numbers, figures, and statistics
|
|
33
|
-
- Captions and labels that reference technical/engineering context
|
|
34
|
-
- Enumerated lists
|
|
35
|
-
- Quotations in a technical voice
|
|
36
|
-
|
|
37
|
-
**Rules:**
|
|
38
|
-
- Every character is the same width — avoid long paragraph-length prose in Suisse.
|
|
39
|
-
- It reads large for its point size: **reduce the size by ~10–15%** relative to Apercu text at the same visual scale (e.g. 14px Suisse ≈ 16px Apercu visually).
|
|
40
|
-
- Requires extra line-height to remain readable.
|
|
21
|
+
Use `fontWeight="title"` for headlines / emphasis roles — never hardcode `700` on Percipio/LX unless SPECIFICALLY noted in Figma designs.
|
|
41
22
|
|
|
42
23
|
## Font size scale (`fontSize`)
|
|
43
24
|
|
|
44
|
-
|
|
25
|
+
Theme keys: `64`, `44`, `34`, `26`, `22`, `20`, `18`, `16`, `14`.
|
|
45
26
|
|
|
46
27
|
```tsx
|
|
47
28
|
import { css } from '@codecademy/gamut-styles';
|
|
48
29
|
import styled from '@emotion/styled';
|
|
49
|
-
|
|
50
|
-
// Via system props
|
|
51
30
|
import { system } from '@codecademy/gamut-styles';
|
|
52
|
-
const Text = styled.p(system.typography);
|
|
53
|
-
<Text fontSize={16} />;
|
|
54
31
|
|
|
55
|
-
|
|
56
|
-
|
|
32
|
+
const Paragraph = styled.p(system.typography);
|
|
33
|
+
<Paragraph fontSize={16} lineHeight="base" />;
|
|
57
34
|
|
|
58
|
-
|
|
59
|
-
import { theme } from '@codecademy/gamut-styles';
|
|
60
|
-
const size = theme.fontSize[16];
|
|
35
|
+
const Styled = styled.div(css({ fontSize: 14, fontFamily: 'base' }));
|
|
61
36
|
```
|
|
62
37
|
|
|
63
|
-
## Line
|
|
38
|
+
## Line height (`lineHeight`)
|
|
64
39
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Guidelines:
|
|
68
|
-
- Body text: 150–175% of font size
|
|
69
|
-
- Headlines: 100–110% of font size
|
|
40
|
+
Tokens: `base` (1.5), `spacedTitle` (1.3), `title` (1.2). Prefer tokens over raw decimals. Only specify `lineHeight` when specified by design.
|
|
70
41
|
|
|
71
42
|
## Line length
|
|
72
43
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
|
76
|
-
|
|
77
|
-
|
|
|
78
|
-
| Multi-column layouts | ≤50 characters per line |
|
|
79
|
-
| Minimum | 45 characters |
|
|
80
|
-
|
|
81
|
-
**How to control line length**: Start with the right text style for the design, then adjust the width or column count of the text container.
|
|
82
|
-
|
|
83
|
-
## Alignment
|
|
84
|
-
|
|
85
|
-
- **Left-align** paragraphs by default — this is easiest to read and supports grid alignment.
|
|
86
|
-
- **Center-align** only for short marketing headlines or specific interface components with brief text.
|
|
87
|
-
- **Never right-align** text in normal circumstances (exceptions: numbers, equations).
|
|
88
|
-
- Do not adjust letter-spacing.
|
|
44
|
+
| Context | Target |
|
|
45
|
+
| ------------------ | ------------------------ |
|
|
46
|
+
| Single-column body | ~66 characters (max ~85) |
|
|
47
|
+
| Multi-column | ≤50 characters per line |
|
|
48
|
+
| Minimum | ~45 characters |
|
|
89
49
|
|
|
90
50
|
## Accessing typography tokens
|
|
91
51
|
|
|
92
52
|
```tsx
|
|
93
|
-
// System props (recommended for styled components)
|
|
94
53
|
import { system } from '@codecademy/gamut-styles';
|
|
95
54
|
import { variance } from '@codecademy/variance';
|
|
96
55
|
|
|
97
|
-
const Heading = styled.h2(
|
|
98
|
-
variance.compose(system.typography, system.space)
|
|
99
|
-
);
|
|
56
|
+
const Heading = styled.h2(variance.compose(system.typography, system.space));
|
|
100
57
|
|
|
101
|
-
<Heading
|
|
58
|
+
<Heading
|
|
59
|
+
fontSize={26}
|
|
60
|
+
fontFamily="base"
|
|
61
|
+
fontWeight="title"
|
|
62
|
+
lineHeight="title"
|
|
63
|
+
mb={8}
|
|
64
|
+
/>;
|
|
102
65
|
|
|
103
|
-
// css() utility (recommended for static styles)
|
|
104
66
|
import { css } from '@codecademy/gamut-styles';
|
|
105
67
|
|
|
106
68
|
const Caption = styled.span(
|
|
107
|
-
css({ fontFamily: 'accent', fontSize:
|
|
69
|
+
css({ fontFamily: 'accent', fontSize: 14, color: 'text-secondary' })
|
|
108
70
|
);
|
|
109
|
-
|
|
110
|
-
// Theme object (outside styled components)
|
|
111
|
-
import { theme } from '@codecademy/gamut-styles';
|
|
112
|
-
import { css as emotionCss } from '@emotion/react';
|
|
113
|
-
|
|
114
|
-
const myStyles = emotionCss`
|
|
115
|
-
font-size: ${theme.fontSize[14]};
|
|
116
|
-
`;
|
|
117
71
|
```
|
|
118
72
|
|
|
119
|
-
|
|
73
|
+
Prefer `<Text>` from `@codecademy/gamut` with `variant` / `as` — see Storybook [Typography / Text](https://gamut.codecademy.com/?path=/docs-typography-text--docs).
|
|
74
|
+
|
|
75
|
+
## Semantic vs visual headings
|
|
120
76
|
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
-
|
|
77
|
+
- `<Text as="h1">` … `<Text as="h6">` gets default heading styles: each tag maps to the same scale as `variant="title-xxl"` … `variant="title-xs"` (`h1` largest through `h6` smallest). Plain `<Text>` defaults to `as="span"` (inherits font size).
|
|
78
|
+
- Use `variant` plus `fontSize` / `fontWeight` / `lineHeight` (and other system props) to override element defaults when the outline needs one heading level but the UI needs another visual weight — e.g. `<Text as="h2" variant="title-sm">`.
|
|
79
|
+
- Still pick `h1`–`h6` for document structure and assistive tech; overrides are for intentional divergence between semantics and appearance.
|