@proulr/sdui-components 0.1.87 → 0.1.90
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 +2 -0
- package/package.json +2 -2
- package/src/bdc/compile-bdc-row-template.test.ts +179 -0
- package/src/bdc/compile-bdc-to-wire.ts +52 -1
- package/src/bdc/components/discover.tsx +5 -1
- package/src/bdc/components/list.test.ts +36 -0
- package/src/bdc/components/list.tsx +62 -0
- package/src/bdc/components/media.test.ts +268 -0
- package/src/bdc/components/media.tsx +275 -0
- package/src/bdc/components/native-blocks.tsx +7 -1
- package/src/bdc/components/ui.tsx +25 -0
- package/src/bdc/index.ts +5 -0
- package/src/catalog/block-types.ts +5 -0
- package/src/renderer/SduiTemplateRow.tsx +63 -0
- package/src/renderer/blocks/SduiContentBlocks.tsx +198 -0
- package/src/renderer/blocks/SduiDiscoverBlocks.tsx +37 -2
- package/src/renderer/blocks/SduiMediaBlocks.tsx +348 -0
- package/src/renderer/blocks/SduiNativeDomainBlocks.tsx +2 -0
- package/src/renderer/index.ts +48 -0
- package/src/renderer/lib/sdui-media-block-props.ts +282 -0
- package/src/renderer/lib/sdui-media-blocks.test.ts +279 -0
- package/src/renderer/lib/sdui-media-uri.ts +39 -0
- package/src/renderer/lib/sdui-row-template.test.ts +190 -0
- package/src/renderer/lib/sdui-row-template.ts +153 -0
- package/src/renderer/sdui-component-registry.tsx +16 -0
- package/src/renderer/sdui-hybrid-renderer-context.tsx +2 -0
- package/src/renderer/sdui-media-resolver-context.tsx +84 -0
- package/src/renderer/sdui-video-player-context.tsx +55 -0
- package/src/renderer/ui/Chip.tsx +15 -2
- package/src/renderer/ui/Skeleton.tsx +13 -4
- package/src/renderer/ui/index.ts +1 -0
- package/src/renderer/ui/use-reduce-motion.ts +31 -0
|
@@ -13,7 +13,7 @@ import type { SduiBlock } from '@proulr/sdui-contract';
|
|
|
13
13
|
import { TextField as UiTextField } from '../ui/TextField';
|
|
14
14
|
import { Chip } from '../ui/Chip';
|
|
15
15
|
import { ChipRow } from '../ui/ChipRow';
|
|
16
|
-
import { AppIcon } from '../ui/app-icon';
|
|
16
|
+
import { AppIcon, isAppIconName } from '../ui/app-icon';
|
|
17
17
|
import { colors, fonts, gradients, m3Colors, m3Elevation, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
18
18
|
import { resolveDataRef, resolveDataRefString } from '../lib/resolve-data-ref';
|
|
19
19
|
import { useSduiHybridRenderer } from '../sdui-hybrid-renderer-context';
|
|
@@ -280,6 +280,7 @@ export function SduiDiscoverGridBlock({ block, data, onAction }: SduiBlockRender
|
|
|
280
280
|
export function SduiChipBlock({ block, onAction }: SduiBlockRenderProps) {
|
|
281
281
|
const label = readStringProp(block, 'label') ?? '';
|
|
282
282
|
const glyph = readStringProp(block, 'glyph');
|
|
283
|
+
const tone = readStringProp(block, 'tone');
|
|
283
284
|
const actionId = readStringProp(block, 'action_id');
|
|
284
285
|
const actionParams = readStringRecordProp(block, 'action_params');
|
|
285
286
|
const active = readBooleanProp(block, 'active');
|
|
@@ -288,6 +289,7 @@ export function SduiChipBlock({ block, onAction }: SduiBlockRenderProps) {
|
|
|
288
289
|
<Chip
|
|
289
290
|
label={label}
|
|
290
291
|
icon={glyph}
|
|
292
|
+
tone={tone === 'secondary' || tone === 'primary' ? tone : undefined}
|
|
291
293
|
active={active}
|
|
292
294
|
disabled={!actionId}
|
|
293
295
|
onPress={() => actionId && onAction(actionId, actionParams)}
|
|
@@ -295,7 +297,40 @@ export function SduiChipBlock({ block, onAction }: SduiBlockRenderProps) {
|
|
|
295
297
|
);
|
|
296
298
|
}
|
|
297
299
|
|
|
298
|
-
export function SduiChipRowBlock({ children }: SduiBlockRenderProps) {
|
|
300
|
+
export function SduiChipRowBlock({ block, data, children, onAction }: SduiBlockRenderProps) {
|
|
301
|
+
const itemsRef = readStringProp(block, 'items_ref');
|
|
302
|
+
if (itemsRef) {
|
|
303
|
+
const raw = resolveDataRef(data, itemsRef);
|
|
304
|
+
const items = Array.isArray(raw) ? raw : [];
|
|
305
|
+
return (
|
|
306
|
+
<ChipRow>
|
|
307
|
+
{items.map((item, index) => {
|
|
308
|
+
if (!item || typeof item !== 'object') return null;
|
|
309
|
+
const row = item as Record<string, unknown>;
|
|
310
|
+
const label = typeof row.label === 'string' ? row.label : '';
|
|
311
|
+
if (!label) return null;
|
|
312
|
+
const glyph = typeof row.glyph === 'string' && isAppIconName(row.glyph) ? row.glyph : undefined;
|
|
313
|
+
const toneRaw = row.tone;
|
|
314
|
+
const tone = toneRaw === 'secondary' || toneRaw === 'primary' ? toneRaw : undefined;
|
|
315
|
+
const actionId = typeof row.action_id === 'string' ? row.action_id : undefined;
|
|
316
|
+
const actionParams =
|
|
317
|
+
row.action_params && typeof row.action_params === 'object'
|
|
318
|
+
? (row.action_params as Record<string, string>)
|
|
319
|
+
: undefined;
|
|
320
|
+
return (
|
|
321
|
+
<Chip
|
|
322
|
+
key={`${label}-${index}`}
|
|
323
|
+
label={label}
|
|
324
|
+
icon={glyph}
|
|
325
|
+
tone={tone}
|
|
326
|
+
disabled={!actionId}
|
|
327
|
+
onPress={() => actionId && onAction(actionId, actionParams)}
|
|
328
|
+
/>
|
|
329
|
+
);
|
|
330
|
+
})}
|
|
331
|
+
</ChipRow>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
299
334
|
return <ChipRow>{children}</ChipRow>;
|
|
300
335
|
}
|
|
301
336
|
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Image, Modal, Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
computeSduiMediaFrame,
|
|
5
|
+
deriveSduiInitials,
|
|
6
|
+
parseSduiAvatarProps,
|
|
7
|
+
parseSduiContentImageProps,
|
|
8
|
+
parseSduiIconButtonProps,
|
|
9
|
+
parseSduiRowOverflowMenuProps,
|
|
10
|
+
parseSduiVideoProps,
|
|
11
|
+
} from '../lib/sdui-media-block-props';
|
|
12
|
+
import { useSduiMediaResolver, useSduiMediaUri } from '../sdui-media-resolver-context';
|
|
13
|
+
import { useSduiVideoPlayer } from '../sdui-video-player-context';
|
|
14
|
+
import { AppIcon } from '../ui/app-icon';
|
|
15
|
+
import { APP_ICON_SIZE } from '../ui/app-icon-size-catalog';
|
|
16
|
+
import { colors, fonts, m3Colors, m3Elevation, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
17
|
+
import { resolveViewStyleProps } from '../theme/resolve-style-props';
|
|
18
|
+
import type { SduiBlockRenderProps } from './SduiPrimitiveBlocks';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Blocos genéricos de mídia/estado para row templates e telas BFF-authored:
|
|
22
|
+
* `content_image`, `video`, `avatar`, `icon_button`, `row_overflow_menu`.
|
|
23
|
+
* Layout 100% no wire; path de mídia
|
|
24
|
+
* 100% em dados (`{{item.*}}`); resolução path → URL é capability do app via
|
|
25
|
+
* `SduiMediaResolver`. Zero hex aqui — só tokens do tema.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
function cornerPx(token: keyof typeof m3Shape | undefined): number | undefined {
|
|
29
|
+
return token ? m3Shape[token] : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function m3Color(token: string | undefined, fallback: string): string {
|
|
33
|
+
const value = token ? m3Colors[token as keyof typeof m3Colors] : undefined;
|
|
34
|
+
return value ?? fallback;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function SduiContentImageBlock({ block }: SduiBlockRenderProps) {
|
|
38
|
+
const parsed = parseSduiContentImageProps(block.props);
|
|
39
|
+
const resolver = useSduiMediaResolver();
|
|
40
|
+
const { uri, settled } = useSduiMediaUri(parsed.path);
|
|
41
|
+
|
|
42
|
+
// Capability ausente ou path vazio (binding não resolvido) → renderizar nada.
|
|
43
|
+
// Nunca cair em path/URL de produto hardcoded.
|
|
44
|
+
if (!resolver || !parsed.path) return null;
|
|
45
|
+
if (settled && !uri) return null;
|
|
46
|
+
|
|
47
|
+
const frame = computeSduiMediaFrame(parsed.width, parsed.height, parsed.aspectRatio);
|
|
48
|
+
const containerStyle = [
|
|
49
|
+
styles.mediaShell,
|
|
50
|
+
{
|
|
51
|
+
width: frame.width,
|
|
52
|
+
height: frame.height,
|
|
53
|
+
aspectRatio: frame.aspectRatio,
|
|
54
|
+
...(frame.stretch ? { alignSelf: 'stretch' as const } : null),
|
|
55
|
+
borderRadius: cornerPx(parsed.corner ?? 'cornerMedium'),
|
|
56
|
+
},
|
|
57
|
+
resolveViewStyleProps(block.props.style as Record<string, unknown>),
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
if (!uri) {
|
|
61
|
+
// Placeholder de tamanho estável (mesmo frame do futuro image) — anti-flicker.
|
|
62
|
+
return <View style={containerStyle} testID={parsed.testId} />;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const fillBehind = parsed.blurFill && parsed.fit === 'contain';
|
|
66
|
+
return (
|
|
67
|
+
<View style={containerStyle} testID={parsed.testId}>
|
|
68
|
+
{fillBehind ? (
|
|
69
|
+
<>
|
|
70
|
+
<Image source={{ uri }} resizeMode="cover" style={StyleSheet.absoluteFill} />
|
|
71
|
+
<View style={[StyleSheet.absoluteFill, styles.blurVeil]} pointerEvents="none" />
|
|
72
|
+
</>
|
|
73
|
+
) : null}
|
|
74
|
+
<Image
|
|
75
|
+
source={{ uri }}
|
|
76
|
+
resizeMode={parsed.fit === 'contain' ? 'contain' : 'cover'}
|
|
77
|
+
style={StyleSheet.absoluteFill}
|
|
78
|
+
/>
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function SduiVideoBlock({ block, data, onAction }: SduiBlockRenderProps) {
|
|
84
|
+
const parsed = parseSduiVideoProps(block.props);
|
|
85
|
+
const resolver = useSduiMediaResolver();
|
|
86
|
+
const player = useSduiVideoPlayer();
|
|
87
|
+
const { uri, pending, settled } = useSduiMediaUri(parsed.path);
|
|
88
|
+
const { uri: posterUri } = useSduiMediaUri(parsed.posterPath ?? '');
|
|
89
|
+
|
|
90
|
+
// Capability ausente ou path vazio (binding não resolvido) → renderizar nada.
|
|
91
|
+
if (!resolver || !parsed.path) return null;
|
|
92
|
+
|
|
93
|
+
// Slot do app (expo-video etc.): a lib só entrega URI resolvida + frame do wire.
|
|
94
|
+
if (player) {
|
|
95
|
+
return <>{player({ block, data, onAction, parsed, uri, pending, settled, posterUri })}</>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Default sem player: FRAME com poster/still (ou placeholder estável com
|
|
99
|
+
// glyph `play`). Anti-flicker: mesmo frame do wire em todos os estados.
|
|
100
|
+
const frame = computeSduiMediaFrame(parsed.width, parsed.height, parsed.aspectRatio);
|
|
101
|
+
const containerStyle = [
|
|
102
|
+
styles.mediaShell,
|
|
103
|
+
{
|
|
104
|
+
width: frame.width,
|
|
105
|
+
height: frame.height,
|
|
106
|
+
aspectRatio: frame.aspectRatio,
|
|
107
|
+
...(frame.stretch ? { alignSelf: 'stretch' as const } : null),
|
|
108
|
+
borderRadius: cornerPx(parsed.corner ?? 'cornerMedium'),
|
|
109
|
+
},
|
|
110
|
+
resolveViewStyleProps(block.props.style as Record<string, unknown>),
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
if (settled && !uri && !posterUri) return null;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<View style={containerStyle} testID={parsed.testId}>
|
|
117
|
+
{posterUri ? (
|
|
118
|
+
<Image
|
|
119
|
+
source={{ uri: posterUri }}
|
|
120
|
+
resizeMode={parsed.fit === 'contain' ? 'contain' : 'cover'}
|
|
121
|
+
style={StyleSheet.absoluteFill}
|
|
122
|
+
/>
|
|
123
|
+
) : null}
|
|
124
|
+
<View style={[StyleSheet.absoluteFill, styles.videoStillCenter]} pointerEvents="none">
|
|
125
|
+
<AppIcon name="play" size={APP_ICON_SIZE.fab} color={m3Colors.onSurface} />
|
|
126
|
+
</View>
|
|
127
|
+
</View>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function SduiRowOverflowMenuBlock({ block, onAction }: SduiBlockRenderProps) {
|
|
132
|
+
const parsed = parseSduiRowOverflowMenuProps(block.props);
|
|
133
|
+
const [open, setOpen] = useState(false);
|
|
134
|
+
|
|
135
|
+
// Sem entradas válidas → o trigger não abriria nada; não renderiza.
|
|
136
|
+
if (parsed.actions.length === 0) return null;
|
|
137
|
+
|
|
138
|
+
const pressAction = (actionId: string) => {
|
|
139
|
+
setOpen(false);
|
|
140
|
+
onAction(actionId);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
<Pressable
|
|
146
|
+
onPress={() => setOpen(true)}
|
|
147
|
+
accessibilityRole="button"
|
|
148
|
+
testID={parsed.testId}
|
|
149
|
+
style={({ pressed }) => [
|
|
150
|
+
styles.iconButton,
|
|
151
|
+
pressed && styles.iconButtonPressed,
|
|
152
|
+
resolveViewStyleProps(block.props.style as Record<string, unknown>),
|
|
153
|
+
]}
|
|
154
|
+
>
|
|
155
|
+
<AppIcon
|
|
156
|
+
name={parsed.triggerIcon}
|
|
157
|
+
size={APP_ICON_SIZE.feedAction}
|
|
158
|
+
color={m3Colors.onSurfaceVariant}
|
|
159
|
+
/>
|
|
160
|
+
</Pressable>
|
|
161
|
+
|
|
162
|
+
<Modal visible={open} transparent animationType="fade" onRequestClose={() => setOpen(false)}>
|
|
163
|
+
<View style={styles.overflowOverlay}>
|
|
164
|
+
<Pressable style={StyleSheet.absoluteFill} onPress={() => setOpen(false)} />
|
|
165
|
+
<View style={styles.overflowMenu}>
|
|
166
|
+
{parsed.actions.map((action) => (
|
|
167
|
+
<Pressable
|
|
168
|
+
key={action.actionId}
|
|
169
|
+
onPress={() => pressAction(action.actionId)}
|
|
170
|
+
accessibilityRole="button"
|
|
171
|
+
accessibilityLabel={action.label}
|
|
172
|
+
style={({ pressed }) => [
|
|
173
|
+
styles.overflowMenuItem,
|
|
174
|
+
pressed && styles.overflowMenuItemPressed,
|
|
175
|
+
]}
|
|
176
|
+
>
|
|
177
|
+
{action.icon ? (
|
|
178
|
+
<AppIcon
|
|
179
|
+
name={action.icon}
|
|
180
|
+
size={APP_ICON_SIZE.feedAction}
|
|
181
|
+
color={action.destructive ? m3Colors.error : m3Colors.onSurfaceVariant}
|
|
182
|
+
/>
|
|
183
|
+
) : null}
|
|
184
|
+
<Text
|
|
185
|
+
style={[
|
|
186
|
+
styles.overflowMenuLabel,
|
|
187
|
+
action.destructive && { color: m3Colors.error },
|
|
188
|
+
]}
|
|
189
|
+
>
|
|
190
|
+
{action.label}
|
|
191
|
+
</Text>
|
|
192
|
+
</Pressable>
|
|
193
|
+
))}
|
|
194
|
+
</View>
|
|
195
|
+
</View>
|
|
196
|
+
</Modal>
|
|
197
|
+
</>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function SduiAvatarBlock({ block }: SduiBlockRenderProps) {
|
|
202
|
+
const parsed = parseSduiAvatarProps(block.props);
|
|
203
|
+
const { uri } = useSduiMediaUri(parsed.path);
|
|
204
|
+
const initials = (parsed.initials?.trim() || deriveSduiInitials(parsed.name)).slice(0, 2);
|
|
205
|
+
const sizeStyle = {
|
|
206
|
+
width: parsed.sizePx,
|
|
207
|
+
height: parsed.sizePx,
|
|
208
|
+
borderRadius: cornerPx(parsed.corner),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if (parsed.path && uri) {
|
|
212
|
+
return (
|
|
213
|
+
<View style={[styles.avatarShell, sizeStyle]} testID={parsed.testId}>
|
|
214
|
+
<Image source={{ uri }} style={StyleSheet.absoluteFill} resizeMode="cover" />
|
|
215
|
+
</View>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Sem foto resolvível (path vazio, sem resolver, pendente sem iniciais?) →
|
|
220
|
+
// placeholder genérico: iniciais derivadas de dados ou glyph do catalog.
|
|
221
|
+
// Nunca um path de produto fallback.
|
|
222
|
+
if (initials) {
|
|
223
|
+
return (
|
|
224
|
+
<View
|
|
225
|
+
style={[styles.avatarShell, styles.avatarInitials, sizeStyle]}
|
|
226
|
+
testID={parsed.testId}
|
|
227
|
+
accessibilityLabel={parsed.name}
|
|
228
|
+
>
|
|
229
|
+
<Text
|
|
230
|
+
style={[
|
|
231
|
+
styles.avatarInitialsText,
|
|
232
|
+
{ fontSize: Math.max(10, Math.round(parsed.sizePx * 0.36)) },
|
|
233
|
+
]}
|
|
234
|
+
>
|
|
235
|
+
{initials}
|
|
236
|
+
</Text>
|
|
237
|
+
</View>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<View style={[styles.avatarShell, styles.avatarFallback, sizeStyle]} testID={parsed.testId}>
|
|
243
|
+
<AppIcon name="profile" size={Math.round(parsed.sizePx * 0.55)} color={m3Colors.onSurfaceVariant} />
|
|
244
|
+
</View>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function SduiIconButtonBlock({ block, onAction }: SduiBlockRenderProps) {
|
|
249
|
+
const parsed = parseSduiIconButtonProps(block.props);
|
|
250
|
+
if (!parsed.icon) return null;
|
|
251
|
+
|
|
252
|
+
const glyphSize = parsed.size ?? APP_ICON_SIZE.feedAction;
|
|
253
|
+
const name = parsed.selected && parsed.selectedIcon ? parsed.selectedIcon : parsed.icon;
|
|
254
|
+
const tint = parsed.selected
|
|
255
|
+
? m3Color(parsed.selectedColor, m3Colors.primary)
|
|
256
|
+
: m3Color(parsed.color, m3Colors.onSurfaceVariant);
|
|
257
|
+
const pressableSize = glyphSize + spacing.xs * 2;
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<Pressable
|
|
261
|
+
onPress={() => parsed.actionId && onAction(parsed.actionId)}
|
|
262
|
+
disabled={parsed.disabled || !parsed.actionId}
|
|
263
|
+
accessibilityRole="button"
|
|
264
|
+
accessibilityLabel={parsed.label}
|
|
265
|
+
accessibilityState={{ disabled: parsed.disabled, selected: parsed.selected }}
|
|
266
|
+
testID={parsed.testId}
|
|
267
|
+
style={({ pressed }) => [
|
|
268
|
+
styles.iconButton,
|
|
269
|
+
{ width: pressableSize, height: pressableSize },
|
|
270
|
+
pressed && styles.iconButtonPressed,
|
|
271
|
+
parsed.disabled && styles.iconButtonDisabled,
|
|
272
|
+
resolveViewStyleProps(block.props.style as Record<string, unknown>),
|
|
273
|
+
]}
|
|
274
|
+
>
|
|
275
|
+
<AppIcon name={name} size={glyphSize} color={tint} />
|
|
276
|
+
</Pressable>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const styles = StyleSheet.create({
|
|
281
|
+
mediaShell: {
|
|
282
|
+
overflow: 'hidden',
|
|
283
|
+
backgroundColor: m3Colors.surfaceContainerHigh,
|
|
284
|
+
},
|
|
285
|
+
/** Véu OLED sobre o cover de trás no blur-fill (o app pode trocar pelo
|
|
286
|
+
* MediaBlurFillImage canônico com blur real; a estrutura é a mesma). */
|
|
287
|
+
blurVeil: {
|
|
288
|
+
backgroundColor: colors.blackAlpha62,
|
|
289
|
+
},
|
|
290
|
+
avatarShell: {
|
|
291
|
+
overflow: 'hidden',
|
|
292
|
+
alignItems: 'center',
|
|
293
|
+
justifyContent: 'center',
|
|
294
|
+
backgroundColor: m3Colors.surfaceContainerHigh,
|
|
295
|
+
},
|
|
296
|
+
avatarInitials: {
|
|
297
|
+
backgroundColor: m3Colors.secondaryContainer,
|
|
298
|
+
},
|
|
299
|
+
avatarInitialsText: {
|
|
300
|
+
...m3Type.labelSmall,
|
|
301
|
+
fontFamily: fonts.bold,
|
|
302
|
+
color: m3Colors.onSecondaryContainer,
|
|
303
|
+
},
|
|
304
|
+
avatarFallback: {
|
|
305
|
+
backgroundColor: m3Colors.surfaceContainerHighest,
|
|
306
|
+
},
|
|
307
|
+
iconButton: {
|
|
308
|
+
alignItems: 'center',
|
|
309
|
+
justifyContent: 'center',
|
|
310
|
+
},
|
|
311
|
+
iconButtonPressed: {
|
|
312
|
+
backgroundColor: colors.whiteAlpha12,
|
|
313
|
+
borderRadius: m3Shape.cornerFull,
|
|
314
|
+
},
|
|
315
|
+
iconButtonDisabled: {
|
|
316
|
+
opacity: 0.4,
|
|
317
|
+
},
|
|
318
|
+
videoStillCenter: {
|
|
319
|
+
alignItems: 'center',
|
|
320
|
+
justifyContent: 'center',
|
|
321
|
+
},
|
|
322
|
+
overflowOverlay: {
|
|
323
|
+
flex: 1,
|
|
324
|
+
backgroundColor: colors.blackAlpha62,
|
|
325
|
+
justifyContent: 'flex-end',
|
|
326
|
+
},
|
|
327
|
+
overflowMenu: {
|
|
328
|
+
margin: spacing.md,
|
|
329
|
+
paddingVertical: spacing.xs,
|
|
330
|
+
backgroundColor: m3Colors.surfaceContainerHigh,
|
|
331
|
+
borderRadius: m3Shape.cornerLarge,
|
|
332
|
+
...m3Elevation.level3,
|
|
333
|
+
},
|
|
334
|
+
overflowMenuItem: {
|
|
335
|
+
flexDirection: 'row',
|
|
336
|
+
alignItems: 'center',
|
|
337
|
+
gap: spacing.sm,
|
|
338
|
+
paddingHorizontal: spacing.md,
|
|
339
|
+
paddingVertical: spacing.sm,
|
|
340
|
+
},
|
|
341
|
+
overflowMenuItemPressed: {
|
|
342
|
+
backgroundColor: colors.whiteAlpha12,
|
|
343
|
+
},
|
|
344
|
+
overflowMenuLabel: {
|
|
345
|
+
...m3Type.body,
|
|
346
|
+
color: m3Colors.onSurface,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
@@ -14,6 +14,7 @@ function NativeBlockShell({
|
|
|
14
14
|
block,
|
|
15
15
|
data,
|
|
16
16
|
onAction,
|
|
17
|
+
children,
|
|
17
18
|
rendererKey,
|
|
18
19
|
fallbackLabel,
|
|
19
20
|
}: SduiBlockRenderProps & {
|
|
@@ -53,6 +54,7 @@ function NativeBlockShell({
|
|
|
53
54
|
block,
|
|
54
55
|
data,
|
|
55
56
|
onAction,
|
|
57
|
+
children,
|
|
56
58
|
dataRef: readStringProp(block, 'data_ref'),
|
|
57
59
|
conversationIdRef: readStringProp(block, 'conversation_id_ref'),
|
|
58
60
|
peerIdRef: readStringProp(block, 'peer_id_ref'),
|
package/src/renderer/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export { SduiScreenRenderer } from './SduiScreenRenderer';
|
|
2
|
+
export { SduiTemplateRow } from './SduiTemplateRow';
|
|
3
|
+
export {
|
|
4
|
+
materializeRowTemplate,
|
|
5
|
+
readSduiRowTemplate,
|
|
6
|
+
readSduiRowTemplates,
|
|
7
|
+
resolveRowTemplateBindings,
|
|
8
|
+
} from './lib/sdui-row-template';
|
|
9
|
+
export type { SduiRowTemplateSource } from './lib/sdui-row-template';
|
|
2
10
|
export {
|
|
3
11
|
resolveSduiBlockComponent,
|
|
4
12
|
registerSduiBlockComponent,
|
|
@@ -9,6 +17,46 @@ export {
|
|
|
9
17
|
SduiHybridRendererProvider,
|
|
10
18
|
useSduiHybridRenderer,
|
|
11
19
|
} from './sdui-hybrid-renderer-context';
|
|
20
|
+
export {
|
|
21
|
+
SduiMediaResolverProvider,
|
|
22
|
+
useSduiMediaResolver,
|
|
23
|
+
useSduiMediaUri,
|
|
24
|
+
} from './sdui-media-resolver-context';
|
|
25
|
+
export { invokeSduiMediaResolver } from './lib/sdui-media-uri';
|
|
26
|
+
export type { SduiMediaResolver } from './lib/sdui-media-uri';
|
|
27
|
+
export {
|
|
28
|
+
SDUI_MEDIA_ASPECT_RATIOS,
|
|
29
|
+
SDUI_AVATAR_SIZES,
|
|
30
|
+
SDUI_ROW_OVERFLOW_DEFAULT_ICON,
|
|
31
|
+
computeSduiMediaFrame,
|
|
32
|
+
deriveSduiInitials,
|
|
33
|
+
parseSduiAvatarProps,
|
|
34
|
+
parseSduiContentImageProps,
|
|
35
|
+
parseSduiIconButtonProps,
|
|
36
|
+
parseSduiRowOverflowMenuProps,
|
|
37
|
+
parseSduiVideoProps,
|
|
38
|
+
} from './lib/sdui-media-block-props';
|
|
39
|
+
export type {
|
|
40
|
+
SduiAvatarParsed,
|
|
41
|
+
SduiContentImageParsed,
|
|
42
|
+
SduiIconButtonParsed,
|
|
43
|
+
SduiMediaFrame,
|
|
44
|
+
SduiRowOverflowMenuActionParsed,
|
|
45
|
+
SduiRowOverflowMenuParsed,
|
|
46
|
+
SduiVideoParsed,
|
|
47
|
+
} from './lib/sdui-media-block-props';
|
|
48
|
+
export {
|
|
49
|
+
SduiAvatarBlock,
|
|
50
|
+
SduiContentImageBlock,
|
|
51
|
+
SduiIconButtonBlock,
|
|
52
|
+
SduiRowOverflowMenuBlock,
|
|
53
|
+
SduiVideoBlock,
|
|
54
|
+
} from './blocks/SduiMediaBlocks';
|
|
55
|
+
export {
|
|
56
|
+
SduiVideoPlayerProvider,
|
|
57
|
+
useSduiVideoPlayer,
|
|
58
|
+
} from './sdui-video-player-context';
|
|
59
|
+
export type { SduiVideoPlayer, SduiVideoPlayerProps } from './sdui-video-player-context';
|
|
12
60
|
export {
|
|
13
61
|
SduiChromeScrollProvider,
|
|
14
62
|
useSduiChromeScroll,
|