@lerianstudio/sindarian-ui 2.0.0-beta.1 → 2.0.0-beta.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/form/file-upload-field/index.d.ts +11 -2
- package/dist/components/form/file-upload-field/index.d.ts.map +1 -1
- package/dist/components/form/file-upload-field/index.js +8 -3
- package/dist/components/form/input-field/index.d.ts +41 -2
- package/dist/components/form/input-field/index.d.ts.map +1 -1
- package/dist/components/form/input-field/index.js +73 -15
- package/dist/components/form/select-field/index.d.ts +9 -1
- package/dist/components/form/select-field/index.d.ts.map +1 -1
- package/dist/components/form/select-field/index.js +2 -2
- package/dist/components/ui/button/styles.css +31 -1
- package/dist/components/ui/calendar/index.d.ts.map +1 -1
- package/dist/components/ui/calendar/index.js +7 -1
- package/dist/components/ui/file-upload/index.d.ts +76 -9
- package/dist/components/ui/file-upload/index.d.ts.map +1 -1
- package/dist/components/ui/file-upload/index.js +47 -12
- package/dist/components/ui/form.d.ts +4 -0
- package/dist/components/ui/form.d.ts.map +1 -1
- package/dist/components/ui/form.js +48 -6
- package/dist/components/ui/toggle-group/index.d.ts +1 -1
- package/dist/components/ui/toggle-group/index.d.ts.map +1 -1
- package/dist/components/ui/toggle-group/index.js +73 -4
- package/dist/esm/components/form/file-upload-field/index.d.ts +11 -2
- package/dist/esm/components/form/file-upload-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/file-upload-field/index.js +8 -3
- package/dist/esm/components/form/input-field/index.d.ts +41 -2
- package/dist/esm/components/form/input-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/input-field/index.js +73 -15
- package/dist/esm/components/form/select-field/index.d.ts +9 -1
- package/dist/esm/components/form/select-field/index.d.ts.map +1 -1
- package/dist/esm/components/form/select-field/index.js +2 -2
- package/dist/esm/components/ui/calendar/index.d.ts.map +1 -1
- package/dist/esm/components/ui/calendar/index.js +8 -2
- package/dist/esm/components/ui/file-upload/index.d.ts +76 -9
- package/dist/esm/components/ui/file-upload/index.d.ts.map +1 -1
- package/dist/esm/components/ui/file-upload/index.js +47 -13
- package/dist/esm/components/ui/form.d.ts +4 -0
- package/dist/esm/components/ui/form.d.ts.map +1 -1
- package/dist/esm/components/ui/form.js +48 -6
- package/dist/esm/components/ui/toggle-group/index.d.ts +1 -1
- package/dist/esm/components/ui/toggle-group/index.d.ts.map +1 -1
- package/dist/esm/components/ui/toggle-group/index.js +73 -4
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { Control, FieldPathByValue, FieldValues } from 'react-hook-form';
|
|
3
|
-
import { type FileUploadResult } from '../../../components/ui/file-upload';
|
|
3
|
+
import { type FileUploadError, type FileUploadLabels, type FileUploadResult } from '../../../components/ui/file-upload';
|
|
4
4
|
import { type RenderableLabel } from '../../../components/form/renderable-label';
|
|
5
5
|
type FileUploadFieldOwnProps<T extends FieldValues = FieldValues> = {
|
|
6
6
|
control: Control<T>;
|
|
@@ -17,6 +17,15 @@ type FileUploadFieldOwnProps<T extends FieldValues = FieldValues> = {
|
|
|
17
17
|
maxSizeBytes?: number;
|
|
18
18
|
disabled?: boolean;
|
|
19
19
|
className?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Override the picker's user-visible copy. Threaded straight to FileUpload;
|
|
22
|
+
* omitted fields keep the English defaults. Note `labels.error` governs the
|
|
23
|
+
* PICKER's own size/type/read refusal only — resolver messages still come
|
|
24
|
+
* from the schema through FormMessage.
|
|
25
|
+
*/
|
|
26
|
+
labels?: FileUploadLabels;
|
|
27
|
+
/** Observe size, type, and read failures after the form value is cleared. */
|
|
28
|
+
onError?: (error: FileUploadError) => void;
|
|
20
29
|
/** Optional escape hatch to also observe the picked File/clear alongside the form's text value. */
|
|
21
30
|
onSelect?: (result: FileUploadResult | null) => void;
|
|
22
31
|
};
|
|
@@ -47,6 +56,6 @@ export type FileUploadFieldProps<T extends FieldValues = FieldValues> = FileUplo
|
|
|
47
56
|
* rejection; on resolver failure the FormMessage announces. Both surfaces are
|
|
48
57
|
* wired.
|
|
49
58
|
*/
|
|
50
|
-
export declare const FileUploadField: <T extends FieldValues = FieldValues>({ control, name, label, description, tooltip, required, accept, maxSizeBytes, disabled, className, onSelect, "aria-label": ariaLabelProp }: FileUploadFieldProps<T>) => import("react").JSX.Element;
|
|
59
|
+
export declare const FileUploadField: <T extends FieldValues = FieldValues>({ control, name, label, description, tooltip, required, accept, maxSizeBytes, disabled, className, labels, onError, onSelect, "aria-label": ariaLabelProp }: FileUploadFieldProps<T>) => import("react").JSX.Element;
|
|
51
60
|
export {};
|
|
52
61
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/form/file-upload-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAWxE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/form/file-upload-field/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,MAAM,OAAO,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAWxE,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,oCAAoC,CAAA;AAE3C,KAAK,uBAAuB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAAI;IAClE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACnB;;;;OAIG;IACH,IAAI,EAAE,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC7C,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;IAC1C,mGAAmG;IACnG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAA;CACrD,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAClE,uBAAuB,CAAC,CAAC,CAAC,GACxB,CACI;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAC1C,CAAA;AAEL;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,6JAelE,oBAAoB,CAAC,CAAC,CAAC,gCAoFzB,CAAA"}
|
|
@@ -22,7 +22,7 @@ const renderable_label_1 = require("../../../components/form/renderable-label");
|
|
|
22
22
|
* rejection; on resolver failure the FormMessage announces. Both surfaces are
|
|
23
23
|
* wired.
|
|
24
24
|
*/
|
|
25
|
-
const FileUploadField = ({ control, name, label, description, tooltip, required, accept, maxSizeBytes, disabled, className, onSelect, 'aria-label': ariaLabelProp }) => {
|
|
25
|
+
const FileUploadField = ({ control, name, label, description, tooltip, required, accept, maxSizeBytes, disabled, className, labels, onError, onSelect, 'aria-label': ariaLabelProp }) => {
|
|
26
26
|
const [localValue, setLocalValue] = (0, react_1.useState)(null);
|
|
27
27
|
const showLabel = (0, renderable_label_1.hasRenderableLabel)(label);
|
|
28
28
|
// An empty or whitespace-only aria-label is worse than none: it names the
|
|
@@ -54,11 +54,16 @@ const FileUploadField = ({ control, name, label, description, tooltip, required,
|
|
|
54
54
|
// value carries nothing else to tell them apart. Fix needs a reset epoch
|
|
55
55
|
// from react-hook-form, or a form value richer than the file text.
|
|
56
56
|
const shown = localValue && field.value === localValue.text ? localValue : null;
|
|
57
|
-
return ((0, jsx_runtime_1.jsxs)(form_1.FormItem, { required: required, children: [showLabel && ((0, jsx_runtime_1.jsx)(form_1.FormLabel, { extra: tooltip ? (0, jsx_runtime_1.jsx)(form_1.FormTooltip, { children: tooltip }) : undefined, children: label })), (0, jsx_runtime_1.jsx)(form_1.FormControl, { children: (0, jsx_runtime_1.jsx)(file_upload_1.FileUpload, { ref: field.ref, name: field.name, onBlur: field.onBlur, "aria-label": ariaLabel, accept: accept, maxSizeBytes: maxSizeBytes, disabled: disabled, className: className, value: shown, onSelect: (result) => {
|
|
57
|
+
return ((0, jsx_runtime_1.jsxs)(form_1.FormItem, { required: required, children: [showLabel && ((0, jsx_runtime_1.jsx)(form_1.FormLabel, { extra: tooltip ? (0, jsx_runtime_1.jsx)(form_1.FormTooltip, { children: tooltip }) : undefined, children: label })), (0, jsx_runtime_1.jsx)(form_1.FormControl, { children: (0, jsx_runtime_1.jsx)(file_upload_1.FileUpload, { ref: field.ref, name: field.name, onBlur: field.onBlur, "aria-label": ariaLabel, labels: labels, accept: accept, maxSizeBytes: maxSizeBytes, disabled: disabled, className: className, value: shown, onSelect: (result) => {
|
|
58
58
|
setLocalValue(result);
|
|
59
59
|
field.onChange(result?.text ?? '');
|
|
60
60
|
onSelect?.(result);
|
|
61
|
-
}, onError: () =>
|
|
61
|
+
}, onError: (error) => {
|
|
62
|
+
setLocalValue(null);
|
|
63
|
+
field.onChange('');
|
|
64
|
+
onSelect?.(null);
|
|
65
|
+
onError?.(error);
|
|
66
|
+
} }) }), (0, jsx_runtime_1.jsx)(form_1.FormMessage, {}), description && (0, jsx_runtime_1.jsx)(form_1.FormDescription, { children: description })] }));
|
|
62
67
|
} }));
|
|
63
68
|
};
|
|
64
69
|
exports.FileUploadField = FileUploadField;
|
|
@@ -1,9 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type InputRef } from '../../../components/ui/input';
|
|
2
|
+
import { HTMLInputTypeAttribute, ReactNode, Ref } from 'react';
|
|
2
3
|
import { Control, FieldValues } from 'react-hook-form';
|
|
3
4
|
export type InputFieldProps<T extends FieldValues = FieldValues> = {
|
|
4
5
|
className?: string;
|
|
5
6
|
name: string;
|
|
6
7
|
type?: HTMLInputTypeAttribute;
|
|
8
|
+
/**
|
|
9
|
+
* Announce the field's own invalidity. Reaches the rendered control on BOTH
|
|
10
|
+
* branches, so a field with no `control` — and therefore no react-hook-form
|
|
11
|
+
* error state — can still tell a screen reader it is invalid.
|
|
12
|
+
*
|
|
13
|
+
* ORed with the form's error, never overriding it: `false` here cannot
|
|
14
|
+
* suppress a live resolver error. Nobody asked to mute a real validation
|
|
15
|
+
* failure, and on a financial console a muted one is a wrong number reaching
|
|
16
|
+
* a money path.
|
|
17
|
+
*
|
|
18
|
+
* TypeScript exempts hyphenated JSX attributes from excess-property
|
|
19
|
+
* checking, so passing this used to compile and then vanish.
|
|
20
|
+
*/
|
|
21
|
+
'aria-invalid'?: boolean;
|
|
22
|
+
/** Lower bound, forwarded to the single-line control on both branches. */
|
|
23
|
+
min?: number | string;
|
|
24
|
+
/** Upper bound, forwarded to the single-line control on both branches. */
|
|
25
|
+
max?: number | string;
|
|
26
|
+
/** Increment, forwarded to the single-line control on both branches. */
|
|
27
|
+
step?: number | string;
|
|
28
|
+
/** Character ceiling, forwarded on both branches (single-line and textarea). */
|
|
29
|
+
maxLength?: number;
|
|
30
|
+
/** Browser autofill hint. `"off"` is the one a money field usually wants. */
|
|
31
|
+
autoComplete?: React.HTMLInputAutoCompleteAttribute;
|
|
32
|
+
/** Virtual-keyboard hint, e.g. `"numeric"` or `"decimal"`. Both branches. */
|
|
33
|
+
inputMode?: React.HTMLAttributes<HTMLElement>['inputMode'];
|
|
34
|
+
/** Native validation regex. Single-line only — a textarea has no `pattern`. */
|
|
35
|
+
pattern?: string;
|
|
36
|
+
/** Focus the control on mount. React focuses the node rather than emitting an attribute. */
|
|
37
|
+
autoFocus?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Imperative handle on the rendered control: `focus()`/`blur()` on the
|
|
40
|
+
* single-line branch, the textarea node on the `textArea` branch (which
|
|
41
|
+
* satisfies the same shape). COMPOSED with react-hook-form's own ref when
|
|
42
|
+
* `control` is present, never replacing it — the form keeps the node it
|
|
43
|
+
* needs for `shouldFocusError`.
|
|
44
|
+
*/
|
|
45
|
+
ref?: Ref<InputRef>;
|
|
7
46
|
label?: ReactNode;
|
|
8
47
|
tooltip?: string;
|
|
9
48
|
labelExtra?: ReactNode;
|
|
@@ -32,5 +71,5 @@ export type InputFieldProps<T extends FieldValues = FieldValues> = {
|
|
|
32
71
|
'data-testid'?: string;
|
|
33
72
|
onChange?: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
34
73
|
};
|
|
35
|
-
export declare const InputField: <T extends FieldValues = FieldValues>({ className, type, label, tooltip, labelExtra, placeholder, description, startAdornment, endAdornment, required, readOnly, minHeight, maxHeight, textArea, defaultValue, value, onChange, ...others }: InputFieldProps<T>) => import("react").JSX.Element;
|
|
74
|
+
export declare const InputField: <T extends FieldValues = FieldValues>({ className, type, label, tooltip, labelExtra, placeholder, description, startAdornment, endAdornment, required, readOnly, minHeight, maxHeight, textArea, defaultValue, value, onChange, min, max, step, maxLength, autoComplete, inputMode, pattern, autoFocus, ref, "aria-invalid": ariaInvalid, ...others }: InputFieldProps<T>) => import("react").JSX.Element;
|
|
36
75
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/form/input-field/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/form/input-field/index.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,EAAE,sBAAsB,EAAE,SAAS,EAAE,GAAG,EAAmB,MAAM,OAAO,CAAA;AAC/E,OAAO,EAAE,OAAO,EAAkB,WAAW,EAAQ,MAAM,iBAAiB,CAAA;AAE5E,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAAI;IACjE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B;;;;;;;;;;;;OAYG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,KAAK,CAAC,8BAA8B,CAAA;IACnD,6EAA6E;IAC7E,SAAS,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,CAAA;IAC1D,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;IACnB,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,SAAS,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;oDACgD;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,CACT,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,GAAG,mBAAmB,CAAC,KACzD,IAAI,CAAA;CACV,CAAA;AA4CD,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,iTA6B7D,eAAe,CAAC,CAAC,CAAC,gCAyHpB,CAAA"}
|
|
@@ -5,8 +5,59 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const autosize_textarea_1 = require("../../../components/ui/autosize-textarea");
|
|
6
6
|
const form_1 = require("../../../components/ui/form");
|
|
7
7
|
const input_1 = require("../../../components/ui/input");
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
/**
|
|
10
|
+
* Fan one node out to several refs. React hands a node to ONE ref, so a
|
|
11
|
+
* consumer ref and react-hook-form's `field.ref` cannot both be passed
|
|
12
|
+
* directly — one silently wins. Only used when the consumer actually supplied
|
|
13
|
+
* a ref, so a field without one keeps `field.ref`'s own identity and React
|
|
14
|
+
* never re-attaches it.
|
|
15
|
+
*/
|
|
16
|
+
function fanOutRef(...refs) {
|
|
17
|
+
return (node) => {
|
|
18
|
+
const cleanups = [];
|
|
19
|
+
for (const ref of refs) {
|
|
20
|
+
if (typeof ref === 'function') {
|
|
21
|
+
const cleanup = ref(node);
|
|
22
|
+
cleanups.push(typeof cleanup === 'function' ? cleanup : () => ref(null));
|
|
23
|
+
}
|
|
24
|
+
else if (ref) {
|
|
25
|
+
;
|
|
26
|
+
ref.current = node;
|
|
27
|
+
cleanups.push(() => {
|
|
28
|
+
;
|
|
29
|
+
ref.current = null;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return () => cleanups.forEach((cleanup) => cleanup());
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const InputField = ({ className, type, label, tooltip, labelExtra, placeholder, description, startAdornment, endAdornment, required, readOnly, minHeight, maxHeight, textArea, defaultValue, value, onChange, min, max, step, maxLength, autoComplete, inputMode, pattern, autoFocus, ref, 'aria-invalid': ariaInvalid, ...others }) => {
|
|
37
|
+
// OR with the form's error, expressed through the Slot merge rather than by
|
|
38
|
+
// reading the form: FormControl already injects `aria-invalid={!!error}`, and
|
|
39
|
+
// a child prop only wins that merge when the KEY is present. So emit the key
|
|
40
|
+
// only for an explicit `true`, and let FormControl answer for every other
|
|
41
|
+
// case. `true || error` is true; `false || error` is whatever the form says,
|
|
42
|
+
// which is the whole point — an explicit `false` cannot mute a live error.
|
|
43
|
+
// With no `control` there is no error to OR against, so FormControl injects
|
|
44
|
+
// `false` and today's behaviour is unchanged.
|
|
45
|
+
const ariaInvalidProp = ariaInvalid ? { 'aria-invalid': true } : undefined;
|
|
46
|
+
const formRef = (0, react_1.useRef)(undefined);
|
|
47
|
+
const composedRef = (0, react_1.useMemo)(() => ref ? fanOutRef((node) => formRef.current?.(node), ref) : ref,
|
|
48
|
+
// `others.name` belongs here, and leaving it out is not a lint nit.
|
|
49
|
+
// react-hook-form hands a fresh `field.ref` for every name, and that ref
|
|
50
|
+
// only receives the element when React reattaches, which it does only when
|
|
51
|
+
// this callback's identity changes. Memoised on `ref` alone, one identity
|
|
52
|
+
// survived a name change, the new field never got its element, and
|
|
53
|
+
// `form.setFocus(newName)` then found nothing to focus and said nothing.
|
|
54
|
+
[ref, others.name]);
|
|
55
|
+
const renderItem = (binding) => ((0, jsx_runtime_1.jsxs)(form_1.FormItem, { required: required, children: [label && ((0, jsx_runtime_1.jsx)(form_1.FormLabel, { extra: tooltip ? (0, jsx_runtime_1.jsx)(form_1.FormTooltip, { children: tooltip }) : labelExtra, children: label })), (0, jsx_runtime_1.jsx)(form_1.FormControl, { children: textArea ? ((0, jsx_runtime_1.jsx)(autosize_textarea_1.AutosizeTextarea, { className: className, placeholder: placeholder, readOnly: readOnly, minHeight: minHeight, maxHeight: maxHeight, maxLength: maxLength, autoComplete: autoComplete, inputMode: inputMode, autoFocus: autoFocus, "data-testid": others['data-testid'], ...ariaInvalidProp, ...binding })) : ((0, jsx_runtime_1.jsx)(input_1.Input, { className: className, type: type, placeholder: placeholder, readOnly: readOnly, startAdornment: startAdornment, endAdornment: endAdornment,
|
|
56
|
+
// Bounds belong to the single-line control only: `min`/`max`/`step`
|
|
57
|
+
// mean nothing on a textarea and would render as invalid markup.
|
|
58
|
+
min: min, max: max, step: step, maxLength: maxLength, autoComplete: autoComplete, inputMode: inputMode,
|
|
59
|
+
// Single-line only: `<textarea pattern>` is not a thing.
|
|
60
|
+
pattern: pattern, autoFocus: autoFocus, "data-testid": others['data-testid'], ...ariaInvalidProp, ...binding })) }), (0, jsx_runtime_1.jsx)(form_1.FormMessage, {}), description && (0, jsx_runtime_1.jsx)(form_1.FormDescription, { children: description })] }));
|
|
10
61
|
if (!others.control) {
|
|
11
62
|
return renderItem({
|
|
12
63
|
name: others.name,
|
|
@@ -14,7 +65,10 @@ const InputField = ({ className, type, label, tooltip, labelExtra, placeholder,
|
|
|
14
65
|
// Controlled when the caller holds the value, uncontrolled when it only
|
|
15
66
|
// seeds one — supplying both is what React warns about.
|
|
16
67
|
...(value !== undefined ? { value } : { defaultValue }),
|
|
17
|
-
onChange: (e) => onChange?.(e)
|
|
68
|
+
onChange: (e) => onChange?.(e),
|
|
69
|
+
// Nothing else claims the node on this branch, so the consumer ref goes
|
|
70
|
+
// straight through. Without it there was no node to focus at all.
|
|
71
|
+
ref: ref
|
|
18
72
|
});
|
|
19
73
|
}
|
|
20
74
|
return ((0, jsx_runtime_1.jsx)(form_1.FormField, { ...others, name: others.name,
|
|
@@ -22,17 +76,21 @@ const InputField = ({ className, type, label, tooltip, labelExtra, placeholder,
|
|
|
22
76
|
// value painted on the box only makes the field LOOK filled while the
|
|
23
77
|
// form still submits nothing for that name. Controller seeds its own
|
|
24
78
|
// state from this, so `field.value` carries the seed on both branches.
|
|
25
|
-
defaultValue: defaultValue, render: ({ field }) =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
field.
|
|
34
|
-
onChange
|
|
35
|
-
|
|
36
|
-
|
|
79
|
+
defaultValue: defaultValue, render: ({ field }) => {
|
|
80
|
+
formRef.current = field.ref;
|
|
81
|
+
return renderItem({
|
|
82
|
+
...field,
|
|
83
|
+
// Controlled from mount. react-hook-form hands `undefined` for any
|
|
84
|
+
// name the form declares no default for; React then mounts the box
|
|
85
|
+
// uncontrolled and flips it on the first keystroke, losing that
|
|
86
|
+
// character.
|
|
87
|
+
value: field.value ?? '',
|
|
88
|
+
onChange: (e) => {
|
|
89
|
+
field.onChange(e);
|
|
90
|
+
onChange?.(e);
|
|
91
|
+
},
|
|
92
|
+
ref: (ref ? composedRef : field.ref)
|
|
93
|
+
});
|
|
94
|
+
} }));
|
|
37
95
|
};
|
|
38
96
|
exports.InputField = InputField;
|
|
@@ -13,7 +13,15 @@ type SelectFieldSharedProps<T extends FieldValues = FieldValues> = PropsWithChil
|
|
|
13
13
|
* with `value` + `onChange`. */
|
|
14
14
|
control?: Control<T>;
|
|
15
15
|
required?: boolean;
|
|
16
|
+
/** Copy for the "nothing to pick" row. Defaults to "No options found." */
|
|
16
17
|
emptyMessage?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Placed on the field's own root box, merged with its spacing rather than
|
|
20
|
+
* replacing it. The root is the element a parent grid positions, so
|
|
21
|
+
* without this a consumer had to wrap the field in a spare div just to
|
|
22
|
+
* span a column.
|
|
23
|
+
*/
|
|
24
|
+
className?: string;
|
|
17
25
|
'data-testid'?: string;
|
|
18
26
|
};
|
|
19
27
|
/**
|
|
@@ -39,6 +47,6 @@ type SelectFieldModeProps = {
|
|
|
39
47
|
onChange?: (value: string) => void;
|
|
40
48
|
};
|
|
41
49
|
export type SelectFieldProps<T extends FieldValues = FieldValues> = SelectFieldSharedProps<T> & SelectFieldModeProps;
|
|
42
|
-
export declare const SelectField: <T extends FieldValues = FieldValues>({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage, onChange, ...others }: SelectFieldProps<T>) => React.JSX.Element;
|
|
50
|
+
export declare const SelectField: <T extends FieldValues = FieldValues>({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage, className, onChange, ...others }: SelectFieldProps<T>) => React.JSX.Element;
|
|
43
51
|
export {};
|
|
44
52
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/form/select-field/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3D,OAAO,EACL,OAAO,EAEP,WAAW,EAEZ,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC7D,iBAAiB,GAAG;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;qCACiC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAEH;;;;;;;;GAQG;AACH,KAAK,oBAAoB,GACrB;IACE,KAAK,EAAE,IAAI,CAAA;IACX;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACrC,GACD;IACE,KAAK,CAAC,EAAE,KAAK,CAAA;IACb;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,CAAA;AAEL,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC9D,sBAAsB,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAA;AAWlD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/form/select-field/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3D,OAAO,EACL,OAAO,EAEP,WAAW,EAEZ,MAAM,iBAAiB,CAAA;AAExB,KAAK,sBAAsB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC7D,iBAAiB,GAAG;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;qCACiC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAEH;;;;;;;;GAQG;AACH,KAAK,oBAAoB,GACrB;IACE,KAAK,EAAE,IAAI,CAAA;IACX;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;CACrC,GACD;IACE,KAAK,CAAC,EAAE,KAAK,CAAA;IACb;yEACqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,CAAA;AAEL,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAC9D,sBAAsB,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAA;AAWlD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,6KAkB9D,gBAAgB,CAAC,CAAC,CAAC,sBA6FrB,CAAA"}
|
|
@@ -6,14 +6,14 @@ const form_1 = require("../../../components/ui/form");
|
|
|
6
6
|
const multiple_select_1 = require("../../../components/ui/multiple-select");
|
|
7
7
|
const select_1 = require("../../../components/ui/select");
|
|
8
8
|
const utils_1 = require("../../../lib/utils");
|
|
9
|
-
const SelectField = ({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage = 'No options found.', onChange, ...others }) => {
|
|
9
|
+
const SelectField = ({ name, label, tooltip, labelExtra, required, placeholder, description, disabled, readOnly, multi, control, value, children, emptyMessage = 'No options found.', className, onChange, ...others }) => {
|
|
10
10
|
// The props union already pairs `multi` with the callback shape that branch
|
|
11
11
|
// emits, but destructuring an intersection loses that correlation — TS sees
|
|
12
12
|
// two unrelated callbacks. Both branches report through this one seam, each
|
|
13
13
|
// with the payload its own union member declared.
|
|
14
14
|
const emitChange = onChange;
|
|
15
15
|
const renderItem = (field) => {
|
|
16
|
-
return ((0, jsx_runtime_1.jsxs)(form_1.FormItem, { required: required, children: [label && ((0, jsx_runtime_1.jsx)(form_1.FormLabel, { extra: tooltip ? (0, jsx_runtime_1.jsx)(form_1.FormTooltip, { children: tooltip }) : labelExtra, children: label })), multi ? ((0, jsx_runtime_1.jsxs)(multiple_select_1.MultipleSelect, { onValueChange: (value) => {
|
|
16
|
+
return ((0, jsx_runtime_1.jsxs)(form_1.FormItem, { required: required, className: className, children: [label && ((0, jsx_runtime_1.jsx)(form_1.FormLabel, { extra: tooltip ? (0, jsx_runtime_1.jsx)(form_1.FormTooltip, { children: tooltip }) : labelExtra, children: label })), multi ? ((0, jsx_runtime_1.jsxs)(multiple_select_1.MultipleSelect, { onValueChange: (value) => {
|
|
17
17
|
field.onChange(value);
|
|
18
18
|
emitChange?.(value);
|
|
19
19
|
}, disabled: disabled, ...field, children: [(0, jsx_runtime_1.jsx)(multiple_select_1.MultipleSelectTrigger, { readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(multiple_select_1.MultipleSelectValue, { placeholder: placeholder }) }), (0, jsx_runtime_1.jsx)(multiple_select_1.MultipleSelectContent, { children: children })] })) : ((0, jsx_runtime_1.jsxs)(select_1.Select, { onValueChange: (value) => {
|
|
@@ -34,7 +34,12 @@
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
.button-plain {
|
|
37
|
-
|
|
37
|
+
/* The dark counterpart is not optional: black/5 over a dark ground is
|
|
38
|
+
invisible, so the hover affordance simply vanished in dark mode. Same
|
|
39
|
+
/5-light, /10-dark pairing the kit already uses for translucent state
|
|
40
|
+
fills (`ui/field`: `has-data-checked:bg-primary/5
|
|
41
|
+
dark:has-data-checked:bg-primary/10`). */
|
|
42
|
+
@apply bg-transparent text-inherit shadow-none hover:bg-black/5 dark:hover:bg-white/10;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
.button-primary {
|
|
@@ -63,6 +68,31 @@
|
|
|
63
68
|
@apply !text-button-link !h-4 !p-0 !px-0 !py-0 underline shadow-none;
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
/*
|
|
72
|
+
WCAG 2.5.8 (AA) asks a pointer target for 24 by 24 CSS px. This variant pins
|
|
73
|
+
its box at 16, and pins it with `!important` on the height and all three
|
|
74
|
+
padding axes — so unlike an ordinary too-small target, no consumer can
|
|
75
|
+
correct it at the call site. That is what makes it a library defect rather
|
|
76
|
+
than a call-site one; the 16px itself is not new.
|
|
77
|
+
|
|
78
|
+
The hit area grows instead of the box. `.button-base` is already
|
|
79
|
+
`position: relative`, so an inset pseudo-element takes the target to 24px
|
|
80
|
+
tall at zero layout cost: the element's own box, and every line box around
|
|
81
|
+
it, stay at 16. Growing the box was the alternative and it was rejected —
|
|
82
|
+
link buttons sit inside running text, where a taller box reflows the
|
|
83
|
+
sentence, and in table cells and filter bars, where it shifts the row.
|
|
84
|
+
|
|
85
|
+
Horizontal size is left to the label. SC 2.5.8 exempts a target whose size
|
|
86
|
+
is constrained by the line-height of the text around it, and stealing
|
|
87
|
+
horizontal space from adjacent words would take clicks that belong to them.
|
|
88
|
+
|
|
89
|
+
Overridable: Tailwind's `utilities` layer wins over `components`, so a
|
|
90
|
+
consumer that needs the band gone writes `before:inset-y-0`.
|
|
91
|
+
*/
|
|
92
|
+
.button-link::before {
|
|
93
|
+
@apply absolute inset-x-0 -inset-y-1 content-[''];
|
|
94
|
+
}
|
|
95
|
+
|
|
66
96
|
.button-disabled {
|
|
67
97
|
@apply disabled:pointer-events-none disabled:border-transparent disabled:opacity-50 disabled:shadow-none;
|
|
68
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/calendar/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EACL,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/calendar/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EACL,SAAS,EAGT,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAE,MAAM,EAAkB,MAAM,wBAAwB,CAAA;AAO/D,iBAAS,QAAQ,CAAC,EAChB,SAAS,EACT,UAAU,EACV,eAAsB,EACtB,aAAuB,EACvB,aAAuB,EACvB,UAAU,EACV,UAAU,EACV,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,GAAG;IAC1C,aAAa,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAA;CAC/D,qBAgKA;AAED,iBAAS,iBAAiB,CAAC,EACzB,SAAS,EACT,GAAG,EACH,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,qBAqCxC;AAED,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAA"}
|
|
@@ -45,7 +45,13 @@ const lucide_react_1 = require("lucide-react");
|
|
|
45
45
|
function Calendar({ className, classNames, showOutsideDays = true, captionLayout = 'label', buttonVariant = 'plain', formatters, components, ...props }) {
|
|
46
46
|
const defaultClassNames = (0, react_day_picker_1.getDefaultClassNames)();
|
|
47
47
|
return ((0, jsx_runtime_1.jsx)(react_day_picker_1.DayPicker, { showOutsideDays: showOutsideDays, className: (0, utils_1.cn)('bg-background group/calendar p-2 [--cell-radius:var(--radius-md)] [--cell-size:34px] [--nav-size:28px] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent', String.raw `rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw `rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className), captionLayout: captionLayout, formatters: {
|
|
48
|
-
|
|
48
|
+
// react-day-picker's own default is dateLib.format(month, 'LLLL'), the
|
|
49
|
+
// full month name in the locale it was given. Shortening it has to go
|
|
50
|
+
// through the same dateLib: Date.toLocaleString('default') reads the
|
|
51
|
+
// runtime locale instead, so the dropdown fell back to English while
|
|
52
|
+
// the weekdays and the caption around it stayed in the consumer's
|
|
53
|
+
// language. 'LLL' is the abbreviated form of the same standalone month.
|
|
54
|
+
formatMonthDropdown: (month, dateLib = react_day_picker_1.defaultDateLib) => dateLib.format(month, 'LLL'),
|
|
49
55
|
...formatters
|
|
50
56
|
}, classNames: {
|
|
51
57
|
root: (0, utils_1.cn)('w-fit', defaultClassNames.root),
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* FileUpload — a controlled "pick a file,
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* FileUpload — a controlled "pick a file, hand it back" primitive. The defining
|
|
3
|
+
* job: select one file (by click, keyboard, or drag-and-drop), validate it
|
|
4
|
+
* against an accept filter + a byte ceiling, and emit `{ file, text }`.
|
|
5
|
+
*
|
|
6
|
+
* `readAs` decides whether the bytes are decoded on the way through. `'text'`
|
|
7
|
+
* (the default) reads UTF-8 via FileReader.readAsText, which is what a PEM/CSV
|
|
8
|
+
* host wants. `'none'` hands the `File` over untouched and is the only mode a
|
|
9
|
+
* BINARY file can use: a PDF, XLSX or PFX put through readAsText decodes into
|
|
10
|
+
* replacement-character garbage, and that garbage string is then retained for
|
|
11
|
+
* as long as the host holds the value — 20 MiB of PDF became a useless 20 MiB
|
|
12
|
+
* string, and the host still had to read the file a second time to get at the
|
|
13
|
+
* real bytes.
|
|
7
14
|
*
|
|
8
15
|
* The real `<input type="file">` IS the accessible control: it is `sr-only`
|
|
9
16
|
* (visually hidden) but focusable and labelable — never `aria-hidden`, never
|
|
@@ -24,6 +31,11 @@
|
|
|
24
31
|
* a value, so it cannot masquerade as valid. `accept` is validated against
|
|
25
32
|
* BOTH extension and MIME because the native `accept` attribute is only a
|
|
26
33
|
* browser hint and is bypassable via drag-drop.
|
|
34
|
+
*
|
|
35
|
+
* A host that already announces the rejection itself — a toast in its own
|
|
36
|
+
* locale, driven off `onError` — silences this one by returning nothing from
|
|
37
|
+
* `labels.error`. Two announcements for one event, in two languages, is the
|
|
38
|
+
* accessibility defect; the wording is not.
|
|
27
39
|
*/
|
|
28
40
|
import * as React from 'react';
|
|
29
41
|
export type FileUploadResult = {
|
|
@@ -42,6 +54,33 @@ export type FileUploadError = {
|
|
|
42
54
|
kind: 'read-failed';
|
|
43
55
|
file: File;
|
|
44
56
|
};
|
|
57
|
+
/** How the picked file's bytes are handled on the way to `onSelect`. */
|
|
58
|
+
export type FileUploadReadAs = 'text' | 'none';
|
|
59
|
+
/**
|
|
60
|
+
* Overrides for the fixed English copy. Every field is optional and defaults to
|
|
61
|
+
* the current value, so a consumer that passes nothing renders exactly as
|
|
62
|
+
* before. These four strings were the whole of the component's user-visible
|
|
63
|
+
* text, and none of them was reachable — a pt-BR console rendered an English
|
|
64
|
+
* affordance and handed a screen reader an English accessible name.
|
|
65
|
+
*/
|
|
66
|
+
export interface FileUploadLabels {
|
|
67
|
+
/** Emphasised call to action in the empty zone. Defaults to "Choose a file". */
|
|
68
|
+
action?: string;
|
|
69
|
+
/** Trailing hint after the action. Defaults to "or drag and drop". */
|
|
70
|
+
hint?: string;
|
|
71
|
+
/** Accessible name of the clear button. Defaults to "Remove file". */
|
|
72
|
+
remove?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Copy for the component's own `role="alert"` refusal. Receives the rejection
|
|
75
|
+
* so the message can interpolate the cap or the accept filter; use the
|
|
76
|
+
* exported `humanizeSize` to format `maxSizeBytes` the way the chip does.
|
|
77
|
+
*
|
|
78
|
+
* Return `null`, `undefined` or `''` to stay SILENT and leave the
|
|
79
|
+
* announcement to the host's own `onError` handling. Defaults to the
|
|
80
|
+
* built-in English messages.
|
|
81
|
+
*/
|
|
82
|
+
error?: (error: FileUploadError) => string | null | undefined;
|
|
83
|
+
}
|
|
45
84
|
export type FileUploadProps = {
|
|
46
85
|
/** Comma-separated accept filter, e.g. ".pem,.key" or "text/plain". Mirrors the native input accept. */
|
|
47
86
|
accept?: string;
|
|
@@ -49,7 +88,18 @@ export type FileUploadProps = {
|
|
|
49
88
|
maxSizeBytes?: number;
|
|
50
89
|
/** Controlled selection. `null` = empty. The host owns state. */
|
|
51
90
|
value?: FileUploadResult | null;
|
|
52
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* How the accepted file is handed over.
|
|
93
|
+
* - `'text'` (default): decode as UTF-8 via FileReader.readAsText and emit
|
|
94
|
+
* `{ file, text }`. Unchanged behaviour for every existing consumer.
|
|
95
|
+
* - `'none'`: skip decoding entirely and emit `{ file, text: '' }`. The mode
|
|
96
|
+
* for BINARY files (PDF, XLSX, PFX/DER): the host gets the `File` intact
|
|
97
|
+
* and `text` carries nothing, so do not read it in this mode.
|
|
98
|
+
*/
|
|
99
|
+
readAs?: FileUploadReadAs;
|
|
100
|
+
/** Override the fixed English copy. Omitted fields keep their defaults. */
|
|
101
|
+
labels?: FileUploadLabels;
|
|
102
|
+
/** Fires on accept (with the result) or clear (null). See `readAs` for whether `text` is populated. */
|
|
53
103
|
onSelect: (result: FileUploadResult | null) => void;
|
|
54
104
|
/** Fires when a pick is rejected (size/type/read). Optional — the component also shows its own inline error. */
|
|
55
105
|
onError?: (error: FileUploadError) => void;
|
|
@@ -60,7 +110,7 @@ export type FileUploadProps = {
|
|
|
60
110
|
'aria-required'?: boolean;
|
|
61
111
|
'aria-describedby'?: string;
|
|
62
112
|
'aria-label'?: string;
|
|
63
|
-
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'accept' | 'value' | 'disabled' | 'onChange' | 'onSelect' | 'className' | 'multiple'>;
|
|
113
|
+
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'accept' | 'value' | 'disabled' | 'onChange' | 'onSelect' | 'className' | 'onError' | 'multiple'>;
|
|
64
114
|
/**
|
|
65
115
|
* Validate a chosen file against an accept filter and a byte ceiling. Exported
|
|
66
116
|
* so hosts can pre-validate before handing a file over. `accept` is matched
|
|
@@ -72,6 +122,12 @@ export declare function validateFile(file: File, opts: {
|
|
|
72
122
|
accept?: string;
|
|
73
123
|
maxSizeBytes?: number;
|
|
74
124
|
}): FileUploadError | null;
|
|
125
|
+
/**
|
|
126
|
+
* Humanize a byte count for the selected-file chip. Binary units, 1 decimal.
|
|
127
|
+
* Exported so a `labels.error` override can format `maxSizeBytes` exactly the
|
|
128
|
+
* way the chip and the default message do, instead of re-deriving binary units.
|
|
129
|
+
*/
|
|
130
|
+
export declare function humanizeSize(bytes: number): string;
|
|
75
131
|
export declare const FileUpload: React.ForwardRefExoticComponent<{
|
|
76
132
|
/** Comma-separated accept filter, e.g. ".pem,.key" or "text/plain". Mirrors the native input accept. */
|
|
77
133
|
accept?: string;
|
|
@@ -79,7 +135,18 @@ export declare const FileUpload: React.ForwardRefExoticComponent<{
|
|
|
79
135
|
maxSizeBytes?: number;
|
|
80
136
|
/** Controlled selection. `null` = empty. The host owns state. */
|
|
81
137
|
value?: FileUploadResult | null;
|
|
82
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* How the accepted file is handed over.
|
|
140
|
+
* - `'text'` (default): decode as UTF-8 via FileReader.readAsText and emit
|
|
141
|
+
* `{ file, text }`. Unchanged behaviour for every existing consumer.
|
|
142
|
+
* - `'none'`: skip decoding entirely and emit `{ file, text: '' }`. The mode
|
|
143
|
+
* for BINARY files (PDF, XLSX, PFX/DER): the host gets the `File` intact
|
|
144
|
+
* and `text` carries nothing, so do not read it in this mode.
|
|
145
|
+
*/
|
|
146
|
+
readAs?: FileUploadReadAs;
|
|
147
|
+
/** Override the fixed English copy. Omitted fields keep their defaults. */
|
|
148
|
+
labels?: FileUploadLabels;
|
|
149
|
+
/** Fires on accept (with the result) or clear (null). See `readAs` for whether `text` is populated. */
|
|
83
150
|
onSelect: (result: FileUploadResult | null) => void;
|
|
84
151
|
/** Fires when a pick is rejected (size/type/read). Optional — the component also shows its own inline error. */
|
|
85
152
|
onError?: (error: FileUploadError) => void;
|
|
@@ -90,5 +157,5 @@ export declare const FileUpload: React.ForwardRefExoticComponent<{
|
|
|
90
157
|
'aria-required'?: boolean;
|
|
91
158
|
'aria-describedby'?: string;
|
|
92
159
|
'aria-label'?: string;
|
|
93
|
-
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "disabled" | "type" | "value" | "onChange" | "onSelect" | "accept" | "multiple"> & React.RefAttributes<HTMLInputElement>>;
|
|
160
|
+
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "disabled" | "type" | "value" | "onChange" | "onError" | "onSelect" | "accept" | "multiple"> & React.RefAttributes<HTMLInputElement>>;
|
|
94
161
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/file-upload/index.tsx"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/file-upload/index.tsx"],"names":[],"mappings":"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"}
|