Skip to content

Commit 6af2e32

Browse files
committed
Merge remote-tracking branch 'origin/develop' into bugfix/centeredNodeContentMenuItems-CMEM-7184
2 parents 972d701 + 59fa23a commit 6af2e32

15 files changed

Lines changed: 331 additions & 38 deletions

CHANGELOG.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
1010

1111
- `<ApplicationViewability />`
1212
- component for hiding elements in specific media
13+
- `<InlineText />`
14+
- force children to get displayed as inline content
15+
- `<StringPreviewContentBlobToggler />`
16+
- `useOnly` property: specify if only parts of the content should be used for the shortened preview, this property replaces `firstNonEmptyLineOnly`
17+
18+
### Fixed
19+
20+
- `<Tag />`
21+
- create more whitespace inside `small` tag
22+
- reduce visual impact of border
23+
- `<StringPreviewContentBlobToggler />`
24+
- take Markdown rendering into account before testing the maximum preview length
25+
- `<NodeContent />`
26+
- header-menu items are vertically centered now
1327

1428
### Changed
1529

@@ -24,9 +38,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
2438
- `<GridColumn />`
2539
- `<PropertyName />` and `<PropertyValue />`
2640

27-
### Fixed
41+
### Deprecated
2842

29-
- Centered header-menu items in <NodeContent />
43+
- `<StringPreviewContentBlobToggler />`
44+
- `firstNonEmptyLineOnly` will be removed, is replaced by `useOnly="firstNonEmptyLine"`
3045

3146
## [25.0.0] - 2025-12-01
3247

src/cmem/ContentBlobToggler/ContentBlobToggler.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function ContentBlobToggler({
5858
{previewContent}
5959
{enableToggler && (
6060
<>
61-
&hellip;{" "}
61+
{" "}&hellip;{" "}
6262
<Link
6363
href="#more"
6464
data-test-id={"content-blob-toggler-more-link"}
Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,46 @@
11
import React from "react";
22

3-
import { ContentBlobToggler, ContentBlobTogglerProps, Markdown } from "./..";
3+
import { ContentBlobToggler, ContentBlobTogglerProps, InlineText, Markdown, utils } from "./../../index";
44

55
export interface StringPreviewContentBlobTogglerProps
66
extends Omit<ContentBlobTogglerProps, "previewContent" | "enableToggler"> {
77
/**
8-
The preview content will be cut to this length if it is too long.
8+
* The preview content will be cut to this length if it is too long.
99
*/
1010
previewMaxLength?: number;
1111
/**
12-
The content string. If it is smaller than previewMaxLength this will be displayed in full, else fullviewContent will be displayed.
12+
* The content string.
13+
* If it is smaller than `previewMaxLength` this will be displayed in full, else `fullviewContent` will be displayed.
1314
*/
1415
content: string;
15-
/** If only the first non-empty line should be shown in the preview. This will in addition also be shortened according to previewMaxLength. */
16-
firstNonEmptyLineOnly?: boolean;
17-
/** If enabled the preview is rendered as markdown. */
16+
/**
17+
* Use only parts of `content` in the preview.
18+
* `firstMarkdownSection` uses the content until the first double line return.
19+
* Currently overwritten by `firstNonEmptyLineOnly`.
20+
*/
21+
useOnly?: "firstNonEmptyLine" | "firstMarkdownSection";
22+
/**
23+
* If enabled the preview is rendered as Markdown.
24+
*/
1825
renderPreviewAsMarkdown?: boolean;
19-
/** White-listing of HTML elements that will be rendered when renderPreviewAsMarkdown is enabled. */
26+
/**
27+
* White-listing of HTML elements that will be rendered when renderPreviewAsMarkdown is enabled.
28+
*/
2029
allowedHtmlElementsInPreview?: string[];
21-
/** Allows to add non-string elements at the end of the content if the full description is shown, i.e. no toggler is necessary.
30+
/**
31+
* Allows to add non-string elements at the end of the content if the full description is shown, i.e. no toggler is necessary.
2232
* This allows to add non-string elements to both the full-view content and the pure string content.
2333
*/
2434
noTogglerContentSuffix?: JSX.Element;
35+
/**
36+
* If only the first non-empty line should be shown in the preview.
37+
* This will in addition also be shortened according to `previewMaxLength`.
38+
* @deprecated (v26) use `useOnly="firstNonEmptyLine"` instead
39+
*/
40+
firstNonEmptyLineOnly?: boolean;
2541
}
2642

27-
/** Version of the content toggler for text only content. */
43+
/** Version of the content toggler for text centric content. */
2844
export function StringPreviewContentBlobToggler({
2945
className = "",
3046
previewMaxLength,
@@ -33,21 +49,44 @@ export function StringPreviewContentBlobToggler({
3349
content,
3450
fullviewContent,
3551
startExtended,
36-
firstNonEmptyLineOnly,
52+
useOnly,
3753
renderPreviewAsMarkdown = false,
3854
allowedHtmlElementsInPreview,
3955
noTogglerContentSuffix,
56+
firstNonEmptyLineOnly,
4057
}: StringPreviewContentBlobTogglerProps) {
41-
const previewMaybeFirstLine = firstNonEmptyLineOnly ? firstNonEmptyLine(content) : content;
42-
const previewString = previewMaxLength ? previewMaybeFirstLine.substr(0, previewMaxLength) : previewMaybeFirstLine;
43-
const enableToggler = previewString !== content;
58+
// need to test `firstNonEmptyLineOnly` until property is removed
59+
const useOnlyTest: StringPreviewContentBlobTogglerProps["useOnly"] = firstNonEmptyLineOnly
60+
? "firstNonEmptyLine"
61+
: useOnly;
62+
63+
let previewString = content;
64+
switch (useOnlyTest) {
65+
case "firstNonEmptyLine":
66+
previewString = useOnlyPart(content, regexFirstNonEmptyLine);
67+
break;
68+
case "firstMarkdownSection":
69+
previewString = useOnlyPart(content, regexFirstMarkdownSection);
70+
}
71+
72+
let enableToggler = previewString !== content;
73+
4474
let previewContent = renderPreviewAsMarkdown ? (
4575
<Markdown key="markdown-content" allowedElements={allowedHtmlElementsInPreview}>
4676
{previewString}
4777
</Markdown>
4878
) : (
4979
previewString
5080
);
81+
82+
if (
83+
previewMaxLength &&
84+
utils.reduceToText(previewContent, { decodeHtmlEntities: true }).length > previewMaxLength
85+
) {
86+
previewContent = utils.reduceToText(previewContent, { decodeHtmlEntities: true }).slice(0, previewMaxLength);
87+
enableToggler = true;
88+
}
89+
5190
if (!enableToggler && noTogglerContentSuffix) {
5291
previewContent = (
5392
<>
@@ -60,7 +99,7 @@ export function StringPreviewContentBlobToggler({
6099
return (
61100
<ContentBlobToggler
62101
className={className}
63-
previewContent={previewContent}
102+
previewContent={<InlineText>{previewContent}</InlineText>}
64103
toggleExtendText={toggleExtendText}
65104
toggleReduceText={toggleReduceText}
66105
fullviewContent={fullviewContent}
@@ -70,17 +109,26 @@ export function StringPreviewContentBlobToggler({
70109
);
71110
}
72111

73-
const newLineRegex = new RegExp("\r|\n"); // eslint-disable-line
112+
const regexFirstNonEmptyLine = new RegExp("\r|\n"); // eslint-disable-line
113+
const regexFirstMarkdownSection = new RegExp("\r\n\r\n|\n\n"); // eslint-disable-line
74114

75115
/**
76116
* Takes the first non-empty line from a preview string.
77117
*/
78118
function firstNonEmptyLine(preview: string) {
119+
return useOnlyPart(preview, regexFirstNonEmptyLine);
120+
}
121+
122+
/**
123+
* Returns only the first part from a preview string.
124+
* Or the full string as fallback.
125+
*/
126+
function useOnlyPart(preview: string, regexTest: RegExp): string {
79127
const previewString = preview.trim();
80-
const result = newLineRegex.exec(previewString);
81-
return result !== null ? previewString.substr(0, result.index) : previewString;
128+
const result = regexTest.exec(previewString);
129+
return result !== null ? result.input.slice(0, result.index) : previewString;
82130
}
83131

84132
export const stringPreviewContentBlobTogglerUtils = {
85133
firstNonEmptyLine,
86-
};
134+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from "react";
2+
import { Meta, StoryFn } from "@storybook/react";
3+
4+
import { Markdown, StringPreviewContentBlobToggler } from "../../../index";
5+
6+
const config = {
7+
title: "CMEM/ContentBlobToggler/StringPreview",
8+
component: StringPreviewContentBlobToggler,
9+
} as Meta<typeof StringPreviewContentBlobToggler>;
10+
export default config;
11+
12+
const Template: StoryFn<typeof StringPreviewContentBlobToggler> = (args) => (
13+
<StringPreviewContentBlobToggler {...args} />
14+
);
15+
16+
const initialTeststring =
17+
"A library for GUI elements.\nIn order to create graphical user interfaces, please have look at the documentation at [Github](https://github.com/eccenca/gui-elements).";
18+
19+
export const Default = Template.bind({});
20+
Default.args = {
21+
content: initialTeststring,
22+
fullviewContent: <Markdown htmlContentBlockProps={{ large: true }}>{initialTeststring}</Markdown>,
23+
previewMaxLength: 64,
24+
renderPreviewAsMarkdown: true,
25+
toggleExtendText: "show more",
26+
toggleReduceText: "show less",
27+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import React from "react";
2+
import { render, RenderResult } from "@testing-library/react";
3+
4+
import "@testing-library/jest-dom";
5+
6+
import {
7+
StringPreviewContentBlobToggler,
8+
StringPreviewContentBlobTogglerProps,
9+
} from "../StringPreviewContentBlobToggler";
10+
11+
import { Default as StringPreviewContentBlobTogglerStory } from "./../stories/StringPreviewContentBlobToggler.stories";
12+
13+
describe("StringPreviewContentBlobToggler", () => {
14+
const textMustExist = (queryByText: RenderResult["queryByText"], text: string) => {
15+
expect(queryByText(text, { exact: false })).not.toBeNull();
16+
};
17+
const textMustNotExist = (queryByText: RenderResult["queryByText"], text: string) => {
18+
expect(queryByText(text, { exact: false })).toBeNull();
19+
};
20+
it("should cut preview and show toggler to extend", () => {
21+
const { queryByText } = render(
22+
<StringPreviewContentBlobToggler
23+
{...(StringPreviewContentBlobTogglerStory.args as StringPreviewContentBlobTogglerProps)}
24+
/>
25+
);
26+
textMustExist(queryByText, "A library for GUI elements.");
27+
textMustNotExist(
28+
queryByText,
29+
"In order to create graphical user interfaces, please have look at the documentation at"
30+
);
31+
textMustExist(queryByText, "show more");
32+
});
33+
it("should display full view if `startExtended` is enabled, and show toggler to reduce", () => {
34+
const { queryByText } = render(
35+
<StringPreviewContentBlobToggler
36+
{...(StringPreviewContentBlobTogglerStory.args as StringPreviewContentBlobTogglerProps)}
37+
startExtended
38+
/>
39+
);
40+
textMustExist(
41+
queryByText,
42+
"In order to create graphical user interfaces, please have look at the documentation at"
43+
);
44+
textMustExist(queryByText, "show less");
45+
});
46+
it('should display only first content line on `useOnly={"firstNonEmptyLine"}`', () => {
47+
const { queryByText } = render(
48+
<StringPreviewContentBlobToggler
49+
{...(StringPreviewContentBlobTogglerStory.args as StringPreviewContentBlobTogglerProps)}
50+
useOnly={"firstNonEmptyLine"}
51+
/>
52+
);
53+
textMustExist(queryByText, "A library for GUI elements.");
54+
textMustNotExist(queryByText, "In order to create");
55+
});
56+
it('should use first Markdown paragraph as preview content on `useOnly={"firstMarkdownSection"}` but shorten it', () => {
57+
const { queryByText } = render(
58+
<StringPreviewContentBlobToggler
59+
{...(StringPreviewContentBlobTogglerStory.args as StringPreviewContentBlobTogglerProps)}
60+
useOnly={"firstMarkdownSection"}
61+
/>
62+
);
63+
textMustExist(queryByText, "A library for GUI elements.");
64+
textMustExist(queryByText, "In order to create");
65+
textMustNotExist(queryByText, "please have look at the documentation at");
66+
});
67+
it("should display full preview and no toggler if content is short enough", () => {
68+
const { queryByText } = render(
69+
<StringPreviewContentBlobToggler
70+
{...(StringPreviewContentBlobTogglerStory.args as StringPreviewContentBlobTogglerProps)}
71+
previewMaxLength={144}
72+
/>
73+
);
74+
textMustExist(queryByText, "A library for GUI elements.");
75+
textMustExist(
76+
queryByText,
77+
"In order to create graphical user interfaces, please have look at the documentation at"
78+
);
79+
textMustNotExist(queryByText, "https://github.com/"); // test if Markdown was rendered
80+
textMustNotExist(queryByText, "show more");
81+
});
82+
it("should not use Markdown rendering on `renderPreviewAsMarkdown={false}`", () => {
83+
const { queryByText } = render(
84+
<StringPreviewContentBlobToggler
85+
{...(StringPreviewContentBlobTogglerStory.args as StringPreviewContentBlobTogglerProps)}
86+
previewMaxLength={144}
87+
renderPreviewAsMarkdown={false}
88+
/>
89+
);
90+
textMustExist(queryByText, "A library for GUI elements.");
91+
textMustExist(
92+
queryByText,
93+
"In order to create graphical user interfaces, please have look at the documentation at"
94+
);
95+
textMustExist(queryByText, "https://github.com/"); // test if Markdown was rendered
96+
textMustExist(queryByText, "show more");
97+
});
98+
});

src/components/OverviewItem/stories/OverviewItem.stories.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ import {
66
Badge,
77
Card,
88
Depiction,
9+
OverflowText,
910
OverviewItem,
1011
OverviewItemActions,
1112
OverviewItemDepiction,
1213
OverviewItemDescription,
14+
OverviewItemLine,
15+
Tag,
16+
TagList,
1317
} from "./../../../../index";
1418
import { FullExample as OtherDepictionExample } from "./../../Depiction/stories/Depiction.stories";
1519
import { Default as ActionsExample } from "./OverviewItemActions.stories";
1620
import { AutoTransform as DepictionExample } from "./OverviewItemDepiction.stories";
1721
import { Default as DescriptionExample } from "./OverviewItemDescription.stories";
22+
import { Default as LineExample } from "./OverviewItemLine.stories";
1823

1924
export default {
2025
title: "Components/OverviewItem",
@@ -76,3 +81,26 @@ ItemWithDepictionElement.args = {
7681
hasSpacing: true,
7782
hasCardWrapper: true,
7883
};
84+
85+
export const ItemWithTags = Template.bind({});
86+
ItemWithTags.args = {
87+
children: [
88+
<OverviewItemDepiction {...DepictionExample.args} key={"depiction"} />,
89+
<OverviewItemDescription key={"description"}>
90+
<OverviewItemLine {...LineExample.args} />
91+
<OverviewItemLine small>
92+
<OverflowText>
93+
<TagList>
94+
<Tag small>Test</Tag>
95+
<Tag small>Tag</Tag>
96+
<Tag small>List</Tag>
97+
</TagList>
98+
</OverflowText>
99+
</OverviewItemLine>
100+
</OverviewItemDescription>,
101+
<OverviewItemActions children={ActionsExample.args.children[0]} key={"actions"} />,
102+
],
103+
densityHigh: false,
104+
hasSpacing: true,
105+
hasCardWrapper: true,
106+
};

src/components/OverviewItem/stories/OverviewItemActions.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
subcomponents: { Button, IconButton, ContextMenu },
1010
argTypes: {
1111
children: {
12-
control: "none",
12+
control: false,
1313
description: "User-interactive elements.",
1414
},
1515
},
@@ -20,7 +20,7 @@ const Template: StoryFn<typeof OverviewItemActions> = (args) => <OverviewItemAct
2020
export const Default = Template.bind({});
2121
Default.args = {
2222
children: [
23-
<IconButton name="item-remove" tooltip="Remove this item" disruptive />,
23+
<IconButton name="item-remove" text="Remove this item" disruptive />,
2424
<Button affirmative>Other action</Button>,
2525
],
2626
};

src/components/OverviewItem/stories/OverviewItemDescription.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default {
1212
},
1313
argTypes: {
1414
children: {
15-
control: "none",
15+
control: false,
1616
description: "Elements for text content.",
1717
},
1818
},

src/components/OverviewItem/stories/OverviewItemLine.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
component: OverviewItemLine,
1010
argTypes: {
1111
children: {
12-
control: "none",
12+
control: false,
1313
description: "Elements for line content.",
1414
},
1515
},

0 commit comments

Comments
 (0)