@umituz/react-native-design-system 2.6.62 → 2.6.64
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 +1 -1
- package/src/atoms/AtomicIcon.tsx +2 -6
- package/src/atoms/AtomicIcon.types.ts +5 -0
- package/src/atoms/AtomicInput.tsx +34 -154
- package/src/atoms/AtomicPicker.tsx +31 -123
- package/src/atoms/index.ts +6 -4
- package/src/atoms/input/components/InputHelper.tsx +49 -0
- package/src/atoms/input/components/InputIcon.tsx +44 -0
- package/src/atoms/input/components/InputLabel.tsx +20 -0
- package/src/atoms/input/styles/inputStylesHelper.ts +1 -1
- package/src/atoms/input/types.ts +72 -0
- package/src/atoms/picker/hooks/usePickerState.ts +139 -0
- package/src/exports/atoms.ts +69 -0
- package/src/exports/device.ts +58 -0
- package/src/exports/layouts.ts +19 -0
- package/src/exports/molecules.ts +166 -0
- package/src/exports/organisms.ts +9 -0
- package/src/exports/responsive.ts +36 -0
- package/src/exports/safe-area.ts +6 -0
- package/src/exports/theme.ts +47 -0
- package/src/exports/typography.ts +22 -0
- package/src/exports/utilities.ts +6 -0
- package/src/exports/variants.ts +22 -0
- package/src/index.ts +11 -417
- package/src/molecules/avatar/Avatar.constants.ts +103 -0
- package/src/molecules/avatar/Avatar.types.ts +64 -0
- package/src/molecules/avatar/Avatar.utils.ts +8 -160
- package/src/molecules/calendar/index.ts +4 -9
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +103 -302
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts.bak +116 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.types.ts +64 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.utils.ts +56 -0
- package/src/molecules/calendar/infrastructure/storage/EventActions.ts +140 -0
- package/src/molecules/calendar/infrastructure/storage/NavigationActions.ts +118 -0
- package/src/molecules/calendar/infrastructure/stores/storageAdapter.ts +34 -0
- package/src/molecules/calendar/infrastructure/stores/useCalendarEvents.ts +168 -0
- package/src/molecules/calendar/infrastructure/stores/useCalendarNavigation.ts +47 -0
- package/src/molecules/calendar/infrastructure/stores/useCalendarView.ts +24 -0
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +7 -11
- package/src/responsive/compute/computeDeviceInfo.ts +22 -0
- package/src/responsive/compute/computeResponsivePositioning.ts +42 -0
- package/src/responsive/compute/computeResponsiveSizes.ts +48 -0
- package/src/responsive/padding/paddingUtils.ts +65 -0
- package/src/responsive/positioning/positioningUtils.ts +61 -0
- package/src/responsive/responsiveLayout.ts +11 -264
- package/src/responsive/screen/screenLayoutConfig.ts +38 -0
- package/src/responsive/tabbar/tabBarConfig.ts +88 -0
- package/src/responsive/types/responsiveTypes.ts +69 -0
- package/src/responsive/useResponsive.ts +69 -158
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Navigation Store
|
|
3
|
+
* Manages date navigation state (no persistence needed)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand';
|
|
7
|
+
|
|
8
|
+
interface CalendarNavigationState {
|
|
9
|
+
readonly selectedDate: Date;
|
|
10
|
+
readonly currentMonth: Date;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface CalendarNavigationActions {
|
|
14
|
+
readonly setSelectedDate: (date: Date) => void;
|
|
15
|
+
readonly goToToday: () => void;
|
|
16
|
+
readonly navigateMonth: (direction: 'prev' | 'next') => void;
|
|
17
|
+
readonly setCurrentMonth: (date: Date) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type CalendarNavigationStore = CalendarNavigationState & CalendarNavigationActions;
|
|
21
|
+
|
|
22
|
+
export const useCalendarNavigation = create<CalendarNavigationStore>()((set) => ({
|
|
23
|
+
selectedDate: new Date(),
|
|
24
|
+
currentMonth: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
|
|
25
|
+
|
|
26
|
+
setSelectedDate: (date) => set({ selectedDate: date }),
|
|
27
|
+
|
|
28
|
+
goToToday: () => {
|
|
29
|
+
const today = new Date();
|
|
30
|
+
set({
|
|
31
|
+
selectedDate: today,
|
|
32
|
+
currentMonth: new Date(today.getFullYear(), today.getMonth(), 1),
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
navigateMonth: (direction) => {
|
|
37
|
+
set((state) => {
|
|
38
|
+
const { currentMonth } = state;
|
|
39
|
+
const newMonth = direction === 'next' ? currentMonth.getMonth() + 1 : currentMonth.getMonth() - 1;
|
|
40
|
+
return {
|
|
41
|
+
currentMonth: new Date(currentMonth.getFullYear(), newMonth, 1),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
setCurrentMonth: (date) => set({ currentMonth: new Date(date.getFullYear(), date.getMonth(), 1) }),
|
|
47
|
+
}));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar View Store
|
|
3
|
+
* Manages calendar view mode (no persistence needed)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand';
|
|
7
|
+
|
|
8
|
+
export type CalendarViewMode = 'month' | 'week' | 'day' | 'list';
|
|
9
|
+
|
|
10
|
+
interface CalendarViewState {
|
|
11
|
+
readonly viewMode: CalendarViewMode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface CalendarViewActions {
|
|
15
|
+
readonly setViewMode: (mode: CalendarViewMode) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CalendarViewStore = CalendarViewState & CalendarViewActions;
|
|
19
|
+
|
|
20
|
+
export const useCalendarView = create<CalendarViewStore>()((set) => ({
|
|
21
|
+
viewMode: 'month',
|
|
22
|
+
|
|
23
|
+
setViewMode: (mode) => set({ viewMode: mode }),
|
|
24
|
+
}));
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
|
|
29
29
|
import { useMemo, useEffect } from 'react';
|
|
30
30
|
import { useCalendarStore, type CalendarViewMode } from '../../infrastructure/storage/CalendarStore';
|
|
31
|
-
import { CalendarService } from '../../infrastructure/services/CalendarService';
|
|
32
31
|
import type { CalendarDay } from '../../domain/entities/CalendarDay.entity';
|
|
33
32
|
import type { CalendarEvent } from '../../domain/entities/CalendarEvent.entity';
|
|
34
33
|
|
|
@@ -62,7 +61,6 @@ export interface UseCalendarReturn {
|
|
|
62
61
|
setSelectedDate: (date: Date) => void;
|
|
63
62
|
goToToday: () => void;
|
|
64
63
|
navigateMonth: (direction: 'prev' | 'next') => void;
|
|
65
|
-
navigateWeek: (direction: 'prev' | 'next') => void;
|
|
66
64
|
setCurrentMonth: (date: Date) => void;
|
|
67
65
|
setViewMode: (mode: CalendarViewMode) => void;
|
|
68
66
|
getEventsForDate: (date: Date) => CalendarEvent[];
|
|
@@ -75,7 +73,7 @@ export interface UseCalendarReturn {
|
|
|
75
73
|
/**
|
|
76
74
|
* Main calendar hook
|
|
77
75
|
*/
|
|
78
|
-
export const
|
|
76
|
+
export const useCalendarPresentation = (): UseCalendarReturn => {
|
|
79
77
|
const store = useCalendarStore();
|
|
80
78
|
const {
|
|
81
79
|
events,
|
|
@@ -92,13 +90,6 @@ export const useCalendar = (): UseCalendarReturn => {
|
|
|
92
90
|
actions.loadEvents();
|
|
93
91
|
}, []);
|
|
94
92
|
|
|
95
|
-
// Generate calendar days for current month
|
|
96
|
-
const days = useMemo(() => {
|
|
97
|
-
const year = currentMonth.getFullYear();
|
|
98
|
-
const month = currentMonth.getMonth();
|
|
99
|
-
return CalendarService.getMonthDays(year, month, events);
|
|
100
|
-
}, [currentMonth, events]);
|
|
101
|
-
|
|
102
93
|
// Get events for selected date
|
|
103
94
|
const selectedDateEvents = useMemo(() => {
|
|
104
95
|
return actions.getEventsForDate(selectedDate);
|
|
@@ -112,7 +103,7 @@ export const useCalendar = (): UseCalendarReturn => {
|
|
|
112
103
|
}, [currentMonth, events]);
|
|
113
104
|
|
|
114
105
|
return {
|
|
115
|
-
days,
|
|
106
|
+
days: store.days,
|
|
116
107
|
events,
|
|
117
108
|
selectedDate,
|
|
118
109
|
currentMonth,
|
|
@@ -181,3 +172,8 @@ export const useCalendarEvents = () => {
|
|
|
181
172
|
clearError,
|
|
182
173
|
};
|
|
183
174
|
};
|
|
175
|
+
|
|
176
|
+
// Re-export for convenience
|
|
177
|
+
export { useCalendar, useCalendarStore } from '../../infrastructure/storage/CalendarStore';
|
|
178
|
+
export type { CalendarViewMode } from '../../infrastructure/storage/CalendarStore';
|
|
179
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Info Computation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isSmallPhone, isTablet, isLandscape, getDeviceType, getSpacingMultiplier } from '../../device/detection';
|
|
6
|
+
import type { DeviceType } from '../../device/detection';
|
|
7
|
+
|
|
8
|
+
export interface ComputedDeviceInfo {
|
|
9
|
+
readonly isSmallDevice: boolean;
|
|
10
|
+
readonly isTabletDevice: boolean;
|
|
11
|
+
readonly isLandscapeDevice: boolean;
|
|
12
|
+
readonly deviceType: DeviceType;
|
|
13
|
+
readonly spacingMultiplier: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const computeDeviceInfo = (): ComputedDeviceInfo => ({
|
|
17
|
+
isSmallDevice: isSmallPhone(),
|
|
18
|
+
isTabletDevice: isTablet(),
|
|
19
|
+
isLandscapeDevice: isLandscape(),
|
|
20
|
+
deviceType: getDeviceType(),
|
|
21
|
+
spacingMultiplier: getSpacingMultiplier(),
|
|
22
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Positioning Computation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getResponsiveHorizontalPadding,
|
|
7
|
+
getResponsiveVerticalPadding,
|
|
8
|
+
getResponsiveBottomPosition,
|
|
9
|
+
getResponsiveFABPosition,
|
|
10
|
+
getScreenLayoutConfig,
|
|
11
|
+
getResponsiveTabBarConfig,
|
|
12
|
+
getResponsiveModalLayout,
|
|
13
|
+
getResponsiveBottomSheetLayout,
|
|
14
|
+
getResponsiveDialogLayout,
|
|
15
|
+
} from '../responsive';
|
|
16
|
+
import type { ResponsiveModalLayout, ResponsiveBottomSheetLayout, ResponsiveDialogLayout, ResponsiveTabBarConfig, ScreenLayoutConfig } from '../responsive';
|
|
17
|
+
|
|
18
|
+
export interface ComputedResponsivePositioning {
|
|
19
|
+
readonly horizontalPadding: number;
|
|
20
|
+
readonly verticalPadding: number;
|
|
21
|
+
readonly bottomPosition: number;
|
|
22
|
+
readonly fabPosition: { readonly bottom: number; readonly right: number };
|
|
23
|
+
readonly screenLayoutConfig: ScreenLayoutConfig;
|
|
24
|
+
readonly tabBarConfig: ResponsiveTabBarConfig;
|
|
25
|
+
readonly modalLayout: ResponsiveModalLayout;
|
|
26
|
+
readonly bottomSheetLayout: ResponsiveBottomSheetLayout;
|
|
27
|
+
readonly dialogLayout: ResponsiveDialogLayout;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const computeResponsivePositioning = (
|
|
31
|
+
insets: { top: number; bottom: number; left: number; right: number }
|
|
32
|
+
): ComputedResponsivePositioning => ({
|
|
33
|
+
horizontalPadding: getResponsiveHorizontalPadding(undefined, insets),
|
|
34
|
+
verticalPadding: getResponsiveVerticalPadding(insets),
|
|
35
|
+
bottomPosition: getResponsiveBottomPosition(undefined, insets),
|
|
36
|
+
fabPosition: getResponsiveFABPosition(insets),
|
|
37
|
+
screenLayoutConfig: getScreenLayoutConfig(insets),
|
|
38
|
+
tabBarConfig: getResponsiveTabBarConfig(insets),
|
|
39
|
+
modalLayout: getResponsiveModalLayout(),
|
|
40
|
+
bottomSheetLayout: getResponsiveBottomSheetLayout(),
|
|
41
|
+
dialogLayout: getResponsiveDialogLayout(),
|
|
42
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Sizes Computation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getResponsiveLogoSize,
|
|
7
|
+
getResponsiveInputHeight,
|
|
8
|
+
getResponsiveIconContainerSize,
|
|
9
|
+
getResponsiveMaxWidth,
|
|
10
|
+
getResponsiveModalMaxHeight,
|
|
11
|
+
getResponsiveMinModalHeight,
|
|
12
|
+
getResponsiveGridColumns,
|
|
13
|
+
} from '../responsive';
|
|
14
|
+
import { getMinTouchTarget } from '../platformConstants';
|
|
15
|
+
import type { ComputedDeviceInfo } from './computeDeviceInfo';
|
|
16
|
+
|
|
17
|
+
export interface ComputedResponsiveSizes {
|
|
18
|
+
readonly logoSize: number;
|
|
19
|
+
readonly inputHeight: number;
|
|
20
|
+
readonly iconContainerSize: number;
|
|
21
|
+
readonly maxContentWidth: number;
|
|
22
|
+
readonly minTouchTarget: number;
|
|
23
|
+
readonly modalMaxHeight: string;
|
|
24
|
+
readonly modalMinHeight: number;
|
|
25
|
+
readonly gridColumns: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const computeResponsiveSizes = (): ComputedResponsiveSizes => ({
|
|
29
|
+
logoSize: getResponsiveLogoSize(),
|
|
30
|
+
inputHeight: getResponsiveInputHeight(),
|
|
31
|
+
iconContainerSize: getResponsiveIconContainerSize(),
|
|
32
|
+
maxContentWidth: getResponsiveMaxWidth(),
|
|
33
|
+
minTouchTarget: getMinTouchTarget(),
|
|
34
|
+
modalMaxHeight: getResponsiveModalMaxHeight(),
|
|
35
|
+
modalMinHeight: getResponsiveMinModalHeight(),
|
|
36
|
+
gridColumns: getResponsiveGridColumns(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const computeOnboardingSizes = (
|
|
40
|
+
deviceInfo: ComputedDeviceInfo
|
|
41
|
+
) => ({
|
|
42
|
+
onboardingIconSize: getResponsiveIconContainerSize(64),
|
|
43
|
+
onboardingIconMarginTop: deviceInfo.spacingMultiplier * 24,
|
|
44
|
+
onboardingIconMarginBottom: deviceInfo.spacingMultiplier * 16,
|
|
45
|
+
onboardingTitleMarginBottom: deviceInfo.spacingMultiplier * 16,
|
|
46
|
+
onboardingDescriptionMarginTop: deviceInfo.spacingMultiplier * 12,
|
|
47
|
+
onboardingTextPadding: deviceInfo.spacingMultiplier * 20,
|
|
48
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Padding Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isTablet, isSmallPhone, getSpacingMultiplier } from '../../device/detection';
|
|
6
|
+
import { LAYOUT_CONSTANTS } from '../config';
|
|
7
|
+
import { validateNumber, validateSafeAreaInsets } from '../validation';
|
|
8
|
+
|
|
9
|
+
export const getResponsiveVerticalPadding = (
|
|
10
|
+
insets: { top?: number; bottom?: number } = { top: 0, bottom: 0 }
|
|
11
|
+
): number => {
|
|
12
|
+
try {
|
|
13
|
+
validateSafeAreaInsets(insets);
|
|
14
|
+
const { top = 0 } = insets;
|
|
15
|
+
const isTabletDevice = isTablet();
|
|
16
|
+
const isSmall = isSmallPhone();
|
|
17
|
+
const spacingMultiplier = getSpacingMultiplier();
|
|
18
|
+
|
|
19
|
+
// Base padding adjusted by device type
|
|
20
|
+
let basePadding: number = LAYOUT_CONSTANTS.VERTICAL_PADDING_STANDARD;
|
|
21
|
+
if (isTabletDevice) {
|
|
22
|
+
basePadding = LAYOUT_CONSTANTS.VERTICAL_PADDING_TABLET;
|
|
23
|
+
} else if (isSmall) {
|
|
24
|
+
basePadding = LAYOUT_CONSTANTS.VERTICAL_PADDING_SMALL;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Apply spacing multiplier for consistency
|
|
28
|
+
const adjustedPadding = basePadding * spacingMultiplier;
|
|
29
|
+
|
|
30
|
+
// Ensure minimum padding respects safe area
|
|
31
|
+
return Math.max(adjustedPadding, top > 0 ? 8 : adjustedPadding);
|
|
32
|
+
} catch {
|
|
33
|
+
return LAYOUT_CONSTANTS.VERTICAL_PADDING_STANDARD;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const getResponsiveHorizontalPadding = (
|
|
38
|
+
basePadding: number = LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
39
|
+
insets: { left?: number; right?: number } = { left: 0, right: 0 }
|
|
40
|
+
): number => {
|
|
41
|
+
try {
|
|
42
|
+
const validatedBasePadding = validateNumber(basePadding, 'basePadding', 0, 100);
|
|
43
|
+
validateSafeAreaInsets(insets);
|
|
44
|
+
|
|
45
|
+
const { left = 0, right = 0 } = insets;
|
|
46
|
+
const isTabletDevice = isTablet();
|
|
47
|
+
|
|
48
|
+
if (isTabletDevice) {
|
|
49
|
+
const tabletPadding = validatedBasePadding * LAYOUT_CONSTANTS.SPACING_MULTIPLIER_TABLET;
|
|
50
|
+
return Math.max(
|
|
51
|
+
tabletPadding,
|
|
52
|
+
left + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
53
|
+
right + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Math.max(
|
|
58
|
+
validatedBasePadding,
|
|
59
|
+
left + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET,
|
|
60
|
+
right + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET
|
|
61
|
+
);
|
|
62
|
+
} catch {
|
|
63
|
+
return 16;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Positioning Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isTablet } from '../../device/detection';
|
|
6
|
+
import { LAYOUT_CONSTANTS } from '../config';
|
|
7
|
+
import { validateNumber, validateSafeAreaInsets } from '../validation';
|
|
8
|
+
|
|
9
|
+
export const getResponsiveBottomPosition = (
|
|
10
|
+
basePosition: number = LAYOUT_CONSTANTS.BOTTOM_POSITION_BASE,
|
|
11
|
+
insets: { bottom?: number } = { bottom: 0 }
|
|
12
|
+
): number => {
|
|
13
|
+
try {
|
|
14
|
+
const validatedBasePosition = validateNumber(basePosition, 'basePosition', 0, 500);
|
|
15
|
+
validateSafeAreaInsets(insets);
|
|
16
|
+
|
|
17
|
+
const { bottom = 0 } = insets;
|
|
18
|
+
return Math.max(validatedBasePosition, bottom + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET);
|
|
19
|
+
} catch {
|
|
20
|
+
return 32;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getResponsiveFABPosition = (
|
|
25
|
+
insets: { bottom?: number; right?: number } = { bottom: 0, right: 0 }
|
|
26
|
+
): { bottom: number; right: number } => {
|
|
27
|
+
try {
|
|
28
|
+
validateSafeAreaInsets(insets);
|
|
29
|
+
const { bottom = 0, right = 0 } = insets;
|
|
30
|
+
const isTabletDevice = isTablet();
|
|
31
|
+
|
|
32
|
+
if (isTabletDevice) {
|
|
33
|
+
return {
|
|
34
|
+
bottom: Math.max(
|
|
35
|
+
LAYOUT_CONSTANTS.FAB_BOTTOM_TABLET,
|
|
36
|
+
bottom + LAYOUT_CONSTANTS.TAB_BAR_OFFSET
|
|
37
|
+
),
|
|
38
|
+
right: Math.max(
|
|
39
|
+
LAYOUT_CONSTANTS.FAB_RIGHT_TABLET,
|
|
40
|
+
right + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
bottom: Math.max(
|
|
47
|
+
LAYOUT_CONSTANTS.TAB_BAR_OFFSET,
|
|
48
|
+
bottom + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET
|
|
49
|
+
),
|
|
50
|
+
right: Math.max(
|
|
51
|
+
LAYOUT_CONSTANTS.FAB_RIGHT_PHONE,
|
|
52
|
+
right + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET
|
|
53
|
+
),
|
|
54
|
+
};
|
|
55
|
+
} catch {
|
|
56
|
+
return {
|
|
57
|
+
bottom: LAYOUT_CONSTANTS.TAB_BAR_OFFSET,
|
|
58
|
+
right: LAYOUT_CONSTANTS.FAB_RIGHT_PHONE,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -1,270 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Responsive Layout Utilities
|
|
3
|
-
* Layout utilities for positioning and spacing.
|
|
2
|
+
* Responsive Layout Utilities - Barrel Export
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// Screen layout
|
|
6
|
+
export { getScreenLayoutConfig } from './screen/screenLayoutConfig';
|
|
7
|
+
export type { ScreenLayoutConfig } from './screen/screenLayoutConfig';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* Uses expo-device based detection for accuracy
|
|
13
|
-
*/
|
|
14
|
-
const checkIsTabletSize = (): boolean => isTablet();
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Screen layout configuration for ScreenLayout component
|
|
18
|
-
*/
|
|
19
|
-
export interface ScreenLayoutConfig {
|
|
20
|
-
maxContentWidth: number | undefined;
|
|
21
|
-
horizontalPadding: number;
|
|
22
|
-
verticalPadding: number;
|
|
23
|
-
spacingMultiplier: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get complete screen layout configuration
|
|
28
|
-
* Returns all responsive values needed for ScreenLayout
|
|
29
|
-
*/
|
|
30
|
-
export const getScreenLayoutConfig = (
|
|
31
|
-
insets: { left?: number; right?: number; top?: number; bottom?: number } = {}
|
|
32
|
-
): ScreenLayoutConfig => {
|
|
33
|
-
try {
|
|
34
|
-
const isTabletDevice = checkIsTabletSize();
|
|
35
|
-
const spacingMultiplier = getSpacingMultiplier();
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
maxContentWidth: isTabletDevice ? SIZE_CONSTRAINTS.CONTENT_MAX_TABLET : undefined,
|
|
39
|
-
horizontalPadding: getResponsiveHorizontalPadding(LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE, insets),
|
|
40
|
-
verticalPadding: getResponsiveVerticalPadding(insets),
|
|
41
|
-
spacingMultiplier,
|
|
42
|
-
};
|
|
43
|
-
} catch {
|
|
44
|
-
return {
|
|
45
|
-
maxContentWidth: undefined,
|
|
46
|
-
horizontalPadding: LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
47
|
-
verticalPadding: LAYOUT_CONSTANTS.VERTICAL_PADDING_STANDARD,
|
|
48
|
-
spacingMultiplier: LAYOUT_CONSTANTS.SPACING_MULTIPLIER_STANDARD,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Responsive vertical padding
|
|
55
|
-
* Adjusts based on device type and safe area insets
|
|
56
|
-
*/
|
|
57
|
-
export const getResponsiveVerticalPadding = (
|
|
58
|
-
insets: { top?: number; bottom?: number } = { top: 0, bottom: 0 }
|
|
59
|
-
): number => {
|
|
60
|
-
try {
|
|
61
|
-
validateSafeAreaInsets(insets);
|
|
62
|
-
const { top = 0 } = insets;
|
|
63
|
-
const isTabletDevice = checkIsTabletSize();
|
|
64
|
-
const isSmall = isSmallPhone();
|
|
65
|
-
const spacingMultiplier = getSpacingMultiplier();
|
|
66
|
-
|
|
67
|
-
// Base padding adjusted by device type
|
|
68
|
-
let basePadding: number = LAYOUT_CONSTANTS.VERTICAL_PADDING_STANDARD;
|
|
69
|
-
if (isTabletDevice) {
|
|
70
|
-
basePadding = LAYOUT_CONSTANTS.VERTICAL_PADDING_TABLET;
|
|
71
|
-
} else if (isSmall) {
|
|
72
|
-
basePadding = LAYOUT_CONSTANTS.VERTICAL_PADDING_SMALL;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Apply spacing multiplier for consistency
|
|
76
|
-
const adjustedPadding = basePadding * spacingMultiplier;
|
|
77
|
-
|
|
78
|
-
// Ensure minimum padding respects safe area
|
|
79
|
-
return Math.max(adjustedPadding, top > 0 ? 8 : adjustedPadding);
|
|
80
|
-
} catch {
|
|
81
|
-
return LAYOUT_CONSTANTS.VERTICAL_PADDING_STANDARD;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Responsive horizontal padding
|
|
87
|
-
*/
|
|
88
|
-
export const getResponsiveHorizontalPadding = (
|
|
89
|
-
basePadding: number = LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
90
|
-
insets: { left?: number; right?: number } = { left: 0, right: 0 }
|
|
91
|
-
): number => {
|
|
92
|
-
try {
|
|
93
|
-
const validatedBasePadding = validateNumber(basePadding, 'basePadding', 0, 100);
|
|
94
|
-
validateSafeAreaInsets(insets);
|
|
95
|
-
|
|
96
|
-
const { left = 0, right = 0 } = insets;
|
|
97
|
-
const isTabletDevice = checkIsTabletSize();
|
|
98
|
-
|
|
99
|
-
if (isTabletDevice) {
|
|
100
|
-
const tabletPadding = validatedBasePadding * LAYOUT_CONSTANTS.SPACING_MULTIPLIER_TABLET;
|
|
101
|
-
return Math.max(
|
|
102
|
-
tabletPadding,
|
|
103
|
-
left + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
104
|
-
right + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return Math.max(
|
|
109
|
-
validatedBasePadding,
|
|
110
|
-
left + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET,
|
|
111
|
-
right + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET
|
|
112
|
-
);
|
|
113
|
-
} catch {
|
|
114
|
-
return 16;
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Responsive bottom positioning
|
|
120
|
-
*/
|
|
121
|
-
export const getResponsiveBottomPosition = (
|
|
122
|
-
basePosition: number = LAYOUT_CONSTANTS.BOTTOM_POSITION_BASE,
|
|
123
|
-
insets: { bottom?: number } = { bottom: 0 }
|
|
124
|
-
): number => {
|
|
125
|
-
try {
|
|
126
|
-
const validatedBasePosition = validateNumber(basePosition, 'basePosition', 0, 500);
|
|
127
|
-
validateSafeAreaInsets(insets);
|
|
128
|
-
|
|
129
|
-
const { bottom = 0 } = insets;
|
|
130
|
-
return Math.max(validatedBasePosition, bottom + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET);
|
|
131
|
-
} catch {
|
|
132
|
-
return 32;
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Responsive FAB position
|
|
138
|
-
*/
|
|
139
|
-
export const getResponsiveFABPosition = (
|
|
140
|
-
insets: { bottom?: number; right?: number } = { bottom: 0, right: 0 }
|
|
141
|
-
): { bottom: number; right: number } => {
|
|
142
|
-
try {
|
|
143
|
-
validateSafeAreaInsets(insets);
|
|
144
|
-
const { bottom = 0, right = 0 } = insets;
|
|
145
|
-
const isTabletDevice = checkIsTabletSize();
|
|
146
|
-
|
|
147
|
-
if (isTabletDevice) {
|
|
148
|
-
return {
|
|
149
|
-
bottom: Math.max(
|
|
150
|
-
LAYOUT_CONSTANTS.FAB_BOTTOM_TABLET,
|
|
151
|
-
bottom + LAYOUT_CONSTANTS.TAB_BAR_OFFSET
|
|
152
|
-
),
|
|
153
|
-
right: Math.max(
|
|
154
|
-
LAYOUT_CONSTANTS.FAB_RIGHT_TABLET,
|
|
155
|
-
right + LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE
|
|
156
|
-
),
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
bottom: Math.max(
|
|
162
|
-
LAYOUT_CONSTANTS.TAB_BAR_OFFSET,
|
|
163
|
-
bottom + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET
|
|
164
|
-
),
|
|
165
|
-
right: Math.max(
|
|
166
|
-
LAYOUT_CONSTANTS.FAB_RIGHT_PHONE,
|
|
167
|
-
right + LAYOUT_CONSTANTS.SAFE_AREA_OFFSET
|
|
168
|
-
),
|
|
169
|
-
};
|
|
170
|
-
} catch {
|
|
171
|
-
return {
|
|
172
|
-
bottom: LAYOUT_CONSTANTS.TAB_BAR_OFFSET,
|
|
173
|
-
right: LAYOUT_CONSTANTS.FAB_RIGHT_PHONE,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Tab bar height constants
|
|
180
|
-
*/
|
|
181
|
-
const TAB_BAR_CONSTANTS = {
|
|
182
|
-
BASE_HEIGHT_PHONE: 60,
|
|
183
|
-
BASE_HEIGHT_TABLET: 70,
|
|
184
|
-
MIN_PADDING_BOTTOM: 8,
|
|
185
|
-
MIN_PADDING_TOP: 8,
|
|
186
|
-
ICON_SIZE_PHONE: 24,
|
|
187
|
-
ICON_SIZE_TABLET: 28,
|
|
188
|
-
FAB_SIZE_PHONE: 64,
|
|
189
|
-
FAB_SIZE_TABLET: 72,
|
|
190
|
-
FAB_OFFSET_Y_PHONE: -24,
|
|
191
|
-
FAB_OFFSET_Y_TABLET: -28,
|
|
192
|
-
} as const;
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Responsive tab bar configuration
|
|
196
|
-
*/
|
|
197
|
-
export interface ResponsiveTabBarConfig {
|
|
198
|
-
height: number;
|
|
199
|
-
paddingBottom: number;
|
|
200
|
-
paddingTop: number;
|
|
201
|
-
iconSize: number;
|
|
202
|
-
fabSize: number;
|
|
203
|
-
fabOffsetY: number;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Get responsive tab bar height based on device and safe area
|
|
208
|
-
*/
|
|
209
|
-
export const getResponsiveTabBarHeight = (
|
|
210
|
-
insets: { bottom?: number } = { bottom: 0 }
|
|
211
|
-
): number => {
|
|
212
|
-
try {
|
|
213
|
-
validateSafeAreaInsets(insets);
|
|
214
|
-
const { bottom = 0 } = insets;
|
|
215
|
-
const isTabletDevice = checkIsTabletSize();
|
|
216
|
-
|
|
217
|
-
const baseHeight = isTabletDevice
|
|
218
|
-
? TAB_BAR_CONSTANTS.BASE_HEIGHT_TABLET
|
|
219
|
-
: TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE;
|
|
220
|
-
|
|
221
|
-
const bottomPadding = Math.max(bottom, TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM);
|
|
222
|
-
|
|
223
|
-
return baseHeight + bottomPadding;
|
|
224
|
-
} catch {
|
|
225
|
-
return TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE + TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM;
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Get complete responsive tab bar configuration
|
|
231
|
-
*/
|
|
232
|
-
export const getResponsiveTabBarConfig = (
|
|
233
|
-
insets: { bottom?: number } = { bottom: 0 }
|
|
234
|
-
): ResponsiveTabBarConfig => {
|
|
235
|
-
try {
|
|
236
|
-
validateSafeAreaInsets(insets);
|
|
237
|
-
const { bottom = 0 } = insets;
|
|
238
|
-
const isTabletSize = checkIsTabletSize();
|
|
239
|
-
|
|
240
|
-
const baseHeight = isTabletSize
|
|
241
|
-
? TAB_BAR_CONSTANTS.BASE_HEIGHT_TABLET
|
|
242
|
-
: TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE;
|
|
9
|
+
// Padding utilities
|
|
10
|
+
export { getResponsiveVerticalPadding, getResponsiveHorizontalPadding } from './padding/paddingUtils';
|
|
243
11
|
|
|
244
|
-
|
|
12
|
+
// Positioning utilities
|
|
13
|
+
export { getResponsiveBottomPosition, getResponsiveFABPosition } from './positioning/positioningUtils';
|
|
245
14
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
paddingTop: TAB_BAR_CONSTANTS.MIN_PADDING_TOP,
|
|
250
|
-
iconSize: isTabletSize
|
|
251
|
-
? TAB_BAR_CONSTANTS.ICON_SIZE_TABLET
|
|
252
|
-
: TAB_BAR_CONSTANTS.ICON_SIZE_PHONE,
|
|
253
|
-
fabSize: isTabletSize
|
|
254
|
-
? TAB_BAR_CONSTANTS.FAB_SIZE_TABLET
|
|
255
|
-
: TAB_BAR_CONSTANTS.FAB_SIZE_PHONE,
|
|
256
|
-
fabOffsetY: isTabletSize
|
|
257
|
-
? TAB_BAR_CONSTANTS.FAB_OFFSET_Y_TABLET
|
|
258
|
-
: TAB_BAR_CONSTANTS.FAB_OFFSET_Y_PHONE,
|
|
259
|
-
};
|
|
260
|
-
} catch {
|
|
261
|
-
return {
|
|
262
|
-
height: TAB_BAR_CONSTANTS.BASE_HEIGHT_PHONE + TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM,
|
|
263
|
-
paddingBottom: TAB_BAR_CONSTANTS.MIN_PADDING_BOTTOM,
|
|
264
|
-
paddingTop: TAB_BAR_CONSTANTS.MIN_PADDING_TOP,
|
|
265
|
-
iconSize: TAB_BAR_CONSTANTS.ICON_SIZE_PHONE,
|
|
266
|
-
fabSize: TAB_BAR_CONSTANTS.FAB_SIZE_PHONE,
|
|
267
|
-
fabOffsetY: TAB_BAR_CONSTANTS.FAB_OFFSET_Y_PHONE,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
};
|
|
15
|
+
// Tab bar configuration
|
|
16
|
+
export { getResponsiveTabBarHeight, getResponsiveTabBarConfig } from './tabbar/tabBarConfig';
|
|
17
|
+
export type { ResponsiveTabBarConfig } from './tabbar/tabBarConfig';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen Layout Configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isTablet, getSpacingMultiplier } from '../../device/detection';
|
|
6
|
+
import { LAYOUT_CONSTANTS, SIZE_CONSTRAINTS } from '../config';
|
|
7
|
+
import { getResponsiveHorizontalPadding } from '../padding/paddingUtils';
|
|
8
|
+
import { getResponsiveVerticalPadding } from '../padding/paddingUtils';
|
|
9
|
+
|
|
10
|
+
export interface ScreenLayoutConfig {
|
|
11
|
+
readonly maxContentWidth: number | undefined;
|
|
12
|
+
readonly horizontalPadding: number;
|
|
13
|
+
readonly verticalPadding: number;
|
|
14
|
+
readonly spacingMultiplier: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getScreenLayoutConfig = (
|
|
18
|
+
insets: { left?: number; right?: number; top?: number; bottom?: number } = {}
|
|
19
|
+
): ScreenLayoutConfig => {
|
|
20
|
+
try {
|
|
21
|
+
const isTabletDevice = isTablet();
|
|
22
|
+
const spacingMultiplier = getSpacingMultiplier();
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
maxContentWidth: isTabletDevice ? SIZE_CONSTRAINTS.CONTENT_MAX_TABLET : undefined,
|
|
26
|
+
horizontalPadding: getResponsiveHorizontalPadding(LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE, insets),
|
|
27
|
+
verticalPadding: getResponsiveVerticalPadding(insets),
|
|
28
|
+
spacingMultiplier,
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return {
|
|
32
|
+
maxContentWidth: undefined,
|
|
33
|
+
horizontalPadding: LAYOUT_CONSTANTS.HORIZONTAL_PADDING_BASE,
|
|
34
|
+
verticalPadding: LAYOUT_CONSTANTS.VERTICAL_PADDING_STANDARD,
|
|
35
|
+
spacingMultiplier: LAYOUT_CONSTANTS.SPACING_MULTIPLIER_STANDARD,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|