@cdx-ui/utils 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/lib/commonjs/common/accessibilityUtils.js +9 -0
- package/lib/commonjs/common/accessibilityUtils.js.map +1 -0
- package/lib/commonjs/common/cn.js +12 -0
- package/lib/commonjs/common/cn.js.map +1 -0
- package/lib/commonjs/common/composeEventHandlers.js +14 -0
- package/lib/commonjs/common/composeEventHandlers.js.map +1 -0
- package/lib/commonjs/common/getSpacedChild.js +27 -0
- package/lib/commonjs/common/getSpacedChild.js.map +1 -0
- package/lib/commonjs/common/index.js +48 -0
- package/lib/commonjs/common/index.js.map +1 -0
- package/lib/commonjs/common/mergeRefs.js +18 -0
- package/lib/commonjs/common/mergeRefs.js.map +1 -0
- package/lib/commonjs/common/useControllableState.js +65 -0
- package/lib/commonjs/common/useControllableState.js.map +1 -0
- package/lib/commonjs/context/createContext.js +31 -0
- package/lib/commonjs/context/createContext.js.map +1 -0
- package/lib/commonjs/context/index.js +13 -0
- package/lib/commonjs/context/index.js.map +1 -0
- package/lib/commonjs/form-control/index.js +25 -0
- package/lib/commonjs/form-control/index.js.map +1 -0
- package/lib/commonjs/form-control/useFormControl.js +51 -0
- package/lib/commonjs/form-control/useFormControl.js.map +1 -0
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/style/context/index.js +18 -0
- package/lib/commonjs/style/context/index.js.map +1 -0
- package/lib/commonjs/style/index.js +28 -0
- package/lib/commonjs/style/index.js.map +1 -0
- package/lib/commonjs/style/withStyleContext/index.js +33 -0
- package/lib/commonjs/style/withStyleContext/index.js.map +1 -0
- package/lib/module/common/accessibilityUtils.js +4 -0
- package/lib/module/common/accessibilityUtils.js.map +1 -0
- package/lib/module/common/cn.js +8 -0
- package/lib/module/common/cn.js.map +1 -0
- package/lib/module/common/composeEventHandlers.js +10 -0
- package/lib/module/common/composeEventHandlers.js.map +1 -0
- package/lib/module/common/getSpacedChild.js +22 -0
- package/lib/module/common/getSpacedChild.js.map +1 -0
- package/lib/module/common/index.js +9 -0
- package/lib/module/common/index.js.map +1 -0
- package/lib/module/common/mergeRefs.js +14 -0
- package/lib/module/common/mergeRefs.js.map +1 -0
- package/lib/module/common/useControllableState.js +62 -0
- package/lib/module/common/useControllableState.js.map +1 -0
- package/lib/module/context/createContext.js +27 -0
- package/lib/module/context/createContext.js.map +1 -0
- package/lib/module/context/index.js +4 -0
- package/lib/module/context/index.js.map +1 -0
- package/lib/module/form-control/index.js +4 -0
- package/lib/module/form-control/index.js.map +1 -0
- package/lib/module/form-control/useFormControl.js +43 -0
- package/lib/module/form-control/useFormControl.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/style/context/index.js +12 -0
- package/lib/module/style/context/index.js.map +1 -0
- package/lib/module/style/index.js +5 -0
- package/lib/module/style/index.js.map +1 -0
- package/lib/module/style/withStyleContext/index.js +28 -0
- package/lib/module/style/withStyleContext/index.js.map +1 -0
- package/lib/typescript/common/accessibilityUtils.d.ts +2 -0
- package/lib/typescript/common/accessibilityUtils.d.ts.map +1 -0
- package/lib/typescript/common/cn.d.ts +3 -0
- package/lib/typescript/common/cn.d.ts.map +1 -0
- package/lib/typescript/common/composeEventHandlers.d.ts +2 -0
- package/lib/typescript/common/composeEventHandlers.d.ts.map +1 -0
- package/lib/typescript/common/getSpacedChild.d.ts +5 -0
- package/lib/typescript/common/getSpacedChild.d.ts.map +1 -0
- package/lib/typescript/common/index.d.ts +7 -0
- package/lib/typescript/common/index.d.ts.map +1 -0
- package/lib/typescript/common/mergeRefs.d.ts +3 -0
- package/lib/typescript/common/mergeRefs.d.ts.map +1 -0
- package/lib/typescript/common/useControllableState.d.ts +8 -0
- package/lib/typescript/common/useControllableState.d.ts.map +1 -0
- package/lib/typescript/context/createContext.d.ts +6 -0
- package/lib/typescript/context/createContext.d.ts.map +1 -0
- package/lib/typescript/context/index.d.ts +2 -0
- package/lib/typescript/context/index.d.ts.map +1 -0
- package/lib/typescript/form-control/index.d.ts +2 -0
- package/lib/typescript/form-control/index.d.ts.map +1 -0
- package/lib/typescript/form-control/useFormControl.d.ts +38 -0
- package/lib/typescript/form-control/useFormControl.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/style/context/index.d.ts +5 -0
- package/lib/typescript/style/context/index.d.ts.map +1 -0
- package/lib/typescript/style/index.d.ts +3 -0
- package/lib/typescript/style/index.d.ts.map +1 -0
- package/lib/typescript/style/withStyleContext/index.d.ts +6 -0
- package/lib/typescript/style/withStyleContext/index.d.ts.map +1 -0
- package/package.json +65 -0
- package/src/common/accessibilityUtils.ts +1 -0
- package/src/common/cn.ts +6 -0
- package/src/common/composeEventHandlers.ts +7 -0
- package/src/common/getSpacedChild.tsx +32 -0
- package/src/common/index.ts +6 -0
- package/src/common/mergeRefs.ts +13 -0
- package/src/common/useControllableState.ts +75 -0
- package/src/context/createContext.tsx +27 -0
- package/src/context/index.ts +1 -0
- package/src/form-control/index.ts +8 -0
- package/src/form-control/useFormControl.tsx +63 -0
- package/src/index.ts +4 -0
- package/src/style/context/index.tsx +14 -0
- package/src/style/index.tsx +2 -0
- package/src/style/withStyleContext/index.tsx +36 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// This project uses code from WorkOS/Radix Primitives.
|
|
2
|
+
// The code is licensed under the MIT License.
|
|
3
|
+
// https://github.com/radix-ui/primitives/tree/main
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
export interface UseControllableStateParams<T> {
|
|
8
|
+
prop?: T | undefined;
|
|
9
|
+
defaultProp?: T | undefined;
|
|
10
|
+
onChange?: (state: T) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type SetStateFn<T> = (prevState?: T) => T;
|
|
14
|
+
|
|
15
|
+
function useControllableState<T>({
|
|
16
|
+
prop,
|
|
17
|
+
defaultProp,
|
|
18
|
+
onChange = () => {},
|
|
19
|
+
}: UseControllableStateParams<T>) {
|
|
20
|
+
const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({ defaultProp, onChange });
|
|
21
|
+
const isControlled = prop !== undefined;
|
|
22
|
+
const value = isControlled ? prop : uncontrolledProp;
|
|
23
|
+
const handleChange = useCallbackRef(onChange);
|
|
24
|
+
|
|
25
|
+
const setValue: React.Dispatch<React.SetStateAction<T | undefined>> = useCallback(
|
|
26
|
+
(nextValue) => {
|
|
27
|
+
if (isControlled) {
|
|
28
|
+
const setter = nextValue as SetStateFn<T>;
|
|
29
|
+
const value = typeof nextValue === 'function' ? setter(prop) : nextValue;
|
|
30
|
+
if (value !== prop) handleChange(value as T);
|
|
31
|
+
} else {
|
|
32
|
+
setUncontrolledProp(nextValue);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
[isControlled, prop, setUncontrolledProp, handleChange],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return [value, setValue] as const;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function useUncontrolledState<T>({
|
|
42
|
+
defaultProp,
|
|
43
|
+
onChange,
|
|
44
|
+
}: Omit<UseControllableStateParams<T>, 'prop'>) {
|
|
45
|
+
const uncontrolledState = useState<T | undefined>(defaultProp);
|
|
46
|
+
const [value] = uncontrolledState;
|
|
47
|
+
const prevValueRef = useRef(value);
|
|
48
|
+
const handleChange = useCallbackRef(onChange);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (prevValueRef.current !== value) {
|
|
52
|
+
handleChange(value as T);
|
|
53
|
+
prevValueRef.current = value;
|
|
54
|
+
}
|
|
55
|
+
}, [value, prevValueRef, handleChange]);
|
|
56
|
+
|
|
57
|
+
return uncontrolledState;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
|
|
62
|
+
* prop or avoid re-executing effects when passed as a dependency
|
|
63
|
+
*/
|
|
64
|
+
function useCallbackRef<T extends (...args: any[]) => any>(callback: T | undefined): T {
|
|
65
|
+
const callbackRef = useRef(callback);
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
callbackRef.current = callback;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// https://github.com/facebook/react/issues/19240
|
|
72
|
+
return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { useControllableState };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentType,
|
|
3
|
+
createContext as createReactContext,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useContext,
|
|
6
|
+
} from 'react';
|
|
7
|
+
|
|
8
|
+
export function createContext<T>(displayName: string) {
|
|
9
|
+
const Context = createReactContext<T | undefined>(undefined);
|
|
10
|
+
|
|
11
|
+
const Provider: ComponentType<{ children: ReactNode; value: T }> = (props) => {
|
|
12
|
+
const { children, value } = props;
|
|
13
|
+
return <Context.Provider value={value}>{children}</Context.Provider>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
Provider.displayName = `${displayName}Provider`;
|
|
17
|
+
|
|
18
|
+
function useC(): T {
|
|
19
|
+
const context = useContext(Context);
|
|
20
|
+
if (context === undefined) {
|
|
21
|
+
throw new Error(`use${displayName} must be used within a ${displayName}Provider`);
|
|
22
|
+
}
|
|
23
|
+
return context;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [Provider, useC] as const;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createContext } from './createContext';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ariaAttr } from '../common/accessibilityUtils';
|
|
3
|
+
|
|
4
|
+
type HTMLProps = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export interface FormControlState {
|
|
7
|
+
isRequired: boolean;
|
|
8
|
+
isInvalid: boolean;
|
|
9
|
+
isDisabled: boolean;
|
|
10
|
+
isReadOnly: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FormControlContextValue extends FormControlState {
|
|
14
|
+
hasFeedbackText: boolean;
|
|
15
|
+
hasHelpText: boolean;
|
|
16
|
+
id: string;
|
|
17
|
+
labelId: string;
|
|
18
|
+
feedbackId: string;
|
|
19
|
+
helpTextId: string;
|
|
20
|
+
htmlProps: HTMLProps;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const FormControlContext = React.createContext<Partial<FormControlContextValue>>({});
|
|
24
|
+
|
|
25
|
+
export interface FormControlFieldProps extends Partial<FormControlState> {
|
|
26
|
+
id?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hook that merges local field props with the nearest FormControl context
|
|
31
|
+
* and returns normalised HTML / ARIA attributes for input elements.
|
|
32
|
+
*/
|
|
33
|
+
export function useFormControl(props: FormControlFieldProps) {
|
|
34
|
+
const field = useFormControlContext();
|
|
35
|
+
const describedBy: string[] = [];
|
|
36
|
+
|
|
37
|
+
if (field?.hasFeedbackText && field.feedbackId) {
|
|
38
|
+
describedBy.push(field.feedbackId);
|
|
39
|
+
}
|
|
40
|
+
if (field?.hasHelpText && field.helpTextId) {
|
|
41
|
+
describedBy.push(field.helpTextId);
|
|
42
|
+
}
|
|
43
|
+
const ariaDescribedBy = describedBy.join(' ');
|
|
44
|
+
|
|
45
|
+
const { isInvalid, isDisabled, isReadOnly, isRequired, ...cleanProps } = props;
|
|
46
|
+
const id = props.id ?? (field?.id ? `${field.id}-input` : undefined);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...cleanProps,
|
|
50
|
+
id,
|
|
51
|
+
disabled: isDisabled || field?.isDisabled,
|
|
52
|
+
readOnly: isReadOnly || field?.isReadOnly,
|
|
53
|
+
required: isRequired || field?.isRequired,
|
|
54
|
+
'aria-invalid': ariaAttr(isInvalid || field?.isInvalid),
|
|
55
|
+
'aria-required': ariaAttr(isRequired || field?.isRequired),
|
|
56
|
+
'aria-readonly': ariaAttr(isReadOnly || field?.isReadOnly),
|
|
57
|
+
'aria-describedby': ariaDescribedBy || undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const useFormControlContext = () => {
|
|
62
|
+
return React.useContext(FormControlContext);
|
|
63
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export type StyleContextMap = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export const ParentContext = createContext<StyleContextMap>({});
|
|
6
|
+
|
|
7
|
+
export const useParentContext = (): StyleContextMap => {
|
|
8
|
+
return useContext(ParentContext);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const useStyleContext = (scope = 'Global') => {
|
|
12
|
+
const parentContextValues = useParentContext();
|
|
13
|
+
return parentContextValues[scope] as Record<string, unknown>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentPropsWithoutRef,
|
|
3
|
+
type ComponentRef,
|
|
4
|
+
type ElementType,
|
|
5
|
+
type ForwardRefExoticComponent,
|
|
6
|
+
type RefAttributes,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useMemo,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { ParentContext, type StyleContextMap, useParentContext } from '../context';
|
|
11
|
+
|
|
12
|
+
export interface WithStyleContextProps {
|
|
13
|
+
context?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const withStyleContext = <T extends ElementType>(Component: T, scope = 'Global') => {
|
|
17
|
+
type Ref = ComponentRef<T>;
|
|
18
|
+
type Props = ComponentPropsWithoutRef<T> & WithStyleContextProps;
|
|
19
|
+
|
|
20
|
+
const Wrapped = forwardRef<Ref, Props>(({ context, ...props }, ref) => {
|
|
21
|
+
const parentContextValues = useParentContext();
|
|
22
|
+
|
|
23
|
+
const contextValues: StyleContextMap = useMemo(() => {
|
|
24
|
+
return { ...parentContextValues, [scope]: context };
|
|
25
|
+
}, [parentContextValues, context]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ParentContext.Provider value={contextValues}>
|
|
29
|
+
{/* @ts-expect-error -- generic component spread is not fully inferrable */}
|
|
30
|
+
<Component {...props} ref={ref} />
|
|
31
|
+
</ParentContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return Wrapped as ForwardRefExoticComponent<Props & RefAttributes<Ref>>;
|
|
36
|
+
};
|