@cdx-ui/primitives 0.0.1-beta.1 → 0.0.1-beta.11
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 +21 -21
- package/lib/commonjs/index.js +24 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/list-item/context.js +11 -0
- package/lib/commonjs/list-item/context.js.map +1 -0
- package/lib/commonjs/list-item/createListItemContent.js +30 -0
- package/lib/commonjs/list-item/createListItemContent.js.map +1 -0
- package/lib/commonjs/list-item/createListItemDescription.js +25 -0
- package/lib/commonjs/list-item/createListItemDescription.js.map +1 -0
- package/lib/commonjs/list-item/createListItemLeadingSlot.js +34 -0
- package/lib/commonjs/list-item/createListItemLeadingSlot.js.map +1 -0
- package/lib/commonjs/list-item/createListItemMeta.js +25 -0
- package/lib/commonjs/list-item/createListItemMeta.js.map +1 -0
- package/lib/commonjs/list-item/createListItemRoot.js +142 -0
- package/lib/commonjs/list-item/createListItemRoot.js.map +1 -0
- package/lib/commonjs/list-item/createListItemSectionHeader.js +54 -0
- package/lib/commonjs/list-item/createListItemSectionHeader.js.map +1 -0
- package/lib/commonjs/list-item/createListItemTitle.js +25 -0
- package/lib/commonjs/list-item/createListItemTitle.js.map +1 -0
- package/lib/commonjs/list-item/createListItemTrailingSlot.js +28 -0
- package/lib/commonjs/list-item/createListItemTrailingSlot.js.map +1 -0
- package/lib/commonjs/list-item/index.js +55 -0
- package/lib/commonjs/list-item/index.js.map +1 -0
- package/lib/commonjs/list-item/types.js +6 -0
- package/lib/commonjs/list-item/types.js.map +1 -0
- package/lib/commonjs/radio/context.js +14 -0
- package/lib/commonjs/radio/context.js.map +1 -0
- package/lib/commonjs/radio/createRadioGroup.js +66 -0
- package/lib/commonjs/radio/createRadioGroup.js.map +1 -0
- package/lib/commonjs/radio/createRadioIndicator.js +43 -0
- package/lib/commonjs/radio/createRadioIndicator.js.map +1 -0
- package/lib/commonjs/radio/createRadioLabel.js +38 -0
- package/lib/commonjs/radio/createRadioLabel.js.map +1 -0
- package/lib/commonjs/radio/createRadioRoot.js +95 -0
- package/lib/commonjs/radio/createRadioRoot.js.map +1 -0
- package/lib/commonjs/radio/createRadioRoot.web.js +87 -0
- package/lib/commonjs/radio/createRadioRoot.web.js.map +1 -0
- package/lib/commonjs/radio/index.js +26 -0
- package/lib/commonjs/radio/index.js.map +1 -0
- package/lib/commonjs/radio/types.js +6 -0
- package/lib/commonjs/radio/types.js.map +1 -0
- package/lib/commonjs/radio/useRadioRoot.js +64 -0
- package/lib/commonjs/radio/useRadioRoot.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/list-item/context.js +5 -0
- package/lib/module/list-item/context.js.map +1 -0
- package/lib/module/list-item/createListItemContent.js +24 -0
- package/lib/module/list-item/createListItemContent.js.map +1 -0
- package/lib/module/list-item/createListItemDescription.js +19 -0
- package/lib/module/list-item/createListItemDescription.js.map +1 -0
- package/lib/module/list-item/createListItemLeadingSlot.js +28 -0
- package/lib/module/list-item/createListItemLeadingSlot.js.map +1 -0
- package/lib/module/list-item/createListItemMeta.js +19 -0
- package/lib/module/list-item/createListItemMeta.js.map +1 -0
- package/lib/module/list-item/createListItemRoot.js +136 -0
- package/lib/module/list-item/createListItemRoot.js.map +1 -0
- package/lib/module/list-item/createListItemSectionHeader.js +48 -0
- package/lib/module/list-item/createListItemSectionHeader.js.map +1 -0
- package/lib/module/list-item/createListItemTitle.js +19 -0
- package/lib/module/list-item/createListItemTitle.js.map +1 -0
- package/lib/module/list-item/createListItemTrailingSlot.js +22 -0
- package/lib/module/list-item/createListItemTrailingSlot.js.map +1 -0
- package/lib/module/list-item/index.js +39 -0
- package/lib/module/list-item/index.js.map +1 -0
- package/lib/module/list-item/types.js +4 -0
- package/lib/module/list-item/types.js.map +1 -0
- package/lib/module/radio/context.js +7 -0
- package/lib/module/radio/context.js.map +1 -0
- package/lib/module/radio/createRadioGroup.js +61 -0
- package/lib/module/radio/createRadioGroup.js.map +1 -0
- package/lib/module/radio/createRadioIndicator.js +38 -0
- package/lib/module/radio/createRadioIndicator.js.map +1 -0
- package/lib/module/radio/createRadioLabel.js +33 -0
- package/lib/module/radio/createRadioLabel.js.map +1 -0
- package/lib/module/radio/createRadioRoot.js +90 -0
- package/lib/module/radio/createRadioRoot.js.map +1 -0
- package/lib/module/radio/createRadioRoot.web.js +82 -0
- package/lib/module/radio/createRadioRoot.web.js.map +1 -0
- package/lib/module/radio/index.js +22 -0
- package/lib/module/radio/index.js.map +1 -0
- package/lib/module/radio/types.js +4 -0
- package/lib/module/radio/types.js.map +1 -0
- package/lib/module/radio/useRadioRoot.js +60 -0
- package/lib/module/radio/useRadioRoot.js.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/list-item/context.d.ts +6 -0
- package/lib/typescript/list-item/context.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemContent.d.ts +3 -0
- package/lib/typescript/list-item/createListItemContent.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemDescription.d.ts +3 -0
- package/lib/typescript/list-item/createListItemDescription.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemLeadingSlot.d.ts +4 -0
- package/lib/typescript/list-item/createListItemLeadingSlot.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemMeta.d.ts +3 -0
- package/lib/typescript/list-item/createListItemMeta.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemRoot.d.ts +4 -0
- package/lib/typescript/list-item/createListItemRoot.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemSectionHeader.d.ts +4 -0
- package/lib/typescript/list-item/createListItemSectionHeader.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemTitle.d.ts +3 -0
- package/lib/typescript/list-item/createListItemTitle.d.ts.map +1 -0
- package/lib/typescript/list-item/createListItemTrailingSlot.d.ts +3 -0
- package/lib/typescript/list-item/createListItemTrailingSlot.d.ts.map +1 -0
- package/lib/typescript/list-item/index.d.ts +16 -0
- package/lib/typescript/list-item/index.d.ts.map +1 -0
- package/lib/typescript/list-item/types.d.ts +86 -0
- package/lib/typescript/list-item/types.d.ts.map +1 -0
- package/lib/typescript/radio/context.d.ts +21 -0
- package/lib/typescript/radio/context.d.ts.map +1 -0
- package/lib/typescript/radio/createRadioGroup.d.ts +3 -0
- package/lib/typescript/radio/createRadioGroup.d.ts.map +1 -0
- package/lib/typescript/radio/createRadioIndicator.d.ts +5 -0
- package/lib/typescript/radio/createRadioIndicator.d.ts.map +1 -0
- package/lib/typescript/radio/createRadioLabel.d.ts +5 -0
- package/lib/typescript/radio/createRadioLabel.d.ts.map +1 -0
- package/lib/typescript/radio/createRadioRoot.d.ts +3 -0
- package/lib/typescript/radio/createRadioRoot.d.ts.map +1 -0
- package/lib/typescript/radio/createRadioRoot.web.d.ts +3 -0
- package/lib/typescript/radio/createRadioRoot.web.d.ts.map +1 -0
- package/lib/typescript/radio/index.d.ts +10 -0
- package/lib/typescript/radio/index.d.ts.map +1 -0
- package/lib/typescript/radio/types.d.ts +54 -0
- package/lib/typescript/radio/types.d.ts.map +1 -0
- package/lib/typescript/radio/useRadioRoot.d.ts +149 -0
- package/lib/typescript/radio/useRadioRoot.d.ts.map +1 -0
- package/package.json +5 -2
- package/src/index.ts +2 -0
- package/src/list-item/context.tsx +5 -0
- package/src/list-item/createListItemContent.tsx +23 -0
- package/src/list-item/createListItemDescription.tsx +19 -0
- package/src/list-item/createListItemLeadingSlot.tsx +30 -0
- package/src/list-item/createListItemMeta.tsx +17 -0
- package/src/list-item/createListItemRoot.tsx +163 -0
- package/src/list-item/createListItemSectionHeader.tsx +53 -0
- package/src/list-item/createListItemTitle.tsx +17 -0
- package/src/list-item/createListItemTrailingSlot.tsx +21 -0
- package/src/list-item/index.ts +88 -0
- package/src/list-item/types.ts +122 -0
- package/src/radio/context.tsx +21 -0
- package/src/radio/createRadioGroup.tsx +67 -0
- package/src/radio/createRadioIndicator.tsx +32 -0
- package/src/radio/createRadioLabel.tsx +28 -0
- package/src/radio/createRadioRoot.tsx +100 -0
- package/src/radio/createRadioRoot.web.tsx +81 -0
- package/src/radio/index.ts +37 -0
- package/src/radio/types.ts +67 -0
- package/src/radio/useRadioRoot.ts +69 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ForwardRefExoticComponent, PropsWithoutRef, ReactNode, RefAttributes } from 'react';
|
|
2
|
+
import type { PressableProps, TextProps, ViewProps } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export type ListItemSize = 'default' | 'compact';
|
|
5
|
+
|
|
6
|
+
export type ListItemSurface = 'default' | 'negative';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Cross-axis alignment for the row (`flex-direction: row`).
|
|
10
|
+
* - **center**: Leading, content, and trailing are vertically centered (typical transactions).
|
|
11
|
+
* - **start**: Top-aligned — use when trailing text should align with the title row (e.g. branch distance “2.04 mi”) or status chips sit above a tall content stack.
|
|
12
|
+
*/
|
|
13
|
+
export type ListItemCrossAlign = 'center' | 'start';
|
|
14
|
+
|
|
15
|
+
export type IListItemPressablePassthrough = Partial<
|
|
16
|
+
Pick<PressableProps, 'onPressIn' | 'onPressOut' | 'onHoverIn' | 'onHoverOut'>
|
|
17
|
+
>;
|
|
18
|
+
|
|
19
|
+
export interface IListItemProps extends ViewProps, IListItemPressablePassthrough {
|
|
20
|
+
/**
|
|
21
|
+
* Disables press handling when the root is pressable (maps to Pressable `disabled`).
|
|
22
|
+
*/
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* When set (and `asChild` is not used), root renders as pressable with interaction props.
|
|
26
|
+
*/
|
|
27
|
+
onPress?: PressableProps['onPress'];
|
|
28
|
+
/**
|
|
29
|
+
* Vertical density; mapped at styled layer via `data-size`.
|
|
30
|
+
* @default 'default'
|
|
31
|
+
*/
|
|
32
|
+
size?: ListItemSize;
|
|
33
|
+
/**
|
|
34
|
+
* Semantic row surface (e.g. canceled transaction); mapped via `data-surface`.
|
|
35
|
+
* @default 'default'
|
|
36
|
+
*/
|
|
37
|
+
surface?: ListItemSurface;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the row shows a bottom separator; mapped via `data-separator`.
|
|
40
|
+
* @default true
|
|
41
|
+
*/
|
|
42
|
+
showSeparator?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Vertical alignment of leading / content / trailing along the row cross axis.
|
|
45
|
+
* @default 'center'
|
|
46
|
+
*/
|
|
47
|
+
crossAlign?: ListItemCrossAlign;
|
|
48
|
+
/**
|
|
49
|
+
* Merge interaction props onto a single child instead of rendering the default pressable root.
|
|
50
|
+
* Intended for use with `onPress`.
|
|
51
|
+
*/
|
|
52
|
+
asChild?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface IListItemContextValue {
|
|
56
|
+
isPressed: boolean;
|
|
57
|
+
isHovered: boolean;
|
|
58
|
+
isDisabled: boolean;
|
|
59
|
+
crossAlign: ListItemCrossAlign;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface IListItemLeadingSlotProps extends ViewProps {
|
|
63
|
+
/**
|
|
64
|
+
* Decorative leading content defaults to hidden from the accessibility tree.
|
|
65
|
+
* Set to `false` when the leading region contains meaningful controls.
|
|
66
|
+
* @default true
|
|
67
|
+
*/
|
|
68
|
+
'aria-hidden'?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type IListItemContentProps = ViewProps;
|
|
72
|
+
|
|
73
|
+
export type IListItemTitleProps = TextProps;
|
|
74
|
+
|
|
75
|
+
export type IListItemDescriptionProps = TextProps;
|
|
76
|
+
|
|
77
|
+
export type IListItemMetaProps = TextProps;
|
|
78
|
+
|
|
79
|
+
export type IListItemTrailingSlotProps = ViewProps;
|
|
80
|
+
|
|
81
|
+
export interface IListItemSectionHeaderProps extends ViewProps {
|
|
82
|
+
/** Primary section label (e.g. date heading). */
|
|
83
|
+
children: ReactNode;
|
|
84
|
+
/** Optional right-aligned adornment (e.g. section Chip). */
|
|
85
|
+
trailing?: ReactNode;
|
|
86
|
+
/**
|
|
87
|
+
* Whether to expose the top divider marker for styling (`data-divider`).
|
|
88
|
+
* @default true
|
|
89
|
+
*/
|
|
90
|
+
showDivider?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type IListItemComponentType<
|
|
94
|
+
RootRef,
|
|
95
|
+
LeadingSlot,
|
|
96
|
+
Content,
|
|
97
|
+
Title,
|
|
98
|
+
Description,
|
|
99
|
+
Meta,
|
|
100
|
+
TrailingSlot,
|
|
101
|
+
SectionHeader,
|
|
102
|
+
> = ForwardRefExoticComponent<RefAttributes<RootRef> & IListItemProps> & {
|
|
103
|
+
LeadingSlot: ForwardRefExoticComponent<
|
|
104
|
+
RefAttributes<LeadingSlot> & PropsWithoutRef<LeadingSlot> & IListItemLeadingSlotProps
|
|
105
|
+
>;
|
|
106
|
+
Content: ForwardRefExoticComponent<
|
|
107
|
+
RefAttributes<Content> & PropsWithoutRef<Content> & IListItemContentProps
|
|
108
|
+
>;
|
|
109
|
+
Title: ForwardRefExoticComponent<
|
|
110
|
+
RefAttributes<Title> & PropsWithoutRef<Title> & IListItemTitleProps
|
|
111
|
+
>;
|
|
112
|
+
Description: ForwardRefExoticComponent<
|
|
113
|
+
RefAttributes<Description> & PropsWithoutRef<Description> & IListItemDescriptionProps
|
|
114
|
+
>;
|
|
115
|
+
Meta: ForwardRefExoticComponent<RefAttributes<Meta> & PropsWithoutRef<Meta> & IListItemMetaProps>;
|
|
116
|
+
TrailingSlot: ForwardRefExoticComponent<
|
|
117
|
+
RefAttributes<TrailingSlot> & PropsWithoutRef<TrailingSlot> & IListItemTrailingSlotProps
|
|
118
|
+
>;
|
|
119
|
+
SectionHeader: ForwardRefExoticComponent<
|
|
120
|
+
RefAttributes<SectionHeader> & PropsWithoutRef<SectionHeader> & IListItemSectionHeaderProps
|
|
121
|
+
>;
|
|
122
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useContext, useRef } from 'react';
|
|
2
|
+
import { mergeRefs, useFormControlContext } from '@cdx-ui/utils';
|
|
3
|
+
import { useRadio } from '@react-native-aria/radio';
|
|
4
|
+
import { useHover } from '@react-native-aria/interactions';
|
|
5
|
+
import { RadioGroupContext } from './context';
|
|
6
|
+
import type { IRadioProps } from './types';
|
|
7
|
+
|
|
8
|
+
interface UseRadioRootOptions {
|
|
9
|
+
useInputRefForAria?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useRadioRoot(
|
|
13
|
+
props: IRadioProps,
|
|
14
|
+
ref?: React.Ref<unknown>,
|
|
15
|
+
{ useInputRefForAria = false }: UseRadioRootOptions = {},
|
|
16
|
+
) {
|
|
17
|
+
const formControlContext = useFormControlContext();
|
|
18
|
+
|
|
19
|
+
const { isInvalid, isReadOnly, ...combinedProps } = {
|
|
20
|
+
...formControlContext,
|
|
21
|
+
...props,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const radioGroupContext = useContext(RadioGroupContext);
|
|
25
|
+
|
|
26
|
+
if (!radioGroupContext) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Radio must be rendered inside a Radio.Group. Standalone Radio is not supported.',
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rootRef = useRef(null);
|
|
33
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
34
|
+
const mergedRootRef = mergeRefs(ref as any, rootRef as any);
|
|
35
|
+
const ariaLabel = combinedProps['aria-label'] || combinedProps.value || 'Radio';
|
|
36
|
+
|
|
37
|
+
const radio = useRadio(
|
|
38
|
+
{
|
|
39
|
+
...combinedProps,
|
|
40
|
+
'aria-label': ariaLabel,
|
|
41
|
+
isReadOnly: isReadOnly || radioGroupContext.state.isReadOnly,
|
|
42
|
+
isDisabled: combinedProps.isDisabled || radioGroupContext.state.isDisabled,
|
|
43
|
+
} as any,
|
|
44
|
+
radioGroupContext.radioGroupState,
|
|
45
|
+
(useInputRefForAria ? inputRef : rootRef) as any,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const { inputProps, labelProps } = radio as typeof radio & {
|
|
49
|
+
labelProps?: Record<string, unknown>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const { checked: isChecked, disabled: isDisabled } = inputProps;
|
|
53
|
+
|
|
54
|
+
const { hoverProps, isHovered } = useHover({}, rootRef);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
combinedProps,
|
|
58
|
+
isInvalid: isInvalid || radioGroupContext.state.isInvalid,
|
|
59
|
+
isReadOnly: isReadOnly || radioGroupContext.state.isReadOnly,
|
|
60
|
+
inputProps,
|
|
61
|
+
labelProps: labelProps ?? {},
|
|
62
|
+
isChecked,
|
|
63
|
+
isDisabled,
|
|
64
|
+
isHovered,
|
|
65
|
+
hoverProps,
|
|
66
|
+
mergedRef: mergedRootRef,
|
|
67
|
+
inputRef,
|
|
68
|
+
};
|
|
69
|
+
}
|