@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 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 +1,2 @@
1
+ /** The one control that opens and closes the assistant panel, in Admin's header. */
1
2
  export declare function EditorAssistantAction(): import("react").JSX.Element | null;
@@ -1,17 +1,23 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { Button, useAuth } from "@payloadcms/ui";
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 { collapsed, setCollapsed, setOpen } = useAssistantState();
9
+ const { i18n } = useTranslation();
10
+ const { open, setOpen } = useAssistantState();
8
11
  if (!user) {
9
12
  return null;
10
13
  }
11
- return (_jsx(Button, { buttonStyle: "pill", className: "ea-trigger", onClick: (event) => {
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(true);
15
- setCollapsed(false);
16
- }, size: "medium", type: "button", children: collapsed ? "Open assistant" : "Assistant" }));
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 { collapsed, setCollapsed, status, messages, shortcuts, collections, bridge, formAdapter, pendingApproval, selection, setSelection, resetConversation, setStatus, setMessages, setPendingApproval, stop, setAbortController, } = useAssistantState();
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
- if (collapsed) {
338
- return (_jsx("aside", { className: "ea-aside ea-aside--collapsed", "aria-label": copy.assistant, children: _jsxs("button", { className: "ea-aside__rail", type: "button", onClick: () => setCollapsed(false), children: [_jsx(ChevronIcon, { direction: "left" }), _jsx("span", { children: copy.assistant })] }) }));
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: () => setCollapsed(true), 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
+ 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 }), _jsx(Button, { buttonStyle: "subtle", margin: false, onClick: () => stop(), size: "small", children: copy.stop })] })) : 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
+ }, 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: _jsx(Button, { buttonStyle: "primary", disabled: status === "streaming" || draft.trim() === "", margin: false, size: "medium", type: "submit", children: copy.send }) })] })] })] })] }));
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 { collapsed } = useAssistantState();
14
- return (_jsxs("div", { className: collapsed ? "ea-root ea-root--collapsed" : "ea-root", children: [_jsx("div", { className: "ea-main", children: children }), _jsx(EditorAssistantAside, {}), _jsx(ChangedFieldsHighlight, {})] }));
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, setOpen] = useState(true);
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
- // Letter lookarounds rather than \b, which treats å, ä and ö as word boundaries.
14
- const ASKS_FOR_CHANGE = /(?<!\p{L})(make|change|rewrite|write|add|replace|shorten|tighten|fill|translate|update|improve|set|rename|remove|delete|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)(?!\p{L})/iu;
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
- return claimedUnstagedChange(input.text) || offeredValue(input.text, input.request ?? "");
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("If the editor wants those filled, call form.propose now with text in the open language. Do not ask.");
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simmalugnt-se/payload-editor-assistant",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Context-aware editor assistant plugin for Payload Admin",
5
5
  "keywords": [
6
6
  "payload",