@eccenca/gui-elements 25.0.0-rc.2 → 25.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +64 -18
  2. package/dist/cjs/cmem/ActivityControl/SilkActivityControl.js +1 -3
  3. package/dist/cjs/cmem/ActivityControl/SilkActivityControl.js.map +1 -1
  4. package/dist/cjs/cmem/ContentBlobToggler/ContentBlobToggler.js +1 -0
  5. package/dist/cjs/cmem/ContentBlobToggler/ContentBlobToggler.js.map +1 -1
  6. package/dist/cjs/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.js +34 -11
  7. package/dist/cjs/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.js.map +1 -1
  8. package/dist/cjs/cmem/react-flow/ReactFlow/ReactFlowV12.js.map +1 -1
  9. package/dist/cjs/common/Intent/index.js +1 -1
  10. package/dist/cjs/common/Intent/index.js.map +1 -1
  11. package/dist/cjs/common/index.js +3 -1
  12. package/dist/cjs/common/index.js.map +1 -1
  13. package/dist/cjs/common/utils/reduceToText.js +26 -1
  14. package/dist/cjs/common/utils/reduceToText.js.map +1 -1
  15. package/dist/cjs/components/Application/ApplicationViewability.js +33 -0
  16. package/dist/cjs/components/Application/ApplicationViewability.js.map +1 -0
  17. package/dist/cjs/components/Application/index.js +1 -0
  18. package/dist/cjs/components/Application/index.js.map +1 -1
  19. package/dist/cjs/components/ContextOverlay/ContextMenu.js +2 -2
  20. package/dist/cjs/components/ContextOverlay/ContextMenu.js.map +1 -1
  21. package/dist/cjs/components/ContextOverlay/ContextOverlay.js +62 -24
  22. package/dist/cjs/components/ContextOverlay/ContextOverlay.js.map +1 -1
  23. package/dist/cjs/components/Icon/IconButton.js.map +1 -1
  24. package/dist/cjs/components/TextReducer/TextReducer.js +15 -3
  25. package/dist/cjs/components/TextReducer/TextReducer.js.map +1 -1
  26. package/dist/cjs/components/Typography/InlineText.js +29 -0
  27. package/dist/cjs/components/Typography/InlineText.js.map +1 -0
  28. package/dist/cjs/components/Typography/index.js +1 -0
  29. package/dist/cjs/components/Typography/index.js.map +1 -1
  30. package/dist/cjs/extensions/codemirror/CodeMirror.js +2 -2
  31. package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
  32. package/dist/cjs/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.js +1 -1
  33. package/dist/cjs/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.js.map +1 -1
  34. package/dist/cjs/extensions/codemirror/tests/codemirrorTestHelper.js +2 -2
  35. package/dist/cjs/extensions/codemirror/tests/codemirrorTestHelper.js.map +1 -1
  36. package/dist/cjs/extensions/react-flow/edges/EdgeNew.js +1 -1
  37. package/dist/cjs/extensions/react-flow/edges/EdgeNew.js.map +1 -1
  38. package/dist/cjs/extensions/react-flow/handles/HandleDefault.js +1 -1
  39. package/dist/cjs/extensions/react-flow/handles/HandleDefault.js.map +1 -1
  40. package/dist/cjs/extensions/react-flow/minimap/MiniMap.js +1 -1
  41. package/dist/cjs/extensions/react-flow/minimap/MiniMap.js.map +1 -1
  42. package/dist/cjs/extensions/react-flow/minimap/MiniMapV12.js.map +1 -1
  43. package/dist/cjs/extensions/react-flow/nodes/nodeUtils.js.map +1 -1
  44. package/dist/esm/cmem/ActivityControl/SilkActivityControl.js +1 -3
  45. package/dist/esm/cmem/ActivityControl/SilkActivityControl.js.map +1 -1
  46. package/dist/esm/cmem/ContentBlobToggler/ContentBlobToggler.js +1 -0
  47. package/dist/esm/cmem/ContentBlobToggler/ContentBlobToggler.js.map +1 -1
  48. package/dist/esm/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.js +32 -9
  49. package/dist/esm/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.js.map +1 -1
  50. package/dist/esm/cmem/react-flow/ReactFlow/ReactFlowV12.js.map +1 -1
  51. package/dist/esm/common/Intent/index.js +1 -1
  52. package/dist/esm/common/Intent/index.js.map +1 -1
  53. package/dist/esm/common/index.js +3 -1
  54. package/dist/esm/common/index.js.map +1 -1
  55. package/dist/esm/common/utils/reduceToText.js +37 -1
  56. package/dist/esm/common/utils/reduceToText.js.map +1 -1
  57. package/dist/esm/components/Application/ApplicationViewability.js +28 -0
  58. package/dist/esm/components/Application/ApplicationViewability.js.map +1 -0
  59. package/dist/esm/components/Application/index.js +1 -0
  60. package/dist/esm/components/Application/index.js.map +1 -1
  61. package/dist/esm/components/ContextOverlay/ContextMenu.js +2 -2
  62. package/dist/esm/components/ContextOverlay/ContextMenu.js.map +1 -1
  63. package/dist/esm/components/ContextOverlay/ContextOverlay.js +66 -28
  64. package/dist/esm/components/ContextOverlay/ContextOverlay.js.map +1 -1
  65. package/dist/esm/components/Icon/IconButton.js.map +1 -1
  66. package/dist/esm/components/TextReducer/TextReducer.js +14 -3
  67. package/dist/esm/components/TextReducer/TextReducer.js.map +1 -1
  68. package/dist/esm/components/Typography/InlineText.js +33 -0
  69. package/dist/esm/components/Typography/InlineText.js.map +1 -0
  70. package/dist/esm/components/Typography/index.js +1 -0
  71. package/dist/esm/components/Typography/index.js.map +1 -1
  72. package/dist/esm/extensions/codemirror/CodeMirror.js +3 -3
  73. package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
  74. package/dist/esm/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.js +1 -1
  75. package/dist/esm/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.js.map +1 -1
  76. package/dist/esm/extensions/codemirror/tests/codemirrorTestHelper.js +3 -3
  77. package/dist/esm/extensions/codemirror/tests/codemirrorTestHelper.js.map +1 -1
  78. package/dist/esm/extensions/react-flow/edges/EdgeNew.js +1 -1
  79. package/dist/esm/extensions/react-flow/edges/EdgeNew.js.map +1 -1
  80. package/dist/esm/extensions/react-flow/handles/HandleDefault.js +1 -1
  81. package/dist/esm/extensions/react-flow/handles/HandleDefault.js.map +1 -1
  82. package/dist/esm/extensions/react-flow/minimap/MiniMap.js +1 -1
  83. package/dist/esm/extensions/react-flow/minimap/MiniMap.js.map +1 -1
  84. package/dist/esm/extensions/react-flow/minimap/MiniMapV12.js.map +1 -1
  85. package/dist/esm/extensions/react-flow/nodes/nodeUtils.js.map +1 -1
  86. package/dist/types/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.d.ts +26 -10
  87. package/dist/types/common/index.d.ts +2 -0
  88. package/dist/types/common/utils/reduceToText.d.ts +1 -1
  89. package/dist/types/components/Application/ApplicationViewability.d.ts +36 -0
  90. package/dist/types/components/Application/index.d.ts +1 -0
  91. package/dist/types/components/ContextOverlay/ContextMenu.d.ts +1 -1
  92. package/dist/types/components/Icon/IconButton.d.ts +1 -1
  93. package/dist/types/components/Structure/TitleSubsection.d.ts +1 -1
  94. package/dist/types/components/Tabs/Tab.d.ts +2 -2
  95. package/dist/types/components/TextReducer/TextReducer.d.ts +13 -1
  96. package/dist/types/components/Typography/InlineText.d.ts +13 -0
  97. package/dist/types/components/Typography/index.d.ts +1 -0
  98. package/dist/types/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.d.ts +1 -1
  99. package/dist/types/extensions/codemirror/tests/codemirrorTestHelper.d.ts +1 -1
  100. package/package.json +54 -53
  101. package/src/cmem/ActivityControl/SilkActivityControl.tsx +1 -1
  102. package/src/cmem/ContentBlobToggler/ContentBlobToggler.tsx +1 -1
  103. package/src/cmem/ContentBlobToggler/StringPreviewContentBlobToggler.tsx +66 -18
  104. package/src/cmem/ContentBlobToggler/stories/StringPreviewContentBlobToggler.stories.tsx +27 -0
  105. package/src/cmem/ContentBlobToggler/tests/StringPreviewContentBlobToggler.test.tsx +98 -0
  106. package/src/cmem/react-flow/ReactFlow/ReactFlowV12.tsx +1 -0
  107. package/src/common/Intent/index.ts +2 -1
  108. package/src/common/index.ts +6 -2
  109. package/src/common/utils/reduceToText.tsx +30 -2
  110. package/src/components/Application/ApplicationViewability.tsx +61 -0
  111. package/src/components/Application/_content.scss +7 -0
  112. package/src/components/Application/_header.scss +12 -3
  113. package/src/components/Application/_viewability.scss +13 -0
  114. package/src/components/Application/application.scss +1 -0
  115. package/src/components/Application/index.ts +1 -0
  116. package/src/components/Application/stories/ApplicationViewability.stories.tsx +37 -0
  117. package/src/components/Application/tests/ApplicationViewability.test.tsx +43 -0
  118. package/src/components/AutoSuggestion/tests/ExtendedCodeEditor.test.tsx +1 -1
  119. package/src/components/Card/card.scss +6 -0
  120. package/src/components/Checkbox/checkbox.scss +14 -2
  121. package/src/components/ContentGroup/_contentgroup.scss +9 -0
  122. package/src/components/ContextOverlay/ContextMenu.tsx +4 -1
  123. package/src/components/ContextOverlay/ContextOverlay.tsx +74 -24
  124. package/src/components/ContextOverlay/tests/ContextMenu.test.tsx +43 -0
  125. package/src/components/ContextOverlay/tests/ContextOverlay.test.tsx +71 -0
  126. package/src/components/Depiction/depiction.scss +6 -0
  127. package/src/components/Dialog/stories/Modal.stories.tsx +12 -150
  128. package/src/components/Dialog/stories/ModalContext.stories.tsx +153 -0
  129. package/src/components/FlexibleLayout/flexiblelayout.scss +16 -0
  130. package/src/components/Grid/grid.scss +17 -0
  131. package/src/components/Grid/stories/Grid.stories.tsx +10 -7
  132. package/src/components/Grid/stories/GridRow.stories.tsx +13 -7
  133. package/src/components/Icon/IconButton.tsx +1 -1
  134. package/src/components/Notification/notification.scss +6 -0
  135. package/src/components/OverviewItem/overviewitem.scss +9 -0
  136. package/src/components/OverviewItem/stories/OverviewItem.stories.tsx +28 -0
  137. package/src/components/OverviewItem/stories/OverviewItemActions.stories.tsx +2 -2
  138. package/src/components/OverviewItem/stories/OverviewItemDescription.stories.tsx +1 -1
  139. package/src/components/OverviewItem/stories/OverviewItemLine.stories.tsx +1 -1
  140. package/src/components/PropertyValuePair/propertyvalue.scss +23 -1
  141. package/src/components/Separation/separation.scss +6 -0
  142. package/src/components/Table/table.scss +22 -0
  143. package/src/components/Tabs/stories/TabTitle.stories.tsx +7 -2
  144. package/src/components/Tag/stories/TagList.stories.tsx +2 -2
  145. package/src/components/Tag/tag.scss +19 -9
  146. package/src/components/TextReducer/TextReducer.stories.tsx +2 -1
  147. package/src/components/TextReducer/TextReducer.test.tsx +44 -0
  148. package/src/components/TextReducer/TextReducer.tsx +14 -4
  149. package/src/components/Typography/InlineText.tsx +24 -0
  150. package/src/components/Typography/_reset.scss +1 -0
  151. package/src/components/Typography/index.ts +1 -0
  152. package/src/components/Typography/stories/InlineText.stories.tsx +27 -0
  153. package/src/components/Typography/typography.scss +28 -2
  154. package/src/extensions/codemirror/CodeMirror.tsx +4 -4
  155. package/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts +1 -2
  156. package/src/extensions/codemirror/tests/codemirrorTestHelper.ts +3 -3
  157. package/src/extensions/react-flow/edges/EdgeNew.tsx +2 -1
  158. package/src/extensions/react-flow/handles/HandleDefault.tsx +2 -2
  159. package/src/extensions/react-flow/minimap/MiniMap.tsx +2 -1
  160. package/src/extensions/react-flow/minimap/MiniMapV12.tsx +1 -1
  161. package/src/extensions/react-flow/nodes/_nodes.scss +4 -3
  162. package/src/extensions/react-flow/nodes/nodeUtils.tsx +1 -0
  163. package/src/extensions/react-flow/nodes/stories/NodeContent.stories.tsx +2 -2
@@ -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
+ };
@@ -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
+ });
@@ -9,6 +9,7 @@ import {
9
9
  } from "@xyflow/react";
10
10
 
11
11
  import { CLASSPREFIX as eccgui } from "../../../configuration/constants";
12
+
12
13
  import { EdgeNew } from "./../../../extensions/react-flow/edges/EdgeNew";
13
14
 
14
15
  export type ReactFlowV12ContainerProps = ReactFlowV12Props;
@@ -1,6 +1,7 @@
1
- import { CLASSPREFIX as eccgui } from "../../configuration/constants";
2
1
  import { Intent as BlueprintIntent } from "@blueprintjs/core";
3
2
 
3
+ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
4
+
4
5
  export type IntentBlueprint = BlueprintIntent;
5
6
  export const DefinitionsBlueprint = BlueprintIntent;
6
7
 
@@ -1,3 +1,5 @@
1
+ import { decode } from "he";
2
+
1
3
  import { invisibleZeroWidthCharacters } from "./utils/characters";
2
4
  import { colorCalculateDistance } from "./utils/colorCalculateDistance";
3
5
  import decideContrastColorValue from "./utils/colorDecideContrastvalue";
@@ -6,7 +8,8 @@ import getColorConfiguration from "./utils/getColorConfiguration";
6
8
  import { getScrollParent } from "./utils/getScrollParent";
7
9
  import { getGlobalVar, setGlobalVar } from "./utils/globalVars";
8
10
  import { openInNewTab } from "./utils/openInNewTab";
9
- import { reduceToText } from "./utils/reduceToText"
11
+ import { reduceToText } from "./utils/reduceToText";
12
+ export type { DecodeOptions as DecodeHtmlEntitiesOptions } from "he";
10
13
  export type { IntentTypes as IntentBaseTypes } from "./Intent";
11
14
 
12
15
  export const utils = {
@@ -20,5 +23,6 @@ export const utils = {
20
23
  getScrollParent,
21
24
  getEnabledColorsFromPalette,
22
25
  textToColorHash,
23
- reduceToText
26
+ reduceToText,
27
+ decodeHtmlEntities: decode,
24
28
  };
@@ -3,6 +3,7 @@ import { renderToString } from "react-dom/server";
3
3
  import * as ReactIs from "react-is";
4
4
 
5
5
  import { TextReducerProps } from "./../../components/TextReducer/TextReducer";
6
+ import { DecodeHtmlEntitiesOptions, utils } from "./../";
6
7
 
7
8
  export interface ReduceToTextFuncType {
8
9
  (
@@ -10,12 +11,12 @@ export interface ReduceToTextFuncType {
10
11
  * Component or text to reduce HTML markup content to plain text.
11
12
  */
12
13
  input: React.ReactNode | React.ReactNode[] | string,
13
- options?: Pick<TextReducerProps, "maxNodes" | "maxLength">
14
+ options?: Pick<TextReducerProps, "maxNodes" | "maxLength" | "decodeHtmlEntities" | "decodeHtmlEntitiesOptions">
14
15
  ): string;
15
16
  }
16
17
 
17
18
  export const reduceToText: ReduceToTextFuncType = (input, options) => {
18
- const { maxNodes, maxLength } = options || {};
19
+ const { maxNodes, maxLength, decodeHtmlEntities } = options || {};
19
20
  const content: React.ReactNode | React.ReactNode[] = input;
20
21
  let nodeCount = 0;
21
22
 
@@ -46,6 +47,33 @@ export const reduceToText: ReduceToTextFuncType = (input, options) => {
46
47
  // Basic HTML cleanup
47
48
  text = text.replace(/<[^\s][^>]*>/g, "").replace(/\n/g, " ");
48
49
 
50
+ if (decodeHtmlEntities) {
51
+ const decodeDefaultOptions = {
52
+ isAttributeValue: true,
53
+ strict: true,
54
+ } as DecodeHtmlEntitiesOptions;
55
+ let decodeErrors = 0;
56
+ // we decode in pieces to apply some error tolerance even in strict mode
57
+ text = text
58
+ .split(" ")
59
+ .map((value) => {
60
+ try {
61
+ return utils.decodeHtmlEntities(value, {
62
+ ...decodeDefaultOptions,
63
+ ...options?.decodeHtmlEntitiesOptions,
64
+ });
65
+ } catch {
66
+ decodeErrors++;
67
+ return value;
68
+ }
69
+ })
70
+ .join(" ");
71
+ if (decodeErrors > 0) {
72
+ // eslint-disable-next-line no-console
73
+ console.warn(`${decodeErrors} parse error(s) for decodeHtmlEntities, return un-decoded text`, text);
74
+ }
75
+ }
76
+
49
77
  if (typeof maxLength === "number") {
50
78
  text = text.slice(0, maxLength);
51
79
  }
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+ import classNames from "classnames";
3
+
4
+ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
5
+
6
+ type media = "print" | "screen";
7
+
8
+ interface ApplicationViewabilityShow {
9
+ /**
10
+ * Show on media type.
11
+ * If used, `hide` cannot be set.
12
+ */
13
+ show: media;
14
+ hide?: never;
15
+ }
16
+
17
+ interface ApplicationViewabilityHide {
18
+ /**
19
+ * Hide on media type.
20
+ * If used, `show` cannot be set.
21
+ */
22
+ hide: media;
23
+ show?: never;
24
+ }
25
+
26
+ interface ApplicationViewabilityUndecided {
27
+ /**
28
+ * Only one child allowed.
29
+ * Need to process the `className` property.
30
+ */
31
+ children: React.ReactElement<{ className?: string }>;
32
+ }
33
+
34
+ export type ApplicationViewabilityProps = ApplicationViewabilityUndecided &
35
+ (ApplicationViewabilityShow | ApplicationViewabilityHide);
36
+
37
+ /**
38
+ * Sets the viewability of the the contained element regarding media.
39
+ * Can be used to hide elements, e.g. when the page is printed.
40
+ */
41
+ export const ApplicationViewability = ({ children, show, hide }: ApplicationViewabilityProps) => {
42
+ if (!show && !hide) {
43
+ return children;
44
+ }
45
+ if (show === hide) {
46
+ // eslint-disable-next-line no-console
47
+ console.warn("`<ApplicationViewability/>` used with same media type for `hide` and `show`.");
48
+ return children;
49
+ }
50
+
51
+ const enhancedClone = React.cloneElement(children, {
52
+ className: classNames(children.props.className, {
53
+ [`${eccgui}-application__hide--${hide}`]: hide,
54
+ [`${eccgui}-application__show--${show}`]: show,
55
+ }),
56
+ });
57
+
58
+ return enhancedClone;
59
+ };
60
+
61
+ export default ApplicationViewability;
@@ -25,3 +25,10 @@ $ui-02: $eccgui-color-workspace-background !default;
25
25
  .#{$eccgui}-application__content--railsidebar {
26
26
  margin-left: mini-units(8);
27
27
  }
28
+
29
+ @media print {
30
+ .#{$eccgui}-application__content {
31
+ padding: $eccgui-size-block-whitespace 0 0 0 !important;
32
+ margin: 0;
33
+ }
34
+ }
@@ -98,10 +98,10 @@ span.#{$prefix}--header__name {
98
98
  .#{$eccgui}-application__title--content {
99
99
  display: inline-block;
100
100
  overflow: hidden;
101
+ text-overflow: ellipsis;
101
102
  font-size: $eccgui-size-typo-caption;
102
103
  font-weight: $eccgui-font-weight-bold;
103
104
  line-height: $eccgui-size-typo-caption-lineheight;
104
- text-overflow: ellipsis;
105
105
  letter-spacing: $eccgui-font-spacing-wide;
106
106
  white-space: nowrap;
107
107
  }
@@ -122,7 +122,7 @@ span.#{$prefix}--header__name {
122
122
  height: auto;
123
123
  max-height: mini-units(5);
124
124
  padding: 0;
125
- margin: mini-units(1.4) 0 mini-units(1.6) 0;
125
+ margin: mini-units(1.4) 0 mini-units(1.6);
126
126
  vertical-align: middle;
127
127
  }
128
128
  }
@@ -195,9 +195,9 @@ a.#{$prefix}--header__menu-item:active {
195
195
  .#{$prefix}--header__action.#{$prefix}--btn--primary:focus,
196
196
  a.#{$prefix}--header__name:focus,
197
197
  a.#{$prefix}--header__menu-item:focus {
198
- border: none;
199
198
  outline: 1px dotted $shell-header-focus;
200
199
  outline-offset: -1px;
200
+ border: none;
201
201
  box-shadow: none;
202
202
  }
203
203
  .#{$prefix}--header__menu-title[aria-expanded="true"] {
@@ -267,3 +267,12 @@ a.#{$prefix}--header__menu-item:focus > svg {
267
267
  margin: 0;
268
268
  }
269
269
  }
270
+
271
+ @media print {
272
+ .#{$eccgui}-application__header {
273
+ position: relative;
274
+ & > :not(.#{$eccgui}-workspace__header) {
275
+ display: none;
276
+ }
277
+ }
278
+ }
@@ -0,0 +1,13 @@
1
+ @media print {
2
+ .#{eccgui}-application__hide--print,
3
+ .#{eccgui}-application__show--screen {
4
+ display: none !important;
5
+ }
6
+ }
7
+
8
+ @media screen {
9
+ .#{eccgui}-application__hide--screen,
10
+ .#{eccgui}-application__show--print {
11
+ display: none !important;
12
+ }
13
+ }
@@ -10,3 +10,4 @@
10
10
  // @import '~@carbon/react/scss/components/ui-shell/navigation-menu';
11
11
  @import "content";
12
12
  @import "dropzone";
13
+ @import "viewability";
@@ -8,4 +8,5 @@ export * from "./ApplicationToolbar";
8
8
  export * from "./ApplicationToolbarSection";
9
9
  export * from "./ApplicationToolbarAction";
10
10
  export * from "./ApplicationToolbarPanel";
11
+ export * from "./ApplicationViewability";
11
12
  export * from "./helper";
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { LoremIpsum } from "react-lorem-ipsum";
3
+ import { Meta, StoryFn } from "@storybook/react";
4
+
5
+ import { ApplicationViewability } from "../../../index";
6
+ export default {
7
+ title: "Components/Application/Viewability",
8
+ component: ApplicationViewability,
9
+ argTypes: {
10
+ children: {
11
+ control: false,
12
+ },
13
+ hide: {
14
+ control: {
15
+ type: "radio",
16
+ },
17
+ options: ["print", "screen"],
18
+ },
19
+ show: {
20
+ control: {
21
+ type: "radio",
22
+ },
23
+ options: ["print", "screen"],
24
+ },
25
+ },
26
+ } as Meta<typeof ApplicationViewability>;
27
+
28
+ const TemplateBasicExample: StoryFn<typeof ApplicationViewability> = (args) => <ApplicationViewability {...args} />;
29
+
30
+ export const Default = TemplateBasicExample.bind({});
31
+ Default.args = {
32
+ children: (
33
+ <div>
34
+ <LoremIpsum random={false} />
35
+ </div>
36
+ ),
37
+ };
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ import { expect } from "@storybook/test";
3
+ import { render } from "@testing-library/react";
4
+
5
+ import "@testing-library/jest-dom";
6
+
7
+ import { ApplicationViewability, ApplicationViewabilityProps, CLASSPREFIX as eccgui } from "../../../index";
8
+
9
+ import { Default as ApplicationViewabilityStory } from "./../stories/ApplicationViewability.stories";
10
+
11
+ const applyViewabilityAndCheckClass = (props: Omit<ApplicationViewabilityProps, "children">) => {
12
+ const { container } = render(<ApplicationViewability {...ApplicationViewabilityStory.args} {...props} />);
13
+ const element = container.getElementsByClassName(
14
+ props.hide ? `${eccgui}-application__hide--${props.hide}` : `${eccgui}-application__show--${props.show}`
15
+ );
16
+ expect(element.length).toBe(1);
17
+ return element;
18
+ };
19
+
20
+ describe("ApplicationViewability", () => {
21
+ it("should be visible on `show=screen`", () => {
22
+ applyViewabilityAndCheckClass({ show: "screen" });
23
+ /**
24
+ * Currently we cannot really test visibility via jest if it is defined by S/CSS rules because those styles are not known.
25
+ * Looks like it is not too easy to include and test them.
26
+ * So we only test for the correct CSS class.
27
+ */
28
+ // console.log(window.getComputedStyle(element.item(0)??new Element).getPropertyValue("display"));
29
+ // waitFor(() => expect(element).toBeVisible());
30
+ });
31
+ it("should not be visible on `hide=screen`", () => {
32
+ applyViewabilityAndCheckClass({ hide: "screen" });
33
+ // waitFor(() => expect(element).not.toBeVisible());
34
+ });
35
+ it("should be visible on `hide=print`", () => {
36
+ applyViewabilityAndCheckClass({ hide: "print" });
37
+ // waitFor(() => expect(element).toBeVisible());
38
+ });
39
+ it("should not be visible on `show=print`", () => {
40
+ applyViewabilityAndCheckClass({ show: "print" });
41
+ // waitFor(() => expect(element).not.toBeVisible());
42
+ });
43
+ });
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { render } from "@testing-library/react";
3
2
  import { EditorView } from "@codemirror/view";
3
+ import { render } from "@testing-library/react";
4
4
 
5
5
  import "@testing-library/jest-dom";
6
6
 
@@ -288,3 +288,9 @@ $eccgui-size-card-spacing: $eccgui-size-typo-base !default;
288
288
  }
289
289
  }
290
290
  }
291
+
292
+ @media print {
293
+ .#{$eccgui}-card__actions {
294
+ display: none;
295
+ }
296
+ }
@@ -24,9 +24,15 @@ $control-indicator-spacing: $eccgui-size-inline-whitespace !default;
24
24
  // $switch-background-color-active: rgba($gray1, 0.5) !default;
25
25
  // $switch-background-color-disabled: $button-background-color-disabled !default;
26
26
  $switch-checked-background-color: $eccgui-color-accent !default;
27
- $switch-checked-background-color-hover: eccgui-color-rgba($switch-checked-background-color, $eccgui-opacity-narrow) !default;
27
+ $switch-checked-background-color-hover: eccgui-color-rgba(
28
+ $switch-checked-background-color,
29
+ $eccgui-opacity-narrow
30
+ ) !default;
28
31
  $switch-checked-background-color-active: $switch-checked-background-color-hover !default;
29
- $switch-checked-background-color-disabled: eccgui-color-rgba($switch-checked-background-color, $eccgui-opacity-disabled) !default;
32
+ $switch-checked-background-color-disabled: eccgui-color-rgba(
33
+ $switch-checked-background-color,
34
+ $eccgui-opacity-disabled
35
+ ) !default;
30
36
 
31
37
  @import "~@blueprintjs/core/src/components/forms/controls"; // Checkbox, Radio, Switch
32
38
 
@@ -73,3 +79,9 @@ $switch-checked-background-color-disabled: eccgui-color-rgba($switch-checked-bac
73
79
  display: inline-block;
74
80
  vertical-align: text-top;
75
81
  }
82
+
83
+ @media print {
84
+ .#{$ns}-control {
85
+ print-color-adjust: exact;
86
+ }
87
+ }
@@ -60,3 +60,12 @@ $eccgui-color-scontentgroup-border-sub: eccgui-color-rgba(
60
60
  flex-shrink: 1;
61
61
  width: 100%;
62
62
  }
63
+
64
+ @media print {
65
+ .#{$eccgui}-contentgroup__header__options {
66
+ display: none;
67
+ }
68
+ .#{$eccgui}-contentgroup--border-sub::after {
69
+ print-color-adjust: exact;
70
+ }
71
+ }
@@ -66,6 +66,8 @@ export const ContextMenu = ({
66
66
  so by default we use the title attribute instead of Tooltip. */
67
67
  tooltipAsTitle = true,
68
68
  preventPlaceholder = false,
69
+ "data-test-id": dataTestId,
70
+ "data-testid": dataTestid,
69
71
  ...restProps
70
72
  }: ContextMenuProps) => {
71
73
  const toggleButton =
@@ -76,7 +78,8 @@ export const ContextMenu = ({
76
78
  text={togglerText}
77
79
  large={togglerLarge}
78
80
  disabled={!!disabled}
79
- data-test-id={restProps["data-test-id"]}
81
+ data-test-id={dataTestId ?? undefined}
82
+ data-testid={dataTestid ?? undefined}
80
83
  />
81
84
  ) : (
82
85
  (togglerElement as ReactElement)
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import {
3
3
  Classes as BlueprintClasses,
4
4
  Popover as BlueprintPopover,
5
+ PopoverInteractionKind as InteractionKind,
5
6
  PopoverProps as BlueprintPopoverProps,
6
7
  Utils as BlueprintUtils,
7
8
  } from "@blueprintjs/core";
@@ -37,9 +38,10 @@ export const ContextOverlay = ({
37
38
  usePlaceholder = false,
38
39
  ...otherPopoverProps
39
40
  }: ContextOverlayProps) => {
40
- const placeholderRef = React.useRef(null);
41
- const eventMemory = React.useRef<undefined | "afterhover" | "afterfocus">(undefined);
41
+ const placeholderRef = React.useRef<HTMLElement>(null);
42
+ const eventMemory = React.useRef<undefined | "mouseenter" | "focusin" | "click">(undefined);
42
43
  const swapDelay = React.useRef<null | NodeJS.Timeout>(null);
44
+ const interactionKind = React.useRef<InteractionKind>(otherPopoverProps.interactionKind ?? InteractionKind.CLICK);
43
45
  const swapDelayTime = 15;
44
46
  const [placeholder, setPlaceholder] = React.useState<boolean>(
45
47
  // use placeholder only for "simple" overlays without special states
@@ -50,37 +52,85 @@ export const ContextOverlay = ({
50
52
  usePlaceholder
51
53
  );
52
54
 
55
+ const swap = (ev: MouseEvent | globalThis.FocusEvent) => {
56
+ const waitForClick =
57
+ interactionKind.current === InteractionKind.CLICK ||
58
+ interactionKind.current === InteractionKind.CLICK_TARGET_ONLY;
59
+
60
+ if (swapDelay.current) {
61
+ clearTimeout(swapDelay.current);
62
+ }
63
+
64
+ const replacePlaceholder = () => {
65
+ eventMemory.current = ev.type as "mouseenter" | "focusin" | "click";
66
+ setPlaceholder(false);
67
+ };
68
+
69
+ if (waitForClick) {
70
+ ev.stopImmediatePropagation();
71
+ replacePlaceholder();
72
+ return;
73
+ }
74
+
75
+ swapDelay.current = setTimeout(
76
+ replacePlaceholder,
77
+ // we delay the swap for hover/focus to prevent unwanted effects
78
+ // (e.g. event hickup after replacing elements when it is not really necessary)
79
+ swapDelayTime
80
+ );
81
+ };
82
+
53
83
  React.useEffect(() => {
84
+ interactionKind.current = otherPopoverProps.interactionKind ?? InteractionKind.CLICK;
85
+ const waitForClick =
86
+ interactionKind.current === InteractionKind.CLICK ||
87
+ interactionKind.current === InteractionKind.CLICK_TARGET_ONLY;
88
+ const removeEvents = () => {
89
+ if (placeholderRef.current) {
90
+ placeholderRef.current.removeEventListener("click", swap);
91
+ placeholderRef.current.removeEventListener("mouseenter", swap);
92
+ placeholderRef.current.removeEventListener("focusin", swap);
93
+ }
94
+ return;
95
+ };
54
96
  if (placeholderRef.current) {
55
- const swap = (ev: MouseEvent | globalThis.FocusEvent) => {
56
- if (swapDelay.current) {
57
- clearTimeout(swapDelay.current);
58
- }
59
- swapDelay.current = setTimeout(() => {
60
- // we delay the swap to prevent unwanted effects
61
- // (e.g. event hickup after replacing elements when it is not really necessary)
62
- eventMemory.current = ev.type === "focusin" ? "afterfocus" : "afterhover";
63
- setPlaceholder(false);
64
- }, swapDelayTime);
65
- };
66
- (placeholderRef.current as HTMLElement).addEventListener("mouseenter", swap);
67
- (placeholderRef.current as HTMLElement).addEventListener("focusin", swap);
97
+ removeEvents(); // remove events in case of interaction kind changed during existence
98
+ if (waitForClick) {
99
+ placeholderRef.current.addEventListener("click", swap);
100
+ } else {
101
+ placeholderRef.current.addEventListener("mouseenter", swap);
102
+ placeholderRef.current.addEventListener("focusin", swap);
103
+ }
68
104
  return () => {
69
- if (placeholderRef.current) {
70
- (placeholderRef.current as HTMLElement).removeEventListener("mouseenter", swap);
71
- (placeholderRef.current as HTMLElement).removeEventListener("focusin", swap);
72
- }
105
+ removeEvents();
73
106
  };
74
107
  }
75
108
  return () => {};
76
- }, [!!placeholderRef.current]);
109
+ }, [!!placeholderRef.current, otherPopoverProps.interactionKind]);
77
110
 
78
111
  const refocus = React.useCallback((node) => {
79
- if (eventMemory.current === "afterfocus" && node) {
80
- const target = node.targetRef.current.children[0];
81
- if (target) {
112
+ const target = node?.targetRef.current.children[0];
113
+ if (!eventMemory.current || !target) {
114
+ return;
115
+ }
116
+ switch (eventMemory.current) {
117
+ case "focusin":
82
118
  target.focus();
83
- }
119
+ break;
120
+ case "click":
121
+ target.click();
122
+ break;
123
+ case "mouseenter":
124
+ // re-check if the cursor is still over the element after swapping the placeholder before triggering the event to bubble up
125
+ (target as HTMLElement).addEventListener(
126
+ "mouseover",
127
+ () => (target as HTMLElement).dispatchEvent(new MouseEvent("mouseover", { bubbles: true })),
128
+ {
129
+ capture: true,
130
+ once: true,
131
+ }
132
+ );
133
+ break;
84
134
  }
85
135
  }, []);
86
136