@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.
Files changed (36) hide show
  1. package/dist/components/Checkout/CheckoutGuestForm.svelte +124 -5
  2. package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte +4 -0
  3. package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte.d.ts +1 -1
  4. package/dist/components/ContactUsForm/ContactUsForm.svelte +107 -10
  5. package/dist/components/ContactUsForm/README.md +16 -1
  6. package/dist/components/ContactUsForm/_internal/contact-form-types.d.ts +6 -1
  7. package/dist/components/LoginForm/LoginForm.svelte +40 -5
  8. package/dist/components/LoginForm/README.md +34 -19
  9. package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte +73 -16
  10. package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte.d.ts +12 -1
  11. package/dist/components/LoginOrRegisterForm/LoginOrRegisterFormModal.svelte +32 -1
  12. package/dist/components/LoginOrRegisterForm/LoginOrRegisterFormModal.svelte.d.ts +7 -1
  13. package/dist/components/LoginOrRegisterForm/README.md +45 -29
  14. package/dist/components/LoginOrRegisterForm/_internal/login-or-register-form-i18n-defaults.js +1 -0
  15. package/dist/components/LoginOrRegisterForm/index.css +18 -0
  16. package/dist/components/RegisterForm/README.md +177 -37
  17. package/dist/components/RegisterForm/RegisterForm.svelte +329 -76
  18. package/dist/components/RegisterForm/RegisterForm.svelte.d.ts +77 -3
  19. package/dist/components/RegisterForm/RegisterFormModal.svelte +78 -0
  20. package/dist/components/RegisterForm/RegisterFormModal.svelte.d.ts +31 -0
  21. package/dist/components/RegisterForm/_internal/register-form-i18n-defaults.js +1 -0
  22. package/dist/components/RegisterForm/_internal/register-form-types.d.ts +12 -2
  23. package/dist/components/RegisterForm/_internal/register-form-utils.d.ts +19 -2
  24. package/dist/components/RegisterForm/_internal/register-form-utils.js +34 -17
  25. package/dist/components/RegisterForm/index.css +33 -2
  26. package/dist/components/RegisterForm/index.d.ts +2 -1
  27. package/dist/components/RegisterForm/index.js +1 -1
  28. package/dist/utils/field-errors.svelte.d.ts +114 -0
  29. package/dist/utils/field-errors.svelte.js +131 -0
  30. package/dist/utils/index.d.ts +1 -0
  31. package/dist/utils/index.js +1 -0
  32. package/docs/domains/components.md +54 -38
  33. package/docs/domains/utils.md +57 -0
  34. package/package.json +2 -2
  35. package/dist/components/Input/node_modules/.vite/vitest/d2a04d71301a8915217dd5faf81d12cffd6cd958/_svelte_metadata.json +0 -1
  36. 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 (hidden in verify mode — OAuth doesn't apply mid-verification) -->
333
- {#if socialLogins && mode !== "verify"}
334
- <div class={unstyled ? undefined : "stuic-login-or-register-form-social"}>
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 | 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
- | `socialDividerLabel` | `string \| false` | i18n | Override (or hide with `false`) the divider above social buttons. |
47
- | `footer` | `Snippet<[{ mode, setMode }]>` | - | Mode-aware footer. |
48
- | `notifications` | `NotificationsStack` | - | Forwarded to inner forms. |
49
- | `onModeChange` | `(next, prev) => void` | - | Called when the active mode changes. Use to clear parent-owned mode-specific state. |
50
- | `animateHeight` | `boolean` | `true` | Smoothly animate content height on mode/content change. Respects `prefers-reduced-motion`; no effect when `unstyled`. |
51
- | `t` | `TranslateFn` | English | i18n function. |
52
- | `unstyled` / `class` | - | - | Standard styling escape hatches. |
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
 
@@ -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
  }