@umituz/react-native-design-system 2.3.8 → 2.3.10
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/molecules/avatar/Avatar.tsx +206 -0
- package/src/molecules/avatar/Avatar.utils.ts +288 -0
- package/src/molecules/avatar/AvatarGroup.tsx +175 -0
- package/src/molecules/avatar/index.ts +10 -0
- package/src/molecules/bottom-sheet/components/BottomSheet.tsx +122 -0
- package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +124 -0
- package/src/molecules/bottom-sheet/components/SafeBottomSheetModalProvider.tsx +11 -0
- package/src/molecules/bottom-sheet/components/filter/FilterBottomSheet.tsx +168 -0
- package/src/molecules/bottom-sheet/components/filter/FilterSheet.tsx +116 -0
- package/src/molecules/bottom-sheet/components/filter/FilterSheetComponents/FilterSheetHeader.tsx +33 -0
- package/src/molecules/bottom-sheet/components/filter/FilterSheetComponents/FilterSheetOption.tsx +59 -0
- package/src/molecules/bottom-sheet/hooks/useBottomSheet.ts +16 -0
- package/src/molecules/bottom-sheet/hooks/useBottomSheetModal.ts +17 -0
- package/src/molecules/bottom-sheet/hooks/useListFilters.ts +95 -0
- package/src/molecules/bottom-sheet/index.ts +10 -0
- package/src/molecules/bottom-sheet/types/BottomSheet.ts +122 -0
- package/src/molecules/bottom-sheet/types/Filter.ts +48 -0
- package/src/molecules/index.ts +2 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useListFilters Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
6
|
+
import { FilterOption } from '../types/Filter';
|
|
7
|
+
|
|
8
|
+
export interface FilterItem extends FilterOption {
|
|
9
|
+
active?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UseListFiltersReturn<T> {
|
|
13
|
+
filters: FilterItem[];
|
|
14
|
+
setFilters: (filters: FilterItem[]) => void;
|
|
15
|
+
activeFilters: FilterItem[];
|
|
16
|
+
selectedIds: string[];
|
|
17
|
+
applyFilters: (items: T[]) => T[];
|
|
18
|
+
toggleFilter: (id: string) => void;
|
|
19
|
+
handleFilterPress: (id: string) => void;
|
|
20
|
+
clearFilters: () => void;
|
|
21
|
+
handleClearFilters: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseListFiltersConfig<T> {
|
|
25
|
+
options: FilterOption[];
|
|
26
|
+
defaultFilterId?: string;
|
|
27
|
+
singleSelect?: boolean;
|
|
28
|
+
filterFn?: (item: T, activeFilters: FilterItem[]) => boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Overload 1: When config object is passed
|
|
32
|
+
export function useListFilters<T>(config: UseListFiltersConfig<T>): UseListFiltersReturn<T>;
|
|
33
|
+
// Overload 2: When initial filters array and optional filterFn are passed
|
|
34
|
+
export function useListFilters<T>(initialFilters: FilterItem[], filterFn?: (item: T, activeFilters: FilterItem[]) => boolean): UseListFiltersReturn<T>;
|
|
35
|
+
// Unified implementation
|
|
36
|
+
export function useListFilters<T>(
|
|
37
|
+
configOrFilters: FilterItem[] | UseListFiltersConfig<T>,
|
|
38
|
+
filterFnParam?: (item: T, activeFilters: FilterItem[]) => boolean
|
|
39
|
+
): UseListFiltersReturn<T> {
|
|
40
|
+
const isConfig = !Array.isArray(configOrFilters);
|
|
41
|
+
|
|
42
|
+
// Normalize initial state
|
|
43
|
+
const initialFilters = isConfig
|
|
44
|
+
? (configOrFilters as UseListFiltersConfig<T>).options.map(opt => ({
|
|
45
|
+
...opt,
|
|
46
|
+
active: (configOrFilters as UseListFiltersConfig<T>).defaultFilterId
|
|
47
|
+
? opt.id === (configOrFilters as UseListFiltersConfig<T>).defaultFilterId
|
|
48
|
+
: false
|
|
49
|
+
}))
|
|
50
|
+
: (configOrFilters as FilterItem[]);
|
|
51
|
+
|
|
52
|
+
const filterFn = isConfig
|
|
53
|
+
? (configOrFilters as UseListFiltersConfig<T>).filterFn
|
|
54
|
+
: filterFnParam;
|
|
55
|
+
|
|
56
|
+
const singleSelect = isConfig
|
|
57
|
+
? (configOrFilters as UseListFiltersConfig<T>).singleSelect
|
|
58
|
+
: false;
|
|
59
|
+
|
|
60
|
+
const [filters, setFilters] = useState<FilterItem[]>(initialFilters);
|
|
61
|
+
|
|
62
|
+
const activeFilters = useMemo(() => filters.filter((f) => f.active), [filters]);
|
|
63
|
+
|
|
64
|
+
const toggleFilter = useCallback((id: string) => {
|
|
65
|
+
setFilters((current) => {
|
|
66
|
+
if (singleSelect) {
|
|
67
|
+
return current.map((f) => ({ ...f, active: f.id === id }));
|
|
68
|
+
}
|
|
69
|
+
return current.map((f) => (f.id === id ? { ...f, active: !f.active } : f));
|
|
70
|
+
});
|
|
71
|
+
}, [singleSelect]);
|
|
72
|
+
|
|
73
|
+
const clearFilters = useCallback(() => {
|
|
74
|
+
setFilters((current) => current.map((f) => ({ ...f, active: false })));
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const applyFilters = useCallback((items: T[]) => {
|
|
78
|
+
if (activeFilters.length === 0 || !filterFn) return items;
|
|
79
|
+
return items.filter((item) => filterFn(item, activeFilters));
|
|
80
|
+
}, [activeFilters, filterFn]);
|
|
81
|
+
|
|
82
|
+
const selectedIds = useMemo(() => activeFilters.map((f) => f.id), [activeFilters]);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
filters,
|
|
86
|
+
setFilters,
|
|
87
|
+
activeFilters,
|
|
88
|
+
selectedIds,
|
|
89
|
+
applyFilters,
|
|
90
|
+
toggleFilter,
|
|
91
|
+
handleFilterPress: toggleFilter,
|
|
92
|
+
clearFilters,
|
|
93
|
+
handleClearFilters: clearFilters,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './components/BottomSheet';
|
|
2
|
+
export * from './components/BottomSheetModal';
|
|
3
|
+
export * from './components/SafeBottomSheetModalProvider';
|
|
4
|
+
export * from './components/filter/FilterBottomSheet';
|
|
5
|
+
export * from './components/filter/FilterSheet';
|
|
6
|
+
export * from './hooks/useBottomSheet';
|
|
7
|
+
export * from './hooks/useBottomSheetModal';
|
|
8
|
+
export * from './hooks/useListFilters';
|
|
9
|
+
export * from './types/BottomSheet';
|
|
10
|
+
export * from './types/Filter';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export type SnapPoint = string | number;
|
|
4
|
+
export type BottomSheetPreset = 'small' | 'medium' | 'large' | 'full' | 'custom';
|
|
5
|
+
export type KeyboardBehavior = 'interactive' | 'extend' | 'fillParent';
|
|
6
|
+
|
|
7
|
+
export interface BottomSheetConfig {
|
|
8
|
+
snapPoints: SnapPoint[];
|
|
9
|
+
initialIndex?: number;
|
|
10
|
+
enableBackdrop?: boolean;
|
|
11
|
+
backdropAppearsOnIndex?: number;
|
|
12
|
+
backdropDisappearsOnIndex?: number;
|
|
13
|
+
keyboardBehavior?: KeyboardBehavior;
|
|
14
|
+
enableHandleIndicator?: boolean;
|
|
15
|
+
enablePanDownToClose?: boolean;
|
|
16
|
+
enableDynamicSizing?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BottomSheetRef {
|
|
20
|
+
snapToIndex: (index: number) => void;
|
|
21
|
+
snapToPosition: (position: string | number) => void;
|
|
22
|
+
expand: () => void;
|
|
23
|
+
collapse: () => void;
|
|
24
|
+
close: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BottomSheetModalRef {
|
|
28
|
+
present: () => void;
|
|
29
|
+
dismiss: () => void;
|
|
30
|
+
snapToIndex: (index: number) => void;
|
|
31
|
+
snapToPosition: (position: string | number) => void;
|
|
32
|
+
expand: () => void;
|
|
33
|
+
collapse: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface BottomSheetProps {
|
|
37
|
+
children: React.ReactNode;
|
|
38
|
+
preset?: BottomSheetPreset;
|
|
39
|
+
snapPoints?: SnapPoint[];
|
|
40
|
+
initialIndex?: number;
|
|
41
|
+
enableBackdrop?: boolean;
|
|
42
|
+
backdropAppearsOnIndex?: number;
|
|
43
|
+
backdropDisappearsOnIndex?: number;
|
|
44
|
+
keyboardBehavior?: KeyboardBehavior;
|
|
45
|
+
enableHandleIndicator?: boolean;
|
|
46
|
+
enablePanDownToClose?: boolean;
|
|
47
|
+
enableDynamicSizing?: boolean;
|
|
48
|
+
onChange?: (index: number) => void;
|
|
49
|
+
onClose?: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface BottomSheetModalProps extends Omit<BottomSheetProps, 'onClose'> {
|
|
53
|
+
onDismiss?: () => void;
|
|
54
|
+
backgroundColor?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface UseBottomSheetReturn {
|
|
58
|
+
sheetRef: React.RefObject<BottomSheetRef | null>;
|
|
59
|
+
open: () => void;
|
|
60
|
+
close: () => void;
|
|
61
|
+
expand: () => void;
|
|
62
|
+
collapse: () => void;
|
|
63
|
+
snapToIndex: (index: number) => void;
|
|
64
|
+
snapToPosition: (position: string | number) => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface UseBottomSheetModalReturn {
|
|
68
|
+
modalRef: React.RefObject<BottomSheetModalRef | null>;
|
|
69
|
+
present: () => void;
|
|
70
|
+
dismiss: () => void;
|
|
71
|
+
snapToIndex: (index: number) => void;
|
|
72
|
+
snapToPosition: (position: string | number) => void;
|
|
73
|
+
expand: () => void;
|
|
74
|
+
collapse: () => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export const BottomSheetUtils = {
|
|
79
|
+
getPreset: (preset: BottomSheetPreset): BottomSheetConfig => {
|
|
80
|
+
switch (preset) {
|
|
81
|
+
case 'small':
|
|
82
|
+
return {
|
|
83
|
+
snapPoints: ['25%'],
|
|
84
|
+
enableBackdrop: true,
|
|
85
|
+
enablePanDownToClose: true,
|
|
86
|
+
};
|
|
87
|
+
case 'medium':
|
|
88
|
+
return {
|
|
89
|
+
snapPoints: ['50%'],
|
|
90
|
+
enableBackdrop: true,
|
|
91
|
+
enablePanDownToClose: true,
|
|
92
|
+
};
|
|
93
|
+
case 'large':
|
|
94
|
+
return {
|
|
95
|
+
snapPoints: ['90%'],
|
|
96
|
+
enableBackdrop: true,
|
|
97
|
+
enablePanDownToClose: true,
|
|
98
|
+
};
|
|
99
|
+
case 'full':
|
|
100
|
+
return {
|
|
101
|
+
snapPoints: ['100%'],
|
|
102
|
+
enableBackdrop: true,
|
|
103
|
+
enablePanDownToClose: false,
|
|
104
|
+
};
|
|
105
|
+
default:
|
|
106
|
+
return {
|
|
107
|
+
snapPoints: ['50%'],
|
|
108
|
+
enableBackdrop: true,
|
|
109
|
+
enablePanDownToClose: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
createConfig: (config: Partial<BottomSheetConfig>): BottomSheetConfig => {
|
|
115
|
+
return {
|
|
116
|
+
snapPoints: ['50%'],
|
|
117
|
+
enableBackdrop: true,
|
|
118
|
+
enablePanDownToClose: true,
|
|
119
|
+
...config,
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain - Filter Entities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface FilterOption {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
type?: string;
|
|
10
|
+
count?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FilterCategory {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
options: FilterOption[];
|
|
17
|
+
multiSelect?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class FilterUtils {
|
|
21
|
+
static hasActiveFilter(selectedIds: string[], defaultId: string = "all"): boolean {
|
|
22
|
+
if (selectedIds.length === 0) return false;
|
|
23
|
+
if (selectedIds.length === 1 && selectedIds[0] === defaultId) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static toggleFilter(
|
|
28
|
+
selectedIds: string[],
|
|
29
|
+
filterId: string,
|
|
30
|
+
multiSelect: boolean = false,
|
|
31
|
+
defaultId: string = "all"
|
|
32
|
+
): string[] {
|
|
33
|
+
if (filterId === defaultId) {
|
|
34
|
+
return [defaultId];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (multiSelect) {
|
|
38
|
+
const newIds = selectedIds.filter((id) => id !== defaultId);
|
|
39
|
+
if (newIds.includes(filterId)) {
|
|
40
|
+
const filtered = newIds.filter((id) => id !== filterId);
|
|
41
|
+
return filtered.length === 0 ? [defaultId] : filtered;
|
|
42
|
+
}
|
|
43
|
+
return [...newIds, filterId];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return [filterId];
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/molecules/index.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Component exports
|
|
7
|
+
export * from './avatar';
|
|
8
|
+
export * from './bottom-sheet';
|
|
7
9
|
export { FormField, type FormFieldProps } from './FormField';
|
|
8
10
|
export { ListItem, type ListItemProps } from './ListItem';
|
|
9
11
|
export { SearchBar, type SearchBarProps } from './SearchBar';
|