@aglyn/shared-ui-jsx 1.0.0-beta.277 → 1.0.0-beta.283

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.
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * SEARCH A STOCK PHOTO LIBRARY AND CHOOSE ONE (AGL-4066): the media picker's
19
+ * Stock photos tab, and the details of the photo chosen.
20
+ *
21
+ * Presentation only. What a search asks and where a photo goes are the
22
+ * caller's: it hands in a {@link StockPhotoSearchClient} (the console binds
23
+ * its route and the member's credentials) and receives the photo clicked,
24
+ * which it places the way it places a library file. Every result shows its
25
+ * photographer and the library it comes from, since a library's terms ask
26
+ * that its results say where they are from.
27
+ *
28
+ * Thumbnails arrive as image bytes from the caller's server and are shown
29
+ * through object URLs this component owns, so no address at the library is
30
+ * ever loaded by the browser; it fetches them a few at a time, only for the
31
+ * results on screen.
32
+ */
33
+ /** The shapes the search offers. */
34
+ export type StockPhotoSearchOrientation = 'any' | 'horizontal' | 'vertical';
35
+ /** One result, as much of it as this shows. */
36
+ export interface StockPhotoSearchHit {
37
+ /** What the caller names the photo by when it asks for its preview. */
38
+ ref: unknown;
39
+ /** Unique among the results. */
40
+ sourceKey: string;
41
+ width: number;
42
+ height: number;
43
+ photographer: string;
44
+ tags: string[];
45
+ alt?: string;
46
+ }
47
+ /** The details panel's further facts about a result. */
48
+ export interface StockPhotoDetailsHit extends StockPhotoSearchHit {
49
+ photographerUrl?: string;
50
+ pageUrl: string;
51
+ /** "Photo by … on …". */
52
+ credit: string;
53
+ license: string;
54
+ licenseUrl: string;
55
+ }
56
+ type Refusal = {
57
+ status: 'busy' | 'unconfigured' | 'refused' | 'gone';
58
+ message: string;
59
+ };
60
+ /** What the caller's client answers. */
61
+ export interface StockPhotoSearchClient<Hit extends StockPhotoSearchHit> {
62
+ status(): Promise<{
63
+ status: 'ready';
64
+ library: {
65
+ label: string;
66
+ };
67
+ } | Refusal>;
68
+ search(query: string, orientation: StockPhotoSearchOrientation): Promise<{
69
+ status: 'ok';
70
+ library: {
71
+ label: string;
72
+ };
73
+ hits: Hit[];
74
+ } | Refusal>;
75
+ preview(ref: Hit['ref']): Promise<Blob | null>;
76
+ }
77
+ export interface StockPhotoSearchPanelProps<Hit extends StockPhotoSearchHit> {
78
+ client: StockPhotoSearchClient<Hit>;
79
+ /** Where a used photo is saved, as a sentence ends: "this site's library". */
80
+ libraryName: string;
81
+ /** The longest search the library takes. */
82
+ maxQueryLength: number;
83
+ /** The result currently chosen. */
84
+ selectedKey?: string | null;
85
+ /** A result was clicked; `previewUrl` is its thumbnail, when it has loaded. */
86
+ onSelect: (hit: Hit, context: {
87
+ previewUrl: string | null;
88
+ libraryLabel: string;
89
+ }) => void;
90
+ }
91
+ /** Results shown at first, and added by each Show more. */
92
+ export declare const STOCK_PHOTO_SEARCH_PAGE = 20;
93
+ /**
94
+ * The Stock photos tab: a search box, the shape, and the results. Asks the
95
+ * client for its status once, on mount, so a deployment with no library, or
96
+ * a member who cannot add files here, is told so before typing anything.
97
+ */
98
+ export declare function StockPhotoSearchPanel<Hit extends StockPhotoSearchHit>(props: StockPhotoSearchPanelProps<Hit>): import("react").JSX.Element;
99
+ export interface StockPhotoDetailsProps {
100
+ hit: StockPhotoDetailsHit;
101
+ previewUrl: string | null;
102
+ /** The library's name: "Pixabay". */
103
+ libraryLabel: string;
104
+ /** Where using the photo saves it, as a sentence ends: "this site's library". */
105
+ libraryName: string;
106
+ /** Why using it failed, said above the facts. */
107
+ error?: string | null;
108
+ /** Small screens, where the details replace the results: back to them. */
109
+ onBack?: () => void;
110
+ }
111
+ /**
112
+ * The stock photo clicked, before it is used: a larger look, who took it,
113
+ * the license and where it will be saved. Nothing is copied until the
114
+ * caller's confirm button is pressed.
115
+ */
116
+ export declare function StockPhotoDetails(props: StockPhotoDetailsProps): import("react").JSX.Element;
117
+ export {};
@@ -0,0 +1,506 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { mdiArrowLeft, mdiImageOutline, mdiMagnify } from "@mdi/js";
20
+ import { Alert, Box, Button, ButtonBase, InputAdornment, LinearProgress, Link, Skeleton, Stack, TextField, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
21
+ import { useCallback, useEffect, useRef, useState } from "react";
22
+ import EmptyStateComponent from "./empty-state.component.js";
23
+ import { MdiIcon } from "./mdi-icon/index.js";
24
+ /** Results shown at first, and added by each Show more. */ export const STOCK_PHOTO_SEARCH_PAGE = 20;
25
+ /** Previews fetched at once. */ const PREVIEWS_AT_ONCE = 4;
26
+ /** Skeleton tiles while a search runs. */ const SKELETON_TILES = 12;
27
+ const ORIENTATION_LABELS = [
28
+ [
29
+ 'any',
30
+ 'Any'
31
+ ],
32
+ [
33
+ 'horizontal',
34
+ 'Landscape'
35
+ ],
36
+ [
37
+ 'vertical',
38
+ 'Portrait'
39
+ ]
40
+ ];
41
+ /** The grid every state of the results lays out in. */ const gridSx = (theme)=>({
42
+ display: 'grid',
43
+ gap: theme.spacing(2),
44
+ gridTemplateColumns: `repeat(auto-fill, minmax(${theme.spacing(20)}, 1fr))`
45
+ });
46
+ /** A result's tile: the thumbnail, the photographer and the library. */ function StockPhotoTile(props) {
47
+ const { hit, previewUrl, libraryLabel, selected, onClick } = props;
48
+ const described = hit.alt || hit.tags.slice(0, 3).join(', ');
49
+ return /*#__PURE__*/ _jsxs(ButtonBase, {
50
+ onClick: onClick,
51
+ "aria-pressed": selected,
52
+ "aria-label": `Photo by ${hit.photographer} on ${libraryLabel}${described ? `: ${described}` : ''}`,
53
+ "data-testid": "stock-photo-tile",
54
+ sx: (theme)=>({
55
+ display: 'flex',
56
+ flexDirection: 'column',
57
+ alignItems: 'stretch',
58
+ textAlign: 'left',
59
+ borderRadius: 1,
60
+ overflow: 'hidden',
61
+ border: 1,
62
+ borderColor: selected ? 'primary.main' : 'divider',
63
+ boxShadow: selected ? `0 0 0 1px ${theme.palette.primary.main}` : 'none',
64
+ bgcolor: 'background.paper',
65
+ transition: theme.transitions.create([
66
+ 'border-color',
67
+ 'box-shadow'
68
+ ]),
69
+ '&:hover': {
70
+ borderColor: selected ? 'primary.main' : 'text.primary'
71
+ },
72
+ '&.Mui-focusVisible': {
73
+ outline: `${theme.spacing(0.25)} solid ${theme.palette.primary.main}`,
74
+ outlineOffset: theme.spacing(0.25)
75
+ }
76
+ }),
77
+ children: [
78
+ /*#__PURE__*/ _jsx(Box, {
79
+ sx: {
80
+ aspectRatio: '4 / 3',
81
+ bgcolor: 'action.hover',
82
+ display: 'flex',
83
+ alignItems: 'center',
84
+ justifyContent: 'center',
85
+ color: 'text.disabled'
86
+ },
87
+ children: previewUrl ? /*#__PURE__*/ _jsx(Box, {
88
+ component: "img",
89
+ src: previewUrl,
90
+ alt: "",
91
+ sx: {
92
+ width: '100%',
93
+ height: '100%',
94
+ objectFit: 'cover',
95
+ display: 'block'
96
+ }
97
+ }) : previewUrl === null ? /*#__PURE__*/ _jsx(MdiIcon, {
98
+ path: mdiImageOutline,
99
+ fontSize: "large"
100
+ }) : /*#__PURE__*/ _jsx(Skeleton, {
101
+ variant: "rectangular",
102
+ sx: {
103
+ width: '100%',
104
+ height: '100%'
105
+ }
106
+ })
107
+ }),
108
+ /*#__PURE__*/ _jsxs(Box, {
109
+ sx: {
110
+ px: 1,
111
+ py: 0.75,
112
+ minWidth: 0
113
+ },
114
+ children: [
115
+ /*#__PURE__*/ _jsx(Typography, {
116
+ variant: "body2",
117
+ noWrap: true,
118
+ children: hit.photographer
119
+ }),
120
+ /*#__PURE__*/ _jsx(Typography, {
121
+ variant: "caption",
122
+ color: "text.secondary",
123
+ noWrap: true,
124
+ component: "div",
125
+ children: libraryLabel
126
+ })
127
+ ]
128
+ })
129
+ ]
130
+ });
131
+ }
132
+ /**
133
+ * The Stock photos tab: a search box, the shape, and the results. Asks the
134
+ * client for its status once, on mount, so a deployment with no library, or
135
+ * a member who cannot add files here, is told so before typing anything.
136
+ */ export function StockPhotoSearchPanel(props) {
137
+ const { client, libraryName, maxQueryLength, selectedKey, onSelect } = props;
138
+ const [status, setStatus] = useState({
139
+ status: 'loading'
140
+ });
141
+ const [query, setQuery] = useState('');
142
+ const [orientation, setOrientation] = useState('any');
143
+ const [search, setSearch] = useState({
144
+ kind: 'idle'
145
+ });
146
+ const [shown, setShown] = useState(STOCK_PHOTO_SEARCH_PAGE);
147
+ const [previews, setPreviews] = useState({});
148
+ const owned = useRef(new Map());
149
+ const asked = useRef(new Set());
150
+ const latest = useRef(0);
151
+ useEffect(()=>{
152
+ let live = true;
153
+ setStatus({
154
+ status: 'loading'
155
+ });
156
+ client.status().then((answer)=>{
157
+ if (live) setStatus(answer);
158
+ });
159
+ return ()=>{
160
+ live = false;
161
+ };
162
+ }, [
163
+ client
164
+ ]);
165
+ // The object URLs are this component's: let them go with it.
166
+ useEffect(()=>{
167
+ const urls = owned.current;
168
+ return ()=>{
169
+ for (const url of urls.values())if (url) URL.revokeObjectURL(url);
170
+ urls.clear();
171
+ };
172
+ }, []);
173
+ const libraryLabel = status.status === 'ready' ? status.library.label : '';
174
+ const run = useCallback(async (words, shape)=>{
175
+ const trimmed = words.replace(/\s+/g, ' ').trim();
176
+ if (!trimmed) return;
177
+ const ticket = ++latest.current;
178
+ setSearch({
179
+ kind: 'searching',
180
+ query: trimmed
181
+ });
182
+ setShown(STOCK_PHOTO_SEARCH_PAGE);
183
+ const answer = await client.search(trimmed, shape);
184
+ if (ticket !== latest.current) return;
185
+ setSearch(answer.status === 'ok' ? {
186
+ kind: 'results',
187
+ query: trimmed,
188
+ hits: answer.hits
189
+ } : {
190
+ kind: 'refused',
191
+ refusal: answer,
192
+ query: trimmed
193
+ });
194
+ }, [
195
+ client
196
+ ]);
197
+ const visible = search.kind === 'results' ? search.hits.slice(0, shown) : [];
198
+ // Previews for the results on screen, a few at a time, each asked once.
199
+ useEffect(()=>{
200
+ let live = true;
201
+ const queue = visible.filter((hit)=>!asked.current.has(hit.sourceKey));
202
+ const next = async ()=>{
203
+ const hit = queue.shift();
204
+ if (!hit || !live) return;
205
+ asked.current.add(hit.sourceKey);
206
+ const blob = await client.preview(hit.ref);
207
+ if (!live) {
208
+ // Superseded: the next run asks for it again, for the results then on screen.
209
+ asked.current.delete(hit.sourceKey);
210
+ return;
211
+ }
212
+ const url = blob ? URL.createObjectURL(blob) : null;
213
+ owned.current.set(hit.sourceKey, url);
214
+ setPreviews((held)=>_extends({}, held, {
215
+ [hit.sourceKey]: url
216
+ }));
217
+ return next();
218
+ };
219
+ void Promise.all(Array.from({
220
+ length: PREVIEWS_AT_ONCE
221
+ }, next));
222
+ return ()=>{
223
+ live = false;
224
+ };
225
+ // `visible` is derived from these.
226
+ // eslint-disable-next-line react-hooks/exhaustive-deps
227
+ }, [
228
+ client,
229
+ search,
230
+ shown
231
+ ]);
232
+ const submit = (event)=>{
233
+ event.preventDefault();
234
+ void run(query, orientation);
235
+ };
236
+ if (status.status === 'loading') {
237
+ return /*#__PURE__*/ _jsx(LinearProgress, {
238
+ "aria-label": "Loading stock photos"
239
+ });
240
+ }
241
+ if (status.status !== 'ready') {
242
+ return /*#__PURE__*/ _jsx(Alert, {
243
+ severity: status.status === 'refused' ? 'warning' : 'info',
244
+ "data-testid": "stock-photos-unavailable",
245
+ children: status.message
246
+ });
247
+ }
248
+ return /*#__PURE__*/ _jsxs(Stack, {
249
+ spacing: 2,
250
+ "data-testid": "stock-photo-search",
251
+ children: [
252
+ /*#__PURE__*/ _jsxs(Box, {
253
+ component: "form",
254
+ role: "search",
255
+ onSubmit: submit,
256
+ sx: {
257
+ display: 'flex',
258
+ flexWrap: 'wrap',
259
+ gap: 1,
260
+ alignItems: 'center'
261
+ },
262
+ children: [
263
+ /*#__PURE__*/ _jsx(TextField, {
264
+ size: "small",
265
+ value: query,
266
+ onChange: (event)=>setQuery(event.target.value.slice(0, maxQueryLength)),
267
+ placeholder: `Search ${libraryLabel} photos`,
268
+ slotProps: {
269
+ htmlInput: {
270
+ maxLength: maxQueryLength,
271
+ 'aria-label': `Search ${libraryLabel} photos`
272
+ },
273
+ input: {
274
+ startAdornment: /*#__PURE__*/ _jsx(InputAdornment, {
275
+ position: "start",
276
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
277
+ path: mdiMagnify
278
+ })
279
+ })
280
+ }
281
+ },
282
+ sx: {
283
+ flex: 1,
284
+ minWidth: (theme)=>theme.spacing(25)
285
+ }
286
+ }),
287
+ /*#__PURE__*/ _jsx(ToggleButtonGroup, {
288
+ size: "small",
289
+ exclusive: true,
290
+ value: orientation,
291
+ "aria-label": "Photo shape",
292
+ onChange: (_event, value)=>{
293
+ if (!value) return;
294
+ setOrientation(value);
295
+ if (search.kind !== 'idle') void run(search.query, value);
296
+ },
297
+ children: ORIENTATION_LABELS.map(([value, label])=>/*#__PURE__*/ _jsx(ToggleButton, {
298
+ value: value,
299
+ children: label
300
+ }, value))
301
+ }),
302
+ /*#__PURE__*/ _jsx(Button, {
303
+ type: "submit",
304
+ variant: "contained",
305
+ disabled: !query.trim() || search.kind === 'searching',
306
+ children: 'Search'
307
+ })
308
+ ]
309
+ }),
310
+ /*#__PURE__*/ _jsx(Typography, {
311
+ variant: "body2",
312
+ color: "text.secondary",
313
+ children: `Photos come from ${libraryLabel}. The one you use is saved to ${libraryName} with the photographer's credit.`
314
+ }),
315
+ search.kind === 'idle' ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
316
+ label: "Find a photo for your page",
317
+ description: "Describe what the photo should show, such as a coffee shop counter or a mountain lake, then choose Search."
318
+ }) : search.kind === 'searching' ? /*#__PURE__*/ _jsx(Box, {
319
+ sx: gridSx,
320
+ "aria-busy": "true",
321
+ "aria-label": "Searching",
322
+ children: Array.from({
323
+ length: SKELETON_TILES
324
+ }, (_, index)=>/*#__PURE__*/ _jsx(Skeleton, {
325
+ variant: "rectangular",
326
+ sx: {
327
+ aspectRatio: '4 / 3',
328
+ height: 'auto',
329
+ borderRadius: 1
330
+ }
331
+ }, index))
332
+ }) : search.kind === 'refused' ? /*#__PURE__*/ _jsx(Alert, {
333
+ severity: search.refusal.status === 'refused' ? 'warning' : 'info',
334
+ action: search.refusal.status === 'busy' ? /*#__PURE__*/ _jsx(Button, {
335
+ color: "inherit",
336
+ size: "small",
337
+ onClick: ()=>void run(search.query, orientation),
338
+ children: 'Try again'
339
+ }) : undefined,
340
+ children: search.refusal.message
341
+ }) : search.hits.length === 0 ? /*#__PURE__*/ _jsx(EmptyStateComponent, {
342
+ label: `No photos found for “${search.query}”`,
343
+ description: "Try fewer words, or describe it differently."
344
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
345
+ children: [
346
+ /*#__PURE__*/ _jsx(Box, {
347
+ sx: gridSx,
348
+ children: visible.map((hit)=>/*#__PURE__*/ _jsx(StockPhotoTile, {
349
+ hit: hit,
350
+ previewUrl: previews[hit.sourceKey],
351
+ libraryLabel: libraryLabel,
352
+ selected: hit.sourceKey === selectedKey,
353
+ onClick: ()=>{
354
+ var _previews_hit_sourceKey;
355
+ return onSelect(hit, {
356
+ previewUrl: (_previews_hit_sourceKey = previews[hit.sourceKey]) != null ? _previews_hit_sourceKey : null,
357
+ libraryLabel
358
+ });
359
+ }
360
+ }, hit.sourceKey))
361
+ }),
362
+ search.hits.length > shown ? /*#__PURE__*/ _jsx(Button, {
363
+ onClick: ()=>setShown((count)=>count + STOCK_PHOTO_SEARCH_PAGE),
364
+ sx: {
365
+ alignSelf: 'center'
366
+ },
367
+ children: 'Show more photos'
368
+ }) : null
369
+ ]
370
+ })
371
+ ]
372
+ });
373
+ }
374
+ /** A caption over one fact. */ const Label = ({ children })=>/*#__PURE__*/ _jsx(Typography, {
375
+ variant: "caption",
376
+ color: "text.secondary",
377
+ component: "div",
378
+ sx: {
379
+ mb: 0.5
380
+ },
381
+ children: children
382
+ });
383
+ /** An outside page, opened in a new tab. */ const OutLink = ({ href, children })=>/*#__PURE__*/ _jsx(Link, {
384
+ href: href,
385
+ target: "_blank",
386
+ rel: "noopener noreferrer",
387
+ children: children
388
+ });
389
+ /**
390
+ * The stock photo clicked, before it is used: a larger look, who took it,
391
+ * the license and where it will be saved. Nothing is copied until the
392
+ * caller's confirm button is pressed.
393
+ */ export function StockPhotoDetails(props) {
394
+ const { hit, previewUrl, libraryLabel, libraryName, error, onBack } = props;
395
+ return /*#__PURE__*/ _jsxs(Stack, {
396
+ spacing: 2,
397
+ "data-testid": "stock-photo-details",
398
+ children: [
399
+ onBack ? /*#__PURE__*/ _jsx(Button, {
400
+ size: "small",
401
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
402
+ path: mdiArrowLeft
403
+ }),
404
+ onClick: onBack,
405
+ sx: {
406
+ alignSelf: 'flex-start'
407
+ },
408
+ children: 'Back to photos'
409
+ }) : null,
410
+ /*#__PURE__*/ _jsx(Box, {
411
+ sx: {
412
+ aspectRatio: `${hit.width} / ${hit.height}`,
413
+ maxHeight: (theme)=>theme.spacing(30),
414
+ borderRadius: 1,
415
+ overflow: 'hidden',
416
+ bgcolor: 'action.hover',
417
+ display: 'flex',
418
+ alignItems: 'center',
419
+ justifyContent: 'center',
420
+ color: 'text.disabled'
421
+ },
422
+ children: previewUrl ? /*#__PURE__*/ _jsx(Box, {
423
+ component: "img",
424
+ src: previewUrl,
425
+ alt: hit.alt || `Photo by ${hit.photographer}`,
426
+ sx: {
427
+ width: '100%',
428
+ height: '100%',
429
+ objectFit: 'contain',
430
+ display: 'block'
431
+ }
432
+ }) : /*#__PURE__*/ _jsx(MdiIcon, {
433
+ path: mdiImageOutline,
434
+ fontSize: "large"
435
+ })
436
+ }),
437
+ /*#__PURE__*/ _jsx(Typography, {
438
+ variant: "subtitle1",
439
+ component: "h3",
440
+ children: hit.photographerUrl ? /*#__PURE__*/ _jsxs(_Fragment, {
441
+ children: [
442
+ 'Photo by ',
443
+ /*#__PURE__*/ _jsx(OutLink, {
444
+ href: hit.photographerUrl,
445
+ children: hit.photographer
446
+ })
447
+ ]
448
+ }) : `Photo by ${hit.photographer}`
449
+ }),
450
+ error ? /*#__PURE__*/ _jsx(Alert, {
451
+ severity: "warning",
452
+ children: error
453
+ }) : null,
454
+ /*#__PURE__*/ _jsxs(Box, {
455
+ children: [
456
+ /*#__PURE__*/ _jsx(Label, {
457
+ children: 'Credit saved with the photo'
458
+ }),
459
+ /*#__PURE__*/ _jsx(Typography, {
460
+ variant: "body2",
461
+ children: hit.credit
462
+ })
463
+ ]
464
+ }),
465
+ /*#__PURE__*/ _jsxs(Box, {
466
+ children: [
467
+ /*#__PURE__*/ _jsx(Label, {
468
+ children: 'License'
469
+ }),
470
+ /*#__PURE__*/ _jsx(Typography, {
471
+ variant: "body2",
472
+ children: hit.licenseUrl ? /*#__PURE__*/ _jsx(OutLink, {
473
+ href: hit.licenseUrl,
474
+ children: hit.license
475
+ }) : hit.license
476
+ })
477
+ ]
478
+ }),
479
+ /*#__PURE__*/ _jsxs(Box, {
480
+ children: [
481
+ /*#__PURE__*/ _jsx(Label, {
482
+ children: 'Size'
483
+ }),
484
+ /*#__PURE__*/ _jsx(Typography, {
485
+ variant: "body2",
486
+ children: `${hit.width} × ${hit.height} pixels`
487
+ })
488
+ ]
489
+ }),
490
+ /*#__PURE__*/ _jsx(Typography, {
491
+ variant: "body2",
492
+ children: /*#__PURE__*/ _jsx(OutLink, {
493
+ href: hit.pageUrl,
494
+ children: `See it on ${libraryLabel}`
495
+ })
496
+ }),
497
+ /*#__PURE__*/ _jsx(Typography, {
498
+ variant: "body2",
499
+ color: "text.secondary",
500
+ children: `When you use it, a copy is saved to ${libraryName} and placed from there. If the library already has this photo, that copy is used.`
501
+ })
502
+ ]
503
+ });
504
+ }
505
+
506
+ //# sourceMappingURL=stock-photo-search.component.js.map