@proulr/sdui-components 0.1.11 → 0.1.13

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.11",
3
+ "version": "0.1.13",
4
4
  "main": "./src/renderer/index.ts",
5
5
  "types": "./src/renderer/index.ts",
6
6
  "exports": {
@@ -1,11 +1,21 @@
1
+ import type { ReactElement } from 'react';
2
+
1
3
  export type NativeAdPlacementConfig = {
2
4
  aspectRatio?: number;
3
5
  };
4
6
 
7
+ export type NativeAdWebSlotProps = {
8
+ placement: string;
9
+ width?: number;
10
+ aspectRatio: number;
11
+ };
12
+
5
13
  export type NativeAdCatalog = {
6
14
  isEnabled: () => boolean;
7
15
  resolveAdUnitId: (placement: string, testNativeUnitId: string) => string;
8
16
  getPlacementConfig?: (placement: string) => NativeAdPlacementConfig | undefined;
17
+ /** Web-only: AdSense (ou outro). Mobile ignora. */
18
+ renderWebSlot?: (props: NativeAdWebSlotProps) => ReactElement | null;
9
19
  };
10
20
 
11
21
  let nativeAdCatalog: NativeAdCatalog | null = null;
@@ -126,6 +126,11 @@ export function SduiDiscoverProfileCardBlock({ block, data, onAction }: SduiBloc
126
126
  <Text style={styles.newBadgeText}>Novo</Text>
127
127
  </View>
128
128
  ) : null}
129
+ {person.traveling ? (
130
+ <View style={[styles.travelBadge, person.has_new_media ? styles.travelBadgeOffset : null]}>
131
+ <Text style={styles.travelBadgeText}>Viajando</Text>
132
+ </View>
133
+ ) : null}
129
134
  <View style={styles.profileInfo}>
130
135
  <Text style={styles.profileName} numberOfLines={1}>
131
136
  {person.display_name ?? 'Alguém'}
@@ -394,6 +399,19 @@ const styles = StyleSheet.create({
394
399
  borderRadius: m3Shape.cornerSmall,
395
400
  },
396
401
  newBadgeText: { color: m3Colors.onPrimary, fontSize: 11, fontWeight: '700' },
402
+ travelBadge: {
403
+ position: 'absolute',
404
+ top: spacing.sm,
405
+ right: spacing.sm,
406
+ backgroundColor: m3Colors.secondaryContainer,
407
+ paddingHorizontal: 8,
408
+ paddingVertical: 3,
409
+ borderRadius: m3Shape.cornerSmall,
410
+ },
411
+ travelBadgeOffset: {
412
+ top: spacing.sm + 24,
413
+ },
414
+ travelBadgeText: { color: m3Colors.onSecondaryContainer, fontSize: 11, fontWeight: '700' },
397
415
  profileInfo: { position: 'absolute', left: spacing.sm, bottom: spacing.sm, right: spacing.sm },
398
416
  profileName: { color: colors.white, fontSize: 13, fontWeight: '700' },
399
417
  distRow: { flexDirection: 'row', alignItems: 'center', gap: 6, marginTop: 2 },
@@ -1,6 +1,41 @@
1
+ import {
2
+ DEFAULT_NATIVE_AD_ASPECT_RATIO,
3
+ DEFAULT_NATIVE_AD_PLACEMENT,
4
+ getNativeAdCatalog,
5
+ } from '../ads/native-ad-catalog';
1
6
  import type { SduiBlockRenderProps } from './SduiPrimitiveBlocks';
2
7
 
3
- /** Web stub — native ads require a native build. */
4
- export function SduiNativeAdBlock(_props: SduiBlockRenderProps) {
5
- return null;
8
+ function resolveBlockWidth(props: Record<string, unknown>): number | undefined {
9
+ if (typeof props.width === 'number' && props.width > 0) return props.width;
10
+ return undefined;
11
+ }
12
+
13
+ function resolvePlacement(props: Record<string, unknown>): string {
14
+ return typeof props.placement === 'string' && props.placement.length > 0
15
+ ? props.placement
16
+ : DEFAULT_NATIVE_AD_PLACEMENT;
17
+ }
18
+
19
+ function resolveAspectRatio(placement: string, props: Record<string, unknown>): number {
20
+ if (typeof props.aspect_ratio === 'number' && props.aspect_ratio > 0) {
21
+ return props.aspect_ratio;
22
+ }
23
+
24
+ const catalog = getNativeAdCatalog();
25
+ const placementConfig = catalog?.getPlacementConfig?.(placement);
26
+ return placementConfig?.aspectRatio ?? DEFAULT_NATIVE_AD_ASPECT_RATIO;
27
+ }
28
+
29
+ /** Web — AdSense via `catalog.renderWebSlot` (configurado no app). */
30
+ export function SduiNativeAdBlock({ block }: SduiBlockRenderProps) {
31
+ const catalog = getNativeAdCatalog();
32
+ if (!catalog?.isEnabled() || !catalog.renderWebSlot) {
33
+ return null;
34
+ }
35
+
36
+ const placement = resolvePlacement(block.props);
37
+ const width = resolveBlockWidth(block.props);
38
+ const aspectRatio = resolveAspectRatio(placement, block.props);
39
+
40
+ return catalog.renderWebSlot({ placement, width, aspectRatio });
6
41
  }
@@ -50,6 +50,10 @@ export {
50
50
  DEFAULT_NATIVE_AD_PLACEMENT,
51
51
  DEFAULT_NATIVE_AD_ASPECT_RATIO,
52
52
  } from './ads/native-ad-catalog';
53
- export type { NativeAdCatalog, NativeAdPlacementConfig } from './ads/native-ad-catalog';
53
+ export type {
54
+ NativeAdCatalog,
55
+ NativeAdPlacementConfig,
56
+ NativeAdWebSlotProps,
57
+ } from './ads/native-ad-catalog';
54
58
  export * from './ui';
55
59
  export { brToISO, isValidDateBR, maskDateBR } from './lib/brazilian-date-input';
@@ -9,6 +9,7 @@ export type DiscoverNearbyProfileWire = {
9
9
  last_seen?: string | null;
10
10
  is_creator?: boolean | null;
11
11
  has_new_media?: boolean | null;
12
+ traveling?: boolean | null;
12
13
  };
13
14
 
14
15
  export type DiscoverGridProfileItemWire = {
@@ -38,7 +39,17 @@ export function profileAgeFromBirthDate(birthDate: string | null | undefined): n
38
39
  return age >= 0 ? age : null;
39
40
  }
40
41
 
41
- export function formatDistanceLabel(distanceM: number): string {
42
+ export function formatDistanceLabel(
43
+ distanceM: number,
44
+ unit: 'metric' | 'imperial' = 'metric',
45
+ ): string {
46
+ if (unit === 'imperial') {
47
+ const feet = distanceM * 3.28084;
48
+ if (feet < 528) return `${Math.round(feet)} ft`;
49
+ const miles = distanceM / 1609.34;
50
+ return miles < 10 ? `${miles.toFixed(1)} mi` : `${Math.round(miles)} mi`;
51
+ }
52
+
42
53
  if (distanceM < 1000) return `${Math.round(distanceM)} m`;
43
54
  const km = distanceM / 1000;
44
55
  return km < 10 ? `${km.toFixed(1)} km` : `${Math.round(km)} km`;
@@ -235,6 +235,10 @@ export type ComposePillInputProps = {
235
235
  /** Bare row without pill chrome — text field only grows inline (ex.: comentários). */
236
236
  bare?: boolean;
237
237
  rowAlignItems?: 'center' | 'flex-end';
238
+ textInputProps?: Omit<
239
+ ComposePillTextFieldProps,
240
+ 'value' | 'onChangeText' | 'variant' | 'maxLines' | 'inputRef' | 'layout' | 'editable'
241
+ >;
238
242
  };
239
243
 
240
244
  function composePillVariantColors(variant: ComposePillVariant) {
@@ -378,6 +382,7 @@ export function ComposePillInput({
378
382
  transparentWrap = false,
379
383
  bare = false,
380
384
  rowAlignItems = bare ? 'center' : 'flex-end',
385
+ textInputProps,
381
386
  }: ComposePillInputProps) {
382
387
  const layout = useComposePillLineLayout(value, { maxLines });
383
388
  const { isMultiline, pillHeight, resetLineCount } = layout;
@@ -406,6 +411,7 @@ export function ComposePillInput({
406
411
  onBlur={onBlur}
407
412
  editable={!disabled}
408
413
  style={bare ? styles.bareInput : undefined}
414
+ {...textInputProps}
409
415
  />
410
416
  );
411
417