@idealyst/components 1.0.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 (112) hide show
  1. package/README.md +568 -0
  2. package/package.json +107 -0
  3. package/plugin/web.js +186 -0
  4. package/src/Avatar/Avatar.native.tsx +44 -0
  5. package/src/Avatar/Avatar.styles.tsx +67 -0
  6. package/src/Avatar/Avatar.web.tsx +51 -0
  7. package/src/Avatar/index.native.ts +2 -0
  8. package/src/Avatar/index.ts +2 -0
  9. package/src/Avatar/index.web.ts +2 -0
  10. package/src/Avatar/types.ts +43 -0
  11. package/src/Badge/Badge.native.tsx +43 -0
  12. package/src/Badge/Badge.styles.tsx +154 -0
  13. package/src/Badge/Badge.web.tsx +45 -0
  14. package/src/Badge/index.native.ts +2 -0
  15. package/src/Badge/index.ts +2 -0
  16. package/src/Badge/index.web.ts +2 -0
  17. package/src/Badge/types.ts +34 -0
  18. package/src/Button/Button.native.tsx +39 -0
  19. package/src/Button/Button.styles.tsx +215 -0
  20. package/src/Button/Button.types.ts +12 -0
  21. package/src/Button/Button.web.tsx +56 -0
  22. package/src/Button/index.native.ts +3 -0
  23. package/src/Button/index.ts +5 -0
  24. package/src/Button/index.web.ts +3 -0
  25. package/src/Button/types.ts +49 -0
  26. package/src/Card/Card.native.tsx +52 -0
  27. package/src/Card/Card.styles.tsx +240 -0
  28. package/src/Card/Card.web.tsx +62 -0
  29. package/src/Card/index.native.ts +3 -0
  30. package/src/Card/index.ts +5 -0
  31. package/src/Card/index.web.ts +3 -0
  32. package/src/Card/types.ts +59 -0
  33. package/src/Checkbox/Checkbox.native.tsx +99 -0
  34. package/src/Checkbox/Checkbox.styles.tsx +292 -0
  35. package/src/Checkbox/Checkbox.web.tsx +131 -0
  36. package/src/Checkbox/index.native.ts +3 -0
  37. package/src/Checkbox/index.ts +5 -0
  38. package/src/Checkbox/index.web.ts +3 -0
  39. package/src/Checkbox/types.ts +79 -0
  40. package/src/Divider/Divider.native.tsx +145 -0
  41. package/src/Divider/Divider.styles.tsx +602 -0
  42. package/src/Divider/Divider.web.tsx +73 -0
  43. package/src/Divider/index.native.ts +3 -0
  44. package/src/Divider/index.ts +5 -0
  45. package/src/Divider/index.web.ts +3 -0
  46. package/src/Divider/types.ts +54 -0
  47. package/src/Icon/Icon.native.tsx +39 -0
  48. package/src/Icon/Icon.styles.tsx +50 -0
  49. package/src/Icon/Icon.web.tsx +47 -0
  50. package/src/Icon/icon-types.ts +7452 -0
  51. package/src/Icon/index.native.ts +3 -0
  52. package/src/Icon/index.ts +5 -0
  53. package/src/Icon/index.web.ts +3 -0
  54. package/src/Icon/types.ts +36 -0
  55. package/src/Input/Input.native.tsx +75 -0
  56. package/src/Input/Input.styles.tsx +177 -0
  57. package/src/Input/Input.web.tsx +71 -0
  58. package/src/Input/index.native.ts +3 -0
  59. package/src/Input/index.ts +5 -0
  60. package/src/Input/index.web.ts +3 -0
  61. package/src/Input/types.ts +69 -0
  62. package/src/Screen/Screen.native.tsx +41 -0
  63. package/src/Screen/Screen.styles.tsx +60 -0
  64. package/src/Screen/Screen.web.tsx +33 -0
  65. package/src/Screen/index.native.ts +2 -0
  66. package/src/Screen/index.ts +2 -0
  67. package/src/Screen/index.web.ts +2 -0
  68. package/src/Screen/types.ts +38 -0
  69. package/src/Text/Text.native.tsx +36 -0
  70. package/src/Text/Text.styles.tsx +67 -0
  71. package/src/Text/Text.web.tsx +41 -0
  72. package/src/Text/index.native.ts +3 -0
  73. package/src/Text/index.ts +5 -0
  74. package/src/Text/index.web.ts +3 -0
  75. package/src/Text/types.ts +39 -0
  76. package/src/View/View.native.tsx +56 -0
  77. package/src/View/View.styles.tsx +103 -0
  78. package/src/View/View.web.tsx +60 -0
  79. package/src/View/index.native.ts +3 -0
  80. package/src/View/index.ts +5 -0
  81. package/src/View/index.web.ts +3 -0
  82. package/src/View/types.ts +73 -0
  83. package/src/examples/AllExamples.tsx +72 -0
  84. package/src/examples/AvatarExamples.tsx +97 -0
  85. package/src/examples/BadgeExamples.tsx +200 -0
  86. package/src/examples/ButtonExamples.tsx +150 -0
  87. package/src/examples/CardExamples.tsx +176 -0
  88. package/src/examples/CheckboxExamples.tsx +217 -0
  89. package/src/examples/DividerExamples.tsx +218 -0
  90. package/src/examples/IconExamples.tsx +342 -0
  91. package/src/examples/InputExamples.tsx +134 -0
  92. package/src/examples/README.md +136 -0
  93. package/src/examples/ScreenExamples.tsx +154 -0
  94. package/src/examples/TextExamples.tsx +89 -0
  95. package/src/examples/ThemeExtensionExamples.tsx +91 -0
  96. package/src/examples/ValidationExamples.tsx +95 -0
  97. package/src/examples/ViewExamples.tsx +129 -0
  98. package/src/examples/extendedTheme.ts +331 -0
  99. package/src/examples/index.ts +15 -0
  100. package/src/index.native.ts +52 -0
  101. package/src/index.ts +48 -0
  102. package/src/theme/breakpoints.ts +8 -0
  103. package/src/theme/colorResolver.ts +218 -0
  104. package/src/theme/colors.ts +315 -0
  105. package/src/theme/defaultThemes.ts +326 -0
  106. package/src/theme/index.ts +188 -0
  107. package/src/theme/themeBuilder.ts +602 -0
  108. package/src/theme/unistyles.d.ts +6 -0
  109. package/src/theme/variantHelpers.ts +584 -0
  110. package/src/theme/variants.ts +56 -0
  111. package/src/unistyles.d.ts +108 -0
  112. package/src/unistyles.ts +43 -0
package/plugin/web.js ADDED
@@ -0,0 +1,186 @@
1
+ module.exports = function ({ types: t }) {
2
+ console.log('[mdi-auto-import] Plugin loaded');
3
+
4
+ const importedIcons = new Set();
5
+ const iconImportIdentifiers = new Map();
6
+ let hasIconImport = false;
7
+
8
+ function formatIconName(name) {
9
+ // Handle empty or invalid names
10
+ if (!name || typeof name !== 'string') {
11
+ throw new Error(`Invalid icon name: ${name}`);
12
+ }
13
+
14
+ const formatted = name
15
+ // Convert kebab-case and snake_case to PascalCase
16
+ .replace(/[-_]/g, ' ')
17
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
18
+ .split(' ')
19
+ .filter(part => part.length > 0)
20
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
21
+ .join('');
22
+
23
+ console.log(`[mdi-auto-import] formatIconName: ${name} -> ${formatted}`);
24
+ return formatted;
25
+ }
26
+
27
+ function getMdiIconName(name) {
28
+ const mdiName = `mdi${formatIconName(name)}`;
29
+ console.log(`[mdi-auto-import] getMdiIconName: ${name} -> ${mdiName}`);
30
+ return mdiName;
31
+ }
32
+
33
+ function getIconIdentifier(iconName) {
34
+ if (!iconImportIdentifiers.has(iconName)) {
35
+ iconImportIdentifiers.set(iconName, `_${iconName}`);
36
+ }
37
+ const identifier = iconImportIdentifiers.get(iconName);
38
+ console.log(`[mdi-auto-import] getIconIdentifier: ${iconName} -> ${identifier}`);
39
+ return identifier;
40
+ }
41
+
42
+ return {
43
+ name: 'mdi-auto-import',
44
+ visitor: {
45
+ Program: {
46
+ enter(path) {
47
+ // Reset state for each file
48
+ importedIcons.clear();
49
+ iconImportIdentifiers.clear();
50
+ hasIconImport = false;
51
+
52
+ // Check if Icon is already imported from @mdi/react
53
+ path.node.body.forEach(node => {
54
+ if (t.isImportDeclaration(node) && node.source.value === '@mdi/react') {
55
+ console.log('[mdi-auto-import] Found @mdi/react import');
56
+ const hasIconSpecifier = node.specifiers.some(spec =>
57
+ t.isImportDefaultSpecifier(spec) && spec.local.name === 'MdiIcon'
58
+ );
59
+ if (hasIconSpecifier) {
60
+ console.log('[mdi-auto-import] MdiIcon already imported');
61
+ hasIconImport = true;
62
+ }
63
+ }
64
+ });
65
+ },
66
+ exit(path) {
67
+ if (importedIcons.size === 0) {
68
+ return;
69
+ }
70
+ console.log(`[mdi-auto-import] importedIcons.size: ${importedIcons.size}`);
71
+
72
+ // Add imports at the top of the file if any icons were used
73
+ if (importedIcons.size > 0) {
74
+ console.log('[mdi-auto-import] Adding imports for icons:', Array.from(importedIcons));
75
+
76
+ // Import individual icons from @mdi/js
77
+ const iconImportSpecifiers = Array.from(importedIcons).map(iconName => {
78
+ const identifier = getIconIdentifier(iconName);
79
+ return t.importSpecifier(
80
+ t.identifier(identifier),
81
+ t.identifier(iconName)
82
+ );
83
+ });
84
+
85
+ const iconImportDeclaration = t.importDeclaration(
86
+ iconImportSpecifiers,
87
+ t.stringLiteral('@mdi/js')
88
+ );
89
+
90
+ // Import Icon component from @mdi/react if not already imported
91
+ if (!hasIconImport) {
92
+ console.log('[mdi-auto-import] Adding MdiIcon import from @mdi/react');
93
+ const iconComponentImport = t.importDeclaration(
94
+ [t.importDefaultSpecifier(t.identifier('MdiIcon'))],
95
+ t.stringLiteral('@mdi/react')
96
+ );
97
+ path.unshiftContainer('body', iconComponentImport);
98
+ } else {
99
+ console.log('[mdi-auto-import] MdiIcon already imported, skipping');
100
+ }
101
+
102
+ // Add icon imports
103
+ path.unshiftContainer('body', iconImportDeclaration);
104
+ console.log('[mdi-auto-import] Imports added successfully');
105
+ } else {
106
+ console.log('[mdi-auto-import] No icons to import');
107
+ }
108
+ }
109
+ },
110
+
111
+ JSXElement(path) {
112
+ const { node } = path;
113
+
114
+ // Check if this is an Icon component from @idealyst/components
115
+ if (
116
+ t.isJSXIdentifier(node.openingElement.name) &&
117
+ node.openingElement.name.name === 'Icon'
118
+ ) {
119
+
120
+ // Find the name attribute
121
+ const nameAttr = node.openingElement.attributes.find(attr =>
122
+ t.isJSXAttribute(attr) &&
123
+ t.isJSXIdentifier(attr.name) &&
124
+ attr.name.name === 'name'
125
+ );
126
+
127
+ if (!nameAttr) {
128
+ console.log('[mdi-auto-import] No name attribute found');
129
+ return;
130
+ }
131
+
132
+ let iconName;
133
+
134
+ // Handle both string literals and JSX expressions
135
+ if (nameAttr && t.isStringLiteral(nameAttr.value)) {
136
+ iconName = nameAttr.value.value;
137
+ } else if (nameAttr && t.isJSXExpressionContainer(nameAttr.value)) {
138
+ // Handle JSX expressions like name={'icon-name'} or name={iconVariable}
139
+ const expression = nameAttr.value.expression;
140
+ if (t.isStringLiteral(expression)) {
141
+ iconName = expression.value;
142
+ } else {
143
+ // For dynamic expressions, we can't determine the icon at build time
144
+ // Skip transformation but warn the user
145
+ console.warn(`[mdi-auto-import] Cannot determine icon name for dynamic expression at ${path.node.loc ? `${path.node.loc.start.line}:${path.node.loc.start.column}` : 'unknown location'}`);
146
+ return;
147
+ }
148
+ }
149
+
150
+ if (iconName) {
151
+ console.log(`[mdi-auto-import] Processing icon: ${iconName}`);
152
+ try {
153
+ const mdiIconName = getMdiIconName(iconName);
154
+ const iconIdentifier = getIconIdentifier(mdiIconName);
155
+
156
+ // Track that we need to import this icon
157
+ importedIcons.add(mdiIconName);
158
+ console.log(`[mdi-auto-import] Added icon to import list: ${mdiIconName}`);
159
+
160
+ // Replace name="iconName" with path={iconIdentifier}
161
+ const pathAttr = t.jsxAttribute(
162
+ t.jsxIdentifier('path'),
163
+ t.jsxExpressionContainer(t.identifier(iconIdentifier))
164
+ );
165
+
166
+ // Remove the name attribute and add the path attribute
167
+ node.openingElement.attributes = node.openingElement.attributes
168
+ .filter(attr => !(
169
+ t.isJSXAttribute(attr) &&
170
+ t.isJSXIdentifier(attr.name) &&
171
+ attr.name.name === 'name'
172
+ ))
173
+ .concat(pathAttr);
174
+
175
+ console.log(`[mdi-auto-import] Transformed Icon component: name="${iconName}" -> path={${iconIdentifier}}`);
176
+ } catch (error) {
177
+ console.error(`[mdi-auto-import] Error processing icon "${iconName}": ${error.message}`);
178
+ }
179
+ } else {
180
+ console.log('[mdi-auto-import] No icon name found');
181
+ }
182
+ }
183
+ }
184
+ }
185
+ };
186
+ };
@@ -0,0 +1,44 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Text, Image } from 'react-native';
3
+ import { AvatarProps } from './types';
4
+ import { avatarStyles } from './Avatar.styles';
5
+
6
+ const Avatar: React.FC<AvatarProps> = ({
7
+ src,
8
+ alt,
9
+ fallback,
10
+ size = 'medium',
11
+ shape = 'circle',
12
+ style,
13
+ testID,
14
+ }) => {
15
+ const [hasError, setHasError] = useState(false);
16
+
17
+ avatarStyles.useVariants({
18
+ size,
19
+ shape,
20
+ });
21
+
22
+ const handleImageError = () => {
23
+ setHasError(true);
24
+ };
25
+
26
+ return (
27
+ <View style={[avatarStyles.avatar, style]} testID={testID}>
28
+ {src && !hasError ? (
29
+ <Image
30
+ source={typeof src === 'string' ? { uri: src } : src}
31
+ style={avatarStyles.image}
32
+ onError={handleImageError}
33
+ accessibilityLabel={alt}
34
+ />
35
+ ) : (
36
+ <Text style={avatarStyles.fallback}>
37
+ {fallback}
38
+ </Text>
39
+ )}
40
+ </View>
41
+ );
42
+ };
43
+
44
+ export default Avatar;
@@ -0,0 +1,67 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export const avatarStyles = StyleSheet.create((theme) => ({
4
+ avatar: {
5
+ display: 'flex',
6
+ alignItems: 'center',
7
+ justifyContent: 'center',
8
+ backgroundColor: theme.colors?.surface?.secondary || '#f3f4f6',
9
+ overflow: 'hidden',
10
+
11
+ variants: {
12
+ size: {
13
+ small: {
14
+ width: 32,
15
+ height: 32,
16
+ },
17
+ medium: {
18
+ width: 40,
19
+ height: 40,
20
+ },
21
+ large: {
22
+ width: 48,
23
+ height: 48,
24
+ },
25
+ xlarge: {
26
+ width: 64,
27
+ height: 64,
28
+ },
29
+ },
30
+ shape: {
31
+ circle: {
32
+ borderRadius: 9999,
33
+ },
34
+ square: {
35
+ borderRadius: theme.borderRadius?.md || 8,
36
+ },
37
+ },
38
+ },
39
+ },
40
+
41
+ image: {
42
+ width: '100%',
43
+ height: '100%',
44
+ },
45
+
46
+ fallback: {
47
+ color: theme.colors?.text?.primary || '#374151',
48
+ fontWeight: '600',
49
+
50
+ variants: {
51
+ size: {
52
+ small: {
53
+ fontSize: 14,
54
+ },
55
+ medium: {
56
+ fontSize: 16,
57
+ },
58
+ large: {
59
+ fontSize: 18,
60
+ },
61
+ xlarge: {
62
+ fontSize: 24,
63
+ },
64
+ },
65
+ },
66
+ },
67
+ }));
@@ -0,0 +1,51 @@
1
+ import React, { useState } from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { AvatarProps } from './types';
4
+ import { avatarStyles } from './Avatar.styles';
5
+
6
+ const Avatar: React.FC<AvatarProps> = ({
7
+ src,
8
+ alt,
9
+ fallback,
10
+ size = 'medium',
11
+ shape = 'circle',
12
+ style,
13
+ testID,
14
+ }) => {
15
+ const [hasError, setHasError] = useState(false);
16
+
17
+ avatarStyles.useVariants({
18
+ size,
19
+ shape,
20
+ });
21
+
22
+ const avatarStyleArray = [avatarStyles.avatar, style];
23
+ const avatarProps = getWebProps(avatarStyleArray);
24
+
25
+ // Generate fallback text styles with proper theming and size
26
+ const fallbackStyleArray = [avatarStyles.fallback];
27
+ const fallbackProps = getWebProps(fallbackStyleArray);
28
+
29
+ const handleImageError = () => {
30
+ setHasError(true);
31
+ };
32
+
33
+ return (
34
+ <div {...avatarProps} data-testid={testID}>
35
+ {src && !hasError ? (
36
+ <img
37
+ src={src}
38
+ alt={alt}
39
+ onError={handleImageError}
40
+ style={{ width: '100%', height: '100%', objectFit: 'cover' }}
41
+ />
42
+ ) : (
43
+ <span {...fallbackProps}>
44
+ {fallback}
45
+ </span>
46
+ )}
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export default Avatar;
@@ -0,0 +1,2 @@
1
+ export { default } from './Avatar.native';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { default } from './Avatar.web';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { default } from './Avatar.web';
2
+ export * from './types';
@@ -0,0 +1,43 @@
1
+ import type { ColorVariant } from '../theme/variants';
2
+
3
+ export interface AvatarProps {
4
+ /**
5
+ * Image source (URL or require())
6
+ */
7
+ src?: string | any;
8
+
9
+ /**
10
+ * Alt text for the image
11
+ */
12
+ alt?: string;
13
+
14
+ /**
15
+ * Fallback text (usually initials)
16
+ */
17
+ fallback?: string;
18
+
19
+ /**
20
+ * Size of the avatar
21
+ */
22
+ size?: 'small' | 'medium' | 'large' | 'xlarge';
23
+
24
+ /**
25
+ * Shape of the avatar
26
+ */
27
+ shape?: 'circle' | 'square';
28
+
29
+ /**
30
+ * The color scheme of the avatar (for background when no image)
31
+ */
32
+ color?: ColorVariant;
33
+
34
+ /**
35
+ * Additional styles
36
+ */
37
+ style?: any;
38
+
39
+ /**
40
+ * Test ID for testing
41
+ */
42
+ testID?: string;
43
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { View, Text } from 'react-native';
3
+ import { BadgeProps } from './types';
4
+ import { badgeStyles } from './Badge.styles';
5
+
6
+ const Badge: React.FC<BadgeProps> = ({
7
+ children,
8
+ size = 'medium',
9
+ variant = 'filled',
10
+ color = 'blue',
11
+ style,
12
+ testID,
13
+ }) => {
14
+ badgeStyles.useVariants({
15
+ size,
16
+ variant: variant as any,
17
+ color,
18
+ });
19
+
20
+ if (variant === 'dot') {
21
+ return (
22
+ <View
23
+ style={[badgeStyles.badge, style]}
24
+ testID={testID}
25
+ accessibilityLabel="status indicator"
26
+ />
27
+ );
28
+ }
29
+
30
+ return (
31
+ <View
32
+ style={[badgeStyles.badge, style]}
33
+ testID={testID}
34
+ accessibilityLabel="badge"
35
+ >
36
+ <Text style={badgeStyles.text}>
37
+ {children}
38
+ </Text>
39
+ </View>
40
+ );
41
+ };
42
+
43
+ export default Badge;
@@ -0,0 +1,154 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ import { generateBadgeCompoundVariants } from '../theme/variantHelpers';
3
+
4
+ export const badgeStyles = StyleSheet.create((theme) => ({
5
+ badge: {
6
+ alignItems: 'center',
7
+ justifyContent: 'center',
8
+ borderRadius: 9999,
9
+
10
+ // Web-specific styles for better text centering
11
+ _web: {
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ boxSizing: 'border-box',
16
+ // Text styles for web (since text is rendered directly in the container)
17
+ fontSize: 12, // Default medium size
18
+ fontWeight: '600',
19
+ color: '#ffffff',
20
+ lineHeight: 1, // Use ratio instead of fixed value for better centering
21
+ },
22
+
23
+ variants: {
24
+ size: {
25
+ small: {
26
+ minWidth: 16,
27
+ height: 16,
28
+ paddingHorizontal: 4,
29
+ },
30
+ medium: {
31
+ minWidth: 20,
32
+ height: 20,
33
+ paddingHorizontal: 6,
34
+ },
35
+ large: {
36
+ minWidth: 24,
37
+ height: 24,
38
+ paddingHorizontal: 8,
39
+ },
40
+ },
41
+ variant: {
42
+ filled: {
43
+ // Colors handled by compound variants
44
+ },
45
+ outlined: {
46
+ backgroundColor: 'transparent',
47
+ borderWidth: 1,
48
+ },
49
+ dot: {
50
+ minWidth: 8,
51
+ width: 8,
52
+ height: 8,
53
+ paddingHorizontal: 0, // Override size variant padding
54
+ paddingVertical: 0,
55
+ },
56
+ },
57
+ // Dynamically generated color variants (placeholder - actual styling in compound variants)
58
+ color: Object.fromEntries([
59
+ // All palette colors including shade-specific variants
60
+ ...(theme.palettes ? Object.entries(theme.palettes).flatMap(([colorKey, palette]) => [
61
+ // Base color
62
+ [colorKey, {}],
63
+ // All shade variants
64
+ ...Object.keys(palette).map((shade) => [`${colorKey}.${shade}`, {}])
65
+ ]) : [
66
+ // Fallback base colors
67
+ ['blue', {}], ['green', {}], ['red', {}], ['amber', {}],
68
+ ['gray', {}], ['purple', {}], ['pink', {}], ['cyan', {}]
69
+ ]),
70
+ // Semantic colors
71
+ ['primary', {}],
72
+ ['secondary', {}],
73
+ ['disabled', {}],
74
+ ['inverse', {}],
75
+ ['muted', {}],
76
+ ]),
77
+ },
78
+
79
+ // Dynamically generated compound variants plus static size variants
80
+ compoundVariants: [
81
+ ...generateBadgeCompoundVariants(theme),
82
+ // Web-specific text size variants
83
+ {
84
+ size: 'small',
85
+ styles: {
86
+ _web: {
87
+ fontSize: 10,
88
+ },
89
+ },
90
+ },
91
+ {
92
+ size: 'medium',
93
+ styles: {
94
+ _web: {
95
+ fontSize: 12,
96
+ },
97
+ },
98
+ },
99
+ {
100
+ size: 'large',
101
+ styles: {
102
+ _web: {
103
+ fontSize: 14,
104
+ },
105
+ },
106
+ },
107
+ ],
108
+ },
109
+
110
+ text: {
111
+ color: '#ffffff',
112
+ fontWeight: '600',
113
+ textAlign: 'center',
114
+
115
+ variants: {
116
+ size: {
117
+ small: {
118
+ fontSize: 10,
119
+ lineHeight: 12, // Tight line height for better vertical centering
120
+ },
121
+ medium: {
122
+ fontSize: 12,
123
+ lineHeight: 14,
124
+ },
125
+ large: {
126
+ fontSize: 14,
127
+ lineHeight: 16,
128
+ },
129
+ },
130
+ variant: {
131
+ filled: {
132
+ color: '#ffffff',
133
+ },
134
+ outlined: {
135
+ // Colors handled by compound variants
136
+ },
137
+ dot: {
138
+ // No text for dot variant
139
+ },
140
+ },
141
+ },
142
+
143
+ compoundVariants: [
144
+ // Outlined variant text colors - dynamically generated
145
+ ...Object.entries(theme.palettes || {}).map(([colorKey, palette]: [string, any]) => ({
146
+ variant: 'outlined',
147
+ color: colorKey,
148
+ styles: {
149
+ color: palette?.[500] || '#6b7280',
150
+ },
151
+ })),
152
+ ],
153
+ },
154
+ }));
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { getWebProps } from 'react-native-unistyles/web';
3
+ import { BadgeProps } from './types';
4
+ import { badgeStyles } from './Badge.styles';
5
+
6
+ const Badge: React.FC<BadgeProps> = ({
7
+ children,
8
+ size = 'medium',
9
+ variant = 'filled',
10
+ color = 'blue',
11
+ style,
12
+ testID,
13
+ }) => {
14
+ badgeStyles.useVariants({
15
+ size,
16
+ variant: variant as any,
17
+ color,
18
+ });
19
+
20
+ const badgeStyleArray = [badgeStyles.badge, style];
21
+ const badgeProps = getWebProps(badgeStyleArray);
22
+
23
+ if (variant === 'dot') {
24
+ return (
25
+ <span
26
+ {...badgeProps}
27
+ data-testid={testID}
28
+ role="status"
29
+ aria-label="status indicator"
30
+ />
31
+ );
32
+ }
33
+
34
+ return (
35
+ <span
36
+ {...badgeProps}
37
+ data-testid={testID}
38
+ role="status"
39
+ >
40
+ {children}
41
+ </span>
42
+ );
43
+ };
44
+
45
+ export default Badge;
@@ -0,0 +1,2 @@
1
+ export { default } from './Badge.native';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { default } from './Badge.web';
2
+ export * from './types';
@@ -0,0 +1,2 @@
1
+ export { default } from './Badge.web';
2
+ export * from './types';
@@ -0,0 +1,34 @@
1
+ import { ReactNode } from 'react';
2
+ import type { DisplayColorVariant } from '../theme/variants';
3
+
4
+ export interface BadgeProps {
5
+ /**
6
+ * The content to display inside the badge
7
+ */
8
+ children?: ReactNode;
9
+
10
+ /**
11
+ * The size of the badge
12
+ */
13
+ size?: 'small' | 'medium' | 'large';
14
+
15
+ /**
16
+ * The visual style variant of the badge
17
+ */
18
+ variant?: 'filled' | 'outlined' | 'dot';
19
+
20
+ /**
21
+ * The color scheme of the badge
22
+ */
23
+ color?: DisplayColorVariant;
24
+
25
+ /**
26
+ * Additional styles
27
+ */
28
+ style?: any;
29
+
30
+ /**
31
+ * Test ID for testing
32
+ */
33
+ testID?: string;
34
+ }