@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.
- package/README.md +29 -0
- package/package.json +60 -0
- package/src/bdc/compile-bdc-to-wire.ts +123 -0
- package/src/bdc/components/discover.tsx +55 -0
- package/src/bdc/components/forms.tsx +20 -0
- package/src/bdc/components/native-blocks.tsx +35 -0
- package/src/bdc/components/social.tsx +27 -0
- package/src/bdc/components/ui.tsx +114 -0
- package/src/bdc/index.ts +41 -0
- package/src/bdc/jsx-runtime.ts +49 -0
- package/src/catalog/block-types.ts +18 -0
- package/src/catalog/style-tokens.ts +48 -0
- package/src/global.d.ts +1 -0
- package/src/renderer/SduiScreenRenderer.tsx +28 -0
- package/src/renderer/ads/native-ad-catalog.ts +23 -0
- package/src/renderer/blocks/SduiDiscoverBlocks.tsx +470 -0
- package/src/renderer/blocks/SduiLayoutBlocks.tsx +177 -0
- package/src/renderer/blocks/SduiNativeAdBlock.tsx +201 -0
- package/src/renderer/blocks/SduiNativeAdBlock.web.tsx +6 -0
- package/src/renderer/blocks/SduiNativeDomainBlocks.tsx +141 -0
- package/src/renderer/blocks/SduiPrimitiveBlocks.tsx +158 -0
- package/src/renderer/blocks/SduiSocialBlocks.tsx +64 -0
- package/src/renderer/index.ts +45 -0
- package/src/renderer/lib/brazilian-date-input.ts +22 -0
- package/src/renderer/lib/discover-profile-formatters.ts +75 -0
- package/src/renderer/lib/resolve-data-ref.ts +21 -0
- package/src/renderer/sdui-chrome-scroll-context.tsx +40 -0
- package/src/renderer/sdui-component-registry.tsx +129 -0
- package/src/renderer/sdui-hybrid-renderer-context.tsx +91 -0
- package/src/renderer/sdui-layout-debug.tsx +107 -0
- package/src/renderer/theme/proulr-theme.ts +164 -0
- package/src/renderer/theme/resolve-style-props.ts +85 -0
- package/src/renderer/ui/AppButton.tsx +71 -0
- package/src/renderer/ui/AppLoadingSpinner.tsx +38 -0
- package/src/renderer/ui/AuthStepShell.tsx +128 -0
- package/src/renderer/ui/Card.tsx +13 -0
- package/src/renderer/ui/DateField.tsx +36 -0
- package/src/renderer/ui/InfoBanner.tsx +51 -0
- package/src/renderer/ui/TextField.tsx +81 -0
- package/src/renderer/ui/compose-audience-pill.tsx +47 -0
- package/src/renderer/ui/compose-glyphs.tsx +400 -0
- package/src/renderer/ui/compose-media-carousel.tsx +116 -0
- package/src/renderer/ui/compose-media-shortcut.tsx +80 -0
- package/src/renderer/ui/compose-recent-media-grid.tsx +268 -0
- package/src/renderer/ui/compose-reply-audience-sheet.tsx +155 -0
- package/src/renderer/ui/compose-screen-header.tsx +115 -0
- package/src/renderer/ui/compose-text-area.tsx +94 -0
- package/src/renderer/ui/compose-toolbar-icon.tsx +45 -0
- package/src/renderer/ui/compose-toolbar.tsx +57 -0
- package/src/renderer/ui/compose-visibility-row.tsx +51 -0
- package/src/renderer/ui/floating-create-menu.tsx +165 -0
- package/src/renderer/ui/index.ts +44 -0
- package/src/renderer/ui/story-compose-header.tsx +72 -0
- package/src/renderer/ui/story-create-shortcut-row.tsx +89 -0
- package/src/renderer/ui/story-editor-chrome.tsx +231 -0
- package/src/renderer/ui/story-glyphs.tsx +144 -0
- package/src/renderer/ui/story-surface-picker.tsx +124 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# proulr-sdui-components (Proulr UI — analogia Nuds)
|
|
2
|
+
|
|
3
|
+
Lib de UI completa do Proulr: identidade Night Stalker OLED + componentes BDC + renderer RN.
|
|
4
|
+
|
|
5
|
+
## Exports
|
|
6
|
+
|
|
7
|
+
| Export | Consumidor | Uso |
|
|
8
|
+
|--------|------------|-----|
|
|
9
|
+
| `@proulr/sdui-components/bdc` | BFFs | Autoria com `<Button>`, `<Text>`, `<Screen>` |
|
|
10
|
+
| `@proulr/sdui-components/renderer` | `proulr-app` | Blocos RN + theme |
|
|
11
|
+
|
|
12
|
+
`jsxImportSource` no tsconfig do BFF: `@proulr/sdui-components/bdc` (resolve `./bdc/jsx-runtime` automaticamente).
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd ../proulr-sdui-contract && npm install
|
|
18
|
+
npm install
|
|
19
|
+
npm run typecheck
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Novo bloco
|
|
23
|
+
|
|
24
|
+
1. Adicionar em `src/catalog/block-types.ts`
|
|
25
|
+
2. Criar componente BDC em `src/bdc/components/`
|
|
26
|
+
3. Criar renderer em `src/renderer/blocks/`
|
|
27
|
+
4. Registrar em `sdui-component-registry.tsx`
|
|
28
|
+
|
|
29
|
+
Ver `docs/adding-a-block-type.md`.
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@proulr/sdui-components",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "./src/renderer/index.ts",
|
|
5
|
+
"types": "./src/renderer/index.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./bdc": "./src/bdc/index.ts",
|
|
8
|
+
"./bdc/jsx-runtime": "./src/bdc/jsx-runtime.ts",
|
|
9
|
+
"./renderer": "./src/renderer/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/saviorbp/proulr-sdui-components.git"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run typecheck"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@proulr/sdui-contract": "^0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"expo-linear-gradient": ">=56",
|
|
30
|
+
"react": ">=19",
|
|
31
|
+
"react-native": ">=0.85",
|
|
32
|
+
"react-native-google-mobile-ads": ">=16",
|
|
33
|
+
"react-native-safe-area-context": ">=5"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"react": {
|
|
37
|
+
"optional": true
|
|
38
|
+
},
|
|
39
|
+
"react-native": {
|
|
40
|
+
"optional": true
|
|
41
|
+
},
|
|
42
|
+
"expo-linear-gradient": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"react-native-google-mobile-ads": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"react-native-safe-area-context": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^22.20.0",
|
|
54
|
+
"@types/react": "~19.2.2",
|
|
55
|
+
"expo-linear-gradient": "~56.0.4",
|
|
56
|
+
"react": "19.2.3",
|
|
57
|
+
"react-native": "0.85.3",
|
|
58
|
+
"typescript": "~6.0.3"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SduiActionWire,
|
|
3
|
+
SduiBlockWire,
|
|
4
|
+
SduiHeaderWire,
|
|
5
|
+
SduiScreenMetaWire,
|
|
6
|
+
SduiScreenWire,
|
|
7
|
+
StyleWire,
|
|
8
|
+
} from '@proulr/sdui-contract';
|
|
9
|
+
import type { BdcElement } from './jsx-runtime';
|
|
10
|
+
|
|
11
|
+
function camelToSnake(key: string): string {
|
|
12
|
+
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mapPropsToWire(props: Record<string, unknown>): Record<string, unknown> {
|
|
16
|
+
const wire: Record<string, unknown> = {};
|
|
17
|
+
for (const [key, value] of Object.entries(props)) {
|
|
18
|
+
if (key === 'children') continue;
|
|
19
|
+
if (key === 'actionId') {
|
|
20
|
+
wire.action_id = value;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (key === 'style' && value && typeof value === 'object') {
|
|
24
|
+
const styleWire: Record<string, unknown> = {};
|
|
25
|
+
for (const [sk, sv] of Object.entries(value as StyleWire)) {
|
|
26
|
+
styleWire[camelToSnake(sk)] = sv;
|
|
27
|
+
}
|
|
28
|
+
wire.style = styleWire;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
wire[camelToSnake(key)] = value;
|
|
32
|
+
}
|
|
33
|
+
return wire;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function textFromChildren(children: BdcElement[]): string {
|
|
37
|
+
return children
|
|
38
|
+
.map((child) => {
|
|
39
|
+
if (child.type === '_text_node') return String(child.props.value ?? '');
|
|
40
|
+
if (child.type === 'text' && typeof child.props.text === 'string') return child.props.text;
|
|
41
|
+
return '';
|
|
42
|
+
})
|
|
43
|
+
.join('')
|
|
44
|
+
.trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function compileBdcBlock(element: BdcElement, path = '0'): SduiBlockWire {
|
|
48
|
+
return mapBlock(element, path);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function mapBlock(element: BdcElement, path: string): SduiBlockWire {
|
|
52
|
+
if (element.type === '_text_node') {
|
|
53
|
+
return { id: path, type: 'text', props: { text: String(element.props.value ?? '') } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const props = mapPropsToWire(element.props);
|
|
57
|
+
|
|
58
|
+
if (element.type === 'button' && !props.label) {
|
|
59
|
+
props.label = textFromChildren(element.children);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (element.type === 'text' && !props.text) {
|
|
63
|
+
props.text = textFromChildren(element.children);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const blockChildren = element.children.filter(
|
|
67
|
+
(child) => child.type !== '_text_node' && child.type !== '_fragment',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
id: typeof element.props.id === 'string' ? element.props.id : path,
|
|
72
|
+
type: element.type,
|
|
73
|
+
props,
|
|
74
|
+
children: blockChildren.map((child, index) => mapBlock(child, `${path}.${index}`)),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function compileBdcToWire(root: BdcElement): SduiScreenWire {
|
|
79
|
+
if (root.type !== 'screen') {
|
|
80
|
+
throw new Error('bdc_root_must_be_screen');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const {
|
|
84
|
+
id,
|
|
85
|
+
header,
|
|
86
|
+
headerSlot,
|
|
87
|
+
actions,
|
|
88
|
+
meta,
|
|
89
|
+
data,
|
|
90
|
+
children: _ignored,
|
|
91
|
+
...rest
|
|
92
|
+
} = root.props as {
|
|
93
|
+
id: string;
|
|
94
|
+
header?: SduiHeaderWire;
|
|
95
|
+
headerSlot?: BdcElement | BdcElement[];
|
|
96
|
+
actions?: Record<string, SduiActionWire>;
|
|
97
|
+
meta?: SduiScreenMetaWire;
|
|
98
|
+
data?: Record<string, unknown>;
|
|
99
|
+
children?: unknown;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
void rest;
|
|
103
|
+
|
|
104
|
+
const blocks = root.children
|
|
105
|
+
.filter((child) => child.type !== '_fragment')
|
|
106
|
+
.map((child, index) => mapBlock(child, String(index)));
|
|
107
|
+
|
|
108
|
+
const compiledHeader = header ? { ...header } : undefined;
|
|
109
|
+
if (compiledHeader && headerSlot) {
|
|
110
|
+
const slotRoots = Array.isArray(headerSlot) ? headerSlot : [headerSlot];
|
|
111
|
+
compiledHeader.slot_blocks = slotRoots.map((slot, index) => mapBlock(slot, `header.${index}`));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
schema_version: 1,
|
|
116
|
+
screen_id: id,
|
|
117
|
+
header: compiledHeader,
|
|
118
|
+
blocks,
|
|
119
|
+
data: data ?? {},
|
|
120
|
+
actions: actions ?? {},
|
|
121
|
+
meta: meta ?? { bff: undefined, cache_ttl_ms: 30_000, offline_policy: 'cache_only' },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { BdcComponent, BdcElement } from '../jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type LayoutProps = {
|
|
4
|
+
id?: string;
|
|
5
|
+
gap?: number | string;
|
|
6
|
+
padding?: number | string;
|
|
7
|
+
children?: BdcElement[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function layoutBlock(type: string, props: LayoutProps): BdcElement {
|
|
11
|
+
return { type, props: { ...props }, children: props.children ?? [] };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type ChipProps = LayoutProps & {
|
|
15
|
+
label: string;
|
|
16
|
+
actionId?: string;
|
|
17
|
+
actionParams?: Record<string, string>;
|
|
18
|
+
active?: boolean;
|
|
19
|
+
variant?: 'default' | 'on_header';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const Chip: BdcComponent = (props) => layoutBlock('chip', props as ChipProps);
|
|
23
|
+
|
|
24
|
+
type ChipRowProps = LayoutProps & {
|
|
25
|
+
variant?: 'default' | 'on_header';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const ChipRow: BdcComponent = (props) => layoutBlock('chip_row', props as ChipRowProps);
|
|
29
|
+
|
|
30
|
+
type DiscoverGridProps = LayoutProps & {
|
|
31
|
+
itemsRef: string;
|
|
32
|
+
columns?: number;
|
|
33
|
+
profileActionId?: string;
|
|
34
|
+
adPlacement?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const DiscoverGrid: BdcComponent = (props) =>
|
|
38
|
+
layoutBlock('discover_grid', props as DiscoverGridProps);
|
|
39
|
+
|
|
40
|
+
type DiscoverProfileCardProps = LayoutProps & {
|
|
41
|
+
profileRef: string;
|
|
42
|
+
actionId?: string;
|
|
43
|
+
width?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const DiscoverProfileCard: BdcComponent = (props) =>
|
|
47
|
+
layoutBlock('discover_profile_card', props as DiscoverProfileCardProps);
|
|
48
|
+
|
|
49
|
+
type DiscoverMapProps = LayoutProps & {
|
|
50
|
+
centerRef?: string;
|
|
51
|
+
labelRef?: string;
|
|
52
|
+
height?: number;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const DiscoverMap: BdcComponent = (props) => layoutBlock('discover_map', props as DiscoverMapProps);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BdcComponent, BdcElement } from '../jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type LayoutProps = {
|
|
4
|
+
id?: string;
|
|
5
|
+
gap?: number | string;
|
|
6
|
+
padding?: number | string;
|
|
7
|
+
children?: BdcElement[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function layoutBlock(type: string, props: LayoutProps): BdcElement {
|
|
11
|
+
return { type, props: { ...props }, children: props.children ?? [] };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type TextFieldProps = LayoutProps & {
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
dataRef?: string;
|
|
17
|
+
submitActionId?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const TextField: BdcComponent = (props) => layoutBlock('text_field', props as TextFieldProps);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BdcComponent, BdcElement } from '../jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type NativeBlockProps = {
|
|
4
|
+
id?: string;
|
|
5
|
+
dataRef?: string;
|
|
6
|
+
conversationIdRef?: string;
|
|
7
|
+
peerIdRef?: string;
|
|
8
|
+
peerNameRef?: string;
|
|
9
|
+
handleRef?: string;
|
|
10
|
+
postIdRef?: string;
|
|
11
|
+
albumIdRef?: string;
|
|
12
|
+
profileIdRef?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function nativeBlock(type: string, props: NativeBlockProps): BdcElement {
|
|
16
|
+
return { type, props: { ...props }, children: [] };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const FlashInboxList: BdcComponent = (props) => nativeBlock('flash_inbox_list', props);
|
|
20
|
+
export const ChatInboxList: BdcComponent = (props) => nativeBlock('chat_inbox_list', props);
|
|
21
|
+
export const ChatThread: BdcComponent = (props) => nativeBlock('chat_thread', props);
|
|
22
|
+
export const SocialPostFeed: BdcComponent = (props) => nativeBlock('social_post_feed', props);
|
|
23
|
+
export const ThoughtFeed: BdcComponent = (props) => nativeBlock('thought_feed', props);
|
|
24
|
+
export const VerticalVideoPager: BdcComponent = (props) => nativeBlock('vertical_video_pager', props);
|
|
25
|
+
export const ProfileView: BdcComponent = (props) => nativeBlock('profile_view', props);
|
|
26
|
+
export const ProfileSelf: BdcComponent = (props) => nativeBlock('profile_self', props);
|
|
27
|
+
export const ProfileSetup: BdcComponent = (props) => nativeBlock('profile_setup', props);
|
|
28
|
+
export const ProfileOnboarding: BdcComponent = (props) => nativeBlock('profile_onboarding', props);
|
|
29
|
+
export const AlbumGrid: BdcComponent = (props) => nativeBlock('album_grid', props);
|
|
30
|
+
export const AlbumDetail: BdcComponent = (props) => nativeBlock('album_detail', props);
|
|
31
|
+
export const NewPostComposer: BdcComponent = (props) => nativeBlock('new_post_composer', props);
|
|
32
|
+
export const PostComments: BdcComponent = (props) => nativeBlock('post_comments', props);
|
|
33
|
+
export const SocialProfile: BdcComponent = (props) => nativeBlock('social_profile', props);
|
|
34
|
+
export const SettingsHome: BdcComponent = (props) => nativeBlock('settings_home', props);
|
|
35
|
+
export const DiscoverBrowseLocation: BdcComponent = (props) => nativeBlock('discover_browse_location', props);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { BdcComponent, BdcElement } from '../jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type LayoutProps = {
|
|
4
|
+
id?: string;
|
|
5
|
+
gap?: number | string;
|
|
6
|
+
padding?: number | string;
|
|
7
|
+
children?: BdcElement[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function layoutBlock(type: string, props: LayoutProps): BdcElement {
|
|
11
|
+
return { type, props: { ...props }, children: props.children ?? [] };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type StoryRailProps = LayoutProps & {
|
|
15
|
+
surface?: 'discover' | 'vibes';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const StoryRail: BdcComponent = (props) => layoutBlock('story_rail', props as StoryRailProps);
|
|
19
|
+
|
|
20
|
+
type ProfileCompletionBannerProps = LayoutProps & {
|
|
21
|
+
percentRef?: string;
|
|
22
|
+
dismissActionId?: string;
|
|
23
|
+
completeActionId?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const ProfileCompletionBanner: BdcComponent = (props) =>
|
|
27
|
+
layoutBlock('profile_completion_banner', props as ProfileCompletionBannerProps);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SduiActionWire,
|
|
3
|
+
SduiHeaderWire,
|
|
4
|
+
SduiScreenMetaWire,
|
|
5
|
+
StyleWire,
|
|
6
|
+
} from '@proulr/sdui-contract';
|
|
7
|
+
import type { BdcComponent, BdcElement } from '../jsx-runtime';
|
|
8
|
+
|
|
9
|
+
type LayoutProps = {
|
|
10
|
+
id?: string;
|
|
11
|
+
gap?: number | string;
|
|
12
|
+
padding?: number | string;
|
|
13
|
+
height?: number;
|
|
14
|
+
variant?: string;
|
|
15
|
+
style?: StyleWire;
|
|
16
|
+
children?: BdcElement[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function layoutBlock(type: string, props: LayoutProps): BdcElement {
|
|
20
|
+
return { type, props: { ...props }, children: props.children ?? [] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Screen: BdcComponent = (props) => ({
|
|
24
|
+
type: 'screen',
|
|
25
|
+
props,
|
|
26
|
+
children: (props.children as BdcElement[]) ?? [],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const Column: BdcComponent = (props) => layoutBlock('column', props as LayoutProps);
|
|
30
|
+
|
|
31
|
+
export const Row: BdcComponent = (props) => layoutBlock('row', props as LayoutProps);
|
|
32
|
+
|
|
33
|
+
export const Scroll: BdcComponent = (props) => layoutBlock('scroll', props as LayoutProps);
|
|
34
|
+
|
|
35
|
+
export const Spacer: BdcComponent = (props) => layoutBlock('spacer', props as LayoutProps);
|
|
36
|
+
|
|
37
|
+
type TextProps = LayoutProps & {
|
|
38
|
+
text?: string;
|
|
39
|
+
variant?: 'headline' | 'title' | 'body' | 'label';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const Text: BdcComponent = (props) => layoutBlock('text', props as TextProps);
|
|
43
|
+
|
|
44
|
+
type ButtonProps = LayoutProps & {
|
|
45
|
+
actionId: string;
|
|
46
|
+
label?: string;
|
|
47
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'outline' | 'danger';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Button: BdcComponent = (props) => layoutBlock('button', props as ButtonProps);
|
|
51
|
+
|
|
52
|
+
type InfoBannerProps = LayoutProps & {
|
|
53
|
+
message: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const InfoBanner: BdcComponent = (props) => layoutBlock('info_banner', props as InfoBannerProps);
|
|
57
|
+
|
|
58
|
+
type NativeAdProps = LayoutProps & {
|
|
59
|
+
placement?: string;
|
|
60
|
+
width?: number;
|
|
61
|
+
aspect_ratio?: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const NativeAd: BdcComponent = (props) => layoutBlock('native_ad', props as NativeAdProps);
|
|
65
|
+
|
|
66
|
+
type CardProps = LayoutProps;
|
|
67
|
+
|
|
68
|
+
export const Card: BdcComponent = (props) => layoutBlock('card', props as CardProps);
|
|
69
|
+
|
|
70
|
+
type SectionHeaderProps = LayoutProps & {
|
|
71
|
+
title?: string;
|
|
72
|
+
actionLabel?: string;
|
|
73
|
+
actionId?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const SectionHeader: BdcComponent = (props) =>
|
|
77
|
+
layoutBlock('section_header', props as SectionHeaderProps);
|
|
78
|
+
|
|
79
|
+
type ListItemProps = LayoutProps & {
|
|
80
|
+
title?: string;
|
|
81
|
+
subtitle?: string;
|
|
82
|
+
subtitleRef?: string;
|
|
83
|
+
trailingText?: string;
|
|
84
|
+
trailingRef?: string;
|
|
85
|
+
actionId?: string;
|
|
86
|
+
showDivider?: boolean;
|
|
87
|
+
showChevron?: boolean;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const ListItem: BdcComponent = (props) => layoutBlock('list_item', props as ListItemProps);
|
|
91
|
+
|
|
92
|
+
type ToggleRowProps = LayoutProps & {
|
|
93
|
+
label?: string;
|
|
94
|
+
hint?: string;
|
|
95
|
+
dataRef?: string;
|
|
96
|
+
toggleActionId?: string;
|
|
97
|
+
disabled?: boolean;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const ToggleRow: BdcComponent = (props) => layoutBlock('toggle_row', props as ToggleRowProps);
|
|
101
|
+
|
|
102
|
+
export type ScreenProps = {
|
|
103
|
+
id: string;
|
|
104
|
+
header?: SduiHeaderWire;
|
|
105
|
+
headerSlot?: BdcElement | BdcElement[];
|
|
106
|
+
actions?: Record<string, SduiActionWire>;
|
|
107
|
+
meta?: SduiScreenMetaWire;
|
|
108
|
+
data?: Record<string, unknown>;
|
|
109
|
+
children?: BdcElement[];
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export function screenProps(props: ScreenProps): ScreenProps {
|
|
113
|
+
return props;
|
|
114
|
+
}
|
package/src/bdc/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export { compileBdcToWire, compileBdcBlock } from './compile-bdc-to-wire';
|
|
2
|
+
export {
|
|
3
|
+
Button,
|
|
4
|
+
Card,
|
|
5
|
+
Column,
|
|
6
|
+
InfoBanner,
|
|
7
|
+
ListItem,
|
|
8
|
+
NativeAd,
|
|
9
|
+
Row,
|
|
10
|
+
Screen,
|
|
11
|
+
Scroll,
|
|
12
|
+
SectionHeader,
|
|
13
|
+
Spacer,
|
|
14
|
+
Text,
|
|
15
|
+
ToggleRow,
|
|
16
|
+
screenProps,
|
|
17
|
+
} from './components/ui';
|
|
18
|
+
export { Chip, ChipRow, DiscoverGrid, DiscoverMap, DiscoverProfileCard } from './components/discover';
|
|
19
|
+
export { TextField } from './components/forms';
|
|
20
|
+
export { ProfileCompletionBanner, StoryRail } from './components/social';
|
|
21
|
+
export {
|
|
22
|
+
AlbumDetail,
|
|
23
|
+
AlbumGrid,
|
|
24
|
+
ChatInboxList,
|
|
25
|
+
ChatThread,
|
|
26
|
+
FlashInboxList,
|
|
27
|
+
NewPostComposer,
|
|
28
|
+
PostComments,
|
|
29
|
+
ProfileOnboarding,
|
|
30
|
+
ProfileSelf,
|
|
31
|
+
ProfileSetup,
|
|
32
|
+
ProfileView,
|
|
33
|
+
SocialPostFeed,
|
|
34
|
+
SocialProfile,
|
|
35
|
+
SettingsHome,
|
|
36
|
+
DiscoverBrowseLocation,
|
|
37
|
+
ThoughtFeed,
|
|
38
|
+
VerticalVideoPager,
|
|
39
|
+
} from './components/native-blocks';
|
|
40
|
+
export type { BdcElement } from './jsx-runtime';
|
|
41
|
+
export type { ScreenProps } from './components/ui';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { StyleWire } from '@proulr/sdui-contract';
|
|
2
|
+
|
|
3
|
+
export type BdcElement = {
|
|
4
|
+
type: string;
|
|
5
|
+
props: Record<string, unknown>;
|
|
6
|
+
children: BdcElement[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type BdcComponent = (props: Record<string, unknown>) => BdcElement;
|
|
10
|
+
|
|
11
|
+
function flattenChild(child: unknown): BdcElement[] {
|
|
12
|
+
if (child == null || typeof child === 'boolean') return [];
|
|
13
|
+
if (Array.isArray(child)) return child.flatMap(flattenChild);
|
|
14
|
+
if (typeof child === 'object' && 'type' in child) return [child as BdcElement];
|
|
15
|
+
if (typeof child === 'string' || typeof child === 'number') {
|
|
16
|
+
return [{ type: '_text_node', props: { value: String(child) }, children: [] }];
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createElement(
|
|
22
|
+
type: string | BdcComponent,
|
|
23
|
+
props: Record<string, unknown> | null,
|
|
24
|
+
...childInputs: unknown[]
|
|
25
|
+
): BdcElement {
|
|
26
|
+
const rawProps = props ?? {};
|
|
27
|
+
const { children: propsChildren, ...restProps } = rawProps as Record<string, unknown> & {
|
|
28
|
+
children?: unknown;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const children = [
|
|
32
|
+
...flattenChild(propsChildren),
|
|
33
|
+
...childInputs.flatMap(flattenChild),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
if (typeof type === 'function') {
|
|
37
|
+
return type({ ...restProps, children });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { type, props: restProps, children };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const jsx = createElement;
|
|
44
|
+
export const jsxs = createElement;
|
|
45
|
+
export const Fragment = '_fragment';
|
|
46
|
+
|
|
47
|
+
export function styleWire(style: StyleWire): { style: StyleWire } {
|
|
48
|
+
return { style };
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Block types registered in the SDUI renderer (snake_case wire). */
|
|
2
|
+
|
|
3
|
+
export const SDUI_BLOCK_TYPES = [
|
|
4
|
+
'text',
|
|
5
|
+
'button',
|
|
6
|
+
'column',
|
|
7
|
+
'row',
|
|
8
|
+
'scroll',
|
|
9
|
+
'spacer',
|
|
10
|
+
'info_banner',
|
|
11
|
+
'native_ad',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type SduiBlockType = (typeof SDUI_BLOCK_TYPES)[number];
|
|
15
|
+
|
|
16
|
+
export function isKnownBlockType(type: string): type is SduiBlockType {
|
|
17
|
+
return (SDUI_BLOCK_TYPES as readonly string[]).includes(type);
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ColorToken,
|
|
3
|
+
ShapeToken,
|
|
4
|
+
SpacingToken,
|
|
5
|
+
StyleWire,
|
|
6
|
+
TypeToken,
|
|
7
|
+
} from '@proulr/sdui-contract';
|
|
8
|
+
|
|
9
|
+
export const COLOR_TOKENS = [
|
|
10
|
+
'primary',
|
|
11
|
+
'onPrimary',
|
|
12
|
+
'primaryContainer',
|
|
13
|
+
'secondary',
|
|
14
|
+
'onSecondary',
|
|
15
|
+
'secondaryContainer',
|
|
16
|
+
'tertiary',
|
|
17
|
+
'surface',
|
|
18
|
+
'onSurface',
|
|
19
|
+
'onSurfaceVariant',
|
|
20
|
+
'surfaceContainer',
|
|
21
|
+
'surfaceContainerHigh',
|
|
22
|
+
'surfaceContainerHighest',
|
|
23
|
+
'outline',
|
|
24
|
+
'error',
|
|
25
|
+
'onError',
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export const SPACING_TOKENS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'] as const;
|
|
29
|
+
|
|
30
|
+
export const SHAPE_TOKENS = [
|
|
31
|
+
'cornerExtraSmall',
|
|
32
|
+
'cornerSmall',
|
|
33
|
+
'cornerMedium',
|
|
34
|
+
'cornerLarge',
|
|
35
|
+
'cornerExtraLarge',
|
|
36
|
+
'cornerFull',
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
export const TYPE_TOKENS = [
|
|
40
|
+
'display',
|
|
41
|
+
'headline',
|
|
42
|
+
'title',
|
|
43
|
+
'titleSmall',
|
|
44
|
+
'body',
|
|
45
|
+
'bodySmall',
|
|
46
|
+
'label',
|
|
47
|
+
'labelSmall',
|
|
48
|
+
] as const;
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare const __DEV__: boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import type { SduiScreen } from '@proulr/sdui-contract';
|
|
4
|
+
import { colors } from './theme/proulr-theme';
|
|
5
|
+
import { renderBlockTree } from './sdui-component-registry';
|
|
6
|
+
import { SduiLayoutDebugFrame } from './sdui-layout-debug';
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
screen: SduiScreen;
|
|
10
|
+
onAction: (actionId: string, vars?: Record<string, string>) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function SduiScreenRenderer({ screen, onAction }: Props) {
|
|
14
|
+
return (
|
|
15
|
+
<SduiLayoutDebugFrame layer="renderer" style={styles.screen}>
|
|
16
|
+
{screen.blocks.map((block) => (
|
|
17
|
+
<SduiLayoutDebugFrame key={block.id} layer="block" extra={block.type} style={styles.block}>
|
|
18
|
+
{renderBlockTree(block, screen.data, onAction)}
|
|
19
|
+
</SduiLayoutDebugFrame>
|
|
20
|
+
))}
|
|
21
|
+
</SduiLayoutDebugFrame>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const styles = StyleSheet.create({
|
|
26
|
+
screen: { flex: 1, minHeight: 0, backgroundColor: colors.bg },
|
|
27
|
+
block: { flex: 1, minHeight: 0 },
|
|
28
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type NativeAdPlacementConfig = {
|
|
2
|
+
aspectRatio?: number;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export type NativeAdCatalog = {
|
|
6
|
+
isEnabled: () => boolean;
|
|
7
|
+
resolveAdUnitId: (placement: string, testNativeUnitId: string) => string;
|
|
8
|
+
getPlacementConfig?: (placement: string) => NativeAdPlacementConfig | undefined;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let nativeAdCatalog: NativeAdCatalog | null = null;
|
|
12
|
+
|
|
13
|
+
export function configureNativeAdCatalog(catalog: NativeAdCatalog): void {
|
|
14
|
+
nativeAdCatalog = catalog;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getNativeAdCatalog(): NativeAdCatalog | null {
|
|
18
|
+
return nativeAdCatalog;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_NATIVE_AD_PLACEMENT = 'discover_grid';
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_NATIVE_AD_ASPECT_RATIO = 0.82;
|