diff --git a/src/app/matricole/page.tsx b/src/app/matricole/page.tsx index 1ac27122..59248e53 100644 --- a/src/app/matricole/page.tsx +++ b/src/app/matricole/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next" +import { CONTAINER, PAGE_MAIN } from "@/components/layout/page" import { FAQsPage } from "@/components/matricole/faqs" import { MatricoleGuides } from "@/components/matricole/guides" import { MatricoleIntro } from "@/components/matricole/intro" @@ -14,7 +15,7 @@ export const dynamic = "force-dynamic" export default async function MatricolePage() { const content = await getFreshmanContent() return ( -
+
{content ? ( <> @@ -22,7 +23,7 @@ export default async function MatricolePage() { ) : ( -

+

Le risorse per le matricole non sono disponibili al momento. Riprova più tardi.

)} diff --git a/src/components/card-group/index.tsx b/src/components/card-group/index.tsx index 84c994ae..8abe2f1c 100644 --- a/src/components/card-group/index.tsx +++ b/src/components/card-group/index.tsx @@ -5,16 +5,18 @@ import type { CardGroupProps } from "./types" export function CardGroup({ icon, title, description, children, horizontal = false, className }: CardGroupProps) { return ( - + {horizontal ? ( -
+
{children}
) : ( <> -
{children}
+
{children}
)} diff --git a/src/components/card-icon/index.tsx b/src/components/card-icon/index.tsx index 79105d70..0a7fc50d 100644 --- a/src/components/card-icon/index.tsx +++ b/src/components/card-icon/index.tsx @@ -9,7 +9,7 @@ import type { CardIconProps } from "./types" import { getAlignmentClasses, getCardPaddingClasses, getContentGapClasses, getTitleSizeClasses } from "./utils" export function CardIcon(props: CardIconProps) { - const { title, icon, size = "md", href, hoverEffect = false, align = "center", className } = props + const { title, icon, size = "md", href, hoverEffect = Boolean(props.href), align = "center", className } = props const description = "description" in props ? props.description : undefined const cta = "cta" in props ? props.cta : undefined const Root = href ? "a" : "div" diff --git a/src/components/card-icon/types.ts b/src/components/card-icon/types.ts index d40cbef8..cdff14ae 100644 --- a/src/components/card-icon/types.ts +++ b/src/components/card-icon/types.ts @@ -22,6 +22,7 @@ export type SharedCardProps = { size?: ResponsiveCardSize align?: CardAlign href?: string + /** Defaults to whether the card has an `href`: only cards that lead somewhere react to hover. */ hoverEffect?: boolean className?: string } diff --git a/src/components/home/materials.tsx b/src/components/home/materials.tsx index fff6659b..9c732929 100644 --- a/src/components/home/materials.tsx +++ b/src/components/home/materials.tsx @@ -79,7 +79,6 @@ export function Materials() { description={card.description} icon={card.icon} href={card.href} - hoverEffect={Boolean(card.href)} size="md" className="hidden h-full xl:block" /> diff --git a/src/components/matricole/cards.ts b/src/components/matricole/cards.ts index 20889aea..dbd1e6b7 100644 --- a/src/components/matricole/cards.ts +++ b/src/components/matricole/cards.ts @@ -43,3 +43,13 @@ export function freshmanCardProps(card: FreshmanCard): CardIconProps { href: card.href || undefined, } } + +/** + * The large resource cards (featured, "Guida della matricola", "Materiali"). Their one-sentence descriptions were + * capped at 240px inside cards three times as wide; 448px keeps them to one or two lines. + */ +export const FEATURED_CARD = { + align: "start", + size: "compact", + className: "h-full min-h-50 [&_p]:max-w-md", +} as const satisfies Partial diff --git a/src/components/matricole/faqs.tsx b/src/components/matricole/faqs.tsx index 3b6b4329..7a7d27fd 100644 --- a/src/components/matricole/faqs.tsx +++ b/src/components/matricole/faqs.tsx @@ -1,4 +1,6 @@ import AccordionList from "@/components/accordion-list" +import { CONTAINER, SECTION_TITLE } from "@/components/layout/page" +import { cn } from "@/lib/utils" import type { FreshmanContent } from "@/queries/types" export function FAQsPage({ faqs }: { faqs: FreshmanContent["faqs"] }) { @@ -8,12 +10,10 @@ export function FAQsPage({ faqs }: { faqs: FreshmanContent["faqs"] }) { content: faq.descriptionIt, })) return ( -
-

- Domande Frequenti tra le Matricole -

+
+

Domande Frequenti tra le Matricole

-
+
diff --git a/src/components/matricole/guides.tsx b/src/components/matricole/guides.tsx index 73ad7508..77b047dd 100644 --- a/src/components/matricole/guides.tsx +++ b/src/components/matricole/guides.tsx @@ -1,7 +1,9 @@ import { CardIcon } from "@/components/card-icon" +import { CONTAINER, SECTION_TITLE } from "@/components/layout/page" +import { cn } from "@/lib/utils" import type { FreshmanContent } from "@/queries/types" import { CardGroup } from "../card-group" -import { freshmanCardProps } from "./cards" +import { FEATURED_CARD, freshmanCardProps } from "./cards" import { hub, techTools } from "./constants" export function MatricoleGuides({ content }: { content: FreshmanContent }) { @@ -9,55 +11,54 @@ export function MatricoleGuides({ content }: { content: FreshmanContent }) { const materials = content.resources.filter((card) => card.group === "materials") const tools = content.resources.filter((card) => card.group === "tools") return ( -
-
-

Guide e Utility

+
+

Guide e Utility

-
- {/* Hidden in mobile */} +
+ {/* 5/12 for the large card: at 1:1 the 2×2 grid next to it squeezed its titles onto two lines */} +
+ {/* Hidden on phones, as in the mobile design */} {guides.slice(0, 1).map((guide) => ( ))} -
+
{guides.slice(1).map((guide) => ( - + ))}
- -
+ +
{content.telegram.map((card) => ( ))}
-
- {/* hidden in mobile */} +
+ {/* Hidden on phones, as in the mobile design */} {materials.map((material) => ( ))} - -
+ +
{tools.map((card) => ( ))} diff --git a/src/components/matricole/intro.tsx b/src/components/matricole/intro.tsx index 6152cdbb..f1d4ccb7 100644 --- a/src/components/matricole/intro.tsx +++ b/src/components/matricole/intro.tsx @@ -1,33 +1,26 @@ import { CardIcon } from "@/components/card-icon" +import { CONTAINER, PAGE_HERO_TOP } from "@/components/layout/page" +import { Hero } from "@/components/ui/hero" +import { cn } from "@/lib/utils" import type { FreshmanContent } from "@/queries/types" -import { freshmanCardProps } from "./cards" +import { FEATURED_CARD, freshmanCardProps } from "./cards" export function MatricoleIntro({ resources }: { resources: FreshmanContent["resources"] }) { return ( -
-
-
-

- Matricole -

-

- Ecco una raccolta curata di risorse utili, guide e strumenti per supportare il tuo percorso. -

-
+ // The featured resources close the hero, so it takes only the hero's top padding +
+ -
- {resources - .filter((resource) => resource.group === "featured") - .map((resource) => ( - - ))} -
+ {/* Hidden on phones, as in the mobile design */} +
+ {resources + .filter((resource) => resource.group === "featured") + .map((resource) => ( + + ))}
) diff --git a/src/components/wizard/school-step.tsx b/src/components/wizard/school-step.tsx index 4c1fa760..1dd8eb18 100644 --- a/src/components/wizard/school-step.tsx +++ b/src/components/wizard/school-step.tsx @@ -27,7 +27,7 @@ export function SchoolStep({ title={school.name} icon={SCHOOL_ICONS[school.slug] ?? DEFAULT_SCHOOL_ICON} href={stepHref({ school: school.slug })} - hoverEffect + size={{ base: "sm", md: "md" }} /> ))}
diff --git a/src/styles/globals.css b/src/styles/globals.css index 98dafe80..f73816c4 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -8,6 +8,13 @@ @custom-variant dark (&:is(.dark *)); +/* + CardIcon's responsive `size` ({ base, sm, md, lg }) prefixes the classes in card-icon/classes.ts at runtime, so + Tailwind never sees "sm:h-14" and friends in the source. Generate every prefixed variant here. + tests/card-icon-classes.test.mjs fails if classes.ts gains a class that is missing from this list. +*/ +@source inline("{sm:,md:,lg:}{gap-2,gap-4,gap-6,h-6,h-10,h-12,h-14,h-16,h-32,h-44,w-6,w-10,w-12,w-14,w-16,w-32,w-44,p-3,p-6,p-8,px-8,py-4,typo-headline-extrasmall,typo-headline-medium,typo-label-large,typo-title-large}"); + @theme { --breakpoint-1xl: 85rem; /* 1360px */ --breakpoint-3xl: 90rem; /* 1440px */ diff --git a/tests/card-icon-classes.test.mjs b/tests/card-icon-classes.test.mjs new file mode 100644 index 00000000..3dfb96bf --- /dev/null +++ b/tests/card-icon-classes.test.mjs @@ -0,0 +1,18 @@ +import assert from "node:assert/strict" +import { readFileSync } from "node:fs" +import test from "node:test" + +const read = (path) => readFileSync(new URL(path, import.meta.url), "utf8") + +test("every CardIcon size class has its responsive variants generated", () => { + const classes = read("../src/components/card-icon/classes.ts") + const used = new Set([...classes.matchAll(/:\s*"([^"]+)"/g)].flatMap((match) => match[1].split(/\s+/))) + + const css = read("../src/styles/globals.css") + const inline = css.match(/@source inline\("\{sm:,md:,lg:\}\{([^}]+)\}"\)/) + assert.ok(inline, "the CardIcon @source inline() list is missing from globals.css") + const generated = new Set(inline[1].split(",")) + + const missing = [...used].filter((name) => !generated.has(name)) + assert.deepEqual(missing, [], `add these classes to the @source inline() list in globals.css: ${missing.join(", ")}`) +})