From d72bd959bc7c13d216e9c24ea41f3c8a9a5824a0 Mon Sep 17 00:00:00 2001 From: phoebus-84 <83974413+phoebus-84@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:37:22 +0100 Subject: [PATCH] feat: add input component (#58) * feat: add input component * fix: error and touched classes * test: fix input tests --- src/components.d.ts | 51 ++++++++++++ src/components/button/readme.md | 13 ++++ src/components/input/d-input.css | 22 ++++++ src/components/input/d-input.tsx | 90 ++++++++++++++++++++++ src/components/input/input.stories.ts | 65 ++++++++++++++++ src/components/input/readme.md | 52 +++++++++++++ src/components/input/test/d-input.e2e.ts | 11 +++ src/components/input/test/d-input.spec.tsx | 20 +++++ src/components/text/readme.md | 13 ++++ src/global/app.ts | 1 + src/global/global.css | 3 + 11 files changed, 341 insertions(+) create mode 100644 src/components/input/d-input.css create mode 100644 src/components/input/d-input.tsx create mode 100644 src/components/input/input.stories.ts create mode 100644 src/components/input/readme.md create mode 100644 src/components/input/test/d-input.e2e.ts create mode 100644 src/components/input/test/d-input.spec.tsx diff --git a/src/components.d.ts b/src/components.d.ts index 33161df..81e6ab5 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -56,6 +56,18 @@ export namespace Components { "color": Color; "size": Size; } + interface DInput { + "autoFocus": boolean; + "clearButton": boolean; + "errorText": string; + "helperText": string; + "label": string; + "name": string; + "personIcon": boolean; + "placeholder": string; + "type": 'text' | 'password' | 'email' | 'number'; + "value": string; + } interface DLogo { } interface DText { @@ -67,6 +79,10 @@ export interface DButtonCustomEvent extends CustomEvent { detail: T; target: HTMLDButtonElement; } +export interface DInputCustomEvent extends CustomEvent { + detail: T; + target: HTMLDInputElement; +} declare global { interface HTMLDAvatarElement extends Components.DAvatar, HTMLStencilElement { } @@ -122,6 +138,24 @@ declare global { prototype: HTMLDHeadingElement; new (): HTMLDHeadingElement; }; + interface HTMLDInputElementEventMap { + "dInput": string; + "dChange": string; + } + interface HTMLDInputElement extends Components.DInput, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLDInputElement, ev: DInputCustomEvent) => 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: HTMLDInputElement, ev: DInputCustomEvent) => 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 HTMLDInputElement: { + prototype: HTMLDInputElement; + new (): HTMLDInputElement; + }; interface HTMLDLogoElement extends Components.DLogo, HTMLStencilElement { } var HTMLDLogoElement: { @@ -142,6 +176,7 @@ declare global { "d-credential-service": HTMLDCredentialServiceElement; "d-definition": HTMLDDefinitionElement; "d-heading": HTMLDHeadingElement; + "d-input": HTMLDInputElement; "d-logo": HTMLDLogoElement; "d-text": HTMLDTextElement; } @@ -197,6 +232,20 @@ declare namespace LocalJSX { "color"?: Color; "size"?: Size; } + interface DInput { + "autoFocus"?: boolean; + "clearButton"?: boolean; + "errorText"?: string; + "helperText"?: string; + "label"?: string; + "name"?: string; + "onDChange"?: (event: DInputCustomEvent) => void; + "onDInput"?: (event: DInputCustomEvent) => void; + "personIcon"?: boolean; + "placeholder"?: string; + "type"?: 'text' | 'password' | 'email' | 'number'; + "value"?: string; + } interface DLogo { } interface DText { @@ -211,6 +260,7 @@ declare namespace LocalJSX { "d-credential-service": DCredentialService; "d-definition": DDefinition; "d-heading": DHeading; + "d-input": DInput; "d-logo": DLogo; "d-text": DText; } @@ -226,6 +276,7 @@ declare module "@stencil/core" { "d-credential-service": LocalJSX.DCredentialService & JSXBase.HTMLAttributes; "d-definition": LocalJSX.DDefinition & JSXBase.HTMLAttributes; "d-heading": LocalJSX.DHeading & JSXBase.HTMLAttributes; + "d-input": LocalJSX.DInput & JSXBase.HTMLAttributes; "d-logo": LocalJSX.DLogo & JSXBase.HTMLAttributes; "d-text": LocalJSX.DText & JSXBase.HTMLAttributes; } diff --git a/src/components/button/readme.md b/src/components/button/readme.md index c5b70f3..d1c0fb2 100644 --- a/src/components/button/readme.md +++ b/src/components/button/readme.md @@ -28,6 +28,19 @@ | `dFocus` | | `CustomEvent` | +## Dependencies + +### Used by + + - [d-input](../input) + +### Graph +```mermaid +graph TD; + d-input --> d-button + style d-button fill:#f9f,stroke:#333,stroke-width:4px +``` + ---------------------------------------------- *Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/input/d-input.css b/src/components/input/d-input.css new file mode 100644 index 0000000..5109a98 --- /dev/null +++ b/src/components/input/d-input.css @@ -0,0 +1,22 @@ +:host { + @apply flex flex-col items-start gap-2; +} + +.input { + --background: var(--input-background); + --highlight-color-focused: var(--accent); + --highlight-color-invalid: var(--error); + --highlight-color-valid: var(--success); +} + +.label { + @apply text-on; +} + +.error-text { + @apply text-error leading-3; +} + +.helper-text { + @apply text-on; +} diff --git a/src/components/input/d-input.tsx b/src/components/input/d-input.tsx new file mode 100644 index 0000000..ab04ba6 --- /dev/null +++ b/src/components/input/d-input.tsx @@ -0,0 +1,90 @@ +import { Component, Host, Prop, Event, h, EventEmitter } from '@stencil/core'; + +@Component({ + tag: 'd-input', + styleUrl: 'd-input.css', + shadow: true, +}) +export class DInput { + @Prop({ reflect: true }) type: 'text' | 'password' | 'email' | 'number' = 'text'; + @Prop({ reflect: true }) name: string; + @Prop({ reflect: true }) label: string; + @Prop({ reflect: true }) placeholder: string; + @Prop({ reflect: true }) helperText: string; + @Prop({ reflect: true }) errorText: string; + @Prop({ reflect: true }) value: string; + @Prop({ reflect: true }) clearButton: boolean; + @Prop({ reflect: true }) personIcon: boolean; + @Prop({ reflect: true }) autoFocus: boolean; + @Event() dInput!: EventEmitter; + @Event() dChange!: EventEmitter; + + private updateValue = (value: string) => { + this.dInput.emit(value); + }; + private clearValue = () => { + this.value = undefined; + }; + + render() { + return ( + + + {this.label} + + 0, + }} + type={this.type} + name={this.name} + fill="outline" + placeholder={this.placeholder} + autofocus={this.autoFocus} + class:ion-invalid={this.errorText} + class:ion-touched={this.errorText} + value={this.value} + onIonInput={e => { + this.updateValue(e.detail.value); + }} + onIonChange={e => { + this.updateValue(e.detail.value); + }} + > + {this.personIcon && ( +
+ + + + + +
+ )} + {this.clearButton && ( + + x + + )} +
+ {this.errorText && ( + + {this.errorText} + + )} + + {this.helperText && ( + + {this.helperText} + + )} +
+ ); + } +} diff --git a/src/components/input/input.stories.ts b/src/components/input/input.stories.ts new file mode 100644 index 0000000..bffe628 --- /dev/null +++ b/src/components/input/input.stories.ts @@ -0,0 +1,65 @@ +import { DInput } from './d-input'; +import { Meta, StoryObj } from '@storybook/html'; + +const meta = { + title: 'Design System/Atoms/Input', + render: args => + ``, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + name: 'input', + label: 'Label', + placeholder: 'Placeholder', + helperText: 'Helper text', + value: 'Value', + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/pdwfO3dMKtaCAQakht0JE6/DIDRoom-%2B-Signroom---WF-and-GUI---Dyne.org?type=design&node-id=1240-14918&mode=design&t=8XpkAMSjaMrPNeqn-0', + }, + }, +}; + +export const WithClearButton: Story = { + args: { + ...Default.args, + clearButton: true, + }, +}; + +export const WithPersonIcon: Story = { + args: { + ...Default.args, + personIcon: true, + }, +}; + +export const WithAutoFocus: Story = { + args: { + ...Default.args, + autoFocus: true, + }, +}; + +export const WithError: Story = { + args: { + ...Default.args, + errorText: 'Error text', + }, +}; diff --git a/src/components/input/readme.md b/src/components/input/readme.md new file mode 100644 index 0000000..013acf8 --- /dev/null +++ b/src/components/input/readme.md @@ -0,0 +1,52 @@ +# d-input + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------- | -------------- | ----------- | --------------------------------------------- | ----------- | +| `autoFocus` | `auto-focus` | | `boolean` | `undefined` | +| `clearButton` | `clear-button` | | `boolean` | `undefined` | +| `errorText` | `error-text` | | `string` | `undefined` | +| `helperText` | `helper-text` | | `string` | `undefined` | +| `label` | `label` | | `string` | `undefined` | +| `name` | `name` | | `string` | `undefined` | +| `personIcon` | `person-icon` | | `boolean` | `undefined` | +| `placeholder` | `placeholder` | | `string` | `undefined` | +| `type` | `type` | | `"email" \| "number" \| "password" \| "text"` | `'text'` | +| `value` | `value` | | `string` | `undefined` | + + +## Events + +| Event | Description | Type | +| --------- | ----------- | --------------------- | +| `dChange` | | `CustomEvent` | +| `dInput` | | `CustomEvent` | + + +## Dependencies + +### Depends on + +- [d-text](../text) +- ion-input +- [d-button](../button) + +### Graph +```mermaid +graph TD; + d-input --> d-text + d-input --> ion-input + d-input --> d-button + ion-input --> ion-icon + style d-input fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/components/input/test/d-input.e2e.ts b/src/components/input/test/d-input.e2e.ts new file mode 100644 index 0000000..4a4a2f6 --- /dev/null +++ b/src/components/input/test/d-input.e2e.ts @@ -0,0 +1,11 @@ +import { newE2EPage } from '@stencil/core/testing'; + +describe('d-input', () => { + it('renders', async () => { + const page = await newE2EPage(); + await page.setContent(''); + + const element = await page.find('d-input'); + expect(element).toHaveClass('hydrated'); + }); +}); diff --git a/src/components/input/test/d-input.spec.tsx b/src/components/input/test/d-input.spec.tsx new file mode 100644 index 0000000..7d0daa2 --- /dev/null +++ b/src/components/input/test/d-input.spec.tsx @@ -0,0 +1,20 @@ +import { newSpecPage } from '@stencil/core/testing'; +import { DInput } from '../d-input'; + +describe('d-input', () => { + it('renders', async () => { + const page = await newSpecPage({ + components: [DInput], + html: ``, + }); + expect(page.root).toEqualHtml(` + + + + + + + + `); + }); +}); diff --git a/src/components/text/readme.md b/src/components/text/readme.md index 7eeb29d..a81a129 100644 --- a/src/components/text/readme.md +++ b/src/components/text/readme.md @@ -13,6 +13,19 @@ | `size` | `size` | | `string` | `'m'` | +## Dependencies + +### Used by + + - [d-input](../input) + +### Graph +```mermaid +graph TD; + d-input --> d-text + style d-text fill:#f9f,stroke:#333,stroke-width:4px +``` + ---------------------------------------------- *Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/global/app.ts b/src/global/app.ts index e69de29..368d1b8 100644 --- a/src/global/app.ts +++ b/src/global/app.ts @@ -0,0 +1 @@ +import '@ionic/core' \ No newline at end of file diff --git a/src/global/global.css b/src/global/global.css index d514034..b3cc3bc 100644 --- a/src/global/global.css +++ b/src/global/global.css @@ -27,6 +27,7 @@ --sans-font-family: 'Gantari Variable', 'sans-serif'; --icon-font-family: 'Material Symbols Rounded'; --background-card-url: url('/dist/didroom-components/assets/rect.png'); + --input-background: #F9FAFB; } .dark { @@ -40,6 +41,7 @@ --warning: #ff9601; --error: #ff443b; --background-card-url: url('/dist/didroom-components/assets/rect-dark.png'); + --input-background: #374151; } @media (prefers-color-scheme: dark) { @@ -54,6 +56,7 @@ --warning: #ff9601; --error: #ff443b; --background-card-url: url('/dist/didroom-components/assets/rect-dark.png'); + --input-background: #374151; } } }