@xetwa/design-system 1.0.48 → 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/DataDisplay/Stamp/Stamp.stories.tsx +153 -0
- package/src/components/DataDisplay/Stamp/Stamp.tsx +173 -0
- package/src/components/DataDisplay/StampCard/StampCard.stories.tsx +275 -0
- package/src/components/DataDisplay/StampCard/StampCard.tsx +196 -0
- package/src/components/Headers/CountryHeader/CountryHeader.tsx +34 -8
- package/src/components/Headers/RouteHeader/RouteHeader.stories.tsx +28 -0
- package/src/components/Headers/RouteHeader/RouteHeader.tsx +85 -16
- package/src/components/Navigation/PaginationDots/PaginationDots.stories.tsx +20 -4
- package/src/components/Navigation/PaginationDots/PaginationDots.tsx +23 -9
- package/src/index.ts +3 -0
- package/src/styles/theme.ts +34 -0
- package/src/utils/iconMap.ts +13 -1
|
@@ -5,6 +5,7 @@ import { theme } from '../../../styles/theme';
|
|
|
5
5
|
import { Flag } from '../../DataDisplay/Flag/Flag';
|
|
6
6
|
import { ProgressBar } from '../../DataDisplay/ProgressBar/ProgressBar';
|
|
7
7
|
import { Typography } from '../../Typography/Typography';
|
|
8
|
+
import { IconButton } from '../../Buttons/IconButton/IconButton';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Propriedades para o componente RouteHeader.
|
|
@@ -69,6 +70,19 @@ export interface RouteHeaderProps {
|
|
|
69
70
|
* Deve receber o `insets.top` devolvido pelo hook `useSafeAreaInsets()`.
|
|
70
71
|
*/
|
|
71
72
|
safeAreaTop?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Variante visual do cabeçalho.
|
|
75
|
+
* - `default`: Mostra o progresso em texto gigante à direita (ex: 63%).
|
|
76
|
+
* - `minimal`: Oculta o progresso em texto e pode mostrar ações contextuais à direita (ex: sino e globo).
|
|
77
|
+
* @default 'default'
|
|
78
|
+
*/
|
|
79
|
+
variant?: 'default' | 'minimal';
|
|
80
|
+
/** Contagem de notificações não lidas. Requer variant="minimal" */
|
|
81
|
+
notificationCount?: number;
|
|
82
|
+
/** Callback ao clicar no sino de notificações. Requer variant="minimal" */
|
|
83
|
+
onNotificationPress?: () => void;
|
|
84
|
+
/** Callback ao clicar no mapa/globo. Requer variant="minimal" */
|
|
85
|
+
onGlobePress?: () => void;
|
|
72
86
|
/**
|
|
73
87
|
* Estilos adicionais para o contentor exterior (ex: zIndex, position absolute).
|
|
74
88
|
*/
|
|
@@ -98,6 +112,10 @@ export const RouteHeader = ({
|
|
|
98
112
|
progress,
|
|
99
113
|
totalSegments = 1,
|
|
100
114
|
progressType = 'continuous',
|
|
115
|
+
variant = 'default',
|
|
116
|
+
notificationCount = 0,
|
|
117
|
+
onNotificationPress,
|
|
118
|
+
onGlobePress,
|
|
101
119
|
safeAreaTop = 0,
|
|
102
120
|
style,
|
|
103
121
|
}: RouteHeaderProps) => {
|
|
@@ -109,14 +127,21 @@ export const RouteHeader = ({
|
|
|
109
127
|
<View style={[styles.container, { paddingTop: safeAreaTop + scale(12), paddingHorizontal: scale(20), paddingBottom: scale(20) }, style]}>
|
|
110
128
|
{/* Top Row: Labels */}
|
|
111
129
|
<View style={[styles.topRow, { marginBottom: scale(10) }]}>
|
|
112
|
-
<
|
|
113
|
-
{
|
|
114
|
-
|
|
115
|
-
{stepLabel ? (
|
|
116
|
-
<Typography variant="smallHeavy" color={theme.colors.brown[500]}>
|
|
117
|
-
{stepLabel}
|
|
130
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
131
|
+
<Typography variant="bodySmHeavy" color={theme.colors.brown[500]} style={{ textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
|
132
|
+
{routeLabel}
|
|
118
133
|
</Typography>
|
|
119
|
-
|
|
134
|
+
{stepLabel ? (
|
|
135
|
+
<>
|
|
136
|
+
<Typography variant="bodySmHeavy" color={theme.colors.brown[400]} style={{ marginHorizontal: scale(6) }}>
|
|
137
|
+
•
|
|
138
|
+
</Typography>
|
|
139
|
+
<Typography variant="bodySmHeavy" color={theme.colors.brown[500]}>
|
|
140
|
+
{stepLabel}
|
|
141
|
+
</Typography>
|
|
142
|
+
</>
|
|
143
|
+
) : null}
|
|
144
|
+
</View>
|
|
120
145
|
</View>
|
|
121
146
|
|
|
122
147
|
{/* Middle Row: Flag, Title, Progress Text */}
|
|
@@ -135,21 +160,49 @@ export const RouteHeader = ({
|
|
|
135
160
|
</Typography>
|
|
136
161
|
</View>
|
|
137
162
|
|
|
138
|
-
|
|
139
|
-
<
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
{variant === 'default' && (
|
|
164
|
+
<View style={styles.progressTextContainer}>
|
|
165
|
+
<Typography variant="h4" color={theme.colors.primary}>
|
|
166
|
+
{progressText}
|
|
167
|
+
</Typography>
|
|
168
|
+
<Typography variant="smallHeavy" color={theme.colors.brown[500]}>
|
|
169
|
+
{progressSubText}
|
|
170
|
+
</Typography>
|
|
171
|
+
</View>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
{variant === 'minimal' && (
|
|
175
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: scale(8) }}>
|
|
176
|
+
<View style={{ zIndex: 10, elevation: 10 }}>
|
|
177
|
+
<IconButton
|
|
178
|
+
iconName="Bell"
|
|
179
|
+
variant="secondary"
|
|
180
|
+
size="sm"
|
|
181
|
+
iconColor={theme.colors.primary}
|
|
182
|
+
onPress={onNotificationPress}
|
|
183
|
+
/>
|
|
184
|
+
{!!notificationCount && notificationCount > 0 && (
|
|
185
|
+
<View style={styles.badgeContainer}>
|
|
186
|
+
<Typography variant="smallHeavy" color={theme.colors.white}>{notificationCount > 99 ? '99+' : notificationCount}</Typography>
|
|
187
|
+
</View>
|
|
188
|
+
)}
|
|
189
|
+
</View>
|
|
190
|
+
<IconButton
|
|
191
|
+
iconName="Globe"
|
|
192
|
+
variant="secondary"
|
|
193
|
+
size="sm"
|
|
194
|
+
iconColor={theme.colors.brown[800]}
|
|
195
|
+
onPress={onGlobePress}
|
|
196
|
+
/>
|
|
197
|
+
</View>
|
|
198
|
+
)}
|
|
146
199
|
</View>
|
|
147
200
|
|
|
148
201
|
{/* Bottom Row: Progress Bar */}
|
|
149
202
|
<ProgressBar
|
|
150
203
|
progress={progress}
|
|
151
204
|
segments={segments}
|
|
152
|
-
size="
|
|
205
|
+
size="xs"
|
|
153
206
|
variant="default" // Usa a cor primária (laranja)
|
|
154
207
|
trackColor={theme.colors.brown[100]} // Fundo da barra um pouco mais escuro para ser visível
|
|
155
208
|
/>
|
|
@@ -217,4 +270,20 @@ const styles = StyleSheet.create({
|
|
|
217
270
|
alignItems: 'flex-end',
|
|
218
271
|
justifyContent: 'center',
|
|
219
272
|
},
|
|
273
|
+
badgeContainer: {
|
|
274
|
+
position: 'absolute',
|
|
275
|
+
top: -4,
|
|
276
|
+
right: -4,
|
|
277
|
+
backgroundColor: theme.colors.error,
|
|
278
|
+
borderRadius: 10,
|
|
279
|
+
minWidth: 18,
|
|
280
|
+
height: 18,
|
|
281
|
+
justifyContent: 'center',
|
|
282
|
+
alignItems: 'center',
|
|
283
|
+
paddingHorizontal: 4,
|
|
284
|
+
borderWidth: 1.5,
|
|
285
|
+
borderColor: theme.colors.white,
|
|
286
|
+
zIndex: 10,
|
|
287
|
+
elevation: 10,
|
|
288
|
+
}
|
|
220
289
|
});
|
|
@@ -17,6 +17,13 @@ const meta = {
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
tags: ['autodocs'],
|
|
20
|
+
argTypes: {
|
|
21
|
+
variant: {
|
|
22
|
+
control: 'radio',
|
|
23
|
+
options: ['default', 'brown'],
|
|
24
|
+
description: 'Variante visual dos pontos.',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
20
27
|
decorators: [
|
|
21
28
|
(Story) => (
|
|
22
29
|
<View style={{ padding: 40, backgroundColor: theme.colors.cream }}>
|
|
@@ -30,16 +37,21 @@ export default meta;
|
|
|
30
37
|
type Story = StoryObj<any>;
|
|
31
38
|
|
|
32
39
|
export const Default: Story = {
|
|
33
|
-
args: { totalPages: 4, currentPage: 1 } as any,
|
|
40
|
+
args: { totalPages: 4, currentPage: 1, variant: 'default' } as any,
|
|
41
|
+
render: (args: any) => <PaginationDots {...args} />,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Brown: Story = {
|
|
45
|
+
args: { totalPages: 4, currentPage: 1, variant: 'brown' } as any,
|
|
34
46
|
render: (args: any) => <PaginationDots {...args} />,
|
|
35
47
|
};
|
|
36
48
|
|
|
37
|
-
const InteractiveDots = ({ totalPages }: { totalPages: number }) => {
|
|
49
|
+
const InteractiveDots = ({ totalPages, variant }: { totalPages: number, variant?: 'default' | 'brown' }) => {
|
|
38
50
|
const [currentPage, setCurrentStep] = useState(0);
|
|
39
51
|
|
|
40
52
|
return (
|
|
41
53
|
<View style={{ alignItems: 'center' }}>
|
|
42
|
-
<PaginationDots totalPages={totalPages} currentPage={currentPage} />
|
|
54
|
+
<PaginationDots totalPages={totalPages} currentPage={currentPage} variant={variant} />
|
|
43
55
|
|
|
44
56
|
<View style={{ flexDirection: 'row', gap: 20, marginTop: 20 }}>
|
|
45
57
|
<Button
|
|
@@ -58,5 +70,9 @@ const InteractiveDots = ({ totalPages }: { totalPages: number }) => {
|
|
|
58
70
|
};
|
|
59
71
|
|
|
60
72
|
export const Interactive: Story = {
|
|
61
|
-
render: () => <InteractiveDots totalPages={4} />,
|
|
73
|
+
render: () => <InteractiveDots totalPages={4} variant="default" />,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const InteractiveBrown: Story = {
|
|
77
|
+
render: () => <InteractiveDots totalPages={4} variant="brown" />,
|
|
62
78
|
};
|
|
@@ -17,27 +17,41 @@ export interface PaginationDotsProps {
|
|
|
17
17
|
inactiveColor?: string;
|
|
18
18
|
/** Cor customizada do ponto ativo (opcional). */
|
|
19
19
|
activeColor?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Variante visual dos pontos.
|
|
22
|
+
* - `default`: Ponto ativo longo (primário), inativos redondos.
|
|
23
|
+
* - `brown`: Cores da paleta castanha, pontos inativos mais largos (em formato de traço curto).
|
|
24
|
+
*/
|
|
25
|
+
variant?: 'default' | 'brown';
|
|
20
26
|
style?: ViewStyle | ViewStyle[];
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
interface DotProps {
|
|
24
30
|
isActive: boolean;
|
|
31
|
+
variant: 'default' | 'brown';
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
const Dot = ({ isActive }: DotProps) => {
|
|
34
|
+
const Dot = ({ isActive, variant }: DotProps) => {
|
|
28
35
|
const { scale } = useResponsive();
|
|
29
36
|
|
|
30
|
-
//
|
|
31
|
-
const
|
|
37
|
+
// Se for 'brown', os inativos são mais largos (traços curtos em vez de círculos perfeitos)
|
|
38
|
+
const inactiveWidth = variant === 'brown' ? scale(12) : scale(6);
|
|
39
|
+
const activeWidth = scale(20);
|
|
40
|
+
|
|
41
|
+
const activeColor = variant === 'brown' ? theme.colors.brown[500] : theme.colors.primary;
|
|
42
|
+
const inactiveColor = theme.colors.brown[100]; // Bolinhas cinzas inativas mantêm a cor padrão (brown[100])
|
|
43
|
+
|
|
44
|
+
// Animação da largura
|
|
45
|
+
const widthAnim = useRef(new Animated.Value(isActive ? activeWidth : inactiveWidth)).current;
|
|
32
46
|
|
|
33
47
|
useEffect(() => {
|
|
34
48
|
Animated.timing(widthAnim, {
|
|
35
|
-
toValue: isActive ?
|
|
49
|
+
toValue: isActive ? activeWidth : inactiveWidth,
|
|
36
50
|
duration: 250,
|
|
37
|
-
easing: Easing.bezier(0.175, 0.885, 0.32, 1.275), // Efeito "ease-bounce"
|
|
51
|
+
easing: Easing.bezier(0.175, 0.885, 0.32, 1.275), // Efeito "ease-bounce"
|
|
38
52
|
useNativeDriver: false, // Animating width requires useNativeDriver: false
|
|
39
53
|
}).start();
|
|
40
|
-
}, [isActive, widthAnim,
|
|
54
|
+
}, [isActive, widthAnim, activeWidth, inactiveWidth]);
|
|
41
55
|
|
|
42
56
|
return (
|
|
43
57
|
<Animated.View
|
|
@@ -47,14 +61,14 @@ const Dot = ({ isActive }: DotProps) => {
|
|
|
47
61
|
width: widthAnim,
|
|
48
62
|
height: scale(6),
|
|
49
63
|
borderRadius: scale(3),
|
|
50
|
-
backgroundColor: isActive ?
|
|
64
|
+
backgroundColor: isActive ? activeColor : inactiveColor,
|
|
51
65
|
},
|
|
52
66
|
]}
|
|
53
67
|
/>
|
|
54
68
|
);
|
|
55
69
|
};
|
|
56
70
|
|
|
57
|
-
export const PaginationDots = ({ totalPages, currentPage, style }: PaginationDotsProps) => {
|
|
71
|
+
export const PaginationDots = ({ totalPages, currentPage, variant = 'default', style }: PaginationDotsProps) => {
|
|
58
72
|
const { scale } = useResponsive();
|
|
59
73
|
|
|
60
74
|
// Garantir que não crasha caso passem steps negativos
|
|
@@ -63,7 +77,7 @@ export const PaginationDots = ({ totalPages, currentPage, style }: PaginationDot
|
|
|
63
77
|
return (
|
|
64
78
|
<View style={[styles.container, { gap: scale(7), marginBottom: scale(24) }, style]}>
|
|
65
79
|
{Array.from({ length: totalPages }).map((_, index) => (
|
|
66
|
-
<Dot key={index} isActive={index === safeCurrentStep} />
|
|
80
|
+
<Dot key={index} isActive={index === safeCurrentStep} variant={variant} />
|
|
67
81
|
))}
|
|
68
82
|
</View>
|
|
69
83
|
);
|
package/src/index.ts
CHANGED
|
@@ -22,11 +22,14 @@ 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';
|
|
28
29
|
export * from './components/DataDisplay/NumberBadge/NumberBadge';
|
|
29
30
|
export * from './components/DataDisplay/ProgressBar/ProgressBar';
|
|
31
|
+
export * from './components/DataDisplay/Stamp/Stamp';
|
|
32
|
+
export * from './components/DataDisplay/StampCard/StampCard';
|
|
30
33
|
export * from './components/DataDisplay/StatBadge/StatBadge';
|
|
31
34
|
export * from './components/DataDisplay/Tag/Tag';
|
|
32
35
|
export * from './components/Feedback/BottomSheet/BottomSheet';
|
package/src/styles/theme.ts
CHANGED
|
@@ -234,6 +234,40 @@ export const theme = {
|
|
|
234
234
|
desktopFontSize: 16,
|
|
235
235
|
},
|
|
236
236
|
|
|
237
|
+
// FAMÍLIA SERIF (Elementos Clássicos, Carimbos)
|
|
238
|
+
stampTop: {
|
|
239
|
+
...font('IM Fell English', '400'),
|
|
240
|
+
fontStyle: 'italic',
|
|
241
|
+
fontSize: 12,
|
|
242
|
+
lineHeight: 14,
|
|
243
|
+
letterSpacing: 2.5,
|
|
244
|
+
textTransform: 'uppercase',
|
|
245
|
+
desktopFontSize: 16,
|
|
246
|
+
},
|
|
247
|
+
stampTitle: {
|
|
248
|
+
...font('Fredoka-SemiBold', '600'),
|
|
249
|
+
fontSize: 24,
|
|
250
|
+
lineHeight: 28,
|
|
251
|
+
textTransform: 'uppercase',
|
|
252
|
+
desktopFontSize: 28,
|
|
253
|
+
},
|
|
254
|
+
stampBottom1: {
|
|
255
|
+
...font('Nunito-Regular', '400'),
|
|
256
|
+
fontSize: 9,
|
|
257
|
+
lineHeight: 12,
|
|
258
|
+
letterSpacing: 2,
|
|
259
|
+
textTransform: 'uppercase',
|
|
260
|
+
desktopFontSize: 10,
|
|
261
|
+
},
|
|
262
|
+
stampBottom2: {
|
|
263
|
+
...font('Nunito-Regular', '400'),
|
|
264
|
+
fontSize: 7,
|
|
265
|
+
lineHeight: 10,
|
|
266
|
+
letterSpacing: 0.5,
|
|
267
|
+
textTransform: 'uppercase',
|
|
268
|
+
desktopFontSize: 8,
|
|
269
|
+
},
|
|
270
|
+
|
|
237
271
|
// FAMÍLIA NUNITO (Corpo, Labels, Listas e Infos)
|
|
238
272
|
bodyLgHeavy: {
|
|
239
273
|
...font('Nunito-Black', '900'),
|
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 } 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
|
|
@@ -29,4 +29,16 @@ export const IconMap: Record<string, any> = {
|
|
|
29
29
|
ShoppingCart,
|
|
30
30
|
FileText,
|
|
31
31
|
Wallet,
|
|
32
|
+
Bell,
|
|
33
|
+
Globe,
|
|
34
|
+
Shield,
|
|
35
|
+
Settings,
|
|
36
|
+
CheckCheck,
|
|
37
|
+
Users,
|
|
38
|
+
ShieldAlert,
|
|
39
|
+
Plus,
|
|
40
|
+
Lock,
|
|
41
|
+
MapPin,
|
|
42
|
+
Clock,
|
|
43
|
+
Smartphone,
|
|
32
44
|
};
|