@overdoser/react-toolkit 0.0.1 → 0.0.3

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/llms.txt ADDED
@@ -0,0 +1,480 @@
1
+ # @overdoser/react-toolkit — LLM reference
2
+
3
+ A flat, exhaustive component reference for AI coding assistants. Every component, every prop, every allowed string-literal value, with a minimal copy-paste example. If something isn't here, it isn't in the public API.
4
+
5
+ Package: `@overdoser/react-toolkit`
6
+ Peer deps: `react ^18 || ^19`, `react-dom ^18 || ^19`, optional `react-hook-form ^7` (only needed for `Form`/`FormField`).
7
+
8
+ ## Setup
9
+
10
+ ```ts
11
+ // Theme stylesheet — import once at the app entry.
12
+ import '@overdoser/react-toolkit/theme.css';
13
+ ```
14
+
15
+ Override CSS custom properties on `:root` to theme:
16
+ ```css
17
+ :root {
18
+ --crk-color-primary: #3b82f6;
19
+ --crk-color-danger: #ef4444;
20
+ --crk-font-family: 'Inter', sans-serif;
21
+ }
22
+ ```
23
+
24
+ All components forward `className` and `style`. Most also accept a `classes` object to override internal element classNames (see `*Classes` types per component).
25
+
26
+ ## Components
27
+
28
+ ### Button
29
+ Import: `import { Button } from '@overdoser/react-toolkit'`
30
+ Element: `<button>` (forwards ref, accepts all native button props).
31
+
32
+ Props:
33
+ - `variant?: 'primary' | 'secondary' | 'danger' | 'ghost'` — default `'primary'`
34
+ - `size?: 'sm' | 'md' | 'lg'` — default `'md'`
35
+ - `loading?: boolean` — default `false`. When `true`, button is disabled and `aria-busy`.
36
+ - `loadingStyle?: 'dots' | 'shimmer' | 'border'` — default `'dots'`. Only used when `loading` is true.
37
+ - `fullWidth?: boolean` — default `false`
38
+ - `classes?: Partial<ButtonClasses>` where `ButtonClasses = { root, content, shimmer, dots, dot }`
39
+
40
+ Example:
41
+ ```tsx
42
+ <Button variant="danger" size="lg" loading loadingStyle="shimmer" onClick={onDelete}>
43
+ Delete
44
+ </Button>
45
+ ```
46
+
47
+ ### Link
48
+ Import: `import { Link } from '@overdoser/react-toolkit'`
49
+ Element: `<a>` (forwards ref, accepts all native anchor props).
50
+
51
+ Props:
52
+ - `variant?: 'default' | 'muted' | 'danger'` — default `'default'`
53
+ - `external?: boolean` — default `false`. When `true`, sets `target="_blank" rel="noopener noreferrer"` and adds a visually-hidden " (opens in a new tab)" suffix.
54
+
55
+ Example:
56
+ ```tsx
57
+ <Link href="https://example.com" external>Docs</Link>
58
+ ```
59
+
60
+ ### Typography
61
+ Import: `import { Typography } from '@overdoser/react-toolkit'`
62
+ Element: dynamic — renders the tag named by `variant`.
63
+
64
+ Props:
65
+ - `variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'label'` — default `'p'`
66
+ - `weight?: 'normal' | 'medium' | 'semibold' | 'bold'`
67
+ - `color?: 'default' | 'muted' | 'primary' | 'danger' | 'success'` — note: native HTML `color` attr is intentionally omitted in favor of these presets.
68
+ - `align?: 'left' | 'center' | 'right'`
69
+ - `truncate?: boolean` — single-line ellipsis truncation.
70
+
71
+ Example:
72
+ ```tsx
73
+ <Typography variant="h2" weight="bold" color="primary">Heading</Typography>
74
+ ```
75
+
76
+ ### List / ListItem
77
+ Import: `import { List, ListItem } from '@overdoser/react-toolkit'`
78
+ Element: `<ul>` or `<ol>` depending on `variant`; `ListItem` renders `<li>`.
79
+
80
+ `ListProps`:
81
+ - `variant?: 'unordered' | 'ordered' | 'none'` — default `'unordered'`. `'none'` removes bullets.
82
+ - `spacing?: 'sm' | 'md' | 'lg'` — default `'md'`
83
+
84
+ `ListItemProps`: native `<li>` props only.
85
+
86
+ Example:
87
+ ```tsx
88
+ <List variant="ordered" spacing="lg">
89
+ <ListItem>First</ListItem>
90
+ <ListItem>Second</ListItem>
91
+ </List>
92
+ ```
93
+
94
+ ### Table
95
+ Import: `import { Table, useTableSort, type ColumnDef, type SortConfig } from '@overdoser/react-toolkit'`
96
+ Generic: `Table<T extends Record<string, unknown>>`.
97
+
98
+ Props:
99
+ - `data: T[]`
100
+ - `columns: ColumnDef<T>[]` — see `ColumnDef` below.
101
+ - `sortConfig?: SortConfig[]` — controlled sort. Provide together with `onSort` for server-side sorting.
102
+ - `onSort?: (config: SortConfig[]) => void` — when provided, table is in **controlled** mode: it does NOT sort `data` internally and does NOT slice for pagination (server is assumed to handle both).
103
+ - `multiSort?: boolean` — default `true`. Ctrl/Cmd+click a sortable header to add/cycle a secondary sort.
104
+ - `striped?: boolean` — default `false`
105
+ - `hoverable?: boolean` — default `false`
106
+ - `compact?: boolean` — default `false`
107
+ - `rowKey?: keyof T & string` — column key to use as React key; falls back to row index.
108
+ - `emptyMessage?: ReactNode` — default `'No data'`
109
+ - `pagination?: PaginationConfig` — see below. When omitted, all rows render.
110
+ - `classes?: Partial<TableClasses>` where `TableClasses = { wrapper, root, headerCell, row, cell, emptyCell, paginator, pageButton }`
111
+
112
+ `ColumnDef<T>`:
113
+ - `key: keyof T & string` — required.
114
+ - `header: ReactNode` — required.
115
+ - `sortable?: boolean`
116
+ - `render?: (value: T[keyof T], row: T, index: number) => ReactNode`
117
+ - `width?: string | number`
118
+ - `align?: 'left' | 'center' | 'right'`
119
+
120
+ `SortConfig`:
121
+ - `key: string`
122
+ - `direction: 'asc' | 'desc'`
123
+
124
+ `PaginationConfig`:
125
+ - `page: number` — 1-based.
126
+ - `pageSize: number`
127
+ - `totalRows?: number` — required for server-side mode; for client-side, defaults to `data.length`.
128
+ - `pageSizeOptions?: number[]` — default `[10, 25, 50, 100]`. Pass `[n]` to hide the page-size select.
129
+ - `onPageChange: (page: number, pageSize: number) => void`
130
+
131
+ Sort cycle on a sortable header click: `none → asc → desc → none`.
132
+
133
+ Client-side example:
134
+ ```tsx
135
+ const columns: ColumnDef<User>[] = [
136
+ { key: 'name', header: 'Name', sortable: true },
137
+ { key: 'email', header: 'Email' },
138
+ ];
139
+ <Table data={users} columns={columns} striped hoverable rowKey="id" />
140
+ ```
141
+
142
+ With pagination (client-side):
143
+ ```tsx
144
+ const [page, setPage] = useState(1);
145
+ const [pageSize, setPageSize] = useState(10);
146
+ <Table
147
+ data={users}
148
+ columns={columns}
149
+ pagination={{ page, pageSize, onPageChange: (p, s) => { setPage(p); setPageSize(s); } }}
150
+ />
151
+ ```
152
+
153
+ Server-side (controlled) example:
154
+ ```tsx
155
+ <Table
156
+ data={pageRows}
157
+ columns={columns}
158
+ sortConfig={sort}
159
+ onSort={setSort}
160
+ pagination={{ page, pageSize, totalRows, onPageChange }}
161
+ />
162
+ ```
163
+
164
+ ### `useTableSort` (hook)
165
+ Import: `import { useTableSort } from '@overdoser/react-toolkit'`
166
+ Signature: `useTableSort<T>(data: T[], initialSort?: SortConfig[])`
167
+ Returns:
168
+ - `sortedData: T[]`
169
+ - `sortConfig: SortConfig[]`
170
+ - `requestSort(key): void` — single-column; cycles asc → desc → cleared.
171
+ - `requestMultiSort(key): void` — adds/toggles in multi-sort.
172
+ - `resetSort(): void`
173
+
174
+ Use this only if you want to manage sort state outside `<Table>`; `<Table>` already does this internally when `onSort` is not supplied.
175
+
176
+ ### Dropdown / DropdownItem
177
+ Import: `import { Dropdown, DropdownItem } from '@overdoser/react-toolkit'`
178
+
179
+ Two distinct usage modes — pick one:
180
+
181
+ **Mode A — menu of arbitrary items:** pass `trigger` + `<DropdownItem>` children. The trigger button toggles a menu; clicking outside or pressing Escape closes it.
182
+
183
+ **Mode B — selectable form input:** pass `options` + `value` + `onChange`. The `trigger` prop is ignored; the selected option's label appears in the trigger.
184
+
185
+ `DropdownProps`:
186
+ - `trigger?: ReactNode` — required for Mode A; ignored in Mode B.
187
+ - `children?: ReactNode` — `<DropdownItem>` elements (Mode A).
188
+ - `options?: { value: string; label: ReactNode; disabled?: boolean }[]` — switches to Mode B.
189
+ - `value?: string` — Mode B selected value.
190
+ - `onChange?: (value: string) => void` — Mode B select callback.
191
+ - `placeholder?: ReactNode` — default `'Select...'` (Mode B).
192
+ - `align?: 'left' | 'right'` — default `'left'`. Menu alignment relative to trigger.
193
+ - `error?: boolean` — default `false`
194
+ - `fullWidth?: boolean` — default `true`
195
+ - `id?: string`
196
+ - `onOpen?: () => void`
197
+ - `onClose?: () => void`
198
+ - `classes?: Partial<DropdownClasses>` where `DropdownClasses = { root, trigger, triggerLabel, chevron, menu, item }`
199
+
200
+ `DropdownItemProps`: native `<button>` props plus `disabled?: boolean`.
201
+
202
+ Mode A example:
203
+ ```tsx
204
+ <Dropdown trigger={<>Menu ▾</>}>
205
+ <DropdownItem onClick={onEdit}>Edit</DropdownItem>
206
+ <DropdownItem onClick={onDelete}>Delete</DropdownItem>
207
+ </Dropdown>
208
+ ```
209
+
210
+ Mode B example:
211
+ ```tsx
212
+ <Dropdown
213
+ options={[{ value: 'a', label: 'Option A' }, { value: 'b', label: 'Option B' }]}
214
+ value={value}
215
+ onChange={setValue}
216
+ placeholder="Pick one"
217
+ />
218
+ ```
219
+
220
+ ### Popover
221
+ Import: `import { Popover } from '@overdoser/react-toolkit'`
222
+
223
+ Props:
224
+ - `trigger: ReactNode` — required. Wrapped in an internal `<button>`.
225
+ - `content: ReactNode` — required. Rendered inside an `[role="dialog"]` panel when open.
226
+ - `position?: 'top' | 'bottom' | 'left' | 'right'` — default `'bottom'`
227
+ - `open?: boolean` — controlled.
228
+ - `onOpenChange?: (open: boolean) => void`
229
+ - `classes?: Partial<PopoverClasses>` where `PopoverClasses = { root, trigger, popover }`
230
+
231
+ Closes on outside click and Escape. Auto-focuses the first focusable child of `content` (or the dialog itself).
232
+
233
+ `children` is intentionally typed as `never` — pass via `content`.
234
+
235
+ Example:
236
+ ```tsx
237
+ <Popover trigger="Help" content={<p>Some help text</p>} position="right" />
238
+ ```
239
+
240
+ ### Modal
241
+ Import: `import { Modal } from '@overdoser/react-toolkit'`
242
+
243
+ Compound component: `Modal.Header`, `Modal.Body`, `Modal.Footer`. Renders into a portal at `document.body`.
244
+
245
+ `ModalProps`:
246
+ - `open: boolean` — required.
247
+ - `onClose: () => void` — required.
248
+ - `closeOnBackdrop?: boolean` — default `true`
249
+ - `closeOnEscape?: boolean` — default `true`
250
+ - `size?: 'sm' | 'md' | 'lg' | 'fullscreen'` — default `'md'`
251
+ - `aria-label?: string` — use this OR `aria-labelledby`. If neither is provided, the `Modal.Header` content is auto-wired as the `aria-labelledby` target.
252
+ - `aria-labelledby?: string`
253
+ - `classes?: Partial<ModalClasses>` where `ModalClasses = { backdrop, modal, header, closeButton, body, footer }`
254
+
255
+ Locks body scroll while open; traps focus; restores focus to previously-focused element on close.
256
+
257
+ `Modal.Header` props:
258
+ - `children: ReactNode` — required.
259
+ - `onClose?: () => void` — when provided, renders an "×" close button.
260
+
261
+ `Modal.Body`, `Modal.Footer`: just `children`, `className`, `style`.
262
+
263
+ Example:
264
+ ```tsx
265
+ <Modal open={open} onClose={() => setOpen(false)} size="md">
266
+ <Modal.Header onClose={() => setOpen(false)}>Confirm</Modal.Header>
267
+ <Modal.Body>Are you sure?</Modal.Body>
268
+ <Modal.Footer>
269
+ <Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
270
+ <Button variant="danger" onClick={confirm}>Delete</Button>
271
+ </Modal.Footer>
272
+ </Modal>
273
+ ```
274
+
275
+ ### Form / FormField / FormRow
276
+ Requires `react-hook-form` peer dependency.
277
+
278
+ Imports: `import { Form, FormField, FormRow } from '@overdoser/react-toolkit'`
279
+
280
+ `Form<T>` props:
281
+ - `form: UseFormReturn<T>` — **the prop name is `form`, not `formMethods`**. Pass the result of `useForm()`.
282
+ - `onSubmit: SubmitHandler<T>` — passed through `form.handleSubmit`.
283
+ - `errors?: ReactNode[]` — top-of-form error list (rendered above children with `role="alert"`).
284
+
285
+ `FormField` props:
286
+ - `name: string` — required. Becomes the field id.
287
+ - `label?: ReactNode`
288
+ - `helperText?: ReactNode`
289
+ - `required?: boolean` — adds a "*" indicator next to the label.
290
+ - `rules?: Record<string, unknown>` — react-hook-form rules.
291
+ - `children: ReactElement` — exactly one child input. The child does NOT need `value`/`onChange`/`name` — `FormField` injects them via `cloneElement`.
292
+ - `classes?: Partial<FormFieldClasses>` where `FormFieldClasses = { field, label, error, helperText }`
293
+
294
+ `FormField` injects bridges so these inputs work without manual wiring:
295
+ - `Input`, `Textarea`, `Select`, `Dropdown` (Mode B), `Checkbox`, `Radio`/`RadioGroup`
296
+ - For `Select` (multi/searchable) / `Dropdown` (Mode B): bridges `onChange` → `onValueChange`/`onValuesChange`.
297
+ - For `Checkbox`: bridges `value` → `checked` when value is boolean.
298
+
299
+ `FormRow` props: `children`, `className`, `style`. Wraps fields in a horizontal flex row.
300
+
301
+ Example:
302
+ ```tsx
303
+ import { useForm } from 'react-hook-form';
304
+ import { Form, FormField, FormRow, Input, Button } from '@overdoser/react-toolkit';
305
+
306
+ function LoginForm() {
307
+ const form = useForm<{ email: string; password: string }>();
308
+ return (
309
+ <Form form={form} onSubmit={(values) => console.log(values)}>
310
+ <FormField name="email" label="Email" required rules={{ required: 'Email is required' }}>
311
+ <Input type="email" />
312
+ </FormField>
313
+ <FormField name="password" label="Password" required rules={{ required: true, minLength: { value: 8, message: 'Min 8 chars' } }}>
314
+ <Input type="password" />
315
+ </FormField>
316
+ <Button type="submit">Sign in</Button>
317
+ </Form>
318
+ );
319
+ }
320
+ ```
321
+
322
+ ### Input
323
+ Import: `import { Input } from '@overdoser/react-toolkit'`
324
+ Element: `<input>` (forwards ref). Native `prefix` attribute is omitted in favor of the prop below.
325
+
326
+ Props:
327
+ - `inputSize?: 'sm' | 'md' | 'lg'` — default `'md'`. **Note**: it is `inputSize`, NOT `size` — the native HTML `size` attribute is preserved separately.
328
+ - `error?: boolean` — default `false`
329
+ - `prefix?: ReactNode` — content rendered inside the input wrapper, before the field.
330
+ - `suffix?: ReactNode` — same, after the field.
331
+ - `classes?: Partial<InputClasses>` where `InputClasses = { root, wrapper, prefix, suffix }`
332
+
333
+ Example:
334
+ ```tsx
335
+ <Input type="email" inputSize="lg" prefix="@" placeholder="username" />
336
+ ```
337
+
338
+ ### Select
339
+ Import: `import { Select } from '@overdoser/react-toolkit'`
340
+
341
+ Three internal modes, picked automatically:
342
+ 1. **Native** (default) — wraps `<select>`.
343
+ 2. **Searchable** — set `searchable`. Custom dropdown with text filter.
344
+ 3. **Multi** — set `multiple`. Chip-based multi-select with text filter.
345
+
346
+ Props:
347
+ - `inputSize?: 'sm' | 'md' | 'lg'` — default `'md'`. (Same naming convention as `Input`.)
348
+ - `error?: boolean`
349
+ - `options?: { value: string; label: string; content?: ReactNode; disabled?: boolean }[]`
350
+ - `placeholder?: string` — default `'Select...'` for searchable/multi.
351
+ - `searchable?: boolean`
352
+ - `searchPlaceholder?: string` — default `'Search...'`
353
+ - `multiple?: boolean`
354
+ - `value?: string | string[]` — string for single, string[] for multi.
355
+ - `onChange?: (e: ChangeEvent<HTMLSelectElement>) => void` — fires for native + searchable (synthetic for searchable).
356
+ - `onValueChange?: (value: string) => void` — single-mode value-only callback.
357
+ - `onValuesChange?: (values: string[]) => void` — multi-mode callback.
358
+ - `clearSearchOnSelect?: boolean` — default `true`. Multi-mode only.
359
+ - `classes?: Partial<SelectClasses>` where `SelectClasses = { wrapper, root, arrow, search, menu, item, chip, chipRemove }`
360
+
361
+ Searchable example:
362
+ ```tsx
363
+ <Select
364
+ searchable
365
+ options={users.map(u => ({ value: u.id, label: u.name }))}
366
+ value={userId}
367
+ onValueChange={setUserId}
368
+ />
369
+ ```
370
+
371
+ Multi example:
372
+ ```tsx
373
+ <Select
374
+ multiple
375
+ options={tags}
376
+ value={selected}
377
+ onValuesChange={setSelected}
378
+ placeholder="Pick tags"
379
+ />
380
+ ```
381
+
382
+ ### Checkbox
383
+ Import: `import { Checkbox } from '@overdoser/react-toolkit'`
384
+ Element: `<input type="checkbox">` wrapped in a `<label>` (forwards ref to the input).
385
+
386
+ Props (extends native input props):
387
+ - `label?: ReactNode`
388
+ - `indeterminate?: boolean` — default `false`. Sets the DOM `indeterminate` property; visually distinct.
389
+ - `classes?: Partial<CheckboxClasses>` where `CheckboxClasses = { root, input, label }`
390
+
391
+ Example:
392
+ ```tsx
393
+ <Checkbox label="Remember me" checked={remember} onChange={(e) => setRemember(e.target.checked)} />
394
+ ```
395
+
396
+ ### Radio / RadioGroup
397
+ Import: `import { Radio, RadioGroup } from '@overdoser/react-toolkit'`
398
+
399
+ `RadioGroup` props:
400
+ - `name: string` — required. Becomes the `name` attribute on every `Radio` inside.
401
+ - `value?: string` — controlled selected value.
402
+ - `onChange?: (value: string) => void`
403
+ - `id?: string`
404
+ - `aria-label?` / `aria-labelledby?`
405
+ - `required?: boolean`
406
+
407
+ `Radio` props (extends native input props minus `type`):
408
+ - `value: string` — required.
409
+ - `label?: ReactNode`
410
+ - `classes?: Partial<RadioClasses>` where `RadioClasses = { root, input, label }`
411
+
412
+ Inside a `RadioGroup`, individual `Radio` components do NOT need their own `name`/`checked`/`onChange` — the group provides them via context.
413
+
414
+ Example:
415
+ ```tsx
416
+ <RadioGroup name="plan" value={plan} onChange={setPlan} aria-label="Plan">
417
+ <Radio value="free" label="Free" />
418
+ <Radio value="pro" label="Pro" />
419
+ </RadioGroup>
420
+ ```
421
+
422
+ ### Textarea
423
+ Import: `import { Textarea } from '@overdoser/react-toolkit'`
424
+ Element: `<textarea>` (forwards ref).
425
+
426
+ Props:
427
+ - `inputSize?: 'sm' | 'md' | 'lg'` — default `'md'`
428
+ - `error?: boolean`
429
+ - `resize?: 'none' | 'vertical' | 'horizontal' | 'both'` — default `'vertical'`. Ignored when `autoExpand` is true.
430
+ - `autoExpand?: boolean` — default `true`. Auto-grows height to fit content; `resize` is suppressed.
431
+
432
+ Example:
433
+ ```tsx
434
+ <Textarea placeholder="Bio" rows={3} autoExpand />
435
+ ```
436
+
437
+ ## Hooks
438
+
439
+ ### useClickOutside
440
+ Import: `import { useClickOutside } from '@overdoser/react-toolkit'`
441
+ Signature: `useClickOutside(ref: RefObject<HTMLElement | null>, handler: () => void, enabled?: boolean): void`
442
+
443
+ Calls `handler` on mousedown outside `ref.current`. Pass `enabled = false` to pause the listener (e.g., when a menu is closed) without unmounting.
444
+
445
+ ```tsx
446
+ useClickOutside(ref, close, isOpen);
447
+ ```
448
+
449
+ ### useFocusTrap
450
+ Import: `import { useFocusTrap } from '@overdoser/react-toolkit'`
451
+ Signature: `useFocusTrap(ref: RefObject<HTMLElement | null>, active: boolean): void`
452
+
453
+ Traps Tab / Shift+Tab inside `ref.current` while `active`. Focuses the first focusable child on activation; restores prior focus on deactivation.
454
+
455
+ ```tsx
456
+ useFocusTrap(dialogRef, isOpen);
457
+ ```
458
+
459
+ ### useKeyboard
460
+ Import: `import { useKeyboard } from '@overdoser/react-toolkit'`
461
+ Signature: `useKeyboard(handlers: Record<string, (e: KeyboardEvent) => void>, active?: boolean): void`
462
+
463
+ Binds global keydown listeners keyed by `KeyboardEvent.key`. `active` defaults to `true`.
464
+
465
+ ```tsx
466
+ useKeyboard({ Escape: () => close(), Enter: () => confirm() }, isOpen);
467
+ ```
468
+
469
+ ## Common gotchas (LLM-relevant)
470
+
471
+ 1. **`Form` prop is `form`, not `formMethods`.** Older docs may show `formMethods`.
472
+ 2. **`Button` prop is `loadingStyle`, not `loadingType`.**
473
+ 3. **`Input` / `Select` / `Textarea` size prop is `inputSize`** — the native `size` attribute still works as itself.
474
+ 4. **`Typography` does not accept a free-form `color` string** — only the listed presets. The native `color` attr is omitted.
475
+ 5. **`Dropdown` is dual-mode.** If you pass `options`, the `trigger` prop is ignored and it behaves like a select. If you don't, you must pass `trigger` and `<DropdownItem>` children.
476
+ 6. **`Popover.children` is typed `never`** — content goes through the `content` prop.
477
+ 7. **`Modal` requires `Modal.Header`/`Body`/`Footer`** — don't put raw markup inside `<Modal>`.
478
+ 8. **`FormField` clones its single child** to inject `value`/`onChange`/`id`/`name`/`error`/aria. Never put more than one element inside, never wire those props on the child manually inside a form.
479
+ 9. **Server-side `Table`:** when `onSort` is provided, the table won't sort or paginate `data` itself — your server must.
480
+ 10. **Theme stylesheet is a separate import**: `'@overdoser/react-toolkit/theme.css'`. Without it, components render unstyled.