@cronocode/react-box 3.2.0 → 3.2.1

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/BOX_AI_CONTEXT.md CHANGED
@@ -1,213 +1,74 @@
1
1
  # @cronocode/react-box - AI Assistant Context
2
2
 
3
- Reference document for AI assistants helping developers use this runtime CSS-in-JS library.
3
+ Runtime CSS-in-JS library. `Box` component accepts ~144 CSS props and generates CSS classes at runtime. Same prop values share a single class.
4
4
 
5
5
  ---
6
6
 
7
- ## CRITICAL RULES (Read First!)
7
+ ## CRITICAL RULES
8
8
 
9
9
  ### Rule #1: NEVER Use Inline Styles
10
10
 
11
- **WRONG** - Using `style` attribute:
12
-
13
- ```tsx
14
- // DO NOT DO THIS
15
- <Box style={{ minHeight: "100vh", width: "100%" }} />
16
- <Box style={{ pointerEvents: "none" }} />
17
- <Box style={{ alignItems: "center" }} />
18
- <Box style={{ maxWidth: "1200px" }} />
19
- ```
20
-
21
- **CORRECT** - Using Box props:
22
-
23
- ```tsx
24
- // DO THIS INSTEAD
25
- <Box minHeight="fit-screen" width="fit" />
26
- <Box pointerEvents="none" />
27
- <Box ai="center" />
28
- <Box maxWidth={300} />
29
- ```
30
-
31
- **If a prop doesn't exist for a CSS property**, create it using `Box.extend()` in your boxExtends.ts file. Never fall back to inline styles.
32
-
33
- ### Common Style-to-Prop Conversions
34
-
35
- | Inline Style (WRONG) | Box Prop (CORRECT) |
36
- | --------------------------------------------- | ------------------------------------- |
37
- | `style={{ width: "100%" }}` | `width="fit"` |
38
- | `style={{ width: "100vw" }}` | `width="fit-screen"` |
39
- | `style={{ height: "100%" }}` | `height="fit"` |
40
- | `style={{ height: "100vh" }}` | `height="fit-screen"` |
41
- | `style={{ minHeight: "100vh" }}` | `minHeight="fit-screen"` |
42
- | `style={{ maxWidth: "1200px" }}` | `maxWidth={300}` (300/4=75rem=1200px) |
43
- | `style={{ alignItems: "center" }}` | `ai="center"` |
44
- | `style={{ justifyContent: "space-between" }}` | `jc="between"` |
45
- | `style={{ flexDirection: "column" }}` | `d="column"` |
46
- | `style={{ pointerEvents: "none" }}` | `pointerEvents="none"` |
47
- | `style={{ cursor: "pointer" }}` | `cursor="pointer"` |
48
- | `style={{ overflow: "hidden" }}` | `overflow="hidden"` |
49
- | `style={{ position: "relative" }}` | `position="relative"` |
50
- | `style={{ zIndex: 10 }}` | `zIndex={10}` |
51
- | `style={{ opacity: 0.5 }}` | `opacity={0.5}` |
52
-
53
- ### When a Prop Doesn't Exist
54
-
55
- If you need a CSS property that doesn't have a Box prop, extend Box instead of using inline styles:
56
-
57
- ```tsx
58
- // boxExtends.ts - Create new props for missing CSS properties
59
- import Box from '@cronocode/react-box';
60
-
61
- export const { extendedProps, extendedPropTypes } = Box.extend(
62
- {}, // CSS variables (if needed)
63
- {
64
- // Add new props
65
- aspectRatio: [
66
- {
67
- values: ['auto', '1/1', '16/9', '4/3', '3/2'] as const,
68
- styleName: 'aspect-ratio',
69
- valueFormat: (value) => value,
70
- },
71
- ],
72
- backdropBlur: [
73
- {
74
- values: ['none', 'sm', 'md', 'lg'] as const,
75
- styleName: 'backdrop-filter',
76
- valueFormat: (value) => {
77
- const map = { none: 'none', sm: 'blur(4px)', md: 'blur(8px)', lg: 'blur(16px)' };
78
- return map[value];
79
- },
80
- },
81
- ],
82
- },
83
- {}, // Extended existing props (if needed)
84
- );
85
-
86
- // Now use the new props
87
- <Box aspectRatio="16/9" backdropBlur="md" />;
88
- ```
11
+ Always use Box props. If a prop doesn't exist, create it with `Box.extend()` (see Extension System).
12
+
13
+ | Inline Style (WRONG) | Box Prop (CORRECT) |
14
+ |---|---|
15
+ | `style={{ width: "100%" }}` | `width="fit"` |
16
+ | `style={{ width: "100vw" }}` | `width="fit-screen"` |
17
+ | `style={{ height: "100%" }}` | `height="fit"` |
18
+ | `style={{ height: "100vh" }}` | `height="fit-screen"` |
19
+ | `style={{ minHeight: "100vh" }}` | `minHeight="fit-screen"` |
20
+ | `style={{ maxWidth: "1200px" }}` | `maxWidth={300}` (300/4=75rem=1200px) |
21
+ | `style={{ alignItems: "center" }}` | `ai="center"` |
22
+ | `style={{ justifyContent: "space-between" }}` | `jc="between"` |
23
+ | `style={{ flexDirection: "column" }}` | `d="column"` |
24
+ | `style={{ pointerEvents: "none" }}` | `pointerEvents="none"` |
25
+ | `style={{ cursor: "pointer" }}` | `cursor="pointer"` |
26
+ | `style={{ overflow: "hidden" }}` | `overflow="hidden"` |
27
+ | `style={{ position: "relative" }}` | `position="relative"` |
28
+ | `style={{ zIndex: 10 }}` | `zIndex={10}` |
29
+ | `style={{ opacity: 0.5 }}` | `opacity={0.5}` |
89
30
 
90
31
  ### Rule #2: ALWAYS Use Component Shortcuts
91
32
 
92
- **⚠️ NEVER use `<Box tag="...">` when a semantic component exists!**
93
-
94
- The `tag` prop should ONLY be used for rare HTML elements that don't have a component shortcut (e.g., `<Box tag="datalist">`). For all common elements, use the corresponding component.
33
+ NEVER use `<Box tag="...">` when a component exists. NEVER use `<Box display="flex/grid">`.
95
34
 
96
- **WRONG** - Using `tag` prop for common elements:
97
-
98
- ```tsx
99
- // DO NOT DO THIS - These are WRONG even though they work
100
- <Box tag="img" props={{ src: logo, alt: "Logo" }} /> // WRONG!
101
- <Box tag="a" props={{ href: "#" }}>Link</Box> // WRONG!
102
- <Box tag="button" onClick={...}>Click</Box> // WRONG!
103
- <Box tag="nav">...</Box> // WRONG!
104
- <Box tag="h1" fontSize={32}>Title</Box> // WRONG!
105
- <Box tag="p">Text</Box> // WRONG!
106
- <Box tag="span">Inline</Box> // WRONG!
107
- <Box display="flex" gap={4}>...</Box> // WRONG!
108
- <Box display="grid" gridCols={3}>...</Box> // WRONG!
109
- ```
110
-
111
- **CORRECT** - Using semantic components:
112
-
113
- ```tsx
114
- // DO THIS INSTEAD
115
- import { Img, Link, H1, Nav } from '@cronocode/react-box/components/semantics';
116
- import Button from '@cronocode/react-box/components/button';
117
- import Flex from '@cronocode/react-box/components/flex';
118
- import Grid from '@cronocode/react-box/components/grid';
119
-
120
- <Img props={{ src: logo, alt: "Logo" }} />
121
- <Link props={{ href: "#" }}>Link</Link>
122
- <Button onClick={...}>Click</Button>
123
- <Flex gap={4}>...</Flex>
124
- <Grid gridCols={3}>...</Grid>
125
- <Nav>...</Nav>
126
- <H1 fontSize={32}>Title</H1>
127
- ```
35
+ | Instead of... | Use... | Import from |
36
+ |---|---|---|
37
+ | `<Box display="flex">` | `<Flex>` | `components/flex` |
38
+ | `<Box display="grid">` | `<Grid>` | `components/grid` |
39
+ | `<Box tag="button">` | `<Button>` | `components/button` |
40
+ | `<Box tag="input">` | `<Textbox>` | `components/textbox` |
41
+ | `<Box tag="textarea">` | `<Textarea>` | `components/textarea` |
42
+ | `<Box tag="a/img/label">` | `<Link>/<Img>/<Label>` | `components/semantics` |
43
+ | `<Box tag="h1/h2/h3/h4/h5/h6">` | `<H1>/<H2>/.../<H6>` | `components/semantics` |
44
+ | `<Box tag="p/span">` | `<P>/<Span>` | `components/semantics` |
45
+ | `<Box tag="nav/header/footer/main">` | `<Nav>/<Header>/<Footer>/<Main>` | `components/semantics` |
46
+ | `<Box tag="section/article/aside">` | `<Section>/<Article>/<Aside>` | `components/semantics` |
128
47
 
129
- ### Component Shortcuts Reference
130
-
131
- | Instead of... | Use... | Import from |
132
- | ---------------------- | ------------ | ------------------------------------------- |
133
- | `<Box display="flex">` | `<Flex>` | `@cronocode/react-box/components/flex` |
134
- | `<Box display="grid">` | `<Grid>` | `@cronocode/react-box/components/grid` |
135
- | `<Box tag="button">` | `<Button>` | `@cronocode/react-box/components/button` |
136
- | `<Box tag="input">` | `<Textbox>` | `@cronocode/react-box/components/textbox` |
137
- | `<Box tag="textarea">` | `<Textarea>` | `@cronocode/react-box/components/textarea` |
138
- | `<Box tag="a">` | `<Link>` | `@cronocode/react-box/components/semantics` |
139
- | `<Box tag="img">` | `<Img>` | `@cronocode/react-box/components/semantics` |
140
- | `<Box tag="h1">` | `<H1>` | `@cronocode/react-box/components/semantics` |
141
- | `<Box tag="h2">` | `<H2>` | `@cronocode/react-box/components/semantics` |
142
- | `<Box tag="h3">` | `<H3>` | `@cronocode/react-box/components/semantics` |
143
- | `<Box tag="p">` | `<P>` | `@cronocode/react-box/components/semantics` |
144
- | `<Box tag="span">` | `<Span>` | `@cronocode/react-box/components/semantics` |
145
- | `<Box tag="nav">` | `<Nav>` | `@cronocode/react-box/components/semantics` |
146
- | `<Box tag="header">` | `<Header>` | `@cronocode/react-box/components/semantics` |
147
- | `<Box tag="footer">` | `<Footer>` | `@cronocode/react-box/components/semantics` |
148
- | `<Box tag="main">` | `<Main>` | `@cronocode/react-box/components/semantics` |
149
- | `<Box tag="section">` | `<Section>` | `@cronocode/react-box/components/semantics` |
150
- | `<Box tag="article">` | `<Article>` | `@cronocode/react-box/components/semantics` |
151
- | `<Box tag="aside">` | `<Aside>` | `@cronocode/react-box/components/semantics` |
152
- | `<Box tag="label">` | `<Label>` | `@cronocode/react-box/components/semantics` |
48
+ All imports from `@cronocode/react-box/components/...`. Semantics also export: `Mark`, `Figure`, `Figcaption`, `Details`, `Summary`, `Menu`, `Time`.
153
49
 
154
50
  ---
155
51
 
156
- ## Quick Overview
52
+ ## Numeric Value Formatters
157
53
 
158
- **What it is**: A React library that converts component props directly into CSS classes at runtime. No CSS files needed.
54
+ **#1 source of confusion.** Different props have different dividers:
159
55
 
160
- **Core component**: `Box` - a polymorphic component that accepts ~144 CSS props and renders any HTML element.
56
+ | Prop Category | Divider | Example | CSS Output |
57
+ |---|---|---|---|
58
+ | Spacing (`p`, `m`, `gap`, `px`, `py`, `mx`, `my`, etc.) | 4 | `p={4}` | `padding: 1rem` (16px) |
59
+ | Font size (`fontSize`) | **16** | `fontSize={14}` | `font-size: 0.875rem` (14px) |
60
+ | Width/Height (numeric) | 4 | `width={20}` | `width: 5rem` (80px) |
61
+ | Border width (`b`, `bx`, `by`, `bt`, `br`, `bb`, `bl`) | none | `b={1}` | `border-width: 1px` |
62
+ | Border radius (`borderRadius`) | none | `borderRadius={8}` | `border-radius: 8px` |
63
+ | Line height (`lineHeight`) | none | `lineHeight={24}` | `line-height: 24px` |
161
64
 
162
65
  ```tsx
163
- import Box from '@cronocode/react-box';
164
-
165
- // Basic usage
166
- <Box p={4} bgColor="blue-500" color="white">Content</Box>
167
-
168
- // Renders as different elements
169
- <Box tag="button" p={3}>Button</Box>
170
- <Box tag="a" props={{ href: '/link' }}>Link</Box>
66
+ // fontSize: divider 16 → value maps directly to px
67
+ fontSize={12} // 12px fontSize={14} // 14px fontSize={16} // 16px
68
+ fontSize={18} // 18px fontSize={24} // 24px fontSize={32} // 32px
171
69
 
172
- // Alias components (recommended)
173
- import Button from '@cronocode/react-box/components/button';
174
- <Button p={3}>Button</Button>
175
- ```
176
-
177
- ---
178
-
179
- ## Critical: Numeric Value Formatters
180
-
181
- **This is the #1 source of confusion.** Different props have different dividers:
182
-
183
- | Prop Category | Divider | Formula | Example | CSS Output |
184
- | ------------------------------------------------------- | ------- | ------------ | ------------------ | ------------------------ |
185
- | Spacing (`p`, `m`, `gap`, `px`, `py`, `mx`, `my`, etc.) | 4 | value/4 rem | `p={4}` | `padding: 1rem` (16px) |
186
- | Font size (`fontSize`) | **16** | value/16 rem | `fontSize={16}` | `font-size: 1rem` (16px) |
187
- | Width/Height (numeric) | 4 | value/4 rem | `width={20}` | `width: 5rem` (80px) |
188
- | Border width (`b`, `bx`, `by`, `bt`, `br`, `bb`, `bl`) | none | direct px | `b={1}` | `border-width: 1px` |
189
- | Border radius (`borderRadius`) | none | direct px | `borderRadius={8}` | `border-radius: 8px` |
190
-
191
- ### Common fontSize Values
192
-
193
- ```tsx
194
- fontSize={12} // → 0.75rem ≈ 12px (small)
195
- fontSize={14} // → 0.875rem ≈ 14px (body)
196
- fontSize={16} // → 1rem = 16px (default)
197
- fontSize={18} // → 1.125rem ≈ 18px (large)
198
- fontSize={24} // → 1.5rem = 24px (h2)
199
- fontSize={32} // → 2rem = 32px (h1)
200
- ```
201
-
202
- ### Common Spacing Values (divider = 4)
203
-
204
- ```tsx
205
- p={1} // → 0.25rem = 4px
206
- p={2} // → 0.5rem = 8px
207
- p={3} // → 0.75rem = 12px
208
- p={4} // → 1rem = 16px
209
- p={6} // → 1.5rem = 24px
210
- p={8} // → 2rem = 32px
70
+ // Spacing: divider 4 → value/4 = rem
71
+ p={1} // 4px p={2} // 8px p={3} // 12px p={4} // 16px p={6} // 24px p={8} // 32px
211
72
  ```
212
73
 
213
74
  ---
@@ -216,375 +77,116 @@ p={8} // → 2rem = 32px
216
77
 
217
78
  ### Spacing
218
79
 
219
- | Prop | CSS Property |
220
- | ---------------------- | ----------------------------- |
221
- | `p` | padding |
222
- | `px` | padding-left + padding-right |
223
- | `py` | padding-top + padding-bottom |
224
- | `pt`, `pr`, `pb`, `pl` | padding-top/right/bottom/left |
225
- | `m` | margin |
226
- | `mx`, `my` | margin horizontal/vertical |
227
- | `mt`, `mr`, `mb`, `ml` | margin-top/right/bottom/left |
228
- | `gap` | gap (flexbox/grid) |
80
+ | Prop | CSS Property |
81
+ |---|---|
82
+ | `p` / `px` / `py` / `pt` / `pr` / `pb` / `pl` | padding (all / horizontal / vertical / individual) |
83
+ | `m` / `mx` / `my` / `mt` / `mr` / `mb` / `ml` | margin (all / horizontal / vertical / individual) |
84
+ | `gap` | gap (flexbox/grid) |
229
85
 
230
86
  ### Layout
231
87
 
232
- | Prop | CSS Property | Values |
233
- | --------- | --------------- | -------------------------------------------------------------------------- |
234
- | `display` | display | `'flex'`, `'block'`, `'inline'`, `'grid'`, `'none'`, `'inline-flex'`, etc. |
235
- | `d` | flex-direction | `'row'`, `'column'`, `'row-reverse'`, `'column-reverse'` |
236
- | `wrap` | flex-wrap | `'wrap'`, `'nowrap'`, `'wrap-reverse'` |
237
- | `ai` | align-items | `'center'`, `'start'`, `'end'`, `'stretch'`, `'baseline'` |
238
- | `jc` | justify-content | `'center'`, `'start'`, `'end'`, `'between'`, `'around'`, `'evenly'` |
239
- | `flex` | flex | number or string |
240
- | `grow` | flex-grow | number |
241
- | `shrink` | flex-shrink | number |
88
+ | Prop | CSS Property | Values |
89
+ |---|---|---|
90
+ | `display` | display | `'flex'`, `'block'`, `'inline'`, `'grid'`, `'none'`, `'inline-flex'`, etc. |
91
+ | `d` | flex-direction | `'row'`, `'column'`, `'row-reverse'`, `'column-reverse'` |
92
+ | `wrap` | flex-wrap | `'wrap'`, `'nowrap'`, `'wrap-reverse'` |
93
+ | `ai` | align-items | `'center'`, `'start'`, `'end'`, `'stretch'`, `'baseline'` |
94
+ | `jc` | justify-content | `'center'`, `'start'`, `'end'`, `'between'`, `'around'`, `'evenly'` |
95
+ | `flex` / `grow` / `shrink` | flex / flex-grow / flex-shrink | number or string |
242
96
 
243
97
  ### Sizing
244
98
 
245
- | Prop | CSS Property | Accepts |
246
- | ------------------------ | -------------- | ------------------------------------------------------------------- |
247
- | `width` | width | number (rem/4), string (`'auto'`, `'1/2'`, `'fit'`, `'fit-screen'`) |
248
- | `height` | height | number (rem/4), string (`'auto'`, `'fit'`, `'fit-screen'`) |
249
- | `minWidth`, `maxWidth` | min/max-width | number or string |
250
- | `minHeight`, `maxHeight` | min/max-height | number or string |
251
-
252
- **Percentage/Fraction Values:**
253
-
254
- All sizing, spacing, and positioning props accept percentage strings:
255
-
256
- ```tsx
257
- // Sizing
258
- width="33%" // 33%
259
- height="50%" // 50%
260
- minWidth="20%" // min-width: 20%
261
- maxWidth="80%" // max-width: 80%
262
- minHeight="10%" // min-height: 10%
263
- maxHeight="90%" // max-height: 90%
264
-
265
- // Spacing (margin, padding, gap)
266
- p="5%" // padding: 5%
267
- px="10%" // padding-left/right: 10%
268
- m="10%" // margin: 10%
269
- mt="5%" // margin-top: 5%
270
- gap="2%" // gap: 2%
271
-
272
- // Positioning
273
- top="10%" // → top: 10%
274
- left="50%" // → left: 50%
275
- right="0%" // → right: 0%
276
- bottom="20%" // → bottom: 20%
277
-
278
- // Fraction shortcuts (width/height only)
279
- width="1/2" // → 50%
280
- width="1/3" // → 33.333%
281
- width="2/3" // → 66.666%
282
- width="1/4" // → 25%
283
- width="3/4" // → 75%
284
-
285
- // Special values
286
- width="fit" // → 100%
287
- width="fit-screen" // → 100vw
288
- height="fit" // → 100%
289
- height="fit-screen"// → 100vh
290
- ```
291
-
292
- ### Colors (Tailwind-like palette)
293
-
294
- | Prop | CSS Property |
295
- | ------------- | ---------------- |
296
- | `bgColor` | background-color |
297
- | `color` | color |
298
- | `borderColor` | border-color |
299
-
300
- **Color values**: `'gray-50'` through `'gray-900'`, same for `red`, `orange`, `yellow`, `green`, `teal`, `blue`, `indigo`, `purple`, `pink`, `violet`.
301
-
302
- Also: `'white'`, `'black'`, `'transparent'`, `'inherit'`, `'currentColor'`
303
-
304
- ### Borders
305
-
306
- | Prop | CSS Property |
307
- | ---------------------- | ---------------------------------------------------------- |
308
- | `b` | border-width (all sides) |
309
- | `bx` | border-left-width + border-right-width |
310
- | `by` | border-top-width + border-bottom-width |
311
- | `bt`, `br`, `bb`, `bl` | individual sides |
312
- | `borderRadius` | border-radius |
313
- | `borderStyle` | border-style (`'solid'`, `'dashed'`, `'dotted'`, `'none'`) |
314
-
315
- ### Typography
316
-
317
- | Prop | CSS Property |
318
- | ---------------- | ------------------------------------------------------------ |
319
- | `fontSize` | font-size (divider: 16) |
320
- | `fontWeight` | font-weight (`400`, `500`, `600`, `700`, etc.) |
321
- | `lineHeight` | line-height (number = pixels, e.g. `lineHeight={24}` → 24px) |
322
- | `textAlign` | text-align |
323
- | `textDecoration` | text-decoration |
324
- | `textTransform` | text-transform |
325
- | `whiteSpace` | white-space |
326
- | `overflow` | overflow |
327
- | `textOverflow` | text-overflow |
328
-
329
- ### Positioning
330
-
331
- | Prop | CSS Property |
332
- | -------------------------------- | ------------------------------------------------------------ |
333
- | `position` | position (`'relative'`, `'absolute'`, `'fixed'`, `'sticky'`) |
334
- | `top`, `right`, `bottom`, `left` | positioning offsets |
335
- | `zIndex` | z-index |
336
-
337
- ### Effects
338
-
339
- | Prop | CSS Property |
340
- | --------------- | --------------------------------------------------------------- |
341
- | `shadow` | box-shadow (`'small'`, `'medium'`, `'large'`, `'xl'`, `'none'`) |
342
- | `opacity` | opacity |
343
- | `cursor` | cursor |
344
- | `pointerEvents` | pointer-events |
345
- | `transition` | transition |
346
- | `transform` | transform |
99
+ | Prop | CSS Property | Accepts |
100
+ |---|---|---|
101
+ | `width` / `height` | width / height | number (rem/4), `'auto'`, `'fit'` (100%), `'fit-screen'` (100vw/vh), fractions (`'1/2'`, `'1/3'`, `'2/3'`, `'1/4'`, `'3/4'`), percentages (`'33%'`) |
102
+ | `minWidth` / `maxWidth` / `minHeight` / `maxHeight` | min/max sizing | number or string |
103
+
104
+ All sizing, spacing, and positioning props also accept percentage strings: `p="5%"`, `top="10%"`, `gap="2%"`.
105
+
106
+ ### Visual
107
+
108
+ | Prop | CSS Property | Notes |
109
+ |---|---|---|
110
+ | `bgColor` / `color` / `borderColor` | background-color / color / border-color | Tailwind palette: `'gray-50'`..`'gray-900'`, same for red/orange/yellow/green/teal/blue/indigo/purple/pink/violet. Also `'white'`, `'black'`, `'transparent'`, `'currentColor'` |
111
+ | `b` / `bx` / `by` / `bt` / `br` / `bb` / `bl` | border-width | direct px |
112
+ | `borderRadius` | border-radius | direct px |
113
+ | `borderStyle` | border-style | `'solid'`, `'dashed'`, `'dotted'`, `'none'` |
114
+ | `fontSize` | font-size | divider 16 |
115
+ | `fontWeight` | font-weight | `400`, `500`, `600`, `700`, etc. |
116
+ | `lineHeight` | line-height | direct px |
117
+ | `textAlign` / `textDecoration` / `textTransform` / `whiteSpace` / `textOverflow` | text properties | string values |
118
+ | `overflow` | overflow | `'hidden'`, `'auto'`, `'scroll'`, `'visible'` |
119
+ | `position` | position | `'relative'`, `'absolute'`, `'fixed'`, `'sticky'` |
120
+ | `top` / `right` / `bottom` / `left` / `inset` | positioning offsets | number or string |
121
+ | `zIndex` | z-index | number |
122
+ | `shadow` | box-shadow | `'small'`, `'medium'`, `'large'`, `'xl'`, `'none'` |
123
+ | `opacity` | opacity | number |
124
+ | `cursor` / `pointerEvents` / `transition` / `transform` / `userSelect` | misc | string values |
347
125
 
348
126
  ---
349
127
 
350
- ## Pseudo-Classes
351
-
352
- Apply styles on interaction states:
128
+ ## Pseudo-Classes, Breakpoints & Themes
353
129
 
354
130
  ```tsx
355
- <Box
356
- bgColor="blue-500"
357
- hover={{ bgColor: 'blue-600' }}
358
- focus={{ outline: 'none', ring: 2 }}
359
- active={{ bgColor: 'blue-700' }}
360
- disabled={{ opacity: 0.5, cursor: 'not-allowed' }}
361
- />
362
- ```
131
+ // Pseudo-classes: hover, focus, active, disabled, checked, indeterminate, required, selected,
132
+ // focusWithin, focusVisible, first, last, even, odd, empty
133
+ <Box bgColor="blue-500" hover={{ bgColor: 'blue-600' }} disabled={{ opacity: 0.5 }} />
363
134
 
364
- **Available pseudo-classes**: `hover`, `focus`, `active`, `disabled`, `checked`, `indeterminate`, `required`, `selected`, `focusWithin`, `focusVisible`, `first`, `last`, `even`, `odd`, `empty`
365
-
366
- ---
135
+ // Responsive breakpoints (mobile-first): sm(640) md(768) lg(1024) xl(1280) xxl(1536)
136
+ <Box p={2} md={{ p: 4 }} lg={{ p: 6 }} />
367
137
 
368
- ## Responsive Breakpoints
369
-
370
- Mobile-first breakpoints using min-width media queries:
371
-
372
- ```tsx
373
- <Box
374
- p={2} // Base (mobile)
375
- sm={{ p: 3 }} // ≥640px
376
- md={{ p: 4 }} // ≥768px
377
- lg={{ p: 6 }} // ≥1024px
378
- xl={{ p: 8 }} // ≥1280px
379
- xxl={{ p: 10 }} // ≥1536px
380
- />
138
+ // Combine: breakpoints can nest pseudo-classes
139
+ <Box bgColor="white" hover={{ bgColor: 'gray-100' }} md={{ bgColor: 'gray-50', hover: { bgColor: 'gray-200' } }} />
381
140
  ```
382
141
 
383
- **Combine with pseudo-classes**:
384
-
385
- ```tsx
386
- <Box
387
- bgColor="white"
388
- hover={{ bgColor: 'gray-100' }}
389
- md={{
390
- bgColor: 'gray-50',
391
- hover: { bgColor: 'gray-200' },
392
- }}
393
- />
394
- ```
395
-
396
- ---
397
-
398
- ## Theme System
399
-
400
- ### Setting Up Themes
401
-
402
- The `Box.Theme` component provides theme management with automatic detection based on system preferences.
403
-
404
- **Auto-detect theme** (recommended):
405
-
406
- ```tsx
407
- import Box from '@cronocode/react-box';
408
-
409
- function App() {
410
- return (
411
- <Box.Theme>
412
- {/* Theme auto-detects from prefers-color-scheme: 'light' or 'dark' */}
413
- <YourApp />
414
- </Box.Theme>
415
- );
416
- }
417
- ```
418
-
419
- **Explicit theme**:
420
-
421
- ```tsx
422
- <Box.Theme theme="dark">
423
- <YourApp />
424
- </Box.Theme>
425
- ```
426
-
427
- **Global vs Local theme**:
428
-
429
- ```tsx
430
- // Global: Adds theme class to document.documentElement (affects entire page)
431
- <Box.Theme theme="dark" use="global">
432
- <YourApp />
433
- </Box.Theme>
434
-
435
- // Local (default): Wraps children in a Box with theme class (scoped)
436
- <Box.Theme theme="dark" use="local">
437
- <Section>Content</Section>
438
- </Box.Theme>
439
- ```
440
-
441
- ### Using the Theme Hook
142
+ ### Theme System
442
143
 
443
144
  ```tsx
444
145
  import Box from '@cronocode/react-box';
445
146
 
446
- function ThemeSwitcher() {
447
- const [theme, setTheme] = Box.useTheme();
448
-
449
- return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Current: {theme}</button>;
450
- }
451
-
452
- // Must be used within Box.Theme provider
453
- function App() {
454
- return (
455
- <Box.Theme>
456
- <ThemeSwitcher />
457
- </Box.Theme>
458
- );
459
- }
460
- ```
461
-
462
- ### Theme-Aware Styles
463
-
464
- Apply styles based on the active theme:
465
-
466
- ```tsx
467
- <Box
468
- bgColor="white"
469
- color="gray-900"
470
- theme={{
471
- dark: {
472
- bgColor: 'gray-900',
473
- color: 'gray-100',
474
- },
475
- }}
476
- />
477
- ```
147
+ // Setup: wrap app in Box.Theme (auto-detects via prefers-color-scheme)
148
+ <Box.Theme> {/* auto light/dark detection */}
149
+ <Box.Theme theme="dark"> {/* explicit theme, ignores system pref */}
150
+ <Box.Theme theme="dark" use="global"> {/* applies class + data-theme to <html> */}
151
+ <Box.Theme storageKey="app-theme"> {/* persists user choice to localStorage */}
478
152
 
479
- ### Theme + Pseudo-Classes
153
+ // Hook: read + set theme programmatically (must be within Box.Theme)
154
+ const [theme, setTheme] = Box.useTheme();
155
+ setTheme('dark'); // set explicit theme (persists to localStorage if storageKey provided)
156
+ setTheme(null); // reset to system auto-detection (clears localStorage)
480
157
 
481
- Combine theme styles with pseudo-classes:
158
+ // Supports any custom theme name not limited to 'light'/'dark'
159
+ <Box.Theme theme="high-contrast">
482
160
 
483
- ```tsx
161
+ // Theme-aware styles — nests with pseudo-classes and breakpoints
484
162
  <Box
485
- bgColor="white"
163
+ bgColor="white" color="gray-900"
486
164
  hover={{ bgColor: 'gray-100' }}
487
- theme={{
488
- dark: {
489
- bgColor: 'gray-800',
490
- hover: { bgColor: 'gray-700' },
491
- },
492
- }}
165
+ theme={{ dark: { bgColor: 'gray-900', color: 'gray-100', hover: { bgColor: 'gray-700' } } }}
493
166
  />
494
167
  ```
495
168
 
496
- ### Theme + Responsive Breakpoints
497
-
498
- Combine all three systems:
499
-
500
- ```tsx
501
- <Box
502
- p={4}
503
- bgColor="white"
504
- md={{
505
- p: 6,
506
- bgColor: 'gray-50',
507
- }}
508
- theme={{
509
- dark: {
510
- bgColor: 'gray-900',
511
- md: {
512
- bgColor: 'gray-800',
513
- },
514
- },
515
- }}
516
- />
517
- ```
169
+ **Props**: `theme?` (string explicit theme name), `use?` (`'global'`|`'local'`, default `'local'`), `storageKey?` (string — localStorage key for persistence).
170
+ **DOM**: Sets `data-theme` attribute and theme class on wrapper (local) or `document.documentElement` (global). Cleaned up on unmount.
518
171
 
519
172
  ---
520
173
 
521
174
  ## Component System
522
175
 
523
- ### Using Built-in Components
524
-
525
- ```tsx
526
- <Box component="button" variant="primary">Click me</Box>
527
- <Box component="button.icon" />
528
- ```
529
-
530
- ### Pre-built Components
531
-
532
- Import ready-to-use components:
533
-
534
176
  ```tsx
535
- // Form components
536
- import Button from '@cronocode/react-box/components/button';
537
- import Textbox from '@cronocode/react-box/components/textbox';
538
- import Checkbox from '@cronocode/react-box/components/checkbox';
539
- import RadioButton from '@cronocode/react-box/components/radioButton';
540
- import Dropdown from '@cronocode/react-box/components/dropdown';
541
- import Tooltip from '@cronocode/react-box/components/tooltip';
542
-
543
- // Layout
544
- import Flex from '@cronocode/react-box/components/flex'; // Box with display="flex" (supports inline prop)
545
- import Grid from '@cronocode/react-box/components/grid'; // Box with display="grid" (supports inline prop)
546
-
547
- <Button variant="primary">Submit</Button>
548
- <Textbox placeholder="Enter text..." />
549
- <Flex gap={4} ai="center">...</Flex>
550
- <Flex inline gap={2}>Inline flex</Flex>
551
- ```
552
-
553
- ### Semantic HTML Components
554
-
555
- Alias components for semantic HTML elements (from `components/semantics`):
556
-
557
- ```tsx
558
- import {
559
- // Typography
560
- P, H1, H2, H3, H4, H5, H6, Span, Mark,
561
- // Structure
562
- Header, Footer, Main, Nav, Section, Article, Aside,
563
- // Other
564
- Label, Link, Img, Figure, Figcaption, Details, Summary, Menu, Time,
565
- } from '@cronocode/react-box/components/semantics';
566
-
567
- // These are Box aliases with the correct HTML tag
568
- <H1 fontSize={32} fontWeight={700}>Title</H1>
569
- <P color="gray-600">Paragraph text</P>
570
- <Link props={{ href: '/about' }} color="blue-500">About</Link>
571
- <Flex tag="nav" gap={4}>...</Flex>
177
+ // Component + variant props apply registered styles from Box.components()
178
+ <Box component="card" variant="bordered">
179
+ <Box component="card.header">Title</Box>
180
+ <Box component="card.body">Content</Box>
181
+ </Box>
572
182
  ```
573
183
 
574
- ### Custom Components with Box.components()
184
+ ### Box.components() — Define Custom Component Styles
575
185
 
576
186
  ```tsx
577
- // Define custom component styles
578
187
  Box.components({
579
188
  card: {
580
- styles: {
581
- display: 'flex',
582
- d: 'column',
583
- p: 4,
584
- bgColor: 'white',
585
- borderRadius: 8,
586
- shadow: 'medium',
587
- },
189
+ styles: { display: 'flex', d: 'column', p: 4, bgColor: 'white', borderRadius: 8, shadow: 'medium' },
588
190
  variants: {
589
191
  bordered: { b: 1, borderColor: 'gray-200', shadow: 'none' },
590
192
  elevated: { shadow: 'large' },
@@ -595,148 +197,78 @@ Box.components({
595
197
  },
596
198
  },
597
199
  });
598
-
599
- // Use it
600
- <Box component="card" variant="bordered">
601
- <Box component="card.header">Title</Box>
602
- <Box component="card.body">Content</Box>
603
- </Box>;
604
200
  ```
605
201
 
606
- #### Component Inheritance with `extends`
607
-
608
- Components can inherit from other components using the `extends` property. The base component's full style tree (styles, variants, children) is used as the foundation, and the extending component's definitions are deep-merged on top:
202
+ ### Component Inheritance with `extends`
609
203
 
610
204
  ```tsx
611
205
  Box.components({
612
206
  subgrid: {
613
- extends: 'datagrid', // inherit all datagrid styles, variants, and children
207
+ extends: 'datagrid', // inherits all styles, variants, and children; deep-merges overrides
614
208
  styles: { b: 0, borderRadius: 0, shadow: 'none' },
615
- children: {
616
- header: { children: { cell: { styles: { fontSize: 12 } } } },
617
- },
209
+ children: { header: { children: { cell: { styles: { fontSize: 12 } } } } },
618
210
  },
619
211
  });
620
212
  ```
621
213
 
622
- This is especially useful for DataGrid, where the internal style tree is complex (pinning, sticky headers, hover groups, etc.). With `extends`, you only need to declare overrides.
623
-
624
214
  ---
625
215
 
626
216
  ## Extension System
627
217
 
628
- ### Adding Custom Colors/Variables
218
+ ### Box.extend() Add New Props, Colors, Variables
629
219
 
630
220
  ```tsx
631
221
  import Box from '@cronocode/react-box';
632
222
 
633
223
  export const { extendedProps, extendedPropTypes } = Box.extend(
634
- // Custom CSS variables
635
- {
636
- 'brand-primary': '#ff6600',
637
- 'brand-secondary': '#0066ff',
224
+ { 'brand-primary': '#ff6600', 'brand-secondary': '#0066ff' }, // CSS variables
225
+ { // New props
226
+ aspectRatio: [{
227
+ values: ['auto', '1/1', '16/9', '4/3'] as const,
228
+ styleName: 'aspect-ratio',
229
+ valueFormat: (value) => value,
230
+ }],
638
231
  },
639
-
640
- // New props (optional)
641
- {},
642
-
643
- // Extend existing props with new values
644
- {
645
- bgColor: [
646
- {
647
- values: ['brand-primary', 'brand-secondary'] as const,
648
- styleName: 'background-color',
649
- valueFormat: (value, getVariable) => getVariable(value),
650
- },
651
- ],
652
- color: [
653
- {
654
- values: ['brand-primary', 'brand-secondary'] as const,
655
- styleName: 'color',
656
- valueFormat: (value, getVariable) => getVariable(value),
657
- },
658
- ],
232
+ { // Extend existing props with new values
233
+ bgColor: [{
234
+ values: ['brand-primary', 'brand-secondary'] as const,
235
+ styleName: 'background-color',
236
+ valueFormat: (value, getVariable) => getVariable(value),
237
+ }],
238
+ color: [{
239
+ values: ['brand-primary', 'brand-secondary'] as const,
240
+ styleName: 'color',
241
+ valueFormat: (value, getVariable) => getVariable(value),
242
+ }],
659
243
  },
660
244
  );
661
-
662
- // Now use your custom colors
663
- <Box bgColor="brand-primary" color="white">
664
- Branded
665
- </Box>;
666
245
  ```
667
246
 
668
- ### Per-Property Values (Multi-styleName with Different Values)
247
+ ### Per-Property Values (Multi-styleName)
669
248
 
670
- When a `BoxStyle` has `styleName` as an array, `valueFormat` is called once **per CSS property** with the current `styleName` as the third argument. This lets a single prop generate multiple CSS properties with different values:
249
+ When `styleName` is an array, `valueFormat` is called once per CSS property with `styleName` as the third argument. Use for typography presets or any design token spanning multiple CSS properties:
671
250
 
672
251
  ```tsx
673
- const textStyleNames = ['display-lg', 'display-sm'] as const;
674
-
675
252
  Box.extend(
676
- {
677
- 'text-display-lg-size': '36px',
678
- 'text-display-lg-weight': '700',
679
- 'text-display-lg-line-height': '1.2',
680
- 'text-display-lg-letter-spacing': '-0.02em',
681
- 'text-display-sm-size': '28px',
682
- 'text-display-sm-weight': '700',
683
- 'text-display-sm-line-height': '1.25',
684
- 'text-display-sm-letter-spacing': '-0.015em',
685
- },
686
- {
687
- textStyle: [
688
- {
689
- values: textStyleNames,
690
- styleName: ['font-size', 'font-weight', 'line-height', 'letter-spacing'],
691
- valueFormat: (value, getVariable, styleName) => {
692
- const suffixMap: Record<string, string> = {
693
- 'font-size': 'size',
694
- 'font-weight': 'weight',
695
- 'line-height': 'line-height',
696
- 'letter-spacing': 'letter-spacing',
697
- };
698
- return getVariable(`text-${value}-${suffixMap[styleName!]}`);
699
- },
253
+ { 'text-display-lg-size': '36px', 'text-display-lg-weight': '700',
254
+ 'text-display-lg-line-height': '1.2', 'text-display-lg-letter-spacing': '-0.02em' },
255
+ { textStyle: [{
256
+ values: ['display-lg', 'display-sm'] as const,
257
+ styleName: ['font-size', 'font-weight', 'line-height', 'letter-spacing'],
258
+ valueFormat: (value, getVariable, styleName) => {
259
+ const suffix = { 'font-size': 'size', 'font-weight': 'weight', 'line-height': 'line-height', 'letter-spacing': 'letter-spacing' };
260
+ return getVariable(`text-${value}-${suffix[styleName!]}`);
700
261
  },
701
- ],
702
- },
262
+ }] },
703
263
  {},
704
264
  );
705
-
706
- // Usage — one prop sets four CSS properties with different values
707
- <Box textStyle="display-lg" />
708
- // Generates: font-size:var(--text-display-lg-size); font-weight:var(--text-display-lg-weight);
709
- // line-height:var(--text-display-lg-line-height); letter-spacing:var(--text-display-lg-letter-spacing)
265
+ // <Box textStyle="display-lg" /> → sets all 4 CSS properties
710
266
  ```
711
267
 
712
- **Note**: The `styleName` parameter is optional and only available on string-valued `BoxStyle` definitions (the ones with `getVariableValue` as the second parameter). For single `styleName` props, `valueFormat` still works exactly as before.
713
-
714
- **When to use style grouping**: Always use this feature via `Box.extend()` when multiple CSS properties logically belong together and should always be applied as a unit. Typography presets (font-size + font-weight + line-height + letter-spacing), spacing scales, or any design token that spans multiple CSS properties should be defined as a grouped prop rather than relying on developers to set each property individually. This ensures consistency — the grouped properties can't drift apart — and reduces the number of props on each component. When building a project with react-box, define grouped props in your `Box.extend()` setup for any set of styles that always travel together.
715
-
716
268
  ### TypeScript Type Augmentation
717
269
 
718
- **Manual approach** (simple cases):
719
-
720
270
  ```typescript
721
- // types.d.ts
722
- import '@cronocode/react-box/types';
723
-
724
- declare module '@cronocode/react-box/types' {
725
- namespace Augmented {
726
- interface BoxPropTypes {
727
- bgColor: 'brand-primary' | 'brand-secondary';
728
- }
729
- interface ComponentsTypes {
730
- card: 'bordered' | 'elevated';
731
- }
732
- }
733
- }
734
- ```
735
-
736
- **Generic approach** (recommended - auto-extracts types from your definitions):
737
-
738
- ```typescript
739
- // types.d.ts
271
+ // types.d.ts — Generic approach (recommended)
740
272
  import { ExtractComponentsAndVariants, ExtractBoxStyles } from '@cronocode/react-box/types';
741
273
  import { components } from './boxComponents';
742
274
  import { extendedPropTypes, extendedProps } from './boxExtends';
@@ -748,201 +280,103 @@ declare module '@cronocode/react-box/types' {
748
280
  interface ComponentsTypes extends ExtractComponentsAndVariants<typeof components> {}
749
281
  }
750
282
  }
283
+
284
+ // Manual approach (simple cases):
285
+ declare module '@cronocode/react-box/types' {
286
+ namespace Augmented {
287
+ interface BoxPropTypes { bgColor: 'brand-primary' | 'brand-secondary' }
288
+ interface ComponentsTypes { card: 'bordered' | 'elevated' }
289
+ }
290
+ }
751
291
  ```
752
292
 
753
293
  ---
754
294
 
755
295
  ## Common Patterns
756
296
 
757
- ### Flex Container
758
-
759
297
  ```tsx
760
298
  import Flex from '@cronocode/react-box/components/flex';
761
-
762
- <Flex d="column" gap={4} ai="center" jc="between">
763
- {children}
764
- </Flex>;
765
- ```
766
-
767
- ### Card
768
-
769
- ```tsx
770
- <Box p={4} bgColor="white" borderRadius={8} shadow="medium">
771
- {content}
772
- </Box>
773
- ```
774
-
775
- ### Button
776
-
777
- ```tsx
299
+ import Grid from '@cronocode/react-box/components/grid';
778
300
  import Button from '@cronocode/react-box/components/button';
779
-
780
- <Button
781
- px={4}
782
- py={2}
783
- bgColor="blue-500"
784
- color="white"
785
- borderRadius={6}
786
- fontWeight={500}
787
- hover={{ bgColor: 'blue-600' }}
788
- disabled={{ opacity: 0.5, cursor: 'not-allowed' }}
789
- >
790
- Click me
791
- </Button>;
792
- ```
793
-
794
- ### Input Field
795
-
796
- ```tsx
797
301
  import Textbox from '@cronocode/react-box/components/textbox';
798
302
 
799
- <Textbox
800
- placeholder="Enter text..."
801
- width="fit"
802
- px={3}
803
- py={2}
804
- b={1}
805
- borderColor="gray-300"
806
- borderRadius={6}
807
- focus={{ borderColor: 'blue-500', outline: 'none' }}
808
- />;
809
- ```
810
-
811
- ### Grid Layout
303
+ // Flex layout
304
+ <Flex d="column" gap={4} ai="center" jc="between">{children}</Flex>
305
+ <Flex inline gap={2}>Inline flex</Flex>
812
306
 
813
- ```tsx
814
- import Grid from '@cronocode/react-box/components/grid';
307
+ // Responsive stack
308
+ <Flex d="column" gap={2} md={{ d: 'row', gap: 4 }}>{children}</Flex>
815
309
 
816
- <Grid gridCols={3} gap={4}>
817
- {items.map((item) => (
818
- <Box key={item.id}>{item.content}</Box>
819
- ))}
820
- </Grid>;
821
- ```
310
+ // Grid
311
+ <Grid gridCols={3} gap={4}>{items.map(i => <Box key={i.id}>{i.content}</Box>)}</Grid>
822
312
 
823
- ### Responsive Stack
313
+ // Card
314
+ <Box p={4} bgColor="white" borderRadius={8} shadow="medium">{content}</Box>
824
315
 
825
- ```tsx
826
- import Flex from '@cronocode/react-box/components/flex';
316
+ // Button with states
317
+ <Button px={4} py={2} bgColor="blue-500" color="white" borderRadius={6}
318
+ hover={{ bgColor: 'blue-600' }} disabled={{ opacity: 0.5, cursor: 'not-allowed' }}>Click</Button>
827
319
 
828
- <Flex d="column" gap={2} md={{ d: 'row', gap: 4 }}>
829
- {children}
830
- </Flex>;
831
- ```
320
+ // Input
321
+ <Textbox placeholder="Enter..." width="fit" px={3} py={2} b={1} borderColor="gray-300"
322
+ borderRadius={6} focus={{ borderColor: 'blue-500', outline: 'none' }} />
832
323
 
833
- ### Truncated Text
324
+ // Truncated text
325
+ <Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">Long text...</Box>
834
326
 
835
- ```tsx
836
- <Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">
837
- Long text that will be truncated...
838
- </Box>
839
- ```
840
-
841
- ### Overlay/Modal Backdrop
842
-
843
- ```tsx
327
+ // Overlay
844
328
  <Box position="fixed" top={0} left={0} right={0} bottom={0} bgColor="black" opacity={0.5} zIndex={50} />
845
329
  ```
846
330
 
847
- **Note**: For tooltips, dropdowns, and popups that need to escape `overflow: hidden` containers, use the `Tooltip` component instead of manual `zIndex`. It uses React portals to render content outside the DOM hierarchy, avoiding z-index and overflow issues.
848
-
849
- ```tsx
850
- import Tooltip from '@cronocode/react-box/components/tooltip';
851
-
852
- <Tooltip content="Tooltip text">
853
- <Button>Hover me</Button>
854
- </Tooltip>;
855
- ```
856
-
857
- ---
858
-
859
- ## Group Hover (hoverGroup)
331
+ **Portals**: For tooltips/dropdowns that need to escape `overflow: hidden`, use `Tooltip` component (renders via React portal into `#crono-box` container).
860
332
 
861
- Style child elements when parent is hovered:
333
+ ### Group Hover (hoverGroup)
862
334
 
863
335
  ```tsx
864
- // Parent with a className
865
336
  <Flex className="card-row" gap={2}>
866
- {/* Child responds to parent hover */}
867
- <Box opacity={0} hoverGroup={{ 'card-row': { opacity: 1 } }}>
868
- Actions
869
- </Box>
337
+ <Box opacity={0} hoverGroup={{ 'card-row': { opacity: 1 } }}>Actions</Box>
870
338
  </Flex>
871
339
  ```
872
340
 
873
- ---
874
-
875
- ## Server-Side Rendering (SSG/SSR)
341
+ ### Server-Side Rendering
876
342
 
877
343
  ```tsx
878
344
  import { getStyles, resetStyles } from '@cronocode/react-box/ssg';
879
-
880
- // After rendering your app, get generated styles
881
- const cssString = getStyles();
882
-
883
- // Inject into your HTML
884
- <style id="crono-box">{cssString}</style>;
885
-
886
- // Reset for next request (in SSR)
887
- resetStyles();
345
+ const cssString = getStyles(); // after rendering, get CSS string
346
+ <style id="crono-box">{cssString}</style>
347
+ resetStyles(); // reset for next SSR request
888
348
  ```
889
349
 
890
350
  ---
891
351
 
892
352
  ## Key Reminders for AI Assistants
893
353
 
894
- ### ⚠️ MOST IMPORTANT (Common AI Mistakes)
895
-
896
- 1. **NEVER use `style={{ }}` attribute** - Always use Box props instead. If a prop doesn't exist, extend Box with `Box.extend()`
897
- 2. **NEVER use `<Box tag="...">` for common elements** - Use semantic components instead:
898
- - `<Box tag="a">``<Link>`
899
- - `<Box tag="img">``<Img>`
900
- - `<Box tag="button">` `<Button>`
901
- - `<Box tag="h1/h2/h3">``<H1>/<H2>/<H3>`
902
- - `<Box tag="p">` `<P>`
903
- - `<Box tag="nav/header/footer>` `<Nav>/<Header>/<Footer>`
904
- 3. **NEVER use `<Box display="flex/grid">`** - Use `<Flex>` or `<Grid>` components instead
905
-
906
- ### Value Formatting
907
-
908
- 4. **fontSize uses divider 16**, not 4. `fontSize={14}` → 14px, NOT 3.5px
909
- 5. **Spacing uses divider 4**. `p={4}` → 16px (1rem)
910
- 6. **Border width is direct px**. `b={1}` → 1px
911
- 7. **lineHeight is direct px**. `lineHeight={24}` → 24px
912
-
913
- ### Syntax & Patterns
914
-
915
- 8. **Colors are Tailwind-like**: `'gray-500'`, `'blue-600'`, etc.
916
- 9. **Breakpoints are mobile-first**: base → sm → md → lg → xl → xxl
917
- 10. **Theme styles nest**: `theme={{ dark: { hover: { ... } } }}`
918
- 11. **HTML attributes go in `props`**: `<Link props={{ href: '/link' }}>` - NOT directly as Box props
919
- 12. **Percentage widths**: Use strings like `width="1/2"` for 50%
920
- 13. **Full size shortcuts**: `width="fit"` = 100%, `width="fit-screen"` = 100vw
921
- 14. **Box is memoized** with `React.memo` - props comparison is efficient
354
+ 1. **NEVER `style={{ }}`** use Box props. Missing prop? Use `Box.extend()`
355
+ 2. **NEVER `<Box tag="...">` for common elements** — use `<Button>`, `<Link>`, `<H1>`, `<P>`, `<Nav>`, etc.
356
+ 3. **NEVER `<Box display="flex/grid">`** use `<Flex>` / `<Grid>`
357
+ 4. **fontSize divider is 16** (not 4). `fontSize={14}` 14px
358
+ 5. **Spacing divider is 4**. `p={4}`16px (1rem)
359
+ 6. **Border/borderRadius/lineHeight are direct px**. `b={1}`1px
360
+ 7. **Colors are Tailwind-like**: `'gray-500'`, `'blue-600'`
361
+ 8. **Breakpoints are mobile-first**: base sm md → lg → xl → xxl
362
+ 9. **Theme styles nest**: `theme={{ dark: { hover: { ... } } }}`
363
+ 10. **HTML attributes go in `props` prop**: `<Link props={{ href: '/about' }}>` not `<Link href>`
364
+ 11. **Size shortcuts**: `width="fit"` = 100%, `width="fit-screen"` = 100vw, `width="1/2"` = 50%
365
+ 12. **Box is memoized** with `React.memo`
922
366
 
923
367
  ---
924
368
 
925
369
  ## DataGrid Component
926
370
 
927
- > **Full documentation:** See [`../docs/DATAGRID.md`](../docs/DATAGRID.md) for complete API reference, all features, component tree, style customization, and designs system.
928
-
929
- A feature-rich data grid with sorting, filtering, grouping, row selection, column pinning, row detail, server-side pagination, virtualization, and full style customization via the component design system.
930
-
931
371
  ```tsx
932
372
  import DataGrid from '@cronocode/react-box/components/dataGrid';
933
- ```
934
-
935
- ### Quick Reference
936
373
 
937
- ```tsx
938
374
  <DataGrid
939
375
  data={users}
940
376
  def={{
941
377
  rowKey: 'id',
942
378
  title: 'Users',
943
- topBar: true,
944
- bottomBar: true,
945
- globalFilter: true,
379
+ topBar: true, bottomBar: true, globalFilter: true,
946
380
  rowSelection: { pinned: true },
947
381
  showRowNumber: { pinned: true },
948
382
  rowHeight: 40,
@@ -953,81 +387,265 @@ import DataGrid from '@cronocode/react-box/components/dataGrid';
953
387
  { key: 'email', header: 'Email', width: 250, filterable: true },
954
388
  { key: 'status', header: 'Status', filterable: { type: 'multiselect' } },
955
389
  { key: 'country', header: 'Country', pin: 'RIGHT' },
390
+ {
391
+ key: 'actions', header: '', pin: 'RIGHT', width: 80, sortable: false, resizable: false,
392
+ Cell: ({ cell }) => <Button onClick={() => edit(cell.row.data)}>Edit</Button>,
393
+ },
956
394
  ],
957
- rowDetail: {
958
- content: (user) => <UserDetails user={user} />,
959
- height: 'auto',
960
- expandOnRowClick: true,
961
- },
962
- // pagination: { totalCount: 500, pageSize: 25 }, // for server-side pagination
395
+ rowDetail: { content: (user) => <UserDetails user={user} />, height: 'auto', expandOnRowClick: true },
963
396
  }}
964
- component="subgrid" // optional: use a different component style tree
965
397
  onSelectionChange={(e) => console.log(e.selectedRowKeys)}
966
- // page={1} // controlled pagination
967
- // onPageChange={(p, size) => {}} // page change handler
968
- // onSortChange={(col, dir) => {}} // server-side sort handler
969
- // onServerStateChange={(state) => fetch(state)} // unified server state callback
970
398
  />
971
399
  ```
972
400
 
973
- ### Key Props
401
+ ### DataGridProps
974
402
 
975
403
  | Prop | Type | Description |
976
- |------|------|-------------|
977
- | `data` | `TRow[]` | Row data array (required) |
978
- | `def` | `GridDefinition` | Grid configuration (required) |
979
- | `component` | `string` | Component style tree name (default: `'datagrid'`) |
404
+ |---|---|---|
405
+ | `data` | `TRow[]` | Row data (required) |
406
+ | `def` | `GridDefinition` | Grid config (required) |
407
+ | `component` | `string` | Style tree name (default: `'datagrid'`) |
980
408
  | `loading` | `boolean` | Loading state |
981
- | `filters` | `((row: TRow) => boolean)[]` | External predicate filters |
982
- | `page` | `number` | Controlled page (1-indexed) |
983
- | `onPageChange` | `(page, pageSize) => void` | Page change callback |
984
- | `onSortChange` | `(columnKey, direction) => void` | Sort change callback |
985
- | `onServerStateChange` | `(state: ServerState) => void` | Unified callback with full state (page, pageSize, sort, filters) |
986
- | `onSelectionChange` | `(event) => void` | Selection change callback |
987
- | `expandedRowKeys` | `Key[]` | Controlled expanded rows |
409
+ | `filters` | `((row) => boolean)[]` | External predicate filters (applied before column/global filters) |
410
+ | `page` / `onPageChange` | `number` / `(page, size) => void` | Controlled pagination (1-indexed) |
411
+ | `onSortChange` | `(columnKey, direction) => void` | Sort callback (`direction`: `'ASC'`/`'DESC'`/`undefined`) |
412
+ | `onServerStateChange` | `(state) => void` | Unified: `{ page, pageSize, sortColumn, sortDirection, columnFilters, globalFilterValue }` |
413
+ | `onSelectionChange` | `(event) => void` | `event`: `{ action, selectedRowKeys, affectedRowKeys, isAllSelected }` |
414
+ | `expandedRowKeys` / `onExpandedRowKeysChange` | `Key[]` / `(keys) => void` | Controlled expanded rows |
415
+ | `globalFilterValue` / `onGlobalFilterChange` | `string` / `(value) => void` | Controlled global filter |
416
+ | `columnFilters` / `onColumnFiltersChange` | `ColumnFilters` / `(filters) => void` | Controlled column filters |
417
+
418
+ ### GridDefinition
419
+
420
+ | Prop | Type | Default | Description |
421
+ |---|---|---|---|
422
+ | `columns` | `ColumnType[]` | required | Column definitions |
423
+ | `rowKey` | `keyof TRow \| (row) => Key` | auto | Unique row identifier |
424
+ | `rowHeight` | `number` | `48` | Row height in px |
425
+ | `visibleRowsCount` | `number \| 'all'` | `10` | Visible rows. `'all'` disables virtualization |
426
+ | `showRowNumber` | `boolean \| { pinned?, width? }` | `false` | Row number column |
427
+ | `rowSelection` | `boolean \| { pinned? }` | `false` | Checkbox selection column |
428
+ | `rowDetail` | `{ content, height?, expandOnRowClick?, pinned? }` | — | Expandable detail panel. `height`: `'auto'`/number/`(row) => number` |
429
+ | `pagination` | `{ totalCount, pageSize? }` | — | Server-side pagination. Bypasses client-side filtering |
430
+ | `topBar` / `bottomBar` | `boolean` | `false` | Show top/bottom bars |
431
+ | `title` / `topBarContent` | `ReactNode` | — | Top bar content |
432
+ | `globalFilter` | `boolean` | `false` | Enable global fuzzy search |
433
+ | `globalFilterKeys` | `(keyof TRow)[]` | all | Limit global filter columns |
434
+ | `sortable` / `resizable` | `boolean` | `true` | Enable sorting/resizing for all columns |
435
+ | `noDataComponent` | `ReactNode` | `'empty'` | Custom empty state |
436
+
437
+ ### ColumnType
438
+
439
+ | Prop | Type | Default | Description |
440
+ |---|---|---|---|
441
+ | `key` | `Key` | required | Column identifier (maps to TRow property) |
442
+ | `header` | `string` | — | Header text |
443
+ | `width` | `number` | `200` | Base width in px |
444
+ | `align` | `'left' \| 'right' \| 'center'` | `'left'` | Cell alignment |
445
+ | `pin` | `'LEFT' \| 'RIGHT'` | — | Pin to edge (sticky on scroll) |
446
+ | `columns` | `ColumnType[]` | — | Nested columns (grouped header) |
447
+ | `Cell` | `({ cell }) => ReactNode` | — | Custom renderer. `cell`: `{ value, row, column, grid }` |
448
+ | `sortable` / `resizable` | `boolean` | inherits | Override grid-level setting |
449
+ | `flexible` | `boolean` | `true` | Participate in flex width distribution |
450
+ | `filterable` | `boolean \| FilterConfig` | — | `true` (text), `{ type: 'number', min?, max? }`, `{ type: 'multiselect', options? }` |
451
+
452
+ ### Server-Side Pagination
453
+
454
+ ```tsx
455
+ <DataGrid
456
+ data={pageData} page={page} loading={loading}
457
+ onServerStateChange={(state) => {
458
+ // state = { page, pageSize, sortColumn, sortDirection, columnFilters, globalFilterValue }
459
+ setPage(state.page); refetch(state);
460
+ }}
461
+ def={{ columns: [...], bottomBar: true, pagination: { totalCount, pageSize: 25 }, globalFilter: true }}
462
+ />
463
+ ```
464
+
465
+ ### Style Customization
466
+
467
+ Component tree (all customizable via `Box.components()`):
468
+ ```
469
+ datagrid → content, topBar (globalFilter > stats, columnGroups > item), header > cell (contextMenu, resizer),
470
+ filter > row > cell, body > cell | row | groupRow | detailRow, bottomBar > pagination
471
+ ```
472
+
473
+ ```tsx
474
+ Box.components({
475
+ datagrid: { children: { header: { children: { cell: { styles: { textTransform: 'uppercase' } } } } } },
476
+ subgrid: {
477
+ extends: 'datagrid', // MUST use extends — inherits pinning, sticky, hover groups, filters, etc.
478
+ styles: { b: 0, shadow: 'none' },
479
+ children: { body: { children: { cell: { styles: { fontSize: 13 } } } } },
480
+ },
481
+ });
482
+ <DataGrid component="subgrid" data={data} def={def} /> {/* children resolve under subgrid.* */}
483
+ ```
484
+
485
+ ---
486
+
487
+ ## Dropdown Component
488
+
489
+ ```tsx
490
+ import Dropdown from '@cronocode/react-box/components/dropdown';
491
+ ```
988
492
 
989
- ### Filter Types
493
+ ### Usage
990
494
 
991
495
  ```tsx
992
- { key: 'name', filterable: true } // Text (fuzzy)
993
- { key: 'age', filterable: { type: 'number', min: 0, max: 100 } } // Number
994
- { key: 'status', filterable: { type: 'multiselect' } } // Multiselect
496
+ // Single selection (uncontrolled)
497
+ <Dropdown<string> defaultValue="apple" onChange={(value, values) => console.log(value)}>
498
+ <Dropdown.Unselect>Pick a fruit...</Dropdown.Unselect>
499
+ <Dropdown.Item value="apple">Apple</Dropdown.Item>
500
+ <Dropdown.Item value="banana">Banana</Dropdown.Item>
501
+ </Dropdown>
502
+
503
+ // Controlled
504
+ <Dropdown<string> value={fruit} onChange={(value) => setFruit(value!)}>
505
+ <Dropdown.Item value="apple">Apple</Dropdown.Item>
506
+ <Dropdown.Item value="banana">Banana</Dropdown.Item>
507
+ </Dropdown>
508
+
509
+ // Multiple + search + checkboxes
510
+ <Dropdown<string> multiple showCheckbox isSearchable searchPlaceholder="Search...">
511
+ <Dropdown.SelectAll>Select all</Dropdown.SelectAll>
512
+ <Dropdown.EmptyItem>No results</Dropdown.EmptyItem>
513
+ <Dropdown.Display>{(values) => values.length === 0 ? 'Pick...' : `${values.length} selected`}</Dropdown.Display>
514
+ <Dropdown.Item value="apple">Apple</Dropdown.Item>
515
+ <Dropdown.Item value="banana">Banana</Dropdown.Item>
516
+ </Dropdown>
517
+
518
+ // Form integration: name prop renders hidden inputs with JSON-stringified values
519
+ <Dropdown<string> name="fruits" multiple defaultValue={['apple']}>...</Dropdown>
995
520
  ```
996
521
 
522
+ ### Props
523
+
524
+ | Prop | Type | Description |
525
+ |---|---|---|
526
+ | `value` / `defaultValue` | `TVal \| TVal[]` | Controlled / uncontrolled selected value(s) |
527
+ | `multiple` | `boolean` | Multi-select mode |
528
+ | `isSearchable` | `boolean` | Show search input when open |
529
+ | `searchPlaceholder` | `string` | Search input placeholder |
530
+ | `hideIcon` | `boolean` | Hide chevron icon |
531
+ | `showCheckbox` | `boolean` | Show checkboxes in multiple mode |
532
+ | `name` | `string` | Form field name (renders hidden `<input>` elements) |
533
+ | `onChange` | `(value: TVal \| undefined, values: TVal[]) => void` | Selection callback |
534
+ | `itemsProps` | `BoxStyleProps` | Style overrides for the opened items container (`dropdown.items`) |
535
+ | `iconProps` | `BoxStyleProps` | Style overrides for the chevron icon container (`dropdown.icon`) |
536
+ | `variant` | `ClassNameType` | Propagates to root **and all child sub-components** |
537
+
538
+ Also accepts all `BoxProps` (styling props) which apply to the root button element.
539
+
540
+ ### Sub-Components
541
+
542
+ All sub-components accept BoxProps for per-instance style overrides.
543
+
544
+ | Sub-Component | Purpose |
545
+ |---|---|
546
+ | `Dropdown.Item<TVal>` | Selectable option. Requires `value` prop |
547
+ | `Dropdown.Unselect` | Clear selection option (shown when items selected) |
548
+ | `Dropdown.SelectAll` | Select all (shown in `multiple` when not all selected) |
549
+ | `Dropdown.EmptyItem` | Shown when search yields no results |
550
+ | `Dropdown.Display` | Custom display: static content or `(values: TVal[], isOpen: boolean) => ReactNode` |
551
+
997
552
  ### Style Customization
998
553
 
999
- Every subcomponent is customizable via `Box.components()`. Register a separate component tree for different visual styles:
554
+ **Per-instance** use `itemsProps`, `iconProps`, or BoxProps directly on sub-components:
555
+
556
+ ```tsx
557
+ <Dropdown<string> itemsProps={{ width: 80, maxHeight: 50 }} iconProps={{ color: 'gray-400' }}>
558
+ <Dropdown.Item value="a" bgColor="blue-50" fontWeight={600}>Highlighted</Dropdown.Item>
559
+ </Dropdown>
560
+ ```
561
+
562
+ **Global** — override defaults via `Box.components()` (deep-merged):
1000
563
 
1001
564
  ```tsx
1002
565
  Box.components({
1003
- // Customize default datagrid
1004
- datagrid: {
566
+ dropdown: {
567
+ styles: { borderRadius: 4, bgColor: 'gray-50' },
1005
568
  children: {
1006
- body: { children: { detailRow: { styles: { bgColor: 'blue-50' } } } },
1007
- header: { children: { cell: { styles: { textTransform: 'uppercase' } } } },
569
+ items: { styles: { shadow: 'large', borderRadius: 4 } },
570
+ item: { styles: { borderRadius: 2, hover: { bgColor: 'blue-50' } } },
1008
571
  },
1009
572
  },
1010
- // Extend datagrid styles for embedded grids — inherits all internal styles (pinning, sticky, etc.)
1011
- subgrid: {
1012
- extends: 'datagrid',
1013
- styles: { b: 0, shadow: 'none', bgColor: 'transparent' },
573
+ });
574
+ ```
575
+
576
+ **Custom variants** `variant` propagates to all children. Define matching variants on each child:
577
+
578
+ ```tsx
579
+ Box.components({
580
+ dropdown: {
581
+ variants: { dense: { p: 1, fontSize: 12 } },
1014
582
  children: {
1015
- header: { children: { cell: { styles: { fontSize: 12, py: 1 } } } },
1016
- body: { children: { cell: { styles: { fontSize: 13 } } } },
583
+ items: { variants: { dense: { maxHeight: 40, gap: 0 } } },
584
+ item: { variants: { dense: { p: 1, lineHeight: 16 } } },
585
+ unselect: { variants: { dense: { p: 1 } } },
586
+ selectAll: { variants: { dense: { p: 1 } } },
587
+ emptyItem: { variants: { dense: { p: 1 } } },
1017
588
  },
1018
589
  },
1019
590
  });
591
+ <Dropdown variant="dense">...</Dropdown> // applies to root + all children
592
+ ```
593
+
594
+ ### Component Style Tree
595
+
596
+ | Component Name | Description | Built-in Variants |
597
+ |---|---|---|
598
+ | `dropdown` | Root button trigger | `compact` |
599
+ | `dropdown.items` | Opened items container (portal) | — |
600
+ | `dropdown.item` | Selectable item | `compact`, `multiple` |
601
+ | `dropdown.unselect` | Clear selection option | `compact` |
602
+ | `dropdown.selectAll` | Select all option | `compact` |
603
+ | `dropdown.emptyItem` | No results placeholder | `compact` |
604
+ | `dropdown.icon` | Chevron arrow container | — |
605
+
606
+ ---
607
+
608
+ ## Select Component
1020
609
 
1021
- // Use it all children automatically resolve under "subgrid.*"
1022
- <DataGrid component="subgrid" data={data} def={def} />
610
+ Data-driven dropdownpass `data` + `def` instead of composing children. Wraps Dropdown internally, shares the same `dropdown.*` style tree.
611
+
612
+ ```tsx
613
+ import Select from '@cronocode/react-box/components/select';
614
+
615
+ // Basic
616
+ <Select<User, number> data={users} def={{ valueKey: 'id', displayKey: 'name', placeholder: 'Pick...' }}
617
+ value={selected} onChange={(value) => setSelected(value!)} />
618
+
619
+ // Multiple + search + custom display
620
+ <Select<User, number> data={users} multiple showCheckbox isSearchable searchPlaceholder="Search..."
621
+ def={{
622
+ valueKey: 'id', displayKey: 'name', placeholder: 'Pick users...',
623
+ selectAllText: 'Select all', emptyText: 'No results',
624
+ display: (user) => `${user.name} — ${user.role}`,
625
+ selectedDisplay: (rows) => `${rows.length} selected`,
626
+ }} />
1023
627
  ```
1024
628
 
629
+ ### SelectDef
630
+
631
+ | Prop | Type | Description |
632
+ |---|---|---|
633
+ | `valueKey` | `keyof TRow` | Required — field used as option value |
634
+ | `displayKey` | `keyof TRow` | Field to display (defaults to valueKey) |
635
+ | `display` | `(row: TRow) => ReactNode` | Custom render per item |
636
+ | `selectedDisplay` | `(rows: TRow[], isOpen: boolean) => ReactNode` | Custom trigger display (receives resolved row objects) |
637
+ | `placeholder` | `string` | Unselect/placeholder text |
638
+ | `selectAllText` | `string` | Select all option text (multiple mode) |
639
+ | `emptyText` | `string` | Empty search results text |
640
+
641
+ Also accepts: `data` (TRow[]), `value`/`defaultValue`, `multiple`, `isSearchable`, `searchPlaceholder`, `showCheckbox`, `hideIcon`, `name`, `onChange`, `itemsProps`, `iconProps`, `variant`, and all BoxProps. Same styling/variants as Dropdown.
642
+
1025
643
  ---
1026
644
 
1027
645
  ## Debugging Tips
1028
646
 
1029
- 1. **Inspect styles**: Look for `<style id="crono-box">` in document head
1030
- 2. **Check class names**: Elements get classes like `_b`, `_2a`, etc.
1031
- 3. **Verify variables**: CSS variables are in `:root` rules
647
+ 1. **Inspect styles**: `<style id="crono-box">` in document head
648
+ 2. **Class names**: Elements get classes like `_b`, `_2a`, etc.
649
+ 3. **CSS variables**: In `:root` rules
1032
650
  4. **Theme issues**: Ensure `<Box.Theme>` wraps your app
1033
651
  5. **Portal theming**: Tooltips/dropdowns use `#crono-box` container