@marianmeres/stuic 3.77.1 → 3.79.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.
@@ -5,6 +5,8 @@
5
5
  import type { Props as GuestFormProps } from "./CheckoutGuestForm.svelte";
6
6
  import type { Props as LoginFormProps } from "./CheckoutLoginForm.svelte";
7
7
  import type { Props as LoginFormModalProps } from "../LoginForm/LoginFormModal.svelte";
8
+ import type { Props as LoginOrRegisterFormModalProps } from "../LoginOrRegisterForm/LoginOrRegisterFormModal.svelte";
9
+ import type { LoginOrRegisterFormMode } from "../LoginOrRegisterForm/LoginOrRegisterForm.svelte";
8
10
  import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
9
11
 
10
12
  export type FormMode = "guest-only" | "login-only" | "tabbed" | "stacked";
@@ -41,6 +43,49 @@
41
43
  | "showRememberMe"
42
44
  >;
43
45
 
46
+ /**
47
+ * When provided (and `formMode === "tabbed"`), clicking the login tab opens
48
+ * a `LoginOrRegisterFormModal` instead of rendering `<CheckoutLoginForm>` inline,
49
+ * giving consumers login + register + verify-OTP in a single modal.
50
+ *
51
+ * Mutually exclusive with `loginModal` — if both are provided,
52
+ * `loginOrRegisterModal` wins.
53
+ *
54
+ * `mode` and `verifyEmail` are forwarded one-way (prop → modal); use
55
+ * `onModeChange` to keep consumer-side state in sync. To programmatically
56
+ * flip into verify mode (e.g., on a `requiresVerification` server response),
57
+ * the consumer updates its own `mode` state and the new value flows down.
58
+ */
59
+ loginOrRegisterModal?: Pick<
60
+ LoginOrRegisterFormModalProps,
61
+ | "title"
62
+ | "classModal"
63
+ | "classInner"
64
+ | "classForm"
65
+ | "noXClose"
66
+ | "noClickOutsideClose"
67
+ | "onClose"
68
+ | "mode"
69
+ | "verifyEmail"
70
+ | "onLogin"
71
+ | "onRegister"
72
+ | "onVerify"
73
+ | "onResendCode"
74
+ | "onForgotPassword"
75
+ | "onModeChange"
76
+ | "isSubmitting"
77
+ | "loginProps"
78
+ | "registerProps"
79
+ | "verifyProps"
80
+ | "socialLogins"
81
+ | "socialDividerLabel"
82
+ | "footer"
83
+ | "modeSwitcher"
84
+ | "loginModeLabel"
85
+ | "registerModeLabel"
86
+ | "verifyModeLabel"
87
+ >;
88
+
44
89
  /** Tab label for the guest form tab. Default from i18n. */
45
90
  guestTabLabel?: string;
46
91
 
@@ -73,12 +118,15 @@
73
118
  import CheckoutGuestForm from "./CheckoutGuestForm.svelte";
74
119
  import CheckoutLoginForm from "./CheckoutLoginForm.svelte";
75
120
  import LoginFormModal from "../LoginForm/LoginFormModal.svelte";
76
- import TabbedMenu from "../TabbedMenu/TabbedMenu.svelte";
121
+ import LoginOrRegisterFormModal from "../LoginOrRegisterForm/LoginOrRegisterFormModal.svelte";
122
+ import ButtonGroupRadio from "../ButtonGroupRadio/ButtonGroupRadio.svelte";
77
123
  import { H, type HLevel } from "../H/index.js";
78
124
  import CheckoutSectionHeader from "./CheckoutSectionHeader.svelte";
79
125
 
80
- // Map login_form.* keys checkout.login.* keys (same as CheckoutLoginForm)
81
- const LOGIN_FORM_KEY_MAP: Record<string, string> = {
126
+ // Map login_form.* / register_form.* / email_verify_form.* keys to
127
+ // their checkout.* equivalents so consumers can keep a single i18n prefix.
128
+ const FORM_KEY_MAP: Record<string, string> = {
129
+ // LoginForm
82
130
  "login_form.email_label": "checkout.login.email_label",
83
131
  "login_form.email_placeholder": "checkout.login.email_placeholder",
84
132
  "login_form.password_label": "checkout.login.password_label",
@@ -93,6 +141,46 @@
93
141
  "login_form.remember_me": "checkout.login.remember_me",
94
142
  "login_form.remember_me_tooltip": "checkout.login.remember_me_tooltip",
95
143
  "login_form.modal_title": "checkout.login.modal_title",
144
+ // RegisterForm
145
+ "register_form.email_label": "checkout.register.email_label",
146
+ "register_form.email_placeholder": "checkout.register.email_placeholder",
147
+ "register_form.password_label": "checkout.register.password_label",
148
+ "register_form.password_placeholder": "checkout.register.password_placeholder",
149
+ "register_form.password_confirm_label": "checkout.register.password_confirm_label",
150
+ "register_form.password_confirm_placeholder":
151
+ "checkout.register.password_confirm_placeholder",
152
+ "register_form.submit": "checkout.register.submit",
153
+ "register_form.submitting": "checkout.register.submitting",
154
+ "register_form.email_required": "checkout.register.email_required",
155
+ "register_form.email_invalid": "checkout.register.email_invalid",
156
+ "register_form.password_required": "checkout.register.password_required",
157
+ "register_form.password_too_short": "checkout.register.password_too_short",
158
+ "register_form.password_confirm_required": "checkout.register.password_confirm_required",
159
+ "register_form.password_mismatch": "checkout.register.password_mismatch",
160
+ "register_form.field_required": "checkout.register.field_required",
161
+ "register_form.social_divider": "checkout.register.social_divider",
162
+ "register_form.already_have_account": "checkout.register.already_have_account",
163
+ "register_form.modal_title": "checkout.register.modal_title",
164
+ // EmailVerifyForm
165
+ "email_verify_form.heading": "checkout.verify.heading",
166
+ "email_verify_form.subheading": "checkout.verify.subheading",
167
+ "email_verify_form.submit": "checkout.verify.submit",
168
+ "email_verify_form.submitting": "checkout.verify.submitting",
169
+ "email_verify_form.resend_prompt": "checkout.verify.resend_prompt",
170
+ "email_verify_form.resend": "checkout.verify.resend",
171
+ "email_verify_form.resend_cooldown": "checkout.verify.resend_cooldown",
172
+ "email_verify_form.resent": "checkout.verify.resent",
173
+ "email_verify_form.attempts_remaining": "checkout.verify.attempts_remaining",
174
+ // LoginOrRegisterForm (composite)
175
+ "login_or_register_form.mode_login": "checkout.login_or_register.mode_login",
176
+ "login_or_register_form.mode_register": "checkout.login_or_register.mode_register",
177
+ "login_or_register_form.mode_verify": "checkout.login_or_register.mode_verify",
178
+ "login_or_register_form.social_divider": "checkout.login_or_register.social_divider",
179
+ "login_or_register_form.modal_title_login": "checkout.login_or_register.modal_title_login",
180
+ "login_or_register_form.modal_title_register":
181
+ "checkout.login_or_register.modal_title_register",
182
+ "login_or_register_form.modal_title_verify":
183
+ "checkout.login_or_register.modal_title_verify",
96
184
  };
97
185
 
98
186
  let {
@@ -100,6 +188,7 @@
100
188
  loginForm,
101
189
  formMode = "tabbed",
102
190
  loginModal,
191
+ loginOrRegisterModal,
103
192
  notifications,
104
193
  guestTabLabel,
105
194
  loginTabLabel,
@@ -116,33 +205,58 @@
116
205
 
117
206
  let t = $derived(tProp ?? t_default);
118
207
 
208
+ // `loginOrRegisterModal` wins when both are passed.
209
+ let _useLoginOrRegisterModal = $derived(!!loginOrRegisterModal);
210
+ let _useLoginModal = $derived(!loginOrRegisterModal && !!loginModal);
211
+
212
+ $effect(() => {
213
+ if (loginModal && loginOrRegisterModal) {
214
+ console.warn(
215
+ "[CheckoutGuestOrLoginForm] Both `loginModal` and `loginOrRegisterModal` " +
216
+ "were provided; `loginOrRegisterModal` takes precedence."
217
+ );
218
+ }
219
+ });
220
+
119
221
  let loginModalRef: LoginFormModal = $state()!;
222
+ let loginOrRegisterModalRef: LoginOrRegisterFormModal = $state()!;
120
223
 
121
- // Adapted t for LoginFormModal (maps login_form.* checkout.login.*)
224
+ // Adapted t for the modals — maps login_form.* / register_form.* /
225
+ // email_verify_form.* / login_or_register_form.* keys to their checkout.* equivalents.
122
226
  let modalT = $derived(
123
- loginModal
227
+ loginModal || loginOrRegisterModal
124
228
  ? (
125
229
  key: string,
126
230
  values?: false | null | undefined | Record<string, string | number>,
127
231
  fallback?: string | boolean
128
- ) => t(LOGIN_FORM_KEY_MAP[key] ?? key, values, fallback)
232
+ ) => t(FORM_KEY_MAP[key] ?? key, values, fallback)
129
233
  : undefined
130
234
  );
131
235
 
132
- let tabItems = $derived([
133
- { id: "guest", label: guestTabLabel ?? t("checkout.guest_or_login.guest_tab") },
134
- {
135
- id: "login",
136
- label: loginTabLabel ?? t("checkout.guest_or_login.login_tab"),
137
- ...(loginModal
138
- ? {
139
- onSelect: () => {
140
- loginModalRef?.open();
141
- return false;
142
- },
143
- }
144
- : {}),
145
- },
236
+ // Local mirrors of the bindable LoginOrRegisterFormModal state — kept in sync
237
+ // from the consumer-supplied values via $effect, so consumer-driven updates
238
+ // (e.g., flipping `mode = "verify"` after a `requiresVerification` response)
239
+ // flow down. Modal-driven changes are forwarded back via `onModeChange`.
240
+ let _loroMode: LoginOrRegisterFormMode = $state("login");
241
+ let _loroVerifyEmail = $state("");
242
+
243
+ $effect(() => {
244
+ if (loginOrRegisterModal?.mode !== undefined && loginOrRegisterModal.mode !== _loroMode) {
245
+ _loroMode = loginOrRegisterModal.mode;
246
+ }
247
+ });
248
+ $effect(() => {
249
+ if (
250
+ loginOrRegisterModal?.verifyEmail !== undefined &&
251
+ loginOrRegisterModal.verifyEmail !== _loroVerifyEmail
252
+ ) {
253
+ _loroVerifyEmail = loginOrRegisterModal.verifyEmail;
254
+ }
255
+ });
256
+
257
+ let tabOptions = $derived([
258
+ { value: "guest", label: guestTabLabel ?? t("checkout.guest_or_login.guest_tab") },
259
+ { value: "login", label: loginTabLabel ?? t("checkout.guest_or_login.login_tab") },
146
260
  ]);
147
261
 
148
262
  let _class = $derived(
@@ -173,23 +287,26 @@
173
287
  <CheckoutLoginForm {...loginForm} {notifications} t={tProp} {unstyled} />
174
288
  {/if}
175
289
  {:else if formMode === "tabbed"}
176
- <TabbedMenu
177
- items={tabItems}
178
- value={activeTab}
179
- onSelect={(item) => {
180
- activeTab = item.id as "guest" | "login";
181
- }}
182
- {unstyled}
290
+ <ButtonGroupRadio
291
+ options={tabOptions}
292
+ bind:value={activeTab as string}
183
293
  class={unstyled ? undefined : "stuic-checkout-guest-or-login-tabs"}
294
+ onButtonClick={(index) => {
295
+ if (tabOptions[index]?.value !== "login") return;
296
+ if (_useLoginOrRegisterModal) {
297
+ loginOrRegisterModalRef?.open();
298
+ return false;
299
+ }
300
+ if (_useLoginModal) {
301
+ loginModalRef?.open();
302
+ return false;
303
+ }
304
+ }}
184
305
  />
185
306
  {#if activeTab === "guest" && guestForm}
186
- <div role="tabpanel">
187
- <CheckoutGuestForm {...guestForm} {notifications} t={tProp} {unstyled} />
188
- </div>
189
- {:else if activeTab === "login" && loginForm}
190
- <div role="tabpanel">
191
- <CheckoutLoginForm {...loginForm} {notifications} t={tProp} {unstyled} />
192
- </div>
307
+ <CheckoutGuestForm {...guestForm} {notifications} t={tProp} {unstyled} />
308
+ {:else if activeTab === "login" && loginForm && !_useLoginOrRegisterModal && !_useLoginModal}
309
+ <CheckoutLoginForm {...loginForm} {notifications} t={tProp} {unstyled} />
193
310
  {/if}
194
311
  {:else if formMode === "stacked"}
195
312
  {#if loginForm}
@@ -203,7 +320,7 @@
203
320
  {/if}
204
321
  {/if}
205
322
 
206
- {#if loginModal && loginForm}
323
+ {#if _useLoginModal && loginModal && loginForm}
207
324
  <LoginFormModal
208
325
  bind:this={loginModalRef}
209
326
  formData={loginForm.formData}
@@ -225,4 +342,41 @@
225
342
  {...loginModal}
226
343
  />
227
344
  {/if}
345
+
346
+ {#if _useLoginOrRegisterModal && loginOrRegisterModal}
347
+ <LoginOrRegisterFormModal
348
+ bind:this={loginOrRegisterModalRef}
349
+ bind:mode={_loroMode}
350
+ bind:verifyEmail={_loroVerifyEmail}
351
+ onLogin={loginOrRegisterModal.onLogin}
352
+ onRegister={loginOrRegisterModal.onRegister}
353
+ onVerify={loginOrRegisterModal.onVerify}
354
+ onResendCode={loginOrRegisterModal.onResendCode}
355
+ onForgotPassword={loginOrRegisterModal.onForgotPassword}
356
+ onModeChange={(next, prev) => {
357
+ loginOrRegisterModal!.onModeChange?.(next, prev);
358
+ }}
359
+ isSubmitting={loginOrRegisterModal.isSubmitting}
360
+ loginProps={loginOrRegisterModal.loginProps}
361
+ registerProps={loginOrRegisterModal.registerProps}
362
+ verifyProps={loginOrRegisterModal.verifyProps}
363
+ modeSwitcher={loginOrRegisterModal.modeSwitcher}
364
+ loginModeLabel={loginOrRegisterModal.loginModeLabel}
365
+ registerModeLabel={loginOrRegisterModal.registerModeLabel}
366
+ verifyModeLabel={loginOrRegisterModal.verifyModeLabel}
367
+ socialLogins={loginOrRegisterModal.socialLogins}
368
+ socialDividerLabel={loginOrRegisterModal.socialDividerLabel}
369
+ footer={loginOrRegisterModal.footer}
370
+ {notifications}
371
+ title={loginOrRegisterModal.title}
372
+ classModal={loginOrRegisterModal.classModal}
373
+ classInner={loginOrRegisterModal.classInner}
374
+ classForm={loginOrRegisterModal.classForm}
375
+ noXClose={loginOrRegisterModal.noXClose}
376
+ noClickOutsideClose={loginOrRegisterModal.noClickOutsideClose}
377
+ onClose={loginOrRegisterModal.onClose}
378
+ t={modalT}
379
+ {unstyled}
380
+ />
381
+ {/if}
228
382
  </div>
@@ -4,6 +4,7 @@ import type { TranslateFn } from "../../types.js";
4
4
  import type { Props as GuestFormProps } from "./CheckoutGuestForm.svelte";
5
5
  import type { Props as LoginFormProps } from "./CheckoutLoginForm.svelte";
6
6
  import type { Props as LoginFormModalProps } from "../LoginForm/LoginFormModal.svelte";
7
+ import type { Props as LoginOrRegisterFormModalProps } from "../LoginOrRegisterForm/LoginOrRegisterFormModal.svelte";
7
8
  import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
8
9
  export type FormMode = "guest-only" | "login-only" | "tabbed" | "stacked";
9
10
  export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
@@ -25,6 +26,20 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
25
26
  * Form-related props are taken from loginForm.
26
27
  */
27
28
  loginModal?: Pick<LoginFormModalProps, "title" | "classModal" | "classInner" | "classForm" | "noXClose" | "onClose" | "showRememberMe">;
29
+ /**
30
+ * When provided (and `formMode === "tabbed"`), clicking the login tab opens
31
+ * a `LoginOrRegisterFormModal` instead of rendering `<CheckoutLoginForm>` inline,
32
+ * giving consumers login + register + verify-OTP in a single modal.
33
+ *
34
+ * Mutually exclusive with `loginModal` — if both are provided,
35
+ * `loginOrRegisterModal` wins.
36
+ *
37
+ * `mode` and `verifyEmail` are forwarded one-way (prop → modal); use
38
+ * `onModeChange` to keep consumer-side state in sync. To programmatically
39
+ * flip into verify mode (e.g., on a `requiresVerification` server response),
40
+ * the consumer updates its own `mode` state and the new value flows down.
41
+ */
42
+ loginOrRegisterModal?: Pick<LoginOrRegisterFormModalProps, "title" | "classModal" | "classInner" | "classForm" | "noXClose" | "noClickOutsideClose" | "onClose" | "mode" | "verifyEmail" | "onLogin" | "onRegister" | "onVerify" | "onResendCode" | "onForgotPassword" | "onModeChange" | "isSubmitting" | "loginProps" | "registerProps" | "verifyProps" | "socialLogins" | "socialDividerLabel" | "footer" | "modeSwitcher" | "loginModeLabel" | "registerModeLabel" | "verifyModeLabel">;
28
43
  /** Tab label for the guest form tab. Default from i18n. */
29
44
  guestTabLabel?: string;
30
45
  /** Tab label for the login form tab. Default from i18n. */
@@ -26,7 +26,7 @@ Each step container renders a `CheckoutProgress` indicator (unless `hideProgress
26
26
  | `CheckoutOrderSummary` | Totals (subtotal/tax/shipping/discount/total) |
27
27
  | `CheckoutOrderReview` | Read-only order dump (items + addresses + delivery) |
28
28
  | `CheckoutOrderConfirmation` | Completed-order summary with order number & next steps |
29
- | `CheckoutGuestOrLoginForm` | Tabbed guest / login |
29
+ | `CheckoutGuestOrLoginForm` | Guest / login switcher (segmented pill) |
30
30
  | `CheckoutGuestForm` | Guest-checkout fields |
31
31
  | `CheckoutLoginForm` | Login (adapts the generic `LoginForm` to checkout i18n) |
32
32
  | `CheckoutAddressForm` | Structured address input |
@@ -139,11 +139,89 @@ Every component accepts an optional `t?: TranslateFn` prop. Sensible English def
139
139
 
140
140
  `CheckoutLoginForm` internally bridges `checkout.login.*` keys to the generic `LoginForm` component's `login_form.*` keys, so you only need one consistent prefix.
141
141
 
142
+ When `CheckoutGuestOrLoginForm` is wired to `LoginOrRegisterFormModal` via the optional `loginOrRegisterModal` prop, the same bridging is applied to `register_form.*` (→ `checkout.register.*`), `email_verify_form.*` (→ `checkout.verify.*`), and `login_or_register_form.*` (→ `checkout.login_or_register.*`) — so the entire login + register + verify flow stays under the `checkout.*` prefix.
143
+
144
+ ## Login + register + verify in checkout
145
+
146
+ By default `CheckoutGuestOrLoginForm` in tabbed mode renders an inline `<CheckoutLoginForm>` in the login tab. For apps with self-registration, pass `loginOrRegisterModal` to wire the login tab to a `LoginOrRegisterFormModal` instead — giving you login, register, and post-register OTP verification in a single modal:
147
+
148
+ ```svelte
149
+ <script lang="ts">
150
+ import {
151
+ CheckoutGuestOrLoginForm,
152
+ createEmptyCustomerFormData,
153
+ createEmptyLoginFormData,
154
+ type LoginOrRegisterFormMode,
155
+ type LoginFormData,
156
+ type RegisterFormData,
157
+ } from "@marianmeres/stuic";
158
+
159
+ let formData = $state(createEmptyCustomerFormData());
160
+ let loginFormData = $state(createEmptyLoginFormData());
161
+
162
+ let mode = $state<LoginOrRegisterFormMode>("login");
163
+ let verifyEmail = $state("");
164
+ let isSubmitting = $state(false);
165
+ let formError = $state<string | null>(null);
166
+
167
+ const loginProps = $derived({ error: formError ?? undefined, showRememberMe: true });
168
+ const registerProps = $derived({ error: formError ?? undefined });
169
+ const verifyProps = $derived({ error: formError ?? undefined, heading: false as const });
170
+
171
+ async function onLogin(d: LoginFormData) {
172
+ // ... call API; on `requiresVerification`, flip:
173
+ // verifyEmail = d.email; mode = "verify";
174
+ }
175
+ async function onRegister(d: RegisterFormData) {
176
+ // ... call API; on success, flip to verify:
177
+ // verifyEmail = d.email; mode = "verify";
178
+ }
179
+ async function onVerify(code: string) {
180
+ // ... call API; on success, modal closes via consumer-managed state.
181
+ }
182
+ async function onResendCode() { /* ... */ }
183
+ </script>
184
+
185
+ <CheckoutGuestOrLoginForm
186
+ formMode="tabbed"
187
+ guestForm={{ formData, onSubmit: handleStartCheckout, isSubmitting, errors: [] }}
188
+ loginForm={{ formData: loginFormData, onSubmit: onLogin, isSubmitting }}
189
+ loginOrRegisterModal={{
190
+ mode,
191
+ verifyEmail,
192
+ onLogin,
193
+ onRegister,
194
+ onVerify,
195
+ onResendCode,
196
+ onForgotPassword: () => {/* ... */},
197
+ onModeChange: (next) => {
198
+ // mirror mode changes back into our local state and clear errors
199
+ mode = next;
200
+ formError = null;
201
+ },
202
+ isSubmitting,
203
+ loginProps,
204
+ registerProps,
205
+ verifyProps,
206
+ onClose: () => {
207
+ formError = null;
208
+ mode = "login";
209
+ },
210
+ }}
211
+ />
212
+ ```
213
+
214
+ **State sync.** `mode` and `verifyEmail` flow one-way from prop into the modal — programmatically flipping `mode = "verify"` (e.g., on a `requiresVerification` server response) updates the modal. To observe modal-driven changes (user clicks the "Sign up" tab, etc.), wire `onModeChange` and update your local state there.
215
+
216
+ **Precedence.** `loginOrRegisterModal` takes precedence over `loginModal`. If both are passed, only `loginOrRegisterModal` is wired up (and a dev-mode `console.warn` fires).
217
+
218
+ **i18n.** All `register_form.*` / `email_verify_form.*` / `login_or_register_form.*` keys are bridged to `checkout.register.*` / `checkout.verify.*` / `checkout.login_or_register.*` respectively, so a single `t` function with a unified `checkout.*` prefix covers the full flow.
219
+
142
220
  ## Accessibility
143
221
 
144
222
  - `CheckoutProgress` renders past/current/future steps with `aria-current="step"` on the active step.
145
223
  - Form submissions do **not** automatically move focus to the first error field. Consumers wanting this behavior should do it in their `onContinue` handler after receiving validation errors.
146
- - `CheckoutGuestOrLoginForm` uses the underlying `TabbedMenu` semantics; focus does not auto-move to the tab-panel heading on tab switch.
224
+ - `CheckoutGuestOrLoginForm` uses `ButtonGroupRadio` (`role="radiogroup"`) for the guest/login switch; focus does not auto-move to the panel heading on switch.
147
225
 
148
226
  ## Address equality (advanced)
149
227
 
@@ -3,10 +3,6 @@
3
3
  --stuic-checkout-guest-or-login-gap: 0.25rem;
4
4
  --stuic-checkout-guest-or-login-divider-color: var(--stuic-color-border);
5
5
  --stuic-checkout-guest-or-login-tabs-margin-bottom: 1.5rem;
6
- --stuic-checkout-guest-or-login-tabs-background-color: var(--stuic-color-muted);
7
- --stuic-checkout-guest-or-login-tabs-active-background-color: var(
8
- --stuic-color-muted-foreground
9
- );
10
6
  }
11
7
 
12
8
  @layer components {
@@ -16,32 +12,11 @@
16
12
  gap: var(--stuic-checkout-guest-or-login-gap);
17
13
  }
18
14
 
19
- /* Pill-styled tabs via TabbedMenu CSS variable overrides */
15
+ /* Spacing below the guest/login switcher (ButtonGroupRadio) */
20
16
  .stuic-checkout-guest-or-login-tabs {
21
- --stuic-tabbed-menu-radius: 9999px;
22
- --stuic-tabbed-menu-item-max-width: none;
23
- background-color: var(--stuic-checkout-guest-or-login-tabs-background-color);
24
- }
25
-
26
- .stuic-checkout-guest-or-login-tabs.stuic-tabbed-menu {
27
- padding: 3px;
28
- border: 1px solid var(--stuic-checkout-guest-or-login-divider-color);
29
- border-radius: 9999px;
30
17
  margin-bottom: var(--stuic-checkout-guest-or-login-tabs-margin-bottom);
31
18
  }
32
19
 
33
- /* Override TabbedMenu's top-only border-radius to full pill shape */
34
- .stuic-checkout-guest-or-login-tabs .stuic-tabbed-menu-tab {
35
- border-radius: 9999px;
36
- background-color: var(--stuic-checkout-guest-or-login-tabs-background-color);
37
- border: 0;
38
- }
39
-
40
- .stuic-checkout-guest-or-login-tabs .stuic-tabbed-menu-tab[aria-selected="true"] {
41
- border-color: var(--stuic-tabbed-menu-border-active);
42
- background-color: var(--stuic-checkout-guest-or-login-tabs-active-background-color);
43
- }
44
-
45
20
  /* Divider (stacked mode) */
46
21
  .stuic-checkout-guest-or-login-divider {
47
22
  display: flex;
@@ -98,6 +98,45 @@ const DEFAULTS = {
98
98
  "checkout.complete.delivery_label": "Delivery",
99
99
  "checkout.complete.totals_title": "Order Total",
100
100
  "checkout.complete.continue_shopping": "Continue Shopping",
101
+ // -- Register (mirrors register_form.*; surfaced via CheckoutGuestOrLoginForm
102
+ // when the optional `loginOrRegisterModal` is wired up) --
103
+ "checkout.register.email_label": "Email",
104
+ "checkout.register.email_placeholder": "you@example.com",
105
+ "checkout.register.password_label": "Password",
106
+ "checkout.register.password_placeholder": "",
107
+ "checkout.register.password_confirm_label": "Confirm password",
108
+ "checkout.register.password_confirm_placeholder": "",
109
+ "checkout.register.submit": "Create account",
110
+ "checkout.register.submitting": "Creating account...",
111
+ "checkout.register.email_required": "Email is required",
112
+ "checkout.register.email_invalid": "Please enter a valid email address",
113
+ "checkout.register.password_required": "Password is required",
114
+ "checkout.register.password_too_short": "Password must be at least {min} characters",
115
+ "checkout.register.password_confirm_required": "Please confirm your password",
116
+ "checkout.register.password_mismatch": "Passwords do not match",
117
+ "checkout.register.field_required": "{label} is required",
118
+ "checkout.register.social_divider": "or continue with",
119
+ "checkout.register.already_have_account": "Already have an account?",
120
+ "checkout.register.modal_title": "Create account",
121
+ // -- Verify (mirrors email_verify_form.*; surfaced via CheckoutGuestOrLoginForm
122
+ // when the optional `loginOrRegisterModal` is wired up) --
123
+ "checkout.verify.heading": "Check your email",
124
+ "checkout.verify.subheading": "We sent a 6-digit code to {email}",
125
+ "checkout.verify.submit": "Verify",
126
+ "checkout.verify.submitting": "Verifying...",
127
+ "checkout.verify.resend_prompt": "Didn't receive it?",
128
+ "checkout.verify.resend": "Resend code",
129
+ "checkout.verify.resend_cooldown": "Resend available in {seconds}s",
130
+ "checkout.verify.resent": "New code sent",
131
+ "checkout.verify.attempts_remaining": "{count} attempts remaining",
132
+ // -- LoginOrRegister composite (mirrors login_or_register_form.*) --
133
+ "checkout.login_or_register.mode_login": "Log in",
134
+ "checkout.login_or_register.mode_register": "Sign up",
135
+ "checkout.login_or_register.mode_verify": "Verify",
136
+ "checkout.login_or_register.social_divider": "or continue with",
137
+ "checkout.login_or_register.modal_title_login": "Log In",
138
+ "checkout.login_or_register.modal_title_register": "Create account",
139
+ "checkout.login_or_register.modal_title_verify": "Verify your email",
101
140
  // -- CheckoutGuestOrLoginForm (composite) --
102
141
  "checkout.guest_or_login.guest_tab": "Guest",
103
142
  "checkout.guest_or_login.login_tab": "Log In",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.77.1",
3
+ "version": "3.79.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",