@idealyst/components 1.1.6 → 1.1.8

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 (144) hide show
  1. package/package.json +8 -3
  2. package/src/Accordion/Accordion.native.tsx +22 -14
  3. package/src/Accordion/Accordion.styles.old.tsx +298 -0
  4. package/src/Accordion/Accordion.styles.tsx +139 -248
  5. package/src/Accordion/Accordion.web.tsx +12 -7
  6. package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
  7. package/src/ActivityIndicator/ActivityIndicator.styles.old.tsx +94 -0
  8. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +43 -62
  9. package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -2
  10. package/src/Alert/Alert.native.tsx +26 -15
  11. package/src/Alert/Alert.styles.old.tsx +209 -0
  12. package/src/Alert/Alert.styles.tsx +108 -281
  13. package/src/Alert/Alert.web.tsx +6 -10
  14. package/src/Avatar/Avatar.native.tsx +5 -2
  15. package/src/Avatar/Avatar.styles.old.tsx +99 -0
  16. package/src/Avatar/Avatar.styles.tsx +47 -62
  17. package/src/Avatar/Avatar.web.tsx +2 -2
  18. package/src/Badge/Badge.native.tsx +2 -2
  19. package/src/Badge/Badge.styles.old.tsx +157 -0
  20. package/src/Badge/Badge.styles.tsx +69 -108
  21. package/src/Badge/Badge.web.tsx +6 -6
  22. package/src/Breadcrumb/Breadcrumb.native.tsx +12 -5
  23. package/src/Breadcrumb/Breadcrumb.styles.old.tsx +231 -0
  24. package/src/Breadcrumb/Breadcrumb.styles.tsx +93 -209
  25. package/src/Breadcrumb/Breadcrumb.web.tsx +39 -27
  26. package/src/Button/Button.native.tsx +39 -14
  27. package/src/Button/Button.styles.tsx +99 -253
  28. package/src/Button/Button.web.tsx +10 -8
  29. package/src/Card/Card.native.tsx +8 -4
  30. package/src/Card/Card.styles.old.tsx +160 -0
  31. package/src/Card/Card.styles.tsx +107 -142
  32. package/src/Card/Card.web.tsx +6 -4
  33. package/src/Checkbox/Checkbox.native.tsx +14 -6
  34. package/src/Checkbox/Checkbox.styles.old.tsx +271 -0
  35. package/src/Checkbox/Checkbox.styles.tsx +109 -197
  36. package/src/Checkbox/Checkbox.web.tsx +7 -7
  37. package/src/Chip/Chip.native.tsx +5 -5
  38. package/src/Chip/Chip.styles.old.tsx +184 -0
  39. package/src/Chip/Chip.styles.tsx +34 -22
  40. package/src/Chip/Chip.web.tsx +5 -5
  41. package/src/Dialog/Dialog.native.tsx +16 -7
  42. package/src/Dialog/Dialog.styles.old.tsx +202 -0
  43. package/src/Dialog/Dialog.styles.tsx +108 -132
  44. package/src/Dialog/Dialog.web.tsx +4 -4
  45. package/src/Divider/Divider.native.tsx +29 -42
  46. package/src/Divider/Divider.styles.old.tsx +172 -0
  47. package/src/Divider/Divider.styles.tsx +116 -242
  48. package/src/Divider/Divider.web.tsx +17 -14
  49. package/src/Icon/Icon.native.tsx +12 -4
  50. package/src/Icon/Icon.styles.old.tsx +81 -0
  51. package/src/Icon/Icon.styles.tsx +52 -60
  52. package/src/Icon/Icon.web.tsx +43 -7
  53. package/src/Icon/IconSvg/IconSvg.web.tsx +2 -0
  54. package/src/Image/Image.styles.old.tsx +69 -0
  55. package/src/Image/Image.styles.tsx +45 -43
  56. package/src/Input/Input.native.tsx +140 -56
  57. package/src/Input/Input.styles.old.tsx +289 -0
  58. package/src/Input/Input.styles.tsx +177 -228
  59. package/src/Input/Input.web.tsx +5 -8
  60. package/src/Link/Link.native.tsx +4 -1
  61. package/src/List/List.native.tsx +5 -2
  62. package/src/List/List.styles.old.tsx +242 -0
  63. package/src/List/List.styles.tsx +178 -240
  64. package/src/List/ListItem.native.tsx +16 -8
  65. package/src/List/ListItem.web.tsx +26 -15
  66. package/src/Menu/Menu.native.tsx +1 -1
  67. package/src/Menu/Menu.styles.old.tsx +197 -0
  68. package/src/Menu/Menu.styles.tsx +90 -156
  69. package/src/Menu/Menu.web.tsx +2 -2
  70. package/src/Menu/MenuItem.native.tsx +9 -5
  71. package/src/Menu/MenuItem.styles.old.tsx +114 -0
  72. package/src/Menu/MenuItem.styles.tsx +71 -104
  73. package/src/Menu/MenuItem.web.tsx +23 -5
  74. package/src/Popover/Popover.native.tsx +10 -4
  75. package/src/Popover/Popover.styles.old.tsx +135 -0
  76. package/src/Popover/Popover.styles.tsx +46 -96
  77. package/src/Popover/Popover.web.tsx +1 -1
  78. package/src/Pressable/Pressable.native.tsx +3 -1
  79. package/src/Pressable/Pressable.styles.old.tsx +27 -0
  80. package/src/Pressable/Pressable.styles.tsx +35 -20
  81. package/src/Pressable/Pressable.web.tsx +1 -1
  82. package/src/Progress/Progress.native.tsx +15 -6
  83. package/src/Progress/Progress.styles.old.tsx +200 -0
  84. package/src/Progress/Progress.styles.tsx +69 -118
  85. package/src/Progress/Progress.web.tsx +10 -9
  86. package/src/RadioButton/RadioButton.native.tsx +10 -4
  87. package/src/RadioButton/RadioButton.styles.old.tsx +175 -0
  88. package/src/RadioButton/RadioButton.styles.tsx +81 -145
  89. package/src/RadioButton/RadioButton.web.tsx +4 -4
  90. package/src/SVGImage/SVGImage.styles.old.tsx +86 -0
  91. package/src/SVGImage/SVGImage.styles.tsx +35 -66
  92. package/src/Screen/Screen.native.tsx +30 -27
  93. package/src/Screen/Screen.styles.old.tsx +87 -0
  94. package/src/Screen/Screen.styles.tsx +120 -71
  95. package/src/Screen/Screen.web.tsx +2 -2
  96. package/src/Select/Select.native.tsx +44 -29
  97. package/src/Select/Select.styles.old.tsx +353 -0
  98. package/src/Select/Select.styles.tsx +244 -293
  99. package/src/Select/Select.web.tsx +5 -5
  100. package/src/Skeleton/Skeleton.styles.old.tsx +67 -0
  101. package/src/Skeleton/Skeleton.styles.tsx +31 -43
  102. package/src/Slider/Slider.native.tsx +9 -5
  103. package/src/Slider/Slider.styles.old.tsx +259 -0
  104. package/src/Slider/Slider.styles.tsx +157 -227
  105. package/src/Slider/Slider.web.tsx +5 -5
  106. package/src/Switch/Switch.native.tsx +11 -5
  107. package/src/Switch/Switch.styles.old.tsx +203 -0
  108. package/src/Switch/Switch.styles.tsx +103 -149
  109. package/src/Switch/Switch.web.tsx +8 -8
  110. package/src/TabBar/TabBar.native.tsx +24 -31
  111. package/src/TabBar/TabBar.styles.old.tsx +343 -0
  112. package/src/TabBar/TabBar.styles.tsx +204 -494
  113. package/src/TabBar/TabBar.web.tsx +21 -33
  114. package/src/Table/Table.native.tsx +18 -9
  115. package/src/Table/Table.styles.old.tsx +311 -0
  116. package/src/Table/Table.styles.tsx +151 -278
  117. package/src/Table/Table.web.tsx +1 -1
  118. package/src/Text/Text.native.tsx +1 -4
  119. package/src/Text/Text.style.demo.tsx +16 -0
  120. package/src/Text/Text.styles.old.tsx +219 -0
  121. package/src/Text/Text.styles.tsx +94 -78
  122. package/src/Text/Text.web.tsx +2 -2
  123. package/src/Text/index.ts +1 -0
  124. package/src/TextArea/TextArea.styles.old.tsx +213 -0
  125. package/src/TextArea/TextArea.styles.tsx +101 -157
  126. package/src/Tooltip/Tooltip.native.tsx +2 -2
  127. package/src/Tooltip/Tooltip.styles.old.tsx +82 -0
  128. package/src/Tooltip/Tooltip.styles.tsx +38 -53
  129. package/src/Tooltip/Tooltip.web.tsx +2 -2
  130. package/src/Video/Video.styles.old.tsx +51 -0
  131. package/src/Video/Video.styles.tsx +32 -28
  132. package/src/View/View.native.tsx +12 -12
  133. package/src/View/View.styles.old.tsx +125 -0
  134. package/src/View/View.styles.tsx +84 -103
  135. package/src/View/View.web.tsx +14 -2
  136. package/src/examples/CardExamples.tsx +0 -6
  137. package/src/extensions/applyExtension.ts +210 -0
  138. package/src/extensions/extendComponent.ts +438 -0
  139. package/src/extensions/index.ts +102 -0
  140. package/src/extensions/types.ts +497 -0
  141. package/src/globals.ts +16 -0
  142. package/src/index.native.ts +4 -0
  143. package/src/index.ts +28 -0
  144. package/src/utils/deepMerge.ts +54 -2
@@ -26,6 +26,7 @@ const View = forwardRef<RNView | RNScrollView, ViewProps>(({
26
26
  testID,
27
27
  id,
28
28
  }, ref) => {
29
+ // Set active variants for this render
29
30
  viewStyles.useVariants({
30
31
  background,
31
32
  radius,
@@ -39,23 +40,22 @@ const View = forwardRef<RNView | RNScrollView, ViewProps>(({
39
40
  marginHorizontal,
40
41
  });
41
42
 
42
- const getStyles = (): ViewStyle => {
43
- const baseStyles: ViewStyle = {};
43
+ // Call style as function to get theme-reactive styles
44
+ const viewStyle = (viewStyles.view as any)({});
44
45
 
45
- if (backgroundColor) baseStyles.backgroundColor = backgroundColor;
46
- if (borderRadius !== undefined) baseStyles.borderRadius = borderRadius;
47
- if (borderWidth !== undefined) baseStyles.borderWidth = borderWidth;
48
- if (borderColor) baseStyles.borderColor = borderColor;
49
-
50
- return baseStyles;
51
- };
46
+ // Override styles for direct prop values
47
+ const overrideStyles: ViewStyle = {};
48
+ if (backgroundColor) overrideStyles.backgroundColor = backgroundColor;
49
+ if (borderRadius !== undefined) overrideStyles.borderRadius = borderRadius;
50
+ if (borderWidth !== undefined) overrideStyles.borderWidth = borderWidth;
51
+ if (borderColor) overrideStyles.borderColor = borderColor;
52
52
 
53
53
  if (scrollable) {
54
54
  return (
55
55
  <RNScrollView
56
56
  ref={ref as any}
57
- style={[{ flex: 1 }, style]}
58
- contentContainerStyle={[viewStyles.view, getStyles()]}
57
+ style={[viewStyle, { flex: 1 }, overrideStyles, style]}
58
+ contentContainerStyle={[viewStyle, overrideStyles]}
59
59
  testID={testID}
60
60
  nativeID={id}
61
61
  >
@@ -65,7 +65,7 @@ const View = forwardRef<RNView | RNScrollView, ViewProps>(({
65
65
  }
66
66
 
67
67
  return (
68
- <RNView ref={ref as any} style={[viewStyles.view, getStyles(), style]} testID={testID} nativeID={id}>
68
+ <RNView ref={ref as any} style={[viewStyle, overrideStyles, style]} testID={testID} nativeID={id}>
69
69
  {children}
70
70
  </RNView>
71
71
  );
@@ -0,0 +1,125 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { Theme, StylesheetStyles, Surface } from '@idealyst/theme';
3
+ import {
4
+ buildGapVariants,
5
+ buildPaddingVariants,
6
+ buildPaddingVerticalVariants,
7
+ buildPaddingHorizontalVariants,
8
+ buildMarginVariants,
9
+ buildMarginVerticalVariants,
10
+ buildMarginHorizontalVariants,
11
+ } from '../utils/buildViewStyleVariants';
12
+ import { ViewBackgroundVariant, ViewBorderVariant, ViewRadiusVariant } from './types';
13
+ import { ViewStyleSize } from '../utils/viewStyleProps';
14
+ import { applyExtensions } from '../extensions/applyExtension';
15
+
16
+ type ViewVariants = {
17
+ background: ViewBackgroundVariant;
18
+ radius: ViewRadiusVariant;
19
+ border: ViewBorderVariant;
20
+ gap: ViewStyleSize;
21
+ padding: ViewStyleSize;
22
+ paddingVertical: ViewStyleSize;
23
+ paddingHorizontal: ViewStyleSize;
24
+ margin: ViewStyleSize;
25
+ marginVertical: ViewStyleSize;
26
+ marginHorizontal: ViewStyleSize;
27
+ };
28
+
29
+ export type ExpandedViewStyles = StylesheetStyles<keyof ViewVariants>;
30
+
31
+ export type ViewStylesheet = {
32
+ view: ExpandedViewStyles;
33
+ };
34
+
35
+ /**
36
+ * Create background variants for view
37
+ */
38
+ function createBackgroundVariants(theme: Theme) {
39
+ const variants: any = {
40
+ transparent: {
41
+ backgroundColor: 'transparent',
42
+ },
43
+ };
44
+
45
+ // Add all surface colors programmatically
46
+ for (const surface in theme.colors.surface) {
47
+ variants[surface] = {
48
+ backgroundColor: theme.colors.surface[surface as Surface],
49
+ };
50
+ }
51
+
52
+ return variants;
53
+ }
54
+
55
+ /**
56
+ * Create radius variants for view
57
+ */
58
+ function createRadiusVariants() {
59
+ return {
60
+ none: { borderRadius: 0 },
61
+ xs: { borderRadius: 2 },
62
+ sm: { borderRadius: 4 },
63
+ md: { borderRadius: 8 },
64
+ lg: { borderRadius: 12 },
65
+ xl: { borderRadius: 16 },
66
+ } as const;
67
+ }
68
+
69
+ /**
70
+ * Create border variants for view
71
+ */
72
+ function createBorderVariants(theme: Theme) {
73
+ return {
74
+ none: {
75
+ borderWidth: 0,
76
+ },
77
+ thin: {
78
+ borderWidth: 1,
79
+ borderStyle: 'solid',
80
+ borderColor: theme.colors['gray.300'],
81
+ },
82
+ thick: {
83
+ borderWidth: 2,
84
+ borderStyle: 'solid',
85
+ borderColor: theme.colors['gray.300'],
86
+ },
87
+ } as const;
88
+ }
89
+
90
+ /**
91
+ * Create dynamic view styles.
92
+ * Returns a function to ensure Unistyles can track theme changes.
93
+ * All styles must be dynamic functions (not static objects) to work with
94
+ * Unistyles' Babel transform and theme reactivity on native.
95
+ */
96
+ function createViewStyles(theme: Theme) {
97
+ return (_props?: {}) => ({
98
+ display: 'flex' as const,
99
+ variants: {
100
+ background: createBackgroundVariants(theme),
101
+ radius: createRadiusVariants(),
102
+ border: createBorderVariants(theme),
103
+ gap: buildGapVariants(theme),
104
+ padding: buildPaddingVariants(theme),
105
+ paddingVertical: buildPaddingVerticalVariants(theme),
106
+ paddingHorizontal: buildPaddingHorizontalVariants(theme),
107
+ margin: buildMarginVariants(theme),
108
+ marginVertical: buildMarginVerticalVariants(theme),
109
+ marginHorizontal: buildMarginHorizontalVariants(theme),
110
+ },
111
+ _web: {
112
+ display: 'flex',
113
+ flexDirection: 'column',
114
+ boxSizing: 'border-box',
115
+ },
116
+ });
117
+ }
118
+
119
+ // Styles use applyExtensions to enable theme extensions and ensure proper
120
+ // reactivity with Unistyles' native Shadow Tree updates.
121
+ export const viewStyles = StyleSheet.create((theme: Theme) => {
122
+ return applyExtensions('View', theme, {
123
+ view: createViewStyles(theme),
124
+ });
125
+ });
@@ -1,114 +1,95 @@
1
+ /**
2
+ * View styles using defineStyle with $iterator expansion.
3
+ */
1
4
  import { StyleSheet } from 'react-native-unistyles';
2
- import { Theme, StylesheetStyles, Surface } from '@idealyst/theme';
3
- import {
4
- buildGapVariants,
5
- buildPaddingVariants,
6
- buildPaddingVerticalVariants,
7
- buildPaddingHorizontalVariants,
8
- buildMarginVariants,
9
- buildMarginVerticalVariants,
10
- buildMarginHorizontalVariants,
11
- } from '../utils/buildViewStyleVariants';
5
+ import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
6
+ import type { Theme as BaseTheme } from '@idealyst/theme';
12
7
  import { ViewBackgroundVariant, ViewBorderVariant, ViewRadiusVariant } from './types';
13
8
  import { ViewStyleSize } from '../utils/viewStyleProps';
14
9
 
15
- type ViewVariants = {
16
- background: ViewBackgroundVariant;
17
- radius: ViewRadiusVariant;
18
- border: ViewBorderVariant;
19
- gap: ViewStyleSize;
20
- padding: ViewStyleSize;
21
- paddingVertical: ViewStyleSize;
22
- paddingHorizontal: ViewStyleSize;
23
- margin: ViewStyleSize;
24
- marginVertical: ViewStyleSize;
25
- marginHorizontal: ViewStyleSize;
26
- };
10
+ // Required: Unistyles must see StyleSheet usage in original source to process this file
11
+ void StyleSheet;
27
12
 
28
- export type ExpandedViewStyles = StylesheetStyles<keyof ViewVariants>;
13
+ // Wrap theme for $iterator support
14
+ type Theme = ThemeStyleWrapper<BaseTheme>;
29
15
 
30
- export type ViewStylesheet = {
31
- view: ExpandedViewStyles;
16
+ export type ViewVariants = {
17
+ background: ViewBackgroundVariant;
18
+ radius: ViewRadiusVariant;
19
+ border: ViewBorderVariant;
20
+ gap: ViewStyleSize;
21
+ padding: ViewStyleSize;
22
+ paddingVertical: ViewStyleSize;
23
+ paddingHorizontal: ViewStyleSize;
24
+ margin: ViewStyleSize;
25
+ marginVertical: ViewStyleSize;
26
+ marginHorizontal: ViewStyleSize;
32
27
  };
33
28
 
34
- /**
35
- * Create background variants for view
36
- */
37
- function createBackgroundVariants(theme: Theme) {
38
- const variants: any = {
39
- transparent: {
40
- backgroundColor: 'transparent',
41
- },
42
- };
43
-
44
- // Add all surface colors programmatically
45
- for (const surface in theme.colors.surface) {
46
- variants[surface] = {
47
- backgroundColor: theme.colors.surface[surface as Surface],
48
- };
49
- }
50
-
51
- return variants;
52
- }
29
+ export type ViewDynamicProps = Partial<ViewVariants>;
53
30
 
54
31
  /**
55
- * Create radius variants for view
32
+ * View styles with $iterator expansion for spacing variants.
33
+ *
34
+ * NOTE: At least one top-level theme access is required for Unistyles to trace
35
+ * theme dependencies. We use a transparent borderColor as a marker.
56
36
  */
57
- function createRadiusVariants() {
58
- return {
59
- none: { borderRadius: 0 },
60
- xs: { borderRadius: 2 },
61
- sm: { borderRadius: 4 },
62
- md: { borderRadius: 8 },
63
- lg: { borderRadius: 12 },
64
- xl: { borderRadius: 16 },
65
- } as const;
66
- }
67
-
68
- /**
69
- * Create border variants for view
70
- */
71
- function createBorderVariants(theme: Theme) {
72
- return {
73
- none: {
74
- borderWidth: 0,
75
- },
76
- thin: {
77
- borderWidth: 1,
78
- borderStyle: 'solid',
79
- borderColor: theme.colors['gray.300'],
80
- },
81
- thick: {
82
- borderWidth: 2,
83
- borderStyle: 'solid',
84
- borderColor: theme.colors['gray.300'],
85
- },
86
- } as const;
87
- }
88
-
89
- // Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel
90
- // transform on native cannot resolve function calls to extract variant structures.
91
- export const viewStyles = StyleSheet.create((theme: Theme) => {
92
- return {
93
- view: {
94
- display: 'flex',
95
- variants: {
96
- background: createBackgroundVariants(theme),
97
- radius: createRadiusVariants(),
98
- border: createBorderVariants(theme),
99
- gap: buildGapVariants(theme),
100
- padding: buildPaddingVariants(theme),
101
- paddingVertical: buildPaddingVerticalVariants(theme),
102
- paddingHorizontal: buildPaddingHorizontalVariants(theme),
103
- margin: buildMarginVariants(theme),
104
- marginVertical: buildMarginVerticalVariants(theme),
105
- marginHorizontal: buildMarginHorizontalVariants(theme),
106
- },
107
- _web: {
108
- display: 'flex',
109
- flexDirection: 'column',
110
- boxSizing: 'border-box',
111
- },
112
- },
113
- };
114
- });
37
+ export const viewStyles = defineStyle('View', (theme: Theme) => ({
38
+ view: (_props: ViewDynamicProps) => ({
39
+ display: 'flex' as const,
40
+ // Theme marker for Unistyles reactivity (invisible, overridden by variants)
41
+ borderColor: theme.colors.border.primary,
42
+ borderWidth: 0,
43
+ variants: {
44
+ background: {
45
+ transparent: { backgroundColor: 'transparent' },
46
+ primary: { backgroundColor: theme.colors.surface.primary },
47
+ secondary: { backgroundColor: theme.colors.surface.secondary },
48
+ tertiary: { backgroundColor: theme.colors.surface.tertiary },
49
+ inverse: { backgroundColor: theme.colors.surface.inverse },
50
+ 'inverse-secondary': { backgroundColor: theme.colors.surface['inverse-secondary'] },
51
+ 'inverse-tertiary': { backgroundColor: theme.colors.surface['inverse-tertiary'] },
52
+ },
53
+ radius: {
54
+ none: { borderRadius: 0 },
55
+ xs: { borderRadius: 2 },
56
+ sm: { borderRadius: 4 },
57
+ md: { borderRadius: 8 },
58
+ lg: { borderRadius: 12 },
59
+ xl: { borderRadius: 16 },
60
+ },
61
+ border: {
62
+ none: { borderWidth: 0 },
63
+ thin: { borderWidth: 1, borderStyle: 'solid' as const, borderColor: theme.colors['gray.300'] },
64
+ thick: { borderWidth: 2, borderStyle: 'solid' as const, borderColor: theme.colors['gray.300'] },
65
+ },
66
+ // $iterator expands for each view size
67
+ gap: {
68
+ gap: theme.sizes.$view.spacing,
69
+ },
70
+ padding: {
71
+ padding: theme.sizes.$view.padding,
72
+ },
73
+ paddingVertical: {
74
+ paddingVertical: theme.sizes.$view.padding,
75
+ },
76
+ paddingHorizontal: {
77
+ paddingHorizontal: theme.sizes.$view.padding,
78
+ },
79
+ margin: {
80
+ margin: theme.sizes.$view.padding,
81
+ },
82
+ marginVertical: {
83
+ marginVertical: theme.sizes.$view.padding,
84
+ },
85
+ marginHorizontal: {
86
+ marginHorizontal: theme.sizes.$view.padding,
87
+ },
88
+ },
89
+ _web: {
90
+ display: 'flex',
91
+ flexDirection: 'column',
92
+ boxSizing: 'border-box',
93
+ },
94
+ }),
95
+ }));
@@ -1,4 +1,5 @@
1
- import React, { forwardRef } from 'react';
1
+ import React, { forwardRef, useMemo } from 'react';
2
+ import { StyleSheet } from 'react-native';
2
3
  import { getWebProps } from 'react-native-unistyles/web';
3
4
  import { ViewProps } from './types';
4
5
  import { viewStyles } from './View.styles';
@@ -48,13 +49,24 @@ const View = forwardRef<HTMLDivElement, ViewProps>(({
48
49
  if (borderWidth !== undefined) dynamicStyles.borderWidth = borderWidth;
49
50
  if (borderColor) dynamicStyles.borderColor = borderColor;
50
51
 
52
+ // Flatten style array to object (HTML divs don't support style arrays)
53
+ const flattenedStyle = useMemo(() => {
54
+ if (!style) return undefined;
55
+ if (Array.isArray(style)) {
56
+ return StyleSheet.flatten(style);
57
+ }
58
+ return style;
59
+ }, [style]);
60
+
61
+ // Call style as function to get theme-reactive styles
51
62
  /** @ts-ignore */
52
- const webProps = getWebProps([viewStyles.view, dynamicStyles, style as any]);
63
+ const webProps = getWebProps([(viewStyles.view as any)({}), dynamicStyles]);
53
64
 
54
65
  const mergedRef = useMergeRefs(ref, webProps.ref);
55
66
 
56
67
  return (
57
68
  <div
69
+ style={flattenedStyle as any}
58
70
  {...webProps}
59
71
  ref={mergedRef}
60
72
  id={id}
@@ -17,12 +17,6 @@ export const CardExamples = () => {
17
17
  <View gap="md">
18
18
  <Text typography="subtitle1">Variants</Text>
19
19
  <View gap="sm" style={{ gap: 10 }}>
20
- <Card type="default" padding="md">
21
- <Text>Default Card</Text>
22
- <Text typography="caption" color="secondary">
23
- This is a default card with standard styling
24
- </Text>
25
- </Card>
26
20
 
27
21
  <Card type="outlined" padding="md">
28
22
  <Text>Outlined Card</Text>
@@ -0,0 +1,210 @@
1
+ import { deepMerge } from '../utils/deepMerge';
2
+ import { Styles, ElementStyle, ComponentName } from './types';
3
+ import { getExtension, getReplacement } from './extendComponent';
4
+ import { Theme } from '@idealyst/theme';
5
+
6
+ /**
7
+ * Wrap a dynamic style function to merge with extension styles.
8
+ *
9
+ * All styles in Unistyles must be dynamic functions (not static objects)
10
+ * to avoid Babel transform issues. This utility wraps a style function
11
+ * to automatically merge extension styles when the function is called.
12
+ *
13
+ * @param styleFn - The original dynamic style function
14
+ * @param elementExtension - Extension styles for this element (can be undefined).
15
+ * Can be either a static styles object or a function (props) => styles
16
+ * for prop-aware extensions.
17
+ * @returns A new function that returns merged styles
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { withExtension } from '../extensions/applyExtension';
22
+ * import { getExtension } from '../extensions/extendComponent';
23
+ *
24
+ * export const buttonStyles = StyleSheet.create((theme: Theme) => {
25
+ * const ext = getExtension('Button', theme);
26
+ *
27
+ * return {
28
+ * button: withExtension(createButtonStyles(theme), ext?.button),
29
+ * text: withExtension(createTextStyles(theme), ext?.text),
30
+ * };
31
+ * });
32
+ * ```
33
+ *
34
+ * @remarks
35
+ * - If no extension is provided, returns the original function unchanged
36
+ * - Extension styles take priority over base styles (deep merged)
37
+ * - Works with any style function signature
38
+ * - If extension is a function, it receives the same props as the base style function
39
+ */
40
+ export function withExtension<TProps, TResult extends Styles>(
41
+ styleFn: (props: TProps) => TResult,
42
+ elementExtension: Styles | ((props: TProps) => Styles) | undefined
43
+ ): (props: TProps) => TResult {
44
+ // If no extension, return original function unchanged
45
+ if (!elementExtension) {
46
+ return styleFn;
47
+ }
48
+
49
+ // Return wrapped function that merges extension
50
+ return (props: TProps): TResult => {
51
+ const baseStyles = styleFn(props);
52
+ // If extension is a function, call it with props; otherwise use as-is
53
+ const extStyles = typeof elementExtension === 'function'
54
+ ? elementExtension(props)
55
+ : elementExtension;
56
+ return deepMerge(baseStyles, extStyles) as TResult;
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Wrap a parameterless style function with extension.
62
+ *
63
+ * Use this for style functions that don't take any parameters.
64
+ * This is common for simpler elements like iconContainer.
65
+ *
66
+ * @param styleFn - The original style function (no parameters)
67
+ * @param elementExtension - Extension styles for this element
68
+ * @returns A new function that returns merged styles
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const createIconContainerStyles = (theme: Theme) => {
73
+ * return () => ({
74
+ * display: 'flex',
75
+ * flexDirection: 'row',
76
+ * gap: 4,
77
+ * });
78
+ * };
79
+ *
80
+ * // In StyleSheet.create:
81
+ * iconContainer: withSimpleExtension(
82
+ * createIconContainerStyles(theme),
83
+ * ext?.iconContainer
84
+ * ),
85
+ * ```
86
+ */
87
+ export function withSimpleExtension<TResult extends Styles>(
88
+ styleFn: () => TResult,
89
+ elementExtension: Styles | undefined
90
+ ): () => TResult {
91
+ if (!elementExtension) {
92
+ return styleFn;
93
+ }
94
+
95
+ return (): TResult => {
96
+ const baseStyles = styleFn();
97
+ return deepMerge(baseStyles, elementExtension) as TResult;
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Normalize a style value (from replacement) into a dynamic function.
103
+ *
104
+ * Replacements can be either:
105
+ * - A function (props) => styles - used directly
106
+ * - A static styles object - wrapped in a function
107
+ *
108
+ * @param value - The replacement value (function or static object)
109
+ * @param defaultFn - Default function to use if value is undefined
110
+ * @returns The default function (type-safe) - replacement handling is done at runtime
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const replacement = getReplacement('Button', theme);
115
+ *
116
+ * // If replacement.button is a function, use it directly
117
+ * // If replacement.button is an object, wrap it in () => replacement.button
118
+ * // If undefined, use createButtonStyles(theme)
119
+ * button: withExtension(
120
+ * normalizeStyleFn(replacement?.button, createButtonStyles(theme)),
121
+ * ext?.button
122
+ * ),
123
+ * ```
124
+ */
125
+ export function normalizeStyleFn<TProps, TResult>(
126
+ value: unknown,
127
+ defaultFn: (props: TProps) => TResult
128
+ ): (props: TProps) => TResult {
129
+ if (value === undefined || value === null) {
130
+ return defaultFn;
131
+ }
132
+ if (typeof value === 'function') {
133
+ return value as (props: TProps) => TResult;
134
+ }
135
+ // Static object - wrap in a function that ignores props
136
+ return (() => value) as (props: TProps) => TResult;
137
+ }
138
+
139
+ /**
140
+ * Normalize a simple style value (no props) into a parameterless function.
141
+ *
142
+ * @param value - The replacement value (function or static object)
143
+ * @param defaultFn - Default function to use if value is undefined
144
+ * @returns A parameterless function that returns styles
145
+ */
146
+ export function normalizeSimpleStyleFn<TResult>(
147
+ value: unknown,
148
+ defaultFn: () => TResult
149
+ ): () => TResult {
150
+ if (value === undefined || value === null) {
151
+ return defaultFn;
152
+ }
153
+ if (typeof value === 'function') {
154
+ return value as () => TResult;
155
+ }
156
+ // Static object - wrap in a function
157
+ return (() => value) as () => TResult;
158
+ }
159
+
160
+ /**
161
+ * Apply extensions and replacements to a set of style creators.
162
+ *
163
+ * This is a simplified helper that handles the common pattern of:
164
+ * 1. Getting extensions and replacements for a component
165
+ * 2. Applying normalizeStyleFn for each element
166
+ * 3. Merging extensions on top
167
+ *
168
+ * @param component - The component name
169
+ * @param theme - The current theme
170
+ * @param styleCreators - Object mapping element names to their style creator functions
171
+ * @returns Object with the same keys, but with extensions/replacements applied
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * export const buttonStyles = StyleSheet.create((theme: Theme) => {
176
+ * return applyExtensions('Button', theme, {
177
+ * button: createButtonStyles(theme),
178
+ * text: createTextStyles(theme),
179
+ * icon: createIconStyles(theme),
180
+ * });
181
+ * });
182
+ * ```
183
+ */
184
+ export function applyExtensions<
185
+ K extends ComponentName,
186
+ T extends Record<string, ((...args: any[]) => any)>
187
+ >(
188
+ component: K,
189
+ theme: Theme,
190
+ styleCreators: T
191
+ ): T {
192
+ const ext = getExtension(component, theme);
193
+ const replacement = getReplacement(component, theme);
194
+
195
+ const result = {} as T;
196
+
197
+ for (const key in styleCreators) {
198
+ const creator = styleCreators[key];
199
+ const elementExt = ext?.[key as string as keyof typeof ext] as ElementStyle | undefined;
200
+ const elementReplacement = replacement?.[key as string as keyof typeof replacement];
201
+
202
+ // Apply replacement (if any) then extension (if any)
203
+ result[key] = withExtension(
204
+ normalizeStyleFn(elementReplacement, creator),
205
+ elementExt
206
+ ) as T[typeof key];
207
+ }
208
+
209
+ return result;
210
+ }