Draw an expanded folder as a panel around its servers
An open folder and the servers inside it now share a container with a hairline border and a ground a shade lighter than the rail, the way Discord marks one. Without it there was nothing to say where the folder ended - the contents are deliberately not indented, so a run of tiles below a folder looked exactly like the rest of the column. This needed the members to belong to the folder rather than being spliced into the entry list beside it, which is a better shape anyway: the folder now carries what is in it and the rail decides whether to draw them. Collapsed, the container has no border, no ground and no padding - the folder is just another tile in the column. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
94a51e9e51
commit
e36bc09408
3 files changed
+63
-39
No files matched your search
@@ -2338,6 +2338,25 @@ video.lightbox-media {
|
||||
|
||||
/* --- rail folders ------------------------------------------------------ */
|
||||
|
||||
/* An expanded folder and its servers share one panel: a hairline border and
|
||||
a ground a shade lighter than the rail, which is what marks where the
|
||||
folder ends now that its contents are not indented. Collapsed, the
|
||||
container is invisible and the folder is just another tile. */
|
||||
.rail-folder-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rail-folder-group.open {
|
||||
padding: var(--spacing-xxs) 0;
|
||||
background: var(--surface-container-low);
|
||||
border: 1px solid var(--outline-medium);
|
||||
border-radius: var(--corner-radius);
|
||||
}
|
||||
|
||||
/* Squarer than a server tile, so a folder is not mistaken for one. */
|
||||
.rail-folder .rail-face {
|
||||
border-radius: var(--corner-radius);
|
||||
|
||||
@@ -269,7 +269,38 @@ export function ServerRail(): JSX.Element | null {
|
||||
setNaming({ id, name: 'New folder' })
|
||||
}
|
||||
|
||||
const entries = railEntries(ordered, folders, isFolderOpen)
|
||||
const entries = railEntries(ordered, folders)
|
||||
|
||||
const renderTile = (g: RailGroup): JSX.Element => {
|
||||
const t = totals.get(g.id)
|
||||
return (
|
||||
<RailTile
|
||||
key={g.id}
|
||||
group={g}
|
||||
customIcon={customIcons[g.id]}
|
||||
muted={mutedGroups.includes(g.id)}
|
||||
onToggleMute={() =>
|
||||
setMutedGroups(
|
||||
mutedGroups.includes(g.id)
|
||||
? mutedGroups.filter((x) => x !== g.id)
|
||||
: [...mutedGroups, g.id]
|
||||
)
|
||||
}
|
||||
active={g.id === activeGroupId}
|
||||
unread={t?.unread ?? 0}
|
||||
highlight={t?.highlight ?? false}
|
||||
// Direct messages and pinned lead the rail by definition, so
|
||||
// there is nowhere for them to be dragged to.
|
||||
draggable={!isFixedEntry(g)}
|
||||
dropTarget={over === g.id && dragging !== '' && dragging !== g.id}
|
||||
onSelect={() => store.selectGroup(g.id)}
|
||||
onDragStart={() => beginDrag(g.id)}
|
||||
onDragOver={() => !isFixedEntry(g) && setOver(g.id)}
|
||||
onDrop={() => onDrop(g.id)}
|
||||
onDragEnd={endDrag}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="server-rail" aria-label="Servers">
|
||||
@@ -294,7 +325,12 @@ export function ServerRail(): JSX.Element | null {
|
||||
>
|
||||
{entries.map((entry) => {
|
||||
if (entry.kind === 'folder') {
|
||||
const open = isFolderOpen(entry.id)
|
||||
return (
|
||||
/* Expanded, the folder and its servers sit in one panel - the
|
||||
border and lighter ground are what say where the folder ends,
|
||||
now that its contents are no longer indented. */
|
||||
<div key={entry.id} className={classes('rail-folder-group', open && 'open')}>
|
||||
<FolderTile
|
||||
key={entry.id}
|
||||
folder={entry.folder}
|
||||
@@ -310,37 +346,11 @@ export function ServerRail(): JSX.Element | null {
|
||||
onDrop={() => onDropInFolder(entry.id)}
|
||||
onDragEnd={endDrag}
|
||||
/>
|
||||
{open && entry.members.map((m) => renderTile(m))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const g = entry.group
|
||||
const t = totals.get(g.id)
|
||||
return (
|
||||
<RailTile
|
||||
key={g.id}
|
||||
group={g}
|
||||
customIcon={customIcons[g.id]}
|
||||
muted={mutedGroups.includes(g.id)}
|
||||
onToggleMute={() =>
|
||||
setMutedGroups(
|
||||
mutedGroups.includes(g.id)
|
||||
? mutedGroups.filter((x) => x !== g.id)
|
||||
: [...mutedGroups, g.id]
|
||||
)
|
||||
}
|
||||
active={g.id === activeGroupId}
|
||||
unread={t?.unread ?? 0}
|
||||
highlight={t?.highlight ?? false}
|
||||
// Direct messages and pinned lead the rail by definition, so
|
||||
// there is nowhere for them to be dragged to.
|
||||
draggable={!isFixedEntry(g)}
|
||||
dropTarget={over === g.id && dragging !== '' && dragging !== g.id}
|
||||
onSelect={() => store.selectGroup(g.id)}
|
||||
onDragStart={() => beginDrag(g.id)}
|
||||
onDragOver={() => !isFixedEntry(g) && setOver(g.id)}
|
||||
onDrop={() => onDrop(g.id)}
|
||||
onDragEnd={endDrag}
|
||||
/>
|
||||
)
|
||||
return renderTile(entry.group)
|
||||
})}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -227,13 +227,11 @@ export type RailEntry =
|
||||
*
|
||||
* A folder takes the position of its first member, so putting servers into one
|
||||
* does not also reshuffle the column - the folder appears where the topmost of
|
||||
* them already was. Members are drawn under it only while it is open.
|
||||
* them already was. Its members come back attached to it rather than spliced
|
||||
* into the list, because the rail draws an expanded folder and its servers
|
||||
* inside one container.
|
||||
*/
|
||||
export function railEntries(
|
||||
ordered: RailGroup[],
|
||||
folders: RailFolder[],
|
||||
open: (folderId: string) => boolean
|
||||
): RailEntry[] {
|
||||
export function railEntries(ordered: RailGroup[], folders: RailFolder[]): RailEntry[] {
|
||||
const owner = new Map<string, RailFolder>()
|
||||
for (const f of folders) {
|
||||
for (const id of f.members) owner.set(id, f)
|
||||
@@ -255,9 +253,6 @@ export function railEntries(
|
||||
.map((id) => ordered.find((x) => x.id === id))
|
||||
.filter((x): x is RailGroup => !!x)
|
||||
out.push({ kind: 'folder', id: folder.id, folder, members })
|
||||
if (open(folder.id)) {
|
||||
for (const m of members) out.push({ kind: 'group', id: m.id, group: m })
|
||||
}
|
||||
}
|
||||
|
||||
// A folder with nothing in it still draws, at the foot of the column. A
|
||||
|
||||
Reference in new issue
Block a user