@epilot/spark-ui 0.15.0 → 0.15.2
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/dist/components/bubble.d.cts +1 -1
- package/dist/components/bubble.d.ts +1 -1
- package/dist/components/button.d.cts +3 -3
- package/dist/components/button.d.ts +3 -3
- package/dist/components/callout.d.cts +1 -1
- package/dist/components/callout.d.ts +1 -1
- package/dist/components/field.cjs +42 -7
- package/dist/components/field.cjs.map +1 -1
- package/dist/components/field.d.cts +7 -0
- package/dist/components/field.d.ts +7 -0
- package/dist/components/field.js +42 -7
- package/dist/components/field.js.map +1 -1
- package/dist/components/input-group.d.cts +1 -1
- package/dist/components/input-group.d.ts +1 -1
- package/dist/components/input-otp.cjs +2 -1
- package/dist/components/input-otp.cjs.map +1 -1
- package/dist/components/input-otp.js +2 -1
- package/dist/components/input-otp.js.map +1 -1
- package/dist/components/input.cjs +2 -1
- package/dist/components/input.cjs.map +1 -1
- package/dist/components/input.js +2 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/page-header.d.cts +1 -1
- package/dist/components/page-header.d.ts +1 -1
- package/dist/components/reparent.cjs +23 -9
- package/dist/components/reparent.cjs.map +1 -1
- package/dist/components/reparent.d.cts +1 -0
- package/dist/components/reparent.d.ts +1 -0
- package/dist/components/reparent.js +23 -9
- package/dist/components/reparent.js.map +1 -1
- package/dist/components/select.cjs +2 -1
- package/dist/components/select.cjs.map +1 -1
- package/dist/components/select.js +2 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/textarea.cjs +2 -1
- package/dist/components/textarea.cjs.map +1 -1
- package/dist/components/textarea.js +2 -1
- package/dist/components/textarea.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ declare function BubbleGroup({
|
|
|
10
10
|
...props
|
|
11
11
|
}: React$1.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
12
12
|
declare const bubbleVariants: (props?: ({
|
|
13
|
-
variant?: "default" | "
|
|
13
|
+
variant?: "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive" | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
15
|
declare function Bubble({
|
|
16
16
|
variant,
|
|
@@ -10,7 +10,7 @@ declare function BubbleGroup({
|
|
|
10
10
|
...props
|
|
11
11
|
}: React$1.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
12
12
|
declare const bubbleVariants: (props?: ({
|
|
13
|
-
variant?: "default" | "
|
|
13
|
+
variant?: "default" | "secondary" | "muted" | "tinted" | "outline" | "ghost" | "destructive" | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
15
|
declare function Bubble({
|
|
16
16
|
variant,
|
|
@@ -7,9 +7,9 @@ import { VariantProps } from "class-variance-authority";
|
|
|
7
7
|
|
|
8
8
|
//#region src/components/button.d.ts
|
|
9
9
|
declare const buttonVariants: (props?: ({
|
|
10
|
-
variant?: "
|
|
11
|
-
color?: "
|
|
12
|
-
size?: "small" | "
|
|
10
|
+
variant?: "secondary" | "destructive" | "primary" | "tertiary" | "bare" | null | undefined;
|
|
11
|
+
color?: "neutral" | "accent" | "danger" | null | undefined;
|
|
12
|
+
size?: "small" | "icon" | "default" | "medium" | "icon-small" | "icon-medium" | null | undefined;
|
|
13
13
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
14
14
|
type SparkButtonColor = 'accent' | 'neutral' | 'danger';
|
|
15
15
|
interface ButtonProps extends Omit<React$1.ComponentProps<typeof Button$1>, 'ref'>, Omit<VariantProps<typeof buttonVariants>, 'variant' | 'color'> {
|
|
@@ -7,9 +7,9 @@ import { VariantProps } from "class-variance-authority";
|
|
|
7
7
|
|
|
8
8
|
//#region src/components/button.d.ts
|
|
9
9
|
declare const buttonVariants: (props?: ({
|
|
10
|
-
variant?: "
|
|
11
|
-
color?: "
|
|
12
|
-
size?: "small" | "
|
|
10
|
+
variant?: "secondary" | "destructive" | "primary" | "tertiary" | "bare" | null | undefined;
|
|
11
|
+
color?: "neutral" | "accent" | "danger" | null | undefined;
|
|
12
|
+
size?: "small" | "icon" | "default" | "medium" | "icon-small" | "icon-medium" | null | undefined;
|
|
13
13
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
14
14
|
type SparkButtonColor = 'accent' | 'neutral' | 'danger';
|
|
15
15
|
interface ButtonProps extends Omit<React$1.ComponentProps<typeof Button$1>, 'ref'>, Omit<VariantProps<typeof buttonVariants>, 'variant' | 'color'> {
|
|
@@ -8,7 +8,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
8
8
|
//#region src/components/callout.d.ts
|
|
9
9
|
type CalloutVariant = 'error' | 'info' | 'neutral' | 'success' | 'warning';
|
|
10
10
|
declare const calloutVariants: (props?: ({
|
|
11
|
-
variant?: "
|
|
11
|
+
variant?: "info" | "warning" | "success" | "error" | "neutral" | null | undefined;
|
|
12
12
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
13
13
|
interface CalloutProps extends React$1.HTMLAttributes<HTMLDivElement>, VariantProps<typeof calloutVariants> {
|
|
14
14
|
/** Overrides the variant's default icon; the variant's color is kept. `false` renders no icon. */
|
|
@@ -8,7 +8,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
8
8
|
//#region src/components/callout.d.ts
|
|
9
9
|
type CalloutVariant = 'error' | 'info' | 'neutral' | 'success' | 'warning';
|
|
10
10
|
declare const calloutVariants: (props?: ({
|
|
11
|
-
variant?: "
|
|
11
|
+
variant?: "info" | "warning" | "success" | "error" | "neutral" | null | undefined;
|
|
12
12
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
13
13
|
interface CalloutProps extends React$1.HTMLAttributes<HTMLDivElement>, VariantProps<typeof calloutVariants> {
|
|
14
14
|
/** Overrides the variant's default icon; the variant's color is kept. `false` renders no icon. */
|
|
@@ -18,12 +18,21 @@ function useFieldContext() {
|
|
|
18
18
|
function useFieldControl(options) {
|
|
19
19
|
const field = useFieldContext();
|
|
20
20
|
const theme = require_components_theme.useTheme();
|
|
21
|
+
const id = options?.id ?? field?.id;
|
|
22
|
+
const ariaDescribedBy = [
|
|
23
|
+
field?.hasError ? field.errorId : void 0,
|
|
24
|
+
field?.hasDescription ? field.descriptionId : void 0,
|
|
25
|
+
options?.ariaDescribedBy
|
|
26
|
+
].filter(Boolean).join(" ") || void 0;
|
|
27
|
+
const inputStyle = options?.variant ?? field?.inputStyle ?? theme.inputStyle ?? "soft";
|
|
28
|
+
const inputColor = field?.inputColor ?? theme.inputColor ?? "brand";
|
|
21
29
|
return {
|
|
22
30
|
field,
|
|
23
|
-
id
|
|
24
|
-
ariaDescribedBy
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
id,
|
|
32
|
+
ariaDescribedBy,
|
|
33
|
+
ariaInvalid: field?.hasError || void 0,
|
|
34
|
+
inputStyle,
|
|
35
|
+
inputColor
|
|
27
36
|
};
|
|
28
37
|
}
|
|
29
38
|
function FieldSet({ className, ...props }) {
|
|
@@ -119,9 +128,14 @@ const fieldVariants = (0, class_variance_authority.cva)("group/field relative fl
|
|
|
119
128
|
}
|
|
120
129
|
]
|
|
121
130
|
});
|
|
122
|
-
function Field({ className, children, labelPosition: labelPositionProp, inputStyle: inputStyleProp, inputColor: inputColorProp, readOnly = false, ...props }) {
|
|
131
|
+
function Field({ className, children, labelPosition: labelPositionProp, inputStyle: inputStyleProp, inputColor: inputColorProp, readOnly = false, "data-invalid": dataInvalid, ...props }) {
|
|
123
132
|
const id = (0, react.useId)();
|
|
124
|
-
const [
|
|
133
|
+
const [descriptionCount, setDescriptionCount] = react.useState(0);
|
|
134
|
+
const [errorCount, setErrorCount] = react.useState(0);
|
|
135
|
+
const setHasDescription = react.useCallback((has) => setDescriptionCount((n) => n + (has ? 1 : -1)), []);
|
|
136
|
+
const setHasError = react.useCallback((has) => setErrorCount((n) => n + (has ? 1 : -1)), []);
|
|
137
|
+
const hasDescription = descriptionCount > 0;
|
|
138
|
+
const hasError = errorCount > 0;
|
|
125
139
|
const theme = require_components_theme.useTheme();
|
|
126
140
|
const labelPosition = labelPositionProp ?? theme.labelPosition ?? "inset";
|
|
127
141
|
const inputStyle = inputStyleProp ?? theme.inputStyle ?? "soft";
|
|
@@ -134,6 +148,9 @@ function Field({ className, children, labelPosition: labelPositionProp, inputSty
|
|
|
134
148
|
descriptionId: `${fieldId}-description`,
|
|
135
149
|
hasDescription,
|
|
136
150
|
setHasDescription,
|
|
151
|
+
errorId: `${fieldId}-error`,
|
|
152
|
+
hasError,
|
|
153
|
+
setHasError,
|
|
137
154
|
readOnly,
|
|
138
155
|
labelPosition,
|
|
139
156
|
inputStyle,
|
|
@@ -142,6 +159,9 @@ function Field({ className, children, labelPosition: labelPositionProp, inputSty
|
|
|
142
159
|
fieldId,
|
|
143
160
|
labelId,
|
|
144
161
|
hasDescription,
|
|
162
|
+
setHasDescription,
|
|
163
|
+
hasError,
|
|
164
|
+
setHasError,
|
|
145
165
|
readOnly,
|
|
146
166
|
labelPosition,
|
|
147
167
|
inputStyle,
|
|
@@ -155,6 +175,7 @@ function Field({ className, children, labelPosition: labelPositionProp, inputSty
|
|
|
155
175
|
"data-label-position": labelPosition,
|
|
156
176
|
"data-input-style": inputStyle,
|
|
157
177
|
"data-input-color": inputColor,
|
|
178
|
+
"data-invalid": dataInvalid ?? (hasError || void 0),
|
|
158
179
|
"aria-labelledby": readOnly ? labelId : void 0,
|
|
159
180
|
className: (0, cnfast.cn)(fieldVariants({
|
|
160
181
|
labelPosition,
|
|
@@ -251,7 +272,9 @@ function FieldSeparator({ children, className, ...props }) {
|
|
|
251
272
|
})]
|
|
252
273
|
});
|
|
253
274
|
}
|
|
254
|
-
function FieldError({ className, children, errors, ...props }) {
|
|
275
|
+
function FieldError({ className, children, errors, id, ...props }) {
|
|
276
|
+
const field = useFieldContext();
|
|
277
|
+
const setHasError = field?.setHasError;
|
|
255
278
|
const content = react.useMemo(() => {
|
|
256
279
|
if (children) return children;
|
|
257
280
|
if (!errors?.length) return null;
|
|
@@ -262,10 +285,22 @@ function FieldError({ className, children, errors, ...props }) {
|
|
|
262
285
|
children: unique.map((error, index) => error?.message && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: error.message }, index))
|
|
263
286
|
});
|
|
264
287
|
}, [children, errors]);
|
|
288
|
+
const hasContent = Boolean(content);
|
|
289
|
+
const hasCustomId = id != null;
|
|
290
|
+
react.useEffect(() => {
|
|
291
|
+
if (!setHasError || !hasContent || hasCustomId) return;
|
|
292
|
+
setHasError(true);
|
|
293
|
+
return () => setHasError(false);
|
|
294
|
+
}, [
|
|
295
|
+
setHasError,
|
|
296
|
+
hasContent,
|
|
297
|
+
hasCustomId
|
|
298
|
+
]);
|
|
265
299
|
if (!content) return null;
|
|
266
300
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
267
301
|
role: "alert",
|
|
268
302
|
"data-slot": "field-error",
|
|
303
|
+
id: id ?? field?.errorId,
|
|
269
304
|
className: (0, cnfast.cn)("mt-0.5 text-sm font-normal text-error", className),
|
|
270
305
|
...props,
|
|
271
306
|
children: content
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field.cjs","names":["React","useTheme","Skeleton","Separator"],"sources":["../../src/components/field.tsx"],"sourcesContent":["'use client'\n\nimport { cva } from 'class-variance-authority'\nimport * as React from 'react'\nimport { useId } from 'react'\nimport type {\n SparkInputColor,\n SparkInputStyle,\n SparkLabelPosition,\n} from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Separator } from './separator'\nimport { Skeleton } from './skeleton'\nimport { useTheme } from './theme'\n\ntype FieldContextValue = {\n id: string\n labelId: string\n descriptionId: string\n hasDescription: boolean\n setHasDescription: (has: boolean) => void\n readOnly: boolean\n labelPosition: SparkLabelPosition\n inputStyle: SparkInputStyle\n inputColor: SparkInputColor\n}\n\nconst FieldContext = React.createContext<FieldContextValue | null>(null)\n\nexport function useFieldContext() {\n return React.useContext(FieldContext)\n}\n\n// Field→Theme→default resolution + id/aria-describedby wiring, shared by every\n// control that can sit inside a Field (Input, SelectTrigger, …) so they can't\n// drift on how a style/color/id resolves. `variant` is the control's own\n// per-instance override (already normalized — e.g. Input maps its 'none' to\n// undefined before calling this).\nexport function useFieldControl(options?: {\n id?: string\n ariaDescribedBy?: string\n variant?: SparkInputStyle\n}) {\n const field = useFieldContext()\n const theme = useTheme()\n const id = options?.id ?? field?.id\n const descriptionId = field?.hasDescription ? field.descriptionId : undefined\n const ariaDescribedBy =\n [descriptionId, options?.ariaDescribedBy].filter(Boolean).join(' ') ||\n undefined\n const inputStyle =\n options?.variant ?? field?.inputStyle ?? theme.inputStyle ?? 'soft'\n const inputColor = field?.inputColor ?? theme.inputColor ?? 'brand'\n return { field, id, ariaDescribedBy, inputStyle, inputColor }\n}\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n return (\n <fieldset\n data-slot=\"field-set\"\n className={cn(\n 'flex flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldLegend({\n className,\n variant = 'legend',\n ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n return (\n <legend\n data-slot=\"field-legend\"\n data-variant={variant}\n className={cn(\n 'mb-1.5 font-medium text-gray data-[variant=label]:text-sm data-[variant=legend]:text-base',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-group\"\n className={cn(\n 'group/field-group flex w-full flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldVariants = cva(\n 'group/field relative flex w-full flex-col data-[invalid=true]:text-error-active',\n {\n variants: {\n labelPosition: {\n // inset puts label + input inside one shell, so the shell is the box\n // focus inside inset is the classic underline cue, not a shell ring\n inset: 'gap-y-1 rounded-lg px-4 py-3',\n outset: 'gap-y-2',\n },\n inputStyle: { soft: '', surface: '', outlined: '', ghost: '' },\n inputColor: { brand: '', gray: '' },\n },\n compoundVariants: [\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'brand',\n class:\n 'bg-input-base has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-input-base-hover',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'gray',\n class:\n 'bg-base-gray-soft has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-gray-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'brand',\n class: 'border border-input bg-base-accent-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'gray',\n class: 'border border-input-gray bg-base-gray-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'brand',\n class: 'border border-input',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'gray',\n class: 'border border-input-gray',\n },\n // ghost inset = bare label + underlined input; the input carries the line\n {\n labelPosition: 'inset',\n inputStyle: 'ghost',\n class: 'rounded-none px-0',\n },\n // shared inset states: filled shells swap bg, bordered shells swap border\n // invalid hover deepens the error tint instead of falling back to accent\n {\n labelPosition: 'inset',\n inputStyle: ['soft', 'surface'],\n class:\n 'data-[disabled=true]:bg-base-gray-soft data-[invalid=true]:bg-base-error-soft data-[invalid=true]:has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-red-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: ['surface', 'outlined'],\n class:\n 'data-[disabled=true]:border-input-disabled data-[invalid=true]:border-input-invalid',\n },\n ],\n },\n)\n\ninterface FieldProps extends React.ComponentProps<'div'> {\n labelPosition?: SparkLabelPosition\n inputStyle?: SparkInputStyle\n inputColor?: SparkInputColor\n readOnly?: boolean\n}\n\nfunction Field({\n className,\n children,\n labelPosition: labelPositionProp,\n inputStyle: inputStyleProp,\n inputColor: inputColorProp,\n readOnly = false,\n ...props\n}: FieldProps) {\n const id = useId()\n const [hasDescription, setHasDescription] = React.useState(false)\n // knobs are structural (they move the label / pick the box carrier), so they\n // resolve here via the <Theme> context; props override per instance\n const theme = useTheme()\n const labelPosition = labelPositionProp ?? theme.labelPosition ?? 'inset'\n const inputStyle = inputStyleProp ?? theme.inputStyle ?? 'soft'\n const inputColor = inputColorProp ?? theme.inputColor ?? 'brand'\n const fieldId = `field-${id}`\n const labelId = `${fieldId}-label`\n const value = React.useMemo<FieldContextValue>(\n () => ({\n id: fieldId,\n labelId,\n descriptionId: `${fieldId}-description`,\n hasDescription,\n setHasDescription,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n }),\n [\n fieldId,\n labelId,\n hasDescription,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n ],\n )\n\n return (\n <FieldContext.Provider value={value}>\n <div\n role=\"group\"\n data-slot=\"field\"\n data-label-position={labelPosition}\n data-input-style={inputStyle}\n data-input-color={inputColor}\n aria-labelledby={readOnly ? labelId : undefined}\n className={cn(\n fieldVariants({ labelPosition, inputStyle, inputColor }),\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </FieldContext.Provider>\n )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-0.5 leading-snug',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldLabelClassName =\n 'text-sm leading-5 font-normal text-gray-light transition-all group-data-[invalid=true]/field:text-error-active! motion-reduce:transition-none'\n\nfunction FieldLabel({\n className,\n htmlFor,\n srOnly,\n children,\n ...props\n}: React.ComponentProps<'label'> & { srOnly?: boolean }) {\n const field = useFieldContext()\n const classNames = cn(\n // labels stay full-contrast even when disabled — the control itself\n // (dimmed text, gray shell) signals the state\n fieldLabelClassName,\n // visually hidden but kept in the a11y tree (still names + focuses the control)\n srOnly && 'sr-only',\n className,\n )\n\n if (field?.readOnly) {\n return (\n <div data-slot=\"field-label\" id={field.labelId} className={classNames}>\n {children}\n </div>\n )\n }\n\n return (\n <label\n data-slot=\"field-label\"\n className={classNames}\n htmlFor={htmlFor ?? field?.id}\n {...props}\n >\n {children}\n </label>\n )\n}\n\nfunction FieldValue({\n className,\n id,\n title,\n truncate,\n children,\n ...props\n}: React.ComponentProps<'div'> & { truncate?: boolean }) {\n return (\n <div\n data-slot=\"field-value\"\n id={id}\n // truncated string values keep the full text reachable on hover\n title={\n title ??\n (truncate && typeof children === 'string' ? children : undefined)\n }\n className={cn(\n // values are content, not controls — disabled dims but stays ≥4.5:1\n 'min-h-8 w-full text-base text-gray tabular-nums group-data-[disabled=true]/field:opacity-70',\n truncate\n ? 'block truncate leading-8'\n : 'inline-flex items-center wrap-anywhere whitespace-pre-wrap',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nfunction FieldSkeleton({\n className,\n labelClassName,\n valueClassName,\n truncate,\n ...props\n}: React.ComponentProps<'div'> & {\n truncate?: boolean\n labelClassName?: string\n valueClassName?: string\n}) {\n return (\n <div\n data-slot=\"field-skeleton\"\n aria-hidden=\"true\"\n className={cn('flex w-full flex-col gap-y-1', className)}\n {...props}\n >\n <Skeleton\n data-slot=\"field-skeleton-label\"\n className={cn('h-5 w-24', labelClassName)}\n />\n <Skeleton\n data-slot=\"field-skeleton-value\"\n className={cn(\n 'h-8 min-h-8 w-full',\n truncate && 'max-w-full truncate',\n valueClassName,\n )}\n />\n </div>\n )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-title\"\n className={cn(\n 'flex w-fit items-center gap-2 text-sm font-medium text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldDescription({\n className,\n id,\n ...props\n}: React.ComponentProps<'div'>) {\n const field = useFieldContext()\n const setHasDescription = field?.setHasDescription\n\n React.useEffect(() => {\n if (!setHasDescription) return\n setHasDescription(true)\n return () => setHasDescription(false)\n }, [setHasDescription])\n\n return (\n <div\n data-slot=\"field-description\"\n id={id ?? field?.descriptionId}\n className={cn('mt-0.5 text-sm leading-normal text-gray-light', className)}\n {...props}\n />\n )\n}\n\nfunction FieldSeparator({\n children,\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-separator\"\n data-content={!!children}\n className={cn('relative -my-2 h-5 text-sm', className)}\n {...props}\n >\n <Separator className=\"absolute inset-0 top-1/2\" />\n {children && (\n <span\n className=\"relative mx-auto block w-fit bg-(--spark-background) px-2 text-gray-light\"\n data-slot=\"field-separator-content\"\n >\n {children}\n </span>\n )}\n </div>\n )\n}\n\nfunction FieldError({\n className,\n children,\n errors,\n ...props\n}: React.ComponentProps<'div'> & {\n errors?: Array<{ message?: string } | undefined>\n}) {\n const content = React.useMemo(() => {\n if (children) return children\n if (!errors?.length) return null\n const unique = [\n ...new Map(errors.map((error) => [error?.message, error])).values(),\n ]\n if (unique.length === 1) return unique[0]?.message\n return (\n <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n {unique.map(\n (error, index) =>\n error?.message && <li key={index}>{error.message}</li>,\n )}\n </ul>\n )\n }, [children, errors])\n\n if (!content) return null\n\n return (\n <div\n role=\"alert\"\n data-slot=\"field-error\"\n className={cn('mt-0.5 text-sm font-normal text-error', className)}\n {...props}\n >\n {content}\n </div>\n )\n}\n\nexport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n FieldLegend,\n FieldSeparator,\n FieldSet,\n FieldSkeleton,\n FieldTitle,\n FieldValue,\n}\nexport type { FieldProps }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"field.cjs","names":["React","useTheme","Skeleton","Separator"],"sources":["../../src/components/field.tsx"],"sourcesContent":["'use client'\n\nimport { cva } from 'class-variance-authority'\nimport * as React from 'react'\nimport { useId } from 'react'\nimport type {\n SparkInputColor,\n SparkInputStyle,\n SparkLabelPosition,\n} from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Separator } from './separator'\nimport { Skeleton } from './skeleton'\nimport { useTheme } from './theme'\n\ntype FieldContextValue = {\n id: string\n labelId: string\n descriptionId: string\n hasDescription: boolean\n setHasDescription: (has: boolean) => void\n errorId: string\n hasError: boolean\n setHasError: (has: boolean) => void\n readOnly: boolean\n labelPosition: SparkLabelPosition\n inputStyle: SparkInputStyle\n inputColor: SparkInputColor\n}\n\nconst FieldContext = React.createContext<FieldContextValue | null>(null)\n\nexport function useFieldContext() {\n return React.useContext(FieldContext)\n}\n\n// Field→Theme→default resolution + id/aria-describedby wiring, shared by every\n// control that can sit inside a Field (Input, SelectTrigger, …) so they can't\n// drift on how a style/color/id resolves. `variant` is the control's own\n// per-instance override (already normalized — e.g. Input maps its 'none' to\n// undefined before calling this).\nexport function useFieldControl(options?: {\n id?: string\n ariaDescribedBy?: string\n variant?: SparkInputStyle\n}) {\n const field = useFieldContext()\n const theme = useTheme()\n const id = options?.id ?? field?.id\n const errorId = field?.hasError ? field.errorId : undefined\n const descriptionId = field?.hasDescription ? field.descriptionId : undefined\n // error before hint: the actionable message should be announced first\n const ariaDescribedBy =\n [errorId, descriptionId, options?.ariaDescribedBy]\n .filter(Boolean)\n .join(' ') || undefined\n const inputStyle =\n options?.variant ?? field?.inputStyle ?? theme.inputStyle ?? 'soft'\n const inputColor = field?.inputColor ?? theme.inputColor ?? 'brand'\n const ariaInvalid = field?.hasError || undefined\n return { field, id, ariaDescribedBy, ariaInvalid, inputStyle, inputColor }\n}\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n return (\n <fieldset\n data-slot=\"field-set\"\n className={cn(\n 'flex flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldLegend({\n className,\n variant = 'legend',\n ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n return (\n <legend\n data-slot=\"field-legend\"\n data-variant={variant}\n className={cn(\n 'mb-1.5 font-medium text-gray data-[variant=label]:text-sm data-[variant=legend]:text-base',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-group\"\n className={cn(\n 'group/field-group flex w-full flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldVariants = cva(\n 'group/field relative flex w-full flex-col data-[invalid=true]:text-error-active',\n {\n variants: {\n labelPosition: {\n // inset puts label + input inside one shell, so the shell is the box\n // focus inside inset is the classic underline cue, not a shell ring\n inset: 'gap-y-1 rounded-lg px-4 py-3',\n outset: 'gap-y-2',\n },\n inputStyle: { soft: '', surface: '', outlined: '', ghost: '' },\n inputColor: { brand: '', gray: '' },\n },\n compoundVariants: [\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'brand',\n class:\n 'bg-input-base has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-input-base-hover',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'gray',\n class:\n 'bg-base-gray-soft has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-gray-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'brand',\n class: 'border border-input bg-base-accent-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'gray',\n class: 'border border-input-gray bg-base-gray-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'brand',\n class: 'border border-input',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'gray',\n class: 'border border-input-gray',\n },\n // ghost inset = bare label + underlined input; the input carries the line\n {\n labelPosition: 'inset',\n inputStyle: 'ghost',\n class: 'rounded-none px-0',\n },\n // shared inset states: filled shells swap bg, bordered shells swap border\n // invalid hover deepens the error tint instead of falling back to accent\n {\n labelPosition: 'inset',\n inputStyle: ['soft', 'surface'],\n class:\n 'data-[disabled=true]:bg-base-gray-soft data-[invalid=true]:bg-base-error-soft data-[invalid=true]:has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-red-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: ['surface', 'outlined'],\n class:\n 'data-[disabled=true]:border-input-disabled data-[invalid=true]:border-input-invalid',\n },\n ],\n },\n)\n\ninterface FieldProps extends React.ComponentProps<'div'> {\n labelPosition?: SparkLabelPosition\n inputStyle?: SparkInputStyle\n inputColor?: SparkInputColor\n readOnly?: boolean\n 'data-invalid'?: boolean | 'true' | 'false'\n}\n\nfunction Field({\n className,\n children,\n labelPosition: labelPositionProp,\n inputStyle: inputStyleProp,\n inputColor: inputColorProp,\n readOnly = false,\n 'data-invalid': dataInvalid,\n ...props\n}: FieldProps) {\n const id = useId()\n const [descriptionCount, setDescriptionCount] = React.useState(0)\n const [errorCount, setErrorCount] = React.useState(0)\n // counts, not flags: one slot unmounting must not clear what siblings still need\n const setHasDescription = React.useCallback(\n (has: boolean) => setDescriptionCount((n) => n + (has ? 1 : -1)),\n [],\n )\n const setHasError = React.useCallback(\n (has: boolean) => setErrorCount((n) => n + (has ? 1 : -1)),\n [],\n )\n const hasDescription = descriptionCount > 0\n const hasError = errorCount > 0\n // knobs are structural (they move the label / pick the box carrier), so they\n // resolve here via the <Theme> context; props override per instance\n const theme = useTheme()\n const labelPosition = labelPositionProp ?? theme.labelPosition ?? 'inset'\n const inputStyle = inputStyleProp ?? theme.inputStyle ?? 'soft'\n const inputColor = inputColorProp ?? theme.inputColor ?? 'brand'\n const fieldId = `field-${id}`\n const labelId = `${fieldId}-label`\n const value = React.useMemo<FieldContextValue>(\n () => ({\n id: fieldId,\n labelId,\n descriptionId: `${fieldId}-description`,\n hasDescription,\n setHasDescription,\n errorId: `${fieldId}-error`,\n hasError,\n setHasError,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n }),\n [\n fieldId,\n labelId,\n hasDescription,\n setHasDescription,\n hasError,\n setHasError,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n ],\n )\n\n return (\n <FieldContext.Provider value={value}>\n <div\n role=\"group\"\n data-slot=\"field\"\n data-label-position={labelPosition}\n data-input-style={inputStyle}\n data-input-color={inputColor}\n data-invalid={dataInvalid ?? (hasError || undefined)}\n aria-labelledby={readOnly ? labelId : undefined}\n className={cn(\n fieldVariants({ labelPosition, inputStyle, inputColor }),\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </FieldContext.Provider>\n )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-0.5 leading-snug',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldLabelClassName =\n 'text-sm leading-5 font-normal text-gray-light transition-all group-data-[invalid=true]/field:text-error-active! motion-reduce:transition-none'\n\nfunction FieldLabel({\n className,\n htmlFor,\n srOnly,\n children,\n ...props\n}: React.ComponentProps<'label'> & { srOnly?: boolean }) {\n const field = useFieldContext()\n const classNames = cn(\n // labels stay full-contrast even when disabled — the control itself\n // (dimmed text, gray shell) signals the state\n fieldLabelClassName,\n // visually hidden but kept in the a11y tree (still names + focuses the control)\n srOnly && 'sr-only',\n className,\n )\n\n if (field?.readOnly) {\n return (\n <div data-slot=\"field-label\" id={field.labelId} className={classNames}>\n {children}\n </div>\n )\n }\n\n return (\n <label\n data-slot=\"field-label\"\n className={classNames}\n htmlFor={htmlFor ?? field?.id}\n {...props}\n >\n {children}\n </label>\n )\n}\n\nfunction FieldValue({\n className,\n id,\n title,\n truncate,\n children,\n ...props\n}: React.ComponentProps<'div'> & { truncate?: boolean }) {\n return (\n <div\n data-slot=\"field-value\"\n id={id}\n // truncated string values keep the full text reachable on hover\n title={\n title ??\n (truncate && typeof children === 'string' ? children : undefined)\n }\n className={cn(\n // values are content, not controls — disabled dims but stays ≥4.5:1\n 'min-h-8 w-full text-base text-gray tabular-nums group-data-[disabled=true]/field:opacity-70',\n truncate\n ? 'block truncate leading-8'\n : 'inline-flex items-center wrap-anywhere whitespace-pre-wrap',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nfunction FieldSkeleton({\n className,\n labelClassName,\n valueClassName,\n truncate,\n ...props\n}: React.ComponentProps<'div'> & {\n truncate?: boolean\n labelClassName?: string\n valueClassName?: string\n}) {\n return (\n <div\n data-slot=\"field-skeleton\"\n aria-hidden=\"true\"\n className={cn('flex w-full flex-col gap-y-1', className)}\n {...props}\n >\n <Skeleton\n data-slot=\"field-skeleton-label\"\n className={cn('h-5 w-24', labelClassName)}\n />\n <Skeleton\n data-slot=\"field-skeleton-value\"\n className={cn(\n 'h-8 min-h-8 w-full',\n truncate && 'max-w-full truncate',\n valueClassName,\n )}\n />\n </div>\n )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-title\"\n className={cn(\n 'flex w-fit items-center gap-2 text-sm font-medium text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldDescription({\n className,\n id,\n ...props\n}: React.ComponentProps<'div'>) {\n const field = useFieldContext()\n const setHasDescription = field?.setHasDescription\n\n React.useEffect(() => {\n if (!setHasDescription) return\n setHasDescription(true)\n return () => setHasDescription(false)\n }, [setHasDescription])\n\n return (\n <div\n data-slot=\"field-description\"\n id={id ?? field?.descriptionId}\n className={cn('mt-0.5 text-sm leading-normal text-gray-light', className)}\n {...props}\n />\n )\n}\n\nfunction FieldSeparator({\n children,\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-separator\"\n data-content={!!children}\n className={cn('relative -my-2 h-5 text-sm', className)}\n {...props}\n >\n <Separator className=\"absolute inset-0 top-1/2\" />\n {children && (\n <span\n className=\"relative mx-auto block w-fit bg-(--spark-background) px-2 text-gray-light\"\n data-slot=\"field-separator-content\"\n >\n {children}\n </span>\n )}\n </div>\n )\n}\n\nfunction FieldError({\n className,\n children,\n errors,\n id,\n ...props\n}: React.ComponentProps<'div'> & {\n errors?: Array<{ message?: string } | undefined>\n}) {\n const field = useFieldContext()\n const setHasError = field?.setHasError\n const content = React.useMemo(() => {\n if (children) return children\n if (!errors?.length) return null\n const unique = [\n ...new Map(errors.map((error) => [error?.message, error])).values(),\n ]\n if (unique.length === 1) return unique[0]?.message\n return (\n <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n {unique.map(\n (error, index) =>\n error?.message && <li key={index}>{error.message}</li>,\n )}\n </ul>\n )\n }, [children, errors])\n\n const hasContent = Boolean(content)\n // a custom id means no element carries field.errorId, so don't advertise it\n const hasCustomId = id != null\n\n // must run before the empty-content early return to keep hook order stable\n React.useEffect(() => {\n if (!setHasError || !hasContent || hasCustomId) return\n setHasError(true)\n return () => setHasError(false)\n }, [setHasError, hasContent, hasCustomId])\n\n if (!content) return null\n\n return (\n <div\n role=\"alert\"\n data-slot=\"field-error\"\n id={id ?? field?.errorId}\n className={cn('mt-0.5 text-sm font-normal text-error', className)}\n {...props}\n >\n {content}\n </div>\n )\n}\n\nexport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n FieldLegend,\n FieldSeparator,\n FieldSet,\n FieldSkeleton,\n FieldTitle,\n FieldValue,\n}\nexport type { FieldProps }\n"],"mappings":"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"}
|
|
@@ -10,6 +10,9 @@ type FieldContextValue = {
|
|
|
10
10
|
descriptionId: string;
|
|
11
11
|
hasDescription: boolean;
|
|
12
12
|
setHasDescription: (has: boolean) => void;
|
|
13
|
+
errorId: string;
|
|
14
|
+
hasError: boolean;
|
|
15
|
+
setHasError: (has: boolean) => void;
|
|
13
16
|
readOnly: boolean;
|
|
14
17
|
labelPosition: SparkLabelPosition;
|
|
15
18
|
inputStyle: SparkInputStyle;
|
|
@@ -24,6 +27,7 @@ declare function useFieldControl(options?: {
|
|
|
24
27
|
field: FieldContextValue | null;
|
|
25
28
|
id: string | undefined;
|
|
26
29
|
ariaDescribedBy: string | undefined;
|
|
30
|
+
ariaInvalid: true | undefined;
|
|
27
31
|
inputStyle: SparkInputStyle;
|
|
28
32
|
inputColor: SparkInputColor;
|
|
29
33
|
};
|
|
@@ -47,6 +51,7 @@ interface FieldProps extends React$1.ComponentProps<'div'> {
|
|
|
47
51
|
inputStyle?: SparkInputStyle;
|
|
48
52
|
inputColor?: SparkInputColor;
|
|
49
53
|
readOnly?: boolean;
|
|
54
|
+
'data-invalid'?: boolean | 'true' | 'false';
|
|
50
55
|
}
|
|
51
56
|
declare function Field({
|
|
52
57
|
className,
|
|
@@ -55,6 +60,7 @@ declare function Field({
|
|
|
55
60
|
inputStyle: inputStyleProp,
|
|
56
61
|
inputColor: inputColorProp,
|
|
57
62
|
readOnly,
|
|
63
|
+
'data-invalid': dataInvalid,
|
|
58
64
|
...props
|
|
59
65
|
}: FieldProps): import("react/jsx-runtime").JSX.Element;
|
|
60
66
|
declare function FieldContent({
|
|
@@ -109,6 +115,7 @@ declare function FieldError({
|
|
|
109
115
|
className,
|
|
110
116
|
children,
|
|
111
117
|
errors,
|
|
118
|
+
id,
|
|
112
119
|
...props
|
|
113
120
|
}: React$1.ComponentProps<'div'> & {
|
|
114
121
|
errors?: Array<{
|
|
@@ -10,6 +10,9 @@ type FieldContextValue = {
|
|
|
10
10
|
descriptionId: string;
|
|
11
11
|
hasDescription: boolean;
|
|
12
12
|
setHasDescription: (has: boolean) => void;
|
|
13
|
+
errorId: string;
|
|
14
|
+
hasError: boolean;
|
|
15
|
+
setHasError: (has: boolean) => void;
|
|
13
16
|
readOnly: boolean;
|
|
14
17
|
labelPosition: SparkLabelPosition;
|
|
15
18
|
inputStyle: SparkInputStyle;
|
|
@@ -24,6 +27,7 @@ declare function useFieldControl(options?: {
|
|
|
24
27
|
field: FieldContextValue | null;
|
|
25
28
|
id: string | undefined;
|
|
26
29
|
ariaDescribedBy: string | undefined;
|
|
30
|
+
ariaInvalid: true | undefined;
|
|
27
31
|
inputStyle: SparkInputStyle;
|
|
28
32
|
inputColor: SparkInputColor;
|
|
29
33
|
};
|
|
@@ -47,6 +51,7 @@ interface FieldProps extends React$1.ComponentProps<'div'> {
|
|
|
47
51
|
inputStyle?: SparkInputStyle;
|
|
48
52
|
inputColor?: SparkInputColor;
|
|
49
53
|
readOnly?: boolean;
|
|
54
|
+
'data-invalid'?: boolean | 'true' | 'false';
|
|
50
55
|
}
|
|
51
56
|
declare function Field({
|
|
52
57
|
className,
|
|
@@ -55,6 +60,7 @@ declare function Field({
|
|
|
55
60
|
inputStyle: inputStyleProp,
|
|
56
61
|
inputColor: inputColorProp,
|
|
57
62
|
readOnly,
|
|
63
|
+
'data-invalid': dataInvalid,
|
|
58
64
|
...props
|
|
59
65
|
}: FieldProps): import("react/jsx-runtime").JSX.Element;
|
|
60
66
|
declare function FieldContent({
|
|
@@ -109,6 +115,7 @@ declare function FieldError({
|
|
|
109
115
|
className,
|
|
110
116
|
children,
|
|
111
117
|
errors,
|
|
118
|
+
id,
|
|
112
119
|
...props
|
|
113
120
|
}: React$1.ComponentProps<'div'> & {
|
|
114
121
|
errors?: Array<{
|
package/dist/components/field.js
CHANGED
|
@@ -16,12 +16,21 @@ function useFieldContext() {
|
|
|
16
16
|
function useFieldControl(options) {
|
|
17
17
|
const field = useFieldContext();
|
|
18
18
|
const theme = useTheme();
|
|
19
|
+
const id = options?.id ?? field?.id;
|
|
20
|
+
const ariaDescribedBy = [
|
|
21
|
+
field?.hasError ? field.errorId : void 0,
|
|
22
|
+
field?.hasDescription ? field.descriptionId : void 0,
|
|
23
|
+
options?.ariaDescribedBy
|
|
24
|
+
].filter(Boolean).join(" ") || void 0;
|
|
25
|
+
const inputStyle = options?.variant ?? field?.inputStyle ?? theme.inputStyle ?? "soft";
|
|
26
|
+
const inputColor = field?.inputColor ?? theme.inputColor ?? "brand";
|
|
19
27
|
return {
|
|
20
28
|
field,
|
|
21
|
-
id
|
|
22
|
-
ariaDescribedBy
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
id,
|
|
30
|
+
ariaDescribedBy,
|
|
31
|
+
ariaInvalid: field?.hasError || void 0,
|
|
32
|
+
inputStyle,
|
|
33
|
+
inputColor
|
|
25
34
|
};
|
|
26
35
|
}
|
|
27
36
|
function FieldSet({ className, ...props }) {
|
|
@@ -117,9 +126,14 @@ const fieldVariants = cva("group/field relative flex w-full flex-col data-[inval
|
|
|
117
126
|
}
|
|
118
127
|
]
|
|
119
128
|
});
|
|
120
|
-
function Field({ className, children, labelPosition: labelPositionProp, inputStyle: inputStyleProp, inputColor: inputColorProp, readOnly = false, ...props }) {
|
|
129
|
+
function Field({ className, children, labelPosition: labelPositionProp, inputStyle: inputStyleProp, inputColor: inputColorProp, readOnly = false, "data-invalid": dataInvalid, ...props }) {
|
|
121
130
|
const id = useId();
|
|
122
|
-
const [
|
|
131
|
+
const [descriptionCount, setDescriptionCount] = React$1.useState(0);
|
|
132
|
+
const [errorCount, setErrorCount] = React$1.useState(0);
|
|
133
|
+
const setHasDescription = React$1.useCallback((has) => setDescriptionCount((n) => n + (has ? 1 : -1)), []);
|
|
134
|
+
const setHasError = React$1.useCallback((has) => setErrorCount((n) => n + (has ? 1 : -1)), []);
|
|
135
|
+
const hasDescription = descriptionCount > 0;
|
|
136
|
+
const hasError = errorCount > 0;
|
|
123
137
|
const theme = useTheme();
|
|
124
138
|
const labelPosition = labelPositionProp ?? theme.labelPosition ?? "inset";
|
|
125
139
|
const inputStyle = inputStyleProp ?? theme.inputStyle ?? "soft";
|
|
@@ -132,6 +146,9 @@ function Field({ className, children, labelPosition: labelPositionProp, inputSty
|
|
|
132
146
|
descriptionId: `${fieldId}-description`,
|
|
133
147
|
hasDescription,
|
|
134
148
|
setHasDescription,
|
|
149
|
+
errorId: `${fieldId}-error`,
|
|
150
|
+
hasError,
|
|
151
|
+
setHasError,
|
|
135
152
|
readOnly,
|
|
136
153
|
labelPosition,
|
|
137
154
|
inputStyle,
|
|
@@ -140,6 +157,9 @@ function Field({ className, children, labelPosition: labelPositionProp, inputSty
|
|
|
140
157
|
fieldId,
|
|
141
158
|
labelId,
|
|
142
159
|
hasDescription,
|
|
160
|
+
setHasDescription,
|
|
161
|
+
hasError,
|
|
162
|
+
setHasError,
|
|
143
163
|
readOnly,
|
|
144
164
|
labelPosition,
|
|
145
165
|
inputStyle,
|
|
@@ -153,6 +173,7 @@ function Field({ className, children, labelPosition: labelPositionProp, inputSty
|
|
|
153
173
|
"data-label-position": labelPosition,
|
|
154
174
|
"data-input-style": inputStyle,
|
|
155
175
|
"data-input-color": inputColor,
|
|
176
|
+
"data-invalid": dataInvalid ?? (hasError || void 0),
|
|
156
177
|
"aria-labelledby": readOnly ? labelId : void 0,
|
|
157
178
|
className: cn(fieldVariants({
|
|
158
179
|
labelPosition,
|
|
@@ -249,7 +270,9 @@ function FieldSeparator({ children, className, ...props }) {
|
|
|
249
270
|
})]
|
|
250
271
|
});
|
|
251
272
|
}
|
|
252
|
-
function FieldError({ className, children, errors, ...props }) {
|
|
273
|
+
function FieldError({ className, children, errors, id, ...props }) {
|
|
274
|
+
const field = useFieldContext();
|
|
275
|
+
const setHasError = field?.setHasError;
|
|
253
276
|
const content = React$1.useMemo(() => {
|
|
254
277
|
if (children) return children;
|
|
255
278
|
if (!errors?.length) return null;
|
|
@@ -260,10 +283,22 @@ function FieldError({ className, children, errors, ...props }) {
|
|
|
260
283
|
children: unique.map((error, index) => error?.message && /* @__PURE__ */ jsx("li", { children: error.message }, index))
|
|
261
284
|
});
|
|
262
285
|
}, [children, errors]);
|
|
286
|
+
const hasContent = Boolean(content);
|
|
287
|
+
const hasCustomId = id != null;
|
|
288
|
+
React$1.useEffect(() => {
|
|
289
|
+
if (!setHasError || !hasContent || hasCustomId) return;
|
|
290
|
+
setHasError(true);
|
|
291
|
+
return () => setHasError(false);
|
|
292
|
+
}, [
|
|
293
|
+
setHasError,
|
|
294
|
+
hasContent,
|
|
295
|
+
hasCustomId
|
|
296
|
+
]);
|
|
263
297
|
if (!content) return null;
|
|
264
298
|
return /* @__PURE__ */ jsx("div", {
|
|
265
299
|
role: "alert",
|
|
266
300
|
"data-slot": "field-error",
|
|
301
|
+
id: id ?? field?.errorId,
|
|
267
302
|
className: cn("mt-0.5 text-sm font-normal text-error", className),
|
|
268
303
|
...props,
|
|
269
304
|
children: content
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field.js","names":["React"],"sources":["../../src/components/field.tsx"],"sourcesContent":["'use client'\n\nimport { cva } from 'class-variance-authority'\nimport * as React from 'react'\nimport { useId } from 'react'\nimport type {\n SparkInputColor,\n SparkInputStyle,\n SparkLabelPosition,\n} from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Separator } from './separator'\nimport { Skeleton } from './skeleton'\nimport { useTheme } from './theme'\n\ntype FieldContextValue = {\n id: string\n labelId: string\n descriptionId: string\n hasDescription: boolean\n setHasDescription: (has: boolean) => void\n readOnly: boolean\n labelPosition: SparkLabelPosition\n inputStyle: SparkInputStyle\n inputColor: SparkInputColor\n}\n\nconst FieldContext = React.createContext<FieldContextValue | null>(null)\n\nexport function useFieldContext() {\n return React.useContext(FieldContext)\n}\n\n// Field→Theme→default resolution + id/aria-describedby wiring, shared by every\n// control that can sit inside a Field (Input, SelectTrigger, …) so they can't\n// drift on how a style/color/id resolves. `variant` is the control's own\n// per-instance override (already normalized — e.g. Input maps its 'none' to\n// undefined before calling this).\nexport function useFieldControl(options?: {\n id?: string\n ariaDescribedBy?: string\n variant?: SparkInputStyle\n}) {\n const field = useFieldContext()\n const theme = useTheme()\n const id = options?.id ?? field?.id\n const descriptionId = field?.hasDescription ? field.descriptionId : undefined\n const ariaDescribedBy =\n [descriptionId, options?.ariaDescribedBy].filter(Boolean).join(' ') ||\n undefined\n const inputStyle =\n options?.variant ?? field?.inputStyle ?? theme.inputStyle ?? 'soft'\n const inputColor = field?.inputColor ?? theme.inputColor ?? 'brand'\n return { field, id, ariaDescribedBy, inputStyle, inputColor }\n}\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n return (\n <fieldset\n data-slot=\"field-set\"\n className={cn(\n 'flex flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldLegend({\n className,\n variant = 'legend',\n ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n return (\n <legend\n data-slot=\"field-legend\"\n data-variant={variant}\n className={cn(\n 'mb-1.5 font-medium text-gray data-[variant=label]:text-sm data-[variant=legend]:text-base',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-group\"\n className={cn(\n 'group/field-group flex w-full flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldVariants = cva(\n 'group/field relative flex w-full flex-col data-[invalid=true]:text-error-active',\n {\n variants: {\n labelPosition: {\n // inset puts label + input inside one shell, so the shell is the box\n // focus inside inset is the classic underline cue, not a shell ring\n inset: 'gap-y-1 rounded-lg px-4 py-3',\n outset: 'gap-y-2',\n },\n inputStyle: { soft: '', surface: '', outlined: '', ghost: '' },\n inputColor: { brand: '', gray: '' },\n },\n compoundVariants: [\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'brand',\n class:\n 'bg-input-base has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-input-base-hover',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'gray',\n class:\n 'bg-base-gray-soft has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-gray-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'brand',\n class: 'border border-input bg-base-accent-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'gray',\n class: 'border border-input-gray bg-base-gray-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'brand',\n class: 'border border-input',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'gray',\n class: 'border border-input-gray',\n },\n // ghost inset = bare label + underlined input; the input carries the line\n {\n labelPosition: 'inset',\n inputStyle: 'ghost',\n class: 'rounded-none px-0',\n },\n // shared inset states: filled shells swap bg, bordered shells swap border\n // invalid hover deepens the error tint instead of falling back to accent\n {\n labelPosition: 'inset',\n inputStyle: ['soft', 'surface'],\n class:\n 'data-[disabled=true]:bg-base-gray-soft data-[invalid=true]:bg-base-error-soft data-[invalid=true]:has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-red-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: ['surface', 'outlined'],\n class:\n 'data-[disabled=true]:border-input-disabled data-[invalid=true]:border-input-invalid',\n },\n ],\n },\n)\n\ninterface FieldProps extends React.ComponentProps<'div'> {\n labelPosition?: SparkLabelPosition\n inputStyle?: SparkInputStyle\n inputColor?: SparkInputColor\n readOnly?: boolean\n}\n\nfunction Field({\n className,\n children,\n labelPosition: labelPositionProp,\n inputStyle: inputStyleProp,\n inputColor: inputColorProp,\n readOnly = false,\n ...props\n}: FieldProps) {\n const id = useId()\n const [hasDescription, setHasDescription] = React.useState(false)\n // knobs are structural (they move the label / pick the box carrier), so they\n // resolve here via the <Theme> context; props override per instance\n const theme = useTheme()\n const labelPosition = labelPositionProp ?? theme.labelPosition ?? 'inset'\n const inputStyle = inputStyleProp ?? theme.inputStyle ?? 'soft'\n const inputColor = inputColorProp ?? theme.inputColor ?? 'brand'\n const fieldId = `field-${id}`\n const labelId = `${fieldId}-label`\n const value = React.useMemo<FieldContextValue>(\n () => ({\n id: fieldId,\n labelId,\n descriptionId: `${fieldId}-description`,\n hasDescription,\n setHasDescription,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n }),\n [\n fieldId,\n labelId,\n hasDescription,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n ],\n )\n\n return (\n <FieldContext.Provider value={value}>\n <div\n role=\"group\"\n data-slot=\"field\"\n data-label-position={labelPosition}\n data-input-style={inputStyle}\n data-input-color={inputColor}\n aria-labelledby={readOnly ? labelId : undefined}\n className={cn(\n fieldVariants({ labelPosition, inputStyle, inputColor }),\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </FieldContext.Provider>\n )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-0.5 leading-snug',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldLabelClassName =\n 'text-sm leading-5 font-normal text-gray-light transition-all group-data-[invalid=true]/field:text-error-active! motion-reduce:transition-none'\n\nfunction FieldLabel({\n className,\n htmlFor,\n srOnly,\n children,\n ...props\n}: React.ComponentProps<'label'> & { srOnly?: boolean }) {\n const field = useFieldContext()\n const classNames = cn(\n // labels stay full-contrast even when disabled — the control itself\n // (dimmed text, gray shell) signals the state\n fieldLabelClassName,\n // visually hidden but kept in the a11y tree (still names + focuses the control)\n srOnly && 'sr-only',\n className,\n )\n\n if (field?.readOnly) {\n return (\n <div data-slot=\"field-label\" id={field.labelId} className={classNames}>\n {children}\n </div>\n )\n }\n\n return (\n <label\n data-slot=\"field-label\"\n className={classNames}\n htmlFor={htmlFor ?? field?.id}\n {...props}\n >\n {children}\n </label>\n )\n}\n\nfunction FieldValue({\n className,\n id,\n title,\n truncate,\n children,\n ...props\n}: React.ComponentProps<'div'> & { truncate?: boolean }) {\n return (\n <div\n data-slot=\"field-value\"\n id={id}\n // truncated string values keep the full text reachable on hover\n title={\n title ??\n (truncate && typeof children === 'string' ? children : undefined)\n }\n className={cn(\n // values are content, not controls — disabled dims but stays ≥4.5:1\n 'min-h-8 w-full text-base text-gray tabular-nums group-data-[disabled=true]/field:opacity-70',\n truncate\n ? 'block truncate leading-8'\n : 'inline-flex items-center wrap-anywhere whitespace-pre-wrap',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nfunction FieldSkeleton({\n className,\n labelClassName,\n valueClassName,\n truncate,\n ...props\n}: React.ComponentProps<'div'> & {\n truncate?: boolean\n labelClassName?: string\n valueClassName?: string\n}) {\n return (\n <div\n data-slot=\"field-skeleton\"\n aria-hidden=\"true\"\n className={cn('flex w-full flex-col gap-y-1', className)}\n {...props}\n >\n <Skeleton\n data-slot=\"field-skeleton-label\"\n className={cn('h-5 w-24', labelClassName)}\n />\n <Skeleton\n data-slot=\"field-skeleton-value\"\n className={cn(\n 'h-8 min-h-8 w-full',\n truncate && 'max-w-full truncate',\n valueClassName,\n )}\n />\n </div>\n )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-title\"\n className={cn(\n 'flex w-fit items-center gap-2 text-sm font-medium text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldDescription({\n className,\n id,\n ...props\n}: React.ComponentProps<'div'>) {\n const field = useFieldContext()\n const setHasDescription = field?.setHasDescription\n\n React.useEffect(() => {\n if (!setHasDescription) return\n setHasDescription(true)\n return () => setHasDescription(false)\n }, [setHasDescription])\n\n return (\n <div\n data-slot=\"field-description\"\n id={id ?? field?.descriptionId}\n className={cn('mt-0.5 text-sm leading-normal text-gray-light', className)}\n {...props}\n />\n )\n}\n\nfunction FieldSeparator({\n children,\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-separator\"\n data-content={!!children}\n className={cn('relative -my-2 h-5 text-sm', className)}\n {...props}\n >\n <Separator className=\"absolute inset-0 top-1/2\" />\n {children && (\n <span\n className=\"relative mx-auto block w-fit bg-(--spark-background) px-2 text-gray-light\"\n data-slot=\"field-separator-content\"\n >\n {children}\n </span>\n )}\n </div>\n )\n}\n\nfunction FieldError({\n className,\n children,\n errors,\n ...props\n}: React.ComponentProps<'div'> & {\n errors?: Array<{ message?: string } | undefined>\n}) {\n const content = React.useMemo(() => {\n if (children) return children\n if (!errors?.length) return null\n const unique = [\n ...new Map(errors.map((error) => [error?.message, error])).values(),\n ]\n if (unique.length === 1) return unique[0]?.message\n return (\n <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n {unique.map(\n (error, index) =>\n error?.message && <li key={index}>{error.message}</li>,\n )}\n </ul>\n )\n }, [children, errors])\n\n if (!content) return null\n\n return (\n <div\n role=\"alert\"\n data-slot=\"field-error\"\n className={cn('mt-0.5 text-sm font-normal text-error', className)}\n {...props}\n >\n {content}\n </div>\n )\n}\n\nexport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n FieldLegend,\n FieldSeparator,\n FieldSet,\n FieldSkeleton,\n FieldTitle,\n FieldValue,\n}\nexport type { FieldProps }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"field.js","names":["React"],"sources":["../../src/components/field.tsx"],"sourcesContent":["'use client'\n\nimport { cva } from 'class-variance-authority'\nimport * as React from 'react'\nimport { useId } from 'react'\nimport type {\n SparkInputColor,\n SparkInputStyle,\n SparkLabelPosition,\n} from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Separator } from './separator'\nimport { Skeleton } from './skeleton'\nimport { useTheme } from './theme'\n\ntype FieldContextValue = {\n id: string\n labelId: string\n descriptionId: string\n hasDescription: boolean\n setHasDescription: (has: boolean) => void\n errorId: string\n hasError: boolean\n setHasError: (has: boolean) => void\n readOnly: boolean\n labelPosition: SparkLabelPosition\n inputStyle: SparkInputStyle\n inputColor: SparkInputColor\n}\n\nconst FieldContext = React.createContext<FieldContextValue | null>(null)\n\nexport function useFieldContext() {\n return React.useContext(FieldContext)\n}\n\n// Field→Theme→default resolution + id/aria-describedby wiring, shared by every\n// control that can sit inside a Field (Input, SelectTrigger, …) so they can't\n// drift on how a style/color/id resolves. `variant` is the control's own\n// per-instance override (already normalized — e.g. Input maps its 'none' to\n// undefined before calling this).\nexport function useFieldControl(options?: {\n id?: string\n ariaDescribedBy?: string\n variant?: SparkInputStyle\n}) {\n const field = useFieldContext()\n const theme = useTheme()\n const id = options?.id ?? field?.id\n const errorId = field?.hasError ? field.errorId : undefined\n const descriptionId = field?.hasDescription ? field.descriptionId : undefined\n // error before hint: the actionable message should be announced first\n const ariaDescribedBy =\n [errorId, descriptionId, options?.ariaDescribedBy]\n .filter(Boolean)\n .join(' ') || undefined\n const inputStyle =\n options?.variant ?? field?.inputStyle ?? theme.inputStyle ?? 'soft'\n const inputColor = field?.inputColor ?? theme.inputColor ?? 'brand'\n const ariaInvalid = field?.hasError || undefined\n return { field, id, ariaDescribedBy, ariaInvalid, inputStyle, inputColor }\n}\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n return (\n <fieldset\n data-slot=\"field-set\"\n className={cn(\n 'flex flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldLegend({\n className,\n variant = 'legend',\n ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n return (\n <legend\n data-slot=\"field-legend\"\n data-variant={variant}\n className={cn(\n 'mb-1.5 font-medium text-gray data-[variant=label]:text-sm data-[variant=legend]:text-base',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-group\"\n className={cn(\n 'group/field-group flex w-full flex-col gap-[calc(var(--spark-layout-gap)*4)]',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldVariants = cva(\n 'group/field relative flex w-full flex-col data-[invalid=true]:text-error-active',\n {\n variants: {\n labelPosition: {\n // inset puts label + input inside one shell, so the shell is the box\n // focus inside inset is the classic underline cue, not a shell ring\n inset: 'gap-y-1 rounded-lg px-4 py-3',\n outset: 'gap-y-2',\n },\n inputStyle: { soft: '', surface: '', outlined: '', ghost: '' },\n inputColor: { brand: '', gray: '' },\n },\n compoundVariants: [\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'brand',\n class:\n 'bg-input-base has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-input-base-hover',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'soft',\n inputColor: 'gray',\n class:\n 'bg-base-gray-soft has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-gray-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'brand',\n class: 'border border-input bg-base-accent-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'surface',\n inputColor: 'gray',\n class: 'border border-input-gray bg-base-gray-surface',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'brand',\n class: 'border border-input',\n },\n {\n labelPosition: 'inset',\n inputStyle: 'outlined',\n inputColor: 'gray',\n class: 'border border-input-gray',\n },\n // ghost inset = bare label + underlined input; the input carries the line\n {\n labelPosition: 'inset',\n inputStyle: 'ghost',\n class: 'rounded-none px-0',\n },\n // shared inset states: filled shells swap bg, bordered shells swap border\n // invalid hover deepens the error tint instead of falling back to accent\n {\n labelPosition: 'inset',\n inputStyle: ['soft', 'surface'],\n class:\n 'data-[disabled=true]:bg-base-gray-soft data-[invalid=true]:bg-base-error-soft data-[invalid=true]:has-[:is(input,textarea,[data-slot=select-trigger]):not(:disabled)]:hover:bg-red-a4',\n },\n {\n labelPosition: 'inset',\n inputStyle: ['surface', 'outlined'],\n class:\n 'data-[disabled=true]:border-input-disabled data-[invalid=true]:border-input-invalid',\n },\n ],\n },\n)\n\ninterface FieldProps extends React.ComponentProps<'div'> {\n labelPosition?: SparkLabelPosition\n inputStyle?: SparkInputStyle\n inputColor?: SparkInputColor\n readOnly?: boolean\n 'data-invalid'?: boolean | 'true' | 'false'\n}\n\nfunction Field({\n className,\n children,\n labelPosition: labelPositionProp,\n inputStyle: inputStyleProp,\n inputColor: inputColorProp,\n readOnly = false,\n 'data-invalid': dataInvalid,\n ...props\n}: FieldProps) {\n const id = useId()\n const [descriptionCount, setDescriptionCount] = React.useState(0)\n const [errorCount, setErrorCount] = React.useState(0)\n // counts, not flags: one slot unmounting must not clear what siblings still need\n const setHasDescription = React.useCallback(\n (has: boolean) => setDescriptionCount((n) => n + (has ? 1 : -1)),\n [],\n )\n const setHasError = React.useCallback(\n (has: boolean) => setErrorCount((n) => n + (has ? 1 : -1)),\n [],\n )\n const hasDescription = descriptionCount > 0\n const hasError = errorCount > 0\n // knobs are structural (they move the label / pick the box carrier), so they\n // resolve here via the <Theme> context; props override per instance\n const theme = useTheme()\n const labelPosition = labelPositionProp ?? theme.labelPosition ?? 'inset'\n const inputStyle = inputStyleProp ?? theme.inputStyle ?? 'soft'\n const inputColor = inputColorProp ?? theme.inputColor ?? 'brand'\n const fieldId = `field-${id}`\n const labelId = `${fieldId}-label`\n const value = React.useMemo<FieldContextValue>(\n () => ({\n id: fieldId,\n labelId,\n descriptionId: `${fieldId}-description`,\n hasDescription,\n setHasDescription,\n errorId: `${fieldId}-error`,\n hasError,\n setHasError,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n }),\n [\n fieldId,\n labelId,\n hasDescription,\n setHasDescription,\n hasError,\n setHasError,\n readOnly,\n labelPosition,\n inputStyle,\n inputColor,\n ],\n )\n\n return (\n <FieldContext.Provider value={value}>\n <div\n role=\"group\"\n data-slot=\"field\"\n data-label-position={labelPosition}\n data-input-style={inputStyle}\n data-input-color={inputColor}\n data-invalid={dataInvalid ?? (hasError || undefined)}\n aria-labelledby={readOnly ? labelId : undefined}\n className={cn(\n fieldVariants({ labelPosition, inputStyle, inputColor }),\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </FieldContext.Provider>\n )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-0.5 leading-snug',\n className,\n )}\n {...props}\n />\n )\n}\n\nconst fieldLabelClassName =\n 'text-sm leading-5 font-normal text-gray-light transition-all group-data-[invalid=true]/field:text-error-active! motion-reduce:transition-none'\n\nfunction FieldLabel({\n className,\n htmlFor,\n srOnly,\n children,\n ...props\n}: React.ComponentProps<'label'> & { srOnly?: boolean }) {\n const field = useFieldContext()\n const classNames = cn(\n // labels stay full-contrast even when disabled — the control itself\n // (dimmed text, gray shell) signals the state\n fieldLabelClassName,\n // visually hidden but kept in the a11y tree (still names + focuses the control)\n srOnly && 'sr-only',\n className,\n )\n\n if (field?.readOnly) {\n return (\n <div data-slot=\"field-label\" id={field.labelId} className={classNames}>\n {children}\n </div>\n )\n }\n\n return (\n <label\n data-slot=\"field-label\"\n className={classNames}\n htmlFor={htmlFor ?? field?.id}\n {...props}\n >\n {children}\n </label>\n )\n}\n\nfunction FieldValue({\n className,\n id,\n title,\n truncate,\n children,\n ...props\n}: React.ComponentProps<'div'> & { truncate?: boolean }) {\n return (\n <div\n data-slot=\"field-value\"\n id={id}\n // truncated string values keep the full text reachable on hover\n title={\n title ??\n (truncate && typeof children === 'string' ? children : undefined)\n }\n className={cn(\n // values are content, not controls — disabled dims but stays ≥4.5:1\n 'min-h-8 w-full text-base text-gray tabular-nums group-data-[disabled=true]/field:opacity-70',\n truncate\n ? 'block truncate leading-8'\n : 'inline-flex items-center wrap-anywhere whitespace-pre-wrap',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n )\n}\n\nfunction FieldSkeleton({\n className,\n labelClassName,\n valueClassName,\n truncate,\n ...props\n}: React.ComponentProps<'div'> & {\n truncate?: boolean\n labelClassName?: string\n valueClassName?: string\n}) {\n return (\n <div\n data-slot=\"field-skeleton\"\n aria-hidden=\"true\"\n className={cn('flex w-full flex-col gap-y-1', className)}\n {...props}\n >\n <Skeleton\n data-slot=\"field-skeleton-label\"\n className={cn('h-5 w-24', labelClassName)}\n />\n <Skeleton\n data-slot=\"field-skeleton-value\"\n className={cn(\n 'h-8 min-h-8 w-full',\n truncate && 'max-w-full truncate',\n valueClassName,\n )}\n />\n </div>\n )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-title\"\n className={cn(\n 'flex w-fit items-center gap-2 text-sm font-medium text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction FieldDescription({\n className,\n id,\n ...props\n}: React.ComponentProps<'div'>) {\n const field = useFieldContext()\n const setHasDescription = field?.setHasDescription\n\n React.useEffect(() => {\n if (!setHasDescription) return\n setHasDescription(true)\n return () => setHasDescription(false)\n }, [setHasDescription])\n\n return (\n <div\n data-slot=\"field-description\"\n id={id ?? field?.descriptionId}\n className={cn('mt-0.5 text-sm leading-normal text-gray-light', className)}\n {...props}\n />\n )\n}\n\nfunction FieldSeparator({\n children,\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"field-separator\"\n data-content={!!children}\n className={cn('relative -my-2 h-5 text-sm', className)}\n {...props}\n >\n <Separator className=\"absolute inset-0 top-1/2\" />\n {children && (\n <span\n className=\"relative mx-auto block w-fit bg-(--spark-background) px-2 text-gray-light\"\n data-slot=\"field-separator-content\"\n >\n {children}\n </span>\n )}\n </div>\n )\n}\n\nfunction FieldError({\n className,\n children,\n errors,\n id,\n ...props\n}: React.ComponentProps<'div'> & {\n errors?: Array<{ message?: string } | undefined>\n}) {\n const field = useFieldContext()\n const setHasError = field?.setHasError\n const content = React.useMemo(() => {\n if (children) return children\n if (!errors?.length) return null\n const unique = [\n ...new Map(errors.map((error) => [error?.message, error])).values(),\n ]\n if (unique.length === 1) return unique[0]?.message\n return (\n <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n {unique.map(\n (error, index) =>\n error?.message && <li key={index}>{error.message}</li>,\n )}\n </ul>\n )\n }, [children, errors])\n\n const hasContent = Boolean(content)\n // a custom id means no element carries field.errorId, so don't advertise it\n const hasCustomId = id != null\n\n // must run before the empty-content early return to keep hook order stable\n React.useEffect(() => {\n if (!setHasError || !hasContent || hasCustomId) return\n setHasError(true)\n return () => setHasError(false)\n }, [setHasError, hasContent, hasCustomId])\n\n if (!content) return null\n\n return (\n <div\n role=\"alert\"\n data-slot=\"field-error\"\n id={id ?? field?.errorId}\n className={cn('mt-0.5 text-sm font-normal text-error', className)}\n {...props}\n >\n {content}\n </div>\n )\n}\n\nexport {\n Field,\n FieldContent,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLabel,\n FieldLegend,\n FieldSeparator,\n FieldSet,\n FieldSkeleton,\n FieldTitle,\n FieldValue,\n}\nexport type { FieldProps }\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ declare function InputGroup({
|
|
|
10
10
|
...props
|
|
11
11
|
}: React$1.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
12
12
|
declare const inputGroupAddonVariants: (props?: ({
|
|
13
|
-
align?: "inline-
|
|
13
|
+
align?: "inline-end" | "inline-start" | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
15
|
declare function InputGroupAddon({
|
|
16
16
|
className,
|
|
@@ -10,7 +10,7 @@ declare function InputGroup({
|
|
|
10
10
|
...props
|
|
11
11
|
}: React$1.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
12
12
|
declare const inputGroupAddonVariants: (props?: ({
|
|
13
|
-
align?: "inline-
|
|
13
|
+
align?: "inline-end" | "inline-start" | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
15
|
declare function InputGroupAddon({
|
|
16
16
|
className,
|
|
@@ -86,7 +86,7 @@ const slotVariants = (0, class_variance_authority.cva)("flex w-11 min-w-0 items-
|
|
|
86
86
|
]
|
|
87
87
|
});
|
|
88
88
|
function InputOtp({ length = 6, className, id: idProp, onComplete, slotAriaLabel = (index) => `Character ${index + 1}`, validationType = "alphanumeric", ...props }) {
|
|
89
|
-
const { field, id, ariaDescribedBy, inputStyle, inputColor } = require_components_field.useFieldControl({ id: idProp });
|
|
89
|
+
const { field, id, ariaDescribedBy, ariaInvalid, inputStyle, inputColor } = require_components_field.useFieldControl({ id: idProp });
|
|
90
90
|
const chrome = field?.labelPosition === "inset" ? "inset" : "box";
|
|
91
91
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_otp_field.OTPField.Root, {
|
|
92
92
|
"data-slot": "input-otp",
|
|
@@ -99,6 +99,7 @@ function InputOtp({ length = 6, className, id: idProp, onComplete, slotAriaLabel
|
|
|
99
99
|
children: Array.from({ length }, (_, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_otp_field.OTPField.Input, {
|
|
100
100
|
"aria-label": index === 0 ? void 0 : slotAriaLabel(index),
|
|
101
101
|
"aria-describedby": index === 0 ? ariaDescribedBy : void 0,
|
|
102
|
+
"aria-invalid": index === 0 ? ariaInvalid : void 0,
|
|
102
103
|
className: slotVariants({
|
|
103
104
|
chrome,
|
|
104
105
|
inputStyle,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-otp.cjs","names":["GHOST_UNDERLINE","boxChrome","useFieldControl","OTPField"],"sources":["../../src/components/input-otp.tsx"],"sourcesContent":["import { OTPField, type OTPFieldRootProps } from '@base-ui/react/otp-field'\nimport { cva } from 'class-variance-authority'\nimport { boxChrome, GHOST_UNDERLINE } from '../lib/input-box-variants'\nimport { cn } from '../utils/cn'\nimport { useFieldControl } from './field'\n\n// One code box per slot. Mirrors Input's box/underline carrier model so the\n// slots read like the rest of the input family across every style/color knob.\nconst slotVariants = cva(\n 'flex w-11 min-w-0 items-center justify-center bg-transparent text-center text-base font-medium text-gray tabular-nums outline-hidden transition-all group-data-[invalid=true]/field:text-error placeholder:text-gray-disabled disabled:cursor-not-allowed disabled:text-gray-disabled aria-invalid:text-error motion-reduce:transition-none',\n {\n variants: {\n chrome: {\n // inside an inset field the shell is the box; slots keep the classic\n // underline (the pre-knob default look)\n inset: GHOST_UNDERLINE,\n box: 'h-11 rounded-lg',\n },\n inputStyle: { soft: '', surface: '', outlined: '', ghost: '' },\n inputColor: { brand: '', gray: '' },\n },\n compoundVariants: [\n {\n chrome: 'box',\n inputStyle: 'soft',\n inputColor: 'brand',\n class: boxChrome.soft.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'soft',\n inputColor: 'gray',\n class: boxChrome.soft.gray,\n },\n {\n chrome: 'box',\n inputStyle: 'surface',\n inputColor: 'brand',\n class: boxChrome.surface.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'surface',\n inputColor: 'gray',\n class: boxChrome.surface.gray,\n },\n {\n chrome: 'box',\n inputStyle: 'outlined',\n inputColor: 'brand',\n class: boxChrome.outlined.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'outlined',\n inputColor: 'gray',\n class: boxChrome.outlined.gray,\n },\n {\n chrome: 'box',\n inputStyle: 'ghost',\n inputColor: 'brand',\n class: boxChrome.ghost.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'ghost',\n inputColor: 'gray',\n class: boxChrome.ghost.gray,\n },\n { chrome: 'inset', inputColor: 'brand', class: 'border-b-input' },\n { chrome: 'inset', inputColor: 'gray', class: 'border-b-input-gray' },\n ],\n },\n)\n\ninterface InputOtpProps extends Pick<\n OTPFieldRootProps,\n | 'value'\n | 'defaultValue'\n | 'onValueChange'\n | 'disabled'\n | 'className'\n | 'id'\n | 'validationType'\n> {\n /** Number of code slots. */\n length?: number\n /** Fired when every slot is filled. */\n onComplete?: OTPFieldRootProps['onValueComplete']\n /** Accessible name per slot (for localization). */\n slotAriaLabel?: (index: number) => string\n}\n\nfunction InputOtp({\n length = 6,\n className,\n id: idProp,\n onComplete,\n slotAriaLabel = (index) => `Character ${index + 1}`,\n validationType = 'alphanumeric',\n ...props\n}: InputOtpProps) {\n const { field, id, ariaDescribedBy, inputStyle, inputColor } =\n useFieldControl({ id: idProp })\n const chrome = field?.labelPosition === 'inset' ? 'inset' : 'box'\n\n return (\n <OTPField.Root\n data-slot=\"input-otp\"\n length={length}\n id={id}\n validationType={validationType}\n onValueComplete={onComplete}\n className={cn('flex items-center gap-2', className)}\n {...props}\n >\n {Array.from({ length }, (_, index) => (\n <OTPField.Input\n key={index}\n // the first slot is named by the Field label (Base UI ignores\n // aria-label there); the rest get a positional name for a11y\n aria-label={index === 0 ? undefined : slotAriaLabel(index)}\n aria-describedby={index === 0 ? ariaDescribedBy : undefined}\n className={slotVariants({ chrome, inputStyle, inputColor })}\n />\n ))}\n </OTPField.Root>\n )\n}\n\nexport { InputOtp }\nexport type { InputOtpProps }\n"],"mappings":";;;;;;;;;AAQA,MAAM,gBAAA,GAAA,yBAAA,KACJ,+UACA;CACE,UAAU;EACR,QAAQ;GAGN,OAAOA,+BAAAA;GACP,KAAK;EACP;EACA,YAAY;GAAE,MAAM;GAAI,SAAS;GAAI,UAAU;GAAI,OAAO;EAAG;EAC7D,YAAY;GAAE,OAAO;GAAI,MAAM;EAAG;CACpC;CACA,kBAAkB;EAChB;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOC,+BAAAA,UAAU,KAAK;EACxB;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,KAAK;EACxB;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,QAAQ;EAC3B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,QAAQ;EAC3B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,SAAS;EAC5B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,SAAS;EAC5B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,MAAM;EACzB;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,MAAM;EACzB;EACA;GAAE,QAAQ;GAAS,YAAY;GAAS,OAAO;EAAiB;EAChE;GAAE,QAAQ;GAAS,YAAY;GAAQ,OAAO;EAAsB;CACtE;AACF,CACF;AAoBA,SAAS,SAAS,EAChB,SAAS,GACT,WACA,IAAI,QACJ,YACA,iBAAiB,UAAU,aAAa,QAAQ,KAChD,iBAAiB,gBACjB,GAAG,SACa;CAChB,MAAM,EAAE,OAAO,IAAI,iBAAiB,YAAY,
|
|
1
|
+
{"version":3,"file":"input-otp.cjs","names":["GHOST_UNDERLINE","boxChrome","useFieldControl","OTPField"],"sources":["../../src/components/input-otp.tsx"],"sourcesContent":["import { OTPField, type OTPFieldRootProps } from '@base-ui/react/otp-field'\nimport { cva } from 'class-variance-authority'\nimport { boxChrome, GHOST_UNDERLINE } from '../lib/input-box-variants'\nimport { cn } from '../utils/cn'\nimport { useFieldControl } from './field'\n\n// One code box per slot. Mirrors Input's box/underline carrier model so the\n// slots read like the rest of the input family across every style/color knob.\nconst slotVariants = cva(\n 'flex w-11 min-w-0 items-center justify-center bg-transparent text-center text-base font-medium text-gray tabular-nums outline-hidden transition-all group-data-[invalid=true]/field:text-error placeholder:text-gray-disabled disabled:cursor-not-allowed disabled:text-gray-disabled aria-invalid:text-error motion-reduce:transition-none',\n {\n variants: {\n chrome: {\n // inside an inset field the shell is the box; slots keep the classic\n // underline (the pre-knob default look)\n inset: GHOST_UNDERLINE,\n box: 'h-11 rounded-lg',\n },\n inputStyle: { soft: '', surface: '', outlined: '', ghost: '' },\n inputColor: { brand: '', gray: '' },\n },\n compoundVariants: [\n {\n chrome: 'box',\n inputStyle: 'soft',\n inputColor: 'brand',\n class: boxChrome.soft.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'soft',\n inputColor: 'gray',\n class: boxChrome.soft.gray,\n },\n {\n chrome: 'box',\n inputStyle: 'surface',\n inputColor: 'brand',\n class: boxChrome.surface.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'surface',\n inputColor: 'gray',\n class: boxChrome.surface.gray,\n },\n {\n chrome: 'box',\n inputStyle: 'outlined',\n inputColor: 'brand',\n class: boxChrome.outlined.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'outlined',\n inputColor: 'gray',\n class: boxChrome.outlined.gray,\n },\n {\n chrome: 'box',\n inputStyle: 'ghost',\n inputColor: 'brand',\n class: boxChrome.ghost.brand,\n },\n {\n chrome: 'box',\n inputStyle: 'ghost',\n inputColor: 'gray',\n class: boxChrome.ghost.gray,\n },\n { chrome: 'inset', inputColor: 'brand', class: 'border-b-input' },\n { chrome: 'inset', inputColor: 'gray', class: 'border-b-input-gray' },\n ],\n },\n)\n\ninterface InputOtpProps extends Pick<\n OTPFieldRootProps,\n | 'value'\n | 'defaultValue'\n | 'onValueChange'\n | 'disabled'\n | 'className'\n | 'id'\n | 'validationType'\n> {\n /** Number of code slots. */\n length?: number\n /** Fired when every slot is filled. */\n onComplete?: OTPFieldRootProps['onValueComplete']\n /** Accessible name per slot (for localization). */\n slotAriaLabel?: (index: number) => string\n}\n\nfunction InputOtp({\n length = 6,\n className,\n id: idProp,\n onComplete,\n slotAriaLabel = (index) => `Character ${index + 1}`,\n validationType = 'alphanumeric',\n ...props\n}: InputOtpProps) {\n const { field, id, ariaDescribedBy, ariaInvalid, inputStyle, inputColor } =\n useFieldControl({ id: idProp })\n const chrome = field?.labelPosition === 'inset' ? 'inset' : 'box'\n\n return (\n <OTPField.Root\n data-slot=\"input-otp\"\n length={length}\n id={id}\n validationType={validationType}\n onValueComplete={onComplete}\n className={cn('flex items-center gap-2', className)}\n {...props}\n >\n {Array.from({ length }, (_, index) => (\n <OTPField.Input\n key={index}\n // the first slot is named by the Field label (Base UI ignores\n // aria-label there); the rest get a positional name for a11y\n aria-label={index === 0 ? undefined : slotAriaLabel(index)}\n aria-describedby={index === 0 ? ariaDescribedBy : undefined}\n aria-invalid={index === 0 ? ariaInvalid : undefined}\n className={slotVariants({ chrome, inputStyle, inputColor })}\n />\n ))}\n </OTPField.Root>\n )\n}\n\nexport { InputOtp }\nexport type { InputOtpProps }\n"],"mappings":";;;;;;;;;AAQA,MAAM,gBAAA,GAAA,yBAAA,KACJ,+UACA;CACE,UAAU;EACR,QAAQ;GAGN,OAAOA,+BAAAA;GACP,KAAK;EACP;EACA,YAAY;GAAE,MAAM;GAAI,SAAS;GAAI,UAAU;GAAI,OAAO;EAAG;EAC7D,YAAY;GAAE,OAAO;GAAI,MAAM;EAAG;CACpC;CACA,kBAAkB;EAChB;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOC,+BAAAA,UAAU,KAAK;EACxB;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,KAAK;EACxB;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,QAAQ;EAC3B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,QAAQ;EAC3B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,SAAS;EAC5B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,SAAS;EAC5B;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,MAAM;EACzB;EACA;GACE,QAAQ;GACR,YAAY;GACZ,YAAY;GACZ,OAAOA,+BAAAA,UAAU,MAAM;EACzB;EACA;GAAE,QAAQ;GAAS,YAAY;GAAS,OAAO;EAAiB;EAChE;GAAE,QAAQ;GAAS,YAAY;GAAQ,OAAO;EAAsB;CACtE;AACF,CACF;AAoBA,SAAS,SAAS,EAChB,SAAS,GACT,WACA,IAAI,QACJ,YACA,iBAAiB,UAAU,aAAa,QAAQ,KAChD,iBAAiB,gBACjB,GAAG,SACa;CAChB,MAAM,EAAE,OAAO,IAAI,iBAAiB,aAAa,YAAY,eAC3DC,yBAAAA,gBAAgB,EAAE,IAAI,OAAO,CAAC;CAChC,MAAM,SAAS,OAAO,kBAAkB,UAAU,UAAU;CAE5D,OACE,iBAAA,GAAA,kBAAA,KAACC,yBAAAA,SAAS,MAAV;EACE,aAAU;EACF;EACJ;EACY;EAChB,iBAAiB;EACjB,YAAA,GAAA,OAAA,IAAc,2BAA2B,SAAS;EAClD,GAAI;YAEH,MAAM,KAAK,EAAE,OAAO,IAAI,GAAG,UAC1B,iBAAA,GAAA,kBAAA,KAACA,yBAAAA,SAAS,OAAV;GAIE,cAAY,UAAU,IAAI,KAAA,IAAY,cAAc,KAAK;GACzD,oBAAkB,UAAU,IAAI,kBAAkB,KAAA;GAClD,gBAAc,UAAU,IAAI,cAAc,KAAA;GAC1C,WAAW,aAAa;IAAE;IAAQ;IAAY;GAAW,CAAC;EAC3D,GAPM,KAON,CACF;CACY,CAAA;AAEnB"}
|