panelize 0.0.0-stage → 0.1.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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +617 -2
- package/dist/index.d.cts +1815 -0
- package/dist/index.d.ts +1815 -0
- package/dist/panelize.cjs +858 -0
- package/dist/panelize.cjs.map +1 -0
- package/dist/panelize.es.js +3011 -0
- package/dist/panelize.es.js.map +1 -0
- package/docs/accessibility.md +43 -0
- package/docs/components.md +61 -0
- package/docs/composition.md +138 -0
- package/docs/controls.md +47 -0
- package/docs/forms.md +64 -0
- package/docs/layout.md +59 -0
- package/docs/recipes.md +170 -0
- package/docs/shell.md +57 -0
- package/docs/text.md +26 -0
- package/docs/theme.md +121 -0
- package/examples/AGENTS.md +9 -0
- package/examples/cursor-rule.mdc +17 -0
- package/examples/panelize.theme.md +312 -0
- package/llms-full.txt +1206 -0
- package/llms.txt +51 -0
- package/package.json +105 -4
package/llms-full.txt
ADDED
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
<!-- llms.txt -->
|
|
2
|
+
|
|
3
|
+
# Panelize
|
|
4
|
+
|
|
5
|
+
> A React layout kit. Named boxes and type are the map of the screen. `createPanelizeTheme` + `PanelizeProvider` + `usePanelizeTheme` standardize paint. Forms are bonus.
|
|
6
|
+
|
|
7
|
+
This file is for agents **building apps and custom components with Panelize**. Maintainers of this repo: [CONTRIBUTING.md](CONTRIBUTING.md) and [AGENTS.md](AGENTS.md). After `npm install panelize`, read `node_modules/panelize/llms.txt` (or the package export `panelize/llms.txt`). One-file dump: [llms-full.txt](llms-full.txt).
|
|
8
|
+
|
|
9
|
+
Import from `'panelize'`. Wrap the host once with `PanelizeProvider`. Do not take over the host’s styled-components `ThemeProvider`. Do not set `html` / `body` from the library. Host CSS must give the shell a height (`html`, `body`, `#root { height: 100% }` for a filled app).
|
|
10
|
+
|
|
11
|
+
## How to compose (strict)
|
|
12
|
+
|
|
13
|
+
Read [docs/composition.md](docs/composition.md) before writing JSX. Summary:
|
|
14
|
+
|
|
15
|
+
- `Panels` is the **outer shell** of a view. Direct children are only `Panel` (one `Panel` is allowed).
|
|
16
|
+
- `Panel` is a **section**. It may contain tracks, `Div`, type, controls, nested `Panels`, and custom components. It must not contain a stray `Row`, `Col`, or `Cell`.
|
|
17
|
+
- Pairing is bidirectional. `Row` exists only as a direct child of `Rows`. `Col` only under `Cols`. `Cell` only under `Grid`. `Panel` only under `Panels`.
|
|
18
|
+
- `Rows` / `Cols` / `Grid` need **two or more** of their item. One band → put it in the `Panel` or a `Div`.
|
|
19
|
+
- The **wrapper** owns place (`alignX` / `alignY`, `flex`, `fill`, `gap`, size). Nested controls and custom components do not position themselves.
|
|
20
|
+
- Visible copy goes in `Text` / `Heading` / `Span` / `TextLink` / `Label` so it inherits the nearest `Panel` text tokens.
|
|
21
|
+
|
|
22
|
+
The library does not warn at runtime if a host ignores this. Follow it anyway — that is how the tree stays readable.
|
|
23
|
+
|
|
24
|
+
## Core
|
|
25
|
+
|
|
26
|
+
- [Composition](docs/composition.md): pairing, who positions, good and bad trees, which primitive to use
|
|
27
|
+
- [Recipes](docs/recipes.md): copyable page, sidebar, centered card, form, dashboard grid, header/body/footer
|
|
28
|
+
- [Components](docs/components.md): every export, element, parent, and value API on one page
|
|
29
|
+
- Prop reference: `node_modules/panelize/dist/index.d.ts` (every prop is documented in JSDoc; read the type, not a guess)
|
|
30
|
+
- [Shell (`Panels` / `Panel`)](docs/shell.md): page vs split, `fill`, `scroll`, `horizontal`, `stack`
|
|
31
|
+
- [Layout vocabulary](docs/layout.md): `fill` vs `flex`, `gap`, `wrap`, `stack`, `Div`, `Grid`
|
|
32
|
+
- [Controls](docs/controls.md): per-control behavior notes (chrome, defaults, edge cases)
|
|
33
|
+
- [Accessibility](docs/accessibility.md): names, structure, keyboard, roles, focus
|
|
34
|
+
- [Text](docs/text.md): `Text`, `Heading`, `Span`, `TextLink`, `Label`
|
|
35
|
+
- [Theme and hooks](docs/theme.md): `createPanelizeTheme`, `PanelizeProvider`, `usePanelizeTheme`
|
|
36
|
+
|
|
37
|
+
## Bonus
|
|
38
|
+
|
|
39
|
+
- [Forms, buttons, chips](docs/forms.md): controls on top of the layout core, value ownership, `Select` / `MultiSelect`, validation
|
|
40
|
+
|
|
41
|
+
## Full context
|
|
42
|
+
|
|
43
|
+
- [llms-full.txt](llms-full.txt): this index, every `docs/` page, and the theme keys in one file
|
|
44
|
+
|
|
45
|
+
## Host setup files
|
|
46
|
+
|
|
47
|
+
- [examples/cursor-rule.mdc](examples/cursor-rule.mdc): drop into a host app’s `.cursor/rules/`
|
|
48
|
+
- [examples/AGENTS.md](examples/AGENTS.md): drop into a host app’s `AGENTS.md`
|
|
49
|
+
|
|
50
|
+
## Human docs
|
|
51
|
+
|
|
52
|
+
- [README.md](README.md): install, quick start, fonts, goals, usage, vocabulary, caveats
|
|
53
|
+
- [examples/panelize.theme.md](examples/panelize.theme.md): every key `createPanelizeTheme` accepts
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
<!-- docs/composition.md -->
|
|
58
|
+
|
|
59
|
+
# Composition
|
|
60
|
+
|
|
61
|
+
Build views and custom components from layout primitives. The JSX should read as the picture without opening the browser.
|
|
62
|
+
|
|
63
|
+
## Pairing (bidirectional)
|
|
64
|
+
|
|
65
|
+
| Item | Direct parent | That parent’s direct children |
|
|
66
|
+
| ------- | ------------- | ----------------------------- |
|
|
67
|
+
| `Panel` | `Panels` | only `Panel` (one is allowed) |
|
|
68
|
+
| `Row` | `Rows` | only `Row`, **two or more** |
|
|
69
|
+
| `Col` | `Cols` | only `Col`, **two or more** |
|
|
70
|
+
| `Cell` | `Grid` | only `Cell`, **two or more** |
|
|
71
|
+
|
|
72
|
+
`Row` must not sit on `Panel`, `Div`, `Col`, `Cell`, or `Grid`. Same idea for `Col` and `Cell`. Fragments and `.map` still have to produce those children.
|
|
73
|
+
|
|
74
|
+
Custom components are **not** substitutes for `Row` / `Col` / `Cell`. Build them from primitives, then place the custom component inside a `Row`, `Col`, `Cell`, `Panel`, or `Div`.
|
|
75
|
+
|
|
76
|
+
## Shell → sections → tracks
|
|
77
|
+
|
|
78
|
+
1. `Panels` — outer shell of the view (`fill`, `scroll`, column vs `horizontal`).
|
|
79
|
+
2. `Panel` — sections (surfaces). One is fine. Several for nav + main, or stacked regions.
|
|
80
|
+
3. Inside a `Panel`: `Rows` / `Cols` / `Grid` / `Div` / nested `Panels` / type / controls / custom components.
|
|
81
|
+
|
|
82
|
+
A `Panel` is an **open** section. It is not a homogeneous track.
|
|
83
|
+
|
|
84
|
+
If there is only one band of content, do not wrap it in `Rows` of one `Row` (or `Cols` of one `Col`, or `Grid` of one `Cell`). Put that content in the `Panel` or a `Div`.
|
|
85
|
+
|
|
86
|
+
## Who positions
|
|
87
|
+
|
|
88
|
+
The wrapper layout box owns place. Set `alignX` / `alignY`, `flex`, `fill`, `gap`, `width` / `height` on `Panels`, `Panel`, `Rows`, `Row`, `Cols`, `Col`, `Grid`, `Cell`, or `Div` — not on `Text`, `Button`, or a custom widget so it can “nudge itself.”
|
|
89
|
+
|
|
90
|
+
`Text` / `Heading` / `Span` / `TextLink` / `Label` are not layout shells (no `fill` / `flex` / padding). Controls may take `fill` to stretch the control itself; the parent still decides where the control sits.
|
|
91
|
+
|
|
92
|
+
## Type
|
|
93
|
+
|
|
94
|
+
Visible writing uses `Text`, `Heading`, `Span`, `TextLink`, or `Label`. Bare strings in a `Panel` / `Row` / `Col` / `Div` do not pick up `--panelize-text` / muted / highlighted.
|
|
95
|
+
|
|
96
|
+
## Good
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<Panels fill scroll='vertical'>
|
|
100
|
+
<Panel variant='muted' minHeight='100%'>
|
|
101
|
+
<Rows minHeight='100%' gap='base' alignX='center' alignY='center'>
|
|
102
|
+
<Row>
|
|
103
|
+
<Heading level={1} color='primary'>
|
|
104
|
+
Panelize
|
|
105
|
+
</Heading>
|
|
106
|
+
</Row>
|
|
107
|
+
<Row>
|
|
108
|
+
<Text muted>A React panel layout library.</Text>
|
|
109
|
+
</Row>
|
|
110
|
+
<Row>
|
|
111
|
+
<Cols gap='small'>
|
|
112
|
+
<Col>
|
|
113
|
+
<Button label='Deploy now' />
|
|
114
|
+
</Col>
|
|
115
|
+
<Col>
|
|
116
|
+
<Button outline label='Details' />
|
|
117
|
+
</Col>
|
|
118
|
+
</Cols>
|
|
119
|
+
</Row>
|
|
120
|
+
</Rows>
|
|
121
|
+
</Panel>
|
|
122
|
+
</Panels>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Column of rows, last row is two columns of buttons. The `Cols` gap and the `Row` place the buttons; the buttons do not.
|
|
126
|
+
|
|
127
|
+
One section, one heading — no `Rows`:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<Panels fill>
|
|
131
|
+
<Panel variant='base' padding='base'>
|
|
132
|
+
<Heading level={2}>Account</Heading>
|
|
133
|
+
</Panel>
|
|
134
|
+
</Panels>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Bad
|
|
138
|
+
|
|
139
|
+
Each block is wrong. The comment says why.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
// Row is not a direct child of Rows
|
|
143
|
+
<Panel>
|
|
144
|
+
<Row>
|
|
145
|
+
<Text>Hello</Text>
|
|
146
|
+
</Row>
|
|
147
|
+
</Panel>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
// Rows of one Row: put the content in the Panel or a Div
|
|
152
|
+
<Rows>
|
|
153
|
+
<Row>
|
|
154
|
+
<Text>Hello</Text>
|
|
155
|
+
</Row>
|
|
156
|
+
</Rows>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// Col is not a Rows child
|
|
161
|
+
<Rows>
|
|
162
|
+
<Col>
|
|
163
|
+
<Text>Hello</Text>
|
|
164
|
+
</Col>
|
|
165
|
+
<Row>
|
|
166
|
+
<Text>World</Text>
|
|
167
|
+
</Row>
|
|
168
|
+
</Rows>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
// Copy is not in a type primitive, so it misses --panelize-text
|
|
173
|
+
<Panel>Hello</Panel>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
// A control positions itself. Button has no alignX
|
|
178
|
+
<Col>
|
|
179
|
+
<Button alignX='center' label='Save' />
|
|
180
|
+
</Col>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Center with `alignX` on the wrapper (`Cols`, `Row`, `Panel`), not on `Button`.
|
|
184
|
+
|
|
185
|
+
## Which primitive?
|
|
186
|
+
|
|
187
|
+
| You want | Use |
|
|
188
|
+
| --------------------------- | -------------------------------------------------- |
|
|
189
|
+
| The outer frame of a view | `Panels` |
|
|
190
|
+
| A surface (nav, main, card) | `Panel` |
|
|
191
|
+
| A vertical list of bands | `Rows` of two or more `Row` |
|
|
192
|
+
| A horizontal list of items | `Cols` of two or more `Col` |
|
|
193
|
+
| A two-dimensional layout | `Grid` of two or more `Cell` |
|
|
194
|
+
| One wrapper or one band | `Div`, or the `Panel` itself |
|
|
195
|
+
| Words on screen | `Text` / `Heading` / `Span` / `TextLink` / `Label` |
|
|
196
|
+
| Copyable starting points | [recipes](recipes.md) |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
<!-- docs/shell.md -->
|
|
201
|
+
|
|
202
|
+
# Shell (`Panels` / `Panel`)
|
|
203
|
+
|
|
204
|
+
`Panels` is the outer shell of a view. Setting it up well matters more than any control.
|
|
205
|
+
|
|
206
|
+
Default `Panels` is an **absolute** column (`inset` when `fill`). `relative` opt-in. `horizontal` is a row (sidebar + main). Overlay shells belong on `Panels`, not `Rows fill`.
|
|
207
|
+
|
|
208
|
+
Direct children: only `Panel`. **One `Panel` is allowed.** Nested shells: a `Panel` may contain another `Panels`.
|
|
209
|
+
|
|
210
|
+
`Panel` is a section (surface). `variant` is `muted` / `base` / `highlighted` (default `base`). It publishes `--panelize-bg`, `--panelize-text`, `--panelize-text-muted`, `--panelize-text-highlighted`, `--panelize-border`. Nested panels and children share those. `dark` / `light` are schemes on the provider, not panel variants.
|
|
211
|
+
|
|
212
|
+
## Host height
|
|
213
|
+
|
|
214
|
+
A filled shell needs a viewport-tall host:
|
|
215
|
+
|
|
216
|
+
```css
|
|
217
|
+
html,
|
|
218
|
+
body,
|
|
219
|
+
#root {
|
|
220
|
+
height: 100%;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The library does not set `html` / `body`. For a long in-flow document, drop host `height` and use `min-height: 100%`.
|
|
225
|
+
|
|
226
|
+
## Page (scroll the shell)
|
|
227
|
+
|
|
228
|
+
Default column — do not add `horizontal`. Do not put `fill` on the inner `Panel` (that locks height). Use `minHeight='100%'` so a short window still paints. Do not center on the scrolling `Panels`; center on inner `Rows`.
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
<Panels fill scroll='vertical'>
|
|
232
|
+
<Panel variant='muted' minHeight='100%' padding='base'>
|
|
233
|
+
<Heading level={1}>Title</Heading>
|
|
234
|
+
<Text muted>Long page copy…</Text>
|
|
235
|
+
</Panel>
|
|
236
|
+
</Panels>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Split (scroll the pane)
|
|
240
|
+
|
|
241
|
+
`horizontal` + `fill` on `Panels`. `scroll` on the pane `Panel`, not the outer shell. Sidebar: `width`. Main: `flex` (not `fill` — `fill` fights the sidebar). `stack` on this row: **at most** this wide → column (not Tailwind `md:` and-up).
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
<Panels horizontal fill stack='sm'>
|
|
245
|
+
<Panel variant='muted' width='12rem'>
|
|
246
|
+
<Text>Nav</Text>
|
|
247
|
+
</Panel>
|
|
248
|
+
<Panel variant='base' flex scroll='vertical'>
|
|
249
|
+
<Text>Main</Text>
|
|
250
|
+
</Panel>
|
|
251
|
+
</Panels>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## `scroll`
|
|
255
|
+
|
|
256
|
+
Only on `Panels` and `Panel`. `vertical` clips x. `horizontal` / `both` need a child actually wider than the box (`width` / `minWidth` on children). Nested `scroll` in a split needs `flex` on the pane (`min-width` / `min-height` 0) so it can shrink.
|
|
257
|
+
|
|
258
|
+
`Panel` / `Row` / `Col` / `Div` are `flex-shrink: 0` until `flex`.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
<!-- docs/layout.md -->
|
|
263
|
+
|
|
264
|
+
# Layout vocabulary
|
|
265
|
+
|
|
266
|
+
Space / size / border tokens: `none` | `extraSmall` | `small` | `base` | `large` | `extraLarge`. Breakpoints: `sm` | `md` | `lg`. Token or CSS length — not a unitless number. Boxes are `border-box`.
|
|
267
|
+
|
|
268
|
+
| Prop | Meaning |
|
|
269
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
270
|
+
| `fill` | Size to parent. Absolute `Panels`: `inset: 0`. Else height / width 100%. `Row` theme height `auto` → width 100% only. `Col` theme width `auto` → height 100% only. Explicit `width` / `height` win. Not on `Cell`. |
|
|
271
|
+
| `width` / `height` | All layout except `Cell`. `Div` omit = unset. |
|
|
272
|
+
| `flex` | `flex: 1 1 0%` plus `min-width` / `min-height` 0 (nested `scroll` can shrink). Not `fill`. `Panel` / `Row` / `Col` / `Div` are `flex-shrink: 0` until `flex`. |
|
|
273
|
+
| `alignX` / `alignY` | Screen axes. Set on the **wrapper**. Track omit: flex cross `stretch` / main `start`; `Grid` `stretch` / `stretch`. Child omit: inherit. |
|
|
274
|
+
| `gap` | `Panels`, `Rows`, `Cols`, `Grid`. |
|
|
275
|
+
| `wrap` | `Rows` / `Cols`. Not `stack`. Not `Grid`. |
|
|
276
|
+
| `stack` | `horizontal` `Panels` / `Cols` / `Grid`. Container query, **at most** this wide → column. |
|
|
277
|
+
| `stackCols` | `Grid` + `stack`: column count while stacked. Default 1. |
|
|
278
|
+
| `numCols` / `numRows` | `Grid` only. Equal `minmax(0, 1fr)` tracks. |
|
|
279
|
+
| `spanX` / `spanY` | `Cell` only. Default 1. `stack` forces `spanX` to 1 when `stackCols` is 1. |
|
|
280
|
+
| `scroll` | `Panels` / `Panel` only. See [shell](shell.md). |
|
|
281
|
+
| `relative` / `horizontal` | `Panels` only. |
|
|
282
|
+
|
|
283
|
+
## `Div`
|
|
284
|
+
|
|
285
|
+
Catch-all box (spacing, size, border, `fill` / `flex`). No background or color of its own. Use it when you need one wrapper and a `Rows` of one `Row` would be wrong. Direct children: anything except a stray `Row` / `Col` / `Cell` (those still belong under their tracks).
|
|
286
|
+
|
|
287
|
+
## Tracks
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
<Rows gap='small' alignX='center'>
|
|
291
|
+
<Row>
|
|
292
|
+
<Text>One</Text>
|
|
293
|
+
</Row>
|
|
294
|
+
<Row>
|
|
295
|
+
<Text>Two</Text>
|
|
296
|
+
</Row>
|
|
297
|
+
</Rows>
|
|
298
|
+
|
|
299
|
+
<Cols stack='sm' gap='small'>
|
|
300
|
+
<Col width='12rem'>
|
|
301
|
+
<Text>Side</Text>
|
|
302
|
+
</Col>
|
|
303
|
+
<Col flex>
|
|
304
|
+
<Text>Main</Text>
|
|
305
|
+
</Col>
|
|
306
|
+
</Cols>
|
|
307
|
+
|
|
308
|
+
<Grid numCols={3} stackCols={2} gap='small' stack='sm'>
|
|
309
|
+
<Cell>
|
|
310
|
+
<Text>A</Text>
|
|
311
|
+
</Cell>
|
|
312
|
+
<Cell spanX={2}>
|
|
313
|
+
<Text>B</Text>
|
|
314
|
+
</Cell>
|
|
315
|
+
</Grid>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`Cell` has no `width` / `height` / `fill`. Size comes from the grid tracks and `spanX` / `spanY`. `Cell` is `min-width` / `min-height` 0 so content can shrink.
|
|
319
|
+
|
|
320
|
+
## Borders
|
|
321
|
+
|
|
322
|
+
Every layout box. Width cascade like padding: `borderWidth` → `borderX` / `Y` → side. `borderColor` is surface, brand, or status — not a `Panel` `variant`. Default width `none`.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
<!-- docs/text.md -->
|
|
327
|
+
|
|
328
|
+
# Text
|
|
329
|
+
|
|
330
|
+
Visible copy must use a type primitive so it inherits the nearest `Panel` (`--panelize-text`, muted, highlighted) and theme type tokens.
|
|
331
|
+
|
|
332
|
+
| Component | Element | Notes |
|
|
333
|
+
| ---------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
334
|
+
| `Text` | `p` | Body. Omit color → panel `text.base`. |
|
|
335
|
+
| `Heading` | `h1`–`h6` | Required `level` `1`–`6`. Omit `size` → theme heading size for that level. Omit `weight` → `bold`. Omit `family` → `display`. |
|
|
336
|
+
| `Span` | `span` | Nested in `Text` / `Span`: omit size/weight/`family` → inherit. Omit color → CSS `inherit`. |
|
|
337
|
+
| `TextLink` | `a` | Default chroma `primary`. Nested omit size/weight/`family` → inherit. |
|
|
338
|
+
| `Label` | `label` | Caption for controls. Prefer `label` prop; `htmlFor` + control `id`. Wrap `Switch` / `Checkbox` / `Radio`. |
|
|
339
|
+
|
|
340
|
+
None of these is a layout shell: no `fill` / `flex` / padding. Gap belongs on `Rows` / `Cols` / `Grid` / `Div`.
|
|
341
|
+
|
|
342
|
+
`muted` / `highlighted` use the panel `text` object. `color='primary'` / `secondary` / `accent` → `brand.*.base`. `color='danger'` / `warning` / `success` / `info` → `status.*.base`. Raw CSS `color` wins. If both flags, `highlighted` wins.
|
|
343
|
+
|
|
344
|
+
Size: `fontSizes` tokens or CSS. Weight: omit on `Text` = `base` (400). Family: `base` / `display` / `mono`. The library does not load font files.
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
<Heading level={1} uppercase color='primary'>
|
|
348
|
+
Panelize
|
|
349
|
+
</Heading>
|
|
350
|
+
<Text muted size='small'>
|
|
351
|
+
Nested <Span weight='bold'>bold</Span> and <TextLink href='/docs'>Docs</TextLink>
|
|
352
|
+
</Text>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
<!-- docs/theme.md -->
|
|
358
|
+
|
|
359
|
+
# Theme and hooks
|
|
360
|
+
|
|
361
|
+
Appearance comes from the theme, not one-off color props on every box. That is how views stay standardized.
|
|
362
|
+
|
|
363
|
+
## `createPanelizeTheme`
|
|
364
|
+
|
|
365
|
+
Author schemes, surfaces, brand, type, and component defaults. Export a **stable** object. Omit any key to keep the library default. Full keys: [examples/panelize.theme.md](../examples/panelize.theme.md). Frozen gray bank: `palette` from `'panelize'` (not a theme key).
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { createPanelizeTheme, palette } from 'panelize'
|
|
369
|
+
|
|
370
|
+
export const panelizeTheme = createPanelizeTheme({
|
|
371
|
+
colors: {
|
|
372
|
+
dark: {
|
|
373
|
+
app: { background: palette.gray.extraDark, text: palette.gray.extraLight },
|
|
374
|
+
muted: {
|
|
375
|
+
background: palette.gray.dark,
|
|
376
|
+
text: { base: palette.white, muted: palette.gray.base, highlighted: palette.white },
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
brand: { primary: '#ff9f2e' },
|
|
381
|
+
})
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`colors: { dark, light }` are schemes. After create, **`theme.colors` is the active slice** (`muted` / `base` / `highlighted` / `app`) — not `{ dark, light }`. Both maps are `theme.colorSchemes`. Surfaces belong under a scheme (`colors.dark.muted`). A top-level `colors.muted` is ignored. `dark` is a scheme, not a panel variant.
|
|
385
|
+
|
|
386
|
+
Brand (`primary` / `secondary` / `accent`) and status (`danger` / `warning` / `success` / `info`) are global, shared across schemes. Library `secondary` / `accent` follow `primary` until set. Status does not follow `primary`. Hosts set their brand kit here; the library defaults stay neutral.
|
|
387
|
+
|
|
388
|
+
## `PanelizeProvider`
|
|
389
|
+
|
|
390
|
+
Puts that object on the tree and wraps a canvas (`color-scheme` from `mode`; optional `app` paint). `mode` (`dark` default) selects the slice. Hosts own the toggle — there is no library toggle. `prefers-color-scheme` is host-side.
|
|
391
|
+
|
|
392
|
+
Do **not** take over the host’s styled-components `ThemeProvider`. Do **not** set `html` / `body`.
|
|
393
|
+
|
|
394
|
+
Without a provider, components still render using `defaultTheme` (`mode` `dark`). Always wrap a real host.
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
<PanelizeProvider theme={panelizeTheme} mode={mode}>
|
|
398
|
+
{children}
|
|
399
|
+
</PanelizeProvider>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## `usePanelizeTheme`
|
|
403
|
+
|
|
404
|
+
**Use this in views and custom components** that need colors, space, or type from the kit. `.colors` is the current scheme’s surfaces. `.mode` is `'dark'` | `'light'`. `.brand` / `.status` are the shared slots. `.space` / `.fontSizes` / … are the scales.
|
|
405
|
+
|
|
406
|
+
Do not invent a parallel palette in a custom component. Read the theme (or inherit `--panelize-*` from the nearest `Panel`). Nested `Panel` `variant` changes the CSS variables for descendants.
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
function Stat({ label, value }: { label: string; value: string }) {
|
|
410
|
+
const { colors } = usePanelizeTheme()
|
|
411
|
+
return (
|
|
412
|
+
<Div padding='small' borderWidth='extraSmall' borderColor={colors.base.border}>
|
|
413
|
+
<Text muted size='small'>
|
|
414
|
+
{label}
|
|
415
|
+
</Text>
|
|
416
|
+
<Text highlighted>{value}</Text>
|
|
417
|
+
</Div>
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Place `Stat` in a `Col` / `Row` / `Cell` / `Panel`. `Stat` does not take `alignX`.
|
|
423
|
+
|
|
424
|
+
## Recipes
|
|
425
|
+
|
|
426
|
+
Author the theme once in the host, export a stable object, and pass `mode` from wherever the host keeps its color-scheme state. The library has no toggle and does not read `prefers-color-scheme`.
|
|
427
|
+
|
|
428
|
+
**Brand kit.** `secondary` and `accent` follow `primary` until set. Status colors do not follow brand.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
export const panelizeTheme = createPanelizeTheme({
|
|
432
|
+
brand: {
|
|
433
|
+
primary: '#6d5dfc',
|
|
434
|
+
secondary: '#00b8a9',
|
|
435
|
+
accent: { base: '#f9c80e', hover: '#ffd84d' },
|
|
436
|
+
},
|
|
437
|
+
status: { danger: '#d64550' },
|
|
438
|
+
})
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Both schemes.** Author `dark` and `light` surfaces together. Omit any surface to keep the default.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
createPanelizeTheme({
|
|
445
|
+
colors: {
|
|
446
|
+
dark: { app: { background: '#0e0e10', text: '#ececec' } },
|
|
447
|
+
light: {
|
|
448
|
+
app: { background: '#ffffff', text: '#1a1a1a' },
|
|
449
|
+
muted: {
|
|
450
|
+
background: '#f3f3f5',
|
|
451
|
+
text: { base: '#1a1a1a', muted: '#6b6b73', highlighted: '#000000' },
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
})
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Fonts and type scale.** The library does not load font files. Point the tokens at what the host loads.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
createPanelizeTheme({
|
|
462
|
+
fontFamilies: { base: 'Inter, sans-serif', display: '"Fraunces", serif' },
|
|
463
|
+
fontSizes: { base: '1.0625rem' },
|
|
464
|
+
})
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Component defaults.** Change a control everywhere instead of on every instance. `defaults.textarea` and `defaults.select` start from the merged `defaults.input`.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
createPanelizeTheme({
|
|
471
|
+
defaults: {
|
|
472
|
+
button: { borderRadius: 'small', paddingX: 'large' },
|
|
473
|
+
input: { borderRadius: 'small' },
|
|
474
|
+
select: { listMaxHeight: '12rem' },
|
|
475
|
+
},
|
|
476
|
+
})
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**Custom component on the theme.** Read `usePanelizeTheme` or inherit `--panelize-*` from the nearest `Panel`; do not hard-code hexes. See `Stat` above.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
<!-- docs/forms.md -->
|
|
484
|
+
|
|
485
|
+
# Forms, buttons, chips (bonus)
|
|
486
|
+
|
|
487
|
+
Core is layout + text. These controls exist so a host can ship a complete kit. Still compose them **inside** `Panel` / `Row` / `Col` / `Cell` / `Div`. The wrapper places them.
|
|
488
|
+
|
|
489
|
+
Per-control behavior notes: [controls](controls.md). Full prop list: exported types in `node_modules/panelize/dist/index.d.ts` (JSDoc on every prop). Human detail: [README.md](../README.md). Copyable field layout: [recipes](recipes.md#form-fields). Keyboard and labelling rules: [accessibility](accessibility.md).
|
|
490
|
+
|
|
491
|
+
- `Button` — `label` preferred; `children` win. `color` is the chroma slot. `outline` stays hollow. `fill` stretches the button, not a panel.
|
|
492
|
+
- `Chip` — badge, not a button. No hover.
|
|
493
|
+
- `Input` / `NumberInput` / `Textarea` — field chrome. Instance `color` is **focus** chroma, not glyph ink (`ink`). Prefer `Label` + `htmlFor`.
|
|
494
|
+
- `Select` / `MultiSelect` — custom listboxes, not native `<select>`. Host `value` / `onChange`. `MultiSelect` is a count in the field (`N selected`), not chips.
|
|
495
|
+
- `Switch` / `Checkbox` / `Radio` — wrap with `Label` (same `size`). Empty checkbox/radio stroke follows the label (`currentColor`).
|
|
496
|
+
- `Label` — type-like ink; not a layout shell.
|
|
497
|
+
|
|
498
|
+
Do not add `alignX` / `flex` / padding-as-layout on these to position them. Use a `Col` / `Row` / `Div`.
|
|
499
|
+
|
|
500
|
+
## Value ownership
|
|
501
|
+
|
|
502
|
+
The host owns every value. Nothing here keeps state you did not ask for.
|
|
503
|
+
|
|
504
|
+
| Control | Controlled | Uncontrolled |
|
|
505
|
+
| --------------------- | ----------------------------------------------------------------------------------------------- | ---------------- |
|
|
506
|
+
| `Input` etc. | `value` + `onChange(event)` | `defaultValue` |
|
|
507
|
+
| `NumberInput` | `value` + `onChange(event)`; read `event.currentTarget.valueAsNumber` (the `value` is a string) | `defaultValue` |
|
|
508
|
+
| `Select` | `value: string` + `onChange(value: string)` | not offered |
|
|
509
|
+
| `MultiSelect` | `value: string[]` + `onChange(value: string[])` | not offered |
|
|
510
|
+
| `Switch` / `Checkbox` | `checked` + `onChange(event)` | `defaultChecked` |
|
|
511
|
+
| `Radio` | `name` + `value` + `checked` + `onChange(event)` | `defaultChecked` |
|
|
512
|
+
|
|
513
|
+
`Select` / `MultiSelect` call `onChange` with plain values, not events. Cleared is `''` (`Select`) or `[]` (`MultiSelect`). A `value` that has no matching option is still shown.
|
|
514
|
+
|
|
515
|
+
## `Select` and `MultiSelect`
|
|
516
|
+
|
|
517
|
+
```tsx
|
|
518
|
+
<Label htmlFor='country' label='Country' />
|
|
519
|
+
<Select id='country' value={country} onChange={setCountry} placeholder='Choose' clearable>
|
|
520
|
+
<Option value='us'>United States</Option>
|
|
521
|
+
<Option value='mx' disabled>Mexico</Option>
|
|
522
|
+
</Select>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
- Options: `<Option value>` children and/or `options={[{ value, label, disabled? }]}`. Children win on a duplicate `value`.
|
|
526
|
+
- The menu renders in a portal on `document.body` (`position: fixed`). A scrolling `Panels` does not clip it. It sits at `defaults.select.listZIndex` (default 1100); raise that in the theme if a host overlay covers it.
|
|
527
|
+
- `clearable` shows ×. `required` blocks an empty value and disables clearing. `selectedAtTop` lists the selected option(s) first. `Select` also has `emptyOption`. `readOnly` opens to inspect without changing.
|
|
528
|
+
- `name` writes hidden input(s) so a native `<form>` submits the value. `MultiSelect` writes one input per value.
|
|
529
|
+
- `open` / `onOpenChange` control the menu. Omit for internal state.
|
|
530
|
+
- `MultiSelect` stays open while toggling and shows `N selected`. The caption is English-only.
|
|
531
|
+
- No native fallback, optgroups, or typing into the field.
|
|
532
|
+
|
|
533
|
+
## `Switch`, `Checkbox`, `Radio`
|
|
534
|
+
|
|
535
|
+
Wrap the control in a `Label` and give both the same `size`. A sibling caption works only with `htmlFor`, and a `disabled` control then cannot dim its label.
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
<Label size='small'>
|
|
539
|
+
<Checkbox size='small' checked={agree} onChange={(e) => setAgree(e.currentTarget.checked)} /> I
|
|
540
|
+
agree
|
|
541
|
+
</Label>
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
`Checkbox` supports `indeterminate` (the native property; it does not set `checked`). A `Radio` group shares `name`; the host decides which `value` is checked.
|
|
545
|
+
|
|
546
|
+
## Validation
|
|
547
|
+
|
|
548
|
+
`Input` / `NumberInput` / `Textarea` / `Select` paint `status.danger` for `:user-invalid` (after the user edits) and for `aria-invalid='true'`. Set `aria-invalid` yourself for submit-time errors. Show the message in a `Text color='danger'` and tie it to the field with `aria-describedby`.
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
<!-- docs/accessibility.md -->
|
|
553
|
+
|
|
554
|
+
# Accessibility
|
|
555
|
+
|
|
556
|
+
What Panelize does for you, and what the host must still do.
|
|
557
|
+
|
|
558
|
+
## Names
|
|
559
|
+
|
|
560
|
+
Every control needs an accessible name. Use a `Label` with `htmlFor` matching the control `id`, or wrap the control in a `Label`. With no visible text, use `aria-label`. A `placeholder` is not a name.
|
|
561
|
+
|
|
562
|
+
- `Input` / `NumberInput` / `Textarea` / `Select` / `MultiSelect`: `Label htmlFor` + `id`, else `aria-label`.
|
|
563
|
+
- `Switch` / `Checkbox` / `Radio`: wrap in a `Label` (same `size`). These have no `label` prop.
|
|
564
|
+
- `Button`: `label`, or `children` with text. Icon-only: `aria-label`.
|
|
565
|
+
- `Chip` is not interactive. Do not use it as a button.
|
|
566
|
+
|
|
567
|
+
## Structure
|
|
568
|
+
|
|
569
|
+
- `Heading` needs `level` and renders `h1`–`h6`. Pick the level for the document outline, not for size (`size` is separate).
|
|
570
|
+
- `Panels` / `Panel` / `Rows` / `Cols` / `Grid` are plain `div`s. They add no landmarks or roles. Wrap them in `header` / `nav` / `main` / `section` in the host.
|
|
571
|
+
- Copy goes in `Text` / `Heading` / `Span` / `TextLink` / `Label` (real `p` / `h*` / `span` / `a` / `label`).
|
|
572
|
+
|
|
573
|
+
## Keyboard
|
|
574
|
+
|
|
575
|
+
| Control | Keys |
|
|
576
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
577
|
+
| `Select` | Arrows move, Enter / Space pick, Home / End, type-ahead, Escape closes, Backspace / Delete clears when `clearable`, Tab leaves and closes |
|
|
578
|
+
| `MultiSelect` | Same, but Enter / Space toggle. Type-ahead on a closed field opens and highlights |
|
|
579
|
+
| `Switch` / `Checkbox` | Space toggles |
|
|
580
|
+
| `Radio` | Arrow keys move within a `name` group |
|
|
581
|
+
| `Button` / `TextLink` | Native |
|
|
582
|
+
|
|
583
|
+
Disabled options are skipped. Escape is handled on the trigger, not on `window`.
|
|
584
|
+
|
|
585
|
+
## Roles and state
|
|
586
|
+
|
|
587
|
+
- `Select` / `MultiSelect`: trigger is `role='combobox'` with `aria-haspopup='listbox'`, `aria-expanded`, and `aria-autocomplete='none'`. The menu is `role='listbox'` with `role='option'` rows. The listbox is labelled by the control only when you set `id`. `readOnly` sets `aria-readonly`.
|
|
588
|
+
- `Switch` is `input[type=checkbox][role=switch]`.
|
|
589
|
+
- Invalid fields: set `aria-invalid='true'`. The library paints it and `:user-invalid`. Link the error text with `aria-describedby`.
|
|
590
|
+
|
|
591
|
+
## Focus and contrast
|
|
592
|
+
|
|
593
|
+
- Focus is `:focus-visible` only: fields ring and glow in their `color`, `TextLink` uses a `currentColor` outline, switches / checkboxes / radios glow in the on-state chroma.
|
|
594
|
+
- `disabled` controls dim and are not focusable. `readOnly` on `Select` / `MultiSelect` stays full contrast.
|
|
595
|
+
- Contrast is the host’s job. The default theme is dark first. `palette` adjacent steps are **not** a safe text-on-fill pair; skip a step. Default `warning` (`#ffd60a`) is weak as small text on light panels. Check any custom `createPanelizeTheme` colors.
|
|
596
|
+
- `PanelizeProvider` sets CSS `color-scheme` from `mode` so native scrollbars and form chrome match. Respecting `prefers-color-scheme` or `prefers-reduced-motion` is host-side.
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
<!-- docs/controls.md -->
|
|
601
|
+
|
|
602
|
+
# Controls
|
|
603
|
+
|
|
604
|
+
Behavior notes for each control: chrome, defaults, edge cases. Props are in `node_modules/panelize/dist/index.d.ts`. Examples live in the [README](../README.md#controls). Value ownership and validation: [forms](forms.md). Labelling and keyboard: [accessibility](accessibility.md).
|
|
605
|
+
|
|
606
|
+
## Button
|
|
607
|
+
|
|
608
|
+
`<button type="button">`. Caption: `label` (preferred). Custom insides: `children` (`Cols` / `Text` / host icons) — they win if both are set. A string as `children` still works; don’t prefer it. No visible text: `aria-label`. Omit chrome = filled chroma; `outline` = border + slot label, hollow at rest/hover/active. `color` is the chroma slot (`primary` / `secondary` / `accent` / `danger` / `warning` / `success` / `info`) for rest, hover, and active (outline border stays on this slot). Filled ink is `defaults.button.ink` (library `palette.black`), not chroma-on-fill. `size` is font size; `fill` is the box (100% of parent). Native `disabled` dims and skips hover. Instance `padding` / `paddingX` / `paddingY` / `borderWidth` / `borderRadius` override the theme. `background` / `ink` are a token/CSS (all steps) or `{ base, hover, active }`; omitted keys keep the slot / black ink / transparent outline. `background` on `outline` opts in to fill. No margin.
|
|
609
|
+
|
|
610
|
+
## Chip
|
|
611
|
+
|
|
612
|
+
`<span>` badge. Label is the chroma slot (`primary` / `secondary` / `accent` / `danger` / …). Fill is `color-mix` of that chroma at `defaults.chip.tint` (`12%`) into the nearest panel `--panelize-bg` until `background`. Capsule radius until `borderRadius`. `borderWidth` default `none`. Caption: `label` (preferred); `children` win. Instance `ink` / `borderColor` / `background` are surface / brand / status / CSS. `base` on those is the panel `base` surface (fill vs text vs border by prop), not a brand slot. Not a layout shell.
|
|
613
|
+
|
|
614
|
+
## Input
|
|
615
|
+
|
|
616
|
+
`<input type="text">` unless `type` is set. Rest chrome is the nearest `Panel` (bg, text, border, muted placeholder). **`color` is focus chroma only** (caret, border, glow) — not the typed text (`ink`). Instance `background` / `ink` / `borderColor` override rest fill, glyphs, and rest stroke (surface / brand / status / CSS). `base` means that surface’s fill, `text.base`, or `border` depending on the prop. Instance `borderWidth` / `borderRadius` override the theme. Focus / `:user-invalid` still own the stroke. After the user has edited, `:user-invalid` paints `status.danger` (same 2px / 18% glow, caret included). Hosts can also set `aria-invalid="true"` for submit-time paint without a prior edit. Pair a visible name with `Label htmlFor`; placeholder-only still needs `aria-label`. Native HTML `size` is omitted; `size` is font size. `fill` is the box. `ref` works as a normal prop. Host owns the value. `readOnly` dims like `disabled` but keeps a text cursor.
|
|
617
|
+
|
|
618
|
+
## NumberInput
|
|
619
|
+
|
|
620
|
+
`Input` locked to `type="number"`. Same chrome (`css`, `background`, `ink`, `borderColor`, `:user-invalid` / `aria-invalid`). Native `min` / `max` / `step` (`step='any'` allowed). No `type` prop. UA stepper arrows are hidden (`appearance: none`); bounds and step are the API. `onChange` still gives a string — use `event.currentTarget.valueAsNumber`. Wheel over the field blurs so the value does not accidentally step. Prefer `NumberInput` over `<Input type='number'>`.
|
|
621
|
+
|
|
622
|
+
## Textarea
|
|
623
|
+
|
|
624
|
+
Same field chrome as `Input` (`<textarea>`), including `:user-invalid` / `aria-invalid`. Theme field keys inherit `defaults.input` unless `defaults.textarea` overrides them. Default `rows={3}`. Resize is off until `resize`. `fill` stretches the box and does not lock resize.
|
|
625
|
+
|
|
626
|
+
## Select
|
|
627
|
+
|
|
628
|
+
Custom listbox (not a native `<select>`). Trigger uses the same field chrome as `Input` (`background` / `ink` / `borderColor` / `aria-invalid`). Theme field keys inherit `defaults.input` unless `defaults.select` overrides them (`listMaxHeight` / `listOffset` / option padding / `listZIndex` / `listShadow`). The menu portals to `document.body` (`position: fixed`) so a scrolling `Panels` does not clip it. Host owns `value` / `onChange` (`(value: string) => void`; empty string when cleared). A value that is not in `options` still shows (raw `value`) and can be cleared. Omit `open` for internal open state, or pass `open` + `onOpenChange`. Options: `options={{ value, label, disabled? }[]}` and/or child `<Option value>` (children win on the same `value`). `placeholder` when empty (muted). `required` blocks clearing to empty and marks the hidden input `required`. `clearable` shows an × when there is a value (ignored when `required`). `selectedAtTop` lists the selected option first in the menu (the `emptyOption` row stays first). `emptyOption` adds a first row with value `''` (`true` uses `placeholder`; a string is the caption; ignored when `required`). `readOnly` opens to inspect without pick / clear (not dimmed). Without `required`, clicking the selected option deselects it. `name` writes a hidden input for form submit (`disabled` when the select is). Pointer: pick, clear, caret-toggle close, and click-outside **blur** the trigger (no open ring). Keyboard pick / Escape keep focus. Tab away closes the menu. Keyboard: arrows, Enter, Space, Escape on the trigger (not `window`), Home, End, type-ahead; Backspace / Delete clears when clearable. `aria-autocomplete="none"`. The listbox uses `aria-labelledby` only when `id` is set. Disabled options are skipped. CSS chevron on the trigger; the value and rest option labels share highlighted ink. The selected option uses `color` (primary) for the label plus a CSS check, with no row fill. Hover (and keyboard active) fills the row only — text color does not change. No native fallback, multi-select, combobox typing, or optgroups. Prefer `Label` + `htmlFor`. `fill` stretches the trigger. Not a layout shell.
|
|
629
|
+
|
|
630
|
+
## MultiSelect
|
|
631
|
+
|
|
632
|
+
Custom multi listbox (not `multiple` on `Select`). Same field chrome, portal menu, and `defaults.select` as `Select`. Host owns `value` / `onChange` (`string[]`; empty array when cleared). The trigger shows a count (`2 selected`) — single line, no chips, English caption. The menu stays open while toggling. Each row is a `Checkbox` plus label; checked rows use `color` (primary) for the label, matching `Select`. Hover fills the row only. `clearable` is clear-all. `required` blocks an empty set (cannot uncheck the last). `selectedAtTop` lists checked options first (host order within groups); the highlight follows the same value after a toggle. `readOnly` opens to inspect without toggle / clear (not dimmed; row checkboxes stay enabled visually). No `emptyOption`. `name` writes one hidden input per value (`value=''` when empty, `required` on that input). Pointer: clear, caret-toggle close, and click-outside **blur** the trigger (toggles do not). Keyboard toggle / Escape keep focus. Tab away closes the menu. Keyboard: arrows, Enter / Space toggle, Escape on the trigger (not `window`), Home, End, type-ahead; Backspace / Delete clears when clearable. Type-ahead while **closed** opens and highlights (does not toggle). Disabled options are skipped. CSS chevron on the trigger. Prefer `Label` + `htmlFor`. `fill` stretches the trigger. Not a layout shell.
|
|
633
|
+
|
|
634
|
+
## Switch
|
|
635
|
+
|
|
636
|
+
`<input type="checkbox" role="switch">`. Native `checked` / `defaultChecked` / `onChange` — host owns the value. **`color` is the on-track chroma** (`primary` / `secondary` / `accent` / status; theme default `primary`). Off-track mixes panel text into `--panelize-bg` until `background`. Thumb is `defaults.switch.ink` (`palette.white`) until `ink`. Pair a visible name with `Label htmlFor`. No `label` / `children`. No `fill`. `size` is the type token (`extraSmall` … `extraLarge`). Track height is that size × theme `lineHeight` (keep `lineHeight` unitless, default `1.2`). Width is type × `defaults.switch.widthFactor` (default `2`). Thumb is height − 2×inset (`insetFactor` × type, default `0.2`). Pair with a `Label` of the **same** `size`. On-track hover/active use the `color` slot (same steps as `Button`). Instance `width` / `height` are space tokens or CSS and override those axes. Instance `borderRadius` (theme default `999px` capsule) shapes the track; the thumb uses `min(that, 50%)` so a capsule stays round and `small` / `none` squares both. `onChange` is the action (`event.currentTarget.checked`). Native `disabled` dims like `Button`. `:focus-visible` uses the on-track chroma glow (same mix as fields). Not a layout shell.
|
|
637
|
+
|
|
638
|
+
## Checkbox
|
|
639
|
+
|
|
640
|
+
`<input type="checkbox">`. Native `checked` / `defaultChecked` / `onChange` — host owns the value. **`color` is the chroma slot** (`primary` / `secondary` / `accent` / status; theme default `primary`), same steps as `Button`. Empty rest / hover / active stroke is `currentColor` (wrapping `Label` ink, else panel `text.base`) until `borderColor`. Checked / indeterminate fill the `color` slot’s `.base` / `.hover` / `.active`. Rest fill is transparent until `background`. Check / dash is `defaults.checkbox.ink` (`palette.black`, same as filled `Button`) until `ink` (token/CSS or `{ base, hover, active }`; hover/active unused on the mark). Pair a visible name with `Label htmlFor`. No `label` / `children`. No `fill`. `size` is the type token; the box is that size (`1em`, square). Pair with a `Label` of the **same** `size`. Instance `width` / `height` are space tokens or CSS. Instance `borderRadius` (theme default `small`) and `borderWidth` (theme default `extraSmall`, `1px`) override the theme. `indeterminate` is the native property (dash); it does not set `checked`. `hideCheckIcon` is fill-only when checked (no check glyph); the dash still draws when indeterminate. Empty `disabled` keeps the inherited stroke (pair with a muted wrapping `Label`); checked / indeterminate still dim like `Button`. Disabled skips hover. `:focus-visible` uses the on-fill chroma glow. Not a layout shell.
|
|
641
|
+
|
|
642
|
+
## Radio
|
|
643
|
+
|
|
644
|
+
`<input type="radio">`. Native `name` / `value` / `checked` / `defaultChecked` / `onChange` — host owns the group. **`color` is the chroma slot** (`primary` / `secondary` / `accent` / status; theme default `primary`), same steps as `Button`. Empty rest / hover / active stroke is `currentColor` (wrapping `Label` ink, else panel `text.base`) until `borderColor`. Checked ring and inner disc use the `color` slot’s `.base` / `.hover` / `.active`. Rest fill stays transparent until `background` so the panel shows in the gap around the disc. `ink` is the disc (`PaintSteps`, same as `Checkbox`); omit = disc follows `color` including hover/active. Pair a visible name with a wrapping `Label htmlFor`. No `label` / `children`. No `fill`. `size` is the type token; the box is that size (`1em`, circle). Pair with a `Label` of the **same** `size`. Disc inset is type × `defaults.radio.insetFactor` (default `0.22`). Instance `width` / `height` are space tokens or CSS. Instance `borderRadius` (theme default `999px` circle) shapes the ring; the disc uses `min(that, 50%)`. Instance `borderWidth` (theme default `extraSmall`, `1px`) overrides the theme. Empty `disabled` keeps the inherited stroke (pair with a muted wrapping `Label`); checked still dims like `Button`. Disabled skips hover. `:focus-visible` uses the on-ring chroma glow. Not a layout shell.
|
|
645
|
+
|
|
646
|
+
## Label
|
|
647
|
+
|
|
648
|
+
`<label>`. Ink like `Text` (`color` / `muted` / `highlighted`). The line box hugs the glyphs (`line-height: 1` and `text-box: trim-both cap alphabetic`, same idea as `Heading`) so a `Switch` / `Checkbox` / `Radio` in a centered `Cols` row lines up. Fields use `ink` for glyphs; field `color` is focus chroma only. Caption: `label` (preferred). Custom insides: `children` — they win if both are set. Point `htmlFor` at the control’s `id`. Cursor is `pointer` when `htmlFor` / `onClick` is set or the label wraps a control (`button` / `input` / `select` / `textarea`). Wrap the control (not a sibling-only `htmlFor`) so a `disabled` target is `not-allowed` via `:has(:disabled)`. Field labels stay `pointer`; the field itself keeps `text` / `not-allowed`.
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
<!-- docs/components.md -->
|
|
653
|
+
|
|
654
|
+
# Components
|
|
655
|
+
|
|
656
|
+
One line per export from `'panelize'`. Prop details are in the exported prop types (IDE hover) and the [README](../README.md). Layout props are in [layout](layout.md). Every component takes React `style` and `css?: string` (appended last).
|
|
657
|
+
|
|
658
|
+
## Layout
|
|
659
|
+
|
|
660
|
+
| Component | Element | Direct parent | Notes |
|
|
661
|
+
| --------- | -------------- | ------------- | ---------------------------------------------------------------------------------------- |
|
|
662
|
+
| `Panels` | `div` | any | View shell. Children: only `Panel`. `fill`, `horizontal`, `relative`, `scroll`, `stack`. |
|
|
663
|
+
| `Panel` | `div` | `Panels` | Section. `variant` `muted` / `base` / `highlighted`. Publishes `--panelize-*`. |
|
|
664
|
+
| `Rows` | `div` (column) | any | Children: two or more `Row`. `gap`, `wrap`, `alignX` / `alignY`. |
|
|
665
|
+
| `Row` | `div` | `Rows` | `height` defaults to `auto`. |
|
|
666
|
+
| `Cols` | `div` (row) | any | Children: two or more `Col`. `gap`, `wrap`, `stack`. |
|
|
667
|
+
| `Col` | `div` | `Cols` | `width` defaults to `auto`. |
|
|
668
|
+
| `Grid` | `div` (grid) | any | Children: two or more `Cell`. `numCols`, `numRows`, `stack`, `stackCols`. |
|
|
669
|
+
| `Cell` | `div` | `Grid` | `spanX` / `spanY`. No `width` / `height` / `fill`. |
|
|
670
|
+
| `Div` | `div` | any | Catch-all wrapper. No background or color. |
|
|
671
|
+
|
|
672
|
+
## Type
|
|
673
|
+
|
|
674
|
+
| Component | Element | Notes |
|
|
675
|
+
| ---------- | --------- | --------------------------------------------------------------------------------------------------------- |
|
|
676
|
+
| `Text` | `p` | `muted` / `highlighted` / `color` / `size` / `weight` / `family` / `italic` / `underlined` / `uppercase`. |
|
|
677
|
+
| `Heading` | `h1`–`h6` | Required `level`. Same type props as `Text`. |
|
|
678
|
+
| `Span` | `span` | Inline. Inherits size / weight / family from a parent `Text` / `Span`. |
|
|
679
|
+
| `TextLink` | `a` | `color`, `hover`, `hoverUnderline`. Native `href`. |
|
|
680
|
+
| `Label` | `label` | `label` or `children`, `htmlFor`. Wrap `Switch` / `Checkbox` / `Radio`. |
|
|
681
|
+
|
|
682
|
+
`color` on type and controls: `primary` / `secondary` / `accent` (brand), `danger` / `warning` / `success` / `info` (status), or raw CSS.
|
|
683
|
+
|
|
684
|
+
## Controls
|
|
685
|
+
|
|
686
|
+
None is a layout shell. The wrapper (`Row` / `Col` / `Cell` / `Panel` / `Div`) places them.
|
|
687
|
+
|
|
688
|
+
| Component | Element | Value API | Notes |
|
|
689
|
+
| ------------- | ------------------------------ | ----------------------------------------- | --------------------------------------------------------------------------- |
|
|
690
|
+
| `Button` | `button` | `onClick` | `label` or `children`. `color`, `outline`, `size`, `fill`, `disabled`. |
|
|
691
|
+
| `Chip` | `span` | none | Badge, not a button. `label`, `color`. |
|
|
692
|
+
| `Input` | `input` | `value` / `onChange` | `color` is focus chroma. `ink` is text. Needs `Label` or `aria-label`. |
|
|
693
|
+
| `NumberInput` | `input[type=number]` | `value` / `onChange` (string) | Use `event.currentTarget.valueAsNumber`. |
|
|
694
|
+
| `Textarea` | `textarea` | `value` / `onChange` | `resize` opt-in, `rows` default 3. |
|
|
695
|
+
| `Select` | custom listbox | `value: string` / `onChange(value)` | `options` and/or `Option` children. `clearable`, `required`, `emptyOption`. |
|
|
696
|
+
| `MultiSelect` | custom multi listbox | `value: string[]` / `onChange(value)` | Count caption. Menu stays open while toggling. |
|
|
697
|
+
| `Option` | child of selects | `value` | Label is `children`. |
|
|
698
|
+
| `Switch` | `input[checkbox][role=switch]` | `checked` / `onChange` | `color` is the on track. |
|
|
699
|
+
| `Checkbox` | `input[checkbox]` | `checked` / `onChange` | `indeterminate`, `hideCheckIcon`. |
|
|
700
|
+
| `Radio` | `input[radio]` | `name` / `value` / `checked` / `onChange` | Host owns the group. |
|
|
701
|
+
|
|
702
|
+
`Switch` / `Checkbox` / `Radio` and their `Label` share one `size`. These three have no `fill`, `label`, or `children`.
|
|
703
|
+
|
|
704
|
+
## Theme
|
|
705
|
+
|
|
706
|
+
| Export | Purpose |
|
|
707
|
+
| -------------------------------- | ------------------------------------------------------------------------------------- |
|
|
708
|
+
| `createPanelizeTheme(overrides)` | Merge overrides onto the defaults. Keys: [theme keys](../examples/panelize.theme.md). |
|
|
709
|
+
| `PanelizeProvider` | `theme` (optional) and `mode` `dark` (default) / `light`. |
|
|
710
|
+
| `usePanelizeTheme()` | Active `colors`, `mode`, `brand`, `status`, scales, `defaults`. |
|
|
711
|
+
| `defaultTheme` | What components use without a provider. |
|
|
712
|
+
| `palette` | Frozen gray ramp: `gray[0]`–`[6]`, `black`, `white`, `extraDark` … `extraLight`. |
|
|
713
|
+
|
|
714
|
+
Tokens: space / border / radius `none` | `extraSmall` | `small` | `base` | `large` | `extraLarge`. Font size `extraSmall` … `extraLarge`. Weight `thin` | `base` | `bold` | `heavy`. Family `base` | `display` | `mono`. Breakpoints `sm` | `md` | `lg`.
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
<!-- docs/recipes.md -->
|
|
719
|
+
|
|
720
|
+
# Recipes
|
|
721
|
+
|
|
722
|
+
Copyable trees that follow [composition](composition.md). Pick the closest recipe, then change copy and tokens. Every tree sits inside `PanelizeProvider` and a host with `html` / `body` / `#root { height: 100% }`.
|
|
723
|
+
|
|
724
|
+
| Need | Recipe |
|
|
725
|
+
| ------------------------------- | ------------------------------------------- |
|
|
726
|
+
| One scrolling page | [Page](#page) |
|
|
727
|
+
| Sidebar plus main | [Sidebar and main](#sidebar-and-main) |
|
|
728
|
+
| Centered card (login, empty) | [Centered card](#centered-card) |
|
|
729
|
+
| Label above a field | [Form fields](#form-fields) |
|
|
730
|
+
| Metric tiles that reflow | [Dashboard grid](#dashboard-grid) |
|
|
731
|
+
| Header bar, content, action row | [Header, body, footer](#header-body-footer) |
|
|
732
|
+
|
|
733
|
+
## Page
|
|
734
|
+
|
|
735
|
+
Scroll the shell. Inner `Panel` uses `minHeight='100%'`, not `fill`.
|
|
736
|
+
|
|
737
|
+
```tsx
|
|
738
|
+
<Panels fill scroll='vertical'>
|
|
739
|
+
<Panel variant='muted' minHeight='100%' padding='base'>
|
|
740
|
+
<Rows gap='base'>
|
|
741
|
+
<Row>
|
|
742
|
+
<Heading level={1}>Title</Heading>
|
|
743
|
+
</Row>
|
|
744
|
+
<Row>
|
|
745
|
+
<Text muted>Long page copy…</Text>
|
|
746
|
+
</Row>
|
|
747
|
+
</Rows>
|
|
748
|
+
</Panel>
|
|
749
|
+
</Panels>
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
## Sidebar and main
|
|
753
|
+
|
|
754
|
+
`horizontal` + `fill` on `Panels`. Sidebar gets `width`. Main gets `flex` and owns `scroll`. `stack` is "at most this wide → column".
|
|
755
|
+
|
|
756
|
+
```tsx
|
|
757
|
+
<Panels horizontal fill stack='sm'>
|
|
758
|
+
<Panel variant='muted' width='14rem' padding='base'>
|
|
759
|
+
<Rows gap='small'>
|
|
760
|
+
<Row>
|
|
761
|
+
<TextLink href='/'>Home</TextLink>
|
|
762
|
+
</Row>
|
|
763
|
+
<Row>
|
|
764
|
+
<TextLink href='/settings'>Settings</TextLink>
|
|
765
|
+
</Row>
|
|
766
|
+
</Rows>
|
|
767
|
+
</Panel>
|
|
768
|
+
<Panel variant='base' flex scroll='vertical' padding='base'>
|
|
769
|
+
<Heading level={2}>Main</Heading>
|
|
770
|
+
</Panel>
|
|
771
|
+
</Panels>
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
## Centered card
|
|
775
|
+
|
|
776
|
+
Center on `Panels` (not scrolling). One `Panel` is the card.
|
|
777
|
+
|
|
778
|
+
```tsx
|
|
779
|
+
<Panels fill alignX='center' alignY='center'>
|
|
780
|
+
<Panel variant='muted' width='24rem' padding='large' borderWidth='extraSmall' borderRadius='base'>
|
|
781
|
+
<Rows gap='base'>
|
|
782
|
+
<Row>
|
|
783
|
+
<Heading level={2}>Sign in</Heading>
|
|
784
|
+
</Row>
|
|
785
|
+
<Row>
|
|
786
|
+
<Button fill label='Continue' />
|
|
787
|
+
</Row>
|
|
788
|
+
</Rows>
|
|
789
|
+
</Panel>
|
|
790
|
+
</Panels>
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
## Form fields
|
|
794
|
+
|
|
795
|
+
Each field is a `Rows` of two `Row`s (label, control). The outer `Rows` spaces the fields. Controls take `fill` to stretch themselves; the `Row` places them.
|
|
796
|
+
|
|
797
|
+
```tsx
|
|
798
|
+
<Rows gap='base'>
|
|
799
|
+
<Row>
|
|
800
|
+
<Rows gap='extraSmall'>
|
|
801
|
+
<Row>
|
|
802
|
+
<Label htmlFor='email' label='Email' />
|
|
803
|
+
</Row>
|
|
804
|
+
<Row>
|
|
805
|
+
<Input id='email' type='email' fill />
|
|
806
|
+
</Row>
|
|
807
|
+
</Rows>
|
|
808
|
+
</Row>
|
|
809
|
+
<Row>
|
|
810
|
+
<Rows gap='extraSmall'>
|
|
811
|
+
<Row>
|
|
812
|
+
<Label htmlFor='plan' label='Plan' />
|
|
813
|
+
</Row>
|
|
814
|
+
<Row>
|
|
815
|
+
<Select id='plan' value={plan} onChange={setPlan} fill>
|
|
816
|
+
<Option value='hobby'>Hobby</Option>
|
|
817
|
+
<Option value='pro'>Pro</Option>
|
|
818
|
+
</Select>
|
|
819
|
+
</Row>
|
|
820
|
+
</Rows>
|
|
821
|
+
</Row>
|
|
822
|
+
</Rows>
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
A toggle pairs with a **wrapping** `Label` of the same `size`:
|
|
826
|
+
|
|
827
|
+
```tsx
|
|
828
|
+
<Label htmlFor='auto-save'>
|
|
829
|
+
<Switch id='auto-save' defaultChecked /> Auto-save drafts
|
|
830
|
+
</Label>
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
## Dashboard grid
|
|
834
|
+
|
|
835
|
+
`stack='sm'` collapses to `stackCols` columns at most `sm` wide.
|
|
836
|
+
|
|
837
|
+
```tsx
|
|
838
|
+
<Grid numCols={3} stackCols={1} stack='md' gap='base'>
|
|
839
|
+
<Cell>
|
|
840
|
+
<Panel variant='muted' padding='base'>
|
|
841
|
+
<Text muted size='small'>
|
|
842
|
+
Users
|
|
843
|
+
</Text>
|
|
844
|
+
<Heading level={3}>1,204</Heading>
|
|
845
|
+
</Panel>
|
|
846
|
+
</Cell>
|
|
847
|
+
<Cell>
|
|
848
|
+
<Panel variant='muted' padding='base'>
|
|
849
|
+
<Text muted size='small'>
|
|
850
|
+
Revenue
|
|
851
|
+
</Text>
|
|
852
|
+
<Heading level={3}>$8.4k</Heading>
|
|
853
|
+
</Panel>
|
|
854
|
+
</Cell>
|
|
855
|
+
<Cell>
|
|
856
|
+
<Panel variant='muted' padding='base'>
|
|
857
|
+
<Text muted size='small'>
|
|
858
|
+
Errors
|
|
859
|
+
</Text>
|
|
860
|
+
<Heading level={3}>3</Heading>
|
|
861
|
+
</Panel>
|
|
862
|
+
</Cell>
|
|
863
|
+
</Grid>
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
## Header, body, footer
|
|
867
|
+
|
|
868
|
+
A default `Panels` is a column. The body takes `flex` and scrolls; the bars keep their size.
|
|
869
|
+
|
|
870
|
+
```tsx
|
|
871
|
+
<Panels fill>
|
|
872
|
+
<Panel variant='highlighted' padding='base' borderBottom='extraSmall'>
|
|
873
|
+
<Heading level={2}>Project</Heading>
|
|
874
|
+
</Panel>
|
|
875
|
+
<Panel flex scroll='vertical' padding='base'>
|
|
876
|
+
<Text>Body…</Text>
|
|
877
|
+
</Panel>
|
|
878
|
+
<Panel variant='muted' padding='base' borderTop='extraSmall'>
|
|
879
|
+
<Cols gap='small' alignX='end'>
|
|
880
|
+
<Col>
|
|
881
|
+
<Button outline label='Cancel' />
|
|
882
|
+
</Col>
|
|
883
|
+
<Col>
|
|
884
|
+
<Button label='Save' />
|
|
885
|
+
</Col>
|
|
886
|
+
</Cols>
|
|
887
|
+
</Panel>
|
|
888
|
+
</Panels>
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
<!-- examples/panelize.theme.md -->
|
|
894
|
+
|
|
895
|
+
# Theme keys
|
|
896
|
+
|
|
897
|
+
Every key `createPanelizeTheme` accepts. Omit any key to keep the library default.
|
|
898
|
+
|
|
899
|
+
```ts
|
|
900
|
+
import { createPanelizeTheme, palette } from 'panelize'
|
|
901
|
+
|
|
902
|
+
export const panelizeTheme = createPanelizeTheme({
|
|
903
|
+
colors: {
|
|
904
|
+
dark: {
|
|
905
|
+
app: {
|
|
906
|
+
background: '#121212',
|
|
907
|
+
text: '#e8e8e8',
|
|
908
|
+
},
|
|
909
|
+
muted: {
|
|
910
|
+
background: '#222222',
|
|
911
|
+
text: {
|
|
912
|
+
base: palette.gray.extraLight,
|
|
913
|
+
muted: palette.gray.base,
|
|
914
|
+
highlighted: palette.white,
|
|
915
|
+
},
|
|
916
|
+
border: palette.gray.dark,
|
|
917
|
+
},
|
|
918
|
+
base: {
|
|
919
|
+
background: '#333333',
|
|
920
|
+
text: {
|
|
921
|
+
base: palette.gray.extraLight,
|
|
922
|
+
muted: palette.gray.light,
|
|
923
|
+
highlighted: palette.white,
|
|
924
|
+
},
|
|
925
|
+
border: palette.gray.base,
|
|
926
|
+
},
|
|
927
|
+
highlighted: {
|
|
928
|
+
background: palette.gray.dark,
|
|
929
|
+
text: {
|
|
930
|
+
base: palette.white,
|
|
931
|
+
muted: palette.gray.light,
|
|
932
|
+
highlighted: palette.white,
|
|
933
|
+
},
|
|
934
|
+
border: palette.gray.base,
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
light: {
|
|
938
|
+
app: {
|
|
939
|
+
background: '#fafafa',
|
|
940
|
+
text: '#121212',
|
|
941
|
+
},
|
|
942
|
+
muted: {
|
|
943
|
+
background: '#f2f2f2',
|
|
944
|
+
text: {
|
|
945
|
+
base: '#222222',
|
|
946
|
+
muted: palette.gray.base,
|
|
947
|
+
highlighted: palette.black,
|
|
948
|
+
},
|
|
949
|
+
border: palette.gray.light,
|
|
950
|
+
},
|
|
951
|
+
base: {
|
|
952
|
+
background: '#f7f7f7',
|
|
953
|
+
text: {
|
|
954
|
+
base: '#333333',
|
|
955
|
+
muted: palette.gray.base,
|
|
956
|
+
highlighted: palette.black,
|
|
957
|
+
},
|
|
958
|
+
border: palette.gray.base,
|
|
959
|
+
},
|
|
960
|
+
highlighted: {
|
|
961
|
+
background: palette.white,
|
|
962
|
+
text: {
|
|
963
|
+
base: palette.gray.dark,
|
|
964
|
+
muted: palette.gray.base,
|
|
965
|
+
highlighted: palette.black,
|
|
966
|
+
},
|
|
967
|
+
border: palette.gray.base,
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
brand: {
|
|
972
|
+
primary: {
|
|
973
|
+
base: '#ff9f2e',
|
|
974
|
+
hover: '#ffb762',
|
|
975
|
+
active: '#f1962b',
|
|
976
|
+
},
|
|
977
|
+
secondary: {
|
|
978
|
+
base: '#ff9f2e',
|
|
979
|
+
hover: '#ffb762',
|
|
980
|
+
active: '#f1962b',
|
|
981
|
+
},
|
|
982
|
+
accent: {
|
|
983
|
+
base: '#ff9f2e',
|
|
984
|
+
hover: '#ffb762',
|
|
985
|
+
active: '#f1962b',
|
|
986
|
+
},
|
|
987
|
+
},
|
|
988
|
+
status: {
|
|
989
|
+
danger: '#f2555a',
|
|
990
|
+
warning: '#ffd60a',
|
|
991
|
+
success: '#4ade80',
|
|
992
|
+
info: '#38bdf8',
|
|
993
|
+
},
|
|
994
|
+
space: {
|
|
995
|
+
none: '0rem',
|
|
996
|
+
extraSmall: '0.25rem',
|
|
997
|
+
small: '0.5rem',
|
|
998
|
+
base: '1rem',
|
|
999
|
+
large: '1.5rem',
|
|
1000
|
+
extraLarge: '2rem',
|
|
1001
|
+
},
|
|
1002
|
+
borderWidths: {
|
|
1003
|
+
none: '0',
|
|
1004
|
+
extraSmall: '1px',
|
|
1005
|
+
small: '2px',
|
|
1006
|
+
base: '4px',
|
|
1007
|
+
large: '8px',
|
|
1008
|
+
extraLarge: '12px',
|
|
1009
|
+
},
|
|
1010
|
+
breakpoints: {
|
|
1011
|
+
sm: '40rem',
|
|
1012
|
+
md: '48rem',
|
|
1013
|
+
lg: '64rem',
|
|
1014
|
+
},
|
|
1015
|
+
fontSizes: {
|
|
1016
|
+
extraSmall: '0.75rem',
|
|
1017
|
+
small: '0.875rem',
|
|
1018
|
+
base: '1rem',
|
|
1019
|
+
large: '1.25rem',
|
|
1020
|
+
extraLarge: '1.5rem',
|
|
1021
|
+
},
|
|
1022
|
+
fontWeights: {
|
|
1023
|
+
thin: '100',
|
|
1024
|
+
base: '400',
|
|
1025
|
+
bold: '700',
|
|
1026
|
+
heavy: '800',
|
|
1027
|
+
},
|
|
1028
|
+
fontFamilies: {
|
|
1029
|
+
base: '"Open Sans", sans-serif',
|
|
1030
|
+
display: 'Montserrat, sans-serif',
|
|
1031
|
+
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
1032
|
+
},
|
|
1033
|
+
// Unitless. `Text` leading. Switch track height is type × this. Checkbox / Radio use `1em`.
|
|
1034
|
+
lineHeight: 1.2,
|
|
1035
|
+
radii: {
|
|
1036
|
+
none: '0',
|
|
1037
|
+
extraSmall: '0.125rem',
|
|
1038
|
+
small: '0.25rem',
|
|
1039
|
+
base: '0.5rem',
|
|
1040
|
+
large: '0.75rem',
|
|
1041
|
+
extraLarge: '1rem',
|
|
1042
|
+
},
|
|
1043
|
+
defaults: {
|
|
1044
|
+
panel: {
|
|
1045
|
+
padding: 'none',
|
|
1046
|
+
margin: 'none',
|
|
1047
|
+
borderWidth: 'none',
|
|
1048
|
+
borderRadius: 'none',
|
|
1049
|
+
},
|
|
1050
|
+
panels: {
|
|
1051
|
+
padding: 'none',
|
|
1052
|
+
margin: 'none',
|
|
1053
|
+
gap: 'none',
|
|
1054
|
+
},
|
|
1055
|
+
// No panels.alignX / alignY: Panels is a column, or `horizontal` / `stack`.
|
|
1056
|
+
rows: {
|
|
1057
|
+
padding: 'none',
|
|
1058
|
+
margin: 'none',
|
|
1059
|
+
gap: 'none',
|
|
1060
|
+
alignX: 'stretch',
|
|
1061
|
+
alignY: 'start',
|
|
1062
|
+
},
|
|
1063
|
+
row: {
|
|
1064
|
+
padding: 'none',
|
|
1065
|
+
margin: 'none',
|
|
1066
|
+
height: 'auto',
|
|
1067
|
+
borderWidth: 'none',
|
|
1068
|
+
borderRadius: 'none',
|
|
1069
|
+
},
|
|
1070
|
+
cols: {
|
|
1071
|
+
padding: 'none',
|
|
1072
|
+
margin: 'none',
|
|
1073
|
+
gap: 'none',
|
|
1074
|
+
alignX: 'start',
|
|
1075
|
+
alignY: 'stretch',
|
|
1076
|
+
},
|
|
1077
|
+
col: {
|
|
1078
|
+
padding: 'none',
|
|
1079
|
+
margin: 'none',
|
|
1080
|
+
width: 'auto',
|
|
1081
|
+
borderWidth: 'none',
|
|
1082
|
+
borderRadius: 'none',
|
|
1083
|
+
},
|
|
1084
|
+
grid: {
|
|
1085
|
+
padding: 'none',
|
|
1086
|
+
margin: 'none',
|
|
1087
|
+
gap: 'none',
|
|
1088
|
+
alignX: 'stretch',
|
|
1089
|
+
alignY: 'stretch',
|
|
1090
|
+
},
|
|
1091
|
+
cell: {
|
|
1092
|
+
padding: 'none',
|
|
1093
|
+
margin: 'none',
|
|
1094
|
+
borderWidth: 'none',
|
|
1095
|
+
borderRadius: 'none',
|
|
1096
|
+
},
|
|
1097
|
+
div: {
|
|
1098
|
+
padding: 'none',
|
|
1099
|
+
margin: 'none',
|
|
1100
|
+
borderWidth: 'none',
|
|
1101
|
+
borderRadius: 'none',
|
|
1102
|
+
},
|
|
1103
|
+
text: {
|
|
1104
|
+
size: 'base',
|
|
1105
|
+
weight: 'base',
|
|
1106
|
+
},
|
|
1107
|
+
heading: {
|
|
1108
|
+
size: {
|
|
1109
|
+
1: '2rem',
|
|
1110
|
+
2: '1.5rem',
|
|
1111
|
+
3: '1.17rem',
|
|
1112
|
+
4: '1rem',
|
|
1113
|
+
5: '0.83rem',
|
|
1114
|
+
6: '0.67rem',
|
|
1115
|
+
},
|
|
1116
|
+
weight: 'bold',
|
|
1117
|
+
family: 'display',
|
|
1118
|
+
},
|
|
1119
|
+
textLink: {
|
|
1120
|
+
color: 'primary',
|
|
1121
|
+
hover: 'primary',
|
|
1122
|
+
hoverUnderline: false,
|
|
1123
|
+
},
|
|
1124
|
+
button: {
|
|
1125
|
+
color: 'primary',
|
|
1126
|
+
size: 'small',
|
|
1127
|
+
weight: 'bold',
|
|
1128
|
+
ink: palette.black,
|
|
1129
|
+
hover: { color: palette.black },
|
|
1130
|
+
paddingX: 'base',
|
|
1131
|
+
paddingY: 'small',
|
|
1132
|
+
gap: 'extraSmall',
|
|
1133
|
+
borderWidth: 'extraSmall',
|
|
1134
|
+
borderRadius: 'base',
|
|
1135
|
+
},
|
|
1136
|
+
chip: {
|
|
1137
|
+
color: 'primary',
|
|
1138
|
+
size: 'extraSmall',
|
|
1139
|
+
weight: 'bold',
|
|
1140
|
+
tint: '12%',
|
|
1141
|
+
paddingX: 'base',
|
|
1142
|
+
paddingY: 'extraSmall',
|
|
1143
|
+
gap: 'extraSmall',
|
|
1144
|
+
borderRadius: '999px',
|
|
1145
|
+
uppercase: true,
|
|
1146
|
+
},
|
|
1147
|
+
input: {
|
|
1148
|
+
color: 'primary',
|
|
1149
|
+
size: 'small',
|
|
1150
|
+
weight: 'base',
|
|
1151
|
+
paddingX: 'base',
|
|
1152
|
+
paddingY: 'small',
|
|
1153
|
+
borderWidth: 'extraSmall',
|
|
1154
|
+
borderRadius: 'base',
|
|
1155
|
+
},
|
|
1156
|
+
textarea: {
|
|
1157
|
+
color: 'primary',
|
|
1158
|
+
size: 'small',
|
|
1159
|
+
weight: 'base',
|
|
1160
|
+
paddingX: 'base',
|
|
1161
|
+
paddingY: 'small',
|
|
1162
|
+
borderWidth: 'extraSmall',
|
|
1163
|
+
borderRadius: 'base',
|
|
1164
|
+
resize: false,
|
|
1165
|
+
rows: 3,
|
|
1166
|
+
},
|
|
1167
|
+
select: {
|
|
1168
|
+
color: 'primary',
|
|
1169
|
+
size: 'small',
|
|
1170
|
+
weight: 'base',
|
|
1171
|
+
paddingX: 'base',
|
|
1172
|
+
paddingY: 'small',
|
|
1173
|
+
borderWidth: 'extraSmall',
|
|
1174
|
+
borderRadius: 'base',
|
|
1175
|
+
listMaxHeight: '16rem',
|
|
1176
|
+
listOffset: 'extraSmall',
|
|
1177
|
+
optionPaddingX: 'base',
|
|
1178
|
+
optionPaddingY: 'extraSmall',
|
|
1179
|
+
listZIndex: 1100,
|
|
1180
|
+
listShadow: '0 8px 24px rgb(0 0 0 / 35%)',
|
|
1181
|
+
},
|
|
1182
|
+
switch: {
|
|
1183
|
+
color: 'primary',
|
|
1184
|
+
size: 'base',
|
|
1185
|
+
ink: palette.white,
|
|
1186
|
+
widthFactor: 2,
|
|
1187
|
+
insetFactor: 0.2,
|
|
1188
|
+
borderRadius: '999px',
|
|
1189
|
+
},
|
|
1190
|
+
checkbox: {
|
|
1191
|
+
color: 'primary',
|
|
1192
|
+
size: 'base',
|
|
1193
|
+
ink: palette.black,
|
|
1194
|
+
borderWidth: 'extraSmall',
|
|
1195
|
+
borderRadius: 'small',
|
|
1196
|
+
},
|
|
1197
|
+
radio: {
|
|
1198
|
+
color: 'primary',
|
|
1199
|
+
size: 'base',
|
|
1200
|
+
borderWidth: 'extraSmall',
|
|
1201
|
+
borderRadius: '999px',
|
|
1202
|
+
insetFactor: 0.22,
|
|
1203
|
+
},
|
|
1204
|
+
},
|
|
1205
|
+
})
|
|
1206
|
+
```
|