Docker images / prepare-release (push) Successful in 9s
Docker images / backend-image (push) Successful in 1m43s
Docker images / frontend-image (push) Successful in 2m58s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 39s
Docker images / publish-release (push) Successful in 8s
Replaced existing KPI display implementations across multiple pages with the new KpiStatGrid component for a more consistent and visually appealing presentation of statistics. Updated the Backups, BGP, Certificates, Communities, Containers, Dashboard, and Data Collection pages to utilize the KpiStatGrid, improving the overall user experience and maintainability of the codebase. Additionally, added new dependencies in package.json for required libraries.
301 lines
8.9 KiB
TypeScript
301 lines
8.9 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { Combobox as ComboboxPrimitive } from "@base-ui/react/combobox"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
InputGroup,
|
|
InputGroupAddon,
|
|
InputGroupButton,
|
|
InputGroupInput,
|
|
} from "@/components/ui/input-group"
|
|
import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react"
|
|
|
|
const Combobox = ComboboxPrimitive.Root
|
|
|
|
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
|
|
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />
|
|
}
|
|
|
|
function ComboboxTrigger({
|
|
className,
|
|
children,
|
|
...props
|
|
}: ComboboxPrimitive.Trigger.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Trigger
|
|
data-slot="combobox-trigger"
|
|
className={cn("[&_svg:not([class*='size-'])]:size-4", className)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
|
|
</ComboboxPrimitive.Trigger>
|
|
)
|
|
}
|
|
|
|
function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Clear
|
|
data-slot="combobox-clear"
|
|
render={<InputGroupButton variant="ghost" size="icon-xs" />}
|
|
className={cn(className)}
|
|
{...props}
|
|
>
|
|
<XIcon className="pointer-events-none" />
|
|
</ComboboxPrimitive.Clear>
|
|
)
|
|
}
|
|
|
|
function ComboboxInput({
|
|
className,
|
|
children,
|
|
disabled = false,
|
|
showTrigger = true,
|
|
showClear = false,
|
|
...props
|
|
}: ComboboxPrimitive.Input.Props & {
|
|
showTrigger?: boolean
|
|
showClear?: boolean
|
|
}) {
|
|
return (
|
|
<InputGroup className={cn("w-auto", className)}>
|
|
<ComboboxPrimitive.Input
|
|
render={<InputGroupInput disabled={disabled} />}
|
|
{...props}
|
|
/>
|
|
<InputGroupAddon align="inline-end">
|
|
{showTrigger && (
|
|
<InputGroupButton
|
|
size="icon-xs"
|
|
variant="ghost"
|
|
render={<ComboboxTrigger />}
|
|
data-slot="input-group-button"
|
|
className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
|
|
disabled={disabled}
|
|
/>
|
|
)}
|
|
{showClear && <ComboboxClear disabled={disabled} />}
|
|
</InputGroupAddon>
|
|
{children}
|
|
</InputGroup>
|
|
)
|
|
}
|
|
|
|
function ComboboxContent({
|
|
className,
|
|
side = "bottom",
|
|
sideOffset = 6,
|
|
align = "start",
|
|
alignOffset = 0,
|
|
anchor,
|
|
...props
|
|
}: ComboboxPrimitive.Popup.Props &
|
|
Pick<
|
|
ComboboxPrimitive.Positioner.Props,
|
|
"side" | "align" | "sideOffset" | "alignOffset" | "anchor"
|
|
>) {
|
|
return (
|
|
<ComboboxPrimitive.Portal>
|
|
<ComboboxPrimitive.Positioner
|
|
side={side}
|
|
sideOffset={sideOffset}
|
|
align={align}
|
|
alignOffset={alignOffset}
|
|
anchor={anchor}
|
|
className="isolate z-50"
|
|
>
|
|
<ComboboxPrimitive.Popup
|
|
data-slot="combobox-content"
|
|
data-chips={!!anchor}
|
|
className={cn(
|
|
"group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-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}
|
|
/>
|
|
</ComboboxPrimitive.Positioner>
|
|
</ComboboxPrimitive.Portal>
|
|
)
|
|
}
|
|
|
|
function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
|
|
return (
|
|
<ComboboxPrimitive.List
|
|
data-slot="combobox-list"
|
|
className={cn(
|
|
"no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ComboboxItem({
|
|
className,
|
|
children,
|
|
...props
|
|
}: ComboboxPrimitive.Item.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Item
|
|
data-slot="combobox-item"
|
|
className={cn(
|
|
"relative flex w-full cursor-default items-center gap-2 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**: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}
|
|
>
|
|
{children}
|
|
<ComboboxPrimitive.ItemIndicator
|
|
render={
|
|
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
|
}
|
|
>
|
|
<CheckIcon className="pointer-events-none" />
|
|
</ComboboxPrimitive.ItemIndicator>
|
|
</ComboboxPrimitive.Item>
|
|
)
|
|
}
|
|
|
|
function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Group
|
|
data-slot="combobox-group"
|
|
className={cn(className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ComboboxLabel({
|
|
className,
|
|
...props
|
|
}: ComboboxPrimitive.GroupLabel.Props) {
|
|
return (
|
|
<ComboboxPrimitive.GroupLabel
|
|
data-slot="combobox-label"
|
|
className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
|
|
)
|
|
}
|
|
|
|
function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Empty
|
|
data-slot="combobox-empty"
|
|
className={cn(
|
|
"hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ComboboxSeparator({
|
|
className,
|
|
...props
|
|
}: ComboboxPrimitive.Separator.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Separator
|
|
data-slot="combobox-separator"
|
|
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ComboboxChips({
|
|
className,
|
|
...props
|
|
}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
|
|
ComboboxPrimitive.Chips.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Chips
|
|
data-slot="combobox-chips"
|
|
className={cn(
|
|
"flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent bg-clip-padding px-2.5 py-1 text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
|
|
className,
|
|
)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function ComboboxChip({
|
|
className,
|
|
children,
|
|
showRemove = true,
|
|
...props
|
|
}: ComboboxPrimitive.Chip.Props & {
|
|
showRemove?: boolean
|
|
}) {
|
|
return (
|
|
<ComboboxPrimitive.Chip
|
|
data-slot="combobox-chip"
|
|
className={cn(
|
|
"flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0",
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
{children}
|
|
{showRemove && (
|
|
<ComboboxPrimitive.ChipRemove
|
|
render={<Button variant="ghost" size="icon-xs" />}
|
|
className="-ml-1 opacity-50 hover:opacity-100"
|
|
data-slot="combobox-chip-remove"
|
|
>
|
|
<XIcon className="pointer-events-none" />
|
|
</ComboboxPrimitive.ChipRemove>
|
|
)}
|
|
</ComboboxPrimitive.Chip>
|
|
)
|
|
}
|
|
|
|
function ComboboxChipsInput({
|
|
className,
|
|
...props
|
|
}: ComboboxPrimitive.Input.Props) {
|
|
return (
|
|
<ComboboxPrimitive.Input
|
|
data-slot="combobox-chip-input"
|
|
className={cn("min-w-16 flex-1 outline-none", className)}
|
|
{...props}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function useComboboxAnchor() {
|
|
return React.useRef<HTMLDivElement | null>(null)
|
|
}
|
|
|
|
export {
|
|
Combobox,
|
|
ComboboxInput,
|
|
ComboboxContent,
|
|
ComboboxList,
|
|
ComboboxItem,
|
|
ComboboxGroup,
|
|
ComboboxLabel,
|
|
ComboboxCollection,
|
|
ComboboxEmpty,
|
|
ComboboxSeparator,
|
|
ComboboxChips,
|
|
ComboboxChip,
|
|
ComboboxChipsInput,
|
|
ComboboxTrigger,
|
|
ComboboxValue,
|
|
useComboboxAnchor,
|
|
}
|