panelui-native 0.44.0 → 0.49.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 +6 -1
- package/lib/module/components/accordion/index.js +32 -4
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/button/index.js +83 -15
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/button-group/index.js +186 -0
- package/lib/module/components/button-group/index.js.map +1 -0
- package/lib/module/components/color-picker/index.js +110 -1
- package/lib/module/components/color-picker/index.js.map +1 -1
- package/lib/module/components/combobox/index.js +7 -1
- package/lib/module/components/combobox/index.js.map +1 -1
- package/lib/module/components/date-time-picker/index.js +272 -0
- package/lib/module/components/date-time-picker/index.js.map +1 -0
- package/lib/module/components/fab/index.js +514 -0
- package/lib/module/components/fab/index.js.map +1 -0
- package/lib/module/components/grid-item/index.js +486 -0
- package/lib/module/components/grid-item/index.js.map +1 -0
- package/lib/module/components/{kpi-chart → kpi}/index.js +62 -62
- package/lib/module/components/kpi/index.js.map +1 -0
- package/lib/module/components/line-chart/index.js +92 -10
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/markdown-editor/index.js +406 -0
- package/lib/module/components/markdown-editor/index.js.map +1 -0
- package/lib/module/components/markdown-editor/markdown-transforms.js +243 -0
- package/lib/module/components/markdown-editor/markdown-transforms.js.map +1 -0
- package/lib/module/components/pie-chart/index.js +625 -0
- package/lib/module/components/pie-chart/index.js.map +1 -0
- package/lib/module/components/questionnaire/index.js +1312 -0
- package/lib/module/components/questionnaire/index.js.map +1 -0
- package/lib/module/components/scatter-chart/index.js +1173 -0
- package/lib/module/components/scatter-chart/index.js.map +1 -0
- package/lib/module/components/tabs/index.js +359 -41
- package/lib/module/components/tabs/index.js.map +1 -1
- package/lib/module/components/time-picker/index.js +34 -6
- package/lib/module/components/time-picker/index.js.map +1 -1
- package/lib/module/components/tree/index.js +500 -0
- package/lib/module/components/tree/index.js.map +1 -0
- package/lib/module/icons/index.js +217 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +11 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/chart.js +81 -0
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +21 -0
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +21 -0
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button-group/index.d.ts +212 -0
- package/lib/typescript/src/components/button-group/index.d.ts.map +1 -0
- package/lib/typescript/src/components/color-picker/index.d.ts +82 -1
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/combobox/index.d.ts.map +1 -1
- package/lib/typescript/src/components/date-time-picker/index.d.ts +127 -0
- package/lib/typescript/src/components/date-time-picker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/fab/index.d.ts +285 -0
- package/lib/typescript/src/components/fab/index.d.ts.map +1 -0
- package/lib/typescript/src/components/grid-item/index.d.ts +292 -0
- package/lib/typescript/src/components/grid-item/index.d.ts.map +1 -0
- package/lib/typescript/src/components/{kpi-chart → kpi}/index.d.ts +65 -65
- package/lib/typescript/src/components/kpi/index.d.ts.map +1 -0
- package/lib/typescript/src/components/line-chart/index.d.ts +25 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/markdown-editor/index.d.ts +102 -0
- package/lib/typescript/src/components/markdown-editor/index.d.ts.map +1 -0
- package/lib/typescript/src/components/markdown-editor/markdown-transforms.d.ts +76 -0
- package/lib/typescript/src/components/markdown-editor/markdown-transforms.d.ts.map +1 -0
- package/lib/typescript/src/components/pie-chart/index.d.ts +245 -0
- package/lib/typescript/src/components/pie-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/questionnaire/index.d.ts +336 -0
- package/lib/typescript/src/components/questionnaire/index.d.ts.map +1 -0
- package/lib/typescript/src/components/scatter-chart/index.d.ts +309 -0
- package/lib/typescript/src/components/scatter-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/tabs/index.d.ts +29 -2
- package/lib/typescript/src/components/tabs/index.d.ts.map +1 -1
- package/lib/typescript/src/components/time-picker/index.d.ts +19 -1
- package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tree/index.d.ts +125 -0
- package/lib/typescript/src/components/tree/index.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +22 -0
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +14 -5
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +34 -0
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/accordion/index.tsx +48 -6
- package/src/components/button/index.tsx +97 -15
- package/src/components/button-group/index.tsx +199 -0
- package/src/components/color-picker/index.tsx +140 -3
- package/src/components/combobox/index.tsx +7 -1
- package/src/components/date-time-picker/index.tsx +411 -0
- package/src/components/fab/index.tsx +583 -0
- package/src/components/grid-item/index.tsx +515 -0
- package/src/components/{kpi-chart → kpi}/index.tsx +85 -85
- package/src/components/line-chart/index.tsx +98 -8
- package/src/components/markdown-editor/index.tsx +526 -0
- package/src/components/markdown-editor/markdown-transforms.ts +228 -0
- package/src/components/pie-chart/index.tsx +863 -0
- package/src/components/questionnaire/index.tsx +1615 -0
- package/src/components/scatter-chart/index.tsx +1401 -0
- package/src/components/tabs/index.tsx +392 -50
- package/src/components/time-picker/index.tsx +42 -6
- package/src/components/tree/index.tsx +564 -0
- package/src/icons/index.tsx +154 -0
- package/src/index.ts +142 -18
- package/src/utils/chart.ts +110 -0
- package/lib/module/components/kpi-chart/index.js.map +0 -1
- package/lib/typescript/src/components/kpi-chart/index.d.ts.map +0 -1
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GridItem — a tile in a bento grid, and the grid that lays it out.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* <GridItem.Group columns={2} gap={12}>
|
|
6
|
+
* <GridItem colSpan={2}>
|
|
7
|
+
* <GridItem.Media variant="icon"><ZapIcon /></GridItem.Media>
|
|
8
|
+
* <GridItem.Title>Deploys</GridItem.Title>
|
|
9
|
+
* <GridItem.Value>1,284</GridItem.Value>
|
|
10
|
+
* <GridItem.Description>this week</GridItem.Description>
|
|
11
|
+
* </GridItem>
|
|
12
|
+
* <GridItem rowSpan={2}>…</GridItem>
|
|
13
|
+
* <GridItem>…</GridItem>
|
|
14
|
+
* </GridItem.Group>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* ## Why the group places the tiles
|
|
18
|
+
*
|
|
19
|
+
* `Item` next door is a row, and a list of rows is a column of views — the
|
|
20
|
+
* layout falls out of the flexbox and nothing has to be measured. A bento is
|
|
21
|
+
* not that. Its whole idea is that tiles are different sizes and the grid still
|
|
22
|
+
* lines up: a wide tile and the two square ones beside it share a left edge, a
|
|
23
|
+
* tall one runs past the tile next to it and the next tile fills in underneath.
|
|
24
|
+
*
|
|
25
|
+
* A wrapping flex row cannot do the last of those. Wrapping puts everything
|
|
26
|
+
* that did not fit on a *new line*, so nothing ever tucks under a tall tile,
|
|
27
|
+
* and `rowSpan` in a wrapping row is a prop that quietly does nothing.
|
|
28
|
+
*
|
|
29
|
+
* So the group measures itself, walks its children into the first free cell
|
|
30
|
+
* that fits each one — row by row, the way a grid places anything it is not
|
|
31
|
+
* told where to put — and positions them absolutely. Every tile is a whole
|
|
32
|
+
* number of cells, which is what makes the edges line up, and the group's own
|
|
33
|
+
* height is the number of rows it ended up needing.
|
|
34
|
+
*
|
|
35
|
+
* The consequence worth knowing: **a tile's height is its cells, not its
|
|
36
|
+
* content**. That is the right way round for a bento — a grid of boxes that
|
|
37
|
+
* each grew to fit its own text is not a grid — but it does mean long text
|
|
38
|
+
* needs `numberOfLines`, a smaller `size`, or a taller `rowSpan`.
|
|
39
|
+
*
|
|
40
|
+
* ## Nesting
|
|
41
|
+
*
|
|
42
|
+
* A group inside a tile is a group like any other, and it measures the cell it
|
|
43
|
+
* was put in. That is how a bento gets a sub-rhythm — two small tiles stacked
|
|
44
|
+
* inside one cell of the outer grid — without the outer grid needing a notion
|
|
45
|
+
* of half a row.
|
|
46
|
+
*/
|
|
47
|
+
import {
|
|
48
|
+
Children,
|
|
49
|
+
createContext,
|
|
50
|
+
forwardRef,
|
|
51
|
+
isValidElement,
|
|
52
|
+
useContext,
|
|
53
|
+
useMemo,
|
|
54
|
+
useState,
|
|
55
|
+
type ReactNode,
|
|
56
|
+
} from 'react';
|
|
57
|
+
import { View, type LayoutChangeEvent, type ViewProps } from 'react-native';
|
|
58
|
+
import { tv, type VariantProps } from 'tailwind-variants';
|
|
59
|
+
import {
|
|
60
|
+
AnimatedPressable,
|
|
61
|
+
type AnimatedPressableProps,
|
|
62
|
+
} from '../../primitives/animated-pressable';
|
|
63
|
+
import { Text, type TextProps, textChildren } from '../../primitives/text';
|
|
64
|
+
import { cn } from '../../utils/cn';
|
|
65
|
+
|
|
66
|
+
type GridItemSize = 'default' | 'sm';
|
|
67
|
+
|
|
68
|
+
const gridItemVariants = tv({
|
|
69
|
+
slots: {
|
|
70
|
+
// Full-bleed inside its cell: the group has already decided how big the
|
|
71
|
+
// tile is, and a tile that sized itself would break the grid it is in.
|
|
72
|
+
root: 'h-full w-full overflow-hidden rounded-2xl',
|
|
73
|
+
title: 'font-medium text-foreground',
|
|
74
|
+
value: 'font-semibold text-foreground',
|
|
75
|
+
description: 'text-muted-foreground',
|
|
76
|
+
},
|
|
77
|
+
variants: {
|
|
78
|
+
variant: {
|
|
79
|
+
/** A card in a tray — the tile that reads as a tile. */
|
|
80
|
+
default: { root: 'border border-border bg-card' },
|
|
81
|
+
/** Outline only, for a grid over a coloured or patterned page. */
|
|
82
|
+
outline: { root: 'border border-border' },
|
|
83
|
+
/** Filled and unbordered, for a quieter tile among louder ones. */
|
|
84
|
+
muted: { root: 'bg-muted' },
|
|
85
|
+
/** Nothing at all: the tile is whatever is put inside it. */
|
|
86
|
+
plain: { root: '' },
|
|
87
|
+
},
|
|
88
|
+
size: {
|
|
89
|
+
default: { root: 'gap-2 p-4', title: 'text-sm', value: 'text-2xl', description: 'text-xs' },
|
|
90
|
+
sm: { root: 'gap-1.5 p-3', title: 'text-xs', value: 'text-xl', description: 'text-xs' },
|
|
91
|
+
},
|
|
92
|
+
disabled: {
|
|
93
|
+
true: { root: 'opacity-[0.64]' },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
defaultVariants: {
|
|
97
|
+
variant: 'default',
|
|
98
|
+
size: 'default',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const mediaVariants = tv({
|
|
103
|
+
base: 'shrink-0 items-center justify-center',
|
|
104
|
+
variants: {
|
|
105
|
+
variant: {
|
|
106
|
+
/** No box — for an Avatar or anything that styles itself. */
|
|
107
|
+
default: '',
|
|
108
|
+
/** Rounded square tile sized for an icon. */
|
|
109
|
+
icon: 'rounded-lg border border-border bg-muted',
|
|
110
|
+
/** Clipped frame for an image or thumbnail. */
|
|
111
|
+
image: 'overflow-hidden rounded-lg bg-muted',
|
|
112
|
+
},
|
|
113
|
+
size: {
|
|
114
|
+
default: '',
|
|
115
|
+
sm: '',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
compoundVariants: [
|
|
119
|
+
{ variant: 'icon', size: 'default', class: 'h-9 w-9' },
|
|
120
|
+
{ variant: 'icon', size: 'sm', class: 'h-7 w-7' },
|
|
121
|
+
{ variant: 'image', size: 'default', class: 'h-11 w-11' },
|
|
122
|
+
{ variant: 'image', size: 'sm', class: 'h-9 w-9' },
|
|
123
|
+
],
|
|
124
|
+
defaultVariants: {
|
|
125
|
+
variant: 'default',
|
|
126
|
+
size: 'default',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/** Density, set once on the group and read by every part inside a tile. */
|
|
131
|
+
const GridItemContext = createContext<{ size: GridItemSize }>({ size: 'default' });
|
|
132
|
+
|
|
133
|
+
export interface GridItemGroupProps extends ViewProps {
|
|
134
|
+
className?: string;
|
|
135
|
+
/** How many tracks wide the grid is. */
|
|
136
|
+
columns?: number;
|
|
137
|
+
/** Gutter between tiles, in points — both ways. */
|
|
138
|
+
gap?: number;
|
|
139
|
+
/**
|
|
140
|
+
* The shape of one cell, as width ÷ height. `1` is square; below one the
|
|
141
|
+
* cells are taller than they are wide. Ignored when `rowHeight` is given.
|
|
142
|
+
*/
|
|
143
|
+
aspect?: number;
|
|
144
|
+
/** Cell height in points, when the grid should not be driven by its width. */
|
|
145
|
+
rowHeight?: number;
|
|
146
|
+
/** Density for every tile in the grid. Set here rather than on each one. */
|
|
147
|
+
size?: GridItemSize;
|
|
148
|
+
children?: ReactNode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** One tile's place in the grid, in cells. */
|
|
152
|
+
interface Placement {
|
|
153
|
+
row: number;
|
|
154
|
+
column: number;
|
|
155
|
+
colSpan: number;
|
|
156
|
+
rowSpan: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The first cell a tile of this size fits in, scanning row by row.
|
|
161
|
+
*
|
|
162
|
+
* The same rule a grid uses for anything it is not told where to put: take the
|
|
163
|
+
* earliest place it fits, never move anything already placed, and open a new
|
|
164
|
+
* row when nothing on the existing ones will do. Row-major, so the order the
|
|
165
|
+
* tiles are written in is the order they are read in — which is the order a
|
|
166
|
+
* screen reader will walk them in too, since that is the child order.
|
|
167
|
+
*/
|
|
168
|
+
function findSpot(
|
|
169
|
+
occupied: boolean[][],
|
|
170
|
+
columns: number,
|
|
171
|
+
colSpan: number,
|
|
172
|
+
rowSpan: number
|
|
173
|
+
): { row: number; column: number } {
|
|
174
|
+
for (let row = 0; ; row += 1) {
|
|
175
|
+
for (let column = 0; column + colSpan <= columns; column += 1) {
|
|
176
|
+
let free = true;
|
|
177
|
+
for (let y = row; y < row + rowSpan && free; y += 1) {
|
|
178
|
+
for (let x = column; x < column + colSpan; x += 1) {
|
|
179
|
+
if (occupied[y]?.[x]) {
|
|
180
|
+
free = false;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (free) return { row, column };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The grid. Measures its own width, places its tiles, and stands as tall as the
|
|
192
|
+
* rows they ended up needing.
|
|
193
|
+
*/
|
|
194
|
+
const GridItemGroup = forwardRef<View, GridItemGroupProps>(
|
|
195
|
+
(
|
|
196
|
+
{
|
|
197
|
+
className,
|
|
198
|
+
columns = 2,
|
|
199
|
+
gap = 12,
|
|
200
|
+
aspect = 1,
|
|
201
|
+
rowHeight,
|
|
202
|
+
size = 'default',
|
|
203
|
+
children,
|
|
204
|
+
...props
|
|
205
|
+
},
|
|
206
|
+
ref
|
|
207
|
+
) => {
|
|
208
|
+
const [width, setWidth] = useState(0);
|
|
209
|
+
|
|
210
|
+
const tracks = Math.max(1, Math.floor(columns));
|
|
211
|
+
const track = (width - gap * (tracks - 1)) / tracks;
|
|
212
|
+
const cell = rowHeight ?? track / (aspect || 1);
|
|
213
|
+
|
|
214
|
+
const { placements, rows } = useMemo(() => {
|
|
215
|
+
const occupied: boolean[][] = [];
|
|
216
|
+
const result: Placement[] = [];
|
|
217
|
+
let used = 0;
|
|
218
|
+
|
|
219
|
+
Children.forEach(children, (child) => {
|
|
220
|
+
if (!isValidElement(child)) return;
|
|
221
|
+
const spans = child.props as { colSpan?: number; rowSpan?: number };
|
|
222
|
+
const colSpan = Math.max(1, Math.min(Math.floor(spans.colSpan ?? 1), tracks));
|
|
223
|
+
const rowSpan = Math.max(1, Math.floor(spans.rowSpan ?? 1));
|
|
224
|
+
|
|
225
|
+
const { row, column } = findSpot(occupied, tracks, colSpan, rowSpan);
|
|
226
|
+
for (let y = row; y < row + rowSpan; y += 1) {
|
|
227
|
+
const line = (occupied[y] ??= []);
|
|
228
|
+
for (let x = column; x < column + colSpan; x += 1) line[x] = true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
result.push({ row, column, colSpan, rowSpan });
|
|
232
|
+
used = Math.max(used, row + rowSpan);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return { placements: result, rows: used };
|
|
236
|
+
}, [children, tracks]);
|
|
237
|
+
|
|
238
|
+
const onLayout = (event: LayoutChangeEvent) => {
|
|
239
|
+
const next = event.nativeEvent.layout.width;
|
|
240
|
+
if (Math.abs(next - width) > 0.5) setWidth(next);
|
|
241
|
+
props.onLayout?.(event);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const height = rows > 0 ? rows * cell + gap * (rows - 1) : 0;
|
|
245
|
+
|
|
246
|
+
let index = -1;
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<GridItemContext.Provider value={{ size }}>
|
|
250
|
+
<View
|
|
251
|
+
ref={ref}
|
|
252
|
+
onLayout={onLayout}
|
|
253
|
+
accessibilityRole="list"
|
|
254
|
+
style={width > 0 ? { height } : undefined}
|
|
255
|
+
className={cn('w-full', className)}
|
|
256
|
+
{...props}
|
|
257
|
+
>
|
|
258
|
+
{/* Nothing is drawn until the width is known: every tile is placed
|
|
259
|
+
from it, and one frame of tiles piled on the origin is worse than
|
|
260
|
+
one frame of an empty box. */}
|
|
261
|
+
{width > 0
|
|
262
|
+
? Children.map(children, (child) => {
|
|
263
|
+
if (!isValidElement(child)) return null;
|
|
264
|
+
index += 1;
|
|
265
|
+
const place = placements[index];
|
|
266
|
+
if (!place) return null;
|
|
267
|
+
return (
|
|
268
|
+
<View
|
|
269
|
+
style={{
|
|
270
|
+
position: 'absolute',
|
|
271
|
+
left: place.column * (track + gap),
|
|
272
|
+
top: place.row * (cell + gap),
|
|
273
|
+
width: place.colSpan * track + (place.colSpan - 1) * gap,
|
|
274
|
+
height: place.rowSpan * cell + (place.rowSpan - 1) * gap,
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
{child}
|
|
278
|
+
</View>
|
|
279
|
+
);
|
|
280
|
+
})
|
|
281
|
+
: null}
|
|
282
|
+
</View>
|
|
283
|
+
</GridItemContext.Provider>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
GridItemGroup.displayName = 'GridItem.Group';
|
|
288
|
+
|
|
289
|
+
export interface GridItemProps
|
|
290
|
+
extends Omit<AnimatedPressableProps, 'children' | 'disabled'>,
|
|
291
|
+
Omit<VariantProps<typeof gridItemVariants>, 'disabled' | 'size'> {
|
|
292
|
+
className?: string;
|
|
293
|
+
disabled?: boolean;
|
|
294
|
+
/**
|
|
295
|
+
* How many tracks wide the tile is. Clamped to the group's column count, so
|
|
296
|
+
* a tile asking for three columns of a two-column grid is two wide rather
|
|
297
|
+
* than overflowing it.
|
|
298
|
+
*
|
|
299
|
+
* Read by `GridItem.Group`, which does the placing — a tile does not size
|
|
300
|
+
* itself, because a tile that sized itself would not be in a grid.
|
|
301
|
+
*/
|
|
302
|
+
colSpan?: number;
|
|
303
|
+
/** How many rows tall it is. Also read by the group. */
|
|
304
|
+
rowSpan?: number;
|
|
305
|
+
children?: ReactNode;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* One tile.
|
|
310
|
+
*
|
|
311
|
+
* Renders as a pressable when given `onPress`, and as a plain view otherwise,
|
|
312
|
+
* so a tile that is only showing a number does not announce itself as a button.
|
|
313
|
+
*/
|
|
314
|
+
const GridItemRoot = forwardRef<View, GridItemProps>(
|
|
315
|
+
(
|
|
316
|
+
{
|
|
317
|
+
className,
|
|
318
|
+
variant,
|
|
319
|
+
disabled,
|
|
320
|
+
children,
|
|
321
|
+
onPress,
|
|
322
|
+
// Declared so they are part of the tile's API and typed at the call site,
|
|
323
|
+
// read by the group, and deliberately not applied here.
|
|
324
|
+
colSpan: _colSpan,
|
|
325
|
+
rowSpan: _rowSpan,
|
|
326
|
+
...props
|
|
327
|
+
},
|
|
328
|
+
ref
|
|
329
|
+
) => {
|
|
330
|
+
const { size } = useContext(GridItemContext);
|
|
331
|
+
const { root } = gridItemVariants({ variant, size, disabled: !!disabled });
|
|
332
|
+
|
|
333
|
+
if (!onPress) {
|
|
334
|
+
return (
|
|
335
|
+
<View
|
|
336
|
+
ref={ref}
|
|
337
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
338
|
+
className={root({ className })}
|
|
339
|
+
{...(props as ViewProps)}
|
|
340
|
+
>
|
|
341
|
+
{textChildren(children)}
|
|
342
|
+
</View>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<AnimatedPressable
|
|
348
|
+
ref={ref}
|
|
349
|
+
accessibilityRole="button"
|
|
350
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
351
|
+
disabled={disabled}
|
|
352
|
+
onPress={onPress}
|
|
353
|
+
className={root({ className })}
|
|
354
|
+
{...props}
|
|
355
|
+
>
|
|
356
|
+
{textChildren(children)}
|
|
357
|
+
</AnimatedPressable>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
GridItemRoot.displayName = 'GridItem';
|
|
362
|
+
|
|
363
|
+
export interface GridItemBackgroundProps extends ViewProps {
|
|
364
|
+
className?: string;
|
|
365
|
+
children?: ReactNode;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The layer behind the tile's content — a chart, a gradient, an image, a
|
|
370
|
+
* pattern.
|
|
371
|
+
*
|
|
372
|
+
* Absolutely filling the tile and taking no touches, so it can be written
|
|
373
|
+
* anywhere among the children without moving anything. It is what makes a bento
|
|
374
|
+
* grid read as a bento grid rather than as a wall of stat cards, and it is the
|
|
375
|
+
* one part that is meant to be cropped: the tile clips it.
|
|
376
|
+
*/
|
|
377
|
+
const GridItemBackground = forwardRef<View, GridItemBackgroundProps>(
|
|
378
|
+
({ className, children, ...props }, ref) => (
|
|
379
|
+
<View
|
|
380
|
+
ref={ref}
|
|
381
|
+
pointerEvents="none"
|
|
382
|
+
style={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}
|
|
383
|
+
className={cn('overflow-hidden', className)}
|
|
384
|
+
{...props}
|
|
385
|
+
>
|
|
386
|
+
{textChildren(children)}
|
|
387
|
+
</View>
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
GridItemBackground.displayName = 'GridItem.Background';
|
|
391
|
+
|
|
392
|
+
export interface GridItemMediaProps
|
|
393
|
+
extends ViewProps,
|
|
394
|
+
VariantProps<typeof mediaVariants> {
|
|
395
|
+
className?: string;
|
|
396
|
+
children?: ReactNode;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** Leading slot: an icon tile, a thumbnail, or an avatar passed through. */
|
|
400
|
+
const GridItemMedia = forwardRef<View, GridItemMediaProps>(
|
|
401
|
+
({ className, variant, size, children, ...props }, ref) => {
|
|
402
|
+
const grid = useContext(GridItemContext);
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<View
|
|
406
|
+
ref={ref}
|
|
407
|
+
className={mediaVariants({ variant, size: size ?? grid.size, className })}
|
|
408
|
+
{...props}
|
|
409
|
+
>
|
|
410
|
+
{textChildren(children)}
|
|
411
|
+
</View>
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
GridItemMedia.displayName = 'GridItem.Media';
|
|
416
|
+
|
|
417
|
+
export interface GridItemTitleProps extends TextProps {
|
|
418
|
+
className?: string;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** What the tile is of. Quiet, because the number under it is the message. */
|
|
422
|
+
const GridItemTitle = forwardRef<React.ElementRef<typeof Text>, GridItemTitleProps>(
|
|
423
|
+
({ className, ...props }, ref) => {
|
|
424
|
+
const { size } = useContext(GridItemContext);
|
|
425
|
+
const { title } = gridItemVariants({ size });
|
|
426
|
+
|
|
427
|
+
return <Text ref={ref} className={title({ className })} {...props} />;
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
GridItemTitle.displayName = 'GridItem.Title';
|
|
431
|
+
|
|
432
|
+
export interface GridItemValueProps extends TextProps {
|
|
433
|
+
className?: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** The figure. The largest thing on the tile, and the reason it is there. */
|
|
437
|
+
const GridItemValue = forwardRef<React.ElementRef<typeof Text>, GridItemValueProps>(
|
|
438
|
+
({ className, ...props }, ref) => {
|
|
439
|
+
const { size } = useContext(GridItemContext);
|
|
440
|
+
const { value } = gridItemVariants({ size });
|
|
441
|
+
|
|
442
|
+
return <Text ref={ref} className={value({ className })} {...props} />;
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
GridItemValue.displayName = 'GridItem.Value';
|
|
446
|
+
|
|
447
|
+
export interface GridItemDescriptionProps extends TextProps {
|
|
448
|
+
className?: string;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const GridItemDescription = forwardRef<
|
|
452
|
+
React.ElementRef<typeof Text>,
|
|
453
|
+
GridItemDescriptionProps
|
|
454
|
+
>(({ className, ...props }, ref) => {
|
|
455
|
+
const { size } = useContext(GridItemContext);
|
|
456
|
+
const { description } = gridItemVariants({ size });
|
|
457
|
+
|
|
458
|
+
return <Text ref={ref} className={description({ className })} {...props} />;
|
|
459
|
+
});
|
|
460
|
+
GridItemDescription.displayName = 'GridItem.Description';
|
|
461
|
+
|
|
462
|
+
export interface GridItemFooterProps extends ViewProps {
|
|
463
|
+
className?: string;
|
|
464
|
+
children?: ReactNode;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* The strip along the bottom of the tile.
|
|
469
|
+
*
|
|
470
|
+
* `mt-auto` rather than `absolute`: a tile's height is fixed by its cells, so
|
|
471
|
+
* pushing the footer down with the space left over pins it to the bottom
|
|
472
|
+
* without taking it out of the layout the rest of the tile is sharing.
|
|
473
|
+
*/
|
|
474
|
+
const GridItemFooter = forwardRef<View, GridItemFooterProps>(
|
|
475
|
+
({ className, children, ...props }, ref) => (
|
|
476
|
+
<View
|
|
477
|
+
ref={ref}
|
|
478
|
+
className={cn('mt-auto w-full flex-row items-center gap-2', className)}
|
|
479
|
+
{...props}
|
|
480
|
+
>
|
|
481
|
+
{textChildren(children)}
|
|
482
|
+
</View>
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
GridItemFooter.displayName = 'GridItem.Footer';
|
|
486
|
+
|
|
487
|
+
export interface GridItemActionsProps extends ViewProps {
|
|
488
|
+
className?: string;
|
|
489
|
+
children?: ReactNode;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Trailing slot: buttons, a chip, a chevron. */
|
|
493
|
+
const GridItemActions = forwardRef<View, GridItemActionsProps>(
|
|
494
|
+
({ className, children, ...props }, ref) => (
|
|
495
|
+
<View
|
|
496
|
+
ref={ref}
|
|
497
|
+
className={cn('shrink-0 flex-row items-center gap-1.5', className)}
|
|
498
|
+
{...props}
|
|
499
|
+
>
|
|
500
|
+
{textChildren(children)}
|
|
501
|
+
</View>
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
GridItemActions.displayName = 'GridItem.Actions';
|
|
505
|
+
|
|
506
|
+
export const GridItem = Object.assign(GridItemRoot, {
|
|
507
|
+
Group: GridItemGroup,
|
|
508
|
+
Background: GridItemBackground,
|
|
509
|
+
Media: GridItemMedia,
|
|
510
|
+
Title: GridItemTitle,
|
|
511
|
+
Value: GridItemValue,
|
|
512
|
+
Description: GridItemDescription,
|
|
513
|
+
Footer: GridItemFooter,
|
|
514
|
+
Actions: GridItemActions,
|
|
515
|
+
});
|