@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.
Files changed (84) hide show
  1. package/package.json +8 -3
  2. package/src/Accordion/Accordion.native.tsx +23 -2
  3. package/src/Accordion/Accordion.web.tsx +73 -2
  4. package/src/Accordion/types.ts +2 -1
  5. package/src/ActivityIndicator/ActivityIndicator.native.tsx +15 -1
  6. package/src/ActivityIndicator/ActivityIndicator.web.tsx +19 -2
  7. package/src/ActivityIndicator/types.ts +2 -1
  8. package/src/Avatar/Avatar.native.tsx +19 -2
  9. package/src/Avatar/Avatar.web.tsx +19 -2
  10. package/src/Avatar/types.ts +2 -1
  11. package/src/Breadcrumb/types.ts +3 -2
  12. package/src/Button/Button.native.tsx +48 -1
  13. package/src/Button/Button.styles.tsx +3 -5
  14. package/src/Button/Button.web.tsx +61 -2
  15. package/src/Button/types.ts +2 -1
  16. package/src/Card/Card.native.tsx +21 -5
  17. package/src/Card/Card.web.tsx +21 -4
  18. package/src/Card/types.ts +2 -6
  19. package/src/Checkbox/Checkbox.native.tsx +46 -5
  20. package/src/Checkbox/Checkbox.web.tsx +80 -4
  21. package/src/Checkbox/types.ts +2 -6
  22. package/src/Chip/Chip.native.tsx +5 -0
  23. package/src/Chip/Chip.web.tsx +5 -1
  24. package/src/Chip/types.ts +2 -1
  25. package/src/Dialog/Dialog.native.tsx +20 -3
  26. package/src/Dialog/Dialog.web.tsx +29 -4
  27. package/src/Dialog/types.ts +2 -1
  28. package/src/Image/Image.native.tsx +1 -1
  29. package/src/Image/Image.web.tsx +2 -0
  30. package/src/Input/Input.native.tsx +37 -1
  31. package/src/Input/Input.web.tsx +75 -8
  32. package/src/Input/types.ts +2 -1
  33. package/src/List/List.native.tsx +18 -2
  34. package/src/List/ListItem.native.tsx +44 -8
  35. package/src/List/ListItem.web.tsx +16 -0
  36. package/src/List/types.ts +6 -3
  37. package/src/Menu/Menu.native.tsx +21 -2
  38. package/src/Menu/Menu.web.tsx +110 -3
  39. package/src/Menu/MenuItem.web.tsx +12 -3
  40. package/src/Menu/types.ts +2 -1
  41. package/src/Popover/Popover.native.tsx +17 -1
  42. package/src/Popover/Popover.web.tsx +31 -2
  43. package/src/Popover/types.ts +2 -1
  44. package/src/RadioButton/RadioButton.native.tsx +41 -3
  45. package/src/RadioButton/RadioButton.web.tsx +45 -6
  46. package/src/RadioButton/RadioGroup.native.tsx +20 -2
  47. package/src/RadioButton/RadioGroup.web.tsx +24 -3
  48. package/src/RadioButton/types.ts +3 -2
  49. package/src/Select/types.ts +2 -6
  50. package/src/Skeleton/Skeleton.native.tsx +15 -1
  51. package/src/Skeleton/Skeleton.web.tsx +20 -1
  52. package/src/Skeleton/types.ts +2 -1
  53. package/src/Slider/Slider.native.tsx +42 -2
  54. package/src/Slider/Slider.web.tsx +81 -7
  55. package/src/Slider/types.ts +2 -1
  56. package/src/Switch/Switch.native.tsx +41 -3
  57. package/src/Switch/Switch.web.tsx +45 -5
  58. package/src/Switch/types.ts +2 -1
  59. package/src/TabBar/TabBar.native.tsx +23 -2
  60. package/src/TabBar/TabBar.web.tsx +71 -2
  61. package/src/TabBar/types.ts +2 -1
  62. package/src/Table/Table.native.tsx +17 -1
  63. package/src/Table/Table.web.tsx +20 -3
  64. package/src/Table/types.ts +3 -2
  65. package/src/TextArea/TextArea.native.tsx +50 -1
  66. package/src/TextArea/TextArea.web.tsx +82 -6
  67. package/src/TextArea/types.ts +2 -1
  68. package/src/Tooltip/Tooltip.native.tsx +19 -2
  69. package/src/Tooltip/Tooltip.web.tsx +54 -2
  70. package/src/Tooltip/types.ts +2 -1
  71. package/src/Video/Video.native.tsx +18 -3
  72. package/src/Video/Video.web.tsx +17 -1
  73. package/src/Video/types.ts +2 -1
  74. package/src/examples/InputExamples.tsx +53 -0
  75. package/src/examples/ListExamples.tsx +34 -0
  76. package/src/internal/index.ts +2 -0
  77. package/src/utils/accessibility/ariaHelpers.ts +393 -0
  78. package/src/utils/accessibility/index.ts +210 -0
  79. package/src/utils/accessibility/keyboardPatterns.ts +263 -0
  80. package/src/utils/accessibility/types.ts +223 -0
  81. package/src/utils/accessibility/useAnnounce.ts +210 -0
  82. package/src/utils/accessibility/useFocusTrap.ts +265 -0
  83. package/src/utils/accessibility/useKeyboardNavigation.ts +292 -0
  84. 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: React.FC<MenuItemProps> = ({ item, onPress, size = 'md', testID }) => {
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 ref={mergedPopoverRef} id={id} data-testid={testID}>
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}
@@ -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
- accessibilityRole="radio"
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={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
- role="radiogroup"
39
- id={id}
59
+ {...ariaProps}
60
+ id={groupId}
40
61
  data-testid={testID}
41
62
  style={{
42
63
  display: 'flex',
@@ -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;
@@ -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,
@@ -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>