@proulr/sdui-components 0.1.6 → 0.1.8

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,12 +1,15 @@
1
1
  {
2
2
  "name": "@proulr/sdui-components",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "main": "./src/renderer/index.ts",
5
5
  "types": "./src/renderer/index.ts",
6
6
  "exports": {
7
7
  "./bdc": "./src/bdc/index.ts",
8
8
  "./bdc/jsx-runtime": "./src/bdc/jsx-runtime.ts",
9
- "./renderer": "./src/renderer/index.ts"
9
+ "./renderer": "./src/renderer/index.ts",
10
+ "./ui": "./src/renderer/ui/index.ts",
11
+ "./primitives": "./src/renderer/ui/primitives.ts",
12
+ "./theme": "./src/renderer/theme/proulr-theme.ts"
10
13
  },
11
14
  "files": [
12
15
  "src"
@@ -0,0 +1,62 @@
1
+ import { Pressable, StyleSheet } from 'react-native';
2
+ import { AppIcon, type AppIconName, APP_ICON_SIZE } from './app-icon';
3
+ import { m3Colors, spacing } from '../theme/proulr-theme';
4
+
5
+ type AppChromeIconButtonProps = {
6
+ label: string;
7
+ icon: AppIconName;
8
+ onPress?: () => void;
9
+ disabled?: boolean;
10
+ color?: string;
11
+ size?: number;
12
+ /** Header chrome pill — circular bordered target. */
13
+ variant?: 'header' | 'plain';
14
+ };
15
+
16
+ export function AppChromeIconButton({
17
+ label,
18
+ icon,
19
+ onPress,
20
+ disabled,
21
+ color = m3Colors.primary,
22
+ size = APP_ICON_SIZE.chromeHeader,
23
+ variant = 'header',
24
+ }: AppChromeIconButtonProps) {
25
+ return (
26
+ <Pressable
27
+ onPress={onPress}
28
+ disabled={disabled || !onPress}
29
+ hitSlop={8}
30
+ accessibilityRole="button"
31
+ accessibilityLabel={label}
32
+ style={[
33
+ variant === 'header' ? styles.headerBtn : styles.plainBtn,
34
+ disabled && styles.disabled,
35
+ ]}
36
+ >
37
+ <AppIcon name={icon} color={color} size={size} />
38
+ </Pressable>
39
+ );
40
+ }
41
+
42
+ const styles = StyleSheet.create({
43
+ headerBtn: {
44
+ width: 45,
45
+ height: 45,
46
+ borderRadius: 22.5,
47
+ borderWidth: 2,
48
+ borderColor: 'rgba(255,255,255,0.22)',
49
+ margin: spacing.xs,
50
+ alignItems: 'center',
51
+ justifyContent: 'center',
52
+ },
53
+ plainBtn: {
54
+ width: 40,
55
+ height: 40,
56
+ alignItems: 'center',
57
+ justifyContent: 'center',
58
+ },
59
+ disabled: {
60
+ opacity: 0.4,
61
+ },
62
+ });
@@ -0,0 +1,60 @@
1
+ import { Pressable, StyleSheet, Text } from 'react-native';
2
+ import { colors, fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
3
+
4
+ export function Chip({
5
+ label,
6
+ active,
7
+ onPress,
8
+ variant = 'default',
9
+ }: {
10
+ label: string;
11
+ active?: boolean;
12
+ onPress?: () => void;
13
+ variant?: 'default' | 'onHeader';
14
+ }) {
15
+ const isHeader = variant === 'onHeader';
16
+
17
+ const containerStyle = active
18
+ ? isHeader
19
+ ? { backgroundColor: m3Colors.onPrimary, borderColor: m3Colors.onPrimary }
20
+ : { backgroundColor: m3Colors.secondaryContainer, borderColor: m3Colors.outline }
21
+ : isHeader
22
+ ? { backgroundColor: colors.surfaceGlass, borderColor: 'rgba(255,255,255,0.28)' }
23
+ : { backgroundColor: m3Colors.surfaceContainerHigh, borderColor: m3Colors.outline };
24
+
25
+ const textColor = active
26
+ ? isHeader
27
+ ? colors.headerStart
28
+ : m3Colors.onSecondaryContainer
29
+ : isHeader
30
+ ? colors.textOnAccent
31
+ : m3Colors.onSurfaceVariant;
32
+
33
+ return (
34
+ <Pressable onPress={onPress} style={[styles.chip, isHeader && styles.chipOnHeader, containerStyle]}>
35
+ <Text
36
+ style={{
37
+ color: textColor,
38
+ fontSize: isHeader ? 12 : 13,
39
+ fontFamily: fonts.semibold,
40
+ }}
41
+ >
42
+ {label}
43
+ </Text>
44
+ </Pressable>
45
+ );
46
+ }
47
+
48
+ const styles = StyleSheet.create({
49
+ chip: {
50
+ paddingHorizontal: spacing.sm,
51
+ paddingVertical: spacing.sm,
52
+ borderRadius: m3Shape.cornerSmall,
53
+ borderWidth: 1,
54
+ },
55
+ chipOnHeader: {
56
+ borderRadius: m3Shape.cornerFull,
57
+ paddingHorizontal: spacing.sm + 2,
58
+ paddingVertical: spacing.xs,
59
+ },
60
+ });
@@ -0,0 +1,22 @@
1
+ import { StyleSheet, Text, type TextProps } from 'react-native';
2
+ import { fonts, m3Colors, spacing } from '../theme/proulr-theme';
3
+
4
+ type HintTextProps = TextProps & {
5
+ spaced?: boolean;
6
+ };
7
+
8
+ export function HintText({ spaced, style, ...props }: HintTextProps) {
9
+ return <Text style={[styles.hint, spaced && styles.spaced, style]} {...props} />;
10
+ }
11
+
12
+ const styles = StyleSheet.create({
13
+ hint: {
14
+ color: m3Colors.onSurfaceVariant,
15
+ fontSize: 12,
16
+ lineHeight: 17,
17
+ fontFamily: fonts.regular,
18
+ },
19
+ spaced: {
20
+ marginTop: spacing.sm,
21
+ },
22
+ });
@@ -1,27 +1,33 @@
1
- import React from 'react';
2
1
  import { Pressable, StyleSheet, Text, View } from 'react-native';
2
+ import { AppIcon, type AppIconName, APP_ICON_SIZE } from './app-icon';
3
3
  import { colors, fonts, radius, spacing } from '../theme/proulr-theme';
4
4
 
5
5
  export function InfoBanner({
6
6
  message,
7
7
  icon = 'ℹ',
8
+ appIcon,
8
9
  onDismiss,
9
10
  }: {
10
11
  message: string;
11
12
  icon?: string;
13
+ appIcon?: AppIconName;
12
14
  onDismiss?: () => void;
13
15
  }) {
14
16
  return (
15
17
  <View style={styles.infoBanner}>
16
18
  <View style={styles.infoIconWrap}>
17
- <Text style={styles.infoIcon}>{icon}</Text>
19
+ {appIcon ? (
20
+ <AppIcon name={appIcon} size={APP_ICON_SIZE.banner} color={colors.accentText} />
21
+ ) : (
22
+ <Text style={styles.infoIcon}>{icon}</Text>
23
+ )}
18
24
  </View>
19
25
  <Text style={styles.infoText}>{message}</Text>
20
- {onDismiss && (
26
+ {onDismiss ? (
21
27
  <Pressable onPress={onDismiss} hitSlop={8}>
22
- <Text style={styles.infoDismiss}>✕</Text>
28
+ <AppIcon name="close" size={14} color={colors.textMuted} />
23
29
  </Pressable>
24
- )}
30
+ ) : null}
25
31
  </View>
26
32
  );
27
33
  }
@@ -47,5 +53,4 @@ const styles = StyleSheet.create({
47
53
  },
48
54
  infoIcon: { fontSize: 16, color: colors.accentText },
49
55
  infoText: { flex: 1, color: colors.textSecondary, fontSize: 13, lineHeight: 18, fontFamily: fonts.medium },
50
- infoDismiss: { color: colors.textMuted, fontSize: 16 },
51
56
  });
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ import { StyleSheet, Text } from 'react-native';
3
+ import { colors, fonts, spacing } from '../theme/proulr-theme';
4
+
5
+ export function Label({ children }: { children: ReactNode }) {
6
+ return <Text style={styles.label}>{children}</Text>;
7
+ }
8
+
9
+ const styles = StyleSheet.create({
10
+ label: {
11
+ color: colors.textSecondary,
12
+ fontSize: 12,
13
+ fontFamily: fonts.bold,
14
+ letterSpacing: 0.8,
15
+ marginTop: spacing.sm,
16
+ marginBottom: spacing.sm,
17
+ },
18
+ });
@@ -0,0 +1,69 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
3
+ import { colors, fonts, m3Colors, spacing } from '../theme/proulr-theme';
4
+
5
+ export function ListItem({
6
+ avatar,
7
+ title,
8
+ subtitle,
9
+ trailing,
10
+ onPress,
11
+ showDivider,
12
+ showChevron,
13
+ }: {
14
+ avatar?: ReactNode;
15
+ title: string;
16
+ subtitle?: string;
17
+ trailing?: ReactNode;
18
+ onPress?: () => void;
19
+ showDivider?: boolean;
20
+ showChevron?: boolean;
21
+ }) {
22
+ const navChevron = onPress && showChevron !== false && !trailing;
23
+
24
+ const inner = (
25
+ <>
26
+ {avatar ? <View style={styles.listAvatar}>{avatar}</View> : null}
27
+ <View style={styles.listContent}>
28
+ <Text style={styles.listTitle} numberOfLines={1}>
29
+ {title}
30
+ </Text>
31
+ {subtitle ? (
32
+ <Text style={styles.listSubtitle} numberOfLines={1}>
33
+ {subtitle}
34
+ </Text>
35
+ ) : null}
36
+ </View>
37
+ {navChevron ? <Text style={styles.chevron}>{'\u203a'}</Text> : null}
38
+ {trailing ? <View style={styles.listTrailing}>{trailing}</View> : null}
39
+ </>
40
+ );
41
+
42
+ if (onPress) {
43
+ return (
44
+ <Pressable style={[styles.listItem, showDivider && styles.listItemDivider]} onPress={onPress}>
45
+ {inner}
46
+ </Pressable>
47
+ );
48
+ }
49
+ return <View style={[styles.listItem, showDivider && styles.listItemDivider]}>{inner}</View>;
50
+ }
51
+
52
+ const styles = StyleSheet.create({
53
+ listItem: {
54
+ flexDirection: 'row',
55
+ alignItems: 'center',
56
+ gap: spacing.md,
57
+ paddingVertical: spacing.md,
58
+ },
59
+ listItemDivider: {
60
+ borderBottomWidth: StyleSheet.hairlineWidth,
61
+ borderBottomColor: colors.border,
62
+ },
63
+ listAvatar: {},
64
+ listContent: { flex: 1 },
65
+ listTitle: { color: colors.textPrimary, fontSize: 15, fontFamily: fonts.semibold },
66
+ listSubtitle: { color: colors.textSecondary, fontSize: 13, marginTop: 2 },
67
+ listTrailing: { alignItems: 'flex-end' },
68
+ chevron: { color: m3Colors.onSurfaceVariant, fontSize: 22 },
69
+ });
@@ -0,0 +1,21 @@
1
+ import { StyleSheet, View } from 'react-native';
2
+ import { colors, radius } from '../theme/proulr-theme';
3
+
4
+ export function ProgressBar({ progress, color = colors.accent }: { progress: number; color?: string }) {
5
+ const pct = Math.min(1, Math.max(0, progress));
6
+ return (
7
+ <View style={styles.progressTrack}>
8
+ <View style={[styles.progressFill, { width: `${pct * 100}%`, backgroundColor: color }]} />
9
+ </View>
10
+ );
11
+ }
12
+
13
+ const styles = StyleSheet.create({
14
+ progressTrack: {
15
+ height: 8,
16
+ borderRadius: radius.pill,
17
+ backgroundColor: colors.surface2,
18
+ overflow: 'hidden',
19
+ },
20
+ progressFill: { height: '100%', borderRadius: radius.pill },
21
+ });
@@ -0,0 +1,43 @@
1
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
2
+ import { colors, fonts, spacing } from '../theme/proulr-theme';
3
+
4
+ export function SectionHeader({
5
+ title,
6
+ action,
7
+ onAction,
8
+ }: {
9
+ title: string;
10
+ action?: string;
11
+ onAction?: () => void;
12
+ }) {
13
+ return (
14
+ <View style={styles.sectionHeaderRow}>
15
+ <Text style={styles.sectionHeader}>{title}</Text>
16
+ {action && onAction ? (
17
+ <Pressable onPress={onAction} hitSlop={8}>
18
+ <Text style={styles.sectionAction}>{action}</Text>
19
+ </Pressable>
20
+ ) : null}
21
+ </View>
22
+ );
23
+ }
24
+
25
+ const styles = StyleSheet.create({
26
+ sectionHeaderRow: {
27
+ flexDirection: 'row',
28
+ alignItems: 'center',
29
+ justifyContent: 'space-between',
30
+ marginBottom: spacing.sm,
31
+ marginTop: spacing.xl,
32
+ },
33
+ sectionHeader: {
34
+ color: colors.textSecondary,
35
+ fontSize: 14,
36
+ fontFamily: fonts.semibold,
37
+ },
38
+ sectionAction: {
39
+ color: colors.accentText,
40
+ fontSize: 13,
41
+ fontFamily: fonts.semibold,
42
+ },
43
+ });
@@ -0,0 +1,156 @@
1
+ import { useState } from 'react';
2
+ import { Modal, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
3
+ import { AppButton } from './AppButton';
4
+ import { colors, fonts, radius, spacing } from '../theme/proulr-theme';
5
+
6
+ function OptionModal({
7
+ title,
8
+ visible,
9
+ options,
10
+ selected,
11
+ multi,
12
+ onClose,
13
+ onSelect,
14
+ }: {
15
+ title: string;
16
+ visible: boolean;
17
+ options: string[];
18
+ selected: string[];
19
+ multi?: boolean;
20
+ onClose: () => void;
21
+ onSelect: (v: string) => void;
22
+ }) {
23
+ return (
24
+ <Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
25
+ <Pressable style={styles.modalBackdrop} onPress={onClose}>
26
+ <Pressable style={styles.modalSheet}>
27
+ <Text style={styles.modalTitle}>{title}</Text>
28
+ <ScrollView style={{ maxHeight: 360 }}>
29
+ {options.map((opt) => {
30
+ const isSel = selected.includes(opt);
31
+ return (
32
+ <Pressable key={opt} style={styles.optionRow} onPress={() => onSelect(opt)}>
33
+ <Text style={{ color: isSel ? colors.accentText : colors.white, fontSize: 16 }}>{opt}</Text>
34
+ {isSel ? <Text style={{ color: colors.accent, fontSize: 16 }}>{'\u2713'}</Text> : null}
35
+ </Pressable>
36
+ );
37
+ })}
38
+ </ScrollView>
39
+ {multi ? (
40
+ <View style={{ marginTop: spacing.md }}>
41
+ <AppButton label="Pronto" onPress={onClose} />
42
+ </View>
43
+ ) : null}
44
+ </Pressable>
45
+ </Pressable>
46
+ </Modal>
47
+ );
48
+ }
49
+
50
+ export function SelectRow({
51
+ label,
52
+ value,
53
+ options,
54
+ onChange,
55
+ }: {
56
+ label: string;
57
+ value?: string | null;
58
+ options: string[];
59
+ onChange: (v: string) => void;
60
+ }) {
61
+ const [open, setOpen] = useState(false);
62
+ return (
63
+ <>
64
+ <Pressable style={styles.selectRow} onPress={() => setOpen(true)}>
65
+ <Text style={styles.selectLabel}>{label}</Text>
66
+ <Text style={styles.selectValue} numberOfLines={1}>
67
+ {value || 'Selecionar'}
68
+ </Text>
69
+ <Text style={styles.chevron}>{'\u203a'}</Text>
70
+ </Pressable>
71
+ <OptionModal
72
+ title={label}
73
+ visible={open}
74
+ options={options}
75
+ selected={value ? [value] : []}
76
+ onClose={() => setOpen(false)}
77
+ onSelect={(v) => {
78
+ onChange(v);
79
+ setOpen(false);
80
+ }}
81
+ />
82
+ </>
83
+ );
84
+ }
85
+
86
+ export function MultiSelectRow({
87
+ label,
88
+ values,
89
+ options,
90
+ onChange,
91
+ }: {
92
+ label: string;
93
+ values: string[];
94
+ options: string[];
95
+ onChange: (v: string[]) => void;
96
+ }) {
97
+ const [open, setOpen] = useState(false);
98
+ return (
99
+ <>
100
+ <Pressable style={styles.selectRow} onPress={() => setOpen(true)}>
101
+ <Text style={styles.selectLabel}>{label}</Text>
102
+ <Text style={styles.selectValue} numberOfLines={1}>
103
+ {values.length ? values.join(', ') : 'Selecionar'}
104
+ </Text>
105
+ <Text style={styles.chevron}>{'\u203a'}</Text>
106
+ </Pressable>
107
+ <OptionModal
108
+ title={label}
109
+ visible={open}
110
+ options={options}
111
+ selected={values}
112
+ multi
113
+ onClose={() => setOpen(false)}
114
+ onSelect={(v) => {
115
+ const set = new Set(values);
116
+ if (set.has(v)) set.delete(v);
117
+ else set.add(v);
118
+ onChange(Array.from(set));
119
+ }}
120
+ />
121
+ </>
122
+ );
123
+ }
124
+
125
+ const styles = StyleSheet.create({
126
+ selectRow: {
127
+ flexDirection: 'row',
128
+ alignItems: 'center',
129
+ gap: spacing.sm,
130
+ paddingHorizontal: spacing.sm,
131
+ paddingVertical: 15,
132
+ borderRadius: radius.lg,
133
+ backgroundColor: colors.surface2,
134
+ },
135
+ selectLabel: { flex: 1, color: colors.white, fontSize: 15, fontFamily: fonts.medium },
136
+ selectValue: { color: colors.textSecondary, fontSize: 14, maxWidth: 160 },
137
+ chevron: { color: colors.textSecondary, fontSize: 18 },
138
+ modalBackdrop: { flex: 1, backgroundColor: colors.overlay, justifyContent: 'flex-end' },
139
+ modalSheet: {
140
+ backgroundColor: colors.surface,
141
+ borderTopLeftRadius: radius.xl,
142
+ borderTopRightRadius: radius.xl,
143
+ padding: spacing.xl,
144
+ borderTopWidth: 1,
145
+ borderColor: colors.border,
146
+ },
147
+ modalTitle: { color: colors.white, fontSize: 18, fontFamily: fonts.bold, marginBottom: spacing.md },
148
+ optionRow: {
149
+ flexDirection: 'row',
150
+ justifyContent: 'space-between',
151
+ alignItems: 'center',
152
+ paddingVertical: 14,
153
+ borderBottomWidth: StyleSheet.hairlineWidth,
154
+ borderBottomColor: colors.border,
155
+ },
156
+ });
@@ -0,0 +1,33 @@
1
+ import { Fragment } from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+ import { colors, fonts, radius, spacing } from '../theme/proulr-theme';
4
+
5
+ export function StatRow({ stats }: { stats: { value: string; label: string; highlight?: boolean }[] }) {
6
+ return (
7
+ <View style={styles.statRow}>
8
+ {stats.map((s, i) => (
9
+ <Fragment key={s.label}>
10
+ {i > 0 ? <View style={styles.statDivider} /> : null}
11
+ <View style={styles.statItem}>
12
+ <Text style={[styles.statValue, s.highlight && { color: colors.accentText }]}>{s.value}</Text>
13
+ <Text style={styles.statLabel}>{s.label}</Text>
14
+ </View>
15
+ </Fragment>
16
+ ))}
17
+ </View>
18
+ );
19
+ }
20
+
21
+ const styles = StyleSheet.create({
22
+ statRow: {
23
+ flexDirection: 'row',
24
+ alignItems: 'center',
25
+ backgroundColor: colors.surface2,
26
+ borderRadius: radius.lg,
27
+ paddingVertical: spacing.sm,
28
+ },
29
+ statItem: { flex: 1, alignItems: 'center' },
30
+ statDivider: { width: 1, height: 36, backgroundColor: colors.border },
31
+ statValue: { color: colors.white, fontSize: 18, fontFamily: fonts.bold },
32
+ statLabel: { color: colors.textSecondary, fontSize: 12, marginTop: 2, fontFamily: fonts.medium },
33
+ });
@@ -1,10 +1,19 @@
1
1
  import React, { useState } from 'react';
2
- import { Pressable, StyleSheet, Text, TextInput, TextInputProps, View, ViewStyle } from 'react-native';
2
+ import {
3
+ Pressable,
4
+ StyleSheet,
5
+ StyleProp,
6
+ TextInput,
7
+ TextInputProps,
8
+ View,
9
+ ViewStyle,
10
+ } from 'react-native';
11
+ import { AppIcon, type AppIconName } from './app-icon';
3
12
  import { fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
4
13
 
5
14
  export function TextField(
6
15
  props: TextInputProps & {
7
- containerStyle?: ViewStyle;
16
+ containerStyle?: StyleProp<ViewStyle>;
8
17
  focused?: boolean;
9
18
  inputRef?: React.Ref<TextInput>;
10
19
  },
@@ -30,7 +39,7 @@ export function TextField(
30
39
 
31
40
  export function PasswordField(
32
41
  props: TextInputProps & {
33
- containerStyle?: ViewStyle;
42
+ containerStyle?: StyleProp<ViewStyle>;
34
43
  inputRef?: React.Ref<TextInput>;
35
44
  },
36
45
  ) {
@@ -51,8 +60,13 @@ export function PasswordField(
51
60
  hitSlop={8}
52
61
  style={styles.passwordToggle}
53
62
  accessibilityLabel={visible ? 'Ocultar senha' : 'Mostrar senha'}
63
+ accessibilityRole="button"
54
64
  >
55
- <Text style={styles.passwordToggleIcon}>{visible ? '🙈' : '👁'}</Text>
65
+ <AppIcon
66
+ name={(visible ? 'visibility-off' : 'visibility') as AppIconName}
67
+ size={20}
68
+ color={m3Colors.onSurfaceVariant}
69
+ />
56
70
  </Pressable>
57
71
  </View>
58
72
  );
@@ -77,5 +91,4 @@ const styles = StyleSheet.create({
77
91
  alignItems: 'center',
78
92
  justifyContent: 'center',
79
93
  },
80
- passwordToggleIcon: { fontSize: 18 },
81
94
  });
@@ -0,0 +1,56 @@
1
+ import { StyleSheet, Switch, Text, View } from 'react-native';
2
+ import { fonts, m3Colors, spacing } from '../theme/proulr-theme';
3
+
4
+ export function ToggleRow({
5
+ label,
6
+ hint,
7
+ value,
8
+ onChange,
9
+ disabled,
10
+ }: {
11
+ label: string;
12
+ hint?: string;
13
+ value: boolean;
14
+ onChange: (value: boolean) => void;
15
+ disabled?: boolean;
16
+ }) {
17
+ return (
18
+ <View style={styles.row}>
19
+ <View style={styles.copy}>
20
+ <Text style={styles.label}>{label}</Text>
21
+ {hint ? <Text style={styles.hint}>{hint}</Text> : null}
22
+ </View>
23
+ <Switch
24
+ value={value}
25
+ onValueChange={onChange}
26
+ disabled={disabled}
27
+ trackColor={{ false: m3Colors.surfaceContainerHighest, true: m3Colors.primary }}
28
+ thumbColor={m3Colors.onPrimary}
29
+ />
30
+ </View>
31
+ );
32
+ }
33
+
34
+ const styles = StyleSheet.create({
35
+ row: {
36
+ flexDirection: 'row',
37
+ alignItems: 'center',
38
+ gap: spacing.md,
39
+ paddingVertical: spacing.xs,
40
+ },
41
+ copy: {
42
+ flex: 1,
43
+ gap: spacing.xs,
44
+ },
45
+ label: {
46
+ color: m3Colors.onSurface,
47
+ fontSize: 15,
48
+ fontFamily: fonts.semibold,
49
+ },
50
+ hint: {
51
+ color: m3Colors.onSurfaceVariant,
52
+ fontSize: 12,
53
+ lineHeight: 17,
54
+ fontFamily: fonts.regular,
55
+ },
56
+ });
@@ -168,21 +168,26 @@ export function AppChromeMicGlyph({ color = m3Colors.onPrimary, size = 24 }: App
168
168
  }
169
169
 
170
170
  export function AppChromeSendGlyph({ color = m3Colors.onPrimary, size = 24 }: AppChromeGlyphProps) {
171
+ const wingSpan = size * 0.17;
172
+ const bodyLength = size * 0.5;
173
+
171
174
  return (
172
175
  <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
173
- <View
174
- style={{
175
- width: 0,
176
- height: 0,
177
- borderTopWidth: size * 0.24,
178
- borderBottomWidth: size * 0.24,
179
- borderLeftWidth: size * 0.38,
180
- borderTopColor: 'transparent',
181
- borderBottomColor: 'transparent',
182
- borderLeftColor: color,
183
- marginLeft: size * 0.1,
184
- }}
185
- />
176
+ <View style={{ transform: [{ rotate: '-42deg' }] }}>
177
+ <View
178
+ style={{
179
+ width: 0,
180
+ height: 0,
181
+ borderTopWidth: wingSpan,
182
+ borderBottomWidth: wingSpan,
183
+ borderLeftWidth: bodyLength,
184
+ borderTopColor: 'transparent',
185
+ borderBottomColor: 'transparent',
186
+ borderLeftColor: color,
187
+ marginLeft: size * 0.04,
188
+ }}
189
+ />
190
+ </View>
186
191
  </View>
187
192
  );
188
193
  }
@@ -50,10 +50,10 @@ export function FloatingCreateMenu({
50
50
  pressed && { opacity: 0.9 },
51
51
  ]}
52
52
  >
53
- <Text style={[styles.mainFabGlyph, open && styles.mainFabGlyphOpen]}>{open ? '\u2715' : '+'}</Text>
53
+ <Text style={[styles.mainFabGlyph]}>{open ? '\u2715' : '\u002B'}</Text>
54
54
  </Pressable>
55
55
 
56
- <Modal visible={open} transparent animationType="fade" onRequestClose={close}>
56
+ <Modal visible={open} animationType="fade" onRequestClose={close}>
57
57
  <View style={styles.overlay}>
58
58
  <Pressable style={styles.backdrop} onPress={close} accessibilityLabel="Fechar menu" />
59
59
 
@@ -115,18 +115,12 @@ const styles = StyleSheet.create({
115
115
  color: m3Colors.onSecondary,
116
116
  fontSize: 30,
117
117
  fontFamily: fonts.bold,
118
- marginTop: -2,
119
- },
120
- mainFabGlyphOpen: {
121
- color: m3Colors.onSurface,
122
- fontSize: 22,
123
118
  },
124
119
  overlay: {
125
120
  flex: 1,
126
121
  },
127
122
  backdrop: {
128
123
  ...StyleSheet.absoluteFill,
129
- backgroundColor: m3Colors.scrim,
130
124
  },
131
125
  stack: {
132
126
  position: 'absolute',
@@ -1,7 +1,9 @@
1
1
  export { AppButton } from './AppButton';
2
+ export { AppChromeIconButton } from './AppChromeIconButton';
2
3
  export { AppLoadingSpinner } from './AppLoadingSpinner';
3
4
  export { AuthStepShell } from './AuthStepShell';
4
5
  export { Card } from './Card';
6
+ export { Chip } from './Chip';
5
7
  export { ComposeAudiencePill } from './compose-audience-pill';
6
8
  export {
7
9
  ComposeCameraGlyph,
@@ -92,8 +94,16 @@ export { ComposeToolbar, ComposeToolbarIcon, COMPOSE_TOOLBAR_ICON_SIZE } from '.
92
94
  export { ComposeReplyAudienceSheet, type ComposeReplyAudienceOption, type ComposeReplyAudienceValue } from './compose-reply-audience-sheet';
93
95
  export { ComposeVisibilityRow } from './compose-visibility-row';
94
96
  export { DateField } from './DateField';
97
+ export { HintText } from './HintText';
95
98
  export { FloatingCreateMenu, type FloatingCreateMenuAction } from './floating-create-menu';
96
99
  export { InfoBanner } from './InfoBanner';
100
+ export { Label } from './Label';
101
+ export { ListItem } from './ListItem';
102
+ export { ProgressBar } from './ProgressBar';
103
+ export { SectionHeader } from './SectionHeader';
104
+ export { SelectRow, MultiSelectRow } from './SelectRow';
105
+ export { StatRow } from './StatRow';
106
+ export { ToggleRow } from './ToggleRow';
97
107
  export { PasswordField, TextField } from './TextField';
98
108
  export {
99
109
  StoryCollageGlyph,
@@ -0,0 +1,16 @@
1
+ /** Barrel leve — só primitivos de formulário/lista (sem glyphs/compose/story). */
2
+ export { AppButton } from './AppButton';
3
+ export { AppLoadingSpinner } from './AppLoadingSpinner';
4
+ export { Card } from './Card';
5
+ export { Chip } from './Chip';
6
+ export { DateField } from './DateField';
7
+ export { HintText } from './HintText';
8
+ export { InfoBanner } from './InfoBanner';
9
+ export { Label } from './Label';
10
+ export { ListItem } from './ListItem';
11
+ export { ProgressBar } from './ProgressBar';
12
+ export { SectionHeader } from './SectionHeader';
13
+ export { SelectRow, MultiSelectRow } from './SelectRow';
14
+ export { StatRow } from './StatRow';
15
+ export { TextField, PasswordField } from './TextField';
16
+ export { ToggleRow } from './ToggleRow';