@codecademy/gamut 68.6.1-alpha.c211a2.0 → 68.6.1-alpha.d46fc5.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/.claude-plugin/marketplace.json +16 -0
- package/agent-tools/.claude-plugin/plugin.json +7 -0
- package/agent-tools/.cursor-plugin/plugin.json +7 -0
- package/agent-tools/DESIGN.Codecademy.md +643 -0
- package/agent-tools/DESIGN.LXStudio.md +444 -0
- package/agent-tools/DESIGN.Percipio.md +435 -0
- package/agent-tools/DESIGN.md +1 -0
- package/agent-tools/agents/.gitkeep +0 -0
- package/agent-tools/commands/gamut-review.md +246 -0
- package/agent-tools/guidelines/components/buttons.md +91 -0
- package/agent-tools/guidelines/components/overview.md +52 -0
- package/agent-tools/guidelines/foundations/color.md +172 -0
- package/agent-tools/guidelines/foundations/modes.md +47 -0
- package/agent-tools/guidelines/foundations/spacing.md +107 -0
- package/agent-tools/guidelines/foundations/typography.md +84 -0
- package/agent-tools/guidelines/overview.md +46 -0
- package/agent-tools/guidelines/setup.md +81 -0
- package/agent-tools/rules/accessibility.mdc +78 -0
- package/agent-tools/skills/gamut-accessibility/SKILL.md +214 -0
- package/agent-tools/skills/gamut-color-mode/SKILL.md +140 -0
- package/agent-tools/skills/gamut-forms/SKILL.md +84 -0
- package/agent-tools/skills/gamut-style-utilities/SKILL.md +107 -0
- package/agent-tools/skills/gamut-system-props/SKILL.md +203 -0
- package/agent-tools/skills/gamut-testing/SKILL.md +221 -0
- package/agent-tools/skills/gamut-theming/SKILL.md +48 -0
- package/agent-tools/skills/gamut-typography/SKILL.md +75 -0
- package/bin/commands/plugin/install.mjs +213 -0
- package/bin/commands/plugin/list.mjs +73 -0
- package/bin/commands/plugin/remove.mjs +108 -0
- package/bin/commands/plugin/update.mjs +59 -0
- package/bin/gamut.mjs +96 -0
- package/bin/lib/claude.mjs +52 -0
- package/bin/lib/cursor.mjs +40 -0
- package/bin/lib/design.mjs +71 -0
- package/bin/lib/io.mjs +14 -0
- package/bin/lib/resolve-plugin-dir.mjs +38 -0
- package/bin/lib/run-command.mjs +22 -0
- package/package.json +11 -8
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gamut-color-mode
|
|
3
|
+
description: Use this skill when implementing light/dark behavior, semantic color aliases, the Background component for contrast-safe surfaces, or color-mode hooks in Gamut — including replacing hardcoded hex, fixing mode bugs, or reviewing color usage.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gamut ColorMode
|
|
7
|
+
|
|
8
|
+
Source: `@codecademy/gamut-styles` — [`ColorMode.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/ColorMode.tsx)
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Gamut's color system uses **semantic aliases** instead of raw color tokens. This means components automatically adapt across light and dark modes without configuration.
|
|
13
|
+
|
|
14
|
+
### Semantic color aliases
|
|
15
|
+
|
|
16
|
+
| Alias | Purpose |
|
|
17
|
+
| ------------ | ------------------------------------------ |
|
|
18
|
+
| `text` | Standard text color for all type |
|
|
19
|
+
| `background` | Base background color |
|
|
20
|
+
| `primary` | Interactive elements with primary action |
|
|
21
|
+
| `secondary` | Interactive elements with secondary action |
|
|
22
|
+
|
|
23
|
+
This set is not exhaustive (e.g. `text-accent`, `background-disabled`, `danger` — see the light/dark tables in Storybook).
|
|
24
|
+
|
|
25
|
+
**Key principle**: Always use these aliases in component styles — never hardcode specific color tokens like `navy-400` for anything that needs to change per mode.
|
|
26
|
+
|
|
27
|
+
**Storybook:** [Foundations / ColorMode](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page) — interactive reference. [Meta / Best practices](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page) — semantic tokens, `css` / `variant` / `states`, and system props with ColorMode.
|
|
28
|
+
|
|
29
|
+
**Agent skill:** [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) — `css` / `variant` / `states` with semantic colors alongside ColorMode.
|
|
30
|
+
|
|
31
|
+
## `<ColorMode />`
|
|
32
|
+
|
|
33
|
+
Wraps content in a color mode context. Place `<ColorMode />` as high in the app tree as practical. For a nested or static themed area on a page, use `<Background />` instead.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { ColorMode } from '@codecademy/gamut-styles';
|
|
37
|
+
|
|
38
|
+
// Explicit light or dark
|
|
39
|
+
<ColorMode mode="light">{children}</ColorMode>
|
|
40
|
+
|
|
41
|
+
// Follow OS light/dark preference (see mode="system" below)
|
|
42
|
+
<ColorMode mode="system">{children}</ColorMode>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Props**: `mode="light" | "dark" | "system"`
|
|
46
|
+
|
|
47
|
+
### `mode="system"` (OS preference)
|
|
48
|
+
|
|
49
|
+
`system` is **not** a third color theme. It always resolves to `"light"` or `"dark"` based on the user's OS setting.
|
|
50
|
+
|
|
51
|
+
**How it works**
|
|
52
|
+
|
|
53
|
+
1. `ColorMode` calls `usePrefersDarkMode()`, which reads `window.matchMedia('(prefers-color-scheme: dark)')`.
|
|
54
|
+
2. If the query matches → active mode is `"dark"`; otherwise `"light"`.
|
|
55
|
+
3. Descendants receive that mode's semantic color variables (`text`, `background`, `primary`, etc.) — same as passing `mode="light"` or `mode="dark"` directly.
|
|
56
|
+
|
|
57
|
+
**When the OS changes** (e.g. user toggles system appearance), the media query fires a `change` event, `ColorMode` re-renders, and semantic colors update for everything inside the wrapper.
|
|
58
|
+
|
|
59
|
+
**What it does not do**
|
|
60
|
+
|
|
61
|
+
- Read in-app preferences (account settings, a theme toggle in localStorage). For those, pass `mode="light"` or `mode="dark"` yourself from your own state.
|
|
62
|
+
- Replace `<Background>`. A colored band still needs `<Background bg="hyper">` if you want contrast-based mode selection for that surface.
|
|
63
|
+
|
|
64
|
+
**Prefer `mode="system"` over wiring the hook yourself**
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// Prefer — ColorMode owns light/dark resolution
|
|
68
|
+
<ColorMode mode="system">{children}</ColorMode>;
|
|
69
|
+
|
|
70
|
+
// Avoid — duplicates what mode="system" already does
|
|
71
|
+
const prefersDark = usePrefersDarkMode();
|
|
72
|
+
<ColorMode mode={prefersDark ? 'dark' : 'light'}>{children}</ColorMode>;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use `usePrefersDarkMode()` only when you need the OS preference for something **other** than setting `ColorMode`'s mode (e.g. picking a decorative palette `bg` in a demo).
|
|
76
|
+
|
|
77
|
+
Place `<ColorMode mode="system">` high in the tree (often inside `GamutProvider`) so the whole app follows system appearance unless a subtree overrides with `mode="light"`, `mode="dark"`, or `<Background>`.
|
|
78
|
+
|
|
79
|
+
## `<Background />`
|
|
80
|
+
|
|
81
|
+
Use `<Background>` instead of putting `bg` on a layout component when a section needs a **fixed palette color** (card, hero, landing band, etc.) — independent of the parent color mode. Pass a **palette token** to `bg` (e.g. `hyper`, `navy`, `paleGreen`), not a semantic alias (`text`, `background`, `primary`).
|
|
82
|
+
|
|
83
|
+
`<Background>` switches light/dark to whichever mode gives the **highest contrast** between that surface and body `text`. Nested Gamut components inherit readable colors without extra setup.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { Background } from '@codecademy/gamut-styles';
|
|
87
|
+
|
|
88
|
+
// Single background — mode switches automatically if needed
|
|
89
|
+
const Card = ({ children }) => <Background bg="hyper">{children}</Background>;
|
|
90
|
+
|
|
91
|
+
// Nested backgrounds — each creates its own color context
|
|
92
|
+
const Page = () => (
|
|
93
|
+
<Background bg="black" p={24}>
|
|
94
|
+
<Background bg="paleGreen" p={24}>
|
|
95
|
+
{/* content inside inner Background uses its own mode */}
|
|
96
|
+
</Background>
|
|
97
|
+
</Background>
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `background-current` CSS variable
|
|
102
|
+
|
|
103
|
+
When `<Background>` is rendered, it sets a `background-current` CSS variable on the theme. Use this to reference an ancestor's background color (e.g. for simulating transparency or masking content).
|
|
104
|
+
|
|
105
|
+
## Color mode hooks
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import {
|
|
109
|
+
useColorModes,
|
|
110
|
+
useCurrentMode,
|
|
111
|
+
usePrefersDarkMode,
|
|
112
|
+
} from '@codecademy/gamut-styles';
|
|
113
|
+
|
|
114
|
+
// [activeModeKey, activeModeColors, allModes, getColorValue]
|
|
115
|
+
const [current, currentColors, modes, getColorValue] = useColorModes();
|
|
116
|
+
|
|
117
|
+
// Active mode key: "light" | "dark" (optional override argument)
|
|
118
|
+
const current = useCurrentMode();
|
|
119
|
+
const forced = useCurrentMode('light');
|
|
120
|
+
|
|
121
|
+
// Boolean from window.matchMedia('(prefers-color-scheme: dark)')
|
|
122
|
+
const prefersDark = usePrefersDarkMode();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Decision guide
|
|
126
|
+
|
|
127
|
+
| Need | Use |
|
|
128
|
+
| ------------------------------------------------------------- | ---------------------------------- | ---- | --------- |
|
|
129
|
+
| Set a page or section to a specific mode | `<ColorMode mode="light | dark | system">` |
|
|
130
|
+
| Place content on a colored background with automatic contrast | `<Background bg="...">` |
|
|
131
|
+
| Read the current mode in JavaScript | `useCurrentMode()` |
|
|
132
|
+
| Access all modes, variables, and resolve raw colors | `useColorModes()` |
|
|
133
|
+
| Detect OS dark mode preference | `usePrefersDarkMode()` |
|
|
134
|
+
| Access full emotion theme | `useTheme()` from `@emotion/react` |
|
|
135
|
+
|
|
136
|
+
## Common mistakes to avoid
|
|
137
|
+
|
|
138
|
+
- Do not use raw color tokens (e.g. `color: 'navy-400'`) for text, backgrounds, or borders that need to be accessible across modes — use semantic aliases instead.
|
|
139
|
+
- Do not use a raw `bg` prop for colored section backgrounds that need static, ColorMode-agnostic, background colors — use `<Background>` so mode selection is handled for you.
|
|
140
|
+
- Do not manually set `ColorMode`'s `mode` from `usePrefersDarkMode()` when `mode="system"` is enough. The hook is still useful for non-mode concerns (e.g. choosing a decorative `bg` in Storybook demos).
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gamut-forms
|
|
3
|
+
description: Implementing or auditing Gamut forms — FormGroup, ConnectedForm, ConnectedFormGroup, GridForm, react-hook-form wiring, labels, and accessible error/description regions. Pair with **`gamut-accessibility`** for non-form widgets and **`accessibility.mdc`** for universal HTML/ARIA rules.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gamut forms
|
|
7
|
+
|
|
8
|
+
Canonical wiring for **`FormGroup`**, **`ConnectedForm`**, **`ConnectedFormGroup`**, **`GridForm`**, and field renderers. Source: **`packages/gamut/src/Form/`**, **`ConnectedForm/`**, **`GridForm/`**.
|
|
9
|
+
|
|
10
|
+
Universal label and primitive guidance: **[`accessibility.mdc`](../../rules/accessibility.mdc)** · overlay and composite patterns: **[`gamut-accessibility`](../gamut-accessibility/SKILL.md)**.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Prefer connected layouts
|
|
15
|
+
|
|
16
|
+
For typical product forms, prefer **`GridForm`** (declarative **`fields`**, **`LayoutGrid`**, submit/cancel) or **`ConnectedForm`** with **`ConnectedFormGroup`** / **`useConnectedForm`**. Use raw **`FormGroup`** + atoms only when the layout is simple and you fully own **`id`**, **`htmlFor`**, invalid state, and any **`aria-describedby`** (see below).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Labels and controls
|
|
21
|
+
|
|
22
|
+
**`htmlFor`** / **`id`** pairing — universal rule in **[`accessibility.mdc`](../../rules/accessibility.mdc)** (Form label association). Form-specific notes:
|
|
23
|
+
|
|
24
|
+
- **`FormGroupLabel`** → control **`id`** (or stable **`name`** when that is your field’s id convention).
|
|
25
|
+
- **Checkbox**, **Radio**, **Select**: same pairing; checkbox/radio use the visually hidden input pattern from **`@codecademy/gamut-styles`** where applicable.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## `FormGroup` (baseline)
|
|
30
|
+
|
|
31
|
+
[`FormGroup.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/elements/FormGroup.tsx)
|
|
32
|
+
|
|
33
|
+
- **`description`** → **`FormGroupDescription`** with **`aria-live="assertive"`**.
|
|
34
|
+
- **`error`** (string) → **`FormError`** with **`aria-live="polite"`** and **`role="alert"`**.
|
|
35
|
+
|
|
36
|
+
Raw **`FormGroup`** does **not** set **`aria-describedby`** or **`aria-invalid`** on **`children`**. If you compose fields outside **`ConnectedFormGroup`** / **`GridForm`**, wire those yourself or accept that only the live regions above communicate errors/descriptions.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## `ConnectedFormGroup`
|
|
41
|
+
|
|
42
|
+
[`ConnectedFormGroup.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx)
|
|
43
|
+
|
|
44
|
+
- Passes **`aria-describedby`** (error region id when shown) and **`aria-invalid`** on the rendered field component.
|
|
45
|
+
- **`FormError`**: **`aria-live="assertive"`** and **`role="alert"`** only when **`isFirstError`**; otherwise **`aria-live="off"`** and **`role="status"`** so subsequent errors do not interrupt repeatedly.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## `GridForm`
|
|
50
|
+
|
|
51
|
+
[`GridFormInputGroup/index.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/GridForm/GridFormInputGroup/index.tsx) · [`GridFormTextInput`](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/GridForm/GridFormInputGroup/GridFormTextInput/index.tsx)
|
|
52
|
+
|
|
53
|
+
- Composes **`ConnectedForm`**, **`LayoutGrid`**, **`GridFormButtons`**, and field metadata. **`FormError`** uses the same **first-error assertive** pattern as **`ConnectedFormGroup`** (**`aria-live`** assertive vs off, **`role`** alert vs status).
|
|
54
|
+
- Built-in text inputs set **`aria-invalid`** and register with **react-hook-form** via **`register`**. Custom / **`custom`** / **`custom-group`** renderers must still expose correct **`id`**, **`label`**, and error surfacing consistent with this pattern.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Live regions — do not double up
|
|
59
|
+
|
|
60
|
+
**`FormGroup`**, **`ConnectedFormGroup`**, and **`GridForm`** already render **`FormError`** (and base **`FormGroup`** renders **`FormGroupDescription`**) with live-region attributes. Do not add a second **`aria-live`** wrapper for the same message stream.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Storybook
|
|
65
|
+
|
|
66
|
+
- [Organisms / GridForm / About](https://gamut.codecademy.com/?path=/docs-organisms-gridform-about--docs) · [Usage](https://gamut.codecademy.com/?path=/docs-organisms-gridform-usage--docs) · [Validation](https://gamut.codecademy.com/?path=/docs-organisms-gridform-validation--docs) · [Fields](https://gamut.codecademy.com/?path=/docs-organisms-gridform-fields--docs)
|
|
67
|
+
- [Organisms / ConnectedForm / ConnectedForm](https://gamut.codecademy.com/?path=/docs-organisms-connectedform-connectedform--docs) · [ConnectedFormGroup](https://gamut.codecademy.com/?path=/docs-organisms-connectedform-connectedformgroup--docs)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Example — baseline `FormGroup`
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<FormGroup
|
|
75
|
+
htmlFor="email-input"
|
|
76
|
+
description="Used for login"
|
|
77
|
+
error={errors.email}
|
|
78
|
+
>
|
|
79
|
+
<FormGroupLabel htmlFor="email-input">Email</FormGroupLabel>
|
|
80
|
+
<Input id="email-input" type="email" />
|
|
81
|
+
</FormGroup>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
When using **`ConnectedFormGroup`** or **`GridForm`**, prefer their docs and defaults over hand-rolling the above for every field.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gamut-style-utilities
|
|
3
|
+
description: Use this skill when authoring Gamut styles with @codecademy/gamut-styles — css(), variant(), states(), StyleProps from variance, or the useTheme() escape hatch; choosing between these APIs and system props; semantic tokens with ColorMode.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gamut style utilities
|
|
7
|
+
|
|
8
|
+
Source: `@codecademy/gamut-styles` — [`variance/props.ts`](https://github.com/Codecademy/gamut/blob/main/packages/gamut-styles/src/variance/props.ts) (`css`, `variant`, `states` built on `PROPERTIES.all`).
|
|
9
|
+
|
|
10
|
+
**See also:** [`gamut-theming`](../gamut-theming/SKILL.md) (which theme, `GamutProvider`, new themes). [`gamut-system-props`](../gamut-system-props/SKILL.md) (`system.*`, responsive props, `Box`). [`gamut-color-mode`](../gamut-color-mode/SKILL.md) (semantic color, `<ColorMode>`, `<Background>`). [Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx) and [system compose](https://gamut.codecademy.com/?path=/docs-foundations-system-compose--page).
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Use **`css()`**, **`variant()`**, and **`states()`** from `@codecademy/gamut-styles` for **typed, token-scaled** style objects (same scales as composed **`system.*`** props). Prefer **semantic** color keys so styles track **ColorMode** and theme.
|
|
15
|
+
|
|
16
|
+
For **layout-heavy** styled components, prefer composing **`system.*`** via `variance.compose()` (see **`gamut-system-props`**) instead of re-stating every longhand in `css()`.
|
|
17
|
+
|
|
18
|
+
## `css()` — static style objects
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { css } from '@codecademy/gamut-styles';
|
|
22
|
+
import styled from '@emotion/styled';
|
|
23
|
+
|
|
24
|
+
const Box = styled.div(css({ bg: 'navy-400', p: 4 }));
|
|
25
|
+
const Text = styled.div(css({ color: 'primary', p: 4 }));
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## `variant()` and `states()` — branching and toggles
|
|
29
|
+
|
|
30
|
+
- **`variant()`** — mutually exclusive modes: `base`, `defaultVariant`, and a `variants` map (semantic colors, spacing shorthands, nested selectors such as `'&:hover'`).
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { variant } from '@codecademy/gamut-styles';
|
|
34
|
+
import styled from '@emotion/styled';
|
|
35
|
+
|
|
36
|
+
const Anchor = styled.a(
|
|
37
|
+
variant({
|
|
38
|
+
base: { p: 4 },
|
|
39
|
+
defaultVariant: 'interface',
|
|
40
|
+
variants: {
|
|
41
|
+
interface: {
|
|
42
|
+
color: 'text',
|
|
43
|
+
'&:hover': { color: 'text-accent' },
|
|
44
|
+
},
|
|
45
|
+
inline: {
|
|
46
|
+
color: 'primary',
|
|
47
|
+
'&:hover': { color: 'secondary' },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **`states()`** — independent boolean-style props (`base` + named keys).
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { states } from '@codecademy/gamut-styles';
|
|
58
|
+
import styled from '@emotion/styled';
|
|
59
|
+
|
|
60
|
+
const UtilityBox = styled.div(
|
|
61
|
+
states({
|
|
62
|
+
base: { mx: 4, my: 8, p: 16 },
|
|
63
|
+
disabled: { bg: 'background-disabled', color: 'text-disabled' },
|
|
64
|
+
center: { alignItems: 'center', justifyContent: 'center' },
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `StyleProps` on React components
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { states } from '@codecademy/gamut-styles';
|
|
73
|
+
import { StyleProps } from '@codecademy/variance';
|
|
74
|
+
import styled from '@emotion/styled';
|
|
75
|
+
|
|
76
|
+
const panelShellStates = states({ base: { p: 4 }, dense: { p: 2 } });
|
|
77
|
+
const PanelShell = styled.div(panelShellStates);
|
|
78
|
+
|
|
79
|
+
export type PanelShellProps = StyleProps<typeof panelShellStates> & {
|
|
80
|
+
title: string;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const Panel: React.FC<PanelShellProps> = ({ title, ...rest }) => (
|
|
84
|
+
<PanelShell {...rest}>{title}</PanelShell>
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Prefer **`variant` / `states`** over branching on raw `theme` fields inside styled **template literals** when the output is Emotion-managed CSS.
|
|
89
|
+
|
|
90
|
+
## `useTheme()` — escape hatch
|
|
91
|
+
|
|
92
|
+
Prefer **`css()`**, **`system.*`**, **`variant()`**, and **`states()`** for styling. Use **`useTheme()`** from `@emotion/react` when a token value must be read in **plain JS** (charts, canvas, third-party props), not as the default way to color DOM nodes.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { useTheme } from '@emotion/react';
|
|
96
|
+
|
|
97
|
+
const Sparkline = () => {
|
|
98
|
+
const theme = useTheme();
|
|
99
|
+
return <path strokeWidth={theme.spacing[4]} d="M0 0 L10 10" />;
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Key principles
|
|
104
|
+
|
|
105
|
+
- Prefer **semantic** color keys in `css` / `variant` / `states` so **ColorMode** and theme switches apply; see **`gamut-color-mode`**.
|
|
106
|
+
- Never hardcode hex in component styles — use tokens / semantic aliases.
|
|
107
|
+
- Prefer **`variant` / `states`** for modes and toggles instead of ad-hoc `theme` interpolation in template literals.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
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, list styles, or responsive values from @codecademy/gamut-styles — including composing system prop groups with variance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gamut System Props
|
|
7
|
+
|
|
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:** [`gamut-style-utilities`](../gamut-style-utilities/SKILL.md) (`css`, `variant`, `states`, `StyleProps`). [Styleguide — Best practices](../../../../styleguide/src/lib/Meta/Best%20practices.mdx) (semantic colors, responsive examples) and Storybook [Responsive properties](https://gamut.codecademy.com/storybook/?path=/docs-foundations-system-responsive-properties--page).
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
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`.
|
|
15
|
+
|
|
16
|
+
Each prop group has:
|
|
17
|
+
|
|
18
|
+
- **`properties`**: The CSS properties it controls
|
|
19
|
+
- **`scale`**: Token scale it's restricted to (theme colors, spacing values, etc.)
|
|
20
|
+
- **`transform`**: Optional transform applied before output (e.g. `width={0.5}` → `width: 50%`)
|
|
21
|
+
|
|
22
|
+
## Basic usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import styled from '@emotion/styled';
|
|
26
|
+
import { system } from '@codecademy/gamut-styles';
|
|
27
|
+
|
|
28
|
+
// Apply a single group
|
|
29
|
+
const Box = styled.div(system.layout);
|
|
30
|
+
|
|
31
|
+
// Compose multiple groups
|
|
32
|
+
import { variance } from '@codecademy/variance';
|
|
33
|
+
|
|
34
|
+
const FlexBox = styled.div(
|
|
35
|
+
variance.compose(system.layout, system.flex, system.space)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
<FlexBox display="flex" p={16} gap={8} width="100%" />;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Prop groups
|
|
42
|
+
|
|
43
|
+
### `system.layout`
|
|
44
|
+
|
|
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`.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
const Box = styled.div(system.layout);
|
|
49
|
+
|
|
50
|
+
<Box display="flex" width="50%" height="300px" verticalAlign="middle" />;
|
|
51
|
+
```
|
|
52
|
+
|
|
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).
|
|
54
|
+
|
|
55
|
+
### `system.space`
|
|
56
|
+
|
|
57
|
+
Margin and padding using the theme's spacing scale. Supports logical properties (switches based on `useLogicalProperties` in `<GamutProvider>`).
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
const Box = styled.div(system.space);
|
|
61
|
+
|
|
62
|
+
// Single value
|
|
63
|
+
<Box p={8} m={16} />;
|
|
64
|
+
|
|
65
|
+
// Responsive (array / object — see Responsive values)
|
|
66
|
+
<Box my={[16, 24, 32]} px={[8, 16]} />;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Key props: `p`, `pt`, `pr`, `pb`, `pl`, `px`, `py`, `m`, `mt`, `mr`, `mb`, `ml`, `mx`, `my`
|
|
70
|
+
|
|
71
|
+
### `system.color`
|
|
72
|
+
|
|
73
|
+
Foreground, background, and border colors restricted to the theme's color palette.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const Box = styled.div(system.color);
|
|
77
|
+
|
|
78
|
+
<Box bg="navy" color="gray-900" textColor="gray-100" borderColor="blue" />;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Key props: `color`, `textColor` (both set CSS `color`), `bg`, `borderColor`, plus directional `borderColor*` variants — see `config.ts` for the full set.
|
|
82
|
+
|
|
83
|
+
### `system.typography`
|
|
84
|
+
|
|
85
|
+
Text styling connected to theme typography scales.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
const Text = styled.p(system.typography);
|
|
89
|
+
|
|
90
|
+
<Text
|
|
91
|
+
fontSize={16}
|
|
92
|
+
fontFamily="accent"
|
|
93
|
+
fontStyle="italic"
|
|
94
|
+
textTransform="uppercase"
|
|
95
|
+
lineHeight="base"
|
|
96
|
+
/>;
|
|
97
|
+
```
|
|
98
|
+
|
|
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.
|
|
100
|
+
|
|
101
|
+
### `system.border`
|
|
102
|
+
|
|
103
|
+
Border width, style, radius, and color. Many **logical shorthands** exist (`borderX`, `borderColorY`, `borderRadiusTop`, …); see `config.ts` for the full map.
|
|
104
|
+
|
|
105
|
+
Key props (non-exhaustive): `border`, `borderTop`, `borderRight`, `borderBottom`, `borderLeft`, `borderRadius`, `borderWidth`, `borderStyle`
|
|
106
|
+
|
|
107
|
+
### `system.background`
|
|
108
|
+
|
|
109
|
+
Background image, size, position, and repeat (for images/patterns — use `system.color` for solid background colors).
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import myBg from './myBg.png';
|
|
113
|
+
|
|
114
|
+
const Box = styled.div(system.background);
|
|
115
|
+
|
|
116
|
+
<Box
|
|
117
|
+
background={`url(${myBg})`}
|
|
118
|
+
backgroundSize="cover"
|
|
119
|
+
backgroundPosition="center"
|
|
120
|
+
/>;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Key props: `background`, `backgroundImage`, `backgroundSize`, `backgroundPosition`, `backgroundRepeat`
|
|
124
|
+
|
|
125
|
+
### `system.flex`
|
|
126
|
+
|
|
127
|
+
Flexbox child and container properties.
|
|
128
|
+
|
|
129
|
+
Key props (non-exhaustive): `flex`, `flexDirection`, `flexWrap`, `flexGrow`, `flexShrink`, `flexBasis`, `alignItems`, `alignContent`, `alignSelf`, `justifyContent`, `justifyItems`, `justifySelf`, `gap`, `rowGap`, `columnGap`
|
|
130
|
+
|
|
131
|
+
### `system.grid`
|
|
132
|
+
|
|
133
|
+
CSS Grid container and child properties.
|
|
134
|
+
|
|
135
|
+
Key props (non-exhaustive): `gridTemplateColumns`, `gridTemplateRows`, `gridTemplateAreas`, `gridColumn`, `gridRow`, `gridArea`, `gridAutoFlow`, `gridAutoColumns`, `gridAutoRows`, `gap`, `rowGap`, `columnGap`
|
|
136
|
+
|
|
137
|
+
### `system.positioning`
|
|
138
|
+
|
|
139
|
+
Position and offset properties. Inset shorthands use **`transformSize`**; physical vs logical edges follow **`useLogicalProperties`**.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
const Overlay = styled.div(variance.compose(system.layout, system.positioning));
|
|
143
|
+
|
|
144
|
+
<Overlay position="absolute" top={0} left={0} width="100%" height="100%" />;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Key props: `position`, `inset`, `top`, `right`, `bottom`, `left`, `zIndex`, `opacity`
|
|
148
|
+
|
|
149
|
+
### `system.shadow`
|
|
150
|
+
|
|
151
|
+
Box and text shadow.
|
|
152
|
+
|
|
153
|
+
Key props: `boxShadow`, `textShadow`
|
|
154
|
+
|
|
155
|
+
### `system.list`
|
|
156
|
+
|
|
157
|
+
List marker styling (`listStyle`, `listStyleType`, `listStylePosition`, `listStyleImage`). Included on **`Box`** alongside the other composed groups.
|
|
158
|
+
|
|
159
|
+
## Responsive values
|
|
160
|
+
|
|
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`.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<Box width={{ _: '100%', sm: '50%', md: '33%' }} px={{ _: 8, md: 16 }} />
|
|
169
|
+
```
|
|
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
|
+
|
|
181
|
+
## Using `css()` for styled definitions
|
|
182
|
+
|
|
183
|
+
For static styles in styled components, use **`css()`** from `@codecademy/gamut-styles` (same implementation as **`system.css`** on the `system` namespace).
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import { css } from '@codecademy/gamut-styles';
|
|
187
|
+
import styled from '@emotion/styled';
|
|
188
|
+
|
|
189
|
+
// Static color using raw token
|
|
190
|
+
const Box = styled.div(css({ bg: 'navy-400', p: 4 }));
|
|
191
|
+
|
|
192
|
+
// Semantic color (adapts to color mode)
|
|
193
|
+
const Text = styled.div(css({ color: 'primary', p: 4 }));
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Key principles
|
|
197
|
+
|
|
198
|
+
- Compose `system.*` groups via `variance.compose()` — don't apply multiple groups by chaining `styled.div(system.a)(system.b)`.
|
|
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).
|