@ui-sdk/react 0.0.0-stage → 0.1.0-beta.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.
- package/LICENSE +202 -0
- package/README.md +10 -2
- package/dist/accordion.d.ts +331 -0
- package/dist/accordion.js +25 -0
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +8 -0
- package/dist/alert-dialog.d.ts +56 -0
- package/dist/alert-dialog.js +45 -0
- package/dist/async-options.d.ts +22 -0
- package/dist/async-options.js +56 -0
- package/dist/autocomplete.d.ts +102 -0
- package/dist/autocomplete.js +195 -0
- package/dist/avatar.d.ts +301 -0
- package/dist/avatar.js +17 -0
- package/dist/button.d.ts +21 -0
- package/dist/button.js +45 -0
- package/dist/calendar-context.d.ts +10 -0
- package/dist/calendar-context.js +9 -0
- package/dist/calendar.d.ts +80 -0
- package/dist/calendar.js +257 -0
- package/dist/carousel.d.ts +52 -0
- package/dist/carousel.js +151 -0
- package/dist/checkbox.d.ts +24 -0
- package/dist/checkbox.js +98 -0
- package/dist/children.d.ts +38 -0
- package/dist/children.js +63 -0
- package/dist/collapsible.d.ts +302 -0
- package/dist/collapsible.js +19 -0
- package/dist/color-picker.d.ts +72 -0
- package/dist/color-picker.js +133 -0
- package/dist/color.d.ts +2 -0
- package/dist/color.js +22 -0
- package/dist/combobox.d.ts +152 -0
- package/dist/combobox.js +338 -0
- package/dist/command.d.ts +88 -0
- package/dist/command.js +233 -0
- package/dist/controller-target.d.ts +3 -0
- package/dist/controller-target.js +59 -0
- package/dist/date-input.d.ts +19 -0
- package/dist/date-input.js +58 -0
- package/dist/date-picker.d.ts +47 -0
- package/dist/date-picker.js +310 -0
- package/dist/date.d.ts +2 -0
- package/dist/date.js +60 -0
- package/dist/dialog-parts.d.ts +23 -0
- package/dist/dialog-parts.js +142 -0
- package/dist/dialog.d.ts +57 -0
- package/dist/dialog.js +32 -0
- package/dist/drawer.d.ts +70 -0
- package/dist/drawer.js +158 -0
- package/dist/editor-context.d.ts +4 -0
- package/dist/editor-context.js +9 -0
- package/dist/field-context.d.ts +47 -0
- package/dist/field-context.js +87 -0
- package/dist/field.d.ts +49 -0
- package/dist/field.js +169 -0
- package/dist/file-input.d.ts +46 -0
- package/dist/file-input.js +170 -0
- package/dist/form-inputs.d.ts +5 -0
- package/dist/form-inputs.js +22 -0
- package/dist/gesture.d.ts +1 -0
- package/dist/gesture.js +16 -0
- package/dist/history.d.ts +2 -0
- package/dist/history.js +12 -0
- package/dist/hover-card.d.ts +33 -0
- package/dist/hover-card.js +84 -0
- package/dist/image-cropper.d.ts +333 -0
- package/dist/image-cropper.js +37 -0
- package/dist/image.d.ts +2 -0
- package/dist/image.js +6 -0
- package/dist/input.d.ts +22 -0
- package/dist/input.js +51 -0
- package/dist/label.d.ts +294 -0
- package/dist/label.js +9 -0
- package/dist/list-items.d.ts +15 -0
- package/dist/list-items.js +39 -0
- package/dist/list-text.d.ts +4 -0
- package/dist/list-text.js +15 -0
- package/dist/listbox.d.ts +56 -0
- package/dist/listbox.js +122 -0
- package/dist/menu.d.ts +116 -0
- package/dist/menu.js +426 -0
- package/dist/menubar-context.d.ts +4 -0
- package/dist/menubar-context.js +7 -0
- package/dist/menubar.d.ts +11 -0
- package/dist/menubar.js +32 -0
- package/dist/meter.d.ts +312 -0
- package/dist/meter.js +30 -0
- package/dist/navigation-menu.d.ts +61 -0
- package/dist/navigation-menu.js +169 -0
- package/dist/number-input.d.ts +44 -0
- package/dist/number-input.js +185 -0
- package/dist/number.d.ts +2 -0
- package/dist/number.js +40 -0
- package/dist/observe.d.ts +2 -0
- package/dist/observe.js +8 -0
- package/dist/otp-input.d.ts +29 -0
- package/dist/otp-input.js +94 -0
- package/dist/overlays.d.ts +26 -0
- package/dist/overlays.js +82 -0
- package/dist/pagination.d.ts +336 -0
- package/dist/pagination.js +71 -0
- package/dist/password-input.d.ts +23 -0
- package/dist/password-input.js +65 -0
- package/dist/popover.d.ts +54 -0
- package/dist/popover.js +151 -0
- package/dist/popup.d.ts +41 -0
- package/dist/popup.js +180 -0
- package/dist/preferences.d.ts +35 -0
- package/dist/preferences.js +93 -0
- package/dist/primitive.d.ts +2 -0
- package/dist/primitive.js +14 -0
- package/dist/progress.d.ts +312 -0
- package/dist/progress.js +30 -0
- package/dist/radio.d.ts +22 -0
- package/dist/radio.js +86 -0
- package/dist/rating.d.ts +20 -0
- package/dist/rating.js +94 -0
- package/dist/render-element.d.ts +35 -0
- package/dist/render-element.js +103 -0
- package/dist/resizable.d.ts +302 -0
- package/dist/resizable.js +23 -0
- package/dist/scroll-area.d.ts +324 -0
- package/dist/scroll-area.js +49 -0
- package/dist/segmented-control.d.ts +24 -0
- package/dist/segmented-control.js +78 -0
- package/dist/segmented-input.d.ts +15 -0
- package/dist/segmented-input.js +48 -0
- package/dist/select.d.ts +125 -0
- package/dist/select.js +330 -0
- package/dist/separator.d.ts +290 -0
- package/dist/separator.js +9 -0
- package/dist/shortcuts.d.ts +32 -0
- package/dist/shortcuts.js +61 -0
- package/dist/slider.d.ts +38 -0
- package/dist/slider.js +163 -0
- package/dist/switch.d.ts +15 -0
- package/dist/switch.js +39 -0
- package/dist/tabs.d.ts +331 -0
- package/dist/tabs.js +28 -0
- package/dist/text-control.d.ts +22 -0
- package/dist/text-control.js +24 -0
- package/dist/text-match.d.ts +2 -0
- package/dist/text-match.js +8 -0
- package/dist/textarea.d.ts +10 -0
- package/dist/textarea.js +48 -0
- package/dist/time-input.d.ts +19 -0
- package/dist/time-input.js +63 -0
- package/dist/timeline.d.ts +327 -0
- package/dist/timeline.js +31 -0
- package/dist/toast-parts.d.ts +63 -0
- package/dist/toast-parts.js +144 -0
- package/dist/toast.d.ts +2 -0
- package/dist/toast.js +22 -0
- package/dist/toggle.d.ts +23 -0
- package/dist/toggle.js +76 -0
- package/dist/token-input.d.ts +35 -0
- package/dist/token-input.js +100 -0
- package/dist/toolbar-context.d.ts +6 -0
- package/dist/toolbar-context.js +16 -0
- package/dist/toolbar.d.ts +38 -0
- package/dist/toolbar.js +63 -0
- package/dist/tooltip.d.ts +39 -0
- package/dist/tooltip.js +108 -0
- package/dist/tree.d.ts +51 -0
- package/dist/tree.js +150 -0
- package/dist/trigger-menu.d.ts +83 -0
- package/dist/trigger-menu.js +167 -0
- package/dist/use-primitive.d.ts +178 -0
- package/dist/use-primitive.js +674 -0
- package/dist/waterfall.d.ts +80 -0
- package/dist/waterfall.js +108 -0
- package/docs/README.md +89 -0
- package/docs/agents.md +1523 -0
- package/docs/components/accordion.md +1391 -0
- package/docs/components/alert-dialog.md +850 -0
- package/docs/components/autocomplete.md +1963 -0
- package/docs/components/avatar.md +252 -0
- package/docs/components/button.md +444 -0
- package/docs/components/calendar.md +1687 -0
- package/docs/components/carousel.md +1244 -0
- package/docs/components/checkbox.md +1069 -0
- package/docs/components/collapsible.md +567 -0
- package/docs/components/color-picker.md +736 -0
- package/docs/components/combobox.md +3647 -0
- package/docs/components/command.md +1573 -0
- package/docs/components/date-input.md +608 -0
- package/docs/components/date-picker.md +1266 -0
- package/docs/components/dialog.md +1469 -0
- package/docs/components/drawer.md +1197 -0
- package/docs/components/field.md +549 -0
- package/docs/components/file-input.md +609 -0
- package/docs/components/hover-card.md +514 -0
- package/docs/components/image-cropper.md +579 -0
- package/docs/components/input.md +301 -0
- package/docs/components/label.md +217 -0
- package/docs/components/listbox.md +1130 -0
- package/docs/components/menu.md +1286 -0
- package/docs/components/menubar.md +455 -0
- package/docs/components/meter.md +304 -0
- package/docs/components/navigation-menu.md +1581 -0
- package/docs/components/number-input.md +1086 -0
- package/docs/components/otp-input.md +565 -0
- package/docs/components/pagination.md +648 -0
- package/docs/components/password-input.md +737 -0
- package/docs/components/popover.md +1552 -0
- package/docs/components/preferences.md +220 -0
- package/docs/components/progress.md +443 -0
- package/docs/components/radio.md +846 -0
- package/docs/components/rating.md +458 -0
- package/docs/components/resizable.md +982 -0
- package/docs/components/scroll-area.md +980 -0
- package/docs/components/segmented-control.md +465 -0
- package/docs/components/select.md +2133 -0
- package/docs/components/separator.md +243 -0
- package/docs/components/slider.md +1169 -0
- package/docs/components/switch.md +613 -0
- package/docs/components/tabs.md +1014 -0
- package/docs/components/textarea.md +318 -0
- package/docs/components/time-input.md +231 -0
- package/docs/components/timeline.md +677 -0
- package/docs/components/toast.md +1395 -0
- package/docs/components/toggle.md +541 -0
- package/docs/components/token-input.md +1007 -0
- package/docs/components/toolbar.md +787 -0
- package/docs/components/tooltip.md +884 -0
- package/docs/components/tree.md +1346 -0
- package/docs/components/trigger-menu.md +1257 -0
- package/docs/components/waterfall.md +754 -0
- package/docs/controllers.md +89 -0
- package/docs/core.md +8027 -0
- package/docs/custom-parts.md +120 -0
- package/docs/first-component.md +83 -0
- package/docs/forms.md +148 -0
- package/docs/hooks/async-options.md +366 -0
- package/docs/hooks/shortcuts.md +221 -0
- package/docs/installation.md +45 -0
- package/docs/introduction.md +32 -0
- package/docs/lists-and-menus.md +170 -0
- package/docs/llms.txt +1844 -0
- package/docs/parts-and-composition.md +93 -0
- package/docs/popups.md +117 -0
- package/docs/rendering.md +121 -0
- package/docs/server-rendering.md +253 -0
- package/docs/state-and-events.md +105 -0
- package/docs/styling-and-animation.md +176 -0
- package/package.json +333 -3
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Custom Parts
|
|
2
|
+
|
|
3
|
+
Write a part with its own React logic and connect it to a primitive.
|
|
4
|
+
|
|
5
|
+
The parts from [Rendering Primitives](./rendering.md) are enough until one needs its own logic. You can replace just that part and keep the same `rating.ts` definition from [Compose Behaviors](./core.md#complete-primitive).
|
|
6
|
+
|
|
7
|
+
## Write a Root Part
|
|
8
|
+
|
|
9
|
+
Here's a replacement for the generated `RatingRoot`. `usePrimitive` creates its instance, and `usePart` connects the `root` slot:
|
|
10
|
+
|
|
11
|
+
```tsx title="rating-root.tsx"
|
|
12
|
+
"use client"
|
|
13
|
+
|
|
14
|
+
import type { StateOf } from "@ui-sdk/core"
|
|
15
|
+
import { usePart, usePrimitive, type PartProps } from "@ui-sdk/react/primitive"
|
|
16
|
+
import { rating, type RatingOptions } from "./rating"
|
|
17
|
+
|
|
18
|
+
function RatingRoot({
|
|
19
|
+
value,
|
|
20
|
+
defaultValue,
|
|
21
|
+
onValueChange,
|
|
22
|
+
...props
|
|
23
|
+
}: RatingOptions &
|
|
24
|
+
Omit<PartProps<"div", StateOf<typeof rating, "root">>, "defaultValue">) {
|
|
25
|
+
const handle = usePrimitive(rating, { value, defaultValue, onValueChange })
|
|
26
|
+
return usePart(handle, "root", {}, props, "div")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { RatingRoot }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Pass primitive options to `usePrimitive` and element props to `usePart`. The empty `{}` is for slot props; this root doesn't need any. `usePart` shares the instance with child parts and renders nothing when the slot isn't present.
|
|
33
|
+
|
|
34
|
+
The adapter keeps options current and manages the instance's lifetime. Call `handle.api` from event handlers or effects. To render changing state, use a part's `children` or `render` function; reading `handle.state` alone doesn't subscribe to changes.
|
|
35
|
+
|
|
36
|
+
## Connect a Child Part
|
|
37
|
+
|
|
38
|
+
A child can pass the primitive itself to `usePart`. It finds the instance shared by the root above it.
|
|
39
|
+
|
|
40
|
+
This `RatingStar` works inside the manual root above or the generated `RatingRoot`. It adds a default accessible label:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
"use client"
|
|
44
|
+
|
|
45
|
+
import { usePart, type PartProps } from "@ui-sdk/react/primitive"
|
|
46
|
+
import { rating } from "./rating"
|
|
47
|
+
|
|
48
|
+
function RatingStar({
|
|
49
|
+
value,
|
|
50
|
+
...props
|
|
51
|
+
}: { value: string } & Omit<PartProps<"button">, "value">) {
|
|
52
|
+
const label = `${value} ${value === "1" ? "star" : "stars"}`
|
|
53
|
+
return usePart(
|
|
54
|
+
rating,
|
|
55
|
+
"star",
|
|
56
|
+
{ value },
|
|
57
|
+
{ "aria-label": label, ...props },
|
|
58
|
+
"button"
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { RatingStar }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`{ value }` identifies the star. The remaining props go to its button. A caller can override the label with `aria-label`.
|
|
66
|
+
|
|
67
|
+
Use it in place of the generated star in [Render the Rating](./rendering.md#render-the-rating). Keep it inside `RatingRoot` so it can find the instance.
|
|
68
|
+
|
|
69
|
+
## Merge Props
|
|
70
|
+
|
|
71
|
+
Use `mergeProps` when your part adds props that need to combine with the caller's. For example, an input can select its text on focus while still accepting an `onFocus` handler:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
"use client"
|
|
75
|
+
|
|
76
|
+
import {
|
|
77
|
+
mergeProps,
|
|
78
|
+
renderElement,
|
|
79
|
+
type PartProps,
|
|
80
|
+
} from "@ui-sdk/react/primitive"
|
|
81
|
+
import type { FocusEvent } from "react"
|
|
82
|
+
|
|
83
|
+
function SelectOnFocusInput({ render, ...props }: PartProps<"input">) {
|
|
84
|
+
const own = {
|
|
85
|
+
onFocus: (event: FocusEvent<HTMLInputElement>) =>
|
|
86
|
+
event.currentTarget.select(),
|
|
87
|
+
}
|
|
88
|
+
return renderElement("input", mergeProps(own, props), render)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { SelectOnFocusInput }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The caller's handler runs first and can call `preventDefault()` to skip yours. Class names join, styles merge, and refs combine. Other values from later arguments win; `undefined` doesn't replace an existing value.
|
|
95
|
+
|
|
96
|
+
For native `pointerenter` and `pointerleave` slot listeners, canceling React's handler doesn't cancel the slot's listener.
|
|
97
|
+
|
|
98
|
+
## An Element Without a Primitive
|
|
99
|
+
|
|
100
|
+
`renderElement` also works for a plain wrapper. It gives the wrapper the same `render` prop as the other parts:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
"use client"
|
|
104
|
+
|
|
105
|
+
import { renderElement, type PartProps } from "@ui-sdk/react/primitive"
|
|
106
|
+
|
|
107
|
+
function Card({ render, ...props }: PartProps<"div">) {
|
|
108
|
+
return renderElement("div", props, render)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { Card }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`PartProps` supplies the element props and `render`. Add its second type argument when `className`, `style`, or `children` should receive your part's state.
|
|
115
|
+
|
|
116
|
+
`renderElement` uses no hooks, so a wrapper like `Card` also works in a Server Component. Anything built on `usePrimitive`, `usePart` or `createParts` still needs a client component, since those use hooks. [Server Rendering](./server-rendering.md#wrappers-in-a-server-component) shows one.
|
|
117
|
+
|
|
118
|
+
## Compose a Popup
|
|
119
|
+
|
|
120
|
+
A custom part can render an existing Popover or Dialog and connect its open state. [Compose Ours](./core.md#compose-ours) shows that connection with an emoji picker.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# First Component
|
|
2
|
+
|
|
3
|
+
Add a component to your app and style its state.
|
|
4
|
+
|
|
5
|
+
Let's add a Bookmark toggle. Press it once to turn it on, then again to turn it off.
|
|
6
|
+
|
|
7
|
+
If you haven't installed the packages yet, start with [Installation](./installation.md).
|
|
8
|
+
|
|
9
|
+
## Create the Component
|
|
10
|
+
|
|
11
|
+
Create `bookmark-button.tsx` next to your app component and render a `Toggle`:
|
|
12
|
+
|
|
13
|
+
```tsx title="bookmark-button.tsx"
|
|
14
|
+
import { Toggle } from "@ui-sdk/react/toggle"
|
|
15
|
+
|
|
16
|
+
function BookmarkButton() {
|
|
17
|
+
return <Toggle>Bookmark</Toggle>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { BookmarkButton }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`Toggle` renders a button. It keeps track of its pressed state, handles Enter and Space, and sets `aria-pressed` for screen readers.
|
|
24
|
+
|
|
25
|
+
## Use It in Your App
|
|
26
|
+
|
|
27
|
+
In your app component (`app.tsx` here), import and render `BookmarkButton`:
|
|
28
|
+
|
|
29
|
+
```tsx title="app.tsx"
|
|
30
|
+
import { BookmarkButton } from "./bookmark-button"
|
|
31
|
+
|
|
32
|
+
function App() {
|
|
33
|
+
return <BookmarkButton />
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { App }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Open your app. You should see a button labeled Bookmark. Next, add styles so you can tell when it is pressed.
|
|
40
|
+
|
|
41
|
+
## Add Styles
|
|
42
|
+
|
|
43
|
+
When the toggle is pressed, it has a `data-pressed` attribute. Use that attribute to change its appearance.
|
|
44
|
+
|
|
45
|
+
With Tailwind CSS configured in your app, add utility classes to `className`. The `data-pressed:` variant applies while the toggle is pressed:
|
|
46
|
+
|
|
47
|
+
```tsx title="bookmark-button.tsx"
|
|
48
|
+
import { Toggle } from "@ui-sdk/react/toggle"
|
|
49
|
+
|
|
50
|
+
function BookmarkButton() {
|
|
51
|
+
return (
|
|
52
|
+
<Toggle className="inline-flex h-8 items-center rounded-md border border-neutral-300 px-3 text-sm font-medium hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2 data-pressed:bg-neutral-900 data-pressed:text-white">
|
|
53
|
+
Bookmark
|
|
54
|
+
</Toggle>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { BookmarkButton }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Try the styled toggle:
|
|
62
|
+
|
|
63
|
+
```tsx title="first-component.tsx"
|
|
64
|
+
import { Toggle } from "@ui-sdk/react/toggle"
|
|
65
|
+
|
|
66
|
+
function Example() {
|
|
67
|
+
return <Toggle data-variant="outline">Bookmark</Toggle>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { Example }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Click Bookmark to turn it on or off. A filled background means it is on. You can also focus the button with Tab and press Enter or Space.
|
|
74
|
+
|
|
75
|
+
Keep the label the same in both states. `aria-pressed` tells a screen reader whether the toggle is on.
|
|
76
|
+
|
|
77
|
+
## Keep Going
|
|
78
|
+
|
|
79
|
+
- [Toggle](./components/toggle.md) covers disabled states, controlled state, and groups of toggles.
|
|
80
|
+
- [Components](./README.md) lists the other components you can add with the same install.
|
|
81
|
+
- [Parts and Composition](./parts-and-composition.md) shows how to put a component with several parts together.
|
|
82
|
+
- [State and Events](./state-and-events.md) shows how to read changes and control state from your app.
|
|
83
|
+
- [Styling and Animation](./styling-and-animation.md) covers state attributes, CSS variables, and transitions.
|
package/docs/forms.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
Submit component values, label controls, and show validation errors.
|
|
4
|
+
|
|
5
|
+
Give a control a `name` to include its value in a form. This works for Select and Combobox as well as inputs and checkboxes.
|
|
6
|
+
|
|
7
|
+
## Submit a Value
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Checkbox } from "@ui-sdk/react/checkbox"
|
|
11
|
+
|
|
12
|
+
function Example() {
|
|
13
|
+
return (
|
|
14
|
+
<form action="/preferences" method="post">
|
|
15
|
+
<label>
|
|
16
|
+
<Checkbox name="updates" value="yes" defaultChecked />
|
|
17
|
+
Email me updates
|
|
18
|
+
</label>
|
|
19
|
+
<button type="submit">Save</button>
|
|
20
|
+
<button type="reset">Reset</button>
|
|
21
|
+
</form>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { Example }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The checkbox submits `updates=yes` when checked. Reset restores its default value.
|
|
29
|
+
|
|
30
|
+
## Reset a Controlled Value
|
|
31
|
+
|
|
32
|
+
A reset also goes through `onCheckedChange`. Update your React state there, just as you would after a click:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
"use client"
|
|
36
|
+
|
|
37
|
+
import { Checkbox, type CheckedState } from "@ui-sdk/react/checkbox"
|
|
38
|
+
import { useState } from "react"
|
|
39
|
+
|
|
40
|
+
function Example() {
|
|
41
|
+
const [checked, setChecked] = useState<CheckedState>(true)
|
|
42
|
+
const [reason, setReason] = useState("No change yet")
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<form>
|
|
46
|
+
<label>
|
|
47
|
+
<Checkbox
|
|
48
|
+
name="updates"
|
|
49
|
+
defaultChecked
|
|
50
|
+
checked={checked}
|
|
51
|
+
onCheckedChange={(next, details) => {
|
|
52
|
+
setChecked(next)
|
|
53
|
+
setReason(details.reason)
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
Email me updates
|
|
57
|
+
</label>
|
|
58
|
+
<button type="reset">Reset</button>
|
|
59
|
+
<output>{reason}</output>
|
|
60
|
+
</form>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Example }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Uncheck the box, then press Reset. `defaultChecked` supplies the reset value, and the callback receives `true` with reason `"reset"`. The callback runs when reset requests a different value; it does not run if the checkbox is already at its default.
|
|
68
|
+
|
|
69
|
+
## Label and Validate a Field
|
|
70
|
+
|
|
71
|
+
`Field` connects a control to its label, description, and error message:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import {
|
|
75
|
+
Field,
|
|
76
|
+
FieldDescription,
|
|
77
|
+
FieldError,
|
|
78
|
+
FieldLabel,
|
|
79
|
+
} from "@ui-sdk/react/field"
|
|
80
|
+
import { Input } from "@ui-sdk/react/input"
|
|
81
|
+
|
|
82
|
+
function Example() {
|
|
83
|
+
return (
|
|
84
|
+
<Field name="email">
|
|
85
|
+
<FieldLabel>Email</FieldLabel>
|
|
86
|
+
<Input type="email" required />
|
|
87
|
+
<FieldDescription>We'll only use it for receipts.</FieldDescription>
|
|
88
|
+
<FieldError />
|
|
89
|
+
</Field>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { Example }
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You can use a Textarea, Checkbox, Switch, Radio Group, or Select in place of the Input. [Field](./components/field.md) shows how they connect.
|
|
97
|
+
|
|
98
|
+
These props follow native form conventions on controls that support them:
|
|
99
|
+
|
|
100
|
+
| Prop | What It Does |
|
|
101
|
+
| ---------- | --------------------------------------------------------------------------- |
|
|
102
|
+
| `name` | Sets the submitted field name. Without one, no value is submitted. |
|
|
103
|
+
| `required` | Requires a value before the form can submit. |
|
|
104
|
+
| `disabled` | Prevents interaction and leaves the control out of the submission. |
|
|
105
|
+
| `readOnly` | Prevents edits while keeping the control focusable and its value submitted. |
|
|
106
|
+
| `form` | Connects a control to a form by ID when it's outside that form. |
|
|
107
|
+
|
|
108
|
+
## Handle a Submission
|
|
109
|
+
|
|
110
|
+
On a normal submit, the browser checks constraints such as `required` and `type="email"` before running your handler.
|
|
111
|
+
|
|
112
|
+
Your server may still reject a valid-looking value. Pass `invalid` to the field and show the server’s message. When the user edits the field, clear the message and return `invalid` to `undefined` so the field uses native validation again. This example expects a `/api/preferences` endpoint:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
"use client"
|
|
116
|
+
|
|
117
|
+
import { Field, FieldError, FieldLabel } from "@ui-sdk/react/field"
|
|
118
|
+
import { Input } from "@ui-sdk/react/input"
|
|
119
|
+
import * as React from "react"
|
|
120
|
+
|
|
121
|
+
function Example() {
|
|
122
|
+
const [error, setError] = React.useState("")
|
|
123
|
+
|
|
124
|
+
async function save(event: React.FormEvent<HTMLFormElement>) {
|
|
125
|
+
event.preventDefault()
|
|
126
|
+
const response = await fetch("/api/preferences", {
|
|
127
|
+
method: "POST",
|
|
128
|
+
body: new FormData(event.currentTarget),
|
|
129
|
+
})
|
|
130
|
+
setError(response.ok ? "" : "That email is already taken.")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<form onSubmit={save}>
|
|
135
|
+
<Field name="email" invalid={error ? true : undefined}>
|
|
136
|
+
<FieldLabel>Email</FieldLabel>
|
|
137
|
+
<Input type="email" required onValueChange={() => setError("")} />
|
|
138
|
+
<FieldError>{(state) => error || state.message}</FieldError>
|
|
139
|
+
</Field>
|
|
140
|
+
<button type="submit">Save</button>
|
|
141
|
+
</form>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { Example }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Without a handler, the form submits through its `action`, including a React server action.
|