Files
famapp/src/modules/calendar/components/shared-view.tsx
T
ginnoirandClaude Opus 4.7 9612a54e52
Release / build-and-push (push) Has been cancelled
Apply paper-and-ink design system across all surfaces
Replaces the generic shadcn/ui gray theme + horizontal top-bar shell with
the paper-and-ink language from the Claude Design handoff bundle: warm
off-white paper, near-black ink, Source Serif 4 + Inter, hairline borders,
muted ink accents (clay/indigo/sage/plum/ochre) used functionally for
calendars and share scopes.

Theme switcher expanded from 2 dimensions (theme × mode) to 7: palette ×
mode × fontPair × density × dashLayout × calView × navStyle. All exposed
in Settings → Appearance and persisted on the users row. Pre-paint script
applies all four data-* attributes from localStorage so reload doesn't
flash.

App shell restructured to a CSS-grid driven by data-nav on <html>: sidebar
on desktop, bottom-nav + FAB under 760px. Four desktop nav modes wired
(sidebar/rail/top/fab-only). Topbar gets a search-→-CommandPalette button,
notification bell, "+ New" quick-add, avatar.

Dashboard, calendar, lists, notes, settings, login, public share viewer,
and quick-add sheet all reskinned. Dashboard editor gains a Preset menu
(classic/split/glance) that fills the layout from the registered widgets.
FullCalendar wrapped in .fc-skin and inherits all paper-and-ink tokens via
CSS variable overrides. Public share viewer (/s/<token>) rebuilt around
ShareFrame: expiration banner, brand strip, eyebrow chip, 38px serif
title, mini-day + mini-map cards, share-rows.

Schema: drops users.theme; adds theme_palette, theme_font_pair,
theme_density, theme_dash_layout, theme_cal_view, theme_nav_style with
defaults that match the design (clay / serif-sans / regular / classic /
month / rail-desktop). Migration 0014_paper_ink_theme.

Middleware sets x-pathname so the AppShell server component can render
bare for /s/* and /login without a route-group refactor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 01:03:21 -05:00

168 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Calendar as CalendarIcon, MapPin } from "lucide-react";
import type { CalendarShareData, EventShareData } from "../server/share-queries";
import { ShareEyebrow } from "@/components/share/share-eyebrow";
import { MiniDayCard } from "@/components/share/mini-day-card";
import { MiniMapCard } from "@/components/share/mini-map-card";
import { ShareDetailCard, ShareRow } from "@/components/share/share-detail-card";
function formatTime(d: Date): string {
return d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
}
function formatTimeRange(startAt: string, endAt: string, allDay: boolean): string {
const start = new Date(startAt);
const end = new Date(endAt);
if (allDay) return "All day";
return `${formatTime(start)} ${formatTime(end)}`;
}
function formatFullDate(d: Date): string {
return d.toLocaleDateString(undefined, {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
});
}
export function EventSharedView({ data }: { data: EventShareData }) {
const start = new Date(data.startAt);
const end = new Date(data.endAt);
const time = data.allDay ? "All day" : `${formatTime(start)} ${formatTime(end)}`;
return (
<>
<ShareEyebrow>
<CalendarIcon className="size-3" />
Event
</ShareEyebrow>
<h1
className="serif"
style={{
fontSize: "clamp(28px, 6vw, 38px)",
lineHeight: 1.15,
letterSpacing: "-0.02em",
color: "var(--ink)",
margin: "0 0 8px",
textWrap: "pretty",
}}
>
{data.title}
</h1>
{data.calendarName && (
<p
className="serif"
style={{
fontSize: 17,
color: "var(--ink-soft)",
margin: "0 0 28px",
}}
>
On the {data.calendarName} calendar.
</p>
)}
<div
className="grid gap-3.5 mb-6"
style={{ gridTemplateColumns: data.location ? "1fr 1fr" : "1fr" }}
>
<MiniDayCard date={start} time={time} />
{data.location && <MiniMapCard name={data.location} />}
</div>
<ShareDetailCard>
<ShareRow label="When">
{formatFullDate(start)}
{!data.allDay && (
<>
{" · "}
{formatTimeRange(data.startAt, data.endAt, data.allDay)}
</>
)}
</ShareRow>
{data.location && (
<ShareRow label="Where">
<span className="inline-flex items-center gap-1.5">
<MapPin className="size-3.5 text-[var(--ink-mute)]" />
{data.location}
</span>
</ShareRow>
)}
<ShareRow label="Calendar">{data.calendarName}</ShareRow>
</ShareDetailCard>
{data.notes && (
<p
className="serif"
style={{
fontSize: 16,
lineHeight: 1.65,
color: "var(--ink-2)",
background: "var(--paper-2)",
borderRadius: 8,
padding: "18px 22px",
margin: "14px 0 24px",
textWrap: "pretty",
whiteSpace: "pre-wrap",
}}
>
{data.notes}
</p>
)}
</>
);
}
export function CalendarSharedView({ data }: { data: CalendarShareData }) {
return (
<>
<ShareEyebrow>
<CalendarIcon className="size-3" />
Calendar
</ShareEyebrow>
<h1
className="serif"
style={{
fontSize: "clamp(28px, 6vw, 38px)",
lineHeight: 1.15,
letterSpacing: "-0.02em",
color: "var(--ink)",
margin: "0 0 8px",
textWrap: "pretty",
}}
>
{data.name}
</h1>
<p className="serif" style={{ fontSize: 17, color: "var(--ink-soft)", margin: "0 0 28px" }}>
Upcoming events next 90 days.
</p>
<ShareDetailCard>
{data.events.length === 0 ? (
<p className="muted text-[13.5px] py-2">No upcoming events.</p>
) : (
data.events.map((event) => {
const start = new Date(event.startAt);
return (
<div key={event.id} className="list-row" style={{ padding: "10px 0" }}>
<span className="dot" style={{ background: data.color ?? "var(--c-household)" }} />
<div className="flex-1 min-w-0">
<div className="text-[14px] font-medium text-[var(--ink-2)]">{event.title}</div>
<div className="muted tnum text-[12px] mt-0.5">
{start.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})}
{!event.allDay && ` · ${formatTime(start)}`}
{event.location && ` · ${event.location}`}
</div>
</div>
</div>
);
})
)}
</ShareDetailCard>
</>
);
}