@proulr/sdui-components 0.1.86 → 0.1.88
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/README.md +2 -0
- package/package.json +2 -2
- package/src/bdc/components/discover.tsx +1 -4
- package/src/bdc/components/forms.tsx +4 -0
- package/src/bdc/components/inbox.tsx +4 -0
- package/src/renderer/SduiScreenRenderer.tsx +14 -2
- package/src/renderer/blocks/SduiDiscoverBlocks.tsx +50 -34
- package/src/renderer/blocks/SduiInboxBlocks.tsx +26 -11
- package/src/renderer/blocks/SduiLayoutBlocks.tsx +39 -46
- package/src/renderer/blocks/SduiNativeAdBlock.tsx +7 -8
- package/src/renderer/blocks/SduiSocialBlocks.tsx +4 -27
- package/src/renderer/lib/discover-profile-formatters.test.ts +7 -0
- package/src/renderer/lib/discover-profile-formatters.ts +6 -3
- package/src/renderer/sdui-hybrid-renderer-context.tsx +6 -1
- package/src/renderer/theme/proulr-theme.test.ts +107 -0
- package/src/renderer/theme/proulr-theme.ts +51 -9
- package/src/renderer/ui/AppButton.tsx +54 -28
- package/src/renderer/ui/AppChromeIconButton.tsx +19 -7
- package/src/renderer/ui/AuthStepShell.tsx +1 -2
- package/src/renderer/ui/Chip.tsx +41 -34
- package/src/renderer/ui/ChipRow.tsx +7 -13
- package/src/renderer/ui/HintText.tsx +2 -4
- package/src/renderer/ui/InfoBanner.tsx +20 -3
- package/src/renderer/ui/Label.tsx +2 -3
- package/src/renderer/ui/ListItem.tsx +28 -4
- package/src/renderer/ui/RangeSlider.tsx +1 -2
- package/src/renderer/ui/SectionHeader.tsx +3 -4
- package/src/renderer/ui/SelectRow.tsx +113 -22
- package/src/renderer/ui/Skeleton.tsx +13 -4
- package/src/renderer/ui/StatRow.tsx +3 -3
- package/src/renderer/ui/TextField.tsx +15 -11
- package/src/renderer/ui/ToggleRow.tsx +38 -11
- package/src/renderer/ui/compose-audience-pill.tsx +2 -3
- package/src/renderer/ui/compose-media-carousel.tsx +20 -7
- package/src/renderer/ui/compose-media-shortcut.tsx +2 -3
- package/src/renderer/ui/compose-pill-input.tsx +28 -9
- package/src/renderer/ui/compose-recent-media-grid.tsx +5 -8
- package/src/renderer/ui/compose-reply-audience-sheet.tsx +19 -8
- package/src/renderer/ui/compose-screen-header.tsx +15 -10
- package/src/renderer/ui/compose-text-area.tsx +14 -8
- package/src/renderer/ui/compose-toolbar-icon.tsx +9 -5
- package/src/renderer/ui/compose-toolbar.tsx +0 -7
- package/src/renderer/ui/compose-visibility-row.tsx +2 -3
- package/src/renderer/ui/floating-create-menu.tsx +2 -3
- package/src/renderer/ui/focus-ring.ts +26 -0
- package/src/renderer/ui/index.ts +3 -1
- package/src/renderer/ui/story-compose-header.tsx +14 -9
- package/src/renderer/ui/story-create-shortcut-row.tsx +1 -1
- package/src/renderer/ui/story-editor-chrome.tsx +30 -16
- package/src/renderer/ui/story-surface-picker.tsx +1 -2
- package/src/renderer/ui/use-reduce-motion.ts +31 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { Image, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import { AppIcon } from './app-icon';
|
|
4
|
-
import { fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
|
|
4
|
+
import { fonts, m3Colors, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
5
5
|
|
|
6
6
|
export type ComposeMediaCarouselItem = {
|
|
7
7
|
id: string;
|
|
@@ -11,14 +11,25 @@ export type ComposeMediaCarouselItem = {
|
|
|
11
11
|
|
|
12
12
|
const TILE_SIZE = 168;
|
|
13
13
|
|
|
14
|
+
/** Coluna do compositor: 40 de avatar + 8 de gap — o rail de mídia alinha com o texto. */
|
|
15
|
+
const COMPOSER_TEXT_GUTTER = 48;
|
|
16
|
+
|
|
17
|
+
/** O badge visual é pequeno para não cobrir a miniatura; o alvo de toque cresce com o slop. */
|
|
18
|
+
const REMOVE_HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 };
|
|
19
|
+
|
|
14
20
|
export function ComposeMediaCarousel({
|
|
15
21
|
items,
|
|
16
22
|
limit,
|
|
23
|
+
videoLabel,
|
|
24
|
+
removeLabel,
|
|
17
25
|
onRemove,
|
|
18
26
|
renderVideo,
|
|
19
27
|
}: {
|
|
20
28
|
items: ComposeMediaCarouselItem[];
|
|
21
29
|
limit: number;
|
|
30
|
+
/** Copy vem do caller (Rosetta) — o renderer não traduz. */
|
|
31
|
+
videoLabel: string;
|
|
32
|
+
removeLabel: string;
|
|
22
33
|
onRemove: (id: string) => void;
|
|
23
34
|
renderVideo?: (uri: string) => ReactNode;
|
|
24
35
|
}) {
|
|
@@ -38,7 +49,7 @@ export function ComposeMediaCarousel({
|
|
|
38
49
|
renderVideo(item.uri)
|
|
39
50
|
) : (
|
|
40
51
|
<View style={styles.videoFallback}>
|
|
41
|
-
<Text style={styles.videoFallbackText}>
|
|
52
|
+
<Text style={styles.videoFallbackText}>{videoLabel}</Text>
|
|
42
53
|
</View>
|
|
43
54
|
)
|
|
44
55
|
) : (
|
|
@@ -47,8 +58,9 @@ export function ComposeMediaCarousel({
|
|
|
47
58
|
<Pressable
|
|
48
59
|
onPress={() => onRemove(item.id)}
|
|
49
60
|
accessibilityRole="button"
|
|
50
|
-
accessibilityLabel=
|
|
51
|
-
|
|
61
|
+
accessibilityLabel={removeLabel}
|
|
62
|
+
hitSlop={REMOVE_HIT_SLOP}
|
|
63
|
+
style={({ pressed }) => [styles.removeBtn, pressed && styles.removeBtnPressed]}
|
|
52
64
|
>
|
|
53
65
|
<AppIcon name="close" size={14} color={m3Colors.onPrimary} />
|
|
54
66
|
</Pressable>
|
|
@@ -65,7 +77,7 @@ export function ComposeMediaCarousel({
|
|
|
65
77
|
const styles = StyleSheet.create({
|
|
66
78
|
wrap: {
|
|
67
79
|
marginTop: spacing.md,
|
|
68
|
-
marginLeft:
|
|
80
|
+
marginLeft: COMPOSER_TEXT_GUTTER,
|
|
69
81
|
},
|
|
70
82
|
scrollContent: {
|
|
71
83
|
gap: spacing.sm,
|
|
@@ -89,8 +101,8 @@ const styles = StyleSheet.create({
|
|
|
89
101
|
},
|
|
90
102
|
videoFallbackText: {
|
|
91
103
|
color: m3Colors.onSurfaceVariant,
|
|
104
|
+
...m3Type.bodySmall,
|
|
92
105
|
fontFamily: fonts.medium,
|
|
93
|
-
fontSize: 13,
|
|
94
106
|
},
|
|
95
107
|
removeBtn: {
|
|
96
108
|
position: 'absolute',
|
|
@@ -103,10 +115,11 @@ const styles = StyleSheet.create({
|
|
|
103
115
|
alignItems: 'center',
|
|
104
116
|
justifyContent: 'center',
|
|
105
117
|
},
|
|
118
|
+
removeBtnPressed: { backgroundColor: m3Colors.statePressed },
|
|
106
119
|
counter: {
|
|
107
120
|
marginTop: spacing.xs,
|
|
108
121
|
color: m3Colors.onSurfaceVariant,
|
|
122
|
+
...m3Type.labelSmall,
|
|
109
123
|
fontFamily: fonts.medium,
|
|
110
|
-
fontSize: 12,
|
|
111
124
|
},
|
|
112
125
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
|
-
import {
|
|
3
|
+
import { m3Colors, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
4
4
|
|
|
5
5
|
const MEDIA_TILE_SIZE = 88;
|
|
6
6
|
|
|
@@ -48,9 +48,8 @@ const styles = StyleSheet.create({
|
|
|
48
48
|
position: 'relative',
|
|
49
49
|
},
|
|
50
50
|
hintText: {
|
|
51
|
+
...m3Type.labelSmall,
|
|
51
52
|
color: m3Colors.onPrimary,
|
|
52
|
-
fontFamily: fonts.medium,
|
|
53
|
-
fontSize: 11,
|
|
54
53
|
},
|
|
55
54
|
hintTail: {
|
|
56
55
|
position: 'absolute',
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from 'react-native';
|
|
19
19
|
import { AppIcon } from './app-icon';
|
|
20
20
|
import { ChromeGlassLayer } from './ChromeGlass';
|
|
21
|
-
import { colors, fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
|
|
21
|
+
import { colors, fonts, m3Colors, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
22
22
|
|
|
23
23
|
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
|
24
24
|
UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
@@ -27,8 +27,12 @@ if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental
|
|
|
27
27
|
const ACTION_BUTTON_SIZE = 48;
|
|
28
28
|
/** Filled action circle when nested inside the pill (Instagram-style composer). */
|
|
29
29
|
const ACTION_BUTTON_SIZE_INSET = 36;
|
|
30
|
-
|
|
31
|
-
const
|
|
30
|
+
/** O círculo inset é menor visualmente; o slop devolve os 44pt de alvo de toque. */
|
|
31
|
+
const ACTION_BUTTON_INSET_HIT_SLOP = { top: 4, bottom: 4, left: 4, right: 4 };
|
|
32
|
+
// A medição de altura do composer depende da linha do texto: deriva do token de
|
|
33
|
+
// corpo para não criar uma segunda escala tipográfica no design system.
|
|
34
|
+
const FONT_SIZE = m3Type.body.fontSize;
|
|
35
|
+
const LINE_HEIGHT = m3Type.body.lineHeight;
|
|
32
36
|
const MAX_LINES_DEFAULT = 4;
|
|
33
37
|
const PILL_MIN_HEIGHT = ACTION_BUTTON_SIZE;
|
|
34
38
|
const VERTICAL_PAD = (PILL_MIN_HEIGHT - LINE_HEIGHT) / 2;
|
|
@@ -38,6 +42,9 @@ export type ComposePillVariant = 'surface' | 'chrome' | 'overlay';
|
|
|
38
42
|
export type ComposePillReplyPreview = {
|
|
39
43
|
title: string;
|
|
40
44
|
body: string;
|
|
45
|
+
/** Rótulo de acessibilidade do botão de dispensa — vem de quem compõe o wire,
|
|
46
|
+
* o renderer não traduz copy. */
|
|
47
|
+
dismissLabel: string;
|
|
41
48
|
onDismiss: () => void;
|
|
42
49
|
};
|
|
43
50
|
|
|
@@ -340,7 +347,7 @@ export function ComposePillReplyBar({
|
|
|
340
347
|
hitSlop={8}
|
|
341
348
|
style={styles.replyBarClose}
|
|
342
349
|
accessibilityRole="button"
|
|
343
|
-
accessibilityLabel=
|
|
350
|
+
accessibilityLabel={preview.dismissLabel}
|
|
344
351
|
>
|
|
345
352
|
<AppIcon name="close" size={14} color={m3Colors.onSurfaceVariant} />
|
|
346
353
|
</Pressable>
|
|
@@ -361,7 +368,11 @@ export function ComposePillIconButton({
|
|
|
361
368
|
}) {
|
|
362
369
|
return (
|
|
363
370
|
<Pressable
|
|
364
|
-
style={
|
|
371
|
+
style={({ pressed }) => [
|
|
372
|
+
styles.iconButton,
|
|
373
|
+
disabled && styles.iconButtonDisabled,
|
|
374
|
+
pressed && !disabled && styles.iconButtonPressed,
|
|
375
|
+
]}
|
|
365
376
|
onPress={onPress}
|
|
366
377
|
disabled={disabled || !onPress}
|
|
367
378
|
hitSlop={6}
|
|
@@ -390,15 +401,17 @@ export function ComposePillActionButton({
|
|
|
390
401
|
const size = inset ? ACTION_BUTTON_SIZE_INSET : ACTION_BUTTON_SIZE;
|
|
391
402
|
return (
|
|
392
403
|
<Pressable
|
|
393
|
-
style={[
|
|
404
|
+
style={({ pressed }) => [
|
|
394
405
|
styles.actionButton,
|
|
395
406
|
{ width: size, height: size, borderRadius: size / 2 },
|
|
396
407
|
disabled && styles.actionButtonDisabled,
|
|
408
|
+
pressed && !disabled && styles.actionButtonPressed,
|
|
397
409
|
]}
|
|
398
410
|
onPress={onPress}
|
|
399
411
|
disabled={disabled || !onPress}
|
|
400
412
|
accessibilityRole="button"
|
|
401
413
|
accessibilityLabel={label}
|
|
414
|
+
hitSlop={inset ? ACTION_BUTTON_INSET_HIT_SLOP : undefined}
|
|
402
415
|
>
|
|
403
416
|
{children}
|
|
404
417
|
</Pressable>
|
|
@@ -571,17 +584,17 @@ const styles = StyleSheet.create({
|
|
|
571
584
|
},
|
|
572
585
|
replyBarContent: {
|
|
573
586
|
flex: 1,
|
|
574
|
-
gap:
|
|
587
|
+
gap: spacing.xs,
|
|
575
588
|
zIndex: 1,
|
|
576
589
|
},
|
|
577
590
|
replyBarName: {
|
|
591
|
+
...m3Type.labelSmall,
|
|
578
592
|
color: m3Colors.primary,
|
|
579
|
-
fontSize: 13,
|
|
580
593
|
fontFamily: fonts.semibold,
|
|
581
594
|
},
|
|
582
595
|
replyBarPreview: {
|
|
596
|
+
...m3Type.labelSmall,
|
|
583
597
|
color: m3Colors.onSurfaceVariant,
|
|
584
|
-
fontSize: 13,
|
|
585
598
|
fontFamily: fonts.regular,
|
|
586
599
|
},
|
|
587
600
|
replyBarClose: {
|
|
@@ -632,6 +645,9 @@ const styles = StyleSheet.create({
|
|
|
632
645
|
iconButtonDisabled: {
|
|
633
646
|
opacity: 0.4,
|
|
634
647
|
},
|
|
648
|
+
iconButtonPressed: {
|
|
649
|
+
opacity: 0.85,
|
|
650
|
+
},
|
|
635
651
|
trailingIcons: {
|
|
636
652
|
flexDirection: 'row',
|
|
637
653
|
alignItems: 'center',
|
|
@@ -688,6 +704,9 @@ const styles = StyleSheet.create({
|
|
|
688
704
|
actionButtonDisabled: {
|
|
689
705
|
opacity: 0.45,
|
|
690
706
|
},
|
|
707
|
+
actionButtonPressed: {
|
|
708
|
+
opacity: 0.85,
|
|
709
|
+
},
|
|
691
710
|
});
|
|
692
711
|
|
|
693
712
|
export const COMPOSE_PILL_ACTION_BUTTON_SIZE = ACTION_BUTTON_SIZE;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useWindowDimensions,
|
|
8
8
|
View,
|
|
9
9
|
} from 'react-native';
|
|
10
|
-
import {
|
|
10
|
+
import { m3Colors, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
11
11
|
import { AppIcon } from './app-icon';
|
|
12
12
|
import { AppLoadingSpinner } from './AppLoadingSpinner';
|
|
13
13
|
import { StoryTextGlyph } from './story-glyphs';
|
|
@@ -210,9 +210,8 @@ const styles = StyleSheet.create({
|
|
|
210
210
|
borderColor: m3Colors.outlineVariant,
|
|
211
211
|
},
|
|
212
212
|
selectLabel: {
|
|
213
|
+
...m3Type.label,
|
|
213
214
|
color: m3Colors.onSurface,
|
|
214
|
-
fontFamily: fonts.medium,
|
|
215
|
-
fontSize: 13,
|
|
216
215
|
},
|
|
217
216
|
loadingWrap: {
|
|
218
217
|
alignItems: 'center',
|
|
@@ -255,14 +254,12 @@ const styles = StyleSheet.create({
|
|
|
255
254
|
backgroundColor: m3Colors.scrim,
|
|
256
255
|
},
|
|
257
256
|
videoBadgeText: {
|
|
257
|
+
...m3Type.labelSmall,
|
|
258
258
|
color: m3Colors.onPrimary,
|
|
259
|
-
fontFamily: fonts.medium,
|
|
260
|
-
fontSize: 10,
|
|
261
259
|
},
|
|
262
260
|
textCellLabel: {
|
|
263
|
-
|
|
261
|
+
...m3Type.labelSmall,
|
|
264
262
|
color: m3Colors.onSurfaceVariant,
|
|
265
|
-
|
|
266
|
-
fontSize: 11,
|
|
263
|
+
marginTop: spacing.xs,
|
|
267
264
|
},
|
|
268
265
|
});
|
|
@@ -14,12 +14,19 @@ export type ComposeReplyAudienceOption = {
|
|
|
14
14
|
|
|
15
15
|
export function ComposeReplyAudienceSheet({
|
|
16
16
|
visible,
|
|
17
|
+
title,
|
|
18
|
+
subtitle,
|
|
19
|
+
closeLabel,
|
|
17
20
|
value,
|
|
18
21
|
options,
|
|
19
22
|
onClose,
|
|
20
23
|
onSelect,
|
|
21
24
|
}: {
|
|
22
25
|
visible: boolean;
|
|
26
|
+
/** Copy do caller (Rosetta) — o renderer não traduz. */
|
|
27
|
+
title: string;
|
|
28
|
+
subtitle: string;
|
|
29
|
+
closeLabel: string;
|
|
23
30
|
value: ComposeReplyAudienceValue;
|
|
24
31
|
options: ComposeReplyAudienceOption[];
|
|
25
32
|
onClose: () => void;
|
|
@@ -28,13 +35,16 @@ export function ComposeReplyAudienceSheet({
|
|
|
28
35
|
return (
|
|
29
36
|
<Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
|
|
30
37
|
<View style={styles.backdrop}>
|
|
31
|
-
<Pressable
|
|
38
|
+
<Pressable
|
|
39
|
+
style={styles.backdropPress}
|
|
40
|
+
onPress={onClose}
|
|
41
|
+
accessibilityRole="button"
|
|
42
|
+
accessibilityLabel={closeLabel}
|
|
43
|
+
/>
|
|
32
44
|
<View style={styles.sheet}>
|
|
33
45
|
<View style={styles.handle} />
|
|
34
|
-
<Text style={styles.title}>
|
|
35
|
-
<Text style={styles.subtitle}>
|
|
36
|
-
Escolha quem pode responder a este post. Perfis mencionados sempre poderão responder.
|
|
37
|
-
</Text>
|
|
46
|
+
<Text style={styles.title}>{title}</Text>
|
|
47
|
+
<Text style={styles.subtitle}>{subtitle}</Text>
|
|
38
48
|
|
|
39
49
|
{options.map((option) => {
|
|
40
50
|
const selected = option.value === value;
|
|
@@ -47,7 +57,7 @@ export function ComposeReplyAudienceSheet({
|
|
|
47
57
|
}}
|
|
48
58
|
accessibilityRole="button"
|
|
49
59
|
accessibilityState={{ selected }}
|
|
50
|
-
style={({ pressed }) => [styles.option, pressed &&
|
|
60
|
+
style={({ pressed }) => [styles.option, pressed && styles.optionPressed]}
|
|
51
61
|
>
|
|
52
62
|
<View style={styles.optionIconWrap}>{option.icon}</View>
|
|
53
63
|
<View style={styles.optionBody}>
|
|
@@ -107,14 +117,15 @@ const styles = StyleSheet.create({
|
|
|
107
117
|
color: m3Colors.onSurfaceVariant,
|
|
108
118
|
...m3Type.bodySmall,
|
|
109
119
|
marginBottom: spacing.md,
|
|
110
|
-
lineHeight: 18,
|
|
111
120
|
},
|
|
112
121
|
option: {
|
|
113
122
|
flexDirection: 'row',
|
|
114
123
|
alignItems: 'center',
|
|
124
|
+
minHeight: 56,
|
|
115
125
|
paddingVertical: spacing.md,
|
|
116
126
|
gap: spacing.md,
|
|
117
127
|
},
|
|
128
|
+
optionPressed: { backgroundColor: m3Colors.statePressed },
|
|
118
129
|
optionIconWrap: {
|
|
119
130
|
width: 44,
|
|
120
131
|
height: 44,
|
|
@@ -129,8 +140,8 @@ const styles = StyleSheet.create({
|
|
|
129
140
|
},
|
|
130
141
|
optionLabel: {
|
|
131
142
|
color: m3Colors.onSurface,
|
|
143
|
+
...m3Type.titleSmall,
|
|
132
144
|
fontFamily: fonts.semibold,
|
|
133
|
-
fontSize: 16,
|
|
134
145
|
},
|
|
135
146
|
optionHint: {
|
|
136
147
|
color: m3Colors.onSurfaceVariant,
|
|
@@ -6,14 +6,17 @@ import { AppLoadingSpinner } from './AppLoadingSpinner';
|
|
|
6
6
|
|
|
7
7
|
export function ComposeScreenHeader({
|
|
8
8
|
title,
|
|
9
|
-
|
|
9
|
+
closeLabel,
|
|
10
|
+
actionLabel,
|
|
10
11
|
canAction,
|
|
11
12
|
loading,
|
|
12
13
|
onClose,
|
|
13
14
|
onAction,
|
|
14
15
|
}: {
|
|
15
16
|
title?: string;
|
|
16
|
-
|
|
17
|
+
/** Copy vem do caller (wire/Rosetta) — o renderer não traduz nem tem default de produto. */
|
|
18
|
+
closeLabel: string;
|
|
19
|
+
actionLabel: string;
|
|
17
20
|
canAction: boolean;
|
|
18
21
|
loading?: boolean;
|
|
19
22
|
onClose: () => void;
|
|
@@ -25,10 +28,9 @@ export function ComposeScreenHeader({
|
|
|
25
28
|
<View style={[styles.header, { paddingTop: insets.top + spacing.xs }]}>
|
|
26
29
|
<Pressable
|
|
27
30
|
onPress={onClose}
|
|
28
|
-
hitSlop={10}
|
|
29
31
|
accessibilityRole="button"
|
|
30
|
-
accessibilityLabel=
|
|
31
|
-
style={styles.closeBtn}
|
|
32
|
+
accessibilityLabel={closeLabel}
|
|
33
|
+
style={({ pressed }) => [styles.closeBtn, pressed && styles.closeBtnPressed]}
|
|
32
34
|
>
|
|
33
35
|
<AppIcon name="close" color={m3Colors.onSurface} size={18} />
|
|
34
36
|
</Pressable>
|
|
@@ -50,7 +52,7 @@ export function ComposeScreenHeader({
|
|
|
50
52
|
styles.publishPill,
|
|
51
53
|
canAction && styles.publishPillActive,
|
|
52
54
|
(!canAction || loading) && styles.publishPillDisabled,
|
|
53
|
-
pressed && canAction &&
|
|
55
|
+
pressed && canAction && styles.publishPillPressed,
|
|
54
56
|
]}
|
|
55
57
|
>
|
|
56
58
|
{loading ? (
|
|
@@ -75,11 +77,13 @@ const styles = StyleSheet.create({
|
|
|
75
77
|
backgroundColor: m3Colors.surface,
|
|
76
78
|
},
|
|
77
79
|
closeBtn: {
|
|
78
|
-
width:
|
|
79
|
-
height:
|
|
80
|
+
width: 44,
|
|
81
|
+
height: 44,
|
|
80
82
|
alignItems: 'center',
|
|
81
83
|
justifyContent: 'center',
|
|
84
|
+
borderRadius: m3Shape.cornerFull,
|
|
82
85
|
},
|
|
86
|
+
closeBtnPressed: { backgroundColor: m3Colors.statePressed },
|
|
83
87
|
title: {
|
|
84
88
|
flex: 1,
|
|
85
89
|
color: m3Colors.onSurface,
|
|
@@ -91,7 +95,7 @@ const styles = StyleSheet.create({
|
|
|
91
95
|
},
|
|
92
96
|
publishPill: {
|
|
93
97
|
minWidth: 88,
|
|
94
|
-
height:
|
|
98
|
+
height: 44,
|
|
95
99
|
borderRadius: m3Shape.cornerFull,
|
|
96
100
|
alignItems: 'center',
|
|
97
101
|
justifyContent: 'center',
|
|
@@ -101,13 +105,14 @@ const styles = StyleSheet.create({
|
|
|
101
105
|
publishPillActive: {
|
|
102
106
|
backgroundColor: m3Colors.primary,
|
|
103
107
|
},
|
|
108
|
+
publishPillPressed: { opacity: 0.88 },
|
|
104
109
|
publishPillDisabled: {
|
|
105
110
|
opacity: 0.45,
|
|
106
111
|
},
|
|
107
112
|
publishLabel: {
|
|
108
113
|
color: m3Colors.onSurfaceVariant,
|
|
114
|
+
...m3Type.label,
|
|
109
115
|
fontFamily: fonts.semibold,
|
|
110
|
-
fontSize: 14,
|
|
111
116
|
},
|
|
112
117
|
publishLabelActive: {
|
|
113
118
|
color: m3Colors.onPrimary,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { Platform, Pressable, StyleSheet, TextInput, TextInputProps, View } from 'react-native';
|
|
3
|
-
import {
|
|
3
|
+
import { m3Colors, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
4
4
|
|
|
5
|
-
export function ComposeTextArea(
|
|
5
|
+
export function ComposeTextArea({
|
|
6
|
+
placeholder,
|
|
7
|
+
...props
|
|
8
|
+
}: TextInputProps & { placeholder: string }) {
|
|
6
9
|
return (
|
|
7
10
|
<TextInput
|
|
8
|
-
placeholder=
|
|
11
|
+
placeholder={placeholder}
|
|
9
12
|
placeholderTextColor={m3Colors.onSurfaceVariant}
|
|
10
13
|
multiline
|
|
11
14
|
style={[styles.input, Platform.OS === 'web' ? styles.inputWeb : null]}
|
|
@@ -17,9 +20,12 @@ export function ComposeTextArea(props: TextInputProps) {
|
|
|
17
20
|
|
|
18
21
|
export function ComposeMediaPreviewFrame({
|
|
19
22
|
children,
|
|
23
|
+
removeLabel,
|
|
20
24
|
onRemove,
|
|
21
25
|
}: {
|
|
22
26
|
children: ReactNode;
|
|
27
|
+
/** Copy do caller (Rosetta) — o renderer não traduz. */
|
|
28
|
+
removeLabel: string;
|
|
23
29
|
onRemove: () => void;
|
|
24
30
|
}) {
|
|
25
31
|
return (
|
|
@@ -28,8 +34,9 @@ export function ComposeMediaPreviewFrame({
|
|
|
28
34
|
<Pressable
|
|
29
35
|
onPress={onRemove}
|
|
30
36
|
accessibilityRole="button"
|
|
31
|
-
accessibilityLabel=
|
|
32
|
-
|
|
37
|
+
accessibilityLabel={removeLabel}
|
|
38
|
+
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
39
|
+
style={({ pressed }) => [styles.previewRemove, pressed && styles.previewRemovePressed]}
|
|
33
40
|
>
|
|
34
41
|
<ComposeRemoveGlyph />
|
|
35
42
|
</Pressable>
|
|
@@ -48,10 +55,8 @@ function ComposeRemoveGlyph() {
|
|
|
48
55
|
|
|
49
56
|
const styles = StyleSheet.create({
|
|
50
57
|
input: {
|
|
58
|
+
...m3Type.body,
|
|
51
59
|
color: m3Colors.onSurface,
|
|
52
|
-
fontFamily: fonts.regular,
|
|
53
|
-
fontSize: 20,
|
|
54
|
-
lineHeight: 28,
|
|
55
60
|
minHeight: 120,
|
|
56
61
|
textAlignVertical: 'top',
|
|
57
62
|
paddingTop: spacing.xs,
|
|
@@ -84,6 +89,7 @@ const styles = StyleSheet.create({
|
|
|
84
89
|
alignItems: 'center',
|
|
85
90
|
justifyContent: 'center',
|
|
86
91
|
},
|
|
92
|
+
previewRemovePressed: { backgroundColor: m3Colors.statePressed },
|
|
87
93
|
removeGlyph: {
|
|
88
94
|
width: 14,
|
|
89
95
|
height: 14,
|
|
@@ -2,7 +2,8 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import { Pressable, StyleSheet } from 'react-native';
|
|
3
3
|
import { m3Colors } from '../theme/proulr-theme';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/** Alvo de toque do toolbar (48pt); dono único — o toolbar só reexporta. */
|
|
6
|
+
export const COMPOSE_TOOLBAR_ICON_SIZE = 48;
|
|
6
7
|
|
|
7
8
|
export function ComposeToolbarIcon({
|
|
8
9
|
label,
|
|
@@ -23,7 +24,7 @@ export function ComposeToolbarIcon({
|
|
|
23
24
|
style={({ pressed }) => [
|
|
24
25
|
styles.icon,
|
|
25
26
|
active && styles.iconActive,
|
|
26
|
-
pressed &&
|
|
27
|
+
pressed && styles.iconPressed,
|
|
27
28
|
]}
|
|
28
29
|
>
|
|
29
30
|
{children}
|
|
@@ -33,13 +34,16 @@ export function ComposeToolbarIcon({
|
|
|
33
34
|
|
|
34
35
|
const styles = StyleSheet.create({
|
|
35
36
|
icon: {
|
|
36
|
-
width:
|
|
37
|
-
height:
|
|
38
|
-
borderRadius:
|
|
37
|
+
width: COMPOSE_TOOLBAR_ICON_SIZE,
|
|
38
|
+
height: COMPOSE_TOOLBAR_ICON_SIZE,
|
|
39
|
+
borderRadius: COMPOSE_TOOLBAR_ICON_SIZE / 2,
|
|
39
40
|
alignItems: 'center',
|
|
40
41
|
justifyContent: 'center',
|
|
41
42
|
},
|
|
42
43
|
iconActive: {
|
|
43
44
|
backgroundColor: m3Colors.primaryContainer,
|
|
44
45
|
},
|
|
46
|
+
iconPressed: {
|
|
47
|
+
opacity: 0.75,
|
|
48
|
+
},
|
|
45
49
|
});
|
|
@@ -2,9 +2,6 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
4
|
import { m3Colors, spacing } from '../theme/proulr-theme';
|
|
5
|
-
import { ComposeToolbarIcon } from './compose-toolbar-icon';
|
|
6
|
-
|
|
7
|
-
const TOOLBAR_ICON_SIZE = 48;
|
|
8
5
|
|
|
9
6
|
export function ComposeToolbar({
|
|
10
7
|
children,
|
|
@@ -28,8 +25,6 @@ export function ComposeToolbar({
|
|
|
28
25
|
);
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
export { ComposeToolbarIcon };
|
|
32
|
-
|
|
33
28
|
const styles = StyleSheet.create({
|
|
34
29
|
toolbar: {
|
|
35
30
|
position: 'absolute',
|
|
@@ -53,5 +48,3 @@ const styles = StyleSheet.create({
|
|
|
53
48
|
bottom: undefined,
|
|
54
49
|
},
|
|
55
50
|
});
|
|
56
|
-
|
|
57
|
-
export const COMPOSE_TOOLBAR_ICON_SIZE = TOOLBAR_ICON_SIZE;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { Pressable, StyleSheet, Text } from 'react-native';
|
|
3
|
-
import {
|
|
3
|
+
import { m3Colors, m3Type, spacing } from '../theme/proulr-theme';
|
|
4
4
|
import { AppIcon } from './app-icon';
|
|
5
5
|
|
|
6
6
|
export function ComposeVisibilityRow({
|
|
@@ -44,8 +44,7 @@ const styles = StyleSheet.create({
|
|
|
44
44
|
borderTopColor: m3Colors.outlineVariant,
|
|
45
45
|
},
|
|
46
46
|
label: {
|
|
47
|
+
...m3Type.label,
|
|
47
48
|
color: m3Colors.primary,
|
|
48
|
-
fontFamily: fonts.medium,
|
|
49
|
-
fontSize: 13,
|
|
50
49
|
},
|
|
51
50
|
});
|
|
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import { useCallback, useState } from 'react';
|
|
3
3
|
import { Modal, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
4
4
|
import { AppIcon, APP_ICON_SIZE } from './app-icon';
|
|
5
|
-
import {
|
|
5
|
+
import { m3Colors, m3Elevation, m3Type, spacing } from '../theme/proulr-theme';
|
|
6
6
|
|
|
7
7
|
export type FloatingCreateMenuAction = {
|
|
8
8
|
id: string;
|
|
@@ -139,9 +139,8 @@ const styles = StyleSheet.create({
|
|
|
139
139
|
paddingVertical: spacing.xs,
|
|
140
140
|
},
|
|
141
141
|
label: {
|
|
142
|
+
...m3Type.titleSmall,
|
|
142
143
|
color: m3Colors.onSurface,
|
|
143
|
-
fontFamily: fonts.semibold,
|
|
144
|
-
fontSize: 16,
|
|
145
144
|
},
|
|
146
145
|
actionFab: {
|
|
147
146
|
width: ACTION_FAB_SIZE,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import type { ViewStyle } from 'react-native';
|
|
3
|
+
import { m3Colors } from '../theme/proulr-theme';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Anel de foco de teclado (WCAG 2.4.7). `outline*` pinta fora da caixa — não
|
|
7
|
+
* desloca layout — e `borderColor` cobre as variantes sem borda no nativo.
|
|
8
|
+
*/
|
|
9
|
+
export const FOCUS_RING_STYLE: ViewStyle = {
|
|
10
|
+
borderColor: m3Colors.focus,
|
|
11
|
+
outlineColor: m3Colors.focus,
|
|
12
|
+
outlineStyle: 'solid',
|
|
13
|
+
outlineWidth: 2,
|
|
14
|
+
outlineOffset: 2,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function useFocusRing(): {
|
|
18
|
+
focused: boolean;
|
|
19
|
+
onFocus: () => void;
|
|
20
|
+
onBlur: () => void;
|
|
21
|
+
} {
|
|
22
|
+
const [focused, setFocused] = useState(false);
|
|
23
|
+
const onFocus = useCallback(() => setFocused(true), []);
|
|
24
|
+
const onBlur = useCallback(() => setFocused(false), []);
|
|
25
|
+
return { focused, onFocus, onBlur };
|
|
26
|
+
}
|
package/src/renderer/ui/index.ts
CHANGED
|
@@ -51,7 +51,8 @@ export {
|
|
|
51
51
|
type ComposePillTextFieldProps,
|
|
52
52
|
type ComposePillVariant,
|
|
53
53
|
} from './compose-pill-input';
|
|
54
|
-
export { ComposeToolbar
|
|
54
|
+
export { ComposeToolbar } from './compose-toolbar';
|
|
55
|
+
export { ComposeToolbarIcon, COMPOSE_TOOLBAR_ICON_SIZE } from './compose-toolbar-icon';
|
|
55
56
|
export { ComposeReplyAudienceSheet, type ComposeReplyAudienceOption, type ComposeReplyAudienceValue } from './compose-reply-audience-sheet';
|
|
56
57
|
export { ComposeVisibilityRow } from './compose-visibility-row';
|
|
57
58
|
export { DateField } from './DateField';
|
|
@@ -88,3 +89,4 @@ export { StoryComposeHeader } from './story-compose-header';
|
|
|
88
89
|
export { StoryCreateShortcutRow, type StoryCreateShortcutItem } from './story-create-shortcut-row';
|
|
89
90
|
export { StoryEditorChrome, type StoryEditorTool } from './story-editor-chrome';
|
|
90
91
|
export { StorySurfacePicker, type StorySurfaceOption } from './story-surface-picker';
|
|
92
|
+
export { useReduceMotion } from './use-reduce-motion';
|