@coopdigital/react 1.27.2 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Button/Button.js +1 -1
- package/dist/components/Card/Card.js +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +3 -3
- package/dist/components/Checkbox/Checkbox.js +5 -3
- package/dist/components/Combobox/Combobox.js +6 -3
- package/dist/components/DatePicker/DatePicker.js +5 -3
- package/dist/components/Dialog/Dialog.js +1 -1
- package/dist/components/Expandable/Expandable.js +1 -1
- package/dist/components/Field/Field.context.js +5 -6
- package/dist/components/Field/Field.d.ts +2 -2
- package/dist/components/Field/Field.js +40 -20
- package/dist/components/FieldMarkers/Error.js +10 -3
- package/dist/components/FieldMarkers/Label.js +1 -5
- package/dist/components/FieldMarkers/fieldmarkers-utils.js +13 -0
- package/dist/components/Fieldset/Fieldset.context.js +7 -0
- package/dist/components/Fieldset/Fieldset.d.ts +3 -1
- package/dist/components/Fieldset/Fieldset.js +34 -7
- package/dist/components/Pill/Pill.js +1 -1
- package/dist/components/Popover/Popover.js +1 -1
- package/dist/components/Radio/Radio.d.ts +1 -1
- package/dist/components/Radio/Radio.js +5 -3
- package/dist/components/Searchbox/Searchbox.js +2 -1
- package/dist/components/Select/Select.d.ts +3 -3
- package/dist/components/Select/Select.js +5 -3
- package/dist/components/Tag/Tag.js +1 -1
- package/dist/components/TextInput/TextInput.d.ts +3 -3
- package/dist/components/TextInput/TextInput.js +7 -3
- package/dist/components/Textarea/Textarea.d.ts +3 -3
- package/dist/components/Textarea/Textarea.js +5 -3
- package/dist/components/ValidationSummary/ValidationSummary.context.js +10 -0
- package/dist/components/ValidationSummary/ValidationSummary.d.ts +1 -8
- package/dist/components/ValidationSummary/ValidationSummary.js +36 -17
- package/dist/components/ValidationSummary/validationsummary-utils.d.ts +11 -0
- package/dist/components/ValidationSummary/validationsummary-utils.js +29 -6
- package/dist/hooks/useSlots.js +2 -26
- package/dist/types/index.d.ts +15 -0
- package/dist/utils/classes.d.ts +6 -0
- package/dist/utils/classes.js +13 -0
- package/dist/utils/index.d.ts +3 -7
- package/dist/utils/index.js +3 -17
- package/dist/utils/slots.d.ts +9 -0
- package/dist/utils/slots.js +37 -0
- package/package.json +3 -3
- package/src/components/Checkbox/Checkbox.tsx +8 -5
- package/src/components/Combobox/Combobox.context.ts +1 -0
- package/src/components/Combobox/Combobox.tsx +7 -3
- package/src/components/DatePicker/DatePicker.tsx +5 -3
- package/src/components/Field/Field.context.ts +14 -8
- package/src/components/Field/Field.tsx +39 -20
- package/src/components/FieldMarkers/Error.tsx +2 -1
- package/src/components/FieldMarkers/Label.tsx +0 -7
- package/src/components/FieldMarkers/fieldmarkers-utils.ts +26 -0
- package/src/components/Fieldset/Fieldset.context.ts +13 -0
- package/src/components/Fieldset/Fieldset.tsx +39 -5
- package/src/components/Radio/Radio.tsx +6 -3
- package/src/components/Select/Select.tsx +7 -4
- package/src/components/TextInput/TextInput.tsx +9 -8
- package/src/components/Textarea/Textarea.tsx +8 -7
- package/src/components/ValidationSummary/ValidationSummary.context.ts +18 -0
- package/src/components/ValidationSummary/ValidationSummary.tsx +38 -24
- package/src/components/ValidationSummary/validationsummary-utils.ts +81 -13
- package/src/hooks/useSlots.ts +1 -41
- package/src/utils/classes.ts +14 -0
- package/src/utils/index.ts +2 -24
- package/src/utils/slots.ts +63 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
//#region src/components/ValidationSummary/ValidationSummary.context.ts
|
|
4
|
+
const ValidationSummaryContext = createContext({
|
|
5
|
+
fieldOrder: [],
|
|
6
|
+
getForm: () => null
|
|
7
|
+
});
|
|
8
|
+
const useValidationSummary = () => useContext(ValidationSummaryContext);
|
|
9
|
+
//#endregion
|
|
10
|
+
export { ValidationSummaryContext, useValidationSummary };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FieldError } from "./validationsummary-utils.js";
|
|
1
2
|
import React, { HTMLAttributes, JSX, ReactNode, Ref, RefObject } from "react";
|
|
2
3
|
//#region src/components/ValidationSummary/ValidationSummary.d.ts
|
|
3
4
|
export interface ValidationSummaryProps extends HTMLAttributes<HTMLDivElement> {
|
|
@@ -46,14 +47,6 @@ interface ValidationSummaryPromptProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
46
47
|
*/
|
|
47
48
|
children?: React.ReactNode;
|
|
48
49
|
}
|
|
49
|
-
interface FieldError {
|
|
50
|
-
message?: string | number;
|
|
51
|
-
ref?: {
|
|
52
|
-
focus?: () => void;
|
|
53
|
-
id?: string;
|
|
54
|
-
};
|
|
55
|
-
type?: string | number;
|
|
56
|
-
}
|
|
57
50
|
interface ValidationSummaryErrorsProps extends HTMLAttributes<HTMLDivElement> {
|
|
58
51
|
/** Specify the validation error array. Refer to the FieldError type for the shape.. */
|
|
59
52
|
errors?: Record<string, FieldError>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { getSlotProp } from "../../utils/
|
|
2
|
+
import { getSlotProp } from "../../utils/slots.js";
|
|
3
3
|
import { useSlots } from "../../hooks/useSlots.js";
|
|
4
|
-
import { getFieldElement, sanitizeFieldId } from "./validationsummary-utils.js";
|
|
4
|
+
import { getFieldElement, getFieldOrder, getForm, sanitizeFieldId, sortErrorsByFieldOrder } from "./validationsummary-utils.js";
|
|
5
|
+
import { ValidationSummaryContext, useValidationSummary } from "./ValidationSummary.context.js";
|
|
5
6
|
import clsx from "clsx";
|
|
6
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
8
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
8
9
|
import mergeRefs from "merge-refs";
|
|
9
10
|
//#region src/components/ValidationSummary/ValidationSummary.tsx
|
|
10
11
|
const componentSlots = {
|
|
@@ -19,11 +20,18 @@ const ValidationSummary = ({ autoScroll = true, children, className, forceMount
|
|
|
19
20
|
const errors = Object.keys(getSlotProp(slots.ValidationSummaryErrors, "errors") ?? {});
|
|
20
21
|
const hasErrors = errors && errors.length > 0;
|
|
21
22
|
const internalRef = useRef(null);
|
|
23
|
+
const [fieldOrder, setFieldOrder] = useState([]);
|
|
22
24
|
const onSubmit = useCallback(() => {
|
|
25
|
+
const form = getForm(formRef, internalRef);
|
|
26
|
+
if (form) setFieldOrder(getFieldOrder(form));
|
|
23
27
|
setStamp(Date.now());
|
|
24
|
-
}, []);
|
|
28
|
+
}, [formRef]);
|
|
29
|
+
const context = useMemo(() => ({
|
|
30
|
+
fieldOrder,
|
|
31
|
+
getForm: () => getForm(formRef, internalRef)
|
|
32
|
+
}), [fieldOrder, formRef]);
|
|
25
33
|
useEffect(() => {
|
|
26
|
-
const form = formRef
|
|
34
|
+
const form = getForm(formRef, internalRef);
|
|
27
35
|
form?.addEventListener("submit", onSubmit);
|
|
28
36
|
if (form && hasErrors && autoScroll) {
|
|
29
37
|
if (typeof form?.scrollIntoView === "function") form.scrollIntoView({ behavior: "smooth" });
|
|
@@ -46,18 +54,21 @@ const ValidationSummary = ({ autoScroll = true, children, className, forceMount
|
|
|
46
54
|
inert: true,
|
|
47
55
|
ref: mergeRefs(internalRef, ref)
|
|
48
56
|
});
|
|
49
|
-
return /* @__PURE__ */
|
|
57
|
+
return /* @__PURE__ */ jsx("div", {
|
|
50
58
|
...componentProps,
|
|
51
59
|
"aria-live": "assertive",
|
|
52
60
|
ref: mergeRefs(internalRef, ref),
|
|
53
61
|
role: "alert",
|
|
54
62
|
tabIndex: -1,
|
|
55
|
-
children:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
children: /* @__PURE__ */ jsxs(ValidationSummaryContext.Provider, {
|
|
64
|
+
value: context,
|
|
65
|
+
children: [
|
|
66
|
+
slots.ValidationSummaryHeading,
|
|
67
|
+
slots.ValidationSummaryPrompt,
|
|
68
|
+
slots.ValidationSummaryErrors,
|
|
69
|
+
slots.Children
|
|
70
|
+
]
|
|
71
|
+
})
|
|
61
72
|
});
|
|
62
73
|
};
|
|
63
74
|
const ValidationSummaryHeading = ({ as = "h2", children, className }) => {
|
|
@@ -71,20 +82,28 @@ const focusable = [
|
|
|
71
82
|
"TEXTAREA",
|
|
72
83
|
"SELECT"
|
|
73
84
|
];
|
|
85
|
+
const focusOptions = {
|
|
86
|
+
focusVisible: true,
|
|
87
|
+
preventScroll: true
|
|
88
|
+
};
|
|
74
89
|
const ValidationSummaryErrors = ({ errors }) => {
|
|
75
90
|
const errorsArray = (errors && Object.entries(errors)) ?? [];
|
|
91
|
+
const { fieldOrder, getForm } = useValidationSummary();
|
|
92
|
+
const sortedErrors = sortErrorsByFieldOrder(errorsArray, fieldOrder);
|
|
76
93
|
return errorsArray.length ? /* @__PURE__ */ jsx("div", {
|
|
77
94
|
className: "coop-validation-summary--errors",
|
|
78
|
-
children: /* @__PURE__ */ jsx("ul", { children:
|
|
95
|
+
children: /* @__PURE__ */ jsx("ul", { children: sortedErrors.map(([field, error]) => {
|
|
79
96
|
const id = error.ref?.id ? sanitizeFieldId(error.ref.id) : void 0;
|
|
80
97
|
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
|
|
81
98
|
href: "#",
|
|
82
99
|
onClick: (e) => {
|
|
83
100
|
e.preventDefault();
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
const form = getForm();
|
|
102
|
+
const el = form && getFieldElement(form, field, id);
|
|
103
|
+
if (el instanceof HTMLElement) {
|
|
104
|
+
el.scrollIntoView({ block: "center" });
|
|
105
|
+
if (focusable.includes(el.nodeName)) el.focus(focusOptions);
|
|
106
|
+
else el.querySelector("input")?.focus(focusOptions);
|
|
88
107
|
}
|
|
89
108
|
},
|
|
90
109
|
children: error.message
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
//#region src/components/ValidationSummary/validationsummary-utils.d.ts
|
|
3
|
+
export interface FieldError {
|
|
4
|
+
message?: string | number;
|
|
5
|
+
ref?: {
|
|
6
|
+
focus?: () => void;
|
|
7
|
+
id?: string;
|
|
8
|
+
};
|
|
9
|
+
type?: string | number;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
//#region src/components/ValidationSummary/validationsummary-utils.ts
|
|
2
2
|
const sanitizeFieldId = (id) => id?.startsWith("#") ? id.slice(1) : id;
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const getFieldKeys = (field, id) => [
|
|
4
|
+
...id ? [sanitizeFieldId(id)] : [],
|
|
5
|
+
`${field}_display`,
|
|
6
|
+
field
|
|
7
|
+
];
|
|
8
|
+
const getFieldElement = (form, field, id) => {
|
|
9
|
+
for (const key of getFieldKeys(field, id)) {
|
|
10
|
+
const item = form.elements.namedItem(key);
|
|
11
|
+
const el = item instanceof RadioNodeList ? item[0] : item;
|
|
12
|
+
if (el instanceof Element) return el;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
const getForm = (formRef, internalRef) => {
|
|
17
|
+
return formRef?.current ?? internalRef?.current?.closest("form") ?? null;
|
|
18
|
+
};
|
|
19
|
+
const isFormControl = (el) => el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement;
|
|
20
|
+
const getFieldOrder = (form) => [...form.elements].filter(isFormControl).map(({ id, name }) => ({
|
|
21
|
+
id,
|
|
22
|
+
name
|
|
23
|
+
}));
|
|
24
|
+
const getFieldPosition = (fieldOrder, [field, error]) => getFieldKeys(field, error.ref?.id).map((key) => fieldOrder.findIndex((f) => f.id === key || f.name === key)).find((index) => index !== -1) ?? fieldOrder.length;
|
|
25
|
+
const sortErrorsByFieldOrder = (errorsArray, fieldOrder) => {
|
|
26
|
+
if (!fieldOrder.length) return errorsArray;
|
|
27
|
+
return errorsArray.map((entry) => ({
|
|
28
|
+
entry,
|
|
29
|
+
position: getFieldPosition(fieldOrder, entry)
|
|
30
|
+
})).sort((a, b) => a.position - b.position).map(({ entry }) => entry);
|
|
8
31
|
};
|
|
9
32
|
//#endregion
|
|
10
|
-
export { getFieldElement, sanitizeFieldId };
|
|
33
|
+
export { getFieldElement, getFieldOrder, getFieldPosition, getForm, sanitizeFieldId, sortErrorsByFieldOrder };
|
package/dist/hooks/useSlots.js
CHANGED
|
@@ -1,33 +1,9 @@
|
|
|
1
|
+
import { getSlotName } from "../utils/slots.js";
|
|
1
2
|
import React from "react";
|
|
2
3
|
//#region src/hooks/useSlots.ts
|
|
3
4
|
function isKey(x, k) {
|
|
4
5
|
return k in x;
|
|
5
6
|
}
|
|
6
|
-
function isLazy(type) {
|
|
7
|
-
return typeof type === "object" && type !== null && "_payload" in type && "_init" in type;
|
|
8
|
-
}
|
|
9
|
-
function resolveComponentName(child) {
|
|
10
|
-
if (child && typeof child === "object" && "type" in child) {
|
|
11
|
-
const type = child.type;
|
|
12
|
-
if (isLazy(type)) try {
|
|
13
|
-
const resolved = type._init(type._payload);
|
|
14
|
-
if ("slot" in resolved) return resolved.slot.name;
|
|
15
|
-
return resolved.displayName ?? resolved.name ?? null;
|
|
16
|
-
} catch {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
if (typeof type === "function") {
|
|
20
|
-
if ("slot" in type) return type.slot.name;
|
|
21
|
-
return type.displayName ?? type.name ?? null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
function getSlotName(node) {
|
|
27
|
-
const slotName = resolveComponentName(node);
|
|
28
|
-
if (slotName) return slotName.replaceAll(".", "");
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
7
|
function useSlots(componentSlots, children, options) {
|
|
32
8
|
return React.Children.toArray(children).reduce((slots, child) => {
|
|
33
9
|
const slotName = getSlotName(child);
|
|
@@ -39,4 +15,4 @@ function useSlots(componentSlots, children, options) {
|
|
|
39
15
|
}, { ...componentSlots });
|
|
40
16
|
}
|
|
41
17
|
//#endregion
|
|
42
|
-
export {
|
|
18
|
+
export { isKey, useSlots };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
//#region src/types/index.d.ts
|
|
2
2
|
export type StandardSizes = "sm" | "md" | "lg";
|
|
3
3
|
export type StandardSizesWithNone = "none" | "sm" | "md" | "lg";
|
|
4
|
+
export interface SlotConfig {
|
|
5
|
+
field?: boolean;
|
|
6
|
+
inline?: boolean;
|
|
7
|
+
name: string;
|
|
8
|
+
suffix?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface LazyPayload<T> {
|
|
11
|
+
default: T;
|
|
12
|
+
}
|
|
13
|
+
export interface LazyComponent<T extends React.ComponentType<any>> {
|
|
14
|
+
_init: (payload: unknown) => T;
|
|
15
|
+
_payload: Promise<LazyPayload<T>> | LazyPayload<T>;
|
|
16
|
+
$$typeof: typeof REACT_LAZY_TYPE;
|
|
17
|
+
}
|
|
18
|
+
export declare const REACT_LAZY_TYPE: any;
|
|
4
19
|
//#endregion
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/utils/classes.d.ts
|
|
2
|
+
export declare const bgPropToClass: (color: string, userClasses?: string) => string;
|
|
3
|
+
export declare const hasUserBg: (userClasses?: string) => boolean;
|
|
4
|
+
export declare const hasUserBorder: (userClasses?: string) => boolean;
|
|
5
|
+
export declare const bgClassToColor: (userClasses: string) => string | null;
|
|
6
|
+
//#endregion
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/utils/classes.ts
|
|
2
|
+
const bgPropToClass = (color, userClasses) => userClasses?.includes("bg-") ? "" : `bg-${color}`;
|
|
3
|
+
const hasUserBg = (userClasses) => {
|
|
4
|
+
return typeof userClasses === "string" && userClasses?.includes("bg-") ? true : false;
|
|
5
|
+
};
|
|
6
|
+
const hasUserBorder = (userClasses) => {
|
|
7
|
+
return typeof userClasses === "string" && userClasses?.includes("border-") ? true : false;
|
|
8
|
+
};
|
|
9
|
+
const bgClassToColor = (userClasses) => {
|
|
10
|
+
return /bg-([^\s]+)/.exec(userClasses)?.[1] ?? null;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { bgClassToColor, bgPropToClass, hasUserBg, hasUserBorder };
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export declare const hasUserBorder: (userClasses?: string) => boolean;
|
|
5
|
-
export declare const bgClassToColor: (userClasses: string) => string | null;
|
|
6
|
-
export declare function getSlotProp<R = unknown>(slot: React.ReactNode, prop: string): R | undefined;
|
|
7
|
-
//#endregion
|
|
1
|
+
import { bgClassToColor, bgPropToClass, hasUserBg, hasUserBorder } from "./classes.js";
|
|
2
|
+
import { forEachSlot, getSlotName, getSlotProp, isLazy, resolveComponentName } from "./slots.js";
|
|
3
|
+
export { bgClassToColor, bgPropToClass, forEachSlot, getSlotName, getSlotProp, hasUserBg, hasUserBorder, isLazy, resolveComponentName };
|
package/dist/utils/index.js
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const hasUserBg = (userClasses) => {
|
|
5
|
-
return typeof userClasses === "string" && userClasses?.includes("bg-") ? true : false;
|
|
6
|
-
};
|
|
7
|
-
const hasUserBorder = (userClasses) => {
|
|
8
|
-
return typeof userClasses === "string" && userClasses?.includes("border-") ? true : false;
|
|
9
|
-
};
|
|
10
|
-
const bgClassToColor = (userClasses) => {
|
|
11
|
-
return /bg-([^\s]+)/.exec(userClasses)?.[1] ?? null;
|
|
12
|
-
};
|
|
13
|
-
function getSlotProp(slot, prop) {
|
|
14
|
-
if (isValidElement(slot) && prop in slot.props) return slot.props[prop];
|
|
15
|
-
}
|
|
16
|
-
//#endregion
|
|
17
|
-
export { bgClassToColor, bgPropToClass, getSlotProp, hasUserBg, hasUserBorder };
|
|
1
|
+
import { bgClassToColor, bgPropToClass, hasUserBg, hasUserBorder } from "./classes.js";
|
|
2
|
+
import { forEachSlot, getSlotName, getSlotProp, isLazy, resolveComponentName } from "./slots.js";
|
|
3
|
+
export { bgClassToColor, bgPropToClass, forEachSlot, getSlotName, getSlotProp, hasUserBg, hasUserBorder, isLazy, resolveComponentName };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LazyComponent, SlotConfig } from "../types/index.js";
|
|
2
|
+
import React, { ReactElement, ReactNode } from "react";
|
|
3
|
+
//#region src/utils/slots.d.ts
|
|
4
|
+
export declare function getSlotProp<R = unknown>(slot: React.ReactNode, prop: string): R | undefined;
|
|
5
|
+
export declare const forEachSlot: (children: ReactNode, fn: (slot: SlotConfig, child: ReactElement) => void) => void;
|
|
6
|
+
export declare function isLazy<T extends React.ComponentType<any>>(type: unknown): type is LazyComponent<T>;
|
|
7
|
+
export declare function resolveComponentName(child: React.ReactNode): string | null;
|
|
8
|
+
export declare function getSlotName(node: React.ReactNode): string | false;
|
|
9
|
+
//#endregion
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isValidElement } from "react";
|
|
2
|
+
//#region src/utils/slots.ts
|
|
3
|
+
function getSlotProp(slot, prop) {
|
|
4
|
+
if (isValidElement(slot) && prop in slot.props) return slot.props[prop];
|
|
5
|
+
}
|
|
6
|
+
const forEachSlot = (children, fn) => {
|
|
7
|
+
Array.from([children].flat()).forEach((child) => {
|
|
8
|
+
if (isValidElement(child) && typeof child.type === "function" && "slot" in child.type) fn(child.type.slot, child);
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
function isLazy(type) {
|
|
12
|
+
return typeof type === "object" && type !== null && "_payload" in type && "_init" in type;
|
|
13
|
+
}
|
|
14
|
+
function resolveComponentName(child) {
|
|
15
|
+
if (child && typeof child === "object" && "type" in child) {
|
|
16
|
+
const type = child.type;
|
|
17
|
+
if (isLazy(type)) try {
|
|
18
|
+
const resolved = type._init(type._payload);
|
|
19
|
+
if ("slot" in resolved) return resolved.slot.name;
|
|
20
|
+
return resolved.displayName ?? resolved.name ?? null;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (typeof type === "function") {
|
|
25
|
+
if ("slot" in type) return type.slot.name;
|
|
26
|
+
return type.displayName ?? type.name ?? null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function getSlotName(node) {
|
|
32
|
+
const slotName = resolveComponentName(node);
|
|
33
|
+
if (slotName) return slotName.replaceAll(".", "");
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { forEachSlot, getSlotName, getSlotProp, isLazy, resolveComponentName };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coopdigital/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.29.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"storybook": "$storybook"
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
110
|
-
"@coopdigital/styles": "^1.
|
|
110
|
+
"@coopdigital/styles": "^1.20.0",
|
|
111
111
|
"@radix-ui/react-popover": "^1.1.23",
|
|
112
112
|
"clsx": "^2.1.1",
|
|
113
113
|
"date-fns": "^4.4.0",
|
|
@@ -116,5 +116,5 @@
|
|
|
116
116
|
"merge-refs": "^2.0.0",
|
|
117
117
|
"react-day-picker": "^10.0.2"
|
|
118
118
|
},
|
|
119
|
-
"gitHead": "
|
|
119
|
+
"gitHead": "e141d3a3d63b5c85e82237bfa0417b62da9ea548"
|
|
120
120
|
}
|
|
@@ -3,7 +3,7 @@ import type { InputHTMLAttributes, JSX, Ref } from "react"
|
|
|
3
3
|
|
|
4
4
|
import clsx from "clsx"
|
|
5
5
|
|
|
6
|
-
import { useField } from "@/components/Field/Field.context"
|
|
6
|
+
import { getDescribedBy, getFieldError, useField } from "@/components/Field/Field.context"
|
|
7
7
|
import { StandardSizes } from "@/types"
|
|
8
8
|
|
|
9
9
|
export interface CheckboxProps extends Omit<
|
|
@@ -18,8 +18,8 @@ export interface CheckboxProps extends Omit<
|
|
|
18
18
|
error?: boolean
|
|
19
19
|
/** Specify the component id. Will be auto-generated if not set. */
|
|
20
20
|
id?: string
|
|
21
|
-
/** Specify the component name. */
|
|
22
|
-
name
|
|
21
|
+
/** Specify the component name. Without a name, the value is not submitted with a form. */
|
|
22
|
+
name?: string
|
|
23
23
|
/** Specify a custom React ref for this component. */
|
|
24
24
|
ref?: Ref<HTMLInputElement>
|
|
25
25
|
/** Specify the component size. */
|
|
@@ -27,9 +27,10 @@ export interface CheckboxProps extends Omit<
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const Checkbox = ({
|
|
30
|
+
"aria-describedby": ariaDescribedBy,
|
|
30
31
|
className,
|
|
31
32
|
disabled,
|
|
32
|
-
error
|
|
33
|
+
error,
|
|
33
34
|
id,
|
|
34
35
|
name,
|
|
35
36
|
ref,
|
|
@@ -37,10 +38,12 @@ const Checkbox = ({
|
|
|
37
38
|
...props
|
|
38
39
|
}: CheckboxProps): JSX.Element => {
|
|
39
40
|
const field = useField()
|
|
41
|
+
const hasError = getFieldError(error, field)
|
|
40
42
|
|
|
41
43
|
const componentProps = {
|
|
44
|
+
"aria-describedby": getDescribedBy(ariaDescribedBy, field),
|
|
45
|
+
"aria-invalid": hasError || undefined,
|
|
42
46
|
className: clsx("coop-checkbox", className),
|
|
43
|
-
"data-error": error || undefined,
|
|
44
47
|
"data-size": size ?? undefined,
|
|
45
48
|
disabled,
|
|
46
49
|
id: id ?? field?.id,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
normalizeOptions,
|
|
22
22
|
} from "@/components/Combobox/combobox-utils"
|
|
23
23
|
import { ComboboxContext, useComboboxContext } from "@/components/Combobox/Combobox.context"
|
|
24
|
-
import { useField } from "@/components/Field/Field.context"
|
|
24
|
+
import { getFieldError, useField } from "@/components/Field/Field.context"
|
|
25
25
|
import { ChevronDownIcon } from "@/components/icons"
|
|
26
26
|
import { TextInput, TextInputProps } from "@/components/TextInput"
|
|
27
27
|
import { useSlots } from "@/hooks"
|
|
@@ -156,6 +156,7 @@ export const Combobox = ({
|
|
|
156
156
|
}: ComboboxProps): JSX.Element | null => {
|
|
157
157
|
const field = useField()
|
|
158
158
|
const uid = id ?? field?.id
|
|
159
|
+
const hasError = getFieldError(error, field)
|
|
159
160
|
|
|
160
161
|
const slots = useSlots(componentSlots, children)
|
|
161
162
|
|
|
@@ -166,7 +167,7 @@ export const Combobox = ({
|
|
|
166
167
|
|
|
167
168
|
const componentProps = {
|
|
168
169
|
className: clsx("coop-combobox", className),
|
|
169
|
-
"data-
|
|
170
|
+
"data-field": "",
|
|
170
171
|
"data-size": size ?? undefined,
|
|
171
172
|
id: uid,
|
|
172
173
|
...props,
|
|
@@ -219,7 +220,9 @@ export const Combobox = ({
|
|
|
219
220
|
// }
|
|
220
221
|
|
|
221
222
|
return (
|
|
222
|
-
<ComboboxContext.Provider
|
|
223
|
+
<ComboboxContext.Provider
|
|
224
|
+
value={{ disabled, error: hasError, items: inputItems, props: comboboxProps, size }}
|
|
225
|
+
>
|
|
223
226
|
<div {...componentProps} ref={ref}>
|
|
224
227
|
{slots.ComboboxControls}
|
|
225
228
|
{slots.ComboboxOptions}
|
|
@@ -233,6 +236,7 @@ const ComboboxInput = ({ className, placeholder, ...props }: ComboboxInputProps)
|
|
|
233
236
|
const componentProps = {
|
|
234
237
|
className,
|
|
235
238
|
disabled: combobox.disabled,
|
|
239
|
+
error: combobox.error,
|
|
236
240
|
placeholder,
|
|
237
241
|
size: combobox.size,
|
|
238
242
|
...props,
|
|
@@ -16,7 +16,7 @@ import { DayPicker } from "react-day-picker"
|
|
|
16
16
|
import type { StandardSizes } from "@/types"
|
|
17
17
|
|
|
18
18
|
import { Button } from "@/components/Button"
|
|
19
|
-
import { useField } from "@/components/Field/Field.context"
|
|
19
|
+
import { getFieldError, useField } from "@/components/Field/Field.context"
|
|
20
20
|
import { CalendarIcon, CrossIcon, TickIcon } from "@/components/icons"
|
|
21
21
|
import { Popover } from "@/components/Popover"
|
|
22
22
|
import { TextInput } from "@/components/TextInput"
|
|
@@ -163,7 +163,7 @@ const DatePicker = ({
|
|
|
163
163
|
disabled,
|
|
164
164
|
disabledDates,
|
|
165
165
|
endDate,
|
|
166
|
-
error
|
|
166
|
+
error,
|
|
167
167
|
id,
|
|
168
168
|
initialDate = now,
|
|
169
169
|
initialSelected = false,
|
|
@@ -178,6 +178,7 @@ const DatePicker = ({
|
|
|
178
178
|
...props
|
|
179
179
|
}: DatePickerProps): JSX.Element => {
|
|
180
180
|
const field = useField()
|
|
181
|
+
const hasError = getFieldError(error, field)
|
|
181
182
|
|
|
182
183
|
initialDate = getInitialDate(mode, initialDate)
|
|
183
184
|
|
|
@@ -262,7 +263,7 @@ const DatePicker = ({
|
|
|
262
263
|
const componentProps = {
|
|
263
264
|
className: clsx("coop-datepicker", className),
|
|
264
265
|
"data-disabled": disabled ?? undefined,
|
|
265
|
-
"data-
|
|
266
|
+
"data-field": "",
|
|
266
267
|
"data-mode": mode,
|
|
267
268
|
...props,
|
|
268
269
|
}
|
|
@@ -315,6 +316,7 @@ const DatePicker = ({
|
|
|
315
316
|
autoComplete="off"
|
|
316
317
|
className="coop-datepicker-input"
|
|
317
318
|
disabled={disabled}
|
|
319
|
+
error={hasError}
|
|
318
320
|
id={valueProps.id + "_display"}
|
|
319
321
|
//maxLength={dateFormat.length}
|
|
320
322
|
name={name + "_display"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
import { createContext, useContext } from "react"
|
|
3
3
|
|
|
4
|
+
import { joinIds, type MarkerIds } from "@/components/FieldMarkers/fieldmarkers-utils"
|
|
5
|
+
|
|
4
6
|
export interface FieldConfig {
|
|
5
7
|
disabled: boolean
|
|
6
8
|
id: string
|
|
@@ -8,8 +10,9 @@ export interface FieldConfig {
|
|
|
8
10
|
suffix?: string
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
export interface FieldContextValue {
|
|
13
|
+
export interface FieldContextValue extends Partial<MarkerIds> {
|
|
12
14
|
disabled?: boolean
|
|
15
|
+
error?: boolean
|
|
13
16
|
id?: string
|
|
14
17
|
inline?: boolean
|
|
15
18
|
suffix?: string
|
|
@@ -17,12 +20,15 @@ export interface FieldContextValue {
|
|
|
17
20
|
|
|
18
21
|
export const FieldContext = createContext<FieldContextValue | null>(null)
|
|
19
22
|
|
|
20
|
-
export const useField = () =>
|
|
21
|
-
const context = useContext(FieldContext)
|
|
23
|
+
export const useField = () => useContext(FieldContext)
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// Combine the hint and error ids from the Field with any aria-describedby set on the control
|
|
26
|
+
export const getDescribedBy = (
|
|
27
|
+
describedBy: string | undefined,
|
|
28
|
+
field: FieldContextValue | null
|
|
29
|
+
): string | undefined => joinIds(field?.describedBy, describedBy)
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
export const getFieldError = (
|
|
32
|
+
error: boolean | undefined,
|
|
33
|
+
field: FieldContextValue | null
|
|
34
|
+
): boolean => error ?? field?.error ?? false
|