@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 +15 -15
- package/src/layouts/AuthLayout/AuthLayout.tsx +9 -9
- package/src/layouts/AuthLayout/README.md +12 -2
- package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +6 -11
- package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +1 -2
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +2 -2
- package/src/layouts/AuthLayout/context.tsx +3 -3
- package/src/layouts/AuthLayout/styles/auth.css +23 -40
- package/src/layouts/AuthLayout/types.ts +3 -2
- package/src/layouts/PublicLayout/index.ts +0 -4
- package/src/layouts/PublicLayout/primitives/index.ts +0 -2
- package/src/testing/MockAuthFormProvider.tsx +3 -3
- package/src/layouts/PublicLayout/primitives/ThemeBrandMark.tsx +0 -75
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
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.
|
|
93
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
94
|
-
"@djangocfg/debuger": "^2.1.
|
|
95
|
-
"@djangocfg/i18n": "^2.1.
|
|
96
|
-
"@djangocfg/monitor": "^2.1.
|
|
97
|
-
"@djangocfg/ui-core": "^2.1.
|
|
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.
|
|
129
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
130
|
-
"@djangocfg/debuger": "^2.1.
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
132
|
-
"@djangocfg/monitor": "^2.1.
|
|
133
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
134
|
-
"@djangocfg/ui-core": "^2.1.
|
|
135
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
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,
|
|
124
|
+
const { step, logo, redirectUrl } = useAuthFormContext();
|
|
125
125
|
|
|
126
126
|
if (step !== 'success') {
|
|
127
127
|
return null;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
return <AuthSuccess
|
|
130
|
+
return <AuthSuccess logo={logo} redirectUrl={redirectUrl} />;
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
// AuthSuccess component - Apple-style success screen
|
|
134
134
|
interface AuthSuccessInlineProps {
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
168
|
-
<
|
|
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 `
|
|
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
|
-
| `
|
|
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?:
|
|
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
|
-
*
|
|
17
|
-
*
|
|
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
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
-
|
|
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={
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|