@nymbalhq/web-components 1.1.0 → 1.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.
- package/astro/NymbalElement.astro +19 -0
- package/dist/base-element-CPMgAdyQ.d.ts +91 -0
- package/dist/chunk-2TPQNQYO.js +155 -0
- package/dist/chunk-2TPQNQYO.js.map +1 -0
- package/dist/chunk-2XXBQPDB.js +113 -0
- package/dist/chunk-2XXBQPDB.js.map +1 -0
- package/dist/chunk-42SSME5H.js +84 -0
- package/dist/chunk-42SSME5H.js.map +1 -0
- package/dist/chunk-4COVK727.js +204 -0
- package/dist/chunk-4COVK727.js.map +1 -0
- package/dist/chunk-5G3C53A2.js +666 -0
- package/dist/chunk-5G3C53A2.js.map +1 -0
- package/dist/chunk-5VWZ2U5K.js +149 -0
- package/dist/chunk-5VWZ2U5K.js.map +1 -0
- package/dist/chunk-7JAAOVCE.js +114 -0
- package/dist/chunk-7JAAOVCE.js.map +1 -0
- package/dist/chunk-B73QVSA5.js +206 -0
- package/dist/chunk-B73QVSA5.js.map +1 -0
- package/dist/chunk-DR5QB5VN.js +17 -0
- package/dist/chunk-DR5QB5VN.js.map +1 -0
- package/dist/chunk-DXWMNB5F.js +70 -0
- package/dist/chunk-DXWMNB5F.js.map +1 -0
- package/dist/chunk-EBMA7JGV.js +130 -0
- package/dist/chunk-EBMA7JGV.js.map +1 -0
- package/dist/chunk-EYDMJNNK.js +176 -0
- package/dist/chunk-EYDMJNNK.js.map +1 -0
- package/dist/chunk-FIF6GQ6U.js +141 -0
- package/dist/chunk-FIF6GQ6U.js.map +1 -0
- package/dist/chunk-GHXTJ2CF.js +109 -0
- package/dist/chunk-GHXTJ2CF.js.map +1 -0
- package/dist/chunk-H3ZBYNHV.js +153 -0
- package/dist/chunk-H3ZBYNHV.js.map +1 -0
- package/dist/chunk-HPBC3OG7.js +174 -0
- package/dist/chunk-HPBC3OG7.js.map +1 -0
- package/dist/chunk-IYT7VXFS.js +105 -0
- package/dist/chunk-IYT7VXFS.js.map +1 -0
- package/dist/chunk-LHYXNQWY.js +105 -0
- package/dist/chunk-LHYXNQWY.js.map +1 -0
- package/dist/chunk-LO2EW5YP.js +25 -0
- package/dist/chunk-LO2EW5YP.js.map +1 -0
- package/dist/chunk-LSQPUZK6.js +291 -0
- package/dist/chunk-LSQPUZK6.js.map +1 -0
- package/dist/chunk-N4Y5DI4X.js +82 -0
- package/dist/chunk-N4Y5DI4X.js.map +1 -0
- package/dist/chunk-NE6UHTOT.js +148 -0
- package/dist/chunk-NE6UHTOT.js.map +1 -0
- package/dist/chunk-NW47OJVN.js +138 -0
- package/dist/chunk-NW47OJVN.js.map +1 -0
- package/dist/chunk-O2YKCDR2.js +77 -0
- package/dist/chunk-O2YKCDR2.js.map +1 -0
- package/dist/chunk-PN6QKYE4.js +71 -0
- package/dist/chunk-PN6QKYE4.js.map +1 -0
- package/dist/chunk-RAOGZH3Z.js +188 -0
- package/dist/chunk-RAOGZH3Z.js.map +1 -0
- package/dist/chunk-RVSKX2RX.js +11 -0
- package/dist/chunk-RVSKX2RX.js.map +1 -0
- package/dist/chunk-RZVFA3N5.js +154 -0
- package/dist/chunk-RZVFA3N5.js.map +1 -0
- package/dist/chunk-SM7TASEW.js +135 -0
- package/dist/chunk-SM7TASEW.js.map +1 -0
- package/dist/chunk-U2PU4CWB.js +124 -0
- package/dist/chunk-U2PU4CWB.js.map +1 -0
- package/dist/chunk-UCEVCLQ7.js +5 -0
- package/dist/chunk-UCEVCLQ7.js.map +1 -0
- package/dist/chunk-UYSZOEOA.js +32 -0
- package/dist/chunk-UYSZOEOA.js.map +1 -0
- package/dist/chunk-V55K2CS5.js +126 -0
- package/dist/chunk-V55K2CS5.js.map +1 -0
- package/dist/chunk-VTQVGNVC.js +346 -0
- package/dist/chunk-VTQVGNVC.js.map +1 -0
- package/dist/chunk-W23P7SI4.js +239 -0
- package/dist/chunk-W23P7SI4.js.map +1 -0
- package/dist/chunk-W7D3STJH.js +279 -0
- package/dist/chunk-W7D3STJH.js.map +1 -0
- package/dist/chunk-WEEHXWM3.js +172 -0
- package/dist/chunk-WEEHXWM3.js.map +1 -0
- package/dist/chunk-WTEVTCIL.js +128 -0
- package/dist/chunk-WTEVTCIL.js.map +1 -0
- package/dist/chunk-XPFGMYBV.js +137 -0
- package/dist/chunk-XPFGMYBV.js.map +1 -0
- package/dist/elements/nymbal-add-to-cart.d.ts +14 -0
- package/dist/elements/nymbal-add-to-cart.js +10 -0
- package/dist/elements/nymbal-add-to-cart.js.map +1 -0
- package/dist/elements/nymbal-address-book.d.ts +30 -0
- package/dist/elements/nymbal-address-book.js +13 -0
- package/dist/elements/nymbal-address-book.js.map +1 -0
- package/dist/elements/nymbal-address-form.d.ts +29 -0
- package/dist/elements/nymbal-address-form.js +12 -0
- package/dist/elements/nymbal-address-form.js.map +1 -0
- package/dist/elements/nymbal-breadcrumb.d.ts +15 -0
- package/dist/elements/nymbal-breadcrumb.js +11 -0
- package/dist/elements/nymbal-breadcrumb.js.map +1 -0
- package/dist/elements/nymbal-cart-drawer.d.ts +24 -0
- package/dist/elements/nymbal-cart-drawer.js +11 -0
- package/dist/elements/nymbal-cart-drawer.js.map +1 -0
- package/dist/elements/nymbal-cart-page.d.ts +20 -0
- package/dist/elements/nymbal-cart-page.js +14 -0
- package/dist/elements/nymbal-cart-page.js.map +1 -0
- package/dist/elements/nymbal-category-nav.d.ts +15 -0
- package/dist/elements/nymbal-category-nav.js +11 -0
- package/dist/elements/nymbal-category-nav.js.map +1 -0
- package/dist/elements/nymbal-checkout.d.ts +77 -0
- package/dist/elements/nymbal-checkout.js +15 -0
- package/dist/elements/nymbal-checkout.js.map +1 -0
- package/dist/elements/nymbal-image-gallery.d.ts +19 -0
- package/dist/elements/nymbal-image-gallery.js +10 -0
- package/dist/elements/nymbal-image-gallery.js.map +1 -0
- package/dist/elements/nymbal-login-form.d.ts +17 -0
- package/dist/elements/nymbal-login-form.js +12 -0
- package/dist/elements/nymbal-login-form.js.map +1 -0
- package/dist/elements/nymbal-mini-cart.d.ts +17 -0
- package/dist/elements/nymbal-mini-cart.js +10 -0
- package/dist/elements/nymbal-mini-cart.js.map +1 -0
- package/dist/elements/nymbal-order-summary.d.ts +20 -0
- package/dist/elements/nymbal-order-summary.js +11 -0
- package/dist/elements/nymbal-order-summary.js.map +1 -0
- package/dist/elements/nymbal-payment-element.d.ts +4 -0
- package/dist/elements/nymbal-payment-element.js +10 -0
- package/dist/elements/nymbal-payment-element.js.map +1 -0
- package/dist/elements/nymbal-product-card.d.ts +4 -0
- package/dist/elements/nymbal-product-card.js +12 -0
- package/dist/elements/nymbal-product-card.js.map +1 -0
- package/dist/elements/nymbal-product-filter.d.ts +32 -0
- package/dist/elements/nymbal-product-filter.js +10 -0
- package/dist/elements/nymbal-product-filter.js.map +1 -0
- package/dist/elements/nymbal-product-grid.d.ts +24 -0
- package/dist/elements/nymbal-product-grid.js +14 -0
- package/dist/elements/nymbal-product-grid.js.map +1 -0
- package/dist/elements/nymbal-product-price.d.ts +18 -0
- package/dist/elements/nymbal-product-price.js +12 -0
- package/dist/elements/nymbal-product-price.js.map +1 -0
- package/dist/elements/nymbal-profile-form.d.ts +19 -0
- package/dist/elements/nymbal-profile-form.js +12 -0
- package/dist/elements/nymbal-profile-form.js.map +1 -0
- package/dist/elements/nymbal-quantity-selector.d.ts +20 -0
- package/dist/elements/nymbal-quantity-selector.js +10 -0
- package/dist/elements/nymbal-quantity-selector.js.map +1 -0
- package/dist/elements/nymbal-register-form.d.ts +17 -0
- package/dist/elements/nymbal-register-form.js +12 -0
- package/dist/elements/nymbal-register-form.js.map +1 -0
- package/dist/elements/nymbal-review-form.d.ts +17 -0
- package/dist/elements/nymbal-review-form.js +12 -0
- package/dist/elements/nymbal-review-form.js.map +1 -0
- package/dist/elements/nymbal-review-list.d.ts +14 -0
- package/dist/elements/nymbal-review-list.js +10 -0
- package/dist/elements/nymbal-review-list.js.map +1 -0
- package/dist/elements/nymbal-search-bar.d.ts +19 -0
- package/dist/elements/nymbal-search-bar.js +10 -0
- package/dist/elements/nymbal-search-bar.js.map +1 -0
- package/dist/elements/nymbal-search-results.d.ts +29 -0
- package/dist/elements/nymbal-search-results.js +14 -0
- package/dist/elements/nymbal-search-results.js.map +1 -0
- package/dist/elements/nymbal-site-footer.d.ts +15 -0
- package/dist/elements/nymbal-site-footer.js +11 -0
- package/dist/elements/nymbal-site-footer.js.map +1 -0
- package/dist/elements/nymbal-site-header.d.ts +19 -0
- package/dist/elements/nymbal-site-header.js +11 -0
- package/dist/elements/nymbal-site-header.js.map +1 -0
- package/dist/elements/nymbal-sort-dropdown.d.ts +28 -0
- package/dist/elements/nymbal-sort-dropdown.js +10 -0
- package/dist/elements/nymbal-sort-dropdown.js.map +1 -0
- package/dist/elements/nymbal-stock-badge.d.ts +16 -0
- package/dist/elements/nymbal-stock-badge.js +11 -0
- package/dist/elements/nymbal-stock-badge.js.map +1 -0
- package/dist/elements/nymbal-toast.d.ts +18 -0
- package/dist/elements/nymbal-toast.js +10 -0
- package/dist/elements/nymbal-toast.js.map +1 -0
- package/dist/elements/nymbal-variant-selector.d.ts +16 -0
- package/dist/elements/nymbal-variant-selector.js +10 -0
- package/dist/elements/nymbal-variant-selector.js.map +1 -0
- package/dist/form-element-C-qo3gpD.d.ts +25 -0
- package/dist/hydrate-support.d.ts +2 -0
- package/dist/hydrate-support.js +3 -0
- package/dist/hydrate-support.js.map +1 -0
- package/dist/index.d.ts +38 -157
- package/dist/index.js +127 -1407
- package/dist/index.js.map +1 -1
- package/dist/nymbal-payment-element-Bt8EF42T.d.ts +51 -0
- package/dist/nymbal-product-card-C8HkE_rG.d.ts +47 -0
- package/dist/register-components.d.ts +46 -0
- package/dist/register-components.js +48 -0
- package/dist/register-components.js.map +1 -0
- package/dist/selected-variant-Dym0shlJ.d.ts +23 -0
- package/dist/ssr/index.d.ts +30 -0
- package/dist/ssr/index.js +143 -0
- package/dist/ssr/index.js.map +1 -0
- package/package.json +33 -6
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
import {
|
|
3
|
+
NymbalFormElement
|
|
4
|
+
} from "./chunk-PN6QKYE4.js";
|
|
5
|
+
import {
|
|
6
|
+
buttonStyles,
|
|
7
|
+
fieldStyles
|
|
8
|
+
} from "./chunk-GHXTJ2CF.js";
|
|
9
|
+
import {
|
|
10
|
+
baseStyles,
|
|
11
|
+
propertiesOf
|
|
12
|
+
} from "./chunk-EYDMJNNK.js";
|
|
13
|
+
|
|
14
|
+
// src/components/review-form.ts
|
|
15
|
+
import { css, html, nothing } from "lit";
|
|
16
|
+
var SCHEMA = {
|
|
17
|
+
tag: "nymbal-review-form",
|
|
18
|
+
reactName: "ReviewForm",
|
|
19
|
+
attributes: [{ name: "product-id", prop: "productId", type: "string" }],
|
|
20
|
+
events: [
|
|
21
|
+
{
|
|
22
|
+
name: "nymbal:review:submitted",
|
|
23
|
+
prop: "onSubmitted",
|
|
24
|
+
detail: "{ reviewId: string }"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
parts: ["form", "rating", "title", "body", "submit", "error"],
|
|
28
|
+
slots: [],
|
|
29
|
+
children: false
|
|
30
|
+
};
|
|
31
|
+
var RATINGS = [
|
|
32
|
+
["5", "5 - Excellent"],
|
|
33
|
+
["4", "4 - Very Good"],
|
|
34
|
+
["3", "3 - Good"],
|
|
35
|
+
["2", "2 - Fair"],
|
|
36
|
+
["1", "1 - Poor"]
|
|
37
|
+
];
|
|
38
|
+
var NymbalReviewForm = class extends NymbalFormElement {
|
|
39
|
+
static schema = SCHEMA;
|
|
40
|
+
static properties = propertiesOf(SCHEMA);
|
|
41
|
+
static styles = [
|
|
42
|
+
baseStyles,
|
|
43
|
+
buttonStyles,
|
|
44
|
+
fieldStyles,
|
|
45
|
+
css`
|
|
46
|
+
:host {
|
|
47
|
+
max-width: 32rem;
|
|
48
|
+
}
|
|
49
|
+
h3 {
|
|
50
|
+
margin: 0 0 var(--nymbal-spacing-md, 1rem);
|
|
51
|
+
line-height: 1.2;
|
|
52
|
+
font-size: var(--nymbal-font-size-lg, 1.125rem);
|
|
53
|
+
font-weight: var(--nymbal-font-weight-bold, 700);
|
|
54
|
+
}
|
|
55
|
+
label {
|
|
56
|
+
display: block;
|
|
57
|
+
margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
|
|
58
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
59
|
+
font-weight: var(--nymbal-font-weight-medium, 500);
|
|
60
|
+
}
|
|
61
|
+
[part~='rating'],
|
|
62
|
+
[part~='title'],
|
|
63
|
+
[part~='body'] {
|
|
64
|
+
display: block;
|
|
65
|
+
width: 100%;
|
|
66
|
+
margin-bottom: var(--nymbal-spacing-md, 1rem);
|
|
67
|
+
padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);
|
|
68
|
+
font: inherit;
|
|
69
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
70
|
+
color: var(--nymbal-color-text, #18181b);
|
|
71
|
+
background-color: var(--nymbal-color-surface, #ffffff);
|
|
72
|
+
border: 1px solid var(--nymbal-color-border, #e4e4e7);
|
|
73
|
+
border-radius: var(--nymbal-radius-md, 0.5rem);
|
|
74
|
+
}
|
|
75
|
+
[part~='body'] {
|
|
76
|
+
resize: vertical;
|
|
77
|
+
}
|
|
78
|
+
.notice {
|
|
79
|
+
margin-top: var(--nymbal-spacing-md, 1rem);
|
|
80
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
81
|
+
color: var(--nymbal-color-success, #16a34a);
|
|
82
|
+
}
|
|
83
|
+
`
|
|
84
|
+
];
|
|
85
|
+
async send() {
|
|
86
|
+
const answer = await this.client.adapter.reviews.submit({
|
|
87
|
+
productId: this.productId ?? "",
|
|
88
|
+
rating: Number(this.value("rating")),
|
|
89
|
+
title: this.value("title"),
|
|
90
|
+
body: this.value("body")
|
|
91
|
+
});
|
|
92
|
+
this.renderRoot.querySelector("form")?.reset();
|
|
93
|
+
this.notice = "Thank you for your review!";
|
|
94
|
+
const reviewId = answer?.review?.id;
|
|
95
|
+
if (reviewId !== void 0) this.emit("nymbal:review:submitted", { reviewId });
|
|
96
|
+
}
|
|
97
|
+
failureText() {
|
|
98
|
+
return "Something went wrong. Please try again.";
|
|
99
|
+
}
|
|
100
|
+
render() {
|
|
101
|
+
return html`
|
|
102
|
+
${this.renderError()}
|
|
103
|
+
<form part="form" data-testid="review-form" @submit=${this.onSubmit}>
|
|
104
|
+
<h3>Write a Review</h3>
|
|
105
|
+
<label for="review-rating">Rating</label>
|
|
106
|
+
<select part="rating" id="review-rating" name="rating" required data-testid="review-rating">
|
|
107
|
+
<option value="">Select a rating</option>
|
|
108
|
+
${RATINGS.map(([value, label]) => html`<option value=${value}>${label}</option>`)}
|
|
109
|
+
</select>
|
|
110
|
+
<label for="review-title">Title</label>
|
|
111
|
+
<input
|
|
112
|
+
part="title"
|
|
113
|
+
id="review-title"
|
|
114
|
+
name="title"
|
|
115
|
+
type="text"
|
|
116
|
+
placeholder="Summarise your experience"
|
|
117
|
+
required
|
|
118
|
+
data-testid="review-title"
|
|
119
|
+
/>
|
|
120
|
+
<label for="review-body">Review</label>
|
|
121
|
+
<textarea
|
|
122
|
+
part="body"
|
|
123
|
+
id="review-body"
|
|
124
|
+
name="body"
|
|
125
|
+
rows="4"
|
|
126
|
+
placeholder="Tell us what you think..."
|
|
127
|
+
required
|
|
128
|
+
data-testid="review-body"
|
|
129
|
+
></textarea>
|
|
130
|
+
<button
|
|
131
|
+
type="submit"
|
|
132
|
+
part="submit"
|
|
133
|
+
class="btn btn-primary"
|
|
134
|
+
data-testid="review-submit"
|
|
135
|
+
?disabled=${this.pending}
|
|
136
|
+
>
|
|
137
|
+
${this.pending ? "Submitting..." : "Submit Review"}
|
|
138
|
+
</button>
|
|
139
|
+
${this.notice === null ? nothing : html`<p class="notice" role="status">${this.notice}</p>`}
|
|
140
|
+
</form>
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
NymbalReviewForm
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=chunk-NE6UHTOT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/review-form.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalFormElement } from '../form-element.js'\nimport { buttonStyles, fieldStyles } from '../view-styles.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-review-form',\n reactName: 'ReviewForm',\n attributes: [{ name: 'product-id', prop: 'productId', type: 'string' }],\n events: [\n {\n name: 'nymbal:review:submitted',\n prop: 'onSubmitted',\n detail: '{ reviewId: string }',\n },\n ],\n parts: ['form', 'rating', 'title', 'body', 'submit', 'error'],\n slots: [],\n children: false,\n}\n\nconst RATINGS: Array<[string, string]> = [\n ['5', '5 - Excellent'],\n ['4', '4 - Very Good'],\n ['3', '3 - Good'],\n ['2', '2 - Fair'],\n ['1', '1 - Poor'],\n]\n\n/** A review of one product: one submission per click, the form cleared once the store has it. */\nexport class NymbalReviewForm extends NymbalFormElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n fieldStyles,\n css`\n :host {\n max-width: 32rem;\n }\n h3 {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n line-height: 1.2;\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n }\n label {\n display: block;\n margin-bottom: var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='rating'],\n [part~='title'],\n [part~='body'] {\n display: block;\n width: 100%;\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);\n font: inherit;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text, #18181b);\n background-color: var(--nymbal-color-surface, #ffffff);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-md, 0.5rem);\n }\n [part~='body'] {\n resize: vertical;\n }\n .notice {\n margin-top: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-success, #16a34a);\n }\n `,\n ]\n\n declare productId: string | undefined\n\n protected override async send(): Promise<void> {\n const answer: { review?: { id: string } | null } | null | undefined =\n await this.client.adapter.reviews.submit({\n productId: this.productId ?? '',\n rating: Number(this.value('rating')),\n title: this.value('title'),\n body: this.value('body'),\n })\n this.renderRoot.querySelector('form')?.reset()\n this.notice = 'Thank you for your review!'\n // A 2xx answer is the store's acceptance; without a review in it there is no id to announce.\n const reviewId = answer?.review?.id\n if (reviewId !== undefined) this.emit('nymbal:review:submitted', { reviewId })\n }\n\n protected override failureText(): string {\n return 'Something went wrong. Please try again.'\n }\n\n protected override render() {\n return html`\n ${this.renderError()}\n <form part=\"form\" data-testid=\"review-form\" @submit=${this.onSubmit}>\n <h3>Write a Review</h3>\n <label for=\"review-rating\">Rating</label>\n <select part=\"rating\" id=\"review-rating\" name=\"rating\" required data-testid=\"review-rating\">\n <option value=\"\">Select a rating</option>\n ${RATINGS.map(([value, label]) => html`<option value=${value}>${label}</option>`)}\n </select>\n <label for=\"review-title\">Title</label>\n <input\n part=\"title\"\n id=\"review-title\"\n name=\"title\"\n type=\"text\"\n placeholder=\"Summarise your experience\"\n required\n data-testid=\"review-title\"\n />\n <label for=\"review-body\">Review</label>\n <textarea\n part=\"body\"\n id=\"review-body\"\n name=\"body\"\n rows=\"4\"\n placeholder=\"Tell us what you think...\"\n required\n data-testid=\"review-body\"\n ></textarea>\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"review-submit\"\n ?disabled=${this.pending}\n >\n ${this.pending ? 'Submitting...' : 'Submit Review'}\n </button>\n ${this.notice === null ? nothing : html`<p class=\"notice\" role=\"status\">${this.notice}</p>`}\n </form>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AAKnC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,YAAY,CAAC,EAAE,MAAM,cAAc,MAAM,aAAa,MAAM,SAAS,CAAC;AAAA,EACtE,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,OAAO,CAAC,QAAQ,UAAU,SAAS,QAAQ,UAAU,OAAO;AAAA,EAC5D,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AAEA,IAAM,UAAmC;AAAA,EACvC,CAAC,KAAK,eAAe;AAAA,EACrB,CAAC,KAAK,eAAe;AAAA,EACrB,CAAC,KAAK,UAAU;AAAA,EAChB,CAAC,KAAK,UAAU;AAAA,EAChB,CAAC,KAAK,UAAU;AAClB;AAGO,IAAM,mBAAN,cAA+B,kBAAkB;AAAA,EACtD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuCF;AAAA,EAIA,MAAyB,OAAsB;AAC7C,UAAM,SACJ,MAAM,KAAK,OAAO,QAAQ,QAAQ,OAAO;AAAA,MACvC,WAAW,KAAK,aAAa;AAAA,MAC7B,QAAQ,OAAO,KAAK,MAAM,QAAQ,CAAC;AAAA,MACnC,OAAO,KAAK,MAAM,OAAO;AAAA,MACzB,MAAM,KAAK,MAAM,MAAM;AAAA,IACzB,CAAC;AACH,SAAK,WAAW,cAAc,MAAM,GAAG,MAAM;AAC7C,SAAK,SAAS;AAEd,UAAM,WAAW,QAAQ,QAAQ;AACjC,QAAI,aAAa,OAAW,MAAK,KAAK,2BAA2B,EAAE,SAAS,CAAC;AAAA,EAC/E;AAAA,EAEmB,cAAsB;AACvC,WAAO;AAAA,EACT;AAAA,EAEmB,SAAS;AAC1B,WAAO;AAAA,QACH,KAAK,YAAY,CAAC;AAAA,4DACkC,KAAK,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,YAK7D,QAAQ,IAAI,CAAC,CAAC,OAAO,KAAK,MAAM,qBAAqB,KAAK,IAAI,KAAK,WAAW,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBA2BrE,KAAK,OAAO;AAAA;AAAA,YAEtB,KAAK,UAAU,kBAAkB,eAAe;AAAA;AAAA,UAElD,KAAK,WAAW,OAAO,UAAU,uCAAuC,KAAK,MAAM,MAAM;AAAA;AAAA;AAAA,EAGjG;AACF;","names":[]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
import {
|
|
3
|
+
NymbalFormElement,
|
|
4
|
+
renderField
|
|
5
|
+
} from "./chunk-PN6QKYE4.js";
|
|
6
|
+
import {
|
|
7
|
+
buttonStyles,
|
|
8
|
+
fieldStyles
|
|
9
|
+
} from "./chunk-GHXTJ2CF.js";
|
|
10
|
+
import {
|
|
11
|
+
baseStyles,
|
|
12
|
+
propertiesOf
|
|
13
|
+
} from "./chunk-EYDMJNNK.js";
|
|
14
|
+
|
|
15
|
+
// src/components/register-form.ts
|
|
16
|
+
import { css, html } from "lit";
|
|
17
|
+
var SCHEMA = {
|
|
18
|
+
tag: "nymbal-register-form",
|
|
19
|
+
reactName: "RegisterForm",
|
|
20
|
+
attributes: [],
|
|
21
|
+
events: [
|
|
22
|
+
{
|
|
23
|
+
name: "nymbal:auth:signed-in",
|
|
24
|
+
prop: "onSignedIn",
|
|
25
|
+
detail: "{ customer: unknown }"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
parts: ["form", "row", "field", "label", "input", "error", "submit"],
|
|
29
|
+
slots: ["submit-label"],
|
|
30
|
+
children: false
|
|
31
|
+
};
|
|
32
|
+
var NymbalRegisterForm = class extends NymbalFormElement {
|
|
33
|
+
static schema = SCHEMA;
|
|
34
|
+
static properties = propertiesOf(SCHEMA);
|
|
35
|
+
static styles = [
|
|
36
|
+
baseStyles,
|
|
37
|
+
buttonStyles,
|
|
38
|
+
fieldStyles,
|
|
39
|
+
css`
|
|
40
|
+
[part~='row'] {
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
|
43
|
+
gap: 0 var(--nymbal-spacing-md, 1rem);
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
];
|
|
47
|
+
// The page leaves on success; a second submit while it does would try to register the email twice.
|
|
48
|
+
lockOnSuccess = true;
|
|
49
|
+
async send() {
|
|
50
|
+
if (this.value("password") !== this.value("confirmPassword")) {
|
|
51
|
+
throw new Error("Passwords do not match.");
|
|
52
|
+
}
|
|
53
|
+
await this.client.auth.register({
|
|
54
|
+
email: this.value("email"),
|
|
55
|
+
password: this.value("password"),
|
|
56
|
+
firstName: this.value("firstName"),
|
|
57
|
+
lastName: this.value("lastName"),
|
|
58
|
+
phone: this.value("phone")
|
|
59
|
+
});
|
|
60
|
+
this.emit("nymbal:auth:signed-in", { customer: this.client.auth.getState().customer });
|
|
61
|
+
}
|
|
62
|
+
failureText(err) {
|
|
63
|
+
return err instanceof Error && err.message !== "" ? err.message : "Registration failed. Please try again.";
|
|
64
|
+
}
|
|
65
|
+
render() {
|
|
66
|
+
return html`
|
|
67
|
+
${this.renderError()}
|
|
68
|
+
<form part="form" data-testid="register-form" @submit=${this.onSubmit}>
|
|
69
|
+
<div part="row">
|
|
70
|
+
${renderField({
|
|
71
|
+
id: "register-firstName",
|
|
72
|
+
name: "firstName",
|
|
73
|
+
label: "First Name",
|
|
74
|
+
autocomplete: "given-name",
|
|
75
|
+
placeholder: "First name"
|
|
76
|
+
})}
|
|
77
|
+
${renderField({
|
|
78
|
+
id: "register-lastName",
|
|
79
|
+
name: "lastName",
|
|
80
|
+
label: "Last Name",
|
|
81
|
+
autocomplete: "family-name",
|
|
82
|
+
placeholder: "Last name"
|
|
83
|
+
})}
|
|
84
|
+
</div>
|
|
85
|
+
${renderField({
|
|
86
|
+
id: "register-email",
|
|
87
|
+
name: "email",
|
|
88
|
+
label: "Email",
|
|
89
|
+
type: "email",
|
|
90
|
+
autocomplete: "email",
|
|
91
|
+
required: true,
|
|
92
|
+
placeholder: "you@example.com"
|
|
93
|
+
})}
|
|
94
|
+
${renderField({
|
|
95
|
+
id: "register-password",
|
|
96
|
+
name: "password",
|
|
97
|
+
label: "Password",
|
|
98
|
+
type: "password",
|
|
99
|
+
autocomplete: "new-password",
|
|
100
|
+
required: true,
|
|
101
|
+
minlength: 8,
|
|
102
|
+
placeholder: "Choose a password"
|
|
103
|
+
})}
|
|
104
|
+
${renderField({
|
|
105
|
+
id: "register-confirmPassword",
|
|
106
|
+
name: "confirmPassword",
|
|
107
|
+
label: "Confirm Password",
|
|
108
|
+
type: "password",
|
|
109
|
+
autocomplete: "new-password",
|
|
110
|
+
required: true,
|
|
111
|
+
placeholder: "Confirm your password"
|
|
112
|
+
})}
|
|
113
|
+
${renderField({
|
|
114
|
+
id: "register-phone",
|
|
115
|
+
name: "phone",
|
|
116
|
+
label: "Phone (optional)",
|
|
117
|
+
type: "tel",
|
|
118
|
+
autocomplete: "tel",
|
|
119
|
+
placeholder: "+44 7700 900000"
|
|
120
|
+
})}
|
|
121
|
+
<button
|
|
122
|
+
type="submit"
|
|
123
|
+
part="submit"
|
|
124
|
+
class="btn btn-primary"
|
|
125
|
+
data-testid="register-submit"
|
|
126
|
+
?disabled=${this.pending}
|
|
127
|
+
>
|
|
128
|
+
${this.pending ? "Creating account..." : html`<slot name="submit-label">Create Account</slot>`}
|
|
129
|
+
</button>
|
|
130
|
+
</form>
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
NymbalRegisterForm
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=chunk-NW47OJVN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/register-form.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalFormElement, renderField } from '../form-element.js'\nimport { buttonStyles, fieldStyles } from '../view-styles.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-register-form',\n reactName: 'RegisterForm',\n attributes: [],\n events: [\n {\n name: 'nymbal:auth:signed-in',\n prop: 'onSignedIn',\n detail: '{ customer: unknown }',\n },\n ],\n parts: ['form', 'row', 'field', 'label', 'input', 'error', 'submit'],\n slots: ['submit-label'],\n children: false,\n}\n\n/** Creates the shopper's account and signs them in; the page decides where they go next. */\nexport class NymbalRegisterForm extends NymbalFormElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n fieldStyles,\n css`\n [part~='row'] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n gap: 0 var(--nymbal-spacing-md, 1rem);\n }\n `,\n ]\n\n // The page leaves on success; a second submit while it does would try to register the email twice.\n protected override readonly lockOnSuccess = true\n\n protected override async send(): Promise<void> {\n if (this.value('password') !== this.value('confirmPassword')) {\n throw new Error('Passwords do not match.')\n }\n await this.client.auth.register({\n email: this.value('email'),\n password: this.value('password'),\n firstName: this.value('firstName'),\n lastName: this.value('lastName'),\n phone: this.value('phone'),\n })\n this.emit('nymbal:auth:signed-in', { customer: this.client.auth.getState().customer })\n }\n\n protected override failureText(err: unknown): string {\n return err instanceof Error && err.message !== ''\n ? err.message\n : 'Registration failed. Please try again.'\n }\n\n protected override render() {\n return html`\n ${this.renderError()}\n <form part=\"form\" data-testid=\"register-form\" @submit=${this.onSubmit}>\n <div part=\"row\">\n ${renderField({\n id: 'register-firstName',\n name: 'firstName',\n label: 'First Name',\n autocomplete: 'given-name',\n placeholder: 'First name',\n })}\n ${renderField({\n id: 'register-lastName',\n name: 'lastName',\n label: 'Last Name',\n autocomplete: 'family-name',\n placeholder: 'Last name',\n })}\n </div>\n ${renderField({\n id: 'register-email',\n name: 'email',\n label: 'Email',\n type: 'email',\n autocomplete: 'email',\n required: true,\n placeholder: 'you@example.com',\n })}\n ${renderField({\n id: 'register-password',\n name: 'password',\n label: 'Password',\n type: 'password',\n autocomplete: 'new-password',\n required: true,\n minlength: 8,\n placeholder: 'Choose a password',\n })}\n ${renderField({\n id: 'register-confirmPassword',\n name: 'confirmPassword',\n label: 'Confirm Password',\n type: 'password',\n autocomplete: 'new-password',\n required: true,\n placeholder: 'Confirm your password',\n })}\n ${renderField({\n id: 'register-phone',\n name: 'phone',\n label: 'Phone (optional)',\n type: 'tel',\n autocomplete: 'tel',\n placeholder: '+44 7700 900000',\n })}\n <button\n type=\"submit\"\n part=\"submit\"\n class=\"btn btn-primary\"\n data-testid=\"register-submit\"\n ?disabled=${this.pending}\n >\n ${this.pending ? 'Creating account...' : html`<slot name=\"submit-label\">Create Account</slot>`}\n </button>\n </form>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,KAAK,YAAY;AAK1B,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA,EACX,YAAY,CAAC;AAAA,EACb,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,OAAO,CAAC,QAAQ,OAAO,SAAS,SAAS,SAAS,SAAS,QAAQ;AAAA,EACnE,OAAO,CAAC,cAAc;AAAA,EACtB,UAAU;AACZ;AAGO,IAAM,qBAAN,cAAiC,kBAAkB;AAAA,EACxD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOF;AAAA;AAAA,EAG4B,gBAAgB;AAAA,EAE5C,MAAyB,OAAsB;AAC7C,QAAI,KAAK,MAAM,UAAU,MAAM,KAAK,MAAM,iBAAiB,GAAG;AAC5D,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AACA,UAAM,KAAK,OAAO,KAAK,SAAS;AAAA,MAC9B,OAAO,KAAK,MAAM,OAAO;AAAA,MACzB,UAAU,KAAK,MAAM,UAAU;AAAA,MAC/B,WAAW,KAAK,MAAM,WAAW;AAAA,MACjC,UAAU,KAAK,MAAM,UAAU;AAAA,MAC/B,OAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,CAAC;AACD,SAAK,KAAK,yBAAyB,EAAE,UAAU,KAAK,OAAO,KAAK,SAAS,EAAE,SAAS,CAAC;AAAA,EACvF;AAAA,EAEmB,YAAY,KAAsB;AACnD,WAAO,eAAe,SAAS,IAAI,YAAY,KAC3C,IAAI,UACJ;AAAA,EACN;AAAA,EAEmB,SAAS;AAC1B,WAAO;AAAA,QACH,KAAK,YAAY,CAAC;AAAA,8DACoC,KAAK,QAAQ;AAAA;AAAA,YAE/D,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,YACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA;AAAA,UAEF,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC,CAAC;AAAA,UACA,YAAY;AAAA,MACZ,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,MAAM;AAAA,MACN,cAAc;AAAA,MACd,aAAa;AAAA,IACf,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAMY,KAAK,OAAO;AAAA;AAAA,YAEtB,KAAK,UAAU,wBAAwB,qDAAqD;AAAA;AAAA;AAAA;AAAA,EAItG;AACF;","names":[]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
import {
|
|
3
|
+
NymbalVariantElement
|
|
4
|
+
} from "./chunk-UYSZOEOA.js";
|
|
5
|
+
import {
|
|
6
|
+
baseStyles,
|
|
7
|
+
propertiesOf
|
|
8
|
+
} from "./chunk-EYDMJNNK.js";
|
|
9
|
+
|
|
10
|
+
// src/components/stock-badge.ts
|
|
11
|
+
import { css, html, nothing } from "lit";
|
|
12
|
+
import { STOCK_LABELS, stockLevel } from "@nymbalhq/types";
|
|
13
|
+
var SCHEMA = {
|
|
14
|
+
tag: "nymbal-stock-badge",
|
|
15
|
+
reactName: "StockBadge",
|
|
16
|
+
// The product's first variant's stock as a server read it, and the product whose store updates it may follow.
|
|
17
|
+
attributes: [
|
|
18
|
+
{ name: "product-id", prop: "productId", type: "string" },
|
|
19
|
+
{ name: "stock", prop: "stock", type: "number" }
|
|
20
|
+
],
|
|
21
|
+
events: [],
|
|
22
|
+
parts: ["badge", "label"],
|
|
23
|
+
slots: [],
|
|
24
|
+
children: false
|
|
25
|
+
};
|
|
26
|
+
var NymbalStockBadge = class extends NymbalVariantElement {
|
|
27
|
+
static schema = SCHEMA;
|
|
28
|
+
static properties = propertiesOf(SCHEMA);
|
|
29
|
+
static styles = [
|
|
30
|
+
baseStyles,
|
|
31
|
+
css`
|
|
32
|
+
:host {
|
|
33
|
+
display: block;
|
|
34
|
+
font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
|
|
35
|
+
}
|
|
36
|
+
[part~='badge'] {
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: var(--nymbal-spacing-xs, 0.25rem);
|
|
40
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
41
|
+
font-weight: var(--nymbal-font-weight-medium, 500);
|
|
42
|
+
}
|
|
43
|
+
[part~='badge'][data-level='in'] {
|
|
44
|
+
color: var(--nymbal-color-success, #16a34a);
|
|
45
|
+
}
|
|
46
|
+
[part~='badge'][data-level='low'] {
|
|
47
|
+
color: var(--nymbal-color-warning, #d97706);
|
|
48
|
+
}
|
|
49
|
+
[part~='badge'][data-level='out'] {
|
|
50
|
+
color: var(--nymbal-color-error, #dc2626);
|
|
51
|
+
}
|
|
52
|
+
.dot {
|
|
53
|
+
width: 8px;
|
|
54
|
+
height: 8px;
|
|
55
|
+
border-radius: var(--nymbal-radius-full, 9999px);
|
|
56
|
+
background-color: currentColor;
|
|
57
|
+
}
|
|
58
|
+
`
|
|
59
|
+
];
|
|
60
|
+
render() {
|
|
61
|
+
const store = this.selected();
|
|
62
|
+
const stock = store === null ? this.stock : store.variant?.stock ?? 0;
|
|
63
|
+
if (stock === void 0 || Number.isNaN(stock)) return nothing;
|
|
64
|
+
const level = stockLevel(stock);
|
|
65
|
+
return html`
|
|
66
|
+
<span part="badge" data-testid="stock-badge" data-level=${level}>
|
|
67
|
+
<span class="dot" aria-hidden="true"></span>
|
|
68
|
+
<span part="label">${STOCK_LABELS[level]}</span>
|
|
69
|
+
</span>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
NymbalStockBadge
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=chunk-O2YKCDR2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/stock-badge.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport { STOCK_LABELS, stockLevel } from '@nymbalhq/types'\nimport { baseStyles, propertiesOf, type ElementSchema } from '../base-element.js'\nimport { NymbalVariantElement } from '../selected-variant.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-stock-badge',\n reactName: 'StockBadge',\n // The product's first variant's stock as a server read it, and the product whose store updates it may follow.\n attributes: [\n { name: 'product-id', prop: 'productId', type: 'string' },\n { name: 'stock', prop: 'stock', type: 'number' },\n ],\n events: [],\n parts: ['badge', 'label'],\n slots: [],\n children: false,\n}\n\n/** A product page's stock level (`@nymbalhq/types`' rule): the selected variant's once the store holds this product. */\nexport class NymbalStockBadge extends NymbalVariantElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n }\n [part~='badge'] {\n display: inline-flex;\n align-items: center;\n gap: var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='badge'][data-level='in'] {\n color: var(--nymbal-color-success, #16a34a);\n }\n [part~='badge'][data-level='low'] {\n color: var(--nymbal-color-warning, #d97706);\n }\n [part~='badge'][data-level='out'] {\n color: var(--nymbal-color-error, #dc2626);\n }\n .dot {\n width: 8px;\n height: 8px;\n border-radius: var(--nymbal-radius-full, 9999px);\n background-color: currentColor;\n }\n `,\n ]\n\n declare stock: number | undefined\n\n protected override render() {\n const store = this.selected()\n // A product with no variants has nothing to sell, so it reads as out of stock.\n const stock = store === null ? this.stock : (store.variant?.stock ?? 0)\n if (stock === undefined || Number.isNaN(stock)) return nothing\n const level = stockLevel(stock)\n return html`\n <span part=\"badge\" data-testid=\"stock-badge\" data-level=${level}>\n <span class=\"dot\" aria-hidden=\"true\"></span>\n <span part=\"label\">${STOCK_LABELS[level]}</span>\n </span>\n `\n }\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AACnC,SAAS,cAAc,kBAAkB;AAIzC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,IACV,EAAE,MAAM,cAAc,MAAM,aAAa,MAAM,SAAS;AAAA,IACxD,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,SAAS;AAAA,EACjD;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,SAAS,OAAO;AAAA,EACxB,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AAGO,IAAM,mBAAN,cAA+B,qBAAqB;AAAA,EACzD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BF;AAAA,EAImB,SAAS;AAC1B,UAAM,QAAQ,KAAK,SAAS;AAE5B,UAAM,QAAQ,UAAU,OAAO,KAAK,QAAS,MAAM,SAAS,SAAS;AACrE,QAAI,UAAU,UAAa,OAAO,MAAM,KAAK,EAAG,QAAO;AACvD,UAAM,QAAQ,WAAW,KAAK;AAC9B,WAAO;AAAA,gEACqD,KAAK;AAAA;AAAA,6BAExC,aAAa,KAAK,CAAC;AAAA;AAAA;AAAA,EAG9C;AACF;","names":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
import {
|
|
3
|
+
NymbalElement
|
|
4
|
+
} from "./chunk-EYDMJNNK.js";
|
|
5
|
+
|
|
6
|
+
// src/form-element.ts
|
|
7
|
+
import { html, nothing } from "lit";
|
|
8
|
+
function renderField(f) {
|
|
9
|
+
return html`
|
|
10
|
+
<div part="field">
|
|
11
|
+
<label part="label" for=${f.id}>${f.label}</label>
|
|
12
|
+
<input
|
|
13
|
+
part="input"
|
|
14
|
+
id=${f.id}
|
|
15
|
+
name=${f.name}
|
|
16
|
+
type=${f.type ?? "text"}
|
|
17
|
+
autocomplete=${f.autocomplete}
|
|
18
|
+
placeholder=${f.placeholder ?? nothing}
|
|
19
|
+
minlength=${f.minlength ?? nothing}
|
|
20
|
+
?required=${f.required ?? false}
|
|
21
|
+
?disabled=${f.disabled ?? false}
|
|
22
|
+
.value=${f.value ?? ""}
|
|
23
|
+
data-testid=${f.id}
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
var NymbalFormElement = class extends NymbalElement {
|
|
29
|
+
pending = false;
|
|
30
|
+
error = null;
|
|
31
|
+
notice = null;
|
|
32
|
+
/** Keeps the submit disabled after a success, for a form whose page navigates away on it. */
|
|
33
|
+
lockOnSuccess = false;
|
|
34
|
+
/** The error text a refusal shows. */
|
|
35
|
+
failureText(err) {
|
|
36
|
+
return err instanceof Error && err.message !== "" ? err.message : "Something went wrong.";
|
|
37
|
+
}
|
|
38
|
+
onSubmit = (event) => {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
if (this.pending) return;
|
|
41
|
+
this.pending = true;
|
|
42
|
+
this.error = null;
|
|
43
|
+
this.notice = null;
|
|
44
|
+
const sent = this.send();
|
|
45
|
+
this.requestUpdate();
|
|
46
|
+
sent.then(
|
|
47
|
+
() => this.settle(null),
|
|
48
|
+
(err) => this.settle(this.failureText(err))
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
settle(error) {
|
|
52
|
+
this.pending = error === null && this.lockOnSuccess;
|
|
53
|
+
this.error = error;
|
|
54
|
+
this.requestUpdate();
|
|
55
|
+
}
|
|
56
|
+
value(name) {
|
|
57
|
+
return this.renderRoot.querySelector(`[name="${name}"]`)?.value ?? "";
|
|
58
|
+
}
|
|
59
|
+
emit(name, detail) {
|
|
60
|
+
this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }));
|
|
61
|
+
}
|
|
62
|
+
renderError() {
|
|
63
|
+
return this.error === null ? nothing : html`<div part="error" role="alert">${this.error}</div>`;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
renderField,
|
|
69
|
+
NymbalFormElement
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=chunk-PN6QKYE4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/form-element.ts"],"sourcesContent":["import { html, nothing, type TemplateResult } from 'lit'\nimport { NymbalElement } from './base-element.js'\n\nexport interface FieldSpec {\n /** The input's id and test id, unique within the element's shadow root. */\n id: string\n name: string\n label: string\n type?: 'text' | 'email' | 'password' | 'tel'\n autocomplete: string\n required?: boolean\n disabled?: boolean\n minlength?: number\n placeholder?: string\n /** Set on the input when it changes, so typing is kept across renders. */\n value?: string\n}\n\n/** A labelled input; the label's `for` and the input share the element's shadow root. */\nexport function renderField(f: FieldSpec): TemplateResult {\n return html`\n <div part=\"field\">\n <label part=\"label\" for=${f.id}>${f.label}</label>\n <input\n part=\"input\"\n id=${f.id}\n name=${f.name}\n type=${f.type ?? 'text'}\n autocomplete=${f.autocomplete}\n placeholder=${f.placeholder ?? nothing}\n minlength=${f.minlength ?? nothing}\n ?required=${f.required ?? false}\n ?disabled=${f.disabled ?? false}\n .value=${f.value ?? ''}\n data-testid=${f.id}\n />\n </div>\n `\n}\n\n/**\n * A view whose form makes one SDK call per submission. `requestSubmit()` and Enter still fire `submit` while the\n * button is disabled, so the pending flag, not the button, is the guard.\n */\nexport abstract class NymbalFormElement extends NymbalElement {\n protected pending = false\n protected error: string | null = null\n protected notice: string | null = null\n /** Keeps the submit disabled after a success, for a form whose page navigates away on it. */\n protected readonly lockOnSuccess: boolean = false\n\n /** Reads the fields and makes the call; it must call the SDK before its first `await`. */\n protected abstract send(): Promise<void>\n\n /** The error text a refusal shows. */\n protected failureText(err: unknown): string {\n return err instanceof Error && err.message !== '' ? err.message : 'Something went wrong.'\n }\n\n protected readonly onSubmit = (event: Event): void => {\n event.preventDefault()\n if (this.pending) return\n this.pending = true\n this.error = null\n this.notice = null\n const sent = this.send()\n this.requestUpdate()\n sent.then(\n () => this.settle(null),\n (err: unknown) => this.settle(this.failureText(err)),\n )\n }\n\n private settle(error: string | null): void {\n this.pending = error === null && this.lockOnSuccess\n this.error = error\n this.requestUpdate()\n }\n\n protected value(name: string): string {\n return this.renderRoot.querySelector<HTMLInputElement>(`[name=\"${name}\"]`)?.value ?? ''\n }\n\n protected emit(name: string, detail: unknown): void {\n this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail }))\n }\n\n protected renderError(): TemplateResult | typeof nothing {\n return this.error === null\n ? nothing\n : html`<div part=\"error\" role=\"alert\">${this.error}</div>`\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,MAAM,eAAoC;AAmB5C,SAAS,YAAY,GAA8B;AACxD,SAAO;AAAA;AAAA,gCAEuB,EAAE,EAAE,IAAI,EAAE,KAAK;AAAA;AAAA;AAAA,aAGlC,EAAE,EAAE;AAAA,eACF,EAAE,IAAI;AAAA,eACN,EAAE,QAAQ,MAAM;AAAA,uBACR,EAAE,YAAY;AAAA,sBACf,EAAE,eAAe,OAAO;AAAA,oBAC1B,EAAE,aAAa,OAAO;AAAA,oBACtB,EAAE,YAAY,KAAK;AAAA,oBACnB,EAAE,YAAY,KAAK;AAAA,iBACtB,EAAE,SAAS,EAAE;AAAA,sBACR,EAAE,EAAE;AAAA;AAAA;AAAA;AAI1B;AAMO,IAAe,oBAAf,cAAyC,cAAc;AAAA,EAClD,UAAU;AAAA,EACV,QAAuB;AAAA,EACvB,SAAwB;AAAA;AAAA,EAEf,gBAAyB;AAAA;AAAA,EAMlC,YAAY,KAAsB;AAC1C,WAAO,eAAe,SAAS,IAAI,YAAY,KAAK,IAAI,UAAU;AAAA,EACpE;AAAA,EAEmB,WAAW,CAAC,UAAuB;AACpD,UAAM,eAAe;AACrB,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,UAAM,OAAO,KAAK,KAAK;AACvB,SAAK,cAAc;AACnB,SAAK;AAAA,MACH,MAAM,KAAK,OAAO,IAAI;AAAA,MACtB,CAAC,QAAiB,KAAK,OAAO,KAAK,YAAY,GAAG,CAAC;AAAA,IACrD;AAAA,EACF;AAAA,EAEQ,OAAO,OAA4B;AACzC,SAAK,UAAU,UAAU,QAAQ,KAAK;AACtC,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACrB;AAAA,EAEU,MAAM,MAAsB;AACpC,WAAO,KAAK,WAAW,cAAgC,UAAU,IAAI,IAAI,GAAG,SAAS;AAAA,EACvF;AAAA,EAEU,KAAK,MAAc,QAAuB;AAClD,SAAK,cAAc,IAAI,YAAY,MAAM,EAAE,SAAS,MAAM,UAAU,MAAM,OAAO,CAAC,CAAC;AAAA,EACrF;AAAA,EAEU,cAA+C;AACvD,WAAO,KAAK,UAAU,OAClB,UACA,sCAAsC,KAAK,KAAK;AAAA,EACtD;AACF;","names":[]}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
import {
|
|
3
|
+
NymbalProductCard
|
|
4
|
+
} from "./chunk-RZVFA3N5.js";
|
|
5
|
+
import {
|
|
6
|
+
buttonStyles
|
|
7
|
+
} from "./chunk-GHXTJ2CF.js";
|
|
8
|
+
import {
|
|
9
|
+
NymbalElement,
|
|
10
|
+
baseStyles,
|
|
11
|
+
parsedJson,
|
|
12
|
+
propertiesOf
|
|
13
|
+
} from "./chunk-EYDMJNNK.js";
|
|
14
|
+
|
|
15
|
+
// src/components/search-results.ts
|
|
16
|
+
import { css, html, nothing } from "lit";
|
|
17
|
+
var SCHEMA = {
|
|
18
|
+
tag: "nymbal-search-results",
|
|
19
|
+
reactName: "SearchResults",
|
|
20
|
+
// The search a server ran: the store's `results` (the card products) and the term they answer, shown until the
|
|
21
|
+
// search store settles.
|
|
22
|
+
attributes: [
|
|
23
|
+
{ name: "results", prop: "results", type: "json" },
|
|
24
|
+
{ name: "query", prop: "query", type: "string" }
|
|
25
|
+
],
|
|
26
|
+
events: [],
|
|
27
|
+
parts: [
|
|
28
|
+
"query",
|
|
29
|
+
"loading",
|
|
30
|
+
"empty",
|
|
31
|
+
"empty-icon",
|
|
32
|
+
"empty-title",
|
|
33
|
+
"empty-text",
|
|
34
|
+
"header",
|
|
35
|
+
"count",
|
|
36
|
+
"grid",
|
|
37
|
+
"pagination",
|
|
38
|
+
"load-more"
|
|
39
|
+
],
|
|
40
|
+
slots: ["no-term", "no-results"],
|
|
41
|
+
children: false
|
|
42
|
+
};
|
|
43
|
+
var NymbalSearchResults = class extends NymbalElement {
|
|
44
|
+
static schema = SCHEMA;
|
|
45
|
+
static uses = [NymbalProductCard];
|
|
46
|
+
static properties = propertiesOf(SCHEMA);
|
|
47
|
+
static styles = [
|
|
48
|
+
baseStyles,
|
|
49
|
+
buttonStyles,
|
|
50
|
+
css`
|
|
51
|
+
:host {
|
|
52
|
+
display: block;
|
|
53
|
+
font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
|
|
54
|
+
color: var(--nymbal-color-text, #18181b);
|
|
55
|
+
}
|
|
56
|
+
[part~='query'] {
|
|
57
|
+
margin: 0 0 var(--nymbal-spacing-md, 1rem);
|
|
58
|
+
color: var(--nymbal-color-text-muted, #71717a);
|
|
59
|
+
}
|
|
60
|
+
[part~='loading'] {
|
|
61
|
+
padding: var(--nymbal-spacing-xl, 2rem) 0;
|
|
62
|
+
text-align: center;
|
|
63
|
+
color: var(--nymbal-color-text-muted, #71717a);
|
|
64
|
+
}
|
|
65
|
+
[part~='empty'] {
|
|
66
|
+
padding: 4rem var(--nymbal-spacing-md, 1rem);
|
|
67
|
+
text-align: center;
|
|
68
|
+
}
|
|
69
|
+
[part~='empty-icon'] {
|
|
70
|
+
font-size: 3rem;
|
|
71
|
+
margin-bottom: var(--nymbal-spacing-md, 1rem);
|
|
72
|
+
opacity: 0.3;
|
|
73
|
+
}
|
|
74
|
+
[part~='empty-title'] {
|
|
75
|
+
margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);
|
|
76
|
+
font-size: var(--nymbal-font-size-lg, 1.125rem);
|
|
77
|
+
font-weight: var(--nymbal-font-weight-medium, 500);
|
|
78
|
+
}
|
|
79
|
+
[part~='empty-text'] {
|
|
80
|
+
max-width: 28ch;
|
|
81
|
+
margin: 0 auto;
|
|
82
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
83
|
+
color: var(--nymbal-color-text-muted, #71717a);
|
|
84
|
+
}
|
|
85
|
+
[part~='header'] {
|
|
86
|
+
margin-bottom: var(--nymbal-spacing-md, 1rem);
|
|
87
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
88
|
+
color: var(--nymbal-color-text-muted, #71717a);
|
|
89
|
+
}
|
|
90
|
+
[part~='grid'] {
|
|
91
|
+
display: grid;
|
|
92
|
+
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
|
93
|
+
gap: var(--nymbal-spacing-lg, 1.5rem);
|
|
94
|
+
}
|
|
95
|
+
[part~='pagination'] {
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
margin-top: var(--nymbal-spacing-xl, 2rem);
|
|
99
|
+
}
|
|
100
|
+
`
|
|
101
|
+
];
|
|
102
|
+
stores() {
|
|
103
|
+
return [this.client.search];
|
|
104
|
+
}
|
|
105
|
+
storeSettled() {
|
|
106
|
+
this.results = void 0;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The results a server gave while they stand (`given`), else the search store's; with neither, no search. Given
|
|
110
|
+
* results are shown whether or not the server named their term.
|
|
111
|
+
*/
|
|
112
|
+
shown() {
|
|
113
|
+
const given = parsedJson(this, "results", this.results);
|
|
114
|
+
if (given === void 0 && this.readsStores) {
|
|
115
|
+
return { ...this.client.search.getState(), given: false };
|
|
116
|
+
}
|
|
117
|
+
const results = given ?? [];
|
|
118
|
+
const query = this.query ?? "";
|
|
119
|
+
return { query, results, total: results.length, nextCursor: null, loading: false, given: true };
|
|
120
|
+
}
|
|
121
|
+
empty(title, text, slot) {
|
|
122
|
+
return html`
|
|
123
|
+
<div part="empty">
|
|
124
|
+
<div part="empty-icon" aria-hidden="true">🔍</div>
|
|
125
|
+
<slot name=${slot}>
|
|
126
|
+
<h2 part="empty-title">${title}</h2>
|
|
127
|
+
<p part="empty-text">${text}</p>
|
|
128
|
+
</slot>
|
|
129
|
+
</div>
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
body(state) {
|
|
133
|
+
const { results, query } = state;
|
|
134
|
+
if (state.loading && results.length === 0) {
|
|
135
|
+
return html`<div part="loading" role="status">Searching...</div>`;
|
|
136
|
+
}
|
|
137
|
+
if (query === "" && !(state.given && results.length > 0)) {
|
|
138
|
+
return this.empty("Search our store", "Type a keyword to find products.", "no-term");
|
|
139
|
+
}
|
|
140
|
+
if (results.length === 0) {
|
|
141
|
+
return this.empty(
|
|
142
|
+
"No results found",
|
|
143
|
+
"Try a different search term or browse our categories.",
|
|
144
|
+
"no-results"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const total = state.total;
|
|
148
|
+
return html`
|
|
149
|
+
<div part="header">
|
|
150
|
+
<span part="count" data-testid="search-total"
|
|
151
|
+
>${total} result${total === 1 ? "" : "s"}</span
|
|
152
|
+
>
|
|
153
|
+
</div>
|
|
154
|
+
<div part="grid">
|
|
155
|
+
${results.map(
|
|
156
|
+
(p) => html`<nymbal-product-card product=${JSON.stringify(p)}></nymbal-product-card>`
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
${state.nextCursor === null ? nothing : html`
|
|
160
|
+
<div part="pagination">
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
part="load-more"
|
|
164
|
+
class="btn btn-secondary"
|
|
165
|
+
data-testid="search-load-more"
|
|
166
|
+
?disabled=${state.loading}
|
|
167
|
+
@click=${() => void this.client.search.loadMore()}
|
|
168
|
+
>
|
|
169
|
+
${state.loading ? "Loading..." : "Load more"}
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
`}
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
render() {
|
|
176
|
+
const state = this.shown();
|
|
177
|
+
const { query } = state;
|
|
178
|
+
return html`
|
|
179
|
+
${query === "" ? nothing : html`<p part="query">Results for <strong>"${query}"</strong></p>`}
|
|
180
|
+
${this.body(state)}
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
NymbalSearchResults
|
|
187
|
+
};
|
|
188
|
+
//# sourceMappingURL=chunk-RAOGZH3Z.js.map
|