@djangocfg/layouts 2.1.590 → 2.1.592

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 (71) hide show
  1. package/package.json +12 -12
  2. package/src/layouts/PrivateLayout/PrivateLayout.tsx +37 -26
  3. package/src/layouts/PrivateLayout/README.md +20 -12
  4. package/src/layouts/PrivateLayout/components/PrivateContent.tsx +29 -42
  5. package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +23 -92
  6. package/src/layouts/PrivateLayout/components/PrivateSidebarAccount.tsx +32 -62
  7. package/src/layouts/PrivateLayout/components/SidebarBody.tsx +45 -0
  8. package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +64 -152
  9. package/src/layouts/PrivateLayout/components/SidebarBrandSwitcher.tsx +2 -30
  10. package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +55 -115
  11. package/src/layouts/PrivateLayout/components/SidebarNavItem.tsx +37 -100
  12. package/src/layouts/PrivateLayout/components/SidebarPeek.tsx +34 -0
  13. package/src/layouts/PrivateLayout/components/SidebarResizeHandle.tsx +76 -0
  14. package/src/layouts/PrivateLayout/components/SidebarSlots.tsx +14 -53
  15. package/src/layouts/PrivateLayout/components/index.ts +0 -4
  16. package/src/layouts/PrivateLayout/context.tsx +10 -115
  17. package/src/layouts/PrivateLayout/geometry.ts +17 -0
  18. package/src/layouts/PrivateLayout/hooks/index.ts +2 -1
  19. package/src/layouts/PrivateLayout/hooks/useLayoutVisual.ts +4 -4
  20. package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +33 -93
  21. package/src/layouts/PrivateLayout/hooks/useSidebarPeek.tsx +151 -0
  22. package/src/layouts/PrivateLayout/hooks/useSidebarWidth.ts +83 -0
  23. package/src/layouts/PrivateLayout/types.ts +13 -20
  24. package/src/layouts/SettingsLayout/README.md +23 -10
  25. package/src/layouts/SettingsLayout/SettingsDialog.tsx +14 -8
  26. package/src/layouts/SettingsLayout/SettingsForm.tsx +13 -9
  27. package/src/layouts/SettingsLayout/components/ApiKeySection/ApiKeySection.tsx +9 -7
  28. package/src/layouts/SettingsLayout/components/ApiKeySection/context.tsx +6 -41
  29. package/src/layouts/SettingsLayout/components/AvatarSection.tsx +11 -3
  30. package/src/layouts/SettingsLayout/components/SettingsNav.tsx +5 -9
  31. package/src/layouts/SettingsLayout/components/SettingsNavItem.tsx +5 -7
  32. package/src/layouts/SettingsLayout/components/SettingsPanel.tsx +5 -10
  33. package/src/layouts/SettingsLayout/components/SettingsSearch.tsx +5 -3
  34. package/src/layouts/SettingsLayout/components/SettingsShell.tsx +14 -21
  35. package/src/layouts/SettingsLayout/components/TwoFactorSection/TwoFactorSection.tsx +52 -105
  36. package/src/layouts/SettingsLayout/components/index.ts +0 -1
  37. package/src/layouts/SettingsLayout/context/SettingsContext.tsx +5 -2
  38. package/src/layouts/SettingsLayout/i18n/index.ts +39 -0
  39. package/src/layouts/SettingsLayout/i18n/locales/ar.ts +66 -0
  40. package/src/layouts/SettingsLayout/i18n/locales/da.ts +66 -0
  41. package/src/layouts/SettingsLayout/i18n/locales/de.ts +66 -0
  42. package/src/layouts/SettingsLayout/i18n/locales/en.ts +66 -0
  43. package/src/layouts/SettingsLayout/i18n/locales/es.ts +66 -0
  44. package/src/layouts/SettingsLayout/i18n/locales/fr.ts +66 -0
  45. package/src/layouts/SettingsLayout/i18n/locales/it.ts +66 -0
  46. package/src/layouts/SettingsLayout/i18n/locales/ja.ts +66 -0
  47. package/src/layouts/SettingsLayout/i18n/locales/ko.ts +66 -0
  48. package/src/layouts/SettingsLayout/i18n/locales/nl.ts +66 -0
  49. package/src/layouts/SettingsLayout/i18n/locales/no.ts +66 -0
  50. package/src/layouts/SettingsLayout/i18n/locales/pl.ts +66 -0
  51. package/src/layouts/SettingsLayout/i18n/locales/ptBR.ts +66 -0
  52. package/src/layouts/SettingsLayout/i18n/locales/ru.ts +66 -0
  53. package/src/layouts/SettingsLayout/i18n/locales/sv.ts +66 -0
  54. package/src/layouts/SettingsLayout/i18n/locales/tr.ts +66 -0
  55. package/src/layouts/SettingsLayout/i18n/locales/zh.ts +66 -0
  56. package/src/layouts/SettingsLayout/i18n/types.ts +65 -0
  57. package/src/layouts/SettingsLayout/index.ts +0 -1
  58. package/src/layouts/SettingsLayout/sections/AccountSection.tsx +9 -4
  59. package/src/layouts/SettingsLayout/sections/DeleteAccountRow.tsx +4 -1
  60. package/src/layouts/SettingsLayout/sections/GeneralSection.tsx +25 -0
  61. package/src/layouts/SettingsLayout/sections/PreferencesRows.tsx +12 -4
  62. package/src/layouts/SettingsLayout/sections/builtins.tsx +34 -29
  63. package/src/layouts/SettingsLayout/sections/index.ts +2 -2
  64. package/src/layouts/SettingsLayout/types.ts +12 -4
  65. package/src/layouts/types/layout.types.ts +5 -6
  66. package/src/styles/dashboard.css +10 -0
  67. package/src/layouts/PrivateLayout/density.ts +0 -48
  68. package/src/layouts/PrivateLayout/hooks/useHoverExpand.ts +0 -110
  69. package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +0 -130
  70. package/src/layouts/SettingsLayout/components/SettingsRailFooter.tsx +0 -74
  71. package/src/layouts/SettingsLayout/sections/SecuritySection.tsx +0 -15
@@ -0,0 +1,66 @@
1
+ import type { SettingsCopy } from '../types';
2
+
3
+ export const tr: SettingsCopy = {
4
+ nav: {
5
+ title: "Ayarlar",
6
+ platform: "Platform",
7
+ account: "Hesap",
8
+ preferences: "Tercihler",
9
+ apiKeys: "API anahtarları",
10
+ apiKeysDescription: "Programatik erişim için anahtarlar oluşturun ve yönetin.",
11
+ },
12
+ prefs: {
13
+ general: "Genel",
14
+ other: "Diğer",
15
+ language: "Dil",
16
+ timeZone: "Saat dilimi",
17
+ theme: "Tema",
18
+ },
19
+ account: {
20
+ dangerZone: "Tehlikeli bölge",
21
+ deleteAccountDescription: "Hesabınızı ve ilişkili tüm verileri kalıcı olarak silin.",
22
+ changeAvatar: "Avatarı değiştir",
23
+ },
24
+ search: {
25
+ clear: "Aramayı temizle",
26
+ },
27
+ security: {
28
+ title: "Güvenlik",
29
+ twoFactor: "İki adımlı doğrulama",
30
+ on: "Açık",
31
+ off: "Kapalı · oturum açarken ikinci bir adım ekler",
32
+ checking: "Kontrol ediliyor…",
33
+ unavailable: "Kullanılamıyor",
34
+ turnOn: "Aç",
35
+ turnOff: "Kapat",
36
+ setupTitle: "İki adımlı doğrulamayı ayarla",
37
+ setupHint: "QR kodunu kimlik doğrulama uygulamanızla (Google Authenticator, Authy vb.) tarayın",
38
+ cancel: "İptal",
39
+ deviceAdded: "{{date}} tarihinde eklendi",
40
+ devicePrimary: "Birincil",
41
+ disableTitle: "İki adımlı doğrulama kapatılsın mı?",
42
+ disableHint: "Onaylamak için kimlik doğrulama uygulamanızdaki 6 haneli kodu girin. Bu, hesabınızı daha az güvenli hale getirir.",
43
+ disabling: "Kapatılıyor…",
44
+ disable: "2FA'yı kapat",
45
+ },
46
+ apiKey: {
47
+ title: "API anahtarı",
48
+ description: "Programatik erişim için kişisel API anahtarınız",
49
+ created: "Oluşturuldu",
50
+ reissued: "Son yenileme",
51
+ neverReissued: "Hiç yenilenmedi",
52
+ regenerate: "API anahtarını yeniden oluştur",
53
+ confirmRegenerate: "Onaylamak için tıklayın",
54
+ regenerating: "Yeniden oluşturuluyor…",
55
+ regenerated: "API anahtarı yeniden oluşturuldu",
56
+ failedToRegenerate: "API anahtarı yeniden oluşturulamadı",
57
+ done: "Bitti",
58
+ test: "API anahtarını test et",
59
+ testing: "Test ediliyor…",
60
+ testSuccess: "API anahtarı geçerli",
61
+ testFailed: "API anahtarı testi başarısız oldu",
62
+ keyRow: "API anahtarı",
63
+ active: "Etkin",
64
+ freshKey: "Bu yeni API anahtarınız: şimdi kopyalayın. Bu sayfadan ayrıldığınızda gizlenecek.",
65
+ },
66
+ };
@@ -0,0 +1,66 @@
1
+ import type { SettingsCopy } from '../types';
2
+
3
+ export const zh: SettingsCopy = {
4
+ nav: {
5
+ title: "设置",
6
+ platform: "平台",
7
+ account: "账户",
8
+ preferences: "偏好设置",
9
+ apiKeys: "API 密钥",
10
+ apiKeysDescription: "创建并管理用于程序化访问的密钥。",
11
+ },
12
+ prefs: {
13
+ general: "常规",
14
+ other: "其他",
15
+ language: "语言",
16
+ timeZone: "时区",
17
+ theme: "主题",
18
+ },
19
+ account: {
20
+ dangerZone: "危险区域",
21
+ deleteAccountDescription: "永久删除您的账户及所有相关数据。",
22
+ changeAvatar: "更换头像",
23
+ },
24
+ search: {
25
+ clear: "清除搜索",
26
+ },
27
+ security: {
28
+ title: "安全",
29
+ twoFactor: "双重身份验证",
30
+ on: "已开启",
31
+ off: "已关闭 · 登录时增加第二步验证",
32
+ checking: "检查中…",
33
+ unavailable: "不可用",
34
+ turnOn: "开启",
35
+ turnOff: "关闭",
36
+ setupTitle: "设置双重身份验证",
37
+ setupHint: "使用您的验证器应用(Google Authenticator、Authy 等)扫描二维码",
38
+ cancel: "取消",
39
+ deviceAdded: "添加于 {{date}}",
40
+ devicePrimary: "主要",
41
+ disableTitle: "要关闭双重身份验证吗?",
42
+ disableHint: "请输入验证器应用中的 6 位验证码以确认。这会降低账户的安全性。",
43
+ disabling: "正在关闭…",
44
+ disable: "关闭双重验证",
45
+ },
46
+ apiKey: {
47
+ title: "API 密钥",
48
+ description: "用于程序化访问的个人 API 密钥",
49
+ created: "创建时间",
50
+ reissued: "上次重新签发",
51
+ neverReissued: "从未重新签发",
52
+ regenerate: "重新生成 API 密钥",
53
+ confirmRegenerate: "点击确认",
54
+ regenerating: "正在重新生成…",
55
+ regenerated: "API 密钥已重新生成",
56
+ failedToRegenerate: "重新生成 API 密钥失败",
57
+ done: "完成",
58
+ test: "测试 API 密钥",
59
+ testing: "测试中…",
60
+ testSuccess: "API 密钥有效",
61
+ testFailed: "API 密钥测试失败",
62
+ keyRow: "API 密钥",
63
+ active: "有效",
64
+ freshKey: "这是您的新 API 密钥,请立即复制。离开此页面后将被隐藏。",
65
+ },
66
+ };
@@ -0,0 +1,65 @@
1
+ /** Built-in Settings strings. English is the schema of record; `{{name}}` is filled by `fill`. */
2
+ export interface SettingsCopy {
3
+ nav: {
4
+ title: string;
5
+ platform: string;
6
+ account: string;
7
+ preferences: string;
8
+ apiKeys: string;
9
+ apiKeysDescription: string;
10
+ };
11
+ prefs: {
12
+ general: string;
13
+ other: string;
14
+ language: string;
15
+ timeZone: string;
16
+ theme: string;
17
+ };
18
+ account: {
19
+ dangerZone: string;
20
+ deleteAccountDescription: string;
21
+ changeAvatar: string;
22
+ };
23
+ search: {
24
+ clear: string;
25
+ };
26
+ security: {
27
+ title: string;
28
+ twoFactor: string;
29
+ on: string;
30
+ off: string;
31
+ checking: string;
32
+ unavailable: string;
33
+ turnOn: string;
34
+ turnOff: string;
35
+ setupTitle: string;
36
+ setupHint: string;
37
+ cancel: string;
38
+ deviceAdded: string;
39
+ devicePrimary: string;
40
+ disableTitle: string;
41
+ disableHint: string;
42
+ disabling: string;
43
+ disable: string;
44
+ };
45
+ apiKey: {
46
+ title: string;
47
+ description: string;
48
+ created: string;
49
+ reissued: string;
50
+ neverReissued: string;
51
+ regenerate: string;
52
+ confirmRegenerate: string;
53
+ regenerating: string;
54
+ regenerated: string;
55
+ failedToRegenerate: string;
56
+ done: string;
57
+ test: string;
58
+ testing: string;
59
+ testSuccess: string;
60
+ testFailed: string;
61
+ keyRow: string;
62
+ active: string;
63
+ freshKey: string;
64
+ };
65
+ }
@@ -50,7 +50,6 @@ export {
50
50
  buildBuiltinSections,
51
51
  BUILTIN_GROUP,
52
52
  AccountSection,
53
- SecuritySection,
54
53
  ApiKeysSection,
55
54
  SettingRow,
56
55
  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
- {/* Language + Theme moved to the settings-rail footer (bottom-left of the
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="Danger zone" collapsible defaultOpen={false}>
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="Permanently remove your account and all associated data."
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 { SettingRow } from '@djangocfg/ui-core/components';
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';
10
+ import { useSettingsCopy } from '../i18n';
9
11
  import { useLayoutI18nOptional } from '../../AppLayout/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="Language"
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 label="Theme" action={<ThemeSegmented />} />
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, ShieldCheck, UserRound } from 'lucide-react';
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 { SecuritySection } from './SecuritySection';
11
+ import type { SettingsCopy } from '../i18n';
11
12
 
12
13
  /**
13
- * The group every built-in section belongs to. App sections can reuse this id
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', label: 'General', order: 0 };
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 — labels
25
- * are left as plain strings here (the inner section bodies pull localized copy
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 management) is a built-in the user
29
- * must be able to find without the app opting in — `/cfg/totp/*` ships with
30
- * every django-cfg backend. Apps that truly lack it opt out explicitly via
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: 'Account',
49
+ label: copy.nav.account,
44
50
  icon: UserRound,
45
51
  group: BUILTIN_GROUP.id,
46
- title: 'Account',
47
- description: 'Manage your profile, preferences, and account.',
48
- keywords: ['profile', 'name', 'email', 'avatar', 'language', 'theme', 'preferences'],
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 (security) {
58
+ if (general) {
54
59
  sections.push({
55
- id: 'security',
56
- label: 'Security',
57
- icon: ShieldCheck,
60
+ id: 'general',
61
+ label: copy.nav.preferences,
62
+ icon: SettingsIcon,
58
63
  group: BUILTIN_GROUP.id,
59
- title: 'Security',
60
- description: 'Two-factor authentication and active sessions.',
61
- keywords: ['2fa', 'two-factor', 'mfa', 'authenticator', 'password', 'devices'],
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: 'API keys',
73
+ label: copy.nav.apiKeys,
70
74
  icon: KeyRound,
71
- group: BUILTIN_GROUP.id,
72
- title: 'API keys',
73
- description: 'Create and manage keys for programmatic access.',
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.
@@ -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
- /** Account / profile fields + preferences. Default: true. */
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 / security. Default: false (opt-in, needs API support). */
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
- /** Show the search box above the nav rail. Default: true. */
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
  }
@@ -133,11 +133,10 @@ export interface I18nLayoutConfig {
133
133
 
134
134
  /**
135
135
  * Visual style of the private/app shell.
136
- * - `boxed` (default) — content lives in a fixed, rounded card inset from the
137
- * viewport edges while the sidebar-coloured background bleeds to the screen
138
- * border. Internal scroll inside the card; mobile (<md) falls back to
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: `'boxed'`. */
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.
@@ -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;