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.
- package/README.md +53 -0
- package/bin/cli.js +200 -0
- package/lib/components/Badge.tsx +96 -0
- package/lib/components/Button.tsx +131 -0
- package/lib/components/CircleProgress.tsx +180 -0
- package/lib/components/Divider.tsx +43 -0
- package/lib/components/GradientBackground.tsx +68 -0
- package/lib/components/GradientText.tsx +121 -0
- package/lib/components/Icon.tsx +13 -0
- package/lib/components/IconWrapper.tsx +109 -0
- package/lib/components/Input.tsx +120 -0
- package/lib/components/Link.tsx +87 -0
- package/lib/components/Modal.tsx +157 -0
- package/lib/components/ReText.tsx +124 -0
- package/lib/components/Slider.tsx +334 -0
- package/lib/components/Slideshow.tsx +317 -0
- package/lib/components/SlidingDrawer.tsx +307 -0
- package/lib/components/Spinner.tsx +44 -0
- package/lib/components/Switch.tsx +224 -0
- package/lib/components/Text.tsx +107 -0
- package/lib/components/index.tsx +83 -0
- package/lib/mixins.ts +180 -0
- package/lib/patterns/index.tsx +426 -0
- package/lib/theme/Badge.recipe.tsx +68 -0
- package/lib/theme/Button.recipe-old.tsx +67 -0
- package/lib/theme/Button.recipe.tsx +83 -0
- package/lib/theme/CircleProgress.recipe.tsx +42 -0
- package/lib/theme/GradientBackground.recipe.tsx +38 -0
- package/lib/theme/GradientText.recipe.tsx +49 -0
- package/lib/theme/Icon.recipe.tsx +122 -0
- package/lib/theme/IconWrapper.recipe.tsx +121 -0
- package/lib/theme/Input.recipe.tsx +110 -0
- package/lib/theme/Link.recipe.tsx +51 -0
- package/lib/theme/Modal.recipe.tsx +34 -0
- package/lib/theme/ReText.recipe.tsx +7 -0
- package/lib/theme/Slider.recipe.tsx +226 -0
- package/lib/theme/Slideshow.recipe.tsx +142 -0
- package/lib/theme/SlidingDrawer.recipe.tsx +91 -0
- package/lib/theme/Spinner.recipe.tsx +8 -0
- package/lib/theme/Switch.recipe.tsx +70 -0
- package/lib/theme/Text.recipe.tsx +10 -0
- package/lib/types.ts +70 -0
- package/lib/utils.ts +80 -0
- 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;
|