@cdx-ui/primitives 0.0.1-beta.171 → 0.0.1-beta.173

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 (64) 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/module/accordion/Accordion.js +55 -0
  20. package/lib/module/accordion/Accordion.js.map +1 -0
  21. package/lib/module/accordion/AccordionItem.js +64 -0
  22. package/lib/module/accordion/AccordionItem.js.map +1 -0
  23. package/lib/module/accordion/AccordionPanel.js +67 -0
  24. package/lib/module/accordion/AccordionPanel.js.map +1 -0
  25. package/lib/module/accordion/AccordionTrigger.js +118 -0
  26. package/lib/module/accordion/AccordionTrigger.js.map +1 -0
  27. package/lib/module/accordion/context.js +20 -0
  28. package/lib/module/accordion/context.js.map +1 -0
  29. package/lib/module/accordion/index.js +8 -0
  30. package/lib/module/accordion/index.js.map +1 -0
  31. package/lib/module/accordion/types.js +4 -0
  32. package/lib/module/accordion/types.js.map +1 -0
  33. package/lib/module/accordion/utils.js +19 -0
  34. package/lib/module/accordion/utils.js.map +1 -0
  35. package/lib/module/index.js +1 -0
  36. package/lib/module/index.js.map +1 -1
  37. package/lib/typescript/accordion/Accordion.d.ts +8 -0
  38. package/lib/typescript/accordion/Accordion.d.ts.map +1 -0
  39. package/lib/typescript/accordion/AccordionItem.d.ts +8 -0
  40. package/lib/typescript/accordion/AccordionItem.d.ts.map +1 -0
  41. package/lib/typescript/accordion/AccordionPanel.d.ts +7 -0
  42. package/lib/typescript/accordion/AccordionPanel.d.ts.map +1 -0
  43. package/lib/typescript/accordion/AccordionTrigger.d.ts +6 -0
  44. package/lib/typescript/accordion/AccordionTrigger.d.ts.map +1 -0
  45. package/lib/typescript/accordion/context.d.ts +6 -0
  46. package/lib/typescript/accordion/context.d.ts.map +1 -0
  47. package/lib/typescript/accordion/index.d.ts +7 -0
  48. package/lib/typescript/accordion/index.d.ts.map +1 -0
  49. package/lib/typescript/accordion/types.d.ts +50 -0
  50. package/lib/typescript/accordion/types.d.ts.map +1 -0
  51. package/lib/typescript/accordion/utils.d.ts +4 -0
  52. package/lib/typescript/accordion/utils.d.ts.map +1 -0
  53. package/lib/typescript/index.d.ts +1 -0
  54. package/lib/typescript/index.d.ts.map +1 -1
  55. package/package.json +5 -3
  56. package/src/accordion/Accordion.tsx +72 -0
  57. package/src/accordion/AccordionItem.tsx +70 -0
  58. package/src/accordion/AccordionPanel.tsx +72 -0
  59. package/src/accordion/AccordionTrigger.tsx +120 -0
  60. package/src/accordion/context.tsx +22 -0
  61. package/src/accordion/index.ts +21 -0
  62. package/src/accordion/types.ts +56 -0
  63. package/src/accordion/utils.ts +22 -0
  64. package/src/index.ts +1 -0
@@ -0,0 +1,70 @@
1
+ import { forwardRef, useCallback, useId, useMemo } from 'react';
2
+ import { View } from 'react-native';
3
+ import { Slot } from '../slot';
4
+ import { dataAttributes, mergeDataAttributes } from '../utils/dataAttributes';
5
+ import { AccordionItemContext, useAccordionContext } from './context';
6
+ import type { IAccordionItemProps } from './types';
7
+
8
+ export interface AccordionItemRootProps extends IAccordionItemProps {
9
+ asChild?: boolean;
10
+ className?: string;
11
+ }
12
+
13
+ export const AccordionItemRoot = forwardRef<View, AccordionItemRootProps>(
14
+ ({ asChild, children, className, style, value, disabled = false, ...props }, ref) => {
15
+ const { groupState, type, collapsible, isDisabled: groupDisabled } = useAccordionContext();
16
+ const reactId = useId().replace(/[^a-zA-Z0-9_-]/g, '');
17
+ const triggerId = `accordion-${reactId}-trigger`;
18
+ const panelId = `accordion-${reactId}-panel`;
19
+
20
+ const isExpanded = groupState.expandedKeys.has(value);
21
+ const isDisabled = disabled || groupDisabled;
22
+
23
+ const toggle = useCallback(() => {
24
+ if (isDisabled) {
25
+ return;
26
+ }
27
+ if (type === 'single' && !collapsible && isExpanded) {
28
+ return;
29
+ }
30
+ groupState.toggleKey(value);
31
+ }, [isDisabled, type, collapsible, isExpanded, groupState, value]);
32
+
33
+ const contextValue = useMemo(
34
+ () => ({
35
+ value,
36
+ isExpanded,
37
+ isDisabled,
38
+ triggerId,
39
+ panelId,
40
+ toggle,
41
+ }),
42
+ [value, isExpanded, isDisabled, triggerId, panelId, toggle],
43
+ );
44
+
45
+ const Comp = asChild ? Slot : View;
46
+
47
+ return (
48
+ <AccordionItemContext.Provider value={contextValue}>
49
+ <Comp
50
+ ref={ref}
51
+ className={className}
52
+ style={style}
53
+ {...props}
54
+ {...mergeDataAttributes(
55
+ props,
56
+ dataAttributes({
57
+ slot: 'accordion-item',
58
+ state: isExpanded ? 'open' : 'closed',
59
+ disabled: isDisabled,
60
+ }),
61
+ )}
62
+ >
63
+ {children}
64
+ </Comp>
65
+ </AccordionItemContext.Provider>
66
+ );
67
+ },
68
+ );
69
+
70
+ AccordionItemRoot.displayName = 'AccordionItemRoot';
@@ -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';