@qoretechnologies/reqraft 0.10.25 → 0.10.26
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/.claude/CLAUDE.md +52 -23
- package/design/COMPACT_ENGINE_REDESIGN.md +156 -0
- package/design/FORM_ENGINE_COMPACT_UX_PLAN.md +353 -0
- 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/composer/Composer.d.ts +108 -0
- package/dist/components/composer/Composer.d.ts.map +1 -0
- package/dist/components/composer/Composer.js +238 -0
- package/dist/components/composer/Composer.js.map +1 -0
- package/dist/components/composer/helpers.d.ts +21 -0
- package/dist/components/composer/helpers.d.ts.map +1 -0
- package/dist/components/composer/helpers.js +75 -0
- package/dist/components/composer/helpers.js.map +1 -0
- package/dist/components/composer/index.d.ts +3 -0
- package/dist/components/composer/index.d.ts.map +1 -0
- package/dist/components/composer/index.js +19 -0
- package/dist/components/composer/index.js.map +1 -0
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +307 -110
- 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 +123 -106
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +107 -11
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +618 -179
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/compactRowContext.d.ts +9 -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 +7 -3
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +88 -51
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +1 -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/optionActions.d.ts +28 -0
- package/dist/components/form/engine/optionActions.d.ts.map +1 -0
- package/dist/components/form/engine/optionActions.js +18 -0
- package/dist/components/form/engine/optionActions.js.map +1 -0
- package/dist/components/form/engine/readFirst.d.ts +63 -2
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +121 -6
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rendererTypes.d.ts +35 -0
- package/dist/components/form/engine/rendererTypes.d.ts.map +1 -0
- package/dist/components/form/engine/rendererTypes.js +63 -0
- package/dist/components/form/engine/rendererTypes.js.map +1 -0
- package/dist/components/form/engine/variants/VariantCalmTable.d.ts +6 -0
- package/dist/components/form/engine/variants/VariantCalmTable.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantCalmTable.js +94 -0
- package/dist/components/form/engine/variants/VariantCalmTable.js.map +1 -0
- package/dist/components/form/engine/variants/VariantCards.d.ts +6 -0
- package/dist/components/form/engine/variants/VariantCards.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantCards.js +80 -0
- package/dist/components/form/engine/variants/VariantCards.js.map +1 -0
- package/dist/components/form/engine/variants/VariantFocus.d.ts +7 -0
- package/dist/components/form/engine/variants/VariantFocus.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantFocus.js +138 -0
- package/dist/components/form/engine/variants/VariantFocus.js.map +1 -0
- package/dist/components/form/engine/variants/VariantMinimal.d.ts +6 -0
- package/dist/components/form/engine/variants/VariantMinimal.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantMinimal.js +73 -0
- package/dist/components/form/engine/variants/VariantMinimal.js.map +1 -0
- package/dist/components/form/engine/variants/focusDemo.d.ts +13 -0
- package/dist/components/form/engine/variants/focusDemo.d.ts.map +1 -0
- package/dist/components/form/engine/variants/focusDemo.js +139 -0
- package/dist/components/form/engine/variants/focusDemo.js.map +1 -0
- package/dist/components/form/engine/variants/variantModel.d.ts +70 -0
- package/dist/components/form/engine/variants/variantModel.d.ts.map +1 -0
- package/dist/components/form/engine/variants/variantModel.js +133 -0
- package/dist/components/form/engine/variants/variantModel.js.map +1 -0
- package/dist/components/form/engine/variants/variantParts.d.ts +79 -0
- package/dist/components/form/engine/variants/variantParts.d.ts.map +1 -0
- package/dist/components/form/engine/variants/variantParts.js +191 -0
- package/dist/components/form/engine/variants/variantParts.js.map +1 -0
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +13 -2
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/useExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/useExpressions.js +4 -1
- package/dist/components/form/expressions/useExpressions.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +5 -3
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +2 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +8 -26
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts +3 -0
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +42 -8
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/binary/Binary.d.ts +15 -0
- package/dist/components/form/fields/binary/Binary.d.ts.map +1 -0
- package/dist/components/form/fields/binary/Binary.js +74 -0
- package/dist/components/form/fields/binary/Binary.js.map +1 -0
- package/dist/components/form/fields/boolean/Boolean.js +1 -1
- package/dist/components/form/fields/boolean/Boolean.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 +5 -4
- package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
- package/dist/components/form/fields/select/Select.d.ts.map +1 -1
- package/dist/components/form/fields/select/Select.js +29 -12
- package/dist/components/form/fields/select/Select.js.map +1 -1
- package/dist/components/form/fields/select/SelectCollection.d.ts +8 -2
- package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
- package/dist/components/form/fields/select/SelectCollection.js +52 -16
- package/dist/components/form/fields/select/SelectCollection.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 +21 -0
- package/dist/components/imageLightbox/ImageLightbox.d.ts.map +1 -0
- package/dist/components/imageLightbox/ImageLightbox.js +169 -0
- package/dist/components/imageLightbox/ImageLightbox.js.map +1 -0
- package/dist/components/imageLightbox/index.d.ts +2 -0
- package/dist/components/imageLightbox/index.d.ts.map +1 -0
- package/dist/components/imageLightbox/index.js +18 -0
- package/dist/components/imageLightbox/index.js.map +1 -0
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
- package/dist/components/searchFilterBar/index.d.ts +2 -0
- package/dist/components/searchFilterBar/index.d.ts.map +1 -0
- package/dist/components/searchFilterBar/index.js +18 -0
- package/dist/components/searchFilterBar/index.js.map +1 -0
- package/dist/components/supportTicket/AttachmentChips.d.ts +10 -0
- package/dist/components/supportTicket/AttachmentChips.d.ts.map +1 -0
- package/dist/components/supportTicket/AttachmentChips.js +122 -0
- package/dist/components/supportTicket/AttachmentChips.js.map +1 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +43 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.js +31 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -0
- package/dist/components/supportTicket/ReferencePicker.d.ts +87 -0
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -0
- package/dist/components/supportTicket/ReferencePicker.js +295 -0
- package/dist/components/supportTicket/ReferencePicker.js.map +1 -0
- package/dist/components/supportTicket/TicketHeader.d.ts +86 -0
- package/dist/components/supportTicket/TicketHeader.d.ts.map +1 -0
- package/dist/components/supportTicket/TicketHeader.js +149 -0
- package/dist/components/supportTicket/TicketHeader.js.map +1 -0
- package/dist/components/supportTicket/TicketMetaTags.d.ts +32 -0
- package/dist/components/supportTicket/TicketMetaTags.d.ts.map +1 -0
- package/dist/components/supportTicket/TicketMetaTags.js +21 -0
- package/dist/components/supportTicket/TicketMetaTags.js.map +1 -0
- package/dist/components/supportTicket/TicketReplyBox.d.ts +63 -0
- package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -0
- package/dist/components/supportTicket/TicketReplyBox.js +115 -0
- package/dist/components/supportTicket/TicketReplyBox.js.map +1 -0
- package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
- package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
- package/dist/components/supportTicket/attachmentAccess.js +132 -0
- package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
- package/dist/components/supportTicket/fileToAttachment.d.ts +9 -0
- package/dist/components/supportTicket/fileToAttachment.d.ts.map +1 -0
- package/dist/components/supportTicket/fileToAttachment.js +28 -0
- package/dist/components/supportTicket/fileToAttachment.js.map +1 -0
- package/dist/components/supportTicket/index.d.ts +11 -0
- package/dist/components/supportTicket/index.d.ts.map +1 -0
- package/dist/components/supportTicket/index.js +27 -0
- package/dist/components/supportTicket/index.js.map +1 -0
- package/dist/components/supportTicket/meta.d.ts +71 -0
- package/dist/components/supportTicket/meta.d.ts.map +1 -0
- package/dist/components/supportTicket/meta.js +115 -0
- package/dist/components/supportTicket/meta.js.map +1 -0
- package/dist/components/supportTicket/ticketModel.d.ts +102 -0
- package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
- package/dist/components/supportTicket/ticketModel.js +3 -0
- package/dist/components/supportTicket/ticketModel.js.map +1 -0
- package/dist/components/ticketReferences/TicketReferences.d.ts +52 -0
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -0
- package/dist/components/ticketReferences/TicketReferences.js +492 -0
- package/dist/components/ticketReferences/TicketReferences.js.map +1 -0
- package/dist/components/ticketReferences/index.d.ts +3 -0
- package/dist/components/ticketReferences/index.d.ts.map +1 -0
- package/dist/components/ticketReferences/index.js +6 -0
- package/dist/components/ticketReferences/index.js.map +1 -0
- package/dist/components/ticketThread/TicketThread.d.ts +57 -0
- package/dist/components/ticketThread/TicketThread.d.ts.map +1 -0
- package/dist/components/ticketThread/TicketThread.js +299 -0
- package/dist/components/ticketThread/TicketThread.js.map +1 -0
- package/dist/components/ticketThread/index.d.ts +3 -0
- package/dist/components/ticketThread/index.d.ts.map +1 -0
- package/dist/components/ticketThread/index.js +7 -0
- package/dist/components/ticketThread/index.js.map +1 -0
- package/dist/helpers/common.d.ts +5 -0
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +20 -1
- package/dist/helpers/common.js.map +1 -1
- package/dist/helpers/scrollbar.d.ts +2 -0
- package/dist/helpers/scrollbar.d.ts.map +1 -0
- package/dist/helpers/scrollbar.js +25 -0
- package/dist/helpers/scrollbar.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/StorageProvider.d.ts +13 -0
- package/dist/providers/StorageProvider.d.ts.map +1 -1
- package/dist/providers/StorageProvider.js +29 -5
- package/dist/providers/StorageProvider.js.map +1 -1
- package/package.json +6 -5
- package/src/components/FocusedEditing/index.tsx +17 -2
- package/src/components/composer/Composer.stories.tsx +238 -0
- package/src/components/composer/Composer.tsx +545 -0
- package/src/components/composer/helpers.ts +74 -0
- package/src/components/composer/index.ts +2 -0
- package/src/components/dpqlEditor/DpqlEditor.stories.tsx +74 -2
- package/src/components/form/engine/CompactRow.tsx +561 -283
- package/src/components/form/engine/CompactToolbar.tsx +216 -182
- package/src/components/form/engine/FormEngine.stories.tsx +1736 -162
- package/src/components/form/engine/FormEngine.tsx +886 -170
- package/src/components/form/engine/FormEngineRemote.stories.tsx +377 -0
- package/src/components/form/engine/_structuredData/StructuredDataView.stories.tsx +8 -0
- package/src/components/form/engine/compactRowContext.ts +14 -0
- package/src/components/form/engine/compactRowStyles.ts +273 -146
- package/src/components/form/engine/compactToolbarContext.ts +1 -0
- package/src/components/form/engine/optionActions.ts +40 -0
- package/src/components/form/engine/readFirst.ts +169 -10
- package/src/components/form/engine/rendererTypes.ts +55 -0
- package/src/components/form/engine/variants/FormEngineVariants.stories.tsx +159 -0
- package/src/components/form/engine/variants/VariantCalmTable.tsx +242 -0
- package/src/components/form/engine/variants/VariantCards.tsx +212 -0
- package/src/components/form/engine/variants/VariantFocus.tsx +382 -0
- package/src/components/form/engine/variants/VariantMinimal.tsx +170 -0
- package/src/components/form/engine/variants/focusDemo.ts +145 -0
- package/src/components/form/engine/variants/variantModel.ts +216 -0
- package/src/components/form/engine/variants/variantParts.tsx +313 -0
- package/src/components/form/expressions/ExpressionField.stories.tsx +84 -2
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +213 -19
- package/src/components/form/expressions/builder/index.tsx +12 -1
- package/src/components/form/expressions/useExpressions.ts +4 -1
- package/src/components/form/fields/Field.stories.tsx +40 -0
- package/src/components/form/fields/Field.tsx +10 -1
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -36
- package/src/components/form/fields/array/ArrayAutoField.stories.tsx +64 -0
- package/src/components/form/fields/auto/AutoFormField.stories.tsx +146 -2
- package/src/components/form/fields/auto/AutoFormField.tsx +42 -1
- package/src/components/form/fields/binary/Binary.stories.tsx +122 -0
- package/src/components/form/fields/binary/Binary.tsx +81 -0
- package/src/components/form/fields/boolean/Boolean.stories.tsx +58 -0
- package/src/components/form/fields/boolean/Boolean.tsx +1 -1
- package/src/components/form/fields/byte-size/ByteSize.stories.tsx +24 -0
- package/src/components/form/fields/color/Color.stories.tsx +30 -0
- package/src/components/form/fields/cron/Cron.stories.tsx +8 -0
- package/src/components/form/fields/date/Date.stories.tsx +22 -0
- package/src/components/form/fields/file/File.stories.tsx +48 -0
- package/src/components/form/fields/long-string/LongString.stories.tsx +24 -0
- package/src/components/form/fields/markdown/Markdown.stories.tsx +8 -0
- package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +14 -10
- package/src/components/form/fields/number/Number.stories.tsx +31 -0
- package/src/components/form/fields/object/Object.stories.tsx +88 -0
- package/src/components/form/fields/radio-group/RadioGroup.stories.tsx +24 -0
- package/src/components/form/fields/rich-text/RichText.stories.tsx +23 -0
- package/src/components/form/fields/schema-definition/SchemaDefinitionField.stories.tsx +72 -0
- package/src/components/form/fields/select/Select.stories.tsx +108 -0
- package/src/components/form/fields/select/Select.tsx +38 -12
- package/src/components/form/fields/select/SelectCollection.tsx +78 -17
- package/src/components/form/fields/string/String.stories.tsx +16 -0
- package/src/components/form/fields/template/TemplateField.stories.tsx +160 -0
- package/src/components/form/fields/url/Url.stories.tsx +24 -0
- package/src/components/form/index.tsx +2 -0
- package/src/components/imageLightbox/ImageLightbox.stories.tsx +238 -0
- package/src/components/imageLightbox/ImageLightbox.tsx +236 -0
- package/src/components/imageLightbox/index.ts +1 -0
- package/src/components/log/Log.stories.tsx +96 -0
- package/src/components/menu/Menu.stories.tsx +54 -0
- package/src/components/qonsoleSmartInput/QonsoleSmartInput.stories.tsx +83 -37
- package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
- package/src/components/searchFilterBar/index.ts +1 -0
- package/src/components/smartEditor/SmartEditor.stories.tsx +32 -0
- package/src/components/supportTicket/AttachmentChips.stories.tsx +171 -0
- package/src/components/supportTicket/AttachmentChips.tsx +147 -0
- package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +171 -0
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +96 -0
- package/src/components/supportTicket/ReferencePicker.stories.tsx +519 -0
- package/src/components/supportTicket/ReferencePicker.tsx +563 -0
- package/src/components/supportTicket/TicketConversation.stories.tsx +121 -0
- package/src/components/supportTicket/TicketHeader.stories.tsx +183 -0
- package/src/components/supportTicket/TicketHeader.tsx +247 -0
- package/src/components/supportTicket/TicketMetaTags.stories.tsx +103 -0
- package/src/components/supportTicket/TicketMetaTags.tsx +85 -0
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +351 -0
- package/src/components/supportTicket/TicketReplyBox.tsx +146 -0
- package/src/components/supportTicket/attachmentAccess.ts +97 -0
- package/src/components/supportTicket/fileToAttachment.ts +24 -0
- package/src/components/supportTicket/index.ts +10 -0
- package/src/components/supportTicket/meta.ts +185 -0
- package/src/components/supportTicket/ticketModel.ts +131 -0
- package/src/components/ticketReferences/TicketReferences.stories.tsx +315 -0
- package/src/components/ticketReferences/TicketReferences.tsx +886 -0
- package/src/components/ticketReferences/index.ts +2 -0
- package/src/components/ticketThread/TicketThread.stories.tsx +828 -0
- package/src/components/ticketThread/TicketThread.tsx +704 -0
- package/src/components/ticketThread/index.ts +6 -0
- package/src/helpers/common.ts +19 -0
- package/src/helpers/scrollbar.ts +32 -0
- package/src/hooks/useFetch/useFetch.stories.tsx +30 -0
- package/src/hooks/useStorage/useStorage.stories.tsx +82 -0
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +127 -1
- package/src/index.tsx +8 -1
- package/src/providers/StorageProvider.tsx +30 -4
- package/src/stores/currentUser/currentUser.stories.tsx +49 -0
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
IQorusFormField,
|
|
3
|
+
TQorusFormFieldSchema,
|
|
4
|
+
TQorusType,
|
|
5
|
+
} from '@qoretechnologies/ts-toolkit';
|
|
6
|
+
import { isRendererOnlyUiType } from './rendererTypes';
|
|
2
7
|
import { isUiEncodedValue } from './_structuredData/structuredData';
|
|
3
8
|
import { renderExpressionToText } from '../expressions/renderExpressionToText';
|
|
4
9
|
import { IExpressionValue } from '../expressions/types';
|
|
@@ -17,12 +22,33 @@ const pluralize = (count: number, noun: string): string =>
|
|
|
17
22
|
// full server allow-list, incl. keys like `sensitive`/`required`).
|
|
18
23
|
const isTypedEnvelope = (raw: unknown): raw is IQorusFormField => isUiEncodedValue(raw);
|
|
19
24
|
|
|
20
|
-
/**
|
|
25
|
+
/** Reduce ONE list item to a short, human label — never an object. Unwraps a
|
|
26
|
+
* typed `{type, value}` envelope, prefers a `name`/`display_name`, and returns
|
|
27
|
+
* undefined for a bare object (no name) so the caller falls back to a count
|
|
28
|
+
* instead of printing "[object Object]". */
|
|
29
|
+
const summarizeListItem = (item: unknown): string | number | undefined => {
|
|
30
|
+
if (item === null || item === undefined) return undefined;
|
|
31
|
+
if (typeof item !== 'object') return item as string | number;
|
|
32
|
+
const obj = item as Record<string, unknown>;
|
|
33
|
+
// A named object (or allowed-value) → its label.
|
|
34
|
+
if (typeof obj.name === 'string' || typeof obj.name === 'number') return obj.name;
|
|
35
|
+
if (typeof obj.display_name === 'string') return obj.display_name;
|
|
36
|
+
// A typed envelope ({type, value}) or a {value} wrapper → look inside once.
|
|
37
|
+
if ('value' in obj) {
|
|
38
|
+
const inner = obj.value;
|
|
39
|
+
if (inner === null || inner === undefined) return undefined;
|
|
40
|
+
if (typeof inner !== 'object') return inner as string | number;
|
|
41
|
+
const innerName = (inner as Record<string, unknown>).name;
|
|
42
|
+
return typeof innerName === 'string' || typeof innerName === 'number' ? innerName : undefined;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Summarise a list value: join item labels, or fall back to an "N items" count
|
|
48
|
+
* (e.g. a list of anonymous hashes). Never prints raw objects. */
|
|
21
49
|
const formatList = (items: unknown[]): string => {
|
|
22
50
|
const parts = items
|
|
23
|
-
.map(
|
|
24
|
-
item && typeof item === 'object' ? ((item as any).name ?? (item as any).value ?? '') : item
|
|
25
|
-
)
|
|
51
|
+
.map(summarizeListItem)
|
|
26
52
|
.filter((part) => part !== '' && part !== undefined && part !== null);
|
|
27
53
|
|
|
28
54
|
return parts.length ? parts.join(', ') : pluralize(items.length, 'item');
|
|
@@ -61,6 +87,48 @@ export const isOptionValueEmpty = (value: unknown): boolean =>
|
|
|
61
87
|
value === '' ||
|
|
62
88
|
(Array.isArray(value) && value.length === 0);
|
|
63
89
|
|
|
90
|
+
export const isFixedCompactAllowedValueOption = (
|
|
91
|
+
schema: TQorusFormFieldSchema | undefined
|
|
92
|
+
): boolean => {
|
|
93
|
+
if (!schema) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const selectableSchema = schema as TQorusFormFieldSchema & { items?: unknown[] };
|
|
98
|
+
const options = selectableSchema.allowed_values?.length
|
|
99
|
+
? selectableSchema.allowed_values
|
|
100
|
+
: selectableSchema.items;
|
|
101
|
+
|
|
102
|
+
return !!(
|
|
103
|
+
options?.length &&
|
|
104
|
+
!schema.allowed_values_creatable &&
|
|
105
|
+
!schema.multiselect &&
|
|
106
|
+
!schema.arg_schema
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const shouldAutoCollapseCompactAllowedValueOption = (
|
|
111
|
+
schema: TQorusFormFieldSchema | undefined,
|
|
112
|
+
value: unknown
|
|
113
|
+
): boolean => !isOptionValueEmpty(value) && isFixedCompactAllowedValueOption(schema);
|
|
114
|
+
|
|
115
|
+
/** Boolean fields have three meaningful states: unset, Yes, and No. Once the
|
|
116
|
+
* user chooses either boolean value in compact mode, the choice is complete
|
|
117
|
+
* and the inline editor can close without a separate confirmation. */
|
|
118
|
+
export const isCompactBooleanOption = (
|
|
119
|
+
schema: TQorusFormFieldSchema | undefined
|
|
120
|
+
): boolean => {
|
|
121
|
+
const type = (schema?.ui_type || schema?.type)?.toLowerCase();
|
|
122
|
+
return type === 'bool' || type === 'boolean';
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const shouldAutoCollapseCompactOption = (
|
|
126
|
+
schema: TQorusFormFieldSchema | undefined,
|
|
127
|
+
value: unknown
|
|
128
|
+
): boolean =>
|
|
129
|
+
!isOptionValueEmpty(value) &&
|
|
130
|
+
(isCompactBooleanOption(schema) || isFixedCompactAllowedValueOption(schema));
|
|
131
|
+
|
|
64
132
|
/** Prefer the matching allowed_values entry's display_name (fallback `name`)
|
|
65
133
|
* over the raw stored value. */
|
|
66
134
|
const findAllowedOption = (value: unknown, schema?: TQorusFormFieldSchema): any | undefined => {
|
|
@@ -202,15 +270,29 @@ export const getFileSize = (value: unknown): number | undefined => {
|
|
|
202
270
|
return typeof size === 'number' && Number.isFinite(size) && size >= 0 ? size : undefined;
|
|
203
271
|
};
|
|
204
272
|
|
|
205
|
-
/**
|
|
206
|
-
*
|
|
273
|
+
/**
|
|
274
|
+
* Effective UI type for DISPLAY — which preview/summary a value renders as.
|
|
275
|
+
*
|
|
276
|
+
* A renderer-only `ui_type` wins outright: the field's stored `type` holds the
|
|
277
|
+
* STORAGE type for those (a `code-editor` stores a string), so consulting the
|
|
278
|
+
* stored type here would render the bespoke editor's value as a plain string and
|
|
279
|
+
* silently drop its preview. Every other case keeps the stored type first, so an
|
|
280
|
+
* `any`/`auto` field still displays as whatever concrete type the user picked.
|
|
281
|
+
*/
|
|
207
282
|
export const getValueType = (
|
|
208
283
|
option?: IQorusFormField,
|
|
209
284
|
schema?: TQorusFormFieldSchema
|
|
210
|
-
): string | undefined =>
|
|
211
|
-
(
|
|
212
|
-
|
|
285
|
+
): string | undefined => {
|
|
286
|
+
const uiType = (schema as { ui_type?: string } | undefined)?.ui_type;
|
|
287
|
+
|
|
288
|
+
if (isRendererOnlyUiType(uiType as TQorusType)) {
|
|
289
|
+
return uiType;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return (option?.type ||
|
|
293
|
+
uiType ||
|
|
213
294
|
(schema as { type?: string } | undefined)?.type) as string | undefined;
|
|
295
|
+
};
|
|
214
296
|
|
|
215
297
|
/** Human-readable byte count (e.g. `1.2 KB`). */
|
|
216
298
|
export const formatBytes = (bytes: number): string => {
|
|
@@ -435,6 +517,41 @@ export interface IReadFirstCompletion {
|
|
|
435
517
|
pct: number;
|
|
436
518
|
}
|
|
437
519
|
|
|
520
|
+
/** Read-first row status, shared by the status DOT (CompactRow) and the
|
|
521
|
+
* status BOX bucketing (FormEngine) so the two can never disagree:
|
|
522
|
+
* invalid — a value fails validation, or a danger message is present (red)
|
|
523
|
+
* todo — empty & required (or has a warning message), needs a value (amber)
|
|
524
|
+
* set — has a valid value (green)
|
|
525
|
+
* optional — empty & not required, or covered by a one-of sibling (calm) */
|
|
526
|
+
export type TReadFirstStatus = 'invalid' | 'todo' | 'set' | 'optional';
|
|
527
|
+
|
|
528
|
+
export const getReadFirstStatus = (s: {
|
|
529
|
+
empty: boolean;
|
|
530
|
+
required: boolean;
|
|
531
|
+
/** Empty member of a one-of required group already satisfied by a sibling. */
|
|
532
|
+
covered: boolean;
|
|
533
|
+
/** Non-empty value fails validation, or a danger message is attached. */
|
|
534
|
+
invalid: boolean;
|
|
535
|
+
/** A warning message is attached (surfaces an empty field for attention). */
|
|
536
|
+
warned: boolean;
|
|
537
|
+
}): TReadFirstStatus => {
|
|
538
|
+
if (s.invalid) return 'invalid';
|
|
539
|
+
if (!s.empty) return 'set';
|
|
540
|
+
// A one-of member covered by a satisfied sibling reads as "set" (green) — the
|
|
541
|
+
// requirement is met; the row just shows a "Covered by …" note.
|
|
542
|
+
if (s.covered) return 'set';
|
|
543
|
+
if (s.required || s.warned) return 'todo';
|
|
544
|
+
return 'optional';
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
/** Coarse bucket for the three status boxes (Needs attention / Set / Optional). */
|
|
548
|
+
export const getReadFirstBucket = (
|
|
549
|
+
status: TReadFirstStatus
|
|
550
|
+
): 'attention' | 'set' | 'optional' =>
|
|
551
|
+
status === 'set' ? 'set'
|
|
552
|
+
: status === 'optional' ? 'optional'
|
|
553
|
+
: 'attention';
|
|
554
|
+
|
|
438
555
|
/** Count how many of the shown options have a value set, for the progress meter. */
|
|
439
556
|
export const getReadFirstCompletion = (
|
|
440
557
|
shownOptions: Record<string, IQorusFormField | undefined> = {}
|
|
@@ -446,3 +563,45 @@ export const getReadFirstCompletion = (
|
|
|
446
563
|
|
|
447
564
|
return { total, set, pct };
|
|
448
565
|
};
|
|
566
|
+
|
|
567
|
+
/** What the "first field to fix" selector needs to know about one field.
|
|
568
|
+
* Kept deliberately minimal — both verdicts are supplied by the caller — so the
|
|
569
|
+
* selector is decoupled from the engine's value/schema shapes and unit-testable
|
|
570
|
+
* in isolation. */
|
|
571
|
+
export interface IFirstAttentionFieldMeta {
|
|
572
|
+
/** Whether the field can actually receive focus — the caller folds in
|
|
573
|
+
* disabled / readonly / read-only-form / unmet-dependency gating here. */
|
|
574
|
+
focusable: boolean;
|
|
575
|
+
/** Whether the form considers this field to "need attention". The caller
|
|
576
|
+
* supplies this from the SAME `getOptionBucket` the read-first status boxes
|
|
577
|
+
* use, so it covers every attention case — empty-required, an unsatisfied
|
|
578
|
+
* one-of group, AND a filled-but-invalid value — and the autofocus target can
|
|
579
|
+
* never drift from the visible needs-attention set. */
|
|
580
|
+
needsAttention: boolean;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Pick the first field the user must fix: the first focusable field — in the
|
|
585
|
+
* supplied (already ordered) name list — that the form buckets as "needs
|
|
586
|
+
* attention".
|
|
587
|
+
*
|
|
588
|
+
* Pure and side-effect free. It deliberately does NOT re-derive emptiness /
|
|
589
|
+
* required / validity itself; the caller passes `needsAttention` straight from
|
|
590
|
+
* the engine's own `getOptionBucket`, so there is a single source of truth and
|
|
591
|
+
* the "focus the first field to fix" affordance can never disagree with the
|
|
592
|
+
* per-row status the user sees (including filled-but-invalid rows). Returns
|
|
593
|
+
* `undefined` when nothing needs attention.
|
|
594
|
+
*/
|
|
595
|
+
export const getFirstAttentionOptionName = (
|
|
596
|
+
orderedNames: string[],
|
|
597
|
+
getFieldMeta: (name: string) => IFirstAttentionFieldMeta | undefined
|
|
598
|
+
): string | undefined => {
|
|
599
|
+
for (const name of orderedNames) {
|
|
600
|
+
const meta = getFieldMeta(name);
|
|
601
|
+
if (meta?.focusable && meta.needsAttention) {
|
|
602
|
+
return name;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
return undefined;
|
|
607
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { TQorusType } from '@qoretechnologies/ts-toolkit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A schema entry carries two type-ish keys with different jobs:
|
|
5
|
+
*
|
|
6
|
+
* - `type` names how the value is STORED and validated (`string`, `hash`, `list`…).
|
|
7
|
+
* - `ui_type` names which EDITOR renders it.
|
|
8
|
+
*
|
|
9
|
+
* For most fields `ui_type` is a storage-compatible refinement (`long-string`,
|
|
10
|
+
* `enum`, `url`) and it is safe to let it win for both jobs. But some `ui_type`s
|
|
11
|
+
* name a bespoke editor whose value is still stored as the plainer `type` — a
|
|
12
|
+
* `cron` editor stores a string, `processor-mappings` stores a hash. Writing
|
|
13
|
+
* those renderer names into the field's `type` breaks validation and round-trips
|
|
14
|
+
* the value through the wrong branch, which is what `RENDERER_ONLY_UI_TYPES`
|
|
15
|
+
* exists to prevent.
|
|
16
|
+
*
|
|
17
|
+
* There is deliberately no derivation here: a renderer-only `ui_type` is not
|
|
18
|
+
* distinguishable from a storage-compatible one by inspecting the value or the
|
|
19
|
+
* `TQorusType` union (`code-editor` and `tool-catalog` are members of it, and
|
|
20
|
+
* the validator has cases for `cron` and `schema-definition`). It is a fact
|
|
21
|
+
* about which editor a name selects, so it has to be declared.
|
|
22
|
+
*/
|
|
23
|
+
export const BUILT_IN_RENDERER_ONLY_UI_TYPES: readonly string[] = [
|
|
24
|
+
'active-windows',
|
|
25
|
+
'alert-threshold',
|
|
26
|
+
'code-editor',
|
|
27
|
+
'cron',
|
|
28
|
+
'dpql',
|
|
29
|
+
'processor-mappings',
|
|
30
|
+
'schema-definition',
|
|
31
|
+
'test-cases',
|
|
32
|
+
'test-value-contract',
|
|
33
|
+
'tool-catalog',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build the renderer-only predicate for one FormEngine instance.
|
|
38
|
+
*
|
|
39
|
+
* Consumers inject their own editors through `componentOverrides`, so the set of
|
|
40
|
+
* renderer-only names is open-ended — the IDE ships editors reqraft has never
|
|
41
|
+
* heard of. `extraTypes` (the `rendererOnlyUiTypes` prop) lets a consumer declare
|
|
42
|
+
* its own without waiting on a reqraft release, which is what previously made
|
|
43
|
+
* every new IDE editor a silent value-corruption bug until this list caught up.
|
|
44
|
+
*/
|
|
45
|
+
export const createRendererOnlyUiTypeCheck = (
|
|
46
|
+
extraTypes?: readonly string[]
|
|
47
|
+
): ((type?: TQorusType | TQorusType[]) => boolean) => {
|
|
48
|
+
const known = new Set<string>([...BUILT_IN_RENDERER_ONLY_UI_TYPES, ...(extraTypes ?? [])]);
|
|
49
|
+
|
|
50
|
+
return (type?: TQorusType | TQorusType[]): boolean =>
|
|
51
|
+
typeof type === 'string' && known.has(type);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The default predicate — built-ins only, for module-scope callers with no props. */
|
|
55
|
+
export const isRendererOnlyUiType = createRendererOnlyUiTypeCheck();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { ReqoreP, ReqorePanel } from '@qoretechnologies/reqore';
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { IQorusFormField } from '@qoretechnologies/ts-toolkit';
|
|
4
|
+
import { basicFormValue, getBasicFormOptions } from '../__fixtures__/basicFormOptions';
|
|
5
|
+
import {
|
|
6
|
+
focusDemoGroupLabels,
|
|
7
|
+
focusDemoInvalid,
|
|
8
|
+
focusDemoValue,
|
|
9
|
+
getFocusDemoOptions,
|
|
10
|
+
} from './focusDemo';
|
|
11
|
+
import { VariantCalmTable } from './VariantCalmTable';
|
|
12
|
+
import { VariantCards } from './VariantCards';
|
|
13
|
+
import { VariantFocus } from './VariantFocus';
|
|
14
|
+
import { VariantMinimal } from './VariantMinimal';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* VARIANTS PLAYGROUND for the compact FormEngine read-first view.
|
|
18
|
+
* All four render the SAME schema + values as `Form/Engine/FormEngine › Basic`
|
|
19
|
+
* (the reference screenshot), so the comparison is purely about layout / chrome
|
|
20
|
+
* / UX. Resize the Storybook viewport (or the canvas) to ~390px to check phone.
|
|
21
|
+
* See design/FORM_ENGINE_COMPACT_UX_PLAN.md for the rationale behind each.
|
|
22
|
+
*/
|
|
23
|
+
const options = getBasicFormOptions();
|
|
24
|
+
|
|
25
|
+
// Merge declared default_values into the values map so "set via default" fields
|
|
26
|
+
// (number=42, bool=true, the file) render as set — matching the real engine.
|
|
27
|
+
const values: Record<string, IQorusFormField> = (() => {
|
|
28
|
+
const merged: Record<string, IQorusFormField> = { ...(basicFormValue as any) };
|
|
29
|
+
Object.entries(options).forEach(([name, schema]: [string, any]) => {
|
|
30
|
+
if (!merged[name] && schema?.default_value) merged[name] = schema.default_value;
|
|
31
|
+
});
|
|
32
|
+
return merged;
|
|
33
|
+
})();
|
|
34
|
+
|
|
35
|
+
// Fill the available width (the IDE form panel context); no artificial cap.
|
|
36
|
+
// Focus uses the richer demo schema (named groups, a one-of required group,
|
|
37
|
+
// complex nested fields) to show full engine parity.
|
|
38
|
+
const focusOptions = getFocusDemoOptions();
|
|
39
|
+
const focusValues: Record<string, IQorusFormField> = (() => {
|
|
40
|
+
const merged: Record<string, IQorusFormField> = { ...focusDemoValue };
|
|
41
|
+
Object.entries(focusOptions).forEach(([name, schema]: [string, any]) => {
|
|
42
|
+
if (!merged[name] && schema?.default_value) merged[name] = schema.default_value;
|
|
43
|
+
});
|
|
44
|
+
return merged;
|
|
45
|
+
})();
|
|
46
|
+
const focusConfig = { groupLabels: focusDemoGroupLabels, invalidReasons: focusDemoInvalid };
|
|
47
|
+
|
|
48
|
+
const Frame = ({ children }: { children: React.ReactNode }) => (
|
|
49
|
+
<div style={{ width: '100%', padding: '16px 32px', boxSizing: 'border-box' }}>{children}</div>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const meta = {
|
|
53
|
+
title: 'Form/Engine/FormEngine Variants',
|
|
54
|
+
parameters: { layout: 'fullscreen', chromatic: { viewports: [1440, 390] } },
|
|
55
|
+
} satisfies Meta;
|
|
56
|
+
|
|
57
|
+
export default meta;
|
|
58
|
+
type Story = StoryObj<typeof meta>;
|
|
59
|
+
|
|
60
|
+
export const V1CalmTable: Story = {
|
|
61
|
+
name: '1 · Calm Table',
|
|
62
|
+
parameters: {
|
|
63
|
+
docs: {
|
|
64
|
+
description: {
|
|
65
|
+
story:
|
|
66
|
+
'Renders the "Calm Table" compact-form variant over the shared basic schema — one of four visual candidates for the read-first FormEngine view.',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
render: () => (
|
|
71
|
+
<Frame>
|
|
72
|
+
<VariantCalmTable options={options} values={values} />
|
|
73
|
+
</Frame>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const V2Cards: Story = {
|
|
78
|
+
name: '2 · Cards',
|
|
79
|
+
parameters: {
|
|
80
|
+
docs: {
|
|
81
|
+
description: {
|
|
82
|
+
story:
|
|
83
|
+
'Renders the "Cards" compact-form variant over the shared basic schema — each field group is a card so the presentation reads as chunked panels.',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
render: () => (
|
|
88
|
+
<Frame>
|
|
89
|
+
<VariantCards options={options} values={values} />
|
|
90
|
+
</Frame>
|
|
91
|
+
),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const V3Focus: Story = {
|
|
95
|
+
name: '3 · Focus',
|
|
96
|
+
parameters: {
|
|
97
|
+
docs: {
|
|
98
|
+
description: {
|
|
99
|
+
story:
|
|
100
|
+
'Renders the "Focus" compact-form variant over the richer focus-demo schema (named groups, required-group choices) so the variant is compared under full engine parity.',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
render: () => (
|
|
105
|
+
<Frame>
|
|
106
|
+
<VariantFocus options={focusOptions} values={focusValues} config={focusConfig} />
|
|
107
|
+
</Frame>
|
|
108
|
+
),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const V4Minimal: Story = {
|
|
112
|
+
name: '4 · Minimal',
|
|
113
|
+
parameters: {
|
|
114
|
+
docs: {
|
|
115
|
+
description: {
|
|
116
|
+
story:
|
|
117
|
+
'Renders the "Minimal" compact-form variant over the shared basic schema — the sparest of the four candidates, only labels and values.',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
render: () => (
|
|
122
|
+
<Frame>
|
|
123
|
+
<VariantMinimal options={options} values={values} />
|
|
124
|
+
</Frame>
|
|
125
|
+
),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** All four stacked, labelled — for a quick scroll-through comparison. */
|
|
129
|
+
export const CompareAll: Story = {
|
|
130
|
+
name: '★ Compare all',
|
|
131
|
+
parameters: {
|
|
132
|
+
docs: {
|
|
133
|
+
description: {
|
|
134
|
+
story:
|
|
135
|
+
'Renders all four compact-form variants stacked in a single scroll — the Calm Table, Cards, Focus and Minimal layouts sit under labelled panels so they can be compared side by side.',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
render: () => {
|
|
140
|
+
const items: [string, React.ReactNode][] = [
|
|
141
|
+
['1 · Calm Table', <VariantCalmTable key='v1' options={options} values={values} />],
|
|
142
|
+
['2 · Cards', <VariantCards key='v2' options={options} values={values} />],
|
|
143
|
+
['3 · Focus', <VariantFocus key='v3' options={focusOptions} values={focusValues} config={focusConfig} />],
|
|
144
|
+
['4 · Minimal', <VariantMinimal key='v4' options={options} values={values} />],
|
|
145
|
+
];
|
|
146
|
+
return (
|
|
147
|
+
<div style={{ maxWidth: 1100, margin: '0 auto', padding: 16, display: 'flex', flexFlow: 'column', gap: 28 }}>
|
|
148
|
+
{items.map(([label, node]) => (
|
|
149
|
+
<ReqorePanel key={label} label={label} flat minimal collapsible>
|
|
150
|
+
<div style={{ padding: 8 }}>{node}</div>
|
|
151
|
+
</ReqorePanel>
|
|
152
|
+
))}
|
|
153
|
+
<ReqoreP size='small' effect={{ opacity: 0.5 }}>
|
|
154
|
+
Same data as “FormEngine › Basic”. Resize the viewport to ~390px to test phone layout.
|
|
155
|
+
</ReqoreP>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VARIANT 1 — "Calm Table"
|
|
3
|
+
*
|
|
4
|
+
* Direction: treat it like a clean data table. One status channel (segmented
|
|
5
|
+
* meter), one mark per row (a status dot), whitespace instead of borders/boxes.
|
|
6
|
+
* Errors are quiet inline text, not full-width red boxes. WHOLE-ROW hover.
|
|
7
|
+
* ONE unified description control (tap to reveal short+long inline). Click a row
|
|
8
|
+
* to edit it inline. Responsive: collapses to stacked label/value on phone.
|
|
9
|
+
*/
|
|
10
|
+
import { ReqoreIcon, ReqoreP } from '@qoretechnologies/reqore';
|
|
11
|
+
import { IQorusFormField, IQorusFormSchema } from '@qoretechnologies/ts-toolkit';
|
|
12
|
+
import styled from 'styled-components';
|
|
13
|
+
import {
|
|
14
|
+
InlineEdit,
|
|
15
|
+
StatusDot,
|
|
16
|
+
TVariantForm,
|
|
17
|
+
ValueView,
|
|
18
|
+
VariantToolbar,
|
|
19
|
+
useDisclosure,
|
|
20
|
+
useVariantColors,
|
|
21
|
+
useVariantForm,
|
|
22
|
+
} from './variantParts';
|
|
23
|
+
|
|
24
|
+
const Wrap = styled.div<{ $line: string; $hover: string; $muted: string; $faint: string }>`
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-flow: column;
|
|
27
|
+
gap: 22px;
|
|
28
|
+
font-size: 13px;
|
|
29
|
+
|
|
30
|
+
.vct-row {
|
|
31
|
+
display: grid;
|
|
32
|
+
grid-template-columns: minmax(180px, 320px) minmax(0, 1fr) auto;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 18px;
|
|
35
|
+
min-height: 40px;
|
|
36
|
+
padding: 6px 10px;
|
|
37
|
+
border-radius: 8px;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: background 0.12s ease;
|
|
40
|
+
}
|
|
41
|
+
/* WHOLE-row hover (fixes: hover used to only tint the label cell) */
|
|
42
|
+
.vct-row:hover,
|
|
43
|
+
.vct-row:focus-within {
|
|
44
|
+
background: ${({ $hover }) => $hover};
|
|
45
|
+
}
|
|
46
|
+
.vct-label {
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 6px;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
}
|
|
53
|
+
.vct-actions {
|
|
54
|
+
display: inline-flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 10px;
|
|
57
|
+
opacity: 0.85;
|
|
58
|
+
}
|
|
59
|
+
.vct-desc {
|
|
60
|
+
grid-column: 1 / -1;
|
|
61
|
+
padding: 2px 2px 6px 0;
|
|
62
|
+
color: ${({ $muted }) => $muted};
|
|
63
|
+
}
|
|
64
|
+
.vct-reason {
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
margin-left: 8px;
|
|
67
|
+
}
|
|
68
|
+
.vct-info {
|
|
69
|
+
background: none;
|
|
70
|
+
border: none;
|
|
71
|
+
color: ${({ $faint }) => $faint};
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
padding: 4px;
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
border-radius: 6px;
|
|
76
|
+
}
|
|
77
|
+
.vct-info:hover {
|
|
78
|
+
color: ${({ $muted }) => $muted};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (max-width: 620px) {
|
|
82
|
+
.vct-row {
|
|
83
|
+
grid-template-columns: 1fr auto;
|
|
84
|
+
grid-template-areas: 'label actions' 'value value';
|
|
85
|
+
row-gap: 2px;
|
|
86
|
+
}
|
|
87
|
+
.vct-label {
|
|
88
|
+
grid-area: label;
|
|
89
|
+
}
|
|
90
|
+
.vct-value {
|
|
91
|
+
grid-area: value;
|
|
92
|
+
}
|
|
93
|
+
.vct-actions {
|
|
94
|
+
grid-area: actions;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const Meter = ({ form }: { form: TVariantForm }) => {
|
|
100
|
+
const c = useVariantColors();
|
|
101
|
+
const s = form.summary;
|
|
102
|
+
const filtering = form.filter === 'attention';
|
|
103
|
+
return (
|
|
104
|
+
<div style={{ display: 'flex', flexFlow: 'column', gap: 8 }}>
|
|
105
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 12, fontSize: 12, color: c.muted }}>
|
|
106
|
+
<ReqoreP
|
|
107
|
+
size='small'
|
|
108
|
+
effect={{
|
|
109
|
+
uppercase: true,
|
|
110
|
+
spaced: 1,
|
|
111
|
+
weight: 'bold',
|
|
112
|
+
color: (s.attention ? c.warning : c.success) as never,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{s.attention ? 'Draft' : 'Ready'}
|
|
116
|
+
</ReqoreP>
|
|
117
|
+
<span>
|
|
118
|
+
{s.set} of {s.total} set
|
|
119
|
+
</span>
|
|
120
|
+
{s.attention ?
|
|
121
|
+
<button
|
|
122
|
+
type='button'
|
|
123
|
+
onClick={form.toggleAttention}
|
|
124
|
+
style={{
|
|
125
|
+
marginLeft: 'auto',
|
|
126
|
+
background: 'none',
|
|
127
|
+
border: 'none',
|
|
128
|
+
cursor: 'pointer',
|
|
129
|
+
color: c.warning,
|
|
130
|
+
fontSize: 12,
|
|
131
|
+
textDecoration: 'underline',
|
|
132
|
+
textUnderlineOffset: 3,
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{filtering ? '← show all fields' : `${s.attention} need attention →`}
|
|
136
|
+
</button>
|
|
137
|
+
: <span style={{ color: c.success, marginLeft: 'auto' }}>All clear</span>}
|
|
138
|
+
</div>
|
|
139
|
+
<div style={{ display: 'flex', height: 6, borderRadius: 3, overflow: 'hidden', background: c.line }}>
|
|
140
|
+
<div style={{ width: `${(s.set / s.total) * 100}%`, background: c.success }} />
|
|
141
|
+
<div style={{ width: `${(s.attention / s.total) * 100}%`, background: c.warning }} />
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const VariantCalmTable = ({
|
|
148
|
+
options,
|
|
149
|
+
values,
|
|
150
|
+
}: {
|
|
151
|
+
options: IQorusFormSchema;
|
|
152
|
+
values: Record<string, IQorusFormField>;
|
|
153
|
+
}) => {
|
|
154
|
+
const c = useVariantColors();
|
|
155
|
+
const form = useVariantForm(options, values);
|
|
156
|
+
const disc = useDisclosure();
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<Wrap $line={c.line} $hover={c.hover} $muted={c.muted} $faint={c.faint}>
|
|
160
|
+
<Meter form={form} />
|
|
161
|
+
<VariantToolbar form={form} />
|
|
162
|
+
{form.visibleGroups.map((g) => (
|
|
163
|
+
<div key={g.name} style={{ display: 'flex', flexFlow: 'column', gap: 2 }}>
|
|
164
|
+
<div
|
|
165
|
+
style={{
|
|
166
|
+
display: 'flex',
|
|
167
|
+
alignItems: 'center',
|
|
168
|
+
gap: 10,
|
|
169
|
+
padding: '0 10px 6px',
|
|
170
|
+
borderBottom: `1px solid ${c.line}`,
|
|
171
|
+
marginBottom: 4,
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<ReqoreP effect={{ weight: 'bold' }}>{g.label}</ReqoreP>
|
|
175
|
+
<span style={{ color: c.faint, fontSize: 12 }}>{g.rows.length}</span>
|
|
176
|
+
</div>
|
|
177
|
+
{g.rows.map((r) => {
|
|
178
|
+
const hasDesc = !!(r.shortDesc || r.longDesc);
|
|
179
|
+
const open = disc.isOpen(r.name);
|
|
180
|
+
const editing = form.editing === r.name;
|
|
181
|
+
return (
|
|
182
|
+
<div
|
|
183
|
+
key={r.name}
|
|
184
|
+
className='vct-row'
|
|
185
|
+
role='button'
|
|
186
|
+
tabIndex={0}
|
|
187
|
+
aria-label={r.label}
|
|
188
|
+
onClick={() => !r.readOnly && form.startEdit(r.name)}
|
|
189
|
+
>
|
|
190
|
+
<span className='vct-label' style={{ color: c.text }}>
|
|
191
|
+
{r.label}
|
|
192
|
+
{r.required ?
|
|
193
|
+
<ReqoreIcon icon='Asterisk' size='9px' style={{ color: c.danger }} />
|
|
194
|
+
: null}
|
|
195
|
+
</span>
|
|
196
|
+
<span className='vct-value' style={{ minWidth: 0, color: c.muted }}>
|
|
197
|
+
<ValueView value={r.value} />
|
|
198
|
+
{r.status === 'invalid' || r.status === 'todo' ?
|
|
199
|
+
<span
|
|
200
|
+
className='vct-reason'
|
|
201
|
+
style={{ color: r.status === 'invalid' ? c.danger : c.warning }}
|
|
202
|
+
>
|
|
203
|
+
{r.reason}
|
|
204
|
+
</span>
|
|
205
|
+
: null}
|
|
206
|
+
</span>
|
|
207
|
+
<span className='vct-actions'>
|
|
208
|
+
<StatusDot status={r.status} />
|
|
209
|
+
{hasDesc ?
|
|
210
|
+
<button
|
|
211
|
+
type='button'
|
|
212
|
+
className='vct-info'
|
|
213
|
+
aria-label='Toggle description'
|
|
214
|
+
aria-expanded={open}
|
|
215
|
+
onClick={(e) => {
|
|
216
|
+
e.stopPropagation();
|
|
217
|
+
disc.toggle(r.name);
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
<ReqoreIcon icon={open ? 'InformationFill' : 'InformationLine'} size='14px' />
|
|
221
|
+
</button>
|
|
222
|
+
: null}
|
|
223
|
+
</span>
|
|
224
|
+
{hasDesc && open ?
|
|
225
|
+
<div className='vct-desc'>
|
|
226
|
+
{r.shortDesc ? <div>{r.shortDesc}</div> : null}
|
|
227
|
+
{r.longDesc ?
|
|
228
|
+
<div style={{ opacity: 0.8, marginTop: r.shortDesc ? 4 : 0 }}>
|
|
229
|
+
{r.longDesc.replace(/[#`*]/g, '')}
|
|
230
|
+
</div>
|
|
231
|
+
: null}
|
|
232
|
+
</div>
|
|
233
|
+
: null}
|
|
234
|
+
{editing ? <InlineEdit row={r} onDone={form.stopEdit} /> : null}
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
})}
|
|
238
|
+
</div>
|
|
239
|
+
))}
|
|
240
|
+
</Wrap>
|
|
241
|
+
);
|
|
242
|
+
};
|