@umituz/react-native-design-system 2.3.13 → 2.3.15
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 +32 -13
- package/src/index.ts +116 -0
- package/src/layouts/ScreenLayout/ScreenLayout.example.tsx +2 -2
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +1 -1
- package/src/molecules/animation/core/AnimationCore.ts +29 -0
- package/src/molecules/animation/domain/entities/Animation.ts +81 -0
- package/src/molecules/animation/domain/entities/Fireworks.ts +44 -0
- package/src/molecules/animation/domain/entities/Theme.ts +76 -0
- package/src/molecules/animation/index.ts +146 -0
- package/src/molecules/animation/infrastructure/services/AnimationConfigService.ts +35 -0
- package/src/molecules/animation/infrastructure/services/SpringAnimationConfigService.ts +67 -0
- package/src/molecules/animation/infrastructure/services/TimingAnimationConfigService.ts +57 -0
- package/src/molecules/animation/infrastructure/services/__tests__/SpringAnimationConfigService.test.ts +114 -0
- package/src/molecules/animation/infrastructure/services/__tests__/TimingAnimationConfigService.test.ts +105 -0
- package/src/molecules/animation/presentation/components/Fireworks.tsx +126 -0
- package/src/molecules/animation/presentation/components/__tests__/Fireworks.test.tsx +189 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useAnimation.integration.test.ts +216 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useFireworks.test.ts +242 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useGesture.test.ts +111 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useSpringAnimation.test.ts +131 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTimingAnimation.test.ts +175 -0
- package/src/molecules/animation/presentation/hooks/__tests__/useTransformAnimation.test.ts +137 -0
- package/src/molecules/animation/presentation/hooks/useAnimation.ts +77 -0
- package/src/molecules/animation/presentation/hooks/useFireworks.ts +141 -0
- package/src/molecules/animation/presentation/hooks/useGesture.ts +61 -0
- package/src/molecules/animation/presentation/hooks/useGestureCreators.ts +163 -0
- package/src/molecules/animation/presentation/hooks/useGestureState.ts +53 -0
- package/src/molecules/animation/presentation/hooks/useIconAnimations.ts +119 -0
- package/src/molecules/animation/presentation/hooks/useModalAnimations.ts +124 -0
- package/src/molecules/animation/presentation/hooks/useReanimatedReady.ts +60 -0
- package/src/molecules/animation/presentation/hooks/useSpringAnimation.ts +69 -0
- package/src/molecules/animation/presentation/hooks/useTimingAnimation.ts +111 -0
- package/src/molecules/animation/presentation/hooks/useTransformAnimation.ts +57 -0
- package/src/molecules/animation/presentation/providers/AnimationThemeProvider.tsx +62 -0
- package/src/molecules/animation/presentation/providers/__tests__/AnimationThemeProvider.test.tsx +165 -0
- package/src/molecules/animation/types/global.d.ts +97 -0
- package/src/molecules/calendar/domain/entities/CalendarDay.entity.ts +115 -0
- package/src/molecules/calendar/domain/entities/CalendarEvent.entity.ts +202 -0
- package/src/molecules/calendar/domain/repositories/ICalendarRepository.ts +120 -0
- package/src/molecules/calendar/index.ts +98 -0
- package/src/molecules/calendar/infrastructure/services/CalendarEvents.ts +196 -0
- package/src/molecules/calendar/infrastructure/services/CalendarGeneration.ts +172 -0
- package/src/molecules/calendar/infrastructure/services/CalendarPermissions.ts +92 -0
- package/src/molecules/calendar/infrastructure/services/CalendarService.ts +161 -0
- package/src/molecules/calendar/infrastructure/services/CalendarSync.ts +205 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +307 -0
- package/src/molecules/calendar/infrastructure/utils/DateUtilities.ts +128 -0
- package/src/molecules/calendar/presentation/components/AtomicCalendar.tsx +279 -0
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +356 -0
- package/src/molecules/celebration/domain/entities/CelebrationConfig.ts +17 -0
- package/src/molecules/celebration/domain/entities/FireworksConfig.ts +32 -0
- package/src/molecules/celebration/index.ts +93 -0
- package/src/molecules/celebration/infrastructure/services/FireworksConfigService.ts +49 -0
- package/src/molecules/celebration/presentation/components/CelebrationFireworksOverlay.tsx +33 -0
- package/src/molecules/celebration/presentation/components/CelebrationModal.tsx +78 -0
- package/src/molecules/celebration/presentation/components/CelebrationModalContent.tsx +90 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationModalAnimation.ts +49 -0
- package/src/molecules/celebration/presentation/hooks/useCelebrationState.ts +45 -0
- package/src/molecules/celebration/presentation/styles/CelebrationModalStyles.ts +65 -0
- package/src/molecules/countdown/components/Countdown.tsx +128 -0
- package/src/molecules/countdown/components/CountdownHeader.tsx +84 -0
- package/src/molecules/countdown/components/TimeUnit.tsx +73 -0
- package/src/molecules/countdown/hooks/useCountdown.ts +107 -0
- package/src/molecules/countdown/index.ts +25 -0
- package/src/molecules/countdown/types/CountdownTypes.ts +31 -0
- package/src/molecules/countdown/utils/TimeCalculator.ts +46 -0
- package/src/molecules/emoji/domain/entities/Emoji.ts +129 -0
- package/src/molecules/emoji/index.ts +177 -0
- package/src/molecules/emoji/presentation/components/EmojiPicker.tsx +102 -0
- package/src/molecules/emoji/presentation/hooks/useEmojiPicker.ts +171 -0
- package/src/molecules/index.ts +24 -0
- package/src/molecules/long-press-menu/domain/entities/MenuAction.ts +37 -0
- package/src/molecules/long-press-menu/index.ts +16 -0
- package/src/molecules/navigation/StackNavigator.tsx +75 -0
- package/src/molecules/navigation/TabsNavigator.tsx +94 -0
- package/src/molecules/navigation/components/FabButton.tsx +45 -0
- package/src/molecules/navigation/components/TabLabel.tsx +47 -0
- package/src/molecules/navigation/createStackNavigator.ts +20 -0
- package/src/molecules/navigation/createTabNavigator.ts +20 -0
- package/src/molecules/navigation/hooks/useTabBarStyles.ts +54 -0
- package/src/molecules/navigation/index.ts +37 -0
- package/src/molecules/navigation/types.ts +118 -0
- package/src/molecules/navigation/utils/AppNavigation.ts +101 -0
- package/src/molecules/navigation/utils/IconRenderer.ts +50 -0
- package/src/molecules/navigation/utils/LabelProcessor.ts +70 -0
- package/src/molecules/navigation/utils/NavigationCleanup.ts +62 -0
- package/src/molecules/navigation/utils/NavigationTheme.ts +21 -0
- package/src/molecules/navigation/utils/NavigationValidator.ts +61 -0
- package/src/molecules/navigation/utils/ScreenFactory.ts +115 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.getIconName.test.ts +109 -0
- package/src/molecules/navigation/utils/__tests__/IconRenderer.renderIcon.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processLabel.test.ts +116 -0
- package/src/molecules/navigation/utils/__tests__/LabelProcessor.processTitle.test.ts +59 -0
- package/src/molecules/navigation/utils/__tests__/NavigationCleanup.test.ts +271 -0
- package/src/molecules/navigation/utils/__tests__/NavigationValidator.test.ts +252 -0
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +194 -0
- package/src/molecules/swipe-actions/index.ts +6 -0
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +131 -0
- package/src/theme/hooks/useResponsiveDesignTokens.ts +1 -1
- package/src/utilities/clipboard/ClipboardUtils.ts +71 -0
- package/src/utilities/clipboard/index.ts +5 -0
- package/src/utilities/index.ts +6 -0
- package/src/utilities/sharing/domain/entities/Share.ts +210 -0
- package/src/utilities/sharing/index.ts +205 -0
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +165 -0
- package/src/utilities/sharing/presentation/hooks/useSharing.ts +154 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TimeRemaining } from '../types/CountdownTypes';
|
|
2
|
+
|
|
3
|
+
export function calculateTimeRemaining(targetDate: Date | string): TimeRemaining {
|
|
4
|
+
const now = new Date().getTime();
|
|
5
|
+
const target = typeof targetDate === 'string'
|
|
6
|
+
? new Date(targetDate).getTime()
|
|
7
|
+
: targetDate.getTime();
|
|
8
|
+
|
|
9
|
+
const difference = Math.max(0, target - now);
|
|
10
|
+
const totalSeconds = Math.floor(difference / 1000);
|
|
11
|
+
|
|
12
|
+
const days = Math.floor(totalSeconds / (24 * 3600));
|
|
13
|
+
const hours = Math.floor((totalSeconds % (24 * 3600)) / 3600);
|
|
14
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
15
|
+
const seconds = totalSeconds % 60;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
days,
|
|
19
|
+
hours,
|
|
20
|
+
minutes,
|
|
21
|
+
seconds,
|
|
22
|
+
totalSeconds,
|
|
23
|
+
isExpired: totalSeconds <= 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function padNumber(num: number): string {
|
|
28
|
+
return String(num).padStart(2, '0');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getNextDayStart(date?: Date): Date {
|
|
32
|
+
const baseDate = date || new Date();
|
|
33
|
+
const nextDay = new Date(baseDate);
|
|
34
|
+
nextDay.setDate(nextDay.getDate() + 1);
|
|
35
|
+
nextDay.setHours(0, 0, 0, 0);
|
|
36
|
+
return nextDay;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getNextYearStart(date?: Date): Date {
|
|
40
|
+
const baseDate = date || new Date();
|
|
41
|
+
const nextYear = new Date(baseDate);
|
|
42
|
+
nextYear.setFullYear(nextYear.getFullYear() + 1);
|
|
43
|
+
nextYear.setMonth(0, 1);
|
|
44
|
+
nextYear.setHours(0, 0, 0, 0);
|
|
45
|
+
return nextYear;
|
|
46
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emoji Domain Entities
|
|
3
|
+
*
|
|
4
|
+
* Core emoji types and interfaces for emoji picker functionality
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Emoji selection with picker UI
|
|
8
|
+
* - Emoji object with metadata (emoji string, name, slug, unicode)
|
|
9
|
+
* - Category-based organization
|
|
10
|
+
* - Search functionality
|
|
11
|
+
* - Recent emojis tracking
|
|
12
|
+
*
|
|
13
|
+
* Dependencies:
|
|
14
|
+
* - rn-emoji-keyboard (emoji picker UI library)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Emoji object returned by the picker
|
|
19
|
+
* Conforms to rn-emoji-keyboard EmojiType structure
|
|
20
|
+
*/
|
|
21
|
+
export interface EmojiObject {
|
|
22
|
+
/** The actual emoji character (e.g., "😀") */
|
|
23
|
+
emoji: string;
|
|
24
|
+
|
|
25
|
+
/** Human-readable name (e.g., "Grinning Face") */
|
|
26
|
+
name: string;
|
|
27
|
+
|
|
28
|
+
/** URL-friendly slug (e.g., "grinning_face") */
|
|
29
|
+
slug: string;
|
|
30
|
+
|
|
31
|
+
/** Unicode codepoint (e.g., "1F600") */
|
|
32
|
+
unicode_version: string;
|
|
33
|
+
|
|
34
|
+
/** Emoji category (e.g., "smileys_emotion") */
|
|
35
|
+
category?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Emoji picker configuration
|
|
40
|
+
*/
|
|
41
|
+
export interface EmojiPickerConfig {
|
|
42
|
+
/** Whether to show category tabs */
|
|
43
|
+
enableCategoryTabs?: boolean;
|
|
44
|
+
|
|
45
|
+
/** Whether to enable search */
|
|
46
|
+
enableSearch?: boolean;
|
|
47
|
+
|
|
48
|
+
/** Whether to show recently used emojis */
|
|
49
|
+
enableRecentlyUsed?: boolean;
|
|
50
|
+
|
|
51
|
+
/** Custom category order */
|
|
52
|
+
categoryOrder?: string[];
|
|
53
|
+
|
|
54
|
+
/** Translation object for labels */
|
|
55
|
+
translation?: {
|
|
56
|
+
search: string;
|
|
57
|
+
categories: Record<string, string>;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Emoji selection callback type
|
|
63
|
+
*/
|
|
64
|
+
export type EmojiSelectCallback = (emojiObject: EmojiObject) => void;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Emoji picker categories
|
|
68
|
+
* Based on Unicode emoji categories
|
|
69
|
+
*/
|
|
70
|
+
export enum EmojiCategory {
|
|
71
|
+
SMILEYS_EMOTION = 'smileys_emotion',
|
|
72
|
+
PEOPLE_BODY = 'people_body',
|
|
73
|
+
ANIMALS_NATURE = 'animals_nature',
|
|
74
|
+
FOOD_DRINK = 'food_drink',
|
|
75
|
+
TRAVEL_PLACES = 'travel_places',
|
|
76
|
+
ACTIVITIES = 'activities',
|
|
77
|
+
OBJECTS = 'objects',
|
|
78
|
+
SYMBOLS = 'symbols',
|
|
79
|
+
FLAGS = 'flags',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Emoji picker state
|
|
84
|
+
*/
|
|
85
|
+
export interface EmojiPickerState {
|
|
86
|
+
/** Whether picker is currently open */
|
|
87
|
+
isOpen: boolean;
|
|
88
|
+
|
|
89
|
+
/** Currently selected emoji (null if none selected) */
|
|
90
|
+
selectedEmoji: string | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Emoji validation utilities
|
|
95
|
+
*/
|
|
96
|
+
export const EmojiUtils = {
|
|
97
|
+
/**
|
|
98
|
+
* Check if a string contains valid emoji
|
|
99
|
+
* @param text - Text to validate
|
|
100
|
+
* @returns True if contains emoji
|
|
101
|
+
*/
|
|
102
|
+
isEmoji: (text: string): boolean => {
|
|
103
|
+
if (!text || text.length === 0) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
// Basic emoji regex (covers most common emojis)
|
|
107
|
+
const emojiRegex = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u;
|
|
108
|
+
return emojiRegex.test(text);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Extract first emoji from text
|
|
113
|
+
* @param text - Text containing emoji
|
|
114
|
+
* @returns First emoji or empty string
|
|
115
|
+
*/
|
|
116
|
+
extractFirstEmoji: (text: string): string => {
|
|
117
|
+
const match = text.match(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u);
|
|
118
|
+
return match ? match[0] : '';
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get emoji length (handles multi-byte emojis correctly)
|
|
123
|
+
* @param emoji - Emoji string
|
|
124
|
+
* @returns Actual character count
|
|
125
|
+
*/
|
|
126
|
+
getEmojiLength: (emoji: string): number => {
|
|
127
|
+
return Array.from(emoji).length;
|
|
128
|
+
},
|
|
129
|
+
} as const;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emoji Domain - Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Global infrastructure domain for emoji selection and display
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Universal emoji picker component
|
|
8
|
+
* - Category-based emoji selection (smileys, animals, food, etc.)
|
|
9
|
+
* - Search functionality
|
|
10
|
+
* - Recently used emojis tracking
|
|
11
|
+
* - React hook for state management
|
|
12
|
+
* - Theme-aware styling
|
|
13
|
+
* - Validation utilities
|
|
14
|
+
*
|
|
15
|
+
* Dependencies:
|
|
16
|
+
* - rn-emoji-keyboard (emoji picker UI library)
|
|
17
|
+
*
|
|
18
|
+
* USAGE:
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Method 1: Using hook (recommended)
|
|
21
|
+
* import { useEmojiPicker, EmojiPicker } from '@umituz/react-native-emoji';
|
|
22
|
+
*
|
|
23
|
+
* const MyComponent = () => {
|
|
24
|
+
* const {
|
|
25
|
+
* isOpen,
|
|
26
|
+
* selectedEmoji,
|
|
27
|
+
* openPicker,
|
|
28
|
+
* closePicker,
|
|
29
|
+
* handleEmojiSelect,
|
|
30
|
+
* } = useEmojiPicker();
|
|
31
|
+
*
|
|
32
|
+
* return (
|
|
33
|
+
* <View>
|
|
34
|
+
* <TouchableOpacity onPress={openPicker}>
|
|
35
|
+
* <Text style={{ fontSize: 32 }}>
|
|
36
|
+
* {selectedEmoji || 'Select Emoji'}
|
|
37
|
+
* </Text>
|
|
38
|
+
* </TouchableOpacity>
|
|
39
|
+
*
|
|
40
|
+
* <EmojiPicker
|
|
41
|
+
* open={isOpen}
|
|
42
|
+
* onClose={closePicker}
|
|
43
|
+
* onEmojiSelected={handleEmojiSelect}
|
|
44
|
+
* />
|
|
45
|
+
* </View>
|
|
46
|
+
* );
|
|
47
|
+
* };
|
|
48
|
+
*
|
|
49
|
+
* // Method 2: Manual state management
|
|
50
|
+
* import { EmojiPicker, type EmojiObject } from '@umituz/react-native-emoji';
|
|
51
|
+
*
|
|
52
|
+
* const MyComponent = () => {
|
|
53
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
54
|
+
* const [emoji, setEmoji] = useState('');
|
|
55
|
+
*
|
|
56
|
+
* const handleSelect = (emojiObject: EmojiObject) => {
|
|
57
|
+
* setEmoji(emojiObject.emoji);
|
|
58
|
+
* setIsOpen(false);
|
|
59
|
+
* };
|
|
60
|
+
*
|
|
61
|
+
* return (
|
|
62
|
+
* <View>
|
|
63
|
+
* <TouchableOpacity onPress={() => setIsOpen(true)}>
|
|
64
|
+
* <Text>{emoji || 'Select'}</Text>
|
|
65
|
+
* </TouchableOpacity>
|
|
66
|
+
*
|
|
67
|
+
* <EmojiPicker
|
|
68
|
+
* open={isOpen}
|
|
69
|
+
* onClose={() => setIsOpen(false)}
|
|
70
|
+
* onEmojiSelected={handleSelect}
|
|
71
|
+
* />
|
|
72
|
+
* </View>
|
|
73
|
+
* );
|
|
74
|
+
* };
|
|
75
|
+
*
|
|
76
|
+
* // Validation utilities
|
|
77
|
+
* import { EmojiUtils } from '@umituz/react-native-emoji';
|
|
78
|
+
*
|
|
79
|
+
* const isValid = EmojiUtils.isEmoji('😀'); // true
|
|
80
|
+
* const first = EmojiUtils.extractFirstEmoji('Hello 😀 World 🌍'); // '😀'
|
|
81
|
+
* const length = EmojiUtils.getEmojiLength('👨👩👧👦'); // 1 (handles multi-byte)
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* REAL-WORLD EXAMPLE (Category Creation):
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { useEmojiPicker, EmojiPicker } from '@umituz/react-native-emoji';
|
|
87
|
+
* import { AtomicInput, AtomicText } from '@umituz/react-native-design-system';
|
|
88
|
+
*
|
|
89
|
+
* const AddCategoryScreen = () => {
|
|
90
|
+
* const [name, setName] = useState('');
|
|
91
|
+
* const {
|
|
92
|
+
* selectedEmoji,
|
|
93
|
+
* isOpen,
|
|
94
|
+
* openPicker,
|
|
95
|
+
* closePicker,
|
|
96
|
+
* handleEmojiSelect,
|
|
97
|
+
* } = useEmojiPicker();
|
|
98
|
+
*
|
|
99
|
+
* const handleSubmit = () => {
|
|
100
|
+
* if (!name.trim() || !selectedEmoji) {
|
|
101
|
+
* alert('Please fill all fields');
|
|
102
|
+
* return;
|
|
103
|
+
* }
|
|
104
|
+
*
|
|
105
|
+
* // Save category with emoji
|
|
106
|
+
* saveCategory({ name, emoji: selectedEmoji });
|
|
107
|
+
* };
|
|
108
|
+
*
|
|
109
|
+
* return (
|
|
110
|
+
* <View>
|
|
111
|
+
* <AtomicInput
|
|
112
|
+
* label="Category Name"
|
|
113
|
+
* value={name}
|
|
114
|
+
* onChangeText={setName}
|
|
115
|
+
* />
|
|
116
|
+
*
|
|
117
|
+
* <TouchableOpacity onPress={openPicker}>
|
|
118
|
+
* <Text style={{ fontSize: 32, textAlign: 'center' }}>
|
|
119
|
+
* {selectedEmoji || 'Tap to select emoji'}
|
|
120
|
+
* </Text>
|
|
121
|
+
* </TouchableOpacity>
|
|
122
|
+
*
|
|
123
|
+
* <AtomicButton onPress={handleSubmit}>
|
|
124
|
+
* Create Category
|
|
125
|
+
* </AtomicButton>
|
|
126
|
+
*
|
|
127
|
+
* <EmojiPicker
|
|
128
|
+
* open={isOpen}
|
|
129
|
+
* onClose={closePicker}
|
|
130
|
+
* onEmojiSelected={handleEmojiSelect}
|
|
131
|
+
* />
|
|
132
|
+
* </View>
|
|
133
|
+
* );
|
|
134
|
+
* };
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* EMOJI CATEGORIES:
|
|
138
|
+
* - 😀 Smileys & Emotion
|
|
139
|
+
* - 👨 People & Body
|
|
140
|
+
* - 🐶 Animals & Nature
|
|
141
|
+
* - 🍔 Food & Drink
|
|
142
|
+
* - ✈️ Travel & Places
|
|
143
|
+
* - ⚽ Activities
|
|
144
|
+
* - 💡 Objects
|
|
145
|
+
* - ❤️ Symbols
|
|
146
|
+
* - 🏁 Flags
|
|
147
|
+
*
|
|
148
|
+
* FEATURES:
|
|
149
|
+
* - 3600+ emojis across 9 categories
|
|
150
|
+
* - Full Unicode 15.0 support
|
|
151
|
+
* - Search by name or keywords
|
|
152
|
+
* - Recently used tracking (persisted)
|
|
153
|
+
* - Category change animations
|
|
154
|
+
* - Smooth scrolling and gestures
|
|
155
|
+
* - Theme-aware (light/dark mode)
|
|
156
|
+
* - Localization support
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
// Domain Entities
|
|
160
|
+
export type {
|
|
161
|
+
EmojiObject,
|
|
162
|
+
EmojiPickerConfig,
|
|
163
|
+
EmojiSelectCallback,
|
|
164
|
+
EmojiPickerState,
|
|
165
|
+
} from './domain/entities/Emoji';
|
|
166
|
+
export { EmojiCategory, EmojiUtils } from './domain/entities/Emoji';
|
|
167
|
+
|
|
168
|
+
// Presentation Components
|
|
169
|
+
export { EmojiPicker } from './presentation/components/EmojiPicker';
|
|
170
|
+
export type { EmojiPickerProps } from './presentation/components/EmojiPicker';
|
|
171
|
+
|
|
172
|
+
// Presentation Hooks
|
|
173
|
+
export { useEmojiPicker } from './presentation/hooks/useEmojiPicker';
|
|
174
|
+
export type {
|
|
175
|
+
UseEmojiPickerOptions,
|
|
176
|
+
UseEmojiPickerReturn,
|
|
177
|
+
} from './presentation/hooks/useEmojiPicker';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmojiPicker Component
|
|
3
|
+
*
|
|
4
|
+
* Universal emoji picker component wrapping rn-emoji-keyboard
|
|
5
|
+
* Provides a clean, factory-standard interface for emoji selection
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Category-based emoji selection
|
|
9
|
+
* - Search functionality
|
|
10
|
+
* - Recently used emojis
|
|
11
|
+
* - Theme-aware styling
|
|
12
|
+
* - Localization support
|
|
13
|
+
*
|
|
14
|
+
* Dependencies:
|
|
15
|
+
* - rn-emoji-keyboard (emoji picker UI library)
|
|
16
|
+
*
|
|
17
|
+
* USAGE:
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { EmojiPicker } from '@umituz/react-native-emoji';
|
|
20
|
+
*
|
|
21
|
+
* const MyComponent = () => {
|
|
22
|
+
* const [isOpen, setIsOpen] = useState(false);
|
|
23
|
+
* const [emoji, setEmoji] = useState('');
|
|
24
|
+
*
|
|
25
|
+
* const handleEmojiSelect = (emojiObject: EmojiObject) => {
|
|
26
|
+
* setEmoji(emojiObject.emoji);
|
|
27
|
+
* setIsOpen(false);
|
|
28
|
+
* };
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <>
|
|
32
|
+
* <TouchableOpacity onPress={() => setIsOpen(true)}>
|
|
33
|
+
* <Text>{emoji || 'Select Emoji'}</Text>
|
|
34
|
+
* </TouchableOpacity>
|
|
35
|
+
*
|
|
36
|
+
* <EmojiPicker
|
|
37
|
+
* open={isOpen}
|
|
38
|
+
* onClose={() => setIsOpen(false)}
|
|
39
|
+
* onEmojiSelected={handleEmojiSelect}
|
|
40
|
+
* />
|
|
41
|
+
* </>
|
|
42
|
+
* );
|
|
43
|
+
* };
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import React from 'react';
|
|
48
|
+
import RNEmojiKeyboard, { EmojiType } from 'rn-emoji-keyboard';
|
|
49
|
+
import type { EmojiObject, EmojiPickerConfig } from '../../domain/entities/Emoji';
|
|
50
|
+
|
|
51
|
+
export interface EmojiPickerProps {
|
|
52
|
+
/** Whether the picker is currently open */
|
|
53
|
+
open: boolean;
|
|
54
|
+
|
|
55
|
+
/** Callback when picker is closed */
|
|
56
|
+
onClose: () => void;
|
|
57
|
+
|
|
58
|
+
/** Callback when an emoji is selected */
|
|
59
|
+
onEmojiSelected: (emojiObject: EmojiObject) => void;
|
|
60
|
+
|
|
61
|
+
/** Optional configuration */
|
|
62
|
+
config?: EmojiPickerConfig;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Universal emoji picker component
|
|
67
|
+
* Wraps rn-emoji-keyboard with factory-standard interface
|
|
68
|
+
*/
|
|
69
|
+
export const EmojiPicker: React.FC<EmojiPickerProps> = ({
|
|
70
|
+
open,
|
|
71
|
+
onClose,
|
|
72
|
+
onEmojiSelected,
|
|
73
|
+
config,
|
|
74
|
+
}) => {
|
|
75
|
+
const handleEmojiSelect = (emojiObject: EmojiType) => {
|
|
76
|
+
// Map rn-emoji-keyboard EmojiType to our EmojiObject interface
|
|
77
|
+
const mappedEmoji: EmojiObject = {
|
|
78
|
+
emoji: emojiObject.emoji,
|
|
79
|
+
name: emojiObject.name,
|
|
80
|
+
slug: emojiObject.slug,
|
|
81
|
+
unicode_version: emojiObject.unicode_version,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
onEmojiSelected(mappedEmoji);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<RNEmojiKeyboard
|
|
89
|
+
onEmojiSelected={handleEmojiSelect}
|
|
90
|
+
open={open}
|
|
91
|
+
onClose={onClose}
|
|
92
|
+
enableCategoryChangeAnimation={true}
|
|
93
|
+
enableSearchAnimation={true}
|
|
94
|
+
categoryPosition="top"
|
|
95
|
+
enableRecentlyUsed={config?.enableRecentlyUsed ?? true}
|
|
96
|
+
enableSearchBar={config?.enableSearch ?? true}
|
|
97
|
+
enableCategoryChangeGesture={config?.enableCategoryTabs ?? true}
|
|
98
|
+
categoryOrder={config?.categoryOrder as any}
|
|
99
|
+
translation={config?.translation as any}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEmojiPicker Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for emoji picker state management
|
|
5
|
+
* Provides a clean, declarative API for emoji selection
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Open/close state management
|
|
9
|
+
* - Selected emoji tracking
|
|
10
|
+
* - Clear/reset functionality
|
|
11
|
+
* - Simple, reusable hook pattern
|
|
12
|
+
*
|
|
13
|
+
* USAGE:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { useEmojiPicker, EmojiPicker } from '@umituz/react-native-emoji';
|
|
16
|
+
*
|
|
17
|
+
* const MyComponent = () => {
|
|
18
|
+
* const {
|
|
19
|
+
* isOpen,
|
|
20
|
+
* selectedEmoji,
|
|
21
|
+
* openPicker,
|
|
22
|
+
* closePicker,
|
|
23
|
+
* handleEmojiSelect,
|
|
24
|
+
* clearEmoji,
|
|
25
|
+
* } = useEmojiPicker();
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <View>
|
|
29
|
+
* <TouchableOpacity onPress={openPicker}>
|
|
30
|
+
* <Text>{selectedEmoji || 'Select Emoji'}</Text>
|
|
31
|
+
* </TouchableOpacity>
|
|
32
|
+
*
|
|
33
|
+
* <EmojiPicker
|
|
34
|
+
* open={isOpen}
|
|
35
|
+
* onClose={closePicker}
|
|
36
|
+
* onEmojiSelected={handleEmojiSelect}
|
|
37
|
+
* />
|
|
38
|
+
* </View>
|
|
39
|
+
* );
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { useState, useCallback } from 'react';
|
|
45
|
+
import type { EmojiObject } from '../../domain/entities/Emoji';
|
|
46
|
+
|
|
47
|
+
export interface UseEmojiPickerOptions {
|
|
48
|
+
/** Initial emoji value */
|
|
49
|
+
initialEmoji?: string;
|
|
50
|
+
|
|
51
|
+
/** Callback when emoji is selected */
|
|
52
|
+
onEmojiChange?: (emoji: string) => void;
|
|
53
|
+
|
|
54
|
+
/** Whether to auto-close picker after selection */
|
|
55
|
+
autoClose?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface UseEmojiPickerReturn {
|
|
59
|
+
/** Whether the picker is currently open */
|
|
60
|
+
isOpen: boolean;
|
|
61
|
+
|
|
62
|
+
/** Currently selected emoji (null if none) */
|
|
63
|
+
selectedEmoji: string | null;
|
|
64
|
+
|
|
65
|
+
/** Open the emoji picker */
|
|
66
|
+
openPicker: () => void;
|
|
67
|
+
|
|
68
|
+
/** Close the emoji picker */
|
|
69
|
+
closePicker: () => void;
|
|
70
|
+
|
|
71
|
+
/** Toggle picker open/closed */
|
|
72
|
+
togglePicker: () => void;
|
|
73
|
+
|
|
74
|
+
/** Handle emoji selection (use as onEmojiSelected callback) */
|
|
75
|
+
handleEmojiSelect: (emojiObject: EmojiObject) => void;
|
|
76
|
+
|
|
77
|
+
/** Clear the currently selected emoji */
|
|
78
|
+
clearEmoji: () => void;
|
|
79
|
+
|
|
80
|
+
/** Set emoji directly (without opening picker) */
|
|
81
|
+
setEmoji: (emoji: string) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Hook for emoji picker state management
|
|
86
|
+
*/
|
|
87
|
+
export const useEmojiPicker = (
|
|
88
|
+
options?: UseEmojiPickerOptions
|
|
89
|
+
): UseEmojiPickerReturn => {
|
|
90
|
+
const { initialEmoji, onEmojiChange, autoClose = true } = options || {};
|
|
91
|
+
|
|
92
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
93
|
+
const [selectedEmoji, setSelectedEmoji] = useState<string | null>(
|
|
94
|
+
initialEmoji || null
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Open the emoji picker
|
|
99
|
+
*/
|
|
100
|
+
const openPicker = useCallback(() => {
|
|
101
|
+
setIsOpen(true);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Close the emoji picker
|
|
106
|
+
*/
|
|
107
|
+
const closePicker = useCallback(() => {
|
|
108
|
+
setIsOpen(false);
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Toggle picker open/closed
|
|
113
|
+
*/
|
|
114
|
+
const togglePicker = useCallback(() => {
|
|
115
|
+
setIsOpen((prev) => !prev);
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Handle emoji selection
|
|
120
|
+
*/
|
|
121
|
+
const handleEmojiSelect = useCallback(
|
|
122
|
+
(emojiObject: EmojiObject) => {
|
|
123
|
+
setSelectedEmoji(emojiObject.emoji);
|
|
124
|
+
|
|
125
|
+
// Call onChange callback if provided
|
|
126
|
+
if (onEmojiChange) {
|
|
127
|
+
onEmojiChange(emojiObject.emoji);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Auto-close picker if enabled
|
|
131
|
+
if (autoClose) {
|
|
132
|
+
setIsOpen(false);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
[onEmojiChange, autoClose]
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Clear the currently selected emoji
|
|
140
|
+
*/
|
|
141
|
+
const clearEmoji = useCallback(() => {
|
|
142
|
+
setSelectedEmoji(null);
|
|
143
|
+
if (onEmojiChange) {
|
|
144
|
+
onEmojiChange('');
|
|
145
|
+
}
|
|
146
|
+
}, [onEmojiChange]);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Set emoji directly
|
|
150
|
+
*/
|
|
151
|
+
const setEmoji = useCallback(
|
|
152
|
+
(emoji: string) => {
|
|
153
|
+
setSelectedEmoji(emoji);
|
|
154
|
+
if (onEmojiChange) {
|
|
155
|
+
onEmojiChange(emoji);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
[onEmojiChange]
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
isOpen,
|
|
163
|
+
selectedEmoji,
|
|
164
|
+
openPicker,
|
|
165
|
+
closePicker,
|
|
166
|
+
togglePicker,
|
|
167
|
+
handleEmojiSelect,
|
|
168
|
+
clearEmoji,
|
|
169
|
+
setEmoji,
|
|
170
|
+
};
|
|
171
|
+
};
|
package/src/molecules/index.ts
CHANGED
|
@@ -29,3 +29,27 @@ export { List, type ListProps } from './List';
|
|
|
29
29
|
|
|
30
30
|
// Alerts
|
|
31
31
|
export * from './alerts';
|
|
32
|
+
|
|
33
|
+
// Calendar
|
|
34
|
+
export * from './calendar';
|
|
35
|
+
|
|
36
|
+
// Swipe Actions
|
|
37
|
+
export * from './swipe-actions';
|
|
38
|
+
|
|
39
|
+
// Navigation
|
|
40
|
+
export * from './navigation';
|
|
41
|
+
|
|
42
|
+
// Celebration
|
|
43
|
+
export * from './celebration';
|
|
44
|
+
|
|
45
|
+
// Animation
|
|
46
|
+
export * from './animation';
|
|
47
|
+
|
|
48
|
+
// Long Press Menu
|
|
49
|
+
export * from './long-press-menu';
|
|
50
|
+
|
|
51
|
+
// Emoji
|
|
52
|
+
export * from './emoji';
|
|
53
|
+
|
|
54
|
+
// Countdown
|
|
55
|
+
export * from './countdown';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IconName } from '../../../../atoms';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Menu action entity
|
|
5
|
+
* Represents a single action in the long press menu
|
|
6
|
+
*/
|
|
7
|
+
export interface MenuAction {
|
|
8
|
+
/** Unique identifier for the action */
|
|
9
|
+
id: string;
|
|
10
|
+
|
|
11
|
+
/** Icon name from ../../../atoms (Lucide icons) */
|
|
12
|
+
icon: IconName;
|
|
13
|
+
|
|
14
|
+
/** Display label for the action */
|
|
15
|
+
label: string;
|
|
16
|
+
|
|
17
|
+
/** Destructive action (uses error color) */
|
|
18
|
+
destructive?: boolean;
|
|
19
|
+
|
|
20
|
+
/** Disabled state (greyed out, not pressable) */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Predefined menu action types for common use cases
|
|
26
|
+
*/
|
|
27
|
+
export const MenuActionType = {
|
|
28
|
+
EDIT: 'edit',
|
|
29
|
+
DELETE: 'delete',
|
|
30
|
+
SHARE: 'share',
|
|
31
|
+
COPY: 'copy',
|
|
32
|
+
ARCHIVE: 'archive',
|
|
33
|
+
FAVORITE: 'favorite',
|
|
34
|
+
MORE: 'more',
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
export type MenuActionTypeValue = typeof MenuActionType[keyof typeof MenuActionType];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-long-press-menu - Public API
|
|
3
|
+
*
|
|
4
|
+
* Context menus with long press for React Native
|
|
5
|
+
* Features:
|
|
6
|
+
* - iOS/Android-style menus
|
|
7
|
+
* - Haptic feedback
|
|
8
|
+
* - Smooth animations
|
|
9
|
+
* - Customizable actions
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Domain - Entities
|
|
13
|
+
export type { MenuAction, MenuActionTypeValue } from './domain/entities/MenuAction';
|
|
14
|
+
export { MenuActionType } from './domain/entities/MenuAction';
|
|
15
|
+
|
|
16
|
+
// Note: Add more exports as needed when implementation files are found
|