@justeattakeaway/pie-thumbnail 0.4.0 → 0.6.0

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.
@@ -36,6 +36,14 @@
36
36
  },
37
37
  "default": "{\n default: 'c-thumbnail--backgroundDefault',\n subtle: 'c-thumbnail--backgroundSubtle',\n strong: 'c-thumbnail--backgroundStrong',\n dark: 'c-thumbnail--backgroundDark',\n inverse: 'c-thumbnail--backgroundInverse',\n 'inverse-alternative': 'c-thumbnail--backgroundInverseAlternative',\n}"
38
38
  },
39
+ {
40
+ "kind": "variable",
41
+ "name": "aspectRatios",
42
+ "type": {
43
+ "text": "['1by1', '4by3', '16by9']"
44
+ },
45
+ "default": "['1by1', '4by3', '16by9']"
46
+ },
39
47
  {
40
48
  "kind": "variable",
41
49
  "name": "sizes",
@@ -47,7 +55,7 @@
47
55
  "type": {
48
56
  "text": "DefaultProps"
49
57
  },
50
- "default": "{\n variant: 'default',\n size: 48,\n src: '',\n alt: '',\n disabled: false,\n hasPadding: false,\n backgroundColor: 'default',\n placeholder: {\n src: '',\n alt: '',\n },\n}"
58
+ "default": "{\n variant: 'default',\n size: 48,\n src: '',\n alt: '',\n disabled: false,\n hasPadding: false,\n hideDefaultPlaceholder: false,\n backgroundColor: 'default',\n aspectRatio: '1by1',\n placeholder: {\n src: '',\n alt: '',\n },\n}"
51
59
  }
52
60
  ],
53
61
  "exports": [
@@ -75,6 +83,14 @@
75
83
  "module": "src/defs.js"
76
84
  }
77
85
  },
86
+ {
87
+ "kind": "js",
88
+ "name": "aspectRatios",
89
+ "declaration": {
90
+ "name": "aspectRatios",
91
+ "module": "src/defs.js"
92
+ }
93
+ },
78
94
  {
79
95
  "kind": "js",
80
96
  "name": "sizes",
@@ -114,6 +130,12 @@
114
130
  "privacy": "public",
115
131
  "attribute": "size"
116
132
  },
133
+ {
134
+ "kind": "field",
135
+ "name": "aspectRatio",
136
+ "privacy": "public",
137
+ "attribute": "aspectRatio"
138
+ },
117
139
  {
118
140
  "kind": "field",
119
141
  "name": "src",
@@ -138,6 +160,12 @@
138
160
  "privacy": "public",
139
161
  "attribute": "hasPadding"
140
162
  },
163
+ {
164
+ "kind": "field",
165
+ "name": "hideDefaultPlaceholder",
166
+ "privacy": "public",
167
+ "attribute": "hideDefaultPlaceholder"
168
+ },
141
169
  {
142
170
  "kind": "field",
143
171
  "name": "backgroundColor",
@@ -161,6 +189,44 @@
161
189
  },
162
190
  "privacy": "private"
163
191
  },
192
+ {
193
+ "kind": "field",
194
+ "name": "_hasError",
195
+ "type": {
196
+ "text": "boolean"
197
+ },
198
+ "privacy": "private",
199
+ "default": "false"
200
+ },
201
+ {
202
+ "kind": "field",
203
+ "name": "_isDefaultPlaceholderVisible",
204
+ "type": {
205
+ "text": "boolean"
206
+ },
207
+ "privacy": "private",
208
+ "description": "Determines if the default placeholder should be displayed.",
209
+ "readonly": true
210
+ },
211
+ {
212
+ "kind": "field",
213
+ "name": "_controlledSrc",
214
+ "type": {
215
+ "text": "string"
216
+ },
217
+ "privacy": "private",
218
+ "description": "Returns the appropriate image props based on the following order:\n1. If there is no error, return the provided image props.\n2. If there is an error and a custom placeholder is provided, return the placeholder props.\n3. If there is an error and no custom placeholder is provided, return the component default placeholder.\n4. Otherwise, fall back to the provided src (resulting in a broken image).",
219
+ "readonly": true
220
+ },
221
+ {
222
+ "kind": "field",
223
+ "name": "_controlledAlt",
224
+ "type": {
225
+ "text": "string"
226
+ },
227
+ "privacy": "private",
228
+ "readonly": true
229
+ },
164
230
  {
165
231
  "kind": "method",
166
232
  "name": "_generateSizeStyles",
@@ -169,19 +235,25 @@
169
235
  "type": {
170
236
  "text": "string"
171
237
  }
172
- }
238
+ },
239
+ "description": "Assigns CSS variables based on the size prop."
173
240
  },
174
241
  {
175
242
  "kind": "method",
176
243
  "name": "_handleImageError",
177
244
  "privacy": "private",
178
- "description": "Handles image load errors by replacing the src and alt props\nwith the placeholder props."
245
+ "return": {
246
+ "type": {
247
+ "text": "void"
248
+ }
249
+ },
250
+ "description": "Handles the image error event."
179
251
  },
180
252
  {
181
253
  "kind": "method",
182
254
  "name": "_checkImageError",
183
255
  "privacy": "private",
184
- "description": "Detects image load status and applies the placeholder on failure.\nThis is needed as the `onerror` event is not triggered in SSR."
256
+ "description": "Checks the image load status and triggers error handling if needed.\nThis is needed as the `onerror` event is not triggered in SSR."
185
257
  }
186
258
  ],
187
259
  "attributes": [
@@ -193,6 +265,10 @@
193
265
  "name": "size",
194
266
  "fieldName": "size"
195
267
  },
268
+ {
269
+ "name": "aspectRatio",
270
+ "fieldName": "aspectRatio"
271
+ },
196
272
  {
197
273
  "name": "src",
198
274
  "fieldName": "src"
@@ -209,6 +285,10 @@
209
285
  "name": "hasPadding",
210
286
  "fieldName": "hasPadding"
211
287
  },
288
+ {
289
+ "name": "hideDefaultPlaceholder",
290
+ "fieldName": "hideDefaultPlaceholder"
291
+ },
212
292
  {
213
293
  "name": "backgroundColor",
214
294
  "fieldName": "backgroundColor"
package/declaration.d.ts CHANGED
@@ -7,3 +7,8 @@ declare module '*.scss?inline' {
7
7
  const content: Record<string, string>;
8
8
  export default content;
9
9
  }
10
+
11
+ declare module '*.svg?inline' {
12
+ const content: string;
13
+ export default content;
14
+ }
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ import type { CSSResult } from 'lit';
3
3
  import type { LitElement } from 'lit';
4
4
  import type { TemplateResult } from 'lit-html';
5
5
 
6
+ export declare const aspectRatios: readonly ["1by1", "4by3", "16by9"];
7
+
6
8
  export declare const backgroundColorClassNames: Record<typeof backgroundColors[number], string>;
7
9
 
8
10
  export declare const backgroundColors: readonly ["default", "subtle", "strong", "dark", "inverse", "inverse-alternative"];
@@ -17,21 +19,39 @@ export declare const defaultProps: DefaultProps;
17
19
  export declare class PieThumbnail extends LitElement implements ThumbnailProps {
18
20
  variant: "default" | "outline";
19
21
  size: number;
22
+ aspectRatio: "1by1" | "4by3" | "16by9";
20
23
  src: string;
21
24
  alt: string;
22
25
  disabled: boolean;
23
26
  hasPadding: boolean;
27
+ hideDefaultPlaceholder: boolean;
24
28
  backgroundColor: "default" | "subtle" | "strong" | "dark" | "inverse" | "inverse-alternative";
25
29
  placeholder: ThumbnailProps['placeholder'];
26
30
  private img;
31
+ private _hasError;
32
+ /**
33
+ * Determines if the default placeholder should be displayed.
34
+ */
35
+ private get _isDefaultPlaceholderVisible();
36
+ /**
37
+ * Returns the appropriate image props based on the following order:
38
+ * 1. If there is no error, return the provided image props.
39
+ * 2. If there is an error and a custom placeholder is provided, return the placeholder props.
40
+ * 3. If there is an error and no custom placeholder is provided, return the component default placeholder.
41
+ * 4. Otherwise, fall back to the provided src (resulting in a broken image).
42
+ */
43
+ private get _controlledSrc();
44
+ private get _controlledAlt();
45
+ /**
46
+ * Assigns CSS variables based on the size prop.
47
+ */
27
48
  private _generateSizeStyles;
28
49
  /**
29
- * Handles image load errors by replacing the src and alt props
30
- * with the placeholder props.
50
+ * Handles the image error event.
31
51
  */
32
52
  private _handleImageError;
33
53
  /**
34
- * Detects image load status and applies the placeholder on failure.
54
+ * Checks the image load status and triggers error handling if needed.
35
55
  * This is needed as the `onerror` event is not triggered in SSR.
36
56
  */
37
57
  private _checkImageError;
@@ -77,9 +97,17 @@ export declare interface ThumbnailProps {
77
97
  */
78
98
  backgroundColor?: typeof backgroundColors[number];
79
99
  /**
80
- * What placeholder should be used when the image fails to load.
100
+ * When true, hides the component default placeholder on image load failure.
101
+ */
102
+ hideDefaultPlaceholder?: boolean;
103
+ /**
104
+ * Overrides the component default placeholder with a custom one on image load failure.
81
105
  */
82
106
  placeholder?: PlaceholderProps;
107
+ /**
108
+ * Sets the aspect-ratio of the thumbnail image.
109
+ */
110
+ aspectRatio?: typeof aspectRatios[number];
83
111
  }
84
112
 
85
113
  export declare const variants: readonly ["default", "outline"];
package/dist/index.js CHANGED
@@ -1,27 +1,27 @@
1
- import { LitElement as f, html as y, unsafeCSS as k } from "lit";
2
- import { validPropertyValues as b, defineCustomElement as S } from "@justeattakeaway/pie-webc-core";
3
- import { classMap as z } from "lit/directives/class-map.js";
4
- import { property as o, query as _ } from "lit/decorators.js";
5
- const C = [
1
+ import { LitElement as k, html as _, unsafeCSS as E } from "lit";
2
+ import { validPropertyValues as h, defineCustomElement as S } from "@justeattakeaway/pie-webc-core";
3
+ import { classMap as P } from "lit/directives/class-map.js";
4
+ import { property as l, query as z, state as C } from "lit/decorators.js";
5
+ const I = [
6
6
  "default",
7
7
  "outline"
8
- ], E = [
8
+ ], D = [
9
9
  "default",
10
10
  "subtle",
11
11
  "strong",
12
12
  "dark",
13
13
  "inverse",
14
14
  "inverse-alternative"
15
- ], I = {
15
+ ], x = {
16
16
  default: "c-thumbnail--backgroundDefault",
17
17
  subtle: "c-thumbnail--backgroundSubtle",
18
18
  strong: "c-thumbnail--backgroundStrong",
19
19
  dark: "c-thumbnail--backgroundDark",
20
20
  inverse: "c-thumbnail--backgroundInverse",
21
21
  "inverse-alternative": "c-thumbnail--backgroundInverseAlternative"
22
- }, m = 8, g = 24, x = 128, $ = Object.freeze(Array.from(
23
- { length: (x - g) / m + 1 },
24
- (c, t) => g + t * m
22
+ }, $ = ["1by1", "4by3", "16by9"], m = 8, g = 24, w = 128, R = Object.freeze(Array.from(
23
+ { length: (w - g) / m + 1 },
24
+ (u, t) => g + t * m
25
25
  )), e = {
26
26
  variant: "default",
27
27
  size: 48,
@@ -29,39 +29,65 @@ const C = [
29
29
  alt: "",
30
30
  disabled: !1,
31
31
  hasPadding: !1,
32
+ hideDefaultPlaceholder: !1,
32
33
  backgroundColor: "default",
34
+ aspectRatio: "1by1",
33
35
  placeholder: {
34
36
  src: "",
35
37
  alt: ""
36
38
  }
37
- }, P = ".c-thumbnail{--thumbnail-size: 48px;--thumbnail-border-radius: var(--dt-radius-rounded-b);--thumbnail-bg-color: var(--dt-color-container-default);--thumbnail-border-color: transparent;--thumbnail-padding: calc(var(--dt-spacing-a) / 2);display:flex;justify-content:center;align-items:center;box-sizing:border-box;overflow:hidden;width:var(--thumbnail-size);height:var(--thumbnail-size);border-radius:var(--thumbnail-border-radius);border:1px solid var(--thumbnail-border-color);background-color:var(--thumbnail-bg-color)}.c-thumbnail.c-thumbnail--outline{--thumbnail-border-color: var(--dt-color-border-default)}.c-thumbnail.c-thumbnail--disabled{opacity:.5}.c-thumbnail.c-thumbnail--backgroundSubtle{--thumbnail-bg-color: var(--dt-color-container-subtle)}.c-thumbnail.c-thumbnail--backgroundStrong{--thumbnail-bg-color: var(--dt-color-container-strong)}.c-thumbnail.c-thumbnail--backgroundDark{--thumbnail-bg-color: var(--dt-color-container-dark)}.c-thumbnail.c-thumbnail--backgroundInverse{--thumbnail-bg-color: var(--dt-color-container-inverse)}.c-thumbnail.c-thumbnail--backgroundInverseAlternative{--thumbnail-bg-color: var(--dt-color-container-inverse-alternative)}.c-thumbnail.c-thumbnail--padding{padding:var(--thumbnail-padding)}.c-thumbnail .c-thumbnail-img{width:100%;height:auto}";
38
- var A = Object.defineProperty, i = (c, t, r, u) => {
39
- for (var n = void 0, l = c.length - 1, s; l >= 0; l--)
40
- (s = c[l]) && (n = s(t, r, n) || n);
41
- return n && A(t, r, n), n;
39
+ }, j = ".c-thumbnail{--thumbnail-size: 48px;--thumbnail-border-radius: var(--dt-radius-rounded-b);--thumbnail-bg-color: var(--dt-color-container-default);--thumbnail-padding: calc(var(--dt-spacing-a) / 2);--thumbnail-default-placeholder-padding: var(--dt-spacing-b);--thumbnail-img-border-radius: unset;--thumbnail-img-object-fit: cover;display:flex;justify-content:center;align-items:center;box-sizing:border-box;overflow:hidden;width:var(--thumbnail-size);height:var(--thumbnail-size);border-radius:var(--thumbnail-border-radius);background-color:var(--thumbnail-bg-color)}.c-thumbnail.c-thumbnail--outline{border:1px solid var(--dt-color-border-default)}.c-thumbnail.c-thumbnail--disabled{opacity:.5}.c-thumbnail.c-thumbnail--backgroundSubtle{--thumbnail-bg-color: var(--dt-color-container-subtle)}.c-thumbnail.c-thumbnail--backgroundStrong{--thumbnail-bg-color: var(--dt-color-container-strong)}.c-thumbnail.c-thumbnail--backgroundDark{--thumbnail-bg-color: var(--dt-color-container-dark)}.c-thumbnail.c-thumbnail--backgroundInverse{--thumbnail-bg-color: var(--dt-color-container-inverse)}.c-thumbnail.c-thumbnail--backgroundInverseAlternative{--thumbnail-bg-color: var(--dt-color-container-inverse-alternative)}.c-thumbnail.c-thumbnail--padding{--thumbnail-img-border-radius: calc(var(--thumbnail-border-radius) - var(--thumbnail-padding));padding:var(--thumbnail-padding)}.c-thumbnail.c-thumbnail--4by3{height:calc(var(--thumbnail-size) * 3 / 4)}.c-thumbnail.c-thumbnail--16by9{height:calc(var(--thumbnail-size) * 9 / 16)}.c-thumbnail .c-thumbnail-img{width:100%;height:100%;border-radius:var(--thumbnail-img-border-radius);object-fit:var(--thumbnail-img-object-fit)}.c-thumbnail.c-thumbnail--defaultPlaceholder{--thumbnail-bg-color: var(--dt-color-container-default);--thumbnail-img-object-fit: fill;padding:var(--thumbnail-default-placeholder-padding)}", A = "data:image/svg+xml,%3csvg%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2032%2032'%3e%3cpath%20d='M31.9%2015.493c-.103-.254-1.496-2.753-4.081-5.887a.507.507%200%200%201-.12-.267%2048.166%2048.166%200%200%200-.777-5.436.881.881%200%200%200-.282-.468.925.925%200%200%200-.51-.22l-2.678-.324a.595.595%200%200%200-.07%200%20.575.575%200%200%200-.402.161.543.543%200%200%200-.166.39v.905a.034.034%200%200%201-.01.024.036.036%200%200%201-.037.007A2.43%202.43%200%200%200%2015.992%200c-.472%200-.935.138-1.327.395C5.387%205.956.282%2015.005.085%2015.493a.87.87%200%200%200%20.25%201.064.929.929%200%200%200%20.355.175l2.672.5a.917.917%200%200%201%20.454.28.872.872%200%200%201%20.213.479c.023.457.528%2010.69%201.163%2013.344a.89.89%200%200%200%20.327.48c.161.12.358.185.561.185h.02c1.56-.038%202.986-.063%204.528-.084h.13a.303.303%200%200%200%20.21-.086.288.288%200%200%200%20.087-.206c-.062-.893-.197-2.936-.306-5.093%200-.054-.007-.121-.01-.176a.558.558%200%200%200-.272-.442%202.847%202.847%200%200%201-.992-.96%202.745%202.745%200%200%201-.41-1.3c-.142-3.59-.211-7.736-.008-10.982a.515.515%200%200%201%20.165-.365.547.547%200%200%201%20.76.015c.098.1.152.234.149.373v.04c-.132%202.163-.142%204.721-.095%207.244a.61.61%200%200%200%20.195.435.648.648%200%200%200%20.902-.015.61.61%200%200%200%20.179-.442c-.049-2.54-.036-5.114.099-7.285a.515.515%200%200%201%20.165-.365.547.547%200%200%201%20.76.015c.098.1.152.234.15.373v.04c-.133%202.152-.143%204.695-.097%207.204a.608.608%200%200%200%20.194.436.648.648%200%200%200%20.902-.014.61.61%200%200%200%20.18-.44c-.047-2.53-.035-5.094.1-7.25a.515.515%200%200%201%20.165-.366.546.546%200%200%201%20.76.016c.099.1.152.234.15.372v.041c-.194%203.154-.131%207.148%200%2010.651a.26.26%200%200%201%200%20.033v.087c0%20.454-.114.902-.332%201.304a2.827%202.827%200%200%201-.919%201.004.54.54%200%200%200-.237.406s-.066.539.179%203.145c.113%201.138.201%201.927.241%202.283a.273.273%200%200%200%20.095.172c.052.044.12.068.188.068h4.054l1.516.012c.07%200%20.137-.024.19-.069a.273.273%200%200%200%20.094-.173c.35-3.124.434-4.879.434-4.879a.307.307%200%200%200-.084-.201.323.323%200%200%200-.2-.099l-1.842-.246a.636.636%200%200%201-.331-.143.606.606%200%200%201-.195-.296.832.832%200%200%201-.024-.276c.747-10.41%204.281-13.92%204.281-13.92a1.05%201.05%200%200%201%20.172-.144.576.576%200%200%201%20.859.245.968.968%200%200%201%20.045.25c.306%203.273.157%208.842-.032%2013.187a316.08%20316.08%200%200%201-.353%206.582c0%20.057.023.112.064.152.042.04.098.063.157.063%201.002.017%201.984.036%203.037.062h.021c.203%200%20.4-.065.561-.185a.893.893%200%200%200%20.328-.48c.635-2.654%201.134-12.887%201.16-13.344a.877.877%200%200%201%20.215-.479.92.92%200%200%201%20.453-.28l2.672-.5a.92.92%200%200%200%20.5-.316.875.875%200%200%200%20.193-.548.883.883%200%200%200-.1-.368Z'%20fill='%23EFEDEA'/%3e%3c/svg%3e";
40
+ var B = Object.defineProperty, i = (u, t, r, o) => {
41
+ for (var n = void 0, c = u.length - 1, d; c >= 0; c--)
42
+ (d = u[c]) && (n = d(t, r, n) || n);
43
+ return n && B(t, r, n), n;
42
44
  };
43
- const d = "pie-thumbnail", h = class h extends f {
45
+ const s = "pie-thumbnail", b = class b extends k {
44
46
  constructor() {
45
- super(...arguments), this.variant = e.variant, this.size = e.size, this.src = e.src, this.alt = e.alt, this.disabled = e.disabled, this.hasPadding = e.hasPadding, this.backgroundColor = e.backgroundColor, this.placeholder = e.placeholder;
47
+ super(...arguments), this.variant = e.variant, this.size = e.size, this.aspectRatio = e.aspectRatio, this.src = e.src, this.alt = e.alt, this.disabled = e.disabled, this.hasPadding = e.hasPadding, this.hideDefaultPlaceholder = e.hideDefaultPlaceholder, this.backgroundColor = e.backgroundColor, this.placeholder = e.placeholder, this._hasError = !1;
46
48
  }
49
+ /**
50
+ * Determines if the default placeholder should be displayed.
51
+ */
52
+ get _isDefaultPlaceholderVisible() {
53
+ const { _hasError: t, placeholder: r, hideDefaultPlaceholder: o } = this;
54
+ return t && !(r != null && r.src) && !o;
55
+ }
56
+ /**
57
+ * Returns the appropriate image props based on the following order:
58
+ * 1. If there is no error, return the provided image props.
59
+ * 2. If there is an error and a custom placeholder is provided, return the placeholder props.
60
+ * 3. If there is an error and no custom placeholder is provided, return the component default placeholder.
61
+ * 4. Otherwise, fall back to the provided src (resulting in a broken image).
62
+ */
63
+ get _controlledSrc() {
64
+ var t;
65
+ return this._hasError ? (t = this.placeholder) != null && t.src ? this.placeholder.src : this.hideDefaultPlaceholder ? this.src : A : this.src;
66
+ }
67
+ get _controlledAlt() {
68
+ var t;
69
+ return this._hasError ? (t = this.placeholder) != null && t.src ? this.placeholder.alt ?? "" : this.hideDefaultPlaceholder ? this.alt : "" : this.alt;
70
+ }
71
+ /**
72
+ * Assigns CSS variables based on the size prop.
73
+ */
47
74
  _generateSizeStyles() {
48
75
  const { size: t } = this;
49
- let r = "--dt-radius-rounded-c";
50
- return t <= 40 ? r = "--dt-radius-rounded-a" : t <= 56 && (r = "--dt-radius-rounded-b"), `
76
+ let r = "--dt-radius-rounded-c", o = "--dt-spacing-d";
77
+ return t <= 40 ? (r = "--dt-radius-rounded-a", o = "--dt-spacing-a") : t <= 56 && (r = "--dt-radius-rounded-b", o = "--dt-spacing-b"), `
51
78
  --thumbnail-size: ${t}px;
52
79
  --thumbnail-border-radius: var(${r});
80
+ --thumbnail-default-placeholder-padding: var(${o});
53
81
  `;
54
82
  }
55
83
  /**
56
- * Handles image load errors by replacing the src and alt props
57
- * with the placeholder props.
84
+ * Handles the image error event.
58
85
  */
59
86
  _handleImageError() {
60
- var t, r;
61
- (t = this.placeholder) != null && t.src && this.setAttribute("src", this.placeholder.src), (r = this.placeholder) != null && r.alt && this.setAttribute("alt", this.placeholder.alt);
87
+ this._hasError = !0;
62
88
  }
63
89
  /**
64
- * Detects image load status and applies the placeholder on failure.
90
+ * Checks the image load status and triggers error handling if needed.
65
91
  * This is needed as the `onerror` event is not triggered in SSR.
66
92
  */
67
93
  _checkImageError() {
@@ -76,71 +102,86 @@ const d = "pie-thumbnail", h = class h extends f {
76
102
  render() {
77
103
  const {
78
104
  variant: t,
79
- src: r,
80
- alt: u,
105
+ _controlledSrc: r,
106
+ _controlledAlt: o,
81
107
  disabled: n,
82
- hasPadding: l,
83
- backgroundColor: s
84
- } = this, p = {
108
+ hasPadding: c,
109
+ _isDefaultPlaceholderVisible: d,
110
+ backgroundColor: p,
111
+ aspectRatio: v
112
+ } = this, f = {
85
113
  "c-thumbnail": !0,
86
114
  [`c-thumbnail--${t}`]: !0,
87
- [I[s]]: !0,
115
+ [`c-thumbnail--${v}`]: !0,
116
+ [x[p]]: !0,
88
117
  "c-thumbnail--disabled": n,
89
- "c-thumbnail--padding": l
90
- }, v = this._generateSizeStyles();
91
- return y`
118
+ "c-thumbnail--padding": c,
119
+ "c-thumbnail--defaultPlaceholder": d
120
+ }, y = this._generateSizeStyles();
121
+ return _`
92
122
  <div data-test-id="pie-thumbnail"
93
- class="${z(p)}"
94
- style="${v}">
123
+ class="${P(f)}"
124
+ style="${y}">
95
125
  <img
96
126
  data-test-id="pie-thumbnail-img"
97
127
  src="${r}"
128
+ alt="${o}"
98
129
  class="c-thumbnail-img"
99
- alt="${u}"
100
130
  @error="${this._handleImageError}"
101
131
  />
102
132
  </div>
103
133
  `;
104
134
  }
105
135
  };
106
- h.styles = k(P);
107
- let a = h;
136
+ b.styles = E(j);
137
+ let a = b;
108
138
  i([
109
- o({ type: String }),
110
- b(d, C, e.variant)
139
+ l({ type: String }),
140
+ h(s, I, e.variant)
111
141
  ], a.prototype, "variant");
112
142
  i([
113
- o({ type: Number }),
114
- b(d, $, e.size)
143
+ l({ type: Number }),
144
+ h(s, R, e.size)
115
145
  ], a.prototype, "size");
116
146
  i([
117
- o({ type: String })
147
+ l({ type: String }),
148
+ h(s, $, e.aspectRatio)
149
+ ], a.prototype, "aspectRatio");
150
+ i([
151
+ l({ type: String })
118
152
  ], a.prototype, "src");
119
153
  i([
120
- o({ type: String })
154
+ l({ type: String })
121
155
  ], a.prototype, "alt");
122
156
  i([
123
- o({ type: Boolean })
157
+ l({ type: Boolean })
124
158
  ], a.prototype, "disabled");
125
159
  i([
126
- o({ type: Boolean })
160
+ l({ type: Boolean })
127
161
  ], a.prototype, "hasPadding");
128
162
  i([
129
- o({ type: String }),
130
- b(d, E, e.backgroundColor)
163
+ l({ type: Boolean })
164
+ ], a.prototype, "hideDefaultPlaceholder");
165
+ i([
166
+ l({ type: String }),
167
+ h(s, D, e.backgroundColor)
131
168
  ], a.prototype, "backgroundColor");
132
169
  i([
133
- o({ type: Object })
170
+ l({ type: Object })
134
171
  ], a.prototype, "placeholder");
135
172
  i([
136
- _("img")
173
+ z("img")
137
174
  ], a.prototype, "img");
138
- S(d, a);
175
+ i([
176
+ C()
177
+ ], a.prototype, "_hasError");
178
+ S(s, a);
139
179
  export {
140
180
  a as PieThumbnail,
141
- I as backgroundColorClassNames,
142
- E as backgroundColors,
181
+ $ as aspectRatios,
182
+ x as backgroundColorClassNames,
183
+ D as backgroundColors,
143
184
  e as defaultProps,
144
- $ as sizes,
145
- C as variants
185
+ R as sizes,
186
+ I as variants
146
187
  };
package/dist/react.d.ts CHANGED
@@ -4,6 +4,8 @@ import type { LitElement } from 'lit';
4
4
  import * as React_2 from 'react';
5
5
  import type { TemplateResult } from 'lit-html';
6
6
 
7
+ export declare const aspectRatios: readonly ["1by1", "4by3", "16by9"];
8
+
7
9
  export declare const backgroundColorClassNames: Record<typeof backgroundColors[number], string>;
8
10
 
9
11
  export declare const backgroundColors: readonly ["default", "subtle", "strong", "dark", "inverse", "inverse-alternative"];
@@ -20,21 +22,39 @@ export declare const PieThumbnail: React_2.ForwardRefExoticComponent<ThumbnailPr
20
22
  declare class PieThumbnail_2 extends LitElement implements ThumbnailProps {
21
23
  variant: "default" | "outline";
22
24
  size: number;
25
+ aspectRatio: "1by1" | "4by3" | "16by9";
23
26
  src: string;
24
27
  alt: string;
25
28
  disabled: boolean;
26
29
  hasPadding: boolean;
30
+ hideDefaultPlaceholder: boolean;
27
31
  backgroundColor: "default" | "subtle" | "strong" | "dark" | "inverse" | "inverse-alternative";
28
32
  placeholder: ThumbnailProps['placeholder'];
29
33
  private img;
34
+ private _hasError;
35
+ /**
36
+ * Determines if the default placeholder should be displayed.
37
+ */
38
+ private get _isDefaultPlaceholderVisible();
39
+ /**
40
+ * Returns the appropriate image props based on the following order:
41
+ * 1. If there is no error, return the provided image props.
42
+ * 2. If there is an error and a custom placeholder is provided, return the placeholder props.
43
+ * 3. If there is an error and no custom placeholder is provided, return the component default placeholder.
44
+ * 4. Otherwise, fall back to the provided src (resulting in a broken image).
45
+ */
46
+ private get _controlledSrc();
47
+ private get _controlledAlt();
48
+ /**
49
+ * Assigns CSS variables based on the size prop.
50
+ */
30
51
  private _generateSizeStyles;
31
52
  /**
32
- * Handles image load errors by replacing the src and alt props
33
- * with the placeholder props.
53
+ * Handles the image error event.
34
54
  */
35
55
  private _handleImageError;
36
56
  /**
37
- * Detects image load status and applies the placeholder on failure.
57
+ * Checks the image load status and triggers error handling if needed.
38
58
  * This is needed as the `onerror` event is not triggered in SSR.
39
59
  */
40
60
  private _checkImageError;
@@ -82,9 +102,17 @@ export declare interface ThumbnailProps {
82
102
  */
83
103
  backgroundColor?: typeof backgroundColors[number];
84
104
  /**
85
- * What placeholder should be used when the image fails to load.
105
+ * When true, hides the component default placeholder on image load failure.
106
+ */
107
+ hideDefaultPlaceholder?: boolean;
108
+ /**
109
+ * Overrides the component default placeholder with a custom one on image load failure.
86
110
  */
87
111
  placeholder?: PlaceholderProps;
112
+ /**
113
+ * Sets the aspect-ratio of the thumbnail image.
114
+ */
115
+ aspectRatio?: typeof aspectRatios[number];
88
116
  }
89
117
 
90
118
  export declare const variants: readonly ["default", "outline"];
package/dist/react.js CHANGED
@@ -1,19 +1,20 @@
1
1
  import * as a from "react";
2
2
  import { createComponent as e } from "@lit/react";
3
3
  import { PieThumbnail as o } from "./index.js";
4
- import { backgroundColorClassNames as l, backgroundColors as p, defaultProps as u, sizes as b, variants as c } from "./index.js";
5
- const i = e({
4
+ import { aspectRatios as l, backgroundColorClassNames as p, backgroundColors as c, defaultProps as u, sizes as b, variants as h } from "./index.js";
5
+ const t = e({
6
6
  displayName: "PieThumbnail",
7
7
  elementClass: o,
8
8
  react: a,
9
9
  tagName: "pie-thumbnail",
10
10
  events: {}
11
- }), r = i;
11
+ }), s = t;
12
12
  export {
13
- r as PieThumbnail,
14
- l as backgroundColorClassNames,
15
- p as backgroundColors,
13
+ s as PieThumbnail,
14
+ l as aspectRatios,
15
+ p as backgroundColorClassNames,
16
+ c as backgroundColors,
16
17
  u as defaultProps,
17
18
  b as sizes,
18
- c as variants
19
+ h as variants
19
20
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-thumbnail",
3
3
  "description": "PIE Design System Thumbnail built using Web Components",
4
- "version": "0.4.0",
4
+ "version": "0.6.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -24,8 +24,6 @@
24
24
  "lint:style": "run -T stylelint ./src/**/*.{css,scss}",
25
25
  "lint:style:fix": "yarn lint:style --fix",
26
26
  "watch": "run -T vite build --watch",
27
- "test": "echo \"Error: no test specified\" && exit 0",
28
- "test:ci": "yarn test",
29
27
  "test:browsers": "npx playwright test -c ./playwright-lit.config.ts",
30
28
  "test:browsers:ci": "yarn test:browsers",
31
29
  "test:visual": "run -T cross-env-shell PERCY_TOKEN=${PERCY_TOKEN_PIE_THUMBNAIL} percy exec --allowed-hostname cloudfront.net -- npx playwright test -c ./playwright-lit-visual.config.ts",
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M31.9 15.493c-.103-.254-1.496-2.753-4.081-5.887a.507.507 0 0 1-.12-.267 48.166 48.166 0 0 0-.777-5.436.881.881 0 0 0-.282-.468.925.925 0 0 0-.51-.22l-2.678-.324a.595.595 0 0 0-.07 0 .575.575 0 0 0-.402.161.543.543 0 0 0-.166.39v.905a.034.034 0 0 1-.01.024.036.036 0 0 1-.037.007A2.43 2.43 0 0 0 15.992 0c-.472 0-.935.138-1.327.395C5.387 5.956.282 15.005.085 15.493a.87.87 0 0 0 .25 1.064.929.929 0 0 0 .355.175l2.672.5a.917.917 0 0 1 .454.28.872.872 0 0 1 .213.479c.023.457.528 10.69 1.163 13.344a.89.89 0 0 0 .327.48c.161.12.358.185.561.185h.02c1.56-.038 2.986-.063 4.528-.084h.13a.303.303 0 0 0 .21-.086.288.288 0 0 0 .087-.206c-.062-.893-.197-2.936-.306-5.093 0-.054-.007-.121-.01-.176a.558.558 0 0 0-.272-.442 2.847 2.847 0 0 1-.992-.96 2.745 2.745 0 0 1-.41-1.3c-.142-3.59-.211-7.736-.008-10.982a.515.515 0 0 1 .165-.365.547.547 0 0 1 .76.015c.098.1.152.234.149.373v.04c-.132 2.163-.142 4.721-.095 7.244a.61.61 0 0 0 .195.435.648.648 0 0 0 .902-.015.61.61 0 0 0 .179-.442c-.049-2.54-.036-5.114.099-7.285a.515.515 0 0 1 .165-.365.547.547 0 0 1 .76.015c.098.1.152.234.15.373v.04c-.133 2.152-.143 4.695-.097 7.204a.608.608 0 0 0 .194.436.648.648 0 0 0 .902-.014.61.61 0 0 0 .18-.44c-.047-2.53-.035-5.094.1-7.25a.515.515 0 0 1 .165-.366.546.546 0 0 1 .76.016c.099.1.152.234.15.372v.041c-.194 3.154-.131 7.148 0 10.651a.26.26 0 0 1 0 .033v.087c0 .454-.114.902-.332 1.304a2.827 2.827 0 0 1-.919 1.004.54.54 0 0 0-.237.406s-.066.539.179 3.145c.113 1.138.201 1.927.241 2.283a.273.273 0 0 0 .095.172c.052.044.12.068.188.068h4.054l1.516.012c.07 0 .137-.024.19-.069a.273.273 0 0 0 .094-.173c.35-3.124.434-4.879.434-4.879a.307.307 0 0 0-.084-.201.323.323 0 0 0-.2-.099l-1.842-.246a.636.636 0 0 1-.331-.143.606.606 0 0 1-.195-.296.832.832 0 0 1-.024-.276c.747-10.41 4.281-13.92 4.281-13.92a1.05 1.05 0 0 1 .172-.144.576.576 0 0 1 .859.245.968.968 0 0 1 .045.25c.306 3.273.157 8.842-.032 13.187a316.08 316.08 0 0 1-.353 6.582c0 .057.023.112.064.152.042.04.098.063.157.063 1.002.017 1.984.036 3.037.062h.021c.203 0 .4-.065.561-.185a.893.893 0 0 0 .328-.48c.635-2.654 1.134-12.887 1.16-13.344a.877.877 0 0 1 .215-.479.92.92 0 0 1 .453-.28l2.672-.5a.92.92 0 0 0 .5-.316.875.875 0 0 0 .193-.548.883.883 0 0 0-.1-.368Z" fill="#EFEDEA"/></svg>
package/src/defs.ts CHANGED
@@ -17,6 +17,8 @@ export const backgroundColorClassNames: Record<typeof backgroundColors[number],
17
17
  'inverse-alternative': 'c-thumbnail--backgroundInverseAlternative',
18
18
  };
19
19
 
20
+ export const aspectRatios = ['1by1', '4by3', '16by9'] as const;
21
+
20
22
  const SIZE_INCREMENT_BY = 8;
21
23
  const SIZE_MIN = 24;
22
24
  const SIZE_MAX = 128;
@@ -61,9 +63,17 @@ export interface ThumbnailProps {
61
63
  */
62
64
  backgroundColor?: typeof backgroundColors[number];
63
65
  /**
64
- * What placeholder should be used when the image fails to load.
66
+ * When true, hides the component default placeholder on image load failure.
67
+ */
68
+ hideDefaultPlaceholder?: boolean;
69
+ /**
70
+ * Overrides the component default placeholder with a custom one on image load failure.
65
71
  */
66
72
  placeholder?: PlaceholderProps;
73
+ /**
74
+ * Sets the aspect-ratio of the thumbnail image.
75
+ */
76
+ aspectRatio?: typeof aspectRatios[number];
67
77
  }
68
78
 
69
79
  export type DefaultProps = ComponentDefaultProps<ThumbnailProps>;
@@ -75,7 +85,9 @@ export const defaultProps: DefaultProps = {
75
85
  alt: '',
76
86
  disabled: false,
77
87
  hasPadding: false,
88
+ hideDefaultPlaceholder: false,
78
89
  backgroundColor: 'default',
90
+ aspectRatio: '1by1',
79
91
  placeholder: {
80
92
  src: '',
81
93
  alt: '',
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from 'lit';
6
6
  import { defineCustomElement, validPropertyValues } from '@justeattakeaway/pie-webc-core';
7
7
  import { classMap } from 'lit/directives/class-map.js';
8
- import { property, query } from 'lit/decorators.js';
8
+ import { property, query, state } from 'lit/decorators.js';
9
9
  import {
10
10
  type ThumbnailProps,
11
11
  defaultProps,
@@ -13,8 +13,10 @@ import {
13
13
  backgroundColors,
14
14
  backgroundColorClassNames,
15
15
  sizes,
16
+ aspectRatios,
16
17
  } from './defs';
17
18
  import styles from './thumbnail.scss?inline';
19
+ import defaultPlaceholder from './default-placeholder.svg?inline';
18
20
 
19
21
  // Valid values available to consumers
20
22
  export * from './defs';
@@ -33,6 +35,10 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
33
35
  @validPropertyValues(componentSelector, sizes, defaultProps.size)
34
36
  public size = defaultProps.size;
35
37
 
38
+ @property({ type: String })
39
+ @validPropertyValues(componentSelector, aspectRatios, defaultProps.aspectRatio)
40
+ public aspectRatio = defaultProps.aspectRatio;
41
+
36
42
  @property({ type: String })
37
43
  public src = defaultProps.src;
38
44
 
@@ -45,6 +51,9 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
45
51
  @property({ type: Boolean })
46
52
  public hasPadding = defaultProps.hasPadding;
47
53
 
54
+ @property({ type: Boolean })
55
+ public hideDefaultPlaceholder = defaultProps.hideDefaultPlaceholder;
56
+
48
57
  @property({ type: String })
49
58
  @validPropertyValues(componentSelector, backgroundColors, defaultProps.backgroundColor)
50
59
  public backgroundColor = defaultProps.backgroundColor;
@@ -55,32 +64,69 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
55
64
  @query('img')
56
65
  private img!: HTMLImageElement;
57
66
 
67
+ @state()
68
+ private _hasError = false;
69
+
70
+ /**
71
+ * Determines if the default placeholder should be displayed.
72
+ */
73
+ private get _isDefaultPlaceholderVisible (): boolean {
74
+ const { _hasError, placeholder, hideDefaultPlaceholder } = this;
75
+ return _hasError && !placeholder?.src && !hideDefaultPlaceholder;
76
+ }
77
+
78
+ /**
79
+ * Returns the appropriate image props based on the following order:
80
+ * 1. If there is no error, return the provided image props.
81
+ * 2. If there is an error and a custom placeholder is provided, return the placeholder props.
82
+ * 3. If there is an error and no custom placeholder is provided, return the component default placeholder.
83
+ * 4. Otherwise, fall back to the provided src (resulting in a broken image).
84
+ */
85
+ private get _controlledSrc (): string {
86
+ if (!this._hasError) return this.src;
87
+ if (this.placeholder?.src) return this.placeholder.src;
88
+ if (!this.hideDefaultPlaceholder) return defaultPlaceholder;
89
+ return this.src;
90
+ }
91
+
92
+ private get _controlledAlt (): string {
93
+ if (!this._hasError) return this.alt;
94
+ if (this.placeholder?.src) return this.placeholder.alt ?? '';
95
+ if (!this.hideDefaultPlaceholder) return '';
96
+ return this.alt;
97
+ }
98
+
99
+ /**
100
+ * Assigns CSS variables based on the size prop.
101
+ */
58
102
  private _generateSizeStyles (): string {
59
103
  const { size } = this;
60
104
  let borderRadius = '--dt-radius-rounded-c';
105
+ let defaultPlaceholderPadding = '--dt-spacing-d';
61
106
  if (size <= 40) {
62
107
  borderRadius = '--dt-radius-rounded-a';
108
+ defaultPlaceholderPadding = '--dt-spacing-a';
63
109
  } else if (size <= 56) {
64
110
  borderRadius = '--dt-radius-rounded-b';
111
+ defaultPlaceholderPadding = '--dt-spacing-b';
65
112
  }
66
113
 
67
114
  return `
68
115
  --thumbnail-size: ${size}px;
69
116
  --thumbnail-border-radius: var(${borderRadius});
117
+ --thumbnail-default-placeholder-padding: var(${defaultPlaceholderPadding});
70
118
  `;
71
119
  }
72
120
 
73
121
  /**
74
- * Handles image load errors by replacing the src and alt props
75
- * with the placeholder props.
122
+ * Handles the image error event.
76
123
  */
77
- private _handleImageError () {
78
- if (this.placeholder?.src) this.setAttribute('src', this.placeholder.src);
79
- if (this.placeholder?.alt) this.setAttribute('alt', this.placeholder.alt);
124
+ private _handleImageError (): void {
125
+ this._hasError = true;
80
126
  }
81
127
 
82
128
  /**
83
- * Detects image load status and applies the placeholder on failure.
129
+ * Checks the image load status and triggers error handling if needed.
84
130
  * This is needed as the `onerror` event is not triggered in SSR.
85
131
  */
86
132
  private _checkImageError () {
@@ -98,19 +144,23 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
98
144
  render () {
99
145
  const {
100
146
  variant,
101
- src,
102
- alt,
147
+ _controlledSrc,
148
+ _controlledAlt,
103
149
  disabled,
104
150
  hasPadding,
151
+ _isDefaultPlaceholderVisible,
105
152
  backgroundColor,
153
+ aspectRatio,
106
154
  } = this;
107
155
 
108
156
  const wrapperClasses = {
109
157
  'c-thumbnail': true,
110
158
  [`c-thumbnail--${variant}`]: true,
159
+ [`c-thumbnail--${aspectRatio}`]: true,
111
160
  [backgroundColorClassNames[backgroundColor]]: true,
112
161
  'c-thumbnail--disabled': disabled,
113
162
  'c-thumbnail--padding': hasPadding,
163
+ 'c-thumbnail--defaultPlaceholder': _isDefaultPlaceholderVisible,
114
164
  };
115
165
 
116
166
  const sizeStyles = this._generateSizeStyles();
@@ -121,9 +171,9 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
121
171
  style="${sizeStyles}">
122
172
  <img
123
173
  data-test-id="pie-thumbnail-img"
124
- src="${src}"
174
+ src="${_controlledSrc}"
175
+ alt="${_controlledAlt}"
125
176
  class="c-thumbnail-img"
126
- alt="${alt}"
127
177
  @error="${this._handleImageError}"
128
178
  />
129
179
  </div>
@@ -2,8 +2,10 @@
2
2
  --thumbnail-size: 48px;
3
3
  --thumbnail-border-radius: var(--dt-radius-rounded-b);
4
4
  --thumbnail-bg-color: var(--dt-color-container-default);
5
- --thumbnail-border-color: transparent;
6
5
  --thumbnail-padding: calc(var(--dt-spacing-a) / 2);
6
+ --thumbnail-default-placeholder-padding: var(--dt-spacing-b);
7
+ --thumbnail-img-border-radius: unset;
8
+ --thumbnail-img-object-fit: cover;
7
9
 
8
10
  display: flex;
9
11
  justify-content: center;
@@ -13,11 +15,10 @@
13
15
  width: var(--thumbnail-size);
14
16
  height: var(--thumbnail-size);
15
17
  border-radius: var(--thumbnail-border-radius);
16
- border: 1px solid var(--thumbnail-border-color);
17
18
  background-color: var(--thumbnail-bg-color);
18
19
 
19
20
  &.c-thumbnail--outline {
20
- --thumbnail-border-color: var(--dt-color-border-default);
21
+ border: 1px solid var(--dt-color-border-default);
21
22
  }
22
23
 
23
24
  &.c-thumbnail--disabled {
@@ -45,11 +46,28 @@
45
46
  }
46
47
 
47
48
  &.c-thumbnail--padding {
49
+ --thumbnail-img-border-radius: calc(var(--thumbnail-border-radius) - var(--thumbnail-padding));
48
50
  padding: var(--thumbnail-padding);
49
51
  }
50
52
 
53
+ &.c-thumbnail--4by3 {
54
+ height: calc(var(--thumbnail-size) * 3 / 4)
55
+ }
56
+
57
+ &.c-thumbnail--16by9 {
58
+ height: calc(var(--thumbnail-size) * 9 / 16)
59
+ }
60
+
51
61
  .c-thumbnail-img {
52
62
  width: 100%;
53
- height: auto;
63
+ height: 100%;
64
+ border-radius: var(--thumbnail-img-border-radius);
65
+ object-fit: var(--thumbnail-img-object-fit);
66
+ }
67
+
68
+ &.c-thumbnail--defaultPlaceholder {
69
+ --thumbnail-bg-color: var(--dt-color-container-default);
70
+ --thumbnail-img-object-fit: fill;
71
+ padding: var(--thumbnail-default-placeholder-padding);
54
72
  }
55
73
  }