@overdoser/react-toolkit 0.0.3 → 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 CHANGED
@@ -25,18 +25,16 @@ For exhaustive component reference (every prop, every variant, every signature),
25
25
  - Always import from the package root: `import { Button } from '@overdoser/react-toolkit'`. Do **not** reach into subpaths (`@overdoser/react-toolkit/dist/...`) — they're not part of the public API.
26
26
  - The only valid stylesheet subpath is `@overdoser/react-toolkit/theme.css`.
27
27
 
28
- ## Hard rules (don't violate)
29
-
30
- 1. **Don't ship raw `<button>`, `<input>`, `<select>`, `<textarea>`, `<a>` for UI affordances.** Use the toolkit's `Button`, `Input`, `Select`, `Textarea`, `Link` so styling and a11y come for free.
31
- 2. **Inside `<Form>`, every focusable input must be wrapped in `<FormField>`.** Don't manually wire `value`/`onChange`/`name` on the inner input — `FormField` clones the child to inject them.
32
- 3. **`<Modal>` content must be wrapped in `Modal.Header`, `Modal.Body`, `Modal.Footer`.** Raw children may render but they bypass focus targeting and styling.
33
- 4. **Use `inputSize`, not `size`,** on `Input`/`Select`/`Textarea`. The native `size` attribute is preserved separately.
34
- 5. **`Form`'s prop is `form`, not `formMethods`.** Pass the result of `useForm()`.
35
- 6. **`Button` uses `loadingStyle`** (`'dots' | 'shimmer' | 'border'`), not `loadingType`.
36
- 7. **`Typography` `color`** is an enum (`default | muted | primary | danger | success`), not a free-form string. The native HTML `color` attribute is omitted.
37
- 8. **`Popover` content goes in the `content` prop**, not as children. `children` is typed `never`.
38
- 9. **Server-side `Table`:** if you provide `onSort`, the table will not sort or paginate `data` itself. Your backend must return the page already sorted and sliced. Pass `pagination.totalRows` for the paginator to compute pages.
39
- 10. **Theming is via CSS custom properties** (`--crk-*`) on `:root`. Don't override styles by hand-writing CSS that targets internal class names — those are hashed and unstable. Use the `classes` prop where finer control is needed.
28
+ ## Rules
29
+
30
+ 1. **Use the toolkit components for UI affordances** `Button`, `Input`, `Select`, `Textarea`, `Link`. Styling and a11y come for free.
31
+ 2. **Inside `<Form>`, wrap every focusable input in `<FormField>`.** `FormField` clones the child to inject `value`/`onChange`/`name`/`error`/aria — let it.
32
+ 3. **`<Modal>` content lives in `Modal.Header`, `Modal.Body`, `Modal.Footer`.** They own focus targeting and spacing.
33
+ 4. **Inputs use `inputSize`** (`'sm' | 'md' | 'lg'`) for the toolkit size scale; the native `size` HTML attribute is independent.
34
+ 5. **`Typography` `color`** accepts the preset tokens (`default | muted | primary | danger | success`). For arbitrary colors, use `style` or `className`.
35
+ 6. **`Popover` content** goes through the `content` prop.
36
+ 7. **Server-side `Table`:** providing `onSort` puts the table in controlled mode the backend returns the page already sorted and sliced. Pass `pagination.totalRows` so the paginator can compute pages.
37
+ 8. **Theme via CSS custom properties** (`--crk-*`) on `:root`. For finer overrides, use each component's `classes` prop internal class names are hashed and unstable.
40
38
 
41
39
  ## Decision flow
42
40
 
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # @crk/react-toolkit
1
+ # @overdoser/react-toolkit
2
2
 
3
3
  A modern, themeable React component library with SCSS modules.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install @crk/react-toolkit
8
+ npm install @overdoser/react-toolkit
9
9
  ```
10
10
 
11
11
  ## Setup
@@ -13,7 +13,7 @@ npm install @crk/react-toolkit
13
13
  Import the stylesheet in your app entry point:
14
14
 
15
15
  ```ts
16
- import '@crk/react-toolkit/index.css';
16
+ import '@overdoser/react-toolkit/theme.css';
17
17
  ```
18
18
 
19
19
  ## Theming
@@ -61,13 +61,13 @@ Override `--crk-*` CSS custom properties to customize the theme:
61
61
  ### Button
62
62
 
63
63
  ```tsx
64
- import { Button } from '@crk/react-toolkit';
64
+ import { Button } from '@overdoser/react-toolkit';
65
65
 
66
66
  <Button variant="primary" size="md" onClick={handleClick}>
67
67
  Save
68
68
  </Button>
69
69
 
70
- <Button variant="danger" loading loadingType="dots">
70
+ <Button variant="danger" loading loadingStyle="dots">
71
71
  Deleting...
72
72
  </Button>
73
73
  ```
@@ -75,14 +75,14 @@ import { Button } from '@crk/react-toolkit';
75
75
  ### Form
76
76
 
77
77
  ```tsx
78
- import { Form, FormField, Input, Button } from '@crk/react-toolkit';
78
+ import { Form, FormField, Input, Button } from '@overdoser/react-toolkit';
79
79
  import { useForm } from 'react-hook-form';
80
80
 
81
81
  function LoginForm() {
82
- const methods = useForm();
82
+ const form = useForm();
83
83
 
84
84
  return (
85
- <Form formMethods={methods} onSubmit={methods.handleSubmit(onSubmit)}>
85
+ <Form form={form} onSubmit={(values) => console.log(values)}>
86
86
  <FormField name="email" label="Email">
87
87
  <Input />
88
88
  </FormField>
@@ -7,11 +7,25 @@ export interface ButtonClasses {
7
7
  dot: string;
8
8
  }
9
9
  export interface ButtonProps extends ComponentPropsWithRef<'button'> {
10
+ /** Override class names on internal elements. */
10
11
  classes?: Partial<ButtonClasses>;
12
+ /** Visual style. @default 'primary' */
11
13
  variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
14
+ /** Button size. @default 'md' */
12
15
  size?: 'sm' | 'md' | 'lg';
16
+ /** When true, the button is disabled and `aria-busy` is set. @default false */
13
17
  loading?: boolean;
18
+ /** Loading animation style. Only applied when `loading` is true. @default 'dots' */
14
19
  loadingStyle?: 'dots' | 'shimmer' | 'border';
20
+ /** Stretch the button to fill its container's width. @default false */
15
21
  fullWidth?: boolean;
16
22
  }
23
+ /**
24
+ * Styled `<button>` with variants, sizes, and three loading-state animations.
25
+ *
26
+ * @example
27
+ * <Button variant="danger" size="lg" loading loadingStyle="shimmer" onClick={onDelete}>
28
+ * Delete
29
+ * </Button>
30
+ */
17
31
  export declare const Button: import('react').ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & import('react').RefAttributes<HTMLButtonElement>>;
@@ -1,7 +1,10 @@
1
1
  import { CSSProperties } from 'react';
2
2
  export interface DropdownOption {
3
+ /** Selected value sent through `onChange`. */
3
4
  value: string;
5
+ /** Visible label in the menu and trigger. */
4
6
  label: React.ReactNode;
7
+ /** When true, the option is non-interactive. */
5
8
  disabled?: boolean;
6
9
  }
7
10
  export interface DropdownClasses {
@@ -13,28 +16,53 @@ export interface DropdownClasses {
13
16
  item: string;
14
17
  }
15
18
  export interface DropdownProps {
16
- /** Custom trigger content — used when no `options` are provided */
19
+ /**
20
+ * Trigger content for **menu mode**. Required when `options` is not provided.
21
+ * Ignored in **select mode** (when `options` is provided) — the trigger then
22
+ * shows the selected option's label.
23
+ */
17
24
  trigger?: React.ReactNode;
25
+ /** `<DropdownItem>` children for menu mode. */
18
26
  children?: React.ReactNode;
27
+ /** Menu alignment relative to the trigger. @default 'left' */
19
28
  align?: 'left' | 'right';
20
29
  className?: string;
21
30
  style?: CSSProperties;
22
31
  id?: string;
32
+ /** Called when the menu opens. */
23
33
  onOpen?: () => void;
34
+ /** Called when the menu closes (outside click, Escape, or selection). */
24
35
  onClose?: () => void;
25
- /** When provided, the Dropdown acts as a selectable input */
36
+ /** Switches Dropdown to **select mode**: the trigger shows the selected option. */
26
37
  options?: DropdownOption[];
27
- /** Placeholder shown when no value is selected */
38
+ /** Placeholder shown in select mode when no value is selected. @default 'Select...' */
28
39
  placeholder?: React.ReactNode;
29
- /** Controlled value */
40
+ /** Controlled selected value (select mode). */
30
41
  value?: string;
31
- /** Called when an option is selected */
42
+ /** Fires when an option is selected (select mode). */
32
43
  onChange?: (value: string) => void;
33
- /** Error state styling */
44
+ /** Error styling on the trigger. @default false */
34
45
  error?: boolean;
35
- /** Full width trigger */
46
+ /** Stretch trigger and menu to container width. @default true */
36
47
  fullWidth?: boolean;
37
- /** Custom class overrides for internal elements */
48
+ /** Override class names on internal elements. */
38
49
  classes?: Partial<DropdownClasses>;
39
50
  }
51
+ /**
52
+ * Two-mode dropdown:
53
+ * - **Menu mode**: pass `trigger` + `<DropdownItem>` children for an action menu.
54
+ * - **Select mode**: pass `options` + `value` + `onChange` to use as a form input.
55
+ * In this mode the `trigger` prop is ignored.
56
+ *
57
+ * Closes on outside click and Escape; auto-focuses the first menu item on open.
58
+ *
59
+ * @example Menu mode
60
+ * <Dropdown trigger="More ▾">
61
+ * <DropdownItem onClick={onEdit}>Edit</DropdownItem>
62
+ * <DropdownItem onClick={onDelete}>Delete</DropdownItem>
63
+ * </Dropdown>
64
+ *
65
+ * @example Select mode
66
+ * <Dropdown options={options} value={value} onChange={setValue} />
67
+ */
40
68
  export declare const Dropdown: import('react').ForwardRefExoticComponent<DropdownProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -1,5 +1,10 @@
1
1
  import { ComponentPropsWithRef } from 'react';
2
2
  export interface DropdownItemProps extends ComponentPropsWithRef<'button'> {
3
+ /** Non-interactive when true. @default false */
3
4
  disabled?: boolean;
4
5
  }
6
+ /**
7
+ * Action item rendered inside a `<Dropdown>` (menu mode).
8
+ * Renders as a `<button role="menuitem">`; supports all native button props.
9
+ */
5
10
  export declare const DropdownItem: import('react').ForwardRefExoticComponent<Omit<DropdownItemProps, "ref"> & import('react').RefAttributes<HTMLButtonElement>>;
@@ -1,10 +1,26 @@
1
1
  import { UseFormReturn, FieldValues, SubmitHandler } from 'react-hook-form';
2
2
  export interface FormProps<T extends FieldValues> {
3
+ /** The result of `useForm()`. */
3
4
  form: UseFormReturn<T>;
5
+ /** Called with validated values. `Form` handles `preventDefault` and validation internally. */
4
6
  onSubmit: SubmitHandler<T>;
7
+ /** Top-of-form error messages. Rendered above children with `role="alert"`. */
5
8
  errors?: React.ReactNode[];
6
9
  className?: string;
7
10
  style?: React.CSSProperties;
8
11
  children: React.ReactNode;
9
12
  }
13
+ /**
14
+ * react-hook-form wrapper that provides `FormProvider` and a styled `<form>` element.
15
+ *
16
+ * Pair with `<FormField>` for each input — `FormField` reads the form context
17
+ * via `useFormContext` and clones its child to inject `value`/`onChange`/`name`/`error`.
18
+ *
19
+ * @example
20
+ * const form = useForm<Values>();
21
+ * <Form form={form} onSubmit={(values) => save(values)}>
22
+ * <FormField name="email" label="Email"><Input /></FormField>
23
+ * <Button type="submit">Save</Button>
24
+ * </Form>
25
+ */
10
26
  export declare function Form<T extends FieldValues>({ form, onSubmit, errors, className, style, children, }: FormProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -6,14 +6,39 @@ export interface FormFieldClasses {
6
6
  helperText: string;
7
7
  }
8
8
  export interface FormFieldProps {
9
+ /** Field name. Becomes the field `id` and the react-hook-form path. */
9
10
  name: string;
11
+ /** Field label rendered above the input. */
10
12
  label?: ReactNode;
13
+ /** Helper text rendered below the input (replaced by error message when invalid). */
11
14
  helperText?: ReactNode;
15
+ /** Renders a `*` indicator next to the label. Does NOT add validation rules — pass those in `rules`. */
12
16
  required?: boolean;
17
+ /** react-hook-form `useController` rules (e.g., `{ required: 'msg', minLength: { value: 8, message: '…' } }`). */
13
18
  rules?: Record<string, unknown>;
19
+ /** Override class names on internal elements. */
14
20
  classes?: Partial<FormFieldClasses>;
15
21
  className?: string;
16
22
  style?: CSSProperties;
23
+ /**
24
+ * Exactly one input element. `FormField` clones it to inject
25
+ * `value`/`onChange`/`name`/`id`/`error`/aria props — do NOT pass those manually.
26
+ *
27
+ * Supported: `Input`, `Textarea`, `Select`, `Dropdown` (select-mode), `Checkbox`, `Radio`/`RadioGroup`.
28
+ */
17
29
  children: ReactElement;
18
30
  }
31
+ /**
32
+ * Bridges react-hook-form to a single child input via `cloneElement`.
33
+ * Must be used inside a `<Form>` (which provides `FormProvider`).
34
+ *
35
+ * Bridges automatically applied:
36
+ * - `Select` (multi/searchable) and `Dropdown` (select-mode): `onChange` → `onValueChange`/`onValuesChange`.
37
+ * - `Checkbox`: when the form value is boolean, it's bridged to `checked`.
38
+ *
39
+ * @example
40
+ * <FormField name="email" label="Email" required rules={{ required: 'Required' }}>
41
+ * <Input type="email" />
42
+ * </FormField>
43
+ */
19
44
  export declare function FormField({ name, label, helperText, required, rules, classes, className, style, children, }: FormFieldProps): import("react/jsx-runtime").JSX.Element;
@@ -4,4 +4,5 @@ export interface FormRowProps {
4
4
  className?: string;
5
5
  style?: CSSProperties;
6
6
  }
7
+ /** Horizontal flex row that lays out its children — useful for putting two `<FormField>`s side-by-side. */
7
8
  export declare function FormRow({ children, className, style }: FormRowProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,18 @@
1
1
  import { ComponentPropsWithRef } from 'react';
2
2
  export interface LinkProps extends ComponentPropsWithRef<'a'> {
3
+ /** Visual style. @default 'default' */
3
4
  variant?: 'default' | 'muted' | 'danger';
5
+ /**
6
+ * Mark the link as opening in a new tab.
7
+ * Sets `target="_blank"` and `rel="noopener noreferrer"`,
8
+ * and appends a visually-hidden " (opens in a new tab)" suffix for screen readers.
9
+ * @default false
10
+ */
4
11
  external?: boolean;
5
12
  }
13
+ /**
14
+ * Styled `<a>` with variants and safe external-link defaults.
15
+ *
16
+ * @example <Link href="https://example.com" external>Docs</Link>
17
+ */
6
18
  export declare const Link: import('react').ForwardRefExoticComponent<Omit<LinkProps, "ref"> & import('react').RefAttributes<HTMLAnchorElement>>;
@@ -1,9 +1,25 @@
1
1
  import { ComponentPropsWithRef } from 'react';
2
2
  export interface ListProps extends ComponentPropsWithRef<'ul'> {
3
+ /**
4
+ * List style. `'ordered'` renders `<ol>`, `'unordered'` renders `<ul>`,
5
+ * `'none'` renders `<ul>` without bullets.
6
+ * @default 'unordered'
7
+ */
3
8
  variant?: 'unordered' | 'ordered' | 'none';
9
+ /** Vertical spacing between items. @default 'md' */
4
10
  spacing?: 'sm' | 'md' | 'lg';
5
11
  }
12
+ /**
13
+ * Themed list (`<ul>` or `<ol>` based on `variant`).
14
+ *
15
+ * @example
16
+ * <List variant="ordered" spacing="lg">
17
+ * <ListItem>First</ListItem>
18
+ * <ListItem>Second</ListItem>
19
+ * </List>
20
+ */
6
21
  export declare const List: import('react').ForwardRefExoticComponent<Omit<ListProps, "ref"> & import('react').RefAttributes<HTMLOListElement | HTMLUListElement>>;
7
22
  export interface ListItemProps extends ComponentPropsWithRef<'li'> {
8
23
  }
24
+ /** Themed `<li>`. Use only inside `<List>`. */
9
25
  export declare const ListItem: import('react').ForwardRefExoticComponent<Omit<ListItemProps, "ref"> & import('react').RefAttributes<HTMLLIElement>>;
@@ -11,6 +11,7 @@ export interface ModalHeaderProps {
11
11
  children: ReactNode;
12
12
  className?: string;
13
13
  style?: CSSProperties;
14
+ /** When provided, renders an "×" close button in the header. */
14
15
  onClose?: () => void;
15
16
  }
16
17
  export interface ModalBodyProps {
@@ -27,15 +28,23 @@ declare const Header: FC<ModalHeaderProps>;
27
28
  declare const Body: FC<ModalBodyProps>;
28
29
  declare const Footer: FC<ModalFooterProps>;
29
30
  export interface ModalProps {
31
+ /** Whether the modal is visible. */
30
32
  open: boolean;
33
+ /** Called when the user requests close (backdrop, Escape, or `Modal.Header` close button). */
31
34
  onClose: () => void;
35
+ /** Close when the backdrop is clicked. @default true */
32
36
  closeOnBackdrop?: boolean;
37
+ /** Close when Escape is pressed. @default true */
33
38
  closeOnEscape?: boolean;
39
+ /** Modal size. `'fullscreen'` covers the viewport. @default 'md' */
34
40
  size?: 'sm' | 'md' | 'lg' | 'fullscreen';
41
+ /** Override class names on internal elements. */
35
42
  classes?: Partial<ModalClasses>;
36
43
  className?: string;
37
44
  style?: CSSProperties;
45
+ /** Should be `<Modal.Header>`, `<Modal.Body>`, and `<Modal.Footer>`. */
38
46
  children: ReactNode;
47
+ /** Accessible label. Use this OR `aria-labelledby`. If neither is set, `Modal.Header` is auto-wired as `aria-labelledby`. */
39
48
  'aria-label'?: string;
40
49
  'aria-labelledby'?: string;
41
50
  }
@@ -44,5 +53,19 @@ type ModalComponent = ReturnType<typeof forwardRef<HTMLDivElement, ModalProps>>
44
53
  Body: typeof Body;
45
54
  Footer: typeof Footer;
46
55
  };
56
+ /**
57
+ * Portal-based modal with focus trap, body scroll lock, and Escape/backdrop close.
58
+ * Compose with `Modal.Header`, `Modal.Body`, and `Modal.Footer`.
59
+ *
60
+ * @example
61
+ * <Modal open={open} onClose={() => setOpen(false)} size="md">
62
+ * <Modal.Header onClose={() => setOpen(false)}>Title</Modal.Header>
63
+ * <Modal.Body>Content</Modal.Body>
64
+ * <Modal.Footer>
65
+ * <Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
66
+ * <Button variant="primary" onClick={confirm}>OK</Button>
67
+ * </Modal.Footer>
68
+ * </Modal>
69
+ */
47
70
  export declare const Modal: ModalComponent;
48
71
  export {};
@@ -5,14 +5,32 @@ export interface PopoverClasses {
5
5
  popover: string;
6
6
  }
7
7
  export interface PopoverProps {
8
+ /** Trigger content. Wrapped in an internal `<button>`. */
8
9
  trigger: React.ReactNode;
10
+ /** Panel content. Rendered inside a `[role="dialog"]` when open. */
9
11
  content: React.ReactNode;
12
+ /** Side of the trigger to anchor the panel to. @default 'bottom' */
10
13
  position?: 'top' | 'bottom' | 'left' | 'right';
14
+ /** Controlled open state. */
11
15
  open?: boolean;
16
+ /** Called when the open state changes. */
12
17
  onOpenChange?: (open: boolean) => void;
18
+ /** Override class names on internal elements. */
13
19
  classes?: Partial<PopoverClasses>;
14
20
  className?: string;
15
21
  style?: CSSProperties;
22
+ /** Use the `content` prop. */
16
23
  children?: never;
17
24
  }
25
+ /**
26
+ * Anchored popover panel. Closes on outside click and Escape; auto-focuses
27
+ * the first focusable element inside `content` when opened.
28
+ *
29
+ * @example
30
+ * <Popover
31
+ * trigger="Help"
32
+ * content={<p>Some help text</p>}
33
+ * position="right"
34
+ * />
35
+ */
18
36
  export declare const Popover: import('react').ForwardRefExoticComponent<PopoverProps & import('react').RefAttributes<HTMLDivElement>>;
@@ -1,11 +1,17 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { SortConfig } from './useTableSort';
3
3
  export interface ColumnDef<T> {
4
+ /** Object key on each row to read the cell value from. */
4
5
  key: keyof T & string;
6
+ /** Header content (string or any ReactNode). */
5
7
  header: ReactNode;
8
+ /** Enable click-to-sort on the header. */
6
9
  sortable?: boolean;
10
+ /** Custom cell renderer; defaults to rendering `row[key]`. */
7
11
  render?: (value: T[keyof T], row: T, index: number) => ReactNode;
12
+ /** Column width (CSS value). */
8
13
  width?: string | number;
14
+ /** Text alignment for cells in this column. */
9
15
  align?: 'left' | 'center' | 'right';
10
16
  }
11
17
  export interface PaginationConfig {
@@ -31,25 +37,49 @@ export interface TableClasses {
31
37
  pageButton: string;
32
38
  }
33
39
  export interface TableProps<T extends Record<string, unknown>> {
40
+ /** Row data. */
34
41
  data: T[];
42
+ /** Column definitions. */
35
43
  columns: ColumnDef<T>[];
36
- /** Controlled sort config when provided with onSort, sorting is external (server-side) */
44
+ /** Controlled sort config. Pair with `onSort` for server-side sorting. */
37
45
  sortConfig?: SortConfig[];
38
- /** Sort callback — when provided, Table won't sort data internally */
46
+ /**
47
+ * Sort callback. When provided, Table is in **controlled** mode and will
48
+ * NOT sort `data` internally and will NOT slice for pagination — your
49
+ * server is expected to handle both.
50
+ */
39
51
  onSort?: (config: SortConfig[]) => void;
40
- /** Enable multi-column sort via Ctrl+click (default: true) */
52
+ /** Enable multi-column sort via Ctrl/Cmd-click on a sortable header. @default true */
41
53
  multiSort?: boolean;
54
+ /** Alternate row backgrounds. @default false */
42
55
  striped?: boolean;
56
+ /** Highlight rows on hover. @default false */
43
57
  hoverable?: boolean;
58
+ /** Tighter row padding. @default false */
44
59
  compact?: boolean;
45
60
  className?: string;
46
61
  style?: React.CSSProperties;
62
+ /** Column key to use as React `key` per row. Falls back to row index. */
47
63
  rowKey?: keyof T & string;
64
+ /** Content rendered when `data` is empty. @default 'No data' */
48
65
  emptyMessage?: ReactNode;
49
- /** Pagination config — when provided, shows a paginator below the table */
66
+ /** When provided, renders a paginator below the table. */
50
67
  pagination?: PaginationConfig;
68
+ /** Override class names on internal elements. */
51
69
  classes?: Partial<TableClasses>;
52
70
  }
71
+ /**
72
+ * Generic data table with sortable columns, multi-sort (Ctrl/Cmd-click), and
73
+ * client- or server-side pagination.
74
+ *
75
+ * Sort cycle on header click: `none → asc → desc → none`.
76
+ *
77
+ * **Client-side mode** (default): omit `onSort`. Table sorts and paginates internally.
78
+ *
79
+ * **Controlled mode**: pass `sortConfig` + `onSort`. Your server returns the
80
+ * page already sorted and sliced; pass `pagination.totalRows` so the
81
+ * paginator can compute page count.
82
+ */
53
83
  declare function TableInner<T extends Record<string, unknown>>({ data, columns, sortConfig: controlledSortConfig, onSort, multiSort, striped, hoverable, compact, className, style, rowKey, emptyMessage, pagination, classes, }: TableProps<T>): import("react/jsx-runtime").JSX.Element;
54
84
  export declare const Table: typeof TableInner;
55
85
  export {};
@@ -1,12 +1,22 @@
1
1
  import { ComponentPropsWithRef } from 'react';
2
2
  type TypographyVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'label';
3
3
  export interface TypographyProps extends Omit<ComponentPropsWithRef<'p'>, 'color'> {
4
+ /** HTML tag to render. @default 'p' */
4
5
  variant?: TypographyVariant;
6
+ /** Font weight. */
5
7
  weight?: 'normal' | 'medium' | 'semibold' | 'bold';
8
+ /** Preset color token. For arbitrary colors, use `style` or `className`. */
6
9
  color?: 'default' | 'muted' | 'primary' | 'danger' | 'success';
10
+ /** Text alignment. */
7
11
  align?: 'left' | 'center' | 'right';
12
+ /** Single-line ellipsis truncation. @default false */
8
13
  truncate?: boolean;
9
14
  children: React.ReactNode;
10
15
  }
16
+ /**
17
+ * Renders `h1`–`h6`, `p`, `span`, or `label` with theme-aware typography tokens.
18
+ *
19
+ * @example <Typography variant="h2" weight="bold" color="primary">Title</Typography>
20
+ */
11
21
  export declare const Typography: import('react').ForwardRefExoticComponent<Omit<TypographyProps, "ref"> & import('react').RefAttributes<HTMLElement>>;
12
22
  export {};
@@ -5,8 +5,22 @@ export interface CheckboxClasses {
5
5
  label: string;
6
6
  }
7
7
  export interface CheckboxProps extends ComponentPropsWithRef<'input'> {
8
+ /** Visible label rendered next to the box. */
8
9
  label?: ReactNode;
10
+ /** Sets the DOM `indeterminate` property; visually distinct from checked/unchecked. @default false */
9
11
  indeterminate?: boolean;
12
+ /** Override class names on internal elements. */
10
13
  classes?: Partial<CheckboxClasses>;
11
14
  }
15
+ /**
16
+ * Themed checkbox. The `<input>` is wrapped in a `<label>` so clicking the
17
+ * label toggles the input.
18
+ *
19
+ * @example
20
+ * <Checkbox
21
+ * label="Remember me"
22
+ * checked={remember}
23
+ * onChange={(e) => setRemember(e.target.checked)}
24
+ * />
25
+ */
12
26
  export declare const Checkbox: import('react').ForwardRefExoticComponent<Omit<CheckboxProps, "ref"> & import('react').RefAttributes<HTMLInputElement>>;
@@ -6,10 +6,20 @@ export interface InputClasses {
6
6
  suffix: string;
7
7
  }
8
8
  export interface InputProps extends Omit<ComponentPropsWithRef<'input'>, 'prefix'> {
9
+ /** Toolkit size scale (independent of the native `size` HTML attribute). @default 'md' */
9
10
  inputSize?: 'sm' | 'md' | 'lg';
11
+ /** Apply error styling. @default false */
10
12
  error?: boolean;
13
+ /** Content rendered inside the input wrapper, before the field. */
11
14
  prefix?: ReactNode;
15
+ /** Content rendered inside the input wrapper, after the field. */
12
16
  suffix?: ReactNode;
17
+ /** Override class names on internal elements. */
13
18
  classes?: Partial<InputClasses>;
14
19
  }
20
+ /**
21
+ * Themed text input with optional `prefix`/`suffix` slots.
22
+ *
23
+ * @example <Input type="email" inputSize="lg" prefix="@" placeholder="username" />
24
+ */
15
25
  export declare const Input: import('react').ForwardRefExoticComponent<Omit<InputProps, "ref"> & import('react').RefAttributes<HTMLInputElement>>;
@@ -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 CHANGED
@@ -278,7 +278,7 @@ Requires `react-hook-form` peer dependency.
278
278
  Imports: `import { Form, FormField, FormRow } from '@overdoser/react-toolkit'`
279
279
 
280
280
  `Form<T>` props:
281
- - `form: UseFormReturn<T>` — **the prop name is `form`, not `formMethods`**. Pass the result of `useForm()`.
281
+ - `form: UseFormReturn<T>` — pass the result of `useForm()`.
282
282
  - `onSubmit: SubmitHandler<T>` — passed through `form.handleSubmit`.
283
283
  - `errors?: ReactNode[]` — top-of-form error list (rendered above children with `role="alert"`).
284
284
 
@@ -324,7 +324,7 @@ Import: `import { Input } from '@overdoser/react-toolkit'`
324
324
  Element: `<input>` (forwards ref). Native `prefix` attribute is omitted in favor of the prop below.
325
325
 
326
326
  Props:
327
- - `inputSize?: 'sm' | 'md' | 'lg'` — default `'md'`. **Note**: it is `inputSize`, NOT `size` — the native HTML `size` attribute is preserved separately.
327
+ - `inputSize?: 'sm' | 'md' | 'lg'` — default `'md'`. Toolkit size scale; independent of the native HTML `size` attribute.
328
328
  - `error?: boolean` — default `false`
329
329
  - `prefix?: ReactNode` — content rendered inside the input wrapper, before the field.
330
330
  - `suffix?: ReactNode` — same, after the field.
@@ -344,7 +344,7 @@ Three internal modes, picked automatically:
344
344
  3. **Multi** — set `multiple`. Chip-based multi-select with text filter.
345
345
 
346
346
  Props:
347
- - `inputSize?: 'sm' | 'md' | 'lg'` — default `'md'`. (Same naming convention as `Input`.)
347
+ - `inputSize?: 'sm' | 'md' | 'lg'` — default `'md'`. Toolkit size scale.
348
348
  - `error?: boolean`
349
349
  - `options?: { value: string; label: string; content?: ReactNode; disabled?: boolean }[]`
350
350
  - `placeholder?: string` — default `'Select...'` for searchable/multi.
@@ -466,15 +466,13 @@ Binds global keydown listeners keyed by `KeyboardEvent.key`. `active` defaults t
466
466
  useKeyboard({ Escape: () => close(), Enter: () => confirm() }, isOpen);
467
467
  ```
468
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.
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.
package/manifest.json CHANGED
@@ -41,7 +41,7 @@
41
41
  "props": {
42
42
  "variant": { "type": "enum", "values": ["h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "label"], "default": "p" },
43
43
  "weight": { "type": "enum", "values": ["normal", "medium", "semibold", "bold"] },
44
- "color": { "type": "enum", "values": ["default", "muted", "primary", "danger", "success"], "notes": "Native color attribute is omitted in favor of these presets." },
44
+ "color": { "type": "enum", "values": ["default", "muted", "primary", "danger", "success"], "notes": "For arbitrary colors, use style or className." },
45
45
  "align": { "type": "enum", "values": ["left", "center", "right"] },
46
46
  "truncate": { "type": "boolean", "default": false }
47
47
  }
@@ -185,7 +185,7 @@
185
185
  "requires": "react-hook-form",
186
186
  "generic": "T extends FieldValues",
187
187
  "props": {
188
- "form": { "type": "UseFormReturn<T>", "required": true, "notes": "The result of useForm(). NOTE: prop is `form`, not `formMethods`." },
188
+ "form": { "type": "UseFormReturn<T>", "required": true, "notes": "The result of useForm()." },
189
189
  "onSubmit": { "type": "SubmitHandler<T>", "required": true },
190
190
  "errors": { "type": "ReactNode[]", "notes": "Top-of-form errors rendered above children with role=alert." }
191
191
  }
@@ -221,7 +221,7 @@
221
221
  "extendsNativeProps": "input (without prefix)",
222
222
  "forwardsRef": true,
223
223
  "props": {
224
- "inputSize": { "type": "enum", "values": ["sm", "md", "lg"], "default": "md", "notes": "Named `inputSize` so the native `size` attribute is preserved." },
224
+ "inputSize": { "type": "enum", "values": ["sm", "md", "lg"], "default": "md", "notes": "Toolkit size scale; independent of the native `size` HTML attribute." },
225
225
  "error": { "type": "boolean", "default": false },
226
226
  "prefix": { "type": "ReactNode" },
227
227
  "suffix": { "type": "ReactNode" },
@@ -339,17 +339,15 @@
339
339
  "notes": "Only needed if you want to manage sort state outside <Table>."
340
340
  }
341
341
  },
342
- "gotchas": [
343
- "Form prop is `form`, not `formMethods`.",
344
- "Button prop is `loadingStyle`, not `loadingType`.",
345
- "Input/Select/Textarea size prop is `inputSize`, not `size` native size attribute is preserved.",
346
- "Typography color is restricted to enum presets; native `color` HTML attribute is omitted.",
347
- "Dropdown is dual-mode: `options` triggers select-mode and `trigger` is ignored.",
348
- "Popover.children is typed `never`content goes through the `content` prop.",
349
- "Modal contents must be wrapped in Modal.Header/Body/Footer.",
350
- "FormField clones a single child element to inject form props — don't wire value/onChange manually.",
351
- "Server-side Table mode (onSort provided) means Table renders data verbatim — no internal sort or pagination slicing.",
352
- "Theme stylesheet must be imported separately: '@overdoser/react-toolkit/theme.css'."
342
+ "conventions": [
343
+ "Inputs use `inputSize` for the toolkit size scale; the native `size` HTML attribute is independent.",
344
+ "Typography color accepts only the preset tokens; for arbitrary colors use `style` or `className`.",
345
+ "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.",
346
+ "Popover content goes through the `content` prop.",
347
+ "Modal content lives in Modal.Header / Modal.Body / Modal.Footer compound components.",
348
+ "FormField clones a single child element to inject form props pass exactly one input element.",
349
+ "Server-side Table mode (when `onSort` is provided): the consumer is responsible for sorting and slicing `data` server-side.",
350
+ "Theme stylesheet is imported separately: '@overdoser/react-toolkit/theme.css'."
353
351
  ],
354
352
  "recipes": [
355
353
  "recipes/login-form.tsx",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overdoser/react-toolkit",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.mjs",