feat(advanced-custom): remove fallback option and enhance path matching for advanced custom routes

This commit is contained in:
CaIon
2026-06-18 20:29:24 +08:00
parent 3f2c0aeda7
commit 55b00fcf09
22 changed files with 378 additions and 329 deletions
+83 -13
View File
@@ -20,6 +20,8 @@ import * as React from 'react'
import { Add01Icon } from '@hugeicons/core-free-icons'
import { HugeiconsIcon } from '@hugeicons/react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { copyToClipboard } from '@/lib/copy-to-clipboard'
import { cn } from '@/lib/utils'
import {
Combobox,
@@ -64,6 +66,11 @@ interface MultiSelectProps {
* Hidden values remain searchable/removable from the dropdown.
*/
maxVisibleChips?: number
/**
* When true, clicking a chip's label copies its value to the clipboard
* instead of being inert. The remove (×) button keeps its own behaviour.
*/
copyChipOnClick?: boolean
}
const COMMA_REGEX = /[,\n]/
@@ -109,6 +116,7 @@ export function MultiSelect(props: MultiSelectProps) {
const [inputValue, setInputValue] = React.useState('')
const [open, setOpen] = React.useState(false)
const [expanded, setExpanded] = React.useState(false)
const selectedSet = React.useMemo(
() => new Set(props.selected),
@@ -195,6 +203,25 @@ export function MultiSelect(props: MultiSelectProps) {
}
}
const handleCopyChip = React.useCallback(
async (
event: React.MouseEvent<HTMLButtonElement>,
value: string,
label: string
) => {
// Prevent the click from toggling the combobox popup or focusing input.
event.preventDefault()
event.stopPropagation()
const ok = await copyToClipboard(value)
if (ok) {
toast.success(t('Copied: {{model}}', { model: label }))
} else {
toast.error(t('Failed to copy'))
}
},
[t]
)
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
// Enter without a highlighted option commits the typed value.
if (event.key === 'Enter' && props.allowCreate && canCreate) {
@@ -231,26 +258,69 @@ export function MultiSelect(props: MultiSelectProps) {
>
<ComboboxValue>
{(values: string[]) => {
const visibleValues =
typeof props.maxVisibleChips === 'number'
? values.slice(0, props.maxVisibleChips)
: values
const shouldLimit =
typeof props.maxVisibleChips === 'number' && !expanded
const visibleValues = shouldLimit
? values.slice(0, props.maxVisibleChips)
: values
const hiddenCount = values.length - visibleValues.length
return (
<>
{visibleValues.map((value) => (
<ComboboxChip key={value}>
<span className='max-w-[16rem] truncate'>
{labelMap.get(value) ?? value}
</span>
</ComboboxChip>
))}
{visibleValues.map((value) => {
const label = labelMap.get(value) ?? value
return (
<ComboboxChip key={value}>
{props.copyChipOnClick ? (
<button
type='button'
onClick={(event) =>
handleCopyChip(event, value, label)
}
onPointerDown={(event) => event.stopPropagation()}
title={t('Click to copy')}
className='max-w-[16rem] cursor-pointer truncate rounded-sm hover:underline'
>
{label}
</button>
) : (
<span className='max-w-[16rem] truncate'>{label}</span>
)}
</ComboboxChip>
)
})}
{hiddenCount > 0 && (
<span className='bg-muted text-muted-foreground flex h-[calc(--spacing(5.25))] w-fit items-center justify-center rounded-sm px-1.5 text-xs font-medium whitespace-nowrap'>
<button
type='button'
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
setExpanded(true)
}}
onPointerDown={(event) => event.stopPropagation()}
title={t('Show All')}
className='bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground flex h-[calc(--spacing(5.25))] w-fit cursor-pointer items-center justify-center rounded-sm px-1.5 text-xs font-medium whitespace-nowrap transition-colors'
>
{t('+{{count}} more', { count: hiddenCount })}
</span>
</button>
)}
{expanded &&
typeof props.maxVisibleChips === 'number' &&
values.length > props.maxVisibleChips && (
<button
type='button'
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
setExpanded(false)
}}
onPointerDown={(event) => event.stopPropagation()}
title={t('Collapse')}
className='bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground flex h-[calc(--spacing(5.25))] w-fit cursor-pointer items-center justify-center rounded-sm px-1.5 text-xs font-medium whitespace-nowrap transition-colors'
>
{t('Collapse')}
</button>
)}
</>
)
}}