diff --git a/CHANGELOG.md b/CHANGELOG.md index ba502792..76f01b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### 🎨 Enhancements +- [#307](https://github.com/estruyf/vscode-front-matter/issues/307): New `list` field which allows to create a list of items - [#345](https://github.com/estruyf/vscode-front-matter/issues/345): Media dashboard UI improvements to visualize the content and public folders ### 🐞 Fixes diff --git a/package.json b/package.json index 71b18ae2..94aa6ae8 100644 --- a/package.json +++ b/package.json @@ -806,6 +806,7 @@ "fields", "json", "block", + "list", "dataFile" ], "description": "Define the type of field" diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 01f07729..2fd27387 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -48,7 +48,7 @@ export interface ContentType { pageBundle?: boolean; } -export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file" | "dataFile"; +export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file" | "dataFile" | "list"; export interface Field { title?: string; diff --git a/src/panelWebView/components/DataBlock/DataBlockField.tsx b/src/panelWebView/components/DataBlock/DataBlockField.tsx index 582900e1..bc540fc9 100644 --- a/src/panelWebView/components/DataBlock/DataBlockField.tsx +++ b/src/panelWebView/components/DataBlock/DataBlockField.tsx @@ -198,7 +198,7 @@ export const DataBlockField: React.FunctionComponent = ({ } return ( -
+
diff --git a/src/panelWebView/components/Fields/ListField.tsx b/src/panelWebView/components/Fields/ListField.tsx new file mode 100644 index 00000000..4acef0ed --- /dev/null +++ b/src/panelWebView/components/Fields/ListField.tsx @@ -0,0 +1,126 @@ +import { PencilIcon, TrashIcon, ViewListIcon } from '@heroicons/react/outline'; +import * as React from 'react'; +import { useCallback, useRef } from 'react'; +import { VsLabel } from '../VscodeComponents'; + +export interface IListFieldProps { + label: string; + value: string[] | null; + onChange: (value: string | string[]) => void; +} + +export const ListField: React.FunctionComponent = ({ label, value, onChange }: React.PropsWithChildren) => { + const [ text, setText ] = React.useState(""); + const [ list, setList ] = React.useState(value); + const [ itemToEdit, setItemToEdit ] = React.useState(null); + const inputRef = useRef(null); + + const onTextChange = (txtValue: string) => { + setText(txtValue); + // onChange(txtValue); + }; + + const onSaveForm = useCallback(() => { + if (itemToEdit !== null) { + if (list && text) { + const newList = [...(list || [])]; + newList[itemToEdit] = text; + setList(newList); + onChange(newList); + } + } else { + if (text) { + const newList = list ? [ ...list, text ] : [ text ]; + setList(newList); + onChange(newList); + } + } + + onCancelForm(); + }, [list, text]); + + const onEdit = useCallback((index: number) => { + setItemToEdit(index); + setText(list ? list[index] : ""); + inputRef.current?.focus(); + }, [list]); + + const onCancelForm = useCallback(() => { + setText(""); + setItemToEdit(null); + }, []); + + const onDelete = useCallback((index: number) => { + if (list) { + const newList = [...list]; + newList.splice(index, 1); + setList(newList); + onChange(newList); + } + }, [list]); + + let isValid = true; + + return ( +
+ +
+ {label} +
+
+ + onTextChange(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + onSaveForm(); + } + }} + style={{ + border: isValid ? "1px solid var(--vscode-inputValidation-infoBorder)" : "1px solid var(--vscode-inputValidation-warningBorder)" + }} /> + +
+ + +
+ +
    + { + list && list.map((item, index) => ( +
  • +
    + {item} +
    + +
    + + +
    +
  • + )) + } +
+
+ ); +}; \ No newline at end of file diff --git a/src/panelWebView/components/Fields/WrapperField.tsx b/src/panelWebView/components/Fields/WrapperField.tsx index 6c829502..16025490 100644 --- a/src/panelWebView/components/Fields/WrapperField.tsx +++ b/src/panelWebView/components/Fields/WrapperField.tsx @@ -21,6 +21,7 @@ import { DataFileField } from './DataFileField'; import { DateTimeField } from './DateTimeField'; import { DraftField } from './DraftField'; import { FileField } from './FileField'; +import { ListField } from './ListField'; import { NumberField } from './NumberField'; import { PreviewImageField, PreviewImageValue } from './PreviewImageField'; import { TextField } from './TextField'; @@ -360,6 +361,15 @@ export const WrapperField: React.FunctionComponent = ({ onChange={(value => onSendUpdate(field.name, value, parentFields))} /> ); + } else if (field.type === 'list') { + return ( + + onSendUpdate(field.name, value, parentFields))} /> + + ); } else { console.warn(`Unknown field type: ${field.type}`); return null; diff --git a/src/panelWebView/styles.css b/src/panelWebView/styles.css index 83840f5b..200d0ec5 100644 --- a/src/panelWebView/styles.css +++ b/src/panelWebView/styles.css @@ -1,11 +1,13 @@ -.block_field__form { +.block_field__form, +.list_field__form { background-color: transparent; border: 1px dashed var(--vscode-button-background); padding: 1rem; } -.block_field__form__buttons { +.block_field__form__buttons, +.list_field__form__buttons { display: flex; justify-content: space-between; margin-top: 1rem; @@ -23,7 +25,8 @@ } } -.block_field__form__button__save { +.block_field__form__button__save, +.list_field__form__button__save { background-color: var(--vscode-button-background); color: var(--vscode-button-foreground); @@ -32,7 +35,8 @@ } } -.block_field__form__button__cancel { +.block_field__form__button__cancel, +.list_field__form__button__cancel { background-color: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); @@ -41,7 +45,9 @@ } } -.json_data__field { +.json_data__field, +.block_field, +.list_field { border: 1px dashed var(--vscode-button-secondaryBackground); color: var(--vscode--settings-headerForeground); padding: .5rem 1rem; @@ -120,7 +126,12 @@ } } -.json_data__list { +.list_field__list { + padding-left: 0; +} + +.json_data__list, +.list_field__list { margin-top: 1rem; label { @@ -229,7 +240,8 @@ } .sortable_item button, -.json_data__list__button { +.json_data__list__button, +.list_field__list__button { width: auto; background: none; color: inherit; @@ -244,7 +256,8 @@ } } -.json_data__list__button_icon { +.json_data__list__button_icon, +.list_field__list__button_icon { height: 1rem; width: 1rem; }