@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.
@@ -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
- <Typography variant="bodySmHeavy" color={theme.colors.brown[500]} style={{ textTransform: 'uppercase', letterSpacing: 0.5 }}>
113
- {routeLabel}
114
- </Typography>
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
- ) : null}
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
- <View style={styles.progressTextContainer}>
139
- <Typography variant="h4" color={theme.colors.primary}>
140
- {progressText}
141
- </Typography>
142
- <Typography variant="smallHeavy" color={theme.colors.brown[500]}>
143
- {progressSubText}
144
- </Typography>
145
- </View>
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="sm"
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
- // Animação da largura: Inativo (6px) -> Ativo (20px)
31
- const widthAnim = useRef(new Animated.Value(isActive ? scale(20) : scale(6))).current;
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 ? scale(20) : scale(6),
49
+ toValue: isActive ? activeWidth : inactiveWidth,
36
50
  duration: 250,
37
- easing: Easing.bezier(0.175, 0.885, 0.32, 1.275), // Efeito "ease-bounce" semelhante ao CSS
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, scale]);
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 ? theme.colors.primary : theme.colors.brown[100],
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';
@@ -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'),
@@ -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
  };