@qoretechnologies/reqraft 0.8.2 → 0.8.3

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 (27) hide show
  1. package/dist/components/form/fields/Field.d.ts +2 -13
  2. package/dist/components/form/fields/Field.d.ts.map +1 -1
  3. package/dist/components/form/fields/Field.js +10 -9
  4. package/dist/components/form/fields/Field.js.map +1 -1
  5. package/dist/components/form/fields/array/ArrayAutoField.d.ts +2 -2
  6. package/dist/components/form/fields/array/ArrayAutoField.d.ts.map +1 -1
  7. package/dist/components/form/fields/array/ArrayAutoField.js +91 -0
  8. package/dist/components/form/fields/array/ArrayAutoField.js.map +1 -1
  9. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts +2 -1
  10. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
  11. package/dist/components/form/fields/multi-select/MultiSelectFormField.js +2 -2
  12. package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
  13. package/dist/components/form/fields/select/Select.d.ts.map +1 -1
  14. package/dist/components/form/fields/select/Select.js +43 -34
  15. package/dist/components/form/fields/select/Select.js.map +1 -1
  16. package/dist/components/form/fields/template/TemplateField.js +3 -3
  17. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  18. package/dist/types/Form.d.ts +3 -2
  19. package/dist/types/Form.d.ts.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/form/fields/Field.tsx +27 -49
  22. package/src/components/form/fields/array/ArrayAutoField.stories.tsx +277 -0
  23. package/src/components/form/fields/array/ArrayAutoField.tsx +167 -7
  24. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +6 -2
  25. package/src/components/form/fields/select/Select.tsx +65 -62
  26. package/src/components/form/fields/template/TemplateField.tsx +3 -3
  27. package/src/types/Form.ts +10 -42
@@ -3,12 +3,13 @@ import {
3
3
  ReqoreControlGroup,
4
4
  ReqorePanel,
5
5
  ReqoreTag,
6
+ ReqoreTagGroup,
6
7
  ReqoreVerticalSpacer,
7
8
  useReqoreProperty,
8
9
  } from '@qoretechnologies/reqore';
9
10
  import { IQorusFormSchema } from '@qoretechnologies/ts-toolkit';
10
11
  import { map, size } from 'lodash';
11
- import { memo, useCallback, useEffect, useState } from 'react';
12
+ import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
12
13
  import { useDebounce } from 'react-use';
13
14
  import { validateFieldWithResult } from '../../../../helpers/validations';
14
15
 
@@ -48,6 +49,11 @@ const defaultValueByType: Record<string, unknown> = {
48
49
  list: [],
49
50
  };
50
51
 
52
+ const formatTagLabel = (val: unknown): string => {
53
+ if (val === undefined || val === null) return '';
54
+ return String(val);
55
+ };
56
+
51
57
  export const ArrayAutoField = memo(
52
58
  ({
53
59
  name,
@@ -65,6 +71,10 @@ export const ArrayAutoField = memo(
65
71
  }: IArrayAutoFieldProps) => {
66
72
  const confirmAction = useReqoreProperty('confirmAction');
67
73
  const [localValue, setLocalValue] = useState(value);
74
+ // Compact mode state: staging value for the input field and optional editing index
75
+ const [inputValue, setInputValue] = useState<unknown>(undefined);
76
+ const [editingIndex, setEditingIndex] = useState<number | null>(null);
77
+ const inputKeyRef = useRef(0);
68
78
 
69
79
  useEffect(() => {
70
80
  setLocalValue(value);
@@ -82,12 +92,9 @@ export const ArrayAutoField = memo(
82
92
  setLocalValue((prev) => [...(prev || []), defaultValueByType[type] ?? undefined]);
83
93
  }, [type]);
84
94
 
85
- const removeItem = useCallback(
86
- (idx: number) => {
87
- setLocalValue((prev) => (prev || []).filter((_, i) => i !== idx));
88
- },
89
- []
90
- );
95
+ const removeItem = useCallback((idx: number) => {
96
+ setLocalValue((prev) => (prev || []).filter((_, i) => i !== idx));
97
+ }, []);
91
98
 
92
99
  const handleItemChange = useCallback((idx: number, itemValue: unknown) => {
93
100
  setLocalValue((prev) => {
@@ -117,6 +124,159 @@ export const ArrayAutoField = memo(
117
124
  return null;
118
125
  };
119
126
 
127
+ // ── Compact mode handlers ──────────────────────────────────────────────
128
+
129
+ const resetInput = useCallback(() => {
130
+ setInputValue(undefined);
131
+ setEditingIndex(null);
132
+ inputKeyRef.current += 1;
133
+ }, []);
134
+
135
+ const confirmInput = useCallback(() => {
136
+ if (inputValue === undefined || inputValue === '') return;
137
+
138
+ if (editingIndex !== null) {
139
+ // Update existing item
140
+ setLocalValue((prev) => {
141
+ const next = [...(prev || [])];
142
+ next[editingIndex] = inputValue;
143
+ return next;
144
+ });
145
+ } else {
146
+ // Add new item
147
+ setLocalValue((prev) => [...(prev || []), inputValue]);
148
+ }
149
+
150
+ resetInput();
151
+ }, [inputValue, editingIndex, resetInput]);
152
+
153
+ const handleEditTag = useCallback(
154
+ (idx: number) => {
155
+ setInputValue(localValue[idx]);
156
+ setEditingIndex(idx);
157
+ inputKeyRef.current += 1;
158
+ },
159
+ [localValue]
160
+ );
161
+
162
+ const handleRemoveTag = useCallback(
163
+ (idx: number) => {
164
+ // If we're editing this item, cancel the edit
165
+ if (editingIndex === idx) {
166
+ resetInput();
167
+ } else if (editingIndex !== null && idx < editingIndex) {
168
+ // Adjust editing index if removing an item before it
169
+ setEditingIndex((prev) => (prev !== null ? prev - 1 : null));
170
+ }
171
+ removeItem(idx);
172
+ },
173
+ [editingIndex, removeItem, resetInput]
174
+ );
175
+
176
+ const handleInputKeyDown = useCallback(
177
+ (e: React.KeyboardEvent) => {
178
+ if (e.key === 'Enter') {
179
+ e.preventDefault();
180
+ confirmInput();
181
+ } else if (e.key === 'Escape' && editingIndex !== null) {
182
+ e.preventDefault();
183
+ resetInput();
184
+ }
185
+ },
186
+ [confirmInput, editingIndex, resetInput]
187
+ );
188
+
189
+ // ── Compact mode: non-hash, non-list element types ─────────────────────
190
+
191
+ if (type !== 'hash' && type !== 'list' && type !== 'rgbcolor') {
192
+ const isEditing = editingIndex !== null;
193
+ const hasInput = inputValue !== undefined && inputValue !== '';
194
+
195
+ return (
196
+ <ReqoreControlGroup
197
+ vertical
198
+ fluid
199
+ className='array-auto-compact'
200
+ onKeyDown={handleInputKeyDown as any}
201
+ >
202
+ {size(localValue) > 0 && (
203
+ <ReqoreTagGroup>
204
+ {map(localValue, (val, idx) => (
205
+ <ReqoreTag
206
+ key={`${idx}-${formatTagLabel(val)}`}
207
+ label={formatTagLabel(val)}
208
+ className='array-auto-compact-tag'
209
+ size={fieldSize as any}
210
+ intent={editingIndex === idx ? 'info' : undefined}
211
+ actions={
212
+ disabled || readOnly ? undefined : (
213
+ [
214
+ {
215
+ icon: 'EditLine',
216
+ tooltip: 'Edit',
217
+ className: 'array-auto-compact-edit',
218
+ onClick: () => handleEditTag(idx),
219
+ },
220
+ {
221
+ icon: 'DeleteBinLine',
222
+ intent: 'danger',
223
+ tooltip: 'Remove',
224
+ className: 'array-auto-compact-remove',
225
+ onClick: () => handleRemoveTag(idx),
226
+ },
227
+ ]
228
+ )
229
+ }
230
+ />
231
+ ))}
232
+ </ReqoreTagGroup>
233
+ )}
234
+
235
+ <ReqoreControlGroup fluid>
236
+ <div style={{ flex: 1 }}>
237
+ {renderItem({
238
+ value: inputValue,
239
+ onChange: (v) => setInputValue(v),
240
+ index: -1,
241
+ type,
242
+ arg_schema,
243
+ allowed_values,
244
+ allowed_values_creatable,
245
+ disabled,
246
+ readOnly,
247
+ size: fieldSize,
248
+ key: inputKeyRef.current,
249
+ } as any)}
250
+ </div>
251
+ {!disabled && !readOnly && (
252
+ <>
253
+ <ReqoreButton
254
+ icon={isEditing ? 'CheckLine' : 'AddLine'}
255
+ intent={isEditing ? 'info' : undefined}
256
+ className='array-auto-compact-confirm'
257
+ tooltip={isEditing ? 'Save changes' : 'Add item'}
258
+ disabled={!hasInput}
259
+ onClick={confirmInput}
260
+ fixed
261
+ />
262
+ {isEditing && (
263
+ <ReqoreButton
264
+ icon='CloseLine'
265
+ className='array-auto-compact-cancel'
266
+ tooltip='Cancel editing'
267
+ onClick={resetInput}
268
+ fixed
269
+ />
270
+ )}
271
+ </>
272
+ )}
273
+ </ReqoreControlGroup>
274
+ </ReqoreControlGroup>
275
+ );
276
+ }
277
+
278
+ // ── Complex mode: hash/list element types ──────────────────────────────
279
+
120
280
  return (
121
281
  <ReqoreControlGroup vertical fluid>
122
282
  {map(localValue, (val, idx) => (
@@ -8,6 +8,7 @@ export interface IMultiSelectFormFieldProps {
8
8
  value?: unknown[];
9
9
  /** The fixed list of allowed items */
10
10
  items: IQorusAllowedValue[];
11
+ canCreateItems?: boolean;
11
12
  onChange: (value: unknown[]) => void;
12
13
  disabled?: boolean;
13
14
  size?: string;
@@ -18,7 +19,7 @@ export interface IMultiSelectFormFieldProps {
18
19
  * Used by FormField for `list` fields that have `element_allowed_values`.
19
20
  */
20
21
  export const MultiSelectFormField = memo(
21
- ({ value = [], items, onChange, disabled, size }: IMultiSelectFormFieldProps) => {
22
+ ({ value = [], items, onChange, disabled, size, canCreateItems }: IMultiSelectFormFieldProps) => {
22
23
  const reqoreItems = useMemo<TReqoreMultiSelectItem[]>(
23
24
  () =>
24
25
  items.map((item) => ({
@@ -39,9 +40,12 @@ export const MultiSelectFormField = memo(
39
40
  <ReqoreMultiSelect
40
41
  items={reqoreItems}
41
42
  value={selectedValues}
42
- onValueChange={(selected) => onChange(selected as unknown[])}
43
+ onValueChange={(selected) => onChange?.(selected as unknown[])}
43
44
  disabled={disabled}
44
45
  size={size as any}
46
+ canCreateItems={canCreateItems}
47
+ enterKeySelects
48
+ canRemoveItems
45
49
  />
46
50
  );
47
51
  }
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  ReqoreButton,
3
- ReqoreControlGroup,
4
3
  ReqoreDropdown,
5
4
  ReqoreMenu,
6
5
  ReqoreMenuItem,
@@ -12,10 +11,7 @@ import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
12
11
  import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
13
12
  import { isEqual, size } from 'lodash';
14
13
  import { memo, useCallback, useEffect, useMemo, useState } from 'react';
15
- import {
16
- ISelectFieldCollectionItem,
17
- SelectFieldCollection,
18
- } from './SelectCollection';
14
+ import { ISelectFieldCollectionItem, SelectFieldCollection } from './SelectCollection';
19
15
 
20
16
  export type ISelectFormFieldItem = ISelectFieldCollectionItem;
21
17
 
@@ -215,14 +211,17 @@ export const SelectFormField = memo(
215
211
 
216
212
  const itemCount: TReqoreBadge = useMemo(
217
213
  () =>
218
- hideItemCount
219
- ? undefined
220
- : {
221
- label: size(items),
222
- align: 'right' as const,
223
- flat: false,
224
- intent: hasError(items) ? 'danger' : hasWarning(items) ? 'warning' : undefined,
225
- },
214
+ hideItemCount ? undefined : (
215
+ {
216
+ label: size(items),
217
+ align: 'right' as const,
218
+ flat: false,
219
+ intent:
220
+ hasError(items) ? 'danger'
221
+ : hasWarning(items) ? 'warning'
222
+ : undefined,
223
+ }
224
+ ),
226
225
  [size(items), hideItemCount]
227
226
  );
228
227
 
@@ -244,16 +243,19 @@ export const SelectFormField = memo(
244
243
  fixed
245
244
  minimal
246
245
  {...getIcon(filteredItems, filteredItems[0].value)}
247
- intent={itemHasError ? 'danger' : itemHasWarning ? 'warning' : 'info'}
246
+ intent={
247
+ itemHasError ? 'danger'
248
+ : itemHasWarning ?
249
+ 'warning'
250
+ : 'info'
251
+ }
248
252
  disabled={false}
249
253
  />
250
254
  );
251
255
  }
252
256
 
253
257
  if (!filteredItems || filteredItems.length === 0) {
254
- return (
255
- <ReqoreTag intent='muted' label='No data available' icon='ForbidLine' fixed />
256
- );
258
+ return <ReqoreTag intent='muted' label='No data available' icon='ForbidLine' fixed />;
257
259
  }
258
260
 
259
261
  return (
@@ -268,7 +270,7 @@ export const SelectFormField = memo(
268
270
  onClose={() => setCollectionOpen(false)}
269
271
  />
270
272
  )}
271
- {asMenu ? (
273
+ {asMenu ?
272
274
  <ReqoreMenu>
273
275
  {filteredItems.map((item) => (
274
276
  <ReqoreMenuItem
@@ -280,43 +282,39 @@ export const SelectFormField = memo(
280
282
  />
281
283
  ))}
282
284
  </ReqoreMenu>
283
- ) : hasItemsWithDesc(items) && !forceDropdown ? (
284
- <ReqoreControlGroup stack fluid={fluid}>
285
- <ReqoreButton
286
- transparent={!value}
287
- minimal
288
- intent={
289
- hasError(items, value)
290
- ? 'danger'
291
- : hasWarning(items, value)
292
- ? 'warning'
293
- : value
294
- ? 'info'
295
- : rest.intent as TReqoreIntent
296
- }
297
- fluid={fluid}
298
- compact
299
- key={valueToShow(value) as string}
300
- badge={itemCount}
301
- {...getIcon(items, value)}
302
- rightIcon={showRightIcon ? 'ExpandUpDownLine' : undefined}
303
- onClick={(e) => {
304
- e.stopPropagation();
305
- setCollectionOpen(true);
306
- }}
307
- description={getItemShortDescription(value as string) as string}
308
- tooltip={rest.tooltip as IReqoreButtonProps['tooltip']}
309
- disabled={disabled}
310
- >
311
- {value
312
- ? getLabel(items, value as string)
313
- : showPlaceholder
314
- ? placeholder || 'Please select'
315
- : undefined}
316
- </ReqoreButton>
317
- </ReqoreControlGroup>
318
- ) : (
319
- <ReqoreDropdown
285
+ : hasItemsWithDesc(items) && !forceDropdown ?
286
+ <ReqoreButton
287
+ transparent={!value}
288
+ minimal
289
+ intent={
290
+ hasError(items, value) ? 'danger'
291
+ : hasWarning(items, value) ?
292
+ 'warning'
293
+ : value ?
294
+ 'info'
295
+ : (rest.intent as TReqoreIntent)
296
+ }
297
+ fluid={fluid}
298
+ compact
299
+ key={valueToShow(value) as string}
300
+ badge={itemCount}
301
+ {...getIcon(items, value)}
302
+ rightIcon={showRightIcon ? 'ExpandUpDownLine' : undefined}
303
+ onClick={(e) => {
304
+ e.stopPropagation();
305
+ setCollectionOpen(true);
306
+ }}
307
+ description={getItemShortDescription(value as string) as string}
308
+ tooltip={rest.tooltip as IReqoreButtonProps['tooltip']}
309
+ disabled={disabled}
310
+ >
311
+ {value ?
312
+ getLabel(items, value as string)
313
+ : showPlaceholder ?
314
+ placeholder || 'Please select'
315
+ : undefined}
316
+ </ReqoreButton>
317
+ : <ReqoreDropdown
320
318
  items={reqoreItems}
321
319
  listCustomTheme={{
322
320
  main: '#010811',
@@ -336,15 +334,20 @@ export const SelectFormField = memo(
336
334
  }}
337
335
  description={getItemShortDescription(value as string) as string}
338
336
  minimal
339
- intent={hasError(items, value) ? 'danger' : value ? 'info' : rest.intent as TReqoreIntent}
337
+ intent={
338
+ hasError(items, value) ? 'danger'
339
+ : value ?
340
+ 'info'
341
+ : (rest.intent as TReqoreIntent)
342
+ }
340
343
  >
341
- {value
342
- ? getLabel(items, value as string)
343
- : showPlaceholder
344
- ? placeholder || 'Please select'
345
- : undefined}
344
+ {value ?
345
+ getLabel(items, value as string)
346
+ : showPlaceholder ?
347
+ placeholder || 'Please select'
348
+ : undefined}
346
349
  </ReqoreDropdown>
347
- )}
350
+ }
348
351
  </>
349
352
  );
350
353
  }
@@ -52,18 +52,18 @@ export const mapQorusTypeToFormFieldType = (type: string): TFormFieldType => {
52
52
  return 'richtext';
53
53
  case 'bool':
54
54
  case 'boolean':
55
- return 'boolean';
55
+ return 'bool';
56
56
  case 'int':
57
57
  case 'integer':
58
58
  case 'float':
59
59
  case 'number':
60
- return 'number';
60
+ return 'int';
61
61
  case 'date':
62
62
  return 'date';
63
63
  case 'file':
64
64
  return 'file';
65
65
  case 'rgbcolor':
66
- return 'color';
66
+ return 'rgbcolor';
67
67
  case 'hash':
68
68
  case 'free-hash':
69
69
  return 'hash';
package/src/types/Form.ts CHANGED
@@ -1,50 +1,18 @@
1
+ import { TQorusType } from '@qoretechnologies/ts-toolkit';
1
2
  import { IColorFormFieldProps } from '../components/form/fields/color/Color';
2
3
 
3
- export type TFormFieldType =
4
- | 'string'
5
- | 'number'
6
- | 'boolean'
7
- | 'date'
8
- | 'time'
9
- | 'datetime'
10
- | 'select'
11
- | 'multiSelect'
12
- | 'radio'
13
- | 'checkbox'
14
- | 'file'
15
- | 'image'
16
- | 'color'
17
- | 'password'
18
- | 'email'
19
- | 'phone'
20
- | 'url'
21
- | 'markdown'
22
- | 'long-string'
23
- | 'cron'
24
- | 'richtext'
25
- | 'hash'
26
- | 'list';
4
+ export type TFormFieldType = TQorusType;
27
5
 
28
6
  export type TFormFieldValueType<T> =
29
- T extends 'string' ? string
30
- : T extends 'number' ? number
31
- : T extends 'boolean' ? boolean
7
+ T extends 'string' | 'long-string' | 'binary' | 'email' | 'url' | 'enum' | 'select-string' | 'file-as-string' ? string
8
+ : T extends 'int' | 'integer' | 'float' | 'number' ? number
9
+ : T extends 'bool' | 'boolean' ? boolean
32
10
  : T extends 'date' ? Date | string
33
- : T extends 'time' ? Date | string
34
- : T extends 'datetime' ? Date | string
35
- : T extends 'select' ? string
36
- : T extends 'multiSelect' ? string[]
37
- : T extends 'radio' ? string
38
- : T extends 'checkbox' ? boolean
11
+ : T extends 'hash' | 'free-hash' | 'data' ? Record<string, any>
12
+ : T extends 'list' | 'free-list' | 'range' ? unknown[]
13
+ : T extends 'rgbcolor' ? IColorFormFieldProps['value']
39
14
  : T extends 'file' ? File
40
- : T extends 'image' ? string
41
- : T extends 'color' ? IColorFormFieldProps['value']
42
- : T extends 'password' ? string
43
- : T extends 'email' ? string
44
- : T extends 'phone' ? string
45
- : T extends 'url' ? string
46
- : T extends 'markdown' ? string
47
- : T extends 'long-string' ? string
48
- : T extends 'cron' ? string
49
15
  : T extends 'richtext' ? string
16
+ : T extends 'auto' | 'any' ? any
17
+ : T extends 'null' | 'nothing' ? null
50
18
  : any;