@idealyst/components 1.1.6 → 1.1.8
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 +8 -3
- package/src/Accordion/Accordion.native.tsx +22 -14
- package/src/Accordion/Accordion.styles.old.tsx +298 -0
- package/src/Accordion/Accordion.styles.tsx +139 -248
- package/src/Accordion/Accordion.web.tsx +12 -7
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
- package/src/ActivityIndicator/ActivityIndicator.styles.old.tsx +94 -0
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +43 -62
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -2
- package/src/Alert/Alert.native.tsx +26 -15
- package/src/Alert/Alert.styles.old.tsx +209 -0
- package/src/Alert/Alert.styles.tsx +108 -281
- package/src/Alert/Alert.web.tsx +6 -10
- package/src/Avatar/Avatar.native.tsx +5 -2
- package/src/Avatar/Avatar.styles.old.tsx +99 -0
- package/src/Avatar/Avatar.styles.tsx +47 -62
- package/src/Avatar/Avatar.web.tsx +2 -2
- package/src/Badge/Badge.native.tsx +2 -2
- package/src/Badge/Badge.styles.old.tsx +157 -0
- package/src/Badge/Badge.styles.tsx +69 -108
- package/src/Badge/Badge.web.tsx +6 -6
- package/src/Breadcrumb/Breadcrumb.native.tsx +12 -5
- package/src/Breadcrumb/Breadcrumb.styles.old.tsx +231 -0
- package/src/Breadcrumb/Breadcrumb.styles.tsx +93 -209
- package/src/Breadcrumb/Breadcrumb.web.tsx +39 -27
- package/src/Button/Button.native.tsx +39 -14
- package/src/Button/Button.styles.tsx +99 -253
- package/src/Button/Button.web.tsx +10 -8
- package/src/Card/Card.native.tsx +8 -4
- package/src/Card/Card.styles.old.tsx +160 -0
- package/src/Card/Card.styles.tsx +107 -142
- package/src/Card/Card.web.tsx +6 -4
- package/src/Checkbox/Checkbox.native.tsx +14 -6
- package/src/Checkbox/Checkbox.styles.old.tsx +271 -0
- package/src/Checkbox/Checkbox.styles.tsx +109 -197
- package/src/Checkbox/Checkbox.web.tsx +7 -7
- package/src/Chip/Chip.native.tsx +5 -5
- package/src/Chip/Chip.styles.old.tsx +184 -0
- package/src/Chip/Chip.styles.tsx +34 -22
- package/src/Chip/Chip.web.tsx +5 -5
- package/src/Dialog/Dialog.native.tsx +16 -7
- package/src/Dialog/Dialog.styles.old.tsx +202 -0
- package/src/Dialog/Dialog.styles.tsx +108 -132
- package/src/Dialog/Dialog.web.tsx +4 -4
- package/src/Divider/Divider.native.tsx +29 -42
- package/src/Divider/Divider.styles.old.tsx +172 -0
- package/src/Divider/Divider.styles.tsx +116 -242
- package/src/Divider/Divider.web.tsx +17 -14
- package/src/Icon/Icon.native.tsx +12 -4
- package/src/Icon/Icon.styles.old.tsx +81 -0
- package/src/Icon/Icon.styles.tsx +52 -60
- package/src/Icon/Icon.web.tsx +43 -7
- package/src/Icon/IconSvg/IconSvg.web.tsx +2 -0
- package/src/Image/Image.styles.old.tsx +69 -0
- package/src/Image/Image.styles.tsx +45 -43
- package/src/Input/Input.native.tsx +140 -56
- package/src/Input/Input.styles.old.tsx +289 -0
- package/src/Input/Input.styles.tsx +177 -228
- package/src/Input/Input.web.tsx +5 -8
- package/src/Link/Link.native.tsx +4 -1
- package/src/List/List.native.tsx +5 -2
- package/src/List/List.styles.old.tsx +242 -0
- package/src/List/List.styles.tsx +178 -240
- package/src/List/ListItem.native.tsx +16 -8
- package/src/List/ListItem.web.tsx +26 -15
- package/src/Menu/Menu.native.tsx +1 -1
- package/src/Menu/Menu.styles.old.tsx +197 -0
- package/src/Menu/Menu.styles.tsx +90 -156
- package/src/Menu/Menu.web.tsx +2 -2
- package/src/Menu/MenuItem.native.tsx +9 -5
- package/src/Menu/MenuItem.styles.old.tsx +114 -0
- package/src/Menu/MenuItem.styles.tsx +71 -104
- package/src/Menu/MenuItem.web.tsx +23 -5
- package/src/Popover/Popover.native.tsx +10 -4
- package/src/Popover/Popover.styles.old.tsx +135 -0
- package/src/Popover/Popover.styles.tsx +46 -96
- package/src/Popover/Popover.web.tsx +1 -1
- package/src/Pressable/Pressable.native.tsx +3 -1
- package/src/Pressable/Pressable.styles.old.tsx +27 -0
- package/src/Pressable/Pressable.styles.tsx +35 -20
- package/src/Pressable/Pressable.web.tsx +1 -1
- package/src/Progress/Progress.native.tsx +15 -6
- package/src/Progress/Progress.styles.old.tsx +200 -0
- package/src/Progress/Progress.styles.tsx +69 -118
- package/src/Progress/Progress.web.tsx +10 -9
- package/src/RadioButton/RadioButton.native.tsx +10 -4
- package/src/RadioButton/RadioButton.styles.old.tsx +175 -0
- package/src/RadioButton/RadioButton.styles.tsx +81 -145
- package/src/RadioButton/RadioButton.web.tsx +4 -4
- package/src/SVGImage/SVGImage.styles.old.tsx +86 -0
- package/src/SVGImage/SVGImage.styles.tsx +35 -66
- package/src/Screen/Screen.native.tsx +30 -27
- package/src/Screen/Screen.styles.old.tsx +87 -0
- package/src/Screen/Screen.styles.tsx +120 -71
- package/src/Screen/Screen.web.tsx +2 -2
- package/src/Select/Select.native.tsx +44 -29
- package/src/Select/Select.styles.old.tsx +353 -0
- package/src/Select/Select.styles.tsx +244 -293
- package/src/Select/Select.web.tsx +5 -5
- package/src/Skeleton/Skeleton.styles.old.tsx +67 -0
- package/src/Skeleton/Skeleton.styles.tsx +31 -43
- package/src/Slider/Slider.native.tsx +9 -5
- package/src/Slider/Slider.styles.old.tsx +259 -0
- package/src/Slider/Slider.styles.tsx +157 -227
- package/src/Slider/Slider.web.tsx +5 -5
- package/src/Switch/Switch.native.tsx +11 -5
- package/src/Switch/Switch.styles.old.tsx +203 -0
- package/src/Switch/Switch.styles.tsx +103 -149
- package/src/Switch/Switch.web.tsx +8 -8
- package/src/TabBar/TabBar.native.tsx +24 -31
- package/src/TabBar/TabBar.styles.old.tsx +343 -0
- package/src/TabBar/TabBar.styles.tsx +204 -494
- package/src/TabBar/TabBar.web.tsx +21 -33
- package/src/Table/Table.native.tsx +18 -9
- package/src/Table/Table.styles.old.tsx +311 -0
- package/src/Table/Table.styles.tsx +151 -278
- package/src/Table/Table.web.tsx +1 -1
- package/src/Text/Text.native.tsx +1 -4
- package/src/Text/Text.style.demo.tsx +16 -0
- package/src/Text/Text.styles.old.tsx +219 -0
- package/src/Text/Text.styles.tsx +94 -78
- package/src/Text/Text.web.tsx +2 -2
- package/src/Text/index.ts +1 -0
- package/src/TextArea/TextArea.styles.old.tsx +213 -0
- package/src/TextArea/TextArea.styles.tsx +101 -157
- package/src/Tooltip/Tooltip.native.tsx +2 -2
- package/src/Tooltip/Tooltip.styles.old.tsx +82 -0
- package/src/Tooltip/Tooltip.styles.tsx +38 -53
- package/src/Tooltip/Tooltip.web.tsx +2 -2
- package/src/Video/Video.styles.old.tsx +51 -0
- package/src/Video/Video.styles.tsx +32 -28
- package/src/View/View.native.tsx +12 -12
- package/src/View/View.styles.old.tsx +125 -0
- package/src/View/View.styles.tsx +84 -103
- package/src/View/View.web.tsx +14 -2
- package/src/examples/CardExamples.tsx +0 -6
- package/src/extensions/applyExtension.ts +210 -0
- package/src/extensions/extendComponent.ts +438 -0
- package/src/extensions/index.ts +102 -0
- package/src/extensions/types.ts +497 -0
- package/src/globals.ts +16 -0
- package/src/index.native.ts +4 -0
- package/src/index.ts +28 -0
- package/src/utils/deepMerge.ts +54 -2
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { Theme } from '@idealyst/theme';
|
|
2
|
+
import { UnistylesRuntime } from 'react-native-unistyles';
|
|
3
|
+
import {
|
|
4
|
+
ComponentStyleElements,
|
|
5
|
+
ComponentName,
|
|
6
|
+
StyleExtension,
|
|
7
|
+
} from './types';
|
|
8
|
+
import { deepMergeAll } from '../utils/deepMerge';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Registry storing style extensions for each component.
|
|
12
|
+
* Key is the component name, value is an array of extensions (applied in order).
|
|
13
|
+
*/
|
|
14
|
+
const extensionRegistry = new Map<ComponentName, StyleExtension<any>[]>();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Registry storing complete style replacements for each component.
|
|
18
|
+
* When set, the replacement is used instead of base styles + extensions.
|
|
19
|
+
*/
|
|
20
|
+
const replacementRegistry = new Map<ComponentName, StyleExtension<any>>();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compute all extensions for a given theme.
|
|
24
|
+
* Returns an object with component names as keys and merged element extensions as values.
|
|
25
|
+
*/
|
|
26
|
+
function computeExtensionsForTheme(theme: Theme): Record<string, Record<string, any>> {
|
|
27
|
+
const result: Record<string, Record<string, any>> = {};
|
|
28
|
+
|
|
29
|
+
for (const [component, extensions] of extensionRegistry) {
|
|
30
|
+
if (!extensions || extensions.length === 0) continue;
|
|
31
|
+
|
|
32
|
+
// Resolve all extensions (call functions with theme)
|
|
33
|
+
const resolved = extensions.map(ext =>
|
|
34
|
+
typeof ext === 'function' ? ext(theme) : ext
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Merge all extensions in order (later ones win)
|
|
38
|
+
result[component] = deepMergeAll(...resolved);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Update theme's __extensions to trigger Unistyles reactivity.
|
|
46
|
+
* This is called whenever extensions change.
|
|
47
|
+
*/
|
|
48
|
+
function syncExtensionsToThemes(): void {
|
|
49
|
+
try {
|
|
50
|
+
// Update both light and dark themes with computed extensions
|
|
51
|
+
UnistylesRuntime.updateTheme('light', (currentTheme) => {
|
|
52
|
+
const extensions = computeExtensionsForTheme(currentTheme);
|
|
53
|
+
return {
|
|
54
|
+
...currentTheme,
|
|
55
|
+
__extensions: extensions,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
UnistylesRuntime.updateTheme('dark', (currentTheme) => {
|
|
60
|
+
const extensions = computeExtensionsForTheme(currentTheme);
|
|
61
|
+
return {
|
|
62
|
+
...currentTheme,
|
|
63
|
+
__extensions: extensions,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// UnistylesRuntime may not be available in all contexts (e.g., SSR)
|
|
68
|
+
// Silently ignore errors - extensions will still work via getExtension
|
|
69
|
+
console.warn('Unable to sync extensions to theme:', error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Completely replace the styles of a component.
|
|
75
|
+
*
|
|
76
|
+
* Unlike `extendComponent` which merges with base styles, `replaceStyles`
|
|
77
|
+
* completely overrides the default stylesheet. Extensions are NOT applied
|
|
78
|
+
* when a replacement is set.
|
|
79
|
+
*
|
|
80
|
+
* **Use with caution:** You're responsible for providing all necessary styles,
|
|
81
|
+
* including variant styles, platform-specific styles, and accessibility states.
|
|
82
|
+
*
|
|
83
|
+
* @param component - The component name to replace styles for
|
|
84
|
+
* @param replacement - Complete style replacement, either as an object or a function receiving theme
|
|
85
|
+
*
|
|
86
|
+
* @example Complete replacement with theme access
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { replaceStyles } from '@idealyst/components';
|
|
89
|
+
*
|
|
90
|
+
* replaceStyles('Button', (theme) => ({
|
|
91
|
+
* button: {
|
|
92
|
+
* backgroundColor: theme.colors.surface.primary,
|
|
93
|
+
* borderRadius: 0,
|
|
94
|
+
* padding: 16,
|
|
95
|
+
* variants: {
|
|
96
|
+
* size: {
|
|
97
|
+
* sm: { padding: 8 },
|
|
98
|
+
* md: { padding: 16 },
|
|
99
|
+
* lg: { padding: 24 },
|
|
100
|
+
* },
|
|
101
|
+
* },
|
|
102
|
+
* },
|
|
103
|
+
* text: {
|
|
104
|
+
* color: theme.colors.text.primary,
|
|
105
|
+
* fontSize: 16,
|
|
106
|
+
* },
|
|
107
|
+
* icon: {
|
|
108
|
+
* width: 20,
|
|
109
|
+
* height: 20,
|
|
110
|
+
* },
|
|
111
|
+
* iconContainer: {
|
|
112
|
+
* display: 'flex',
|
|
113
|
+
* alignItems: 'center',
|
|
114
|
+
* },
|
|
115
|
+
* }));
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* @example Clear replacement to restore defaults
|
|
119
|
+
* ```typescript
|
|
120
|
+
* clearReplacement('Button');
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function replaceStyles<K extends ComponentName>(
|
|
124
|
+
component: K,
|
|
125
|
+
replacement: StyleExtension<ComponentStyleElements[K]>
|
|
126
|
+
): void {
|
|
127
|
+
replacementRegistry.set(component, replacement);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get the replacement styles for a component, if set.
|
|
132
|
+
*
|
|
133
|
+
* @param component - The component name
|
|
134
|
+
* @param theme - The current theme (used if replacement is a function)
|
|
135
|
+
* @returns The resolved replacement styles, or undefined if none set
|
|
136
|
+
*
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
export function getReplacement<K extends ComponentName>(
|
|
140
|
+
component: K,
|
|
141
|
+
theme: Theme
|
|
142
|
+
): Partial<ComponentStyleElements[K]> | undefined {
|
|
143
|
+
const replacement = replacementRegistry.get(component);
|
|
144
|
+
if (!replacement) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return typeof replacement === 'function' ? replacement(theme) : replacement;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Clear the style replacement for a specific component.
|
|
152
|
+
* After clearing, the component will use base styles + extensions again.
|
|
153
|
+
*
|
|
154
|
+
* @param component - The component name to clear replacement for
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* import { clearReplacement } from '@idealyst/components';
|
|
159
|
+
*
|
|
160
|
+
* clearReplacement('Button');
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export function clearReplacement<K extends ComponentName>(component: K): void {
|
|
164
|
+
replacementRegistry.delete(component);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Clear all style replacements.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* import { clearAllReplacements } from '@idealyst/components';
|
|
173
|
+
*
|
|
174
|
+
* clearAllReplacements();
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export function clearAllReplacements(): void {
|
|
178
|
+
replacementRegistry.clear();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if a component has a style replacement set.
|
|
183
|
+
*
|
|
184
|
+
* @param component - The component name to check
|
|
185
|
+
* @returns true if the component has a replacement set
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* import { hasReplacement } from '@idealyst/components';
|
|
190
|
+
*
|
|
191
|
+
* if (hasReplacement('Button')) {
|
|
192
|
+
* console.log('Button styles are completely replaced');
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function hasReplacement<K extends ComponentName>(component: K): boolean {
|
|
197
|
+
return replacementRegistry.has(component);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Globally extend the styles of a component.
|
|
202
|
+
*
|
|
203
|
+
* Extensions affect ALL instances of that component app-wide.
|
|
204
|
+
* Extensions are merged with base styles - extension styles win on conflict.
|
|
205
|
+
*
|
|
206
|
+
* **Precedence rules:**
|
|
207
|
+
* - Extensions override base component styles
|
|
208
|
+
* - Later extensions override earlier extensions
|
|
209
|
+
* - Setting a value to `undefined` removes that style property
|
|
210
|
+
* - Nested objects (like `_web`, `variants`) are deep merged
|
|
211
|
+
*
|
|
212
|
+
* @param component - The component name to extend (e.g., 'Button', 'Card')
|
|
213
|
+
* @param extension - Style overrides, either as an object or a function receiving theme
|
|
214
|
+
*
|
|
215
|
+
* @example Static extension
|
|
216
|
+
* ```typescript
|
|
217
|
+
* import { extendComponent } from '@idealyst/components';
|
|
218
|
+
*
|
|
219
|
+
* extendComponent('Button', {
|
|
220
|
+
* button: {
|
|
221
|
+
* borderRadius: 20,
|
|
222
|
+
* shadowColor: '#000',
|
|
223
|
+
* shadowOffset: { width: 0, height: 2 },
|
|
224
|
+
* shadowOpacity: 0.25,
|
|
225
|
+
* shadowRadius: 4,
|
|
226
|
+
* },
|
|
227
|
+
* text: {
|
|
228
|
+
* textTransform: 'uppercase',
|
|
229
|
+
* letterSpacing: 1,
|
|
230
|
+
* },
|
|
231
|
+
* });
|
|
232
|
+
* ```
|
|
233
|
+
*
|
|
234
|
+
* @example Theme-aware extension
|
|
235
|
+
* ```typescript
|
|
236
|
+
* extendComponent('Button', (theme) => ({
|
|
237
|
+
* button: {
|
|
238
|
+
* ...theme.shadows.lg,
|
|
239
|
+
* backgroundColor: theme.colors.surface.secondary,
|
|
240
|
+
* },
|
|
241
|
+
* text: {
|
|
242
|
+
* color: theme.colors.text.primary,
|
|
243
|
+
* },
|
|
244
|
+
* }));
|
|
245
|
+
* ```
|
|
246
|
+
*
|
|
247
|
+
* @example Multiple extensions (later ones have higher precedence)
|
|
248
|
+
* ```typescript
|
|
249
|
+
* // Base brand styles
|
|
250
|
+
* extendComponent('Button', {
|
|
251
|
+
* button: { borderRadius: 8 },
|
|
252
|
+
* });
|
|
253
|
+
*
|
|
254
|
+
* // Feature-specific override (wins over base)
|
|
255
|
+
* extendComponent('Button', {
|
|
256
|
+
* button: { borderRadius: 20 },
|
|
257
|
+
* });
|
|
258
|
+
* // Result: borderRadius is 20
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* @example Removing a style property
|
|
262
|
+
* ```typescript
|
|
263
|
+
* extendComponent('Button', {
|
|
264
|
+
* button: {
|
|
265
|
+
* shadowColor: undefined, // Removes shadowColor
|
|
266
|
+
* shadowOpacity: undefined, // Removes shadowOpacity
|
|
267
|
+
* },
|
|
268
|
+
* });
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* @example Web-specific styles
|
|
272
|
+
* ```typescript
|
|
273
|
+
* extendComponent('Button', {
|
|
274
|
+
* button: {
|
|
275
|
+
* _web: {
|
|
276
|
+
* cursor: 'pointer',
|
|
277
|
+
* transition: 'all 0.2s ease',
|
|
278
|
+
* _hover: {
|
|
279
|
+
* transform: 'scale(1.02)',
|
|
280
|
+
* },
|
|
281
|
+
* },
|
|
282
|
+
* },
|
|
283
|
+
* });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
// Overload for static object extension
|
|
287
|
+
export function extendComponent<K extends ComponentName>(
|
|
288
|
+
component: K,
|
|
289
|
+
extension: Partial<ComponentStyleElements[K]>
|
|
290
|
+
): void;
|
|
291
|
+
// Overload for theme-aware function extension
|
|
292
|
+
export function extendComponent<K extends ComponentName>(
|
|
293
|
+
component: K,
|
|
294
|
+
extension: (theme: Theme) => Partial<ComponentStyleElements[K]>
|
|
295
|
+
): void;
|
|
296
|
+
// Implementation
|
|
297
|
+
export function extendComponent<K extends ComponentName>(
|
|
298
|
+
component: K,
|
|
299
|
+
extension: StyleExtension<ComponentStyleElements[K]>
|
|
300
|
+
): void {
|
|
301
|
+
const existing = extensionRegistry.get(component) ?? [];
|
|
302
|
+
existing.push(extension);
|
|
303
|
+
extensionRegistry.set(component, existing);
|
|
304
|
+
|
|
305
|
+
// Sync extensions to theme for Unistyles reactivity
|
|
306
|
+
syncExtensionsToThemes();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get the resolved extension for a component.
|
|
311
|
+
*
|
|
312
|
+
* This is an internal function used by component stylesheets to retrieve
|
|
313
|
+
* and apply extensions. All registered extensions are merged in order,
|
|
314
|
+
* with later extensions taking precedence.
|
|
315
|
+
*
|
|
316
|
+
* @param component - The component name
|
|
317
|
+
* @param theme - The current theme (used if extension is a function)
|
|
318
|
+
* @returns The resolved merged extension styles, or undefined if none registered
|
|
319
|
+
*
|
|
320
|
+
* @internal
|
|
321
|
+
*/
|
|
322
|
+
export function getExtension<K extends ComponentName>(
|
|
323
|
+
component: K,
|
|
324
|
+
theme: Theme
|
|
325
|
+
): Partial<ComponentStyleElements[K]> | undefined {
|
|
326
|
+
const extensions = extensionRegistry.get(component);
|
|
327
|
+
|
|
328
|
+
if (!extensions || extensions.length === 0) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Resolve all extensions (call functions with theme)
|
|
333
|
+
const resolved = extensions.map(ext =>
|
|
334
|
+
typeof ext === 'function' ? ext(theme) : ext
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Merge all extensions in order (later ones win)
|
|
338
|
+
return deepMergeAll(...resolved) as Partial<ComponentStyleElements[K]>;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Clear all extensions for a specific component.
|
|
343
|
+
*
|
|
344
|
+
* @param component - The component name to clear extensions for
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* import { clearExtension } from '@idealyst/components';
|
|
349
|
+
*
|
|
350
|
+
* // Remove all Button extensions
|
|
351
|
+
* clearExtension('Button');
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
export function clearExtension<K extends ComponentName>(component: K): void {
|
|
355
|
+
extensionRegistry.delete(component);
|
|
356
|
+
|
|
357
|
+
// Sync extensions to theme for Unistyles reactivity
|
|
358
|
+
syncExtensionsToThemes();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Clear all component extensions.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```typescript
|
|
366
|
+
* import { clearAllExtensions } from '@idealyst/components';
|
|
367
|
+
*
|
|
368
|
+
* // Remove all component extensions
|
|
369
|
+
* clearAllExtensions();
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
export function clearAllExtensions(): void {
|
|
373
|
+
extensionRegistry.clear();
|
|
374
|
+
|
|
375
|
+
// Sync extensions to theme for Unistyles reactivity
|
|
376
|
+
syncExtensionsToThemes();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Check if a component has any extensions registered.
|
|
381
|
+
*
|
|
382
|
+
* @param component - The component name to check
|
|
383
|
+
* @returns true if the component has at least one extension
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* import { hasExtension } from '@idealyst/components';
|
|
388
|
+
*
|
|
389
|
+
* if (hasExtension('Button')) {
|
|
390
|
+
* console.log('Button has custom styles');
|
|
391
|
+
* }
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
export function hasExtension<K extends ComponentName>(component: K): boolean {
|
|
395
|
+
const extensions = extensionRegistry.get(component);
|
|
396
|
+
return extensions !== undefined && extensions.length > 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get all registered component extensions.
|
|
401
|
+
* Useful for debugging or inspecting what extensions are active.
|
|
402
|
+
*
|
|
403
|
+
* @returns Array of component names that have extensions
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* import { getExtendedComponents } from '@idealyst/components';
|
|
408
|
+
*
|
|
409
|
+
* const extended = getExtendedComponents();
|
|
410
|
+
* console.log('Extended components:', extended);
|
|
411
|
+
* // ['Button', 'Card', 'Input']
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
export function getExtendedComponents(): ComponentName[] {
|
|
415
|
+
return Array.from(extensionRegistry.keys()).filter(key => {
|
|
416
|
+
const extensions = extensionRegistry.get(key);
|
|
417
|
+
return extensions && extensions.length > 0;
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get the number of extensions registered for a component.
|
|
423
|
+
* Useful for debugging.
|
|
424
|
+
*
|
|
425
|
+
* @param component - The component name
|
|
426
|
+
* @returns Number of extensions registered
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* import { getExtensionCount } from '@idealyst/components';
|
|
431
|
+
*
|
|
432
|
+
* console.log('Button extensions:', getExtensionCount('Button'));
|
|
433
|
+
* // 3
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
export function getExtensionCount<K extends ComponentName>(component: K): number {
|
|
437
|
+
return extensionRegistry.get(component)?.length ?? 0;
|
|
438
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Style Extensions
|
|
3
|
+
*
|
|
4
|
+
* This module provides a type-safe way to globally customize component styles.
|
|
5
|
+
*
|
|
6
|
+
* @example Basic usage
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { extendComponent } from '@idealyst/components';
|
|
9
|
+
*
|
|
10
|
+
* // Add shadows and rounded corners to all buttons
|
|
11
|
+
* extendComponent('Button', {
|
|
12
|
+
* button: {
|
|
13
|
+
* borderRadius: 20,
|
|
14
|
+
* shadowColor: '#000',
|
|
15
|
+
* shadowOpacity: 0.1,
|
|
16
|
+
* shadowRadius: 4,
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example Theme-aware extensions
|
|
22
|
+
* ```typescript
|
|
23
|
+
* extendComponent('Card', (theme) => ({
|
|
24
|
+
* card: {
|
|
25
|
+
* ...theme.shadows.md,
|
|
26
|
+
* borderColor: theme.colors.border.primary,
|
|
27
|
+
* },
|
|
28
|
+
* }));
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @module
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// Public API
|
|
35
|
+
export {
|
|
36
|
+
// Extension functions
|
|
37
|
+
extendComponent,
|
|
38
|
+
clearExtension,
|
|
39
|
+
clearAllExtensions,
|
|
40
|
+
hasExtension,
|
|
41
|
+
getExtendedComponents,
|
|
42
|
+
getExtensionCount,
|
|
43
|
+
// Replacement functions
|
|
44
|
+
replaceStyles,
|
|
45
|
+
clearReplacement,
|
|
46
|
+
clearAllReplacements,
|
|
47
|
+
hasReplacement,
|
|
48
|
+
} from './extendComponent';
|
|
49
|
+
|
|
50
|
+
// Internal API (for component stylesheets)
|
|
51
|
+
export { getExtension, getReplacement } from './extendComponent';
|
|
52
|
+
export {
|
|
53
|
+
withExtension,
|
|
54
|
+
withSimpleExtension,
|
|
55
|
+
normalizeStyleFn,
|
|
56
|
+
normalizeSimpleStyleFn,
|
|
57
|
+
applyExtensions,
|
|
58
|
+
} from './applyExtension';
|
|
59
|
+
|
|
60
|
+
// Types
|
|
61
|
+
export type {
|
|
62
|
+
ComponentStyleElements,
|
|
63
|
+
ComponentStyleExtensions,
|
|
64
|
+
ComponentName,
|
|
65
|
+
StyleExtension,
|
|
66
|
+
ElementStyle,
|
|
67
|
+
Styles,
|
|
68
|
+
// Individual component styleable elements
|
|
69
|
+
ButtonStyleableElements,
|
|
70
|
+
CardStyleableElements,
|
|
71
|
+
InputStyleableElements,
|
|
72
|
+
ChipStyleableElements,
|
|
73
|
+
AlertStyleableElements,
|
|
74
|
+
SwitchStyleableElements,
|
|
75
|
+
SelectStyleableElements,
|
|
76
|
+
BadgeStyleableElements,
|
|
77
|
+
AvatarStyleableElements,
|
|
78
|
+
ProgressStyleableElements,
|
|
79
|
+
CheckboxStyleableElements,
|
|
80
|
+
RadioButtonStyleableElements,
|
|
81
|
+
SliderStyleableElements,
|
|
82
|
+
TextAreaStyleableElements,
|
|
83
|
+
AccordionStyleableElements,
|
|
84
|
+
DialogStyleableElements,
|
|
85
|
+
MenuStyleableElements,
|
|
86
|
+
MenuItemStyleableElements,
|
|
87
|
+
ListStyleableElements,
|
|
88
|
+
TabBarStyleableElements,
|
|
89
|
+
TableStyleableElements,
|
|
90
|
+
TooltipStyleableElements,
|
|
91
|
+
PopoverStyleableElements,
|
|
92
|
+
BreadcrumbStyleableElements,
|
|
93
|
+
ActivityIndicatorStyleableElements,
|
|
94
|
+
SkeletonStyleableElements,
|
|
95
|
+
DividerStyleableElements,
|
|
96
|
+
TextStyleableElements,
|
|
97
|
+
ViewStyleableElements,
|
|
98
|
+
IconStyleableElements,
|
|
99
|
+
ImageStyleableElements,
|
|
100
|
+
PressableStyleableElements,
|
|
101
|
+
ScreenStyleableElements,
|
|
102
|
+
} from './types';
|