Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `<ApplicationViewability />`
- component for hiding elements in specific media
- `<InlineText />`
- force children to get displayed as inline content
- force children to get displayed as inline content
- `<StringPreviewContentBlobToggler />`
- `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

- `<Tag />`
- create more whitespace inside `small` tag
- reduce visual impact of border
- `<StringPreviewContentBlobToggler />`
- take Markdown rendering into account before testing the maximum preview length
- `<NodeContent />`
- header-menu items are vertically centered now
- take Markdown rendering into account before testing the maximum preview length
- `<CodeEditor />`
- fix `disabled` property update

### Changed

Expand All @@ -37,16 +40,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `<FlexibleLayoutItem />`
- `<GridColumn />`
- `<PropertyName />` and `<PropertyValue />`
- `<EdgeDefault />`
- reduce stroke width to only 1px

### Deprecated

- `<StringPreviewContentBlobToggler />`
- `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

- `<ActivityControlWidget />`
- Add parameter `active` to activity control action to set the `active` state of its button.

### Changed

- `<MultiSelect />`:
- 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
Expand Down
82 changes: 54 additions & 28 deletions src/cmem/ActivityControl/ActivityControlWidget.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -97,14 +100,24 @@ 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<TestIconProps>;
// Action is currently disabled (but shown)
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 {
Expand Down Expand Up @@ -210,26 +223,39 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
>
{activityActions &&
activityActions.map((action, idx) => {
return (
<IconButton
key={
typeof action.icon === "string"
? action.icon
: action["data-test-id"] ?? action["data-testid"] ?? idx
}
data-test-id={action["data-test-id"]}
data-testid={action["data-testid"]}
name={action.icon}
text={action.tooltip}
onClick={action.action}
disabled={action.disabled}
intent={action.hasStateWarning ? "warning" : undefined}
tooltipProps={{
hoverOpenDelay: 200,
placement: "bottom",
}}
/>
);
const ActionButton = () => <IconButton
key={
typeof action.icon === "string"
? action.icon
: action["data-test-id"] ?? action["data-testid"] ?? idx
}
data-test-id={action["data-test-id"]}
data-testid={action["data-testid"]}
name={action.icon}
text={action.tooltip}
onClick={action.action}
disabled={action.disabled}
intent={action.hasStateWarning ? "warning" : undefined}
tooltipProps={{
hoverOpenDelay: 200,
placement: "bottom"
}}
active={action.active}
/>
return action.notification ?
<ContextOverlay
content={<Notification
message={action.notification.message}
intent={action.notification.intent ?? "neutral"}
onDismiss={action.notification.onClose}
timeout={action.notification.timeout}
/>}
defaultIsOpen={true}
onClose={action.notification.onClose}
>
<ActionButton/>
</ContextOverlay> :
<ActionButton/>
})}
{additionalActions}
{activityContextMenu && activityContextMenu.menuItems.length > 0 && (
Expand Down
30 changes: 30 additions & 0 deletions src/components/DecoupledOverlay/DecoupledOverlay.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof DecoupledOverlay>;

const Template: StoryFn<typeof DecoupledOverlay> = (args: DecoupledOverlayProps) => {
return (
<>
<Tag id={"decoupledTarget"}>Decoupled target</Tag>
<DecoupledOverlay {...args} />
</>
);
};

export const Default = Template.bind({});

Default.args = {
children: (
<WhiteSpaceContainer marginTop={"small"} marginRight={"small"} marginBottom={"small"} marginLeft={"small"}>
Decoupled overlay
</WhiteSpaceContainer>
),
targetSelectorOrElement: "#decoupledTarget",
};
86 changes: 86 additions & 0 deletions src/components/DecoupledOverlay/DecoupledOverlay.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>,
TestableComponent,
Pick<ContextOverlayProps, "usePortal" | "portalContainer" | "placement" | "minimal"> {
/**
* 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 = (
<div
className={
`${eccgui}-decoupled-overlay` +
` ${eccgui}-decoupled-overlay--${size}` +
` ${BlueprintClasses.POPOVER}` +
(minimal ? ` ${BlueprintClasses.MINIMAL}` : "")
}
role="tooltip"
ref={overlayRef}
>
{!minimal && (
<div
className={`${eccgui}-decoupled-overlay__arrow ${BlueprintClasses.POPOVER_ARROW}`}
data-popper-arrow
aria-hidden
/>
)}
<div className={`${BlueprintClasses.POPOVER_CONTENT} ${eccgui}-decoupled-overlay__content`}>{children}</div>
</div>
);

return usePortal ? createPortal(overlay, portalContainer) : overlay;
};

export default DecoupledOverlay;
46 changes: 46 additions & 0 deletions src/components/DecoupledOverlay/_decoupledoverlay.scss
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions src/components/Icon/canonicalIconNames.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions src/components/Icon/transformIcon.tsx
Original file line number Diff line number Diff line change
@@ -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<React.ReactSVGElement>) => {
return (
<IconSymbol
{...props}
ref={ref}
transform={
`scale(${flipH ? "-1" : "1"}, ${flipV ? "-1" : "1"}) rotate(${rotate})`
}
/>
);
})
}

Loading