react-native-pro-accordion 1.0.1

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 (99) hide show
  1. package/README.md +476 -0
  2. package/index.js +3 -0
  3. package/lib/commonjs/components/Accordion.js +130 -0
  4. package/lib/commonjs/components/Accordion.js.map +1 -0
  5. package/lib/commonjs/components/AccordionContent.js +125 -0
  6. package/lib/commonjs/components/AccordionContent.js.map +1 -0
  7. package/lib/commonjs/components/AccordionItem.js +146 -0
  8. package/lib/commonjs/components/AccordionItem.js.map +1 -0
  9. package/lib/commonjs/components/AccordionSeparator.js +159 -0
  10. package/lib/commonjs/components/AccordionSeparator.js.map +1 -0
  11. package/lib/commonjs/core/AccordionContext.js +82 -0
  12. package/lib/commonjs/core/AccordionContext.js.map +1 -0
  13. package/lib/commonjs/core/AccordionRef.js +64 -0
  14. package/lib/commonjs/core/AccordionRef.js.map +1 -0
  15. package/lib/commonjs/core/types.js +6 -0
  16. package/lib/commonjs/core/types.js.map +1 -0
  17. package/lib/commonjs/hooks/useAccordion.js +18 -0
  18. package/lib/commonjs/hooks/useAccordion.js.map +1 -0
  19. package/lib/commonjs/hooks/useAccordionAccessibility.js +39 -0
  20. package/lib/commonjs/hooks/useAccordionAccessibility.js.map +1 -0
  21. package/lib/commonjs/hooks/useAccordionAnimation.js +135 -0
  22. package/lib/commonjs/hooks/useAccordionAnimation.js.map +1 -0
  23. package/lib/commonjs/hooks/useAccordionKeyboard.js +43 -0
  24. package/lib/commonjs/hooks/useAccordionKeyboard.js.map +1 -0
  25. package/lib/commonjs/hooks/useAccordionState.js +96 -0
  26. package/lib/commonjs/hooks/useAccordionState.js.map +1 -0
  27. package/lib/commonjs/index.js +176 -0
  28. package/lib/commonjs/index.js.map +1 -0
  29. package/lib/commonjs/package.json +1 -0
  30. package/lib/commonjs/theme/ThemeContext.js +48 -0
  31. package/lib/commonjs/theme/ThemeContext.js.map +1 -0
  32. package/lib/commonjs/theme/defaultTheme.js +79 -0
  33. package/lib/commonjs/theme/defaultTheme.js.map +1 -0
  34. package/lib/commonjs/theme/types.js +6 -0
  35. package/lib/commonjs/theme/types.js.map +1 -0
  36. package/lib/commonjs/utils/animations.js +41 -0
  37. package/lib/commonjs/utils/animations.js.map +1 -0
  38. package/lib/commonjs/utils/layout.js +155 -0
  39. package/lib/commonjs/utils/layout.js.map +1 -0
  40. package/lib/commonjs/utils/validators.js +26 -0
  41. package/lib/commonjs/utils/validators.js.map +1 -0
  42. package/lib/module/components/Accordion.js +125 -0
  43. package/lib/module/components/Accordion.js.map +1 -0
  44. package/lib/module/components/AccordionContent.js +120 -0
  45. package/lib/module/components/AccordionContent.js.map +1 -0
  46. package/lib/module/components/AccordionItem.js +141 -0
  47. package/lib/module/components/AccordionItem.js.map +1 -0
  48. package/lib/module/components/AccordionSeparator.js +155 -0
  49. package/lib/module/components/AccordionSeparator.js.map +1 -0
  50. package/lib/module/core/AccordionContext.js +75 -0
  51. package/lib/module/core/AccordionContext.js.map +1 -0
  52. package/lib/module/core/AccordionRef.js +59 -0
  53. package/lib/module/core/AccordionRef.js.map +1 -0
  54. package/lib/module/core/types.js +136 -0
  55. package/lib/module/core/types.js.map +1 -0
  56. package/lib/module/hooks/useAccordion.js +14 -0
  57. package/lib/module/hooks/useAccordion.js.map +1 -0
  58. package/lib/module/hooks/useAccordionAccessibility.js +34 -0
  59. package/lib/module/hooks/useAccordionAccessibility.js.map +1 -0
  60. package/lib/module/hooks/useAccordionAnimation.js +131 -0
  61. package/lib/module/hooks/useAccordionAnimation.js.map +1 -0
  62. package/lib/module/hooks/useAccordionKeyboard.js +38 -0
  63. package/lib/module/hooks/useAccordionKeyboard.js.map +1 -0
  64. package/lib/module/hooks/useAccordionState.js +92 -0
  65. package/lib/module/hooks/useAccordionState.js.map +1 -0
  66. package/lib/module/index.js +43 -0
  67. package/lib/module/index.js.map +1 -0
  68. package/lib/module/theme/ThemeContext.js +41 -0
  69. package/lib/module/theme/ThemeContext.js.map +1 -0
  70. package/lib/module/theme/defaultTheme.js +75 -0
  71. package/lib/module/theme/defaultTheme.js.map +1 -0
  72. package/lib/module/theme/types.js +66 -0
  73. package/lib/module/theme/types.js.map +1 -0
  74. package/lib/module/utils/animations.js +35 -0
  75. package/lib/module/utils/animations.js.map +1 -0
  76. package/lib/module/utils/layout.js +147 -0
  77. package/lib/module/utils/layout.js.map +1 -0
  78. package/lib/module/utils/validators.js +20 -0
  79. package/lib/module/utils/validators.js.map +1 -0
  80. package/package.json +25 -0
  81. package/src/components/Accordion.js +164 -0
  82. package/src/components/AccordionContent.js +149 -0
  83. package/src/components/AccordionItem.js +146 -0
  84. package/src/components/AccordionSeparator.js +168 -0
  85. package/src/core/AccordionContext.js +86 -0
  86. package/src/core/AccordionRef.js +69 -0
  87. package/src/core/types.js +133 -0
  88. package/src/hooks/useAccordion.js +14 -0
  89. package/src/hooks/useAccordionAccessibility.js +30 -0
  90. package/src/hooks/useAccordionAnimation.js +165 -0
  91. package/src/hooks/useAccordionKeyboard.js +38 -0
  92. package/src/hooks/useAccordionState.js +119 -0
  93. package/src/index.js +56 -0
  94. package/src/theme/ThemeContext.js +40 -0
  95. package/src/theme/defaultTheme.js +73 -0
  96. package/src/theme/types.js +63 -0
  97. package/src/utils/animations.js +38 -0
  98. package/src/utils/layout.js +138 -0
  99. package/src/utils/validators.js +20 -0
@@ -0,0 +1,69 @@
1
+ export class AccordionRefImpl {
2
+ constructor(expandedValuesRef, setExpandedValues, mode, readOnly) {
3
+ this.expandedValuesRef = expandedValuesRef;
4
+ this.setExpandedValues = setExpandedValues;
5
+ this.mode = mode;
6
+ this.readOnly = readOnly;
7
+ }
8
+
9
+ open = (value) => {
10
+ if (this.readOnly) return;
11
+
12
+ const currentValues = this.expandedValuesRef.current;
13
+ if (currentValues.includes(value)) return;
14
+
15
+ let newValues;
16
+ if (this.mode === "single") {
17
+ newValues = [value];
18
+ } else {
19
+ newValues = [...currentValues, value];
20
+ }
21
+
22
+ this.setExpandedValues(newValues);
23
+ };
24
+
25
+ close = (value) => {
26
+ if (this.readOnly) return;
27
+
28
+ const currentValues = this.expandedValuesRef.current;
29
+ if (!currentValues.includes(value)) return;
30
+
31
+ const newValues = currentValues.filter((v) => v !== value);
32
+ this.setExpandedValues(newValues);
33
+ };
34
+
35
+ toggle = (value) => {
36
+ if (this.readOnly) return;
37
+
38
+ const currentValues = this.expandedValuesRef.current;
39
+ if (currentValues.includes(value)) {
40
+ this.close(value);
41
+ } else {
42
+ this.open(value);
43
+ }
44
+ };
45
+
46
+ expandAll = () => {
47
+ if (this.readOnly || this.mode !== "multiple") return;
48
+ console.warn("expandAll requires all item values to be known");
49
+ };
50
+
51
+ collapseAll = () => {
52
+ if (this.readOnly) return;
53
+ this.setExpandedValues([]);
54
+ };
55
+
56
+ toggleAll = () => {
57
+ if (this.readOnly || this.mode !== "multiple") return;
58
+ const currentValues = this.expandedValuesRef.current;
59
+ if (currentValues.length > 0) {
60
+ this.collapseAll();
61
+ } else {
62
+ this.expandAll();
63
+ }
64
+ };
65
+
66
+ getExpandedItems = () => {
67
+ return [...this.expandedValuesRef.current];
68
+ };
69
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @typedef {'timing' | 'spring'} AnimationType
3
+ */
4
+
5
+ /**
6
+ * @typedef {Object} TimingAnimationConfig
7
+ * @property {'timing'} type
8
+ * @property {number} [duration]
9
+ * @property {Function} [easing]
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} SpringAnimationConfig
14
+ * @property {'spring'} type
15
+ * @property {number} [damping]
16
+ * @property {number} [mass]
17
+ * @property {number} [stiffness]
18
+ * @property {boolean} [overshootClamping]
19
+ * @property {number} [restSpeedThreshold]
20
+ * @property {number} [restDisplacementThreshold]
21
+ */
22
+
23
+ /**
24
+ * @typedef {TimingAnimationConfig | SpringAnimationConfig} AnimationConfig
25
+ */
26
+
27
+ /**
28
+ * @typedef {Object} AccordionTheme
29
+ * @property {Object} colors
30
+ * @property {string} colors.background
31
+ * @property {string} colors.surface
32
+ * @property {string} colors.primary
33
+ * @property {string} colors.text
34
+ * @property {string} colors.textSecondary
35
+ * @property {string} colors.border
36
+ * @property {string} colors.disabled
37
+ * @property {string} colors.icon
38
+ * @property {Object} spacing
39
+ * @property {number} spacing.xs
40
+ * @property {number} spacing.sm
41
+ * @property {number} spacing.md
42
+ * @property {number} spacing.lg
43
+ * @property {number} spacing.xl
44
+ * @property {Object} typography
45
+ * @property {Object} typography.header
46
+ * @property {Object} typography.content
47
+ * @property {Object} borderRadius
48
+ * @property {number} borderRadius.sm
49
+ * @property {number} borderRadius.md
50
+ * @property {number} borderRadius.lg
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object} AccordionItemProps
55
+ * @property {string} value - Unique identifier for the item
56
+ * @property {React.ReactNode | Function} header - Header content or render function
57
+ * @property {React.ReactNode | Function} children - Content to display when expanded
58
+ * @property {boolean} [disabled] - Whether the item is disabled
59
+ * @property {object} [style] - Custom style overrides
60
+ * @property {object} [headerStyle] - Custom header style
61
+ * @property {object} [contentStyle] - Custom content style
62
+ * @property {any} [data] - Custom data attached to item
63
+ */
64
+
65
+ /**
66
+ * @typedef {Object} AccordionEventHandlers
67
+ * @property {Function} [onOpen]
68
+ * @property {Function} [onClose]
69
+ * @property {Function} [onToggle]
70
+ * @property {Function} [onAnimationStart]
71
+ * @property {Function} [onAnimationEnd]
72
+ * @property {Function} [onStateChange]
73
+ */
74
+
75
+ /**
76
+ * @typedef {Object} AccordionProps
77
+ * @property {React.ReactNode} children - Array of accordion items
78
+ * @property {string[]} [value] - Controlled expanded values
79
+ * @property {string[]} [defaultValue] - Initial expanded values (uncontrolled)
80
+ * @property {Function} [onChange] - Callback when expanded values change
81
+ * @property {'single' | 'multiple'} [mode] - Expand mode
82
+ * @property {boolean} [readOnly] - Whether accordion is in read-only mode
83
+ * @property {AnimationConfig} [animation] - Animation configuration
84
+ * @property {Function} [renderIcon] - Custom renderer for expand/collapse icon
85
+ * @property {'left' | 'right'} [iconPosition] - Icon position
86
+ * @property {boolean} [showSeparators] - Whether to show item separators
87
+ * @property {React.ReactNode} [separatorComponent] - Custom separator component
88
+ * @property {object} [separatorStyle] - Separator style
89
+ * @property {boolean} [stickyHeaders] - Enable sticky headers
90
+ * @property {boolean} [lazyRender] - Lazy render content
91
+ * @property {Partial<AccordionTheme>} [theme] - Custom theme
92
+ * @property {boolean} [darkMode] - Enable dark mode
93
+ * @property {string} [accessibilityLabel] - Accessibility label
94
+ * @property {string} [testID] - Test ID for testing
95
+ * @property {Function} [onOpen]
96
+ * @property {Function} [onClose]
97
+ * @property {Function} [onToggle]
98
+ * @property {Function} [onAnimationStart]
99
+ * @property {Function} [onAnimationEnd]
100
+ * @property {Function} [onStateChange]
101
+ */
102
+
103
+ /**
104
+ * @typedef {Object} AccordionRef
105
+ * @property {Function} open - Open a specific item by value
106
+ * @property {Function} close - Close a specific item by value
107
+ * @property {Function} toggle - Toggle a specific item by value
108
+ * @property {Function} expandAll - Open all items (only works in multiple mode)
109
+ * @property {Function} collapseAll - Close all items
110
+ * @property {Function} toggleAll - Toggle all items
111
+ * @property {Function} getExpandedItems - Get currently expanded items
112
+ */
113
+
114
+ /**
115
+ * @typedef {Object} AccordionContextType
116
+ * @property {string} mode
117
+ * @property {boolean} readOnly
118
+ * @property {AnimationConfig} animationConfig
119
+ * @property {Function} [renderIcon]
120
+ * @property {string} iconPosition
121
+ * @property {boolean} showSeparators
122
+ * @property {boolean} lazyRender
123
+ * @property {AccordionTheme} [theme]
124
+ * @property {boolean} darkMode
125
+ * @property {Function} registerItem
126
+ * @property {Function} unregisterItem
127
+ * @property {Function} toggleItem
128
+ * @property {Function} isExpanded
129
+ * @property {Function} getItemData
130
+ * @property {Function} [onEvent]
131
+ */
132
+
133
+ export {};
@@ -0,0 +1,14 @@
1
+ import { useState } from "react";
2
+
3
+ export function useAccordion(defaultIndex = null) {
4
+ const [activeIndex, setActiveIndex] = useState(defaultIndex);
5
+
6
+ const toggle = (index) => {
7
+ setActiveIndex((prev) => (prev === index ? null : index));
8
+ };
9
+
10
+ return {
11
+ activeIndex,
12
+ toggle,
13
+ };
14
+ }
@@ -0,0 +1,30 @@
1
+ import { useMemo } from "react";
2
+ import { Platform } from "react-native";
3
+
4
+ export const useAccordionAccessibility = (props) => {
5
+ const { expanded, disabled, label } = props;
6
+
7
+ const accessibilityProps = useMemo(() => {
8
+ const baseProps = {
9
+ accessible: true,
10
+ accessibilityRole: "button",
11
+ accessibilityState: {
12
+ expanded,
13
+ disabled,
14
+ },
15
+ accessibilityLabel: label || "Toggle accordion",
16
+ };
17
+
18
+ if (Platform.OS === "web") {
19
+ return {
20
+ ...baseProps,
21
+ "aria-expanded": expanded,
22
+ "aria-disabled": disabled,
23
+ };
24
+ }
25
+
26
+ return baseProps;
27
+ }, [expanded, disabled, label]);
28
+
29
+ return { accessibilityProps };
30
+ };
@@ -0,0 +1,165 @@
1
+ import { useRef, useEffect, useCallback } from "react";
2
+ import {
3
+ useSharedValue,
4
+ withTiming,
5
+ withSpring,
6
+ Easing,
7
+ runOnJS,
8
+ } from "react-native-reanimated";
9
+
10
+ /**
11
+ * Hook for managing accordion animations
12
+ * @param {Object} options
13
+ * @param {boolean} options.expanded - Whether item is expanded
14
+ * @param {number} options.contentHeight - Height of the content
15
+ * @param {Object} options.animationConfig - Animation configuration
16
+ * @param {Function} options.onAnimationStart - Callback when animation starts
17
+ * @param {Function} options.onAnimationEnd - Callback when animation ends
18
+ * @returns {Object} Animation values and utilities
19
+ */
20
+ export const useAccordionAnimation = ({
21
+ expanded,
22
+ contentHeight,
23
+ animationConfig,
24
+ onAnimationStart,
25
+ onAnimationEnd,
26
+ }) => {
27
+ const height = useSharedValue(expanded ? contentHeight : 0);
28
+ const opacity = useSharedValue(expanded ? 1 : 0);
29
+ const rotation = useSharedValue(expanded ? 1 : 0);
30
+ const isAnimating = useRef(false);
31
+
32
+ const getAnimatedHeight = useCallback(
33
+ (toValue) => {
34
+ if (animationConfig.type === "spring") {
35
+ return withSpring(toValue, {
36
+ damping: animationConfig.damping ?? 10,
37
+ mass: animationConfig.mass ?? 1,
38
+ stiffness: animationConfig.stiffness ?? 100,
39
+ overshootClamping: animationConfig.overshootClamping ?? false,
40
+ restSpeedThreshold: animationConfig.restSpeedThreshold ?? 0.01,
41
+ restDisplacementThreshold:
42
+ animationConfig.restDisplacementThreshold ?? 0.01,
43
+ });
44
+ } else {
45
+ return withTiming(toValue, {
46
+ duration: animationConfig.duration ?? 300,
47
+ easing: animationConfig.easing ?? Easing.inOut(Easing.ease),
48
+ });
49
+ }
50
+ },
51
+ [animationConfig],
52
+ );
53
+
54
+ const getAnimatedOpacity = useCallback(
55
+ (toValue) => {
56
+ if (animationConfig.type === "spring") {
57
+ return withSpring(toValue, {
58
+ damping: animationConfig.damping ?? 10,
59
+ mass: animationConfig.mass ?? 1,
60
+ stiffness: animationConfig.stiffness ?? 100,
61
+ });
62
+ } else {
63
+ return withTiming(toValue, {
64
+ duration: (animationConfig.duration ?? 300) * 0.6,
65
+ easing: animationConfig.easing ?? Easing.inOut(Easing.ease),
66
+ });
67
+ }
68
+ },
69
+ [animationConfig],
70
+ );
71
+
72
+ const getAnimatedRotation = useCallback(
73
+ (toValue) => {
74
+ const targetRotation = toValue ? 1 : 0;
75
+ if (animationConfig.type === "spring") {
76
+ return withSpring(targetRotation, {
77
+ damping: animationConfig.damping ?? 10,
78
+ mass: animationConfig.mass ?? 1,
79
+ stiffness: animationConfig.stiffness ?? 100,
80
+ });
81
+ } else {
82
+ return withTiming(targetRotation, {
83
+ duration: animationConfig.duration ?? 300,
84
+ easing: animationConfig.easing ?? Easing.inOut(Easing.ease),
85
+ });
86
+ }
87
+ },
88
+ [animationConfig],
89
+ );
90
+
91
+ const animate = useCallback(() => {
92
+ if (onAnimationStart) {
93
+ runOnJS(onAnimationStart)();
94
+ }
95
+
96
+ isAnimating.current = true;
97
+ const toHeight = expanded ? contentHeight : 0;
98
+ const toOpacity = expanded ? 1 : 0;
99
+ const toRotation = expanded ? 1 : 0;
100
+
101
+ height.value = getAnimatedHeight(toHeight);
102
+ opacity.value = getAnimatedOpacity(toOpacity);
103
+ rotation.value = getAnimatedRotation(toRotation);
104
+
105
+ // Set up animation end callback
106
+ const timeoutId = setTimeout(
107
+ () => {
108
+ if (isAnimating.current && onAnimationEnd) {
109
+ runOnJS(onAnimationEnd)();
110
+ isAnimating.current = false;
111
+ }
112
+ },
113
+ animationConfig.type === "spring"
114
+ ? 500
115
+ : (animationConfig.duration ?? 300),
116
+ );
117
+
118
+ return () => clearTimeout(timeoutId);
119
+ }, [
120
+ expanded,
121
+ contentHeight,
122
+ animationConfig,
123
+ height,
124
+ opacity,
125
+ rotation,
126
+ getAnimatedHeight,
127
+ getAnimatedOpacity,
128
+ getAnimatedRotation,
129
+ onAnimationStart,
130
+ onAnimationEnd,
131
+ ]);
132
+
133
+ useEffect(() => {
134
+ const cleanup = animate();
135
+ return () => {
136
+ if (cleanup) cleanup();
137
+ isAnimating.current = false;
138
+ };
139
+ }, [animate]);
140
+
141
+ const resetAnimation = useCallback(() => {
142
+ height.value = 0;
143
+ opacity.value = 0;
144
+ rotation.value = 0;
145
+ isAnimating.current = false;
146
+ }, [height, opacity, rotation]);
147
+
148
+ const getRotatedStyle = useCallback(() => {
149
+ "worklet";
150
+ return {
151
+ transform: [{ rotate: `${rotation.value * 90}deg` }],
152
+ };
153
+ }, [rotation]);
154
+
155
+ return {
156
+ height,
157
+ opacity,
158
+ rotation,
159
+ isAnimating: isAnimating.current,
160
+ animatedHeightStyle: { height },
161
+ animatedOpacityStyle: { opacity },
162
+ animatedRotationStyle: getRotatedStyle,
163
+ resetAnimation,
164
+ };
165
+ };
@@ -0,0 +1,38 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { Platform } from "react-native";
3
+
4
+ export const useAccordionKeyboard = (props) => {
5
+ const { onPress, disabled = false } = props;
6
+ const elementRef = useRef(null);
7
+
8
+ useEffect(() => {
9
+ if (Platform.OS !== "web") return;
10
+
11
+ const handleKeyPress = (event) => {
12
+ if (disabled) return;
13
+
14
+ if (event.key === "Enter" || event.key === " ") {
15
+ event.preventDefault();
16
+ onPress();
17
+ }
18
+ };
19
+
20
+ const element = elementRef.current;
21
+ if (element) {
22
+ element.addEventListener("keydown", handleKeyPress);
23
+ }
24
+
25
+ return () => {
26
+ if (element) {
27
+ element.removeEventListener("keydown", handleKeyPress);
28
+ }
29
+ };
30
+ }, [onPress, disabled]);
31
+
32
+ const keyboardProps =
33
+ Platform.OS === "web"
34
+ ? { tabIndex: disabled ? -1 : 0, ref: elementRef }
35
+ : {};
36
+
37
+ return { keyboardProps };
38
+ };
@@ -0,0 +1,119 @@
1
+ import { useState, useCallback, useRef, useEffect } from "react";
2
+
3
+ /**
4
+ * Hook for managing accordion state with controlled/uncontrolled modes
5
+ * @param {Object} options
6
+ * @param {string[]} options.value - Controlled expanded values
7
+ * @param {string[]} options.defaultValue - Default expanded values for uncontrolled mode
8
+ * @param {Function} options.onChange - Callback when expanded values change
9
+ * @param {string} options.mode - 'single' or 'multiple' mode
10
+ * @returns {Object} State management utilities
11
+ */
12
+ export const useAccordionState = ({
13
+ value,
14
+ defaultValue = [],
15
+ onChange,
16
+ mode = "multiple",
17
+ }) => {
18
+ const isControlled = value !== undefined;
19
+ const [internalExpandedValues, setInternalExpandedValues] =
20
+ useState(defaultValue);
21
+
22
+ const expandedValues = isControlled ? value : internalExpandedValues;
23
+ const expandedValuesRef = useRef(expandedValues);
24
+ expandedValuesRef.current = expandedValues;
25
+
26
+ const setExpandedValues = useCallback(
27
+ (newValues) => {
28
+ if (!isControlled) {
29
+ setInternalExpandedValues(newValues);
30
+ }
31
+ onChange?.(newValues);
32
+ },
33
+ [isControlled, onChange],
34
+ );
35
+
36
+ const isExpanded = useCallback(
37
+ (itemValue) => {
38
+ return expandedValues.includes(itemValue);
39
+ },
40
+ [expandedValues],
41
+ );
42
+
43
+ const openItem = useCallback(
44
+ (itemValue) => {
45
+ if (expandedValues.includes(itemValue)) return;
46
+
47
+ let newValues;
48
+ if (mode === "single") {
49
+ newValues = [itemValue];
50
+ } else {
51
+ newValues = [...expandedValues, itemValue];
52
+ }
53
+
54
+ setExpandedValues(newValues);
55
+ return newValues;
56
+ },
57
+ [expandedValues, mode, setExpandedValues],
58
+ );
59
+
60
+ const closeItem = useCallback(
61
+ (itemValue) => {
62
+ if (!expandedValues.includes(itemValue)) return;
63
+
64
+ const newValues = expandedValues.filter((v) => v !== itemValue);
65
+ setExpandedValues(newValues);
66
+ return newValues;
67
+ },
68
+ [expandedValues, setExpandedValues],
69
+ );
70
+
71
+ const toggleItem = useCallback(
72
+ (itemValue) => {
73
+ if (expandedValues.includes(itemValue)) {
74
+ return closeItem(itemValue);
75
+ } else {
76
+ return openItem(itemValue);
77
+ }
78
+ },
79
+ [expandedValues, openItem, closeItem],
80
+ );
81
+
82
+ const expandAll = useCallback(() => {
83
+ if (mode !== "multiple") return;
84
+ // Note: This requires access to all registered items
85
+ console.warn("expandAll requires all item values to be accessible");
86
+ }, [mode]);
87
+
88
+ const collapseAll = useCallback(() => {
89
+ setExpandedValues([]);
90
+ }, [setExpandedValues]);
91
+
92
+ const toggleAll = useCallback(() => {
93
+ if (mode !== "multiple") return;
94
+ if (expandedValues.length > 0) {
95
+ collapseAll();
96
+ } else {
97
+ expandAll();
98
+ }
99
+ }, [mode, expandedValues.length, collapseAll, expandAll]);
100
+
101
+ const getExpandedItems = useCallback(() => {
102
+ return [...expandedValues];
103
+ }, [expandedValues]);
104
+
105
+ return {
106
+ expandedValues,
107
+ expandedValuesRef,
108
+ setExpandedValues,
109
+ isExpanded,
110
+ openItem,
111
+ closeItem,
112
+ toggleItem,
113
+ expandAll,
114
+ collapseAll,
115
+ toggleAll,
116
+ getExpandedItems,
117
+ isControlled,
118
+ };
119
+ };
package/src/index.js ADDED
@@ -0,0 +1,56 @@
1
+ // src/index.js
2
+ // export { Accordion } from "./Accordion";
3
+ // export { AccordionItem } from "./AccordionItem";
4
+
5
+ // Main components
6
+ export { Accordion } from "./components/Accordion";
7
+ export { AccordionItem } from "./components/AccordionItem";
8
+ export { AccordionContent } from "./components/AccordionContent";
9
+ export {
10
+ AccordionSeparator,
11
+ NestedAccordionSeparator,
12
+ DashedSeparator,
13
+ GradientSeparator,
14
+ } from "./components/AccordionSeparator";
15
+
16
+ // Context
17
+ export { useAccordion, AccordionContext } from "./core/AccordionContext";
18
+
19
+ // Hooks
20
+ export { useAccordionAccessibility } from "./hooks/useAccordionAccessibility";
21
+ export { useAccordionKeyboard } from "./hooks/useAccordionKeyboard";
22
+ export { useAccordionState } from "./hooks/useAccordionState";
23
+ export { useAccordionAnimation } from "./hooks/useAccordionAnimation";
24
+
25
+ // Theme
26
+ export { defaultLightTheme, defaultDarkTheme } from "./theme/defaultTheme";
27
+ export { ThemeProvider, useTheme } from "./theme/ThemeContext";
28
+
29
+ // Utilities
30
+ export { getAnimatedHeight, getAnimatedOpacity } from "./utils/animations";
31
+
32
+ export {
33
+ validateAnimationConfig,
34
+ validateAccordionMode,
35
+ } from "./utils/validators";
36
+
37
+ export {
38
+ useContentHeight,
39
+ useMultipleContentHeights,
40
+ animateLayout,
41
+ scrollToAccordionItem,
42
+ getElementPosition,
43
+ } from "./utils/layout";
44
+
45
+ // Re-export types for JSDoc (for documentation purposes)
46
+ /**
47
+ * @typedef {import('./core/types').AccordionProps} AccordionProps
48
+ * @typedef {import('./core/types').AccordionItemProps} AccordionItemProps
49
+ * @typedef {import('./core/types').AccordionRef} AccordionRef
50
+ * @typedef {import('./core/types').AccordionTheme} AccordionTheme
51
+ * @typedef {import('./core/types').AnimationConfig} AnimationConfig
52
+ * @typedef {import('./core/types').TimingAnimationConfig} TimingAnimationConfig
53
+ * @typedef {import('./core/types').SpringAnimationConfig} SpringAnimationConfig
54
+ * @typedef {import('./core/types').AnimationType} AnimationType
55
+ * @typedef {import('./core/types').AccordionEventHandlers} AccordionEventHandlers
56
+ */
@@ -0,0 +1,40 @@
1
+ import React, { createContext, useContext, useMemo } from "react";
2
+ import { defaultLightTheme, defaultDarkTheme } from "./defaultTheme";
3
+
4
+ const ThemeContext = createContext(undefined);
5
+
6
+ export const ThemeProvider = ({
7
+ children,
8
+ theme: customTheme,
9
+ darkMode = false,
10
+ onDarkModeChange,
11
+ }) => {
12
+ const baseTheme = darkMode ? defaultDarkTheme : defaultLightTheme;
13
+
14
+ const theme = useMemo(
15
+ () => ({
16
+ ...baseTheme,
17
+ ...customTheme,
18
+ colors: { ...baseTheme.colors, ...customTheme?.colors },
19
+ }),
20
+ [baseTheme, customTheme],
21
+ );
22
+
23
+ const setDarkMode = (dark) => {
24
+ onDarkModeChange?.(dark);
25
+ };
26
+
27
+ return (
28
+ <ThemeContext.Provider value={{ theme, darkMode, setDarkMode }}>
29
+ {children}
30
+ </ThemeContext.Provider>
31
+ );
32
+ };
33
+
34
+ export const useTheme = () => {
35
+ const context = useContext(ThemeContext);
36
+ if (!context) {
37
+ throw new Error("useTheme must be used within a ThemeProvider");
38
+ }
39
+ return context;
40
+ };