Skip to content

Commit

Permalink
feat: Add Button component
Browse files Browse the repository at this point in the history
  • Loading branch information
ynotdraw committed Jan 20, 2023
1 parent 8cc0ca9 commit 550fda4
Show file tree
Hide file tree
Showing 11 changed files with 440 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
},
"dependencies": {
"@crowdstrike/ember-oss-docs": "^1.1.0",
"@crowdstrike/ember-toucan-core": "^0.0.0",
"@ember/test-waiters": "^3.0.2",
"@embroider/router": "^1.9.0",
"dompurify": "^2.4.0",
Expand Down
15 changes: 15 additions & 0 deletions docs/components/button/demo/base-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```hbs template
<Button @onClick={{this.onClick}} @variant='secondary'>Button</Button>
```

```js component
import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class extends Component {
@action
onClick(e) {
alert('Button clicked!');
}
}
```
148 changes: 148 additions & 0 deletions docs/components/button/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Button

Buttons are clickable elements used primarily for actions. Button content expresses what action will occur when the user interacts with it.

## Variants

You can customize the appearance of the button with the `@variant` component argument.

<div class="flex gap-x-4">
<Button @variant="primary">Primary</Button>
<Button @variant="secondary">Secondary</Button>
<Button @variant="destructive">Destructive</Button>
<Button @variant="link">Link</Button>
<Button @variant="quiet">Quiet</Button>
<Button @variant="bare">Bare</Button>
</div>

## Type

To provide the [type attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) to the Button, the `@type` component argument is used.

```hbs
<Button @type='submit'>Submit</Button>
```

## Handling Clicks

To handle click events use the `@onClick` component argument.

```hbs
<Button @onClick={{this.handleClick}}>Click Me</Button>
```

## Disabled State

`aria-disabled` is used over the `disabled` attribute so that screenreaders can still focus the element. To set the button as disabled, use `@isDisabled`.

```hbs
<Button @isDisabled={{true}}>Disabled</Button>
```

A disabled named block is provided so that users can optionally render additional content when the button is disabled.

```hbs
<Button @isDisabled={{true}}>
<:disabled>
<svg
class='h-4 w-4'
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
stroke='currentColor'
viewBox='0 0 24 24'
>
<path
d='M18.644 21h-13.2a.945.945 0 01-1-1v-7.2a.945.945 0 011-1h13.1a.945.945 0 011 1V20a.808.808 0 01-.225.725.966.966 0 01-.675.275zm-10.9-9.2V7.3a4.3 4.3 0 118.6 0v4.5m-4.3 3.7v2'
fill='none'
stroke-linecap='round'
stroke-linejoin='round'
stroke-width='2'
/>
</svg>
</:disabled>
<:default>
Disabled
</:default>
</Button>
```

<div class="flex gap-x-4">
{{#each (array "primary" "secondary" "destructive" "link" "quiet" "bare") as |variant|}}
<Button @variant={{variant}} @isDisabled={{true}}>
<:disabled>
<svg
class='h-4 w-4'
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
stroke='currentColor'
viewBox='0 0 24 24'
>
<path
d="M18.644 21h-13.2a.945.945 0 01-1-1v-7.2a.945.945 0 011-1h13.1a.945.945 0 011 1V20a.808.808 0 01-.225.725.966.966 0 01-.675.275zm-10.9-9.2V7.3a4.3 4.3 0 118.6 0v4.5m-4.3 3.7v2"
fill='none'
stroke-linecap='round'
stroke-linejoin='round'
stroke-width='2'
/>
</svg>
</:disabled>

<:default>
{{variant}}
</:default>
</Button>
{{/each}}

</div>

## Loading State

Button exposes an `@isLoading` component argument. The button content will be only visible to screenreaders.

```hbs
<Button @isLoading={{true}}>Loading…</Button>
```

A loading named block is also provided for providing custom loading content.

```hbs
<Button @isLoading={{true}}>
<:loading>
<svg
class='h-4 w-4 animate-spin'
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
stroke='currentColor'
viewBox='0 0 24 24'
><path
d='M5.95 5.7L7 6.75 8.05 7.8m8.4 8.4l.95.95.95.95m.2-12.4L17.5 6.75 16.45 7.8M6.35 12h-3.1m17.5 0h-2.6m-5.9 9v-3.1m0-14.9v3.1'
fill='none'
stroke-linecap='round'
stroke-linejoin='round'
stroke-width='2'
/></svg>
</:loading>
<:default>
Loading…
</:default>
</Button>
```

<div class="flex gap-x-4">
{{#each (array "primary" "secondary" "destructive" "link" "quiet" "bare") as |variant|}}
<Button @variant={{variant}} @isLoading={{true}}>
<:loading>
<svg class='h-4 w-4 animate-spin' xmlns="http://www.w3.org/2000/svg" width="24" height="24" stroke="currentColor" viewBox="0 0 24 24"><path d="M5.95 5.7L7 6.75 8.05 7.8m8.4 8.4l.95.95.95.95m.2-12.4L17.5 6.75 16.45 7.8M6.35 12h-3.1m17.5 0h-2.6m-5.9 9v-3.1m0-14.9v3.1" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" /></svg>
</:loading>

<:default>
{{variant}}
</:default>
</Button>
{{/each}}

</div>
1 change: 0 additions & 1 deletion docs/demos/demo-a.md

This file was deleted.

5 changes: 4 additions & 1 deletion ember-toucan-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"tailwindcss": "^2.2.15 || ^3.0.0"
},
"dependencies": {
"@babel/runtime": "^7.20.7",
"@embroider/addon-shim": "^1.0.0"
},
"devDependencies": {
Expand Down Expand Up @@ -107,7 +108,9 @@
"version": 2,
"type": "addon",
"main": "addon-main.cjs",
"app-js": {}
"app-js": {
"./components/button/index.js": "./dist/_app_/components/button/index.js"
}
},
"exports": {
".": "./dist/index.js",
Expand Down
20 changes: 20 additions & 0 deletions ember-toucan-core/src/components/button/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<button
aria-disabled={{if @isDisabled "true"}}
class={{this.styles}}
data-test-selector="button"
type={{this.type}}
{{on "click" this.onClick}}
...attributes
>
{{#if @isLoading}}
{{yield to="loading"}}
<span class="sr-only">{{yield}}</span>
{{else if @isDisabled}}
<span class="flex items-center gap-x-2">
{{yield}}
{{yield to="disabled"}}
</span>
{{else}}
{{yield}}
{{/if}}
</button>
93 changes: 93 additions & 0 deletions ember-toucan-core/src/components/button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import Component from '@glimmer/component';
import { assert } from '@ember/debug';
import { action } from '@ember/object';

const VALID_VARIANTS = [
'bare',
'destructive',
'link',
'primary',
'quiet',
'secondary',
] as const;

export type ButtonVariant = (typeof VALID_VARIANTS)[number];

const STYLES = {
base: [
'focusable',
'inline-flex',
'items-center',
'justify-center',
'rounded-sm',
'transition',
'truncate',
'type-md-medium',
],
variants: {
bare: ['focusable'],
destructive: ['focusable-destructive', 'interactive-destructive'],
link: ['font-normal', 'interactive-link', 'underline'],
primary: ['interactive-primary'],
quiet: ['font-normal', 'interactive-quiet'],
secondary: ['interactive-normal'],
},
};

export interface ButtonSignature {
Args: {
isDisabled?: boolean;
onClick?: (event: MouseEvent) => void;
type?: 'button' | 'reset' | 'submit';
variant?: ButtonVariant;
};
Blocks: { default: []; disabled: []; loading: [] };
Element: HTMLButtonElement;
}

export default class Button extends Component<ButtonSignature> {
get type() {
return this.args?.type || 'button';
}

get variant() {
const { variant } = this.args;

assert(
`Invalid variant for Button: '${variant}' (allowed values: [${VALID_VARIANTS.join(
', '
)}])`,
VALID_VARIANTS.includes(variant ?? 'primary')
);

return variant || 'primary';
}

get styles() {
if (this.variant === 'bare') {
return STYLES.variants.bare;
}

const buttonStyles = [...STYLES.base, ...STYLES.variants[this.variant]];
const disabledStyles = ['interactive-disabled', 'focus:outline-none'];

if (this.variant !== 'link') {
buttonStyles.push('px-4', 'py-1');
}

return this.args.isDisabled
? [...buttonStyles, ...disabledStyles].join(' ')
: buttonStyles.join(' ');
}

@action
onClick(event: MouseEvent) {
if (this.args.isDisabled) {
event.stopImmediatePropagation();

return;
}

this.args.onClick?.(event);
}
}
5 changes: 3 additions & 2 deletions ember-toucan-core/src/template-registry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type ButtonComponent from './components/button';

export default interface Registry {
// TODO: put components here
Button: unknown;
Button: typeof ButtonComponent;
}
Loading

0 comments on commit 550fda4

Please sign in to comment.