Add new styles for home and landing pages

- Created home.css with comprehensive styles for the home page layout, including typography, buttons, and responsive design adjustments.
- Created landing.css to style the landing page, focusing on typography, layout, and responsive behavior for various screen sizes.
This commit is contained in:
syntaxbullet
2026-09-04 11:50:24 +02:00
parent 1acf016169
commit 084603bb6e
43 changed files with 7191 additions and 65 deletions

View File

@@ -0,0 +1,245 @@
import { useEffect, useId, useRef, useState, type ReactNode } from "react";
import { CalendarLegend } from "./CalendarLegend";
import {
describeDay,
progressShade,
MONTH_VIEWS,
visibleCalendarDays,
type CalendarDay,
type MonthView,
} from "./calendar-model";
export function CalendarHeatmap({
days: allDays,
unit,
label,
color = "#111111",
emptyColor = "#eeeeee",
compact = false,
selectedDate: controlledDate,
onSelectDate,
historyLabel,
legend,
}: {
days: CalendarDay[];
unit: string;
label: string;
color?: string;
emptyColor?: string;
compact?: boolean;
selectedDate?: string;
onSelectDate?: (date: string) => void;
historyLabel?: string;
legend?: ReactNode;
}) {
const [months, setMonths] = useState<MonthView>(6);
const latestDate =
allDays.findLast((day) => day.state !== "future")?.date ??
allDays.at(-1)?.date;
// A date selected in the editor must stay visible even outside the current window.
const anchor =
controlledDate && allDays.some((day) => day.date === controlledDate)
? controlledDate
: latestDate;
const [localSelectedDate, setLocalSelectedDate] = useState(anchor);
const selectedDate = controlledDate ?? localSelectedDate;
function setSelectedDate(date: string) {
if (onSelectDate) onSelectDate(date);
else setLocalSelectedDate(date);
}
const days = anchor ? visibleCalendarDays(allDays, months, anchor) : [];
const selected =
days.find((day) => day.date === selectedDate) ??
days.find((day) => day.date === anchor) ??
days[0];
const offset = days[0]
? new Date(`${days[0].date}T12:00:00Z`).getUTCDay()
: 0;
const weeks = Math.ceil((days.length + offset) / 7);
const buttons = useRef<(HTMLButtonElement | null)[]>([]);
const scrollArea = useRef<HTMLDivElement | null>(null);
const inspectorId = useId();
const selectedIndex = selected ? days.indexOf(selected) : -1;
useEffect(() => {
const container = scrollArea.current;
const button = buttons.current[selectedIndex];
if (!container || !button) return;
const cellBounds = button.getBoundingClientRect();
const bounds = container.getBoundingClientRect();
if (cellBounds.right > bounds.right - 5)
container.scrollLeft += cellBounds.right - bounds.right + 5;
else if (cellBounds.left < bounds.left + 5)
container.scrollLeft += cellBounds.left - bounds.left - 5;
}, [months, selectedIndex]);
if (!selected) return <p>No calendar dates to display.</p>;
return (
<div
className={`ds-calendar${compact ? " ds-calendar--compact" : ""}`}
data-months={months}
>
<div className="ds-calendar-range-toolbar">
<span className="ds-calendar-range-label" aria-live="polite">
{new Date(`${days[0]!.date}T12:00:00Z`).toLocaleDateString("en", {
month: "short",
year: "numeric",
timeZone: "UTC",
})}{" "}
–{" "}
{new Date(`${days.at(-1)!.date}T12:00:00Z`).toLocaleDateString("en", {
month: "short",
year: "numeric",
timeZone: "UTC",
})}
</span>
<div
className="ds-month-views"
role="group"
aria-label={`${label} time range`}
>
{MONTH_VIEWS.map((value) => (
<button
key={value}
type="button"
aria-label={`${value} months`}
aria-pressed={months === value}
onClick={() => setMonths(value)}
>
{value}m
</button>
))}
</div>
</div>
<div
ref={scrollArea}
className="ds-calendar-scroll"
role="group"
aria-label={label}
>
<div
className="ds-calendar-inner"
style={{
minWidth:
months === 12
? 0
: Math.max(compact ? 280 : 600, weeks * 14 + 38),
}}
>
<div
className="ds-calendar-months"
aria-hidden="true"
style={{
gridTemplateColumns: `repeat(${weeks}, minmax(0, 1fr))`,
}}
>
{days.map(
(day, index) =>
(index === 0 || day.date.endsWith("-01")) && (
<span
key={day.date}
style={{ gridColumn: Math.floor((index + offset) / 7) + 1 }}
>
{new Date(`${day.date}T12:00:00Z`).toLocaleDateString(
"en",
{ month: "short", timeZone: "UTC" },
)}
</span>
),
)}
</div>
<div className="ds-calendar-body">
<div className="ds-calendar-weekdays" aria-hidden="true">
<span>Mon</span>
<span>Wed</span>
<span>Fri</span>
</div>
<div
className="ds-calendar-grid"
style={{
gridTemplateColumns: `repeat(${weeks}, 1fr)`,
}}
>
{Array.from({ length: offset }, (_, index) => (
<span key={`padding-${index}`} aria-hidden="true" />
))}
{days.map((day, index) => (
<button
key={day.date}
ref={(element) => {
buttons.current[index] = element;
}}
type="button"
className={`ds-day ds-day--${day.state}`}
style={{
backgroundColor:
day.state === "not-due"
? "transparent"
: day.state === "future"
? emptyColor
: progressShade(day, color),
}}
aria-label={`${day.date}: ${describeDay(day, unit)}`}
title={`${day.date}: ${describeDay(day, unit)}`}
aria-pressed={day.date === selected.date}
aria-describedby={inspectorId}
tabIndex={day.date === selected.date ? 0 : -1}
onClick={() => setSelectedDate(day.date)}
onKeyDown={(event) => {
const offset = {
ArrowDown: 1,
ArrowUp: -1,
ArrowRight: 7,
ArrowLeft: -7,
}[event.key];
const next =
event.key === "Home"
? 0
: event.key === "End"
? days.length - 1
: offset !== undefined
? Math.max(
0,
Math.min(days.length - 1, index + offset),
)
: undefined;
if (next !== undefined) {
event.preventDefault();
setSelectedDate(days[next]!.date);
buttons.current[next]?.focus();
}
}}
/>
))}
</div>
</div>
</div>
</div>
{legend ?? <CalendarLegend days={days} color={color} label={label} />}
<div className="ds-calendar-caption">
<span>
{months} months{" "}
<span className="ds-muted">
/{" "}
{historyLabel ??
(compact ? "Demo history" : "Illustrative history")}
</span>
</span>
<span className="ds-muted">
{compact
? "Select a day · Arrow keys"
: "Select a day to inspect · Arrow keys to move"}
</span>
</div>
<div id={inspectorId} className="ds-date-inspector" aria-live="polite">
<span>
{new Date(`${selected.date}T12:00:00Z`).toLocaleDateString("en", {
month: "long",
day: "numeric",
year: "numeric",
timeZone: "UTC",
})}
</span>
<span>{describeDay(selected, unit)}</span>
</div>
</div>
);
}