@justeattakeaway/pie-button 0.48.1 → 0.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ClassInfo } from 'lit/directives/class-map.js';
1
2
  import { ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2
3
  import type { CSSResult } from 'lit';
3
4
  import type { FormControlInterface } from '@justeattakeaway/pie-webc-core';
@@ -8,6 +9,10 @@ import * as React_2 from 'react';
8
9
  import type { TemplateResult } from 'lit';
9
10
 
10
11
  export declare interface ButtonProps {
12
+ /**
13
+ * Which HTML element to use when rendering the button.
14
+ */
15
+ tag?: typeof tags[number];
11
16
  /**
12
17
  * What size the button should be.
13
18
  */
@@ -82,9 +87,24 @@ export declare interface ButtonProps {
82
87
  * What size should be attributed to the button when isResponsive is true and the screen is wide.
83
88
  */
84
89
  responsiveSize?: typeof responsiveSizes[number];
90
+ /**
91
+ * If the button is rendered as an anchor element, this attribute will be applied to the `href` attribute on the anchor.
92
+ * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href)
93
+ */
94
+ href?: string;
95
+ /**
96
+ * If the button is rendered as an anchor element, this attribute will be applied to the `rel` attribute on the anchor.
97
+ * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel)
98
+ */
99
+ rel?: string;
100
+ /**
101
+ * If the button is rendered as an anchor element, this attribute will be applied to the `target` attribute on the anchor.
102
+ * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)
103
+ */
104
+ target?: string;
85
105
  }
86
106
 
87
- export declare type DefaultProps = ComponentDefaultProps<ButtonProps, 'size' | 'type' | 'variant' | 'iconPlacement' | 'disabled' | 'isFullWidth' | 'isLoading' | 'isResponsive'>;
107
+ export declare type DefaultProps = ComponentDefaultProps<ButtonProps, 'tag' | 'size' | 'type' | 'variant' | 'iconPlacement' | 'disabled' | 'isFullWidth' | 'isLoading' | 'isResponsive'>;
88
108
 
89
109
  export declare const defaultProps: DefaultProps;
90
110
 
@@ -107,22 +127,26 @@ declare class PieButton_2 extends PieButton_base implements ButtonProps {
107
127
  connectedCallback(): void;
108
128
  disconnectedCallback(): void;
109
129
  updated(changedProperties: PropertyValues<this>): void;
110
- size: ButtonProps['size'];
111
- type: ButtonProps['type'];
112
- variant: ButtonProps['variant'];
113
- iconPlacement: ButtonProps['iconPlacement'];
130
+ tag: "button" | "a";
131
+ size: "xsmall" | "medium" | "large" | "small-productive" | "small-expressive";
132
+ type: "button" | "submit" | "reset";
133
+ variant: "secondary" | "inverse" | "primary" | "outline" | "outline-inverse" | "ghost" | "ghost-inverse" | "destructive" | "destructive-ghost";
134
+ iconPlacement: "leading" | "trailing";
114
135
  disabled: boolean;
115
136
  isLoading: boolean;
116
137
  isFullWidth: boolean;
117
138
  isResponsive: boolean;
118
- name?: string;
119
- value?: string;
139
+ name: ButtonProps['name'];
140
+ value: ButtonProps['value'];
120
141
  formaction: ButtonProps['formaction'];
121
142
  formenctype: ButtonProps['formenctype'];
122
143
  formmethod: ButtonProps['formmethod'];
123
144
  formnovalidate: ButtonProps['formnovalidate'];
124
145
  formtarget: ButtonProps['formtarget'];
125
- responsiveSize?: ButtonProps['responsiveSize'];
146
+ responsiveSize: ButtonProps['responsiveSize'];
147
+ href: ButtonProps['href'];
148
+ rel: ButtonProps['rel'];
149
+ target: ButtonProps['target'];
126
150
  /**
127
151
  * This method creates an invisible button of the same type as pie-button. It is then clicked, and immediately removed from the DOM.
128
152
  * This is done so that we trigger native form actions, such as submit and reset in the browser. The performance impact of adding and removing a single button to the DOM
@@ -140,6 +164,8 @@ declare class PieButton_2 extends PieButton_base implements ButtonProps {
140
164
  * @private
141
165
  */
142
166
  private renderSpinner;
167
+ renderAnchor(classes: ClassInfo): TemplateResult<1>;
168
+ renderButton(classes: ClassInfo): TemplateResult<1>;
143
169
  render(): TemplateResult<1>;
144
170
  focus(): void;
145
171
  static styles: CSSResult;
@@ -153,6 +179,8 @@ export declare const responsiveSizes: readonly ["productive", "expressive"];
153
179
 
154
180
  export declare const sizes: readonly ["xsmall", "small-productive", "small-expressive", "medium", "large"];
155
181
 
182
+ export declare const tags: readonly ["button", "a"];
183
+
156
184
  export declare const types: readonly ["submit", "button", "reset"];
157
185
 
158
186
  export declare type Variant = typeof variants[number];
package/dist/react.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as t from "react";
2
2
  import { createComponent as e } from "@lit/react";
3
3
  import { PieButton as o } from "./index.js";
4
- import { defaultProps as l, formEncodingtypes as y, formMethodTypes as B, formTargetTypes as d, iconPlacements as g, responsiveSizes as v, sizes as T, types as x, variants as z } from "./index.js";
4
+ import { defaultProps as y, formEncodingtypes as B, formMethodTypes as d, formTargetTypes as g, iconPlacements as v, responsiveSizes as T, sizes as x, tags as z, types as C, variants as N } from "./index.js";
5
5
  import "lit";
6
6
  import "lit/directives/class-map.js";
7
+ import "lit/directives/if-defined.js";
7
8
  import "lit/decorators.js";
8
9
  import "@justeattakeaway/pie-webc-core";
9
10
  import "@justeattakeaway/pie-spinner";
@@ -13,16 +14,17 @@ const r = e({
13
14
  react: t,
14
15
  tagName: "pie-button",
15
16
  events: {}
16
- }), f = r;
17
+ }), u = r;
17
18
  export {
18
- f as PieButton,
19
- l as defaultProps,
20
- y as formEncodingtypes,
21
- B as formMethodTypes,
22
- d as formTargetTypes,
23
- g as iconPlacements,
24
- v as responsiveSizes,
25
- T as sizes,
26
- x as types,
27
- z as variants
19
+ u as PieButton,
20
+ y as defaultProps,
21
+ B as formEncodingtypes,
22
+ d as formMethodTypes,
23
+ g as formTargetTypes,
24
+ v as iconPlacements,
25
+ T as responsiveSizes,
26
+ x as sizes,
27
+ z as tags,
28
+ C as types,
29
+ N as variants
28
30
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-button",
3
- "version": "0.48.1",
3
+ "version": "0.49.1",
4
4
  "description": "PIE design system button built using web components",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "license": "Apache-2.0",
37
37
  "devDependencies": {
38
38
  "@custom-elements-manifest/analyzer": "0.9.0",
39
- "@justeattakeaway/pie-components-config": "0.17.0",
39
+ "@justeattakeaway/pie-components-config": "0.18.0",
40
40
  "@justeattakeaway/pie-css": "0.12.1",
41
41
  "@justeattakeaway/pie-wrapper-react": "0.14.1",
42
42
  "cem-plugin-module-file-extensions": "0.0.5"
@@ -49,7 +49,7 @@
49
49
  "dist/*.js"
50
50
  ],
51
51
  "dependencies": {
52
- "@justeattakeaway/pie-spinner": "0.6.7",
52
+ "@justeattakeaway/pie-spinner": "0.7.0",
53
53
  "@justeattakeaway/pie-webc-core": "0.24.0",
54
54
  "element-internals-polyfill": "1.3.11"
55
55
  }
package/src/button.scss CHANGED
@@ -72,7 +72,7 @@
72
72
  }
73
73
 
74
74
  position: relative;
75
- display: flex;
75
+ display: inline-flex;
76
76
  gap: var(--dt-spacing-b);
77
77
  align-items: center;
78
78
  justify-content: center;
@@ -89,6 +89,7 @@
89
89
  line-height: var(--btn-line-height);
90
90
  cursor: pointer;
91
91
  user-select: none;
92
+ text-decoration: none;
92
93
 
93
94
  // used to specify whether the button should be full width or not
94
95
  inline-size: var(--btn-inline-size);
package/src/defs.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';
2
2
 
3
+ export const tags = ['button', 'a'] as const;
3
4
  export const sizes = ['xsmall', 'small-productive', 'small-expressive', 'medium', 'large'] as const;
4
5
  export const responsiveSizes = ['productive', 'expressive'] as const;
5
6
  export const types = ['submit', 'button', 'reset'] as const;
@@ -16,30 +17,41 @@ export const formMethodTypes = ['post', 'get', 'dialog'] as const;
16
17
  export const formTargetTypes = ['_self', '_blank', '_parent', '_top'] as const;
17
18
 
18
19
  export interface ButtonProps {
20
+ /**
21
+ * Which HTML element to use when rendering the button.
22
+ */
23
+ tag?: typeof tags[number];
24
+
19
25
  /**
20
26
  * What size the button should be.
21
27
  */
22
28
  size?: typeof sizes[number];
29
+
23
30
  /**
24
31
  * What type attribute should be applied to the button. For example submit, button.
25
32
  */
26
33
  type?: typeof types[number];
34
+
27
35
  /**
28
36
  * What style variant the button should be such as primary, outline or ghost.
29
37
  */
30
38
  variant?: Variant;
39
+
31
40
  /**
32
41
  * The placement of the icon slot, if provided, such as leading or trailing
33
42
  */
34
43
  iconPlacement?: typeof iconPlacements[number];
44
+
35
45
  /**
36
46
  * When true, the button element is disabled.
37
47
  */
38
48
  disabled?: boolean;
49
+
39
50
  /**
40
51
  * When true, the button element will occupy the full width of its container.
41
52
  */
42
53
  isFullWidth?: boolean;
54
+
43
55
  /**
44
56
  * When true, displays a loading indicator inside the button.
45
57
  */
@@ -99,11 +111,30 @@ export interface ButtonProps {
99
111
  * What size should be attributed to the button when isResponsive is true and the screen is wide.
100
112
  */
101
113
  responsiveSize?: typeof responsiveSizes[number];
114
+
115
+ /**
116
+ * If the button is rendered as an anchor element, this attribute will be applied to the `href` attribute on the anchor.
117
+ * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href)
118
+ */
119
+ href?: string;
120
+
121
+ /**
122
+ * If the button is rendered as an anchor element, this attribute will be applied to the `rel` attribute on the anchor.
123
+ * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel)
124
+ */
125
+ rel?: string;
126
+
127
+ /**
128
+ * If the button is rendered as an anchor element, this attribute will be applied to the `target` attribute on the anchor.
129
+ * [MDN reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target)
130
+ */
131
+ target?: string;
102
132
  }
103
133
 
104
- export type DefaultProps = ComponentDefaultProps<ButtonProps, 'size' | 'type' | 'variant' | 'iconPlacement' | 'disabled' | 'isFullWidth' | 'isLoading' | 'isResponsive'>;
134
+ export type DefaultProps = ComponentDefaultProps<ButtonProps, 'tag' | 'size' | 'type' | 'variant' | 'iconPlacement' | 'disabled' | 'isFullWidth' | 'isLoading' | 'isResponsive'>;
105
135
 
106
136
  export const defaultProps: DefaultProps = {
137
+ tag: 'button',
107
138
  size: 'medium',
108
139
  type: 'submit',
109
140
  variant: 'primary',
package/src/index.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  import {
2
- LitElement, html, unsafeCSS, nothing, PropertyValues, TemplateResult,
2
+ LitElement, html, unsafeCSS, nothing, type PropertyValues, type TemplateResult,
3
3
  } from 'lit';
4
- import { classMap } from 'lit/directives/class-map.js';
4
+ import { classMap, type ClassInfo } from 'lit/directives/class-map.js';
5
+ import { ifDefined } from 'lit/directives/if-defined.js';
5
6
  import { property } from 'lit/decorators.js';
7
+ import 'element-internals-polyfill';
8
+
6
9
  import { validPropertyValues, defineCustomElement, FormControlMixin } from '@justeattakeaway/pie-webc-core';
10
+
11
+ import '@justeattakeaway/pie-spinner';
12
+ import { type SpinnerProps } from '@justeattakeaway/pie-spinner';
13
+
7
14
  import {
8
- ButtonProps, sizes, types, variants, iconPlacements, defaultProps,
15
+ type ButtonProps, defaultProps, iconPlacements, sizes, tags, types, variants,
9
16
  } from './defs';
10
17
  import styles from './button.scss?inline';
11
- import 'element-internals-polyfill';
12
- import '@justeattakeaway/pie-spinner';
13
18
 
14
19
  // Valid values available to consumers
15
20
  export * from './defs';
@@ -39,8 +44,6 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro
39
44
  }
40
45
 
41
46
  updated (changedProperties: PropertyValues<this>): void {
42
- super.updated(changedProperties);
43
-
44
47
  if (changedProperties.has('type')) {
45
48
  // If the new type is "submit", add the keydown event listener
46
49
  if (this.type === 'submit') {
@@ -51,21 +54,25 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro
51
54
  }
52
55
  }
53
56
 
54
- @property()
57
+ @property({ type: String })
58
+ @validPropertyValues(componentSelector, tags, defaultProps.tag)
59
+ public tag = defaultProps.tag;
60
+
61
+ @property({ type: String })
55
62
  @validPropertyValues(componentSelector, sizes, defaultProps.size)
56
- public size: ButtonProps['size'] = defaultProps.size;
63
+ public size = defaultProps.size;
57
64
 
58
- @property()
65
+ @property({ type: String })
59
66
  @validPropertyValues(componentSelector, types, defaultProps.type)
60
- public type: ButtonProps['type'] = defaultProps.type;
67
+ public type = defaultProps.type;
61
68
 
62
- @property()
69
+ @property({ type: String })
63
70
  @validPropertyValues(componentSelector, variants, defaultProps.variant)
64
- public variant: ButtonProps['variant'] = defaultProps.variant;
71
+ public variant = defaultProps.variant;
65
72
 
66
73
  @property({ type: String })
67
74
  @validPropertyValues(componentSelector, iconPlacements, defaultProps.iconPlacement)
68
- public iconPlacement: ButtonProps['iconPlacement'] = defaultProps.iconPlacement;
75
+ public iconPlacement = defaultProps.iconPlacement;
69
76
 
70
77
  @property({ type: Boolean })
71
78
  public disabled = defaultProps.disabled;
@@ -80,28 +87,37 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro
80
87
  public isResponsive = defaultProps.isResponsive;
81
88
 
82
89
  @property({ type: String })
83
- public name?: string;
90
+ public name: ButtonProps['name'];
84
91
 
85
92
  @property({ type: String })
86
- public value?: string;
93
+ public value: ButtonProps['value'];
87
94
 
88
- @property()
95
+ @property({ type: String })
89
96
  public formaction: ButtonProps['formaction'];
90
97
 
91
- @property()
98
+ @property({ type: String })
92
99
  public formenctype: ButtonProps['formenctype'];
93
100
 
94
- @property()
101
+ @property({ type: String })
95
102
  public formmethod: ButtonProps['formmethod'];
96
103
 
97
104
  @property({ type: Boolean })
98
105
  public formnovalidate: ButtonProps['formnovalidate'];
99
106
 
100
- @property()
107
+ @property({ type: String })
101
108
  public formtarget: ButtonProps['formtarget'];
102
109
 
103
110
  @property({ type: String })
104
- public responsiveSize?: ButtonProps['responsiveSize'];
111
+ public responsiveSize: ButtonProps['responsiveSize'];
112
+
113
+ @property({ type: String })
114
+ public href: ButtonProps['href'];
115
+
116
+ @property({ type: String })
117
+ public rel: ButtonProps['rel'];
118
+
119
+ @property({ type: String })
120
+ public target: ButtonProps['target'];
105
121
 
106
122
  /**
107
123
  * This method creates an invisible button of the same type as pie-button. It is then clicked, and immediately removed from the DOM.
@@ -157,17 +173,17 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro
157
173
  }
158
174
 
159
175
  private _handleClick () {
160
- if (!this.isLoading && this.form) {
161
- if (this.type === 'submit') {
162
- // only submit the form if either formnovalidate is set, or the form passes validation checks (triggers native form validation)
163
- if (this.formnovalidate || this.form.reportValidity()) {
164
- this._simulateNativeButtonClick('submit');
165
- }
166
- }
176
+ if (!this.form) return;
177
+ if (this.isLoading) return;
178
+ if (this.tag !== 'button') return;
167
179
 
168
- if (this.type === 'reset') {
169
- this._simulateNativeButtonClick('reset');
180
+ if (this.type === 'submit') {
181
+ // only submit the form if either formnovalidate is set, or the form passes validation checks (triggers native form validation)
182
+ if (this.formnovalidate || this.form.reportValidity()) {
183
+ this._simulateNativeButtonClick('submit');
170
184
  }
185
+ } else if (this.type === 'reset') {
186
+ this._simulateNativeButtonClick('reset');
171
187
  }
172
188
  }
173
189
 
@@ -197,8 +213,9 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro
197
213
  */
198
214
  private renderSpinner (): TemplateResult {
199
215
  const { size, variant, disabled } = this;
200
- const spinnerSize = size && size.includes('small') ? 'small' : 'medium'; // includes("small") matches for any small size value and xsmall
201
- let spinnerVariant;
216
+
217
+ const spinnerSize: SpinnerProps['size'] = size && size.includes('small') ? 'small' : 'medium'; // includes("small") matches for any small size value and xsmall
218
+ let spinnerVariant: SpinnerProps['variant'];
202
219
  if (disabled) {
203
220
  spinnerVariant = variant === 'ghost-inverse' ? 'inverse' : 'secondary';
204
221
  } else {
@@ -207,23 +224,60 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro
207
224
  }
208
225
 
209
226
  return html`
210
- <pie-spinner
211
- size="${spinnerSize}"
212
- variant="${spinnerVariant}">
213
- </pie-spinner>`;
227
+ <pie-spinner
228
+ size="${spinnerSize}"
229
+ variant="${spinnerVariant}">
230
+ </pie-spinner>`;
231
+ }
232
+
233
+ renderAnchor (classes: ClassInfo) {
234
+ const {
235
+ href, iconPlacement, rel, target,
236
+ } = this;
237
+
238
+ return html`
239
+ <a
240
+ href="${ifDefined(href)}"
241
+ rel="${ifDefined(rel)}"
242
+ target="${ifDefined(target)}"
243
+ class="${classMap(classes)}">
244
+ ${iconPlacement === 'leading' ? html`<slot name="icon"></slot>` : nothing}
245
+ <slot></slot>
246
+ ${iconPlacement === 'trailing' ? html`<slot name="icon"></slot>` : nothing}
247
+ </a>`;
248
+ }
249
+
250
+ renderButton (classes: ClassInfo) {
251
+ const {
252
+ disabled, iconPlacement, isLoading, type,
253
+ } = this;
254
+
255
+ const buttonClasses = {
256
+ ...classes,
257
+ 'is-loading': isLoading,
258
+ };
259
+
260
+ return html`
261
+ <button
262
+ @click=${this._handleClick}
263
+ class=${classMap(buttonClasses)}
264
+ type=${type}
265
+ ?disabled=${disabled}>
266
+ ${isLoading ? this.renderSpinner() : nothing}
267
+ ${iconPlacement === 'leading' ? html`<slot name="icon"></slot>` : nothing}
268
+ <slot></slot>
269
+ ${iconPlacement === 'trailing' ? html`<slot name="icon"></slot>` : nothing}
270
+ </button>`;
214
271
  }
215
272
 
216
273
  render () {
217
274
  const {
218
- type,
219
- disabled,
220
275
  isFullWidth,
221
- variant,
222
- size,
223
- isLoading,
224
276
  isResponsive,
225
- iconPlacement,
226
277
  responsiveSize,
278
+ size,
279
+ tag,
280
+ variant,
227
281
  } = this;
228
282
 
229
283
  const classes = {
@@ -233,20 +287,13 @@ export class PieButton extends FormControlMixin(LitElement) implements ButtonPro
233
287
  [`o-btn--${responsiveSize}`]: Boolean(isResponsive && responsiveSize),
234
288
  [`o-btn--${variant}`]: true,
235
289
  [`o-btn--${size}`]: true,
236
- 'is-loading': isLoading,
237
290
  };
238
291
 
239
- return html`
240
- <button
241
- @click=${this._handleClick}
242
- class=${classMap(classes)}
243
- type=${type || 'submit'}
244
- ?disabled=${disabled}>
245
- ${isLoading ? this.renderSpinner() : nothing}
246
- ${iconPlacement === 'leading' ? html`<slot name="icon"></slot>` : nothing}
247
- <slot></slot>
248
- ${iconPlacement === 'trailing' ? html`<slot name="icon"></slot>` : nothing}
249
- </button>`;
292
+ if (tag === 'a') {
293
+ return this.renderAnchor(classes);
294
+ }
295
+
296
+ return this.renderButton(classes);
250
297
  }
251
298
 
252
299
  focus () {