@kb.ui/react 0.10.4 → 0.12.0
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/README.md +142 -707
- package/dist/components/Accordion/Accordion.d.ts +33 -0
- package/dist/components/Avatar/Avatar.d.ts +19 -0
- package/dist/components/Code/Code.d.ts +26 -3
- package/dist/components/Code/tokenize.d.ts +5 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +10 -0
- package/dist/components/Collection/Collection.d.ts +99 -0
- package/dist/components/Collection/useCollection.d.ts +66 -0
- package/dist/components/CollectionList/CollectionList.d.ts +12 -0
- package/dist/components/CollectionRow/CollectionRow.d.ts +17 -0
- package/dist/components/Combobox/Combobox.d.ts +14 -2
- package/dist/components/ConfirmAction/ConfirmAction.d.ts +42 -0
- package/dist/components/CopyableText/CopyableText.d.ts +38 -0
- package/dist/components/CopyableText/content.d.ts +14 -0
- package/dist/components/Dashboard/Dashboard.d.ts +17 -0
- package/dist/components/DescriptionList/DescriptionList.d.ts +20 -0
- package/dist/components/Dialog/Dialog.d.ts +3 -1
- package/dist/components/Disclosure/Disclosure.d.ts +21 -0
- package/dist/components/EmptyState/EmptyState.d.ts +30 -0
- package/dist/components/Form/Form.d.ts +19 -0
- package/dist/components/FormSection/FormSection.d.ts +26 -0
- package/dist/components/Gallery/Gallery.d.ts +26 -0
- package/dist/components/Grid/Grid.d.ts +17 -6
- package/dist/components/Image/Image.d.ts +20 -0
- package/dist/components/Link/Link.d.ts +15 -2
- package/dist/components/PageHeader/PageHeader.d.ts +27 -0
- package/dist/components/Panel/Panel.d.ts +3 -1
- package/dist/components/Popover/Popover.d.ts +55 -0
- package/dist/components/Settings/Settings.d.ts +11 -0
- package/dist/components/Skeleton/Skeleton.d.ts +34 -0
- package/dist/components/Spinner/Spinner.d.ts +26 -0
- package/dist/components/Stack/Stack.d.ts +15 -0
- package/dist/components/StatsRow/StatsRow.d.ts +33 -0
- package/dist/components/SupportingPane/SupportingPane.d.ts +15 -0
- package/dist/components/Table/Table.d.ts +65 -0
- package/dist/components/Tabs/Tabs.d.ts +69 -0
- package/dist/components/Tabs/index.d.ts +1 -0
- package/dist/components/Thumbnail/Thumbnail.d.ts +14 -0
- package/dist/components/Toast/Toast.d.ts +72 -0
- package/dist/index.d.ts +39 -4
- package/dist/index.js +3901 -1238
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/CollapsiblePanel.d.ts +10 -0
- package/dist/utils/useClipboard.d.ts +28 -0
- package/docs/components/Accordion.md +66 -0
- package/docs/components/Avatar.md +80 -0
- package/docs/components/Badge.md +37 -0
- package/docs/components/Button.md +46 -0
- package/docs/components/Callout.md +41 -0
- package/docs/components/Card.md +41 -0
- package/docs/components/Checkbox.md +60 -0
- package/docs/components/Code.md +77 -0
- package/docs/components/CodeBlock.md +77 -0
- package/docs/components/Collection.md +183 -0
- package/docs/components/CollectionList.md +44 -0
- package/docs/components/CollectionRow.md +62 -0
- package/docs/components/Combobox.md +88 -0
- package/docs/components/ConfirmAction.md +100 -0
- package/docs/components/Container.md +41 -0
- package/docs/components/CopyableText.md +93 -0
- package/docs/components/Dashboard.md +81 -0
- package/docs/components/DescriptionList.md +61 -0
- package/docs/components/Dialog.md +84 -0
- package/docs/components/Disclosure.md +53 -0
- package/docs/components/EmptyState.md +50 -0
- package/docs/components/ErrorSummary.md +48 -0
- package/docs/components/Fieldset.md +42 -0
- package/docs/components/FileInput.md +74 -0
- package/docs/components/Form.md +67 -0
- package/docs/components/FormActions.md +45 -0
- package/docs/components/FormSection.md +57 -0
- package/docs/components/Gallery.md +84 -0
- package/docs/components/Grid.md +66 -0
- package/docs/components/Heading.md +40 -0
- package/docs/components/Image.md +62 -0
- package/docs/components/Inline.md +42 -0
- package/docs/components/Input.md +82 -0
- package/docs/components/Link.md +52 -0
- package/docs/components/Menu.md +44 -0
- package/docs/components/MultiSelect.md +59 -0
- package/docs/components/NumberField.md +65 -0
- package/docs/components/PageHeader.md +68 -0
- package/docs/components/Panel.md +153 -0
- package/docs/components/Popover.md +89 -0
- package/docs/components/Progress.md +37 -0
- package/docs/components/RadioGroup.md +69 -0
- package/docs/components/ScrollArea.md +40 -0
- package/docs/components/Select.md +71 -0
- package/docs/components/Separator.md +40 -0
- package/docs/components/Settings.md +57 -0
- package/docs/components/Skeleton.md +86 -0
- package/docs/components/Spinner.md +45 -0
- package/docs/components/Split.md +47 -0
- package/docs/components/Stack.md +54 -0
- package/docs/components/Stat.md +34 -0
- package/docs/components/StatsRow.md +80 -0
- package/docs/components/Status.md +48 -0
- package/docs/components/SupportingPane.md +59 -0
- package/docs/components/Switch.md +73 -0
- package/docs/components/Table.md +105 -0
- package/docs/components/Tabs.md +89 -0
- package/docs/components/Text.md +36 -0
- package/docs/components/Textarea.md +47 -0
- package/docs/components/Thumbnail.md +66 -0
- package/docs/components/Toast.md +130 -0
- package/docs/components/Toolbar.md +39 -0
- package/docs/components/Tooltip.md +42 -0
- package/package.json +3 -2
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Collection
|
|
2
|
+
|
|
3
|
+
Work through record sets using search, facet filters, sortable columns, page selection, pagination and bulk actions. `Collection` composes a native table and kbui controls. `useCollection` provides the same state contract for custom cards or lists. `Collection.Pagination` and `Collection.BulkActions` also work independently.
|
|
4
|
+
|
|
5
|
+
[All components](../../README.md#components) · [Usage](#usage) · [Examples](#more-examples) · [Reference](#reference) · [Live examples and accessibility](https://kbui.kcb.pro/storybook/?path=/docs/components-collection--docs)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import "@kb.ui/react/styles.css";
|
|
11
|
+
import { Collection } from "@kb.ui/react";
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
|
|
14
|
+
type Project = { id: string; name: string; status: string };
|
|
15
|
+
const initial: Project[] = [
|
|
16
|
+
{ id: "web", name: "Website refresh", status: "Active" },
|
|
17
|
+
{ id: "help", name: "Help center", status: "Paused" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export function Projects() {
|
|
21
|
+
const [items, setItems] = useState(initial);
|
|
22
|
+
return (
|
|
23
|
+
<Collection
|
|
24
|
+
label="Projects"
|
|
25
|
+
items={items}
|
|
26
|
+
getKey={item => item.id}
|
|
27
|
+
getItemLabel={item => item.name}
|
|
28
|
+
columns={[
|
|
29
|
+
{ key: "name", header: "Project", cell: item => item.name,
|
|
30
|
+
compare: (a, b) => a.name.localeCompare(b.name) },
|
|
31
|
+
{ key: "status", header: "Status", cell: item => item.status },
|
|
32
|
+
]}
|
|
33
|
+
matches={(item, query, filters) =>
|
|
34
|
+
item.name.toLowerCase().includes(query.toLowerCase()) &&
|
|
35
|
+
(!filters.status || item.status === filters.status)}
|
|
36
|
+
filters={[{ key: "status", label: "Status", options: [
|
|
37
|
+
{ value: "Active", label: "Active" },
|
|
38
|
+
{ value: "Paused", label: "Paused" },
|
|
39
|
+
] }]}
|
|
40
|
+
bulkActions={[{ key: "archive", label: "Archive", onAction: async keys => {
|
|
41
|
+
setItems(current => current.filter(item => !keys.includes(item.id)));
|
|
42
|
+
} }]}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## More examples
|
|
49
|
+
|
|
50
|
+
- [Large collection](https://kbui.kcb.pro/storybook/?path=/story/components-collection--large-collection): Search and filter 120 records; paused records cannot be selected.
|
|
51
|
+
- [Server pagination](https://kbui.kcb.pro/storybook/?path=/story/components-collection--server-pagination): Controlled state with application-owned data processing.
|
|
52
|
+
- [Empty](https://kbui.kcb.pro/storybook/?path=/story/components-collection--empty), [loading](https://kbui.kcb.pro/storybook/?path=/story/components-collection--loading), and [fetch error](https://kbui.kcb.pro/storybook/?path=/story/components-collection--fetch-error): Explicit result states.
|
|
53
|
+
- [Bulk failure](https://kbui.kcb.pro/storybook/?path=/story/components-collection--bulk-failure): Selection survives rejection so users can retry.
|
|
54
|
+
|
|
55
|
+
## Reference
|
|
56
|
+
|
|
57
|
+
### Collection props
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Contract |
|
|
60
|
+
| --- | --- | --- | --- |
|
|
61
|
+
| `label` | `string` | required | Visible caption and accessible region name |
|
|
62
|
+
| `items` | `readonly T[]` | required | Complete dataset in client mode; current response page in server mode |
|
|
63
|
+
| `getKey` | `(item: T) => string` | required | Unique stable record ID; duplicates in supplied rows throw |
|
|
64
|
+
| `getItemLabel` | `(item: T) => string` | required | Meaningful accessible checkbox name |
|
|
65
|
+
| `columns` | `readonly CollectionColumn<T>[]` | required | Ordered unique keys, string headers and cell renderers |
|
|
66
|
+
| `mode` | `client` / `server` | `client` | Server mode skips all local filtering, sorting and slicing |
|
|
67
|
+
| `totalItems` | `number` | client derives it | Required in server mode; known filtered total, non-negative safe integer |
|
|
68
|
+
| `matches` | `(item, query, filters) => boolean` | unset | Client predicate; also enables client search UI |
|
|
69
|
+
| `compare` | `(a, b, sort) => number` | column comparator | Optional hook-level comparison override; return ascending order |
|
|
70
|
+
| `filters` | `readonly CollectionFilter[]` | `[]` | Facet key, visible label and nonempty option values; empty string means All |
|
|
71
|
+
| `isItemDisabled` | `(item: T) => boolean` | unset | Prevent individual and page selection of ineligible supplied records |
|
|
72
|
+
| `state` / `onStateChange` | `CollectionState` / `(next) => void` | uncontrolled | Parent applies complete next state; suitable for URL or server ownership |
|
|
73
|
+
| `defaultState` | `Partial<CollectionState>` | see state table | Initial uncontrolled state; later changes do not reinitialize it |
|
|
74
|
+
| `selectable` | `boolean` | `true` | Show row/page checkboxes and selection actions |
|
|
75
|
+
| `bulkActions` | `readonly CollectionBulkAction[]` | `[]` | Unique key, label, `onAction(keys)`, optional disabled flag |
|
|
76
|
+
| `loading` | `boolean` | `false` | Mark busy, suppress stale rows and disable workflow controls |
|
|
77
|
+
| `error` | `ReactNode` | unset | Fetch failure alert, suppress rows and disable workflow controls |
|
|
78
|
+
| `empty` | `ReactNode` | `No matching records` | Zero-result content; include a useful next step |
|
|
79
|
+
| `toolbar` | `ReactNode` | unset | Extra application-owned controls, retry or creation actions |
|
|
80
|
+
| `className`, `style`, `ref` | native section props | unset | Root customization and element reference |
|
|
81
|
+
| `classNames` | slot classes | unset | `toolbar`, `filters`, `viewport`, `table`, `caption`, `head`, `cell`, `row`, `empty`, `status`, `bulk`, `pagination` |
|
|
82
|
+
|
|
83
|
+
A column has `key`, `header`, `cell(item)`, optional ascending `compare(a, b)`, and optional `sortable` for server-only sort requests. Sorting cycles ascending, descending, input order. Ties preserve supplied order. Sorting never mutates the supplied array. Search and facet interpretation belong to `matches`; kbui does not guess text from rendered React nodes. Server mode always exposes search and emits its value unchanged.
|
|
84
|
+
|
|
85
|
+
### State and transitions
|
|
86
|
+
|
|
87
|
+
| State field | Default | Transition contract |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `query` | `""` | `setQuery` resets page to one and clears selection |
|
|
90
|
+
| `filters` | `{}` | `setFilter(key, value)` preserves other facets, resets page and clears selection |
|
|
91
|
+
| `sort` | `null` | `{ key, direction: "ascending" or "descending" }`; `setSort` resets page, preserves selection |
|
|
92
|
+
| `page` | `1` | One-based positive safe integer; effective page clamps when results shrink |
|
|
93
|
+
| `pageSize` | `10` | Positive safe integer; `setPageSize` resets page and preserves selection |
|
|
94
|
+
| `selectedKeys` | `[]` | Explicit unique IDs; never an implicit selection of every remote result |
|
|
95
|
+
|
|
96
|
+
Client mode processes records in this order: predicate, stable comparison, page slice. Removed or newly disabled IDs disappear from effective selection. Server mode preserves off-page IDs; eligibility for unseen records remains the application’s responsibility. Both modes remove disabled IDs present in the current response. Zero results have one empty page. Invalid counts, pages and sizes throw `RangeError` rather than emitting broken navigation.
|
|
97
|
+
|
|
98
|
+
`state` is controlled when supplied. Each controlled proposal starts from the latest accepted props; a rejected proposal cannot affect a later interaction. Combine coordinated controlled changes in the parent before applying one next state. Uncontrolled commands in one event compose sequentially, including derived page bounds and page selection. Retained methods consult the latest committed dataset, eligibility rules, callbacks and accepted state, so asynchronous completion removes only its submitted IDs. Rendered state remains owned by the parent until it accepts `onStateChange`. Normalized selection and clamped page never trigger `onStateChange` during data refresh; the next user transition emits normalized state. Uncontrolled state retains those corrections so deleted/reintroduced IDs stay deselected and growing datasets do not restore an obsolete page. Controlled parent stores should reconcile their canonical state when external data changes. Use immutable arrays/objects and stable IDs across refreshes.
|
|
99
|
+
|
|
100
|
+
### useCollection
|
|
101
|
+
|
|
102
|
+
Accepts `items`, `getKey`, `isItemDisabled`, `matches`, `compare`, `mode`, `totalItems`, `state`, `defaultState`, and `onStateChange`. It renders no DOM and performs no fetching or mutations.
|
|
103
|
+
|
|
104
|
+
| Result | Contract |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `state`, `items`, `totalItems`, `pageCount` | Effective normalized state, visible rows and derived page metadata |
|
|
107
|
+
| `pageSelection` | `none`, `some`, or `all` among eligible current-page records |
|
|
108
|
+
| `selectableCount` | Eligible current-page record count |
|
|
109
|
+
| `setQuery`, `setFilter`, `setSort`, `setPage`, `setPageSize` | Apply transitions described above |
|
|
110
|
+
| `toggle(key, checked)` | Change selection for a supplied eligible record; unknown or disabled IDs are ignored |
|
|
111
|
+
| `selectPage(checked)` | Add/remove eligible page IDs while preserving off-page IDs |
|
|
112
|
+
| `clearSelection()` | Clear all selected IDs |
|
|
113
|
+
| `removeSelection(keys)` | Remove only specified IDs, suitable for successful submitted snapshots |
|
|
114
|
+
|
|
115
|
+
For a custom list, reuse the controller and compound controls:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import "@kb.ui/react/styles.css";
|
|
119
|
+
import { Checkbox, Collection, useCollection } from "@kb.ui/react";
|
|
120
|
+
import { useState } from "react";
|
|
121
|
+
|
|
122
|
+
export function RecordList() {
|
|
123
|
+
const [items, setItems] = useState([
|
|
124
|
+
{ id: "web", name: "Website refresh" },
|
|
125
|
+
{ id: "help", name: "Help center" },
|
|
126
|
+
]);
|
|
127
|
+
const [pending, setPending] = useState(false);
|
|
128
|
+
const collection = useCollection({ items, getKey: item => item.id });
|
|
129
|
+
return (
|
|
130
|
+
<section aria-label="Project list">
|
|
131
|
+
<ul>
|
|
132
|
+
{collection.items.map(item => (
|
|
133
|
+
<li key={item.id}>
|
|
134
|
+
<Checkbox label={item.name} disabled={pending}
|
|
135
|
+
checked={collection.state.selectedKeys.includes(item.id)}
|
|
136
|
+
onCheckedChange={checked => collection.toggle(item.id, checked)} />
|
|
137
|
+
</li>
|
|
138
|
+
))}
|
|
139
|
+
</ul>
|
|
140
|
+
<Collection.BulkActions selectedKeys={collection.state.selectedKeys}
|
|
141
|
+
onClear={collection.clearSelection} onSuccess={collection.removeSelection}
|
|
142
|
+
onPendingChange={setPending} actions={[{ key: "archive", label: "Archive",
|
|
143
|
+
onAction: keys => setItems(current => current.filter(item => !keys.includes(item.id))) }]} />
|
|
144
|
+
<Collection.Pagination page={collection.state.page}
|
|
145
|
+
pageSize={collection.state.pageSize} totalItems={collection.totalItems}
|
|
146
|
+
onPageChange={collection.setPage} disabled={pending} />
|
|
147
|
+
</section>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Standalone controls
|
|
153
|
+
|
|
154
|
+
| Component | Required props | Optional props and behavior |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| `Collection.Pagination` | `page`, `pageSize`, `totalItems`, `onPageChange` | `onPageSizeChange`, `pageSizes` (10, 25, 50), `disabled`, native nav props/ref. Includes current page size, emits bounded page requests; caller resets page when size changes. |
|
|
157
|
+
| `Collection.BulkActions` | `selectedKeys`, `actions`, `onClear` | `onSuccess(keys)`, `onPendingChange(boolean)`, `disabled`, native `fieldset` props/ref. Caller owns selection; omitted `onSuccess` preserves it. |
|
|
158
|
+
|
|
159
|
+
### Bulk-action boundary
|
|
160
|
+
|
|
161
|
+
Actions receive a copied, deduplicated ID array captured at submission. Selection counts use the same distinct IDs. Buttons remain mounted during selection changes. Empty selections and disabled actions cannot submit; an immediate lock prevents repeated submission before React rerenders. Pending actions disable other workflow controls in `Collection`. Success handlers come from the latest committed render; suspended renders cannot replace an active operation’s handler. A resolved operation removes only submitted IDs; a rejected operation announces a generic failure and retains selection. Clear selection dismisses the displayed failure.
|
|
162
|
+
|
|
163
|
+
Applications own permission checks, destructive confirmation, server authorization, cancellation, idempotency, optimistic updates, undo, auditing and focus recovery after record removal. A resolved promise means the complete snapshot succeeded. For partial success, reconcile affected records and selection in controlled state and reject or show application-specific detail; kbui does not infer partial outcomes. Do not automatically retry mutations. Avoid changing dataset identity while an action is pending; remount with a different React key when switching collections.
|
|
164
|
+
|
|
165
|
+
### Server ownership
|
|
166
|
+
|
|
167
|
+
Supply controlled state, `mode="server"`, the current response’s rows and its filtered `totalItems`. Apply emitted query, facets, sort and page to your request. Keep `loading` true while rows belong to a previous request. Cancel or discard stale responses, reconcile out-of-range pages after deletions, and expose fetch failures with `error` and a retry action through `toolbar`. Map column sort keys to an explicit server allowlist. Validate IDs and permissions again at mutation time, including off-page records.
|
|
168
|
+
|
|
169
|
+
Offset pagination requires a known total. Cursor paging, unknown totals, all-matching remote selection, virtualization, column resizing, drag ordering and spreadsheet grid navigation are outside this API. Use bounded server pages for large remote datasets; client mode processes the entire supplied array and has no benchmark-backed dataset-size guarantee.
|
|
170
|
+
|
|
171
|
+
### Styling and accessibility
|
|
172
|
+
|
|
173
|
+
Override `--kb-collection-gap`, `--kb-collection-cell-padding`, `--kb-collection-radius`, `--kb-collection-border`, `--kb-collection-header-background`, and `--kb-collection-selected-background`, and `--kb-collection-table-min-width` (36rem) on a subtree. Partial CSS imports also need `Button`, `Checkbox`, `Input` and `Select` styles. CSS provides layout and visual states; a non-React renderer owns every state transition and ARIA relationship.
|
|
174
|
+
|
|
175
|
+
Filters and page-size controls use the shared `Select` theme, keyboard navigation, option indicators and focus recovery. Labels sit above fields; filter fields wrap and pagination buttons stay grouped, with long page counts wrapping within the available width.
|
|
176
|
+
|
|
177
|
+
Native table semantics, `aria-sort`, mixed checkboxes, named pagination, polite statuses and failure alerts expose workflow state. Tab moves between controls; Space toggles checkboxes; Enter/Space activate buttons. The named table viewport is keyboard focusable for horizontal scrolling. Controls wrap on narrow screens; tabular content can scroll independently. Selection remains explicit in checkbox state in forced colors.
|
|
178
|
+
|
|
179
|
+
Verify the full search, sort, select-across-pages, submit, failure and retry journey with keyboard and a supported screen reader. Check 320 CSS pixels, enlarged text, both themes and forced colors. Automated stories cover their fixtures; manual screen-reader announcement and complete-page WCAG conformance remain unverified. Follow the [shared accessibility verification procedure](../../../../docs/guides/accessibility.md).
|
|
180
|
+
|
|
181
|
+
## Related components
|
|
182
|
+
|
|
183
|
+
Use [`Grid`](Grid.md) for static card layout, [`Checkbox`](Checkbox.md) for independent choices, and [`MultiSelect`](MultiSelect.md) for choosing values rather than operating on records. Use [`Dialog`](Dialog.md) for application-owned confirmation and [`Toast`](Toast.md) for mutation outcomes outside the collection.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# CollectionList
|
|
2
|
+
|
|
3
|
+
Group records in a native list when people scan names, summaries, status and actions. Use `Table` when column-by-column comparison is the main task.
|
|
4
|
+
|
|
5
|
+
[All components](../../README.md#components) · [Usage](#usage) · [Examples](#more-examples) · [Reference](#reference) · [Live examples and accessibility](https://kbui.kcb.pro/storybook/?path=/docs/components-collectionlist--docs)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import "@kb.ui/react/styles.css";
|
|
11
|
+
import { CollectionList, CollectionRow, Link, Status } from "@kb.ui/react";
|
|
12
|
+
|
|
13
|
+
export function Example() {
|
|
14
|
+
return (
|
|
15
|
+
<CollectionList aria-label="Projects">
|
|
16
|
+
<CollectionRow title={<Link href="/projects/portal">Customer portal</Link>} description="Owned by Ada Chen" metadata={<Status tone="success">Ready for review</Status>} />
|
|
17
|
+
<CollectionRow title={<Link href="/projects/search">Search improvements</Link>} description="Owned by Lin Patel" metadata={<Status tone="warning">Needs changes</Status>} />
|
|
18
|
+
</CollectionList>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## More examples
|
|
24
|
+
|
|
25
|
+
- [Ordered sequence](https://kbui.kcb.pro/storybook/?path=/story/components-collectionlist--ordered): Native `ol` markers, alphabetic `type` and `start`.
|
|
26
|
+
- [Filtering](https://kbui.kcb.pro/storybook/?path=/story/components-collectionlist--filterable): App-owned search, result announcement, empty recovery and stable search focus.
|
|
27
|
+
|
|
28
|
+
## Reference
|
|
29
|
+
|
|
30
|
+
| Prop | Default | Behavior |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `as` | `ul` | `ul` for records without a required order; `ol` for an ordered sequence |
|
|
33
|
+
| `density` | `comfortable` | `comfortable` or `dense`; affects immediate `CollectionRow` children |
|
|
34
|
+
| `dividers` | true | Rules between immediate `CollectionRow` children |
|
|
35
|
+
|
|
36
|
+
The root accepts native attributes, `className`, `style` and the correct list ref. With `as="ol"`, native `start`, `reversed`, `type` and numbering behavior remain available. Public types are `CollectionListElement` and generic `CollectionListProps`. A default `role="list"` preserves list semantics where list-style removal affects accessibility exposure; the native element still supplies the structure. See the [W3C list styling guidance](https://design-system.w3.org/styles/lists.html#considerations) for marker removal and list semantics. This is an ordinary list with normal Tab navigation, without listbox selection or arrow-key item navigation.
|
|
37
|
+
|
|
38
|
+
Use `CollectionRow` or native `li` children. A non-row `li` retains native semantics but does not receive row slot styling. If a record has leading content or an aside, the list reserves matching minimum columns across its immediate rows; missing optional content does not shift record titles. Metadata and actions share one end-aligned line, and stack beneath content, still end-aligned, when a row is narrower than 48rem. Nested lists keep their own sizing and density. Ordered markers remain visible in both directions.
|
|
39
|
+
|
|
40
|
+
Your app owns stable keys, data ordering, filtering, selection policy, pagination and requests. Keep filters mounted while results update so drafts and keyboard focus survive. Put useful empty/error guidance and a named retry action outside an empty list. Use `aria-busy` for pending content and a concise status message outside the busy subtree. Pagination or virtualization is app-owned; `CollectionList` renders the children you provide.
|
|
41
|
+
|
|
42
|
+
## Related components
|
|
43
|
+
|
|
44
|
+
[`CollectionRow`](CollectionRow.md) supplies record slots; [`DescriptionList`](DescriptionList.md) presents individual record facts; [`Table`](Table.md) supports aligned column comparison. See [Structured data](../../../../docs/guides/structured-data.md) for composition guidance.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# CollectionRow
|
|
2
|
+
|
|
3
|
+
Make one record easy to scan inside `CollectionList`. Compose its primary name, supporting detail, optional selection, metadata and independent actions.
|
|
4
|
+
|
|
5
|
+
[All components](../../README.md#components) · [Usage](#usage) · [Examples](#more-examples) · [Reference](#reference) · [Live examples and accessibility](https://kbui.kcb.pro/storybook/?path=/docs/components-collectionrow--docs)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import "@kb.ui/react/styles.css";
|
|
11
|
+
import { Button, Checkbox, CollectionList, CollectionRow, Link, Status } from "@kb.ui/react";
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
|
|
14
|
+
export function Example() {
|
|
15
|
+
const [selected, setSelected] = useState(false);
|
|
16
|
+
const [message, setMessage] = useState("No action taken");
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<p role="status">{message}</p>
|
|
20
|
+
<CollectionList aria-label="Projects">
|
|
21
|
+
<CollectionRow
|
|
22
|
+
title={<Link href="/projects/portal">Customer portal</Link>}
|
|
23
|
+
description="Owned by Ada Chen"
|
|
24
|
+
leading={<Checkbox label="Select Customer portal" visuallyHidden checked={selected} onCheckedChange={setSelected} />}
|
|
25
|
+
metadata={<Status tone="success">Ready for review</Status>}
|
|
26
|
+
actions={<Button variant="secondary" onClick={() => setMessage("Editing Customer portal")}>Edit portal</Button>}
|
|
27
|
+
selected={selected}
|
|
28
|
+
/>
|
|
29
|
+
</CollectionList>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## More examples
|
|
36
|
+
|
|
37
|
+
- [Selection and actions](https://kbui.kcb.pro/storybook/?path=/story/components-collectionrow--selection-and-actions): `Checkbox`, primary navigation, independent Buttons, `Menu` focus return and disabled actions.
|
|
38
|
+
- [Long content](https://kbui.kcb.pro/storybook/?path=/story/components-collectionrow--long-content): Wrapping identifiers, metadata and action text.
|
|
39
|
+
|
|
40
|
+
## Reference
|
|
41
|
+
|
|
42
|
+
| Prop | Default | Behavior |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `title` | required | Record name as a `ReactNode`; compose a `Link` or correctly ranked `Heading` when appropriate |
|
|
45
|
+
| `description` | unset | Supporting `ReactNode`, including `DescriptionList` or zero values |
|
|
46
|
+
| `leading` | unset | `Avatar`, decorative icon or a separately named selection control |
|
|
47
|
+
| `metadata` | unset | `Status`, formatted number, timestamp or other concise `ReactNode` |
|
|
48
|
+
| `actions` | unset | Named Buttons, links or `Menu` composition; controls keep their own semantics |
|
|
49
|
+
| `selected` | false | Visual state; a selection control owns accessible checked state |
|
|
50
|
+
| `classNames` | unset | Extra classes for `layout`, `leading`, `content`, `title`, `description`, `aside`, `metadata` and `actions` |
|
|
51
|
+
|
|
52
|
+
`CollectionRow` renders native `li`; place it inside `CollectionList` or another native list. Optional null, boolean and empty-string content omits its slot; numeric zero remains visible. Native list-item attributes, `className`, `style` and ref pass through. `CollectionRowProps` replaces the native title tooltip attribute with the required record title; use a `Tooltip` composition for contextual help. `CollectionRow` has no whole-row click handler, navigation destination or automatic focus stop. A link in title handles navigation; an action `Button` handles its own workflow. Independent actions do not toggle selection implicitly.
|
|
53
|
+
|
|
54
|
+
Keep a persistent, record-specific name on selection controls and actions. Hide decorative icons yourself, and supply meaningful alternative text for informative images. When content can be selected, keep `selected` consistent with `Checkbox` checked state; use stable IDs when filtering or sorting. Disable actual controls for unavailable operations rather than treating the whole row as disabled. A selected row is an ordinary list item, so `CollectionRow` does not emit grid/listbox `aria-selected` semantics.
|
|
55
|
+
|
|
56
|
+
`CollectionList` aligns optional leading and aside columns across rows. The defaults reserve at least 3rem for leading content and 12rem for an aside; wider leading media, or an aside that needs more room to keep metadata and actions on one line, widens its own row, so set `--kb-collectionrow-leading-width` and `--kb-collectionrow-aside-width` to your widest content to keep every title aligned. Set padding and gap with component tokens. Metadata and actions share one end-aligned line; when the row itself is narrower than 48rem, inside or outside a list, they stack beneath content in DOM order and stay end-aligned. Long labels and composed `Status` text wrap, including chips with decorative icons. Action Buttons retain size-based minimum heights while expanding for text. Dense list spacing does not shrink control targets. Standalone native lists do not provide `CollectionList`'s shared responsive alignment; use the paired components for this behavior.
|
|
57
|
+
|
|
58
|
+
Your app owns loading, error text, formatting, focus after a removed record, and selection scope. Avoid nesting interactive controls inside the title link or making the whole list item act as another button. Verify keyboard order, responsive wrapping and focus in your actual composition; automated stories do not establish screen-reader interoperability or complete-page accessibility.
|
|
59
|
+
|
|
60
|
+
## Related components
|
|
61
|
+
|
|
62
|
+
Use [`CollectionList`](CollectionList.md) for a native record list, [`DescriptionList`](DescriptionList.md) for key/value details, [`Table`](Table.md) for comparison across columns, and [`Menu`](Menu.md) for overflow actions. See [Structured data](../../../../docs/guides/structured-data.md) for the shared decision guide.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Combobox
|
|
2
|
+
|
|
3
|
+
Use `Combobox` to filter a longer option list before choosing one or multiple values. Each option has a string `value`, a visible `label`, and an optional `disabled` flag. Single selection uses `string | null`; enable `multiple` to use a `readonly string[]` value and a `(value: string[]) => void` callback. Pass `null` or `[]` for the corresponding empty selection.
|
|
4
|
+
|
|
5
|
+
Multiple mode delegates to [`MultiSelect`](MultiSelect.md), including removable chips, keyboard navigation, clear-all, form submission, and disabled/read-only states. Its popup stays open after selections and deselections, retaining the current filter. Press Escape, click outside, or use the toggle to dismiss it. It uses `.kb-multiselect` CSS anatomy and `ClassNames<MultiSelectSlot>` slots, including `chips`, `chip`, `chipLabel`, and `chipRemove`. Single mode retains `.kb-combobox` anatomy. `ComboboxProps` remains the extensible single-selection interface, so existing wrappers can keep extending it. `ComboboxSingleProps` aliases that interface; use `ComboboxMultipleProps` for a multiple-selection wrapper or React's `ComponentProps<typeof Combobox>` for the complete discriminated union.
|
|
6
|
+
|
|
7
|
+
[All components](../../README.md#components) · [Usage](#usage) · [Examples](#more-examples) · [Reference](#reference) · [Live examples and accessibility](https://kbui.kcb.pro/storybook/?path=/docs/components-combobox--docs)
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import "@kb.ui/react/styles.css";
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import { Combobox } from "@kb.ui/react";
|
|
15
|
+
|
|
16
|
+
const regions = [
|
|
17
|
+
{ value: "us-east-1", label: "US East (N. Virginia)" },
|
|
18
|
+
{ value: "eu-central-1", label: "EU Central (Frankfurt)" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function RegionCombobox() {
|
|
22
|
+
const [region, setRegion] = useState<string | null>("us-east-1");
|
|
23
|
+
return (
|
|
24
|
+
<Combobox
|
|
25
|
+
label="Deployment region"
|
|
26
|
+
options={regions}
|
|
27
|
+
value={region}
|
|
28
|
+
onValueChange={setRegion}
|
|
29
|
+
placeholder="Search regions..."
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Multiple selections
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import "@kb.ui/react/styles.css";
|
|
39
|
+
import { useState } from "react";
|
|
40
|
+
import { Combobox } from "@kb.ui/react";
|
|
41
|
+
|
|
42
|
+
export function DeploymentRegions() {
|
|
43
|
+
const [regions, setRegions] = useState<string[]>([]);
|
|
44
|
+
return (
|
|
45
|
+
<Combobox
|
|
46
|
+
multiple
|
|
47
|
+
label="Deployment regions"
|
|
48
|
+
options={[
|
|
49
|
+
{ value: "us-east-1", label: "US East (N. Virginia)" },
|
|
50
|
+
{ value: "eu-central-1", label: "EU Central (Frankfurt)" },
|
|
51
|
+
]}
|
|
52
|
+
value={regions}
|
|
53
|
+
onValueChange={setRegions}
|
|
54
|
+
placeholder="Search regions..."
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## More examples
|
|
61
|
+
|
|
62
|
+
- [Multiple selections](https://kbui.kcb.pro/storybook/?path=/story/components-combobox--multiple): Filter once, choose several options, then remove chips individually.
|
|
63
|
+
- [Retained selection](https://kbui.kcb.pro/storybook/?path=/story/components-combobox--preselected): Start with a known option.
|
|
64
|
+
- [No matching options](https://kbui.kcb.pro/storybook/?path=/story/components-combobox--empty): Inspect the empty state when filtering finds nothing.
|
|
65
|
+
- [Validation feedback](https://kbui.kcb.pro/storybook/?path=/story/components-combobox--invalid): Associate a correction with the field.
|
|
66
|
+
|
|
67
|
+
## Reference
|
|
68
|
+
|
|
69
|
+
| Prop | Type | Default | Notes |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `label` | `string` | required | Visible field label and accessible combobox name |
|
|
72
|
+
| `options` | `readonly ComboboxOption[]` | required | Selectable options filtered by label |
|
|
73
|
+
| `multiple` | `boolean` | `false` | Enable multiple selections; determines value and callback types |
|
|
74
|
+
| `value` | `string \| null` or `readonly string[]` | required | Single selection or multiple selected values |
|
|
75
|
+
| `onValueChange` | `(value: string \| null) => void` or `(value: string[]) => void` | required | Callback matches selection mode |
|
|
76
|
+
| `placeholder` | `string` | unset | Input placeholder shown when empty |
|
|
77
|
+
| `emptyMessage` | `ReactNode` | `"No matching options"` | Polite status text when no options match |
|
|
78
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Control height and text scale |
|
|
79
|
+
| `description` / `error` | `ReactNode` | unset | Associated help and invalid-state text |
|
|
80
|
+
| `disabled` / `readOnly` | `boolean` | `false` | Prevent edits; disabled also blocks form submission |
|
|
81
|
+
| `clearLabel` / `toggleLabel` | `string` | derived from `label` | Accessible names for the clear and toggle buttons |
|
|
82
|
+
| `container` | `Combobox.Portal.Props["container"]` | field root | Portal host; default retains nested theme |
|
|
83
|
+
| `classNames` | `ClassNames<ComboboxSlot>` or `ClassNames<MultiSelectSlot>` | unset | Slots match selection mode |
|
|
84
|
+
| `removeLabel` | `(option: ComboboxOption) => string` | `"Remove {option.label}"` | Multiple mode only; accessible chip removal name |
|
|
85
|
+
|
|
86
|
+
## Related components
|
|
87
|
+
|
|
88
|
+
Use [`Select`](Select.md) for a dropdown without text filtering, [`RadioGroup`](RadioGroup.md) for visible options, and [`MultiSelect`](MultiSelect.md) for several searchable choices.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ConfirmAction
|
|
2
|
+
|
|
3
|
+
`ConfirmAction` puts a destructive action behind a modal confirmation built on [`Dialog`](Dialog.md). The trigger only opens the confirmation; `onConfirm` runs after a second, explicit choice. It owns Cancel-first focus, the pending guard, blocked dismissal while pending, the failure alert with retry, and closing only after success.
|
|
4
|
+
|
|
5
|
+
[All components](../../README.md#components) · [Usage](#usage) · [Examples](#more-examples) · [Reference](#reference) · [Live examples and accessibility](https://kbui.kcb.pro/storybook/?path=/docs/components-confirmaction--docs)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import "@kb.ui/react/styles.css";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { ConfirmAction } from "@kb.ui/react";
|
|
13
|
+
|
|
14
|
+
async function deleteProject(): Promise<void> {
|
|
15
|
+
const response = await fetch("/api/projects/portal", { method: "DELETE" });
|
|
16
|
+
if (!response.ok) throw new Error(`Delete failed: ${response.status}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Example() {
|
|
20
|
+
const [deleted, setDeleted] = useState(false);
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<p role="status">{deleted ? "Project deleted." : ""}</p>
|
|
24
|
+
<ConfirmAction
|
|
25
|
+
title="Delete project?"
|
|
26
|
+
description="This permanently removes the project and its data."
|
|
27
|
+
pendingLabel="Deleting…"
|
|
28
|
+
errorMessage="Could not delete the project. Try again."
|
|
29
|
+
onConfirm={deleteProject}
|
|
30
|
+
onConfirmed={() => setDeleted(true)}
|
|
31
|
+
>
|
|
32
|
+
Delete project
|
|
33
|
+
</ConfirmAction>
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## More examples
|
|
40
|
+
|
|
41
|
+
- [Default](https://kbui.kcb.pro/storybook/?path=/story/components-confirmaction--default): A danger-zone deletion with consequence details and a pending state.
|
|
42
|
+
- [Failure and retry](https://kbui.kcb.pro/storybook/?path=/story/components-confirmaction--failure-and-retry): The first attempt fails, the dialog stays open with an alert, and the retry succeeds.
|
|
43
|
+
- [Row action](https://kbui.kcb.pro/storybook/?path=/story/components-confirmaction--row-action): A quiet row trigger whose success removes the row and moves focus to the list heading.
|
|
44
|
+
|
|
45
|
+
## Reference
|
|
46
|
+
|
|
47
|
+
| Part or behavior | Purpose |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| Trigger | A `danger` [`Button`](Button.md) by default. Native button props, `ref`, `className` and `disabled` reach it. Replace its look with `render`, such as `render={<Button variant="ghost" size="sm" />}`. |
|
|
50
|
+
| Popup | A `Dialog.Content` with `.kb-confirmaction`. `title` names it and `description` describes it; `details` adds further consequence content. |
|
|
51
|
+
| Actions | Cancel first, focused on open, then a `danger` confirm button. |
|
|
52
|
+
| Pending | While `onConfirm` runs, the popup has `aria-busy` and `data-pending`, Escape is ignored, and both buttons stay focusable with `aria-disabled` and ignore presses, so focus is never dropped. |
|
|
53
|
+
| Failure | A throw or rejection keeps the dialog open and shows `errorMessage` in a `role="alert"` danger [`Callout`](Callout.md). Confirming again retries. Closing or reopening clears the error. |
|
|
54
|
+
| Success | The dialog closes, focus returns to the trigger or `finalFocus`, then `onConfirmed` runs. |
|
|
55
|
+
| Outside press | Never dismisses, pending or not. |
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Default | Notes |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `children` | `ReactNode` | required | Trigger text; also the confirm button text unless `confirmLabel` is set |
|
|
60
|
+
| `title` | `ReactNode` | required | `Dialog` heading and accessible name; ask the question the action settles |
|
|
61
|
+
| `description` | `ReactNode` | unset | The consequence; the dialog's accessible description |
|
|
62
|
+
| `details` | `ReactNode` | unset | Further consequence content, such as what else is removed |
|
|
63
|
+
| `onConfirm` | `() => unknown` | required | Runs the action. Return a promise for asynchronous work; throw or reject on failure |
|
|
64
|
+
| `onConfirmed` | `() => void` | unset | Runs once per success, after the popup has unmounted; remove the trigger here. Also runs when success lands after a controlled close |
|
|
65
|
+
| `confirmLabel` | `ReactNode` | `children` | Confirm button text; repeat the action's verb |
|
|
66
|
+
| `pendingLabel` | `ReactNode` | `confirmLabel` | Confirm button text while the action runs |
|
|
67
|
+
| `cancelLabel` | `ReactNode` | `"Cancel"` | Cancel button text |
|
|
68
|
+
| `errorMessage` | `ReactNode \| (error: unknown) => ReactNode` | `"The action did not complete. Try again."` | Alert text after a failure; a function receives the thrown reason |
|
|
69
|
+
| `open` | `boolean` | uncontrolled | Controlled visibility |
|
|
70
|
+
| `defaultOpen` | `boolean` | `false` | Initial visibility when uncontrolled |
|
|
71
|
+
| `onOpenChange` | `(open: boolean) => void` | unset | Each visibility change, including the close after success; pending dismissals never reach it |
|
|
72
|
+
| `finalFocus` | `Dialog` `finalFocus` | trigger | Focus target after closing; set it when success removes the trigger |
|
|
73
|
+
| `container` | portal container | document body | Themed host for the popup |
|
|
74
|
+
| `classNames` | `{ popup?, backdrop?, details?, error? }` | unset | Classes for inner parts |
|
|
75
|
+
| `render` | Base UI `render` | `<Button variant="danger" />` | Trigger element |
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<ConfirmAction
|
|
79
|
+
render={<Button variant="ghost" size="sm" />}
|
|
80
|
+
aria-label="Revoke Laptop"
|
|
81
|
+
title="Revoke Laptop?"
|
|
82
|
+
description="Requests signed with this key fail immediately."
|
|
83
|
+
confirmLabel="Revoke key"
|
|
84
|
+
finalFocus={listHeading}
|
|
85
|
+
onConfirm={() => revokeKey("laptop")}
|
|
86
|
+
onConfirmed={() => removeRow("laptop")}
|
|
87
|
+
>
|
|
88
|
+
Revoke
|
|
89
|
+
</ConfirmAction>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Responsibilities
|
|
93
|
+
|
|
94
|
+
The application owns the operation: authorization, idempotency, timeouts and what the server deletes. `onConfirm` must settle; a promise that never settles leaves the dialog pending. Announce success in the page, because the dialog is gone when it completes. Remove the trigger's row in `onConfirmed`, not inside `onConfirm`: a data layer that removes the row while the action is still settling unmounts the dialog mid-close and loses focus. Do not attach the same action anywhere else on the trigger; the confirmation is its one route.
|
|
95
|
+
|
|
96
|
+
For an urgent interruption that needs `alertdialog` semantics, use an alert-dialog primitive. `ConfirmAction` remains a named modal dialog, as the [confirmation pattern](../../../../docs/guides/forms-and-overlays.md#confirmation-pattern) describes.
|
|
97
|
+
|
|
98
|
+
## Related components
|
|
99
|
+
|
|
100
|
+
Use [`Dialog`](Dialog.md) to compose a confirmation that needs fields or a different action layout. Use [`Button`](Button.md) `variant="danger"` alone only when the action is reversible, such as one with undo.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Container
|
|
2
|
+
|
|
3
|
+
Centered page boundary with responsive inline gutters. Native props and matching ref pass through. See [Layout composition](https://github.com/KhallilB/kbui/blob/main/docs/guides/layout.md) for rules, CSS recipes and token overrides.
|
|
4
|
+
|
|
5
|
+
[All components](../../README.md#components) · [Usage](#usage) · [Examples](#more-examples) · [Reference](#reference) · [Live examples and accessibility](https://kbui.kcb.pro/storybook/?path=/docs/layout-container--docs)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import "@kb.ui/react/styles.css";
|
|
11
|
+
import { Container } from "@kb.ui/react";
|
|
12
|
+
|
|
13
|
+
export function Example() {
|
|
14
|
+
return (
|
|
15
|
+
<Container as="main">
|
|
16
|
+
<h1>Projects</h1>
|
|
17
|
+
<p>Your current work.</p>
|
|
18
|
+
</Container>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## More examples
|
|
24
|
+
|
|
25
|
+
- [A composed page](https://kbui.kcb.pro/storybook/?path=/story/layout-container--page): Combine a page boundary, content, and actions.
|
|
26
|
+
- [Narrow viewports](https://kbui.kcb.pro/storybook/?path=/story/layout-container--responsive): Compare responsive gutters.
|
|
27
|
+
- [Width choices](https://kbui.kcb.pro/storybook/?path=/story/layout-container--sizes): Compare available page widths.
|
|
28
|
+
|
|
29
|
+
## Reference
|
|
30
|
+
|
|
31
|
+
| Prop | Values | Default |
|
|
32
|
+
| -------- | ---------------------------------------------------------- | ------- |
|
|
33
|
+
| `as` | `div` `main` `section` `article` `aside` `header` `footer` | `div` |
|
|
34
|
+
| `size` | `sm` (40rem) `md` (64rem) `lg` (80rem) `full` (unbounded) | `lg` |
|
|
35
|
+
| `gutter` | Shared 1–2rem inline padding; false removes it | `true` |
|
|
36
|
+
|
|
37
|
+
Set `as` explicitly when using an element other than the default; native props and refs then match that element. An explicit `gutter` value takes priority over a conflicting data attribute. Widths include side padding. Use one page container, and turn off nested gutters when the parent already provides padding.
|
|
38
|
+
|
|
39
|
+
## Related components
|
|
40
|
+
|
|
41
|
+
Use [`Inline`](Inline.md) for a row, [`Stack`](Stack.md) for vertical flow, [`Grid`](Grid.md) for a collection, and [`Split`](Split.md) for two responsive columns.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# CopyableText
|
|
2
|
+
|
|
3
|
+
Copy identifiers, commands and references with explicit feedback. Values remain selectable, including when clipboard permission is denied.
|
|
4
|
+
|
|
5
|
+
[All components](../../README.md#components) · [Usage](#usage) · [Reference](#reference) · [Live examples and accessibility](https://kbui.kcb.pro/storybook/?path=/docs/components-copyabletext--docs)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import "@kb.ui/react/styles.css";
|
|
11
|
+
import { CopyableText } from "@kb.ui/react";
|
|
12
|
+
|
|
13
|
+
export function Example() {
|
|
14
|
+
return (
|
|
15
|
+
<CopyableText
|
|
16
|
+
label="Request ID"
|
|
17
|
+
value="req_01JCP6NF8B4M2WQ9K7X3"
|
|
18
|
+
copyLabel="Copy request ID"
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`value` is the clipboard source. Case, whitespace, Unicode and trailing newlines are preserved. `children` can format the collapsed display independently; expanding or a copy failure reveals the exact value for inspection and manual copying. Do not use this component to conceal secrets.
|
|
25
|
+
|
|
26
|
+
The native `button` retains focus while a write is pending. Repeated activation during that write does nothing. Success appears only after the Clipboard API resolves; its callback receives the copied value. If value changes, the component is disabled, or it unmounts, outstanding writes cannot update feedback or fire callbacks. A write already sent to the operating system cannot be cancelled.
|
|
27
|
+
|
|
28
|
+
A visible failure message remains until retry or content change. Users can select the full value and copy manually. Clipboard writes require a secure context such as HTTPS or localhost; browser permission, user activation, iframe policy or host restrictions can still reject them. No legacy clipboard fallback is attempted.
|
|
29
|
+
|
|
30
|
+
With `truncate`, a single-line preview has an explicit `Show full value` button. Activating it wraps the complete raw value across the field width; `Show less` restores the compact preview. The action works with touch, Enter and Space, retains focus, and exposes `aria-expanded` and `aria-controls`. It stays available when copying is disabled. A changed value resets the disclosure. Failure automatically expands it; users can collapse it again deliberately. CSS truncation preserves full text in the accessibility tree; disclosure controls visual wrapping. No hover-only tooltip is required.
|
|
31
|
+
|
|
32
|
+
Set `tone` to color the value and softly tint field appearance. Neutral is the default; accent, info, success, warning and danger reuse semantic theme colors. Tone is emphasis, not clipboard state or an announcement. Keep meaning in the label and value, including forced-colors mode. For a custom neutral color, override `--kb-copyabletext-value-color`; use `classNames.value` for richer display styling.
|
|
33
|
+
|
|
34
|
+
Set `highlight` to a string or array of literal, case-sensitive terms. Matches use semantic `mark` elements with bold emphasis; the longest match wins when terms overlap at the same position. Matching never interprets HTML or regular expressions and never changes copied content.
|
|
35
|
+
|
|
36
|
+
Automatic `format="auto"` recognizes HTTP(S) and `www.` URLs, qualified ASCII email addresses, backtick-delimited inline code, fenced code, valid JSON objects/arrays, and conservative JavaScript/TypeScript, CSS, HTML and common package-manager/Git/curl command patterns. `Code` receives the existing syntax colors and suppresses navigation inside source. Delimiters, whitespace and clipboard bytes remain literal.
|
|
37
|
+
|
|
38
|
+
Use `format="text"` for identifiers or prose that resemble code, or `format="code"` with `language` for explicit source. A language hint forces code in automatic mode. Detection is intentionally conservative; it does not validate email deliverability or infer every programming language. Unknown fenced languages render as plain code. For large listings, prefer `CodeBlock`.
|
|
39
|
+
|
|
40
|
+
Detected links reuse `Link` styling and `Link.Provider`. Emails use mailto destinations with an encoded address; web links remain underlined native anchors. Sentence punctuation and unmatched closing brackets stay outside anchors. Set `linkify={false}` to disable detected links, or `linkify` with `format="text"` to opt back into links without code detection. Authored children retain caller-owned `Link`, strong, em or `Code` composition; automatic formatting applies to displayed raw source.
|
|
41
|
+
|
|
42
|
+
In truncated previews, detected links become interactive only after expansion. This keeps clipped links out of Tab order. After expanding, Shift+Tab reaches links above the disclosure; Tab continues to Copy. Copying remains a separate action. Errors expand the same formatted raw source for manual selection. `mono={false}` gives prose the proportional font and body line spacing.
|
|
43
|
+
|
|
44
|
+
## More examples
|
|
45
|
+
|
|
46
|
+
- [Automatic recognition](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--automatic): URLs, emails, inline commands and source code.
|
|
47
|
+
- [Text and links](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--rich-content): prose with literal highlights and native web links.
|
|
48
|
+
- [References](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--references): identifiers, URLs and invitation text.
|
|
49
|
+
- [Inline](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--inline): compact reference beside prose.
|
|
50
|
+
- [Long values](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--long-value): expand and collapse a full-width wrapped value.
|
|
51
|
+
- [Colors](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--colors): six value tones with matching field tints.
|
|
52
|
+
- [Copy feedback](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--copy-feedback): pending, focus retention and success.
|
|
53
|
+
- [Copy failure](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--copy-failure): persistent recovery instructions.
|
|
54
|
+
- [Localized](https://kbui.kcb.pro/storybook/?path=/story/components-copyabletext--localized): translated button and announcement text.
|
|
55
|
+
|
|
56
|
+
## Reference
|
|
57
|
+
|
|
58
|
+
| Prop | Type | Default | Notes |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| `value` | `string` | required | Exact clipboard contents; empty string disables action |
|
|
61
|
+
| `children` | `ReactNode` | `value` | Optional display; exact value is shown on failure |
|
|
62
|
+
| `label` | `string` | unset | Visible context above value; does not name the button |
|
|
63
|
+
| `appearance` | `"field" \| "plain"` | `"field"` | Bordered field or inline treatment |
|
|
64
|
+
| `size` | `"sm" \| "md"` | `"md"` | Density; default copy target follows button height token |
|
|
65
|
+
| `mono` | `boolean` | `true` | Monospace value display |
|
|
66
|
+
| `tone` | `CopyableTextTone` | `"neutral"` | `neutral`, `accent`, `info`, `success`, `warning` or `danger`; independent of copy state |
|
|
67
|
+
| `truncate` | `boolean` | `false` | Single-line preview with explicit full-value disclosure |
|
|
68
|
+
| `expandLabel` | `string` | `"Show full value"` | `Disclosure` text when collapsed |
|
|
69
|
+
| `collapseLabel` | `string` | `"Show less"` | `Disclosure` text when expanded |
|
|
70
|
+
| `disabled` | `boolean` | `false` | Disables copying, retains selectable value |
|
|
71
|
+
| `copyLabel` | `string` | `"Copy"` | Visible action and accessible name; use specific purpose |
|
|
72
|
+
| `copyingLabel` | `string` | `"Copying…"` | Pending label and polite announcement |
|
|
73
|
+
| `copiedLabel` | `string` | `"Copied"` | Success label |
|
|
74
|
+
| `copyErrorLabel` | `string` | `"Retry copy"` | Failure label; action retries |
|
|
75
|
+
| `successMessage` | `string` | `"Copied to clipboard"` | Polite success announcement |
|
|
76
|
+
| `errorMessage` | `string` | `"Copy failed. Select and copy the text manually."` | Visible polite error and recovery instructions |
|
|
77
|
+
| `feedbackDuration` | `number` | `2000` | Success reset in milliseconds; zero retains success, invalid or negative uses default |
|
|
78
|
+
| `onCopy` | `(value: string) => void` | unset | Current write succeeded; replaces native clipboard event prop |
|
|
79
|
+
| `onCopyError` | `(error: unknown) => void` | unset | Current write failed, including unavailable API |
|
|
80
|
+
| `highlight` | `string \| readonly string[]` | unset | Literal case-sensitive terms; empty terms ignored, longest overlapping match wins |
|
|
81
|
+
| `format` | `CopyableTextFormat` | `"auto"` | `auto`, `text`, or `code`; conservative recognition with explicit overrides |
|
|
82
|
+
| `language` | `CodeLanguage` | unset | `Code` hint; forces code in auto mode; text mode remains literal |
|
|
83
|
+
| `linkify` | `boolean` | automatic | Override detected web/email links; false disables links; code ranges never become links |
|
|
84
|
+
| `classNames` | `Partial<Record<CopyableTextPart, string>>` | unset | `label`, `value`, `actions`, `toggle`, `copy`, `feedback`, `link`, `highlight`, `code` slots |
|
|
85
|
+
| `className`, `ref`, other props | Native `span` props | unset | Root element; native `onCopy` replaced by success callback |
|
|
86
|
+
|
|
87
|
+
The root exposes `data-copy-state="idle|pending|copied|failed"`, appearance, size, tone, mono, expandable, expanded, truncate and disabled attributes. Pending sets `aria-disabled` on the button while preserving focus. The status region exists before interaction and is empty initially. Failure is both visible and announced; success has a visible button label and polite announcement.
|
|
88
|
+
|
|
89
|
+
Use `--kb-copyabletext-highlight-background` for marked text and `Link` tokens for navigation styling. Standalone CSS users also load `link.css` and `code.css` for detected links and syntax styling. Use `--kb-copyabletext-*` tokens for gap, padding, radius, border, background, neutral value color, target size and minimum copy width. `className` styles the root; `classNames` styles individual parts. Verify contrast and target sizes after overrides. Automated tests cover component behavior, browser keyboard activation and accessibility scans; screen-reader announcement quality remains a human verification task.
|
|
90
|
+
|
|
91
|
+
## Related components
|
|
92
|
+
|
|
93
|
+
Use [`CodeBlock`](CodeBlock.md) for highlighted source and multiline commands, [`Code`](Code.md) for inline literals, and [`Link`](Link.md) for navigation. [Copyable content guidance](https://github.com/KhallilB/kbui/blob/main/docs/guides/copyable-content.md) covers shared recovery and localization.
|