React+Shadcn UI希望清单【TodoList】

329 阅读3分钟

该项目用的是React+TS写的,是自学写的,写得不好见谅!然后里面有一个自封的BtnIcon组件,那个是我页面需要用的,相当于是我的草稿。和希望清单无关。

1、介绍

Shadcn UI:Shadcn UI不是一个UI库 或者 框架,它也是一个集合【可复用组件的集合】

  • 官网
https://ui.shadcn.com/
  • 中文网
https://www.shadcn.com.cn/docs/installation

2、安装

  • 安装
npm install -D tailwindcss postcss autoprefixer
​
npx tailwindcss init -p
  • 在 tsconfig.json 里面添加配置
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  }
}
  • 更新 vite.config.ts
npm i -D @types/node
import path from "path"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})
  • 运行 init 命令以设置您的项目:shadcn-ui
npx shadcn-ui@latest init
  • 配置components.json

1.png

Would you like to use TypeScript (recommended)? no / yes
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Where is your global CSS file? › › src/index.css
Do you want to use CSS variables for colors? › no / yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Are you using React Server Components? › no / yes (no)

3、使用

Shadcn UI 通过 npx 添加你需要用到的组件

  • 案列【按钮】

    • 首先 添加按钮
    npx shadcn-ui@latest add button
    

    添加完之后,在 src 的 components 文件夹下的 ui 文件夹里面就会生成一个 button.tsx

2.png

-   在需要的部分

```
//导入
import { Button } from "@/components/ui/button"
function App() {
​
  return (
    <>
      <div>
         {/*使用*/}
        <Button>Click me</Button>
      </div>
    </>
  )
}
​
export default Apps
```

4、希望清单

  • App.tsx
import React, { useState } from 'react';
import './App.css';
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
​
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { InputForm } from './InputForm';
import { ListType } from '@/types/ListType';
import { BtnIcon } from '@/btnIcon';
import logo from "@/assets/1.png";
​
function App() {
  const [data, setData] = useState<ListType[]>([
    {
      id: 1,
      plan: '学习TailwindCSS',
      date: '2024-08-12',
      status: 1,
    },
  ]);
​
  const addBtn = (newData: ListType) => {
    setData([...data, newData]);
  };
  const delBtn = (id: number) => {
    setData(data.map((item) =>
      item.id === id ? { ...item, status: 0 } : item
    ));
  };
​
  const selectsData = ['选择1', '选择2'];
  const handleClick = (value: string) => {
    console.log(value);
  }
​
  const handleSelect = (value: string) => {
    console.log('select',value);
  }
​
  return (
    <div className='w-screen h-screen flex justify-center'>
      <div className="tabs">
        <Tabs defaultValue="dataList" className="w-[400px]">
          <TabsList>
            <TabsTrigger value="btn">按钮</TabsTrigger>
            <TabsTrigger value="dataList">列表</TabsTrigger>
            <TabsTrigger value="addData">添加</TabsTrigger>
          </TabsList>
​
          {/* 按钮 (前端机器管理)*/}
          <TabsContent value="btn">
            <BtnIcon title='测试1' photo={logo} selects={selectsData} dataClick={handleClick} onSelect={handleSelect}></BtnIcon>
            <BtnIcon title='测试2' selects={selectsData} className='mt-[20px]' dataClick={handleClick} onSelect={handleSelect}></BtnIcon>
          </TabsContent>
​
          {/* 列表 */}
          <TabsContent value="dataList" className='w-[500px]'>
            <Table className='w-[500px]'>
              <TableHeader>
                <TableRow>
                  <TableHead className="w-[150px]">计划</TableHead>
                  <TableHead className='w-[150px]'>时间</TableHead>
                  <TableHead className='w-[100px]'>操作</TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {data.map((item) => (
                  item.status === 1 ? (
                    <TableRow key={item.id}>
                      <TableCell className="font-medium">{item.plan}</TableCell>
                      <TableCell>{item.date}</TableCell>
                      <TableCell>
                        <Button variant="destructive" onClick={() => delBtn(item.id)}>删除</Button>
                      </TableCell>
                    </TableRow>
                  ) : null
                ))}
              </TableBody>
            </Table>
          </TabsContent>
​
          {/* 添加 */}
          <TabsContent value="addData">
            <InputForm submitData={addBtn} />
          </TabsContent>
        </Tabs>
      </div>
    </div>
  );
}
​
export default App;
  • BtnIcon.tsx【组件封装,使用】
import React, { useState } from 'react';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
​
interface BtnIconProps {
    title: string;
    photo?: string;
    selects: string[];
    className?: string;
    dataClick?: (value: string) => void;
    onSelect?: (value: string) => void;
}
​
export function BtnIcon({ title, photo, selects, className, dataClick, onSelect }: BtnIconProps) {
    const handleClick = () => {
        if (dataClick) {
            dataClick(title);
        }
    }
​
    const handleSelect = (value: string) => {
        if (onSelect) {
            onSelect(value);
        }
    }
​
    const [isHovered, setIsHovered] = useState(false);
    return (
        <>
            <div className={className}>
                <div
                    onClick={handleClick}
                    className={`border w-[300px] h-[150px] flex-col justify-center rounded-lg ${isHovered ? 'bg-[#0479ffda] text-[#fff]' : 'bg-white'}`}
                >
                    <div className="
                        flex 
                        justify-center 
                        items-center 
                        w-[300px] h-[100px] 
                        border-b 
                        font-semibold 
                        text-[24px] 
                        hover:bg-[#0479ffda]
                        hover:rounded-lg
                        hover:text-[#fff] 
                        transition-all 
                        duration-300 
                        ease-in-out
                    ">
                        <div className='h-[50px] flex justify-center items-center'>
                            {photo && <img src={photo} alt="" className='w-[80px] h-[50px]' />}
                            <div>{title}</div>
                        </div>
                    </div>
                    <Select onValueChange={handleSelect}>
                        <SelectTrigger
                            className="w-[300px] h-[50px] bg-transparent border-none outline-none"
                            style={{ border: 'none', boxShadow: 'none' }}
                            onMouseEnter={() => setIsHovered(true)}
                            onMouseLeave={() => setIsHovered(false)}
                        >
                            <SelectValue placeholder="Please Select" />
                        </SelectTrigger>
                        <SelectContent>
                            {selects.map((item, index) => (
                                <SelectItem key={index} value={item}>{item}</SelectItem>
                            ))}
                        </SelectContent>
                    </Select>
                </div>
            </div>
        </>
    );
}
  • InputForm.tsx
import React from 'react';
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
​
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { ListType } from '@/types/ListType';
​
const FormSchema = z.object({
  plan: z.string(),
  date: z.string(),
});
​
interface InputFormProps {
  submitData: (data: ListType) => void;
}
​
export function InputForm({ submitData }: InputFormProps) {
  const form = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      plan: "",
      date: "",
    },
  });
​
  const onSubmit = (data: z.infer<typeof FormSchema>) => {
    const newData = { id: Date.now(), ...data, status: 1 };
    submitData(newData);
    form.reset();
  };
​
  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
        <FormField
          control={form.control}
          name="plan"
          render={({ field }) => (
            <FormItem>
              <FormLabel>目标</FormLabel>
              <FormControl>
                <Input placeholder="请输入..." {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="date"
          render={({ field }) => (
            <FormItem>
              <FormLabel>时间</FormLabel>
              <FormControl>
                <Input placeholder="请输入..." {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">提交</Button>
      </form>
    </Form>
  );
}
  • ListType.tsx

    数据类型

export type ListType = {
    id: number;
    plan: string;
    date: string;
    status: number;
};
  • 结果展示

1.png

2.png