@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xetwa/design-system",
3
- "version": "1.0.49",
3
+ "version": "1.0.50",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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: 5 },
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 { View, StyleSheet } from 'react-native';
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
- <ProgressBar
91
- progress={progress}
92
- segments={progressSegments}
93
- size="sm"
94
- fillColor={countryTheme.progressFill}
95
- trackColor={countryTheme.progressTrack}
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="sm"
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';
@@ -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
  };