@idealyst/components 1.2.29 → 1.2.31

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 (143) hide show
  1. package/README.md +3 -3
  2. package/package.json +4 -4
  3. package/plugin/__tests__/web.test.ts +2 -2
  4. package/plugin/web.js +2 -0
  5. package/src/Accordion/Accordion.native.tsx +3 -2
  6. package/src/ActivityIndicator/ActivityIndicator.native.tsx +4 -2
  7. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +22 -27
  8. package/src/ActivityIndicator/ActivityIndicator.web.tsx +17 -29
  9. package/src/Alert/Alert.native.tsx +20 -10
  10. package/src/Alert/Alert.styles.tsx +173 -86
  11. package/src/Alert/Alert.web.tsx +34 -30
  12. package/src/Alert/types.ts +53 -3
  13. package/src/Avatar/Avatar.native.tsx +3 -2
  14. package/src/Avatar/Avatar.web.tsx +2 -1
  15. package/src/Avatar/types.ts +1 -1
  16. package/src/Badge/Badge.native.tsx +18 -6
  17. package/src/Badge/Badge.styles.tsx +22 -5
  18. package/src/Badge/Badge.web.tsx +12 -4
  19. package/src/Badge/types.ts +14 -2
  20. package/src/Breadcrumb/Breadcrumb.native.tsx +3 -2
  21. package/src/Button/Button.native.tsx +16 -6
  22. package/src/Button/Button.styles.tsx +2 -2
  23. package/src/Button/Button.web.tsx +19 -15
  24. package/src/Button/types.ts +6 -10
  25. package/src/Card/Card.native.tsx +27 -3
  26. package/src/Card/Card.web.tsx +30 -4
  27. package/src/Card/types.ts +15 -0
  28. package/src/Checkbox/Checkbox.native.tsx +5 -4
  29. package/src/Checkbox/Checkbox.styles.tsx +62 -52
  30. package/src/Checkbox/Checkbox.web.tsx +4 -3
  31. package/src/Checkbox/types.ts +1 -1
  32. package/src/Chip/Chip.native.tsx +30 -7
  33. package/src/Chip/Chip.web.tsx +28 -5
  34. package/src/Chip/types.ts +15 -0
  35. package/src/Dialog/Dialog.native.tsx +6 -6
  36. package/src/Dialog/Dialog.web.tsx +5 -5
  37. package/src/Dialog/types.ts +2 -2
  38. package/src/Divider/Divider.native.tsx +20 -17
  39. package/src/Divider/Divider.styles.tsx +51 -29
  40. package/src/Divider/Divider.web.tsx +5 -4
  41. package/src/Divider/types.ts +3 -3
  42. package/src/Icon/Icon.native.tsx +3 -2
  43. package/src/Icon/Icon.web.tsx +2 -1
  44. package/src/Icon/IconSvg/IconSvg.native.tsx +3 -2
  45. package/src/IconButton/IconButton.native.tsx +219 -0
  46. package/src/IconButton/IconButton.styles.tsx +127 -0
  47. package/src/IconButton/IconButton.web.tsx +198 -0
  48. package/src/IconButton/index.native.ts +5 -0
  49. package/src/IconButton/index.ts +5 -0
  50. package/src/IconButton/index.web.ts +5 -0
  51. package/src/IconButton/types.ts +84 -0
  52. package/src/Image/Image.native.tsx +3 -2
  53. package/src/Input/Input.native.tsx +42 -290
  54. package/src/Input/Input.styles.tsx +1 -1
  55. package/src/Input/Input.web.tsx +37 -288
  56. package/src/Input/index.native.ts +9 -2
  57. package/src/Input/index.ts +8 -1
  58. package/src/Input/index.web.ts +8 -1
  59. package/src/Input/types.ts +1 -1
  60. package/src/List/List.native.tsx +3 -2
  61. package/src/List/ListItem.native.tsx +3 -2
  62. package/src/List/ListSection.native.tsx +3 -2
  63. package/src/Menu/Menu.native.tsx +2 -1
  64. package/src/Menu/Menu.styles.tsx +79 -29
  65. package/src/Menu/Menu.web.tsx +2 -1
  66. package/src/Menu/MenuItem.native.tsx +4 -3
  67. package/src/Menu/MenuItem.styles.tsx +81 -32
  68. package/src/Menu/MenuItem.web.tsx +2 -1
  69. package/src/Menu/docs.ts +1 -1
  70. package/src/Popover/Popover.native.tsx +2 -1
  71. package/src/Popover/Popover.web.tsx +2 -1
  72. package/src/Popover/types.ts +15 -4
  73. package/src/Pressable/Pressable.native.tsx +3 -2
  74. package/src/Pressable/Pressable.web.tsx +3 -5
  75. package/src/Progress/Progress.native.tsx +5 -4
  76. package/src/Progress/Progress.web.tsx +3 -3
  77. package/src/Progress/types.ts +3 -3
  78. package/src/RadioButton/RadioButton.native.tsx +4 -3
  79. package/src/RadioButton/RadioButton.styles.tsx +53 -33
  80. package/src/RadioButton/RadioGroup.native.tsx +3 -2
  81. package/src/SVGImage/SVGImage.native.tsx +5 -4
  82. package/src/SVGImage/SVGImage.styles.tsx +44 -10
  83. package/src/SVGImage/SVGImage.web.tsx +2 -1
  84. package/src/Screen/Screen.native.tsx +2 -1
  85. package/src/Screen/Screen.web.tsx +2 -1
  86. package/src/Select/Select.native.tsx +6 -5
  87. package/src/Select/Select.styles.tsx +1 -1
  88. package/src/Select/Select.web.tsx +4 -3
  89. package/src/Select/types.ts +1 -1
  90. package/src/Skeleton/Skeleton.native.tsx +2 -1
  91. package/src/Skeleton/Skeleton.web.tsx +1 -1
  92. package/src/Slider/Slider.native.tsx +9 -8
  93. package/src/Slider/Slider.web.tsx +10 -9
  94. package/src/Slider/types.ts +9 -2
  95. package/src/Switch/Switch.native.tsx +7 -6
  96. package/src/Switch/Switch.styles.tsx +52 -17
  97. package/src/Switch/Switch.web.tsx +15 -16
  98. package/src/Switch/types.ts +44 -4
  99. package/src/TabBar/TabBar.native.tsx +3 -2
  100. package/src/Text/Text.native.tsx +3 -2
  101. package/src/Text/Text.web.tsx +2 -1
  102. package/src/TextArea/TextArea.native.tsx +3 -2
  103. package/src/TextArea/TextArea.styles.tsx +2 -2
  104. package/src/TextArea/TextArea.web.tsx +2 -1
  105. package/src/TextInput/TextInput.native.tsx +300 -0
  106. package/src/TextInput/TextInput.styles.tsx +207 -0
  107. package/src/TextInput/TextInput.web.tsx +301 -0
  108. package/src/TextInput/index.native.ts +3 -0
  109. package/src/TextInput/index.ts +5 -0
  110. package/src/TextInput/index.web.ts +5 -0
  111. package/src/TextInput/types.ts +163 -0
  112. package/src/Tooltip/Tooltip.native.tsx +3 -2
  113. package/src/Video/Video.native.tsx +4 -3
  114. package/src/View/View.native.tsx +2 -1
  115. package/src/View/View.styles.tsx +1 -0
  116. package/src/View/View.web.tsx +9 -2
  117. package/src/examples/ActivityIndicatorExamples.tsx +177 -0
  118. package/src/examples/AlertExamples.tsx +5 -5
  119. package/src/examples/ButtonExamples.tsx +12 -12
  120. package/src/examples/CardExamples.tsx +1 -1
  121. package/src/examples/CheckboxExamples.tsx +2 -2
  122. package/src/examples/ChipExamples.tsx +6 -6
  123. package/src/examples/DialogExamples.tsx +1 -1
  124. package/src/examples/DividerExamples.tsx +1 -1
  125. package/src/examples/InputExamples.tsx +1 -1
  126. package/src/examples/LinkExamples.tsx +1 -1
  127. package/src/examples/ListExamples.tsx +1 -1
  128. package/src/examples/MenuExamples.tsx +2 -2
  129. package/src/examples/ProgressExamples.tsx +1 -1
  130. package/src/examples/RadioButtonExamples.tsx +5 -5
  131. package/src/examples/SVGImageExamples.tsx +1 -1
  132. package/src/examples/SelectExamples.tsx +1 -1
  133. package/src/examples/SliderExamples.tsx +5 -5
  134. package/src/examples/SwitchExamples.tsx +26 -26
  135. package/src/examples/TableExamples.tsx +1 -1
  136. package/src/examples/TooltipExamples.tsx +2 -2
  137. package/src/examples/index.ts +1 -0
  138. package/src/extensions/index.ts +1 -0
  139. package/src/extensions/types.ts +22 -3
  140. package/src/index.native.ts +4 -0
  141. package/src/index.ts +27 -2
  142. package/src/utils/index.ts +12 -0
  143. package/src/utils/refTypes.ts +50 -0
package/README.md CHANGED
@@ -198,15 +198,15 @@ The library includes a comprehensive theme system with light and dark mode suppo
198
198
  All components use a consistent intent-based color system:
199
199
 
200
200
  - `primary`: Main brand actions
201
- - `neutral`: Secondary actions
201
+ - `neutral`: Secondary actions
202
202
  - `success`: Positive actions (save, confirm)
203
- - `error`: Destructive actions (delete, cancel)
203
+ - `danger`: Destructive actions (delete, cancel)
204
204
  - `warning`: Caution actions
205
205
 
206
206
  ```tsx
207
207
  <Button variant="contained" intent="primary">Primary Action</Button>
208
208
  <Button variant="contained" intent="success">Save</Button>
209
- <Button variant="contained" intent="error">Delete</Button>
209
+ <Button variant="contained" intent="danger">Delete</Button>
210
210
  ```
211
211
 
212
212
  ### Theme Integration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.2.29",
3
+ "version": "1.2.31",
4
4
  "description": "Shared component library for React and React Native",
5
5
  "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
6
6
  "readme": "README.md",
@@ -56,7 +56,7 @@
56
56
  "publish:npm": "npm publish"
57
57
  },
58
58
  "peerDependencies": {
59
- "@idealyst/theme": "^1.2.29",
59
+ "@idealyst/theme": "^1.2.31",
60
60
  "@mdi/js": ">=7.0.0",
61
61
  "@mdi/react": ">=1.0.0",
62
62
  "@react-native-vector-icons/common": ">=12.0.0",
@@ -106,8 +106,8 @@
106
106
  }
107
107
  },
108
108
  "devDependencies": {
109
- "@idealyst/theme": "^1.2.29",
110
- "@idealyst/tooling": "^1.2.4",
109
+ "@idealyst/theme": "^1.2.31",
110
+ "@idealyst/tooling": "^1.2.30",
111
111
  "@mdi/react": "^1.6.1",
112
112
  "@types/react": "^19.1.0",
113
113
  "react": "^19.1.0",
@@ -468,12 +468,12 @@ describe('MDI Icon Registry Babel Plugin', () => {
468
468
 
469
469
  it('does not detect icon-like strings in other props', () => {
470
470
  const code = `
471
- <Button title="home" aria-label="eye">Click me</Button>
471
+ <Button data-name="home" aria-label="eye">Click me</Button>
472
472
  `;
473
473
  const result = transform(code);
474
474
  const icons = getDetectedIcons(result?.code);
475
475
 
476
- // title and aria-label are not icon props
476
+ // data-name and aria-label are not icon props
477
477
  expect(icons).not.toContain('home');
478
478
  expect(icons).not.toContain('eye');
479
479
  });
package/plugin/web.js CHANGED
@@ -248,6 +248,8 @@ module.exports = function ({ types: t }, options = {}) {
248
248
  'Alert': ['icon'],
249
249
  'Chip': ['icon', 'deleteIcon'],
250
250
  'Input': ['leftIcon', 'rightIcon'],
251
+ 'TextInput': ['leftIcon', 'rightIcon'],
252
+ 'IconButton': ['icon'],
251
253
  };
252
254
 
253
255
  const iconProps = iconPropMap[componentName];
@@ -6,6 +6,7 @@ import { accordionStyles } from './Accordion.styles';
6
6
  import Text from '../Text';
7
7
  import type { AccordionProps, AccordionItem as AccordionItemType } from './types';
8
8
  import { getNativeAccessibilityProps } from '../utils/accessibility';
9
+ import type { IdealystElement } from '../utils/refTypes';
9
10
 
10
11
  interface AccordionItemProps {
11
12
  item: AccordionItemType;
@@ -135,7 +136,7 @@ const AccordionItem: React.FC<AccordionItemProps> = ({
135
136
  );
136
137
  };
137
138
 
138
- const Accordion = forwardRef<View, AccordionProps>(({
139
+ const Accordion = forwardRef<IdealystElement, AccordionProps>(({
139
140
  items,
140
141
  allowMultiple = false,
141
142
  defaultExpanded = [],
@@ -204,7 +205,7 @@ const Accordion = forwardRef<View, AccordionProps>(({
204
205
  const containerStyle = (accordionStyles.container as any)({});
205
206
 
206
207
  return (
207
- <View ref={ref} nativeID={id} style={[containerStyle, style]} testID={testID} {...nativeA11yProps}>
208
+ <View ref={ref as any} nativeID={id} style={[containerStyle, style]} testID={testID} {...nativeA11yProps}>
208
209
  {items.map((item, index) => (
209
210
  <AccordionItem
210
211
  key={item.id}
@@ -3,8 +3,9 @@ import { ActivityIndicator as RNActivityIndicator, View } from 'react-native';
3
3
  import { ActivityIndicatorProps } from './types';
4
4
  import { activityIndicatorStyles } from './ActivityIndicator.styles';
5
5
  import { getNativeLiveRegionAccessibilityProps } from '../utils/accessibility';
6
+ import type { IdealystElement } from '../utils/refTypes';
6
7
 
7
- const ActivityIndicator = forwardRef<View, ActivityIndicatorProps>(({
8
+ const ActivityIndicator = forwardRef<IdealystElement, ActivityIndicatorProps>(({
8
9
  animating = true,
9
10
  size = 'md',
10
11
  intent = 'primary',
@@ -36,6 +37,7 @@ const ActivityIndicator = forwardRef<View, ActivityIndicatorProps>(({
36
37
  activityIndicatorStyles.useVariants({
37
38
  size: sizeVariant,
38
39
  animating,
40
+ intent,
39
41
  });
40
42
 
41
43
  // Call dynamic style with intent variant
@@ -55,7 +57,7 @@ const ActivityIndicator = forwardRef<View, ActivityIndicatorProps>(({
55
57
  },
56
58
  style
57
59
  ]}
58
- ref={ref}
60
+ ref={ref as any}
59
61
  nativeID={id}
60
62
  testID={testID}
61
63
  {...nativeA11yProps}
@@ -22,10 +22,10 @@ export type ActivityIndicatorDynamicProps = {
22
22
  */
23
23
  export const activityIndicatorStyles = defineStyle('ActivityIndicator', (theme: Theme) => ({
24
24
  container: (_props: ActivityIndicatorDynamicProps) => ({
25
+ display: 'flex' as const,
25
26
  alignItems: 'center' as const,
26
27
  justifyContent: 'center' as const,
27
28
  variants: {
28
- // $iterator expands for each activityIndicator size
29
29
  size: {
30
30
  width: theme.sizes.$activityIndicator.size,
31
31
  height: theme.sizes.$activityIndicator.size,
@@ -37,32 +37,27 @@ export const activityIndicatorStyles = defineStyle('ActivityIndicator', (theme:
37
37
  },
38
38
  }),
39
39
 
40
- spinner: ({ intent = 'primary' }: ActivityIndicatorDynamicProps) => {
41
- // Inline color access for Unistyles to trace
42
- const color = theme.intents[intent].primary;
43
-
44
- return {
45
- borderRadius: 9999,
46
- borderStyle: 'solid' as const,
47
- color,
48
- variants: {
49
- size: {
50
- width: theme.sizes.$activityIndicator.size,
51
- height: theme.sizes.$activityIndicator.size,
52
- borderWidth: theme.sizes.$activityIndicator.borderWidth,
53
- },
54
- animating: {
55
- true: {},
56
- false: {},
57
- },
40
+ spinner: (_props: ActivityIndicatorDynamicProps) => ({
41
+ borderRadius: 9999,
42
+ borderStyle: 'solid' as const,
43
+ variants: {
44
+ size: {
45
+ width: theme.sizes.$activityIndicator.size,
46
+ height: theme.sizes.$activityIndicator.size,
47
+ borderWidth: theme.sizes.$activityIndicator.borderWidth,
58
48
  },
59
- _web: {
60
- borderColor: 'transparent',
61
- borderTopColor: color,
62
- borderRightColor: color,
63
- animation: 'spin 1s linear infinite',
64
- boxSizing: 'border-box',
49
+ intent: {
50
+ color: theme.$intents.primary,
51
+ _web: {
52
+ borderColor: 'transparent',
53
+ borderTopColor: theme.$intents.primary,
54
+ borderRightColor: theme.$intents.primary,
55
+ },
65
56
  },
66
- } as const;
67
- },
57
+ },
58
+ _web: {
59
+ animation: 'spin 1s linear infinite',
60
+ boxSizing: 'border-box',
61
+ },
62
+ }),
68
63
  }));
@@ -4,12 +4,13 @@ import { ActivityIndicatorProps } from './types';
4
4
  import { activityIndicatorStyles } from './ActivityIndicator.styles';
5
5
  import useMergeRefs from '../hooks/useMergeRefs';
6
6
  import { getWebLiveRegionAriaProps } from '../utils/accessibility';
7
+ import type { IdealystElement } from '../utils/refTypes';
7
8
 
8
9
  /**
9
10
  * Spinning loading indicator for async operations and content loading.
10
11
  * Supports intent-based coloring and automatic hiding when stopped.
11
12
  */
12
- const ActivityIndicator = forwardRef<HTMLDivElement, ActivityIndicatorProps>(({
13
+ const ActivityIndicator = forwardRef<IdealystElement, ActivityIndicatorProps>(({
13
14
  animating = true,
14
15
  size = 'md',
15
16
  intent = 'primary',
@@ -41,8 +42,9 @@ const ActivityIndicator = forwardRef<HTMLDivElement, ActivityIndicatorProps>(({
41
42
 
42
43
  // Apply variants using the correct Unistyles 3.0 pattern
43
44
  activityIndicatorStyles.useVariants({
44
- size: sizeVariant,
45
+ size: size,
45
46
  animating,
47
+ intent,
46
48
  });
47
49
 
48
50
  // Don't render if not animating and hidesWhenStopped is true
@@ -50,35 +52,21 @@ const ActivityIndicator = forwardRef<HTMLDivElement, ActivityIndicatorProps>(({
50
52
  return null;
51
53
  }
52
54
 
53
- // Create the style array following the official documentation pattern
54
- const containerStyleArray = [
55
- (activityIndicatorStyles.container as any)({}),
56
- customSize && {
57
- width: customSize,
58
- height: customSize,
59
- },
60
- style,
61
- ];
55
+ // Dynamic props for style functions
56
+ const dynamicProps = { size: sizeVariant, animating, intent };
62
57
 
63
- const spinnerStyleArray = [
64
- (activityIndicatorStyles.spinner as any)({
65
- intent,
66
- }),
67
- customSize ? {
68
- width: customSize,
69
- height: customSize,
70
- borderWidth: Math.max(2, customSize / 10),
71
- } : {},
72
- color ? { borderTopColor: color, borderRightColor: color } : {},
73
- // Add inline CSS animation
74
- {
75
- animation: animating ? 'spin 1s linear infinite' : undefined,
76
- },
77
- ];
58
+ // Use getWebProps - same pattern as Alert
59
+ const containerProps = getWebProps([
60
+ (activityIndicatorStyles.container as any)(dynamicProps),
61
+ customSize && { width: customSize, height: customSize },
62
+ style,
63
+ ]);
78
64
 
79
- // Use getWebProps to generate className and ref for web
80
- const containerProps = getWebProps(containerStyleArray);
81
- const spinnerProps = getWebProps(spinnerStyleArray);
65
+ const spinnerProps = getWebProps([
66
+ (activityIndicatorStyles.spinner as any)(dynamicProps),
67
+ customSize && { width: customSize, height: customSize, borderWidth: Math.max(2, customSize / 10) },
68
+ color && { borderTopColor: color, borderRightColor: color },
69
+ ]);
82
70
 
83
71
  const mergedRef = useMergeRefs(ref, containerProps.ref);
84
72
 
@@ -1,9 +1,10 @@
1
- import { isValidElement, forwardRef, ComponentRef } from 'react';
1
+ import { isValidElement, forwardRef } from 'react';
2
2
  import { View, Text, TouchableOpacity } from 'react-native';
3
3
  import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons';
4
- import { alertStyles } from './Alert.styles';
4
+ import { alertStyles, alertSizeConfig } from './Alert.styles';
5
5
  import { isIconName } from '../Icon/icon-resolver';
6
6
  import type { AlertProps } from './types';
7
+ import type { IdealystElement } from '../utils/refTypes';
7
8
 
8
9
  // Default icon names for each intent
9
10
  const defaultIcons: Record<string, string> = {
@@ -15,12 +16,13 @@ const defaultIcons: Record<string, string> = {
15
16
  neutral: 'circle',
16
17
  };
17
18
 
18
- const Alert = forwardRef<ComponentRef<typeof View>, AlertProps>(({
19
+ const Alert = forwardRef<IdealystElement, AlertProps>(({
19
20
  title,
20
21
  message,
21
22
  children,
22
23
  intent = 'neutral',
23
24
  type = 'soft',
25
+ size = 'md',
24
26
  icon,
25
27
  showIcon = true,
26
28
  dismissible = false,
@@ -30,17 +32,25 @@ const Alert = forwardRef<ComponentRef<typeof View>, AlertProps>(({
30
32
  testID,
31
33
  id,
32
34
  }, ref) => {
35
+ // Apply variants for size, intent, and type
36
+ alertStyles.useVariants({ size, intent, type });
37
+
33
38
  // Call all styles as functions to get theme-reactive styles
34
- const dynamicProps = { intent, type };
39
+ const dynamicProps = { intent, type, size };
35
40
  const containerStyle = (alertStyles.container as any)(dynamicProps);
36
41
  const iconContainerStyle = (alertStyles.iconContainer as any)(dynamicProps);
37
42
  const titleStyle = (alertStyles.title as any)(dynamicProps);
38
43
  const messageStyle = (alertStyles.message as any)(dynamicProps);
39
- const contentStyle = (alertStyles.content as any)({});
40
- const actionsStyle = (alertStyles.actions as any)({});
41
- const closeButtonStyle = (alertStyles.closeButton as any)({});
44
+ const contentStyle = (alertStyles.content as any)(dynamicProps);
45
+ const actionsStyle = (alertStyles.actions as any)(dynamicProps);
46
+ const closeButtonStyle = (alertStyles.closeButton as any)(dynamicProps);
42
47
  const closeIconStyle = (alertStyles.closeIcon as any)(dynamicProps);
43
48
 
49
+ // Get size-specific icon dimensions
50
+ const sizeConfig = alertSizeConfig[size];
51
+ const iconSize = sizeConfig.iconSize;
52
+ const closeIconSize = sizeConfig.closeIconSize;
53
+
44
54
  const displayIcon = icon !== undefined ? icon : (showIcon ? defaultIcons[intent] : null);
45
55
 
46
56
  // Helper to render icon
@@ -51,7 +61,7 @@ const Alert = forwardRef<ComponentRef<typeof View>, AlertProps>(({
51
61
  return (
52
62
  <MaterialDesignIcons
53
63
  name={displayIcon}
54
- size={20}
64
+ size={iconSize}
55
65
  style={iconContainerStyle}
56
66
  />
57
67
  );
@@ -63,7 +73,7 @@ const Alert = forwardRef<ComponentRef<typeof View>, AlertProps>(({
63
73
 
64
74
  return (
65
75
  <View
66
- ref={ref}
76
+ ref={ref as any}
67
77
  nativeID={id}
68
78
  style={[containerStyle, style]}
69
79
  testID={testID}
@@ -112,7 +122,7 @@ const Alert = forwardRef<ComponentRef<typeof View>, AlertProps>(({
112
122
  >
113
123
  <MaterialDesignIcons
114
124
  name="close"
115
- size={16}
125
+ size={closeIconSize}
116
126
  style={closeIconStyle}
117
127
  />
118
128
  </TouchableOpacity>
@@ -1,9 +1,12 @@
1
1
  /**
2
- * Alert styles using defineStyle with dynamic intent/type handling.
2
+ * Alert styles using defineStyle with variant expansion.
3
+ *
4
+ * Alert has compound logic between type+intent that's handled via compoundVariants.
5
+ * The $intents iterator in compoundVariants expands for all intent values.
3
6
  */
4
7
  import { StyleSheet } from 'react-native-unistyles';
5
8
  import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
- import type { Theme as BaseTheme, Intent } from '@idealyst/theme';
9
+ import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
7
10
 
8
11
  // Required: Unistyles must see StyleSheet usage in original source to process this file
9
12
  void StyleSheet;
@@ -16,98 +19,154 @@ type AlertType = 'filled' | 'outlined' | 'soft';
16
19
  export type AlertDynamicProps = {
17
20
  intent?: Intent;
18
21
  type?: AlertType;
22
+ size?: Size;
19
23
  };
20
24
 
21
25
  /**
22
- * Alert styles with intent/type combination handling.
26
+ * Alert styles with variant expansion for size/intent/type.
27
+ *
28
+ * The intent variant is expanded via $intents iterator.
29
+ * CompoundVariants handle type+intent combinations for colors.
23
30
  */
24
31
  export const alertStyles = defineStyle('Alert', (theme: Theme) => ({
25
- container: ({ intent = 'neutral', type = 'soft' }: AlertDynamicProps) => {
26
- const intentValue = theme.intents[intent];
27
-
28
- // Background color based on type
29
- const backgroundColor = type === 'filled'
30
- ? intentValue.primary
31
- : type === 'soft'
32
- ? intentValue.light
33
- : 'transparent';
34
-
35
- // Border color based on type
36
- const borderColor = (type === 'filled' || type === 'outlined')
37
- ? intentValue.primary
38
- : intentValue.light;
39
-
40
- return {
41
- display: 'flex' as const,
42
- flexDirection: 'row' as const,
43
- alignItems: 'flex-start' as const,
44
- gap: 8,
45
- padding: 16,
46
- borderRadius: 8,
47
- borderWidth: 1,
48
- borderStyle: 'solid' as const,
49
- backgroundColor,
50
- borderColor,
51
- } as const;
52
- },
53
-
54
- iconContainer: ({ intent = 'neutral', type = 'soft' }: AlertDynamicProps) => {
55
- const intentValue = theme.intents[intent];
56
- const color = type === 'filled' ? intentValue.contrast : intentValue.primary;
57
-
58
- return {
59
- display: 'flex' as const,
60
- alignItems: 'center' as const,
61
- justifyContent: 'center' as const,
62
- alignSelf: 'flex-start' as const,
63
- flexShrink: 0,
64
- width: 24,
65
- height: 24,
66
- marginTop: 2,
67
- color,
68
- } as const;
69
- },
70
-
71
- title: ({ intent = 'neutral', type = 'soft' }: AlertDynamicProps) => {
72
- const intentValue = theme.intents[intent];
73
- const color = type === 'filled' ? intentValue.contrast : intentValue.primary;
32
+ container: (_props: AlertDynamicProps) => ({
33
+ display: 'flex' as const,
34
+ flexDirection: 'row' as const,
35
+ alignItems: 'flex-start' as const,
36
+ borderWidth: 1,
37
+ borderStyle: 'solid' as const,
38
+ variants: {
39
+ size: {
40
+ gap: theme.sizes.$alert.gap,
41
+ padding: theme.sizes.$alert.padding,
42
+ borderRadius: theme.sizes.$alert.borderRadius,
43
+ },
44
+ // Intent variant - expands to primary, success, danger, etc.
45
+ intent: {
46
+ // Base styles per intent (overridden by type+intent compoundVariants)
47
+ backgroundColor: theme.$intents.primary,
48
+ borderColor: theme.$intents.primary,
49
+ },
50
+ type: {
51
+ filled: {},
52
+ outlined: {
53
+ backgroundColor: 'transparent',
54
+ },
55
+ soft: {},
56
+ },
57
+ },
58
+ compoundVariants: [
59
+ // filled: use intent primary for bg and border
60
+ { type: 'filled', styles: { backgroundColor: theme.$intents.primary, borderColor: theme.$intents.primary } },
61
+ // outlined: transparent bg, intent primary for border
62
+ { type: 'outlined', styles: { backgroundColor: 'transparent', borderColor: theme.$intents.primary } },
63
+ // soft: intent light for bg and border
64
+ { type: 'soft', styles: { backgroundColor: theme.$intents.light, borderColor: theme.$intents.light } },
65
+ ],
66
+ }),
74
67
 
75
- return {
76
- fontSize: 16,
77
- lineHeight: 24,
78
- fontWeight: '600' as const,
79
- color,
80
- } as const;
81
- },
68
+ iconContainer: (_props: AlertDynamicProps) => ({
69
+ display: 'flex' as const,
70
+ alignItems: 'center' as const,
71
+ justifyContent: 'center' as const,
72
+ alignSelf: 'flex-start' as const,
73
+ flexShrink: 0,
74
+ marginTop: 2,
75
+ variants: {
76
+ size: {
77
+ width: theme.sizes.$alert.iconSize,
78
+ height: theme.sizes.$alert.iconSize,
79
+ },
80
+ intent: {
81
+ color: theme.$intents.primary,
82
+ },
83
+ type: {
84
+ filled: {},
85
+ outlined: {},
86
+ soft: {},
87
+ },
88
+ },
89
+ compoundVariants: [
90
+ // filled: contrast color for icon
91
+ { type: 'filled', styles: { color: theme.$intents.contrast } },
92
+ // outlined/soft: primary color for icon
93
+ { type: 'outlined', styles: { color: theme.$intents.primary } },
94
+ { type: 'soft', styles: { color: theme.$intents.primary } },
95
+ ],
96
+ }),
82
97
 
83
- message: ({ intent = 'neutral', type = 'soft' }: AlertDynamicProps) => {
84
- const intentValue = theme.intents[intent];
85
- const color = type === 'filled' ? intentValue.contrast : theme.colors.text.primary;
98
+ title: (_props: AlertDynamicProps) => ({
99
+ fontWeight: '600' as const,
100
+ variants: {
101
+ size: {
102
+ fontSize: theme.sizes.$alert.titleFontSize,
103
+ lineHeight: theme.sizes.$alert.titleLineHeight,
104
+ },
105
+ intent: {
106
+ color: theme.$intents.primary,
107
+ },
108
+ type: {
109
+ filled: {},
110
+ outlined: {},
111
+ soft: {},
112
+ },
113
+ },
114
+ compoundVariants: [
115
+ // filled: contrast color for title
116
+ { type: 'filled', styles: { color: theme.$intents.contrast } },
117
+ // outlined/soft: primary color for title
118
+ { type: 'outlined', styles: { color: theme.$intents.primary } },
119
+ { type: 'soft', styles: { color: theme.$intents.primary } },
120
+ ],
121
+ }),
86
122
 
87
- return {
88
- fontSize: 14,
89
- lineHeight: 20,
90
- color,
91
- } as const;
92
- },
123
+ message: (_props: AlertDynamicProps) => ({
124
+ variants: {
125
+ size: {
126
+ fontSize: theme.sizes.$alert.messageFontSize,
127
+ lineHeight: theme.sizes.$alert.messageLineHeight,
128
+ },
129
+ intent: {
130
+ color: theme.colors.text.primary,
131
+ },
132
+ type: {
133
+ filled: {},
134
+ outlined: {},
135
+ soft: {},
136
+ },
137
+ },
138
+ compoundVariants: [
139
+ // filled: contrast color for message
140
+ { type: 'filled', styles: { color: theme.$intents.contrast } },
141
+ // outlined/soft: use default text color (set in intent variant)
142
+ { type: 'outlined', styles: { color: theme.colors.text.primary } },
143
+ { type: 'soft', styles: { color: theme.colors.text.primary } },
144
+ ],
145
+ }),
93
146
 
94
147
  content: (_props: AlertDynamicProps) => ({
95
148
  flex: 1,
96
149
  display: 'flex' as const,
97
150
  flexDirection: 'column' as const,
98
- gap: 4,
151
+ variants: {
152
+ size: {
153
+ gap: theme.sizes.$alert.gap,
154
+ },
155
+ },
99
156
  }),
100
157
 
101
158
  actions: (_props: AlertDynamicProps) => ({
102
- marginTop: 4,
103
159
  display: 'flex' as const,
104
160
  flexDirection: 'row' as const,
105
- gap: 8,
161
+ variants: {
162
+ size: {
163
+ marginTop: theme.sizes.$alert.gap,
164
+ gap: theme.sizes.$alert.gap,
165
+ },
166
+ },
106
167
  }),
107
168
 
108
169
  closeButton: (_props: AlertDynamicProps) => ({
109
- padding: 4,
110
- borderRadius: 4,
111
170
  display: 'flex' as const,
112
171
  alignItems: 'center' as const,
113
172
  justifyContent: 'center' as const,
@@ -126,19 +185,47 @@ export const alertStyles = defineStyle('Alert', (theme: Theme) => ({
126
185
  backgroundColor: 'rgba(0, 0, 0, 0.1)',
127
186
  },
128
187
  },
188
+ variants: {
189
+ size: {
190
+ padding: theme.sizes.$alert.padding,
191
+ borderRadius: theme.sizes.$alert.borderRadius,
192
+ },
193
+ },
129
194
  }),
130
195
 
131
- closeIcon: ({ intent = 'neutral', type = 'soft' }: AlertDynamicProps) => {
132
- const intentValue = theme.intents[intent];
133
- const color = type === 'filled' ? intentValue.contrast : intentValue.primary;
134
-
135
- return {
136
- display: 'flex' as const,
137
- alignItems: 'center' as const,
138
- justifyContent: 'center' as const,
139
- width: 16,
140
- height: 16,
141
- color,
142
- } as const;
143
- },
196
+ closeIcon: (_props: AlertDynamicProps) => ({
197
+ display: 'flex' as const,
198
+ alignItems: 'center' as const,
199
+ justifyContent: 'center' as const,
200
+ variants: {
201
+ size: {
202
+ width: theme.sizes.$alert.closeIconSize,
203
+ height: theme.sizes.$alert.closeIconSize,
204
+ },
205
+ intent: {
206
+ color: theme.$intents.primary,
207
+ },
208
+ type: {
209
+ filled: {},
210
+ outlined: {},
211
+ soft: {},
212
+ },
213
+ },
214
+ compoundVariants: [
215
+ // filled: contrast color for close icon
216
+ { type: 'filled', styles: { color: theme.$intents.contrast } },
217
+ // outlined/soft: primary color for close icon
218
+ { type: 'outlined', styles: { color: theme.$intents.primary } },
219
+ { type: 'soft', styles: { color: theme.$intents.primary } },
220
+ ],
221
+ }),
144
222
  }));
223
+
224
+ // Export theme sizes for use in components (for icon sizing in native)
225
+ export const alertSizeConfig = {
226
+ xs: { padding: 8, gap: 6, borderRadius: 4, titleFontSize: 12, titleLineHeight: 16, messageFontSize: 11, messageLineHeight: 14, iconSize: 16, closeIconSize: 12 },
227
+ sm: { padding: 12, gap: 8, borderRadius: 6, titleFontSize: 14, titleLineHeight: 20, messageFontSize: 12, messageLineHeight: 16, iconSize: 20, closeIconSize: 14 },
228
+ md: { padding: 16, gap: 10, borderRadius: 8, titleFontSize: 16, titleLineHeight: 24, messageFontSize: 14, messageLineHeight: 20, iconSize: 24, closeIconSize: 16 },
229
+ lg: { padding: 20, gap: 12, borderRadius: 10, titleFontSize: 18, titleLineHeight: 28, messageFontSize: 16, messageLineHeight: 24, iconSize: 28, closeIconSize: 18 },
230
+ xl: { padding: 24, gap: 14, borderRadius: 12, titleFontSize: 20, titleLineHeight: 32, messageFontSize: 18, messageLineHeight: 28, iconSize: 32, closeIconSize: 20 },
231
+ };