@korsolutions/ui 0.0.38 → 0.0.39
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/dist/module/components/autocomplete/autocomplete.js +31 -0
- package/dist/module/components/autocomplete/autocomplete.js.map +1 -0
- package/dist/module/components/autocomplete/variants/default.js +88 -0
- package/dist/module/components/autocomplete/variants/default.js.map +1 -0
- package/dist/module/components/autocomplete/variants/index.js +7 -0
- package/dist/module/components/autocomplete/variants/index.js.map +1 -0
- package/dist/module/components/index.js +1 -0
- package/dist/module/components/index.js.map +1 -1
- package/dist/module/components/input/numeric-input.js +7 -6
- package/dist/module/components/input/numeric-input.js.map +1 -1
- package/dist/module/primitives/alert-dialog/alert-dialog-trigger.js.map +1 -1
- package/dist/module/primitives/autocomplete/autocomplete-content.js +33 -0
- package/dist/module/primitives/autocomplete/autocomplete-content.js.map +1 -0
- package/dist/module/primitives/autocomplete/autocomplete-empty.js +17 -0
- package/dist/module/primitives/autocomplete/autocomplete-empty.js.map +1 -0
- package/dist/module/primitives/autocomplete/autocomplete-input.js +73 -0
- package/dist/module/primitives/autocomplete/autocomplete-input.js.map +1 -0
- package/dist/module/primitives/autocomplete/autocomplete-option.js +54 -0
- package/dist/module/primitives/autocomplete/autocomplete-option.js.map +1 -0
- package/dist/module/primitives/autocomplete/autocomplete-overlay.js +20 -0
- package/dist/module/primitives/autocomplete/autocomplete-overlay.js.map +1 -0
- package/dist/module/primitives/autocomplete/autocomplete-portal.js +25 -0
- package/dist/module/primitives/autocomplete/autocomplete-portal.js.map +1 -0
- package/dist/module/primitives/autocomplete/autocomplete-root.js +69 -0
- package/dist/module/primitives/autocomplete/autocomplete-root.js.map +1 -0
- package/dist/module/primitives/autocomplete/context.js +12 -0
- package/dist/module/primitives/autocomplete/context.js.map +1 -0
- package/dist/module/primitives/autocomplete/index.js +20 -0
- package/dist/module/primitives/autocomplete/index.js.map +1 -0
- package/dist/module/primitives/autocomplete/types.js +4 -0
- package/dist/module/primitives/autocomplete/types.js.map +1 -0
- package/dist/module/primitives/dropdown-menu/dropdown-menu-divider.js +2 -2
- package/dist/module/primitives/dropdown-menu/dropdown-menu-divider.js.map +1 -1
- package/dist/module/primitives/dropdown-menu/dropdown-menu-trigger.js +3 -7
- package/dist/module/primitives/dropdown-menu/dropdown-menu-trigger.js.map +1 -1
- package/dist/module/primitives/index.js +1 -0
- package/dist/module/primitives/index.js.map +1 -1
- package/dist/module/primitives/input/input.js +3 -4
- package/dist/module/primitives/input/input.js.map +1 -1
- package/dist/module/primitives/popover/popover-trigger.js +3 -7
- package/dist/module/primitives/popover/popover-trigger.js.map +1 -1
- package/dist/module/primitives/portal/portal.js +4 -35
- package/dist/module/primitives/portal/portal.js.map +1 -1
- package/dist/module/primitives/select/context.js.map +1 -1
- package/dist/module/primitives/select/select-content.js +16 -5
- package/dist/module/primitives/select/select-content.js.map +1 -1
- package/dist/module/primitives/select/select-root.js +7 -3
- package/dist/module/primitives/select/select-root.js.map +1 -1
- package/dist/module/primitives/select/select-trigger.js +16 -11
- package/dist/module/primitives/select/select-trigger.js.map +1 -1
- package/dist/module/utils/normalize-layout.js +17 -0
- package/dist/module/utils/normalize-layout.js.map +1 -1
- package/dist/typescript/src/components/autocomplete/autocomplete.d.ts +11 -0
- package/dist/typescript/src/components/autocomplete/autocomplete.d.ts.map +1 -0
- package/dist/typescript/src/components/autocomplete/variants/default.d.ts +3 -0
- package/dist/typescript/src/components/autocomplete/variants/default.d.ts.map +1 -0
- package/dist/typescript/src/components/autocomplete/variants/index.d.ts +5 -0
- package/dist/typescript/src/components/autocomplete/variants/index.d.ts.map +1 -0
- package/dist/typescript/src/components/index.d.ts +1 -0
- package/dist/typescript/src/components/index.d.ts.map +1 -1
- package/dist/typescript/src/components/input/numeric-input.d.ts +2 -1
- package/dist/typescript/src/components/input/numeric-input.d.ts.map +1 -1
- package/dist/typescript/src/primitives/alert-dialog/alert-dialog-trigger.d.ts +1 -147
- package/dist/typescript/src/primitives/alert-dialog/alert-dialog-trigger.d.ts.map +1 -1
- package/dist/typescript/src/primitives/autocomplete/autocomplete-content.d.ts +8 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-content.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-empty.d.ts +9 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-empty.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-input.d.ts +7 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-input.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-option.d.ts +11 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-option.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-overlay.d.ts +10 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-overlay.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-portal.d.ts +6 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-portal.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-root.d.ts +24 -0
- package/dist/typescript/src/primitives/autocomplete/autocomplete-root.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/context.d.ts +30 -0
- package/dist/typescript/src/primitives/autocomplete/context.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/index.d.ts +25 -0
- package/dist/typescript/src/primitives/autocomplete/index.d.ts.map +1 -0
- package/dist/typescript/src/primitives/autocomplete/types.d.ts +21 -0
- package/dist/typescript/src/primitives/autocomplete/types.d.ts.map +1 -0
- package/dist/typescript/src/primitives/dropdown-menu/dropdown-menu-divider.d.ts.map +1 -1
- package/dist/typescript/src/primitives/dropdown-menu/dropdown-menu-trigger.d.ts.map +1 -1
- package/dist/typescript/src/primitives/index.d.ts +1 -0
- package/dist/typescript/src/primitives/index.d.ts.map +1 -1
- package/dist/typescript/src/primitives/input/input.d.ts +1 -2
- package/dist/typescript/src/primitives/input/input.d.ts.map +1 -1
- package/dist/typescript/src/primitives/popover/popover-trigger.d.ts.map +1 -1
- package/dist/typescript/src/primitives/portal/portal.constants.d.ts +6 -2
- package/dist/typescript/src/primitives/portal/portal.constants.d.ts.map +1 -1
- package/dist/typescript/src/primitives/portal/portal.d.ts +2 -5
- package/dist/typescript/src/primitives/portal/portal.d.ts.map +1 -1
- package/dist/typescript/src/primitives/select/context.d.ts +5 -2
- package/dist/typescript/src/primitives/select/context.d.ts.map +1 -1
- package/dist/typescript/src/primitives/select/select-content.d.ts.map +1 -1
- package/dist/typescript/src/primitives/select/select-root.d.ts.map +1 -1
- package/dist/typescript/src/primitives/select/select-trigger.d.ts +0 -5
- package/dist/typescript/src/primitives/select/select-trigger.d.ts.map +1 -1
- package/dist/typescript/src/types/element.types.d.ts +10 -3
- package/dist/typescript/src/types/element.types.d.ts.map +1 -1
- package/dist/typescript/src/utils/normalize-layout.d.ts +3 -1
- package/dist/typescript/src/utils/normalize-layout.d.ts.map +1 -1
- package/package.json +8 -5
- package/scripts/build.sh +2 -2
- package/src/components/autocomplete/autocomplete.tsx +34 -0
- package/src/components/autocomplete/variants/default.tsx +84 -0
- package/src/components/autocomplete/variants/index.ts +5 -0
- package/src/components/index.ts +1 -0
- package/src/components/input/numeric-input.tsx +8 -6
- package/src/primitives/alert-dialog/alert-dialog-trigger.tsx +1 -1
- package/src/primitives/autocomplete/autocomplete-content.tsx +38 -0
- package/src/primitives/autocomplete/autocomplete-empty.tsx +19 -0
- package/src/primitives/autocomplete/autocomplete-input.tsx +85 -0
- package/src/primitives/autocomplete/autocomplete-option.tsx +66 -0
- package/src/primitives/autocomplete/autocomplete-overlay.tsx +29 -0
- package/src/primitives/autocomplete/autocomplete-portal.tsx +27 -0
- package/src/primitives/autocomplete/autocomplete-root.tsx +111 -0
- package/src/primitives/autocomplete/context.ts +45 -0
- package/src/primitives/autocomplete/index.ts +27 -0
- package/src/primitives/autocomplete/types.ts +23 -0
- package/src/primitives/dropdown-menu/dropdown-menu-divider.tsx +2 -2
- package/src/primitives/dropdown-menu/dropdown-menu-trigger.tsx +3 -7
- package/src/primitives/index.ts +1 -0
- package/src/primitives/input/input.tsx +3 -6
- package/src/primitives/popover/popover-trigger.tsx +3 -7
- package/src/primitives/portal/portal.constants.tsx +2 -2
- package/src/primitives/portal/portal.tsx +4 -36
- package/src/primitives/select/context.ts +5 -2
- package/src/primitives/select/select-content.tsx +16 -9
- package/src/primitives/select/select-root.tsx +7 -3
- package/src/primitives/select/select-trigger.tsx +19 -21
- package/src/types/element.types.ts +10 -3
- package/src/utils/normalize-layout.ts +22 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type { AutocompleteContentProps } from "./autocomplete-content";
|
|
2
|
+
export type { AutocompleteEmptyProps } from "./autocomplete-empty";
|
|
3
|
+
export type { AutocompleteInputProps } from "./autocomplete-input";
|
|
4
|
+
export type { AutocompleteOptionProps } from "./autocomplete-option";
|
|
5
|
+
export type { AutocompleteOverlayProps } from "./autocomplete-overlay";
|
|
6
|
+
export type { AutocompletePortalProps } from "./autocomplete-portal";
|
|
7
|
+
export type { AutocompleteRootBaseProps, AutocompleteRootProps } from "./autocomplete-root";
|
|
8
|
+
export { useAutocomplete } from "./context";
|
|
9
|
+
export type { AutocompleteOption, AutocompleteOptionState, AutocompleteState, AutocompleteStyles } from "./types";
|
|
10
|
+
|
|
11
|
+
import { AutocompleteContent } from "./autocomplete-content";
|
|
12
|
+
import { AutocompleteEmpty } from "./autocomplete-empty";
|
|
13
|
+
import { AutocompleteInput } from "./autocomplete-input";
|
|
14
|
+
import { AutocompleteOption } from "./autocomplete-option";
|
|
15
|
+
import { AutocompleteOverlay } from "./autocomplete-overlay";
|
|
16
|
+
import { AutocompletePortal } from "./autocomplete-portal";
|
|
17
|
+
import { AutocompleteRoot } from "./autocomplete-root";
|
|
18
|
+
|
|
19
|
+
export const AutocompletePrimitive = {
|
|
20
|
+
Root: AutocompleteRoot,
|
|
21
|
+
Input: AutocompleteInput,
|
|
22
|
+
Portal: AutocompletePortal,
|
|
23
|
+
Overlay: AutocompleteOverlay,
|
|
24
|
+
Content: AutocompleteContent,
|
|
25
|
+
Option: AutocompleteOption,
|
|
26
|
+
Empty: AutocompleteEmpty,
|
|
27
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AutocompleteContentProps } from "./autocomplete-content";
|
|
2
|
+
import type { AutocompleteEmptyProps } from "./autocomplete-empty";
|
|
3
|
+
import type { AutocompleteInputProps } from "./autocomplete-input";
|
|
4
|
+
import type { AutocompleteOptionProps } from "./autocomplete-option";
|
|
5
|
+
import type { AutocompleteOverlayProps } from "./autocomplete-overlay";
|
|
6
|
+
import type { AutocompleteRootProps } from "./autocomplete-root";
|
|
7
|
+
|
|
8
|
+
export type AutocompleteState = "default" | "focused" | "disabled";
|
|
9
|
+
export type AutocompleteOptionState = AutocompleteState | "hovered" | "selected";
|
|
10
|
+
|
|
11
|
+
export interface AutocompleteStyles {
|
|
12
|
+
root?: Partial<Record<AutocompleteState, AutocompleteRootProps["style"]>>;
|
|
13
|
+
input?: Partial<Record<AutocompleteState, AutocompleteInputProps["style"]>>;
|
|
14
|
+
overlay?: Partial<Record<AutocompleteState, AutocompleteOverlayProps["style"]>>;
|
|
15
|
+
content?: Partial<Record<AutocompleteState, AutocompleteContentProps["style"]>>;
|
|
16
|
+
option?: Partial<Record<AutocompleteOptionState, AutocompleteOptionProps["style"]>>;
|
|
17
|
+
empty?: Partial<Record<AutocompleteState, AutocompleteEmptyProps["style"]>>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AutocompleteOption {
|
|
21
|
+
value: string;
|
|
22
|
+
label: string;
|
|
23
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { type StyleProp, View, type ViewStyle } from "react-native";
|
|
2
|
+
import { type StyleProp, StyleSheet, View, type ViewStyle } from "react-native";
|
|
3
3
|
import { useDropdownMenu } from "./context";
|
|
4
4
|
|
|
5
5
|
export interface DropdownMenuDividerProps {
|
|
@@ -11,7 +11,7 @@ export interface DropdownMenuDividerProps {
|
|
|
11
11
|
export function DropdownMenuDivider(props: DropdownMenuDividerProps) {
|
|
12
12
|
const menu = useDropdownMenu();
|
|
13
13
|
|
|
14
|
-
const composedStyle = [menu.styles?.divider, props.style];
|
|
14
|
+
const composedStyle = StyleSheet.flatten([menu.styles?.divider, props.style]);
|
|
15
15
|
|
|
16
16
|
const Component = props.render ?? View;
|
|
17
17
|
return <Component {...props} style={composedStyle} />;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ViewRef } from "@/types/element.types";
|
|
2
|
+
import { measureLayoutPosition } from "@/utils/normalize-layout";
|
|
2
3
|
import React, { forwardRef, useImperativeHandle, useRef } from "react";
|
|
3
4
|
import { type PressableProps } from "react-native";
|
|
4
5
|
import { useDropdownMenu } from "./context";
|
|
@@ -18,13 +19,8 @@ export const DropdownMenuTrigger = forwardRef<DropdownMenuTriggerRef, DropdownMe
|
|
|
18
19
|
|
|
19
20
|
const onTriggerPress = async () => {
|
|
20
21
|
if (!dropdownMenu.isOpen) {
|
|
21
|
-
triggerRef.current
|
|
22
|
-
dropdownMenu.setTriggerPosition(
|
|
23
|
-
height,
|
|
24
|
-
width,
|
|
25
|
-
pageX,
|
|
26
|
-
pageY,
|
|
27
|
-
});
|
|
22
|
+
measureLayoutPosition(triggerRef.current, (layout) => {
|
|
23
|
+
dropdownMenu.setTriggerPosition(layout);
|
|
28
24
|
dropdownMenu.setIsOpen(true);
|
|
29
25
|
});
|
|
30
26
|
} else {
|
package/src/primitives/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TextInputRef } from "@/types/element.types";
|
|
2
2
|
import { forwardRef, useState } from "react";
|
|
3
|
-
import { TextInput, type TextInputProps } from "react-native";
|
|
3
|
+
import { StyleSheet, TextInput, type TextInputProps } from "react-native";
|
|
4
4
|
import { useFieldOptional } from "../field/context";
|
|
5
5
|
import type { InputState, InputStyles } from "./types";
|
|
6
6
|
|
|
@@ -11,8 +11,6 @@ export type InputPrimitiveBaseProps = Omit<TextInputProps, "onChange"> & {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export interface InputPrimitiveProps extends InputPrimitiveBaseProps {
|
|
14
|
-
render?: (props: InputPrimitiveProps) => React.ReactNode;
|
|
15
|
-
|
|
16
14
|
styles?: InputStyles;
|
|
17
15
|
}
|
|
18
16
|
|
|
@@ -31,16 +29,15 @@ export const InputPrimitive = forwardRef<TextInputRef, InputPrimitiveProps>((pro
|
|
|
31
29
|
const state = calculateState(props, isFocused);
|
|
32
30
|
const field = useFieldOptional();
|
|
33
31
|
|
|
34
|
-
const composedStyles = [props.styles?.default?.style, props.styles?.[state]?.style, props.style];
|
|
32
|
+
const composedStyles = StyleSheet.flatten([props.styles?.default?.style, props.styles?.[state]?.style, props.style]);
|
|
35
33
|
const composedProps = {
|
|
36
34
|
...props.styles?.default,
|
|
37
35
|
...props.styles?.[state],
|
|
38
36
|
...props,
|
|
39
37
|
};
|
|
40
|
-
const Component = props.render ?? TextInput;
|
|
41
38
|
|
|
42
39
|
return (
|
|
43
|
-
<
|
|
40
|
+
<TextInput
|
|
44
41
|
{...composedProps}
|
|
45
42
|
ref={ref}
|
|
46
43
|
id={field?.id}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ViewRef } from "@/types/element.types";
|
|
2
|
+
import { measureLayoutPosition } from "@/utils/normalize-layout";
|
|
2
3
|
import React, { forwardRef, useImperativeHandle, useRef } from "react";
|
|
3
4
|
import { type PressableProps } from "react-native";
|
|
4
5
|
import { usePopover } from "./context";
|
|
@@ -18,13 +19,8 @@ export const PopoverTrigger = forwardRef<PopoverTriggerRef, PopoverTriggerProps>
|
|
|
18
19
|
|
|
19
20
|
const onTriggerPress = async () => {
|
|
20
21
|
if (!popover.isOpen) {
|
|
21
|
-
triggerRef.current
|
|
22
|
-
popover.setTriggerPosition(
|
|
23
|
-
height,
|
|
24
|
-
width,
|
|
25
|
-
pageX,
|
|
26
|
-
pageY,
|
|
27
|
-
});
|
|
22
|
+
measureLayoutPosition(triggerRef.current, (layout) => {
|
|
23
|
+
popover.setTriggerPosition(layout);
|
|
28
24
|
popover.setIsOpen(true);
|
|
29
25
|
});
|
|
30
26
|
} else {
|
|
@@ -3,8 +3,8 @@ export const DEFAULT_PORTAL_HOST = "__KOR_PORTAL_HOST__";
|
|
|
3
3
|
export interface PortalHostProps {
|
|
4
4
|
name?: string;
|
|
5
5
|
container?: {
|
|
6
|
-
ios?: React.ComponentType<React.
|
|
7
|
-
android?: React.ComponentType<React.
|
|
6
|
+
ios?: React.ComponentType<{ children: React.ReactNode }>;
|
|
7
|
+
android?: React.ComponentType<{ children: React.ReactNode }>;
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, useSyncExternalStore } from "react";
|
|
2
2
|
import { Platform, View } from "react-native";
|
|
3
3
|
import { DEFAULT_PORTAL_HOST, type PortalHostProps, type PortalProps } from "./portal.constants";
|
|
4
4
|
|
|
@@ -48,7 +48,7 @@ function removePortal(hostName: string, name: string) {
|
|
|
48
48
|
emit();
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function
|
|
51
|
+
export function PortalHost({ name = DEFAULT_PORTAL_HOST, container }: PortalHostProps) {
|
|
52
52
|
const map = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
53
53
|
const portalMap = map.get(name) ?? new Map<string, React.ReactNode>();
|
|
54
54
|
if (portalMap.size === 0) return null;
|
|
@@ -69,23 +69,13 @@ function NativePortalHost({ name = DEFAULT_PORTAL_HOST, container }: PortalHostP
|
|
|
69
69
|
}}
|
|
70
70
|
/>
|
|
71
71
|
),
|
|
72
|
-
|
|
73
|
-
android: container?.android,
|
|
72
|
+
...container,
|
|
74
73
|
});
|
|
75
74
|
|
|
76
75
|
return <Container>{Array.from(portalMap.values())}</Container>;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
function
|
|
80
|
-
return <></>;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export const PortalHost = Platform.select({
|
|
84
|
-
default: NativePortalHost,
|
|
85
|
-
web: WebPortalHost,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
function NativePortal({ name, hostName = DEFAULT_PORTAL_HOST, children }: PortalProps) {
|
|
78
|
+
export function Portal({ name, hostName = DEFAULT_PORTAL_HOST, children }: PortalProps) {
|
|
89
79
|
useEffect(() => {
|
|
90
80
|
updatePortal(hostName, name, children);
|
|
91
81
|
}, [hostName, name, children]);
|
|
@@ -98,25 +88,3 @@ function NativePortal({ name, hostName = DEFAULT_PORTAL_HOST, children }: Portal
|
|
|
98
88
|
|
|
99
89
|
return <></>;
|
|
100
90
|
}
|
|
101
|
-
|
|
102
|
-
function WebPortal({ name, hostName = DEFAULT_PORTAL_HOST, children }: PortalProps) {
|
|
103
|
-
const [] = useState(() => {
|
|
104
|
-
let container = document.getElementById(hostName);
|
|
105
|
-
|
|
106
|
-
if (!container) {
|
|
107
|
-
container = document.createElement("div");
|
|
108
|
-
container.id = hostName;
|
|
109
|
-
document.body.appendChild(container);
|
|
110
|
-
}
|
|
111
|
-
return container;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const createPortal = require("react-dom").createPortal as typeof import("react-dom").createPortal;
|
|
115
|
-
|
|
116
|
-
return <>{createPortal(children, document.body, name)}</>;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export const Portal = Platform.select({
|
|
120
|
-
default: NativePortal,
|
|
121
|
-
web: WebPortal,
|
|
122
|
-
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LayoutPosition } from "@/hooks";
|
|
1
2
|
import { createContext, type Dispatch, useContext } from "react";
|
|
2
3
|
import type { LayoutRectangle } from "react-native";
|
|
3
4
|
import type { SelectOption, SelectState, SelectStyles } from "./types";
|
|
@@ -9,8 +10,10 @@ export interface SelectContext {
|
|
|
9
10
|
|
|
10
11
|
isOpen: boolean;
|
|
11
12
|
setIsOpen: Dispatch<React.SetStateAction<boolean>>;
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
triggerPosition: LayoutPosition;
|
|
14
|
+
setTriggerPosition: Dispatch<React.SetStateAction<LayoutPosition>>;
|
|
15
|
+
contentLayout: LayoutRectangle;
|
|
16
|
+
setContentLayout: Dispatch<React.SetStateAction<LayoutRectangle>>;
|
|
14
17
|
options: Array<SelectOption>;
|
|
15
18
|
setOptions: Dispatch<React.SetStateAction<Array<SelectOption>>>;
|
|
16
19
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useRelativePosition } from "@/hooks/use-relative-position";
|
|
1
2
|
import { calculateComposedStyles } from "@/utils/calculate-styles";
|
|
2
3
|
import React from "react";
|
|
3
4
|
import { type StyleProp, View, type ViewStyle } from "react-native";
|
|
@@ -15,18 +16,24 @@ export function SelectContent(props: SelectContentProps) {
|
|
|
15
16
|
const select = useSelect();
|
|
16
17
|
const composedStyles = calculateComposedStyles(select.styles, select.state, "content", props.style);
|
|
17
18
|
|
|
19
|
+
const positionStyle = useRelativePosition({
|
|
20
|
+
align: "start",
|
|
21
|
+
avoidCollisions: true,
|
|
22
|
+
triggerPosition: select.triggerPosition,
|
|
23
|
+
contentLayout: select.contentLayout,
|
|
24
|
+
alignOffset: 0,
|
|
25
|
+
side: "bottom",
|
|
26
|
+
sideOffset: 0,
|
|
27
|
+
});
|
|
28
|
+
|
|
18
29
|
const Component = props.render ?? View;
|
|
19
30
|
return (
|
|
20
31
|
<Component
|
|
21
|
-
style={[
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
left: select.triggerLayout?.x!,
|
|
27
|
-
width: select.triggerLayout?.width!,
|
|
28
|
-
},
|
|
29
|
-
]}
|
|
32
|
+
style={[positionStyle, composedStyles, { width: select.triggerPosition.width }]}
|
|
33
|
+
onLayout={(e) => {
|
|
34
|
+
select.setContentLayout(e.nativeEvent.layout);
|
|
35
|
+
}}
|
|
36
|
+
pointerEvents="box-none"
|
|
30
37
|
>
|
|
31
38
|
{props.children}
|
|
32
39
|
</Component>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_LAYOUT, DEFAULT_POSITION, type LayoutPosition } from "@/hooks";
|
|
1
2
|
import { calculateComposedStyles } from "@/utils/calculate-styles";
|
|
2
3
|
import React, { useState } from "react";
|
|
3
4
|
import { type LayoutRectangle, type StyleProp, View, type ViewStyle } from "react-native";
|
|
@@ -34,7 +35,8 @@ const calculateState = (props: SelectRootProps): SelectState => {
|
|
|
34
35
|
|
|
35
36
|
export function SelectRoot(props: SelectRootProps) {
|
|
36
37
|
const [isOpen, setIsOpen] = useState(false);
|
|
37
|
-
const [
|
|
38
|
+
const [contentLayout, setContentLayout] = useState<LayoutRectangle>(DEFAULT_LAYOUT);
|
|
39
|
+
const [triggerPosition, setTriggerPosition] = useState<LayoutPosition>(DEFAULT_POSITION);
|
|
38
40
|
const [options, setOptions] = useState<Array<SelectOption>>([]);
|
|
39
41
|
|
|
40
42
|
const state = calculateState(props);
|
|
@@ -49,8 +51,10 @@ export function SelectRoot(props: SelectRootProps) {
|
|
|
49
51
|
placeholder: props.placeholder,
|
|
50
52
|
isOpen,
|
|
51
53
|
setIsOpen,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
triggerPosition,
|
|
55
|
+
setTriggerPosition,
|
|
56
|
+
contentLayout,
|
|
57
|
+
setContentLayout,
|
|
54
58
|
options,
|
|
55
59
|
setOptions,
|
|
56
60
|
state,
|
|
@@ -1,38 +1,36 @@
|
|
|
1
|
+
import type { ViewRef } from "@/types/element.types";
|
|
1
2
|
import { calculateComposedStyles } from "@/utils/calculate-styles";
|
|
2
|
-
import {
|
|
3
|
-
import React from "react";
|
|
3
|
+
import { measureLayoutPosition } from "@/utils/normalize-layout";
|
|
4
|
+
import React, { useRef } from "react";
|
|
4
5
|
import { Pressable, type StyleProp, type ViewStyle } from "react-native";
|
|
5
6
|
import { useSelect } from "./context";
|
|
6
7
|
|
|
7
|
-
interface SelectTriggerInjectionProps {
|
|
8
|
-
onPress?: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
8
|
export interface SelectTriggerProps {
|
|
12
9
|
children?: React.ReactNode;
|
|
13
10
|
|
|
14
11
|
style?: StyleProp<ViewStyle>;
|
|
15
|
-
|
|
16
|
-
render?: (props: SelectTriggerInjectionProps) => React.ReactElement;
|
|
17
12
|
}
|
|
18
13
|
|
|
19
14
|
export function SelectTrigger(props: SelectTriggerProps) {
|
|
20
15
|
const select = useSelect();
|
|
16
|
+
const triggerRef = useRef<ViewRef>(null);
|
|
17
|
+
|
|
21
18
|
const composedStyles = calculateComposedStyles(select.styles, select.state, "trigger", props.style);
|
|
22
|
-
|
|
19
|
+
|
|
20
|
+
const onTriggerPress = () => {
|
|
21
|
+
if (!select.isOpen) {
|
|
22
|
+
measureLayoutPosition(triggerRef.current, (layout) => {
|
|
23
|
+
select.setTriggerPosition(layout);
|
|
24
|
+
select.setIsOpen(true);
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
select.setIsOpen(false);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
23
31
|
return (
|
|
24
|
-
<
|
|
25
|
-
onPress={() => {
|
|
26
|
-
select.setIsOpen((prev) => !prev);
|
|
27
|
-
}}
|
|
28
|
-
onLayout={(e) => {
|
|
29
|
-
const layout = normalizeLayout(e.nativeEvent.layout);
|
|
30
|
-
select.setTriggerLayout(layout);
|
|
31
|
-
}}
|
|
32
|
-
disabled={select.isDisabled}
|
|
33
|
-
style={composedStyles}
|
|
34
|
-
>
|
|
32
|
+
<Pressable ref={triggerRef} onPress={onTriggerPress} disabled={select.isDisabled} style={composedStyles}>
|
|
35
33
|
{props.children}
|
|
36
|
-
</
|
|
34
|
+
</Pressable>
|
|
37
35
|
);
|
|
38
36
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HostInstance } from "react-native";
|
|
2
2
|
|
|
3
|
-
export type ViewRef =
|
|
4
|
-
export type TextInputRef =
|
|
3
|
+
export type ViewRef = HostInstance;
|
|
4
|
+
export type TextInputRef = HostInstance & {
|
|
5
|
+
focus: () => void;
|
|
6
|
+
blur: () => void;
|
|
7
|
+
clear: () => void;
|
|
8
|
+
isFocused: () => boolean;
|
|
9
|
+
getNativeRef: () => HostInstance;
|
|
10
|
+
setSelection: (start: number, end?: number) => void;
|
|
11
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LayoutPosition } from "@/hooks";
|
|
2
|
+
import type { HostInstance, LayoutRectangle } from "react-native";
|
|
3
|
+
|
|
2
4
|
export const normalizeLayout = (layout: LayoutRectangle) => {
|
|
3
5
|
const _layout = { ...layout };
|
|
4
6
|
// Web layout doesn't provide x/y, but left/top
|
|
@@ -10,3 +12,22 @@ export const normalizeLayout = (layout: LayoutRectangle) => {
|
|
|
10
12
|
}
|
|
11
13
|
return _layout;
|
|
12
14
|
};
|
|
15
|
+
|
|
16
|
+
const isValidNumber = (value: unknown): value is number => {
|
|
17
|
+
const isValid = typeof value === "number" && !isNaN(value) && isFinite(value);
|
|
18
|
+
if (!isValid) {
|
|
19
|
+
console.warn(`Expected a valid number but received: ${value}`);
|
|
20
|
+
}
|
|
21
|
+
return isValid;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const measureLayoutPosition = (ref: HostInstance | null, callback: (layout: LayoutPosition) => void) => {
|
|
25
|
+
ref?.measureInWindow((pageX, pageY, width, height) => {
|
|
26
|
+
callback({
|
|
27
|
+
height: isValidNumber(height) ? height : 0,
|
|
28
|
+
width: isValidNumber(width) ? width : 0,
|
|
29
|
+
pageX: isValidNumber(pageX) ? pageX : 0,
|
|
30
|
+
pageY: isValidNumber(pageY) ? pageY : 0,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
};
|