Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/app/matricole/page.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -14,15 +15,15 @@ export const dynamic = "force-dynamic"
export default async function MatricolePage() {
const content = await getFreshmanContent()
return (
<main className="w-full">
<main className={PAGE_MAIN}>
<MatricoleIntro resources={content?.resources ?? []} />
{content ? (
<>
<MatricoleGuides content={content} />
<FAQsPage faqs={content.faqs} />
</>
) : (
<p className="typo-body-large px-6 pb-32 text-center text-text-primary">
<p className={`${CONTAINER} typo-body-large text-center text-text-primary`}>
Le risorse per le matricole non sono disponibili al momento. Riprova più tardi.
</p>
)}
Expand Down
8 changes: 5 additions & 3 deletions src/components/card-group/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import type { CardGroupProps } from "./types"

export function CardGroup({ icon, title, description, children, horizontal = false, className }: CardGroupProps) {
return (
<Glass className={cn("w-full rounded-rectangles border-white/50 bg-background-blur p-12", className)}>
<Glass
className={cn("w-full rounded-rectangles border-white/50 bg-background-blur p-6 sm:p-10 lg:p-12", className)}
>
{horizontal ? (
<div className="flex flex-col gap-12 lg:flex-row lg:items-center">
<div className="flex flex-col gap-8 sm:gap-12 lg:flex-row lg:items-center">
<CardGroupHeader icon={icon} title={title} description={description} horizontal />
<div className="flex-1">{children}</div>
</div>
) : (
<>
<CardGroupHeader icon={icon} title={title} description={description} />
<div className="mt-12">{children}</div>
<div className="mt-8 sm:mt-12">{children}</div>
</>
)}
</Glass>
Expand Down
2 changes: 1 addition & 1 deletion src/components/card-icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/components/card-icon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 0 additions & 1 deletion src/components/home/materials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
Expand Down
10 changes: 10 additions & 0 deletions src/components/matricole/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<CardIconProps>
10 changes: 5 additions & 5 deletions src/components/matricole/faqs.tsx
Original file line number Diff line number Diff line change
@@ -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"] }) {
Expand All @@ -8,12 +10,10 @@ export function FAQsPage({ faqs }: { faqs: FreshmanContent["faqs"] }) {
content: faq.descriptionIt,
}))
return (
<section className="mx-auto flex w-full max-w-400 flex-col items-center justify-center gap-16 px-9 py-32 sm:gap-20 sm:py-38">
<h2 className="typo-headline-medium sm:typo-display-medium text-center text-text-primary">
Domande Frequenti tra le Matricole
</h2>
<section className={cn(CONTAINER, "flex flex-col items-center gap-10 sm:gap-14")}>
<h2 className={cn(SECTION_TITLE, "text-center")}>Domande Frequenti tra le Matricole</h2>

<div className="mx-auto flex w-full max-w-255 flex-col">
<div className="flex w-full max-w-255 flex-col">
<AccordionList items={accordionItems} />
</div>
</section>
Expand Down
53 changes: 27 additions & 26 deletions src/components/matricole/guides.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,64 @@
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 }) {
const guides = content.resources.filter((card) => card.group === "guides")
const materials = content.resources.filter((card) => card.group === "materials")
const tools = content.resources.filter((card) => card.group === "tools")
return (
<section className="flex w-full flex-col items-center px-6 sm:px-10">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 text-start sm:gap-13">
<h3 className="typo-title-large sm:typo-display-medium">Guide e Utility</h3>
<section className={cn(CONTAINER, "flex flex-col gap-8 sm:gap-12")}>
<h2 className={SECTION_TITLE}>Guide e Utility</h2>

<div className="grid w-full gap-6 sm:grid-cols-2">
{/* Hidden in mobile */}
<div className="flex flex-col gap-16 sm:gap-6">
{/* 5/12 for the large card: at 1:1 the 2×2 grid next to it squeezed its titles onto two lines */}
<div className="grid w-full gap-6 xl:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]">
{/* Hidden on phones, as in the mobile design */}
{guides.slice(0, 1).map((guide) => (
<CardIcon
key={guide.id}
{...freshmanCardProps(guide)}
align="start"
className="hidden h-full sm:block"
size="compact"
{...FEATURED_CARD}
className={cn(FEATURED_CARD.className, "hidden sm:block")}
/>
))}
<div className="grid w-full gap-3 sm:gap-6 lg:grid-cols-2">
<div className="grid w-full gap-3 sm:grid-cols-2 sm:gap-6">
{guides.slice(1).map((guide) => (
<CardIcon key={guide.id} {...freshmanCardProps(guide)} align="inline" className="h-full" size="inline" />
<CardIcon
key={guide.id}
{...freshmanCardProps(guide)}
align="inline"
className="h-full"
size="inline-sm"
/>
))}
</div>
</div>

<CardGroup title={hub.title} description={hub.description} className="mt-18 w-full sm:mt-0">
<div className="grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
<CardGroup title={hub.title} description={hub.description}>
<div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-6 xl:grid-cols-4">
{content.telegram.map((card) => (
<CardIcon key={card.id} {...freshmanCardProps(card)} align="inline" size="inline" />
))}
</div>
</CardGroup>

<div className="grid w-full gap-6 sm:grid-cols-2">
{/* hidden in mobile */}
<div className="grid w-full gap-6 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)]">
{/* Hidden on phones, as in the mobile design */}
{materials.map((material) => (
<CardIcon
key={material.id}
{...freshmanCardProps(material)}
align="start"
className="hidden h-full sm:block"
size="compact"
{...FEATURED_CARD}
className={cn(FEATURED_CARD.className, "hidden sm:block")}
/>
))}
<CardGroup
title={techTools.title}
description={techTools.description}
icon={techTools.icon}
className="mt-18 w-full sm:mt-0"
horizontal
>
<div className="flex flex-col gap-6">
<CardGroup title={techTools.title} description={techTools.description} icon={techTools.icon} horizontal>
<div className="flex flex-col gap-3 sm:gap-6">
{tools.map((card) => (
<CardIcon key={card.id} {...freshmanCardProps(card)} align="inline" size="inline" />
))}
Expand Down
41 changes: 17 additions & 24 deletions src/components/matricole/intro.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<section className="flex w-full flex-col items-center px-6 pt-40 pb-32 sm:px-10 sm:pb-13">
<div className="mx-auto flex w-full max-w-7xl flex-col items-center">
<div className="flex w-full max-w-200 flex-col items-center gap-6 text-center">
<h1 className="typo-display-large md:typo-display-extralarge bg-linear-to-b from-text-primary to-text-secondary bg-clip-text py-8 text-transparent">
Matricole
</h1>
<p className="typo-title-large md:typo-headline-small w-full text-text-primary">
Ecco una raccolta curata di risorse utili, guide e strumenti per supportare il tuo percorso.
</p>
</div>
// The featured resources close the hero, so it takes only the hero's top padding
<section className={cn(CONTAINER, PAGE_HERO_TOP, "flex flex-col gap-16 xl:gap-20")}>
<Hero
title="Matricole"
description="Ecco una raccolta curata di risorse utili, guide e strumenti per supportare il tuo percorso."
/>

<div className="mt-30 hidden w-full gap-6 sm:grid sm:grid-cols-2">
{resources
.filter((resource) => resource.group === "featured")
.map((resource) => (
<CardIcon
key={resource.id}
{...freshmanCardProps(resource)}
align="start"
className="h-full min-h-50"
size="compact"
/>
))}
</div>
{/* Hidden on phones, as in the mobile design */}
<div className="hidden w-full gap-6 sm:grid sm:grid-cols-2">
{resources
.filter((resource) => resource.group === "featured")
.map((resource) => (
<CardIcon key={resource.id} {...freshmanCardProps(resource)} {...FEATURED_CARD} />
))}
</div>
</section>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/wizard/school-step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" }}
/>
))}
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
18 changes: 18 additions & 0 deletions tests/card-icon-classes.test.mjs
Original file line number Diff line number Diff line change
@@ -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(", ")}`)
})
Loading