@idealyst/navigation 1.0.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.
@@ -0,0 +1,55 @@
1
+ // Simple style objects for the GeneralLayout
2
+ export const generalLayoutStyles = {
3
+ container: {
4
+ display: 'flex',
5
+ width: '100%',
6
+ height: '100vh',
7
+ overflow: 'hidden',
8
+ } as const,
9
+
10
+ headerContainer: {
11
+ display: 'flex',
12
+ flexDirection: 'row',
13
+ width: '100%',
14
+ zIndex: 100,
15
+ } as const,
16
+
17
+ bodyContainer: {
18
+ display: 'flex',
19
+ flexDirection: 'row',
20
+ flex: 1,
21
+ overflow: 'hidden',
22
+ } as const,
23
+
24
+ sidebar: {
25
+ display: 'flex',
26
+ flexDirection: 'column',
27
+ transition: 'width 0.3s ease-in-out',
28
+ overflow: 'hidden',
29
+ zIndex: 50,
30
+ } as const,
31
+
32
+ sidebarContent: {
33
+ flex: 1,
34
+ overflow: 'auto',
35
+ } as const,
36
+
37
+ sidebarToggle: {
38
+ alignSelf: 'flex-end',
39
+ margin: '8px',
40
+ marginBottom: '16px',
41
+ } as const,
42
+
43
+ mainContent: {
44
+ flex: 1,
45
+ display: 'flex',
46
+ flexDirection: 'column',
47
+ overflow: 'auto',
48
+ order: 2,
49
+ } as const,
50
+
51
+ contentArea: {
52
+ flex: 1,
53
+ overflow: 'auto',
54
+ } as const,
55
+ };
@@ -0,0 +1,144 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { View, Button, Text } from '@idealyst/components';
3
+ import { GeneralLayoutProps, SidebarConfig, HeaderConfig } from './types';
4
+ import { generalLayoutStyles } from './GeneralLayout.styles';
5
+
6
+ const GeneralLayout: React.FC<GeneralLayoutProps> = ({
7
+ children,
8
+ sidebar = {},
9
+ header = {},
10
+ style,
11
+ testID,
12
+ }) => {
13
+ // Default sidebar configuration
14
+ const sidebarConfig: Required<SidebarConfig> = {
15
+ enabled: true,
16
+ initiallyExpanded: true,
17
+ expandedWidth: 280,
18
+ collapsedWidth: 60,
19
+ collapsible: true,
20
+ position: 'left',
21
+ content: null,
22
+ style: undefined,
23
+ ...sidebar,
24
+ };
25
+
26
+ // Default header configuration
27
+ const headerConfig: Required<HeaderConfig> = {
28
+ enabled: true,
29
+ height: 64,
30
+ content: null,
31
+ style: undefined,
32
+ ...header,
33
+ };
34
+
35
+ // State for sidebar expansion
36
+ const [isSidebarExpanded, setIsSidebarExpanded] = useState(
37
+ sidebarConfig.initiallyExpanded
38
+ );
39
+
40
+ // Toggle sidebar expansion
41
+ const toggleSidebar = useCallback(() => {
42
+ if (sidebarConfig.collapsible) {
43
+ setIsSidebarExpanded((prev) => !prev);
44
+ }
45
+ }, [sidebarConfig.collapsible]);
46
+
47
+ // Calculate sidebar width based on expanded state
48
+ const sidebarWidth = isSidebarExpanded
49
+ ? sidebarConfig.expandedWidth
50
+ : sidebarConfig.collapsedWidth;
51
+
52
+ // Create dynamic styles for sidebar width
53
+ const sidebarDynamicStyles = {
54
+ width: sidebarWidth,
55
+ minWidth: sidebarWidth,
56
+ maxWidth: sidebarWidth,
57
+ order: sidebarConfig.position === 'right' ? 3 : 1,
58
+ };
59
+
60
+ // Create dynamic styles for header height
61
+ const headerDynamicStyles = {
62
+ height: headerConfig.height,
63
+ minHeight: headerConfig.height,
64
+ };
65
+
66
+ return (
67
+ <View
68
+ background="transparent"
69
+ style={[
70
+ generalLayoutStyles.container,
71
+ style,
72
+ ]}
73
+ testID={testID}
74
+ >
75
+ {/* Header */}
76
+ {headerConfig.enabled && (
77
+ <View
78
+ background="surface"
79
+ border="thin"
80
+ spacing="md"
81
+ style={[
82
+ generalLayoutStyles.headerContainer,
83
+ headerDynamicStyles,
84
+ headerConfig.style,
85
+ ]}
86
+ >
87
+ {headerConfig.content}
88
+ </View>
89
+ )}
90
+
91
+ {/* Body Container */}
92
+ <View style={generalLayoutStyles.bodyContainer}>
93
+ {/* Sidebar */}
94
+ {sidebarConfig.enabled && (
95
+ <View
96
+ background="surface"
97
+ border="thin"
98
+ spacing="md"
99
+ style={[
100
+ generalLayoutStyles.sidebar,
101
+ sidebarDynamicStyles,
102
+ sidebarConfig.style,
103
+ ]}
104
+ >
105
+ {/* Sidebar Toggle Button - Positioned within sidebar using flexbox */}
106
+ {sidebarConfig.collapsible && (
107
+ <Button
108
+ onPress={toggleSidebar}
109
+ variant="outlined"
110
+ size="small"
111
+ style={[
112
+ generalLayoutStyles.sidebarToggle,
113
+ // Align toggle button based on sidebar position
114
+ { alignSelf: sidebarConfig.position === 'right' ? 'flex-start' : 'flex-end' }
115
+ ]}
116
+ >
117
+ {sidebarConfig.position === 'right'
118
+ ? (isSidebarExpanded ? '→' : '←')
119
+ : (isSidebarExpanded ? '←' : '→')
120
+ }
121
+ </Button>
122
+ )}
123
+
124
+ {/* Sidebar Content */}
125
+ <View style={generalLayoutStyles.sidebarContent}>
126
+ {sidebarConfig.content}
127
+ </View>
128
+ </View>
129
+ )}
130
+
131
+ {/* Main Content Area */}
132
+ <View style={generalLayoutStyles.mainContent}>
133
+ <View
134
+ style={generalLayoutStyles.contentArea}
135
+ >
136
+ {children}
137
+ </View>
138
+ </View>
139
+ </View>
140
+ </View>
141
+ );
142
+ };
143
+
144
+ export default GeneralLayout;
@@ -0,0 +1,171 @@
1
+ # GeneralLayout
2
+
3
+ A flexible layout component that provides a sidebar and header for web applications using the @idealyst/components library.
4
+
5
+ ## Features
6
+
7
+ - **Collapsible Sidebar**: Expandable and collapsible sidebar with smooth animations
8
+ - **Configurable Header**: Optional header with customizable content and height
9
+ - **Responsive Design**: Adapts to different screen sizes
10
+ - **Theme Integration**: Uses @idealyst/components theme system
11
+ - **Flexible Configuration**: Both sidebar and header can be disabled
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { GeneralLayout } from '@idealyst/navigation';
17
+ import { View, Text } from '@idealyst/components';
18
+
19
+ function App() {
20
+ return (
21
+ <GeneralLayout
22
+ header={{
23
+ enabled: true,
24
+ height: 64,
25
+ content: <Text>My Header Content</Text>,
26
+ }}
27
+ sidebar={{
28
+ enabled: true,
29
+ initiallyExpanded: true,
30
+ expandedWidth: 280,
31
+ collapsedWidth: 60,
32
+ collapsible: true,
33
+ position: 'left',
34
+ content: <Text>My Sidebar Content</Text>,
35
+ }}
36
+ >
37
+ <View>
38
+ <Text>Main content goes here</Text>
39
+ </View>
40
+ </GeneralLayout>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ## Props
46
+
47
+ ### GeneralLayoutProps
48
+
49
+ | Prop | Type | Default | Description |
50
+ |------|------|---------|-------------|
51
+ | `children` | `ReactNode` | - | The main content to display |
52
+ | `sidebar` | `SidebarConfig` | `{}` | Sidebar configuration object |
53
+ | `header` | `HeaderConfig` | `{}` | Header configuration object |
54
+ | `style` | `any` | - | Additional styles for the layout container |
55
+ | `testID` | `string` | - | Test ID for testing |
56
+
57
+ ### SidebarConfig
58
+
59
+ | Prop | Type | Default | Description |
60
+ |------|------|---------|-------------|
61
+ | `enabled` | `boolean` | `true` | Whether the sidebar is enabled |
62
+ | `initiallyExpanded` | `boolean` | `true` | Whether the sidebar is initially expanded |
63
+ | `expandedWidth` | `number` | `280` | Width of the sidebar when expanded (px) |
64
+ | `collapsedWidth` | `number` | `60` | Width of the sidebar when collapsed (px) |
65
+ | `collapsible` | `boolean` | `true` | Whether the sidebar can be collapsed |
66
+ | `position` | `'left' \| 'right'` | `'left'` | Position of the sidebar |
67
+ | `content` | `ReactNode` | - | Content to display in the sidebar |
68
+ | `style` | `any` | - | Custom styles for the sidebar |
69
+
70
+ ### HeaderConfig
71
+
72
+ | Prop | Type | Default | Description |
73
+ |------|------|---------|-------------|
74
+ | `enabled` | `boolean` | `true` | Whether the header is enabled |
75
+ | `height` | `number` | `64` | Height of the header (px) |
76
+ | `content` | `ReactNode` | - | Content to display in the header |
77
+ | `style` | `any` | - | Custom styles for the header |
78
+
79
+ ## Examples
80
+
81
+ ### Basic Layout with Header and Sidebar
82
+
83
+ ```tsx
84
+ <GeneralLayout
85
+ header={{
86
+ content: <Text>My App</Text>,
87
+ }}
88
+ sidebar={{
89
+ content: (
90
+ <View>
91
+ <Text>Home</Text>
92
+ <Text>About</Text>
93
+ </View>
94
+ ),
95
+ }}
96
+ >
97
+ <View>
98
+ <Text>Main content</Text>
99
+ </View>
100
+ </GeneralLayout>
101
+ ```
102
+
103
+ ### Layout with Disabled Header
104
+
105
+ ```tsx
106
+ <GeneralLayout
107
+ header={{ enabled: false }}
108
+ sidebar={{
109
+ content: <Text>Navigation items</Text>,
110
+ }}
111
+ >
112
+ <View>
113
+ <Text>Main content</Text>
114
+ </View>
115
+ </GeneralLayout>
116
+ ```
117
+
118
+ ### Layout with Right Sidebar
119
+
120
+ ```tsx
121
+ <GeneralLayout
122
+ sidebar={{
123
+ position: 'right',
124
+ content: <Text>Right sidebar content</Text>,
125
+ }}
126
+ >
127
+ <View>
128
+ <Text>Main content</Text>
129
+ </View>
130
+ </GeneralLayout>
131
+ ```
132
+
133
+ ### Layout with Custom Dimensions
134
+
135
+ ```tsx
136
+ <GeneralLayout
137
+ header={{
138
+ height: 80,
139
+ content: <Text>Tall header</Text>,
140
+ }}
141
+ sidebar={{
142
+ expandedWidth: 320,
143
+ collapsedWidth: 80,
144
+ content: <Text>Wide sidebar</Text>,
145
+ }}
146
+ >
147
+ <View>
148
+ <Text>Main content</Text>
149
+ </View>
150
+ </GeneralLayout>
151
+ ```
152
+
153
+ ## Styling
154
+
155
+ The GeneralLayout uses the @idealyst/components theme system for consistent styling:
156
+
157
+ - **Background**: Uses `background="surface"` for sidebar and header
158
+ - **Borders**: Uses `border="thin"` for visual separation
159
+ - **Spacing**: Uses `spacing="md"` for consistent padding
160
+ - **Theme Integration**: Automatically inherits theme colors and spacing
161
+
162
+ ## Platform Support
163
+
164
+ Currently, the GeneralLayout only supports web platforms. React Native support may be added in future versions.
165
+
166
+ ## Implementation Notes
167
+
168
+ - The layout uses CSS flexbox for responsive behavior
169
+ - Sidebar transitions use CSS animations for smooth expand/collapse
170
+ - Toggle button automatically adjusts position based on sidebar location
171
+ - Theme colors and spacing are applied through the View component variants
@@ -0,0 +1,2 @@
1
+ export { default as GeneralLayout } from './GeneralLayout';
2
+ export type { GeneralLayoutProps, SidebarConfig, HeaderConfig } from './types';
@@ -0,0 +1,99 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ export interface SidebarConfig {
4
+ /**
5
+ * Whether the sidebar is enabled
6
+ */
7
+ enabled?: boolean;
8
+
9
+ /**
10
+ * Whether the sidebar is initially expanded
11
+ */
12
+ initiallyExpanded?: boolean;
13
+
14
+ /**
15
+ * Width of the sidebar when expanded
16
+ */
17
+ expandedWidth?: number;
18
+
19
+ /**
20
+ * Width of the sidebar when collapsed
21
+ */
22
+ collapsedWidth?: number;
23
+
24
+ /**
25
+ * Whether the sidebar can be collapsed
26
+ */
27
+ collapsible?: boolean;
28
+
29
+ /**
30
+ * Position of the sidebar
31
+ */
32
+ position?: 'left' | 'right';
33
+
34
+ /**
35
+ * Content to display in the sidebar
36
+ */
37
+ content?: ReactNode;
38
+
39
+ /**
40
+ * Custom styles for the sidebar
41
+ */
42
+ style?: any;
43
+ }
44
+
45
+ export interface HeaderConfig {
46
+ /**
47
+ * Whether the header is enabled
48
+ */
49
+ enabled?: boolean;
50
+
51
+ /**
52
+ * Height of the header
53
+ */
54
+ height?: number;
55
+
56
+ /**
57
+ * Content to display in the header
58
+ */
59
+ content?: ReactNode;
60
+
61
+ /**
62
+ * Custom styles for the header
63
+ */
64
+ style?: any;
65
+ }
66
+
67
+ export interface GeneralLayoutProps {
68
+ /**
69
+ * The main content to display
70
+ */
71
+ children?: ReactNode;
72
+
73
+ /**
74
+ * Sidebar configuration
75
+ */
76
+ sidebar?: SidebarConfig;
77
+
78
+ /**
79
+ * Header configuration
80
+ */
81
+ header?: HeaderConfig;
82
+
83
+ /**
84
+ * Additional styles for the layout container
85
+ */
86
+ style?: any;
87
+
88
+ /**
89
+ * Test ID for testing
90
+ */
91
+ testID?: string;
92
+ }
93
+
94
+ export interface GeneralLayoutState {
95
+ /**
96
+ * Whether the sidebar is currently expanded
97
+ */
98
+ isSidebarExpanded: boolean;
99
+ }
@@ -0,0 +1 @@
1
+ export * from './GeneralLayout';
@@ -0,0 +1,2 @@
1
+ export * from './router.native';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export * from './router.web';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export * from './router.web';
2
+ export * from './types';
@@ -0,0 +1,90 @@
1
+ import React from "react";
2
+
3
+ import { createNativeStackNavigator } from "@react-navigation/native-stack";
4
+ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
5
+ import { createDrawerNavigator } from "@react-navigation/drawer";
6
+
7
+ import { RouteParam } from "./types"
8
+ import { TypedNavigator } from "@react-navigation/native";
9
+
10
+ export const buildRouter = (routeParam: RouteParam, path: string = '') => {
11
+ return () => buildNativeRouter(routeParam, path)
12
+ }
13
+
14
+ /**
15
+ * Create the router supporting React Navigation
16
+ * @param routeParam
17
+ * @param path
18
+ * @param LastNavigator
19
+ * @returns
20
+ */
21
+ const buildNativeRouter = (routeParam: RouteParam, path: string = '', LastNavigator?: TypedNavigator<any>): React.ReactElement => {
22
+ const nextPath = (routeParam.path ? path + routeParam.path : path) || '';
23
+ const type = routeParam.layout?.type;
24
+ console.log('Registered routes', nextPath, routeParam.routes);
25
+ switch (type) {
26
+ case 'stack':
27
+ const Stack = createNativeStackNavigator();
28
+ return (
29
+ <Stack.Navigator
30
+ screenOptions={{
31
+ // Disable screen optimization to ensure theme updates
32
+ freezeOnBlur: false,
33
+ }}
34
+ >
35
+ <Stack.Screen name={nextPath} component={routeParam.component} />
36
+ {routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, Stack))}
37
+ </Stack.Navigator>
38
+ )
39
+ case 'tab':
40
+ const Tab = createBottomTabNavigator();
41
+ return (
42
+ <Tab.Navigator
43
+ screenOptions={{
44
+ // Disable screen optimization to ensure theme updates
45
+ lazy: false,
46
+ freezeOnBlur: false,
47
+ }}
48
+ >
49
+ <Tab.Screen name={nextPath} component={routeParam.component} />
50
+ {routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, Tab))}
51
+ </Tab.Navigator>
52
+ )
53
+ case 'drawer':
54
+ const Drawer = createDrawerNavigator();
55
+ return (
56
+ <Drawer.Navigator
57
+ screenOptions={{
58
+ // Disable screen optimization to ensure theme updates
59
+ lazy: false,
60
+ freezeOnBlur: false,
61
+ }}
62
+ >
63
+ <Drawer.Screen name={nextPath} component={routeParam.component} />
64
+ {routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, Drawer))}
65
+ </Drawer.Navigator>
66
+ )
67
+ case 'modal':
68
+ if (!LastNavigator) {
69
+ throw new Error('LastNavigator is required for modal layout');
70
+ }
71
+ return (
72
+ <>
73
+ <LastNavigator.Screen options={{ headerShown: false, presentation: 'modal' }} name={nextPath} component={routeParam.component} />
74
+ {routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, LastNavigator))}
75
+ </>
76
+ )
77
+ case undefined:
78
+ if (!LastNavigator) {
79
+ throw new Error('LastNavigator is required for undefined layout');
80
+ }
81
+ return (
82
+ <>
83
+ <LastNavigator.Screen name={nextPath} component={routeParam.component} />
84
+ {routeParam.routes?.map((route) => buildNativeRouter(route, nextPath, LastNavigator))}
85
+ </>
86
+ )
87
+ default:
88
+ throw new Error(`Unknown layout type: ${type}`);
89
+ }
90
+ }
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+ import { Routes, Route, Outlet } from "react-router-dom";
3
+ import { RouteParam } from "./types";
4
+
5
+ export const buildRouter = (routeParam: RouteParam, path: string = '') => {
6
+ return () => (
7
+ <Routes>
8
+ {buildWebRoutes(routeParam, path)}
9
+ </Routes>
10
+ );
11
+ };
12
+
13
+ /**
14
+ * Create React Router routes from RouteParam configuration
15
+ * @param routeParam The route parameter configuration
16
+ * @param parentPath The parent path for nested routes
17
+ * @returns Array of React Router Route elements
18
+ */
19
+ const buildWebRoutes = (routeParam: RouteParam, parentPath: string = ''): React.ReactElement[] => {
20
+ const routes: React.ReactElement[] = [];
21
+ const currentPath = routeParam.path ? `${parentPath}${routeParam.path}` : parentPath;
22
+
23
+ // Handle layout wrapping
24
+ const LayoutComponent = routeParam.layout?.component;
25
+ const RouteComponent = routeParam.component;
26
+
27
+ if (LayoutComponent && routeParam.routes) {
28
+ // Create a wrapper component that renders the layout with Outlet
29
+ const LayoutWrapper: React.FC = () => (
30
+ <LayoutComponent>
31
+ <Outlet />
32
+ </LayoutComponent>
33
+ );
34
+
35
+ // Create parent route with layout
36
+ const layoutRoute = (
37
+ <Route
38
+ key={`layout-${currentPath || 'root'}`}
39
+ path={currentPath || '/'}
40
+ element={<LayoutWrapper />}
41
+ >
42
+ {/* Add index route for the main component */}
43
+ <Route
44
+ index
45
+ element={<RouteComponent />}
46
+ />
47
+ {/* Add nested routes */}
48
+ {routeParam.routes.reduce((acc, nestedRoute) => {
49
+ return acc.concat(buildWebRoutes(nestedRoute, currentPath));
50
+ }, [] as React.ReactElement[])}
51
+ </Route>
52
+ );
53
+
54
+ routes.push(layoutRoute);
55
+ } else {
56
+ // Simple route without layout
57
+ routes.push(
58
+ <Route
59
+ key={currentPath || 'root'}
60
+ path={currentPath || '/'}
61
+ element={<RouteComponent />}
62
+ />
63
+ );
64
+
65
+ // Handle nested routes without layout
66
+ if (routeParam.routes) {
67
+ routeParam.routes.forEach(nestedRoute => {
68
+ routes.push(...buildWebRoutes(nestedRoute, currentPath));
69
+ });
70
+ }
71
+ }
72
+
73
+ return routes;
74
+ };
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+
3
+ export type RouteParam = {
4
+ path?: string;
5
+ routes?: RouteParam[];
6
+ component: React.ComponentType;
7
+ layout?: LayoutParam;
8
+ }
9
+
10
+ export type LayoutType = 'stack' | 'tab' | 'drawer' | 'modal';
11
+
12
+ export type LayoutParam = {
13
+ type: LayoutType;
14
+ component?: React.ComponentType<{ children?: React.ReactNode }>;
15
+ }