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/docs/shell.md ADDED
@@ -0,0 +1,57 @@
1
+ # Shell (`Panels` / `Panel`)
2
+
3
+ `Panels` is the outer shell of a view. Setting it up well matters more than any control.
4
+
5
+ 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`.
6
+
7
+ Direct children: only `Panel`. **One `Panel` is allowed.** Nested shells: a `Panel` may contain another `Panels`.
8
+
9
+ `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.
10
+
11
+ ## Host height
12
+
13
+ A filled shell needs a viewport-tall host:
14
+
15
+ ```css
16
+ html,
17
+ body,
18
+ #root {
19
+ height: 100%;
20
+ }
21
+ ```
22
+
23
+ The library does not set `html` / `body`. For a long in-flow document, drop host `height` and use `min-height: 100%`.
24
+
25
+ ## Page (scroll the shell)
26
+
27
+ 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`.
28
+
29
+ ```tsx
30
+ <Panels fill scroll='vertical'>
31
+ <Panel variant='muted' minHeight='100%' padding='base'>
32
+ <Heading level={1}>Title</Heading>
33
+ <Text muted>Long page copy…</Text>
34
+ </Panel>
35
+ </Panels>
36
+ ```
37
+
38
+ ## Split (scroll the pane)
39
+
40
+ `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).
41
+
42
+ ```tsx
43
+ <Panels horizontal fill stack='sm'>
44
+ <Panel variant='muted' width='12rem'>
45
+ <Text>Nav</Text>
46
+ </Panel>
47
+ <Panel variant='base' flex scroll='vertical'>
48
+ <Text>Main</Text>
49
+ </Panel>
50
+ </Panels>
51
+ ```
52
+
53
+ ## `scroll`
54
+
55
+ 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.
56
+
57
+ `Panel` / `Row` / `Col` / `Div` are `flex-shrink: 0` until `flex`.
package/docs/text.md ADDED
@@ -0,0 +1,26 @@
1
+ # Text
2
+
3
+ Visible copy must use a type primitive so it inherits the nearest `Panel` (`--panelize-text`, muted, highlighted) and theme type tokens.
4
+
5
+ | Component | Element | Notes |
6
+ | ---------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- |
7
+ | `Text` | `p` | Body. Omit color → panel `text.base`. |
8
+ | `Heading` | `h1`–`h6` | Required `level` `1`–`6`. Omit `size` → theme heading size for that level. Omit `weight` → `bold`. Omit `family` → `display`. |
9
+ | `Span` | `span` | Nested in `Text` / `Span`: omit size/weight/`family` → inherit. Omit color → CSS `inherit`. |
10
+ | `TextLink` | `a` | Default chroma `primary`. Nested omit size/weight/`family` → inherit. |
11
+ | `Label` | `label` | Caption for controls. Prefer `label` prop; `htmlFor` + control `id`. Wrap `Switch` / `Checkbox` / `Radio`. |
12
+
13
+ None of these is a layout shell: no `fill` / `flex` / padding. Gap belongs on `Rows` / `Cols` / `Grid` / `Div`.
14
+
15
+ `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.
16
+
17
+ Size: `fontSizes` tokens or CSS. Weight: omit on `Text` = `base` (400). Family: `base` / `display` / `mono`. The library does not load font files.
18
+
19
+ ```tsx
20
+ <Heading level={1} uppercase color='primary'>
21
+ Panelize
22
+ </Heading>
23
+ <Text muted size='small'>
24
+ Nested <Span weight='bold'>bold</Span> and <TextLink href='/docs'>Docs</TextLink>
25
+ </Text>
26
+ ```
package/docs/theme.md ADDED
@@ -0,0 +1,121 @@
1
+ # Theme and hooks
2
+
3
+ Appearance comes from the theme, not one-off color props on every box. That is how views stay standardized.
4
+
5
+ ## `createPanelizeTheme`
6
+
7
+ 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).
8
+
9
+ ```ts
10
+ import { createPanelizeTheme, palette } from 'panelize'
11
+
12
+ export const panelizeTheme = createPanelizeTheme({
13
+ colors: {
14
+ dark: {
15
+ app: { background: palette.gray.extraDark, text: palette.gray.extraLight },
16
+ muted: {
17
+ background: palette.gray.dark,
18
+ text: { base: palette.white, muted: palette.gray.base, highlighted: palette.white },
19
+ },
20
+ },
21
+ },
22
+ brand: { primary: '#ff9f2e' },
23
+ })
24
+ ```
25
+
26
+ `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.
27
+
28
+ 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.
29
+
30
+ ## `PanelizeProvider`
31
+
32
+ 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.
33
+
34
+ Do **not** take over the host’s styled-components `ThemeProvider`. Do **not** set `html` / `body`.
35
+
36
+ Without a provider, components still render using `defaultTheme` (`mode` `dark`). Always wrap a real host.
37
+
38
+ ```tsx
39
+ <PanelizeProvider theme={panelizeTheme} mode={mode}>
40
+ {children}
41
+ </PanelizeProvider>
42
+ ```
43
+
44
+ ## `usePanelizeTheme`
45
+
46
+ **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.
47
+
48
+ 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.
49
+
50
+ ```tsx
51
+ function Stat({ label, value }: { label: string; value: string }) {
52
+ const { colors } = usePanelizeTheme()
53
+ return (
54
+ <Div padding='small' borderWidth='extraSmall' borderColor={colors.base.border}>
55
+ <Text muted size='small'>
56
+ {label}
57
+ </Text>
58
+ <Text highlighted>{value}</Text>
59
+ </Div>
60
+ )
61
+ }
62
+ ```
63
+
64
+ Place `Stat` in a `Col` / `Row` / `Cell` / `Panel`. `Stat` does not take `alignX`.
65
+
66
+ ## Recipes
67
+
68
+ 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`.
69
+
70
+ **Brand kit.** `secondary` and `accent` follow `primary` until set. Status colors do not follow brand.
71
+
72
+ ```ts
73
+ export const panelizeTheme = createPanelizeTheme({
74
+ brand: {
75
+ primary: '#6d5dfc',
76
+ secondary: '#00b8a9',
77
+ accent: { base: '#f9c80e', hover: '#ffd84d' },
78
+ },
79
+ status: { danger: '#d64550' },
80
+ })
81
+ ```
82
+
83
+ **Both schemes.** Author `dark` and `light` surfaces together. Omit any surface to keep the default.
84
+
85
+ ```ts
86
+ createPanelizeTheme({
87
+ colors: {
88
+ dark: { app: { background: '#0e0e10', text: '#ececec' } },
89
+ light: {
90
+ app: { background: '#ffffff', text: '#1a1a1a' },
91
+ muted: {
92
+ background: '#f3f3f5',
93
+ text: { base: '#1a1a1a', muted: '#6b6b73', highlighted: '#000000' },
94
+ },
95
+ },
96
+ },
97
+ })
98
+ ```
99
+
100
+ **Fonts and type scale.** The library does not load font files. Point the tokens at what the host loads.
101
+
102
+ ```ts
103
+ createPanelizeTheme({
104
+ fontFamilies: { base: 'Inter, sans-serif', display: '"Fraunces", serif' },
105
+ fontSizes: { base: '1.0625rem' },
106
+ })
107
+ ```
108
+
109
+ **Component defaults.** Change a control everywhere instead of on every instance. `defaults.textarea` and `defaults.select` start from the merged `defaults.input`.
110
+
111
+ ```ts
112
+ createPanelizeTheme({
113
+ defaults: {
114
+ button: { borderRadius: 'small', paddingX: 'large' },
115
+ input: { borderRadius: 'small' },
116
+ select: { listMaxHeight: '12rem' },
117
+ },
118
+ })
119
+ ```
120
+
121
+ **Custom component on the theme.** Read `usePanelizeTheme` or inherit `--panelize-*` from the nearest `Panel`; do not hard-code hexes. See `Stat` above.
@@ -0,0 +1,9 @@
1
+ # Agent notes (UI)
2
+
3
+ This app builds views with [Panelize](https://github.com/jayzizzle/panelize).
4
+
5
+ - Before writing JSX, read `node_modules/panelize/llms.txt` and `node_modules/panelize/docs/composition.md`. Copyable trees: `node_modules/panelize/docs/recipes.md`.
6
+ - Import from `'panelize'`. Wrap the app once with `PanelizeProvider`.
7
+ - Host CSS owns `html` / `body` / `#root` height. The library does not set them.
8
+ - Follow the pairing rules: `Panels` > `Panel`; `Rows` > `Row`; `Cols` > `Col`; `Grid` > `Cell`. Two or more per track.
9
+ - The wrapper places things. Controls and type do not take `alignX` / `flex` / padding to position themselves.
@@ -0,0 +1,17 @@
1
+ ---
2
+ description: Compose views with Panelize layout primitives and type
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Panelize (this app)
7
+
8
+ Before writing or changing UI, read `node_modules/panelize/llms.txt` and `node_modules/panelize/docs/composition.md`.
9
+
10
+ - Import from `'panelize'`. Wrap the tree with `PanelizeProvider`.
11
+ - `Panels` is the view shell. Direct children are only `Panel` (one is allowed).
12
+ - `Row` only under `Rows` (two or more). `Col` only under `Cols` (two or more). `Cell` only under `Grid` (two or more).
13
+ - One band of content → `Panel` or `Div`, not a track of one item.
14
+ - The wrapper owns place (`alignX` / `alignY`, `flex`, `fill`, `gap`). Controls do not position themselves.
15
+ - Visible copy uses `Text` / `Heading` / `Span` / `TextLink` / `Label`.
16
+ - Custom components use `usePanelizeTheme` (or inherit `--panelize-*` from the nearest `Panel`). Do not invent a parallel palette.
17
+ - Forms, buttons, and chips are bonus. Core is panels, tracks, grid, and text.
@@ -0,0 +1,312 @@
1
+ # Theme keys
2
+
3
+ Every key `createPanelizeTheme` accepts. Omit any key to keep the library default.
4
+
5
+ ```ts
6
+ import { createPanelizeTheme, palette } from 'panelize'
7
+
8
+ export const panelizeTheme = createPanelizeTheme({
9
+ colors: {
10
+ dark: {
11
+ app: {
12
+ background: '#121212',
13
+ text: '#e8e8e8',
14
+ },
15
+ muted: {
16
+ background: '#222222',
17
+ text: {
18
+ base: palette.gray.extraLight,
19
+ muted: palette.gray.base,
20
+ highlighted: palette.white,
21
+ },
22
+ border: palette.gray.dark,
23
+ },
24
+ base: {
25
+ background: '#333333',
26
+ text: {
27
+ base: palette.gray.extraLight,
28
+ muted: palette.gray.light,
29
+ highlighted: palette.white,
30
+ },
31
+ border: palette.gray.base,
32
+ },
33
+ highlighted: {
34
+ background: palette.gray.dark,
35
+ text: {
36
+ base: palette.white,
37
+ muted: palette.gray.light,
38
+ highlighted: palette.white,
39
+ },
40
+ border: palette.gray.base,
41
+ },
42
+ },
43
+ light: {
44
+ app: {
45
+ background: '#fafafa',
46
+ text: '#121212',
47
+ },
48
+ muted: {
49
+ background: '#f2f2f2',
50
+ text: {
51
+ base: '#222222',
52
+ muted: palette.gray.base,
53
+ highlighted: palette.black,
54
+ },
55
+ border: palette.gray.light,
56
+ },
57
+ base: {
58
+ background: '#f7f7f7',
59
+ text: {
60
+ base: '#333333',
61
+ muted: palette.gray.base,
62
+ highlighted: palette.black,
63
+ },
64
+ border: palette.gray.base,
65
+ },
66
+ highlighted: {
67
+ background: palette.white,
68
+ text: {
69
+ base: palette.gray.dark,
70
+ muted: palette.gray.base,
71
+ highlighted: palette.black,
72
+ },
73
+ border: palette.gray.base,
74
+ },
75
+ },
76
+ },
77
+ brand: {
78
+ primary: {
79
+ base: '#ff9f2e',
80
+ hover: '#ffb762',
81
+ active: '#f1962b',
82
+ },
83
+ secondary: {
84
+ base: '#ff9f2e',
85
+ hover: '#ffb762',
86
+ active: '#f1962b',
87
+ },
88
+ accent: {
89
+ base: '#ff9f2e',
90
+ hover: '#ffb762',
91
+ active: '#f1962b',
92
+ },
93
+ },
94
+ status: {
95
+ danger: '#f2555a',
96
+ warning: '#ffd60a',
97
+ success: '#4ade80',
98
+ info: '#38bdf8',
99
+ },
100
+ space: {
101
+ none: '0rem',
102
+ extraSmall: '0.25rem',
103
+ small: '0.5rem',
104
+ base: '1rem',
105
+ large: '1.5rem',
106
+ extraLarge: '2rem',
107
+ },
108
+ borderWidths: {
109
+ none: '0',
110
+ extraSmall: '1px',
111
+ small: '2px',
112
+ base: '4px',
113
+ large: '8px',
114
+ extraLarge: '12px',
115
+ },
116
+ breakpoints: {
117
+ sm: '40rem',
118
+ md: '48rem',
119
+ lg: '64rem',
120
+ },
121
+ fontSizes: {
122
+ extraSmall: '0.75rem',
123
+ small: '0.875rem',
124
+ base: '1rem',
125
+ large: '1.25rem',
126
+ extraLarge: '1.5rem',
127
+ },
128
+ fontWeights: {
129
+ thin: '100',
130
+ base: '400',
131
+ bold: '700',
132
+ heavy: '800',
133
+ },
134
+ fontFamilies: {
135
+ base: '"Open Sans", sans-serif',
136
+ display: 'Montserrat, sans-serif',
137
+ mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
138
+ },
139
+ // Unitless. `Text` leading. Switch track height is type × this. Checkbox / Radio use `1em`.
140
+ lineHeight: 1.2,
141
+ radii: {
142
+ none: '0',
143
+ extraSmall: '0.125rem',
144
+ small: '0.25rem',
145
+ base: '0.5rem',
146
+ large: '0.75rem',
147
+ extraLarge: '1rem',
148
+ },
149
+ defaults: {
150
+ panel: {
151
+ padding: 'none',
152
+ margin: 'none',
153
+ borderWidth: 'none',
154
+ borderRadius: 'none',
155
+ },
156
+ panels: {
157
+ padding: 'none',
158
+ margin: 'none',
159
+ gap: 'none',
160
+ },
161
+ // No panels.alignX / alignY: Panels is a column, or `horizontal` / `stack`.
162
+ rows: {
163
+ padding: 'none',
164
+ margin: 'none',
165
+ gap: 'none',
166
+ alignX: 'stretch',
167
+ alignY: 'start',
168
+ },
169
+ row: {
170
+ padding: 'none',
171
+ margin: 'none',
172
+ height: 'auto',
173
+ borderWidth: 'none',
174
+ borderRadius: 'none',
175
+ },
176
+ cols: {
177
+ padding: 'none',
178
+ margin: 'none',
179
+ gap: 'none',
180
+ alignX: 'start',
181
+ alignY: 'stretch',
182
+ },
183
+ col: {
184
+ padding: 'none',
185
+ margin: 'none',
186
+ width: 'auto',
187
+ borderWidth: 'none',
188
+ borderRadius: 'none',
189
+ },
190
+ grid: {
191
+ padding: 'none',
192
+ margin: 'none',
193
+ gap: 'none',
194
+ alignX: 'stretch',
195
+ alignY: 'stretch',
196
+ },
197
+ cell: {
198
+ padding: 'none',
199
+ margin: 'none',
200
+ borderWidth: 'none',
201
+ borderRadius: 'none',
202
+ },
203
+ div: {
204
+ padding: 'none',
205
+ margin: 'none',
206
+ borderWidth: 'none',
207
+ borderRadius: 'none',
208
+ },
209
+ text: {
210
+ size: 'base',
211
+ weight: 'base',
212
+ },
213
+ heading: {
214
+ size: {
215
+ 1: '2rem',
216
+ 2: '1.5rem',
217
+ 3: '1.17rem',
218
+ 4: '1rem',
219
+ 5: '0.83rem',
220
+ 6: '0.67rem',
221
+ },
222
+ weight: 'bold',
223
+ family: 'display',
224
+ },
225
+ textLink: {
226
+ color: 'primary',
227
+ hover: 'primary',
228
+ hoverUnderline: false,
229
+ },
230
+ button: {
231
+ color: 'primary',
232
+ size: 'small',
233
+ weight: 'bold',
234
+ ink: palette.black,
235
+ hover: { color: palette.black },
236
+ paddingX: 'base',
237
+ paddingY: 'small',
238
+ gap: 'extraSmall',
239
+ borderWidth: 'extraSmall',
240
+ borderRadius: 'base',
241
+ },
242
+ chip: {
243
+ color: 'primary',
244
+ size: 'extraSmall',
245
+ weight: 'bold',
246
+ tint: '12%',
247
+ paddingX: 'base',
248
+ paddingY: 'extraSmall',
249
+ gap: 'extraSmall',
250
+ borderRadius: '999px',
251
+ uppercase: true,
252
+ },
253
+ input: {
254
+ color: 'primary',
255
+ size: 'small',
256
+ weight: 'base',
257
+ paddingX: 'base',
258
+ paddingY: 'small',
259
+ borderWidth: 'extraSmall',
260
+ borderRadius: 'base',
261
+ },
262
+ textarea: {
263
+ color: 'primary',
264
+ size: 'small',
265
+ weight: 'base',
266
+ paddingX: 'base',
267
+ paddingY: 'small',
268
+ borderWidth: 'extraSmall',
269
+ borderRadius: 'base',
270
+ resize: false,
271
+ rows: 3,
272
+ },
273
+ select: {
274
+ color: 'primary',
275
+ size: 'small',
276
+ weight: 'base',
277
+ paddingX: 'base',
278
+ paddingY: 'small',
279
+ borderWidth: 'extraSmall',
280
+ borderRadius: 'base',
281
+ listMaxHeight: '16rem',
282
+ listOffset: 'extraSmall',
283
+ optionPaddingX: 'base',
284
+ optionPaddingY: 'extraSmall',
285
+ listZIndex: 1100,
286
+ listShadow: '0 8px 24px rgb(0 0 0 / 35%)',
287
+ },
288
+ switch: {
289
+ color: 'primary',
290
+ size: 'base',
291
+ ink: palette.white,
292
+ widthFactor: 2,
293
+ insetFactor: 0.2,
294
+ borderRadius: '999px',
295
+ },
296
+ checkbox: {
297
+ color: 'primary',
298
+ size: 'base',
299
+ ink: palette.black,
300
+ borderWidth: 'extraSmall',
301
+ borderRadius: 'small',
302
+ },
303
+ radio: {
304
+ color: 'primary',
305
+ size: 'base',
306
+ borderWidth: 'extraSmall',
307
+ borderRadius: '999px',
308
+ insetFactor: 0.22,
309
+ },
310
+ },
311
+ })
312
+ ```