diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b550f94..ef821cc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,12 @@ 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 - `` - - `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` +- new icons: + - `state-confirmed-all` + - `state-declined-all` ### Fixed @@ -21,9 +24,9 @@ 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 -- `` - - header-menu items are vertically centered now + - take Markdown rendering into account before testing the maximum preview length +- `` + - fix `disabled` property update ### Changed @@ -37,16 +40,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `` - `` - `` and `` +- `` + - reduce stroke width to only 1px ### 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 This is a major release, and it might be not compatible with your current usage of our library. Please read about the necessary changes in the section about how to migrate. +### Added + +- `` + - Add parameter `active` to activity control action to set the `active` state of its button. + +### Changed + +- ``: + - Change default filter predicate to match multi-word queries. + ### Migration from v24 to v25 - remove deprecated components, properties and imports from your project, if the info cannot be found here then it was already mentioned in **Deprecated** sections of the past changelogs diff --git a/src/cmem/ActivityControl/ActivityControlWidget.tsx b/src/cmem/ActivityControl/ActivityControlWidget.tsx index efda5060..b529f96a 100644 --- a/src/cmem/ActivityControl/ActivityControlWidget.tsx +++ b/src/cmem/ActivityControl/ActivityControlWidget.tsx @@ -1,17 +1,20 @@ import React from "react"; -import { ValidIconName } from "../../components/Icon/canonicalIconNames"; -import { IconProps } from "../../components/Icon/Icon"; -import { TestIconProps } from "../../components/Icon/TestIcon"; -import { TestableComponent } from "../../components/interfaces"; -import { ProgressBarProps } from "../../components/ProgressBar/ProgressBar"; -import { SpinnerProps } from "../../components/Spinner/Spinner"; -import { CLASSPREFIX as eccgui } from "../../configuration/constants"; +import {ValidIconName} from "../../components/Icon/canonicalIconNames"; +import {IconProps} from "../../components/Icon/Icon"; +import {TestIconProps} from "../../components/Icon/TestIcon"; +import {TestableComponent} from "../../components/interfaces"; +import {ProgressBarProps} from "../../components/ProgressBar/ProgressBar"; +import {SpinnerProps} from "../../components/Spinner/Spinner"; +import {CLASSPREFIX as eccgui} from "../../configuration/constants"; import { Card, ContextMenu, + ContextOverlay, IconButton, MenuItem, + Notification, + NotificationProps, OverflowText, OverviewItem, OverviewItemActions, @@ -97,7 +100,7 @@ interface IActivityContextMenu extends TestableComponent { export interface ActivityControlWidgetAction extends TestableComponent { // The action that should be triggered action: () => void; - // The tooltip that should be shown over the action icon + // The tooltip that should be shown over the action icon on hover tooltip?: string; // The icon of the action button icon: ValidIconName | React.ReactElement; @@ -105,6 +108,16 @@ export interface ActivityControlWidgetAction extends TestableComponent { disabled?: boolean; // Warning state hasStateWarning?: boolean; + // Active state + active?: boolean + /** A notification that is shown in an overlay pointing at the activity action button. */ + notification?: { + message: string + onClose: () => void + intent?: NotificationProps["intent"] + // Timeout in ms before notification is closed. Default: none + timeout?: number + } } interface IActivityMenuAction extends ActivityControlWidgetAction { @@ -210,26 +223,39 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) { > {activityActions && activityActions.map((action, idx) => { - return ( - - ); + const ActionButton = () => + return action.notification ? + } + defaultIsOpen={true} + onClose={action.notification.onClose} + > + + : + })} {additionalActions} {activityContextMenu && activityContextMenu.menuItems.length > 0 && ( diff --git a/src/components/DecoupledOverlay/DecoupledOverlay.stories.tsx b/src/components/DecoupledOverlay/DecoupledOverlay.stories.tsx new file mode 100644 index 00000000..ecc5eb35 --- /dev/null +++ b/src/components/DecoupledOverlay/DecoupledOverlay.stories.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { Meta, StoryFn } from "@storybook/react"; + +import { DecoupledOverlay, DecoupledOverlayProps, Tag, WhiteSpaceContainer } from "../../../index"; + +export default { + title: "Components/DecoupledOverlay", + component: DecoupledOverlay, + argTypes: {}, +} as Meta; + +const Template: StoryFn = (args: DecoupledOverlayProps) => { + return ( + <> + Decoupled target + + + ); +}; + +export const Default = Template.bind({}); + +Default.args = { + children: ( + + Decoupled overlay + + ), + targetSelectorOrElement: "#decoupledTarget", +}; diff --git a/src/components/DecoupledOverlay/DecoupledOverlay.tsx b/src/components/DecoupledOverlay/DecoupledOverlay.tsx new file mode 100644 index 00000000..d5dde670 --- /dev/null +++ b/src/components/DecoupledOverlay/DecoupledOverlay.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { createPortal } from "react-dom"; +import { Classes as BlueprintClasses } from "@blueprintjs/core"; +import { createPopper } from "@popperjs/core"; + +import { CLASSPREFIX as eccgui } from "../../configuration/constants"; +import { ContextOverlayProps, TestableComponent, TooltipSize } from "../../index"; + +export interface DecoupledOverlayProps + extends React.HTMLAttributes, + TestableComponent, + Pick { + /** + * Element that should be used. The step content is displayed as a tooltip instead of a modal. + * In case of an array, the first match is highlighted. */ + targetSelectorOrElement: string | Element; + /** + * The size of the overlay. + * */ + size?: TooltipSize; +} + +/** + * Use an overlay popover without the necessity to use a target that need to be rendered in place. + * The target is referenced by a selector string or element object. + * It can exist somewhere in the DOM, but it must exist when the overlay is rendered. + * It is always displayed, close it by removement. + */ +export const DecoupledOverlay = ({ + targetSelectorOrElement, + usePortal = true, + portalContainer = document.body, + minimal = false, + placement = "auto", + size = "large", + children, +}: DecoupledOverlayProps) => { + const overlayRef = React.useCallback( + (overlay: HTMLDivElement | null) => { + const target = + typeof targetSelectorOrElement === "string" + ? document.querySelector(targetSelectorOrElement) + : targetSelectorOrElement; + if (overlay && target) { + createPopper(target, overlay, { + placement: placement, + modifiers: [ + { + name: "offset", + options: { + offset: [0, 15], + }, + }, + ], + }); + } + }, + [targetSelectorOrElement] + ); + + const overlay = ( +
+ {!minimal && ( +
+ )} +
{children}
+
+ ); + + return usePortal ? createPortal(overlay, portalContainer) : overlay; +}; + +export default DecoupledOverlay; diff --git a/src/components/DecoupledOverlay/_decoupledoverlay.scss b/src/components/DecoupledOverlay/_decoupledoverlay.scss new file mode 100644 index 00000000..2c514223 --- /dev/null +++ b/src/components/DecoupledOverlay/_decoupledoverlay.scss @@ -0,0 +1,46 @@ +.#{$eccgui}-decoupled-overlay__arrow { + &::before { + background: $card-background-color; + } + + .#{$eccgui}-decoupled-overlay[data-popper-placement="top"] & { + bottom: -0.5 * $eccgui-size-block-whitespace; + } + .#{$eccgui}-decoupled-overlay[data-popper-placement="right"] & { + left: -0.5 * $eccgui-size-block-whitespace; + } + .#{$eccgui}-decoupled-overlay[data-popper-placement="bottom"] & { + top: -0.5 * $eccgui-size-block-whitespace; + } + .#{$eccgui}-decoupled-overlay[data-popper-placement="left"] & { + right: -0.5 * $eccgui-size-block-whitespace; + } +} + +.#{$eccgui}-decoupled-overlay { + &.#{$prefix-blueprintjs}-popover { + z-index: 8002; // 2 over application header + } + + &--small { + @extend .#{$eccgui}-tooltip--small; + } + + &--medium { + @extend .#{$eccgui}-tooltip--medium; + } + + &--large { + @extend .#{$eccgui}-tooltip--large; + } + + &:has(.#{$eccgui}-decoupled-overlay__arrow) { + .#{$eccgui}-decoupled-overlay__content { + min-height: 30px; // height of blueprint arrow + } + } +} + +.#{$eccgui}-decoupled-overlay__content { + padding: 0.1px; // force margins of children to stay inside +} diff --git a/src/components/Icon/canonicalIconNames.tsx b/src/components/Icon/canonicalIconNames.tsx index 93ed7f87..309226b2 100644 --- a/src/components/Icon/canonicalIconNames.tsx +++ b/src/components/Icon/canonicalIconNames.tsx @@ -1,5 +1,6 @@ import * as icons from "@carbon/react/icons"; import { CarbonIconType as IconType } from "@carbon/react/icons"; +import { transform} from "./transformIcon"; const canonicalIcons = { "application-activities": icons.Activity, @@ -172,8 +173,10 @@ const canonicalIcons = { "state-checked": icons.CheckboxChecked, "state-checkedsimple": icons.Checkmark, "state-confirmed": icons.ThumbsUp, + "state-confirmed-all": icons.ThumbsUpDouble, "state-danger": icons.ErrorFilled, "state-declined": icons.ThumbsDown, + "state-declined-all": transform(icons.ThumbsUpDouble, 0, false, true), "state-flagged": icons.Flag, "state-info": icons.InformationFilled, "state-locked": icons.Locked, diff --git a/src/components/Icon/transformIcon.tsx b/src/components/Icon/transformIcon.tsx new file mode 100644 index 00000000..63a716a8 --- /dev/null +++ b/src/components/Icon/transformIcon.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { CarbonIconType, CarbonIconProps } from "@carbon/react/icons"; + +export const transform = (IconSymbol: CarbonIconType, rotate: number = 0, flipH: boolean = false, flipV: boolean = false) : CarbonIconType => { + return React.forwardRef((props: CarbonIconProps, ref: React.ForwardedRef) => { + return ( + + ); + }) +} + diff --git a/src/components/MultiSelect/MultiSelect.tsx b/src/components/MultiSelect/MultiSelect.tsx index 3685ed86..eb0f07c1 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; @@ -53,7 +61,7 @@ interface MultiSuggestFieldCommonProps /** * prop to listen for query changes, when text is entered in the multi-select input */ - runOnQueryChange?: (query: string) => Promise; + runOnQueryChange?: (query: string) => Promise | (T[] | undefined); /** * Whether the component should take up the full width of its container. * This overrides `tagInputProps.fill`. @@ -265,7 +273,8 @@ export function MultiSuggestField({ }; const defaultFilterPredicate = (item: T, query: string) => { - return itemLabel(item).toLowerCase().includes(query); + const searchWords = highlighterUtils.extractSearchWords(query, true) + return highlighterUtils.matchesAllWords(itemLabel(item).toLowerCase(), searchWords) }; /** diff --git a/src/components/VisualTour/VisualTour.tsx b/src/components/VisualTour/VisualTour.tsx index 095a7dff..04b85f77 100644 --- a/src/components/VisualTour/VisualTour.tsx +++ b/src/components/VisualTour/VisualTour.tsx @@ -1,7 +1,5 @@ import React from "react"; import { createPortal } from "react-dom"; -import { Classes as BlueprintClasses } from "@blueprintjs/core"; -import { createPopper } from "@popperjs/core"; import { CLASSPREFIX as eccgui } from "../../configuration/constants"; import { @@ -14,6 +12,7 @@ import { CardHeader, CardOptions, CardTitle, + DecoupledOverlay, IconButton, Markdown, ModalSize, @@ -130,7 +129,7 @@ export const VisualTour = ({