@proulr/sdui-components 0.1.51 → 0.1.55

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.51",
3
+ "version": "0.1.55",
4
4
  "type": "module",
5
5
  "main": "./src/renderer/index.ts",
6
6
  "types": "./src/renderer/index.ts",
@@ -6,12 +6,13 @@ import {
6
6
  DiscoverMap,
7
7
  DiscoverProfileCard,
8
8
  } from './discover';
9
- import { TextField } from './forms';
9
+ import { SelectRow, TextField } from './forms';
10
10
  import {
11
11
  ChatInboxCapabilities,
12
12
  EmptyState,
13
13
  FlashInboxCapabilities,
14
14
  FloatingCreateMenu,
15
+ InboxHubChrome,
15
16
  InboxPersonRow,
16
17
  InboxSearchField,
17
18
  ProfileVisitsCapabilities,
@@ -28,6 +29,7 @@ import {
28
29
  DiscoverBrowseLocation,
29
30
  NewPostComposer,
30
31
  NewStoryComposer,
32
+ StoryViewersPanel,
31
33
  PostComments,
32
34
  ProfileOnboarding,
33
35
  ProfileReport,
@@ -85,6 +87,7 @@ describe('bdc discover/forms/social/inbox/native', () => {
85
87
  expect(DiscoverProfileCard({ profileRef: 'p' }).type).toBe('discover_profile_card');
86
88
  expect(DiscoverMap({}).type).toBe('discover_map');
87
89
  expect(TextField({ placeholder: 'x' }).type).toBe('text_field');
90
+ expect(SelectRow({ label: 'Pick', optionsRef: 'form_options.gender' }).type).toBe('select_row');
88
91
  expect(StoryRail({ surface: 'discover' }).type).toBe('story_rail');
89
92
  expect(ProfileCompletionBanner({}).type).toBe('profile_completion_banner');
90
93
  });
@@ -97,6 +100,11 @@ describe('bdc discover/forms/social/inbox/native', () => {
97
100
  expect(ChatInboxCapabilities({}).type).toBe('chat_inbox_capabilities');
98
101
  expect(FlashInboxCapabilities({}).type).toBe('flash_inbox_capabilities');
99
102
  expect(ProfileVisitsCapabilities({}).type).toBe('profile_visits_capabilities');
103
+ expect(InboxHubChrome({}).type).toBe('inbox_hub_chrome');
104
+ expect(InboxHubChrome({}).props).toMatchObject({
105
+ hub_ref: 'hub',
106
+ active_filter_ref: 'active_filter',
107
+ });
100
108
  const menu = FloatingCreateMenu({
101
109
  id: 'fab',
102
110
  closedLabel: '+',
@@ -123,6 +131,7 @@ describe('bdc discover/forms/social/inbox/native', () => {
123
131
  AlbumDetail,
124
132
  NewPostComposer,
125
133
  NewStoryComposer,
134
+ StoryViewersPanel,
126
135
  PostComments,
127
136
  SocialProfile,
128
137
  SettingsHome,
@@ -18,3 +18,13 @@ type TextFieldProps = LayoutProps & {
18
18
  };
19
19
 
20
20
  export const TextField: BdcComponent = (props) => layoutBlock('text_field', props as TextFieldProps);
21
+
22
+ type SelectRowProps = LayoutProps & {
23
+ label?: string;
24
+ dataRef?: string;
25
+ optionsRef?: string;
26
+ changeActionId?: string;
27
+ disabled?: boolean;
28
+ };
29
+
30
+ export const SelectRow: BdcComponent = (props) => layoutBlock('select_row', props as SelectRowProps);
@@ -81,6 +81,26 @@ type FloatingCreateMenuProps = {
81
81
  actions: FloatingCreateMenuActionProps[];
82
82
  };
83
83
 
84
+ type InboxHubChromeProps = {
85
+ id?: string;
86
+ hubRef?: string;
87
+ activeFilterRef?: string;
88
+ };
89
+
90
+ /**
91
+ * Overlay do seletor/filtros do hub inbox (sibling do Scroll).
92
+ * Hybrid nativo: collapse + badges realtime; actions via onAction.
93
+ */
94
+ export const InboxHubChrome: BdcComponent = (props) =>
95
+ leafBlock('inbox_hub_chrome', {
96
+ ...(typeof (props as InboxHubChromeProps).id === 'string'
97
+ ? { id: (props as InboxHubChromeProps).id }
98
+ : {}),
99
+ hub_ref: (props as InboxHubChromeProps).hubRef ?? 'hub',
100
+ active_filter_ref:
101
+ (props as InboxHubChromeProps).activeFilterRef ?? 'active_filter',
102
+ });
103
+
84
104
  /** Overlay FAB “+” (sibling do Scroll) — ações via action_id do wire. */
85
105
  export const FloatingCreateMenu: BdcComponent = (props) => {
86
106
  const menu = props as FloatingCreateMenuProps;
@@ -30,6 +30,7 @@ export const AlbumGrid: BdcComponent = (props) => nativeBlock('album_grid', prop
30
30
  export const AlbumDetail: BdcComponent = (props) => nativeBlock('album_detail', props);
31
31
  export const NewPostComposer: BdcComponent = (props) => nativeBlock('new_post_composer', props);
32
32
  export const NewStoryComposer: BdcComponent = (props) => nativeBlock('new_story_composer', props);
33
+ export const StoryViewersPanel: BdcComponent = (props) => nativeBlock('story_viewers_panel', props);
33
34
  export const PostComments: BdcComponent = (props) => nativeBlock('post_comments', props);
34
35
  export const SocialProfile: BdcComponent = (props) => nativeBlock('social_profile', props);
35
36
  export const SettingsHome: BdcComponent = (props) => nativeBlock('settings_home', props);
package/src/bdc/index.ts CHANGED
@@ -23,13 +23,14 @@ export {
23
23
  EmptyState,
24
24
  FlashInboxCapabilities,
25
25
  FloatingCreateMenu,
26
+ InboxHubChrome,
26
27
  InboxPersonRow,
27
28
  InboxSearchField,
28
29
  ProfileVisitsCapabilities,
29
30
  SegmentedTabs,
30
31
  } from './components/inbox';
31
32
  export type { FloatingCreateMenuActionProps } from './components/inbox';
32
- export { TextField } from './components/forms';
33
+ export { TextField, SelectRow } from './components/forms';
33
34
  export { ProfileCompletionBanner, StoryRail } from './components/social';
34
35
  export {
35
36
  AlbumDetail,
@@ -37,6 +38,7 @@ export {
37
38
  ChatThread,
38
39
  NewPostComposer,
39
40
  NewStoryComposer,
41
+ StoryViewersPanel,
40
42
  PostComments,
41
43
  ProfileOnboarding,
42
44
  ProfileReport,
@@ -9,6 +9,8 @@ export const SDUI_BLOCK_TYPES = [
9
9
  'spacer',
10
10
  'info_banner',
11
11
  'native_ad',
12
+ 'inbox_hub_chrome',
13
+ 'select_row',
12
14
  ] as const;
13
15
 
14
16
  export type SduiBlockType = (typeof SDUI_BLOCK_TYPES)[number];
@@ -32,7 +32,9 @@ import {
32
32
  describe('block-types', () => {
33
33
  it('lists known block types', () => {
34
34
  expect(SDUI_BLOCK_TYPES).toContain('text');
35
+ expect(SDUI_BLOCK_TYPES).toContain('inbox_hub_chrome');
35
36
  expect(isKnownBlockType('button')).toBe(true);
37
+ expect(isKnownBlockType('inbox_hub_chrome')).toBe(true);
36
38
  expect(isKnownBlockType('unknown_block')).toBe(false);
37
39
  });
38
40
  });
@@ -11,7 +11,7 @@ type Props = {
11
11
  };
12
12
 
13
13
  /** Blocos overlay (FAB etc.) — não competem por flex com o Scroll. */
14
- const OVERLAY_BLOCK_TYPES = new Set(['floating_create_menu']);
14
+ const OVERLAY_BLOCK_TYPES = new Set(['floating_create_menu', 'inbox_hub_chrome']);
15
15
 
16
16
  export function SduiScreenRenderer({ screen, onAction }: Props) {
17
17
  return (
@@ -227,6 +227,20 @@ export function SduiFloatingCreateMenuBlock(props: SduiBlockRenderProps) {
227
227
  return <>{hybrid.renderFloatingCreateMenu(props)}</>;
228
228
  }
229
229
 
230
+ export function SduiInboxHubChromeBlock(props: SduiBlockRenderProps) {
231
+ const hybrid = useSduiHybridRenderer();
232
+ if (typeof hybrid.renderInboxHubChrome !== 'function') return null;
233
+ return (
234
+ <>
235
+ {hybrid.renderInboxHubChrome({
236
+ ...props,
237
+ hubRef: readStringProp(props.block, 'hub_ref') ?? 'hub',
238
+ activeFilterRef: readStringProp(props.block, 'active_filter_ref') ?? 'active_filter',
239
+ })}
240
+ </>
241
+ );
242
+ }
243
+
230
244
  const styles = StyleSheet.create({
231
245
  segmentTrack: {
232
246
  flexDirection: 'row',
@@ -5,9 +5,11 @@ import { Card } from '../ui/Card';
5
5
  import { AppIcon } from '../ui/app-icon';
6
6
  import { colors, fonts, m3Colors, m3Type, spacing } from '../theme/proulr-theme';
7
7
  import {
8
+ resolveDataRef,
8
9
  resolveDataRefBoolean,
9
10
  resolveDataRefString,
10
11
  } from '../lib/resolve-data-ref';
12
+ import { SelectRow as UiSelectRow } from '../ui/SelectRow';
11
13
  import { resolveViewStyleProps } from '../theme/resolve-style-props';
12
14
  import type { SduiBlockRenderProps } from './SduiPrimitiveBlocks';
13
15
 
@@ -117,6 +119,29 @@ export function SduiToggleRowBlock({ block, data, onAction }: SduiBlockRenderPro
117
119
  );
118
120
  }
119
121
 
122
+ export function SduiSelectRowBlock({ block, data, onAction }: SduiBlockRenderProps) {
123
+ const label = readStringProp(block, 'label') ?? '';
124
+ const dataRef = readStringProp(block, 'data_ref');
125
+ const optionsRef = readStringProp(block, 'options_ref');
126
+ const changeActionId = readStringProp(block, 'change_action_id');
127
+ const disabled = readBooleanProp(block, 'disabled');
128
+ const value = dataRef ? resolveDataRefString(data, dataRef) : '';
129
+ const optionsRaw = optionsRef ? resolveDataRef(data, optionsRef) : [];
130
+ const options = Array.isArray(optionsRaw) ? optionsRaw.map(String) : [];
131
+
132
+ return (
133
+ <UiSelectRow
134
+ label={label}
135
+ value={value || null}
136
+ options={options}
137
+ onChange={(next) => {
138
+ if (disabled || !changeActionId) return;
139
+ onAction(changeActionId, { value: next });
140
+ }}
141
+ />
142
+ );
143
+ }
144
+
120
145
  const styles = StyleSheet.create({
121
146
  card: {
122
147
  paddingVertical: spacing.xs,
@@ -30,6 +30,7 @@ function NativeBlockShell({
30
30
  | 'renderAlbumDetail'
31
31
  | 'renderNewPostComposer'
32
32
  | 'renderNewStoryComposer'
33
+ | 'renderStoryViewersPanel'
33
34
  | 'renderPostComments'
34
35
  | 'renderSocialProfile'
35
36
  | 'renderSettingsHome'
@@ -118,6 +119,12 @@ export function SduiNewStoryComposerBlock(props: SduiBlockRenderProps) {
118
119
  return <NativeBlockShell {...props} rendererKey="renderNewStoryComposer" fallbackLabel="Novo story" />;
119
120
  }
120
121
 
122
+ export function SduiStoryViewersPanelBlock(props: SduiBlockRenderProps) {
123
+ return (
124
+ <NativeBlockShell {...props} rendererKey="renderStoryViewersPanel" fallbackLabel="Story viewers" />
125
+ );
126
+ }
127
+
121
128
  export function SduiPostCommentsBlock(props: SduiBlockRenderProps) {
122
129
  return <NativeBlockShell {...props} rendererKey="renderPostComments" fallbackLabel="Comentários" />;
123
130
  }
@@ -12,11 +12,13 @@ export {
12
12
  export {
13
13
  SduiChromeScrollProvider,
14
14
  useSduiChromeScroll,
15
+ useSduiChromeScrollOverlayRegistry,
15
16
  } from './sdui-chrome-scroll-context';
16
17
  export type {
17
18
  SduiChromeScrollConfig,
18
19
  SduiChromeScrollInsetProps,
19
20
  SduiChromeScrollInsets,
21
+ SduiChromeScrollOverlayContribution,
20
22
  } from './sdui-chrome-scroll-context';
21
23
  export type {
22
24
  SduiHybridDiscoverMapProps,
@@ -1,9 +1,18 @@
1
- import React, { createContext, useContext, type ReactElement, type ReactNode } from 'react';
2
- import type {
3
- NativeScrollEvent,
4
- NativeSyntheticEvent,
5
- RefreshControlProps,
6
- ViewStyle,
1
+ import React, {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useMemo,
6
+ useState,
7
+ type ReactElement,
8
+ type ReactNode,
9
+ } from 'react';
10
+ import {
11
+ Platform,
12
+ type NativeScrollEvent,
13
+ type NativeSyntheticEvent,
14
+ type RefreshControlProps,
15
+ type ViewStyle,
7
16
  } from 'react-native';
8
17
 
9
18
  export type SduiChromeScrollInsetProps = {
@@ -31,7 +40,74 @@ export type SduiChromeScrollConfig = {
31
40
  scrollViewProps?: Record<string, unknown>;
32
41
  };
33
42
 
43
+ export type SduiChromeScrollOverlayContribution = {
44
+ /** Altura reservada abaixo do AppHeader (ex.: seletor + filtros do inbox). */
45
+ extraTopInset?: number;
46
+ onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
47
+ };
48
+
49
+ type OverlayRegistry = {
50
+ register: (id: string, contribution: SduiChromeScrollOverlayContribution) => () => void;
51
+ };
52
+
34
53
  const SduiChromeScrollContext = createContext<SduiChromeScrollConfig | null>(null);
54
+ const SduiChromeScrollOverlayRegistryContext = createContext<OverlayRegistry | null>(null);
55
+
56
+ function mergeChromeScrollConfig(
57
+ base: SduiChromeScrollConfig,
58
+ overlays: Map<string, SduiChromeScrollOverlayContribution>,
59
+ ): SduiChromeScrollConfig {
60
+ let extraTop = 0;
61
+ const scrollHandlers: ((event: NativeSyntheticEvent<NativeScrollEvent>) => void)[] = [];
62
+ if (base.onScroll) scrollHandlers.push(base.onScroll);
63
+
64
+ for (const contribution of overlays.values()) {
65
+ if (typeof contribution.extraTopInset === 'number' && contribution.extraTopInset > 0) {
66
+ extraTop = Math.max(extraTop, contribution.extraTopInset);
67
+ }
68
+ if (contribution.onScroll) scrollHandlers.push(contribution.onScroll);
69
+ }
70
+
71
+ if (extraTop <= 0 && scrollHandlers.length <= (base.onScroll ? 1 : 0)) {
72
+ return base;
73
+ }
74
+
75
+ const contentContainerStyle: ViewStyle = { ...base.contentContainerStyle };
76
+ let scrollInsetProps: SduiChromeScrollInsetProps = { ...base.scrollInsetProps };
77
+
78
+ if (extraTop > 0) {
79
+ // Paridade com buildSduiChromeScrollConfig(extraTopInset): iOS reserva header via contentInset.
80
+ const headerTop = base.chromeInsets.top;
81
+ const insetTop = headerTop + extraTop;
82
+ contentContainerStyle.paddingTop = Platform.OS === 'ios' ? extraTop : insetTop;
83
+ if (Platform.OS === 'ios') {
84
+ scrollInsetProps = {
85
+ contentInset: {
86
+ top: insetTop,
87
+ bottom: base.chromeInsets.bottom,
88
+ },
89
+ contentOffset: { x: 0, y: -insetTop },
90
+ automaticallyAdjustContentInsets: false,
91
+ scrollIndicatorInsets: {
92
+ top: insetTop,
93
+ bottom: base.chromeInsets.bottom,
94
+ },
95
+ };
96
+ }
97
+ }
98
+
99
+ return {
100
+ ...base,
101
+ contentContainerStyle,
102
+ scrollInsetProps,
103
+ onScroll:
104
+ scrollHandlers.length > 0
105
+ ? (event) => {
106
+ for (const handler of scrollHandlers) handler(event);
107
+ }
108
+ : undefined,
109
+ };
110
+ }
35
111
 
36
112
  export function SduiChromeScrollProvider({
37
113
  value,
@@ -40,11 +116,43 @@ export function SduiChromeScrollProvider({
40
116
  value: SduiChromeScrollConfig;
41
117
  children: ReactNode;
42
118
  }) {
119
+ const [overlays, setOverlays] = useState(
120
+ () => new Map<string, SduiChromeScrollOverlayContribution>(),
121
+ );
122
+
123
+ const register = useCallback((id: string, contribution: SduiChromeScrollOverlayContribution) => {
124
+ setOverlays((prev) => {
125
+ const next = new Map(prev);
126
+ next.set(id, contribution);
127
+ return next;
128
+ });
129
+ return () => {
130
+ setOverlays((prev) => {
131
+ if (!prev.has(id)) return prev;
132
+ const next = new Map(prev);
133
+ next.delete(id);
134
+ return next;
135
+ });
136
+ };
137
+ }, []);
138
+
139
+ const registry = useMemo<OverlayRegistry>(() => ({ register }), [register]);
140
+ const merged = useMemo(() => mergeChromeScrollConfig(value, overlays), [overlays, value]);
141
+
43
142
  return (
44
- <SduiChromeScrollContext.Provider value={value}>{children}</SduiChromeScrollContext.Provider>
143
+ <SduiChromeScrollOverlayRegistryContext.Provider value={registry}>
144
+ <SduiChromeScrollContext.Provider value={merged}>{children}</SduiChromeScrollContext.Provider>
145
+ </SduiChromeScrollOverlayRegistryContext.Provider>
45
146
  );
46
147
  }
47
148
 
48
149
  export function useSduiChromeScroll(): SduiChromeScrollConfig | null {
49
150
  return useContext(SduiChromeScrollContext);
50
151
  }
152
+
153
+ /**
154
+ * Hybrids de overlay (ex.: inbox_hub_chrome) registram inset/onScroll sem especializar o shell.
155
+ */
156
+ export function useSduiChromeScrollOverlayRegistry(): OverlayRegistry | null {
157
+ return useContext(SduiChromeScrollOverlayRegistryContext);
158
+ }
@@ -16,6 +16,7 @@ import {
16
16
  SduiCardBlock,
17
17
  SduiListItemBlock,
18
18
  SduiSectionHeaderBlock,
19
+ SduiSelectRowBlock,
19
20
  SduiToggleRowBlock,
20
21
  } from './blocks/SduiLayoutBlocks';
21
22
  import {
@@ -38,6 +39,7 @@ import {
38
39
  SduiInboxSearchFieldBlock,
39
40
  SduiProfileVisitsCapabilitiesBlock,
40
41
  SduiFloatingCreateMenuBlock,
42
+ SduiInboxHubChromeBlock,
41
43
  SduiSegmentedTabsBlock,
42
44
  } from './blocks/SduiInboxBlocks';
43
45
  import { SduiNativeAdBlock } from './blocks/SduiNativeAdBlock';
@@ -48,6 +50,7 @@ import {
48
50
  SduiChatThreadBlock,
49
51
  SduiNewPostComposerBlock,
50
52
  SduiNewStoryComposerBlock,
53
+ SduiStoryViewersPanelBlock,
51
54
  SduiPostCommentsBlock,
52
55
  SduiProfileOnboardingBlock,
53
56
  SduiProfileSelfBlock,
@@ -85,6 +88,7 @@ const REGISTRY: Record<string, SduiBlockComponent> = {
85
88
  section_header: SduiSectionHeaderBlock,
86
89
  list_item: SduiListItemBlock,
87
90
  toggle_row: SduiToggleRowBlock,
91
+ select_row: SduiSelectRowBlock,
88
92
  chip: SduiChipBlock,
89
93
  chip_row: SduiChipRowBlock,
90
94
  segmented_tabs: SduiSegmentedTabsBlock,
@@ -95,6 +99,7 @@ const REGISTRY: Record<string, SduiBlockComponent> = {
95
99
  flash_inbox_capabilities: SduiFlashInboxCapabilitiesBlock,
96
100
  profile_visits_capabilities: SduiProfileVisitsCapabilitiesBlock,
97
101
  floating_create_menu: SduiFloatingCreateMenuBlock,
102
+ inbox_hub_chrome: SduiInboxHubChromeBlock,
98
103
  discover_grid: SduiDiscoverGridBlock,
99
104
  discover_profile_card: SduiDiscoverProfileCardBlock,
100
105
  discover_map: SduiDiscoverMapBlock,
@@ -113,6 +118,7 @@ const REGISTRY: Record<string, SduiBlockComponent> = {
113
118
  album_detail: SduiAlbumDetailBlock,
114
119
  new_post_composer: SduiNewPostComposerBlock,
115
120
  new_story_composer: SduiNewStoryComposerBlock,
121
+ story_viewers_panel: SduiStoryViewersPanelBlock,
116
122
  post_comments: SduiPostCommentsBlock,
117
123
  social_profile: SduiSocialProfileBlock,
118
124
  settings_home: SduiSettingsHomeBlock,
@@ -66,6 +66,7 @@ export type SduiHybridRenderers = {
66
66
  renderAlbumDetail?: (props: SduiNativeBlockRenderProps) => ReactNode;
67
67
  renderNewPostComposer?: (props: SduiNativeBlockRenderProps) => ReactNode;
68
68
  renderNewStoryComposer?: (props: SduiNativeBlockRenderProps) => ReactNode;
69
+ renderStoryViewersPanel?: (props: SduiNativeBlockRenderProps) => ReactNode;
69
70
  renderPostComments?: (props: SduiNativeBlockRenderProps) => ReactNode;
70
71
  renderSocialProfile?: (props: SduiNativeBlockRenderProps) => ReactNode;
71
72
  renderSettingsHome?: (props: SduiNativeBlockRenderProps) => ReactNode;
@@ -105,6 +106,17 @@ export type SduiHybridRenderers = {
105
106
  renderFlashInboxCapabilities?: (props: SduiNativeBlockRenderProps) => ReactNode;
106
107
  renderProfileVisitsCapabilities?: (props: SduiNativeBlockRenderProps) => ReactNode;
107
108
  renderFloatingCreateMenu?: (props: SduiNativeBlockRenderProps) => ReactNode;
109
+ renderInboxHubChrome?: (
110
+ props: SduiNativeBlockRenderProps & {
111
+ hubRef?: string;
112
+ activeFilterRef?: string;
113
+ },
114
+ ) => ReactNode;
115
+ /**
116
+ * Optional host wrap for screen body (ex.: InboxHubSduiProvider).
117
+ * Keeps domain providers out of the generic shell.
118
+ */
119
+ wrapScreenBody?: (children: ReactNode) => ReactNode;
108
120
  };
109
121
 
110
122
  const SduiHybridRendererContext = createContext<SduiHybridRenderers>({});
@@ -12,23 +12,29 @@ export function StorySurfacePicker({
12
12
  visible,
13
13
  value,
14
14
  options,
15
+ title,
16
+ subtitle,
17
+ closeLabel,
15
18
  onClose,
16
19
  onSelect,
17
20
  }: {
18
21
  visible: boolean;
19
22
  value: string;
20
23
  options: StorySurfaceOption[];
24
+ title: string;
25
+ subtitle: string;
26
+ closeLabel: string;
21
27
  onClose: () => void;
22
28
  onSelect: (value: string) => void;
23
29
  }) {
24
30
  return (
25
31
  <Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
26
32
  <View style={styles.backdrop}>
27
- <Pressable style={styles.backdropPress} onPress={onClose} accessibilityLabel="Fechar" />
33
+ <Pressable style={styles.backdropPress} onPress={onClose} accessibilityLabel={closeLabel} />
28
34
  <View style={styles.sheet}>
29
35
  <View style={styles.handle} />
30
- <Text style={styles.title}>Onde publicar</Text>
31
- <Text style={styles.subtitle}>Escolha em qual superfície o story vai aparecer.</Text>
36
+ <Text style={styles.title}>{title}</Text>
37
+ <Text style={styles.subtitle}>{subtitle}</Text>
32
38
 
33
39
  {options.map((option) => {
34
40
  const selected = option.value === value;