mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
ui: a Back button is a row of one, not a stretched flex child
Reported: the Back button fills the page width on the domain and channel edit pages. It fills it on all nine pages that have one, and this is mine from the last PR. `.wrolpi-stack` is a flex column at `align-items: stretch`, deliberately -- a panel was full-width as a block element and has to stay that way. A BARE button as a direct child of that stack is stretched too, and every page's Back button was one. On the domain, channel and playlist edit pages it also pushed the button beside it onto its own line, where the two had been inline. Wrapping each in a `.wrolpi-button-row` fixes both without touching the stack: the row is the stretched child, and it lays its buttons out at their own width. The three pages that have a second button get one row holding both, which is how they read before. Also the Videos, Archives and Documents select bars, whose Tag/Delete/Invert/ Clear were four touching buttons in a bare div. They are inline elements rather than interpolations, which is the shape the bare-siblings scan does not see. Guarded twice: the row keeps its buttons their own width inside a stack while itself stretching (both halves, since a row that refused to stretch would pass a test that only looked at the button), and every `<BackButton/>` in the app is inside a row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7741ee1f12
commit
076e982b69
8 changed files with 88 additions and 21 deletions
|
|
@ -254,7 +254,7 @@ function ArchivePage() {
|
|||
const labelStyle = {whiteSpace: 'nowrap', paddingRight: '1.5em', verticalAlign: 'top'};
|
||||
|
||||
return <>
|
||||
<BackButton/>
|
||||
<div className='wrolpi-button-row'><BackButton/></div>
|
||||
|
||||
<Panel>
|
||||
{screenshot}
|
||||
|
|
@ -619,7 +619,7 @@ export function DomainEditPage() {
|
|||
if (!form.ready) {
|
||||
if (form.error) {
|
||||
return <>
|
||||
<BackButton/>
|
||||
<div className='wrolpi-button-row'><BackButton/></div>
|
||||
<Message kind='error' title='Domain not found'>
|
||||
<p>{form.error}</p>
|
||||
</Message>
|
||||
|
|
@ -685,10 +685,12 @@ export function DomainEditPage() {
|
|||
const [descriptionProps] = form.getCustomProps({name: 'description', path: 'description'});
|
||||
|
||||
return <>
|
||||
<BackButton/>
|
||||
<Link to={`/archives?domain=${domain?.domain}`}>
|
||||
<Button>Archives</Button>
|
||||
</Link>
|
||||
<div className='wrolpi-button-row'>
|
||||
<BackButton/>
|
||||
<Link to={`/archives?domain=${domain?.domain}`}>
|
||||
<Button>Archives</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{domain?.needs_reorganization && (
|
||||
<Message kind='warning' title='File Format Changed'>
|
||||
|
|
@ -1171,7 +1173,7 @@ function ArchivesPage() {
|
|||
setSelectedArchives([]);
|
||||
}
|
||||
|
||||
const selectElm = <div style={{marginTop: '0.5em'}}>
|
||||
const selectElm = <div className='wrolpi-button-row' style={{marginTop: '0.5em'}}>
|
||||
<Button
|
||||
role='primary'
|
||||
disabled={_.isEmpty(selectedArchives)}
|
||||
|
|
|
|||
|
|
@ -209,10 +209,12 @@ export function ChannelEditPage() {
|
|||
const downloadMissingDataLabel = <>Download Missing Data<InfoPopup content={downloadMissingDataInfo}/></>;
|
||||
|
||||
return <>
|
||||
<BackButton/>
|
||||
<Link to={`/videos/channel/${channel.id}/video`}>
|
||||
<Button>Videos</Button>
|
||||
</Link>
|
||||
<div className='wrolpi-button-row'>
|
||||
<BackButton/>
|
||||
<Link to={`/videos/channel/${channel.id}/video`}>
|
||||
<Button>Videos</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{channel?.needs_reorganization && (
|
||||
<Message kind='warning' title='File Format Changed'>
|
||||
|
|
@ -419,7 +421,7 @@ export function ChannelNewPage() {
|
|||
}
|
||||
|
||||
return <>
|
||||
<BackButton/>
|
||||
<div className='wrolpi-button-row'><BackButton/></div>
|
||||
|
||||
<Panel>
|
||||
<Header as="h1">New Channel</Header>
|
||||
|
|
|
|||
|
|
@ -1002,6 +1002,41 @@ describe('a row of buttons', () => {
|
|||
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it('puts every Back button in a row, so the page stack cannot stretch it', () => {
|
||||
/*
|
||||
* A page's blocks are stacked by `.wrolpi-stack`, which is a flex column at
|
||||
* `align-items: stretch` -- deliberately, because a panel was full-width as a block
|
||||
* element and has to stay that way. A BARE button as a direct child of that stack is
|
||||
* stretched too, and every page's Back button was one: full-page-width, and on the domain,
|
||||
* channel and playlist edit pages the button beside it was pushed onto its own line where
|
||||
* the two had been inline before.
|
||||
*
|
||||
* A row fixes it without touching the stack: the row is the stretched child, and it lays
|
||||
* its buttons out at their own width. Nine sites, so this scan rather than nine
|
||||
* assertions -- and a scan because a stretched button is still a working button.
|
||||
*/
|
||||
const offenders = [];
|
||||
let found = 0;
|
||||
|
||||
for (const file of sourceFiles()) {
|
||||
const text = fs.readFileSync(file, 'utf8');
|
||||
const tags = tagsIn(text);
|
||||
|
||||
for (const match of text.matchAll(/<BackButton\b/g)) {
|
||||
found += 1;
|
||||
if (!isRow(enclosingTag(tags, match.index))) {
|
||||
offenders.push(`${path.relative(SRC, file)}:${
|
||||
text.slice(0, match.index).split('\n').length}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nine renders. Common.js declares it, and a declaration is not a `<BackButton`.
|
||||
expect(found).toBe(9);
|
||||
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
/** The app's own source, minus its tests: what a source scan is allowed to read. */
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ function DocsPage() {
|
|||
setSelectedDocs([]);
|
||||
}
|
||||
|
||||
const selectElm = <div style={{marginTop: '0.5em'}}>
|
||||
const selectElm = <div className='wrolpi-button-row' style={{marginTop: '0.5em'}}>
|
||||
<Button color='violet' disabled={_.isEmpty(selectedDocs)}
|
||||
onClick={() => setBulkTagOpen(true)}>Tag</Button>
|
||||
<APIButton
|
||||
|
|
@ -339,7 +339,7 @@ function DocPage() {
|
|||
</table>;
|
||||
|
||||
return <>
|
||||
<BackButton/>
|
||||
<div className='wrolpi-button-row'><BackButton/></div>
|
||||
|
||||
{isCbz && activePath && <CbzViewer path={activePath}/>}
|
||||
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ export function PlaylistViewPage() {
|
|||
const items = playlist.items || [];
|
||||
|
||||
return <>
|
||||
<BackButton/>
|
||||
<div className='wrolpi-button-row'><BackButton/></div>
|
||||
|
||||
<Header as='h1'>
|
||||
{playlist.name}
|
||||
|
|
@ -498,10 +498,12 @@ export function PlaylistEditPage() {
|
|||
const form = {error: null, loading: false, disabled: !editable || !name.trim(), ready: true};
|
||||
|
||||
return <>
|
||||
<BackButton/>
|
||||
<Link to={`/playlists/${playlistId}`}>
|
||||
<Button>View</Button>
|
||||
</Link>
|
||||
<div className='wrolpi-button-row'>
|
||||
<BackButton/>
|
||||
<Link to={`/playlists/${playlistId}`}>
|
||||
<Button>View</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<CollectionEditForm
|
||||
form={form}
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ function VideoPage({videoFile, prevFile, nextFile, fetchVideo, ...props}) {
|
|||
const audioPlayPauseLabel = audioPlaying ? 'Pause' : 'Play';
|
||||
|
||||
return <>
|
||||
<div style={{margin: '1em'}}>
|
||||
<div className='wrolpi-button-row' style={{margin: '1em'}}>
|
||||
<BackButton/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ export function VideosPage() {
|
|||
setSelectedVideos([]);
|
||||
}
|
||||
|
||||
const selectElm = <div style={{marginTop: '0.5em'}}>
|
||||
const selectElm = <div className='wrolpi-button-row' style={{marginTop: '0.5em'}}>
|
||||
<Button
|
||||
role='primary'
|
||||
disabled={_.isEmpty(selectedVideos)}
|
||||
|
|
|
|||
|
|
@ -390,6 +390,32 @@ describe('a row of buttons is spaced and aligned by the row', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('keeps its buttons their own width inside a page stack', () => {
|
||||
/*
|
||||
* `.wrolpi-stack` is a flex column at `align-items: stretch`, which is what keeps a panel
|
||||
* full-width as it was as a block element -- and which stretched every page's Back button
|
||||
* to the full width of the page, because a bare button was a direct child of it.
|
||||
*
|
||||
* The row is what absorbs that: the ROW is the stretched child, and it lays its buttons out
|
||||
* at their own width. Both halves are asserted, because a row that also refused to stretch
|
||||
* would pass a test that only looked at the button.
|
||||
*/
|
||||
cy.mountUI(<div className='wrolpi-stack'>
|
||||
<div className='wrolpi-button-row'><Button>Back</Button></div>
|
||||
<Panel>The page</Panel>
|
||||
</div>);
|
||||
|
||||
cy.get('.wrolpi-stack').should(($stack) => {
|
||||
const stack = $stack[0].getBoundingClientRect();
|
||||
const row = $stack[0].querySelector('.wrolpi-button-row').getBoundingClientRect();
|
||||
const button = $stack[0].querySelector('button').getBoundingClientRect();
|
||||
|
||||
expect(stack.width, 'the stack has a width to fill').to.be.greaterThan(200);
|
||||
expect(row.width, 'the row stretches, as the panel does').to.be.closeTo(stack.width, 0.5);
|
||||
expect(button.width, 'the button does not').to.be.lessThan(stack.width / 2);
|
||||
});
|
||||
});
|
||||
|
||||
it('grows its gap with the interface scale', () => {
|
||||
cy.mountUI(<div className='wrolpi-button-row'><Button>One</Button><Button>Two</Button></div>);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue