Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐹 refactor: upgrade forwardRef to useRef for React v19 #44

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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