@boyernick/standard-ui-react 0.4.2-canary.69 → 0.4.2-canary.71
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/modal.tsx +10 -46
- package/src/video-player.tsx +20 -6
package/package.json
CHANGED
package/src/modal.tsx
CHANGED
|
@@ -36,8 +36,6 @@ import {
|
|
|
36
36
|
IconArrowDownSmall,
|
|
37
37
|
IconChevronRightSmall,
|
|
38
38
|
IconCrossSmall,
|
|
39
|
-
IconZoomIn,
|
|
40
|
-
IconZoomOut,
|
|
41
39
|
} from "./icons"
|
|
42
40
|
import { cn } from "./lib/cn"
|
|
43
41
|
import { focusRing, focusRingBorder } from "./lib/focus"
|
|
@@ -89,10 +87,7 @@ export type ModalContentProps = Omit<
|
|
|
89
87
|
downloadName?: string
|
|
90
88
|
imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">
|
|
91
89
|
children?: ReactNode
|
|
92
|
-
/**
|
|
93
|
-
* Magnification when the image is clicked or the zoom control pressed.
|
|
94
|
-
* `false` turns zoom off. Default 2.
|
|
95
|
-
*/
|
|
90
|
+
/** Magnification when the image is clicked. `false` turns zoom off. Default 2. */
|
|
96
91
|
zoom?: number | false
|
|
97
92
|
}
|
|
98
93
|
|
|
@@ -234,7 +229,10 @@ const ModalImage = ({
|
|
|
234
229
|
? "motion-safe:[transition:opacity_var(--duration-lg)_linear,scale_var(--duration-md)_var(--ease-move),translate_var(--duration-md)_var(--ease-move)] motion-safe:data-[panning]:[transition-property:opacity,scale]"
|
|
235
230
|
: "transition-opacity duration-[var(--duration-lg)] ease-linear motion-reduce:transition-none",
|
|
236
231
|
!loaded && "opacity-0",
|
|
237
|
-
|
|
232
|
+
// Sized by the image itself: width and height attributes with
|
|
233
|
+
// separate max caps made a box of another shape, which object-contain
|
|
234
|
+
// letterboxed, leaving the rounded corners on empty space.
|
|
235
|
+
"h-auto w-auto rounded-lg object-contain shadow-lg select-none",
|
|
238
236
|
// Scaled from the top-left, so the pan lands the image exactly where
|
|
239
237
|
// it is measured.
|
|
240
238
|
zoom && "origin-top-left",
|
|
@@ -325,34 +323,6 @@ const ModalDismiss = () => (
|
|
|
325
323
|
</ModalControlTooltip>
|
|
326
324
|
)
|
|
327
325
|
|
|
328
|
-
const ModalZoom = ({
|
|
329
|
-
zoomed,
|
|
330
|
-
onToggle,
|
|
331
|
-
}: {
|
|
332
|
-
zoomed: boolean
|
|
333
|
-
onToggle: () => void
|
|
334
|
-
}) => (
|
|
335
|
-
<ModalControlTooltip label={zoomed ? "Zoom out" : "Zoom in"}>
|
|
336
|
-
<GlassButton
|
|
337
|
-
type="button"
|
|
338
|
-
config={mediaGlass}
|
|
339
|
-
size="md"
|
|
340
|
-
iconOnly
|
|
341
|
-
rounded
|
|
342
|
-
aria-label={zoomed ? "Zoom out" : "Zoom in"}
|
|
343
|
-
aria-pressed={zoomed}
|
|
344
|
-
onClick={onToggle}
|
|
345
|
-
className={cn(
|
|
346
|
-
modalControlClassName,
|
|
347
|
-
// Beside Download: 16px inset, a 36px control and an 8px gap.
|
|
348
|
-
"absolute top-4 left-15 z-20",
|
|
349
|
-
)}
|
|
350
|
-
>
|
|
351
|
-
{zoomed ? <IconZoomOut aria-hidden /> : <IconZoomIn aria-hidden />}
|
|
352
|
-
</GlassButton>
|
|
353
|
-
</ModalControlTooltip>
|
|
354
|
-
)
|
|
355
|
-
|
|
356
326
|
const ModalCaption = ({ children, hidden }: { children: ReactNode; hidden?: boolean }) => (
|
|
357
327
|
<p
|
|
358
328
|
className={cn(
|
|
@@ -367,8 +337,8 @@ const ModalCaption = ({ children, hidden }: { children: ReactNode; hidden?: bool
|
|
|
367
337
|
)
|
|
368
338
|
|
|
369
339
|
/**
|
|
370
|
-
* Zoom for one image:
|
|
371
|
-
* mouse or pen pans by moving; a finger drags
|
|
340
|
+
* Zoom for one image: a click magnifies at the point, then the image pans
|
|
341
|
+
* under the pointer. A mouse or pen pans by moving; a finger drags.
|
|
372
342
|
*/
|
|
373
343
|
function useImageZoom(scale: number | false) {
|
|
374
344
|
const [zoomed, setZoomed] = useState(false)
|
|
@@ -378,22 +348,17 @@ function useImageZoom(scale: number | false) {
|
|
|
378
348
|
const aim = useRef({ x: 0, y: 0 })
|
|
379
349
|
|
|
380
350
|
const toggle = useCallback(
|
|
381
|
-
(x
|
|
351
|
+
(x: number, y: number) => {
|
|
382
352
|
const image = imageRef.current
|
|
383
353
|
const frame = frameRef.current
|
|
384
354
|
if (!scale || !image || !frame) return
|
|
385
|
-
const bounds = frame.getBoundingClientRect()
|
|
386
355
|
// The zoom itself eases, towards the point.
|
|
387
356
|
delete image.dataset.panning
|
|
388
357
|
if (zoomed) {
|
|
389
358
|
image.style.translate = "0px 0px"
|
|
390
359
|
} else {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
x: x ?? bounds.left + bounds.width / 2,
|
|
394
|
-
y: y ?? bounds.top + bounds.height / 2,
|
|
395
|
-
}
|
|
396
|
-
panZoomedImage(image, bounds, aim.current.x, aim.current.y, scale)
|
|
360
|
+
aim.current = { x, y }
|
|
361
|
+
panZoomedImage(image, frame.getBoundingClientRect(), x, y, scale)
|
|
397
362
|
}
|
|
398
363
|
setZoomed(!zoomed)
|
|
399
364
|
},
|
|
@@ -507,7 +472,6 @@ export const ModalContent = ({
|
|
|
507
472
|
src={downloadSrc ?? src}
|
|
508
473
|
name={downloadName}
|
|
509
474
|
/>
|
|
510
|
-
{zoom ? <ModalZoom zoomed={zoomed} onToggle={() => toggle()} /> : null}
|
|
511
475
|
</GlassRoot>
|
|
512
476
|
</TooltipProvider>
|
|
513
477
|
</DialogPopup>
|
package/src/video-player.tsx
CHANGED
|
@@ -54,6 +54,16 @@ export type VideoPlayerProps = ComponentProps<"div"> & {
|
|
|
54
54
|
* to fix it, or `null` never to ask.
|
|
55
55
|
*/
|
|
56
56
|
crossOrigin?: "anonymous" | "use-credentials" | null
|
|
57
|
+
/**
|
|
58
|
+
* Show the mute control. Off for footage with no sound, such as screen
|
|
59
|
+
* recordings; the timeline takes its place. Default true.
|
|
60
|
+
*/
|
|
61
|
+
muteButton?: boolean
|
|
62
|
+
/**
|
|
63
|
+
* Show the picture-in-picture control where the browser supports it. The
|
|
64
|
+
* timeline takes its place when off. Default true.
|
|
65
|
+
*/
|
|
66
|
+
pictureInPictureButton?: boolean
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
export const VideoPlayer = ({
|
|
@@ -63,6 +73,8 @@ export const VideoPlayer = ({
|
|
|
63
73
|
className,
|
|
64
74
|
"aria-label": ariaLabel,
|
|
65
75
|
crossOrigin,
|
|
76
|
+
muteButton = true,
|
|
77
|
+
pictureInPictureButton = true,
|
|
66
78
|
...props
|
|
67
79
|
}: VideoPlayerProps) => {
|
|
68
80
|
const playerRef = useRef<HTMLDivElement>(null)
|
|
@@ -387,7 +399,7 @@ export const VideoPlayer = ({
|
|
|
387
399
|
<IconPlay size={14} mode="raw" className="size-3.5" aria-hidden />
|
|
388
400
|
)}
|
|
389
401
|
</GlassButton>
|
|
390
|
-
<GlassButton
|
|
402
|
+
{muteButton ? <GlassButton
|
|
391
403
|
type="button"
|
|
392
404
|
rounded
|
|
393
405
|
iconOnly
|
|
@@ -411,13 +423,15 @@ export const VideoPlayer = ({
|
|
|
411
423
|
aria-hidden
|
|
412
424
|
/>
|
|
413
425
|
)}
|
|
414
|
-
</GlassButton>
|
|
426
|
+
</GlassButton> : null}
|
|
415
427
|
<Glass
|
|
416
428
|
data-glass-radius="css"
|
|
417
429
|
className={cn(
|
|
418
430
|
controlVisibility,
|
|
419
|
-
"
|
|
420
|
-
|
|
431
|
+
"flex h-9 min-w-0 items-center gap-2 rounded-full px-3",
|
|
432
|
+
// The timeline spans whatever the side controls leave free.
|
|
433
|
+
muteButton ? "left-24 group-data-[fullscreen]:left-27" : "left-13 group-data-[fullscreen]:left-16",
|
|
434
|
+
pictureInPictureButton && pipSupported ? "right-24 group-data-[fullscreen]:right-27" : "right-13 group-data-[fullscreen]:right-16",
|
|
421
435
|
(failed || loading) && "invisible",
|
|
422
436
|
)}
|
|
423
437
|
>
|
|
@@ -495,7 +509,7 @@ export const VideoPlayer = ({
|
|
|
495
509
|
</Glass>
|
|
496
510
|
|
|
497
511
|
|
|
498
|
-
<GlassButton
|
|
512
|
+
{pictureInPictureButton ? <GlassButton
|
|
499
513
|
type="button"
|
|
500
514
|
rounded
|
|
501
515
|
iconOnly
|
|
@@ -515,7 +529,7 @@ export const VideoPlayer = ({
|
|
|
515
529
|
className="size-3.5"
|
|
516
530
|
aria-hidden
|
|
517
531
|
/>
|
|
518
|
-
</GlassButton>
|
|
532
|
+
</GlassButton> : null}
|
|
519
533
|
<GlassButton
|
|
520
534
|
type="button"
|
|
521
535
|
rounded
|