diff --git a/packages/ecc-utils-design/package.json b/packages/ecc-utils-design/package.json index 833857e6..331ca5ce 100644 --- a/packages/ecc-utils-design/package.json +++ b/packages/ecc-utils-design/package.json @@ -7,10 +7,7 @@ "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - }, + ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }, "./dist/custom-elements.json": "./dist/custom-elements.json", "./dist/index.js": "./dist/index.js", "./dist/components/*": "./dist/components/*", diff --git a/packages/ecc-utils-design/src/components/form/form.ts b/packages/ecc-utils-design/src/components/form/form.ts index 2e49dcf2..3da95a63 100644 --- a/packages/ecc-utils-design/src/components/form/form.ts +++ b/packages/ecc-utils-design/src/components/form/form.ts @@ -1,54 +1,17 @@ import { html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators.js"; -import "@shoelace-style/shoelace/dist/components/input/input.js"; import "@shoelace-style/shoelace/dist/components/button/button.js"; -import "@shoelace-style/shoelace/dist/components/switch/switch.js"; -import "@shoelace-style/shoelace/dist/components/icon-button/icon-button.js"; -import "@shoelace-style/shoelace/dist/components/alert/alert.js"; -import "@shoelace-style/shoelace/dist/components/details/details.js"; -import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; import _ from "lodash-es"; import getShoelaceStyles from "../../styles/shoelace.styles.js"; import { hostStyles } from "../../styles/host.styles.js"; import formStyles from "./form.styles.js"; - -export interface Field { - key: string; - label: string; - type?: - | "text" - | "date" - | "number" - | "email" - | "password" - | "tel" - | "url" - | "search" - | "datetime-local" - | "time" - | "array" - | "switch" - | "file" - | "group"; - fieldOptions?: { - required?: boolean; - default?: string | boolean; - multiple?: boolean; - accept?: string; - returnIfEmpty?: string; - tooltip?: string; - }; - arrayOptions?: { - defaultInstances?: number; - max?: number; - min?: number; - }; - groupOptions?: { - collapsible: boolean; - }; - error?: string; - children?: Array; -} +import { Field } from "./types.js"; +import inputTemplate from "./templates/inputTemplate.js"; +import arrayTemplate from "./templates/arrayTemplate.js"; +import groupTemplate from "./templates/groupTemplate.js"; +import errorTemplate from "./templates/errorTemplate.js"; +import successTemplate from "./templates/successTemplate.js"; +import switchTemplate from "./templates/switchTemplate.js"; /** * @summary This component is used to render a form with the given fields. @@ -62,7 +25,6 @@ export interface Field { * * @event ecc-utils-submit - This event is fired when the form is submitted. The event detail contains the form data. */ - export default class EccUtilsDesignForm extends LitElement { static styles = [ getShoelaceStyles( @@ -127,39 +89,22 @@ export default class EccUtilsDesignForm extends LitElement { const { switchControl, switchThumb, label, switchLabel, formControl } = this.cssParts; - return html` -
- ${field.fieldOptions?.tooltip && field.fieldOptions.tooltip !== "" - ? html` - - - - ` - : html` - - `} - { - _.set(this.form, path, (e.target as HTMLInputElement).checked); - this.requestUpdate(); - }} - > - -
- `; + + const parts = [ + formControl, + `${label} ${switchLabel}`, + `control: ${switchControl}, thumb: ${switchThumb}`, + ]; + + const changeAction = (e: Event) => { + _.set(this.form, path, (e.target as HTMLInputElement).checked); + this.requestUpdate(); + }; + + return switchTemplate(field, parts, _.get(this.form, path), changeAction); } - renderInputTemplate(field: Field, path: string): TemplateResult { + private renderInputTemplate(field: Field, path: string): TemplateResult { if ( field.type === "array" || field.type === "switch" || @@ -169,88 +114,54 @@ export default class EccUtilsDesignForm extends LitElement { const { formControl, formControlLabel, input, inputBase, label } = this.cssParts; - if (field.type === "file") { - return html` -
- ${field.fieldOptions?.tooltip && field.fieldOptions.tooltip !== "" - ? html` - - - - ` - : html` - - `} - { - const { files } = e.target as HTMLInputElement; - _.set(this.form, path, files); - this.requestUpdate(); - }} - /> -
- `; - } - if (!_.get(this.form, path)) { - if (field.fieldOptions?.default && !this.hasUpdated) { - _.set(this.form, path, field.fieldOptions.default); - } else if (field.fieldOptions?.returnIfEmpty) { - _.set(this.form, path, ""); - } - } - - return html` - { - const { value } = e.target as HTMLInputElement; - if (!value) { - _.unset(this.form, path); - } else { - _.set(this.form, path, value); + const changeAction = + field.type === "file" + ? (e: Event) => { + const { files } = e.target as HTMLInputElement; + _.set(this.form, path, files); + this.requestUpdate(); } + : (e: Event) => { + const { value } = e.target as HTMLInputElement; + if (!value) { + _.unset(this.form, path); + } else { + _.set(this.form, path, value); + } - this.requestUpdate(); - }} - > - - ` - : html` `} - - - `; + this.requestUpdate(); + }; + + const emptyFieldAction = () => { + if (!_.get(this.form, path)) { + if (field.fieldOptions?.default && !this.hasUpdated) { + _.set(this.form, path, field.fieldOptions.default); + } else if (field.fieldOptions?.returnIfEmpty) { + _.set(this.form, path, ""); + } + } + }; + + return inputTemplate( + field, + [ + formControl, + `${label} ${formControlLabel}`, + `${inputBase} ${input}`, + `form-control: ${formControl}, form-control-label: ${formControlLabel}, form-control-label: ${label}, input: ${input}, base: ${inputBase}`, + ], + changeAction, + emptyFieldAction, + _.get(this.form, path) + ); } private renderArrayTemplate(field: Field, path: string): TemplateResult { const { arrayOptions } = field; if (!_.get(this.form, path)) { - const defaultCount = field.arrayOptions?.defaultInstances; + const defaultCount = arrayOptions?.defaultInstances; if (defaultCount) { _.set( this.form, @@ -285,95 +196,49 @@ export default class EccUtilsDesignForm extends LitElement { arrayItem, item, } = this.cssParts; - return html` -
-
- ${field.fieldOptions?.tooltip && field.fieldOptions.tooltip !== "" - ? html` - - - - ` - : html` - - `} - { - if (resolveAddButtonIsActive()) { - const instances: [] = _.get(this.form, path) || []; - _.set(this.form, path, [...instances, {}]); - this.requestUpdate(); - } - }} - > - - - - Add - -
- ${_.get(this.form, path)?.map( - (_item: any, index: number) => html` -
- { - resolveDeleteButtonIsActive() && - _.get(this.form, path).splice(index, 1) && - this.requestUpdate(); - }} - > - - - - -
- ${field.children?.map((child) => - this.renderTemplate(child, `${path}[${index}]`) - )} -
-
- ` - )} -
- `; + + const parts = [ + `${container} ${arrayContainer}`, + `header: ${arrayHeader}, header: ${header}`, + `${label} ${arrayLabel}`, + `base: ${button}, base: ${addButton}`, + `${item} ${arrayItem}`, + `base: ${button}, base: ${removeButton}`, + ]; + + const addAction = () => { + if (resolveAddButtonIsActive()) { + const instances: [] = _.get(this.form, path) || []; + _.set(this.form, path, [...instances, {}]); + this.requestUpdate(); + } + }; + + const deleteAction = (index: number) => { + resolveDeleteButtonIsActive() && + _.get(this.form, path).splice(index, 1) && + this.requestUpdate(); + }; + + const renderChildren = (index: number) => { + if (field.children?.length) + return html` ${field.children?.map((child) => + this.renderTemplate(child, `${path}[${index}]`) + )}`; + + return html``; + }; + + return arrayTemplate( + field, + parts, + resolveAddButtonIsActive(), + resolveDeleteButtonIsActive(), + addAction, + deleteAction, + renderChildren, + _.get(this.form, path) + ); } private renderGroupTemplate(field: Field, path: string): TemplateResult { @@ -390,6 +255,12 @@ export default class EccUtilsDesignForm extends LitElement { groupLabel, groupToggleIcon, } = this.cssParts; + + const parts = [ + `base: ${groupBase}, header: ${groupHeader}, header: ${header}, summary: ${label}, summary: ${groupLabel}, summary-icon: ${groupToggleIcon}, content: ${groupContent}`, + `${header} ${groupHeader}`, + `${label} ${groupLabel}`, + ]; const renderChildren = () => html`
@@ -399,35 +270,7 @@ export default class EccUtilsDesignForm extends LitElement {
`; - return html`
- ${field.groupOptions?.collapsible - ? html` - ${renderChildren()} - ` - : html` -
- ${field.fieldOptions?.tooltip && field.fieldOptions.tooltip !== "" - ? html` - - - - ` - : html` - - `} -
- ${renderChildren()} - `} -
`; + return groupTemplate(field, parts, renderChildren); } private renderTemplate(field: Field, path: string): TemplateResult { @@ -450,49 +293,13 @@ export default class EccUtilsDesignForm extends LitElement { private renderErrorTemplate(): TemplateResult { if (this.formState !== "error") return html``; - return html` - - - - ${this.errorMessage} - `; + return errorTemplate(this.errorMessage); } private renderSuccessTemplate(): TemplateResult { if (this.formState !== "success") return html``; - return html` - - - - - - ${this.successMessage} - - `; + + return successTemplate(this.successMessage); } public disableSubmit(disable = true) { @@ -574,3 +381,5 @@ export default class EccUtilsDesignForm extends LitElement { `; } } + +export { Field }; diff --git a/packages/ecc-utils-design/src/components/form/templates/arrayTemplate.ts b/packages/ecc-utils-design/src/components/form/templates/arrayTemplate.ts new file mode 100644 index 00000000..5ca39455 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/templates/arrayTemplate.ts @@ -0,0 +1,84 @@ +import { TemplateResult, html } from "lit"; +import { Field } from "../types.js"; +import { renderLabel } from "../utils.js"; +import "@shoelace-style/shoelace/dist/components/button/button.js"; + +export default ( + field: Field, + parts: Array, + addButtonIsActive: boolean, + deleteButtonIsActive: boolean, + addAction: () => void, + deleteAction: (index: number) => void, + renderChildren: (index: number) => TemplateResult, + children = [] +) => { + const arrayItem = (index: number) => html` +
+ deleteAction(index)} + > + + + + +
${renderChildren(index)}
+
+ `; + + return html` +
+
+ ${renderLabel( + { + part: parts[2], + class: "array-label", + content: field.label, + required: field.fieldOptions?.required, + }, + { content: field.fieldOptions?.tooltip } + )} + + + + + Add + +
+ ${children.map((_item: any, index: number) => arrayItem(index))} +
+ `; +}; diff --git a/packages/ecc-utils-design/src/components/form/templates/errorTemplate.ts b/packages/ecc-utils-design/src/components/form/templates/errorTemplate.ts new file mode 100644 index 00000000..16e93487 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/templates/errorTemplate.ts @@ -0,0 +1,21 @@ +import { html } from "lit"; +import "@shoelace-style/shoelace/dist/components/alert/alert.js"; + +export default (errorMessage: string) => html` + + + + ${errorMessage} + `; diff --git a/packages/ecc-utils-design/src/components/form/templates/groupTemplate.ts b/packages/ecc-utils-design/src/components/form/templates/groupTemplate.ts new file mode 100644 index 00000000..6431385c --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/templates/groupTemplate.ts @@ -0,0 +1,32 @@ +import { TemplateResult, html } from "lit"; +import { Field } from "../types.js"; +import { renderLabel } from "../utils.js"; +import "@shoelace-style/shoelace/dist/components/details/details.js"; + +export default ( + field: Field, + parts: Array, + renderChildren: () => TemplateResult +) => html`
+ ${field.groupOptions?.collapsible + ? html` + ${renderChildren()} + ` + : html` +
+ ${renderLabel( + { + part: parts[2], + class: "group-label", + content: field.label, + required: field.fieldOptions?.required, + }, + { content: field.fieldOptions?.tooltip } + )} +
+
${renderChildren()}
+ `} +
`; diff --git a/packages/ecc-utils-design/src/components/form/templates/inputTemplate.ts b/packages/ecc-utils-design/src/components/form/templates/inputTemplate.ts new file mode 100644 index 00000000..082f0862 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/templates/inputTemplate.ts @@ -0,0 +1,61 @@ +import { html } from "lit"; +import { renderLabel } from "../utils.js"; +import { Field } from "../types.js"; +import "@shoelace-style/shoelace/dist/components/input/input.js"; +import "@shoelace-style/shoelace/dist/components/button/button.js"; + +export default function ( + field: Field, + parts: Array, + changeAction: (e: Event) => void, + emptyFieldAction: () => void, + value = "" +) { + if (field.type === "file") { + return html` +
+ ${renderLabel( + { + part: parts[1], + class: "file-input-label", + content: field.label, + required: field.fieldOptions?.required, + }, + { content: field.fieldOptions?.tooltip } + )} + +
+ `; + } + + emptyFieldAction(); + + return html` + + + + `; +} diff --git a/packages/ecc-utils-design/src/components/form/templates/successTemplate.ts b/packages/ecc-utils-design/src/components/form/templates/successTemplate.ts new file mode 100644 index 00000000..a57b90b4 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/templates/successTemplate.ts @@ -0,0 +1,24 @@ +import { html } from "lit"; +import "@shoelace-style/shoelace/dist/components/alert/alert.js"; + +export default (successMessage: string) => html` + + + + + + ${successMessage} + +`; diff --git a/packages/ecc-utils-design/src/components/form/templates/switchTemplate.ts b/packages/ecc-utils-design/src/components/form/templates/switchTemplate.ts new file mode 100644 index 00000000..243deb52 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/templates/switchTemplate.ts @@ -0,0 +1,34 @@ +import { html } from "lit"; +import { Field } from "../types.js"; +import { renderLabel } from "../utils.js"; +import "@shoelace-style/shoelace/dist/components/switch/switch.js"; + +export default ( + field: Field, + parts: Array, + checked: boolean, + changeAction: (e: Event) => void +) => html` +
+ ${renderLabel( + { + part: parts[1], + class: "switch-label", + content: field.label, + required: field.fieldOptions?.required, + }, + { content: field.fieldOptions?.tooltip } + )} + + + +
+`; diff --git a/packages/ecc-utils-design/src/components/form/types.ts b/packages/ecc-utils-design/src/components/form/types.ts new file mode 100644 index 00000000..f883e2b7 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/types.ts @@ -0,0 +1,37 @@ +export interface Field { + key: string; + label: string; + type?: + | "text" + | "date" + | "number" + | "email" + | "password" + | "tel" + | "url" + | "search" + | "datetime-local" + | "time" + | "array" + | "switch" + | "file" + | "group"; + fieldOptions?: { + required?: boolean; + default?: string | boolean; + multiple?: boolean; + accept?: string; + returnIfEmpty?: string; + tooltip?: string; + }; + arrayOptions?: { + defaultInstances?: number; + max?: number; + min?: number; + }; + groupOptions?: { + collapsible: boolean; + }; + error?: string; + children?: Array; +} diff --git a/packages/ecc-utils-design/src/components/form/utils.ts b/packages/ecc-utils-design/src/components/form/utils.ts new file mode 100644 index 00000000..87444ec5 --- /dev/null +++ b/packages/ecc-utils-design/src/components/form/utils.ts @@ -0,0 +1,31 @@ +import { html } from "lit"; +import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js"; + +export interface Label { + part?: string; + class?: string; + content: string; + required?: boolean; +} + +export interface Tooltip { + content: string | undefined; +} + +export const renderLabel = (label: Label, tooltip: Tooltip) => { + const labelComponent = () => html` + + `; + + if (tooltip.content) { + return html` + + ${labelComponent()} + + `; + } + + return labelComponent(); +};