@codecademy/gamut 68.6.1-alpha.d52035.0 → 68.6.1-alpha.df4bce.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/commands/gamut-review.md +8 -8
- package/agent-tools/guidelines/components/overview.md +15 -7
- package/agent-tools/guidelines/foundations/spacing.md +78 -37
- package/agent-tools/guidelines/foundations/typography.md +70 -37
- package/agent-tools/guidelines/overview.md +21 -19
- package/agent-tools/guidelines/setup.md +57 -18
- package/agent-tools/rules/accessibility.mdc +21 -11
- package/agent-tools/skills/gamut-accessibility/SKILL.md +99 -124
- package/agent-tools/skills/gamut-forms/SKILL.md +84 -0
- package/agent-tools/skills/gamut-system-props/SKILL.md +56 -26
- package/agent-tools/skills/gamut-testing/SKILL.md +102 -62
- package/agent-tools/skills/gamut-typography/SKILL.md +34 -82
- package/package.json +6 -6
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gamut-system-props
|
|
3
|
-
description: Use this skill when building or refactoring styled Gamut components that need layout, spacing, color, border, background, typography, positioning, grid, flex, shadow, or responsive values from @codecademy/gamut-styles — including composing system prop groups with variance.
|
|
3
|
+
description: Use this skill when building or refactoring styled Gamut components that need layout, spacing, color, border, background, typography, positioning, grid, flex, shadow, list styles, or responsive values from @codecademy/gamut-styles — including composing system prop groups with variance.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Gamut System Props
|
|
7
7
|
|
|
8
|
-
Source: `@codecademy/gamut-styles` — [`variance/config.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/config.ts)
|
|
8
|
+
Source: `@codecademy/gamut-styles` — [`variance/config.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/config.ts) (definitions) and [`variance/props.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/props.ts) (`variance.create` groups). **`Box`**, **`FlexBox`**, and **`GridBox`** compose the same groups in [`packages/gamut/src/Box/props.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Box/props.ts).
|
|
9
|
+
|
|
10
|
+
**See also:** [Styleguide — Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx) (semantic colors, `variant` / `states`, `StyleProps`, responsive examples) and Storybook [Responsive properties](https://gamut.codecademy.com/storybook/?path=/docs-foundations-system-responsive-properties--page).
|
|
9
11
|
|
|
10
12
|
## Overview
|
|
11
13
|
|
|
12
14
|
System props are strongly-typed, theme-connected CSS prop groups from `@codecademy/gamut-styles`. They give styled components a consistent, responsive API. All props are built on top of `@codecademy/variance`.
|
|
13
15
|
|
|
14
16
|
Each prop group has:
|
|
17
|
+
|
|
15
18
|
- **`properties`**: The CSS properties it controls
|
|
16
19
|
- **`scale`**: Token scale it's restricted to (theme colors, spacing values, etc.)
|
|
17
20
|
- **`transform`**: Optional transform applied before output (e.g. `width={0.5}` → `width: 50%`)
|
|
@@ -39,7 +42,7 @@ const FlexBox = styled.div(
|
|
|
39
42
|
|
|
40
43
|
### `system.layout`
|
|
41
44
|
|
|
42
|
-
Controls dimensions, display, overflow, and container behavior.
|
|
45
|
+
Controls dimensions, display, overflow, and container behavior. This group also carries **flex/grid item** props used when laying out children: `flexGrow`, `flexShrink`, `flexBasis`, `order`, `gridColumn`, `gridRow`, `gridColumnStart`, `gridRowStart`, `gridColumnEnd`, `gridRowEnd`, `alignSelf`, `justifySelf`, `gridArea`.
|
|
43
46
|
|
|
44
47
|
```tsx
|
|
45
48
|
const Box = styled.div(system.layout);
|
|
@@ -47,7 +50,7 @@ const Box = styled.div(system.layout);
|
|
|
47
50
|
<Box display="flex" width="50%" height="300px" verticalAlign="middle" />;
|
|
48
51
|
```
|
|
49
52
|
|
|
50
|
-
Key props: `display`, `width`, `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `overflow`, `overflowX`, `overflowY`, `verticalAlign`
|
|
53
|
+
Key props: `containerType`, `display`, `direction`, `dimensions`, `width`, `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `overflow`, `overflowX`, `overflowY`, `verticalAlign`, plus the item props above (see `config.ts` for the full map).
|
|
51
54
|
|
|
52
55
|
### `system.space`
|
|
53
56
|
|
|
@@ -59,7 +62,7 @@ const Box = styled.div(system.space);
|
|
|
59
62
|
// Single value
|
|
60
63
|
<Box p={8} m={16} />;
|
|
61
64
|
|
|
62
|
-
// Responsive array
|
|
65
|
+
// Responsive (array / object — see Responsive values)
|
|
63
66
|
<Box my={[16, 24, 32]} px={[8, 16]} />;
|
|
64
67
|
```
|
|
65
68
|
|
|
@@ -72,10 +75,10 @@ Foreground, background, and border colors restricted to the theme's color palett
|
|
|
72
75
|
```tsx
|
|
73
76
|
const Box = styled.div(system.color);
|
|
74
77
|
|
|
75
|
-
<Box bg="navy" textColor="gray-100" borderColor="blue" />;
|
|
78
|
+
<Box bg="navy" color="gray-900" textColor="gray-100" borderColor="blue" />;
|
|
76
79
|
```
|
|
77
80
|
|
|
78
|
-
Key props: `
|
|
81
|
+
Key props: `color`, `textColor` (both set CSS `color`), `bg`, `borderColor`, plus directional `borderColor*` variants — see `config.ts` for the full set.
|
|
79
82
|
|
|
80
83
|
### `system.typography`
|
|
81
84
|
|
|
@@ -84,16 +87,22 @@ Text styling connected to theme typography scales.
|
|
|
84
87
|
```tsx
|
|
85
88
|
const Text = styled.p(system.typography);
|
|
86
89
|
|
|
87
|
-
<Text
|
|
90
|
+
<Text
|
|
91
|
+
fontSize={16}
|
|
92
|
+
fontFamily="accent"
|
|
93
|
+
fontStyle="italic"
|
|
94
|
+
textTransform="uppercase"
|
|
95
|
+
lineHeight="base"
|
|
96
|
+
/>;
|
|
88
97
|
```
|
|
89
98
|
|
|
90
|
-
Key props: `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, `textAlign`, `textTransform`, `textDecoration`, `letterSpacing`, `whiteSpace`
|
|
99
|
+
Key props: `fontFamily`, `fontSize`, `fontWeight`, `fontStyle`, `lineHeight`, `textAlign`, `textTransform`, `textDecoration`, `letterSpacing`, `whiteSpace` — prefer **`lineHeight`** scale keys (`base`, `title`, `spacedTitle`) from the theme over raw numbers when possible.
|
|
91
100
|
|
|
92
101
|
### `system.border`
|
|
93
102
|
|
|
94
|
-
Border width, style, radius, and color.
|
|
103
|
+
Border width, style, radius, and color. Many **logical shorthands** exist (`borderX`, `borderColorY`, `borderRadiusTop`, …); see `config.ts` for the full map.
|
|
95
104
|
|
|
96
|
-
Key props: `border`, `borderTop`, `borderRight`, `borderBottom`, `borderLeft`, `borderRadius`, `borderWidth`, `borderStyle`
|
|
105
|
+
Key props (non-exhaustive): `border`, `borderTop`, `borderRight`, `borderBottom`, `borderLeft`, `borderRadius`, `borderWidth`, `borderStyle`
|
|
97
106
|
|
|
98
107
|
### `system.background`
|
|
99
108
|
|
|
@@ -104,7 +113,11 @@ import myBg from './myBg.png';
|
|
|
104
113
|
|
|
105
114
|
const Box = styled.div(system.background);
|
|
106
115
|
|
|
107
|
-
<Box
|
|
116
|
+
<Box
|
|
117
|
+
background={`url(${myBg})`}
|
|
118
|
+
backgroundSize="cover"
|
|
119
|
+
backgroundPosition="center"
|
|
120
|
+
/>;
|
|
108
121
|
```
|
|
109
122
|
|
|
110
123
|
Key props: `background`, `backgroundImage`, `backgroundSize`, `backgroundPosition`, `backgroundRepeat`
|
|
@@ -113,27 +126,25 @@ Key props: `background`, `backgroundImage`, `backgroundSize`, `backgroundPositio
|
|
|
113
126
|
|
|
114
127
|
Flexbox child and container properties.
|
|
115
128
|
|
|
116
|
-
Key props: `flex`, `flexDirection`, `flexWrap`, `flexGrow`, `flexShrink`, `flexBasis`, `alignItems`, `alignSelf`, `justifyContent`, `justifySelf`, `gap`, `rowGap`, `columnGap`
|
|
129
|
+
Key props (non-exhaustive): `flex`, `flexDirection`, `flexWrap`, `flexGrow`, `flexShrink`, `flexBasis`, `alignItems`, `alignContent`, `alignSelf`, `justifyContent`, `justifyItems`, `justifySelf`, `gap`, `rowGap`, `columnGap`
|
|
117
130
|
|
|
118
131
|
### `system.grid`
|
|
119
132
|
|
|
120
133
|
CSS Grid container and child properties.
|
|
121
134
|
|
|
122
|
-
Key props: `gridTemplateColumns`, `gridTemplateRows`, `gridTemplateAreas`, `gridColumn`, `gridRow`, `gridArea`, `gridAutoFlow`
|
|
135
|
+
Key props (non-exhaustive): `gridTemplateColumns`, `gridTemplateRows`, `gridTemplateAreas`, `gridColumn`, `gridRow`, `gridArea`, `gridAutoFlow`, `gridAutoColumns`, `gridAutoRows`, `gap`, `rowGap`, `columnGap`
|
|
123
136
|
|
|
124
137
|
### `system.positioning`
|
|
125
138
|
|
|
126
|
-
Position and offset properties.
|
|
139
|
+
Position and offset properties. Inset shorthands use **`transformSize`**; physical vs logical edges follow **`useLogicalProperties`**.
|
|
127
140
|
|
|
128
141
|
```tsx
|
|
129
|
-
const Overlay = styled.div(
|
|
130
|
-
variance.compose(system.layout, system.positioning)
|
|
131
|
-
);
|
|
142
|
+
const Overlay = styled.div(variance.compose(system.layout, system.positioning));
|
|
132
143
|
|
|
133
144
|
<Overlay position="absolute" top={0} left={0} width="100%" height="100%" />;
|
|
134
145
|
```
|
|
135
146
|
|
|
136
|
-
Key props: `position`, `top`, `right`, `bottom`, `left`, `zIndex`
|
|
147
|
+
Key props: `position`, `inset`, `top`, `right`, `bottom`, `left`, `zIndex`, `opacity`
|
|
137
148
|
|
|
138
149
|
### `system.shadow`
|
|
139
150
|
|
|
@@ -141,18 +152,35 @@ Box and text shadow.
|
|
|
141
152
|
|
|
142
153
|
Key props: `boxShadow`, `textShadow`
|
|
143
154
|
|
|
155
|
+
### `system.list`
|
|
156
|
+
|
|
157
|
+
List marker styling (`listStyle`, `listStyleType`, `listStylePosition`, `listStyleImage`). Included on **`Box`** alongside the other composed groups.
|
|
158
|
+
|
|
144
159
|
## Responsive values
|
|
145
160
|
|
|
146
|
-
All system props accept
|
|
161
|
+
All system props accept responsive values **mobile-first** (min-width queries). Two shapes are supported:
|
|
162
|
+
|
|
163
|
+
### Object syntax
|
|
164
|
+
|
|
165
|
+
Keys are breakpoints; **`_`** is the base (no breakpoint). Includes `xs`, `sm`, `md`, `lg`, `xl`, and container keys `c_xs` … `c_xl`.
|
|
147
166
|
|
|
148
167
|
```tsx
|
|
149
|
-
|
|
150
|
-
<Box width={['100%', '50%', '33%']} p={[8, 16, 24]} />;
|
|
168
|
+
<Box width={{ _: '100%', sm: '50%', md: '33%' }} px={{ _: 8, md: 16 }} />
|
|
151
169
|
```
|
|
152
170
|
|
|
171
|
+
### Array syntax
|
|
172
|
+
|
|
173
|
+
Slots map in order to: base, `xs`, `sm`, `md`, `lg`, `xl`, then `c_xs` … `c_xl`. Leave a slot **empty** (or use `undefined`) to skip a breakpoint.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<Box width={['100%', , '50%']} p={[8, 16, , 24]} />
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Full typings and behavior: [Responsive properties (Storybook)](https://gamut.codecademy.com/storybook/?path=/docs-foundations-system-responsive-properties--page).
|
|
180
|
+
|
|
153
181
|
## Using `css()` for styled definitions
|
|
154
182
|
|
|
155
|
-
For static styles in styled components, use
|
|
183
|
+
For static styles in styled components, use **`css()`** from `@codecademy/gamut-styles` (same implementation as **`system.css`** on the `system` namespace).
|
|
156
184
|
|
|
157
185
|
```tsx
|
|
158
186
|
import { css } from '@codecademy/gamut-styles';
|
|
@@ -168,6 +196,8 @@ const Text = styled.div(css({ color: 'primary', p: 4 }));
|
|
|
168
196
|
## Key principles
|
|
169
197
|
|
|
170
198
|
- Compose `system.*` groups via `variance.compose()` — don't apply multiple groups by chaining `styled.div(system.a)(system.b)`.
|
|
171
|
-
-
|
|
172
|
-
- Use
|
|
173
|
-
-
|
|
199
|
+
- Prefer **semantic color names** on `system.color` (e.g. `bg="background"`, `textColor="text"`) so values track **ColorMode**; use raw palette keys only when the design should stay fixed across modes.
|
|
200
|
+
- Use **`bg`** with semantic tokens for most mode-aware surfaces; use **`<Background>`** from `@codecademy/gamut-styles` when you need its **contrast- and mode-aware** behavior, not for every tinted panel.
|
|
201
|
+
- Use `system.space` values on the **spacing scale** rather than arbitrary pixel strings to keep rhythm consistent.
|
|
202
|
+
- For background images/patterns use `system.background`; for solid fills use `system.color` / semantic **`bg`** (or **`Background`** when that component’s behavior is required).
|
|
203
|
+
- For reusable **variants** or **boolean states** on styled primitives, use **`variant`** / **`states`** from `@codecademy/gamut-styles` and expose props with **`StyleProps<typeof …>`** from `@codecademy/variance` — see [Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gamut-testing
|
|
3
|
-
description: Use this skill when writing or fixing unit tests for React components that use Gamut — setupRtl
|
|
3
|
+
description: Use this skill when writing or fixing unit tests for React components that use Gamut — prefer setupRtl from @codecademy/gamut-tests, harness patterns for useLogicalProperties and ColorMode, RTL/dir testing, emotion matchers, or removing jest.mock of @codecademy/gamut / gamut-styles.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Gamut Testing
|
|
@@ -9,40 +9,34 @@ Source: `@codecademy/gamut-tests` — [`index.tsx`](https://github.com/Codecadem
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
## Core rule: never mock Gamut components
|
|
13
|
-
|
|
14
|
-
Do not use `jest.mock('@codecademy/gamut')` or manually mock individual Gamut components in test files. This silently bypasses emotion's theme context, produces tests that can't catch real rendering failures, and requires every test file to maintain its own fragile mock list.
|
|
15
|
-
|
|
16
|
-
Use `MockGamutProvider` or `setupRtl` — both are designed for exactly this purpose.
|
|
17
|
-
|
|
18
12
|
---
|
|
19
13
|
|
|
20
|
-
## What `MockGamutProvider` does
|
|
14
|
+
## What `MockGamutProvider` does (under `setupRtl`)
|
|
21
15
|
|
|
22
|
-
`MockGamutProvider`
|
|
16
|
+
`MockGamutProvider` forwards to `GamutProvider` with:
|
|
23
17
|
|
|
24
|
-
- `useCache={false}` —
|
|
25
|
-
- `useGlobals={false}` —
|
|
26
|
-
- `theme={
|
|
18
|
+
- `useCache={false}` — stable Emotion output across tests
|
|
19
|
+
- `useGlobals={false}` — no global Reboot/Typography bleed between files
|
|
20
|
+
- `theme={theme}` — full token theme for styled components
|
|
21
|
+
- Optional **`useLogicalProperties`** — forwarded for logical vs physical CSS in variance
|
|
27
22
|
|
|
28
|
-
You
|
|
23
|
+
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
24
|
|
|
30
25
|
---
|
|
31
26
|
|
|
32
27
|
## Decision guide
|
|
33
28
|
|
|
34
|
-
| Scenario
|
|
35
|
-
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
29
|
+
| Scenario | Prefer |
|
|
30
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
31
|
+
| Default unit test for a Gamut (or app) component | **`setupRtl(Component, defaultProps)`** once per file / describe |
|
|
32
|
+
| Vary **`useLogicalProperties`** across cases | **Harness** that accepts `useLogicalProperties` and wraps **`MockGamutProvider`**, then **`setupRtl(Harness, defaults)`**; pass overrides per `it` / `describe.each` |
|
|
33
|
+
| 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 |
|
|
34
|
+
| **`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 |
|
|
35
|
+
| Storybook-only mock, chromatic-style wrapper, or non-RTL harness | **`MockGamutProvider`** (± **`ColorMode`**) in the exported wrapper component |
|
|
40
36
|
|
|
41
37
|
---
|
|
42
38
|
|
|
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.
|
|
39
|
+
## `setupRtl` — primary pattern
|
|
46
40
|
|
|
47
41
|
```tsx
|
|
48
42
|
import { setupRtl } from '@codecademy/gamut-tests';
|
|
@@ -66,13 +60,21 @@ it('accepts prop overrides', () => {
|
|
|
66
60
|
```
|
|
67
61
|
|
|
68
62
|
`renderView` returns `{ view, props, update }`:
|
|
69
|
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
63
|
+
|
|
64
|
+
- **`view`** — RTL `RenderResult` (`getByRole`, `getByLabelText`, `getByText`, …)
|
|
65
|
+
- **`props`** — resolved props (handy for `jest.fn()` assertions)
|
|
66
|
+
- **`update`** — re-render with new props without remounting
|
|
67
|
+
|
|
68
|
+
### Query and interaction habits (RTL)
|
|
69
|
+
|
|
70
|
+
- Prefer **`getByRole`**, **`getByLabelText`**, and accessible names over CSS selectors or snapshotting class strings unless you are explicitly testing styling.
|
|
71
|
+
- Prefer **`@testing-library/user-event`** over `fireEvent` when simulating real input (import `userEvent` from **`@testing-library/user-event`** in current major versions).
|
|
72
72
|
|
|
73
73
|
### Accessing mock functions via `props`
|
|
74
74
|
|
|
75
75
|
```tsx
|
|
76
|
+
import userEvent from '@testing-library/user-event';
|
|
77
|
+
|
|
76
78
|
it('calls onClick when clicked', async () => {
|
|
77
79
|
const { view, props } = renderView();
|
|
78
80
|
await userEvent.click(view.getByRole('button'));
|
|
@@ -82,50 +84,84 @@ it('calls onClick when clicked', async () => {
|
|
|
82
84
|
|
|
83
85
|
---
|
|
84
86
|
|
|
85
|
-
## `
|
|
87
|
+
## Harness + `setupRtl` when the wrapper is not default
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
`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.
|
|
90
|
+
|
|
91
|
+
### Varying `useLogicalProperties` (logical vs physical CSS)
|
|
88
92
|
|
|
89
93
|
```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
|
-
```
|
|
94
|
+
import { MockGamutProvider, setupRtl } from '@codecademy/gamut-tests';
|
|
102
95
|
|
|
103
|
-
|
|
96
|
+
import { MyComponent } from '../MyComponent';
|
|
97
|
+
|
|
98
|
+
type HarnessProps = React.ComponentProps<typeof MyComponent> & {
|
|
99
|
+
useLogicalProperties?: boolean;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const MyHarness = ({ useLogicalProperties, ...rest }: HarnessProps) => (
|
|
103
|
+
<MockGamutProvider useLogicalProperties={useLogicalProperties}>
|
|
104
|
+
<MyComponent {...rest} />
|
|
105
|
+
</MockGamutProvider>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const renderView = setupRtl(MyHarness, { width: '200px' });
|
|
104
109
|
|
|
105
|
-
```tsx
|
|
106
110
|
describe.each([
|
|
107
|
-
{ useLogicalProperties: true, widthProp: 'inlineSize' },
|
|
108
|
-
{ useLogicalProperties: false, widthProp: 'width' },
|
|
111
|
+
{ useLogicalProperties: true as const, widthProp: 'inlineSize' as const },
|
|
112
|
+
{ useLogicalProperties: false as const, widthProp: 'width' as const },
|
|
109
113
|
])(
|
|
110
114
|
'useLogicalProperties=$useLogicalProperties',
|
|
111
115
|
({ useLogicalProperties, widthProp }) => {
|
|
112
116
|
it(`uses ${widthProp}`, () => {
|
|
113
|
-
const {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
118
|
-
expect(container.firstChild).toHaveStyle({ [widthProp]: '200px' });
|
|
117
|
+
const { view } = renderView({ useLogicalProperties });
|
|
118
|
+
expect(view.getByTestId('my-component-root')).toHaveStyle({
|
|
119
|
+
[widthProp]: '200px',
|
|
120
|
+
});
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
123
|
);
|
|
122
124
|
```
|
|
123
125
|
|
|
126
|
+
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).
|
|
127
|
+
|
|
128
|
+
### `ColorMode` without abandoning `setupRtl`
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { ColorMode } from '@codecademy/gamut-styles';
|
|
132
|
+
import { setupRtl } from '@codecademy/gamut-tests';
|
|
133
|
+
|
|
134
|
+
const DarkHarness = (props: React.ComponentProps<typeof MyComponent>) => (
|
|
135
|
+
<ColorMode mode="dark">
|
|
136
|
+
<MyComponent {...props} />
|
|
137
|
+
</ColorMode>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const renderDark = setupRtl(DarkHarness, { title: 'Hi' });
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
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.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Raw `render` + `MockGamutProvider` — rare
|
|
148
|
+
|
|
149
|
+
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`**.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## RTL / `dir` and document-level behavior
|
|
154
|
+
|
|
155
|
+
Some components (e.g. overlays that call **`useElementDir`**) resolve direction from **`document.documentElement`** when there is no real target node. For those tests:
|
|
156
|
+
|
|
157
|
+
- 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.
|
|
158
|
+
- Combine with the harness pattern above when **`useLogicalProperties`** affects which longhand wins (`left` vs `insetInlineStart`, etc.).
|
|
159
|
+
|
|
124
160
|
---
|
|
125
161
|
|
|
126
162
|
## Emotion style assertions
|
|
127
163
|
|
|
128
|
-
Install
|
|
164
|
+
Install **`@emotion/jest`** matchers if you absolutely need to enable CSS-in-JS assertions:
|
|
129
165
|
|
|
130
166
|
```tsx
|
|
131
167
|
import { matchers } from '@emotion/jest';
|
|
@@ -140,7 +176,7 @@ expect(element).toHaveStyle({ borderRadius: '2px' });
|
|
|
140
176
|
expect(element).toHaveStyleRule('padding', '1rem');
|
|
141
177
|
```
|
|
142
178
|
|
|
143
|
-
Use
|
|
179
|
+
Use **`theme`** from **`@codecademy/gamut-styles`** instead of hardcoding token strings:
|
|
144
180
|
|
|
145
181
|
```tsx
|
|
146
182
|
import { theme } from '@codecademy/gamut-styles';
|
|
@@ -150,15 +186,17 @@ expect(element).toHaveStyle({ columnGap: theme.spacing[40] });
|
|
|
150
186
|
|
|
151
187
|
---
|
|
152
188
|
|
|
153
|
-
## Visual test wrappers
|
|
189
|
+
## Visual test wrappers and Storybook
|
|
154
190
|
|
|
155
|
-
|
|
191
|
+
Exported mocks and stories may wrap with **`MockGamutProvider`** and **`ColorMode`** explicitly (no `setupRtl` in Storybook):
|
|
156
192
|
|
|
157
193
|
```tsx
|
|
158
194
|
import { MockGamutProvider } from '@codecademy/gamut-tests';
|
|
159
195
|
import { ColorMode } from '@codecademy/gamut-styles';
|
|
160
196
|
|
|
161
|
-
export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (
|
|
197
|
+
export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (
|
|
198
|
+
props
|
|
199
|
+
) => (
|
|
162
200
|
<MockGamutProvider>
|
|
163
201
|
<ColorMode mode="light">
|
|
164
202
|
<MyComponent {...props} />
|
|
@@ -171,11 +209,13 @@ export const MyComponentMock: React.FC<ComponentProps<typeof MyComponent>> = (pr
|
|
|
171
209
|
|
|
172
210
|
## Common anti-patterns
|
|
173
211
|
|
|
174
|
-
| Anti-pattern
|
|
175
|
-
|
|
176
|
-
| `jest.mock('@codecademy/gamut', () => ({ ... }))`
|
|
177
|
-
| `jest.mock('@codecademy/gamut-styles', ...)`
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
212
|
+
| Anti-pattern | Fix |
|
|
213
|
+
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
214
|
+
| `jest.mock('@codecademy/gamut', () => ({ ... }))` | Remove; use **`setupRtl`** (or harness + **`setupRtl`**) |
|
|
215
|
+
| `jest.mock('@codecademy/gamut-styles', ...)` | Remove; **`MockGamutProvider`** / **`setupRtl`** supplies theme |
|
|
216
|
+
| **`GamutProvider`** in test files | Use **`MockGamutProvider`** only when building a harness or story; default tests go through **`setupRtl`** |
|
|
217
|
+
| **`import { setupRtl } from 'component-test-setup'`** in Gamut / apps | Import **`setupRtl` from `@codecademy/gamut-tests`** so **`MockGamutProvider`** is applied |
|
|
218
|
+
| Repeated **`render(<MockGamutProvider>…`** | **Harness + `setupRtl`**, or a shared **`renderView`** factory |
|
|
219
|
+
| One **`setupRtl`** call per **`it`** | Define **`renderView`** once outside **`describe`**, call it inside each **`it`** |
|
|
220
|
+
| Asserting raw CSS strings for tokens | Use **`theme`** from **`@codecademy/gamut-styles`** |
|
|
221
|
+
| Leaking **`dir="rtl"`** between tests | Reset **`document.documentElement`** in **`afterEach`** |
|
|
@@ -1,123 +1,75 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gamut-typography
|
|
3
|
-
description: Use this skill when creating or reviewing UI text in Gamut apps — headlines, body, captions, labels, code snippets, or text-heavy layouts
|
|
3
|
+
description: Use this skill when creating or reviewing UI text in Gamut apps — headlines, body, captions, labels, code snippets, or text-heavy layouts. Covers theme-specific stacks (Core Apercu/Suisse vs Percipio/LX Skillsoft), fontSize / lineHeight tokens, semantic fontWeight title (700 vs 500), line length, and alignment for Codecademy-branded surfaces.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Gamut Typography
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**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). Agent guideline: [foundations/typography.md](../../guidelines/foundations/typography.md).
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Scope by theme
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
| Themes | Fonts | `fontWeight.title` |
|
|
13
|
+
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
|
14
|
+
| **Core**, **Admin**, **Platform** | `base` → Apercu stack; `accent` → Suisse + Apercu stack | **700** |
|
|
15
|
+
| **Percipio**, **LX Studio** | `base` → Skillsoft Text; `accent` → Skillsoft Sans; Percipio `monospace` → Roboto Mono; LX `monospace` matches Core stack per theme file | **500** |
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
The primary typeface. Geometric-ish, humanist sans-serif. Use for:
|
|
17
|
-
- Headlines (Bold weight)
|
|
18
|
-
- Body / paragraph text (Regular weight)
|
|
19
|
-
- UI labels, menu items (Regular weight)
|
|
20
|
-
- Emphasis within body copy (Italic — **not** Bold)
|
|
21
|
-
|
|
22
|
-
**Rules:**
|
|
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.
|
|
17
|
+
Use **`fontWeight="title"`** for headlines / emphasis roles — never hardcode **`700`** on Percipio/LX unless SPECIFICALLY noted in Figma designs.
|
|
41
18
|
|
|
42
19
|
## Font size scale (`fontSize`)
|
|
43
20
|
|
|
44
|
-
|
|
21
|
+
Theme keys: `64`, `44`, `34`, `26`, `22`, `20`, `18`, `16`, `14`.
|
|
45
22
|
|
|
46
23
|
```tsx
|
|
47
24
|
import { css } from '@codecademy/gamut-styles';
|
|
48
25
|
import styled from '@emotion/styled';
|
|
49
|
-
|
|
50
|
-
// Via system props
|
|
51
26
|
import { system } from '@codecademy/gamut-styles';
|
|
52
|
-
const Text = styled.p(system.typography);
|
|
53
|
-
<Text fontSize={16} />;
|
|
54
27
|
|
|
55
|
-
|
|
56
|
-
|
|
28
|
+
const Paragraph = styled.p(system.typography);
|
|
29
|
+
<Paragraph fontSize={16} lineHeight="base" />;
|
|
57
30
|
|
|
58
|
-
|
|
59
|
-
import { theme } from '@codecademy/gamut-styles';
|
|
60
|
-
const size = theme.fontSize[16];
|
|
31
|
+
const Styled = styled.div(css({ fontSize: 14, fontFamily: 'base' }));
|
|
61
32
|
```
|
|
62
33
|
|
|
63
|
-
## Line
|
|
34
|
+
## Line height (`lineHeight`)
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Guidelines:
|
|
68
|
-
- Body text: 150–175% of font size
|
|
69
|
-
- Headlines: 100–110% of font size
|
|
36
|
+
Tokens: **`base`** (1.5), **`spacedTitle`** (1.3), **`title`** (1.2). Prefer tokens over raw decimals. Only specify `lineHeight` when specified by design.
|
|
70
37
|
|
|
71
38
|
## Line length
|
|
72
39
|
|
|
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.
|
|
40
|
+
| Context | Target |
|
|
41
|
+
| ------------------ | ------------------------ |
|
|
42
|
+
| Single-column body | ~66 characters (max ~85) |
|
|
43
|
+
| Multi-column | ≤50 characters per line |
|
|
44
|
+
| Minimum | ~45 characters |
|
|
89
45
|
|
|
90
46
|
## Accessing typography tokens
|
|
91
47
|
|
|
92
48
|
```tsx
|
|
93
|
-
// System props (recommended for styled components)
|
|
94
49
|
import { system } from '@codecademy/gamut-styles';
|
|
95
50
|
import { variance } from '@codecademy/variance';
|
|
96
51
|
|
|
97
|
-
const Heading = styled.h2(
|
|
98
|
-
variance.compose(system.typography, system.space)
|
|
99
|
-
);
|
|
52
|
+
const Heading = styled.h2(variance.compose(system.typography, system.space));
|
|
100
53
|
|
|
101
|
-
<Heading
|
|
54
|
+
<Heading
|
|
55
|
+
fontSize={26}
|
|
56
|
+
fontFamily="base"
|
|
57
|
+
fontWeight="title"
|
|
58
|
+
lineHeight="title"
|
|
59
|
+
mb={8}
|
|
60
|
+
/>;
|
|
102
61
|
|
|
103
|
-
// css() utility (recommended for static styles)
|
|
104
62
|
import { css } from '@codecademy/gamut-styles';
|
|
105
63
|
|
|
106
64
|
const Caption = styled.span(
|
|
107
|
-
css({ fontFamily: 'accent', fontSize:
|
|
65
|
+
css({ fontFamily: 'accent', fontSize: 14, color: 'text-secondary' })
|
|
108
66
|
);
|
|
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
67
|
```
|
|
118
68
|
|
|
119
|
-
|
|
69
|
+
Prefer `<Text>` from `@codecademy/gamut` with `variant` / `as` — see Storybook [Typography / Text](https://gamut.codecademy.com/?path=/docs-typography-text--docs).
|
|
70
|
+
|
|
71
|
+
## Semantic vs visual headings
|
|
120
72
|
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
-
|
|
73
|
+
- `<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).
|
|
74
|
+
- 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">`.
|
|
75
|
+
- Still pick **`h1`–`h6`** for document structure and assistive tech; overrides are for intentional divergence between semantics and appearance.
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecademy/gamut",
|
|
3
3
|
"description": "Styleguide & Component library for Codecademy",
|
|
4
|
-
"version": "68.6.1-alpha.
|
|
4
|
+
"version": "68.6.1-alpha.df4bce.0",
|
|
5
5
|
"author": "Codecademy Engineering <dev@codecademy.com>",
|
|
6
6
|
"bin": "./bin/gamut.mjs",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@codecademy/gamut-icons": "9.57.6-alpha.
|
|
9
|
-
"@codecademy/gamut-illustrations": "0.58.12-alpha.
|
|
10
|
-
"@codecademy/gamut-patterns": "0.10.31-alpha.
|
|
11
|
-
"@codecademy/gamut-styles": "18.0.1-alpha.
|
|
12
|
-
"@codecademy/variance": "0.26.2-alpha.
|
|
8
|
+
"@codecademy/gamut-icons": "9.57.6-alpha.df4bce.0",
|
|
9
|
+
"@codecademy/gamut-illustrations": "0.58.12-alpha.df4bce.0",
|
|
10
|
+
"@codecademy/gamut-patterns": "0.10.31-alpha.df4bce.0",
|
|
11
|
+
"@codecademy/gamut-styles": "18.0.1-alpha.df4bce.0",
|
|
12
|
+
"@codecademy/variance": "0.26.2-alpha.df4bce.0",
|
|
13
13
|
"@formatjs/intl-locale": "5.3.1",
|
|
14
14
|
"@react-aria/interactions": "3.25.0",
|
|
15
15
|
"@types/marked": "^4.0.8",
|