@qoretechnologies/reqraft 0.10.19 → 0.10.20

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 (63) hide show
  1. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  2. package/dist/components/form/engine/CompactRow.js +25 -11
  3. package/dist/components/form/engine/CompactRow.js.map +1 -1
  4. package/dist/components/form/engine/FormEngine.d.ts +6 -1
  5. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  6. package/dist/components/form/engine/FormEngine.js +38 -19
  7. package/dist/components/form/engine/FormEngine.js.map +1 -1
  8. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  9. package/dist/components/form/engine/compactRowStyles.js +1 -1
  10. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  11. package/dist/components/form/engine/readFirst.d.ts +6 -0
  12. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  13. package/dist/components/form/engine/readFirst.js +19 -3
  14. package/dist/components/form/engine/readFirst.js.map +1 -1
  15. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  16. package/dist/components/form/expressions/builder/index.js +13 -2
  17. package/dist/components/form/expressions/builder/index.js.map +1 -1
  18. package/dist/components/form/expressions/useExpressions.d.ts.map +1 -1
  19. package/dist/components/form/expressions/useExpressions.js +4 -1
  20. package/dist/components/form/expressions/useExpressions.js.map +1 -1
  21. package/dist/components/form/fields/Field.d.ts.map +1 -1
  22. package/dist/components/form/fields/Field.js +5 -3
  23. package/dist/components/form/fields/Field.js.map +1 -1
  24. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +2 -1
  25. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  26. package/dist/components/form/fields/allowed-values/AllowedValues.js +8 -26
  27. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  28. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  29. package/dist/components/form/fields/auto/AutoFormField.js +3 -3
  30. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  31. package/dist/components/form/fields/boolean/Boolean.js +1 -1
  32. package/dist/components/form/fields/boolean/Boolean.js.map +1 -1
  33. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
  34. package/dist/components/form/fields/multi-select/MultiSelectFormField.js +5 -4
  35. package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
  36. package/dist/components/form/fields/select/Select.d.ts.map +1 -1
  37. package/dist/components/form/fields/select/Select.js +29 -12
  38. package/dist/components/form/fields/select/Select.js.map +1 -1
  39. package/dist/components/form/fields/select/SelectCollection.d.ts +8 -2
  40. package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
  41. package/dist/components/form/fields/select/SelectCollection.js +52 -16
  42. package/dist/components/form/fields/select/SelectCollection.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/components/form/engine/CompactRow.tsx +46 -29
  45. package/src/components/form/engine/FormEngine.stories.tsx +99 -2
  46. package/src/components/form/engine/FormEngine.tsx +42 -10
  47. package/src/components/form/engine/compactRowStyles.ts +2 -1
  48. package/src/components/form/engine/readFirst.ts +25 -4
  49. package/src/components/form/expressions/ExpressionField.stories.tsx +10 -0
  50. package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +26 -19
  51. package/src/components/form/expressions/builder/index.tsx +12 -1
  52. package/src/components/form/expressions/useExpressions.ts +4 -1
  53. package/src/components/form/fields/Field.tsx +10 -1
  54. package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -36
  55. package/src/components/form/fields/auto/AutoFormField.stories.tsx +1 -0
  56. package/src/components/form/fields/auto/AutoFormField.tsx +2 -0
  57. package/src/components/form/fields/boolean/Boolean.stories.tsx +34 -0
  58. package/src/components/form/fields/boolean/Boolean.tsx +1 -1
  59. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +14 -10
  60. package/src/components/form/fields/select/Select.stories.tsx +4 -0
  61. package/src/components/form/fields/select/Select.tsx +38 -12
  62. package/src/components/form/fields/select/SelectCollection.tsx +78 -17
  63. package/src/stores/currentUser/currentUser.stories.tsx +32 -0
@@ -25,6 +25,7 @@ import NumberFormField from './number/Number';
25
25
  import { ReqraftObjectFormField } from './object/Object';
26
26
  import { RichTextFormField } from './rich-text/RichText';
27
27
  import { SelectFormField } from './select/Select';
28
+ import { getSelectItemShortDescription } from './select/SelectCollection';
28
29
  import { StringFormField } from './string/String';
29
30
  import { ArrayAutoField } from './array/ArrayAutoField';
30
31
  import { AutoFormField } from './auto/AutoFormField';
@@ -538,6 +539,10 @@ export const FormField = <T extends TFormFieldType>({
538
539
  icon='SaveFill'
539
540
  fluid
540
541
  hideItemCount
542
+ forceDropdown={
543
+ (fieldProps as { forceDropdown?: boolean } | undefined)?.forceDropdown ??
544
+ (rest as { forceDropdown?: boolean }).forceDropdown
545
+ }
541
546
  />
542
547
  );
543
548
  }
@@ -557,7 +562,7 @@ export const FormField = <T extends TFormFieldType>({
557
562
  margin='right'
558
563
  key={index}
559
564
  label={item.display_name ?? JSON.stringify(itemValue)}
560
- tooltip={item.short_desc || item.desc}
565
+ tooltip={getSelectItemShortDescription(mapAllowedValue(item))}
561
566
  disabled={item.disabled}
562
567
  checked={checked}
563
568
  intent={checked ? 'info' : undefined}
@@ -578,6 +583,10 @@ export const FormField = <T extends TFormFieldType>({
578
583
  value={value}
579
584
  onChange={(val) => handleChange(val as TFormFieldValueType<T>)}
580
585
  fluid
586
+ forceDropdown={
587
+ (fieldProps as { forceDropdown?: boolean } | undefined)?.forceDropdown ??
588
+ (rest as { forceDropdown?: boolean }).forceDropdown
589
+ }
581
590
  />
582
591
  );
583
592
  };
@@ -12,8 +12,8 @@ import { ReqoreCheckbox, ReqoreControlGroup, ReqoreSpan } from '@qoretechnologie
12
12
  import { IReqoreButtonProps } from '@qoretechnologies/reqore/dist/components/Button';
13
13
  import { IQorusAllowedValue, TQorusType } from '@qoretechnologies/ts-toolkit';
14
14
  import { size as count, isEqual } from 'lodash';
15
- import { memo, useCallback, useEffect, useMemo, useState } from 'react';
16
- import { useDebounce } from 'react-use';
15
+ import { memo, useMemo } from 'react';
16
+ import { getSelectItemShortDescription } from '../select/SelectCollection';
17
17
  import { ISelectFormFieldItem, SelectFormField as Select } from '../select/Select';
18
18
 
19
19
  export interface IFieldAllowedValuesProps extends Pick<
@@ -30,6 +30,7 @@ export interface IFieldAllowedValuesProps extends Pick<
30
30
  app?: string;
31
31
  action?: string;
32
32
  showSavedValues?: boolean;
33
+ forceDropdown?: boolean;
33
34
  [key: string]: any;
34
35
  }
35
36
 
@@ -57,7 +58,7 @@ export const FieldAllowedValuesCheckGroup = memo(
57
58
  margin='right'
58
59
  key={item.value?.toString()}
59
60
  label={item.display_name || JSON.stringify(item.value)}
60
- tooltip={item.short_desc || item.desc}
61
+ tooltip={getSelectItemShortDescription(item)}
61
62
  disabled={rest.disabled}
62
63
  readOnly={rest.readOnly}
63
64
  intent={item.value === value ? 'info' : undefined}
@@ -97,49 +98,23 @@ export const FieldAllowedValues = memo(
97
98
  allowCreation,
98
99
  showSavedValues,
99
100
  showDescription,
101
+ forceDropdown,
100
102
  size,
101
103
  disabled,
102
104
  name,
103
105
  readOnly,
104
106
  }: IFieldAllowedValuesProps) => {
105
- const [localValue, setLocalValue] = useState(value);
106
- const [localItems, setLocalItems] = useState<IQorusAllowedValue[]>(items);
107
-
108
- useEffect(() => {
109
- setLocalItems(items);
110
- }, [JSON.stringify(items)]);
111
-
112
- const handleLocalChange = useCallback((_name: string, value: unknown) => {
113
- setLocalValue(value);
114
- }, []);
115
-
116
- useDebounce(
117
- () => {
118
- if (localValue !== value) {
119
- onChange(name, localValue);
120
- }
121
- },
122
- 200,
123
- [localValue]
124
- );
125
-
126
- useEffect(() => {
127
- if (value !== localValue) {
128
- setLocalValue(value);
129
- }
130
- }, [value]);
131
-
132
107
  const fullItems = useMemo(() => {
133
108
  const result = [
134
109
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
135
- ...localItems.map(({ metadata, value, ...rest }) => ({
110
+ ...items.map(({ metadata, value, ...rest }) => ({
136
111
  value: value?.value,
137
112
  ...rest,
138
113
  })),
139
114
  ] as ISelectFormFieldItem[];
140
115
 
141
116
  return result;
142
- }, [JSON.stringify(localItems), type, value, showSavedValues, disabled, readOnly]);
117
+ }, [JSON.stringify(items), type, value, showSavedValues, disabled, readOnly]);
143
118
 
144
119
  const style = useMemo(() => ({ width: '100%' }), []);
145
120
 
@@ -153,8 +128,8 @@ export const FieldAllowedValues = memo(
153
128
  return (
154
129
  <FieldAllowedValuesCheckGroup
155
130
  items={fullItems}
156
- onChange={handleLocalChange}
157
- value={localValue}
131
+ onChange={onChange}
132
+ value={value}
158
133
  name={name}
159
134
  />
160
135
  );
@@ -164,10 +139,11 @@ export const FieldAllowedValues = memo(
164
139
  <Select
165
140
  items={fullItems}
166
141
  value={value}
167
- onChange={(value) => handleLocalChange(name, value)}
142
+ onChange={(value) => onChange(name, value)}
168
143
  fluid
169
144
  fixed={false}
170
145
  showDescription={Boolean(showDescription || showDescription === undefined)}
146
+ forceDropdown={forceDropdown}
171
147
  style={style}
172
148
  size={size}
173
149
  disabled={disabled}
@@ -178,10 +154,11 @@ export const FieldAllowedValues = memo(
178
154
  return (
179
155
  <Select
180
156
  items={fullItems}
181
- onChange={(value) => handleLocalChange(name, value)}
157
+ onChange={(value) => onChange(name, value)}
182
158
  fluid
183
159
  fixed={false}
184
160
  showDescription={false}
161
+ forceDropdown={forceDropdown}
185
162
  style={style}
186
163
  minimal
187
164
  placeholder={'Saved & Suggested Values'}
@@ -330,6 +330,7 @@ export const RichText: Story = {
330
330
  export const ConnectionWithAllowedValues: Story = {
331
331
  args: {
332
332
  defaultType: 'connection',
333
+ forceDropdown: false,
333
334
  allowed_values: [
334
335
  {
335
336
  display_name: 'test',
@@ -374,6 +374,7 @@ function AutoField<T = any>({
374
374
  ) {
375
375
  return (
376
376
  <SchemaDefinitionEditor
377
+ {...((rest as any).fieldProps || {})}
377
378
  value={
378
379
  value === null || value === undefined
379
380
  ? undefined
@@ -419,6 +420,7 @@ function AutoField<T = any>({
419
420
  app={rest.app}
420
421
  action={rest.action}
421
422
  showDescription={rest.showDescription}
423
+ forceDropdown={rest.forceDropdown}
422
424
  allowCreation={rest.allowed_values_creatable}
423
425
  showSavedValues={showSavedValues}
424
426
  readOnly={rest.readonly}
@@ -75,6 +75,40 @@ export const Unchecked: Story = {
75
75
  },
76
76
  };
77
77
 
78
+ export const Unset: Story = {
79
+ args: {
80
+ checked: undefined,
81
+ },
82
+ parameters: {
83
+ docs: {
84
+ description: {
85
+ story:
86
+ 'Renders the Boolean field without a default choice. The switch remains visibly unset until the user explicitly chooses Yes or No.',
87
+ },
88
+ },
89
+ },
90
+ };
91
+
92
+ export const UnsetSelectsYes: Story = {
93
+ args: {
94
+ checked: undefined,
95
+ },
96
+ parameters: {
97
+ docs: {
98
+ description: {
99
+ story:
100
+ 'Verifies that an unset Boolean field saves the first explicit Yes selection instead of treating the missing value as No.',
101
+ },
102
+ },
103
+ },
104
+ async play({ canvasElement, args }) {
105
+ const canvas = within(canvasElement);
106
+
107
+ await userEvent.click(canvas.getByLabelText('Boolean'));
108
+ await expect(args.onChange).toHaveBeenLastCalledWith(true);
109
+ },
110
+ };
111
+
78
112
  export const Disabled: Story = {
79
113
  args: {
80
114
  checked: true,
@@ -18,7 +18,7 @@ export const BooleanFormField = ({
18
18
 
19
19
  return (
20
20
  <ReqoreCheckbox
21
- checked={checked ?? false}
21
+ checked={checked}
22
22
  onClick={toggle}
23
23
  asSwitch
24
24
  onText='Yes'
@@ -2,6 +2,7 @@ import { ReqoreMultiSelect } from '@qoretechnologies/reqore';
2
2
  import { TReqoreMultiSelectItem } from '@qoretechnologies/reqore/dist/components/MultiSelect';
3
3
  import { IQorusAllowedValue } from '@qoretechnologies/ts-toolkit';
4
4
  import { memo, useMemo } from 'react';
5
+ import { getSelectItemShortDescription } from '../select/SelectCollection';
5
6
 
6
7
  export interface IMultiSelectFormFieldProps {
7
8
  /** Currently selected values (plain scalars) */
@@ -33,16 +34,19 @@ export const MultiSelectFormField = memo(
33
34
 
34
35
  const reqoreItems = useMemo<TReqoreMultiSelectItem[]>(() => {
35
36
  const base = items.map(
36
- (item): TReqoreMultiSelectItem => ({
37
- value: item.value?.value as string,
38
- label: item.display_name ?? String(item.value?.value ?? ''),
39
- description: item.short_desc || item.desc,
40
- tooltip: item.desc || item.short_desc,
41
- wrap: true,
42
- // IDE parity: when `*` is selected, every other item is disabled.
43
- disabled:
44
- !!item.disabled || (selectedValues.includes('*') && item.value?.value !== '*'),
45
- })
37
+ (item): TReqoreMultiSelectItem => {
38
+ const description = getSelectItemShortDescription(item);
39
+
40
+ return {
41
+ value: item.value?.value as string,
42
+ label: item.display_name ?? String(item.value?.value ?? ''),
43
+ description,
44
+ wrap: true,
45
+ // IDE parity: when `*` is selected, every other item is disabled.
46
+ disabled:
47
+ !!item.disabled || (selectedValues.includes('*') && item.value?.value !== '*'),
48
+ };
49
+ }
46
50
  );
47
51
 
48
52
  // IDE parity: selected values that aren't in the allowed list still
@@ -62,6 +62,7 @@ export const ItemsWithDescription: Story = {
62
62
  },
63
63
  },
64
64
  args: {
65
+ forceDropdown: false,
65
66
  items: [
66
67
  { display_name: 'Item 1', desc: 'This is item 1', value: 'item1', icon: 'MoneyEuroCircleFill', groups: ['Miscellaneous'] },
67
68
  { display_name: 'Item 2', desc: 'This is item 2', value: 'item2', image: 'https://avatars.githubusercontent.com/u/8861481?v=4' },
@@ -106,6 +107,7 @@ export const ItemsWithDescriptionAndMessages: Story = {
106
107
  },
107
108
  },
108
109
  args: {
110
+ forceDropdown: false,
109
111
  items: [
110
112
  {
111
113
  display_name: 'Item 1',
@@ -162,6 +164,7 @@ export const DisabledItemsWithIntent: Story = {
162
164
 
163
165
  export const DisabledItemsWithIntentAndDescriptions: Story = {
164
166
  args: {
167
+ forceDropdown: false,
165
168
  items: [
166
169
  { display_name: 'Item 1', desc: 'This is item 1', value: 'item1' },
167
170
  { display_name: 'Item 2', short_desc: 'This is item 2', value: 'item2' },
@@ -326,6 +329,7 @@ export const AutoSelectWithDescriptions: Story = {
326
329
 
327
330
  export const ItemValuesAreObjectsAndCanBeSelected: Story = {
328
331
  args: {
332
+ forceDropdown: false,
329
333
  value: { id: { type: 'string', value: 'item1' } },
330
334
  items: [
331
335
  { value: { id: { type: 'string', value: 'item1' } } },
@@ -11,7 +11,11 @@ import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
11
11
  import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
12
12
  import { isEqual, size } from 'lodash';
13
13
  import { memo, useCallback, useEffect, useMemo, useState } from 'react';
14
- import { ISelectFieldCollectionItem, SelectFieldCollection } from './SelectCollection';
14
+ import {
15
+ getSelectItemShortDescription,
16
+ ISelectFieldCollectionItem,
17
+ SelectFieldCollection,
18
+ } from './SelectCollection';
15
19
 
16
20
  export type ISelectFormFieldItem = ISelectFieldCollectionItem;
17
21
 
@@ -68,7 +72,7 @@ export const SelectFormField = memo(
68
72
  showPlaceholder = true,
69
73
  showRightIcon = true,
70
74
  hideItemCount,
71
- forceDropdown,
75
+ forceDropdown = true,
72
76
  fluid,
73
77
  flat,
74
78
  intent,
@@ -89,6 +93,7 @@ export const SelectFormField = memo(
89
93
  return;
90
94
  }
91
95
  onChange?.(item.value);
96
+ setCollectionOpen(false);
92
97
  },
93
98
  [onChange, value]
94
99
  );
@@ -96,7 +101,7 @@ export const SelectFormField = memo(
96
101
  const getItemDescription = useCallback(
97
102
  (itemValue: unknown) => {
98
103
  const item = items.find((item) => isEqual(item.value, itemValue));
99
- return item?.desc || item?.short_desc;
104
+ return getSelectItemShortDescription(item);
100
105
  },
101
106
  [items]
102
107
  );
@@ -133,7 +138,7 @@ export const SelectFormField = memo(
133
138
  const item = items.find(
134
139
  (item) => isEqual(item.display_name, itemName) || isEqual(item.value, itemName)
135
140
  );
136
- return item?.short_desc || (item?.desc ? 'Hover to see description' : defaultDesc);
141
+ return getSelectItemShortDescription(item, defaultDesc);
137
142
  },
138
143
  [items, showDescription]
139
144
  );
@@ -230,13 +235,35 @@ export const SelectFormField = memo(
230
235
  [size(items), hideItemCount]
231
236
  );
232
237
 
233
- if (autoSelect && filteredItems.length === 1 && !filteredItems[0].disabled) {
234
- if (!disabled && !isEqual(filteredItems[0].value, value) && !value) {
235
- handleSelectClick(filteredItems[0]);
238
+ const autoSelectedItem = useMemo(
239
+ () =>
240
+ autoSelect && filteredItems.length === 1 && !filteredItems[0].disabled
241
+ ? filteredItems[0]
242
+ : undefined,
243
+ [autoSelect, filteredItems]
244
+ );
245
+
246
+ // Selecting during render causes a parent-controlled field to synchronously
247
+ // update while this component is still rendering. React rejects that update
248
+ // and can enter an infinite render loop. Commit the derived default after
249
+ // rendering instead; the button below already renders the sole item's label
250
+ // while the controlled value catches up.
251
+ useEffect(() => {
252
+ const valueIsUnset = value === undefined || value === null || value === '';
253
+
254
+ if (
255
+ autoSelectedItem &&
256
+ !disabled &&
257
+ valueIsUnset &&
258
+ !isEqual(autoSelectedItem.value, value)
259
+ ) {
260
+ handleSelectClick(autoSelectedItem);
236
261
  }
262
+ }, [autoSelectedItem, disabled, handleSelectClick, value]);
237
263
 
238
- const itemHasError = hasError(items, filteredItems[0].value);
239
- const itemHasWarning = hasWarning(items, filteredItems[0].value);
264
+ if (autoSelectedItem) {
265
+ const itemHasError = hasError(items, autoSelectedItem.value);
266
+ const itemHasWarning = hasWarning(items, autoSelectedItem.value);
240
267
 
241
268
  return (
242
269
  <ReqoreButton
@@ -247,12 +274,12 @@ export const SelectFormField = memo(
247
274
  fluid={fluid}
248
275
  flat={flat ?? false}
249
276
  size={componentSize}
250
- label={getLabel(items, value ?? filteredItems[0].value)}
277
+ label={getLabel(items, value ?? autoSelectedItem.value)}
251
278
  description={getItemShortDescription(value as string) as string}
252
279
  readOnly
253
280
  fixed
254
281
  minimal
255
- {...getIcon(filteredItems, filteredItems[0].value)}
282
+ {...getIcon(filteredItems, autoSelectedItem.value)}
256
283
  intent={
257
284
  itemHasError ? 'danger'
258
285
  : itemHasWarning ?
@@ -291,7 +318,6 @@ export const SelectFormField = memo(
291
318
  <SelectFieldCollection
292
319
  items={filteredItems}
293
320
  filters={filters}
294
- getItemDescription={(v) => getItemDescription(v) as string}
295
321
  value={value}
296
322
  onItemSelect={handleSelectClick}
297
323
  onClose={() => setCollectionOpen(false)}
@@ -4,7 +4,6 @@ import {
4
4
  ReqoreControlGroup,
5
5
  ReqoreMessage,
6
6
  ReqoreModal,
7
- ReqoreP,
8
7
  } from '@qoretechnologies/reqore';
9
8
  import { IReqoreCollectionItemProps } from '@qoretechnologies/reqore/dist/components/Collection/item';
10
9
  import { TReqoreBadge } from '@qoretechnologies/reqore/dist/components/Button';
@@ -12,7 +11,7 @@ import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
12
11
  import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
13
12
  import { capitalize, isEqual, size } from 'lodash';
14
13
  import { useMemo, useState } from 'react';
15
- import ReactMarkdown from 'react-markdown';
14
+ import { Description } from '../../../Description';
16
15
 
17
16
  const PositiveColorEffect = {
18
17
  gradient: {
@@ -25,6 +24,7 @@ const PositiveColorEffect = {
25
24
 
26
25
  export interface ISelectFieldCollectionItem {
27
26
  value?: unknown;
27
+ name?: unknown;
28
28
  display_name?: string;
29
29
  desc?: string;
30
30
  short_desc?: string;
@@ -42,16 +42,76 @@ export interface ISelectFieldCollectionItem {
42
42
  export interface ISelectFieldCollectionProps {
43
43
  onClose: () => void;
44
44
  items: ISelectFieldCollectionItem[];
45
- getItemDescription: (value: unknown) => string;
46
45
  value?: unknown;
47
46
  onItemSelect: (item: ISelectFieldCollectionItem) => void;
48
47
  filters?: string[];
49
48
  }
50
49
 
50
+ const normalizeSelectItemDescription = (value: unknown): string | undefined => {
51
+ if (typeof value !== 'string' || !value.trim()) {
52
+ return undefined;
53
+ }
54
+
55
+ const seen = new Set<string>();
56
+
57
+ return value
58
+ .split('\n')
59
+ .filter((line) => {
60
+ const normalized = line.trim().replace(/\s+/g, ' ').toLocaleLowerCase();
61
+
62
+ if (!normalized) {
63
+ return true;
64
+ }
65
+
66
+ if (seen.has(normalized)) {
67
+ return false;
68
+ }
69
+
70
+ seen.add(normalized);
71
+ return true;
72
+ })
73
+ .join('\n')
74
+ .trim();
75
+ };
76
+
77
+ export const getSelectItemDescription = (
78
+ item: Pick<ISelectFieldCollectionItem, 'desc' | 'short_desc'>
79
+ ): string | undefined => {
80
+ const desc = normalizeSelectItemDescription(item.desc);
81
+ const shortDesc = normalizeSelectItemDescription(item.short_desc);
82
+ return desc ?? shortDesc;
83
+ };
84
+
85
+ export const getSelectItemShortDescription = (
86
+ item: Pick<ISelectFieldCollectionItem, 'desc' | 'short_desc'> | undefined,
87
+ defaultDesc = ''
88
+ ): string | undefined => {
89
+ if (!item) {
90
+ return defaultDesc || undefined;
91
+ }
92
+
93
+ const shortDescription = normalizeSelectItemDescription(item.short_desc);
94
+
95
+ return shortDescription || defaultDesc || undefined;
96
+ };
97
+
98
+ export const getSelectItemDescriptionProps = (
99
+ item: Pick<ISelectFieldCollectionItem, 'desc' | 'short_desc'>
100
+ ): { longDescription?: string; shortDescription?: string } => {
101
+ const longDescription = normalizeSelectItemDescription(item.desc);
102
+
103
+ if (longDescription) {
104
+ return { longDescription };
105
+ }
106
+
107
+ const shortDescription = normalizeSelectItemDescription(item.short_desc);
108
+
109
+ return { shortDescription };
110
+ };
111
+
51
112
  export const SelectFieldCollection = ({
52
113
  onClose,
53
114
  items,
54
- getItemDescription,
55
115
  value,
56
116
  onItemSelect,
57
117
  filters,
@@ -100,8 +160,10 @@ export const SelectFieldCollection = ({
100
160
  transparent={false}
101
161
  size='tiny'
102
162
  showLayoutSwitch={false}
103
- items={filteredItems.map(
104
- (item): IReqoreCollectionItemProps => ({
163
+ items={filteredItems.map((item): IReqoreCollectionItemProps => {
164
+ const { longDescription, shortDescription } = getSelectItemDescriptionProps(item);
165
+
166
+ return {
105
167
  label: item.display_name || item.value?.toString(),
106
168
  size: 'tiny',
107
169
  groups: item.groups,
@@ -109,8 +171,14 @@ export const SelectFieldCollection = ({
109
171
  responsiveTitle: false,
110
172
  content: (
111
173
  <ReqoreControlGroup vertical fluid>
112
- {item.short_desc && <ReqoreP size='small'>{item.short_desc}</ReqoreP>}
113
- {item.desc && <ReqoreP size='small'>{item.desc}</ReqoreP>}
174
+ {longDescription || shortDescription ? (
175
+ <Description
176
+ longDescription={longDescription || ''}
177
+ shortDescription={shortDescription}
178
+ size='small'
179
+ margin='none'
180
+ />
181
+ ) : null}
114
182
  {item.actions?.map(({ as: Component = ReqoreButton, props, ...rest }, index) => (
115
183
  <Component
116
184
  key={index}
@@ -148,13 +216,6 @@ export const SelectFieldCollection = ({
148
216
  size: '20px',
149
217
  rounded: true,
150
218
  },
151
- tooltip: !!item.desc
152
- ? {
153
- delay: 800,
154
- content: <ReactMarkdown>{getItemDescription(item.value)}</ReactMarkdown>,
155
- maxWidth: '70vw',
156
- }
157
- : undefined,
158
219
  onClick: !item.disabled
159
220
  ? (e) => {
160
221
  if (e.currentTarget.contains(e.target as Node)) {
@@ -163,8 +224,8 @@ export const SelectFieldCollection = ({
163
224
  }
164
225
  }
165
226
  : undefined,
166
- })
167
- )}
227
+ };
228
+ })}
168
229
  actions={filters?.map((filter) => ({
169
230
  label: capitalize(filter.replace('_', ' ')),
170
231
  badge: items.filter((item) => item[filter]).length,
@@ -6,6 +6,38 @@ import { currentUserStore } from './currentUser';
6
6
 
7
7
  const meta = {
8
8
  title: 'Stores/Current User',
9
+ parameters: {
10
+ mockData: [
11
+ {
12
+ url: 'https://hq.qoretechnologies.com:8092/api/latest/users?action=current',
13
+ method: 'GET',
14
+ status: 200,
15
+ response: {
16
+ provider: 'local',
17
+ username: 'david',
18
+ name: 'David Nichols',
19
+ has_default: true,
20
+ roles: ['admin'],
21
+ permissions: ['USER-CONTROL'],
22
+ workflows: [],
23
+ services: [],
24
+ jobs: [],
25
+ mappers: [],
26
+ vmaps: [],
27
+ groups: [],
28
+ fsms: [],
29
+ },
30
+ },
31
+ ],
32
+ },
33
+ beforeEach: () => {
34
+ currentUserStore.setState({
35
+ currentUser: undefined,
36
+ loading: false,
37
+ error: undefined,
38
+ errorData: undefined,
39
+ });
40
+ },
9
41
  render: () => {
10
42
  const { load, loading, currentUser = {}, errorData } = currentUserStore();
11
43