@cdx-ui/primitives 0.0.1-beta.170 → 0.0.1-beta.172

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 (112) hide show
  1. package/lib/commonjs/accordion/Accordion.js +59 -0
  2. package/lib/commonjs/accordion/Accordion.js.map +1 -0
  3. package/lib/commonjs/accordion/AccordionItem.js +68 -0
  4. package/lib/commonjs/accordion/AccordionItem.js.map +1 -0
  5. package/lib/commonjs/accordion/AccordionPanel.js +71 -0
  6. package/lib/commonjs/accordion/AccordionPanel.js.map +1 -0
  7. package/lib/commonjs/accordion/AccordionTrigger.js +122 -0
  8. package/lib/commonjs/accordion/AccordionTrigger.js.map +1 -0
  9. package/lib/commonjs/accordion/context.js +26 -0
  10. package/lib/commonjs/accordion/context.js.map +1 -0
  11. package/lib/commonjs/accordion/index.js +59 -0
  12. package/lib/commonjs/accordion/index.js.map +1 -0
  13. package/lib/commonjs/accordion/types.js +6 -0
  14. package/lib/commonjs/accordion/types.js.map +1 -0
  15. package/lib/commonjs/accordion/utils.js +24 -0
  16. package/lib/commonjs/accordion/utils.js.map +1 -0
  17. package/lib/commonjs/index.js +12 -0
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/slider/SliderRail.js +35 -9
  20. package/lib/commonjs/slider/SliderRail.js.map +1 -1
  21. package/lib/commonjs/slider/SliderRoot.js +155 -97
  22. package/lib/commonjs/slider/SliderRoot.js.map +1 -1
  23. package/lib/commonjs/slider/SliderThumb.js +182 -68
  24. package/lib/commonjs/slider/SliderThumb.js.map +1 -1
  25. package/lib/commonjs/slider/context.js +4 -1
  26. package/lib/commonjs/slider/context.js.map +1 -1
  27. package/lib/commonjs/slider/index.js +36 -0
  28. package/lib/commonjs/slider/index.js.map +1 -1
  29. package/lib/commonjs/slider/useSliderPointer.js +1 -1
  30. package/lib/commonjs/slider/useSliderPointer.js.map +1 -1
  31. package/lib/commonjs/slider/utils.js +68 -0
  32. package/lib/commonjs/slider/utils.js.map +1 -1
  33. package/lib/module/accordion/Accordion.js +55 -0
  34. package/lib/module/accordion/Accordion.js.map +1 -0
  35. package/lib/module/accordion/AccordionItem.js +64 -0
  36. package/lib/module/accordion/AccordionItem.js.map +1 -0
  37. package/lib/module/accordion/AccordionPanel.js +67 -0
  38. package/lib/module/accordion/AccordionPanel.js.map +1 -0
  39. package/lib/module/accordion/AccordionTrigger.js +118 -0
  40. package/lib/module/accordion/AccordionTrigger.js.map +1 -0
  41. package/lib/module/accordion/context.js +20 -0
  42. package/lib/module/accordion/context.js.map +1 -0
  43. package/lib/module/accordion/index.js +8 -0
  44. package/lib/module/accordion/index.js.map +1 -0
  45. package/lib/module/accordion/types.js +4 -0
  46. package/lib/module/accordion/types.js.map +1 -0
  47. package/lib/module/accordion/utils.js +19 -0
  48. package/lib/module/accordion/utils.js.map +1 -0
  49. package/lib/module/index.js +1 -0
  50. package/lib/module/index.js.map +1 -1
  51. package/lib/module/slider/SliderRail.js +35 -9
  52. package/lib/module/slider/SliderRail.js.map +1 -1
  53. package/lib/module/slider/SliderRoot.js +157 -99
  54. package/lib/module/slider/SliderRoot.js.map +1 -1
  55. package/lib/module/slider/SliderThumb.js +185 -71
  56. package/lib/module/slider/SliderThumb.js.map +1 -1
  57. package/lib/module/slider/context.js +1 -0
  58. package/lib/module/slider/context.js.map +1 -1
  59. package/lib/module/slider/index.js +2 -2
  60. package/lib/module/slider/index.js.map +1 -1
  61. package/lib/module/slider/useSliderPointer.js +1 -1
  62. package/lib/module/slider/useSliderPointer.js.map +1 -1
  63. package/lib/module/slider/utils.js +63 -0
  64. package/lib/module/slider/utils.js.map +1 -1
  65. package/lib/typescript/accordion/Accordion.d.ts +8 -0
  66. package/lib/typescript/accordion/Accordion.d.ts.map +1 -0
  67. package/lib/typescript/accordion/AccordionItem.d.ts +8 -0
  68. package/lib/typescript/accordion/AccordionItem.d.ts.map +1 -0
  69. package/lib/typescript/accordion/AccordionPanel.d.ts +7 -0
  70. package/lib/typescript/accordion/AccordionPanel.d.ts.map +1 -0
  71. package/lib/typescript/accordion/AccordionTrigger.d.ts +6 -0
  72. package/lib/typescript/accordion/AccordionTrigger.d.ts.map +1 -0
  73. package/lib/typescript/accordion/context.d.ts +6 -0
  74. package/lib/typescript/accordion/context.d.ts.map +1 -0
  75. package/lib/typescript/accordion/index.d.ts +7 -0
  76. package/lib/typescript/accordion/index.d.ts.map +1 -0
  77. package/lib/typescript/accordion/types.d.ts +50 -0
  78. package/lib/typescript/accordion/types.d.ts.map +1 -0
  79. package/lib/typescript/accordion/utils.d.ts +4 -0
  80. package/lib/typescript/accordion/utils.d.ts.map +1 -0
  81. package/lib/typescript/index.d.ts +1 -0
  82. package/lib/typescript/index.d.ts.map +1 -1
  83. package/lib/typescript/slider/SliderRail.d.ts.map +1 -1
  84. package/lib/typescript/slider/SliderRoot.d.ts +2 -2
  85. package/lib/typescript/slider/SliderRoot.d.ts.map +1 -1
  86. package/lib/typescript/slider/SliderThumb.d.ts.map +1 -1
  87. package/lib/typescript/slider/context.d.ts +53 -9
  88. package/lib/typescript/slider/context.d.ts.map +1 -1
  89. package/lib/typescript/slider/index.d.ts +4 -4
  90. package/lib/typescript/slider/index.d.ts.map +1 -1
  91. package/lib/typescript/slider/types.d.ts +44 -8
  92. package/lib/typescript/slider/types.d.ts.map +1 -1
  93. package/lib/typescript/slider/utils.d.ts +8 -0
  94. package/lib/typescript/slider/utils.d.ts.map +1 -1
  95. package/package.json +5 -3
  96. package/src/accordion/Accordion.tsx +72 -0
  97. package/src/accordion/AccordionItem.tsx +70 -0
  98. package/src/accordion/AccordionPanel.tsx +72 -0
  99. package/src/accordion/AccordionTrigger.tsx +120 -0
  100. package/src/accordion/context.tsx +22 -0
  101. package/src/accordion/index.ts +21 -0
  102. package/src/accordion/types.ts +56 -0
  103. package/src/accordion/utils.ts +22 -0
  104. package/src/index.ts +1 -0
  105. package/src/slider/SliderRail.tsx +56 -8
  106. package/src/slider/SliderRoot.tsx +255 -136
  107. package/src/slider/SliderThumb.tsx +240 -88
  108. package/src/slider/context.tsx +53 -9
  109. package/src/slider/index.ts +9 -2
  110. package/src/slider/types.ts +49 -8
  111. package/src/slider/useSliderPointer.ts +1 -1
  112. package/src/slider/utils.ts +76 -0
@@ -0,0 +1,72 @@
1
+ import { forwardRef } from 'react';
2
+ import { Platform, View } from 'react-native';
3
+ import { useDisclosure } from '@react-native-aria/disclosure';
4
+ import { Slot } from '../slot';
5
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
6
+ import { useAccordionItemContext } from './context';
7
+ import type { IAccordionPanelProps } from './types';
8
+
9
+ export interface AccordionPanelRootProps extends IAccordionPanelProps {
10
+ asChild?: boolean;
11
+ }
12
+
13
+ export const AccordionPanelRoot = forwardRef<View, AccordionPanelRootProps>(
14
+ ({ asChild, children, className, style, ...props }, ref) => {
15
+ const { isExpanded, isDisabled, triggerId, panelId, toggle } = useAccordionItemContext();
16
+
17
+ const toggleState = {
18
+ isSelected: isExpanded,
19
+ defaultSelected: false,
20
+ setSelected: (selected: boolean) => {
21
+ if (selected !== isExpanded) {
22
+ toggle();
23
+ }
24
+ },
25
+ toggle,
26
+ };
27
+
28
+ const { disclosureProps } = useDisclosure({}, toggleState);
29
+ const Comp = asChild ? Slot : View;
30
+
31
+ if (!isExpanded) {
32
+ return null;
33
+ }
34
+
35
+ const platformA11yProps =
36
+ Platform.OS === 'web'
37
+ ? {
38
+ id: panelId,
39
+ nativeID: panelId,
40
+ role: 'region' as const,
41
+ 'aria-labelledby': triggerId,
42
+ }
43
+ : {
44
+ nativeID: panelId,
45
+ accessibilityLabelledBy: triggerId,
46
+ accessibilityRole: 'none' as const,
47
+ };
48
+
49
+ return (
50
+ <Comp
51
+ ref={ref}
52
+ className={className}
53
+ style={style}
54
+ {...disclosureProps}
55
+ {...props}
56
+ {...mergeDataAttributes(
57
+ props,
58
+ dataAttributes({
59
+ slot: 'accordion-panel',
60
+ state: 'open',
61
+ disabled: isDisabled,
62
+ }),
63
+ )}
64
+ {...platformA11yProps}
65
+ >
66
+ {children}
67
+ </Comp>
68
+ );
69
+ },
70
+ );
71
+
72
+ AccordionPanelRoot.displayName = 'AccordionPanelRoot';
@@ -0,0 +1,120 @@
1
+ import { forwardRef, useMemo } from 'react';
2
+ import { Platform, Pressable } from 'react-native';
3
+ import { composeEventHandlers } from '@cdx-ui/utils';
4
+ import { useFocus, useFocusRing } from '@react-native-aria/focus';
5
+ import { usePress, useHover } from '@react-native-aria/interactions';
6
+ import { useDisclosureButton } from '@react-native-aria/disclosure';
7
+ import { Slot } from '../slot';
8
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
9
+ import { useAccordionItemContext } from './context';
10
+ import type { IAccordionTriggerProps } from './types';
11
+
12
+ export interface AccordionTriggerRootProps extends IAccordionTriggerProps {
13
+ className?: string;
14
+ }
15
+
16
+ export const AccordionTriggerRoot = forwardRef<
17
+ React.ComponentRef<typeof Pressable>,
18
+ AccordionTriggerRootProps
19
+ >(
20
+ (
21
+ {
22
+ asChild,
23
+ children,
24
+ className,
25
+ style,
26
+ onPress,
27
+ onPressIn,
28
+ onPressOut,
29
+ onHoverIn,
30
+ onHoverOut,
31
+ onFocus,
32
+ onBlur,
33
+ accessibilityLabel,
34
+ ...props
35
+ },
36
+ ref,
37
+ ) => {
38
+ const { isExpanded, isDisabled, triggerId, panelId, toggle } = useAccordionItemContext();
39
+
40
+ const toggleState = useMemo(
41
+ () => ({
42
+ isSelected: isExpanded,
43
+ defaultSelected: false,
44
+ setSelected: (selected: boolean) => {
45
+ if (selected !== isExpanded) {
46
+ toggle();
47
+ }
48
+ },
49
+ toggle,
50
+ }),
51
+ [isExpanded, toggle],
52
+ );
53
+
54
+ const { buttonProps } = useDisclosureButton({}, toggleState);
55
+ const { focusProps, isFocused } = useFocus();
56
+ const { focusProps: focusRingProps, isFocusVisible } = useFocusRing() as {
57
+ focusProps: { onFocus?: () => void; onBlur?: () => void };
58
+ isFocusVisible: boolean;
59
+ };
60
+ const { pressProps, isPressed } = usePress({ isDisabled });
61
+ const { hoverProps, isHovered } = useHover({ isDisabled });
62
+
63
+ const Comp = asChild ? Slot : Pressable;
64
+
65
+ const platformA11yProps =
66
+ Platform.OS === 'web'
67
+ ? {
68
+ id: triggerId,
69
+ role: 'button' as const,
70
+ 'aria-expanded': isExpanded,
71
+ 'aria-disabled': isDisabled || undefined,
72
+ 'aria-controls': isExpanded ? panelId : undefined,
73
+ }
74
+ : {
75
+ nativeID: triggerId,
76
+ accessibilityRole: 'button' as const,
77
+ accessibilityState: { expanded: isExpanded, disabled: isDisabled },
78
+ accessibilityControls: isExpanded ? panelId : undefined,
79
+ };
80
+
81
+ const { onPress: disclosureOnPress, ...disclosureRestProps } = buttonProps;
82
+ const disclosureProps = Platform.OS === 'web' ? {} : disclosureRestProps;
83
+
84
+ return (
85
+ <Comp
86
+ ref={ref}
87
+ disabled={isDisabled}
88
+ accessibilityLabel={accessibilityLabel}
89
+ className={className}
90
+ style={style}
91
+ {...props}
92
+ {...disclosureProps}
93
+ {...platformA11yProps}
94
+ {...mergeDataAttributes(
95
+ props,
96
+ dataAttributes({
97
+ slot: 'accordion-trigger',
98
+ state: isExpanded ? 'open' : 'closed',
99
+ disabled: isDisabled,
100
+ hover: isHovered,
101
+ active: isPressed,
102
+ focus: isFocused,
103
+ focusVisible: isFocusVisible,
104
+ }),
105
+ )}
106
+ onPressIn={composeEventHandlers(onPressIn, pressProps.onPressIn)}
107
+ onPressOut={composeEventHandlers(onPressOut, pressProps.onPressOut)}
108
+ onHoverIn={composeEventHandlers(onHoverIn, hoverProps.onHoverIn)}
109
+ onHoverOut={composeEventHandlers(onHoverOut, hoverProps.onHoverOut)}
110
+ onPress={composeEventHandlers(onPress, disclosureOnPress)}
111
+ onFocus={composeEventHandlers(onFocus, focusProps.onFocus, focusRingProps.onFocus)}
112
+ onBlur={composeEventHandlers(onBlur, focusProps.onBlur, focusRingProps.onBlur)}
113
+ >
114
+ {children}
115
+ </Comp>
116
+ );
117
+ },
118
+ );
119
+
120
+ AccordionTriggerRoot.displayName = 'AccordionTriggerRoot';
@@ -0,0 +1,22 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { IAccordionContextValue, IAccordionItemContextValue } from './types';
3
+
4
+ export const AccordionContext = createContext<IAccordionContextValue | null>(null);
5
+
6
+ export const AccordionItemContext = createContext<IAccordionItemContextValue | null>(null);
7
+
8
+ export function useAccordionContext(): IAccordionContextValue {
9
+ const context = useContext(AccordionContext);
10
+ if (!context) {
11
+ throw new Error('Accordion compound components must be used within Accordion');
12
+ }
13
+ return context;
14
+ }
15
+
16
+ export function useAccordionItemContext(): IAccordionItemContextValue {
17
+ const context = useContext(AccordionItemContext);
18
+ if (!context) {
19
+ throw new Error('Accordion.Item sub-components must be used within Accordion.Item');
20
+ }
21
+ return context;
22
+ }
@@ -0,0 +1,21 @@
1
+ export type {
2
+ IAccordionProps,
3
+ IAccordionItemProps,
4
+ IAccordionTriggerProps,
5
+ IAccordionPanelProps,
6
+ IAccordionContextValue,
7
+ IAccordionItemContextValue,
8
+ AccordionType,
9
+ } from './types';
10
+
11
+ export {
12
+ AccordionContext,
13
+ AccordionItemContext,
14
+ useAccordionContext,
15
+ useAccordionItemContext,
16
+ } from './context';
17
+
18
+ export { AccordionRoot, type AccordionRootProps } from './Accordion';
19
+ export { AccordionItemRoot, type AccordionItemRootProps } from './AccordionItem';
20
+ export { AccordionTriggerRoot, type AccordionTriggerRootProps } from './AccordionTrigger';
21
+ export { AccordionPanelRoot, type AccordionPanelRootProps } from './AccordionPanel';
@@ -0,0 +1,56 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PressableProps, ViewProps } from 'react-native';
3
+ import type { DisclosureGroupState } from '@react-stately/disclosure';
4
+
5
+ export type AccordionType = 'single' | 'multiple';
6
+
7
+ export interface IAccordionProps extends ViewProps {
8
+ /** Controlled expanded item value(s). */
9
+ value?: string | string[];
10
+ /** Default expanded value(s) for uncontrolled usage. */
11
+ defaultValue?: string | string[];
12
+ /** Called when expanded items change. */
13
+ onValueChange?: (value: string | string[]) => void;
14
+ /** Single-select (accordion) vs multi-select (disclosure group). @default 'single' */
15
+ type?: AccordionType;
16
+ /** When `type="single"`, allow closing the open item. @default true */
17
+ collapsible?: boolean;
18
+ /** Disables expansion for all items. */
19
+ isDisabled?: boolean;
20
+ 'aria-label'?: string;
21
+ 'aria-labelledby'?: string;
22
+ }
23
+
24
+ export interface IAccordionItemProps extends ViewProps {
25
+ /** Required stable id within the Accordion. */
26
+ value: string;
27
+ disabled?: boolean;
28
+ }
29
+
30
+ export interface IAccordionTriggerProps extends PressableProps {
31
+ children?: ReactNode;
32
+ asChild?: boolean;
33
+ }
34
+
35
+ export interface IAccordionPanelProps extends ViewProps {
36
+ children?: ReactNode;
37
+ className?: string;
38
+ }
39
+
40
+ export interface IAccordionContextValue {
41
+ groupState: DisclosureGroupState;
42
+ type: AccordionType;
43
+ collapsible: boolean;
44
+ isDisabled: boolean;
45
+ }
46
+
47
+ export interface IAccordionItemContextValue {
48
+ value: string;
49
+ isExpanded: boolean;
50
+ isDisabled: boolean;
51
+ triggerId: string;
52
+ panelId: string;
53
+ toggle: () => void;
54
+ disableGutters?: boolean;
55
+ showSeparator?: boolean;
56
+ }
@@ -0,0 +1,22 @@
1
+ import type { AccordionType } from './types';
2
+
3
+ export function toExpandedKeys(value: string | string[] | undefined): Set<string> | undefined {
4
+ if (value === undefined) {
5
+ return undefined;
6
+ }
7
+ if (Array.isArray(value)) {
8
+ return new Set(value);
9
+ }
10
+ if (value === '') {
11
+ return new Set<string>();
12
+ }
13
+ return new Set<string>([value]);
14
+ }
15
+
16
+ export function fromExpandedKeys(
17
+ keys: Iterable<string | number>,
18
+ type: AccordionType,
19
+ ): string | string[] {
20
+ const values = Array.from(keys).map(String);
21
+ return type === 'single' ? (values[0] ?? '') : values;
22
+ }
package/src/index.ts CHANGED
@@ -65,6 +65,7 @@ export * from './tooltip';
65
65
  export * from './portal';
66
66
  export * from './progress-bar';
67
67
  export * from './radio';
68
+ export * from './accordion';
68
69
  export * from './slider';
69
70
  export type { InteractionState } from './types';
70
71
  export { FormControlContext, useFormControlContext } from '@cdx-ui/utils';
@@ -14,23 +14,30 @@ import { dataAttributes } from '../utils/dataAttributes';
14
14
  import { useSliderContext } from './context';
15
15
  import type { InterfaceSliderRailProps } from './types';
16
16
  import { getPointerCoords, getRatioFromPagePoint, useWebSliderDrag } from './useSliderPointer';
17
+ import { clamp, getClosestValueIndex } from './utils';
17
18
 
18
19
  export interface SliderRailProps extends InterfaceSliderRailProps {}
19
20
 
20
21
  export const SliderRail = forwardRef<View, SliderRailProps>(
21
22
  ({ asChild, style, onLayout, children, ...props }, ref) => {
22
23
  const {
24
+ values,
25
+ min,
26
+ max,
27
+ step,
23
28
  orientation,
24
29
  isDisabled,
25
30
  isDragging,
26
31
  trackLayout,
27
32
  thumbSize,
28
- setValueFromRatio,
33
+ setNearestThumbValueFromRatio,
34
+ setThumbValueFromRatio,
35
+ setDraggingIndex,
29
36
  setTrackLayout,
30
- setIsDragging,
31
37
  } = useSliderContext();
32
38
 
33
39
  const railRef = useRef<View>(null);
40
+ const draggingThumbIndexRef = useRef<number | null>(null);
34
41
 
35
42
  const measureRail = useCallback(() => {
36
43
  const node = railRef.current;
@@ -50,10 +57,32 @@ export const SliderRail = forwardRef<View, SliderRailProps>(
50
57
  [measureRail, onLayout],
51
58
  );
52
59
 
60
+ const setRatioForRailDrag = useCallback(
61
+ (ratio: number, complete?: boolean) => {
62
+ const thumbIndex = draggingThumbIndexRef.current;
63
+ if (thumbIndex == null) {
64
+ setNearestThumbValueFromRatio(ratio, complete);
65
+ return;
66
+ }
67
+ setThumbValueFromRatio(thumbIndex, ratio, complete);
68
+ if (complete) {
69
+ draggingThumbIndexRef.current = null;
70
+ }
71
+ },
72
+ [setNearestThumbValueFromRatio, setThumbValueFromRatio],
73
+ );
74
+
75
+ const setDraggingForRail = useCallback(
76
+ (dragging: boolean) => {
77
+ setDraggingIndex(dragging ? draggingThumbIndexRef.current : null);
78
+ },
79
+ [setDraggingIndex],
80
+ );
81
+
53
82
  const { startDrag } = useWebSliderDrag({
54
83
  isDisabled,
55
- setIsDragging,
56
- setValueFromRatio,
84
+ setIsDragging: setDraggingForRail,
85
+ setValueFromRatio: setRatioForRailDrag,
57
86
  trackLayout,
58
87
  orientation,
59
88
  thumbSize,
@@ -65,9 +94,12 @@ export const SliderRail = forwardRef<View, SliderRailProps>(
65
94
  return;
66
95
  }
67
96
  const { x, y } = getPointerCoords(event.nativeEvent);
68
- setValueFromRatio(getRatioFromPagePoint(x, y, trackLayout, orientation, thumbSize), true);
97
+ setNearestThumbValueFromRatio(
98
+ getRatioFromPagePoint(x, y, trackLayout, orientation, thumbSize),
99
+ true,
100
+ );
69
101
  },
70
- [isDisabled, orientation, setValueFromRatio, thumbSize, trackLayout],
102
+ [isDisabled, orientation, setNearestThumbValueFromRatio, thumbSize, trackLayout],
71
103
  );
72
104
 
73
105
  const handlePress = useCallback(
@@ -91,9 +123,25 @@ export const SliderRail = forwardRef<View, SliderRailProps>(
91
123
  setTrackLayout(freshLayout);
92
124
 
93
125
  const { x, y } = getPointerCoords(event.nativeEvent);
94
- startDrag(x, y, undefined, freshLayout);
126
+ const ratio = getRatioFromPagePoint(x, y, freshLayout, orientation, thumbSize);
127
+ const target = min + clamp(ratio, 0, 1) * (max - min);
128
+ const nearestIndex = getClosestValueIndex(values, target);
129
+ draggingThumbIndexRef.current = nearestIndex;
130
+ setDraggingIndex(nearestIndex);
131
+ startDrag(x, y, ratio, freshLayout);
95
132
  },
96
- [isDisabled, setTrackLayout, startDrag],
133
+ [
134
+ isDisabled,
135
+ max,
136
+ min,
137
+ orientation,
138
+ setDraggingIndex,
139
+ setTrackLayout,
140
+ startDrag,
141
+ step,
142
+ thumbSize,
143
+ values,
144
+ ],
97
145
  );
98
146
 
99
147
  const Comp = asChild ? Slot : View;