@xetwa/design-system 1.0.49 → 1.0.50
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/package.json +1 -1
- package/src/components/Buttons/Chip/Chip.tsx +5 -1
- package/src/components/DataDisplay/CircularProgress/CircularProgress.stories.tsx +67 -0
- package/src/components/DataDisplay/CircularProgress/CircularProgress.tsx +92 -0
- package/src/components/DataDisplay/ProgressBar/ProgressBar.tsx +1 -1
- package/src/components/Headers/CountryHeader/CountryHeader.tsx +34 -8
- package/src/components/Headers/RouteHeader/RouteHeader.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/utils/iconMap.ts +11 -1
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useRef } from 'react';
|
|
2
|
-
import { Pressable, StyleSheet, Animated } from 'react-native';
|
|
2
|
+
import { Pressable, StyleSheet, Animated, View } from 'react-native';
|
|
3
3
|
import type { ViewStyle } from 'react-native';
|
|
4
4
|
import { Typography } from '../../Typography/Typography';
|
|
5
5
|
import { useResponsive } from '../../../hooks/useResponsive';
|
|
@@ -8,6 +8,8 @@ import { theme } from '../../../styles/theme';
|
|
|
8
8
|
export interface ChipProps {
|
|
9
9
|
/** Texto a ser exibido no chip */
|
|
10
10
|
label: string;
|
|
11
|
+
/** Elemento extra à esquerda (ex: ícone, flag) */
|
|
12
|
+
leftElement?: React.ReactNode;
|
|
11
13
|
/** Função a executar ao pressionar */
|
|
12
14
|
onPress?: () => void;
|
|
13
15
|
/** Tamanho do chip (afeta paddings e fonte) */
|
|
@@ -30,6 +32,7 @@ export interface ChipProps {
|
|
|
30
32
|
*/
|
|
31
33
|
export const Chip = ({
|
|
32
34
|
label,
|
|
35
|
+
leftElement,
|
|
33
36
|
onPress,
|
|
34
37
|
size = 'md',
|
|
35
38
|
backgroundColor = theme.colors.white,
|
|
@@ -104,6 +107,7 @@ export const Chip = ({
|
|
|
104
107
|
style,
|
|
105
108
|
]}
|
|
106
109
|
>
|
|
110
|
+
{leftElement && <View style={{ marginRight: 6 }}>{leftElement}</View>}
|
|
107
111
|
<Typography variant={typographyVariant} color={textColor} style={{ textAlign: 'center' }}>
|
|
108
112
|
{label}
|
|
109
113
|
</Typography>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// eslint-disable-next-line storybook/no-renderer-packages
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { CircularProgress } from './CircularProgress';
|
|
6
|
+
import { Typography } from '../../Typography/Typography';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Components/DataDisplay/CircularProgress',
|
|
10
|
+
component: CircularProgress,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'centered',
|
|
13
|
+
docs: {
|
|
14
|
+
description: {
|
|
15
|
+
component: 'O **CircularProgress** exibe o progresso de uma determinada métrica num formato circular (usando SVG). Permite configurar cores, espessura da linha e incluir texto ou filhos personalizados no centro.\n\n### Exemplo de Uso\n```tsx\n<CircularProgress progress={50} size={120} strokeWidth={12} text="50%" />\n```',
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
tags: ['autodocs'],
|
|
20
|
+
argTypes: {
|
|
21
|
+
progress: { control: { type: 'range', min: 0, max: 100 } },
|
|
22
|
+
size: { control: 'number' },
|
|
23
|
+
strokeWidth: { control: 'number' },
|
|
24
|
+
color: { control: 'color' },
|
|
25
|
+
trackColor: { control: 'color' },
|
|
26
|
+
text: { control: 'text' },
|
|
27
|
+
},
|
|
28
|
+
} satisfies Meta<typeof CircularProgress>;
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
progress: 45,
|
|
36
|
+
size: 100,
|
|
37
|
+
strokeWidth: 10,
|
|
38
|
+
text: '45%',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const CustomTypography: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
progress: 75,
|
|
45
|
+
size: 140,
|
|
46
|
+
strokeWidth: 12,
|
|
47
|
+
},
|
|
48
|
+
render: (args) => (
|
|
49
|
+
<CircularProgress {...args}>
|
|
50
|
+
<View style={{ alignItems: 'center' }}>
|
|
51
|
+
<Typography variant="h2">75%</Typography>
|
|
52
|
+
<Typography variant="small" color="#9a8478">Concluído</Typography>
|
|
53
|
+
</View>
|
|
54
|
+
</CircularProgress>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const Sizes: Story = {
|
|
59
|
+
args: { progress: 30 },
|
|
60
|
+
render: () => (
|
|
61
|
+
<View style={{ flexDirection: 'row', gap: 20, alignItems: 'center' }}>
|
|
62
|
+
<CircularProgress progress={15} size={50} strokeWidth={6} text="15%" />
|
|
63
|
+
<CircularProgress progress={30} size={80} strokeWidth={8} text="30%" />
|
|
64
|
+
<CircularProgress progress={85} size={120} strokeWidth={12} text="85%" />
|
|
65
|
+
</View>
|
|
66
|
+
),
|
|
67
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleSheet } from 'react-native';
|
|
3
|
+
import Svg, { Circle } from 'react-native-svg';
|
|
4
|
+
import { theme } from '../../../styles/theme';
|
|
5
|
+
import { Typography } from '../../Typography/Typography';
|
|
6
|
+
|
|
7
|
+
export interface CircularProgressProps {
|
|
8
|
+
/** O valor do progresso (0 a 100) */
|
|
9
|
+
progress: number;
|
|
10
|
+
/** Tamanho total (largura/altura) do círculo */
|
|
11
|
+
size?: number;
|
|
12
|
+
/** Grossura da linha */
|
|
13
|
+
strokeWidth?: number;
|
|
14
|
+
/** Cor da linha de progresso */
|
|
15
|
+
color?: string;
|
|
16
|
+
/** Cor da linha de fundo (track) */
|
|
17
|
+
trackColor?: string;
|
|
18
|
+
/** Texto opcional para aparecer no centro */
|
|
19
|
+
text?: string;
|
|
20
|
+
/** Permite customizar o conteúdo central, sobrepõe a prop text */
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const CircularProgress = ({
|
|
25
|
+
progress,
|
|
26
|
+
size = 100,
|
|
27
|
+
strokeWidth = 10,
|
|
28
|
+
color = theme.colors.primary,
|
|
29
|
+
trackColor = theme.colors.primaryLight,
|
|
30
|
+
text,
|
|
31
|
+
children,
|
|
32
|
+
}: CircularProgressProps) => {
|
|
33
|
+
const radius = (size - strokeWidth) / 2;
|
|
34
|
+
const circumference = radius * 2 * Math.PI;
|
|
35
|
+
// Limita o progresso entre 0 e 100
|
|
36
|
+
const safeProgress = Math.min(Math.max(progress, 0), 100);
|
|
37
|
+
const strokeDashoffset = circumference - (safeProgress / 100) * circumference;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View style={[styles.container, { width: size, height: size }]}>
|
|
41
|
+
<Svg width={size} height={size} style={styles.svg}>
|
|
42
|
+
{/* Fundo do Progresso (Track) */}
|
|
43
|
+
<Circle
|
|
44
|
+
stroke={trackColor}
|
|
45
|
+
fill="none"
|
|
46
|
+
cx={size / 2}
|
|
47
|
+
cy={size / 2}
|
|
48
|
+
r={radius}
|
|
49
|
+
strokeWidth={strokeWidth}
|
|
50
|
+
/>
|
|
51
|
+
{/* Linha de Progresso */}
|
|
52
|
+
<Circle
|
|
53
|
+
stroke={color}
|
|
54
|
+
fill="none"
|
|
55
|
+
cx={size / 2}
|
|
56
|
+
cy={size / 2}
|
|
57
|
+
r={radius}
|
|
58
|
+
strokeWidth={strokeWidth}
|
|
59
|
+
strokeDasharray={circumference}
|
|
60
|
+
strokeDashoffset={strokeDashoffset}
|
|
61
|
+
strokeLinecap="round"
|
|
62
|
+
/>
|
|
63
|
+
</Svg>
|
|
64
|
+
|
|
65
|
+
{/* Conteúdo Central */}
|
|
66
|
+
<View style={styles.contentContainer}>
|
|
67
|
+
{children ? (
|
|
68
|
+
children
|
|
69
|
+
) : text ? (
|
|
70
|
+
<Typography variant="h3" color={theme.colors.brown[800]}>
|
|
71
|
+
{text}
|
|
72
|
+
</Typography>
|
|
73
|
+
) : null}
|
|
74
|
+
</View>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const styles = StyleSheet.create({
|
|
80
|
+
container: {
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
justifyContent: 'center',
|
|
83
|
+
},
|
|
84
|
+
svg: {
|
|
85
|
+
transform: [{ rotate: '-90deg' }],
|
|
86
|
+
},
|
|
87
|
+
contentContainer: {
|
|
88
|
+
position: 'absolute',
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
@@ -53,7 +53,7 @@ export const ProgressBar = ({
|
|
|
53
53
|
}, [progress, animatedProgress]);
|
|
54
54
|
|
|
55
55
|
const sizeStyles = {
|
|
56
|
-
xs: { height: 8, borderRadius:
|
|
56
|
+
xs: { height: 8, borderRadius: 4 },
|
|
57
57
|
sm: { height: 12, borderRadius: 7 },
|
|
58
58
|
md: { height: 16, borderRadius: 9 },
|
|
59
59
|
}[size === 'lg' ? 'md' : size] || { height: 16, borderRadius: 9 };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { View, StyleSheet, Animated, Easing } from 'react-native';
|
|
2
3
|
import type { ViewStyle } from 'react-native';
|
|
3
4
|
import { useResponsive } from '../../../hooks/useResponsive';
|
|
4
5
|
import { getCountryTheme } from '../../../styles/countryThemes';
|
|
@@ -27,6 +28,8 @@ export interface CountryHeaderProps {
|
|
|
27
28
|
* O developer deve passar o \`insets.top\` gerado pelo \`react-native-safe-area-context\`.
|
|
28
29
|
*/
|
|
29
30
|
safeAreaTop?: number;
|
|
31
|
+
/** Se deve ocultar a barra de progresso com animação */
|
|
32
|
+
hideProgressBar?: boolean;
|
|
30
33
|
/** Estilos customizados opcionais para o container principal */
|
|
31
34
|
style?: ViewStyle | ViewStyle[];
|
|
32
35
|
}
|
|
@@ -40,11 +43,23 @@ export const CountryHeader = ({
|
|
|
40
43
|
progressSegments = 4,
|
|
41
44
|
onBackPress,
|
|
42
45
|
safeAreaTop = 0,
|
|
46
|
+
hideProgressBar = false,
|
|
43
47
|
style,
|
|
44
48
|
}: CountryHeaderProps) => {
|
|
45
49
|
const { scale, scaleText } = useResponsive();
|
|
46
50
|
const countryTheme = getCountryTheme(countryCode);
|
|
47
51
|
|
|
52
|
+
const progressAnim = useRef(new Animated.Value(hideProgressBar ? 0 : 1)).current;
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
Animated.timing(progressAnim, {
|
|
56
|
+
toValue: hideProgressBar ? 0 : 1,
|
|
57
|
+
duration: 300,
|
|
58
|
+
easing: Easing.inOut(Easing.ease),
|
|
59
|
+
useNativeDriver: false,
|
|
60
|
+
}).start();
|
|
61
|
+
}, [hideProgressBar, progressAnim]);
|
|
62
|
+
|
|
48
63
|
return (
|
|
49
64
|
<View style={[styles.container, { backgroundColor: countryTheme.bg }, style]}>
|
|
50
65
|
{/* Círculo decorativo de fundo */}
|
|
@@ -87,13 +102,24 @@ export const CountryHeader = ({
|
|
|
87
102
|
</View>
|
|
88
103
|
|
|
89
104
|
{/* Progress Bar */}
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
<Animated.View
|
|
106
|
+
style={{
|
|
107
|
+
opacity: progressAnim,
|
|
108
|
+
height: progressAnim.interpolate({
|
|
109
|
+
inputRange: [0, 1],
|
|
110
|
+
outputRange: [0, 12],
|
|
111
|
+
}),
|
|
112
|
+
overflow: 'hidden',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<ProgressBar
|
|
116
|
+
progress={progress}
|
|
117
|
+
segments={progressSegments}
|
|
118
|
+
size="sm"
|
|
119
|
+
fillColor={countryTheme.progressFill}
|
|
120
|
+
trackColor={countryTheme.progressTrack}
|
|
121
|
+
/>
|
|
122
|
+
</Animated.View>
|
|
97
123
|
</View>
|
|
98
124
|
</View>
|
|
99
125
|
);
|
|
@@ -202,7 +202,7 @@ export const RouteHeader = ({
|
|
|
202
202
|
<ProgressBar
|
|
203
203
|
progress={progress}
|
|
204
204
|
segments={segments}
|
|
205
|
-
size="
|
|
205
|
+
size="xs"
|
|
206
206
|
variant="default" // Usa a cor primária (laranja)
|
|
207
207
|
trackColor={theme.colors.brown[100]} // Fundo da barra um pouco mais escuro para ser visível
|
|
208
208
|
/>
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './components/Cards/TeacherCard/TeacherCard';
|
|
|
22
22
|
export * from './components/Charts/WeeklyProgressChart';
|
|
23
23
|
export * from './components/DataDisplay/Avatar/Avatar';
|
|
24
24
|
export * from './components/DataDisplay/Badge/Badge';
|
|
25
|
+
export * from './components/DataDisplay/CircularProgress/CircularProgress';
|
|
25
26
|
export * from './components/DataDisplay/Flag/Flag';
|
|
26
27
|
export * from './components/DataDisplay/InstructionBubble/InstructionBubble';
|
|
27
28
|
export * from './components/DataDisplay/LevelBadge/LevelBadge';
|
package/src/utils/iconMap.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Play, Volume2, Volume1, ArrowRight, X, Check, ChevronRight, ChevronLeft, Info, AlertTriangle, Book, Mic, Home, Compass, GraduationCap, FolderOpen, User, ShoppingCart, FileText, Wallet, Bell, Globe } from 'lucide-react-native';
|
|
1
|
+
import { Play, Volume2, Volume1, ArrowRight, X, Check, ChevronRight, ChevronLeft, Info, AlertTriangle, Book, Mic, Home, Compass, GraduationCap, FolderOpen, User, ShoppingCart, FileText, Wallet, Bell, Globe, Shield, Settings, CheckCheck, Users, ShieldAlert, Plus, Lock, MapPin, Clock, Smartphone } from 'lucide-react-native';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Para evitar que o Vite e o Metro (React Native) tentem empacotar os 1400+ ícones
|
|
@@ -31,4 +31,14 @@ export const IconMap: Record<string, any> = {
|
|
|
31
31
|
Wallet,
|
|
32
32
|
Bell,
|
|
33
33
|
Globe,
|
|
34
|
+
Shield,
|
|
35
|
+
Settings,
|
|
36
|
+
CheckCheck,
|
|
37
|
+
Users,
|
|
38
|
+
ShieldAlert,
|
|
39
|
+
Plus,
|
|
40
|
+
Lock,
|
|
41
|
+
MapPin,
|
|
42
|
+
Clock,
|
|
43
|
+
Smartphone,
|
|
34
44
|
};
|