Skip to content

Commit

Permalink
web: Allow opening popups with "stable" size (#1156)
Browse files Browse the repository at this point in the history
Although the (ab)use of popup/modal dialog has been avoided as much as
possible, they still being a valid resource for some use cases. However,
they can become a bit annoying when holding _dynamic_ content and users
interactions makes them change their size.

To bypass such a problem, two props has been added to the Agama
core/Popup component: `blockSize` and `inlineSize`. In a first
iteration, both accepts "auto", "small", "medium", and "large" values,
setting a _stable size_ on the corresponding axis that it's calculated
with respect to the size of viewport (except when using "auto", which
actually means _no stable size_).

As a side effect, in order to have more control on the sizing, the
PF/Modal `variant` prop is being ignored.
  • Loading branch information
dgdavid authored Apr 19, 2024
2 parents 85c0ed5 + c307876 commit 233f29c
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 38 deletions.
14 changes: 0 additions & 14 deletions web/src/assets/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,6 @@
}
}

.auto-modal-popup {
block-size: auto;
}

.medium-modal-popup {
min-block-size: 55vh;
max-block-size: 55vh;
}

.large-modal-popup {
min-block-size: 85vh;
max-block-size: 85vh;
}

// *DataLists custom styles
li.pf-v5-c-data-list__item {
border-block-end-width: thin;
Expand Down
57 changes: 57 additions & 0 deletions web/src/assets/styles/utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,63 @@
--stack-gutter: 0;
}

.block-size-auto {
block-size: auto;
}


.inline-size-auto {
inline-size: auto;
}

.block-size-small,
.block-size-medium,
.block-size-large {
block-size: calc(100dvb - var(--space-large));
}

.inline-size-small,
.inline-size-medium,
.inline-size-large {
inline-size: calc(100dvi - var(--spacer-large));
}

@media (height > 500px) {
.block-size-small {
block-size: 30dvb;
}

.block-size-medium {
block-size: 60dvb;
}

.block-size-large {
block-size: 90dvb;
}
}

@media (width > 500px) {
.inline-size-small,
.inline-size-medium,
.inline-size-large {
min-inline-size: calc(500px - var(--spacer-large));
}

.inline-size-small {
inline-size: 30dvi;
}

.inline-size-medium {
inline-size: 60dvi;
max-inline-size: var(--ui-max-inline-size);
}

.inline-size-large {
inline-size: 90dvi;
max-inline-size: calc(var(--ui-max-inline-size) * 2);
}
}

.large {
/** block-size fallbacks **/
height: 95dvh;
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/core/FileViewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export default function FileViewer({ file, title, onCloseCallback }) {
<Popup
isOpen={isOpen}
title={title || file}
className="large"
blockSize="large"
inlineSize="large"
>
{state === "loading" && <Loading text={_("Reading file...")} />}
{(content === null || error) &&
Expand Down
19 changes: 13 additions & 6 deletions web/src/components/core/Popup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,27 +187,34 @@ const AncillaryAction = ({ children, ...actionsProps }) => (
* </Popup.Actions>
* </Popup>
*
* @param {ModalProps} props
* @typedef {object} PopupBaseProps
* @property {"auto" | "small" | "medium" | "large"} [blockSize="auto"] - The block/height size for the dialog. Default is "auto".
* @property {"auto" | "small" | "medium" | "large"} [inlineSize="medium"] - The inline/width size for the dialog. Default is "medium".
* @typedef {Omit<ModalProps, "variant" | "size"> & PopupBaseProps} PopupProps
*
* @param {PopupProps} props
*/
const Popup = ({
isOpen = false,
showClose = false,
variant = "small",
inlineSize = "medium",
blockSize = "auto",
className = "",
children,
...pfModalProps
...props
}) => {
const [actions, content] = partition(React.Children.toArray(children), child => child.type === Actions);

return (
/** @ts-ignore */
<Modal
{ ...pfModalProps }
{...props}
isOpen={isOpen}
showClose={showClose}
variant={variant}
actions={actions}
className={`${className} block-size-${blockSize} inline-size-${inlineSize}`.trim()}
>
{ content }
{content}
</Modal>
);
};
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/core/Terminal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ export default function Terminal({ onCloseCallback }) {
return (
<Popup
isOpen={isOpen}
className="large"
aria-label="terminal popup"
blockSize="large"
inlineSize="large"
>

<iframe className="vertically-centered" src="/cockpit/@localhost/system/terminal.html" />
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/l10n/L10nPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const TimezonePopup = ({ onFinish = noop, onCancel = noop }) => {
isOpen
title={_("Select time zone")}
description={sprintf(_("%s will use the selected time zone."), selectedProduct.name)}
className="height-75"
blockSize="large"
>
<Form id="timezoneForm" onSubmit={onSubmit}>
<TimezoneSelector value={timezoneId} timezones={sortedTimezones} onChange={setTimezoneId} />
Expand Down Expand Up @@ -181,7 +181,7 @@ const LocalePopup = ({ onFinish = noop, onCancel = noop }) => {
isOpen
title={_("Select language")}
description={sprintf(_("%s will use the selected language."), selectedProduct.name)}
className="height-75"
blockSize="large"
>
<Form id="localeForm" onSubmit={onSubmit}>
<LocaleSelector value={localeId} locales={sortedLocales} onChange={setLocaleId} />
Expand Down Expand Up @@ -294,7 +294,7 @@ const KeymapPopup = ({ onFinish = noop, onCancel = noop }) => {
isOpen
title={_("Select keyboard")}
description={sprintf(_("%s will use the selected keyboard."), selectedProduct.name)}
className="height-75"
blockSize="large"
>
<Form id="keymapForm" onSubmit={onSubmit}>
<KeymapSelector value={keymapId} keymaps={sortedKeymaps} onChange={setKeymapId} />
Expand Down
6 changes: 5 additions & 1 deletion web/src/components/network/IpSettingsForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ export default function IpSettingsForm({ connection, onClose }) {
// TRANSLATORS: manual network configuration mode with a static IP address
// %s is replaced by the connection name
return (
<Popup isOpen title={sprintf(_("Edit %s"), connection.id)}>
<Popup
isOpen
title={sprintf(_("Edit %s"), connection.id)}
blockSize="medium"
>
{renderError("object")}
<Form id="edit-connection" onSubmit={onSubmit}>
<FormGroup fieldId="method" label={_("Mode")} isRequired>
Expand Down
6 changes: 5 additions & 1 deletion web/src/components/network/WifiSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@ function WifiSelector({ isOpen = false, onClose }) {
});

return (
<Popup isOpen={isOpen} title={_("Connect to a Wi-Fi network")}>
<Popup
isOpen={isOpen}
title={_("Connect to a Wi-Fi network")}
blockSize="medium"
>
<WifiNetworksList
networks={networksFromValues(networks)}
hiddenNetwork={baseHiddenNetwork}
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/storage/DeviceSelectionDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ devices.").split(/[[\]]/);
<Popup
title={_("Device for installing the system")}
isOpen={isOpen}
variant="medium"
blockSize="large"
inlineSize="large"
{...props}
>
<Form id="target-form" onSubmit={onSubmit}>
Expand Down
10 changes: 6 additions & 4 deletions web/src/components/storage/ProposalActionsDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const ActionsList = ({ actions }) => {
* @param {object} props
* @param {object[]} [props.actions=[]] - The actions to perform in the system.
* @param {boolean} [props.isOpen=false] - Whether the dialog is visible or not.
* @param {function} props.onClose - Whether the dialog is visible or not.
* @param {() => void} props.onClose - Whether the dialog is visible or not.
*/
export default function ProposalActionsDialog({ actions = [], isOpen = false, onClose }) {
const [isExpanded, setIsExpanded] = useState(false);
Expand All @@ -68,14 +68,16 @@ export default function ProposalActionsDialog({ actions = [], isOpen = false, on
// TRANSLATORS: show/hide toggle action, this is a clickable link
: sprintf(n_("Show %d subvolume action", "Show %d subvolume actions", subvolActions.length), subvolActions.length);

const blockSize = actions.length < 15 ? "medium" : "large";

return (
<Popup
// TRANSLATORS: The storage "Planned Actions" section's title. The
// section shows a list of planned actions for the selected device, e.g.
// TRANSLATORS: The storage "Planned Actions" dialog's title. The
// dialog shows a list of planned actions for the selected device, e.g.
// "delete partition A", "create partition B with filesystem C", ...
title={_("Planned Actions")}
isOpen={isOpen}
variant="large"
blockSize={subvolActions.length === 0 ? "auto" : blockSize}
>
<ActionsList actions={generalActions} />
<If
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/storage/SpacePolicyDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ in the devices listed below. Choose how to do it.");
title={_("Find space")}
description={description}
isOpen={isOpen}
variant="medium"
blockSize="large"
inlineSize="large"
{...props}
>
<Form id="space-policy-form" onSubmit={onSubmit}>
Expand Down
12 changes: 8 additions & 4 deletions web/src/components/users/FirstUser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,14 @@ export default function FirstUser() {

return (
<>
{ isUserDefined ? <UserData user={user} actions={actions} /> : <UserNotDefined actionCb={openForm} /> }
{ /* TODO: Extract this form to a component, if possible */ }
{ isFormOpen &&
<Popup isOpen title={isEditing ? _("Edit user account") : _("Create user account")}>
{isUserDefined ? <UserData user={user} actions={actions} /> : <UserNotDefined actionCb={openForm} />}
{ /* TODO: Extract this form to a component, if possible */}
{isFormOpen &&
<Popup
isOpen
title={isEditing ? _("Edit user account") : _("Create user account")}
inlineSize="small"
>
<Form id="createUser" onSubmit={(e) => accept("createUser", e)}>
{ showErrors() &&
<Alert variant="warning" isInline title={_("Something went wrong")}>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/users/RootPasswordPopup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function RootPasswordPopup({
const onPasswordValidation = (isValid) => setIsValidPassword(isValid);

return (
<Popup title={title} isOpen={isOpen}>
<Popup title={title} isOpen={isOpen} inlineSize="small">
<Form id="root-password" onSubmit={accept}>
<PasswordAndConfirmationInput
value={password}
Expand Down

0 comments on commit 233f29c

Please sign in to comment.