feat(selection): 重构选股结果筛选与状态入口
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from "lucide-react"
|
||||
import { DayPicker, type DayPickerProps } from "react-day-picker"
|
||||
import { zhCN } from "react-day-picker/locale"
|
||||
|
||||
import { cn } from "@/shared/ui/utils"
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: DayPickerProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col gap-4 sm:flex-row",
|
||||
month: "flex w-full flex-col gap-4",
|
||||
month_caption: "flex items-center justify-center pt-1",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "flex items-center gap-1",
|
||||
button_previous:
|
||||
"absolute left-1 inline-flex size-7 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50",
|
||||
button_next:
|
||||
"absolute right-1 inline-flex size-7 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50",
|
||||
month_grid: "w-full border-collapse",
|
||||
weekdays: "flex",
|
||||
weekday:
|
||||
"w-9 rounded-md text-[0.8rem] font-normal text-muted-foreground",
|
||||
week: "mt-2 flex w-full",
|
||||
day: "relative size-9 p-0 text-center text-sm [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-muted/50 [&:has([aria-selected])]:bg-muted first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
||||
day_button:
|
||||
"inline-flex size-9 items-center justify-center rounded-md p-0 font-normal outline-none hover:bg-muted hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50 aria-selected:bg-primary aria-selected:text-primary-foreground",
|
||||
range_start: "day-range-start rounded-l-md bg-muted",
|
||||
range_middle: "rounded-none bg-muted",
|
||||
range_end: "day-range-end rounded-r-md bg-muted",
|
||||
selected: "rounded-md bg-primary text-primary-foreground",
|
||||
today: "bg-muted text-foreground",
|
||||
outside:
|
||||
"day-outside text-muted-foreground opacity-50 aria-selected:bg-muted/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
|
||||
disabled: "text-muted-foreground opacity-50",
|
||||
hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
Chevron: ({ className: chevronClassName, orientation, style }) => {
|
||||
const Icon =
|
||||
orientation === "left"
|
||||
? ChevronLeft
|
||||
: orientation === "right"
|
||||
? ChevronRight
|
||||
: orientation === "up"
|
||||
? ChevronUp
|
||||
: ChevronDown
|
||||
return (
|
||||
<Icon
|
||||
aria-hidden="true"
|
||||
className={cn("size-4", chevronClassName)}
|
||||
style={style}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}}
|
||||
locale={zhCN}
|
||||
showOutsideDays={showOutsideDays}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Calendar }
|
||||
@@ -0,0 +1,62 @@
|
||||
import { format } from "date-fns"
|
||||
import { CalendarDays } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
import type { ComponentProps } from "react"
|
||||
|
||||
import { Button } from "@/shared/ui/button"
|
||||
import { Calendar } from "@/shared/ui/calendar"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover"
|
||||
|
||||
export interface DatePickerProps extends Omit<
|
||||
ComponentProps<typeof Button>,
|
||||
"children" | "onChange" | "value"
|
||||
> {
|
||||
value?: Date
|
||||
onChange?: (value: Date | undefined) => void
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export function DatePicker({
|
||||
className,
|
||||
disabled,
|
||||
onChange,
|
||||
placeholder = "选择日期",
|
||||
value,
|
||||
...props
|
||||
}: DatePickerProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
function handleSelect(nextValue: Date | undefined) {
|
||||
onChange?.(nextValue)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger
|
||||
disabled={disabled}
|
||||
render={
|
||||
<Button
|
||||
data-empty={!value}
|
||||
data-slot="date-picker-trigger"
|
||||
disabled={disabled}
|
||||
variant="outline"
|
||||
className={className}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CalendarDays aria-hidden="true" data-icon="inline-start" />
|
||||
{value ? format(value, "yyyy-MM-dd") : <span>{placeholder}</span>}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-auto p-0">
|
||||
<Calendar
|
||||
defaultMonth={value}
|
||||
mode="single"
|
||||
onSelect={handleSelect}
|
||||
selected={value}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Popover as PopoverPrimitive } from "@base-ui/react/popover"
|
||||
|
||||
import { cn } from "@/shared/ui/utils"
|
||||
|
||||
function Popover({ ...props }: PopoverPrimitive.Root.Props) {
|
||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||
}
|
||||
|
||||
function PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {
|
||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
align = "center",
|
||||
className,
|
||||
children,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: PopoverPrimitive.Popup.Props & {
|
||||
align?: PopoverPrimitive.Positioner.Props["align"]
|
||||
sideOffset?: number
|
||||
}) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Positioner
|
||||
data-slot="popover-positioner"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className="z-50 outline-none"
|
||||
>
|
||||
<PopoverPrimitive.Popup
|
||||
data-slot="popover-content"
|
||||
className={cn(
|
||||
"z-50 w-72 origin-(--transform-origin) rounded-lg border border-border bg-card p-4 text-card-foreground shadow-lg ring-1 ring-foreground/10 outline-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</PopoverPrimitive.Popup>
|
||||
</PopoverPrimitive.Positioner>
|
||||
</PopoverPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function PopoverClose({ ...props }: PopoverPrimitive.Close.Props) {
|
||||
return <PopoverPrimitive.Close data-slot="popover-close" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverClose, PopoverContent, PopoverTrigger }
|
||||
@@ -0,0 +1,200 @@
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||
import { Select as SelectPrimitive } from "@base-ui/react/select"
|
||||
|
||||
import { cn } from "@/shared/ui/utils"
|
||||
|
||||
function Select<Value, Multiple extends boolean | undefined = false>(
|
||||
props: SelectPrimitive.Root.Props<Value, Multiple>,
|
||||
) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
}
|
||||
|
||||
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
|
||||
return (
|
||||
<SelectPrimitive.Group
|
||||
data-slot="select-group"
|
||||
className={cn("p-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
className,
|
||||
...props
|
||||
}: SelectPrimitive.GroupLabel.Props) {
|
||||
return (
|
||||
<SelectPrimitive.GroupLabel
|
||||
data-slot="select-label"
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-xs font-medium text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
className,
|
||||
children,
|
||||
size = "default",
|
||||
...props
|
||||
}: SelectPrimitive.Trigger.Props & {
|
||||
size?: "sm" | "default"
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-2.5 text-sm whitespace-nowrap outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 [&>span]:line-clamp-1 [&>svg]:pointer-events-none [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon
|
||||
data-slot="select-icon"
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
<ChevronDown aria-hidden="true" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
|
||||
return (
|
||||
<SelectPrimitive.Value
|
||||
data-slot="select-value"
|
||||
className={cn("line-clamp-1", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
className,
|
||||
children,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: SelectPrimitive.Popup.Props & {
|
||||
sideOffset?: number
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Positioner
|
||||
data-slot="select-positioner"
|
||||
sideOffset={sideOffset}
|
||||
className="z-50 outline-none"
|
||||
>
|
||||
<SelectPrimitive.Popup
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"relative z-50 max-h-(--available-height) min-w-[8rem] origin-(--transform-origin) overflow-hidden rounded-lg border border-border bg-card text-card-foreground shadow-lg ring-1 ring-foreground/10 outline-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.List
|
||||
data-slot="select-list"
|
||||
className="max-h-72 overflow-y-auto p-1"
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.List>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Popup>
|
||||
</SelectPrimitive.Positioner>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: SelectPrimitive.Item.Props) {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"relative flex min-h-8 w-full cursor-default items-center rounded-md py-1.5 pr-8 pl-2 text-sm outline-none select-none data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex size-4 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator data-slot="select-item-indicator">
|
||||
<Check aria-hidden="true" className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText data-slot="select-item-text">
|
||||
{children}
|
||||
</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
className,
|
||||
...props
|
||||
}: SelectPrimitive.Separator.Props) {
|
||||
return (
|
||||
<SelectPrimitive.Separator
|
||||
data-slot="select-separator"
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
className,
|
||||
...props
|
||||
}: SelectPrimitive.ScrollUpArrow.Props) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollUpArrow
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex h-6 items-center justify-center bg-card text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp aria-hidden="true" className="size-4" />
|
||||
</SelectPrimitive.ScrollUpArrow>
|
||||
)
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
className,
|
||||
...props
|
||||
}: SelectPrimitive.ScrollDownArrow.Props) {
|
||||
return (
|
||||
<SelectPrimitive.ScrollDownArrow
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex h-6 items-center justify-center bg-card text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown aria-hidden="true" className="size-4" />
|
||||
</SelectPrimitive.ScrollDownArrow>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectScrollDownButton,
|
||||
SelectScrollUpButton,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
}
|
||||
Reference in New Issue
Block a user