feat(advanced-custom): remove fallback option and enhance path matching for advanced custom routes
This commit is contained in:
+83
-13
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user