- {orgPath(chapter) && (
+ {path && (
@@ -551,7 +639,18 @@ function ChapterCard({ chapter, color, open, onOpen }) {
);
}
-function ChapterDetail({ chapter, region, onClose }) {
+function ChapterDetail({
+ chapter,
+ region,
+ onClose,
+}: {
+ chapter: Chapter;
+ region: Region;
+ onClose: () => void;
+}) {
+ const path = orgPath(chapter);
+ const color = colorOf(region);
+
/* "Led by" comes from affiliations rather than a text field, so
it lists real people and stays empty until they exist. */
const leads = (chapter.leadership ?? [])
@@ -560,21 +659,23 @@ function ChapterDetail({ chapter, region, onClose }) {
)
.join(", ");
- const rows = [
- ["Region", region.name],
- ["Where", chapter.venue],
- ["Meets", chapter.meets],
- ["Led by", leads],
- ["Since", chapter.started],
- ].filter(([, v]) => v);
+ const rows = (
+ [
+ ["Region", region.name],
+ ["Where", chapter.venue],
+ ["Meets", chapter.meets],
+ ["Led by", leads],
+ ["Since", chapter.started],
+ ] satisfies Array<[label: string, value: string | null | undefined]>
+ ).filter(([, v]) => v);
return (
-
+
{chapter.name}
@@ -585,13 +686,13 @@ function ChapterDetail({ chapter, region, onClose }) {
onClick={onClose}
aria-label="Close details"
className="shrink-0 h-8 w-8 rounded-full flex items-center justify-center text-lg font-700 transition-transform duration-200 hover:scale-110"
- style={{ border: `1px solid ${region.color}`, color: region.color }}
+ style={{ border: `1px solid ${color}`, color: color }}
>
×
-
+
{rows.length > 0 && (
@@ -609,11 +710,11 @@ function ChapterDetail({ chapter, region, onClose }) {
)}
- {orgPath(chapter) && (
+ {path && (
Chapter page
@@ -624,7 +725,7 @@ function ChapterDetail({ chapter, region, onClose }) {
target="_blank"
rel="noopener noreferrer"
className="py-2 px-5 rounded-xl font-700 text-sm transition-transform duration-200 hover:scale-105"
- style={{ border: `1px solid ${region.color}`, color: region.color }}
+ style={{ border: `1px solid ${color}`, color: color }}
>
Visit site
@@ -634,7 +735,7 @@ function ChapterDetail({ chapter, region, onClose }) {
Get in touch
@@ -644,7 +745,19 @@ function ChapterDetail({ chapter, region, onClose }) {
);
}
-function ChapterGrid({ regions, chapters, chaptersIn, subtextFor, openId, setOpenId }) {
+type ChapterGridProps = Pick
& {
+ openId: string | null;
+ setOpenId: (id: string | null) => void;
+};
+
+function ChapterGrid({
+ regions,
+ chapters,
+ chaptersIn,
+ subtextFor,
+ openId,
+ setOpenId,
+}: ChapterGridProps) {
// Only regions that actually have chapters get a grid.
const populated = regions
.map(region => ({ region, list: chaptersIn(region.id) }))
@@ -656,14 +769,15 @@ function ChapterGrid({ regions, chapters, chaptersIn, subtextFor, openId, setOpe
{populated.map(({ region, list }) => {
const subtext = subtextFor(region);
+ const color = colorOf(region);
return (
-
+
{region.name}
@@ -696,7 +810,7 @@ function ChapterGrid({ regions, chapters, chaptersIn, subtextFor, openId, setOpe
setOpenId(openId === c.id ? null : c.id)}
/>
@@ -717,8 +831,12 @@ function ChapterGrid({ regions, chapters, chaptersIn, subtextFor, openId, setOpe
}
/* The control for the section heading's action bar. */
-export function OrgMapToggle({ view, setView, accent }) {
- const btn = active => ({
+export function OrgMapToggle({
+ view,
+ setView,
+ accent,
+}: Pick) {
+ const btn = (active: boolean) => ({
background: active ? accent : "transparent",
color: active ? "#ffffff" : accent,
});
@@ -748,7 +866,14 @@ export function OrgMapToggle({ view, setView, accent }) {
);
}
-export default function OrgListMap({ view = "map", accent = FALLBACK_COLOR }) {
+export default function OrgListMap({
+ view = "map",
+ accent = FALLBACK_COLOR,
+}: {
+ /** "map" or "grid". */
+ view?: string;
+ accent?: string;
+}) {
const {
loading,
error,
@@ -765,16 +890,16 @@ export default function OrgListMap({ view = "map", accent = FALLBACK_COLOR }) {
subtextFor,
} = useCommunity();
- const [selected, setSelected] = useState(null);
- const [hovered, setHovered] = useState(null);
- const [openId, setOpenId] = useState(null); // no card open on arrival
+ const [selected, setSelected] = useState(null);
+ const [hovered, setHovered] = useState(null);
+ const [openId, setOpenId] = useState(null); // no card open on arrival
// The list scrolls itself to whatever the map or legend points at.
- const listRef = useRef(null);
- const regionRefs = useRef({});
- const chapterRefs = useRef({});
+ const listRef = useRef(null);
+ const regionRefs = useRef>({});
+ const chapterRefs = useRef>({});
- const scrollListTo = el => {
+ const scrollListTo = (el: HTMLElement | null | undefined) => {
const box = listRef.current;
if (!box || !el) return;
// Only when the list is its own scroll area (lg and up). Below
@@ -792,7 +917,7 @@ export default function OrgListMap({ view = "map", accent = FALLBACK_COLOR }) {
// Clicking a tile jumps to its first chapter when it has one,
// otherwise to the region it belongs to.
- const pickArea = code => {
+ const pickArea = (code: string) => {
const chapter = chapters.find(c => c.area_code === code);
if (chapter && chapterRefs.current[chapter.id]) {
return scrollListTo(chapterRefs.current[chapter.id]);
@@ -801,7 +926,7 @@ export default function OrgListMap({ view = "map", accent = FALLBACK_COLOR }) {
if (region) scrollListTo(regionRefs.current[region.id]);
};
- const shell = children => (
+ const shell = (children: ReactNode) => (
{children}
@@ -837,7 +962,7 @@ export default function OrgListMap({ view = "map", accent = FALLBACK_COLOR }) {
const shared = { selected, setSelected, hovered, setHovered };
const onMapIds = new Set(regionAreas.map(a => a.region_id));
- const block = (region, indent) => (
+ const block = (region: Region, indent: boolean) => (
&
+ ButtonHTMLAttributes;
+
+function RowButton({ as: As = "button", color, children, ...rest }: RowButtonProps) {
return (
;
+ color: string;
+}) {
const [failed, setFailed] = useState(false);
if (org.logo && !failed) {
@@ -94,7 +115,9 @@ function OrgMark({ org, color }) {
);
}
-function OrgRow({ org, pageLabel, siteLabel }) {
+type RowLabels = { pageLabel: string; siteLabel: string };
+
+function OrgRow({ org, pageLabel, siteLabel }: RowLabels & { org: OrganizationListItem }) {
const [open, setOpen] = useState(false);
const panelId = useId();
@@ -245,7 +268,12 @@ function OrgRow({ org, pageLabel, siteLabel }) {
);
}
-function Block({ title, orgs, pageLabel, siteLabel }) {
+function Block({
+ title,
+ orgs,
+ pageLabel,
+ siteLabel,
+}: RowLabels & { title?: string; orgs: OrganizationListItem[] }) {
if (orgs.length === 0) return null;
return (
@@ -262,7 +290,30 @@ function Block({ title, orgs, pageLabel, siteLabel }) {
);
}
-const byName = (a, b) => a.name.localeCompare(b.name);
+const byName = (a: OrganizationListItem, b: OrganizationListItem) =>
+ a.name.localeCompare(b.name);
+
+/* One heading's worth of the list. Labels override the list's own. */
+type OrgGroup = {
+ key: string;
+ title: string;
+ pageLabel?: string;
+ siteLabel?: string;
+};
+
+type OrgListVerticalProps = {
+ kind?: OrgKind;
+ title?: string;
+ groups?: OrgGroup[];
+ /** Which group key an organization files under. */
+ groupBy?: (org: OrganizationListItem) => string | null | undefined;
+ accent?: string;
+ pageLabel?: string;
+ siteLabel?: string;
+ /** Null keeps the API's order. */
+ sort?: ((a: OrganizationListItem, b: OrganizationListItem) => number) | null;
+ empty?: string;
+};
export default function OrgListVertical({
kind,
@@ -274,10 +325,10 @@ export default function OrgListVertical({
siteLabel = "Visit site",
sort = byName,
empty = "· Nothing to show here just yet ·",
-}) {
+}: OrgListVerticalProps) {
const { organizations, loading, error } = useOrganizations(kind);
- const shell = children => (
+ const shell = (children: ReactNode) => (
{children}
);