Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 35 additions & 21 deletions example-apps/dashnote/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useEffect, useMemo, useState } from "react";
import { Toaster } from "sonner";

import { ActivityPanel } from "./components/ActivityPanel";
import { AppShell } from "./components/AppShell";
import { HowItWorks } from "./components/HowItWorks";
import { LoginModal } from "./components/LoginModal";
import { NotesToolbar } from "./components/NotesToolbar";
import { NotesWorkspace } from "./components/NotesWorkspace";
import { OperationResultNotice } from "./components/OperationResultNotice";
import { SettingsPanel } from "./components/SettingsPanel";
Expand Down Expand Up @@ -33,6 +35,7 @@ function App() {
const { status, sdk, enterReadOnly, viewAsRemembered } = session;
const [tab, setTab] = useState<TopTab>("notes");
const [loginOpen, setLoginOpen] = useState(false);
const [activityOpen, setActivityOpen] = useState(false);

const mobileFullBleed = tab === "notes";

Expand All @@ -41,6 +44,17 @@ function App() {
else if (status === "browsing" && !sdk) void viewAsRemembered();
}, [enterReadOnly, viewAsRemembered, status, sdk]);

useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "l") {
e.preventDefault();
setActivityOpen((v) => !v);
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, []);

const header = useMemo(() => screenCopy[tab], [tab]);

return (
Expand All @@ -56,30 +70,26 @@ function App() {
onLoginOpen={() => setLoginOpen(true)}
mobileFullBleed={mobileFullBleed}
>
<header
className={`rounded-[28px] border border-line bg-surface px-5 py-5 shadow-[0_20px_60px_-36px_rgba(0,0,0,0.45)] max-md:rounded-none max-md:border-0 max-md:bg-transparent max-md:px-4 max-md:py-4 max-md:shadow-none ${
tab === "notes" &&
(status === "authenticated" || status === "browsing")
? "max-md:hidden"
: ""
}`}
>
<div className="text-[10px] font-semibold uppercase tracking-[0.14em] text-ink-4">
Dash Platform Notes Tutorial
</div>
<h1 className="mt-2 text-[28px] font-semibold leading-[1.05] tracking-tight text-ink">
{header.title}
</h1>
<p className="mt-2 max-w-[760px] text-[13px] leading-6 text-ink-3">
{header.subtitle}
</p>
</header>
{tab === "notes" ? (
<NotesToolbar
title="Notes"
onOpenActivity={() => setActivityOpen(true)}
/>
) : tab === "how-it-works" ? null : (
<header className="rounded-[28px] border border-line bg-surface px-5 py-5 shadow-[0_20px_60px_-36px_rgba(0,0,0,0.45)] max-md:rounded-none max-md:border-0 max-md:bg-transparent max-md:px-4 max-md:py-4 max-md:shadow-none">
<h1 className="mt-2 text-[28px] font-semibold leading-[1.05] tracking-tight text-ink">
{header.title}
</h1>
<p className="mt-2 max-w-[760px] text-[13px] leading-6 text-ink-3">
{header.subtitle}
</p>
</header>
)}

<div
className={`${
tab === "notes" &&
(status === "authenticated" || status === "browsing")
? "mt-6 max-md:mt-0"
tab === "notes" || tab === "how-it-works"
? "mt-4 max-md:mt-0"
: "mt-6"
} ${mobileFullBleed ? "max-md:flex max-md:min-h-0 max-md:flex-1 max-md:flex-col" : ""}`}
>
Expand All @@ -105,6 +115,10 @@ function App() {
</AppShell>

<LoginModal open={loginOpen} onClose={() => setLoginOpen(false)} />
<ActivityPanel
open={activityOpen}
onClose={() => setActivityOpen(false)}
/>
</>
);
}
Expand Down
116 changes: 116 additions & 0 deletions example-apps/dashnote/src/components/ActivityPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useEffect } from "react";

import { formatRelativeTime } from "../lib/format";
import { useSession } from "../session/useSession";

interface ActivityPanelProps {
open: boolean;
onClose: () => void;
}

export function ActivityPanel({ open, onClose }: ActivityPanelProps) {
const { activityLog, clearActivityLog } = useSession();

useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);

if (!open) return null;

return (
<div
role="dialog"
aria-modal="true"
aria-label="Activity log"
className="fixed inset-0 z-40 flex justify-end bg-black/40"
onClick={onClose}
>
<aside
onClick={(e) => e.stopPropagation()}
className="flex h-full w-full max-w-[440px] flex-col border-l border-line bg-surface shadow-[0_30px_70px_-22px_rgba(0,0,0,0.7)]"
>
<div className="flex items-center justify-between border-b border-line px-5 py-3">
<div className="flex items-center gap-2">
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
className="text-accent"
aria-hidden
>
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
</svg>
<div className="text-[13px] font-semibold text-ink">Activity</div>
<span className="rounded-full bg-surface-2 px-2 py-0.5 font-mono text-[10px] text-ink-4">
live
</span>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={clearActivityLog}
className="rounded-md border border-line px-2 py-1 text-[11px] text-ink-3 hover:border-line-2 hover:text-ink"
>
Clear
</button>
<button
type="button"
onClick={onClose}
aria-label="Close"
className="text-ink-4 hover:text-ink"
>
×
</button>
</div>
</div>

<div className="flex-1 overflow-y-auto">
{activityLog.length === 0 ? (
<div className="px-5 py-8 text-center text-[12.5px] text-ink-4">
No activity yet. Save a note to see SDK calls land here.
</div>
) : (
activityLog.map((entry) => (
<div
key={entry.id}
className="grid grid-cols-[10px_1fr_auto] items-center gap-3 px-5 py-2.5 hover:bg-bg/30"
>
<span
className={`h-1.5 w-1.5 rounded-full ${
entry.level === "success"
? "bg-[oklch(70%_0.16_150)]"
: entry.level === "error"
? "bg-[color:var(--color-danger)]"
: "bg-ink-4"
}`}
aria-hidden
/>
<div className="min-w-0">
<div className="truncate text-[12.5px] text-ink">
{entry.message}
</div>
{entry.detail && (
<div className="truncate font-mono text-[11px] text-ink-4">
{entry.detail}
</div>
)}
</div>
<div className="font-mono text-[10.5px] text-ink-4">
{formatRelativeTime(entry.timestamp)}
</div>
</div>
))
)}
</div>
</aside>
</div>
);
}
20 changes: 18 additions & 2 deletions example-apps/dashnote/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,28 @@ interface AppShellProps {
function LogoAvatar() {
return (
<div
className="h-[28px] w-[28px] shrink-0 rounded-[9px] border border-black/10 shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]"
className="flex h-[28px] w-[28px] shrink-0 items-center justify-center rounded-[9px] border border-black/10 shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]"
style={{
background:
"linear-gradient(135deg, oklch(94% 0.04 95), oklch(88% 0.05 85))",
}}
/>
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="oklch(35% 0.05 65)"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M14 3v4a1 1 0 0 0 1 1h4" />
<path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z" />
<path d="M9 9h1M9 13h6M9 17h6" />
</svg>
</div>
);
}

Expand Down
Loading