@idealyst/components 1.1.8 → 1.2.0

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 (46) hide show
  1. package/package.json +3 -3
  2. package/plugin/web.js +280 -532
  3. package/src/Accordion/Accordion.web.tsx +1 -3
  4. package/src/Alert/Alert.web.tsx +3 -4
  5. package/src/Badge/Badge.web.tsx +8 -15
  6. package/src/Breadcrumb/Breadcrumb.web.tsx +4 -8
  7. package/src/Button/Button.native.tsx +14 -21
  8. package/src/Button/Button.styles.tsx +15 -0
  9. package/src/Button/Button.web.tsx +9 -19
  10. package/src/Checkbox/Checkbox.web.tsx +1 -2
  11. package/src/Chip/Chip.web.tsx +3 -5
  12. package/src/Dialog/Dialog.web.tsx +3 -3
  13. package/src/Dialog/types.ts +1 -1
  14. package/src/Icon/Icon.web.tsx +22 -17
  15. package/src/Icon/IconRegistry.native.ts +41 -0
  16. package/src/Icon/IconRegistry.ts +107 -0
  17. package/src/Icon/IconSvg/IconSvg.web.tsx +26 -5
  18. package/src/Icon/icon-resolver.ts +12 -43
  19. package/src/Icon/index.native.ts +2 -1
  20. package/src/Icon/index.ts +1 -0
  21. package/src/Icon/index.web.ts +1 -0
  22. package/src/Input/Input.styles.tsx +56 -83
  23. package/src/Input/Input.web.tsx +5 -8
  24. package/src/List/ListItem.native.tsx +6 -7
  25. package/src/List/ListItem.web.tsx +3 -3
  26. package/src/Menu/MenuItem.web.tsx +3 -5
  27. package/src/Screen/Screen.native.tsx +1 -1
  28. package/src/Screen/Screen.styles.tsx +3 -6
  29. package/src/Screen/Screen.web.tsx +1 -1
  30. package/src/Select/Select.styles.tsx +31 -48
  31. package/src/Select/Select.web.tsx +45 -33
  32. package/src/Slider/Slider.web.tsx +2 -4
  33. package/src/Switch/Switch.native.tsx +2 -2
  34. package/src/Switch/Switch.web.tsx +2 -3
  35. package/src/Table/Table.native.tsx +168 -65
  36. package/src/Table/Table.styles.tsx +26 -33
  37. package/src/Table/Table.web.tsx +169 -70
  38. package/src/Text/Text.web.tsx +1 -0
  39. package/src/TextArea/TextArea.native.tsx +21 -8
  40. package/src/TextArea/TextArea.styles.tsx +15 -27
  41. package/src/TextArea/TextArea.web.tsx +17 -6
  42. package/src/View/View.native.tsx +33 -3
  43. package/src/View/View.web.tsx +4 -21
  44. package/src/View/types.ts +31 -3
  45. package/src/examples/ButtonExamples.tsx +20 -0
  46. package/src/index.ts +1 -1
@@ -2,7 +2,6 @@ import React, { forwardRef, useEffect, useRef, useState } from 'react';
2
2
  // @ts-ignore - web-specific import
3
3
  import { getWebProps } from 'react-native-unistyles/web';
4
4
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
5
- import { resolveIconPath } from '../Icon/icon-resolver';
6
5
  import useMergeRefs from '../hooks/useMergeRefs';
7
6
  import { PositionedPortal } from '../internal/PositionedPortal';
8
7
  import { selectStyles } from './Select.styles';
@@ -175,14 +174,31 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
175
174
  setFocusedIndex(0);
176
175
  };
177
176
 
178
- const containerWebProps = getWebProps([
179
- (selectStyles.container as any)({}),
180
- style as any
181
- ]);
177
+ // Get dynamic styles - call as functions for theme reactivity
178
+ const containerStyle = (selectStyles.container as any)({});
179
+ const labelStyle = (selectStyles.label as any)({});
180
+ const triggerStyle = (selectStyles.trigger as any)({ type, intent, disabled, error, focused: isOpen });
181
+ const triggerContentStyle = (selectStyles.triggerContent as any)({});
182
+ const triggerTextStyle = (selectStyles.triggerText as any)({});
183
+ const placeholderStyle = (selectStyles.placeholder as any)({});
184
+ const iconStyle = (selectStyles.icon as any)({});
185
+ const chevronStyle = (selectStyles.chevron as any)({});
186
+ const chevronOpenStyle = (selectStyles.chevronOpen as any)({});
187
+ const dropdownStyle = (selectStyles.dropdown as any)({});
188
+ const searchContainerStyle = (selectStyles.searchContainer as any)({});
189
+ const searchInputStyle = (selectStyles.searchInput as any)({});
190
+ const optionsListStyle = (selectStyles.optionsList as any)({});
191
+ const optionStyle = (selectStyles.option as any)({});
192
+ const optionFocusedStyle = (selectStyles.optionFocused as any)({});
193
+ const optionDisabledStyle = (selectStyles.optionDisabled as any)({});
194
+ const optionContentStyle = (selectStyles.optionContent as any)({});
195
+ const optionIconStyle = (selectStyles.optionIcon as any)({});
196
+ const optionTextStyle = (selectStyles.optionText as any)({});
197
+ const optionTextDisabledStyle = (selectStyles.optionTextDisabled as any)({});
198
+ const helperTextStyle = (selectStyles.helperText as any)({ error });
182
199
 
183
- const triggerWebProps = getWebProps([
184
- (selectStyles.trigger as any)({ type, intent }),
185
- ]);
200
+ const containerWebProps = getWebProps([containerStyle, style as any]);
201
+ const triggerWebProps = getWebProps([triggerStyle]);
186
202
 
187
203
  const handleClose = () => {
188
204
  setIsOpen(false);
@@ -194,7 +210,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
194
210
  return (
195
211
  <div {...containerWebProps} ref={mergedRef} id={id} data-testid={testID}>
196
212
  {label && (
197
- <label {...getWebProps([selectStyles.label])}>
213
+ <label {...getWebProps([labelStyle])}>
198
214
  {label}
199
215
  </label>
200
216
  )}
@@ -211,15 +227,15 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
211
227
  aria-haspopup="listbox"
212
228
  type="button"
213
229
  >
214
- <div {...getWebProps([selectStyles.triggerContent])}>
230
+ <div {...getWebProps([triggerContentStyle])}>
215
231
  {selectedOption?.icon && (
216
- <span {...getWebProps([selectStyles.icon])}>
232
+ <span {...getWebProps([iconStyle])}>
217
233
  {selectedOption.icon}
218
234
  </span>
219
235
  )}
220
236
  <span
221
237
  {...getWebProps([
222
- selectedOption ? selectStyles.triggerText : selectStyles.placeholder
238
+ selectedOption ? triggerTextStyle : placeholderStyle
223
239
  ])}
224
240
  >
225
241
  {selectedOption ? selectedOption.label : placeholder}
@@ -227,10 +243,10 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
227
243
  </div>
228
244
 
229
245
  <IconSvg
230
- path={resolveIconPath('chevron-down')}
246
+ name="chevron-down"
231
247
  {...getWebProps([
232
- selectStyles.chevron,
233
- isOpen && selectStyles.chevronOpen
248
+ chevronStyle,
249
+ isOpen && chevronOpenStyle
234
250
  ])}
235
251
  aria-label="chevron-down"
236
252
  />
@@ -247,7 +263,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
247
263
  zIndex={1000}
248
264
  >
249
265
  <div
250
- {...getWebProps([(selectStyles.dropdown as any)({})])}
266
+ {...getWebProps([dropdownStyle])}
251
267
  style={{
252
268
  maxHeight: maxHeight,
253
269
  position: 'relative',
@@ -258,19 +274,19 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
258
274
  role="listbox"
259
275
  >
260
276
  {searchable && (
261
- <div {...getWebProps([selectStyles.searchContainer])}>
277
+ <div {...getWebProps([searchContainerStyle])}>
262
278
  <input
263
279
  ref={searchInputRef}
264
280
  type="text"
265
281
  placeholder="Search options..."
266
282
  value={searchTerm}
267
283
  onChange={handleSearchChange}
268
- {...getWebProps([selectStyles.searchInput])}
284
+ {...getWebProps([searchInputStyle])}
269
285
  />
270
286
  </div>
271
287
  )}
272
288
 
273
- <div {...getWebProps([selectStyles.optionsList])}>
289
+ <div {...getWebProps([optionsListStyle])}>
274
290
  {filteredOptions.map((option, index) => {
275
291
  const isFocused = index === focusedIndex;
276
292
 
@@ -282,20 +298,20 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
282
298
  aria-selected={option.value === value}
283
299
  onMouseEnter={() => setFocusedIndex(index)}
284
300
  {...getWebProps([
285
- (selectStyles.option as any)({}),
286
- isFocused && selectStyles.optionFocused,
287
- option.disabled && selectStyles.optionDisabled,
301
+ optionStyle,
302
+ isFocused && optionFocusedStyle,
303
+ option.disabled && optionDisabledStyle,
288
304
  ])}
289
305
  >
290
- <div {...getWebProps([selectStyles.optionContent])}>
306
+ <div {...getWebProps([optionContentStyle])}>
291
307
  {option.icon && (
292
- <span {...getWebProps([selectStyles.optionIcon])}>
308
+ <span {...getWebProps([optionIconStyle])}>
293
309
  {option.icon}
294
310
  </span>
295
311
  )}
296
312
  <span {...getWebProps([
297
- selectStyles.optionText,
298
- option.disabled && selectStyles.optionTextDisabled
313
+ optionTextStyle,
314
+ option.disabled && optionTextDisabledStyle
299
315
  ])}>
300
316
  {option.label}
301
317
  </span>
@@ -305,8 +321,8 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
305
321
  })}
306
322
 
307
323
  {filteredOptions.length === 0 && (
308
- <div {...getWebProps([(selectStyles.option as any)({})])} style={{ cursor: 'default' }}>
309
- <span {...getWebProps([selectStyles.optionText])}>
324
+ <div {...getWebProps([optionStyle])} style={{ cursor: 'default' }}>
325
+ <span {...getWebProps([optionTextStyle])}>
310
326
  No options found
311
327
  </span>
312
328
  </div>
@@ -316,11 +332,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
316
332
  </PositionedPortal>
317
333
 
318
334
  {helperText && (
319
- <div
320
- {...getWebProps([
321
- selectStyles.helperText,
322
- ])}
323
- >
335
+ <div {...getWebProps([helperTextStyle])}>
324
336
  {helperText}
325
337
  </div>
326
338
  )}
@@ -3,7 +3,7 @@ import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { sliderStyles } from './Slider.styles';
4
4
  import type { SliderProps } from './types';
5
5
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
- import { resolveIconPath, isIconName } from '../Icon/icon-resolver';
6
+ import { isIconName } from '../Icon/icon-resolver';
7
7
  import useMergeRefs from '../hooks/useMergeRefs';
8
8
  import { getWebRangeAriaProps, generateAccessibilityId, SLIDER_KEYS } from '../utils/accessibility';
9
9
 
@@ -223,11 +223,9 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(({
223
223
  if (!icon) return null;
224
224
 
225
225
  if (isIconName(icon)) {
226
- // Resolve icon name to path and render with IconSvg
227
- const iconPath = resolveIconPath(icon);
228
226
  return (
229
227
  <IconSvg
230
- path={iconPath}
228
+ name={icon}
231
229
  {...thumbIconProps}
232
230
  aria-label={icon}
233
231
  />
@@ -44,8 +44,8 @@ const Switch = forwardRef<ComponentRef<typeof Pressable>, SwitchProps>(({
44
44
 
45
45
  React.useEffect(() => {
46
46
  progress.value = withSpring(checked ? 1 : 0, {
47
- damping: 15,
48
- stiffness: 150,
47
+ damping: 40,
48
+ stiffness: 200,
49
49
  });
50
50
  }, [checked, progress]);
51
51
 
@@ -3,7 +3,7 @@ import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { switchStyles } from './Switch.styles';
4
4
  import type { SwitchProps } from './types';
5
5
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
- import { resolveIconPath, isIconName } from '../Icon/icon-resolver';
6
+ import { isIconName } from '../Icon/icon-resolver';
7
7
  import useMergeRefs from '../hooks/useMergeRefs';
8
8
  import { getWebSelectionAriaProps, generateAccessibilityId } from '../utils/accessibility';
9
9
 
@@ -93,10 +93,9 @@ const Switch = forwardRef<HTMLDivElement | HTMLButtonElement, SwitchProps>(({
93
93
  if (!iconToRender) return null;
94
94
 
95
95
  if (isIconName(iconToRender)) {
96
- const iconPath = resolveIconPath(iconToRender);
97
96
  return (
98
97
  <IconSvg
99
- path={iconPath}
98
+ name={iconToRender}
100
99
  {...thumbIconProps}
101
100
  aria-label={iconToRender}
102
101
  />
@@ -1,9 +1,140 @@
1
- import React, { forwardRef, useMemo } from 'react';
1
+ import React, { forwardRef, useMemo, ReactNode } from 'react';
2
2
  import { View, ScrollView, Text, TouchableOpacity } from 'react-native';
3
3
  import { tableStyles } from './Table.styles';
4
- import type { TableProps, TableColumn } from './types';
4
+ import type { TableProps, TableColumn, TableType, TableSizeVariant, TableAlignVariant } from './types';
5
5
  import { getNativeAccessibilityProps } from '../utils/accessibility';
6
6
 
7
+ // ============================================================================
8
+ // Sub-component Props
9
+ // ============================================================================
10
+
11
+ interface TRProps {
12
+ children: ReactNode;
13
+ size?: TableSizeVariant;
14
+ type?: TableType;
15
+ clickable?: boolean;
16
+ onPress?: () => void;
17
+ testID?: string;
18
+ }
19
+
20
+ interface THProps {
21
+ children: ReactNode;
22
+ size?: TableSizeVariant;
23
+ type?: TableType;
24
+ align?: TableAlignVariant;
25
+ width?: number | string;
26
+ }
27
+
28
+ interface TDProps {
29
+ children: ReactNode;
30
+ size?: TableSizeVariant;
31
+ type?: TableType;
32
+ align?: TableAlignVariant;
33
+ width?: number | string;
34
+ }
35
+
36
+ // ============================================================================
37
+ // TR Component
38
+ // ============================================================================
39
+
40
+ function TR({
41
+ children,
42
+ size = 'md',
43
+ type = 'standard',
44
+ clickable = false,
45
+ onPress,
46
+ testID,
47
+ }: TRProps) {
48
+ tableStyles.useVariants({
49
+ size,
50
+ type,
51
+ clickable,
52
+ });
53
+
54
+ const rowStyle = (tableStyles.row as any)({});
55
+ const RowComponent = clickable ? TouchableOpacity : View;
56
+
57
+ return (
58
+ <RowComponent
59
+ style={rowStyle}
60
+ onPress={clickable ? onPress : undefined}
61
+ testID={testID}
62
+ >
63
+ <View style={{ flexDirection: 'row' }}>
64
+ {children}
65
+ </View>
66
+ </RowComponent>
67
+ );
68
+ }
69
+
70
+ // ============================================================================
71
+ // TH Component
72
+ // ============================================================================
73
+
74
+ function TH({
75
+ children,
76
+ size = 'md',
77
+ type = 'standard',
78
+ align = 'left',
79
+ width,
80
+ }: THProps) {
81
+ tableStyles.useVariants({
82
+ size,
83
+ type,
84
+ align,
85
+ });
86
+
87
+ const headerCellStyle = (tableStyles.headerCell as any)({});
88
+
89
+ return (
90
+ <View
91
+ style={[
92
+ headerCellStyle,
93
+ { width, flex: width ? undefined : 1 },
94
+ ]}
95
+ >
96
+ <Text style={headerCellStyle}>
97
+ {children}
98
+ </Text>
99
+ </View>
100
+ );
101
+ }
102
+
103
+ // ============================================================================
104
+ // TD Component
105
+ // ============================================================================
106
+
107
+ function TD({
108
+ children,
109
+ size = 'md',
110
+ type = 'standard',
111
+ align = 'left',
112
+ width,
113
+ }: TDProps) {
114
+ tableStyles.useVariants({
115
+ size,
116
+ type,
117
+ align,
118
+ });
119
+
120
+ const cellStyle = (tableStyles.cell as any)({});
121
+
122
+ return (
123
+ <View
124
+ style={[
125
+ cellStyle,
126
+ { width, flex: width ? undefined : 1 },
127
+ ]}
128
+ >
129
+ {children}
130
+ </View>
131
+ );
132
+ }
133
+
134
+ // ============================================================================
135
+ // Main Table Component
136
+ // ============================================================================
137
+
7
138
  function TableInner<T = any>({
8
139
  columns,
9
140
  data,
@@ -37,7 +168,8 @@ function TableInner<T = any>({
37
168
  accessibilityHidden,
38
169
  });
39
170
  }, [accessibilityLabel, accessibilityHint, accessibilityRole, accessibilityHidden]);
40
- // Apply variants
171
+
172
+ // Apply variants for container
41
173
  tableStyles.useVariants({
42
174
  type,
43
175
  size,
@@ -55,8 +187,6 @@ function TableInner<T = any>({
55
187
  const tableStyle = (tableStyles.table as any)({});
56
188
  const theadStyle = (tableStyles.thead as any)({});
57
189
  const tbodyStyle = (tableStyles.tbody as any)({});
58
- const rowStyle = (tableStyles.row as any)({});
59
- const headerCellStyle = (tableStyles.headerCell as any)({});
60
190
  const cellStyle = (tableStyles.cell as any)({});
61
191
 
62
192
  // Helper to get cell value
@@ -84,71 +214,44 @@ function TableInner<T = any>({
84
214
  {/* Header */}
85
215
  <View style={theadStyle}>
86
216
  <View style={{ flexDirection: 'row' }}>
87
- {columns.map((column) => {
88
- tableStyles.useVariants({
89
- size,
90
- align: column.align || 'left',
91
- type,
92
- });
93
-
94
- return (
95
- <View
96
- key={column.key}
97
- style={[
98
- headerCellStyle,
99
- { width: column.width, flex: column.width ? undefined : 1 },
100
- ]}
101
- >
102
- <Text style={headerCellStyle}>
103
- {column.title}
104
- </Text>
105
- </View>
106
- );
107
- })}
217
+ {columns.map((column) => (
218
+ <TH
219
+ key={column.key}
220
+ size={size}
221
+ type={type}
222
+ align={column.align}
223
+ width={column.width}
224
+ >
225
+ {column.title}
226
+ </TH>
227
+ ))}
108
228
  </View>
109
229
  </View>
110
230
 
111
231
  {/* Body */}
112
232
  <View style={tbodyStyle}>
113
- {data.map((row, rowIndex) => {
114
- tableStyles.useVariants({
115
- type,
116
- clickable: isClickable,
117
- });
118
-
119
- const RowComponent = isClickable ? TouchableOpacity : View;
120
-
121
- return (
122
- <RowComponent
123
- key={rowIndex}
124
- style={rowStyle}
125
- onPress={isClickable ? () => onRowPress?.(row, rowIndex) : undefined}
126
- testID={`${testID}-row-${rowIndex}`}
127
- >
128
- <View style={{ flexDirection: 'row' }}>
129
- {columns.map((column) => {
130
- tableStyles.useVariants({
131
- size,
132
- align: column.align || 'left',
133
- type,
134
- });
135
-
136
- return (
137
- <View
138
- key={column.key}
139
- style={[
140
- cellStyle,
141
- { width: column.width, flex: column.width ? undefined : 1 },
142
- ]}
143
- >
144
- {getCellValue(column, row, rowIndex)}
145
- </View>
146
- );
147
- })}
148
- </View>
149
- </RowComponent>
150
- );
151
- })}
233
+ {data.map((row, rowIndex) => (
234
+ <TR
235
+ key={rowIndex}
236
+ size={size}
237
+ type={type}
238
+ clickable={isClickable}
239
+ onPress={() => onRowPress?.(row, rowIndex)}
240
+ testID={testID ? `${testID}-row-${rowIndex}` : undefined}
241
+ >
242
+ {columns.map((column) => (
243
+ <TD
244
+ key={column.key}
245
+ size={size}
246
+ type={type}
247
+ align={column.align}
248
+ width={column.width}
249
+ >
250
+ {getCellValue(column, row, rowIndex)}
251
+ </TD>
252
+ ))}
253
+ </TR>
254
+ ))}
152
255
  </View>
153
256
  </View>
154
257
  </ScrollView>
@@ -113,41 +113,34 @@ export const tableStyles = defineStyle('Table', (theme: Theme) => ({
113
113
  } as const;
114
114
  },
115
115
 
116
- headerCell: ({ type = 'standard', align = 'left' }: TableDynamicProps) => {
117
- const alignStyles = {
118
- left: { textAlign: 'left' as const, justifyContent: 'flex-start' as const },
119
- center: { textAlign: 'center' as const, justifyContent: 'center' as const },
120
- right: { textAlign: 'right' as const, justifyContent: 'flex-end' as const },
121
- }[align];
122
-
123
- const borderStyles = type === 'bordered' ? {
124
- borderRightWidth: 1,
125
- borderRightColor: theme.colors.border.primary,
126
- } : {};
127
-
128
- return {
129
- flexDirection: 'row' as const,
130
- alignItems: 'center' as const,
131
- fontWeight: '600' as const,
132
- color: theme.colors.text.primary,
133
- borderBottomWidth: 2,
134
- borderBottomColor: theme.colors.border.primary,
135
- ...alignStyles,
136
- ...borderStyles,
137
- variants: {
138
- size: {
139
- padding: theme.sizes.$table.padding,
140
- fontSize: theme.sizes.$table.fontSize,
141
- lineHeight: theme.sizes.$table.lineHeight,
142
- },
116
+ headerCell: ({ type = 'standard', align = 'left' }: TableDynamicProps) => ({
117
+ flexDirection: 'row' as const,
118
+ alignItems: 'center' as const,
119
+ fontWeight: '600' as const,
120
+ color: theme.colors.text.primary,
121
+ borderBottomWidth: 2,
122
+ borderBottomColor: theme.colors.border.primary,
123
+ variants: {
124
+ type: {
125
+ bordered: { borderRightWidth: 1, borderRightColor: theme.colors.border.primary },
143
126
  },
144
- _web: {
145
- borderBottom: `2px solid ${theme.colors.border.primary}`,
146
- borderRight: type === 'bordered' ? `1px solid ${theme.colors.border.primary}` : undefined,
147
- ':last-child': type === 'bordered' ? { borderRight: 'none' } : {},
127
+ align: {
128
+ left: { textAlign: 'left' as const, justifyContent: 'flex-start' as const },
129
+ center: { textAlign: 'center' as const, justifyContent: 'center' as const },
130
+ right: { textAlign: 'right' as const, justifyContent: 'flex-end' as const },
148
131
  },
149
- } as const;
150
- },
132
+ size: {
133
+ padding: theme.sizes.$table.padding,
134
+ fontSize: theme.sizes.$table.fontSize,
135
+ lineHeight: theme.sizes.$table.lineHeight,
136
+ },
137
+ },
138
+ _web: {
139
+ borderBottom: `2px solid ${theme.colors.border.primary}`,
140
+ borderRight: type === 'bordered' ? `1px solid ${theme.colors.border.primary}` : undefined,
141
+ ':last-child': type === 'bordered' ? { borderRight: 'none' } : {},
142
+ },
143
+ }),
151
144
 
152
145
  cell: ({ type = 'standard', align = 'left' }: TableDynamicProps) => {
153
146
  const alignStyles = {