@marianmeres/stuic 3.72.2 → 3.74.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 +2 -2
- package/API.md +30 -0
- package/README.md +1 -1
- package/dist/components/Book/BookResponsive.svelte +2 -1
- package/dist/components/Input/FieldPhoneNumber.svelte +2 -3
- package/dist/components/LoginForm/LoginForm.svelte +26 -64
- package/dist/components/LoginForm/LoginForm.svelte.d.ts +0 -1
- package/dist/components/LoginForm/LoginFormModal.svelte +1 -2
- package/dist/components/LoginForm/index.css +12 -29
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte +9 -1
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte.d.ts +6 -1
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterFormModal.svelte +8 -0
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterFormModal.svelte.d.ts +5 -0
- package/dist/components/LoginOrRegisterForm/index.css +3 -3
- package/dist/components/Pill/Pill.svelte +205 -0
- package/dist/components/Pill/Pill.svelte.d.ts +51 -0
- package/dist/components/Pill/README.md +211 -0
- package/dist/components/Pill/index.css +488 -0
- package/dist/components/Pill/index.d.ts +1 -0
- package/dist/components/Pill/index.js +1 -0
- package/dist/components/PricingTable/PricingTable.svelte +0 -2
- package/dist/components/RegisterForm/RegisterForm.svelte +1 -23
- package/dist/components/RegisterForm/RegisterForm.svelte.d.ts +0 -1
- package/dist/components/RegisterForm/RegisterFormModal.svelte +0 -1
- package/dist/components/RegisterForm/index.css +6 -16
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp.js +1 -0
- package/docs/domains/components.md +2 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
src/lib/
|
|
26
|
-
├── components/ #
|
|
26
|
+
├── components/ # 56 UI components
|
|
27
27
|
├── actions/ # 15 Svelte actions
|
|
28
28
|
├── utils/ # 43 utility modules
|
|
29
29
|
├── themes/ # Generated theme CSS (css/) — definitions from @marianmeres/design-tokens
|
|
@@ -116,7 +116,7 @@ Global tokens that control cross-component visual properties. Defined in `src/li
|
|
|
116
116
|
|
|
117
117
|
### Domain Docs
|
|
118
118
|
|
|
119
|
-
- [Components](./docs/domains/components.md) —
|
|
119
|
+
- [Components](./docs/domains/components.md) — 56 component directories, Props pattern, snippets
|
|
120
120
|
- [Theming](./docs/domains/theming.md) — CSS tokens, dark mode, themes
|
|
121
121
|
- [Actions](./docs/domains/actions.md) — 15 Svelte directives
|
|
122
122
|
- [Utils](./docs/domains/utils.md) — 43 utility modules
|
package/API.md
CHANGED
|
@@ -841,6 +841,36 @@ User avatar with fallback to initials or icon.
|
|
|
841
841
|
<!-- Shows "JD" initials -->
|
|
842
842
|
```
|
|
843
843
|
|
|
844
|
+
#### `Pill`
|
|
845
|
+
|
|
846
|
+
Inline rounded badge/tag/chip with intent + variant + size system. Polymorphic: renders as `<span>` (default), `<a>` (when `href` set), or `<button>` (when `onclick` set).
|
|
847
|
+
|
|
848
|
+
| Prop | Type | Default | Description |
|
|
849
|
+
| --------------- | ------------------------------------------------------------------ | -------- | ---------------------------------------------------- |
|
|
850
|
+
| `intent` | `"primary" \| "accent" \| "destructive" \| "warning" \| "success"` | - | Semantic color |
|
|
851
|
+
| `variant` | `"solid" \| "outline" \| "ghost" \| "soft" \| "link"` | `"soft"` | Visual treatment |
|
|
852
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Pill size |
|
|
853
|
+
| `roundedFull` | `boolean` | `true` | Fully rounded (9999px). `false` → element radius |
|
|
854
|
+
| `block` | `boolean` | `false` | Block-level flex (full width) |
|
|
855
|
+
| `dot` | `boolean` | `false` | Status dot before content |
|
|
856
|
+
| `dismissible` | `boolean` | `false` | Built-in X dismiss button |
|
|
857
|
+
| `ondismiss` | `(e: MouseEvent) => void` | - | Called when X clicked (stops propagation) |
|
|
858
|
+
| `active` | `boolean` | `false` | Selected state (filter-chip) |
|
|
859
|
+
| `muted` | `boolean` | `false` | Lower opacity |
|
|
860
|
+
| `href`, `target`| `string` | - | Render as `<a>` |
|
|
861
|
+
| `onclick` | `(e: MouseEvent) => void` | - | Render as `<button>` |
|
|
862
|
+
| `contentBefore` | `THC` | - | Content before children |
|
|
863
|
+
| `contentAfter` | `THC` | - | Content after children |
|
|
864
|
+
|
|
865
|
+
```svelte
|
|
866
|
+
<Pill intent="success" dot>Online</Pill>
|
|
867
|
+
<Pill intent="primary" dismissible ondismiss={() => removeTag()}>tag</Pill>
|
|
868
|
+
<Pill intent="accent" href="/profile">Profile</Pill>
|
|
869
|
+
<Pill intent="warning" variant="outline" active onclick={toggle}>Filter</Pill>
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
CSS tokens: `--stuic-pill-radius`, `--stuic-pill-font-family`, `--stuic-pill-font-weight`, `--stuic-pill-gap`, `--stuic-pill-dot-size`, `--stuic-pill-ring-{width,color}`, `--stuic-pill-{padding-x,padding-y,font-size,min-height}-{sm,md,lg}`.
|
|
873
|
+
|
|
844
874
|
#### `KbdShortcut`
|
|
845
875
|
|
|
846
876
|
Keyboard shortcut display.
|
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
|
|
|
164
164
|
|
|
165
165
|
### Display & Utility
|
|
166
166
|
|
|
167
|
-
Avatar, Book, BookResponsive, Card, Carousel, Circle, AnimatedElipsis, H, IconSwap, ImageCycler, Separator, ThemePreview, Tree, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, AssetsPreviewInline, DataTable
|
|
167
|
+
Avatar, Pill, Book, BookResponsive, Card, Carousel, Circle, AnimatedElipsis, H, IconSwap, ImageCycler, Separator, ThemePreview, Tree, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, AssetsPreviewInline, DataTable
|
|
168
168
|
|
|
169
169
|
### E-commerce
|
|
170
170
|
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<script lang="ts">
|
|
28
|
+
import { untrack } from "svelte";
|
|
28
29
|
import {
|
|
29
30
|
iconBookOpen,
|
|
30
31
|
iconArrowRight as iconNext,
|
|
@@ -89,7 +90,7 @@
|
|
|
89
90
|
|
|
90
91
|
// ---- Manual mode override ----
|
|
91
92
|
|
|
92
|
-
let manualMode: "book" | "inline" | null = $state(initialMode ?? null);
|
|
93
|
+
let manualMode: "book" | "inline" | null = $state(untrack(() => initialMode ?? null));
|
|
93
94
|
|
|
94
95
|
// ---- Inline mode ----
|
|
95
96
|
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
</script>
|
|
60
60
|
|
|
61
61
|
<script lang="ts">
|
|
62
|
-
import { tick } from "svelte";
|
|
62
|
+
import { tick, untrack } from "svelte";
|
|
63
63
|
import {
|
|
64
64
|
validate as validateAction,
|
|
65
65
|
type ValidationResult,
|
|
@@ -142,9 +142,8 @@
|
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
// Selected country object (initialize once from defaultCountry prop)
|
|
145
|
-
const _initCountry = defaultCountry;
|
|
146
145
|
let selectedCountry: Country | undefined = $state(
|
|
147
|
-
|
|
146
|
+
untrack(() => (defaultCountry ? ISO_MAP.get(defaultCountry.toUpperCase()) : undefined))
|
|
148
147
|
);
|
|
149
148
|
|
|
150
149
|
// Internal local number (for controlled input)
|
|
@@ -73,8 +73,6 @@
|
|
|
73
73
|
unstyled?: boolean;
|
|
74
74
|
class?: string;
|
|
75
75
|
el?: HTMLFormElement;
|
|
76
|
-
|
|
77
|
-
compact?: boolean;
|
|
78
76
|
}
|
|
79
77
|
</script>
|
|
80
78
|
|
|
@@ -111,7 +109,6 @@
|
|
|
111
109
|
unstyled = false,
|
|
112
110
|
class: classProp,
|
|
113
111
|
el = $bindable(),
|
|
114
|
-
compact,
|
|
115
112
|
...rest
|
|
116
113
|
}: Props = $props();
|
|
117
114
|
|
|
@@ -158,13 +155,7 @@
|
|
|
158
155
|
let _class = $derived(unstyled ? classProp : twMerge("stuic-login-form", classProp));
|
|
159
156
|
</script>
|
|
160
157
|
|
|
161
|
-
<form
|
|
162
|
-
bind:this={el}
|
|
163
|
-
class={_class}
|
|
164
|
-
use:onSubmitValidityCheck
|
|
165
|
-
{...rest}
|
|
166
|
-
data-compact={compact ? "" : undefined}
|
|
167
|
-
>
|
|
158
|
+
<form bind:this={el} class={_class} use:onSubmitValidityCheck {...rest}>
|
|
168
159
|
<!-- General error alert -->
|
|
169
160
|
<DismissibleMessage message={error} intent="destructive" onDismiss={false} />
|
|
170
161
|
|
|
@@ -184,7 +175,6 @@
|
|
|
184
175
|
required
|
|
185
176
|
name="login-email"
|
|
186
177
|
labelLeftBreakpoint={0}
|
|
187
|
-
class={compact ? "mb-4" : ""}
|
|
188
178
|
validate={{
|
|
189
179
|
customValidator(val) {
|
|
190
180
|
return fieldError("email") || "";
|
|
@@ -203,7 +193,6 @@
|
|
|
203
193
|
required
|
|
204
194
|
name="login-password"
|
|
205
195
|
labelLeftBreakpoint={0}
|
|
206
|
-
class={compact ? "mb-4" : ""}
|
|
207
196
|
validate={{
|
|
208
197
|
customValidator(val) {
|
|
209
198
|
return fieldError("password") || "";
|
|
@@ -211,9 +200,22 @@
|
|
|
211
200
|
}}
|
|
212
201
|
/>
|
|
213
202
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
203
|
+
<!-- CTA -->
|
|
204
|
+
{#if submitButton}
|
|
205
|
+
{@render submitButton({ isSubmitting, disabled: isSubmitting })}
|
|
206
|
+
{:else}
|
|
207
|
+
<div class={unstyled ? undefined : "stuic-login-form-submit"}>
|
|
208
|
+
<Button intent="primary" type="submit" disabled={isSubmitting} class="w-full">
|
|
209
|
+
{isSubmitting
|
|
210
|
+
? (submittingLabel ?? t("login_form.submitting"))
|
|
211
|
+
: (submitLabel ?? t("login_form.submit"))}
|
|
212
|
+
</Button>
|
|
213
|
+
</div>
|
|
214
|
+
{/if}
|
|
215
|
+
|
|
216
|
+
<!-- Remember me + Forgot password -->
|
|
217
|
+
{#if showRememberMe || onForgotPassword}
|
|
218
|
+
<div class={unstyled ? undefined : "stuic-login-form-options"}>
|
|
217
219
|
{#if showRememberMe}
|
|
218
220
|
<!-- svelte-ignore binding_property_non_reactive -->
|
|
219
221
|
<span use:tooltip aria-label={t("login_form.remember_me_tooltip")}>
|
|
@@ -225,58 +227,18 @@
|
|
|
225
227
|
/>
|
|
226
228
|
</span>
|
|
227
229
|
{/if}
|
|
228
|
-
{#if
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
{
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
{#if onForgotPassword}
|
|
231
|
+
<Button
|
|
232
|
+
variant="link"
|
|
233
|
+
type="button"
|
|
234
|
+
class={unstyled ? undefined : "text-muted-foreground ml-auto"}
|
|
235
|
+
size="sm"
|
|
236
|
+
onclick={onForgotPassword}
|
|
237
|
+
>
|
|
238
|
+
{t("login_form.forgot_password")}
|
|
235
239
|
</Button>
|
|
236
240
|
{/if}
|
|
237
241
|
</div>
|
|
238
|
-
{:else}
|
|
239
|
-
<!-- Normal: remember me above submit -->
|
|
240
|
-
{#if showRememberMe}
|
|
241
|
-
<!-- svelte-ignore binding_property_non_reactive -->
|
|
242
|
-
<div class={unstyled ? undefined : "stuic-login-form-remember"}>
|
|
243
|
-
<span use:tooltip aria-label={t("login_form.remember_me_tooltip")}>
|
|
244
|
-
<FieldCheckbox
|
|
245
|
-
bind:checked={formData.rememberMe}
|
|
246
|
-
label={t("login_form.remember_me")}
|
|
247
|
-
name="login-remember-me"
|
|
248
|
-
/>
|
|
249
|
-
</span>
|
|
250
|
-
</div>
|
|
251
|
-
{/if}
|
|
252
|
-
|
|
253
|
-
<!-- CTA -->
|
|
254
|
-
{#if submitButton}
|
|
255
|
-
{@render submitButton({ isSubmitting, disabled: isSubmitting })}
|
|
256
|
-
{:else}
|
|
257
|
-
<div class={unstyled ? undefined : "stuic-login-form-submit"}>
|
|
258
|
-
<Button intent="primary" type="submit" disabled={isSubmitting} class="w-full">
|
|
259
|
-
{isSubmitting
|
|
260
|
-
? (submittingLabel ?? t("login_form.submitting"))
|
|
261
|
-
: (submitLabel ?? t("login_form.submit"))}
|
|
262
|
-
</Button>
|
|
263
|
-
</div>
|
|
264
|
-
{/if}
|
|
265
|
-
{/if}
|
|
266
|
-
|
|
267
|
-
<!-- Forgot password -->
|
|
268
|
-
{#if onForgotPassword}
|
|
269
|
-
<div class={unstyled ? undefined : "stuic-login-form-forgot"}>
|
|
270
|
-
<Button
|
|
271
|
-
variant="link"
|
|
272
|
-
type="button"
|
|
273
|
-
class="text-muted-foreground"
|
|
274
|
-
size="sm"
|
|
275
|
-
onclick={onForgotPassword}
|
|
276
|
-
>
|
|
277
|
-
{t("login_form.forgot_password")}
|
|
278
|
-
</Button>
|
|
279
|
-
</div>
|
|
280
242
|
{/if}
|
|
281
243
|
|
|
282
244
|
<!-- Social logins -->
|
|
@@ -57,7 +57,6 @@ export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children">
|
|
|
57
57
|
unstyled?: boolean;
|
|
58
58
|
class?: string;
|
|
59
59
|
el?: HTMLFormElement;
|
|
60
|
-
compact?: boolean;
|
|
61
60
|
}
|
|
62
61
|
declare const LoginForm: import("svelte").Component<Props, {}, "el" | "formData">;
|
|
63
62
|
type LoginForm = ReturnType<typeof LoginForm>;
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
classDialog="flex items-center justify-center"
|
|
155
155
|
>
|
|
156
156
|
{#snippet header()}
|
|
157
|
-
<div class="flex items-center justify-between p-4
|
|
157
|
+
<div class="flex items-center justify-between p-4">
|
|
158
158
|
<H level={1} renderLevel={3} class="pl-2">
|
|
159
159
|
{title ?? t("login_form.modal_title")}
|
|
160
160
|
</H>
|
|
@@ -193,7 +193,6 @@
|
|
|
193
193
|
t={tProp}
|
|
194
194
|
{unstyled}
|
|
195
195
|
class={classForm}
|
|
196
|
-
compact
|
|
197
196
|
/>
|
|
198
197
|
</div>
|
|
199
198
|
</Modal>
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
/* LoginForm */
|
|
3
3
|
--stuic-login-form-gap: 0rem;
|
|
4
4
|
--stuic-login-form-gap-row: 1rem;
|
|
5
|
-
--stuic-login-form-forgot-margin-y: 0.5rem;
|
|
6
|
-
--stuic-login-form-forgot-margin-x: 0.5rem;
|
|
7
5
|
|
|
8
6
|
/* Social login section */
|
|
9
|
-
--stuic-login-form-social-margin-top:
|
|
7
|
+
--stuic-login-form-social-margin-top: 1.5rem;
|
|
10
8
|
--stuic-login-form-social-gap: 0.75rem;
|
|
11
9
|
--stuic-login-form-social-divider-color: var(--stuic-color-muted-foreground);
|
|
12
10
|
--stuic-login-form-social-divider-line-color: var(--stuic-color-border);
|
|
@@ -21,18 +19,15 @@
|
|
|
21
19
|
gap: var(--stuic-login-form-gap);
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
.stuic-login-form-
|
|
25
|
-
margin:
|
|
26
|
-
|
|
27
|
-
var(--stuic-login-form-forgot-margin-x);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.stuic-login-form-remember {
|
|
31
|
-
margin-top: 0.25rem;
|
|
22
|
+
.stuic-login-form-submit {
|
|
23
|
+
margin-top: 1.5rem;
|
|
24
|
+
margin-bottom: 1.5rem;
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
.stuic-login-form-
|
|
35
|
-
|
|
27
|
+
.stuic-login-form-options {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: 1rem;
|
|
36
31
|
}
|
|
37
32
|
|
|
38
33
|
/* Social login container */
|
|
@@ -63,21 +58,9 @@
|
|
|
63
58
|
flex-direction: column;
|
|
64
59
|
gap: var(--stuic-login-form-social-gap);
|
|
65
60
|
}
|
|
61
|
+
}
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/* justify-content: space-between; */
|
|
71
|
-
margin-top: 0.5rem;
|
|
72
|
-
gap: 2rem;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.stuic-login-form[data-compact] {
|
|
76
|
-
.stuic-login-form-submit {
|
|
77
|
-
margin-top: 0.5rem;
|
|
78
|
-
}
|
|
79
|
-
.stuic-button {
|
|
80
|
-
flex: 1;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
63
|
+
/* Tighten default FieldInput bottom margin (mb-8 → 1rem) inside the form */
|
|
64
|
+
.stuic-login-form .stuic-input {
|
|
65
|
+
margin-bottom: 1rem;
|
|
83
66
|
}
|
|
@@ -40,8 +40,14 @@
|
|
|
40
40
|
/** Applied to both inner forms' submit buttons. */
|
|
41
41
|
isSubmitting?: boolean;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Called when "Forgot password?" is clicked in login mode.
|
|
45
|
+
* If undefined, the link is not rendered.
|
|
46
|
+
*/
|
|
47
|
+
onForgotPassword?: () => void;
|
|
48
|
+
|
|
43
49
|
/** Pass-through props for the inner LoginForm (spread). */
|
|
44
|
-
loginProps?: Omit<LoginFormProps, InnerPropsCommonOmit>;
|
|
50
|
+
loginProps?: Omit<LoginFormProps, InnerPropsCommonOmit | "onForgotPassword">;
|
|
45
51
|
|
|
46
52
|
/** Pass-through props for the inner RegisterForm (spread). */
|
|
47
53
|
registerProps?: Omit<RegisterFormProps, InnerPropsCommonOmit>;
|
|
@@ -137,6 +143,7 @@
|
|
|
137
143
|
onVerify,
|
|
138
144
|
onResendCode,
|
|
139
145
|
isSubmitting = false,
|
|
146
|
+
onForgotPassword,
|
|
140
147
|
loginProps,
|
|
141
148
|
registerProps,
|
|
142
149
|
verifyProps,
|
|
@@ -221,6 +228,7 @@
|
|
|
221
228
|
onSubmit={onLogin}
|
|
222
229
|
{isSubmitting}
|
|
223
230
|
{notifications}
|
|
231
|
+
{onForgotPassword}
|
|
224
232
|
t={tProp}
|
|
225
233
|
{...loginProps}
|
|
226
234
|
/>
|
|
@@ -22,8 +22,13 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children">
|
|
|
22
22
|
onRegister: (data: RegisterFormData) => void;
|
|
23
23
|
/** Applied to both inner forms' submit buttons. */
|
|
24
24
|
isSubmitting?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Called when "Forgot password?" is clicked in login mode.
|
|
27
|
+
* If undefined, the link is not rendered.
|
|
28
|
+
*/
|
|
29
|
+
onForgotPassword?: () => void;
|
|
25
30
|
/** Pass-through props for the inner LoginForm (spread). */
|
|
26
|
-
loginProps?: Omit<LoginFormProps, InnerPropsCommonOmit>;
|
|
31
|
+
loginProps?: Omit<LoginFormProps, InnerPropsCommonOmit | "onForgotPassword">;
|
|
27
32
|
/** Pass-through props for the inner RegisterForm (spread). */
|
|
28
33
|
registerProps?: Omit<RegisterFormProps, InnerPropsCommonOmit>;
|
|
29
34
|
/**
|
|
@@ -33,6 +33,12 @@
|
|
|
33
33
|
|
|
34
34
|
isSubmitting?: boolean;
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Called when "Forgot password?" is clicked in login mode.
|
|
38
|
+
* If undefined, the link is not rendered.
|
|
39
|
+
*/
|
|
40
|
+
onForgotPassword?: () => void;
|
|
41
|
+
|
|
36
42
|
loginProps?: InnerProps["loginProps"];
|
|
37
43
|
registerProps?: InnerProps["registerProps"];
|
|
38
44
|
verifyProps?: InnerProps["verifyProps"];
|
|
@@ -101,6 +107,7 @@
|
|
|
101
107
|
onVerify,
|
|
102
108
|
onResendCode,
|
|
103
109
|
isSubmitting = false,
|
|
110
|
+
onForgotPassword,
|
|
104
111
|
loginProps,
|
|
105
112
|
registerProps,
|
|
106
113
|
verifyProps,
|
|
@@ -188,6 +195,7 @@
|
|
|
188
195
|
{onVerify}
|
|
189
196
|
{onResendCode}
|
|
190
197
|
{isSubmitting}
|
|
198
|
+
{onForgotPassword}
|
|
191
199
|
{loginProps}
|
|
192
200
|
{registerProps}
|
|
193
201
|
{verifyProps}
|
|
@@ -20,6 +20,11 @@ export interface Props {
|
|
|
20
20
|
/** Called when the user clicks "Resend code" in the verify view. */
|
|
21
21
|
onResendCode?: () => Promise<void> | void;
|
|
22
22
|
isSubmitting?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Called when "Forgot password?" is clicked in login mode.
|
|
25
|
+
* If undefined, the link is not rendered.
|
|
26
|
+
*/
|
|
27
|
+
onForgotPassword?: () => void;
|
|
23
28
|
loginProps?: InnerProps["loginProps"];
|
|
24
29
|
registerProps?: InnerProps["registerProps"];
|
|
25
30
|
verifyProps?: InnerProps["verifyProps"];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
/* LoginOrRegisterForm */
|
|
3
|
-
--stuic-login-or-register-form-gap:
|
|
4
|
-
--stuic-login-or-register-form-switcher-margin-bottom:
|
|
3
|
+
--stuic-login-or-register-form-gap: 0rem;
|
|
4
|
+
--stuic-login-or-register-form-switcher-margin-bottom: 2rem;
|
|
5
5
|
|
|
6
6
|
/* Social login section (shared, rendered at composite level) */
|
|
7
|
-
--stuic-login-or-register-form-social-margin-top:
|
|
7
|
+
--stuic-login-or-register-form-social-margin-top: 1.5rem;
|
|
8
8
|
--stuic-login-or-register-form-social-gap: 0.75rem;
|
|
9
9
|
--stuic-login-or-register-form-social-divider-color: var(
|
|
10
10
|
--stuic-color-muted-foreground
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type {
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
HTMLAnchorAttributes,
|
|
5
|
+
HTMLButtonAttributes,
|
|
6
|
+
} from "svelte/elements";
|
|
7
|
+
import type { Snippet } from "svelte";
|
|
8
|
+
import type { IntentColorKey } from "../../utils/design-tokens.js";
|
|
9
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
10
|
+
|
|
11
|
+
export type PillVariant = "solid" | "outline" | "ghost" | "soft" | "link";
|
|
12
|
+
export type PillSize = "sm" | "md" | "lg";
|
|
13
|
+
|
|
14
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
15
|
+
/** Color intent (semantic meaning) */
|
|
16
|
+
intent?: IntentColorKey;
|
|
17
|
+
/** Visual variant (how colors are applied) */
|
|
18
|
+
variant?: PillVariant | string;
|
|
19
|
+
/** Size preset */
|
|
20
|
+
size?: PillSize | string;
|
|
21
|
+
/** Reduce emphasis (lower opacity) */
|
|
22
|
+
muted?: boolean;
|
|
23
|
+
/** Selected/active state — useful for filter-chip behavior */
|
|
24
|
+
active?: boolean;
|
|
25
|
+
/** Pill is fully rounded by default; set false to use element radius */
|
|
26
|
+
roundedFull?: boolean;
|
|
27
|
+
/** Render as block-level flex (full width). Inline-flex by default. */
|
|
28
|
+
block?: boolean;
|
|
29
|
+
/** Skip all default styling, use only custom classes */
|
|
30
|
+
unstyled?: boolean;
|
|
31
|
+
/** Additional CSS classes */
|
|
32
|
+
class?: string;
|
|
33
|
+
/** Render as anchor tag */
|
|
34
|
+
href?: string;
|
|
35
|
+
/** Link target (e.g. "_blank") — only relevant when href is set */
|
|
36
|
+
target?: string;
|
|
37
|
+
/** Render as button (when href not set) */
|
|
38
|
+
onclick?: (e: MouseEvent) => void;
|
|
39
|
+
/** Disabled (interactive variants only) */
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
/** Show built-in X dismiss control */
|
|
42
|
+
dismissible?: boolean;
|
|
43
|
+
/** Called when X is clicked. Stops propagation so parent onclick is unaffected. */
|
|
44
|
+
ondismiss?: (e: MouseEvent) => void;
|
|
45
|
+
/** Status dot rendered before content (uses current intent color) */
|
|
46
|
+
dot?: boolean;
|
|
47
|
+
/** Content rendered before children */
|
|
48
|
+
contentBefore?: THC;
|
|
49
|
+
/** Content rendered after children */
|
|
50
|
+
contentAfter?: THC;
|
|
51
|
+
/** Bindable element reference */
|
|
52
|
+
el?: HTMLElement;
|
|
53
|
+
/** Content snippet */
|
|
54
|
+
children?: Snippet;
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<script lang="ts">
|
|
59
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
60
|
+
import Thc, { isTHCNotEmpty } from "../Thc/Thc.svelte";
|
|
61
|
+
import { X } from "../X/index.js";
|
|
62
|
+
|
|
63
|
+
let {
|
|
64
|
+
class: classProp,
|
|
65
|
+
intent,
|
|
66
|
+
variant = "soft",
|
|
67
|
+
size = "md",
|
|
68
|
+
muted = false,
|
|
69
|
+
active = false,
|
|
70
|
+
roundedFull = true,
|
|
71
|
+
block = false,
|
|
72
|
+
unstyled = false,
|
|
73
|
+
href,
|
|
74
|
+
target,
|
|
75
|
+
onclick,
|
|
76
|
+
disabled,
|
|
77
|
+
dismissible = false,
|
|
78
|
+
ondismiss,
|
|
79
|
+
dot = false,
|
|
80
|
+
contentBefore,
|
|
81
|
+
contentAfter,
|
|
82
|
+
el = $bindable(),
|
|
83
|
+
children,
|
|
84
|
+
...rest
|
|
85
|
+
}: Props = $props();
|
|
86
|
+
|
|
87
|
+
let _class = $derived(unstyled ? classProp : twMerge("stuic-pill", classProp));
|
|
88
|
+
|
|
89
|
+
function handleDismiss(e: MouseEvent) {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
ondismiss?.(e);
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
{#snippet body()}
|
|
96
|
+
{#if dot}
|
|
97
|
+
<span class="stuic-pill-dot" aria-hidden="true"></span>
|
|
98
|
+
{/if}
|
|
99
|
+
{#if isTHCNotEmpty(contentBefore)}
|
|
100
|
+
<Thc thc={contentBefore as THC} />
|
|
101
|
+
{/if}
|
|
102
|
+
{@render children?.()}
|
|
103
|
+
{#if isTHCNotEmpty(contentAfter)}
|
|
104
|
+
<Thc thc={contentAfter as THC} />
|
|
105
|
+
{/if}
|
|
106
|
+
{/snippet}
|
|
107
|
+
|
|
108
|
+
{#snippet dismissBtn()}
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
class="stuic-pill-dismiss"
|
|
112
|
+
aria-label="Dismiss"
|
|
113
|
+
onclick={handleDismiss}
|
|
114
|
+
{disabled}
|
|
115
|
+
>
|
|
116
|
+
<X strokeWidth={2} />
|
|
117
|
+
</button>
|
|
118
|
+
{/snippet}
|
|
119
|
+
|
|
120
|
+
{#if dismissible}
|
|
121
|
+
<!-- Wrapper pattern: outer span carries pill styling; inner element is the
|
|
122
|
+
interactive area (when href/onclick); X dismiss is a sibling button. -->
|
|
123
|
+
<span
|
|
124
|
+
bind:this={el}
|
|
125
|
+
class={_class}
|
|
126
|
+
data-intent={!unstyled ? intent : undefined}
|
|
127
|
+
data-variant={!unstyled ? variant : undefined}
|
|
128
|
+
data-size={!unstyled ? size : undefined}
|
|
129
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
130
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
131
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
132
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
133
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
134
|
+
data-dismissible="true"
|
|
135
|
+
{...rest}
|
|
136
|
+
>
|
|
137
|
+
{#if href}
|
|
138
|
+
<a {href} {target} class="stuic-pill-main">
|
|
139
|
+
{@render body()}
|
|
140
|
+
</a>
|
|
141
|
+
{:else if onclick}
|
|
142
|
+
<button type="button" class="stuic-pill-main" {onclick} {disabled}>
|
|
143
|
+
{@render body()}
|
|
144
|
+
</button>
|
|
145
|
+
{:else}
|
|
146
|
+
{@render body()}
|
|
147
|
+
{/if}
|
|
148
|
+
{@render dismissBtn()}
|
|
149
|
+
</span>
|
|
150
|
+
{:else if href}
|
|
151
|
+
<a
|
|
152
|
+
{href}
|
|
153
|
+
{target}
|
|
154
|
+
bind:this={el}
|
|
155
|
+
class={_class}
|
|
156
|
+
data-intent={!unstyled ? intent : undefined}
|
|
157
|
+
data-variant={!unstyled ? variant : undefined}
|
|
158
|
+
data-size={!unstyled ? size : undefined}
|
|
159
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
160
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
161
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
162
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
163
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
164
|
+
data-interactive="true"
|
|
165
|
+
{...rest as HTMLAnchorAttributes}
|
|
166
|
+
>
|
|
167
|
+
{@render body()}
|
|
168
|
+
</a>
|
|
169
|
+
{:else if onclick}
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
bind:this={el}
|
|
173
|
+
class={_class}
|
|
174
|
+
data-intent={!unstyled ? intent : undefined}
|
|
175
|
+
data-variant={!unstyled ? variant : undefined}
|
|
176
|
+
data-size={!unstyled ? size : undefined}
|
|
177
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
178
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
179
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
180
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
181
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
182
|
+
data-interactive="true"
|
|
183
|
+
{onclick}
|
|
184
|
+
{disabled}
|
|
185
|
+
{...rest as HTMLButtonAttributes}
|
|
186
|
+
>
|
|
187
|
+
{@render body()}
|
|
188
|
+
</button>
|
|
189
|
+
{:else}
|
|
190
|
+
<span
|
|
191
|
+
bind:this={el}
|
|
192
|
+
class={_class}
|
|
193
|
+
data-intent={!unstyled ? intent : undefined}
|
|
194
|
+
data-variant={!unstyled ? variant : undefined}
|
|
195
|
+
data-size={!unstyled ? size : undefined}
|
|
196
|
+
data-muted={!unstyled && muted ? "true" : undefined}
|
|
197
|
+
data-active={!unstyled && active ? "true" : undefined}
|
|
198
|
+
data-rounded-full={!unstyled && roundedFull ? "true" : undefined}
|
|
199
|
+
data-block={!unstyled && block ? "true" : undefined}
|
|
200
|
+
data-with-dot={!unstyled && dot ? "true" : undefined}
|
|
201
|
+
{...rest}
|
|
202
|
+
>
|
|
203
|
+
{@render body()}
|
|
204
|
+
</span>
|
|
205
|
+
{/if}
|