Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add input component #58

Merged
merged 3 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading