@umituz/react-native-design-system 2.9.30 → 2.9.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.9.30",
3
+ "version": "2.9.31",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -29,7 +29,6 @@ export {
29
29
  // Bottom Sheet
30
30
  BottomSheet,
31
31
  BottomSheetModal,
32
-
33
32
  FilterBottomSheet,
34
33
  FilterSheet,
35
34
  useBottomSheet,
@@ -114,8 +113,6 @@ export {
114
113
  type FabButtonProps,
115
114
  type TabScreen,
116
115
  type TabNavigatorConfig,
117
- type StackScreen,
118
- type StackNavigatorConfig,
119
116
  type BaseScreen,
120
117
  type BaseNavigatorConfig,
121
118
  type IconRendererProps,
@@ -190,5 +187,4 @@ export {
190
187
  InfoGrid,
191
188
  type InfoGridProps,
192
189
  type InfoGridItem,
193
- } from '../molecules';
194
-
190
+ } from "../molecules";
@@ -0,0 +1,151 @@
1
+ import React, { useMemo } from "react";
2
+ import {
3
+ View,
4
+ Modal,
5
+ TouchableWithoutFeedback,
6
+ StyleSheet,
7
+ ViewStyle,
8
+ } from "react-native";
9
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
10
+ import {
11
+ ARC_BACKGROUND,
12
+ OVERLAY,
13
+ ROW_CONFIG,
14
+ LAYOUT,
15
+ getTopRowPosition,
16
+ getBottomRowPosition,
17
+ } from "./constants";
18
+ import { CircularMenuBackground } from "./CircularMenuBackground";
19
+ import { CircularMenuItem } from "./CircularMenuItem";
20
+ import { CircularMenuCloseButton } from "./CircularMenuCloseButton";
21
+
22
+ export interface CircularMenuAction {
23
+ id: string;
24
+ label: string;
25
+ icon: string;
26
+ onPress: () => void;
27
+ }
28
+
29
+ export interface CircularMenuProps {
30
+ visible: boolean;
31
+ onClose: () => void;
32
+ actions: CircularMenuAction[];
33
+ }
34
+
35
+ export const CircularMenu: React.FC<CircularMenuProps> = ({
36
+ visible,
37
+ onClose,
38
+ actions,
39
+ }) => {
40
+ const insets = useSafeAreaInsets();
41
+
42
+ /**
43
+ * Determine layout based on number of items.
44
+ * If strictly 3 items, use the specific "Top Triangle" layout requested.
45
+ * Otherwise, use a generic algorithm.
46
+ */
47
+ const layoutItems = useMemo(() => {
48
+ if (actions.length === 3) {
49
+ // Triangle Layout: Top Center (Index 1), Bottom Left (Index 0), Bottom Right (Index 2)
50
+ return [
51
+ { ...actions[1], position: getTopRowPosition(0, 1) }, // Text to Video (Top Center)
52
+ { ...actions[0], position: getBottomRowPosition("left") }, // Text to Image (Bottom Left)
53
+ { ...actions[2], position: getBottomRowPosition("right") }, // Image to Video (Bottom Right)
54
+ ];
55
+ } else {
56
+ // Default: 2 on top, 2 on-bottom, etc.
57
+ // This is a fallback if actions change.
58
+ // For now, implementing simple distribute:
59
+ const topCount = Math.min(actions.length, 2);
60
+ const bottomCount = Math.max(0, actions.length - 2);
61
+
62
+ const mapped = [];
63
+ // Top row
64
+ for (let i = 0; i < topCount; i++) {
65
+ mapped.push({
66
+ ...actions[i],
67
+ position: getTopRowPosition(i, topCount),
68
+ });
69
+ }
70
+ // Bottom row (left/right)
71
+ if (bottomCount > 0) {
72
+ mapped.push({
73
+ ...actions[2],
74
+ position: getBottomRowPosition("left")
75
+ })
76
+ }
77
+ if (bottomCount > 1) {
78
+ mapped.push({
79
+ ...actions[3],
80
+ position: getBottomRowPosition("right")
81
+ })
82
+ }
83
+ return mapped;
84
+ }
85
+ }, [actions]);
86
+
87
+ return (
88
+ <Modal
89
+ visible={visible}
90
+ transparent
91
+ animationType="fade"
92
+ onRequestClose={onClose}
93
+ statusBarTranslucent
94
+ >
95
+ <TouchableWithoutFeedback onPress={onClose}>
96
+ <View style={styles.overlay}>
97
+ <TouchableWithoutFeedback>
98
+ <View
99
+ style={[
100
+ styles.container,
101
+ {
102
+ paddingBottom: insets.bottom + OVERLAY.PADDING_BOTTOM_OFFSET,
103
+ },
104
+ ]}
105
+ >
106
+ <CircularMenuBackground />
107
+
108
+ <View style={styles.itemsContainer}>
109
+ {layoutItems.map((item) => (
110
+ <View key={item.id} style={item.position as ViewStyle}>
111
+ <CircularMenuItem
112
+ icon={item.icon}
113
+ label={item.label}
114
+ onPress={item.onPress}
115
+ />
116
+ </View>
117
+ ))}
118
+
119
+ <View style={styles.closeButton}>
120
+ <CircularMenuCloseButton onPress={onClose} />
121
+ </View>
122
+ </View>
123
+ </View>
124
+ </TouchableWithoutFeedback>
125
+ </View>
126
+ </TouchableWithoutFeedback>
127
+ </Modal>
128
+ );
129
+ };
130
+
131
+ const styles = StyleSheet.create({
132
+ overlay: {
133
+ flex: 1,
134
+ justifyContent: "flex-end",
135
+ backgroundColor: "rgba(0, 0, 0, 0.85)",
136
+ },
137
+ container: {
138
+ alignItems: "center",
139
+ },
140
+ itemsContainer: {
141
+ width: ARC_BACKGROUND.WIDTH,
142
+ height: ARC_BACKGROUND.HEIGHT,
143
+ position: "relative",
144
+ },
145
+ closeButton: {
146
+ position: "absolute",
147
+ left: ARC_BACKGROUND.WIDTH / 2 - LAYOUT.CLOSE_BUTTON_SIZE / 2,
148
+ top:
149
+ ROW_CONFIG.CENTER_Y + ROW_CONFIG.BOTTOM_Y - LAYOUT.CLOSE_BUTTON_SIZE / 2,
150
+ },
151
+ });
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+ import { useAppDesignTokens } from "../../theme/useAppDesignTokens";
4
+ import { ARC_BACKGROUND } from "./constants";
5
+
6
+ export const CircularMenuBackground: React.FC = () => {
7
+ const tokens = useAppDesignTokens();
8
+
9
+ return (
10
+ <View
11
+ style={[
12
+ styles.arc,
13
+ {
14
+ backgroundColor: tokens.colors.surface,
15
+ width: ARC_BACKGROUND.WIDTH,
16
+ height: ARC_BACKGROUND.HEIGHT,
17
+ borderTopLeftRadius: ARC_BACKGROUND.BORDER_RADIUS_TOP,
18
+ borderTopRightRadius: ARC_BACKGROUND.BORDER_RADIUS_TOP,
19
+ },
20
+ ]}
21
+ />
22
+ );
23
+ };
24
+
25
+ const styles = StyleSheet.create({
26
+ arc: {
27
+ position: "absolute",
28
+ bottom: 0,
29
+ borderBottomLeftRadius: 0,
30
+ borderBottomRightRadius: 0,
31
+ },
32
+ });
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { TouchableOpacity, StyleSheet } from "react-native";
3
+ import { AtomicIcon } from "../../atoms/AtomicIcon";
4
+ import { useAppDesignTokens } from "../../theme/useAppDesignTokens";
5
+ import { LAYOUT } from "./constants";
6
+
7
+ export interface CircularMenuCloseButtonProps {
8
+ onPress: () => void;
9
+ }
10
+
11
+ export const CircularMenuCloseButton: React.FC<CircularMenuCloseButtonProps> = ({
12
+ onPress,
13
+ }) => {
14
+ const tokens = useAppDesignTokens();
15
+
16
+ return (
17
+ <TouchableOpacity
18
+ onPress={onPress}
19
+ style={[
20
+ styles.container,
21
+ {
22
+ backgroundColor: tokens.colors.surfaceVariant,
23
+ width: LAYOUT.CLOSE_BUTTON_SIZE,
24
+ height: LAYOUT.CLOSE_BUTTON_SIZE,
25
+ borderRadius: LAYOUT.CLOSE_BUTTON_SIZE / 2,
26
+ borderWidth: 1,
27
+ borderColor: tokens.colors.border,
28
+ },
29
+ ]}
30
+ activeOpacity={0.8}
31
+ accessibilityRole="button"
32
+ accessibilityLabel="Close menu"
33
+ >
34
+ <AtomicIcon name="close" size="md" color="secondary" />
35
+ </TouchableOpacity>
36
+ );
37
+ };
38
+
39
+ const styles = StyleSheet.create({
40
+ container: {
41
+ justifyContent: "center",
42
+ alignItems: "center",
43
+ },
44
+ });
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { View, StyleSheet, TouchableOpacity } from "react-native";
3
+ import { AtomicIcon } from "../../atoms/AtomicIcon";
4
+ import { AtomicText } from "../../atoms/AtomicText";
5
+ import { useAppDesignTokens } from "../../theme/useAppDesignTokens";
6
+ import { LAYOUT } from "./constants";
7
+
8
+ export interface CircularMenuItemProps {
9
+ icon: string;
10
+ label: string;
11
+ onPress: () => void;
12
+ }
13
+
14
+ export const CircularMenuItem: React.FC<CircularMenuItemProps> = ({
15
+ icon,
16
+ label,
17
+ onPress,
18
+ }) => {
19
+ const tokens = useAppDesignTokens();
20
+
21
+ return (
22
+ <TouchableOpacity
23
+ onPress={onPress}
24
+ style={styles.container}
25
+ activeOpacity={0.7}
26
+ accessibilityRole="button"
27
+ accessibilityLabel={label}
28
+ >
29
+ <View
30
+ style={[
31
+ styles.iconContainer,
32
+ {
33
+ backgroundColor: tokens.colors.surfaceVariant,
34
+ width: LAYOUT.ITEM_SIZE,
35
+ height: LAYOUT.ITEM_SIZE,
36
+ borderRadius: LAYOUT.ITEM_SIZE / 2,
37
+ borderWidth: 1,
38
+ borderColor: tokens.colors.border,
39
+ },
40
+ ]}
41
+ >
42
+ <AtomicIcon name={icon as any} size="lg" color="primary" />
43
+ </View>
44
+ <AtomicText
45
+ type="labelSmall"
46
+ style={[styles.label, { color: tokens.colors.textPrimary }]}
47
+ >
48
+ {label}
49
+ </AtomicText>
50
+ </TouchableOpacity>
51
+ );
52
+ };
53
+
54
+ const styles = StyleSheet.create({
55
+ container: {
56
+ alignItems: "center",
57
+ gap: 6,
58
+ width: 90,
59
+ },
60
+ iconContainer: {
61
+ justifyContent: "center",
62
+ alignItems: "center",
63
+ },
64
+ label: {
65
+ fontSize: 11,
66
+ fontWeight: "500",
67
+ textAlign: "center",
68
+ },
69
+ });
@@ -0,0 +1,73 @@
1
+ import { ViewStyle } from "react-native";
2
+
3
+ export const LAYOUT = {
4
+ ITEM_SIZE: 52,
5
+ CLOSE_BUTTON_SIZE: 48,
6
+ } as const;
7
+
8
+ export const ROW_CONFIG = {
9
+ TOP_RADIUS: 80,
10
+ TOP_START_ANGLE: -135,
11
+ TOP_END_ANGLE: -45,
12
+ BOTTOM_OFFSET_X: 160,
13
+ BOTTOM_Y: 60,
14
+ CENTER_Y: 150,
15
+ } as const;
16
+
17
+ export const ARC_BACKGROUND = {
18
+ WIDTH: 450,
19
+ HEIGHT: 280,
20
+ BORDER_RADIUS_TOP: 225,
21
+ } as const;
22
+
23
+ export const OVERLAY = {
24
+ PADDING_BOTTOM_OFFSET: 0,
25
+ } as const;
26
+
27
+ const CENTER_X = ARC_BACKGROUND.WIDTH / 2;
28
+
29
+ function toRadians(degrees: number): number {
30
+ return (degrees * Math.PI) / 180;
31
+ }
32
+
33
+ export function getTopRowPosition(
34
+ index: number,
35
+ totalInRow: number
36
+ ): ViewStyle {
37
+ if (totalInRow <= 1) {
38
+ const radian = toRadians(-90);
39
+ const x = ROW_CONFIG.TOP_RADIUS * Math.cos(radian);
40
+ const y = ROW_CONFIG.TOP_RADIUS * Math.sin(radian);
41
+
42
+ return {
43
+ position: "absolute",
44
+ left: CENTER_X + x - LAYOUT.ITEM_SIZE / 2,
45
+ top: ROW_CONFIG.CENTER_Y + y - LAYOUT.ITEM_SIZE / 2,
46
+ };
47
+ }
48
+
49
+ const arcSpan = ROW_CONFIG.TOP_END_ANGLE - ROW_CONFIG.TOP_START_ANGLE;
50
+ const step = arcSpan / (totalInRow - 1);
51
+ const angle = ROW_CONFIG.TOP_START_ANGLE + step * index;
52
+ const radian = toRadians(angle);
53
+
54
+ const x = ROW_CONFIG.TOP_RADIUS * Math.cos(radian);
55
+ const y = ROW_CONFIG.TOP_RADIUS * Math.sin(radian);
56
+
57
+ return {
58
+ position: "absolute",
59
+ left: CENTER_X + x - LAYOUT.ITEM_SIZE / 2,
60
+ top: ROW_CONFIG.CENTER_Y + y - LAYOUT.ITEM_SIZE / 2,
61
+ };
62
+ }
63
+
64
+ export function getBottomRowPosition(side: "left" | "right"): ViewStyle {
65
+ const offsetX =
66
+ side === "left" ? -ROW_CONFIG.BOTTOM_OFFSET_X : ROW_CONFIG.BOTTOM_OFFSET_X;
67
+
68
+ return {
69
+ position: "absolute",
70
+ left: CENTER_X + offsetX - LAYOUT.ITEM_SIZE / 2,
71
+ top: ROW_CONFIG.CENTER_Y + ROW_CONFIG.BOTTOM_Y - LAYOUT.ITEM_SIZE / 2,
72
+ };
73
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./CircularMenu";
2
+ export * from "./CircularMenuItem";
3
+ export * from "./CircularMenuBackground";
4
+ export * from "./CircularMenuCloseButton";
@@ -64,3 +64,4 @@ export * from './hero-section';
64
64
  export * from './info-grid';
65
65
 
66
66
 
67
+ export * from './circular-menu';
@@ -0,0 +1,13 @@
1
+ import { NavigationContainerRef } from "@react-navigation/native";
2
+
3
+ /**
4
+ * NavigationContainer Component
5
+ *
6
+ * Wrapper around React Navigation's NavigationContainerRef
7
+ * Provides navigation support to applications.
8
+ */
9
+ export const NavigationContainer: React.FC<{
10
+ children: React.ReactNode;
11
+ }> = ({ children }) => {
12
+ return <NavigationContainerRef>{children}</NavigationContainerRef>;
13
+ };
@@ -0,0 +1,4 @@
1
+ export { NavigationContainer } from "./NavigationContainer";
2
+ export { NavigationHeader } from "./NavigationHeader";
3
+ export { TabLabel } from "./TabLabel";
4
+ export { FabButton } from "./FabButton";
@@ -3,6 +3,7 @@ export { createStackNavigator } from "./createStackNavigator";
3
3
  export { TabsNavigator, type TabsNavigatorProps } from "./TabsNavigator";
4
4
  export { StackNavigator, type StackNavigatorProps } from "./StackNavigator";
5
5
  export { FabButton, type FabButtonProps } from "./components/FabButton";
6
+ export { NavigationContainer } from "./components/NavigationContainer";
6
7
 
7
8
  export type {
8
9
  TabScreen,
@@ -34,7 +35,7 @@ export type { NavigationCleanup } from "./utils/NavigationCleanup";
34
35
  // Navigation Utilities
35
36
  export { AppNavigation } from "./utils/AppNavigation";
36
37
  export { TabLabel, type TabLabelProps } from "./components/TabLabel";
37
- export * from './components/NavigationHeader';
38
+ export * from "./components/NavigationHeader";
38
39
  export { useTabBarStyles, type TabBarConfig } from "./hooks/useTabBarStyles";
39
40
  export { useTabConfig, type UseTabConfigProps } from "./hooks/useTabConfig";
40
41
  export { useAppNavigation } from "./hooks/useAppNavigation";