@idealyst/components 1.2.14 → 1.2.16

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 (50) hide show
  1. package/package.json +3 -3
  2. package/src/Accordion/Accordion.web.tsx +1 -1
  3. package/src/Alert/Alert.native.tsx +1 -1
  4. package/src/Alert/Alert.web.tsx +1 -1
  5. package/src/Badge/Badge.web.tsx +6 -2
  6. package/src/Badge/types.ts +5 -0
  7. package/src/Button/Button.native.tsx +3 -3
  8. package/src/Button/Button.web.tsx +5 -1
  9. package/src/Button/types.ts +5 -0
  10. package/src/Card/Card.web.tsx +4 -1
  11. package/src/Card/types.ts +5 -0
  12. package/src/Dialog/Dialog.native.tsx +1 -1
  13. package/src/Divider/Divider.web.tsx +2 -2
  14. package/src/Icon/Icon.web.tsx +2 -2
  15. package/src/Image/Image.styles.tsx +5 -5
  16. package/src/Image/Image.web.tsx +3 -3
  17. package/src/List/List.native.tsx +1 -2
  18. package/src/List/List.web.tsx +1 -2
  19. package/src/List/ListSection.web.tsx +3 -3
  20. package/src/Menu/Menu.web.tsx +8 -10
  21. package/src/Menu/MenuItem.web.tsx +1 -1
  22. package/src/Popover/Popover.web.tsx +1 -1
  23. package/src/Pressable/Pressable.web.tsx +1 -1
  24. package/src/Progress/Progress.styles.tsx +76 -30
  25. package/src/Progress/Progress.web.tsx +13 -15
  26. package/src/SVGImage/SVGImage.web.tsx +1 -1
  27. package/src/Select/Select.web.tsx +2 -2
  28. package/src/Slider/Slider.styles.tsx +131 -44
  29. package/src/Slider/Slider.web.tsx +22 -22
  30. package/src/Text/Text.web.tsx +29 -3
  31. package/src/Text/types.ts +14 -1
  32. package/src/TextArea/TextArea.styles.tsx +96 -57
  33. package/src/TextArea/TextArea.web.tsx +19 -28
  34. package/src/Tooltip/Tooltip.web.tsx +3 -3
  35. package/src/Video/Video.styles.tsx +3 -3
  36. package/src/Video/Video.web.tsx +1 -1
  37. package/src/View/View.styles.tsx +2 -2
  38. package/src/View/View.web.tsx +95 -9
  39. package/src/View/types.ts +5 -1
  40. package/src/examples/ViewExamples.tsx +34 -0
  41. package/src/extensions/index.ts +0 -7
  42. package/src/hooks/useMergeRefs.ts +12 -6
  43. package/src/utils/accessibility/keyboardPatterns.ts +4 -0
  44. package/src/utils/accessibility/types.ts +5 -1
  45. package/src/utils/accessibility/useAnnounce.ts +1 -1
  46. package/src/utils/accessibility/useKeyboardNavigation.ts +1 -1
  47. package/src/utils/index.ts +0 -3
  48. package/src/utils/viewStyleProps.ts +2 -0
  49. package/src/extensions/applyExtension.ts +0 -210
  50. package/src/utils/buildSizeVariants.ts +0 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.2.14",
3
+ "version": "1.2.16",
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.14",
59
+ "@idealyst/theme": "^1.2.16",
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,7 +106,7 @@
106
106
  }
107
107
  },
108
108
  "devDependencies": {
109
- "@idealyst/theme": "^1.2.14",
109
+ "@idealyst/theme": "^1.2.16",
110
110
  "@idealyst/tooling": "^1.2.4",
111
111
  "@mdi/react": "^1.6.1",
112
112
  "@types/react": "^19.1.0",
@@ -47,7 +47,7 @@ const AccordionItem: React.FC<AccordionItemProps> = ({
47
47
  const itemStyle = (accordionStyles.item as any)({ type, isLast });
48
48
  const itemProps = getWebProps([itemStyle]);
49
49
  const headerProps = getWebProps([(accordionStyles.header as any)({})]);
50
- const titleProps = getWebProps([accordionStyles.title]);
50
+ const titleProps = getWebProps([accordionStyles.title as any]);
51
51
  const iconProps = getWebProps([(accordionStyles.icon as any)({})]);
52
52
  // Pass expanded state to get correct maxHeight from styles
53
53
  const contentProps = getWebProps([(accordionStyles.content as any)({ expanded: isExpanded })]);
@@ -6,7 +6,7 @@ import { isIconName } from '../Icon/icon-resolver';
6
6
  import type { AlertProps } from './types';
7
7
 
8
8
  // Default icon names for each intent
9
- const defaultIcons = {
9
+ const defaultIcons: Record<string, string> = {
10
10
  primary: 'information',
11
11
  success: 'check-circle',
12
12
  error: 'alert-circle',
@@ -7,7 +7,7 @@ import { isIconName } from '../Icon/icon-resolver';
7
7
  import useMergeRefs from '../hooks/useMergeRefs';
8
8
 
9
9
  // Default icons for each intent
10
- const defaultIcons = {
10
+ const defaultIcons: Record<string, string> = {
11
11
  primary: 'information',
12
12
  success: 'check-circle',
13
13
  error: 'alert-circle',
@@ -13,7 +13,8 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>((props, ref) => {
13
13
  const {
14
14
  children,
15
15
  size = 'md',
16
- type = 'filled',
16
+ type: typeProp,
17
+ variant,
17
18
  color = 'blue',
18
19
  icon,
19
20
  style,
@@ -21,13 +22,16 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>((props, ref) => {
21
22
  id,
22
23
  } = props;
23
24
 
25
+ // variant is an alias for type - variant takes precedence if both are set
26
+ const type = variant ?? typeProp ?? 'filled';
27
+
24
28
  badgeStyles.useVariants({
25
29
  size,
26
30
  type,
27
31
  });
28
32
 
29
33
  const badgeStyle = (badgeStyles.badge as any)({ color });
30
- const contentStyle = badgeStyles.content;
34
+ const contentStyle = badgeStyles.content as any;
31
35
  const textStyle = (badgeStyles.text as any)({ color });
32
36
 
33
37
  const badgeProps = getWebProps([badgeStyle]);
@@ -29,6 +29,11 @@ export interface BadgeProps extends BaseProps {
29
29
  */
30
30
  type?: BadgeType;
31
31
 
32
+ /**
33
+ * Alias for type - the visual style variant of the badge
34
+ */
35
+ variant?: BadgeType;
36
+
32
37
  /**
33
38
  * The color scheme of the badge
34
39
  */
@@ -68,14 +68,14 @@ const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonProps>((p
68
68
  };
69
69
 
70
70
  // Map button size to icon size
71
- const iconSizeMap = {
71
+ const iconSizeMap: Record<string, number> = {
72
72
  xs: 12,
73
73
  sm: 14,
74
74
  md: 16,
75
75
  lg: 18,
76
76
  xl: 20,
77
- } as const;
78
- const iconSize = iconSizeMap[size];
77
+ };
78
+ const iconSize = iconSizeMap[size] ?? 16;
79
79
 
80
80
 
81
81
  // Use children if available, otherwise use title
@@ -17,7 +17,8 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
17
17
  onPress,
18
18
  disabled = false,
19
19
  loading = false,
20
- type = 'contained',
20
+ type: typeProp,
21
+ variant,
21
22
  intent = 'primary',
22
23
  size = 'md',
23
24
  gradient,
@@ -41,6 +42,9 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
41
42
  accessibilityHasPopup,
42
43
  } = props;
43
44
 
45
+ // variant is an alias for type - variant takes precedence if both are set
46
+ const type = variant ?? typeProp ?? 'contained';
47
+
44
48
  // Button is effectively disabled when loading
45
49
  const isDisabled = disabled || loading;
46
50
 
@@ -48,6 +48,11 @@ export interface ButtonProps extends BaseProps, InteractiveAccessibilityProps {
48
48
  */
49
49
  type?: ButtonType;
50
50
 
51
+ /**
52
+ * Alias for type - the visual style variant of the button
53
+ */
54
+ variant?: ButtonType;
55
+
51
56
  /**
52
57
  * The intent/color scheme of the button
53
58
  */
@@ -11,7 +11,8 @@ import { getWebInteractiveAriaProps } from '../utils/accessibility';
11
11
  */
12
12
  const Card = forwardRef<HTMLDivElement | HTMLButtonElement, CardProps>(({
13
13
  children,
14
- type = 'elevated',
14
+ type: typeProp,
15
+ variant,
15
16
  radius = 'md',
16
17
  intent,
17
18
  clickable = false,
@@ -36,6 +37,8 @@ const Card = forwardRef<HTMLDivElement | HTMLButtonElement, CardProps>(({
36
37
  accessibilityRole,
37
38
  accessibilityPressed,
38
39
  }, ref) => {
40
+ // variant is an alias for type - variant takes precedence if both are set
41
+ const type = variant ?? typeProp ?? 'elevated';
39
42
  // Generate ARIA props
40
43
  const ariaProps = useMemo(() => {
41
44
  return getWebInteractiveAriaProps({
package/src/Card/types.ts CHANGED
@@ -24,6 +24,11 @@ export interface CardProps extends ContainerStyleProps, InteractiveAccessibility
24
24
  */
25
25
  type?: CardType;
26
26
 
27
+ /**
28
+ * Alias for type - the visual style variant of the card
29
+ */
30
+ variant?: CardType;
31
+
27
32
  /**
28
33
  * The border radius of the card
29
34
  */
@@ -11,7 +11,7 @@ const Dialog = forwardRef<View, DialogProps>(({
11
11
  title,
12
12
  children,
13
13
  size = 'md',
14
- type = 'standard',
14
+ type = 'default',
15
15
  showCloseButton = true,
16
16
  closeOnBackdropClick = true,
17
17
  animationType = 'fade',
@@ -45,8 +45,8 @@ const Divider = forwardRef<HTMLDivElement, DividerProps>(({
45
45
 
46
46
  // Generate web props
47
47
  const dividerProps = getWebProps([dividerStyle, style as any]);
48
- const containerProps = getWebProps([dividerStyles.container]);
49
- const contentProps = getWebProps([dividerStyles.content]);
48
+ const containerProps = getWebProps([dividerStyles.container as any]);
49
+ const contentProps = getWebProps([dividerStyles.content as any]);
50
50
  const lineProps = getWebProps([lineStyle]);
51
51
 
52
52
  const mergedDividerRef = useMergeRefs(ref, dividerProps.ref);
@@ -45,7 +45,8 @@ const Icon = forwardRef<HTMLSpanElement, IconProps>((props, ref) => {
45
45
  iconSize = size;
46
46
  } else {
47
47
  const themeSize = theme.sizes.icon[size as keyof typeof theme.sizes.icon];
48
- iconSize = typeof themeSize === 'number' ? themeSize : (themeSize?.width ?? 24);
48
+ const rawSize = typeof themeSize === 'number' ? themeSize : themeSize?.width;
49
+ iconSize = typeof rawSize === 'number' ? rawSize : 24;
49
50
  }
50
51
 
51
52
  // Compute color - priority: intent > color > textColor > default
@@ -70,7 +71,6 @@ const Icon = forwardRef<HTMLSpanElement, IconProps>((props, ref) => {
70
71
  ref={mergedRef}
71
72
  id={id}
72
73
  style={{
73
- ...iconProps.style,
74
74
  fontSize: iconSize,
75
75
  width: '1em',
76
76
  height: '1em',
@@ -20,7 +20,7 @@ export const imageStyles = defineStyle('Image', (theme: Theme) => ({
20
20
  container: (_props: ImageDynamicProps) => ({
21
21
  position: 'relative' as const,
22
22
  overflow: 'hidden' as const,
23
- backgroundColor: theme.colors['gray.200'],
23
+ backgroundColor: theme.colors.pallet.gray?.['200'] ?? theme.colors.surface.secondary,
24
24
  }),
25
25
 
26
26
  image: (_props: ImageDynamicProps) => ({
@@ -37,7 +37,7 @@ export const imageStyles = defineStyle('Image', (theme: Theme) => ({
37
37
  display: 'flex' as const,
38
38
  alignItems: 'center' as const,
39
39
  justifyContent: 'center' as const,
40
- backgroundColor: theme.colors['gray.200'],
40
+ backgroundColor: theme.colors.pallet.gray?.['200'] ?? theme.colors.surface.secondary,
41
41
  }),
42
42
 
43
43
  fallback: (_props: ImageDynamicProps) => ({
@@ -49,11 +49,11 @@ export const imageStyles = defineStyle('Image', (theme: Theme) => ({
49
49
  display: 'flex' as const,
50
50
  alignItems: 'center' as const,
51
51
  justifyContent: 'center' as const,
52
- backgroundColor: theme.colors['gray.300'],
53
- color: theme.colors['gray.600'],
52
+ backgroundColor: theme.colors.pallet.gray?.['300'] ?? theme.colors.surface.tertiary,
53
+ color: theme.colors.pallet.gray?.['600'] ?? theme.colors.text.secondary,
54
54
  }),
55
55
 
56
56
  loadingIndicator: (_props: ImageDynamicProps) => ({
57
- color: theme.colors['gray.600'],
57
+ color: theme.colors.pallet.gray?.['600'] ?? theme.colors.text.secondary,
58
58
  }),
59
59
  }));
@@ -50,15 +50,15 @@ const Image: React.FC<ImageProps> = ({
50
50
  ]);
51
51
 
52
52
  const imageProps = getWebProps([
53
- imageStyles.image,
53
+ imageStyles.image as any,
54
54
  {
55
55
  objectFit: objectFit,
56
56
  borderRadius: borderRadius ? `${borderRadius}px` : undefined,
57
57
  }
58
58
  ]);
59
59
 
60
- const placeholderProps = getWebProps([imageStyles.placeholder]);
61
- const fallbackProps = getWebProps([imageStyles.fallback]);
60
+ const placeholderProps = getWebProps([imageStyles.placeholder as any]);
61
+ const fallbackProps = getWebProps([imageStyles.fallback as any]);
62
62
 
63
63
  return (
64
64
  <div
@@ -64,9 +64,8 @@ const List = forwardRef<View, ListProps>(({
64
64
  const processedChildren = childArray.map((child, index) => {
65
65
  if (isValidElement(child)) {
66
66
  return cloneElement(child, {
67
- ...child.props,
68
67
  isLast: index === childArray.length - 1,
69
- });
68
+ } as Record<string, unknown>);
70
69
  }
71
70
  return child;
72
71
  });
@@ -52,9 +52,8 @@ const List: React.FC<ListProps> = ({
52
52
  const processedChildren = childArray.map((child, index) => {
53
53
  if (isValidElement(child)) {
54
54
  return cloneElement(child, {
55
- ...child.props,
56
55
  isLast: index === childArray.length - 1,
57
- });
56
+ } as Record<string, unknown>);
58
57
  }
59
58
  return child;
60
59
  });
@@ -10,9 +10,9 @@ const ListSection: React.FC<ListSectionProps> = ({
10
10
  style,
11
11
  testID,
12
12
  }) => {
13
- const sectionProps = getWebProps([listStyles.section, style as any]);
14
- const titleProps = getWebProps([listStyles.sectionTitle]);
15
- const contentProps = getWebProps([listStyles.sectionContent]);
13
+ const sectionProps = getWebProps([listStyles.section as any, style as any]);
14
+ const titleProps = getWebProps([listStyles.sectionTitle as any]);
15
+ const contentProps = getWebProps([listStyles.sectionContent as any]);
16
16
 
17
17
  return (
18
18
  <div {...sectionProps} data-testid={testID}>
@@ -5,7 +5,7 @@ import type { MenuProps } from './types';
5
5
  import MenuItem from './MenuItem.web';
6
6
  import useMergeRefs from '../hooks/useMergeRefs';
7
7
  import { PositionedPortal } from '../internal/PositionedPortal';
8
- import { getWebInteractiveAriaProps, generateAccessibilityId, MENU_KEYS } from '../utils/accessibility';
8
+ import { getWebInteractiveAriaProps, generateAccessibilityId, MENU_KEYS, matchesKey } from '../utils/accessibility';
9
9
 
10
10
  /**
11
11
  * Dropdown menu for actions and navigation triggered by a button or element.
@@ -64,9 +64,7 @@ const Menu = forwardRef<HTMLDivElement, MenuProps>(({
64
64
 
65
65
  // Keyboard navigation handler
66
66
  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
67
- const key = e.key;
68
-
69
- if (MENU_KEYS.close.includes(key)) {
67
+ if (matchesKey(e, MENU_KEYS.close)) {
70
68
  e.preventDefault();
71
69
  onOpenChange?.(false);
72
70
  // Return focus to trigger
@@ -78,16 +76,16 @@ const Menu = forwardRef<HTMLDivElement, MenuProps>(({
78
76
 
79
77
  let nextIndex = focusedIndex.current;
80
78
 
81
- if (MENU_KEYS.next.includes(key)) {
79
+ if (matchesKey(e, MENU_KEYS.next)) {
82
80
  e.preventDefault();
83
81
  nextIndex = focusedIndex.current < enabledItems.length - 1 ? focusedIndex.current + 1 : 0;
84
- } else if (MENU_KEYS.prev.includes(key)) {
82
+ } else if (matchesKey(e, MENU_KEYS.prev)) {
85
83
  e.preventDefault();
86
84
  nextIndex = focusedIndex.current > 0 ? focusedIndex.current - 1 : enabledItems.length - 1;
87
- } else if (MENU_KEYS.first.includes(key)) {
85
+ } else if (matchesKey(e, MENU_KEYS.first)) {
88
86
  e.preventDefault();
89
87
  nextIndex = 0;
90
- } else if (MENU_KEYS.last.includes(key)) {
88
+ } else if (matchesKey(e, MENU_KEYS.last)) {
91
89
  e.preventDefault();
92
90
  nextIndex = enabledItems.length - 1;
93
91
  }
@@ -119,7 +117,7 @@ const Menu = forwardRef<HTMLDivElement, MenuProps>(({
119
117
 
120
118
  const overlayProps = getWebProps([(menuStyles.overlay as any)({})]);
121
119
  const menuProps = getWebProps([(menuStyles.menu as any)({}), style as any]);
122
- const separatorProps = getWebProps([menuStyles.separator]);
120
+ const separatorProps = getWebProps([menuStyles.separator as any]);
123
121
 
124
122
  const handleTriggerClick = () => {
125
123
  onOpenChange?.(!open);
@@ -161,7 +159,7 @@ const Menu = forwardRef<HTMLDivElement, MenuProps>(({
161
159
 
162
160
  <PositionedPortal
163
161
  open={open}
164
- anchor={triggerRef}
162
+ anchor={triggerRef as React.RefObject<HTMLElement>}
165
163
  placement={placement}
166
164
  offset={4}
167
165
  onClickOutside={() => onOpenChange?.(false)}
@@ -67,7 +67,7 @@ const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(({ item, onPress,
67
67
  <button
68
68
  {...itemProps}
69
69
  ref={mergedRef}
70
- style={{ ...buttonResetStyles, ...itemProps.style }}
70
+ style={buttonResetStyles}
71
71
  onClick={() => onPress(item)}
72
72
  disabled={item.disabled}
73
73
  role="menuitem"
@@ -49,7 +49,7 @@ const Popover = forwardRef<HTMLDivElement, PopoverProps>(({
49
49
  popoverStyles.useVariants({});
50
50
 
51
51
  const containerProps = getWebProps([(popoverStyles.container as any)({})]);
52
- const contentProps = getWebProps([popoverStyles.content]);
52
+ const contentProps = getWebProps([popoverStyles.content as any]);
53
53
 
54
54
  const mergedPopoverRef = useMergeRefs(ref, popoverRef);
55
55
 
@@ -78,7 +78,7 @@ const Pressable = forwardRef<HTMLDivElement, PressableProps>(({
78
78
  id={id}
79
79
  role={accessibilityRole}
80
80
  tabIndex={disabled ? -1 : 0}
81
- style={{ ...baseStyle, ...webProps.style }}
81
+ style={baseStyle}
82
82
  onMouseDown={handleMouseDown}
83
83
  onMouseUp={handleMouseUp}
84
84
  onMouseLeave={handleMouseUp} // Handle mouse leave as press out
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Progress styles using defineStyle with $iterator expansion.
2
+ * Progress styles using static styles with variants.
3
3
  */
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
- import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
5
+ import { defineStyle, ThemeStyleWrapper, CompoundVariants } from '@idealyst/theme';
6
6
  import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
7
7
 
8
8
  // Required: Unistyles must see StyleSheet usage in original source to process this file
@@ -11,26 +11,34 @@ void StyleSheet;
11
11
  // Wrap theme for $iterator support
12
12
  type Theme = ThemeStyleWrapper<BaseTheme>;
13
13
 
14
- export type ProgressDynamicProps = {
15
- size?: Size;
16
- intent?: Intent;
17
- rounded?: boolean;
14
+ export type ProgressVariants = {
15
+ size: Size;
16
+ intent: Intent;
17
+ rounded: boolean;
18
18
  };
19
19
 
20
+ // Create intent variants dynamically from theme
21
+ function createIntentVariants(theme: Theme) {
22
+ const variants: Record<string, object> = {};
23
+ for (const intent in theme.intents) {
24
+ variants[intent] = {};
25
+ }
26
+ return variants;
27
+ }
28
+
20
29
  /**
21
- * Progress styles with intent-based coloring.
30
+ * Progress styles with static styles and variants.
22
31
  */
23
32
  export const progressStyles = defineStyle('Progress', (theme: Theme) => ({
24
- container: (_props: ProgressDynamicProps) => ({
33
+ container: {
25
34
  gap: 4 as const,
26
- }),
35
+ },
27
36
 
28
- linearTrack: (_props: ProgressDynamicProps) => ({
37
+ linearTrack: {
29
38
  backgroundColor: theme.colors.border.secondary,
30
39
  overflow: 'hidden' as const,
31
40
  position: 'relative' as const,
32
41
  variants: {
33
- // $iterator expands for each progress size
34
42
  size: {
35
43
  height: theme.sizes.$progress.linearHeight,
36
44
  },
@@ -39,36 +47,60 @@ export const progressStyles = defineStyle('Progress', (theme: Theme) => ({
39
47
  false: { borderRadius: 0 },
40
48
  },
41
49
  },
42
- }),
50
+ },
43
51
 
44
- linearBar: ({ intent = 'primary' }: ProgressDynamicProps) => ({
52
+ linearBar: {
45
53
  height: '100%' as const,
46
- backgroundColor: theme.intents[intent].primary,
47
54
  variants: {
48
55
  rounded: {
49
56
  true: { borderRadius: 9999 },
50
57
  false: { borderRadius: 0 },
51
58
  },
59
+ intent: createIntentVariants(theme),
52
60
  },
61
+ compoundVariants: (() => {
62
+ const cv: CompoundVariants<keyof ProgressVariants> = [];
63
+ for (const intent in theme.intents) {
64
+ cv.push({
65
+ intent,
66
+ styles: {
67
+ backgroundColor: theme.intents[intent as Intent].primary,
68
+ },
69
+ });
70
+ }
71
+ return cv;
72
+ })(),
53
73
  _web: {
54
74
  transition: 'width 0.3s ease' as const,
55
75
  },
56
- }),
76
+ },
57
77
 
58
- indeterminateBar: ({ intent = 'primary' }: ProgressDynamicProps) => ({
78
+ indeterminateBar: {
59
79
  position: 'absolute' as const,
60
80
  height: '100%' as const,
61
81
  width: '40%' as const,
62
- backgroundColor: theme.intents[intent].primary,
63
82
  variants: {
64
83
  rounded: {
65
84
  true: { borderRadius: 9999 },
66
85
  false: { borderRadius: 0 },
67
86
  },
87
+ intent: createIntentVariants(theme),
68
88
  },
69
- }),
89
+ compoundVariants: (() => {
90
+ const cv: CompoundVariants<keyof ProgressVariants> = [];
91
+ for (const intent in theme.intents) {
92
+ cv.push({
93
+ intent,
94
+ styles: {
95
+ backgroundColor: theme.intents[intent as Intent].primary,
96
+ },
97
+ });
98
+ }
99
+ return cv;
100
+ })(),
101
+ },
70
102
 
71
- circularContainer: (_props: ProgressDynamicProps) => ({
103
+ circularContainer: {
72
104
  alignItems: 'center' as const,
73
105
  justifyContent: 'center' as const,
74
106
  position: 'relative' as const,
@@ -78,21 +110,35 @@ export const progressStyles = defineStyle('Progress', (theme: Theme) => ({
78
110
  height: theme.sizes.$progress.circularSize,
79
111
  },
80
112
  },
81
- }),
113
+ },
82
114
 
83
- circularTrack: (_props: ProgressDynamicProps) => ({
115
+ circularTrack: {
84
116
  _web: {
85
117
  stroke: theme.colors.border.secondary,
86
118
  },
87
- }),
119
+ },
88
120
 
89
- circularBar: ({ intent = 'primary' }: ProgressDynamicProps) => ({
90
- _web: {
91
- stroke: theme.intents[intent].primary,
121
+ circularBar: {
122
+ variants: {
123
+ intent: createIntentVariants(theme),
92
124
  },
93
- }),
125
+ compoundVariants: (() => {
126
+ const cv: CompoundVariants<keyof ProgressVariants> = [];
127
+ for (const intent in theme.intents) {
128
+ cv.push({
129
+ intent,
130
+ styles: {
131
+ _web: {
132
+ stroke: theme.intents[intent as Intent].primary,
133
+ },
134
+ },
135
+ });
136
+ }
137
+ return cv;
138
+ })(),
139
+ },
94
140
 
95
- label: (_props: ProgressDynamicProps) => ({
141
+ label: {
96
142
  color: theme.colors.text.primary,
97
143
  textAlign: 'center' as const,
98
144
  variants: {
@@ -100,9 +146,9 @@ export const progressStyles = defineStyle('Progress', (theme: Theme) => ({
100
146
  fontSize: theme.sizes.$progress.labelFontSize,
101
147
  },
102
148
  },
103
- }),
149
+ },
104
150
 
105
- circularLabel: (_props: ProgressDynamicProps) => ({
151
+ circularLabel: {
106
152
  position: 'absolute' as const,
107
153
  fontWeight: '600' as const,
108
154
  color: theme.colors.text.primary,
@@ -111,5 +157,5 @@ export const progressStyles = defineStyle('Progress', (theme: Theme) => ({
111
157
  fontSize: theme.sizes.$progress.circularLabelFontSize,
112
158
  },
113
159
  },
114
- }),
160
+ },
115
161
  }));
@@ -23,21 +23,19 @@ const Progress: React.FC<ProgressProps> = ({
23
23
  }) => {
24
24
  const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
25
25
 
26
- // Apply variants (for size and rounded)
26
+ // Apply variants (for size, intent, and rounded)
27
27
  progressStyles.useVariants({
28
28
  size,
29
+ intent,
29
30
  rounded,
30
31
  });
31
32
 
32
- // Compute dynamic styles with intent
33
- const dynamicProps = { intent };
34
-
35
33
  // Linear progress
36
- const containerProps = getWebProps([(progressStyles.container as any)({}), style as any]);
37
- const trackProps = getWebProps([(progressStyles.linearTrack as any)({})]);
38
- const barProps = getWebProps([(progressStyles.linearBar as any)(dynamicProps), { width: `${percentage}%` }]);
39
- const indeterminateProps = getWebProps([(progressStyles.indeterminateBar as any)(dynamicProps)]);
40
- const labelProps = getWebProps([progressStyles.label]);
34
+ const containerProps = getWebProps([progressStyles.container as any, style as any]);
35
+ const trackProps = getWebProps([progressStyles.linearTrack as any]);
36
+ const barProps = getWebProps([progressStyles.linearBar as any, { width: `${percentage}%` }]);
37
+ const indeterminateProps = getWebProps([progressStyles.indeterminateBar as any]);
38
+ const labelProps = getWebProps([progressStyles.label as any]);
41
39
 
42
40
  const getCircularSize = () => {
43
41
  if (size === 'sm') return 32;
@@ -53,13 +51,13 @@ const Progress: React.FC<ProgressProps> = ({
53
51
  const strokeDashoffset = indeterminate ? circumference * 0.25 : circumference - (percentage / 100) * circumference;
54
52
 
55
53
  const computedContainerProps = getWebProps([
56
- progressStyles.circularContainer,
57
- style,
54
+ progressStyles.circularContainer as any,
55
+ style as any,
58
56
  { display: 'inline-flex' }
59
57
  ]);
60
- const labelProps = getWebProps([progressStyles.circularLabel]);
61
- const trackColorProps = getWebProps([progressStyles.circularTrack]);
62
- const barColorProps = getWebProps([(progressStyles.circularBar as any)(dynamicProps)]);
58
+ const circularLabelProps = getWebProps([progressStyles.circularLabel as any]);
59
+ const trackColorProps = getWebProps([progressStyles.circularTrack as any]);
60
+ const barColorProps = getWebProps([progressStyles.circularBar as any]);
63
61
 
64
62
  return (
65
63
  <div {...computedContainerProps} id={id} data-testid={testID}>
@@ -93,7 +91,7 @@ const Progress: React.FC<ProgressProps> = ({
93
91
  />
94
92
  </svg>
95
93
  {showLabel && (
96
- <span {...labelProps}>
94
+ <span {...circularLabelProps}>
97
95
  {label || `${Math.round(percentage)}%`}
98
96
  </span>
99
97
  )}
@@ -56,7 +56,7 @@ const SVGImage = forwardRef<HTMLDivElement, SVGImageProps>(({
56
56
 
57
57
  // Use getWebProps to generate className and ref for web
58
58
  const containerWebProps = getWebProps(containerStyleArray);
59
- const imageWebProps = getWebProps(svgImageStyles.image);
59
+ const imageWebProps = getWebProps([svgImageStyles.image as any]);
60
60
 
61
61
  // Apply custom color if provided
62
62
  // Convert React Native resize modes to CSS object-fit values