@djangocfg/layouts 2.1.482 → 2.1.484
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/README.md +2 -2
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +18 -0
- package/src/layouts/AuthLayout/context.tsx +3 -0
- package/src/layouts/AuthLayout/types.ts +13 -2
- package/src/layouts/PublicLayout/components/UserMenu.tsx +2 -2
- package/src/layouts/PublicLayout/primitives/NavActionItem.tsx +17 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.484",
|
|
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/analytics": "^2.1.
|
|
93
|
-
"@djangocfg/api": "^2.1.
|
|
94
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
95
|
-
"@djangocfg/devtools": "^2.1.
|
|
96
|
-
"@djangocfg/i18n": "^2.1.
|
|
97
|
-
"@djangocfg/ui-core": "^2.1.
|
|
92
|
+
"@djangocfg/analytics": "^2.1.484",
|
|
93
|
+
"@djangocfg/api": "^2.1.484",
|
|
94
|
+
"@djangocfg/centrifugo": "^2.1.484",
|
|
95
|
+
"@djangocfg/devtools": "^2.1.484",
|
|
96
|
+
"@djangocfg/i18n": "^2.1.484",
|
|
97
|
+
"@djangocfg/ui-core": "^2.1.484",
|
|
98
98
|
"@hookform/resolvers": "^5.2.2",
|
|
99
99
|
"consola": "^3.4.2",
|
|
100
100
|
"lucide-react": "^0.545.0",
|
|
@@ -124,14 +124,14 @@
|
|
|
124
124
|
"uuid": "^11.1.1"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
|
-
"@djangocfg/analytics": "^2.1.
|
|
128
|
-
"@djangocfg/api": "^2.1.
|
|
129
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
130
|
-
"@djangocfg/devtools": "^2.1.
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
132
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
133
|
-
"@djangocfg/ui-core": "^2.1.
|
|
134
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
127
|
+
"@djangocfg/analytics": "^2.1.484",
|
|
128
|
+
"@djangocfg/api": "^2.1.484",
|
|
129
|
+
"@djangocfg/centrifugo": "^2.1.484",
|
|
130
|
+
"@djangocfg/devtools": "^2.1.484",
|
|
131
|
+
"@djangocfg/i18n": "^2.1.484",
|
|
132
|
+
"@djangocfg/typescript-config": "^2.1.484",
|
|
133
|
+
"@djangocfg/ui-core": "^2.1.484",
|
|
134
|
+
"@djangocfg/ui-tools": "^2.1.484",
|
|
135
135
|
"@types/node": "^25.9.5",
|
|
136
136
|
"@types/react": "19.2.15",
|
|
137
137
|
"@types/react-dom": "19.2.3",
|
|
@@ -50,7 +50,7 @@ import { AuthLayout } from '@djangocfg/layouts';
|
|
|
50
50
|
|
|
51
51
|
<AuthLayout
|
|
52
52
|
sourceUrl="https://myapp.com"
|
|
53
|
-
redirectUrl="/
|
|
53
|
+
redirectUrl="/private"
|
|
54
54
|
enableGithubAuth
|
|
55
55
|
termsUrl="/terms"
|
|
56
56
|
privacyUrl="/privacy"
|
|
@@ -100,7 +100,7 @@ import { AuthLayout } from '@djangocfg/layouts';
|
|
|
100
100
|
| `background` | `AuthBackgroundConfig` | — | Background image/gradient/overlay/blur |
|
|
101
101
|
| `sidebar` | `ReactNode` | — | Right column content (split variant only) |
|
|
102
102
|
| `sourceUrl` | `string` | — | App URL for analytics/tracking |
|
|
103
|
-
| `redirectUrl` | `string` | `/
|
|
103
|
+
| `redirectUrl` | `string` | `/private` | Where to redirect after auth |
|
|
104
104
|
| `enableGithubAuth` | `boolean` | `false` | Show GitHub OAuth button |
|
|
105
105
|
| `enablePhoneAuth` | `boolean` | `false` | Allow phone number input |
|
|
106
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 |
|
|
@@ -48,9 +48,12 @@ function IdentifierStepRaw() {
|
|
|
48
48
|
enableGithubAuth,
|
|
49
49
|
sourceUrl,
|
|
50
50
|
marketingConsent,
|
|
51
|
+
rememberMe,
|
|
51
52
|
marketingConsentConfig,
|
|
53
|
+
rememberMeConfig,
|
|
52
54
|
setIdentifier,
|
|
53
55
|
setMarketingConsent,
|
|
56
|
+
setRememberMe,
|
|
54
57
|
setError,
|
|
55
58
|
handleIdentifierSubmit,
|
|
56
59
|
} = useAuthFormContext();
|
|
@@ -100,6 +103,7 @@ function IdentifierStepRaw() {
|
|
|
100
103
|
// GitHub OAuth
|
|
101
104
|
const { isLoading: isGithubLoading, startGithubAuth } = useGithubAuth({
|
|
102
105
|
sourceUrl,
|
|
106
|
+
rememberMe,
|
|
103
107
|
onError: setError,
|
|
104
108
|
});
|
|
105
109
|
|
|
@@ -142,6 +146,20 @@ function IdentifierStepRaw() {
|
|
|
142
146
|
</label>
|
|
143
147
|
)}
|
|
144
148
|
|
|
149
|
+
{rememberMeConfig?.enabled !== false && (
|
|
150
|
+
<label className="auth-remember">
|
|
151
|
+
<Checkbox
|
|
152
|
+
checked={rememberMe}
|
|
153
|
+
onCheckedChange={(checked) => setRememberMe(checked === true)}
|
|
154
|
+
disabled={isLoading}
|
|
155
|
+
className="auth-remember-checkbox"
|
|
156
|
+
/>
|
|
157
|
+
<span className="auth-remember-label">
|
|
158
|
+
{rememberMeConfig?.label ?? 'Keep me signed in for 30 days'}
|
|
159
|
+
</span>
|
|
160
|
+
</label>
|
|
161
|
+
)}
|
|
162
|
+
|
|
145
163
|
<AuthError message={error} />
|
|
146
164
|
|
|
147
165
|
<AuthButton
|
|
@@ -28,6 +28,7 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
|
|
|
28
28
|
logo,
|
|
29
29
|
redirectUrl,
|
|
30
30
|
marketingConsent,
|
|
31
|
+
rememberMe,
|
|
31
32
|
onIdentifierSuccess,
|
|
32
33
|
onOTPSuccess,
|
|
33
34
|
onError,
|
|
@@ -64,6 +65,7 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
|
|
|
64
65
|
redirectUrl,
|
|
65
66
|
// Renamed: `marketingConsent` (the name) is the checkbox STATE from the form hook.
|
|
66
67
|
marketingConsentConfig: marketingConsent,
|
|
68
|
+
rememberMeConfig: rememberMe,
|
|
67
69
|
}),
|
|
68
70
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
69
71
|
[
|
|
@@ -76,6 +78,7 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
|
|
|
76
78
|
logo,
|
|
77
79
|
redirectUrl,
|
|
78
80
|
marketingConsent,
|
|
81
|
+
rememberMe,
|
|
79
82
|
]
|
|
80
83
|
);
|
|
81
84
|
|
|
@@ -50,6 +50,14 @@ export interface AuthMarketingConsentConfig {
|
|
|
50
50
|
defaultChecked?: boolean;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** Persistent-login checkbox on the identifier step. */
|
|
54
|
+
export interface AuthRememberMeConfig {
|
|
55
|
+
/** Render the checkbox. Default: true. */
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
/** Checkbox label. Default: "Keep me signed in for 30 days". */
|
|
58
|
+
label?: React.ReactNode;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
export interface AuthLayoutConfig {
|
|
54
62
|
/** Support page URL */
|
|
55
63
|
supportUrl?: string;
|
|
@@ -64,10 +72,12 @@ export interface AuthLayoutConfig {
|
|
|
64
72
|
/** Logo node rendered above the form and on the success screen. Pass any
|
|
65
73
|
* React node — an inline-SVG brand component, an `<img>`, etc. */
|
|
66
74
|
logo?: React.ReactNode;
|
|
67
|
-
/** URL to redirect after successful auth (default: /
|
|
75
|
+
/** URL to redirect after successful auth (default: /private) */
|
|
68
76
|
redirectUrl?: string;
|
|
69
77
|
/** Optional marketing-consent checkbox on the identifier step. Absent ⇒ no checkbox, OTP body unchanged. */
|
|
70
78
|
marketingConsent?: AuthMarketingConsentConfig;
|
|
79
|
+
/** Optional persistent-login control. */
|
|
80
|
+
rememberMe?: AuthRememberMeConfig;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -77,8 +87,9 @@ export interface AuthLayoutConfig {
|
|
|
77
87
|
// `marketingConsent` is omitted from the config side of the merge: the name is
|
|
78
88
|
// taken by the checkbox *state* (boolean | null) from AuthFormReturn, so the
|
|
79
89
|
// prop object is re-exposed as `marketingConsentConfig`.
|
|
80
|
-
export interface AuthFormContextType extends AuthFormReturn, Omit<AuthLayoutConfig, 'marketingConsent'> {
|
|
90
|
+
export interface AuthFormContextType extends AuthFormReturn, Omit<AuthLayoutConfig, 'marketingConsent' | 'rememberMe'> {
|
|
81
91
|
marketingConsentConfig?: AuthMarketingConsentConfig;
|
|
92
|
+
rememberMeConfig?: AuthRememberMeConfig;
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -140,14 +140,14 @@ export function UserMenu({
|
|
|
140
140
|
}
|
|
141
141
|
if (disableAuth) {
|
|
142
142
|
return (
|
|
143
|
-
<Button variant="default" size="sm" className="rounded-full px-5" disabled>
|
|
143
|
+
<Button variant="default" size="sm" className="h-9 rounded-full px-5" disabled>
|
|
144
144
|
{labels.signIn}
|
|
145
145
|
</Button>
|
|
146
146
|
);
|
|
147
147
|
}
|
|
148
148
|
return (
|
|
149
149
|
<Link href={resolvedAuthPath}>
|
|
150
|
-
<Button variant="default" size="sm" className="rounded-full px-5">
|
|
150
|
+
<Button variant="default" size="sm" className="h-9 rounded-full px-5">
|
|
151
151
|
{labels.signIn}
|
|
152
152
|
</Button>
|
|
153
153
|
</Link>
|
|
@@ -22,10 +22,11 @@ export interface NavAction {
|
|
|
22
22
|
* - `link` — plain text link
|
|
23
23
|
* - `ghost` — transparent pill, fills on hover (default)
|
|
24
24
|
* - `outline` — bordered pill
|
|
25
|
+
* - `secondary` — solid neutral pill (a real action, not the main CTA)
|
|
25
26
|
* - `primary` — solid high-contrast pill (main CTA)
|
|
26
27
|
* @default 'ghost'
|
|
27
28
|
*/
|
|
28
|
-
variant?: 'link' | 'ghost' | 'outline' | 'primary';
|
|
29
|
+
variant?: 'link' | 'ghost' | 'outline' | 'secondary' | 'primary';
|
|
29
30
|
/** Hide on screens below lg (useful for secondary CTAs). @default false */
|
|
30
31
|
hideOnSmall?: boolean;
|
|
31
32
|
/** Optional inline icon (use lucide-react or any ReactNode). */
|
|
@@ -37,15 +38,27 @@ export interface NavAction {
|
|
|
37
38
|
const baseCls =
|
|
38
39
|
'inline-flex shrink-0 items-center justify-center gap-1.5 rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/35';
|
|
39
40
|
|
|
41
|
+
// Height is FIXED (`h-9`), not a `min-h-9` floor. With a minimum the pill grew
|
|
42
|
+
// past its neighbours as soon as it carried an icon — the navbar's own UserMenu
|
|
43
|
+
// login button is a ui-core `Button size="sm"` (a fixed `h-8`), so a growing CTA
|
|
44
|
+
// sat visibly taller than the control right next to it. A nav action is a
|
|
45
|
+
// control in a row of controls: its height must be a constant, and the label is
|
|
46
|
+
// already truncated rather than wrapped.
|
|
40
47
|
const variantCls: Record<NonNullable<NavAction['variant']>, string> = {
|
|
41
48
|
link:
|
|
42
49
|
'px-1.5 py-1 text-foreground/85 hover:text-foreground',
|
|
43
50
|
ghost:
|
|
44
|
-
'px-4
|
|
51
|
+
'h-9 px-4 text-foreground/90 hover:bg-accent/55 hover:text-foreground',
|
|
45
52
|
outline:
|
|
46
|
-
'px-4
|
|
53
|
+
'h-9 px-4 border border-border/70 text-foreground/90 hover:bg-accent/40 hover:text-foreground',
|
|
54
|
+
// Filled but neutral — the middle rung between `outline` (which disappears
|
|
55
|
+
// into a dark navbar) and `primary` (which competes with the one real CTA).
|
|
56
|
+
// Uses the `secondary` token pair rather than a hand-mixed tint so it retunes
|
|
57
|
+
// with the active preset.
|
|
58
|
+
secondary:
|
|
59
|
+
'h-9 px-4 bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm',
|
|
47
60
|
primary:
|
|
48
|
-
'px-4
|
|
61
|
+
'h-9 px-4 bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm',
|
|
49
62
|
};
|
|
50
63
|
|
|
51
64
|
interface NavActionItemProps {
|