@tartaguti/ui-components 0.1.0 → 0.3.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.
Files changed (213) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +12 -0
  3. package/custom-elements.json +2887 -823
  4. package/dist/accordion/tt-accordion-item.d.ts.map +1 -1
  5. package/dist/accordion/tt-accordion-item.js +5 -2
  6. package/dist/accordion/tt-accordion-item.js.map +1 -1
  7. package/dist/bento/tt-bento-item.d.ts.map +1 -1
  8. package/dist/bento/tt-bento-item.js +7 -13
  9. package/dist/bento/tt-bento-item.js.map +1 -1
  10. package/dist/docs.d.ts +7 -0
  11. package/dist/docs.d.ts.map +1 -0
  12. package/dist/docs.js +15 -0
  13. package/dist/docs.js.map +1 -0
  14. package/dist/index.d.ts +4 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +8 -7
  17. package/dist/index.js.map +1 -1
  18. package/dist/sidebar/tt-menu-group.d.ts.map +1 -1
  19. package/dist/sidebar/tt-menu-group.js +16 -5
  20. package/dist/sidebar/tt-menu-group.js.map +1 -1
  21. package/dist/sidebar/tt-menu-item.d.ts.map +1 -1
  22. package/dist/sidebar/tt-menu-item.js +24 -11
  23. package/dist/sidebar/tt-menu-item.js.map +1 -1
  24. package/dist/sidebar/tt-sidebar-footer.d.ts.map +1 -1
  25. package/dist/sidebar/tt-sidebar-footer.js +7 -3
  26. package/dist/sidebar/tt-sidebar-footer.js.map +1 -1
  27. package/dist/sidebar/tt-sidebar.d.ts +8 -3
  28. package/dist/sidebar/tt-sidebar.d.ts.map +1 -1
  29. package/dist/sidebar/tt-sidebar.js +196 -44
  30. package/dist/sidebar/tt-sidebar.js.map +1 -1
  31. package/dist/strings.d.ts +64 -0
  32. package/dist/strings.d.ts.map +1 -0
  33. package/dist/strings.js +68 -0
  34. package/dist/strings.js.map +1 -0
  35. package/dist/tokens.css +30 -6
  36. package/dist/tt-alert.d.ts.map +1 -1
  37. package/dist/tt-alert.js +5 -2
  38. package/dist/tt-alert.js.map +1 -1
  39. package/dist/tt-auth.d.ts +5 -1
  40. package/dist/tt-auth.d.ts.map +1 -1
  41. package/dist/tt-auth.js +72 -10
  42. package/dist/tt-auth.js.map +1 -1
  43. package/dist/tt-breadcrumbs.d.ts.map +1 -1
  44. package/dist/tt-breadcrumbs.js +6 -3
  45. package/dist/tt-breadcrumbs.js.map +1 -1
  46. package/dist/tt-button-group.d.ts.map +1 -1
  47. package/dist/tt-button-group.js +7 -1
  48. package/dist/tt-button-group.js.map +1 -1
  49. package/dist/tt-button.d.ts.map +1 -1
  50. package/dist/tt-button.js +37 -28
  51. package/dist/tt-button.js.map +1 -1
  52. package/dist/tt-card.d.ts.map +1 -1
  53. package/dist/tt-card.js +9 -6
  54. package/dist/tt-card.js.map +1 -1
  55. package/dist/tt-chart.d.ts +10 -2
  56. package/dist/tt-chart.d.ts.map +1 -1
  57. package/dist/tt-chart.js +167 -29
  58. package/dist/tt-chart.js.map +1 -1
  59. package/dist/tt-checkbox.d.ts.map +1 -1
  60. package/dist/tt-checkbox.js +5 -2
  61. package/dist/tt-checkbox.js.map +1 -1
  62. package/dist/tt-code-block.d.ts +4 -5
  63. package/dist/tt-code-block.d.ts.map +1 -1
  64. package/dist/tt-code-block.js +53 -17
  65. package/dist/tt-code-block.js.map +1 -1
  66. package/dist/tt-combobox.d.ts +14 -0
  67. package/dist/tt-combobox.d.ts.map +1 -1
  68. package/dist/tt-combobox.js +234 -31
  69. package/dist/tt-combobox.js.map +1 -1
  70. package/dist/tt-company-card.d.ts +18 -0
  71. package/dist/tt-company-card.d.ts.map +1 -1
  72. package/dist/tt-company-card.js +73 -4
  73. package/dist/tt-company-card.js.map +1 -1
  74. package/dist/tt-comparison-table.d.ts.map +1 -1
  75. package/dist/tt-comparison-table.js +6 -3
  76. package/dist/tt-comparison-table.js.map +1 -1
  77. package/dist/tt-component-doc.d.ts.map +1 -1
  78. package/dist/tt-component-doc.js +6 -3
  79. package/dist/tt-component-doc.js.map +1 -1
  80. package/dist/tt-contact-list.d.ts +17 -2
  81. package/dist/tt-contact-list.d.ts.map +1 -1
  82. package/dist/tt-contact-list.js +68 -10
  83. package/dist/tt-contact-list.js.map +1 -1
  84. package/dist/tt-copy-button.d.ts.map +1 -1
  85. package/dist/tt-copy-button.js +6 -3
  86. package/dist/tt-copy-button.js.map +1 -1
  87. package/dist/tt-data-table.d.ts +32 -1
  88. package/dist/tt-data-table.d.ts.map +1 -1
  89. package/dist/tt-data-table.js +302 -16
  90. package/dist/tt-data-table.js.map +1 -1
  91. package/dist/tt-dock-item.d.ts +53 -0
  92. package/dist/tt-dock-item.d.ts.map +1 -0
  93. package/dist/tt-dock-item.js +270 -0
  94. package/dist/tt-dock-item.js.map +1 -0
  95. package/dist/tt-dock.d.ts +59 -0
  96. package/dist/tt-dock.d.ts.map +1 -0
  97. package/dist/tt-dock.js +337 -0
  98. package/dist/tt-dock.js.map +1 -0
  99. package/dist/tt-drawer.d.ts +4 -0
  100. package/dist/tt-drawer.d.ts.map +1 -1
  101. package/dist/tt-drawer.js +14 -4
  102. package/dist/tt-drawer.js.map +1 -1
  103. package/dist/tt-dropdown-menu.d.ts +5 -0
  104. package/dist/tt-dropdown-menu.d.ts.map +1 -1
  105. package/dist/tt-dropdown-menu.js +34 -8
  106. package/dist/tt-dropdown-menu.js.map +1 -1
  107. package/dist/tt-file-upload.d.ts +35 -0
  108. package/dist/tt-file-upload.d.ts.map +1 -1
  109. package/dist/tt-file-upload.js +210 -21
  110. package/dist/tt-file-upload.js.map +1 -1
  111. package/dist/tt-filter.d.ts.map +1 -1
  112. package/dist/tt-filter.js +10 -5
  113. package/dist/tt-filter.js.map +1 -1
  114. package/dist/tt-form.d.ts +25 -1
  115. package/dist/tt-form.d.ts.map +1 -1
  116. package/dist/tt-form.js +151 -12
  117. package/dist/tt-form.js.map +1 -1
  118. package/dist/tt-grid.d.ts +10 -1
  119. package/dist/tt-grid.d.ts.map +1 -1
  120. package/dist/tt-grid.js +38 -9
  121. package/dist/tt-grid.js.map +1 -1
  122. package/dist/tt-icon.d.ts.map +1 -1
  123. package/dist/tt-icon.js +5 -0
  124. package/dist/tt-icon.js.map +1 -1
  125. package/dist/tt-inbox.d.ts +4 -0
  126. package/dist/tt-inbox.d.ts.map +1 -1
  127. package/dist/tt-inbox.js +13 -3
  128. package/dist/tt-inbox.js.map +1 -1
  129. package/dist/tt-input.d.ts.map +1 -1
  130. package/dist/tt-input.js +15 -5
  131. package/dist/tt-input.js.map +1 -1
  132. package/dist/tt-key-values.d.ts +2 -0
  133. package/dist/tt-key-values.d.ts.map +1 -1
  134. package/dist/tt-key-values.js +45 -2
  135. package/dist/tt-key-values.js.map +1 -1
  136. package/dist/tt-layout.d.ts +53 -2
  137. package/dist/tt-layout.d.ts.map +1 -1
  138. package/dist/tt-layout.js +233 -27
  139. package/dist/tt-layout.js.map +1 -1
  140. package/dist/tt-modal.d.ts +4 -0
  141. package/dist/tt-modal.d.ts.map +1 -1
  142. package/dist/tt-modal.js +14 -4
  143. package/dist/tt-modal.js.map +1 -1
  144. package/dist/tt-navbar.d.ts +4 -0
  145. package/dist/tt-navbar.d.ts.map +1 -1
  146. package/dist/tt-navbar.js +42 -7
  147. package/dist/tt-navbar.js.map +1 -1
  148. package/dist/tt-pagination.d.ts +4 -0
  149. package/dist/tt-pagination.d.ts.map +1 -1
  150. package/dist/tt-pagination.js +8 -1
  151. package/dist/tt-pagination.js.map +1 -1
  152. package/dist/tt-product.d.ts +67 -15
  153. package/dist/tt-product.d.ts.map +1 -1
  154. package/dist/tt-product.js +447 -366
  155. package/dist/tt-product.js.map +1 -1
  156. package/dist/tt-progress.d.ts +1 -0
  157. package/dist/tt-progress.d.ts.map +1 -1
  158. package/dist/tt-progress.js +26 -7
  159. package/dist/tt-progress.js.map +1 -1
  160. package/dist/tt-quantity.d.ts.map +1 -1
  161. package/dist/tt-quantity.js +5 -2
  162. package/dist/tt-quantity.js.map +1 -1
  163. package/dist/tt-radio-group.d.ts.map +1 -1
  164. package/dist/tt-radio-group.js +5 -2
  165. package/dist/tt-radio-group.js.map +1 -1
  166. package/dist/tt-scroller.d.ts +76 -0
  167. package/dist/tt-scroller.d.ts.map +1 -0
  168. package/dist/tt-scroller.js +525 -0
  169. package/dist/tt-scroller.js.map +1 -0
  170. package/dist/tt-search.d.ts.map +1 -1
  171. package/dist/tt-search.js +10 -5
  172. package/dist/tt-search.js.map +1 -1
  173. package/dist/tt-select.d.ts +9 -0
  174. package/dist/tt-select.d.ts.map +1 -1
  175. package/dist/tt-select.js +99 -14
  176. package/dist/tt-select.js.map +1 -1
  177. package/dist/tt-slider.d.ts.map +1 -1
  178. package/dist/tt-slider.js +13 -2
  179. package/dist/tt-slider.js.map +1 -1
  180. package/dist/tt-social-links.d.ts.map +1 -1
  181. package/dist/tt-social-links.js +7 -4
  182. package/dist/tt-social-links.js.map +1 -1
  183. package/dist/tt-switcher.d.ts +2 -0
  184. package/dist/tt-switcher.d.ts.map +1 -1
  185. package/dist/tt-switcher.js +24 -76
  186. package/dist/tt-switcher.js.map +1 -1
  187. package/dist/tt-table-row.d.ts.map +1 -1
  188. package/dist/tt-table-row.js +5 -2
  189. package/dist/tt-table-row.js.map +1 -1
  190. package/dist/tt-table.d.ts.map +1 -1
  191. package/dist/tt-table.js +5 -2
  192. package/dist/tt-table.js.map +1 -1
  193. package/dist/tt-tabs.d.ts.map +1 -1
  194. package/dist/tt-tabs.js +5 -2
  195. package/dist/tt-tabs.js.map +1 -1
  196. package/dist/tt-tag-list.d.ts.map +1 -1
  197. package/dist/tt-tag-list.js +6 -3
  198. package/dist/tt-tag-list.js.map +1 -1
  199. package/dist/tt-textarea.d.ts.map +1 -1
  200. package/dist/tt-textarea.js +5 -2
  201. package/dist/tt-textarea.js.map +1 -1
  202. package/dist/tt-toast.d.ts +4 -1
  203. package/dist/tt-toast.d.ts.map +1 -1
  204. package/dist/tt-toast.js +40 -6
  205. package/dist/tt-toast.js.map +1 -1
  206. package/dist/tt-toggle-group.d.ts.map +1 -1
  207. package/dist/tt-toggle-group.js +7 -1
  208. package/dist/tt-toggle-group.js.map +1 -1
  209. package/dist/tt-toggle.d.ts.map +1 -1
  210. package/dist/tt-toggle.js +14 -9
  211. package/dist/tt-toggle.js.map +1 -1
  212. package/package.json +1 -1
  213. package/vscode.html-custom-data.json +241 -66
@@ -5,44 +5,112 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { LitElement, html, css, nothing } from "lit";
8
- import { customElement, property } from "lit/decorators.js";
9
- import { ifDefined } from "lit/directives/if-defined.js";
8
+ import { customElement, property, state } from "lit/decorators.js";
10
9
  import { styleMap } from "lit/directives/style-map.js";
11
10
  import { srOnly } from "./sr-only.js";
12
- import { openSafely } from "./safe-url.js";
13
- import "./tt-card.js";
11
+ import { safeUrl } from "./safe-url.js";
14
12
  import "./tt-image.js";
15
13
  import "./tt-button.js";
16
14
  import "./tt-icon.js";
15
+ /** "1", "4 / 3", "16/9": one number, or two with a slash between. */
16
+ const RATIO = /^\s*\d*\.?\d+\s*(\/\s*\d*\.?\d+\s*)?$/;
17
+ /**
18
+ * A product in a shop: photo, name, price and a button.
19
+ *
20
+ * TWO CHOICES, FOUR RESULTS. `orientation` (a tile for a grid, or a row
21
+ * for a list) and `size` (medium, or small for two across on a phone).
22
+ * Every pairing of the two is designed, so there is no combination that
23
+ * breaks. It had four variants and three layouts, twenty-four pairings,
24
+ * and several of them did: text on a pale photo, a white band under the
25
+ * photo of a tall row, a layout that did nothing in a row.
26
+ *
27
+ * PROPS GIVE THE DEFAULTS, SLOTS ADD TO THEM. `src` draws the photo,
28
+ * `rating` the star, `button-label` the button. The `media`, `meta` and
29
+ * `actions` slots add your own beside them (or over the photo), and
30
+ * clearing the prop drops its default. One rule, so nothing disappears
31
+ * when something is added.
32
+ *
33
+ * ONE TARGET. With `href` the name is a link, or with `clickable` a
34
+ * button, and either one is stretched over the whole card by a
35
+ * pseudo-element, so the card is clickable while the accessibility tree
36
+ * holds ONE link named by the product, not a card-sized link wrapping a
37
+ * button (interactive content inside a link is invalid and announced
38
+ * twice). The action row sits above the stretched link, so its buttons
39
+ * stay separate targets.
40
+ *
41
+ * The card draws its own surface with tt-card's tokens rather than
42
+ * composing tt-card, because the stretched link needs the surface as its
43
+ * positioned box and the focus ring has to go round the whole card.
44
+ *
45
+ * @fires tt-product-action - { value, heading } when the built-in button is pressed.
46
+ * @fires tt-product-open - { value, heading } when a `clickable` card is opened.
47
+ *
48
+ * @slot media - Your own media instead of the photo (a carousel, a video), filling the ratio box. Leave `src` empty.
49
+ * @slot badge - Over the top-left of the photo: Sale, New, Sold out.
50
+ * @slot meta - Beside the rating: tags, stock, delivery.
51
+ * @slot actions - After the built-in button: a wishlist icon, a quantity, your own buttons.
52
+ */
17
53
  let TTProduct = class TTProduct extends LitElement {
18
54
  constructor() {
19
55
  super(...arguments);
20
- this.src = "";
21
- this.ratio = "1";
22
- this.orientation = "vertical";
23
- this.variant = "default";
24
- this.layout = "standard";
25
56
  this.heading = "";
57
+ /** Its place in the page outline, as on tt-card. */
58
+ this.headingLevel = 3;
26
59
  this.description = "";
27
60
  this.descriptionLines = 2;
28
61
  this.price = "";
29
62
  this.wasPrice = "";
30
63
  this.rating = "";
64
+ this.src = "";
65
+ this.ratio = "1";
66
+ /** Identifies the product in its events. */
31
67
  this.value = "";
32
- this.buttonLink = "";
68
+ /** The product's page. The name becomes a link over the whole card. */
69
+ this.href = "";
70
+ /** Without href: the whole card is a button that emits tt-product-open. */
71
+ this.clickable = false;
72
+ /** The built-in button's text. Empty for no built-in button. */
33
73
  this.buttonLabel = "Add to basket";
74
+ this.orientation = "vertical";
75
+ this.size = "medium";
76
+ /** Cannot be bought (sold out): the button is off and the photo greys. Still a link. */
34
77
  this.disabled = false;
35
- }
36
- handleClick() {
78
+ // From slotchange: CSS cannot ask whether a slot has anything in it.
79
+ this.hasMedia = false;
80
+ this.hasMeta = false;
81
+ this.hasActions = false;
82
+ this.onMedia = (event) => (this.hasMedia = this.filled(event));
83
+ this.onMeta = (event) => (this.hasMeta = this.filled(event));
84
+ this.onActions = (event) => (this.hasActions = this.filled(event));
85
+ }
86
+ filled(event) {
87
+ const slot = event.target;
88
+ return slot
89
+ .assignedNodes({ flatten: true })
90
+ .some((node) => node.nodeType === Node.ELEMENT_NODE || (node.textContent ?? "").trim() !== "");
91
+ }
92
+ get detail() {
93
+ return { value: this.value, heading: this.heading };
94
+ }
95
+ /* The built-in button's own event stays inside: the card reports
96
+ what happened, once, in its own words. A button you slot into
97
+ actions is yours, so its events reach you as usual. */
98
+ handleAction(event) {
99
+ event.stopPropagation();
37
100
  if (this.disabled)
38
101
  return;
39
- this.dispatchEvent(new CustomEvent("tt-product-action", {
40
- detail: { value: this.value, heading: this.heading },
41
- bubbles: true,
42
- composed: true,
43
- }));
44
- if (this.buttonLink)
45
- openSafely(this.buttonLink);
102
+ this.dispatchEvent(new CustomEvent("tt-product-action", { detail: this.detail, bubbles: true, composed: true }));
103
+ }
104
+ handleOpen() {
105
+ this.dispatchEvent(new CustomEvent("tt-product-open", { detail: this.detail, bubbles: true, composed: true }));
106
+ }
107
+ /** The link's address, when it is one a browser may follow. */
108
+ get url() {
109
+ return this.href && safeUrl(this.href) ? this.href.trim() : "";
110
+ }
111
+ /** A link or a button covers the card: it has a name to carry. */
112
+ get interactive() {
113
+ return this.heading !== "" && (this.url !== "" || this.clickable);
46
114
  }
47
115
  static { this.styles = [srOnly, css `
48
116
  /* The browser's own [hidden] rule loses to any :host display, so a
@@ -53,509 +121,498 @@ let TTProduct = class TTProduct extends LitElement {
53
121
 
54
122
  :host {
55
123
  display: block;
56
- width: 100%;
124
+ /* Fills its grid cell, so a row of cards is one height and the
125
+ prices and buttons line up at the bottom. */
57
126
  height: 100%;
58
- font-family: var(--tt-font-sans);
127
+ min-width: 0;
59
128
  text-align: start;
129
+ font-family: var(--tt-font-sans);
130
+ -webkit-font-smoothing: antialiased;
60
131
 
61
- --_name: var(--tt-text-sm);
132
+ --_radius: var(--tt-radius-lg);
133
+ --_pad: var(--tt-space-4);
134
+ --_title: var(--tt-text-sm);
62
135
  --_price: var(--tt-text-md);
63
- --_card-radius: var(--tt-radius-lg);
64
- --_surface: var(--tt-card);
65
- --_border: var(--tt-border);
66
- --_shadow: none;
67
- --_content-gap: var(--tt-space-2);
136
+ --_was: var(--tt-text-sm);
137
+ }
138
+
139
+ /* A step down for two across on a phone, where 52px of the row is the
140
+ button and its gap: "EUR 89.00" still fits beside it at 160px. */
141
+ :host([size="small"]) {
142
+ --_pad: var(--tt-space-3);
143
+ --_price: var(--tt-text-sm);
144
+ --_was: var(--tt-text-xs);
68
145
  }
69
146
 
70
147
  :host([orientation="horizontal"]) {
71
- --_name: var(--tt-text-md);
148
+ --_title: var(--tt-text-md);
72
149
  --_price: var(--tt-text-lg);
73
150
  }
74
151
 
75
- :host([variant="minimal"]) {
76
- --_surface: transparent;
77
- --_border: transparent;
78
- --_shadow: none;
152
+ :host([orientation="horizontal"][size="small"]) {
153
+ --_title: var(--tt-text-sm);
154
+ --_price: var(--tt-text-md);
155
+ --_was: var(--tt-text-sm);
79
156
  }
80
157
 
81
- :host([variant="elevated"]) {
82
- --_shadow: var(--tt-elevation-1);
158
+ [hidden] {
159
+ display: none !important;
83
160
  }
84
161
 
85
- :host([variant="featured"]) {
86
- --_card-radius: var(--tt-radius-xl);
87
- --_shadow: var(--tt-elevation-1);
88
- }
162
+ /* ---------- the surface: tt-card's tokens ---------- */
89
163
 
90
- tt-card {
164
+ .card {
165
+ /* The stretched link's box. Nothing between it and the link may be
166
+ positioned, or the link would cover that instead. */
167
+ position: relative;
168
+ box-sizing: border-box;
91
169
  display: flex;
92
170
  flex-direction: column;
93
171
  height: 100%;
94
- /* tt-card's own radius knob, so its padding presets and
95
- --tt-radius-nested follow the product's radius. */
96
- --tt-card-radius: var(--_card-radius);
97
- transition: transform var(--tt-duration-normal) var(--tt-ease-out);
98
- }
172
+ min-width: 0;
99
173
 
100
- /*
101
- The SURFACE is styled through tt-card's "card" part.
174
+ background: var(--tt-card);
175
+ color: var(--tt-card-foreground);
176
+ border: 1px solid var(--tt-border);
177
+ border-radius: var(--_radius);
178
+ box-shadow: var(--tt-elevation-1);
102
179
 
103
- These used to be set on the tt-card host, but tt-card draws its
104
- background, border and shadow on an element inside its own shadow
105
- root, which a rule on the host never reaches. So every variant
106
- looked the same: "minimal" still drew a white card with a border.
107
- ::part() is the one door a component opens for exactly this.
108
- */
109
- tt-card::part(card) {
110
- background: var(--_surface);
111
- border-color: var(--_border);
112
- box-shadow: var(--_shadow);
113
- overflow: hidden; /* clips the image to the card's corners */
180
+ transition:
181
+ border-color var(--tt-duration-normal) var(--tt-ease),
182
+ box-shadow var(--tt-duration-normal) var(--tt-ease);
114
183
  }
115
184
 
116
- :host(:hover:not([disabled])) tt-card {
117
- transform: translateY(-2px);
185
+ :host([orientation="horizontal"]) .card {
186
+ flex-direction: row;
118
187
  }
119
188
 
120
- :host([variant="minimal"]:hover) tt-card {
121
- transform: none;
122
- }
189
+ /* Hover promises a click, so only a card that has one lifts. Only
190
+ where a pointer can hover: a tap would leave it on. */
191
+ @media (hover: hover) {
192
+ .interactive:hover {
193
+ border-color: var(--tt-border-strong);
194
+ box-shadow: var(--tt-elevation-2);
195
+ }
123
196
 
124
- /* One rung up on hover, as tt-card's own elevated variant does. */
125
- :host([variant="elevated"]:hover:not([disabled])) tt-card::part(card),
126
- :host([variant="featured"]:hover:not([disabled])) tt-card::part(card) {
127
- box-shadow: var(--tt-elevation-2);
197
+ .interactive:hover .image {
198
+ transform: scale(1.03);
199
+ }
200
+
201
+ .interactive:hover .link {
202
+ text-decoration: underline;
203
+ text-underline-offset: 0.2em;
204
+ }
128
205
  }
129
206
 
130
- /* ---------- media (Square corners) ---------- */
207
+ /* ---------- media ---------- */
131
208
 
132
209
  .media {
133
210
  position: relative;
211
+ flex: none;
134
212
  overflow: hidden;
135
213
  background: var(--tt-muted);
136
- border-radius: 0; /* Zero radius on images */
214
+
215
+ /* The card's curve less its border, on the corners it shares. */
216
+ --_inner: max(0px, calc(var(--_radius) - 1px));
217
+ border-start-start-radius: var(--_inner);
218
+ border-start-end-radius: var(--_inner);
137
219
  }
138
220
 
139
- tt-image {
221
+ /* The ratio box: an in-flow spacer sets the height from the width.
222
+ The photo is laid over it, so in a row whose text is taller the
223
+ media stretches and the photo still covers it, with no band of
224
+ card showing under it. */
225
+ .media::before {
226
+ content: "";
140
227
  display: block;
228
+ aspect-ratio: var(--_ratio, 1);
229
+ }
230
+
231
+ :host([orientation="horizontal"]) .media {
232
+ flex: 0 0 clamp(6rem, 34%, 13.75rem);
233
+ border-start-end-radius: 0;
234
+ border-end-start-radius: var(--_inner);
235
+ }
236
+
237
+ :host([orientation="horizontal"][size="small"]) .media {
238
+ flex-basis: clamp(4.5rem, 26%, 8rem);
239
+ }
240
+
241
+ .image,
242
+ slot[name="media"]::slotted(*) {
243
+ position: absolute;
244
+ inset: 0;
141
245
  width: 100%;
142
246
  height: 100%;
143
- object-fit: cover;
144
- transition: transform var(--tt-duration-slow) var(--tt-ease-out);
145
247
  }
146
248
 
147
- :host(:hover:not([disabled])) tt-image {
148
- transform: scale(1.035);
249
+ .image {
250
+ transition: transform var(--tt-duration-slow) var(--tt-ease-out);
149
251
  }
150
252
 
151
- @media (pointer: coarse) {
152
- :host(:hover) tt-image {
153
- transform: none;
154
- }
253
+ :host([disabled]) .image,
254
+ :host([disabled]) slot[name="media"]::slotted(*) {
255
+ filter: grayscale(1);
256
+ opacity: 0.6;
155
257
  }
156
258
 
157
- .badge {
259
+ /* Badges sit on a photo, where a tinted badge (a 13% wash of its
260
+ colour) would show the photo through it. Each one gets the card
261
+ behind it, so it reads exactly as it does on a card. */
262
+ .badges {
158
263
  position: absolute;
264
+ inset-block-start: var(--tt-space-3);
265
+ inset-inline: var(--tt-space-3);
266
+ display: flex;
267
+ flex-wrap: wrap;
268
+ gap: var(--tt-space-1);
269
+ pointer-events: none;
270
+ }
271
+
272
+ :host([size="small"]) .badges {
159
273
  inset-block-start: var(--tt-space-2);
160
- inset-inline-start: var(--tt-space-2);
161
- z-index: 2;
162
- max-width: calc(100% - var(--tt-space-4));
274
+ inset-inline: var(--tt-space-2);
163
275
  }
164
276
 
165
- .badge ::slotted(*) {
277
+ .badges ::slotted(*) {
278
+ max-width: 100%;
279
+ background: var(--tt-card);
280
+ border-radius: var(--tt-radius-full);
166
281
  box-shadow: var(--tt-elevation-1);
167
282
  }
168
283
 
169
- /* ---------- body & typography ---------- */
284
+ /* ---------- body ---------- */
170
285
 
171
286
  .body {
172
287
  display: flex;
173
- flex-direction: column;
174
288
  flex: 1;
289
+ flex-direction: column;
290
+ gap: var(--tt-space-1);
175
291
  min-width: 0;
292
+ padding: var(--_pad);
176
293
  }
177
294
 
178
- h2 {
295
+ .heading {
179
296
  margin: 0;
180
297
  display: -webkit-box;
181
298
  -webkit-line-clamp: 2;
182
299
  -webkit-box-orient: vertical;
183
300
  overflow: hidden;
184
301
 
185
- font-size: var(--_name);
302
+ color: var(--tt-foreground);
303
+ font-size: var(--_title);
186
304
  font-weight: var(--tt-weight-semibold);
187
305
  line-height: var(--tt-leading-snug);
188
- letter-spacing: -0.012em;
189
- color: var(--tt-foreground);
306
+ letter-spacing: -0.01em;
190
307
  overflow-wrap: break-word;
191
308
  }
192
309
 
193
- .price {
194
- display: flex;
195
- align-items: baseline;
196
- flex-wrap: wrap;
197
- gap: var(--tt-space-2);
198
- margin-top: var(--_content-gap);
310
+ .link {
311
+ margin: 0;
312
+ padding: 0;
313
+ border: 0;
314
+ background: none;
315
+ color: inherit;
316
+ font: inherit;
317
+ letter-spacing: inherit;
318
+ text-align: inherit;
319
+ text-decoration: none;
320
+ cursor: pointer;
321
+ }
322
+
323
+ /* The whole card is the link's hit area. The ring goes round the
324
+ card, on the same corners. */
325
+ .link::after {
326
+ content: "";
327
+ position: absolute;
328
+ inset: -1px;
329
+ z-index: 1;
330
+ border-radius: var(--_radius);
331
+ }
199
332
 
200
- font-size: var(--_price);
201
- font-weight: var(--tt-weight-bold);
202
- letter-spacing: -0.025em;
203
- color: var(--tt-foreground);
204
- font-variant-numeric: tabular-nums;
333
+ .link:focus-visible {
334
+ outline: none;
205
335
  }
206
336
 
207
- .was {
208
- font-size: var(--tt-text-xs);
209
- font-weight: var(--tt-weight-regular);
210
- text-decoration: line-through;
211
- color: var(--tt-muted-foreground);
337
+ .link:focus-visible::after {
338
+ outline: var(--tt-ring-width) solid var(--tt-ring);
339
+ outline-offset: var(--tt-ring-offset);
212
340
  }
213
341
 
214
- p {
215
- margin: var(--_content-gap) 0 0;
342
+ .description {
343
+ margin: 0;
216
344
  display: -webkit-box;
217
- -webkit-line-clamp: var(--_desc-lines, 2);
345
+ -webkit-line-clamp: var(--_lines, 2);
218
346
  -webkit-box-orient: vertical;
219
347
  overflow: hidden;
220
348
 
349
+ color: var(--tt-muted-foreground);
221
350
  font-size: var(--tt-text-sm);
222
351
  line-height: var(--tt-leading-snug);
352
+ }
353
+
354
+ .meta {
355
+ display: flex;
356
+ flex-wrap: wrap;
357
+ align-items: center;
358
+ gap: var(--tt-space-1) var(--tt-space-2);
359
+ min-width: 0;
360
+ margin-top: var(--tt-space-1);
361
+
223
362
  color: var(--tt-muted-foreground);
363
+ font-size: var(--tt-text-xs);
364
+ line-height: var(--tt-leading-snug);
224
365
  }
225
366
 
226
367
  .rating {
227
368
  display: inline-flex;
228
369
  align-items: center;
229
370
  gap: var(--tt-space-1);
230
- min-width: 0;
231
-
232
- font-size: var(--tt-text-xs);
233
- color: var(--tt-muted-foreground);
234
371
  font-variant-numeric: tabular-nums;
235
372
  }
236
373
 
237
374
  .rating tt-icon {
375
+ --tt-icon-size: 0.875rem;
238
376
  color: var(--tt-warning);
239
- flex: 0 0 auto;
240
377
  }
241
378
 
242
- /* ---------- vertical / standard ---------- */
243
-
244
- :host([orientation="vertical"]) .media {
245
- margin-bottom: var(--tt-space-3);
246
- border-radius: 0;
247
- }
248
-
249
- :host([orientation="vertical"]) .price,
250
- :host([orientation="vertical"]) p {
251
- margin-top: var(--tt-space-1);
252
- }
379
+ /* ---------- price and actions, pinned to the bottom ---------- */
253
380
 
254
381
  .foot {
255
382
  display: flex;
256
- align-items: center;
257
- gap: var(--tt-space-2);
258
- min-width: 0;
383
+ flex-direction: column;
384
+ align-items: stretch;
385
+ gap: var(--tt-space-3);
259
386
  margin-top: auto;
260
387
  padding-top: var(--tt-space-3);
261
388
  }
262
389
 
263
- .foot .rating {
264
- margin-top: 0;
265
- }
266
-
267
- .foot > tt-button {
268
- margin-inline-start: auto;
269
- }
270
-
271
- /* ---------- vertical / compact ---------- */
272
-
273
- /* tt-card's padding variables, not padding on its host: host padding
274
- lands OUTSIDE the card's border and left a gap around it. */
275
- :host([orientation="vertical"][layout="compact"]) tt-card {
276
- --tt-card-padding-x: var(--tt-space-2);
277
- --tt-card-padding-y: var(--tt-space-2);
278
- }
279
-
280
- :host([orientation="vertical"][layout="compact"]) .media {
281
- margin-bottom: var(--tt-space-2);
282
- border-radius: 0;
390
+ /* A row: the price, then the action at the end. Small tiles and
391
+ rows; it wraps under the price when the row is too narrow. */
392
+ :host([size="small"]) .foot,
393
+ :host([orientation="horizontal"]) .foot {
394
+ flex-direction: row;
395
+ flex-wrap: wrap;
396
+ align-items: center;
397
+ justify-content: space-between;
398
+ gap: var(--tt-space-2) var(--tt-space-3);
283
399
  }
284
400
 
285
- :host([orientation="vertical"][layout="compact"]) .price,
286
- :host([orientation="vertical"][layout="compact"]) p {
287
- margin-top: 2px;
401
+ /* A small one keeps the icon beside the price: the old price drops
402
+ under the new one instead of pushing the button onto a line of its
403
+ own, so every tile in a row ends the same way. Extra actions of
404
+ your own may need that line, so then it wraps. */
405
+ :host([size="small"]) .foot {
406
+ column-gap: var(--tt-space-2);
288
407
  }
289
408
 
290
- /* ---------- vertical / overlay ---------- */
291
-
292
- :host([orientation="vertical"][layout="overlay"]) tt-card {
293
- position: relative;
294
- --tt-card-padding-x: 0px;
295
- --tt-card-padding-y: 0px;
296
- --tt-card-gap: 0px;
409
+ :host([size="small"]) .foot:not(.extra) {
410
+ flex-wrap: nowrap;
297
411
  }
298
412
 
299
- :host([orientation="vertical"][layout="overlay"]) .media {
413
+ .price {
300
414
  margin: 0;
301
- height: 100%;
302
- border-radius: 0;
303
- }
304
-
305
- :host([orientation="vertical"][layout="overlay"]) .body {
306
- position: absolute;
307
- inset-inline: 0;
308
- inset-block-end: 0;
309
- z-index: 2;
310
- padding: var(--tt-space-4);
311
-
312
- /*
313
- Text ON A PHOTO, the one place in the library where colour is
314
- deliberately not themed: the scrim below is always dark, whatever
315
- the theme, so the text on it is always light. These literals are
316
- that decision, written once. (The colour here used to be
317
- --tt-card-foreground, which is DARK in the light theme: any text
318
- not overridden below came out dark on the dark scrim.)
319
- */
320
- --_on-image: oklch(1 0 0);
321
- --_on-image-muted: oklch(1 0 0 / 0.85);
322
- color: var(--_on-image);
323
- background: linear-gradient(
324
- to top,
325
- oklch(0 0 0 / 0.85) 0%,
326
- oklch(0 0 0 / 0.45) 60%,
327
- transparent 100%
328
- );
329
- }
330
-
331
- :host([orientation="vertical"][layout="overlay"]) h2,
332
- :host([orientation="vertical"][layout="overlay"]) .price {
333
- color: var(--_on-image);
415
+ min-width: 0;
416
+ color: var(--tt-foreground);
417
+ font-size: var(--_price);
418
+ font-weight: var(--tt-weight-bold);
419
+ line-height: var(--tt-leading-tight);
420
+ letter-spacing: -0.02em;
421
+ font-variant-numeric: tabular-nums;
334
422
  }
335
423
 
336
- :host([orientation="vertical"][layout="overlay"]) p,
337
- :host([orientation="vertical"][layout="overlay"]) .rating {
338
- color: var(--_on-image-muted);
424
+ /* The old price drops under the new one when there is no room
425
+ beside it: a line can break between two inline blocks, and with no
426
+ space between the spans it could not. Only on a tile too narrow
427
+ for even one price does a price break inside itself, rather than
428
+ run out of the card. The gap is on the end of the new price, so a
429
+ wrapped old price starts flush with it. */
430
+ .now,
431
+ .was {
432
+ display: inline-block;
339
433
  }
340
434
 
341
- /* ---------- horizontal ---------- */
342
-
343
- .row {
344
- display: flex;
345
- align-items: stretch;
346
- min-height: 7rem;
347
- width: 100%;
348
- overflow: hidden;
349
- border-radius: inherit;
435
+ .now {
436
+ margin-inline-end: var(--tt-space-2);
350
437
  }
351
438
 
352
- .row .media {
353
- flex: 0 0 34%;
354
- max-width: 13.75rem;
355
- border-radius: 0;
439
+ /* On sale, the price takes the sale colour; the struck-through old
440
+ price beside it says the same thing without colour. */
441
+ .now.sale {
442
+ color: var(--tt-destructive);
356
443
  }
357
444
 
358
- .column {
359
- display: flex;
360
- flex: 1;
361
- flex-direction: column;
362
- min-width: 0;
363
- padding: var(--tt-space-4);
445
+ .was {
446
+ color: var(--tt-muted-foreground);
447
+ font-size: var(--_was);
448
+ font-weight: var(--tt-weight-regular);
449
+ letter-spacing: 0;
450
+ text-decoration-thickness: 1px;
364
451
  }
365
452
 
453
+ /* Above the stretched link: its buttons are targets of their own. */
366
454
  .actions {
455
+ position: relative;
456
+ z-index: 2;
367
457
  display: flex;
458
+ flex-wrap: wrap;
368
459
  align-items: center;
369
- justify-content: space-between;
370
- margin-top: auto;
371
- padding-top: var(--tt-space-3);
372
- }
373
-
374
- :host([orientation="horizontal"][layout="compact"]) .row {
375
- min-height: 5.5rem;
376
- }
377
-
378
- :host([orientation="horizontal"][layout="compact"]) .column {
379
- padding: var(--tt-space-3);
460
+ gap: var(--tt-space-2);
461
+ min-width: 0;
380
462
  }
381
463
 
382
- :host([orientation="horizontal"][variant="minimal"]) .row {
383
- border: 1px solid var(--tt-border);
384
- background: var(--tt-card);
464
+ /* A tile's built-in button takes the width; anything added keeps
465
+ its own size beside it. */
466
+ .actions > .action {
467
+ flex: 1 1 auto;
385
468
  }
386
469
 
387
- :host([orientation="horizontal"][variant="featured"]) .row {
388
- min-height: 8.5rem;
470
+ /* At the end of the row. The built-in button alone is never
471
+ squeezed: the price gives way first, by wrapping. Your own actions
472
+ get a line of their own when they need it, and wrap inside it
473
+ rather than run out of the card. */
474
+ :host([size="small"]) .actions,
475
+ :host([orientation="horizontal"]) .actions {
476
+ justify-content: flex-end;
477
+ margin-inline-start: auto;
389
478
  }
390
479
 
391
- /* ---------- disabled ---------- */
392
-
393
- :host([disabled]) {
394
- opacity: var(--tt-opacity-disabled);
395
- pointer-events: none;
480
+ :host([size="small"]) .foot:not(.extra) .actions,
481
+ :host([orientation="horizontal"]) .foot:not(.extra) .actions {
482
+ flex-shrink: 0;
396
483
  }
397
484
 
398
- @media (max-width: 32.5rem) {
399
- :host([orientation="horizontal"]) .row {
400
- min-height: 6rem;
401
- }
402
-
403
- :host([orientation="horizontal"]) .row .media {
404
- flex-basis: 31%;
405
- }
406
-
407
- :host([orientation="horizontal"]) .column {
408
- padding: var(--tt-space-3);
409
- }
485
+ :host([size="small"]) .actions > .action,
486
+ :host([orientation="horizontal"]) .actions > .action {
487
+ flex: none;
410
488
  }
411
489
  `]; }
412
- renderMedia(inTile) {
413
- return html `
414
- <div
415
- class="media"
416
- part="media"
417
- slot=${ifDefined(inTile ? "image" : undefined)}
418
- >
419
- <div class="badge"><slot name="badge"></slot></div>
420
-
421
- <tt-image
422
- part="image"
423
- radius="none"
424
- ratio=${this.ratio}
425
- src=${this.src}
426
- alt=${this.heading}
427
- ></tt-image>
428
- </div>
429
- `;
430
- }
431
- renderRating() {
432
- return this.rating
433
- ? html `
434
- <div class="rating" part="rating">
435
- <tt-icon name="star" size="12px"></tt-icon>
436
- <span>${this.rating}</span>
437
- </div>
438
- `
439
- : nothing;
440
- }
441
- renderBody(withRating = false) {
442
- const descStyles = styleMap({
443
- "--_desc-lines": String(this.descriptionLines),
444
- });
445
- return html `
446
- <div class="body" part="body">
447
- <h2 part="heading">${this.heading}</h2>
448
-
449
- ${this.price
450
- ? html `
451
- <div class="price" part="price">
452
- <span>${this.price}</span>${this.wasPrice
453
- ? html `<span class="was"
454
- ><span class="sr-only">Was </span>${this.wasPrice}</span
455
- >`
456
- : nothing}
457
- </div>
458
- `
459
- : nothing}
460
-
461
- ${this.description
462
- ? html `
463
- <p part="description" style=${descStyles}>${this.description}
464
- </p>
465
- `
466
- : nothing}
467
-
468
- ${!withRating
469
- ? html `
470
- <div class="foot" part="foot">
471
- ${this.renderRating()}${this.renderQuickAdd()}
472
- </div>
473
- `
474
- : nothing}
475
- </div>
476
- `;
477
- }
478
490
  /*
479
491
  The button's accessible name carries the product: "Add to basket,
480
492
  Linen lamp". A grid of twelve products otherwise reads as twelve
481
- identical "Add to basket" buttons, which is what the docs said it did
482
- not do. The visible text comes first, so the name still starts with
483
- what a sighted user sees (WCAG 2.5.3, label in name).
493
+ identical "Add to basket" buttons. The visible text comes first, so
494
+ the name still starts with what a sighted user sees (WCAG 2.5.3).
484
495
  */
485
496
  get actionName() {
486
497
  return this.heading ? `${this.buttonLabel}, ${this.heading}` : this.buttonLabel;
487
498
  }
488
- renderQuickAdd() {
499
+ renderAction() {
500
+ if (!this.buttonLabel)
501
+ return nothing;
502
+ // Small: a round bag icon, so the price keeps its line on a 150px tile.
503
+ if (this.size === "small") {
504
+ return html `
505
+ <tt-button
506
+ class="action"
507
+ part="button"
508
+ variant="secondary"
509
+ pill
510
+ label=${this.actionName}
511
+ ?disabled=${this.disabled}
512
+ @tt-button-click=${this.handleAction}
513
+ >
514
+ <tt-icon name="shopping-bag"></tt-icon>
515
+ </tt-button>
516
+ `;
517
+ }
489
518
  return html `
490
519
  <tt-button
491
- class="quick-add"
520
+ class="action"
492
521
  part="button"
493
- pill
494
- size="small"
522
+ variant="secondary"
495
523
  label=${this.actionName}
496
524
  ?disabled=${this.disabled}
497
- @tt-button-click=${this.handleClick}
525
+ @tt-button-click=${this.handleAction}
498
526
  >
499
- <tt-icon name="shopping-bag"></tt-icon>
527
+ ${this.buttonLabel}
500
528
  </tt-button>
501
529
  `;
502
530
  }
503
- renderButton() {
531
+ renderHeading() {
532
+ if (!this.heading)
533
+ return nothing;
534
+ const name = this.url
535
+ ? html `<a class="link" part="link" href=${this.url}>${this.heading}</a>`
536
+ : this.clickable
537
+ ? html `<button class="link" part="link" type="button" @click=${this.handleOpen}>
538
+ ${this.heading}
539
+ </button>`
540
+ : this.heading;
504
541
  return html `
505
- <tt-button
506
- part="button"
507
- size="small"
508
- label=${this.actionName}
509
- ?disabled=${this.disabled}
510
- @tt-button-click=${this.handleClick}
511
- >
512
- ${this.buttonLabel}
513
- </tt-button>
542
+ <div class="heading" part="heading" role="heading" aria-level=${this.headingLevel}>
543
+ ${name}
544
+ </div>
545
+ `;
546
+ }
547
+ /*
548
+ Read as "Sale price EUR 89.00, was EUR 109.00". A strike-through is
549
+ not announced by screen readers, so without the hidden words the two
550
+ prices were read as one list of numbers.
551
+ */
552
+ renderPrice() {
553
+ if (!this.price)
554
+ return nothing;
555
+ const sale = this.wasPrice !== "";
556
+ return html `
557
+ <p class="price" part="price">${sale
558
+ ? html `<span class="sr-only">Sale price </span>`
559
+ : nothing}<span class="now ${sale ? "sale" : ""}">${this.price}</span>${sale
560
+ ? html `<span class="sr-only">, was </span><s class="was">${this.wasPrice}</s>`
561
+ : nothing}</p>
514
562
  `;
515
563
  }
516
564
  render() {
517
- if (this.orientation === "horizontal") {
518
- return html `
519
- <tt-card part="card" padding="none">
520
- <div class="row">
521
- ${this.renderMedia(false)}
522
- <div class="column">
523
- ${this.renderBody(true)}
524
- <div class="actions">
525
- ${this.renderRating()}
526
- ${this.renderButton()}
527
- </div>
565
+ const ratio = RATIO.test(this.ratio) ? this.ratio : "1";
566
+ const showMeta = this.rating !== "" || this.hasMeta;
567
+ const showActions = this.buttonLabel !== "" || this.hasActions;
568
+ return html `
569
+ <div class="card ${this.interactive ? "interactive" : ""}" part="card">
570
+ <div class="media" part="media" style=${styleMap({ "--_ratio": ratio })}>
571
+ ${this.src !== "" || !this.hasMedia
572
+ ? html `<tt-image class="image" part="image" radius="none" src=${this.src} alt=""></tt-image>`
573
+ : nothing}
574
+ <slot name="media" @slotchange=${this.onMedia}></slot>
575
+ <div class="badges"><slot name="badge"></slot></div>
576
+ </div>
577
+
578
+ <div class="body" part="body">
579
+ ${this.renderHeading()}
580
+ ${this.description
581
+ ? html `<p
582
+ class="description"
583
+ part="description"
584
+ style=${styleMap({ "--_lines": String(this.descriptionLines) })}
585
+ >${this.description}</p>`
586
+ : nothing}
587
+
588
+ <div class="meta" part="meta" ?hidden=${!showMeta}>
589
+ ${this.rating
590
+ ? html `<span class="rating" part="rating">
591
+ <tt-icon name="star"></tt-icon>
592
+ <span class="sr-only">Rating </span>${this.rating}
593
+ </span>`
594
+ : nothing}
595
+ <slot name="meta" @slotchange=${this.onMeta}></slot>
596
+ </div>
597
+
598
+ <div class="foot ${this.hasActions ? "extra" : ""}" ?hidden=${!this.price && !showActions}>
599
+ ${this.renderPrice()}
600
+ <div class="actions" part="actions" ?hidden=${!showActions}>
601
+ ${this.renderAction()}
602
+ <slot name="actions" @slotchange=${this.onActions}></slot>
528
603
  </div>
529
604
  </div>
530
- </tt-card>
531
- `;
532
- }
533
- return html `
534
- <tt-card part="card" padding="small">
535
- ${this.renderMedia(true)}
536
- ${this.renderBody()}
537
- </tt-card>
605
+ </div>
606
+ </div>
538
607
  `;
539
608
  }
540
609
  };
541
- __decorate([
542
- property({ type: String })
543
- ], TTProduct.prototype, "src", void 0);
544
- __decorate([
545
- property({ type: String })
546
- ], TTProduct.prototype, "ratio", void 0);
547
- __decorate([
548
- property({ type: String, reflect: true })
549
- ], TTProduct.prototype, "orientation", void 0);
550
- __decorate([
551
- property({ type: String, reflect: true })
552
- ], TTProduct.prototype, "variant", void 0);
553
- __decorate([
554
- property({ type: String, reflect: true })
555
- ], TTProduct.prototype, "layout", void 0);
556
610
  __decorate([
557
611
  property({ type: String })
558
612
  ], TTProduct.prototype, "heading", void 0);
613
+ __decorate([
614
+ property({ type: Number, attribute: "heading-level" })
615
+ ], TTProduct.prototype, "headingLevel", void 0);
559
616
  __decorate([
560
617
  property({ type: String })
561
618
  ], TTProduct.prototype, "description", void 0);
@@ -571,18 +628,42 @@ __decorate([
571
628
  __decorate([
572
629
  property({ type: String })
573
630
  ], TTProduct.prototype, "rating", void 0);
631
+ __decorate([
632
+ property({ type: String })
633
+ ], TTProduct.prototype, "src", void 0);
634
+ __decorate([
635
+ property({ type: String })
636
+ ], TTProduct.prototype, "ratio", void 0);
574
637
  __decorate([
575
638
  property({ type: String })
576
639
  ], TTProduct.prototype, "value", void 0);
577
640
  __decorate([
578
- property({ type: String, attribute: "button-link" })
579
- ], TTProduct.prototype, "buttonLink", void 0);
641
+ property({ type: String })
642
+ ], TTProduct.prototype, "href", void 0);
643
+ __decorate([
644
+ property({ type: Boolean, reflect: true })
645
+ ], TTProduct.prototype, "clickable", void 0);
580
646
  __decorate([
581
647
  property({ type: String, attribute: "button-label" })
582
648
  ], TTProduct.prototype, "buttonLabel", void 0);
649
+ __decorate([
650
+ property({ type: String, reflect: true })
651
+ ], TTProduct.prototype, "orientation", void 0);
652
+ __decorate([
653
+ property({ type: String, reflect: true })
654
+ ], TTProduct.prototype, "size", void 0);
583
655
  __decorate([
584
656
  property({ type: Boolean, reflect: true })
585
657
  ], TTProduct.prototype, "disabled", void 0);
658
+ __decorate([
659
+ state()
660
+ ], TTProduct.prototype, "hasMedia", void 0);
661
+ __decorate([
662
+ state()
663
+ ], TTProduct.prototype, "hasMeta", void 0);
664
+ __decorate([
665
+ state()
666
+ ], TTProduct.prototype, "hasActions", void 0);
586
667
  TTProduct = __decorate([
587
668
  customElement("tt-product")
588
669
  ], TTProduct);