@grupalia/rn-ui-kit 0.0.41 → 0.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 (141) hide show
  1. package/lib/commonjs/components/BaseAlert.js +149 -0
  2. package/lib/commonjs/components/BaseAlert.js.map +1 -0
  3. package/lib/commonjs/components/BaseBadge.js +40 -19
  4. package/lib/commonjs/components/BaseBadge.js.map +1 -1
  5. package/lib/commonjs/components/BaseButton.js +71 -38
  6. package/lib/commonjs/components/BaseButton.js.map +1 -1
  7. package/lib/commonjs/components/BaseIcon.js +31 -0
  8. package/lib/commonjs/components/BaseIcon.js.map +1 -0
  9. package/lib/commonjs/components/BaseSpinner.js +21 -0
  10. package/lib/commonjs/components/BaseSpinner.js.map +1 -0
  11. package/lib/commonjs/components/BaseStackedList.js +108 -0
  12. package/lib/commonjs/components/BaseStackedList.js.map +1 -0
  13. package/lib/commonjs/components/index.js +28 -0
  14. package/lib/commonjs/components/index.js.map +1 -1
  15. package/lib/commonjs/hoc-components.js +40 -0
  16. package/lib/commonjs/hoc-components.js.map +1 -0
  17. package/lib/commonjs/hooks/useBreakpoints.js +25 -0
  18. package/lib/commonjs/hooks/useBreakpoints.js.map +1 -0
  19. package/lib/commonjs/hooks/useIsAboveBreakpoint.js +15 -0
  20. package/lib/commonjs/hooks/useIsAboveBreakpoint.js.map +1 -0
  21. package/lib/commonjs/index.js +17 -1
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/preset.js +6 -2
  24. package/lib/commonjs/preset.js.map +1 -1
  25. package/lib/commonjs/styles/background-colors.js +1 -1
  26. package/lib/commonjs/styles/background-colors.js.map +1 -1
  27. package/lib/commonjs/styles/border-colors.js +1 -1
  28. package/lib/commonjs/styles/border-colors.js.map +1 -1
  29. package/lib/commonjs/styles/foreground-colors.js +0 -12
  30. package/lib/commonjs/styles/foreground-colors.js.map +1 -1
  31. package/lib/commonjs/styles/text-colors.js +1 -1
  32. package/lib/commonjs/styles/text-colors.js.map +1 -1
  33. package/lib/commonjs/styles/utility-colors.js +124 -124
  34. package/lib/commonjs/styles/utility-colors.js.map +1 -1
  35. package/lib/module/components/BaseAlert.js +144 -0
  36. package/lib/module/components/BaseAlert.js.map +1 -0
  37. package/lib/module/components/BaseBadge.js +39 -18
  38. package/lib/module/components/BaseBadge.js.map +1 -1
  39. package/lib/module/components/BaseButton.js +68 -35
  40. package/lib/module/components/BaseButton.js.map +1 -1
  41. package/lib/module/components/BaseIcon.js +26 -0
  42. package/lib/module/components/BaseIcon.js.map +1 -0
  43. package/lib/module/components/BaseSpinner.js +16 -0
  44. package/lib/module/components/BaseSpinner.js.map +1 -0
  45. package/lib/module/components/BaseStackedList.js +102 -0
  46. package/lib/module/components/BaseStackedList.js.map +1 -0
  47. package/lib/module/components/index.js +4 -0
  48. package/lib/module/components/index.js.map +1 -1
  49. package/lib/module/hoc-components.js +37 -0
  50. package/lib/module/hoc-components.js.map +1 -0
  51. package/lib/module/hooks/useBreakpoints.js +20 -0
  52. package/lib/module/hooks/useBreakpoints.js.map +1 -0
  53. package/lib/module/hooks/useIsAboveBreakpoint.js +11 -0
  54. package/lib/module/hooks/useIsAboveBreakpoint.js.map +1 -0
  55. package/lib/module/index.js +2 -0
  56. package/lib/module/index.js.map +1 -1
  57. package/lib/module/preset.js +6 -2
  58. package/lib/module/preset.js.map +1 -1
  59. package/lib/module/styles/background-colors.js +1 -1
  60. package/lib/module/styles/background-colors.js.map +1 -1
  61. package/lib/module/styles/border-colors.js +1 -1
  62. package/lib/module/styles/border-colors.js.map +1 -1
  63. package/lib/module/styles/foreground-colors.js +0 -12
  64. package/lib/module/styles/foreground-colors.js.map +1 -1
  65. package/lib/module/styles/text-colors.js +1 -1
  66. package/lib/module/styles/text-colors.js.map +1 -1
  67. package/lib/module/styles/utility-colors.js +124 -124
  68. package/lib/module/styles/utility-colors.js.map +1 -1
  69. package/lib/typescript/commonjs/components/BaseAlert.d.ts +11 -0
  70. package/lib/typescript/commonjs/components/BaseAlert.d.ts.map +1 -0
  71. package/lib/typescript/commonjs/components/BaseBadge.d.ts +11 -5
  72. package/lib/typescript/commonjs/components/BaseBadge.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/components/BaseButton.d.ts +9 -4
  74. package/lib/typescript/commonjs/components/BaseButton.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/components/BaseIcon.d.ts +19 -0
  76. package/lib/typescript/commonjs/components/BaseIcon.d.ts.map +1 -0
  77. package/lib/typescript/commonjs/components/BaseSpinner.d.ts +8 -0
  78. package/lib/typescript/commonjs/components/BaseSpinner.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/components/BaseStackedList.d.ts +23 -0
  80. package/lib/typescript/commonjs/components/BaseStackedList.d.ts.map +1 -0
  81. package/lib/typescript/commonjs/components/index.d.ts +4 -0
  82. package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/hoc-components.d.ts +139 -0
  84. package/lib/typescript/commonjs/hoc-components.d.ts.map +1 -0
  85. package/lib/typescript/commonjs/hooks/useBreakpoints.d.ts +8 -0
  86. package/lib/typescript/commonjs/hooks/useBreakpoints.d.ts.map +1 -0
  87. package/lib/typescript/commonjs/hooks/useIsAboveBreakpoint.d.ts +3 -0
  88. package/lib/typescript/commonjs/hooks/useIsAboveBreakpoint.d.ts.map +1 -0
  89. package/lib/typescript/commonjs/index.d.ts +2 -0
  90. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/preset.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/styles/foreground-colors.d.ts +1 -12
  93. package/lib/typescript/commonjs/styles/foreground-colors.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/styles/utility-colors.d.ts +125 -124
  95. package/lib/typescript/commonjs/styles/utility-colors.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/types/heroicons.d.ts +2 -2
  97. package/lib/typescript/commonjs/types/heroicons.d.ts.map +1 -1
  98. package/lib/typescript/module/components/BaseAlert.d.ts +11 -0
  99. package/lib/typescript/module/components/BaseAlert.d.ts.map +1 -0
  100. package/lib/typescript/module/components/BaseBadge.d.ts +11 -5
  101. package/lib/typescript/module/components/BaseBadge.d.ts.map +1 -1
  102. package/lib/typescript/module/components/BaseButton.d.ts +9 -4
  103. package/lib/typescript/module/components/BaseButton.d.ts.map +1 -1
  104. package/lib/typescript/module/components/BaseIcon.d.ts +19 -0
  105. package/lib/typescript/module/components/BaseIcon.d.ts.map +1 -0
  106. package/lib/typescript/module/components/BaseSpinner.d.ts +8 -0
  107. package/lib/typescript/module/components/BaseSpinner.d.ts.map +1 -0
  108. package/lib/typescript/module/components/BaseStackedList.d.ts +23 -0
  109. package/lib/typescript/module/components/BaseStackedList.d.ts.map +1 -0
  110. package/lib/typescript/module/components/index.d.ts +4 -0
  111. package/lib/typescript/module/components/index.d.ts.map +1 -1
  112. package/lib/typescript/module/hoc-components.d.ts +139 -0
  113. package/lib/typescript/module/hoc-components.d.ts.map +1 -0
  114. package/lib/typescript/module/hooks/useBreakpoints.d.ts +8 -0
  115. package/lib/typescript/module/hooks/useBreakpoints.d.ts.map +1 -0
  116. package/lib/typescript/module/hooks/useIsAboveBreakpoint.d.ts +3 -0
  117. package/lib/typescript/module/hooks/useIsAboveBreakpoint.d.ts.map +1 -0
  118. package/lib/typescript/module/index.d.ts +2 -0
  119. package/lib/typescript/module/index.d.ts.map +1 -1
  120. package/lib/typescript/module/preset.d.ts.map +1 -1
  121. package/lib/typescript/module/styles/foreground-colors.d.ts +1 -12
  122. package/lib/typescript/module/styles/foreground-colors.d.ts.map +1 -1
  123. package/lib/typescript/module/styles/utility-colors.d.ts +125 -124
  124. package/lib/typescript/module/styles/utility-colors.d.ts.map +1 -1
  125. package/lib/typescript/module/types/heroicons.d.ts +2 -2
  126. package/lib/typescript/module/types/heroicons.d.ts.map +1 -1
  127. package/package.json +8 -4
  128. package/src/components/BaseAlert.tsx +175 -0
  129. package/src/components/BaseBadge.tsx +191 -0
  130. package/src/components/BaseButton.tsx +288 -0
  131. package/src/components/BaseIcon.tsx +41 -0
  132. package/src/components/BaseSpinner.tsx +18 -0
  133. package/src/components/BaseStackedList.tsx +137 -0
  134. package/src/components/index.ts +6 -0
  135. package/src/preset.cjs +1 -1
  136. package/src/preset.ts +7 -1
  137. package/src/styles/background-colors.ts +1 -1
  138. package/src/styles/border-colors.ts +1 -1
  139. package/src/styles/foreground-colors.ts +19 -12
  140. package/src/styles/text-colors.ts +1 -1
  141. package/src/styles/utility-colors.ts +250 -124
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grupalia/rn-ui-kit",
3
- "version": "0.0.41",
3
+ "version": "0.2.0",
4
4
  "description": "Grupalia React Native UI Kit",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/module/index.d.ts",
@@ -8,7 +8,8 @@
8
8
  "lib",
9
9
  "src/preset.ts",
10
10
  "src/preset.cjs",
11
- "src/styles"
11
+ "src/styles",
12
+ "src/components"
12
13
  ],
13
14
  "scripts": {
14
15
  "dev": "chokidar \"src/**/*\" -c \"yarn bob build\"",
@@ -22,7 +23,9 @@
22
23
  "nativewind": "^2.0.11",
23
24
  "react": "*",
24
25
  "react-native": "*",
25
- "tailwindcss": "3.3.2"
26
+ "tailwindcss": "3.3.2",
27
+ "react-native-heroicons": "^4.0.0",
28
+ "react-native-svg": "*"
26
29
  },
27
30
  "devDependencies": {
28
31
  "@babel/core": "^7.20.0",
@@ -44,7 +47,8 @@
44
47
  "react-native-builder-bob": "^0.40.6",
45
48
  "react-native-svg": "^15.11.2",
46
49
  "tailwindcss": "3.3.2",
47
- "typescript": "~5.3.3"
50
+ "typescript": "~5.3.3",
51
+ "react-native-heroicons": "^4.0.0"
48
52
  },
49
53
  "exports": {
50
54
  ".": {
@@ -0,0 +1,175 @@
1
+ import clsx from 'clsx';
2
+ import { InformationCircleIcon as OutlinedInformationCircleIcon } from 'react-native-heroicons/outline';
3
+ import {
4
+ ExclamationTriangleIcon,
5
+ CheckCircleIcon,
6
+ InformationCircleIcon,
7
+ XCircleIcon,
8
+ } from 'react-native-heroicons/solid';
9
+
10
+ import { View, Text } from '../hoc-components';
11
+ import BaseIcon from './BaseIcon';
12
+ import { useIsAboveBreakpoint } from '../hooks/useIsAboveBreakpoint';
13
+
14
+ interface IconComponentProps {
15
+ type: 'error' | 'success' | 'alert' | 'info' | 'notice';
16
+ variant?: 'primary' | 'secondary';
17
+ }
18
+
19
+ function IconComponent({ type, variant = 'primary' }: IconComponentProps) {
20
+ const iconColors = {
21
+ error: {
22
+ primary: 'fg-error-primary',
23
+ secondary: 'fg-error-secondary',
24
+ },
25
+ success: {
26
+ primary: 'fg-success-primary',
27
+ secondary: 'fg-success-secondary',
28
+ },
29
+ alert: {
30
+ primary: 'fg-warning-primary',
31
+ secondary: 'fg-warning-secondary',
32
+ },
33
+ info: {
34
+ primary: 'utility-gray-500',
35
+ secondary: 'utility-gray-400',
36
+ },
37
+ notice: {
38
+ primary: 'utility-blue-500',
39
+ secondary: 'utility-blue-400',
40
+ },
41
+ } as const;
42
+
43
+ const icons = {
44
+ error: XCircleIcon,
45
+ success: CheckCircleIcon,
46
+ alert: ExclamationTriangleIcon,
47
+ info: OutlinedInformationCircleIcon,
48
+ notice: InformationCircleIcon,
49
+ };
50
+
51
+ return (
52
+ <BaseIcon
53
+ icon={icons[type]}
54
+ size={20}
55
+ color={iconColors[type][variant]}
56
+ />
57
+ );
58
+ }
59
+
60
+ interface Props extends React.ComponentProps<typeof View> {
61
+ type: 'error' | 'success' | 'alert' | 'info' | 'notice';
62
+ message: string;
63
+ description?: string;
64
+ variant?: 'primary' | 'secondary';
65
+ }
66
+
67
+ export default function AlertComponent({
68
+ type,
69
+ message,
70
+ description,
71
+ variant = 'primary',
72
+ children,
73
+ ...props
74
+ }: Props) {
75
+ const largePhone = useIsAboveBreakpoint('xs');
76
+ const alertClasses = () => {
77
+ if (variant === 'secondary') {
78
+ return 'bg-white border';
79
+ }
80
+
81
+ switch (type) {
82
+ case 'error':
83
+ return 'bg-error-secondary';
84
+ case 'success':
85
+ return 'bg-success-secondary';
86
+ case 'alert':
87
+ return 'bg-yellow-50 border-yellow-400';
88
+ case 'info':
89
+ return 'bg-white border border-gray-300';
90
+ case 'notice':
91
+ default:
92
+ return 'bg-blue-100';
93
+ }
94
+ };
95
+
96
+ const textColor = () => {
97
+ if (variant === 'secondary') {
98
+ switch (type) {
99
+ case 'error':
100
+ return 'text-red-500';
101
+ case 'success':
102
+ return 'text-green-500';
103
+ case 'alert':
104
+ return 'text-yellow-500';
105
+ case 'info':
106
+ case 'notice':
107
+ default:
108
+ return 'text-blue-500';
109
+ }
110
+ } else {
111
+ switch (type) {
112
+ case 'error':
113
+ return 'text-red-700';
114
+ case 'success':
115
+ return 'text-green-700';
116
+ case 'alert':
117
+ return 'text-yellow-700';
118
+ case 'info':
119
+ return 'text-gray-500';
120
+ case 'notice':
121
+ default:
122
+ return 'text-blue-700';
123
+ }
124
+ }
125
+ };
126
+
127
+ const borderColor = () => {
128
+ if (variant === 'secondary') {
129
+ switch (type) {
130
+ case 'error':
131
+ return 'border-red-300';
132
+ case 'success':
133
+ return 'border-green-300';
134
+ case 'alert':
135
+ return 'border-yellow-300';
136
+ case 'info':
137
+ case 'notice':
138
+ default:
139
+ return 'border-blue-300';
140
+ }
141
+ }
142
+
143
+ return '';
144
+ };
145
+
146
+ return (
147
+ <View
148
+ {...props}
149
+ className={clsx(
150
+ 'flex flex-row rounded-lg',
151
+ alertClasses(),
152
+ borderColor(),
153
+ largePhone ? 'px-2 py-4' : 'px-1 py-2',
154
+ )}
155
+ >
156
+ <View className="shrink-0">
157
+ <IconComponent
158
+ type={type}
159
+ variant={variant}
160
+ />
161
+ </View>
162
+ <View className={clsx('flex flex-1 flex-col', largePhone ? 'ml-3' : 'ml-1')}>
163
+ <Text className={clsx('font-medium', textColor(), largePhone ? 'text-base' : 'text-xs')}>
164
+ {message}
165
+ </Text>
166
+ {description && (
167
+ <Text className={clsx('text-xs text-gray-700', largePhone ? 'mt-3' : 'mt-1')}>
168
+ {description}
169
+ </Text>
170
+ )}
171
+ {children}
172
+ </View>
173
+ </View>
174
+ );
175
+ }
@@ -0,0 +1,191 @@
1
+ import clsx from 'clsx';
2
+ import { styled } from 'nativewind';
3
+ import { ViewProps } from 'react-native';
4
+
5
+ import { View, Text } from '../hoc-components';
6
+ import BaseIcon from './BaseIcon';
7
+ import { HeroIcon } from '../types/heroicons';
8
+
9
+ export type BadgeVariant =
10
+ 'gray' |
11
+ 'brand' |
12
+ 'error' |
13
+ 'warning' |
14
+ 'success' |
15
+ 'blue_gray' |
16
+ 'blue_light' |
17
+ 'blue' |
18
+ 'indigo' |
19
+ 'purple' |
20
+ 'pink' |
21
+ 'orange';
22
+
23
+ export type BadgeSize = 'sm' | 'md' | 'lg';
24
+
25
+ interface BaseBadgeProps extends ViewProps {
26
+ variant?: BadgeVariant;
27
+ size?: BadgeSize;
28
+ text: string;
29
+ transparent?: boolean;
30
+ squared?: boolean;
31
+ leftIcon?: HeroIcon | null;
32
+ rightIcon?: HeroIcon | null;
33
+ }
34
+
35
+ function BaseBadge({
36
+ variant = 'gray',
37
+ size = 'md',
38
+ text,
39
+ transparent = false,
40
+ squared = false,
41
+ leftIcon = null,
42
+ rightIcon = null,
43
+ ...props
44
+ }: BaseBadgeProps) {
45
+ const variantStyles = {
46
+ gray: {
47
+ bg: 'bg-utility-gray-50',
48
+ text: 'text-utility-gray-700',
49
+ border: 'border-utility-gray-200',
50
+ },
51
+ brand: {
52
+ bg: 'bg-utility-brand-50',
53
+ text: 'text-utility-brand-700',
54
+ border: 'border-utility-brand-200',
55
+ },
56
+ error: {
57
+ bg: 'bg-utility-error-50',
58
+ text: 'text-utility-error-700',
59
+ border: 'border-utility-error-200',
60
+ },
61
+ warning: {
62
+ bg: 'bg-utility-warning-50',
63
+ text: 'text-utility-warning-700',
64
+ border: 'border-utility-warning-200',
65
+ },
66
+ success: {
67
+ bg: 'bg-utility-success-50',
68
+ text: 'text-utility-success-700',
69
+ border: 'border-utility-success-200',
70
+ },
71
+ blue_gray: {
72
+ bg: 'bg-utility-gray_blue-50',
73
+ text: 'text-utility-gray_blue-700',
74
+ border: 'border-utility-gray_blue-200',
75
+ },
76
+ blue_light: {
77
+ bg: 'bg-utility-blue_light-50',
78
+ text: 'text-utility-blue_light-700',
79
+ border: 'border-utility-blue_light-200',
80
+ },
81
+ blue: {
82
+ bg: 'bg-utility-blue-50',
83
+ text: 'text-utility-blue-700',
84
+ border: 'border-utility-blue-200',
85
+ },
86
+ indigo: {
87
+ bg: 'bg-utility-indigo-50',
88
+ text: 'text-utility-indigo-700',
89
+ border: 'border-utility-indigo-200',
90
+ },
91
+ purple: {
92
+ bg: 'bg-utility-purple-50',
93
+ text: 'text-utility-purple-700',
94
+ border: 'border-utility-purple-200',
95
+ },
96
+ pink: {
97
+ bg: 'bg-utility-pink-50',
98
+ text: 'text-utility-pink-700',
99
+ border: 'border-utility-pink-200',
100
+ },
101
+ orange: {
102
+ bg: 'bg-utility-orange-50',
103
+ text: 'text-utility-orange-700',
104
+ border: 'border-utility-orange-200',
105
+ },
106
+ };
107
+
108
+ const sizeStyles = {
109
+ sm: {
110
+ container: 'px-1.5 py-0.5',
111
+ text: 'text-xs',
112
+ },
113
+ md: {
114
+ container: 'px-2.5 py-0.5',
115
+ text: 'text-sm',
116
+ },
117
+ lg: {
118
+ container: 'px-3 py-1',
119
+ text: 'text-base',
120
+ },
121
+ };
122
+
123
+ const iconSizes = {
124
+ sm: 12,
125
+ md: 14,
126
+ lg: 16,
127
+ };
128
+
129
+ const containerStyles = clsx(
130
+ 'flex-row items-center justify-center border',
131
+ squared ? 'rounded-md' : 'rounded-full',
132
+ transparent ? 'bg-transparent' : variantStyles[variant].bg,
133
+ transparent ? 'border-primary' : variantStyles[variant].border,
134
+ sizeStyles[size].container,
135
+ );
136
+
137
+ const textStyles = clsx(
138
+ 'font-medium',
139
+ variantStyles[variant].text,
140
+ sizeStyles[size].text,
141
+ );
142
+
143
+ const iconSpacingStyles = {
144
+ left: 'mr-1.5',
145
+ right: 'ml-1.5',
146
+ };
147
+
148
+ const iconColors = {
149
+ gray: 'utility-gray-500',
150
+ brand: 'utility-brand-500',
151
+ error: 'utility-error-500',
152
+ warning: 'utility-warning-500',
153
+ success: 'utility-success-500',
154
+ blue_gray: 'utility-gray_blue-500',
155
+ blue_light: 'utility-blue_light-500',
156
+ blue: 'utility-blue-500',
157
+ indigo: 'utility-indigo-500',
158
+ purple: 'utility-purple-500',
159
+ pink: 'utility-pink-500',
160
+ orange: 'utility-orange-500',
161
+ } as const;
162
+
163
+ return (
164
+ <View
165
+ className={containerStyles}
166
+ {...props}
167
+ >
168
+ {leftIcon && (
169
+ <View className={iconSpacingStyles.left}>
170
+ <BaseIcon
171
+ icon={leftIcon}
172
+ size={iconSizes[size]}
173
+ color={iconColors[variant]}
174
+ />
175
+ </View>
176
+ )}
177
+ <Text className={textStyles}>{text}</Text>
178
+ {rightIcon && (
179
+ <View className={iconSpacingStyles.right}>
180
+ <BaseIcon
181
+ icon={rightIcon}
182
+ size={iconSizes[size]}
183
+ color={iconColors[variant]}
184
+ />
185
+ </View>
186
+ )}
187
+ </View>
188
+ );
189
+ }
190
+
191
+ export default styled(BaseBadge);
@@ -0,0 +1,288 @@
1
+ import clsx from 'clsx';
2
+ import { styled } from 'nativewind';
3
+ import React from 'react';
4
+ import { PressableProps } from 'react-native';
5
+
6
+ import {
7
+ Pressable, Text, View, ActivityIndicator,
8
+ } from '../hoc-components';
9
+ import BaseIcon from './BaseIcon';
10
+ import { Color as ForegroundColor } from '../styles/foreground-colors';
11
+ import { HeroIcon } from '../types/heroicons';
12
+
13
+ interface ButtonProps extends PressableProps {
14
+ variant?: 'primary' | 'secondary' | 'tertiary' | 'link-color' | 'link-gray';
15
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
16
+ text?: string;
17
+ loading?: boolean;
18
+ disabled?: boolean;
19
+ destructive?: boolean;
20
+ leftIcon?: HeroIcon | null;
21
+ rightIcon?: HeroIcon | null;
22
+ countdownSecs?: number;
23
+ }
24
+
25
+ function BaseButton({
26
+ variant = 'primary',
27
+ size = 'md',
28
+ text = '',
29
+ loading = false,
30
+ disabled = false,
31
+ destructive = false,
32
+ leftIcon = null,
33
+ rightIcon = null,
34
+ hitSlop = 6,
35
+ countdownSecs = 0,
36
+ ...props
37
+ }: ButtonProps) {
38
+ const [timer, setTimer] = React.useState(countdownSecs);
39
+ const [isDisabled, setIsDisabled] = React.useState(disabled || countdownSecs > 0);
40
+
41
+ React.useEffect(() => {
42
+ let intervalId: NodeJS.Timeout;
43
+ if (countdownSecs > 0) {
44
+ setIsDisabled(true);
45
+ setTimer(countdownSecs);
46
+ intervalId = setInterval(() => {
47
+ setTimer((prevTimer: number) => {
48
+ if (prevTimer <= 1) {
49
+ clearInterval(intervalId);
50
+ setIsDisabled(Boolean(disabled));
51
+ return 0;
52
+ }
53
+ return prevTimer - 1;
54
+ });
55
+ }, 1000);
56
+ } else {
57
+ setIsDisabled(Boolean(disabled));
58
+ }
59
+ return () => {
60
+ if (intervalId) clearInterval(intervalId);
61
+ };
62
+ }, [countdownSecs, disabled]);
63
+
64
+ const sizeClasses = {
65
+ xs: 'rounded-md h-7',
66
+ sm: 'rounded-md h-8',
67
+ md: 'rounded-lg h-9',
68
+ lg: 'rounded-lg h-10',
69
+ xl: 'rounded-lg h-11',
70
+ };
71
+
72
+ const iconOnlyClasses = {
73
+ xs: 'rounded-md h-8 w-8',
74
+ sm: 'rounded-md h-9 w-9',
75
+ md: 'rounded-lg h-10 w-10',
76
+ lg: 'rounded-lg h-11 w-11',
77
+ xl: 'rounded-lg h-12 w-12',
78
+ };
79
+
80
+ const containerClasses = {
81
+ primary: {
82
+ default: destructive ? 'bg-error-solid' : 'bg-brand-solid',
83
+ disabled: 'bg-disabled border border-disabled_subtle',
84
+ loading: destructive ? 'bg-error-solid' : 'bg-brand-solid_hover',
85
+ },
86
+ secondary: {
87
+ default: destructive ? 'bg-primary border border-error_subtle' : 'bg-primary border border-primary',
88
+ disabled: 'bg-primary border border-disabled_subtle',
89
+ loading: destructive ? 'bg-error-primary border border-error_subtle' : 'bg-primary_hover border border-primary',
90
+ },
91
+ tertiary: {
92
+ default: 'bg-transparent',
93
+ disabled: 'bg-transparent',
94
+ loading: destructive ? 'bg-error-primary' : 'bg-primary_hover',
95
+ },
96
+ 'link-color': {
97
+ default: 'bg-transparent',
98
+ disabled: 'bg-transparent',
99
+ loading: 'bg-transparent',
100
+ },
101
+ 'link-gray': {
102
+ default: 'bg-transparent',
103
+ disabled: 'bg-transparent',
104
+ loading: 'bg-transparent',
105
+ },
106
+ };
107
+
108
+ const textColorClasses = {
109
+ primary: {
110
+ default: 'text-white',
111
+ disabled: 'text-disabled',
112
+ loading: 'text-white',
113
+ },
114
+ secondary: {
115
+ default: destructive ? 'text-error' : 'text-secondary',
116
+ disabled: 'text-disabled',
117
+ loading: destructive ? 'text-error-primary' : 'text-secondary',
118
+ },
119
+ tertiary: {
120
+ default: destructive ? 'text-error' : 'text-tertiary',
121
+ disabled: 'text-disabled',
122
+ loading: destructive ? 'text-error-primary' : 'text-tertiary',
123
+ },
124
+ 'link-color': {
125
+ default: destructive ? 'text-error' : 'text-brand-tertiary',
126
+ disabled: 'text-disabled',
127
+ loading: destructive ? 'text-error' : 'text-brand-tertiary',
128
+ },
129
+ 'link-gray': {
130
+ default: destructive ? 'text-error' : 'text-tertiary',
131
+ disabled: 'text-disabled',
132
+ loading: destructive ? 'text-error' : 'text-tertiary',
133
+ },
134
+ };
135
+
136
+ const textSizeClasses = {
137
+ xs: 'text-xs',
138
+ sm: 'text-sm',
139
+ md: 'text-sm',
140
+ lg: 'text-sm',
141
+ xl: 'text-base',
142
+ };
143
+
144
+ const iconSizes = {
145
+ xs: 16,
146
+ sm: 18,
147
+ md: 20,
148
+ lg: 22,
149
+ xl: 24,
150
+ };
151
+
152
+ let buttonState: 'default' | 'disabled' | 'loading' = 'default';
153
+ if (loading) {
154
+ buttonState = 'loading';
155
+ } else if (isDisabled) {
156
+ buttonState = 'disabled';
157
+ }
158
+
159
+ const isIconOnly = !text && (leftIcon || rightIcon);
160
+
161
+ const buttonStyles = clsx(
162
+ 'flex flex-row items-center justify-center',
163
+ isIconOnly ? iconOnlyClasses[size] : sizeClasses[size],
164
+ containerClasses[variant][buttonState],
165
+ );
166
+
167
+ const textStyles = clsx(
168
+ 'font-medium',
169
+ textSizeClasses[size],
170
+ textColorClasses[variant][buttonState],
171
+ );
172
+
173
+ const iconColors = {
174
+ primary: {
175
+ default: 'fg-white',
176
+ disabled: 'fg-disabled',
177
+ loading: 'fg-white',
178
+ },
179
+ secondary: {
180
+ default: 'fg-secondary',
181
+ disabled: 'fg-disabled',
182
+ loading: 'fg-secondary',
183
+ },
184
+ tertiary: {
185
+ default: 'fg-tertiary',
186
+ disabled: 'fg-disabled',
187
+ loading: 'fg-tertiary',
188
+ },
189
+ 'link-color': {
190
+ default: 'fg-brand-primary',
191
+ disabled: 'fg-disabled',
192
+ loading: 'fg-brand-primary',
193
+ },
194
+ 'link-gray': {
195
+ default: 'fg-secondary',
196
+ disabled: 'fg-disabled',
197
+ loading: 'fg-secondary',
198
+ },
199
+ };
200
+
201
+ const spinnerColorMap = {
202
+ primary: {
203
+ default: '#fff',
204
+ disabled: '#9AA4B2', // text-disabled color
205
+ loading: '#fff',
206
+ },
207
+ secondary: {
208
+ default: destructive ? '#F04438' : '#667085',
209
+ disabled: '#9AA4B2', // text-disabled color
210
+ loading: destructive ? '#F04438' : '#667085',
211
+ },
212
+ tertiary: {
213
+ default: destructive ? '#F04438' : '#667085',
214
+ disabled: '#9AA4B2', // text-disabled color
215
+ loading: destructive ? '#F04438' : '#667085',
216
+ },
217
+ 'link-color': {
218
+ default: destructive ? '#F04438' : '#7F56D9',
219
+ disabled: '#9AA4B2', // text-disabled color
220
+ loading: destructive ? '#F04438' : '#7F56D9',
221
+ },
222
+ 'link-gray': {
223
+ default: destructive ? '#F04438' : '#667085',
224
+ disabled: '#9AA4B2', // text-disabled color
225
+ loading: destructive ? '#F04438' : '#667085',
226
+ },
227
+ };
228
+
229
+ let spinnerColor = '#fff';
230
+ if (buttonState === 'disabled') {
231
+ spinnerColor = spinnerColorMap[variant].disabled;
232
+ } else if (buttonState === 'loading') {
233
+ spinnerColor = spinnerColorMap[variant].loading;
234
+ } else {
235
+ spinnerColor = spinnerColorMap[variant].default;
236
+ }
237
+
238
+ return (
239
+ <Pressable
240
+ disabled={isDisabled || loading}
241
+ className={buttonStyles}
242
+ hitSlop={hitSlop}
243
+ {...props}
244
+ >
245
+ <View className={clsx('flex flex-row items-center justify-center', loading && 'opacity-0')}>
246
+ {leftIcon && (
247
+ <BaseIcon
248
+ icon={leftIcon}
249
+ size={iconSizes[size]}
250
+ color={iconColors[variant][buttonState] as ForegroundColor}
251
+ className="mr-1.5"
252
+ />
253
+ )}
254
+ {text && (
255
+ <Text className={textStyles}>
256
+ {text}
257
+ {timer > 0 ? ` (${timer})` : ''}
258
+ </Text>
259
+ )}
260
+ {rightIcon && (
261
+ <BaseIcon
262
+ icon={rightIcon}
263
+ size={iconSizes[size]}
264
+ color={iconColors[variant][buttonState] as ForegroundColor}
265
+ className="ml-1.5"
266
+ />
267
+ )}
268
+ </View>
269
+ {loading && (
270
+ <ActivityIndicator
271
+ size="small"
272
+ color={spinnerColor}
273
+ style={{
274
+ position: 'absolute',
275
+ top: 0,
276
+ left: 0,
277
+ right: 0,
278
+ bottom: 0,
279
+ justifyContent: 'center',
280
+ alignItems: 'center',
281
+ }}
282
+ />
283
+ )}
284
+ </Pressable>
285
+ );
286
+ }
287
+
288
+ export default styled(BaseButton);