@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
@@ -1,31 +1,33 @@
1
- import { isValidElement, forwardRef } from 'react';
1
+ import { isValidElement, forwardRef, ElementType, useMemo } from 'react';
2
2
  import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { alertStyles } from './Alert.styles';
4
4
  import type { AlertProps } from './types';
5
5
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
6
  import { isIconName } from '../Icon/icon-resolver';
7
7
  import useMergeRefs from '../hooks/useMergeRefs';
8
+ import type { IdealystElement } from '../utils/refTypes';
8
9
 
9
10
  // Default icons for each intent
10
- const defaultIcons: Record<string, string> = {
11
- primary: 'information',
12
- success: 'check-circle',
13
- error: 'alert-circle',
14
- warning: 'alert',
15
- info: 'information',
16
- neutral: 'record-circle',
11
+ const defaultIcons: Record<string, React.ComponentType<any>> = {
12
+ primary: (props: any) => <IconSvg {...props} name="information" aria-label="information" />,
13
+ success: (props: any) => <IconSvg {...props} name="check-circle" aria-label="check-circle" />,
14
+ error: (props: any) => <IconSvg {...props} name="alert-circle" aria-label="alert-circle" />,
15
+ warning: (props: any) => <IconSvg {...props} name="alert" aria-label="alert" />,
16
+ info: (props: any) => <IconSvg {...props} name="information" aria-label="information" />,
17
+ neutral: (props: any) => <IconSvg {...props} name="record-circle" aria-label="record-circle" />,
17
18
  };
18
19
 
19
20
  /**
20
21
  * Notification banner for displaying important messages, warnings, or status updates.
21
22
  * Supports multiple intents, dismissibility, and custom actions.
22
23
  */
23
- const Alert = forwardRef<HTMLDivElement, AlertProps>(({
24
+ const Alert = forwardRef<IdealystElement, AlertProps>(({
24
25
  title,
25
26
  message,
26
27
  children,
27
28
  intent = 'neutral',
28
29
  type = 'soft',
30
+ size = 'md',
29
31
  icon,
30
32
  showIcon = true,
31
33
  dismissible = false,
@@ -35,37 +37,39 @@ const Alert = forwardRef<HTMLDivElement, AlertProps>(({
35
37
  testID,
36
38
  id,
37
39
  }, ref) => {
38
- // Compute dynamic styles with intent and type
39
- const dynamicProps = { intent, type };
40
+ // Apply variants for size, intent, and type
41
+ alertStyles.useVariants({ size, intent, type });
42
+
43
+ // Compute dynamic styles with intent, type, and size
44
+ const dynamicProps = { intent, type, size };
40
45
  const containerProps = getWebProps([(alertStyles.container as any)(dynamicProps), style as any]);
41
46
  const iconContainerProps = getWebProps([(alertStyles.iconContainer as any)(dynamicProps)]);
42
- const contentProps = getWebProps([(alertStyles.content as any)({})]);
47
+ const contentProps = getWebProps([(alertStyles.content as any)(dynamicProps)]);
43
48
  const titleProps = getWebProps([(alertStyles.title as any)(dynamicProps)]);
44
49
  const messageProps = getWebProps([(alertStyles.message as any)(dynamicProps)]);
45
- const actionsProps = getWebProps([(alertStyles.actions as any)({})]);
50
+ const actionsProps = getWebProps([(alertStyles.actions as any)(dynamicProps)]);
46
51
  const closeButtonProps = getWebProps([(alertStyles.closeButton as any)(dynamicProps)]);
47
52
  const closeIconProps = getWebProps([(alertStyles.closeIcon as any)(dynamicProps)]);
48
53
 
49
- const displayIcon = icon !== undefined ? icon : (showIcon ? defaultIcons[intent] : null);
50
-
51
- // Helper to render icon
52
- const renderIcon = (iconProp: typeof displayIcon) => {
53
- if (!iconProp) return null;
54
-
55
- if (isIconName(iconProp)) {
56
- return (
57
- <IconSvg
58
- name={iconProp}
54
+ const Icon = useMemo(() => {
55
+ if (!showIcon) return null;
56
+ if (!icon) {
57
+ const Element = defaultIcons[intent];
58
+ if (Element) {
59
+ return <Element {...iconContainerProps} />;
60
+ }
61
+ return null
62
+ } else if (typeof icon === 'string') {
63
+ return <IconSvg
64
+ name={icon}
59
65
  {...iconContainerProps}
60
- aria-label={iconProp}
66
+ aria-label={icon}
61
67
  />
62
- );
63
- } else if (isValidElement(iconProp)) {
64
- return iconProp;
68
+ } else if (isValidElement(icon)) {
69
+ return icon;
65
70
  }
66
-
67
71
  return null;
68
- };
72
+ }, [icon, showIcon, intent]);
69
73
 
70
74
  const mergedRef = useMergeRefs(ref, containerProps.ref);
71
75
 
@@ -77,7 +81,7 @@ const Alert = forwardRef<HTMLDivElement, AlertProps>(({
77
81
  data-testid={testID}
78
82
  role="alert"
79
83
  >
80
- {displayIcon && renderIcon(displayIcon)}
84
+ {Icon}
81
85
 
82
86
  <div {...contentProps}>
83
87
  {title && (
@@ -1,29 +1,79 @@
1
1
  import type { StyleProp, ViewStyle } from 'react-native';
2
- import type { Intent } from '@idealyst/theme';
2
+ import type { Intent, Size } from '@idealyst/theme';
3
3
  import { BaseProps } from '../utils/viewStyleProps';
4
4
 
5
5
  // Component-specific type aliases for future extensibility
6
6
  export type AlertIntentVariant = Intent;
7
- export type AlertType= 'filled' | 'outlined' | 'soft';
7
+ export type AlertSizeVariant = Size;
8
+ export type AlertType = 'filled' | 'outlined' | 'soft';
8
9
 
9
10
  /**
10
11
  * Feedback message component for displaying important information to users.
11
- * Supports multiple intent colors, dismissible behavior, and custom actions.
12
+ * Supports multiple intent colors, sizes, dismissible behavior, and custom actions.
12
13
  */
13
14
  export interface AlertProps extends BaseProps {
14
15
  /**
15
16
  * The title of the alert
16
17
  */
17
18
  title?: string;
19
+
20
+ /**
21
+ * The message content of the alert
22
+ */
18
23
  message?: string;
24
+
25
+ /**
26
+ * Custom content to display in the alert
27
+ */
19
28
  children?: React.ReactNode;
29
+
30
+ /**
31
+ * The intent/color scheme of the alert
32
+ */
20
33
  intent?: AlertIntentVariant;
34
+
35
+ /**
36
+ * The visual style type of the alert
37
+ */
21
38
  type?: AlertType;
39
+
40
+ /**
41
+ * The size of the alert
42
+ */
43
+ size?: AlertSizeVariant;
44
+
45
+ /**
46
+ * Custom icon to display. Pass `null` to hide icon.
47
+ */
22
48
  icon?: React.ReactNode;
49
+
50
+ /**
51
+ * Whether to show the default intent icon
52
+ */
23
53
  showIcon?: boolean;
54
+
55
+ /**
56
+ * Whether the alert can be dismissed
57
+ */
24
58
  dismissible?: boolean;
59
+
60
+ /**
61
+ * Called when the alert is dismissed
62
+ */
25
63
  onDismiss?: () => void;
64
+
65
+ /**
66
+ * Action buttons to display at the bottom of the alert
67
+ */
26
68
  actions?: React.ReactNode;
69
+
70
+ /**
71
+ * Additional styles
72
+ */
27
73
  style?: StyleProp<ViewStyle>;
74
+
75
+ /**
76
+ * Test ID for testing
77
+ */
28
78
  testID?: string;
29
79
  }
@@ -3,8 +3,9 @@ import { View, Text, Image } from 'react-native';
3
3
  import { AvatarProps } from './types';
4
4
  import { avatarStyles } from './Avatar.styles';
5
5
  import { getNativeAccessibilityProps } from '../utils/accessibility';
6
+ import type { IdealystElement } from '../utils/refTypes';
6
7
 
7
- const Avatar = forwardRef<View, AvatarProps>(({
8
+ const Avatar = forwardRef<IdealystElement, AvatarProps>(({
8
9
  src,
9
10
  alt,
10
11
  fallback,
@@ -45,7 +46,7 @@ const Avatar = forwardRef<View, AvatarProps>(({
45
46
  const fallbackStyle = (avatarStyles.fallback as any)({});
46
47
 
47
48
  return (
48
- <View ref={ref} nativeID={id} style={[avatarStyle, style]} testID={testID} {...nativeA11yProps}>
49
+ <View ref={ref as any} nativeID={id} style={[avatarStyle, style]} testID={testID} {...nativeA11yProps}>
49
50
  {src && !hasError ? (
50
51
  <Image
51
52
  source={typeof src === 'string' ? { uri: src } : src}
@@ -4,12 +4,13 @@ import { AvatarProps } from './types';
4
4
  import { avatarStyles } from './Avatar.styles';
5
5
  import useMergeRefs from '../hooks/useMergeRefs';
6
6
  import { getWebAriaProps } from '../utils/accessibility';
7
+ import type { IdealystElement } from '../utils/refTypes';
7
8
 
8
9
  /**
9
10
  * User or entity representation with image support and fallback initials.
10
11
  * Available in circle or square shapes with multiple sizes.
11
12
  */
12
- const Avatar = forwardRef<HTMLDivElement, AvatarProps>(({
13
+ const Avatar = forwardRef<IdealystElement, AvatarProps>(({
13
14
  src,
14
15
  alt,
15
16
  fallback,
@@ -5,7 +5,7 @@ import { AccessibilityProps } from '../utils/accessibility';
5
5
 
6
6
  // Component-specific type aliases for future extensibility
7
7
  export type AvatarColorVariant = Color;
8
- export type AvatarSizeVariant = 'sm' | 'md' | 'lg' | 'xl';
8
+ export type AvatarSizeVariant = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
9
9
  export type AvatarShapeVariant = 'circle' | 'square';
10
10
 
11
11
  /**
@@ -4,25 +4,37 @@ import MaterialDesignIcons from '@react-native-vector-icons/material-design-icon
4
4
  import { BadgeProps } from './types';
5
5
  import { badgeStyles } from './Badge.styles';
6
6
  import { isIconName } from '../Icon/icon-resolver';
7
+ import type { IdealystElement } from '../utils/refTypes';
7
8
 
8
- const Badge = forwardRef<View, BadgeProps>(({
9
+ /**
10
+ * Small status indicator for counts, labels, or notifications.
11
+ * Available in filled, outlined, and dot variants with customizable colors.
12
+ *
13
+ * Supports both `intent` (semantic colors) and `color` (raw palette colors).
14
+ * If both are provided, `intent` takes precedence.
15
+ */
16
+ const Badge = forwardRef<IdealystElement, BadgeProps>(({
9
17
  children,
10
18
  icon,
11
19
  size = 'md',
12
20
  type = 'filled',
13
- color = 'blue',
21
+ intent,
22
+ color,
14
23
  style,
15
24
  testID,
16
25
  id,
17
26
  }, ref) => {
27
+ // Default to 'primary' intent if neither intent nor color is provided
28
+ const effectiveColor = intent ? undefined : (color ?? 'primary');
29
+
18
30
  badgeStyles.useVariants({
19
31
  size,
20
32
  type,
21
33
  });
22
34
 
23
- // Call dynamic styles with color variant
24
- const badgeStyle = (badgeStyles.badge as any)({ color });
25
- const textStyle = (badgeStyles.text as any)({ color });
35
+ // Call dynamic styles with intent/color - intent takes precedence
36
+ const badgeStyle = (badgeStyles.badge as any)({ intent, color: effectiveColor });
37
+ const textStyle = (badgeStyles.text as any)({ intent, color: effectiveColor });
26
38
 
27
39
  // Map badge size to icon size
28
40
  const iconSize = size === 'sm' ? 12 : size === 'md' ? 14 : 16;
@@ -62,7 +74,7 @@ const Badge = forwardRef<View, BadgeProps>(({
62
74
 
63
75
  return (
64
76
  <View
65
- ref={ref}
77
+ ref={ref as any}
66
78
  nativeID={id}
67
79
  style={[badgeStyle, style]}
68
80
  testID={testID}
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * Badge styles using defineStyle with $iterator expansion.
3
+ *
4
+ * Supports both `intent` (semantic colors) and `color` (raw palette colors).
5
+ * If both are provided, `intent` takes precedence.
3
6
  */
4
7
  import { StyleSheet } from 'react-native-unistyles';
5
8
  import { defineStyle, ThemeStyleWrapper, getColorFromString } from '@idealyst/theme';
6
- import type { Theme as BaseTheme, Size, Color } from '@idealyst/theme';
9
+ import type { Theme as BaseTheme, Size, Color, Intent } 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,15 +19,29 @@ type BadgeType = 'filled' | 'outlined' | 'dot';
16
19
  export type BadgeDynamicProps = {
17
20
  size?: Size;
18
21
  type?: BadgeType;
22
+ intent?: Intent;
19
23
  color?: Color;
20
24
  };
21
25
 
26
+ /**
27
+ * Helper to resolve badge color from intent or color prop.
28
+ * Intent takes precedence over color.
29
+ */
30
+ const resolveBadgeColor = (theme: Theme, intent?: Intent, color?: Color): string => {
31
+ if (intent) {
32
+ // Use intent's primary color
33
+ return (theme as unknown as BaseTheme).intents[intent].primary;
34
+ }
35
+ // Fall back to color prop or default to primary intent
36
+ return getColorFromString(theme as unknown as BaseTheme, color ?? 'primary');
37
+ };
38
+
22
39
  /**
23
40
  * Badge styles with color-based type variants.
24
41
  */
25
42
  export const badgeStyles = defineStyle('Badge', (theme: Theme) => ({
26
- badge: ({ color = 'primary', type = 'filled' }: BadgeDynamicProps) => {
27
- const colorValue = getColorFromString(theme as unknown as BaseTheme, color);
43
+ badge: ({ intent, color, type = 'filled' }: BadgeDynamicProps) => {
44
+ const colorValue = resolveBadgeColor(theme, intent, color);
28
45
 
29
46
  const typeStyles = type === 'filled'
30
47
  ? { borderWidth: 0, backgroundColor: colorValue }
@@ -56,8 +73,8 @@ export const badgeStyles = defineStyle('Badge', (theme: Theme) => ({
56
73
  } as const;
57
74
  },
58
75
 
59
- text: ({ color = 'primary', type = 'filled' }: BadgeDynamicProps) => {
60
- const colorValue = getColorFromString(theme as unknown as BaseTheme, color);
76
+ text: ({ intent, color, type = 'filled' }: BadgeDynamicProps) => {
77
+ const colorValue = resolveBadgeColor(theme, intent, color);
61
78
 
62
79
  const textColor = type === 'filled'
63
80
  ? theme.colors.text.inverse
@@ -4,18 +4,23 @@ import { BadgeProps } from './types';
4
4
  import { badgeStyles } from './Badge.styles';
5
5
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
6
  import useMergeRefs from '../hooks/useMergeRefs';
7
+ import type { IdealystElement } from '../utils/refTypes';
7
8
 
8
9
  /**
9
10
  * Small status indicator for counts, labels, or notifications.
10
11
  * Available in filled, outlined, and dot variants with customizable colors.
12
+ *
13
+ * Supports both `intent` (semantic colors) and `color` (raw palette colors).
14
+ * If both are provided, `intent` takes precedence.
11
15
  */
12
- const Badge = forwardRef<HTMLSpanElement, BadgeProps>((props, ref) => {
16
+ const Badge = forwardRef<IdealystElement, BadgeProps>((props, ref) => {
13
17
  const {
14
18
  children,
15
19
  size = 'md',
16
20
  type: typeProp,
17
21
  variant,
18
- color = 'blue',
22
+ intent,
23
+ color,
19
24
  icon,
20
25
  style,
21
26
  testID,
@@ -25,14 +30,17 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>((props, ref) => {
25
30
  // variant is an alias for type - variant takes precedence if both are set
26
31
  const type = variant ?? typeProp ?? 'filled';
27
32
 
33
+ // Default to 'primary' intent if neither intent nor color is provided
34
+ const effectiveColor = intent ? undefined : (color ?? 'primary');
35
+
28
36
  badgeStyles.useVariants({
29
37
  size,
30
38
  type,
31
39
  });
32
40
 
33
- const badgeStyle = (badgeStyles.badge as any)({ color });
41
+ const badgeStyle = (badgeStyles.badge as any)({ intent, color: effectiveColor });
34
42
  const contentStyle = badgeStyles.content as any;
35
- const textStyle = (badgeStyles.text as any)({ color });
43
+ const textStyle = (badgeStyles.text as any)({ intent, color: effectiveColor });
36
44
 
37
45
  const badgeProps = getWebProps([badgeStyle]);
38
46
  const contentProps = getWebProps([contentStyle]);
@@ -1,11 +1,12 @@
1
1
  import type { CSSProperties, ReactNode } from 'react';
2
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
3
  import type { IconName } from '../Icon/icon-types';
4
- import { Color, Size } from '@idealyst/theme';
4
+ import { Color, Intent, Size } from '@idealyst/theme';
5
5
  import { BaseProps } from '../utils/viewStyleProps';
6
6
 
7
7
  // Component-specific type aliases for future extensibility
8
8
  export type BadgeColorVariant = Color;
9
+ export type BadgeIntentVariant = Intent;
9
10
  export type BadgeSizeVariant = Size;
10
11
  export type BadgeType = 'filled' | 'outlined' | 'dot';
11
12
 
@@ -35,7 +36,18 @@ export interface BadgeProps extends BaseProps {
35
36
  variant?: BadgeType;
36
37
 
37
38
  /**
38
- * The color scheme of the badge
39
+ * The semantic color scheme of the badge.
40
+ * Use for status-based coloring (success, danger, warning, etc.).
41
+ *
42
+ * **Note:** Only one of `intent` or `color` should be set. If both are provided, `intent` takes precedence.
43
+ */
44
+ intent?: BadgeIntentVariant;
45
+
46
+ /**
47
+ * The raw palette color of the badge.
48
+ * Use for custom coloring that doesn't map to a semantic intent.
49
+ *
50
+ * **Note:** Only one of `intent` or `color` should be set. If both are provided, `intent` takes precedence.
39
51
  */
40
52
  color?: BadgeColorVariant;
41
53
 
@@ -6,6 +6,7 @@ import Icon from '../Icon';
6
6
  import type { IconName } from '../Icon/icon-types';
7
7
  import Menu from '../Menu/Menu.native';
8
8
  import type { MenuItem } from '../Menu/types';
9
+ import type { IdealystElement } from '../utils/refTypes';
9
10
 
10
11
  interface BreadcrumbItemProps {
11
12
  item: BreadcrumbItemType;
@@ -113,7 +114,7 @@ const BreadcrumbEllipsis: React.FC<BreadcrumbEllipsisProps> = ({ size, intent })
113
114
  );
114
115
  };
115
116
 
116
- const Breadcrumb = forwardRef<View, BreadcrumbProps>(({
117
+ const Breadcrumb = forwardRef<IdealystElement, BreadcrumbProps>(({
117
118
  items,
118
119
  separator = '/',
119
120
  maxItems,
@@ -169,7 +170,7 @@ const Breadcrumb = forwardRef<View, BreadcrumbProps>(({
169
170
 
170
171
  return (
171
172
  <View
172
- ref={ref}
173
+ ref={ref as any}
173
174
  nativeID={id}
174
175
  style={[containerStyle, style]}
175
176
  testID={testID}
@@ -1,16 +1,17 @@
1
- import { ComponentRef, forwardRef, useMemo } from 'react';
1
+ import { forwardRef, useMemo } from 'react';
2
2
  import { ActivityIndicator, StyleSheet as RNStyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
3
  import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons';
4
4
  import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
5
5
  import { buttonStyles } from './Button.styles';
6
6
  import { ButtonProps } from './types';
7
7
  import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
8
+ import type { IdealystElement } from '../utils/refTypes';
8
9
 
9
- const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonProps>((props, ref) => {
10
+ const Button = forwardRef<IdealystElement, ButtonProps>((props, ref) => {
10
11
  const {
11
12
  children,
12
- title,
13
13
  onPress,
14
+ onClick,
14
15
  disabled = false,
15
16
  loading = false,
16
17
  type = 'contained',
@@ -38,6 +39,16 @@ const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonProps>((p
38
39
  // Button is effectively disabled when loading
39
40
  const isDisabled = disabled || loading;
40
41
 
42
+ // Determine the handler to use - onPress takes precedence
43
+ const pressHandler = onPress ?? onClick;
44
+
45
+ // Warn about deprecated onClick usage in development
46
+ if (__DEV__ && onClick && !onPress) {
47
+ console.warn(
48
+ 'Button: onClick prop is deprecated. Use onPress instead for cross-platform compatibility.'
49
+ );
50
+ }
51
+
41
52
  // Apply variants for size, disabled, gradient
42
53
  buttonStyles.useVariants({
43
54
  size,
@@ -67,8 +78,7 @@ const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonProps>((p
67
78
  const iconSize = iconSizeMap[size] ?? 16;
68
79
 
69
80
 
70
- // Use children if available, otherwise use title
71
- const buttonContent = children || title;
81
+ const buttonContent = children;
72
82
 
73
83
  // Determine if we need to wrap content in icon container
74
84
  const hasIcons = leftIcon || rightIcon;
@@ -159,7 +169,7 @@ const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonProps>((p
159
169
  // TouchableOpacity types don't include nativeID but it's a valid RN prop
160
170
  const touchableProps = {
161
171
  ref,
162
- onPress,
172
+ onPress: pressHandler,
163
173
  disabled: isDisabled,
164
174
  testID,
165
175
  nativeID: id,
@@ -70,12 +70,12 @@ export const buttonStyles = defineStyle('Button', (theme: Theme) => ({
70
70
  // $iterator expands for each button size
71
71
  type: {
72
72
  contained: {
73
- backgroundColor: theme.intents[intent].primary,
73
+ backgroundColor: theme.$intents.primary,
74
74
  borderColor: 'transparent',
75
75
  },
76
76
  outlined: {
77
77
  backgroundColor: 'transparent',
78
- borderColor: theme.intents[intent].primary,
78
+ borderColor: theme.$intents.primary,
79
79
  },
80
80
  text: {
81
81
  backgroundColor: 'transparent',
@@ -5,20 +5,20 @@ import { buttonStyles } from './Button.styles';
5
5
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
6
  import useMergeRefs from '../hooks/useMergeRefs';
7
7
  import { getWebInteractiveAriaProps, generateAccessibilityId } from '../utils/accessibility';
8
+ import type { IdealystElement } from '../utils/refTypes';
8
9
 
9
10
  /**
10
11
  * Interactive button component with multiple visual variants, sizes, and icon support.
11
12
  * Supports contained, outlined, and text styles with customizable intent colors.
12
13
  */
13
- const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
14
+ const Button = forwardRef<IdealystElement, ButtonProps>((props, ref) => {
14
15
  const {
15
- title,
16
16
  children,
17
17
  onPress,
18
+ onClick,
18
19
  disabled = false,
19
20
  loading = false,
20
- type: typeProp,
21
- variant,
21
+ type = 'contained',
22
22
  intent = 'primary',
23
23
  size = 'md',
24
24
  gradient,
@@ -42,9 +42,6 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
42
42
  accessibilityHasPopup,
43
43
  } = props;
44
44
 
45
- // variant is an alias for type - variant takes precedence if both are set
46
- const type = variant ?? typeProp ?? 'contained';
47
-
48
45
  // Button is effectively disabled when loading
49
46
  const isDisabled = disabled || loading;
50
47
 
@@ -55,13 +52,23 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
55
52
  gradient,
56
53
  });
57
54
 
55
+ // Determine the handler to use - onPress takes precedence
56
+ const pressHandler = onPress ?? onClick;
57
+
58
+ // Warn about deprecated onClick usage in development
59
+ if (process.env.NODE_ENV !== 'production' && onClick && !onPress) {
60
+ console.warn(
61
+ 'Button: onClick prop is deprecated. Use onPress instead for cross-platform compatibility.'
62
+ );
63
+ }
64
+
58
65
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
59
66
  e.preventDefault();
60
- // Only stop propagation if we have an onPress handler
67
+ // Only stop propagation if we have a handler
61
68
  // Otherwise, let clicks bubble up to parent handlers (e.g., Menu triggers)
62
- if (!isDisabled && onPress) {
69
+ if (!isDisabled && pressHandler) {
63
70
  e.stopPropagation();
64
- onPress();
71
+ pressHandler();
65
72
  }
66
73
  };
67
74
 
@@ -71,8 +78,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
71
78
  // Generate ARIA props - especially important for icon-only buttons
72
79
  const ariaProps = useMemo(() => {
73
80
  // For icon-only buttons, accessibilityLabel is critical
74
- const buttonContent = children || title;
75
- const isIconOnly = !buttonContent && (leftIcon || rightIcon);
81
+ const isIconOnly = !children && (leftIcon || rightIcon);
76
82
  const computedLabel = accessibilityLabel ?? (isIconOnly && typeof leftIcon === 'string' ? leftIcon : undefined);
77
83
 
78
84
  return getWebInteractiveAriaProps({
@@ -92,7 +98,6 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
92
98
  }, [
93
99
  accessibilityLabel,
94
100
  children,
95
- title,
96
101
  leftIcon,
97
102
  rightIcon,
98
103
  accessibilityHint,
@@ -149,8 +154,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
149
154
  return null;
150
155
  };
151
156
 
152
- // Use children if available, otherwise use title
153
- const buttonContent = children || title;
157
+ const buttonContent = children;
154
158
 
155
159
  // Determine if we need to wrap content in icon container
156
160
  const hasIcons = leftIcon || rightIcon;
@@ -24,19 +24,20 @@ export type ButtonGradient = 'darken' | 'lighten';
24
24
  */
25
25
  export interface ButtonProps extends BaseProps, InteractiveAccessibilityProps {
26
26
  /**
27
- * The text or content to display inside the button
27
+ * The content to display inside the button
28
28
  */
29
29
  children?: ReactNode;
30
30
 
31
31
  /**
32
- * The text title to display inside the button (for web)
32
+ * Called when the button is pressed
33
33
  */
34
- title?: string;
34
+ onPress?: () => void;
35
35
 
36
36
  /**
37
- * Called when the button is pressed
37
+ * @deprecated Use `onPress` instead. This prop exists for web compatibility only.
38
+ * Using onClick will log a deprecation warning in development.
38
39
  */
39
- onPress?: () => void;
40
+ onClick?: () => void;
40
41
 
41
42
  /**
42
43
  * Whether the button is disabled
@@ -48,11 +49,6 @@ export interface ButtonProps extends BaseProps, InteractiveAccessibilityProps {
48
49
  */
49
50
  type?: ButtonType;
50
51
 
51
- /**
52
- * Alias for type - the visual style variant of the button
53
- */
54
- variant?: ButtonType;
55
-
56
52
  /**
57
53
  * The intent/color scheme of the button
58
54
  */