@qoretechnologies/reqraft 0.10.65 → 0.10.66-pr.145.g124fc92
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.
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +130 -25
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +78 -15
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +48 -1
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +260 -99
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.d.ts +18 -2
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +22 -3
- package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js +27 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
- package/dist/components/form/engine/compactRowContext.d.ts +5 -0
- package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowContext.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts +12 -0
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +72 -39
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +3 -0
- package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.js.map +1 -1
- package/dist/components/form/engine/formAppearance.d.ts +48 -0
- package/dist/components/form/engine/formAppearance.d.ts.map +1 -0
- package/dist/components/form/engine/formAppearance.js +50 -0
- package/dist/components/form/engine/formAppearance.js.map +1 -0
- package/dist/components/form/engine/readFirst.d.ts +14 -2
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +6 -2
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rowFocus.d.ts +9 -1
- package/dist/components/form/engine/rowFocus.d.ts.map +1 -1
- package/dist/components/form/engine/rowFocus.js +17 -5
- package/dist/components/form/engine/rowFocus.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +21 -0
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/fields/ChoiceGroupHeading.d.ts +21 -0
- package/dist/components/form/fields/ChoiceGroupHeading.d.ts.map +1 -0
- package/dist/components/form/fields/ChoiceGroupHeading.js +33 -0
- package/dist/components/form/fields/ChoiceGroupHeading.js.map +1 -0
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +13 -11
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +20 -7
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/allowed-values/savedValues.d.ts +30 -0
- package/dist/components/form/fields/allowed-values/savedValues.d.ts.map +1 -0
- package/dist/components/form/fields/allowed-values/savedValues.js +18 -0
- package/dist/components/form/fields/allowed-values/savedValues.js.map +1 -0
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +18 -8
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/long-string/LongString.d.ts +7 -1
- package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
- package/dist/components/form/fields/long-string/LongString.js +9 -5
- package/dist/components/form/fields/long-string/LongString.js.map +1 -1
- package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
- package/dist/components/form/fields/multi-select/MultiSelectFormField.js +6 -5
- package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
- package/dist/components/form/fields/number/Number.d.ts +4 -1
- package/dist/components/form/fields/number/Number.d.ts.map +1 -1
- package/dist/components/form/fields/number/Number.js +13 -5
- package/dist/components/form/fields/number/Number.js.map +1 -1
- package/dist/components/form/fields/radio-group/RadioGroup.d.ts.map +1 -1
- package/dist/components/form/fields/radio-group/RadioGroup.js +13 -7
- package/dist/components/form/fields/radio-group/RadioGroup.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +71 -42
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +2 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/imageLightbox/ImageLightbox.d.ts.map +1 -1
- package/dist/components/imageLightbox/ImageLightbox.js +2 -0
- package/dist/components/imageLightbox/ImageLightbox.js.map +1 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +3 -2
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.d.ts +2 -1
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -1
- package/dist/components/supportTicket/ReferencePicker.js +19 -13
- package/dist/components/supportTicket/ReferencePicker.js.map +1 -1
- package/dist/components/supportTicket/TicketHeader.d.ts.map +1 -1
- package/dist/components/supportTicket/TicketHeader.js +4 -1
- package/dist/components/supportTicket/TicketHeader.js.map +1 -1
- package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -1
- package/dist/components/supportTicket/TicketReplyBox.js +29 -10
- package/dist/components/supportTicket/TicketReplyBox.js.map +1 -1
- package/dist/components/supportTicket/useLoadedImages.d.ts +10 -0
- package/dist/components/supportTicket/useLoadedImages.d.ts.map +1 -1
- package/dist/components/supportTicket/useLoadedImages.js +63 -1
- package/dist/components/supportTicket/useLoadedImages.js.map +1 -1
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -1
- package/dist/components/ticketReferences/TicketReferences.js +6 -2
- package/dist/components/ticketReferences/TicketReferences.js.map +1 -1
- package/dist/components/ticketThread/TicketThread.d.ts.map +1 -1
- package/dist/components/ticketThread/TicketThread.js +10 -16
- package/dist/components/ticketThread/TicketThread.js.map +1 -1
- package/dist/helpers/allowedValues.d.ts +12 -0
- package/dist/helpers/allowedValues.d.ts.map +1 -1
- package/dist/helpers/allowedValues.js +20 -1
- package/dist/helpers/allowedValues.js.map +1 -1
- package/dist/helpers/fieldDescription.d.ts +16 -0
- package/dist/helpers/fieldDescription.d.ts.map +1 -0
- package/dist/helpers/fieldDescription.js +40 -0
- package/dist/helpers/fieldDescription.js.map +1 -0
- package/dist/helpers/singleLineString.d.ts.map +1 -1
- package/dist/helpers/singleLineString.js +1 -0
- package/dist/helpers/singleLineString.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/engine/CompactRow.tsx +143 -23
- package/src/components/form/engine/CompactToolbar.tsx +85 -16
- package/src/components/form/engine/FormEngine.stories.tsx +25 -13
- package/src/components/form/engine/FormEngine.tsx +367 -81
- package/src/components/form/engine/FormEngineAppearance.stories.tsx +540 -0
- package/src/components/form/engine/FormEngineStickyHeaders.stories.tsx +5 -1
- package/src/components/form/engine/OptionFieldMessages.tsx +73 -2
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +17 -4
- package/src/components/form/engine/compactRowContext.ts +5 -0
- package/src/components/form/engine/compactRowStyles.ts +218 -38
- package/src/components/form/engine/compactToolbarContext.ts +3 -0
- package/src/components/form/engine/formAppearance.ts +78 -0
- package/src/components/form/engine/readFirst.ts +16 -3
- package/src/components/form/engine/rowFocus.ts +17 -6
- package/src/components/form/expressions/renderExpressionToText.ts +15 -0
- package/src/components/form/fields/ChoiceGroupHeading.tsx +38 -0
- package/src/components/form/fields/Field.tsx +11 -9
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +21 -9
- package/src/components/form/fields/allowed-values/savedValues.ts +41 -0
- package/src/components/form/fields/auto/AutoFormField.tsx +17 -4
- package/src/components/form/fields/long-string/LongString.stories.tsx +27 -0
- package/src/components/form/fields/long-string/LongString.tsx +14 -3
- package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +5 -4
- package/src/components/form/fields/number/Number.tsx +19 -4
- package/src/components/form/fields/radio-group/RadioGroup.tsx +13 -7
- package/src/components/form/fields/template/TemplateField.tsx +32 -4
- package/src/components/form/index.tsx +2 -0
- package/src/components/imageLightbox/ImageLightbox.tsx +2 -0
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +3 -2
- package/src/components/supportTicket/ReferencePicker.stories.tsx +0 -4
- package/src/components/supportTicket/ReferencePicker.tsx +22 -12
- package/src/components/supportTicket/TicketConversation.stories.tsx +0 -1
- package/src/components/supportTicket/TicketHeader.tsx +2 -1
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +24 -21
- package/src/components/supportTicket/TicketReplyBox.tsx +27 -5
- package/src/components/supportTicket/useLoadedImages.ts +20 -0
- package/src/components/ticketReferences/TicketReferences.stories.tsx +26 -0
- package/src/components/ticketReferences/TicketReferences.tsx +12 -4
- package/src/components/ticketThread/TicketThread.stories.tsx +71 -2
- package/src/components/ticketThread/TicketThread.tsx +15 -28
- package/src/helpers/allowedValues.ts +19 -0
- package/src/helpers/fieldDescription.ts +39 -0
- package/src/helpers/singleLineString.ts +1 -0
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
IReqoreLabelProps,
|
|
3
3
|
ReqoreCheckbox,
|
|
4
4
|
ReqoreControlGroup,
|
|
5
|
-
ReqoreSpan,
|
|
6
5
|
} from '@qoretechnologies/reqore';
|
|
7
6
|
import { IWithReqoreSize } from '@qoretechnologies/reqore/dist/types/global';
|
|
8
7
|
import { TReqoreBadge } from '@qoretechnologies/reqore/dist/components/Button';
|
|
@@ -34,6 +33,7 @@ import {
|
|
|
34
33
|
UNAVAILABLE_ITEM_CLASS,
|
|
35
34
|
} from './select/SelectCollection';
|
|
36
35
|
import { useAllowedValueAvailability } from '../engine/OptionFieldMessages';
|
|
36
|
+
import { ChoiceGroupHeading, useSelectedChoiceIntent } from './ChoiceGroupHeading';
|
|
37
37
|
import { StringFormField } from './string/String';
|
|
38
38
|
import { ArrayAutoField } from './array/ArrayAutoField';
|
|
39
39
|
import { AutoFormField } from './auto/AutoFormField';
|
|
@@ -46,6 +46,7 @@ import { IDataSchemaDefinition } from './schema-definition/types';
|
|
|
46
46
|
// because modules are all loaded before any component renders
|
|
47
47
|
import { FormEngine } from '../engine/FormEngine';
|
|
48
48
|
import { FormFieldsSkeleton } from '../engine/FormFieldsSkeleton';
|
|
49
|
+
import { allowedValueOf } from '../../../helpers/allowedValues';
|
|
49
50
|
|
|
50
51
|
/** Soft Qore types (softint, softstring, …) render as their concrete base —
|
|
51
52
|
* the soft/hard distinction is value-level, not UI. */
|
|
@@ -152,6 +153,7 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
152
153
|
...rest
|
|
153
154
|
}: IFormFieldProps<T>) => {
|
|
154
155
|
const id = useId();
|
|
156
|
+
const selectedChoiceIntent = useSelectedChoiceIntent();
|
|
155
157
|
const { schema: finalArgSchema, loading: argSchemaLoading } = useArgSchema(arg_schema);
|
|
156
158
|
|
|
157
159
|
const handleChange = (value: TFormFieldValueType<T>, event?: unknown) => {
|
|
@@ -415,7 +417,7 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
415
417
|
if (size(element_allowed_values) && !element_allowed_values_creatable) {
|
|
416
418
|
const formatToServer = (vals: unknown[]) =>
|
|
417
419
|
(vals || []).map((v) => {
|
|
418
|
-
const match = element_allowed_values.find((ev) => isEqual(ev
|
|
420
|
+
const match = element_allowed_values.find((ev) => isEqual(allowedValueOf(ev), v));
|
|
419
421
|
return { value: v, type: match?.value?.type ?? effectiveElementType };
|
|
420
422
|
});
|
|
421
423
|
const formatFromServer = (vals: unknown[]) =>
|
|
@@ -426,7 +428,7 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
426
428
|
return (
|
|
427
429
|
<ReqoreControlGroup vertical size='tiny'>
|
|
428
430
|
{element_allowed_values.map((item, index) => {
|
|
429
|
-
const itemValue = item
|
|
431
|
+
const itemValue = allowedValueOf(item);
|
|
430
432
|
const checked = serverValue.some((v) => isEqual(v, itemValue));
|
|
431
433
|
return (
|
|
432
434
|
<ReqoreCheckbox
|
|
@@ -435,7 +437,7 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
435
437
|
label={item.display_name ?? String(itemValue)}
|
|
436
438
|
tooltip={item.short_desc || item.desc}
|
|
437
439
|
checked={checked}
|
|
438
|
-
intent={checked
|
|
440
|
+
intent={selectedChoiceIntent(checked)}
|
|
439
441
|
onClick={() => {
|
|
440
442
|
const next =
|
|
441
443
|
checked ?
|
|
@@ -590,7 +592,7 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
590
592
|
resolved && !resolved.available ?
|
|
591
593
|
{ title: resolved.title, content: resolved.reason as string, intent: resolved.intent }
|
|
592
594
|
: undefined,
|
|
593
|
-
value: item
|
|
595
|
+
value: allowedValueOf(item),
|
|
594
596
|
};
|
|
595
597
|
};
|
|
596
598
|
|
|
@@ -653,13 +655,13 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
653
655
|
if (allowed_values.length <= 3) {
|
|
654
656
|
return (
|
|
655
657
|
<ReqoreControlGroup vertical size='tiny'>
|
|
656
|
-
<
|
|
658
|
+
<ChoiceGroupHeading>
|
|
657
659
|
{/* Headed by the field's own name; "Select one:" only when it has
|
|
658
660
|
none. Same rule as FieldAllowedValuesCheckGroup. */}
|
|
659
661
|
{display_name ? `${display_name}:` : 'Select one:'}
|
|
660
|
-
</
|
|
662
|
+
</ChoiceGroupHeading>
|
|
661
663
|
{allowed_values.map((item, index) => {
|
|
662
|
-
const itemValue = item
|
|
664
|
+
const itemValue = allowedValueOf(item);
|
|
663
665
|
const checked = isEqual(value, itemValue);
|
|
664
666
|
const mapped = mapAllowedValue(item, index);
|
|
665
667
|
const unavailable = getSelectItemUnavailability(mapped);
|
|
@@ -695,7 +697,7 @@ export const FormField = <T extends TFormFieldType>({
|
|
|
695
697
|
readOnly={!!unavailable}
|
|
696
698
|
uncheckedIcon={unavailable ? 'LockLine' : undefined}
|
|
697
699
|
checked={checked}
|
|
698
|
-
intent={checked
|
|
700
|
+
intent={selectedChoiceIntent(checked)}
|
|
699
701
|
onClick={
|
|
700
702
|
unavailable ? undefined : (
|
|
701
703
|
() => handleChange((checked ? undefined : itemValue) as TFormFieldValueType<T>)
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// (FIELD_STACK_REPORT batch). Markup, props and the debounced local-value
|
|
3
3
|
// flow are preserved. Seams (IDE-only infrastructure, same classes as the
|
|
4
4
|
// ExpressionBuilder port):
|
|
5
|
-
// - `useSavedValues` — saved-values storage is
|
|
6
|
-
//
|
|
5
|
+
// - `useSavedValues` — saved-values storage is the host's; a field that shows
|
|
6
|
+
// saved values reads them from `SavedValuesContext` (see `savedValues.ts`).
|
|
7
7
|
// - `useSubscriptionEvents` (CONNECTION_DELETED pruning) — dropped.
|
|
8
8
|
// - `ConnectionManagement` per-item actions — dropped (actions omitted).
|
|
9
9
|
// - `Select` → reqraft `SelectFormField` (`items` instead of `defaultItems`,
|
|
10
10
|
// single-arg `onChange`; `type`/`closeCollectionOnUnMount` not forwarded).
|
|
11
|
-
import { ReqoreCheckbox, ReqoreControlGroup
|
|
11
|
+
import { ReqoreCheckbox, ReqoreControlGroup } from '@qoretechnologies/reqore';
|
|
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';
|
|
@@ -20,7 +20,10 @@ import {
|
|
|
20
20
|
} from '../select/SelectCollection';
|
|
21
21
|
import { ISelectFormFieldItem, SelectFormField as Select } from '../select/Select';
|
|
22
22
|
import { useAllowedValueAvailability } from '../../engine/OptionFieldMessages';
|
|
23
|
+
import { ChoiceGroupHeading, useSelectedChoiceIntent } from '../ChoiceGroupHeading';
|
|
23
24
|
import type { IReqraftAllowedValue } from '../Field';
|
|
25
|
+
import { allowedValueOf } from '../../../../helpers/allowedValues';
|
|
26
|
+
import { useSavedValueChoices } from './savedValues';
|
|
24
27
|
|
|
25
28
|
export interface IFieldAllowedValuesProps extends Pick<
|
|
26
29
|
IReqoreButtonProps,
|
|
@@ -97,13 +100,15 @@ export const FieldAllowedValuesCheckGroup = memo(
|
|
|
97
100
|
*/
|
|
98
101
|
label?: string;
|
|
99
102
|
}) => {
|
|
103
|
+
const selectedChoiceIntent = useSelectedChoiceIntent();
|
|
104
|
+
|
|
100
105
|
return (
|
|
101
106
|
<ReqoreControlGroup vertical size={rest.size || 'small'} gapSize='tiny'>
|
|
102
|
-
<
|
|
107
|
+
<ChoiceGroupHeading>
|
|
103
108
|
{label ? `${label}:`
|
|
104
109
|
: multiSelect ? 'Select one or more:'
|
|
105
110
|
: 'Select one:'}
|
|
106
|
-
</
|
|
111
|
+
</ChoiceGroupHeading>
|
|
107
112
|
{items?.map((item) => {
|
|
108
113
|
const unavailable = getSelectItemUnavailability(item);
|
|
109
114
|
|
|
@@ -134,7 +139,7 @@ export const FieldAllowedValuesCheckGroup = memo(
|
|
|
134
139
|
which keeps the pointer events the tooltip is read through.
|
|
135
140
|
It does not withhold `onClick` — the handler below does. */
|
|
136
141
|
readOnly={rest.readOnly || !!unavailable}
|
|
137
|
-
intent={item.value === value
|
|
142
|
+
intent={selectedChoiceIntent(item.value === value)}
|
|
138
143
|
// `isEqual`, and only once there is a value to match. The single
|
|
139
144
|
// -select branch compared JSON.stringify(value) with
|
|
140
145
|
// JSON.stringify(item.value), and JSON.stringify(undefined) is
|
|
@@ -194,6 +199,8 @@ export const FieldAllowedValues = memo(
|
|
|
194
199
|
`depends_on`, and a refusal the server had already decided. Both end up
|
|
195
200
|
as one state on the item: no click handler, and a reason to read. */
|
|
196
201
|
const availability = useAllowedValueAvailability(items as IReqraftAllowedValue[]);
|
|
202
|
+
const savedValues = useSavedValueChoices(type);
|
|
203
|
+
const offersSavedValues = !!showSavedValues && !readOnly && !disabled && count(savedValues) > 0;
|
|
197
204
|
|
|
198
205
|
const fullItems = useMemo(() => {
|
|
199
206
|
const result = [
|
|
@@ -202,7 +209,7 @@ export const FieldAllowedValues = memo(
|
|
|
202
209
|
const resolved = availability[index];
|
|
203
210
|
|
|
204
211
|
return {
|
|
205
|
-
value: value
|
|
212
|
+
value: allowedValueOf({ value, name: rest.name }),
|
|
206
213
|
...rest,
|
|
207
214
|
disabled: rest.disabled || resolved?.available === false,
|
|
208
215
|
unavailable:
|
|
@@ -217,12 +224,17 @@ export const FieldAllowedValues = memo(
|
|
|
217
224
|
}),
|
|
218
225
|
] as ISelectFormFieldItem[];
|
|
219
226
|
|
|
227
|
+
if (offersSavedValues) {
|
|
228
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
229
|
+
result.push(...savedValues.map(({ valueType, ...saved }) => saved));
|
|
230
|
+
}
|
|
231
|
+
|
|
220
232
|
return result;
|
|
221
|
-
}, [JSON.stringify(items), availability, type, value,
|
|
233
|
+
}, [JSON.stringify(items), availability, type, value, offersSavedValues, savedValues]);
|
|
222
234
|
|
|
223
235
|
const style = useMemo(() => ({ width: '100%' }), []);
|
|
224
236
|
|
|
225
|
-
if (!count(fullItems) || type === 'enum' || (!count(items) && !
|
|
237
|
+
if (!count(fullItems) || type === 'enum' || (!count(items) && !offersSavedValues)) {
|
|
226
238
|
return null;
|
|
227
239
|
}
|
|
228
240
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import { ISelectFormFieldItem } from '../select/Select';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SEAM (reqraft): values a person saved to reuse in other fields.
|
|
6
|
+
*
|
|
7
|
+
* Where they are kept is the host's business — the IDE stores them in the
|
|
8
|
+
* user's settings — so this package draws them and asks the host to save one;
|
|
9
|
+
* it never stores anything itself. Without a provider a form offers neither,
|
|
10
|
+
* which is what it did before the seam existed (`showSavedValues` and
|
|
11
|
+
* `allowSaving` were inert).
|
|
12
|
+
*/
|
|
13
|
+
export interface ISavedValueChoice extends ISelectFormFieldItem {
|
|
14
|
+
/** The type of the saved value: a field offers only the values of its own type. */
|
|
15
|
+
valueType?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ISavedValues {
|
|
19
|
+
/** Every saved value, drawn as choices by a field that shows saved values. */
|
|
20
|
+
items: ISavedValueChoice[];
|
|
21
|
+
/** Saves a field's current value; the host asks what to call it. */
|
|
22
|
+
saveValue?: (field: { value: unknown; type?: string }) => void;
|
|
23
|
+
/** The label of the row-menu item that saves a value. */
|
|
24
|
+
saveLabel?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const SavedValuesContext = createContext<ISavedValues | undefined>(undefined);
|
|
28
|
+
|
|
29
|
+
const NO_SAVED_VALUES: ISavedValueChoice[] = [];
|
|
30
|
+
|
|
31
|
+
/** The saved values a field of `type` can choose from. */
|
|
32
|
+
export const useSavedValueChoices = (type?: string): ISavedValueChoice[] => {
|
|
33
|
+
const items = useContext(SavedValuesContext)?.items;
|
|
34
|
+
return useMemo(
|
|
35
|
+
() =>
|
|
36
|
+
!items ? NO_SAVED_VALUES
|
|
37
|
+
: type ? items.filter((item) => !item.valueType || item.valueType === type)
|
|
38
|
+
: items,
|
|
39
|
+
[items, type]
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -17,6 +17,7 @@ import React, { memo, useCallback, useEffect, useState } from 'react';
|
|
|
17
17
|
import { useUpdateEffect } from 'react-use';
|
|
18
18
|
import useMount from 'react-use/lib/useMount';
|
|
19
19
|
import { typedToYaml, yamlToTyped } from '../../../../helpers/common';
|
|
20
|
+
import { allowedValueOf } from '../../../../helpers/allowedValues';
|
|
20
21
|
import { getListElementValue } from '../../../../helpers/options';
|
|
21
22
|
import { isSingleLineStringType } from '../../../../helpers/singleLineString';
|
|
22
23
|
import {
|
|
@@ -658,9 +659,21 @@ function AutoField<T = any>({
|
|
|
658
659
|
Without the type the field cannot tell them apart and treats them all
|
|
659
660
|
as documents, so an interface's Internal Name — which becomes a YAML
|
|
660
661
|
key — accepted Enter. Only text takes references; `data` and
|
|
661
|
-
`binary` are encoded content, where a chip would stand for nothing.
|
|
662
|
+
`binary` are encoded content, where a chip would stand for nothing.
|
|
663
|
+
|
|
664
|
+
The semantic string formats (`email` … `phone`) are one line of text
|
|
665
|
+
whose identity the validator checks. They are in the option vocabulary
|
|
666
|
+
(`optionUiTypes`) and the single-line list, but had no case here, so a
|
|
667
|
+
field of one drew "Unknown type!" in every form. */
|
|
662
668
|
case 'string':
|
|
663
669
|
case 'long-string':
|
|
670
|
+
case 'email':
|
|
671
|
+
case 'uri':
|
|
672
|
+
case 'uuid':
|
|
673
|
+
case 'hostname':
|
|
674
|
+
case 'ipv4':
|
|
675
|
+
case 'ipv6':
|
|
676
|
+
case 'phone':
|
|
664
677
|
return renderTextEditor(currentType);
|
|
665
678
|
case 'data':
|
|
666
679
|
case 'binary':
|
|
@@ -974,8 +987,8 @@ function AutoField<T = any>({
|
|
|
974
987
|
// previously only `allowed_values` was read, leaving `items` empty.
|
|
975
988
|
const enumItems = (rest.allowed_values as any[] | undefined)?.length
|
|
976
989
|
? (rest.allowed_values as any[]).map((av) => ({
|
|
977
|
-
label: av.display_name ?? String(av
|
|
978
|
-
value: av
|
|
990
|
+
label: av.display_name ?? String(allowedValueOf(av)),
|
|
991
|
+
value: allowedValueOf(av),
|
|
979
992
|
image: av.image,
|
|
980
993
|
icon: av.icon,
|
|
981
994
|
}))
|
|
@@ -1008,7 +1021,7 @@ function AutoField<T = any>({
|
|
|
1008
1021
|
<SelectFormField
|
|
1009
1022
|
items={(rest.allowed_values || []).map((av) => ({
|
|
1010
1023
|
...av,
|
|
1011
|
-
value: av
|
|
1024
|
+
value: allowedValueOf(av),
|
|
1012
1025
|
}))}
|
|
1013
1026
|
value={value}
|
|
1014
1027
|
onChange={(val) => handleChange(name, val)}
|
|
@@ -118,3 +118,30 @@ export const Disabled: Story = {
|
|
|
118
118
|
await expect(textarea).toHaveValue('Cannot edit this');
|
|
119
119
|
},
|
|
120
120
|
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `debounceMs={0}` passes every change on at once. A form submitted by Enter (a Qonsole
|
|
124
|
+
* question) reads the value the moment the key goes down; with the usual wait it would
|
|
125
|
+
* submit the text as it was before the last few keys.
|
|
126
|
+
*/
|
|
127
|
+
export const PassedOnAtOnce: Story = {
|
|
128
|
+
args: {
|
|
129
|
+
debounceMs: 0,
|
|
130
|
+
},
|
|
131
|
+
parameters: {
|
|
132
|
+
docs: {
|
|
133
|
+
description: {
|
|
134
|
+
story:
|
|
135
|
+
'Renders the LongString field with no wait before a change is passed on, so each key reaches the form immediately — what a form submitted by Enter needs.',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
async play({ canvasElement, args }) {
|
|
140
|
+
const canvas = within(canvasElement);
|
|
141
|
+
const textarea = canvas.getByLabelText('LongString');
|
|
142
|
+
|
|
143
|
+
await userEvent.type(textarea, 'abc');
|
|
144
|
+
// no waitFor: the last key is already with the form
|
|
145
|
+
await expect(args.onChange).toHaveBeenLastCalledWith('abc');
|
|
146
|
+
},
|
|
147
|
+
};
|
|
@@ -20,6 +20,12 @@ export interface ILongStringFormFieldProps extends Omit<IReqoreTextareaProps, 'o
|
|
|
20
20
|
* names and URLs with a newline in them. See `helpers/singleLineString`.
|
|
21
21
|
*/
|
|
22
22
|
type?: string;
|
|
23
|
+
/**
|
|
24
|
+
* How long the text waits after the last key before it is passed on (ms). `0` passes
|
|
25
|
+
* every change on at once: a form submitted by Enter — a Qonsole question — must see
|
|
26
|
+
* the last thing typed, not the text from 100 ms earlier.
|
|
27
|
+
*/
|
|
28
|
+
debounceMs?: number;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export const LongStringFormField = ({
|
|
@@ -27,6 +33,7 @@ export const LongStringFormField = ({
|
|
|
27
33
|
onChange,
|
|
28
34
|
onClearClick,
|
|
29
35
|
type,
|
|
36
|
+
debounceMs = 100,
|
|
30
37
|
...rest
|
|
31
38
|
}: ILongStringFormFieldProps) => {
|
|
32
39
|
const [localValue, setLocalValue] = useState<string>(value ?? '');
|
|
@@ -72,7 +79,7 @@ export const LongStringFormField = ({
|
|
|
72
79
|
emit(localValue);
|
|
73
80
|
}
|
|
74
81
|
},
|
|
75
|
-
|
|
82
|
+
debounceMs,
|
|
76
83
|
[localValue, emit]
|
|
77
84
|
);
|
|
78
85
|
|
|
@@ -84,9 +91,13 @@ export const LongStringFormField = ({
|
|
|
84
91
|
// Flatten here rather than only blocking the Enter key: a line break also
|
|
85
92
|
// arrives by paste, by drag-and-drop, from autofill and from an IME, and
|
|
86
93
|
// a keydown guard catches none of those.
|
|
87
|
-
|
|
94
|
+
const next = singleLine && hasLineBreak(raw) ? flattenToSingleLine(raw) : raw;
|
|
95
|
+
setLocalValue(next);
|
|
96
|
+
if (debounceMs === 0 && next !== lastEmittedRef.current) {
|
|
97
|
+
emit(next);
|
|
98
|
+
}
|
|
88
99
|
},
|
|
89
|
-
[singleLine]
|
|
100
|
+
[singleLine, debounceMs, emit]
|
|
90
101
|
);
|
|
91
102
|
|
|
92
103
|
// Stop Enter before it inserts anything, so the caret does not jump to a
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getSelectItemUnavailability,
|
|
8
8
|
UNAVAILABLE_ITEM_CLASS,
|
|
9
9
|
} from '../select/SelectCollection';
|
|
10
|
+
import { allowedValueOf } from '../../../../helpers/allowedValues';
|
|
10
11
|
|
|
11
12
|
export interface IMultiSelectFormFieldProps {
|
|
12
13
|
/** Currently selected values (plain scalars) */
|
|
@@ -55,7 +56,7 @@ export const MultiSelectFormField = memo(
|
|
|
55
56
|
messages: item.messages,
|
|
56
57
|
})
|
|
57
58
|
)
|
|
58
|
-
.map((item) => String(item
|
|
59
|
+
.map((item) => String(allowedValueOf(item) ?? ''))
|
|
59
60
|
),
|
|
60
61
|
[JSON.stringify(items)]
|
|
61
62
|
);
|
|
@@ -75,8 +76,8 @@ export const MultiSelectFormField = memo(
|
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
return {
|
|
78
|
-
value: item
|
|
79
|
-
label: item.display_name ?? String(item
|
|
79
|
+
value: allowedValueOf(item) as string,
|
|
80
|
+
label: item.display_name ?? String(allowedValueOf(item) ?? ''),
|
|
80
81
|
description:
|
|
81
82
|
unavailable ?
|
|
82
83
|
[unavailable.title, unavailable.content, description].filter(Boolean).join(' — ')
|
|
@@ -91,7 +92,7 @@ export const MultiSelectFormField = memo(
|
|
|
91
92
|
That one stays reqore's `disabled` — the wildcard is a state of
|
|
92
93
|
the SELECTION rather than a refusal of the value, and it has no
|
|
93
94
|
reason of its own to protect. */
|
|
94
|
-
disabled: selectedValues.includes('*') && item
|
|
95
|
+
disabled: selectedValues.includes('*') && allowedValueOf(item) !== '*',
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
);
|
|
@@ -25,6 +25,9 @@ export interface INumberFormFieldProps extends Omit<IReqoreInputProps, 'value' |
|
|
|
25
25
|
*/
|
|
26
26
|
min_value?: number;
|
|
27
27
|
max_value?: number;
|
|
28
|
+
/** How long a typed number waits after the last key before it is passed on (ms); `0`
|
|
29
|
+
* passes every change on at once, for a form submitted by Enter. */
|
|
30
|
+
debounceMs?: number;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/** A whole number as typed: an optional sign and digits. */
|
|
@@ -51,6 +54,7 @@ export const NumberFormField = ({
|
|
|
51
54
|
templates,
|
|
52
55
|
min_value,
|
|
53
56
|
max_value,
|
|
57
|
+
debounceMs = 100,
|
|
54
58
|
...rest
|
|
55
59
|
}: INumberFormFieldProps) => {
|
|
56
60
|
// What is shown is what was typed; what the form gets is the number it writes, or the text when it writes
|
|
@@ -77,13 +81,24 @@ export const NumberFormField = ({
|
|
|
77
81
|
onChange?.(localValue);
|
|
78
82
|
}
|
|
79
83
|
},
|
|
80
|
-
|
|
84
|
+
debounceMs,
|
|
81
85
|
[localValue, onChange]
|
|
82
86
|
);
|
|
83
87
|
|
|
84
|
-
const handleChange = useCallback(
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
const handleChange = useCallback(
|
|
89
|
+
(rawValue: number | string): void => {
|
|
90
|
+
const nextText = String(rawValue);
|
|
91
|
+
setText(nextText);
|
|
92
|
+
if (debounceMs === 0) {
|
|
93
|
+
const next = nextText === '' ? '' : typedNumber(nextText, type);
|
|
94
|
+
if (next !== value) {
|
|
95
|
+
emitted.record(next);
|
|
96
|
+
onChange?.(next);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[debounceMs, type, value, onChange]
|
|
101
|
+
);
|
|
87
102
|
|
|
88
103
|
const handleInputChange = useCallback(
|
|
89
104
|
(event: ChangeEvent<HTMLInputElement>): void => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReqoreRadioGroup } from '@qoretechnologies/reqore';
|
|
2
2
|
import { IReqoreRadioGroupProps } from '@qoretechnologies/reqore/dist/components/RadioGroup';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
|
+
import { useIsCalmForm } from '../../engine/formAppearance';
|
|
4
5
|
|
|
5
6
|
export interface IRadioGroupFormFieldProps
|
|
6
7
|
extends Omit<IReqoreRadioGroupProps, 'onChange' | 'onSelectClick' | 'selected'> {
|
|
@@ -16,20 +17,25 @@ export const RadioGroupFormField = ({
|
|
|
16
17
|
value,
|
|
17
18
|
...rest
|
|
18
19
|
}: IRadioGroupFormFieldProps) => {
|
|
20
|
+
const calm = useIsCalmForm();
|
|
19
21
|
const items: IReqoreRadioGroupProps['items'] = useMemo(
|
|
20
22
|
() =>
|
|
21
23
|
_items.map((item) => ({
|
|
22
24
|
margin: 'right',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
// Calm: an option's name as written — a choice is read, not shouted.
|
|
26
|
+
labelEffect:
|
|
27
|
+
calm ?
|
|
28
|
+
{ textSize: 'small' }
|
|
29
|
+
: {
|
|
30
|
+
spaced: 1,
|
|
31
|
+
weight: 'bold',
|
|
32
|
+
uppercase: true,
|
|
33
|
+
textSize: 'small',
|
|
34
|
+
},
|
|
29
35
|
...item,
|
|
30
36
|
disabled: disabled || item.disabled,
|
|
31
37
|
})),
|
|
32
|
-
[_items]
|
|
38
|
+
[_items, calm]
|
|
33
39
|
);
|
|
34
40
|
|
|
35
41
|
return (
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
30
30
|
import { IQorusFormFieldSchemaBase, TQorusType } from '@qoretechnologies/ts-toolkit';
|
|
31
31
|
import { size } from 'lodash';
|
|
32
|
-
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
32
|
+
import React, { memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
33
33
|
import { useUpdateEffect } from 'react-use';
|
|
34
34
|
import {
|
|
35
35
|
filterTemplatesByType as templatesFilterFunc,
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
useRowMenu,
|
|
74
74
|
useRowMenuPublisher,
|
|
75
75
|
} from '../../engine/rowMenuContext';
|
|
76
|
+
import { SavedValuesContext } from '../allowed-values/savedValues';
|
|
76
77
|
|
|
77
78
|
// Re-export template utilities for consumers
|
|
78
79
|
export { getTemplateKey, getTemplateValue, isValueTemplate };
|
|
@@ -919,9 +920,22 @@ const TemplateFieldImpl = memo(
|
|
|
919
920
|
[name, onChange]
|
|
920
921
|
);
|
|
921
922
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
923
|
+
/* Saving a value for reuse goes to the HOST, which keeps saved values and
|
|
924
|
+
asks what to call this one (`SavedValuesContext`). Offered where a value
|
|
925
|
+
can be typed in — a field of fixed choices has nothing of its own to
|
|
926
|
+
save, and neither does a yes/no — and only once there is a value. */
|
|
927
|
+
const savedValues = useContext(SavedValuesContext);
|
|
928
|
+
const canSaveValue =
|
|
929
|
+
!!rest.allowSaving &&
|
|
930
|
+
!!savedValues?.saveValue &&
|
|
931
|
+
(!rest.allowed_values || !!rest.allowed_values_creatable) &&
|
|
932
|
+
type !== 'bool' &&
|
|
933
|
+
type !== 'boolean' &&
|
|
934
|
+
!rest.readonly &&
|
|
935
|
+
!rest.disabled &&
|
|
936
|
+
value !== undefined &&
|
|
937
|
+
value !== null &&
|
|
938
|
+
value !== '';
|
|
925
939
|
|
|
926
940
|
const handleRemoveTemplateClick = useCallback(() => {
|
|
927
941
|
/* This is the `×` ON the template control, and it means "I do not want a
|
|
@@ -1218,6 +1232,15 @@ const TemplateFieldImpl = memo(
|
|
|
1218
1232
|
},
|
|
1219
1233
|
]
|
|
1220
1234
|
: []),
|
|
1235
|
+
...(canSaveValue
|
|
1236
|
+
? [
|
|
1237
|
+
{
|
|
1238
|
+
label: savedValues?.saveLabel ?? 'Save value',
|
|
1239
|
+
icon: 'SaveLine' as const,
|
|
1240
|
+
onClick: () => savedValues?.saveValue?.({ value, type }),
|
|
1241
|
+
},
|
|
1242
|
+
]
|
|
1243
|
+
: []),
|
|
1221
1244
|
/* The "set a value of this type" choices an untyped field offers.
|
|
1222
1245
|
Dividers are dropped: they grouped items in a menu this field drew
|
|
1223
1246
|
itself, and in the row's shared menu they would divide other
|
|
@@ -1243,6 +1266,10 @@ const TemplateFieldImpl = memo(
|
|
|
1243
1266
|
handleSelectFunctionChange,
|
|
1244
1267
|
handleTemplateToggleClick,
|
|
1245
1268
|
handleUseCustomValueClick,
|
|
1269
|
+
canSaveValue,
|
|
1270
|
+
savedValues,
|
|
1271
|
+
value,
|
|
1272
|
+
type,
|
|
1246
1273
|
]
|
|
1247
1274
|
);
|
|
1248
1275
|
|
|
@@ -1253,6 +1280,7 @@ const TemplateFieldImpl = memo(
|
|
|
1253
1280
|
canOfferExpression && !functions.loading ? 'expression' : '',
|
|
1254
1281
|
canOfferTemplate ? 'template' : '',
|
|
1255
1282
|
canOfferCustomValue ? 'custom-value' : '',
|
|
1283
|
+
canSaveValue ? 'save-value' : '',
|
|
1256
1284
|
`custom:${((menuItems ?? []) as { label?: unknown }[]).map((item) => String(item.label ?? '')).join('|')}`,
|
|
1257
1285
|
].join(',');
|
|
1258
1286
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './fields/Field';
|
|
2
2
|
export * from './fields/allowed-values/AllowedValues';
|
|
3
|
+
export * from './fields/allowed-values/savedValues';
|
|
3
4
|
export * from './fields/array/ArrayAuto';
|
|
4
5
|
export * from './fields/array/ArrayAutoField';
|
|
5
6
|
export * from './fields/auto/AutoFormField';
|
|
@@ -24,6 +25,7 @@ export * from './fields/string/String';
|
|
|
24
25
|
export * from './fields/template/ExampleValueModal';
|
|
25
26
|
export * from './fields/template/TemplateField';
|
|
26
27
|
export * from './engine/FormEngine';
|
|
28
|
+
export * from './engine/formAppearance';
|
|
27
29
|
export * from './engine/FormFieldsSkeleton';
|
|
28
30
|
export * from './engine/optionActions';
|
|
29
31
|
export * from './engine/rendererTypes';
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '@qoretechnologies/reqore';
|
|
8
8
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
9
9
|
import styled from 'styled-components';
|
|
10
|
+
import { loadedImageUrl } from '../supportTicket/useLoadedImages';
|
|
10
11
|
|
|
11
12
|
/*
|
|
12
13
|
* The shared full-image viewer: a `ReqoreModal` holding one image at a time, with
|
|
@@ -121,6 +122,7 @@ export const ImageLightbox = ({
|
|
|
121
122
|
}
|
|
122
123
|
requested.current.add(id);
|
|
123
124
|
fetchRef.current(id)
|
|
125
|
+
.then(loadedImageUrl)
|
|
124
126
|
.then((resolved) => mounted.current && setUrls((current) => ({ ...current, [id]: resolved })))
|
|
125
127
|
.catch(() => mounted.current && setUrls((current) => ({ ...current, [id]: null })));
|
|
126
128
|
return undefined;
|
|
@@ -31,8 +31,9 @@ export interface IInterfaceReferenceTagsProps {
|
|
|
31
31
|
*/
|
|
32
32
|
onRemove?: (reference: IInterfaceReference) => void;
|
|
33
33
|
size?: TSizes;
|
|
34
|
-
/** Chip styling
|
|
35
|
-
*
|
|
34
|
+
/** Chip styling. Left out, a reference is a quiet tag with its kind's icon, as the
|
|
35
|
+
* support surfaces draw it; tint it only where the colour has a job (Qonsole's purple
|
|
36
|
+
* is Qonsole's alone — instruction-files decision 002). */
|
|
36
37
|
intent?: ComponentProps<typeof ReqoreTag>['intent'];
|
|
37
38
|
customTheme?: ComponentProps<typeof ReqoreTag>['customTheme'];
|
|
38
39
|
/** Resolve a per-reference image — e.g. a qog's trigger-app logo, the way the
|
|
@@ -29,8 +29,6 @@ const meta = {
|
|
|
29
29
|
export default meta;
|
|
30
30
|
type Story = StoryObj<typeof meta>;
|
|
31
31
|
|
|
32
|
-
const ACCENT = { main: 'custom1' } as const;
|
|
33
|
-
|
|
34
32
|
/* The kind ids a host passes — wire values, not labels. `qog` is the customer-facing
|
|
35
33
|
* kind for what the instance still lists as `fsm`; hosts map that at the fetch
|
|
36
34
|
* boundary, so `fsm` never reaches the picker. */
|
|
@@ -143,7 +141,6 @@ const Hosted = ({
|
|
|
143
141
|
their own remove — and read a size up from the thread's, where they're
|
|
144
142
|
a record rather than a control. */
|
|
145
143
|
<InterfaceReferenceTags
|
|
146
|
-
customTheme={ACCENT}
|
|
147
144
|
references={picked}
|
|
148
145
|
onRemove={(reference) =>
|
|
149
146
|
setPicked((current) =>
|
|
@@ -167,7 +164,6 @@ const Hosted = ({
|
|
|
167
164
|
flat
|
|
168
165
|
badge={picked.length || undefined}
|
|
169
166
|
active={open}
|
|
170
|
-
customTheme={ACCENT}
|
|
171
167
|
tooltip={open ? 'Hide references' : 'Reference interfaces'}
|
|
172
168
|
onClick={() => setOpen((shown) => !shown)}
|
|
173
169
|
/>
|