@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.
@@ -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 syntax: [mobile, tablet, desktop]
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: `bg` (background-color shorthand), `textColor`, `borderColor`
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 fontSize={16} fontFamily="accent" textTransform="uppercase" lineHeight={1.5} />;
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 background={`url(${myBg})`} backgroundSize="cover" backgroundPosition="center" />;
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 an **array of values** for responsive breakpoints (Gamut uses a mobile-first approach):
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
- // [mobile, tablet, desktop]
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 `css()` from `@codecademy/gamut-styles`:
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
- - Use `system.color` (semantic aliases) for colors that need to adapt to dark/light mode; use raw tokens only for colors that should stay fixed regardless of mode.
172
- - Use `system.space` values that reference the spacing scale rather than arbitrary pixel values to maintain visual rhythm.
173
- - For background images/patterns use `system.background`; for solid background colors that may switch mode use `<Background>` from ColorMode.
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, MockGamutProvider, ColorMode in tests, emotion matchers, or removing jest.mock of @codecademy/gamut / gamut-styles.
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` wraps `GamutProvider` with test-safe settings:
16
+ `MockGamutProvider` forwards to `GamutProvider` with:
23
17
 
24
- - `useCache={false}` — disables emotion's style injection cache so styles are predictable across tests
25
- - `useGlobals={false}` — disables global CSS (Reboot, Typography, CSS Variables) to avoid side effects between test files
26
- - `theme={coreTheme}` — provides the full Gamut token set so styled components resolve correctly
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 should never construct a `GamutProvider` manually in a test. Use `MockGamutProvider`.
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 | Use |
35
- |---|---|
36
- | Standard component unit test | `setupRtl` from `@codecademy/gamut-tests` |
37
- | Test that needs to vary `useLogicalProperties` | `render` + `MockGamutProvider` directly |
38
- | Test that needs `ColorMode` context | `render` + `MockGamutProvider` + `<ColorMode>` |
39
- | Visual test mock / Storybook wrapper | `MockGamutProvider` directly |
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` — preferred pattern
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
- - `view` — RTL `RenderResult` (getByRole, getByText, etc.)
70
- - `props`the resolved props passed to the component (useful for asserting on jest.fn() mocks)
71
- - `update`re-render with updated props without remounting
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
- ## `MockGamutProvider` directly when you need more control
87
+ ## Harness + `setupRtl` when the wrapper is not default
86
88
 
87
- Use `render` + `MockGamutProvider` when `setupRtl` doesn't give enough control over the wrapper for example, when testing both logical and physical CSS property modes, or when adding `ColorMode`.
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
- ### Testing both logical and physical property modes
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 { container } = render(
114
- <MockGamutProvider useLogicalProperties={useLogicalProperties}>
115
- <MyComponent width="200px" />
116
- </MockGamutProvider>
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 `@emotion/jest` matchers once per test file to enable CSS-in-JS assertions:
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 `theme` from `@codecademy/gamut-styles` to avoid hardcoding token values:
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
- When creating mock components for visual tests or Storybook, wrap with `MockGamutProvider` and `ColorMode`:
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>> = (props) => (
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 | Fix |
175
- |---|---|
176
- | `jest.mock('@codecademy/gamut', () => ({ ... }))` | Remove mock; use `setupRtl` or `MockGamutProvider` |
177
- | `jest.mock('@codecademy/gamut-styles', ...)` | Remove mock; `MockGamutProvider` handles theme context |
178
- | Wrapping with `GamutProvider` directly in tests | Use `MockGamutProvider` it sets `useCache={false}` and `useGlobals={false}` |
179
- | Repeating `render(<MockGamutProvider>...</MockGamutProvider>)` in every test | Extract with `setupRtl`; define `renderView` once above the `describe` block |
180
- | One `setupRtl` call per `it` block | Define `renderView` once outside `describe`, call it inside each `it` |
181
- | Asserting on raw CSS token strings | Import `theme` from `@codecademy/gamut-styles` and use `theme.spacing[n]`, `theme.fontSize`, etc. |
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 even if the user does not name fonts or tokens. Covers Apercu Pro, Suisse Intl Mono, scale, line heights, line length, and alignment.
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
- > **Scope**: This skill covers typography for **Codecademy products** using the Core, Admin, or Platform themes (Apercu + Suisse). Percipio uses Roboto for all type — see `DESIGN.md` for Percipio-specific guidance. LX Studio uses Hanken Grotesk in place of both Apercu and Suisse.
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
- ## Typefaces
10
+ ## Scope by theme
11
11
 
12
- Codecademy products use two typefaces:
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
- ### Apercu Pro (`fontFamily: "base"`)
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
- Sizes are accessed via the theme's `fontSize` scale. Common keys:
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
- // Via css() utility
56
- const Box = styled.div(css({ fontSize: 14 }));
28
+ const Paragraph = styled.p(system.typography);
29
+ <Paragraph fontSize={16} lineHeight="base" />;
57
30
 
58
- // Via theme directly (outside styled components)
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 heights (`lineHeight`)
34
+ ## Line height (`lineHeight`)
64
35
 
65
- Line heights are limited to **multiples of 4px**. Type boxes are placed on an **8px placement grid**.
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
- Controlling line length is essential for readability:
74
-
75
- | Context | Target |
76
- |---|---|
77
- | Single-column body text | ~66 characters (max 85) |
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 fontSize={24} fontFamily="base" fontWeight="bold" lineHeight={1.1} mb={8} />;
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: 12, color: 'secondary' })
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
- ## Semantic vs. visual sizing
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
- - The term **"Title"** distinguishes visual size from semantic HTML hierarchy (H1–H6).
122
- - A visually large title may use `<h2>` or `<p>` semantically visual scale and semantic meaning are independent in Gamut.
123
- - Choose HTML heading levels for document structure, choose font size for visual hierarchy.
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.d52035.0",
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.d52035.0",
9
- "@codecademy/gamut-illustrations": "0.58.12-alpha.d52035.0",
10
- "@codecademy/gamut-patterns": "0.10.31-alpha.d52035.0",
11
- "@codecademy/gamut-styles": "18.0.1-alpha.d52035.0",
12
- "@codecademy/variance": "0.26.2-alpha.d52035.0",
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",