v4: the playlist and dump edit forms end in one bar of actions, a panel of its own — the playlist editor drew rules between its fields and its actions, and on a phone its Delete, Cancel and Save wrapped into a staircase, then stacked (a0c8b1d), neither of which held together. Both forms are now built as DumpEdit was: surface panels 3px apart (.form-panel, from .dump-edit-panel), the last a bar (.form-panel--bar, from .dump-edit-footer) holding Delete on the left and Cancel and Save on the right, compact (btn-sm), on one line. The new DeleteButton says "Delete" and names what goes to assistive tech ("Delete playlist", "Delete dump"); in a bar too narrow for the three labels in every face and language (a container query, under 18rem), it shows 🗑 instead. While edited, the playlist's header drops its own card for the form's panels. The phone stacking rule is gone. An auto-sized editor also re-fits when its width changes with its text unchanged: opening the form makes the page tall enough for a scrollbar, which narrowed the description after it was measured, so it overflowed; and it hides its overflow below its max-height, where a scrollbar could only come of rounding, and drops the resize grip, which the next keystroke would undo. 669 frontend tests pass, and tsc, eslint, deno fmt, doc:lint and i18n:compile --strict are clean; checked in screenshots in Kanagawa dark (French, 385 and 360px), Smooth light at 1000px and DumpEdit in Smooth at 390px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-08 22:05:43 +00:00
parent b9281da9c9
commit 5acdf79c8d
9 changed files with 313 additions and 224 deletions

View File

@@ -47,6 +47,7 @@ import { SubmitButton } from "../components/form/SubmitButton.tsx";
import { TextField } from "../components/form/TextField.tsx";
import { useApiForm } from "../hooks/form/useApiForm.ts";
import { VisibilityToggle } from "../components/form/SegmentedField.tsx";
import { DeleteButton } from "../components/form/DeleteButton.tsx";
import { SlugField } from "../components/form/SlugField.tsx";
import { useCanonicalPath } from "../hooks/shared/useCanonicalPath.ts";
import { userPath } from "../../shared/paths.ts";
@@ -446,7 +447,7 @@ function DumpEditForm({
/* The source panel holds what was dumped, the details panel how it
is presented and who sees it. */
}
<div className="dump-edit-panel dump-edit-panel--source">
<div className="form-panel dump-edit-panel--source">
{preview}
<div className="form-field">
@@ -507,7 +508,7 @@ function DumpEditForm({
)}
</div>
<div className="dump-edit-panel dump-edit-panel--details">
<div className="form-panel dump-edit-panel--details">
<FormError title={t`Could not save`} />
<TextField<EditValues>
@@ -553,23 +554,17 @@ function DumpEditForm({
<VisibilityToggle<EditValues> name="isPublic" />
</div>
<div className="dump-edit-footer">
<div className="form-panel form-panel--bar">
{deleteError && (
<ErrorCard title={t`Could not delete`} message={deleteError} />
)}
<div className="form-actions">
<button
type="button"
onClick={onRequestDelete}
className="btn-danger"
>
<Trans>Delete dump</Trans>
</button>
<DeleteButton label={t`Delete dump`} onClick={onRequestDelete} />
<div className="form-actions-right">
<Link to={dumpUrl(dump)} className="form-cancel">
<Trans>Cancel</Trans>
</Link>
<SubmitButton>
<SubmitButton className="btn-primary btn-sm">
<Trans>Save</Trans>
</SubmitButton>
</div>

View File

@@ -90,7 +90,12 @@ export function PlaylistDetail(): React.JSX.Element {
return (
<PageShell>
<div className="playlist-detail-header" ref={headerRef}>
<div
className={`playlist-detail-header${
editOpen ? " playlist-detail-header--editing" : ""
}`}
ref={headerRef}
>
{editOpen
? (
<PlaylistEditForm