@eccenca/gui-elements 25.1.0-rc.0 → 25.1.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 (131) hide show
  1. package/CHANGELOG.md +43 -5
  2. package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js +17 -13
  3. package/dist/cjs/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
  4. package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +1 -1
  5. package/dist/cjs/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
  6. package/dist/cjs/common/index.js +1 -0
  7. package/dist/cjs/common/index.js.map +1 -1
  8. package/dist/cjs/common/utils/CssCustomProperties.js.map +1 -1
  9. package/dist/cjs/common/utils/colorHash.js +26 -12
  10. package/dist/cjs/common/utils/colorHash.js.map +1 -1
  11. package/dist/cjs/components/ColorField/ColorField.js +114 -0
  12. package/dist/cjs/components/ColorField/ColorField.js.map +1 -0
  13. package/dist/cjs/components/ContextOverlay/ContextOverlay.js +6 -6
  14. package/dist/cjs/components/ContextOverlay/ContextOverlay.js.map +1 -1
  15. package/dist/cjs/components/DecoupledOverlay/DecoupledOverlay.js +47 -0
  16. package/dist/cjs/components/DecoupledOverlay/DecoupledOverlay.js.map +1 -0
  17. package/dist/cjs/components/Icon/canonicalIconNames.js +3 -0
  18. package/dist/cjs/components/Icon/canonicalIconNames.js.map +1 -1
  19. package/dist/cjs/components/Icon/transformIcon.js +14 -0
  20. package/dist/cjs/components/Icon/transformIcon.js.map +1 -0
  21. package/dist/cjs/components/MultiSelect/MultiSelect.js +2 -1
  22. package/dist/cjs/components/MultiSelect/MultiSelect.js.map +1 -1
  23. package/dist/cjs/components/RadioButton/RadioButton.js +5 -2
  24. package/dist/cjs/components/RadioButton/RadioButton.js.map +1 -1
  25. package/dist/cjs/components/TextField/useTextValidation.js +17 -8
  26. package/dist/cjs/components/TextField/useTextValidation.js.map +1 -1
  27. package/dist/cjs/components/VisualTour/VisualTour.js +24 -32
  28. package/dist/cjs/components/VisualTour/VisualTour.js.map +1 -1
  29. package/dist/cjs/components/index.js +2 -0
  30. package/dist/cjs/components/index.js.map +1 -1
  31. package/dist/cjs/extensions/codemirror/CodeMirror.js +56 -18
  32. package/dist/cjs/extensions/codemirror/CodeMirror.js.map +1 -1
  33. package/dist/cjs/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js +23 -0
  34. package/dist/cjs/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js.map +1 -0
  35. package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js +5 -2
  36. package/dist/cjs/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -1
  37. package/dist/cjs/extensions/react-flow/edges/EdgeLabel.js +1 -1
  38. package/dist/cjs/extensions/react-flow/edges/EdgeLabel.js.map +1 -1
  39. package/dist/esm/cmem/ActivityControl/ActivityControlWidget.js +19 -14
  40. package/dist/esm/cmem/ActivityControl/ActivityControlWidget.js.map +1 -1
  41. package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js +1 -1
  42. package/dist/esm/cmem/react-flow/StickyNoteModal/StickyNoteModal.js.map +1 -1
  43. package/dist/esm/common/index.js +2 -1
  44. package/dist/esm/common/index.js.map +1 -1
  45. package/dist/esm/common/utils/CssCustomProperties.js.map +1 -1
  46. package/dist/esm/common/utils/colorHash.js +26 -13
  47. package/dist/esm/common/utils/colorHash.js.map +1 -1
  48. package/dist/esm/components/ColorField/ColorField.js +140 -0
  49. package/dist/esm/components/ColorField/ColorField.js.map +1 -0
  50. package/dist/esm/components/ContextOverlay/ContextOverlay.js +3 -3
  51. package/dist/esm/components/ContextOverlay/ContextOverlay.js.map +1 -1
  52. package/dist/esm/components/DecoupledOverlay/DecoupledOverlay.js +41 -0
  53. package/dist/esm/components/DecoupledOverlay/DecoupledOverlay.js.map +1 -0
  54. package/dist/esm/components/Icon/canonicalIconNames.js +3 -0
  55. package/dist/esm/components/Icon/canonicalIconNames.js.map +1 -1
  56. package/dist/esm/components/Icon/transformIcon.js +21 -0
  57. package/dist/esm/components/Icon/transformIcon.js.map +1 -0
  58. package/dist/esm/components/MultiSelect/MultiSelect.js +3 -2
  59. package/dist/esm/components/MultiSelect/MultiSelect.js.map +1 -1
  60. package/dist/esm/components/RadioButton/RadioButton.js +6 -2
  61. package/dist/esm/components/RadioButton/RadioButton.js.map +1 -1
  62. package/dist/esm/components/TextField/useTextValidation.js +39 -8
  63. package/dist/esm/components/TextField/useTextValidation.js.map +1 -1
  64. package/dist/esm/components/VisualTour/VisualTour.js +25 -33
  65. package/dist/esm/components/VisualTour/VisualTour.js.map +1 -1
  66. package/dist/esm/components/index.js +2 -0
  67. package/dist/esm/components/index.js.map +1 -1
  68. package/dist/esm/extensions/codemirror/CodeMirror.js +58 -20
  69. package/dist/esm/extensions/codemirror/CodeMirror.js.map +1 -1
  70. package/dist/esm/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js +47 -0
  71. package/dist/esm/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.js.map +1 -0
  72. package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js +16 -2
  73. package/dist/esm/extensions/codemirror/toolbars/markdown.toolbar.js.map +1 -1
  74. package/dist/esm/extensions/react-flow/edges/EdgeLabel.js +1 -1
  75. package/dist/esm/extensions/react-flow/edges/EdgeLabel.js.map +1 -1
  76. package/dist/types/cmem/ActivityControl/ActivityControlWidget.d.ts +9 -0
  77. package/dist/types/common/index.d.ts +2 -1
  78. package/dist/types/common/utils/CssCustomProperties.d.ts +2 -2
  79. package/dist/types/common/utils/colorHash.d.ts +5 -4
  80. package/dist/types/components/ColorField/ColorField.d.ts +30 -0
  81. package/dist/types/components/ContextOverlay/ContextOverlay.d.ts +7 -1
  82. package/dist/types/components/DecoupledOverlay/DecoupledOverlay.d.ts +20 -0
  83. package/dist/types/components/Icon/canonicalIconNames.d.ts +2 -0
  84. package/dist/types/components/Icon/transformIcon.d.ts +2 -0
  85. package/dist/types/components/MultiSelect/MultiSelect.d.ts +1 -1
  86. package/dist/types/components/RadioButton/RadioButton.d.ts +8 -2
  87. package/dist/types/components/index.d.ts +2 -0
  88. package/dist/types/extensions/codemirror/CodeMirror.d.ts +12 -9
  89. package/dist/types/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.d.ts +24 -0
  90. package/dist/types/extensions/codemirror/toolbars/markdown.toolbar.d.ts +2 -0
  91. package/package.json +1 -1
  92. package/src/cmem/ActivityControl/ActivityControlWidget.tsx +68 -35
  93. package/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx +1 -1
  94. package/src/common/index.ts +2 -1
  95. package/src/common/utils/CssCustomProperties.ts +5 -3
  96. package/src/common/utils/colorHash.ts +38 -20
  97. package/src/components/Application/_colors.scss +15 -0
  98. package/src/components/ColorField/ColorField.stories.tsx +72 -0
  99. package/src/components/ColorField/ColorField.test.tsx +101 -0
  100. package/src/components/ColorField/ColorField.tsx +200 -0
  101. package/src/components/ColorField/_colorfield.scss +67 -0
  102. package/src/components/ContextOverlay/ContextOverlay.tsx +20 -1
  103. package/src/components/DecoupledOverlay/DecoupledOverlay.stories.tsx +30 -0
  104. package/src/components/DecoupledOverlay/DecoupledOverlay.tsx +97 -0
  105. package/src/components/DecoupledOverlay/_decoupledoverlay.scss +46 -0
  106. package/src/components/Icon/canonicalIconNames.tsx +3 -0
  107. package/src/components/Icon/transformIcon.tsx +17 -0
  108. package/src/components/Link/Link.stories.tsx +30 -0
  109. package/src/components/Link/link.scss +28 -2
  110. package/src/components/MultiSelect/MultiSelect.tsx +12 -3
  111. package/src/components/RadioButton/RadioButton.tsx +15 -3
  112. package/src/components/RadioButton/radiobutton.scss +13 -0
  113. package/src/components/TextField/stories/TextField.stories.tsx +23 -0
  114. package/src/components/TextField/tests/useTextValidation.test.tsx +83 -0
  115. package/src/components/TextField/useTextValidation.ts +17 -8
  116. package/src/components/VisualTour/VisualTour.tsx +30 -50
  117. package/src/components/VisualTour/visualTour.scss +0 -34
  118. package/src/components/index.scss +2 -0
  119. package/src/components/index.ts +2 -0
  120. package/src/configuration/_customproperties.scss +32 -0
  121. package/src/configuration/stories/customproperties.stories.tsx +118 -0
  122. package/src/extensions/codemirror/CodeMirror.stories.tsx +9 -4
  123. package/src/extensions/codemirror/CodeMirror.tsx +87 -31
  124. package/src/extensions/codemirror/tests/CodeEditor.test.tsx +138 -0
  125. package/src/extensions/codemirror/tests/EditorAppearanceConfigMenu.test.tsx +131 -0
  126. package/src/extensions/codemirror/toolbars/EditorAppearanceConfigMenu.tsx +59 -0
  127. package/src/extensions/codemirror/toolbars/markdown.toolbar.tsx +17 -3
  128. package/src/extensions/react-flow/_config.scss +3 -3
  129. package/src/extensions/react-flow/edges/EdgeLabel.tsx +5 -3
  130. package/src/extensions/react-flow/edges/_edges.scss +3 -2
  131. package/src/index.scss +1 -0
@@ -1,17 +1,20 @@
1
1
  import React from "react";
2
2
 
3
- import { ValidIconName } from "../../components/Icon/canonicalIconNames";
4
- import { IconProps } from "../../components/Icon/Icon";
5
- import { TestIconProps } from "../../components/Icon/TestIcon";
6
- import { TestableComponent } from "../../components/interfaces";
7
- import { ProgressBarProps } from "../../components/ProgressBar/ProgressBar";
8
- import { SpinnerProps } from "../../components/Spinner/Spinner";
9
- import { CLASSPREFIX as eccgui } from "../../configuration/constants";
3
+ import {ValidIconName} from "../../components/Icon/canonicalIconNames";
4
+ import {IconProps} from "../../components/Icon/Icon";
5
+ import {TestIconProps} from "../../components/Icon/TestIcon";
6
+ import {TestableComponent} from "../../components/interfaces";
7
+ import {ProgressBarProps} from "../../components/ProgressBar/ProgressBar";
8
+ import {SpinnerProps} from "../../components/Spinner/Spinner";
9
+ import {CLASSPREFIX as eccgui} from "../../configuration/constants";
10
10
  import {
11
11
  Card,
12
12
  ContextMenu,
13
+ DecoupledOverlay,
13
14
  IconButton,
14
15
  MenuItem,
16
+ Notification,
17
+ NotificationProps,
15
18
  OverflowText,
16
19
  OverviewItem,
17
20
  OverviewItemActions,
@@ -97,7 +100,7 @@ interface IActivityContextMenu extends TestableComponent {
97
100
  export interface ActivityControlWidgetAction extends TestableComponent {
98
101
  // The action that should be triggered
99
102
  action: () => void;
100
- // The tooltip that should be shown over the action icon
103
+ // The tooltip that should be shown over the action icon on hover
101
104
  tooltip?: string;
102
105
  // The icon of the action button
103
106
  icon: ValidIconName | React.ReactElement<TestIconProps>;
@@ -105,6 +108,16 @@ export interface ActivityControlWidgetAction extends TestableComponent {
105
108
  disabled?: boolean;
106
109
  // Warning state
107
110
  hasStateWarning?: boolean;
111
+ // Active state
112
+ active?: boolean
113
+ /** A notification that is shown in an overlay pointing at the activity action button. */
114
+ notification?: {
115
+ message: string
116
+ onClose: () => void
117
+ intent?: NotificationProps["intent"]
118
+ // Timeout in ms before notification is closed. Default: none
119
+ timeout?: number
120
+ }
108
121
  }
109
122
 
110
123
  interface IActivityMenuAction extends ActivityControlWidgetAction {
@@ -209,28 +222,11 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
209
222
  data-test-id={dataTestIdLegacy ? `${dataTestIdLegacy}-actions` : undefined}
210
223
  >
211
224
  {activityActions &&
212
- activityActions.map((action, idx) => {
213
- return (
214
- <IconButton
215
- key={
216
- typeof action.icon === "string"
217
- ? action.icon
218
- : action["data-test-id"] ?? action["data-testid"] ?? idx
219
- }
220
- data-test-id={action["data-test-id"]}
221
- data-testid={action["data-testid"]}
222
- name={action.icon}
223
- text={action.tooltip}
224
- onClick={action.action}
225
- disabled={action.disabled}
226
- intent={action.hasStateWarning ? "warning" : undefined}
227
- tooltipProps={{
228
- hoverOpenDelay: 200,
229
- placement: "bottom",
230
- }}
231
- />
232
- );
233
- })}
225
+ activityActions.map((action, idx) => <ActivityActionButton
226
+ key={idx}
227
+ action={action}
228
+ />
229
+ )}
234
230
  {additionalActions}
235
231
  {activityContextMenu && activityContextMenu.menuItems.length > 0 && (
236
232
  <ContextMenu
@@ -241,11 +237,7 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
241
237
  return (
242
238
  <MenuItem
243
239
  icon={menuAction.icon}
244
- key={
245
- typeof menuAction.icon === "string"
246
- ? menuAction.icon
247
- : menuAction["data-test-id"] ?? idx
248
- }
240
+ key={idx}
249
241
  onClick={menuAction.action}
250
242
  text={menuAction.tooltip}
251
243
  />
@@ -267,3 +259,44 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) {
267
259
  <div className={classname}>{widget}</div>
268
260
  );
269
261
  }
262
+
263
+ interface ActivityActionButtonProps {
264
+ action: ActivityControlWidgetAction
265
+ }
266
+
267
+ const ActivityActionButton = ({action}: ActivityActionButtonProps) => {
268
+ const actionButtonRef = React.useRef(null);
269
+ const ActionButton = () => (
270
+ <IconButton
271
+ data-test-id={action["data-test-id"]}
272
+ data-testid={action["data-testid"]}
273
+ name={action.icon}
274
+ text={action.tooltip}
275
+ onClick={action.action}
276
+ disabled={action.disabled}
277
+ intent={action.hasStateWarning ? "warning" : undefined}
278
+ tooltipProps={{
279
+ hoverOpenDelay: 200,
280
+ placement: "bottom"
281
+ }}
282
+ active={action.active}
283
+ />
284
+ )
285
+ return action.notification ?
286
+ <>
287
+ <span ref={actionButtonRef}>
288
+ <ActionButton/>
289
+ </span>
290
+ {actionButtonRef.current && (
291
+ <DecoupledOverlay targetSelectorOrElement={actionButtonRef.current} paddingSize={"small"}>
292
+ <Notification
293
+ message={action.notification.message}
294
+ intent={action.notification.intent ?? "neutral"}
295
+ onDismiss={action.notification.onClose}
296
+ timeout={action.notification.timeout}
297
+ />
298
+ </DecoupledOverlay>
299
+ )}
300
+ </> :
301
+ <ActionButton/>
302
+ }
@@ -130,7 +130,7 @@ export const StickyNoteModal: React.FC<StickyNoteModalProps> = React.memo(
130
130
  name={translate("noteLabel")}
131
131
  id={"sticky-note-input"}
132
132
  mode="markdown"
133
- preventLineNumbers
133
+ useToolbar
134
134
  onChange={(value) => {
135
135
  refNote.current = value;
136
136
  }}
@@ -3,7 +3,7 @@ import { decode } from "he";
3
3
  import { invisibleZeroWidthCharacters } from "./utils/characters";
4
4
  import { colorCalculateDistance } from "./utils/colorCalculateDistance";
5
5
  import decideContrastColorValue from "./utils/colorDecideContrastvalue";
6
- import { getEnabledColorsFromPalette, textToColorHash } from "./utils/colorHash";
6
+ import { getEnabledColorPropertiesFromPalette, getEnabledColorsFromPalette, textToColorHash } from "./utils/colorHash";
7
7
  import getColorConfiguration from "./utils/getColorConfiguration";
8
8
  import { getScrollParent } from "./utils/getScrollParent";
9
9
  import { getGlobalVar, setGlobalVar } from "./utils/globalVars";
@@ -22,6 +22,7 @@ export const utils = {
22
22
  setGlobalVar,
23
23
  getScrollParent,
24
24
  getEnabledColorsFromPalette,
25
+ getEnabledColorPropertiesFromPalette,
25
26
  textToColorHash,
26
27
  reduceToText,
27
28
  decodeHtmlEntities: decode,
@@ -28,7 +28,7 @@ export default class CssCustomProperties {
28
28
 
29
29
  // Methods
30
30
 
31
- customProperties = (props: getCustomPropertiesProps = {}): string[][] | Record<string, string> => {
31
+ customProperties = (props: getCustomPropertiesProps = {}): [string, string][] | Record<string, string> => {
32
32
  // FIXME:
33
33
  // in case of performance issues results should get saved at least into intern variables
34
34
  // other cache strategies could be also tested
@@ -104,7 +104,9 @@ export default class CssCustomProperties {
104
104
  });
105
105
  };
106
106
 
107
- static listCustomProperties = (props: getCustomPropertiesProps = {}): string[][] | Record<string, string> => {
107
+ static listCustomProperties = (
108
+ props: getCustomPropertiesProps = {}
109
+ ): [string, string][] | Record<string, string> => {
108
110
  const { removeDashPrefix = true, returnObject = true, filterName = () => true, ...filterProps } = props;
109
111
 
110
112
  const customProperties = CssCustomProperties.listLocalCssStyleRuleProperties({
@@ -123,6 +125,6 @@ export default class CssCustomProperties {
123
125
 
124
126
  return returnObject
125
127
  ? (Object.fromEntries(customProperties) as Record<string, string>)
126
- : (customProperties as string[][]);
128
+ : (customProperties as [string, string][]);
127
129
  };
128
130
  }
@@ -6,10 +6,10 @@ import { colorCalculateDistance } from "./colorCalculateDistance";
6
6
  import CssCustomProperties from "./CssCustomProperties";
7
7
 
8
8
  type ColorOrFalse = Color | false;
9
- type ColorWeight = 100 | 300 | 500 | 700 | 900;
10
- type PaletteGroup = "identity" | "semantic" | "layout" | "extra";
9
+ export type ColorWeight = 100 | 300 | 500 | 700 | 900;
10
+ export type PaletteGroup = "identity" | "semantic" | "layout" | "extra";
11
11
 
12
- interface getEnabledColorsProps {
12
+ export interface getEnabledColorsProps {
13
13
  /** Specify the palette groups used to define the set of colors. */
14
14
  includePaletteGroup?: PaletteGroup[];
15
15
  /** Use only some weights of a color tint. */
@@ -21,20 +21,43 @@ interface getEnabledColorsProps {
21
21
  }
22
22
 
23
23
  const getEnabledColorsFromPaletteCache = new Map<string, Color[]>();
24
+ const getEnabledColorPropertiesFromPaletteCache = new Map<string, [string, string][]>();
24
25
 
25
- export function getEnabledColorsFromPalette({
26
+ export function getEnabledColorsFromPalette(props: getEnabledColorsProps): Color[] {
27
+ const configId = JSON.stringify({
28
+ includePaletteGroup: props.includePaletteGroup,
29
+ includeColorWeight: props.includeColorWeight,
30
+ });
31
+
32
+ if (getEnabledColorsFromPaletteCache.has(configId)) {
33
+ return getEnabledColorsFromPaletteCache.get(configId)!;
34
+ }
35
+
36
+ const colorPropertiesFromPalette = Object.values(getEnabledColorPropertiesFromPalette(props));
37
+
38
+ getEnabledColorsFromPaletteCache.set(
39
+ configId,
40
+ colorPropertiesFromPalette.map((color) => {
41
+ return Color(color[1]);
42
+ })
43
+ );
44
+
45
+ return getEnabledColorsFromPaletteCache.get(configId)!;
46
+ }
47
+
48
+ export function getEnabledColorPropertiesFromPalette({
26
49
  includePaletteGroup = ["layout"],
27
50
  includeColorWeight = [100, 300, 500, 700, 900],
28
- // TODO (planned for later): includeMixedColors = false,
51
+ // (planned for later): includeMixedColors = false,
29
52
  minimalColorDistance = COLORMINDISTANCE,
30
- }: getEnabledColorsProps): Color[] {
53
+ }: getEnabledColorsProps): [string, string][] {
31
54
  const configId = JSON.stringify({
32
55
  includePaletteGroup,
33
56
  includeColorWeight,
34
57
  });
35
58
 
36
- if (getEnabledColorsFromPaletteCache.has(configId)) {
37
- return getEnabledColorsFromPaletteCache.get(configId)!;
59
+ if (getEnabledColorPropertiesFromPaletteCache.has(configId)) {
60
+ return getEnabledColorPropertiesFromPaletteCache.get(configId)!;
38
61
  }
39
62
 
40
63
  const colorsFromPalette = new CssCustomProperties({
@@ -50,18 +73,18 @@ export function getEnabledColorsFromPalette({
50
73
  const weight = parseInt(tint[2], 10) as ColorWeight;
51
74
  return includePaletteGroup.includes(group) && includeColorWeight.includes(weight);
52
75
  },
53
- removeDashPrefix: false,
76
+ removeDashPrefix: true,
54
77
  returnObject: true,
55
78
  }).customProperties();
56
79
 
57
- const colorsFromPaletteValues = Object.values(colorsFromPalette) as string[];
80
+ const colorsFromPaletteValues = Object.entries(colorsFromPalette) as [string, string][];
58
81
 
59
82
  const colorsFromPaletteWithEnoughDistance =
60
83
  minimalColorDistance > 0
61
- ? colorsFromPaletteValues.reduce((enoughDistance: string[], color: string) => {
84
+ ? colorsFromPaletteValues.reduce((enoughDistance: [string, string][], color: [string, string]) => {
62
85
  if (enoughDistance.includes(color)) {
63
86
  return enoughDistance.filter((checkColor) => {
64
- const distance = colorCalculateDistance({ color1: color, color2: checkColor });
87
+ const distance = colorCalculateDistance({ color1: color[1], color2: checkColor[1] });
65
88
  return checkColor === color || (distance && minimalColorDistance <= distance);
66
89
  });
67
90
  } else {
@@ -70,14 +93,9 @@ export function getEnabledColorsFromPalette({
70
93
  }, colorsFromPaletteValues)
71
94
  : colorsFromPaletteValues;
72
95
 
73
- getEnabledColorsFromPaletteCache.set(
74
- configId,
75
- colorsFromPaletteWithEnoughDistance.map((color: string) => {
76
- return Color(color);
77
- })
78
- );
96
+ getEnabledColorPropertiesFromPaletteCache.set(configId, colorsFromPaletteWithEnoughDistance);
79
97
 
80
- return getEnabledColorsFromPaletteCache.get(configId)!;
98
+ return getEnabledColorPropertiesFromPaletteCache.get(configId)!;
81
99
  }
82
100
 
83
101
  function getColorcode(text: string): ColorOrFalse {
@@ -148,7 +166,7 @@ export function textToColorHash({
148
166
  }
149
167
 
150
168
  function stringToIntegerHash(inputString: string): number {
151
- /* this function is idempotend, meaning it retrieves the same result for the same input
169
+ /* this function is idempotent, meaning it retrieves the same result for the same input
152
170
  no matter how many times it's called */
153
171
  // Convert the string to a hash code
154
172
  let hashCode = 0;
@@ -2,6 +2,7 @@
2
2
  @use "sass:list";
3
3
 
4
4
  :root {
5
+ // creating css custom properties from palette colors
5
6
  @each $palette-group-name, $palette-group-tints in $eccgui-color-palette-light {
6
7
  @each $palette-tint-name, $palette-tint-colors in $palette-group-tints {
7
8
  @for $i from 1 through list.length($palette-tint-colors) {
@@ -12,4 +13,18 @@
12
13
  }
13
14
  }
14
15
  }
16
+
17
+ // set aliases for base colors
18
+ --#{$eccgui}-color-primary: #{$eccgui-color-primary};
19
+ --#{$eccgui}-color-primary-contrast: #{$eccgui-color-primary-contrast};
20
+ --#{$eccgui}-color-accent: #{$eccgui-color-accent};
21
+ --#{$eccgui}-color-accent-contrast: #{$eccgui-color-accent-contrast};
22
+ --#{$eccgui}-color-success-foreground: #{$eccgui-color-success-text};
23
+ --#{$eccgui}-color-success-background: #{$eccgui-color-success-background};
24
+ --#{$eccgui}-color-info-foreground: #{$eccgui-color-info-text};
25
+ --#{$eccgui}-color-info-background: #{$eccgui-color-info-background};
26
+ --#{$eccgui}-color-warning-foreground: #{$eccgui-color-warning-text};
27
+ --#{$eccgui}-color-warning-background: #{$eccgui-color-warning-background};
28
+ --#{$eccgui}-color-danger-foreground: #{$eccgui-color-danger-text};
29
+ --#{$eccgui}-color-danger-background: #{$eccgui-color-danger-background};
15
30
  }
@@ -0,0 +1,72 @@
1
+ import React from "react";
2
+ import { Meta, StoryFn } from "@storybook/react";
3
+
4
+ import { getEnabledColorsProps } from "../../common/utils/colorHash";
5
+ import textFieldTest from "../TextField/stories/TextField.stories";
6
+
7
+ import { ColorField, ColorFieldProps } from "./ColorField";
8
+
9
+ export default {
10
+ title: "Forms/ColorField",
11
+ component: ColorField,
12
+ argTypes: {
13
+ ...textFieldTest.argTypes,
14
+ },
15
+ } as Meta<typeof ColorField>;
16
+
17
+ const Template: StoryFn<typeof ColorField> = (args) => <ColorField {...args}></ColorField>;
18
+
19
+ export const Default = Template.bind({});
20
+ Default.args = {
21
+ onChange: (e) => {
22
+ alert(e.target.value);
23
+ },
24
+ };
25
+
26
+ export const NoPalettePresets = Template.bind({});
27
+ NoPalettePresets.args = {
28
+ ...Default.args,
29
+ allowCustomColor: true,
30
+ colorPresets: [],
31
+ };
32
+
33
+ type TemplateColorHashProps = { stringForColorHashValue: string } & Pick<
34
+ ColorFieldProps,
35
+ "onChange" | "allowCustomColor"
36
+ > &
37
+ Pick<getEnabledColorsProps, "includeColorWeight" | "includePaletteGroup">;
38
+
39
+ const TemplateColorHash: StoryFn<TemplateColorHashProps> = (args: TemplateColorHashProps) => (
40
+ <ColorField
41
+ allowCustomColor={args.allowCustomColor}
42
+ colorPresets={ColorField.listColorPalettePresets({
43
+ includeColorWeight: args.includeColorWeight,
44
+ includePaletteGroup: args.includePaletteGroup,
45
+ })}
46
+ value={ColorField.calculateColorHashValue(args.stringForColorHashValue, {
47
+ allowCustomColor: args.allowCustomColor,
48
+ includeColorWeight: args.includeColorWeight,
49
+ includePaletteGroup: args.includePaletteGroup,
50
+ })}
51
+ />
52
+ );
53
+
54
+ /**
55
+ * Component provides a helper function to calculate a color hash from a text,
56
+ * that can be used as `value` or `defaultValue`.
57
+ *
58
+ * ```
59
+ * <ColorField value={ColorField.calculateColorHashValue("MyText")} />
60
+ * ```
61
+ *
62
+ * You can add `options` to set the config for the color palette filters.
63
+ * The same default values like on `ColorField` are used for them.
64
+ */
65
+ export const ColorHashValue = TemplateColorHash.bind({});
66
+ ColorHashValue.args = {
67
+ ...Default.args,
68
+ allowCustomColor: true,
69
+ includeColorWeight: [300, 500, 700],
70
+ includePaletteGroup: ["layout", "extra"],
71
+ stringForColorHashValue: "My text that will used to create a color hash as initial value.",
72
+ };
@@ -0,0 +1,101 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+
5
+ import "@testing-library/jest-dom";
6
+
7
+ import { CLASSPREFIX as eccgui } from "../../configuration/constants";
8
+
9
+ import { ColorField } from "./ColorField";
10
+
11
+ describe("ColorField", () => {
12
+ describe("rendering", () => {
13
+ it("renders without crashing, and correct component CSS class is applied", () => {
14
+ const { container } = render(<ColorField />);
15
+ expect(container).not.toBeEmptyDOMElement();
16
+ expect(container.getElementsByClassName(`${eccgui}-colorfield`).length).toBe(1);
17
+ });
18
+
19
+ it("renders a color input by default (no palette presets)", () => {
20
+ const { container } = render(<ColorField colorPresets={[]} allowCustomColor={true} />);
21
+ expect(container.querySelector("input[type='color']")).toBeInTheDocument();
22
+ });
23
+
24
+ it("renders a readonly text input when palette colors are configured, and custom picker CSS class is applied", () => {
25
+ const { container } = render(
26
+ <ColorField
27
+ className="my-custom-class"
28
+ colorPresets={[
29
+ ["my-black", "#000000"],
30
+ ["my-white", "#ffffff"],
31
+ ]}
32
+ />
33
+ );
34
+ // With default palette settings, a text input with readOnly is shown
35
+ expect(container.querySelector("input[type='text']")).toBeInTheDocument();
36
+ expect(container.querySelector("input[readonly]")).toBeInTheDocument();
37
+ expect(container.querySelector(`.${eccgui}-colorfield--custom-picker`)).toBeInTheDocument();
38
+ });
39
+
40
+ it("applies additional className", () => {
41
+ render(<ColorField className="my-custom-class" colorPresets={[]} allowCustomColor={true} />);
42
+ expect(document.querySelector(".my-custom-class")).toBeInTheDocument();
43
+ });
44
+ });
45
+
46
+ describe("value handling", () => {
47
+ it("uses defaultValue as initial color", () => {
48
+ render(<ColorField defaultValue="#ff0000" colorPresets={[]} allowCustomColor={true} />);
49
+ const input = document.querySelector("input") as HTMLInputElement;
50
+ expect(input.value).toBe("#ff0000");
51
+ });
52
+
53
+ it("uses value prop as initial color", () => {
54
+ render(<ColorField value="#00ff00" colorPresets={[]} allowCustomColor={true} />);
55
+ const input = document.querySelector("input") as HTMLInputElement;
56
+ expect(input.value).toBe("#00ff00");
57
+ });
58
+
59
+ it("falls back to #000000 when no value or defaultValue is provided", () => {
60
+ render(<ColorField colorPresets={[]} allowCustomColor={true} />);
61
+ const input = document.querySelector("input") as HTMLInputElement;
62
+ expect(input.value).toBe("#000000");
63
+ });
64
+
65
+ it("updates displayed value when value prop changes", () => {
66
+ const { rerender } = render(<ColorField value="#ff0000" colorPresets={[]} allowCustomColor={true} />);
67
+ let input = document.querySelector("input") as HTMLInputElement;
68
+ expect(input.value).toBe("#ff0000");
69
+
70
+ rerender(<ColorField value="#0000ff" colorPresets={[]} allowCustomColor={true} />);
71
+ input = document.querySelector("input") as HTMLInputElement;
72
+ expect(input.value).toBe("#0000ff");
73
+ });
74
+ });
75
+
76
+ describe("disabled state", () => {
77
+ it("is disabled when disabled prop is true", () => {
78
+ render(<ColorField disabled colorPresets={[]} allowCustomColor={true} />);
79
+ const input = document.querySelector("input") as HTMLInputElement;
80
+ expect(input).toBeDisabled();
81
+ });
82
+
83
+ it("is disabled when no palette colors and allowCustomColor is false", () => {
84
+ render(<ColorField colorPresets={[]} allowCustomColor={false} />);
85
+ const input = document.querySelector("input") as HTMLInputElement;
86
+ expect(input).toBeDisabled();
87
+ });
88
+ });
89
+
90
+ describe("onChange callback", () => {
91
+ it("calls onChange when native color input changes", async () => {
92
+ const user = userEvent.setup();
93
+ const onChange = jest.fn();
94
+ render(<ColorField onChange={onChange} colorPresets={[]} allowCustomColor={true} />);
95
+ const input = document.querySelector("input[type='color']") as HTMLInputElement;
96
+ input.type = "text"; // for unknown reasons Jest seems not able to test it on color inputs
97
+ await user.type(input, "#123456");
98
+ expect(onChange).toHaveBeenCalled();
99
+ });
100
+ });
101
+ });