Skip to content
Merged
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
5 changes: 4 additions & 1 deletion example-apps/dashnote/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { NotesWorkspace } from "./components/NotesWorkspace";
import { OperationResultNotice } from "./components/OperationResultNotice";
import { SettingsPanel } from "./components/SettingsPanel";
import type { TopTab } from "./components/Tabs";
import { useMediaQuery } from "./hooks/useMediaQuery";
import { useSession } from "./session/useSession";

const screenCopy: Record<TopTab, { title: string; subtitle: string }> = {
Expand All @@ -36,6 +37,7 @@ function App() {
const [tab, setTab] = useState<TopTab>("notes");
const [loginOpen, setLoginOpen] = useState(false);
const [activityOpen, setActivityOpen] = useState(false);
const isDesktop = useMediaQuery("(min-width: 768px)");

const mobileFullBleed = tab === "notes";

Expand All @@ -59,7 +61,7 @@ function App() {

return (
<>
<Toaster position="bottom-center" />
<Toaster position={isDesktop ? "bottom-center" : "top-center"} />
<AppShell
tab={tab}
onTabChange={setTab}
Expand All @@ -68,6 +70,7 @@ function App() {
dpnsName={session.dpnsName}
contractId={session.contractId}
onLoginOpen={() => setLoginOpen(true)}
onOpenActivity={() => setActivityOpen(true)}
mobileFullBleed={mobileFullBleed}
>
{tab === "notes" ? (
Expand Down
15 changes: 15 additions & 0 deletions example-apps/dashnote/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface AppShellProps {
dpnsName: string | null;
contractId: string | null;
onLoginOpen: () => void;
onOpenActivity?: () => void;
children: ReactNode;
mobileFullBleed?: boolean;
}
Expand Down Expand Up @@ -97,6 +98,7 @@ export function AppShell({
dpnsName,
contractId,
onLoginOpen,
onOpenActivity,
children,
mobileFullBleed = false,
}: AppShellProps) {
Expand Down Expand Up @@ -132,6 +134,19 @@ export function AppShell({
closeDrawer();
}}
/>
{onOpenActivity && (
<div className="md:hidden">
<NavButton
label="Activity"
glyph="⌁"
active={false}
onClick={() => {
onOpenActivity();
closeDrawer();
}}
/>
</div>
)}
{status !== "authenticated" && status !== "browsing" && (
<NavButton
label="Sign in"
Expand Down
7 changes: 4 additions & 3 deletions example-apps/dashnote/src/components/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
<Modal
open={open}
onClose={onClose}
panelClassName="max-md:max-h-[calc(100dvh-1.5rem)] max-md:overflow-y-auto"
title={
<div className="flex items-center gap-2.5">
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-accent text-bg">
Expand Down Expand Up @@ -279,14 +280,14 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
<button
type="submit"
disabled={submitting || !secret.trim() || previewBlocksLogin}
className="flex-1 rounded-md bg-accent px-4 py-2 text-[13px] font-semibold text-bg transition hover:bg-accent-dim disabled:cursor-not-allowed disabled:bg-surface-2 disabled:text-ink-4"
className="min-h-11 flex-1 rounded-md bg-accent px-4 py-2 text-[13px] font-semibold text-bg transition hover:bg-accent-dim disabled:cursor-not-allowed disabled:bg-surface-2 disabled:text-ink-4"
>
{submitting ? "Connecting…" : "Sign in"}
</button>
<button
type="button"
onClick={onClose}
className="rounded-md border border-line bg-transparent px-4 py-2 text-[13px] font-semibold text-ink-3 transition hover:border-line-2 hover:text-ink-2"
className="min-h-11 rounded-md border border-line bg-transparent px-4 py-2 text-[13px] font-semibold text-ink-3 transition hover:border-line-2 hover:text-ink-2"
>
Cancel
</button>
Expand All @@ -312,7 +313,7 @@ export function LoginModal({ open, onClose }: LoginModalProps) {
href="https://bridge.thepasta.org/"
target="_blank"
rel="noreferrer"
className="font-semibold text-accent underline-offset-2 hover:underline"
className="font-semibold text-accent underline-offset-2 hover:underline max-md:mt-2 max-md:inline-flex max-md:min-h-10 max-md:items-center max-md:rounded-full max-md:border max-md:border-line-2 max-md:px-3 max-md:no-underline"
>
Create one on Dash Bridge →
</a>
Expand Down
115 changes: 115 additions & 0 deletions example-apps/dashnote/src/components/MobileActionSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { useEffect, useId, useRef, type ReactNode } from "react";

interface MobileActionSheetProps {
open: boolean;
title: string;
children: ReactNode;
onClose: () => void;
}

const FOCUSABLE_SELECTOR = [
"a[href]",
"button:not(:disabled)",
"input:not(:disabled)",
"select:not(:disabled)",
"textarea:not(:disabled)",
'[tabindex]:not([tabindex="-1"])',
].join(",");

export function MobileActionSheet({
open,
title,
children,
onClose,
}: MobileActionSheetProps) {
const dialogRef = useRef<HTMLDivElement>(null);
const titleId = useId();

useEffect(() => {
if (!open) return;

const previousFocus =
document.activeElement instanceof HTMLElement
? document.activeElement
: null;
const dialog = dialogRef.current;

function focusableElements() {
if (!dialog) return [];
return Array.from(
dialog.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
);
}

const firstFocusable = focusableElements()[0];
(firstFocusable ?? dialog)?.focus();

function onKey(event: KeyboardEvent) {
if (event.key === "Escape") {
onClose();
return;
}
if (event.key !== "Tab") return;

const focusable = focusableElements();
if (focusable.length === 0) {
event.preventDefault();
dialog?.focus();
return;
}

const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}

window.addEventListener("keydown", onKey);
return () => {
window.removeEventListener("keydown", onKey);
if (previousFocus && document.contains(previousFocus)) {
previousFocus.focus();
}
};
}, [open, onClose]);

if (!open) return null;

return (
<div
className="fixed inset-0 z-50 flex items-end bg-black/40 px-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] md:hidden"
onClick={onClose}
data-testid="mobile-action-sheet-backdrop"
>
<div
ref={dialogRef}
role="dialog"
aria-modal="true"
aria-labelledby={titleId}
tabIndex={-1}
className="w-full rounded-2xl border border-line bg-surface p-2 shadow-[0_22px_60px_-24px_rgba(0,0,0,0.65)] outline-none"
onClick={(event) => event.stopPropagation()}
>
<h2
id={titleId}
className="px-4 pb-1 pt-3 text-[12px] font-semibold uppercase tracking-[0.12em] text-ink-4"
>
{title}
</h2>
<div className="py-1">{children}</div>
<button
type="button"
onClick={onClose}
className="mt-1 flex min-h-12 w-full items-center justify-center rounded-xl bg-surface-2 px-4 py-3 text-[15px] font-semibold text-ink"
>
Cancel
</button>
</div>
</div>
);
}
4 changes: 2 additions & 2 deletions example-apps/dashnote/src/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function Modal({

return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm max-md:items-end max-md:p-0"
onClick={onClose}
>
<div
Expand All @@ -51,7 +51,7 @@ export function Modal({
aria-modal="true"
aria-labelledby={titleId}
tabIndex={-1}
className={`w-full max-w-md overflow-hidden rounded-xl border border-line bg-surface shadow-[0_24px_60px_-20px_rgba(0,0,0,0.7)] outline-none ${panelClassName ?? ""}`}
className={`w-full max-w-md overflow-hidden rounded-xl border border-line bg-surface shadow-[0_24px_60px_-20px_rgba(0,0,0,0.7)] outline-none max-md:max-w-none max-md:rounded-b-none max-md:border-x-0 max-md:border-b-0 ${panelClassName ?? ""}`}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between px-5 py-3">
Expand Down
Loading