cleanplate 0.1.11 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +193 -60
  2. package/dist/components/accordion/Accordion.d.ts +23 -12
  3. package/dist/components/accordion/Accordion.d.ts.map +1 -1
  4. package/dist/components/accordion/index.d.ts +2 -1
  5. package/dist/components/accordion/index.d.ts.map +1 -1
  6. package/dist/components/app-shell/AppShell.d.ts +35 -3
  7. package/dist/components/app-shell/AppShell.d.ts.map +1 -1
  8. package/dist/components/app-shell/index.d.ts +2 -1
  9. package/dist/components/app-shell/index.d.ts.map +1 -1
  10. package/dist/components/bottom-sheet/BottomSheet.d.ts +17 -16
  11. package/dist/components/bottom-sheet/BottomSheet.d.ts.map +1 -1
  12. package/dist/components/bottom-sheet/index.d.ts +2 -1
  13. package/dist/components/bottom-sheet/index.d.ts.map +1 -1
  14. package/dist/components/breadcrumb/BreadCrumb.d.ts +26 -0
  15. package/dist/components/breadcrumb/BreadCrumb.d.ts.map +1 -0
  16. package/dist/components/breadcrumb/index.d.ts +3 -2
  17. package/dist/components/breadcrumb/index.d.ts.map +1 -1
  18. package/dist/components/footer/Footer.d.ts +21 -16
  19. package/dist/components/footer/Footer.d.ts.map +1 -1
  20. package/dist/components/footer/index.d.ts +2 -1
  21. package/dist/components/footer/index.d.ts.map +1 -1
  22. package/dist/components/header/Header.d.ts +32 -28
  23. package/dist/components/header/Header.d.ts.map +1 -1
  24. package/dist/components/header/index.d.ts +2 -1
  25. package/dist/components/header/index.d.ts.map +1 -1
  26. package/dist/components/icon/material-icon-names.d.ts +5 -5
  27. package/dist/components/icon/material-icon-names.d.ts.map +1 -1
  28. package/dist/components/menu-list/MenuList.d.ts +32 -22
  29. package/dist/components/menu-list/MenuList.d.ts.map +1 -1
  30. package/dist/components/menu-list/index.d.ts +2 -1
  31. package/dist/components/menu-list/index.d.ts.map +1 -1
  32. package/dist/components/page-header/PageHeader.d.ts +22 -3
  33. package/dist/components/page-header/PageHeader.d.ts.map +1 -1
  34. package/dist/components/page-header/index.d.ts +2 -1
  35. package/dist/components/page-header/index.d.ts.map +1 -1
  36. package/dist/components/toast/Toast.d.ts +20 -12
  37. package/dist/components/toast/Toast.d.ts.map +1 -1
  38. package/dist/components/toast/index.d.ts +2 -1
  39. package/dist/components/toast/index.d.ts.map +1 -1
  40. package/dist/index.css +1 -1
  41. package/dist/index.es.css +1 -1
  42. package/dist/index.es.js +3 -3
  43. package/dist/index.js +3 -3
  44. package/docs/Accordion.md +125 -0
  45. package/docs/Alert.md +131 -0
  46. package/docs/Animated.md +101 -0
  47. package/docs/AppShell.md +145 -0
  48. package/docs/Avatar.md +130 -0
  49. package/docs/Badge.md +83 -0
  50. package/docs/BottomSheet.md +78 -0
  51. package/docs/BreadCrumb.md +133 -0
  52. package/docs/Button.md +189 -0
  53. package/docs/ConfirmDialog.md +139 -0
  54. package/docs/Container.md +230 -0
  55. package/docs/Dropdown.md +175 -0
  56. package/docs/Footer.md +93 -0
  57. package/docs/FormControls.md +115 -0
  58. package/docs/Header.md +115 -0
  59. package/docs/Icon.md +225 -0
  60. package/docs/MediaObject.md +303 -0
  61. package/docs/MenuList.md +113 -0
  62. package/docs/Modal.md +152 -0
  63. package/docs/PageHeader.md +134 -0
  64. package/docs/Pagination.md +142 -0
  65. package/docs/Pills.md +104 -0
  66. package/docs/Spinner.md +115 -0
  67. package/docs/Stepper.md +131 -0
  68. package/docs/Table.md +194 -0
  69. package/docs/Toast.md +96 -0
  70. package/docs/Typography.md +231 -0
  71. package/llms.txt +293 -0
  72. package/package.json +6 -1
package/docs/Modal.md ADDED
@@ -0,0 +1,152 @@
1
+ # Modal Component
2
+
3
+ Purpose: A full-featured modal overlay for forms, long content, or custom dialogs. Use it when you need a larger, flexible dialog than ConfirmDialog—with optional title, close button, footer actions, and configurable close behavior (overlay click, Escape). Supports body scroll lock and focus management when open.
4
+
5
+ ## Props / Inputs
6
+
7
+ | Prop | Type | Required | Default | Description |
8
+ | --- | --- | --- | --- | --- |
9
+ | children | React.ReactNode | yes | — | Modal body content. |
10
+ | isOpen | boolean | no | false | Whether the modal is visible. |
11
+ | onClose | () => void | no | — | Called when the modal should close (close button, overlay, or Escape). |
12
+ | title | string | no | "" | Title displayed in the modal header. |
13
+ | size | "small" \| "medium" \| "large" \| "fullscreen" | no | "medium" | Size of the modal panel. |
14
+ | showCloseButton | boolean | no | true | Whether to show the X close button in the header. |
15
+ | closeOnOverlayClick | boolean | no | true | Whether clicking the overlay closes the modal. |
16
+ | closeOnEscape | boolean | no | true | Whether pressing Escape closes the modal. |
17
+ | margin | string \| SpacingOption[] | no | "m-0" | Margin around the modal. Use full class string (e.g. "m-0") or array of spacing suffixes; component adds `m-` prefix. |
18
+ | className | string | no | "" | Additional class names for the modal panel. |
19
+ | overlayClassName | string | no | "" | Additional class names for the overlay. |
20
+ | contentClassName | string | no | "" | Additional class names for the content wrapper. |
21
+ | primaryButtonLabel | string | no | "" | Label for the primary footer button; empty hides it. |
22
+ | onPrimaryButtonClick | () => void | no | — | Called when the primary footer button is clicked. |
23
+ | secondaryButtonLabel | string | no | "" | Label for the secondary footer button; empty hides it. |
24
+ | onSecondaryButtonClick | () => void | no | — | Called when the secondary footer button is clicked. |
25
+
26
+ ## Types
27
+
28
+ ### SpacingOption
29
+ ```typescript
30
+ type SpacingOption = (typeof SPACING_OPTIONS)[number];
31
+ ```
32
+
33
+ ### ModalSize
34
+ ```typescript
35
+ type ModalSize = "small" | "medium" | "large" | "fullscreen";
36
+ ```
37
+
38
+ ### ModalMargin
39
+ ```typescript
40
+ type ModalMargin = string | SpacingOption[];
41
+ ```
42
+
43
+ ### ModalProps
44
+ ```typescript
45
+ interface ModalProps {
46
+ children: React.ReactNode;
47
+ isOpen?: boolean;
48
+ onClose?: () => void;
49
+ title?: string;
50
+ size?: ModalSize;
51
+ showCloseButton?: boolean;
52
+ closeOnOverlayClick?: boolean;
53
+ closeOnEscape?: boolean;
54
+ margin?: ModalMargin;
55
+ className?: string;
56
+ overlayClassName?: string;
57
+ contentClassName?: string;
58
+ primaryButtonLabel?: string;
59
+ onPrimaryButtonClick?: () => void;
60
+ secondaryButtonLabel?: string;
61
+ onSecondaryButtonClick?: () => void;
62
+ }
63
+ ```
64
+
65
+ ## Usage Examples
66
+
67
+ ### Basic
68
+
69
+ ```jsx
70
+ import { Modal, Button, Typography } from "cleanplate";
71
+ import { useState } from "react";
72
+
73
+ const App = () => {
74
+ const [isOpen, setIsOpen] = useState(false);
75
+ return (
76
+ <>
77
+ <Button onClick={() => setIsOpen(true)}>Open Modal</Button>
78
+ <Modal
79
+ isOpen={isOpen}
80
+ onClose={() => setIsOpen(false)}
81
+ title="Modal Title"
82
+ >
83
+ <Typography variant="p">Modal body content goes here.</Typography>
84
+ </Modal>
85
+ </>
86
+ );
87
+ };
88
+ ```
89
+
90
+ ### Sizes
91
+
92
+ ```jsx
93
+ <Modal size="small" title="Small" isOpen={isOpen} onClose={onClose}>...</Modal>
94
+ <Modal size="medium" title="Medium" isOpen={isOpen} onClose={onClose}>...</Modal>
95
+ <Modal size="large" title="Large" isOpen={isOpen} onClose={onClose}>...</Modal>
96
+ <Modal size="fullscreen" title="Fullscreen" isOpen={isOpen} onClose={onClose}>...</Modal>
97
+ ```
98
+
99
+ ### With footer buttons
100
+
101
+ ```jsx
102
+ <Modal
103
+ isOpen={isOpen}
104
+ onClose={() => setIsOpen(false)}
105
+ title="Save Changes"
106
+ primaryButtonLabel="Save"
107
+ onPrimaryButtonClick={handleSave}
108
+ secondaryButtonLabel="Cancel"
109
+ onSecondaryButtonClick={() => setIsOpen(false)}
110
+ >
111
+ <Typography variant="p">Review and save your changes.</Typography>
112
+ </Modal>
113
+ ```
114
+
115
+ ### Close behavior
116
+
117
+ ```jsx
118
+ <Modal
119
+ showCloseButton={true}
120
+ closeOnOverlayClick={true}
121
+ closeOnEscape={true}
122
+ onClose={handleClose}
123
+ ...
124
+ />
125
+ ```
126
+
127
+ ### Form inside modal
128
+
129
+ ```jsx
130
+ <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Contact Form" size="medium">
131
+ <form onSubmit={handleSubmit}>
132
+ {/* form fields */}
133
+ <Button type="submit">Submit</Button>
134
+ </form>
135
+ </Modal>
136
+ ```
137
+
138
+ ## Behavior Notes
139
+
140
+ - **Rendering:** When `isOpen` is false, the component returns `null` (nothing in the DOM).
141
+ - **Close:** onClose is called when the user clicks the X button (if showCloseButton), the overlay (if closeOnOverlayClick), or Escape (if closeOnEscape). Footer buttons do not auto-close; call onClose in their handlers if desired.
142
+ - **Body scroll:** When open, `document.body.style.overflow` is set to `"hidden"`; restored on close.
143
+ - **Focus:** On open, focus moves to the modal panel (ref + tabIndex={-1}); on close, focus returns to the previously focused element.
144
+ - **ARIA:** The overlay has `role="dialog"`, `aria-modal="true"`, and `aria-labelledby` pointing to the title when present.
145
+ - **Spacing:** `margin` accepts a full class string (e.g. "m-0") or an array of spacing suffixes; the component uses `getSpacingClass` with prefix `m-`.
146
+
147
+ ## Related Components / Links
148
+
149
+ - ConfirmDialog (simpler confirmation-only modal with title, description, primary/secondary actions)
150
+ - Button (often used to open the modal and for footer actions)
151
+ - Typography (used for title and body content)
152
+ - Icon (used for the close button)
@@ -0,0 +1,134 @@
1
+ # PageHeader Component
2
+
3
+ Purpose: Two-column page header with left column (title and optional subtitle) and right column (primary CTA and optional more menu with three-dots icon). Use at the top of a page or section. Right column is aligned to the right edge. Title and subtitle accept string or ReactNode; more menu can be a list of items (label, onClick) or custom content via moreMenuContent.
4
+
5
+ ## Props / Inputs
6
+
7
+ | Prop | Type | Required | Default | Description |
8
+ | --- | --- | --- | --- | --- |
9
+ | title | ReactNode | yes | — | Page title (left column). String or custom ReactNode. |
10
+ | subtitle | ReactNode | no | — | Optional subtitle below the title (left column). |
11
+ | primaryCta | ReactNode | no | — | Primary call-to-action, e.g. a Button (right column). |
12
+ | moreMenuItems | PageHeaderMoreMenuItem[] | no | — | More menu items; renders three-dots (more_vert) icon and dropdown. Each item has label and optional onClick; menu closes after click. |
13
+ | moreMenuContent | ReactNode | no | — | Custom content for the more menu dropdown instead of moreMenuItems (right column). |
14
+ | className | string | no | "" | Additional class name for the root element. |
15
+
16
+ ## Types
17
+
18
+ ### PageHeaderMoreMenuItem
19
+ ```typescript
20
+ interface PageHeaderMoreMenuItem {
21
+ label: string; // Menu item label
22
+ onClick?: () => void; // Called when clicked; menu closes after
23
+ }
24
+ ```
25
+
26
+ ### PageHeaderProps
27
+ ```typescript
28
+ interface PageHeaderProps {
29
+ title: React.ReactNode;
30
+ subtitle?: React.ReactNode;
31
+ primaryCta?: React.ReactNode;
32
+ moreMenuItems?: PageHeaderMoreMenuItem[];
33
+ moreMenuContent?: React.ReactNode;
34
+ className?: string;
35
+ }
36
+ ```
37
+
38
+ ## Usage Examples
39
+
40
+ ### Full header
41
+
42
+ ```jsx
43
+ import { PageHeader, Button } from "cleanplate";
44
+
45
+ <PageHeader
46
+ title="Projects"
47
+ subtitle="Manage and track your team projects"
48
+ primaryCta={<Button>New project</Button>}
49
+ moreMenuItems={[
50
+ { label: "Export", onClick: () => exportData() },
51
+ { label: "Archive", onClick: () => archive() },
52
+ { label: "Settings", onClick: () => openSettings() },
53
+ ]}
54
+ />
55
+ ```
56
+
57
+ ### Title and subtitle only
58
+
59
+ ```jsx
60
+ <PageHeader
61
+ title="Settings"
62
+ subtitle="Configure your account and preferences"
63
+ />
64
+ ```
65
+
66
+ ### With primary CTA only
67
+
68
+ ```jsx
69
+ <PageHeader
70
+ title="Documents"
71
+ subtitle="All your documents in one place"
72
+ primaryCta={<Button>Upload</Button>}
73
+ />
74
+ ```
75
+
76
+ ### With more menu only
77
+
78
+ ```jsx
79
+ <PageHeader
80
+ title="Report"
81
+ subtitle="Generated on 17 Feb 2025"
82
+ moreMenuItems={[
83
+ { label: "Print", onClick: handlePrint },
84
+ { label: "Download PDF", onClick: handleDownload },
85
+ { label: "Share", onClick: handleShare },
86
+ ]}
87
+ />
88
+ ```
89
+
90
+ ### Custom title (ReactNode)
91
+
92
+ ```jsx
93
+ <PageHeader
94
+ title={
95
+ <span style={{ display: "flex", alignItems: "center", gap: "8px" }}>
96
+ <span>📋</span>
97
+ Custom title
98
+ </span>
99
+ }
100
+ subtitle="Optional subtitle"
101
+ primaryCta={<Button>Action</Button>}
102
+ />
103
+ ```
104
+
105
+ ### Custom more menu content
106
+
107
+ ```jsx
108
+ <PageHeader
109
+ title="Page"
110
+ primaryCta={<Button>Save</Button>}
111
+ moreMenuContent={
112
+ <div style={{ padding: "8px" }}>
113
+ <a href="/export">Export</a>
114
+ <hr />
115
+ <button type="button">Settings</button>
116
+ </div>
117
+ }
118
+ />
119
+ ```
120
+
121
+ ## Behavior Notes
122
+
123
+ - **Layout:** Root is a `<header>`. Flex row: left (title + subtitle), right (CTA + more trigger). Right column uses margin-left: auto for right alignment.
124
+ - **Title / subtitle:** If string, rendered with Typography (h4 / p). If ReactNode, rendered in a div with the same layout class.
125
+ - **More menu:** When moreMenuItems is set, renders a Dropdown with an icon Button (more_vert) and a list; each item onClick runs and the dropdown closes. When moreMenuContent is set, that content is shown in the dropdown. Use one or the other.
126
+ - **Accessibility:** More trigger has aria-expanded and aria-haspopup; list uses role="menu" and role="menuitem".
127
+
128
+ ## Related Components / Links
129
+
130
+ - Button (typically for primaryCta)
131
+ - Typography (used internally for string title and subtitle)
132
+ - Dropdown (used internally for the more menu)
133
+ - Icon (more_vert trigger)
134
+ - Container (wrap PageHeader and page content)
@@ -0,0 +1,142 @@
1
+ # Pagination Component
2
+
3
+ Purpose: Lets users navigate large sets of content by splitting them into pages. Shows total count, previous/next and page-number buttons (with ellipsis for long ranges), and an optional “rows per page” select. Use it below tables, lists, or search results. Fully controlled via `currentPage` and `rowsPerPage` with `onPageChange` and `onRowsPerPageChange`.
4
+
5
+ ## Props / Inputs
6
+
7
+ | Prop | Type | Required | Default | Description |
8
+ | --- | --- | --- | --- | --- |
9
+ | totalItems | number | yes | — | Total number of items across all pages. |
10
+ | totalLabel | string | no | "Items" | Label for the total count (e.g. "Items", "Results"). |
11
+ | currentPage | number | yes | — | Current 1-based page number (controlled). |
12
+ | rowsPerPage | number | no | 10 | Number of rows per page. |
13
+ | rowsPerPageOptions | PaginationRowsPerPageOption[] | no | [{ label: "10", value: 10 }, ...] | Options for the rows-per-page select; each has `label` and `value` (number). |
14
+ | onPageChange | (page, rowsPerPage) => void | yes | — | Called when the page changes; receives (page, rowsPerPage). |
15
+ | onRowsPerPageChange | (rowsPerPage: number) => void | no | — | Called when the user changes rows per page; receives the new value. |
16
+ | variant | "default" \| "minimal" | no | "default" | Visual variant. |
17
+ | margin | string \| SpacingOption[] | no | "0" | Margin spacing. Suffix or array of spacing suffixes; component adds `m-` prefix (e.g. "0" → m-0). |
18
+ | className | string | no | "" | Additional class names for the root element. |
19
+
20
+ ## Types
21
+
22
+ ### SpacingOption
23
+ ```typescript
24
+ type SpacingOption = (typeof SPACING_OPTIONS)[number];
25
+ ```
26
+
27
+ ### PaginationVariant
28
+ ```typescript
29
+ type PaginationVariant = "default" | "minimal";
30
+ ```
31
+
32
+ ### PaginationMargin
33
+ ```typescript
34
+ type PaginationMargin = string | SpacingOption[];
35
+ ```
36
+
37
+ ### PaginationRowsPerPageOption
38
+ ```typescript
39
+ interface PaginationRowsPerPageOption {
40
+ label: string;
41
+ value: number;
42
+ }
43
+ ```
44
+
45
+ ### PaginationProps
46
+ ```typescript
47
+ interface PaginationProps {
48
+ totalItems: number;
49
+ totalLabel?: string;
50
+ currentPage: number;
51
+ rowsPerPage?: number;
52
+ rowsPerPageOptions?: PaginationRowsPerPageOption[];
53
+ onPageChange: (page: number, rowsPerPage: number) => void;
54
+ onRowsPerPageChange?: (rowsPerPage: number) => void;
55
+ variant?: PaginationVariant;
56
+ margin?: PaginationMargin;
57
+ className?: string;
58
+ }
59
+ ```
60
+
61
+ ## Usage Examples
62
+
63
+ ### Basic
64
+
65
+ ```jsx
66
+ import { Pagination } from "cleanplate";
67
+ import { useState } from "react";
68
+
69
+ const App = () => {
70
+ const [currentPage, setCurrentPage] = useState(1);
71
+ const [rowsPerPage, setRowsPerPage] = useState(10);
72
+
73
+ return (
74
+ <Pagination
75
+ totalItems={120}
76
+ totalLabel="Items"
77
+ currentPage={currentPage}
78
+ rowsPerPage={rowsPerPage}
79
+ onPageChange={(page) => setCurrentPage(page)}
80
+ onRowsPerPageChange={(rpp) => {
81
+ setRowsPerPage(rpp);
82
+ setCurrentPage(1);
83
+ }}
84
+ />
85
+ );
86
+ };
87
+ ```
88
+
89
+ ### Variants
90
+
91
+ ```jsx
92
+ <Pagination variant="default" totalItems={120} currentPage={currentPage} rowsPerPage={rowsPerPage} onPageChange={handlePageChange} onRowsPerPageChange={handleRowsPerPageChange} />
93
+ <Pagination variant="minimal" totalItems={120} currentPage={currentPage} rowsPerPage={rowsPerPage} onPageChange={handlePageChange} onRowsPerPageChange={handleRowsPerPageChange} />
94
+ ```
95
+
96
+ ### Custom rows per page options
97
+
98
+ ```jsx
99
+ const options = [
100
+ { label: "10", value: 10 },
101
+ { label: "25", value: 25 },
102
+ { label: "50", value: 50 },
103
+ { label: "100", value: 100 },
104
+ ];
105
+
106
+ <Pagination
107
+ totalItems={500}
108
+ currentPage={currentPage}
109
+ rowsPerPage={rowsPerPage}
110
+ rowsPerPageOptions={options}
111
+ onPageChange={handlePageChange}
112
+ onRowsPerPageChange={handleRowsPerPageChange}
113
+ />
114
+ ```
115
+
116
+ ### With Table
117
+
118
+ ```jsx
119
+ import { Table, Pagination } from "cleanplate";
120
+
121
+ // Table can hide its built-in pagination (hidePagination) and you render Pagination below with your own state.
122
+ <Pagination
123
+ totalItems={totalCount}
124
+ currentPage={page}
125
+ rowsPerPage={pageSize}
126
+ onPageChange={(p, rpp) => { setPage(p); }}
127
+ onRowsPerPageChange={(rpp) => { setPageSize(rpp); setPage(1); }}
128
+ />
129
+ ```
130
+
131
+ ## Behavior Notes
132
+
133
+ - **Controlled:** You must hold `currentPage` (and typically `rowsPerPage`) in state and pass them in; update them in `onPageChange` and `onRowsPerPageChange`.
134
+ - **onPageChange:** Called when the user clicks a page number or prev/next; receives `(page, rowsPerPage)`. Update `currentPage` in state.
135
+ - **onRowsPerPageChange:** Called when the user selects a new rows-per-page value; receives the new number. Update `rowsPerPage` and usually set `currentPage` to 1.
136
+ - **Page buttons:** First page, last page, and a range around the current page are shown; gaps are represented as ellipsis (disabled "..." button).
137
+ - **Spacing:** `margin` uses the suffix API; the component adds the `m-` prefix via `getSpacingClass`.
138
+
139
+ ## Related Components / Links
140
+
141
+ - Table (often used with Pagination for tabular data; Table can show Pagination via `hidePagination={false}` or you can render Pagination separately)
142
+ - Container, Button, FormControls.Select, Typography, Icon (used internally)
package/docs/Pills.md ADDED
@@ -0,0 +1,104 @@
1
+ # Pills Component
2
+
3
+ Purpose: A single tag or chip that can show a label only (read-only), an inline input with submit (edit), or a label with a close button (remove). Use it for tags, filters, or editable chips where the user can add or remove items. Submit in edit mode via Enter or check button; remove via close button. Optional `isLoading` and `isDisabled`; margin uses the suffix API.
4
+
5
+ ## Props / Inputs
6
+
7
+ | Prop | Type | Required | Default | Description |
8
+ | --- | --- | --- | --- | --- |
9
+ | margin | string \| SpacingOption[] | no | "0" | Margin spacing. Suffix or array of spacing suffixes; component adds `m-` prefix. |
10
+ | className | string | no | "" | Additional class names for the root element. |
11
+ | label | string | no | "" | Label in read-only/remove mode; initial value in edit mode. |
12
+ | placeholder | string | no | "Add tag" | Placeholder for the input in edit mode. |
13
+ | onSubmit | (value: string) => void | no | — | Called when user submits in edit mode (Enter or check); receives current input value. |
14
+ | onRemove | () => void | no | — | Called when user clicks close in remove mode. |
15
+ | isDisabled | boolean | no | false | Disables the input and action button. |
16
+ | isLoading | boolean | no | false | Shows spinner instead of icon in edit/remove mode. |
17
+ | mode | "read-only" \| "edit" \| "remove" | no | "read-only" | read-only (label only), edit (input + check), remove (label + close). |
18
+
19
+ ## Types
20
+
21
+ ### SpacingOption
22
+ ```typescript
23
+ type SpacingOption = (typeof SPACING_OPTIONS)[number];
24
+ ```
25
+
26
+ ### PillsMargin
27
+ ```typescript
28
+ type PillsMargin = string | SpacingOption[];
29
+ ```
30
+
31
+ ### PillsMode
32
+ ```typescript
33
+ type PillsMode = "read-only" | "edit" | "remove";
34
+ ```
35
+
36
+ ### PillsProps
37
+ ```typescript
38
+ interface PillsProps {
39
+ margin?: PillsMargin;
40
+ className?: string;
41
+ label?: string;
42
+ placeholder?: string;
43
+ onSubmit?: (value: string) => void;
44
+ onRemove?: () => void;
45
+ isDisabled?: boolean;
46
+ isLoading?: boolean;
47
+ mode?: PillsMode;
48
+ }
49
+ ```
50
+
51
+ ## Usage Examples
52
+
53
+ ### Basic
54
+
55
+ ```jsx
56
+ import { Pills } from "cleanplate";
57
+
58
+ <Pills label="Taxi" mode="read-only" />
59
+ <Pills mode="edit" placeholder="Add tag" onSubmit={(v) => console.log(v)} />
60
+ <Pills label="Tag" mode="remove" onRemove={() => {}} />
61
+ ```
62
+
63
+ ### Controlled (edit)
64
+
65
+ ```jsx
66
+ import { Pills } from "cleanplate";
67
+ import { useState } from "react";
68
+
69
+ const [tag, setTag] = useState("Taxi");
70
+ <Pills
71
+ label={tag}
72
+ mode="edit"
73
+ placeholder="Add tag"
74
+ onSubmit={(v) => setTag(v)}
75
+ onRemove={() => setTag("")}
76
+ />
77
+ ```
78
+
79
+ ### Modes
80
+
81
+ ```jsx
82
+ <Pills label="Tag" mode="read-only" />
83
+ <Pills label="" mode="edit" placeholder="Add tag" onSubmit={onSubmit} />
84
+ <Pills label="Tag" mode="remove" onRemove={onRemove} />
85
+ ```
86
+
87
+ ### Loading and disabled
88
+
89
+ ```jsx
90
+ <Pills mode="edit" isLoading />
91
+ <Pills label="Tag" mode="remove" isDisabled onRemove={() => {}} />
92
+ ```
93
+
94
+ ## Behavior Notes
95
+
96
+ - **Edit mode:** Internal state holds the input value. On submit (Enter or check), `onSubmit(value)` is called and internal value is cleared. Parent can pass new `label` to reflect saved value.
97
+ - **Remove mode:** Close button calls `onRemove()`; parent typically clears or unmounts the pill.
98
+ - **Spacing:** `margin` uses the suffix API; the component adds the `m-` prefix via `getSpacingClass`.
99
+
100
+ ## Related Components / Links
101
+
102
+ - Container (layout for multiple pills)
103
+ - FormControls.Input, Button, Icon, Spinner (used internally)
104
+ - Typography (used for label in read-only/remove mode)
@@ -0,0 +1,115 @@
1
+ # Spinner Component
2
+
3
+ Purpose: A loading indicator that shows a rotating Material icon. Use it for progress or activity states. Supports six icon options (all animate when rotated), sizes, light/dark variant, and margin spacing.
4
+
5
+ ## Props / Inputs
6
+
7
+ | Prop | Type | Required | Default | Description |
8
+ | --- | --- | --- | --- | --- |
9
+ | size | "small" \| "medium" \| "large" | no | "medium" | Size of the spinner. |
10
+ | variant | "light" \| "dark" | no | "light" | Visual variant; use dark on dark backgrounds. |
11
+ | icon | SpinnerIcon | no | "progress_activity" | Icon to display as the spinner. One of: progress_activity, autorenew, sync, refresh, cached, loop. |
12
+ | margin | string \| string[] | no | "0" | Spacing **suffix** for outer margin. The component adds the `m-` prefix (e.g. `"0"` → m-0, `"b-2"` → m-b-2). Use a single string or array. |
13
+ | className | string | no | "" | Additional class names for the wrapper. |
14
+
15
+ ## Types
16
+
17
+ ### SpinnerSize
18
+ ```typescript
19
+ type SpinnerSize = "small" | "medium" | "large";
20
+ ```
21
+
22
+ ### SpinnerVariant
23
+ ```typescript
24
+ type SpinnerVariant = "light" | "dark";
25
+ ```
26
+
27
+ ### SpinnerIcon
28
+ ```typescript
29
+ type SpinnerIcon =
30
+ | "progress_activity"
31
+ | "autorenew"
32
+ | "sync"
33
+ | "refresh"
34
+ | "cached"
35
+ | "loop";
36
+ ```
37
+
38
+ ### SpacingOption
39
+ ```typescript
40
+ type SpacingOption = (typeof SPACING_OPTIONS)[number];
41
+ ```
42
+
43
+ ### SpinnerMargin
44
+ ```typescript
45
+ type SpinnerMargin = string | SpacingOption[];
46
+ ```
47
+
48
+ ### SpinnerProps
49
+ ```typescript
50
+ interface SpinnerProps {
51
+ size?: SpinnerSize;
52
+ variant?: SpinnerVariant;
53
+ icon?: SpinnerIcon;
54
+ margin?: SpinnerMargin;
55
+ className?: string;
56
+ }
57
+ ```
58
+
59
+ ## Usage Examples
60
+
61
+ ### Basic
62
+
63
+ ```jsx
64
+ import { Spinner } from "cleanplate";
65
+
66
+ export const Example = () => <Spinner size="medium" variant="light" />;
67
+ ```
68
+
69
+ ### Icon options
70
+
71
+ All of these icons rotate via the same CSS animation.
72
+
73
+ ```jsx
74
+ import { Spinner } from "cleanplate";
75
+
76
+ <Spinner /> // progress_activity (default)
77
+ <Spinner icon="autorenew" /> // Circular arrows
78
+ <Spinner icon="sync" /> // Sync arrows
79
+ <Spinner icon="refresh" /> // Refresh arrows
80
+ <Spinner icon="cached" /> // Cache/refresh arrows
81
+ <Spinner icon="loop" /> // Circular loop
82
+ ```
83
+
84
+ ### Sizes and variants
85
+
86
+ ```jsx
87
+ <Spinner size="small" />
88
+ <Spinner size="medium" />
89
+ <Spinner size="large" />
90
+ <Spinner variant="light" />
91
+ <Spinner variant="dark" />
92
+ ```
93
+
94
+ ### With Container
95
+
96
+ ```jsx
97
+ import { Spinner, Container } from "cleanplate";
98
+
99
+ export const Example = () => (
100
+ <Container padding="4">
101
+ <Spinner icon="sync" size="medium" margin="b-2" />
102
+ </Container>
103
+ );
104
+ ```
105
+
106
+ ## Behavior Notes
107
+
108
+ - **Animation:** The icon is rotated continuously via CSS (class `cp-spinner-icon`). All six icon options work with this rotation.
109
+ - **Layout:** Spinner renders a Container wrapping an Icon; size and variant classes apply to the wrapper.
110
+ - **Spacing:** `margin` accepts the **spacing suffix**; the component adds the `m-` prefix via `getSpacingClass`. Use suffix form (e.g. `"0"`, `"b-2"`) when passing values.
111
+
112
+ ## Related Components / Links
113
+
114
+ - Container (used internally as the wrapper; use for layout around the spinner)
115
+ - Icon (used internally to render the Material icon that rotates)