@proulr/sdui-components 0.1.0

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.
Files changed (57) hide show
  1. package/README.md +29 -0
  2. package/package.json +60 -0
  3. package/src/bdc/compile-bdc-to-wire.ts +123 -0
  4. package/src/bdc/components/discover.tsx +55 -0
  5. package/src/bdc/components/forms.tsx +20 -0
  6. package/src/bdc/components/native-blocks.tsx +35 -0
  7. package/src/bdc/components/social.tsx +27 -0
  8. package/src/bdc/components/ui.tsx +114 -0
  9. package/src/bdc/index.ts +41 -0
  10. package/src/bdc/jsx-runtime.ts +49 -0
  11. package/src/catalog/block-types.ts +18 -0
  12. package/src/catalog/style-tokens.ts +48 -0
  13. package/src/global.d.ts +1 -0
  14. package/src/renderer/SduiScreenRenderer.tsx +28 -0
  15. package/src/renderer/ads/native-ad-catalog.ts +23 -0
  16. package/src/renderer/blocks/SduiDiscoverBlocks.tsx +470 -0
  17. package/src/renderer/blocks/SduiLayoutBlocks.tsx +177 -0
  18. package/src/renderer/blocks/SduiNativeAdBlock.tsx +201 -0
  19. package/src/renderer/blocks/SduiNativeAdBlock.web.tsx +6 -0
  20. package/src/renderer/blocks/SduiNativeDomainBlocks.tsx +141 -0
  21. package/src/renderer/blocks/SduiPrimitiveBlocks.tsx +158 -0
  22. package/src/renderer/blocks/SduiSocialBlocks.tsx +64 -0
  23. package/src/renderer/index.ts +45 -0
  24. package/src/renderer/lib/brazilian-date-input.ts +22 -0
  25. package/src/renderer/lib/discover-profile-formatters.ts +75 -0
  26. package/src/renderer/lib/resolve-data-ref.ts +21 -0
  27. package/src/renderer/sdui-chrome-scroll-context.tsx +40 -0
  28. package/src/renderer/sdui-component-registry.tsx +129 -0
  29. package/src/renderer/sdui-hybrid-renderer-context.tsx +91 -0
  30. package/src/renderer/sdui-layout-debug.tsx +107 -0
  31. package/src/renderer/theme/proulr-theme.ts +164 -0
  32. package/src/renderer/theme/resolve-style-props.ts +85 -0
  33. package/src/renderer/ui/AppButton.tsx +71 -0
  34. package/src/renderer/ui/AppLoadingSpinner.tsx +38 -0
  35. package/src/renderer/ui/AuthStepShell.tsx +128 -0
  36. package/src/renderer/ui/Card.tsx +13 -0
  37. package/src/renderer/ui/DateField.tsx +36 -0
  38. package/src/renderer/ui/InfoBanner.tsx +51 -0
  39. package/src/renderer/ui/TextField.tsx +81 -0
  40. package/src/renderer/ui/compose-audience-pill.tsx +47 -0
  41. package/src/renderer/ui/compose-glyphs.tsx +400 -0
  42. package/src/renderer/ui/compose-media-carousel.tsx +116 -0
  43. package/src/renderer/ui/compose-media-shortcut.tsx +80 -0
  44. package/src/renderer/ui/compose-recent-media-grid.tsx +268 -0
  45. package/src/renderer/ui/compose-reply-audience-sheet.tsx +155 -0
  46. package/src/renderer/ui/compose-screen-header.tsx +115 -0
  47. package/src/renderer/ui/compose-text-area.tsx +94 -0
  48. package/src/renderer/ui/compose-toolbar-icon.tsx +45 -0
  49. package/src/renderer/ui/compose-toolbar.tsx +57 -0
  50. package/src/renderer/ui/compose-visibility-row.tsx +51 -0
  51. package/src/renderer/ui/floating-create-menu.tsx +165 -0
  52. package/src/renderer/ui/index.ts +44 -0
  53. package/src/renderer/ui/story-compose-header.tsx +72 -0
  54. package/src/renderer/ui/story-create-shortcut-row.tsx +89 -0
  55. package/src/renderer/ui/story-editor-chrome.tsx +231 -0
  56. package/src/renderer/ui/story-glyphs.tsx +144 -0
  57. package/src/renderer/ui/story-surface-picker.tsx +124 -0
@@ -0,0 +1,400 @@
1
+ import { StyleSheet, Text, View } from 'react-native';
2
+ import { m3Colors } from '../theme/proulr-theme';
3
+
4
+ type GlyphProps = {
5
+ color?: string;
6
+ size?: number;
7
+ };
8
+
9
+ export function ComposeCloseGlyph({ color = m3Colors.onSurface, size = 18 }: GlyphProps) {
10
+ const stroke = Math.max(1.5, size * 0.1);
11
+ return (
12
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
13
+ <View style={[styles.diag, { width: size, backgroundColor: color, transform: [{ rotate: '45deg' }] }]} />
14
+ <View style={[styles.diag, { width: size, backgroundColor: color, transform: [{ rotate: '-45deg' }] }]} />
15
+ </View>
16
+ );
17
+ }
18
+
19
+ export function ComposeCameraGlyph({ color = m3Colors.primary, size = 24 }: GlyphProps) {
20
+ const bodyW = size * 0.92;
21
+ const bodyH = size * 0.62;
22
+ const lens = size * 0.28;
23
+ return (
24
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
25
+ <View
26
+ style={{
27
+ width: bodyW,
28
+ height: bodyH,
29
+ borderRadius: size * 0.12,
30
+ borderWidth: 1.8,
31
+ borderColor: color,
32
+ alignItems: 'center',
33
+ justifyContent: 'center',
34
+ }}
35
+ >
36
+ <View
37
+ style={{
38
+ width: lens,
39
+ height: lens,
40
+ borderRadius: lens / 2,
41
+ borderWidth: 1.8,
42
+ borderColor: color,
43
+ }}
44
+ />
45
+ </View>
46
+ <View
47
+ style={{
48
+ position: 'absolute',
49
+ top: size * 0.14,
50
+ right: size * 0.18,
51
+ width: size * 0.16,
52
+ height: size * 0.1,
53
+ borderRadius: 2,
54
+ backgroundColor: color,
55
+ }}
56
+ />
57
+ </View>
58
+ );
59
+ }
60
+
61
+ export function ComposeGalleryGlyph({ color = m3Colors.primary, size = 24 }: GlyphProps) {
62
+ const frame = size * 0.88;
63
+ return (
64
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
65
+ <View
66
+ style={{
67
+ width: frame,
68
+ height: frame,
69
+ borderRadius: size * 0.14,
70
+ borderWidth: 1.8,
71
+ borderColor: color,
72
+ overflow: 'hidden',
73
+ }}
74
+ >
75
+ <View
76
+ style={{
77
+ position: 'absolute',
78
+ bottom: frame * 0.12,
79
+ left: frame * 0.1,
80
+ width: frame * 0.42,
81
+ height: frame * 0.22,
82
+ borderRadius: 2,
83
+ backgroundColor: color,
84
+ }}
85
+ />
86
+ <View
87
+ style={{
88
+ position: 'absolute',
89
+ top: frame * 0.16,
90
+ right: frame * 0.14,
91
+ width: frame * 0.18,
92
+ height: frame * 0.18,
93
+ borderRadius: frame * 0.09,
94
+ backgroundColor: color,
95
+ }}
96
+ />
97
+ </View>
98
+ </View>
99
+ );
100
+ }
101
+
102
+ export function ComposeVideoGlyph({ color = m3Colors.primary, size = 24 }: GlyphProps) {
103
+ const frame = size * 0.9;
104
+ return (
105
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
106
+ <View
107
+ style={{
108
+ width: frame,
109
+ height: frame * 0.72,
110
+ borderRadius: size * 0.1,
111
+ borderWidth: 1.8,
112
+ borderColor: color,
113
+ alignItems: 'center',
114
+ justifyContent: 'center',
115
+ }}
116
+ >
117
+ <View
118
+ style={{
119
+ width: 0,
120
+ height: 0,
121
+ borderTopWidth: size * 0.14,
122
+ borderBottomWidth: size * 0.14,
123
+ borderLeftWidth: size * 0.22,
124
+ borderTopColor: 'transparent',
125
+ borderBottomColor: 'transparent',
126
+ borderLeftColor: color,
127
+ marginLeft: size * 0.04,
128
+ }}
129
+ />
130
+ </View>
131
+ </View>
132
+ );
133
+ }
134
+
135
+ export function ComposeGlobeGlyph({ color = m3Colors.primary, size = 16 }: GlyphProps) {
136
+ const diameter = size;
137
+ return (
138
+ <View
139
+ style={{
140
+ width: diameter,
141
+ height: diameter,
142
+ borderRadius: diameter / 2,
143
+ borderWidth: 1.4,
144
+ borderColor: color,
145
+ alignItems: 'center',
146
+ justifyContent: 'center',
147
+ }}
148
+ >
149
+ <View style={{ width: diameter * 0.55, height: 1.2, backgroundColor: color }} />
150
+ <View
151
+ style={{
152
+ position: 'absolute',
153
+ width: diameter * 0.42,
154
+ height: diameter * 0.72,
155
+ borderRadius: diameter * 0.21,
156
+ borderWidth: 1.2,
157
+ borderColor: color,
158
+ }}
159
+ />
160
+ </View>
161
+ );
162
+ }
163
+
164
+ export function ComposeFollowersGlyph({ color = m3Colors.onPrimary, size = 20 }: GlyphProps) {
165
+ const head = size * 0.22;
166
+ return (
167
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
168
+ <View
169
+ style={{
170
+ width: head,
171
+ height: head,
172
+ borderRadius: head / 2,
173
+ backgroundColor: color,
174
+ marginBottom: 1,
175
+ }}
176
+ />
177
+ <View
178
+ style={{
179
+ width: size * 0.52,
180
+ height: size * 0.24,
181
+ borderTopLeftRadius: size * 0.26,
182
+ borderTopRightRadius: size * 0.26,
183
+ backgroundColor: color,
184
+ }}
185
+ />
186
+ <View
187
+ style={{
188
+ position: 'absolute',
189
+ right: size * 0.08,
190
+ bottom: size * 0.18,
191
+ width: size * 0.22,
192
+ height: size * 0.22,
193
+ borderRadius: size * 0.11,
194
+ borderWidth: 1.4,
195
+ borderColor: color,
196
+ alignItems: 'center',
197
+ justifyContent: 'center',
198
+ }}
199
+ >
200
+ <View
201
+ style={{
202
+ width: 0,
203
+ height: 0,
204
+ borderLeftWidth: size * 0.05,
205
+ borderRightWidth: size * 0.05,
206
+ borderBottomWidth: size * 0.08,
207
+ borderLeftColor: 'transparent',
208
+ borderRightColor: 'transparent',
209
+ borderBottomColor: color,
210
+ transform: [{ rotate: '-90deg' }],
211
+ marginLeft: size * 0.01,
212
+ }}
213
+ />
214
+ </View>
215
+ </View>
216
+ );
217
+ }
218
+
219
+ export function ComposeFollowingGlyph({ color = m3Colors.onPrimary, size = 20 }: GlyphProps) {
220
+ const head = size * 0.22;
221
+ return (
222
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
223
+ <View
224
+ style={{
225
+ width: head,
226
+ height: head,
227
+ borderRadius: head / 2,
228
+ backgroundColor: color,
229
+ marginBottom: 1,
230
+ }}
231
+ />
232
+ <View
233
+ style={{
234
+ width: size * 0.52,
235
+ height: size * 0.24,
236
+ borderTopLeftRadius: size * 0.26,
237
+ borderTopRightRadius: size * 0.26,
238
+ backgroundColor: color,
239
+ }}
240
+ />
241
+ <View
242
+ style={{
243
+ position: 'absolute',
244
+ right: size * 0.1,
245
+ bottom: size * 0.2,
246
+ width: size * 0.18,
247
+ height: size * 0.1,
248
+ borderBottomWidth: 1.6,
249
+ borderRightWidth: 1.6,
250
+ borderColor: color,
251
+ transform: [{ rotate: '45deg' }],
252
+ }}
253
+ />
254
+ </View>
255
+ );
256
+ }
257
+
258
+ export function ComposeMentionGlyph({ color = m3Colors.onPrimary, size = 20 }: GlyphProps) {
259
+ return (
260
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
261
+ <Text style={{ color, fontSize: size * 0.72, fontWeight: '700', lineHeight: size * 0.8 }}>@</Text>
262
+ </View>
263
+ );
264
+ }
265
+
266
+ export function ComposeTrashGlyph({ color = m3Colors.primary, size = 22 }: GlyphProps) {
267
+ const binW = size * 0.62;
268
+ const binH = size * 0.58;
269
+ return (
270
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
271
+ <View style={{ width: binW * 1.1, height: 1.6, backgroundColor: color, marginBottom: 2 }} />
272
+ <View
273
+ style={{
274
+ width: binW,
275
+ height: binH,
276
+ borderBottomLeftRadius: 3,
277
+ borderBottomRightRadius: 3,
278
+ borderWidth: 1.6,
279
+ borderTopWidth: 0,
280
+ borderColor: color,
281
+ }}
282
+ />
283
+ <View
284
+ style={{
285
+ position: 'absolute',
286
+ top: size * 0.2,
287
+ width: binW * 0.42,
288
+ height: 1.6,
289
+ backgroundColor: color,
290
+ }}
291
+ />
292
+ </View>
293
+ );
294
+ }
295
+
296
+ export function ComposePostGlyph({ color = m3Colors.onPrimary, size = 22 }: GlyphProps) {
297
+ return (
298
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
299
+ <View
300
+ style={{
301
+ width: size * 0.14,
302
+ height: size * 0.72,
303
+ borderRadius: size * 0.07,
304
+ backgroundColor: color,
305
+ transform: [{ rotate: '-18deg' }],
306
+ }}
307
+ />
308
+ <View
309
+ style={{
310
+ position: 'absolute',
311
+ top: size * 0.08,
312
+ left: size * 0.34,
313
+ width: size * 0.34,
314
+ height: size * 0.12,
315
+ borderTopWidth: 1.8,
316
+ borderRightWidth: 1.8,
317
+ borderColor: color,
318
+ borderTopRightRadius: size * 0.08,
319
+ transform: [{ rotate: '12deg' }],
320
+ }}
321
+ />
322
+ </View>
323
+ );
324
+ }
325
+
326
+ export function ComposeStoryGlyph({ color = m3Colors.primary, size = 22 }: GlyphProps) {
327
+ const ring = size * 0.82;
328
+ return (
329
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
330
+ <View
331
+ style={{
332
+ width: ring,
333
+ height: ring,
334
+ borderRadius: ring / 2,
335
+ borderWidth: 2,
336
+ borderColor: color,
337
+ alignItems: 'center',
338
+ justifyContent: 'center',
339
+ }}
340
+ >
341
+ <View
342
+ style={{
343
+ width: ring * 0.42,
344
+ height: ring * 0.42,
345
+ borderRadius: ring * 0.21,
346
+ backgroundColor: color,
347
+ }}
348
+ />
349
+ </View>
350
+ </View>
351
+ );
352
+ }
353
+
354
+ export function ComposeLiveGlyph({ color = m3Colors.primary, size = 22 }: GlyphProps) {
355
+ const bodyW = size * 0.88;
356
+ const bodyH = size * 0.58;
357
+ return (
358
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
359
+ <View
360
+ style={{
361
+ width: bodyW,
362
+ height: bodyH,
363
+ borderRadius: size * 0.1,
364
+ borderWidth: 1.8,
365
+ borderColor: color,
366
+ alignItems: 'center',
367
+ justifyContent: 'center',
368
+ }}
369
+ >
370
+ <View
371
+ style={{
372
+ width: bodyW * 0.24,
373
+ height: bodyH * 0.48,
374
+ borderRadius: 2,
375
+ backgroundColor: color,
376
+ }}
377
+ />
378
+ </View>
379
+ <View
380
+ style={{
381
+ position: 'absolute',
382
+ right: size * 0.04,
383
+ top: size * 0.2,
384
+ width: size * 0.14,
385
+ height: size * 0.1,
386
+ borderRadius: 2,
387
+ backgroundColor: color,
388
+ }}
389
+ />
390
+ </View>
391
+ );
392
+ }
393
+
394
+ const styles = StyleSheet.create({
395
+ diag: {
396
+ position: 'absolute',
397
+ height: 1.8,
398
+ borderRadius: 1,
399
+ },
400
+ });
@@ -0,0 +1,116 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Image, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
3
+ import { fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
4
+
5
+ export type ComposeMediaCarouselItem = {
6
+ id: string;
7
+ uri: string;
8
+ mediaType: 'image' | 'video';
9
+ };
10
+
11
+ const TILE_SIZE = 168;
12
+
13
+ export function ComposeMediaCarousel({
14
+ items,
15
+ limit,
16
+ onRemove,
17
+ renderVideo,
18
+ }: {
19
+ items: ComposeMediaCarouselItem[];
20
+ limit: number;
21
+ onRemove: (id: string) => void;
22
+ renderVideo?: (uri: string) => ReactNode;
23
+ }) {
24
+ if (!items.length) return null;
25
+
26
+ return (
27
+ <View style={styles.wrap}>
28
+ <ScrollView
29
+ horizontal
30
+ showsHorizontalScrollIndicator={false}
31
+ contentContainerStyle={styles.scrollContent}
32
+ >
33
+ {items.map((item) => (
34
+ <View key={item.id} style={styles.tile}>
35
+ {item.mediaType === 'video' ? (
36
+ renderVideo ? (
37
+ renderVideo(item.uri)
38
+ ) : (
39
+ <View style={styles.videoFallback}>
40
+ <Text style={styles.videoFallbackText}>Vídeo</Text>
41
+ </View>
42
+ )
43
+ ) : (
44
+ <Image source={{ uri: item.uri }} style={styles.media} resizeMode="cover" />
45
+ )}
46
+ <Pressable
47
+ onPress={() => onRemove(item.id)}
48
+ accessibilityRole="button"
49
+ accessibilityLabel="Remover mídia"
50
+ style={styles.removeBtn}
51
+ >
52
+ <Text style={styles.removeIcon}>{'\u2715'}</Text>
53
+ </Pressable>
54
+ </View>
55
+ ))}
56
+ </ScrollView>
57
+ <Text style={styles.counter}>
58
+ {items.length}/{limit}
59
+ </Text>
60
+ </View>
61
+ );
62
+ }
63
+
64
+ const styles = StyleSheet.create({
65
+ wrap: {
66
+ marginTop: spacing.md,
67
+ marginLeft: 48,
68
+ },
69
+ scrollContent: {
70
+ gap: spacing.sm,
71
+ paddingRight: spacing.md,
72
+ },
73
+ tile: {
74
+ width: TILE_SIZE,
75
+ height: TILE_SIZE,
76
+ borderRadius: m3Shape.cornerLarge,
77
+ overflow: 'hidden',
78
+ backgroundColor: m3Colors.surfaceContainerHigh,
79
+ },
80
+ media: {
81
+ width: '100%',
82
+ height: '100%',
83
+ },
84
+ videoFallback: {
85
+ flex: 1,
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ },
89
+ videoFallbackText: {
90
+ color: m3Colors.onSurfaceVariant,
91
+ fontFamily: fonts.medium,
92
+ fontSize: 13,
93
+ },
94
+ removeBtn: {
95
+ position: 'absolute',
96
+ top: spacing.xs,
97
+ right: spacing.xs,
98
+ width: 30,
99
+ height: 30,
100
+ borderRadius: 15,
101
+ backgroundColor: m3Colors.scrim,
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ },
105
+ removeIcon: {
106
+ color: m3Colors.onPrimary,
107
+ fontSize: 14,
108
+ fontFamily: fonts.semibold,
109
+ },
110
+ counter: {
111
+ marginTop: spacing.xs,
112
+ color: m3Colors.onSurfaceVariant,
113
+ fontFamily: fonts.medium,
114
+ fontSize: 12,
115
+ },
116
+ });
@@ -0,0 +1,80 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
3
+ import { fonts, m3Colors, m3Shape, spacing } from '../theme/proulr-theme';
4
+
5
+ const MEDIA_TILE_SIZE = 88;
6
+
7
+ export function ComposeMediaShortcut({
8
+ label,
9
+ hint,
10
+ children,
11
+ onPress,
12
+ }: {
13
+ label: string;
14
+ hint?: string;
15
+ children: ReactNode;
16
+ onPress: () => void;
17
+ }) {
18
+ return (
19
+ <View style={styles.wrap}>
20
+ {hint ? (
21
+ <View style={styles.hintBubble}>
22
+ <Text style={styles.hintText}>{hint}</Text>
23
+ <View style={styles.hintTail} />
24
+ </View>
25
+ ) : null}
26
+ <Pressable
27
+ onPress={onPress}
28
+ accessibilityRole="button"
29
+ accessibilityLabel={label}
30
+ style={({ pressed }) => [styles.tile, pressed && { opacity: 0.85 }]}
31
+ >
32
+ {children}
33
+ </Pressable>
34
+ </View>
35
+ );
36
+ }
37
+
38
+ const styles = StyleSheet.create({
39
+ wrap: {
40
+ alignItems: 'center',
41
+ },
42
+ hintBubble: {
43
+ marginBottom: spacing.xs,
44
+ paddingHorizontal: spacing.sm,
45
+ paddingVertical: spacing.xs,
46
+ borderRadius: m3Shape.cornerFull,
47
+ backgroundColor: m3Colors.primary,
48
+ position: 'relative',
49
+ },
50
+ hintText: {
51
+ color: m3Colors.onPrimary,
52
+ fontFamily: fonts.medium,
53
+ fontSize: 11,
54
+ },
55
+ hintTail: {
56
+ position: 'absolute',
57
+ bottom: -5,
58
+ alignSelf: 'center',
59
+ width: 0,
60
+ height: 0,
61
+ borderLeftWidth: 6,
62
+ borderRightWidth: 6,
63
+ borderTopWidth: 6,
64
+ borderLeftColor: 'transparent',
65
+ borderRightColor: 'transparent',
66
+ borderTopColor: m3Colors.primary,
67
+ },
68
+ tile: {
69
+ width: MEDIA_TILE_SIZE,
70
+ height: MEDIA_TILE_SIZE,
71
+ borderRadius: m3Shape.cornerLarge,
72
+ borderWidth: 1,
73
+ borderColor: m3Colors.outlineVariant,
74
+ backgroundColor: m3Colors.surfaceContainer,
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ },
78
+ });
79
+
80
+ export const COMPOSE_MEDIA_TILE_SIZE = MEDIA_TILE_SIZE;