@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/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,64 @@
|
|
|
1
1
|
export { Cart, CartProduct, CartProps } from './cart.js';
|
|
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 };
|
package/dist/styles.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.contents{display:contents}.flex{display:flex}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.flex-col{flex-direction:column}.items-baseline{align-items:baseline}.justify-between{justify-content:space-between}.gap-4{gap:calc(var(--spacing) * 4)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.bg-white{background-color:var(--color-white)}.p-4{padding:calc(var(--spacing) * 4)}.py-2{padding-block:calc(var(--spacing) * 2)}.pt-2{padding-top:calc(var(--spacing) * 2)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--radius-lg:.5rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.top-1\/2{top:50%}.right-\[9px\]{right:9px}.m-0{margin:0}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.ml-auto{margin-left:auto}.contents{display:contents}.flex{display:flex}.inline{display:inline}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-10{height:calc(var(--spacing) * 10)}.w-4{width:calc(var(--spacing) * 4)}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.flex-col{flex-direction:column}.items-baseline{align-items:baseline}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}.rounded-field{border-radius:4px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-danger{border-color:var(--sixr-danger)}.border-gray-200{border-color:var(--color-gray-200)}.border-line{border-color:var(--sixr-line)}.bg-accent{background-color:var(--sixr-accent)}.bg-surface{background-color:var(--sixr-surface)}.bg-white{background-color:var(--color-white)}.p-0{padding:0}.p-4{padding:calc(var(--spacing) * 4)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-2{padding-block:calc(var(--spacing) * 2)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-16{padding-right:calc(var(--spacing) * 16)}.font-\[inherit\]{font-family:inherit}.font-code{font-family:var(--sixr-font-code)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-field{font-size:13px}.text-hint{font-size:11px}.text-label{font-size:11.5px}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-danger{color:var(--sixr-danger)}.text-fg{color:var(--sixr-fg)}.text-fg-muted{color:var(--sixr-fg-muted)}.text-fg-subtle{color:var(--sixr-fg-subtle)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-on-accent{color:var(--sixr-on-accent)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.placeholder\:text-fg-subtle::placeholder{color:var(--sixr-fg-subtle)}.focus\:border-accent:focus{border-color:var(--sixr-accent)}.focus\:border-danger:focus{border-color:var(--sixr-danger)}.focus\:ring-3:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-accent-soft:focus{--tw-ring-color:var(--sixr-accent-soft)}.focus\:ring-danger-soft:focus{--tw-ring-color:var(--sixr-danger-soft)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-45:disabled{opacity:.45}}:root{--sixr-bg:#f4f4f5;--sixr-surface:#fff;--sixr-surface-2:#f4f4f6;--sixr-surface-3:#eaeaec;--sixr-line:#e2e2e5;--sixr-line-strong:#cdcdd2;--sixr-fg:#0b0b0d;--sixr-fg-muted:#55555e;--sixr-fg-subtle:#8e8e99;--sixr-accent:#0b0b0d;--sixr-accent-2:#000;--sixr-accent-soft:#ededef;--sixr-on-accent:#fff;--sixr-success:#16a34a;--sixr-success-soft:#e9f5ec;--sixr-warning:#b45309;--sixr-info:#2563eb;--sixr-danger:#dc2626;--sixr-danger-soft:#fdecec;--sixr-font-code:"JetBrains Mono", ui-monospace, monospace}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--sixr-bg:#000;--sixr-surface:#0b0b0d;--sixr-surface-2:#121215;--sixr-surface-3:#1b1b1f;--sixr-line:#232328;--sixr-line-strong:#3a3a42;--sixr-fg:#fff;--sixr-fg-muted:#a3a3ad;--sixr-fg-subtle:#70707b;--sixr-accent:#fff;--sixr-accent-2:#e4e4e8;--sixr-accent-soft:#17171b;--sixr-on-accent:#000;--sixr-success:#34d399;--sixr-success-soft:#12301e;--sixr-warning:#fbbf24;--sixr-info:#60a5fa;--sixr-danger:#f87171;--sixr-danger-soft:#2a1416}}:root[data-theme=dark]{--sixr-bg:#000;--sixr-surface:#0b0b0d;--sixr-surface-2:#121215;--sixr-surface-3:#1b1b1f;--sixr-line:#232328;--sixr-line-strong:#3a3a42;--sixr-fg:#fff;--sixr-fg-muted:#a3a3ad;--sixr-fg-subtle:#70707b;--sixr-accent:#fff;--sixr-accent-2:#e4e4e8;--sixr-accent-soft:#17171b;--sixr-on-accent:#000;--sixr-success:#34d399;--sixr-success-soft:#12301e;--sixr-warning:#fbbf24;--sixr-info:#60a5fa;--sixr-danger:#f87171;--sixr-danger-soft:#2a1416}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
|
|
@@ -0,0 +1,216 @@
|
|
|
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/text-input/index.tsx
|
|
23
|
+
var text_input_exports = {};
|
|
24
|
+
__export(text_input_exports, {
|
|
25
|
+
TextInput: () => TextInput
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(text_input_exports);
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
var FIELD_CLASSES = "h-10 w-full rounded-field border bg-surface px-3 font-[inherit] text-field text-fg outline-none placeholder:text-fg-subtle disabled:cursor-not-allowed disabled:opacity-45";
|
|
31
|
+
var VALID_CLASSES = "border-line focus:border-accent focus:ring-3 focus:ring-accent-soft";
|
|
32
|
+
var INVALID_CLASSES = "border-danger focus:border-danger focus:ring-3 focus:ring-danger-soft";
|
|
33
|
+
var FALLBACK_MESSAGE = "Invalid value";
|
|
34
|
+
function runSchema(schema, value) {
|
|
35
|
+
if (!schema) {
|
|
36
|
+
return { valid: true };
|
|
37
|
+
}
|
|
38
|
+
const result = schema.safeParse(value);
|
|
39
|
+
if (result.success) {
|
|
40
|
+
return { valid: true };
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
valid: false,
|
|
44
|
+
error: result.error.issues[0]?.message ?? FALLBACK_MESSAGE
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function fieldClassName(invalid, mono, hasTrailing, className) {
|
|
48
|
+
return [
|
|
49
|
+
FIELD_CLASSES,
|
|
50
|
+
invalid ? INVALID_CLASSES : VALID_CLASSES,
|
|
51
|
+
mono ? "font-code" : "",
|
|
52
|
+
hasTrailing ? "pr-16" : "",
|
|
53
|
+
className ?? ""
|
|
54
|
+
].filter(Boolean).join(" ");
|
|
55
|
+
}
|
|
56
|
+
function FieldLabel({
|
|
57
|
+
htmlFor,
|
|
58
|
+
label,
|
|
59
|
+
optional,
|
|
60
|
+
action
|
|
61
|
+
}) {
|
|
62
|
+
if (!label) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
66
|
+
"label",
|
|
67
|
+
{
|
|
68
|
+
htmlFor,
|
|
69
|
+
className: "mb-1.5 flex items-center text-label font-semibold text-fg-muted",
|
|
70
|
+
children: [
|
|
71
|
+
label,
|
|
72
|
+
optional ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto text-hint font-semibold text-fg-subtle", children: "Optional" }) : null,
|
|
73
|
+
action ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto", children: action }) : null
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function FieldMessage({
|
|
79
|
+
id,
|
|
80
|
+
error,
|
|
81
|
+
hint
|
|
82
|
+
}) {
|
|
83
|
+
if (error) {
|
|
84
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id, role: "alert", className: "mt-1.5 text-hint text-danger", children: error });
|
|
85
|
+
}
|
|
86
|
+
if (hint) {
|
|
87
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id, className: "mt-1.5 text-hint text-fg-subtle", children: hint });
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function useFieldBehaviour({
|
|
92
|
+
value,
|
|
93
|
+
defaultValue,
|
|
94
|
+
error,
|
|
95
|
+
onChange,
|
|
96
|
+
onBlur,
|
|
97
|
+
schema,
|
|
98
|
+
validateOn = "blur",
|
|
99
|
+
onValidationChange
|
|
100
|
+
}) {
|
|
101
|
+
const [uncontrolledValue, setUncontrolledValue] = (0, import_react.useState)(
|
|
102
|
+
defaultValue ?? ""
|
|
103
|
+
);
|
|
104
|
+
const [schemaError, setSchemaError] = (0, import_react.useState)(void 0);
|
|
105
|
+
const isControlled = value !== void 0;
|
|
106
|
+
const validate = (0, import_react.useCallback)(
|
|
107
|
+
(next) => {
|
|
108
|
+
const state = runSchema(schema, next);
|
|
109
|
+
setSchemaError(state.error);
|
|
110
|
+
onValidationChange?.(state);
|
|
111
|
+
},
|
|
112
|
+
[schema, onValidationChange]
|
|
113
|
+
);
|
|
114
|
+
const handleChange = (0, import_react.useCallback)(
|
|
115
|
+
(event) => {
|
|
116
|
+
const next = event.target.value;
|
|
117
|
+
if (!isControlled) {
|
|
118
|
+
setUncontrolledValue(next);
|
|
119
|
+
}
|
|
120
|
+
onChange?.(next);
|
|
121
|
+
if (validateOn === "change" || validateOn === "blur" && schemaError) {
|
|
122
|
+
validate(next);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
[isControlled, onChange, validateOn, schemaError, validate]
|
|
126
|
+
);
|
|
127
|
+
const handleBlur = (0, import_react.useCallback)(
|
|
128
|
+
(event) => {
|
|
129
|
+
if (validateOn === "blur") {
|
|
130
|
+
validate(event.target.value);
|
|
131
|
+
}
|
|
132
|
+
onBlur?.(event);
|
|
133
|
+
},
|
|
134
|
+
[validateOn, validate, onBlur]
|
|
135
|
+
);
|
|
136
|
+
return {
|
|
137
|
+
currentValue: isControlled ? value : uncontrolledValue,
|
|
138
|
+
// A caller-supplied error outranks the schema's verdict.
|
|
139
|
+
shownError: error ?? schemaError,
|
|
140
|
+
handleChange,
|
|
141
|
+
handleBlur
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function TextInput({
|
|
145
|
+
label,
|
|
146
|
+
hint,
|
|
147
|
+
error,
|
|
148
|
+
optional = false,
|
|
149
|
+
action,
|
|
150
|
+
trailing,
|
|
151
|
+
mono = false,
|
|
152
|
+
type = "text",
|
|
153
|
+
value,
|
|
154
|
+
defaultValue,
|
|
155
|
+
onChange,
|
|
156
|
+
schema,
|
|
157
|
+
validateOn = "blur",
|
|
158
|
+
onValidationChange,
|
|
159
|
+
id,
|
|
160
|
+
className,
|
|
161
|
+
onBlur,
|
|
162
|
+
...rest
|
|
163
|
+
}) {
|
|
164
|
+
const generatedId = (0, import_react.useId)();
|
|
165
|
+
const inputId = id ?? generatedId;
|
|
166
|
+
const describedById = `${inputId}-description`;
|
|
167
|
+
const { currentValue, shownError, handleChange, handleBlur } = useFieldBehaviour({
|
|
168
|
+
value,
|
|
169
|
+
defaultValue,
|
|
170
|
+
error,
|
|
171
|
+
onChange,
|
|
172
|
+
onBlur,
|
|
173
|
+
schema,
|
|
174
|
+
validateOn,
|
|
175
|
+
onValidationChange
|
|
176
|
+
});
|
|
177
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col", children: [
|
|
178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
179
|
+
FieldLabel,
|
|
180
|
+
{
|
|
181
|
+
htmlFor: inputId,
|
|
182
|
+
label,
|
|
183
|
+
optional,
|
|
184
|
+
action
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: trailing ? "relative" : void 0, children: [
|
|
188
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
189
|
+
"input",
|
|
190
|
+
{
|
|
191
|
+
...rest,
|
|
192
|
+
id: inputId,
|
|
193
|
+
type,
|
|
194
|
+
value: currentValue,
|
|
195
|
+
"aria-invalid": shownError ? true : void 0,
|
|
196
|
+
"aria-describedby": shownError || hint ? describedById : void 0,
|
|
197
|
+
onChange: handleChange,
|
|
198
|
+
onBlur: handleBlur,
|
|
199
|
+
className: fieldClassName(
|
|
200
|
+
Boolean(shownError),
|
|
201
|
+
mono,
|
|
202
|
+
Boolean(trailing),
|
|
203
|
+
className
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
trailing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute top-1/2 right-[9px] -translate-y-1/2 text-hint font-semibold text-fg-subtle", children: trailing }) : null
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FieldMessage, { id: describedById, error: shownError, hint })
|
|
210
|
+
] });
|
|
211
|
+
}
|
|
212
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
213
|
+
0 && (module.exports = {
|
|
214
|
+
TextInput
|
|
215
|
+
});
|
|
216
|
+
//# sourceMappingURL=text-input.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/text-input/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type ChangeEvent,\n type FocusEvent,\n type InputHTMLAttributes,\n type ReactNode,\n useCallback,\n useId,\n useState,\n} from \"react\";\n\n/**\n * Structural shape of a zod schema — every zod schema satisfies it, and any\n * version of zod does. Declaring it this way rather than importing `ZodType`\n * keeps zod out of this module's public types, so consumers who only want an\n * input do not have to install zod to typecheck it.\n */\nexport type TextInputSchema = {\n safeParse: (\n value: unknown,\n ) =>\n | { success: true }\n | { success: false; error: { issues: readonly { message: string }[] } };\n};\n\n/** Outcome of a single validation pass, handed to `onValidationChange`. */\nexport type TextInputValidation = {\n valid: boolean;\n /** First issue reported by the schema, or `undefined` when valid. */\n error?: string;\n};\n\nexport type TextInputProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"onChange\" | \"value\" | \"defaultValue\" | \"type\"\n> & {\n label?: string;\n /** Guidance under the field. Replaced by the error message while invalid. */\n hint?: ReactNode;\n /**\n * Error supplied by the caller — a server-side or cross-field failure. Takes\n * precedence over the schema, so a form can hold a field invalid regardless\n * of what `schema` makes of the value on its own.\n */\n error?: string;\n /** Renders the design's muted \"Optional\" marker at the end of the label. */\n optional?: boolean;\n /** Trailing label slot, for affordances like the design's \"Forgot?\" link. */\n action?: ReactNode;\n /** Trailing in-field slot, for affordances like the design's password peek. */\n trailing?: ReactNode;\n /** Renders the value in the design's monospace face. */\n mono?: boolean;\n type?: \"text\" | \"email\" | \"password\" | \"tel\" | \"url\" | \"search\";\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n /**\n * Any zod schema that accepts a string. Only `safeParse` is used, so a\n * refined, transformed or preprocessed schema works as well as a plain one.\n */\n schema?: TextInputSchema;\n /**\n * When the schema runs. `blur` — the default — avoids scolding someone\n * mid-keystroke; once a field has gone invalid it revalidates on every\n * keystroke so the message clears as soon as it is fixed.\n */\n validateOn?: \"blur\" | \"change\" | \"never\";\n onValidationChange?: (state: TextInputValidation) => void;\n};\n\nconst FIELD_CLASSES =\n \"h-10 w-full rounded-field border bg-surface px-3 font-[inherit] text-field text-fg outline-none placeholder:text-fg-subtle disabled:cursor-not-allowed disabled:opacity-45\";\nconst VALID_CLASSES =\n \"border-line focus:border-accent focus:ring-3 focus:ring-accent-soft\";\nconst INVALID_CLASSES =\n \"border-danger focus:border-danger focus:ring-3 focus:ring-danger-soft\";\nconst FALLBACK_MESSAGE = \"Invalid value\";\n\nfunction runSchema(\n schema: TextInputSchema | undefined,\n value: string,\n): TextInputValidation {\n if (!schema) {\n return { valid: true };\n }\n\n const result = schema.safeParse(value);\n if (result.success) {\n return { valid: true };\n }\n\n return {\n valid: false,\n error: result.error.issues[0]?.message ?? FALLBACK_MESSAGE,\n };\n}\n\nfunction fieldClassName(\n invalid: boolean,\n mono: boolean,\n hasTrailing: boolean,\n className: string | undefined,\n): string {\n return [\n FIELD_CLASSES,\n invalid ? INVALID_CLASSES : VALID_CLASSES,\n mono ? \"font-code\" : \"\",\n hasTrailing ? \"pr-16\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n}\n\nfunction FieldLabel({\n htmlFor,\n label,\n optional,\n action,\n}: {\n htmlFor: string;\n label: string | undefined;\n optional: boolean;\n action: ReactNode;\n}) {\n if (!label) {\n return null;\n }\n\n return (\n <label\n htmlFor={htmlFor}\n className=\"mb-1.5 flex items-center text-label font-semibold text-fg-muted\"\n >\n {label}\n {optional ? (\n <span className=\"ml-auto text-hint font-semibold text-fg-subtle\">\n Optional\n </span>\n ) : null}\n {action ? <span className=\"ml-auto\">{action}</span> : null}\n </label>\n );\n}\n\nfunction FieldMessage({\n id,\n error,\n hint,\n}: {\n id: string;\n error: string | undefined;\n hint: ReactNode;\n}) {\n if (error) {\n return (\n <p id={id} role=\"alert\" className=\"mt-1.5 text-hint text-danger\">\n {error}\n </p>\n );\n }\n\n if (hint) {\n return (\n <p id={id} className=\"mt-1.5 text-hint text-fg-subtle\">\n {hint}\n </p>\n );\n }\n\n return null;\n}\n\ntype FieldBehaviour = Pick<\n TextInputProps,\n | \"value\"\n | \"defaultValue\"\n | \"error\"\n | \"onChange\"\n | \"onBlur\"\n | \"schema\"\n | \"validateOn\"\n | \"onValidationChange\"\n>;\n\n/**\n * Owns the value (controlled or not), the schema verdict, and when to re-run\n * it. Split out so the component itself stays markup.\n */\nfunction useFieldBehaviour({\n value,\n defaultValue,\n error,\n onChange,\n onBlur,\n schema,\n validateOn = \"blur\",\n onValidationChange,\n}: FieldBehaviour) {\n const [uncontrolledValue, setUncontrolledValue] = useState(\n defaultValue ?? \"\",\n );\n const [schemaError, setSchemaError] = useState<string | undefined>(undefined);\n\n const isControlled = value !== undefined;\n\n const validate = useCallback(\n (next: string) => {\n const state = runSchema(schema, next);\n setSchemaError(state.error);\n onValidationChange?.(state);\n },\n [schema, onValidationChange],\n );\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n if (!isControlled) {\n setUncontrolledValue(next);\n }\n onChange?.(next);\n\n // Revalidate on every keystroke once the field is already invalid, so a\n // corrected value clears the message without waiting for another blur.\n if (validateOn === \"change\" || (validateOn === \"blur\" && schemaError)) {\n validate(next);\n }\n },\n [isControlled, onChange, validateOn, schemaError, validate],\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLInputElement>) => {\n if (validateOn === \"blur\") {\n validate(event.target.value);\n }\n onBlur?.(event);\n },\n [validateOn, validate, onBlur],\n );\n\n return {\n currentValue: isControlled ? value : uncontrolledValue,\n // A caller-supplied error outranks the schema's verdict.\n shownError: error ?? schemaError,\n handleChange,\n handleBlur,\n };\n}\n\nexport function TextInput({\n label,\n hint,\n error,\n optional = false,\n action,\n trailing,\n mono = false,\n type = \"text\",\n value,\n defaultValue,\n onChange,\n schema,\n validateOn = \"blur\",\n onValidationChange,\n id,\n className,\n onBlur,\n ...rest\n}: TextInputProps) {\n const generatedId = useId();\n const inputId = id ?? generatedId;\n const describedById = `${inputId}-description`;\n\n const { currentValue, shownError, handleChange, handleBlur } =\n useFieldBehaviour({\n value,\n defaultValue,\n error,\n onChange,\n onBlur,\n schema,\n validateOn,\n onValidationChange,\n });\n\n return (\n <div className=\"flex flex-col\">\n <FieldLabel\n htmlFor={inputId}\n label={label}\n optional={optional}\n action={action}\n />\n\n <div className={trailing ? \"relative\" : undefined}>\n <input\n {...rest}\n id={inputId}\n type={type}\n value={currentValue}\n aria-invalid={shownError ? true : undefined}\n aria-describedby={shownError || hint ? describedById : undefined}\n onChange={handleChange}\n onBlur={handleBlur}\n className={fieldClassName(\n Boolean(shownError),\n mono,\n Boolean(trailing),\n className,\n )}\n />\n {trailing ? (\n <span className=\"absolute top-1/2 right-[9px] -translate-y-1/2 text-hint font-semibold text-fg-subtle\">\n {trailing}\n </span>\n ) : null}\n </div>\n\n <FieldMessage id={describedById} error={shownError} hint={hint} />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as react from '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
|
+
declare function TextInput({ label, hint, error, optional, action, trailing, mono, type, value, defaultValue, onChange, schema, validateOn, onValidationChange, id, className, onBlur, ...rest }: TextInputProps): react.JSX.Element;
|
|
64
|
+
|
|
65
|
+
export { TextInput, type TextInputProps, type TextInputSchema, type TextInputValidation };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as react from '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
|
+
declare function TextInput({ label, hint, error, optional, action, trailing, mono, type, value, defaultValue, onChange, schema, validateOn, onValidationChange, id, className, onBlur, ...rest }: TextInputProps): react.JSX.Element;
|
|
64
|
+
|
|
65
|
+
export { TextInput, type TextInputProps, type TextInputSchema, type TextInputValidation };
|