Skip to content

Commit

Permalink
feat: new button system with onclick and typology (#48)
Browse files Browse the repository at this point in the history
feat: add some props to button and refactor code

Co-authored-by: Puria Nafisi Azizi <[email protected]>
  • Loading branch information
phoebus-84 and puria authored Feb 15, 2024
1 parent cbbfd17 commit 31b094a
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 40 deletions.
34 changes: 31 additions & 3 deletions src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ export namespace Components {
"src"?: string;
}
interface DButton {
"buttonType": string;
"color"?: Color;
"disabled"?: boolean;
"href"?: string;
"disabled": boolean;
"expand"?: boolean;
"form"?: string | HTMLFormElement;
"href": string | undefined;
"size"?: 'small' | 'default' | 'large';
"type": 'submit' | 'reset' | 'button';
}
interface DCredentialCard {
"description"?: string;
Expand Down Expand Up @@ -57,14 +62,30 @@ export namespace Components {
"size": Size;
}
}
export interface DButtonCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLDButtonElement;
}
declare global {
interface HTMLDAvatarElement extends Components.DAvatar, HTMLStencilElement {
}
var HTMLDAvatarElement: {
prototype: HTMLDAvatarElement;
new (): HTMLDAvatarElement;
};
interface HTMLDButtonElementEventMap {
"dFocus": void;
"dBlur": void;
}
interface HTMLDButtonElement extends Components.DButton, HTMLStencilElement {
addEventListener<K extends keyof HTMLDButtonElementEventMap>(type: K, listener: (this: HTMLDButtonElement, ev: DButtonCustomEvent<HTMLDButtonElementEventMap[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 HTMLDButtonElementEventMap>(type: K, listener: (this: HTMLDButtonElement, ev: DButtonCustomEvent<HTMLDButtonElementEventMap[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 HTMLDButtonElement: {
prototype: HTMLDButtonElement;
Expand Down Expand Up @@ -132,9 +153,16 @@ declare namespace LocalJSX {
"src"?: string;
}
interface DButton {
"buttonType"?: string;
"color"?: Color;
"disabled"?: boolean;
"href"?: string;
"expand"?: boolean;
"form"?: string | HTMLFormElement;
"href"?: string | undefined;
"onDBlur"?: (event: DButtonCustomEvent<void>) => void;
"onDFocus"?: (event: DButtonCustomEvent<void>) => void;
"size"?: 'small' | 'default' | 'large';
"type"?: 'submit' | 'reset' | 'button';
}
interface DCredentialCard {
"description"?: string;
Expand Down
64 changes: 60 additions & 4 deletions src/components/button/button.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { ColorArgTypes } from '../types.js';

const meta = {
title: 'Design System/Atoms/Button',
render: args => `<d-button color=${args.color} disabled=${args.disabled}>BUTTON</d-button>`,
render: (args, story) => `<d-button color=${args.color} disabled=${args.disabled} ${args.href ? `href=${args.href}` : ''} size=${args.size} ${args.expand ? 'expand' : ''}>
${Boolean(story.parameters.slot) ? `<div slot="${story.parameters.slot.position}">${story.parameters.slot.icon}</div>` : ''}
${Boolean(story.parameters.slot?.position == 'icon-only') ? '' : 'BUTTON'}</d-button>`,
argTypes: {
disabled: { control: 'boolean', description: 'Disable the button' },
color: ColorArgTypes,
Expand All @@ -17,7 +19,6 @@ type Story = StoryObj<Components.DButton>;
export const Default: Story = {
args: {
color: 'primary',
href: '#',
disabled: false,
},
parameters: {
Expand All @@ -39,8 +40,7 @@ export const Accent: Story = {
...Default.args,
color: 'accent',
},
};
export const AccentDisabled: Story = {
};export const AccentDisabled: Story = {
args: {
...Default.args,
color: 'accent',
Expand All @@ -54,9 +54,65 @@ export const PrimaryDisabled: Story = {
disabled: true,
},
};
export const Expand: Story = {
args: {
...Default.args,
expand: true,
},
};
export const IconBefore: Story = {
args: {
...Default.args,
},
parameters: {
slot: {
position: 'start',
icon: '🚀',
},
},
};

export const IconAfter: Story = {
args: {
...Default.args,
},
parameters: {
slot: {
position: 'end',
icon: '🚀',
},
},
};
export const IconOnly: Story = {
args: {
...Default.args,
},
parameters: {
slot: {
position: 'icon-only',
icon: '🚀',
},
},
};


export const Link: Story = {
args: {
...Default.args,
href: '/',
},
};

export const Small: Story = {
args: {
...Default.args,
size: 'small',
},
};

export const Large: Story = {
args: {
...Default.args,
size: 'large',
},
};
60 changes: 54 additions & 6 deletions src/components/button/d-button.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
:host > a,
:host > button {
@apply flex w-full justify-center items-center shrink-0 rounded px-0 py-2 text-center text-base not-italic font-semibold leading-8 tracking-[0.16px] uppercase;
@apply flex w-fit justify-center items-center shrink-0 rounded px-4 py-2 text-center text-base not-italic font-semibold leading-8 tracking-[0.16px] uppercase;
}
:host(.button-block) {
@apply block;
}
:host(.button-block) .button-native {
@apply mx-0 w-full clear-both;

contain: content;
}

:host([disabled]:not([disabled='false'])) > a,
:host([disabled]:not([disabled='false'])) > button {
:host(.button-disabled) > a,
:host(.button-disabled) > button {
pointer-events: none;
cursor: not-allowed;
&.accent {
Expand All @@ -15,8 +23,8 @@
@apply opacity-60 text-opacity-60;
}
}

.accent {
:host(.accent) > a,
:host(.accent) > button {
@apply bg-accent text-on-accent;
* {
@apply text-on-accent;
Expand All @@ -26,7 +34,8 @@
}
}

.primary {
:host(.primary) > a,
:host(.primary) > button {
@apply bg-primary text-on;
* {
@apply text-on;
Expand All @@ -35,3 +44,42 @@
background: linear-gradient(0deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.1) 100%), var(--primary);
}
}

.button-inner {
@apply flex flex-row flex-shrink-0 items-center justify-center w-full h-full z-10;
}

::slotted([slot='start']),
::slotted([slot='end']) {
@apply shrink-0;
}

::slotted([slot='start']) {
// @include margin(0, .3em, 0, -.3em);
@apply mr-1 -ml-1 my-0;
}

::slotted([slot='end']) {
// @include margin(0, -.2em, 0, .3em);
@apply ml-1 -mr-1 my-0;
}

::slotted([slot='icon-only']) {
// font-size: 1.8em;
@apply text-2xl;
}

:host(.button-large) > button,
:host(.button-large) > a {
@apply px-5 py-4 text-lg min-h-7;
}

:host(.button-small) > button,
:host(.button-small) > a {
@apply px-2 py-1 text-sm min-h-2;
}

:host(.button-has-icon-only) > button,
:host(.button-has-icon-only) > a {
@apply px-3;
}
104 changes: 82 additions & 22 deletions src/components/button/d-button.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,94 @@
import { Component, Host, Prop, h } from '@stencil/core';
import { Component, Element, Event, Host, Prop, Watch, h } from '@stencil/core';
import { Color } from '../types';
import type { EventEmitter, ComponentInterface } from '@stencil/core';

const hasShadowDom = (el: HTMLElement) => {
return !!el.shadowRoot && !!(el as any).attachShadow;
};

@Component({
tag: 'd-button',
styleUrl: 'd-button.scss',
shadow: true,
})
export class DButton {
@Prop() color?: Color = 'primary';
// @Prop() outline?: boolean = false;
@Prop({ reflect: true }) href?: string;
@Prop({ reflect: true }) disabled?: boolean = false;
export class DButton implements ComponentInterface {
private formButtonEl: HTMLButtonElement | null = null;
private formEl: HTMLFormElement | null = null;

@Element() el!: HTMLElement;
@Prop({ reflect: true }) color?: Color = 'primary';
@Prop({ mutable: true }) buttonType = 'button';
@Prop({ reflect: true }) disabled = false;
@Watch('disabled')
disabledChanged() {
const { disabled } = this;
if (this.formButtonEl) {
this.formButtonEl.disabled = disabled;
}
}
@Prop({ reflect: true }) expand?: boolean;
@Prop() href: string | undefined;
@Prop({ reflect: true }) size?: 'small' | 'default' | 'large';
@Prop() type: 'submit' | 'reset' | 'button' = 'button';
@Prop() form?: string | HTMLFormElement;
@Event() dFocus!: EventEmitter<void>;
@Event() dBlur!: EventEmitter<void>;


private get hasIconOnly() {
return !!this.el.querySelector('[slot="icon-only"]');
}

private submitForm(ev: Event) {
if (this.formEl && this.formButtonEl) {
ev.preventDefault();
this.formButtonEl.click();
}
}

private handleClick = (ev: Event) => {
const { el } = this;
if (hasShadowDom(el)) {
this.submitForm(ev);
}
};

private onFocus = () => {
this.dFocus.emit();
};

private onBlur = () => {
this.dBlur.emit();
};

render() {
if (this.href) {
return (
<Host>
<a class={this.color} href={this.href}>
<slot></slot>
</a>
</Host>
);
} else {
return (
<Host>
<button class={this.color} disabled={this.disabled} aria-disabled={this.disabled}>
const { buttonType, type, disabled, size, href, color, expand, hasIconOnly } = this;
const finalSize = size === undefined ? 'small' : size;
const TagType = href === undefined ? 'button' : ('a' as any);
const attrs = TagType === 'button' ? { type } : { href };

return (
<Host
onClick={this.handleClick}
aria-disabled={disabled ? 'true' : null}
class={{
[color]: true,
[buttonType]: true,
['button-block']: expand,
[`${buttonType}-${finalSize}`]: finalSize !== undefined,
'button-has-icon-only': hasIconOnly,
'button-disabled': disabled,
}}
>
<TagType {...attrs} class={`button-native ${color}`} part="native" disabled={disabled} onFocus={this.onFocus} onBlur={this.onBlur}>
<span class="button-inner">
<slot name="icon-only"></slot>
<slot name="start"></slot>
<slot></slot>
</button>
</Host>
);
}
<slot name="end"></slot>
</span>
</TagType>
</Host>
);
}
}
23 changes: 18 additions & 5 deletions src/components/button/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,24 @@

## Properties

| Property | Attribute | Description | Type | Default |
| ---------- | ---------- | ----------- | --------- | ----------- |
| `color` | `color` | | `string` | `'primary'` |
| `disabled` | `disabled` | | `boolean` | `false` |
| `href` | `href` | | `string` | `undefined` |
| Property | Attribute | Description | Type | Default |
| ------------ | ------------- | ----------- | --------------------------------- | ----------- |
| `buttonType` | `button-type` | | `string` | `'button'` |
| `color` | `color` | | `string` | `'primary'` |
| `disabled` | `disabled` | | `boolean` | `false` |
| `expand` | `expand` | | `boolean` | `undefined` |
| `form` | `form` | | `HTMLFormElement \| string` | `undefined` |
| `href` | `href` | | `string` | `undefined` |
| `size` | `size` | | `"default" \| "large" \| "small"` | `undefined` |
| `type` | `type` | | `"button" \| "reset" \| "submit"` | `'button'` |


## Events

| Event | Description | Type |
| -------- | ----------- | ------------------- |
| `dBlur` | | `CustomEvent<void>` |
| `dFocus` | | `CustomEvent<void>` |


----------------------------------------------
Expand Down

0 comments on commit 31b094a

Please sign in to comment.