settings belong at the bottom of the sidebar, not behind a gear in the header

The owner asked for this after failing to find it, which is the whole argument: a
gear in a header is where a settings control is easy to write and not where
anybody looks for one. The bottom of a sidebar is its conventional home, and a
labelled row rather than an icon means it says what it is.

The form opens ABOVE the button so the control does not move when you use it. A
disclosure that walks away from the pointer as it expands is the small kind of
wrong that makes a thing feel unfinished.

`aria-expanded` on the button, because it is a disclosure and a screen reader has
no other way to know the state of one.

The error path still force-opens it - "no account is mapped to you" is the state
every user starts in, and the fix is in that form.
This commit is contained in:
2026-09-05 21:49:09 +01:00
parent d882e349a9
commit 2b3b986c0e
2 changed files with 26 additions and 11 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ import type { PluginManifest } from '@@/plugins/manifest';
*/
export const manifest: PluginManifest = {
publisher: 'officerdev',
version: '2026.09.05.03',
version: '2026.09.05.04',
platform: '>=1.0.0',
label: 'Karakeep',
+25 -10
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSearchParams } from 'react-router';
import { useClient } from 'hooks/useClient';
import { Folder, Settings2, Layers } from 'lucide-react';
import { Folder, Settings2, Layers, ChevronUp, ChevronDown } from 'lucide-react';
import { SettingsForm } from './SettingsForm';
/**
@@ -61,16 +61,8 @@ export const ListsPanel = () => {
return (
<div className="flex size-full flex-col">
<header className="flex items-center justify-between border-b px-2 py-1.5">
<header className="border-b px-3 py-1.5">
<span className="text-xs font-medium">Lists</span>
<button
type="button"
onClick={() => setShowSettings((was) => !was)}
className="p-1 text-muted-foreground hover:text-foreground"
aria-label="Settings"
>
<Settings2 className="size-3.5" />
</button>
</header>
{error && <p className="px-3 py-2 text-[11px] text-destructive">{error}</p>}
@@ -105,7 +97,30 @@ export const ListsPanel = () => {
))}
</ul>
{/*
Settings, pinned to the bottom.
It was a gear in the header, which is where a settings control is easy to WRITE and not where
anybody looks for one: the owner asked for this after failing to find it. The bottom of a
sidebar is the conventional home for the thing you configure once, and being a labelled row
rather than an icon means it says what it is.
The form opens ABOVE the button rather than below it, so the control does not move when you use
it. A disclosure that walks away from the pointer as it expands is the small kind of wrong that
makes something feel unfinished.
*/}
{showSettings && <SettingsForm />}
<button
type="button"
onClick={() => setShowSettings((was) => !was)}
aria-expanded={showSettings}
className="flex w-full shrink-0 items-center gap-2 border-t px-3 py-2 text-left text-xs text-muted-foreground hover:bg-muted/50 hover:text-foreground"
>
<Settings2 className="size-3.5 shrink-0" />
<span className="flex-1">Settings</span>
{showSettings ? <ChevronDown className="size-3.5" /> : <ChevronUp className="size-3.5" />}
</button>
</div>
);
};