From 31b094a9228287b4721b02265f71f3a25db24c47 Mon Sep 17 00:00:00 2001 From: phoebus-84 <83974413+phoebus-84@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:57:59 +0100 Subject: [PATCH] feat: new button system with onclick and typology (#48) feat: add some props to button and refactor code Co-authored-by: Puria Nafisi Azizi --- src/components.d.ts | 34 +++++++- src/components/button/button.stories.ts | 64 ++++++++++++++- src/components/button/d-button.scss | 60 ++++++++++++-- src/components/button/d-button.tsx | 104 +++++++++++++++++++----- src/components/button/readme.md | 23 ++++-- 5 files changed, 245 insertions(+), 40 deletions(-) diff --git a/src/components.d.ts b/src/components.d.ts index 9b5e7b5..a6ecfcb 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -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; @@ -57,6 +62,10 @@ export namespace Components { "size": Size; } } +export interface DButtonCustomEvent extends CustomEvent { + detail: T; + target: HTMLDButtonElement; +} declare global { interface HTMLDAvatarElement extends Components.DAvatar, HTMLStencilElement { } @@ -64,7 +73,19 @@ declare global { prototype: HTMLDAvatarElement; new (): HTMLDAvatarElement; }; + interface HTMLDButtonElementEventMap { + "dFocus": void; + "dBlur": void; + } interface HTMLDButtonElement extends Components.DButton, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLDButtonElement, ev: DButtonCustomEvent) => 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: HTMLDButtonElement, ev: DButtonCustomEvent) => 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 HTMLDButtonElement: { prototype: HTMLDButtonElement; @@ -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; + "onDFocus"?: (event: DButtonCustomEvent) => void; + "size"?: 'small' | 'default' | 'large'; + "type"?: 'submit' | 'reset' | 'button'; } interface DCredentialCard { "description"?: string; diff --git a/src/components/button/button.stories.ts b/src/components/button/button.stories.ts index 4e10089..89d87b4 100644 --- a/src/components/button/button.stories.ts +++ b/src/components/button/button.stories.ts @@ -4,7 +4,9 @@ import { ColorArgTypes } from '../types.js'; const meta = { title: 'Design System/Atoms/Button', - render: args => `BUTTON`, + render: (args, story) => ` + ${Boolean(story.parameters.slot) ? `
${story.parameters.slot.icon}
` : ''} + ${Boolean(story.parameters.slot?.position == 'icon-only') ? '' : 'BUTTON'}
`, argTypes: { disabled: { control: 'boolean', description: 'Disable the button' }, color: ColorArgTypes, @@ -17,7 +19,6 @@ type Story = StoryObj; export const Default: Story = { args: { color: 'primary', - href: '#', disabled: false, }, parameters: { @@ -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', @@ -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', + }, +}; diff --git a/src/components/button/d-button.scss b/src/components/button/d-button.scss index 3853b9e..a1d897e 100644 --- a/src/components/button/d-button.scss +++ b/src/components/button/d-button.scss @@ -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 { @@ -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; @@ -26,7 +34,8 @@ } } -.primary { +:host(.primary) > a, +:host(.primary) > button { @apply bg-primary text-on; * { @apply text-on; @@ -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; +} diff --git a/src/components/button/d-button.tsx b/src/components/button/d-button.tsx index 0ccb330..1023367 100644 --- a/src/components/button/d-button.tsx +++ b/src/components/button/d-button.tsx @@ -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; + @Event() dBlur!: EventEmitter; + + + 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 ( - - - - - - ); - } else { - return ( - - - - ); - } + + + + + ); } } diff --git a/src/components/button/readme.md b/src/components/button/readme.md index 1467e82..0cc66d0 100644 --- a/src/components/button/readme.md +++ b/src/components/button/readme.md @@ -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` | +| `dFocus` | | `CustomEvent` | ----------------------------------------------