@brand-map/primitives 0.0.0-broken.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.oxfmtrc.json +35 -0
- package/.oxlintrc.json +166 -0
- package/README.md +78 -0
- package/bun.lock +904 -0
- package/mise.toml +3 -0
- package/package.json +61 -0
- package/src/accordion/accordion.tsx +189 -0
- package/src/accordion/accordion.web.tsx +282 -0
- package/src/accordion/index.ts +2 -0
- package/src/accordion/types.ts +44 -0
- package/src/alert-dialog/alert-dialog.tsx +238 -0
- package/src/alert-dialog/alert-dialog.web.tsx +260 -0
- package/src/alert-dialog/index.ts +2 -0
- package/src/alert-dialog/types.ts +81 -0
- package/src/aspect-ratio/aspect-ratio.tsx +27 -0
- package/src/aspect-ratio/index.ts +1 -0
- package/src/avatar/avatar.tsx +122 -0
- package/src/avatar/index.ts +2 -0
- package/src/avatar/types.ts +20 -0
- package/src/checkbox/checkbox.tsx +95 -0
- package/src/checkbox/checkbox.web.tsx +111 -0
- package/src/checkbox/index.ts +2 -0
- package/src/checkbox/types.ts +14 -0
- package/src/collapsible/collapsible.tsx +98 -0
- package/src/collapsible/collapsible.web.tsx +149 -0
- package/src/collapsible/index.ts +2 -0
- package/src/collapsible/types.ts +23 -0
- package/src/context-menu/context-menu.tsx +616 -0
- package/src/context-menu/context-menu.web.tsx +560 -0
- package/src/context-menu/index.ts +2 -0
- package/src/context-menu/types.ts +136 -0
- package/src/dialog/dialog.tsx +286 -0
- package/src/dialog/dialog.web.tsx +215 -0
- package/src/dialog/index.ts +2 -0
- package/src/dialog/types.ts +92 -0
- package/src/dropdown-menu/dropdown-menu.tsx +575 -0
- package/src/dropdown-menu/dropdown-menu.web.tsx +565 -0
- package/src/dropdown-menu/index.ts +2 -0
- package/src/dropdown-menu/types.ts +121 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-Isomorphic-layout-effect.tsx +12 -0
- package/src/hooks/use-augmented-ref.tsx +25 -0
- package/src/hooks/use-controllable-state.tsx +70 -0
- package/src/hooks/use-relative-position.tsx +175 -0
- package/src/hover-card/hover-card.tsx +255 -0
- package/src/hover-card/hover-card.web.tsx +161 -0
- package/src/hover-card/index.ts +2 -0
- package/src/hover-card/types.ts +56 -0
- package/src/label/index.ts +2 -0
- package/src/label/label.tsx +36 -0
- package/src/label/label.web.tsx +38 -0
- package/src/label/types.ts +24 -0
- package/src/menubar/index.ts +2 -0
- package/src/menubar/menubar.tsx +602 -0
- package/src/menubar/menubar.web.tsx +575 -0
- package/src/menubar/types.ts +126 -0
- package/src/navigation-menu/index.ts +2 -0
- package/src/navigation-menu/navigation-menu.tsx +302 -0
- package/src/navigation-menu/navigation-menu.web.tsx +259 -0
- package/src/navigation-menu/types.ts +85 -0
- package/src/popover/index.ts +2 -0
- package/src/popover/popover.tsx +279 -0
- package/src/popover/popover.web.tsx +217 -0
- package/src/popover/types.ts +44 -0
- package/src/portal/index.ts +1 -0
- package/src/portal/portal.tsx +56 -0
- package/src/progress/index.ts +2 -0
- package/src/progress/progress.tsx +59 -0
- package/src/progress/progress.web.tsx +46 -0
- package/src/progress/types.ts +14 -0
- package/src/radio-group/index.ts +2 -0
- package/src/radio-group/radio-group.tsx +106 -0
- package/src/radio-group/radio-group.web.tsx +85 -0
- package/src/radio-group/types.ts +24 -0
- package/src/select/index.ts +2 -0
- package/src/select/select.tsx +447 -0
- package/src/select/select.web.tsx +368 -0
- package/src/select/types.ts +145 -0
- package/src/separator/index.ts +2 -0
- package/src/separator/separator.tsx +21 -0
- package/src/separator/types.ts +10 -0
- package/src/slider/index.ts +2 -0
- package/src/slider/slider.tsx +77 -0
- package/src/slider/slider.web.tsx +75 -0
- package/src/slider/types.ts +39 -0
- package/src/slot/index.ts +1 -0
- package/src/slot/slot.tsx +224 -0
- package/src/switch/index.ts +2 -0
- package/src/switch/switch.tsx +49 -0
- package/src/switch/switch.web.tsx +60 -0
- package/src/switch/types.ts +19 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.tsx +121 -0
- package/src/tabs/index.ts +2 -0
- package/src/tabs/tabs.tsx +120 -0
- package/src/tabs/tabs.web.tsx +106 -0
- package/src/tabs/types.ts +37 -0
- package/src/toast/index.ts +2 -0
- package/src/toast/toast.tsx +124 -0
- package/src/toast/types.ts +20 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.tsx +35 -0
- package/src/toggle/toggle.web.tsx +36 -0
- package/src/toggle/types.ts +11 -0
- package/src/toggle-group/index.ts +2 -0
- package/src/toggle-group/toggle-group.tsx +100 -0
- package/src/toggle-group/toggle-group.web.tsx +103 -0
- package/src/toggle-group/types.ts +46 -0
- package/src/toolbar/index.ts +2 -0
- package/src/toolbar/toolbar.tsx +141 -0
- package/src/toolbar/toolbar.web.tsx +158 -0
- package/src/toolbar/types.ts +64 -0
- package/src/tooltip/index.ts +2 -0
- package/src/tooltip/tooltip.tsx +261 -0
- package/src/tooltip/tooltip.web.tsx +175 -0
- package/src/tooltip/types.ts +61 -0
- package/src/types/index.ts +141 -0
- package/src/utils/index.ts +69 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type ImageErrorEventData, type ImageLoadEventData, type ImageSourcePropType, type NativeSyntheticEvent, Image as RNImage, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { useIsomorphicLayoutEffect } from "../hooks";
|
|
5
|
+
import * as Slot from "../slot";
|
|
6
|
+
|
|
7
|
+
import type { FallbackProps, FallbackRef, ImageProps, ImageRef, RootProps, RootRef } from "./types";
|
|
8
|
+
|
|
9
|
+
type AvatarState = "loading" | "error" | "loaded";
|
|
10
|
+
|
|
11
|
+
interface RootContextInterface extends RootProps {
|
|
12
|
+
status: AvatarState;
|
|
13
|
+
setStatus: (status: AvatarState) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const RootContext = React.createContext<RootContextInterface | null>(null);
|
|
17
|
+
|
|
18
|
+
const Root = React.forwardRef<RootRef, RootProps>(({ alt, ...viewProps }, ref) => {
|
|
19
|
+
const [status, setStatus] = React.useState<AvatarState>("error");
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<RootContext.Provider value={{ alt, status, setStatus }}>
|
|
23
|
+
<Component
|
|
24
|
+
ref={ref}
|
|
25
|
+
{...viewProps}
|
|
26
|
+
/>
|
|
27
|
+
</RootContext.Provider>
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
Root.displayName = "RootAvatar";
|
|
32
|
+
|
|
33
|
+
function useRootContext() {
|
|
34
|
+
const context = React.useContext(RootContext);
|
|
35
|
+
if (!context) {
|
|
36
|
+
throw new Error("Avatar compound components cannot be rendered outside the Avatar component");
|
|
37
|
+
}
|
|
38
|
+
return context;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const Image = React.forwardRef<ImageRef, ImageProps>(({ onLoad: onLoadProps, onError: onErrorProps, onLoadingStatusChange, ...props }, ref) => {
|
|
42
|
+
const { alt, setStatus, status } = useRootContext();
|
|
43
|
+
|
|
44
|
+
useIsomorphicLayoutEffect(() => {
|
|
45
|
+
if (isValidSource(props?.source)) {
|
|
46
|
+
setStatus("loading");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
setStatus("error");
|
|
51
|
+
};
|
|
52
|
+
}, [props?.source]);
|
|
53
|
+
|
|
54
|
+
const onLoad = React.useCallback(
|
|
55
|
+
(e: NativeSyntheticEvent<ImageLoadEventData>) => {
|
|
56
|
+
setStatus("loaded");
|
|
57
|
+
onLoadingStatusChange?.("loaded");
|
|
58
|
+
onLoadProps?.(e);
|
|
59
|
+
},
|
|
60
|
+
[onLoadProps],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const onError = React.useCallback(
|
|
64
|
+
(e: NativeSyntheticEvent<ImageErrorEventData>) => {
|
|
65
|
+
setStatus("error");
|
|
66
|
+
onLoadingStatusChange?.("error");
|
|
67
|
+
onErrorProps?.(e);
|
|
68
|
+
},
|
|
69
|
+
[onErrorProps],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (status === "error") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Component
|
|
78
|
+
ref={ref}
|
|
79
|
+
alt={alt}
|
|
80
|
+
onLoad={onLoad}
|
|
81
|
+
onError={onError}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
Image.displayName = "ImageAvatar";
|
|
88
|
+
|
|
89
|
+
const Fallback = React.forwardRef<FallbackRef, FallbackProps>(({ ...props }, ref) => {
|
|
90
|
+
const { alt, status } = useRootContext();
|
|
91
|
+
|
|
92
|
+
if (status !== "error") {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Component
|
|
98
|
+
ref={ref}
|
|
99
|
+
role={"img"}
|
|
100
|
+
aria-label={alt}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
Fallback.displayName = "FallbackAvatar";
|
|
107
|
+
|
|
108
|
+
export { Fallback, Image, Root };
|
|
109
|
+
|
|
110
|
+
function isValidSource(source?: ImageSourcePropType) {
|
|
111
|
+
if (!source) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// Using require() for the source returns a number
|
|
115
|
+
if (typeof source === "number") {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (Array.isArray(source)) {
|
|
119
|
+
return source.some((source) => !!source.uri);
|
|
120
|
+
}
|
|
121
|
+
return !!source.uri;
|
|
122
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Image } from "react-native";
|
|
2
|
+
|
|
3
|
+
import type { ComponentPropsWithRender, RenderViewProps, ViewRef } from "../types";
|
|
4
|
+
|
|
5
|
+
type RootProps = RenderViewProps & {
|
|
6
|
+
alt: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type ImageProps = Omit<ComponentPropsWithRender<typeof Image>, "alt"> & {
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
onLoadingStatusChange?: (status: "error" | "loaded") => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type FallbackProps = RenderViewProps;
|
|
15
|
+
|
|
16
|
+
type RootRef = ViewRef;
|
|
17
|
+
type ImageRef = React.ElementRef<typeof Image>;
|
|
18
|
+
type FallbackRef = ViewRef;
|
|
19
|
+
|
|
20
|
+
export type { FallbackProps, FallbackRef, ImageProps, ImageRef, RootProps, RootRef };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { GestureResponderEvent, Pressable, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import * as Slot from "../slot";
|
|
5
|
+
import type { PressableRef, RenderPressableProps } from "../types";
|
|
6
|
+
|
|
7
|
+
import type { IndicatorProps, IndicatorRef, RootProps, RootRef } from "./types";
|
|
8
|
+
|
|
9
|
+
interface RootContext extends RootProps {
|
|
10
|
+
nativeID?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CheckboxContext = React.createContext<RootContext | null>(null);
|
|
14
|
+
|
|
15
|
+
const Root = React.forwardRef<RootRef, RootProps>(({ disabled = false, checked, onCheckedChange, nativeID, ...props }, ref) => {
|
|
16
|
+
return (
|
|
17
|
+
<CheckboxContext.Provider
|
|
18
|
+
value={{
|
|
19
|
+
disabled,
|
|
20
|
+
checked,
|
|
21
|
+
onCheckedChange,
|
|
22
|
+
nativeID,
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Trigger
|
|
26
|
+
ref={ref}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
</CheckboxContext.Provider>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
Root.displayName = "RootNativeCheckbox";
|
|
34
|
+
|
|
35
|
+
function useCheckboxContext() {
|
|
36
|
+
const context = React.useContext(CheckboxContext);
|
|
37
|
+
if (!context) {
|
|
38
|
+
throw new Error("Checkbox compound components cannot be rendered outside the Checkbox component");
|
|
39
|
+
}
|
|
40
|
+
return context;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const Trigger = React.forwardRef<PressableRef, RenderPressableProps>(({ onPress: onPressProp, ...props }, ref) => {
|
|
44
|
+
const { disabled, checked, onCheckedChange, nativeID } = useCheckboxContext();
|
|
45
|
+
|
|
46
|
+
function onPress(ev: GestureResponderEvent) {
|
|
47
|
+
if (disabled) return;
|
|
48
|
+
const newValue = !checked;
|
|
49
|
+
onCheckedChange(newValue);
|
|
50
|
+
onPressProp?.(ev);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Component
|
|
55
|
+
ref={ref}
|
|
56
|
+
nativeID={nativeID}
|
|
57
|
+
aria-disabled={disabled}
|
|
58
|
+
role="checkbox"
|
|
59
|
+
aria-checked={checked}
|
|
60
|
+
onPress={onPress}
|
|
61
|
+
accessibilityState={{
|
|
62
|
+
checked,
|
|
63
|
+
disabled,
|
|
64
|
+
}}
|
|
65
|
+
disabled={disabled}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
Trigger.displayName = "TriggerNativeCheckbox";
|
|
72
|
+
|
|
73
|
+
const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>(({ keepMounted, ...props }, ref) => {
|
|
74
|
+
const { checked, disabled } = useCheckboxContext();
|
|
75
|
+
|
|
76
|
+
if (!keepMounted) {
|
|
77
|
+
if (!checked) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Component
|
|
84
|
+
ref={ref}
|
|
85
|
+
aria-disabled={disabled}
|
|
86
|
+
aria-hidden={!(keepMounted || checked)}
|
|
87
|
+
role={"presentation"}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
Indicator.displayName = "IndicatorNativeCheckbox";
|
|
94
|
+
|
|
95
|
+
export { Indicator, Root };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// import * as Checkbox from "@radix-ui/react-checkbox";
|
|
2
|
+
// import * as React from "react";
|
|
3
|
+
// import { GestureResponderEvent, Pressable, View } from "react-native";
|
|
4
|
+
|
|
5
|
+
// import { useAugmentedRef, useIsomorphicLayoutEffect } from "../hooks";
|
|
6
|
+
// import * as Slot from "../slot";
|
|
7
|
+
|
|
8
|
+
// import type { IndicatorProps, IndicatorRef, RootProps, RootRef } from "./types";
|
|
9
|
+
|
|
10
|
+
// const CheckboxContext = React.createContext<RootProps | null>(null);
|
|
11
|
+
|
|
12
|
+
// const Root = React.forwardRef<RootRef, RootProps>(({ disabled, checked, onCheckedChange, onPress: onPressProp, role: _role, ...props }, ref) => {
|
|
13
|
+
// const augmentedRef = useAugmentedRef({ ref });
|
|
14
|
+
|
|
15
|
+
// function onPress(ev: GestureResponderEvent) {
|
|
16
|
+
// onPressProp?.(ev);
|
|
17
|
+
// onCheckedChange(!checked);
|
|
18
|
+
// }
|
|
19
|
+
|
|
20
|
+
// useIsomorphicLayoutEffect(() => {
|
|
21
|
+
// if (augmentedRef.current) {
|
|
22
|
+
// const augRef = augmentedRef.current as unknown as HTMLButtonElement;
|
|
23
|
+
// augRef.dataset.state = checked ? "checked" : "unchecked";
|
|
24
|
+
// augRef.value = checked ? "on" : "off";
|
|
25
|
+
// }
|
|
26
|
+
// }, [checked]);
|
|
27
|
+
|
|
28
|
+
// useIsomorphicLayoutEffect(() => {
|
|
29
|
+
// if (augmentedRef.current) {
|
|
30
|
+
// const augRef = augmentedRef.current as unknown as HTMLButtonElement;
|
|
31
|
+
// augRef.type = "button";
|
|
32
|
+
// augRef.role = "checkbox";
|
|
33
|
+
|
|
34
|
+
// if (disabled) {
|
|
35
|
+
// augRef.dataset.disabled = "true";
|
|
36
|
+
// } else {
|
|
37
|
+
// augRef.dataset.disabled = undefined;
|
|
38
|
+
// }
|
|
39
|
+
// }
|
|
40
|
+
// }, [disabled]);
|
|
41
|
+
|
|
42
|
+
//
|
|
43
|
+
// return (
|
|
44
|
+
// <CheckboxContext.Provider value={{ checked, disabled, onCheckedChange }}>
|
|
45
|
+
// <Checkbox.Root
|
|
46
|
+
// checked={checked}
|
|
47
|
+
// onCheckedChange={onCheckedChange}
|
|
48
|
+
// disabled={disabled}
|
|
49
|
+
// render
|
|
50
|
+
// >
|
|
51
|
+
// <Component
|
|
52
|
+
// ref={augmentedRef}
|
|
53
|
+
// role="button"
|
|
54
|
+
// onPress={onPress}
|
|
55
|
+
// disabled={disabled}
|
|
56
|
+
// {...props}
|
|
57
|
+
// />
|
|
58
|
+
// </Checkbox.Root>
|
|
59
|
+
// </CheckboxContext.Provider>
|
|
60
|
+
// );
|
|
61
|
+
// });
|
|
62
|
+
|
|
63
|
+
// Root.displayName = "RootWebCheckbox";
|
|
64
|
+
|
|
65
|
+
// function useCheckboxContext() {
|
|
66
|
+
// const context = React.useContext(CheckboxContext);
|
|
67
|
+
// if (context === null) {
|
|
68
|
+
// throw new Error("Checkbox compound components cannot be rendered outside the Checkbox component");
|
|
69
|
+
// }
|
|
70
|
+
// return context;
|
|
71
|
+
// }
|
|
72
|
+
|
|
73
|
+
// const Indicator = React.forwardRef<IndicatorRef, IndicatorProps>(({ keepMounted, ...props }, ref) => {
|
|
74
|
+
// const { checked, disabled } = useCheckboxContext();
|
|
75
|
+
// const augmentedRef = useAugmentedRef({ ref });
|
|
76
|
+
|
|
77
|
+
// useIsomorphicLayoutEffect(() => {
|
|
78
|
+
// if (augmentedRef.current) {
|
|
79
|
+
// const augRef = augmentedRef.current as unknown as HTMLDivElement;
|
|
80
|
+
// augRef.dataset.state = checked ? "checked" : "unchecked";
|
|
81
|
+
// }
|
|
82
|
+
// }, [checked]);
|
|
83
|
+
|
|
84
|
+
// useIsomorphicLayoutEffect(() => {
|
|
85
|
+
// if (augmentedRef.current) {
|
|
86
|
+
// const augRef = augmentedRef.current as unknown as HTMLDivElement;
|
|
87
|
+
// if (disabled) {
|
|
88
|
+
// augRef.dataset.disabled = "true";
|
|
89
|
+
// } else {
|
|
90
|
+
// augRef.dataset.disabled = undefined;
|
|
91
|
+
// }
|
|
92
|
+
// }
|
|
93
|
+
// }, [disabled]);
|
|
94
|
+
|
|
95
|
+
//
|
|
96
|
+
// return (
|
|
97
|
+
// <Checkbox.Indicator
|
|
98
|
+
// keepMounted={keepMounted}
|
|
99
|
+
// render
|
|
100
|
+
// >
|
|
101
|
+
// <Component
|
|
102
|
+
// ref={ref}
|
|
103
|
+
// {...props}
|
|
104
|
+
// />
|
|
105
|
+
// </Checkbox.Indicator>
|
|
106
|
+
// );
|
|
107
|
+
// });
|
|
108
|
+
|
|
109
|
+
// Indicator.displayName = "IndicatorWebCheckbox";
|
|
110
|
+
|
|
111
|
+
// export { Indicator, Root };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { KeepMountable, PressableRef, RenderPressableProps, RenderViewProps, ViewRef } from "../types";
|
|
2
|
+
|
|
3
|
+
type RootProps = RenderPressableProps & {
|
|
4
|
+
checked: boolean;
|
|
5
|
+
onCheckedChange: (checked: boolean) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type IndicatorProps = KeepMountable & RenderViewProps;
|
|
10
|
+
|
|
11
|
+
type RootRef = PressableRef;
|
|
12
|
+
type IndicatorRef = ViewRef;
|
|
13
|
+
|
|
14
|
+
export type { IndicatorProps, IndicatorRef, RootProps, RootRef };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Pressable, View, type GestureResponderEvent } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { useControllableState } from "../hooks";
|
|
5
|
+
import * as Slot from "../slot";
|
|
6
|
+
|
|
7
|
+
import type { ContentProps, ContentRef, RootContext, RootProps, RootRef, TriggerProps, TriggerRef } from "./types";
|
|
8
|
+
|
|
9
|
+
const CollapsibleContext = React.createContext<(RootContext & { nativeID: string }) | null>(null);
|
|
10
|
+
|
|
11
|
+
const Root = React.forwardRef<RootRef, RootProps>(
|
|
12
|
+
({ render, disabled = false, open: openProp, defaultOpen, onOpenChange: onOpenChangeProp, ...viewProps }, ref) => {
|
|
13
|
+
const nativeID = React.useId();
|
|
14
|
+
const [open = false, onOpenChange] = useControllableState({
|
|
15
|
+
prop: openProp,
|
|
16
|
+
defaultProp: defaultOpen,
|
|
17
|
+
onChange: onOpenChangeProp,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<CollapsibleContext.Provider
|
|
22
|
+
value={{
|
|
23
|
+
disabled,
|
|
24
|
+
open,
|
|
25
|
+
onOpenChange,
|
|
26
|
+
nativeID,
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<Component
|
|
30
|
+
ref={ref}
|
|
31
|
+
{...viewProps}
|
|
32
|
+
/>
|
|
33
|
+
</CollapsibleContext.Provider>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
Root.displayName = "RootNativeCollapsible";
|
|
39
|
+
|
|
40
|
+
function useCollapsibleContext() {
|
|
41
|
+
const context = React.useContext(CollapsibleContext);
|
|
42
|
+
if (!context) {
|
|
43
|
+
throw new Error("Collapsible compound components cannot be rendered outside the Collapsible component");
|
|
44
|
+
}
|
|
45
|
+
return context;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const Trigger = React.forwardRef<TriggerRef, TriggerProps>(({ onPress: onPressProp, disabled: disabledProp = false, ...props }, ref) => {
|
|
49
|
+
const { disabled, open, onOpenChange, nativeID } = useCollapsibleContext();
|
|
50
|
+
|
|
51
|
+
function onPress(ev: GestureResponderEvent) {
|
|
52
|
+
if (disabled || disabledProp) return;
|
|
53
|
+
onOpenChange(!open);
|
|
54
|
+
onPressProp?.(ev);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Component
|
|
59
|
+
ref={ref}
|
|
60
|
+
nativeID={nativeID}
|
|
61
|
+
aria-disabled={(disabled || disabledProp) ?? undefined}
|
|
62
|
+
role="button"
|
|
63
|
+
onPress={onPress}
|
|
64
|
+
accessibilityState={{
|
|
65
|
+
expanded: open,
|
|
66
|
+
disabled: (disabled || disabledProp) ?? undefined,
|
|
67
|
+
}}
|
|
68
|
+
disabled={disabled || disabledProp}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
Trigger.displayName = "TriggerNativeCollapsible";
|
|
75
|
+
|
|
76
|
+
const Content = React.forwardRef<ContentRef, ContentProps>(({ keepMounted, ...props }, ref) => {
|
|
77
|
+
const { nativeID, open } = useCollapsibleContext();
|
|
78
|
+
|
|
79
|
+
if (!keepMounted) {
|
|
80
|
+
if (!open) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Component
|
|
87
|
+
ref={ref}
|
|
88
|
+
aria-hidden={!(keepMounted || open)}
|
|
89
|
+
aria-labelledby={nativeID}
|
|
90
|
+
role={"region"}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
Content.displayName = "ContentNativeCollapsible";
|
|
97
|
+
|
|
98
|
+
export { Content, Root, Trigger };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// import * as Collapsible from "@radix-ui/react-collapsible";
|
|
2
|
+
// import * as React from "react";
|
|
3
|
+
// import { Pressable, View, type GestureResponderEvent } from "react-native";
|
|
4
|
+
|
|
5
|
+
// import { useAugmentedRef, useControllableState, useIsomorphicLayoutEffect } from "../hooks";
|
|
6
|
+
// import * as Slot from "../slot";
|
|
7
|
+
|
|
8
|
+
// import type { ContentProps, ContentRef, RootContext, RootProps, RootRef, TriggerProps, TriggerRef } from "./types";
|
|
9
|
+
|
|
10
|
+
// const CollapsibleContext = React.createContext<RootContext | null>(null);
|
|
11
|
+
|
|
12
|
+
// const Root = React.forwardRef<RootRef, RootProps>(
|
|
13
|
+
// ({ render, disabled = false, open: openProp, defaultOpen, onOpenChange: onOpenChangeProp, ...viewProps }, ref) => {
|
|
14
|
+
// const [open = false, onOpenChange] = useControllableState({
|
|
15
|
+
// prop: openProp,
|
|
16
|
+
// defaultProp: defaultOpen,
|
|
17
|
+
// onChange: onOpenChangeProp,
|
|
18
|
+
// });
|
|
19
|
+
// const augmentedRef = useAugmentedRef({ ref });
|
|
20
|
+
|
|
21
|
+
// useIsomorphicLayoutEffect(() => {
|
|
22
|
+
// if (augmentedRef.current) {
|
|
23
|
+
// const augRef = augmentedRef.current as unknown as HTMLDivElement;
|
|
24
|
+
// augRef.dataset.state = open ? "open" : "closed";
|
|
25
|
+
// }
|
|
26
|
+
// }, [open]);
|
|
27
|
+
|
|
28
|
+
// useIsomorphicLayoutEffect(() => {
|
|
29
|
+
// if (augmentedRef.current) {
|
|
30
|
+
// const augRef = augmentedRef.current as unknown as HTMLDivElement;
|
|
31
|
+
// if (disabled) {
|
|
32
|
+
// augRef.dataset.disabled = "true";
|
|
33
|
+
// } else {
|
|
34
|
+
// augRef.dataset.disabled = undefined;
|
|
35
|
+
// }
|
|
36
|
+
// }
|
|
37
|
+
// }, [disabled]);
|
|
38
|
+
|
|
39
|
+
//
|
|
40
|
+
// return (
|
|
41
|
+
// <CollapsibleContext.Provider
|
|
42
|
+
// value={{
|
|
43
|
+
// disabled,
|
|
44
|
+
// open,
|
|
45
|
+
// onOpenChange,
|
|
46
|
+
// }}
|
|
47
|
+
// >
|
|
48
|
+
// <Collapsible.Root
|
|
49
|
+
// open={open}
|
|
50
|
+
// defaultOpen={defaultOpen}
|
|
51
|
+
// onOpenChange={onOpenChange}
|
|
52
|
+
// disabled={disabled}
|
|
53
|
+
// >
|
|
54
|
+
// <Component
|
|
55
|
+
// ref={ref}
|
|
56
|
+
// {...viewProps}
|
|
57
|
+
// />
|
|
58
|
+
// </Collapsible.Root>
|
|
59
|
+
// </CollapsibleContext.Provider>
|
|
60
|
+
// );
|
|
61
|
+
// },
|
|
62
|
+
// );
|
|
63
|
+
|
|
64
|
+
// Root.displayName = "RootWebCollapsible";
|
|
65
|
+
|
|
66
|
+
// function useCollapsibleContext() {
|
|
67
|
+
// const context = React.useContext(CollapsibleContext);
|
|
68
|
+
// if (!context) {
|
|
69
|
+
// throw new Error("Collapsible compound components cannot be rendered outside the Collapsible component");
|
|
70
|
+
// }
|
|
71
|
+
// return context;
|
|
72
|
+
// }
|
|
73
|
+
|
|
74
|
+
// const Trigger = React.forwardRef<TriggerRef, TriggerProps>(({ onPress: onPressProp, disabled: disabledProp = false, ...props }, ref) => {
|
|
75
|
+
// const { disabled, open, onOpenChange } = useCollapsibleContext();
|
|
76
|
+
// const augmentedRef = useAugmentedRef({ ref });
|
|
77
|
+
|
|
78
|
+
// useIsomorphicLayoutEffect(() => {
|
|
79
|
+
// if (augmentedRef.current) {
|
|
80
|
+
// const augRef = augmentedRef.current as unknown as HTMLButtonElement;
|
|
81
|
+
// augRef.dataset.state = open ? "open" : "closed";
|
|
82
|
+
// }
|
|
83
|
+
// }, [open]);
|
|
84
|
+
|
|
85
|
+
// useIsomorphicLayoutEffect(() => {
|
|
86
|
+
// if (augmentedRef.current) {
|
|
87
|
+
// const augRef = augmentedRef.current as unknown as HTMLButtonElement;
|
|
88
|
+
// augRef.type = "button";
|
|
89
|
+
|
|
90
|
+
// if (disabled) {
|
|
91
|
+
// augRef.dataset.disabled = "true";
|
|
92
|
+
// } else {
|
|
93
|
+
// augRef.dataset.disabled = undefined;
|
|
94
|
+
// }
|
|
95
|
+
// }
|
|
96
|
+
// }, [disabled]);
|
|
97
|
+
|
|
98
|
+
// function onPress(ev: GestureResponderEvent) {
|
|
99
|
+
// onPressProp?.(ev);
|
|
100
|
+
// onOpenChange(!open);
|
|
101
|
+
// }
|
|
102
|
+
|
|
103
|
+
//
|
|
104
|
+
// return (
|
|
105
|
+
// <Collapsible.Trigger
|
|
106
|
+
// disabled={disabled}
|
|
107
|
+
// render
|
|
108
|
+
// >
|
|
109
|
+
// <Component
|
|
110
|
+
// ref={augmentedRef}
|
|
111
|
+
// role="button"
|
|
112
|
+
// onPress={onPress}
|
|
113
|
+
// disabled={disabled}
|
|
114
|
+
// {...props}
|
|
115
|
+
// />
|
|
116
|
+
// </Collapsible.Trigger>
|
|
117
|
+
// );
|
|
118
|
+
// });
|
|
119
|
+
|
|
120
|
+
// Trigger.displayName = "TriggerWebCollapsible";
|
|
121
|
+
|
|
122
|
+
// const Content = React.forwardRef<ContentRef, ContentProps>(({ keepMounted, ...props }, ref) => {
|
|
123
|
+
// const augmentedRef = useAugmentedRef({ ref });
|
|
124
|
+
// const { open } = useCollapsibleContext();
|
|
125
|
+
|
|
126
|
+
// useIsomorphicLayoutEffect(() => {
|
|
127
|
+
// if (augmentedRef.current) {
|
|
128
|
+
// const augRef = augmentedRef.current as unknown as HTMLDivElement;
|
|
129
|
+
// augRef.dataset.state = open ? "open" : "closed";
|
|
130
|
+
// }
|
|
131
|
+
// }, [open]);
|
|
132
|
+
|
|
133
|
+
//
|
|
134
|
+
// return (
|
|
135
|
+
// <Collapsible.Content
|
|
136
|
+
// keepMounted={keepMounted}
|
|
137
|
+
// render
|
|
138
|
+
// >
|
|
139
|
+
// <Component
|
|
140
|
+
// ref={augmentedRef}
|
|
141
|
+
// {...props}
|
|
142
|
+
// />
|
|
143
|
+
// </Collapsible.Content>
|
|
144
|
+
// );
|
|
145
|
+
// });
|
|
146
|
+
|
|
147
|
+
// Content.displayName = "ContentWebCollapsible";
|
|
148
|
+
|
|
149
|
+
// export { Content, Root, Trigger };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { KeepMountable, PressableRef, RenderPressableProps, RenderViewProps, ViewRef } from "../types";
|
|
2
|
+
|
|
3
|
+
interface RootContext {
|
|
4
|
+
open: boolean;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type RootProps = RenderViewProps & {
|
|
10
|
+
open?: boolean;
|
|
11
|
+
defaultOpen?: boolean;
|
|
12
|
+
onOpenChange?: (open: boolean) => void;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type TriggerProps = RenderPressableProps;
|
|
17
|
+
type ContentProps = KeepMountable & RenderViewProps;
|
|
18
|
+
|
|
19
|
+
type RootRef = ViewRef;
|
|
20
|
+
type TriggerRef = PressableRef;
|
|
21
|
+
type ContentRef = ViewRef;
|
|
22
|
+
|
|
23
|
+
export type { ContentProps, ContentRef, RootContext, RootProps, RootRef, TriggerProps, TriggerRef };
|