diff --git a/web/default/src/components/multi-select.tsx b/web/default/src/components/multi-select.tsx index d8309680..d1f9f178 100644 --- a/web/default/src/components/multi-select.tsx +++ b/web/default/src/components/multi-select.tsx @@ -32,6 +32,7 @@ import { ComboboxItem, ComboboxList, ComboboxValue, + useComboboxAnchor, } from '@/components/ui/combobox' export type Option = { @@ -58,6 +59,11 @@ interface MultiSelectProps { id?: string /** Disable the entire control. */ disabled?: boolean + /** + * Limits rendered chips while keeping all values selected. + * Hidden values remain searchable/removable from the dropdown. + */ + maxVisibleChips?: number } const COMMA_REGEX = /[,,\n]/ @@ -87,6 +93,8 @@ function splitDraft(value: string): { completed: string[]; draft: string } { * - A "Add \"\"" item appears at the top of the dropdown when the * typed text doesn't match any option. * - Backspace on an empty input removes the last selected chip (Base UI default). + * - `maxVisibleChips` can cap large selections and show a compact "+N more" + * summary so forms do not grow vertically without bound. * * Focus/border styling is inherited from `ComboboxChips`, which uses the same * tokens as `Input` so it stays visually consistent with other form fields. @@ -95,6 +103,10 @@ export function MultiSelect(props: MultiSelectProps) { const { t } = useTranslation() const placeholder = props.placeholder ?? t('Select items...') + // Anchor the popup to the chips container so its width tracks the entire + // input row, not just the leftover space at the end of wrapped chips. + const chipsAnchorRef = useComboboxAnchor() + const [inputValue, setInputValue] = React.useState('') const [open, setOpen] = React.useState(false) @@ -213,17 +225,35 @@ export function MultiSelect(props: MultiSelectProps) { onOpenChange={setOpen} disabled={props.disabled} > - + - {(values: string[]) => - values.map((value) => ( - - - {labelMap.get(value) ?? value} - - - )) - } + {(values: string[]) => { + const visibleValues = + typeof props.maxVisibleChips === 'number' + ? values.slice(0, props.maxVisibleChips) + : values + const hiddenCount = values.length - visibleValues.length + + return ( + <> + {visibleValues.map((value) => ( + + + {labelMap.get(value) ?? value} + + + ))} + {hiddenCount > 0 && ( + + {t('+{{count}} more', { count: hiddenCount })} + + )} + + ) + }} - + {(item: string) => { diff --git a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx index 9d48fb9c..92302365 100644 --- a/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx +++ b/web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx @@ -2172,6 +2172,7 @@ export function ChannelMutateDrawer({ )} allowCreate createLabel='Add custom model "{{value}}"' + maxVisibleChips={8} /> {modelMappingGuardrail.exposedTargetModels diff --git a/web/default/src/features/home/components/sections/hero.tsx b/web/default/src/features/home/components/sections/hero.tsx index 7f138b7f..58a4ae3f 100644 --- a/web/default/src/features/home/components/sections/hero.tsx +++ b/web/default/src/features/home/components/sections/hero.tsx @@ -17,11 +17,11 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { Link } from '@tanstack/react-router' -import { ArrowRight, BookOpen } from 'lucide-react' import { CherryStudio } from '@lobehub/icons' +import { ArrowRight, BookOpen } from 'lucide-react' import { useTranslation } from 'react-i18next' -import { Button } from '@/components/ui/button' import { useStatus } from '@/hooks/use-status' +import { Button } from '@/components/ui/button' import { HeroTerminalDemo } from '../hero-terminal-demo' interface HeroProps { @@ -32,7 +32,7 @@ interface HeroProps { // Stylized three-dots indicator representing "More" const MoreIcon = () => ( ( export function Hero(props: HeroProps) { const { t } = useTranslation() const { status } = useStatus() - const docsUrl = (status?.docs_link as string | undefined) || 'https://docs.newapi.pro' + const docsUrl = + (status?.docs_link as string | undefined) || 'https://docs.newapi.pro' const renderDocsButton = () => { const isExternal = docsUrl.startsWith('http') @@ -54,16 +55,12 @@ export function Hero(props: HeroProps) { return ( + } > - + {t('Docs')} ) @@ -71,10 +68,10 @@ export function Hero(props: HeroProps) { return ( ) @@ -105,7 +102,7 @@ export function Hero(props: HeroProps) {
{/* Top Pill Badge */}
@@ -141,7 +138,7 @@ export function Hero(props: HeroProps) { {props.isAuthenticated ? ( <>