diff --git a/app/[locale]/apps/[application]/page.tsx b/app/[locale]/apps/[application]/page.tsx index a5b1c7835a2..066a9bd41de 100644 --- a/app/[locale]/apps/[application]/page.tsx +++ b/app/[locale]/apps/[application]/page.tsx @@ -368,7 +368,7 @@ const Page = async ({ { const cardContent = (
{app.name} @@ -93,9 +79,7 @@ const AppCard = ({ }} /> )} -

- {app.subCategory.map((subCategory) => subCategory).join(" · ")} -

+
) diff --git a/app/[locale]/apps/_components/TopApps.tsx b/app/[locale]/apps/_components/TopApps.tsx index 6fc98d1361b..9acd01e7267 100644 --- a/app/[locale]/apps/_components/TopApps.tsx +++ b/app/[locale]/apps/_components/TopApps.tsx @@ -143,7 +143,7 @@ const TopApps = ({ appsData }: TopAppsProps) => { app={app} imageSize={imageSize} isVertical={isVertical} - hideTag={true} + hideTag matomoCategory="apps" matomoAction="top_apps" /> diff --git a/app/[locale]/apps/page.tsx b/app/[locale]/apps/page.tsx index c374f948c0c..3a75854e393 100644 --- a/app/[locale]/apps/page.tsx +++ b/app/[locale]/apps/page.tsx @@ -101,7 +101,7 @@ const Page = async ({ params }: { params: PageParams }) => { key={app.name} app={app} imageSize={24} - showDescription={true} + showDescription matomoCategory="apps" matomoAction="staff" /> diff --git a/app/[locale]/developers/_components/HackathonCard.tsx b/app/[locale]/developers/_components/HackathonCard.tsx deleted file mode 100644 index 46e60264c54..00000000000 --- a/app/[locale]/developers/_components/HackathonCard.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { getLocale } from "next-intl/server" - -import type { EventItem } from "@/lib/types" - -import { Image } from "@/components/Image" -import CardImage from "@/components/Image/CardImage" -import { - Card, - CardBanner, - CardContent, - CardHighlight, - CardSubTitle, - CardTitle, -} from "@/components/ui/card" - -import { formatDateRange } from "@/lib/utils/date" - -import EventFallback from "@/public/images/events/event-placeholder.png" - -type HackathonCardProps = { - event: EventItem - className?: string -} - -const HackathonCard = async ({ event, className }: HackathonCardProps) => { - const locale = await getLocale() - const { title, link, bannerImage, location, startTime, endTime } = event - const formattedDate = formatDateRange(startTime, endTime, locale, { - year: "numeric", - }) - - return ( - - - {bannerImage ? ( - - ) : ( - - )} - - - {title} - {formattedDate} - {location} - - - ) -} - -export default HackathonCard diff --git a/app/[locale]/developers/page.tsx b/app/[locale]/developers/page.tsx index f2b99203b1e..486b2a88779 100644 --- a/app/[locale]/developers/page.tsx +++ b/app/[locale]/developers/page.tsx @@ -9,9 +9,16 @@ import FeedbackCard from "@/components/FeedbackCard" import HubHero from "@/components/Hero/HubHero" import { CheckCircle } from "@/components/icons/CheckCircle" import { Image } from "@/components/Image" +import CardImage from "@/components/Image/CardImage" import MainArticle from "@/components/MainArticle" import { ButtonLink } from "@/components/ui/buttons/Button" -import { Card } from "@/components/ui/card" +import { + Card, + CardBanner, + CardContent, + CardParagraph, + CardTitle, +} from "@/components/ui/card" import { EdgeScrollContainer, EdgeScrollItem, @@ -23,12 +30,12 @@ import { Section } from "@/components/ui/section" import { cn } from "@/lib/utils/cn" import { getAppPageContributorInfo } from "@/lib/utils/contributors" +import { formatDateRange } from "@/lib/utils/date" import { getMetadata } from "@/lib/utils/metadata" import { screens } from "@/lib/utils/screen" import BuilderCard from "./_components/BuilderCard" import BuilderSwiper from "./_components/BuilderSwiper/lazy" -import HackathonCard from "./_components/HackathonCard" import SpeedRunCard from "./_components/SpeedRunCard" import VideoCourseCard from "./_components/VideoCourseCard" import VideoCourseSwiper from "./_components/VideoCourseSwiper/lazy" @@ -40,8 +47,8 @@ import scaffoldDebugScreenshot from "@/public/images/developers/scaffold-debug-s import stackExchangeScreenshot from "@/public/images/developers/stack-exchange-screenshot.png" import tutorialTagsBanner from "@/public/images/developers/tutorial-tags-banner.png" import dogeImage from "@/public/images/doge-computer.png" +import EventFallback from "@/public/images/events/event-placeholder.png" import heroImage from "@/public/images/heroes/developers-hub-hero.png" - const H3 = (props: ChildOnlyProp) =>

const Text = (props: ChildOnlyProp) =>

@@ -531,19 +538,53 @@ const DevelopersPage = async ({ params }: { params: PageParams }) => {

{t("page-developers-hackathons-desc")}

- {hackathons.map((event) => ( - - { + const { + title, + link, + bannerImage, + location, + startTime, + endTime, + } = event + + return ( + - - ))} + asChild + className="ms-6 w-[calc(100%-4rem)] max-w-md md:min-w-96 md:flex-1 lg:max-w-[33%]" + > + + + {bannerImage ? ( + + ) : ( + + )} + + + {title} + + {formatDateRange(startTime, endTime, locale, { + year: "numeric", + })} + + + {location} + + + + + ) + })}
diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 6f09b998d8e..d6faa9cd25d 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -44,8 +44,7 @@ import { Card, CardBanner, CardContent, - CardHighlight, - CardSubTitle, + CardParagraph, CardTitle, } from "@/components/ui/card" import InlineLink from "@/components/ui/Link" @@ -863,7 +862,7 @@ const Page = async ({ params }: { params: PageParams }) => { eventName: title, }} > - + {bannerImage ? ( { {title} - + {formatDateRange(startTime, endTime, locale, { month: "long", year: "numeric", })} - - {location} + + + {location} + ) diff --git a/app/[locale]/trillion-dollar-security/page.tsx b/app/[locale]/trillion-dollar-security/page.tsx index 0aaa6816dd8..138789747d2 100644 --- a/app/[locale]/trillion-dollar-security/page.tsx +++ b/app/[locale]/trillion-dollar-security/page.tsx @@ -9,8 +9,8 @@ import { ButtonLink } from "@/components/ui/buttons/Button" import { Card, CardContent, - CardDescription, CardFooter, + CardParagraph, } from "@/components/ui/card" import InlineLink, { BaseLink as Link } from "@/components/ui/Link" @@ -26,13 +26,13 @@ const ReportCard = ({ cta, altText }: { cta: string; altText: string }) => { return ( - + {altText} - + diff --git a/src/components/AreaChart/index.tsx b/src/components/AreaChart/index.tsx index 5782a2104ae..690d656bf8b 100644 --- a/src/components/AreaChart/index.tsx +++ b/src/components/AreaChart/index.tsx @@ -11,9 +11,9 @@ import { import { Card, CardContent, - CardDescription, CardFooter, CardHeader, + CardParagraph, CardTitle, } from "@/components/ui/card" import { @@ -66,7 +66,11 @@ export function AreaChart({ {title && {title}} - {description && {description}} + {description && ( + + {description} + + )} diff --git a/src/components/BarChart/index.tsx b/src/components/BarChart/index.tsx index 468f786fe9f..07a0a9b00e9 100644 --- a/src/components/BarChart/index.tsx +++ b/src/components/BarChart/index.tsx @@ -11,9 +11,9 @@ import { import { Card, CardContent, - CardDescription, CardFooter, CardHeader, + CardParagraph, CardTitle, } from "@/components/ui/card" import { @@ -66,7 +66,11 @@ export function BarChart({ {title && {title}} - {description && {description}} + {description && ( + + {description} + + )} diff --git a/src/components/FindWalletProductTable/WalletInfo.tsx b/src/components/FindWalletProductTable/WalletInfo.tsx index a49d344107b..98028704055 100644 --- a/src/components/FindWalletProductTable/WalletInfo.tsx +++ b/src/components/FindWalletProductTable/WalletInfo.tsx @@ -15,6 +15,7 @@ import { NUMBER_OF_SUPPORTED_LANGUAGES_SHOWN } from "@/lib/constants" import MediaQuery from "../MediaQuery" import { ButtonLink } from "../ui/buttons/Button" +import { TagsInlineText } from "../ui/tag" import PersonaTags from "./PersonaTags" @@ -44,10 +45,6 @@ const WalletInfo = ({ wallet }: WalletInfoProps) => { return labels }, [wallet, t]) - const deviceLabelsText = useMemo(() => { - return deviceLabels.join(" · ") - }, [deviceLabels]) - const formattedLanguages = useMemo(() => { return formatStringList( wallet.supportedLanguages, @@ -127,7 +124,7 @@ const WalletInfo = ({ wallet }: WalletInfoProps) => { {deviceLabels.length > 0 && (
-

{deviceLabelsText}

+
)}
diff --git a/src/components/Homepage/RecentPostsSwiper.tsx b/src/components/Homepage/RecentPostsSwiper.tsx index 9c578d54a69..dc25474eb9b 100644 --- a/src/components/Homepage/RecentPostsSwiper.tsx +++ b/src/components/Homepage/RecentPostsSwiper.tsx @@ -10,8 +10,7 @@ import { Card, CardBanner, CardContent, - CardHighlight, - CardSubTitle, + CardParagraph, CardTitle, } from "../ui/card" import { @@ -58,13 +57,19 @@ const RecentPostsSwiper = ({ eventName: source, }} > - + {title} - {isValidDate(pubDate) && {pubDate}} - {source} + {isValidDate(pubDate) && ( + + {pubDate} + + )} + + {source} + diff --git a/src/components/Image/CardImage.tsx b/src/components/Image/CardImage.tsx index 21d4f8138dc..a331bd5333c 100644 --- a/src/components/Image/CardImage.tsx +++ b/src/components/Image/CardImage.tsx @@ -1,25 +1,28 @@ "use client" -import { ComponentProps } from "react" +import { ComponentProps, forwardRef } from "react" import EventFallback from "@/public/images/events/event-placeholder.png" type CardImageProps = ComponentProps<"img"> -const CardImage = ({ src, className, ...props }: CardImageProps) => ( - // eslint-disable-next-line @next/next/no-img-element - { - e.currentTarget.onerror = null - e.currentTarget.src = EventFallback.src - }} - referrerPolicy="no-referrer" - className={className} - {...props} - /> +const CardImage = forwardRef( + ({ src, className, ...props }, ref) => ( + // eslint-disable-next-line @next/next/no-img-element + { + e.currentTarget.onerror = null + e.currentTarget.src = EventFallback.src + }} + referrerPolicy="no-referrer" + className={className} + {...props} + /> + ) ) CardImage.displayName = "CardImage" diff --git a/src/components/PieChart/index.tsx b/src/components/PieChart/index.tsx index 7b993bddd6c..f9f827323bb 100644 --- a/src/components/PieChart/index.tsx +++ b/src/components/PieChart/index.tsx @@ -14,9 +14,9 @@ import type { Formatter } from "recharts/types/component/DefaultLegendContent" import { Card, CardContent, - CardDescription, CardFooter, CardHeader, + CardParagraph, CardTitle, } from "@/components/ui/card" import { @@ -119,7 +119,11 @@ export function PieChart({ {(title || description) && ( {title && {title}} - {description && {description}} + {description && ( + + {description} + + )} )} @@ -195,7 +199,11 @@ export function PieChart({ > {title && {title}} - {description && {description}} + {description && ( + + {description} + + )} diff --git a/src/components/ui/TruncatedText.tsx b/src/components/ui/TruncatedText.tsx index 108aa30ce05..3f233c3457f 100644 --- a/src/components/ui/TruncatedText.tsx +++ b/src/components/ui/TruncatedText.tsx @@ -14,7 +14,7 @@ interface TruncatedTextProps { showMoreText?: string showLessText?: string className?: string - matomoEvent: MatomoEventOptions + matomoEvent?: MatomoEventOptions } const TruncatedText = ({ diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 58fa03ac000..07478b61f1f 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -5,49 +5,36 @@ import { cn } from "@/lib/utils/cn" import { BaseLink, LinkProps } from "./Link" -const titleVariants = cva( - "group-hover/link:underline group-focus/link:underline", - { - variants: { - variant: { - bold: "text-2xl font-bold", - black: "text-3xl font-black", - }, - }, - defaultVariants: { - variant: "bold", - }, - } -) +const cardVariants = cva("rounded-2xl text-body no-underline hover:text-body", { + variants: {}, +}) -type CardProps = React.HTMLAttributes & - Pick -const Card = React.forwardRef( - ({ className, href, customEventOptions, ...props }, ref) => { - if (href) { - return ( - -
- - ) - } +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & + Pick & + VariantProps +>(({ className, href, customEventOptions, ...props }, ref) => { + if (href) { return ( -
+ +
+ ) } -) + return ( +
+ ) +}) Card.displayName = "Card" const CardHeader = React.forwardRef< @@ -62,24 +49,65 @@ const CardHeader = React.forwardRef< )) CardHeader.displayName = "CardHeader" +const cardBannerVariants = cva( + cn( + "overflow-hidden rounded-2xl", + "[&_img]:size-full [&_img]:object-cover [&_img]:duration-200", + "group-hover/link:[&_img]:scale-110 group-hover/link:[&_img]:duration-200 group-focus/link:[&_img]:scale-110 group-focus/link:[&_img]:duration-200" + ), + { + variants: { + background: { + "accent-a": + "bg-gradient-to-b from-accent-a/5 to-accent-a/10 dark:from-accent-a/10 dark:to-accent-a/20", + "accent-b": + "bg-gradient-to-b from-accent-b/5 to-accent-b/10 dark:from-accent-b/10 dark:to-accent-b/20", + "accent-c": + "bg-gradient-to-b from-accent-c/5 to-accent-c/10 dark:from-accent-c/10 dark:to-accent-c/20", + primary: + "bg-gradient-to-b from-primary/5 to-primary/10 dark:from-primary/10 dark:to-primary/20", + body: "bg-gradient-to-b from-body/5 to-body/10 dark:from-body/10 dark:to-body/20", + none: "", + }, + size: { + full: "h-48 w-full self-stretch", + thumbnail: "size-16", + }, + }, + defaultVariants: { + background: "body", + size: "full", + }, + } +) + const CardBanner = React.forwardRef< HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( + React.HTMLAttributes & VariantProps +>(({ className, background, size, ...props }, ref) => (
)) CardBanner.displayName = "CardBanner" +const titleVariants = cva( + "group-hover/link:underline group-focus/link:underline", + { + variants: { + variant: { + bold: "text-2xl font-bold", + black: "text-3xl font-black", + }, + }, + defaultVariants: { + variant: "bold", + }, + } +) + const CardTitle = React.forwardRef< HTMLHeadingElement, React.HTMLAttributes & VariantProps @@ -92,37 +120,37 @@ const CardTitle = React.forwardRef< )) CardTitle.displayName = "CardTitle" -const CardSubTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardSubTitle.displayName = "CardSubTitle" - -const CardHighlight = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) -CardHighlight.displayName = "CardHighlight" +const paragraphVariants = cva("", { + variants: { + variant: { + base: "text-body", + light: "text-body-medium", + uppercase: "uppercase text-body-medium", + subtitle: "italic", + }, + size: { + base: "", + sm: "text-sm", + }, + }, + defaultVariants: { + variant: "base", + size: "base", + }, +}) -const CardDescription = React.forwardRef< +const CardParagraph = React.forwardRef< HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( + React.HTMLAttributes & + VariantProps +>(({ className, variant, size, ...props }, ref) => (

)) -CardDescription.displayName = "CardDescription" +CardParagraph.displayName = "CardParagraph" const CardContent = React.forwardRef< HTMLDivElement, @@ -148,10 +176,8 @@ export { Card, CardBanner, CardContent, - CardDescription, CardFooter, CardHeader, - CardHighlight, - CardSubTitle, + CardParagraph, CardTitle, } diff --git a/src/components/ui/tag.tsx b/src/components/ui/tag.tsx index 5df48e75842..d935a340261 100644 --- a/src/components/ui/tag.tsx +++ b/src/components/ui/tag.tsx @@ -1,4 +1,4 @@ -import { forwardRef } from "react" +import { forwardRef, HTMLAttributes } from "react" import { cva, VariantProps } from "class-variance-authority" import { Slot } from "@radix-ui/react-slot" @@ -165,4 +165,36 @@ const TagButton = forwardRef( TagButton.displayName = "TagButton" -export { Tag, TagButton } +const listVariants = cva("", { + variants: { + variant: { + light: "text-sm text-body-medium", + }, + }, +}) + +export interface TagsInlineTextProps + extends Omit, "children">, + VariantProps { + list: string[] + delimiter?: string + max?: number +} + +const TagsInlineText = forwardRef( + ({ className, list, delimiter = "·", max, variant, ...props }, ref) => { + return ( +

+ {list.slice(0, max).join(` ${delimiter} `)} +

+ ) + } +) + +TagsInlineText.displayName = "TagsInlineText" + +export { Tag, TagButton, TagsInlineText } diff --git a/src/lib/constants.ts b/src/lib/constants.ts index d3ef0b5429a..fd69ee33964 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -241,3 +241,11 @@ export const BLOG_FEEDS = COMMUNITY_BLOGS.map(({ feed }) => feed).filter( ) as string[] export const BLOGS_WITHOUT_FEED = COMMUNITY_BLOGS.filter((item) => !item.feed) + +export const SIZE_CLASS_MAPPING = { + 10: "size-10", + 12: "size-12", + 14: "size-14", + 16: "size-16", + 24: "size-24", +} as const