silverbullet/web/components/top_bar.tsx

143 lines
4.2 KiB
TypeScript

import type {
CompletionContext,
CompletionResult,
} from "@codemirror/autocomplete";
import type { ComponentChildren, FunctionalComponent } from "preact";
import type { Notification } from "@silverbulletmd/silverbullet/type/client";
import type { FeatherProps } from "preact-feather/types";
import type { IconBaseProps } from "react-icons/types";
import { MiniEditor } from "./mini_editor.tsx";
export type ActionButton = {
icon: FunctionalComponent<FeatherProps | IconBaseProps>;
description: string;
class?: string;
callback: () => void;
href?: string;
mobile?: boolean;
};
export function TopBar({
pageName,
unsavedChanges,
syncFailures,
isLoading,
notifications,
onRename,
actionButtons,
darkMode,
vimMode,
progressPerc,
completer,
lhs,
onClick,
rhs,
pageNamePrefix,
cssClass,
}: {
pageName?: string;
unsavedChanges: boolean;
syncFailures: number;
isLoading: boolean;
notifications: Notification[];
darkMode: boolean;
vimMode: boolean;
progressPerc?: number;
onRename: (newName?: string) => Promise<void>;
onClick: () => void;
completer: (context: CompletionContext) => Promise<CompletionResult | null>;
actionButtons: ActionButton[];
lhs?: ComponentChildren;
rhs?: ComponentChildren;
pageNamePrefix?: string;
cssClass?: string;
}) {
return (
<div
id="sb-top"
className={syncFailures > 1 ? "sb-sync-error" : undefined}
onClick={onClick}
>
{lhs}
<div className="main">
<div className="inner">
<div className="wrapper">
<div className="sb-page-prefix">{pageNamePrefix}</div>
<span
id="sb-current-page"
className={(isLoading
? "sb-loading"
: unsavedChanges
? "sb-unsaved"
: "sb-saved") +
(cssClass ? " sb-decorated-object " + cssClass : "")}
>
<MiniEditor
text={pageName ?? ""}
vimMode={vimMode}
darkMode={darkMode}
onBlur={(newName) => {
if (newName !== pageName) {
return onRename(newName);
} else {
return onRename();
}
}}
completer={completer}
onEnter={(newName) => {
onRename(newName);
}}
/>
</span>
{notifications.length > 0 && (
<div className="sb-notifications">
{notifications.map((notification) => (
<div
key={notification.id}
className={`sb-notification-${notification.type}`}
>
{notification.message}
</div>
))}
</div>
)}
<div className="sb-actions">
{progressPerc !== undefined &&
(
<div className="progress-wrapper" title={`Sync Progress: ${progressPerc}%`}>
<div
className="progress-bar"
style={`background: radial-gradient(closest-side, var(--top-background-color) 79%, transparent 80% 100%), conic-gradient(var(--button-color) ${progressPerc}%, var(--button-background-color) 0);`}
>
{progressPerc}
</div>
</div>
)}
{actionButtons.map((actionButton) => {
const button = (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
actionButton.callback();
}}
title={actionButton.description}
className={actionButton.class}
>
<actionButton.icon size={18} />
</button>
);
return actionButton.href !== undefined
? <a href={actionButton.href}>{button}</a>
: button;
})}
</div>
</div>
</div>
</div>
{rhs}
</div>
);
}