@simmalugnt-se/payload-editor-assistant 0.3.0 → 0.3.2
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/CHANGELOG.md +14 -0
- package/dist/components/EditorAssistantAction.d.ts +1 -0
- package/dist/components/EditorAssistantAction.js +13 -7
- package/dist/components/EditorAssistantAside.d.ts +1 -1
- package/dist/components/EditorAssistantAside.js +9 -6
- package/dist/components/EditorAssistantProvider.js +2 -2
- package/dist/components/assistant-context.d.ts +1 -2
- package/dist/components/assistant-context.js +30 -6
- package/dist/runtime/propose-loop.js +9 -3
- package/dist/runtime/system-prompt.js +1 -1
- package/dist/styles/assistant.css +45 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
- One control opens and closes the assistant: the text button with a sparkles icon in Admin's
|
|
6
|
+
header. The narrow rail shown while the panel was collapsed is gone.
|
|
7
|
+
- The panel starts closed, and the browser remembers whether you left it open.
|
|
8
|
+
|
|
9
|
+
## 0.3.1
|
|
10
|
+
|
|
11
|
+
- A greeting or a question no longer fills empty fields: the assistant fills them only when asked.
|
|
12
|
+
Before, "hello" on a page with an empty field could come back as a proposal.
|
|
13
|
+
- The follow-up that makes the model stage a change it only talked about now needs a request for a
|
|
14
|
+
change (or a yes) from the editor; a question it asks after a greeting is left as a question.
|
|
15
|
+
- While the assistant works, Send turns into Stop instead of a separate button in the thread.
|
|
16
|
+
|
|
3
17
|
## 0.3.0
|
|
4
18
|
|
|
5
19
|
- Keep or undo: a proposed form change goes into the form right away, so the fields and Live Preview
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useAuth, useTranslation } from "@payloadcms/ui";
|
|
4
4
|
import { useAssistantState } from "./assistant-context.js";
|
|
5
|
+
import { COPY, uiLanguage } from "./copy.js";
|
|
6
|
+
/** The one control that opens and closes the assistant panel, in Admin's header. */
|
|
5
7
|
export function EditorAssistantAction() {
|
|
6
8
|
const { user } = useAuth();
|
|
7
|
-
const {
|
|
9
|
+
const { i18n } = useTranslation();
|
|
10
|
+
const { open, setOpen } = useAssistantState();
|
|
8
11
|
if (!user) {
|
|
9
12
|
return null;
|
|
10
13
|
}
|
|
11
|
-
return (
|
|
14
|
+
return (_jsxs("button", { "aria-expanded": open, className: open ? "ea-trigger ea-trigger--open" : "ea-trigger", onClick: (event) => {
|
|
12
15
|
event.preventDefault();
|
|
13
16
|
event.stopPropagation();
|
|
14
|
-
setOpen(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
setOpen(!open);
|
|
18
|
+
}, type: "button", children: [_jsx(SparklesIcon, {}), COPY[uiLanguage(i18n.language)].assistant] }));
|
|
19
|
+
}
|
|
20
|
+
/** A large four-point star with two small ones: the usual sign for AI help. */
|
|
21
|
+
function SparklesIcon() {
|
|
22
|
+
return (_jsxs("svg", { "aria-hidden": "true", className: "ea-trigger__icon", fill: "currentColor", viewBox: "0 0 20 20", children: [_jsx("path", { d: "M8 2.5l1.3 3.7a4 4 0 0 0 2.5 2.5L15.5 10l-3.7 1.3a4 4 0 0 0-2.5 2.5L8 17.5l-1.3-3.7a4 4 0 0 0-2.5-2.5L.5 10l3.7-1.3a4 4 0 0 0 2.5-2.5L8 2.5z" }), _jsx("path", { d: "M15.5 1l.6 1.6a1.5 1.5 0 0 0 .9.9l1.6.6-1.6.6a1.5 1.5 0 0 0-.9.9l-.6 1.6-.6-1.6a1.5 1.5 0 0 0-.9-.9L12.4 4.1l1.6-.6a1.5 1.5 0 0 0 .9-.9L15.5 1z" }), _jsx("path", { d: "M16 13.5l.4 1.1a1 1 0 0 0 .6.6l1.1.4-1.1.4a1 1 0 0 0-.6.6L16 17.7l-.4-1.1a1 1 0 0 0-.6-.6l-1.1-.4 1.1-.4a1 1 0 0 0 .6-.6l.4-1.1z" })] }));
|
|
17
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function EditorAssistantAside(): import("react").JSX.Element;
|
|
1
|
+
export declare function EditorAssistantAside(): import("react").JSX.Element | null;
|
|
@@ -9,7 +9,7 @@ import { useAssistantState, } from "./assistant-context.js";
|
|
|
9
9
|
import { COPY, uiLanguage } from "./copy.js";
|
|
10
10
|
import { KEEP_SELECTION_ATTR } from "./selection-dom.js";
|
|
11
11
|
export function EditorAssistantAside() {
|
|
12
|
-
const {
|
|
12
|
+
const { open, setOpen, status, messages, shortcuts, collections, bridge, formAdapter, pendingApproval, selection, setSelection, resetConversation, setStatus, setMessages, setPendingApproval, stop, setAbortController, } = useAssistantState();
|
|
13
13
|
const { config, getEntityConfig } = useConfig();
|
|
14
14
|
const router = useRouter();
|
|
15
15
|
const locale = useLocale();
|
|
@@ -334,18 +334,21 @@ export function EditorAssistantAside() {
|
|
|
334
334
|
void send(draft);
|
|
335
335
|
}
|
|
336
336
|
};
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
// Closed: nothing is shown. The component stays mounted, so an applied preview survives.
|
|
338
|
+
if (!open) {
|
|
339
|
+
return null;
|
|
339
340
|
}
|
|
340
|
-
return (_jsxs("aside", { className: "ea-aside", "aria-label": copy.assistant, [KEEP_SELECTION_ATTR]: "", children: [_jsxs("div", { className: "ea-aside__head", children: [_jsxs("div", { className: "ea-aside__title", children: [_jsx("h2", { children: copy.assistant }), bridge ? _jsx("p", { className: "ea-aside__context", children: contextLine(bridge, config) }) : null] }), (messages.length > 0 || pendingApproval) && !pendingApproval?.applied ? (_jsx(Button, { buttonStyle: "subtle", margin: false, onClick: () => resetConversation(), size: "medium", children: copy.newChat })) : null, _jsx(Button, { "aria-label": copy.collapseLabel, buttonStyle: "icon-label", className: "ea-aside__collapse", icon: _jsx(ChevronIcon, { direction: "right" }), margin: false, onClick: () =>
|
|
341
|
+
return (_jsxs("aside", { className: "ea-aside", "aria-label": copy.assistant, [KEEP_SELECTION_ATTR]: "", children: [_jsxs("div", { className: "ea-aside__head", children: [_jsxs("div", { className: "ea-aside__title", children: [_jsx("h2", { children: copy.assistant }), bridge ? _jsx("p", { className: "ea-aside__context", children: contextLine(bridge, config) }) : null] }), (messages.length > 0 || pendingApproval) && !pendingApproval?.applied ? (_jsx(Button, { buttonStyle: "subtle", margin: false, onClick: () => resetConversation(), size: "medium", children: copy.newChat })) : null, _jsx(Button, { "aria-label": copy.collapseLabel, buttonStyle: "icon-label", className: "ea-aside__collapse", icon: _jsx(ChevronIcon, { direction: "right" }), margin: false, onClick: () => setOpen(false), tooltip: copy.collapse })] }), _jsxs("div", { className: "ea-aside__body", children: [_jsxs("div", { className: "ea-messages", ref: threadRef, onScroll: onThreadScroll, children: [messages.length === 0 ? (_jsx("div", { className: "ea-msg ea-msg--assistant", children: introText(view, bridge, collections, config, copy) })) : null, messages.map((message) => (_jsxs("div", { className: `ea-msg ea-msg--${message.role}`, children: [message.text, message.candidates?.length ? (_jsx("div", { className: "ea-actions", children: message.candidates.map((candidate) => (_jsx(Button, { buttonStyle: "pill", margin: false, onClick: () => {
|
|
341
342
|
router.push(candidate.href);
|
|
342
|
-
}, size: "small", children: candidate.label }, candidate.href))) })) : null] }, message.id))), status === "streaming" ? (_jsxs("div", { className: "ea-msg ea-msg--working", children: [_jsxs("span", { className: "ea-dots", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), _jsx("span", { children: copy.working })
|
|
343
|
+
}, size: "small", children: candidate.label }, candidate.href))) })) : null] }, message.id))), status === "streaming" ? (_jsxs("div", { className: "ea-msg ea-msg--working", children: [_jsxs("span", { className: "ea-dots", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), _jsx("span", { children: copy.working })] })) : null] }), pendingApproval ? (_jsxs("div", { className: "ea-approval", children: [_jsx("h3", { children: pendingApproval.title }), pendingApproval.applied ? (_jsx("p", { className: "ea-approval__hint", children: autosave ? copy.previewHintAutosave : copy.previewHint })) : null, _jsx("ul", { className: "ea-approval__diff", children: pendingApproval.summary.split("\n").map((line, index) => line.startsWith(" ") ? (
|
|
343
344
|
// biome-ignore lint/suspicious/noArrayIndexKey: lines are static for one approval
|
|
344
345
|
_jsx("li", { className: "ea-approval__sub", children: line.trim() }, index)) : (
|
|
345
346
|
// biome-ignore lint/suspicious/noArrayIndexKey: lines are static for one approval
|
|
346
347
|
_jsx("li", { children: line }, index))) }), confirmUndo ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "ea-approval__warn", children: copy.undoLosesEdits }), _jsxs("div", { className: "ea-actions", children: [_jsx(Button, { buttonStyle: "primary", margin: false, onClick: () => void undoPreview(pendingApproval, true), size: "medium", children: copy.undoAnyway }), _jsx(Button, { buttonStyle: "secondary", margin: false, onClick: () => setConfirmUndo(false), size: "medium", children: copy.cancel })] })] })) : (_jsxs("div", { className: "ea-actions", children: [_jsx(Button, { buttonStyle: "primary", margin: false, onClick: () => void decide("approve"), size: "medium", children: pendingApproval.applied ? copy.keep : copy.approve }), _jsx(Button, { buttonStyle: "secondary", margin: false, onClick: () => void decide("reject"), size: "medium", children: pendingApproval.applied ? copy.undo : copy.reject })] }))] })) : null, status !== "idle" && status !== "streaming" ? (_jsx("div", { className: `ea-status ea-status--${status}`, children: statusText(status, copy) })) : null, _jsxs("form", { className: "ea-composer", onSubmit: onSubmit, children: [visibleShortcuts.length > 0 ? (_jsx("div", { className: "ea-shortcuts", children: visibleShortcuts.map((shortcut) => (_jsx(Button, { buttonStyle: "pill", disabled: status === "streaming", margin: false, onClick: () => {
|
|
347
348
|
void send(shortcut.prompt);
|
|
348
|
-
}, size: "small", children: shortcut.label }, shortcut.id))) })) : null, selection && bridge ? (_jsxs("div", { className: "ea-selection", children: [_jsxs("span", { className: "ea-selection__label", children: [copy.selected, ": ", _jsx("strong", { children: selection.label })] }), _jsx("button", { "aria-label": copy.clearSelection, className: "ea-selection__clear", type: "button", onClick: () => setSelection(null), children: _jsx(XIcon, {}) })] })) : null, _jsxs("div", { className: "ea-composer__box", children: [_jsx("textarea", { disabled: status === "streaming", placeholder: copy.placeholder, rows: 2, value: draft, onChange: (event) => setDraft(event.target.value), onKeyDown: onKeyDown }), _jsx("div", { className: "ea-composer__bar", children:
|
|
349
|
+
}, size: "small", children: shortcut.label }, shortcut.id))) })) : null, selection && bridge ? (_jsxs("div", { className: "ea-selection", children: [_jsxs("span", { className: "ea-selection__label", children: [copy.selected, ": ", _jsx("strong", { children: selection.label })] }), _jsx("button", { "aria-label": copy.clearSelection, className: "ea-selection__clear", type: "button", onClick: () => setSelection(null), children: _jsx(XIcon, {}) })] })) : null, _jsxs("div", { className: "ea-composer__box", children: [_jsx("textarea", { disabled: status === "streaming", placeholder: copy.placeholder, rows: 2, value: draft, onChange: (event) => setDraft(event.target.value), onKeyDown: onKeyDown }), _jsx("div", { className: "ea-composer__bar", children: status === "streaming" ? (
|
|
350
|
+
// While the assistant works, Send becomes Stop, where the editor's eye already is.
|
|
351
|
+
_jsx(Button, { "aria-label": copy.stop, buttonStyle: "secondary", className: "ea-composer__stop", icon: _jsx("span", { className: "ea-stop-icon", "aria-hidden": "true" }), iconPosition: "left", margin: false, onClick: () => stop(), size: "medium", type: "button", children: copy.stop })) : (_jsx(Button, { buttonStyle: "primary", disabled: draft.trim() === "", margin: false, size: "medium", type: "submit", children: copy.send })) })] })] })] })] }));
|
|
349
352
|
}
|
|
350
353
|
/** "Page · Home", with the locale when the project has more than one. */
|
|
351
354
|
function contextLine(bridge, config) {
|
|
@@ -10,6 +10,6 @@ export function EditorAssistantProvider({ children, shortcuts = [], collections
|
|
|
10
10
|
return (_jsx(AssistantStateProvider, { shortcuts: shortcuts, collections: collections, children: user ? _jsx(EditorAssistantShell, { children: children }) : children }));
|
|
11
11
|
}
|
|
12
12
|
function EditorAssistantShell({ children }) {
|
|
13
|
-
const {
|
|
14
|
-
return (_jsxs("div", { className:
|
|
13
|
+
const { open } = useAssistantState();
|
|
14
|
+
return (_jsxs("div", { className: open ? "ea-root" : "ea-root ea-root--closed", children: [_jsx("div", { className: "ea-main", children: children }), _jsx(EditorAssistantAside, {}), _jsx(ChangedFieldsHighlight, {})] }));
|
|
15
15
|
}
|
|
@@ -46,8 +46,8 @@ export type AssistantMessage = {
|
|
|
46
46
|
}>;
|
|
47
47
|
};
|
|
48
48
|
type AssistantContextValue = {
|
|
49
|
+
/** Whether the panel is shown. Closed at first; the browser remembers the choice. */
|
|
49
50
|
open: boolean;
|
|
50
|
-
collapsed: boolean;
|
|
51
51
|
status: AssistantChromeStatus;
|
|
52
52
|
messages: AssistantMessage[];
|
|
53
53
|
shortcuts: AssistantShortcut[];
|
|
@@ -59,7 +59,6 @@ type AssistantContextValue = {
|
|
|
59
59
|
/** What the editor last clicked or focused in the open form. */
|
|
60
60
|
selection: DomSelection | null;
|
|
61
61
|
setOpen: (open: boolean) => void;
|
|
62
|
-
setCollapsed: (collapsed: boolean) => void;
|
|
63
62
|
setBridge: (bridge: DocumentBridgeSnapshot | null) => void;
|
|
64
63
|
setFormAdapter: (adapter: FormAdapter | null) => void;
|
|
65
64
|
setPendingApproval: (pending: PendingApproval | null) => void;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useCallback, useContext, useMemo, useState } from "react";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
|
|
4
4
|
const AssistantContext = createContext(null);
|
|
5
5
|
export function AssistantStateProvider({ children, shortcuts, collections, }) {
|
|
6
|
-
const [open,
|
|
7
|
-
const [collapsed, setCollapsed] = useState(false);
|
|
6
|
+
const [open, setOpenState] = useState(false);
|
|
8
7
|
const [status, setStatus] = useState("idle");
|
|
9
8
|
const [messages, setMessages] = useState([]);
|
|
10
9
|
const [bridge, setBridge] = useState(null);
|
|
@@ -12,6 +11,15 @@ export function AssistantStateProvider({ children, shortcuts, collections, }) {
|
|
|
12
11
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
13
12
|
const [abortController, setAbortController] = useState(null);
|
|
14
13
|
const [selection, setSelection] = useState(null);
|
|
14
|
+
// Read after mount, so the server render (closed) and the first client render agree.
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (readStoredOpen())
|
|
17
|
+
setOpenState(true);
|
|
18
|
+
}, []);
|
|
19
|
+
const setOpen = useCallback((next) => {
|
|
20
|
+
setOpenState(next);
|
|
21
|
+
storeOpen(next);
|
|
22
|
+
}, []);
|
|
15
23
|
const stop = useCallback(() => {
|
|
16
24
|
abortController?.abort();
|
|
17
25
|
setAbortController(null);
|
|
@@ -26,7 +34,6 @@ export function AssistantStateProvider({ children, shortcuts, collections, }) {
|
|
|
26
34
|
}, [abortController]);
|
|
27
35
|
const value = useMemo(() => ({
|
|
28
36
|
open,
|
|
29
|
-
collapsed,
|
|
30
37
|
status,
|
|
31
38
|
messages,
|
|
32
39
|
shortcuts,
|
|
@@ -36,7 +43,6 @@ export function AssistantStateProvider({ children, shortcuts, collections, }) {
|
|
|
36
43
|
pendingApproval,
|
|
37
44
|
selection,
|
|
38
45
|
setOpen,
|
|
39
|
-
setCollapsed,
|
|
40
46
|
setBridge,
|
|
41
47
|
setFormAdapter,
|
|
42
48
|
setPendingApproval,
|
|
@@ -49,7 +55,6 @@ export function AssistantStateProvider({ children, shortcuts, collections, }) {
|
|
|
49
55
|
setAbortController,
|
|
50
56
|
}), [
|
|
51
57
|
open,
|
|
52
|
-
collapsed,
|
|
53
58
|
status,
|
|
54
59
|
messages,
|
|
55
60
|
shortcuts,
|
|
@@ -58,6 +63,7 @@ export function AssistantStateProvider({ children, shortcuts, collections, }) {
|
|
|
58
63
|
formAdapter,
|
|
59
64
|
pendingApproval,
|
|
60
65
|
selection,
|
|
66
|
+
setOpen,
|
|
61
67
|
resetConversation,
|
|
62
68
|
stop,
|
|
63
69
|
abortController,
|
|
@@ -71,3 +77,21 @@ export function useAssistantState() {
|
|
|
71
77
|
}
|
|
72
78
|
return value;
|
|
73
79
|
}
|
|
80
|
+
const OPEN_KEY = "payload-editor-assistant:open";
|
|
81
|
+
/** Storage can be unavailable (private windows, blocked site data); the panel then starts closed. */
|
|
82
|
+
function readStoredOpen() {
|
|
83
|
+
try {
|
|
84
|
+
return window.localStorage.getItem(OPEN_KEY) === "true";
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function storeOpen(open) {
|
|
91
|
+
try {
|
|
92
|
+
window.localStorage.setItem(OPEN_KEY, String(open));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Not remembered; nothing else depends on it.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -10,8 +10,9 @@ export function claimedUnstagedChange(text) {
|
|
|
10
10
|
export function offeredValue(text, request) {
|
|
11
11
|
return QUOTED.test(text) && ASKS_FOR_CHANGE.test(request);
|
|
12
12
|
}
|
|
13
|
-
//
|
|
14
|
-
|
|
13
|
+
// A request for a change, or a yes to one the assistant asked about. Letter lookarounds rather
|
|
14
|
+
// than \b, which treats å, ä and ö as word boundaries.
|
|
15
|
+
const ASKS_FOR_CHANGE = /(?<!\p{L})(make|change|rewrite|write|add|replace|shorten|tighten|fill|translate|update|improve|set|rename|remove|delete|yes|yeah|sure|ok|okay|do it|go ahead|gör|göra|ändra|skriv|skriva|lägg|lägga|byt|byta|korta|fyll|fylla|översätt|uppdatera|förbättra|sätt|ta bort|ja|japp|kör|gärna)(?!\p{L})/iu;
|
|
15
16
|
const QUOTED = /["“”«»][^"“”«»]{2,}["“”«»]|'[^']{2,}'/;
|
|
16
17
|
const CLAIMED_CHANGE = /godkänn|godkann|approve|rubriken är nu|the headline is now|sparas den på sidan|fälten i på sidan|pending form change|i've applied|i have applied/i;
|
|
17
18
|
const ASKED_TO_MUTATE = /ska jag (lägga|fylla|ändra|sätta|uppdatera)|should i (put|fill|change|set|update|apply)|shall i (put|fill|change|set|update|apply)|want me to (put|fill|change|set|update|apply)/i;
|
|
@@ -19,5 +20,10 @@ export function needsProposeRetry(input) {
|
|
|
19
20
|
if (!input.hasOpenDocument || input.pendingApproval || input.navigate) {
|
|
20
21
|
return false;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
+
const request = input.request ?? "";
|
|
24
|
+
// A claim that the change is made is wrong whatever was asked; asking for permission or writing
|
|
25
|
+
// a value out is only a missed proposal when a change was asked for.
|
|
26
|
+
return (CLAIMED_CHANGE.test(input.text) ||
|
|
27
|
+
(ASKS_FOR_CHANGE.test(request) && ASKED_TO_MUTATE.test(input.text)) ||
|
|
28
|
+
offeredValue(input.text, request));
|
|
23
29
|
}
|
|
@@ -90,7 +90,7 @@ function thisTurnBlock(bridge, turnContext) {
|
|
|
90
90
|
if (source) {
|
|
91
91
|
lines.push(source);
|
|
92
92
|
}
|
|
93
|
-
lines.push("
|
|
93
|
+
lines.push("Fill them only when the editor asks for that (fill in, translate, write, fyll i, översätt). A greeting, a question or small talk is not such a request: answer it and propose nothing. When they do ask, call form.propose now with text in the open language. Do not ask.");
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
return `\n\n${lines.join(" ")}`;
|
|
@@ -8,10 +8,6 @@
|
|
|
8
8
|
overflow: hidden;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
.ea-root--collapsed {
|
|
12
|
-
--ea-aside-width: 48px;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
11
|
html:has(.ea-root),
|
|
16
12
|
html:has(.ea-root) body {
|
|
17
13
|
height: 100%;
|
|
@@ -54,34 +50,6 @@ html[data-theme="dark"] .ea-aside {
|
|
|
54
50
|
--ea-accent-tint: rgba(96, 165, 250, 0.14);
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
.ea-aside--collapsed {
|
|
58
|
-
border-inline-start-color: var(--theme-elevation-100);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.ea-aside__rail {
|
|
62
|
-
display: flex;
|
|
63
|
-
flex-direction: column;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: calc(var(--base) * 0.5);
|
|
66
|
-
width: 100%;
|
|
67
|
-
height: 100%;
|
|
68
|
-
padding: calc(var(--base) * 0.8) 0;
|
|
69
|
-
border: 0;
|
|
70
|
-
background: transparent;
|
|
71
|
-
color: var(--theme-elevation-500);
|
|
72
|
-
font: inherit;
|
|
73
|
-
cursor: pointer;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.ea-aside__rail span {
|
|
77
|
-
writing-mode: vertical-rl;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.ea-aside__rail:hover {
|
|
81
|
-
color: var(--theme-text);
|
|
82
|
-
background: var(--theme-elevation-50);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
53
|
.ea-aside__head {
|
|
86
54
|
display: flex;
|
|
87
55
|
align-items: center;
|
|
@@ -386,6 +354,51 @@ html[data-theme="dark"] .ea-aside {
|
|
|
386
354
|
gap: calc(var(--base) * 0.4);
|
|
387
355
|
}
|
|
388
356
|
|
|
357
|
+
/* Header control: text with an icon, like Admin's other header links rather than a button. */
|
|
389
358
|
.ea-trigger {
|
|
359
|
+
display: inline-flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
gap: 6px;
|
|
390
362
|
margin-inline: 8px;
|
|
363
|
+
padding: 6px 4px;
|
|
364
|
+
border: 0;
|
|
365
|
+
border-radius: var(--style-radius-s, 3px);
|
|
366
|
+
background: none;
|
|
367
|
+
color: var(--theme-elevation-800);
|
|
368
|
+
font: inherit;
|
|
369
|
+
font-size: 14px;
|
|
370
|
+
line-height: 1;
|
|
371
|
+
cursor: pointer;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.ea-trigger:hover,
|
|
375
|
+
.ea-trigger--open {
|
|
376
|
+
color: var(--theme-text);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.ea-trigger:focus-visible {
|
|
380
|
+
outline: 2px solid var(--ea-accent, #2563eb);
|
|
381
|
+
outline-offset: 2px;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.ea-trigger__icon {
|
|
385
|
+
width: 16px;
|
|
386
|
+
height: 16px;
|
|
387
|
+
flex-shrink: 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.ea-trigger--open .ea-trigger__icon {
|
|
391
|
+
color: var(--ea-accent, #2563eb);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.ea-stop-icon {
|
|
395
|
+
display: block;
|
|
396
|
+
width: 9px;
|
|
397
|
+
height: 9px;
|
|
398
|
+
border-radius: 2px;
|
|
399
|
+
background: currentColor;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
html[data-theme="dark"] .ea-trigger--open .ea-trigger__icon {
|
|
403
|
+
color: #60a5fa;
|
|
391
404
|
}
|