@enigmax/primitives 0.17.0 → 0.19.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/button-CaXaqG_K.d.ts +63 -0
- package/dist/chunk-2QFTRNAZ.js +88 -0
- package/dist/chunk-6BGBYUSZ.js +114 -0
- package/dist/chunk-AU3H5WIY.js +107 -0
- package/dist/{chunk-UZFEEFMF.js → chunk-D5A2ZMAG.js} +34 -1
- package/dist/chunk-DTWZDONY.js +99 -0
- package/dist/chunk-F25CGNQC.js +19 -0
- package/dist/chunk-FWVWX67R.js +462 -0
- package/dist/chunk-HC2ME5PU.js +168 -0
- package/dist/chunk-HS3X3XCW.js +43 -0
- package/dist/chunk-IXVMRVD4.js +96 -0
- package/dist/chunk-KJINGUQN.js +188 -0
- package/dist/chunk-MMQPZGSU.js +161 -0
- package/dist/chunk-OCMI7R6H.js +79 -0
- package/dist/chunk-QQFNAKMY.js +47 -0
- package/dist/chunk-QYMUIW5I.js +28 -0
- package/dist/chunk-R4ZAEE7V.js +249 -0
- package/dist/chunk-S653GLSF.js +17 -0
- package/dist/chunk-SNYUBXWQ.js +149 -0
- package/dist/chunk-U3V4EHOB.js +41 -0
- package/dist/chunk-UOSSNUSC.js +309 -0
- package/dist/chunk-XNNQRA35.js +31 -0
- package/dist/chunk-XQHCZAPJ.js +102 -0
- package/dist/chunk-ZCUFYBPB.js +154 -0
- package/dist/chunk-ZWR2EXHQ.js +55 -0
- package/dist/flags-BBJc9unY.d.ts +133 -0
- package/dist/index-dTdAbOWl.d.ts +144 -0
- package/dist/index.d.ts +18 -603
- package/dist/index.js +11 -2
- package/dist/input-BwXjFenq.d.ts +77 -0
- package/dist/marquee-CJ3Uwy3E.d.ts +81 -0
- package/dist/network-D2LsBG_k.d.ts +39 -0
- package/dist/next/index.d.ts +22 -4
- package/dist/next/index.js +25 -4
- package/dist/notifications-BpVV6sel.d.ts +70 -0
- package/dist/palette-D7iuQh_T.d.ts +86 -0
- package/dist/password-3DRQYAYQ.js +2 -0
- package/dist/password-C8lG4Zm9.d.ts +71 -0
- package/dist/password-FB2CUEKJ.js +1 -0
- package/dist/react/button.d.ts +75 -0
- package/dist/react/button.js +4 -0
- package/dist/react/flag.d.ts +37 -0
- package/dist/react/flag.js +3 -0
- package/dist/react/index.d.ts +29 -312
- package/dist/react/index.js +24 -3
- package/dist/react/input.d.ts +4 -0
- package/dist/react/input.js +3 -0
- package/dist/react/marquee.d.ts +44 -0
- package/dist/react/marquee.js +3 -0
- package/dist/react/network.d.ts +20 -0
- package/dist/react/network.js +3 -0
- package/dist/react/notifications.d.ts +17 -0
- package/dist/react/notifications.js +3 -0
- package/dist/react/palette.d.ts +231 -0
- package/dist/react/palette.js +5 -0
- package/dist/react/relative-time.d.ts +21 -0
- package/dist/react/relative-time.js +3 -0
- package/dist/react/search.d.ts +30 -0
- package/dist/react/search.js +3 -0
- package/dist/react/slot.d.ts +47 -0
- package/dist/react/slot.js +2 -0
- package/dist/react/toast.d.ts +40 -0
- package/dist/react/toast.js +4 -0
- package/dist/react-router/index.d.ts +22 -4
- package/dist/react-router/index.js +25 -4
- package/dist/relative-time-YpRTG7YH.d.ts +106 -0
- package/dist/search/index.d.ts +2 -2
- package/dist/search/index.js +1 -1
- package/dist/{search-CsO3L1Lw.d.ts → search-DXxY8SEH.d.ts} +15 -1
- package/dist/search-UQEXAPQB.js +50 -0
- package/package.json +54 -3
- package/recipes/input/styles.css +60 -20
- package/recipes/palette/styles.css +243 -0
- package/registry.json +186 -18
- package/src/core/flags.ts +88 -42
- package/src/core/input-icons.ts +34 -0
- package/src/core/input.ts +5 -21
- package/src/core/palette.ts +0 -0
- package/src/core/search.ts +60 -0
- package/src/index.ts +19 -3
- package/src/react/button.tsx +70 -4
- package/src/react/flag.tsx +18 -6
- package/src/react/index.ts +50 -5
- package/src/react/input/icon.tsx +17 -0
- package/src/react/input/index.tsx +307 -0
- package/src/react/input/password.tsx +174 -0
- package/src/react/input/search.tsx +82 -0
- package/src/react/input/types.ts +146 -0
- package/src/react/input/write-value.ts +18 -0
- package/src/react/palette/context.ts +60 -0
- package/src/react/palette/index.tsx +66 -0
- package/src/react/palette/root.tsx +661 -0
- package/src/react/slot.tsx +91 -0
- package/src/react/use-button.ts +3 -1
- package/dist/chunk-4GLYRF4B.js +0 -1259
- package/dist/chunk-JFNND6P4.js +0 -749
- package/src/react/input.tsx +0 -429
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createSearch } from './chunk-D5A2ZMAG.js';
|
|
2
|
+
import { useState, useRef, useMemo, useEffect } from 'react';
|
|
3
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function SearchExtras({
|
|
6
|
+
input,
|
|
7
|
+
items,
|
|
8
|
+
keys,
|
|
9
|
+
delay = 150,
|
|
10
|
+
limit,
|
|
11
|
+
fuse,
|
|
12
|
+
fuseOptions,
|
|
13
|
+
matcher,
|
|
14
|
+
onResults,
|
|
15
|
+
renderResults
|
|
16
|
+
}) {
|
|
17
|
+
const [results, setResults] = useState([]);
|
|
18
|
+
const [query, setQuery] = useState("");
|
|
19
|
+
const listener = useRef(onResults);
|
|
20
|
+
listener.current = onResults;
|
|
21
|
+
const instance = useMemo(() => createSearch({
|
|
22
|
+
items,
|
|
23
|
+
keys,
|
|
24
|
+
fuse,
|
|
25
|
+
fuseOptions,
|
|
26
|
+
matcher,
|
|
27
|
+
debounce: delay,
|
|
28
|
+
limit,
|
|
29
|
+
onResults: (next, nextQuery) => {
|
|
30
|
+
setResults(next);
|
|
31
|
+
setQuery(nextQuery);
|
|
32
|
+
listener.current?.(next, nextQuery);
|
|
33
|
+
}
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
|
+
}), []);
|
|
36
|
+
useEffect(() => () => instance.destroy(), [instance]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
instance.setItems(items ?? []);
|
|
39
|
+
}, [instance, items]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
instance.update({ keys, fuse, fuseOptions, matcher, debounce: delay, limit });
|
|
42
|
+
}, [instance, keys, fuse, fuseOptions, matcher, delay, limit]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!input) return;
|
|
45
|
+
return instance.attach(input);
|
|
46
|
+
}, [instance, input]);
|
|
47
|
+
return renderResults ? /* @__PURE__ */ jsx(Fragment, { children: renderResults(results, query) }) : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { SearchExtras };
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Headless components: the behaviour, the timing and the accessibility, with no styles of their own. Framework-agnostic core plus thin adapters.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"*.css"
|
|
8
|
+
],
|
|
6
9
|
"main": "./dist/index.js",
|
|
7
10
|
"types": "./dist/index.d.ts",
|
|
8
11
|
"exports": {
|
|
@@ -14,6 +17,50 @@
|
|
|
14
17
|
"types": "./dist/react/index.d.ts",
|
|
15
18
|
"import": "./dist/react/index.js"
|
|
16
19
|
},
|
|
20
|
+
"./react/input": {
|
|
21
|
+
"types": "./dist/react/input.d.ts",
|
|
22
|
+
"import": "./dist/react/input.js"
|
|
23
|
+
},
|
|
24
|
+
"./react/palette": {
|
|
25
|
+
"types": "./dist/react/palette.d.ts",
|
|
26
|
+
"import": "./dist/react/palette.js"
|
|
27
|
+
},
|
|
28
|
+
"./react/button": {
|
|
29
|
+
"types": "./dist/react/button.d.ts",
|
|
30
|
+
"import": "./dist/react/button.js"
|
|
31
|
+
},
|
|
32
|
+
"./react/flag": {
|
|
33
|
+
"types": "./dist/react/flag.d.ts",
|
|
34
|
+
"import": "./dist/react/flag.js"
|
|
35
|
+
},
|
|
36
|
+
"./react/toast": {
|
|
37
|
+
"types": "./dist/react/toast.d.ts",
|
|
38
|
+
"import": "./dist/react/toast.js"
|
|
39
|
+
},
|
|
40
|
+
"./react/marquee": {
|
|
41
|
+
"types": "./dist/react/marquee.d.ts",
|
|
42
|
+
"import": "./dist/react/marquee.js"
|
|
43
|
+
},
|
|
44
|
+
"./react/search": {
|
|
45
|
+
"types": "./dist/react/search.d.ts",
|
|
46
|
+
"import": "./dist/react/search.js"
|
|
47
|
+
},
|
|
48
|
+
"./react/network": {
|
|
49
|
+
"types": "./dist/react/network.d.ts",
|
|
50
|
+
"import": "./dist/react/network.js"
|
|
51
|
+
},
|
|
52
|
+
"./react/notifications": {
|
|
53
|
+
"types": "./dist/react/notifications.d.ts",
|
|
54
|
+
"import": "./dist/react/notifications.js"
|
|
55
|
+
},
|
|
56
|
+
"./react/relative-time": {
|
|
57
|
+
"types": "./dist/react/relative-time.d.ts",
|
|
58
|
+
"import": "./dist/react/relative-time.js"
|
|
59
|
+
},
|
|
60
|
+
"./react/slot": {
|
|
61
|
+
"types": "./dist/react/slot.d.ts",
|
|
62
|
+
"import": "./dist/react/slot.js"
|
|
63
|
+
},
|
|
17
64
|
"./registry.json": "./registry.json",
|
|
18
65
|
"./search": {
|
|
19
66
|
"types": "./dist/search/index.d.ts",
|
|
@@ -31,7 +78,8 @@
|
|
|
31
78
|
"./toast.css": "./recipes/toast/styles.css",
|
|
32
79
|
"./input.css": "./recipes/input/styles.css",
|
|
33
80
|
"./marquee.css": "./recipes/marquee/styles.css",
|
|
34
|
-
"./logo-marquee.css": "./recipes/logo-marquee/styles.css"
|
|
81
|
+
"./logo-marquee.css": "./recipes/logo-marquee/styles.css",
|
|
82
|
+
"./palette.css": "./recipes/palette/styles.css"
|
|
35
83
|
},
|
|
36
84
|
"scripts": {
|
|
37
85
|
"build": "tsup",
|
|
@@ -51,6 +99,7 @@
|
|
|
51
99
|
],
|
|
52
100
|
"peerDependencies": {
|
|
53
101
|
"@github/relative-time-element": ">=4",
|
|
102
|
+
"react-dom": ">=18",
|
|
54
103
|
"fuse.js": ">=6",
|
|
55
104
|
"next": ">=13",
|
|
56
105
|
"react": ">=18",
|
|
@@ -60,6 +109,9 @@
|
|
|
60
109
|
"react": {
|
|
61
110
|
"optional": true
|
|
62
111
|
},
|
|
112
|
+
"react-dom": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
63
115
|
"fuse.js": {
|
|
64
116
|
"optional": true
|
|
65
117
|
},
|
|
@@ -92,7 +144,6 @@
|
|
|
92
144
|
"keywords": [
|
|
93
145
|
"carousel",
|
|
94
146
|
"enigma",
|
|
95
|
-
"flag-icons",
|
|
96
147
|
"flags",
|
|
97
148
|
"fuse",
|
|
98
149
|
"headless",
|
package/recipes/input/styles.css
CHANGED
|
@@ -1,23 +1,64 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
1
|
+
/*
|
|
2
|
+
* A starting point for the field, yours to edit.
|
|
3
|
+
*
|
|
4
|
+
* The primitive renders the structure and publishes its state through data-* attributes;
|
|
5
|
+
* every colour and distance below is this file's. The custom properties at the top are the
|
|
6
|
+
* whole API - override them on `:root` and you have a different field without touching a
|
|
7
|
+
* selector, which is the same arrangement the toast and the palette use.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--enigma-input-bg: #171717;
|
|
12
|
+
--enigma-input-border: #404040;
|
|
13
|
+
--enigma-input-border-focus: #a3a3a3;
|
|
14
|
+
--enigma-input-text: #f5f5f5;
|
|
15
|
+
--enigma-input-muted: #a3a3a3;
|
|
16
|
+
--enigma-input-radius: 0.5rem;
|
|
17
|
+
--enigma-input-font-size: 0.875rem;
|
|
18
|
+
--enigma-input-padding: 0 0.75rem;
|
|
19
|
+
--enigma-input-danger: #dc2626;
|
|
20
|
+
--enigma-input-accent: #fbbf24;
|
|
21
|
+
|
|
22
|
+
/* The meter, worst to best. A headless package has no business deciding what "strong"
|
|
23
|
+
looks like on your brand, so the five bands are here rather than in the component. */
|
|
24
|
+
--enigma-strength-empty: #262626;
|
|
25
|
+
--enigma-strength-0: #dc2626;
|
|
26
|
+
--enigma-strength-1: #ea580c;
|
|
27
|
+
--enigma-strength-2: #eab308;
|
|
28
|
+
--enigma-strength-3: #84cc16;
|
|
29
|
+
--enigma-strength-4: #16a34a;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@media (prefers-color-scheme: light) {
|
|
33
|
+
:root {
|
|
34
|
+
--enigma-input-bg: #ffffff;
|
|
35
|
+
--enigma-input-border: #d4d4d4;
|
|
36
|
+
--enigma-input-border-focus: #737373;
|
|
37
|
+
--enigma-input-text: #171717;
|
|
38
|
+
--enigma-input-muted: #737373;
|
|
39
|
+
--enigma-strength-empty: #e5e5e5;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
3
42
|
|
|
4
43
|
[data-enigma-input-root] { display: grid; gap: 0.375rem; }
|
|
5
44
|
|
|
6
45
|
[data-enigma-input-field] {
|
|
7
46
|
display: flex; align-items: center; gap: 0.375rem;
|
|
8
|
-
padding:
|
|
9
|
-
background:
|
|
47
|
+
padding: var(--enigma-input-padding);
|
|
48
|
+
background: var(--enigma-input-bg);
|
|
49
|
+
border: 1px solid var(--enigma-input-border);
|
|
50
|
+
border-radius: var(--enigma-input-radius);
|
|
10
51
|
}
|
|
11
|
-
[data-enigma-input-field]:focus-within { border-color:
|
|
52
|
+
[data-enigma-input-field]:focus-within { border-color: var(--enigma-input-border-focus); }
|
|
12
53
|
|
|
13
54
|
[data-enigma-input] {
|
|
14
55
|
flex: 1; min-width: 0; padding: 0.625rem 0;
|
|
15
|
-
font-size:
|
|
56
|
+
font-size: var(--enigma-input-font-size); color: var(--enigma-input-text);
|
|
16
57
|
background: transparent; border: 0; outline: none;
|
|
17
58
|
}
|
|
18
59
|
|
|
19
60
|
/* A breached password is worth showing on the field itself, not only in the message. */
|
|
20
|
-
[data-enigma-input-root][data-breached] [data-enigma-input-field] { border-color:
|
|
61
|
+
[data-enigma-input-root][data-breached] [data-enigma-input-field] { border-color: var(--enigma-input-danger); }
|
|
21
62
|
|
|
22
63
|
[data-enigma-input-actions] { display: inline-flex; gap: 0.125rem; }
|
|
23
64
|
[data-enigma-input-actions][data-position="start"] { order: -1; }
|
|
@@ -25,30 +66,29 @@
|
|
|
25
66
|
[data-enigma-input-action] {
|
|
26
67
|
display: grid; place-items: center;
|
|
27
68
|
width: 1.75rem; height: 1.75rem;
|
|
28
|
-
color:
|
|
69
|
+
color: var(--enigma-input-muted); background: none; border: 0; border-radius: 0.375rem;
|
|
29
70
|
cursor: pointer; font-size: 0.9375rem;
|
|
30
71
|
}
|
|
31
|
-
[data-enigma-input-action]:hover { color:
|
|
32
|
-
[data-enigma-input-action][aria-pressed="true"] { color:
|
|
72
|
+
[data-enigma-input-action]:hover { color: var(--enigma-input-text); background: color-mix(in srgb, var(--enigma-input-border) 45%, transparent); }
|
|
73
|
+
[data-enigma-input-action][aria-pressed="true"] { color: var(--enigma-input-accent); }
|
|
33
74
|
[data-enigma-input-action]:disabled { opacity: 0.5; cursor: default; }
|
|
34
75
|
|
|
35
|
-
/* The meter. Five bars, one per score, red through green
|
|
36
|
-
a headless package has no business choosing what "strong" looks like on your brand. */
|
|
76
|
+
/* The meter. Five bars, one per score, red through green. */
|
|
37
77
|
[data-enigma-password-strength] { display: grid; gap: 0.25rem; }
|
|
38
78
|
[data-enigma-password-strength-track] { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.25rem; }
|
|
39
79
|
|
|
40
80
|
[data-enigma-password-strength-segment] {
|
|
41
|
-
height: 0.25rem; border-radius: 999px; background:
|
|
81
|
+
height: 0.25rem; border-radius: 999px; background: var(--enigma-strength-empty);
|
|
42
82
|
transition: background-color 120ms ease-out;
|
|
43
83
|
}
|
|
44
84
|
|
|
45
|
-
[data-score="0"] [data-enigma-password-strength-segment][data-filled] { background:
|
|
46
|
-
[data-score="1"] [data-enigma-password-strength-segment][data-filled] { background:
|
|
47
|
-
[data-score="2"] [data-enigma-password-strength-segment][data-filled] { background:
|
|
48
|
-
[data-score="3"] [data-enigma-password-strength-segment][data-filled] { background:
|
|
49
|
-
[data-score="4"] [data-enigma-password-strength-segment][data-filled] { background:
|
|
85
|
+
[data-score="0"] [data-enigma-password-strength-segment][data-filled] { background: var(--enigma-strength-0); }
|
|
86
|
+
[data-score="1"] [data-enigma-password-strength-segment][data-filled] { background: var(--enigma-strength-1); }
|
|
87
|
+
[data-score="2"] [data-enigma-password-strength-segment][data-filled] { background: var(--enigma-strength-2); }
|
|
88
|
+
[data-score="3"] [data-enigma-password-strength-segment][data-filled] { background: var(--enigma-strength-3); }
|
|
89
|
+
[data-score="4"] [data-enigma-password-strength-segment][data-filled] { background: var(--enigma-strength-4); }
|
|
50
90
|
|
|
51
|
-
[data-enigma-password-strength-label] { margin: 0; font-size: 0.75rem; color:
|
|
52
|
-
[data-enigma-password-strength-warning] { margin: 0; font-size: 0.75rem; color:
|
|
91
|
+
[data-enigma-password-strength-label] { margin: 0; font-size: 0.75rem; color: var(--enigma-input-muted); }
|
|
92
|
+
[data-enigma-password-strength-warning] { margin: 0; font-size: 0.75rem; color: var(--enigma-input-accent); }
|
|
53
93
|
/* An empty field is not a bad password: say nothing until there is something to say. */
|
|
54
94
|
[data-enigma-password-strength][data-empty] [data-enigma-password-strength-label] { visibility: hidden; }
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The palette's look.
|
|
3
|
+
*
|
|
4
|
+
* `SearchPalette` renders the structure, the state and every key it answers to; every
|
|
5
|
+
* colour, distance and curve is in this file. Nothing below is something the package decided
|
|
6
|
+
* for you, so editing it is the supported way to make it yours.
|
|
7
|
+
*
|
|
8
|
+
* The custom properties at the top are the whole API. Override them on `:root` and you have
|
|
9
|
+
* a different palette without touching a selector.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
--enigma-palette-width: 34rem;
|
|
14
|
+
--enigma-palette-radius: 0.85rem;
|
|
15
|
+
--enigma-palette-top: 12vh;
|
|
16
|
+
|
|
17
|
+
--enigma-palette-bg: #101010;
|
|
18
|
+
--enigma-palette-border: #2a2a2a;
|
|
19
|
+
--enigma-palette-text: #f5f5f5;
|
|
20
|
+
--enigma-palette-muted: #a3a3a3;
|
|
21
|
+
--enigma-palette-active: #1c1c1c;
|
|
22
|
+
--enigma-palette-accent: #e0a458;
|
|
23
|
+
--enigma-palette-shadow: 0 20px 60px rgb(0 0 0 / 55%);
|
|
24
|
+
--enigma-palette-scrim: rgb(0 0 0 / 55%);
|
|
25
|
+
--enigma-palette-enter: 160ms;
|
|
26
|
+
--enigma-palette-exit: 140ms;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media (prefers-color-scheme: light) {
|
|
30
|
+
:root {
|
|
31
|
+
--enigma-palette-bg: #ffffff;
|
|
32
|
+
--enigma-palette-border: #e5e5e5;
|
|
33
|
+
--enigma-palette-text: #171717;
|
|
34
|
+
--enigma-palette-muted: #737373;
|
|
35
|
+
--enigma-palette-active: #f5f5f5;
|
|
36
|
+
--enigma-palette-shadow: 0 20px 60px rgb(0 0 0 / 18%);
|
|
37
|
+
--enigma-palette-scrim: rgb(0 0 0 / 25%);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* -------- the trigger -------- */
|
|
42
|
+
|
|
43
|
+
[data-enigma-palette-trigger] {
|
|
44
|
+
display: inline-flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: 0.5rem;
|
|
47
|
+
min-width: 12rem;
|
|
48
|
+
padding: 0.45rem 0.6rem;
|
|
49
|
+
font: inherit;
|
|
50
|
+
font-size: 0.8125rem;
|
|
51
|
+
/* Set explicitly: this may be rendered as a button or, through asChild, as something
|
|
52
|
+
else entirely, and only a button gets `line-height: normal` from the user agent. */
|
|
53
|
+
line-height: 1.25;
|
|
54
|
+
color: var(--enigma-palette-muted);
|
|
55
|
+
background: transparent;
|
|
56
|
+
border: 1px solid var(--enigma-palette-border);
|
|
57
|
+
border-radius: 0.6rem;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[data-enigma-palette-trigger]:hover { color: var(--enigma-palette-text); }
|
|
62
|
+
[data-enigma-palette-trigger-label] { flex: 1; text-align: left; }
|
|
63
|
+
|
|
64
|
+
[data-enigma-palette-trigger-key] {
|
|
65
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
66
|
+
font-size: 0.6875rem;
|
|
67
|
+
padding: 0.15rem 0.35rem;
|
|
68
|
+
border: 1px solid var(--enigma-palette-border);
|
|
69
|
+
border-radius: 0.3rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* -------- the panel -------- */
|
|
73
|
+
|
|
74
|
+
[data-enigma-palette-overlay] {
|
|
75
|
+
position: fixed;
|
|
76
|
+
inset: 0;
|
|
77
|
+
z-index: 9998;
|
|
78
|
+
background: var(--enigma-palette-scrim);
|
|
79
|
+
/* Behind the panel, and the only thing a click outside can land on. */
|
|
80
|
+
backdrop-filter: blur(2px);
|
|
81
|
+
animation: enigma-palette-fade var(--enigma-palette-enter) ease-out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* The panel stays in the DOM through its exit (`closeDuration`), which is the only way a
|
|
85
|
+
component that unmounts on close can animate out at all. */
|
|
86
|
+
[data-enigma-palette-overlay][data-state="closed"] {
|
|
87
|
+
animation: enigma-palette-fade var(--enigma-palette-exit) ease-in reverse forwards;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[data-enigma-palette-content] {
|
|
91
|
+
position: fixed;
|
|
92
|
+
z-index: 9999;
|
|
93
|
+
top: var(--enigma-palette-top);
|
|
94
|
+
left: 50%;
|
|
95
|
+
transform: translateX(-50%);
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
width: min(var(--enigma-palette-width), calc(100vw - 2rem));
|
|
99
|
+
/* Never taller than the room it has: the list scrolls, the field and the footer stay. */
|
|
100
|
+
max-height: min(70vh, 32rem);
|
|
101
|
+
overflow: hidden;
|
|
102
|
+
color: var(--enigma-palette-text);
|
|
103
|
+
background: var(--enigma-palette-bg);
|
|
104
|
+
border: 1px solid var(--enigma-palette-border);
|
|
105
|
+
border-radius: var(--enigma-palette-radius);
|
|
106
|
+
box-shadow: var(--enigma-palette-shadow);
|
|
107
|
+
animation: enigma-palette-in var(--enigma-palette-enter) cubic-bezier(0.16, 1, 0.3, 1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[data-enigma-palette-content][data-state="closed"] {
|
|
111
|
+
animation: enigma-palette-in var(--enigma-palette-exit) ease-in reverse forwards;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@keyframes enigma-palette-fade {
|
|
115
|
+
from { opacity: 0; }
|
|
116
|
+
to { opacity: 1; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Down and in: the panel arrives rather than appearing whole. */
|
|
120
|
+
@keyframes enigma-palette-in {
|
|
121
|
+
from { opacity: 0; transform: translateX(-50%) translateY(-8px) scale(0.98); }
|
|
122
|
+
to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Reduced motion keeps the fade and drops the movement: the panel still has to announce
|
|
126
|
+
itself as new, and a hard cut in the middle of the screen is its own kind of jolt. */
|
|
127
|
+
@media (prefers-reduced-motion: reduce) {
|
|
128
|
+
[data-enigma-palette-content],
|
|
129
|
+
[data-enigma-palette-content][data-state="closed"] {
|
|
130
|
+
animation-name: enigma-palette-fade;
|
|
131
|
+
animation-duration: 90ms;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* The dialog needs a name; nobody needs to see it. */
|
|
136
|
+
[data-enigma-palette-title] {
|
|
137
|
+
position: absolute;
|
|
138
|
+
width: 1px;
|
|
139
|
+
height: 1px;
|
|
140
|
+
margin: -1px;
|
|
141
|
+
padding: 0;
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
clip-path: inset(50%);
|
|
144
|
+
white-space: nowrap;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
[data-enigma-palette-field] {
|
|
148
|
+
display: block;
|
|
149
|
+
width: 100%;
|
|
150
|
+
padding: 0.95rem 1.1rem;
|
|
151
|
+
font: inherit;
|
|
152
|
+
font-size: 0.95rem;
|
|
153
|
+
/* Both set explicitly. A field inherits nothing useful from a dialog - a page that
|
|
154
|
+
styles `input` at all (a reset, a framework, the site's own form CSS) otherwise
|
|
155
|
+
reaches in and leaves the palette with a field that does not match its own panel. */
|
|
156
|
+
color: var(--enigma-palette-text);
|
|
157
|
+
background: var(--enigma-palette-bg);
|
|
158
|
+
border: 0;
|
|
159
|
+
border-bottom: 1px solid var(--enigma-palette-border);
|
|
160
|
+
border-radius: 0;
|
|
161
|
+
outline: none;
|
|
162
|
+
box-shadow: none;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
[data-enigma-palette-field]::placeholder { color: var(--enigma-palette-muted); }
|
|
166
|
+
/* The platform's own clear button would sit next to a list that already answers to Escape. */
|
|
167
|
+
[data-enigma-palette-field]::-webkit-search-cancel-button { display: none; }
|
|
168
|
+
|
|
169
|
+
[data-enigma-palette-list] {
|
|
170
|
+
flex: 1;
|
|
171
|
+
overflow-y: auto;
|
|
172
|
+
/* A scroll that reaches the end stays in the panel instead of moving the page under it. */
|
|
173
|
+
overscroll-behavior: contain;
|
|
174
|
+
padding: 0.4rem;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
[data-enigma-palette-group-label] {
|
|
178
|
+
margin: 0.4rem 0.6rem 0.25rem;
|
|
179
|
+
font-size: 0.6875rem;
|
|
180
|
+
font-weight: 600;
|
|
181
|
+
letter-spacing: 0.04em;
|
|
182
|
+
text-transform: uppercase;
|
|
183
|
+
color: var(--enigma-palette-muted);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
[data-enigma-palette-item] {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 0.6rem;
|
|
190
|
+
padding: 0.55rem 0.6rem;
|
|
191
|
+
border-radius: 0.5rem;
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* One highlight, whether it was the keyboard or the pointer that moved it. */
|
|
196
|
+
[data-enigma-palette-item][data-active="true"] { background: var(--enigma-palette-active); }
|
|
197
|
+
[data-enigma-palette-item][data-kind="recent"] [data-enigma-palette-item-label]::before {
|
|
198
|
+
content: "";
|
|
199
|
+
display: inline-block;
|
|
200
|
+
width: 0.35rem;
|
|
201
|
+
height: 0.35rem;
|
|
202
|
+
margin-right: 0.5rem;
|
|
203
|
+
vertical-align: 0.1rem;
|
|
204
|
+
border-radius: 999px;
|
|
205
|
+
background: var(--enigma-palette-accent);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
[data-enigma-palette-item-label] { flex: 1; min-width: 0; }
|
|
209
|
+
[data-enigma-palette-item-description] { color: var(--enigma-palette-muted); font-size: 0.8125rem; }
|
|
210
|
+
|
|
211
|
+
[data-enigma-palette-empty] {
|
|
212
|
+
margin: 0;
|
|
213
|
+
padding: 2.5rem 1rem;
|
|
214
|
+
text-align: center;
|
|
215
|
+
color: var(--enigma-palette-muted);
|
|
216
|
+
font-size: 0.875rem;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
[data-enigma-palette-footer] {
|
|
220
|
+
display: flex;
|
|
221
|
+
gap: 1rem;
|
|
222
|
+
padding: 0.55rem 0.9rem;
|
|
223
|
+
font-size: 0.75rem;
|
|
224
|
+
color: var(--enigma-palette-muted);
|
|
225
|
+
border-top: 1px solid var(--enigma-palette-border);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
[data-enigma-palette-hint] kbd {
|
|
229
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
230
|
+
font-size: 0.6875rem;
|
|
231
|
+
padding: 0.1rem 0.3rem;
|
|
232
|
+
margin-right: 0.2rem;
|
|
233
|
+
border: 1px solid var(--enigma-palette-border);
|
|
234
|
+
border-radius: 0.25rem;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* A phone has no Escape key and less room: the panel takes the top of the screen. */
|
|
238
|
+
@media (max-width: 640px) {
|
|
239
|
+
[data-enigma-palette-content] {
|
|
240
|
+
top: 0.75rem;
|
|
241
|
+
max-height: calc(100vh - 1.5rem);
|
|
242
|
+
}
|
|
243
|
+
}
|