From efc230f81e111580f671645fe74d76093a45d169 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 16 Sep 2022 16:47:32 +0200 Subject: [PATCH] #391 - New `description` property --- CHANGELOG.md | 1 + package.json | 4 +++ src/models/BaseFieldProps.ts | 1 + src/models/PanelSettings.ts | 1 + .../components/DataBlock/DataBlockField.tsx | 9 ++++- .../components/Fields/ChoiceField.tsx | 6 ++-- .../components/Fields/DataFileField.tsx | 7 ++-- .../components/Fields/DateTimeField.tsx | 6 ++-- .../components/Fields/DraftField.tsx | 4 ++- .../components/Fields/FieldMessage.tsx | 33 +++++++++++++++++++ .../components/Fields/FileField.tsx | 6 ++-- .../components/Fields/ListField.tsx | 6 ++-- .../components/Fields/NumberField.tsx | 6 ++-- .../components/Fields/PreviewImageField.tsx | 5 +-- .../components/Fields/RequiredMessage.tsx | 19 ----------- .../components/Fields/SlugField.tsx | 9 +++-- .../components/Fields/TextField.tsx | 9 +++-- src/panelWebView/components/Fields/Toggle.tsx | 6 ++-- .../components/Fields/WrapperField.tsx | 21 ++++++++++++ src/panelWebView/components/Fields/index.ts | 2 +- src/panelWebView/components/TagPicker.tsx | 8 ++--- src/panelWebView/styles.css | 28 ++++++++++++++-- 22 files changed, 139 insertions(+), 58 deletions(-) create mode 100644 src/panelWebView/components/Fields/FieldMessage.tsx delete mode 100644 src/panelWebView/components/Fields/RequiredMessage.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 07284237..a99d1084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#376](https://github.com/estruyf/vscode-front-matter/issues/376): Ability to run scripts after content was created - [#377](https://github.com/estruyf/vscode-front-matter/issues/377): Git sync actions added on panel and content dashboard (pull and push your changes to remote) - [#379](https://github.com/estruyf/vscode-front-matter/issues/377): New `frontMatter.config.reload` command to reload the configuration file + reinitialize its listeners +- [#391](https://github.com/estruyf/vscode-front-matter/issues/391): New `description` property to show a message underneath the input field - [#401](https://github.com/estruyf/vscode-front-matter/issues/401): Content dashboard now has pagination enabled and can be disabled via the `frontMatter.dashboard.content.pagination` setting ### 🎨 Enhancements diff --git a/package.json b/package.json index d048c10a..edac7456 100644 --- a/package.json +++ b/package.json @@ -885,6 +885,10 @@ "type": "string", "description": "Title to show in the UI" }, + "description": { + "type": "string", + "description": "Description to show in the UI" + }, "default": { "type": [ "string", diff --git a/src/models/BaseFieldProps.ts b/src/models/BaseFieldProps.ts index fe7fd2a2..092afd1d 100644 --- a/src/models/BaseFieldProps.ts +++ b/src/models/BaseFieldProps.ts @@ -3,5 +3,6 @@ export interface BaseFieldProps { label: string; value: T | null; + description?: string; required?: boolean; } \ No newline at end of file diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 79ec7db8..99f6b13a 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -55,6 +55,7 @@ export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | export interface Field { title?: string; + description?: string; name: string; type: FieldType; choices?: string[] | Choice[]; diff --git a/src/panelWebView/components/DataBlock/DataBlockField.tsx b/src/panelWebView/components/DataBlock/DataBlockField.tsx index 63514928..432fa173 100644 --- a/src/panelWebView/components/DataBlock/DataBlockField.tsx +++ b/src/panelWebView/components/DataBlock/DataBlockField.tsx @@ -10,6 +10,7 @@ import { FieldTitle } from '../Fields/FieldTitle'; export interface IDataBlockFieldProps { label: string; + description?: string; settings: PanelSettings; field: Field; parentFields: string[]; @@ -28,7 +29,7 @@ export interface IDataBlockFieldProps { required?: boolean; } -export const DataBlockField: React.FunctionComponent = ({ label, filePath, settings, field, parentFields = [], value, fieldsRenderer, onSubmit, parentBlock, required }: React.PropsWithChildren) => { +export const DataBlockField: React.FunctionComponent = ({ label, description, filePath, settings, field, parentFields = [], value, fieldsRenderer, onSubmit, parentBlock, required }: React.PropsWithChildren) => { const [ selectedIndex, setSelectedIndex ] = useState(null); const [ selectedGroup, setSelectedGroup ] = useState(null); const [ selectedBlockData, setSelectedBlockData ] = useState(null); @@ -235,6 +236,12 @@ export const DataBlockField: React.FunctionComponent = ({ icon={} required={required} /> + { description && ( +
+ {description} +
+ )} + { (!hideSubBlock) ? (
diff --git a/src/panelWebView/components/Fields/ChoiceField.tsx b/src/panelWebView/components/Fields/ChoiceField.tsx index a5ffdd12..9477669f 100644 --- a/src/panelWebView/components/Fields/ChoiceField.tsx +++ b/src/panelWebView/components/Fields/ChoiceField.tsx @@ -6,7 +6,7 @@ import { BaseFieldProps } from '../../../models'; import { Choice } from '../../../models/Choice'; import { ChoiceButton } from './ChoiceButton'; import { FieldTitle } from './FieldTitle'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface IChoiceFieldProps extends BaseFieldProps { choices: string[] | Choice[]; @@ -14,7 +14,7 @@ export interface IChoiceFieldProps extends BaseFieldProps { onChange: (value: string | string[]) => void; } -export const ChoiceField: React.FunctionComponent = ({ label, value, choices, multiSelect, onChange, required }: React.PropsWithChildren) => { +export const ChoiceField: React.FunctionComponent = ({ label, description, value, choices, multiSelect, onChange, required }: React.PropsWithChildren) => { const [ crntSelected, setCrntSelected ] = React.useState(value); const dsRef = React.useRef | null>(null); @@ -116,7 +116,7 @@ export const ChoiceField: React.FunctionComponent = ({ label, )} - + { crntSelected instanceof Array ? crntSelected.map((value: string) => ( diff --git a/src/panelWebView/components/Fields/DataFileField.tsx b/src/panelWebView/components/Fields/DataFileField.tsx index e33c68a2..d2e81ac9 100644 --- a/src/panelWebView/components/Fields/DataFileField.tsx +++ b/src/panelWebView/components/Fields/DataFileField.tsx @@ -8,10 +8,11 @@ import { CommandToCode } from '../../CommandToCode'; import Downshift from 'downshift'; import { ChoiceButton } from './ChoiceButton'; import { FieldTitle } from './FieldTitle'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface IDataFileFieldProps { label: string; + description?: string; dataFileId?: string; dataFileKey?: string; dataFileValue?: string; @@ -21,7 +22,7 @@ export interface IDataFileFieldProps { onChange: (value: string | string[]) => void; } -export const DataFileField: React.FunctionComponent = ({ label, dataFileId, dataFileKey, dataFileValue, selected, multiSelect, onChange, required }: React.PropsWithChildren) => { +export const DataFileField: React.FunctionComponent = ({ label, description, dataFileId, dataFileKey, dataFileValue, selected, multiSelect, onChange, required }: React.PropsWithChildren) => { const [ dataEntries, setDataEntries ] = useState(null); const [ crntSelected, setCrntSelected ] = React.useState(); const dsRef = React.useRef | null>(null); @@ -164,7 +165,7 @@ export const DataFileField: React.FunctionComponent = ({ la )} - + { crntSelected instanceof Array ? crntSelected.map((value: string) => ( diff --git a/src/panelWebView/components/Fields/DateTimeField.tsx b/src/panelWebView/components/Fields/DateTimeField.tsx index a76881ab..7251c7d5 100644 --- a/src/panelWebView/components/Fields/DateTimeField.tsx +++ b/src/panelWebView/components/Fields/DateTimeField.tsx @@ -4,7 +4,7 @@ import DatePicker from 'react-datepicker'; import { forwardRef, useEffect, useMemo } from 'react'; import { DateHelper } from '../../../helpers/DateHelper'; import { BaseFieldProps } from '../../../models'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; import { FieldTitle } from './FieldTitle'; export interface IDateTimeFieldProps extends BaseFieldProps { @@ -22,7 +22,7 @@ const CustomInput = forwardRef(({ value, onClick } ) }); -export const DateTimeField: React.FunctionComponent = ({label, value, required, format, onChange}: React.PropsWithChildren) => { +export const DateTimeField: React.FunctionComponent = ({label, description, value, required, format, onChange}: React.PropsWithChildren) => { const [ dateValue, setDateValue ] = React.useState(null); const onDateChange = (date: Date) => { @@ -65,7 +65,7 @@ export const DateTimeField: React.FunctionComponent = ({lab
- + ); }; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/DraftField.tsx b/src/panelWebView/components/Fields/DraftField.tsx index 6569f8ce..9a3fcf6d 100644 --- a/src/panelWebView/components/Fields/DraftField.tsx +++ b/src/panelWebView/components/Fields/DraftField.tsx @@ -11,12 +11,13 @@ export interface IDraftFieldProps extends BaseFieldProps void; } -export const DraftField: React.FunctionComponent = ({ label, type, value, choices, onChanged, required }: React.PropsWithChildren) => { +export const DraftField: React.FunctionComponent = ({ label, description, type, value, choices, onChanged, required }: React.PropsWithChildren) => { if (type === "boolean") { return ( onChanged(checked)} /> @@ -27,6 +28,7 @@ export const DraftField: React.FunctionComponent = ({ label, t return ( = ({ name, description, showRequired }: React.PropsWithChildren) => { + + console.log(description, showRequired); + if (!showRequired && !description) { + return null; + } + + if (showRequired) { + return ( +
+ The {name} field is required. +
+ ); + } + + if (description) { + return ( +
+ {description} +
+ ); + } + + return null; +}; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/FileField.tsx b/src/panelWebView/components/Fields/FileField.tsx index e59becfb..16771da1 100644 --- a/src/panelWebView/components/Fields/FileField.tsx +++ b/src/panelWebView/components/Fields/FileField.tsx @@ -6,7 +6,7 @@ import { useCallback, useMemo } from 'react'; import { BaseFieldProps, BlockFieldData } from '../../../models'; import { CommandToCode } from '../../CommandToCode'; import { FieldTitle } from './FieldTitle'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface IFileFieldProps extends BaseFieldProps { fieldName: string; @@ -34,7 +34,7 @@ const File = ({ value, onRemove }: { value: string, onRemove: (value: string) => ) } -export const FileField: React.FunctionComponent = ({ label, multiple, filePath, fileExtensions, fieldName, value, parents, blockData, onChange, required }: React.PropsWithChildren) => { +export const FileField: React.FunctionComponent = ({ label, description, multiple, filePath, fileExtensions, fieldName, value, parents, blockData, onChange, required }: React.PropsWithChildren) => { const selectFile = useCallback(() => { Messenger.send(CommandToCode.selectFile, { @@ -79,7 +79,7 @@ export const FileField: React.FunctionComponent = ({ label, mul ) } - + { value && !Array.isArray(value) && ( diff --git a/src/panelWebView/components/Fields/ListField.tsx b/src/panelWebView/components/Fields/ListField.tsx index 33812a8b..c45a2fd0 100644 --- a/src/panelWebView/components/Fields/ListField.tsx +++ b/src/panelWebView/components/Fields/ListField.tsx @@ -3,13 +3,13 @@ import * as React from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react'; import { BaseFieldProps } from '../../../models'; import { FieldTitle } from './FieldTitle'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface IListFieldProps extends BaseFieldProps { onChange: (value: string | string[]) => void; } -export const ListField: React.FunctionComponent = ({ label, value, required, onChange }: React.PropsWithChildren) => { +export const ListField: React.FunctionComponent = ({ label, description, value, required, onChange }: React.PropsWithChildren) => { const [ text, setText ] = React.useState(""); const [ list, setList ] = React.useState(null); const [ itemToEdit, setItemToEdit ] = React.useState(null); @@ -90,7 +90,7 @@ export const ListField: React.FunctionComponent = ({ label, val } }} /> - +
); }; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/PreviewImageField.tsx b/src/panelWebView/components/Fields/PreviewImageField.tsx index 12b25113..0001b683 100644 --- a/src/panelWebView/components/Fields/PreviewImageField.tsx +++ b/src/panelWebView/components/Fields/PreviewImageField.tsx @@ -7,7 +7,7 @@ import { BaseFieldProps, BlockFieldData } from '../../../models'; import { CommandToCode } from '../../CommandToCode'; import { FieldTitle } from './FieldTitle'; import { PreviewImage } from './PreviewImage'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface PreviewImageValue { original: string; @@ -25,6 +25,7 @@ export interface IPreviewImageFieldProps extends BaseFieldProps = ({ label, + description, fieldName, blockData, onChange, @@ -93,7 +94,7 @@ export const PreviewImageField: React.FunctionComponent } - + ); }; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/RequiredMessage.tsx b/src/panelWebView/components/Fields/RequiredMessage.tsx deleted file mode 100644 index d54a2f3c..00000000 --- a/src/panelWebView/components/Fields/RequiredMessage.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from 'react'; - -export interface IRequiredMessageProps { - name: string; - show?: boolean; -} - -export const RequiredMessage: React.FunctionComponent = ({ name, show }: React.PropsWithChildren) => { - - if (!show) { - return null; - } - - return ( -
- The {name} field is required. -
- ); -}; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/SlugField.tsx b/src/panelWebView/components/Fields/SlugField.tsx index e657181f..dd0998ad 100644 --- a/src/panelWebView/components/Fields/SlugField.tsx +++ b/src/panelWebView/components/Fields/SlugField.tsx @@ -7,7 +7,7 @@ import { BaseFieldProps } from '../../../models'; import { Command } from '../../Command'; import { CommandToCode } from '../../CommandToCode'; import { FieldTitle } from './FieldTitle'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface ISlugFieldProps extends BaseFieldProps { titleValue: string | null; @@ -15,7 +15,7 @@ export interface ISlugFieldProps extends BaseFieldProps { onChange: (txtValue: string) => void; } -export const SlugField: React.FunctionComponent = ({ label, editable, value, titleValue, onChange, required }: React.PropsWithChildren) => { +export const SlugField: React.FunctionComponent = ({ label, description, editable, value, titleValue, onChange, required }: React.PropsWithChildren) => { const [ text, setText ] = React.useState(value); const [ slug, setSlug ] = React.useState(value); @@ -84,7 +84,10 @@ export const SlugField: React.FunctionComponent = ({ label, edi - + ); } \ No newline at end of file diff --git a/src/panelWebView/components/Fields/TextField.tsx b/src/panelWebView/components/Fields/TextField.tsx index 51e84c5c..46f064af 100644 --- a/src/panelWebView/components/Fields/TextField.tsx +++ b/src/panelWebView/components/Fields/TextField.tsx @@ -5,7 +5,7 @@ import { useRecoilState } from 'recoil'; import { BaseFieldProps } from '../../../models'; import { RequiredFieldsAtom } from '../../state'; import { FieldTitle } from './FieldTitle'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface ITextFieldProps extends BaseFieldProps { singleLine: boolean | undefined; @@ -17,7 +17,7 @@ export interface ITextFieldProps extends BaseFieldProps { const WysiwygField = React.lazy(() => import('./WysiwygField')); -export const TextField: React.FunctionComponent = ({singleLine, wysiwyg, limit, label, value, rows, onChange, required}: React.PropsWithChildren) => { +export const TextField: React.FunctionComponent = ({singleLine, wysiwyg, limit, label, description, value, rows, onChange, required}: React.PropsWithChildren) => { const [ requiredFields, setRequiredFields ] = useRecoilState(RequiredFieldsAtom); const [ text, setText ] = React.useState(value); @@ -110,7 +110,10 @@ export const TextField: React.FunctionComponent = ({singleLine, ) } - + ); }; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/Toggle.tsx b/src/panelWebView/components/Fields/Toggle.tsx index 2a657d23..42691c29 100644 --- a/src/panelWebView/components/Fields/Toggle.tsx +++ b/src/panelWebView/components/Fields/Toggle.tsx @@ -3,13 +3,13 @@ import { useEffect, useMemo } from 'react'; import { BaseFieldProps } from '../../../models'; import { ToggleIcon } from '../Icons/ToggleIcon'; import { FieldTitle } from './FieldTitle'; -import { RequiredMessage } from './RequiredMessage'; +import { FieldMessage } from './FieldMessage'; export interface IToggleProps extends BaseFieldProps { onChanged: (checked: boolean) => void; } -export const Toggle: React.FunctionComponent = ({label, value, required, onChanged}: React.PropsWithChildren) => { +export const Toggle: React.FunctionComponent = ({label, description, value, required, onChanged}: React.PropsWithChildren) => { const [ isChecked, setIsChecked ] = React.useState(value); const onChange = (event: React.ChangeEvent) => { @@ -40,7 +40,7 @@ export const Toggle: React.FunctionComponent = ({label, value, req - + ); }; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/WrapperField.tsx b/src/panelWebView/components/Fields/WrapperField.tsx index 1e71b27d..9d8db899 100644 --- a/src/panelWebView/components/Fields/WrapperField.tsx +++ b/src/panelWebView/components/Fields/WrapperField.tsx @@ -116,6 +116,7 @@ export const WrapperField: React.FunctionComponent = ({ = ({ onSendUpdate(field.name, checked, parentFields)} /> @@ -146,6 +148,7 @@ export const WrapperField: React.FunctionComponent = ({ = ({ onSendUpdate(field.name, value, parentFields)} value={nrValue} required={!!field.required} /> @@ -176,6 +180,7 @@ export const WrapperField: React.FunctionComponent = ({ = ({ = ({ = ({ } crntSelected={fieldValue as string[] || []} @@ -243,6 +251,7 @@ export const WrapperField: React.FunctionComponent = ({ } crntSelected={fieldValue as string[] || []} options={taxonomyData?.options || []} @@ -263,6 +272,7 @@ export const WrapperField: React.FunctionComponent = ({ } crntSelected={fieldValue as string[] || []} @@ -289,6 +299,7 @@ export const WrapperField: React.FunctionComponent = ({ = ({ label={field.title || field.name} icon={undefined} required={field.required} /> + + { field.description && ( +
+ {field.description} +
+ )} { renderFields(field.fields, subMetadata, [...parentFields, field.name], blockData, onSendUpdate) } @@ -339,6 +356,7 @@ export const WrapperField: React.FunctionComponent = ({ = ({ = ({ onSendUpdate(field.name, value, parentFields))} /> @@ -379,6 +399,7 @@ export const WrapperField: React.FunctionComponent = ({ = (props: React.PropsWithChildren) => { - const { label, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable, fieldName, taxonomyId, parents, blockData, limit, required } = props; +const TagPicker: React.FunctionComponent = ({ label, description, icon, type, crntSelected, options, freeform, focussed, unsetFocus, disableConfigurable, fieldName, taxonomyId, parents, blockData, limit, required }: React.PropsWithChildren) => { const [ selected, setSelected ] = React.useState([]); const [ inputValue, setInputValue ] = React.useState(""); const prevSelected = usePrevious(crntSelected); @@ -280,7 +280,7 @@ const TagPicker: React.FunctionComponent = (props: React.PropsW } - + a?.toLowerCase() < b?.toLowerCase() ? -1 : 1 )} diff --git a/src/panelWebView/styles.css b/src/panelWebView/styles.css index 8fe6f619..b4520faf 100644 --- a/src/panelWebView/styles.css +++ b/src/panelWebView/styles.css @@ -61,7 +61,7 @@ border: 1px dashed var(--vscode-button-secondaryBackground); color: var(--vscode--settings-headerForeground); padding: .5rem 1rem; - margin-bottom: .5rem; + margin-bottom: 1rem; .autoform { background-color: transparent; @@ -319,6 +319,28 @@ outline: none; } +/* Description message */ +.metadata_field__description { + color: var(--vscode--settings-headerForeground); + opacity: .75; + margin-bottom: .5rem; +} + +.metadata_field__description, +.metadata_field__required__message { + padding-top: .5rem; + font-size: .9rem; + margin-left: .5rem; + margin-right: .5rem; +} + +.block_field > .metadata_field__description, +.metadata_field__box > .metadata_field__description { + margin-left: 0; + margin-top: -.5rem; + margin-bottom: 1rem; +} + /* Required field */ .metadata_field__required__asterix { color: var(--vscode-inputValidation-errorBorder); @@ -326,9 +348,9 @@ } .metadata_field__required__message { - color: var(--vscode-inputValidation-errorBorder); padding-top: .5rem; font-size: .9rem; + margin-left: .5rem; } /* Text field */ @@ -377,7 +399,7 @@ vscode-divider { .metadata_field__box { background: rgba(255, 255, 255, 0.1); border: 1px dashed rgba(255, 255, 255, 0.2); - margin-bottom: .5rem; + margin-bottom: 1rem; padding: .5rem 1rem; }