@idealyst/components 1.1.7 → 1.1.9

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 (129) hide show
  1. package/package.json +3 -3
  2. package/plugin/web.js +280 -532
  3. package/src/Accordion/Accordion.native.tsx +8 -6
  4. package/src/Accordion/Accordion.styles.old.tsx +298 -0
  5. package/src/Accordion/Accordion.styles.tsx +102 -236
  6. package/src/Accordion/Accordion.web.tsx +1 -3
  7. package/src/ActivityIndicator/ActivityIndicator.styles.old.tsx +94 -0
  8. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +44 -74
  9. package/src/Alert/Alert.native.tsx +16 -6
  10. package/src/Alert/Alert.styles.old.tsx +209 -0
  11. package/src/Alert/Alert.styles.tsx +67 -149
  12. package/src/Alert/Alert.web.tsx +3 -4
  13. package/src/Avatar/Avatar.styles.old.tsx +99 -0
  14. package/src/Avatar/Avatar.styles.tsx +35 -80
  15. package/src/Badge/Badge.styles.old.tsx +157 -0
  16. package/src/Badge/Badge.styles.tsx +61 -121
  17. package/src/Badge/Badge.web.tsx +8 -15
  18. package/src/Breadcrumb/Breadcrumb.styles.old.tsx +231 -0
  19. package/src/Breadcrumb/Breadcrumb.styles.tsx +83 -200
  20. package/src/Breadcrumb/Breadcrumb.web.tsx +31 -30
  21. package/src/Button/Button.native.tsx +14 -21
  22. package/src/Button/Button.styles.tsx +103 -140
  23. package/src/Button/Button.web.tsx +9 -19
  24. package/src/Card/Card.native.tsx +7 -11
  25. package/src/Card/Card.styles.old.tsx +160 -0
  26. package/src/Card/Card.styles.tsx +105 -142
  27. package/src/Card/Card.web.tsx +5 -4
  28. package/src/Checkbox/Checkbox.native.tsx +9 -5
  29. package/src/Checkbox/Checkbox.styles.old.tsx +271 -0
  30. package/src/Checkbox/Checkbox.styles.tsx +104 -216
  31. package/src/Checkbox/Checkbox.web.tsx +7 -8
  32. package/src/Chip/Chip.styles.old.tsx +184 -0
  33. package/src/Chip/Chip.styles.tsx +34 -72
  34. package/src/Chip/Chip.web.tsx +3 -5
  35. package/src/Dialog/Dialog.native.tsx +7 -4
  36. package/src/Dialog/Dialog.styles.old.tsx +202 -0
  37. package/src/Dialog/Dialog.styles.tsx +69 -133
  38. package/src/Dialog/Dialog.web.tsx +3 -3
  39. package/src/Dialog/types.ts +1 -1
  40. package/src/Divider/Divider.styles.old.tsx +172 -0
  41. package/src/Divider/Divider.styles.tsx +62 -84
  42. package/src/Icon/Icon.native.tsx +8 -8
  43. package/src/Icon/Icon.styles.old.tsx +81 -0
  44. package/src/Icon/Icon.styles.tsx +52 -66
  45. package/src/Icon/Icon.web.tsx +62 -21
  46. package/src/Icon/IconRegistry.native.ts +41 -0
  47. package/src/Icon/IconRegistry.ts +107 -0
  48. package/src/Icon/IconSvg/IconSvg.web.tsx +28 -5
  49. package/src/Icon/icon-resolver.ts +12 -43
  50. package/src/Icon/index.native.ts +2 -1
  51. package/src/Icon/index.ts +1 -0
  52. package/src/Icon/index.web.ts +1 -0
  53. package/src/Image/Image.styles.old.tsx +69 -0
  54. package/src/Image/Image.styles.tsx +46 -60
  55. package/src/Input/Input.native.tsx +138 -53
  56. package/src/Input/Input.styles.old.tsx +289 -0
  57. package/src/Input/Input.styles.tsx +134 -232
  58. package/src/Input/Input.web.tsx +5 -8
  59. package/src/List/List.native.tsx +5 -2
  60. package/src/List/List.styles.old.tsx +242 -0
  61. package/src/List/List.styles.tsx +179 -215
  62. package/src/List/ListItem.native.tsx +16 -11
  63. package/src/List/ListItem.web.tsx +26 -16
  64. package/src/Menu/Menu.styles.old.tsx +197 -0
  65. package/src/Menu/Menu.styles.tsx +68 -150
  66. package/src/Menu/MenuItem.native.tsx +5 -3
  67. package/src/Menu/MenuItem.styles.old.tsx +114 -0
  68. package/src/Menu/MenuItem.styles.tsx +57 -89
  69. package/src/Menu/MenuItem.web.tsx +10 -7
  70. package/src/Popover/Popover.native.tsx +10 -4
  71. package/src/Popover/Popover.styles.old.tsx +135 -0
  72. package/src/Popover/Popover.styles.tsx +51 -112
  73. package/src/Pressable/Pressable.styles.old.tsx +27 -0
  74. package/src/Pressable/Pressable.styles.tsx +35 -27
  75. package/src/Progress/Progress.styles.old.tsx +200 -0
  76. package/src/Progress/Progress.styles.tsx +75 -164
  77. package/src/RadioButton/RadioButton.native.tsx +4 -3
  78. package/src/RadioButton/RadioButton.styles.old.tsx +175 -0
  79. package/src/RadioButton/RadioButton.styles.tsx +83 -154
  80. package/src/RadioButton/RadioButton.web.tsx +2 -2
  81. package/src/SVGImage/SVGImage.styles.old.tsx +86 -0
  82. package/src/SVGImage/SVGImage.styles.tsx +35 -78
  83. package/src/Screen/Screen.native.tsx +19 -26
  84. package/src/Screen/Screen.styles.old.tsx +87 -0
  85. package/src/Screen/Screen.styles.tsx +103 -68
  86. package/src/Screen/Screen.web.tsx +2 -2
  87. package/src/Select/Select.native.tsx +42 -33
  88. package/src/Select/Select.styles.old.tsx +353 -0
  89. package/src/Select/Select.styles.tsx +214 -300
  90. package/src/Select/Select.web.tsx +45 -33
  91. package/src/Skeleton/Skeleton.styles.old.tsx +67 -0
  92. package/src/Skeleton/Skeleton.styles.tsx +29 -53
  93. package/src/Slider/Slider.styles.old.tsx +259 -0
  94. package/src/Slider/Slider.styles.tsx +153 -234
  95. package/src/Slider/Slider.web.tsx +2 -4
  96. package/src/Switch/Switch.native.tsx +9 -7
  97. package/src/Switch/Switch.styles.old.tsx +203 -0
  98. package/src/Switch/Switch.styles.tsx +101 -174
  99. package/src/Switch/Switch.web.tsx +7 -8
  100. package/src/TabBar/TabBar.native.tsx +3 -2
  101. package/src/TabBar/TabBar.styles.old.tsx +343 -0
  102. package/src/TabBar/TabBar.styles.tsx +145 -279
  103. package/src/Table/Table.native.tsx +180 -68
  104. package/src/Table/Table.styles.old.tsx +311 -0
  105. package/src/Table/Table.styles.tsx +140 -281
  106. package/src/Table/Table.web.tsx +169 -70
  107. package/src/Text/Text.native.tsx +1 -3
  108. package/src/Text/Text.style.demo.tsx +16 -0
  109. package/src/Text/Text.styles.old.tsx +219 -0
  110. package/src/Text/Text.styles.tsx +94 -84
  111. package/src/Text/Text.web.tsx +3 -2
  112. package/src/Text/index.ts +1 -0
  113. package/src/TextArea/TextArea.native.tsx +21 -8
  114. package/src/TextArea/TextArea.styles.old.tsx +213 -0
  115. package/src/TextArea/TextArea.styles.tsx +87 -187
  116. package/src/TextArea/TextArea.web.tsx +17 -6
  117. package/src/Tooltip/Tooltip.styles.old.tsx +82 -0
  118. package/src/Tooltip/Tooltip.styles.tsx +32 -56
  119. package/src/Video/Video.styles.old.tsx +51 -0
  120. package/src/Video/Video.styles.tsx +32 -44
  121. package/src/View/View.native.tsx +41 -13
  122. package/src/View/View.styles.old.tsx +125 -0
  123. package/src/View/View.styles.tsx +76 -106
  124. package/src/View/View.web.tsx +5 -21
  125. package/src/View/types.ts +31 -3
  126. package/src/examples/ButtonExamples.tsx +20 -0
  127. package/src/examples/CardExamples.tsx +0 -6
  128. package/src/extensions/extendComponent.ts +61 -0
  129. package/src/index.ts +1 -1
@@ -59,19 +59,24 @@ const ListItem = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pres
59
59
  disabled,
60
60
  });
61
61
 
62
- // Get dynamic item style with type, disabled, and clickable props
62
+ // Get dynamic styles - call as functions to get theme-reactive styles
63
63
  const itemStyle = (listStyles.item as any)({ type: effectiveVariant, disabled, clickable: isClickable });
64
+ const labelStyle = (listStyles.label as any)({ disabled, selected });
65
+ const leadingStyle = (listStyles.leading as any)({});
66
+ const trailingStyle = (listStyles.trailing as any)({});
67
+ const trailingIconStyle = (listStyles.trailingIcon as any)({});
68
+ const labelContainerStyle = (listStyles.labelContainer as any)({});
64
69
 
65
70
  // Resolve icon color - check intents first, then color palette
66
- const resolvedIconColor = (() => {
67
- if (!iconColor) return undefined;
71
+ const resolvedIconColor = useMemo(() => {
72
+ if (!iconColor) return trailingIconStyle.color || leadingStyle.color;
68
73
  // Check if it's an intent name
69
74
  if (iconColor in theme.intents) {
70
75
  return theme.intents[iconColor as Intent]?.primary;
71
76
  }
72
77
  // Otherwise try color palette
73
78
  return getColorFromString(theme, iconColor as Color);
74
- })();
79
+ }, [iconColor, theme, trailingIconStyle.color, leadingStyle.color]);
75
80
 
76
81
  // Helper to render leading/trailing icons
77
82
  const renderElement = (element: typeof leading | typeof trailing, styleKey: 'leading' | 'trailingIcon') => {
@@ -79,12 +84,12 @@ const ListItem = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pres
79
84
 
80
85
  // If it's a string, treat it as an icon name
81
86
  if (typeof element === 'string') {
82
- const iconStyle = styleKey === 'leading' ? listStyles.leading : listStyles.trailingIcon;
87
+ const iconSize = styleKey === 'leading' ? leadingStyle.width : trailingIconStyle.width;
83
88
  return (
84
89
  <MaterialCommunityIcons
85
90
  name={element}
86
- size={iconStyle.width}
87
- color={resolvedIconColor ?? iconStyle.color}
91
+ size={iconSize}
92
+ color={resolvedIconColor}
88
93
  />
89
94
  );
90
95
  } else if (isValidElement(element)) {
@@ -98,20 +103,20 @@ const ListItem = forwardRef<ComponentRef<typeof View> | ComponentRef<typeof Pres
98
103
  const content = (
99
104
  <>
100
105
  {leading && (
101
- <View style={listStyles.leading}>
106
+ <View style={leadingStyle}>
102
107
  {renderElement(leading, 'leading')}
103
108
  </View>
104
109
  )}
105
110
 
106
- <View style={listStyles.labelContainer}>
111
+ <View style={labelContainerStyle}>
107
112
  {label && (
108
- <Text style={listStyles.label}>{label}</Text>
113
+ <Text style={labelStyle}>{label}</Text>
109
114
  )}
110
115
  {children}
111
116
  </View>
112
117
 
113
118
  {trailing && (
114
- <View style={listStyles.trailing}>
119
+ <View style={trailingStyle}>
115
120
  {renderElement(trailing, 'trailingIcon')}
116
121
  </View>
117
122
  )}
@@ -5,7 +5,7 @@ import { getColorFromString, Intent, Theme, Color } from '@idealyst/theme';
5
5
  import { listStyles } from './List.styles';
6
6
  import type { ListItemProps } from './types';
7
7
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
8
- import { resolveIconPath, isIconName } from '../Icon/icon-resolver';
8
+ import { isIconName } from '../Icon/icon-resolver';
9
9
  import { useListContext } from './ListContext';
10
10
 
11
11
  const ListItem: React.FC<ListItemProps> = ({
@@ -40,13 +40,19 @@ const ListItem: React.FC<ListItemProps> = ({
40
40
  disabled,
41
41
  });
42
42
 
43
- // Get dynamic item style with type, disabled, and clickable props
43
+ // Get dynamic styles - call as functions for theme reactivity
44
44
  const itemStyle = (listStyles.item as any)({ type: effectiveVariant, disabled, clickable: isClickable });
45
+ const labelStyle = (listStyles.label as any)({ disabled, selected });
46
+ const leadingStyle = (listStyles.leading as any)({});
47
+ const trailingStyle = (listStyles.trailing as any)({});
48
+ const trailingIconStyle = (listStyles.trailingIcon as any)({});
49
+ const labelContainerStyle = (listStyles.labelContainer as any)({});
50
+
45
51
  const itemProps = getWebProps([itemStyle, style]);
46
- const labelProps = getWebProps([listStyles.label]);
47
- const leadingProps = getWebProps([listStyles.leading]);
48
- const trailingProps = getWebProps([listStyles.trailing]);
49
- const trailingIconProps = getWebProps([listStyles.trailing, listStyles.trailingIcon]);
52
+ const labelProps = getWebProps([labelStyle]);
53
+ const leadingProps = getWebProps([leadingStyle]);
54
+ const trailingProps = getWebProps([trailingStyle]);
55
+ const trailingIconProps = getWebProps([trailingStyle, trailingIconStyle]);
50
56
 
51
57
  const handleClick = () => {
52
58
  if (!disabled && onPress) {
@@ -66,18 +72,16 @@ const ListItem: React.FC<ListItemProps> = ({
66
72
  })();
67
73
 
68
74
  // Helper to render leading/trailing icons
69
- const renderElement = (element: typeof leading | typeof trailing, props: any, isTrailing = false) => {
75
+ // IconSvg uses size="1em" by default, sized by container's fontSize from styles
76
+ const renderElement = (element: typeof leading | typeof trailing, isTrailing = false) => {
70
77
  if (!element) return null;
71
78
 
72
79
  if (isIconName(element)) {
73
- const iconPath = resolveIconPath(element);
74
- // Use trailingIconProps for trailing icons to apply size constraints
75
- const iconPropsToUse = isTrailing ? trailingIconProps : props;
80
+ // Use IconSvg with name - registry lookup happens inside
76
81
  return (
77
82
  <IconSvg
78
- path={iconPath}
79
- {...iconPropsToUse}
80
- color={resolvedIconColor}
83
+ name={element}
84
+ color={resolvedIconColor || 'currentColor'}
81
85
  aria-label={element}
82
86
  />
83
87
  );
@@ -88,11 +92,17 @@ const ListItem: React.FC<ListItemProps> = ({
88
92
  return null;
89
93
  };
90
94
 
95
+ const labelContainerProps = getWebProps([labelContainerStyle]);
96
+
91
97
  const content = (
92
98
  <>
93
- {leading && renderElement(leading, leadingProps)}
99
+ {leading && (
100
+ <span {...leadingProps}>
101
+ {renderElement(leading)}
102
+ </span>
103
+ )}
94
104
 
95
- <div {...getWebProps([listStyles.labelContainer])}>
105
+ <div {...labelContainerProps}>
96
106
  {label && (
97
107
  <span {...labelProps}>{label}</span>
98
108
  )}
@@ -101,7 +111,7 @@ const ListItem: React.FC<ListItemProps> = ({
101
111
 
102
112
  {trailing && (
103
113
  <div {...trailingProps}>
104
- {renderElement(trailing, trailingIconProps, true)}
114
+ {renderElement(trailing, true)}
105
115
  </div>
106
116
  )}
107
117
  </>
@@ -0,0 +1,197 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, StylesheetStyles, CompoundVariants, Intent, Size } from '@idealyst/theme';
3
+ import { buildSizeVariants } from '../utils/buildSizeVariants';
4
+ import { applyExtensions } from '../extensions/applyExtension';
5
+
6
+ type MenuSize = Size;
7
+ type MenuIntent = Intent;
8
+
9
+ type MenuVariants = {
10
+ size: MenuSize;
11
+ intent: MenuIntent;
12
+ disabled: boolean;
13
+ }
14
+
15
+ export type ExpandedMenuStyles = StylesheetStyles<keyof MenuVariants>;
16
+
17
+ export type MenuStylesheet = {
18
+ overlay: ExpandedMenuStyles;
19
+ menu: ExpandedMenuStyles;
20
+ separator: ExpandedMenuStyles;
21
+ item: ExpandedMenuStyles;
22
+ icon: ExpandedMenuStyles;
23
+ label: ExpandedMenuStyles;
24
+ }
25
+
26
+ /**
27
+ * Create size variants for menu item
28
+ */
29
+ function createItemSizeVariants(theme: Theme) {
30
+ return buildSizeVariants(theme, 'menu', (size) => ({
31
+ paddingVertical: size.paddingVertical,
32
+ paddingHorizontal: size.paddingHorizontal,
33
+ }));
34
+ }
35
+
36
+ /**
37
+ * Get hover styles for menu item based on intent
38
+ */
39
+ function getItemHoverStyles(theme: Theme, intent: MenuIntent) {
40
+ if (intent === 'neutral') {
41
+ return {};
42
+ }
43
+ const intentValue = theme.intents[intent];
44
+ return {
45
+ _web: {
46
+ _hover: {
47
+ backgroundColor: intentValue.light + '20',
48
+ color: intentValue.primary,
49
+ },
50
+ },
51
+ } as const;
52
+ }
53
+
54
+ /**
55
+ * Create compound variants for menu item
56
+ */
57
+ function createItemCompoundVariants(theme: Theme): CompoundVariants<keyof MenuVariants> {
58
+ return [
59
+ {
60
+ disabled: true,
61
+ styles: {
62
+ _web: {
63
+ _hover: {
64
+ backgroundColor: 'transparent',
65
+ },
66
+ },
67
+ },
68
+ },
69
+ ] as const;
70
+ }
71
+
72
+ /**
73
+ * Create size variants for icon
74
+ */
75
+ function createIconSizeVariants(theme: Theme) {
76
+ return buildSizeVariants(theme, 'menu', (size) => ({
77
+ width: size.iconSize,
78
+ height: size.iconSize,
79
+ fontSize: size.iconSize,
80
+ }));
81
+ }
82
+
83
+ /**
84
+ * Create size variants for label
85
+ */
86
+ function createLabelSizeVariants(theme: Theme) {
87
+ return buildSizeVariants(theme, 'menu', (size) => ({
88
+ fontSize: size.labelFontSize,
89
+ }));
90
+ }
91
+
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');
132
+ return {
133
+ flexDirection: 'row',
134
+ alignItems: 'center',
135
+ backgroundColor: 'transparent',
136
+ borderRadius: 4,
137
+ minHeight: 44,
138
+ variants: {
139
+ size: createItemSizeVariants(theme),
140
+ disabled: {
141
+ true: {
142
+ opacity: 0.5,
143
+ _web: {
144
+ cursor: 'not-allowed',
145
+ },
146
+ },
147
+ false: {},
148
+ },
149
+ },
150
+ compoundVariants: createItemCompoundVariants(theme),
151
+ _web: {
152
+ cursor: 'pointer',
153
+ border: 'none',
154
+ outline: 'none',
155
+ transition: 'background-color 0.2s ease',
156
+ textAlign: 'left',
157
+ _hover: {
158
+ backgroundColor: theme.colors.surface.secondary,
159
+ },
160
+ },
161
+ ...hoverStyles,
162
+ } as const;
163
+ };
164
+ }
165
+
166
+ // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
167
+ export const menuStyles = StyleSheet.create((theme: Theme) => {
168
+ // Apply extensions to main visual elements
169
+
170
+ return applyExtensions('Menu', theme, {overlay: createOverlayStyles(theme),
171
+ menu: createMenuStyles(theme),
172
+ item: createItemStyles(theme),
173
+ // Additional styles (merged from return)
174
+ // Minor utility styles (not extended)
175
+ separator: {
176
+ height: 1,
177
+ backgroundColor: theme.colors.border.primary,
178
+ marginTop: 4,
179
+ marginBottom: 4,
180
+ },
181
+ icon: {
182
+ alignItems: 'center',
183
+ justifyContent: 'center',
184
+ flexShrink: 0,
185
+ marginRight: 8,
186
+ variants: {
187
+ size: createIconSizeVariants(theme),
188
+ } as const,
189
+ } as const,
190
+ label: {
191
+ flex: 1,
192
+ color: theme.colors.text.primary,
193
+ variants: {
194
+ size: createLabelSizeVariants(theme),
195
+ } as const,
196
+ } as const});
197
+ });
@@ -1,98 +1,28 @@
1
- import { StyleSheet } from 'react-native-unistyles';
2
- import { Theme, StylesheetStyles, CompoundVariants, Intent, Size } from '@idealyst/theme';
3
- import { buildSizeVariants } from '../utils/buildSizeVariants';
4
- import { applyExtensions } from '../extensions/applyExtension';
5
-
6
- type MenuSize = Size;
7
- type MenuIntent = Intent;
8
-
9
- type MenuVariants = {
10
- size: MenuSize;
11
- intent: MenuIntent;
12
- disabled: boolean;
13
- }
14
-
15
- export type ExpandedMenuStyles = StylesheetStyles<keyof MenuVariants>;
16
-
17
- export type MenuStylesheet = {
18
- overlay: ExpandedMenuStyles;
19
- menu: ExpandedMenuStyles;
20
- separator: ExpandedMenuStyles;
21
- item: ExpandedMenuStyles;
22
- icon: ExpandedMenuStyles;
23
- label: ExpandedMenuStyles;
24
- }
25
-
26
1
  /**
27
- * Create size variants for menu item
2
+ * Menu styles using defineStyle with dynamic props.
28
3
  */
29
- function createItemSizeVariants(theme: Theme) {
30
- return buildSizeVariants(theme, 'menu', (size) => ({
31
- paddingVertical: size.paddingVertical,
32
- paddingHorizontal: size.paddingHorizontal,
33
- }));
34
- }
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
+ import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
35
7
 
36
- /**
37
- * Get hover styles for menu item based on intent
38
- */
39
- function getItemHoverStyles(theme: Theme, intent: MenuIntent) {
40
- if (intent === 'neutral') {
41
- return {};
42
- }
43
- const intentValue = theme.intents[intent];
44
- return {
45
- _web: {
46
- _hover: {
47
- backgroundColor: intentValue.light + '20',
48
- color: intentValue.primary,
49
- },
50
- },
51
- } as const;
52
- }
8
+ // Required: Unistyles must see StyleSheet usage in original source to process this file
9
+ void StyleSheet;
53
10
 
54
- /**
55
- * Create compound variants for menu item
56
- */
57
- function createItemCompoundVariants(theme: Theme): CompoundVariants<keyof MenuVariants> {
58
- return [
59
- {
60
- disabled: true,
61
- styles: {
62
- _web: {
63
- _hover: {
64
- backgroundColor: 'transparent',
65
- },
66
- },
67
- },
68
- },
69
- ] as const;
70
- }
11
+ // Wrap theme for $iterator support
12
+ type Theme = ThemeStyleWrapper<BaseTheme>;
71
13
 
72
- /**
73
- * Create size variants for icon
74
- */
75
- function createIconSizeVariants(theme: Theme) {
76
- return buildSizeVariants(theme, 'menu', (size) => ({
77
- width: size.iconSize,
78
- height: size.iconSize,
79
- fontSize: size.iconSize,
80
- }));
81
- }
14
+ export type MenuDynamicProps = {
15
+ size?: Size;
16
+ intent?: Intent;
17
+ disabled?: boolean;
18
+ };
82
19
 
83
20
  /**
84
- * Create size variants for label
21
+ * Menu styles with intent/disabled handling.
85
22
  */
86
- function createLabelSizeVariants(theme: Theme) {
87
- return buildSizeVariants(theme, 'menu', (size) => ({
88
- fontSize: size.labelFontSize,
89
- }));
90
- }
91
-
92
- // Main element style creators (for extension support)
93
- function createOverlayStyles(theme: Theme) {
94
- return () => ({
95
- backgroundColor: 'transparent',
23
+ export const menuStyles = defineStyle('Menu', (theme: Theme) => ({
24
+ overlay: (_props: MenuDynamicProps) => ({
25
+ backgroundColor: 'transparent' as const,
96
26
  _web: {
97
27
  position: 'fixed' as const,
98
28
  top: 0,
@@ -101,11 +31,9 @@ function createOverlayStyles(theme: Theme) {
101
31
  bottom: 0,
102
32
  zIndex: 999,
103
33
  },
104
- });
105
- }
34
+ }),
106
35
 
107
- function createMenuStyles(theme: Theme) {
108
- return () => ({
36
+ menu: (_props: MenuDynamicProps) => ({
109
37
  position: 'absolute' as const,
110
38
  zIndex: 1000,
111
39
  backgroundColor: theme.colors.surface.primary,
@@ -123,79 +51,69 @@ function createMenuStyles(theme: Theme) {
123
51
  boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
124
52
  width: 'fit-content',
125
53
  },
126
- });
127
- }
54
+ }),
55
+
56
+ item: ({ intent = 'neutral', disabled = false }: MenuDynamicProps) => {
57
+ const intentValue = theme.intents[intent];
58
+ const hoverStyles = intent !== 'neutral' ? {
59
+ backgroundColor: intentValue.light + '20',
60
+ color: intentValue.primary,
61
+ } : {
62
+ backgroundColor: theme.colors.surface.secondary,
63
+ };
128
64
 
129
- function createItemStyles(theme: Theme) {
130
- return ({ intent }: Partial<MenuVariants>) => {
131
- const hoverStyles = getItemHoverStyles(theme, intent ?? 'neutral');
132
65
  return {
133
- flexDirection: 'row',
134
- alignItems: 'center',
135
- backgroundColor: 'transparent',
66
+ flexDirection: 'row' as const,
67
+ alignItems: 'center' as const,
68
+ backgroundColor: 'transparent' as const,
136
69
  borderRadius: 4,
137
70
  minHeight: 44,
71
+ opacity: disabled ? 0.5 : 1,
138
72
  variants: {
139
- size: createItemSizeVariants(theme),
140
- disabled: {
141
- true: {
142
- opacity: 0.5,
143
- _web: {
144
- cursor: 'not-allowed',
145
- },
146
- },
147
- false: {},
73
+ size: {
74
+ paddingVertical: theme.sizes.$menu.paddingVertical,
75
+ paddingHorizontal: theme.sizes.$menu.paddingHorizontal,
148
76
  },
149
77
  },
150
- compoundVariants: createItemCompoundVariants(theme),
151
78
  _web: {
152
- cursor: 'pointer',
79
+ cursor: disabled ? 'not-allowed' : 'pointer',
153
80
  border: 'none',
154
81
  outline: 'none',
155
82
  transition: 'background-color 0.2s ease',
156
83
  textAlign: 'left',
157
- _hover: {
158
- backgroundColor: theme.colors.surface.secondary,
159
- },
84
+ _hover: disabled ? { backgroundColor: 'transparent' } : hoverStyles,
160
85
  },
161
- ...hoverStyles,
162
86
  } as const;
163
- };
164
- }
87
+ },
165
88
 
166
- // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
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
- });
89
+ separator: (_props: MenuDynamicProps) => ({
90
+ height: 1,
91
+ backgroundColor: theme.colors.border.primary,
92
+ marginTop: 4,
93
+ marginBottom: 4,
94
+ }),
174
95
 
175
- return {
176
- ...extended,
177
- // Minor utility styles (not extended)
178
- separator: {
179
- height: 1,
180
- backgroundColor: theme.colors.border.primary,
181
- marginTop: 4,
182
- marginBottom: 4,
96
+ icon: (_props: MenuDynamicProps) => ({
97
+ alignItems: 'center' as const,
98
+ justifyContent: 'center' as const,
99
+ flexShrink: 0,
100
+ marginRight: 8,
101
+ variants: {
102
+ size: {
103
+ width: theme.sizes.$menu.iconSize,
104
+ height: theme.sizes.$menu.iconSize,
105
+ fontSize: theme.sizes.$menu.iconSize,
106
+ },
183
107
  },
184
- icon: {
185
- alignItems: 'center',
186
- justifyContent: 'center',
187
- flexShrink: 0,
188
- marginRight: 8,
189
- variants: {
190
- size: createIconSizeVariants(theme),
191
- } as const,
192
- } as const,
193
- label: {
194
- flex: 1,
195
- color: theme.colors.text.primary,
196
- variants: {
197
- size: createLabelSizeVariants(theme),
198
- } as const,
199
- } as const,
200
- };
201
- });
108
+ }),
109
+
110
+ label: (_props: MenuDynamicProps) => ({
111
+ flex: 1,
112
+ color: theme.colors.text.primary,
113
+ variants: {
114
+ size: {
115
+ fontSize: theme.sizes.$menu.labelFontSize,
116
+ },
117
+ },
118
+ }),
119
+ }));
@@ -19,8 +19,10 @@ const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps>(({ it
19
19
  disabled: Boolean(item.disabled),
20
20
  });
21
21
 
22
- // Compute dynamic item style with intent
22
+ // Call styles as functions to get theme-reactive styles
23
23
  const itemStyle = (menuItemStyles.item as any)({ intent: item.intent || 'neutral' });
24
+ const iconStyle = (menuItemStyles.icon as any)({});
25
+ const labelStyle = (menuItemStyles.label as any)({});
24
26
 
25
27
  const renderIcon = () => {
26
28
  if (!item.icon) return null;
@@ -29,7 +31,7 @@ const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps>(({ it
29
31
  return (
30
32
  <MaterialDesignIcons
31
33
  name={item.icon as any}
32
- style={menuItemStyles.icon}
34
+ style={iconStyle}
33
35
  />
34
36
  );
35
37
  } else if (isValidElement(item.icon)) {
@@ -56,7 +58,7 @@ const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps>(({ it
56
58
  {renderIcon()}
57
59
  </View>
58
60
  )}
59
- <Text style={menuItemStyles.label}>
61
+ <Text style={labelStyle}>
60
62
  {item.label}
61
63
  </Text>
62
64
  </Pressable>