Skip to content

Commit

Permalink
feat: add input component (#58)
Browse files Browse the repository at this point in the history
* feat: add input component

* fix: error and touched classes

* test: fix input tests
  • Loading branch information
phoebus-84 authored Mar 1, 2024
1 parent b99fc0d commit d72bd95
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 0 deletions.
51 changes: 51 additions & 0 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -67,6 +79,10 @@ export interface DButtonCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLDButtonElement;
}
export interface DInputCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLDInputElement;
}
declare global {
interface HTMLDAvatarElement extends Components.DAvatar, HTMLStencilElement {
}
Expand Down Expand Up @@ -122,6 +138,24 @@ declare global {
prototype: HTMLDHeadingElement;
new (): HTMLDHeadingElement;
};
interface HTMLDInputElementEventMap {
"dInput": string;
"dChange": string;
}
interface HTMLDInputElement extends Components.DInput, HTMLStencilElement {
addEventListener<K extends keyof HTMLDInputElementEventMap>(type: K, listener: (this: HTMLDInputElement, ev: DInputCustomEvent<HTMLDInputElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLDInputElementEventMap>(type: K, listener: (this: HTMLDInputElement, ev: DInputCustomEvent<HTMLDInputElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(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: {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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<string>) => void;
"onDInput"?: (event: DInputCustomEvent<string>) => void;
"personIcon"?: boolean;
"placeholder"?: string;
"type"?: 'text' | 'password' | 'email' | 'number';
"value"?: string;
}
interface DLogo {
}
interface DText {
Expand All @@ -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;
}
Expand All @@ -226,6 +276,7 @@ declare module "@stencil/core" {
"d-credential-service": LocalJSX.DCredentialService & JSXBase.HTMLAttributes<HTMLDCredentialServiceElement>;
"d-definition": LocalJSX.DDefinition & JSXBase.HTMLAttributes<HTMLDDefinitionElement>;
"d-heading": LocalJSX.DHeading & JSXBase.HTMLAttributes<HTMLDHeadingElement>;
"d-input": LocalJSX.DInput & JSXBase.HTMLAttributes<HTMLDInputElement>;
"d-logo": LocalJSX.DLogo & JSXBase.HTMLAttributes<HTMLDLogoElement>;
"d-text": LocalJSX.DText & JSXBase.HTMLAttributes<HTMLDTextElement>;
}
Expand Down
13 changes: 13 additions & 0 deletions src/components/button/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@
| `dFocus` | | `CustomEvent<void>` |


## 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/)*
22 changes: 22 additions & 0 deletions src/components/input/d-input.css
Original file line number Diff line number Diff line change
@@ -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;
}
90 changes: 90 additions & 0 deletions src/components/input/d-input.tsx
Original file line number Diff line number Diff line change
@@ -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<string>;
@Event() dChange!: EventEmitter<string>;

private updateValue = (value: string) => {
this.dInput.emit(value);
};
private clearValue = () => {
this.value = undefined;
};

render() {
return (
<Host>
<d-text class="label" size="m">
{this.label}
</d-text>
<ion-input
class={{
'input': true,
'ion-invalid': Boolean(this.errorText),
'ion-touched': this.value && this.value.length > 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 && (
<div slot="start">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="user">
<path
id="Vector"
d="M11.7678 7.76777C11.2989 8.23661 10.663 8.5 10 8.5C9.33696 8.5 8.70107 8.23661 8.23223 7.76777C7.76339 7.29893 7.5 6.66304 7.5 6C7.5 5.33696 7.76339 4.70107 8.23223 4.23223C8.70107 3.76339 9.33696 3.5 10 3.5C10.663 3.5 11.2989 3.76339 11.7678 4.23223C12.2366 4.70107 12.5 5.33696 12.5 6C12.5 6.66304 12.2366 7.29893 11.7678 7.76777ZM3.51926 17.5C3.5719 16.8176 3.73208 16.1468 3.99478 15.5126C4.32144 14.7239 4.80022 14.0074 5.40381 13.4038C6.00739 12.8002 6.72394 12.3214 7.51256 11.9948C8.30117 11.6681 9.14641 11.5 10 11.5C10.8536 11.5 11.6988 11.6681 12.4874 11.9948C13.2761 12.3214 13.9926 12.8002 14.5962 13.4038C15.1998 14.0074 15.6786 14.7239 16.0052 15.5126C16.2679 16.1468 16.4281 16.8176 16.4807 17.5H3.51926Z"
fill="#6B7280"
stroke="#6B7280"
/>
</g>
</svg>
</div>
)}
{this.clearButton && (
<d-button slot="end" clear onClick={this.clearValue}>
x
</d-button>
)}
</ion-input>
{this.errorText && (
<d-text class="error-text" size="s">
{this.errorText}
</d-text>
)}
<slot />
{this.helperText && (
<d-text class="helper-text" size="s">
{this.helperText}
</d-text>
)}
</Host>
);
}
}
65 changes: 65 additions & 0 deletions src/components/input/input.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { DInput } from './d-input';
import { Meta, StoryObj } from '@storybook/html';

const meta = {
title: 'Design System/Atoms/Input',
render: args =>
`<d-input
name="${args.name}"
label="${args.label}"
value="${args.value}"
placeholder="${args.placeholder}"
helper-text="${args.helperText}"
${args.errorText ? `error-text="${args.errorText}"` : ''}
${args.clearButton ? 'clear-button' : ''}
${args.personIcon ? 'person-icon' : ''}
${args.autoFocus ? 'autofocus' : ''}
></d-input>`,
} satisfies Meta<DInput>;

export default meta;
type Story = StoryObj<DInput>;

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',
},
};
52 changes: 52 additions & 0 deletions src/components/input/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# d-input



<!-- Auto Generated Below -->


## 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<string>` |
| `dInput` | | `CustomEvent<string>` |


## 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/)*
11 changes: 11 additions & 0 deletions src/components/input/test/d-input.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { newE2EPage } from '@stencil/core/testing';

describe('d-input', () => {
it('renders', async () => {
const page = await newE2EPage();
await page.setContent('<d-input></d-input>');

const element = await page.find('d-input');
expect(element).toHaveClass('hydrated');
});
});
20 changes: 20 additions & 0 deletions src/components/input/test/d-input.spec.tsx
Original file line number Diff line number Diff line change
@@ -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: `<d-input></d-input>`,
});
expect(page.root).toEqualHtml(`
<d-input type="text">
<mock:shadow-root>
<d-text class="label" size="m"></d-text>
<ion-input class="input" fill="outline" type="text"></ion-input>
<slot></slot>
</mock:shadow-root>
</d-input>
`);
});
});
Loading

0 comments on commit d72bd95

Please sign in to comment.