Files

128 lines
3.0 KiB
TypeScript
Raw Permalink Normal View History

/**
* Small shared pieces for the compare sections: the section shell, the
* row-label + per-school-cell grid, and tone-mapped chips. Copy passed into
* these comes verbatim from the reviewed mockups
* (docs/superpowers/specs/mockups/) — do not paraphrase it here.
*/
'use client';
import type { CSSProperties, ReactNode } from 'react';
import type { School } from '@/lib/types';
import { CHART_COLORS, CHART_TEXT_COLORS, shortName } from '@/lib/utils';
import { InfoPopover } from '@/components/InfoPopover';
import styles from './compareSections.module.css';
export function Section({
title,
how,
children,
}: {
title: string;
how?: ReactNode;
children: ReactNode;
}) {
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>{title}</h2>
{how && <p className={styles.how}>{how}</p>}
{children}
</section>
);
}
export function SectionGrid({
schools,
children,
}: {
schools: School[];
children: ReactNode;
}) {
return (
<div
className={styles.grid}
style={{ '--school-count': schools.length } as CSSProperties}
>
{children}
</div>
);
}
export function RowLabel({ children, tip }: { children: ReactNode; tip?: string }) {
return (
<div className={styles.rowLabel}>
{children}
{tip && <InfoPopover plain={tip} />}
</div>
);
}
/**
* One measure = its row label plus a cell per school. `display: contents` on
* desktop (see CSS) makes these flow into the section grid as if this wrapper
* weren't here, keeping columns aligned across measures; on mobile the wrapper
* becomes a card so each measure reads as its own block.
*/
export function Measure({
label,
tip,
children,
}: {
label: ReactNode;
tip?: string;
children: ReactNode;
}) {
return (
<div className={styles.measure}>
<RowLabel tip={tip}>{label}</RowLabel>
{children}
</div>
);
}
export function Cell({
school,
index,
children,
}: {
school: School;
index: number;
children: ReactNode;
}) {
return (
<div
className={styles.cell}
style={
{
'--sc': CHART_TEXT_COLORS[index % CHART_TEXT_COLORS.length],
'--dot': CHART_COLORS[index % CHART_COLORS.length],
} as CSSProperties
}
>
{/* Mobile-only per-school tag (dot + short name); hidden on desktop,
where the column header identifies the school. */}
<span className={styles.cellTag}>
<span className={styles.cellDot} aria-hidden="true" />
{shortName(school.school_name)}
</span>
{children}
</div>
);
}
export type ChipTone = 'good' | 'warn' | 'bad' | 'neutral';
const CHIP_TONE_CLASS: Record<ChipTone, string> = {
good: styles.chipGood,
warn: styles.chipWarn,
bad: styles.chipBad,
neutral: styles.chipNeutral,
};
export function Chip({ tone, children }: { tone: ChipTone; children: ReactNode }) {
return <span className={`${styles.chip} ${CHIP_TONE_CLASS[tone]}`}>{children}</span>;
}
export const sectionStyles = styles;