public
anord
read
Ledger
Why work hard when you can work easier?
Languages
Repository composition by tracked source files.
TypeScript
86%
CSS
10%
SQL
3%
Shell
1%
HTML
0%
Commit
Continues UI Fixes
3d9ace8
apps/web/src/AppShell.tsx | 24 +++++++++++++
apps/web/src/components/PageEditor.tsx | 27 ++++++++++++---
apps/web/src/docs-theme.css | 63 ++++++++++++++++++++++++++++++++--
3 files changed, 107 insertions(+), 7 deletions(-)
Diff
diff --git a/apps/web/src/AppShell.tsx b/apps/web/src/AppShell.tsx
index 49a8e9f..3164995 100644
--- a/apps/web/src/AppShell.tsx
+++ b/apps/web/src/AppShell.tsx
@@ -231,11 +231,14 @@ function DocsShell({
children: React.ReactNode;
}) {
const location = useLocation();
+ const navigate = useNavigate();
const [searchOpen, setSearchOpen] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(false);
+ const [createOpen, setCreateOpen] = useState(false);
const currentPageSlug = location.pathname.startsWith("/page/")
? decodeURIComponent(location.pathname.split("/page/")[1] ?? "")
: undefined;
+ const canCreate = user?.role === "editor" || user?.role === "admin" || user?.role === "owner";
useCommandShortcut(() => setSearchOpen(true));
@@ -261,6 +264,22 @@ function DocsShell({
results={searchResults}
isLoading={searchLoading}
/>
+ {createOpen ? (
+ <>
+ <div className="command-palette__overlay is-open" onClick={() => setCreateOpen(false)} />
+ <div className="editor-modal" role="dialog" aria-modal="true" aria-label="Create document">
+ <PageEditor
+ spaces={spaces}
+ variant="dialog"
+ onCancel={() => setCreateOpen(false)}
+ onCreated={(slug) => {
+ setCreateOpen(false);
+ navigate(`/page/${slug}`);
+ }}
+ />
+ </div>
+ </>
+ ) : null}
{loadingNavigation ? (
<SidebarSkeleton />
@@ -306,6 +325,11 @@ function DocsShell({
<div className="app-header__right">
{user ? (
<>
+ {canCreate ? (
+ <button type="button" className="button-primary" onClick={() => setCreateOpen(true)}>
+ New doc
+ </button>
+ ) : null}
<Link to="/admin/general" className="button-ghost">Admin</Link>
<button className="button-ghost" onClick={onLogout}>Sign out</button>
</>
diff --git a/apps/web/src/components/PageEditor.tsx b/apps/web/src/components/PageEditor.tsx
index efeb408..89d49b9 100644
--- a/apps/web/src/components/PageEditor.tsx
+++ b/apps/web/src/components/PageEditor.tsx
@@ -12,12 +12,23 @@ const emptyPage = {
tagNames: [] as string[]
};
-export function PageEditor({ spaces }: { spaces: Array<{ id: string; name: string; key: string }> }) {
+export function PageEditor({
+ spaces,
+ variant = "panel",
+ onCreated,
+ onCancel
+}: {
+ spaces: Array<{ id: string; name: string; key: string }>;
+ variant?: "panel" | "dialog";
+ onCreated?: (slug: string) => void;
+ onCancel?: () => void;
+}) {
const [form, setForm] = useState({
...emptyPage,
spaceId: spaces[0]?.id ?? ""
});
const [status, setStatus] = useState<string | null>(null);
+ const isDialog = variant === "dialog";
async function submit() {
try {
@@ -31,17 +42,18 @@ export function PageEditor({ spaces }: { spaces: Array<{ id: string; name: strin
...emptyPage,
spaceId: spaces[0]?.id ?? ""
});
+ onCreated?.(result.slug);
} catch (error) {
setStatus(error instanceof Error ? error.message : "Could not save page");
}
}
return (
- <section className="panel">
+ <section className={isDialog ? "editor-dialog" : "panel"}>
<div className="panel__header">
<div>
<p className="eyebrow">Publishing</p>
- <h3>Create a new page</h3>
+ <h3>{isDialog ? "New document" : "Create a new page"}</h3>
</div>
<span className="pill">Editor</span>
</div>
@@ -120,13 +132,18 @@ export function PageEditor({ spaces }: { spaces: Array<{ id: string; name: strin
<label className="field">
Markdown
<textarea
- className="editor-textarea"
+ className={`editor-textarea${isDialog ? " editor-textarea-dialog" : ""}`}
value={form.bodyMarkdown}
onChange={(event) => setForm((current) => ({ ...current, bodyMarkdown: event.target.value }))}
/>
</label>
<div className="panel__footer">
- <button onClick={submit}>Publish draft</button>
+ <button onClick={submit}>Create draft</button>
+ {onCancel ? (
+ <button type="button" className="button-secondary" onClick={onCancel}>
+ Cancel
+ </button>
+ ) : null}
{status ? <p className="muted">{status}</p> : null}
</div>
</section>
diff --git a/apps/web/src/docs-theme.css b/apps/web/src/docs-theme.css
index 64e0217..9c59639 100644
--- a/apps/web/src/docs-theme.css
+++ b/apps/web/src/docs-theme.css
@@ -127,6 +127,18 @@ a:focus {
background: transparent;
}
+.button-primary {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ border-radius: var(--radius-sm);
+ padding: 0.62rem 0.9rem;
+ background: var(--brand);
+ color: #111318;
+ font-weight: 600;
+}
+
button:not(.button-ghost):not(.button-secondary):not(.tree-toggle):not(.space-link):not(.collection-card):not(.search-launcher):not(.home-tab) {
border-radius: var(--radius-sm);
padding: 0.78rem 1rem;
@@ -400,6 +412,31 @@ button:not(.button-ghost):not(.button-secondary):not(.tree-toggle):not(.space-li
color: var(--text);
}
+.editor-modal {
+ position: fixed;
+ top: 5vh;
+ left: 50%;
+ width: min(820px, calc(100% - 2rem));
+ max-height: 88vh;
+ transform: translateX(-50%);
+ z-index: 31;
+ overflow: auto;
+}
+
+.editor-dialog {
+ border: 1px solid var(--line);
+ background: var(--bg-elevated);
+ padding: 1.1rem 1.1rem 1rem;
+}
+
+.editor-textarea {
+ min-height: 320px;
+}
+
+.editor-textarea-dialog {
+ min-height: 420px;
+}
+
.search-launcher kbd {
border: 1px solid var(--line-strong);
border-radius: 3px;
@@ -463,6 +500,12 @@ button:not(.button-ghost):not(.button-secondary):not(.tree-toggle):not(.space-li
gap: 0.2rem;
padding: 0.95rem 0;
border-bottom: 1px solid var(--line);
+ transition: color 140ms ease, border-color 140ms ease;
+}
+
+.home-stream__item:hover {
+ color: #ffffff;
+ border-bottom-color: var(--line-strong);
}
.home-stream__title {
@@ -491,7 +534,7 @@ button:not(.button-ghost):not(.button-secondary):not(.tree-toggle):not(.space-li
.setup-form {
border: 1px solid var(--line);
border-radius: var(--radius-lg);
- background: transparent;
+ background: #10141a;
}
.overview-hero,
@@ -503,12 +546,18 @@ button:not(.button-ghost):not(.button-secondary):not(.tree-toggle):not(.space-li
padding: 1.05rem 1.1rem;
}
+.doc-card {
+ background: transparent;
+ border: 0;
+ padding: 0;
+}
+
.overview-hero {
display: grid;
grid-template-columns: 1.2fr 0.9fr;
gap: 1rem;
align-items: end;
- background: var(--bg-elevated);
+ background: transparent;
}
.overview-hero h1,
@@ -628,6 +677,7 @@ button:not(.button-ghost):not(.button-secondary):not(.tree-toggle):not(.space-li
padding: 0.9rem 0 0.9rem 1rem;
border: 0;
border-left: 1px solid var(--line);
+ background: transparent;
}
.toc-rail a {
@@ -1103,6 +1153,15 @@ button:not(.button-ghost):not(.button-secondary):not(.tree-toggle):not(.space-li
gap: 1rem;
}
+.admin-content > .panel {
+ background: transparent;
+ border-left: 0;
+ border-right: 0;
+ border-radius: 0;
+ padding-left: 0;
+ padding-right: 0;
+}
+
.settings-section {
display: grid;
gap: 0;