尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

如何将 TanStack Router 接入 shadcn/ui 并解决弹窗动画兼容问题

如何将 TanStack Router 接入 shadcn/ui 并解决弹窗动画兼容问题 如何将 TanStack Router 接入 shadcn/ui 并解决弹窗动画兼容问题【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router在一个已有的 TanStack RouterReact项目里接入 shadcn/ui通常会碰到三类问题弹窗类组件Sheet、Dialog动画不生效、按钮等组件用to属性跳转时出现 TypeScript 报错、以及 shadcn/ui 样式与路由样式互相覆盖。本文按官方集成指南 integrate-shadcn-ui.md 给出完整操作路径安装配置 shadcn/ui、修复弹窗动画、创建类型安全的导航组件最后按文档的清单验证结果。指南标注的难度为 Intermediate预计耗时 30–45 分钟前提是已有一个 TanStack Router 项目。一、安装 shadcn/ui 并配置 components.json根据你的项目状态二选一方式 1新建项目直接用 TanStack Router 模板并带上 Tailwind 与 shadcn 附加项npx create-tsrouter-applatest my-app --template file-router --tailwind --add-ons shadcn方式 2在现有项目上接入运行 shadcn 官方初始化命令npx shadcnlatest init接着创建或更新根目录下的components.json按指南给出的 TanStack Router 兼容配置{ $schema: https://ui.shadcn.com/schema.json, style: default, rsc: false, tsx: true, tailwind: { config: tailwind.config.js, css: src/app/globals.css, baseColor: slate, cssVariables: true }, aliases: { components: /components, utils: /lib/utils } }其中tailwind.css指向你项目里全局样式文件的位置aliases决定 shadcn 组件的安装目录与工具函数路径需与项目的路径别名一致。最后安装最常用的一组组件npx shadcnlatest add button npx shadcnlatest add navigation-menu npx shadcnlatest add sheet npx shadcnlatest add dialog二、解决弹窗动画兼容问题portal 根节点 受控组件动画不生效的修复分两步先保证 portal 有落点再让 Sheet/Dialog 变为受控组件。1. 在根路由中放好内容容器与 portal 根节点更新根路由示例文件为src/routes/__root.tsx把Outlet /包进一个内容容器并额外放一个空的#portal-root节点供 overlay 类组件挂载// src/routes/__root.tsx import { createRootRoute, Outlet } from tanstack/react-router import { TanStackRouterDevtools } from tanstack/react-router-devtools export const Route createRootRoute({ component: () ( {/* Main content wrapper */} div idroot-content Outlet / /div {/* Portal root for overlays */} div idportal-root/div TanStackRouterDevtools / / ), })指南在排查章节也给出了同样结论如果动画组件不动作先确认index.html或根组件里存在div idportal-root/div。2. 创建受控的 RouterSheetshadcn/ui 的 Sheet 在路由环境下容易出现动画问题指南的解法是包一层自己维护open状态的受控组件// src/components/ui/router-sheet.tsx import * as React from react import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from /components/ui/sheet interface RouterSheetProps { children: React.ReactNode trigger: React.ReactNode title: string description?: string onOpenChange?: (open: boolean) void } export function RouterSheet({ children, trigger, title, description, onOpenChange, }: RouterSheetProps) { const [open, setOpen] React.useState(false) const handleOpenChange (newOpen: boolean) { setOpen(newOpen) onOpenChange?.(newOpen) } return ( Sheet open{open} onOpenChange{handleOpenChange} SheetTrigger asChild{trigger}/SheetTrigger SheetContent SheetHeader SheetTitle{title}/SheetTitle {description SheetDescription{description}/SheetDescription} /SheetHeader div classNamemt-4{children}/div /SheetContent /Sheet ) }3. 创建受控的 RouterDialogDialog 同理。注意这个实现支持受控/非受控两种用法传了open就用外部状态不传就回落到内部useState// src/components/ui/router-dialog.tsx import * as React from react import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from /components/ui/dialog interface RouterDialogProps { children: React.ReactNode trigger: React.ReactNode title: string description?: string open?: boolean onOpenChange?: (open: boolean) void } export function RouterDialog({ children, trigger, title, description, open: controlledOpen, onOpenChange, }: RouterDialogProps) { const [internalOpen, setInternalOpen] React.useState(false) const open controlledOpen ?? internalOpen const setOpen onOpenChange ?? setInternalOpen return ( Dialog open{open} onOpenChange{setOpen} DialogTrigger asChild{trigger}/DialogTrigger DialogContent DialogHeader DialogTitle{title}/DialogTitle {description DialogDescription{description}/DialogDescription} /DialogHeader div classNamemt-4{children}/div /DialogContent /Dialog ) }排查要点动画仍不正常时指南给出的另两个检查项是——确认 CSS 导入顺序import tailwindcss/base、import tailwindcss/components、import tailwindcss/utilities要排在自定义样式之前复杂动画场景改为受控写法即自己持有const [open, setOpen] useState(false)以Sheet open{open} onOpenChange{setOpen}形式使用而不是非受控。三、创建类型安全的导航组件用 createLink 解决按钮的 TypeScript 报错直接在 shadcn/ui 的Button上写to会得到类型错误。官方 Custom Link 指南 说明createLink可以基于任意宿主组件创建一个与Link拥有相同类型参数和类型安全的组件实现见 link.tsx。按指南封装一个RouterButton// src/components/ui/router-button.tsx import { createLink } from tanstack/react-router import { Button, type ButtonProps } from /components/ui/button import { forwardRef } from react // Create a router-compatible Button export const RouterButton createLink( forwardRefHTMLButtonElement, ButtonProps((props, ref) { return Button ref{ref} {...props} / }), )用 useMatchRoute 给导航菜单做高亮shadcn/ui 的 NavigationMenu 本身不感知路由状态。指南用useMatchRoute拿到matchRoute函数配合fuzzy选项做前缀匹配/posts会匹配/posts/123这类子路径语义见 useMatchRoute API 与 MatchRouteOptions 中fuzzy的说明// src/components/navigation/main-nav.tsx import { Link, useMatchRoute } from tanstack/react-router import { cn } from /lib/utils import { NavigationMenu, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, navigationMenuTriggerStyle, } from /components/ui/navigation-menu interface NavItem { to: string label: string exact?: boolean } interface MainNavProps { items: NavItem[] className?: string } export function MainNav({ items, className }: MainNavProps) { const matchRoute useMatchRoute() return ( NavigationMenu className{className} NavigationMenuList {items.map((item) { const isActive matchRoute({ to: item.to, fuzzy: !item.exact }) return ( NavigationMenuItem key{item.to} Link to{item.to} className{cn( navigationMenuTriggerStyle(), isActive bg-accent text-accent-foreground font-medium, )} {item.label} /Link /NavigationMenuItem ) })} /NavigationMenuList /NavigationMenu ) }四、组合使用与结果验证指南给出一个组合页面示例展示导航高亮、RouterButton跳转、RouterSheet弹窗三者同页工作// src/routes/posts/index.tsx import { createFileRoute } from tanstack/react-router import { MainNav } from /components/navigation/main-nav import { RouterButton } from /components/ui/router-button import { RouterSheet } from /components/ui/router-sheet import { Button } from /components/ui/button export const Route createFileRoute(/posts/)({ component: PostsPage, }) const navItems [ { to: /, label: Home }, { to: /posts, label: Posts, exact: true }, { to: /about, label: About }, ] function PostsPage() { return ( div classNamecontainer mx-auto p-4 {/* Navigation with active states */} MainNav items{navItems} classNamemb-8 / div classNameflex items-center justify-between mb-6 h1 classNametext-3xl font-boldPosts/h1 {/* Router-compatible button */} RouterButton to/posts/new variantdefault Create Post /RouterButton /div {/* Sheet with proper animations */} RouterSheet trigger{Button variantoutlineOpen Menu/Button} titleNavigation Menu descriptionNavigate through your posts div classNamespace-y-4 pThis sheet animates correctly with TanStack Router!/p RouterButton to/posts/new variantdefault classNamew-full Create New Post /RouterButton /div /RouterSheet /div ) }验证方式以指南的 Production Checklist 为准逐项确认样式所有 shadcn/ui 组件正常渲染路由切换时动画正常CSS 冲突已解决如有响应式布局正常。功能导航组件随路由状态联动激活态正确反映TypeScript 编译成功所有 Sheet、Dialog、Modal 动画正确。性能tree shaking 生效、bundle 体积正常动画在低端设备上表现可接受。五、样式冲突与暗色模式的已知问题如果 shadcn/ui 样式与路由或自定义样式冲突指南给了两条处理路径一是用 CSS layers 分层layer base, components, utilities;声明后把 shadcn 基础样式放base、组件样式放components二是为路由相关样式提高特异性例如Button classNamerouter-active:bg-primary router-active:text-primary-foreground。另有一个独立已知问题暗色模式在路由切换后可能失效指南的解法是正确配置 theme provider文档中给出了完整的ThemeProvider实现读写ui-themestorage key 并给document.documentElement切换light/darkclass。如果你的项目没有暗色模式需求可以跳过。完成上述步骤并通过清单验证后接入即完成后续要补充更多 shadcn/ui 组件时继续用npx shadcnlatest add 组件名安装弹窗类组件统一走RouterSheet/RouterDialog这一层受控封装即可。【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表