diff --git a/packages/react-library/lib/components/stencil-generated/index.ts b/packages/react-library/lib/components/stencil-generated/index.ts index 01e8a6dc3..54cd6b13a 100644 --- a/packages/react-library/lib/components/stencil-generated/index.ts +++ b/packages/react-library/lib/components/stencil-generated/index.ts @@ -28,6 +28,7 @@ export const DnnSelect = /*@__PURE__*/createReactComponent('dnn-sort-icon'); export const DnnTab = /*@__PURE__*/createReactComponent('dnn-tab'); export const DnnTabs = /*@__PURE__*/createReactComponent('dnn-tabs'); +export const DnnTextarea = /*@__PURE__*/createReactComponent('dnn-textarea'); export const DnnToggle = /*@__PURE__*/createReactComponent('dnn-toggle'); export const DnnTreeviewItem = /*@__PURE__*/createReactComponent('dnn-treeview-item'); export const DnnVerticalOverflowMenu = /*@__PURE__*/createReactComponent('dnn-vertical-overflow-menu'); diff --git a/packages/stencil-library/custom-elements.json b/packages/stencil-library/custom-elements.json index 98a758d63..92d8c3285 100644 --- a/packages/stencil-library/custom-elements.json +++ b/packages/stencil-library/custom-elements.json @@ -716,6 +716,15 @@ }, "description": "Sets the text of the fieldset label (caption).", "required": false + }, + { + "name": "resizable", + "type": { + "text": "\"block\" | \"both\" | \"horizontal\" | \"inline\" | \"none\" | \"vertical\"" + }, + "description": "Can be set to specify if the fieldset can be resized by the user.", + "default": "\"none\"", + "required": false } ], "members": [ @@ -1778,6 +1787,164 @@ } ] }, + { + "kind": "javascript-module", + "path": "src/components/dnn-textarea/dnn-textarea.tsx", + "declarations": [ + { + "kind": "class", + "name": "dnn-textarea.tsx", + "tagName": "dnn-textarea", + "description": "A custom textarea component.", + "attributes": [ + { + "name": "autocomplete", + "type": { + "text": "string" + }, + "description": "Defines the type of auto-completion to use for this field, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete.", + "default": "\"off\"", + "required": false + }, + { + "name": "disabled", + "type": { + "text": "boolean" + }, + "description": "Defines whether the field is disabled.", + "required": false + }, + { + "name": "help-text", + "type": { + "text": "string" + }, + "description": "Defines the help label displayed under the field.", + "required": false + }, + { + "name": "label", + "type": { + "text": "string" + }, + "description": "The label for this input.", + "required": false + }, + { + "name": "maxlength", + "type": { + "text": "number" + }, + "description": "Defines the maximum amount of charaters.", + "required": false + }, + { + "name": "minlength", + "type": { + "text": "number" + }, + "description": "Defines the minimum amount of charaters.", + "required": false + }, + { + "name": "name", + "type": { + "text": "string" + }, + "description": "The name for this input when used in forms.", + "required": false + }, + { + "name": "readonly", + "type": { + "text": "boolean" + }, + "description": "Defines wheter the defined value is readonly.", + "required": false + }, + { + "name": "required", + "type": { + "text": "boolean" + }, + "description": "Defines whether the field requires having a value.", + "required": false + }, + { + "name": "resizable", + "type": { + "text": "\"block\" | \"both\" | \"horizontal\" | \"inline\" | \"none\" | \"vertical\"" + }, + "description": "Can be set to change how the user can resize the field.", + "default": "\"block\"", + "required": false + }, + { + "name": "value", + "type": { + "text": "string" + }, + "description": "Sets the value of the textarea.", + "required": false + } + ], + "members": [ + { + "kind": "method", + "name": "checkValidity", + "description": "Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState", + "signature": "checkValidity() => Promise" + }, + { + "kind": "method", + "name": "setCustomValidity", + "description": "Can be used to set a custom validity message.", + "signature": "setCustomValidity(message: string) => Promise" + } + ], + "events": [ + { + "name": "valueChange", + "type": { + "text": "string" + }, + "description": "Fires when the value has changed and the user exits the input." + }, + { + "name": "valueInput", + "type": { + "text": "string" + }, + "description": "Fires when the using is inputing data (on keystrokes)." + } + ], + "slots": [], + "cssProperties": [ + { + "name": "--background", + "description": "Defines the background color." + }, + { + "name": "--control-radius", + "description": "Defines the radius for the control corners." + }, + { + "name": "--danger-color", + "description": "Defines the danger color used for invalid data." + }, + { + "name": "--focus-color", + "description": "Defines the color when the component is focused." + }, + { + "name": "--foreground", + "description": "Defines the foreground color." + } + ], + "cssParts": [] + } + ] + }, { "kind": "javascript-module", "path": "src/components/dnn-toggle/dnn-toggle.tsx", diff --git a/packages/stencil-library/src/components.d.ts b/packages/stencil-library/src/components.d.ts index 5b6cc0ae0..2eb9e7a92 100644 --- a/packages/stencil-library/src/components.d.ts +++ b/packages/stencil-library/src/components.d.ts @@ -260,6 +260,10 @@ export namespace Components { * Places the label on the top of the container. */ "pinLabel": () => Promise; + /** + * Can be set to specify if the fieldset can be resized by the user. + */ + "resizable": "none" | "both" | "horizontal" | "vertical" | "block" | "inline"; /** * Unsets the fieldset focused state. */ @@ -566,6 +570,63 @@ export namespace Components { } interface DnnTabs { } + /** + * A custom textarea component. + */ + interface DnnTextarea { + /** + * Defines the type of auto-completion to use for this field, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete. + */ + "autocomplete": string; + /** + * Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState + */ + "checkValidity": () => Promise; + /** + * Defines whether the field is disabled. + */ + "disabled": boolean; + /** + * Defines the help label displayed under the field. + */ + "helpText": string; + /** + * The label for this input. + */ + "label": string; + /** + * Defines the maximum amount of charaters. + */ + "maxlength": number; + /** + * Defines the minimum amount of charaters. + */ + "minlength": number; + /** + * The name for this input when used in forms. + */ + "name": string; + /** + * Defines wheter the defined value is readonly. + */ + "readonly": boolean; + /** + * Defines whether the field requires having a value. + */ + "required": boolean; + /** + * Can be set to change how the user can resize the field. + */ + "resizable": "none" | "both" | "horizontal" | "vertical" | "block" | "inline"; + /** + * Can be used to set a custom validity message. + */ + "setCustomValidity": (message: string) => Promise; + /** + * Sets the value of the textarea. + */ + "value": string; + } interface DnnToggle { /** * If 'true' the toggle is checked (on). @@ -678,6 +739,10 @@ export interface DnnSortIconCustomEvent extends CustomEvent { detail: T; target: HTMLDnnSortIconElement; } +export interface DnnTextareaCustomEvent extends CustomEvent { + detail: T; + target: HTMLDnnTextareaElement; +} export interface DnnToggleCustomEvent extends CustomEvent { detail: T; target: HTMLDnnToggleElement; @@ -1021,6 +1086,27 @@ declare global { prototype: HTMLDnnTabsElement; new (): HTMLDnnTabsElement; }; + interface HTMLDnnTextareaElementEventMap { + "valueInput": string; + "valueChange": string; + } + /** + * A custom textarea component. + */ + interface HTMLDnnTextareaElement extends Components.DnnTextarea, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLDnnTextareaElement, ev: DnnTextareaCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLDnnTextareaElement, ev: DnnTextareaCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLDnnTextareaElement: { + prototype: HTMLDnnTextareaElement; + new (): HTMLDnnTextareaElement; + }; interface HTMLDnnToggleElementEventMap { "checkChanged": DnnToggleChangeEventDetail; } @@ -1104,6 +1190,7 @@ declare global { "dnn-sort-icon": HTMLDnnSortIconElement; "dnn-tab": HTMLDnnTabElement; "dnn-tabs": HTMLDnnTabsElement; + "dnn-textarea": HTMLDnnTextareaElement; "dnn-toggle": HTMLDnnToggleElement; "dnn-treeview-item": HTMLDnnTreeviewItemElement; "dnn-vertical-overflow-menu": HTMLDnnVerticalOverflowMenuElement; @@ -1362,6 +1449,10 @@ declare namespace LocalJSX { * Sets the text of the fieldset label (caption). */ "label"?: string; + /** + * Can be set to specify if the fieldset can be resized by the user. + */ + "resizable"?: "none" | "both" | "horizontal" | "vertical" | "block" | "inline"; } /** * Allows cropping an image in-browser with the option to enforce a specific final size. @@ -1666,6 +1757,63 @@ declare namespace LocalJSX { } interface DnnTabs { } + /** + * A custom textarea component. + */ + interface DnnTextarea { + /** + * Defines the type of auto-completion to use for this field, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete. + */ + "autocomplete"?: string; + /** + * Defines whether the field is disabled. + */ + "disabled"?: boolean; + /** + * Defines the help label displayed under the field. + */ + "helpText"?: string; + /** + * The label for this input. + */ + "label"?: string; + /** + * Defines the maximum amount of charaters. + */ + "maxlength"?: number; + /** + * Defines the minimum amount of charaters. + */ + "minlength"?: number; + /** + * The name for this input when used in forms. + */ + "name"?: string; + /** + * Fires when the value has changed and the user exits the input. + */ + "onValueChange"?: (event: DnnTextareaCustomEvent) => void; + /** + * Fires when the using is inputing data (on keystrokes). + */ + "onValueInput"?: (event: DnnTextareaCustomEvent) => void; + /** + * Defines wheter the defined value is readonly. + */ + "readonly"?: boolean; + /** + * Defines whether the field requires having a value. + */ + "required"?: boolean; + /** + * Can be set to change how the user can resize the field. + */ + "resizable"?: "none" | "both" | "horizontal" | "vertical" | "block" | "inline"; + /** + * Sets the value of the textarea. + */ + "value"?: string; + } interface DnnToggle { /** * If 'true' the toggle is checked (on). @@ -1743,6 +1891,7 @@ declare namespace LocalJSX { "dnn-sort-icon": DnnSortIcon; "dnn-tab": DnnTab; "dnn-tabs": DnnTabs; + "dnn-textarea": DnnTextarea; "dnn-toggle": DnnToggle; "dnn-treeview-item": DnnTreeviewItem; "dnn-vertical-overflow-menu": DnnVerticalOverflowMenu; @@ -1797,6 +1946,10 @@ declare module "@stencil/core" { */ "dnn-tab": LocalJSX.DnnTab & JSXBase.HTMLAttributes; "dnn-tabs": LocalJSX.DnnTabs & JSXBase.HTMLAttributes; + /** + * A custom textarea component. + */ + "dnn-textarea": LocalJSX.DnnTextarea & JSXBase.HTMLAttributes; "dnn-toggle": LocalJSX.DnnToggle & JSXBase.HTMLAttributes; "dnn-treeview-item": LocalJSX.DnnTreeviewItem & JSXBase.HTMLAttributes; /** diff --git a/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.scss b/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.scss index ab3e1b47e..695341605 100644 --- a/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.scss +++ b/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.scss @@ -27,10 +27,15 @@ position: relative; background-color: var(--fieldset-background); margin-top: 1em; + .resizer{ + width: 100%; + } .inner-container{ position: relative; width: 100%; background-color: var(--fieldset-background); + max-width: calc(100% - 1em); + height: calc(100% - 1em); } label{ display: inline-flex; @@ -45,6 +50,8 @@ max-width: 100%; border-radius: var(--fieldset-control-radius); font-size: 1em; + margin-top: 1em; + z-index: 1; } &.focused{ border: 1px solid var(--fieldset-focus-color); @@ -62,6 +69,7 @@ opacity: 0.6; left: 0; top: calc(50% - 0.5em); + margin-top:0; } } &.disabled{ diff --git a/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.tsx b/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.tsx index 0bdaefa85..af31a7348 100644 --- a/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.tsx +++ b/packages/stencil-library/src/components/dnn-fieldset/dnn-fieldset.tsx @@ -29,6 +29,9 @@ export class DnnFieldset { /** Can be used to show some help text about this field. */ @Prop() helpText: string; + /** Can be set to specify if the fieldset can be resized by the user. */ + @Prop() resizable: "none" | "both" | "horizontal" | "vertical" | "block" | "inline" = "none"; + /** Sets the fieldset to the focused state. */ @Method() async setFocused() { @@ -101,15 +104,17 @@ export class DnnFieldset { return (
-
- {this.label && - - } - + {this.label && + + } +
+
+ +
{this.invalid && this.customValidityMessage && diff --git a/packages/stencil-library/src/components/dnn-fieldset/readme.md b/packages/stencil-library/src/components/dnn-fieldset/readme.md index e3380d208..9cf5f7ed0 100644 --- a/packages/stencil-library/src/components/dnn-fieldset/readme.md +++ b/packages/stencil-library/src/components/dnn-fieldset/readme.md @@ -11,14 +11,15 @@ A custom input component that wraps the html input element is a mobile friendly ## Properties -| Property | Attribute | Description | Type | Default | -| ------------ | ------------- | ---------------------------------------------------------------------------- | --------- | ----------- | -| `disabled` | `disabled` | If true, the fieldset will display as disabled. | `boolean` | `undefined` | -| `floatLabel` | `float-label` | If true, the label will float in the container, set false to show it on top. | `boolean` | `undefined` | -| `focused` | `focused` | If true the fieldset will display as focused. | `boolean` | `undefined` | -| `helpText` | `help-text` | Can be used to show some help text about this field. | `string` | `undefined` | -| `invalid` | `invalid` | If true, the fieldset will display as invalid. | `boolean` | `undefined` | -| `label` | `label` | Sets the text of the fieldset label (caption). | `string` | `undefined` | +| Property | Attribute | Description | Type | Default | +| ------------ | ------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------- | +| `disabled` | `disabled` | If true, the fieldset will display as disabled. | `boolean` | `undefined` | +| `floatLabel` | `float-label` | If true, the label will float in the container, set false to show it on top. | `boolean` | `undefined` | +| `focused` | `focused` | If true the fieldset will display as focused. | `boolean` | `undefined` | +| `helpText` | `help-text` | Can be used to show some help text about this field. | `string` | `undefined` | +| `invalid` | `invalid` | If true, the fieldset will display as invalid. | `boolean` | `undefined` | +| `label` | `label` | Sets the text of the fieldset label (caption). | `string` | `undefined` | +| `resizable` | `resizable` | Can be set to specify if the fieldset can be resized by the user. | `"block" \| "both" \| "horizontal" \| "inline" \| "none" \| "vertical"` | `"none"` | ## Methods @@ -128,6 +129,7 @@ Type: `Promise` - [dnn-example-form](../examples/dnn-example-form) - [dnn-input](../dnn-input) - [dnn-select](../dnn-select) + - [dnn-textarea](../dnn-textarea) ### Graph ```mermaid @@ -136,6 +138,7 @@ graph TD; dnn-example-form --> dnn-fieldset dnn-input --> dnn-fieldset dnn-select --> dnn-fieldset + dnn-textarea --> dnn-fieldset style dnn-fieldset fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/packages/stencil-library/src/components/dnn-input/dnn-input.tsx b/packages/stencil-library/src/components/dnn-input/dnn-input.tsx index f88cda4fe..efc1c8e7e 100644 --- a/packages/stencil-library/src/components/dnn-input/dnn-input.tsx +++ b/packages/stencil-library/src/components/dnn-input/dnn-input.tsx @@ -169,6 +169,7 @@ export class DnnInput { label={`${this.label ?? ""}${this.required ? " *" : ""}`} helpText={this.helpText} id={this.labelId} + disabled={this.disabled} floatLabel={this.shouldLabelFloat()} onClick={() => !this.focused && this.inputField.focus()} > @@ -178,7 +179,6 @@ export class DnnInput { ref={el => this.inputField = el} name={this.name} type={this.type} - aria-label={this.label} disabled={this.disabled} required={this.required} autoComplete={this.autocomplete} diff --git a/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.scss b/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.scss new file mode 100644 index 000000000..60b2c6e8f --- /dev/null +++ b/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.scss @@ -0,0 +1,32 @@ +:host { + display: inline-block; + + /** @prop --foreground: Defines the foreground color. */ + --foreground: var(--dnn-color-foreground, #000); + + /** @prop --background: Defines the background color. */ + --background: var(--dnn-color-background, #fff); + + /** @prop --focus-color: Defines the color when the component is focused. */ + --focus-color: var(--dnn-color-primary, #3792ED); + + /** @prop --danger-color: Defines the danger color used for invalid data. */ + --danger-color: var(--dnn-color-danger, #900); + + /** @prop --control-radius: Defines the radius for the control corners. */ + --control-radius: var(--dnn-controls-radius, 3px); +} + +dnn-fieldset{ + width: 100%; +} + +textarea{ + border: none; + outline: none; + background-color: transparent; + color: var(--foreground); + width: 100%; + height: 100%; + resize: none; +} \ No newline at end of file diff --git a/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.stories.ts b/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.stories.ts new file mode 100644 index 000000000..913413309 --- /dev/null +++ b/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.stories.ts @@ -0,0 +1,84 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html, nothing, TemplateResult } from 'lit'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; +import { actions } from '@storybook/addon-actions'; +import readme from "./readme.md"; + +const meta: Meta = { + title: 'Elements/Textarea', + component: 'dnn-textarea', + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: readme, + } + } + }, + argTypes: { + autocomplete: { + type: 'string', + }, + disabled: { + control: 'boolean', + }, + "help-text": { + control: 'text', + }, + label: { + control: 'text', + }, + minlength: { + control: 'number', + }, + maxlength: { + control: 'number', + }, + name: { + control: 'text', + }, + readonly: { + control: 'boolean', + }, + required: { + control: 'boolean', + }, + value: { + control: 'text', + }, + }, +}; +export default meta; + +const eventsFromNames = actions("valueChange", "valueInput"); + +const Template = (args) => + html` + eventsFromNames.valueChange(e)} + @valueInput=${e => eventsFromNames.valueInput(e)} + > + + `; + + +type Story = StoryObj; + +export const Text : Story = Template.bind({}); +Text.args = { + autocomplete: "off", + disabled: false, + readonly: false, + required: false, +}; \ No newline at end of file diff --git a/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.tsx b/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.tsx new file mode 100644 index 000000000..8d5ce03da --- /dev/null +++ b/packages/stencil-library/src/components/dnn-textarea/dnn-textarea.tsx @@ -0,0 +1,157 @@ +import { Component, Host, h, Prop, AttachInternals, State, Event, EventEmitter, Method } from '@stencil/core'; +import { generateRandomId } from '../../utilities/stringUtilities'; + +/** A custom textarea component. */ +@Component({ + tag: 'dnn-textarea', + styleUrl: 'dnn-textarea.scss', + shadow: true, + formAssociated: true, +}) +export class DnnTextarea { + /** Can be set to change how the user can resize the field. */ + @Prop() resizable: "none" | "both" | "horizontal" | "vertical" | "block" | "inline" = "block"; + + /** Sets the value of the textarea. */ + @Prop({mutable: true}) value: string; + + /** The label for this input. */ + @Prop() label: string; + + /** The name for this input when used in forms. */ + @Prop() name: string; + + /** Defines the help label displayed under the field. */ + @Prop() helpText: string; + + /** Defines whether the field requires having a value. */ + @Prop() required: boolean; + + /** Defines whether the field is disabled. */ + @Prop() disabled: boolean; + + /** Defines the type of auto-completion to use for this field, see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete. */ + @Prop() autocomplete: string = "off"; + + /** Defines the minimum amount of charaters. */ + @Prop() minlength: number; + + /** Defines the maximum amount of charaters. */ + @Prop() maxlength: number; + + /** Defines wheter the defined value is readonly. */ + @Prop() readonly: boolean; + + /** Fires when the using is inputing data (on keystrokes). */ + @Event() valueInput: EventEmitter; + + /** Fires when the value has changed and the user exits the input. */ + @Event() valueChange: EventEmitter; + + /** Reports the input validity details. See https://developer.mozilla.org/en-US/docs/Web/API/ValidityState */ + @Method() + async checkValidity(): Promise { + return this.textarea.validity; + } + + /** Can be used to set a custom validity message. */ + @Method() + async setCustomValidity(message: string): Promise { + this.customValidityMessage = message; + return this.textarea.setCustomValidity(message); + } + + @State() focused = false; + @State() valid = true; + @State() customValidityMessage: string; + + @AttachInternals() internals: ElementInternals; + + private textarea: HTMLTextAreaElement; + private labelId: string; + + componentWillLoad() { + this.labelId = generateRandomId(); + } + + // eslint-disable-next-line @stencil-community/own-methods-must-be-private + formResetCallback() { + this.textarea.setCustomValidity(""); + this.valid = true; + this.value = ""; + this.internals.setValidity({}); + this.internals.setFormValue(""); + } + + private handleInput(value: string): void { + this.value = value; + var valid = this.textarea.checkValidity(); + this.valid = valid; + this.valueInput.emit(this.value); + } + + private handleInvalid(): void { + this.valid = false; + if (this.customValidityMessage == undefined){ + this.customValidityMessage = this.textarea.validationMessage; + } + } + + private handleChange() { + this.valueChange.emit(this.value); + if (this.name != undefined){ + var data = new FormData(); + data.append(this.name, this.value.toString()); + this.internals.setFormValue(data); + } + } + + private shouldLabelFloat(): boolean { + if (this.focused) { + return false; + } + + if (this.value != undefined && this.value != "") { + return false; + } + + return true; + } + + render() { + return ( + + !this.focused && this.textarea.focus()} + > +