From 52bd09a1e2e52396511692bc6fb8e0b972742980 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Thu, 29 Jan 2026 17:31:46 +0100 Subject: [PATCH 01/11] add NotAvailable component --- src/components/NotAvailable/NotAvailable.tsx | 69 ++++++++++++++++++++ src/components/index.ts | 1 + 2 files changed, 70 insertions(+) create mode 100644 src/components/NotAvailable/NotAvailable.tsx diff --git a/src/components/NotAvailable/NotAvailable.tsx b/src/components/NotAvailable/NotAvailable.tsx new file mode 100644 index 00000000..e5e405e5 --- /dev/null +++ b/src/components/NotAvailable/NotAvailable.tsx @@ -0,0 +1,69 @@ +import React from "react"; + +import { + CLASSPREFIX as eccgui, + Tag, + TagProps, + Tooltip, + TooltipProps, +} from "../../../index"; +import { TestableComponent } from "../interfaces"; +export interface NotAvailableProps extends TestableComponent, Pick, Pick { + /** + * Text displayed by the element. + */ + label?: TagProps["children"]; + /** + * Add a tooltip to the element. + * You need to set an empty string `""` to remove it. + */ + tooltip?: TooltipProps["content"]; + /** + * Specify the display of the used `Tag` component. + */ + tagProps?: Omit; + /** + * Specify the display of the used `Tooltip` component. + */ + tooltipProps?: Omit; + /** + * Do not use the `Tag` component for the display. + * The `intent` state can be displayed only on the tooltip then. + */ + noTag?: boolean; +} + +/** + * Simple placeholder element that can be used to display info about missing data. + */ +export const NotAvailable = ({ + label = "n/a", + tooltip = "not available", + icon, + intent, + tagProps, + tooltipProps, + className, + noTag = false, + ...otherProps +}: NotAvailableProps) => { + const defaultTagProps : TagProps = { icon, intent, emphasis: "weaker", className: `${eccgui}-notavailable` + className ? ` ${className}` : "" }; + const naElement = noTag === false ? ( + + { label ?? "n/a"} + + ) : <>{ label ?? "n/a"}; + const defaultTooltipProps : TooltipProps = { + children: naElement, + content: tooltip, + intent, + }; + + return tooltip ? : naElement; +}; + +export default NotAvailable; diff --git a/src/components/index.ts b/src/components/index.ts index 6f7fdf95..5e1c496d 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -23,6 +23,7 @@ export * from "./Link/Link"; export * from "./List/List"; export * from "./Menu"; export * from "./MultiSuggestField"; +export * from "./NotAvailable/NotAvailable"; export * from "./Notification"; export * from "./OverviewItem"; export * from "./Pagination/Pagination"; From b921c68529c4dbd4242b1bc51b278eec7cf94ffa Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Mon, 2 Feb 2026 10:32:54 +0100 Subject: [PATCH 02/11] add tooltip indicator if no tag is used --- src/components/NotAvailable/NotAvailable.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/NotAvailable/NotAvailable.tsx b/src/components/NotAvailable/NotAvailable.tsx index e5e405e5..47b14adb 100644 --- a/src/components/NotAvailable/NotAvailable.tsx +++ b/src/components/NotAvailable/NotAvailable.tsx @@ -61,6 +61,7 @@ export const NotAvailable = ({ children: naElement, content: tooltip, intent, + addIndicator: noTag, }; return tooltip ? : naElement; From 8764a22c290a5a9711e4c20ca7946c03d92798da Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Wed, 4 Feb 2026 15:01:01 +0100 Subject: [PATCH 03/11] provide an inline option for content blob togglers --- .../ContentBlobToggler/ContentBlobToggler.tsx | 15 +++++++++++---- .../StringPreviewContentBlobToggler.tsx | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/cmem/ContentBlobToggler/ContentBlobToggler.tsx b/src/cmem/ContentBlobToggler/ContentBlobToggler.tsx index 26b214ab..91b9a06c 100644 --- a/src/cmem/ContentBlobToggler/ContentBlobToggler.tsx +++ b/src/cmem/ContentBlobToggler/ContentBlobToggler.tsx @@ -1,6 +1,6 @@ -import React, { useState } from "react"; +import React, { useState} from "react"; -import { Link, Spacing } from "../../index"; +import { Link, Spacing, InlineText } from "../../index"; export interface ContentBlobTogglerProps extends React.HTMLAttributes { /** @@ -31,6 +31,10 @@ export interface ContentBlobTogglerProps extends React.HTMLAttributes {!isExtended ? ( <> @@ -76,7 +81,7 @@ export function ContentBlobToggler({ {fullviewContent} {enableToggler && (
- + {forceInline ? <>{" "} : } ); + + return forceInline ? {tooglerDisplay} : tooglerDisplay; } diff --git a/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx b/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx index e5ec7a79..08c19a43 100644 --- a/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx +++ b/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx @@ -54,6 +54,7 @@ export function StringPreviewContentBlobToggler({ allowedHtmlElementsInPreview, noTogglerContentSuffix, firstNonEmptyLineOnly, + ...otherContentBlobTogglerProps }: StringPreviewContentBlobTogglerProps) { // need to test `firstNonEmptyLineOnly` until property is removed const useOnlyTest: StringPreviewContentBlobTogglerProps["useOnly"] = firstNonEmptyLineOnly @@ -105,6 +106,7 @@ export function StringPreviewContentBlobToggler({ fullviewContent={fullviewContent} startExtended={startExtended} enableToggler={enableToggler} + {...otherContentBlobTogglerProps} /> ); } From 432f99d9d33271c637eeac99a431325fa4cc308e Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Thu, 5 Feb 2026 12:26:17 +0100 Subject: [PATCH 04/11] add story for NotAvailable and update changelog --- CHANGELOG.md | 12 ++++++++---- .../NotAvailable/NotAvailable.stories.tsx | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/components/NotAvailable/NotAvailable.stories.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b550f94..0d666913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - component for hiding elements in specific media - `` - - force children to get displayed as inline content + - force children to get displayed as inline content +- `` + - simple placeholder element that can be used to display info about missing data - `` - - `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly` + - `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly` +- `` + - `forceInline` property: force inline rendering ### Fixed @@ -21,7 +25,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - create more whitespace inside `small` tag - reduce visual impact of border - `` - - take Markdown rendering into account before testing the maximum preview length + - take Markdown rendering into account before testing the maximum preview length - `` - header-menu items are vertically centered now @@ -41,7 +45,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Deprecated - `` - - `firstNonEmptyLineOnly` will be removed, is replaced by `useOnly="firstNonEmptyLine"` + - `firstNonEmptyLineOnly` will be removed, is replaced by `useOnly="firstNonEmptyLine"` ## [25.0.0] - 2025-12-01 diff --git a/src/components/NotAvailable/NotAvailable.stories.tsx b/src/components/NotAvailable/NotAvailable.stories.tsx new file mode 100644 index 00000000..29dde1e3 --- /dev/null +++ b/src/components/NotAvailable/NotAvailable.stories.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/react"; + +import { NotAvailable } from "../../../index"; + +export default { + title: "Components/NotAvailable", + component: NotAvailable, + argTypes: {}, +} as Meta; + +const TemplateFull: StoryFn = (args) => ; + +export const Default = TemplateFull.bind({}); +Default.args = {}; From 60624bdd7b00851e9946a2d13ea25cb9cf4ea462 Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Fri, 6 Feb 2026 15:29:55 +0100 Subject: [PATCH 05/11] Add isValidNewOption to MultiSuggestField --- CHANGELOG.md | 2 ++ src/components/MultiSelect/MultiSelect.tsx | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d666913..d8b3b5e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly` - `` - `forceInline` property: force inline rendering +- ``: + - `isValidNewOption` property: Checks if an input string is or can be turned into a valid new option. ### Fixed diff --git a/src/components/MultiSelect/MultiSelect.tsx b/src/components/MultiSelect/MultiSelect.tsx index 3685ed86..b0e0bd5f 100644 --- a/src/components/MultiSelect/MultiSelect.tsx +++ b/src/components/MultiSelect/MultiSelect.tsx @@ -70,9 +70,11 @@ interface MultiSuggestFieldCommonProps */ newItemCreationText?: string; /** - * Allows to creates new item from a given query. If this is not provided then no new items can be created. + * Allows to create new item from a given query. If this is not provided then no new items can be created. */ createNewItemFromQuery?: (query: string) => T; + /** Validates if a new item can be created from the current query string. */ + isValidNewOption?: (query: string) => boolean; /** * Items that were newly created and not taken from the list will be post-fixed with this string. */ @@ -159,6 +161,7 @@ export function MultiSuggestField({ newItemPostfix = " (new item)", disabled, createNewItemFromQuery, + isValidNewOption, requestDelay = 0, clearQueryOnSelection = false, className, @@ -387,7 +390,9 @@ export function MultiSuggestField({ */ const handleOnKeyUp = (event: React.KeyboardEvent) => { if (event.key === "Enter" && !filteredItems.length && !!requestState.current.query && createNewItemFromQuery) { - createNewItem(requestState.current.query); + if(!isValidNewOption || isValidNewOption(requestState.current.query)) { + createNewItem(requestState.current.query); + } } inputRef.current?.focus(); }; @@ -403,7 +408,11 @@ export function MultiSuggestField({ if (focusedItem) { onItemSelect(focusedItem); } else { - onItemSelect(createNewItem(requestState.current.query)); + if (!isValidNewOption || isValidNewOption(requestState.current.query)) { + onItemSelect(createNewItem(requestState.current.query)); + } else { + return + } } requestState.current.query = ""; setTimeout(() => inputRef.current?.focus()); @@ -418,7 +427,7 @@ export function MultiSuggestField({ * @returns */ const newItemRenderer = (label: string, active: boolean, handleClick: React.MouseEventHandler) => { - if (!createNewItemFromQuery) return undefined; + if (!createNewItemFromQuery || isValidNewOption && !isValidNewOption(label)) return undefined; const clickHandler = (e: React.MouseEvent) => { createNewItem(label); handleClick(e); From 24d10e0787e0b7d61bf5daca1a235f8e0fd1edb2 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Mon, 9 Feb 2026 17:05:53 +0100 Subject: [PATCH 06/11] fix border of tag in multi suggest component --- CHANGELOG.md | 2 ++ src/components/Tag/tag.scss | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8b3b5e2..329223ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - take Markdown rendering into account before testing the maximum preview length - `` - header-menu items are vertically centered now +- ``: + - border of the BlueprintJS `Tag` elements were fixed ### Changed diff --git a/src/components/Tag/tag.scss b/src/components/Tag/tag.scss index 8abfb2e1..43f05b95 100644 --- a/src/components/Tag/tag.scss +++ b/src/components/Tag/tag.scss @@ -30,8 +30,6 @@ $tag-round-adjustment: 0 !default; @import "~@blueprintjs/core/src/components/tag/tag"; .#{$eccgui}-tag__item { - --eccgui-tag-border-width: 1px; - flex-grow: 0; flex-shrink: 0; min-width: calc(#{$tag-height} - 2px); @@ -141,6 +139,8 @@ $tag-round-adjustment: 0 !default; } .#{$ns}-tag { + --eccgui-tag-border-width: 1px; + border-style: solid; border-width: var(--eccgui-tag-border-width); From 23720eb086e9c58801c3f2dbae5c38c3b0812952 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 10 Feb 2026 09:26:10 +0100 Subject: [PATCH 07/11] use var for header height --- src/components/Application/_header.scss | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/Application/_header.scss b/src/components/Application/_header.scss index 27d7fd49..0a13abb0 100644 --- a/src/components/Application/_header.scss +++ b/src/components/Application/_header.scss @@ -52,19 +52,22 @@ $shell-header-icon-03: $eccgui-color-applicationheader-text !default; /// Item link $shell-header-link: $blue-60 !default; +/// Height +$shell-header-height: mini-units(8) !default; + // load library sub component @import "~@carbon/react/scss/components/ui-shell/header/index"; // tweak original layout .#{$prefix}--header { - height: mini-units(8); + height: $shell-header-height; } .#{$prefix}--header__action, .#{$prefix}--header__action.#{$prefix}--btn--icon-only { - width: mini-units(8); - height: mini-units(8); + width: $shell-header-height; + height: $shell-header-height; padding-block-start: 0; background-color: transparent; @@ -128,7 +131,7 @@ span.#{$prefix}--header__name { } .#{$prefix}--header__menu .#{$prefix}--header__menu-item { - height: mini-units(8); + height: $shell-header-height; } // tweak original colors (as long as config does not work properly) @@ -255,15 +258,15 @@ a.#{$prefix}--header__menu-item:focus > svg { // adjust position of all other modal dialogs .#{$ns}-dialog-container { - top: mini-units(8); - left: mini-units(8); - width: calc(100% - #{mini-units(8)}); - min-height: calc(100% - #{mini-units(8)}); + top: $shell-header-height; + left: $shell-header-height; + width: calc(100% - #{$shell-header-height}); + min-height: calc(100% - #{$shell-header-height}); } .#{$eccgui}-dialog__wrapper { - max-width: calc(100vw - #{mini-units(8)} - #{2 * $eccgui-size-block-whitespace}); - max-height: calc(100vh - #{mini-units(8)} - #{2 * $eccgui-size-block-whitespace}); + max-width: calc(100vw - #{$shell-header-height} - #{2 * $eccgui-size-block-whitespace}); + max-height: calc(100vh - #{$shell-header-height} - #{2 * $eccgui-size-block-whitespace}); margin: 0; } } From dcdbc36de7d773892d710c15960e2026abd179e2 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 10 Feb 2026 10:26:46 +0100 Subject: [PATCH 08/11] simplify fetching styles for small/large text blobs --- src/components/Typography/typography.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Typography/typography.scss b/src/components/Typography/typography.scss index 87d20c59..623e4797 100644 --- a/src/components/Typography/typography.scss +++ b/src/components/Typography/typography.scss @@ -63,12 +63,14 @@ mark { line-height: $eccgui-size-typo-text-lineheight; } -.#{$eccgui}-typography__contentblock.#{$eccgui}-typography--small { +.#{$eccgui}-typography__contentblock.#{$eccgui}-typography--small, +.#{$eccgui}-typography--small { font-size: $eccgui-size-typo-caption; line-height: $eccgui-size-typo-caption-lineheight; } -.#{$eccgui}-typography__contentblock.#{$eccgui}-typography--large { +.#{$eccgui}-typography__contentblock.#{$eccgui}-typography--large, +.#{$eccgui}-typography--large { font-size: $eccgui-size-typo-subtitle; line-height: $eccgui-size-typo-subtitle-lineheight; } From b06bec76470314241227f2b39eaf2f3f26a4e9d7 Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 10 Feb 2026 15:27:30 +0100 Subject: [PATCH 09/11] add togglerSize property --- CHANGELOG.md | 4 ++++ src/components/ContextOverlay/ContextMenu.tsx | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 329223ad..95e8968f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `forceInline` property: force inline rendering - ``: - `isValidNewOption` property: Checks if an input string is or can be turned into a valid new option. +- `` + - `togglerSize`: replaces the deprecated `togglerLarge` property ### Fixed @@ -50,6 +52,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - `firstNonEmptyLineOnly` will be removed, is replaced by `useOnly="firstNonEmptyLine"` +- `` + - `togglerLarge`: replaced by the more versatile `togglerSize` property ## [25.0.0] - 2025-12-01 diff --git a/src/components/ContextOverlay/ContextMenu.tsx b/src/components/ContextOverlay/ContextMenu.tsx index c51ca0b3..d0ec4bf3 100644 --- a/src/components/ContextOverlay/ContextMenu.tsx +++ b/src/components/ContextOverlay/ContextMenu.tsx @@ -2,7 +2,7 @@ import React, { ReactElement } from "react"; import { CLASSPREFIX as eccgui } from "../../configuration/constants"; import { ValidIconName } from "../Icon/canonicalIconNames"; -import IconButton from "../Icon/IconButton"; +import { IconButton, IconButtonProps } from "../Icon/IconButton"; import { TestableComponent } from "../interfaces"; import Menu from "../Menu/Menu"; @@ -28,8 +28,13 @@ export interface ContextMenuProps extends TestableComponent { * Text displayed as title or tooltip on toggler element. */ togglerText?: string; + /** + * Allow to de- and increase the size of the default toggler button. + */ + togglerSize?: IconButtonProps["size"]; /** * Toggler element is displayed larger than normal. + * @deprecated (v26) use `togglerSize="large" instead */ togglerLarge?: boolean; /** @@ -62,6 +67,7 @@ export const ContextMenu = ({ contextOverlayProps, disabled, togglerLarge = false, + togglerSize, /* FIXME: The Tooltip component can interfere with the opened menu, since it is implemented via portal and may cover the menu, so by default we use the title attribute instead of Tooltip. */ tooltipAsTitle = true, @@ -76,7 +82,7 @@ export const ContextMenu = ({ tooltipAsTitle={tooltipAsTitle} name={[togglerElement]} text={togglerText} - large={togglerLarge} + size={togglerLarge ? "large" : togglerSize} disabled={!!disabled} data-test-id={dataTestId ?? undefined} data-testid={dataTestid ?? undefined} From aff955368da8b205605a96480fbf0d2f9faa33bd Mon Sep 17 00:00:00 2001 From: Michael Haschke Date: Tue, 10 Feb 2026 15:28:37 +0100 Subject: [PATCH 10/11] support the size property of the BlueprintJS button by forwarding it to the icon correctly --- src/components/Button/Button.tsx | 9 +++++++-- src/components/Icon/IconButton.tsx | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index fcf25392..7efb0d02 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -104,13 +104,18 @@ export const Button = ({ const ButtonType = restProps.href ? BlueprintAnchorButton : BlueprintButton; + const iconSize = { + small: restProps["size"] === "small", + large: restProps["size"] === "large", + }; + const button = ( : icon} - rightIcon={typeof rightIcon === "string" ? : rightIcon} + icon={typeof icon === "string" ? : icon} + rightIcon={typeof rightIcon === "string" ? : rightIcon} > {children} {badge && ( diff --git a/src/components/Icon/IconButton.tsx b/src/components/Icon/IconButton.tsx index 969ffde2..1be9f885 100644 --- a/src/components/Icon/IconButton.tsx +++ b/src/components/Icon/IconButton.tsx @@ -54,8 +54,8 @@ export const IconButton = ({ swapPlaceholderDelay: 10, }; const iconProps = { - small: restProps.small, - large: restProps.large, + small: restProps.small || restProps["size"] === "small", + large: restProps.large || restProps["size"] === "large", tooltipText: tooltipAsTitle ? undefined : text, tooltipProps: tooltipProps ? { From a6bffc253181f87e93005237acdbf58ff6f141c5 Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Fri, 13 Feb 2026 10:30:10 +0100 Subject: [PATCH 11/11] Several MultiSelect improvements - `searchListPredicate` property: Allows to filter the complete list of search options at once. - Following optional BlueprintJs properties are forwarded now to override default behaviour: `noResults`, `createNewItemRenderer` and `itemRenderer` - by default, if no searchPredicate or searchListPredicate is defined, the filtering is done via case-insensitive multi-word filtering. - `searchPreficate`: replaced by the, in some cases, more efficient `searchListPredicate` --- CHANGELOG.md | 12 +++-- src/components/MultiSelect/MultiSelect.tsx | 55 +++++++++++++++++----- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e8968f..34b21eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly` - `` - `forceInline` property: force inline rendering -- ``: - - `isValidNewOption` property: Checks if an input string is or can be turned into a valid new option. - `` - `togglerSize`: replaces the deprecated `togglerLarge` property +- `: + - `searchListPredicate` property: Allows to filter the complete list of search options at once. + - Following optional BlueprintJs properties are forwarded now to override default behaviour: `noResults`, `createNewItemRenderer` and `itemRenderer` + - `isValidNewOption` property: Checks if an input string is or can be turned into a valid new option. ### Fixed @@ -32,7 +34,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - take Markdown rendering into account before testing the maximum preview length - `` - header-menu items are vertically centered now -- ``: +- ``: - border of the BlueprintJS `Tag` elements were fixed ### Changed @@ -47,6 +49,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - `` - `` and `` +- `` + - by default, if no searchPredicate or searchListPredicate is defined, the filtering is done via case-insensitive multi-word filtering. ### Deprecated @@ -54,6 +58,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `firstNonEmptyLineOnly` will be removed, is replaced by `useOnly="firstNonEmptyLine"` - `` - `togglerLarge`: replaced by the more versatile `togglerSize` property +- `` + - `searchPreficate`: replaced by the, in some cases, more efficient `searchListPredicate` ## [25.0.0] - 2025-12-01 diff --git a/src/components/MultiSelect/MultiSelect.tsx b/src/components/MultiSelect/MultiSelect.tsx index 09dac3f8..72af76fa 100644 --- a/src/components/MultiSelect/MultiSelect.tsx +++ b/src/components/MultiSelect/MultiSelect.tsx @@ -10,7 +10,15 @@ import { removeExtraSpaces } from "../../common/utils/stringUtils"; import { CLASSPREFIX as eccgui } from "../../configuration/constants"; import { TestableComponent } from "../interfaces"; -import { ContextOverlayProps, Highlighter, IconButton, MenuItem, OverflowText, Spinner } from "./../../index"; +import { + ContextOverlayProps, + Highlighter, + highlighterUtils, + IconButton, + MenuItem, + OverflowText, + Spinner +} from "./../../index"; export interface MultiSuggestFieldSelectionProps { newlySelected?: T; @@ -18,9 +26,10 @@ export interface MultiSuggestFieldSelectionProps { createdItems: Partial[]; } -interface MultiSuggestFieldCommonProps +export interface MultiSuggestFieldCommonProps extends TestableComponent, - Pick, "items" | "placeholder" | "openOnKeyDown"> { + Pick, "items" | "placeholder" | "openOnKeyDown" | "noResults" | "createNewItemRenderer">, + Partial, "itemRenderer">> { /** * Additional class name, space separated. */ @@ -105,9 +114,20 @@ interface MultiSuggestFieldCommonProps wrapperProps?: React.HTMLAttributes; /** * Function that allows us to filter values from the option list. - * If not provided, values are filtered by their labels + * + * @deprecated (v26) use `searchListPredicate` instead. */ searchPredicate?: (item: T, query: string) => boolean; + + /** + * Returns the filtered the search option list. + * By default, a case-insensitive multi-word filtering is applied. + * + * @param items The options. + * @param query The search query. + */ + searchListPredicate?: (items: T[], query: string) => T[] + /** * Limits the height of the input target plus its dropdown menu when it is opened. * Need to be a `number not greater than 100` (as `vh`, a unit describing a length relative to the viewport height) or `true` (equals 100). @@ -169,13 +189,14 @@ export function MultiSuggestField({ "data-testid": dataTestid, wrapperProps, searchPredicate, + searchListPredicate, limitHeightOpened, intent, ...otherMultiSelectProps }: MultiSuggestFieldProps) { // Options created by a user const createdItems = useRef([]); - // Options passed ouside (f.e. from the backend) + // Options passed outside (f.e. from the backend) const [externalItems, setExternalItems] = React.useState([...items]); // All options (created and passed) that match the query const [filteredItems, setFilteredItems] = React.useState([]); @@ -267,9 +288,14 @@ export function MultiSuggestField({ setSelectedItems(filteredItems); }; - const defaultFilterPredicate = (item: T, query: string) => { - return itemLabel(item).toLowerCase().includes(query); - }; + /** Does a case-insensitive multi-word search in the item label. */ + const defaultSearchListPredicate = (items: T[], query: string): T[] => { + const searchWords = highlighterUtils.extractSearchWords(query, true); + return items.filter(item => { + const searchIn = itemLabel(item).toLowerCase() + return highlighterUtils.matchesAllWords(searchIn, searchWords); + }) + } /** * selects and deselects an item from selection list @@ -308,10 +334,17 @@ export function MultiSuggestField({ if (requestState.current.query === query) { // Only use most recent request const outsideOptions = [...(resultFromQuery ?? externalItems)]; - const filter = searchPredicate ?? defaultFilterPredicate; + let itemFilter = defaultSearchListPredicate + if(searchListPredicate) { + itemFilter = searchListPredicate + } else if(searchPredicate) { + itemFilter = (items, query) => { + return items.filter((item) => searchPredicate(item, query)) + } + } setFilteredItems( - [...outsideOptions, ...createdItems.current].filter((item) => filter(item, query.toLowerCase())) + itemFilter([...outsideOptions, ...createdItems.current], query) ); setShowSpinner(false); } @@ -468,7 +501,6 @@ export function MultiSuggestField({ ? "Search for item, or enter term to create new one..." : undefined } - {...otherMultiSelectProps} query={requestState.current.query} onQueryChange={onQueryChange} items={filteredItems} @@ -537,6 +569,7 @@ export function MultiSuggestField({ : undefined, } as BlueprintMultiSelectProps["popoverContentProps"] } + {...otherMultiSelectProps} /> );