@proulr/sdui-components 0.1.59 → 0.1.61

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@proulr/sdui-components",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "type": "module",
5
5
  "main": "./src/renderer/index.ts",
6
6
  "types": "./src/renderer/index.ts",
@@ -50,6 +50,7 @@ export const DiscoverProfileCard: BdcComponent = (props) =>
50
50
  type DiscoverMapProps = LayoutProps & {
51
51
  centerRef?: string;
52
52
  labelRef?: string;
53
+ pinsRef?: string;
53
54
  height?: number;
54
55
  };
55
56
 
@@ -41,3 +41,5 @@ export const ChatGroupAddMembers: BdcComponent = (props) =>
41
41
  nativeBlock('chat_group_add_members', props);
42
42
  export const ChatGroupInvite: BdcComponent = (props) => nativeBlock('chat_group_invite', props);
43
43
  export const DiscoverBrowseLocation: BdcComponent = (props) => nativeBlock('discover_browse_location', props);
44
+ export const RightNowFeed: BdcComponent = (props) => nativeBlock('right_now_feed', props);
45
+ export const RightNowCompose: BdcComponent = (props) => nativeBlock('right_now_compose', props);
package/src/bdc/index.ts CHANGED
@@ -54,6 +54,8 @@ export {
54
54
  ChatGroupAddMembers,
55
55
  ChatGroupInvite,
56
56
  DiscoverBrowseLocation,
57
+ RightNowCompose,
58
+ RightNowFeed,
57
59
  ThoughtFeed,
58
60
  } from './components/native-blocks';
59
61
  export type { BdcElement } from './jsx-runtime';
@@ -26,6 +26,7 @@ export const APP_ICON_NAMES = [
26
26
  'tab-random',
27
27
  'boost',
28
28
  'fire',
29
+ 'right-now',
29
30
  'settings',
30
31
  'edit',
31
32
  'crop',
@@ -134,6 +134,16 @@ export function SduiDiscoverProfileCardBlock({ block, data, onAction }: SduiBloc
134
134
  <Text style={styles.travelBadgeText}>Viajando</Text>
135
135
  </View>
136
136
  ) : null}
137
+ {person.is_right_now ? (
138
+ <View
139
+ style={[
140
+ styles.rightNowBadge,
141
+ person.has_new_media || person.traveling ? styles.rightNowBadgeOffset : null,
142
+ ]}
143
+ >
144
+ <AppIcon name="right-now" size={12} color={m3Colors.primary} />
145
+ </View>
146
+ ) : null}
137
147
  <View style={styles.profileInfo}>
138
148
  <Text style={styles.profileName} numberOfLines={1}>
139
149
  {person.display_name ?? 'Alguém'}
@@ -329,10 +339,34 @@ export function SduiTextFieldBlock({ block, data, onAction }: SduiBlockRenderPro
329
339
  );
330
340
  }
331
341
 
332
- export function SduiDiscoverMapBlock({ block, data }: SduiBlockRenderProps) {
342
+ function readMapPins(data: Record<string, unknown>, ref: string) {
343
+ const raw = resolveDataRef(data, ref);
344
+ if (!Array.isArray(raw)) return [];
345
+ return raw
346
+ .map((item) => {
347
+ if (!item || typeof item !== 'object') return null;
348
+ const row = item as Record<string, unknown>;
349
+ const id = typeof row.id === 'string' ? row.id : null;
350
+ const lat = typeof row.lat === 'number' ? row.lat : Number(row.lat);
351
+ const lng = typeof row.lng === 'number' ? row.lng : Number(row.lng);
352
+ if (!id || !Number.isFinite(lat) || !Number.isFinite(lng)) return null;
353
+ return {
354
+ id,
355
+ lat,
356
+ lng,
357
+ display_name: typeof row.display_name === 'string' ? row.display_name : null,
358
+ primary_photo_path:
359
+ typeof row.primary_photo_path === 'string' ? row.primary_photo_path : null,
360
+ };
361
+ })
362
+ .filter((pin): pin is NonNullable<typeof pin> => Boolean(pin));
363
+ }
364
+
365
+ export function SduiDiscoverMapBlock({ block, data, onAction }: SduiBlockRenderProps) {
333
366
  const hybrid = useSduiHybridRenderer();
334
367
  const centerRefProp = readStringProp(block, 'center_ref') ?? 'browse_center';
335
368
  const labelRef = readStringProp(block, 'label_ref') ?? 'map_label';
369
+ const pinsRef = readStringProp(block, 'pins_ref');
336
370
  const height = readNumberProp(block, 'height', 340);
337
371
  const regionLabel = resolveDataRefString(data, labelRef) || 'Região selecionada';
338
372
  const centerValue = resolveDataRef(data, centerRefProp);
@@ -345,6 +379,7 @@ export function SduiDiscoverMapBlock({ block, data }: SduiBlockRenderProps) {
345
379
  : { lat: -23.5505, lng: -46.6333 };
346
380
  const centerMoveToken =
347
381
  typeof data.map_center_move_token === 'number' ? data.map_center_move_token : 0;
382
+ const pins = pinsRef ? readMapPins(data, pinsRef) : [];
348
383
 
349
384
  if (hybrid.renderDiscoverMap) {
350
385
  return (
@@ -353,8 +388,11 @@ export function SduiDiscoverMapBlock({ block, data }: SduiBlockRenderProps) {
353
388
  center,
354
389
  label: regionLabel,
355
390
  height,
391
+ pins,
392
+ interactive: pins.length === 0,
356
393
  centerMoveToken,
357
394
  onCenterChange: hybrid.onDiscoverMapCenterChange ?? (() => {}),
395
+ onPinPress: (profileId) => onAction('open_profile', { profile_id: profileId }),
358
396
  })}
359
397
  </>
360
398
  );
@@ -408,6 +446,22 @@ const styles = StyleSheet.create({
408
446
  top: spacing.sm + 24,
409
447
  },
410
448
  travelBadgeText: { color: m3Colors.onSecondaryContainer, fontSize: 11, fontWeight: '700' },
449
+ rightNowBadge: {
450
+ position: 'absolute',
451
+ top: spacing.sm,
452
+ left: spacing.sm,
453
+ width: 26,
454
+ height: 26,
455
+ borderRadius: 13,
456
+ alignItems: 'center',
457
+ justifyContent: 'center',
458
+ backgroundColor: m3Colors.surfaceContainerHighest,
459
+ borderWidth: 1,
460
+ borderColor: m3Colors.primary,
461
+ },
462
+ rightNowBadgeOffset: {
463
+ left: spacing.sm + 52,
464
+ },
411
465
  profileInfo: { position: 'absolute', left: spacing.sm, bottom: spacing.sm, right: spacing.sm },
412
466
  profileName: { color: colors.white, fontSize: 13, fontWeight: '700' },
413
467
  distRow: { flexDirection: 'row', alignItems: 'center', gap: 6, marginTop: 2 },
@@ -39,7 +39,9 @@ function NativeBlockShell({
39
39
  | 'renderChatGroupInfo'
40
40
  | 'renderChatGroupAddMembers'
41
41
  | 'renderChatGroupInvite'
42
- | 'renderDiscoverBrowseLocation';
42
+ | 'renderDiscoverBrowseLocation'
43
+ | 'renderRightNowFeed'
44
+ | 'renderRightNowCompose';
43
45
  fallbackLabel: string;
44
46
  }) {
45
47
  const hybrid = useSduiHybridRenderer();
@@ -172,3 +174,13 @@ export function SduiDiscoverBrowseLocationBlock(props: SduiBlockRenderProps) {
172
174
  />
173
175
  );
174
176
  }
177
+
178
+ export function SduiRightNowFeedBlock(props: SduiBlockRenderProps) {
179
+ return <NativeBlockShell {...props} rendererKey="renderRightNowFeed" fallbackLabel="Right Now feed" />;
180
+ }
181
+
182
+ export function SduiRightNowComposeBlock(props: SduiBlockRenderProps) {
183
+ return (
184
+ <NativeBlockShell {...props} rendererKey="renderRightNowCompose" fallbackLabel="Right Now compose" />
185
+ );
186
+ }
@@ -10,6 +10,8 @@ export type DiscoverNearbyProfileWire = {
10
10
  is_creator?: boolean | null;
11
11
  has_new_media?: boolean | null;
12
12
  traveling?: boolean | null;
13
+ right_now_until?: string | null;
14
+ is_right_now?: boolean | null;
13
15
  };
14
16
 
15
17
  export type DiscoverGridProfileItemWire = {
@@ -66,6 +66,8 @@ import {
66
66
  SduiChatGroupAddMembersBlock,
67
67
  SduiChatGroupInviteBlock,
68
68
  SduiDiscoverBrowseLocationBlock,
69
+ SduiRightNowComposeBlock,
70
+ SduiRightNowFeedBlock,
69
71
  SduiThoughtFeedBlock,
70
72
  } from './blocks/SduiNativeDomainBlocks';
71
73
 
@@ -128,6 +130,8 @@ const REGISTRY: Record<string, SduiBlockComponent> = {
128
130
  chat_group_add_members: SduiChatGroupAddMembersBlock,
129
131
  chat_group_invite: SduiChatGroupInviteBlock,
130
132
  discover_browse_location: SduiDiscoverBrowseLocationBlock,
133
+ right_now_feed: SduiRightNowFeedBlock,
134
+ right_now_compose: SduiRightNowComposeBlock,
131
135
  };
132
136
 
133
137
  export function resolveSduiBlockComponent(type: string): SduiBlockComponent {
@@ -12,12 +12,23 @@ export type SduiHybridProfileCompletionBannerProps = {
12
12
  onComplete: () => void;
13
13
  };
14
14
 
15
+ export type SduiHybridDiscoverMapPin = {
16
+ id: string;
17
+ lat: number;
18
+ lng: number;
19
+ display_name?: string | null;
20
+ primary_photo_path?: string | null;
21
+ };
22
+
15
23
  export type SduiHybridDiscoverMapProps = {
16
24
  center: { lat: number; lng: number };
17
25
  label: string;
18
26
  height: number;
27
+ pins?: SduiHybridDiscoverMapPin[];
19
28
  onCenterChange: (coords: { lat: number; lng: number }) => void;
29
+ onPinPress?: (profileId: string) => void;
20
30
  centerMoveToken: number;
31
+ interactive?: boolean;
21
32
  };
22
33
 
23
34
  export type SduiHybridDiscoverProfileCardProps = {
@@ -76,6 +87,8 @@ export type SduiHybridRenderers = {
76
87
  renderChatGroupAddMembers?: (props: SduiNativeBlockRenderProps) => ReactNode;
77
88
  renderChatGroupInvite?: (props: SduiNativeBlockRenderProps) => ReactNode;
78
89
  renderDiscoverBrowseLocation?: (props: SduiNativeBlockRenderProps) => ReactNode;
90
+ renderRightNowFeed?: (props: SduiNativeBlockRenderProps) => ReactNode;
91
+ renderRightNowCompose?: (props: SduiNativeBlockRenderProps) => ReactNode;
79
92
  renderSegmentedTabs?: (props: {
80
93
  block: SduiBlock;
81
94
  data: Record<string, unknown>;
@@ -593,6 +593,64 @@ export function AppChromeFireGlyph({ color = m3Colors.primary, size = 24 }: AppC
593
593
  );
594
594
  }
595
595
 
596
+ /** Right Now — triple droplet / flame cluster (activation + badge). */
597
+ export function AppChromeRightNowGlyph({ color = m3Colors.primary, size = 24 }: AppChromeGlyphProps) {
598
+ const stroke = strokeFor(size);
599
+ const droplet = (opts: {
600
+ width: number;
601
+ height: number;
602
+ left: number;
603
+ top: number;
604
+ rotate: string;
605
+ filled?: boolean;
606
+ }) => (
607
+ <View
608
+ key={`${opts.left}-${opts.top}`}
609
+ style={{
610
+ position: 'absolute',
611
+ left: opts.left,
612
+ top: opts.top,
613
+ width: opts.width,
614
+ height: opts.height,
615
+ borderTopLeftRadius: opts.width * 0.55,
616
+ borderTopRightRadius: opts.width * 0.55,
617
+ borderBottomLeftRadius: opts.width * 0.5,
618
+ borderBottomRightRadius: opts.width * 0.15,
619
+ borderWidth: stroke,
620
+ borderColor: color,
621
+ backgroundColor: opts.filled ? color : 'transparent',
622
+ transform: [{ rotate: opts.rotate }],
623
+ }}
624
+ />
625
+ );
626
+ return (
627
+ <View style={{ width: size, height: size, alignItems: 'center', justifyContent: 'center' }}>
628
+ {droplet({
629
+ width: size * 0.28,
630
+ height: size * 0.42,
631
+ left: size * 0.36,
632
+ top: size * 0.08,
633
+ rotate: '0deg',
634
+ filled: true,
635
+ })}
636
+ {droplet({
637
+ width: size * 0.26,
638
+ height: size * 0.38,
639
+ left: size * 0.12,
640
+ top: size * 0.32,
641
+ rotate: '-48deg',
642
+ })}
643
+ {droplet({
644
+ width: size * 0.26,
645
+ height: size * 0.38,
646
+ left: size * 0.58,
647
+ top: size * 0.32,
648
+ rotate: '48deg',
649
+ })}
650
+ </View>
651
+ );
652
+ }
653
+
596
654
  /** Editar perfil — só lápis (corpo + ponta). */
597
655
  export function AppChromeEditGlyph({ color = m3Colors.primary, size = 24 }: AppChromeGlyphProps) {
598
656
  const bodyW = Math.max(3, size * 0.17);
@@ -22,6 +22,7 @@ import {
22
22
  AppChromeCropGlyph,
23
23
  AppChromeEventsGlyph,
24
24
  AppChromeFireGlyph,
25
+ AppChromeRightNowGlyph,
25
26
  AppChromeFlashGlyph,
26
27
  AppChromeFlipCameraGlyph,
27
28
  AppChromeHeartFilledGlyph,
@@ -115,6 +116,7 @@ const ICON_REGISTRY: Record<AppIconName, ComponentType<AppChromeGlyphProps>> = {
115
116
  'tab-random': AppChromeTabRandomGlyph,
116
117
  boost: AppChromeBoostGlyph,
117
118
  fire: AppChromeFireGlyph,
119
+ 'right-now': AppChromeRightNowGlyph,
118
120
  settings: AppChromeSettingsGlyph,
119
121
  edit: AppChromeEditGlyph,
120
122
  crop: AppChromeCropGlyph,
@@ -48,6 +48,7 @@ export {
48
48
  AppChromeCropGlyph,
49
49
  AppChromeEventsGlyph,
50
50
  AppChromeFireGlyph,
51
+ AppChromeRightNowGlyph,
51
52
  AppChromeFlashGlyph,
52
53
  AppChromeFlipCameraGlyph,
53
54
  AppChromeHeartFilledGlyph,