@qoretechnologies/reqraft 0.10.13 → 0.10.15
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/FocusedEditing/index.d.ts +9 -1
- package/dist/components/FocusedEditing/index.d.ts.map +1 -1
- package/dist/components/FocusedEditing/index.js +2 -2
- package/dist/components/FocusedEditing/index.js.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +15 -8
- 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 +2 -2
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +14 -2
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +15 -20
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +3 -8
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/package.json +2 -2
- package/src/components/FocusedEditing/index.tsx +17 -2
- package/src/components/form/engine/CompactRow.tsx +52 -22
- package/src/components/form/engine/CompactToolbar.tsx +117 -110
- package/src/components/form/engine/FormEngine.stories.tsx +108 -0
- package/src/components/form/engine/FormEngine.tsx +44 -25
- package/src/components/form/engine/compactRowStyles.ts +3 -5
|
@@ -573,6 +573,30 @@ export const CompactRow = memo(
|
|
|
573
573
|
// "descriptions" toggle is engaged (and inside the editor when expanded).
|
|
574
574
|
const showLabelDesc = !!labelShortDesc && showAllDescriptions === true;
|
|
575
575
|
|
|
576
|
+
// The `?` that opens the long-form help dialog. Shared by the read row AND
|
|
577
|
+
// the inline editor: opening a field used to drop it, leaving an editing row
|
|
578
|
+
// with no route to the field's description at all. Stops propagation because
|
|
579
|
+
// both labels are click targets themselves (the row expands / collapses).
|
|
580
|
+
const helpIcon =
|
|
581
|
+
schema?.desc ?
|
|
582
|
+
<ReqoreIcon
|
|
583
|
+
icon='QuestionLine'
|
|
584
|
+
size='12px'
|
|
585
|
+
effect={{ opacity: 0.55 }}
|
|
586
|
+
margin='left'
|
|
587
|
+
marginSize='tiny'
|
|
588
|
+
role='button'
|
|
589
|
+
tabIndex={-1}
|
|
590
|
+
aria-label='Help'
|
|
591
|
+
className='options-readfirst-help'
|
|
592
|
+
style={{ cursor: 'help' }}
|
|
593
|
+
onClick={(event) => {
|
|
594
|
+
event.stopPropagation();
|
|
595
|
+
handleOptionLabelClick(optionName);
|
|
596
|
+
}}
|
|
597
|
+
/>
|
|
598
|
+
: null;
|
|
599
|
+
|
|
576
600
|
const renderInfoStrip = (m: TInfoMsg, index: number) => (
|
|
577
601
|
<ReqoreMessage
|
|
578
602
|
key={`${m.content}-${index}`}
|
|
@@ -688,11 +712,13 @@ export const CompactRow = memo(
|
|
|
688
712
|
>
|
|
689
713
|
{label}
|
|
690
714
|
{required ? <ReqoreIcon icon='Asterisk' color='danger' size='10px' /> : null}
|
|
715
|
+
{helpIcon}
|
|
691
716
|
</StyledRowLabel>
|
|
692
|
-
{/*
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
717
|
+
{/* An OPEN field always shows its short_desc — that's where the hint
|
|
718
|
+
is needed and there's no per-row button to reveal it (the edit
|
|
719
|
+
card does the same). The global descriptions toggle governs READ
|
|
720
|
+
rows only, so collapsing the field is what hides it again. */}
|
|
721
|
+
{labelShortDesc ?
|
|
696
722
|
<StyledLabelDesc
|
|
697
723
|
className='options-readfirst-label-desc'
|
|
698
724
|
size='small'
|
|
@@ -753,6 +779,27 @@ export const CompactRow = memo(
|
|
|
753
779
|
className={clusterBlockClass || undefined}
|
|
754
780
|
>
|
|
755
781
|
{editingRow}
|
|
782
|
+
{/* Focused editing for an INLINE row. The edit card wraps its editor
|
|
783
|
+
in <FocusedEditing>, which renders the field in place AND in the
|
|
784
|
+
modal; an inline row already renders its editor in the row grid,
|
|
785
|
+
so it mounts the modal alone. Without this the More menu's "Edit
|
|
786
|
+
fullscreen" set the state and nothing appeared — the modal simply
|
|
787
|
+
had no mount point on this branch. */}
|
|
788
|
+
{focusedEditing === optionName ?
|
|
789
|
+
<FocusedEditing
|
|
790
|
+
isFullscreen
|
|
791
|
+
modalOnly
|
|
792
|
+
onClose={() => setFocusedEditing(undefined)}
|
|
793
|
+
description={(schema?.display_name as string) || optionName}
|
|
794
|
+
>
|
|
795
|
+
<Description
|
|
796
|
+
longDescription={schema?.desc}
|
|
797
|
+
shortDescription={schema?.short_desc}
|
|
798
|
+
longDescriptionShownByDefault
|
|
799
|
+
/>
|
|
800
|
+
{renderOption(optionName, optionField)}
|
|
801
|
+
</FocusedEditing>
|
|
802
|
+
: null}
|
|
756
803
|
</StyledColumn>
|
|
757
804
|
);
|
|
758
805
|
}
|
|
@@ -1122,24 +1169,7 @@ export const CompactRow = memo(
|
|
|
1122
1169
|
{required ?
|
|
1123
1170
|
<ReqoreIcon icon='Asterisk' color='danger' size='10px' margin='left' marginSize='tiny' />
|
|
1124
1171
|
: null}
|
|
1125
|
-
{
|
|
1126
|
-
<ReqoreIcon
|
|
1127
|
-
icon='QuestionLine'
|
|
1128
|
-
size='12px'
|
|
1129
|
-
effect={{ opacity: 0.55 }}
|
|
1130
|
-
margin='left'
|
|
1131
|
-
marginSize='tiny'
|
|
1132
|
-
role='button'
|
|
1133
|
-
tabIndex={-1}
|
|
1134
|
-
aria-label='Help'
|
|
1135
|
-
className='options-readfirst-help'
|
|
1136
|
-
style={{ cursor: 'help' }}
|
|
1137
|
-
onClick={(event) => {
|
|
1138
|
-
event.stopPropagation();
|
|
1139
|
-
handleOptionLabelClick(optionName);
|
|
1140
|
-
}}
|
|
1141
|
-
/>
|
|
1142
|
-
: null}
|
|
1172
|
+
{helpIcon}
|
|
1143
1173
|
</span>
|
|
1144
1174
|
{typeLabel ?
|
|
1145
1175
|
<ReqoreTag size='tiny' minimal label={typeLabel} labelEffect={{ opacity: 0.55 }} />
|
|
@@ -4,15 +4,15 @@ import {
|
|
|
4
4
|
ReqoreDropdown,
|
|
5
5
|
ReqoreInput,
|
|
6
6
|
} from '@qoretechnologies/reqore';
|
|
7
|
-
import { useReqoreTheme } from '@qoretechnologies/reqore/dist/hooks/useTheme';
|
|
8
7
|
import { IReqoreControlGroupProps } from '@qoretechnologies/reqore/dist/components/ControlGroup';
|
|
9
8
|
import {
|
|
10
9
|
IReqoreDropdownItem,
|
|
11
10
|
TReqoreDropdownItems,
|
|
12
11
|
} from '@qoretechnologies/reqore/dist/components/Dropdown/list';
|
|
12
|
+
import { useReqoreTheme } from '@qoretechnologies/reqore/dist/hooks/useTheme';
|
|
13
13
|
import React, { memo } from 'react';
|
|
14
|
-
import { useContext } from 'use-context-selector';
|
|
15
14
|
import styled from 'styled-components';
|
|
15
|
+
import { useContext } from 'use-context-selector';
|
|
16
16
|
import { CompactToolbarContext, TCompactSort } from './compactToolbarContext';
|
|
17
17
|
|
|
18
18
|
// Field sort modes, listed in the Fields menu's "Sort by" submenu. 'schema' is
|
|
@@ -40,7 +40,13 @@ const StyledCompletionLine = styled.div`
|
|
|
40
40
|
gap: 10px;
|
|
41
41
|
flex-wrap: wrap;
|
|
42
42
|
`;
|
|
43
|
-
const StyledMeter = styled.div<{
|
|
43
|
+
const StyledMeter = styled.div<{
|
|
44
|
+
$set: number;
|
|
45
|
+
$attention: number;
|
|
46
|
+
$track: string;
|
|
47
|
+
$set_c: string;
|
|
48
|
+
$att_c: string;
|
|
49
|
+
}>`
|
|
44
50
|
position: relative;
|
|
45
51
|
height: 4px;
|
|
46
52
|
border-radius: 3px;
|
|
@@ -180,114 +186,115 @@ export const CompactToolbar = memo((reqoreProps: Partial<IReqoreControlGroupProp
|
|
|
180
186
|
{hasMultipleOptions || (invalidCount && !readOnly) ?
|
|
181
187
|
<ReqoreControlGroup vertical fluid gapSize='normal'>
|
|
182
188
|
{hasMultipleOptions ?
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
189
|
+
<ReqoreControlGroup fluid verticalAlign='center'>
|
|
190
|
+
<ReqoreInput
|
|
191
|
+
fluid
|
|
192
|
+
pill
|
|
193
|
+
icon='Search2Line'
|
|
194
|
+
iconColor='muted'
|
|
195
|
+
placeholder='Filter fields...'
|
|
196
|
+
value={compactQuery}
|
|
197
|
+
intent={compactQuery ? 'info' : undefined}
|
|
198
|
+
className='options-readfirst-search'
|
|
199
|
+
onChange={(event: React.FormEvent<HTMLInputElement>) =>
|
|
200
|
+
setCompactQuery(event.currentTarget.value)
|
|
201
|
+
}
|
|
202
|
+
onClearClick={() => setCompactQuery('')}
|
|
203
|
+
/>
|
|
204
|
+
{!readOnly ?
|
|
205
|
+
<ReqoreDropdown
|
|
206
|
+
fixed
|
|
207
|
+
flat
|
|
208
|
+
filterable
|
|
209
|
+
icon='Filter3Line'
|
|
210
|
+
tooltip='Fields'
|
|
211
|
+
className='options-readfirst-fields'
|
|
212
|
+
intent={requiredOnly ? 'info' : undefined}
|
|
213
|
+
badge={requiredOnly ? 'Required only' : undefined}
|
|
214
|
+
onItemSelect={(item: IReqoreDropdownItem) =>
|
|
215
|
+
item.value && onAddOptionalField(item.value)
|
|
216
|
+
}
|
|
217
|
+
items={
|
|
218
|
+
[
|
|
219
|
+
{
|
|
220
|
+
label: 'Required only',
|
|
221
|
+
selected: requiredOnly,
|
|
222
|
+
icon: requiredOnly ? 'CheckboxCircleLine' : 'CheckboxBlankCircleLine',
|
|
223
|
+
tooltip: 'Show only required fields',
|
|
224
|
+
onClick: () => setRequiredOnly((value) => !value),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
label: 'Show field types',
|
|
228
|
+
selected: showFieldTypes,
|
|
229
|
+
icon: showFieldTypes ? 'CheckboxCircleLine' : 'CheckboxBlankCircleLine',
|
|
230
|
+
tooltip: 'Annotate each field with its type',
|
|
231
|
+
onClick: onToggleFieldTypes,
|
|
232
|
+
},
|
|
233
|
+
// Sort by — a submenu (collapsed by default) so the five modes
|
|
234
|
+
// don't crowd the Fields menu. Reorders fields WITHIN each group
|
|
235
|
+
// (groups + required-group rails preserved); the active non-
|
|
236
|
+
// default mode shows as a badge on the parent.
|
|
237
|
+
{
|
|
238
|
+
label: 'Sort by',
|
|
239
|
+
icon: 'ArrowUpDownLine',
|
|
240
|
+
intent: compactSort !== 'schema' ? 'info' : undefined,
|
|
241
|
+
badge:
|
|
242
|
+
compactSort !== 'schema' ?
|
|
243
|
+
SORT_MODES.find((mode) => mode.value === compactSort)?.label
|
|
244
|
+
: undefined,
|
|
245
|
+
items: SORT_MODES.map((mode) => ({
|
|
246
|
+
label: mode.label,
|
|
247
|
+
tooltip: mode.tooltip,
|
|
248
|
+
selected: compactSort === mode.value,
|
|
249
|
+
icon:
|
|
250
|
+
compactSort === mode.value ?
|
|
251
|
+
'CheckboxCircleLine'
|
|
252
|
+
: 'CheckboxBlankCircleLine',
|
|
253
|
+
onClick: () => setCompactSort(mode.value),
|
|
254
|
+
})),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
label: 'Select all',
|
|
258
|
+
icon: 'MenuAddLine',
|
|
259
|
+
tooltip: 'Add every optional field',
|
|
260
|
+
disabled: filteredCount === 0,
|
|
261
|
+
onClick: onAddAll,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
label: 'Default fields',
|
|
265
|
+
icon: 'RestartLine',
|
|
266
|
+
tooltip: 'Reset to the default set of fields',
|
|
267
|
+
onClick: onResetDefaults,
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
label: 'Revert all changes',
|
|
271
|
+
icon: 'HistoryLine',
|
|
272
|
+
tooltip: 'Undo all edits back to the loaded values',
|
|
273
|
+
disabled: !canRevert,
|
|
274
|
+
onClick: onRevertAll,
|
|
275
|
+
},
|
|
276
|
+
// The individual not-yet-added fields are no longer listed here —
|
|
277
|
+
// they render as addable rows in the Optional box instead. The
|
|
278
|
+
// menu keeps only the bulk actions above (Select all / Default
|
|
279
|
+
// fields / filters).
|
|
280
|
+
] as TReqoreDropdownItems
|
|
281
|
+
}
|
|
282
|
+
/>
|
|
283
|
+
: null}
|
|
284
|
+
<ReqoreButton
|
|
285
|
+
fixed
|
|
286
|
+
flat
|
|
287
|
+
minimal={showAllDescriptions === true}
|
|
288
|
+
icon={showAllDescriptions ? 'InformationFill' : 'InformationLine'}
|
|
289
|
+
className='options-readfirst-descriptions'
|
|
290
|
+
intent={showAllDescriptions ? 'info' : undefined}
|
|
291
|
+
tooltip={
|
|
292
|
+
showAllDescriptions ? 'Hide all field information' : 'Show all field information'
|
|
293
|
+
}
|
|
294
|
+
onClick={onToggleAllDescriptions}
|
|
295
|
+
/>
|
|
296
|
+
</ReqoreControlGroup>
|
|
277
297
|
: null}
|
|
278
|
-
<ReqoreButton
|
|
279
|
-
fixed
|
|
280
|
-
flat
|
|
281
|
-
minimal={showAllDescriptions === true}
|
|
282
|
-
icon={showAllDescriptions ? 'InformationFill' : 'InformationLine'}
|
|
283
|
-
className='options-readfirst-descriptions'
|
|
284
|
-
active={showAllDescriptions}
|
|
285
|
-
intent={showAllDescriptions ? 'info' : undefined}
|
|
286
|
-
tooltip={showAllDescriptions ? 'Hide all field information' : 'Show all field information'}
|
|
287
|
-
onClick={onToggleAllDescriptions}
|
|
288
|
-
/>
|
|
289
|
-
</ReqoreControlGroup>
|
|
290
|
-
: null}
|
|
291
298
|
</ReqoreControlGroup>
|
|
292
299
|
: null}
|
|
293
300
|
</ReqoreControlGroup>
|
|
@@ -1680,6 +1680,114 @@ export const Compact: Story = {
|
|
|
1680
1680
|
},
|
|
1681
1681
|
};
|
|
1682
1682
|
|
|
1683
|
+
// `name` gains a long-form `desc` so the `?` help affordance has something to
|
|
1684
|
+
// open — the rest of the compact fixture is unchanged.
|
|
1685
|
+
const CompactHelpSchema: Record<string, TCompactField> = {
|
|
1686
|
+
...CompactSchema,
|
|
1687
|
+
name: {
|
|
1688
|
+
...CompactSchema.name,
|
|
1689
|
+
desc: 'The unique identifier for this interface. It is used in URLs, logs and cross-references, so it cannot be changed once the interface is deployed.',
|
|
1690
|
+
},
|
|
1691
|
+
};
|
|
1692
|
+
|
|
1693
|
+
export const CompactFocusedEditingInline: Story = {
|
|
1694
|
+
parameters: {
|
|
1695
|
+
docs: {
|
|
1696
|
+
description: {
|
|
1697
|
+
story:
|
|
1698
|
+
'Renders the compact form, opens the Name row for inline editing and picks "Edit fullscreen" from its More menu — the Focused Editing modal opens over that single scalar field with its long description above the editor.',
|
|
1699
|
+
},
|
|
1700
|
+
},
|
|
1701
|
+
chromatic: { disable: true },
|
|
1702
|
+
},
|
|
1703
|
+
args: {
|
|
1704
|
+
...Compact.args,
|
|
1705
|
+
options: CompactHelpSchema,
|
|
1706
|
+
},
|
|
1707
|
+
play: async () => {
|
|
1708
|
+
// Name is a scalar, so it edits INLINE in the row (no expanded card) — the
|
|
1709
|
+
// branch whose More menu used to set the focused state with nothing mounted
|
|
1710
|
+
// to render the modal. (CompactFocusedEditing covers the card branch.)
|
|
1711
|
+
await _testsClickText('order-fulfilment');
|
|
1712
|
+
await _testsWaitForInputValue('order-fulfilment', '.options-readfirst-inline .reqore-textarea');
|
|
1713
|
+
await _testsClickButton({ selector: '.options-readfirst-more' });
|
|
1714
|
+
let fsItem: Element | undefined;
|
|
1715
|
+
await waitFor(
|
|
1716
|
+
() => {
|
|
1717
|
+
fsItem = Array.from(document.querySelectorAll('.reqore-menu-item')).find((element) =>
|
|
1718
|
+
element.textContent?.includes('Edit fullscreen')
|
|
1719
|
+
);
|
|
1720
|
+
expect(fsItem).toBeTruthy();
|
|
1721
|
+
},
|
|
1722
|
+
{ timeout: 10000 }
|
|
1723
|
+
);
|
|
1724
|
+
await fireEvent.click(fsItem as Element);
|
|
1725
|
+
// The modal mounts, carrying the field's long description with it.
|
|
1726
|
+
await waitFor(() => expect(document.querySelector('.reqore-modal')).toBeTruthy(), {
|
|
1727
|
+
timeout: 10000,
|
|
1728
|
+
});
|
|
1729
|
+
await _testsWaitForText(/unique identifier for this interface/i);
|
|
1730
|
+
},
|
|
1731
|
+
};
|
|
1732
|
+
|
|
1733
|
+
export const CompactEditingShowsDescription: Story = {
|
|
1734
|
+
parameters: {
|
|
1735
|
+
docs: {
|
|
1736
|
+
description: {
|
|
1737
|
+
story:
|
|
1738
|
+
"Renders the compact form with the descriptions toggle off — a collapsed row hides its short description, and opening the row for editing reveals it under the field name along with the `?` help affordance.",
|
|
1739
|
+
},
|
|
1740
|
+
},
|
|
1741
|
+
},
|
|
1742
|
+
args: {
|
|
1743
|
+
...Compact.args,
|
|
1744
|
+
options: CompactHelpSchema,
|
|
1745
|
+
},
|
|
1746
|
+
play: async () => {
|
|
1747
|
+
// Collapsed: the global descriptions toggle is off, so no short_desc on the
|
|
1748
|
+
// read row (the label carries it as a title attribute only).
|
|
1749
|
+
await _testsWaitForText('order-fulfilment');
|
|
1750
|
+
await _testsWaitForTextToNotExist('Unique identifier for this interface');
|
|
1751
|
+
// Open it: the short description appears without the user clicking anything…
|
|
1752
|
+
await _testsClickText('order-fulfilment');
|
|
1753
|
+
await _testsWaitForInputValue('order-fulfilment', '.options-readfirst-inline .reqore-textarea');
|
|
1754
|
+
await _testsWaitForText('Unique identifier for this interface');
|
|
1755
|
+
// …and the `?` (long-form help) stays reachable while editing.
|
|
1756
|
+
await waitFor(() =>
|
|
1757
|
+
expect(
|
|
1758
|
+
document.querySelector('.options-readfirst-inline .options-readfirst-help')
|
|
1759
|
+
).toBeTruthy()
|
|
1760
|
+
);
|
|
1761
|
+
},
|
|
1762
|
+
};
|
|
1763
|
+
|
|
1764
|
+
export const CompactWithPanelProps: Story = {
|
|
1765
|
+
parameters: {
|
|
1766
|
+
docs: {
|
|
1767
|
+
description: {
|
|
1768
|
+
story:
|
|
1769
|
+
"Renders the Compact fixture with `compactPanelProps` — the read-first form's outer ReqorePanel is dressed from the outside with a label, icon, intent and an extra header action. The engine's own toolbar survives: `actions` append after it and `contentStyle` merges over the engine's flex-column layout instead of replacing either.",
|
|
1770
|
+
},
|
|
1771
|
+
},
|
|
1772
|
+
},
|
|
1773
|
+
args: {
|
|
1774
|
+
...Compact.args,
|
|
1775
|
+
compactPanelProps: {
|
|
1776
|
+
label: 'Connection settings',
|
|
1777
|
+
icon: 'Settings3Line',
|
|
1778
|
+
intent: 'info',
|
|
1779
|
+
actions: [{ label: 'Docs', icon: 'BookLine', responsive: false }],
|
|
1780
|
+
},
|
|
1781
|
+
},
|
|
1782
|
+
play: async () => {
|
|
1783
|
+
// The outside-supplied panel chrome renders…
|
|
1784
|
+
await _testsWaitForText('Connection settings');
|
|
1785
|
+
await _testsWaitForText('Docs');
|
|
1786
|
+
// …and the engine's own toolbar + rows are untouched by it.
|
|
1787
|
+
await _testsWaitForText('order-fulfilment');
|
|
1788
|
+
},
|
|
1789
|
+
};
|
|
1790
|
+
|
|
1683
1791
|
export const CompactReadOnly: Story = {
|
|
1684
1792
|
parameters: {
|
|
1685
1793
|
docs: {
|
|
@@ -21,13 +21,13 @@ import {
|
|
|
21
21
|
} from '@qoretechnologies/reqore/dist/components/Panel';
|
|
22
22
|
import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
23
23
|
import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
|
|
24
|
-
import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
25
24
|
import {
|
|
26
25
|
changeDarkness,
|
|
27
26
|
getMainBackgroundColor,
|
|
28
27
|
getReadableColor,
|
|
29
28
|
percentToHexAlpha,
|
|
30
29
|
} from '@qoretechnologies/reqore/dist/helpers/colors';
|
|
30
|
+
import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
31
31
|
import {
|
|
32
32
|
IQorusFormField,
|
|
33
33
|
IQorusFormFieldMessage,
|
|
@@ -48,11 +48,10 @@ import reduce from 'lodash/reduce';
|
|
|
48
48
|
import size from 'lodash/size';
|
|
49
49
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
50
50
|
import { useMeasure, useMount, useUpdateEffect } from 'react-use';
|
|
51
|
-
import { createContext } from 'use-context-selector';
|
|
52
51
|
import styled from 'styled-components';
|
|
52
|
+
import { createContext } from 'use-context-selector';
|
|
53
53
|
import { getDefaultValue, insertAtIndex, richtextToString } from '../../../helpers/common';
|
|
54
54
|
import { getRequiredOptionMessage } from '../../../helpers/options';
|
|
55
|
-
import { query } from '../../../utils/fetch';
|
|
56
55
|
import {
|
|
57
56
|
IValidationResult,
|
|
58
57
|
hasAllDependenciesFullfilled,
|
|
@@ -61,6 +60,7 @@ import {
|
|
|
61
60
|
} from '../../../helpers/validations';
|
|
62
61
|
import { useQorusTypes } from '../../../hooks/useQorusTypes';
|
|
63
62
|
import { useTemplates } from '../../../hooks/useTemplates';
|
|
63
|
+
import { query } from '../../../utils/fetch';
|
|
64
64
|
import { Description } from '../../Description';
|
|
65
65
|
import { FocusedEditing } from '../../FocusedEditing';
|
|
66
66
|
import { SelectFormField } from '../fields/select/Select';
|
|
@@ -72,12 +72,6 @@ import {
|
|
|
72
72
|
} from '../fields/template/TemplateField';
|
|
73
73
|
import { CompactRow } from './CompactRow';
|
|
74
74
|
import { CompactRowContext, ICompactRowContext } from './compactRowContext';
|
|
75
|
-
import { CompactToolbar } from './CompactToolbar';
|
|
76
|
-
import {
|
|
77
|
-
CompactToolbarContext,
|
|
78
|
-
ICompactToolbarContext,
|
|
79
|
-
TCompactSort,
|
|
80
|
-
} from './compactToolbarContext';
|
|
81
75
|
import {
|
|
82
76
|
GROUP_INDENT,
|
|
83
77
|
LABEL_COL_MAX,
|
|
@@ -91,9 +85,16 @@ import {
|
|
|
91
85
|
StyledStatusBox,
|
|
92
86
|
StyledStatusBoxGroupLabel,
|
|
93
87
|
} from './compactRowStyles';
|
|
88
|
+
import { CompactToolbar } from './CompactToolbar';
|
|
89
|
+
import {
|
|
90
|
+
CompactToolbarContext,
|
|
91
|
+
ICompactToolbarContext,
|
|
92
|
+
TCompactSort,
|
|
93
|
+
} from './compactToolbarContext';
|
|
94
94
|
import { OptionFieldMessages } from './OptionFieldMessages';
|
|
95
95
|
import { OptionsHelpDialog } from './OptionsHelpDialog';
|
|
96
96
|
import {
|
|
97
|
+
TReadFirstStatus,
|
|
97
98
|
getFirstAttentionOptionName,
|
|
98
99
|
getOptionGroup,
|
|
99
100
|
getOptionGroupLabel,
|
|
@@ -101,7 +102,6 @@ import {
|
|
|
101
102
|
getReadFirstCompletion,
|
|
102
103
|
getReadFirstStatus,
|
|
103
104
|
isOptionValueEmpty,
|
|
104
|
-
TReadFirstStatus,
|
|
105
105
|
} from './readFirst';
|
|
106
106
|
|
|
107
107
|
// Re-export types for consumers
|
|
@@ -201,13 +201,6 @@ const StyledCompactWrap = styled.div<{ $flush?: boolean }>`
|
|
|
201
201
|
engage instead of overflowing the container horizontally. */
|
|
202
202
|
min-width: 0;
|
|
203
203
|
max-width: 100%;
|
|
204
|
-
/* A bit of horizontal breathing room for the whole form (header + content
|
|
205
|
-
alike). Horizontal only — top padding would break the sticky toolbar's
|
|
206
|
-
flush pin (see the scroll-context note below). Dropped to flush via the
|
|
207
|
-
compactFlush prop, for embeds that own their own gutters (e.g. the
|
|
208
|
-
SchemaDefinition tab body). */
|
|
209
|
-
padding: ${({ $flush }) => ($flush ? '0' : '0 12px')};
|
|
210
|
-
|
|
211
204
|
/* Own our scroll context instead of borrowing the host's. The sticky toolbar
|
|
212
205
|
pins to whatever scrolls; if that scroller carries top padding (e.g. a
|
|
213
206
|
ReqorePanel/ReqoreContent body), sticky \`top: 0\` resolves against its
|
|
@@ -525,6 +518,18 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
525
518
|
* transparently inside the parent's edit card. Default `false`.
|
|
526
519
|
*/
|
|
527
520
|
compactNested?: boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Compact mode only: props forwarded to the read-first form's outer
|
|
523
|
+
* `ReqorePanel` — the panel that carries the sticky toolbar header and holds
|
|
524
|
+
* the status boxes. Spread AFTER the engine's own defaults, so anything set
|
|
525
|
+
* here wins (`flat`, `raised`, `minimal`, `stickyHeader`, `label`, `icon`,
|
|
526
|
+
* `intent`, `padded`, `size`, …). Two props merge instead of replacing, so a
|
|
527
|
+
* consumer can add to the panel without dismantling the form:
|
|
528
|
+
* - `actions` — appended after the engine's toolbar action.
|
|
529
|
+
* - `contentStyle` — merged over the engine's flex-column layout.
|
|
530
|
+
* No-op in classic (non-compact) mode.
|
|
531
|
+
*/
|
|
532
|
+
compactPanelProps?: IReqorePanelProps;
|
|
528
533
|
/** Compact mode only: per-group display metadata (label / icon / subtitle /
|
|
529
534
|
* order) — the server only sends the bare group key. */
|
|
530
535
|
groups?: Record<string, IFormEngineGroup>;
|
|
@@ -609,6 +614,7 @@ export const FormEngine = ({
|
|
|
609
614
|
compact,
|
|
610
615
|
compactFlush = false,
|
|
611
616
|
compactNested = false,
|
|
617
|
+
compactPanelProps,
|
|
612
618
|
commitMode = 'immediate',
|
|
613
619
|
expandMode = 'single',
|
|
614
620
|
onCommit,
|
|
@@ -1709,8 +1715,13 @@ export const FormEngine = ({
|
|
|
1709
1715
|
<>
|
|
1710
1716
|
{(() => {
|
|
1711
1717
|
const schemaMsgs = (
|
|
1712
|
-
suppressSchemaMessages ?
|
|
1713
|
-
|
|
1718
|
+
suppressSchemaMessages ?
|
|
1719
|
+
[]
|
|
1720
|
+
: (options?.[optionName] as any)?.messages || []) as {
|
|
1721
|
+
intent?: string;
|
|
1722
|
+
title?: string;
|
|
1723
|
+
content?: string;
|
|
1724
|
+
}[];
|
|
1714
1725
|
if (!schemaMsgs.length) return null;
|
|
1715
1726
|
const items = schemaMsgs.map(({ intent, title, content }, index) => (
|
|
1716
1727
|
<ReqoreMessage
|
|
@@ -2114,7 +2125,13 @@ export const FormEngine = ({
|
|
|
2114
2125
|
]
|
|
2115
2126
|
);
|
|
2116
2127
|
|
|
2117
|
-
|
|
2128
|
+
// The engine's own toolbar action, plus whatever the consumer added through
|
|
2129
|
+
// `compactPanelProps.actions` — appended, so an outside action can never
|
|
2130
|
+
// knock the form's toolbar out of the header.
|
|
2131
|
+
const compactHeaderActions = useMemo(
|
|
2132
|
+
() => [{ as: CompactToolbar, responsive: false }, ...(compactPanelProps?.actions || [])],
|
|
2133
|
+
[compactPanelProps?.actions]
|
|
2134
|
+
);
|
|
2118
2135
|
const renderCompact = () => {
|
|
2119
2136
|
const headerBg = `${changeDarkness(getMainBackgroundColor(theme), 0.02)}${percentToHexAlpha(88)}`;
|
|
2120
2137
|
// Toolbar filters narrow the listed rows; the meter reflects the full set.
|
|
@@ -2357,22 +2374,24 @@ export const FormEngine = ({
|
|
|
2357
2374
|
$headerBg={compactNested ? 'transparent' : headerBg}
|
|
2358
2375
|
$nested={compactNested}
|
|
2359
2376
|
flat
|
|
2377
|
+
raised
|
|
2378
|
+
minimal
|
|
2360
2379
|
// No panel background: the form sits transparently on whatever
|
|
2361
2380
|
// hosts it (page, drawer, or — for an arg_schema field — the
|
|
2362
2381
|
// parent's edit card) instead of stacking its own dark surface.
|
|
2363
2382
|
// The status boxes keep their own tints; the sticky toolbar keeps
|
|
2364
2383
|
// its blurred header via the $headerBg override.
|
|
2365
|
-
transparent
|
|
2366
2384
|
stickyHeader={!compactNested}
|
|
2367
|
-
|
|
2385
|
+
// Consumer overrides land last so they win over every default
|
|
2386
|
+
// above; `actions` and `contentStyle` below merge rather than
|
|
2387
|
+
// replace (see `compactPanelProps`).
|
|
2388
|
+
{...compactPanelProps}
|
|
2368
2389
|
actions={compactHeaderActions}
|
|
2369
2390
|
contentStyle={{
|
|
2370
2391
|
display: 'flex',
|
|
2371
2392
|
flexFlow: 'column',
|
|
2372
2393
|
gap: '10px',
|
|
2373
|
-
|
|
2374
|
-
// the parent card); top-level keeps a small bottom gutter.
|
|
2375
|
-
padding: compactNested ? '0' : '0 0 12px',
|
|
2394
|
+
...compactPanelProps?.contentStyle,
|
|
2376
2395
|
}}
|
|
2377
2396
|
>
|
|
2378
2397
|
{size(groupKeys) === 0 ?
|