@eccenca/gui-elements 23.6.0-rc.0 → 23.6.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 (43) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/dist/cjs/components/Application/helper.js +30 -0
  3. package/dist/cjs/components/Application/helper.js.map +1 -0
  4. package/dist/cjs/components/Application/index.js +1 -0
  5. package/dist/cjs/components/Application/index.js.map +1 -1
  6. package/dist/cjs/components/Icon/canonicalIconNames.js +3 -0
  7. package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
  8. package/dist/cjs/components/MultiSelect/MultiSelect.js +19 -8
  9. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  10. package/dist/cjs/components/Select/Select.js +1 -1
  11. package/dist/cjs/components/Select/Select.js.map +1 -1
  12. package/dist/cjs/extensions/codemirror/CodeMirror.js +27 -6
  13. package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
  14. package/dist/esm/components/Application/helper.js +23 -0
  15. package/dist/esm/components/Application/helper.js.map +1 -0
  16. package/dist/esm/components/Application/index.js +1 -0
  17. package/dist/esm/components/Application/index.js.map +1 -1
  18. package/dist/esm/components/Icon/canonicalIconNames.js +3 -0
  19. package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
  20. package/dist/esm/components/MultiSelect/MultiSelect.js +27 -14
  21. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  22. package/dist/esm/components/Select/Select.js +1 -1
  23. package/dist/esm/components/Select/Select.js.map +1 -1
  24. package/dist/esm/extensions/codemirror/CodeMirror.js +27 -6
  25. package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
  26. package/dist/types/components/Application/helper.d.ts +1 -0
  27. package/dist/types/components/Application/index.d.ts +1 -0
  28. package/dist/types/components/Icon/canonicalIconNames.d.ts +1 -1
  29. package/dist/types/components/MultiSelect/MultiSelect.d.ts +13 -3
  30. package/dist/types/extensions/codemirror/CodeMirror.d.ts +20 -3
  31. package/package.json +2 -1
  32. package/src/components/Application/_header.scss +23 -0
  33. package/src/components/Application/helper.ts +21 -0
  34. package/src/components/Application/index.ts +1 -0
  35. package/src/components/Dialog/dialog.scss +2 -3
  36. package/src/components/Icon/canonicalIconNames.tsx +6 -0
  37. package/src/components/MultiSelect/MultiSelect.tsx +60 -12
  38. package/src/components/MultiSuggestField/MultiSuggestField.stories.tsx +60 -1
  39. package/src/components/MultiSuggestField/tests/MultiSuggestField.test.tsx +97 -0
  40. package/src/components/Select/Select.tsx +2 -2
  41. package/src/components/Tag/tag.scss +28 -25
  42. package/src/extensions/codemirror/CodeMirror.tsx +44 -6
  43. package/src/extensions/codemirror/_codemirror.scss +21 -0
@@ -1,7 +1,8 @@
1
+ import React, { useCallback, useMemo, useState } from "react";
1
2
  import { loremIpsum } from "react-lorem-ipsum";
2
3
  import { Meta, StoryFn } from "@storybook/react";
3
4
 
4
- import { MultiSuggestField } from "./../../../index";
5
+ import { MultiSelectSelectionProps, MultiSuggestField } from "./../../../index";
5
6
 
6
7
  export default {
7
8
  title: "Forms/MultiSuggestField",
@@ -55,4 +56,62 @@ dropdownOnFocus.args = {
55
56
  prePopulateWithItems: false,
56
57
  itemId: (item) => item.testId,
57
58
  itemLabel: (item) => item.testLabel,
59
+ noResultText: "No result.",
58
60
  };
61
+
62
+ const selectedItems = items.slice(1, 3);
63
+
64
+ /**
65
+ * Set the default selected values from the client code.
66
+ */
67
+ export const predefinedValues = Template.bind({});
68
+ predefinedValues.args = {
69
+ items,
70
+ selectedItems,
71
+ prePopulateWithItems: false,
72
+ itemId: (item) => item.testId,
73
+ itemLabel: (item) => <span data-testid={`${item.testLabel.trim()}`}>{item.testLabel}</span>,
74
+ };
75
+
76
+ /**
77
+ * New item creation, add to a existing list
78
+ */
79
+ export const newItemCreation = Template.bind({});
80
+ newItemCreation.args = {
81
+ items,
82
+ createNewItemFromQuery: (query) => ({ testId: `${query}-id`, testLabel: query }),
83
+ prePopulateWithItems: false,
84
+ itemId: (item) => item.testId,
85
+ itemLabel: (item) => item.testLabel,
86
+ };
87
+
88
+ const CreationTemplate: StoryFn = () => {
89
+ const [selectedValues, setSelectedValues] = useState<string[]>([]);
90
+
91
+ const empty = useMemo<string[]>(() => [], []);
92
+
93
+ const identity = useCallback((item: string): string => item, []);
94
+
95
+ const handleOnSelect = useCallback((params: MultiSelectSelectionProps<string>) => {
96
+ const selected = params.selectedItems;
97
+
98
+ setSelectedValues(selected);
99
+ }, []);
100
+
101
+ return (
102
+ <MultiSuggestField<string>
103
+ items={empty}
104
+ selectedItems={selectedValues}
105
+ onSelection={handleOnSelect}
106
+ itemId={identity}
107
+ itemLabel={identity}
108
+ createNewItemFromQuery={identity}
109
+ prePopulateWithItems
110
+ />
111
+ );
112
+ };
113
+
114
+ /**
115
+ * Completely create all items from quieries
116
+ */
117
+ export const buildItemsFromQuery = CreationTemplate.bind({});
@@ -0,0 +1,97 @@
1
+ import React from "react";
2
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+
4
+ import "@testing-library/jest-dom";
5
+
6
+ import { MultiSuggestField } from "../MultiSuggestField";
7
+ import { Default, dropdownOnFocus, predefinedValues } from "../MultiSuggestField.stories";
8
+
9
+ describe("MultiSuggestField", () => {
10
+ it("should render default input", () => {
11
+ const { container } = render(<MultiSuggestField {...Default.args} />);
12
+ const [input] = container.getElementsByClassName("eccgui-multiselect");
13
+
14
+ expect(input).toBeInTheDocument();
15
+ });
16
+
17
+ it("should render default selected items", async () => {
18
+ const { getByTestId } = render(<MultiSuggestField {...predefinedValues.args} />);
19
+
20
+ const [firstSelected, secondSelected]: Array<string> = predefinedValues.args.selectedItems.map(
21
+ ({ testLabel }) => testLabel.trim()
22
+ );
23
+
24
+ await waitFor(() => {
25
+ expect(getByTestId(firstSelected)).toBeInTheDocument();
26
+ expect(getByTestId(secondSelected)).toBeInTheDocument();
27
+ });
28
+ });
29
+
30
+ it("should clear all selected items on clear button click", async () => {
31
+ const { queryByTestId, container } = render(<MultiSuggestField {...predefinedValues.args} />);
32
+
33
+ const [firstSelected, secondSelected]: Array<string> = predefinedValues.args.selectedItems.map(
34
+ ({ testLabel }) => testLabel.trim()
35
+ );
36
+
37
+ const clearButton = container.querySelector('[data-test-id="clear-all-items"');
38
+
39
+ expect(clearButton).toBeInTheDocument();
40
+
41
+ fireEvent.click(clearButton!);
42
+
43
+ await waitFor(() => {
44
+ expect(queryByTestId(firstSelected)).not.toBeInTheDocument();
45
+ expect(queryByTestId(secondSelected)).not.toBeInTheDocument();
46
+ });
47
+ });
48
+
49
+ it("should filter options and reset them if the query is empty", async () => {
50
+ const { container } = render(<MultiSuggestField {...dropdownOnFocus.args} />);
51
+
52
+ const [inputContainer] = container.getElementsByClassName("eccgui-multiselect");
53
+ const [input] = inputContainer.getElementsByTagName("input");
54
+
55
+ fireEvent.click(input);
56
+
57
+ await waitFor(() => {
58
+ const listbox = screen.getByRole("listbox");
59
+ expect(listbox).toBeInTheDocument();
60
+
61
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
62
+ expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
63
+ });
64
+
65
+ fireEvent.change(input, { target: { value: "ex" } });
66
+
67
+ await waitFor(() => {
68
+ const listbox = screen.getByRole("listbox");
69
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
70
+
71
+ expect(menuItems.length).toBe(1);
72
+
73
+ const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
74
+ expect(noResult).not.toBeInTheDocument();
75
+ });
76
+
77
+ fireEvent.change(input, { target: { value: "ttt" } });
78
+
79
+ await waitFor(() => {
80
+ const listbox = screen.getByRole("listbox");
81
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
82
+
83
+ expect(menuItems.length).toBe(1);
84
+
85
+ const noResult = screen.queryByText(dropdownOnFocus.args.noResultText);
86
+ expect(noResult).toBeInTheDocument();
87
+ });
88
+
89
+ fireEvent.change(input, { target: { value: "" } });
90
+
91
+ await waitFor(() => {
92
+ const listbox = screen.getByRole("listbox");
93
+ const menuItems = listbox.getElementsByClassName("eccgui-menu__item");
94
+ expect(menuItems.length).toBe(dropdownOnFocus.args.items.length);
95
+ });
96
+ });
97
+ });
@@ -122,8 +122,8 @@ export function Select<T>({
122
122
  </>
123
123
  }
124
124
  textClassName={text ? "" : BlueprintClasses.TEXT_MUTED}
125
- data-test-id={dataTestId + "_togger"}
126
- data-testid={dataTestid + "_togger"}
125
+ data-test-id={dataTestId ? dataTestId + "_toggler" : undefined}
126
+ data-testid={dataTestid ? dataTestid + "_toggler" : undefined}
127
127
  />
128
128
  )}
129
129
  </BlueprintSelect>
@@ -24,6 +24,7 @@ $tag-padding-large: ($tag-height-large - $tag-line-height-large); // !default;
24
24
  // $tag-icon-spacing-large: ($tag-height-large - $pt-icon-size-standard) * 0.5 !default;
25
25
  $tag-round-adjustment: 0 !default;
26
26
 
27
+ @use "sass:color";
27
28
  @import "~@blueprintjs/core/src/components/tag/tag";
28
29
 
29
30
  .#{$eccgui}-tag__item {
@@ -80,6 +81,7 @@ $tag-round-adjustment: 0 !default;
80
81
  }
81
82
 
82
83
  svg {
84
+ max-height: 1em;
83
85
  fill: currentcolor;
84
86
  stroke: initial;
85
87
  }
@@ -107,9 +109,14 @@ $tag-round-adjustment: 0 !default;
107
109
  }
108
110
 
109
111
  .#{$eccgui}-tag__list-item {
110
- display: inline-block;
112
+ display: inline-flex;
111
113
  max-width: 100%;
112
114
  margin-bottom: $eccgui-size-margin-tag;
115
+
116
+ .#{$eccgui}-overviewitem__line &,
117
+ .#{$eccgui}-typography__overflowtext & {
118
+ margin-bottom: 0;
119
+ }
113
120
  }
114
121
 
115
122
  .#{$eccgui}-tag__list-label,
@@ -153,30 +160,6 @@ $tag-round-adjustment: 0 !default;
153
160
  border-color: rgba($tag-default-color, 0.5);
154
161
  }
155
162
 
156
- &.#{$ns}-minimal,
157
- &.#{$ns}-minimal.#{$ns}-interactive {
158
- &.#{$eccgui}-tag--strongeremphasis {
159
- background-color: rgba(mix(rgba($tag-default-color, 0.3), #fff), 1);
160
- border-color: rgba($tag-default-color, 0.3);
161
- }
162
- &.#{$eccgui}-tag--strongemphasis {
163
- background-color: rgba(mix(rgba($tag-default-color, 0.225), #fff), 0.875);
164
- border-color: rgba($tag-default-color, 0.225);
165
- }
166
- &.#{$eccgui}-tag--normalemphasis {
167
- background-color: rgba(mix(rgba($tag-default-color, 0.15), #fff), 0.75);
168
- border-color: rgba($tag-default-color, 0.15);
169
- }
170
- &.#{$eccgui}-tag--weakemphasis {
171
- background-color: rgba(mix(rgba($tag-default-color, 0.075), #fff), 0.625);
172
- border-color: rgba($tag-default-color, 0.075);
173
- }
174
- &.#{$eccgui}-tag--weakeremphasis {
175
- background-color: rgba(mix(rgba($tag-default-color, 0), #fff), 0.5);
176
- border-color: rgba($tag-default-color, 0.15);
177
- }
178
- }
179
-
180
163
  &.#{$eccgui}-intent--primary {
181
164
  color: $eccgui-color-primary-contrast;
182
165
  background-color: $eccgui-color-primary;
@@ -210,6 +193,26 @@ $tag-round-adjustment: 0 !default;
210
193
 
211
194
  &.#{$ns}-minimal,
212
195
  &.#{$ns}-minimal.#{$ns}-interactive {
196
+ &.#{$eccgui}-tag--strongeremphasis {
197
+ background-color: rgba(color.mix(rgba($tag-default-color, 0.3), #fff), 1);
198
+ border-color: rgba($tag-default-color, 0.3);
199
+ }
200
+ &.#{$eccgui}-tag--strongemphasis {
201
+ background-color: rgba(color.mix(rgba($tag-default-color, 0.225), #fff), 0.875);
202
+ border-color: rgba($tag-default-color, 0.225);
203
+ }
204
+ &.#{$eccgui}-tag--normalemphasis {
205
+ background-color: rgba(color.mix(rgba($tag-default-color, 0.15), #fff), 0.75);
206
+ border-color: rgba($tag-default-color, 0.15);
207
+ }
208
+ &.#{$eccgui}-tag--weakemphasis {
209
+ background-color: rgba(color.mix(rgba($tag-default-color, 0.075), #fff), 0.625);
210
+ border-color: rgba($tag-default-color, 0.075);
211
+ }
212
+ &.#{$eccgui}-tag--weakeremphasis {
213
+ background-color: rgba(color.mix(rgba($tag-default-color, 0), #fff), 0.5);
214
+ border-color: rgba($tag-default-color, 0.15);
215
+ }
213
216
  &.#{$eccgui}-intent--primary {
214
217
  color: $eccgui-color-primary;
215
218
  background-color: $eccgui-color-primary-contrast;
@@ -10,6 +10,14 @@ import "codemirror/mode/xml/xml.js";
10
10
  import "codemirror/mode/jinja2/jinja2.js";
11
11
  import "codemirror/mode/yaml/yaml.js";
12
12
  import "codemirror/mode/javascript/javascript.js";
13
+ import "codemirror/mode/ntriples/ntriples.js";
14
+ import "codemirror/mode/mathematica/mathematica.js";
15
+ import "codemirror-formatting";
16
+ //folding imports
17
+ import "codemirror/addon/fold/foldcode";
18
+ import "codemirror/addon/fold/foldgutter";
19
+ import "codemirror/addon/fold/brace-fold";
20
+ import "codemirror/addon/fold/xml-fold.js";
13
21
 
14
22
  import { CLASSPREFIX as eccgui } from "../../configuration/constants";
15
23
 
@@ -23,12 +31,16 @@ export const supportedCodeEditorModes = [
23
31
  "jinja2",
24
32
  "yaml",
25
33
  "json",
34
+ "ntriples",
35
+ "mathematica",
26
36
  "undefined",
27
37
  ] as const;
28
38
  type SupportedModesTuple = typeof supportedCodeEditorModes;
29
39
  export type SupportedCodeEditorModes = SupportedModesTuple[number];
30
40
 
31
41
  export interface CodeEditorProps {
42
+ // Is called with the editor instance that allows access via the CodeMirror API
43
+ setEditorInstance?: (editor: CodeMirror.Editor) => any;
32
44
  /**
33
45
  * `name` attribute of connected textarea element.
34
46
  */
@@ -42,7 +54,7 @@ export interface CodeEditorProps {
42
54
  * Handler method to receive onChange events.
43
55
  * As input the new value is given.
44
56
  */
45
- onChange: (v: any) => void;
57
+ onChange?: (v: any) => void;
46
58
  /**
47
59
  * Syntax mode of the code editor.
48
60
  */
@@ -82,6 +94,15 @@ export interface CodeEditorProps {
82
94
  * For some modes an indent style with `space` can be forced, even if `tabIntentStyle="tab"` is set.
83
95
  */
84
96
  tabForceSpaceForModes?: SupportedCodeEditorModes[];
97
+
98
+ /**
99
+ * handler for scroll event
100
+ */
101
+ onScroll?: (editorInstance: CodeMirror.Editor) => void;
102
+ /**
103
+ * optional property to fold code for the supported modes e.g: xml, json etc.
104
+ */
105
+ supportCodeFolding?: boolean;
85
106
  }
86
107
 
87
108
  /**
@@ -97,6 +118,9 @@ export const CodeEditor = ({
97
118
  readOnly = false,
98
119
  height,
99
120
  wrapLines = false,
121
+ onScroll,
122
+ setEditorInstance,
123
+ supportCodeFolding = false,
100
124
  outerDivAttributes,
101
125
  tabIntentSize = 2,
102
126
  tabIntentStyle = "tab",
@@ -119,23 +143,37 @@ export const CodeEditor = ({
119
143
  cm.execCommand(cm.getOption("indentWithTabs") ? "insertTab" : "insertSoftTab");
120
144
  },
121
145
  },
146
+ foldGutter: supportCodeFolding,
147
+ gutters: supportCodeFolding ? ["CodeMirror-linenumbers", "CodeMirror-foldgutter"] : [],
122
148
  });
123
149
 
124
- editorInstance.on("change", (api) => {
125
- onChange(api.getValue());
126
- });
150
+ setEditorInstance && setEditorInstance(editorInstance);
151
+
152
+ if (onScroll) {
153
+ editorInstance.on("scroll", (instance) => {
154
+ onScroll(instance);
155
+ });
156
+ }
157
+
158
+ if (onChange) {
159
+ editorInstance.on("change", (api) => {
160
+ onChange(api.getValue());
161
+ });
162
+ }
127
163
 
128
164
  if (height) {
129
165
  editorInstance.setSize(null, height);
130
166
  }
131
167
 
168
+ editorInstance.setValue(defaultValue);
169
+
132
170
  return function cleanup() {
133
171
  editorInstance.toTextArea();
134
172
  };
135
173
  }, [onChange, mode, preventLineNumbers]);
136
174
 
137
175
  return (
138
- <div {...outerDivAttributes} className={`${eccgui}-codeeditor`}>
176
+ <div {...outerDivAttributes} className={`${eccgui}-codeeditor ${eccgui}-codeeditor--mode-${mode}`}>
139
177
  <textarea
140
178
  ref={domRef}
141
179
  /**
@@ -159,7 +197,7 @@ const convertMode = (mode: SupportedCodeEditorModes | undefined): string | ModeS
159
197
  case "json":
160
198
  return {
161
199
  name: "javascript",
162
- json: true,
200
+ jsonld: true,
163
201
  };
164
202
  default:
165
203
  return mode;
@@ -1,5 +1,6 @@
1
1
  @import "~codemirror/lib/codemirror";
2
2
  @import "~codemirror/theme/xq-light";
3
+ @import "~codemirror/addon/fold/foldgutter";
3
4
 
4
5
  // adjustments
5
6
  // stylelint-disable selector-class-pattern
@@ -90,3 +91,23 @@
90
91
  }
91
92
  }
92
93
  // stylelint-enable selector-class-pattern
94
+
95
+ //custom styles for json mode
96
+ .#{$eccgui}-codeeditor--mode-json {
97
+ .CodeMirror-line {
98
+ & > span {
99
+ span.cm-property {
100
+ color: $eccgui-color-warning-text;
101
+ }
102
+ span.cm-string {
103
+ color: $eccgui-color-success-text;
104
+ }
105
+ span.cm-number {
106
+ color: $eccgui-color-accent;
107
+ }
108
+ span.cm-atom {
109
+ color: $eccgui-color-primary;
110
+ }
111
+ }
112
+ }
113
+ }