@overdoser/react-toolkit 0.0.2 → 0.0.4
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/AGENTS.md +107 -0
- package/README.md +8 -8
- package/components/Button/Button.d.ts +14 -0
- package/components/Dropdown/Dropdown.d.ts +36 -8
- package/components/Dropdown/DropdownItem.d.ts +5 -0
- package/components/Form/Form.d.ts +16 -0
- package/components/Form/FormField.d.ts +25 -0
- package/components/Form/FormRow.d.ts +1 -0
- package/components/Link/Link.d.ts +12 -0
- package/components/List/List.d.ts +16 -0
- package/components/Modal/Modal.d.ts +23 -0
- package/components/Popover/Popover.d.ts +18 -0
- package/components/Table/Table.d.ts +34 -4
- package/components/Typography/Typography.d.ts +10 -0
- package/components/inputs/Checkbox/Checkbox.d.ts +14 -0
- package/components/inputs/Input/Input.d.ts +10 -0
- package/components/inputs/Radio/Radio.d.ts +22 -0
- package/components/inputs/Select/Select.d.ts +27 -0
- package/components/inputs/Textarea/Textarea.d.ts +9 -0
- package/index.d.ts +14 -0
- package/llms.txt +478 -0
- package/manifest.json +360 -0
- package/package.json +1 -1
- package/recipes/confirm-modal.tsx +63 -0
- package/recipes/dropdown-menu.tsx +36 -0
- package/recipes/login-form.tsx +51 -0
- package/recipes/paginated-table.tsx +48 -0
- package/recipes/searchable-multi-select.tsx +42 -0
- package/recipes/server-side-table.tsx +92 -0
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { ComponentPropsWithRef, ReactNode, CSSProperties } from 'react';
|
|
2
2
|
export interface RadioGroupProps {
|
|
3
|
+
/** Shared `name` attribute applied to every `Radio` child via context. */
|
|
3
4
|
name: string;
|
|
5
|
+
/** Controlled selected value. */
|
|
4
6
|
value?: string;
|
|
7
|
+
/** Fires with the newly selected value. */
|
|
5
8
|
onChange?: (value: string) => void;
|
|
6
9
|
className?: string;
|
|
7
10
|
style?: CSSProperties;
|
|
8
11
|
children: ReactNode;
|
|
9
12
|
id?: string;
|
|
13
|
+
/** Accessible label. Use this OR `aria-labelledby`. */
|
|
10
14
|
'aria-label'?: string;
|
|
11
15
|
'aria-labelledby'?: string;
|
|
16
|
+
/** Marks the group as required (`aria-required`). */
|
|
12
17
|
required?: boolean;
|
|
13
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Manages selection state and `name` attribute for a group of `<Radio>`s via context.
|
|
21
|
+
* Inside a `<RadioGroup>`, `Radio` does not need its own `name`/`checked`/`onChange`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <RadioGroup name="plan" value={plan} onChange={setPlan} aria-label="Plan">
|
|
25
|
+
* <Radio value="free" label="Free" />
|
|
26
|
+
* <Radio value="pro" label="Pro" />
|
|
27
|
+
* </RadioGroup>
|
|
28
|
+
*/
|
|
14
29
|
export declare const RadioGroup: {
|
|
15
30
|
({ name, value, onChange, className, style, children, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required, }: RadioGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
16
31
|
displayName: string;
|
|
@@ -21,8 +36,15 @@ export interface RadioClasses {
|
|
|
21
36
|
label: string;
|
|
22
37
|
}
|
|
23
38
|
export interface RadioProps extends Omit<ComponentPropsWithRef<'input'>, 'type'> {
|
|
39
|
+
/** Visible label rendered next to the radio. */
|
|
24
40
|
label?: ReactNode;
|
|
41
|
+
/** Value of this option. Required (used to identify the radio in the group). */
|
|
25
42
|
value: string;
|
|
43
|
+
/** Override class names on internal elements. */
|
|
26
44
|
classes?: Partial<RadioClasses>;
|
|
27
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A single radio option. Inside a `<RadioGroup>`, `name`/`checked`/`onChange`
|
|
48
|
+
* come from group context — do not pass them manually.
|
|
49
|
+
*/
|
|
28
50
|
export declare const Radio: import('react').ForwardRefExoticComponent<Omit<RadioProps, "ref"> & import('react').RefAttributes<HTMLInputElement>>;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { ComponentPropsWithRef } from 'react';
|
|
2
2
|
export interface SelectOption {
|
|
3
|
+
/** Value sent through `onValueChange` / form. */
|
|
3
4
|
value: string;
|
|
5
|
+
/** Plain-text label (used for filter matching and the trigger text). */
|
|
4
6
|
label: string;
|
|
7
|
+
/** Optional rich rendered content shown in the menu in place of `label`. */
|
|
5
8
|
content?: React.ReactNode;
|
|
9
|
+
/** Non-interactive when true. */
|
|
6
10
|
disabled?: boolean;
|
|
7
11
|
}
|
|
8
12
|
export interface SelectClasses {
|
|
@@ -16,18 +20,41 @@ export interface SelectClasses {
|
|
|
16
20
|
chipRemove: string;
|
|
17
21
|
}
|
|
18
22
|
export interface SelectProps extends Omit<ComponentPropsWithRef<'select'>, 'onChange' | 'value'> {
|
|
23
|
+
/** Toolkit size scale (independent of the native `size` HTML attribute). @default 'md' */
|
|
19
24
|
inputSize?: 'sm' | 'md' | 'lg';
|
|
25
|
+
/** Apply error styling. @default false */
|
|
20
26
|
error?: boolean;
|
|
27
|
+
/** Options. When omitted in native mode, `children` is used instead. */
|
|
21
28
|
options?: SelectOption[];
|
|
29
|
+
/** Placeholder shown when no value is selected. @default 'Select...' */
|
|
22
30
|
placeholder?: string;
|
|
31
|
+
/** Override class names on internal elements. */
|
|
23
32
|
classes?: Partial<SelectClasses>;
|
|
33
|
+
/** Replaces the native `<select>` with a filterable dropdown. @default false */
|
|
24
34
|
searchable?: boolean;
|
|
35
|
+
/** Placeholder for the search input (searchable mode). @default 'Search...' */
|
|
25
36
|
searchPlaceholder?: string;
|
|
37
|
+
/** Native + searchable mode change event (synthetic in searchable mode). */
|
|
26
38
|
onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
|
39
|
+
/** Single-mode value-only callback. */
|
|
27
40
|
onValueChange?: (value: string) => void;
|
|
41
|
+
/** `string` for single mode, `string[]` for multi mode. */
|
|
28
42
|
value?: string | string[];
|
|
43
|
+
/** Enable multi-select with chip rendering and search. @default false */
|
|
29
44
|
multiple?: boolean;
|
|
45
|
+
/** Multi-mode callback. */
|
|
30
46
|
onValuesChange?: (values: string[]) => void;
|
|
47
|
+
/** Clear the search query after each toggle in multi mode. @default true */
|
|
31
48
|
clearSearchOnSelect?: boolean;
|
|
32
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Three internal modes, picked automatically:
|
|
52
|
+
* - **Native** (default): wraps `<select>`. Use `onChange` or pass options as `children`.
|
|
53
|
+
* - **Searchable**: set `searchable`. Custom dropdown with text filter.
|
|
54
|
+
* - **Multi**: set `multiple`. Chip-based multi-select with text filter.
|
|
55
|
+
*
|
|
56
|
+
* @example Native: <Select options={[{ value: 'a', label: 'A' }]} onValueChange={setValue} />
|
|
57
|
+
* @example Searchable: <Select searchable options={users} value={id} onValueChange={setId} />
|
|
58
|
+
* @example Multi: <Select multiple options={tags} value={selected} onValuesChange={setSelected} />
|
|
59
|
+
*/
|
|
33
60
|
export declare const Select: import('react').ForwardRefExoticComponent<Omit<SelectProps, "ref"> & import('react').RefAttributes<HTMLSelectElement>>;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { ComponentPropsWithRef } from 'react';
|
|
2
2
|
export interface TextareaProps extends ComponentPropsWithRef<'textarea'> {
|
|
3
|
+
/** Toolkit size scale. @default 'md' */
|
|
3
4
|
inputSize?: 'sm' | 'md' | 'lg';
|
|
5
|
+
/** Apply error styling. @default false */
|
|
4
6
|
error?: boolean;
|
|
7
|
+
/** User resize handle direction. Ignored when `autoExpand` is true. @default 'vertical' */
|
|
5
8
|
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
|
|
9
|
+
/** Auto-grow height to fit content. Suppresses `resize` while active. @default true */
|
|
6
10
|
autoExpand?: boolean;
|
|
7
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Themed textarea with optional auto-expanding height.
|
|
14
|
+
*
|
|
15
|
+
* @example <Textarea placeholder="Bio" rows={3} autoExpand />
|
|
16
|
+
*/
|
|
8
17
|
export declare const Textarea: import('react').ForwardRefExoticComponent<Omit<TextareaProps, "ref"> & import('react').RefAttributes<HTMLTextAreaElement>>;
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @overdoser/react-toolkit
|
|
3
|
+
*
|
|
4
|
+
* Themeable React component library with SCSS modules.
|
|
5
|
+
*
|
|
6
|
+
* AI assistants: see `llms.txt` and `AGENTS.md` at the package root for an
|
|
7
|
+
* exhaustive component reference, recipes, and integration rules. A
|
|
8
|
+
* machine-readable spec is available in `manifest.json`.
|
|
9
|
+
*
|
|
10
|
+
* Setup: `import '@overdoser/react-toolkit/theme.css';` once at the app entry.
|
|
11
|
+
* Theme via `--crk-*` CSS custom properties on `:root`.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
1
15
|
export { Button } from './components/Button';
|
|
2
16
|
export type { ButtonProps, ButtonClasses } from './components/Button';
|
|
3
17
|
export { Link } from './components/Link';
|
package/llms.txt
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
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>` — 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'`. Toolkit size scale; independent of the native HTML `size` attribute.
|
|
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'`. Toolkit size scale.
|
|
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
|
+
## Conventions worth knowing
|
|
470
|
+
|
|
471
|
+
1. **Inputs use `inputSize`** for the toolkit size scale. The native `size` HTML attribute is independent.
|
|
472
|
+
2. **`Typography` `color`** accepts only the preset tokens; for arbitrary colors use `style` or `className`.
|
|
473
|
+
3. **`Dropdown` has two modes:** with `options` + `value` + `onChange` it acts as a select input (the `trigger` prop is unused). Without `options`, pass `trigger` and `<DropdownItem>` children.
|
|
474
|
+
4. **`Popover` content** goes through the `content` prop.
|
|
475
|
+
5. **`Modal` content** lives in `Modal.Header`, `Modal.Body`, and `Modal.Footer` — these compound components own the spacing and aria wiring.
|
|
476
|
+
6. **`FormField` clones its single child** to inject `value`/`onChange`/`id`/`name`/`error`/aria. Pass exactly one input element and let `FormField` wire it.
|
|
477
|
+
7. **Server-side `Table`:** providing `onSort` puts the table in controlled mode — the consumer is responsible for sorting and slicing `data` server-side.
|
|
478
|
+
8. **Theme stylesheet** is imported separately: `'@overdoser/react-toolkit/theme.css'`. Without it, components render unstyled.
|