Skip to content
Open

Card #41

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a7f088f
Card initial commit
Diubii Mar 11, 2026
cb61f09
Text and icon gradients
Diubii Mar 11, 2026
2e3f23d
Used figma classes
Diubii Mar 11, 2026
d4975c0
Moved card.tsx to components/ui/cards
Diubii Mar 17, 2026
4b4056a
refactor: move card.tsx into ui
Diubii Apr 2, 2026
f34b630
Card initial commit
Diubii Mar 11, 2026
260de21
Text and icon gradients
Diubii Mar 11, 2026
714bf5a
Used figma classes
Diubii Mar 11, 2026
9d80222
Moved card.tsx to components/ui/cards
Diubii Mar 17, 2026
74b8885
refactor: move card.tsx into ui
Diubii Apr 2, 2026
fb70a85
Merge branch 'Diubii/card' of github.com:PoliNetworkOrg/web into Diub…
Diubii Apr 2, 2026
95497ac
chore: biome; fix: imports
Diubii Apr 2, 2026
8d16fc7
aligned card.tsx between all cards
Diubii Apr 9, 2026
e40ed5c
Merge branch 'main' into Diubii/card
Diubii Apr 9, 2026
6a72e27
feat: optional gradient
Diubii Apr 9, 2026
6c2c5cd
fix: text transparency to see gradient
Diubii Apr 9, 2026
cef8d1d
feat: added bg-background-blur to all cards
Diubii Apr 9, 2026
f4d4bcf
feat: add card-caption (#48)
Diubii Apr 9, 2026
7124c21
feat: add card-path-selection (#56)
Diubii Apr 9, 2026
97a3005
feat: add card-course (#63)
Diubii Apr 9, 2026
c7bd2bd
feat: add card-course-group (#67)
Diubii Apr 9, 2026
cd4e805
Merge branch 'main' into Diubii/card
Diubii Apr 9, 2026
1fbb836
fix: some of CodeRabbit's proposed fixes
Diubii Apr 9, 2026
155b62d
Merge branch 'main' into Diubii/card
toto04 Apr 9, 2026
75a1458
Merge branch 'main' into Diubii/card
lorenzocorallo Apr 9, 2026
3b6aa56
required changes by @toto04 and CodeRabbit
Diubii Apr 14, 2026
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
28 changes: 28 additions & 0 deletions src/components/card-caption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IconType } from "react-icons"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "./ui/card"

export function CardCaption({
title,
caption,
icon,
iconPosition = "right",
}: {
title: string
caption: string
icon?: IconType
iconPosition?: "top" | "right"
}) {
return (
<Card>
<CardHeader
className={`typo-headline-medium flex ${iconPosition === "right" ? "justify-between" : "flex-col-reverse"}`}
>
<CardTitle>{title}</CardTitle>
{icon && <CardAction icon={icon} iconSize={iconPosition === "right" ? "normal" : "large"}></CardAction>}
</CardHeader>
<CardContent className="typo-body-medium">
<p>{caption}</p>
</CardContent>
</Card>
)
}
47 changes: 47 additions & 0 deletions src/components/card-course-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { cva, type VariantProps } from "class-variance-authority"
import type { IconType } from "react-icons"
import { FaWhatsapp } from "react-icons/fa"
import { LiaTelegramPlane } from "react-icons/lia"
import { cn } from "@/lib/utils"
import { Card, CardAction, CardTitle } from "./ui/card"

export const cardCourseGroupVariants = cva(
"flex h-fit w-full flex-row items-center gap-5 px-7.5 py-6.25 font-normal leading-6 tracking-[0.03125rem]",
{
variants: {
secondary: {
true: "bg-[rgba(148,192,237,0.40)]",
false: "",
},
},
defaultVariants: {
secondary: false,
},
}
)

export function CardCourseGroup({
groupName,
hasWhatsapp = true,
iconWhatsApp: IconWhatsApp = FaWhatsapp,
hasTelegram = true,
iconTelegram: IconTelegram = LiaTelegramPlane,
secondary = false,
}: {
groupName: string
hasWhatsapp?: boolean
iconWhatsApp?: IconType
hasTelegram?: boolean
iconTelegram?: IconType
} & VariantProps<typeof cardCourseGroupVariants>) {
const actionClassName = cn("rounded-full p-3.75", secondary ? "bg-[#51A2FF]" : "bg-[#74D4FF]")
return (
<Card className={cn(cardCourseGroupVariants({ secondary }))}>
<CardTitle gradient={false} className="typo-headline-small grow">
{groupName}
</CardTitle>
{hasWhatsapp && <CardAction gradient={false} className={actionClassName} icon={IconWhatsApp} iconSize="normal" />}
{hasTelegram && <CardAction gradient={false} className={actionClassName} icon={IconTelegram} iconSize="normal" />}
</Card>
)
}
36 changes: 36 additions & 0 deletions src/components/card-course.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { IconType } from "react-icons"
import { CiGlobe } from "react-icons/ci"
import { FaAngleRight } from "react-icons/fa6"
import { IoLocationOutline } from "react-icons/io5"
import { Card, CardAction, CardContent } from "./ui/card"

export function CardCourse({
courseName,
iconLocation: IconLocation = IoLocationOutline,
location = "Milano Leonardo",
iconLanguage: IconLanguage = CiGlobe,
language = "ITA",
iconSelect: IconSelect = FaAngleRight,
}: {
courseName: string
iconLocation?: IconType
location?: "Milano Leonardo" | "Milano Bovisa" | "Cremona" | "Lecco" | "Mantova" | "Piacenza" | "Xi'an" //Magari poi si mette solo string come tipo nel caso si pullino da un DB
iconLanguage?: IconType
language?: "ITA" | "ENG" //Idem
iconSelect?: IconType
}) {
return (
<Card className="typo-body-large flex h-fit w-full flex-row px-5 py-3.75 font-normal leading-6 tracking-[0.03125rem]">
<CardContent className="basis-1/3 truncate">{courseName}</CardContent>
<CardContent className="flex basis-1/3 items-center gap-1.25">
<CardAction icon={IconLocation} iconSize="small" /> {location}
</CardContent>
<CardContent className="flex basis-1/3 items-center gap-1.25">
<CardAction icon={IconLanguage} iconSize="small" /> {language}
</CardContent>
<CardContent className="flex items-center">
<CardAction icon={IconSelect} iconSize="small" />
</CardContent>
</Card>
)
}
13 changes: 13 additions & 0 deletions src/components/card-path-selection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { IconType } from "react-icons"
import { BsBook } from "react-icons/bs"
import { Card, CardAction, CardContent } from "./ui/card"

export function CardPathSelection({ caption, icon: Icon = BsBook }: { caption: string; icon?: IconType }) {
return (
<Card className="h-fit w-136 px-10 py-7.5">
<CardContent className="typo-body-large flex flex-row items-center justify-center gap-1.25 whitespace-nowrap font-normal leading-6 tracking-[0.03125rem]">
<CardAction icon={Icon} iconSize="small" /> {caption}
</CardContent>
</Card>
)
}
102 changes: 102 additions & 0 deletions src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as React from "react"
import type { IconType } from "react-icons"
import { cn } from "@/lib/utils"
import { Glass } from "../glass"
import { Button } from "./button"

function Card({
className,
size = "default",
...props
}: React.ComponentProps<typeof Glass> & { size?: "default" | "sm" }) {
return (
<Glass
data-slot="card"
data-size={size}
className={cn(
"group/card flex h-66 w-78 flex-col gap-4 overflow-hidden rounded-[1.25rem] bg-background-blur text-card-foreground text-sm ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className
)}
{...props}
/>
)
}

function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
className
)}
{...props}
/>
)
}

function CardTitle({ gradient = true, className, ...props }: React.ComponentProps<"div"> & { gradient?: boolean }) {
return (
<div
data-slot="card-title"
className={cn(
`${gradient ? "bg-linear-to-b from-blue-secondary to-blue-primary bg-clip-text text-transparent" : ""} font-medium text-[1.5rem] leading-snug group-data-[size=sm]/card:text-base`,
className
)}
{...props}
/>
)
}

function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="card-description" className={cn("text-muted-foreground text-sm", className)} {...props} />
}

function CardAction({
className,
icon: Icon,
iconSize = "normal",
gradient = true,
...props
}: React.ComponentProps<"div"> & { icon: IconType; iconSize?: "small" | "normal" | "large"; gradient?: boolean }) {
const gradientId = React.useId()

return (
<div
data-slot="card-action"
className={cn("col-start-2 row-span-2 row-start-1 self-auto justify-self-end", className)}
{...props}
>
{gradient && (
<svg width="0" height="0" className="absolute" aria-hidden="true" focusable="false">
<linearGradient id={gradientId} x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" className="text-blue-secondary" stopColor="currentColor" />
<stop offset="100%" className="text-blue-primary" stopColor="currentColor" />
</linearGradient>
</svg>
)}

<Icon
size={iconSize === "small" ? "1.125rem" : iconSize === "normal" ? "2rem" : "3.5rem"}
fill={gradient ? `url(#${gradientId})` : "currentColor"}
stroke={gradient ? `url(#${gradientId})` : "currentColor"}
/>
</div>
)
}

function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("text-[.875rem] group-data-[size=sm]/card:px-3", className)}
{...props}
/>
)
}

function CardBottomButton({ className, ...props }: React.ComponentProps<typeof Button>) {
return <Button data-slot="card-footer" className={cn("self-end", className)} {...props} />
}

export { Card, CardHeader, CardBottomButton, CardTitle, CardAction, CardDescription, CardContent }
Loading