Skip to content

Commit

Permalink
Match dialog button layout to OS (#6044)
Browse files Browse the repository at this point in the history
This change updates the order of buttons in modals to match a user's
native OS layout. The layout of buttons defaults to OS X button order
when Positron is run in a web environment.

Addresses #3548 

### Release Notes

#### New Features

- Updates modal button order to match native OS layout 

#### Bug Fixes

- N/A


### QA Notes
- [ ] Verify that keyboard navigation works for modals and there are no
regressions with the behavior
- [ ] Verify that confirmation dialogs with an Ok and Cancel button is
ordered:
  - [ ]  Ok/Cancel on windows
  - [ ] Cancel/Ok on non-windows environments
- [ ] Verify that dialogs with more than two actions split actions so
that the primary action and cancel action are positioned at the right
end of the modal and all other tertiary actions are positioned at the
left end of the modal.

### Screenshots

<img width="1346" alt="Screenshot 2025-01-17 at 12 21 03 PM"
src="https://github.com/user-attachments/assets/e71a93d5-33ce-4ea2-bafa-795bca7a222b"
/>

<img width="1346" alt="Screenshot 2025-01-17 at 12 20 47 PM"
src="https://github.com/user-attachments/assets/875680fe-d187-4040-9661-4621a1f93621"
/>

<img width="1346" alt="Screenshot 2025-01-17 at 12 20 17 PM"
src="https://github.com/user-attachments/assets/1fe06f71-e1ed-4d7d-bb18-c478ee6e5896"
/>

<img width="1346" alt="Screenshot 2025-01-17 at 12 20 09 PM"
src="https://github.com/user-attachments/assets/58fea0e7-6b00-46cf-aa8f-9350ea96f7ac"
/>


https://github.com/user-attachments/assets/92b72df1-d87f-4e45-8982-52d8f01825b1

@:new-project-wizard @:win @:modal

---------

Signed-off-by: Dhruvi Sompura <[email protected]>
Co-authored-by: sharon <[email protected]>
  • Loading branch information
dhruvisompura and sharon-wang authored Jan 22, 2025
1 parent 775a468 commit b07fed2
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 176 deletions.
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 {
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}>
<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) => {
// 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 {
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'>
<PlatformNativeDialogActionBar secondaryButton={cancelButton} primaryButton={deleteButton} />
</div>
</div>
</PositronModalDialog>
);
};

This file was deleted.

Loading

0 comments on commit b07fed2

Please sign in to comment.