卡片组件是网页开发中最常见的UI设计模式之一。你在房产列表应用、SaaS分析面板、电子商务产品页面以及管理界面中都能看到它们的身影。

但是,要构建一个能够正确处理悬停效果、支持深色模式、保证可访问性,并且能在不同屏幕尺寸下正常使用的卡片组件,仅仅使用

标签来包裹内容是远远不够的。你需要一套统一的组件结构、可靠的设计系统,以及经过精心设计的Tailwind CSS样式规则。

在本教程中,你将通过Shadcn Space和Base UI的基本元素,构建四种可用于实际开发的卡片组件。每种卡片组件都是针对开发者们在开发过程中经常会遇到的具体UI场景而设计的。

学习完本教程后,你将能够掌握以下内容:

  1. 一种预览卡片组件,它具有悬停时显示图片的效果、叠加的箭头图标,以及用于展示详细信息的布局结构

  2. 一种分析卡片组件,它可以显示具体的指标数据、根据条件改变徽章的颜色,并且配有装饰性的背景图像

  3. 一种统计卡片组件,它采用了响应式设计,能够以四列的形式展示电子商务相关的统计数据,并配有图标徽章

  4. 一种电子商务产品变体卡片组件,用户可以通过它选择产品尺寸、切换加入愿望清单的功能、点击购物按钮时还会看到动画效果

目录

先决条件

在开始学习之前,请确保你已经具备以下条件:

  • 已安装Node.js 18或更高版本

  • 已经搭建好了Next.js或React项目框架

  • 已在你的项目中初始化shadcn/ui插件(执行命令:npx shadcn@latest init

  • 已配置好Tailwind CSS样式

  • 具备React和TypeScript的基本使用知识

如果您尚未初始化 shadcn/ui,请在项目根目录中运行 npx shadcn@latest init,并按照提示操作后再继续下一步。

为什么选择 shadcn/ui?

shadcn/ui 是一套基于 Radix UI 和 Base UI 构建的开源 React 组件库,这些组件采用了 Tailwind CSS 进行样式设计。

它的使用方式与传统组件库不同:您无需安装任何包,而是通过 CLI 直接将组件源代码复制到项目中。这样一来,您就能完全掌控所有代码。您可以随意阅读或修改这些代码,而且由于不会受到第三方库更新的影响,您的项目也不会出现任何问题。

它的一些主要优点包括:

  • 默认具备无障碍访问功能:基于 Radix UI 和 Base UI 的基础组件构建而成

  • 完全采用 Tailwind CSS 设计:无需外部 CSS 文件,也不会出现样式冲突问题

  • 不存在任何绑定风险:这些组件被保存在您的 components/ 目录中,而非 node_modules

  • 兼容多种开发环境:Next.js、Vite、Astro、Remix 等各种 React 框架均可使用

在本教程中会使用的 CardBadgeButtonSeparator 组件,都来自 shadcn/ui 的基础安装包。

什么是 Shadcn Space?

Shadcn Space 是一个开源的组件库,其中包含了许多已经准备好直接用于生产环境的 UI 组件。它基于 shadcn/ui 的基础组件集进行了扩展,提供了更多从常见设计模式衍生出来的变体,从而帮助开发者打造出更加美观的界面。

与 shadcn/ui 中默认提供的 Card 组件相比,Shadcn Space 提供的卡片组件是专门为特定的布局结构设计的,因此使用时能获得更清晰的结构框架。

Shadcn Space 中的每个组件都同时支持 Radix UI 和 Base UI 的基础功能。此外,这些组件还配备了 复制提示功能。本教程中使用的都是 Base UI 版本的组件,安装方法与其他 shadcn/ui 组件相同,只需通过一条 CLI 命令即可将代码文件添加到项目中。

您可以在 Shadcn 组件库 中浏览所有可用的组件。

你将构建什么

以下是您将制作的四款组件的简要介绍及其具体功能:

预览卡片(card-02)

  • 当鼠标悬停时,图片会显示亮度变化及尺寸缩放动画

  • 只有当鼠标悬停时才会出现的箭头图标

  • 属性名称及其所在位置

  • 采用蓝绿色调的价格标识

  • 包含床、浴室等设施图标的行显示区

分析数据卡片(card-05)

  • 输入TypeScript属性值,并可设置内置的默认数据集

  • 两个指标列,中间用竖线分隔

  • 根据数据的增减趋势,徽章颜色会相应变化

  • 在右下角显示装饰性背景图片

统计信息卡片(card-06)

  • 移动设备上显示时,采用四列自适应布局

  • 每个指标都会使用对应的图标进行展示

  • 徽章上会标明数据趋势的方向

  • 最后一列会自动去除边框分隔线

电商产品变体卡片(card-17)

  • 产品图片支持3D阴影效果,悬停时可放大查看

  • 支持在深色模式下切换心愿单功能

  • 尺寸选择器会突出显示当前选中的选项

  • 点击按钮后,购物袋图标会显示出来

  • “立即购买”按钮带有CSS动画效果

  • 配送日期会以序数后缀的形式进行显示

如何设置CLI注册表

在运行任何安装命令之前,你需要将Shadcn Space注册表添加到你的`components.json`文件中。

打开项目根目录下的`components.json`文件,然后添加`registries`字段:

{
  "registries": {
    "@shadcn-space": {
      "url": "https://shadcnspace.com/r/{name}.json"
    }
  }
}

这样就可以告诉shadcn CLI在哪里查找以`@shadcn-space/`为前缀的组件。如果不执行这一步,本教程中的所有安装命令都会失败。

添加注册表后,你的`components.json`文件应该看起来像这样:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@lib/utils"
  },
  "registries": {
    "@shadcn-space": {
      "url": "https://shadcnspace.com/r/{name}.json"
    }
  }
}

如果你想详细了解CLI是如何与第三方注册表协同工作的,可以访问入门指南。如果你更喜欢通过视频来学习,也可以观看相关教程。

如何制作预览卡片(card-02版本)

预览卡片的用途

预览卡片专为房产列表、酒店页面或任何需要使用大图片并搭配下方详细信息的场景设计。

当用户将鼠标悬停在该卡片上时,图片会变暗并放大,同时角落会出现一个箭头图标。图片下方会显示标题、位置、价格信息以及各种设施详情。

如何安装预览卡片

根据您使用的包管理器,运行以下命令之一即可:

npm:

npx shadcn@latest add @shadcn-space/card-02

pnpm:

pnpm dlx shadcn@latest add @shadcn-space/card-02

Yarn:

yarn dlx shadcn@latest add @shadcn-space/card-02

Bun:

bunx --bun shadcn@latest add @shadcn-space/card-02

命令执行后,该组件会被复制到您的项目中,具体路径为:

components/
  shadcn-space/
    card/
      Card-02.tsx

组件代码

import { Badge } from 「@components/ui/badge」;
import { Card } from 「@components/ui/card」;
import { ArrowRight, Bath, BedDouble, Expand } from "lucide-react";

const PreviewCard = () => (
  
    

    

Serenity Residential Home

15 S Aurora Ave, Miami

$570,000

5间卧室

3间浴室

120平方米

); export default PreviewCard;

现在让我们来了解一下这段代码的具体工作原理。

1. 组合元素在悬停时的行为

外层Card元素上应用的group类是这个组件的核心。任何带有groupHover:类的子元素都会在卡片被悬停时产生相应的反应,而不仅仅是那个单独的元素。

这就是图片会变暗(group-hover:brightness-50)、放大(group-hover:scale-125),以及箭头图标会出现(groupHover:block)的原因。

2. 图片缩放时的溢出裁剪功能

如果图片包装元素上没有设置overflow-hidden,那么当应用scale-125这种缩放效果时,图像的边缘就会超出卡片的边框。而通过使用overflow-hidden,就可以将图像裁剪掉,使其始终保持在卡片范围内。

请注意,无论是包装元素还是图片本身,都应用了rounded-t-2xl这个样式,这样在过渡过程中图形的圆角效果就能保持一致。

3. 便利设施行中的逻辑边框属性

在便利设施行中,使用了border-e而不是border-r。这是一个CSS的逻辑属性,表示“位于内联方向的边界”。在从左到右的布局中,它指的是右侧;而在从右到左的布局中,这个属性会自动调整方向。对于那些可能需要支持多种语言环境的组件来说,使用逻辑属性是一种很好的开发习惯。

实时预览:

21233f5e-2d39-4430-8013-a780bd24419c


如何构建分析卡片(card-05)

分析卡片的用途

分析卡片是一种简洁的仪表盘组件。它能够并排显示两个指标的值以及它们的百分比变化情况,右下角还会展示一张装饰性的图表。

这个组件的类型定义使用了TypeScript接口,因此可以很容易地从API中获取实际数据并将其填充到组件中。

如何安装分析卡片

npx shadcn@latest add @shadcn-space/card-05

命令行工具会将这个组件复制到以下路径:

components/
  shadcn-space/
    card/
      card-05.tsx

组件代码

import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@components/ui/card";
import { Separator } from "@components/ui/separator";
import { cn } from "@lib/utils";

type DashboardMetric = {
  label: string;
  value: string;
  percentage: string;
  isPositive?: boolean;
};

type MainDashboardData = {
  title: string;
  description: string;
  metrics: DashboardMetric[];
};

type WidgetProps = {
  mainDashboard?: MainDashboardData;
};

const mainDashboardData: MainDashboardData = {
  title: "分析仪表盘",
  description: "查看所有统计数据",
  metrics: [
    {
      label: "收入",
      value: "$27,850",
      percentage: "+18%",
      isPositive: true,
    },
    {
      label: "支出",
      value: "$18,453",
      percentage: "-5%",
      isPositive: false,
    },
  ],
};

const AnalyticsCard = ({ mainDashboard = mainDashboardData }: WidgetProps) => {
  return (
    

{metric.percentage}
>
{index < mainDashboard.metrics.length - 1 && ( > ))}
> 统计图表
); }; export default AnalyticsCard;

分析卡的工作原理:

1. 带有默认数据集的可选属性

该组件接受一个可选的 `mainDashboard` 属性。如果您不传递任何值,系统会使用 `mainDashboardData`,这个常量在同一文件中定义:

const AnalyticsCard = ({ mainDashboard = mainDashboardData }: WidgetProps) => {

这种设计使得该组件在演示或Storybook环境中可以立即使用,而在生产环境中则完全依赖真实的API数据。要将其与实时数据连接起来,您只需传递一个形状与 `MainDashboardData` 相匹配的属性即可。

2. 使用 `cn()` 实现条件性的徽标颜色设置

cn() 这个工具函数(来自 `@/lib/utils`)能够合并Tailwind样式类名,并优雅地处理条件逻辑。它还能自动消除重复的Tailwind样式类名,而普通的模板字面量是无法做到这一点的:

className={cn(
  "font-normal text-muted-foreground",
  metric.isPositive ? "bg-teal-400/10" : "bg-red-500/10"
)}

3>仅在指标之间添加分隔符,不在最后一个指标之后添加

Separator 组件仅在指标之间显示分隔符,而不会在最后一个指标之后出现。通过索引检查可以实现这一逻辑:


)}

4>绝对定位的装饰性图像

图表图像通过 `absolute bottom-0 right-0` 的定位方式固定在卡片的右下角。在小型屏幕上,会使用 `hidden sm:block` 代码使该图像隐藏起来,以避免移动设备上的布局问题。父级 `Card` 元素采用 `relative` 定位方式来容纳这个图像。

实时预览:

de09194f-ff75-49c1-95e5-21d758e499e8


如何构建统计卡片(card-06)

统计卡片的功能

统计卡片以水平网格的形式显示四个电子商务指标:订单量、销售额、利润和支出。每一列都包含一个图标、一个具体的数值、一个时间周期标签,以及一个用于显示百分比变化趋势的徽标。

这种布局具有完全的响应式特性:在中等尺寸的屏幕上,它会从四列变为两列;在移动设备上,则会以堆叠的形式显示这些数据。

该卡片使用了 `@iconify/react` 来生成图标,而不是 `lucide-react`。这样一来,您就可以通过基于字符串的图标名称来使用成千上万的图标集了。

如何安装统计信息卡片组件

首先,如果还没有安装@iconify/react,请先进行安装:

npm install @iconify/react

然后添加该卡片组件:

npx shadcn@latest add @shadcn-space/card-06

命令行工具会将该组件复制到以下路径中:

components/
  shadcn-space/
    card/
      card-06.tsx

组件代码

"use client";
import { Icon } from "@iconify/react";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from"@components/ui/badge";

const StatisticsCard = () => {
  const EcommerceActions = [
    {
      title: "订单",
      subtitle: "5868",
      cardIcon: "solar:bag-4-line-duotone",
      badgeColor: "bg-teal-400/10",
      statusValue: "+18%",
      statusIcon: "solar:course-up-line-duotone",
    },
    {
      title: "销售额",
      subtitle: "$96,850",
      cardIcon: "solar:box-line-duotone",
      badgeColor: "bg-orange-400/10",
      statusValue: "-5%",
      statusIcon: "solar:course-down-line-duotone",
    },
    {
      title: "利润",
      subtitle: "$82,906",
      cardIcon: "solar:chart-square-line-duotone",
      badgeColor: "bg-teal-400/10",
      statusValue: "+18%",
      statusIcon: "solar:course-up-line-duotone",
    },
    {
      title: "支出",
      subtitle: "$14,653",
      cardIcon: "solar:star-line-duotone",
      badgeColor: "bg-teal-400/10",
      statusValue: "+18%",
      statusIcon: "solar:course-up-line-duotone",
    },
  ];

  return (
    
>
{item.title}</h5>
>
{item.subtitle}</h5>
过去7天

>
>
>
>
))} > >
> ); }; export default StatisticsCard;

统计卡片的运作原理:

1. 基于数组的数据驱动布局

所有四个指标都存储在`EcommerceActions`数组中。添加或删除某个指标只需更新该数组即可,JSX代码保持不变。对于任何具有重复结构的组件来说,这种做法都是正确的——将数据与标记分离处理。

2. 自适应的列宽设置

每列都会使用三种不同的宽度样式来适应不同的屏幕分辨率:

  • 在移动设备上使用`w-full`,此时会显示为单列并垂直堆叠

  • 在中等尺寸的屏幕上使用`md:w-6/12`,此时会显示为两列

  • 在大型屏幕上使用`lg:w-3/12`,此时会显示为四列且宽度相等

`CardContent`元素上的`flex-wrap`属性使得在小屏幕上列能够自动换行;而`lg:flex-nowrap`属性则确保在大屏幕上所有列仍能显示在同一行中。

3>使用`last:`属性去除最后一列的边框

`last:border-e-0`这个类会移除最后一行列的右侧边框。如果没有这个设置,卡片的右边缘就会出现多余的边框。

`last:`是一个Tailwind伪类,它能够精准地定位到某个组中的最后一个子元素,这样比手动计算索引要更加简洁方便。

4>为什么在这里需要使用`”use client”`

`@iconify/react`这个包需要在浏览器环境中运行。在Next.js中,如果某个组件引用了仅适用于客户端的库,那么就需要在文件的开头添加`”use client”`指令。否则,在渲染过程中服务器会抛出错误。

实时预览:

dd7600b4-753c-4e67-abff-2d8955a22653


如何构建电商产品变体卡片(card-17)

电商产品变体卡片的功能

这是本教程中最具交互性的卡片。它用于展示鞋子信息,支持悬停放大、添加到心愿单、选择尺码、切换包装方式,以及点击购买按钮时触发波纹动画效果。所有这些交互功能都是通过React的`useState`状态管理机制来实现的,因此不需要使用任何外部状态管理库。

如何安装产品变体卡片组件

npx shadcn@latest add @shadcn-space/card-17

通过命令行工具,该组件会被安装到以下路径:

components/
  shadcn-space/
    card/
      card-17.tsx

组件代码

"use client";

import { useState } from "react";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Button } from "@components/ui/button";
import { Heart, ShoppingBag } from "lucide-react";
import { cn } from "@lib/utils";

const sizes = ["7", "8", "9", "10"];

const getDeliveryDate = () => {
  const date = new Date();
  date.setDate(date.getDate() + 3);
  const day = date.getDate();
  const month = [
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
  ][date.getMonth()];
  const suffix = ["th", "st", "nd", "rd"][
    day % 10 > 3 ? 0 : (day % 100 - day % 10 !== 10 ? day % 10 : 0)
  ];
  return `${day}${suffix} ${month}`;
};

export default function EcommerceProductCard() {
  const [activeSize, setActiveSize] = useState(1);
  const [isWishlisted, setIsWishlisted] = useState(false);
  const [inBag, setInBag] = useState(false);

  return (
    
{/* 图片区域 */}
Nike Air Max Pulse {/* 折扣标志 */} -21% {/* 加入愿望清单按钮 */}
{/* 信息区域 */}

Nike

Air Max Pulse跑鞋

优惠幅度:21% 价格:$150 折扣后价格:$119
配送日期:{" "} {getDeliveryDate}
{/* 动作区域 */}
); }

电子商务产品变体卡的运作原理:

1. 带有名称的悬停组范围

这种卡片使用了两个独立的悬停组范围:外部卡片使用group/card,而“立即购买”按钮则使用group/btn。Tailwind框架通过添加/name后缀来区分这两个范围:

// 卡片级别的悬停效果:会放大产品图片
<Card className="... group/card">
  <img className="... group-hover/card:scale-105" />

// 按钮级别的悬停效果:会触发波纹动画
  <Button className="... group/btn">
    

如果没有使用带有名称的悬停组,那么当鼠标悬停在按钮上时,也会触发卡片的悬停效果。而/card/btn这些后缀正好避免了这种情况。

2. “立即购买”按钮上的CSS波纹动画

这种波纹效果是通过纯CSS的缩放动画实现的。当鼠标悬停在按钮上时,一个白色圆圈(w-8 h-8 rounded-full)会从scale-0状态逐渐变为scale-[20]状态。Buttonoverflow-hidden属性使得这个动画效果仅限于按钮范围内显示,而标签上的z-10属性则确保文字能够始终显示在扩大的圆圈上方。

3>交货日期的序数后缀生成逻辑

getDeliveryDate()函数会计算出三天后的日期,并为其添加正确的序数后缀(如“th”、“nd”等):

const suffix = ["th", "st", "nd", "rd"][
  day % 10 > 3 ? 0 : (day % 100 - day % 10 !== 10 ? day % 10 : 0)
];

这种逻辑能够正确处理第11天、第12天和第13天的情况——在这些情况下,无论日期的个位数是多少,都会使用“th”作为后缀。这是序数格式化中常见的一个注意事项。

4>交货日期文本框上的suppressHydrationWarning功能

交货日期是在渲染时通过new Date()计算得出的;服务器在接收到请求时会重新计算这个日期,而客户端在数据加载完成时也会再次进行计算。

如果不同区域的时区设置存在差异,React会抛出“数据不一致”的警告。suppressHydrationWarning功能可以针对特定节点屏蔽这种警告,而不会影响到页面的其他部分。

实时预览:

f81584b2-a2ba-4233-a628-5daca018a1d9

快速参考表

卡片类型 标识符 使用场景
预览卡片 card-02 用于房产列表、酒店信息展示以及产品预览页面
分析卡片 card-05 包含各种指标数据的仪表盘组件
统计卡片 card-06 用于展示电子商务相关统计数据的信息框
产品变体卡片 card-17 用于产品页面,可让用户选择商品尺寸并加入购物车

要安装其中任何一项,只需在 CLI 命令中替换相应的标识符即可:

npx shadcn@latest add @shadcn-space/

关键概念总结

以下是本教程中四个示例所使用的 Tailwind、React 以及 TypeScript 的核心设计模式的汇总。

Tailwind 组合样式与悬停效果

当父元素上应用了 group 类时,其子元素可以通过 groupHover: 类来响应父元素的悬停状态。

对于嵌套的悬停效果范围,可以使用诸如 group/cardgroup/btn 这样的命名组合,并配合 group-hover/card:groupHover/btn: 来实现特定的样式效果。这样就能避免悬停样式影响到其他组件。

`cn()` 实用工具

来自 `<@/lib/utils>` 的 `cn()` 函数可以合并 Tailwind 中的类名字符串,处理条件性的类逻辑,并消除重复的辅助函数。每当需要使用条件性类样式时,都可以优先使用这个函数而非模板字面量。

`last:` 伪类

`last:` 伪类可用于选中某个组中的最后一个子元素。在统计信息卡片中,`last(border-e-0)` 可以去除最后一列的边框,而无需在 JavaScript 中进行索引判断。

CSS 逻辑属性

border-e 表示“内联方向的边界”,在左-to-right 的布局中指的是右侧,在右-to-left 的布局中则指左侧。使用 border-eps-pe- 这类逻辑属性,而非传统的 border-rpl-pr-,可以让组件默认具备适应不同语言环境的布局能力。

TypeScript 中带默认值的可选属性

在函数签名中直接为属性指定默认值(例如 `{ mainDashboard = mainDashboardData }: WidgetProps `),是一种简洁的实现方式。这种机制既能为组件提供合理的默认值,同时又保留了配置的灵活性。无论是用于演示、Storybook 还是实际的生产环境,并且配合真实的 API 数据使用,这种写法都是可行的。

在 Next.js 应用程序路由器中使用 `use client`

任何使用了 `useState`、浏览器 API 或仅适用于客户端的库(如 `@iconify/react`)的组件,都必须在文件顶部添加 `"use client"` 指令。如果不加这个指令,Next.js 应用程序路由器会尝试在服务器端渲染这些组件,从而导致错误。

抑制 hydration 警告

当服务器端渲染的值(例如当前日期或时间)由于时区差异与客户端渲染的值不同时,React 会发出 hydration 不匹配的警告。通过在特定的元素上添加 `suppressHydrationWarning`,就可以消除这个警告,而不会影响组件树的其余部分。

CSS涟漪动画效果

无需使用JavaScript,只需在位于overflow-hidden容器内的rounded-full元素上应用从scale-0scale-[N]的过渡效果,就能实现CSS涟漪动画效果。当鼠标悬停在该元素上时,该圆圈会扩大,但其边缘仍会被容器的边界所限制;标签文本则通过relative z-10样式位于其上方。

总结

在本教程中,你使用了shadcn/ui和Base UI的基本组件,构建了四个可供实际使用的卡片组件:

  1. 预览卡片:包含悬停时的图像动画效果、溢出内容的裁剪功能,以及用于排列标签的逻辑结构。

  2. 分析卡片:支持输入属性并设置默认值,通过cn()函数实现条件性的徽标颜色变化,同时还包含一个绝对定位的装饰性图像。

  3. 统计卡片:采用数据驱动的布局方式,弹性柱会根据屏幕尺寸自动调整大小,同时会使用last:规则自动去除最后一个元素的边框。

  4. 电商产品变体卡片:支持命名悬停组,拥有CSS涟漪效果按钮,能够对日期进行顺序格式化处理,同时还具备抑制显示警告信息的功能。

每个卡片只需通过一条CLI命令即可安装到项目中,这些代码完全归你所有,你可以根据自己的设计需求对其进行修改。

这里介绍的各种设计模式,无论是命名悬停组、支持默认值的输入属性,还是逻辑性的CSS样式设置,都不仅适用于卡片组件,你在使用shadcn/ui和Tailwind CSS构建其他UI组件时,也会发现它们非常有用。

资源链接

Comments are closed.