@marianmeres/stuic 3.159.0 → 3.161.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.
@@ -58,6 +58,7 @@ interface RegisterFieldConfig {
58
58
  | `passwordConfirmFieldProps` | `Partial<FieldInputProps>` | - | Passthrough props for the built-in confirm field. |
59
59
  | `extraFields` | `RegisterFieldConfig[]` | `[]` | Declarative extra fields. Rendered as `FieldInput`s positioned top or bottom. |
60
60
  | `extraFieldsSlot` | `Snippet<[{ formData, fieldError }]>` | - | Escape hatch for non-FieldInput extras. Rendered after declarative bottom fields. |
61
+ | `topFieldsSeparator` | `boolean` | `true` | Section rule closing the top-position extra-field group. Drawn whenever at least one such field exists, whatever follows it. `false` opts out. |
61
62
  | `submitLabel` | `string` | i18n | Override the CTA label. |
62
63
  | `submittingLabel` | `string` | i18n | Override the CTA label while submitting. |
63
64
  | `submitButton` | `Snippet<[{ isSubmitting, disabled }]>` | - | Override the entire CTA section. `disabled` is `isSubmitting \|\| submitDisabled`. |
@@ -82,7 +83,7 @@ interface RegisterFieldConfig {
82
83
 
83
84
  ```
84
85
  general error alert (`error`)
85
- top-position extra fields
86
+ top-position extra fields ← wrapped as a group, optionally closed with a section rule
86
87
  social block ← only when socialPosition="top" (divider BELOW the buttons)
87
88
  email / password / confirm ← each present only if its show* prop is true
88
89
  credentialsSlot
@@ -214,6 +215,8 @@ Once an external party has confirmed who the user is, the credential fields are
214
215
 
215
216
  Call `form.focusField("tenant_id")` right after the provider confirms: the button the user clicked is about to unmount, and without an explicit move focus falls to `<body>` — a keyboard user's next Tab restarts at the top of the document.
216
217
 
218
+ Those top-position fields are closed off with a section rule. At the form's ordinary field rhythm the last of them sits one field-gap above the first provider button and reads as a caption for it; the break says instead that the workspace id is settled and what follows is the account. The rule is not conditioned on what comes next — provider buttons, a `credentialsSlot`, or the plain credentials all get it — so it does not blink in and out as this flow moves from "pick a provider" to "signing up as jane@…", and a reader of the call site does not have to work out which combination of other props switched it on. `topFieldsSeparator={false}` opts out.
219
+
217
220
  Other shapes the same three props cover:
218
221
 
219
222
  | Flow | Props |
@@ -299,11 +302,16 @@ Prefix: `--stuic-register-form-*`
299
302
  | `--stuic-register-form-social-divider-margin-top` | Divider top margin (`socialPosition="top"`) |
300
303
  | `--stuic-register-form-credentials-margin-bottom` | Bottom margin of the `credentialsSlot` wrapper |
301
304
  | `--stuic-register-form-field-margin-bottom` | Bottom margin of each field inside the form |
305
+ | `--stuic-register-form-fields-top-padding-bottom` | Space added above the top-fields section rule |
306
+ | `--stuic-register-form-fields-top-margin-bottom` | Space below the top-fields section rule |
307
+ | `--stuic-register-form-fields-top-border-color` | Color of the top-fields section rule |
302
308
 
303
309
  The social block carries `data-position="top" \| "bottom"` (suppressed under `unstyled`) if you want to target either placement from your own CSS. Note that the `top` variant hard-resets `margin-top` to `0`, so `--stuic-register-form-social-margin-top` applies to the default position only.
304
310
 
305
311
  `credentialsSlot` content is wrapped in `.stuic-register-form-credentials` (suppressed under `unstyled`) so it inherits the same bottom rhythm the fields have — the form itself is a zero-gap flex column.
306
312
 
313
+ Top-position extra fields are wrapped in `.stuic-register-form-fields-top` (suppressed under `unstyled`), which carries `data-separator` unless `topFieldsSeparator={false}`. The wrapper is otherwise inert — the fields keep their own margins — so targeting `[data-separator]` is the way to restyle the break without touching the ungrouped case. The default padding stacks on top of the last field's own `margin-bottom`, which is why the two spacing tokens are not equal.
314
+
307
315
  ## Gotchas
308
316
 
309
317
  - **Empty social slot.** `socialLogins` is gated on the _snippet being passed_, not on it rendering anything. A consumer that passes a placeholder snippet while it discovers which providers exist gets the wrapper margins and a divider around nothing — pass `socialLogins={undefined}` until you know, or reserve the height inside the snippet and pass `socialDividerLabel={false}`.
@@ -124,6 +124,23 @@
124
124
  ]
125
125
  >;
126
126
 
127
+ /**
128
+ * Close the top-position extra fields with a section break — a hairline
129
+ * rule plus extra space below the group.
130
+ *
131
+ * A field is at the top position because it is about something other than
132
+ * the credentials (a workspace id, an invite code, an org name), so the
133
+ * group is drawn as its own section. Without the break the last of them
134
+ * sits at the ordinary field gap above whatever comes next and reads as a
135
+ * caption for it — a label over the first provider button, or just another
136
+ * row of the credential column.
137
+ *
138
+ * Default: true whenever there is at least one top-position field,
139
+ * regardless of what follows. Set `false` for a form whose top fields
140
+ * really do belong to the same column as the credentials.
141
+ */
142
+ topFieldsSeparator?: boolean;
143
+
127
144
  /** Override CTA label */
128
145
  submitLabel?: string;
129
146
 
@@ -217,6 +234,7 @@
217
234
  passwordConfirmFieldProps,
218
235
  extraFields = [],
219
236
  extraFieldsSlot,
237
+ topFieldsSeparator,
220
238
  submitLabel,
221
239
  submittingLabel,
222
240
  submitButton,
@@ -249,6 +267,16 @@
249
267
  // validated against it).
250
268
  let renderPasswordConfirm = $derived(showPassword && showPasswordConfirm);
251
269
 
270
+ // A field at the top position is there because it is about something other
271
+ // than the credentials — the workspace, the invite, the org — so it is closed
272
+ // off as its own section whichever way the sign-up continues below it.
273
+ // Deliberately NOT conditioned on what follows (provider buttons, a
274
+ // credentialsSlot, plain inputs): the last top field otherwise sits one field
275
+ // gap above the next thing and reads as a caption for it either way, and a
276
+ // rule that comes and goes with an unrelated prop is not explicable from the
277
+ // call site. `topFieldsSeparator={false}` opts out.
278
+ let renderTopFieldsSeparator = $derived(topFieldsSeparator ?? true);
279
+
252
280
  // Internal validation errors (set on submit)
253
281
  let internalErrors = $state<RegisterFormValidationError[]>([]);
254
282
 
@@ -511,28 +539,37 @@
511
539
  <!-- General error alert -->
512
540
  <DismissibleMessage message={error} intent="destructive" />
513
541
 
514
- <!-- Top-position extra fields -->
515
- {#each topFields as cfg (cfg.name)}
516
- <FieldInput
517
- bind:this={extraFieldRefs[cfg.name]}
518
- value={extraValue(cfg)}
519
- oninput={(e: Event) =>
520
- setExtraValue(cfg, (e.currentTarget as HTMLInputElement).value)}
521
- label={cfg.label}
522
- type={cfg.type ?? "text"}
523
- placeholder={cfg.placeholder}
524
- autocomplete={cfg.autocomplete}
525
- required={cfg.required}
526
- name={`register-extra-${cfg.name}`}
527
- labelLeftBreakpoint={0}
528
- validate={{
529
- customValidator() {
530
- return fieldError(cfg.name) || "";
531
- },
532
- }}
533
- {...cfg.props}
534
- />
535
- {/each}
542
+ <!-- Top-position extra fields. Wrapped as a group so `topFieldsSeparator` can
543
+ close it off from the sign-up choice below — the wrapper is inert without
544
+ `data-separator`, the fields keep their own rhythm. -->
545
+ {#if topFields.length}
546
+ <div
547
+ class={unstyled ? undefined : "stuic-register-form-fields-top"}
548
+ data-separator={unstyled || !renderTopFieldsSeparator ? undefined : ""}
549
+ >
550
+ {#each topFields as cfg (cfg.name)}
551
+ <FieldInput
552
+ bind:this={extraFieldRefs[cfg.name]}
553
+ value={extraValue(cfg)}
554
+ oninput={(e: Event) =>
555
+ setExtraValue(cfg, (e.currentTarget as HTMLInputElement).value)}
556
+ label={cfg.label}
557
+ type={cfg.type ?? "text"}
558
+ placeholder={cfg.placeholder}
559
+ autocomplete={cfg.autocomplete}
560
+ required={cfg.required}
561
+ name={`register-extra-${cfg.name}`}
562
+ labelLeftBreakpoint={0}
563
+ validate={{
564
+ customValidator() {
565
+ return fieldError(cfg.name) || "";
566
+ },
567
+ }}
568
+ {...cfg.props}
569
+ />
570
+ {/each}
571
+ </div>
572
+ {/if}
536
573
 
537
574
  <!-- Social logins above the credentials -->
538
575
  {#if socialPosition === "top"}
@@ -97,6 +97,22 @@ export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children">
97
97
  fieldError: (name: string) => string | undefined;
98
98
  }
99
99
  ]>;
100
+ /**
101
+ * Close the top-position extra fields with a section break — a hairline
102
+ * rule plus extra space below the group.
103
+ *
104
+ * A field is at the top position because it is about something other than
105
+ * the credentials (a workspace id, an invite code, an org name), so the
106
+ * group is drawn as its own section. Without the break the last of them
107
+ * sits at the ordinary field gap above whatever comes next and reads as a
108
+ * caption for it — a label over the first provider button, or just another
109
+ * row of the credential column.
110
+ *
111
+ * Default: true whenever there is at least one top-position field,
112
+ * regardless of what follows. Set `false` for a form whose top fields
113
+ * really do belong to the same column as the credentials.
114
+ */
115
+ topFieldsSeparator?: boolean;
100
116
  /** Override CTA label */
101
117
  submitLabel?: string;
102
118
  /** Override CTA label while submitting */
@@ -75,6 +75,9 @@
75
75
  ]
76
76
  >;
77
77
 
78
+ /** Section break below the top-position extra fields. Default: true. */
79
+ topFieldsSeparator?: InnerProps["topFieldsSeparator"];
80
+
78
81
  /** Override CTA label */
79
82
  submitLabel?: string;
80
83
 
@@ -164,6 +167,7 @@
164
167
  passwordConfirmFieldProps,
165
168
  extraFields,
166
169
  extraFieldsSlot,
170
+ topFieldsSeparator,
167
171
  submitLabel,
168
172
  submittingLabel,
169
173
  submitButton,
@@ -273,6 +277,7 @@
273
277
  {passwordConfirmFieldProps}
274
278
  {extraFields}
275
279
  {extraFieldsSlot}
280
+ {topFieldsSeparator}
276
281
  {submitLabel}
277
282
  {submittingLabel}
278
283
  {submitButton}
@@ -51,6 +51,8 @@ export interface Props {
51
51
  fieldError: (name: string) => string | undefined;
52
52
  }
53
53
  ]>;
54
+ /** Section break below the top-position extra fields. Default: true. */
55
+ topFieldsSeparator?: InnerProps["topFieldsSeparator"];
54
56
  /** Override CTA label */
55
57
  submitLabel?: string;
56
58
  /** Override CTA label while submitting */
@@ -18,6 +18,13 @@
18
18
  /* credentialsSlot wrapper + the per-field rhythm inside the form */
19
19
  --stuic-register-form-credentials-margin-bottom: 1rem;
20
20
  --stuic-register-form-field-margin-bottom: 1rem;
21
+
22
+ /* Section break below the top-position extra fields (`topFieldsSeparator`).
23
+ The padding stacks on top of the last field's own margin-bottom, so the
24
+ default reads as 1.5rem of air on either side of the rule. */
25
+ --stuic-register-form-fields-top-padding-bottom: 0.5rem;
26
+ --stuic-register-form-fields-top-margin-bottom: 1.5rem;
27
+ --stuic-register-form-fields-top-border-color: var(--stuic-color-border);
21
28
  }
22
29
 
23
30
  @layer components {
@@ -39,6 +46,16 @@
39
46
  margin-bottom: var(--stuic-register-form-credentials-margin-bottom);
40
47
  }
41
48
 
49
+ /* Top-position extra fields. `data-separator` is on unless the consumer opts
50
+ out, so the group reads as its own section rather than as the first rows of
51
+ the credential column; the wrapper is inert without it, leaving the fields
52
+ their own margins. */
53
+ .stuic-register-form-fields-top[data-separator] {
54
+ padding-bottom: var(--stuic-register-form-fields-top-padding-bottom);
55
+ margin-bottom: var(--stuic-register-form-fields-top-margin-bottom);
56
+ border-bottom: 1px solid var(--stuic-register-form-fields-top-border-color);
57
+ }
58
+
42
59
  /* Social login container */
43
60
  .stuic-register-form-social {
44
61
  margin-top: var(--stuic-register-form-social-margin-top);
@@ -300,7 +300,7 @@ Prefix: `--stuic-login-form-*`
300
300
 
301
301
  Standalone registration form. Mirrors `LoginForm` conventions: `formData`, `onSubmit`, validation, errors, i18n, notifications, social-logins. Adds declarative `extraFields` (top/bottom positioning, custom validators) and an `extraFieldsSlot` escape hatch (e.g., terms checkbox).
302
302
 
303
- Also covers **identity-first signup** (identity established by an OAuth provider / invite / magic link before the account exists): `showEmail` / `showPassword` unmount the credential fields, `credentialsSlot` replaces them, `socialPosition="top"` moves the provider buttons above the credentials.
303
+ Also covers **identity-first signup** (identity established by an OAuth provider / invite / magic link before the account exists): `showEmail` / `showPassword` unmount the credential fields, `credentialsSlot` replaces them, `socialPosition="top"` moves the provider buttons above the credentials, and the top-position extra fields (workspace id, invite code — required on every path) are closed off with a section rule so they don't read as a caption for the first provider button (`topFieldsSeparator`, on by default whenever such a field exists).
304
304
 
305
305
  ### Exports
306
306
 
@@ -317,28 +317,29 @@ Also covers **identity-first signup** (identity established by an OAuth provider
317
317
 
318
318
  ### Key Props
319
319
 
320
- | Prop | Type | Default | Description |
321
- | ---------------------------------------------------------------------- | ------------------------------- | ---------- | ----------------------------------------------------------------------- |
322
- | `formData` | `RegisterFormData` | empty | Bindable form data |
323
- | `onSubmit` | `(data) => void` | required | Submit callback |
324
- | `isSubmitting` | `boolean` | `false` | Disables CTA |
325
- | `submitDisabled` | `boolean` | `false` | Consumer-owned block: disables CTA + blocks submit |
326
- | `errors` | `RegisterFormValidationError[]` | `[]` | Server field errors — self-clearing (see below) |
327
- | `error` | `string` | — | General error (alert above form) |
328
- | `showEmail` | `boolean` | `true` | Render (mount) the email field |
329
- | `showPassword` | `boolean` | `true` | Render (mount) the password + confirm fields |
330
- | `showPasswordConfirm` | `boolean` | `true` | Render password-confirm field |
331
- | `passwordMinLength` | `number` | `8` | Min password length (input + validator) |
332
- | `credentialsSlot` | `Snippet` | — | Content at the credentials position |
333
- | `emailFieldProps` / `passwordFieldProps` / `passwordConfirmFieldProps` | `Partial<FieldInputProps>` | — | Passthrough props per core field (`validate` composed, `value` ignored) |
334
- | `extraFields` | `RegisterFieldConfig[]` | `[]` | Declarative extra fields (top/bottom) |
335
- | `extraFieldsSlot` | `Snippet` | — | Escape-hatch for non-FieldInput extras |
336
- | `submitButton` | `Snippet` | | Custom CTA section |
337
- | `socialLogins` | `Snippet` | — | OAuth buttons |
338
- | `socialPosition` | `"top" \| "bottom"` | `"bottom"` | Social block above the credentials or after the CTA |
339
- | `footer` | `Snippet` | | Content below form |
340
- | `notifications` | `NotificationsStack` | — | Route errors to notifications |
341
- | `t` | `TranslateFn` | built-in | Translation function |
320
+ | Prop | Type | Default | Description |
321
+ | ---------------------------------------------------------------------- | ------------------------------- | ---------- | ------------------------------------------------------------------------- |
322
+ | `formData` | `RegisterFormData` | empty | Bindable form data |
323
+ | `onSubmit` | `(data) => void` | required | Submit callback |
324
+ | `isSubmitting` | `boolean` | `false` | Disables CTA |
325
+ | `submitDisabled` | `boolean` | `false` | Consumer-owned block: disables CTA + blocks submit |
326
+ | `errors` | `RegisterFormValidationError[]` | `[]` | Server field errors — self-clearing (see below) |
327
+ | `error` | `string` | — | General error (alert above form) |
328
+ | `showEmail` | `boolean` | `true` | Render (mount) the email field |
329
+ | `showPassword` | `boolean` | `true` | Render (mount) the password + confirm fields |
330
+ | `showPasswordConfirm` | `boolean` | `true` | Render password-confirm field |
331
+ | `passwordMinLength` | `number` | `8` | Min password length (input + validator) |
332
+ | `credentialsSlot` | `Snippet` | — | Content at the credentials position |
333
+ | `emailFieldProps` / `passwordFieldProps` / `passwordConfirmFieldProps` | `Partial<FieldInputProps>` | — | Passthrough props per core field (`validate` composed, `value` ignored) |
334
+ | `extraFields` | `RegisterFieldConfig[]` | `[]` | Declarative extra fields (top/bottom) |
335
+ | `extraFieldsSlot` | `Snippet` | — | Escape-hatch for non-FieldInput extras |
336
+ | `topFieldsSeparator` | `boolean` | `true` | Section rule closing the top-position extra-field group; `false` opts out |
337
+ | `submitButton` | `Snippet` | — | Custom CTA section |
338
+ | `socialLogins` | `Snippet` | | OAuth buttons |
339
+ | `socialPosition` | `"top" \| "bottom"` | `"bottom"` | Social block above the credentials or after the CTA |
340
+ | `footer` | `Snippet` | — | Content below form |
341
+ | `notifications` | `NotificationsStack` | | Route errors to notifications |
342
+ | `t` | `TranslateFn` | built-in | Translation function |
342
343
 
343
344
  **Imperative** (`bind:this`): `validate()`, `scrollToFirstError(opts?)`, `focusField(name)` — also forwarded by `RegisterFormModal`.
344
345
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.159.0",
3
+ "version": "3.161.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",