@idealyst/components 1.1.4 → 1.1.5
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/package.json +8 -3
- package/src/Accordion/Accordion.native.tsx +23 -2
- package/src/Accordion/Accordion.web.tsx +73 -2
- package/src/Accordion/types.ts +2 -1
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +15 -1
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +19 -2
- package/src/ActivityIndicator/types.ts +2 -1
- package/src/Avatar/Avatar.native.tsx +19 -2
- package/src/Avatar/Avatar.web.tsx +19 -2
- package/src/Avatar/types.ts +2 -1
- package/src/Breadcrumb/types.ts +3 -2
- package/src/Button/Button.native.tsx +48 -1
- package/src/Button/Button.styles.tsx +3 -5
- package/src/Button/Button.web.tsx +61 -2
- package/src/Button/types.ts +2 -1
- package/src/Card/Card.native.tsx +21 -5
- package/src/Card/Card.web.tsx +21 -4
- package/src/Card/types.ts +2 -6
- package/src/Checkbox/Checkbox.native.tsx +46 -5
- package/src/Checkbox/Checkbox.web.tsx +80 -4
- package/src/Checkbox/types.ts +2 -6
- package/src/Chip/Chip.native.tsx +5 -0
- package/src/Chip/Chip.web.tsx +5 -1
- package/src/Chip/types.ts +2 -1
- package/src/Dialog/Dialog.native.tsx +20 -3
- package/src/Dialog/Dialog.web.tsx +29 -4
- package/src/Dialog/types.ts +2 -1
- package/src/Image/Image.native.tsx +1 -1
- package/src/Image/Image.web.tsx +2 -0
- package/src/Input/Input.native.tsx +37 -1
- package/src/Input/Input.web.tsx +75 -8
- package/src/Input/types.ts +2 -1
- package/src/List/List.native.tsx +18 -2
- package/src/List/ListItem.native.tsx +44 -8
- package/src/List/ListItem.web.tsx +16 -0
- package/src/List/types.ts +6 -3
- package/src/Menu/Menu.native.tsx +21 -2
- package/src/Menu/Menu.web.tsx +110 -3
- package/src/Menu/MenuItem.web.tsx +12 -3
- package/src/Menu/types.ts +2 -1
- package/src/Popover/Popover.native.tsx +17 -1
- package/src/Popover/Popover.web.tsx +31 -2
- package/src/Popover/types.ts +2 -1
- package/src/RadioButton/RadioButton.native.tsx +41 -3
- package/src/RadioButton/RadioButton.web.tsx +45 -6
- package/src/RadioButton/RadioGroup.native.tsx +20 -2
- package/src/RadioButton/RadioGroup.web.tsx +24 -3
- package/src/RadioButton/types.ts +3 -2
- package/src/Select/types.ts +2 -6
- package/src/Skeleton/Skeleton.native.tsx +15 -1
- package/src/Skeleton/Skeleton.web.tsx +20 -1
- package/src/Skeleton/types.ts +2 -1
- package/src/Slider/Slider.native.tsx +42 -2
- package/src/Slider/Slider.web.tsx +81 -7
- package/src/Slider/types.ts +2 -1
- package/src/Switch/Switch.native.tsx +41 -3
- package/src/Switch/Switch.web.tsx +45 -5
- package/src/Switch/types.ts +2 -1
- package/src/TabBar/TabBar.native.tsx +23 -2
- package/src/TabBar/TabBar.web.tsx +71 -2
- package/src/TabBar/types.ts +2 -1
- package/src/Table/Table.native.tsx +17 -1
- package/src/Table/Table.web.tsx +20 -3
- package/src/Table/types.ts +3 -2
- package/src/TextArea/TextArea.native.tsx +50 -1
- package/src/TextArea/TextArea.web.tsx +82 -6
- package/src/TextArea/types.ts +2 -1
- package/src/Tooltip/Tooltip.native.tsx +19 -2
- package/src/Tooltip/Tooltip.web.tsx +54 -2
- package/src/Tooltip/types.ts +2 -1
- package/src/Video/Video.native.tsx +18 -3
- package/src/Video/Video.web.tsx +17 -1
- package/src/Video/types.ts +2 -1
- package/src/examples/InputExamples.tsx +53 -0
- package/src/examples/ListExamples.tsx +34 -0
- package/src/internal/index.ts +2 -0
- package/src/utils/accessibility/ariaHelpers.ts +393 -0
- package/src/utils/accessibility/index.ts +210 -0
- package/src/utils/accessibility/keyboardPatterns.ts +263 -0
- package/src/utils/accessibility/types.ts +223 -0
- package/src/utils/accessibility/useAnnounce.ts +210 -0
- package/src/utils/accessibility/useFocusTrap.ts +265 -0
- package/src/utils/accessibility/useKeyboardNavigation.ts +292 -0
- package/src/utils/index.ts +3 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React, { isValidElement } from 'react';
|
|
1
|
+
import React, { isValidElement, forwardRef } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
3
|
import { menuItemStyles } from './MenuItem.styles';
|
|
4
4
|
import type { MenuItem as MenuItemType, MenuSizeVariant } from './types';
|
|
5
5
|
import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
|
|
6
6
|
import { resolveIconPath, isIconName } from '../Icon/icon-resolver';
|
|
7
|
+
import useMergeRefs from '../hooks/useMergeRefs';
|
|
7
8
|
|
|
8
9
|
interface MenuItemProps {
|
|
9
10
|
item: MenuItemType;
|
|
@@ -12,7 +13,7 @@ interface MenuItemProps {
|
|
|
12
13
|
testID?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const MenuItem
|
|
16
|
+
const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(({ item, onPress, size = 'md', testID }, ref) => {
|
|
16
17
|
// Initialize styles with useVariants
|
|
17
18
|
menuItemStyles.useVariants({
|
|
18
19
|
size,
|
|
@@ -44,12 +45,18 @@ const MenuItem: React.FC<MenuItemProps> = ({ item, onPress, size = 'md', testID
|
|
|
44
45
|
return null;
|
|
45
46
|
};
|
|
46
47
|
|
|
48
|
+
// Merge refs
|
|
49
|
+
const mergedRef = useMergeRefs(ref, itemProps.ref);
|
|
50
|
+
|
|
47
51
|
return (
|
|
48
52
|
<button
|
|
49
53
|
{...itemProps}
|
|
54
|
+
ref={mergedRef}
|
|
50
55
|
onClick={() => onPress(item)}
|
|
51
56
|
disabled={item.disabled}
|
|
52
57
|
role="menuitem"
|
|
58
|
+
aria-disabled={item.disabled}
|
|
59
|
+
tabIndex={-1}
|
|
53
60
|
data-testid={testID}
|
|
54
61
|
>
|
|
55
62
|
{item.icon && (
|
|
@@ -62,6 +69,8 @@ const MenuItem: React.FC<MenuItemProps> = ({ item, onPress, size = 'md', testID
|
|
|
62
69
|
</span>
|
|
63
70
|
</button>
|
|
64
71
|
);
|
|
65
|
-
};
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
MenuItem.displayName = 'MenuItem';
|
|
66
75
|
|
|
67
76
|
export default MenuItem;
|
package/src/Menu/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { StyleProp, ViewStyle } from 'react-native';
|
|
|
2
2
|
import type { IconName } from '../Icon/icon-types';
|
|
3
3
|
import { Intent, Size } from '@idealyst/theme';
|
|
4
4
|
import { BaseProps } from '../utils/viewStyleProps';
|
|
5
|
+
import { InteractiveAccessibilityProps } from '../utils/accessibility';
|
|
5
6
|
|
|
6
7
|
// Component-specific type aliases for future extensibility
|
|
7
8
|
export type MenuIntentVariant = Intent;
|
|
@@ -18,7 +19,7 @@ export interface MenuItem {
|
|
|
18
19
|
separator?: boolean;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export interface MenuProps extends BaseProps {
|
|
22
|
+
export interface MenuProps extends BaseProps, InteractiveAccessibilityProps {
|
|
22
23
|
children: React.ReactNode;
|
|
23
24
|
items: MenuItem[];
|
|
24
25
|
open?: boolean;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState, forwardRef } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState, forwardRef, useMemo } from 'react';
|
|
2
2
|
import { Modal, View, TouchableWithoutFeedback, BackHandler, Dimensions } from 'react-native';
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
4
|
import { PopoverProps } from './types';
|
|
5
5
|
import { popoverStyles } from './Popover.styles';
|
|
6
6
|
import { calculateSmartPosition, calculateAvailableHeight } from '../utils/positionUtils.native';
|
|
7
7
|
import { BoundedModalContent } from '../internal/BoundedModalContent.native';
|
|
8
|
+
import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
|
|
8
9
|
|
|
9
10
|
const Popover = forwardRef<View, PopoverProps>(({
|
|
10
11
|
open,
|
|
@@ -18,7 +19,21 @@ const Popover = forwardRef<View, PopoverProps>(({
|
|
|
18
19
|
style,
|
|
19
20
|
testID,
|
|
20
21
|
id,
|
|
22
|
+
// Accessibility props
|
|
23
|
+
accessibilityLabel,
|
|
24
|
+
accessibilityHint,
|
|
25
|
+
accessibilityRole,
|
|
26
|
+
accessibilityHidden,
|
|
21
27
|
}, ref) => {
|
|
28
|
+
// Generate native accessibility props
|
|
29
|
+
const nativeA11yProps = useMemo(() => {
|
|
30
|
+
return getNativeInteractiveAccessibilityProps({
|
|
31
|
+
accessibilityLabel,
|
|
32
|
+
accessibilityHint,
|
|
33
|
+
accessibilityRole: accessibilityRole ?? 'none',
|
|
34
|
+
accessibilityHidden,
|
|
35
|
+
});
|
|
36
|
+
}, [accessibilityLabel, accessibilityHint, accessibilityRole, accessibilityHidden]);
|
|
22
37
|
const popoverRef = useRef<View>(null);
|
|
23
38
|
const [popoverPosition, setPopoverPosition] = useState({ top: 0, left: 0, width: 0 });
|
|
24
39
|
const [popoverSize, setPopoverSize] = useState({ width: 0, height: 0 });
|
|
@@ -141,6 +156,7 @@ const Popover = forwardRef<View, PopoverProps>(({
|
|
|
141
156
|
maxHeight={500}
|
|
142
157
|
style={[popoverStyles.container, style]}
|
|
143
158
|
onLayout={handlePopoverLayout}
|
|
159
|
+
{...nativeA11yProps}
|
|
144
160
|
>
|
|
145
161
|
{showArrow && <View style={popoverStyles.arrow} />}
|
|
146
162
|
<View style={popoverStyles.content}>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React, { useRef, forwardRef } from 'react';
|
|
1
|
+
import React, { useRef, forwardRef, useMemo } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
3
|
import { PopoverProps } from './types';
|
|
4
4
|
import { popoverStyles } from './Popover.styles';
|
|
5
5
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
6
6
|
import { PositionedPortal } from '../internal/PositionedPortal';
|
|
7
|
+
import { getWebInteractiveAriaProps, generateAccessibilityId } from '../utils/accessibility';
|
|
7
8
|
|
|
8
9
|
const Popover = forwardRef<HTMLDivElement, PopoverProps>(({
|
|
9
10
|
open,
|
|
@@ -17,7 +18,28 @@ const Popover = forwardRef<HTMLDivElement, PopoverProps>(({
|
|
|
17
18
|
showArrow = false,
|
|
18
19
|
testID,
|
|
19
20
|
id,
|
|
21
|
+
// Accessibility props
|
|
22
|
+
accessibilityLabel,
|
|
23
|
+
accessibilityHint,
|
|
24
|
+
accessibilityRole,
|
|
25
|
+
accessibilityHidden,
|
|
26
|
+
accessibilityLabelledBy,
|
|
27
|
+
accessibilityDescribedBy,
|
|
20
28
|
}, ref) => {
|
|
29
|
+
// Generate unique ID for the popover
|
|
30
|
+
const popoverId = useMemo(() => id || generateAccessibilityId('popover'), [id]);
|
|
31
|
+
|
|
32
|
+
// Generate ARIA props
|
|
33
|
+
const ariaProps = useMemo(() => {
|
|
34
|
+
return getWebInteractiveAriaProps({
|
|
35
|
+
accessibilityLabel,
|
|
36
|
+
accessibilityHint,
|
|
37
|
+
accessibilityRole: accessibilityRole ?? 'dialog',
|
|
38
|
+
accessibilityHidden,
|
|
39
|
+
accessibilityLabelledBy,
|
|
40
|
+
accessibilityDescribedBy,
|
|
41
|
+
});
|
|
42
|
+
}, [accessibilityLabel, accessibilityHint, accessibilityRole, accessibilityHidden, accessibilityLabelledBy, accessibilityDescribedBy]);
|
|
21
43
|
const popoverRef = useRef<HTMLDivElement>(null);
|
|
22
44
|
|
|
23
45
|
popoverStyles.useVariants({});
|
|
@@ -48,7 +70,14 @@ const Popover = forwardRef<HTMLDivElement, PopoverProps>(({
|
|
|
48
70
|
onEscapeKey={closeOnEscapeKey ? () => onOpenChange(false) : undefined}
|
|
49
71
|
zIndex={9999}
|
|
50
72
|
>
|
|
51
|
-
<div
|
|
73
|
+
<div
|
|
74
|
+
ref={mergedPopoverRef}
|
|
75
|
+
id={popoverId}
|
|
76
|
+
data-testid={testID}
|
|
77
|
+
{...ariaProps}
|
|
78
|
+
role="dialog"
|
|
79
|
+
aria-modal="false"
|
|
80
|
+
>
|
|
52
81
|
<div {...containerProps}>
|
|
53
82
|
<div {...contentProps}>
|
|
54
83
|
{children}
|
package/src/Popover/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
import { BaseProps } from '../utils/viewStyleProps';
|
|
4
|
+
import { InteractiveAccessibilityProps } from '../utils/accessibility';
|
|
4
5
|
|
|
5
6
|
export type PopoverPlacement =
|
|
6
7
|
| 'top' | 'top-start' | 'top-end'
|
|
@@ -8,7 +9,7 @@ export type PopoverPlacement =
|
|
|
8
9
|
| 'left' | 'left-start' | 'left-end'
|
|
9
10
|
| 'right' | 'right-start' | 'right-end';
|
|
10
11
|
|
|
11
|
-
export interface PopoverProps extends BaseProps {
|
|
12
|
+
export interface PopoverProps extends BaseProps, InteractiveAccessibilityProps {
|
|
12
13
|
/**
|
|
13
14
|
* Whether the popover is open/visible
|
|
14
15
|
*/
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import React, { ComponentRef, forwardRef } from 'react';
|
|
1
|
+
import React, { ComponentRef, forwardRef, useMemo } from 'react';
|
|
2
2
|
import { View, Pressable, Animated } from 'react-native';
|
|
3
3
|
import Text from '../Text';
|
|
4
4
|
import { radioButtonStyles } from './RadioButton.styles';
|
|
5
5
|
import type { RadioButtonProps } from './types';
|
|
6
6
|
import { useRadioGroup } from './RadioGroup.native';
|
|
7
|
+
import { getNativeSelectionAccessibilityProps } from '../utils/accessibility';
|
|
7
8
|
|
|
8
9
|
const RadioButton = forwardRef<ComponentRef<typeof Pressable>, RadioButtonProps>(({
|
|
9
10
|
value,
|
|
@@ -20,6 +21,15 @@ const RadioButton = forwardRef<ComponentRef<typeof Pressable>, RadioButtonProps>
|
|
|
20
21
|
style,
|
|
21
22
|
testID,
|
|
22
23
|
id,
|
|
24
|
+
// Accessibility props
|
|
25
|
+
accessibilityLabel,
|
|
26
|
+
accessibilityHint,
|
|
27
|
+
accessibilityDisabled,
|
|
28
|
+
accessibilityHidden,
|
|
29
|
+
accessibilityRole,
|
|
30
|
+
accessibilityLabelledBy,
|
|
31
|
+
accessibilityDescribedBy,
|
|
32
|
+
accessibilityChecked,
|
|
23
33
|
}, ref) => {
|
|
24
34
|
const group = useRadioGroup();
|
|
25
35
|
|
|
@@ -47,6 +57,35 @@ const RadioButton = forwardRef<ComponentRef<typeof Pressable>, RadioButtonProps>
|
|
|
47
57
|
}
|
|
48
58
|
};
|
|
49
59
|
|
|
60
|
+
// Generate native accessibility props
|
|
61
|
+
const nativeA11yProps = useMemo(() => {
|
|
62
|
+
const computedLabel = accessibilityLabel ?? label;
|
|
63
|
+
const computedChecked = accessibilityChecked ?? checked;
|
|
64
|
+
|
|
65
|
+
return getNativeSelectionAccessibilityProps({
|
|
66
|
+
accessibilityLabel: computedLabel,
|
|
67
|
+
accessibilityHint,
|
|
68
|
+
accessibilityDisabled: accessibilityDisabled ?? disabled,
|
|
69
|
+
accessibilityHidden,
|
|
70
|
+
accessibilityRole: accessibilityRole ?? 'radio',
|
|
71
|
+
accessibilityLabelledBy,
|
|
72
|
+
accessibilityDescribedBy,
|
|
73
|
+
accessibilityChecked: computedChecked,
|
|
74
|
+
});
|
|
75
|
+
}, [
|
|
76
|
+
accessibilityLabel,
|
|
77
|
+
label,
|
|
78
|
+
accessibilityHint,
|
|
79
|
+
accessibilityDisabled,
|
|
80
|
+
disabled,
|
|
81
|
+
accessibilityHidden,
|
|
82
|
+
accessibilityRole,
|
|
83
|
+
accessibilityLabelledBy,
|
|
84
|
+
accessibilityDescribedBy,
|
|
85
|
+
accessibilityChecked,
|
|
86
|
+
checked,
|
|
87
|
+
]);
|
|
88
|
+
|
|
50
89
|
// Apply variants for radio styles
|
|
51
90
|
radioButtonStyles.useVariants({
|
|
52
91
|
size,
|
|
@@ -70,8 +109,7 @@ const RadioButton = forwardRef<ComponentRef<typeof Pressable>, RadioButtonProps>
|
|
|
70
109
|
disabled={disabled}
|
|
71
110
|
style={[radioButtonStyles.container, style]}
|
|
72
111
|
testID={testID}
|
|
73
|
-
|
|
74
|
-
accessibilityState={{ checked, disabled }}
|
|
112
|
+
{...nativeA11yProps}
|
|
75
113
|
>
|
|
76
114
|
<View style={radioButtonStyles.radio({ intent })}>
|
|
77
115
|
<Animated.View
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
3
|
import { radioButtonStyles } from './RadioButton.styles';
|
|
4
4
|
import type { RadioButtonProps } from './types';
|
|
5
5
|
import { useRadioGroup } from './RadioGroup.web';
|
|
6
|
+
import { getWebSelectionAriaProps, generateAccessibilityId } from '../utils/accessibility';
|
|
6
7
|
|
|
7
8
|
const RadioButton: React.FC<RadioButtonProps> = ({
|
|
8
9
|
value,
|
|
@@ -19,6 +20,15 @@ const RadioButton: React.FC<RadioButtonProps> = ({
|
|
|
19
20
|
style,
|
|
20
21
|
testID,
|
|
21
22
|
id,
|
|
23
|
+
// Accessibility props
|
|
24
|
+
accessibilityLabel,
|
|
25
|
+
accessibilityHint,
|
|
26
|
+
accessibilityDisabled,
|
|
27
|
+
accessibilityHidden,
|
|
28
|
+
accessibilityRole,
|
|
29
|
+
accessibilityLabelledBy,
|
|
30
|
+
accessibilityDescribedBy,
|
|
31
|
+
accessibilityChecked,
|
|
22
32
|
}) => {
|
|
23
33
|
const group = useRadioGroup();
|
|
24
34
|
|
|
@@ -35,6 +45,38 @@ const RadioButton: React.FC<RadioButtonProps> = ({
|
|
|
35
45
|
}
|
|
36
46
|
};
|
|
37
47
|
|
|
48
|
+
// Generate unique ID for accessibility
|
|
49
|
+
const radioId = useMemo(() => id || generateAccessibilityId('radio'), [id]);
|
|
50
|
+
|
|
51
|
+
// Generate ARIA props
|
|
52
|
+
const ariaProps = useMemo(() => {
|
|
53
|
+
const computedLabel = accessibilityLabel ?? label;
|
|
54
|
+
const computedChecked = accessibilityChecked ?? checked;
|
|
55
|
+
|
|
56
|
+
return getWebSelectionAriaProps({
|
|
57
|
+
accessibilityLabel: computedLabel,
|
|
58
|
+
accessibilityHint,
|
|
59
|
+
accessibilityDisabled: accessibilityDisabled ?? disabled,
|
|
60
|
+
accessibilityHidden,
|
|
61
|
+
accessibilityRole: accessibilityRole ?? 'radio',
|
|
62
|
+
accessibilityLabelledBy,
|
|
63
|
+
accessibilityDescribedBy,
|
|
64
|
+
accessibilityChecked: computedChecked,
|
|
65
|
+
});
|
|
66
|
+
}, [
|
|
67
|
+
accessibilityLabel,
|
|
68
|
+
label,
|
|
69
|
+
accessibilityHint,
|
|
70
|
+
accessibilityDisabled,
|
|
71
|
+
disabled,
|
|
72
|
+
accessibilityHidden,
|
|
73
|
+
accessibilityRole,
|
|
74
|
+
accessibilityLabelledBy,
|
|
75
|
+
accessibilityDescribedBy,
|
|
76
|
+
accessibilityChecked,
|
|
77
|
+
checked,
|
|
78
|
+
]);
|
|
79
|
+
|
|
38
80
|
// Apply variants using the correct Unistyles v3 pattern
|
|
39
81
|
radioButtonStyles.useVariants({
|
|
40
82
|
size,
|
|
@@ -53,13 +95,11 @@ const RadioButton: React.FC<RadioButtonProps> = ({
|
|
|
53
95
|
return (
|
|
54
96
|
<button
|
|
55
97
|
{...containerProps}
|
|
98
|
+
{...ariaProps}
|
|
56
99
|
onClick={handleClick}
|
|
57
100
|
disabled={disabled}
|
|
58
|
-
id={
|
|
101
|
+
id={radioId}
|
|
59
102
|
data-testid={testID}
|
|
60
|
-
role="radio"
|
|
61
|
-
aria-checked={checked}
|
|
62
|
-
aria-disabled={disabled}
|
|
63
103
|
style={{
|
|
64
104
|
background: 'none',
|
|
65
105
|
border: 'none',
|
|
@@ -68,7 +108,6 @@ const RadioButton: React.FC<RadioButtonProps> = ({
|
|
|
68
108
|
display: 'inline-flex',
|
|
69
109
|
alignItems: 'center',
|
|
70
110
|
}}
|
|
71
|
-
{...containerProps}
|
|
72
111
|
>
|
|
73
112
|
<div {...radioProps} style={{
|
|
74
113
|
display: 'flex',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, { forwardRef } from 'react';
|
|
1
|
+
import React, { forwardRef, useMemo } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import { radioButtonStyles } from './RadioButton.styles';
|
|
4
4
|
import type { RadioGroupProps } from './types';
|
|
5
|
+
import { getNativeAccessibilityProps } from '../utils/accessibility';
|
|
5
6
|
|
|
6
7
|
const RadioGroupContext = React.createContext<{
|
|
7
8
|
value?: string;
|
|
@@ -20,8 +21,25 @@ const RadioGroup = forwardRef<View, RadioGroupProps>(({
|
|
|
20
21
|
style,
|
|
21
22
|
testID,
|
|
22
23
|
id,
|
|
24
|
+
// Accessibility props
|
|
25
|
+
accessibilityLabel,
|
|
26
|
+
accessibilityHint,
|
|
27
|
+
accessibilityDisabled,
|
|
28
|
+
accessibilityHidden,
|
|
29
|
+
accessibilityRole,
|
|
23
30
|
}, ref) => {
|
|
24
31
|
|
|
32
|
+
// Generate native accessibility props
|
|
33
|
+
const nativeA11yProps = useMemo(() => {
|
|
34
|
+
return getNativeAccessibilityProps({
|
|
35
|
+
accessibilityLabel,
|
|
36
|
+
accessibilityHint,
|
|
37
|
+
accessibilityDisabled: accessibilityDisabled ?? disabled,
|
|
38
|
+
accessibilityHidden,
|
|
39
|
+
accessibilityRole: accessibilityRole ?? 'radiogroup',
|
|
40
|
+
});
|
|
41
|
+
}, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole]);
|
|
42
|
+
|
|
25
43
|
return (
|
|
26
44
|
<RadioGroupContext.Provider value={{ value, onValueChange, disabled }}>
|
|
27
45
|
<View
|
|
@@ -31,8 +49,8 @@ const RadioGroup = forwardRef<View, RadioGroupProps>(({
|
|
|
31
49
|
radioButtonStyles.groupContainer,
|
|
32
50
|
style as any,
|
|
33
51
|
]}
|
|
34
|
-
accessibilityRole="radiogroup"
|
|
35
52
|
testID={testID}
|
|
53
|
+
{...nativeA11yProps}
|
|
36
54
|
>
|
|
37
55
|
{children}
|
|
38
56
|
</View>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
3
|
import { radioButtonStyles } from './RadioButton.styles';
|
|
4
4
|
import type { RadioGroupProps } from './types';
|
|
5
|
+
import { getWebAriaProps, generateAccessibilityId } from '../utils/accessibility';
|
|
5
6
|
|
|
6
7
|
const RadioGroupContext = React.createContext<{
|
|
7
8
|
value?: string;
|
|
@@ -20,7 +21,27 @@ const RadioGroup: React.FC<RadioGroupProps> = ({
|
|
|
20
21
|
style,
|
|
21
22
|
testID,
|
|
22
23
|
id,
|
|
24
|
+
// Accessibility props
|
|
25
|
+
accessibilityLabel,
|
|
26
|
+
accessibilityHint,
|
|
27
|
+
accessibilityDisabled,
|
|
28
|
+
accessibilityHidden,
|
|
29
|
+
accessibilityRole,
|
|
23
30
|
}) => {
|
|
31
|
+
// Generate unique ID for accessibility
|
|
32
|
+
const groupId = useMemo(() => id || generateAccessibilityId('radiogroup'), [id]);
|
|
33
|
+
|
|
34
|
+
// Generate ARIA props
|
|
35
|
+
const ariaProps = useMemo(() => {
|
|
36
|
+
return getWebAriaProps({
|
|
37
|
+
accessibilityLabel,
|
|
38
|
+
accessibilityHint,
|
|
39
|
+
accessibilityDisabled: accessibilityDisabled ?? disabled,
|
|
40
|
+
accessibilityHidden,
|
|
41
|
+
accessibilityRole: accessibilityRole ?? 'radiogroup',
|
|
42
|
+
});
|
|
43
|
+
}, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole]);
|
|
44
|
+
|
|
24
45
|
// Apply variants
|
|
25
46
|
radioButtonStyles.useVariants({
|
|
26
47
|
orientation,
|
|
@@ -35,8 +56,8 @@ const RadioGroup: React.FC<RadioGroupProps> = ({
|
|
|
35
56
|
<RadioGroupContext.Provider value={{ value, onValueChange, disabled }}>
|
|
36
57
|
<div
|
|
37
58
|
{...groupProps}
|
|
38
|
-
|
|
39
|
-
id={
|
|
59
|
+
{...ariaProps}
|
|
60
|
+
id={groupId}
|
|
40
61
|
data-testid={testID}
|
|
41
62
|
style={{
|
|
42
63
|
display: 'flex',
|
package/src/RadioButton/types.ts
CHANGED
|
@@ -2,12 +2,13 @@ import type { StyleProp, ViewStyle } from 'react-native';
|
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import { Intent, Size } from '@idealyst/theme';
|
|
4
4
|
import { FormInputStyleProps, BaseProps } from '../utils/viewStyleProps';
|
|
5
|
+
import { SelectionAccessibilityProps, AccessibilityProps } from '../utils/accessibility';
|
|
5
6
|
|
|
6
7
|
// Component-specific type aliases for future extensibility
|
|
7
8
|
export type RadioButtonIntentVariant = Intent;
|
|
8
9
|
export type RadioButtonSizeVariant = Size;
|
|
9
10
|
|
|
10
|
-
export interface RadioButtonProps extends FormInputStyleProps {
|
|
11
|
+
export interface RadioButtonProps extends FormInputStyleProps, SelectionAccessibilityProps {
|
|
11
12
|
value: string;
|
|
12
13
|
checked?: boolean;
|
|
13
14
|
onPress?: () => void;
|
|
@@ -19,7 +20,7 @@ export interface RadioButtonProps extends FormInputStyleProps {
|
|
|
19
20
|
testID?: string;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
export interface RadioGroupProps extends BaseProps {
|
|
23
|
+
export interface RadioGroupProps extends BaseProps, AccessibilityProps {
|
|
23
24
|
value?: string;
|
|
24
25
|
onValueChange?: (value: string) => void;
|
|
25
26
|
disabled?: boolean;
|
package/src/Select/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Intent, Size } from '@idealyst/theme';
|
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
4
4
|
import { FormInputStyleProps } from '../utils/viewStyleProps';
|
|
5
|
+
import { FormAccessibilityProps } from '../utils/accessibility';
|
|
5
6
|
|
|
6
7
|
// Component-specific type aliases for future extensibility
|
|
7
8
|
export type SelectIntentVariant = Intent;
|
|
@@ -30,7 +31,7 @@ export interface SelectOption {
|
|
|
30
31
|
icon?: ReactNode;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export interface SelectProps extends FormInputStyleProps {
|
|
34
|
+
export interface SelectProps extends FormInputStyleProps, FormAccessibilityProps {
|
|
34
35
|
/**
|
|
35
36
|
* Array of options to display in the select
|
|
36
37
|
*/
|
|
@@ -117,9 +118,4 @@ export interface SelectProps extends FormInputStyleProps {
|
|
|
117
118
|
* Test ID for testing
|
|
118
119
|
*/
|
|
119
120
|
testID?: string;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Accessibility label
|
|
123
|
-
*/
|
|
124
|
-
accessibilityLabel?: string;
|
|
125
121
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, forwardRef } from 'react';
|
|
1
|
+
import React, { useEffect, forwardRef, useMemo } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import Animated, {
|
|
4
4
|
useSharedValue,
|
|
@@ -10,6 +10,7 @@ import Animated, {
|
|
|
10
10
|
} from 'react-native-reanimated';
|
|
11
11
|
import { skeletonStyles } from './Skeleton.styles';
|
|
12
12
|
import type { SkeletonProps, SkeletonGroupProps } from './types';
|
|
13
|
+
import { getNativeLiveRegionAccessibilityProps } from '../utils/accessibility';
|
|
13
14
|
|
|
14
15
|
const Skeleton = forwardRef<View, SkeletonProps>(({
|
|
15
16
|
width = '100%',
|
|
@@ -20,7 +21,19 @@ const Skeleton = forwardRef<View, SkeletonProps>(({
|
|
|
20
21
|
style,
|
|
21
22
|
testID,
|
|
22
23
|
id,
|
|
24
|
+
// Accessibility props
|
|
25
|
+
accessibilityLabel,
|
|
26
|
+
accessibilityLiveRegion,
|
|
27
|
+
accessibilityBusy,
|
|
23
28
|
}, ref) => {
|
|
29
|
+
// Generate native accessibility props
|
|
30
|
+
const nativeA11yProps = useMemo(() => {
|
|
31
|
+
return getNativeLiveRegionAccessibilityProps({
|
|
32
|
+
accessibilityLabel: accessibilityLabel ?? 'Loading content',
|
|
33
|
+
accessibilityLiveRegion: accessibilityLiveRegion ?? 'polite',
|
|
34
|
+
accessibilityBusy: accessibilityBusy ?? true,
|
|
35
|
+
});
|
|
36
|
+
}, [accessibilityLabel, accessibilityLiveRegion, accessibilityBusy]);
|
|
24
37
|
skeletonStyles.useVariants({
|
|
25
38
|
shape,
|
|
26
39
|
animation,
|
|
@@ -87,6 +100,7 @@ const Skeleton = forwardRef<View, SkeletonProps>(({
|
|
|
87
100
|
pulseAnimatedStyle,
|
|
88
101
|
]}
|
|
89
102
|
testID={testID}
|
|
103
|
+
{...nativeA11yProps}
|
|
90
104
|
>
|
|
91
105
|
{animation === 'wave' && (
|
|
92
106
|
<Animated.View
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useMemo } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
3
|
import { skeletonStyles } from './Skeleton.styles';
|
|
4
4
|
import type { SkeletonProps, SkeletonGroupProps } from './types';
|
|
5
|
+
import { getWebLiveRegionAriaProps } from '../utils/accessibility';
|
|
5
6
|
|
|
6
7
|
const Skeleton: React.FC<SkeletonProps> = ({
|
|
7
8
|
width = '100%',
|
|
@@ -12,7 +13,23 @@ const Skeleton: React.FC<SkeletonProps> = ({
|
|
|
12
13
|
style,
|
|
13
14
|
testID,
|
|
14
15
|
id,
|
|
16
|
+
// Accessibility props
|
|
17
|
+
accessibilityLabel,
|
|
18
|
+
accessibilityLiveRegion,
|
|
19
|
+
accessibilityBusy,
|
|
20
|
+
accessibilityAtomic,
|
|
21
|
+
accessibilityRelevant,
|
|
15
22
|
}) => {
|
|
23
|
+
// Generate ARIA props for loading state
|
|
24
|
+
const ariaProps = useMemo(() => {
|
|
25
|
+
return getWebLiveRegionAriaProps({
|
|
26
|
+
accessibilityLabel: accessibilityLabel ?? 'Loading content',
|
|
27
|
+
accessibilityLiveRegion: accessibilityLiveRegion ?? 'polite',
|
|
28
|
+
accessibilityBusy: accessibilityBusy ?? true,
|
|
29
|
+
accessibilityAtomic,
|
|
30
|
+
accessibilityRelevant,
|
|
31
|
+
});
|
|
32
|
+
}, [accessibilityLabel, accessibilityLiveRegion, accessibilityBusy, accessibilityAtomic, accessibilityRelevant]);
|
|
16
33
|
skeletonStyles.useVariants({
|
|
17
34
|
shape,
|
|
18
35
|
animation,
|
|
@@ -57,6 +74,8 @@ const Skeleton: React.FC<SkeletonProps> = ({
|
|
|
57
74
|
)}
|
|
58
75
|
<div
|
|
59
76
|
{...skeletonProps}
|
|
77
|
+
{...ariaProps}
|
|
78
|
+
role="status"
|
|
60
79
|
style={{
|
|
61
80
|
...customStyles,
|
|
62
81
|
...animationStyles,
|
package/src/Skeleton/types.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
2
|
import { BaseProps } from '../utils/viewStyleProps';
|
|
3
|
+
import { LiveRegionAccessibilityProps } from '../utils/accessibility';
|
|
3
4
|
|
|
4
5
|
export type SkeletonShape = 'rectangle' | 'circle' | 'rounded';
|
|
5
6
|
export type SkeletonAnimation = 'pulse' | 'wave' | 'none';
|
|
6
7
|
|
|
7
|
-
export interface SkeletonProps extends BaseProps {
|
|
8
|
+
export interface SkeletonProps extends BaseProps, LiveRegionAccessibilityProps {
|
|
8
9
|
/**
|
|
9
10
|
* Width of the skeleton (number in pixels or string with units)
|
|
10
11
|
* @default '100%'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useCallback, forwardRef } from 'react';
|
|
1
|
+
import React, { useState, useCallback, forwardRef, useMemo } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
|
|
4
4
|
import Animated, { useSharedValue, useAnimatedStyle, runOnJS, withSpring } from 'react-native-reanimated';
|
|
@@ -7,6 +7,7 @@ import { sliderStyles } from './Slider.styles';
|
|
|
7
7
|
import Text from '../Text';
|
|
8
8
|
import type { SliderProps } from './types';
|
|
9
9
|
import { isIconName } from '../Icon/icon-resolver';
|
|
10
|
+
import { getNativeRangeAccessibilityProps } from '../utils/accessibility';
|
|
10
11
|
|
|
11
12
|
const Slider = forwardRef<View, SliderProps>(({
|
|
12
13
|
value: controlledValue,
|
|
@@ -30,6 +31,16 @@ const Slider = forwardRef<View, SliderProps>(({
|
|
|
30
31
|
style,
|
|
31
32
|
testID,
|
|
32
33
|
id,
|
|
34
|
+
// Accessibility props
|
|
35
|
+
accessibilityLabel,
|
|
36
|
+
accessibilityHint,
|
|
37
|
+
accessibilityDisabled,
|
|
38
|
+
accessibilityHidden,
|
|
39
|
+
accessibilityRole,
|
|
40
|
+
accessibilityValueNow,
|
|
41
|
+
accessibilityValueMin,
|
|
42
|
+
accessibilityValueMax,
|
|
43
|
+
accessibilityValueText,
|
|
33
44
|
}, ref) => {
|
|
34
45
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
35
46
|
const [trackWidthState, setTrackWidthState] = useState(0);
|
|
@@ -75,6 +86,35 @@ const Slider = forwardRef<View, SliderProps>(({
|
|
|
75
86
|
onValueCommit?.(finalValue);
|
|
76
87
|
}, [onValueCommit]);
|
|
77
88
|
|
|
89
|
+
// Generate native accessibility props
|
|
90
|
+
const nativeA11yProps = useMemo(() => {
|
|
91
|
+
return getNativeRangeAccessibilityProps({
|
|
92
|
+
accessibilityLabel,
|
|
93
|
+
accessibilityHint,
|
|
94
|
+
accessibilityDisabled: accessibilityDisabled ?? disabled,
|
|
95
|
+
accessibilityHidden,
|
|
96
|
+
accessibilityRole: accessibilityRole ?? 'adjustable',
|
|
97
|
+
accessibilityValueNow: accessibilityValueNow ?? value,
|
|
98
|
+
accessibilityValueMin: accessibilityValueMin ?? min,
|
|
99
|
+
accessibilityValueMax: accessibilityValueMax ?? max,
|
|
100
|
+
accessibilityValueText: accessibilityValueText ?? `${value}`,
|
|
101
|
+
});
|
|
102
|
+
}, [
|
|
103
|
+
accessibilityLabel,
|
|
104
|
+
accessibilityHint,
|
|
105
|
+
accessibilityDisabled,
|
|
106
|
+
disabled,
|
|
107
|
+
accessibilityHidden,
|
|
108
|
+
accessibilityRole,
|
|
109
|
+
accessibilityValueNow,
|
|
110
|
+
value,
|
|
111
|
+
accessibilityValueMin,
|
|
112
|
+
min,
|
|
113
|
+
accessibilityValueMax,
|
|
114
|
+
max,
|
|
115
|
+
accessibilityValueText,
|
|
116
|
+
]);
|
|
117
|
+
|
|
78
118
|
// Update translateX when value changes externally
|
|
79
119
|
React.useEffect(() => {
|
|
80
120
|
if (!isDragging.value && trackWidth.value > 0) {
|
|
@@ -167,7 +207,7 @@ const Slider = forwardRef<View, SliderProps>(({
|
|
|
167
207
|
};
|
|
168
208
|
|
|
169
209
|
return (
|
|
170
|
-
<View ref={ref} nativeID={id} style={[sliderStyles.container, style]} testID={testID}>
|
|
210
|
+
<View ref={ref} nativeID={id} style={[sliderStyles.container, style]} testID={testID} {...nativeA11yProps}>
|
|
171
211
|
{showValue && (
|
|
172
212
|
<View style={sliderStyles.valueLabel as any}>
|
|
173
213
|
<Text>{value}</Text>
|