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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ breaking changes with `!` or a `BREAKING CHANGE:` footer.

<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know
## This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

Expand Down
16 changes: 12 additions & 4 deletions src/components/card-caption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function CardCaption({
iconPosition = "right",
image,
href,
onClick,
footer,
className,
titleClassName,
Expand All @@ -24,19 +25,26 @@ export function CardCaption({
/** An image shown in place of the icon, e.g. a project's logo. */
image?: string
href?: string
onClick?: () => void
/** Pinned to the bottom of the card, e.g. where a link leads to. */
footer?: ReactNode
className?: string
titleClassName?: string
captionClassName?: string
}) {
return (
// Only cards that lead somewhere react to hover
<Card
hoverBackground={Boolean(href)}
className={cn(href && "has-[>a:focus-visible]:ring-2 has-[>a:focus-visible]:ring-blue-secondary", className)}
hoverBackground={Boolean(href || onClick)}
className={cn(
(href || onClick) && "has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-blue-secondary",
className
)}
>
{href && <Link href={href} className="absolute inset-0 z-10 outline-none" aria-label={title} />}
{href ? (
<Link href={href} className="absolute inset-0 z-10 outline-none" aria-label={title} />
) : onClick ? (
<button type="button" onClick={onClick} className="absolute inset-0 z-10 outline-none" aria-label={title} />
) : null}
<CardHeader
className={`typo-headline-medium relative flex ${iconPosition === "right" ? "justify-between" : "flex-col-reverse"}`}
>
Expand Down
20 changes: 16 additions & 4 deletions src/components/card-icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ import type { CardIconProps } from "./types"
import { getAlignmentClasses, getCardPaddingClasses, getContentGapClasses, getTitleSizeClasses } from "./utils"

export function CardIcon(props: CardIconProps) {
const { title, icon, size = "md", href, hoverEffect = Boolean(props.href), align = "center", className } = props
const {
title,
icon,
size = "md",
href,
onClick,
hoverEffect = Boolean(href || onClick),
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"
const Root = href ? "a" : onClick ? "button" : "div"
const isDescriptionCard = Boolean(description)
const isInlineAligned = align === "inline"
const isCompactDescriptionCard = isDescriptionCard && size === "compact"
Expand All @@ -22,14 +31,17 @@ export function CardIcon(props: CardIconProps) {
<Glass
className={cn(
"w-full overflow-hidden rounded-rectangles border-white/50 bg-background-blur p-0 text-card-foreground",
href && "has-[>a:focus-visible]:ring-2 has-[>a:focus-visible]:ring-blue-secondary",
(href || onClick) && "has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-blue-secondary",
className
)}
>
<Root
href={href}
type={onClick && !href ? "button" : undefined}
onClick={onClick}
aria-label={onClick && !href ? title : undefined}
className={cn(
"group/card relative flex h-full flex-col text-left outline-none",
"group/card relative flex h-full w-full flex-col text-left outline-none",
getCardPaddingClasses(size, isDescriptionCard)
)}
>
Expand Down
4 changes: 3 additions & 1 deletion src/components/card-icon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ 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. */
/** An action card uses a native button instead of a link. */
onClick?: () => void
/** Defaults to whether the card has a link or an action. */
hoverEffect?: boolean
className?: string
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/home/about-us.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function AboutUs() {

<Button variant="primary" size="lg" asChild>
<Link href="/about">
About us
Scopri di più
<FiArrowUpRight />
</Link>
</Button>
Expand Down
18 changes: 15 additions & 3 deletions src/components/home/materials.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"use client"

import Link from "next/link"
import { Fragment } from "react"
import { FiArrowUpRight, FiBook, FiBookOpen, FiClipboard, FiFileText, FiUploadCloud } from "react-icons/fi"
import { CardIcon } from "@/components/card-icon"
import { MATERIALS_URL } from "@/components/header/constants"
import { CONTAINER } from "@/components/layout/page"
import { useActivationToast } from "@/components/ui/activation-toast"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { CardCaption } from "../card-caption"

// "Carica" has no destination yet, so it stays a plain card
// Upload and per-type archive pages will be activated later.
const featuredCards = [
{
title: "Carica",
Expand All @@ -25,14 +28,14 @@ const featuredCards = [
},
]

// What the archive holds. Not links: the archive has no per-type pages to open
const archiveTypes = [
{ title: "Dispense", icon: FiBook },
{ title: "Esami", icon: FiFileText },
{ title: "Appunti", icon: FiClipboard },
]

export function Materials() {
const { showActivationToast, activationToast } = useActivationToast()
return (
<section
className={cn(
Expand Down Expand Up @@ -71,13 +74,15 @@ export function Materials() {
caption={card.description}
icon={card.icon}
href={card.href}
onClick={card.href ? undefined : showActivationToast}
className="h-auto w-full p-6 xl:hidden"
/>
<CardIcon
title={card.title}
description={card.description}
icon={card.icon}
href={card.href}
onClick={card.href ? undefined : showActivationToast}
size="md"
className="hidden h-full xl:block"
/>
Expand All @@ -87,10 +92,17 @@ export function Materials() {

<div className="grid grid-cols-3 gap-4 sm:gap-6">
{archiveTypes.map((card) => (
<CardIcon key={card.title} {...card} size={{ base: "xs", sm: "sm" }} className="h-full" />
<CardIcon
key={card.title}
{...card}
onClick={showActivationToast}
size={{ base: "xs", sm: "sm" }}
className="h-full"
/>
))}
</div>
</div>
{activationToast}
</section>
)
}
34 changes: 34 additions & 0 deletions src/components/ui/activation-toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client"

import { useEffect, useState } from "react"
import { createPortal } from "react-dom"
import { FiInfo } from "react-icons/fi"

/** One short notice at a time; another click restarts its three-second timeout. */
export function useActivationToast() {
const [announcement, setAnnouncement] = useState(0)

useEffect(() => {
if (!announcement) return
const timeout = window.setTimeout(() => setAnnouncement(0), 3000)
return () => window.clearTimeout(timeout)
}, [announcement])

return {
showActivationToast: () => setAnnouncement((current) => current + 1),
activationToast: announcement
? createPortal(
<output
key={announcement}
aria-live="polite"
aria-atomic="true"
className="typo-body-medium pointer-events-none fixed right-4 bottom-4 z-100 flex max-w-[calc(100vw-2rem)] items-center gap-3 rounded-2xl border border-white/70 bg-background/95 px-5 py-4 text-text-primary shadow-lg backdrop-blur-md sm:right-6 sm:bottom-6"
>
<FiInfo aria-hidden className="size-5 shrink-0 text-blue-secondary" />
In attivazione
</output>,
document.body
)
: null,
}
}
Loading