@astratra/native-ui 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/LICENSE +21 -0
- package/README.md +370 -0
- package/package.json +64 -0
- package/src/components/Chevron.js +31 -0
- package/src/components/CollapsibleHeader.js +253 -0
- package/src/components/FloatingPagination.js +135 -0
- package/src/components/GlassButton.js +117 -0
- package/src/components/GlassSurface.js +61 -0
- package/src/components/MarkdownView.js +322 -0
- package/src/components/PaleCard.js +83 -0
- package/src/components/TabBar.js +346 -0
- package/src/components/runtime.js +88 -0
- package/src/components/useCollapsingBar.js +55 -0
- package/src/index.d.ts +229 -0
- package/src/index.js +20 -0
- package/src/logic/anchorQuestion.js +41 -0
- package/src/logic/collapse.js +77 -0
- package/src/logic/collapsibleHeader.js +103 -0
- package/src/logic/color.js +68 -0
- package/src/logic/glass.js +151 -0
- package/src/logic/index.d.ts +203 -0
- package/src/logic/index.js +16 -0
- package/src/logic/markdown.js +229 -0
- package/src/logic/paleCard.js +66 -0
- package/src/logic/tabBar.js +104 -0
- package/src/logic/tableColumns.js +149 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rendering of an AI answer: a walk over the typed blocks that
|
|
3
|
+
* `parseMarkdown` produces. The whole grammar is in the parser, tested dry;
|
|
4
|
+
* here there is only style.
|
|
5
|
+
*
|
|
6
|
+
* Every default style can be overridden by name through `styles`.
|
|
7
|
+
*/
|
|
8
|
+
const { React, h, RN, LinearGradient } = require('./runtime');
|
|
9
|
+
const { GlassButton } = require('./GlassButton');
|
|
10
|
+
const { parseMarkdown, parseInline, inlinesToText } = require('../logic/markdown');
|
|
11
|
+
const { measureColumns } = require('../logic/tableColumns');
|
|
12
|
+
const { withAlpha } = require('../logic/color');
|
|
13
|
+
|
|
14
|
+
const hairline = RN.StyleSheet.hairlineWidth;
|
|
15
|
+
|
|
16
|
+
const DEFAULT_STYLES = {
|
|
17
|
+
stack: { gap: 9 },
|
|
18
|
+
body: { color: '#1d2440', fontSize: 15, lineHeight: 22 },
|
|
19
|
+
plain: { color: '#ffffff', fontWeight: '500' },
|
|
20
|
+
bold: { fontWeight: '700', color: '#0d1235' },
|
|
21
|
+
italic: { fontStyle: 'italic' },
|
|
22
|
+
link: { color: '#1f4dcc', textDecorationLine: 'underline' },
|
|
23
|
+
inlineCode: { fontWeight: '500', fontSize: 13.5, color: '#315bd4', backgroundColor: 'rgba(59,108,240,0.10)' },
|
|
24
|
+
heading: { color: '#0d1235', fontWeight: '800', fontSize: 17, lineHeight: 24, letterSpacing: -0.3 },
|
|
25
|
+
headingSmall: { fontSize: 15, lineHeight: 21 },
|
|
26
|
+
list: { gap: 5 },
|
|
27
|
+
listItem: { flexDirection: 'row', alignItems: 'flex-start', gap: 7 },
|
|
28
|
+
listMarker: { minWidth: 15, color: '#1f4dcc', fontWeight: '700', fontSize: 14, lineHeight: 22 },
|
|
29
|
+
listBody: { flex: 1 },
|
|
30
|
+
code: {
|
|
31
|
+
overflow: 'hidden',
|
|
32
|
+
borderRadius: 12,
|
|
33
|
+
backgroundColor: 'rgba(13,18,53,0.04)',
|
|
34
|
+
borderWidth: hairline,
|
|
35
|
+
borderColor: 'rgba(13,18,53,0.08)'
|
|
36
|
+
},
|
|
37
|
+
codeBar: {
|
|
38
|
+
height: 34,
|
|
39
|
+
paddingLeft: 12,
|
|
40
|
+
paddingRight: 4,
|
|
41
|
+
flexDirection: 'row',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
justifyContent: 'space-between',
|
|
44
|
+
gap: 8
|
|
45
|
+
},
|
|
46
|
+
codeLanguage: { color: 'rgba(13,18,53,0.45)', fontWeight: '700', fontSize: 10, letterSpacing: 0.6 },
|
|
47
|
+
codeText: { paddingHorizontal: 12, paddingBottom: 11, color: '#26304c', fontWeight: '500', fontSize: 12.5, lineHeight: 19 },
|
|
48
|
+
/* THE TABLE TOOK THE SCREEN'S HEIGHT. A one-row table stretched a thousand
|
|
49
|
+
points tall on an iPhone. Not the table growing: the container of a
|
|
50
|
+
horizontal ScrollView aligns its children with `stretch` on the vertical
|
|
51
|
+
axis. `alignItems: flex-start` gives the table back its own height, and
|
|
52
|
+
`alignSelf: flex-start` stops the scroller claiming more room than it
|
|
53
|
+
fills. */
|
|
54
|
+
tableFrame: { position: 'relative', alignSelf: 'flex-start', maxWidth: '100%' },
|
|
55
|
+
tableVeil: { position: 'absolute', top: 0, bottom: 0, right: 0, width: 28 },
|
|
56
|
+
tableScroll: { paddingVertical: 2, alignItems: 'flex-start' },
|
|
57
|
+
tableScrollView: { flexGrow: 0, alignSelf: 'flex-start' },
|
|
58
|
+
/* A CLEAN TABLE, NOT A GRID. A rounded frame, a rule between EVERY column
|
|
59
|
+
and a painted header were four strokes and two colours for three
|
|
60
|
+
numbers — the eye followed the grid instead of the data. What remains:
|
|
61
|
+
one stroke under the header, a light rule between rows, nothing around.
|
|
62
|
+
Columns separate by space. */
|
|
63
|
+
table: { overflow: 'hidden' },
|
|
64
|
+
/* `stretch`: the boxes of a row take the tallest one's height, or rows
|
|
65
|
+
go out of line as soon as one cell wraps. */
|
|
66
|
+
tableRow: { flexDirection: 'row', alignItems: 'stretch', borderTopWidth: hairline, borderTopColor: 'rgba(13,18,53,0.07)' },
|
|
67
|
+
tableHeader: { borderTopWidth: 0, borderBottomWidth: hairline, borderBottomColor: 'rgba(13,18,53,0.22)' },
|
|
68
|
+
/* The width lives on the BOX, not the text: a wide Text lets its content
|
|
69
|
+
float in the room it gets, a wide box frames it. The 12 + 12 padding is
|
|
70
|
+
the `padding` the column measure assumes. */
|
|
71
|
+
tableCellBox: { paddingHorizontal: 12, paddingVertical: 10, justifyContent: 'flex-start' },
|
|
72
|
+
tableHeaderText: {
|
|
73
|
+
color: 'rgba(13,18,53,0.52)',
|
|
74
|
+
fontWeight: '600',
|
|
75
|
+
fontSize: 11,
|
|
76
|
+
lineHeight: 15,
|
|
77
|
+
letterSpacing: 0.3,
|
|
78
|
+
textTransform: 'uppercase'
|
|
79
|
+
},
|
|
80
|
+
tableCell: { color: '#0d1235', fontWeight: '500', fontSize: 13.5, lineHeight: 19 },
|
|
81
|
+
/* A fully numeric column reads right-aligned, in tabular digits — or "1"
|
|
82
|
+
and "8" differ in width and the column ripples from row to row. */
|
|
83
|
+
tableCellNumber: { textAlign: 'right', fontVariant: ['tabular-nums'] },
|
|
84
|
+
quote: { paddingLeft: 11, paddingVertical: 2, borderLeftWidth: 2, borderLeftColor: 'rgba(59,108,240,0.35)' },
|
|
85
|
+
quoteText: { color: 'rgba(13,18,53,0.7)', fontStyle: 'italic' }
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function openLink(href) {
|
|
89
|
+
Promise.resolve(RN.Linking.openURL(href)).catch(() => {});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const DEFAULT_VEIL = 'rgba(255,255,255,0.92)';
|
|
93
|
+
/* The default context lets `MarkdownTable` render on its own, outside a
|
|
94
|
+
MarkdownView. */
|
|
95
|
+
const Ctx = React.createContext({ styles: DEFAULT_STYLES, onLinkPress: openLink, tableVeilColor: DEFAULT_VEIL });
|
|
96
|
+
|
|
97
|
+
function MarkdownView({
|
|
98
|
+
content,
|
|
99
|
+
plain = false,
|
|
100
|
+
styles: overrides,
|
|
101
|
+
onLinkPress = openLink,
|
|
102
|
+
onCopyCode,
|
|
103
|
+
copyLabel,
|
|
104
|
+
copyIcon,
|
|
105
|
+
copiedIcon,
|
|
106
|
+
fontScale,
|
|
107
|
+
tableVeilColor = DEFAULT_VEIL,
|
|
108
|
+
testID
|
|
109
|
+
}) {
|
|
110
|
+
const styles = React.useMemo(() => {
|
|
111
|
+
const merged = { ...DEFAULT_STYLES };
|
|
112
|
+
for (const key of Object.keys(overrides || {})) merged[key] = [DEFAULT_STYLES[key], overrides[key]];
|
|
113
|
+
return merged;
|
|
114
|
+
}, [overrides]);
|
|
115
|
+
const blocks = React.useMemo(() => (plain ? [] : parseMarkdown(content)), [content, plain]);
|
|
116
|
+
|
|
117
|
+
/* A person's own message is not Markdown: they typed what they wanted to
|
|
118
|
+
see. Their stars and dashes are not taken away. */
|
|
119
|
+
if (plain) return h(RN.Text, { testID, style: [styles.body, styles.plain] }, content);
|
|
120
|
+
if (!blocks.length) return null;
|
|
121
|
+
|
|
122
|
+
const context = { styles, onLinkPress, onCopyCode, copyLabel, copyIcon, copiedIcon, fontScale, tableVeilColor };
|
|
123
|
+
return h(
|
|
124
|
+
Ctx.Provider,
|
|
125
|
+
{ value: context },
|
|
126
|
+
h(RN.View, { testID, style: styles.stack }, blocks.map((block, index) => h(Block, { key: index, block })))
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function Block({ block }) {
|
|
131
|
+
const { styles } = React.useContext(Ctx);
|
|
132
|
+
switch (block.kind) {
|
|
133
|
+
case 'heading':
|
|
134
|
+
return h(
|
|
135
|
+
RN.Text,
|
|
136
|
+
{ accessibilityRole: 'header', style: [styles.heading, block.level >= 3 ? styles.headingSmall : null] },
|
|
137
|
+
h(Inlines, { inlines: block.inlines })
|
|
138
|
+
);
|
|
139
|
+
case 'bullets':
|
|
140
|
+
case 'numbers':
|
|
141
|
+
return h(
|
|
142
|
+
RN.View,
|
|
143
|
+
{ style: styles.list },
|
|
144
|
+
block.items.map((item, index) =>
|
|
145
|
+
h(
|
|
146
|
+
RN.View,
|
|
147
|
+
{ key: index, style: [styles.listItem, { marginLeft: item.depth * 14 }] },
|
|
148
|
+
h(RN.Text, { style: styles.listMarker }, block.kind === 'numbers' ? `${block.start + index}.` : '•'),
|
|
149
|
+
h(RN.Text, { style: [styles.body, styles.listBody] }, h(Inlines, { inlines: item.inlines }))
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
case 'code':
|
|
154
|
+
return h(CodeBlock, { language: block.language, text: block.text });
|
|
155
|
+
case 'table':
|
|
156
|
+
return h(MarkdownTable, { header: block.header, rows: block.rows });
|
|
157
|
+
case 'quote':
|
|
158
|
+
return h(
|
|
159
|
+
RN.View,
|
|
160
|
+
{ style: styles.quote },
|
|
161
|
+
h(RN.Text, { style: [styles.body, styles.quoteText] }, h(Inlines, { inlines: block.inlines }))
|
|
162
|
+
);
|
|
163
|
+
default:
|
|
164
|
+
return h(RN.Text, { style: styles.body }, h(Inlines, { inlines: block.inlines }));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function Inlines({ inlines }) {
|
|
169
|
+
const { styles, onLinkPress } = React.useContext(Ctx);
|
|
170
|
+
return h(
|
|
171
|
+
React.Fragment,
|
|
172
|
+
null,
|
|
173
|
+
inlines.map((inline, index) => {
|
|
174
|
+
if (inline.kind === 'bold') return h(RN.Text, { key: index, style: styles.bold }, inline.text);
|
|
175
|
+
if (inline.kind === 'italic') return h(RN.Text, { key: index, style: styles.italic }, inline.text);
|
|
176
|
+
if (inline.kind === 'code') return h(RN.Text, { key: index, style: styles.inlineCode }, inline.text);
|
|
177
|
+
if (inline.kind === 'link') {
|
|
178
|
+
return h(
|
|
179
|
+
RN.Text,
|
|
180
|
+
{ key: index, accessibilityRole: 'link', style: styles.link, onPress: () => onLinkPress(inline.href) },
|
|
181
|
+
inline.text
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return h(RN.Text, { key: index }, inline.text);
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* A plain-text cell stays a string: it inherits its cell's style directly,
|
|
190
|
+
right alignment of numbers included. Only marked cells (bold, link…) go
|
|
191
|
+
through the inline renderer. */
|
|
192
|
+
function cellContent(inlines) {
|
|
193
|
+
if (!inlines || !inlines.length) return '';
|
|
194
|
+
if (inlines.every((inline) => inline.kind === 'text')) return inlinesToText(inlines);
|
|
195
|
+
return h(Inlines, { inlines });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* A code block copies ALONE: in an answer that explains then shows, copying
|
|
199
|
+
the whole bubble means trimming the clipboard by hand. The copy itself
|
|
200
|
+
(clipboard, haptics) is the caller's. */
|
|
201
|
+
function CodeBlock({ language, text }) {
|
|
202
|
+
const { styles, onCopyCode, copyLabel, copyIcon, copiedIcon } = React.useContext(Ctx);
|
|
203
|
+
const [copied, setCopied] = React.useState(false);
|
|
204
|
+
const timer = React.useRef(null);
|
|
205
|
+
React.useEffect(() => () => clearTimeout(timer.current), []);
|
|
206
|
+
const copy = async () => {
|
|
207
|
+
await onCopyCode(text);
|
|
208
|
+
setCopied(true);
|
|
209
|
+
clearTimeout(timer.current);
|
|
210
|
+
timer.current = setTimeout(() => setCopied(false), 1600);
|
|
211
|
+
};
|
|
212
|
+
return h(
|
|
213
|
+
RN.View,
|
|
214
|
+
{ style: styles.code },
|
|
215
|
+
h(
|
|
216
|
+
RN.View,
|
|
217
|
+
{ style: styles.codeBar },
|
|
218
|
+
h(RN.Text, { style: styles.codeLanguage }, language || 'code'),
|
|
219
|
+
onCopyCode && copyIcon
|
|
220
|
+
? h(
|
|
221
|
+
GlassButton,
|
|
222
|
+
{ accessibilityLabel: copyLabel, onPress: () => void copy().catch(() => {}), size: 26 },
|
|
223
|
+
copied && copiedIcon ? copiedIcon : copyIcon
|
|
224
|
+
)
|
|
225
|
+
: null
|
|
226
|
+
),
|
|
227
|
+
h(RN.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false }, h(RN.Text, { style: styles.codeText }, text))
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A table, and not three lists side by side. Each column's width is computed
|
|
233
|
+
* ONCE for the whole table (`measureColumns`), then imposed on the header and
|
|
234
|
+
* on every row: the only way a value falls under its title.
|
|
235
|
+
*/
|
|
236
|
+
function MarkdownTable({ header, rows }) {
|
|
237
|
+
const { styles, fontScale, tableVeilColor } = React.useContext(Ctx);
|
|
238
|
+
const [contentWidth, setContentWidth] = React.useState(0);
|
|
239
|
+
const [viewWidth, setViewWidth] = React.useState(0);
|
|
240
|
+
const [moreOnRight, setMoreOnRight] = React.useState(true);
|
|
241
|
+
/* Two points of slack: a one-pixel difference comes from density rounding,
|
|
242
|
+
not from hidden content. */
|
|
243
|
+
const overflows = contentWidth - viewWidth > 2;
|
|
244
|
+
/* BOLD IN A CELL. Cells were shown raw: "**Starter**" came out with its
|
|
245
|
+
stars. They go through the paragraph renderer, and columns are measured
|
|
246
|
+
on the VISIBLE text, without the marks. */
|
|
247
|
+
const head = React.useMemo(() => header.map(parseInline), [header]);
|
|
248
|
+
const body = React.useMemo(() => rows.map((row) => row.map(parseInline)), [rows]);
|
|
249
|
+
const { widths, numeric } = React.useMemo(
|
|
250
|
+
() =>
|
|
251
|
+
measureColumns(
|
|
252
|
+
head.map(inlinesToText),
|
|
253
|
+
body.map((row) => row.map(inlinesToText)),
|
|
254
|
+
{
|
|
255
|
+
/* THE REAL ROOM. The table fills the measured width; 300 only
|
|
256
|
+
serves before the first measure. Wider than that, it scrolls. */
|
|
257
|
+
targetWidth: viewWidth > 0 ? viewWidth : 300,
|
|
258
|
+
fontScale: fontScale ?? RN.PixelRatio.getFontScale(),
|
|
259
|
+
/* On the real pixel grid, or the hairlines render as two blurry greys. */
|
|
260
|
+
round: (value) => RN.PixelRatio.roundToNearestPixel(value)
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
[head, body, viewWidth, fontScale]
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const row = (cells, key, textStyle, extra) =>
|
|
267
|
+
h(
|
|
268
|
+
RN.View,
|
|
269
|
+
{ key, style: [styles.tableRow, extra] },
|
|
270
|
+
widths.map((width, index) =>
|
|
271
|
+
h(
|
|
272
|
+
RN.View,
|
|
273
|
+
{ key: index, style: [styles.tableCellBox, { width }] },
|
|
274
|
+
h(RN.Text, { style: [textStyle, numeric[index] ? styles.tableCellNumber : null] }, cellContent(cells[index]))
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
return h(
|
|
280
|
+
RN.View,
|
|
281
|
+
{ style: styles.tableFrame },
|
|
282
|
+
h(
|
|
283
|
+
RN.ScrollView,
|
|
284
|
+
{
|
|
285
|
+
horizontal: true,
|
|
286
|
+
/* The native indicator stays: on an overflowing table it is the only
|
|
287
|
+
confirmation that the gesture does something. */
|
|
288
|
+
showsHorizontalScrollIndicator: true,
|
|
289
|
+
style: styles.tableScrollView,
|
|
290
|
+
contentContainerStyle: styles.tableScroll,
|
|
291
|
+
scrollEventThrottle: 16,
|
|
292
|
+
onScroll: ({ nativeEvent }) => {
|
|
293
|
+
const rest = nativeEvent.contentSize.width - nativeEvent.contentOffset.x - nativeEvent.layoutMeasurement.width;
|
|
294
|
+
setMoreOnRight(rest > 2);
|
|
295
|
+
},
|
|
296
|
+
onContentSizeChange: (width) => setContentWidth(width),
|
|
297
|
+
onLayout: ({ nativeEvent }) => setViewWidth(nativeEvent.layout.width)
|
|
298
|
+
},
|
|
299
|
+
h(
|
|
300
|
+
RN.View,
|
|
301
|
+
{ style: styles.table },
|
|
302
|
+
row(head, 'header', styles.tableHeaderText, styles.tableHeader),
|
|
303
|
+
body.map((cells, index) => row(cells, index, styles.tableCell, null))
|
|
304
|
+
)
|
|
305
|
+
),
|
|
306
|
+
/* THE VEIL THAT SAYS THERE IS MORE. A column cut by the screen edge was
|
|
307
|
+
read as an EMPTY column: nothing said the table scrolls. This gradient
|
|
308
|
+
shows while something remains on the right and fades at the end. It
|
|
309
|
+
takes no room and catches no finger. */
|
|
310
|
+
overflows && moreOnRight
|
|
311
|
+
? h(LinearGradient, {
|
|
312
|
+
pointerEvents: 'none',
|
|
313
|
+
colors: [withAlpha(tableVeilColor, 0), tableVeilColor],
|
|
314
|
+
start: { x: 0, y: 0.5 },
|
|
315
|
+
end: { x: 1, y: 0.5 },
|
|
316
|
+
style: styles.tableVeil
|
|
317
|
+
})
|
|
318
|
+
: null
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = { MarkdownView, MarkdownTable, MARKDOWN_STYLES: DEFAULT_STYLES };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE content card: flat, pale, in its own colour — and the tappable card,
|
|
3
|
+
* glass on iOS, pale elsewhere.
|
|
4
|
+
*
|
|
5
|
+
* The colour maths and the reasons (no outline, no shadow, opaque fill) are
|
|
6
|
+
* in logic/paleCard.js. The same rendering on iOS and Android, because there
|
|
7
|
+
* is nothing either platform could composite differently: no shadow (it would
|
|
8
|
+
* make the card "float", and Android draws it as a grey halo), no blur, no
|
|
9
|
+
* glass.
|
|
10
|
+
*/
|
|
11
|
+
const { h, RN, LinearGradient, getGlassMode, useScheme } = require('./runtime');
|
|
12
|
+
const { GlassSurface } = require('./GlassSurface');
|
|
13
|
+
const { PALE_CARD_RADIUS, paleCardColors } = require('../logic/paleCard');
|
|
14
|
+
const { parseColor } = require('../logic/color');
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PAGE = { light: '#ffffff', dark: '#000000' };
|
|
17
|
+
const DEFAULT_INK = { light: '#0d1235', dark: '#ffffff' };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The card's colours for the current scheme — also for a pale background
|
|
21
|
+
* that is not a card (a banner, a chip).
|
|
22
|
+
*/
|
|
23
|
+
function usePaleCardColors({ tint, pageBackground, ink, scheme: schemeOverride } = {}) {
|
|
24
|
+
const scheme = useScheme(schemeOverride);
|
|
25
|
+
return paleCardColors({
|
|
26
|
+
scheme,
|
|
27
|
+
pageBackground: pageBackground || DEFAULT_PAGE[scheme],
|
|
28
|
+
ink: ink || DEFAULT_INK[scheme],
|
|
29
|
+
tint
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function PaleCard({ children, tint, pageBackground, ink, scheme, style, ...rest }) {
|
|
34
|
+
const { fill, top, bottom } = usePaleCardColors({ tint, pageBackground, ink, scheme });
|
|
35
|
+
return h(
|
|
36
|
+
RN.View,
|
|
37
|
+
{ ...rest, style: [{ borderRadius: PALE_CARD_RADIUS, overflow: 'hidden', backgroundColor: fill }, style] },
|
|
38
|
+
top !== bottom
|
|
39
|
+
? h(LinearGradient, {
|
|
40
|
+
pointerEvents: 'none',
|
|
41
|
+
colors: [top, bottom],
|
|
42
|
+
start: { x: 0, y: 0 },
|
|
43
|
+
end: { x: 1, y: 1 },
|
|
44
|
+
style: RN.StyleSheet.absoluteFill
|
|
45
|
+
})
|
|
46
|
+
: null,
|
|
47
|
+
children
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A card you TOUCH: a suggestion, a recipient in a list.
|
|
53
|
+
*
|
|
54
|
+
* Glass left the content of the screens for pale cards — then came back on
|
|
55
|
+
* iOS, but only on what is touched. Never on a search bar, a field, or a card
|
|
56
|
+
* that only shows. On iOS: Apple's glass, interactive (it reacts to the
|
|
57
|
+
* finger), with no white tint added — only a trace of the accent. Elsewhere:
|
|
58
|
+
* the pale card, unchanged; Android has no such material and its imitation
|
|
59
|
+
* came out grey.
|
|
60
|
+
*
|
|
61
|
+
* The card does not handle the press itself: wrap it in (or put inside it)
|
|
62
|
+
* the Pressable that fits the row.
|
|
63
|
+
*/
|
|
64
|
+
function TappableCard({ children, tint, pageBackground, ink, scheme, style }) {
|
|
65
|
+
if (getGlassMode() === 'native') {
|
|
66
|
+
const accent = tint && parseColor(tint);
|
|
67
|
+
return h(
|
|
68
|
+
GlassSurface,
|
|
69
|
+
{
|
|
70
|
+
interactive: true,
|
|
71
|
+
glassStyle: 'regular',
|
|
72
|
+
/* 8 % of the accent: a touch of colour in the glass, not a coat. */
|
|
73
|
+
tintColor: accent ? `rgba(${accent.r},${accent.g},${accent.b},0.08)` : undefined,
|
|
74
|
+
scheme,
|
|
75
|
+
style: [{ borderRadius: PALE_CARD_RADIUS, overflow: 'hidden' }, style]
|
|
76
|
+
},
|
|
77
|
+
children
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return h(PaleCard, { tint, pageBackground, ink, scheme, style }, children);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { PaleCard, TappableCard, usePaleCardColors };
|