mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
The archive page's six actions shared edges, and Update sat lower than the rest. Both come from the same thing: Semantic's Button carried `margin: 0 .25em .25em 0`, so a page could list buttons as bare siblings and they arrived spaced -- and separated from the line below when the row wrapped. Nothing replaced that margin. Two of those six answered with `marginTop: 0.5em`, which is the wrong axis: a Button is `display: inline-flex; vertical-align: middle`, and `vertical-align` is measured on the MARGIN box, so half of any top margin shows up as the visible button dropping. It was covering the wrapped-row case in the only axis a margin can, and paying for it in the other one. `.wrolpi-button-row` is a flex row with a gap, so both axes are covered at once and no button needs a margin of its own -- the same rule as `.wrolpi-stack`, along the other axis. Applied to the five rows in the app that had no gap: the archive page, the shared collection edit form (which three pages hand action buttons to, and where nine buttons plus Cancel carried `margin-top: 1em` each while Save carried none), the doc page's two responsive branches, and the downloads table's edit/restart pair. `.action-button-spacing` goes with them; it was that same margin, on Cancel. Guarded four ways. ui-layout.cy.js measures that the row really spaces what it holds, wraps rather than overflows, centres its buttons against a taller item beside them, and counts a confirmed APIButton as one item rather than two -- that one returns a fragment holding a `<Confirm/>`, which would open a hole in the row if it did not portal. Common.test.js scans for the two ways this comes back: a button in a row stamping a vertical margin, and a row of buttons that is not a row. Neither throws, which is how the app got here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
3.9 KiB
JavaScript
94 lines
3.9 KiB
JavaScript
import React from 'react';
|
|
import {Button, Header, Message, Panel} from '../ui';
|
|
import {TagsContext} from '../../Tags';
|
|
import {WROLModeMessage} from '../Common';
|
|
|
|
/**
|
|
* Reusable form component for editing collections (Domains, Channels, etc).
|
|
*
|
|
* @param {Object} form - Form object from useForm hook
|
|
* @param {Function} onCancel - Optional callback when cancel is clicked
|
|
* @param {Function} onSubmit - Optional custom submit handler (defaults to form.onSubmit)
|
|
* @param {String} title - Page title to display in header
|
|
* @param {String} wrolModeContent - Content to show in WROL mode message (optional)
|
|
* @param {React.ReactNode} actionButtons - Optional additional action buttons to display in the button row
|
|
* @param {String} appliedTagName - Optional tag name to display (similar to ChannelEditPage pattern)
|
|
* @param {React.ReactNode} children - Form fields to render
|
|
*/
|
|
export function CollectionEditForm({
|
|
form,
|
|
onCancel,
|
|
onSubmit,
|
|
title,
|
|
wrolModeContent,
|
|
actionButtons,
|
|
appliedTagName,
|
|
children
|
|
}) {
|
|
const {SingleTag} = React.useContext(TagsContext);
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
if (onSubmit) {
|
|
onSubmit(e);
|
|
} else {
|
|
form.onSubmit();
|
|
}
|
|
};
|
|
|
|
return <Panel>
|
|
{title && <Header as="h1">{title}</Header>}
|
|
{wrolModeContent && <WROLModeMessage content={wrolModeContent}/>}
|
|
|
|
{/* Display form-level errors */}
|
|
{form.error && <Message kind='error' title='Error'>
|
|
<p>{form.error}</p>
|
|
</Message>}
|
|
|
|
<form onSubmit={handleSubmit} autoComplete="off">
|
|
{/*
|
|
Every caller now passes library components rather than Semantic Grid rows, so
|
|
this is a plain flex column instead of the `ui stackable grid` bridge it used
|
|
to be. That bridge was the last thing in the app depending on Semantic's
|
|
stylesheet.
|
|
*/}
|
|
<div
|
|
className='wrolpi-form-rows'
|
|
style={form.loading ? {opacity: 0.6, pointerEvents: 'none'} : undefined}
|
|
>
|
|
{children}
|
|
|
|
{appliedTagName && <div><SingleTag name={appliedTagName}/></div>}
|
|
|
|
{/*
|
|
* The shared row. It was already a flex row at this gap, but it did not wrap, so
|
|
* the pages that hand it four action buttons ran out of width on a phone -- which
|
|
* is why each of those buttons, and the Cancel below, carried `margin-top: 1em`.
|
|
* That margin is what made them sit lower than Save, `vertical-align: middle`
|
|
* being measured on the margin box. Wrapping plus a row gap covers the case the
|
|
* margin was for, in the axis it actually happens in.
|
|
*/}
|
|
<div className='wrolpi-button-row'>
|
|
{actionButtons}
|
|
{onCancel && <Button
|
|
type='button'
|
|
role='cancel'
|
|
onClick={onCancel}
|
|
disabled={form.disabled}
|
|
>
|
|
Cancel
|
|
</Button>}
|
|
<Button
|
|
type='submit'
|
|
role='save'
|
|
size='lg'
|
|
style={{marginLeft: 'auto'}}
|
|
disabled={form.disabled}
|
|
>
|
|
Save
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</Panel>;
|
|
}
|