@carbonbits/sixr 1.1.0 → 1.3.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 +148 -86
- package/dist/form.cjs +124 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.d.cts +17 -0
- package/dist/form.d.ts +17 -0
- package/dist/form.js +103 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.cts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/styles.css +1 -1
- package/dist/text-input.cjs +216 -0
- package/dist/text-input.cjs.map +1 -0
- package/dist/text-input.d.cts +65 -0
- package/dist/text-input.d.ts +65 -0
- package/dist/text-input.js +196 -0
- package/dist/text-input.js.map +1 -0
- package/dist/validation.cjs +43 -0
- package/dist/validation.cjs.map +1 -0
- package/dist/validation.d.cts +28 -0
- package/dist/validation.d.ts +28 -0
- package/dist/validation.js +35 -0
- package/dist/validation.js.map +1 -0
- package/package.json +42 -5
package/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
E-commerce geared React component library for [carbonbits](https://carbonbits.co.ke/sixr).
|
|
4
4
|
|
|
5
|
-
Ships ESM + CJS builds with type declarations, and a prebuilt stylesheet.
|
|
6
|
-
are React Server Component safe
|
|
7
|
-
|
|
5
|
+
Ships ESM + CJS builds with type declarations, and a prebuilt stylesheet. Static
|
|
6
|
+
components are React Server Component safe and need no `"use client"` boundary in the
|
|
7
|
+
Next.js App Router; interactive ones carry their own directive, so either kind imports
|
|
8
|
+
cleanly into a server component.
|
|
8
9
|
|
|
9
10
|
## Install
|
|
10
11
|
|
|
@@ -12,10 +13,13 @@ are React Server Component safe, so they work in the Next.js App Router without
|
|
|
12
13
|
pnpm add @carbonbits/sixr
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
`react` and `react-dom` are peer dependencies (`^19.0.0`).
|
|
16
|
+
`react` and `react-dom` are peer dependencies (`^19.0.0`). `zod` (`^4`) is an optional
|
|
17
|
+
peer dependency, needed only for `@carbonbits/sixr/validation`.
|
|
16
18
|
|
|
17
19
|
## Usage
|
|
18
20
|
|
|
21
|
+
Load the stylesheet once, at the root of your app:
|
|
22
|
+
|
|
19
23
|
```tsx
|
|
20
24
|
// app/layout.tsx
|
|
21
25
|
import "@carbonbits/sixr/styles.css";
|
|
@@ -40,113 +44,171 @@ export default function Page() {
|
|
|
40
44
|
The root barrel (`@carbonbits/sixr`) re-exports everything and works too, but the
|
|
41
45
|
subpath is the one to reach for.
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
library itself uses, so it is safe to load alongside a consumer's own Tailwind build.
|
|
47
|
+
## Components
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
### `Cart`
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
```tsx
|
|
52
|
+
import { Cart } from "@carbonbits/sixr/cart";
|
|
53
|
+
```
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
| Prop | Type | Default | Description |
|
|
56
|
+
| ---------- | --------------- | --------- | --------------------------------------- |
|
|
57
|
+
| `products` | `CartProduct[]` | `[]` | Lines to render. Empty shows a placeholder. |
|
|
58
|
+
| `currency` | `string` | `"KES"` | ISO 4217 code used to format prices. |
|
|
59
|
+
| `locale` | `string` | `"en-KE"` | BCP 47 locale used to format prices. |
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
type CartProduct = {
|
|
63
|
+
/** Stable identity for the line, used as the React key. Falls back to `name`. */
|
|
64
|
+
id?: string;
|
|
65
|
+
name: string;
|
|
66
|
+
price: number;
|
|
67
|
+
};
|
|
60
68
|
```
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
and `performance` use the full `all` preset, and `--error-on-warnings` means a
|
|
65
|
-
warning fails CI too. The handful of disabled rules each carry a reason in
|
|
66
|
-
`biome.json`, and stories, tests and config files get narrow overrides where a tool
|
|
67
|
-
mandates something the rules forbid (Storybook's `export default meta`, for one).
|
|
70
|
+
Pass an `id` whenever a cart can hold two lines with the same name. Prices are
|
|
71
|
+
formatted with `Intl.NumberFormat`, and the total is summed from `price`.
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
pushing. Most editors have a Biome extension that formats on save.
|
|
73
|
+
### `TextInput`
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
```tsx
|
|
76
|
+
import { TextInput } from "@carbonbits/sixr/text-input";
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A labelled text field with optional schema validation. Interactive, so it ships its
|
|
80
|
+
own `"use client"` directive and is reachable only at this subpath — it is not on the
|
|
81
|
+
root barrel, which stays server-safe.
|
|
82
|
+
|
|
83
|
+
| Prop | Type | Default | Description |
|
|
84
|
+
| -------------------- | ------------------------------------ | -------- | ----------------------------------------------------- |
|
|
85
|
+
| `label` | `string` | — | Field label, associated with the input. |
|
|
86
|
+
| `hint` | `ReactNode` | — | Guidance below the field. Replaced by an error. |
|
|
87
|
+
| `error` | `string` | — | Caller-supplied error. Outranks the schema. |
|
|
88
|
+
| `schema` | `TextInputSchema` | — | Any zod schema. Only `safeParse` is called. |
|
|
89
|
+
| `validateOn` | `"blur" \| "change" \| "never"` | `"blur"` | When the schema runs. |
|
|
90
|
+
| `onValidationChange` | `(state: TextInputValidation) => void` | — | Fires on each validation pass. |
|
|
91
|
+
| `onChange` | `(value: string) => void` | — | Receives the value, not the event. |
|
|
92
|
+
| `value` | `string` | — | Makes the field controlled. |
|
|
93
|
+
| `defaultValue` | `string` | `""` | Initial value when uncontrolled. |
|
|
94
|
+
| `optional` | `boolean` | `false` | Shows an "Optional" marker on the label. |
|
|
95
|
+
| `action` | `ReactNode` | — | Trailing label slot, e.g. a "Forgot?" link. |
|
|
96
|
+
| `trailing` | `ReactNode` | — | Trailing in-field slot, e.g. a password peek. |
|
|
97
|
+
| `mono` | `boolean` | `false` | Renders the value in the monospace face. |
|
|
98
|
+
| `type` | `"text" \| "email" \| "password" \| "tel" \| "url" \| "search"` | `"text"` | Input type. |
|
|
99
|
+
|
|
100
|
+
Any other `<input>` attribute — `placeholder`, `maxLength`, `disabled`, `inputMode` —
|
|
101
|
+
is passed straight through.
|
|
73
102
|
|
|
74
|
-
|
|
103
|
+
```tsx
|
|
104
|
+
import { TextInput } from "@carbonbits/sixr/text-input";
|
|
105
|
+
import { emailSchema } from "@carbonbits/sixr/validation";
|
|
106
|
+
|
|
107
|
+
<TextInput
|
|
108
|
+
label="Work email"
|
|
109
|
+
type="email"
|
|
110
|
+
placeholder="you@company.com"
|
|
111
|
+
schema={emailSchema}
|
|
112
|
+
onChange={setEmail}
|
|
113
|
+
/>;
|
|
114
|
+
```
|
|
75
115
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
116
|
+
Validation runs on blur by default, then on every keystroke once the field has gone
|
|
117
|
+
invalid, so a corrected value clears the message immediately. The first schema issue
|
|
118
|
+
is shown, the input gets `aria-invalid`, and the message is announced via `role="alert"`
|
|
119
|
+
and wired up with `aria-describedby`.
|
|
79
120
|
|
|
80
|
-
|
|
121
|
+
`schema` is structurally typed, so any zod schema satisfies it without zod appearing
|
|
122
|
+
in this package's types — you only need zod installed if you use it.
|
|
81
123
|
|
|
82
|
-
|
|
83
|
-
on their tsup entry — a directive in the source file does not survive bundling. Not
|
|
84
|
-
wired up yet; there are no client components in the library so far.
|
|
124
|
+
### `Form` / `FormError` / `SubmitButton`
|
|
85
125
|
|
|
86
|
-
|
|
126
|
+
```tsx
|
|
127
|
+
import { Form, FormError, SubmitButton } from "@carbonbits/sixr/form";
|
|
128
|
+
```
|
|
87
129
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
130
|
+
The shared skeleton behind the login/signup screens: a `<form>` that disables its
|
|
131
|
+
fields for the duration of `onSubmit`, plus a submit button that swaps to a spinner
|
|
132
|
+
for the same duration. Interactive, so it ships its own `"use client"` directive and
|
|
133
|
+
is reachable only at this subpath.
|
|
91
134
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
135
|
+
| Component | Prop | Type | Description |
|
|
136
|
+
| ------------- | -------------- | -------------------------- | --------------------------------------------------------- |
|
|
137
|
+
| `Form` | `onSubmit` | `() => void \| Promise<void>` | Called after `preventDefault`. A returned promise drives the disabled/spinner state until it settles. |
|
|
138
|
+
| `Form` | `children` | `ReactNode` | Fields, `FormError`, `SubmitButton` — whatever the design calls for, in order. |
|
|
139
|
+
| `FormError` | `children` | `ReactNode` | Form-level error (a rejected credential, a failed request). Renders nothing when empty. |
|
|
140
|
+
| `SubmitButton`| `loadingLabel` | `ReactNode` | Shown next to the spinner in place of `children` while submitting. |
|
|
98
141
|
|
|
99
|
-
|
|
100
|
-
|
|
142
|
+
```tsx
|
|
143
|
+
import { Form, FormError, SubmitButton } from "@carbonbits/sixr/form";
|
|
144
|
+
import { TextInput } from "@carbonbits/sixr/text-input";
|
|
145
|
+
|
|
146
|
+
function LoginForm() {
|
|
147
|
+
const [email, setEmail] = useState("");
|
|
148
|
+
const [password, setPassword] = useState("");
|
|
149
|
+
const [error, setError] = useState<string>();
|
|
150
|
+
|
|
151
|
+
const handleSubmit = async () => {
|
|
152
|
+
setError(undefined);
|
|
153
|
+
const ok = await signIn(email, password);
|
|
154
|
+
if (!ok) setError("That email and password don't match.");
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<Form onSubmit={handleSubmit}>
|
|
159
|
+
<TextInput label="Work email" type="email" onChange={setEmail} />
|
|
160
|
+
<TextInput label="Password" type="password" onChange={setPassword} />
|
|
161
|
+
<FormError>{error}</FormError>
|
|
162
|
+
<SubmitButton loadingLabel="Signing in…">Sign in</SubmitButton>
|
|
163
|
+
</Form>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
101
167
|
|
|
102
|
-
|
|
168
|
+
A field never needs its own `disabled={isLoading}` — `Form` groups its children in a
|
|
169
|
+
fieldset that disables for the duration of `onSubmit`, so `SubmitButton` (and any other
|
|
170
|
+
control inside `Form`) picks that up for free. Fields, OAuth rows, dividers — anything
|
|
171
|
+
the design puts between the top of the form and the submit button — are just children;
|
|
172
|
+
`Form` doesn't assume a shape beyond that.
|
|
103
173
|
|
|
104
|
-
|
|
105
|
-
package live. It runs `npm stage publish`, which uploads the tarball and parks it.
|
|
106
|
-
A maintainer then approves it with 2FA:
|
|
174
|
+
## Validation schemas
|
|
107
175
|
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
npm stage view <stage-id> # inspect before approving
|
|
111
|
-
npm stage approve <stage-id> # prompts for 2FA, goes live
|
|
112
|
-
npm stage reject <stage-id> # discard instead
|
|
176
|
+
```tsx
|
|
177
|
+
import { emailSchema, passwordSchema } from "@carbonbits/sixr/validation";
|
|
113
178
|
```
|
|
114
179
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
and
|
|
180
|
+
Ready-made zod schemas matching the Carbonbits auth rules: `emailSchema`,
|
|
181
|
+
`passwordSchema`, `verificationCodeSchema`, `backupCodeSchema`, `fullNameSchema`,
|
|
182
|
+
`jobTitleSchema` and `phoneSchema`. They are ordinary zod schemas — extend them with
|
|
183
|
+
`.refine`, or replace the message with `.min(8, "…")`.
|
|
118
184
|
|
|
119
|
-
|
|
185
|
+
This subpath needs `zod` (`^4`), which is an optional peer dependency. Nothing else in
|
|
186
|
+
the package requires it.
|
|
120
187
|
|
|
121
|
-
|
|
122
|
-
| -------------------------- | ---------------------------------------------------- |
|
|
123
|
-
| `NPM_STAGING_TOKEN` | `npm stage publish` — a *Read and write (stage only)* granular token |
|
|
124
|
-
| `GH_APP_ID` | the release GitHub App |
|
|
125
|
-
| `GH_APP_PRIVATE_KEY` | that App's private key, **base64-encoded** |
|
|
126
|
-
| `CHROMATIC_PROJECT_TOKEN` | Chromatic publish |
|
|
188
|
+
## Styling
|
|
127
189
|
|
|
128
|
-
|
|
129
|
-
|
|
190
|
+
`dist/styles.css` is compiled by Tailwind 4 and contains only the utilities the
|
|
191
|
+
library itself uses, so it is safe to load alongside a consumer's own Tailwind build.
|
|
192
|
+
|
|
193
|
+
Colours and control metrics come from CSS custom properties, all namespaced `--sixr-*`,
|
|
194
|
+
so nothing here can clobber your own Tailwind theme. Override any of them to retheme
|
|
195
|
+
the library without rebuilding it:
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
:root {
|
|
199
|
+
--sixr-accent: #0f766e;
|
|
200
|
+
--sixr-line: #d4d4d8;
|
|
201
|
+
}
|
|
202
|
+
```
|
|
130
203
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
installation token from a GitHub App instead, and **that App must be listed in the
|
|
134
|
-
ruleset's bypass list** or the push still fails. The App needs `contents: write` and
|
|
135
|
-
`pull_requests: write` on this repo, nothing more.
|
|
204
|
+
Dark values follow `prefers-color-scheme` by default. To drive the theme yourself, set
|
|
205
|
+
`data-theme="dark"` or `data-theme="light"` on the root element.
|
|
136
206
|
|
|
137
|
-
|
|
138
|
-
is not interchangeable with `GH_TOKEN`. semantic-release builds its push URL as
|
|
139
|
-
`https://<GH_TOKEN>@github.com/...`, but as
|
|
140
|
-
`https://x-access-token:<GITHUB_TOKEN>@github.com/...` when it detects Actions.
|
|
141
|
-
An App installation token is only accepted in the second form — via `GH_TOKEN` it
|
|
142
|
-
fails on `git ls-remote` with `Invalid username or token`.
|
|
207
|
+
## Versioning
|
|
143
208
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
shadows any working token and fails with `EINVALIDGHTOKEN`. If that secret still
|
|
147
|
-
exists in repo settings, delete it.
|
|
209
|
+
Released under [semantic versioning](https://semver.org/): breaking changes only land
|
|
210
|
+
in major versions, so `^` ranges are safe to pin against.
|
|
148
211
|
|
|
149
|
-
##
|
|
212
|
+
## License
|
|
150
213
|
|
|
151
|
-
|
|
152
|
-
[Chromatic](https://www.chromatic.com/) for visual regression review.
|
|
214
|
+
MIT
|
package/dist/form.cjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
"use client";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/components/form/index.tsx
|
|
23
|
+
var form_exports = {};
|
|
24
|
+
__export(form_exports, {
|
|
25
|
+
Form: () => Form,
|
|
26
|
+
FormError: () => FormError,
|
|
27
|
+
SubmitButton: () => SubmitButton
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(form_exports);
|
|
30
|
+
var import_react = require("react");
|
|
31
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
var SubmittingContext = (0, import_react.createContext)(false);
|
|
33
|
+
var FORM_CLASSES = "flex flex-col gap-4";
|
|
34
|
+
var FIELDSET_CLASSES = "contents border-0 p-0 m-0 min-w-0";
|
|
35
|
+
function Form({ onSubmit, children, className, ...rest }) {
|
|
36
|
+
const [submitting, setSubmitting] = (0, import_react.useState)(false);
|
|
37
|
+
const handleSubmit = (0, import_react.useCallback)(
|
|
38
|
+
(event) => {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
const result = onSubmit();
|
|
41
|
+
if (result && typeof result.then === "function") {
|
|
42
|
+
setSubmitting(true);
|
|
43
|
+
result.finally(() => setSubmitting(false)).catch(() => void 0);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[onSubmit]
|
|
47
|
+
);
|
|
48
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
49
|
+
"form",
|
|
50
|
+
{
|
|
51
|
+
noValidate: true,
|
|
52
|
+
...rest,
|
|
53
|
+
onSubmit: handleSubmit,
|
|
54
|
+
className: [FORM_CLASSES, className ?? ""].filter(Boolean).join(" "),
|
|
55
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SubmittingContext.Provider, { value: submitting, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("fieldset", { disabled: submitting, className: FIELDSET_CLASSES, children }) })
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function FormError({ children }) {
|
|
60
|
+
if (!children) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: "text-hint text-danger", children });
|
|
64
|
+
}
|
|
65
|
+
function Spinner() {
|
|
66
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
67
|
+
"svg",
|
|
68
|
+
{
|
|
69
|
+
className: "h-4 w-4 animate-spin",
|
|
70
|
+
viewBox: "0 0 24 24",
|
|
71
|
+
fill: "none",
|
|
72
|
+
"aria-hidden": "true",
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
75
|
+
"circle",
|
|
76
|
+
{
|
|
77
|
+
className: "opacity-25",
|
|
78
|
+
cx: "12",
|
|
79
|
+
cy: "12",
|
|
80
|
+
r: "10",
|
|
81
|
+
stroke: "currentColor",
|
|
82
|
+
strokeWidth: "4"
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
86
|
+
"path",
|
|
87
|
+
{
|
|
88
|
+
className: "opacity-75",
|
|
89
|
+
fill: "currentColor",
|
|
90
|
+
d: "M4 12a8 8 0 0 1 8-8V0C5.373 0 0 5.373 0 12H4Z"
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
var BUTTON_CLASSES = "flex h-10 w-full items-center justify-center gap-2 rounded-field bg-accent font-[inherit] text-field font-semibold text-on-accent outline-none focus:ring-3 focus:ring-accent-soft disabled:cursor-not-allowed disabled:opacity-45";
|
|
98
|
+
function SubmitButton({
|
|
99
|
+
children,
|
|
100
|
+
loadingLabel,
|
|
101
|
+
className,
|
|
102
|
+
...rest
|
|
103
|
+
}) {
|
|
104
|
+
const submitting = (0, import_react.useContext)(SubmittingContext);
|
|
105
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
106
|
+
"button",
|
|
107
|
+
{
|
|
108
|
+
...rest,
|
|
109
|
+
type: "submit",
|
|
110
|
+
className: [BUTTON_CLASSES, className ?? ""].filter(Boolean).join(" "),
|
|
111
|
+
children: submitting ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
113
|
+
loadingLabel
|
|
114
|
+
] }) : children
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
119
|
+
0 && (module.exports = {
|
|
120
|
+
Form,
|
|
121
|
+
FormError,
|
|
122
|
+
SubmitButton
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=form.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/form/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ButtonHTMLAttributes,\n createContext,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n useCallback,\n useContext,\n useState,\n} from \"react\";\n\nconst SubmittingContext = createContext(false);\n\nexport type FormProps = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"onSubmit\"\n> & {\n onSubmit: () => void | Promise<void>;\n children: ReactNode;\n};\n\nconst FORM_CLASSES = \"flex flex-col gap-4\";\nconst FIELDSET_CLASSES = \"contents border-0 p-0 m-0 min-w-0\";\n\nexport function Form({ onSubmit, children, className, ...rest }: FormProps) {\n const [submitting, setSubmitting] = useState(false);\n\n const handleSubmit = useCallback(\n (event: SubmitEvent<HTMLFormElement>) => {\n event.preventDefault();\n const result = onSubmit();\n if (result && typeof result.then === \"function\") {\n setSubmitting(true);\n result.finally(() => setSubmitting(false)).catch(() => undefined);\n }\n },\n [onSubmit],\n );\n\n return (\n <form\n noValidate\n {...rest}\n onSubmit={handleSubmit}\n className={[FORM_CLASSES, className ?? \"\"].filter(Boolean).join(\" \")}\n >\n <SubmittingContext.Provider value={submitting}>\n <fieldset disabled={submitting} className={FIELDSET_CLASSES}>\n {children}\n </fieldset>\n </SubmittingContext.Provider>\n </form>\n );\n}\n\nexport function FormError({ children }: { children?: ReactNode }) {\n if (!children) {\n return null;\n }\n\n return (\n <p role=\"alert\" className=\"text-hint text-danger\">\n {children}\n </p>\n );\n}\n\nfunction Spinner() {\n return (\n <svg\n className=\"h-4 w-4 animate-spin\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 0 1 8-8V0C5.373 0 0 5.373 0 12H4Z\"\n />\n </svg>\n );\n}\n\nexport type SubmitButtonProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"type\"\n> & {\n loadingLabel?: ReactNode;\n};\n\nconst BUTTON_CLASSES =\n \"flex h-10 w-full items-center justify-center gap-2 rounded-field bg-accent font-[inherit] text-field font-semibold text-on-accent outline-none focus:ring-3 focus:ring-accent-soft disabled:cursor-not-allowed disabled:opacity-45\";\n\nexport function SubmitButton({\n children,\n loadingLabel,\n className,\n ...rest\n}: SubmitButtonProps) {\n const submitting = useContext(SubmittingContext);\n\n return (\n <button\n {...rest}\n type=\"submit\"\n className={[BUTTON_CLASSES, className ?? \"\"].filter(Boolean).join(\" \")}\n >\n {submitting ? (\n <>\n <Spinner />\n {loadingLabel}\n </>\n ) : (\n children\n )}\n </button>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBASO;AAsCC;AApCR,IAAM,wBAAoB,4BAAc,KAAK;AAU7C,IAAM,eAAe;AACrB,IAAM,mBAAmB;AAElB,SAAS,KAAK,EAAE,UAAU,UAAU,WAAW,GAAG,KAAK,GAAc;AAC1E,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAS,KAAK;AAElD,QAAM,mBAAe;AAAA,IACnB,CAAC,UAAwC;AACvC,YAAM,eAAe;AACrB,YAAM,SAAS,SAAS;AACxB,UAAI,UAAU,OAAO,OAAO,SAAS,YAAY;AAC/C,sBAAc,IAAI;AAClB,eAAO,QAAQ,MAAM,cAAc,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,MAClE;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,YAAU;AAAA,MACT,GAAG;AAAA,MACJ,UAAU;AAAA,MACV,WAAW,CAAC,cAAc,aAAa,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MAEnE,sDAAC,kBAAkB,UAAlB,EAA2B,OAAO,YACjC,sDAAC,cAAS,UAAU,YAAY,WAAW,kBACxC,UACH,GACF;AAAA;AAAA,EACF;AAEJ;AAEO,SAAS,UAAU,EAAE,SAAS,GAA6B;AAChE,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,SACE,4CAAC,OAAE,MAAK,SAAQ,WAAU,yBACvB,UACH;AAEJ;AAEA,SAAS,UAAU;AACjB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,eAAY;AAAA,MAEZ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,IAAG;AAAA,YACH,IAAG;AAAA,YACH,GAAE;AAAA,YACF,QAAO;AAAA,YACP,aAAY;AAAA;AAAA,QACd;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,MAAK;AAAA,YACL,GAAE;AAAA;AAAA,QACJ;AAAA;AAAA;AAAA,EACF;AAEJ;AASA,IAAM,iBACJ;AAEK,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,iBAAa,yBAAW,iBAAiB;AAE/C,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,WAAW,CAAC,gBAAgB,aAAa,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MAEpE,uBACC,4EACE;AAAA,oDAAC,WAAQ;AAAA,QACR;AAAA,SACH,IAEA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
package/dist/form.d.cts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { FormHTMLAttributes, ReactNode, ButtonHTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
type FormProps = Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> & {
|
|
5
|
+
onSubmit: () => void | Promise<void>;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
declare function Form({ onSubmit, children, className, ...rest }: FormProps): react.JSX.Element;
|
|
9
|
+
declare function FormError({ children }: {
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}): react.JSX.Element | null;
|
|
12
|
+
type SubmitButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> & {
|
|
13
|
+
loadingLabel?: ReactNode;
|
|
14
|
+
};
|
|
15
|
+
declare function SubmitButton({ children, loadingLabel, className, ...rest }: SubmitButtonProps): react.JSX.Element;
|
|
16
|
+
|
|
17
|
+
export { Form, FormError, type FormProps, SubmitButton, type SubmitButtonProps };
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { FormHTMLAttributes, ReactNode, ButtonHTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
type FormProps = Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> & {
|
|
5
|
+
onSubmit: () => void | Promise<void>;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
declare function Form({ onSubmit, children, className, ...rest }: FormProps): react.JSX.Element;
|
|
9
|
+
declare function FormError({ children }: {
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}): react.JSX.Element | null;
|
|
12
|
+
type SubmitButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> & {
|
|
13
|
+
loadingLabel?: ReactNode;
|
|
14
|
+
};
|
|
15
|
+
declare function SubmitButton({ children, loadingLabel, className, ...rest }: SubmitButtonProps): react.JSX.Element;
|
|
16
|
+
|
|
17
|
+
export { Form, FormError, type FormProps, SubmitButton, type SubmitButtonProps };
|
package/dist/form.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
// src/components/form/index.tsx
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
useCallback,
|
|
8
|
+
useContext,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
11
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var SubmittingContext = createContext(false);
|
|
13
|
+
var FORM_CLASSES = "flex flex-col gap-4";
|
|
14
|
+
var FIELDSET_CLASSES = "contents border-0 p-0 m-0 min-w-0";
|
|
15
|
+
function Form({ onSubmit, children, className, ...rest }) {
|
|
16
|
+
const [submitting, setSubmitting] = useState(false);
|
|
17
|
+
const handleSubmit = useCallback(
|
|
18
|
+
(event) => {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
const result = onSubmit();
|
|
21
|
+
if (result && typeof result.then === "function") {
|
|
22
|
+
setSubmitting(true);
|
|
23
|
+
result.finally(() => setSubmitting(false)).catch(() => void 0);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
[onSubmit]
|
|
27
|
+
);
|
|
28
|
+
return /* @__PURE__ */ jsx(
|
|
29
|
+
"form",
|
|
30
|
+
{
|
|
31
|
+
noValidate: true,
|
|
32
|
+
...rest,
|
|
33
|
+
onSubmit: handleSubmit,
|
|
34
|
+
className: [FORM_CLASSES, className ?? ""].filter(Boolean).join(" "),
|
|
35
|
+
children: /* @__PURE__ */ jsx(SubmittingContext.Provider, { value: submitting, children: /* @__PURE__ */ jsx("fieldset", { disabled: submitting, className: FIELDSET_CLASSES, children }) })
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function FormError({ children }) {
|
|
40
|
+
if (!children) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return /* @__PURE__ */ jsx("p", { role: "alert", className: "text-hint text-danger", children });
|
|
44
|
+
}
|
|
45
|
+
function Spinner() {
|
|
46
|
+
return /* @__PURE__ */ jsxs(
|
|
47
|
+
"svg",
|
|
48
|
+
{
|
|
49
|
+
className: "h-4 w-4 animate-spin",
|
|
50
|
+
viewBox: "0 0 24 24",
|
|
51
|
+
fill: "none",
|
|
52
|
+
"aria-hidden": "true",
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"circle",
|
|
56
|
+
{
|
|
57
|
+
className: "opacity-25",
|
|
58
|
+
cx: "12",
|
|
59
|
+
cy: "12",
|
|
60
|
+
r: "10",
|
|
61
|
+
stroke: "currentColor",
|
|
62
|
+
strokeWidth: "4"
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
/* @__PURE__ */ jsx(
|
|
66
|
+
"path",
|
|
67
|
+
{
|
|
68
|
+
className: "opacity-75",
|
|
69
|
+
fill: "currentColor",
|
|
70
|
+
d: "M4 12a8 8 0 0 1 8-8V0C5.373 0 0 5.373 0 12H4Z"
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
var BUTTON_CLASSES = "flex h-10 w-full items-center justify-center gap-2 rounded-field bg-accent font-[inherit] text-field font-semibold text-on-accent outline-none focus:ring-3 focus:ring-accent-soft disabled:cursor-not-allowed disabled:opacity-45";
|
|
78
|
+
function SubmitButton({
|
|
79
|
+
children,
|
|
80
|
+
loadingLabel,
|
|
81
|
+
className,
|
|
82
|
+
...rest
|
|
83
|
+
}) {
|
|
84
|
+
const submitting = useContext(SubmittingContext);
|
|
85
|
+
return /* @__PURE__ */ jsx(
|
|
86
|
+
"button",
|
|
87
|
+
{
|
|
88
|
+
...rest,
|
|
89
|
+
type: "submit",
|
|
90
|
+
className: [BUTTON_CLASSES, className ?? ""].filter(Boolean).join(" "),
|
|
91
|
+
children: submitting ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
92
|
+
/* @__PURE__ */ jsx(Spinner, {}),
|
|
93
|
+
loadingLabel
|
|
94
|
+
] }) : children
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
Form,
|
|
100
|
+
FormError,
|
|
101
|
+
SubmitButton
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=form.js.map
|
package/dist/form.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/form/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ButtonHTMLAttributes,\n createContext,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n useCallback,\n useContext,\n useState,\n} from \"react\";\n\nconst SubmittingContext = createContext(false);\n\nexport type FormProps = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"onSubmit\"\n> & {\n onSubmit: () => void | Promise<void>;\n children: ReactNode;\n};\n\nconst FORM_CLASSES = \"flex flex-col gap-4\";\nconst FIELDSET_CLASSES = \"contents border-0 p-0 m-0 min-w-0\";\n\nexport function Form({ onSubmit, children, className, ...rest }: FormProps) {\n const [submitting, setSubmitting] = useState(false);\n\n const handleSubmit = useCallback(\n (event: SubmitEvent<HTMLFormElement>) => {\n event.preventDefault();\n const result = onSubmit();\n if (result && typeof result.then === \"function\") {\n setSubmitting(true);\n result.finally(() => setSubmitting(false)).catch(() => undefined);\n }\n },\n [onSubmit],\n );\n\n return (\n <form\n noValidate\n {...rest}\n onSubmit={handleSubmit}\n className={[FORM_CLASSES, className ?? \"\"].filter(Boolean).join(\" \")}\n >\n <SubmittingContext.Provider value={submitting}>\n <fieldset disabled={submitting} className={FIELDSET_CLASSES}>\n {children}\n </fieldset>\n </SubmittingContext.Provider>\n </form>\n );\n}\n\nexport function FormError({ children }: { children?: ReactNode }) {\n if (!children) {\n return null;\n }\n\n return (\n <p role=\"alert\" className=\"text-hint text-danger\">\n {children}\n </p>\n );\n}\n\nfunction Spinner() {\n return (\n <svg\n className=\"h-4 w-4 animate-spin\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 0 1 8-8V0C5.373 0 0 5.373 0 12H4Z\"\n />\n </svg>\n );\n}\n\nexport type SubmitButtonProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n \"type\"\n> & {\n loadingLabel?: ReactNode;\n};\n\nconst BUTTON_CLASSES =\n \"flex h-10 w-full items-center justify-center gap-2 rounded-field bg-accent font-[inherit] text-field font-semibold text-on-accent outline-none focus:ring-3 focus:ring-accent-soft disabled:cursor-not-allowed disabled:opacity-45\";\n\nexport function SubmitButton({\n children,\n loadingLabel,\n className,\n ...rest\n}: SubmitButtonProps) {\n const submitting = useContext(SubmittingContext);\n\n return (\n <button\n {...rest}\n type=\"submit\"\n className={[BUTTON_CLASSES, className ?? \"\"].filter(Boolean).join(\" \")}\n >\n {submitting ? (\n <>\n <Spinner />\n {loadingLabel}\n </>\n ) : (\n children\n )}\n </button>\n );\n}\n"],"mappings":";;;;AAEA;AAAA,EAEE;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAsCC,SAsEA,UAtEA,KAsBJ,YAtBI;AApCR,IAAM,oBAAoB,cAAc,KAAK;AAU7C,IAAM,eAAe;AACrB,IAAM,mBAAmB;AAElB,SAAS,KAAK,EAAE,UAAU,UAAU,WAAW,GAAG,KAAK,GAAc;AAC1E,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAElD,QAAM,eAAe;AAAA,IACnB,CAAC,UAAwC;AACvC,YAAM,eAAe;AACrB,YAAM,SAAS,SAAS;AACxB,UAAI,UAAU,OAAO,OAAO,SAAS,YAAY;AAC/C,sBAAc,IAAI;AAClB,eAAO,QAAQ,MAAM,cAAc,KAAK,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,MAClE;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,YAAU;AAAA,MACT,GAAG;AAAA,MACJ,UAAU;AAAA,MACV,WAAW,CAAC,cAAc,aAAa,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MAEnE,8BAAC,kBAAkB,UAAlB,EAA2B,OAAO,YACjC,8BAAC,cAAS,UAAU,YAAY,WAAW,kBACxC,UACH,GACF;AAAA;AAAA,EACF;AAEJ;AAEO,SAAS,UAAU,EAAE,SAAS,GAA6B;AAChE,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,SACE,oBAAC,OAAE,MAAK,SAAQ,WAAU,yBACvB,UACH;AAEJ;AAEA,SAAS,UAAU;AACjB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,eAAY;AAAA,MAEZ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,IAAG;AAAA,YACH,IAAG;AAAA,YACH,GAAE;AAAA,YACF,QAAO;AAAA,YACP,aAAY;AAAA;AAAA,QACd;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,MAAK;AAAA,YACL,GAAE;AAAA;AAAA,QACJ;AAAA;AAAA;AAAA,EACF;AAEJ;AASA,IAAM,iBACJ;AAEK,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,aAAa,WAAW,iBAAiB;AAE/C,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAK;AAAA,MACL,WAAW,CAAC,gBAAgB,aAAa,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MAEpE,uBACC,iCACE;AAAA,4BAAC,WAAQ;AAAA,QACR;AAAA,SACH,IAEA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,2 +1,64 @@
|
|
|
1
1
|
export { Cart, CartProduct, CartProps } from './cart.cjs';
|
|
2
|
-
import 'react';
|
|
2
|
+
import { InputHTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Structural shape of a zod schema — every zod schema satisfies it, and any
|
|
6
|
+
* version of zod does. Declaring it this way rather than importing `ZodType`
|
|
7
|
+
* keeps zod out of this module's public types, so consumers who only want an
|
|
8
|
+
* input do not have to install zod to typecheck it.
|
|
9
|
+
*/
|
|
10
|
+
type TextInputSchema = {
|
|
11
|
+
safeParse: (value: unknown) => {
|
|
12
|
+
success: true;
|
|
13
|
+
} | {
|
|
14
|
+
success: false;
|
|
15
|
+
error: {
|
|
16
|
+
issues: readonly {
|
|
17
|
+
message: string;
|
|
18
|
+
}[];
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/** Outcome of a single validation pass, handed to `onValidationChange`. */
|
|
23
|
+
type TextInputValidation = {
|
|
24
|
+
valid: boolean;
|
|
25
|
+
/** First issue reported by the schema, or `undefined` when valid. */
|
|
26
|
+
error?: string;
|
|
27
|
+
};
|
|
28
|
+
type TextInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "onChange" | "value" | "defaultValue" | "type"> & {
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Guidance under the field. Replaced by the error message while invalid. */
|
|
31
|
+
hint?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Error supplied by the caller — a server-side or cross-field failure. Takes
|
|
34
|
+
* precedence over the schema, so a form can hold a field invalid regardless
|
|
35
|
+
* of what `schema` makes of the value on its own.
|
|
36
|
+
*/
|
|
37
|
+
error?: string;
|
|
38
|
+
/** Renders the design's muted "Optional" marker at the end of the label. */
|
|
39
|
+
optional?: boolean;
|
|
40
|
+
/** Trailing label slot, for affordances like the design's "Forgot?" link. */
|
|
41
|
+
action?: ReactNode;
|
|
42
|
+
/** Trailing in-field slot, for affordances like the design's password peek. */
|
|
43
|
+
trailing?: ReactNode;
|
|
44
|
+
/** Renders the value in the design's monospace face. */
|
|
45
|
+
mono?: boolean;
|
|
46
|
+
type?: "text" | "email" | "password" | "tel" | "url" | "search";
|
|
47
|
+
value?: string;
|
|
48
|
+
defaultValue?: string;
|
|
49
|
+
onChange?: (value: string) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Any zod schema that accepts a string. Only `safeParse` is used, so a
|
|
52
|
+
* refined, transformed or preprocessed schema works as well as a plain one.
|
|
53
|
+
*/
|
|
54
|
+
schema?: TextInputSchema;
|
|
55
|
+
/**
|
|
56
|
+
* When the schema runs. `blur` — the default — avoids scolding someone
|
|
57
|
+
* mid-keystroke; once a field has gone invalid it revalidates on every
|
|
58
|
+
* keystroke so the message clears as soon as it is fixed.
|
|
59
|
+
*/
|
|
60
|
+
validateOn?: "blur" | "change" | "never";
|
|
61
|
+
onValidationChange?: (state: TextInputValidation) => void;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type { TextInputProps, TextInputSchema, TextInputValidation };
|