@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.
- package/AGENTS.md +7 -6
- package/API.md +90 -16
- package/README.md +1 -1
- package/dist/components/Header/Header.svelte +61 -22
- package/dist/components/Header/Header.svelte.d.ts +27 -2
- package/dist/components/Header/README.md +35 -0
- package/dist/components/Header/index.css +35 -2
- package/dist/components/IconSwap/README.md +69 -0
- package/dist/components/ImageCycler/README.md +83 -0
- package/dist/components/LoginForm/README.md +148 -0
- package/dist/components/LoginOrRegisterForm/README.md +170 -0
- package/dist/components/RegisterForm/README.md +183 -0
- package/dist/components/Separator/README.md +44 -0
- package/docs/architecture.md +14 -10
- package/docs/domains/components.md +112 -28
- package/docs/domains/utils.md +1 -1
- package/package.json +1 -1
|
@@ -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`.
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
-
##
|
|
13
|
+
## Four-Layer Styling System
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
Layer 1: Theme Tokens
|
|
16
|
+
Layer 1: Theme Tokens (--stuic-color-*)
|
|
17
17
|
↓
|
|
18
|
-
Layer 2:
|
|
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
|
|
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 -
|
|
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 -
|
|
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/ #
|
|
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/ #
|
|
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
|