@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.
Files changed (104) hide show
  1. package/package.json +8 -3
  2. package/src/Accordion/Accordion.native.tsx +15 -9
  3. package/src/Accordion/Accordion.styles.tsx +193 -168
  4. package/src/Accordion/Accordion.web.tsx +12 -7
  5. package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
  6. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +22 -11
  7. package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -2
  8. package/src/Alert/Alert.native.tsx +11 -10
  9. package/src/Alert/Alert.styles.tsx +162 -253
  10. package/src/Alert/Alert.web.tsx +6 -10
  11. package/src/Avatar/Avatar.native.tsx +5 -2
  12. package/src/Avatar/Avatar.styles.tsx +48 -18
  13. package/src/Avatar/Avatar.web.tsx +2 -2
  14. package/src/Badge/Badge.native.tsx +2 -2
  15. package/src/Badge/Badge.styles.tsx +37 -16
  16. package/src/Badge/Badge.web.tsx +6 -6
  17. package/src/Breadcrumb/Breadcrumb.native.tsx +12 -5
  18. package/src/Breadcrumb/Breadcrumb.styles.tsx +59 -58
  19. package/src/Breadcrumb/Breadcrumb.web.tsx +13 -6
  20. package/src/Button/Button.native.tsx +39 -14
  21. package/src/Button/Button.styles.tsx +106 -208
  22. package/src/Button/Button.web.tsx +10 -8
  23. package/src/Card/Card.native.tsx +14 -6
  24. package/src/Card/Card.styles.tsx +64 -62
  25. package/src/Card/Card.web.tsx +5 -4
  26. package/src/Checkbox/Checkbox.native.tsx +7 -3
  27. package/src/Checkbox/Checkbox.styles.tsx +49 -25
  28. package/src/Checkbox/Checkbox.web.tsx +3 -3
  29. package/src/Chip/Chip.native.tsx +5 -5
  30. package/src/Chip/Chip.styles.tsx +71 -21
  31. package/src/Chip/Chip.web.tsx +5 -5
  32. package/src/Dialog/Dialog.native.tsx +10 -4
  33. package/src/Dialog/Dialog.styles.tsx +130 -90
  34. package/src/Dialog/Dialog.web.tsx +4 -4
  35. package/src/Divider/Divider.native.tsx +29 -42
  36. package/src/Divider/Divider.styles.tsx +138 -242
  37. package/src/Divider/Divider.web.tsx +17 -14
  38. package/src/Icon/Icon.native.tsx +11 -3
  39. package/src/Icon/Icon.styles.tsx +10 -4
  40. package/src/Image/Image.styles.tsx +53 -37
  41. package/src/Input/Input.native.tsx +6 -7
  42. package/src/Input/Input.styles.tsx +194 -174
  43. package/src/Input/Input.web.tsx +5 -8
  44. package/src/Link/Link.native.tsx +4 -1
  45. package/src/List/List.styles.tsx +79 -105
  46. package/src/List/ListItem.native.tsx +5 -3
  47. package/src/List/ListItem.web.tsx +4 -3
  48. package/src/Menu/Menu.native.tsx +1 -1
  49. package/src/Menu/Menu.styles.tsx +53 -37
  50. package/src/Menu/Menu.web.tsx +2 -2
  51. package/src/Menu/MenuItem.native.tsx +5 -3
  52. package/src/Menu/MenuItem.styles.tsx +68 -69
  53. package/src/Menu/MenuItem.web.tsx +16 -3
  54. package/src/Popover/Popover.native.tsx +1 -1
  55. package/src/Popover/Popover.styles.tsx +40 -29
  56. package/src/Popover/Popover.web.tsx +1 -1
  57. package/src/Pressable/Pressable.native.tsx +3 -1
  58. package/src/Pressable/Pressable.styles.tsx +20 -13
  59. package/src/Pressable/Pressable.web.tsx +1 -1
  60. package/src/Progress/Progress.native.tsx +15 -6
  61. package/src/Progress/Progress.styles.tsx +125 -85
  62. package/src/Progress/Progress.web.tsx +10 -9
  63. package/src/RadioButton/RadioButton.native.tsx +8 -3
  64. package/src/RadioButton/RadioButton.styles.tsx +44 -37
  65. package/src/RadioButton/RadioButton.web.tsx +3 -3
  66. package/src/SVGImage/SVGImage.styles.tsx +28 -16
  67. package/src/Screen/Screen.native.tsx +23 -13
  68. package/src/Screen/Screen.styles.tsx +57 -46
  69. package/src/Screen/Screen.web.tsx +1 -1
  70. package/src/Select/Select.native.tsx +11 -5
  71. package/src/Select/Select.styles.tsx +72 -52
  72. package/src/Select/Select.web.tsx +5 -5
  73. package/src/Skeleton/Skeleton.styles.tsx +26 -14
  74. package/src/Slider/Slider.native.tsx +9 -5
  75. package/src/Slider/Slider.styles.tsx +59 -48
  76. package/src/Slider/Slider.web.tsx +5 -5
  77. package/src/Switch/Switch.native.tsx +6 -2
  78. package/src/Switch/Switch.styles.tsx +46 -19
  79. package/src/Switch/Switch.web.tsx +4 -4
  80. package/src/TabBar/TabBar.native.tsx +23 -31
  81. package/src/TabBar/TabBar.styles.tsx +215 -371
  82. package/src/TabBar/TabBar.web.tsx +21 -33
  83. package/src/Table/Table.native.tsx +1 -1
  84. package/src/Table/Table.styles.tsx +11 -4
  85. package/src/Table/Table.web.tsx +1 -1
  86. package/src/Text/Text.native.tsx +3 -4
  87. package/src/Text/Text.styles.tsx +7 -1
  88. package/src/Text/Text.web.tsx +1 -1
  89. package/src/TextArea/TextArea.styles.tsx +90 -58
  90. package/src/Tooltip/Tooltip.native.tsx +2 -2
  91. package/src/Tooltip/Tooltip.styles.tsx +21 -12
  92. package/src/Tooltip/Tooltip.web.tsx +2 -2
  93. package/src/Video/Video.styles.tsx +39 -23
  94. package/src/View/View.native.tsx +4 -2
  95. package/src/View/View.styles.tsx +33 -22
  96. package/src/View/View.web.tsx +13 -2
  97. package/src/extensions/applyExtension.ts +210 -0
  98. package/src/extensions/extendComponent.ts +377 -0
  99. package/src/extensions/index.ts +102 -0
  100. package/src/extensions/types.ts +497 -0
  101. package/src/globals.ts +16 -0
  102. package/src/index.native.ts +4 -0
  103. package/src/index.ts +28 -0
  104. package/src/utils/deepMerge.ts +54 -2
@@ -1,5 +1,5 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
- import { Theme, CompoundVariants } from '@idealyst/theme';
2
+ import { Theme } from '@idealyst/theme';
3
3
  import { buildSizeVariants } from '../utils/buildSizeVariants';
4
4
  import {
5
5
  buildGapVariants,
@@ -11,6 +11,7 @@ import {
11
11
  buildMarginHorizontalVariants,
12
12
  } from '../utils/buildViewStyleVariants';
13
13
  import { ListSizeVariant, ListType } from './types';
14
+ import { applyExtensions } from '../extensions/applyExtension';
14
15
 
15
16
  type ListVariants = {
16
17
  type: ListType;
@@ -47,97 +48,76 @@ function createContainerTypeVariants(theme: Theme) {
47
48
  }
48
49
 
49
50
 
51
+ type ItemDynamicProps = {
52
+ type?: ListType;
53
+ disabled?: boolean;
54
+ clickable?: boolean;
55
+ };
56
+
50
57
  /**
51
- * Create compound variants for item
58
+ * Get item hover styles based on disabled and clickable state
52
59
  */
53
- function createItemCompoundVariants(theme: Theme): CompoundVariants<keyof ListVariants> {
54
- return [
55
- {
56
- type: 'divided',
57
- styles: {
58
- _web: {
59
- ':last-child': {
60
- borderBottom: 'none',
61
- },
62
- },
63
- },
64
- },
65
- {
66
- disabled: true,
67
- styles: {
68
- _web: {
69
- _hover: {
70
- backgroundColor: 'transparent',
71
- borderRadius: 0,
72
- },
73
- },
74
- },
75
- },
76
- {
77
- clickable: false,
78
- styles: {
79
- _web: {
80
- _hover: {
81
- backgroundColor: 'transparent',
82
- borderRadius: 0,
83
- },
84
- },
85
- },
86
- },
87
- ];
60
+ function getItemHoverStyles(theme: Theme, disabled: boolean, clickable: boolean) {
61
+ if (disabled || !clickable) {
62
+ return {
63
+ backgroundColor: 'transparent',
64
+ borderRadius: 0,
65
+ };
66
+ }
67
+ return {
68
+ backgroundColor: theme.colors.surface.secondary,
69
+ borderRadius: 4,
70
+ };
88
71
  }
89
72
 
90
73
 
91
- export const listStyles = StyleSheet.create((theme: Theme) => {
92
- return {
93
- container: {
94
- display: 'flex',
95
- flexDirection: 'column',
96
- width: '100%',
97
- variants: {
98
- type: createContainerTypeVariants(theme),
99
- scrollable: {
100
- true: {
101
- _web: {
102
- overflow: 'auto',
103
- },
74
+ // Container style creator for extension support
75
+ function createContainerStyles(theme: Theme) {
76
+ return () => ({
77
+ display: 'flex' as const,
78
+ flexDirection: 'column' as const,
79
+ width: '100%',
80
+ variants: {
81
+ type: createContainerTypeVariants(theme),
82
+ scrollable: {
83
+ true: {
84
+ _web: {
85
+ overflow: 'auto',
104
86
  },
105
- false: {},
106
87
  },
107
- // Spacing variants from ContainerStyleProps
108
- gap: buildGapVariants(theme),
109
- padding: buildPaddingVariants(theme),
110
- paddingVertical: buildPaddingVerticalVariants(theme),
111
- paddingHorizontal: buildPaddingHorizontalVariants(theme),
112
- margin: buildMarginVariants(theme),
113
- marginVertical: buildMarginVerticalVariants(theme),
114
- marginHorizontal: buildMarginHorizontalVariants(theme),
88
+ false: {},
115
89
  },
116
- },
117
- item: {
90
+ // Spacing variants from ContainerStyleProps
91
+ gap: buildGapVariants(theme),
92
+ padding: buildPaddingVariants(theme),
93
+ paddingVertical: buildPaddingVerticalVariants(theme),
94
+ paddingHorizontal: buildPaddingHorizontalVariants(theme),
95
+ margin: buildMarginVariants(theme),
96
+ marginVertical: buildMarginVerticalVariants(theme),
97
+ marginHorizontal: buildMarginHorizontalVariants(theme),
98
+ },
99
+ });
100
+ }
101
+
102
+ // Item style creator for extension support
103
+ function createItemStyles(theme: Theme) {
104
+ return ({ type = 'default', disabled = false, clickable = true }: ItemDynamicProps) => {
105
+ const hoverStyles = getItemHoverStyles(theme, disabled, clickable);
106
+ return {
118
107
  display: 'flex',
119
108
  flexDirection: 'row',
120
109
  alignItems: 'center',
121
110
  backgroundColor: 'transparent',
122
111
  textAlign: 'left',
112
+ borderBottomWidth: type === 'divided' ? 1 : 0,
113
+ borderBottomStyle: type === 'divided' ? 'solid' as const : undefined,
114
+ borderBottomColor: type === 'divided' ? theme.colors.border.primary : undefined,
123
115
  variants: {
124
116
  size: buildSizeVariants(theme, 'list', (size) => ({
125
117
  paddingVertical: size.paddingVertical,
126
118
  paddingHorizontal: size.paddingHorizontal,
127
119
  minHeight: size.minHeight,
128
120
  })),
129
- type: {
130
- default: {},
131
- bordered: {},
132
- divided: {
133
- borderBottomWidth: 1,
134
- borderBottomStyle: 'solid',
135
- borderBottomColor: theme.colors.border.primary,
136
- _web: {
137
- borderBottom: `1px solid ${theme.colors.border.primary}`,
138
- },
139
- },
140
- },
141
121
  active: {
142
122
  true: {
143
123
  backgroundColor: theme.colors.surface.secondary,
@@ -155,36 +135,30 @@ export const listStyles = StyleSheet.create((theme: Theme) => {
155
135
  },
156
136
  false: {},
157
137
  },
158
- disabled: {
159
- true: {
160
- opacity: 0.5,
161
- _web: {
162
- cursor: 'not-allowed',
163
- },
164
- },
165
- false: {},
166
- },
167
- clickable: {
168
- true: {},
169
- false: {
170
- _web: {
171
- cursor: 'default',
172
- },
173
- },
174
- },
175
138
  } as const,
176
- compoundVariants: createItemCompoundVariants(theme),
139
+ opacity: disabled ? 0.5 : 1,
177
140
  _web: {
178
141
  border: 'none',
179
- cursor: 'pointer',
142
+ cursor: disabled ? 'not-allowed' : (clickable ? 'pointer' : 'default'),
180
143
  outline: 'none',
181
144
  transition: 'background-color 0.2s ease, border-color 0.2s ease',
182
- _hover: {
183
- backgroundColor: theme.colors.surface.secondary,
184
- borderRadius: 4,
185
- },
145
+ borderBottom: type === 'divided' ? `1px solid ${theme.colors.border.primary}` : undefined,
146
+ _hover: hoverStyles,
186
147
  },
187
- } as const,
148
+ } as const;
149
+ };
150
+ }
151
+
152
+ export const listStyles = StyleSheet.create((theme: Theme) => {
153
+ // Apply extensions to main visual elements
154
+ const extended = applyExtensions('List', theme, {
155
+ container: createContainerStyles(theme),
156
+ item: createItemStyles(theme),
157
+ });
158
+
159
+ return {
160
+ ...extended,
161
+ // Minor utility styles (not extended)
188
162
  itemContent: {
189
163
  display: 'flex',
190
164
  flexDirection: 'row',
@@ -222,17 +196,17 @@ export const listStyles = StyleSheet.create((theme: Theme) => {
222
196
  true: {
223
197
  color: theme.colors.text.secondary,
224
198
  },
225
- false: {},
226
- },
227
- selected: {
228
- true: {
229
- color: theme.intents.primary.primary,
230
- fontWeight: '600',
199
+ false: {},
200
+ },
201
+ selected: {
202
+ true: {
203
+ color: theme.intents.primary.primary,
204
+ fontWeight: '600',
205
+ },
206
+ false: {},
231
207
  },
232
- false: {},
233
208
  },
234
209
  },
235
- },
236
210
  trailing: {
237
211
  display: 'flex',
238
212
  alignItems: 'center',
@@ -51,15 +51,17 @@ const ListItem = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pres
51
51
  const effectiveSize = size ?? listContext.size ?? 'md';
52
52
  const effectiveVariant = listContext.type ?? 'default';
53
53
 
54
- // Apply types
54
+ // Apply variants (for size, active, selected, disabled on label)
55
55
  listStyles.useVariants({
56
56
  size: effectiveSize,
57
57
  active,
58
58
  selected,
59
59
  disabled,
60
- clickable: isClickable,
61
60
  });
62
61
 
62
+ // Get dynamic item style with type, disabled, and clickable props
63
+ const itemStyle = (listStyles.item as any)({ type: effectiveVariant, disabled, clickable: isClickable });
64
+
63
65
  // Resolve icon color - check intents first, then color palette
64
66
  const resolvedIconColor = (() => {
65
67
  if (!iconColor) return undefined;
@@ -117,7 +119,7 @@ const ListItem = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pres
117
119
  );
118
120
 
119
121
  const indentStyle = indent > 0 ? { paddingLeft: indent * 16 } : {};
120
- const combinedStyle = [listStyles.item, indentStyle, style];
122
+ const combinedStyle = [itemStyle, indentStyle, style];
121
123
 
122
124
  if (isClickable) {
123
125
  return (
@@ -32,16 +32,17 @@ const ListItem: React.FC<ListItemProps> = ({
32
32
  const effectiveSize = size ?? listContext.size ?? 'md';
33
33
  const effectiveVariant = listContext.type ?? 'default';
34
34
 
35
- // Apply types
35
+ // Apply variants (for size, active, selected, disabled on label)
36
36
  listStyles.useVariants({
37
37
  size: effectiveSize,
38
38
  active,
39
39
  selected,
40
40
  disabled,
41
- clickable: isClickable,
42
41
  });
43
42
 
44
- const itemProps = getWebProps([listStyles.item, style]);
43
+ // Get dynamic item style with type, disabled, and clickable props
44
+ const itemStyle = (listStyles.item as any)({ type: effectiveVariant, disabled, clickable: isClickable });
45
+ const itemProps = getWebProps([itemStyle, style]);
45
46
  const labelProps = getWebProps([listStyles.label]);
46
47
  const leadingProps = getWebProps([listStyles.leading]);
47
48
  const trailingProps = getWebProps([listStyles.trailing]);
@@ -113,7 +113,7 @@ const Menu = forwardRef<View, MenuProps>(({
113
113
  width={menuPosition.width}
114
114
  maxHeight={300}
115
115
  style={[
116
- menuStyles.menu,
116
+ (menuStyles.menu as any)({}),
117
117
  style,
118
118
  { opacity: shouldShow ? 1 : 0 }
119
119
  ]}
@@ -1,6 +1,7 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
- import { Theme, StylesheetStyles, CompoundVariants, Intent, Size} from '@idealyst/theme';
2
+ import { Theme, StylesheetStyles, CompoundVariants, Intent, Size } from '@idealyst/theme';
3
3
  import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { applyExtensions } from '../extensions/applyExtension';
4
5
 
5
6
  type MenuSize = Size;
6
7
  type MenuIntent = Intent;
@@ -88,9 +89,46 @@ function createLabelSizeVariants(theme: Theme) {
88
89
  }));
89
90
  }
90
91
 
91
- const createItemStyles = (theme: Theme) => {
92
- return ({ intent }: MenuVariants) => {
93
- const hoverStyles = getItemHoverStyles(theme, intent);
92
+ // Main element style creators (for extension support)
93
+ function createOverlayStyles(theme: Theme) {
94
+ return () => ({
95
+ backgroundColor: 'transparent',
96
+ _web: {
97
+ position: 'fixed' as const,
98
+ top: 0,
99
+ left: 0,
100
+ right: 0,
101
+ bottom: 0,
102
+ zIndex: 999,
103
+ },
104
+ });
105
+ }
106
+
107
+ function createMenuStyles(theme: Theme) {
108
+ return () => ({
109
+ position: 'absolute' as const,
110
+ zIndex: 1000,
111
+ backgroundColor: theme.colors.surface.primary,
112
+ borderWidth: 1,
113
+ borderStyle: 'solid' as const,
114
+ borderColor: theme.colors.border.primary,
115
+ borderRadius: 8,
116
+ minWidth: 120,
117
+ maxWidth: 400,
118
+ padding: 4,
119
+ display: 'flex' as const,
120
+ flexDirection: 'column' as const,
121
+ _web: {
122
+ border: `1px solid ${theme.colors.border.primary}`,
123
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
124
+ width: 'fit-content',
125
+ },
126
+ });
127
+ }
128
+
129
+ function createItemStyles(theme: Theme) {
130
+ return ({ intent }: Partial<MenuVariants>) => {
131
+ const hoverStyles = getItemHoverStyles(theme, intent ?? 'neutral');
94
132
  return {
95
133
  flexDirection: 'row',
96
134
  alignItems: 'center',
@@ -122,49 +160,27 @@ const createItemStyles = (theme: Theme) => {
122
160
  },
123
161
  ...hoverStyles,
124
162
  } as const;
125
- }
163
+ };
126
164
  }
127
165
 
128
166
  // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
129
167
  export const menuStyles = StyleSheet.create((theme: Theme) => {
168
+ // Apply extensions to main visual elements
169
+ const extended = applyExtensions('Menu', theme, {
170
+ overlay: createOverlayStyles(theme),
171
+ menu: createMenuStyles(theme),
172
+ item: createItemStyles(theme),
173
+ });
174
+
130
175
  return {
131
- overlay: {
132
- backgroundColor: 'transparent',
133
- _web: {
134
- position: 'fixed' as const,
135
- top: 0,
136
- left: 0,
137
- right: 0,
138
- bottom: 0,
139
- zIndex: 999,
140
- }
141
- } as const,
142
- menu: {
143
- position: 'absolute',
144
- zIndex: 1000,
145
- backgroundColor: theme.colors.surface.primary,
146
- borderWidth: 1,
147
- borderStyle: 'solid',
148
- borderColor: theme.colors.border.primary,
149
- borderRadius: 8,
150
- minWidth: 120,
151
- maxWidth: 400,
152
- padding: 4,
153
- display: 'flex',
154
- flexDirection: 'column',
155
- _web: {
156
- border: `1px solid ${theme.colors.border.primary}`,
157
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
158
- width: 'fit-content',
159
- },
160
- } as const,
176
+ ...extended,
177
+ // Minor utility styles (not extended)
161
178
  separator: {
162
179
  height: 1,
163
180
  backgroundColor: theme.colors.border.primary,
164
181
  marginTop: 4,
165
182
  marginBottom: 4,
166
183
  },
167
- item: createItemStyles(theme),
168
184
  icon: {
169
185
  alignItems: 'center',
170
186
  justifyContent: 'center',
@@ -181,5 +197,5 @@ export const menuStyles = StyleSheet.create((theme: Theme) => {
181
197
  size: createLabelSizeVariants(theme),
182
198
  } as const,
183
199
  } as const,
184
- } as const;
200
+ };
185
201
  });
@@ -113,8 +113,8 @@ const Menu = forwardRef<HTMLDivElement, MenuProps>(({
113
113
  size,
114
114
  });
115
115
 
116
- const overlayProps = getWebProps([menuStyles.overlay]);
117
- const menuProps = getWebProps([menuStyles.menu, style as any]);
116
+ const overlayProps = getWebProps([(menuStyles.overlay as any)({})]);
117
+ const menuProps = getWebProps([(menuStyles.menu as any)({}), style as any]);
118
118
  const separatorProps = getWebProps([menuStyles.separator]);
119
119
 
120
120
  const handleTriggerClick = () => {
@@ -13,13 +13,15 @@ interface MenuItemProps {
13
13
  }
14
14
 
15
15
  const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps>(({ item, onPress, size = 'md', testID }, ref) => {
16
- // Initialize styles with useVariants
16
+ // Initialize styles with useVariants (for size and disabled)
17
17
  menuItemStyles.useVariants({
18
18
  size,
19
19
  disabled: Boolean(item.disabled),
20
- intent: item.intent || 'neutral',
21
20
  });
22
21
 
22
+ // Compute dynamic item style with intent
23
+ const itemStyle = (menuItemStyles.item as any)({ intent: item.intent || 'neutral' });
24
+
23
25
  const renderIcon = () => {
24
26
  if (!item.icon) return null;
25
27
 
@@ -39,7 +41,7 @@ const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps>(({ it
39
41
  return (
40
42
  <Pressable
41
43
  ref={ref}
42
- style={menuItemStyles.item}
44
+ style={itemStyle}
43
45
  onPress={() => onPress(item)}
44
46
  disabled={item.disabled}
45
47
  accessibilityRole="menuitem"
@@ -1,27 +1,48 @@
1
1
  import { StyleSheet } from 'react-native-unistyles';
2
- import { Theme } from '@idealyst/theme';
2
+ import { Theme, Intent } from '@idealyst/theme';
3
3
  import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { applyExtensions } from '../extensions/applyExtension';
4
5
 
5
- // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
6
- // @ts-ignore - TS language server needs restart to pick up theme structure changes
7
- export const menuItemStyles = StyleSheet.create((theme: Theme) => {
6
+ type MenuItemDynamicProps = {
7
+ intent?: Intent;
8
+ };
9
+
10
+ /**
11
+ * Get hover styles for menu item based on intent
12
+ */
13
+ function getItemHoverStyles(theme: Theme, intent: Intent) {
14
+ if (intent === 'neutral') {
15
+ return {
16
+ _web: {
17
+ _hover: {
18
+ backgroundColor: theme.colors.surface.secondary,
19
+ },
20
+ },
21
+ } as const;
22
+ }
23
+ const intentValue = theme.intents[intent];
8
24
  return {
9
- item: {
25
+ _web: {
26
+ _hover: {
27
+ backgroundColor: intentValue.light,
28
+ color: intentValue.primary,
29
+ },
30
+ },
31
+ } as const;
32
+ }
33
+
34
+ /**
35
+ * Create dynamic item styles
36
+ */
37
+ function createItemStyles(theme: Theme) {
38
+ return ({ intent = 'neutral' }: MenuItemDynamicProps) => {
39
+ const hoverStyles = getItemHoverStyles(theme, intent);
40
+ return {
10
41
  flexDirection: 'row',
11
42
  alignItems: 'center',
12
43
  backgroundColor: 'transparent',
13
44
  borderRadius: 4,
14
45
  minHeight: 44,
15
- _web: {
16
- cursor: 'pointer',
17
- border: 'none',
18
- outline: 'none',
19
- transition: 'background-color 0.2s ease',
20
- textAlign: 'left',
21
- _hover: {
22
- backgroundColor: theme.colors.surface.secondary,
23
- },
24
- },
25
46
  variants: {
26
47
  size: buildSizeVariants(theme, 'menu', (size) => ({
27
48
  paddingVertical: size.paddingVertical,
@@ -32,67 +53,42 @@ export const menuItemStyles = StyleSheet.create((theme: Theme) => {
32
53
  opacity: 0.5,
33
54
  _web: {
34
55
  cursor: 'not-allowed',
35
- },
36
- },
37
- false: {},
38
- },
39
- intent: {
40
- primary: {
41
- _web: {
42
- _hover: {
43
- backgroundColor: theme.intents.primary.light,
44
- color: theme.intents.primary.primary,
45
- },
46
- },
47
- },
48
- neutral: {},
49
- success: {
50
- _web: {
51
- _hover: {
52
- backgroundColor: theme.intents.success.light,
53
- color: theme.intents.success.primary,
54
- },
55
- },
56
- },
57
- error: {
58
- _web: {
59
56
  _hover: {
60
- backgroundColor: theme.intents.error.light,
61
- color: theme.intents.error.primary,
62
- },
63
- },
64
- },
65
- warning: {
66
- _web: {
67
- _hover: {
68
- backgroundColor: theme.intents.warning.light,
69
- color: theme.intents.warning.primary,
70
- },
71
- },
72
- },
73
- info: {
74
- _web: {
75
- _hover: {
76
- backgroundColor: theme.intents.info.light,
77
- color: theme.intents.info.primary,
57
+ backgroundColor: 'transparent',
78
58
  },
79
59
  },
80
60
  },
61
+ false: {},
81
62
  },
82
63
  },
83
- compoundVariants: [
84
- {
85
- disabled: true,
86
- styles: {
87
- _web: {
88
- _hover: {
89
- backgroundColor: 'transparent',
90
- },
91
- },
92
- },
64
+ _web: {
65
+ display: 'flex',
66
+ width: '100%',
67
+ cursor: 'pointer',
68
+ border: 'none',
69
+ borderWidth: 0,
70
+ outline: 'none',
71
+ transition: 'background-color 0.2s ease',
72
+ textAlign: 'left',
73
+ _hover: {
74
+ backgroundColor: theme.colors.surface.secondary,
93
75
  },
94
- ],
95
- },
76
+ },
77
+ ...hoverStyles,
78
+ } as const;
79
+ };
80
+ }
81
+
82
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
83
+ export const menuItemStyles = StyleSheet.create((theme: Theme) => {
84
+ // Apply extensions to main visual elements
85
+ const extended = applyExtensions('MenuItem', theme, {
86
+ item: createItemStyles(theme),
87
+ });
88
+
89
+ return {
90
+ ...extended,
91
+ // Minor utility styles (not extended)
96
92
  icon: {
97
93
  alignItems: 'center',
98
94
  justifyContent: 'center',
@@ -105,6 +101,9 @@ export const menuItemStyles = StyleSheet.create((theme: Theme) => {
105
101
  fontSize: size.iconSize,
106
102
  }))
107
103
  },
104
+ _web: {
105
+ display: 'flex',
106
+ },
108
107
  },
109
108
  label: {
110
109
  flex: 1,
@@ -14,14 +14,15 @@ interface MenuItemProps {
14
14
  }
15
15
 
16
16
  const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(({ item, onPress, size = 'md', testID }, ref) => {
17
- // Initialize styles with useVariants
17
+ // Initialize styles with useVariants (for size and disabled)
18
18
  menuItemStyles.useVariants({
19
19
  size,
20
20
  disabled: Boolean(item.disabled),
21
- intent: item.intent || 'neutral',
22
21
  });
23
22
 
24
- const itemProps = getWebProps([menuItemStyles.item]);
23
+ // Compute dynamic item style with intent
24
+ const itemStyle = (menuItemStyles.item as any)({ intent: item.intent || 'neutral' });
25
+ const itemProps = getWebProps([itemStyle]);
25
26
  const iconProps = getWebProps([menuItemStyles.icon]);
26
27
  const labelProps = getWebProps([menuItemStyles.label]);
27
28
 
@@ -48,10 +49,22 @@ const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(({ item, onPress,
48
49
  // Merge refs
49
50
  const mergedRef = useMergeRefs(ref, itemProps.ref);
50
51
 
52
+ // Button reset styles that must be applied directly
53
+ const buttonResetStyles: React.CSSProperties = {
54
+ display: 'flex',
55
+ width: '100%',
56
+ border: 'none',
57
+ outline: 'none',
58
+ cursor: item.disabled ? 'not-allowed' : 'pointer',
59
+ background: 'transparent',
60
+ textAlign: 'left',
61
+ };
62
+
51
63
  return (
52
64
  <button
53
65
  {...itemProps}
54
66
  ref={mergedRef}
67
+ style={{ ...buttonResetStyles, ...itemProps.style }}
55
68
  onClick={() => onPress(item)}
56
69
  disabled={item.disabled}
57
70
  role="menuitem"