怎么做网站弹出公告,天津建设工程信息网 招标发布软件,上海市教育网官网,手机网站图片宽度目录 一、button1. 导入部分2. 定义按钮的样式变体1. variant2. size总结 3. 定义按钮的属性类型4. 定义按钮组件5. 导出组件和样式变体总结 二、multi-select多选组件的核心上下文与状态1. 上下文与钩子2. MultiSelector 组件 组件子部分1. MultiSelectorTrigger2. MultiSelec… 目录 一、button1. 导入部分2. 定义按钮的样式变体1. variant2. size总结 3. 定义按钮的属性类型4. 定义按钮组件5. 导出组件和样式变体总结 二、multi-select多选组件的核心上下文与状态1. 上下文与钩子2. MultiSelector 组件 组件子部分1. MultiSelectorTrigger2. MultiSelectorInput3. MultiSelectorContent4. MultiSelectorList5. MultiSelectorItem 总结 一、button
组件来源https://ui.shadcn.com/docs/components/button
import * as React from react
import { Slot } from radix-ui/react-slot
import { cva, type VariantProps } from class-variance-authorityimport { cn } from /lib/utilsconst buttonVariants cva(inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50,{variants: {variant: {default:bg-primary text-primary-foreground hover:bg-primary/90,destructive:bg-destructive text-destructive-foreground hover:bg-destructive/90,outline:border border-input bg-background hover:bg-accent hover:text-accent-foreground,secondary:bg-secondary text-secondary-foreground hover:bg-secondary/80,disabled:disabled-foreground bg-disabled text-disabled-foreground,ghost: hover:bg-accent focus-visible:ring-0 focus-visible:ring-offset-0,link: hover:text-primary underline-offset-4,icon: border border-input,},size: {default: h-8 px-5 py-1.5,sm: h-9 rounded-md px-3,lg: h-11 rounded-md px-8,icon: h-6 w-6,iconSm: h-8 w-8,ssm: h-6,},},defaultVariants: {variant: default,size: default,},},
)export interface ButtonPropsextends React.ButtonHTMLAttributesHTMLButtonElement,VariantPropstypeof buttonVariants {asChild?: boolean
}const Button React.forwardRefHTMLButtonElement, ButtonProps(({ className, variant, size, asChild false, ...props }, ref) {const Comp asChild ? Slot : buttonreturn (CompclassName{cn(buttonVariants({ variant, size, className }))}ref{ref}{...props}/)},
)
Button.displayName Buttonexport { Button, buttonVariants }这段代码定义了一个可变样式的按钮组件Button使用了多个工具和库。我们将逐步解释各部分代码的作用。
1. 导入部分
import * as React from react
import { Slot } from radix-ui/react-slot
import { cva, type VariantProps } from class-variance-authority
import { cn } from /lib/utilsReact: 导入React库。Slot: 从radix-ui/react-slot库中导入Slot组件用于支持“asChild”属性。cva 和 VariantProps: 从class-variance-authority库中导入用于定义可变样式。cn: 从项目中的utils工具库导入cn函数用于合并CSS类名。
2. 定义按钮的样式变体
const buttonVariants cva(inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50,{variants: {variant: {default: bg-primary text-primary-foreground hover:bg-primary/90,destructive: bg-destructive text-destructive-foreground hover:bg-destructive/90,outline: border border-input bg-background hover:bg-accent hover:text-accent-foreground,secondary: bg-secondary text-secondary-foreground hover:bg-secondary/80,disabled: disabled-foreground bg-disabled text-disabled-foreground,ghost: hover:bg-accent focus-visible:ring-0 focus-visible:ring-offset-0,link: hover:text-primary underline-offset-4,icon: border border-input,},size: {default: h-8 px-5 py-1.5,sm: h-9 rounded-md px-3,lg: h-11 rounded-md px-8,icon: h-6 w-6,iconSm: h-8 w-8,ssm: h-6,},},defaultVariants: {variant: default,size: default,},},
)buttonVariants: 使用cva函数定义了按钮的样式变体。这个对象包含了两个主要部分 variants: 定义了不同的变体选项如variant和size每个选项又包含不同的具体样式。defaultVariants: 定义了默认的变体值。
当然让我们逐一解释variants对象中的每个属性及其对应的CSS属性值。
1. variant
variant属性定义了按钮的多种视觉风格每种风格都对应一组CSS类名。 default bg-primary {background-color: var(--primary-color);
}
text-primary-foreground {color: var(--primary-foreground-color);
}
hover:bg-primary/90 {background-color: var(--primary-color);opacity: 0.9;
}destructive bg-destructive {background-color: var(--destructive-color);
}
text-destructive-foreground {color: var(--destructive-foreground-color);
}
hover:bg-destructive/90 {background-color: var(--destructive-color);opacity: 0.9;
}outline border border-input {border: 1px solid var(--input-border-color);
}
bg-background {background-color: var(--background-color);
}
hover:bg-accent {background-color: var(--accent-color);
}
hover:text-accent-foreground {color: var(--accent-foreground-color);
}secondary bg-secondary {background-color: var(--secondary-color);
}
text-secondary-foreground {color: var(--secondary-foreground-color);
}
hover:bg-secondary/80 {background-color: var(--secondary-color);opacity: 0.8;
}disabled disabled-foreground {color: var(--disabled-foreground-color);
}
bg-disabled {background-color: var(--disabled-color);
}
text-disabled-foreground {color: var(--disabled-foreground-color);
}ghost hover:bg-accent {background-color: var(--accent-color);
}
focus-visible:ring-0 {outline: none;box-shadow: none;
}
focus-visible:ring-offset-0 {box-shadow: none;
}link hover:text-primary {color: var(--primary-color);
}
underline-offset-4 {text-underline-offset: 4px;
}icon border border-input {border: 1px solid var(--input-border-color);
}2. size
size属性定义了按钮的不同尺寸每个尺寸都对应一组CSS类名。 default h-8 {height: 2rem; /* 32px */
}
px-5 {padding-left: 1.25rem; /* 20px */padding-right: 1.25rem; /* 20px */
}
py-1.5 {padding-top: 0.375rem; /* 6px */padding-bottom: 0.375rem; /* 6px */
}sm h-9 {height: 2.25rem; /* 36px */
}
rounded-md {border-radius: 0.375rem; /* 6px */
}
px-3 {padding-left: 0.75rem; /* 12px */padding-right: 0.75rem; /* 12px */
}lg h-11 {height: 2.75rem; /* 44px */
}
rounded-md {border-radius: 0.375rem; /* 6px */
}
px-8 {padding-left: 2rem; /* 32px */padding-right: 2rem; /* 32px */
}icon h-6 {height: 1.5rem; /* 24px */
}
w-6 {width: 1.5rem; /* 24px */
}iconSm h-8 {height: 2rem; /* 32px */
}
w-8 {width: 2rem; /* 32px */
}ssm h-6 {height: 1.5rem; /* 24px */
}总结
这些variants属性提供了丰富的样式变体使得按钮组件可以根据不同的需求应用不同的外观和尺寸通过简单的属性传递实现了多样化的视觉效果。
3. 定义按钮的属性类型
export interface ButtonPropsextends React.ButtonHTMLAttributesHTMLButtonElement,VariantPropstypeof buttonVariants {asChild?: boolean
}ButtonProps: 定义了按钮组件的属性接口扩展了React.ButtonHTMLAttributes和VariantProps。另外还增加了asChild属性用于指定是否将按钮作为子组件渲染。
4. 定义按钮组件
const Button React.forwardRefHTMLButtonElement, ButtonProps(({ className, variant, size, asChild false, ...props }, ref) {const Comp asChild ? Slot : buttonreturn (CompclassName{cn(buttonVariants({ variant, size, className }))}ref{ref}{...props}/)},
)
Button.displayName ButtonButton: 使用React.forwardRef定义一个带有转发引用ref的按钮组件。 Comp: 根据asChild属性动态决定使用Slot组件还是button元素。cn(buttonVariants({ variant, size, className })): 使用cn函数合并传入的className和通过buttonVariants生成的变体样式。
5. 导出组件和样式变体
export { Button, buttonVariants }Button和buttonVariants均被导出允许在其他模块中使用。
总结
这个组件使用了class-variance-authority库来管理按钮的样式变体通过React的forwardRef和条件渲染实现了灵活的按钮组件。这样开发者可以通过简单的属性传递来改变按钮的外观和行为。
二、multi-select
组件来源https://shadcn-extension.vercel.app/docs/multi-select 如果希望有远程搜索能力可以参考 https://shadcnui-expansions.typeart.cc/docs/multiple-selector use client;import { Badge } from /components/ui/badge;
import {Command,CommandItem,CommandEmpty,CommandList,
} from /components/ui/command;
import { cn } from /lib/utils;
import { Command as CommandPrimitive } from cmdk;
import { X as RemoveIcon, Check } from lucide-react;
import React, {KeyboardEvent,createContext,forwardRef,useCallback,useContext,useState,
} from react;type MultiSelectorProps {values: string[];onValuesChange: (value: string[]) void;loop?: boolean;options: {label: string, value: string, color?: string}[];
} React.ComponentPropsWithoutReftypeof CommandPrimitive;interface MultiSelectContextProps {value: string[];options: {label: string, value: string, color?: string}[];onValueChange: (value: any) void;open: boolean;setOpen: (value: boolean) void;inputValue: string;setInputValue: React.DispatchReact.SetStateActionstring;activeIndex: number;setActiveIndex: React.DispatchReact.SetStateActionnumber;
}const MultiSelectContext createContextMultiSelectContextProps | null(null);const useMultiSelect () {const context useContext(MultiSelectContext);if (!context) {throw new Error(useMultiSelect must be used within MultiSelectProvider);}return context;
};const MultiSelector ({values: value,onValuesChange: onValueChange,loop false,className,children,dir,options,...props
}: MultiSelectorProps) {const [inputValue, setInputValue] useState();const [open, setOpen] useStateboolean(false);const [activeIndex, setActiveIndex] useStatenumber(-1);const onValueChangeHandler useCallback((val: string) {if (value.includes(val)) {onValueChange(value.filter((item) item ! val));} else {onValueChange([...value, val]);}},[value]);// TODO : change from else if use to switch case statementconst handleKeyDown useCallback((e: KeyboardEventHTMLDivElement) {const moveNext () {const nextIndex activeIndex 1;setActiveIndex(nextIndex value.length - 1 ? (loop ? 0 : -1) : nextIndex);};const movePrev () {const prevIndex activeIndex - 1;setActiveIndex(prevIndex 0 ? value.length - 1 : prevIndex);};if ((e.key Backspace || e.key Delete) value.length 0) {if (inputValue.length 0) {if (activeIndex ! -1 activeIndex value.length) {onValueChange(value.filter((item) item ! value[activeIndex]));const newIndex activeIndex - 1 0 ? 0 : activeIndex - 1;setActiveIndex(newIndex);} else {onValueChange(value.filter((item) item ! value[value.length - 1]));}}} else if (e.key Enter) {setOpen(true);} else if (e.key Escape) {if (activeIndex ! -1) {setActiveIndex(-1);} else {setOpen(false);}} else if (dir rtl) {if (e.key ArrowRight) {movePrev();} else if (e.key ArrowLeft (activeIndex ! -1 || loop)) {moveNext();}} else {if (e.key ArrowLeft) {movePrev();} else if (e.key ArrowRight (activeIndex ! -1 || loop)) {moveNext();}}},[value, inputValue, activeIndex, loop]);return (MultiSelectContext.Providervalue{{value,onValueChange: onValueChangeHandler,open,setOpen,inputValue,setInputValue,activeIndex,setActiveIndex,options,}}CommandonKeyDown{handleKeyDown}className{cn(overflow-visible bg-transparent flex flex-col,className)}dir{dir}{...props}{children}/Command/MultiSelectContext.Provider);
};const MultiSelectorTrigger forwardRefHTMLDivElement,React.HTMLAttributesHTMLDivElement
(({ className, children, ...props }, ref) {const { value, onValueChange, activeIndex, open, options } useMultiSelect();const mousePreventDefault useCallback((e: React.MouseEvent) {e.preventDefault();e.stopPropagation();}, []);const valueOptions options.filter(option value.includes(option.value))return (divref{ref}className{cn(min-h-9 bg-accent text-sm flex items-center flex-wrap gap-1 px-3 rounded-lg mb-2,open ? ring-ring ring-1 ring-offset-1 bg-background : ,className)}{...props}{valueOptions.map((item, index) (Badgekey{item.value}color{item.color}className{cn(flex flex-wrap gap-1,activeIndex index ring-2 ring-muted-foreground)}span{item.label}/spanbuttonaria-label{Remove ${item.label} option}aria-roledescriptionbutton to remove optiontypebuttononMouseDown{mousePreventDefault}onClick{() onValueChange(item.value)}span classNamesr-onlyRemove {item.label} option/spanRemoveIcon classNameh-4 w-4 hover:stroke-destructive //button/Badge))}{children}/div);
});MultiSelectorTrigger.displayName MultiSelectorTrigger;const MultiSelectorInput forwardRefReact.ElementReftypeof CommandPrimitive.Input,React.ComponentPropsWithoutReftypeof CommandPrimitive.Input
(({ className, ...props }, ref) {const { setOpen, inputValue, setInputValue, activeIndex, setActiveIndex } useMultiSelect();return (CommandPrimitive.Input{...props}ref{ref}value{inputValue}onValueChange{activeIndex -1 ? setInputValue : undefined}onBlur{() setOpen(false)}onFocus{() setOpen(true)}onClick{() setActiveIndex(-1)}className{cn(bg-transparent outline-none placeholder:text-muted-foreground flex-1,className,activeIndex ! -1 caret-transparent)}/);
});MultiSelectorInput.displayName MultiSelectorInput;const MultiSelectorContent forwardRefHTMLDivElement,React.HTMLAttributesHTMLDivElement
(({ children }, ref) {const { open } useMultiSelect();return (div ref{ref} classNamerelative{open children}/div);
});MultiSelectorContent.displayName MultiSelectorContent;const MultiSelectorList forwardRefReact.ElementReftypeof CommandPrimitive.List,React.ComponentPropsWithoutReftypeof CommandPrimitive.List
(({ className, children }, ref) {return (CommandListref{ref}className{cn(p-2 flex flex-col gap-2 rounded-md scrollbar-thin scrollbar-track-transparent transition-colors scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted scrollbar-thumb-rounded-lg w-full absolute bg-background shadow-md z-10 border border-muted top-0,className)}{children}CommandEmptyspan classNametext-muted-foregroundNo results found/span/CommandEmpty/CommandList);
});MultiSelectorList.displayName MultiSelectorList;const MultiSelectorItem forwardRefReact.ElementReftypeof CommandPrimitive.Item,{ value: string } React.ComponentPropsWithoutReftypeof CommandPrimitive.Item
(({ className, value, children, ...props }, ref) {const { value: Options, onValueChange, setInputValue } useMultiSelect();const mousePreventDefault useCallback((e: React.MouseEvent) {e.preventDefault();e.stopPropagation();}, []);const isIncluded Options.includes(value);return (CommandItemref{ref}{...props}onSelect{() {onValueChange(value);setInputValue();}}className{cn(rounded-md cursor-pointer px-4 py-1.5 transition-colors flex justify-between ,className,isIncluded opacity-50 cursor-default,props.disabled opacity-50 cursor-not-allowed)}onMouseDown{mousePreventDefault}{children}{isIncluded Check classNameh-4 w-4 /}/CommandItem);
});MultiSelectorItem.displayName MultiSelectorItem;export {MultiSelector,MultiSelectorTrigger,MultiSelectorInput,MultiSelectorContent,MultiSelectorList,MultiSelectorItem,
};
这个组件库实现了一个多选下拉框包含选择、显示和过滤选项等功能。组件利用了 React 的上下文、钩子和基于 Radix UI 和 CMDK 的组合控件。
以下是对每个主要部分的详细分析
多选组件的核心上下文与状态
1. 上下文与钩子
const MultiSelectContext createContextMultiSelectContextProps | null(null);const useMultiSelect () {const context useContext(MultiSelectContext);if (!context) {throw new Error(useMultiSelect must be used within MultiSelectProvider);}return context;
};MultiSelectContext 是一个 React 上下文用于共享多选组件的状态。useMultiSelect 是一个自定义钩子用于方便地访问这个上下文。
2. MultiSelector 组件
const MultiSelector ({values: value,onValuesChange: onValueChange,loop false,className,children,dir,options,...props
}: MultiSelectorProps) {const [inputValue, setInputValue] useState();const [open, setOpen] useStateboolean(false);const [activeIndex, setActiveIndex] useStatenumber(-1);const onValueChangeHandler useCallback((val: string) {if (value.includes(val)) {onValueChange(value.filter((item) item ! val));} else {onValueChange([...value, val]);}},[value]);const handleKeyDown useCallback((e: KeyboardEventHTMLDivElement) {// handle keyboard navigation and actions},[value, inputValue, activeIndex, loop]);return (MultiSelectContext.Providervalue{{value,onValueChange: onValueChangeHandler,open,setOpen,inputValue,setInputValue,activeIndex,setActiveIndex,options,}}CommandonKeyDown{handleKeyDown}className{cn(overflow-visible bg-transparent flex flex-col,className)}dir{dir}{...props}{children}/Command/MultiSelectContext.Provider);
};MultiSelector 是整个多选组件的核心负责管理状态并提供上下文。它使用 useState 管理输入值、打开状态和活动索引。通过 useCallback 创建 onValueChangeHandler 和 handleKeyDown 函数用于处理选项的选择和键盘事件。
组件子部分
1. MultiSelectorTrigger
const MultiSelectorTrigger forwardRefHTMLDivElement,React.HTMLAttributesHTMLDivElement
(({ className, children, ...props }, ref) {const { value, onValueChange, activeIndex, open, options } useMultiSelect();const mousePreventDefault useCallback((e: React.MouseEvent) {e.preventDefault();e.stopPropagation();}, []);const valueOptions options.filter(option value.includes(option.value))return (divref{ref}className{cn(min-h-9 bg-accent text-sm flex items-center flex-wrap gap-1 px-3 rounded-lg mb-2,open ? ring-ring ring-1 ring-offset-1 bg-background : ,className)}{...props}{valueOptions.map((item, index) (Badgekey{item.value}color{item.color}className{cn(flex flex-wrap gap-1,activeIndex index ring-2 ring-muted-foreground)}span{item.label}/spanbuttonaria-label{Remove ${item.label} option}aria-roledescriptionbutton to remove optiontypebuttononMouseDown{mousePreventDefault}onClick{() onValueChange(item.value)}span classNamesr-onlyRemove {item.label} option/spanRemoveIcon classNameh-4 w-4 hover:stroke-destructive //button/Badge))}{children}/div);
});MultiSelectorTrigger.displayName MultiSelectorTrigger;MultiSelectorTrigger 是一个用于显示已选择选项的组件。它使用 useMultiSelect 钩子从上下文获取状态并渲染已选项的 Badge 组件。每个 Badge 组件包含一个按钮用于移除该选项。
2. MultiSelectorInput
const MultiSelectorInput forwardRefReact.ElementReftypeof CommandPrimitive.Input,React.ComponentPropsWithoutReftypeof CommandPrimitive.Input
(({ className, ...props }, ref) {const { setOpen, inputValue, setInputValue, activeIndex, setActiveIndex } useMultiSelect();return (CommandPrimitive.Input{...props}ref{ref}value{inputValue}onValueChange{activeIndex -1 ? setInputValue : undefined}onBlur{() setOpen(false)}onFocus{() setOpen(true)}onClick{() setActiveIndex(-1)}className{cn(bg-transparent outline-none placeholder:text-muted-foreground flex-1,className,activeIndex ! -1 caret-transparent)}/);
});MultiSelectorInput.displayName MultiSelectorInput;MultiSelectorInput 是一个输入组件用于处理用户输入。它使用 useMultiSelect 钩子从上下文获取状态并根据输入值更新上下文中的 inputValue。它还处理输入框的焦点和点击事件。
3. MultiSelectorContent
const MultiSelectorContent forwardRefHTMLDivElement,React.HTMLAttributesHTMLDivElement
(({ children }, ref) {const { open } useMultiSelect();return (div ref{ref} classNamerelative{open children}/div);
});MultiSelectorContent.displayName MultiSelectorContent;MultiSelectorContent 是一个包装组件用于渲染多选内容。当上下文中的 open 状态为 true 时显示其子组件。
4. MultiSelectorList
const MultiSelectorList forwardRefReact.ElementReftypeof CommandPrimitive.List,React.ComponentPropsWithoutReftypeof CommandPrimitive.List
(({ className, children }, ref) {return (CommandListref{ref}className{cn(p-2 flex flex-col gap-2 rounded-md scrollbar-thin scrollbar-track-transparent transition-colors scrollbar-thumb-muted-foreground dark:scrollbar-thumb-muted scrollbar-thumb-rounded-lg w-full absolute bg-background shadow-md z-10 border border-muted top-0,className)}{children}CommandEmptyspan classNametext-muted-foregroundNo results found/span/CommandEmpty/CommandList);
});MultiSelectorList.displayName MultiSelectorList;MultiSelectorList 是一个列表组件用于渲染所有可选项。它使用 CommandList 组件并在子组件中包含一个 CommandEmpty 组件当没有结果时显示提示。
5. MultiSelectorItem
const MultiSelectorItem forwardRefReact.ElementReftypeof CommandPrimitive.Item,{ value: string } React.ComponentPropsWithoutReftypeof CommandPrimitive.Item
(({ className, value, children, ...props }, ref) {const { value: Options, onValueChange, setInputValue } useMultiSelect();const mousePreventDefault useCallback((e: React.MouseEvent) {e.preventDefault();e.stopPropagation();}, []);const isIncluded Options.includes(value);return (CommandItemref{ref}{...props}onSelect{() {onValueChange(value);setInputValue();}}className{cn(rounded-md cursor-pointer px-4 py-1.5 transition-colors flex justify-between ,className,isIncluded opacity-50 cursor-default,props.disabled opacity-50 cursor-not-allowed)}onMouseDown{mousePreventDefault}{children}{isIncluded Check classNameh-4 w-4 /}/CommandItem);
});MultiSelectorItem.displayName MultiSelectorItem;MultiSelectorItem 是一个可选项组件。它使用 useMultiSelect 钩子从上下文获取状态并在选中时调用 onValueChange 更新上下文中的选项状态。它还会在已选项时显示一个 Check 图标。
总结
这个多选组件库通过上下文和钩子共享状态并将组件划分为多个小组件每个小组件负责处理不同的功能和渲染部分。