@enigmax/primitives 0.24.0 → 0.26.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/dist/chunk-4SZ5N7BA.js +121 -0
- package/dist/chunk-EYZ366LP.js +189 -0
- package/dist/chunk-ISXB5RUH.js +380 -0
- package/dist/chunk-JJVZYW5C.js +946 -0
- package/dist/chunk-KVLSOQTI.js +66 -0
- package/dist/chunk-QIIV2QSV.js +108 -0
- package/dist/{chunk-QJJ34E4G.js → chunk-RMNXVWQ4.js} +32 -15
- package/dist/chunk-RUEQ3TGP.js +155 -0
- package/dist/clipboard-menu-BvXJCSFN.d.ts +93 -0
- package/dist/{color-OPV3BJV6.js → color-T63FLJNH.js} +183 -120
- package/dist/context-BfjYGPnH.d.ts +54 -0
- package/dist/{clipboard-menu-B_ouitfS.d.ts → context-menu-D3FtTn7v.d.ts} +1 -91
- package/dist/{index-ZPvlf9vs.d.ts → index-qXOkYQCU.d.ts} +4 -0
- package/dist/index.d.ts +266 -2
- package/dist/index.js +4 -2
- package/dist/menu-MSQSE6U5.js +54 -0
- package/dist/menu-PNQRTRNV.js +33 -0
- package/dist/next/index.d.ts +7 -3
- package/dist/next/index.js +11 -7
- package/dist/react/context-menu.d.ts +8 -54
- package/dist/react/image.d.ts +109 -0
- package/dist/react/image.js +3 -0
- package/dist/react/index.d.ts +8 -4
- package/dist/react/index.js +11 -7
- package/dist/react/input.d.ts +1 -1
- package/dist/react/input.js +2 -2
- package/dist/react/video.d.ts +120 -0
- package/dist/react/video.js +3 -0
- package/dist/react-router/index.d.ts +7 -3
- package/dist/react-router/index.js +11 -7
- package/dist/viewer-73LSE6RR.js +3 -0
- package/package.json +15 -2
- package/recipes/color/styles.css +20 -0
- package/recipes/image/styles.css +182 -0
- package/recipes/video/styles.css +154 -0
- package/registry.json +477 -3
- package/src/core/image-viewer.ts +222 -0
- package/src/core/player.ts +303 -0
- package/src/index.ts +52 -0
- package/src/react/image/icons.tsx +55 -0
- package/src/react/image/index.tsx +185 -0
- package/src/react/image/menu.tsx +76 -0
- package/src/react/image/styles.ts +213 -0
- package/src/react/image/types.ts +113 -0
- package/src/react/image/viewer.tsx +571 -0
- package/src/react/index.ts +3 -0
- package/src/react/input/color-styles.ts +20 -0
- package/src/react/input/color-swatch.tsx +96 -0
- package/src/react/input/color.tsx +84 -23
- package/src/react/input/index.tsx +27 -4
- package/src/react/input/types.ts +4 -0
- package/src/react/video/icons.tsx +135 -0
- package/src/react/video/index.tsx +724 -0
- package/src/react/video/menu.tsx +66 -0
- package/src/react/video/rail.tsx +99 -0
- package/src/react/video/styles.ts +161 -0
- package/src/react/video/types.ts +135 -0
- package/dist/chunk-S7EE57YB.js +0 -9
- /package/dist/{chunk-MSOCCQGH.js → chunk-T7I6WJSN.js} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { CSSProperties, ReactNode, Ref } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The swatch in the field, and the box the panel hangs from.
|
|
7
|
+
*
|
|
8
|
+
* Its own module, and deliberately NOT part of the picker's chunk. The picker is imported on
|
|
9
|
+
* demand, so a swatch that lived in there would not exist for as long as that request takes -
|
|
10
|
+
* the field renders as a bare text box, the affordance appears late and shifts the row, and a
|
|
11
|
+
* press in that window reaches nothing at all. On a fast connection that is a blink; on a
|
|
12
|
+
* cold cache it is the second or two somebody spends clicking a swatch that is not there yet.
|
|
13
|
+
*
|
|
14
|
+
* So the swatch ships with the field and the panel arrives behind it. It costs the markup
|
|
15
|
+
* below in the base chunk and no colour maths at all: the fill is handed to the browser, which
|
|
16
|
+
* is the only parser that already knows every notation a value could be written in.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Enough of a look to be a swatch, for the frames before the panel's stylesheet exists.
|
|
21
|
+
*
|
|
22
|
+
* The sheet ships with the picker, so the swatch rendered ahead of it would otherwise be a
|
|
23
|
+
* default browser button for as long as that request takes - which trades a missing control
|
|
24
|
+
* for an ugly one. Written against the same custom properties, so a project that themed the
|
|
25
|
+
* picker themes this too, and dropped entirely the moment the real swatch replaces it.
|
|
26
|
+
*/
|
|
27
|
+
const BASELINE: CSSProperties = {
|
|
28
|
+
display: "block",
|
|
29
|
+
boxSizing: "border-box",
|
|
30
|
+
width: "1.25rem",
|
|
31
|
+
height: "1.25rem",
|
|
32
|
+
padding: 0,
|
|
33
|
+
background: "none",
|
|
34
|
+
border: "1px solid var(--enigma-color-border, #404040)",
|
|
35
|
+
borderRadius: "0.3125rem",
|
|
36
|
+
overflow: "hidden",
|
|
37
|
+
cursor: "pointer"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface ColorSwatchProps {
|
|
41
|
+
/** The value to paint, exactly as the field holds it. Anything unparseable paints nothing. */
|
|
42
|
+
value?: string;
|
|
43
|
+
/** Whether the panel is up, for `aria-expanded`. */
|
|
44
|
+
open?: boolean;
|
|
45
|
+
/** The value is not a colour: the swatch shows the chequerboard rather than a stale fill. */
|
|
46
|
+
invalid?: boolean;
|
|
47
|
+
/** The field is disabled or read-only. */
|
|
48
|
+
locked?: boolean;
|
|
49
|
+
label: string;
|
|
50
|
+
anchorRef?: Ref<HTMLSpanElement>;
|
|
51
|
+
buttonRef?: Ref<HTMLButtonElement>;
|
|
52
|
+
onPress?: () => void;
|
|
53
|
+
/** The picker's stylesheet has not arrived yet, so carry the few declarations it needs. */
|
|
54
|
+
baseline?: boolean;
|
|
55
|
+
/** The panel, once its code is here. */
|
|
56
|
+
children?: ReactNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ColorSwatch({ value, open = false, invalid = false, locked = false, label, anchorRef, buttonRef, onPress, baseline = false, children }: ColorSwatchProps): ReactNode {
|
|
60
|
+
return (
|
|
61
|
+
<span
|
|
62
|
+
ref={anchorRef}
|
|
63
|
+
data-enigma-color=""
|
|
64
|
+
data-open={open ? "" : undefined}
|
|
65
|
+
style={baseline ? { position: "relative", display: "inline-flex", flex: "none" } : undefined}
|
|
66
|
+
>
|
|
67
|
+
<button
|
|
68
|
+
ref={buttonRef}
|
|
69
|
+
// Never a bare `<button>`: inside a form it would default to submit, so opening
|
|
70
|
+
// the picker would post the half-filled form. The same trap the reveal has.
|
|
71
|
+
type="button"
|
|
72
|
+
data-enigma-color-swatch=""
|
|
73
|
+
data-enigma-color-checkers=""
|
|
74
|
+
data-invalid={invalid ? "" : undefined}
|
|
75
|
+
aria-haspopup="dialog"
|
|
76
|
+
aria-expanded={open}
|
|
77
|
+
aria-label={label}
|
|
78
|
+
title={label}
|
|
79
|
+
disabled={locked}
|
|
80
|
+
style={baseline ? BASELINE : undefined}
|
|
81
|
+
onClick={onPress}
|
|
82
|
+
>
|
|
83
|
+
{/* The browser parses the value for us: an unparseable string simply applies
|
|
84
|
+
nothing, which is why the swatch needs no colour maths to fall back on. */}
|
|
85
|
+
<span
|
|
86
|
+
data-enigma-color-fill=""
|
|
87
|
+
style={{
|
|
88
|
+
background: invalid ? undefined : value,
|
|
89
|
+
...(baseline ? { display: "block", width: "100%", height: "100%" } : null)
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
</button>
|
|
93
|
+
{children}
|
|
94
|
+
</span>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { writeValue } from "@/react/input/write-value";
|
|
4
|
+
import { ColorSwatch } from "@/react/input/color-swatch";
|
|
4
5
|
import { COLOR_STYLES } from "@/react/input/color-styles";
|
|
5
6
|
import type { ColorLabels, ColorPanelPlacement } from "@/react/input/types";
|
|
6
7
|
import { parseColor, formatColor, rgbToHsv, hsvToRgb, toHex, colorEquals, type Hsv, type ColorFormat } from "@/core/color";
|
|
@@ -47,6 +48,10 @@ const STEP = { fine: 0.01, coarse: 0.1, hue: 1, hueCoarse: 10 };
|
|
|
47
48
|
/** Kept clear of the window edge when deciding which side to open on. */
|
|
48
49
|
const MARGIN = 12;
|
|
49
50
|
|
|
51
|
+
/** The notations the readout cycles through, in the order the button steps over them. */
|
|
52
|
+
const FORMATS: ColorFormat[] = ["hex", "rgb", "hsl"];
|
|
53
|
+
const FORMAT_LABELS: Record<ColorFormat, string> = { hex: "HEX", rgb: "RGB", hsl: "HSL" };
|
|
54
|
+
|
|
50
55
|
let injected = false;
|
|
51
56
|
|
|
52
57
|
function injectStyles(): void {
|
|
@@ -76,6 +81,11 @@ export interface ColorExtrasProps {
|
|
|
76
81
|
styles?: boolean;
|
|
77
82
|
/** The field is disabled or read-only: the swatch is inert and the panel never opens. */
|
|
78
83
|
locked?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* The swatch was already pressed, while this module was still being fetched. Opens the
|
|
86
|
+
* panel on the first render rather than asking for the press a second time.
|
|
87
|
+
*/
|
|
88
|
+
openOnMount?: boolean;
|
|
79
89
|
labels?: ColorLabels;
|
|
80
90
|
}
|
|
81
91
|
|
|
@@ -89,6 +99,7 @@ export function ColorExtras({
|
|
|
89
99
|
placement = "auto",
|
|
90
100
|
styles = true,
|
|
91
101
|
locked = false,
|
|
102
|
+
openOnMount = false,
|
|
92
103
|
labels
|
|
93
104
|
}: ColorExtrasProps): ReactNode {
|
|
94
105
|
// Before paint: a sheet applied after the first frame shows the panel unstyled first.
|
|
@@ -99,7 +110,7 @@ export function ColorExtras({
|
|
|
99
110
|
const swatchRef = useRef<HTMLButtonElement | null>(null);
|
|
100
111
|
const areaRef = useRef<HTMLDivElement | null>(null);
|
|
101
112
|
|
|
102
|
-
const [open, setOpen] = useState(
|
|
113
|
+
const [open, setOpen] = useState(openOnMount && !locked);
|
|
103
114
|
const [side, setSide] = useState<"top" | "bottom">(placement === "top" ? "top" : "bottom");
|
|
104
115
|
|
|
105
116
|
const parsed = useMemo(() => parseColor(value), [value]);
|
|
@@ -296,6 +307,33 @@ export function ColorExtras({
|
|
|
296
307
|
}
|
|
297
308
|
}, [commit, hsv]);
|
|
298
309
|
|
|
310
|
+
/* -------- the readout, which is the value in whichever notation you want to read it -------- */
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Which notation the panel PRINTS the colour in, and only that.
|
|
314
|
+
*
|
|
315
|
+
* `format` is the caller's contract - the spelling their column, their API or their CSS
|
|
316
|
+
* expects - so cycling this button never changes what is written into the field. It
|
|
317
|
+
* changes what you can read out of the panel and paste elsewhere, which is what the
|
|
318
|
+
* browser's own picker offers and what a picker showing no code at all cannot.
|
|
319
|
+
*/
|
|
320
|
+
const [shown, setShown] = useState<ColorFormat>(format);
|
|
321
|
+
useEffect(() => setShown(format), [format]);
|
|
322
|
+
|
|
323
|
+
/** What is being typed, while it is being typed. Null means "show the current colour". */
|
|
324
|
+
const [draft, setDraft] = useState<string | null>(null);
|
|
325
|
+
|
|
326
|
+
const readout = useMemo(() => formatColor(rgb, shown, { alpha }), [rgb, shown, alpha]);
|
|
327
|
+
const nextFormat = FORMATS[(FORMATS.indexOf(shown) + 1) % FORMATS.length] as ColorFormat;
|
|
328
|
+
|
|
329
|
+
const typed = useCallback((next: string) => {
|
|
330
|
+
setDraft(next);
|
|
331
|
+
const colour = parseColor(next);
|
|
332
|
+
// Applied as it becomes a colour, so the square and the rails follow a pasted value
|
|
333
|
+
// rather than waiting for a blur. Half-typed text simply is not one yet.
|
|
334
|
+
if (colour) commit(rgbToHsv(alpha ? colour : { ...colour, a: 1 }, hsv.h));
|
|
335
|
+
}, [commit, alpha, hsv.h]);
|
|
336
|
+
|
|
299
337
|
/* -------- rendering -------- */
|
|
300
338
|
|
|
301
339
|
const css = useMemo(() => formatColor(rgb, "hex", { alpha: true }), [rgb]);
|
|
@@ -306,27 +344,16 @@ export function ColorExtras({
|
|
|
306
344
|
const panelStyle = { "--enigma-color-hue": Math.round(hsv.h), "--enigma-color-opaque": opaque } as CSSProperties;
|
|
307
345
|
|
|
308
346
|
return (
|
|
309
|
-
<
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
aria-expanded={open}
|
|
320
|
-
aria-label={text.open ?? "Pick a colour"}
|
|
321
|
-
title={text.open ?? "Pick a colour"}
|
|
322
|
-
disabled={locked}
|
|
323
|
-
onClick={() => (open ? close(true) : setOpenState(true))}
|
|
324
|
-
>
|
|
325
|
-
{/* The browser parses the value for us: an unparseable string simply applies
|
|
326
|
-
nothing, which is why the swatch needs no colour maths to fall back on. */}
|
|
327
|
-
<span data-enigma-color-fill="" style={{ background: parsed ? css : undefined }} />
|
|
328
|
-
</button>
|
|
329
|
-
|
|
347
|
+
<ColorSwatch
|
|
348
|
+
anchorRef={anchorRef}
|
|
349
|
+
buttonRef={swatchRef}
|
|
350
|
+
value={css}
|
|
351
|
+
invalid={!parsed}
|
|
352
|
+
open={open}
|
|
353
|
+
locked={locked}
|
|
354
|
+
label={text.open ?? "Pick a colour"}
|
|
355
|
+
onPress={() => (open ? close(true) : setOpenState(true))}
|
|
356
|
+
>
|
|
330
357
|
{open && !locked && (
|
|
331
358
|
<div
|
|
332
359
|
ref={panelRef}
|
|
@@ -421,6 +448,40 @@ export function ColorExtras({
|
|
|
421
448
|
</div>
|
|
422
449
|
</div>
|
|
423
450
|
|
|
451
|
+
{/* The colour as text, editable, with the notation next to it. A picker
|
|
452
|
+
that shows no code is one you cannot read a value out of, cannot paste
|
|
453
|
+
one into, and cannot check against the hex somebody sent you. */}
|
|
454
|
+
<div data-enigma-color-value="">
|
|
455
|
+
<button
|
|
456
|
+
type="button"
|
|
457
|
+
data-enigma-color-format=""
|
|
458
|
+
aria-label={`${text.formatAs ?? "Show as"} ${FORMAT_LABELS[nextFormat]}`}
|
|
459
|
+
title={`${text.formatAs ?? "Show as"} ${FORMAT_LABELS[nextFormat]}`}
|
|
460
|
+
onClick={() => { setShown(nextFormat); setDraft(null); }}
|
|
461
|
+
>
|
|
462
|
+
{FORMAT_LABELS[shown]}
|
|
463
|
+
</button>
|
|
464
|
+
<input
|
|
465
|
+
type="text"
|
|
466
|
+
data-enigma-color-input=""
|
|
467
|
+
value={draft ?? readout}
|
|
468
|
+
aria-label={text.value ?? "Colour value"}
|
|
469
|
+
spellCheck={false}
|
|
470
|
+
autoComplete="off"
|
|
471
|
+
autoCorrect="off"
|
|
472
|
+
autoCapitalize="off"
|
|
473
|
+
onChange={(event) => typed(event.target.value)}
|
|
474
|
+
// What was typed goes back to the canonical spelling the moment the
|
|
475
|
+
// field is left, the way the input itself normalizes on blur.
|
|
476
|
+
onBlur={() => setDraft(null)}
|
|
477
|
+
onKeyDown={(event) => {
|
|
478
|
+
if (event.key !== "Enter") return;
|
|
479
|
+
event.preventDefault();
|
|
480
|
+
setDraft(null);
|
|
481
|
+
}}
|
|
482
|
+
/>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
424
485
|
{swatches && swatches.length > 0 && (
|
|
425
486
|
<div data-enigma-color-swatches="" role="group" aria-label={text.swatches ?? "Preset colours"}>
|
|
426
487
|
{swatches.map((preset) => {
|
|
@@ -445,7 +506,7 @@ export function ColorExtras({
|
|
|
445
506
|
)}
|
|
446
507
|
</div>
|
|
447
508
|
)}
|
|
448
|
-
</
|
|
509
|
+
</ColorSwatch>
|
|
449
510
|
);
|
|
450
511
|
}
|
|
451
512
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Icon } from "@/react/input/icon";
|
|
4
|
+
import { ColorSwatch } from "@/react/input/color-swatch";
|
|
4
5
|
import { INPUT_ICON_PATHS } from "@/core/input-icons";
|
|
5
6
|
import { writeValue } from "@/react/input/write-value";
|
|
6
7
|
import type { AnyInputProps, InputProps, FieldAction, BreachState } from "@/react/input/types";
|
|
@@ -96,6 +97,14 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(pro
|
|
|
96
97
|
const innerRef = useRef<HTMLInputElement | null>(null);
|
|
97
98
|
const [element, setElement] = useState<HTMLInputElement | null>(null);
|
|
98
99
|
const [revealed, setRevealed] = useState(false);
|
|
100
|
+
/**
|
|
101
|
+
* The swatch was pressed before the picker's code arrived.
|
|
102
|
+
*
|
|
103
|
+
* Remembered rather than dropped: the press was a request to open the panel, and a
|
|
104
|
+
* control that ignores one because a chunk was in flight is a control that feels broken
|
|
105
|
+
* for exactly as long as the network is slow.
|
|
106
|
+
*/
|
|
107
|
+
const [colorWanted, setColorWanted] = useState(false);
|
|
99
108
|
const describedBy = useId();
|
|
100
109
|
|
|
101
110
|
const isPassword = type === "password";
|
|
@@ -263,12 +272,26 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(pro
|
|
|
263
272
|
data-score={score ?? undefined}
|
|
264
273
|
>
|
|
265
274
|
<div {...fieldProps} data-enigma-input-field="">
|
|
266
|
-
{/*
|
|
267
|
-
|
|
268
|
-
|
|
275
|
+
{/* The swatch is NOT part of the picker's chunk, unlike the meter, which has
|
|
276
|
+
nothing to show until it has been asked a question. A colour field with no
|
|
277
|
+
swatch until a request lands is a field whose only affordance is missing,
|
|
278
|
+
and a press in that window reaches nothing: so the button is here from the
|
|
279
|
+
first frame, and a press before the panel's code arrives opens it as soon
|
|
280
|
+
as it does. */}
|
|
269
281
|
{isColor && (
|
|
270
|
-
<Suspense
|
|
282
|
+
<Suspense
|
|
283
|
+
fallback={
|
|
284
|
+
<ColorSwatch
|
|
285
|
+
value={value}
|
|
286
|
+
locked={locked}
|
|
287
|
+
baseline={styles !== false}
|
|
288
|
+
label={colorLabels?.open ?? "Pick a colour"}
|
|
289
|
+
onPress={() => setColorWanted(true)}
|
|
290
|
+
/>
|
|
291
|
+
}
|
|
292
|
+
>
|
|
271
293
|
<ColorExtras
|
|
294
|
+
openOnMount={colorWanted}
|
|
272
295
|
input={element}
|
|
273
296
|
value={value}
|
|
274
297
|
format={format}
|
package/src/react/input/types.ts
CHANGED
|
@@ -151,6 +151,10 @@ export interface ColorLabels {
|
|
|
151
151
|
alpha?: string;
|
|
152
152
|
eyedropper?: string;
|
|
153
153
|
swatches?: string;
|
|
154
|
+
/** The editable readout under the rails. Default "Colour value". */
|
|
155
|
+
value?: string;
|
|
156
|
+
/** The button that cycles the readout between HEX, RGB and HSL. Default "Show as". */
|
|
157
|
+
formatAs?: string;
|
|
154
158
|
/** The word before the colour in what a screen reader announces. Default "Colour". */
|
|
155
159
|
color?: string;
|
|
156
160
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The player's icons, drawn rather than loaded - a sprite would be a request per player.
|
|
5
|
+
*
|
|
6
|
+
* All of them are on ONE 24x24 grid and inside it. That is not a formality: the gear used to
|
|
7
|
+
* carry a path with absolute `V21` and `H4` commands in it, which drew a shape half again the
|
|
8
|
+
* size of the box and hanging off the bottom of its button while every other control sat
|
|
9
|
+
* centred. An icon that leaves the viewBox cannot be centred by the button it is in.
|
|
10
|
+
*
|
|
11
|
+
* The size comes from the stylesheet (`--enigma-video-icon-size`), not from the label's font:
|
|
12
|
+
* `1.25em` of a 13px bar is a 16px icon, and the same markup in a project with larger text
|
|
13
|
+
* silently grew a different player.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
function Glyph({ children, filled = false }: { children: ReactNode; filled?: boolean; }): ReactNode {
|
|
17
|
+
return (
|
|
18
|
+
<svg
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
width="1em"
|
|
21
|
+
height="1em"
|
|
22
|
+
fill={filled ? "currentColor" : "none"}
|
|
23
|
+
stroke={filled ? "none" : "currentColor"}
|
|
24
|
+
strokeWidth={2}
|
|
25
|
+
strokeLinecap="round"
|
|
26
|
+
strokeLinejoin="round"
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Play(): ReactNode {
|
|
35
|
+
// Centroid at x 11.7 rather than the box's 12: a triangle centred by its bounding box
|
|
36
|
+
// reads as sitting too far left, which is why every player nudges it right.
|
|
37
|
+
return <Glyph filled><path d="M8 5v14l11-7z" /></Glyph>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function Pause(): ReactNode {
|
|
41
|
+
return <Glyph filled><path d="M7 5h3.5v14H7zM13.5 5H17v14h-3.5z" /></Glyph>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function Volume(): ReactNode {
|
|
45
|
+
return <Glyph><path d="M11 5 6 9H2v6h4l5 4z" /><path d="M15.5 8.5a5 5 0 0 1 0 7" /><path d="M19 5a10 10 0 0 1 0 14" /></Glyph>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function VolumeLow(): ReactNode {
|
|
49
|
+
return <Glyph><path d="M11 5 6 9H2v6h4l5 4z" /><path d="M15.5 8.5a5 5 0 0 1 0 7" /></Glyph>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function Muted(): ReactNode {
|
|
53
|
+
return <Glyph><path d="M11 5 6 9H2v6h4l5 4z" /><path d="m16 9 6 6M22 9l-6 6" /></Glyph>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function Captions(): ReactNode {
|
|
57
|
+
return <Glyph><rect x="2" y="5" width="20" height="14" rx="2.5" /><path d="M7 11h3M7 15h5M14 11h3M14 15h3" /></Glyph>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The same box with the lines struck through, for captions that are off. */
|
|
61
|
+
export function CaptionsOff(): ReactNode {
|
|
62
|
+
return (
|
|
63
|
+
<Glyph>
|
|
64
|
+
<path d="M10.5 5H19.5a2.5 2.5 0 0 1 2.5 2.5v9M18 19H4.5A2.5 2.5 0 0 1 2 16.5v-9A2.5 2.5 0 0 1 4.5 5" />
|
|
65
|
+
<path d="M7 11h3M7 15h5" />
|
|
66
|
+
<path d="m3 3 18 18" />
|
|
67
|
+
</Glyph>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function Settings(): ReactNode {
|
|
72
|
+
return (
|
|
73
|
+
<Glyph>
|
|
74
|
+
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z" />
|
|
75
|
+
<circle cx="12" cy="12" r="3" />
|
|
76
|
+
</Glyph>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function Pip(): ReactNode {
|
|
81
|
+
return <Glyph><path d="M21 11V6.5A2.5 2.5 0 0 0 18.5 4h-13A2.5 2.5 0 0 0 3 6.5v9A2.5 2.5 0 0 0 5.5 18H9" /><rect x="12" y="12" width="9" height="7" rx="1.5" /></Glyph>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The screen with the signal arcs - the glyph every platform casts with. */
|
|
85
|
+
export function Cast(): ReactNode {
|
|
86
|
+
return (
|
|
87
|
+
<Glyph>
|
|
88
|
+
<path d="M3 8V6.5A2.5 2.5 0 0 1 5.5 4h13A2.5 2.5 0 0 1 21 6.5v11a2.5 2.5 0 0 1-2.5 2.5H13" />
|
|
89
|
+
<path d="M3 12a8 8 0 0 1 8 8" />
|
|
90
|
+
<path d="M3 16a4 4 0 0 1 4 4" />
|
|
91
|
+
<path d="M3 20h.01" />
|
|
92
|
+
</Glyph>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function Expand(): ReactNode {
|
|
97
|
+
return <Glyph><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5" /></Glyph>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function Collapse(): ReactNode {
|
|
101
|
+
return <Glyph><path d="M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5" /></Glyph>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function Download(): ReactNode {
|
|
105
|
+
return <Glyph><path d="M12 4v11M7 11l5 5 5-5M4 19h16" /></Glyph>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The tick beside a chosen row in the settings panel. */
|
|
109
|
+
export function Check(): ReactNode {
|
|
110
|
+
return <Glyph><path d="m5 12.5 4.5 4.5L19 7" /></Glyph>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function ChevronRight(): ReactNode {
|
|
114
|
+
return <Glyph><path d="m9 5 7 7-7 7" /></Glyph>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function ChevronLeft(): ReactNode {
|
|
118
|
+
return <Glyph><path d="m15 5-7 7 7 7" /></Glyph>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function Loop(): ReactNode {
|
|
122
|
+
return <Glyph><path d="M17 2.5 20.5 6 17 9.5" /><path d="M3.5 12V9a3 3 0 0 1 3-3h14" /><path d="M7 21.5 3.5 18 7 14.5" /><path d="M20.5 12v3a3 3 0 0 1-3 3h-14" /></Glyph>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function Link(): ReactNode {
|
|
126
|
+
return <Glyph><path d="M10 13a5 5 0 0 0 7.07 0l2.83-2.83a5 5 0 0 0-7.07-7.07L11.5 4.4" /><path d="M14 11a5 5 0 0 0-7.07 0L4.1 13.83a5 5 0 0 0 7.07 7.07l1.3-1.3" /></Glyph>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function Clock(): ReactNode {
|
|
130
|
+
return <Glyph><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3.5 2" /></Glyph>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function Speed(): ReactNode {
|
|
134
|
+
return <Glyph><path d="M12 20a8 8 0 1 0-8-8" /><path d="M4 20h16" /><path d="m12 12 4.5-4.5" /></Glyph>;
|
|
135
|
+
}
|