@marianmeres/stuic 3.157.0 → 3.158.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/dist/components/Checkout/CheckoutGuestForm.svelte +124 -5
- package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte +4 -0
- package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte.d.ts +1 -1
- package/dist/components/ContactUsForm/ContactUsForm.svelte +107 -10
- package/dist/components/ContactUsForm/README.md +16 -1
- package/dist/components/ContactUsForm/_internal/contact-form-types.d.ts +6 -1
- package/dist/components/LoginForm/LoginForm.svelte +40 -5
- package/dist/components/LoginForm/README.md +34 -19
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte +73 -16
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte.d.ts +12 -1
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterFormModal.svelte +32 -1
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterFormModal.svelte.d.ts +7 -1
- package/dist/components/LoginOrRegisterForm/README.md +45 -29
- package/dist/components/LoginOrRegisterForm/_internal/login-or-register-form-i18n-defaults.js +1 -0
- package/dist/components/LoginOrRegisterForm/index.css +18 -0
- package/dist/components/RegisterForm/README.md +177 -37
- package/dist/components/RegisterForm/RegisterForm.svelte +329 -76
- package/dist/components/RegisterForm/RegisterForm.svelte.d.ts +77 -3
- package/dist/components/RegisterForm/RegisterFormModal.svelte +78 -0
- package/dist/components/RegisterForm/RegisterFormModal.svelte.d.ts +31 -0
- package/dist/components/RegisterForm/_internal/register-form-i18n-defaults.js +1 -0
- package/dist/components/RegisterForm/_internal/register-form-types.d.ts +12 -2
- package/dist/components/RegisterForm/_internal/register-form-utils.d.ts +19 -2
- package/dist/components/RegisterForm/_internal/register-form-utils.js +34 -17
- package/dist/components/RegisterForm/index.css +33 -2
- package/dist/components/RegisterForm/index.d.ts +2 -1
- package/dist/components/RegisterForm/index.js +1 -1
- package/dist/utils/field-errors.svelte.d.ts +114 -0
- package/dist/utils/field-errors.svelte.js +131 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/docs/domains/components.md +54 -38
- package/docs/domains/utils.md +57 -0
- package/package.json +2 -2
- package/dist/components/Input/node_modules/.vite/vitest/d2a04d71301a8915217dd5faf81d12cffd6cd958/_svelte_metadata.json +0 -1
- package/dist/components/Input/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/_svelte_metadata.json +0 -1
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
export type LoginOrRegisterFormMode = "login" | "register" | "verify";
|
|
13
13
|
|
|
14
|
+
// The composite owns these — the inner forms never see them, so passing one
|
|
15
|
+
// through `loginProps` / `registerProps` would type-check and then do nothing.
|
|
16
|
+
// `socialPosition` is in the list for exactly that reason: the shared social
|
|
17
|
+
// block is rendered HERE, so the inner RegisterForm's own copy is inert.
|
|
14
18
|
type InnerPropsCommonOmit =
|
|
15
19
|
| "formData"
|
|
16
20
|
| "onSubmit"
|
|
@@ -18,6 +22,7 @@
|
|
|
18
22
|
| "t"
|
|
19
23
|
| "notifications"
|
|
20
24
|
| "socialLogins"
|
|
25
|
+
| "socialPosition"
|
|
21
26
|
| "socialDividerLabel"
|
|
22
27
|
| "footer";
|
|
23
28
|
|
|
@@ -102,6 +107,17 @@
|
|
|
102
107
|
*/
|
|
103
108
|
socialLogins?: Snippet;
|
|
104
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Where the shared social block sits relative to the active form:
|
|
112
|
+
* - `"bottom"` (default) — below the form, divider ABOVE the buttons
|
|
113
|
+
* - `"top"` — between the mode switcher and the form, divider BELOW them
|
|
114
|
+
*
|
|
115
|
+
* `"top"` suits identity-first signup, where the provider button is an
|
|
116
|
+
* alternative to the credentials rather than to the whole form.
|
|
117
|
+
* Default: "bottom"
|
|
118
|
+
*/
|
|
119
|
+
socialPosition?: "top" | "bottom";
|
|
120
|
+
|
|
105
121
|
/**
|
|
106
122
|
* Override the divider label above social login buttons.
|
|
107
123
|
* Default: i18n key "login_or_register_form.social_divider".
|
|
@@ -177,6 +193,7 @@
|
|
|
177
193
|
loginModeLabel,
|
|
178
194
|
registerModeLabel,
|
|
179
195
|
socialLogins,
|
|
196
|
+
socialPosition = "bottom",
|
|
180
197
|
socialDividerLabel,
|
|
181
198
|
footer,
|
|
182
199
|
notifications,
|
|
@@ -243,6 +260,7 @@
|
|
|
243
260
|
scrollToFirstError?(
|
|
244
261
|
opts?: Parameters<typeof scrollToFirstInvalidField>[1]
|
|
245
262
|
): boolean;
|
|
263
|
+
focusField?(name: string): boolean;
|
|
246
264
|
}
|
|
247
265
|
| undefined {
|
|
248
266
|
if (mode === "login") return loginFormRef;
|
|
@@ -269,8 +287,55 @@
|
|
|
269
287
|
): boolean {
|
|
270
288
|
return _activeForm()?.scrollToFirstError?.(opts) ?? false;
|
|
271
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Focus a field by name on the active inner form (currently only
|
|
293
|
+
* `RegisterForm` implements it). Returns false if the active form doesn't
|
|
294
|
+
* support it or the field is not rendered.
|
|
295
|
+
*/
|
|
296
|
+
export function focusField(name: string): boolean {
|
|
297
|
+
return _activeForm()?.focusField?.(name) ?? false;
|
|
298
|
+
}
|
|
272
299
|
</script>
|
|
273
300
|
|
|
301
|
+
{#snippet socialDivider(position: "top" | "bottom")}
|
|
302
|
+
<div class={unstyled ? undefined : "stuic-login-or-register-form-social-divider"}>
|
|
303
|
+
<span>
|
|
304
|
+
{typeof socialDividerLabel === "string"
|
|
305
|
+
? socialDividerLabel
|
|
306
|
+
: t(
|
|
307
|
+
position === "top"
|
|
308
|
+
? "login_or_register_form.social_divider_alt"
|
|
309
|
+
: "login_or_register_form.social_divider"
|
|
310
|
+
)}
|
|
311
|
+
</span>
|
|
312
|
+
</div>
|
|
313
|
+
{/snippet}
|
|
314
|
+
|
|
315
|
+
<!--
|
|
316
|
+
Shared social logins — hidden in verify mode (OAuth doesn't apply
|
|
317
|
+
mid-verification). Same markup from either position; only the divider flips
|
|
318
|
+
sides so it always faces the form.
|
|
319
|
+
-->
|
|
320
|
+
{#snippet socialBlock(position: "top" | "bottom")}
|
|
321
|
+
{#if socialLogins && mode !== "verify"}
|
|
322
|
+
<div
|
|
323
|
+
class={unstyled ? undefined : "stuic-login-or-register-form-social"}
|
|
324
|
+
data-position={unstyled ? undefined : position}
|
|
325
|
+
>
|
|
326
|
+
{#if socialDividerLabel !== false && position === "bottom"}
|
|
327
|
+
{@render socialDivider(position)}
|
|
328
|
+
{/if}
|
|
329
|
+
<div class={unstyled ? undefined : "stuic-login-or-register-form-social-buttons"}>
|
|
330
|
+
{@render socialLogins()}
|
|
331
|
+
</div>
|
|
332
|
+
{#if socialDividerLabel !== false && position === "top"}
|
|
333
|
+
{@render socialDivider(position)}
|
|
334
|
+
{/if}
|
|
335
|
+
</div>
|
|
336
|
+
{/if}
|
|
337
|
+
{/snippet}
|
|
338
|
+
|
|
274
339
|
{#snippet formContent()}
|
|
275
340
|
<!-- Mode switcher (verify mode is never rendered as a tab — it's an outcome state) -->
|
|
276
341
|
{#if mode !== "verify"}
|
|
@@ -290,6 +355,11 @@
|
|
|
290
355
|
</div>
|
|
291
356
|
{/if}
|
|
292
357
|
|
|
358
|
+
<!-- Shared social logins above the active form -->
|
|
359
|
+
{#if socialPosition === "top"}
|
|
360
|
+
{@render socialBlock("top")}
|
|
361
|
+
{/if}
|
|
362
|
+
|
|
293
363
|
<!-- Active form -->
|
|
294
364
|
<div class={unstyled ? undefined : "stuic-login-or-register-form-body"}>
|
|
295
365
|
{#if mode === "login"}
|
|
@@ -329,22 +399,9 @@
|
|
|
329
399
|
{/if}
|
|
330
400
|
</div>
|
|
331
401
|
|
|
332
|
-
<!-- Shared social logins
|
|
333
|
-
{#if
|
|
334
|
-
|
|
335
|
-
{#if socialDividerLabel !== false}
|
|
336
|
-
<div class={unstyled ? undefined : "stuic-login-or-register-form-social-divider"}>
|
|
337
|
-
<span>
|
|
338
|
-
{typeof socialDividerLabel === "string"
|
|
339
|
-
? socialDividerLabel
|
|
340
|
-
: t("login_or_register_form.social_divider")}
|
|
341
|
-
</span>
|
|
342
|
-
</div>
|
|
343
|
-
{/if}
|
|
344
|
-
<div class={unstyled ? undefined : "stuic-login-or-register-form-social-buttons"}>
|
|
345
|
-
{@render socialLogins()}
|
|
346
|
-
</div>
|
|
347
|
-
</div>
|
|
402
|
+
<!-- Shared social logins below the active form (default) -->
|
|
403
|
+
{#if socialPosition !== "top"}
|
|
404
|
+
{@render socialBlock("bottom")}
|
|
348
405
|
{/if}
|
|
349
406
|
|
|
350
407
|
<!-- Footer -->
|
|
@@ -8,7 +8,7 @@ import type { RegisterFormData } from "../RegisterForm/_internal/register-form-t
|
|
|
8
8
|
import type { Props as EmailVerifyFormProps } from "../EmailVerifyForm/EmailVerifyForm.svelte";
|
|
9
9
|
import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
|
|
10
10
|
export type LoginOrRegisterFormMode = "login" | "register" | "verify";
|
|
11
|
-
type InnerPropsCommonOmit = "formData" | "onSubmit" | "isSubmitting" | "t" | "notifications" | "socialLogins" | "socialDividerLabel" | "footer";
|
|
11
|
+
type InnerPropsCommonOmit = "formData" | "onSubmit" | "isSubmitting" | "t" | "notifications" | "socialLogins" | "socialPosition" | "socialDividerLabel" | "footer";
|
|
12
12
|
export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
13
13
|
/** Bindable active mode. Default: "login" */
|
|
14
14
|
mode?: LoginOrRegisterFormMode;
|
|
@@ -61,6 +61,16 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
|
|
|
61
61
|
* Shared between both modes (OAuth works for either flow).
|
|
62
62
|
*/
|
|
63
63
|
socialLogins?: Snippet;
|
|
64
|
+
/**
|
|
65
|
+
* Where the shared social block sits relative to the active form:
|
|
66
|
+
* - `"bottom"` (default) — below the form, divider ABOVE the buttons
|
|
67
|
+
* - `"top"` — between the mode switcher and the form, divider BELOW them
|
|
68
|
+
*
|
|
69
|
+
* `"top"` suits identity-first signup, where the provider button is an
|
|
70
|
+
* alternative to the credentials rather than to the whole form.
|
|
71
|
+
* Default: "bottom"
|
|
72
|
+
*/
|
|
73
|
+
socialPosition?: "top" | "bottom";
|
|
64
74
|
/**
|
|
65
75
|
* Override the divider label above social login buttons.
|
|
66
76
|
* Default: i18n key "login_or_register_form.social_divider".
|
|
@@ -100,6 +110,7 @@ import type { scrollToFirstInvalidField } from "../../utils/validate-fields.js";
|
|
|
100
110
|
declare const LoginOrRegisterForm: import("svelte").Component<Props, {
|
|
101
111
|
validate: () => boolean;
|
|
102
112
|
scrollToFirstError: (opts?: Parameters<typeof scrollToFirstInvalidField>[1]) => boolean;
|
|
113
|
+
focusField: (name: string) => boolean;
|
|
103
114
|
}, "mode" | "loginData" | "registerData" | "verifyEmail">;
|
|
104
115
|
type LoginOrRegisterForm = ReturnType<typeof LoginOrRegisterForm>;
|
|
105
116
|
export default LoginOrRegisterForm;
|
|
@@ -49,8 +49,10 @@
|
|
|
49
49
|
registerModeLabel?: string;
|
|
50
50
|
verifyModeLabel?: string;
|
|
51
51
|
|
|
52
|
-
/** Shared social logins (rendered below the active form). */
|
|
52
|
+
/** Shared social logins (rendered below the active form by default). */
|
|
53
53
|
socialLogins?: Snippet;
|
|
54
|
+
/** Place the shared social block above the form instead. Default: "bottom" */
|
|
55
|
+
socialPosition?: InnerProps["socialPosition"];
|
|
54
56
|
socialDividerLabel?: string | false;
|
|
55
57
|
|
|
56
58
|
/** Footer snippet. Receives mode + setter. */
|
|
@@ -114,6 +116,7 @@
|
|
|
114
116
|
import { createEmptyRegisterFormData } from "../RegisterForm/_internal/register-form-utils.js";
|
|
115
117
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
116
118
|
import H from "../H/H.svelte";
|
|
119
|
+
import type { scrollToFirstInvalidField } from "../../utils/validate-fields.js";
|
|
117
120
|
|
|
118
121
|
let {
|
|
119
122
|
mode = $bindable("login"),
|
|
@@ -134,6 +137,7 @@
|
|
|
134
137
|
registerModeLabel,
|
|
135
138
|
verifyModeLabel,
|
|
136
139
|
socialLogins,
|
|
140
|
+
socialPosition,
|
|
137
141
|
socialDividerLabel,
|
|
138
142
|
footer,
|
|
139
143
|
notifications,
|
|
@@ -164,6 +168,7 @@
|
|
|
164
168
|
);
|
|
165
169
|
|
|
166
170
|
let modal: Modal = $state()!;
|
|
171
|
+
let form = $state<LoginOrRegisterForm>();
|
|
167
172
|
|
|
168
173
|
export function open(openerOrEvent?: null | HTMLElement | MouseEvent) {
|
|
169
174
|
modal.open(openerOrEvent);
|
|
@@ -173,6 +178,30 @@
|
|
|
173
178
|
modal.close();
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
// Same imperative API the inner composite exposes, forwarded. The form only
|
|
182
|
+
// exists while the modal is open, so all three are no-ops when it is closed.
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Run the active inner form's validators. Returns true if valid — including
|
|
186
|
+
* when the modal is closed and there is no form to ask, so check `visible`
|
|
187
|
+
* first if that distinction matters.
|
|
188
|
+
*/
|
|
189
|
+
export function validate(): boolean {
|
|
190
|
+
return form?.validate() ?? true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Scroll + focus the active form's first invalid field. Call after `validate()`. */
|
|
194
|
+
export function scrollToFirstError(
|
|
195
|
+
opts?: Parameters<typeof scrollToFirstInvalidField>[1]
|
|
196
|
+
): boolean {
|
|
197
|
+
return form?.scrollToFirstError(opts) ?? false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Focus a field by name on the active form (register mode only). */
|
|
201
|
+
export function focusField(name: string): boolean {
|
|
202
|
+
return form?.focusField(name) ?? false;
|
|
203
|
+
}
|
|
204
|
+
|
|
176
205
|
// Reset terminal `verify` state when the modal *closes* — otherwise a
|
|
177
206
|
// register → OTP → logout → re-open cycle resumes on the stale 6-digit-code
|
|
178
207
|
// screen. Keyed on the visible: true → false transition (not a static
|
|
@@ -222,6 +251,7 @@
|
|
|
222
251
|
|
|
223
252
|
<div class="p-6 pt-3">
|
|
224
253
|
<LoginOrRegisterForm
|
|
254
|
+
bind:this={form}
|
|
225
255
|
bind:mode
|
|
226
256
|
bind:loginData
|
|
227
257
|
bind:registerData
|
|
@@ -240,6 +270,7 @@
|
|
|
240
270
|
{registerModeLabel}
|
|
241
271
|
{verifyModeLabel}
|
|
242
272
|
{socialLogins}
|
|
273
|
+
{socialPosition}
|
|
243
274
|
{socialDividerLabel}
|
|
244
275
|
{footer}
|
|
245
276
|
{notifications}
|
|
@@ -32,8 +32,10 @@ export interface Props {
|
|
|
32
32
|
loginModeLabel?: string;
|
|
33
33
|
registerModeLabel?: string;
|
|
34
34
|
verifyModeLabel?: string;
|
|
35
|
-
/** Shared social logins (rendered below the active form). */
|
|
35
|
+
/** Shared social logins (rendered below the active form by default). */
|
|
36
36
|
socialLogins?: Snippet;
|
|
37
|
+
/** Place the shared social block above the form instead. Default: "bottom" */
|
|
38
|
+
socialPosition?: InnerProps["socialPosition"];
|
|
37
39
|
socialDividerLabel?: string | false;
|
|
38
40
|
/** Footer snippet. Receives mode + setter. */
|
|
39
41
|
footer?: InnerProps["footer"];
|
|
@@ -75,9 +77,13 @@ export interface Props {
|
|
|
75
77
|
/** Forwarded to `LoginOrRegisterForm`. Animate content height on mode change. Default: true. */
|
|
76
78
|
animateHeight?: boolean;
|
|
77
79
|
}
|
|
80
|
+
import type { scrollToFirstInvalidField } from "../../utils/validate-fields.js";
|
|
78
81
|
declare const LoginOrRegisterFormModal: import("svelte").Component<Props, {
|
|
79
82
|
open: (openerOrEvent?: null | HTMLElement | MouseEvent) => void;
|
|
80
83
|
close: () => void;
|
|
84
|
+
validate: () => boolean;
|
|
85
|
+
scrollToFirstError: (opts?: Parameters<typeof scrollToFirstInvalidField>[1]) => boolean;
|
|
86
|
+
focusField: (name: string) => boolean;
|
|
81
87
|
}, "visible" | "mode" | "loginData" | "registerData" | "verifyEmail">;
|
|
82
88
|
type LoginOrRegisterFormModal = ReturnType<typeof LoginOrRegisterFormModal>;
|
|
83
89
|
export default LoginOrRegisterFormModal;
|
|
@@ -24,32 +24,45 @@ When the active mode changes, the relevant email is **one-shot copied** to the d
|
|
|
24
24
|
|
|
25
25
|
## LoginOrRegisterForm — Props
|
|
26
26
|
|
|
27
|
-
| Prop | Type | Default
|
|
28
|
-
| -------------------- | ---------------------------------- |
|
|
29
|
-
| `mode` | `LoginOrRegisterFormMode` | `"login"`
|
|
30
|
-
| `loginData` | `LoginFormData` | empty
|
|
31
|
-
| `registerData` | `RegisterFormData` | empty
|
|
32
|
-
| `verifyEmail` | `string` | `""`
|
|
33
|
-
| `onLogin` | `(data: LoginFormData) => void` | required
|
|
34
|
-
| `onRegister` | `(data: RegisterFormData) => void` | required
|
|
35
|
-
| `onVerify` | `(code: string) => void` | -
|
|
36
|
-
| `onResendCode` | `() => Promise<void> \| void` | -
|
|
37
|
-
| `isSubmitting` | `boolean` | `false`
|
|
38
|
-
| `onForgotPassword` | `() => void` | -
|
|
39
|
-
| `loginProps` | `Partial<LoginFormProps>` | -
|
|
40
|
-
| `registerProps` | `Partial<RegisterFormProps>` | -
|
|
41
|
-
| `verifyProps` | `Partial<EmailVerifyFormProps>` | -
|
|
42
|
-
| `modeSwitcher` | `Snippet<[{ mode, setMode, t }]>` | -
|
|
43
|
-
| `loginModeLabel` | `string` | i18n
|
|
44
|
-
| `registerModeLabel` | `string` | i18n
|
|
45
|
-
| `socialLogins` | `Snippet` | -
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
27
|
+
| Prop | Type | Default | Description |
|
|
28
|
+
| -------------------- | ---------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `mode` | `LoginOrRegisterFormMode` | `"login"` | Bindable active mode. |
|
|
30
|
+
| `loginData` | `LoginFormData` | empty | Bindable login form data (forwarded to `LoginForm`). |
|
|
31
|
+
| `registerData` | `RegisterFormData` | empty | Bindable register form data (forwarded to `RegisterForm`). |
|
|
32
|
+
| `verifyEmail` | `string` | `""` | Bindable email used by `EmailVerifyForm` (auto-seeded on transitions). |
|
|
33
|
+
| `onLogin` | `(data: LoginFormData) => void` | required | Login submit callback. |
|
|
34
|
+
| `onRegister` | `(data: RegisterFormData) => void` | required | Register submit callback. |
|
|
35
|
+
| `onVerify` | `(code: string) => void` | - | Verify submit callback (required only when using verify mode). |
|
|
36
|
+
| `onResendCode` | `() => Promise<void> \| void` | - | Resend handler — when set, `EmailVerifyForm` renders the resend control. |
|
|
37
|
+
| `isSubmitting` | `boolean` | `false` | Forwarded to all three forms. |
|
|
38
|
+
| `onForgotPassword` | `() => void` | - | Forgot-password handler for login mode. |
|
|
39
|
+
| `loginProps` | `Partial<LoginFormProps>` | - | Pass-through to the inner `LoginForm`. |
|
|
40
|
+
| `registerProps` | `Partial<RegisterFormProps>` | - | Pass-through to the inner `RegisterForm`. |
|
|
41
|
+
| `verifyProps` | `Partial<EmailVerifyFormProps>` | - | Pass-through to the inner `EmailVerifyForm` (e.g., `error`, `attemptsRemaining`). |
|
|
42
|
+
| `modeSwitcher` | `Snippet<[{ mode, setMode, t }]>` | - | Override the built-in `ButtonGroupRadio` switcher. |
|
|
43
|
+
| `loginModeLabel` | `string` | i18n | Override the "Log in" tab label. |
|
|
44
|
+
| `registerModeLabel` | `string` | i18n | Override the "Sign up" tab label. |
|
|
45
|
+
| `socialLogins` | `Snippet` | - | Shared OAuth buttons rendered once below the active form (hidden in verify mode). |
|
|
46
|
+
| `socialPosition` | `"top" \| "bottom"` | `"bottom"` | `"top"` renders the shared social block between the mode switcher and the form, divider **below** the buttons. |
|
|
47
|
+
| `socialDividerLabel` | `string \| false` | i18n | Override (or hide with `false`) the divider. Defaults to `social_divider` ("or continue with") at the bottom, `social_divider_alt` ("or") at the top. |
|
|
48
|
+
| `footer` | `Snippet<[{ mode, setMode }]>` | - | Mode-aware footer. |
|
|
49
|
+
| `notifications` | `NotificationsStack` | - | Forwarded to inner forms. |
|
|
50
|
+
| `onModeChange` | `(next, prev) => void` | - | Called when the active mode changes. Use to clear parent-owned mode-specific state. |
|
|
51
|
+
| `animateHeight` | `boolean` | `true` | Smoothly animate content height on mode/content change. Respects `prefers-reduced-motion`; no effect when `unstyled`. |
|
|
52
|
+
| `t` | `TranslateFn` | English | i18n function. |
|
|
53
|
+
| `unstyled` / `class` | - | - | Standard styling escape hatches. |
|
|
54
|
+
|
|
55
|
+
> The composite owns the shared social block, so `socialLogins`, `socialPosition`, `socialDividerLabel` (and `formData` / `onSubmit` / `isSubmitting` / `t` / `notifications` / `footer`) are **excluded** from `loginProps` / `registerProps` — passing one there would type-check and then do nothing.
|
|
56
|
+
|
|
57
|
+
### Imperative methods (via `bind:this`)
|
|
58
|
+
|
|
59
|
+
All three delegate to whichever inner form is currently mounted.
|
|
60
|
+
|
|
61
|
+
| Method | Returns | Purpose |
|
|
62
|
+
| --------------------------- | --------- | ------------------------------------------------------------------------------- |
|
|
63
|
+
| `validate()` | `boolean` | Runs the active form's validators. `true` if valid (or nothing is mounted yet). |
|
|
64
|
+
| `scrollToFirstError(opts?)` | `boolean` | Scrolls + focuses the active form's first invalid field. |
|
|
65
|
+
| `focusField(name)` | `boolean` | Focuses a field by name (implemented by `RegisterForm`; `false` elsewhere). |
|
|
53
66
|
|
|
54
67
|
## LoginOrRegisterFormModal — extra props
|
|
55
68
|
|
|
@@ -67,7 +80,7 @@ Inherits all `LoginOrRegisterForm` props, plus:
|
|
|
67
80
|
| `onClose` | `() => false \| void` | - | Pre-close hook. Return `false` to prevent close. |
|
|
68
81
|
| `noClickOutsideClose` | `boolean` | `true` | Disable backdrop-click close (default-on to protect typed credentials). |
|
|
69
82
|
|
|
70
|
-
**Methods:** `open(openerOrEvent?)`, `close()` — exposed via `bind:this`.
|
|
83
|
+
**Methods:** `open(openerOrEvent?)`, `close()`, plus the inner form's `validate()`, `scrollToFirstError(opts?)` and `focusField(name)` — all exposed via `bind:this`. The three forwarded ones are no-ops (`true` / `false` / `false`) while the modal is closed, since no form is mounted then.
|
|
71
84
|
|
|
72
85
|
## Usage
|
|
73
86
|
|
|
@@ -141,12 +154,14 @@ Prefix: `--stuic-login-or-register-form-*`
|
|
|
141
154
|
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
155
|
| `--stuic-login-or-register-form-gap` | Vertical gap |
|
|
143
156
|
| `--stuic-login-or-register-form-switcher-margin-bottom` | Spacing below the mode switcher |
|
|
144
|
-
| `--stuic-login-or-register-form-social-margin-top` | Margin above social block
|
|
157
|
+
| `--stuic-login-or-register-form-social-margin-top` | Margin above social block (default position) |
|
|
158
|
+
| `--stuic-login-or-register-form-social-margin-bottom` | Margin below social block (`socialPosition="top"`) |
|
|
145
159
|
| `--stuic-login-or-register-form-social-gap` | Gap between social buttons |
|
|
146
160
|
| `--stuic-login-or-register-form-social-divider-color` | Divider text color |
|
|
147
161
|
| `--stuic-login-or-register-form-social-divider-line-color` | Divider line color |
|
|
148
162
|
| `--stuic-login-or-register-form-social-divider-font-size` | Divider text size |
|
|
149
|
-
| `--stuic-login-or-register-form-social-divider-margin-bottom` | Divider bottom margin
|
|
163
|
+
| `--stuic-login-or-register-form-social-divider-margin-bottom` | Divider bottom margin (default position) |
|
|
164
|
+
| `--stuic-login-or-register-form-social-divider-margin-top` | Divider top margin (`socialPosition="top"`) |
|
|
150
165
|
| `--stuic-login-or-register-form-height-transition-duration` | Height animation duration (`animateHeight`) |
|
|
151
166
|
| `--stuic-login-or-register-form-height-transition-easing` | Height animation easing (`animateHeight`) |
|
|
152
167
|
| `--stuic-login-or-register-form-height-clip-margin` | `overflow-clip-margin` while the height animates — how far focus rings / borders may paint past the clip edge so they aren't sliced mid-transition. Default `0.5rem`; raise it if inner controls have larger rings/shadows. |
|
|
@@ -162,6 +177,7 @@ Prefix: `--stuic-login-or-register-form-*`
|
|
|
162
177
|
| `login_or_register_form.modal_title_register` | `Create account` |
|
|
163
178
|
| `login_or_register_form.modal_title_verify` | `Verify your email` |
|
|
164
179
|
| `login_or_register_form.social_divider` | `or continue with` |
|
|
180
|
+
| `login_or_register_form.social_divider_alt` | `or` |
|
|
165
181
|
|
|
166
182
|
## See also
|
|
167
183
|
|
package/dist/components/LoginOrRegisterForm/_internal/login-or-register-form-i18n-defaults.js
CHANGED
|
@@ -5,6 +5,7 @@ const DEFAULTS = {
|
|
|
5
5
|
"login_or_register_form.mode_register": "Sign up",
|
|
6
6
|
"login_or_register_form.mode_verify": "Verify",
|
|
7
7
|
"login_or_register_form.social_divider": "or continue with",
|
|
8
|
+
"login_or_register_form.social_divider_alt": "or",
|
|
8
9
|
"login_or_register_form.modal_title_login": "Log In",
|
|
9
10
|
"login_or_register_form.modal_title_register": "Create account",
|
|
10
11
|
"login_or_register_form.modal_title_verify": "Verify your email",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
--stuic-login-or-register-form-social-divider-line-color: var(--stuic-color-border);
|
|
17
17
|
--stuic-login-or-register-form-social-divider-font-size: var(--text-sm);
|
|
18
18
|
--stuic-login-or-register-form-social-divider-margin-bottom: 0.75rem;
|
|
19
|
+
|
|
20
|
+
/* Social login section when placed above the form (socialPosition="top") */
|
|
21
|
+
--stuic-login-or-register-form-social-margin-bottom: 1.5rem;
|
|
22
|
+
--stuic-login-or-register-form-social-divider-margin-top: 0.75rem;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
@layer components {
|
|
@@ -90,4 +94,18 @@
|
|
|
90
94
|
flex-direction: column;
|
|
91
95
|
gap: var(--stuic-login-or-register-form-social-gap);
|
|
92
96
|
}
|
|
97
|
+
|
|
98
|
+
/* socialPosition="top" — the block sits above the form, so the margin and the
|
|
99
|
+
divider both mirror. `[data-position="bottom"]` is intentionally unstyled:
|
|
100
|
+
the default render stays byte-for-byte what it always was. */
|
|
101
|
+
.stuic-login-or-register-form-social[data-position="top"] {
|
|
102
|
+
margin-top: 0;
|
|
103
|
+
margin-bottom: var(--stuic-login-or-register-form-social-margin-bottom);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.stuic-login-or-register-form-social[data-position="top"]
|
|
107
|
+
.stuic-login-or-register-form-social-divider {
|
|
108
|
+
margin-top: var(--stuic-login-or-register-form-social-divider-margin-top);
|
|
109
|
+
margin-bottom: 0;
|
|
110
|
+
}
|
|
93
111
|
}
|