@proulr/sdui-components 0.1.90 → 0.1.91
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 +1 -1
- package/src/bdc/compile-bdc-to-wire.test.ts +11 -0
- package/src/bdc/components/bdc-components.test.ts +9 -0
- package/src/bdc/components/discover.tsx +2 -0
- package/src/bdc/components/ui.tsx +9 -1
- package/src/renderer/blocks/SduiContentBlocks.tsx +111 -10
- package/src/renderer/blocks/SduiDiscoverBlocks.tsx +4 -3
- package/src/renderer/blocks/SduiPrimitiveBlocks.tsx +22 -3
- package/src/renderer/theme/resolve-style-props.ts +2 -0
- package/src/renderer/ui/Chip.tsx +11 -7
- package/src/renderer/ui/ChipRow.tsx +14 -1
package/package.json
CHANGED
|
@@ -41,6 +41,17 @@ describe('compileBdcToWire', () => {
|
|
|
41
41
|
expect(wire.blocks[0].props).toMatchObject({ action_id: 'go', label: 'Go' });
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
it('keeps grid/wrap layout props of content blocks on the wire', () => {
|
|
45
|
+
const wire = compileBdcToWire(
|
|
46
|
+
screen({ id: 'demo.home' }, [
|
|
47
|
+
{ type: 'stat_tile_row', props: { itemsRef: 'content.health', columns: 2, align: 'start' }, children: [] },
|
|
48
|
+
{ type: 'chip_row', props: { itemsRef: 'content.tribes', layout: 'wrap' }, children: [] },
|
|
49
|
+
]),
|
|
50
|
+
);
|
|
51
|
+
expect(wire.blocks[0].props).toMatchObject({ items_ref: 'content.health', columns: 2, align: 'start' });
|
|
52
|
+
expect(wire.blocks[1].props).toMatchObject({ items_ref: 'content.tribes', layout: 'wrap' });
|
|
53
|
+
});
|
|
54
|
+
|
|
44
55
|
it('rejects non-screen roots', () => {
|
|
45
56
|
expect(() =>
|
|
46
57
|
compileBdcToWire({ type: 'stack', props: {}, children: [] }),
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
Scroll,
|
|
56
56
|
SectionHeader,
|
|
57
57
|
Spacer,
|
|
58
|
+
StatTileRow,
|
|
58
59
|
Text,
|
|
59
60
|
ToggleRow,
|
|
60
61
|
screenProps,
|
|
@@ -75,6 +76,10 @@ describe('bdc ui components', () => {
|
|
|
75
76
|
expect(SectionHeader({ title: 'T' }).type).toBe('section_header');
|
|
76
77
|
expect(ListItem({ title: 'Item' }).type).toBe('list_item');
|
|
77
78
|
expect(ToggleRow({ label: 'On' }).type).toBe('toggle_row');
|
|
79
|
+
expect(StatTileRow({ itemsRef: 'content.health', columns: 2, align: 'start' })).toMatchObject({
|
|
80
|
+
type: 'stat_tile_row',
|
|
81
|
+
props: { itemsRef: 'content.health', columns: 2, align: 'start' },
|
|
82
|
+
});
|
|
78
83
|
expect(screenProps({ id: 'x' })).toEqual({ id: 'x' });
|
|
79
84
|
});
|
|
80
85
|
});
|
|
@@ -83,6 +88,10 @@ describe('bdc discover/forms/social/inbox/native', () => {
|
|
|
83
88
|
it('builds discover and form blocks', () => {
|
|
84
89
|
expect(Chip({ label: 'All' }).type).toBe('chip');
|
|
85
90
|
expect(ChipRow({}).type).toBe('chip_row');
|
|
91
|
+
expect(ChipRow({ itemsRef: 'content.tribes', layout: 'wrap' }).props).toMatchObject({
|
|
92
|
+
itemsRef: 'content.tribes',
|
|
93
|
+
layout: 'wrap',
|
|
94
|
+
});
|
|
86
95
|
expect(DiscoverGrid({ itemsRef: 'items' }).type).toBe('discover_grid');
|
|
87
96
|
expect(DiscoverProfileCard({ profileRef: 'p' }).type).toBe('discover_profile_card');
|
|
88
97
|
expect(DiscoverMap({}).type).toBe('discover_map');
|
|
@@ -25,6 +25,8 @@ export const Chip: BdcComponent = (props) => layoutBlock('chip', props as ChipPr
|
|
|
25
25
|
type ChipRowProps = LayoutProps & {
|
|
26
26
|
/** Caminho em `screen.data` para chips dinâmicos: `{ label, glyph?, tone?, actionId?, actionParams? }[]`. */
|
|
27
27
|
itemsRef?: string;
|
|
28
|
+
/** `wrap` quebra em linhas (todas visíveis); padrão rola na horizontal. */
|
|
29
|
+
layout?: 'scroll' | 'wrap';
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
export const ChipRow: BdcComponent = (props) => layoutBlock('chip_row', props as ChipRowProps);
|
|
@@ -11,6 +11,8 @@ type LayoutProps = {
|
|
|
11
11
|
gap?: number | string;
|
|
12
12
|
padding?: number | string;
|
|
13
13
|
height?: number;
|
|
14
|
+
/** Spacer: ocupa o espaço livre do Row/Column pai (em vez de `height`). */
|
|
15
|
+
flex?: number;
|
|
14
16
|
variant?: string;
|
|
15
17
|
style?: StyleWire;
|
|
16
18
|
children?: BdcElement[];
|
|
@@ -37,6 +39,8 @@ export const Spacer: BdcComponent = (props) => layoutBlock('spacer', props as La
|
|
|
37
39
|
type TextProps = LayoutProps & {
|
|
38
40
|
text?: string;
|
|
39
41
|
variant?: 'headline' | 'title' | 'body' | 'label';
|
|
42
|
+
/** Torna o texto tocável (link) sem o min-height de um Button. */
|
|
43
|
+
actionId?: string;
|
|
40
44
|
};
|
|
41
45
|
|
|
42
46
|
export const Text: BdcComponent = (props) => layoutBlock('text', props as TextProps);
|
|
@@ -97,8 +101,12 @@ export const SectionHeader: BdcComponent = (props) =>
|
|
|
97
101
|
layoutBlock('section_header', props as SectionHeaderProps);
|
|
98
102
|
|
|
99
103
|
type StatTileRowProps = LayoutProps & {
|
|
100
|
-
/** Caminho em `screen.data` para o array de tiles: `{ icon, value, label }[]`. */
|
|
104
|
+
/** Caminho em `screen.data` para o array de tiles: `{ icon, value, label, action_id?, action_params? }[]`. */
|
|
101
105
|
itemsRef?: string;
|
|
106
|
+
/** Tiles por linha (grade); padrão = todos numa linha só. */
|
|
107
|
+
columns?: number;
|
|
108
|
+
/** `start` = card alinhado à esquerda (valor em até 2 linhas); padrão centralizado. */
|
|
109
|
+
align?: 'center' | 'start';
|
|
102
110
|
};
|
|
103
111
|
|
|
104
112
|
/** Linha genérica de tiles de métrica (ícone + valor + rótulo). Reusável em qualquer tela. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StyleSheet, Text, View } from 'react-native';
|
|
1
|
+
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
2
2
|
import { fonts, m3Colors, m3Shape, m3Type, spacing } from '../theme/proulr-theme';
|
|
3
3
|
import { AppIcon } from '../ui/app-icon';
|
|
4
4
|
import { isAppIconName, type AppIconName } from '../../catalog/app-icon-names';
|
|
@@ -33,24 +33,100 @@ function toIcon(value: unknown): AppIconName | undefined {
|
|
|
33
33
|
|
|
34
34
|
/* ── stat_tile_row ─────────────────────────────────────────────────────────── */
|
|
35
35
|
|
|
36
|
-
type StatTile = {
|
|
36
|
+
type StatTile = {
|
|
37
|
+
icon?: AppIconName;
|
|
38
|
+
value: string;
|
|
39
|
+
label: string;
|
|
40
|
+
actionId?: string;
|
|
41
|
+
actionParams?: Record<string, string>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function chunk<T>(items: T[], size: number): T[][] {
|
|
45
|
+
const rows: T[][] = [];
|
|
46
|
+
for (let i = 0; i < items.length; i += size) rows.push(items.slice(i, i + size));
|
|
47
|
+
return rows;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function StatTileView({
|
|
51
|
+
tile,
|
|
52
|
+
card,
|
|
53
|
+
onAction,
|
|
54
|
+
}: {
|
|
55
|
+
tile: StatTile;
|
|
56
|
+
card: boolean;
|
|
57
|
+
onAction: SduiBlockRenderProps['onAction'];
|
|
58
|
+
}) {
|
|
59
|
+
const content = card ? (
|
|
60
|
+
<>
|
|
61
|
+
<View style={styles.cardHead}>
|
|
62
|
+
{tile.icon ? (
|
|
63
|
+
<View style={styles.cardIcon}>
|
|
64
|
+
<AppIcon name={tile.icon} size={16} color={m3Colors.primary} />
|
|
65
|
+
</View>
|
|
66
|
+
) : null}
|
|
67
|
+
{tile.actionId ? <AppIcon name="arrow-up-right" size={16} color={m3Colors.onSurfaceVariant} /> : null}
|
|
68
|
+
</View>
|
|
69
|
+
<Text style={styles.cardValue} numberOfLines={2}>{tile.value}</Text>
|
|
70
|
+
<Text style={styles.label} numberOfLines={1}>{tile.label}</Text>
|
|
71
|
+
</>
|
|
72
|
+
) : (
|
|
73
|
+
<>
|
|
74
|
+
{tile.icon ? <AppIcon name={tile.icon} size={17} color={m3Colors.onSurfaceVariant} /> : null}
|
|
75
|
+
<Text style={styles.value} numberOfLines={1}>{tile.value}</Text>
|
|
76
|
+
<Text style={styles.label} numberOfLines={1}>{tile.label}</Text>
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
const tileStyle = card ? styles.card : styles.tile;
|
|
80
|
+
const actionId = tile.actionId;
|
|
81
|
+
if (!actionId) return <View style={tileStyle}>{content}</View>;
|
|
82
|
+
return (
|
|
83
|
+
<Pressable
|
|
84
|
+
accessibilityRole="link"
|
|
85
|
+
accessibilityLabel={`${tile.label} ${tile.value}`.trim()}
|
|
86
|
+
onPress={() => onAction(actionId, tile.actionParams)}
|
|
87
|
+
style={({ pressed }) => [tileStyle, card && styles.cardLink, pressed && styles.pressed]}
|
|
88
|
+
>
|
|
89
|
+
{content}
|
|
90
|
+
</Pressable>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
37
93
|
|
|
38
94
|
/** Tiles genéricos de métrica (ícone + valor + rótulo) — layout vem do wire, regra do BFF. */
|
|
39
|
-
export function SduiStatTileRowBlock({ block, data }: SduiBlockRenderProps) {
|
|
95
|
+
export function SduiStatTileRowBlock({ block, data, onAction }: SduiBlockRenderProps) {
|
|
40
96
|
const tiles: StatTile[] = itemsFrom(block, data, 'stats')
|
|
41
97
|
.map(asRecord)
|
|
42
98
|
.filter((row): row is Record<string, unknown> => row !== null)
|
|
43
|
-
.map((row) => ({
|
|
99
|
+
.map((row) => ({
|
|
100
|
+
icon: toIcon(row.icon),
|
|
101
|
+
value: toText(row.value),
|
|
102
|
+
label: toText(row.label),
|
|
103
|
+
actionId: typeof row.action_id === 'string' && row.action_id ? row.action_id : undefined,
|
|
104
|
+
actionParams:
|
|
105
|
+
row.action_params && typeof row.action_params === 'object'
|
|
106
|
+
? (row.action_params as Record<string, string>)
|
|
107
|
+
: undefined,
|
|
108
|
+
}))
|
|
44
109
|
.filter((tile) => tile.value || tile.label);
|
|
45
110
|
if (tiles.length === 0) return null;
|
|
46
111
|
|
|
112
|
+
const columnsProp = block.props.columns;
|
|
113
|
+
const columns =
|
|
114
|
+
typeof columnsProp === 'number' && Number.isInteger(columnsProp) && columnsProp > 0
|
|
115
|
+
? columnsProp
|
|
116
|
+
: tiles.length;
|
|
117
|
+
const card = block.props.align === 'start';
|
|
118
|
+
const rows = chunk(tiles, columns);
|
|
119
|
+
|
|
47
120
|
return (
|
|
48
|
-
<View style={styles.
|
|
49
|
-
{
|
|
50
|
-
<View key={
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
121
|
+
<View style={styles.grid} accessibilityRole="list">
|
|
122
|
+
{rows.map((row, rIndex) => (
|
|
123
|
+
<View key={`row-${rIndex}`} style={styles.row}>
|
|
124
|
+
{row.map((tile, index) => (
|
|
125
|
+
<StatTileView key={`${tile.label}-${index}`} tile={tile} card={card} onAction={onAction} />
|
|
126
|
+
))}
|
|
127
|
+
{Array.from({ length: columns - row.length }, (_, pad) => (
|
|
128
|
+
<View key={`pad-${pad}`} style={styles.pad} />
|
|
129
|
+
))}
|
|
54
130
|
</View>
|
|
55
131
|
))}
|
|
56
132
|
</View>
|
|
@@ -136,7 +212,9 @@ export function SduiVouchListBlock({ block, data }: SduiBlockRenderProps) {
|
|
|
136
212
|
}
|
|
137
213
|
|
|
138
214
|
const styles = StyleSheet.create({
|
|
215
|
+
grid: { gap: spacing.sm },
|
|
139
216
|
row: { flexDirection: 'row', gap: spacing.sm },
|
|
217
|
+
pad: { flex: 1 },
|
|
140
218
|
tile: {
|
|
141
219
|
flex: 1,
|
|
142
220
|
minHeight: 64,
|
|
@@ -152,6 +230,29 @@ const styles = StyleSheet.create({
|
|
|
152
230
|
},
|
|
153
231
|
value: { ...m3Type.title, color: m3Colors.onSurface, fontFamily: fonts.extrabold },
|
|
154
232
|
label: { ...m3Type.labelSmall, color: m3Colors.onSurfaceVariant },
|
|
233
|
+
card: {
|
|
234
|
+
flex: 1,
|
|
235
|
+
minWidth: 0,
|
|
236
|
+
minHeight: 92,
|
|
237
|
+
borderRadius: m3Shape.cornerMedium,
|
|
238
|
+
backgroundColor: m3Colors.surfaceContainerHigh,
|
|
239
|
+
borderWidth: 1,
|
|
240
|
+
borderColor: m3Colors.outlineVariant,
|
|
241
|
+
gap: spacing.xs,
|
|
242
|
+
padding: spacing.sm + 4,
|
|
243
|
+
},
|
|
244
|
+
cardLink: { borderColor: m3Colors.outlineInteractive },
|
|
245
|
+
cardHead: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
246
|
+
cardIcon: {
|
|
247
|
+
width: 28,
|
|
248
|
+
height: 28,
|
|
249
|
+
borderRadius: m3Shape.cornerSmall,
|
|
250
|
+
backgroundColor: m3Colors.surfaceContainerHighest,
|
|
251
|
+
alignItems: 'center',
|
|
252
|
+
justifyContent: 'center',
|
|
253
|
+
},
|
|
254
|
+
cardValue: { ...m3Type.body, color: m3Colors.onSurface, fontFamily: fonts.bold },
|
|
255
|
+
pressed: { opacity: 0.85 },
|
|
155
256
|
|
|
156
257
|
groups: { gap: spacing.md },
|
|
157
258
|
group: {
|
|
@@ -299,11 +299,12 @@ export function SduiChipBlock({ block, onAction }: SduiBlockRenderProps) {
|
|
|
299
299
|
|
|
300
300
|
export function SduiChipRowBlock({ block, data, children, onAction }: SduiBlockRenderProps) {
|
|
301
301
|
const itemsRef = readStringProp(block, 'items_ref');
|
|
302
|
+
const wrap = readStringProp(block, 'layout') === 'wrap';
|
|
302
303
|
if (itemsRef) {
|
|
303
304
|
const raw = resolveDataRef(data, itemsRef);
|
|
304
305
|
const items = Array.isArray(raw) ? raw : [];
|
|
305
306
|
return (
|
|
306
|
-
<ChipRow>
|
|
307
|
+
<ChipRow wrap={wrap}>
|
|
307
308
|
{items.map((item, index) => {
|
|
308
309
|
if (!item || typeof item !== 'object') return null;
|
|
309
310
|
const row = item as Record<string, unknown>;
|
|
@@ -323,7 +324,7 @@ export function SduiChipRowBlock({ block, data, children, onAction }: SduiBlockR
|
|
|
323
324
|
label={label}
|
|
324
325
|
icon={glyph}
|
|
325
326
|
tone={tone}
|
|
326
|
-
|
|
327
|
+
readOnly={!actionId}
|
|
327
328
|
onPress={() => actionId && onAction(actionId, actionParams)}
|
|
328
329
|
/>
|
|
329
330
|
);
|
|
@@ -331,7 +332,7 @@ export function SduiChipRowBlock({ block, data, children, onAction }: SduiBlockR
|
|
|
331
332
|
</ChipRow>
|
|
332
333
|
);
|
|
333
334
|
}
|
|
334
|
-
return <ChipRow>{children}</ChipRow>;
|
|
335
|
+
return <ChipRow wrap={wrap}>{children}</ChipRow>;
|
|
335
336
|
}
|
|
336
337
|
|
|
337
338
|
export function SduiTextFieldBlock({ block, data, onAction }: SduiBlockRenderProps) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Platform, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
2
|
+
import { Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import type { SduiBlock } from '@proulr/sdui-contract';
|
|
4
4
|
import { AppButton } from '../ui/AppButton';
|
|
5
5
|
import { AppIcon, isAppIconName } from '../ui/app-icon';
|
|
@@ -22,16 +22,18 @@ export type SduiBlockRenderProps = {
|
|
|
22
22
|
|
|
23
23
|
const textVariants = ['headline', 'title', 'body', 'label'] as const;
|
|
24
24
|
|
|
25
|
-
export function SduiTextBlock({ block }: SduiBlockRenderProps) {
|
|
25
|
+
export function SduiTextBlock({ block, onAction }: SduiBlockRenderProps) {
|
|
26
26
|
const text = typeof block.props.text === 'string' ? block.props.text : '';
|
|
27
27
|
const variant =
|
|
28
28
|
typeof block.props.variant === 'string' &&
|
|
29
29
|
(textVariants as readonly string[]).includes(block.props.variant)
|
|
30
30
|
? block.props.variant
|
|
31
31
|
: 'body';
|
|
32
|
+
const actionId = typeof block.props.action_id === 'string' ? block.props.action_id : '';
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
const label = (
|
|
34
35
|
<Text
|
|
36
|
+
numberOfLines={actionId ? 1 : undefined}
|
|
35
37
|
style={[
|
|
36
38
|
{ color: colors.onSurface },
|
|
37
39
|
resolveTextStyleProps(block.props.style as Record<string, unknown>, variant),
|
|
@@ -40,6 +42,19 @@ export function SduiTextBlock({ block }: SduiBlockRenderProps) {
|
|
|
40
42
|
{text}
|
|
41
43
|
</Text>
|
|
42
44
|
);
|
|
45
|
+
if (!actionId) return label;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Pressable
|
|
49
|
+
accessibilityRole="link"
|
|
50
|
+
accessibilityLabel={text}
|
|
51
|
+
hitSlop={spacing.xs}
|
|
52
|
+
onPress={() => onAction(actionId)}
|
|
53
|
+
style={styles.textLink}
|
|
54
|
+
>
|
|
55
|
+
{label}
|
|
56
|
+
</Pressable>
|
|
57
|
+
);
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
export function SduiColumnBlock({ block, children }: SduiBlockRenderProps) {
|
|
@@ -164,6 +179,9 @@ export function SduiIconBlock({ block }: SduiBlockRenderProps) {
|
|
|
164
179
|
}
|
|
165
180
|
|
|
166
181
|
export function SduiSpacerBlock({ block }: SduiBlockRenderProps) {
|
|
182
|
+
if (typeof block.props.flex === 'number') {
|
|
183
|
+
return <View style={{ flex: block.props.flex, minWidth: 0 }} />;
|
|
184
|
+
}
|
|
167
185
|
const height = typeof block.props.height === 'number' ? block.props.height : spacing.md;
|
|
168
186
|
return <View style={{ height }} />;
|
|
169
187
|
}
|
|
@@ -184,6 +202,7 @@ export function SduiUnknownBlock({ block }: SduiBlockRenderProps) {
|
|
|
184
202
|
|
|
185
203
|
const styles = StyleSheet.create({
|
|
186
204
|
scroll: { flex: 1, minHeight: 0 },
|
|
205
|
+
textLink: { flexShrink: 1, minWidth: 0 },
|
|
187
206
|
banner: {
|
|
188
207
|
padding: spacing.md,
|
|
189
208
|
backgroundColor: colors.surface,
|
|
@@ -59,6 +59,8 @@ export function resolveViewStyleProps(
|
|
|
59
59
|
borderColor: resolveColor(style.border_color),
|
|
60
60
|
opacity: style.opacity,
|
|
61
61
|
flex: style.flex,
|
|
62
|
+
// Sem isso um filho `flex` de Row não encolhe abaixo do conteúdo e estoura a linha.
|
|
63
|
+
minWidth: style.flex != null ? 0 : undefined,
|
|
62
64
|
alignItems: style.align_items,
|
|
63
65
|
};
|
|
64
66
|
}
|
package/src/renderer/ui/Chip.tsx
CHANGED
|
@@ -23,6 +23,7 @@ export function Chip({
|
|
|
23
23
|
disabled,
|
|
24
24
|
icon,
|
|
25
25
|
testID,
|
|
26
|
+
readOnly,
|
|
26
27
|
}: {
|
|
27
28
|
label: string;
|
|
28
29
|
active?: boolean;
|
|
@@ -32,6 +33,8 @@ export function Chip({
|
|
|
32
33
|
disabled?: boolean;
|
|
33
34
|
icon?: string;
|
|
34
35
|
testID?: string;
|
|
36
|
+
/** Tag de conteúdo sem ação: não é botão nem aparece esmaecida como desabilitada. */
|
|
37
|
+
readOnly?: boolean;
|
|
35
38
|
}) {
|
|
36
39
|
const toneColor = tone === 'secondary' ? m3Colors.secondary : m3Colors.primary;
|
|
37
40
|
const toneBorder = tone === 'secondary' ? m3Colors.secondaryContainer : m3Colors.primaryContainer;
|
|
@@ -41,7 +44,7 @@ export function Chip({
|
|
|
41
44
|
? { backgroundColor: toneBorder, borderColor: toneColor }
|
|
42
45
|
: { backgroundColor: colors.surfaceGlass, borderColor: colors.chromePillBorder };
|
|
43
46
|
|
|
44
|
-
const textColor = disabled
|
|
47
|
+
const textColor = disabled && !readOnly
|
|
45
48
|
? m3Colors.onDisabled
|
|
46
49
|
: active
|
|
47
50
|
? colors.headerStart
|
|
@@ -54,13 +57,14 @@ export function Chip({
|
|
|
54
57
|
return (
|
|
55
58
|
<Pressable
|
|
56
59
|
testID={testID}
|
|
57
|
-
accessibilityRole=
|
|
60
|
+
accessibilityRole={readOnly ? 'text' : 'button'}
|
|
58
61
|
accessibilityLabel={label}
|
|
59
|
-
accessibilityState={{ selected: Boolean(active), disabled: Boolean(disabled) }}
|
|
60
|
-
onPress={onPress}
|
|
61
|
-
onFocus={focus.onFocus}
|
|
62
|
-
onBlur={focus.onBlur}
|
|
63
|
-
|
|
62
|
+
accessibilityState={readOnly ? undefined : { selected: Boolean(active), disabled: Boolean(disabled) }}
|
|
63
|
+
onPress={readOnly ? undefined : onPress}
|
|
64
|
+
onFocus={readOnly ? undefined : focus.onFocus}
|
|
65
|
+
onBlur={readOnly ? undefined : focus.onBlur}
|
|
66
|
+
focusable={!readOnly}
|
|
67
|
+
disabled={disabled || readOnly}
|
|
64
68
|
hitSlop={CHIP_HIT_SLOP}
|
|
65
69
|
style={({ pressed }) => [
|
|
66
70
|
styles.chip,
|
|
@@ -4,17 +4,23 @@ import { spacing } from '../theme/proulr-theme';
|
|
|
4
4
|
|
|
5
5
|
type ChipRowProps = {
|
|
6
6
|
children: React.ReactNode;
|
|
7
|
+
/** Quebra em várias linhas (todas visíveis) em vez de rolar para o lado. */
|
|
8
|
+
wrap?: boolean;
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Linha horizontal de pílulas — a mesma fileira do Caçar, do hub do chat e de
|
|
11
13
|
* qualquer seletor de opções. O encaixe horizontal é do invólucro que chama.
|
|
12
14
|
*/
|
|
13
|
-
export function ChipRow({ children }: ChipRowProps) {
|
|
15
|
+
export function ChipRow({ children, wrap = false }: ChipRowProps) {
|
|
14
16
|
if (!children) {
|
|
15
17
|
return <View style={styles.content} />;
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
if (wrap) {
|
|
21
|
+
return <View style={styles.wrap}>{children}</View>;
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
return (
|
|
19
25
|
<ScrollView
|
|
20
26
|
horizontal
|
|
@@ -41,4 +47,11 @@ const styles = StyleSheet.create({
|
|
|
41
47
|
gap: spacing.xs,
|
|
42
48
|
paddingHorizontal: spacing.sm,
|
|
43
49
|
},
|
|
50
|
+
wrap: {
|
|
51
|
+
flexDirection: 'row',
|
|
52
|
+
flexWrap: 'wrap',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
alignSelf: 'stretch',
|
|
55
|
+
gap: spacing.xs,
|
|
56
|
+
},
|
|
44
57
|
});
|