@korsolutions/ui 0.0.58 → 0.0.60
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/icon/icon.js +3 -3
- package/dist/module/components/icon/icon.js.map +1 -1
- package/dist/module/components/menu/components/menu-checkbox-item.js +51 -0
- package/dist/module/components/menu/components/menu-checkbox-item.js.map +1 -0
- package/dist/module/components/menu/components/menu-content.js +2 -2
- package/dist/module/components/menu/components/menu-content.js.map +1 -1
- package/dist/module/components/menu/components/menu-group.js +18 -0
- package/dist/module/components/menu/components/menu-group.js.map +1 -0
- package/dist/module/components/menu/components/menu-item.js +14 -6
- package/dist/module/components/menu/components/menu-item.js.map +1 -1
- package/dist/module/components/menu/components/menu-label.js +17 -0
- package/dist/module/components/menu/components/menu-label.js.map +1 -0
- package/dist/module/components/menu/components/menu-overlay.js +1 -1
- package/dist/module/components/menu/components/menu-overlay.js.map +1 -1
- package/dist/module/components/menu/components/menu-radio-group.js +24 -0
- package/dist/module/components/menu/components/menu-radio-group.js.map +1 -0
- package/dist/module/components/menu/components/menu-radio-item.js +54 -0
- package/dist/module/components/menu/components/menu-radio-item.js.map +1 -0
- package/dist/module/components/menu/components/menu-selection-indicator.js +29 -0
- package/dist/module/components/menu/components/menu-selection-indicator.js.map +1 -0
- package/dist/module/components/menu/components/menu-separator.js +17 -0
- package/dist/module/components/menu/components/menu-separator.js.map +1 -0
- package/dist/module/components/menu/components/menu-shortcut.js +17 -0
- package/dist/module/components/menu/components/menu-shortcut.js.map +1 -0
- package/dist/module/components/menu/context.js +8 -0
- package/dist/module/components/menu/context.js.map +1 -1
- package/dist/module/components/menu/index.js +15 -1
- package/dist/module/components/menu/index.js.map +1 -1
- package/dist/module/components/menu/use-organized-children.js +39 -0
- package/dist/module/components/menu/use-organized-children.js.map +1 -0
- package/dist/module/components/menu/variants/default.js +73 -6
- package/dist/module/components/menu/variants/default.js.map +1 -1
- package/dist/module/components/phone-input/components/country-picker.js +1 -1
- package/dist/module/components/popover/components/popover-content.js +2 -5
- package/dist/module/components/popover/components/popover-content.js.map +1 -1
- package/dist/module/components/portal/index.js +1 -0
- package/dist/module/components/portal/index.js.map +1 -1
- package/dist/module/components/portal/portal-offset.js +32 -0
- package/dist/module/components/portal/portal-offset.js.map +1 -0
- package/dist/module/components/portal/portal.js +39 -17
- package/dist/module/components/portal/portal.js.map +1 -1
- package/dist/module/components/select/components/select-content.js +3 -3
- package/dist/module/components/select/components/select-content.js.map +1 -1
- package/dist/module/components/textarea/variants/default.js +7 -0
- package/dist/module/components/textarea/variants/default.js.map +1 -1
- package/dist/module/hooks/use-relative-position.js +37 -28
- package/dist/module/hooks/use-relative-position.js.map +1 -1
- package/dist/module/themes/provider.js.map +1 -1
- package/dist/module/utils/element-utils.js +11 -0
- package/dist/module/utils/element-utils.js.map +1 -0
- package/dist/typescript/src/components/icon/icon.d.ts +2 -2
- package/dist/typescript/src/components/icon/icon.d.ts.map +1 -1
- package/dist/typescript/src/components/menu/components/menu-checkbox-item.d.ts +13 -0
- package/dist/typescript/src/components/menu/components/menu-checkbox-item.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/components/menu-content.d.ts.map +1 -1
- package/dist/typescript/src/components/menu/components/menu-group.d.ts +9 -0
- package/dist/typescript/src/components/menu/components/menu-group.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/components/menu-item.d.ts +3 -3
- package/dist/typescript/src/components/menu/components/menu-item.d.ts.map +1 -1
- package/dist/typescript/src/components/menu/components/menu-label.d.ts +9 -0
- package/dist/typescript/src/components/menu/components/menu-label.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/components/menu-overlay.d.ts.map +1 -1
- package/dist/typescript/src/components/menu/components/menu-radio-group.d.ts +10 -0
- package/dist/typescript/src/components/menu/components/menu-radio-group.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/components/menu-radio-item.d.ts +12 -0
- package/dist/typescript/src/components/menu/components/menu-radio-item.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/components/menu-selection-indicator.d.ts +7 -0
- package/dist/typescript/src/components/menu/components/menu-selection-indicator.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/components/menu-separator.d.ts +8 -0
- package/dist/typescript/src/components/menu/components/menu-separator.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/components/menu-shortcut.d.ts +9 -0
- package/dist/typescript/src/components/menu/components/menu-shortcut.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/context.d.ts +7 -1
- package/dist/typescript/src/components/menu/context.d.ts.map +1 -1
- package/dist/typescript/src/components/menu/index.d.ts +21 -0
- package/dist/typescript/src/components/menu/index.d.ts.map +1 -1
- package/dist/typescript/src/components/menu/types.d.ts +15 -2
- package/dist/typescript/src/components/menu/types.d.ts.map +1 -1
- package/dist/typescript/src/components/menu/use-organized-children.d.ts +3 -0
- package/dist/typescript/src/components/menu/use-organized-children.d.ts.map +1 -0
- package/dist/typescript/src/components/menu/variants/default.d.ts.map +1 -1
- package/dist/typescript/src/components/popover/components/popover-content.d.ts.map +1 -1
- package/dist/typescript/src/components/portal/index.d.ts +1 -0
- package/dist/typescript/src/components/portal/index.d.ts.map +1 -1
- package/dist/typescript/src/components/portal/portal-offset.d.ts +13 -0
- package/dist/typescript/src/components/portal/portal-offset.d.ts.map +1 -0
- package/dist/typescript/src/components/portal/portal.d.ts +3 -2
- package/dist/typescript/src/components/portal/portal.d.ts.map +1 -1
- package/dist/typescript/src/components/select/components/select-content.d.ts.map +1 -1
- package/dist/typescript/src/components/textarea/variants/default.d.ts.map +1 -1
- package/dist/typescript/src/hooks/use-relative-position.d.ts +4 -7
- package/dist/typescript/src/hooks/use-relative-position.d.ts.map +1 -1
- package/dist/typescript/src/themes/provider.d.ts +5 -2
- package/dist/typescript/src/themes/provider.d.ts.map +1 -1
- package/dist/typescript/src/types/element.types.d.ts +2 -0
- package/dist/typescript/src/types/element.types.d.ts.map +1 -1
- package/dist/typescript/src/utils/element-utils.d.ts +3 -0
- package/dist/typescript/src/utils/element-utils.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/icon/icon.tsx +9 -3
- package/src/components/menu/components/menu-checkbox-item.tsx +73 -0
- package/src/components/menu/components/menu-content.tsx +3 -2
- package/src/components/menu/components/menu-group.tsx +25 -0
- package/src/components/menu/components/menu-item.tsx +25 -9
- package/src/components/menu/components/menu-label.tsx +21 -0
- package/src/components/menu/components/menu-overlay.tsx +11 -2
- package/src/components/menu/components/menu-radio-group.tsx +35 -0
- package/src/components/menu/components/menu-radio-item.tsx +76 -0
- package/src/components/menu/components/menu-selection-indicator.tsx +26 -0
- package/src/components/menu/components/menu-separator.tsx +22 -0
- package/src/components/menu/components/menu-shortcut.tsx +21 -0
- package/src/components/menu/context.ts +18 -1
- package/src/components/menu/index.ts +21 -0
- package/src/components/menu/types.ts +20 -2
- package/src/components/menu/use-organized-children.tsx +38 -0
- package/src/components/menu/variants/default.tsx +73 -6
- package/src/components/phone-input/components/country-picker.tsx +1 -1
- package/src/components/popover/components/popover-content.tsx +1 -4
- package/src/components/portal/index.ts +1 -0
- package/src/components/portal/portal-offset.ts +28 -0
- package/src/components/portal/portal.tsx +77 -27
- package/src/components/select/components/select-content.tsx +14 -5
- package/src/components/textarea/variants/default.tsx +7 -0
- package/src/hooks/use-relative-position.ts +53 -41
- package/src/themes/provider.tsx +5 -4
- package/src/types/element.types.ts +10 -1
- package/src/utils/element-utils.ts +12 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { View, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
import { MenuRadioGroupContext, useMenu } from "../context";
|
|
4
|
+
|
|
5
|
+
export interface MenuRadioGroupProps {
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
value: string;
|
|
8
|
+
onValueChange: (value: string) => void;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function MenuRadioGroup(props: MenuRadioGroupProps) {
|
|
13
|
+
const menu = useMenu();
|
|
14
|
+
const composedStyle = [menu.styles?.radioGroup, props.style];
|
|
15
|
+
|
|
16
|
+
const contextValue = useMemo(
|
|
17
|
+
() => ({
|
|
18
|
+
value: props.value,
|
|
19
|
+
onValueChange: props.onValueChange,
|
|
20
|
+
}),
|
|
21
|
+
[props.value, props.onValueChange],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<MenuRadioGroupContext.Provider value={contextValue}>
|
|
26
|
+
<View
|
|
27
|
+
accessibilityRole="radiogroup"
|
|
28
|
+
role="radiogroup"
|
|
29
|
+
style={composedStyle}
|
|
30
|
+
>
|
|
31
|
+
{props.children}
|
|
32
|
+
</View>
|
|
33
|
+
</MenuRadioGroupContext.Provider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Pressable, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
import { useMenu, useMenuRadioGroup } from "../context";
|
|
4
|
+
import type { MenuRadioItemState } from "../types";
|
|
5
|
+
import { useOrganizedChildren } from "../use-organized-children";
|
|
6
|
+
import { MenuSelectionIndicator } from "./menu-selection-indicator";
|
|
7
|
+
|
|
8
|
+
export interface MenuRadioItemProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
value: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
closeOnPress?: boolean;
|
|
13
|
+
render?: (props: MenuRadioItemProps) => React.ReactNode;
|
|
14
|
+
style?: StyleProp<ViewStyle>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const calculateState = (
|
|
18
|
+
isHovered: boolean,
|
|
19
|
+
isSelected: boolean,
|
|
20
|
+
disabled?: boolean,
|
|
21
|
+
): MenuRadioItemState => {
|
|
22
|
+
if (disabled) return "disabled";
|
|
23
|
+
if (isSelected) return "selected";
|
|
24
|
+
if (isHovered) return "hovered";
|
|
25
|
+
return "default";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function MenuRadioItem(props: MenuRadioItemProps) {
|
|
29
|
+
const menu = useMenu();
|
|
30
|
+
const radioGroup = useMenuRadioGroup();
|
|
31
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
32
|
+
const isSelected = radioGroup.value === props.value;
|
|
33
|
+
const state = calculateState(isHovered, isSelected, props.disabled);
|
|
34
|
+
|
|
35
|
+
const composedStyle = [
|
|
36
|
+
menu.styles?.radioItem?.default,
|
|
37
|
+
menu.styles?.radioItem?.[state],
|
|
38
|
+
props.style,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const handlePress = () => {
|
|
42
|
+
if (props.disabled) return;
|
|
43
|
+
radioGroup.onValueChange(props.value);
|
|
44
|
+
if (props.closeOnPress) {
|
|
45
|
+
menu.setIsOpen(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const organizedChildren = useOrganizedChildren(props.children);
|
|
50
|
+
|
|
51
|
+
if (props.render) {
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
{props.render({
|
|
55
|
+
...props,
|
|
56
|
+
children: organizedChildren,
|
|
57
|
+
})}
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Pressable
|
|
64
|
+
onPress={handlePress}
|
|
65
|
+
onPointerEnter={() => setIsHovered(true)}
|
|
66
|
+
onPointerLeave={() => setIsHovered(false)}
|
|
67
|
+
disabled={props.disabled}
|
|
68
|
+
accessibilityRole="radio"
|
|
69
|
+
accessibilityState={{ checked: isSelected, disabled: props.disabled }}
|
|
70
|
+
style={composedStyle}
|
|
71
|
+
>
|
|
72
|
+
{organizedChildren}
|
|
73
|
+
<MenuSelectionIndicator isSelected={isSelected} />
|
|
74
|
+
</Pressable>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text, View } from "react-native";
|
|
3
|
+
import { useComponentsConfig } from "../../../themes";
|
|
4
|
+
import { useMenu } from "../context";
|
|
5
|
+
|
|
6
|
+
interface MenuSelectionIndicatorProps {
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function MenuSelectionIndicator({
|
|
11
|
+
isSelected,
|
|
12
|
+
}: MenuSelectionIndicatorProps) {
|
|
13
|
+
const config = useComponentsConfig();
|
|
14
|
+
const menu = useMenu();
|
|
15
|
+
const SelectionIcon = config?.menu?.selectionIcon;
|
|
16
|
+
|
|
17
|
+
if (!isSelected) {
|
|
18
|
+
return <View style={menu.styles?.selectionIndicator} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (SelectionIcon) {
|
|
22
|
+
return <SelectionIcon {...menu.styles?.selectionIndicator} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return <Text style={menu.styles?.selectionIndicator}>✓</Text>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, type StyleProp, type ViewStyle } from "react-native";
|
|
3
|
+
import { useMenu } from "../context";
|
|
4
|
+
|
|
5
|
+
export interface MenuSeparatorProps {
|
|
6
|
+
render?: (props: MenuSeparatorProps) => React.ReactNode;
|
|
7
|
+
style?: StyleProp<ViewStyle>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function MenuSeparator(props: MenuSeparatorProps) {
|
|
11
|
+
const menu = useMenu();
|
|
12
|
+
const composedStyle = [menu.styles?.separator, props.style];
|
|
13
|
+
|
|
14
|
+
const Component = props.render ?? View;
|
|
15
|
+
return (
|
|
16
|
+
<Component
|
|
17
|
+
{...props}
|
|
18
|
+
role="separator"
|
|
19
|
+
style={composedStyle}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text, type StyleProp, type TextStyle } from "react-native";
|
|
3
|
+
import { useMenu } from "../context";
|
|
4
|
+
|
|
5
|
+
export interface MenuShortcutProps {
|
|
6
|
+
children: string;
|
|
7
|
+
render?: (props: MenuShortcutProps) => React.ReactNode;
|
|
8
|
+
style?: StyleProp<TextStyle>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function MenuShortcut(props: MenuShortcutProps) {
|
|
12
|
+
const menu = useMenu();
|
|
13
|
+
const composedStyle = [menu.styles?.shortcut, props.style];
|
|
14
|
+
|
|
15
|
+
const Component = props.render ?? Text;
|
|
16
|
+
return (
|
|
17
|
+
<Component {...props} style={composedStyle}>
|
|
18
|
+
{props.children}
|
|
19
|
+
</Component>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { LayoutPosition } from "../../hooks/use-relative-position";
|
|
2
1
|
import { createContext, type Dispatch, useContext } from "react";
|
|
3
2
|
import type { LayoutRectangle } from "react-native";
|
|
3
|
+
import type { LayoutPosition } from "../../hooks/use-relative-position";
|
|
4
4
|
import type { MenuStyles } from "./types";
|
|
5
5
|
|
|
6
6
|
export interface MenuContext {
|
|
@@ -23,3 +23,20 @@ export const useMenu = () => {
|
|
|
23
23
|
}
|
|
24
24
|
return context;
|
|
25
25
|
};
|
|
26
|
+
|
|
27
|
+
export interface MenuRadioGroupContextValue {
|
|
28
|
+
value: string;
|
|
29
|
+
onValueChange: (value: string) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const MenuRadioGroupContext = createContext<
|
|
33
|
+
MenuRadioGroupContextValue | undefined
|
|
34
|
+
>(undefined);
|
|
35
|
+
|
|
36
|
+
export const useMenuRadioGroup = () => {
|
|
37
|
+
const context = useContext(MenuRadioGroupContext);
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error("useMenuRadioGroup must be used within a Menu.RadioGroup");
|
|
40
|
+
}
|
|
41
|
+
return context;
|
|
42
|
+
};
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { MenuCheckboxItem } from "./components/menu-checkbox-item";
|
|
1
2
|
import { MenuContent } from "./components/menu-content";
|
|
3
|
+
import { MenuGroup } from "./components/menu-group";
|
|
2
4
|
import { MenuItem } from "./components/menu-item";
|
|
5
|
+
import { MenuLabel } from "./components/menu-label";
|
|
3
6
|
import { MenuOverlay } from "./components/menu-overlay";
|
|
4
7
|
import { MenuPortal } from "./components/menu-portal";
|
|
8
|
+
import { MenuRadioGroup } from "./components/menu-radio-group";
|
|
9
|
+
import { MenuRadioItem } from "./components/menu-radio-item";
|
|
5
10
|
import { MenuRoot } from "./components/menu-root";
|
|
11
|
+
import { MenuSeparator } from "./components/menu-separator";
|
|
12
|
+
import { MenuShortcut } from "./components/menu-shortcut";
|
|
6
13
|
import { MenuTrigger } from "./components/menu-trigger";
|
|
7
14
|
|
|
8
15
|
export const Menu = {
|
|
@@ -12,12 +19,26 @@ export const Menu = {
|
|
|
12
19
|
Overlay: MenuOverlay,
|
|
13
20
|
Content: MenuContent,
|
|
14
21
|
Item: MenuItem,
|
|
22
|
+
Group: MenuGroup,
|
|
23
|
+
Label: MenuLabel,
|
|
24
|
+
Separator: MenuSeparator,
|
|
25
|
+
CheckboxItem: MenuCheckboxItem,
|
|
26
|
+
RadioGroup: MenuRadioGroup,
|
|
27
|
+
RadioItem: MenuRadioItem,
|
|
28
|
+
Shortcut: MenuShortcut,
|
|
15
29
|
};
|
|
16
30
|
|
|
31
|
+
export type { MenuCheckboxItemProps } from "./components/menu-checkbox-item";
|
|
17
32
|
export type { MenuContentProps } from "./components/menu-content";
|
|
33
|
+
export type { MenuGroupProps } from "./components/menu-group";
|
|
18
34
|
export type { MenuItemProps } from "./components/menu-item";
|
|
35
|
+
export type { MenuLabelProps } from "./components/menu-label";
|
|
19
36
|
export type { MenuOverlayProps } from "./components/menu-overlay";
|
|
20
37
|
export type { MenuPortalProps } from "./components/menu-portal";
|
|
38
|
+
export type { MenuRadioGroupProps } from "./components/menu-radio-group";
|
|
39
|
+
export type { MenuRadioItemProps } from "./components/menu-radio-item";
|
|
21
40
|
export type { MenuRootProps } from "./components/menu-root";
|
|
41
|
+
export type { MenuSeparatorProps } from "./components/menu-separator";
|
|
42
|
+
export type { MenuShortcutProps } from "./components/menu-shortcut";
|
|
22
43
|
export type { MenuTriggerProps } from "./components/menu-trigger";
|
|
23
44
|
export type { MenuStyles } from "./types";
|
|
@@ -1,11 +1,29 @@
|
|
|
1
|
+
import type { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
import type { SvgProps } from "../../types/props.types";
|
|
1
3
|
import type { MenuContentProps } from "./components/menu-content";
|
|
2
|
-
import type { MenuItemProps } from "./components/menu-item";
|
|
3
4
|
import type { MenuOverlayProps } from "./components/menu-overlay";
|
|
4
5
|
|
|
5
6
|
export type MenuButtonState = "default" | "hovered";
|
|
7
|
+
export type MenuCheckboxItemState = "default" | "hovered" | "disabled";
|
|
8
|
+
export type MenuRadioItemState =
|
|
9
|
+
| "default"
|
|
10
|
+
| "hovered"
|
|
11
|
+
| "selected"
|
|
12
|
+
| "disabled";
|
|
6
13
|
|
|
7
14
|
export interface MenuStyles {
|
|
8
15
|
content?: MenuContentProps["style"];
|
|
9
|
-
item?: Partial<Record<MenuButtonState,
|
|
16
|
+
item?: Partial<Record<MenuButtonState, StyleProp<ViewStyle>>>;
|
|
17
|
+
itemText?: StyleProp<TextStyle>;
|
|
18
|
+
itemIcon?: SvgProps;
|
|
10
19
|
overlay?: MenuOverlayProps["style"];
|
|
20
|
+
|
|
21
|
+
group?: StyleProp<ViewStyle>;
|
|
22
|
+
label?: StyleProp<TextStyle>;
|
|
23
|
+
separator?: StyleProp<ViewStyle>;
|
|
24
|
+
checkboxItem?: Partial<Record<MenuCheckboxItemState, StyleProp<ViewStyle>>>;
|
|
25
|
+
selectionIndicator?: TextStyle & SvgProps;
|
|
26
|
+
radioGroup?: StyleProp<ViewStyle>;
|
|
27
|
+
radioItem?: Partial<Record<MenuRadioItemState, StyleProp<ViewStyle>>>;
|
|
28
|
+
shortcut?: StyleProp<TextStyle>;
|
|
11
29
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { getElementProp } from "../../utils/element-utils";
|
|
4
|
+
import { Icon } from "../icon";
|
|
5
|
+
import { useMenu } from "./context";
|
|
6
|
+
|
|
7
|
+
export function useOrganizedChildren(children: React.ReactNode) {
|
|
8
|
+
const menu = useMenu();
|
|
9
|
+
|
|
10
|
+
const organizedChildren = useMemo(() => {
|
|
11
|
+
if (typeof children === "string") {
|
|
12
|
+
return <Text style={menu.styles?.itemText}>{children}</Text>;
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(children)) {
|
|
15
|
+
return children.map((child, index) => {
|
|
16
|
+
if (typeof child === "string") {
|
|
17
|
+
return (
|
|
18
|
+
<Text key={index} style={menu.styles?.itemText}>
|
|
19
|
+
{child}
|
|
20
|
+
</Text>
|
|
21
|
+
);
|
|
22
|
+
} else if (React.isValidElement(child) && child.type === Icon) {
|
|
23
|
+
return React.cloneElement(child as React.ReactElement<any>, {
|
|
24
|
+
key: child.key,
|
|
25
|
+
...menu.styles?.itemIcon,
|
|
26
|
+
style: [
|
|
27
|
+
menu.styles?.itemIcon?.style,
|
|
28
|
+
getElementProp(child, "style"),
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return child;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return children;
|
|
36
|
+
}, [children, menu.styles?.itemIcon]);
|
|
37
|
+
return organizedChildren;
|
|
38
|
+
}
|
|
@@ -4,9 +4,7 @@ import { useThemedStyles } from "../../../utils/use-themed-styles";
|
|
|
4
4
|
export const useMenuVariantDefault = (): MenuStyles => {
|
|
5
5
|
return useThemedStyles(
|
|
6
6
|
({ colors, radius, fontFamily, fontSize }): MenuStyles => ({
|
|
7
|
-
overlay: {
|
|
8
|
-
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
9
|
-
},
|
|
7
|
+
overlay: {},
|
|
10
8
|
content: {
|
|
11
9
|
overflow: "hidden",
|
|
12
10
|
backgroundColor: colors.surface,
|
|
@@ -21,15 +19,84 @@ export const useMenuVariantDefault = (): MenuStyles => {
|
|
|
21
19
|
},
|
|
22
20
|
item: {
|
|
23
21
|
default: {
|
|
22
|
+
flexDirection: "row",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
paddingVertical: 12,
|
|
25
|
+
paddingHorizontal: 16,
|
|
26
|
+
gap: 8,
|
|
27
|
+
},
|
|
28
|
+
hovered: {
|
|
29
|
+
backgroundColor: colors.muted,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
itemText: {
|
|
33
|
+
fontFamily: fontFamily,
|
|
34
|
+
fontSize: fontSize,
|
|
35
|
+
color: colors.foreground,
|
|
36
|
+
},
|
|
37
|
+
itemIcon: {
|
|
38
|
+
color: colors.foreground,
|
|
39
|
+
size: fontSize * 1.25,
|
|
40
|
+
},
|
|
41
|
+
label: {
|
|
42
|
+
paddingVertical: 8,
|
|
43
|
+
paddingHorizontal: 16,
|
|
44
|
+
fontFamily: fontFamily,
|
|
45
|
+
fontSize: fontSize * 0.75,
|
|
46
|
+
fontWeight: "600",
|
|
47
|
+
color: colors.mutedForeground,
|
|
48
|
+
},
|
|
49
|
+
separator: {
|
|
50
|
+
height: 1,
|
|
51
|
+
backgroundColor: colors.border,
|
|
52
|
+
marginVertical: 4,
|
|
53
|
+
marginHorizontal: 8,
|
|
54
|
+
},
|
|
55
|
+
checkboxItem: {
|
|
56
|
+
default: {
|
|
57
|
+
flexDirection: "row",
|
|
58
|
+
alignItems: "center",
|
|
24
59
|
paddingVertical: 12,
|
|
25
60
|
paddingHorizontal: 16,
|
|
26
|
-
|
|
27
|
-
fontSize: fontSize,
|
|
28
|
-
color: colors.foreground,
|
|
61
|
+
gap: 8,
|
|
29
62
|
},
|
|
30
63
|
hovered: {
|
|
31
64
|
backgroundColor: colors.muted,
|
|
32
65
|
},
|
|
66
|
+
disabled: {
|
|
67
|
+
opacity: 0.5,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
selectionIndicator: {
|
|
71
|
+
color: colors.foreground,
|
|
72
|
+
fontSize: fontSize,
|
|
73
|
+
width: fontSize,
|
|
74
|
+
size: fontSize,
|
|
75
|
+
strokeWidth: 2,
|
|
76
|
+
marginLeft: "auto",
|
|
77
|
+
style: {
|
|
78
|
+
marginLeft: "auto",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
radioItem: {
|
|
82
|
+
default: {
|
|
83
|
+
flexDirection: "row",
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
paddingVertical: 12,
|
|
86
|
+
paddingHorizontal: 16,
|
|
87
|
+
gap: 8,
|
|
88
|
+
},
|
|
89
|
+
hovered: {
|
|
90
|
+
backgroundColor: colors.muted,
|
|
91
|
+
},
|
|
92
|
+
disabled: {
|
|
93
|
+
opacity: 0.5,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
shortcut: {
|
|
97
|
+
fontSize: fontSize * 0.75,
|
|
98
|
+
fontFamily: fontFamily,
|
|
99
|
+
color: colors.mutedForeground,
|
|
33
100
|
},
|
|
34
101
|
}),
|
|
35
102
|
);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { useRelativePosition } from "../../../hooks/use-relative-position";
|
|
2
|
-
import { useSafeAreaInsets } from "../../../safe-area";
|
|
3
1
|
import React from "react";
|
|
4
2
|
import { type StyleProp, View, type ViewStyle } from "react-native";
|
|
3
|
+
import { useRelativePosition } from "../../../hooks/use-relative-position";
|
|
5
4
|
import { usePopover } from "../context";
|
|
6
5
|
|
|
7
6
|
export interface PopoverContentProps {
|
|
@@ -14,7 +13,6 @@ export interface PopoverContentProps {
|
|
|
14
13
|
|
|
15
14
|
export function PopoverContent(props: PopoverContentProps) {
|
|
16
15
|
const popover = usePopover();
|
|
17
|
-
const insets = useSafeAreaInsets();
|
|
18
16
|
|
|
19
17
|
const positionStyle = useRelativePosition({
|
|
20
18
|
align: "start",
|
|
@@ -23,7 +21,6 @@ export function PopoverContent(props: PopoverContentProps) {
|
|
|
23
21
|
alignOffset: 0,
|
|
24
22
|
preferredSide: "bottom",
|
|
25
23
|
sideOffset: 0,
|
|
26
|
-
insets,
|
|
27
24
|
});
|
|
28
25
|
|
|
29
26
|
const composedStyle = [positionStyle, popover.styles?.content, props.style];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
export interface PortalOffset {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type PortalOffsetValue = PortalOffset | null | undefined;
|
|
9
|
+
|
|
10
|
+
// null means the offset is not measure yet but will be
|
|
11
|
+
// undefined means the offset is not available and will never be (e.g. on web or ios when FullWindowOverlay is used)
|
|
12
|
+
export const PortalOffsetContext = createContext<PortalOffsetValue>(undefined);
|
|
13
|
+
|
|
14
|
+
type UsePortalOffsetReturn = {
|
|
15
|
+
value: PortalOffset;
|
|
16
|
+
isLoaded: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function usePortalOffset(): UsePortalOffsetReturn {
|
|
20
|
+
const value = useContext(PortalOffsetContext);
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
return { value: { x: 0, y: 0 }, isLoaded: true };
|
|
23
|
+
}
|
|
24
|
+
if (value === null) {
|
|
25
|
+
return { value: { x: 0, y: 0 }, isLoaded: false };
|
|
26
|
+
}
|
|
27
|
+
return { value, isLoaded: true };
|
|
28
|
+
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
useSyncExternalStore,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { Platform, View, type HostInstance } from "react-native";
|
|
10
|
+
import { PortalOffsetContext, type PortalOffset } from "./portal-offset";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_PORTAL_HOST,
|
|
13
|
+
type PortalHostProps,
|
|
14
|
+
type PortalProps,
|
|
15
|
+
} from "./portal.constants";
|
|
4
16
|
|
|
5
17
|
type PortalMap = Map<string, React.ReactNode>;
|
|
6
18
|
type PortalHostMap = Map<string, PortalMap>;
|
|
@@ -11,7 +23,10 @@ type PortalStore = {
|
|
|
11
23
|
};
|
|
12
24
|
|
|
13
25
|
const store: PortalStore = {
|
|
14
|
-
map: new Map<string, PortalMap>().set(
|
|
26
|
+
map: new Map<string, PortalMap>().set(
|
|
27
|
+
DEFAULT_PORTAL_HOST,
|
|
28
|
+
new Map<string, React.ReactNode>(),
|
|
29
|
+
),
|
|
15
30
|
listeners: new Set(),
|
|
16
31
|
};
|
|
17
32
|
|
|
@@ -30,7 +45,11 @@ function subscribe(cb: () => void) {
|
|
|
30
45
|
};
|
|
31
46
|
}
|
|
32
47
|
|
|
33
|
-
function updatePortal(
|
|
48
|
+
function updatePortal(
|
|
49
|
+
hostName: string,
|
|
50
|
+
name: string,
|
|
51
|
+
children: React.ReactNode,
|
|
52
|
+
) {
|
|
34
53
|
const next = new Map(store.map);
|
|
35
54
|
const portal = next.get(hostName) ?? new Map<string, React.ReactNode>();
|
|
36
55
|
portal.set(name, children);
|
|
@@ -48,34 +67,65 @@ function removePortal(hostName: string, name: string) {
|
|
|
48
67
|
emit();
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
function DefaultContainer(props: React.PropsWithChildren) {
|
|
71
|
+
const containerRef = useRef<HostInstance>(null);
|
|
72
|
+
const [offset, setOffset] = useState<PortalOffset | null>(null);
|
|
73
|
+
|
|
74
|
+
const onLayout = useCallback(() => {
|
|
75
|
+
containerRef.current?.measureInWindow((pageX: number, pageY: number) => {
|
|
76
|
+
setOffset((prev) => {
|
|
77
|
+
if (prev?.x === pageX && prev?.y === pageY) return prev;
|
|
78
|
+
return { x: pageX, y: pageY };
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<View
|
|
85
|
+
ref={containerRef}
|
|
86
|
+
onLayout={onLayout}
|
|
87
|
+
style={{
|
|
88
|
+
position: "absolute",
|
|
89
|
+
top: 0,
|
|
90
|
+
left: 0,
|
|
91
|
+
right: 0,
|
|
92
|
+
bottom: 0,
|
|
93
|
+
elevation: 999,
|
|
94
|
+
zIndex: 999,
|
|
95
|
+
pointerEvents: "box-none",
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
<PortalOffsetContext.Provider value={offset}>
|
|
99
|
+
{props.children}
|
|
100
|
+
</PortalOffsetContext.Provider>
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function PortalHost({
|
|
106
|
+
name = DEFAULT_PORTAL_HOST,
|
|
107
|
+
container,
|
|
108
|
+
}: PortalHostProps) {
|
|
52
109
|
const map = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
53
110
|
const portalMap = map.get(name) ?? new Map<string, React.ReactNode>();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
top: 0,
|
|
63
|
-
left: 0,
|
|
64
|
-
right: 0,
|
|
65
|
-
bottom: 0,
|
|
66
|
-
elevation: 999,
|
|
67
|
-
zIndex: 999,
|
|
68
|
-
pointerEvents: "box-none",
|
|
69
|
-
}}
|
|
70
|
-
/>
|
|
71
|
-
),
|
|
72
|
-
...container,
|
|
73
|
-
});
|
|
111
|
+
const Container = useMemo(
|
|
112
|
+
() =>
|
|
113
|
+
Platform.select({
|
|
114
|
+
default: DefaultContainer,
|
|
115
|
+
...container,
|
|
116
|
+
}),
|
|
117
|
+
[container],
|
|
118
|
+
);
|
|
74
119
|
|
|
120
|
+
if (portalMap.size === 0) return null;
|
|
75
121
|
return <Container>{Array.from(portalMap.values())}</Container>;
|
|
76
122
|
}
|
|
77
123
|
|
|
78
|
-
export function Portal({
|
|
124
|
+
export function Portal({
|
|
125
|
+
name,
|
|
126
|
+
hostName = DEFAULT_PORTAL_HOST,
|
|
127
|
+
children,
|
|
128
|
+
}: PortalProps) {
|
|
79
129
|
useEffect(() => {
|
|
80
130
|
updatePortal(hostName, name, children);
|
|
81
131
|
}, [hostName, name, children]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useRelativePosition } from "../../../hooks/use-relative-position";
|
|
2
|
-
import { calculateComposedStyles } from "../../../utils/calculate-styles";
|
|
3
1
|
import React from "react";
|
|
4
2
|
import { type StyleProp, View, type ViewStyle } from "react-native";
|
|
3
|
+
import { useRelativePosition } from "../../../hooks/use-relative-position";
|
|
4
|
+
import { calculateComposedStyles } from "../../../utils/calculate-styles";
|
|
5
5
|
import { useSelect } from "../context";
|
|
6
6
|
|
|
7
7
|
export interface SelectContentProps {
|
|
@@ -14,7 +14,12 @@ export interface SelectContentProps {
|
|
|
14
14
|
|
|
15
15
|
export function SelectContent(props: SelectContentProps) {
|
|
16
16
|
const select = useSelect();
|
|
17
|
-
const composedStyles = calculateComposedStyles(
|
|
17
|
+
const composedStyles = calculateComposedStyles(
|
|
18
|
+
select.styles,
|
|
19
|
+
select.state,
|
|
20
|
+
"content",
|
|
21
|
+
props.style,
|
|
22
|
+
);
|
|
18
23
|
|
|
19
24
|
const positionStyle = useRelativePosition({
|
|
20
25
|
align: "start",
|
|
@@ -22,13 +27,17 @@ export function SelectContent(props: SelectContentProps) {
|
|
|
22
27
|
contentLayout: select.contentLayout,
|
|
23
28
|
alignOffset: 0,
|
|
24
29
|
preferredSide: "bottom",
|
|
25
|
-
sideOffset:
|
|
30
|
+
sideOffset: 2,
|
|
26
31
|
});
|
|
27
32
|
|
|
28
33
|
const Component = props.render ?? View;
|
|
29
34
|
return (
|
|
30
35
|
<Component
|
|
31
|
-
style={[
|
|
36
|
+
style={[
|
|
37
|
+
positionStyle,
|
|
38
|
+
composedStyles,
|
|
39
|
+
{ width: select.triggerPosition.width },
|
|
40
|
+
]}
|
|
32
41
|
onLayout={(e) => {
|
|
33
42
|
select.setContentLayout(e.nativeEvent.layout);
|
|
34
43
|
}}
|