@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.
Files changed (144) hide show
  1. package/package.json +8 -3
  2. package/src/Accordion/Accordion.native.tsx +22 -14
  3. package/src/Accordion/Accordion.styles.old.tsx +298 -0
  4. package/src/Accordion/Accordion.styles.tsx +139 -248
  5. package/src/Accordion/Accordion.web.tsx +12 -7
  6. package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
  7. package/src/ActivityIndicator/ActivityIndicator.styles.old.tsx +94 -0
  8. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +43 -62
  9. package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -2
  10. package/src/Alert/Alert.native.tsx +26 -15
  11. package/src/Alert/Alert.styles.old.tsx +209 -0
  12. package/src/Alert/Alert.styles.tsx +108 -281
  13. package/src/Alert/Alert.web.tsx +6 -10
  14. package/src/Avatar/Avatar.native.tsx +5 -2
  15. package/src/Avatar/Avatar.styles.old.tsx +99 -0
  16. package/src/Avatar/Avatar.styles.tsx +47 -62
  17. package/src/Avatar/Avatar.web.tsx +2 -2
  18. package/src/Badge/Badge.native.tsx +2 -2
  19. package/src/Badge/Badge.styles.old.tsx +157 -0
  20. package/src/Badge/Badge.styles.tsx +69 -108
  21. package/src/Badge/Badge.web.tsx +6 -6
  22. package/src/Breadcrumb/Breadcrumb.native.tsx +12 -5
  23. package/src/Breadcrumb/Breadcrumb.styles.old.tsx +231 -0
  24. package/src/Breadcrumb/Breadcrumb.styles.tsx +93 -209
  25. package/src/Breadcrumb/Breadcrumb.web.tsx +39 -27
  26. package/src/Button/Button.native.tsx +39 -14
  27. package/src/Button/Button.styles.tsx +99 -253
  28. package/src/Button/Button.web.tsx +10 -8
  29. package/src/Card/Card.native.tsx +8 -4
  30. package/src/Card/Card.styles.old.tsx +160 -0
  31. package/src/Card/Card.styles.tsx +107 -142
  32. package/src/Card/Card.web.tsx +6 -4
  33. package/src/Checkbox/Checkbox.native.tsx +14 -6
  34. package/src/Checkbox/Checkbox.styles.old.tsx +271 -0
  35. package/src/Checkbox/Checkbox.styles.tsx +109 -197
  36. package/src/Checkbox/Checkbox.web.tsx +7 -7
  37. package/src/Chip/Chip.native.tsx +5 -5
  38. package/src/Chip/Chip.styles.old.tsx +184 -0
  39. package/src/Chip/Chip.styles.tsx +34 -22
  40. package/src/Chip/Chip.web.tsx +5 -5
  41. package/src/Dialog/Dialog.native.tsx +16 -7
  42. package/src/Dialog/Dialog.styles.old.tsx +202 -0
  43. package/src/Dialog/Dialog.styles.tsx +108 -132
  44. package/src/Dialog/Dialog.web.tsx +4 -4
  45. package/src/Divider/Divider.native.tsx +29 -42
  46. package/src/Divider/Divider.styles.old.tsx +172 -0
  47. package/src/Divider/Divider.styles.tsx +116 -242
  48. package/src/Divider/Divider.web.tsx +17 -14
  49. package/src/Icon/Icon.native.tsx +12 -4
  50. package/src/Icon/Icon.styles.old.tsx +81 -0
  51. package/src/Icon/Icon.styles.tsx +52 -60
  52. package/src/Icon/Icon.web.tsx +43 -7
  53. package/src/Icon/IconSvg/IconSvg.web.tsx +2 -0
  54. package/src/Image/Image.styles.old.tsx +69 -0
  55. package/src/Image/Image.styles.tsx +45 -43
  56. package/src/Input/Input.native.tsx +140 -56
  57. package/src/Input/Input.styles.old.tsx +289 -0
  58. package/src/Input/Input.styles.tsx +177 -228
  59. package/src/Input/Input.web.tsx +5 -8
  60. package/src/Link/Link.native.tsx +4 -1
  61. package/src/List/List.native.tsx +5 -2
  62. package/src/List/List.styles.old.tsx +242 -0
  63. package/src/List/List.styles.tsx +178 -240
  64. package/src/List/ListItem.native.tsx +16 -8
  65. package/src/List/ListItem.web.tsx +26 -15
  66. package/src/Menu/Menu.native.tsx +1 -1
  67. package/src/Menu/Menu.styles.old.tsx +197 -0
  68. package/src/Menu/Menu.styles.tsx +90 -156
  69. package/src/Menu/Menu.web.tsx +2 -2
  70. package/src/Menu/MenuItem.native.tsx +9 -5
  71. package/src/Menu/MenuItem.styles.old.tsx +114 -0
  72. package/src/Menu/MenuItem.styles.tsx +71 -104
  73. package/src/Menu/MenuItem.web.tsx +23 -5
  74. package/src/Popover/Popover.native.tsx +10 -4
  75. package/src/Popover/Popover.styles.old.tsx +135 -0
  76. package/src/Popover/Popover.styles.tsx +46 -96
  77. package/src/Popover/Popover.web.tsx +1 -1
  78. package/src/Pressable/Pressable.native.tsx +3 -1
  79. package/src/Pressable/Pressable.styles.old.tsx +27 -0
  80. package/src/Pressable/Pressable.styles.tsx +35 -20
  81. package/src/Pressable/Pressable.web.tsx +1 -1
  82. package/src/Progress/Progress.native.tsx +15 -6
  83. package/src/Progress/Progress.styles.old.tsx +200 -0
  84. package/src/Progress/Progress.styles.tsx +69 -118
  85. package/src/Progress/Progress.web.tsx +10 -9
  86. package/src/RadioButton/RadioButton.native.tsx +10 -4
  87. package/src/RadioButton/RadioButton.styles.old.tsx +175 -0
  88. package/src/RadioButton/RadioButton.styles.tsx +81 -145
  89. package/src/RadioButton/RadioButton.web.tsx +4 -4
  90. package/src/SVGImage/SVGImage.styles.old.tsx +86 -0
  91. package/src/SVGImage/SVGImage.styles.tsx +35 -66
  92. package/src/Screen/Screen.native.tsx +30 -27
  93. package/src/Screen/Screen.styles.old.tsx +87 -0
  94. package/src/Screen/Screen.styles.tsx +120 -71
  95. package/src/Screen/Screen.web.tsx +2 -2
  96. package/src/Select/Select.native.tsx +44 -29
  97. package/src/Select/Select.styles.old.tsx +353 -0
  98. package/src/Select/Select.styles.tsx +244 -293
  99. package/src/Select/Select.web.tsx +5 -5
  100. package/src/Skeleton/Skeleton.styles.old.tsx +67 -0
  101. package/src/Skeleton/Skeleton.styles.tsx +31 -43
  102. package/src/Slider/Slider.native.tsx +9 -5
  103. package/src/Slider/Slider.styles.old.tsx +259 -0
  104. package/src/Slider/Slider.styles.tsx +157 -227
  105. package/src/Slider/Slider.web.tsx +5 -5
  106. package/src/Switch/Switch.native.tsx +11 -5
  107. package/src/Switch/Switch.styles.old.tsx +203 -0
  108. package/src/Switch/Switch.styles.tsx +103 -149
  109. package/src/Switch/Switch.web.tsx +8 -8
  110. package/src/TabBar/TabBar.native.tsx +24 -31
  111. package/src/TabBar/TabBar.styles.old.tsx +343 -0
  112. package/src/TabBar/TabBar.styles.tsx +204 -494
  113. package/src/TabBar/TabBar.web.tsx +21 -33
  114. package/src/Table/Table.native.tsx +18 -9
  115. package/src/Table/Table.styles.old.tsx +311 -0
  116. package/src/Table/Table.styles.tsx +151 -278
  117. package/src/Table/Table.web.tsx +1 -1
  118. package/src/Text/Text.native.tsx +1 -4
  119. package/src/Text/Text.style.demo.tsx +16 -0
  120. package/src/Text/Text.styles.old.tsx +219 -0
  121. package/src/Text/Text.styles.tsx +94 -78
  122. package/src/Text/Text.web.tsx +2 -2
  123. package/src/Text/index.ts +1 -0
  124. package/src/TextArea/TextArea.styles.old.tsx +213 -0
  125. package/src/TextArea/TextArea.styles.tsx +101 -157
  126. package/src/Tooltip/Tooltip.native.tsx +2 -2
  127. package/src/Tooltip/Tooltip.styles.old.tsx +82 -0
  128. package/src/Tooltip/Tooltip.styles.tsx +38 -53
  129. package/src/Tooltip/Tooltip.web.tsx +2 -2
  130. package/src/Video/Video.styles.old.tsx +51 -0
  131. package/src/Video/Video.styles.tsx +32 -28
  132. package/src/View/View.native.tsx +12 -12
  133. package/src/View/View.styles.old.tsx +125 -0
  134. package/src/View/View.styles.tsx +84 -103
  135. package/src/View/View.web.tsx +14 -2
  136. package/src/examples/CardExamples.tsx +0 -6
  137. package/src/extensions/applyExtension.ts +210 -0
  138. package/src/extensions/extendComponent.ts +438 -0
  139. package/src/extensions/index.ts +102 -0
  140. package/src/extensions/types.ts +497 -0
  141. package/src/globals.ts +16 -0
  142. package/src/index.native.ts +4 -0
  143. package/src/index.ts +28 -0
  144. package/src/utils/deepMerge.ts +54 -2
@@ -1,279 +1,125 @@
1
+ /**
2
+ * Button styles using defineStyle with $iterator expansion.
3
+ *
4
+ * Dynamic style functions are used for intent/type combinations since
5
+ * the color depends on both values (compound logic).
6
+ */
1
7
  import { StyleSheet } from 'react-native-unistyles';
2
- import { Theme, Intent, Size, CompoundVariants} from '@idealyst/theme';
3
- import { buildSizeVariants } from '../utils/buildSizeVariants';
8
+ import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
9
+ import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
4
10
  import { ButtonGradient } from './types';
5
11
 
12
+ // Required: Unistyles must see StyleSheet usage in original source to process this file
13
+ void StyleSheet;
14
+
15
+ // Wrap theme for $iterator support
16
+ type Theme = ThemeStyleWrapper<BaseTheme>;
17
+
6
18
  type ButtonSize = Size;
7
- type ButtonIntent = Intent;
8
19
  type ButtonType = 'contained' | 'outlined' | 'text';
9
20
 
10
21
  export type ButtonVariants = {
11
22
  size: ButtonSize;
12
- intent: ButtonIntent;
23
+ intent: Intent;
13
24
  type: ButtonType;
14
25
  disabled: boolean;
15
26
  gradient?: ButtonGradient;
16
27
  }
17
28
 
18
29
  /**
19
- * Create intent variants (placeholder, colors handled by compound variants)
30
+ * All dynamic props passed to button style functions.
20
31
  */
21
- function createIntentVariants(theme: Theme) {
22
- const variants: any = {};
23
- for (const intent in theme.intents) {
24
- variants[intent] = {};
25
- }
26
- return variants;
27
- }
32
+ export type ButtonDynamicProps = {
33
+ intent?: Intent;
34
+ type?: ButtonType;
35
+ size?: Size;
36
+ disabled?: boolean;
37
+ gradient?: ButtonGradient;
38
+ };
28
39
 
29
40
  /**
30
- * Create type variants (structure only, colors handled by compound variants)
41
+ * Button styles with $iterator expansion for size variants.
42
+ *
43
+ * Intent/type combinations use dynamic functions with inlined theme accesses
44
+ * so Unistyles can trace all possible theme paths.
31
45
  */
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',
46
+ export const buttonStyles = defineStyle('Button', (theme: Theme) => ({
47
+ button: ({ intent = 'primary', type = 'contained' }: ButtonDynamicProps) => ({
48
+ boxSizing: 'border-box',
49
+ alignItems: 'center',
50
+ justifyContent: 'center',
51
+ borderRadius: 8,
52
+ fontWeight: '600',
53
+ textAlign: 'center',
54
+ // Inline theme accesses so Unistyles can trace them
55
+ backgroundColor: type === 'contained'
56
+ ? theme.intents[intent].primary
57
+ : type === 'outlined'
58
+ ? theme.colors.surface.primary
59
+ : 'transparent',
60
+ borderColor: type === 'outlined'
61
+ ? theme.intents[intent].primary
62
+ : 'transparent',
63
+ borderWidth: type === 'outlined' ? 1 : 0,
64
+ borderStyle: type === 'outlined' ? 'solid' as const : undefined,
65
+ _web: {
66
+ display: 'flex',
67
+ transition: 'all 0.1s ease',
45
68
  },
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,
69
+ variants: {
70
+ // $iterator expands for each button size
71
+ size: {
72
+ paddingVertical: theme.sizes.$button.paddingVertical,
73
+ paddingHorizontal: theme.sizes.$button.paddingHorizontal,
74
+ minHeight: theme.sizes.$button.minHeight,
65
75
  },
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
- }
90
-
91
- /**
92
- * Create gradient variant styles for web
93
- * Applies a transparent overlay gradient over the intent background color
94
- */
95
- function createGradientVariants() {
96
- return {
97
- 'darken': {
98
- _web: {
99
- backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(0, 0, 0, 0.15) 100%)',
76
+ disabled: {
77
+ true: { opacity: 0.6 },
78
+ false: { opacity: 1, _web: { cursor: 'pointer', _hover: { opacity: 0.90 }, _active: { opacity: 0.75 } } },
100
79
  },
101
- },
102
- 'lighten': {
103
- _web: {
104
- backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.2) 100%)',
80
+ gradient: {
81
+ darken: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(0, 0, 0, 0.15) 100%)' } },
82
+ lighten: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.2) 100%)' } },
105
83
  },
106
84
  },
107
- } as const;
108
- }
109
-
110
- /**
111
- * Create icon compound variants for intent+type combinations
112
- */
113
- function createIconCompoundVariants(theme: Theme): CompoundVariants<keyof ButtonVariants> {
114
- const compoundVariants: CompoundVariants<keyof ButtonVariants> = [];
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
- });
139
- }
140
-
141
- return compoundVariants;
142
- }
143
-
144
- /**
145
- * Create icon color variants dynamically based on theme, intent, and type
146
- */
147
- function createIconColorVariants(theme: Theme, intent: Intent) {
148
- const intentValue = theme.intents[intent];
149
-
150
- return {
151
- contained: {
152
- color: intentValue.contrast,
153
- },
154
- outlined: {
155
- color: intentValue.primary,
156
- },
157
- text: {
158
- color: intentValue.primary,
159
- },
160
- } as const;
161
- }
162
-
163
- /**
164
- * Generate button icon styles
165
- */
166
- const createButtonIconStyles = (theme: Theme) => {
167
- return ({ intent }: Partial<ButtonVariants>) => {
168
- return {
169
- display: 'flex',
170
- alignItems: 'center',
171
- justifyContent: 'center',
172
- variants: {
173
- size: buildSizeVariants(theme, 'button', size => ({
174
- width: size.iconSize,
175
- height: size.iconSize,
176
- })),
177
- type: createIconColorVariants(theme, intent),
178
- },
179
- } as const;
180
- };
181
- }
182
-
183
- /**
184
- * Generate button text styles
185
- */
186
- const createButtonTextStyles = (theme: Theme) => {
187
- return ({ intent }: Partial<ButtonVariants>) => {
188
- return {
189
- fontWeight: '600',
190
- textAlign: 'center',
191
- variants: {
192
- size: buildSizeVariants(theme, 'button', size => ({
193
- fontSize: size.fontSize,
194
- })),
195
- type: createIconColorVariants(theme, intent), // Text uses same colors as icons
196
- disabled: {
197
- true: { opacity: 0.6 },
198
- false: { opacity: 1 },
199
- },
85
+ }),
86
+ text: ({ intent = 'primary', type = 'contained' }: ButtonDynamicProps) => ({
87
+ fontWeight: '600',
88
+ textAlign: 'center',
89
+ // Inline: contained uses contrast, others use primary
90
+ color: type === 'contained'
91
+ ? theme.intents[intent].contrast
92
+ : theme.intents[intent].primary,
93
+ variants: {
94
+ size: {
95
+ fontSize: theme.sizes.$button.fontSize,
96
+ lineHeight: theme.sizes.$button.fontSize,
200
97
  },
201
- } as const;
202
- };
203
- }
204
-
205
- // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
206
- // transform on native cannot resolve function calls to extract variant structures.
207
- export const buttonStyles = StyleSheet.create((theme: Theme) => {
208
- return {
209
- button: {
210
- boxSizing: 'border-box',
211
- alignItems: 'center',
212
- justifyContent: 'center',
213
- borderRadius: 8,
214
- fontWeight: '600',
215
- textAlign: 'center',
216
- _web: {
217
- display: 'flex',
218
- transition: 'all 0.1s ease',
98
+ disabled: {
99
+ true: { opacity: 0.6 },
100
+ false: { opacity: 1 },
219
101
  },
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: {
244
- display: 'flex',
245
- alignItems: 'center',
246
- justifyContent: 'center',
247
- variants: {
248
- size: buildSizeVariants(theme, 'button', size => ({
249
- width: size.iconSize,
250
- height: size.iconSize,
251
- })),
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
- },
102
+ },
103
+ }),
104
+ icon: ({ intent = 'primary', type = 'contained' }: ButtonDynamicProps) => ({
105
+ display: 'flex',
106
+ alignItems: 'center',
107
+ justifyContent: 'center',
108
+ color: type === 'contained'
109
+ ? theme.intents[intent].contrast
110
+ : theme.intents[intent].primary,
111
+ variants: {
112
+ size: {
113
+ width: theme.sizes.$button.iconSize,
114
+ height: theme.sizes.$button.iconSize,
275
115
  },
276
- compoundVariants: createIconCompoundVariants(theme), // Text uses same colors as icons
277
- } as const,
278
- };
279
- });
116
+ },
117
+ }),
118
+ iconContainer: (_props: ButtonDynamicProps) => ({
119
+ display: 'flex' as const,
120
+ flexDirection: 'row' as const,
121
+ alignItems: 'center' as const,
122
+ justifyContent: 'center' as const,
123
+ gap: 4,
124
+ }),
125
+ }));
@@ -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
- e.stopPropagation();
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
@@ -1,5 +1,6 @@
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
5
  import { cardStyles } from './Card.styles';
5
6
  import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
@@ -42,12 +43,12 @@ const Card = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pressabl
42
43
  accessibilityPressed,
43
44
  });
44
45
  }, [accessibilityLabel, accessibilityHint, accessibilityDisabled, disabled, accessibilityHidden, accessibilityRole, clickable, accessibilityPressed]);
46
+
45
47
  // Apply variants
46
48
  cardStyles.useVariants({
47
- clickable,
48
- radius,
49
49
  type,
50
- intent,
50
+ radius,
51
+ clickable,
51
52
  disabled,
52
53
  gap,
53
54
  padding,
@@ -58,13 +59,16 @@ const Card = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pressabl
58
59
  marginHorizontal,
59
60
  });
60
61
 
62
+ // Get card style
63
+ const cardStyle = (cardStyles.card as any)({});
64
+
61
65
  // Use appropriate component based on clickable state
62
66
  const Component = clickable ? Pressable : View;
63
67
 
64
68
  const componentProps = {
65
69
  ref,
66
70
  nativeID: id,
67
- style: [cardStyles.card, style],
71
+ style: [cardStyle, style],
68
72
  testID,
69
73
  ...nativeA11yProps,
70
74
  ...(clickable && {
@@ -0,0 +1,160 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, Intent, Radius } from '@idealyst/theme';
3
+ import {
4
+ buildGapVariants,
5
+ buildPaddingVariants,
6
+ buildPaddingVerticalVariants,
7
+ buildPaddingHorizontalVariants,
8
+ buildMarginVariants,
9
+ buildMarginVerticalVariants,
10
+ buildMarginHorizontalVariants,
11
+ } from '../utils/buildViewStyleVariants';
12
+ import { ViewStyleSize } from '../utils/viewStyleProps';
13
+ import { applyExtensions } from '../extensions/applyExtension';
14
+
15
+ type CardType = 'outlined' | 'elevated' | 'filled';
16
+ type CardRadius = Radius;
17
+ type CardIntent = Intent | 'info' | 'neutral';
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
+
26
+ export type CardVariants = {
27
+ type: CardType;
28
+ radius: CardRadius;
29
+ intent: CardIntent;
30
+ clickable: boolean;
31
+ disabled: boolean;
32
+ // Spacing variants from ContainerStyleProps
33
+ gap: ViewStyleSize;
34
+ padding: ViewStyleSize;
35
+ paddingVertical: ViewStyleSize;
36
+ paddingHorizontal: ViewStyleSize;
37
+ margin: ViewStyleSize;
38
+ marginVertical: ViewStyleSize;
39
+ marginHorizontal: ViewStyleSize;
40
+ };
41
+
42
+ type CardDynamicProps = {
43
+ intent?: CardIntent;
44
+ type?: CardType;
45
+ };
46
+
47
+ /**
48
+ * Get the border color based on intent (only used for outlined type)
49
+ */
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;
58
+ }
59
+
60
+ /**
61
+ * Get type-specific styles
62
+ */
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 {};
85
+ }
86
+ }
87
+
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,
96
+ position: 'relative',
97
+ overflow: 'hidden',
98
+ variants: {
99
+ radius: {
100
+ none: { borderRadius: 0 },
101
+ xs: { borderRadius: 2 },
102
+ sm: { borderRadius: 4 },
103
+ md: { borderRadius: 8 },
104
+ lg: { borderRadius: 12 },
105
+ xl: { borderRadius: 16 },
106
+ },
107
+ clickable: {
108
+ true: {
109
+ _web: {
110
+ cursor: 'pointer',
111
+ transition: 'all 0.2s ease',
112
+ _hover: {
113
+ transform: 'translateY(-2px)',
114
+ boxShadow:
115
+ '0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.06)',
116
+ },
117
+ },
118
+ },
119
+ false: {
120
+ _web: {
121
+ cursor: 'default',
122
+ },
123
+ },
124
+ },
125
+ disabled: {
126
+ true: {
127
+ opacity: 0.6,
128
+ _web: {
129
+ cursor: 'not-allowed',
130
+ },
131
+ },
132
+ false: {
133
+ opacity: 1,
134
+ },
135
+ },
136
+ // Spacing variants from ContainerStyleProps
137
+ gap: buildGapVariants(theme),
138
+ padding: buildPaddingVariants(theme),
139
+ paddingVertical: buildPaddingVerticalVariants(theme),
140
+ paddingHorizontal: buildPaddingHorizontalVariants(theme),
141
+ margin: buildMarginVariants(theme),
142
+ marginVertical: buildMarginVerticalVariants(theme),
143
+ marginHorizontal: buildMarginHorizontalVariants(theme),
144
+ },
145
+ _web: {
146
+ display: 'flex',
147
+ flexDirection: 'column',
148
+ boxSizing: 'border-box',
149
+ },
150
+ } as const;
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
+ });
160
+ });