@parallelworks/ui 0.79.0 → 0.81.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/README.md +7 -1
- package/dist/ai/adapter/types.d.ts +1 -0
- package/dist/ai/ai.css +3 -92
- package/dist/ai/components/agent/AgentMessageParts.js +1 -0
- package/dist/ai/core/ChatProvider.js +10 -5
- package/dist/ai/strings.d.ts +3 -0
- package/dist/ai/strings.js +2 -0
- package/dist/ai/types.d.ts +5 -0
- package/dist/components/Card.d.ts +16 -1
- package/dist/components/Card.js +13 -2
- package/dist/components/CodeBlock.d.ts +40 -0
- package/dist/components/CodeBlock.js +159 -0
- package/dist/components/Combobox.js +56 -32
- package/dist/components/ContextMenu.d.ts +8 -0
- package/dist/components/ContextMenu.js +42 -6
- package/dist/components/CreateModal.d.ts +3 -1
- package/dist/components/CreateModal.js +15 -8
- package/dist/components/DescriptionList.js +1 -1
- package/dist/components/Disclosure.d.ts +6 -2
- package/dist/components/Disclosure.js +24 -2
- package/dist/components/Drawer.js +1 -1
- package/dist/components/Input.d.ts +5 -0
- package/dist/components/Input.js +4 -3
- package/dist/components/MarkdownImpl.js +89 -2
- package/dist/components/MenuEntries.d.ts +3 -0
- package/dist/components/MenuEntries.js +19 -6
- package/dist/components/Modal.d.ts +1 -1
- package/dist/components/Modal.js +6 -5
- package/dist/components/PageHeader.d.ts +4 -0
- package/dist/components/PageHeader.js +6 -3
- package/dist/components/Provider.d.ts +3 -0
- package/dist/components/Provider.js +1 -0
- package/dist/components/Select.js +1 -1
- package/dist/components/ToggleGroup.d.ts +3 -1
- package/dist/components/ToggleGroup.js +2 -2
- package/dist/components/codeHighlight.d.ts +9 -0
- package/dist/components/codeHighlight.js +29 -0
- package/dist/components/pageHeaderStore.d.ts +4 -0
- package/dist/components/pageHeaderStore.js +5 -2
- package/dist/file-explorer/FileExplorer.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -3
- package/dist/styles/theme.css +1 -0
- package/dist/styles.css +1 -1
- package/dist/theme/derive.d.ts +1 -1
- package/dist/theme/derive.js +3 -1
- package/package.json +1 -1
|
@@ -10,7 +10,11 @@ export interface OpenMenuOptions {
|
|
|
10
10
|
size?: MenuSize | undefined;
|
|
11
11
|
/** As `MenuPopup`'s: classes on the menu and its submenus, such as a terminal's colors. */
|
|
12
12
|
themeClassName?: string | undefined;
|
|
13
|
+
/** What else closes it, for a menu over something that moves under it, such as a
|
|
14
|
+
* terminal's: a wheel outside the menu, the window losing focus, or the window resizing. */
|
|
15
|
+
closeOn?: readonly MenuCloseTrigger[] | undefined;
|
|
13
16
|
}
|
|
17
|
+
export type MenuCloseTrigger = 'wheel' | 'blur' | 'resize';
|
|
14
18
|
/** Where a menu opens: an element, such as the button that opened it, or a pointer event,
|
|
15
19
|
* such as a right-click. An event with Shift held is left to the browser, whose own menu
|
|
16
20
|
* then shows; any other has its default prevented. */
|
|
@@ -29,6 +33,7 @@ export type OpenMenu = (at: MenuOpener, items: MenuEntry[], options?: OpenMenuOp
|
|
|
29
33
|
*/
|
|
30
34
|
export declare function useContextMenu(): {
|
|
31
35
|
openMenu: OpenMenu;
|
|
36
|
+
closeMenu: () => void;
|
|
32
37
|
contextMenu: import("react").JSX.Element | null;
|
|
33
38
|
openedBy: Element | null;
|
|
34
39
|
};
|
|
@@ -40,5 +45,8 @@ export declare function ContextMenuProvider({ children }: {
|
|
|
40
45
|
/** Opens the app's one shared menu: `openMenu(e, items)` from a right-click, or
|
|
41
46
|
* `openMenu(button, items)` under a button, which a second press closes. */
|
|
42
47
|
export declare function useOpenMenu(): OpenMenu;
|
|
48
|
+
/** Closes the app's shared menu, if it is open, for a host whose surface moves out from under
|
|
49
|
+
* it in a way `closeOn` doesn't cover, such as a pane that switches. */
|
|
50
|
+
export declare function useCloseMenu(): () => void;
|
|
43
51
|
/** The element the shared menu is open under, so its button can say it's expanded. */
|
|
44
52
|
export declare function useMenuOpenedBy(): Element | null;
|
|
@@ -3,7 +3,7 @@ import { MenuPopup } from "./Menu.js";
|
|
|
3
3
|
import { isDev } from "../dev.js";
|
|
4
4
|
import { MenuEntries, MenuFilter } from "./MenuEntries.js";
|
|
5
5
|
import { Menu } from "@base-ui/react/menu";
|
|
6
|
-
import { createContext, use, useCallback, useRef, useState } from "react";
|
|
6
|
+
import { createContext, use, useCallback, useEffect, useRef, useState } from "react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/ContextMenu.tsx
|
|
9
9
|
/**
|
|
@@ -44,6 +44,31 @@ function useContextMenu() {
|
|
|
44
44
|
setOpen(true);
|
|
45
45
|
return true;
|
|
46
46
|
}, [release]);
|
|
47
|
+
/** Closes the menu, if one is open, as Escape would. */
|
|
48
|
+
const closeMenu = useCallback(() => {
|
|
49
|
+
if (current.current) {
|
|
50
|
+
current.current = null;
|
|
51
|
+
setOpen(false);
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
const closeOn = open ? opened?.closeOn : void 0;
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!closeOn?.length) return;
|
|
57
|
+
const onWheel = (event) => {
|
|
58
|
+
if (!(event.target instanceof Element && event.target.closest("[role=\"menu\"]"))) closeMenu();
|
|
59
|
+
};
|
|
60
|
+
const listeners = [];
|
|
61
|
+
if (closeOn.includes("wheel")) listeners.push(["wheel", onWheel]);
|
|
62
|
+
if (closeOn.includes("blur")) listeners.push(["blur", closeMenu]);
|
|
63
|
+
if (closeOn.includes("resize")) listeners.push(["resize", closeMenu]);
|
|
64
|
+
for (const [type, listener] of listeners) window.addEventListener(type, listener, {
|
|
65
|
+
capture: true,
|
|
66
|
+
passive: true
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
for (const [type, listener] of listeners) window.removeEventListener(type, listener, { capture: true });
|
|
70
|
+
};
|
|
71
|
+
}, [closeOn, closeMenu]);
|
|
47
72
|
const onOpenChange = useCallback((next, details) => {
|
|
48
73
|
if (next) return;
|
|
49
74
|
const element = current.current?.element;
|
|
@@ -61,6 +86,7 @@ function useContextMenu() {
|
|
|
61
86
|
}, [release]);
|
|
62
87
|
return {
|
|
63
88
|
openMenu,
|
|
89
|
+
closeMenu,
|
|
64
90
|
contextMenu: opened && /* @__PURE__ */ jsx(MenuFilter, {
|
|
65
91
|
search: opened.search,
|
|
66
92
|
children: (query) => /* @__PURE__ */ jsx(Menu.Root, {
|
|
@@ -85,15 +111,19 @@ function useContextMenu() {
|
|
|
85
111
|
}
|
|
86
112
|
var OpenMenuContext = createContext(null);
|
|
87
113
|
var OpenedByContext = createContext(null);
|
|
114
|
+
var CloseMenuContext = createContext(null);
|
|
88
115
|
/** The one menu every row, button and canvas below it opens through `useOpenMenu`, so none
|
|
89
116
|
* renders a menu of its own. `UIProvider` mounts it; nothing else needs to. */
|
|
90
117
|
function ContextMenuProvider({ children }) {
|
|
91
|
-
const { openMenu, contextMenu, openedBy } = useContextMenu();
|
|
118
|
+
const { openMenu, closeMenu, contextMenu, openedBy } = useContextMenu();
|
|
92
119
|
return /* @__PURE__ */ jsx(OpenMenuContext, {
|
|
93
120
|
value: openMenu,
|
|
94
|
-
children: /* @__PURE__ */
|
|
95
|
-
value:
|
|
96
|
-
children:
|
|
121
|
+
children: /* @__PURE__ */ jsx(CloseMenuContext, {
|
|
122
|
+
value: closeMenu,
|
|
123
|
+
children: /* @__PURE__ */ jsxs(OpenedByContext, {
|
|
124
|
+
value: openedBy,
|
|
125
|
+
children: [children, contextMenu]
|
|
126
|
+
})
|
|
97
127
|
})
|
|
98
128
|
});
|
|
99
129
|
}
|
|
@@ -106,9 +136,15 @@ var warnNoProvider = () => {
|
|
|
106
136
|
function useOpenMenu() {
|
|
107
137
|
return use(OpenMenuContext) ?? warnNoProvider;
|
|
108
138
|
}
|
|
139
|
+
var noMenu = () => {};
|
|
140
|
+
/** Closes the app's shared menu, if it is open, for a host whose surface moves out from under
|
|
141
|
+
* it in a way `closeOn` doesn't cover, such as a pane that switches. */
|
|
142
|
+
function useCloseMenu() {
|
|
143
|
+
return use(CloseMenuContext) ?? noMenu;
|
|
144
|
+
}
|
|
109
145
|
/** The element the shared menu is open under, so its button can say it's expanded. */
|
|
110
146
|
function useMenuOpenedBy() {
|
|
111
147
|
return use(OpenedByContext);
|
|
112
148
|
}
|
|
113
149
|
//#endregion
|
|
114
|
-
export { ContextMenuProvider, useContextMenu, useMenuOpenedBy, useOpenMenu };
|
|
150
|
+
export { ContextMenuProvider, useCloseMenu, useContextMenu, useMenuOpenedBy, useOpenMenu };
|
|
@@ -40,7 +40,9 @@ interface CreateModalProps {
|
|
|
40
40
|
dirty?: boolean;
|
|
41
41
|
/** Show a favorite toggle in the footer. */
|
|
42
42
|
showFavorite?: boolean;
|
|
43
|
-
/** Muted text at the footer's start, such as which keys submit
|
|
43
|
+
/** Muted text at the footer's start, such as which keys submit, written with `Kbd` so each
|
|
44
|
+
* reader sees their own keyboard's modifier. Hidden on a touch screen, where keys mean
|
|
45
|
+
* nothing. `error` takes its place. */
|
|
44
46
|
footerHint?: ReactNode;
|
|
45
47
|
/** Why the last submit failed, at the footer's start, read out as it appears. */
|
|
46
48
|
error?: ReactNode;
|
|
@@ -6,6 +6,7 @@ import { useStrings } from "./Provider.js";
|
|
|
6
6
|
import { ActionMenu } from "./ActionMenu.js";
|
|
7
7
|
import { Button } from "./Button.js";
|
|
8
8
|
import { ErrorText } from "./Field.js";
|
|
9
|
+
import { useAutosize } from "./Input.js";
|
|
9
10
|
import { comboboxLeaves } from "./comboboxItems.js";
|
|
10
11
|
import { Combobox } from "./Combobox.js";
|
|
11
12
|
import { Modal } from "./Modal.js";
|
|
@@ -30,6 +31,7 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSea
|
|
|
30
31
|
const [nameTouched, setNameTouched] = useState(false);
|
|
31
32
|
const [interacted, setInteracted] = useState(false);
|
|
32
33
|
const nameRef = useRef(null);
|
|
34
|
+
const { element: nameSizer, resize: resizeName } = useAutosize(open && !hideName, name);
|
|
33
35
|
const nameMessageId = useId();
|
|
34
36
|
const session = open ? initialName ?? "" : null;
|
|
35
37
|
const [shownSession, setShownSession] = useState(session);
|
|
@@ -82,11 +84,11 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSea
|
|
|
82
84
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) handleSubmit(true);
|
|
83
85
|
},
|
|
84
86
|
onMouseDown: (e) => {
|
|
85
|
-
if (e.target instanceof Element && !e.target.closest("input, textarea, button, a, select, label, code, pre, .select-all")) e.preventDefault();
|
|
87
|
+
if (e.target instanceof Element && !e.target.closest("input, textarea, button, a, select, label, code, pre, .select-all, [contenteditable]:not([contenteditable=\"false\"])")) e.preventDefault();
|
|
86
88
|
},
|
|
87
89
|
footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
88
90
|
(showFavorite || footerHint || error) && /* @__PURE__ */ jsxs("div", {
|
|
89
|
-
className: "me-auto flex min-w-0 items-center gap-3",
|
|
91
|
+
className: cx("me-auto flex min-w-0 items-center gap-3", !showFavorite && !error && "pointer-coarse:hidden"),
|
|
90
92
|
children: [showFavorite && /* @__PURE__ */ jsx(Tooltip, {
|
|
91
93
|
content: favorite ? t.favoriteHintOn : t.favoriteHintOff,
|
|
92
94
|
children: /* @__PURE__ */ jsxs(Toggle, {
|
|
@@ -100,7 +102,7 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSea
|
|
|
100
102
|
children: [favorite ? /* @__PURE__ */ jsx(FavoriteIcon, { className: "text-(--theme-favorite)" }) : /* @__PURE__ */ jsx(FavoriteOutlineIcon, {}), favorite ? t.favorited : t.favorite]
|
|
101
103
|
})
|
|
102
104
|
}), error ? /* @__PURE__ */ jsx(ErrorText, { children: error }) : footerHint && /* @__PURE__ */ jsx("p", {
|
|
103
|
-
className: "m-0 min-w-0 text-xs text-(--theme-muted-text-color)",
|
|
105
|
+
className: "m-0 min-w-0 text-xs text-(--theme-muted-text-color) pointer-coarse:hidden",
|
|
104
106
|
children: footerHint
|
|
105
107
|
})]
|
|
106
108
|
}),
|
|
@@ -141,16 +143,21 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSea
|
|
|
141
143
|
children: [typeIcon, typeLabel]
|
|
142
144
|
}),
|
|
143
145
|
hideName ? /* @__PURE__ */ jsx("div", { className: "mt-3" }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
144
|
-
/* @__PURE__ */ jsx("
|
|
145
|
-
ref:
|
|
146
|
-
|
|
146
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
147
|
+
ref: (element) => {
|
|
148
|
+
nameRef.current = element;
|
|
149
|
+
nameSizer.current = element;
|
|
150
|
+
},
|
|
151
|
+
rows: 1,
|
|
147
152
|
autoComplete: "off",
|
|
148
153
|
"data-1p-ignore": true,
|
|
149
154
|
value: name,
|
|
150
155
|
onChange: (e) => {
|
|
151
|
-
const
|
|
156
|
+
const joined = e.target.value.replace(/\s*\n+\s*/g, " ");
|
|
157
|
+
const next = sanitizeName ? sanitizeName(joined) : joined;
|
|
152
158
|
setName(next);
|
|
153
159
|
onNameChange?.(next);
|
|
160
|
+
resizeName?.();
|
|
154
161
|
},
|
|
155
162
|
onBlur: (e) => {
|
|
156
163
|
if (!e.target.value.trim()) setNameTouched(true);
|
|
@@ -168,7 +175,7 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSea
|
|
|
168
175
|
"aria-invalid": nameMissing || !!nameError,
|
|
169
176
|
"aria-describedby": nameError || hint ? nameMessageId : void 0,
|
|
170
177
|
onAnimationEnd: () => setNameFlash(false),
|
|
171
|
-
className: cx("mt-2 -mb-1 -mx-1.5 w-[calc(100%+0.75rem)] rounded-md border-0 px-1.5 py-1 bg-transparent shadow-none text-lg font-medium text-(--theme-app) placeholder:text-(--theme-placeholder) focus:outline-none", nameFlash && "animate-field-flash", nameMissing ? "ring-1 ring-(--theme-error)" : "focus:ring-0")
|
|
178
|
+
className: cx("mt-2 -mb-1 -mx-1.5 block w-[calc(100%+0.75rem)] resize-none overflow-hidden rounded-md border-0 px-1.5 py-1 bg-transparent shadow-none text-lg leading-snug font-medium text-(--theme-app) [field-sizing:content] placeholder:text-(--theme-placeholder) focus:outline-none", nameFlash && "animate-field-flash", nameMissing ? "ring-1 ring-(--theme-error)" : "focus:ring-0")
|
|
172
179
|
}),
|
|
173
180
|
nameError && /* @__PURE__ */ jsx("p", {
|
|
174
181
|
id: nameMessageId,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { Tooltip } from "./Tooltip.js";
|
|
3
3
|
import { useStrings } from "./Provider.js";
|
|
4
|
-
import { InfoTip } from "./InfoTip.js";
|
|
5
4
|
import { CopyButton } from "./CopyButton.js";
|
|
5
|
+
import { InfoTip } from "./InfoTip.js";
|
|
6
6
|
import { withPathBreaks } from "./pathBreaks.js";
|
|
7
7
|
import { createContext, useContext, useId, useRef } from "react";
|
|
8
8
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MouseEventHandler, type ReactNode } from 'react';
|
|
2
2
|
/** The height a collapsible panel animates as it opens and closes. It clips
|
|
3
3
|
* only while it moves, so focus rings and overflowing content show once open. */
|
|
4
4
|
export declare const collapsePanelClassName = "h-(--collapsible-panel-height) transition-[height,opacity] duration-200 ease-out motion-reduce:transition-none data-starting-style:h-0 data-starting-style:overflow-hidden data-starting-style:opacity-0 data-ending-style:h-0 data-ending-style:overflow-hidden data-ending-style:opacity-0";
|
|
@@ -23,6 +23,10 @@ export interface DisclosureProps {
|
|
|
23
23
|
/** Controls beside the summary, outside the toggle, so a row can hold buttons without
|
|
24
24
|
* nesting them in it. */
|
|
25
25
|
actions?: ReactNode;
|
|
26
|
+
/** Controls that read as part of the summary, right after it on its line, such as a button
|
|
27
|
+
* that says why a row failed. They sit outside the toggle, so no button holds another and
|
|
28
|
+
* each is its own stop for Tab; a click on the rest of the line still toggles. */
|
|
29
|
+
summaryControls?: ReactNode;
|
|
26
30
|
/** On the whole disclosure, summary and detail, such as a row's own menu. */
|
|
27
31
|
onContextMenu?: MouseEventHandler<HTMLDivElement> | undefined;
|
|
28
32
|
children: ReactNode;
|
|
@@ -30,7 +34,7 @@ export interface DisclosureProps {
|
|
|
30
34
|
/** A line inside content that reveals more detail beneath it, such as a tool
|
|
31
35
|
* call's arguments or a model's reasoning. For a titled part of a page or
|
|
32
36
|
* form, which belongs in the page's outline, use Section. */
|
|
33
|
-
export declare function Disclosure({ summary, open, onOpenChange, defaultOpen, disabled, keepMounted, className, summaryClassName, panelClassName, chevron, actions, onContextMenu, children, }: DisclosureProps): import("react").JSX.Element;
|
|
37
|
+
export declare function Disclosure({ summary, open, onOpenChange, defaultOpen, disabled, keepMounted, className, summaryClassName, panelClassName, chevron, actions, summaryControls, onContextMenu, children, }: DisclosureProps): import("react").JSX.Element;
|
|
34
38
|
/** The revealing panel alone, for a toggle that lives elsewhere, such as a
|
|
35
39
|
* tree row's chevron that names this panel with `aria-controls`. */
|
|
36
40
|
export declare function DisclosurePanel({ open, id, keepMounted, className, children, }: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { ChevronRightIcon } from "../icons.js";
|
|
3
|
+
import { useRef } from "react";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { Collapsible } from "@base-ui/react/collapsible";
|
|
5
6
|
//#region src/components/Disclosure.tsx
|
|
@@ -9,8 +10,11 @@ var collapsePanelClassName = "h-(--collapsible-panel-height) transition-[height,
|
|
|
9
10
|
/** A line inside content that reveals more detail beneath it, such as a tool
|
|
10
11
|
* call's arguments or a model's reasoning. For a titled part of a page or
|
|
11
12
|
* form, which belongs in the page's outline, use Section. */
|
|
12
|
-
function Disclosure({ summary, open, onOpenChange, defaultOpen = false, disabled = false, keepMounted = false, className, summaryClassName, panelClassName, chevron = true, actions, onContextMenu, children }) {
|
|
13
|
+
function Disclosure({ summary, open, onOpenChange, defaultOpen = false, disabled = false, keepMounted = false, className, summaryClassName, panelClassName, chevron = true, actions, summaryControls, onContextMenu, children }) {
|
|
14
|
+
const trigger = useRef(null);
|
|
15
|
+
const hasControls = summaryControls !== void 0 && summaryControls !== null && summaryControls !== false;
|
|
13
16
|
const toggle = /* @__PURE__ */ jsxs(Collapsible.Trigger, {
|
|
17
|
+
ref: trigger,
|
|
14
18
|
className: cx("group/disclosure flex min-w-0 items-start gap-2 rounded-sm text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--theme-focus)", disabled ? "cursor-default" : "cursor-pointer", summaryClassName),
|
|
15
19
|
children: [summary, !disabled && chevron && /* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
16
20
|
"aria-hidden": "true",
|
|
@@ -24,7 +28,25 @@ function Disclosure({ summary, open, onOpenChange, defaultOpen = false, disabled
|
|
|
24
28
|
disabled,
|
|
25
29
|
className,
|
|
26
30
|
onContextMenu,
|
|
27
|
-
children: [
|
|
31
|
+
children: [hasControls ? /* @__PURE__ */ jsxs("div", {
|
|
32
|
+
className: "flex min-w-0 items-center gap-2",
|
|
33
|
+
children: [
|
|
34
|
+
toggle,
|
|
35
|
+
/* @__PURE__ */ jsx("div", {
|
|
36
|
+
className: "flex shrink-0 items-center gap-1",
|
|
37
|
+
children: summaryControls
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ jsx("div", {
|
|
40
|
+
"aria-hidden": "true",
|
|
41
|
+
className: cx("min-w-0 flex-1 self-stretch", !disabled && "cursor-pointer"),
|
|
42
|
+
onClick: () => trigger.current?.click()
|
|
43
|
+
}),
|
|
44
|
+
actions && /* @__PURE__ */ jsx("div", {
|
|
45
|
+
className: "flex shrink-0 items-center gap-1",
|
|
46
|
+
children: actions
|
|
47
|
+
})
|
|
48
|
+
]
|
|
49
|
+
}) : actions ? /* @__PURE__ */ jsxs("div", {
|
|
28
50
|
className: "flex min-w-0 items-center gap-2",
|
|
29
51
|
children: [/* @__PURE__ */ jsx("div", {
|
|
30
52
|
className: "flex min-w-0 flex-1",
|
|
@@ -47,7 +47,7 @@ function Drawer$1({ open, onClose, title, description, onBack, backLabel, footer
|
|
|
47
47
|
container: contained ? container : portal.container,
|
|
48
48
|
keepMounted,
|
|
49
49
|
className: contained ? "contents" : portal.className,
|
|
50
|
-
children: [modal !== false && /* @__PURE__ */ jsx(Drawer.Backdrop, { className: cx(area, "z-(--layer-drawer) bg-
|
|
50
|
+
children: [modal !== false && /* @__PURE__ */ jsx(Drawer.Backdrop, { className: cx(area, "z-(--layer-drawer) bg-(--theme-backdrop) opacity-[calc(1-var(--drawer-swipe-progress))] transition-opacity duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 data-swiping:duration-0 motion-reduce:transition-none") }), /* @__PURE__ */ jsx(Drawer.Viewport, {
|
|
51
51
|
ref: viewport,
|
|
52
52
|
className: cx(area, "pointer-events-none z-(--layer-drawer) flex", side === "right" ? "justify-end pl-[max(var(--app-inset-start,0px),2.5rem)]" : "justify-start pl-(--app-inset-start,0px) pr-10"),
|
|
53
53
|
children: /* @__PURE__ */ jsxs(Drawer.Popup, {
|
|
@@ -46,6 +46,11 @@ export declare function framed({ label, hint, help, actions, description, error,
|
|
|
46
46
|
/** A single-line text field. `type="password"` adds a button that shows what was typed. Placed in
|
|
47
47
|
* a Field, it takes the Field's label, description and invalid state. */
|
|
48
48
|
export declare function Input({ label, hint, help, description, error, className, mono, size, type, ref, ...props }: InputProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
|
49
|
+
/** Sets a textarea's height to its text's while the browser can't, from its value. */
|
|
50
|
+
export declare function useAutosize(enabled: boolean, value: unknown): {
|
|
51
|
+
element: import("react").RefObject<HTMLTextAreaElement | null>;
|
|
52
|
+
resize: (() => void) | undefined;
|
|
53
|
+
};
|
|
49
54
|
/** A multi-line text field that grows by hand, or with its text with `autosize`. Placed in a
|
|
50
55
|
* Field, it takes the Field's label, description and invalid state. */
|
|
51
56
|
export declare function Textarea({ label, hint, help, description, error, className, mono, size, ref, autosize, bare, secret, style, ...props }: TextareaProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
package/dist/components/Input.js
CHANGED
|
@@ -168,7 +168,7 @@ function TitleInput({ value, onCommit, autoSelect = false, size = "xl", readOnly
|
|
|
168
168
|
setShown(value);
|
|
169
169
|
if (!editing.current) setDraft(value);
|
|
170
170
|
}
|
|
171
|
-
const { resize } = useAutosize(true, draft);
|
|
171
|
+
const { element: sizer, resize } = useAutosize(true, draft);
|
|
172
172
|
useEffect(() => {
|
|
173
173
|
if (autoSelect && element.current) {
|
|
174
174
|
element.current.focus();
|
|
@@ -177,9 +177,10 @@ function TitleInput({ value, onCommit, autoSelect = false, size = "xl", readOnly
|
|
|
177
177
|
}, []);
|
|
178
178
|
const setRef = useCallback((el) => {
|
|
179
179
|
element.current = el;
|
|
180
|
+
sizer.current = el;
|
|
180
181
|
if (typeof ref === "function") ref(el);
|
|
181
182
|
else if (ref) ref.current = el;
|
|
182
|
-
}, [ref]);
|
|
183
|
+
}, [ref, sizer]);
|
|
183
184
|
return /* @__PURE__ */ jsx("textarea", {
|
|
184
185
|
...props,
|
|
185
186
|
ref: setRef,
|
|
@@ -218,4 +219,4 @@ function TitleInput({ value, onCommit, autoSelect = false, size = "xl", readOnly
|
|
|
218
219
|
});
|
|
219
220
|
}
|
|
220
221
|
//#endregion
|
|
221
|
-
export { Input$1 as Input, Textarea, TitleInput, framed };
|
|
222
|
+
export { Input$1 as Input, Textarea, TitleInput, framed, useAutosize };
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
|
+
import { CodeBlock } from "./CodeBlock.js";
|
|
2
3
|
import { ExternalLink } from "./ExternalLink.js";
|
|
3
4
|
import { readIsDark } from "./useCssIsDark.js";
|
|
4
5
|
import { CODE_THEMES } from "./codeTheme.js";
|
|
5
6
|
import { rehypeHttpsImagesOnly } from "./httpsImages.js";
|
|
6
7
|
import { rehypeCodeBreaks } from "./markdownCodeBreaks.js";
|
|
7
|
-
import { useEffect, useState } from "react";
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
8
9
|
import { jsx } from "react/jsx-runtime";
|
|
9
10
|
import { code } from "@streamdown/code";
|
|
10
11
|
import { math } from "@streamdown/math";
|
|
11
12
|
import "katex/dist/katex.min.css";
|
|
12
|
-
import { Streamdown, defaultRehypePlugins } from "streamdown";
|
|
13
|
+
import { Streamdown, defaultComponents, defaultRehypePlugins, useIsCodeFenceIncomplete } from "streamdown";
|
|
13
14
|
//#region src/components/MarkdownImpl.tsx
|
|
14
15
|
var basePlugins = {
|
|
15
16
|
code,
|
|
@@ -35,6 +36,81 @@ function loadMermaid() {
|
|
|
35
36
|
}
|
|
36
37
|
var rehypePlugins = [...Object.values(defaultRehypePlugins), rehypeCodeBreaks];
|
|
37
38
|
var httpsImagesRehypePlugins = [...Object.entries(defaultRehypePlugins).flatMap(([name, plugin]) => name === "harden" ? [rehypeHttpsImagesOnly, plugin] : [plugin]), rehypeCodeBreaks];
|
|
39
|
+
function codeControls(controls) {
|
|
40
|
+
if (controls === false) return {
|
|
41
|
+
copy: false,
|
|
42
|
+
download: null
|
|
43
|
+
};
|
|
44
|
+
const code = controls === void 0 || controls === true ? true : controls.code;
|
|
45
|
+
if (code === false) return {
|
|
46
|
+
copy: false,
|
|
47
|
+
download: null
|
|
48
|
+
};
|
|
49
|
+
if (code === void 0 || code === true) return {
|
|
50
|
+
copy: true,
|
|
51
|
+
download: "file"
|
|
52
|
+
};
|
|
53
|
+
const { download } = code;
|
|
54
|
+
return {
|
|
55
|
+
copy: code.copy !== false,
|
|
56
|
+
download: download === false ? null : typeof download === "object" ? download.filename : "file"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
var EXTENSIONS = {
|
|
60
|
+
javascript: "js",
|
|
61
|
+
typescript: "ts",
|
|
62
|
+
python: "py",
|
|
63
|
+
ruby: "rb",
|
|
64
|
+
rust: "rs",
|
|
65
|
+
golang: "go",
|
|
66
|
+
bash: "sh",
|
|
67
|
+
shell: "sh",
|
|
68
|
+
shellscript: "sh",
|
|
69
|
+
zsh: "sh",
|
|
70
|
+
console: "sh",
|
|
71
|
+
markdown: "md",
|
|
72
|
+
text: "txt",
|
|
73
|
+
plaintext: "txt",
|
|
74
|
+
csharp: "cs",
|
|
75
|
+
kotlin: "kt",
|
|
76
|
+
powershell: "ps1",
|
|
77
|
+
dockerfile: "Dockerfile"
|
|
78
|
+
};
|
|
79
|
+
function extensionFor(language) {
|
|
80
|
+
const lang = language.toLowerCase();
|
|
81
|
+
return EXTENSIONS[lang] ?? (/^[a-z0-9+]{1,10}$/.test(lang) ? lang : "txt");
|
|
82
|
+
}
|
|
83
|
+
var LANGUAGE = /language-([^\s]+)/;
|
|
84
|
+
var START_LINE = /startLine=(\d+)/;
|
|
85
|
+
var NO_LINE_NUMBERS = /\bnoLineNumbers\b/;
|
|
86
|
+
var DefaultCode = defaultComponents.code;
|
|
87
|
+
function textOf(children) {
|
|
88
|
+
if (typeof children === "string") return children;
|
|
89
|
+
if (Array.isArray(children)) return children.map(textOf).join("");
|
|
90
|
+
if (children && typeof children === "object" && "props" in children) return textOf(children.props.children);
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
function makeCode(controls, diagrams) {
|
|
94
|
+
return function MarkdownCode(props) {
|
|
95
|
+
const incomplete = useIsCodeFenceIncomplete();
|
|
96
|
+
const language = props.className?.match(LANGUAGE)?.[1] ?? "";
|
|
97
|
+
if (!("data-block" in props) || diagrams && language === "mermaid") return /* @__PURE__ */ jsx(DefaultCode, { ...props });
|
|
98
|
+
const meta = props.node?.properties?.["metastring"];
|
|
99
|
+
const metastring = typeof meta === "string" ? meta : "";
|
|
100
|
+
const start = Number.parseInt(metastring.match(START_LINE)?.[1] ?? "", 10);
|
|
101
|
+
return /* @__PURE__ */ jsx(CodeBlock, {
|
|
102
|
+
className: "my-3.5",
|
|
103
|
+
code: textOf(props.children),
|
|
104
|
+
language,
|
|
105
|
+
copy: controls.copy,
|
|
106
|
+
filename: controls.download === null ? void 0 : `${controls.download}.${extensionFor(language)}`,
|
|
107
|
+
lineNumbers: NO_LINE_NUMBERS.test(metastring) ? false : void 0,
|
|
108
|
+
startLine: start >= 1 ? start : 1,
|
|
109
|
+
maxHeight: 400,
|
|
110
|
+
streaming: incomplete
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
}
|
|
38
114
|
function MarkdownImpl({ children, isStreaming, controls, components, httpsImagesOnly, compact }) {
|
|
39
115
|
const needsMermaid = !!children && children.includes("```mermaid");
|
|
40
116
|
const [, setMermaidReady] = useState(() => !!pluginsWithMermaid);
|
|
@@ -49,6 +125,16 @@ function MarkdownImpl({ children, isStreaming, controls, components, httpsImages
|
|
|
49
125
|
};
|
|
50
126
|
}, [needsMermaid]);
|
|
51
127
|
const plugins = needsMermaid && pluginsWithMermaid ? pluginsWithMermaid : basePlugins;
|
|
128
|
+
const { copy, download } = codeControls(controls);
|
|
129
|
+
const diagrams = plugins === pluginsWithMermaid;
|
|
130
|
+
const code = useMemo(() => makeCode({
|
|
131
|
+
copy,
|
|
132
|
+
download
|
|
133
|
+
}, diagrams), [
|
|
134
|
+
copy,
|
|
135
|
+
download,
|
|
136
|
+
diagrams
|
|
137
|
+
]);
|
|
52
138
|
return /* @__PURE__ */ jsx("div", {
|
|
53
139
|
className: cx("markdown whitespace-normal text-start w-full", compact && "markdown-compact"),
|
|
54
140
|
children: /* @__PURE__ */ jsx(Streamdown, {
|
|
@@ -63,6 +149,7 @@ function MarkdownImpl({ children, isStreaming, controls, components, httpsImages
|
|
|
63
149
|
rehypePlugins: httpsImagesOnly ? httpsImagesRehypePlugins : rehypePlugins,
|
|
64
150
|
shikiTheme: CODE_THEMES,
|
|
65
151
|
components: {
|
|
152
|
+
code,
|
|
66
153
|
a: ({ node, ...props }) => /* @__PURE__ */ jsx(ExternalLink, {
|
|
67
154
|
...props,
|
|
68
155
|
className: cx("link wrap-break-word", props.className)
|
|
@@ -9,6 +9,9 @@ interface MenuActionBase {
|
|
|
9
9
|
/** The key that does the same from the page, such as `S` or `⌘K`, in plain text at the
|
|
10
10
|
* row's end. The menu doesn't bind it; the host does. */
|
|
11
11
|
shortcut?: string | undefined;
|
|
12
|
+
/** Muted text at the row's end that tells rows apart, such as a team's key `CORE`. It is
|
|
13
|
+
* read with the label, and a menu's search matches it. For a key to press, use `shortcut`. */
|
|
14
|
+
hint?: string | undefined;
|
|
12
15
|
/** A small mark at the row's end, such as a dot for something unseen. It is hidden from
|
|
13
16
|
* screen readers, so say what it means in `label`. */
|
|
14
17
|
badge?: ReactNode;
|
|
@@ -121,7 +121,7 @@ function SearchedEntries({ entries, search, query }) {
|
|
|
121
121
|
const matches = useQueryMatch();
|
|
122
122
|
if (!search) return /* @__PURE__ */ jsx(EntryRows, { entries });
|
|
123
123
|
const needle = query.trim();
|
|
124
|
-
const shown = needle ? searchMatches(entries, (a) => matches(needle, a.label, a.description, a.keywords), (submenu) => matches(needle, submenu.label, submenu.keywords)) : entries;
|
|
124
|
+
const shown = needle ? searchMatches(entries, (a) => matches(needle, a.label, a.description, a.keywords, a.hint), (submenu) => matches(needle, submenu.label, submenu.keywords)) : entries;
|
|
125
125
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
126
126
|
/* @__PURE__ */ jsx("div", {
|
|
127
127
|
className: "sticky top-0 z-[1] bg-(--theme-app-bg) px-1 pt-0.5 pb-1",
|
|
@@ -183,6 +183,7 @@ function RowEnd({ children, className }) {
|
|
|
183
183
|
children
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
|
+
var hintClassName = "ps-3 text-xs text-(--theme-muted-text-color) in-data-[size=sm]:text-[11px]";
|
|
186
187
|
var shortcutClassName = "ps-3 text-xs text-(--theme-muted-text-color) tabular-nums in-data-[size=sm]:text-[11px]";
|
|
187
188
|
/** A row's icon, label, second line and end details, laid out alike in every kind of row. */
|
|
188
189
|
function RowContent({ icon, label, description, end }) {
|
|
@@ -290,11 +291,19 @@ function ActionRow({ action }) {
|
|
|
290
291
|
const runAction = useRunAction();
|
|
291
292
|
const className = cx(action.description && "items-start", action.destructive && destructiveClassName, action.current && "font-medium");
|
|
292
293
|
const keyshortcuts = ariaKeyShortcut(action.shortcut);
|
|
294
|
+
const heard = action.hint && /* @__PURE__ */ jsxs("span", {
|
|
295
|
+
className: "sr-only",
|
|
296
|
+
children: [". ", action.hint]
|
|
297
|
+
});
|
|
293
298
|
const end = (trailing) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
294
299
|
action.badge !== void 0 && action.badge !== null && action.badge !== false && /* @__PURE__ */ jsx(RowEnd, {
|
|
295
300
|
className: "ps-2",
|
|
296
301
|
children: action.badge
|
|
297
302
|
}),
|
|
303
|
+
action.hint && /* @__PURE__ */ jsx(RowEnd, {
|
|
304
|
+
className: hintClassName,
|
|
305
|
+
children: action.hint
|
|
306
|
+
}),
|
|
298
307
|
action.shortcut && /* @__PURE__ */ jsx(RowEnd, {
|
|
299
308
|
className: shortcutClassName,
|
|
300
309
|
children: action.shortcut
|
|
@@ -314,7 +323,7 @@ function ActionRow({ action }) {
|
|
|
314
323
|
className,
|
|
315
324
|
children: /* @__PURE__ */ jsx(RowContent, {
|
|
316
325
|
icon: action.icon,
|
|
317
|
-
label: action.label,
|
|
326
|
+
label: /* @__PURE__ */ jsxs(Fragment$1, { children: [action.label, heard] }),
|
|
318
327
|
description: action.description,
|
|
319
328
|
end: end(action.current ? /* @__PURE__ */ jsx(RowEnd, {
|
|
320
329
|
className: "ps-2",
|
|
@@ -330,10 +339,14 @@ function ActionRow({ action }) {
|
|
|
330
339
|
const reason = action.disabled ? action.disabledReason : void 0;
|
|
331
340
|
const content = (trailing) => /* @__PURE__ */ jsx(RowContent, {
|
|
332
341
|
icon: action.icon,
|
|
333
|
-
label: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
342
|
+
label: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
343
|
+
action.label,
|
|
344
|
+
heard,
|
|
345
|
+
reason && /* @__PURE__ */ jsxs("span", {
|
|
346
|
+
className: "sr-only",
|
|
347
|
+
children: [". ", reason]
|
|
348
|
+
})
|
|
349
|
+
] }),
|
|
337
350
|
description: action.description,
|
|
338
351
|
end: end(trailing)
|
|
339
352
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Dialog } from '@base-ui/react/dialog';
|
|
2
2
|
import { type FormEvent, type ReactNode } from 'react';
|
|
3
3
|
/** The panel a modal draws by default; pass `panel={false}` to paint your own. */
|
|
4
|
-
export declare const modalPanelClassName = "rounded-xl border border-(--theme-border) bg-(--theme-
|
|
4
|
+
export declare const modalPanelClassName = "rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) shadow-2xl";
|
|
5
5
|
/** Names the modal for assistive technology; renders an `<h2>`. */
|
|
6
6
|
export declare const ModalTitle: import("react").ForwardRefExoticComponent<Omit<import("@base-ui/react").DialogTitleProps, "ref"> & import("react").RefAttributes<HTMLHeadingElement>>;
|
|
7
7
|
/** Describes the modal for assistive technology; renders a `<p>`. */
|
package/dist/components/Modal.js
CHANGED
|
@@ -10,7 +10,8 @@ import { AlertDialog } from "@base-ui/react/alert-dialog";
|
|
|
10
10
|
import { Dialog } from "@base-ui/react/dialog";
|
|
11
11
|
//#region src/components/Modal.tsx
|
|
12
12
|
/** The panel a modal draws by default; pass `panel={false}` to paint your own. */
|
|
13
|
-
var modalPanelClassName = "rounded-xl border border-(--theme-border) bg-(--theme-
|
|
13
|
+
var modalPanelClassName = "rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) shadow-2xl";
|
|
14
|
+
var phoneFooterClassName = "max-sm:flex-wrap max-sm:[&>:is(button,a)]:h-10 max-sm:[&>:is(button,a)]:flex-1 max-sm:[&>:not(button,a)]:basis-full";
|
|
14
15
|
/** Names the modal for assistive technology; renders an `<h2>`. */
|
|
15
16
|
var ModalTitle = Dialog.Title;
|
|
16
17
|
/** Describes the modal for assistive technology; renders a `<p>`. */
|
|
@@ -32,7 +33,7 @@ function firstControlIn(panel) {
|
|
|
32
33
|
* and focus returns to where it was on close. Give it a `title`, a `description`
|
|
33
34
|
* and a `footer` for the standard layout, as a Drawer has, or lay out the whole
|
|
34
35
|
* panel with the children. */
|
|
35
|
-
function Modal({ open, onClose, title, description, footer, size, alert = false, dismissible = true, align = "top", panel = true, backdropClassName = "bg-
|
|
36
|
+
function Modal({ open, onClose, title, description, footer, size, alert = false, dismissible = true, align = "top", panel = true, backdropClassName = "bg-(--theme-backdrop)", closeButton = false, onSubmit, className, children, initialFocus, ...popupProps }) {
|
|
36
37
|
const portal = usePopupContainer();
|
|
37
38
|
const { common } = useStrings();
|
|
38
39
|
const panelElement = useRef(null);
|
|
@@ -67,14 +68,14 @@ function Modal({ open, onClose, title, description, footer, size, alert = false,
|
|
|
67
68
|
children
|
|
68
69
|
}),
|
|
69
70
|
footer !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
70
|
-
className: "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3",
|
|
71
|
+
className: cx("flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3", phoneFooterClassName),
|
|
71
72
|
children: footer
|
|
72
73
|
})
|
|
73
74
|
] });
|
|
74
75
|
const content = /* @__PURE__ */ jsxs(Dialog.Portal, {
|
|
75
76
|
...portal,
|
|
76
77
|
children: [/* @__PURE__ */ jsx(Dialog.Backdrop, { className: cx("fixed inset-0 z-(--layer-modal)", backdropMotion, backdropClassName) }), /* @__PURE__ */ jsx(Dialog.Viewport, {
|
|
77
|
-
className: cx("fixed inset-0 z-(--layer-modal) flex justify-center px-4 pt-20 pb-8", align === "center" ? "items-center" : "items-start sm:pt-[max(14vh,5rem)]"),
|
|
78
|
+
className: cx("fixed inset-0 z-(--layer-modal) flex justify-center px-4 pt-20 pb-8 max-sm:px-2 max-sm:pb-[max(8px,env(safe-area-inset-bottom))]", align === "center" ? "items-center" : "items-start sm:pt-[max(14vh,5rem)]"),
|
|
78
79
|
children: /* @__PURE__ */ jsx(Dialog.Popup, {
|
|
79
80
|
...popupProps,
|
|
80
81
|
ref: (element) => {
|
|
@@ -84,7 +85,7 @@ function Modal({ open, onClose, title, description, footer, size, alert = false,
|
|
|
84
85
|
else if (forward) forward.current = element;
|
|
85
86
|
},
|
|
86
87
|
initialFocus: initialFocus ?? (closeButton ? () => firstControlIn(panelElement.current) : void 0),
|
|
87
|
-
className: cx(panel && "rounded-xl border border-(--theme-border) bg-(--theme-
|
|
88
|
+
className: cx(panel && "rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) shadow-2xl", size && [sizeClasses[size], "max-w-full"], panel && (size || laidOut) && "max-sm:w-full", laidOut && "flex max-h-full flex-col", "outline-none", popupMotion, className),
|
|
88
89
|
children: !laidOut ? children : onSubmit ? /* @__PURE__ */ jsx("form", {
|
|
89
90
|
className: "flex min-h-0 flex-1 flex-col",
|
|
90
91
|
onSubmit: (event) => {
|
|
@@ -13,6 +13,10 @@ export interface PageHeaderProps {
|
|
|
13
13
|
* `h1`. A crumb with an empty label, such as a name still loading, shows a
|
|
14
14
|
* placeholder. */
|
|
15
15
|
breadcrumbs?: readonly Breadcrumb[] | undefined;
|
|
16
|
+
/** False keeps the last crumb out of the page's outline, for a page whose `h1` is in its
|
|
17
|
+
* body, such as a title field or a section's heading: the crumb still says it is the
|
|
18
|
+
* current page, and the page has one `h1`. */
|
|
19
|
+
heading?: boolean | undefined;
|
|
16
20
|
/** Beside the title: actions on the page's subject, such as edit, rename or
|
|
17
21
|
* favorite, or its `PageHeaderMenu`. */
|
|
18
22
|
titleActions?: ReactNode;
|