@idealyst/components 1.2.29 → 1.2.30

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 (131) hide show
  1. package/README.md +3 -3
  2. package/package.json +3 -3
  3. package/plugin/__tests__/web.test.ts +2 -2
  4. package/src/Accordion/Accordion.native.tsx +3 -2
  5. package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
  6. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +25 -26
  7. package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -1
  8. package/src/Alert/Alert.native.tsx +20 -10
  9. package/src/Alert/Alert.styles.tsx +148 -86
  10. package/src/Alert/Alert.web.tsx +10 -5
  11. package/src/Alert/types.ts +53 -3
  12. package/src/Avatar/Avatar.native.tsx +3 -2
  13. package/src/Avatar/Avatar.web.tsx +2 -1
  14. package/src/Avatar/types.ts +1 -1
  15. package/src/Badge/Badge.native.tsx +18 -6
  16. package/src/Badge/Badge.styles.tsx +22 -5
  17. package/src/Badge/Badge.web.tsx +12 -4
  18. package/src/Badge/types.ts +14 -2
  19. package/src/Breadcrumb/Breadcrumb.native.tsx +3 -2
  20. package/src/Button/Button.native.tsx +16 -6
  21. package/src/Button/Button.styles.tsx +2 -2
  22. package/src/Button/Button.web.tsx +19 -15
  23. package/src/Button/types.ts +6 -10
  24. package/src/Card/Card.native.tsx +27 -3
  25. package/src/Card/Card.web.tsx +30 -4
  26. package/src/Card/types.ts +15 -0
  27. package/src/Checkbox/Checkbox.native.tsx +5 -4
  28. package/src/Checkbox/Checkbox.styles.tsx +62 -52
  29. package/src/Checkbox/Checkbox.web.tsx +4 -3
  30. package/src/Checkbox/types.ts +1 -1
  31. package/src/Chip/Chip.native.tsx +30 -7
  32. package/src/Chip/Chip.styles.tsx +142 -124
  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/Image/Image.native.tsx +3 -2
  46. package/src/Input/Input.native.tsx +42 -290
  47. package/src/Input/Input.styles.tsx +1 -1
  48. package/src/Input/Input.web.tsx +37 -288
  49. package/src/Input/index.native.ts +9 -2
  50. package/src/Input/index.ts +8 -1
  51. package/src/Input/index.web.ts +8 -1
  52. package/src/Input/types.ts +1 -1
  53. package/src/List/List.native.tsx +3 -2
  54. package/src/List/ListItem.native.tsx +3 -2
  55. package/src/List/ListSection.native.tsx +3 -2
  56. package/src/Menu/Menu.native.tsx +2 -1
  57. package/src/Menu/Menu.styles.tsx +79 -29
  58. package/src/Menu/Menu.web.tsx +2 -1
  59. package/src/Menu/MenuItem.native.tsx +4 -3
  60. package/src/Menu/MenuItem.styles.tsx +81 -32
  61. package/src/Menu/MenuItem.web.tsx +2 -1
  62. package/src/Menu/docs.ts +1 -1
  63. package/src/Popover/Popover.native.tsx +2 -1
  64. package/src/Popover/Popover.web.tsx +2 -1
  65. package/src/Popover/types.ts +15 -4
  66. package/src/Pressable/Pressable.native.tsx +3 -2
  67. package/src/Pressable/Pressable.web.tsx +3 -5
  68. package/src/Progress/Progress.native.tsx +5 -4
  69. package/src/Progress/Progress.web.tsx +3 -3
  70. package/src/Progress/types.ts +3 -3
  71. package/src/RadioButton/RadioButton.native.tsx +4 -3
  72. package/src/RadioButton/RadioButton.styles.tsx +53 -33
  73. package/src/RadioButton/RadioGroup.native.tsx +3 -2
  74. package/src/SVGImage/SVGImage.native.tsx +5 -4
  75. package/src/SVGImage/SVGImage.styles.tsx +44 -10
  76. package/src/SVGImage/SVGImage.web.tsx +2 -1
  77. package/src/Screen/Screen.native.tsx +2 -1
  78. package/src/Screen/Screen.web.tsx +2 -1
  79. package/src/Select/Select.native.tsx +6 -5
  80. package/src/Select/Select.styles.tsx +1 -1
  81. package/src/Select/Select.web.tsx +4 -3
  82. package/src/Select/types.ts +1 -1
  83. package/src/Skeleton/Skeleton.native.tsx +2 -1
  84. package/src/Slider/Slider.native.tsx +9 -8
  85. package/src/Slider/Slider.web.tsx +10 -9
  86. package/src/Slider/types.ts +9 -2
  87. package/src/Switch/Switch.native.tsx +7 -6
  88. package/src/Switch/Switch.styles.tsx +35 -17
  89. package/src/Switch/Switch.web.tsx +8 -7
  90. package/src/Switch/types.ts +44 -4
  91. package/src/TabBar/TabBar.native.tsx +3 -2
  92. package/src/Text/Text.native.tsx +3 -2
  93. package/src/Text/Text.web.tsx +2 -1
  94. package/src/TextArea/TextArea.native.tsx +3 -2
  95. package/src/TextArea/TextArea.styles.tsx +2 -2
  96. package/src/TextArea/TextArea.web.tsx +2 -1
  97. package/src/TextInput/TextInput.native.tsx +300 -0
  98. package/src/TextInput/TextInput.styles.tsx +207 -0
  99. package/src/TextInput/TextInput.web.tsx +301 -0
  100. package/src/TextInput/index.native.ts +3 -0
  101. package/src/TextInput/index.ts +5 -0
  102. package/src/TextInput/index.web.ts +5 -0
  103. package/src/TextInput/types.ts +163 -0
  104. package/src/Tooltip/Tooltip.native.tsx +3 -2
  105. package/src/Video/Video.native.tsx +4 -3
  106. package/src/View/View.native.tsx +2 -1
  107. package/src/View/View.web.tsx +2 -1
  108. package/src/examples/AlertExamples.tsx +5 -5
  109. package/src/examples/ButtonExamples.tsx +12 -12
  110. package/src/examples/CardExamples.tsx +1 -1
  111. package/src/examples/CheckboxExamples.tsx +2 -2
  112. package/src/examples/ChipExamples.tsx +6 -6
  113. package/src/examples/DialogExamples.tsx +1 -1
  114. package/src/examples/DividerExamples.tsx +1 -1
  115. package/src/examples/InputExamples.tsx +1 -1
  116. package/src/examples/LinkExamples.tsx +1 -1
  117. package/src/examples/ListExamples.tsx +1 -1
  118. package/src/examples/MenuExamples.tsx +2 -2
  119. package/src/examples/ProgressExamples.tsx +1 -1
  120. package/src/examples/RadioButtonExamples.tsx +5 -5
  121. package/src/examples/SVGImageExamples.tsx +1 -1
  122. package/src/examples/SelectExamples.tsx +1 -1
  123. package/src/examples/SliderExamples.tsx +5 -5
  124. package/src/examples/SwitchExamples.tsx +2 -2
  125. package/src/examples/TableExamples.tsx +1 -1
  126. package/src/examples/TooltipExamples.tsx +2 -2
  127. package/src/extensions/index.ts +1 -0
  128. package/src/extensions/types.ts +10 -3
  129. package/src/index.ts +23 -2
  130. package/src/utils/index.ts +12 -0
  131. package/src/utils/refTypes.ts +50 -0
@@ -67,63 +67,61 @@ export const checkboxStyles = defineStyle('Checkbox', (theme: Theme) => ({
67
67
  },
68
68
  }),
69
69
 
70
- checkbox: ({ intent = 'primary', type = 'default', checked = false, disabled = false }: CheckboxDynamicProps) => {
71
- const intentColors = theme.intents[intent];
72
-
73
- // Base border styles based on type
74
- const borderWidth = type === 'outlined' ? 2 : 1;
75
-
76
- // Checked state colors
77
- const backgroundColor = checked ? intentColors.primary : 'transparent';
78
- const borderColor = checked ? intentColors.primary : theme.colors.border.primary;
79
-
80
- return {
81
- alignItems: 'center' as const,
82
- justifyContent: 'center' as const,
83
- borderRadius: 4,
84
- position: 'relative' as const,
85
- backgroundColor,
86
- borderColor,
87
- borderWidth,
88
- borderStyle: 'solid' as const,
89
- opacity: disabled ? 0.5 : 1,
90
- variants: {
91
- size: {
92
- xs: { width: 14, height: 14 },
93
- sm: { width: 16, height: 16 },
94
- md: { width: 20, height: 20 },
95
- lg: { width: 24, height: 24 },
96
- xl: { width: 28, height: 28 },
97
- },
70
+ checkbox: (_props: CheckboxDynamicProps) => ({
71
+ alignItems: 'center' as const,
72
+ justifyContent: 'center' as const,
73
+ borderRadius: 4,
74
+ position: 'relative' as const,
75
+ borderStyle: 'solid' as const,
76
+ variants: {
77
+ size: {
78
+ xs: { width: 14, height: 14 },
79
+ sm: { width: 16, height: 16 },
80
+ md: { width: 20, height: 20 },
81
+ lg: { width: 24, height: 24 },
82
+ xl: { width: 28, height: 28 },
83
+ },
84
+ type: {
85
+ default: { borderWidth: 1 },
86
+ outlined: { borderWidth: 2 },
98
87
  },
99
- _web: {
100
- outline: 'none',
101
- display: 'flex',
102
- boxSizing: 'border-box',
103
- userSelect: 'none',
104
- WebkitAppearance: 'none',
105
- MozAppearance: 'none',
106
- appearance: 'none',
107
- transition: 'all 0.2s ease',
108
- cursor: disabled ? 'not-allowed' : 'pointer',
109
- border: `${borderWidth}px solid ${borderColor}`,
110
- _focus: {
111
- outline: `2px solid ${theme.intents.primary.primary}`,
112
- outlineOffset: '2px',
88
+ checked: {
89
+ true: {
90
+ backgroundColor: theme.$intents.primary,
91
+ borderColor: theme.$intents.primary,
113
92
  },
114
- _hover: disabled ? {} : { opacity: 0.8 },
115
- _active: disabled ? {} : { opacity: 0.6 },
93
+ false: {
94
+ backgroundColor: 'transparent',
95
+ borderColor: theme.colors.border.primary,
96
+ },
97
+ },
98
+ disabled: {
99
+ true: { opacity: 0.5 },
100
+ false: { opacity: 1 },
116
101
  },
117
- } as const;
118
- },
102
+ },
103
+ _web: {
104
+ outline: 'none',
105
+ display: 'flex',
106
+ boxSizing: 'border-box',
107
+ userSelect: 'none',
108
+ WebkitAppearance: 'none',
109
+ MozAppearance: 'none',
110
+ appearance: 'none',
111
+ transition: 'all 0.2s ease',
112
+ _focus: {
113
+ outline: `2px solid ${theme.intents.primary.primary}`,
114
+ outlineOffset: '2px',
115
+ },
116
+ },
117
+ }),
119
118
 
120
- checkmark: ({ checked = false }: CheckboxDynamicProps) => ({
119
+ checkmark: (_props: CheckboxDynamicProps) => ({
121
120
  position: 'absolute' as const,
122
121
  display: 'flex' as const,
123
122
  alignItems: 'center' as const,
124
123
  justifyContent: 'center' as const,
125
124
  color: '#ffffff',
126
- opacity: checked ? 1 : 0,
127
125
  variants: {
128
126
  size: {
129
127
  xs: { width: 10, height: 10 },
@@ -132,12 +130,15 @@ export const checkboxStyles = defineStyle('Checkbox', (theme: Theme) => ({
132
130
  lg: { width: 16, height: 16 },
133
131
  xl: { width: 20, height: 20 },
134
132
  },
133
+ checked: {
134
+ true: { opacity: 1 },
135
+ false: { opacity: 0 },
136
+ },
135
137
  },
136
138
  }),
137
139
 
138
- label: ({ disabled = false }: CheckboxDynamicProps) => ({
140
+ label: (_props: CheckboxDynamicProps) => ({
139
141
  color: theme.colors.text.primary,
140
- opacity: disabled ? 0.5 : 1,
141
142
  variants: {
142
143
  size: {
143
144
  xs: { fontSize: 12 },
@@ -146,6 +147,10 @@ export const checkboxStyles = defineStyle('Checkbox', (theme: Theme) => ({
146
147
  lg: { fontSize: 18 },
147
148
  xl: { fontSize: 20 },
148
149
  },
150
+ disabled: {
151
+ true: { opacity: 0.5 },
152
+ false: { opacity: 1 },
153
+ },
149
154
  },
150
155
  _web: {
151
156
  display: 'block',
@@ -155,9 +160,14 @@ export const checkboxStyles = defineStyle('Checkbox', (theme: Theme) => ({
155
160
  },
156
161
  }),
157
162
 
158
- helperText: ({ error = false }: CheckboxDynamicProps) => ({
163
+ helperText: (_props: CheckboxDynamicProps) => ({
159
164
  fontSize: 14,
160
- color: error ? theme.intents.error.primary : theme.colors.text.secondary,
161
165
  marginTop: 2,
166
+ variants: {
167
+ error: {
168
+ true: { color: theme.intents.danger.primary },
169
+ false: { color: theme.colors.text.secondary },
170
+ },
171
+ },
162
172
  }),
163
173
  }));
@@ -5,16 +5,17 @@ import { checkboxStyles } from './Checkbox.styles';
5
5
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
6
  import useMergeRefs from '../hooks/useMergeRefs';
7
7
  import { getWebSelectionAriaProps, generateAccessibilityId, combineIds } from '../utils/accessibility';
8
+ import type { IdealystElement } from '../utils/refTypes';
8
9
 
9
10
  /**
10
11
  * Checkbox input for boolean selection with support for indeterminate state.
11
12
  * Includes label, helper text, and error display options.
12
13
  */
13
- const Checkbox = forwardRef<HTMLDivElement, CheckboxProps>(({
14
+ const Checkbox = forwardRef<IdealystElement, CheckboxProps>(({
14
15
  checked = false,
15
16
  indeterminate = false,
16
17
  disabled = false,
17
- onCheckedChange,
18
+ onChange,
18
19
  size = 'md',
19
20
  intent = 'primary',
20
21
  variant = 'default',
@@ -56,7 +57,7 @@ const Checkbox = forwardRef<HTMLDivElement, CheckboxProps>(({
56
57
 
57
58
  const newChecked = event.target.checked;
58
59
  setInternalChecked(newChecked);
59
- onCheckedChange?.(newChecked);
60
+ onChange?.(newChecked);
60
61
  };
61
62
 
62
63
  // Generate unique IDs for accessibility
@@ -32,7 +32,7 @@ export interface CheckboxProps extends FormInputStyleProps, SelectionAccessibili
32
32
  /**
33
33
  * Called when the checkbox state changes
34
34
  */
35
- onCheckedChange?: (checked: boolean) => void;
35
+ onChange?: (checked: boolean) => void;
36
36
 
37
37
  /**
38
38
  * The size of the checkbox
@@ -1,11 +1,15 @@
1
- import { isValidElement, forwardRef, ComponentRef } from 'react';
1
+ import { isValidElement, forwardRef, useEffect, useRef } from 'react';
2
2
  import { Pressable, Text, View } from 'react-native';
3
3
  import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons';
4
4
  import { chipStyles } from './Chip.styles';
5
5
  import { isIconName } from '../Icon/icon-resolver';
6
6
  import type { ChipProps } from './types';
7
+ import type { IdealystElement } from '../utils/refTypes';
7
8
 
8
- const Chip = forwardRef<ComponentRef<typeof Pressable>, ChipProps>(({
9
+ // Track if we've logged the onClick deprecation warning (log once per session)
10
+ let hasLoggedOnClickWarning = false;
11
+
12
+ const Chip = forwardRef<IdealystElement, ChipProps>(({
9
13
  label,
10
14
  type = 'filled',
11
15
  intent = 'primary',
@@ -16,6 +20,7 @@ const Chip = forwardRef<ComponentRef<typeof Pressable>, ChipProps>(({
16
20
  selectable = false,
17
21
  selected = false,
18
22
  onPress,
23
+ onClick,
19
24
  disabled = false,
20
25
  style,
21
26
  testID,
@@ -24,10 +29,28 @@ const Chip = forwardRef<ComponentRef<typeof Pressable>, ChipProps>(({
24
29
  accessibilityLabel,
25
30
  accessibilityChecked,
26
31
  }, ref) => {
32
+ const hasWarnedRef = useRef(false);
33
+
34
+ // Warn about onClick usage (deprecated)
35
+ useEffect(() => {
36
+ if (onClick && !hasWarnedRef.current && !hasLoggedOnClickWarning) {
37
+ hasWarnedRef.current = true;
38
+ hasLoggedOnClickWarning = true;
39
+ console.warn(
40
+ '[Chip] onClick is deprecated. Use onPress instead.\n' +
41
+ 'Chip is a cross-platform component that follows React Native conventions.\n' +
42
+ 'onClick will be removed in a future version.\n\n' +
43
+ 'Migration: Replace onClick={handler} with onPress={handler}'
44
+ );
45
+ }
46
+ }, [onClick]);
47
+
27
48
  const handlePress = () => {
28
49
  if (disabled) return;
29
- if (onPress) {
30
- onPress();
50
+ // Prefer onPress, fall back to deprecated onClick
51
+ const handler = onPress ?? onClick;
52
+ if (handler) {
53
+ handler();
31
54
  }
32
55
  };
33
56
 
@@ -72,7 +95,7 @@ const Chip = forwardRef<ComponentRef<typeof Pressable>, ChipProps>(({
72
95
  return null;
73
96
  };
74
97
 
75
- const isClickable = (onPress && !disabled) || (selectable && !disabled);
98
+ const isClickable = ((onPress || onClick) && !disabled) || (selectable && !disabled);
76
99
 
77
100
  const innerContent = (
78
101
  <>
@@ -102,7 +125,7 @@ const Chip = forwardRef<ComponentRef<typeof Pressable>, ChipProps>(({
102
125
  if (isClickable) {
103
126
  return (
104
127
  <Pressable
105
- ref={ref}
128
+ ref={ref as any}
106
129
  nativeID={id}
107
130
  onPress={handlePress}
108
131
  disabled={disabled}
@@ -122,7 +145,7 @@ const Chip = forwardRef<ComponentRef<typeof Pressable>, ChipProps>(({
122
145
  }
123
146
 
124
147
  return (
125
- <View ref={ref} nativeID={id} style={[containerStyle, style]} testID={testID}>
148
+ <View ref={ref as any} nativeID={id} style={[containerStyle, style]} testID={testID}>
126
149
  {innerContent}
127
150
  </View>
128
151
  );
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Chip styles using defineStyle with dynamic size/intent/type/selected handling.
2
+ * Chip styles using defineStyle with variant expansion.
3
+ *
4
+ * Chip has compound logic between type+selected+intent that's handled via
5
+ * nested variants. The $intents iterator expands for all intent values.
3
6
  */
4
7
  import { StyleSheet } from 'react-native-unistyles';
5
8
  import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
@@ -22,129 +25,144 @@ export type ChipDynamicProps = {
22
25
  };
23
26
 
24
27
  /**
25
- * Chip styles with size/intent/type/selected handling.
28
+ * Chip styles with variant expansion for size/intent/type/selected.
26
29
  */
27
30
  export const chipStyles = defineStyle('Chip', (theme: Theme) => ({
28
- container: ({ size = 'md', intent = 'primary', type = 'filled', selected = false, disabled = false }: ChipDynamicProps) => {
29
- const intentValue = theme.intents[intent];
30
- const sizeValue = theme.sizes.chip[size];
31
-
32
- // Compute colors based on type and selected state
33
- let backgroundColor: string;
34
- let borderColor: string;
35
- let borderWidth: number;
36
-
37
- if (type === 'filled') {
38
- borderWidth = 1;
39
- backgroundColor = selected ? intentValue.contrast : intentValue.primary;
40
- borderColor = selected ? intentValue.primary : 'transparent';
41
- } else if (type === 'outlined') {
42
- borderWidth = 1;
43
- backgroundColor = selected ? intentValue.primary : 'transparent';
44
- borderColor = intentValue.primary;
45
- } else { // soft
46
- borderWidth = 0;
47
- backgroundColor = selected ? intentValue.primary : intentValue.light;
48
- borderColor = 'transparent';
49
- }
50
-
51
- return {
52
- display: 'flex' as const,
53
- flexDirection: 'row' as const,
54
- alignItems: 'center' as const,
55
- justifyContent: 'center' as const,
56
- gap: 4,
57
- paddingHorizontal: sizeValue.paddingHorizontal as number,
58
- paddingVertical: sizeValue.paddingVertical as number,
59
- minHeight: sizeValue.minHeight as number,
60
- borderRadius: sizeValue.borderRadius as number,
61
- backgroundColor,
62
- borderColor,
63
- borderWidth,
64
- borderStyle: borderWidth > 0 ? ('solid' as const) : undefined,
65
- opacity: disabled ? 0.5 : 1,
66
- } as const;
67
- },
68
-
69
- label: ({ size = 'md', intent = 'primary', type = 'filled', selected = false }: ChipDynamicProps) => {
70
- const intentValue = theme.intents[intent];
71
- const sizeValue = theme.sizes.chip[size];
72
-
73
- // Compute color based on type and selected state
74
- let color: string;
75
- if (type === 'filled') {
76
- color = selected ? intentValue.primary : intentValue.contrast;
77
- } else if (type === 'outlined') {
78
- color = selected ? theme.colors.text.inverse : intentValue.primary;
79
- } else { // soft
80
- color = selected ? theme.colors.text.inverse : intentValue.dark;
81
- }
82
-
83
- return {
84
- fontFamily: 'inherit' as const,
85
- fontWeight: '500' as const,
86
- fontSize: sizeValue.fontSize as number,
87
- lineHeight: sizeValue.lineHeight as number,
88
- color,
89
- } as const;
90
- },
91
-
92
- icon: ({ size = 'md', intent = 'primary', type = 'filled', selected = false }: ChipDynamicProps) => {
93
- const intentValue = theme.intents[intent];
94
- const sizeValue = theme.sizes.chip[size];
95
-
96
- // Same color logic as label
97
- let color: string;
98
- if (type === 'filled') {
99
- color = selected ? intentValue.primary : intentValue.contrast;
100
- } else if (type === 'outlined') {
101
- color = selected ? theme.colors.text.inverse : intentValue.primary;
102
- } else {
103
- color = selected ? theme.colors.text.inverse : intentValue.dark;
104
- }
105
-
106
- return {
107
- display: 'flex' as const,
108
- alignItems: 'center' as const,
109
- justifyContent: 'center' as const,
110
- width: sizeValue.iconSize as number,
111
- height: sizeValue.iconSize as number,
112
- color,
113
- } as const;
114
- },
115
-
116
- deleteButton: ({ size = 'md' }: ChipDynamicProps) => {
117
- const sizeValue = theme.sizes.chip[size];
118
-
119
- return {
120
- display: 'flex' as const,
121
- alignItems: 'center' as const,
122
- justifyContent: 'center' as const,
123
- padding: 0,
124
- marginLeft: 4,
125
- borderRadius: 12,
126
- width: sizeValue.iconSize as number,
127
- height: sizeValue.iconSize as number,
128
- } as const;
129
- },
130
-
131
- deleteIcon: ({ size = 'md', intent = 'primary', type = 'filled', selected = false }: ChipDynamicProps) => {
132
- const intentValue = theme.intents[intent];
133
- const sizeValue = theme.sizes.chip[size];
134
-
135
- // Same color logic as label/icon
136
- let color: string;
137
- if (type === 'filled') {
138
- color = selected ? intentValue.primary : intentValue.contrast;
139
- } else if (type === 'outlined') {
140
- color = selected ? theme.colors.text.inverse : intentValue.primary;
141
- } else {
142
- color = selected ? theme.colors.text.inverse : intentValue.dark;
143
- }
144
-
145
- return {
146
- fontSize: sizeValue.iconSize as number,
147
- color,
148
- } as const;
149
- },
31
+ container: (_props: ChipDynamicProps) => ({
32
+ display: 'flex' as const,
33
+ flexDirection: 'row' as const,
34
+ alignItems: 'center' as const,
35
+ justifyContent: 'center' as const,
36
+ gap: 4,
37
+ borderStyle: 'solid' as const,
38
+ variants: {
39
+ size: {
40
+ paddingHorizontal: theme.sizes.$chip.paddingHorizontal,
41
+ paddingVertical: theme.sizes.$chip.paddingVertical,
42
+ minHeight: theme.sizes.$chip.minHeight,
43
+ borderRadius: theme.sizes.$chip.borderRadius,
44
+ },
45
+ type: {
46
+ filled: {
47
+ borderWidth: 1,
48
+ backgroundColor: theme.$intents.primary,
49
+ borderColor: 'transparent',
50
+ },
51
+ outlined: {
52
+ borderWidth: 1,
53
+ backgroundColor: 'transparent',
54
+ borderColor: theme.$intents.primary,
55
+ },
56
+ soft: {
57
+ borderWidth: 0,
58
+ backgroundColor: theme.$intents.light,
59
+ borderColor: 'transparent',
60
+ },
61
+ },
62
+ selected: {
63
+ true: {},
64
+ false: {},
65
+ },
66
+ disabled: {
67
+ true: { opacity: 0.5 },
68
+ false: { opacity: 1 },
69
+ },
70
+ },
71
+ compoundVariants: [
72
+ // filled + selected: swap bg/border
73
+ { type: 'filled', selected: true, styles: { backgroundColor: theme.$intents.contrast, borderColor: theme.$intents.primary } },
74
+ // outlined + selected: fill with primary
75
+ { type: 'outlined', selected: true, styles: { backgroundColor: theme.$intents.primary } },
76
+ // soft + selected: fill with primary
77
+ { type: 'soft', selected: true, styles: { backgroundColor: theme.$intents.primary } },
78
+ ],
79
+ }),
80
+
81
+ label: (_props: ChipDynamicProps) => ({
82
+ fontFamily: 'inherit' as const,
83
+ fontWeight: '500' as const,
84
+ variants: {
85
+ size: {
86
+ fontSize: theme.sizes.$chip.fontSize,
87
+ lineHeight: theme.sizes.$chip.lineHeight,
88
+ },
89
+ type: {
90
+ filled: { color: theme.$intents.contrast },
91
+ outlined: { color: theme.$intents.primary },
92
+ soft: { color: theme.$intents.dark },
93
+ },
94
+ selected: {
95
+ true: {},
96
+ false: {},
97
+ },
98
+ },
99
+ compoundVariants: [
100
+ { type: 'filled', selected: true, styles: { color: theme.$intents.primary } },
101
+ { type: 'outlined', selected: true, styles: { color: theme.colors.text.inverse } },
102
+ { type: 'soft', selected: true, styles: { color: theme.colors.text.inverse } },
103
+ ],
104
+ }),
105
+
106
+ icon: (_props: ChipDynamicProps) => ({
107
+ display: 'flex' as const,
108
+ alignItems: 'center' as const,
109
+ justifyContent: 'center' as const,
110
+ variants: {
111
+ size: {
112
+ width: theme.sizes.$chip.iconSize,
113
+ height: theme.sizes.$chip.iconSize,
114
+ },
115
+ type: {
116
+ filled: { color: theme.$intents.contrast },
117
+ outlined: { color: theme.$intents.primary },
118
+ soft: { color: theme.$intents.dark },
119
+ },
120
+ selected: {
121
+ true: {},
122
+ false: {},
123
+ },
124
+ },
125
+ compoundVariants: [
126
+ { type: 'filled', selected: true, styles: { color: theme.$intents.primary } },
127
+ { type: 'outlined', selected: true, styles: { color: theme.colors.text.inverse } },
128
+ { type: 'soft', selected: true, styles: { color: theme.colors.text.inverse } },
129
+ ],
130
+ }),
131
+
132
+ deleteButton: (_props: ChipDynamicProps) => ({
133
+ display: 'flex' as const,
134
+ alignItems: 'center' as const,
135
+ justifyContent: 'center' as const,
136
+ padding: 0,
137
+ marginLeft: 4,
138
+ borderRadius: 12,
139
+ variants: {
140
+ size: {
141
+ width: theme.sizes.$chip.iconSize,
142
+ height: theme.sizes.$chip.iconSize,
143
+ },
144
+ },
145
+ }),
146
+
147
+ deleteIcon: (_props: ChipDynamicProps) => ({
148
+ variants: {
149
+ size: {
150
+ fontSize: theme.sizes.$chip.iconSize,
151
+ },
152
+ type: {
153
+ filled: { color: theme.$intents.contrast },
154
+ outlined: { color: theme.$intents.primary },
155
+ soft: { color: theme.$intents.dark },
156
+ },
157
+ selected: {
158
+ true: {},
159
+ false: {},
160
+ },
161
+ },
162
+ compoundVariants: [
163
+ { type: 'filled', selected: true, styles: { color: theme.$intents.primary } },
164
+ { type: 'outlined', selected: true, styles: { color: theme.colors.text.inverse } },
165
+ { type: 'soft', selected: true, styles: { color: theme.colors.text.inverse } },
166
+ ],
167
+ }),
150
168
  }));
@@ -1,16 +1,20 @@
1
- import React, { isValidElement, forwardRef } from 'react';
1
+ import React, { isValidElement, forwardRef, useEffect, useRef } from 'react';
2
2
  import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { chipStyles } from './Chip.styles';
4
4
  import type { ChipProps } 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';
9
+
10
+ // Track if we've logged the onClick deprecation warning (log once per session)
11
+ let hasLoggedOnClickWarning = false;
8
12
 
9
13
  /**
10
14
  * Compact interactive element for tags, filters, or selections.
11
15
  * Supports icons, selection state, and delete functionality.
12
16
  */
13
- const Chip = forwardRef<HTMLDivElement, ChipProps>(({
17
+ const Chip = forwardRef<IdealystElement, ChipProps>(({
14
18
  label,
15
19
  type = 'filled',
16
20
  intent = 'primary',
@@ -22,6 +26,7 @@ const Chip = forwardRef<HTMLDivElement, ChipProps>(({
22
26
  selectable = false,
23
27
  selected = false,
24
28
  onPress,
29
+ onClick,
25
30
  disabled = false,
26
31
  style,
27
32
  testID,
@@ -30,6 +35,22 @@ const Chip = forwardRef<HTMLDivElement, ChipProps>(({
30
35
  accessibilityLabel,
31
36
  accessibilityChecked,
32
37
  }, ref) => {
38
+ const hasWarnedRef = useRef(false);
39
+
40
+ // Warn about onClick usage (deprecated)
41
+ useEffect(() => {
42
+ if (onClick && !hasWarnedRef.current && !hasLoggedOnClickWarning) {
43
+ hasWarnedRef.current = true;
44
+ hasLoggedOnClickWarning = true;
45
+ console.warn(
46
+ '[Chip] onClick is deprecated. Use onPress instead.\n' +
47
+ 'Chip is a cross-platform component that follows React Native conventions.\n' +
48
+ 'onClick will be removed in a future version.\n\n' +
49
+ 'Migration: Replace onClick={handler} with onPress={handler}'
50
+ );
51
+ }
52
+ }, [onClick]);
53
+
33
54
  // Compute actual selected state
34
55
  const isSelected = selectable ? selected : false;
35
56
 
@@ -42,8 +63,10 @@ const Chip = forwardRef<HTMLDivElement, ChipProps>(({
42
63
 
43
64
  const handleClick = () => {
44
65
  if (disabled) return;
45
- if (onPress) {
46
- onPress();
66
+ // Prefer onPress, fall back to deprecated onClick
67
+ const handler = onPress ?? onClick;
68
+ if (handler) {
69
+ handler();
47
70
  }
48
71
  };
49
72
 
@@ -91,7 +114,7 @@ const Chip = forwardRef<HTMLDivElement, ChipProps>(({
91
114
  return null;
92
115
  };
93
116
 
94
- const isClickable = (onPress && !disabled) || (selectable && !disabled);
117
+ const isClickable = ((onPress || onClick) && !disabled) || (selectable && !disabled);
95
118
 
96
119
  const mergedRef = useMergeRefs(ref, containerProps.ref);
97
120
 
package/src/Chip/types.ts CHANGED
@@ -46,6 +46,21 @@ export interface ChipProps extends BaseProps, SelectionAccessibilityProps {
46
46
  /** Callback when chip is pressed */
47
47
  onPress?: () => void;
48
48
 
49
+ /**
50
+ * @deprecated Use `onPress` instead. This is a cross-platform component - use React Native conventions.
51
+ *
52
+ * Using `onClick` will trigger a console warning in development.
53
+ * This prop exists only for migration convenience and will be removed in a future version.
54
+ *
55
+ * @example
56
+ * // ❌ Don't use onClick
57
+ * <Chip label="Tag" onClick={() => {}} />
58
+ *
59
+ * // ✅ Use onPress instead
60
+ * <Chip label="Tag" onPress={() => {}} />
61
+ */
62
+ onClick?: () => void;
63
+
49
64
  /** Whether the chip is disabled */
50
65
  disabled?: boolean;
51
66