frontend fully integreated with the backend
This commit is contained in:
@@ -7,7 +7,64 @@ import { cn } from "@/lib/utils"
|
||||
import { HugeiconsIcon } from "@hugeicons/react"
|
||||
import { UnfoldMoreIcon, Tick02Icon, ArrowUp01Icon, ArrowDown01Icon } from "@hugeicons/core-free-icons"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
function collectSelectItems(children: React.ReactNode): Array<{ value: unknown; label: string }> {
|
||||
const items: Array<{ value: unknown; label: string }> = []
|
||||
|
||||
React.Children.forEach(children, (child) => {
|
||||
if (!React.isValidElement<{ value?: unknown; label?: string; children?: React.ReactNode }>(child)) {
|
||||
return
|
||||
}
|
||||
|
||||
if ("value" in child.props) {
|
||||
items.push({
|
||||
value: child.props.value ?? null,
|
||||
label: child.props.label ?? getItemLabel(child.props.children) ?? String(child.props.value ?? ""),
|
||||
})
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
items.push(...collectSelectItems(child.props.children))
|
||||
}
|
||||
})
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
function Select<Value, Multiple extends boolean | undefined = false>({
|
||||
children,
|
||||
items,
|
||||
...props
|
||||
}: SelectPrimitive.Root.Props<Value, Multiple>) {
|
||||
const derivedItems = React.useMemo(
|
||||
() => (items == null ? collectSelectItems(children) : items),
|
||||
[children, items]
|
||||
)
|
||||
|
||||
return (
|
||||
<SelectPrimitive.Root {...props} items={derivedItems}>
|
||||
{children}
|
||||
</SelectPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
function getItemLabel(children: React.ReactNode): string | undefined {
|
||||
if (typeof children === "string" || typeof children === "number") {
|
||||
return String(children)
|
||||
}
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
const parts = children
|
||||
.map(getItemLabel)
|
||||
.filter((part): part is string => Boolean(part))
|
||||
return parts.length > 0 ? parts.join(" ") : undefined
|
||||
}
|
||||
|
||||
if (React.isValidElement<{ children?: React.ReactNode }>(children)) {
|
||||
return getItemLabel(children.props.children)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
|
||||
return (
|
||||
@@ -64,12 +121,13 @@ function SelectContent({
|
||||
sideOffset = 4,
|
||||
align = "center",
|
||||
alignOffset = 0,
|
||||
alignItemWithTrigger = true,
|
||||
alignItemWithTrigger = false,
|
||||
collisionAvoidance = { side: "shift", align: "shift", fallbackAxisSide: "none" },
|
||||
...props
|
||||
}: SelectPrimitive.Popup.Props &
|
||||
Pick<
|
||||
SelectPrimitive.Positioner.Props,
|
||||
"align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
|
||||
"align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger" | "collisionAvoidance"
|
||||
>) {
|
||||
return (
|
||||
<SelectPrimitive.Portal>
|
||||
@@ -79,6 +137,7 @@ function SelectContent({
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
alignItemWithTrigger={alignItemWithTrigger}
|
||||
collisionAvoidance={collisionAvoidance}
|
||||
className="isolate z-50"
|
||||
>
|
||||
<SelectPrimitive.Popup
|
||||
@@ -114,25 +173,28 @@ function SelectItem({
|
||||
children,
|
||||
...props
|
||||
}: SelectPrimitive.Item.Props) {
|
||||
const label = props.label ?? getItemLabel(children)
|
||||
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
{...props}
|
||||
data-slot="select-item"
|
||||
label={label}
|
||||
className={cn(
|
||||
"relative flex min-h-7 w-full cursor-default items-center gap-2 rounded-xl py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
"relative flex min-h-7 w-full items-center gap-2 rounded-xl py-1.5 pr-8 pl-2 text-sm text-popover-foreground outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
|
||||
{children}
|
||||
</SelectPrimitive.ItemText>
|
||||
<SelectPrimitive.ItemIndicator
|
||||
render={
|
||||
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
||||
<span className="pointer-events-none flex size-4 shrink-0 items-center justify-center" />
|
||||
}
|
||||
>
|
||||
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="pointer-events-none" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
<SelectPrimitive.ItemText className="block flex-1 whitespace-nowrap text-popover-foreground">
|
||||
{children}
|
||||
</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user