@justeattakeaway/pie-button
Advanced tools
Comparing version 0.11.0 to 0.12.0
# Changelog | ||
## 0.12.0 | ||
### Minor Changes | ||
- [Changed] - extended readme file to mention props, events and enum exports ([#434](https://github.com/justeattakeaway/pie/pull/434)) by [@jamieomaguire](https://github.com/jamieomaguire) | ||
- [Changed] - Prevent hover and active status on disabled btns and change outline to border for safari support ([#434](https://github.com/justeattakeaway/pie/pull/434)) by [@jamieomaguire](https://github.com/jamieomaguire) | ||
- [Removed] - custom event handler and updated tests accordingly ([#434](https://github.com/justeattakeaway/pie/pull/434)) by [@jamieomaguire](https://github.com/jamieomaguire) | ||
- [Added] - isFullWidth prop to button ([#434](https://github.com/justeattakeaway/pie/pull/434)) by [@jamieomaguire](https://github.com/jamieomaguire) | ||
## 0.11.0 | ||
@@ -4,0 +16,0 @@ |
@@ -1,46 +0,50 @@ | ||
import { unsafeCSS as f, LitElement as g, html as h } from "lit"; | ||
import { classMap as v } from "lit/directives/class-map.js"; | ||
import { property as p, customElement as m } from "lit/decorators.js"; | ||
const x = `.o-btn{border-radius:50rem;border:none;font-family:JetSansDigital;cursor:pointer;user-select:none;outline:none}.o-btn:focus-visible{outline:2px solid #4996fd}.o-btn--xsmall{min-block-size:32px;padding:6px 8px;font-size:14px;font-weight:700;line-height:20px}.o-btn--small-productive{min-block-size:40px;padding:8px 16px;font-size:16px;font-weight:700;line-height:24px}.o-btn--small-expressive{min-block-size:40px;padding:6px 16px;font-size:20px;font-weight:700;line-height:28px}.o-btn--medium{min-block-size:48px;padding:10px 24px;font-size:20px;font-weight:700;line-height:28px}.o-btn--large{min-block-size:56px;padding:14px 24px;font-size:20px;font-weight:700;line-height:28px}.o-btn--primary{background-color:#f36805;color:#fff}.o-btn--primary:hover{background-color:#df5f05}.o-btn--primary:active{background-color:#b74e04}.o-btn--secondary{background-color:#f5f3f1;color:#242e30}.o-btn--secondary:hover{background-color:#ede9e5}.o-btn--secondary:active{background-color:#dcd4cd}.o-btn--outline{background-color:#fff;color:#303d3f;outline:1px solid #dbd9d7}.o-btn--outline:hover{background-color:#f5f5f5}.o-btn--outline:active{background-color:#e0e0e0}.o-btn--ghost{background-color:#fff;color:#242e30}.o-btn--ghost:hover{background-color:#f5f5f5}.o-btn--ghost:active{background-color:#e0e0e0}.o-btn.o-btn--is-disabled{background-color:#efedea;color:#8c999b;outline:1px solid #efedea;cursor:default}.o-btn.o-btn--is-disabled:hover{background-color:#e6e3de}.o-btn.o-btn--is-disabled:active{background-color:#d5cfc7}.o-btn.o-btn--is-disabled.o-btn--ghost{background-color:#fff;outline:none} | ||
`, u = (o, n) => function(i, t) { | ||
import { unsafeCSS as f, LitElement as h, html as g } from "lit"; | ||
import { classMap as m } from "lit/directives/class-map.js"; | ||
import { property as a, customElement as x } from "lit/decorators.js"; | ||
const v = `.o-btn{border-radius:50rem;border:none;font-family:JetSansDigital;cursor:pointer;user-select:none;outline:none}.o-btn:focus-visible{outline:2px solid #4996fd}.o-btn--xsmall{min-block-size:32px;padding:6px 8px;font-size:14px;font-weight:700;line-height:20px}.o-btn--small-productive{min-block-size:40px;padding:8px 16px;font-size:16px;font-weight:700;line-height:24px}.o-btn--small-expressive{min-block-size:40px;padding:6px 16px;font-size:20px;font-weight:700;line-height:28px}.o-btn--medium{min-block-size:48px;padding:10px 24px;font-size:20px;font-weight:700;line-height:28px}.o-btn--large{min-block-size:56px;padding:14px 24px;font-size:20px;font-weight:700;line-height:28px}.o-btn--primary{background-color:#f36805;color:#fff}.o-btn--primary:hover:not(:disabled){background-color:#df5f05}.o-btn--primary:active:not(:disabled){background-color:#b74e04}.o-btn--secondary{background-color:#f5f3f1;color:#242e30}.o-btn--secondary:hover:not(:disabled){background-color:#ede9e5}.o-btn--secondary:active:not(:disabled){background-color:#dcd4cd}.o-btn--outline{background-color:#fff;color:#303d3f;border:1px solid #dbd9d7}.o-btn--outline:hover:not(:disabled){background-color:#f5f5f5}.o-btn--outline:active:not(:disabled){background-color:#e0e0e0}.o-btn--ghost{background-color:#fff;color:#242e30}.o-btn--ghost:hover:not(:disabled){background-color:#f5f5f5}.o-btn--ghost:active:not(:disabled){background-color:#e0e0e0}.o-btn.o-btn--is-disabled{background-color:#efedea;color:#8c999b;border:1px solid #efedea;cursor:not-allowed}.o-btn.o-btn--is-disabled:hover:not(:disabled){background-color:#e6e3de}.o-btn.o-btn--is-disabled:active:not(:disabled){background-color:#d5cfc7}.o-btn.o-btn--is-disabled.o-btn--ghost{background-color:#fff;outline:none}.o-btn--fullWidth{inline-size:100%} | ||
`, u = (o, n) => function(r, t) { | ||
const e = `#${t}`; | ||
Object.defineProperty(i, t, { | ||
Object.defineProperty(r, t, { | ||
get() { | ||
return this[e]; | ||
}, | ||
set(r) { | ||
const s = this[e]; | ||
o.includes(r) ? this[e] = r : (console.error( | ||
`[pie-button] Invalid value "${r}" provided for property "${t}".`, | ||
set(i) { | ||
const d = this[e]; | ||
o.includes(i) ? this[e] = i : (console.error( | ||
`[pie-button] Invalid value "${i}" provided for property "${t}".`, | ||
`Must be one of: ${o.join(" | ")}.`, | ||
`Falling back to default value: "${n}"` | ||
), this[e] = n), this.requestUpdate(t, s); | ||
), this[e] = n), this.requestUpdate(t, d); | ||
} | ||
}); | ||
}; | ||
var a = /* @__PURE__ */ ((o) => (o.XSMALL = "xsmall", o.SMALL_EXPRESSIVE = "small-expressive", o.SMALL_PRODUCTIVE = "small-productive", o.MEDIUM = "medium", o.LARGE = "large", o))(a || {}), d = /* @__PURE__ */ ((o) => (o.SUBMIT = "submit", o.BUTTON = "button", o.RESET = "reset", o.MENU = "menu", o))(d || {}), b = /* @__PURE__ */ ((o) => (o.PRIMARY = "primary", o.SECONDARY = "secondary", o.OUTLINE = "outline", o.GHOST = "ghost", o))(b || {}), y = Object.defineProperty, k = Object.getOwnPropertyDescriptor, c = (o, n, i, t) => { | ||
for (var e = t > 1 ? void 0 : t ? k(n, i) : n, r = o.length - 1, s; r >= 0; r--) | ||
(s = o[r]) && (e = (t ? s(n, i, e) : s(e)) || e); | ||
return t && e && y(n, i, e), e; | ||
var b = /* @__PURE__ */ ((o) => (o.XSMALL = "xsmall", o.SMALL_EXPRESSIVE = "small-expressive", o.SMALL_PRODUCTIVE = "small-productive", o.MEDIUM = "medium", o.LARGE = "large", o))(b || {}), c = /* @__PURE__ */ ((o) => (o.SUBMIT = "submit", o.BUTTON = "button", o.RESET = "reset", o.MENU = "menu", o))(c || {}), p = /* @__PURE__ */ ((o) => (o.PRIMARY = "primary", o.SECONDARY = "secondary", o.OUTLINE = "outline", o.GHOST = "ghost", o))(p || {}), y = Object.defineProperty, M = Object.getOwnPropertyDescriptor, l = (o, n, r, t) => { | ||
for (var e = t > 1 ? void 0 : t ? M(n, r) : n, i = o.length - 1, d; i >= 0; i--) | ||
(d = o[i]) && (e = (t ? d(n, r, e) : d(e)) || e); | ||
return t && e && y(n, r, e), e; | ||
}; | ||
let l = class extends g { | ||
let s = class extends h { | ||
constructor() { | ||
super(...arguments), this.size = a.MEDIUM, this.type = d.SUBMIT, this.variant = b.PRIMARY, this.disabled = !1; | ||
super(...arguments), this.size = b.MEDIUM, this.type = c.SUBMIT, this.variant = p.PRIMARY, this.disabled = !1, this.isFullWidth = !1; | ||
} | ||
render() { | ||
const { size: o, type: n, variant: i, disabled: t } = this, e = { | ||
const { | ||
size: o, | ||
type: n, | ||
variant: r, | ||
disabled: t, | ||
isFullWidth: e | ||
} = this, i = { | ||
"o-btn": !0, | ||
[`o-btn--${o}`]: o, | ||
[`o-btn--${i}`]: i, | ||
"o-btn--is-disabled": t | ||
}, r = () => { | ||
const s = new CustomEvent("CustomEvent", { detail: "WC event dispatched" }); | ||
console.info("WC event dispatched"), this.dispatchEvent(s); | ||
[`o-btn--${r}`]: r, | ||
"o-btn--is-disabled": t, | ||
"o-btn--fullWidth": e | ||
}; | ||
return h` | ||
return g` | ||
<button | ||
class=${v(e)} | ||
class=${m(i)} | ||
type=${n} | ||
?disabled=${t} | ||
@click="${r}"> | ||
?isFullWidth=${e}> | ||
<slot></slot> | ||
@@ -50,26 +54,29 @@ </button>`; | ||
}; | ||
l.styles = f(x); | ||
c([ | ||
p(), | ||
u(Object.values(a), a.MEDIUM) | ||
], l.prototype, "size", 2); | ||
c([ | ||
p(), | ||
u(Object.values(d), d.SUBMIT) | ||
], l.prototype, "type", 2); | ||
c([ | ||
p(), | ||
u(Object.values(b), b.PRIMARY) | ||
], l.prototype, "variant", 2); | ||
c([ | ||
p({ type: Boolean, reflect: !0 }) | ||
], l.prototype, "disabled", 2); | ||
l = c([ | ||
m("pie-button") | ||
], l); | ||
s.styles = f(v); | ||
l([ | ||
a(), | ||
u(Object.values(b), b.MEDIUM) | ||
], s.prototype, "size", 2); | ||
l([ | ||
a(), | ||
u(Object.values(c), c.SUBMIT) | ||
], s.prototype, "type", 2); | ||
l([ | ||
a(), | ||
u(Object.values(p), p.PRIMARY) | ||
], s.prototype, "variant", 2); | ||
l([ | ||
a({ type: Boolean, reflect: !0 }) | ||
], s.prototype, "disabled", 2); | ||
l([ | ||
a({ type: Boolean, reflect: !0 }) | ||
], s.prototype, "isFullWidth", 2); | ||
s = l([ | ||
x("pie-button") | ||
], s); | ||
export { | ||
a as BUTTON_SIZE, | ||
d as BUTTON_TYPE, | ||
b as BUTTON_VARIANT, | ||
l as PieButton | ||
b as BUTTON_SIZE, | ||
c as BUTTON_TYPE, | ||
p as BUTTON_VARIANT, | ||
s as PieButton | ||
}; |
@@ -9,2 +9,3 @@ import { LitElement } from 'lit'; | ||
disabled: boolean; | ||
isFullWidth: boolean; | ||
render(): import("lit-html").TemplateResult<1>; | ||
@@ -11,0 +12,0 @@ static styles: import("lit").CSSResult; |
{ | ||
"name": "@justeattakeaway/pie-button", | ||
"version": "0.11.0", | ||
"version": "0.12.0", | ||
"description": "PIE design system button built using web components", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -11,6 +11,21 @@ <p align="center"> | ||
# pie-button | ||
# Table of Contents | ||
This button is a Web Component built using Lit. | ||
1. [Introduction](#pie-button) | ||
2. [Local Development](#local-development) | ||
3. [Props](#props) | ||
4. [Events](#events) | ||
- [HTML example](#html) | ||
- [Vue example (using Nuxt 3)](#vue-templates-using-nuxt-3) | ||
- [React example (using Next 13)](#react-templates-using-next-13) | ||
5. [TypeScript Enum Exports](#typescript-enum-exports) | ||
6. [Testing](#testing) | ||
- [Browser Tests](#browser-tests) | ||
- [Visual Tests](#visual-tests) | ||
## `pie-button` | ||
`pie-button` is a Web Component built using the Lit library. It offers a simple and accessible button component for web applications. This component can be easily integrated into various frontend frameworks and customized through a set of properties. For TypeScript projects, it also provides exported enums for type safety and autocompletion. | ||
## Local development | ||
@@ -38,6 +53,62 @@ | ||
``` | ||
## Props | ||
| Property | Type | Default | Description | | ||
|-------------|-----------|-----------------|----------------------------------------------------------------------| | ||
| size | `String` | `BUTTON_SIZE.MEDIUM` | Size of the button, one of `BUTTON_SIZE` enum values (TypeScript Enum) or a raw string value such as `'large'` | | ||
| type | `String` | `BUTTON_TYPE.SUBMIT` | Type of the button, one of `BUTTON_TYPE` enum values (TypeScript Enum) or a raw string value such as `'submit'` | | ||
| variant | `String` | `BUTTON_VARIANT.PRIMARY` | Variant of the button, one of `BUTTON_VARIANT` enum values (TypeScript Enum) or a raw string value such as `'primary'` | | ||
| disabled | `Boolean` | `false` | If `true`, disables the button. | | ||
| isFullWidth | `Boolean` | `false` | If `true`, sets the button width to 100% of it's container. | | ||
## Running tests | ||
## Events | ||
This component does not use any custom event handlers. In order to add event listening to this component, you can treat it like a native HTML element in your application. | ||
For example, to add a click handler in various templates: | ||
### HTML | ||
```html | ||
<!-- Other attributes omitted for clarity --> | ||
<pie-button onclick="e => console.log(e)">Click me!</pie-button> | ||
``` | ||
### Vue templates (using Nuxt 3) | ||
```html | ||
<!-- Other attributes omitted for clarity --> | ||
<pie-button @click="handleClick">Click me!</pie-button> | ||
``` | ||
### React templates (using Next 13) | ||
```html | ||
<!-- Other attributes omitted for clarity --> | ||
<PieButton onClick={handleClick}>increment</PieButton> | ||
``` | ||
## TypeScript Enum Exports | ||
For TypeScript projects, we export three enums related to button properties: `BUTTON_SIZE`, `BUTTON_TYPE`, and `BUTTON_VARIANT`. You can import and use these enums to set the corresponding property values for the `pie-button` component. This ensures better type safety and autocompletion in your project. | ||
Here's an example of how to import and use the enums in a TypeScript project: | ||
```typescript | ||
import { BUTTON_SIZE, BUTTON_TYPE, BUTTON_VARIANT } from '@justeattakeaway/pie-button'; | ||
// Using the enums to set property values | ||
const myButtonSize = BUTTON_SIZE.LARGE; | ||
const myButtonType = BUTTON_TYPE.RESET; | ||
const myButtonVariant = BUTTON_VARIANT.SECONDARY; | ||
``` | ||
In your markup or JSX, you can then use these variables to set the properties for the pie-button component: | ||
```html | ||
<PieButton size={myButtonSize} type={myButtonType} variant={myButtonVariant}>Click me!</PieButton> | ||
``` | ||
## Testing | ||
### Browser tests | ||
@@ -56,4 +127,1 @@ | ||
``` | ||
@@ -16,17 +16,22 @@ import { LitElement, html, unsafeCSS } from 'lit'; | ||
@validPropertyValues(Object.values(BUTTON_SIZE), BUTTON_SIZE.MEDIUM) | ||
size : BUTTON_SIZE = BUTTON_SIZE.MEDIUM; | ||
size : BUTTON_SIZE = BUTTON_SIZE.MEDIUM; | ||
@property() | ||
@validPropertyValues(Object.values(BUTTON_TYPE), BUTTON_TYPE.SUBMIT) | ||
type : BUTTON_TYPE = BUTTON_TYPE.SUBMIT; | ||
type : BUTTON_TYPE = BUTTON_TYPE.SUBMIT; | ||
@property() | ||
@validPropertyValues(Object.values(BUTTON_VARIANT), BUTTON_VARIANT.PRIMARY) | ||
variant : BUTTON_VARIANT = BUTTON_VARIANT.PRIMARY; | ||
variant : BUTTON_VARIANT = BUTTON_VARIANT.PRIMARY; | ||
@property({type: Boolean, reflect: true}) | ||
disabled : boolean = false; | ||
@property({ type: Boolean, reflect: true }) | ||
disabled = false; | ||
@property({ type: Boolean, reflect: true }) | ||
isFullWidth = false; | ||
render () { | ||
const { size, type, variant, disabled } = this; | ||
const { | ||
size, type, variant, disabled, isFullWidth, | ||
} = this; | ||
@@ -38,10 +43,5 @@ const classes = { | ||
'o-btn--is-disabled': disabled, | ||
'o-btn--fullWidth': isFullWidth, | ||
}; | ||
const raiseWCEvent = () => { | ||
const event = new CustomEvent('CustomEvent', { detail: 'WC event dispatched' }) | ||
console.info('WC event dispatched') | ||
this.dispatchEvent(event) | ||
} | ||
return html` | ||
@@ -52,3 +52,3 @@ <button | ||
?disabled=${disabled} | ||
@click="${raiseWCEvent}"> | ||
?isFullWidth=${isFullWidth}> | ||
<slot></slot> | ||
@@ -55,0 +55,0 @@ </button>`; |
@@ -9,44 +9,50 @@ import { test, expect } from '@sand4rt/experimental-ct-web'; | ||
variants.forEach(variant => { | ||
test(`should render - ${variant}`, async ({ mount }) => { | ||
variants.forEach((variant) => { | ||
test(`should render - ${variant}`, async ({ mount }) => { | ||
for (const size of sizes) { | ||
for (const isDisabled of disabledStates) { | ||
const component = await mount( | ||
PieButton, | ||
{ | ||
props: { | ||
size, | ||
variant, | ||
disabled: isDisabled, | ||
}, | ||
slots: { | ||
default: `Hello, ${size} ${variant} Button!`, | ||
}, | ||
}, | ||
); | ||
for (const size of sizes) { | ||
for (const isDisabled of disabledStates) { | ||
const component = await mount(PieButton, | ||
{ | ||
await expect(component).toContainText(`Hello, ${size} ${variant} Button!`); | ||
} | ||
} | ||
}); | ||
}); | ||
test('should correctly work with native click events', async ({ mount }) => { | ||
const messages: string[] = []; | ||
const expectedEventMessage = 'Native event dispatched'; | ||
const component = await mount( | ||
PieButton, | ||
{ | ||
props: { | ||
size, | ||
variant, | ||
disabled: isDisabled | ||
size: BUTTON_SIZE.LARGE, | ||
variant: BUTTON_VARIANT.PRIMARY, | ||
}, | ||
slots: { | ||
default: `Hello, ${size} ${variant} Button!` | ||
default: 'Click me!', | ||
}, | ||
}); | ||
on: { | ||
click: () => { | ||
messages.push(expectedEventMessage); | ||
}, | ||
}, | ||
}, | ||
); | ||
await expect(component).toContainText(`Hello, ${size} ${variant} Button!`); | ||
} | ||
} | ||
}); | ||
}) | ||
await component.click(); | ||
test('should emit an event when clicked', async ({ mount }) => { | ||
const messages: string[] = []; | ||
const component = await mount(PieButton, | ||
{ | ||
props: { | ||
size: BUTTON_SIZE.LARGE, | ||
variant: BUTTON_VARIANT.PRIMARY | ||
}, | ||
slots: { | ||
default: 'Click me!' | ||
}, | ||
on: { | ||
CustomEvent: (data: string) => messages.push(data), | ||
}, | ||
}); | ||
await component.click(); | ||
expect(messages).toEqual(['WC event dispatched']) | ||
}); | ||
expect(messages).toEqual([expectedEventMessage]); | ||
}); |
import { test } from '@sand4rt/experimental-ct-web'; | ||
import percySnapshot from '@percy/playwright'; | ||
import { PieButton } from '@/index'; | ||
import percySnapshot from '@percy/playwright' | ||
import { BUTTON_SIZE, BUTTON_TYPE, BUTTON_VARIANT } from '@/defs'; | ||
@@ -10,23 +10,44 @@ | ||
variants.forEach(variant => { | ||
test(`should render - ${variant}`, async ({ page, mount }) => { | ||
variants.forEach((variant) => { | ||
test(`should render - ${variant}`, async ({ page, mount }) => { | ||
for (const size of sizes) { | ||
for (const disabledState of disabledStates) { | ||
await mount( | ||
PieButton, | ||
{ | ||
props: { | ||
type: BUTTON_TYPE.BUTTON, | ||
size, | ||
variant, | ||
disabled: disabledState, | ||
isFullWidth: false, | ||
}, | ||
slots: { | ||
default: `Hello, ${size} ${variant} Button!`, | ||
}, | ||
}, | ||
); | ||
} | ||
for (const size of sizes) { | ||
for (const isDisabled of disabledStates) { | ||
await mount(PieButton, | ||
{ | ||
props: { | ||
type: BUTTON_TYPE.BUTTON, | ||
size, | ||
variant, | ||
disabled: isDisabled | ||
}, | ||
slots: { | ||
default: `Hello, ${size} ${variant} Button!` | ||
}, | ||
}); | ||
} | ||
} | ||
await percySnapshot(page, `PIE Button - ${variant}`); | ||
}); | ||
}) | ||
for (const disabledState of disabledStates) { | ||
await mount( | ||
PieButton, | ||
{ | ||
props: { | ||
type: BUTTON_TYPE.BUTTON, | ||
size, | ||
variant, | ||
disabled: disabledState, | ||
isFullWidth: true, | ||
}, | ||
slots: { | ||
default: `Hello, ${size} ${variant} Button!`, | ||
}, | ||
}, | ||
); | ||
} | ||
} | ||
await percySnapshot(page, `PIE Button - ${variant}`); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
39614
537
125