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
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})`
}
/>
);
})
}

15 changes: 12 additions & 3 deletions src/components/MultiSelect/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
newlySelected?: T;
Expand Down Expand Up @@ -53,7 +61,7 @@ interface MultiSuggestFieldCommonProps<T>
/**
* prop to listen for query changes, when text is entered in the multi-select input
*/
runOnQueryChange?: (query: string) => Promise<T[] | undefined>;
runOnQueryChange?: (query: string) => Promise<T[] | undefined> | (T[] | undefined);
/**
* Whether the component should take up the full width of its container.
* This overrides `tagInputProps.fill`.
Expand Down Expand Up @@ -265,7 +273,8 @@ export function MultiSuggestField<T>({
};

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)
};

/**
Expand Down
20 changes: 14 additions & 6 deletions src/extensions/codemirror/CodeMirror.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,11 @@ export const CodeEditor = ({
}: CodeEditorProps) => {
const parent = useRef<any>(undefined);
const [view, setView] = React.useState<EditorView | undefined>();
const currentView = React.useRef<EditorView>()
currentView.current = view
const currentReadOnly = React.useRef(readOnly)
currentReadOnly.current = readOnly
const currentView = React.useRef<EditorView>();
currentView.current = view;
const currentReadOnly = React.useRef(readOnly);
currentReadOnly.current = readOnly;
//const currentDisabled = React.useRef(disabled);
const [showPreview, setShowPreview] = React.useState<boolean>(false);
// CodeMirror Compartments in order to allow for re-configuration after initialization
const readOnlyCompartment = React.useRef<Compartment>(compartment())
Expand Down Expand Up @@ -377,7 +378,7 @@ export const CodeEditor = ({
}

if (disabled) {
view.dom.className += ` ${eccgui}-disabled`;
view.dom.classList.add(`${eccgui}-disabled`);
}

if (intent) {
Expand Down Expand Up @@ -432,7 +433,14 @@ export const CodeEditor = ({
}, [tabIntentSize])

React.useEffect(() => {
updateExtension(EditorView?.editable.of(!disabled), disabledCompartment.current)
updateExtension(EditorView?.editable.of(!disabled), disabledCompartment.current);
if (view?.dom) {
if (disabled) {
view.dom.classList.add(`${eccgui}-disabled`);
} else {
view.dom.classList.remove(`${eccgui}-disabled`);
}
}
}, [disabled])

React.useEffect(() => {
Expand Down
6 changes: 3 additions & 3 deletions src/extensions/react-flow/_config.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ $reactflow-node-font-size: $eccgui-size-typo-caption !default;
$reactflow-node-border-width: 2 * $button-border-width !default;
$reactflow-node-border-radius: $button-border-radius !default;
$reactflow-edge-rendering: geometricprecision !default;
$reactflow-edge-stroke-width-default: 2px !default;
$reactflow-edge-stroke-width-hover: 2px !default;
$reactflow-edge-stroke-width-selected: 2px !default;
$reactflow-edge-stroke-width-default: 1px !default;
$reactflow-edge-stroke-width-hover: 1px !default;
$reactflow-edge-stroke-width-selected: 1px !default;
$reactflow-edge-stroke-opacity-default: $eccgui-opacity-muted !default;
$reactflow-edge-stroke-opacity-hover: $eccgui-opacity-narrow !default;
$reactflow-edge-stroke-opacity-selected: $eccgui-opacity-regular !default;
Expand Down
8 changes: 5 additions & 3 deletions src/extensions/react-flow/edges/EdgeLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ export const EdgeLabel = memo(
})}
</div>
)}
<div className={`${eccgui}-graphviz__edge-label__text`} title={title}>
{typeof text === "string" ? <OverflowText>{text}</OverflowText> : text}
</div>
{(title || text) && (
<div className={`${eccgui}-graphviz__edge-label__text`} title={title??undefined}>
{text && (typeof text === "string" ? <OverflowText>{text}</OverflowText> : text)}
</div>
)}
{!!actions && <div className={`${eccgui}-graphviz__edge-label__aux`}>{actions}</div>}
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/react-flow/edges/_edges.scss
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ path.react-flow__edge-path-highlight {
border: 0.5 * $reactflow-node-border-width solid transparent;
border-color: var(--#{$eccgui}-reactflow-edge-label-color, currentColor);
border-radius: $reactflow-node-border-radius;
padding: 0 $eccgui-size-block-whitespace * 0.25;

&.#{$eccgui}-graphviz__edge-label--loose {
background-color: transparent;
Expand Down Expand Up @@ -330,7 +331,6 @@ path.react-flow__edge-path-highlight {
display: inline-flex;
flex-grow: 0;
flex-shrink: 0;
margin-left: 2px;
.#{$eccgui}-depiction__image {
height: calc(#{0.5 * $reactflow-node-largesize} - 4px);

Expand All @@ -342,7 +342,7 @@ path.react-flow__edge-path-highlight {
.#{$eccgui}-graphviz__edge-label__text {
flex-grow: 1;
flex-shrink: 1;
margin: 0 $eccgui-size-block-whitespace * 0.25;
margin: 0 0 0 $eccgui-size-block-whitespace * 0.25;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
Expand Down