Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match dialog button layout to OS #6044

Merged
merged 13 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import React, { ReactElement } from 'react';
// Other dependencies.
import { localize } from '../../../../../nls.js';
import { Button } from '../../../../../base/browser/ui/positronComponents/button/button.js';
import { PlatformNativeDialogActionBar } from './platformNativeDialogActionBar.js';

/**
* OKCancelActionBarProps interface.
Expand All @@ -36,16 +37,22 @@ interface OKCancelActionBarProps {
*/
export const OKCancelActionBar = (props: OKCancelActionBarProps) => {
const preActions = props.preActions ? props.preActions() : null;
const okButton = (
<Button className='action-bar-button default' onPressed={props.onAccept}>
{props.okButtonTitle ?? localize('positronOK', "OK")}
</Button>
);
const cancelButton = (
<Button className='action-bar-button' onPressed={props.onCancel}>
{props.cancelButtonTitle ?? localize('positronCancel', "Cancel")}
</Button>
);

// Render.
return (
<div className='ok-cancel-action-bar top-separator'>
{preActions}
<Button className='action-bar-button default' onPressed={props.onAccept}>
{props.okButtonTitle ?? localize('positronOK', "OK")}
</Button>
<Button className='action-bar-button' onPressed={props.onCancel}>
{props.cancelButtonTitle ?? localize('positronCancel', "Cancel")}
</Button>
<PlatformNativeDialogActionBar secondaryButton={cancelButton} primaryButton={okButton} />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

.positron-modal-dialog-box
.ok-cancel-action-bar {
.positron-modal-dialog-box .ok-cancel-back-action-bar {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed the ok-cancel-action-bar class to ok-cancel-back-action-bar to avoid name collisions. The layout is different for this action bar and the OkCancelActionBar: justify-content: space-between vs justify-content: end.

This change prevents the OKCancelModalDialog using the OkCancelActionBar from having the buttons positioned incorrectly. I noticed during my testing that the styles from this sheet were overriding the styles in OkCancelActionBar which we don't want anymore.

Here's a screenshot of what was happening:
Screenshot 2025-01-17 at 3 14 12 PM

left: 0;
right: 0;
bottom: 0;
Expand All @@ -18,40 +17,30 @@
justify-content: space-between;
}

.positron-modal-dialog-box
.ok-cancel-action-bar
.top-separator {
.positron-modal-dialog-box .ok-cancel-back-action-bar .top-separator {
border-top: 1px solid var(--vscode-positronModalDialog-separator);
}

.positron-modal-dialog-box
.ok-cancel-action-bar
.action-bar-button {
.positron-modal-dialog-box .ok-cancel-back-action-bar .action-bar-button {
width: 80px;
height: 32px;
}

.positron-modal-dialog-box
.ok-cancel-action-bar
.left-actions {
.positron-modal-dialog-box .ok-cancel-back-action-bar .left-actions {
display: flex;
gap: 10px;
align-items: start;
justify-content: space-between;
}

.positron-modal-dialog-box
.ok-cancel-action-bar
.right-actions {
.positron-modal-dialog-box .ok-cancel-back-action-bar .right-actions {
display: flex;
gap: 10px;
align-items: end;
justify-content: space-between;
}

.positron-modal-dialog-box
.ok-cancel-action-bar
.action-bar-button:disabled {
.positron-modal-dialog-box .ok-cancel-back-action-bar .action-bar-button:disabled {
color: var(--vscode-positronModalDialog-buttonDisabledForeground);
background-color: var(--vscode-positronModalDialog-buttonDisabledBackground) !important;
border: 1px solid var(--vscode-positronModalDialog-buttonDisabledBorder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import React from 'react';
// Other dependencies.
import { localize } from '../../../../../nls.js';
import { Button } from '../../../../../base/browser/ui/positronComponents/button/button.js';
import * as platform from '../../../../../base/common/platform.js';

/**
* OKCancelBackNextActionBarProps interface.
Expand All @@ -38,31 +39,33 @@ interface ActionBarButtonConfig {
* @returns The rendered component.
*/
export const OKCancelBackNextActionBar = ({ okButtonConfig, cancelButtonConfig, backButtonConfig, nextButtonConfig }: OKCancelBackNextActionBarProps) => {
const cancelButton = (cancelButtonConfig ?
<Button className='action-bar-button' onPressed={cancelButtonConfig.onClick} disabled={cancelButtonConfig.disable ?? false}>
{cancelButtonConfig.title ?? localize('positronCancel', "Cancel")}
</Button> : null);
const okButton = (okButtonConfig ?
<Button className='action-bar-button default' onPressed={okButtonConfig.onClick} disabled={okButtonConfig.disable ?? false}>
{okButtonConfig.title ?? localize('positronOK', "OK")}
</Button> : null);
const nextButton = (nextButtonConfig ?
<Button className='action-bar-button default' onPressed={nextButtonConfig.onClick} disabled={nextButtonConfig.disable ?? false}>
{nextButtonConfig.title ?? localize('positronNext', "Next")}
</Button> : null);

// Render.
return (
<div className='ok-cancel-action-bar top-separator'>
<div className='ok-cancel-back-action-bar top-separator'>
<div className='left-actions'>
{backButtonConfig ?
<Button className='button action-bar-button' onPressed={backButtonConfig.onClick} disabled={backButtonConfig.disable ?? false}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that we had this button class added to a bunch of buttons in the modals but I couldn't find the class in any of the relevant stylesheets. I think this may be an orphaned class that was left behind by accident? I didn't see any breakage after I removed it.

Let me know if this is wrong and we do need it!

<Button className='action-bar-button' onPressed={backButtonConfig.onClick} disabled={backButtonConfig.disable ?? false}>
{backButtonConfig.title ?? localize('positronBack', "Back")}
</Button> : null
}
</div>
<div className='right-actions'>
{cancelButtonConfig ?
<Button className='button action-bar-button' onPressed={cancelButtonConfig.onClick} disabled={cancelButtonConfig.disable ?? false}>
{cancelButtonConfig.title ?? localize('positronCancel', "Cancel")}
</Button> : null
}
{okButtonConfig ?
<Button className='button action-bar-button default' onPressed={okButtonConfig.onClick} disabled={okButtonConfig.disable ?? false}>
{okButtonConfig.title ?? localize('positronOK', "OK")}
</Button> : null
}
{nextButtonConfig ?
<Button className='button action-bar-button default' onPressed={nextButtonConfig.onClick} disabled={nextButtonConfig.disable ?? false}>
{nextButtonConfig.title ?? localize('positronNext', "Next")}
</Button> : null
{platform.isWindows
? <>{nextButton}{okButton}{cancelButton}</>
: <>{cancelButton}{nextButton}{okButton}</>
}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/


// React.
import React from 'react';

// Other dependencies.
import * as platform from '../../../../../base/common/platform.js';

/**
* PlatformNativeDialogActionBarProps interface.
*/
interface PlatformNativeDialogActionBarProps {
secondaryButton?: React.ReactNode,
primaryButton?: React.ReactNode
}

/**
* PlatformNativeDialogActionBar component.
* @param props A PlatformNativeDialogActionBarProps that contains the component properties.
* @returns The rendered component.
*/
export const PlatformNativeDialogActionBar = ({ secondaryButton, primaryButton }: PlatformNativeDialogActionBarProps) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a super basic component that renders the primary and secondary button in the order most common for a given OS. Windows always positions the cancel/secondary button after the primary button and the inverse is true for Mac.

// Render.
return (
<>
{
platform.isWindows
? <>{primaryButton}{secondaryButton}</>
: <>{secondaryButton}{primaryButton}</>
}
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

.positron-modal-dialog-box
.action-bar {
.positron-modal-dialog-box .action-bar {
left: 0;
right: 0;
bottom: 0;
Expand All @@ -18,34 +17,27 @@
justify-content: space-between;
}

.positron-modal-dialog-box
.action-bar.top-separator {
.positron-modal-dialog-box .action-bar.top-separator {
border-top: 1px solid var(--vscode-positronModalDialog-separator);
}

.positron-modal-dialog-box
.action-bar
.left-actions {
.positron-modal-dialog-box .action-bar .left-actions {
gap: 10px;
align-items: start;
flex-direction: row;
display: inline-flex;
justify-content: space-between;
}

.positron-modal-dialog-box
.action-bar
.right-actions {
.positron-modal-dialog-box .action-bar .right-actions {
gap: 10px;
align-items: end;
flex-direction: row;
display: inline-flex;
justify-content: space-between;
}

.positron-modal-dialog-box
.action-bar
.action-bar-button {
.positron-modal-dialog-box .action-bar .action-bar-button {
height: 32px;
width: 80px;
padding: 0 10px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

// CSS.
import './positronModalDialog.css';
import './confirmDeleteModalDialog.css';

// React.
import React, { PropsWithChildren } from 'react';

// Other dependencies.
import { localize } from '../../../../nls.js';
import { Button } from '../../../../base/browser/ui/positronComponents/button/button.js';
import { ContentArea } from './components/contentArea.js';
import { PositronModalDialog, PositronModalDialogProps } from './positronModalDialog.js';
import { PlatformNativeDialogActionBar } from './components/platformNativeDialogActionBar.js';

/**
* ConfirmDeleteModalDialogProps interface.
*/
export interface ConfirmDeleteModalDialogProps extends PositronModalDialogProps {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This modal has been simplified in its usage so its shown when we want a user to see a confirmation modal for a delete action. We always provide the delete action now and optionally can provide a different label for the delete action.

The secondaryActionDestructive boolean felt unnecessary to me because the OKCancelModalDialog is used when we need a basic confirmation dialog for non-destructive actions. This dialog felt like it was trying to do both even though we have a separate dialog we are using for confirming non destructive actions.

title: string;
cancelButtonTitle?: string;
deleteActionTitle?: string
onCancel: () => (void | Promise<void>);
onDeleteAction: () => (void | Promise<void>);
}

/**
* ConfirmDeleteModalDialog component.
* @param props A PropsWithChildren<ConfirmDeleteModalDialogProps> that contains the component
* properties.
* @returns The rendered component.
*/
export const ConfirmDeleteModalDialog = (props: PropsWithChildren<ConfirmDeleteModalDialogProps>) => {
const cancelButton = (
<Button
className='action-bar-button'
onPressed={async () => await props.onCancel()}
>
{props.cancelButtonTitle ?? localize('positron.cancel', "Cancel")}
</Button>
);
const deleteButton = (
<Button
className='action-bar-button default'
onPressed={async () => await props.onDeleteAction()}
>
{props.deleteActionTitle ?? localize('positron.delete', "Delete")}
</Button>
);

// Render.
return (
<PositronModalDialog {...props}>
<ContentArea>
{props.children}
</ContentArea>
<div className='action-bar top-separator'>
<div className='left-actions'>
</div>
<div className='right-actions'>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we explicitly want the button order here to be delete/cancel.

This button order may seem odd to non-windows users now that we are trying to match native OS button ordering but I have left it as is for now because it looked like the button order was intentionally made so delete was the secondary action. I think we should change it to be native to the OS but would love to get thoughts on this from others.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to OS-applicable button ordering!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With all this work to make our modals consistent with OS norms, it seems a shame to not really be thorough and go the whole way. I know there was some concern about it being too easy to take a destructive action, but it's still multiple user actions to actually delete, for example, the variables content. I would advocate for OS-consistent button ordering.

<PlatformNativeDialogActionBar secondaryButton={cancelButton} primaryButton={deleteButton} />
</div>
</div>
</PositronModalDialog>
);
};

This file was deleted.

Loading
Loading