@djangocfg/layouts 2.1.356 → 2.1.357

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 (75) hide show
  1. package/package.json +17 -17
  2. package/src/layouts/AdminLayout/AdminLayout.tsx +2 -1
  3. package/src/layouts/AppLayout/AppLayout.tsx +35 -15
  4. package/src/layouts/AppLayout/BaseApp.tsx +2 -2
  5. package/src/layouts/AuthLayout/AuthLayout.tsx +26 -19
  6. package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +10 -4
  7. package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +11 -5
  8. package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +10 -10
  9. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +11 -5
  10. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -5
  11. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +11 -5
  12. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +10 -10
  13. package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +11 -5
  14. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +28 -20
  15. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +11 -5
  16. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +12 -4
  17. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +9 -4
  18. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +12 -5
  19. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +9 -4
  20. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +11 -5
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +15 -5
  22. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +9 -4
  23. package/src/layouts/AuthLayout/context.tsx +35 -13
  24. package/src/layouts/AuthLayout/shells/AuthShell.tsx +11 -4
  25. package/src/layouts/AuthLayout/shells/CenteredShell.tsx +10 -4
  26. package/src/layouts/AuthLayout/shells/SplitShell.tsx +10 -4
  27. package/src/layouts/AuthLayout/shells/context.tsx +16 -5
  28. package/src/layouts/PrivateLayout/PrivateLayout.tsx +32 -247
  29. package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +115 -426
  30. package/src/layouts/{_components → PrivateLayout/components}/PrivateSidebarAccount.tsx +40 -19
  31. package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +165 -0
  32. package/src/layouts/{_components → PrivateLayout/components}/SidebarFeatured.tsx +2 -2
  33. package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +189 -0
  34. package/src/layouts/PrivateLayout/components/SidebarNavItem.tsx +137 -0
  35. package/src/layouts/PrivateLayout/components/SidebarSlots.tsx +71 -0
  36. package/src/layouts/PrivateLayout/components/index.ts +4 -0
  37. package/src/layouts/PrivateLayout/context.tsx +211 -0
  38. package/src/layouts/PrivateLayout/density.ts +48 -0
  39. package/src/layouts/PrivateLayout/hooks/index.ts +13 -0
  40. package/src/layouts/PrivateLayout/hooks/useAuthGuard.ts +54 -0
  41. package/src/layouts/PrivateLayout/hooks/useHoverExpand.ts +103 -0
  42. package/src/layouts/PrivateLayout/hooks/useLayoutVisual.ts +113 -0
  43. package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +207 -0
  44. package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +115 -0
  45. package/src/layouts/PrivateLayout/index.ts +2 -2
  46. package/src/layouts/PrivateLayout/types.ts +187 -0
  47. package/src/layouts/ProfileLayout/ProfileLayout.tsx +44 -183
  48. package/src/layouts/ProfileLayout/README.md +58 -0
  49. package/src/layouts/ProfileLayout/components/ApiKeySection/ApiKeySection.tsx +197 -0
  50. package/src/layouts/ProfileLayout/components/ApiKeySection/context.tsx +159 -0
  51. package/src/layouts/ProfileLayout/components/ApiKeySection/index.ts +3 -0
  52. package/src/layouts/ProfileLayout/components/ProfileHeader.tsx +110 -0
  53. package/src/layouts/ProfileLayout/components/ProfileTab.tsx +29 -0
  54. package/src/layouts/ProfileLayout/components/{TwoFactorSection.tsx → TwoFactorSection/TwoFactorSection.tsx} +1 -1
  55. package/src/layouts/ProfileLayout/components/TwoFactorSection/index.ts +1 -0
  56. package/src/layouts/ProfileLayout/components/index.ts +4 -2
  57. package/src/layouts/ProfileLayout/context.tsx +4 -6
  58. package/src/layouts/ProfileLayout/hooks/index.ts +2 -0
  59. package/src/layouts/ProfileLayout/hooks/useProfileTabs.ts +43 -0
  60. package/src/layouts/ProfileLayout/index.ts +6 -3
  61. package/src/layouts/ProfileLayout/types.ts +37 -0
  62. package/src/layouts/{_components → PublicLayout/components}/UserMenu.tsx +3 -3
  63. package/src/layouts/PublicLayout/components/index.ts +4 -0
  64. package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +12 -2
  65. package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +1 -1
  66. package/src/layouts/PublicLayout/primitives/NavActions.tsx +44 -3
  67. package/src/layouts/PublicLayout/primitives/NavBrand.tsx +4 -2
  68. package/src/layouts/PublicLayout/primitives/NavDesktopItems.tsx +42 -2
  69. package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +1 -1
  70. package/src/layouts/PublicLayout/shared/NavbarShell.tsx +60 -1
  71. package/src/layouts/_components/index.ts +2 -7
  72. package/src/layouts/index.ts +9 -4
  73. package/src/layouts/ProfileLayout/__tests__/TwoFactorSection.test.tsx +0 -234
  74. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +0 -198
  75. /package/src/layouts/{_components → PublicLayout/components}/UserAvatar.tsx +0 -0
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { useMemo } from 'react';
3
+ import React, { memo, useMemo } from 'react';
4
4
 
5
5
  import { useAppT } from '@djangocfg/i18n';
6
6
  import { Checkbox } from '@djangocfg/ui-core/components';
@@ -15,16 +15,20 @@ export interface TermsCheckboxProps {
15
15
  }
16
16
 
17
17
  /**
18
- * TermsCheckbox - Compact terms acceptance checkbox
18
+ * TermsCheckbox - Compact terms acceptance checkbox.
19
+ *
20
+ * Memoised: re-renders only when checked, termsUrl, privacyUrl, disabled
21
+ * or className change. `onChange` is compared by reference — callers
22
+ * should stabilise it with useCallback.
19
23
  */
20
- export const TermsCheckbox: React.FC<TermsCheckboxProps> = ({
24
+ function TermsCheckboxRaw({
21
25
  checked,
22
26
  onChange,
23
27
  termsUrl,
24
28
  privacyUrl,
25
29
  disabled = false,
26
30
  className = '',
27
- }) => {
31
+ }: TermsCheckboxProps) {
28
32
  const t = useAppT();
29
33
  const labels = useMemo(() => ({
30
34
  agree: t('layouts.auth.terms.agree'),
@@ -63,4 +67,6 @@ export const TermsCheckbox: React.FC<TermsCheckboxProps> = ({
63
67
  </label>
64
68
  </div>
65
69
  );
66
- };
70
+ }
71
+
72
+ export const TermsCheckbox = memo(TermsCheckboxRaw);
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Github } from 'lucide-react';
4
- import React, { useMemo } from 'react';
4
+ import React, { memo, useMemo } from 'react';
5
5
 
6
6
  import { useGithubAuth } from '@djangocfg/api/auth';
7
7
  import { useAppT } from '@djangocfg/i18n';
@@ -20,15 +20,21 @@ import {
20
20
  } from '../shared';
21
21
 
22
22
  /**
23
- * IdentifierStep - Apple-style email input step
23
+ * IdentifierStep - Apple-style email input step.
24
24
  *
25
25
  * Clean, minimal design with:
26
26
  * - Optional logo
27
27
  * - Single email input field
28
28
  * - Terms checkbox
29
29
  * - OAuth options
30
+ *
31
+ * Memoised: this component has no props; it reads everything from
32
+ * context. The memo wrapper prevents re-renders when the parent
33
+ * (AuthContent) re-renders due to step changes in a sibling branch.
34
+ * Internal context selectors still trigger updates when relevant
35
+ * fields change.
30
36
  */
31
- export const IdentifierStep: React.FC = () => {
37
+ function IdentifierStepRaw() {
32
38
  const { hideHeader } = useAuthLayoutContext();
33
39
 
34
40
  const {
@@ -131,4 +137,6 @@ export const IdentifierStep: React.FC = () => {
131
137
  />
132
138
  </AuthContainer>
133
139
  );
134
- };
140
+ }
141
+
142
+ export const IdentifierStep = memo(IdentifierStepRaw);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { useCallback, useMemo, useState } from 'react';
3
+ import React, { memo, useCallback, useMemo, useState } from 'react';
4
4
 
5
5
  import { useAppT } from '@djangocfg/i18n';
6
6
 
@@ -19,15 +19,18 @@ import {
19
19
  type SubmitState = 'idle' | 'submitting';
20
20
 
21
21
  /**
22
- * OTPStep - Apple-style OTP verification
22
+ * OTPStep - Apple-style OTP verification.
23
23
  *
24
24
  * Minimal design focused on the OTP input:
25
25
  * - Clear title
26
26
  * - Masked identifier for privacy
27
27
  * - Large OTP input
28
28
  * - Text links for actions
29
+ *
30
+ * Memoised: no props; reads from context. Prevents re-renders when
31
+ * AuthContent switches steps or when sibling branches update.
29
32
  */
30
- export const OTPStep: React.FC = () => {
33
+ function OTPStepRaw() {
31
34
  const {
32
35
  identifier,
33
36
  otp,
@@ -121,4 +124,6 @@ export const OTPStep: React.FC = () => {
121
124
  </form>
122
125
  </AuthContainer>
123
126
  );
124
- };
127
+ }
128
+
129
+ export const OTPStep = memo(OTPStepRaw);
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Check, Copy } from 'lucide-react';
4
- import React, { useMemo } from 'react';
4
+ import React, { memo, useMemo } from 'react';
5
5
 
6
6
  import { useAppT } from '@djangocfg/i18n';
7
7
 
@@ -15,13 +15,18 @@ interface SetupCompleteProps {
15
15
  }
16
16
 
17
17
  /**
18
- * SetupComplete - Backup codes display after 2FA setup
18
+ * SetupComplete - Backup codes display after 2FA setup.
19
+ *
20
+ * Memoised: re-renders only when `backupCodes` array reference,
21
+ * `backupCodesWarning`, or `onDone` change. The array content is
22
+ * compared by reference — the parent should not recreate the array
23
+ * on every render.
19
24
  */
20
- export const SetupComplete: React.FC<SetupCompleteProps> = ({
25
+ function SetupCompleteRaw({
21
26
  backupCodes,
22
27
  backupCodesWarning,
23
28
  onDone,
24
- }) => {
29
+ }: SetupCompleteProps) {
25
30
  const { copied, copy } = useCopyToClipboard();
26
31
  const t = useAppT();
27
32
  const content = useMemo(() => ({
@@ -76,4 +81,6 @@ export const SetupComplete: React.FC<SetupCompleteProps> = ({
76
81
  </div>
77
82
  </AuthContainer>
78
83
  );
79
- };
84
+ }
85
+
86
+ export const SetupComplete = memo(SetupCompleteRaw);
@@ -1,15 +1,18 @@
1
1
  'use client';
2
2
 
3
- import React, { useMemo } from 'react';
3
+ import React, { memo, useMemo } from 'react';
4
4
 
5
5
  import { useAppT } from '@djangocfg/i18n';
6
6
 
7
7
  import { AuthButton, AuthContainer, AuthHeader } from '../../shared';
8
8
 
9
9
  /**
10
- * SetupLoading - Loading state for 2FA setup
10
+ * SetupLoading - Loading state for 2FA setup.
11
+ *
12
+ * Memoised: no props; pure translation-driven component. Prevents
13
+ * re-renders when parent orchestrators re-render for unrelated reasons.
11
14
  */
12
- export const SetupLoading: React.FC = () => {
15
+ function SetupLoadingRaw() {
13
16
  const t = useAppT();
14
17
  const content = useMemo(() => ({
15
18
  title: t('layouts.auth.setup.loading.title'),
@@ -26,4 +29,6 @@ export const SetupLoading: React.FC = () => {
26
29
  </div>
27
30
  </AuthContainer>
28
31
  );
29
- };
32
+ }
33
+
34
+ export const SetupLoading = memo(SetupLoadingRaw);
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Check, Copy } from 'lucide-react';
4
- import React, { useMemo, useState } from 'react';
4
+ import React, { memo, useMemo, useState } from 'react';
5
5
  import { QRCodeSVG } from 'qrcode.react';
6
6
 
7
7
  import { useAppT } from '@djangocfg/i18n';
@@ -32,16 +32,20 @@ interface SetupQRCodeProps {
32
32
  }
33
33
 
34
34
  /**
35
- * SetupQRCode - QR code scanning step for 2FA setup
35
+ * SetupQRCode - QR code scanning step for 2FA setup.
36
+ *
37
+ * Memoised: re-renders only when `provisioningUri`, `secret`, `isLoading`,
38
+ * `error`, `onConfirm` or `onSkip` change. `onConfirm` and `onSkip` are
39
+ * compared by reference — callers should stabilise them with useCallback.
36
40
  */
37
- export const SetupQRCode: React.FC<SetupQRCodeProps> = ({
41
+ function SetupQRCodeRaw({
38
42
  provisioningUri,
39
43
  secret,
40
44
  isLoading,
41
45
  error,
42
46
  onConfirm,
43
47
  onSkip,
44
- }) => {
48
+ }: SetupQRCodeProps) {
45
49
  const [confirmCode, setConfirmCode] = useState('');
46
50
  const { copied, copy } = useCopyToClipboard();
47
51
  const t = useAppT();
@@ -132,4 +136,6 @@ export const SetupQRCode: React.FC<SetupQRCodeProps> = ({
132
136
  </form>
133
137
  </AuthContainer>
134
138
  );
135
- };
139
+ }
140
+
141
+ export const SetupQRCode = memo(SetupQRCodeRaw);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React from 'react';
3
+ import React, { memo } from 'react';
4
4
 
5
5
  import { useTwoFactorSetup } from '@djangocfg/api/auth';
6
6
 
@@ -17,8 +17,11 @@ export interface SetupStepProps {
17
17
  /**
18
18
  * SetupStep - Orchestrator for 2FA setup flow (requires AuthFormContext).
19
19
  * For use outside AuthLayout use SetupStepStandalone.
20
+ *
21
+ * Memoised: re-renders only when `onComplete` or `onSkip` references
22
+ * change. Callers should stabilise these callbacks with useCallback.
20
23
  */
21
- export const SetupStep: React.FC<SetupStepProps> = ({ onComplete, onSkip }) => {
24
+ function SetupStepRaw({ onComplete, onSkip }: SetupStepProps) {
22
25
  const { setStep } = useAuthFormContext();
23
26
 
24
27
  const handleComplete = () => {
@@ -84,13 +87,18 @@ export const SetupStep: React.FC<SetupStepProps> = ({ onComplete, onSkip }) => {
84
87
 
85
88
  // Fallback to loading
86
89
  return <SetupLoading />;
87
- };
90
+ }
91
+
92
+ export const SetupStep = memo(SetupStepRaw);
88
93
 
89
94
  /**
90
95
  * SetupStepStandalone — same flow as SetupStep but without AuthFormContext.
91
96
  * Use this inside ProfileLayout or any page outside AuthLayout.
97
+ *
98
+ * Memoised: re-renders only when `onComplete` or `onSkip` references
99
+ * change. Callers should stabilise these callbacks with useCallback.
92
100
  */
93
- export const SetupStepStandalone: React.FC<SetupStepProps> = ({ onComplete, onSkip }) => {
101
+ function SetupStepStandaloneRaw({ onComplete, onSkip }: SetupStepProps) {
94
102
  const {
95
103
  isLoading,
96
104
  error,
@@ -132,4 +140,6 @@ export const SetupStepStandalone: React.FC<SetupStepProps> = ({ onComplete, onSk
132
140
  }
133
141
 
134
142
  return <SetupLoading />;
135
- };
143
+ }
144
+
145
+ export const SetupStepStandalone = memo(SetupStepStandaloneRaw);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { useMemo } from 'react';
3
+ import React, { memo, useMemo } from 'react';
4
4
 
5
5
  import { useAppT } from '@djangocfg/i18n';
6
6
 
@@ -16,14 +16,17 @@ import {
16
16
  } from '../shared';
17
17
 
18
18
  /**
19
- * TwoFactorStep - Apple-style 2FA verification
19
+ * TwoFactorStep - Apple-style 2FA verification.
20
20
  *
21
21
  * Minimal design with:
22
22
  * - TOTP code input (default)
23
23
  * - Backup code input (alternate)
24
24
  * - Clean toggle between modes
25
+ *
26
+ * Memoised: no props; reads from context. Prevents re-renders when
27
+ * AuthContent switches steps or sibling branches update.
25
28
  */
26
- export const TwoFactorStep: React.FC = () => {
29
+ function TwoFactorStepRaw() {
27
30
  const {
28
31
  twoFactorCode,
29
32
  useBackupCode,
@@ -125,4 +128,6 @@ export const TwoFactorStep: React.FC = () => {
125
128
  </div>
126
129
  </AuthContainer>
127
130
  );
128
- };
131
+ }
132
+
133
+ export const TwoFactorStep = memo(TwoFactorStepRaw);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { createContext, useContext } from 'react';
3
+ import React, { createContext, useContext, useMemo } from 'react';
4
4
 
5
5
  import { useAuthForm } from '@djangocfg/api/auth';
6
6
 
@@ -8,6 +8,14 @@ import type { AuthFormContextType, AuthLayoutProps } from './types';
8
8
 
9
9
  const AuthFormContext = createContext<AuthFormContextType | undefined>(undefined);
10
10
 
11
+ /**
12
+ * AuthFormProvider — wraps useAuthForm and merges UI config into context.
13
+ *
14
+ * The context value is memoised so that object reference churn from the
15
+ * hook does not re-render the entire auth subtree when only internal
16
+ * form state (e.g. `isLoading`) changes. Consumers that select specific
17
+ * fields via useAuthFormContext will still re-render as expected.
18
+ */
11
19
  export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
12
20
  children,
13
21
  sourceUrl: sourceUrlProp,
@@ -38,18 +46,32 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
38
46
  enable2FASetup,
39
47
  });
40
48
 
41
- const value: AuthFormContextType = {
42
- ...authForm,
43
- // UI-specific configuration
44
- sourceUrl,
45
- supportUrl,
46
- termsUrl,
47
- privacyUrl,
48
- enableGithubAuth,
49
- enable2FASetup,
50
- logoUrl,
51
- redirectUrl,
52
- };
49
+ const value: AuthFormContextType = useMemo(
50
+ () => ({
51
+ ...authForm,
52
+ // UI-specific configuration
53
+ sourceUrl,
54
+ supportUrl,
55
+ termsUrl,
56
+ privacyUrl,
57
+ enableGithubAuth,
58
+ enable2FASetup,
59
+ logoUrl,
60
+ redirectUrl,
61
+ }),
62
+ // eslint-disable-next-line react-hooks/exhaustive-deps
63
+ [
64
+ authForm,
65
+ sourceUrl,
66
+ supportUrl,
67
+ termsUrl,
68
+ privacyUrl,
69
+ enableGithubAuth,
70
+ enable2FASetup,
71
+ logoUrl,
72
+ redirectUrl,
73
+ ]
74
+ );
53
75
 
54
76
  return <AuthFormContext.Provider value={value}>{children}</AuthFormContext.Provider>;
55
77
  };
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { useMemo } from 'react';
3
+ import React, { memo, useMemo } from 'react';
4
4
 
5
5
  import { useImageLoader } from '@djangocfg/ui-core/hooks';
6
6
 
@@ -18,14 +18,19 @@ import type { AuthShellProps } from './types';
18
18
  * - variant dispatch (centered | split)
19
19
  *
20
20
  * Variants own their own chrome (positioning, shape, background).
21
+ *
22
+ * Memoised: re-renders only when `variant`, `background`, `sidebar`,
23
+ * `className` or `children` reference change. Background styles are
24
+ * memoised internally so object reference churn from the parent does
25
+ * not cause shell re-renders.
21
26
  */
22
- export const AuthShell: React.FC<AuthShellProps> = ({
27
+ function AuthShellRaw({
23
28
  variant,
24
29
  children,
25
30
  background,
26
31
  sidebar,
27
32
  className,
28
- }) => {
33
+ }: AuthShellProps) {
29
34
  const { isLoaded: bgLoaded, hasError: bgError } = useImageLoader(background?.imageUrl);
30
35
 
31
36
  const hasBgImage = Boolean(background?.imageUrl) && bgLoaded && !bgError;
@@ -77,4 +82,6 @@ export const AuthShell: React.FC<AuthShellProps> = ({
77
82
  )}
78
83
  </AuthShellProvider>
79
84
  );
80
- };
85
+ }
86
+
87
+ export const AuthShell = memo(AuthShellRaw);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React from 'react';
3
+ import React, { memo } from 'react';
4
4
 
5
5
  import { GlowBackground } from '@djangocfg/ui-core/components';
6
6
 
@@ -14,13 +14,17 @@ import type { ShellRenderProps } from './types';
14
14
  * - Glow background (default)
15
15
  * - Optional custom background image/gradient via shell props
16
16
  * - No visible card chrome
17
+ *
18
+ * Memoised: re-renders only when bgStyle/overlayStyle references or
19
+ * children change. The background layer is cheap to recreate; the main
20
+ * win is preserving the content subtree when parent re-renders.
17
21
  */
18
- export const CenteredShell: React.FC<ShellRenderProps> = ({
22
+ function CenteredShellRaw({
19
23
  children,
20
24
  className,
21
25
  bgStyle,
22
26
  overlayStyle,
23
- }) => {
27
+ }: ShellRenderProps) {
24
28
  const hasCustomBg = Boolean(bgStyle);
25
29
 
26
30
  return (
@@ -43,4 +47,6 @@ export const CenteredShell: React.FC<ShellRenderProps> = ({
43
47
  </div>
44
48
  </div>
45
49
  );
46
- };
50
+ }
51
+
52
+ export const CenteredShell = memo(CenteredShellRaw);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React from 'react';
3
+ import React, { memo } from 'react';
4
4
 
5
5
  import type { ShellRenderProps } from './types';
6
6
 
@@ -16,15 +16,19 @@ interface SplitShellProps extends ShellRenderProps {
16
16
  * - Desktop (>= xl): two columns inside a card — form left, sidebar right
17
17
  *
18
18
  * Background: full-bleed image/gradient with optional overlay + blur.
19
+ *
20
+ * Memoised: re-renders only when bgStyle, overlayStyle, blurValue or
21
+ * children references change. `sidebar` is compared by reference — the
22
+ * consumer should stabilise it (e.g. wrap in useMemo) to avoid re-renders.
19
23
  */
20
- export const SplitShell: React.FC<SplitShellProps> = ({
24
+ function SplitShellRaw({
21
25
  children,
22
26
  className,
23
27
  bgStyle,
24
28
  overlayStyle,
25
29
  blurValue,
26
30
  sidebar,
27
- }) => {
31
+ }: SplitShellProps) {
28
32
  return (
29
33
  <div className={`auth-shell-split ${className || ''}`}>
30
34
  {/* Background layer */}
@@ -60,4 +64,6 @@ export const SplitShell: React.FC<SplitShellProps> = ({
60
64
  </div>
61
65
  </div>
62
66
  );
63
- };
67
+ }
68
+
69
+ export const SplitShell = memo(SplitShellRaw);
@@ -1,17 +1,28 @@
1
1
  'use client';
2
2
 
3
- import React, { createContext, useContext } from 'react';
3
+ import React, { createContext, memo, useContext } from 'react';
4
4
 
5
5
  import type { AuthShellContextValue } from './types';
6
6
 
7
7
  const AuthShellContext = createContext<AuthShellContextValue | undefined>(undefined);
8
8
 
9
- export const AuthShellProvider: React.FC<{
9
+ interface AuthShellProviderProps {
10
10
  value: AuthShellContextValue;
11
11
  children: React.ReactNode;
12
- }> = ({ value, children }) => (
13
- <AuthShellContext.Provider value={value}>{children}</AuthShellContext.Provider>
14
- );
12
+ }
13
+
14
+ /**
15
+ * AuthShellProvider — context wrapper for shell metadata.
16
+ *
17
+ * Memoised: re-renders only when `value` reference or `children` change.
18
+ * The parent (AuthShell) already memoises `value`, so this stabilises
19
+ * the context subtree against unrelated parent renders.
20
+ */
21
+ function AuthShellProviderRaw({ value, children }: AuthShellProviderProps) {
22
+ return <AuthShellContext.Provider value={value}>{children}</AuthShellContext.Provider>;
23
+ }
24
+
25
+ export const AuthShellProvider = memo(AuthShellProviderRaw);
15
26
 
16
27
  export const useAuthShell = (): AuthShellContextValue => {
17
28
  const ctx = useContext(AuthShellContext);