@idealyst/components 1.1.6 → 1.1.7
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 +15 -9
- package/src/Accordion/Accordion.styles.tsx +193 -168
- package/src/Accordion/Accordion.web.tsx +12 -7
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +22 -11
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -2
- package/src/Alert/Alert.native.tsx +11 -10
- package/src/Alert/Alert.styles.tsx +162 -253
- package/src/Alert/Alert.web.tsx +6 -10
- package/src/Avatar/Avatar.native.tsx +5 -2
- package/src/Avatar/Avatar.styles.tsx +48 -18
- package/src/Avatar/Avatar.web.tsx +2 -2
- package/src/Badge/Badge.native.tsx +2 -2
- package/src/Badge/Badge.styles.tsx +37 -16
- package/src/Badge/Badge.web.tsx +6 -6
- package/src/Breadcrumb/Breadcrumb.native.tsx +12 -5
- package/src/Breadcrumb/Breadcrumb.styles.tsx +59 -58
- package/src/Breadcrumb/Breadcrumb.web.tsx +13 -6
- package/src/Button/Button.native.tsx +39 -14
- package/src/Button/Button.styles.tsx +106 -208
- package/src/Button/Button.web.tsx +10 -8
- package/src/Card/Card.native.tsx +14 -6
- package/src/Card/Card.styles.tsx +64 -62
- package/src/Card/Card.web.tsx +5 -4
- package/src/Checkbox/Checkbox.native.tsx +7 -3
- package/src/Checkbox/Checkbox.styles.tsx +49 -25
- package/src/Checkbox/Checkbox.web.tsx +3 -3
- package/src/Chip/Chip.native.tsx +5 -5
- package/src/Chip/Chip.styles.tsx +71 -21
- package/src/Chip/Chip.web.tsx +5 -5
- package/src/Dialog/Dialog.native.tsx +10 -4
- package/src/Dialog/Dialog.styles.tsx +130 -90
- package/src/Dialog/Dialog.web.tsx +4 -4
- package/src/Divider/Divider.native.tsx +29 -42
- package/src/Divider/Divider.styles.tsx +138 -242
- package/src/Divider/Divider.web.tsx +17 -14
- package/src/Icon/Icon.native.tsx +11 -3
- package/src/Icon/Icon.styles.tsx +10 -4
- package/src/Image/Image.styles.tsx +53 -37
- package/src/Input/Input.native.tsx +6 -7
- package/src/Input/Input.styles.tsx +194 -174
- package/src/Input/Input.web.tsx +5 -8
- package/src/Link/Link.native.tsx +4 -1
- package/src/List/List.styles.tsx +79 -105
- package/src/List/ListItem.native.tsx +5 -3
- package/src/List/ListItem.web.tsx +4 -3
- package/src/Menu/Menu.native.tsx +1 -1
- package/src/Menu/Menu.styles.tsx +53 -37
- package/src/Menu/Menu.web.tsx +2 -2
- package/src/Menu/MenuItem.native.tsx +5 -3
- package/src/Menu/MenuItem.styles.tsx +68 -69
- package/src/Menu/MenuItem.web.tsx +16 -3
- package/src/Popover/Popover.native.tsx +1 -1
- package/src/Popover/Popover.styles.tsx +40 -29
- package/src/Popover/Popover.web.tsx +1 -1
- package/src/Pressable/Pressable.native.tsx +3 -1
- package/src/Pressable/Pressable.styles.tsx +20 -13
- package/src/Pressable/Pressable.web.tsx +1 -1
- package/src/Progress/Progress.native.tsx +15 -6
- package/src/Progress/Progress.styles.tsx +125 -85
- package/src/Progress/Progress.web.tsx +10 -9
- package/src/RadioButton/RadioButton.native.tsx +8 -3
- package/src/RadioButton/RadioButton.styles.tsx +44 -37
- package/src/RadioButton/RadioButton.web.tsx +3 -3
- package/src/SVGImage/SVGImage.styles.tsx +28 -16
- package/src/Screen/Screen.native.tsx +23 -13
- package/src/Screen/Screen.styles.tsx +57 -46
- package/src/Screen/Screen.web.tsx +1 -1
- package/src/Select/Select.native.tsx +11 -5
- package/src/Select/Select.styles.tsx +72 -52
- package/src/Select/Select.web.tsx +5 -5
- package/src/Skeleton/Skeleton.styles.tsx +26 -14
- package/src/Slider/Slider.native.tsx +9 -5
- package/src/Slider/Slider.styles.tsx +59 -48
- package/src/Slider/Slider.web.tsx +5 -5
- package/src/Switch/Switch.native.tsx +6 -2
- package/src/Switch/Switch.styles.tsx +46 -19
- package/src/Switch/Switch.web.tsx +4 -4
- package/src/TabBar/TabBar.native.tsx +23 -31
- package/src/TabBar/TabBar.styles.tsx +215 -371
- package/src/TabBar/TabBar.web.tsx +21 -33
- package/src/Table/Table.native.tsx +1 -1
- package/src/Table/Table.styles.tsx +11 -4
- package/src/Table/Table.web.tsx +1 -1
- package/src/Text/Text.native.tsx +3 -4
- package/src/Text/Text.styles.tsx +7 -1
- package/src/Text/Text.web.tsx +1 -1
- package/src/TextArea/TextArea.styles.tsx +90 -58
- package/src/Tooltip/Tooltip.native.tsx +2 -2
- package/src/Tooltip/Tooltip.styles.tsx +21 -12
- package/src/Tooltip/Tooltip.web.tsx +2 -2
- package/src/Video/Video.styles.tsx +39 -23
- package/src/View/View.native.tsx +4 -2
- package/src/View/View.styles.tsx +33 -22
- package/src/View/View.web.tsx +13 -2
- package/src/extensions/applyExtension.ts +210 -0
- package/src/extensions/extendComponent.ts +377 -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
|
@@ -1,198 +1,123 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native-unistyles';
|
|
2
|
-
import { Theme, Intent, Size
|
|
2
|
+
import { Theme, Intent, Size } from '@idealyst/theme';
|
|
3
3
|
import { buildSizeVariants } from '../utils/buildSizeVariants';
|
|
4
4
|
import { ButtonGradient } from './types';
|
|
5
|
+
import { applyExtensions } from '../extensions/applyExtension';
|
|
5
6
|
|
|
6
7
|
type ButtonSize = Size;
|
|
7
|
-
type ButtonIntent = Intent;
|
|
8
8
|
type ButtonType = 'contained' | 'outlined' | 'text';
|
|
9
9
|
|
|
10
10
|
export type ButtonVariants = {
|
|
11
11
|
size: ButtonSize;
|
|
12
|
-
intent:
|
|
12
|
+
intent: Intent;
|
|
13
13
|
type: ButtonType;
|
|
14
14
|
disabled: boolean;
|
|
15
15
|
gradient?: ButtonGradient;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* All dynamic props passed to button style functions.
|
|
20
|
+
* Every style function receives all props for maximum flexibility
|
|
21
|
+
* when using extensions or replacements.
|
|
20
22
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Create type variants (structure only, colors handled by compound variants)
|
|
31
|
-
*/
|
|
32
|
-
function createButtonTypeVariants(theme: Theme) {
|
|
33
|
-
return {
|
|
34
|
-
contained: {
|
|
35
|
-
borderWidth: 0,
|
|
36
|
-
},
|
|
37
|
-
outlined: {
|
|
38
|
-
borderWidth: 1,
|
|
39
|
-
borderStyle: 'solid' ,
|
|
40
|
-
backgroundColor: theme.colors.surface.primary,
|
|
41
|
-
},
|
|
42
|
-
text: {
|
|
43
|
-
borderWidth: 0,
|
|
44
|
-
backgroundColor: 'transparent',
|
|
45
|
-
},
|
|
46
|
-
} as const;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Create compound variants for intent+type combinations
|
|
51
|
-
*/
|
|
52
|
-
function createButtonCompoundVariants(theme: Theme): CompoundVariants<keyof ButtonVariants> {
|
|
53
|
-
const compoundVariants: CompoundVariants<keyof ButtonVariants> = [];
|
|
54
|
-
|
|
55
|
-
for (const intent in theme.intents) {
|
|
56
|
-
const intentValue = theme.intents[intent];
|
|
57
|
-
|
|
58
|
-
// Contained + intent
|
|
59
|
-
compoundVariants.push({
|
|
60
|
-
intent,
|
|
61
|
-
type: 'contained',
|
|
62
|
-
styles: {
|
|
63
|
-
backgroundColor: intentValue.primary,
|
|
64
|
-
color: intentValue.contrast,
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Outlined + intent
|
|
69
|
-
compoundVariants.push({
|
|
70
|
-
intent,
|
|
71
|
-
type: 'outlined',
|
|
72
|
-
styles: {
|
|
73
|
-
color: intentValue.primary,
|
|
74
|
-
borderColor: intentValue.primary,
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Text + intent
|
|
79
|
-
compoundVariants.push({
|
|
80
|
-
intent,
|
|
81
|
-
type: 'text',
|
|
82
|
-
styles: {
|
|
83
|
-
color: intentValue.primary,
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
return compoundVariants;
|
|
89
|
-
}
|
|
23
|
+
export type ButtonDynamicProps = {
|
|
24
|
+
intent?: Intent;
|
|
25
|
+
type?: ButtonType;
|
|
26
|
+
size?: Size;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
gradient?: ButtonGradient;
|
|
29
|
+
};
|
|
90
30
|
|
|
91
31
|
/**
|
|
92
|
-
*
|
|
93
|
-
* Applies a transparent overlay gradient over the intent background color
|
|
32
|
+
* Get button background color based on intent and type
|
|
94
33
|
*/
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
_web: {
|
|
104
|
-
backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.2) 100%)',
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
} as const;
|
|
34
|
+
function getButtonBackgroundColor(theme: Theme, intent: Intent, type: ButtonType): string {
|
|
35
|
+
if (type === 'contained') {
|
|
36
|
+
return theme.intents[intent].primary;
|
|
37
|
+
}
|
|
38
|
+
if (type === 'outlined') {
|
|
39
|
+
return theme.colors.surface.primary;
|
|
40
|
+
}
|
|
41
|
+
return 'transparent';
|
|
108
42
|
}
|
|
109
43
|
|
|
110
44
|
/**
|
|
111
|
-
*
|
|
45
|
+
* Get button border color based on intent and type
|
|
112
46
|
*/
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
for (const intent in theme.intents) {
|
|
117
|
-
const intentValue = theme.intents[intent as Intent];
|
|
118
|
-
|
|
119
|
-
// Contained + intent
|
|
120
|
-
compoundVariants.push({
|
|
121
|
-
intent,
|
|
122
|
-
type: 'contained',
|
|
123
|
-
styles: { color: intentValue.contrast },
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Outlined + intent
|
|
127
|
-
compoundVariants.push({
|
|
128
|
-
intent,
|
|
129
|
-
type: 'outlined',
|
|
130
|
-
styles: { color: intentValue.primary },
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Text + intent
|
|
134
|
-
compoundVariants.push({
|
|
135
|
-
intent,
|
|
136
|
-
type: 'text',
|
|
137
|
-
styles: { color: intentValue.primary },
|
|
138
|
-
});
|
|
47
|
+
function getButtonBorderColor(theme: Theme, intent: Intent, type: ButtonType): string {
|
|
48
|
+
if (type === 'outlined') {
|
|
49
|
+
return theme.intents[intent].primary;
|
|
139
50
|
}
|
|
140
|
-
|
|
141
|
-
return compoundVariants;
|
|
51
|
+
return 'transparent';
|
|
142
52
|
}
|
|
143
53
|
|
|
144
54
|
/**
|
|
145
|
-
*
|
|
55
|
+
* Get text/icon color based on intent and type
|
|
146
56
|
*/
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
color: intentValue.contrast,
|
|
153
|
-
},
|
|
154
|
-
outlined: {
|
|
155
|
-
color: intentValue.primary,
|
|
156
|
-
},
|
|
157
|
-
text: {
|
|
158
|
-
color: intentValue.primary,
|
|
159
|
-
},
|
|
160
|
-
} as const;
|
|
57
|
+
function getTextColor(theme: Theme, intent: Intent, type: ButtonType): string {
|
|
58
|
+
if (type === 'contained') {
|
|
59
|
+
return theme.intents[intent].contrast;
|
|
60
|
+
}
|
|
61
|
+
return theme.intents[intent].primary;
|
|
161
62
|
}
|
|
162
63
|
|
|
163
64
|
/**
|
|
164
|
-
*
|
|
65
|
+
* Create dynamic button styles
|
|
66
|
+
* Receives all ButtonDynamicProps for flexibility in extensions/replacements
|
|
165
67
|
*/
|
|
166
|
-
|
|
167
|
-
return (
|
|
68
|
+
function createButtonStyles(theme: Theme) {
|
|
69
|
+
return (props: ButtonDynamicProps) => {
|
|
70
|
+
const { intent = 'primary', type = 'contained' } = props;
|
|
168
71
|
return {
|
|
169
|
-
|
|
72
|
+
boxSizing: 'border-box',
|
|
170
73
|
alignItems: 'center',
|
|
171
74
|
justifyContent: 'center',
|
|
75
|
+
borderRadius: 8,
|
|
76
|
+
fontWeight: '600',
|
|
77
|
+
textAlign: 'center',
|
|
78
|
+
backgroundColor: getButtonBackgroundColor(theme, intent, type),
|
|
79
|
+
borderColor: getButtonBorderColor(theme, intent, type),
|
|
80
|
+
borderWidth: type === 'outlined' ? 1 : 0,
|
|
81
|
+
borderStyle: type === 'outlined' ? 'solid' as const : undefined,
|
|
82
|
+
_web: {
|
|
83
|
+
display: 'flex',
|
|
84
|
+
transition: 'all 0.1s ease',
|
|
85
|
+
},
|
|
172
86
|
variants: {
|
|
173
87
|
size: buildSizeVariants(theme, 'button', size => ({
|
|
174
|
-
|
|
175
|
-
|
|
88
|
+
paddingVertical: size.paddingVertical,
|
|
89
|
+
paddingHorizontal: size.paddingHorizontal,
|
|
90
|
+
minHeight: size.minHeight,
|
|
176
91
|
})),
|
|
177
|
-
|
|
92
|
+
disabled: {
|
|
93
|
+
true: { opacity: 0.6 },
|
|
94
|
+
false: { opacity: 1, _web: { cursor: 'pointer', _hover: { opacity: 0.90 }, _active: { opacity: 0.75 } } },
|
|
95
|
+
},
|
|
96
|
+
gradient: {
|
|
97
|
+
darken: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(0, 0, 0, 0.15) 100%)' } },
|
|
98
|
+
lighten: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.2) 100%)' } },
|
|
99
|
+
},
|
|
178
100
|
},
|
|
179
101
|
} as const;
|
|
180
102
|
};
|
|
181
103
|
}
|
|
182
104
|
|
|
183
105
|
/**
|
|
184
|
-
*
|
|
106
|
+
* Create dynamic text styles
|
|
107
|
+
* Receives all ButtonDynamicProps for flexibility in extensions/replacements
|
|
185
108
|
*/
|
|
186
|
-
|
|
187
|
-
return (
|
|
109
|
+
function createTextStyles(theme: Theme) {
|
|
110
|
+
return (props: ButtonDynamicProps) => {
|
|
111
|
+
const { intent = 'primary', type = 'contained' } = props;
|
|
188
112
|
return {
|
|
189
113
|
fontWeight: '600',
|
|
190
114
|
textAlign: 'center',
|
|
115
|
+
color: getTextColor(theme, intent, type),
|
|
191
116
|
variants: {
|
|
192
117
|
size: buildSizeVariants(theme, 'button', size => ({
|
|
193
118
|
fontSize: size.fontSize,
|
|
119
|
+
lineHeight: size.fontSize,
|
|
194
120
|
})),
|
|
195
|
-
type: createIconColorVariants(theme, intent), // Text uses same colors as icons
|
|
196
121
|
disabled: {
|
|
197
122
|
true: { opacity: 0.6 },
|
|
198
123
|
false: { opacity: 1 },
|
|
@@ -202,78 +127,51 @@ const createButtonTextStyles = (theme: Theme) => {
|
|
|
202
127
|
};
|
|
203
128
|
}
|
|
204
129
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
borderRadius: 8,
|
|
214
|
-
fontWeight: '600',
|
|
215
|
-
textAlign: 'center',
|
|
216
|
-
_web: {
|
|
217
|
-
display: 'flex',
|
|
218
|
-
transition: 'all 0.1s ease',
|
|
219
|
-
},
|
|
220
|
-
variants: {
|
|
221
|
-
size: buildSizeVariants(theme, 'button', size => ({
|
|
222
|
-
paddingVertical: size.paddingVertical,
|
|
223
|
-
paddingHorizontal: size.paddingHorizontal,
|
|
224
|
-
minHeight: size.minHeight,
|
|
225
|
-
})),
|
|
226
|
-
type: createButtonTypeVariants(theme),
|
|
227
|
-
disabled: {
|
|
228
|
-
true: { opacity: 0.6 },
|
|
229
|
-
false: { opacity: 1, _web: {
|
|
230
|
-
cursor: 'pointer',
|
|
231
|
-
_hover: {
|
|
232
|
-
opacity: 0.90,
|
|
233
|
-
},
|
|
234
|
-
_active: {
|
|
235
|
-
opacity: 0.75,
|
|
236
|
-
},
|
|
237
|
-
} },
|
|
238
|
-
} as const,
|
|
239
|
-
gradient: createGradientVariants(),
|
|
240
|
-
} as const,
|
|
241
|
-
compoundVariants: createButtonCompoundVariants(theme),
|
|
242
|
-
} as const,
|
|
243
|
-
icon: {
|
|
130
|
+
/**
|
|
131
|
+
* Create dynamic icon styles
|
|
132
|
+
* Receives all ButtonDynamicProps for flexibility in extensions/replacements
|
|
133
|
+
*/
|
|
134
|
+
function createIconStyles(theme: Theme) {
|
|
135
|
+
return (props: ButtonDynamicProps) => {
|
|
136
|
+
const { intent = 'primary', type = 'contained' } = props;
|
|
137
|
+
return {
|
|
244
138
|
display: 'flex',
|
|
245
139
|
alignItems: 'center',
|
|
246
140
|
justifyContent: 'center',
|
|
141
|
+
color: getTextColor(theme, intent, type),
|
|
247
142
|
variants: {
|
|
248
143
|
size: buildSizeVariants(theme, 'button', size => ({
|
|
249
144
|
width: size.iconSize,
|
|
250
145
|
height: size.iconSize,
|
|
251
146
|
})),
|
|
252
|
-
intent: createIntentVariants(theme),
|
|
253
|
-
} as const,
|
|
254
|
-
compoundVariants: createIconCompoundVariants(theme),
|
|
255
|
-
} as const,
|
|
256
|
-
iconContainer: {
|
|
257
|
-
display: 'flex',
|
|
258
|
-
flexDirection: 'row',
|
|
259
|
-
alignItems: 'center',
|
|
260
|
-
justifyContent: 'center',
|
|
261
|
-
gap: 4,
|
|
262
|
-
} as const,
|
|
263
|
-
text: {
|
|
264
|
-
fontWeight: '600',
|
|
265
|
-
textAlign: 'center',
|
|
266
|
-
variants: {
|
|
267
|
-
size: buildSizeVariants(theme, 'button', size => ({
|
|
268
|
-
fontSize: size.fontSize,
|
|
269
|
-
})),
|
|
270
|
-
intent: createIntentVariants(theme),
|
|
271
|
-
disabled: {
|
|
272
|
-
true: { opacity: 0.6 },
|
|
273
|
-
false: { opacity: 1 },
|
|
274
|
-
},
|
|
275
147
|
},
|
|
276
|
-
|
|
277
|
-
} as const,
|
|
148
|
+
} as const;
|
|
278
149
|
};
|
|
279
|
-
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create icon container styles.
|
|
154
|
+
* Receives all ButtonDynamicProps for flexibility in extensions/replacements.
|
|
155
|
+
* NOTE: All styles must be dynamic functions (not static objects) to avoid
|
|
156
|
+
* Babel transform issues with Unistyles on native.
|
|
157
|
+
*/
|
|
158
|
+
function createIconContainerStyles() {
|
|
159
|
+
return (_props: ButtonDynamicProps) => ({
|
|
160
|
+
display: 'flex' as const,
|
|
161
|
+
flexDirection: 'row' as const,
|
|
162
|
+
alignItems: 'center' as const,
|
|
163
|
+
justifyContent: 'center' as const,
|
|
164
|
+
gap: 4,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Styles use dynamic functions for intent/type to support theme extensions
|
|
169
|
+
// applyExtensions handles both replacements and extensions automatically
|
|
170
|
+
export const buttonStyles = StyleSheet.create((theme: Theme) => {
|
|
171
|
+
return applyExtensions('Button', theme, {
|
|
172
|
+
button: createButtonStyles(theme),
|
|
173
|
+
text: createTextStyles(theme),
|
|
174
|
+
icon: createIconStyles(theme),
|
|
175
|
+
iconContainer: createIconContainerStyles(),
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -45,9 +45,8 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props: InternalButton
|
|
|
45
45
|
accessibilityHasPopup,
|
|
46
46
|
} = props;
|
|
47
47
|
|
|
48
|
+
// Apply variants for size, disabled, gradient
|
|
48
49
|
buttonStyles.useVariants({
|
|
49
|
-
type,
|
|
50
|
-
intent,
|
|
51
50
|
size,
|
|
52
51
|
disabled,
|
|
53
52
|
gradient,
|
|
@@ -55,8 +54,10 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props: InternalButton
|
|
|
55
54
|
|
|
56
55
|
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
57
56
|
e.preventDefault();
|
|
58
|
-
|
|
57
|
+
// Only stop propagation if we have an onPress handler
|
|
58
|
+
// Otherwise, let clicks bubble up to parent handlers (e.g., Menu triggers)
|
|
59
59
|
if (!disabled && onPress) {
|
|
60
|
+
e.stopPropagation();
|
|
60
61
|
onPress();
|
|
61
62
|
}
|
|
62
63
|
};
|
|
@@ -105,10 +106,11 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props: InternalButton
|
|
|
105
106
|
accessibilityHasPopup,
|
|
106
107
|
]);
|
|
107
108
|
|
|
108
|
-
// Compute dynamic styles
|
|
109
|
+
// Compute dynamic styles with all props for full flexibility
|
|
110
|
+
const dynamicProps = { intent, type, size, disabled, gradient };
|
|
109
111
|
const buttonStyleArray = [
|
|
110
|
-
buttonStyles.button,
|
|
111
|
-
buttonStyles.text,
|
|
112
|
+
(buttonStyles.button as any)(dynamicProps),
|
|
113
|
+
(buttonStyles.text as any)(dynamicProps),
|
|
112
114
|
style as any,
|
|
113
115
|
];
|
|
114
116
|
|
|
@@ -116,10 +118,10 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props: InternalButton
|
|
|
116
118
|
const webProps = getWebProps(buttonStyleArray);
|
|
117
119
|
|
|
118
120
|
// Icon container styles
|
|
119
|
-
const iconContainerProps = getWebProps([buttonStyles.iconContainer]);
|
|
121
|
+
const iconContainerProps = getWebProps([(buttonStyles.iconContainer as any)(dynamicProps)]);
|
|
120
122
|
|
|
121
123
|
// Icon styles with dynamic function
|
|
122
|
-
const iconStyleArray = [buttonStyles.icon];
|
|
124
|
+
const iconStyleArray = [(buttonStyles.icon as any)(dynamicProps)];
|
|
123
125
|
const iconProps = getWebProps(iconStyleArray);
|
|
124
126
|
|
|
125
127
|
// Helper to render icon
|
package/src/Card/Card.native.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { forwardRef, ComponentRef, useMemo } from 'react';
|
|
2
2
|
import { View, Pressable } from 'react-native';
|
|
3
|
+
import { useUnistyles } from 'react-native-unistyles';
|
|
3
4
|
import { CardProps } from './types';
|
|
4
|
-
import { cardStyles } from './Card.styles';
|
|
5
|
+
import { cardStyles, getCardBorderRadius } from './Card.styles';
|
|
5
6
|
import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
|
|
6
7
|
|
|
7
8
|
const Card = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pressable>, CardProps>(({
|
|
@@ -42,12 +43,13 @@ const Card = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pressabl
|
|
|
42
43
|
accessibilityPressed,
|
|
43
44
|
});
|
|
44
45
|
}, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, clickable, accessibilityPressed]);
|
|
45
|
-
|
|
46
|
+
|
|
47
|
+
// Get theme for radii values
|
|
48
|
+
const { theme } = useUnistyles();
|
|
49
|
+
|
|
50
|
+
// Apply variants (for spacing only - radius is applied directly below)
|
|
46
51
|
cardStyles.useVariants({
|
|
47
52
|
clickable,
|
|
48
|
-
radius,
|
|
49
|
-
type,
|
|
50
|
-
intent,
|
|
51
53
|
disabled,
|
|
52
54
|
gap,
|
|
53
55
|
padding,
|
|
@@ -58,13 +60,19 @@ const Card = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pressabl
|
|
|
58
60
|
marginHorizontal,
|
|
59
61
|
});
|
|
60
62
|
|
|
63
|
+
// Get dynamic card style with type and intent props
|
|
64
|
+
const cardStyle = (cardStyles.card as any)({ type, intent });
|
|
65
|
+
|
|
66
|
+
// Get border radius from theme - variants don't work with dynamic styles on iOS
|
|
67
|
+
const borderRadius = getCardBorderRadius(theme, radius);
|
|
68
|
+
|
|
61
69
|
// Use appropriate component based on clickable state
|
|
62
70
|
const Component = clickable ? Pressable : View;
|
|
63
71
|
|
|
64
72
|
const componentProps = {
|
|
65
73
|
ref,
|
|
66
74
|
nativeID: id,
|
|
67
|
-
style: [
|
|
75
|
+
style: [cardStyle, { borderRadius }, style],
|
|
68
76
|
testID,
|
|
69
77
|
...nativeA11yProps,
|
|
70
78
|
...(clickable && {
|
package/src/Card/Card.styles.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native-unistyles';
|
|
2
|
-
import { Theme, Intent,
|
|
2
|
+
import { Theme, Intent, Radius } from '@idealyst/theme';
|
|
3
3
|
import {
|
|
4
4
|
buildGapVariants,
|
|
5
5
|
buildPaddingVariants,
|
|
@@ -10,11 +10,19 @@ import {
|
|
|
10
10
|
buildMarginHorizontalVariants,
|
|
11
11
|
} from '../utils/buildViewStyleVariants';
|
|
12
12
|
import { ViewStyleSize } from '../utils/viewStyleProps';
|
|
13
|
+
import { applyExtensions } from '../extensions/applyExtension';
|
|
13
14
|
|
|
14
15
|
type CardType = 'outlined' | 'elevated' | 'filled';
|
|
15
|
-
type CardRadius =
|
|
16
|
+
type CardRadius = Radius;
|
|
16
17
|
type CardIntent = Intent | 'info' | 'neutral';
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Get border radius value from theme
|
|
21
|
+
*/
|
|
22
|
+
export function getCardBorderRadius(theme: Theme, radius: CardRadius): number {
|
|
23
|
+
return theme.radii[radius];
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
export type CardVariants = {
|
|
19
27
|
type: CardType;
|
|
20
28
|
radius: CardRadius;
|
|
@@ -31,76 +39,63 @@ export type CardVariants = {
|
|
|
31
39
|
marginHorizontal: ViewStyleSize;
|
|
32
40
|
};
|
|
33
41
|
|
|
42
|
+
type CardDynamicProps = {
|
|
43
|
+
intent?: CardIntent;
|
|
44
|
+
type?: CardType;
|
|
45
|
+
};
|
|
46
|
+
|
|
34
47
|
/**
|
|
35
|
-
*
|
|
48
|
+
* Get the border color based on intent (only used for outlined type)
|
|
36
49
|
*/
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
backgroundColor: theme.colors.surface.primary,
|
|
46
|
-
borderWidth: 0,
|
|
47
|
-
...theme.shadows.md,
|
|
48
|
-
},
|
|
49
|
-
filled: {
|
|
50
|
-
backgroundColor: theme.colors.surface.secondary,
|
|
51
|
-
borderWidth: 0,
|
|
52
|
-
},
|
|
53
|
-
} as const;
|
|
50
|
+
function getBorderColor(theme: Theme, intent: CardIntent): string {
|
|
51
|
+
if (intent === 'info' || intent === 'neutral') {
|
|
52
|
+
return theme.colors.border.secondary;
|
|
53
|
+
}
|
|
54
|
+
if (intent in theme.intents) {
|
|
55
|
+
return theme.intents[intent as Intent].primary;
|
|
56
|
+
}
|
|
57
|
+
return theme.colors.border.secondary;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
/**
|
|
57
|
-
*
|
|
61
|
+
* Get type-specific styles
|
|
58
62
|
*/
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
function getTypeStyles(theme: Theme, type: CardType, intent: CardIntent) {
|
|
64
|
+
switch (type) {
|
|
65
|
+
case 'outlined':
|
|
66
|
+
return {
|
|
67
|
+
backgroundColor: 'transparent',
|
|
68
|
+
borderWidth: 1,
|
|
69
|
+
borderStyle: 'solid' as const,
|
|
70
|
+
borderColor: getBorderColor(theme, intent),
|
|
71
|
+
};
|
|
72
|
+
case 'elevated':
|
|
73
|
+
return {
|
|
74
|
+
backgroundColor: theme.colors.surface.primary,
|
|
75
|
+
borderWidth: 0,
|
|
76
|
+
...theme.shadows.md,
|
|
77
|
+
};
|
|
78
|
+
case 'filled':
|
|
79
|
+
return {
|
|
80
|
+
backgroundColor: theme.colors.surface.secondary,
|
|
81
|
+
borderWidth: 0,
|
|
82
|
+
};
|
|
83
|
+
default:
|
|
84
|
+
return {};
|
|
73
85
|
}
|
|
74
|
-
|
|
75
|
-
// Add special intents (info, neutral) for outlined type
|
|
76
|
-
compoundVariants.push({
|
|
77
|
-
intent: 'info',
|
|
78
|
-
type: 'outlined',
|
|
79
|
-
styles: {
|
|
80
|
-
borderColor: theme.colors.border.secondary,
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
compoundVariants.push({
|
|
84
|
-
intent: 'neutral',
|
|
85
|
-
type: 'outlined',
|
|
86
|
-
styles: {
|
|
87
|
-
borderColor: theme.colors.border.secondary,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return compoundVariants;
|
|
92
86
|
}
|
|
93
87
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Create dynamic card styles
|
|
90
|
+
*/
|
|
91
|
+
function createCardStyles(theme: Theme) {
|
|
92
|
+
return ({ intent = 'neutral', type = 'elevated' }: CardDynamicProps) => {
|
|
93
|
+
const typeStyles = getTypeStyles(theme, type, intent);
|
|
94
|
+
return {
|
|
95
|
+
...typeStyles,
|
|
100
96
|
position: 'relative',
|
|
101
97
|
overflow: 'hidden',
|
|
102
98
|
variants: {
|
|
103
|
-
type: createTypeVariants(theme),
|
|
104
99
|
radius: {
|
|
105
100
|
none: { borderRadius: 0 },
|
|
106
101
|
xs: { borderRadius: 2 },
|
|
@@ -147,12 +142,19 @@ export const cardStyles = StyleSheet.create((theme: Theme) => {
|
|
|
147
142
|
marginVertical: buildMarginVerticalVariants(theme),
|
|
148
143
|
marginHorizontal: buildMarginHorizontalVariants(theme),
|
|
149
144
|
},
|
|
150
|
-
compoundVariants: createCardCompoundVariants(theme),
|
|
151
145
|
_web: {
|
|
152
146
|
display: 'flex',
|
|
153
147
|
flexDirection: 'column',
|
|
154
148
|
boxSizing: 'border-box',
|
|
155
149
|
},
|
|
156
|
-
} as const
|
|
150
|
+
} as const;
|
|
157
151
|
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
|
|
155
|
+
// transform on native cannot resolve function calls to extract variant structures.
|
|
156
|
+
export const cardStyles = StyleSheet.create((theme: Theme) => {
|
|
157
|
+
return applyExtensions('Card', theme, {
|
|
158
|
+
card: createCardStyles(theme),
|
|
159
|
+
});
|
|
158
160
|
});
|
package/src/Card/Card.web.tsx
CHANGED
|
@@ -49,12 +49,10 @@ const Card = forwardRef<HTMLDivElement | HTMLButtonElement, CardProps>(({
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
// Apply variants
|
|
52
|
+
// Apply variants (for radius, clickable, disabled, and spacing)
|
|
53
53
|
cardStyles.useVariants({
|
|
54
54
|
clickable,
|
|
55
55
|
radius,
|
|
56
|
-
type,
|
|
57
|
-
intent,
|
|
58
56
|
disabled,
|
|
59
57
|
gap,
|
|
60
58
|
padding,
|
|
@@ -65,8 +63,11 @@ const Card = forwardRef<HTMLDivElement | HTMLButtonElement, CardProps>(({
|
|
|
65
63
|
marginHorizontal,
|
|
66
64
|
});
|
|
67
65
|
|
|
66
|
+
// Get dynamic card style with type and intent props
|
|
67
|
+
const cardStyle = (cardStyles.card as any)({ type, intent });
|
|
68
|
+
|
|
68
69
|
// Generate web props
|
|
69
|
-
const webProps = getWebProps([
|
|
70
|
+
const webProps = getWebProps([cardStyle, style as any]);
|
|
70
71
|
|
|
71
72
|
const mergedRef = useMergeRefs(ref, webProps.ref);
|
|
72
73
|
|