Skip to content

Commit

Permalink
🐹 refactor: upgrade forwardRef to useRef for React v19 (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
pdsuwwz authored Jan 17, 2025
1 parent 200249f commit 8484173
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 145 deletions.
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![thanks](https://badgen.net/badge/thanks/♥/pink)](https://github.com/pdsuwwz)
[![License](https://img.shields.io/github/license/pdsuwwz/nextjs-nextra-starter?color=466fe8)](https://github.com/pdsuwwz/nextjs-nextra-starter/blob/main/LICENSE)

⚡️ 快速模板 Starter Template - Next.js + Nextra + TypeScript + TailwindCSS + Shadcn UI
⚡️ 快速模板 Starter Template - React v19 + Next.js + Nextra + TypeScript + TailwindCSS + Shadcn UI

[🚀 Live Demo 在线体验](https://nextjs-nextra-starter-green.vercel.app)

Expand All @@ -20,7 +20,7 @@

## 前置条件

- React 18.x
- React 19.x
- Node >= 18.12.x
- Pnpm 9.x
- **VS Code 插件 `dbaeumer.vscode-eslint` >= v3.0.5 (pre-release)**
Expand All @@ -31,8 +31,6 @@
![image](https://github.com/user-attachments/assets/7f4ade20-8364-4e25-a5fd-73e42ec7118c)
![image](https://github.com/user-attachments/assets/a0a07f3f-a457-4521-a45f-4c0f970044f6)



## 安装和运行

- 安装依赖
Expand All @@ -53,7 +51,7 @@ pnpm dev

### Shadcn 结构初始化

首次执行 `pnpm dlx shadcn-ui@latest init` 命令初始化 `Shadcn UI` 基本项目结构(如果尚未初始化)
首次执行 `pnpm dlx shadcn@latest init` 命令初始化 `Shadcn UI` 基本项目结构(如果尚未初始化)

💡 注意

Expand All @@ -66,13 +64,13 @@ pnpm dev
1. 使用 `Shadcn CLI` 添加组件:

```bash
pnpm dlx shadcn-ui@latest add <组件名>
pnpm dlx shadcn@latest add <组件名>
```

如添加 `<Alert />` 组件,执行以下命令即可,[详见文档](https://ui.shadcn.com/docs/components/alert#installation)

```bash
pnpm dlx shadcn-ui@latest add alert
pnpm dlx shadcn@latest add alert
```

2. 使用组件
Expand All @@ -88,7 +86,6 @@ export default function Home() {
You can add components and dependencies to your app using the cli.
</AlertDescription>
</Alert>

)
}
```
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export default antfu({
'react/no-useless-fragment': OFF,
'react/no-array-index-key': OFF,
'react-hooks/rules-of-hooks': OFF,
'react-refresh/only-export-components': OFF,
'react-dom/no-dangerously-set-innerhtml': OFF,

'unused-imports/no-unused-vars': WARN,
curly: [ERROR, 'multi-line', 'consistent'],
Expand Down
20 changes: 19 additions & 1 deletion src/components/HomepageHero/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'
import { FlipWords } from '@/components/ui/flip-words'
import { LinkPreview } from '@/components/ui/link-preview'
import { useLocale } from '@/hooks'
import clsx from 'clsx'
import Link from 'next/link'

interface Props {
Expand All @@ -13,7 +14,6 @@ export function SetupHero(props: Props) {

return (
<div className={styles.container}>
{/* <div className={styles.tilesBox}></div> */}
<div className={styles.content}>
<div className={styles.badgeContainer}>
<a
Expand All @@ -39,6 +39,24 @@ export function SetupHero(props: Props) {
{' '}
Template
</h1>

<p
className={clsx([
'bg-gradient-to-r from-yellow-400 via-orange-500 to-red-500 text-white shadow-lg',
'dark:bg-gradient-to-r dark:from-green-400 dark:via-teal-500 dark:to-cyan-500 dark:text-white',
'text-sm mt-2 inline-block px-3 py-1 rounded-lg',
'[&>span]:font-bold',
'animate-pulse',
'[animation-duration:2s]',
])}
dangerouslySetInnerHTML={{
__html: t('reactSupport', {
feature: `<span>React v19</span>`,
}),
}}
/>


<div className={styles.subtitle}>
Template made
{' '}
Expand Down
6 changes: 0 additions & 6 deletions src/components/HomepageHero/SetupHero.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@
position: relative;
}

.tilesBox {
position: absolute;
width: 100%;
height: 100%;
}

.content {
margin: 0 auto;
position: relative;
Expand Down
101 changes: 57 additions & 44 deletions src/components/ui/accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,70 @@ import * as React from 'react'

const Accordion = AccordionPrimitive.Root

const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn(
'border-b',
className,
)}
{...props}
/>
))
AccordionItem.displayName = 'AccordionItem'
const AccordionItem = ({
className,
...props
}: React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>) => {
const itemRef = React.useRef<React.ComponentRef<typeof AccordionPrimitive.Item>>(null)

const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
return (
<AccordionPrimitive.Item
ref={itemRef}
className={cn(
'flex flex-1 items-center justify-between py-7 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
'text-[18px] font-bold',
'border-b',
className,
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
/>
)
}
AccordionItem.displayName = 'AccordionItem'

const AccordionTrigger = ({
className,
children,
...props
}: React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>) => {
const itemRef = React.useRef<React.ComponentRef<typeof AccordionPrimitive.Trigger>>(null)
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={itemRef}
className={cn(
'flex flex-1 items-center justify-between py-7 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
'text-[18px] font-bold',
className,
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName

const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className={cn(
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
'text-[16px]',
)}
{...props}
>
<div className={cn('pb-6 pt-0', className)}>{children}</div>
</AccordionPrimitive.Content>
))
const AccordionContent = ({
className,
children,
...props
}: React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>) => {
const itemRef = React.useRef<React.ComponentRef<typeof AccordionPrimitive.Content>>(null)

return (
<AccordionPrimitive.Content
ref={itemRef}
className={cn(
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
'text-[16px]',
)}
{...props}
>
<div className={cn('pb-6 pt-0', className)}>{children}</div>
</AccordionPrimitive.Content>
)
}

AccordionContent.displayName = AccordionPrimitive.Content.displayName

Expand Down
74 changes: 43 additions & 31 deletions src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,53 @@ const alertVariants = cva(
},
)

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
const Alert = ({
className,
variant,
...props
}: React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>) => {
const itemRef = React.useRef<HTMLDivElement>(null)
return (
<div
ref={itemRef}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
}
Alert.displayName = 'Alert'

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
))
const AlertTitle = ({
className,
...props
}: React.HTMLAttributes<HTMLHeadingElement>) => {
const itemRef = React.useRef<HTMLParagraphElement>(null)

return (
<h5
ref={itemRef}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
)
}
AlertTitle.displayName = 'AlertTitle'

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
))
const AlertDescription = ({
className,
...props
}: React.HTMLAttributes<HTMLParagraphElement>) => {
const itemRef = React.useRef<HTMLParagraphElement>(null)

return (
<div
ref={itemRef}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
)
}
AlertDescription.displayName = 'AlertDescription'

export { Alert, AlertDescription, AlertTitle }
30 changes: 18 additions & 12 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,24 @@ export interface ButtonProps
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button'
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
},
)
const Button = ({
className,
variant,
size,
asChild = false,
...props
}: ButtonProps) => {
const Comp = asChild ? Slot : 'button'
const itemRef = React.useRef<HTMLButtonElement>(null)

return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={itemRef}
{...props}
/>
)
}
Button.displayName = 'Button'

export { Button, buttonVariants }
31 changes: 16 additions & 15 deletions src/components/ui/hover-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,22 @@ const HoverCard = HoverCardPrimitive.Root

const HoverCardTrigger = HoverCardPrimitive.Trigger

const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
))

const HoverCardContent = ({ className, align = 'center', sideOffset = 4, ...props }: React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>) => {
const itemRef = React.useRef<React.ComponentRef<typeof HoverCardPrimitive.Content>>(null)
return (
<HoverCardPrimitive.Content
ref={itemRef}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
)
}
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName

export { HoverCard, HoverCardContent, HoverCardTrigger }
Loading

0 comments on commit 8484173

Please sign in to comment.