@marianmeres/stuic 3.96.0 → 3.97.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.
@@ -0,0 +1,148 @@
1
+ # LoginForm
2
+
3
+ Standalone login form with email/password fields, optional social/OAuth buttons, forgot-password link, remember-me checkbox, and client + server validation. Ships with English defaults for 16 i18n keys; pass a `t` function to translate.
4
+
5
+ `LoginForm` is the form-only component; `LoginFormModal` wraps it in a `Modal` with an opener trigger.
6
+
7
+ ## Exports
8
+
9
+ | Export | Kind | Description |
10
+ | -------------------------- | --------- | ------------------------------------------------- |
11
+ | `LoginForm` | component | Form component |
12
+ | `LoginFormModal` | component | Modal-wrapped form with optional trigger snippet |
13
+ | `LoginFormProps` | type | Props for `LoginForm` |
14
+ | `LoginFormModalProps` | type | Props for `LoginFormModal` |
15
+ | `LoginFormData` | type | `{ email, password, rememberMe }` |
16
+ | `LoginFormValidationError` | type | `{ field, message }` |
17
+ | `createEmptyLoginFormData` | function | Factory for an empty `LoginFormData` |
18
+ | `validateLoginForm` | function | `(data, t) => LoginFormValidationError[]` |
19
+
20
+ ## LoginForm — Props
21
+
22
+ | Prop | Type | Default | Description |
23
+ | --------------------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
24
+ | `formData` | `LoginFormData` | empty | Bindable form data. |
25
+ | `onSubmit` | `(data: LoginFormData) => void` | required | Called when client-side validation passes. |
26
+ | `isSubmitting` | `boolean` | `false` | Disables the CTA during submission. |
27
+ | `errors` | `LoginFormValidationError[]` | `[]` | Field-specific server errors (merged with internal validation). |
28
+ | `error` | `string` | - | General error rendered as a `DismissibleMessage` above the form. |
29
+ | `onForgotPassword` | `() => void` | - | Click handler for the "Forgot password?" link. Link is hidden when undefined. |
30
+ | `showRememberMe` | `boolean` | `true` | Render the remember-me checkbox. |
31
+ | `submitLabel` | `string` | i18n | Override the CTA label. |
32
+ | `submittingLabel` | `string` | i18n | Override the CTA label while submitting. |
33
+ | `submitButton` | `Snippet<[{ isSubmitting, disabled }]>` | - | Override the entire CTA section. |
34
+ | `socialLogins` | `Snippet` | - | Social/OAuth buttons rendered below the form. A divider is shown above when set. |
35
+ | `socialDividerLabel` | `string \| false` | i18n | Override (or hide with `false`) the divider above social buttons. |
36
+ | `footer` | `Snippet` | - | Content below the form (e.g., sign-up links). |
37
+ | `notifications` | `NotificationsStack` | - | When set, general errors are also pushed via `notifications.error()`. |
38
+ | `t` | `TranslateFn` | English | i18n function. |
39
+ | `unstyled` / `class` | - | - | Standard styling escape hatches. |
40
+ | `el` | `HTMLFormElement` | - | Bindable form element. |
41
+
42
+ ### Imperative methods
43
+
44
+ `bind:this` exposes:
45
+
46
+ | Method | Returns | Purpose |
47
+ | ------------------------------------- | --------- | ------------------------------------------------------------------------ |
48
+ | `validate()` | `boolean` | Forces every inner field's validator to run. `true` if all valid. |
49
+ | `scrollToFirstError(opts?)` | `boolean` | Scrolls + focuses the first invalid field. Call after `validate()`. |
50
+
51
+ These are essential for pristine-form errors (server errors set via the `errors` prop won't render until the user touches each field). See [`docs/domains/components.md#imperative-validate-api`](../../../docs/domains/components.md#imperative-validate-api) for the full pattern.
52
+
53
+ ## LoginFormModal — extra props
54
+
55
+ Inherits all `LoginForm` props, plus:
56
+
57
+ | Prop | Type | Default | Description |
58
+ | --------------------- | ----------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- |
59
+ | `title` | `string` | `"Log In"` (i18n) | Modal title. |
60
+ | `visible` | `boolean` | `false` | Bindable modal visibility. |
61
+ | `trigger` | `Snippet<[{ open }]>` | - | Optional trigger element rendered outside the modal. |
62
+ | `classModal` | `string` | - | Class for the Modal box. |
63
+ | `classInner` | `string` | - | Class for the Modal inner width container. |
64
+ | `classForm` | `string` | - | Class forwarded to the inner `LoginForm`. |
65
+ | `noXClose` | `boolean` | `false` | Hide the close (X) button. |
66
+ | `onClose` | `() => false \| void` | - | Pre-close hook. Return `false` to prevent close. |
67
+ | `noClickOutsideClose` | `boolean` | `true` | Disable backdrop-click close (default-on to protect typed credentials). |
68
+
69
+ **Methods:** `open(openerOrEvent?)`, `close()` — exposed via `bind:this`.
70
+
71
+ ## Usage
72
+
73
+ ### Basic form
74
+
75
+ ```svelte
76
+ <script lang="ts">
77
+ import { LoginForm } from "@marianmeres/stuic";
78
+ </script>
79
+
80
+ <LoginForm
81
+ onSubmit={(data) => login(data)}
82
+ onForgotPassword={() => goto("/forgot-password")}
83
+ />
84
+ ```
85
+
86
+ ### With social logins + footer
87
+
88
+ ```svelte
89
+ <LoginForm onSubmit={handleLogin}>
90
+ {#snippet socialLogins()}
91
+ <Button variant="outline" onclick={loginWithGoogle}>Google</Button>
92
+ <Button variant="outline" onclick={loginWithGitHub}>GitHub</Button>
93
+ {/snippet}
94
+ {#snippet footer()}
95
+ <p>Don't have an account? <a href="/register">Sign up</a></p>
96
+ {/snippet}
97
+ </LoginForm>
98
+ ```
99
+
100
+ ### Modal with trigger
101
+
102
+ ```svelte
103
+ <LoginFormModal onSubmit={handleLogin}>
104
+ {#snippet trigger({ open })}
105
+ <Button onclick={open}>Log In</Button>
106
+ {/snippet}
107
+ </LoginFormModal>
108
+ ```
109
+
110
+ ### Imperative validation from a custom CTA
111
+
112
+ ```svelte
113
+ <script lang="ts">
114
+ import { LoginForm } from "@marianmeres/stuic";
115
+ let form = $state<LoginForm>();
116
+
117
+ function submit() {
118
+ if (!form?.validate()) {
119
+ form?.scrollToFirstError();
120
+ return;
121
+ }
122
+ // ... actually submit ...
123
+ }
124
+ </script>
125
+
126
+ <LoginForm bind:this={form} onSubmit={submit} />
127
+ <Button onclick={submit}>Submit from outside</Button>
128
+ ```
129
+
130
+ ## CSS Variables
131
+
132
+ Prefix: `--stuic-login-form-*`
133
+
134
+ | Variable | Purpose |
135
+ | ------------------------------------------------- | -------------------------------------- |
136
+ | `--stuic-login-form-gap` | Vertical gap between sections |
137
+ | `--stuic-login-form-gap-row` | Gap inside multi-column rows |
138
+ | `--stuic-login-form-forgot-margin-y` | Forgot-password link vertical margin |
139
+ | `--stuic-login-form-forgot-margin-x` | Forgot-password link horizontal margin |
140
+ | `--stuic-login-form-social-margin-top` | Margin above social buttons block |
141
+ | `--stuic-login-form-social-gap` | Gap between social buttons |
142
+ | `--stuic-login-form-social-divider-color` | Divider text color |
143
+ | `--stuic-login-form-social-divider-font-size` | Divider text size |
144
+ | `--stuic-login-form-social-divider-margin-bottom` | Divider bottom margin |
145
+
146
+ ## i18n keys
147
+
148
+ All keys are under the `login_form.*` namespace and cover labels, placeholders, validation messages, the submit/submitting CTA, the social divider, and modal title. See `_internal/login-form-i18n-defaults.ts` for the full list and English defaults.
@@ -0,0 +1,170 @@
1
+ # LoginOrRegisterForm
2
+
3
+ Composite form that toggles between [`LoginForm`](../LoginForm/README.md), [`RegisterForm`](../RegisterForm/README.md), and (since 3.71) [`EmailVerifyForm`](../EmailVerifyForm/). The built-in mode switcher only exposes **login/register** tabs — `"verify"` is an outcome state entered programmatically (typically after a `requiresVerification` register response).
4
+
5
+ `LoginOrRegisterForm` is the form-only component; `LoginOrRegisterFormModal` wraps it in a `Modal` with an opener trigger and a mode-aware title.
6
+
7
+ ## Exports
8
+
9
+ | Export | Kind | Description |
10
+ | --------------------------------- | --------- | ------------------------------------------------- |
11
+ | `LoginOrRegisterForm` | component | Composite form |
12
+ | `LoginOrRegisterFormModal` | component | Modal-wrapped composite form |
13
+ | `LoginOrRegisterFormProps` | type | Props for `LoginOrRegisterForm` |
14
+ | `LoginOrRegisterFormModalProps` | type | Props for `LoginOrRegisterFormModal` |
15
+ | `LoginOrRegisterFormMode` | type | `"login" \| "register" \| "verify"` |
16
+
17
+ ## Mode behavior
18
+
19
+ - **login** — renders `LoginForm` + the shared `socialLogins` block.
20
+ - **register** — renders `RegisterForm` + the shared `socialLogins` block.
21
+ - **verify** — renders `EmailVerifyForm`. Social logins are hidden. The mode switcher is hidden (consumers transition in/out programmatically).
22
+
23
+ When the active mode changes, the relevant email is **one-shot copied** to the destination form's `email` field (login ↔ register ↔ verify) so the user does not have to re-type it. No continuous sync effect — flips are explicit.
24
+
25
+ ## LoginOrRegisterForm — Props
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
+ | `t` | `TranslateFn` | English | i18n function. |
51
+ | `unstyled` / `class` | - | - | Standard styling escape hatches. |
52
+
53
+ ## LoginOrRegisterFormModal — extra props
54
+
55
+ Inherits all `LoginOrRegisterForm` props, plus:
56
+
57
+ | Prop | Type | Default | Description |
58
+ | --------------------- | ------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
59
+ | `title` | `string` | mode-aware ("Log In" / "Create account" / "Verify your email") | Modal title. |
60
+ | `visible` | `boolean` | `false` | Bindable modal visibility. |
61
+ | `trigger` | `Snippet<[{ open }]>` | - | Optional trigger element rendered outside the modal. |
62
+ | `classModal` | `string` | - | Class for the Modal box. |
63
+ | `classInner` | `string` | - | Class for the Modal inner width container. |
64
+ | `classForm` | `string` | - | Class forwarded to the inner `LoginOrRegisterForm`. |
65
+ | `noXClose` | `boolean` | `false` | Hide the close (X) button. |
66
+ | `onClose` | `() => false \| void` | - | Pre-close hook. Return `false` to prevent close. |
67
+ | `noClickOutsideClose` | `boolean` | `true` | Disable backdrop-click close (default-on to protect typed credentials). |
68
+
69
+ **Methods:** `open(openerOrEvent?)`, `close()` — exposed via `bind:this`.
70
+
71
+ ## Usage
72
+
73
+ ### Basic toggle
74
+
75
+ ```svelte
76
+ <script lang="ts">
77
+ import { LoginOrRegisterForm } from "@marianmeres/stuic";
78
+ </script>
79
+
80
+ <LoginOrRegisterForm
81
+ onLogin={(data) => api.login(data)}
82
+ onRegister={(data) => api.register(data)}
83
+ onForgotPassword={() => goto("/forgot-password")}
84
+ />
85
+ ```
86
+
87
+ ### Programmatic verify flow
88
+
89
+ ```svelte
90
+ <script lang="ts">
91
+ import { LoginOrRegisterForm } from "@marianmeres/stuic";
92
+
93
+ let mode = $state<"login" | "register" | "verify">("login");
94
+ let verifyEmail = $state("");
95
+
96
+ async function handleRegister(data) {
97
+ const { requiresVerification } = await api.register(data);
98
+ if (requiresVerification) {
99
+ verifyEmail = data.email;
100
+ mode = "verify";
101
+ }
102
+ }
103
+
104
+ async function handleVerify(code: string) {
105
+ await api.verifyEmail({ email: verifyEmail, code });
106
+ // success — go back to login (or directly into the app)
107
+ mode = "login";
108
+ }
109
+ </script>
110
+
111
+ <LoginOrRegisterForm
112
+ bind:mode
113
+ bind:verifyEmail
114
+ onLogin={api.login}
115
+ onRegister={handleRegister}
116
+ onVerify={handleVerify}
117
+ onResendCode={() => api.resendCode({ email: verifyEmail })}
118
+ />
119
+ ```
120
+
121
+ ### Modal with trigger + shared OAuth
122
+
123
+ ```svelte
124
+ <LoginOrRegisterFormModal
125
+ bind:mode
126
+ onLogin={api.login}
127
+ onRegister={api.register}
128
+ >
129
+ {#snippet trigger({ open })}
130
+ <Button onclick={open}>Sign in / Sign up</Button>
131
+ {/snippet}
132
+ {#snippet socialLogins()}
133
+ <Button variant="outline" onclick={loginWithGoogle}>Google</Button>
134
+ <Button variant="outline" onclick={loginWithGitHub}>GitHub</Button>
135
+ {/snippet}
136
+ </LoginOrRegisterFormModal>
137
+ ```
138
+
139
+ ## CSS Variables
140
+
141
+ Prefix: `--stuic-login-or-register-form-*`
142
+
143
+ | Variable | Purpose |
144
+ | -------------------------------------------------------------- | ---------------------------------- |
145
+ | `--stuic-login-or-register-form-gap` | Vertical gap |
146
+ | `--stuic-login-or-register-form-switcher-margin-bottom` | Spacing below the mode switcher |
147
+ | `--stuic-login-or-register-form-social-margin-top` | Margin above social block |
148
+ | `--stuic-login-or-register-form-social-gap` | Gap between social buttons |
149
+ | `--stuic-login-or-register-form-social-divider-color` | Divider text color |
150
+ | `--stuic-login-or-register-form-social-divider-line-color` | Divider line color |
151
+ | `--stuic-login-or-register-form-social-divider-font-size` | Divider text size |
152
+ | `--stuic-login-or-register-form-social-divider-margin-bottom` | Divider bottom margin |
153
+
154
+ ## i18n keys
155
+
156
+ | Key | English default |
157
+ | ------------------------------------------------ | -------------------- |
158
+ | `login_or_register_form.mode_login` | `Log in` |
159
+ | `login_or_register_form.mode_register` | `Sign up` |
160
+ | `login_or_register_form.mode_verify` | `Verify` |
161
+ | `login_or_register_form.modal_title_login` | `Log In` |
162
+ | `login_or_register_form.modal_title_register` | `Create account` |
163
+ | `login_or_register_form.modal_title_verify` | `Verify your email` |
164
+ | `login_or_register_form.social_divider` | `or continue with` |
165
+
166
+ ## See also
167
+
168
+ - [LoginForm](../LoginForm/README.md) — inner form for login mode.
169
+ - [RegisterForm](../RegisterForm/README.md) — inner form for register mode.
170
+ - [EmailVerifyForm](../EmailVerifyForm/) — inner form for verify mode.
@@ -0,0 +1,183 @@
1
+ # RegisterForm
2
+
3
+ Standalone registration form. Same conventions as [`LoginForm`](../LoginForm/README.md): `formData`, `onSubmit`, internal + server validation, i18n, optional `notifications`, social-logins snippet. Adds **declarative extra fields** (top/bottom positioning, custom validators) and an **`extraFieldsSlot` escape hatch** for non-FieldInput extras (e.g., a terms-of-service checkbox).
4
+
5
+ `RegisterForm` is the form-only component; `RegisterFormModal` wraps it in a `Modal` with an opener trigger.
6
+
7
+ ## Exports
8
+
9
+ | Export | Kind | Description |
10
+ | ------------------------------- | --------- | ------------------------------------------------- |
11
+ | `RegisterForm` | component | Form component |
12
+ | `RegisterFormModal` | component | Modal-wrapped form with optional trigger snippet |
13
+ | `RegisterFormProps` | type | Props for `RegisterForm` |
14
+ | `RegisterFormModalProps` | type | Props for `RegisterFormModal` |
15
+ | `RegisterFormData` | type | `{ email, password, passwordConfirm, extra }` |
16
+ | `RegisterFormValidationError` | type | `{ field, message }` |
17
+ | `RegisterFieldConfig` | type | Declarative extra-field descriptor |
18
+ | `createEmptyRegisterFormData` | function | Factory for an empty `RegisterFormData` |
19
+ | `validateRegisterForm` | function | `(data, t, extraFields, opts) => Error[]` |
20
+
21
+ ## `RegisterFieldConfig`
22
+
23
+ ```ts
24
+ interface RegisterFieldConfig {
25
+ name: string; // unique key under formData.extra
26
+ label: string; // already-translated
27
+ type?: "text" | "email" | "tel" | "url" | "password" | "number";
28
+ placeholder?: string;
29
+ required?: boolean;
30
+ autocomplete?: HTMLInputAttributes["autocomplete"];
31
+ initialValue?: unknown;
32
+ validate?: (value: unknown, data: RegisterFormData) => string | undefined;
33
+ position?: "top" | "bottom"; // default "bottom"
34
+ props?: Record<string, unknown>; // passthrough to FieldInput
35
+ }
36
+ ```
37
+
38
+ ## RegisterForm — Props
39
+
40
+ | Prop | Type | Default | Description |
41
+ | --------------------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
42
+ | `formData` | `RegisterFormData` | empty | Bindable form data. |
43
+ | `onSubmit` | `(data: RegisterFormData) => void` | required | Called after client-side validation passes. |
44
+ | `isSubmitting` | `boolean` | `false` | Disables the CTA during submission. |
45
+ | `errors` | `RegisterFormValidationError[]` | `[]` | Field-specific server errors (merged with internal validation). |
46
+ | `error` | `string` | - | General error rendered as a `DismissibleMessage` above the form. |
47
+ | `showPasswordConfirm` | `boolean` | `true` | Render the password-confirm field. |
48
+ | `passwordMinLength` | `number` | `8` | Minimum password length (fed into both the FieldInput attribute and the validator). |
49
+ | `extraFields` | `RegisterFieldConfig[]` | `[]` | Declarative extra fields. Rendered as `FieldInput`s positioned top or bottom. |
50
+ | `extraFieldsSlot` | `Snippet<[{ formData, fieldError }]>` | - | Escape hatch for non-FieldInput extras. Rendered after declarative bottom fields. |
51
+ | `submitLabel` | `string` | i18n | Override the CTA label. |
52
+ | `submittingLabel` | `string` | i18n | Override the CTA label while submitting. |
53
+ | `submitButton` | `Snippet<[{ isSubmitting, disabled }]>` | - | Override the entire CTA section. |
54
+ | `socialLogins` | `Snippet` | - | Social/OAuth buttons. A divider is shown above when set. |
55
+ | `socialDividerLabel` | `string \| false` | i18n | Override (or hide with `false`) the divider above social buttons. |
56
+ | `footer` | `Snippet` | - | Content below the form (e.g., "Already have an account? Log in"). |
57
+ | `notifications` | `NotificationsStack` | - | When set, general errors are also pushed via `notifications.error()`. |
58
+ | `t` | `TranslateFn` | English | i18n function. |
59
+ | `unstyled` / `class` | - | - | Standard styling escape hatches. |
60
+ | `el` | `HTMLFormElement` | - | Bindable form element. |
61
+
62
+ ### Imperative methods (via `bind:this`)
63
+
64
+ | Method | Returns | Purpose |
65
+ | ------------------------------ | --------- | ------------------------------------------------------------------------ |
66
+ | `validate()` | `boolean` | Forces every inner field's validator to run. `true` if all valid. |
67
+ | `scrollToFirstError(opts?)` | `boolean` | Scrolls + focuses the first invalid field. Call after `validate()`. |
68
+
69
+ ## RegisterFormModal — extra props
70
+
71
+ Inherits all `RegisterForm` props, plus:
72
+
73
+ | Prop | Type | Default | Description |
74
+ | ------------- | ------------------------------- | ------------------------------------ | ---------------------------------------------------------- |
75
+ | `title` | `string` | `"Create account"` (i18n) | Modal title. |
76
+ | `visible` | `boolean` | `false` | Bindable modal visibility. |
77
+ | `trigger` | `Snippet<[{ open }]>` | - | Optional trigger element rendered outside the modal. |
78
+ | `classModal` | `string` | - | Class for the Modal box. |
79
+ | `classInner` | `string` | - | Class for the Modal inner width container. |
80
+ | `classForm` | `string` | - | Class forwarded to the inner `RegisterForm`. |
81
+ | `noXClose` | `boolean` | `false` | Hide the close (X) button. |
82
+ | `onClose` | `() => false \| void` | - | Pre-close hook. Return `false` to prevent close. |
83
+
84
+ **Methods:** `open(openerOrEvent?)`, `close()` — exposed via `bind:this`.
85
+
86
+ ## Usage
87
+
88
+ ### Basic
89
+
90
+ ```svelte
91
+ <script lang="ts">
92
+ import { RegisterForm } from "@marianmeres/stuic";
93
+ </script>
94
+
95
+ <RegisterForm onSubmit={(data) => signup(data)} />
96
+ ```
97
+
98
+ ### With declarative extra fields
99
+
100
+ ```svelte
101
+ <RegisterForm
102
+ onSubmit={signup}
103
+ extraFields={[
104
+ {
105
+ name: "fullName",
106
+ label: "Full name",
107
+ required: true,
108
+ position: "top",
109
+ autocomplete: "name",
110
+ },
111
+ {
112
+ name: "company",
113
+ label: "Company",
114
+ position: "bottom",
115
+ },
116
+ ]}
117
+ />
118
+ ```
119
+
120
+ ### With a terms-of-service checkbox (extraFieldsSlot)
121
+
122
+ ```svelte
123
+ <script lang="ts">
124
+ import { RegisterForm, FieldCheckbox } from "@marianmeres/stuic";
125
+
126
+ let formData = $state({
127
+ email: "",
128
+ password: "",
129
+ passwordConfirm: "",
130
+ extra: { tos: false },
131
+ });
132
+
133
+ function validateTos(value: unknown) {
134
+ return value ? undefined : "You must accept the terms.";
135
+ }
136
+ </script>
137
+
138
+ <RegisterForm bind:formData onSubmit={signup}>
139
+ {#snippet extraFieldsSlot({ formData, fieldError })}
140
+ <FieldCheckbox
141
+ bind:checked={formData.extra.tos}
142
+ label="I accept the terms of service"
143
+ error={fieldError("tos")}
144
+ validate={{ customValidator: () => validateTos(formData.extra.tos) }}
145
+ />
146
+ {/snippet}
147
+ </RegisterForm>
148
+ ```
149
+
150
+ ### Modal with trigger
151
+
152
+ ```svelte
153
+ <RegisterFormModal onSubmit={signup}>
154
+ {#snippet trigger({ open })}
155
+ <Button onclick={open}>Sign up</Button>
156
+ {/snippet}
157
+ </RegisterFormModal>
158
+ ```
159
+
160
+ ## CSS Variables
161
+
162
+ Prefix: `--stuic-register-form-*`
163
+
164
+ | Variable | Purpose |
165
+ | ----------------------------------------------------- | ----------------------------- |
166
+ | `--stuic-register-form-gap` | Vertical gap between sections |
167
+ | `--stuic-register-form-gap-row` | Gap inside multi-column rows |
168
+ | `--stuic-register-form-social-margin-top` | Margin above social block |
169
+ | `--stuic-register-form-social-gap` | Gap between social buttons |
170
+ | `--stuic-register-form-social-divider-color` | Divider text color |
171
+ | `--stuic-register-form-social-divider-line-color` | Divider line color |
172
+ | `--stuic-register-form-social-divider-font-size` | Divider text size |
173
+ | `--stuic-register-form-social-divider-margin-bottom` | Divider bottom margin |
174
+
175
+ ## i18n keys
176
+
177
+ Under the `register_form.*` namespace. See `_internal/register-form-i18n-defaults.ts` for the full list and English defaults.
178
+
179
+ ## See also
180
+
181
+ - [LoginForm](../LoginForm/README.md) — login counterpart.
182
+ - [LoginOrRegisterForm](../LoginOrRegisterForm/README.md) — composite that toggles between login, register, and verify modes.
183
+ - [EmailVerifyForm](../EmailVerifyForm/) — post-registration verify code form, often used in tandem.
@@ -0,0 +1,44 @@
1
+ # Separator
2
+
3
+ A horizontal or vertical separator line. Renders an accessible `role="separator"` element by default; pass `decorative` for a purely visual divider (`aria-hidden`).
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ | ------------- | ---------------------------- | -------------- | -------------------------------------------------------- |
9
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. Also published as `data-orientation`. |
10
+ | `decorative` | `boolean` | `false` | Drop `role` + `aria-orientation`, set `aria-hidden="true"`. |
11
+ | `unstyled` | `boolean` | `false` | Skip all default styling. |
12
+ | `class` | `string` | - | Additional CSS classes. |
13
+ | `style` | `string` | - | Inline style. |
14
+ | `el` | `HTMLDivElement` | - | Bindable root element. |
15
+
16
+ ## Usage
17
+
18
+ ```svelte
19
+ <script lang="ts">
20
+ import { Separator } from "@marianmeres/stuic";
21
+ </script>
22
+
23
+ <p>Above</p>
24
+ <Separator />
25
+ <p>Below</p>
26
+
27
+ <div style="display: flex; align-items: center; gap: 0.5rem; height: 1.5rem;">
28
+ <span>Left</span>
29
+ <Separator orientation="vertical" />
30
+ <span>Right</span>
31
+ </div>
32
+
33
+ <!-- Purely decorative — removed from the a11y tree -->
34
+ <Separator decorative />
35
+ ```
36
+
37
+ ## Data Attributes
38
+
39
+ - `data-orientation` — `"horizontal"` or `"vertical"`. Use for CSS targeting.
40
+
41
+ ## Notes
42
+
43
+ - Vertical separators need a non-zero height from the surrounding layout (the component does not impose one).
44
+ - For semantic separation between landmark sections, prefer `<hr>` or native sectioning elements over a `Separator`.
@@ -10,21 +10,25 @@ STUIC is a Svelte 5 component library with centralized CSS theming via custom pr
10
10
 
11
11
  ---
12
12
 
13
- ## Three-Layer Styling System
13
+ ## Four-Layer Styling System
14
14
 
15
15
  ```
16
- Layer 1: Theme Tokens (--stuic-color-*)
16
+ Layer 1: Theme Tokens (--stuic-color-*)
17
17
 
18
- Layer 2: Component Tokens (--stuic-{component}-*)
18
+ Layer 2: Structural Tokens (--stuic-radius, --stuic-shadow, --stuic-border-width, --stuic-transition)
19
+ ↓ (used as fallbacks)
20
+ Layer 3: Component Tokens (--stuic-{component}-*)
19
21
 
20
- Layer 3: Internal Vars (--_bg, --_text, --_border)
22
+ Layer 4: Internal Vars (--_bg, --_text, --_border)
21
23
  ```
22
24
 
23
- **Layer 1 - Theme Tokens:** Global design tokens defining colors, provided by `@marianmeres/design-tokens/css/`.
25
+ **Layer 1 - Theme Tokens:** Global design tokens defining colors, provided by `@marianmeres/design-tokens/css/`. Includes intent colors (`primary`, `accent`, `destructive`, `warning`, `success`), surface intents (15%/30% color-mix tints), and role colors (`background`, `surface`, `muted`, `foreground`, `border`, `input`, `ring`).
24
26
 
25
- **Layer 2 - Component Tokens:** Component-specific customization points, defined in component `index.css` files.
27
+ **Layer 2 - Structural Tokens:** Shared cross-component visuals defined in `src/lib/index.css`. Override these to reshape the entire library at once (e.g., a brutalist "no radius / no shadow / no border" pass). See [Conventions § Shared Structural Tokens](./conventions.md).
26
28
 
27
- **Layer 3 - Internal Vars:** Private variables set by intent/variant selectors, used in base styles.
29
+ **Layer 3 - Component Tokens:** Component-specific customization points, defined in component `index.css` files. Reference structural tokens as fallbacks at usage sites (NOT at `:root`) so per-instance scoped overrides remain possible.
30
+
31
+ **Layer 4 - Internal Vars:** Private variables (`--_*`) set by intent/variant/size selectors and consumed in base styles.
28
32
 
29
33
  ---
30
34
 
@@ -32,7 +36,7 @@ Layer 3: Internal Vars (--_bg, --_text, --_border)
32
36
 
33
37
  ```
34
38
  src/lib/
35
- ├── components/ # 46 UI components
39
+ ├── components/ # 57 component directories
36
40
  │ └── {Name}/
37
41
  │ ├── {Name}.svelte # Main component
38
42
  │ ├── index.ts # Exports
@@ -44,12 +48,12 @@ src/lib/
44
48
  │ ├── *.ts # Traditional actions
45
49
  │ └── index.ts # Barrel export
46
50
 
47
- ├── utils/ # 43 utility modules
51
+ ├── utils/ # 44 utility modules
48
52
  │ ├── *.svelte.ts # Reactive utilities
49
53
  │ ├── *.ts # Pure functions
50
54
  │ └── index.ts # Barrel export
51
55
 
52
- ├── icons/ # Icon re-exports
56
+ ├── icons/ # Icon re-exports from @marianmeres/icons-fns
53
57
 
54
58
  ├── index.css # CENTRALIZED CSS imports
55
59
  └── index.ts # Main barrel export