@nymbalhq/web-components 1.0.0 → 1.2.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-5VWZ2U5K.js +149 -0
- package/dist/chunk-5VWZ2U5K.js.map +1 -0
- package/dist/chunk-6XCCAAMF.js +656 -0
- package/dist/chunk-6XCCAAMF.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-XE6IWISA.js +137 -0
- package/dist/chunk-XE6IWISA.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 +37 -105
- package/dist/index.js +130 -1098
- 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,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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/search-results.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport type { SearchState } from '@nymbalhq/sdk'\nimport {\n baseStyles,\n NymbalElement,\n parsedJson,\n propertiesOf,\n type ElementSchema,\n} from '../base-element.js'\nimport { buttonStyles } from '../view-styles.js'\nimport { NymbalProductCard } from './product-card.js'\n\ntype Shown = Pick<SearchState, 'query' | 'results' | 'total' | 'nextCursor' | 'loading'> & {\n given: boolean\n}\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-search-results',\n reactName: 'SearchResults',\n // The search a server ran: the store's `results` (the card products) and the term they answer, shown until the\n // search store settles.\n attributes: [\n { name: 'results', prop: 'results', type: 'json' },\n { name: 'query', prop: 'query', type: 'string' },\n ],\n events: [],\n parts: [\n 'query',\n 'loading',\n 'empty',\n 'empty-icon',\n 'empty-title',\n 'empty-text',\n 'header',\n 'count',\n 'grid',\n 'pagination',\n 'load-more',\n ],\n slots: ['no-term', 'no-results'],\n children: false,\n}\n\n/**\n * The search store's results: the term, a grid of `nymbal-product-card`s, the no-term and no-results states, and load\n * more. The page owns the term and the category (the URL and the search input) and searches; this renders the answer.\n */\nexport class NymbalSearchResults extends NymbalElement {\n static override schema = SCHEMA\n static uses = [NymbalProductCard]\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n buttonStyles,\n css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n color: var(--nymbal-color-text, #18181b);\n }\n [part~='query'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='loading'] {\n padding: var(--nymbal-spacing-xl, 2rem) 0;\n text-align: center;\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='empty'] {\n padding: 4rem var(--nymbal-spacing-md, 1rem);\n text-align: center;\n }\n [part~='empty-icon'] {\n font-size: 3rem;\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n opacity: 0.3;\n }\n [part~='empty-title'] {\n margin: 0 0 var(--nymbal-spacing-xs, 0.25rem);\n font-size: var(--nymbal-font-size-lg, 1.125rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n }\n [part~='empty-text'] {\n max-width: 28ch;\n margin: 0 auto;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='header'] {\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-muted, #71717a);\n }\n [part~='grid'] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));\n gap: var(--nymbal-spacing-lg, 1.5rem);\n }\n [part~='pagination'] {\n display: flex;\n justify-content: center;\n margin-top: var(--nymbal-spacing-xl, 2rem);\n }\n `,\n ]\n\n declare results: unknown\n declare query: string | undefined\n\n protected override stores() {\n return [this.client.search]\n }\n\n protected override storeSettled(): void {\n this.results = undefined\n }\n\n /**\n * The results a server gave while they stand (`given`), else the search store's; with neither, no search. Given\n * results are shown whether or not the server named their term.\n */\n private shown(): Shown {\n const given = parsedJson<SearchState['results']>(this, 'results', this.results)\n if (given === undefined && this.readsStores) {\n return { ...this.client.search.getState(), given: false }\n }\n const results = given ?? []\n const query = this.query ?? ''\n return { query, results, total: results.length, nextCursor: null, loading: false, given: true }\n }\n\n private empty(title: string, text: string, slot: string) {\n return html`\n <div part=\"empty\">\n <div part=\"empty-icon\" aria-hidden=\"true\">🔍</div>\n <slot name=${slot}>\n <h2 part=\"empty-title\">${title}</h2>\n <p part=\"empty-text\">${text}</p>\n </slot>\n </div>\n `\n }\n\n private body(state: Shown) {\n const { results, query } = state\n // A load-more or a category change keeps the results shown until its answer replaces them.\n if (state.loading && results.length === 0) {\n return html`<div part=\"loading\" role=\"status\">Searching...</div>`\n }\n if (query === '' && !(state.given && results.length > 0)) {\n return this.empty('Search our store', 'Type a keyword to find products.', 'no-term')\n }\n if (results.length === 0) {\n return this.empty(\n 'No results found',\n 'Try a different search term or browse our categories.',\n 'no-results',\n )\n }\n const total = state.total\n return html`\n <div part=\"header\">\n <span part=\"count\" data-testid=\"search-total\"\n >${total} result${total === 1 ? '' : 's'}</span\n >\n </div>\n <div part=\"grid\">\n ${results.map(\n (p) => html`<nymbal-product-card product=${JSON.stringify(p)}></nymbal-product-card>`,\n )}\n </div>\n ${state.nextCursor === null\n ? nothing\n : html`\n <div part=\"pagination\">\n <button\n type=\"button\"\n part=\"load-more\"\n class=\"btn btn-secondary\"\n data-testid=\"search-load-more\"\n ?disabled=${state.loading}\n @click=${() => void this.client.search.loadMore()}\n >\n ${state.loading ? 'Loading...' : 'Load more'}\n </button>\n </div>\n `}\n `\n }\n\n protected override render() {\n const state = this.shown()\n const { query } = state\n return html`\n ${query === '' ? nothing : html`<p part=\"query\">Results for <strong>\"${query}\"</strong></p>`}\n ${this.body(state)}\n `\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
|
|
3
|
+
// src/format.ts
|
|
4
|
+
function formatPrice(minor, currency) {
|
|
5
|
+
return new Intl.NumberFormat("en-GB", { style: "currency", currency }).format(minor / 100);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
formatPrice
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=chunk-RVSKX2RX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/format.ts"],"sourcesContent":["/** Minor units as the storefronts show money (`en-GB`, the templates' own formatting). */\nexport function formatPrice(minor: number, currency: string): string {\n return new Intl.NumberFormat('en-GB', { style: 'currency', currency }).format(minor / 100)\n}\n"],"mappings":";;;AACO,SAAS,YAAY,OAAe,UAA0B;AACnE,SAAO,IAAI,KAAK,aAAa,SAAS,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,QAAQ,GAAG;AAC3F;","names":[]}
|