@umituz/react-native-design-system 1.1.2 → 1.2.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.
- package/package.json +1 -1
- package/src/domains/icons/domain/config/IconLibraryConfig.ts +93 -0
- package/src/domains/icons/domain/entities/Icon.ts +143 -0
- package/src/domains/icons/domain/interfaces/IIconAdapter.ts +144 -0
- package/src/domains/icons/index.ts +109 -0
- package/src/domains/icons/infrastructure/adapters/LucideAdapter.ts +100 -0
- package/src/domains/icons/infrastructure/registries/ExpoIconRegistry.ts +191 -0
- package/src/domains/icons/presentation/components/Icon.tsx +132 -0
- package/src/domains/icons/presentation/hooks/useIconLibrary.ts +141 -0
- package/src/index.ts +12 -1
- package/src/presentation/atoms/AtomicIcon.tsx +26 -98
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Universal design system for React Native apps - Domain-Driven Design architecture with Material Design 3 components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon Library Configuration
|
|
3
|
+
*
|
|
4
|
+
* 🔧 SINGLE SOURCE OF TRUTH FOR ICON LIBRARY SELECTION
|
|
5
|
+
*
|
|
6
|
+
* To change icon library:
|
|
7
|
+
* 1. Update package.json with new library
|
|
8
|
+
* 2. Change CURRENT_LIBRARY below
|
|
9
|
+
* 3. Create adapter if needed (infrastructure/adapters/)
|
|
10
|
+
* 4. Done! All apps automatically use new library
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Switch to Material Icons:
|
|
14
|
+
* export const CURRENT_LIBRARY: IconLibraryType = 'material';
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export type IconLibraryType = 'lucide' | 'material' | 'fontawesome' | 'ionicons';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 🔧 CHANGE THIS TO SWITCH ICON LIBRARY
|
|
21
|
+
*/
|
|
22
|
+
export const CURRENT_LIBRARY: IconLibraryType = 'lucide';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Icon Library Configuration
|
|
26
|
+
*/
|
|
27
|
+
export const ICON_LIBRARY_CONFIG = {
|
|
28
|
+
/**
|
|
29
|
+
* Current icon library in use
|
|
30
|
+
*/
|
|
31
|
+
library: CURRENT_LIBRARY,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default icon size (in pixels)
|
|
35
|
+
*/
|
|
36
|
+
defaultSize: 24,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default stroke width for outline icons
|
|
40
|
+
*/
|
|
41
|
+
defaultStrokeWidth: 2,
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Library metadata
|
|
45
|
+
*/
|
|
46
|
+
libraries: {
|
|
47
|
+
lucide: {
|
|
48
|
+
name: 'Lucide',
|
|
49
|
+
package: 'lucide-react-native',
|
|
50
|
+
version: '^0.468.0',
|
|
51
|
+
iconCount: 1639,
|
|
52
|
+
type: 'outline',
|
|
53
|
+
adapter: 'LucideAdapter',
|
|
54
|
+
},
|
|
55
|
+
material: {
|
|
56
|
+
name: 'Material Icons',
|
|
57
|
+
package: '@expo/vector-icons',
|
|
58
|
+
version: '^14.0.0',
|
|
59
|
+
iconCount: 2000,
|
|
60
|
+
type: 'filled',
|
|
61
|
+
adapter: 'MaterialAdapter',
|
|
62
|
+
},
|
|
63
|
+
fontawesome: {
|
|
64
|
+
name: 'Font Awesome',
|
|
65
|
+
package: '@expo/vector-icons',
|
|
66
|
+
version: '^14.0.0',
|
|
67
|
+
iconCount: 1500,
|
|
68
|
+
type: 'solid',
|
|
69
|
+
adapter: 'FontAwesomeAdapter',
|
|
70
|
+
},
|
|
71
|
+
ionicons: {
|
|
72
|
+
name: 'Ionicons',
|
|
73
|
+
package: '@expo/vector-icons',
|
|
74
|
+
version: '^14.0.0',
|
|
75
|
+
iconCount: 1300,
|
|
76
|
+
type: 'outline',
|
|
77
|
+
adapter: 'IoniconsAdapter',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
} as const;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get current library metadata
|
|
84
|
+
*/
|
|
85
|
+
export const getCurrentLibrary = () => {
|
|
86
|
+
return ICON_LIBRARY_CONFIG.libraries[CURRENT_LIBRARY];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Type-safe library check
|
|
91
|
+
* Factory uses Lucide icons only
|
|
92
|
+
*/
|
|
93
|
+
export const isLucideLibrary = () => true;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icons Domain - Entities
|
|
3
|
+
*
|
|
4
|
+
* Core icon types and interfaces for the App Factory.
|
|
5
|
+
* Provides unified access to Lucide icons (design-system) + Expo vector icons.
|
|
6
|
+
*
|
|
7
|
+
* @domain icons
|
|
8
|
+
* @layer domain
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Icon library types
|
|
13
|
+
* - lucide: Lucide React Native icons (already in design-system/AtomicIcon)
|
|
14
|
+
* - material: Material Design icons (@expo/vector-icons)
|
|
15
|
+
* - fontawesome: FontAwesome icons (@expo/vector-icons)
|
|
16
|
+
* - ionicons: Ionicons (@expo/vector-icons)
|
|
17
|
+
*/
|
|
18
|
+
export enum IconLibrary {
|
|
19
|
+
LUCIDE = 'lucide',
|
|
20
|
+
MATERIAL = 'material',
|
|
21
|
+
FONTAWESOME = 'fontawesome',
|
|
22
|
+
IONICONS = 'ionicons',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Icon category for organization
|
|
27
|
+
*/
|
|
28
|
+
export enum IconCategory {
|
|
29
|
+
ALL = 'all',
|
|
30
|
+
NAVIGATION = 'navigation',
|
|
31
|
+
ACTION = 'action',
|
|
32
|
+
SOCIAL = 'social',
|
|
33
|
+
COMMUNICATION = 'communication',
|
|
34
|
+
MEDIA = 'media',
|
|
35
|
+
BUSINESS = 'business',
|
|
36
|
+
WEATHER = 'weather',
|
|
37
|
+
SYMBOLS = 'symbols',
|
|
38
|
+
EMOJI = 'emoji',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Icon metadata for registry
|
|
43
|
+
*/
|
|
44
|
+
export interface IconMetadata {
|
|
45
|
+
name: string;
|
|
46
|
+
library: IconLibrary;
|
|
47
|
+
category: IconCategory;
|
|
48
|
+
tags: string[];
|
|
49
|
+
searchTerms: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Icon registry interface
|
|
54
|
+
*/
|
|
55
|
+
export interface IIconRegistry {
|
|
56
|
+
/**
|
|
57
|
+
* Get all icons in the library
|
|
58
|
+
*/
|
|
59
|
+
getAllIcons(): IconMetadata[];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get icons by category
|
|
63
|
+
*/
|
|
64
|
+
getIconsByCategory(category: IconCategory): IconMetadata[];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Search icons by query
|
|
68
|
+
*/
|
|
69
|
+
searchIcons(query: string): IconMetadata[];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get icon metadata by name
|
|
73
|
+
*/
|
|
74
|
+
getIconMetadata(name: string): IconMetadata | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Icon picker configuration
|
|
79
|
+
*/
|
|
80
|
+
export interface IconPickerConfig {
|
|
81
|
+
libraries: IconLibrary[];
|
|
82
|
+
categories: IconCategory[];
|
|
83
|
+
showSearch: boolean;
|
|
84
|
+
multiSelect: boolean;
|
|
85
|
+
maxSelections?: number;
|
|
86
|
+
onSelect: (icons: IconMetadata[]) => void;
|
|
87
|
+
onCancel?: () => void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Icon constants
|
|
92
|
+
*/
|
|
93
|
+
export const ICON_CONSTANTS = {
|
|
94
|
+
DEFAULT_SIZE: 24,
|
|
95
|
+
DEFAULT_COLOR: 'onSurface',
|
|
96
|
+
DEFAULT_LIBRARY: IconLibrary.LUCIDE,
|
|
97
|
+
MAX_SEARCH_RESULTS: 50,
|
|
98
|
+
} as const;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Icon utility functions
|
|
102
|
+
*/
|
|
103
|
+
export const IconUtils = {
|
|
104
|
+
/**
|
|
105
|
+
* Normalize icon name for search
|
|
106
|
+
*/
|
|
107
|
+
normalizeIconName: (name: string): string => {
|
|
108
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Match search query against icon metadata
|
|
113
|
+
*/
|
|
114
|
+
matchesSearch: (icon: IconMetadata, query: string): boolean => {
|
|
115
|
+
const normalizedQuery = IconUtils.normalizeIconName(query);
|
|
116
|
+
const normalizedName = IconUtils.normalizeIconName(icon.name);
|
|
117
|
+
const normalizedTags = icon.tags.map(tag => IconUtils.normalizeIconName(tag));
|
|
118
|
+
const normalizedTerms = icon.searchTerms.map(term => IconUtils.normalizeIconName(term));
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
normalizedName.includes(normalizedQuery) ||
|
|
122
|
+
normalizedTags.some(tag => tag.includes(normalizedQuery)) ||
|
|
123
|
+
normalizedTerms.some(term => term.includes(normalizedQuery))
|
|
124
|
+
);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Filter icons by category
|
|
129
|
+
*/
|
|
130
|
+
filterByCategory: (icons: IconMetadata[], category: IconCategory): IconMetadata[] => {
|
|
131
|
+
if (category === IconCategory.ALL) {
|
|
132
|
+
return icons;
|
|
133
|
+
}
|
|
134
|
+
return icons.filter(icon => icon.category === category);
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Sort icons alphabetically
|
|
139
|
+
*/
|
|
140
|
+
sortByName: (icons: IconMetadata[]): IconMetadata[] => {
|
|
141
|
+
return [...icons].sort((a, b) => a.name.localeCompare(b.name));
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* Universal interface that all icon library adapters must implement.
|
|
5
|
+
* This allows seamless switching between icon libraries.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Implementing for a new library:
|
|
9
|
+
* export const MyLibraryAdapter: IIconAdapter = {
|
|
10
|
+
* getIconComponent: (name) => MyIcons[name],
|
|
11
|
+
* getIconSize: (size) => sizeMap[size],
|
|
12
|
+
* getIconColor: (color, tokens) => colorMap[color],
|
|
13
|
+
* getAllIcons: () => Object.keys(MyIcons),
|
|
14
|
+
* };
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { DesignTokens } from '@umituz/react-native-design-system';
|
|
18
|
+
|
|
19
|
+
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
|
20
|
+
export type IconColor =
|
|
21
|
+
| 'primary'
|
|
22
|
+
| 'secondary'
|
|
23
|
+
| 'success'
|
|
24
|
+
| 'warning'
|
|
25
|
+
| 'error'
|
|
26
|
+
| 'info'
|
|
27
|
+
| 'onSurface'
|
|
28
|
+
| 'surfaceVariant'
|
|
29
|
+
| 'onPrimary'
|
|
30
|
+
| 'onSecondary'
|
|
31
|
+
| 'textInverse';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Icon Adapter Interface
|
|
35
|
+
* All icon library adapters must implement this interface
|
|
36
|
+
*/
|
|
37
|
+
export interface IIconAdapter {
|
|
38
|
+
/**
|
|
39
|
+
* Get the icon component for a given icon name
|
|
40
|
+
* @param name - Icon name (library-specific)
|
|
41
|
+
* @returns Icon component or null if not found
|
|
42
|
+
*/
|
|
43
|
+
getIconComponent: (name: string) => any | null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Convert semantic size to pixel size
|
|
47
|
+
* @param size - Semantic size (xs, sm, md, lg, xl, xxl)
|
|
48
|
+
* @param customSize - Optional custom pixel size
|
|
49
|
+
* @returns Pixel size
|
|
50
|
+
*/
|
|
51
|
+
getIconSize: (size: IconSize, customSize?: number) => number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert semantic color to hex color
|
|
55
|
+
* @param color - Semantic color name
|
|
56
|
+
* @param tokens - Design tokens for theme colors
|
|
57
|
+
* @param customColor - Optional custom hex color
|
|
58
|
+
* @returns Hex color string
|
|
59
|
+
*/
|
|
60
|
+
getIconColor: (
|
|
61
|
+
color: IconColor,
|
|
62
|
+
tokens: DesignTokens,
|
|
63
|
+
customColor?: string
|
|
64
|
+
) => string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get all available icon names for this library
|
|
68
|
+
* @returns Array of icon names
|
|
69
|
+
*/
|
|
70
|
+
getAllIcons: () => string[];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if an icon exists in the library
|
|
74
|
+
* @param name - Icon name to check
|
|
75
|
+
* @returns True if icon exists
|
|
76
|
+
*/
|
|
77
|
+
hasIcon: (name: string) => boolean;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get default stroke width for outline icons
|
|
81
|
+
* @returns Stroke width number
|
|
82
|
+
*/
|
|
83
|
+
getStrokeWidth?: () => number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Icon Props - Universal props for Icon component
|
|
88
|
+
*/
|
|
89
|
+
export interface IconProps {
|
|
90
|
+
/**
|
|
91
|
+
* Icon name (library-specific)
|
|
92
|
+
*/
|
|
93
|
+
name: string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Icon size preset
|
|
97
|
+
*/
|
|
98
|
+
size?: IconSize;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Custom pixel size (overrides size preset)
|
|
102
|
+
*/
|
|
103
|
+
customSize?: number;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Semantic color
|
|
107
|
+
*/
|
|
108
|
+
color?: IconColor;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Custom hex color (overrides semantic color)
|
|
112
|
+
*/
|
|
113
|
+
customColor?: string;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Stroke width for outline icons
|
|
117
|
+
*/
|
|
118
|
+
strokeWidth?: number;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Background circle for icon
|
|
122
|
+
*/
|
|
123
|
+
withBackground?: boolean;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Background color
|
|
127
|
+
*/
|
|
128
|
+
backgroundColor?: string;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Accessibility label
|
|
132
|
+
*/
|
|
133
|
+
accessibilityLabel?: string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Test ID
|
|
137
|
+
*/
|
|
138
|
+
testID?: string;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Custom styles
|
|
142
|
+
*/
|
|
143
|
+
style?: any;
|
|
144
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icons Domain - Centralized Icon System
|
|
3
|
+
*
|
|
4
|
+
* 🎯 SINGLE SOURCE OF TRUTH FOR ALL ICONS
|
|
5
|
+
*
|
|
6
|
+
* Universal icon library system with easy library switching.
|
|
7
|
+
* Change icon library = change one config file!
|
|
8
|
+
*
|
|
9
|
+
* @domain icons
|
|
10
|
+
* @enabled true (All apps)
|
|
11
|
+
*
|
|
12
|
+
* ARCHITECTURE:
|
|
13
|
+
* ```
|
|
14
|
+
* domains/icons/
|
|
15
|
+
* ├── domain/
|
|
16
|
+
* │ ├── config/
|
|
17
|
+
* │ │ └── IconLibraryConfig.ts 🔧 Change library here!
|
|
18
|
+
* │ └── interfaces/
|
|
19
|
+
* │ └── IIconAdapter.ts Interface for adapters
|
|
20
|
+
* ├── infrastructure/
|
|
21
|
+
* │ └── adapters/
|
|
22
|
+
* │ ├── LucideAdapter.ts Current: Lucide (1,639 icons)
|
|
23
|
+
* │ ├── MaterialAdapter.ts Future: Material Icons
|
|
24
|
+
* │ └── FontAwesomeAdapter.ts Future: Font Awesome
|
|
25
|
+
* └── presentation/
|
|
26
|
+
* └── components/
|
|
27
|
+
* └── Icon.tsx Universal Icon component
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* USAGE:
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { Icon } from '@domains/icons';
|
|
33
|
+
*
|
|
34
|
+
* // Basic usage
|
|
35
|
+
* <Icon name="Settings" size="md" color="primary" />
|
|
36
|
+
*
|
|
37
|
+
* // Custom size and color
|
|
38
|
+
* <Icon name="Heart" customSize={32} customColor="#FF0000" />
|
|
39
|
+
*
|
|
40
|
+
* // With background
|
|
41
|
+
* <Icon name="Info" size="lg" withBackground backgroundColor="#667eea" />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* 🔧 TO CHANGE ICON LIBRARY:
|
|
45
|
+
*
|
|
46
|
+
* Step 1: Update package.json
|
|
47
|
+
* ```json
|
|
48
|
+
* {
|
|
49
|
+
* "dependencies": {
|
|
50
|
+
* "new-icon-library": "^1.0.0"
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* Step 2: Change CURRENT_LIBRARY in domain/config/IconLibraryConfig.ts
|
|
56
|
+
* ```typescript
|
|
57
|
+
* export const CURRENT_LIBRARY: IconLibraryType = 'material'; // Changed!
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Step 3: Create adapter (if needed)
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // infrastructure/adapters/MaterialAdapter.ts
|
|
63
|
+
* export const MaterialAdapter: IIconAdapter = {
|
|
64
|
+
* getIconComponent: (name) => MaterialIcons[name],
|
|
65
|
+
* // ... implement interface
|
|
66
|
+
* };
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* Step 4: Done! All apps use new library automatically ✅
|
|
70
|
+
*
|
|
71
|
+
* CURRENT LIBRARY: Lucide (1,639 icons)
|
|
72
|
+
* @see https://lucide.dev/icons/
|
|
73
|
+
*
|
|
74
|
+
* DEPENDENCIES:
|
|
75
|
+
* - lucide-react-native: ^0.468.0 (Current library)
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// PRESENTATION LAYER - Universal Icon Component
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
export { Icon } from './presentation/components/Icon';
|
|
83
|
+
export type {
|
|
84
|
+
IconProps,
|
|
85
|
+
IconSize,
|
|
86
|
+
IconColor,
|
|
87
|
+
IconName,
|
|
88
|
+
} from './presentation/components/Icon';
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// DOMAIN LAYER - Configuration & Interfaces
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
CURRENT_LIBRARY,
|
|
96
|
+
ICON_LIBRARY_CONFIG,
|
|
97
|
+
getCurrentLibrary,
|
|
98
|
+
isLucideLibrary,
|
|
99
|
+
} from './domain/config/IconLibraryConfig';
|
|
100
|
+
export type { IconLibraryType } from './domain/config/IconLibraryConfig';
|
|
101
|
+
|
|
102
|
+
export type { IIconAdapter } from './domain/interfaces/IIconAdapter';
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// INFRASTRUCTURE LAYER - Adapters
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
export { LucideAdapter } from './infrastructure/adapters/LucideAdapter';
|
|
109
|
+
export type { LucideIconName } from './infrastructure/adapters/LucideAdapter';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lucide Icon Library Adapter
|
|
3
|
+
*
|
|
4
|
+
* Adapter for lucide-react-native library (1,639 icons).
|
|
5
|
+
* Implements IIconAdapter interface for seamless library switching.
|
|
6
|
+
*
|
|
7
|
+
* @library lucide-react-native
|
|
8
|
+
* @version ^0.468.0
|
|
9
|
+
* @icons 1,639
|
|
10
|
+
* @type outline
|
|
11
|
+
*
|
|
12
|
+
* @see https://lucide.dev/icons/
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { icons } from 'lucide-react-native';
|
|
16
|
+
import type { IIconAdapter, IconSize, IconColor } from '../../domain/interfaces/IIconAdapter';
|
|
17
|
+
import type { DesignTokens } from '@umituz/react-native-design-system';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Size mapping: Semantic → Pixels
|
|
21
|
+
*/
|
|
22
|
+
const SIZE_MAP: Record<IconSize, number> = {
|
|
23
|
+
xs: 16,
|
|
24
|
+
sm: 20,
|
|
25
|
+
md: 24,
|
|
26
|
+
lg: 28,
|
|
27
|
+
xl: 32,
|
|
28
|
+
xxl: 40,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lucide Adapter Implementation
|
|
33
|
+
*/
|
|
34
|
+
export const LucideAdapter: IIconAdapter = {
|
|
35
|
+
/**
|
|
36
|
+
* Get Lucide icon component by name
|
|
37
|
+
*/
|
|
38
|
+
getIconComponent: (name: string) => {
|
|
39
|
+
const IconComponent = icons[name as keyof typeof icons];
|
|
40
|
+
if (!IconComponent) {
|
|
41
|
+
console.warn(`[LucideAdapter] Icon "${name}" not found in Lucide library`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return IconComponent;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get icon size in pixels
|
|
49
|
+
*/
|
|
50
|
+
getIconSize: (size: IconSize, customSize?: number) => {
|
|
51
|
+
return customSize || SIZE_MAP[size];
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get icon color from theme
|
|
56
|
+
*/
|
|
57
|
+
getIconColor: (color: IconColor, tokens: DesignTokens, customColor?: string) => {
|
|
58
|
+
if (customColor) return customColor;
|
|
59
|
+
|
|
60
|
+
const colorMap: Record<IconColor, string> = {
|
|
61
|
+
primary: tokens.colors.primary,
|
|
62
|
+
secondary: tokens.colors.secondary,
|
|
63
|
+
success: tokens.colors.success,
|
|
64
|
+
warning: tokens.colors.warning,
|
|
65
|
+
error: tokens.colors.error,
|
|
66
|
+
info: tokens.colors.info,
|
|
67
|
+
onSurface: tokens.colors.onSurface,
|
|
68
|
+
surfaceVariant: tokens.colors.surfaceVariant,
|
|
69
|
+
onPrimary: tokens.colors.onPrimary,
|
|
70
|
+
onSecondary: tokens.colors.onSecondary,
|
|
71
|
+
textInverse: tokens.colors.textInverse,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return colorMap[color];
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get all available Lucide icon names
|
|
79
|
+
*/
|
|
80
|
+
getAllIcons: () => {
|
|
81
|
+
return Object.keys(icons);
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if icon exists in Lucide library
|
|
86
|
+
*/
|
|
87
|
+
hasIcon: (name: string) => {
|
|
88
|
+
return name in icons;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Default stroke width for Lucide outline icons
|
|
93
|
+
*/
|
|
94
|
+
getStrokeWidth: () => 2,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Lucide icon names type (for TypeScript autocomplete)
|
|
99
|
+
*/
|
|
100
|
+
export type LucideIconName = keyof typeof icons;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icons Domain - Expo Vector Icons Registry
|
|
3
|
+
*
|
|
4
|
+
* Registry for @expo/vector-icons (Material, FontAwesome, Ionicons).
|
|
5
|
+
* Provides metadata and search capabilities for 1000+ icons.
|
|
6
|
+
*
|
|
7
|
+
* @domain icons
|
|
8
|
+
* @layer infrastructure
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { IIconRegistry, IconMetadata } from '@domains/icons/domain/entities/Icon';
|
|
12
|
+
import { IconLibrary, IconCategory } from '@domains/icons/domain/entities/Icon';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Expo vector icons registry
|
|
16
|
+
* Maps common Material Design, FontAwesome, and Ionicons
|
|
17
|
+
*/
|
|
18
|
+
export class ExpoIconRegistry implements IIconRegistry {
|
|
19
|
+
private icons: IconMetadata[] = [
|
|
20
|
+
// Navigation icons (Material Design)
|
|
21
|
+
{
|
|
22
|
+
name: 'home',
|
|
23
|
+
library: IconLibrary.MATERIAL,
|
|
24
|
+
category: IconCategory.NAVIGATION,
|
|
25
|
+
tags: ['house', 'main', 'start'],
|
|
26
|
+
searchTerms: ['home', 'house', 'main', 'dashboard'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'arrow-back',
|
|
30
|
+
library: IconLibrary.MATERIAL,
|
|
31
|
+
category: IconCategory.NAVIGATION,
|
|
32
|
+
tags: ['back', 'previous', 'return'],
|
|
33
|
+
searchTerms: ['arrow', 'back', 'previous', 'navigate'],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'menu',
|
|
37
|
+
library: IconLibrary.MATERIAL,
|
|
38
|
+
category: IconCategory.NAVIGATION,
|
|
39
|
+
tags: ['hamburger', 'drawer', 'navigation'],
|
|
40
|
+
searchTerms: ['menu', 'hamburger', 'navigation', 'drawer'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'search',
|
|
44
|
+
library: IconLibrary.MATERIAL,
|
|
45
|
+
category: IconCategory.ACTION,
|
|
46
|
+
tags: ['find', 'lookup', 'query'],
|
|
47
|
+
searchTerms: ['search', 'find', 'lookup', 'magnify'],
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Action icons (Material Design)
|
|
51
|
+
{
|
|
52
|
+
name: 'add',
|
|
53
|
+
library: IconLibrary.MATERIAL,
|
|
54
|
+
category: IconCategory.ACTION,
|
|
55
|
+
tags: ['plus', 'create', 'new'],
|
|
56
|
+
searchTerms: ['add', 'plus', 'create', 'new'],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'delete',
|
|
60
|
+
library: IconLibrary.MATERIAL,
|
|
61
|
+
category: IconCategory.ACTION,
|
|
62
|
+
tags: ['remove', 'trash', 'bin'],
|
|
63
|
+
searchTerms: ['delete', 'remove', 'trash', 'garbage'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'edit',
|
|
67
|
+
library: IconLibrary.MATERIAL,
|
|
68
|
+
category: IconCategory.ACTION,
|
|
69
|
+
tags: ['pencil', 'modify', 'update'],
|
|
70
|
+
searchTerms: ['edit', 'pencil', 'modify', 'write'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'settings',
|
|
74
|
+
library: IconLibrary.MATERIAL,
|
|
75
|
+
category: IconCategory.ACTION,
|
|
76
|
+
tags: ['gear', 'preferences', 'config'],
|
|
77
|
+
searchTerms: ['settings', 'gear', 'preferences', 'options'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'favorite',
|
|
81
|
+
library: IconLibrary.MATERIAL,
|
|
82
|
+
category: IconCategory.ACTION,
|
|
83
|
+
tags: ['heart', 'like', 'love'],
|
|
84
|
+
searchTerms: ['favorite', 'heart', 'like', 'love'],
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Social icons (FontAwesome)
|
|
88
|
+
{
|
|
89
|
+
name: 'facebook',
|
|
90
|
+
library: IconLibrary.FONTAWESOME,
|
|
91
|
+
category: IconCategory.SOCIAL,
|
|
92
|
+
tags: ['fb', 'social', 'network'],
|
|
93
|
+
searchTerms: ['facebook', 'fb', 'social', 'meta'],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'twitter',
|
|
97
|
+
library: IconLibrary.FONTAWESOME,
|
|
98
|
+
category: IconCategory.SOCIAL,
|
|
99
|
+
tags: ['x', 'social', 'tweet'],
|
|
100
|
+
searchTerms: ['twitter', 'x', 'social', 'tweet'],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'instagram',
|
|
104
|
+
library: IconLibrary.FONTAWESOME,
|
|
105
|
+
category: IconCategory.SOCIAL,
|
|
106
|
+
tags: ['ig', 'social', 'photo'],
|
|
107
|
+
searchTerms: ['instagram', 'ig', 'social', 'photo'],
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// Communication icons (Ionicons)
|
|
111
|
+
{
|
|
112
|
+
name: 'mail',
|
|
113
|
+
library: IconLibrary.IONICONS,
|
|
114
|
+
category: IconCategory.COMMUNICATION,
|
|
115
|
+
tags: ['email', 'message', 'envelope'],
|
|
116
|
+
searchTerms: ['mail', 'email', 'message', 'contact'],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'call',
|
|
120
|
+
library: IconLibrary.IONICONS,
|
|
121
|
+
category: IconCategory.COMMUNICATION,
|
|
122
|
+
tags: ['phone', 'telephone', 'dial'],
|
|
123
|
+
searchTerms: ['call', 'phone', 'telephone', 'dial'],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'notifications',
|
|
127
|
+
library: IconLibrary.IONICONS,
|
|
128
|
+
category: IconCategory.COMMUNICATION,
|
|
129
|
+
tags: ['bell', 'alert', 'reminder'],
|
|
130
|
+
searchTerms: ['notifications', 'bell', 'alert', 'reminder'],
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Media icons (Material Design)
|
|
134
|
+
{
|
|
135
|
+
name: 'camera',
|
|
136
|
+
library: IconLibrary.MATERIAL,
|
|
137
|
+
category: IconCategory.MEDIA,
|
|
138
|
+
tags: ['photo', 'picture', 'snapshot'],
|
|
139
|
+
searchTerms: ['camera', 'photo', 'picture', 'image'],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'image',
|
|
143
|
+
library: IconLibrary.MATERIAL,
|
|
144
|
+
category: IconCategory.MEDIA,
|
|
145
|
+
tags: ['photo', 'picture', 'gallery'],
|
|
146
|
+
searchTerms: ['image', 'photo', 'picture', 'gallery'],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'videocam',
|
|
150
|
+
library: IconLibrary.MATERIAL,
|
|
151
|
+
category: IconCategory.MEDIA,
|
|
152
|
+
tags: ['video', 'camera', 'record'],
|
|
153
|
+
searchTerms: ['video', 'camera', 'record', 'film'],
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
getAllIcons(): IconMetadata[] {
|
|
158
|
+
return this.icons;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getIconsByCategory(category: IconCategory): IconMetadata[] {
|
|
162
|
+
if (category === IconCategory.ALL) {
|
|
163
|
+
return this.icons;
|
|
164
|
+
}
|
|
165
|
+
return this.icons.filter(icon => icon.category === category);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
searchIcons(query: string): IconMetadata[] {
|
|
169
|
+
if (!query.trim()) {
|
|
170
|
+
return this.icons;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const normalizedQuery = query.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
174
|
+
|
|
175
|
+
return this.icons.filter(icon => {
|
|
176
|
+
const normalizedName = icon.name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
177
|
+
const normalizedTags = icon.tags.map(tag => tag.toLowerCase().replace(/[^a-z0-9]/g, ''));
|
|
178
|
+
const normalizedTerms = icon.searchTerms.map(term => term.toLowerCase().replace(/[^a-z0-9]/g, ''));
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
normalizedName.includes(normalizedQuery) ||
|
|
182
|
+
normalizedTags.some(tag => tag.includes(normalizedQuery)) ||
|
|
183
|
+
normalizedTerms.some(term => term.includes(normalizedQuery))
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getIconMetadata(name: string): IconMetadata | null {
|
|
189
|
+
return this.icons.find(icon => icon.name === name) || null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Icon Component
|
|
3
|
+
*
|
|
4
|
+
* 🎯 SINGLE ICON COMPONENT FOR ALL APPS
|
|
5
|
+
*
|
|
6
|
+
* Automatically uses the icon library configured in IconLibraryConfig.
|
|
7
|
+
* Change library = change config, no code changes needed!
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { Icon } from '@domains/icons';
|
|
12
|
+
*
|
|
13
|
+
* // Basic usage
|
|
14
|
+
* <Icon name="Settings" size="md" color="primary" />
|
|
15
|
+
*
|
|
16
|
+
* // Custom size and color
|
|
17
|
+
* <Icon name="Heart" customSize={32} customColor="#FF0000" />
|
|
18
|
+
*
|
|
19
|
+
* // With background
|
|
20
|
+
* <Icon name="Info" size="lg" withBackground backgroundColor="#667eea" />
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 🔧 To change icon library:
|
|
24
|
+
* 1. Update CURRENT_LIBRARY in domain/config/IconLibraryConfig.ts
|
|
25
|
+
* 2. Done! All apps use new library automatically
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import React from 'react';
|
|
29
|
+
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
|
30
|
+
import { useAppDesignTokens } from '../../../../presentation/hooks/useAppDesignTokens';
|
|
31
|
+
import { CURRENT_LIBRARY } from '../../domain/config/IconLibraryConfig';
|
|
32
|
+
import { LucideAdapter } from '../../infrastructure/adapters/LucideAdapter';
|
|
33
|
+
import type { IconProps } from '../../domain/interfaces/IIconAdapter';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get adapter based on current library configuration
|
|
37
|
+
*/
|
|
38
|
+
const getAdapter = () => {
|
|
39
|
+
switch (CURRENT_LIBRARY) {
|
|
40
|
+
case 'lucide':
|
|
41
|
+
return LucideAdapter;
|
|
42
|
+
// Future: Add more adapters here
|
|
43
|
+
// case 'material':
|
|
44
|
+
// return MaterialAdapter;
|
|
45
|
+
// case 'fontawesome':
|
|
46
|
+
// return FontAwesomeAdapter;
|
|
47
|
+
default:
|
|
48
|
+
return LucideAdapter;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Universal Icon Component
|
|
54
|
+
*/
|
|
55
|
+
export const Icon: React.FC<IconProps> = ({
|
|
56
|
+
name,
|
|
57
|
+
size = 'md',
|
|
58
|
+
customSize,
|
|
59
|
+
color = 'onSurface',
|
|
60
|
+
customColor,
|
|
61
|
+
strokeWidth,
|
|
62
|
+
withBackground = false,
|
|
63
|
+
backgroundColor,
|
|
64
|
+
accessibilityLabel,
|
|
65
|
+
testID,
|
|
66
|
+
style,
|
|
67
|
+
}) => {
|
|
68
|
+
const tokens = useAppDesignTokens();
|
|
69
|
+
const adapter = getAdapter();
|
|
70
|
+
|
|
71
|
+
// Get icon component from adapter
|
|
72
|
+
const IconComponent = adapter.getIconComponent(name);
|
|
73
|
+
|
|
74
|
+
if (!IconComponent) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Calculate icon size
|
|
79
|
+
const iconSize = adapter.getIconSize(size, customSize);
|
|
80
|
+
|
|
81
|
+
// Get icon color from theme
|
|
82
|
+
const iconColor = adapter.getIconColor(color, tokens, customColor);
|
|
83
|
+
|
|
84
|
+
// Get stroke width (for outline icons)
|
|
85
|
+
const iconStrokeWidth = strokeWidth || adapter.getStrokeWidth?.() || 2;
|
|
86
|
+
|
|
87
|
+
// Container size (slightly larger than icon)
|
|
88
|
+
const containerSize = iconSize + 8;
|
|
89
|
+
|
|
90
|
+
const containerStyles: StyleProp<ViewStyle> = [
|
|
91
|
+
withBackground && {
|
|
92
|
+
width: containerSize,
|
|
93
|
+
height: containerSize,
|
|
94
|
+
borderRadius: containerSize / 2,
|
|
95
|
+
backgroundColor: backgroundColor || tokens.colors.surfaceVariant,
|
|
96
|
+
justifyContent: 'center',
|
|
97
|
+
alignItems: 'center',
|
|
98
|
+
},
|
|
99
|
+
style,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const IconElement = (
|
|
103
|
+
<IconComponent
|
|
104
|
+
size={iconSize}
|
|
105
|
+
color={iconColor}
|
|
106
|
+
strokeWidth={iconStrokeWidth}
|
|
107
|
+
accessibilityLabel={accessibilityLabel || `${name} icon`}
|
|
108
|
+
testID={testID}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (withBackground) {
|
|
113
|
+
return (
|
|
114
|
+
<View style={containerStyles} testID={`${testID}-container`}>
|
|
115
|
+
{IconElement}
|
|
116
|
+
</View>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return IconElement;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Export icon types for convenience
|
|
125
|
+
*/
|
|
126
|
+
export type { IconProps } from '../../domain/interfaces/IIconAdapter';
|
|
127
|
+
export { type IconSize, type IconColor } from '../../domain/interfaces/IIconAdapter';
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Export current library's icon names for TypeScript autocomplete
|
|
131
|
+
*/
|
|
132
|
+
export type { LucideIconName as IconName } from '../../infrastructure/adapters/LucideAdapter';
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icons Domain - useIconLibrary Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for accessing icon libraries (Lucide + Expo vector icons).
|
|
5
|
+
* Provides search, filter, and metadata access for all available icons.
|
|
6
|
+
*
|
|
7
|
+
* @domain icons
|
|
8
|
+
* @layer presentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useMemo, useState, useCallback } from 'react';
|
|
12
|
+
import type { IconMetadata } from '@domains/icons/domain/entities/Icon';
|
|
13
|
+
import { IconLibrary, IconCategory, IconUtils, ICON_CONSTANTS } from '@domains/icons/domain/entities/Icon';
|
|
14
|
+
import { ExpoIconRegistry } from '@domains/icons/infrastructure/registries/ExpoIconRegistry';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook for icon library access and search
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { icons, searchIcons, filterByCategory } = useIconLibrary();
|
|
21
|
+
*
|
|
22
|
+
* // Search icons
|
|
23
|
+
* const results = searchIcons('home');
|
|
24
|
+
*
|
|
25
|
+
* // Filter by category
|
|
26
|
+
* const navIcons = filterByCategory(IconCategory.NAVIGATION);
|
|
27
|
+
*/
|
|
28
|
+
export const useIconLibrary = (defaultLibrary: IconLibrary = ICON_CONSTANTS.DEFAULT_LIBRARY) => {
|
|
29
|
+
const [selectedLibrary, setSelectedLibrary] = useState<IconLibrary>(defaultLibrary);
|
|
30
|
+
const [selectedCategory, setSelectedCategory] = useState<IconCategory>(IconCategory.ALL);
|
|
31
|
+
|
|
32
|
+
// Initialize registry
|
|
33
|
+
const registry = useMemo(() => new ExpoIconRegistry(), []);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get all icons from the selected library
|
|
37
|
+
*/
|
|
38
|
+
const icons = useMemo(() => {
|
|
39
|
+
const allIcons = registry.getAllIcons();
|
|
40
|
+
return selectedLibrary === IconLibrary.LUCIDE
|
|
41
|
+
? [] // Lucide icons are in design-system/AtomicIcon
|
|
42
|
+
: allIcons.filter(icon => icon.library === selectedLibrary);
|
|
43
|
+
}, [registry, selectedLibrary]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get icons filtered by category
|
|
47
|
+
*/
|
|
48
|
+
const iconsByCategory = useMemo(() => {
|
|
49
|
+
return IconUtils.filterByCategory(icons, selectedCategory);
|
|
50
|
+
}, [icons, selectedCategory]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Search icons by query
|
|
54
|
+
*/
|
|
55
|
+
const searchIcons = useCallback(
|
|
56
|
+
(query: string): IconMetadata[] => {
|
|
57
|
+
if (!query.trim()) {
|
|
58
|
+
return iconsByCategory;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const results = registry.searchIcons(query);
|
|
62
|
+
const filteredResults = selectedLibrary === IconLibrary.LUCIDE
|
|
63
|
+
? results
|
|
64
|
+
: results.filter(icon => icon.library === selectedLibrary);
|
|
65
|
+
|
|
66
|
+
return IconUtils.filterByCategory(filteredResults, selectedCategory)
|
|
67
|
+
.slice(0, ICON_CONSTANTS.MAX_SEARCH_RESULTS);
|
|
68
|
+
},
|
|
69
|
+
[registry, selectedLibrary, selectedCategory, iconsByCategory]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Filter icons by category
|
|
74
|
+
*/
|
|
75
|
+
const filterByCategory = useCallback(
|
|
76
|
+
(category: IconCategory): IconMetadata[] => {
|
|
77
|
+
return IconUtils.filterByCategory(icons, category);
|
|
78
|
+
},
|
|
79
|
+
[icons]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get icon metadata by name
|
|
84
|
+
*/
|
|
85
|
+
const getIconMetadata = useCallback(
|
|
86
|
+
(name: string): IconMetadata | null => {
|
|
87
|
+
return registry.getIconMetadata(name);
|
|
88
|
+
},
|
|
89
|
+
[registry]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get all available categories
|
|
94
|
+
*/
|
|
95
|
+
const availableCategories = useMemo(() => {
|
|
96
|
+
return Object.values(IconCategory);
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all available libraries
|
|
101
|
+
*/
|
|
102
|
+
const availableLibraries = useMemo(() => {
|
|
103
|
+
return Object.values(IconLibrary);
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Switch library
|
|
108
|
+
*/
|
|
109
|
+
const switchLibrary = useCallback((library: IconLibrary) => {
|
|
110
|
+
setSelectedLibrary(library);
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Switch category
|
|
115
|
+
*/
|
|
116
|
+
const switchCategory = useCallback((category: IconCategory) => {
|
|
117
|
+
setSelectedCategory(category);
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
// Current state
|
|
122
|
+
selectedLibrary,
|
|
123
|
+
selectedCategory,
|
|
124
|
+
icons: iconsByCategory,
|
|
125
|
+
|
|
126
|
+
// Search & filter
|
|
127
|
+
searchIcons,
|
|
128
|
+
filterByCategory,
|
|
129
|
+
getIconMetadata,
|
|
130
|
+
|
|
131
|
+
// Library & category management
|
|
132
|
+
switchLibrary,
|
|
133
|
+
switchCategory,
|
|
134
|
+
availableCategories,
|
|
135
|
+
availableLibraries,
|
|
136
|
+
|
|
137
|
+
// Constants
|
|
138
|
+
IconLibrary,
|
|
139
|
+
IconCategory,
|
|
140
|
+
};
|
|
141
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -350,5 +350,16 @@ export {
|
|
|
350
350
|
|
|
351
351
|
export {
|
|
352
352
|
useDesignSystemTheme,
|
|
353
|
-
type ThemeMode,
|
|
354
353
|
} from './infrastructure/theme/globalThemeStore';
|
|
354
|
+
|
|
355
|
+
// =============================================================================
|
|
356
|
+
// ICONS DOMAIN - Universal Icon System
|
|
357
|
+
// =============================================================================
|
|
358
|
+
|
|
359
|
+
export {
|
|
360
|
+
Icon,
|
|
361
|
+
type IconProps,
|
|
362
|
+
type IconSize,
|
|
363
|
+
type IconColor,
|
|
364
|
+
type IconName,
|
|
365
|
+
} from './domains/icons/presentation/components/Icon';
|
|
@@ -1,113 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AtomicIcon - Design System Icon Component
|
|
2
|
+
* AtomicIcon - Atomic Design System Icon Component
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Provides
|
|
4
|
+
* Wrapper for the universal Icon component from @domains/icons
|
|
5
|
+
* Provides backward compatibility with AtomicIcon naming convention
|
|
6
|
+
* while leveraging the full power of the icons domain architecture.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import React from 'react';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Icon size presets
|
|
14
|
-
*/
|
|
15
|
-
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Semantic color tokens
|
|
19
|
-
*/
|
|
20
|
-
export type IconColor =
|
|
21
|
-
| 'primary'
|
|
22
|
-
| 'secondary'
|
|
23
|
-
| 'error'
|
|
24
|
-
| 'warning'
|
|
25
|
-
| 'success'
|
|
26
|
-
| 'info'
|
|
27
|
-
| 'onPrimary'
|
|
28
|
-
| 'onSurface'
|
|
29
|
-
| 'textPrimary'
|
|
30
|
-
| 'textSecondary'
|
|
31
|
-
| 'textDisabled';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Icon component props
|
|
35
|
-
*/
|
|
36
|
-
export interface IconProps {
|
|
37
|
-
/** Icon name from Lucide icon set (PascalCase) */
|
|
38
|
-
name: string;
|
|
39
|
-
/** Semantic color token */
|
|
40
|
-
color?: IconColor;
|
|
41
|
-
/** Size preset */
|
|
42
|
-
size?: IconSize;
|
|
43
|
-
/** Custom size override (pixels) */
|
|
44
|
-
customSize?: number;
|
|
45
|
-
/** Custom color override (hex) */
|
|
46
|
-
customColor?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Size mapping (pixels)
|
|
51
|
-
*/
|
|
52
|
-
const sizeMap: Record<IconSize, number> = {
|
|
53
|
-
xs: 16,
|
|
54
|
-
sm: 20,
|
|
55
|
-
md: 24,
|
|
56
|
-
lg: 28,
|
|
57
|
-
xl: 32,
|
|
58
|
-
xxl: 48,
|
|
59
|
-
};
|
|
10
|
+
import { Icon } from '../../domains/icons/presentation/components/Icon';
|
|
11
|
+
import type { IconProps, IconSize, IconColor } from '../../domains/icons/domain/interfaces/IIconAdapter';
|
|
12
|
+
import type { LucideIconName } from '../../domains/icons/infrastructure/adapters/LucideAdapter';
|
|
60
13
|
|
|
61
14
|
/**
|
|
62
15
|
* AtomicIcon Component
|
|
63
16
|
*
|
|
64
|
-
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import { AtomicIcon } from '@umituz/react-native-design-system';
|
|
20
|
+
*
|
|
21
|
+
* // Basic usage
|
|
22
|
+
* <AtomicIcon name="Settings" size="md" color="primary" />
|
|
23
|
+
*
|
|
24
|
+
* // Custom size and color
|
|
25
|
+
* <AtomicIcon name="Heart" customSize={32} customColor="#FF0000" />
|
|
26
|
+
*
|
|
27
|
+
* // With background
|
|
28
|
+
* <AtomicIcon name="Info" size="lg" withBackground backgroundColor="#667eea" />
|
|
29
|
+
* ```
|
|
65
30
|
*/
|
|
66
|
-
export const AtomicIcon: React.FC<IconProps> = ({
|
|
67
|
-
|
|
68
|
-
color = 'textPrimary',
|
|
69
|
-
size = 'md',
|
|
70
|
-
customSize,
|
|
71
|
-
customColor,
|
|
72
|
-
}) => {
|
|
73
|
-
const tokens = useAppDesignTokens();
|
|
74
|
-
|
|
75
|
-
// Get icon component from Lucide
|
|
76
|
-
const IconComponent = (LucideIcons as any)[name];
|
|
77
|
-
|
|
78
|
-
if (!IconComponent) {
|
|
79
|
-
console.warn(`Icon "${name}" not found in Lucide icon set`);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Resolve color from semantic token
|
|
84
|
-
const resolveColor = (): string => {
|
|
85
|
-
if (customColor) return customColor;
|
|
86
|
-
|
|
87
|
-
const colorMap: Record<IconColor, string> = {
|
|
88
|
-
primary: tokens.colors.primary,
|
|
89
|
-
secondary: tokens.colors.secondary,
|
|
90
|
-
error: tokens.colors.error,
|
|
91
|
-
warning: tokens.colors.warning,
|
|
92
|
-
success: tokens.colors.success,
|
|
93
|
-
info: tokens.colors.info,
|
|
94
|
-
onPrimary: tokens.colors.onPrimary,
|
|
95
|
-
onSurface: tokens.colors.onSurface,
|
|
96
|
-
textPrimary: tokens.colors.textPrimary,
|
|
97
|
-
textSecondary: tokens.colors.textSecondary,
|
|
98
|
-
textDisabled: tokens.colors.textDisabled,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
return colorMap[color];
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Resolve size
|
|
105
|
-
const iconSize = customSize || sizeMap[size];
|
|
106
|
-
|
|
107
|
-
return <IconComponent color={resolveColor()} size={iconSize} />;
|
|
31
|
+
export const AtomicIcon: React.FC<IconProps> = (props) => {
|
|
32
|
+
return <Icon {...props} />;
|
|
108
33
|
};
|
|
109
34
|
|
|
110
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Re-export types with Atomic naming convention
|
|
37
|
+
*/
|
|
111
38
|
export type AtomicIconProps = IconProps;
|
|
112
39
|
export type AtomicIconSize = IconSize;
|
|
113
40
|
export type AtomicIconColor = IconColor;
|
|
41
|
+
export type AtomicIconName = LucideIconName;
|