This commit is contained in:
zhangkai
2024-06-05 14:27:06 +08:00
commit b825dcd4d5
730 changed files with 100244 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"
import { cname } from "../utils"
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={cname("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
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}
className={cname(
"flex flex-1 items-center py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-90",
className
)}
{...props}
>
<ChevronRightIcon color="#111" className="mx-2 h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
{children}
</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="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cname("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,140 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { buttonVariants } from "@/components/ui/button"
import { cname } from "../utils"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cname(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cname(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-sm translate-x-[-50%] translate-y-[-50%] gap-10 border bg-background-prompt p-3 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cname(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cname(
"flex justify-center flex-col-reverse sm:flex-row sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cname("text-lg font-semibold text-center", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cname("text-sm text-muted-foreground text-center text-prompt-description", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cname(buttonVariants({variant: "destructive"} ), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cname(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}

View File

@@ -0,0 +1,94 @@
import { Button } from "@/components/ui/button"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "."
import ReactDOM from "react-dom"
import i18next from "i18next"
import { useRef, useState } from "react"
import { TipIcon } from "@/components/bs-icons/tip"
import { Cross2Icon } from "@radix-ui/react-icons"
interface ConfirmParams {
title?: string
desc: string | React.ReactNode
canelTxt?: string
okTxt?: string
showClose?: boolean
onClose?: () => void
onCancel?: () => void
onOk?: (next) => void
}
let openFn = (_: ConfirmParams) => { }
function ConfirmWrapper() {
const [open, setOpen] = useState(false)
const paramRef = useRef(null)
openFn = (params: ConfirmParams) => {
paramRef.current = params
setOpen(true)
}
const close = () => {
paramRef.current?.onClose?.()
setOpen(false)
}
const handleCancelClick = () => {
paramRef.current?.onCancel?.()
close()
}
const handleOkClick = () => {
paramRef.current?.onOk
? paramRef.current?.onOk?.(close)
: close()
}
if (!paramRef.current) return null
const { title, desc, okTxt, canelTxt, showClose = true } = paramRef.current
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogContent>
<AlertDialogHeader className="relative">
<div><TipIcon /></div>
{showClose && <Cross2Icon onClick={close} className="absolute right-0 top-[-0.5rem] cursor-pointer text-gray-400 hover:text-gray-600"></Cross2Icon>}
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>
{desc}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleCancelClick} className="px-11">{canelTxt}</AlertDialogCancel>
<AlertDialogAction onClick={handleOkClick} className="px-11">{okTxt}</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
(function () {
// 挂载组件
let el = document.getElementById('#confirm-wrap');
if (!el) {
el = document.createElement('div')
el.id = 'confirm-wrap'
document.body.append(el)
}
ReactDOM.render(<ConfirmWrapper />, el);
})();
const bsConfirm = (params: ConfirmParams) => {
const resource = i18next.getResourceBundle(i18next.language, 'bs')
openFn({
title: resource.prompt,
canelTxt: resource.cancel,
okTxt: resource.confirmButton,
...params,
})
}
export { bsConfirm }

View File

@@ -0,0 +1,35 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cname } from "../utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> { }
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cname(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,45 @@
import { CaretDownIcon } from "@radix-ui/react-icons";
import { Button } from ".";
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../tooltip";
export default function ActionButton({
children,
className = '',
dropDown = null,
align = 'center',
buttonTipContent = null,
delayDuration = 700,
variant = "default",
...props
}) {
return <div className="flex items-center">
<>
{buttonTipContent ? <TooltipProvider>
<Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild>
<Button variant={variant} className={`rounded-r-none ${className}`} {...props}>{children}</Button>
</TooltipTrigger>
<TooltipContent className="bg-[#fff] text-gray-800">
{buttonTipContent}
</TooltipContent>
</Tooltip>
</TooltipProvider> :
<Button variant={variant} className={`rounded-r-none ${className}`} {...props}>{children}</Button>
}
</>
<Popover>
<PopoverTrigger asChild>
<Button
size="icon"
variant={variant}
className="rounded-l-none ml-[1px] [&[data-state=open]>svg]:rotate-180"
><CaretDownIcon /></Button>
</PopoverTrigger>
<PopoverContent className="w-80 p-0" align={align}>
{dropDown}
</PopoverContent>
</Popover>
</div>
};

View File

@@ -0,0 +1,103 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cname } from "../utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary no-underline hover:underline",
black: "bg-[#111] text-primary-foreground shadow hover:bg-[#48494d]"
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cname(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
const ButtonNumber = React.forwardRef<HTMLButtonElement, {
className?: string,
defaultValue?: number,
value?: number,
max?: number,
min?: number,
step?: number,
size?: "default" | "sm" | "lg" | "icon",
onChange?: (value: number) => void
}>(({ className, value: userValue, defaultValue, max = 100, min = 0, step = 1, size = 'sm', onChange }, ref) => {
if (max <= min) {
throw new Error('max must be greater than min');
}
const [value, setValue] = React.useState(defaultValue)
React.useEffect(() => {
setValue(userValue)
}, [userValue])
const getDecimalCount = (value: number) => {
return (String(value).split('.')[1] || '').length;
}
const roundValue = (value: number, decimalCount: number) => {
const rounder = Math.pow(10, decimalCount);
return Math.round(value * rounder) / rounder;
}
const valueAdd = () => {
const sum = roundValue(value + step, getDecimalCount(step))
const updateValue = sum > max ? max : sum
setValue(updateValue)
onChange?.(updateValue)
}
const valueReduce = () => {
const sum = roundValue(value - step, getDecimalCount(step))
const updateValue = sum < min ? min : sum
setValue(updateValue)
onChange?.(updateValue)
}
return (<div className={cname("flex items-center border input-border bg-gray-50 rounded-md", className)}>
<Button variant="ghost" size={size} disabled={value === min} onClick={valueReduce}>-</Button>
<span className="min-w-10 block text-center">{value}</span>
<Button variant="ghost" size={size} disabled={value === max} onClick={valueAdd}>+</Button>
</div>)
}
)
ButtonNumber.displayName = "ButtonNumber"
export { Button, ButtonNumber, buttonVariants }

View File

@@ -0,0 +1,75 @@
import * as React from "react"
import { cname } from "../utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cname(
"rounded-md bg-card text-card-foreground border border-transparent hover:shadow-[0_8px_16px_0px_rgba(40,47,84,0.15)] hover:border-[#EDF0F4] relative hover:z-10",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cname("flex flex-col space-y-1.5 p-5 pb-4", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cname("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cname("text-sm text-muted-foreground ", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cname("p-5 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cname("flex items-center p-5 pb-4 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,29 @@
"use client";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { Check } from "lucide-react";
import * as React from "react";
import { cname } from "../utils";
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cname(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cname("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };

View File

@@ -0,0 +1,15 @@
import { useState } from "react";
import { Dialog, DialogTrigger } from ".";
// 强制刷新children的 dialog 组件
export default function DialogForceUpdate({ children, trigger }) {
const [open, setOpen] = useState(false);
return <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
{trigger}
</DialogTrigger>
{open ? children : null}
</Dialog>
};

View File

@@ -0,0 +1,113 @@
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import * as React from "react"
import { cname } from "../utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cname(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cname(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-5 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cname(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cname(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cname(
"text-base font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cname("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog, DialogClose,
DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger
}

View File

@@ -0,0 +1,62 @@
import { PenLine } from "lucide-react";
import { useContext, useRef, useState } from "react";
import { alertContext } from "../../../contexts/alertContext";
export default function EditLabel({ str, rule, children, onChange }) {
const [edit, setEdit] = useState(false)
const inputRef = useRef(null)
const { setErrorData } = useContext(alertContext);
const handleChange = () => {
const value = inputRef.current.value
if (rule.length) {
const errors = []
rule.forEach(r => {
if (r.pattern) {
if (!r.pattern.test(value)) {
errors.push(r.message)
}
}
if (r.validator) {
if (!r.validator(value)) {
errors.push(r.message)
}
}
})
if (errors.length) return setErrorData({
title: "",
list: errors,
});
}
setEdit(false)
onChange(value)
}
if (edit) return <div className="">
<input
type="text"
ref={inputRef}
defaultValue={str}
onKeyDown={(e) => {
e.key === 'Enter' && handleChange();
e.code === 'Space' && e.preventDefault();
}}
onBlur={handleChange}
className="flex h-6 w-full rounded-xl border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
/>
</div>
return <div className="flex items-center text-gray-900 dark:text-gray-300 group">
{children(inputRef.current?.value || str)}
<button
className="hidden transition-all group-hover:block"
// title={t('flow.editAlias')}
onClick={() => setEdit(true)}
>
<PenLine size={18} className="ml-2 cursor-pointer" />
</button>
</div >
};

View File

@@ -0,0 +1,147 @@
import * as React from "react"
import { cname } from "../utils"
import { SearchIcon } from "../../bs-icons/search"
import { generateUUID } from "../utils"
import { MinusCircledIcon } from "@radix-ui/react-icons"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> { }
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cname(
"flex h-9 w-full rounded-md border border-input bg-[#FAFBFC] px-3 py-1 text-sm text-[#111] shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
const SearchInput = React.forwardRef<HTMLInputElement, InputProps & { inputClassName?: string, iconClassName?: string }>(
({ className, inputClassName, iconClassName, ...props }, ref) => {
return <div className={cname("relative", className)}>
<SearchIcon className={cname("h-5 w-5 absolute left-2 top-2", iconClassName)} />
<Input type="text" ref={ref} className={cname("pl-8 bg-search-input", inputClassName)} {...props}></Input>
</div>
}
)
SearchInput.displayName = "SearchInput"
/**
* 多行文本
*/
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cname(
"flex min-h-[80px] w-full rounded-md border border-input bg-[#FAFBFC] px-3 py-2 text-sm text-[#111] shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
/**
* input list
*/
const InputList = React.forwardRef<HTMLDivElement, InputProps & {
rules: any[],
value?: string[],
inputClassName?: string,
defaultValue?: string[],
onChange?: (values: string[]) => void
}>(
({ rules, className, inputClassName, value = [], defaultValue = [], ...props }, ref) => {
// 初始化 inputs 状态,为每个值分配唯一 ID
const [inputs, setInputs] = React.useState(() =>
value.map(val => ({ id: generateUUID(8), value: val }))
);
React.useEffect(() => {
// 仅为新增的值分配新的 ID
const updatedInputs = value.map((val, index) => {
return inputs[index] && inputs[index].value === val
? inputs[index] // 如果当前输入项与外部值相同,则保持不变
: { id: generateUUID(8), value: val }; // 否则,创建新的输入项
});
setInputs(updatedInputs);
}, [value]); // 依赖项中包含 value确保外部 value 更新时同步更新
const handleChange = (newValue, id, index) => {
let newInputs = inputs.map(input =>
input.id === id ? { ...input, value: newValue } : input
);
// push
if (index === newInputs.length - 1) {
newInputs = ([...newInputs, { id: generateUUID(8), value: '' }]);
}
setInputs(newInputs);
props.onChange(newInputs.map(input => input.value));
};
// delete input
const handleRemoveInput = (id) => {
const newInputs = inputs.filter(input => input.id !== id);
setInputs(newInputs);
props.onChange(newInputs.map(input => input.value));
};
return <div className={cname('', className)}>
{
inputs.map((item, index) => (
<div className="relative mt-2">
<Input
key={item.id}
defaultValue={item.value}
className={cname('pr-8', inputClassName)}
placeholder={props.placeholder || ''}
onChange={(e) => handleChange(e.target.value, item.id, index)}
onInput={(e) => {
rules.some(rule => {
if (rule.maxLength && e.target.value.length > rule.maxLength) {
e.target.nextSibling.textContent = rule.message;
e.target.nextSibling.style.display = '';
return true;
}
e.target.nextSibling.style.display = 'none';
})
}}
// onFocus={(e) => {
// if (e.target.value && index === inputs.length - 1) {
// setInputs([...inputs, { id: generateUUID(8), value: '' }]);
// }
// }}
></Input>
<p className="text-sm text-red-500" style={{ display: 'none' }}></p>
{index !== inputs.length - 1 && <MinusCircledIcon onClick={(e) => {
e.target.previousSibling.style.display = 'none';
handleRemoveInput(item.id)
}} className="absolute top-2.5 right-2 text-gray-500 hover:text-gray-700 cursor-pointer" />}
</div>
))
}
</div>
}
)
export { Input, SearchInput, Textarea, InputList }

View File

@@ -0,0 +1,31 @@
import { useState } from "react"
import { Input } from "."
export default function TextInput({
type = 'doubleclick',
value,
onChange = (val) => { },
onSave = (val) => { },
...props }) {
const [edit, setEdit] = useState(false)
if (edit) return <Input defaultValue={value} {...props}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
onSave(event.target.value)
setEdit(false)
}
}}
onBlur={e => {
onSave(e.target.value)
setEdit(false)
}}
onChange={onChange}
></Input>
return <p
className="text-sm px-3 py-1 border border-transparent w-full overflow-hidden text-ellipsis"
onDoubleClick={() => setEdit(true)} onMouseOver={() => type === 'hover' && setEdit(true)}>{value}</p>
};

View File

@@ -0,0 +1,25 @@
"use client";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cname } from "../utils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
);
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cname(labelVariants(), className)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };

View File

@@ -0,0 +1,127 @@
import { DoubleArrowLeftIcon, DoubleArrowRightIcon } from '@radix-ui/react-icons';
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from './index';
interface IProps {
/** 当前页码 */
page: number,
/** limit */
pageSize: number,
/** item total */
total: number,
/** 最多同时显示 item 数 */
maxVisiblePages?: number,
onChange?: (p: number) => void,
className?: string
}
const AutoPagination = ({ page, pageSize, total, maxVisiblePages = 5, className, onChange }: IProps) => {
const totalPages = Math.ceil(total / pageSize);
const handlePageChange = (newPage) => {
if (newPage >= 1 && newPage <= totalPages && newPage !== page) {
onChange?.(newPage);
}
};
const renderPaginationItems = () => {
const items = [];
// If total pages are more than maxVisiblePages, show at most maxVisiblePages pages
const startPage = Math.max(1, page - Math.floor(maxVisiblePages / 2));
const endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
// page 0
if (page !== 1) {
items.push(
<PaginationItem key="start">
<PaginationLink href="#" onClick={() => handlePageChange(1)} >
<DoubleArrowLeftIcon />
</PaginationLink>
</PaginationItem>
);
}
// Previous Button
items.push(
<PaginationItem key="previous">
<PaginationPrevious href="#"
className={page === startPage && 'text-gray-400'}
onClick={() => handlePageChange(page - 1)} />
</PaginationItem>
);
// Page Buttons
if (totalPages <= maxVisiblePages) {
// If total pages are less than or equal to maxVisiblePages, show all pages
for (let i = 1; i <= totalPages; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink href="#"
className={page === i ? 'font-bold' : 'text-gray-500'}
onClick={() => handlePageChange(i)} isActive={i === page}>
{i}
</PaginationLink>
</PaginationItem>
);
}
} else {
// if (startPage > 1) {
// // Display ellipsis if there are pages before the startPage
// items.push(
// <PaginationItem key="startEllipsis">
// <PaginationEllipsis />
// </PaginationItem>
// );
// }
for (let i = startPage; i <= endPage; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink href="#"
className={page === i ? 'font-bold' : 'text-gray-500'}
onClick={() => handlePageChange(i)} isActive={i === page}>
{i}
</PaginationLink>
</PaginationItem>
);
}
// if (endPage < totalPages) {
// // Display ellipsis if there are pages after the endPage
// items.push(
// <PaginationItem key="endEllipsis">
// <PaginationEllipsis />
// </PaginationItem>
// );
// }
}
// Next Button
items.push(
<PaginationItem key="next">
<PaginationNext href="#"
className={page === endPage && 'text-gray-400'}
onClick={() => handlePageChange(page + 1)} />
</PaginationItem>
);
// page last
if (page !== totalPages) {
items.push(
<PaginationItem key="end">
<PaginationLink href="#" onClick={() => handlePageChange(totalPages)} >
<DoubleArrowRightIcon />
</PaginationLink>
</PaginationItem>
);
}
return items;
};
return (
<Pagination className={className}>
<PaginationContent>{renderPaginationItems()}</PaginationContent>
</Pagination>
);
};
export default AutoPagination;

View File

@@ -0,0 +1,124 @@
import * as React from "react"
import {
ChevronLeftIcon,
ChevronRightIcon,
DotsHorizontalIcon,
} from "@radix-ui/react-icons"
import { ButtonProps, buttonVariants } from "../button"
import { cname } from "../utils"
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cname("mx-auto flex w-full justify-center", className)}
{...props}
/>
)
Pagination.displayName = "Pagination"
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cname("flex flex-row items-center gap-1", className)}
{...props}
/>
))
PaginationContent.displayName = "PaginationContent"
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cname("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">
const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cname(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
"text-gray-950",
"dark:text-[#585858]",
isActive && "dark:bg-[#34353A] dark:text-[#F2F2F2]", // 暗黑模式设计
className
)}
{...props}
/>
)
PaginationLink.displayName = "PaginationLink"
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cname("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeftIcon className="h-4 w-4" />
{/* <span>Previous</span> */}
</PaginationLink>
)
PaginationPrevious.displayName = "PaginationPrevious"
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cname("gap-1 pr-2.5", className)}
{...props}
>
{/* <span>Next</span> */}
<ChevronRightIcon className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = "PaginationNext"
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cname("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<DotsHorizontalIcon className="h-4 w-4" />
{/* <span className="sr-only">More pages</span> */}
</span>
)
PaginationEllipsis.displayName = "PaginationEllipsis"
export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis
}

View File

@@ -0,0 +1,34 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cname } from "../utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverClose = PopoverPrimitive.Close
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cname(
"z-50 w-72 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}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose }

View File

@@ -0,0 +1,44 @@
"use client"
import * as React from "react"
import { CheckIcon } from "@radix-ui/react-icons"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { cname } from "../utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cname("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cname(
"aspect-square h-4 w-4 rounded-full border-2 border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<span className="w-1.5 h-1.5 bg-primary rounded-full"></span>
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }

View File

@@ -0,0 +1,15 @@
import React from "react";
import { cname } from "../utils";
export default function RadioCard({ calssName = '', checked, title, description = '' }) {
return <div className={cname('w-96 border rounded-sm flex gap-2 p-4 cursor-pointer transition-all hover:border-primary/50', calssName, checked && 'border-primary bg-primary/10')}>
<div className={`bg-[#fff] border border-gray-400 rounded-full w-5 h-5 min-w-5 ${checked && 'bg-primary flex justify-center items-center'}`}>
{checked && <div className="w-2 h-2 bg-gray-50 rounded-full" />}
</div>
<div>
<div className="text-sm font-medium leading-none mt-0.5 mb-2">{title}</div>
<div className="text-sm text-muted-foreground">{description}</div>
</div>
</div>
};

View File

@@ -0,0 +1,157 @@
"use client"
import {
CaretDownIcon,
CheckIcon,
ChevronDownIcon,
ChevronUpIcon
} from "@radix-ui/react-icons"
import * as SelectPrimitive from "@radix-ui/react-select"
import * as React from "react"
import { cname } from "../utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cname(
"group flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-[#fcfdff] px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<CaretDownIcon className="h-5 w-5 min-w-5 opacity-80 group-data-[state=open]:rotate-180" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cname(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cname(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, headNode = null, footerNode = null, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cname(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-[#DEE3EF] bg-popover text-popover-foreground shadow-md 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",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
{headNode}
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cname(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
{footerNode}
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cname("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cname(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-[#EBF0FF] focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cname("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue
}

View File

@@ -0,0 +1,133 @@
import { CheckIcon, Cross1Icon } from "@radix-ui/react-icons"
import React, { useEffect, useRef } from "react"
import { Select, SelectContent, SelectItem, SelectTrigger } from "."
import { Badge } from "../badge"
import { SearchInput } from "../input"
const MultiItem = ({ active, children, value, onClick }) => {
return <div key={value}
className={`relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 mb-1 text-sm outline-none hover:bg-[#EBF0FF] hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 break-all
${active && 'bg-[#EBF0FF]'}`}
onClick={() => { onClick(value) }}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
{active && <CheckIcon className="h-4 w-4"></CheckIcon>}
</span>
{children}
</div>
}
interface IProps {
className?: string,
options: { label: string, value: string }[],
value?: string[],
defaultValue?: string[],
children?: React.ReactNode,
placeholder?: string,
searchPlaceholder?: string,
lockedValues?: string[],
onChange?: (value: string[]) => void
}
// 临时用 andt 设计方案封装组件
const MultiSelect = ({
className,
value = [],
defaultValue = [],
options = [],
children = null,
placeholder = '',
searchPlaceholder = '',
lockedValues = [],
onChange, ...props
}: IProps) => {
const [values, setValues] = React.useState(defaultValue)
const [optionFilter, setOptionFilter] = React.useState(options)
const inputRef = useRef(null)
useEffect(() => {
setValues(value)
}, [value])
useEffect(() => {
setOptionFilter(options)
if (inputRef.current) {
inputRef.current.value = ''
}
}, [options])
// delete
const handleDelete = (value: string) => {
const newValues = values.filter((item) => {
return item !== value
})
setValues(newValues)
onChange?.(newValues)
}
// add
const handleSwitch = (value: string) => {
if (lockedValues.includes(value)) {
return
}
if (values.includes(value)) {
const newValues = values.filter((item) => {
return item !== value
})
setValues(newValues)
onChange?.(newValues)
} else {
const _newValues = [...values, value]
setValues(_newValues)
onChange?.(_newValues)
}
}
// search
const handleSearch = (e) => {
const newValues = options.filter((item) => {
return item.label.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1
})
setOptionFilter(newValues)
}
return <Select {...props} required onOpenChange={(e) => !e && setOptionFilter(options)}>
<SelectTrigger className="mt-2 h-auto">
{
values.length
? <div className="flex flex-wrap">
{
options.filter(option => values.includes(option.value)).map(option =>
<Badge onPointerDown={(e) => e.stopPropagation()} key={option.value} className="flex whitespace-normal items-center gap-1 select-none bg-primary/20 text-primary hover:bg-primary/15 m-[2px]">
{option.label}
{lockedValues.includes(option.value) || <Cross1Icon className="h-3 w-3" onClick={() => handleDelete(option.value)}></Cross1Icon>}
</Badge>
)
}
</div>
: placeholder
}
</SelectTrigger>
<SelectContent className={className}
headNode={
<div className="p-2">
<SearchInput ref={inputRef} inputClassName="h-8" placeholder={searchPlaceholder} onChange={handleSearch} iconClassName="w-4 h-4" />
</div>
}
footerNode={children}
>
<div className="mt-2">
{
optionFilter.map((item, index) => (
<MultiItem active={values.includes(item.value)} value={item.value} onClick={handleSwitch}>{item.label}</MultiItem>
))
}
</div>
</SelectContent>
</Select>
}
MultiSelect.displayName = 'MultiSelect'
export default MultiSelect

View File

@@ -0,0 +1,140 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import { cva, type VariantProps } from "class-variance-authority"
import { cname } from "../utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cname(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
onClick={e => e.stopPropagation()}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> { }
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cname(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cname(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cname(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cname("text-lg font-semibold text-foreground mb-2", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cname("text-sm text-muted-foreground", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -0,0 +1,14 @@
import { cname } from "../utils";
export default function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cname("animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
)
};

View File

@@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cname } from "../utils"
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cname(
"relative flex w-full touch-none select-none items-center",
className
)}
{...props}
>
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
))
Slider.displayName = SliderPrimitive.Root.displayName
export { Slider }

View File

@@ -0,0 +1,30 @@
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cname } from "../utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, texts = null, ...props }, ref) => (
<SwitchPrimitives.Root
className={cname(
"group peer relative inline-flex h-5 min-w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cname(
"pointer-events-none block h-3.5 min-w-3.5 w-3.5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:ml-[100%] data-[state=checked]:translate-x-[calc(-50%-8px)] data-[state=unchecked]:translate-x-0"
)}
/>
{texts && <>
<span className="text text-xs absolute left-1 text-gray-50 hidden group-data-[state=checked]:block">{texts[0]}</span>
<span className="text text-xs absolute right-1 text-gray-400 hidden group-data-[state=unchecked]:block">{texts[1]}</span>
</>}
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }

View File

@@ -0,0 +1,119 @@
import * as React from "react"
import { cname } from "../utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cname("w-full caption-bottom text-sm border-separate border-spacing-y-1", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cname("[&>tr]:first:bg-transparent [&>tr]:first:border-none", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cname("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cname(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cname(
"group transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cname(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground text-md [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cname(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] bg-[#FBFBFB] first:rounded-l-md last:rounded-r-md group-odd:bg-[#f4f5f8] group-hover:bg-[#ebf0ff]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cname("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@@ -0,0 +1,54 @@
"use client";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import * as React from "react";
import { cname } from "../utils";
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cname(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cname(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:border data-[state=inactive]:border data-[state=inactive]:border-muted data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm data-[state=inactive]:hover:bg-secondary/80",
className
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cname(
"mt-2 ring-offset-background focus-visible:outline-none",
className
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@@ -0,0 +1,37 @@
import { ToastIcon } from "../../bs-icons/toast"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "./toast"
import { useToast } from "./use-toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props} duration={2600}>
<div className=" self-start">
<ToastIcon type={props.variant} />
</div>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}

View File

@@ -0,0 +1,134 @@
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { cname } from "../utils"
import { Cross2Icon } from "@radix-ui/react-icons"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cname(
"fixed top-0 z-[1000] flex w-full flex-col-reverse pointer-events-none",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full md:w-[420px] items-center space-x-3 overflow-hidden rounded-md border p-4 mt-4 mr-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-top-full",
{
variants: {
variant: {
info: "info border-[#024FE5] bg-[#F1F6FF] self-end",
success: "success border-[#0BA95D] bg-[#F2FFF9] self-end",
warning: "warning border-[#EA991F] bg-[#FFF7EC] self-end",
error: "error border-[#D8341E] bg-[#FFF2F0] self-end",
},
message: {
info: "shadow-xl bg-[#fff] self-center",
success: "shadow-xl bg-[#fff] self-center",
warning: "shadow-xl bg-[#fff] self-center",
error: "shadow-xl bg-[#fff] self-center",
}
},
defaultVariants: {},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants> &
{ isAlert?: boolean }
>(({ className, variant = 'info', isAlert, ...props }, ref) => {
const variants = isAlert ? { message: variant } : { variant }
return (
<ToastPrimitives.Root
ref={ref}
className={cname(toastVariants(variants), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cname(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cname(
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100",
className
)}
toast-close=""
{...props}
>
<Cross2Icon className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cname("text-sm font-semibold [&+div]:text-xs group-[.info]:text-[#024FE5] group-[.success]:text-[#0BA95D] group-[.warning]:text-[#EA991F] group-[.error]:text-[#D8341E]", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cname("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}

View File

@@ -0,0 +1,202 @@
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "./toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 6 * 1000
type ToasterToast = ToastProps & {
id: string
isAlert?: boolean,
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id" | "isAlert"> & { description: string[] | string }
function toast({ description: descs = [], ...props }: Toast & { isAlert?: boolean }) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
const description = Array.isArray(descs) ? descs.map(msg => <p>{msg}</p>) : descs
dispatch({
type: "ADD_TOAST",
toast: {
...props,
description,
id,
open: true,
onOpenChange: (open) => {
console.log('toast remove', id, open);
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function message(props: Toast) {
return toast({ ...props, isAlert: true })
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
message,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
// message中间提示toast右侧告警
export { useToast, toast, message }

View File

@@ -0,0 +1,29 @@
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cname } from "../utils"
const TooltipProvider = TooltipPrimitive.Provider
const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cname(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

View File

@@ -0,0 +1,20 @@
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/**
* 样式合并
*/
export function cname(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const generateUUID = (length: number) => {
let d = new Date().getTime()
const uuid = ''.padStart(length, 'x').replace(/[xy]/g, (c) => {
const r = (d + Math.random() * 16) % 16 | 0
d = Math.floor(d / 16)
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16)
})
return uuid
}