@eccenca/gui-elements 24.2.0-rc.0 → 24.2.0-rc.2

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 (109) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +10 -7
  3. package/dist/cjs/cmem/markdown/Markdown.js +13 -11
  4. package/dist/cjs/cmem/markdown/Markdown.js.map +1 -1
  5. package/dist/cjs/cmem/markdown/highlightSearchWords.js +6 -1
  6. package/dist/cjs/cmem/markdown/highlightSearchWords.js.map +1 -1
  7. package/dist/cjs/common/Intent/index.js +4 -11
  8. package/dist/cjs/common/Intent/index.js.map +1 -1
  9. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js +27 -18
  10. package/dist/cjs/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  11. package/dist/cjs/components/Button/Button.js +2 -2
  12. package/dist/cjs/components/Button/Button.js.map +1 -1
  13. package/dist/cjs/components/ContextOverlay/ContextMenu.js +3 -2
  14. package/dist/cjs/components/ContextOverlay/ContextMenu.js.map +1 -1
  15. package/dist/cjs/components/ContextOverlay/ContextOverlay.js +46 -2
  16. package/dist/cjs/components/ContextOverlay/ContextOverlay.js.map +1 -1
  17. package/dist/cjs/components/Form/FieldItem.js +3 -2
  18. package/dist/cjs/components/Form/FieldItem.js.map +1 -1
  19. package/dist/cjs/components/Form/FieldSet.js +3 -2
  20. package/dist/cjs/components/Form/FieldSet.js.map +1 -1
  21. package/dist/cjs/components/Icon/canonicalIconNames.js +1 -0
  22. package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
  23. package/dist/cjs/components/MultiSelect/MultiSelect.js +18 -14
  24. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  25. package/dist/cjs/components/Notification/Notification.js +7 -4
  26. package/dist/cjs/components/Notification/Notification.js.map +1 -1
  27. package/dist/cjs/components/OverviewItem/OverviewItemActions.js +9 -2
  28. package/dist/cjs/components/OverviewItem/OverviewItemActions.js.map +1 -1
  29. package/dist/cjs/components/Spinner/Spinner.js +7 -5
  30. package/dist/cjs/components/Spinner/Spinner.js.map +1 -1
  31. package/dist/cjs/components/Tooltip/Tooltip.js +75 -7
  32. package/dist/cjs/components/Tooltip/Tooltip.js.map +1 -1
  33. package/dist/esm/cmem/markdown/Markdown.js +13 -11
  34. package/dist/esm/cmem/markdown/Markdown.js.map +1 -1
  35. package/dist/esm/cmem/markdown/highlightSearchWords.js +6 -1
  36. package/dist/esm/cmem/markdown/highlightSearchWords.js.map +1 -1
  37. package/dist/esm/common/Intent/index.js +14 -10
  38. package/dist/esm/common/Intent/index.js.map +1 -1
  39. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js +32 -25
  40. package/dist/esm/components/AutoSuggestion/AutoSuggestion.js.map +1 -1
  41. package/dist/esm/components/Button/Button.js +2 -2
  42. package/dist/esm/components/Button/Button.js.map +1 -1
  43. package/dist/esm/components/ContextOverlay/ContextMenu.js +3 -2
  44. package/dist/esm/components/ContextOverlay/ContextMenu.js.map +1 -1
  45. package/dist/esm/components/ContextOverlay/ContextOverlay.js +63 -3
  46. package/dist/esm/components/ContextOverlay/ContextOverlay.js.map +1 -1
  47. package/dist/esm/components/Form/FieldItem.js +3 -2
  48. package/dist/esm/components/Form/FieldItem.js.map +1 -1
  49. package/dist/esm/components/Form/FieldSet.js +3 -2
  50. package/dist/esm/components/Form/FieldSet.js.map +1 -1
  51. package/dist/esm/components/Icon/canonicalIconNames.js +1 -0
  52. package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
  53. package/dist/esm/components/MultiSelect/MultiSelect.js +18 -14
  54. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  55. package/dist/esm/components/Notification/Notification.js +7 -4
  56. package/dist/esm/components/Notification/Notification.js.map +1 -1
  57. package/dist/esm/components/OverviewItem/OverviewItemActions.js +25 -2
  58. package/dist/esm/components/OverviewItem/OverviewItemActions.js.map +1 -1
  59. package/dist/esm/components/Spinner/Spinner.js +9 -5
  60. package/dist/esm/components/Spinner/Spinner.js.map +1 -1
  61. package/dist/esm/components/Tooltip/Tooltip.js +92 -8
  62. package/dist/esm/components/Tooltip/Tooltip.js.map +1 -1
  63. package/dist/types/cmem/markdown/Markdown.d.ts +8 -1
  64. package/dist/types/common/Intent/index.d.ts +10 -1
  65. package/dist/types/components/Button/Button.d.ts +5 -1
  66. package/dist/types/components/ContextOverlay/ContextMenu.d.ts +9 -2
  67. package/dist/types/components/ContextOverlay/ContextOverlay.d.ts +6 -1
  68. package/dist/types/components/Form/FieldItem.d.ts +10 -1
  69. package/dist/types/components/Form/FieldSet.d.ts +10 -1
  70. package/dist/types/components/Icon/canonicalIconNames.d.ts +1 -0
  71. package/dist/types/components/MultiSelect/MultiSelect.d.ts +12 -4
  72. package/dist/types/components/Notification/Notification.d.ts +10 -1
  73. package/dist/types/components/OverviewItem/OverviewItemActions.d.ts +13 -1
  74. package/dist/types/components/Spinner/Spinner.d.ts +8 -3
  75. package/dist/types/components/Table/TableContainer.d.ts +2 -2
  76. package/dist/types/components/Table/TableExpandRow.d.ts +1 -1
  77. package/dist/types/components/Table/index.d.ts +1 -0
  78. package/dist/types/components/Tooltip/Tooltip.d.ts +9 -1
  79. package/package.json +10 -7
  80. package/src/cmem/markdown/Markdown.tsx +25 -14
  81. package/src/cmem/markdown/highlightSearchWords.test.ts +8 -2
  82. package/src/cmem/markdown/highlightSearchWords.ts +6 -1
  83. package/src/common/Intent/index.ts +6 -6
  84. package/src/components/AutoSuggestion/AutoSuggestion.tsx +35 -27
  85. package/src/components/Button/Button.stories.tsx +10 -6
  86. package/src/components/Button/Button.tsx +7 -2
  87. package/src/components/ContextOverlay/ContextMenu.stories.tsx +1 -1
  88. package/src/components/ContextOverlay/ContextMenu.tsx +26 -13
  89. package/src/components/ContextOverlay/ContextOverlay.tsx +83 -5
  90. package/src/components/Form/FieldItem.tsx +14 -3
  91. package/src/components/Form/FieldSet.tsx +13 -2
  92. package/src/components/Form/Stories/FieldItem.stories.tsx +4 -0
  93. package/src/components/Form/Stories/FieldSet.stories.tsx +4 -0
  94. package/src/components/Icon/canonicalIconNames.tsx +1 -0
  95. package/src/components/MultiSelect/MultiSelect.tsx +27 -15
  96. package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +6 -0
  97. package/src/components/Notification/Notification.stories.tsx +4 -0
  98. package/src/components/Notification/Notification.tsx +17 -4
  99. package/src/components/OverviewItem/OverviewItemActions.tsx +24 -1
  100. package/src/components/OverviewItem/stories/OverviewItemList.stories.tsx +2 -7
  101. package/src/components/OverviewItem/stories/OverviewItemListPerformance.tsx +174 -0
  102. package/src/components/OverviewItem/stories/OverviewItemPerformance.stories.tsx +19 -0
  103. package/src/components/Spinner/Spinner.tsx +13 -5
  104. package/src/components/Spinner/Stories/spinner.stories.tsx +6 -1
  105. package/src/components/Table/TableContainer.tsx +2 -2
  106. package/src/components/Table/TableExpandRow.tsx +1 -1
  107. package/src/components/Table/index.tsx +1 -0
  108. package/src/components/Tooltip/Tooltip.stories.tsx +3 -2
  109. package/src/components/Tooltip/Tooltip.tsx +121 -10
@@ -12,7 +12,7 @@ export default {
12
12
  },
13
13
  argTypes: {
14
14
  children: {
15
- control: "none",
15
+ control: false,
16
16
  description: "Should contain only `OverviewItem` elements, maybe wrapped inside cards.",
17
17
  },
18
18
  },
@@ -26,10 +26,5 @@ ItemList.args = {
26
26
  hasDivider: true,
27
27
  densityHigh: false,
28
28
  columns: 1,
29
- children: [
30
- <OverviewItem {...ItemExample.args} />,
31
- <OverviewItem {...ItemExample.args} />,
32
- <OverviewItem {...ItemExample.args} />,
33
- <OverviewItem {...ItemExample.args} />,
34
- ],
29
+ children: Array(4).fill(<OverviewItem {...ItemExample.args} />),
35
30
  };
@@ -0,0 +1,174 @@
1
+ import React from "react";
2
+ import { loremIpsum } from "react-lorem-ipsum";
3
+
4
+ import {
5
+ ApplicationContainer,
6
+ Button,
7
+ ContextMenu,
8
+ Depiction,
9
+ Icon,
10
+ IconButton,
11
+ OverflowText,
12
+ OverviewItem,
13
+ OverviewItemActions,
14
+ OverviewItemDescription,
15
+ OverviewItemLine,
16
+ OverviewItemList,
17
+ Spinner,
18
+ Tooltip,
19
+ } from "./../../../../index";
20
+ import { Default as ContextMenuExample } from "./../../ContextOverlay/ContextMenu.stories";
21
+ import canonicalIcons, { ValidIconName } from "./../../Icon/canonicalIconNames";
22
+
23
+ interface OverviewItemListPerformanceProps {
24
+ /** list length */
25
+ length: number;
26
+ /** include `OverviewItem` elements in list */
27
+ useOverviewitem: boolean;
28
+ /** include depiction */
29
+ withDepiction: boolean;
30
+ /** include description */
31
+ withDescription: boolean;
32
+ /** include icon button in hidden actions */
33
+ withIconButtonInHiddenActions: boolean;
34
+ /** include button in actions */
35
+ withButtonInActions: boolean;
36
+ /** inlcude context menu in actions */
37
+ withContextMenuInActions: boolean;
38
+ /** include tooltips on all elments that can have one */
39
+ withTooltips: boolean;
40
+ /** delay rendering of action items */
41
+ delayActions: number;
42
+ }
43
+
44
+ const createTextArray = (items: number, length: number) => {
45
+ return loremIpsum({
46
+ p: 1,
47
+ avgWordsPerSentence: length,
48
+ avgSentencesPerParagraph: items,
49
+ startWithLoremIpsum: false,
50
+ random: false,
51
+ })[0].split(". ");
52
+ };
53
+
54
+ const textShort = createTextArray(100, 3);
55
+ const textLong = createTextArray(100, 25);
56
+
57
+ export const OverviewItemListPerformance = ({
58
+ length = 500,
59
+ useOverviewitem = false,
60
+ withDepiction = false,
61
+ withDescription = true,
62
+ withButtonInActions = false,
63
+ withIconButtonInHiddenActions = false,
64
+ withTooltips = false,
65
+ withContextMenuInActions = false,
66
+ delayActions = 1,
67
+ }: OverviewItemListPerformanceProps) => {
68
+ const renderStart = new Date();
69
+ const containerRef = React.useRef(null);
70
+ const observerRef = React.useRef<MutationObserver | undefined>(undefined);
71
+
72
+ const iconNames = Object.keys(canonicalIcons);
73
+
74
+ const ItemWrapper = useOverviewitem ? OverviewItem : "div";
75
+ const ItemDescription = useOverviewitem ? OverviewItemDescription : "div";
76
+ const ItemLine = useOverviewitem ? OverviewItemLine : "div";
77
+ const ItemActions = useOverviewitem ? OverviewItemActions : "span";
78
+
79
+ const actionsProps = useOverviewitem
80
+ ? { delayDisplayChildren: delayActions, delaySkeleton: <Spinner position="inline" size="tiny" /> }
81
+ : {};
82
+ const hiddenActionsProps = useOverviewitem ? { ...actionsProps, hiddenInteractions: true } : {};
83
+
84
+ React.useEffect(() => {
85
+ const renderEnd = new Date();
86
+ // eslint-disable-next-line no-console
87
+ console.log(
88
+ "OverviewItemListPerformance Rendering time (s)",
89
+ (renderEnd.getTime() - renderStart.getTime()) / 1000
90
+ );
91
+
92
+ if (containerRef.current) {
93
+ let changeCount = 0;
94
+ const changeReporter = () => {
95
+ const renderChange = new Date();
96
+ // eslint-disable-next-line no-console
97
+ console.log(
98
+ `Change ${++changeCount} after time (s)`,
99
+ (renderChange.getTime() - renderEnd.getTime()) / 1000
100
+ );
101
+ };
102
+ if (observerRef.current) {
103
+ observerRef.current.disconnect();
104
+ }
105
+ observerRef.current = new MutationObserver(changeReporter);
106
+ observerRef.current.observe(containerRef.current, { childList: true, subtree: true });
107
+ }
108
+ });
109
+
110
+ return (
111
+ <div ref={containerRef}>
112
+ <ApplicationContainer>
113
+ <OverviewItemList hasDivider hasSpacing columns={useOverviewitem ? 2 : 1}>
114
+ {Array(length)
115
+ .fill("x")
116
+ .map((_, id) => {
117
+ return (
118
+ <ItemWrapper key={id}>
119
+ {withDepiction && (
120
+ <Depiction
121
+ size="small"
122
+ image={<Icon name={iconNames[id % iconNames.length] as ValidIconName} />}
123
+ caption={withTooltips ? textShort[(id + 10) % textShort.length] : undefined}
124
+ captionPosition={withTooltips ? "tooltip" : "none"}
125
+ />
126
+ )}
127
+ {withDescription && (
128
+ <ItemDescription>
129
+ <ItemLine large={useOverviewitem ? true : undefined}>
130
+ {textShort[id % textShort.length]}
131
+ </ItemLine>
132
+ <ItemLine small={useOverviewitem ? true : undefined}>
133
+ {withTooltips ? (
134
+ <Tooltip content={textLong[id % textLong.length]}>
135
+ <OverflowText>{textLong[id % textLong.length]}</OverflowText>
136
+ </Tooltip>
137
+ ) : (
138
+ <OverflowText>{textLong[id % textLong.length]}</OverflowText>
139
+ )}
140
+ </ItemLine>
141
+ </ItemDescription>
142
+ )}
143
+ {withIconButtonInHiddenActions && (
144
+ <ItemActions {...hiddenActionsProps}>
145
+ <IconButton
146
+ name={iconNames[(id + 23) % iconNames.length] as ValidIconName}
147
+ text={textShort[(id + 27) % textShort.length]}
148
+ tooltipAsTitle={!withTooltips}
149
+ />
150
+ </ItemActions>
151
+ )}
152
+ {(withButtonInActions || withContextMenuInActions) && (
153
+ <ItemActions {...actionsProps}>
154
+ {withButtonInActions && (
155
+ <Button onClick={() => alert("Button clicked")}>
156
+ {textShort[(id + 77) % textShort.length]}
157
+ </Button>
158
+ )}
159
+ {withContextMenuInActions && (
160
+ <ContextMenu
161
+ {...ContextMenuExample.args}
162
+ tooltipAsTitle={!withTooltips}
163
+ />
164
+ )}
165
+ </ItemActions>
166
+ )}
167
+ </ItemWrapper>
168
+ );
169
+ })}
170
+ </OverviewItemList>
171
+ </ApplicationContainer>
172
+ </div>
173
+ );
174
+ };
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { Meta, StoryFn } from "@storybook/react";
3
+
4
+ import { OverviewItemListPerformance } from "./OverviewItemListPerformance";
5
+
6
+ export default {
7
+ title: "Components/OverviewItem",
8
+ component: OverviewItemListPerformance,
9
+ argTypes: {},
10
+ } as Meta<typeof OverviewItemListPerformance>;
11
+
12
+ const Template: StoryFn<typeof OverviewItemListPerformance> = (args) => (
13
+ <OverviewItemListPerformance {...args}></OverviewItemListPerformance>
14
+ );
15
+
16
+ export const ListPerformance = Template.bind({});
17
+ ListPerformance.args = {
18
+ useOverviewitem: true,
19
+ };
@@ -11,14 +11,19 @@ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
11
11
  type SpinnerPosition = "local" | "inline" | "global";
12
12
  type SpinnerSize = "tiny" | "small" | "medium" | "large" | "xlarge" | "inherit";
13
13
  type SpinnerStroke = "thin" | "medium" | "bold";
14
- type Intent = "inherit" | "primary" | "success" | "warning" | "danger";
14
+ type Intent = "inherit" | "primary" | "success" | "warning" | "danger" | "none";
15
15
 
16
16
  /** A spinner that is either displayed globally or locally. */
17
- export interface SpinnerProps extends Omit<BlueprintSpinnerProps, "size"> {
17
+ export interface SpinnerProps extends Omit<BlueprintSpinnerProps, "size" | "intent"> {
18
18
  /**
19
19
  * intent value or a valid css color definition
20
+ * @deprecated (v25) it will allow in the future only a color value string and that for other states the intent property needs to be used
20
21
  */
21
22
  color?: Intent | string;
23
+ /**
24
+ * Intent state of the field item.
25
+ */
26
+ intent?: Intent;
22
27
  /**
23
28
  * Additional CSS class names.
24
29
  */
@@ -66,12 +71,14 @@ export interface SpinnerProps extends Omit<BlueprintSpinnerProps, "size"> {
66
71
  export const Spinner = ({
67
72
  className = "",
68
73
  color = "inherit",
74
+ intent,
69
75
  position = "local",
70
76
  size,
71
77
  stroke,
72
78
  showLocalBackdrop = false,
73
79
  delay = 0,
74
80
  overlayProps,
81
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
75
82
  description = "Loading indicator", // currently unsupported (FIXME):
76
83
  ...otherProps
77
84
  }: SpinnerProps) => {
@@ -91,8 +98,9 @@ export const Spinner = ({
91
98
  };
92
99
 
93
100
  const spinnerElement = position === "inline" ? "span" : "div";
94
- const spinnerColor = availableIntent.indexOf(color) < 0 ? color : null;
95
- const spinnerIntent = availableIntent.indexOf(color) < 0 ? "usercolor" : color;
101
+
102
+ const spinnerColor = !intent && availableIntent.indexOf(color) < 0 ? color : null;
103
+ const spinnerIntent = !intent && availableIntent.indexOf(color) < 0 ? "usercolor" : intent || color;
96
104
 
97
105
  let spinnerSize;
98
106
  let spinnerStroke;
@@ -130,7 +138,7 @@ export const Spinner = ({
130
138
  />
131
139
  );
132
140
 
133
- if (spinnerColor) {
141
+ if (spinnerColor && spinnerIntent === "usercolor") {
134
142
  spinner = <span style={{ color: spinnerColor }}>{spinner}</span>;
135
143
  }
136
144
 
@@ -1,12 +1,17 @@
1
1
  import React from "react";
2
2
  import { Meta, StoryFn } from "@storybook/react";
3
3
 
4
+ import { helpersArgTypes } from "../../../../.storybook/helpers";
4
5
  import Spinner from "../Spinner";
5
6
  export default {
6
7
  title: "Components/Spinner",
7
8
  component: Spinner,
8
9
  argTypes: {
9
- color: { control: "radio", options: ["inherit", "primary", "success", "warning", "danger"] },
10
+ color: { control: "color" },
11
+ intent: {
12
+ ...helpersArgTypes.exampleIntent,
13
+ options: ["UNDEFINED", "primary", "success", "warning", "danger", "none"],
14
+ },
10
15
  position: { control: "radio", options: ["local", "inline", "global"] },
11
16
  size: { control: "radio", options: ["tiny", "small", "medium", "large", "xlarge", "inherit"] },
12
17
  stroke: { control: "radio", options: ["thin", "medium", "bold"] },
@@ -11,7 +11,7 @@ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
11
11
 
12
12
  import { TableRowHeightSize, tableRowHeightSizes } from "./Table";
13
13
 
14
- interface TableDataContainerProps
14
+ export interface TableDataContainerProps
15
15
  extends Omit<
16
16
  CarbonDataTableProps<
17
17
  Array<Omit<CarbonDataTableRow<Array<CarbonDataTableHeader>>, "cells">>,
@@ -23,7 +23,7 @@ interface TableDataContainerProps
23
23
  children(signature: any): JSX.Element;
24
24
  size?: TableRowHeightSize;
25
25
  }
26
- interface TableSimpleContainerProps
26
+ export interface TableSimpleContainerProps
27
27
  extends Omit<CarbonTableContainerProps, "description" | "stickyHeader" | "title" | "useStaticWidth">,
28
28
  React.HTMLAttributes<HTMLDivElement> {
29
29
  children?: JSX.Element;
@@ -11,7 +11,7 @@ import { TableCell } from "./index";
11
11
  // workaround to get type/interface
12
12
  type CarbonTableExpandRowProps = React.ComponentProps<typeof CarbonTableExpandRow>;
13
13
  export interface TableExpandRowProps
14
- extends Omit<CarbonTableExpandRowProps, "ref" | "ariaLabel" | "expandIconDescription" | "aria-label">,
14
+ extends Omit<CarbonTableExpandRowProps, "children" | "ref" | "ariaLabel" | "expandIconDescription" | "aria-label">,
15
15
  React.HTMLAttributes<HTMLTableRowElement> {
16
16
  /**
17
17
  * This text is displayed as tooltip for the button that toggles the expanded/collapsed state.
@@ -7,3 +7,4 @@ export * from "./TableCell";
7
7
 
8
8
  // TODO, we may wrap to add own classes (currently not necessary)
9
9
  export { TableHead, TableBody, TableExpandedRow, TableHeader } from "@carbon/react";
10
+ export type { TableHeadProps, TableBodyProps, TableExpandedRowProps, TableHeaderProps, DataTableRenderProps } from "@carbon/react";
@@ -11,9 +11,10 @@ export default {
11
11
  argTypes: {},
12
12
  } as Meta<typeof Tooltip>;
13
13
 
14
+ let forcedUpdateKey = 0; // @see https://github.com/storybookjs/storybook/issues/13375#issuecomment-1291011856
14
15
  const Template: StoryFn<typeof Tooltip> = (args) => (
15
16
  <OverlaysProvider>
16
- <Tooltip {...args} />
17
+ <Tooltip {...args} key={++forcedUpdateKey} />
17
18
  </OverlaysProvider>
18
19
  );
19
20
 
@@ -30,7 +31,7 @@ const testContent = loremIpsum({
30
31
  * */
31
32
  export const Default = Template.bind({});
32
33
  Default.args = {
33
- children: <span>hover me</span>,
34
+ children: "hover me",
34
35
  content: testContent,
35
36
  addIndicator: true,
36
37
  };
@@ -3,6 +3,7 @@ import {
3
3
  Classes as BlueprintClasses,
4
4
  Tooltip as BlueprintTooltip,
5
5
  TooltipProps as BlueprintTooltipProps,
6
+ Utils as BlueprintUtils,
6
7
  } from "@blueprintjs/core";
7
8
 
8
9
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
@@ -33,6 +34,14 @@ export interface TooltipProps extends Omit<BlueprintTooltipProps, "position"> {
33
34
  * Set properties for the Markdown parser
34
35
  */
35
36
  markdownProps?: Omit<MarkdownProps, "children">;
37
+ /**
38
+ * Use the overlay target as placeholder before the real `<Tooltip /` is rendered on first hover or focus event.
39
+ * This can boost performance massive but it is currently experimental.
40
+ * Placeholders are never used when `disabled`, `defaultIsOpen` or `isOpen` is set to `true`, or if `renderTarget` is set.
41
+ * If the tooltip `content` is only a string then a placeholder is automatically used, too.
42
+ * You can prevent it in any case by setting it to `false`.
43
+ */
44
+ usePlaceholder?: boolean;
36
45
  }
37
46
 
38
47
  export const Tooltip = ({
@@ -43,8 +52,106 @@ export const Tooltip = ({
43
52
  addIndicator = false,
44
53
  markdownEnabler = "\n\n",
45
54
  markdownProps,
46
- ...otherProps
55
+ usePlaceholder,
56
+ hoverOpenDelay = 500,
57
+ ...otherTooltipProps
47
58
  }: TooltipProps) => {
59
+ const placeholderRef = React.useRef(null);
60
+ const eventMemory = React.useRef<null | "afterhover" | "afterfocus">(null);
61
+ const searchId = React.useRef<null | string>(null);
62
+ const swapDelayTime = 100;
63
+ const [placeholder, setPlaceholder] = React.useState<boolean>(
64
+ !otherTooltipProps.disabled &&
65
+ !otherTooltipProps.defaultIsOpen &&
66
+ !otherTooltipProps.isOpen &&
67
+ otherTooltipProps.renderTarget === undefined &&
68
+ hoverOpenDelay > swapDelayTime &&
69
+ (usePlaceholder === true || (typeof content === "string" && usePlaceholder !== false))
70
+ );
71
+
72
+ const targetClassName =
73
+ `${eccgui}-tooltip__wrapper` +
74
+ (className ? " " + className : "") +
75
+ (addIndicator === true ? " " + BlueprintClasses.TOOLTIP_INDICATOR : "");
76
+
77
+ React.useEffect(() => {
78
+ if (placeholderRef.current !== null) {
79
+ const swap = (ev: MouseEvent | globalThis.FocusEvent) => {
80
+ const swapDelay = setTimeout(() => {
81
+ // we delay the swap to prevent unwanted effects
82
+ // (e.g. forced mouseover after the swap but the cursor is already somewhere else)
83
+ eventMemory.current = ev.type === "focusin" ? "afterfocus" : "afterhover";
84
+ searchId.current = Date.now().toString(16) + Math.random().toString(16).slice(2);
85
+ setPlaceholder(false);
86
+ }, swapDelayTime);
87
+ if (placeholderRef.current !== null) {
88
+ const eventType = ev.type === "focusin" ? "focusout" : "mouseleave";
89
+ (placeholderRef.current as HTMLElement).addEventListener(
90
+ eventType,
91
+ () => {
92
+ if (eventType === "focusout" && eventMemory.current === "afterfocus" ||
93
+ eventType === "mouseleave" && eventMemory.current === "afterhover") {
94
+ eventMemory.current = null
95
+ }
96
+ clearTimeout(swapDelay)
97
+ }
98
+ );
99
+ }
100
+ };
101
+ (placeholderRef.current as HTMLElement).addEventListener("mouseenter", swap);
102
+ (placeholderRef.current as HTMLElement).addEventListener("focusin", swap);
103
+ return () => {
104
+ if (placeholderRef.current) {
105
+ (placeholderRef.current as HTMLElement).removeEventListener("mouseenter", swap);
106
+ (placeholderRef.current as HTMLElement).removeEventListener("focusin", swap);
107
+ }
108
+ };
109
+ }
110
+ return () => {};
111
+ }, [!!placeholderRef.current]);
112
+
113
+ const refocus = React.useCallback((node) => {
114
+ if (eventMemory.current && node) {
115
+ // we do not have a `targetRef` here, so we need to workaround it
116
+ // const target = node.targetRef.current.children[0];
117
+ const target = document.body.querySelector(
118
+ `[data-postplaceholder=id${eventMemory.current}${searchId.current}]`
119
+ )?.children[0];
120
+ if (target) {
121
+ switch (eventMemory.current) {
122
+ case "afterfocus":
123
+ (target as HTMLElement).focus();
124
+ break;
125
+ case "afterhover":
126
+ (target as HTMLElement).dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ }, []);
132
+
133
+ const displayPlaceholder = () => {
134
+ const PlaceholderElement = otherTooltipProps?.targetTagName ?? (otherTooltipProps?.fill ? "div" : "span");
135
+ const childTarget = BlueprintUtils.ensureElement(React.Children.toArray(children)[0]);
136
+ if (!childTarget) {
137
+ return null;
138
+ }
139
+ return React.createElement(
140
+ PlaceholderElement,
141
+ {
142
+ ...otherTooltipProps?.targetProps,
143
+ className: `${BlueprintClasses.POPOVER_TARGET} ${targetClassName} ${eccgui}-tooltip__wrapper--placeholder`,
144
+ ref: placeholderRef,
145
+ },
146
+ React.cloneElement(childTarget, {
147
+ ...childTarget.props,
148
+ className:
149
+ childTarget.props.className ?? "" + (otherTooltipProps.fill ? ` ${BlueprintClasses.FILL}` : ""),
150
+ tabIndex: 0,
151
+ })
152
+ );
153
+ };
154
+
48
155
  let tooltipContent = content;
49
156
 
50
157
  if (
@@ -55,23 +162,27 @@ export const Tooltip = ({
55
162
  tooltipContent = <Markdown {...markdownProps}>{content}</Markdown>;
56
163
  }
57
164
 
58
- return (
165
+ return placeholder ? (
166
+ displayPlaceholder()
167
+ ) : (
59
168
  <BlueprintTooltip
60
169
  lazy={true}
61
- hoverOpenDelay={500}
62
- {...otherProps}
170
+ hoverOpenDelay={hoverOpenDelay - swapDelayTime}
171
+ {...otherTooltipProps}
63
172
  content={tooltipContent}
64
- className={
65
- `${eccgui}-tooltip__wrapper` +
66
- (className ? " " + className : "") +
67
- (addIndicator === true ? " " + BlueprintClasses.TOOLTIP_INDICATOR : "")
68
- }
69
- //targetClassName={`${eccgui}-tooltip__target` + (className ? " " + className + "__target" : "")}
173
+ className={targetClassName}
70
174
  popoverClassName={
71
175
  `${eccgui}-tooltip__content` +
72
176
  ` ${eccgui}-tooltip--${size}` +
73
177
  (className ? " " + className + "__content" : "")
74
178
  }
179
+ ref={refocus}
180
+ targetProps={
181
+ {
182
+ ...otherTooltipProps.targetProps,
183
+ "data-postplaceholder": `id${eventMemory.current}${searchId.current}`,
184
+ } as React.HTMLProps<HTMLElement>
185
+ }
75
186
  >
76
187
  {children}
77
188
  </BlueprintTooltip>