@djangocfg/layouts 2.1.433 → 2.1.434

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.433",
3
+ "version": "2.1.434",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -89,12 +89,12 @@
89
89
  "check": "tsc --noEmit"
90
90
  },
91
91
  "peerDependencies": {
92
- "@djangocfg/api": "^2.1.433",
93
- "@djangocfg/centrifugo": "^2.1.433",
94
- "@djangocfg/debuger": "^2.1.433",
95
- "@djangocfg/i18n": "^2.1.433",
96
- "@djangocfg/monitor": "^2.1.433",
97
- "@djangocfg/ui-core": "^2.1.433",
92
+ "@djangocfg/api": "^2.1.434",
93
+ "@djangocfg/centrifugo": "^2.1.434",
94
+ "@djangocfg/debuger": "^2.1.434",
95
+ "@djangocfg/i18n": "^2.1.434",
96
+ "@djangocfg/monitor": "^2.1.434",
97
+ "@djangocfg/ui-core": "^2.1.434",
98
98
  "@hookform/resolvers": "^5.2.2",
99
99
  "consola": "^3.4.2",
100
100
  "lucide-react": "^0.545.0",
@@ -125,14 +125,14 @@
125
125
  "uuid": "^11.1.0"
126
126
  },
127
127
  "devDependencies": {
128
- "@djangocfg/api": "^2.1.433",
129
- "@djangocfg/centrifugo": "^2.1.433",
130
- "@djangocfg/debuger": "^2.1.433",
131
- "@djangocfg/i18n": "^2.1.433",
132
- "@djangocfg/monitor": "^2.1.433",
133
- "@djangocfg/typescript-config": "^2.1.433",
134
- "@djangocfg/ui-core": "^2.1.433",
135
- "@djangocfg/ui-tools": "^2.1.433",
128
+ "@djangocfg/api": "^2.1.434",
129
+ "@djangocfg/centrifugo": "^2.1.434",
130
+ "@djangocfg/debuger": "^2.1.434",
131
+ "@djangocfg/i18n": "^2.1.434",
132
+ "@djangocfg/monitor": "^2.1.434",
133
+ "@djangocfg/typescript-config": "^2.1.434",
134
+ "@djangocfg/ui-core": "^2.1.434",
135
+ "@djangocfg/ui-tools": "^2.1.434",
136
136
  "@types/node": "^25.2.3",
137
137
  "@types/react": "^19.2.15",
138
138
  "@types/react-dom": "^19.2.3",
@@ -121,24 +121,24 @@ const AuthContent: React.FC = memo(() => {
121
121
  });
122
122
 
123
123
  const AuthSuccessOverlay: React.FC = memo(() => {
124
- const { step, logoUrl, redirectUrl } = useAuthFormContext();
124
+ const { step, logo, redirectUrl } = useAuthFormContext();
125
125
 
126
126
  if (step !== 'success') {
127
127
  return null;
128
128
  }
129
129
 
130
- return <AuthSuccess logoUrl={logoUrl} redirectUrl={redirectUrl} />;
130
+ return <AuthSuccess logo={logo} redirectUrl={redirectUrl} />;
131
131
  });
132
132
 
133
133
  // AuthSuccess component - Apple-style success screen
134
134
  interface AuthSuccessInlineProps {
135
- logoUrl?: string;
135
+ logo?: React.ReactNode;
136
136
  redirectUrl?: string;
137
137
  redirectDelay?: number;
138
138
  }
139
139
 
140
140
  const AuthSuccess: React.FC<AuthSuccessInlineProps> = memo(({
141
- logoUrl,
141
+ logo,
142
142
  redirectUrl,
143
143
  redirectDelay = AUTH.REDIRECT_DELAY,
144
144
  }) => {
@@ -164,13 +164,13 @@ const AuthSuccess: React.FC<AuthSuccessInlineProps> = memo(({
164
164
  return (
165
165
  <div className="auth-success-overlay">
166
166
  <div className="auth-success-content">
167
- {logoUrl ? (
168
- <img
169
- src={logoUrl}
170
- alt=""
167
+ {logo ? (
168
+ <span
171
169
  className="auth-success-logo"
172
170
  style={{ opacity: isVisible ? 1 : 0 }}
173
- />
171
+ >
172
+ {logo}
173
+ </span>
174
174
  ) : (
175
175
  <div
176
176
  className="auth-success-check"
@@ -12,10 +12,20 @@ Shell-based authentication layout with three visual variants:
12
12
  floating over the `background` image; on mobile it collapses to a plain,
13
13
  frameless centered form (no image, no glass, no sidebar).
14
14
 
15
- The logo (when `logoUrl` is set) always renders above the form across all
15
+ The logo (when `logo` is set) always renders above the form across all
16
16
  variants; the `sidebar` slot should carry the quote/illustration, not a second
17
17
  brand mark.
18
18
 
19
+ `logo` is a **`ReactNode`** — pass an inline-SVG brand component, an `<img>`,
20
+ whatever. The layout owns the size: the node is dropped into a fixed slot
21
+ (`.auth-logo`, 56px; `.auth-success-logo`, 72px) and stretched to fill it
22
+ (`width/height: 100%`), so a `currentColor` SVG inherits the slot's size and
23
+ the surrounding text color — no per-theme asset swap needed.
24
+
25
+ ```tsx
26
+ <AuthLayout logo={<BrandMark className="text-foreground" />} … />
27
+ ```
28
+
19
29
  Supports: email OTP, phone OTP, GitHub OAuth, and **verifying** an existing 2FA
20
30
  challenge (TOTP + backup codes).
21
31
 
@@ -93,7 +103,7 @@ import { AuthLayout } from '@djangocfg/layouts';
93
103
  | `redirectUrl` | `string` | `/dashboard` | Where to redirect after auth |
94
104
  | `enableGithubAuth` | `boolean` | `false` | Show GitHub OAuth button |
95
105
  | `enablePhoneAuth` | `boolean` | `false` | Allow phone number input |
96
- | `logoUrl` | `string` | — | Logo shown above the form (centered only) and on the success screen |
106
+ | `logo` | `ReactNode` | — | Logo node shown above the form and on the success screen. Fills its slot (the layout sizes it); use a `currentColor` SVG for theme adaptivity |
97
107
  | `termsUrl` | `string` | — | Terms link — rendered in the passive consent line (opens in a new tab) |
98
108
  | `privacyUrl` | `string` | — | Privacy link — rendered in the passive consent line (opens in a new tab) |
99
109
  | `supportUrl` | `string` | — | Support page link |
@@ -3,7 +3,7 @@
3
3
  import React, { memo } from 'react';
4
4
 
5
5
  export interface AuthHeaderProps {
6
- logo?: string;
6
+ logo?: React.ReactNode;
7
7
  title: string;
8
8
  subtitle?: string;
9
9
  identifier?: string;
@@ -13,9 +13,8 @@ export interface AuthHeaderProps {
13
13
  /**
14
14
  * AuthHeader - Apple-style header with logo, title, and subtitle.
15
15
  *
16
- * Memoised: re-renders only when logo, title, subtitle, identifier or
17
- * className change. All props are primitives (strings), so the default
18
- * shallow comparison is sufficient.
16
+ * `logo` is any React node (inline-SVG brand component, `<img>`, …), rendered
17
+ * inside an `.auth-logo` wrapper so the existing logo sizing/spacing applies.
19
18
  */
20
19
  function AuthHeaderRaw({
21
20
  logo,
@@ -27,13 +26,9 @@ function AuthHeaderRaw({
27
26
  return (
28
27
  <div className={`auth-header ${className}`}>
29
28
  {logo && (
30
- <img
31
- src={logo}
32
- alt=""
33
- className="auth-logo"
34
- aria-hidden="true"
35
- draggable={false}
36
- />
29
+ <span className="auth-logo" aria-hidden="true">
30
+ {logo}
31
+ </span>
37
32
  )}
38
33
  <h1 className="auth-title">{title}</h1>
39
34
  {subtitle && (
@@ -35,7 +35,7 @@ function AuthOTPInputRaw({
35
35
  length = AUTH.TOTP_LENGTH,
36
36
  }: AuthOTPInputProps) {
37
37
  return (
38
- <div className="auth-otp-container auth-otp-wrapper">
38
+ <div className="auth-otp-container">
39
39
  <OTPInput
40
40
  length={length}
41
41
  validationMode="numeric"
@@ -46,7 +46,6 @@ function AuthOTPInputRaw({
46
46
  disabled={disabled}
47
47
  autoFocus={autoFocus}
48
48
  size={size}
49
- fluid
50
49
  />
51
50
  </div>
52
51
  );
@@ -42,7 +42,7 @@ function IdentifierStepRaw() {
42
42
  error,
43
43
  isRateLimited,
44
44
  rateLimitLabel,
45
- logoUrl,
45
+ logo,
46
46
  termsUrl,
47
47
  privacyUrl,
48
48
  enableGithubAuth,
@@ -76,7 +76,7 @@ function IdentifierStepRaw() {
76
76
 
77
77
  return (
78
78
  <AuthContainer step="identifier">
79
- {!hideHeader && <AuthHeader logo={logoUrl} title={content.title} subtitle={content.subtitle.email} />}
79
+ {!hideHeader && <AuthHeader logo={logo} title={content.title} subtitle={content.subtitle.email} />}
80
80
 
81
81
  <form onSubmit={handleIdentifierSubmit} className="auth-form-group">
82
82
  <Input
@@ -25,7 +25,7 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
25
25
  termsUrl,
26
26
  privacyUrl,
27
27
  enableGithubAuth = false,
28
- logoUrl,
28
+ logo,
29
29
  redirectUrl,
30
30
  onIdentifierSuccess,
31
31
  onOTPSuccess,
@@ -55,7 +55,7 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
55
55
  termsUrl,
56
56
  privacyUrl,
57
57
  enableGithubAuth,
58
- logoUrl,
58
+ logo,
59
59
  redirectUrl,
60
60
  }),
61
61
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -66,7 +66,7 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
66
66
  termsUrl,
67
67
  privacyUrl,
68
68
  enableGithubAuth,
69
- logoUrl,
69
+ logo,
70
70
  redirectUrl,
71
71
  ]
72
72
  );
@@ -71,14 +71,23 @@
71
71
  }
72
72
 
73
73
  .auth-logo {
74
+ /* The slot owns the size; the logo node (inline-SVG/<img>) fills it. */
75
+ display: inline-flex;
76
+ align-items: center;
77
+ justify-content: center;
74
78
  width: 56px;
75
79
  height: 56px;
76
80
  margin-bottom: 0.75rem;
77
- object-fit: contain;
78
81
  border-radius: 14px;
79
82
  animation: authLogoIn 0.5s var(--spring-bounce) both;
80
83
  }
81
84
 
85
+ .auth-logo > * {
86
+ width: 100%;
87
+ height: 100%;
88
+ object-fit: contain;
89
+ }
90
+
82
91
  @keyframes authLogoIn {
83
92
  from {
84
93
  opacity: 0;
@@ -469,44 +478,15 @@
469
478
 
470
479
  /* ===== OTP INPUT ===== */
471
480
 
481
+ /* OTP cells are styled + sized by ui-core's `OTPInput` (fixed-size slots via
482
+ * its `size` prop). The layout only centers the cluster — no width/border
483
+ * overrides here (those fought the component and made the cells stretch). */
472
484
  .auth-otp-container {
473
485
  display: flex;
474
486
  justify-content: center;
475
487
  width: 100%;
476
488
  }
477
489
 
478
- .auth-otp-wrapper {
479
- display: flex;
480
- gap: 0.375rem;
481
- width: 100%;
482
- }
483
-
484
- .auth-otp-wrapper input {
485
- width: 3rem;
486
- height: 3.5rem;
487
- font-size: 1.375rem;
488
- font-weight: 600;
489
- text-align: center;
490
- letter-spacing: -0.01em;
491
- background: var(--background);
492
- border: 1px solid color-mix(in oklab, var(--border) 70%, var(--foreground));
493
- border-radius: var(--auth-radius-sm);
494
- color: var(--foreground);
495
- transition:
496
- border-color 0.18s,
497
- box-shadow 0.18s;
498
- }
499
-
500
- .auth-otp-wrapper input:focus {
501
- outline: none;
502
- border-color: var(--ring);
503
- box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 12%, transparent);
504
- }
505
-
506
- .auth-otp-wrapper input:disabled {
507
- opacity: 0.45;
508
- }
509
-
510
490
  /* ===== QR CODE ===== */
511
491
 
512
492
  .auth-qr-container {
@@ -597,13 +577,22 @@
597
577
  }
598
578
 
599
579
  .auth-success-logo {
580
+ /* The slot owns the size; the logo node fills it. */
581
+ display: inline-flex;
582
+ align-items: center;
583
+ justify-content: center;
600
584
  width: 72px;
601
585
  height: 72px;
602
- object-fit: contain;
603
586
  border-radius: 16px;
604
587
  animation: authSuccessIn 0.5s var(--spring-bounce) both;
605
588
  }
606
589
 
590
+ .auth-success-logo > * {
591
+ width: 100%;
592
+ height: 100%;
593
+ object-fit: contain;
594
+ }
595
+
607
596
  .auth-success-check {
608
597
  width: 72px;
609
598
  height: 72px;
@@ -708,12 +697,6 @@
708
697
  .auth-title {
709
698
  font-size: 1.375rem;
710
699
  }
711
-
712
- .auth-otp-wrapper input {
713
- width: 2.625rem;
714
- height: 3.25rem;
715
- font-size: 1.125rem;
716
- }
717
700
  }
718
701
 
719
702
  /* ===== REDUCED MOTION ===== */
@@ -40,8 +40,9 @@ export interface AuthLayoutConfig {
40
40
  sourceUrl: string;
41
41
  /** Enable GitHub OAuth button */
42
42
  enableGithubAuth?: boolean;
43
- /** Logo URL for success screen (SVG recommended) */
44
- logoUrl?: string;
43
+ /** Logo node rendered above the form and on the success screen. Pass any
44
+ * React node — an inline-SVG brand component, an `<img>`, etc. */
45
+ logo?: React.ReactNode;
45
46
  /** URL to redirect after successful auth (default: /dashboard) */
46
47
  redirectUrl?: string;
47
48
  }
@@ -36,14 +36,10 @@ export {
36
36
  NavActionItem,
37
37
  NavControls,
38
38
  NavDesktopItems,
39
- ThemeBrandMark,
40
- ThemeBrandMarkImg,
41
39
  } from './primitives';
42
40
  export type {
43
41
  NavAction,
44
42
  NavControlsProps,
45
- ThemeBrandMarkProps,
46
- ThemeBrandMarkImgProps,
47
43
  } from './primitives';
48
44
 
49
45
  // Navbar variants
@@ -5,5 +5,3 @@ export type { NavAction } from './NavActionItem';
5
5
  export { NavControls } from './NavControls';
6
6
  export type { NavControlsProps } from './NavControls';
7
7
  export { NavDesktopItems } from './NavDesktopItems';
8
- export { ThemeBrandMark, ThemeBrandMarkImg } from './ThemeBrandMark';
9
- export type { ThemeBrandMarkProps, ThemeBrandMarkImgProps } from './ThemeBrandMark';
@@ -53,7 +53,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
53
53
  termsUrl,
54
54
  privacyUrl,
55
55
  enableGithubAuth = false,
56
- logoUrl,
56
+ logo,
57
57
  redirectUrl = '/dashboard',
58
58
 
59
59
  // Initial state
@@ -151,7 +151,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
151
151
  termsUrl,
152
152
  privacyUrl,
153
153
  enableGithubAuth,
154
- logoUrl,
154
+ logo,
155
155
  redirectUrl,
156
156
  };
157
157
  }, [
@@ -174,7 +174,7 @@ export const MockAuthFormProvider: React.FC<MockAuthFormProviderProps> = ({
174
174
  termsUrl,
175
175
  privacyUrl,
176
176
  enableGithubAuth,
177
- logoUrl,
177
+ logo,
178
178
  redirectUrl,
179
179
  ]);
180
180
 
@@ -1,75 +0,0 @@
1
- /**
2
- * Theme-aware brand mark for public chrome (navbar, footer).
3
- *
4
- * Uses two layers and Tailwind `dark:` so the correct asset is chosen with **no JS theme hook**
5
- * and **no hydration mismatch** — the same thing `next-themes` does for `html.dark` / class strategy.
6
- *
7
- * Prefer this over `useTheme().resolvedTheme` + one `<img>` unless you have a strong reason
8
- * (single-DOM node only).
9
- */
10
-
11
- 'use client';
12
-
13
- import React, { type ReactNode } from 'react';
14
-
15
- import { cn } from '@djangocfg/ui-core/lib';
16
-
17
- export interface ThemeBrandMarkProps {
18
- /**
19
- * Mark when the app is in **light** appearance (`html` without `.dark`).
20
- * Usually a dark-colored logo on a light bar.
21
- */
22
- light: ReactNode;
23
- /**
24
- * Mark when the app is in **dark** appearance (`html.dark`).
25
- * Usually a light-colored logo on a dark bar.
26
- */
27
- dark: ReactNode;
28
- /** Outer wrapper — keep sizing here (e.g. `h-5.5 w-auto`). */
29
- className?: string;
30
- /** `role="img"` label when children are decorative. */
31
- 'aria-label'?: string;
32
- }
33
-
34
- export function ThemeBrandMark({ light, dark, className, 'aria-label': ariaLabel }: ThemeBrandMarkProps) {
35
- return (
36
- <span
37
- className={cn('inline-flex shrink-0 items-center justify-center', className)}
38
- role={ariaLabel ? 'img' : undefined}
39
- aria-label={ariaLabel}
40
- >
41
- <span className="flex items-center justify-center dark:hidden">{light}</span>
42
- <span className="hidden items-center justify-center dark:flex">{dark}</span>
43
- </span>
44
- );
45
- }
46
-
47
- export interface ThemeBrandMarkImgProps {
48
- /** Logo URL for light UI (no `html.dark`). */
49
- srcLight: string;
50
- /** Logo URL for dark UI (`html.dark`). */
51
- srcDark: string;
52
- alt?: string;
53
- /** Applied to both images (e.g. `h-5.5 w-auto object-contain`). */
54
- className?: string;
55
- wrapperClassName?: string;
56
- 'aria-label'?: string;
57
- }
58
-
59
- export function ThemeBrandMarkImg({
60
- srcLight,
61
- srcDark,
62
- alt = '',
63
- className,
64
- wrapperClassName,
65
- 'aria-label': ariaLabel,
66
- }: ThemeBrandMarkImgProps) {
67
- return (
68
- <ThemeBrandMark
69
- className={wrapperClassName}
70
- aria-label={ariaLabel}
71
- light={<img src={srcLight} alt={alt} className={className} />}
72
- dark={<img src={srcDark} alt={alt} className={className} />}
73
- />
74
- );
75
- }