@djangocfg/layouts 2.1.591 → 2.1.593
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 +12 -12
- package/src/components/errors/ErrorLayout.tsx +1 -164
- package/src/components/errors/ErrorsTracker/providers/ErrorTrackingProvider.tsx +1 -100
- package/src/components/errors/ErrorsTracker/providers/errorDedupe.ts +101 -0
- package/src/components/errors/errorGlyphs.tsx +167 -0
- package/src/layouts/AppLayout/BaseApp.tsx +1 -1
- package/src/layouts/AppLayout/index.ts +2 -2
- package/src/layouts/AuthLayout/AuthLayout.tsx +3 -23
- package/src/layouts/AuthLayout/components/shared/AuthTopBar.tsx +1 -1
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +1 -1
- package/src/layouts/AuthLayout/layoutContext.ts +25 -0
- package/src/layouts/AuthLayout/styles/split-shell.css +0 -1
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +37 -26
- package/src/layouts/PrivateLayout/README.md +20 -12
- package/src/layouts/PrivateLayout/components/PrivateContent.tsx +29 -42
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +23 -92
- package/src/layouts/PrivateLayout/components/PrivateSidebarAccount.tsx +41 -112
- package/src/layouts/PrivateLayout/components/SidebarBody.tsx +45 -0
- package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +64 -152
- package/src/layouts/PrivateLayout/components/SidebarBrandSwitcher.tsx +2 -30
- package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +55 -115
- package/src/layouts/PrivateLayout/components/SidebarNavItem.tsx +37 -100
- package/src/layouts/PrivateLayout/components/SidebarPeek.tsx +34 -0
- package/src/layouts/PrivateLayout/components/SidebarResizeHandle.tsx +76 -0
- package/src/layouts/PrivateLayout/components/SidebarSlots.tsx +14 -53
- package/src/layouts/PrivateLayout/components/index.ts +0 -4
- package/src/layouts/PrivateLayout/context.tsx +10 -115
- package/src/layouts/PrivateLayout/geometry.ts +17 -0
- package/src/layouts/PrivateLayout/hooks/index.ts +2 -1
- package/src/layouts/PrivateLayout/hooks/useLayoutVisual.ts +4 -4
- package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +33 -93
- package/src/layouts/PrivateLayout/hooks/useSidebarPeek.tsx +151 -0
- package/src/layouts/PrivateLayout/hooks/useSidebarWidth.ts +83 -0
- package/src/layouts/PrivateLayout/types.ts +13 -20
- package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +3 -89
- package/src/layouts/PublicLayout/footers/DefaultFooter/ThemeModeControl.tsx +90 -0
- package/src/layouts/PublicLayout/primitives/DesktopDropdownPortal.tsx +47 -0
- package/src/layouts/PublicLayout/primitives/NavControls.tsx +1 -1
- package/src/layouts/PublicLayout/primitives/NavDesktopItems.tsx +2 -83
- package/src/layouts/PublicLayout/primitives/navDesktopStyles.ts +42 -0
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +4 -171
- package/src/layouts/PublicLayout/shared/index.ts +1 -1
- package/src/layouts/PublicLayout/shared/navbarShell.types.ts +109 -0
- package/src/layouts/PublicLayout/shared/navbarShellStyles.ts +69 -0
- package/src/layouts/SettingsLayout/README.md +23 -10
- package/src/layouts/SettingsLayout/SettingsDialog.tsx +27 -11
- package/src/layouts/SettingsLayout/SettingsForm.tsx +13 -9
- package/src/layouts/SettingsLayout/components/AccountMenuButton.tsx +83 -0
- package/src/layouts/SettingsLayout/components/ApiKeySection/ApiKeySection.tsx +9 -7
- package/src/layouts/SettingsLayout/components/ApiKeySection/context.tsx +6 -41
- package/src/layouts/SettingsLayout/components/AvatarSection.tsx +11 -3
- package/src/layouts/SettingsLayout/components/SettingsNav.tsx +5 -9
- package/src/layouts/SettingsLayout/components/SettingsNavItem.tsx +5 -7
- package/src/layouts/SettingsLayout/components/SettingsPanel.tsx +5 -10
- package/src/layouts/SettingsLayout/components/SettingsSearch.tsx +5 -3
- package/src/layouts/SettingsLayout/components/SettingsShell.tsx +14 -21
- package/src/layouts/SettingsLayout/components/TwoFactorSection/TwoFactorSection.tsx +63 -131
- package/src/layouts/SettingsLayout/components/TwoFactorSection/TwoFactorSetupDialog.tsx +35 -0
- package/src/layouts/SettingsLayout/components/index.ts +0 -1
- package/src/layouts/SettingsLayout/context/SettingsContext.tsx +5 -2
- package/src/layouts/SettingsLayout/hooks/index.ts +3 -0
- package/src/layouts/SettingsLayout/hooks/useAccountMenuItems.ts +69 -0
- package/src/layouts/SettingsLayout/hooks/useSettingsDialog.ts +6 -3
- package/src/layouts/SettingsLayout/i18n/index.ts +39 -0
- package/src/layouts/SettingsLayout/i18n/locales/ar.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/da.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/de.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/en.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/es.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/fr.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/it.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/ja.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/ko.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/nl.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/no.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/pl.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/ptBR.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/ru.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/sv.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/tr.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/locales/zh.ts +66 -0
- package/src/layouts/SettingsLayout/i18n/types.ts +65 -0
- package/src/layouts/SettingsLayout/index.ts +3 -1
- package/src/layouts/SettingsLayout/sections/AccountSection.tsx +9 -4
- package/src/layouts/SettingsLayout/sections/DeleteAccountRow.tsx +4 -1
- package/src/layouts/SettingsLayout/sections/GeneralSection.tsx +25 -0
- package/src/layouts/SettingsLayout/sections/PreferencesRows.tsx +13 -5
- package/src/layouts/SettingsLayout/sections/builtins.tsx +34 -29
- package/src/layouts/SettingsLayout/sections/index.ts +2 -2
- package/src/layouts/SettingsLayout/store.ts +12 -0
- package/src/layouts/SettingsLayout/types.ts +12 -4
- package/src/layouts/_components/locale-switcher/LocaleSwitcher.tsx +1 -1
- package/src/layouts/types/layout.types.ts +5 -6
- package/src/{layouts/AppLayout → shared}/LayoutI18nProvider.tsx +1 -1
- package/src/styles/dashboard.css +10 -0
- package/src/layouts/PrivateLayout/density.ts +0 -48
- package/src/layouts/PrivateLayout/hooks/useHoverExpand.ts +0 -110
- package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +0 -130
- package/src/layouts/SettingsLayout/components/SettingsRailFooter.tsx +0 -74
- package/src/layouts/SettingsLayout/sections/SecuritySection.tsx +0 -15
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
// Top-level components
|
|
25
25
|
export { SettingsDialog } from './SettingsDialog';
|
|
26
26
|
export { SettingsForm } from './SettingsForm';
|
|
27
|
+
export { AccountMenuButton } from './components/AccountMenuButton';
|
|
28
|
+
export type { AccountMenuButtonProps } from './components/AccountMenuButton';
|
|
27
29
|
|
|
28
30
|
// Store + public hooks
|
|
29
31
|
export { useSettingsDialogStore } from './store';
|
|
@@ -31,6 +33,7 @@ export {
|
|
|
31
33
|
useSettingsDialog,
|
|
32
34
|
useSettingsUrl,
|
|
33
35
|
useSettingsSections,
|
|
36
|
+
useAccountMenuItems,
|
|
34
37
|
} from './hooks';
|
|
35
38
|
export type {
|
|
36
39
|
UseSettingsDialogReturn,
|
|
@@ -50,7 +53,6 @@ export {
|
|
|
50
53
|
buildBuiltinSections,
|
|
51
54
|
BUILTIN_GROUP,
|
|
52
55
|
AccountSection,
|
|
53
|
-
SecuritySection,
|
|
54
56
|
ApiKeysSection,
|
|
55
57
|
SettingRow,
|
|
56
58
|
SettingsBlock,
|
|
@@ -8,15 +8,19 @@ import { Button, SettingRow, SettingsBlock } from '@djangocfg/ui-core/components
|
|
|
8
8
|
|
|
9
9
|
import { AvatarSection } from '../components/AvatarSection';
|
|
10
10
|
import { useProfileSave } from '../hooks/useProfileSave';
|
|
11
|
+
import { TwoFactorSection } from '../components/TwoFactorSection';
|
|
12
|
+
import { useSettingsCopy } from '../i18n';
|
|
11
13
|
import { DeleteAccountRow } from './DeleteAccountRow';
|
|
12
14
|
|
|
13
15
|
interface AccountSectionProps {
|
|
14
16
|
/** Render the "Danger zone" delete-account control. Default: true. */
|
|
15
17
|
enableDeleteAccount?: boolean;
|
|
18
|
+
/** Render the "Security" block (two-factor auth). Default: true. */
|
|
19
|
+
enableSecurity?: boolean;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
|
-
* Built-in "Account" section — macOS / Claude-settings layout: flat blocks of
|
|
23
|
+
* Built-in "Account" section (profile + security + danger zone) — macOS / Claude-settings layout: flat blocks of
|
|
20
24
|
* label↔control rows divided by hairlines, no card frames.
|
|
21
25
|
*
|
|
22
26
|
* Editable fields are declared inline via SettingRow's props API
|
|
@@ -25,7 +29,9 @@ interface AccountSectionProps {
|
|
|
25
29
|
*/
|
|
26
30
|
export const AccountSection: React.FC<AccountSectionProps> = ({
|
|
27
31
|
enableDeleteAccount = true,
|
|
32
|
+
enableSecurity = true,
|
|
28
33
|
}) => {
|
|
34
|
+
const copy = useSettingsCopy();
|
|
29
35
|
const { labels, save: onFieldSave, logout } = useProfileSave();
|
|
30
36
|
const { user } = useAuth();
|
|
31
37
|
|
|
@@ -79,8 +85,7 @@ export const AccountSection: React.FC<AccountSectionProps> = ({
|
|
|
79
85
|
/>
|
|
80
86
|
</SettingsBlock>
|
|
81
87
|
|
|
82
|
-
{
|
|
83
|
-
dialog) — they are app-shell "chrome" prefs, not per-account fields. */}
|
|
88
|
+
{enableSecurity && <TwoFactorSection />}
|
|
84
89
|
|
|
85
90
|
<SettingsBlock title={labels.actions}>
|
|
86
91
|
<SettingRow
|
|
@@ -102,7 +107,7 @@ export const AccountSection: React.FC<AccountSectionProps> = ({
|
|
|
102
107
|
{enableDeleteAccount && (
|
|
103
108
|
// The whole Danger zone is collapsible (closed by default) so
|
|
104
109
|
// destructive actions stay tucked away until deliberately revealed.
|
|
105
|
-
<SettingsBlock title=
|
|
110
|
+
<SettingsBlock title={copy.account.dangerZone} collapsible defaultOpen={false}>
|
|
106
111
|
<DeleteAccountRow />
|
|
107
112
|
</SettingsBlock>
|
|
108
113
|
)}
|
|
@@ -7,6 +7,8 @@ import { useDeleteAccount } from '@djangocfg/api/auth';
|
|
|
7
7
|
import { useAppT } from '@djangocfg/i18n';
|
|
8
8
|
import { Button, SettingRow } from '@djangocfg/ui-core/components';
|
|
9
9
|
|
|
10
|
+
import { useSettingsCopy } from '../i18n';
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Compact "Delete account" row — a flat row with a small destructive button on
|
|
12
14
|
* the right. Lives inside the collapsible "Danger zone" block, so the section
|
|
@@ -16,6 +18,7 @@ import { Button, SettingRow } from '@djangocfg/ui-core/components';
|
|
|
16
18
|
export const DeleteAccountRow: React.FC = () => {
|
|
17
19
|
const { deleteAccount, isLoading } = useDeleteAccount();
|
|
18
20
|
const t = useAppT();
|
|
21
|
+
const copy = useSettingsCopy();
|
|
19
22
|
|
|
20
23
|
const confirmationWord = t('layouts.profilePage.confirmationWord');
|
|
21
24
|
|
|
@@ -37,7 +40,7 @@ export const DeleteAccountRow: React.FC = () => {
|
|
|
37
40
|
return (
|
|
38
41
|
<SettingRow
|
|
39
42
|
label={t('layouts.profilePage.deleteAccount')}
|
|
40
|
-
description=
|
|
43
|
+
description={copy.account.deleteAccountDescription}
|
|
41
44
|
action={
|
|
42
45
|
<Button
|
|
43
46
|
variant="destructive"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import { SettingsBlock } from '@djangocfg/ui-core/components';
|
|
6
|
+
|
|
7
|
+
import { useSettingsCopy } from '../i18n';
|
|
8
|
+
import { PreferencesRows } from './PreferencesRows';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Interface preferences — theme, language and time zone live here, not in the dialog
|
|
12
|
+
* chrome. `children` are the host's own rows (see `SettingsContent.generalRows`),
|
|
13
|
+
* rendered in a second block so they read as separate from the built-ins.
|
|
14
|
+
*/
|
|
15
|
+
export const GeneralSection: React.FC<React.PropsWithChildren> = ({ children }) => {
|
|
16
|
+
const copy = useSettingsCopy();
|
|
17
|
+
return (
|
|
18
|
+
<div className="space-y-8">
|
|
19
|
+
<SettingsBlock title={copy.prefs.general}>
|
|
20
|
+
<PreferencesRows />
|
|
21
|
+
</SettingsBlock>
|
|
22
|
+
{children ? <SettingsBlock title={copy.prefs.other}>{children}</SettingsBlock> : null}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -2,26 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useTimezone } from '@djangocfg/api/auth';
|
|
6
|
+
import { SettingRow, TimezoneSelect } from '@djangocfg/ui-core/components';
|
|
6
7
|
import { ThemeSegmented } from '@djangocfg/ui-core/theme';
|
|
7
8
|
|
|
8
9
|
import { LocaleSwitcherDropdown } from '../../_components/locale-switcher';
|
|
9
|
-
import {
|
|
10
|
+
import { useSettingsCopy } from '../i18n';
|
|
11
|
+
import { useLayoutI18nOptional } from '../../../shared/LayoutI18nProvider';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
* Preferences as native SettingRows (Language + Theme), so they share the exact
|
|
14
|
+
* Preferences as native SettingRows (Language + Time zone + Theme), so they share the exact
|
|
13
15
|
* row/divider/spacing of the rest of the settings panel. Mirrors the controls
|
|
14
16
|
* from ProfileLayout's PreferencesSection but in the lighter Claude-style row
|
|
15
17
|
* layout. Language only renders when a layout i18n context is present.
|
|
16
18
|
*/
|
|
17
19
|
export const PreferencesRows: React.FC = () => {
|
|
20
|
+
const copy = useSettingsCopy();
|
|
18
21
|
const layoutI18n = useLayoutI18nOptional();
|
|
22
|
+
const { preference, setTimezone } = useTimezone();
|
|
19
23
|
|
|
20
24
|
return (
|
|
21
25
|
<>
|
|
22
26
|
{layoutI18n && (
|
|
23
27
|
<SettingRow
|
|
24
|
-
label=
|
|
28
|
+
label={copy.prefs.language}
|
|
25
29
|
action={
|
|
26
30
|
<LocaleSwitcherDropdown
|
|
27
31
|
locale={layoutI18n.locale}
|
|
@@ -37,7 +41,11 @@ export const PreferencesRows: React.FC = () => {
|
|
|
37
41
|
}
|
|
38
42
|
/>
|
|
39
43
|
)}
|
|
40
|
-
<SettingRow
|
|
44
|
+
<SettingRow
|
|
45
|
+
label={copy.prefs.timeZone}
|
|
46
|
+
action={<TimezoneSelect value={preference} onChange={setTimezone} className="w-72" />}
|
|
47
|
+
/>
|
|
48
|
+
<SettingRow label={copy.prefs.theme} action={<ThemeSegmented />} />
|
|
41
49
|
</>
|
|
42
50
|
);
|
|
43
51
|
};
|
|
@@ -2,79 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
5
|
-
import { KeyRound,
|
|
5
|
+
import { KeyRound, Settings as SettingsIcon, UserRound } from 'lucide-react';
|
|
6
6
|
|
|
7
7
|
import type { SettingsBuiltins, SettingsGroup, SettingsSection } from '../types';
|
|
8
8
|
import { AccountSection } from './AccountSection';
|
|
9
|
+
import { GeneralSection } from './GeneralSection';
|
|
9
10
|
import { ApiKeysSection } from './ApiKeysSection';
|
|
10
|
-
import {
|
|
11
|
+
import type { SettingsCopy } from '../i18n';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* The group
|
|
14
|
+
* The (unlabelled) group Account and Preferences belong to — they open the rail, like Claude. App sections can reuse this id
|
|
14
15
|
* to sit alongside the built-ins, or define their own groups.
|
|
15
16
|
*/
|
|
16
|
-
export const BUILTIN_GROUP: SettingsGroup = { id: 'general',
|
|
17
|
+
export const BUILTIN_GROUP: SettingsGroup = { id: 'general', order: 0 };
|
|
18
|
+
|
|
19
|
+
/** Developer-facing sections (API keys). */
|
|
20
|
+
export const PLATFORM_GROUP: SettingsGroup = { id: 'platform', order: 1 };
|
|
17
21
|
|
|
18
22
|
interface BuildBuiltinArgs {
|
|
19
23
|
builtins: SettingsBuiltins;
|
|
20
24
|
enableDeleteAccount: boolean;
|
|
25
|
+
generalRows?: React.ReactNode;
|
|
26
|
+
copy: SettingsCopy;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/**
|
|
24
|
-
* Construct the enabled built-in sections. Pure function of the flags
|
|
25
|
-
*
|
|
26
|
-
* from ProfileProvider); only the nav-level label/title live at this layer.
|
|
30
|
+
* Construct the enabled built-in sections. Pure function of the flags and the
|
|
31
|
+
* active `copy` — nav-level labels/titles come from the Settings dictionaries.
|
|
27
32
|
*
|
|
28
|
-
* Defaults: everything on. Security (2FA
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* `builtins.security: false`.
|
|
33
|
+
* Defaults: everything on. Security (2FA) is a block inside Account, not its own
|
|
34
|
+
* section — `/cfg/totp/*` ships with every django-cfg backend. Apps that truly
|
|
35
|
+
* lack it opt out explicitly via `builtins.security: false`.
|
|
32
36
|
*/
|
|
33
37
|
export function buildBuiltinSections({
|
|
34
38
|
builtins,
|
|
35
39
|
enableDeleteAccount,
|
|
40
|
+
generalRows,
|
|
41
|
+
copy,
|
|
36
42
|
}: BuildBuiltinArgs): SettingsSection[] {
|
|
37
|
-
const { account = true, security = true, apiKeys = true } = builtins;
|
|
43
|
+
const { general = true, account = true, security = true, apiKeys = true } = builtins;
|
|
38
44
|
const sections: SettingsSection[] = [];
|
|
39
45
|
|
|
40
46
|
if (account) {
|
|
41
47
|
sections.push({
|
|
42
48
|
id: 'account',
|
|
43
|
-
label:
|
|
49
|
+
label: copy.nav.account,
|
|
44
50
|
icon: UserRound,
|
|
45
51
|
group: BUILTIN_GROUP.id,
|
|
46
|
-
title:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
content: <AccountSection enableDeleteAccount={enableDeleteAccount} />,
|
|
52
|
+
title: copy.nav.account,
|
|
53
|
+
keywords: ['profile', 'name', 'email', 'avatar', 'security', '2fa', 'two-factor', 'mfa', 'authenticator'],
|
|
54
|
+
content: <AccountSection enableDeleteAccount={enableDeleteAccount} enableSecurity={security} />,
|
|
50
55
|
});
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
if (
|
|
58
|
+
if (general) {
|
|
54
59
|
sections.push({
|
|
55
|
-
id: '
|
|
56
|
-
label:
|
|
57
|
-
icon:
|
|
60
|
+
id: 'general',
|
|
61
|
+
label: copy.nav.preferences,
|
|
62
|
+
icon: SettingsIcon,
|
|
58
63
|
group: BUILTIN_GROUP.id,
|
|
59
|
-
title:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
content: <SecuritySection />,
|
|
64
|
+
title: copy.nav.preferences,
|
|
65
|
+
keywords: ['theme', 'appearance', 'dark', 'light', 'language', 'locale', 'preferences'],
|
|
66
|
+
content: <GeneralSection>{generalRows}</GeneralSection>,
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
if (apiKeys) {
|
|
67
71
|
sections.push({
|
|
68
72
|
id: 'api-keys',
|
|
69
|
-
label:
|
|
73
|
+
label: copy.nav.apiKeys,
|
|
70
74
|
icon: KeyRound,
|
|
71
|
-
group:
|
|
72
|
-
title:
|
|
73
|
-
description:
|
|
75
|
+
group: PLATFORM_GROUP.id,
|
|
76
|
+
title: copy.nav.apiKeys,
|
|
77
|
+
description: copy.nav.apiKeysDescription,
|
|
74
78
|
keywords: ['api', 'token', 'key', 'secret', 'developer'],
|
|
75
79
|
content: <ApiKeysSection />,
|
|
76
80
|
});
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
|
|
79
84
|
return sections;
|
|
80
85
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { buildBuiltinSections, BUILTIN_GROUP } from './builtins';
|
|
1
|
+
export { buildBuiltinSections, BUILTIN_GROUP, PLATFORM_GROUP } from './builtins';
|
|
2
|
+
export { GeneralSection } from './GeneralSection';
|
|
2
3
|
export { AccountSection } from './AccountSection';
|
|
3
|
-
export { SecuritySection } from './SecuritySection';
|
|
4
4
|
export { ApiKeysSection } from './ApiKeysSection';
|
|
5
5
|
// SettingRow/SettingsBlock now live in ui-core (universal). Re-export for
|
|
6
6
|
// backward compatibility with consumers importing them from @djangocfg/layouts.
|
|
@@ -15,7 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
import { create } from 'zustand';
|
|
17
17
|
|
|
18
|
+
/** What a menu needs to link to a section; the dialog registers these (host sections are only known to it). */
|
|
19
|
+
export interface SettingsMenuSection {
|
|
20
|
+
id: string;
|
|
21
|
+
label: React.ReactNode;
|
|
22
|
+
icon?: React.ComponentType<{ className?: string }>;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
interface SettingsDialogState {
|
|
26
|
+
/** The visible sections of the mounted dialog, in rail order, for menu shortcuts. */
|
|
27
|
+
sections: SettingsMenuSection[];
|
|
28
|
+
setSections: (sections: SettingsMenuSection[]) => void;
|
|
19
29
|
/** Whether the dialog is visible. */
|
|
20
30
|
isOpen: boolean;
|
|
21
31
|
/**
|
|
@@ -34,6 +44,8 @@ interface SettingsDialogState {
|
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
export const useSettingsDialogStore = create<SettingsDialogState>((set) => ({
|
|
47
|
+
sections: [],
|
|
48
|
+
setSections: (sections) => set({ sections }),
|
|
37
49
|
isOpen: false,
|
|
38
50
|
activeSection: null,
|
|
39
51
|
open: (sectionId) =>
|
|
@@ -56,9 +56,11 @@ export interface SettingsSection {
|
|
|
56
56
|
|
|
57
57
|
/** Toggle flags for the built-in sections shipped by SettingsLayout. */
|
|
58
58
|
export interface SettingsBuiltins {
|
|
59
|
-
/**
|
|
59
|
+
/** Appearance (theme) + language. Default: true. */
|
|
60
|
+
general?: boolean;
|
|
61
|
+
/** Account: profile fields, security block, danger zone. Default: true. */
|
|
60
62
|
account?: boolean;
|
|
61
|
-
/** Two-factor
|
|
63
|
+
/** Two-factor block inside Account. Default: true; needs the totp API. */
|
|
62
64
|
security?: boolean;
|
|
63
65
|
/** API keys. Default: true. */
|
|
64
66
|
apiKeys?: boolean;
|
|
@@ -74,10 +76,16 @@ export interface SettingsContent {
|
|
|
74
76
|
groups?: SettingsGroup[];
|
|
75
77
|
/** Which built-in sections to include. */
|
|
76
78
|
builtins?: SettingsBuiltins;
|
|
79
|
+
/**
|
|
80
|
+
* Extra `SettingRow`s appended to the General section (after Appearance and
|
|
81
|
+
* Language), for preferences only the host knows: density, date format,
|
|
82
|
+
* time zone. The layout adds none of its own.
|
|
83
|
+
*/
|
|
84
|
+
generalRows?: React.ReactNode;
|
|
77
85
|
/** Section id to open first. Defaults to the first visible section. */
|
|
78
86
|
initialSection?: string;
|
|
79
|
-
/**
|
|
80
|
-
searchable?: boolean;
|
|
87
|
+
/** Search box above the nav rail. `'auto'` (default) shows it from 10 sections up. */
|
|
88
|
+
searchable?: boolean | 'auto';
|
|
81
89
|
/** Allow account deletion in the account section. Default: true. */
|
|
82
90
|
enableDeleteAccount?: boolean;
|
|
83
91
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
|
|
5
|
-
import { useLayoutI18n } from '
|
|
5
|
+
import { useLayoutI18n } from '../../../shared/LayoutI18nProvider';
|
|
6
6
|
|
|
7
7
|
import { LocaleSwitcherDialog } from './LocaleSwitcherDialog';
|
|
8
8
|
import { LocaleSwitcherDropdown } from './LocaleSwitcherDropdown';
|
|
@@ -133,11 +133,10 @@ export interface I18nLayoutConfig {
|
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* Visual style of the private/app shell.
|
|
136
|
-
* - `
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* `full-bleed` automatically.
|
|
140
|
-
* - `full-bleed` — content stretches edge-to-edge next to the sidebar (legacy look).
|
|
136
|
+
* - `full-bleed` (default) — flat, Claude-style: content fills the page next to
|
|
137
|
+
* the sidebar, divided by a single 1px border.
|
|
138
|
+
* - `boxed` — content lives in a rounded card inset from the viewport edges on a
|
|
139
|
+
* sidebar-coloured canvas. Mobile (<md) falls back to `full-bleed` automatically.
|
|
141
140
|
*/
|
|
142
141
|
export type LayoutVisualVariant = 'full-bleed' | 'boxed';
|
|
143
142
|
|
|
@@ -152,7 +151,7 @@ export type LayoutBoxedBackground = 'sidebar' | 'muted' | 'card' | 'background';
|
|
|
152
151
|
* All `boxed`-only options are ignored when `variant === 'full-bleed'`.
|
|
153
152
|
*/
|
|
154
153
|
export interface LayoutVisualConfig {
|
|
155
|
-
/** Visual variant. Default: `'
|
|
154
|
+
/** Visual variant. Default: `'full-bleed'`. */
|
|
156
155
|
variant?: LayoutVisualVariant;
|
|
157
156
|
/**
|
|
158
157
|
* Inset (px) between the boxed container and the viewport edges on desktop.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import * as React from 'react';
|
|
17
17
|
|
|
18
|
-
import type { I18nLayoutConfig } from '../types/layout.types';
|
|
18
|
+
import type { I18nLayoutConfig } from '../layouts/types/layout.types';
|
|
19
19
|
|
|
20
20
|
const LayoutI18nContext = React.createContext<I18nLayoutConfig | null>(null);
|
|
21
21
|
|
package/src/styles/dashboard.css
CHANGED
|
@@ -39,3 +39,13 @@
|
|
|
39
39
|
padding: 1.5rem;
|
|
40
40
|
overflow-y: auto;
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
/* While the sidebar edge is dragged the width must follow the pointer 1:1. */
|
|
44
|
+
[data-sidebar-resizing] {
|
|
45
|
+
cursor: col-resize;
|
|
46
|
+
user-select: none;
|
|
47
|
+
}
|
|
48
|
+
[data-sidebar-resizing] [data-variant][data-state],
|
|
49
|
+
[data-sidebar-resizing] [data-variant][data-state] * {
|
|
50
|
+
transition: none !important;
|
|
51
|
+
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Nav Density Tokens
|
|
3
|
-
*
|
|
4
|
-
* Density presets for sidebar navigation items.
|
|
5
|
-
* The active preset is chosen via the `density` prop on `SidebarConfig`;
|
|
6
|
-
* when omitted it defaults to `'default'`.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export type NavDensity = 'comfortable' | 'default' | 'compact';
|
|
10
|
-
|
|
11
|
-
export const DENSITY = {
|
|
12
|
-
comfortable: {
|
|
13
|
-
menu: 'gap-1.5',
|
|
14
|
-
group: 'gap-2',
|
|
15
|
-
groupPad: 'px-2 py-1',
|
|
16
|
-
label:
|
|
17
|
-
'h-7 uppercase text-[10px] font-light leading-none tracking-[0.14em] text-sidebar-foreground/40',
|
|
18
|
-
buttonSize: 'lg' as const,
|
|
19
|
-
iconClass: 'h-5 w-5',
|
|
20
|
-
extraButton: 'rounded-lg !px-3',
|
|
21
|
-
headerRowInset: 'pl-2',
|
|
22
|
-
},
|
|
23
|
-
default: {
|
|
24
|
-
menu: 'gap-1',
|
|
25
|
-
group: 'gap-1.5',
|
|
26
|
-
groupPad: 'px-2 py-1',
|
|
27
|
-
label:
|
|
28
|
-
'uppercase text-[9px] font-light leading-none tracking-[0.12em] text-sidebar-foreground/50',
|
|
29
|
-
buttonSize: 'default' as const,
|
|
30
|
-
iconClass: 'h-4 w-4',
|
|
31
|
-
extraButton: 'rounded-lg',
|
|
32
|
-
headerRowInset: 'pl-1.5',
|
|
33
|
-
},
|
|
34
|
-
compact: {
|
|
35
|
-
menu: 'gap-0.5',
|
|
36
|
-
group: 'gap-0.5',
|
|
37
|
-
groupPad: 'px-2 py-0.5',
|
|
38
|
-
label:
|
|
39
|
-
'h-6 uppercase text-[8px] font-light leading-none tracking-[0.1em] text-sidebar-foreground/40',
|
|
40
|
-
buttonSize: 'sm' as const,
|
|
41
|
-
iconClass: 'h-3.5 w-3.5',
|
|
42
|
-
extraButton: 'rounded-md !px-2',
|
|
43
|
-
headerRowInset: 'pl-1.5',
|
|
44
|
-
},
|
|
45
|
-
} as const;
|
|
46
|
-
|
|
47
|
-
/** Icon rail: always the same geometry — ignore comfortable/compact. */
|
|
48
|
-
export const RAIL_NAV = DENSITY.default;
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hover Expand Hook
|
|
3
|
-
*
|
|
4
|
-
* Manages hover-expand state for collapsed sidebar with debounce.
|
|
5
|
-
* - enterDelay: ms before expanding on hover (default 250)
|
|
6
|
-
* - leaveDelay: ms before collapsing on mouse leave (default 150)
|
|
7
|
-
* - Listens for `sidebar:blockcollapse` / `sidebar:allowcollapse` custom events
|
|
8
|
-
* so descendant components (e.g. account dropdown) can block collapse without
|
|
9
|
-
* prop drilling or context churn.
|
|
10
|
-
* - Clears timers on unmount / re-hover to prevent false triggers.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
'use client';
|
|
14
|
-
|
|
15
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
16
|
-
|
|
17
|
-
interface UseHoverExpandOptions {
|
|
18
|
-
/** Delay before expanding on mouse enter (ms). Default 250. */
|
|
19
|
-
enterDelay?: number;
|
|
20
|
-
/** Delay before collapsing on mouse leave (ms). Default 150. */
|
|
21
|
-
leaveDelay?: number;
|
|
22
|
-
/** Whether hover-expand is enabled (e.g. only on desktop collapsed rail). */
|
|
23
|
-
enabled?: boolean;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface UseHoverExpandResult {
|
|
27
|
-
/** Whether the sidebar is currently hover-expanded */
|
|
28
|
-
isHoverExpanded: boolean;
|
|
29
|
-
/** Attach to the sidebar root element */
|
|
30
|
-
onMouseEnter: () => void;
|
|
31
|
-
/** Attach to the sidebar root element */
|
|
32
|
-
onMouseLeave: () => void;
|
|
33
|
-
/** Programmatically set hover-expanded state (for click-to-expand) */
|
|
34
|
-
setHoverExpanded: (value: boolean) => void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function useHoverExpand({
|
|
38
|
-
enterDelay = 2000,
|
|
39
|
-
leaveDelay = 450,
|
|
40
|
-
enabled = true,
|
|
41
|
-
}: UseHoverExpandOptions = {}): UseHoverExpandResult {
|
|
42
|
-
const [isHoverExpanded, setIsHoverExpanded] = useState(false);
|
|
43
|
-
const blockedRef = useRef(false);
|
|
44
|
-
const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
45
|
-
const leaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
46
|
-
|
|
47
|
-
const clearTimers = useCallback(() => {
|
|
48
|
-
if (enterTimerRef.current) {
|
|
49
|
-
clearTimeout(enterTimerRef.current);
|
|
50
|
-
enterTimerRef.current = null;
|
|
51
|
-
}
|
|
52
|
-
if (leaveTimerRef.current) {
|
|
53
|
-
clearTimeout(leaveTimerRef.current);
|
|
54
|
-
leaveTimerRef.current = null;
|
|
55
|
-
}
|
|
56
|
-
}, []);
|
|
57
|
-
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const onBlock = () => {
|
|
60
|
-
blockedRef.current = true;
|
|
61
|
-
};
|
|
62
|
-
const onAllow = () => {
|
|
63
|
-
blockedRef.current = false;
|
|
64
|
-
};
|
|
65
|
-
window.addEventListener('sidebar:blockcollapse', onBlock);
|
|
66
|
-
window.addEventListener('sidebar:allowcollapse', onAllow);
|
|
67
|
-
return () => {
|
|
68
|
-
window.removeEventListener('sidebar:blockcollapse', onBlock);
|
|
69
|
-
window.removeEventListener('sidebar:allowcollapse', onAllow);
|
|
70
|
-
};
|
|
71
|
-
}, []);
|
|
72
|
-
|
|
73
|
-
const onMouseEnter = useCallback(() => {
|
|
74
|
-
if (!enabled) return;
|
|
75
|
-
clearTimers();
|
|
76
|
-
enterTimerRef.current = setTimeout(() => {
|
|
77
|
-
setIsHoverExpanded(true);
|
|
78
|
-
}, enterDelay);
|
|
79
|
-
}, [enabled, enterDelay, clearTimers]);
|
|
80
|
-
|
|
81
|
-
const onMouseLeave = useCallback(() => {
|
|
82
|
-
if (!enabled) return;
|
|
83
|
-
if (blockedRef.current) return;
|
|
84
|
-
clearTimers();
|
|
85
|
-
leaveTimerRef.current = setTimeout(() => {
|
|
86
|
-
setIsHoverExpanded(false);
|
|
87
|
-
}, leaveDelay);
|
|
88
|
-
}, [enabled, leaveDelay, clearTimers]);
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
return () => clearTimers();
|
|
92
|
-
}, [clearTimers]);
|
|
93
|
-
|
|
94
|
-
const setHoverExpanded = useCallback((value: boolean) => {
|
|
95
|
-
clearTimers();
|
|
96
|
-
setIsHoverExpanded(value);
|
|
97
|
-
}, [clearTimers]);
|
|
98
|
-
|
|
99
|
-
return { isHoverExpanded, onMouseEnter, onMouseLeave, setHoverExpanded };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Dispatch from any descendant to block sidebar collapse while e.g. a dropdown is open. */
|
|
103
|
-
export function blockSidebarCollapse(): void {
|
|
104
|
-
window.dispatchEvent(new CustomEvent('sidebar:blockcollapse'));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Dispatch when the blocking reason is gone (e.g. dropdown closed). */
|
|
108
|
-
export function allowSidebarCollapse(): void {
|
|
109
|
-
window.dispatchEvent(new CustomEvent('sidebar:allowcollapse'));
|
|
110
|
-
}
|