react-native-varia 0.0.1

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 (44) hide show
  1. package/README.md +53 -0
  2. package/bin/cli.js +200 -0
  3. package/lib/components/Badge.tsx +96 -0
  4. package/lib/components/Button.tsx +131 -0
  5. package/lib/components/CircleProgress.tsx +180 -0
  6. package/lib/components/Divider.tsx +43 -0
  7. package/lib/components/GradientBackground.tsx +68 -0
  8. package/lib/components/GradientText.tsx +121 -0
  9. package/lib/components/Icon.tsx +13 -0
  10. package/lib/components/IconWrapper.tsx +109 -0
  11. package/lib/components/Input.tsx +120 -0
  12. package/lib/components/Link.tsx +87 -0
  13. package/lib/components/Modal.tsx +157 -0
  14. package/lib/components/ReText.tsx +124 -0
  15. package/lib/components/Slider.tsx +334 -0
  16. package/lib/components/Slideshow.tsx +317 -0
  17. package/lib/components/SlidingDrawer.tsx +307 -0
  18. package/lib/components/Spinner.tsx +44 -0
  19. package/lib/components/Switch.tsx +224 -0
  20. package/lib/components/Text.tsx +107 -0
  21. package/lib/components/index.tsx +83 -0
  22. package/lib/mixins.ts +180 -0
  23. package/lib/patterns/index.tsx +426 -0
  24. package/lib/theme/Badge.recipe.tsx +68 -0
  25. package/lib/theme/Button.recipe-old.tsx +67 -0
  26. package/lib/theme/Button.recipe.tsx +83 -0
  27. package/lib/theme/CircleProgress.recipe.tsx +42 -0
  28. package/lib/theme/GradientBackground.recipe.tsx +38 -0
  29. package/lib/theme/GradientText.recipe.tsx +49 -0
  30. package/lib/theme/Icon.recipe.tsx +122 -0
  31. package/lib/theme/IconWrapper.recipe.tsx +121 -0
  32. package/lib/theme/Input.recipe.tsx +110 -0
  33. package/lib/theme/Link.recipe.tsx +51 -0
  34. package/lib/theme/Modal.recipe.tsx +34 -0
  35. package/lib/theme/ReText.recipe.tsx +7 -0
  36. package/lib/theme/Slider.recipe.tsx +226 -0
  37. package/lib/theme/Slideshow.recipe.tsx +142 -0
  38. package/lib/theme/SlidingDrawer.recipe.tsx +91 -0
  39. package/lib/theme/Spinner.recipe.tsx +8 -0
  40. package/lib/theme/Switch.recipe.tsx +70 -0
  41. package/lib/theme/Text.recipe.tsx +10 -0
  42. package/lib/types.ts +70 -0
  43. package/lib/utils.ts +80 -0
  44. package/package.json +18 -0
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # React Native Varia
2
+
3
+ A Component library based on **react-native-unistyles** that provides a CLI tool for React Native to effortlessly scaffold components, icons, styles, and layouts into your project.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ 1. Install the library as a development dependency:
10
+
11
+ ```bash
12
+ yarn add -D react-native-varia
13
+ ```
14
+
15
+ You also need to install [react-native-unistyles](https://www.unistyl.es/v3/start/getting-started) for advanced styling capabilities:
16
+
17
+ 2. Some components has reanimated dependency.
18
+ If you are going to use the next components:
19
+
20
+ - Slider
21
+ - SlidingDrawer
22
+ - Slideshow
23
+ - Switch
24
+ - ReText
25
+ - Modal
26
+
27
+ Install react-native-reanimated and react-native-worklets.
28
+ Check the [react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) docs.
29
+ And install react-native-gesture-handler.
30
+ Check the [react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation) docs.
31
+
32
+ 3. Install react-native-svg.
33
+
34
+ `yarn add react-native-svg`
35
+
36
+ ## ⚙️ Available Commands
37
+
38
+ 4. Add varia command to package.json
39
+
40
+ ```json
41
+ "bin": {
42
+ "varia": "./node_modules/new-varia-lib/bin/cli.js"
43
+ }
44
+ ```
45
+
46
+ Once installed, you can use the following commands via `npx varia`:
47
+
48
+ | Command | Description |
49
+ | -------------------------------- | -------------------------------------------------------------------------------- |
50
+ | `npx varia setup` | Copies the mixins, types and utils files from **lib** into **src/style**. |
51
+ | `npx varia add <component_name>` | Copies the specified component from **lib/components** into your project. |
52
+ | `npx varia add-icon <icon_name>` | Copies the **Icon.tsx** template and renames it to the specified icon name. |
53
+ | `npx varia add-patterns` | Copies the entire **lib/patterns** folder into **src/patterns** in your project. |
package/bin/cli.js ADDED
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require("commander");
4
+ const path = require("path");
5
+ const fs = require("fs-extra");
6
+
7
+ // Carpetas base
8
+ const COMPONENTS_DIR = path.join(__dirname, "../lib/components");
9
+ const THEME_DIR = path.join(__dirname, "../lib/theme");
10
+
11
+ /**
12
+ * Obtiene la lista de todos los componentes disponibles en la librería.
13
+ */
14
+ function getAvailableComponents() {
15
+ if (!fs.existsSync(COMPONENTS_DIR)) return [];
16
+ return fs.readdirSync(COMPONENTS_DIR).map((name) => path.parse(name).name);
17
+ }
18
+
19
+ /**
20
+ * Copia un archivo desde origen a destino.
21
+ */
22
+ function copyFile(srcPath, destPath, label) {
23
+ if (!fs.existsSync(srcPath)) {
24
+ console.warn(`⚠️ ${label} no encontrado: ${srcPath}`);
25
+ return false;
26
+ }
27
+ try {
28
+ fs.ensureDirSync(path.dirname(destPath)); // crea carpeta destino si no existe
29
+ fs.copySync(srcPath, destPath, { overwrite: true, errorOnExist: false });
30
+ console.log(`✅ ${label} copiado a: ${destPath}`);
31
+ return true;
32
+ } catch (err) {
33
+ console.error(`❌ No se pudo copiar ${label}: ${err.message}`);
34
+ return false;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Copia un componente y su recipe.
40
+ */
41
+ function copyComponentAndRecipe(component, destComponents, destTheme) {
42
+ const componentSrc = path.join(COMPONENTS_DIR, `${component}.tsx`);
43
+ const componentDest = path.join(process.cwd(), destComponents, `${component}.tsx`);
44
+ copyFile(componentSrc, componentDest, `Componente "${component}"`);
45
+
46
+ const recipeSrc = path.join(THEME_DIR, `${component}.recipe.tsx`);
47
+ const recipeDest = path.join(process.cwd(), destTheme, `${component}.recipe.tsx`);
48
+ copyFile(recipeSrc, recipeDest, `Recipe de "${component}"`);
49
+ }
50
+
51
+ program
52
+ .name("varia")
53
+ .description("CLI para instalar componentes de react-native-varia")
54
+ .version("1.0.0");
55
+
56
+ program
57
+ .command('setup')
58
+ .description('Copia mixins y utils a src/styles')
59
+ .action(() => {
60
+ const sourceDir = path.join(__dirname, '../lib');
61
+ const destDir = path.join(process.cwd(), 'src/style');
62
+
63
+ const filesToCopy = ['mixins', 'utils', 'types'];
64
+
65
+ filesToCopy.forEach((file) => {
66
+ const srcPath = path.join(sourceDir, `${file}.ts`);
67
+ const destPath = path.join(destDir, `${file}.ts`);
68
+
69
+ try {
70
+ fs.ensureDirSync(destDir); // Asegura que el directorio de destino exista
71
+ fs.copySync(srcPath, destPath, { overwrite: true });
72
+ console.log(`✅ ${file}.tsx copiado a ${destPath}`);
73
+ } catch (err) {
74
+ console.error(`❌ Error al copiar ${file}.tsx: ${err.message}`);
75
+ }
76
+ });
77
+ });
78
+
79
+ program
80
+ .command('add-patterns')
81
+ .description('Copia la carpeta lib/patterns a src/patterns')
82
+ .action(() => {
83
+ const sourceDir = path.join(__dirname, '../lib/patterns');
84
+ const destDir = path.join(process.cwd(), 'src/patterns');
85
+
86
+ try {
87
+ fs.ensureDirSync(destDir); // Asegura que el directorio de destino exista
88
+ fs.copySync(sourceDir, destDir, { overwrite: true });
89
+ console.log('✅ Carpeta patterns copiada a src/patterns');
90
+ } catch (err) {
91
+ console.error(`❌ Error al copiar patterns: ${err.message}`);
92
+ }
93
+ });
94
+
95
+ program
96
+ .command("add <components...>")
97
+ .description("Copia uno o más componentes y sus recipes desde la librería a tu proyecto")
98
+ .option("-d, --dest <path>", "Ruta de destino de componentes", "src/components")
99
+ .option("-t, --theme <path>", "Ruta de destino de recipes", "src/theme")
100
+ .action((components, options) => {
101
+ const available = getAvailableComponents();
102
+ const componentsCapitalized = components.map(
103
+ (c) => c.charAt(0).toUpperCase() + c.slice(1)
104
+ );
105
+
106
+ const notFound = componentsCapitalized.filter((c) => !available.includes(c));
107
+ if (notFound.length > 0) {
108
+ console.error(`❌ Los siguientes componentes no existen: ${notFound.join(", ")}`);
109
+ console.log("\n📦 Componentes disponibles:");
110
+ available.forEach((name) => console.log(` - ${name}`));
111
+ process.exit(1);
112
+ }
113
+
114
+ componentsCapitalized.forEach((component) => {
115
+ copyComponentAndRecipe(component, options.dest, options.theme);
116
+ });
117
+ });
118
+
119
+
120
+ function copyIconTemplate(iconName, dest) {
121
+ const srcPath = path.join(COMPONENTS_DIR, "Icon.tsx"); // plantilla
122
+ const destPath = path.join(process.cwd(), dest, `${iconName}.tsx`); // nombre final
123
+
124
+ if (!fs.existsSync(srcPath)) {
125
+ console.error(`❌ Plantilla de icono no encontrada en: ${srcPath}`);
126
+ return false;
127
+ }
128
+
129
+ try {
130
+ // Leemos el contenido de la plantilla
131
+ let content = fs.readFileSync(srcPath, "utf-8");
132
+
133
+ // Reemplazamos el nombre del componente (IconName → el nombre deseado)
134
+ content = content.replace(/\bIconName\b/g, iconName);
135
+
136
+ fs.ensureDirSync(path.dirname(destPath)); // crear carpeta si no existe
137
+ fs.writeFileSync(destPath, content);
138
+
139
+ console.log(`✅ Icono "${iconName}" copiado a: ${destPath}`);
140
+ return true;
141
+ } catch (err) {
142
+ console.error(`❌ No se pudo copiar el icono "${iconName}": ${err.message}`);
143
+ return false;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Copia IconWrapper si no existe en la app
149
+ */
150
+ function ensureIconWrapper(destComponents, destTheme) {
151
+ const wrapperComponentDest = path.join(process.cwd(), destComponents, "IconWrapper.tsx");
152
+ const wrapperRecipeDest = path.join(process.cwd(), destTheme, "IconWrapper.recipe.tsx");
153
+
154
+ if (fs.existsSync(wrapperComponentDest)) {
155
+ return; // ya existe
156
+ }
157
+
158
+ const wrapperComponentSrc = path.join(COMPONENTS_DIR, "IconWrapper.tsx");
159
+ const wrapperRecipeSrc = path.join(THEME_DIR, "IconWrapper.recipe.tsx");
160
+
161
+ // Copiar componente y recipe
162
+ if (fs.existsSync(wrapperComponentSrc)) {
163
+ fs.ensureDirSync(path.dirname(wrapperComponentDest));
164
+ fs.copySync(wrapperComponentSrc, wrapperComponentDest, { overwrite: true, errorOnExist: false });
165
+ console.log(`✅ IconWrapper copiado a: ${wrapperComponentDest}`);
166
+ }
167
+
168
+ if (fs.existsSync(wrapperRecipeSrc)) {
169
+ fs.ensureDirSync(path.dirname(wrapperRecipeDest));
170
+ fs.copySync(wrapperRecipeSrc, wrapperRecipeDest, { overwrite: true, errorOnExist: false });
171
+ console.log(`✅ IconWrapper recipe copiado a: ${wrapperRecipeDest}`);
172
+ }
173
+ }
174
+
175
+ // Comando CLI
176
+ program
177
+ .command("add-icon [iconName]")
178
+ .description("Copia un icono basado en la plantilla Icon.tsx")
179
+ .option("-n, --name <iconName>", "Nombre del icono")
180
+ .option("-d, --dest <path>", "Ruta de destino de iconos", "src/icons")
181
+ .option("-c, --components <path>", "Ruta de destino de componentes", "src/components")
182
+ .option("-t, --theme <path>", "Ruta de destino de recipes", "src/theme")
183
+ .action((iconNameArg, options) => {
184
+ const rawName = iconNameArg || options.name;
185
+ if (!rawName) {
186
+ console.error("❌ Debes indicar el nombre del icono como argumento o con --name");
187
+ process.exit(1);
188
+ }
189
+
190
+ // Capitalizamos primera letra
191
+ const finalName = rawName.charAt(0).toUpperCase() + rawName.slice(1);
192
+
193
+ copyIconTemplate(finalName, options.dest);
194
+
195
+ // Aseguramos IconWrapper
196
+ ensureIconWrapper(options.components, options.theme);
197
+ });
198
+
199
+
200
+ program.parse(process.argv);
@@ -0,0 +1,96 @@
1
+ import {Text, View, ViewStyle, StyleProp} from 'react-native'
2
+ import {StyleSheet} from 'react-native-unistyles'
3
+ import BadgeStyles from '../theme/Badge.recipe'
4
+ import {ReactNode} from 'react'
5
+
6
+ type VeritcalPosition = 'top' | 'bottom'
7
+ type HorizontalPosition = 'left' | 'right'
8
+
9
+ interface BadgeProps {
10
+ size: any
11
+ colorPalette: any
12
+ content: number
13
+ max?: number
14
+ children: ReactNode
15
+ x: HorizontalPosition
16
+ y: VeritcalPosition
17
+ backgroundColor?: string
18
+ color?: string
19
+ mixins?: StyleProp<ViewStyle>
20
+ }
21
+
22
+ const Badge = ({
23
+ size,
24
+ colorPalette,
25
+ content,
26
+ max,
27
+ children,
28
+ x,
29
+ y,
30
+ backgroundColor,
31
+ color,
32
+ mixins,
33
+ }: BadgeProps) => {
34
+ BadgeStyles.useVariants({
35
+ size,
36
+ colorPalette,
37
+ })
38
+
39
+ const displayedContent = max && content > max ? `${max}+` : content
40
+
41
+ return (
42
+ <View>
43
+ {children}
44
+ <View
45
+ testID="varia-badge"
46
+ style={[
47
+ styles.badge(
48
+ x,
49
+ y,
50
+ // resolvedBackgroundColor,
51
+ ),
52
+ // style && styles.customStyle(style),
53
+ mixins && mixins,
54
+ ]}>
55
+ {/* <Text color={color} style={styles.text}> */}
56
+ <Text style={styles.text}>{displayedContent}</Text>
57
+ </View>
58
+ </View>
59
+ )
60
+ }
61
+
62
+ const styles = StyleSheet.create({
63
+ badge: (
64
+ x,
65
+ y,
66
+ // backgroundColor,
67
+ ) => ({
68
+ position: 'absolute',
69
+ [y]: 0,
70
+ [x]: 0,
71
+ transform: [
72
+ {translateY: y === 'top' ? '-30%' : '30%'},
73
+ {translateX: x === 'left' ? '-30%' : '30%'},
74
+ ],
75
+ paddingVertical: 1,
76
+ paddingHorizontal: 6,
77
+ zIndex: 1,
78
+ borderRadius: 9999,
79
+ // backgroundColor,
80
+ borderWidth: 1,
81
+ alignItems: 'center',
82
+ justifyContent: 'center',
83
+ }),
84
+ text: {
85
+ fontSize: 12,
86
+ textAlign: 'center',
87
+ // variants: {
88
+ // colorScheme: variants.colorScheme(theme),
89
+ // },
90
+ },
91
+ customStyle: style => ({
92
+ ...style,
93
+ }),
94
+ })
95
+
96
+ export default Badge
@@ -0,0 +1,131 @@
1
+ import {StyleProp, Text, TouchableOpacity, ViewStyle} from 'react-native'
2
+ import {StyleSheet, UnistylesVariants} from 'react-native-unistyles'
3
+ import {ButtonStyles, ButtonTokens} from '../theme/Button.recipe'
4
+ import {cloneElement, ReactElement, useMemo} from 'react'
5
+ import {IconWrapperProps} from './IconWrapper'
6
+ import Spinner from './Spinner'
7
+ // import Icon from "./Icon";
8
+ // import { IconProps } from "../components/Icon";
9
+
10
+ type ButtonVariants = UnistylesVariants<typeof ButtonStyles>
11
+
12
+ type TextAdjustment = 'singleLine' | 'multiline' | 'adjustToFit'
13
+
14
+ type ButtonProps = ButtonVariants & {
15
+ text: string
16
+ onPress: () => void
17
+ loading?: boolean
18
+ disabled?: boolean
19
+ maxWidth?: number | string
20
+ flex?: number
21
+ textAdjustment?: TextAdjustment
22
+ // style?: StyleProp<ViewStyle>;
23
+ // mixins?: StyleProp<ViewStyle> | StyleProp<ViewStyle>[];
24
+ // customVariants?: CustomButtonVariants;
25
+ icon?: {
26
+ component: React.ComponentType<IconWrapperProps>
27
+ position: 'left' | 'right'
28
+ scale?: number
29
+ rotation?: number
30
+ }
31
+ mixins?: StyleProp<ViewStyle> | StyleProp<ViewStyle>[]
32
+ }
33
+
34
+ const Button = ({
35
+ size = ButtonTokens.defaultProps.size,
36
+ colorPalette = ButtonTokens.defaultProps.colorPalette,
37
+ onPress,
38
+ text,
39
+ loading = false,
40
+ disabled = false,
41
+ maxWidth = '100%',
42
+ flex = 0,
43
+ textAdjustment = 'singleLine',
44
+ icon,
45
+ mixins,
46
+ }: ButtonProps) => {
47
+ ButtonStyles.useVariants({
48
+ size,
49
+ colorPalette,
50
+ })
51
+
52
+ const getTextProps = useMemo(
53
+ () => (): Record<string, any> => {
54
+ switch (textAdjustment) {
55
+ case 'adjustToFit':
56
+ return {adjustsFontSizeToFit: true, numberOfLines: 1}
57
+ case 'multiline':
58
+ return {adjustsFontSizeToFit: false, numberOfLines: undefined}
59
+ case 'singleLine':
60
+ default:
61
+ return {
62
+ adjustsFontSizeToFit: false,
63
+ numberOfLines: 1,
64
+ ellipsizeMode: 'tail',
65
+ }
66
+ }
67
+ },
68
+ [textAdjustment],
69
+ )
70
+
71
+ const heightAuto = textAdjustment === 'multiline' ? 'auto' : null
72
+
73
+ const IconComponent = icon?.component
74
+ const IconRendered = IconComponent ? (
75
+ <IconComponent
76
+ {...{
77
+ rotation: icon.rotation,
78
+ scale: icon.scale,
79
+ colorPalette,
80
+ //@ts-ignore
81
+ color: ButtonStyles.text.color,
82
+ }}
83
+ />
84
+ ) : null
85
+
86
+ return (
87
+ <TouchableOpacity
88
+ style={[
89
+ styles.container(disabled, flex, maxWidth, heightAuto),
90
+ ButtonStyles.container,
91
+ mixins && mixins,
92
+ ]}
93
+ onPress={onPress}
94
+ disabled={disabled || loading}>
95
+ {loading ? (
96
+ <Spinner
97
+ //@ts-ignore
98
+ size={ButtonStyles.text.fontSize}
99
+ //@ts-ignore
100
+ color={ButtonStyles.text.color}
101
+ />
102
+ ) : (
103
+ <>
104
+ {icon?.position === 'left' && IconRendered}
105
+ <Text {...getTextProps()} style={[styles.text, ButtonStyles.text]}>
106
+ {text}
107
+ </Text>
108
+ {icon?.position === 'right' && IconRendered}
109
+ </>
110
+ )}
111
+ </TouchableOpacity>
112
+ )
113
+ }
114
+
115
+ const styles = StyleSheet.create({
116
+ container: (disabled, flex, maxWidth, heightAuto) => ({
117
+ flex,
118
+ maxWidth,
119
+ width: 'auto',
120
+ flexDirection: 'row',
121
+ justifyContent: 'center',
122
+ alignItems: 'center',
123
+ flexShrink: 1,
124
+ opacity: disabled ? 0.6 : 1,
125
+ }),
126
+ text: {
127
+ textAlign: 'center',
128
+ },
129
+ })
130
+
131
+ export default Button
@@ -0,0 +1,180 @@
1
+ import {useEffect} from 'react'
2
+ import {View, StyleSheet, type ColorValue} from 'react-native'
3
+ import Animated, {
4
+ Easing,
5
+ interpolate,
6
+ useAnimatedProps,
7
+ useSharedValue,
8
+ withTiming,
9
+ Extrapolation,
10
+ } from 'react-native-reanimated'
11
+ import Svg, {Circle, G} from 'react-native-svg'
12
+ import {circleProgressTokens} from '../theme/CircleProgress.recipe'
13
+ import {withUnistyles} from 'react-native-unistyles'
14
+
15
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle)
16
+
17
+ interface CircleProgressProps {
18
+ type?: keyof typeof circleProgressTokens.variants.type
19
+ size?: keyof typeof circleProgressTokens.variants.size
20
+ circleSize?: number
21
+ trackStrokeWidth?: number
22
+ progressStrokeWidth?: number
23
+ duration: number
24
+ trackColor?: string
25
+ progressColor?: string
26
+ progressDirection?: 'forward' | 'reverse'
27
+ rotationDirection?: 'clockwise' | 'counterclockwise'
28
+ children?: React.ReactNode
29
+ fontSize?: number | undefined
30
+ colors: Record<string, ColorValue>
31
+ }
32
+
33
+ const BaseCircleProgress = ({
34
+ type = circleProgressTokens.defaultProps.type,
35
+ size = circleProgressTokens.defaultProps.size,
36
+ circleSize,
37
+ trackStrokeWidth,
38
+ progressStrokeWidth,
39
+ duration,
40
+ trackColor,
41
+ progressColor,
42
+ progressDirection = 'forward',
43
+ rotationDirection = 'clockwise',
44
+ children,
45
+ colors,
46
+ }: CircleProgressProps) => {
47
+ const resolvedSize = circleProgressTokens.variants.size[size]
48
+
49
+ const tokenColorKey =
50
+ circleProgressTokens.variants.type[
51
+ type as keyof typeof circleProgressTokens.variants.type
52
+ ]
53
+ console.log('🚀 ~ tokenColorKey:', tokenColorKey)
54
+ const resolvedTrackColor =
55
+ tokenColorKey.trackColor in colors
56
+ ? colors[tokenColorKey.trackColor]
57
+ : tokenColorKey.trackColor
58
+
59
+ const resolvedProgressColor =
60
+ tokenColorKey.progressColor in colors
61
+ ? colors[tokenColorKey.progressColor]
62
+ : tokenColorKey.progressColor
63
+
64
+ console.log('🚀 ~ tokenColorKey:', tokenColorKey)
65
+
66
+ circleSize =
67
+ circleSize ||
68
+ circleProgressTokens.variants.size[
69
+ size as keyof typeof circleProgressTokens.variants.size
70
+ ]?.size
71
+
72
+ trackStrokeWidth = trackStrokeWidth || resolvedSize?.trackStrokeWidth
73
+ progressStrokeWidth = progressStrokeWidth || resolvedSize?.progressStrokeWidth
74
+
75
+ const radius =
76
+ (circleSize! - Math.max(trackStrokeWidth!, progressStrokeWidth!)) / 2 // Calculate radius based on circleSize and stroke width
77
+ const circumference = 2 * Math.PI * radius // Circumference of the circle
78
+
79
+ // Dynamic center based on circleSize
80
+ const center = circleSize! / 2
81
+
82
+ const progress = useSharedValue(duration)
83
+
84
+ useEffect(() => {
85
+ progress.value = withTiming(0, {
86
+ duration: duration,
87
+ easing: Easing.linear,
88
+ })
89
+ }, [])
90
+
91
+ const animatedProps = useAnimatedProps(() => {
92
+ const rStrokeDashOffset = interpolate(
93
+ progress.value,
94
+ [
95
+ progressDirection === 'forward' ? duration : 0,
96
+ progressDirection === 'forward' ? 0 : duration,
97
+ ],
98
+ [circumference, 0], // Adjust dash offset from full (circumference) to 0
99
+ Extrapolation.CLAMP,
100
+ )
101
+ return {
102
+ strokeDashoffset:
103
+ rotationDirection === 'clockwise'
104
+ ? rStrokeDashOffset
105
+ : -rStrokeDashOffset, // Negate for counterclockwise
106
+ }
107
+ })
108
+
109
+ return (
110
+ <View style={styles.container}>
111
+ <Svg
112
+ width="100%" // Ensures the SVG fills the parent width
113
+ height="100%" // Ensures the SVG maintains a square aspect ratio
114
+ viewBox={`0 0 ${circleSize} ${circleSize}`} // Dynamically adjust viewBox
115
+ preserveAspectRatio="xMidYMid meet">
116
+ <G transform={`rotate(-90, ${center}, ${center})`}>
117
+ {/* Rotate to 12 o'clock */}
118
+ {/* Background Track */}
119
+ <Circle
120
+ cx={center} // Dynamic center
121
+ cy={center} // Dynamic center
122
+ r={radius} // Dynamic radius
123
+ stroke={
124
+ trackColor ? trackColor : resolvedTrackColor
125
+ // (trackColor as ColorValue) ||
126
+ // (resolvedType.trackColor
127
+ // ?.trackColor as ColorValue)
128
+ }
129
+ strokeWidth={trackStrokeWidth || resolvedSize?.trackStrokeWidth}
130
+ fill="transparent"
131
+ strokeDasharray={circumference} // Full circumference
132
+ strokeLinecap="round"
133
+ />
134
+ {/* Progress Stroke */}
135
+ <AnimatedCircle
136
+ cx={center} // Dynamic center
137
+ cy={center} // Dynamic center
138
+ r={radius} // Dynamic radius
139
+ stroke={
140
+ progressColor ? progressColor : resolvedProgressColor
141
+ // (progressColor as ColorValue) ||
142
+ // (resolvedType.progressColor
143
+ // ?.progressColor as ColorValue)
144
+ }
145
+ strokeWidth={
146
+ progressStrokeWidth || resolvedSize?.progressStrokeWidth
147
+ }
148
+ fill="transparent"
149
+ strokeDasharray={circumference} // Full circumference
150
+ strokeLinecap="round"
151
+ animatedProps={animatedProps}></AnimatedCircle>
152
+ </G>
153
+ </Svg>
154
+ <View style={styles.childContainer}>{children}</View>
155
+ </View>
156
+ )
157
+ }
158
+
159
+ const styles = StyleSheet.create({
160
+ container: {
161
+ aspectRatio: 1, // Keeps a square aspect ratio
162
+ width: '100%', // Responsive width based on parent
163
+ // height: '100%', // Responsive height
164
+ },
165
+ childContainer: {
166
+ position: 'absolute',
167
+ top: 0,
168
+ left: 0,
169
+ right: 0,
170
+ bottom: 0,
171
+ justifyContent: 'center',
172
+ alignItems: 'center',
173
+ },
174
+ })
175
+
176
+ const CircleProgress = withUnistyles(BaseCircleProgress, theme => ({
177
+ colors: theme.colors,
178
+ }))
179
+
180
+ export default CircleProgress
@@ -0,0 +1,43 @@
1
+ import { View } from 'react-native';
2
+ import { StyleSheet } from 'react-native-unistyles';
3
+ // import type { VariaThemeType } from '../style/theme';
4
+
5
+ const Divider = ({
6
+ color,
7
+ size = 1,
8
+ axis = 'y',
9
+ margin = 0,
10
+ }: {
11
+ color?: string;
12
+ size?: number;
13
+ axis?: 'x' | 'y';
14
+ margin?: number;
15
+ }) => {
16
+ return (
17
+ <View style={styles.container(size, axis, margin)}>
18
+ <View style={[styles.divider(color, size, axis)]} />
19
+ </View>
20
+ );
21
+ };
22
+
23
+ const styles = StyleSheet.create(theme => ({
24
+ container: (size, axis, margin) => ({
25
+ flex: 1,
26
+ flexDirection: 'row',
27
+ alignItems: 'center',
28
+ justifyContent: 'center',
29
+ maxHeight: axis === 'y' ? size : '100%',
30
+ maxWidth: axis === 'x' ? size : '100%',
31
+ marginHorizontal: axis === 'x' ? margin : 0,
32
+ marginVertical: axis === 'y' ? margin : 0,
33
+ }),
34
+ divider: (color, size, axis) => ({
35
+ width: axis === 'x' ? size : '100%',
36
+ height: axis === 'y' ? size : '100%',
37
+ backgroundColor: color
38
+ ? color
39
+ : theme.colors.foreground,
40
+ }),
41
+ }));
42
+
43
+ export default Divider;