首页 > 其他分享 >Shadcn UI 实战:打造可维护的企业级组件库

Shadcn UI 实战:打造可维护的企业级组件库

时间:2024-12-18 19:44:26浏览次数:4  
标签:foreground const -- primary 企业级 UI Shadcn 组件 ui

"我们真的需要自己写一套组件库吗?"上周的技术评审会上,我正在和团队讨论组件库的选型。作为一个快速发展的创业公司,我们既需要高质量的组件,又想保持灵活的定制能力。在对比了多个方案后,我们选择了 shadcn/ui 这个相对较新的解决方案。

说实话,最开始我对这个决定也有些担忧。毕竟相比 Ant Design 这样的成熟方案,shadcn/ui 的知名度确实不高。但经过一个月的实践,这个选择让我们收获了意外的惊喜。

为什么选择 shadcn/ui?

传统组件库给我们带来了一些困扰:

  • 样式难以深度定制
  • 打包体积大
  • 版本升级困难
  • 组件逻辑难以调整

就像租房和买房的选择一样,使用第三方组件库就像租房,虽然能快速入住,但想改造却处处受限。而 shadcn/ui 的方案,更像是买了一栋毛坯房,虽然需要自己装修,但能完全掌控每个细节。

项目实践

1. 初始化配置

首先,我们需要建立一个良好的组件开发基础:

// components.json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "default",
  "rsc": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "app/globals.css",
    "baseColor": "slate",
    "cssVariables": true
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}

// tailwind.config.js
const { fontFamily } = require('tailwindcss/defaultTheme')

/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: ['class'],
  content: ['app/**/*.{ts,tsx}', 'components/**/*.{ts,tsx}'],
  theme: {
    container: {
      center: true,
      padding: '2rem',
      screens: {
        '2xl': '1400px'
      }
    },
    extend: {
      colors: {
        border: 'hsl(var(--border))',
        input: 'hsl(var(--input))',
        ring: 'hsl(var(--ring))',
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))'
        }
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)'
      },
      fontFamily: {
        sans: ['var(--font-sans)', ...fontFamily.sans]
      }
    }
  }
}

2. 组件定制

shadcn/ui 最大的特点是它的组件是可以复制到项目中的。这让我们能够根据业务需求进行深度定制:

// components/ui/button.tsx
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'

// 扩展按钮变体
const buttonVariants = cva('inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', {
  variants: {
    variant: {
      default: 'bg-primary text-primary-foreground hover:bg-primary/90',
      destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
      outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
      secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
      ghost: 'hover:bg-accent hover:text-accent-foreground',
      link: 'underline-offset-4 hover:underline text-primary',
      // 添加自定义变体
      brand: 'bg-brand-500 text-white hover:bg-brand-600',
      success: 'bg-green-500 text-white hover:bg-green-600'
    },
    size: {
      default: 'h-10 px-4 py-2',
      sm: 'h-9 rounded-md px-3',
      lg: 'h-11 rounded-md px-8',
      icon: 'h-10 w-10'
    }
  },
  defaultVariants: {
    variant: 'default',
    size: 'default'
  }
})

// 扩展按钮属性
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
  asChild?: boolean
  loading?: boolean
  icon?: React.ReactNode
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, loading, icon, children, ...props }, ref) => {
  const Comp = asChild ? Slot : 'button'

  return (
    <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props}>
      {loading && <LoadingSpinner className='mr-2 h-4 w-4 animate-spin' />}
      {icon && <span className='mr-2'>{icon}</span>}
      {children}
    </Comp>
  )
})
Button.displayName = 'Button'

3. 主题定制

我们实现了一个灵活的主题切换系统:

// lib/themes.ts
export const themes = {
  light: {
    '--background': '0 0% 100%',
    '--foreground': '222.2 84% 4.9%',
    '--primary': '222.2 47.4% 11.2%',
    '--primary-foreground': '210 40% 98%'
    // ... 其他颜色变量
  },
  dark: {
    '--background': '222.2 84% 4.9%',
    '--foreground': '210 40% 98%',
    '--primary': '210 40% 98%',
    '--primary-foreground': '222.2 47.4% 11.2%'
    // ... 其他颜色变量
  },
  // 添加自定义主题
  brand: {
    '--background': '0 0% 100%',
    '--foreground': '222.2 84% 4.9%',
    '--primary': '220 90% 56%',
    '--primary-foreground': '210 40% 98%'
  }
}

// components/theme-provider.tsx
const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
  const [theme, setTheme] = useState('light')

  useEffect(() => {
    const root = document.documentElement
    const themeVars = themes[theme as keyof typeof themes]

    Object.entries(themeVars).forEach(([key, value]) => {
      root.style.setProperty(key, value)
    })
  }, [theme])

  return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>
}

4. 组件封装

基于 shadcn/ui 的基础组件,我们封装了一些业务组件:

// components/business/data-table.tsx
import { Table } from '@/components/ui/table'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useState } from 'react'

interface DataTableProps<T> {
  data: T[]
  columns: Column[]
  onEdit?: (record: T) => void
  onDelete?: (record: T) => void
}

export function DataTable<T>({ data, columns, onEdit, onDelete }: DataTableProps<T>) {
  const [searchText, setSearchText] = useState('')

  const filteredData = data.filter(record =>
    columns.some(column => {
      const value = record[column.key as keyof T]
      return String(value).toLowerCase().includes(searchText.toLowerCase())
    })
  )

  return (
    <div className='space-y-4'>
      <div className='flex justify-between'>
        <Input placeholder='搜索...' value={searchText} onChange={e => setSearchText(e.target.value)} className='max-w-sm' />
      </div>

      <Table>
        <Table.Header>
          {columns.map(column => (
            <Table.Column key={column.key}>{column.title}</Table.Column>
          ))}
          <Table.Column>操作</Table.Column>
        </Table.Header>
        <Table.Body>
          {filteredData.map((record, index) => (
            <Table.Row key={index}>
              {columns.map(column => (
                <Table.Cell key={column.key}>{record[column.key as keyof T]}</Table.Cell>
              ))}
              <Table.Cell>
                <div className='space-x-2'>
                  {onEdit && (
                    <Button size='sm' variant='outline' onClick={() => onEdit(record)}>
                      编辑
                    </Button>
                  )}
                  {onDelete && (
                    <Button size='sm' variant='destructive' onClick={() => onDelete(record)}>
                      删除
                    </Button>
                  )}
                </div>
              </Table.Cell>
            </Table.Row>
          ))}
        </Table.Body>
      </Table>
    </div>
  )
}

实践效果

经过一个月的使用,我们获得了显著的收益:

  1. 打包体积减少了 60%
  2. 组件定制更加灵活
  3. 开发效率提升
  4. 代码可维护性增强

最让我印象深刻的是一位同事说:"终于可以随心所欲地修改组件了,不用再为覆盖第三方样式发愁。"

经验总结

使用 shadcn/ui 的过程让我们学到:

  1. 组件库不一定要追求"拿来即用"
  2. 掌控组件源码比黑盒封装更有优势
  3. 样式系统的一致性很重要
  4. 渐进式地构建组件库是个好方法

就像装修房子,虽然前期投入较大,但最终得到的是一个完全符合需求的解决方案。

写在最后

shadcn/ui 给了我们一个全新的视角:组件库可以是一系列最佳实践的集合,而不仅仅是一个封装好的产品。正如那句话说的:"授人以鱼不如授人以渔",shadcn/ui 不仅给了我们组件,更教会了我们如何构建组件。

有什么问题欢迎在评论区讨论,让我们一起探讨组件库开发的更多可能!

如果觉得有帮助,别忘了点赞关注,我会继续分享更多实战经验~

标签:foreground,const,--,primary,企业级,UI,Shadcn,组件,ui
From: https://www.cnblogs.com/yuanyanglu/p/18615731

相关文章

  • 【Unity功能】动态锚点缩放平移UI(可用于缩放移动图片或者地图等)
    前言在UnityUGUI开发中,我们经常需要实现图片缩放功能,传统的缩放方式通常是以UI元素的中心点为基准进行缩放,这种方式在某些场景下可能不够直观,本文将介绍一种以鼠标位置为基准点的动态锚点缩放方案,让缩放效果更加自然和符合用户预期。一、效果演示二、制作过程简言该......
  • Vue - 萤石云监控 ezuikit 视频实例销毁方案,解决使用stop方法无法销毁EZUIKit实例或销
    前言这方面教程很少,本文提供详细解决方案。在vue2|vue3项目开发中,项目集成对接萤石监控摄像头如何销毁EZUIKit实例教程,解决页面存在多个实时监控画面视频情况下,关闭某一个监控依然有声音和占用浏览器内存问题,另外如果要管理的摄像头监控播放器很多会导致分页情况下......
  • dotnet9 MAUI + Vue 项目
    MAUI是dotnet的跨平台技术,支持windows平台、android平台、ios平台等。使用MAUI作为基础平台,在其上运行一个前端项目,比如Vue,可以同时享受开发效率与跨平台的好处。使用dotnet9后MAUI提供的组件HybridWebView,可以实现将前端项目嵌入到MAUI项目Page中的效果。支持C#与javascript的互......
  • [WPF UI] 为 AvalonDock 制作一套 Fluent UI 主题
    AvalonDock是我这些天在为自己项目做技术选型时发现的一个很好的开源项目,它是一个用于WPF的布局控件库,可以帮助我们实现类似VisualStudio的布局效果。因为它自带的一些样式我并不是很喜欢,我想要那种跟WinUI风格一样的样式。经过这几天的学习和尝试,我已经按照WinUI的样式......
  • IDEA 常用插件Material Theme UI: 为开发环境提供了主题和颜色方案
    自从Google推出MaterialDesign设计语言以来,它以简洁、直观和响应式的特点,迅速成为现代UI设计的风向标。MaterialThemeUI是一个将MaterialDesign美学带入集成开发环境(IDE)的插件,为开发者提供了一个美观且高效的工作环境。1MaterialThemeUI简介MaterialThemeUI插......
  • idea构建Build Project项目时一直卡在解析阶段解决办法
    可能是内存不足,修改以下三个地方1、help->EditCustomVMOptions-Xmx4096m2、file->settings->Build,Execution,Deployment->BuildTools->Maven->Importing的VMoptionsforimporter写入参数-Xmx4096m3、file->settings->Build,Execution,Deployment->Compiler的Sh......
  • 【重要】easygui库中所有函数简介及示例
    以下是用表格形式整理后的每个easygui函数的信息。请注意,由于某些项(如EgStore,__all__,__builtins__等)并不是easygui的函数,因此它们不会出现在表格中。只列出了与easygui函数相关的项。序号函数名简介简单用法示例1abouteasygui显示关于easygui的信息easygui.abo......
  • DevExpress offers a robust suite CRACK
    DevExpressoffersarobustsuiteCRACKDevExpressv24.2addsAI-poweredextensionsforadvanceddocumentediting,smartactions,andversatileAIchatcomponentsacrossplatforms.DevExpressoffersarobustsuiteofdevelopertoolsdesignedto......
  • Hongcow Builds A Nation 题解
    HongcowBuildsANation题解洛谷。Codeforces。题目描述给定一张\(n\)个点,\(m\)条边的无向图,有\(k\)个点是特殊点。每个连通块中都得保证无重边、无自环,且最多只有一个特殊点。求最多还能加多少条边,满足以上条件。思路简述首先考虑以下有\(n\)个点的完全图共有多......
  • # easygui中所有函数用法示例
    #easygui中所有函数用法示例'''注意事项文件对话框:filesavebox和fileopenbox示例中的filetypes参数可以指定文件类型过滤器,例如["*.txt"]只显示文本文件。运行这个脚本,你将看到easygui提供的各种对话框,并可以测试它们的功能。'''importeasygui#1、msgbox:显......