milestone 23 s3: convert features/dashboard/DashboardPage.tsx to stylex
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||||
|
import * as stylex from "@stylexjs/stylex";
|
||||||
import { healthQuery, statsQuery, timeseriesQuery, upstreamHealthQuery } from "@/lib/queries";
|
import { healthQuery, statsQuery, timeseriesQuery, upstreamHealthQuery } from "@/lib/queries";
|
||||||
import type { Period } from "@/lib/types";
|
import type { Period } from "@/lib/types";
|
||||||
import InlineError from "@/lib/InlineError";
|
import InlineError from "@/lib/InlineError";
|
||||||
import { focusRing } from "@/ui/classes";
|
import { styles as shared } from "@/ui/styles";
|
||||||
|
import { colors } from "@/ui/tokens.stylex";
|
||||||
import DiskCard from "./DiskCard";
|
import DiskCard from "./DiskCard";
|
||||||
import HealthBanners from "./HealthBanners";
|
import HealthBanners from "./HealthBanners";
|
||||||
import StatCards from "./StatCards";
|
import StatCards from "./StatCards";
|
||||||
@@ -12,20 +14,98 @@ import UpstreamHealthTable from "./UpstreamHealthTable";
|
|||||||
|
|
||||||
const PERIODS: Period[] = ["1h", "24h", "7d", "30d"];
|
const PERIODS: Period[] = ["1h", "24h", "7d", "30d"];
|
||||||
|
|
||||||
|
const styles = stylex.create({
|
||||||
|
page: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "1rem",
|
||||||
|
},
|
||||||
|
titleRow: {
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: "0.75rem",
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
lineHeight: "2rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
periodGroup: {
|
||||||
|
display: "flex",
|
||||||
|
gap: "0.25rem",
|
||||||
|
},
|
||||||
|
period: {
|
||||||
|
borderStyle: "none",
|
||||||
|
borderRadius: "0.25rem",
|
||||||
|
paddingInline: "0.625rem",
|
||||||
|
paddingBlock: "0.25rem",
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
lineHeight: "1.25rem",
|
||||||
|
},
|
||||||
|
/** The pressed fill is heavier than `surfaceHover`, so a hover cannot mimic it. */
|
||||||
|
periodSelected: {
|
||||||
|
backgroundColor: {
|
||||||
|
default: "oklch(92% 0.004 286.32)",
|
||||||
|
"@media (prefers-color-scheme: dark)": "oklch(37% 0.013 285.805)",
|
||||||
|
},
|
||||||
|
color: colors.text,
|
||||||
|
fontWeight: 500,
|
||||||
|
},
|
||||||
|
periodIdle: {
|
||||||
|
backgroundColor: { default: "transparent", ":hover": colors.surfaceHover },
|
||||||
|
color: colors.textMuted,
|
||||||
|
},
|
||||||
|
/** Dynamic: the caller sizes the placeholder to the widget it stands in for. */
|
||||||
|
skeletonHeight: (height: number) => ({ height }),
|
||||||
|
skeleton: {
|
||||||
|
borderRadius: "0.25rem",
|
||||||
|
backgroundColor: {
|
||||||
|
default: "oklch(92% 0.004 286.32)",
|
||||||
|
"@media (prefers-color-scheme: dark)": "oklch(27.4% 0.006 286.033)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/** The chart takes two thirds beside the disk card from `lg`, one column below. */
|
||||||
|
panelGrid: {
|
||||||
|
display: "grid",
|
||||||
|
gap: "1rem",
|
||||||
|
gridTemplateColumns: {
|
||||||
|
default: "repeat(1, minmax(0, 1fr))",
|
||||||
|
"@media (min-width: 1024px)": "2fr 1fr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
panel: {
|
||||||
|
borderRadius: "0.25rem",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: colors.border,
|
||||||
|
backgroundColor: colors.surfaceRaised,
|
||||||
|
paddingInline: "1rem",
|
||||||
|
paddingBlock: "0.75rem",
|
||||||
|
},
|
||||||
|
panelHeading: {
|
||||||
|
marginBottom: "0.75rem",
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
lineHeight: "1.25rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function PeriodPicker({ period, onChange }: { period: Period; onChange: (period: Period) => void }) {
|
function PeriodPicker({ period, onChange }: { period: Period; onChange: (period: Period) => void }) {
|
||||||
return (
|
return (
|
||||||
<div role="group" aria-label="Period" className="flex gap-1">
|
<div role="group" aria-label="Period" {...stylex.props(styles.periodGroup)}>
|
||||||
{PERIODS.map((option) => (
|
{PERIODS.map((option) => (
|
||||||
<button
|
<button
|
||||||
key={option}
|
key={option}
|
||||||
type="button"
|
type="button"
|
||||||
aria-pressed={option === period}
|
aria-pressed={option === period}
|
||||||
onClick={() => onChange(option)}
|
onClick={() => onChange(option)}
|
||||||
className={`rounded px-2.5 py-1 text-sm ${focusRing} ${
|
{...stylex.props(
|
||||||
option === period
|
styles.period,
|
||||||
? "bg-zinc-200 font-medium text-zinc-900 dark:bg-zinc-700 dark:text-zinc-50"
|
option === period ? styles.periodSelected : styles.periodIdle,
|
||||||
: "text-zinc-600 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800"
|
shared.focusRing,
|
||||||
}`}
|
)}
|
||||||
>
|
>
|
||||||
{option}
|
{option}
|
||||||
</button>
|
</button>
|
||||||
@@ -35,7 +115,7 @@ function PeriodPicker({ period, onChange }: { period: Period; onChange: (period:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Skeleton({ height }: { height: number }) {
|
function Skeleton({ height }: { height: number }) {
|
||||||
return <div aria-hidden="true" className="animate-pulse rounded bg-zinc-200 dark:bg-zinc-800" style={{ height }} />;
|
return <div aria-hidden="true" {...stylex.props(styles.skeleton, styles.skeletonHeight(height), shared.pulse)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
@@ -46,9 +126,9 @@ export default function DashboardPage() {
|
|||||||
const upstreamHealth = useQuery(upstreamHealthQuery());
|
const upstreamHealth = useQuery(upstreamHealthQuery());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="space-y-4">
|
<section {...stylex.props(styles.page)}>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div {...stylex.props(styles.titleRow)}>
|
||||||
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
<h1 {...stylex.props(styles.heading)}>Dashboard</h1>
|
||||||
<PeriodPicker period={period} onChange={setPeriod} />
|
<PeriodPicker period={period} onChange={setPeriod} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -62,9 +142,9 @@ export default function DashboardPage() {
|
|||||||
<StatCards stats={stats.data} />
|
<StatCards stats={stats.data} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid gap-4 lg:grid-cols-[2fr_1fr]">
|
<div {...stylex.props(styles.panelGrid)}>
|
||||||
<section className="rounded border border-zinc-200 bg-white px-4 py-3 dark:border-zinc-800 dark:bg-zinc-900">
|
<section {...stylex.props(styles.panel)}>
|
||||||
<h2 className="mb-3 text-sm font-semibold">Queries over time</h2>
|
<h2 {...stylex.props(styles.panelHeading)}>Queries over time</h2>
|
||||||
{timeseries.isError ? (
|
{timeseries.isError ? (
|
||||||
<InlineError error={timeseries.error} onRetry={() => void timeseries.refetch()} />
|
<InlineError error={timeseries.error} onRetry={() => void timeseries.refetch()} />
|
||||||
) : timeseries.data === undefined ? (
|
) : timeseries.data === undefined ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user