@djangocfg/layouts 2.1.422 → 2.1.424

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +17 -17
  2. package/src/components/errors/ErrorsTracker/components/ErrorToast.tsx +6 -4
  3. package/src/components/errors/ErrorsTracker/providers/ErrorTrackingProvider.tsx +12 -1
  4. package/src/components/errors/ErrorsTracker/utils/formatters.ts +19 -5
  5. package/src/layouts/AuthLayout/AuthLayout.tsx +8 -11
  6. package/src/layouts/AuthLayout/README.md +50 -18
  7. package/src/layouts/AuthLayout/components/shared/AuthConsent.tsx +46 -0
  8. package/src/layouts/AuthLayout/components/shared/index.ts +2 -2
  9. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +12 -25
  10. package/src/layouts/AuthLayout/context.tsx +0 -4
  11. package/src/layouts/AuthLayout/shells/AuthShell.tsx +10 -1
  12. package/src/layouts/AuthLayout/shells/FullSplitShell.tsx +73 -0
  13. package/src/layouts/AuthLayout/shells/types.ts +14 -4
  14. package/src/layouts/AuthLayout/styles/auth.css +62 -40
  15. package/src/layouts/AuthLayout/styles/fullsplit-shell.css +163 -0
  16. package/src/layouts/AuthLayout/styles/split-shell.css +74 -71
  17. package/src/layouts/AuthLayout/types.ts +6 -6
  18. package/src/layouts/ProfileLayout/ProfileDialog/ProfileDialog.tsx +26 -2
  19. package/src/layouts/ProfileLayout/ProfileDialog/index.ts +2 -0
  20. package/src/layouts/ProfileLayout/ProfileDialog/store.ts +39 -7
  21. package/src/layouts/ProfileLayout/ProfileForm/index.tsx +1 -2
  22. package/src/layouts/ProfileLayout/README.md +34 -2
  23. package/src/layouts/ProfileLayout/components/EditableField.tsx +4 -0
  24. package/src/layouts/ProfileLayout/hooks/index.ts +1 -1
  25. package/src/layouts/ProfileLayout/hooks/useProfileTabs.ts +14 -6
  26. package/src/layouts/ProfileLayout/index.ts +2 -1
  27. package/src/layouts/ProfileLayout/types.ts +3 -2
  28. package/src/testing/MockAuthFormProvider.tsx +0 -3
  29. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +0 -72
@@ -12,11 +12,15 @@ import type { CSSProperties, ReactNode } from 'react';
12
12
  // ─────────────────────────────────────────────────────────────────────────────
13
13
 
14
14
  /**
15
- * - `centered` — Apple-style frameless, centered, glow background (default).
16
- * - `split` Two-column: form left, sidebar right on desktop;
17
- * single-column centered on mobile.
15
+ * - `centered` — Apple-style frameless, centered, glow background (default).
16
+ * - `split` Floating two-column card: form left, sidebar right on
17
+ * desktop; single-column centered on mobile.
18
+ * - `fullsplit` — Full-bleed 50/50: edge-to-edge background image on the left
19
+ * (brand + quote overlaid), form on a solid panel on the right.
20
+ * Mobile collapses to a plain centered form. The large-SaaS
21
+ * look (Vercel / Linear / IBM).
18
22
  */
19
- export type AuthShellVariant = 'centered' | 'split';
23
+ export type AuthShellVariant = 'centered' | 'split' | 'fullsplit';
20
24
 
21
25
  // ─────────────────────────────────────────────────────────────────────────────
22
26
  // Background Configuration
@@ -40,6 +44,8 @@ export interface AuthBackgroundConfig {
40
44
  export interface AuthShellProps {
41
45
  variant: AuthShellVariant;
42
46
  children: ReactNode;
47
+ /** Which side the media half sits on (fullsplit only). @default 'left' */
48
+ mediaSide?: 'left' | 'right';
43
49
  /** Background configuration — image, gradient, overlay, blur. */
44
50
  background?: AuthBackgroundConfig;
45
51
  /** Slot for the right column in split variant (testimonial, branding, etc.). */
@@ -54,6 +60,10 @@ export interface ShellRenderProps {
54
60
  bgStyle?: CSSProperties;
55
61
  overlayStyle?: CSSProperties;
56
62
  blurValue?: string;
63
+ /** Which side the media half sits on (fullsplit only). @default 'left' */
64
+ mediaSide?: 'left' | 'right';
65
+ /** True once the background image has finished loading (drives fade-in). */
66
+ bgLoaded?: boolean;
57
67
  }
58
68
 
59
69
  // ─────────────────────────────────────────────────────────────────────────────
@@ -91,10 +91,10 @@
91
91
  }
92
92
 
93
93
  .auth-title {
94
- font-size: 1.625rem;
95
- font-weight: 700;
96
- line-height: 1.15;
97
- letter-spacing: -0.025em;
94
+ font-size: 1.5rem;
95
+ font-weight: 600;
96
+ line-height: 1.2;
97
+ letter-spacing: -0.02em;
98
98
  color: var(--foreground);
99
99
  margin: 0;
100
100
  }
@@ -124,8 +124,14 @@
124
124
  height: 3rem;
125
125
  padding: 0 0.875rem;
126
126
  font-size: 0.9375rem;
127
- background: var(--input);
128
- border: 1px solid var(--border);
127
+ /* The field is defined by its border, not a fill that fights its container.
128
+ `--input` alone matched `--border` (~90% on light) and dissolved; a fill
129
+ of `--card` would instead dissolve inside the Split card (also --card).
130
+ So: a recessed `--background` fill + a deliberately visible border that
131
+ bounds the field on both the frameless Centered shell and the --card
132
+ Split panel. */
133
+ background: var(--background);
134
+ border: 1px solid color-mix(in oklab, var(--border) 70%, var(--foreground));
129
135
  border-radius: var(--auth-radius-sm);
130
136
  color: var(--foreground);
131
137
  transition:
@@ -133,6 +139,10 @@
133
139
  box-shadow 0.18s var(--spring-snappy);
134
140
  }
135
141
 
142
+ .auth-input:hover:not(:focus):not(:disabled) {
143
+ border-color: color-mix(in oklab, var(--border) 60%, var(--foreground));
144
+ }
145
+
136
146
  .auth-input:focus {
137
147
  outline: none;
138
148
  border-color: var(--ring);
@@ -153,8 +163,8 @@
153
163
  height: 3rem;
154
164
  padding: 0 0.875rem;
155
165
  font-size: 0.9375rem;
156
- background: var(--input);
157
- border: 1px solid var(--border);
166
+ background: var(--background);
167
+ border: 1px solid color-mix(in oklab, var(--border) 70%, var(--foreground));
158
168
  border-radius: var(--auth-radius-sm);
159
169
  color: var(--foreground);
160
170
  transition:
@@ -195,7 +205,6 @@
195
205
  }
196
206
 
197
207
  .auth-button:disabled {
198
- opacity: 0.45;
199
208
  cursor: not-allowed;
200
209
  }
201
210
 
@@ -204,10 +213,30 @@
204
213
  color: var(--primary-foreground);
205
214
  }
206
215
 
216
+ /* Disabled primary: read as genuinely inactive — a flat neutral fill with
217
+ muted text — rather than a translucent version of the active blue, which
218
+ the old `opacity: .45` produced (it still looked clickable). */
219
+ .auth-button-primary:disabled {
220
+ background: var(--muted);
221
+ color: var(--muted-foreground);
222
+ }
223
+
224
+ /* GitHub / OAuth: a quiet outline button so the email path (primary) stays
225
+ the single loudest action. `--secondary` in light is near-black, which made
226
+ the secondary path shout louder than the primary one — HIG wants one accent. */
207
227
  .auth-button-secondary {
208
- background: var(--secondary);
209
- color: var(--secondary-foreground);
210
- border: none;
228
+ background: transparent;
229
+ color: var(--foreground);
230
+ border: 1px solid var(--border);
231
+ }
232
+
233
+ .auth-button-secondary:hover:not(:disabled) {
234
+ background: var(--accent);
235
+ opacity: 1;
236
+ }
237
+
238
+ .auth-button-secondary:disabled {
239
+ opacity: 0.45;
211
240
  }
212
241
 
213
242
  .auth-button:hover:not(:disabled) {
@@ -344,30 +373,26 @@
344
373
  opacity: 0.25;
345
374
  }
346
375
 
347
- /* ===== TERMS ===== */
376
+ /* ===== CONSENT (passive line under the submit button) ===== */
348
377
 
349
- .auth-terms {
350
- display: flex;
351
- align-items: flex-start;
352
- gap: 0.625rem;
353
- font-size: 0.8125rem;
378
+ .auth-consent {
379
+ margin: 0;
380
+ text-align: center;
381
+ font-size: 0.75rem;
354
382
  line-height: 1.5;
355
383
  color: var(--muted-foreground);
356
384
  }
357
385
 
358
- .auth-terms-checkbox {
359
- margin-top: 0.125rem;
360
- flex-shrink: 0;
361
- }
362
-
363
- .auth-terms a {
364
- color: var(--primary);
365
- text-decoration: none;
366
- transition: opacity 0.15s;
386
+ .auth-consent-link {
387
+ color: var(--muted-foreground);
388
+ text-decoration: underline;
389
+ text-underline-offset: 2px;
390
+ text-decoration-color: color-mix(in oklab, var(--muted-foreground) 45%, transparent);
391
+ transition: color 0.15s;
367
392
  }
368
393
 
369
- .auth-terms a:hover {
370
- opacity: 0.75;
394
+ .auth-consent-link:hover {
395
+ color: var(--foreground);
371
396
  }
372
397
 
373
398
  /* ===== REMEMBER ME ===== */
@@ -463,8 +488,8 @@
463
488
  font-weight: 600;
464
489
  text-align: center;
465
490
  letter-spacing: -0.01em;
466
- background: var(--input);
467
- border: 1px solid var(--border);
491
+ background: var(--background);
492
+ border: 1px solid color-mix(in oklab, var(--border) 70%, var(--foreground));
468
493
  border-radius: var(--auth-radius-sm);
469
494
  color: var(--foreground);
470
495
  transition:
@@ -654,20 +679,17 @@
654
679
 
655
680
  /* ===== DEV NOTICE ===== */
656
681
 
682
+ /* Uses the shared warning surface tokens (defined for both themes) instead of
683
+ hardcoded amber — keeps the dev banner consistent with the rest of the
684
+ system and drops the manual `.dark` override. */
657
685
  .auth-dev-notice {
658
686
  padding: 0.5rem 0.75rem;
659
687
  font-size: 0.75rem;
660
688
  text-align: center;
661
- color: hsl(38 92% 40%);
662
- background: hsl(38 92% 95%);
689
+ color: var(--warning-foreground);
690
+ background: var(--warning-background);
663
691
  border-radius: var(--auth-radius-xs);
664
- border: 1px solid hsl(38 92% 80%);
665
- }
666
-
667
- .dark .auth-dev-notice {
668
- color: hsl(38 92% 65%);
669
- background: hsl(38 92% 15%);
670
- border-color: hsl(38 92% 30%);
692
+ border: 1px solid var(--warning-border);
671
693
  }
672
694
 
673
695
  /* ===== INSTRUCTION ===== */
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Full-Split Shell Styles
3
+ *
4
+ * Full-bleed 50/50 auth layout (Vercel / Linear / IBM style).
5
+ * Desktop (>= lg): edge-to-edge — image half (left) + form panel (right).
6
+ * Mobile (< lg): image half hidden; plain centered form on --background.
7
+ */
8
+
9
+ .auth-shell-fullsplit {
10
+ position: relative;
11
+ min-height: 100vh;
12
+ min-height: 100dvh;
13
+ display: flex;
14
+ background: var(--background);
15
+ }
16
+
17
+ /* ── Media half (left) — hidden on mobile ──────────────────────────────── */
18
+ .auth-shell-fullsplit__media {
19
+ display: none;
20
+ }
21
+
22
+ .auth-shell-fullsplit__bg {
23
+ position: absolute;
24
+ inset: 0;
25
+ z-index: 0;
26
+ pointer-events: none;
27
+ /* Quiet preloader: the layer starts transparent and fades in only once the
28
+ image has actually downloaded (data-loaded="true"). No spinner, and the
29
+ form never waits on this — it renders immediately underneath. */
30
+ opacity: 0;
31
+ transition: opacity 0.6s var(--spring-smooth, ease-out);
32
+ /* Ken Burns — a slow, infinite zoom + drift that makes the hero photo
33
+ quietly "breathe". `alternate` reverses each cycle so it eases back
34
+ instead of hard-cutting to the start. Kept very slow (28s) and subtle
35
+ (max 1.08×) so it reads as life, not motion. The media half is
36
+ overflow:hidden, so the scaled-up image never reveals its edges. */
37
+ transform-origin: center;
38
+ animation: authKenBurns 28s ease-in-out infinite alternate;
39
+ will-change: transform, opacity;
40
+ }
41
+
42
+ .auth-shell-fullsplit__bg[data-loaded="true"] {
43
+ opacity: 1;
44
+ }
45
+
46
+ /* While the photo loads, the media half shows a calm base surface (not a
47
+ black void) so the split reads as intentional even on a slow connection. */
48
+ .auth-shell-fullsplit__media {
49
+ background: var(--muted);
50
+ }
51
+
52
+ @keyframes authKenBurns {
53
+ from {
54
+ transform: scale(1) translate3d(0, 0, 0);
55
+ }
56
+ to {
57
+ transform: scale(1.08) translate3d(-1.5%, -1%, 0);
58
+ }
59
+ }
60
+
61
+ /* Honour reduced-motion: hold the photo still. */
62
+ @media (prefers-reduced-motion: reduce) {
63
+ .auth-shell-fullsplit__bg {
64
+ animation: none;
65
+ transform: none;
66
+ }
67
+ }
68
+
69
+ .auth-shell-fullsplit__overlay {
70
+ position: absolute;
71
+ inset: 0;
72
+ z-index: 1;
73
+ pointer-events: none;
74
+ }
75
+
76
+ /* Sidebar content (brand top, quote bottom) over the photo. */
77
+ .auth-shell-fullsplit__sidebar {
78
+ position: relative;
79
+ z-index: 2;
80
+ display: flex;
81
+ flex-direction: column;
82
+ justify-content: space-between;
83
+ height: 100%;
84
+ padding: 3rem;
85
+ color: #fff;
86
+ }
87
+
88
+ /* ── Form half (right) ─────────────────────────────────────────────────── */
89
+ .auth-shell-fullsplit__form {
90
+ flex: 1;
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ padding: 1.5rem clamp(1.5rem, 5vw, 2.5rem);
95
+ padding-bottom: max(1.5rem, env(safe-area-inset-bottom, 1rem));
96
+ }
97
+
98
+ .auth-shell-fullsplit__form-inner {
99
+ width: 100%;
100
+ max-width: 360px;
101
+ }
102
+
103
+ /* ── Desktop: reveal the image half, split 50/50 edge-to-edge ──────────── */
104
+ @media (min-width: 1024px) {
105
+ /* Mirror: media on the right instead of the left. */
106
+ .auth-shell-fullsplit[data-media-side="right"] {
107
+ flex-direction: row-reverse;
108
+ }
109
+
110
+ .auth-shell-fullsplit__media {
111
+ display: block;
112
+ position: relative;
113
+ flex: 1;
114
+ overflow: hidden;
115
+ /* Always-present gradient scrim so brand/quote stay legible over any
116
+ photo, independent of any consumer-supplied overlay. Darkens bottom
117
+ (where the quote sits) and a touch at the top (brand). */
118
+ isolation: isolate;
119
+ }
120
+
121
+ .auth-shell-fullsplit__media::after {
122
+ content: "";
123
+ position: absolute;
124
+ inset: 0;
125
+ z-index: 1;
126
+ pointer-events: none;
127
+ /* Soft shadow rising from the bottom: deep under the quote, easing to
128
+ nothing by the upper third so the photo stays open and bright up top.
129
+ The eased multi-stop curve avoids a hard "band" edge — reads as a
130
+ natural vignette, not a flat overlay. */
131
+ background: linear-gradient(
132
+ to top,
133
+ hsl(0 0% 0% / 0.78) 0%,
134
+ hsl(0 0% 0% / 0.55) 18%,
135
+ hsl(0 0% 0% / 0.28) 38%,
136
+ hsl(0 0% 0% / 0.08) 60%,
137
+ transparent 80%
138
+ );
139
+ }
140
+
141
+ /* Sidebar sits above the scrim. */
142
+ .auth-shell-fullsplit__sidebar {
143
+ z-index: 2;
144
+ }
145
+
146
+ .auth-shell-fullsplit__form {
147
+ flex: 1;
148
+ padding: 2.5rem;
149
+ }
150
+
151
+ .auth-shell-fullsplit__form-inner {
152
+ /* Slightly wider on the solid panel — it has room to breathe. */
153
+ max-width: 380px;
154
+ }
155
+ }
156
+
157
+ /* Wider split bias on very large screens — form panel stays a comfortable
158
+ reading width while the image takes the extra space. */
159
+ @media (min-width: 1536px) {
160
+ .auth-shell-fullsplit__media {
161
+ flex: 1.25;
162
+ }
163
+ }
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Split Shell Styles
3
3
  *
4
- * Two-column auth layout (desktop) / single-column (mobile).
5
- * Desktop: card with form left, sidebar right.
6
- * Mobile: centered form, sidebar hidden.
4
+ * Mobile (< xl): a plain, frameless centered form — no card, no background
5
+ * image, no glass. Reads like an ordinary sign-in screen on small devices.
6
+ * Desktop (>= xl): a two-column frosted-glass card floating over the
7
+ * background image — translucent --card + backdrop blur, form left,
8
+ * sidebar right.
7
9
  */
8
10
 
9
11
  .auth-shell-split {
@@ -16,72 +18,29 @@
16
18
  padding: 1rem;
17
19
  }
18
20
 
19
- /* Background layers */
20
- .auth-shell-split__bg {
21
- position: fixed;
22
- inset: 0;
23
- z-index: 0;
24
- pointer-events: none;
25
- }
26
-
21
+ /* Background layers — hidden on mobile (plain form), shown on desktop. */
22
+ .auth-shell-split__bg,
27
23
  .auth-shell-split__overlay {
24
+ display: none;
28
25
  position: fixed;
29
26
  inset: 0;
30
27
  z-index: 0;
31
28
  pointer-events: none;
32
29
  }
33
30
 
34
- /* Card container */
31
+ /* Card container.
32
+ Mobile: not a card at all — transparent, no border/shadow, just a centered
33
+ column. Desktop overrides below turn it into the frosted glass panel. */
35
34
  .auth-shell-split__card {
36
35
  position: relative;
37
36
  z-index: 1;
38
37
  display: flex;
38
+ flex-direction: column;
39
39
  width: 100%;
40
- max-width: 420px;
41
- background: var(--background);
40
+ max-width: 400px;
41
+ background: transparent;
42
42
  border-radius: var(--auth-radius);
43
- outline: 1px solid var(--border);
44
- box-shadow: 0 4px 24px hsl(0 0% 0% / 0.08);
45
43
  overflow: hidden;
46
- /* Apple-style frosted entry — the card "condenses" out of the
47
- background. Uses the same spring curve (0.16, 1, 0.3, 1) that
48
- iOS modal sheets ride on. We animate filter+transform+opacity
49
- together so it reads as one continuous gesture, not three
50
- stacked tweens. willChange hints the compositor to promote the
51
- element ahead of the run; reset to auto on completion (CSS
52
- `forwards` keeps the end state implicitly). */
53
- animation: authShellCardEntry 700ms cubic-bezier(0.16, 1, 0.3, 1) both;
54
- will-change: transform, filter, opacity;
55
- }
56
-
57
- @keyframes authShellCardEntry {
58
- 0% {
59
- opacity: 0;
60
- transform: translateY(24px) scale(0.96);
61
- filter: blur(10px);
62
- }
63
- 60% {
64
- /* Mid-keyframe lets the blur clear faster than the slide so the
65
- form contents become readable while the card is still settling
66
- — feels responsive instead of waiting on the animation. */
67
- filter: blur(0);
68
- }
69
- 100% {
70
- opacity: 1;
71
- transform: translateY(0) scale(1);
72
- filter: blur(0);
73
- }
74
- }
75
-
76
- /* Respect reduced-motion preference — fall back to a plain fade. */
77
- @media (prefers-reduced-motion: reduce) {
78
- .auth-shell-split__card {
79
- animation: authShellCardEntryReduced 200ms ease-out both;
80
- }
81
- @keyframes authShellCardEntryReduced {
82
- from { opacity: 0; }
83
- to { opacity: 1; }
84
- }
85
44
  }
86
45
 
87
46
  /* Form column */
@@ -90,7 +49,7 @@
90
49
  display: flex;
91
50
  align-items: center;
92
51
  justify-content: center;
93
- padding: 2rem 1.5rem;
52
+ padding: 1rem 0.5rem;
94
53
  }
95
54
 
96
55
  .auth-shell-split__form-inner {
@@ -98,43 +57,87 @@
98
57
  max-width: 360px;
99
58
  }
100
59
 
101
- /* Sidebar column — hidden by default (mobile) */
60
+ /* Sidebar column — hidden on mobile, shown on desktop. */
102
61
  .auth-shell-split__sidebar {
103
62
  display: none;
104
- flex: 1;
105
- flex-direction: column;
106
- justify-content: center;
107
- padding: 2.5rem 2rem;
108
- background: color-mix(in oklab, var(--muted) 25%, transparent);
109
63
  }
110
64
 
111
- /* Desktop: show sidebar, expand card */
65
+ /* ── Desktop: frosted-glass two-column card ──────────────────────────────── */
112
66
  @media (min-width: 1280px) {
67
+ .auth-shell-split__bg,
68
+ .auth-shell-split__overlay {
69
+ display: block;
70
+ }
71
+
113
72
  .auth-shell-split__card {
73
+ flex-direction: row;
114
74
  max-width: 960px;
75
+ /* Give the card real presence on desktop — a taller panel reads as a
76
+ considered layout rather than a thin strip, and gives the sidebar room
77
+ to breathe. */
78
+ min-height: 560px;
79
+ /* Translucent surface + backdrop blur so the background image reads
80
+ through the panel as frosted glass. color-mix keeps it theme-aware. */
81
+ background: color-mix(in oklab, var(--card) 72%, transparent);
82
+ backdrop-filter: blur(20px) saturate(140%);
83
+ -webkit-backdrop-filter: blur(20px) saturate(140%);
84
+ color: var(--card-foreground);
85
+ outline: 1px solid color-mix(in oklab, var(--border) 80%, transparent);
86
+ box-shadow: 0 24px 64px hsl(0 0% 0% / 0.28);
87
+ /* Apple-style frosted entry — the card "condenses" out of the background.
88
+ Same spring curve (0.16, 1, 0.3, 1) iOS modal sheets ride on. */
89
+ animation: authShellCardEntry 700ms cubic-bezier(0.16, 1, 0.3, 1) both;
90
+ will-change: transform, filter, opacity;
115
91
  }
116
92
 
117
93
  .auth-shell-split__form {
118
- padding: 2.5rem;
94
+ padding: 2.75rem;
119
95
  }
120
96
 
121
97
  .auth-shell-split__sidebar {
122
98
  display: flex;
99
+ flex: 1;
100
+ flex-direction: column;
101
+ justify-content: space-between;
102
+ gap: 2rem;
103
+ padding: 2.75rem 2.25rem;
104
+ /* A subtle tint over the glass marks the brand column without becoming a
105
+ second opaque surface. */
106
+ background: color-mix(in oklab, var(--muted) 40%, transparent);
107
+ border-left: 1px solid color-mix(in oklab, var(--border) 70%, transparent);
123
108
  }
124
109
  }
125
110
 
126
- /* Reduced padding on smaller tablets */
111
+ /* Tablet (1024–1279): keep mobile's plain form but allow a touch more room. */
127
112
  @media (min-width: 1024px) and (max-width: 1279px) {
128
113
  .auth-shell-split__card {
129
- max-width: 880px;
114
+ max-width: 420px;
130
115
  }
116
+ }
131
117
 
132
- .auth-shell-split__form {
133
- padding: 2rem;
118
+ @keyframes authShellCardEntry {
119
+ 0% {
120
+ opacity: 0;
121
+ transform: translateY(24px) scale(0.96);
122
+ filter: blur(10px);
123
+ }
124
+ 60% {
125
+ filter: blur(0);
126
+ }
127
+ 100% {
128
+ opacity: 1;
129
+ transform: translateY(0) scale(1);
130
+ filter: blur(0);
134
131
  }
132
+ }
135
133
 
136
- .auth-shell-split__sidebar {
137
- display: flex;
138
- padding: 2rem 1.5rem;
134
+ /* Respect reduced-motion — fall back to a plain fade. */
135
+ @media (prefers-reduced-motion: reduce) {
136
+ .auth-shell-split__card {
137
+ animation: authShellCardEntryReduced 200ms ease-out both;
138
+ }
139
+ @keyframes authShellCardEntryReduced {
140
+ from { opacity: 0; }
141
+ to { opacity: 1; }
139
142
  }
140
143
  }
@@ -44,11 +44,6 @@ export interface AuthLayoutConfig {
44
44
  logoUrl?: string;
45
45
  /** URL to redirect after successful auth (default: /dashboard) */
46
46
  redirectUrl?: string;
47
- /**
48
- * Enable 2FA setup prompt after successful authentication.
49
- * @default true
50
- */
51
- enable2FASetup?: boolean;
52
47
  }
53
48
 
54
49
  // ─────────────────────────────────────────────────────────────────────────────
@@ -64,8 +59,13 @@ export interface AuthFormContextType extends AuthFormReturn, AuthLayoutConfig {}
64
59
  export interface AuthLayoutProps extends AuthLayoutConfig {
65
60
  children?: React.ReactNode;
66
61
  className?: string;
67
- /** Shell variant — controls the overall layout shape. @default 'centered' */
62
+ /** Shell variant — controls the overall layout shape. @default 'fullsplit' */
68
63
  variant?: AuthShellVariant;
64
+ /**
65
+ * Which side the media (image) half sits on in the `fullsplit` variant.
66
+ * Ignored by other variants. @default 'left'
67
+ */
68
+ mediaSide?: 'left' | 'right';
69
69
  /** Background configuration (image, gradient, overlay, blur). */
70
70
  background?: AuthBackgroundConfig;
71
71
  /** Slot for the right column in split variant (testimonial, branding, etc.). */
@@ -17,7 +17,20 @@ export interface ProfileDialogProps {
17
17
  }
18
18
 
19
19
  export const ProfileDialog: React.FC<ProfileDialogProps> = ({ title }) => {
20
- const { isOpen, close, initialTab } = useProfileDialogStore();
20
+ const {
21
+ isOpen,
22
+ close,
23
+ initialTab,
24
+ tabs,
25
+ slots,
26
+ enable2FA,
27
+ enableAPIKeys,
28
+ enableDeleteAccount,
29
+ title: storeTitle,
30
+ } = useProfileDialogStore();
31
+
32
+ // Title precedence: explicit dialog prop > value passed to open() > ProfileForm default.
33
+ const resolvedTitle = title ?? storeTitle;
21
34
 
22
35
  return (
23
36
  <Dialog open={isOpen} onOpenChange={(open) => !open && close()}>
@@ -25,7 +38,18 @@ export const ProfileDialog: React.FC<ProfileDialogProps> = ({ title }) => {
25
38
  <DialogHeader className="sr-only">
26
39
  <DialogTitle>Profile</DialogTitle>
27
40
  </DialogHeader>
28
- <ProfileForm title={title} defaultTab={initialTab} />
41
+ {/* Undefined fields fall through to ProfileForm's own defaults
42
+ * (e.g. enableAPIKeys defaults to true), so this stays backward
43
+ * compatible with bare open() / open({ initialTab }). */}
44
+ <ProfileForm
45
+ title={resolvedTitle}
46
+ defaultTab={initialTab}
47
+ tabs={tabs}
48
+ slots={slots}
49
+ enable2FA={enable2FA}
50
+ enableAPIKeys={enableAPIKeys}
51
+ enableDeleteAccount={enableDeleteAccount}
52
+ />
29
53
  </DialogContent>
30
54
  </Dialog>
31
55
  );
@@ -1,2 +1,4 @@
1
1
  export { ProfileDialog } from './ProfileDialog';
2
+ export type { ProfileDialogProps } from './ProfileDialog';
2
3
  export { useProfileDialogStore } from './store';
4
+ export type { ProfileDialogContent } from './store';