feat(settings): Group page description, collapsed page tree and a seamless title bar

- Group pages open with a short description and list their pages as
  indented links.
- The page tree starts collapsed, expanding only the current page's
  ancestors.
- The Settings window title bar sits on the dialog surface without a
  hairline.
Цей коміт міститься в:
l0ng-ai committed 2026-10-28 10:20:26 +08:00
1 джерело c6a4361513
коміт b3b4cbbe1a
5 files changed
+66 -17

No files matched your search

+21 -8
Переглянути файл
@@ -147,24 +147,37 @@
}
.groupPage {
padding-top: 8px;
}
.groupDescription {
margin: 0 0 14px;
max-width: 720px;
color: var(--fg);
line-height: 1.45;
}
.groupLinks {
display: flex;
flex-direction: column;
max-width: 420px;
align-items: flex-start;
gap: 2px;
padding-left: 32px;
}
.groupLink {
display: flex;
align-items: center;
justify-content: space-between;
height: 28px;
padding: 0 8px;
border-radius: var(--radius-row);
height: 20px;
padding: 0;
border: 0;
background: none;
color: var(--link);
font: inherit;
text-align: left;
cursor: pointer;
}
.groupLink:hover {
background: var(--bg-hover);
color: var(--link-hover);
text-decoration: underline;
}
.groupLink:focus-visible {
outline: 2px solid var(--focus-ring, var(--accent));
}
.footerStatus {
+33 -7
Переглянути файл
@@ -42,20 +42,46 @@ import { PageErrorBoundary } from "./PageErrorBoundary";
import styles from "./SettingsDialog.module.css";
import { tx } from "@/platform/i18n";
/** Keys of the nodes above `key`, root first. */
function ancestorKeys(nodes: readonly PageNode[], key: string): string[] {
for (const n of nodes) {
if (n.key === key) return [];
const sub = ancestorKeys(n.children, key);
if (sub.length || n.children.some((c) => c.key === key)) return [n.key, ...sub];
}
return [];
}
function firstPage(nodes: readonly PageNode[]): string | null {
const list = flatten(nodes).filter((n) => n.page);
return (list.find((n) => n.matched) ?? list[0])?.key ?? null;
}
const GROUP_DESCRIPTIONS: Record<string, string> = {
"Appearance & Behavior":
"Customize IDE appearance and behavior: change themes and font size, configure system settings and notifications.",
Editor: "Configure code editing: completion, folding, tabs, fonts, colors, code style and inspections.",
"Build, Execution, Deployment": "Configure build tools, run configurations, debugger and deployment.",
"Languages & Frameworks": "Configure language servers, interpreters, SDKs and framework support.",
Tools: "Configure external tools, terminal, database and other integrations.",
};
function GroupPage({ node }: { node: PageNode }) {
const description = GROUP_DESCRIPTIONS[node.title];
return (
<div className={styles.groupPage}>
{node.children.map((c) => (
<button key={c.key} type="button" className={styles.groupLink} onClick={() => selectPage(c.key)}>
<SearchLabel text={c.title} />
<Icon name="chevron-right" size={12} />
</button>
))}
{description && (
<p className={styles.groupDescription}>
<T>{description}</T>
</p>
)}
<div className={styles.groupLinks}>
{node.children.map((c) => (
<button key={c.key} type="button" className={styles.groupLink} onClick={() => selectPage(c.key)}>
<SearchLabel text={c.title} />
</button>
))}
</div>
</div>
);
}
@@ -263,7 +289,7 @@ export function SettingsLayout() {
getLabel={(n) => tx(n.title)}
getChildren={(n) => (n.children.length ? n.children : undefined)}
isExpandable={(n) => n.children.length > 0}
defaultExpanded={all.filter((n) => n.children.length).map((n) => n.key)}
defaultExpanded={current ? ancestorKeys(tree, current.key) : []}
expanded={query ? expanded : undefined}
selected={current ? new Set([current.key]) : new Set()}
onSelectionChange={(_sel, node) => node && selectPage(node.key)}