@justeattakeaway/pie-thumbnail 0.5.0 → 0.6.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.
@@ -55,7 +55,7 @@
55
55
  "type": {
56
56
  "text": "DefaultProps"
57
57
  },
58
- "default": "{\n variant: 'default',\n size: 48,\n src: '',\n alt: '',\n disabled: false,\n hasPadding: false,\n backgroundColor: 'default',\n aspectRatio: '1by1',\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}"
59
59
  }
60
60
  ],
61
61
  "exports": [
@@ -160,6 +160,12 @@
160
160
  "privacy": "public",
161
161
  "attribute": "hasPadding"
162
162
  },
163
+ {
164
+ "kind": "field",
165
+ "name": "hideDefaultPlaceholder",
166
+ "privacy": "public",
167
+ "attribute": "hideDefaultPlaceholder"
168
+ },
163
169
  {
164
170
  "kind": "field",
165
171
  "name": "backgroundColor",
@@ -183,6 +189,44 @@
183
189
  },
184
190
  "privacy": "private"
185
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
+ },
186
230
  {
187
231
  "kind": "method",
188
232
  "name": "_generateSizeStyles",
@@ -192,19 +236,24 @@
192
236
  "text": "string"
193
237
  }
194
238
  },
195
- "description": "Assigns the thumbnail size and border radius CSS variables\nbased on the size prop."
239
+ "description": "Assigns CSS variables based on the size prop."
196
240
  },
197
241
  {
198
242
  "kind": "method",
199
243
  "name": "_handleImageError",
200
244
  "privacy": "private",
201
- "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."
202
251
  },
203
252
  {
204
253
  "kind": "method",
205
254
  "name": "_checkImageError",
206
255
  "privacy": "private",
207
- "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."
208
257
  }
209
258
  ],
210
259
  "attributes": [
@@ -236,6 +285,10 @@
236
285
  "name": "hasPadding",
237
286
  "fieldName": "hasPadding"
238
287
  },
288
+ {
289
+ "name": "hideDefaultPlaceholder",
290
+ "fieldName": "hideDefaultPlaceholder"
291
+ },
239
292
  {
240
293
  "name": "backgroundColor",
241
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
@@ -24,21 +24,34 @@ export declare class PieThumbnail extends LitElement implements ThumbnailProps {
24
24
  alt: string;
25
25
  disabled: boolean;
26
26
  hasPadding: boolean;
27
+ hideDefaultPlaceholder: boolean;
27
28
  backgroundColor: "default" | "subtle" | "strong" | "dark" | "inverse" | "inverse-alternative";
28
29
  placeholder: ThumbnailProps['placeholder'];
29
30
  private img;
31
+ private _hasError;
30
32
  /**
31
- * Assigns the thumbnail size and border radius CSS variables
32
- * based on the size prop.
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.
33
47
  */
34
48
  private _generateSizeStyles;
35
49
  /**
36
- * Handles image load errors by replacing the src and alt props
37
- * with the placeholder props.
50
+ * Handles the image error event.
38
51
  */
39
52
  private _handleImageError;
40
53
  /**
41
- * Detects image load status and applies the placeholder on failure.
54
+ * Checks the image load status and triggers error handling if needed.
42
55
  * This is needed as the `onerror` event is not triggered in SSR.
43
56
  */
44
57
  private _checkImageError;
@@ -84,7 +97,11 @@ export declare interface ThumbnailProps {
84
97
  */
85
98
  backgroundColor?: typeof backgroundColors[number];
86
99
  /**
87
- * 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.
88
105
  */
89
106
  placeholder?: PlaceholderProps;
90
107
  /**
package/dist/index.js CHANGED
@@ -1,27 +1,27 @@
1
- import { LitElement as f, html as k, unsafeCSS as S } from "lit";
2
- import { validPropertyValues as d, defineCustomElement as z } from "@justeattakeaway/pie-webc-core";
3
- import { classMap as _ } from "lit/directives/class-map.js";
4
- import { property as n, query as C } from "lit/decorators.js";
5
- const E = [
1
+ import { LitElement as k, html as _, unsafeCSS as S } from "lit";
2
+ import { validPropertyValues as h, defineCustomElement as P } from "@justeattakeaway/pie-webc-core";
3
+ import { classMap as E } 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
- ], I = [
8
+ ], D = [
9
9
  "default",
10
10
  "subtle",
11
11
  "strong",
12
12
  "dark",
13
13
  "inverse",
14
14
  "inverse-alternative"
15
- ], R = {
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
- }, $ = ["1by1", "4by3", "16by9"], m = 8, p = 24, x = 128, P = Object.freeze(Array.from(
23
- { length: (x - p) / m + 1 },
24
- (u, t) => p + 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
  )), r = {
26
26
  variant: "default",
27
27
  size: 48,
@@ -29,50 +29,71 @@ const E = [
29
29
  alt: "",
30
30
  disabled: !1,
31
31
  hasPadding: !1,
32
+ hideDefaultPlaceholder: !1,
32
33
  backgroundColor: "default",
33
34
  aspectRatio: "1by1",
34
35
  placeholder: {
35
36
  src: "",
36
37
  alt: ""
37
38
  }
38
- }, A = ".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-img-border-radius: unset;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:cover}";
39
- var j = Object.defineProperty, i = (u, t, a, b) => {
40
- for (var o = void 0, l = u.length - 1, c; l >= 0; l--)
41
- (c = u[l]) && (o = c(t, a, o) || o);
42
- return o && j(t, a, o), o;
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%20xmlns='http://www.w3.org/2000/svg'%20fill='none'%20viewBox='0%200%2033%2032'%3e%3cpath%20d='M32.399%2015.493c-.102-.255-1.495-2.753-4.081-5.888a.507.507%200%200%201-.119-.267%2048.162%2048.162%200%200%200-.778-5.436.88.88%200%200%200-.282-.467.928.928%200%200%200-.509-.221l-2.679-.323a.84.84%200%200%200-.071%200c-.15%200-.294.058-.401.161s-.166.243-.166.389v.906c0%20.009-.004.017-.011.023s-.015.01-.025.01c-.015-.003-.018-.006-.021-.008A37.89%2037.89%200%200%200%2017.829.395%202.43%202.43%200%200%200%2016.493%200c-.473%200-.936.138-1.328.395C5.887%205.956.782%2015.005.585%2015.493a.87.87%200%200%200%20.004.757.9.9%200%200%200%20.246.306.93.93%200%200%200%20.356.176l2.672.5c.177.047.335.144.454.279s.193.302.213.479c.023.457.527%2010.691%201.162%2013.345a.89.89%200%200%200%20.328.479c.161.12.358.186.561.185h.02l4.528-.084h.129c.079-.001.155-.032.211-.086s.087-.128.087-.205l-.306-5.093c0-.054-.007-.121-.01-.176a.54.54%200%200%200-.08-.254.56.56%200%200%200-.193-.188c-.407-.236-.747-.566-.992-.96s-.385-.841-.41-1.3c-.142-3.589-.211-7.735-.008-10.981.003-.138.062-.27.165-.366a.545.545%200%200%201%20.76.016c.099.1.152.234.149.372v.041c-.132%202.163-.142%204.72-.095%207.244a.61.61%200%200%200%20.195.435.65.65%200%200%200%20.454.174.65.65%200%200%200%20.448-.189.61.61%200%200%200%20.179-.441c-.048-2.54-.035-5.114.099-7.285.003-.138.062-.27.165-.366a.545.545%200%200%201%20.76.016c.099.1.152.234.149.372v.041c-.132%202.152-.142%204.694-.096%207.204a.61.61%200%200%200%20.194.435.65.65%200%200%200%20.454.175.65.65%200%200%200%20.448-.188.61.61%200%200%200%20.18-.441c-.047-2.529-.034-5.093.101-7.249.003-.138.063-.27.165-.366s.241-.148.383-.145.278.061.377.161.152.234.149.372v.041c-.193%203.154-.13%207.147%200%2010.651a.26.26%200%200%201%200%20.033v.086c0%20.455-.113.902-.331%201.305s-.533.747-.919%201.004a.54.54%200%200%200-.166.177.52.52%200%200%200-.07.229s-.067.538.179%203.144l.241%202.284c.009.067.043.128.095.172s.119.068.188.068h4.053l1.516.012c.07%200%20.137-.024.189-.069s.086-.107.094-.174c.35-3.123.434-4.879.434-4.879-.002-.075-.032-.146-.084-.201s-.123-.09-.199-.099l-1.843-.246c-.122-.015-.238-.064-.331-.142s-.161-.181-.195-.297a.83.83%200%200%201-.024-.275c.747-10.411%204.282-13.92%204.282-13.92a1.05%201.05%200%200%201%20.172-.144.575.575%200%200%201%20.491-.082.57.57%200%200%201%20.223.122.55.55%200%200%201%20.145.205.97.97%200%200%201%20.045.251c.306%203.272.157%208.842-.033%2013.186l-.353%206.583c0%20.057.023.112.065.152s.098.063.156.063l3.037.062h.021a.94.94%200%200%200%20.561-.186.892.892%200%200%200%20.328-.479c.635-2.654%201.134-12.887%201.161-13.344a.877.877%200%200%201%20.214-.479.92.92%200%200%201%20.453-.28l2.672-.5a.92.92%200%200%200%20.499-.317.873.873%200%200%200%20.194-.548.885.885%200%200%200-.101-.368z'%20fill='%23efedea'/%3e%3c/svg%3e";
40
+ var B = Object.defineProperty, i = (u, t, e, o) => {
41
+ for (var n = void 0, c = u.length - 1, d; c >= 0; c--)
42
+ (d = u[c]) && (n = d(t, e, n) || n);
43
+ return n && B(t, e, n), n;
43
44
  };
44
- const s = "pie-thumbnail", h = class h extends f {
45
+ const s = "pie-thumbnail", b = class b extends k {
45
46
  constructor() {
46
- super(...arguments), this.variant = r.variant, this.size = r.size, this.aspectRatio = r.aspectRatio, this.src = r.src, this.alt = r.alt, this.disabled = r.disabled, this.hasPadding = r.hasPadding, this.backgroundColor = r.backgroundColor, this.placeholder = r.placeholder;
47
+ super(...arguments), this.variant = r.variant, this.size = r.size, this.aspectRatio = r.aspectRatio, this.src = r.src, this.alt = r.alt, this.disabled = r.disabled, this.hasPadding = r.hasPadding, this.hideDefaultPlaceholder = r.hideDefaultPlaceholder, this.backgroundColor = r.backgroundColor, this.placeholder = r.placeholder, this._hasError = !1;
47
48
  }
48
49
  /**
49
- * Assigns the thumbnail size and border radius CSS variables
50
- * based on the size prop.
50
+ * Determines if the default placeholder should be displayed.
51
+ */
52
+ get _isDefaultPlaceholderVisible() {
53
+ const { _hasError: t, placeholder: e, hideDefaultPlaceholder: o } = this;
54
+ return t && !(e != null && e.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.
51
73
  */
52
74
  _generateSizeStyles() {
53
75
  const { size: t } = this;
54
- let a = "--dt-radius-rounded-c";
55
- return t <= 40 ? a = "--dt-radius-rounded-a" : t <= 56 && (a = "--dt-radius-rounded-b"), `
76
+ let e = "--dt-radius-rounded-c", o = "--dt-spacing-d";
77
+ return t <= 40 ? (e = "--dt-radius-rounded-a", o = "--dt-spacing-a") : t <= 56 && (e = "--dt-radius-rounded-b", o = "--dt-spacing-b"), `
56
78
  --thumbnail-size: ${t}px;
57
- --thumbnail-border-radius: var(${a});
79
+ --thumbnail-border-radius: var(${e});
80
+ --thumbnail-default-placeholder-padding: var(${o});
58
81
  `;
59
82
  }
60
83
  /**
61
- * Handles image load errors by replacing the src and alt props
62
- * with the placeholder props.
84
+ * Handles the image error event.
63
85
  */
64
86
  _handleImageError() {
65
- var t, a;
66
- (t = this.placeholder) != null && t.src && this.setAttribute("src", this.placeholder.src), (a = this.placeholder) != null && a.alt && this.setAttribute("alt", this.placeholder.alt);
87
+ this._hasError = !0;
67
88
  }
68
89
  /**
69
- * Detects image load status and applies the placeholder on failure.
90
+ * Checks the image load status and triggers error handling if needed.
70
91
  * This is needed as the `onerror` event is not triggered in SSR.
71
92
  */
72
93
  _checkImageError() {
73
94
  if (this.img) {
74
- const { complete: t, naturalHeight: a } = this.img;
75
- t && a === 0 && this._handleImageError.call(this);
95
+ const { complete: t, naturalHeight: e } = this.img;
96
+ t && e === 0 && this._handleImageError.call(this);
76
97
  }
77
98
  }
78
99
  firstUpdated() {
@@ -81,78 +102,86 @@ const s = "pie-thumbnail", h = class h extends f {
81
102
  render() {
82
103
  const {
83
104
  variant: t,
84
- src: a,
85
- alt: b,
86
- disabled: o,
87
- hasPadding: l,
88
- backgroundColor: c,
89
- aspectRatio: g
105
+ _controlledSrc: e,
106
+ _controlledAlt: o,
107
+ disabled: n,
108
+ hasPadding: c,
109
+ _isDefaultPlaceholderVisible: d,
110
+ backgroundColor: p,
111
+ aspectRatio: f
90
112
  } = this, v = {
91
113
  "c-thumbnail": !0,
92
114
  [`c-thumbnail--${t}`]: !0,
93
- [`c-thumbnail--${g}`]: !0,
94
- [R[c]]: !0,
95
- "c-thumbnail--disabled": o,
96
- "c-thumbnail--padding": l
115
+ [`c-thumbnail--${f}`]: !0,
116
+ [x[p]]: !0,
117
+ "c-thumbnail--disabled": n,
118
+ "c-thumbnail--padding": c,
119
+ "c-thumbnail--defaultPlaceholder": d
97
120
  }, y = this._generateSizeStyles();
98
- return k`
121
+ return _`
99
122
  <div data-test-id="pie-thumbnail"
100
- class="${_(v)}"
123
+ class="${E(v)}"
101
124
  style="${y}">
102
125
  <img
103
126
  data-test-id="pie-thumbnail-img"
104
- src="${a}"
127
+ src="${e}"
128
+ alt="${o}"
105
129
  class="c-thumbnail-img"
106
- alt="${b}"
107
130
  @error="${this._handleImageError}"
108
131
  />
109
132
  </div>
110
133
  `;
111
134
  }
112
135
  };
113
- h.styles = S(A);
114
- let e = h;
136
+ b.styles = S(j);
137
+ let a = b;
138
+ i([
139
+ l({ type: String }),
140
+ h(s, I, r.variant)
141
+ ], a.prototype, "variant");
142
+ i([
143
+ l({ type: Number }),
144
+ h(s, R, r.size)
145
+ ], a.prototype, "size");
115
146
  i([
116
- n({ type: String }),
117
- d(s, E, r.variant)
118
- ], e.prototype, "variant");
147
+ l({ type: String }),
148
+ h(s, $, r.aspectRatio)
149
+ ], a.prototype, "aspectRatio");
119
150
  i([
120
- n({ type: Number }),
121
- d(s, P, r.size)
122
- ], e.prototype, "size");
151
+ l({ type: String })
152
+ ], a.prototype, "src");
123
153
  i([
124
- n({ type: String }),
125
- d(s, $, r.aspectRatio)
126
- ], e.prototype, "aspectRatio");
154
+ l({ type: String })
155
+ ], a.prototype, "alt");
127
156
  i([
128
- n({ type: String })
129
- ], e.prototype, "src");
157
+ l({ type: Boolean })
158
+ ], a.prototype, "disabled");
130
159
  i([
131
- n({ type: String })
132
- ], e.prototype, "alt");
160
+ l({ type: Boolean })
161
+ ], a.prototype, "hasPadding");
133
162
  i([
134
- n({ type: Boolean })
135
- ], e.prototype, "disabled");
163
+ l({ type: Boolean })
164
+ ], a.prototype, "hideDefaultPlaceholder");
136
165
  i([
137
- n({ type: Boolean })
138
- ], e.prototype, "hasPadding");
166
+ l({ type: String }),
167
+ h(s, D, r.backgroundColor)
168
+ ], a.prototype, "backgroundColor");
139
169
  i([
140
- n({ type: String }),
141
- d(s, I, r.backgroundColor)
142
- ], e.prototype, "backgroundColor");
170
+ l({ type: Object })
171
+ ], a.prototype, "placeholder");
143
172
  i([
144
- n({ type: Object })
145
- ], e.prototype, "placeholder");
173
+ z("img")
174
+ ], a.prototype, "img");
146
175
  i([
147
- C("img")
148
- ], e.prototype, "img");
149
- z(s, e);
176
+ C()
177
+ ], a.prototype, "_hasError");
178
+ P(s, a);
150
179
  export {
151
- e as PieThumbnail,
180
+ a as PieThumbnail,
152
181
  $ as aspectRatios,
153
- R as backgroundColorClassNames,
154
- I as backgroundColors,
182
+ x as backgroundColorClassNames,
183
+ D as backgroundColors,
155
184
  r as defaultProps,
156
- P as sizes,
157
- E as variants
185
+ R as sizes,
186
+ I as variants
158
187
  };
package/dist/react.d.ts CHANGED
@@ -27,21 +27,34 @@ declare class PieThumbnail_2 extends LitElement implements ThumbnailProps {
27
27
  alt: string;
28
28
  disabled: boolean;
29
29
  hasPadding: boolean;
30
+ hideDefaultPlaceholder: boolean;
30
31
  backgroundColor: "default" | "subtle" | "strong" | "dark" | "inverse" | "inverse-alternative";
31
32
  placeholder: ThumbnailProps['placeholder'];
32
33
  private img;
34
+ private _hasError;
33
35
  /**
34
- * Assigns the thumbnail size and border radius CSS variables
35
- * based on the size prop.
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.
36
50
  */
37
51
  private _generateSizeStyles;
38
52
  /**
39
- * Handles image load errors by replacing the src and alt props
40
- * with the placeholder props.
53
+ * Handles the image error event.
41
54
  */
42
55
  private _handleImageError;
43
56
  /**
44
- * Detects image load status and applies the placeholder on failure.
57
+ * Checks the image load status and triggers error handling if needed.
45
58
  * This is needed as the `onerror` event is not triggered in SSR.
46
59
  */
47
60
  private _checkImageError;
@@ -89,7 +102,11 @@ export declare interface ThumbnailProps {
89
102
  */
90
103
  backgroundColor?: typeof backgroundColors[number];
91
104
  /**
92
- * 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.
93
110
  */
94
111
  placeholder?: PlaceholderProps;
95
112
  /**
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.5.0",
4
+ "version": "0.6.1",
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 xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 33 32"><path d="M32.399 15.493c-.102-.255-1.495-2.753-4.081-5.888a.507.507 0 0 1-.119-.267 48.162 48.162 0 0 0-.778-5.436.88.88 0 0 0-.282-.467.928.928 0 0 0-.509-.221l-2.679-.323a.84.84 0 0 0-.071 0c-.15 0-.294.058-.401.161s-.166.243-.166.389v.906c0 .009-.004.017-.011.023s-.015.01-.025.01c-.015-.003-.018-.006-.021-.008A37.89 37.89 0 0 0 17.829.395 2.43 2.43 0 0 0 16.493 0c-.473 0-.936.138-1.328.395C5.887 5.956.782 15.005.585 15.493a.87.87 0 0 0 .004.757.9.9 0 0 0 .246.306.93.93 0 0 0 .356.176l2.672.5c.177.047.335.144.454.279s.193.302.213.479c.023.457.527 10.691 1.162 13.345a.89.89 0 0 0 .328.479c.161.12.358.186.561.185h.02l4.528-.084h.129c.079-.001.155-.032.211-.086s.087-.128.087-.205l-.306-5.093c0-.054-.007-.121-.01-.176a.54.54 0 0 0-.08-.254.56.56 0 0 0-.193-.188c-.407-.236-.747-.566-.992-.96s-.385-.841-.41-1.3c-.142-3.589-.211-7.735-.008-10.981.003-.138.062-.27.165-.366a.545.545 0 0 1 .76.016c.099.1.152.234.149.372v.041c-.132 2.163-.142 4.72-.095 7.244a.61.61 0 0 0 .195.435.65.65 0 0 0 .454.174.65.65 0 0 0 .448-.189.61.61 0 0 0 .179-.441c-.048-2.54-.035-5.114.099-7.285.003-.138.062-.27.165-.366a.545.545 0 0 1 .76.016c.099.1.152.234.149.372v.041c-.132 2.152-.142 4.694-.096 7.204a.61.61 0 0 0 .194.435.65.65 0 0 0 .454.175.65.65 0 0 0 .448-.188.61.61 0 0 0 .18-.441c-.047-2.529-.034-5.093.101-7.249.003-.138.063-.27.165-.366s.241-.148.383-.145.278.061.377.161.152.234.149.372v.041c-.193 3.154-.13 7.147 0 10.651a.26.26 0 0 1 0 .033v.086c0 .455-.113.902-.331 1.305s-.533.747-.919 1.004a.54.54 0 0 0-.166.177.52.52 0 0 0-.07.229s-.067.538.179 3.144l.241 2.284c.009.067.043.128.095.172s.119.068.188.068h4.053l1.516.012c.07 0 .137-.024.189-.069s.086-.107.094-.174c.35-3.123.434-4.879.434-4.879-.002-.075-.032-.146-.084-.201s-.123-.09-.199-.099l-1.843-.246c-.122-.015-.238-.064-.331-.142s-.161-.181-.195-.297a.83.83 0 0 1-.024-.275c.747-10.411 4.282-13.92 4.282-13.92a1.05 1.05 0 0 1 .172-.144.575.575 0 0 1 .491-.082.57.57 0 0 1 .223.122.55.55 0 0 1 .145.205.97.97 0 0 1 .045.251c.306 3.272.157 8.842-.033 13.186l-.353 6.583c0 .057.023.112.065.152s.098.063.156.063l3.037.062h.021a.94.94 0 0 0 .561-.186.892.892 0 0 0 .328-.479c.635-2.654 1.134-12.887 1.161-13.344a.877.877 0 0 1 .214-.479.92.92 0 0 1 .453-.28l2.672-.5a.92.92 0 0 0 .499-.317.873.873 0 0 0 .194-.548.885.885 0 0 0-.101-.368z" fill="#efedea"/></svg>
package/src/defs.ts CHANGED
@@ -63,7 +63,11 @@ export interface ThumbnailProps {
63
63
  */
64
64
  backgroundColor?: typeof backgroundColors[number];
65
65
  /**
66
- * 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.
67
71
  */
68
72
  placeholder?: PlaceholderProps;
69
73
  /**
@@ -81,6 +85,7 @@ export const defaultProps: DefaultProps = {
81
85
  alt: '',
82
86
  disabled: false,
83
87
  hasPadding: false,
88
+ hideDefaultPlaceholder: false,
84
89
  backgroundColor: 'default',
85
90
  aspectRatio: '1by1',
86
91
  placeholder: {
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,
@@ -16,6 +16,7 @@ import {
16
16
  aspectRatios,
17
17
  } from './defs';
18
18
  import styles from './thumbnail.scss?inline';
19
+ import defaultPlaceholder from './default-placeholder.svg?inline';
19
20
 
20
21
  // Valid values available to consumers
21
22
  export * from './defs';
@@ -50,6 +51,9 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
50
51
  @property({ type: Boolean })
51
52
  public hasPadding = defaultProps.hasPadding;
52
53
 
54
+ @property({ type: Boolean })
55
+ public hideDefaultPlaceholder = defaultProps.hideDefaultPlaceholder;
56
+
53
57
  @property({ type: String })
54
58
  @validPropertyValues(componentSelector, backgroundColors, defaultProps.backgroundColor)
55
59
  public backgroundColor = defaultProps.backgroundColor;
@@ -60,36 +64,69 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
60
64
  @query('img')
61
65
  private img!: HTMLImageElement;
62
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
+
63
99
  /**
64
- * Assigns the thumbnail size and border radius CSS variables
65
- * based on the size prop.
100
+ * Assigns CSS variables based on the size prop.
66
101
  */
67
102
  private _generateSizeStyles (): string {
68
103
  const { size } = this;
69
104
  let borderRadius = '--dt-radius-rounded-c';
105
+ let defaultPlaceholderPadding = '--dt-spacing-d';
70
106
  if (size <= 40) {
71
107
  borderRadius = '--dt-radius-rounded-a';
108
+ defaultPlaceholderPadding = '--dt-spacing-a';
72
109
  } else if (size <= 56) {
73
110
  borderRadius = '--dt-radius-rounded-b';
111
+ defaultPlaceholderPadding = '--dt-spacing-b';
74
112
  }
75
113
 
76
114
  return `
77
115
  --thumbnail-size: ${size}px;
78
116
  --thumbnail-border-radius: var(${borderRadius});
117
+ --thumbnail-default-placeholder-padding: var(${defaultPlaceholderPadding});
79
118
  `;
80
119
  }
81
120
 
82
121
  /**
83
- * Handles image load errors by replacing the src and alt props
84
- * with the placeholder props.
122
+ * Handles the image error event.
85
123
  */
86
- private _handleImageError () {
87
- if (this.placeholder?.src) this.setAttribute('src', this.placeholder.src);
88
- if (this.placeholder?.alt) this.setAttribute('alt', this.placeholder.alt);
124
+ private _handleImageError (): void {
125
+ this._hasError = true;
89
126
  }
90
127
 
91
128
  /**
92
- * Detects image load status and applies the placeholder on failure.
129
+ * Checks the image load status and triggers error handling if needed.
93
130
  * This is needed as the `onerror` event is not triggered in SSR.
94
131
  */
95
132
  private _checkImageError () {
@@ -107,10 +144,11 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
107
144
  render () {
108
145
  const {
109
146
  variant,
110
- src,
111
- alt,
147
+ _controlledSrc,
148
+ _controlledAlt,
112
149
  disabled,
113
150
  hasPadding,
151
+ _isDefaultPlaceholderVisible,
114
152
  backgroundColor,
115
153
  aspectRatio,
116
154
  } = this;
@@ -122,6 +160,7 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
122
160
  [backgroundColorClassNames[backgroundColor]]: true,
123
161
  'c-thumbnail--disabled': disabled,
124
162
  'c-thumbnail--padding': hasPadding,
163
+ 'c-thumbnail--defaultPlaceholder': _isDefaultPlaceholderVisible,
125
164
  };
126
165
 
127
166
  const sizeStyles = this._generateSizeStyles();
@@ -132,9 +171,9 @@ export class PieThumbnail extends LitElement implements ThumbnailProps {
132
171
  style="${sizeStyles}">
133
172
  <img
134
173
  data-test-id="pie-thumbnail-img"
135
- src="${src}"
174
+ src="${_controlledSrc}"
175
+ alt="${_controlledAlt}"
136
176
  class="c-thumbnail-img"
137
- alt="${alt}"
138
177
  @error="${this._handleImageError}"
139
178
  />
140
179
  </div>
@@ -3,7 +3,9 @@
3
3
  --thumbnail-border-radius: var(--dt-radius-rounded-b);
4
4
  --thumbnail-bg-color: var(--dt-color-container-default);
5
5
  --thumbnail-padding: calc(var(--dt-spacing-a) / 2);
6
+ --thumbnail-default-placeholder-padding: var(--dt-spacing-b);
6
7
  --thumbnail-img-border-radius: unset;
8
+ --thumbnail-img-object-fit: cover;
7
9
 
8
10
  display: flex;
9
11
  justify-content: center;
@@ -60,6 +62,12 @@
60
62
  width: 100%;
61
63
  height: 100%;
62
64
  border-radius: var(--thumbnail-img-border-radius);
63
- object-fit: cover;
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);
64
72
  }
65
73
  }