admin: tables fit a phone, their first cell carrying the hidden columns, and no positioned label widens the page
This commit is contained in:
@@ -20,6 +20,7 @@ import { ClientName, type ClientNames } from "@/features/clients/clientNames";
|
||||
import { rcodeShortName } from "@/features/provenance/provenanceCopy";
|
||||
import { qtypeName } from "@/features/provenance/qtype";
|
||||
import type { QuerySummary } from "@/features/provenance/querySummary";
|
||||
import FactLine from "@/ui/FactLine";
|
||||
import { styles as shared } from "@/ui/styles";
|
||||
import { colors, metrics } from "@/ui/tokens.stylex";
|
||||
|
||||
@@ -28,6 +29,15 @@ const DARK = "@media (prefers-color-scheme: dark)";
|
||||
/** Wide enough for an ordinary hostname, narrow enough to leave the six other columns room. */
|
||||
const DOMAIN_MAX_WIDTH = "24rem";
|
||||
|
||||
/**
|
||||
* Narrower than the seven columns need with ordinary data, so the table keeps
|
||||
* two: Domain, which takes the other columns' facts as lines under the name,
|
||||
* and Result. Hiding cells keeps a native table; re-laying its rows out as
|
||||
* blocks would put the table semantics at the mercy of each browser engine.
|
||||
* The wrapper that scrolls is the container.
|
||||
*/
|
||||
const COMPACT = "@container (max-width: 50rem)";
|
||||
|
||||
const styles = stylex.create({
|
||||
head: {
|
||||
backgroundColor: { default: "oklch(98.5% 0 none)", [DARK]: "oklch(21% 0.006 285.885)" },
|
||||
@@ -52,6 +62,16 @@ const styles = stylex.create({
|
||||
domainCell: {
|
||||
maxWidth: DOMAIN_MAX_WIDTH,
|
||||
},
|
||||
wide: {
|
||||
display: { default: null, [COMPACT]: "none" },
|
||||
},
|
||||
/** A name can be one unbroken label as wide as the table; `anywhere` also lowers the cell's minimum width. */
|
||||
compact: {
|
||||
display: { default: "none", [COMPACT]: "block" },
|
||||
marginTop: "0.125rem",
|
||||
color: colors.textMuted,
|
||||
overflowWrap: "anywhere",
|
||||
},
|
||||
small: {
|
||||
fontSize: "0.75rem",
|
||||
lineHeight: "1rem",
|
||||
@@ -93,6 +113,7 @@ const styles = stylex.create({
|
||||
*/
|
||||
badge: {
|
||||
display: "inline-block",
|
||||
whiteSpace: "nowrap",
|
||||
borderRadius: metrics.radius,
|
||||
paddingInline: "0.375rem",
|
||||
paddingBlock: "0.125rem",
|
||||
@@ -152,6 +173,29 @@ export function ResultCellContent({ row }: { row: Pick<QuerySummary, "blocked" |
|
||||
return <span {...stylex.props(styles.small, styles.muted)}>{label}</span>;
|
||||
}
|
||||
|
||||
/** The facts the hidden columns hold, in their column order, for the compact Domain cell. */
|
||||
function CompactFacts({ row, clientNames }: { row: QuerySummary; clientNames: ClientNames }) {
|
||||
// The label stands in for the hidden Type header: a screen reader says a bare "A" as the article.
|
||||
const qtype = (
|
||||
<>
|
||||
<span {...stylex.props(shared.srOnly)}>Type </span>
|
||||
{qtypeName(row.qtype)}
|
||||
</>
|
||||
);
|
||||
const facts: ReactNode[] = [formatTime(row.ts), qtype, routeLabel(row.route_kind)];
|
||||
if (row.response_time_us !== null) facts.push(formatMicros(row.response_time_us));
|
||||
return (
|
||||
<>
|
||||
<span {...stylex.props(styles.compact)}>
|
||||
<ClientName ip={row.client_ip} names={clientNames} />
|
||||
</span>
|
||||
<span {...stylex.props(styles.compact, shared.tabularNums)}>
|
||||
<FactLine facts={facts} />
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ActivityCells({
|
||||
row,
|
||||
clientNames,
|
||||
@@ -163,22 +207,23 @@ export function ActivityCells({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap, styles.muted)}>{formatTime(row.ts)}</td>
|
||||
<td
|
||||
title={row.domain}
|
||||
{...stylex.props(styles.cell, styles.small, styles.breakAll, styles.domainCell, shared.mono)}
|
||||
>
|
||||
{renderDomain(row.id, <span {...stylex.props(styles.domainClamp)}>{row.domain}</span>)}
|
||||
<td {...stylex.props(styles.cell, styles.nowrap, styles.muted, styles.wide)}>{formatTime(row.ts)}</td>
|
||||
<td title={row.domain} {...stylex.props(styles.cell, styles.small, styles.domainCell)}>
|
||||
{renderDomain(
|
||||
row.id,
|
||||
<span {...stylex.props(styles.domainClamp, styles.breakAll, shared.mono)}>{row.domain}</span>,
|
||||
)}
|
||||
<CompactFacts row={row} clientNames={clientNames} />
|
||||
</td>
|
||||
<td {...stylex.props(styles.cell, styles.small, styles.nowrap)}>
|
||||
<td {...stylex.props(styles.cell, styles.small, styles.nowrap, styles.wide)}>
|
||||
<ClientName ip={row.client_ip} names={clientNames} />
|
||||
</td>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap)}>{qtypeName(row.qtype)}</td>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap, styles.wide)}>{qtypeName(row.qtype)}</td>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap)}>
|
||||
<ResultCellContent row={row} />
|
||||
</td>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap)}>{routeLabel(row.route_kind)}</td>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap, shared.tabularNums)}>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap, styles.wide)}>{routeLabel(row.route_kind)}</td>
|
||||
<td {...stylex.props(styles.cell, styles.nowrap, shared.tabularNums, styles.wide)}>
|
||||
{row.response_time_us === null ? "—" : formatMicros(row.response_time_us)}
|
||||
</td>
|
||||
</>
|
||||
@@ -187,12 +232,14 @@ export function ActivityCells({
|
||||
|
||||
export const ACTIVITY_COLUMNS = ["Time", "Domain", "Client", "Type", "Result", "Route", "Duration"] as const;
|
||||
|
||||
const COMPACT_COLUMNS: ReadonlySet<string> = new Set(["Domain", "Result"]);
|
||||
|
||||
export function ActivityTableHead() {
|
||||
return (
|
||||
<thead {...stylex.props(styles.head)}>
|
||||
<tr>
|
||||
{ACTIVITY_COLUMNS.map((column) => (
|
||||
<th key={column} {...stylex.props(styles.th)}>
|
||||
<th key={column} {...stylex.props(styles.th, !COMPACT_COLUMNS.has(column) && styles.wide)}>
|
||||
{column}
|
||||
</th>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user