@cdx-ui/primitives 0.0.1-beta.10 → 0.0.1-beta.12

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.
Files changed (72) hide show
  1. package/README.md +1 -0
  2. package/lib/commonjs/index.js +12 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/radio/context.js +14 -0
  5. package/lib/commonjs/radio/context.js.map +1 -0
  6. package/lib/commonjs/radio/createRadioGroup.js +66 -0
  7. package/lib/commonjs/radio/createRadioGroup.js.map +1 -0
  8. package/lib/commonjs/radio/createRadioIndicator.js +43 -0
  9. package/lib/commonjs/radio/createRadioIndicator.js.map +1 -0
  10. package/lib/commonjs/radio/createRadioLabel.js +38 -0
  11. package/lib/commonjs/radio/createRadioLabel.js.map +1 -0
  12. package/lib/commonjs/radio/createRadioRoot.js +95 -0
  13. package/lib/commonjs/radio/createRadioRoot.js.map +1 -0
  14. package/lib/commonjs/radio/createRadioRoot.web.js +87 -0
  15. package/lib/commonjs/radio/createRadioRoot.web.js.map +1 -0
  16. package/lib/commonjs/radio/index.js +26 -0
  17. package/lib/commonjs/radio/index.js.map +1 -0
  18. package/lib/commonjs/radio/types.js +6 -0
  19. package/lib/commonjs/radio/types.js.map +1 -0
  20. package/lib/commonjs/radio/useRadioRoot.js +64 -0
  21. package/lib/commonjs/radio/useRadioRoot.js.map +1 -0
  22. package/lib/module/index.js +1 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/radio/context.js +7 -0
  25. package/lib/module/radio/context.js.map +1 -0
  26. package/lib/module/radio/createRadioGroup.js +61 -0
  27. package/lib/module/radio/createRadioGroup.js.map +1 -0
  28. package/lib/module/radio/createRadioIndicator.js +38 -0
  29. package/lib/module/radio/createRadioIndicator.js.map +1 -0
  30. package/lib/module/radio/createRadioLabel.js +33 -0
  31. package/lib/module/radio/createRadioLabel.js.map +1 -0
  32. package/lib/module/radio/createRadioRoot.js +90 -0
  33. package/lib/module/radio/createRadioRoot.js.map +1 -0
  34. package/lib/module/radio/createRadioRoot.web.js +82 -0
  35. package/lib/module/radio/createRadioRoot.web.js.map +1 -0
  36. package/lib/module/radio/index.js +22 -0
  37. package/lib/module/radio/index.js.map +1 -0
  38. package/lib/module/radio/types.js +4 -0
  39. package/lib/module/radio/types.js.map +1 -0
  40. package/lib/module/radio/useRadioRoot.js +60 -0
  41. package/lib/module/radio/useRadioRoot.js.map +1 -0
  42. package/lib/typescript/index.d.ts +1 -0
  43. package/lib/typescript/index.d.ts.map +1 -1
  44. package/lib/typescript/radio/context.d.ts +21 -0
  45. package/lib/typescript/radio/context.d.ts.map +1 -0
  46. package/lib/typescript/radio/createRadioGroup.d.ts +3 -0
  47. package/lib/typescript/radio/createRadioGroup.d.ts.map +1 -0
  48. package/lib/typescript/radio/createRadioIndicator.d.ts +5 -0
  49. package/lib/typescript/radio/createRadioIndicator.d.ts.map +1 -0
  50. package/lib/typescript/radio/createRadioLabel.d.ts +5 -0
  51. package/lib/typescript/radio/createRadioLabel.d.ts.map +1 -0
  52. package/lib/typescript/radio/createRadioRoot.d.ts +3 -0
  53. package/lib/typescript/radio/createRadioRoot.d.ts.map +1 -0
  54. package/lib/typescript/radio/createRadioRoot.web.d.ts +3 -0
  55. package/lib/typescript/radio/createRadioRoot.web.d.ts.map +1 -0
  56. package/lib/typescript/radio/index.d.ts +10 -0
  57. package/lib/typescript/radio/index.d.ts.map +1 -0
  58. package/lib/typescript/radio/types.d.ts +54 -0
  59. package/lib/typescript/radio/types.d.ts.map +1 -0
  60. package/lib/typescript/radio/useRadioRoot.d.ts +149 -0
  61. package/lib/typescript/radio/useRadioRoot.d.ts.map +1 -0
  62. package/package.json +5 -2
  63. package/src/index.ts +1 -0
  64. package/src/radio/context.tsx +21 -0
  65. package/src/radio/createRadioGroup.tsx +67 -0
  66. package/src/radio/createRadioIndicator.tsx +32 -0
  67. package/src/radio/createRadioLabel.tsx +28 -0
  68. package/src/radio/createRadioRoot.tsx +100 -0
  69. package/src/radio/createRadioRoot.web.tsx +81 -0
  70. package/src/radio/index.ts +37 -0
  71. package/src/radio/types.ts +67 -0
  72. package/src/radio/useRadioRoot.ts +69 -0
@@ -0,0 +1,149 @@
1
+ import type { IRadioProps } from './types';
2
+ interface UseRadioRootOptions {
3
+ useInputRefForAria?: boolean;
4
+ }
5
+ export declare function useRadioRoot(props: IRadioProps, ref?: React.Ref<unknown>, { useInputRefForAria }?: UseRadioRootOptions): {
6
+ combinedProps: {
7
+ value: string;
8
+ isDisabled?: boolean | undefined;
9
+ isHovered?: boolean;
10
+ isFocused?: boolean;
11
+ isPressed?: boolean;
12
+ isFocusVisible?: boolean;
13
+ children?: React.ReactNode;
14
+ onHoverIn?: null | ((event: import("react-native").MouseEvent) => void) | undefined;
15
+ onHoverOut?: null | ((event: import("react-native").MouseEvent) => void) | undefined;
16
+ onPress?: null | ((event: import("react-native").GestureResponderEvent) => void) | undefined;
17
+ onPressIn?: null | ((event: import("react-native").GestureResponderEvent) => void) | undefined;
18
+ onPressOut?: null | ((event: import("react-native").GestureResponderEvent) => void) | undefined;
19
+ onLongPress?: null | ((event: import("react-native").GestureResponderEvent) => void) | undefined;
20
+ onBlur?: null | ((event: import("react-native").NativeSyntheticEvent<import("react-native").TargetedEvent>) => void) | undefined;
21
+ onFocus?: null | ((event: import("react-native").NativeSyntheticEvent<import("react-native").TargetedEvent>) => void) | undefined;
22
+ cancelable?: null | boolean | undefined;
23
+ delayHoverIn?: number | null | undefined;
24
+ delayHoverOut?: number | null | undefined;
25
+ delayLongPress?: null | number | undefined;
26
+ disabled?: null | boolean | undefined;
27
+ hitSlop?: null | import("react-native").Insets | number | undefined;
28
+ pressRetentionOffset?: null | import("react-native").Insets | number | undefined;
29
+ android_disableSound?: null | boolean | undefined;
30
+ android_ripple?: null | import("react-native").PressableAndroidRippleConfig | undefined;
31
+ testOnly_pressed?: null | boolean | undefined;
32
+ style?: import("react-native").StyleProp<import("react-native").ViewStyle> | ((state: import("react-native").PressableStateCallbackType) => import("react-native").StyleProp<import("react-native").ViewStyle>) | undefined;
33
+ unstable_pressDelay?: number | undefined;
34
+ accessible?: boolean | undefined;
35
+ accessibilityActions?: ReadonlyArray<import("react-native").AccessibilityActionInfo> | undefined;
36
+ accessibilityLabel?: string | undefined;
37
+ 'aria-label'?: string | undefined;
38
+ accessibilityRole?: import("react-native").AccessibilityRole | undefined;
39
+ accessibilityState?: import("react-native").AccessibilityState | undefined;
40
+ 'aria-busy'?: boolean | undefined;
41
+ 'aria-checked'?: boolean | "mixed" | undefined;
42
+ 'aria-disabled'?: boolean | undefined;
43
+ 'aria-expanded'?: boolean | undefined;
44
+ 'aria-selected'?: boolean | undefined;
45
+ accessibilityHint?: string | undefined;
46
+ accessibilityValue?: import("react-native").AccessibilityValue | undefined;
47
+ 'aria-valuemax'?: import("react-native").AccessibilityValue["max"] | undefined;
48
+ 'aria-valuemin'?: import("react-native").AccessibilityValue["min"] | undefined;
49
+ 'aria-valuenow'?: import("react-native").AccessibilityValue["now"] | undefined;
50
+ 'aria-valuetext'?: import("react-native").AccessibilityValue["text"] | undefined;
51
+ onAccessibilityAction?: ((event: import("react-native").AccessibilityActionEvent) => void) | undefined;
52
+ importantForAccessibility?: ("auto" | "yes" | "no" | "no-hide-descendants") | undefined;
53
+ 'aria-hidden'?: boolean | undefined;
54
+ 'aria-modal'?: boolean | undefined;
55
+ role?: import("react-native").Role | undefined;
56
+ accessibilityLabelledBy?: string | string[] | undefined;
57
+ 'aria-labelledby'?: string | undefined;
58
+ accessibilityLiveRegion?: "none" | "polite" | "assertive" | undefined;
59
+ 'aria-live'?: ("polite" | "assertive" | "off") | undefined;
60
+ screenReaderFocusable?: boolean | undefined;
61
+ accessibilityElementsHidden?: boolean | undefined;
62
+ accessibilityViewIsModal?: boolean | undefined;
63
+ onAccessibilityEscape?: (() => void) | undefined;
64
+ onAccessibilityTap?: (() => void) | undefined;
65
+ onMagicTap?: (() => void) | undefined;
66
+ accessibilityIgnoresInvertColors?: boolean | undefined;
67
+ accessibilityLanguage?: string | undefined;
68
+ accessibilityShowsLargeContentViewer?: boolean | undefined;
69
+ accessibilityLargeContentTitle?: string | undefined;
70
+ accessibilityRespondsToUserInteraction?: boolean | undefined;
71
+ id?: string | undefined;
72
+ onLayout?: ((event: import("react-native").LayoutChangeEvent) => void) | undefined | undefined;
73
+ testID?: string | undefined | undefined;
74
+ nativeID?: string | undefined | undefined;
75
+ needsOffscreenAlphaCompositing?: boolean | undefined | undefined;
76
+ pointerEvents?: "box-none" | "none" | "box-only" | "auto" | undefined | undefined;
77
+ removeClippedSubviews?: boolean | undefined | undefined;
78
+ collapsable?: boolean | undefined | undefined;
79
+ collapsableChildren?: boolean | undefined | undefined;
80
+ renderToHardwareTextureAndroid?: boolean | undefined | undefined;
81
+ focusable?: boolean | undefined | undefined;
82
+ tabIndex?: 0 | -1 | undefined | undefined;
83
+ shouldRasterizeIOS?: boolean | undefined | undefined;
84
+ isTVSelectable?: boolean | undefined | undefined;
85
+ hasTVPreferredFocus?: boolean | undefined | undefined;
86
+ tvParallaxShiftDistanceX?: number | undefined | undefined;
87
+ tvParallaxShiftDistanceY?: number | undefined | undefined;
88
+ tvParallaxTiltAngle?: number | undefined | undefined;
89
+ tvParallaxMagnification?: number | undefined | undefined;
90
+ onStartShouldSetResponder?: ((event: import("react-native").GestureResponderEvent) => boolean) | undefined | undefined;
91
+ onMoveShouldSetResponder?: ((event: import("react-native").GestureResponderEvent) => boolean) | undefined | undefined;
92
+ onResponderEnd?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
93
+ onResponderGrant?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
94
+ onResponderReject?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
95
+ onResponderMove?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
96
+ onResponderRelease?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
97
+ onResponderStart?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
98
+ onResponderTerminationRequest?: ((event: import("react-native").GestureResponderEvent) => boolean) | undefined | undefined;
99
+ onResponderTerminate?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
100
+ onStartShouldSetResponderCapture?: ((event: import("react-native").GestureResponderEvent) => boolean) | undefined | undefined;
101
+ onMoveShouldSetResponderCapture?: ((event: import("react-native").GestureResponderEvent) => boolean) | undefined | undefined;
102
+ onTouchStart?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
103
+ onTouchMove?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
104
+ onTouchEnd?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
105
+ onTouchCancel?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
106
+ onTouchEndCapture?: ((event: import("react-native").GestureResponderEvent) => void) | undefined | undefined;
107
+ onPointerEnter?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
108
+ onPointerEnterCapture?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
109
+ onPointerLeave?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
110
+ onPointerLeaveCapture?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
111
+ onPointerMove?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
112
+ onPointerMoveCapture?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
113
+ onPointerCancel?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
114
+ onPointerCancelCapture?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
115
+ onPointerDown?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
116
+ onPointerDownCapture?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
117
+ onPointerUp?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
118
+ onPointerUpCapture?: ((event: import("react-native").PointerEvent) => void) | undefined | undefined;
119
+ hasFeedbackText?: boolean | undefined;
120
+ hasHelpText?: boolean | undefined;
121
+ setHasFeedbackText?: import("react").Dispatch<import("react").SetStateAction<boolean>> | undefined;
122
+ setHasHelpText?: import("react").Dispatch<import("react").SetStateAction<boolean>> | undefined;
123
+ name?: string | undefined;
124
+ labelId?: string | undefined;
125
+ feedbackId?: string | undefined;
126
+ helpTextId?: string | undefined;
127
+ htmlProps?: {
128
+ [x: string]: unknown;
129
+ } | undefined;
130
+ inputRef?: React.RefObject<import("react-native").TextInput | null> | undefined;
131
+ focusInput?: (() => void) | undefined;
132
+ isRequired?: boolean | undefined;
133
+ };
134
+ isInvalid: boolean;
135
+ isReadOnly: boolean;
136
+ inputProps: any;
137
+ labelProps: Record<string, unknown>;
138
+ isChecked: any;
139
+ isDisabled: any;
140
+ isHovered: boolean;
141
+ hoverProps: {
142
+ onHoverIn: () => void;
143
+ onHoverOut: () => void;
144
+ };
145
+ mergedRef: import("react").RefCallback<unknown>;
146
+ inputRef: import("react").RefObject<HTMLInputElement | null>;
147
+ };
148
+ export {};
149
+ //# sourceMappingURL=useRadioRoot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRadioRoot.d.ts","sourceRoot":"","sources":["../../../src/radio/useRadioRoot.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,UAAU,mBAAmB;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,WAAW,EAClB,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EACxB,EAAE,kBAA0B,EAAE,GAAE,mBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsDzD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdx-ui/primitives",
3
- "version": "0.0.1-beta.10",
3
+ "version": "0.0.1-beta.12",
4
4
  "main": "lib/commonjs/index.js",
5
5
  "module": "lib/module/index.js",
6
6
  "react-native": "src/index.ts",
@@ -51,13 +51,16 @@
51
51
  }
52
52
  },
53
53
  "dependencies": {
54
+ "@react-aria/utils": "^3.33.0",
54
55
  "@react-aria/visually-hidden": "3.8.30",
55
56
  "@react-native-aria/checkbox": "0.2.10",
56
57
  "@react-native-aria/focus": "^0.2.9",
57
58
  "@react-native-aria/interactions": "^0.2.16",
59
+ "@react-native-aria/radio": "0.2.13",
58
60
  "@react-stately/checkbox": "3.7.4",
61
+ "@react-stately/radio": "3.12.0",
59
62
  "@react-stately/toggle": "3.9.4",
60
- "@cdx-ui/utils": "0.0.1-beta.10"
63
+ "@cdx-ui/utils": "0.0.1-beta.12"
61
64
  },
62
65
  "devDependencies": {
63
66
  "@types/react": "*",
package/src/index.ts CHANGED
@@ -12,5 +12,6 @@ export { type EdgeInsets, OverlayInsetsProvider } from './overlay';
12
12
  export * from './select';
13
13
  export * from './switch';
14
14
  export * from './progress';
15
+ export * from './radio';
15
16
  export type { InteractionState } from './types';
16
17
  export { dataAttributes } from './utils/dataAttributes';
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { createContext } from '@cdx-ui/utils';
3
+ import type { RadioGroupState } from '@react-stately/radio';
4
+ import type { IRadioContextValue } from './types';
5
+
6
+ export const [RadioProvider, useRadioContext] = createContext<IRadioContextValue>('RadioContext');
7
+
8
+ export interface IRadioGroupState {
9
+ isReadOnly: boolean;
10
+ isDisabled: boolean;
11
+ isInvalid: boolean;
12
+ isRequired: boolean;
13
+ isSelected: (value: string) => boolean;
14
+ selectValue: (value: string) => void;
15
+ }
16
+
17
+ export const RadioGroupContext = React.createContext<{
18
+ state: IRadioGroupState;
19
+ radioGroupState: RadioGroupState;
20
+ name?: string;
21
+ } | null>(null);
@@ -0,0 +1,67 @@
1
+ import { forwardRef } from 'react';
2
+ import { useFormControlContext } from '@cdx-ui/utils';
3
+ import { useRadioGroup } from '@react-native-aria/radio';
4
+ import { useRadioGroupState } from '@react-stately/radio';
5
+ import { dataAttributes } from '../utils/dataAttributes';
6
+ import { RadioGroupContext } from './context';
7
+ import type { IRadioGroupProps } from './types';
8
+
9
+ export const createRadioGroup = <T,>(BaseRadioGroup: React.ComponentType<T>) =>
10
+ forwardRef(({ children, ...props }: IRadioGroupProps, ref?: React.Ref<T>) => {
11
+ const formControlContext = useFormControlContext();
12
+
13
+ const combinedProps = {
14
+ ...formControlContext,
15
+ ...props,
16
+ };
17
+
18
+ const radioGroupState = useRadioGroupState({
19
+ ...combinedProps,
20
+ validationState: combinedProps.isInvalid ? 'invalid' : 'valid',
21
+ });
22
+
23
+ const { radioGroupProps } = useRadioGroup(
24
+ {
25
+ ...combinedProps,
26
+ 'aria-label': combinedProps['aria-label'],
27
+ },
28
+ radioGroupState,
29
+ );
30
+
31
+ const isDisabled = combinedProps.isDisabled ?? false;
32
+ const isInvalid = combinedProps.isInvalid ?? false;
33
+ const isRequired = combinedProps.isRequired ?? false;
34
+ const isReadOnly = combinedProps.isReadOnly ?? false;
35
+
36
+ return (
37
+ <RadioGroupContext.Provider
38
+ value={{
39
+ state: {
40
+ isDisabled,
41
+ isInvalid,
42
+ isRequired,
43
+ isReadOnly,
44
+ isSelected: (value: string) => radioGroupState.selectedValue === value,
45
+ selectValue: (value: string) => radioGroupState.setSelectedValue(value),
46
+ },
47
+ radioGroupState,
48
+ name: combinedProps.name,
49
+ }}
50
+ >
51
+ <BaseRadioGroup
52
+ {...radioGroupProps}
53
+ {...(combinedProps as unknown as T)}
54
+ ref={ref}
55
+ aria-required={isRequired || undefined}
56
+ aria-readonly={isReadOnly || undefined}
57
+ {...dataAttributes({
58
+ slot: 'radio-group',
59
+ disabled: isDisabled,
60
+ invalid: isInvalid,
61
+ })}
62
+ >
63
+ {children}
64
+ </BaseRadioGroup>
65
+ </RadioGroupContext.Provider>
66
+ );
67
+ });
@@ -0,0 +1,32 @@
1
+ import { forwardRef } from 'react';
2
+ import { dataAttributes } from '../utils/dataAttributes';
3
+ import { useRadioContext } from './context';
4
+ import type { IRadioIndicatorProps } from './types';
5
+
6
+ export const createRadioIndicator = <T,>(BaseRadioIndicator: React.ComponentType<T>) =>
7
+ forwardRef<unknown, IRadioIndicatorProps & { className?: string }>(
8
+ ({ children, className, ...props }, ref) => {
9
+ const { isChecked, isDisabled, isHovered, isInvalid, isReadOnly, isPressed, isFocusVisible } =
10
+ useRadioContext();
11
+
12
+ return (
13
+ <BaseRadioIndicator
14
+ className={className}
15
+ {...dataAttributes({
16
+ slot: 'radio-indicator',
17
+ hover: isHovered,
18
+ checked: isChecked,
19
+ disabled: isDisabled,
20
+ focusVisible: isFocusVisible,
21
+ invalid: isInvalid,
22
+ readonly: isReadOnly,
23
+ active: isPressed,
24
+ })}
25
+ {...(props as T)}
26
+ ref={ref}
27
+ >
28
+ {children}
29
+ </BaseRadioIndicator>
30
+ );
31
+ },
32
+ );
@@ -0,0 +1,28 @@
1
+ import { forwardRef } from 'react';
2
+ import { dataAttributes } from '../utils/dataAttributes';
3
+ import { useRadioContext } from './context';
4
+ import type { IRadioLabelProps } from './types';
5
+
6
+ export const createRadioLabel = <T,>(BaseRadioLabel: React.ComponentType<T>) =>
7
+ forwardRef<unknown, IRadioLabelProps & { className?: string }>(
8
+ ({ children, className, ...props }, ref) => {
9
+ const { isChecked, isDisabled, isHovered, isInvalid, isReadOnly } = useRadioContext();
10
+
11
+ return (
12
+ <BaseRadioLabel
13
+ className={className}
14
+ {...dataAttributes({
15
+ hover: isHovered,
16
+ checked: isChecked,
17
+ disabled: isDisabled,
18
+ invalid: isInvalid,
19
+ readonly: isReadOnly,
20
+ })}
21
+ {...(props as T)}
22
+ ref={ref}
23
+ >
24
+ {children}
25
+ </BaseRadioLabel>
26
+ );
27
+ },
28
+ );
@@ -0,0 +1,100 @@
1
+ import { forwardRef } from 'react';
2
+ import { composeEventHandlers } from '@cdx-ui/utils';
3
+ import { useFocus } from '@react-native-aria/focus';
4
+ import { usePress } from '@react-native-aria/interactions';
5
+ import { dataAttributes } from '../utils/dataAttributes';
6
+ import { RadioProvider } from './context';
7
+ import type { IRadioProps } from './types';
8
+ import { useRadioRoot } from './useRadioRoot';
9
+
10
+ export const createRadioRoot = <T,>(
11
+ BaseRadio: React.ComponentType<T>,
12
+ RadioIndicator: React.ComponentType<any>,
13
+ ) =>
14
+ forwardRef(
15
+ (
16
+ {
17
+ onPressIn,
18
+ onPressOut,
19
+ onHoverIn,
20
+ onHoverOut,
21
+ onFocus,
22
+ onBlur,
23
+ children,
24
+ ...props
25
+ }: IRadioProps,
26
+ ref?: React.Ref<T>,
27
+ ) => {
28
+ const {
29
+ isHovered: isHoveredProp,
30
+ isDisabled: isDisabledProp,
31
+ isInvalid: isInvalidProp,
32
+ isReadOnly: isReadOnlyProp,
33
+ isPressed: isPressedProp,
34
+ isFocused: isFocusedProp,
35
+ isFocusVisible,
36
+ } = props;
37
+
38
+ const {
39
+ combinedProps,
40
+ isInvalid,
41
+ isReadOnly,
42
+ inputProps,
43
+ isChecked,
44
+ isDisabled,
45
+ isHovered,
46
+ hoverProps,
47
+ mergedRef,
48
+ } = useRadioRoot(props, ref);
49
+
50
+ const { focusProps, isFocused } = useFocus();
51
+
52
+ const { pressProps, isPressed } = usePress({
53
+ isDisabled: isDisabled || isDisabledProp,
54
+ });
55
+
56
+ return (
57
+ <BaseRadio
58
+ disabled={isDisabled || isDisabledProp}
59
+ {...pressProps}
60
+ {...(combinedProps as T)}
61
+ {...inputProps}
62
+ ref={mergedRef}
63
+ role="radio"
64
+ onPressIn={composeEventHandlers(onPressIn, pressProps.onPressIn)}
65
+ onPressOut={composeEventHandlers(onPressOut, pressProps.onPressOut)}
66
+ onHoverIn={composeEventHandlers(onHoverIn, hoverProps.onHoverIn)}
67
+ onHoverOut={composeEventHandlers(onHoverOut, hoverProps.onHoverOut)}
68
+ onFocus={composeEventHandlers(onFocus, focusProps.onFocus)}
69
+ onBlur={composeEventHandlers(onBlur, focusProps.onBlur)}
70
+ {...dataAttributes({
71
+ slot: 'radio',
72
+ checked: isChecked,
73
+ disabled: isDisabled || isDisabledProp,
74
+ hover: isHovered || isHoveredProp,
75
+ invalid: isInvalid || isInvalidProp,
76
+ readonly: isReadOnly || isReadOnlyProp,
77
+ active: isPressed,
78
+ focus: isFocused,
79
+ focusVisible: isFocusVisible,
80
+ })}
81
+ >
82
+ <RadioProvider
83
+ value={{
84
+ isChecked,
85
+ isDisabled: isDisabled || isDisabledProp,
86
+ isHovered: isHovered || isHoveredProp,
87
+ isInvalid: isInvalid || isInvalidProp,
88
+ isReadOnly: isReadOnly || isReadOnlyProp,
89
+ isPressed: isPressed || isPressedProp,
90
+ isFocused: isFocused || isFocusedProp,
91
+ isFocusVisible,
92
+ }}
93
+ >
94
+ <RadioIndicator />
95
+ {children}
96
+ </RadioProvider>
97
+ </BaseRadio>
98
+ );
99
+ },
100
+ );
@@ -0,0 +1,81 @@
1
+ import { forwardRef } from 'react';
2
+ import { VisuallyHidden } from '@react-aria/visually-hidden';
3
+ import { mergeProps } from '@react-aria/utils';
4
+ import { useFocusRing } from '@react-native-aria/focus';
5
+ import { dataAttributes } from '../utils/dataAttributes';
6
+ import { RadioProvider } from './context';
7
+ import type { IRadioProps } from './types';
8
+ import { useRadioRoot } from './useRadioRoot';
9
+
10
+ // TODO: Keyboard focus is not working as expected
11
+
12
+ export const createRadioRoot = <T,>(
13
+ BaseRadio: React.ComponentType<T>,
14
+ RadioIndicator: React.ComponentType<any>,
15
+ ) =>
16
+ forwardRef(({ children, ...props }: IRadioProps, ref?: React.Ref<T>) => {
17
+ const {
18
+ isHovered: isHoveredProp,
19
+ isFocusVisible: isFocusVisibleProp,
20
+ isDisabled: isDisabledProp,
21
+ isInvalid: isInvalidProp,
22
+ isReadOnly: isReadOnlyProp,
23
+ isFocused,
24
+ isPressed,
25
+ } = props;
26
+
27
+ const {
28
+ combinedProps,
29
+ isInvalid,
30
+ isReadOnly,
31
+ inputProps,
32
+ labelProps,
33
+ isChecked,
34
+ isDisabled,
35
+ isHovered,
36
+ mergedRef,
37
+ inputRef,
38
+ } = useRadioRoot(props, ref, { useInputRefForAria: true });
39
+
40
+ const { focusProps, isFocusVisible } = useFocusRing();
41
+
42
+ return (
43
+ <BaseRadio
44
+ {...mergeProps(combinedProps as any, labelProps as any, focusProps as any)}
45
+ ref={mergedRef}
46
+ role="label"
47
+ // eslint-disable-next-line react-native-a11y/has-valid-accessibility-role
48
+ accessibilityRole="label"
49
+ {...dataAttributes({
50
+ slot: 'radio',
51
+ checked: isChecked,
52
+ disabled: isDisabled || isDisabledProp,
53
+ hover: isHovered || isHoveredProp,
54
+ invalid: isInvalid || isInvalidProp,
55
+ readonly: isReadOnly || isReadOnlyProp,
56
+ active: isPressed,
57
+ focus: isFocused,
58
+ focusVisible: isFocusVisible || isFocusVisibleProp,
59
+ })}
60
+ >
61
+ <RadioProvider
62
+ value={{
63
+ isChecked,
64
+ isDisabled: isDisabled || isDisabledProp,
65
+ isFocusVisible: isFocusVisible || isFocusVisibleProp,
66
+ isHovered: isHovered || isHoveredProp,
67
+ isInvalid: isInvalid || isInvalidProp,
68
+ isReadOnly: isReadOnly || isReadOnlyProp,
69
+ isPressed,
70
+ isFocused,
71
+ }}
72
+ >
73
+ <VisuallyHidden>
74
+ <input {...inputProps} ref={inputRef} />
75
+ </VisuallyHidden>
76
+ <RadioIndicator />
77
+ {children}
78
+ </RadioProvider>
79
+ </BaseRadio>
80
+ );
81
+ });
@@ -0,0 +1,37 @@
1
+ import type React from 'react';
2
+ import { createRadioGroup } from './createRadioGroup';
3
+ import { createRadioIndicator } from './createRadioIndicator';
4
+ import { createRadioLabel } from './createRadioLabel';
5
+ import { createRadioRoot } from './createRadioRoot';
6
+ import type { IRadioComponentType } from './types';
7
+
8
+ export type {
9
+ IRadioComponentType,
10
+ IRadioGroupProps,
11
+ IRadioIndicatorProps,
12
+ IRadioLabelProps,
13
+ IRadioProps,
14
+ } from './types';
15
+
16
+ export function createRadio<Root, Indicator, Label, Group>(BaseComponents: {
17
+ Root: React.ComponentType<Root>;
18
+ Indicator: React.ComponentType<Indicator>;
19
+ Label: React.ComponentType<Label>;
20
+ Group: React.ComponentType<Group>;
21
+ }) {
22
+ const Indicator = createRadioIndicator(BaseComponents.Indicator);
23
+ const Radio = createRadioRoot(BaseComponents.Root, Indicator);
24
+ const Label = createRadioLabel(BaseComponents.Label);
25
+ const Group = createRadioGroup(BaseComponents.Group);
26
+
27
+ Radio.displayName = 'RadioPrimitive';
28
+ Indicator.displayName = 'RadioPrimitive.Indicator';
29
+ Label.displayName = 'RadioPrimitive.Label';
30
+ Group.displayName = 'RadioPrimitive.Group';
31
+
32
+ return Object.assign(Radio, {
33
+ Indicator,
34
+ Label,
35
+ Group,
36
+ }) as IRadioComponentType<Root, Indicator, Label, Group>;
37
+ }
@@ -0,0 +1,67 @@
1
+ import type { PressableProps } from 'react-native';
2
+
3
+ export interface IRadioProps extends PressableProps {
4
+ /** Required — identifies this item's value within a Radio.Group. */
5
+ value: string;
6
+ isDisabled?: boolean;
7
+ isInvalid?: boolean;
8
+ isReadOnly?: boolean;
9
+ isHovered?: boolean;
10
+ isFocused?: boolean;
11
+ isPressed?: boolean;
12
+ isFocusVisible?: boolean;
13
+ children?: React.ReactNode;
14
+ }
15
+
16
+ export interface IRadioGroupProps {
17
+ /** Controlled selected value. */
18
+ value?: string;
19
+ /** Default selected value for uncontrolled usage. */
20
+ defaultValue?: string;
21
+ /** Called when the selected value changes. */
22
+ onChange?: (value: string) => void;
23
+ /** Layout direction of the radio items. */
24
+ direction?: 'column' | 'row';
25
+ /** Form field name for web form integration. */
26
+ name?: string;
27
+ isDisabled?: boolean;
28
+ isInvalid?: boolean;
29
+ isRequired?: boolean;
30
+ isReadOnly?: boolean;
31
+ 'aria-label'?: string;
32
+ 'aria-labelledby'?: string;
33
+ children?: React.ReactNode;
34
+ }
35
+
36
+ export interface IRadioIndicatorProps {
37
+ children?: React.ReactNode;
38
+ }
39
+
40
+ export interface IRadioLabelProps {
41
+ children?: React.ReactNode;
42
+ }
43
+
44
+ export type IRadioComponentType<Root, Indicator, Label, Group> = React.ForwardRefExoticComponent<
45
+ React.RefAttributes<Root> & React.PropsWithoutRef<Root> & IRadioProps
46
+ > & {
47
+ Indicator: React.ForwardRefExoticComponent<
48
+ React.RefAttributes<Indicator> & React.PropsWithoutRef<Indicator> & IRadioIndicatorProps
49
+ >;
50
+ Label: React.ForwardRefExoticComponent<
51
+ React.RefAttributes<Label> & React.PropsWithoutRef<Label> & IRadioLabelProps
52
+ >;
53
+ Group: React.ForwardRefExoticComponent<
54
+ React.RefAttributes<Group> & React.PropsWithoutRef<Group> & IRadioGroupProps
55
+ >;
56
+ };
57
+
58
+ export interface IRadioContextValue {
59
+ isChecked?: boolean;
60
+ isDisabled?: boolean;
61
+ isInvalid?: boolean;
62
+ isReadOnly?: boolean;
63
+ isFocused?: boolean;
64
+ isFocusVisible?: boolean;
65
+ isHovered?: boolean;
66
+ isPressed?: boolean;
67
+ }