@eccenca/gui-elements 24.4.0 → 24.4.1-featurechatcomponentscmem6775.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 (64) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/cjs/common/index.js.map +1 -1
  3. package/dist/cjs/components/Chat/ChatArea.js +55 -0
  4. package/dist/cjs/components/Chat/ChatArea.js.map +1 -0
  5. package/dist/cjs/components/Chat/ChatContent.js +53 -0
  6. package/dist/cjs/components/Chat/ChatContent.js.map +1 -0
  7. package/dist/cjs/components/Chat/ChatField.js +45 -0
  8. package/dist/cjs/components/Chat/ChatField.js.map +1 -0
  9. package/dist/cjs/components/Chat/index.js +20 -0
  10. package/dist/cjs/components/Chat/index.js.map +1 -0
  11. package/dist/cjs/components/Icon/IconButton.js +1 -1
  12. package/dist/cjs/components/Icon/IconButton.js.map +1 -1
  13. package/dist/cjs/components/Icon/canonicalIconNames.js +3 -0
  14. package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
  15. package/dist/cjs/components/TextField/TextArea.js +2 -2
  16. package/dist/cjs/components/TextField/TextArea.js.map +1 -1
  17. package/dist/cjs/components/Tooltip/Tooltip.js +5 -1
  18. package/dist/cjs/components/Tooltip/Tooltip.js.map +1 -1
  19. package/dist/cjs/components/index.js +1 -0
  20. package/dist/cjs/components/index.js.map +1 -1
  21. package/dist/esm/common/index.js.map +1 -1
  22. package/dist/esm/components/Chat/ChatArea.js +59 -0
  23. package/dist/esm/components/Chat/ChatArea.js.map +1 -0
  24. package/dist/esm/components/Chat/ChatContent.js +57 -0
  25. package/dist/esm/components/Chat/ChatContent.js.map +1 -0
  26. package/dist/esm/components/Chat/ChatField.js +49 -0
  27. package/dist/esm/components/Chat/ChatField.js.map +1 -0
  28. package/dist/esm/components/Chat/index.js +4 -0
  29. package/dist/esm/components/Chat/index.js.map +1 -0
  30. package/dist/esm/components/Icon/IconButton.js +1 -1
  31. package/dist/esm/components/Icon/IconButton.js.map +1 -1
  32. package/dist/esm/components/Icon/canonicalIconNames.js +3 -0
  33. package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
  34. package/dist/esm/components/TextField/TextArea.js +2 -2
  35. package/dist/esm/components/TextField/TextArea.js.map +1 -1
  36. package/dist/esm/components/Tooltip/Tooltip.js +8 -4
  37. package/dist/esm/components/Tooltip/Tooltip.js.map +1 -1
  38. package/dist/esm/components/index.js +1 -0
  39. package/dist/esm/components/index.js.map +1 -1
  40. package/dist/types/common/index.d.ts +1 -0
  41. package/dist/types/components/Chat/ChatArea.d.ts +34 -0
  42. package/dist/types/components/Chat/ChatContent.d.ts +43 -0
  43. package/dist/types/components/Chat/ChatField.d.ts +19 -0
  44. package/dist/types/components/Chat/index.d.ts +3 -0
  45. package/dist/types/components/Icon/canonicalIconNames.d.ts +3 -0
  46. package/dist/types/components/index.d.ts +1 -0
  47. package/package.json +1 -1
  48. package/src/common/index.ts +1 -0
  49. package/src/components/Chat/ChatArea.tsx +114 -0
  50. package/src/components/Chat/ChatContent.tsx +116 -0
  51. package/src/components/Chat/ChatField.tsx +52 -0
  52. package/src/components/Chat/_chat.scss +81 -0
  53. package/src/components/Chat/index.ts +3 -0
  54. package/src/components/Chat/stories/ChatArea.stories.tsx +33 -0
  55. package/src/components/Chat/stories/ChatContent.stories.tsx +58 -0
  56. package/src/components/Chat/stories/ChatField.stories.tsx +18 -0
  57. package/src/components/Icon/IconButton.tsx +1 -1
  58. package/src/components/Icon/canonicalIconNames.tsx +3 -0
  59. package/src/components/TextField/TextArea.tsx +2 -2
  60. package/src/components/Tooltip/Tooltip.stories.tsx +2 -0
  61. package/src/components/Tooltip/Tooltip.tsx +9 -1
  62. package/src/components/index.scss +1 -0
  63. package/src/components/index.ts +1 -0
  64. package/src/extensions/react-flow/_react-flow_v12.scss +8 -1
@@ -0,0 +1,116 @@
1
+ import React from "react";
2
+
3
+ import { TestableComponent } from "../../components/interfaces";
4
+ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
5
+
6
+ import { Markdown, MarkdownProps } from "./../../cmem/markdown/Markdown";
7
+ import { DepictionProps } from "./../Depiction/Depiction";
8
+ import { FlexibleLayoutContainer, FlexibleLayoutItem } from "./../FlexibleLayout";
9
+ import { Spacing } from "./../Separation/Spacing";
10
+ import { HtmlContentBlock, OverflowTextProps } from "./../Typography";
11
+
12
+ export interface ChatContentProps extends React.HTMLAttributes<HTMLDivElement>, TestableComponent {
13
+ /**
14
+ * Should be a line of text, e.g. username, timestamp, ...
15
+ */
16
+ statusLine?: React.ReactElement<OverflowTextProps>;
17
+ /**
18
+ * How the chat content box is displayed.
19
+ */
20
+ displayType?: "free" | "simple" | "bubble";
21
+ /**
22
+ * A depiction used as avatar next to the content box.
23
+ */
24
+ avatar?: React.ReactElement<DepictionProps>;
25
+ /**
26
+ * If indented then the content box has some white space on the opposite side to the alignment
27
+ */
28
+ indentationSize?: "small" | "medium" | "large";
29
+ /**
30
+ * How the content box and avatar is aligned.
31
+ * If `left` is set then the avatar is on the left side, and the indentation on the right side.
32
+ */
33
+ alignment?: "left" | "right";
34
+ /**
35
+ * If set then the chat bubble only grows to a height of 50% of the viewport.
36
+ * In case you need to set other maximum heights then use the `style` property directly.
37
+ */
38
+ limitHeight?: boolean;
39
+ /**
40
+ * If given then the content is automatically parsed and displayed by our `<Markdown />` component.
41
+ * `children` need to a `string` then, otherwise it cannot be parsed.
42
+ */
43
+ markdownProps?: Omit<MarkdownProps, "children">;
44
+ }
45
+
46
+ /**
47
+ * Component to display singe chat contents, including avatar and status line.
48
+ */
49
+ export const ChatContent = ({
50
+ className,
51
+ children,
52
+ statusLine,
53
+ avatar,
54
+ displayType = "bubble",
55
+ indentationSize,
56
+ alignment = "left",
57
+ limitHeight,
58
+ markdownProps,
59
+ ...otherDivProps
60
+ }: ChatContentProps) => {
61
+ const content = (
62
+ <div
63
+ className={
64
+ `${eccgui}-chat__content` +
65
+ ` ${eccgui}-chat__content--display-${displayType}` +
66
+ ` ${eccgui}-chat__content--align-${alignment}` +
67
+ (limitHeight ? ` ${eccgui}-chat__content--limitheight` : "") +
68
+ (className ? ` ${className}` : "")
69
+ }
70
+ {...otherDivProps}
71
+ >
72
+ {statusLine && (
73
+ <HtmlContentBlock small>
74
+ {statusLine}
75
+ <Spacing size="tiny" />
76
+ </HtmlContentBlock>
77
+ )}
78
+ {markdownProps && typeof children === "string" ? (
79
+ <Markdown {...markdownProps}>{children}</Markdown>
80
+ ) : (
81
+ children
82
+ )}
83
+ </div>
84
+ );
85
+
86
+ const indentationSizes = {
87
+ small: "8%",
88
+ medium: "21%",
89
+ large: "34%",
90
+ };
91
+
92
+ return (
93
+ <div
94
+ style={{
95
+ marginLeft: alignment === "right" && indentationSize ? indentationSizes[indentationSize] : undefined,
96
+ marginRight: alignment === "left" && indentationSize ? indentationSizes[indentationSize] : undefined,
97
+ }}
98
+ >
99
+ <FlexibleLayoutContainer noEqualItemSpace gapSize="tiny">
100
+ {avatar && (
101
+ <FlexibleLayoutItem
102
+ className={`${eccgui}-chat__content-avatar`}
103
+ growFactor={0}
104
+ shrinkFactor={0}
105
+ style={alignment === "right" ? { order: 1 } : undefined}
106
+ >
107
+ {React.cloneElement(avatar, { size: "small", ratio: "1:1", rounded: true, resizing: "cover" })}
108
+ </FlexibleLayoutItem>
109
+ )}
110
+ <FlexibleLayoutItem className={`${eccgui}-chat__content-wrapper`}>{content}</FlexibleLayoutItem>
111
+ </FlexibleLayoutContainer>
112
+ </div>
113
+ );
114
+ };
115
+
116
+ export default ChatContent;
@@ -0,0 +1,52 @@
1
+ import React from "react";
2
+
3
+ import { TestableComponent } from "../../components/interfaces";
4
+ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
5
+ import { IconButton } from "../Icon/IconButton";
6
+ import { TextArea, TextAreaProps } from "../TextField/TextArea";
7
+
8
+ export interface ChatFieldProps extends Pick<TextAreaProps, "className">, TestableComponent {
9
+ /**
10
+ * Default input to start with.
11
+ */
12
+ children?: string;
13
+ /**
14
+ * Callback handler to process the input of the field when `Enter` is pressed or the submit button is clicked.
15
+ */
16
+ onSubmit: (value: string) => void;
17
+ }
18
+
19
+ /**
20
+ * Component to input chat text.
21
+ * Based on `TextArea` component.
22
+ */
23
+ export const ChatField = ({ className, onSubmit, ...otherTextAreaProps }: ChatFieldProps) => {
24
+ const chatvalue = React.useRef<string>(otherTextAreaProps.children ?? "");
25
+
26
+ const onContentChange = (value: string) => {
27
+ chatvalue.current = value;
28
+ };
29
+
30
+ const onEnter = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
31
+ if (e.keyCode === 13 && e.shiftKey === false) {
32
+ e.preventDefault();
33
+ onSubmit(chatvalue.current);
34
+ }
35
+ };
36
+
37
+ return (
38
+ <TextArea
39
+ fill
40
+ autoResize
41
+ className={`${eccgui}-chat__inputfield` + (className ? ` ${className}` : "")}
42
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
43
+ onContentChange(e.target.value);
44
+ }}
45
+ onKeyDown={onEnter}
46
+ rightElement={<IconButton name={"operation-send"} onClick={() => onSubmit(chatvalue.current)} />}
47
+ {...otherTextAreaProps}
48
+ />
49
+ );
50
+ };
51
+
52
+ export default ChatField;
@@ -0,0 +1,81 @@
1
+ .#{$eccgui}-chat__content {
2
+ @extend .bp5-elevation-1;
3
+
4
+ position: relative;
5
+ z-index: 0;
6
+ min-height: $button-height;
7
+ padding: $eccgui-size-inline-whitespace;
8
+ overflow: auto;
9
+ background-color: var(--#{$eccgui}-chat__content-background);
10
+ border-radius: 3 * $pt-border-radius;
11
+ }
12
+
13
+ .#{$eccgui}-chat__content--display-free {
14
+ --#{$eccgui}-chat__content-background: transparent;
15
+
16
+ padding: 1px 0;
17
+ box-shadow: none;
18
+ }
19
+
20
+ .#{$eccgui}-chat__content--display-bubble {
21
+ margin-left: 0.75 * $eccgui-size-block-whitespace;
22
+
23
+ &.#{$eccgui}-chat__content--align-right {
24
+ margin-right: 0.75 * $eccgui-size-block-whitespace;
25
+ margin-left: none;
26
+ }
27
+ }
28
+
29
+ .#{$eccgui}-chat__content-wrapper {
30
+ --#{$eccgui}-chat__content-background: #{$light-gray5};
31
+ &:has(.#{$eccgui}-chat__content--display-bubble) {
32
+ position: relative;
33
+
34
+ &::before {
35
+ @extend .bp5-elevation-1;
36
+
37
+ position: absolute;
38
+ top: calc(#{mini-units(3)} - #{0.5 * $eccgui-size-block-whitespace});
39
+ left: 0.25 * $eccgui-size-block-whitespace;
40
+ z-index: 1;
41
+ width: $eccgui-size-block-whitespace;
42
+ height: $eccgui-size-block-whitespace;
43
+ content: " ";
44
+ background-color: var(--#{$eccgui}-chat__content-background);
45
+ clip-path: polygon(-5px calc(100% + 5px), -5px -5px, calc(100% + 5px) calc(100% + 5px));
46
+ transform: rotate(45deg);
47
+ }
48
+ }
49
+ &:has(.#{$eccgui}-chat__content--display-bubble.#{$eccgui}-chat__content--align-right) {
50
+ &::before {
51
+ right: 0.25 * $eccgui-size-block-whitespace;
52
+ left: auto;
53
+ transform: rotate(225deg);
54
+ }
55
+ }
56
+ }
57
+
58
+ .#{$eccgui}-chat__content--limitheight {
59
+ max-height: 50vh;
60
+ }
61
+
62
+ .#{$eccgui}-chat__inputfield {
63
+ min-height: mini-units(9);
64
+ max-height: 39vh;
65
+ resize: none !important;
66
+ }
67
+
68
+ .#{$eccgui}-chat__area-contentwidth {
69
+ width: 100%;
70
+ margin: 0 auto;
71
+
72
+ .#{$eccgui}-chat__area--small & {
73
+ max-width: 40rem;
74
+ }
75
+ .#{$eccgui}-chat__area--medium & {
76
+ max-width: 60rem;
77
+ }
78
+ .#{$eccgui}-chat__area--large & {
79
+ max-width: 80rem;
80
+ }
81
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./ChatArea";
2
+ export * from "./ChatContent";
3
+ export * from "./ChatField";
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { Meta, StoryFn } from "@storybook/react";
3
+
4
+ import { ChatArea, ChatContent, ChatField } from "../../../index";
5
+
6
+ import { Default as ShortChatBubble, LongChatBubble } from "./ChatContent.stories";
7
+ import { Default as ChatFieldExample } from "./ChatField.stories";
8
+
9
+ export default {
10
+ title: "Components/Chat/ChatArea",
11
+ component: ChatArea,
12
+ argTypes: {},
13
+ } as Meta<typeof ChatArea>;
14
+
15
+ let forceupdate = 0;
16
+ const TemplateFull: StoryFn<typeof ChatArea> = (args) => (
17
+ <div style={args.useAbsoluteSpace ? { position: "relative", height: "75vh" } : undefined}>
18
+ <ChatArea {...args} key={forceupdate++} />
19
+ </div>
20
+ );
21
+
22
+ export const Default = TemplateFull.bind({});
23
+ Default.args = {
24
+ chatField: <ChatField {...ChatFieldExample.args} />,
25
+ children: [
26
+ <ChatContent {...ShortChatBubble.args} alignment="right" indentationSize="medium" />,
27
+ <ChatContent {...ShortChatBubble.args} avatar={undefined} displayType="free" />,
28
+ <ChatContent {...ShortChatBubble.args} alignment="right" indentationSize="medium" />,
29
+ <ChatContent {...LongChatBubble.args} />,
30
+ <ChatContent {...ShortChatBubble.args} alignment="right" indentationSize="medium" />,
31
+ <ChatContent {...ShortChatBubble.args} />,
32
+ ],
33
+ };
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import { LoremIpsum } from "react-lorem-ipsum";
3
+ import { Meta, StoryFn } from "@storybook/react";
4
+
5
+ import { ChatContent, Depiction, HtmlContentBlock, Icon, OverflowText } from "../../../index";
6
+
7
+ import canonicalIcons from "./../../Icon/canonicalIconNames";
8
+
9
+ const allIcons = new Map([
10
+ ...Object.keys(canonicalIcons).map((keyId) => {
11
+ return [`Icon: ${keyId}`, <Depiction image={<Icon name={keyId} />} />];
12
+ }),
13
+ ]);
14
+
15
+ const exampleImages = {
16
+ None: undefined,
17
+ ...Object.fromEntries(allIcons),
18
+ };
19
+
20
+ export default {
21
+ title: "Components/Chat/ChatContent",
22
+ component: ChatContent,
23
+ argTypes: {
24
+ avatar: {
25
+ control: "select",
26
+ options: Object.keys(exampleImages),
27
+ mapping: exampleImages,
28
+ },
29
+ },
30
+ } as Meta<typeof ChatContent>;
31
+
32
+ const TemplateFull: StoryFn<typeof ChatContent> = (args) => <ChatContent {...args} />;
33
+
34
+ export const Default = TemplateFull.bind({});
35
+ Default.args = {
36
+ children: (
37
+ <HtmlContentBlock>
38
+ <LoremIpsum p={1} avgSentencesPerParagraph={5} random={false} />
39
+ </HtmlContentBlock>
40
+ ),
41
+ avatar: <Depiction image={<Icon name={"application-useraccount"} />} />,
42
+ statusLine: (
43
+ <OverflowText>
44
+ <strong>Username</strong> 25 minutes ago
45
+ </OverflowText>
46
+ ),
47
+ };
48
+
49
+ export const LongChatBubble = TemplateFull.bind({});
50
+ LongChatBubble.args = {
51
+ ...Default.args,
52
+ children: (
53
+ <HtmlContentBlock>
54
+ <LoremIpsum p={10} avgSentencesPerParagraph={10} random={false} />
55
+ </HtmlContentBlock>
56
+ ),
57
+ limitHeight: true,
58
+ };
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { Meta, StoryFn } from "@storybook/react";
3
+
4
+ import { ChatField } from "../../../index";
5
+
6
+ export default {
7
+ title: "Components/Chat/ChatField",
8
+ component: ChatField,
9
+ argTypes: {},
10
+ } as Meta<typeof ChatField>;
11
+
12
+ let forceupdate = 0;
13
+ const TemplateFull: StoryFn<typeof ChatField> = (args) => <ChatField {...args} key={forceupdate++} />;
14
+
15
+ export const Default = TemplateFull.bind({});
16
+ Default.args = {
17
+ onSubmit: (value) => alert(value),
18
+ };
@@ -50,7 +50,7 @@ export const IconButton = ({
50
50
  const defaultIconTooltipProps = {
51
51
  hoverOpenDelay: 1000,
52
52
  openOnTargetFocus: restProps.disabled || (restProps.tabIndex ?? 0) < 0 ? false : undefined,
53
- swapPlaceholderDelay: 1,
53
+ swapPlaceholderDelay: 10,
54
54
  };
55
55
  const iconProps = {
56
56
  small: restProps.small,
@@ -8,6 +8,7 @@ const canonicalIcons = {
8
8
  "application-homepage": icons.Workspace,
9
9
  "application-legacygui": icons.ResetAlt,
10
10
  "application-mapping": icons.ModelBuilder,
11
+ "application-colors": icons.ColorPalette,
11
12
  "application-queries": icons.DataView,
12
13
  "application-useraccount": icons.UserAvatar,
13
14
  "application-vocabularies": icons.Catalog,
@@ -50,6 +51,7 @@ const canonicalIcons = {
50
51
  "artefact-workflow": icons.ModelBuilder,
51
52
 
52
53
  "data-boolean": icons.Boolean,
54
+ "data-color": icons.ColorPalette,
53
55
  "data-sourcepath": icons.Data_2,
54
56
  "data-targetpath": icons.Data_1,
55
57
  "data-string": icons.StringText,
@@ -145,6 +147,7 @@ const canonicalIcons = {
145
147
  "operation-merge": icons.DirectionMerge,
146
148
  "operation-redo": icons.Redo,
147
149
  "operation-search": icons.Search,
150
+ "operation-send": icons.Send,
148
151
  "operation-sharelink": icons.CopyLink,
149
152
  "operation-transform": icons.Calculation,
150
153
  "operation-translate": icons.Translate,
@@ -91,7 +91,7 @@ export const TextArea = ({
91
91
  leftIconElementRect.width ?? 0
92
92
  }px)`
93
93
  );
94
- leftIconElement.addEventListener("click", (_event: MouseEvent) => {
94
+ leftIconElement.addEventListener("click", () => {
95
95
  textAreaElement.focus();
96
96
  });
97
97
  }
@@ -157,7 +157,7 @@ export const TextArea = ({
157
157
  rows={
158
158
  rows && !otherBlueprintTextAreaProps.autoResize && !otherBlueprintTextAreaProps.growVertically
159
159
  ? rows
160
- : 1
160
+ : 2
161
161
  }
162
162
  {...otherBlueprintTextAreaProps}
163
163
  dir={"auto"}
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { loremIpsum } from "react-lorem-ipsum";
3
3
  import { OverlaysProvider } from "@blueprintjs/core";
4
4
  import { Meta, StoryFn } from "@storybook/react";
5
+ import { fn } from "@storybook/test";
5
6
 
6
7
  import { Tooltip } from "../../index";
7
8
 
@@ -34,6 +35,7 @@ Default.args = {
34
35
  children: "hover me",
35
36
  content: testContent,
36
37
  addIndicator: true,
38
+ onOpening: fn(),
37
39
  };
38
40
 
39
41
  export const MarkdownSupport = Template.bind({});
@@ -134,7 +134,15 @@ export const Tooltip = ({
134
134
  (target as HTMLElement).focus();
135
135
  break;
136
136
  case "afterhover":
137
- (target as HTMLElement).dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
137
+ // re-check if the cursor is still over the element after swapping the placeholder before triggering the event to bubble up
138
+ (target as HTMLElement).addEventListener(
139
+ "mouseover",
140
+ () => (target as HTMLElement).dispatchEvent(new MouseEvent("mouseover", { bubbles: true })),
141
+ {
142
+ capture: true,
143
+ once: true,
144
+ }
145
+ );
138
146
  break;
139
147
  }
140
148
  }
@@ -3,6 +3,7 @@
3
3
  @import "./Breadcrumb/breadcrumb";
4
4
  @import "./Button/button";
5
5
  @import "./Card/card";
6
+ @import "./Chat/chat";
6
7
  @import "./Checkbox/checkbox";
7
8
  @import "./Depiction/depiction";
8
9
  @import "./Dialog/dialog";
@@ -8,6 +8,7 @@ export * from "./Badge/Badge";
8
8
  export * from "./Breadcrumb";
9
9
  export * from "./Button/Button";
10
10
  export * from "./Card";
11
+ export * from "./Chat";
11
12
  export * from "./Checkbox/Checkbox";
12
13
  export * from "./ContextOverlay";
13
14
  export * from "./Depiction/Depiction";
@@ -1,5 +1,12 @@
1
- .react-flow .react-flow__edges svg {
1
+ // needed styles from "@xyflow/react/dist/style.css" to ensure proper functionality
2
+ svg.react-flow__connectionline {
3
+ position: absolute;
4
+ z-index: 1001;
2
5
  overflow: visible;
6
+ }
7
+
8
+ .react-flow .react-flow__edges svg {
3
9
  position: absolute;
10
+ overflow: visible;
4
11
  pointer-events: none;
5
12
  }