move icon rails to bottom strip row aligned with status bar
Icon rails pulled out of _SidebarSlot and _ContextSlot into the bottom row alongside StatusbarHost. All three strips — left icon rail, app strip, right icon rail — now sit on one continuous horizontal line per D-047. Sidebar and context panels render content only; their bottom rails share the status bar row with matched widths. Collapsed spines get a matching-width spacer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+53
-41
@@ -173,7 +173,19 @@ class RootLayout extends StatelessWidget {
|
|||||||
if (statusVisible)
|
if (statusVisible)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: statusHeight,
|
height: statusHeight,
|
||||||
child: const StatusbarHost(),
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (sidebarVisible && !sidebarCollapsed)
|
||||||
|
SizedBox(width: sidebarSize, child: _BottomRail(slot: Slots.sidebar))
|
||||||
|
else if (sidebarVisible && sidebarCollapsed)
|
||||||
|
const SizedBox(width: ClideSpine.width),
|
||||||
|
Expanded(child: const StatusbarHost()),
|
||||||
|
if (contextVisible && !contextCollapsed)
|
||||||
|
SizedBox(width: contextSize, child: _BottomRail(slot: Slots.contextPanel))
|
||||||
|
else if (contextVisible && contextCollapsed)
|
||||||
|
const SizedBox(width: ClideSpine.width),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -285,38 +297,9 @@ class _SidebarSlot extends StatelessWidget {
|
|||||||
final tokens = ClideTheme.of(context).surface;
|
final tokens = ClideTheme.of(context).surface;
|
||||||
return Container(
|
return Container(
|
||||||
color: tokens.sidebarBackground,
|
color: tokens.sidebarBackground,
|
||||||
child: Column(
|
child: active.build(context),
|
||||||
children: [
|
|
||||||
Expanded(child: active.build(context)),
|
|
||||||
ClideIconRail(
|
|
||||||
items: [
|
|
||||||
for (final t in tabs)
|
|
||||||
ClideIconRailItem(
|
|
||||||
id: t.id,
|
|
||||||
icon: _iconFor(t),
|
|
||||||
tooltip: SlotHost._resolveTitle(context, t),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
activeId: activeId,
|
|
||||||
onSelect: onSelect,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ClideIconPainter _iconFor(TabContribution t) {
|
|
||||||
if (t.icon is ClideIconPainter) return t.icon as ClideIconPainter;
|
|
||||||
return switch (t.id) {
|
|
||||||
'files.tree' => PhosphorIcons.folder,
|
|
||||||
'git.panel' => PhosphorIcons.gitBranch,
|
|
||||||
'pql.panel' => PhosphorIcons.magnifyingGlass,
|
|
||||||
'problems.panel' => PhosphorIcons.warningCircle,
|
|
||||||
'decisions.panel' => PhosphorIcons.lightbulb,
|
|
||||||
'tickets.panel' => PhosphorIcons.ticket,
|
|
||||||
_ => PhosphorIcons.circlesFour,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WorkspaceSlot extends StatelessWidget {
|
class _WorkspaceSlot extends StatelessWidget {
|
||||||
@@ -429,28 +412,57 @@ class _ContextSlot extends StatelessWidget {
|
|||||||
final tokens = ClideTheme.of(context).surface;
|
final tokens = ClideTheme.of(context).surface;
|
||||||
return Container(
|
return Container(
|
||||||
color: tokens.panelBackground,
|
color: tokens.panelBackground,
|
||||||
child: Column(
|
child: active.build(context),
|
||||||
children: [
|
);
|
||||||
Expanded(child: active.build(context)),
|
}
|
||||||
ClideIconRail(
|
}
|
||||||
|
|
||||||
|
class _BottomRail extends StatelessWidget {
|
||||||
|
const _BottomRail({required this.slot});
|
||||||
|
final SlotId slot;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final kernel = ClideKernel.of(context);
|
||||||
|
final tokens = ClideTheme.of(context).surface;
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: kernel.panels,
|
||||||
|
builder: (ctx, _) {
|
||||||
|
final tabs = kernel.panels.tabsFor(slot);
|
||||||
|
if (tabs.isEmpty) return Container(color: tokens.statusBarBackground);
|
||||||
|
final activeId = kernel.panels.activeTabIn(slot) ?? tabs.first.id;
|
||||||
|
return Container(
|
||||||
|
color: tokens.statusBarBackground,
|
||||||
|
child: ClideIconRail(
|
||||||
items: [
|
items: [
|
||||||
for (final t in tabs)
|
for (final t in tabs)
|
||||||
ClideIconRailItem(
|
ClideIconRailItem(
|
||||||
id: t.id,
|
id: t.id,
|
||||||
icon: _iconFor(t),
|
icon: _iconFor(slot, t),
|
||||||
tooltip: SlotHost._resolveTitle(context, t),
|
tooltip: SlotHost._resolveTitle(ctx, t),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
activeId: activeId,
|
activeId: activeId,
|
||||||
onSelect: onSelect,
|
onSelect: (id) => kernel.panels.activateTab(slot, id),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ClideIconPainter _iconFor(TabContribution t) {
|
static ClideIconPainter _iconFor(SlotId slot, TabContribution t) {
|
||||||
if (t.icon is ClideIconPainter) return t.icon as ClideIconPainter;
|
if (t.icon is ClideIconPainter) return t.icon as ClideIconPainter;
|
||||||
|
if (slot == Slots.sidebar) {
|
||||||
|
return switch (t.id) {
|
||||||
|
'files.tree' => PhosphorIcons.folder,
|
||||||
|
'git.panel' => PhosphorIcons.gitBranch,
|
||||||
|
'pql.panel' => PhosphorIcons.magnifyingGlass,
|
||||||
|
'problems.panel' => PhosphorIcons.warningCircle,
|
||||||
|
'decisions.panel' => PhosphorIcons.lightbulb,
|
||||||
|
'tickets.panel' => PhosphorIcons.ticket,
|
||||||
|
_ => PhosphorIcons.circlesFour,
|
||||||
|
};
|
||||||
|
}
|
||||||
return switch (t.id) {
|
return switch (t.id) {
|
||||||
'markdown.viewer' => PhosphorIcons.eye,
|
'markdown.viewer' => PhosphorIcons.eye,
|
||||||
'graph.view' => PhosphorIcons.graph,
|
'graph.view' => PhosphorIcons.graph,
|
||||||
|
|||||||
Reference in New Issue
Block a user