From b67a68d568aa4d53c2dc518d46a0794fcdb04a67 Mon Sep 17 00:00:00 2001 From: Blesilda Ramirez Date: Sun, 17 Nov 2024 23:59:55 +0800 Subject: [PATCH] pkp/pkp-lib#10444 Improve dialog component design (#415) * pkp/pkp-lib#10444 Change Dialog component styling * pkp/pkp-lib#10444 Rename prop type with modalStyle * pkp/pkp-lib#10444 Update default Dialog modal styling * pkp/pkp-lib#10444 Add modalStyle param to existing openDialog calls * pkp/pkp-lib#10444 Add isDismissible prop to the Dialog component, and add the X button when there are no actions in the props * pkp/pkp-lib#10444 Add modalStyle to components that use the openDialog api * pkp/pkp-lib#10444 Update Dialog component to add basic modalStyle and add documentation how the styles are used * pkp/pkp-lib#10444 Remove computed defination of default modalStyle in Dialog component * pkp/pkp-lib#10444 Add modalStyle to new dialogs related to user invitations * pkp/pkp-lib#10444 Remove unused classes for dialog component * pkp/pkp-lib#10444 Remove css style for modal--popup class * pkp/pkp-lib#10444 Update param name for onClose function in Dialog component --- .../ActionPanel/ActionPanel.stories.js | 1 + src/components/Composer/Composer.vue | 1 + src/components/Container/DecisionPage.vue | 2 + src/components/Container/ManageEmailsPage.vue | 5 +- .../Container/SubmissionWizardPage.vue | 2 + src/components/Container/WorkflowPage.vue | 6 +- .../Form/context/NotifyUsersForm.vue | 1 + src/components/Form/fields/FieldOrcid.vue | 5 +- .../Form/fields/FieldShowEnsuringLink.vue | 3 +- .../announcements/AnnouncementsListPanel.vue | 4 +- .../contributors/ContributorsListPanel.vue | 1 + src/components/ListPanel/doi/DoiListItem.vue | 3 +- src/components/ListPanel/doi/DoiListPanel.vue | 3 +- .../highlights/HighlightsListPanel.vue | 4 +- .../institutions/InstitutionsListPanel.vue | 4 +- .../SubmissionFilesListPanel.vue | 4 +- .../submissions/SubmissionsListItem.vue | 4 +- src/components/Modal/Dialog.mdx | 9 ++ src/components/Modal/Dialog.stories.js | 150 ++++++++++++++++++ src/components/Modal/Dialog.vue | 103 +++++++++--- src/components/Modal/Modal.vue | 118 +------------- src/composables/useModal.mdx | 1 + .../FileManager/useFileManagerActions.js | 15 +- .../GalleyManager/useGalleyManagerActions.js | 17 +- .../useParticipantManagerActions.js | 30 ++-- .../useReviewerManagerActions.js | 30 ++-- .../UserInvitationManagerStore.js | 5 +- src/mixins/ajaxError.js | 1 + src/mixins/autosave.js | 1 + src/mixins/dialog.mdx | 4 +- .../AcceptInvitationPageStore.js | 3 + .../userInvitation/UserInvitationPageStore.js | 2 + .../UserInvitationUserGroupsTable.vue | 1 + .../publication/WorkflowPublicationJats.vue | 1 + .../WorkflowPublicationSectionJats.vue | 1 + .../composables/useWorkflowActions.js | 9 +- src/stores/modalStore.js | 1 + 37 files changed, 346 insertions(+), 209 deletions(-) diff --git a/src/components/ActionPanel/ActionPanel.stories.js b/src/components/ActionPanel/ActionPanel.stories.js index 887cc67ca..ec02c6f6b 100644 --- a/src/components/ActionPanel/ActionPanel.stories.js +++ b/src/components/ActionPanel/ActionPanel.stories.js @@ -35,6 +35,7 @@ export const Default = { callback: (close) => close(), }, ], + modalStyle: 'negative', }); } return { diff --git a/src/components/Composer/Composer.vue b/src/components/Composer/Composer.vue index 0c740dbae..87abe7085 100644 --- a/src/components/Composer/Composer.vue +++ b/src/components/Composer/Composer.vue @@ -906,6 +906,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'primary', }); }, diff --git a/src/components/Container/DecisionPage.vue b/src/components/Container/DecisionPage.vue index be9b2c579..2a3f9ee7e 100644 --- a/src/components/Container/DecisionPage.vue +++ b/src/components/Container/DecisionPage.vue @@ -182,6 +182,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, @@ -266,6 +267,7 @@ export default { message: this.decisionCompleteDescription, actions, close, + modalStyle: 'success', }); }, diff --git a/src/components/Container/ManageEmailsPage.vue b/src/components/Container/ManageEmailsPage.vue index d685a9b1b..86cdfb11e 100644 --- a/src/components/Container/ManageEmailsPage.vue +++ b/src/components/Container/ManageEmailsPage.vue @@ -151,6 +151,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, @@ -187,6 +188,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, @@ -224,6 +226,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, @@ -325,7 +328,7 @@ export default { title: mailable ? mailable.name : '', mailable: this.currentMailable, onOpenTemplate: this.openTemplate, - onConfirmResetTemplate: this.confirmRemoveTemplate, + onConfirmResetTemplate: this.confirmResetTemplate, onConfirmRemoveTemplate: this.confirmRemoveTemplate, }); }); diff --git a/src/components/Container/SubmissionWizardPage.vue b/src/components/Container/SubmissionWizardPage.vue index cc49e8a61..d9fbc8bc3 100644 --- a/src/components/Container/SubmissionWizardPage.vue +++ b/src/components/Container/SubmissionWizardPage.vue @@ -432,6 +432,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, @@ -661,6 +662,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'primary', }); }, diff --git a/src/components/Container/WorkflowPage.vue b/src/components/Container/WorkflowPage.vue index abca15ab5..967fd4745 100644 --- a/src/components/Container/WorkflowPage.vue +++ b/src/components/Container/WorkflowPage.vue @@ -341,7 +341,7 @@ export default { actions: [ { label: this.t('common.yes'), - isWarnable: true, + isPrimary: true, callback: (close) => { close(); this.createVersion(); @@ -352,6 +352,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'primary', }); }, @@ -447,7 +448,7 @@ export default { this.workingPublication.status === pkp.const.STATUS_SCHEDULED ? this.unscheduleLabel : this.unpublishLabel, - isPrimary: true, + isWarnable: true, callback: (close) => { this.unpublish(this.workingPublication); close(); @@ -458,6 +459,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, diff --git a/src/components/Form/context/NotifyUsersForm.vue b/src/components/Form/context/NotifyUsersForm.vue index c7b6d7adb..5cf6a10a8 100644 --- a/src/components/Form/context/NotifyUsersForm.vue +++ b/src/components/Form/context/NotifyUsersForm.vue @@ -53,6 +53,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'primary', }); }, }, diff --git a/src/components/Form/fields/FieldOrcid.vue b/src/components/Form/fields/FieldOrcid.vue index 0b492abdf..ebb43edf4 100644 --- a/src/components/Form/fields/FieldOrcid.vue +++ b/src/components/Form/fields/FieldOrcid.vue @@ -190,6 +190,7 @@ export default { }, }, ], + modalStyle: 'primary', close: () => {}, }); }, @@ -227,7 +228,7 @@ export default { actions: [ { label: this.t('common.yes'), - isPrimary: true, + isWarnable: true, callback: async (close) => { await this.deleteOrcid(); close(); @@ -235,12 +236,12 @@ export default { }, { label: this.t('common.no'), - isWarnable: true, callback: (close) => { close(); }, }, ], + modalStyle: 'negative', close: () => {}, }); }, diff --git a/src/components/Form/fields/FieldShowEnsuringLink.vue b/src/components/Form/fields/FieldShowEnsuringLink.vue index 078da1856..92f7acd4e 100644 --- a/src/components/Form/fields/FieldShowEnsuringLink.vue +++ b/src/components/Form/fields/FieldShowEnsuringLink.vue @@ -23,7 +23,7 @@ export default { }, mounted() { /** - * Show the requested message in a modal when the link in the messgae is + * Show the requested message in a modal when the link in the message is * clicked. */ $('.pkpFormField--options__option button', this.$el).click(() => { @@ -36,6 +36,7 @@ export default { callback: (close) => { close(); }, + modalStyle: 'primary', }, { height: 'auto', diff --git a/src/components/ListPanel/announcements/AnnouncementsListPanel.vue b/src/components/ListPanel/announcements/AnnouncementsListPanel.vue index 916245723..e4f9ef03c 100644 --- a/src/components/ListPanel/announcements/AnnouncementsListPanel.vue +++ b/src/components/ListPanel/announcements/AnnouncementsListPanel.vue @@ -209,7 +209,7 @@ export default { actions: [ { label: this.t('common.yes'), - isPrimary: true, + isWarnable: true, callback: (close) => { var self = this; $.ajax({ @@ -233,10 +233,10 @@ export default { }, { label: this.t('common.no'), - isWarnable: true, callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, diff --git a/src/components/ListPanel/contributors/ContributorsListPanel.vue b/src/components/ListPanel/contributors/ContributorsListPanel.vue index e6789a2fb..0ab52503b 100644 --- a/src/components/ListPanel/contributors/ContributorsListPanel.vue +++ b/src/components/ListPanel/contributors/ContributorsListPanel.vue @@ -363,6 +363,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, diff --git a/src/components/ListPanel/doi/DoiListItem.vue b/src/components/ListPanel/doi/DoiListItem.vue index af8e24b30..57e871926 100644 --- a/src/components/ListPanel/doi/DoiListItem.vue +++ b/src/components/ListPanel/doi/DoiListItem.vue @@ -513,12 +513,12 @@ export default { actions: [ { label: this.t('common.ok'), - isPrimary: true, callback: (close) => { close(); }, }, ], + modalStyle: 'negative', }); }, openVersionModal() { @@ -814,6 +814,7 @@ export default { }, }, ], + modalStyle: 'primary', }); }, triggerDeposit() { diff --git a/src/components/ListPanel/doi/DoiListPanel.vue b/src/components/ListPanel/doi/DoiListPanel.vue index cdc06db7a..a1d38e5f1 100644 --- a/src/components/ListPanel/doi/DoiListPanel.vue +++ b/src/components/ListPanel/doi/DoiListPanel.vue @@ -375,13 +375,13 @@ export default { actions: [ { label: this.t('common.ok'), - isPrimary: true, callback: (close) => { this.failedDoiActions = []; close(); }, }, ], + modalStyle: 'negative', bodyComponent: DoiFailedActionDialogBody, bodyProps: { failedDoiActions: this.failedDoiActions, @@ -789,6 +789,7 @@ export default { callback: (close) => close(), }, ], + modalStyle: 'primary', }); }, /** diff --git a/src/components/ListPanel/highlights/HighlightsListPanel.vue b/src/components/ListPanel/highlights/HighlightsListPanel.vue index f245415c1..a8257c884 100644 --- a/src/components/ListPanel/highlights/HighlightsListPanel.vue +++ b/src/components/ListPanel/highlights/HighlightsListPanel.vue @@ -228,7 +228,7 @@ export default { actions: [ { label: this.t('common.yes'), - isPrimary: true, + isWarnable: true, callback: (close) => { $.ajax({ url: this.apiUrl + '/' + id, @@ -251,10 +251,10 @@ export default { }, { label: this.t('common.no'), - isWarnable: true, callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, diff --git a/src/components/ListPanel/institutions/InstitutionsListPanel.vue b/src/components/ListPanel/institutions/InstitutionsListPanel.vue index 59240f1ca..33e19e4fc 100644 --- a/src/components/ListPanel/institutions/InstitutionsListPanel.vue +++ b/src/components/ListPanel/institutions/InstitutionsListPanel.vue @@ -211,7 +211,7 @@ export default { actions: [ { label: this.t('common.yes'), - isPrimary: true, + isWarnable: true, callback: (close) => { var self = this; $.ajax({ @@ -235,10 +235,10 @@ export default { }, { label: this.t('common.no'), - isWarnable: true, callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, diff --git a/src/components/ListPanel/submissionFiles/SubmissionFilesListPanel.vue b/src/components/ListPanel/submissionFiles/SubmissionFilesListPanel.vue index 229f16639..897c46d49 100644 --- a/src/components/ListPanel/submissionFiles/SubmissionFilesListPanel.vue +++ b/src/components/ListPanel/submissionFiles/SubmissionFilesListPanel.vue @@ -259,7 +259,7 @@ export default { actions: [ { label: this.t('common.yes'), - isPrimary: true, + isWarnable: true, callback: (close) => { $.ajax({ url: this.apiUrl + '/' + item.id + '?stageId=' + this.stageId, @@ -281,10 +281,10 @@ export default { }, { label: this.t('common.no'), - isWarnable: true, callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, diff --git a/src/components/ListPanel/submissions/SubmissionsListItem.vue b/src/components/ListPanel/submissions/SubmissionsListItem.vue index 84fb6e968..e66332bf6 100644 --- a/src/components/ListPanel/submissions/SubmissionsListItem.vue +++ b/src/components/ListPanel/submissions/SubmissionsListItem.vue @@ -813,15 +813,15 @@ export default { actions: [ { label: this.t('common.yes'), - isPrimary: true, + isWarnable: true, callback: this.deleteSubmission, }, { label: this.t('common.no'), - isWarnable: true, callback: (close) => close(), }, ], + modalStyle: 'negative', }); }, diff --git a/src/components/Modal/Dialog.mdx b/src/components/Modal/Dialog.mdx index 3e298eae3..ed19df92b 100644 --- a/src/components/Modal/Dialog.mdx +++ b/src/components/Modal/Dialog.mdx @@ -19,6 +19,15 @@ Dialog purpose is to display simple feedback like success and error messages. Or Dialog are opened via method `openDialog` from [useModal](../?path=/docs/composables-usemodal--docs#opensidemodal) composable. Check out the props description for details. +## Styling + +The style of the modal can be changed by passing the prop `modalStyle`. It accepts the following values: + +- **`primary`**: A modal style that uses the primary color scheme. Use this for informational dialogs or confirmation modals that don't involve negative actions. This is the default style used when the modal is invoked via the composable API for the Dialog component. +- **`negative`**: Indicates a negative state, typically for error messages or alerts. It should also be used for confirming negative actions like deleting or removing items that may be irreversible. +- **`success`**: Represents a positive state, often used for success notifications. +- **` basic`**: The standard modal style, which has no special border styling. This style is applied by default in the legacy PHP implementation of the Dialog component, if no value is specified. + ## Accessibility To correctly handle accessibility (title, description) and focus management - [headless-ui](https://headlessui.com) library is used. diff --git a/src/components/Modal/Dialog.stories.js b/src/components/Modal/Dialog.stories.js index 25b577c15..74cfadf1c 100644 --- a/src/components/Modal/Dialog.stories.js +++ b/src/components/Modal/Dialog.stories.js @@ -45,6 +45,7 @@ export const DialogBasic = { callback: (close) => close(), }, ], + modalStyle: 'basic', }, play: async ({canvasElement}) => { // Assigns canvas to the component root element @@ -100,6 +101,51 @@ export const WithBodyComponent = { }, }; +export const NonDismissible = { + args: { + buttonName: 'Show non-dismissible modal', + name: 'non-dismissible-modal', + title: 'Non-Dismissible Modal', + message: + 'Clicking outside will not close this modal. Use the "Cancel" button to close it.', + actions: [ + { + label: 'Cancel', + isWarnable: true, + callback: (close) => close(), + }, + ], + isDismissible: false, + close: () => console.log('closed example dialog'), // eslint-disable-line, + }, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Show non-dismissible modal')); + }, +}; + +export const NoActionButtons = { + args: { + buttonName: 'Show modal', + name: 'no-actions-modal', + title: 'Non Action Buttons Modal', + message: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + actions: [], + close: () => console.log('closed example dialog'), // eslint-disable-line, + }, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Show modal')); + }, +}; + export const DialogComplex = { args: { buttonName: 'Full Example', @@ -138,3 +184,107 @@ export const DialogComplex = { await user.click(canvas.getByText('Full Example')); }, }; + +const ErrorBodyComponent = { + template: ` + Cancelling the invitation sent to Emma Stone will deactivate the acceptance link sent to her via email. Here are the invitation details: + + `, + props: { + failedDoiActions: {type: Array, required: true}, + }, +}; + +export const NegativeState = { + args: { + buttonName: 'Show modal', + name: 'error', + title: 'Cancel Invitation', + bodyComponent: ErrorBodyComponent, + actions: [ + { + label: 'Cancel Invitation', + isWarnable: true, + callback: (close) => { + // Simulate a server request + setTimeout(() => close(), 2000); + }, + }, + { + label: 'Cancel', + callback: (close) => close(), + }, + ], + close: () => console.log('closed example dialog'), // eslint-disable-line, + modalStyle: 'negative', + }, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Show modal')); + }, +}; + +export const SuccessState = { + args: { + buttonName: 'Show modal', + name: 'error', + title: "You've been added as a section editor in OJS", + message: + 'Congratulations! As a section editor you might get access to more options in OJS. If you need any assistance in understanding the system, please click on "Help" buttons throughout the system to guide you through the interface.', + actions: [ + { + label: 'View All Submissions', + callback: (close) => close(), + }, + ], + close: () => console.log('closed example dialog'), // eslint-disable-line, + modalStyle: 'success', + }, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Show modal')); + }, +}; + +export const PrimaryStyle = { + args: { + buttonName: 'Show modal', + name: 'primary', + title: 'Primary Color', + message: "This dialog uses the application's primary color.", + actions: [ + { + label: 'Ok', + callback: (close) => close(), + }, + ], + close: () => console.log('closed example dialog'), // eslint-disable-line, + modalStyle: 'primary', + }, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Show modal')); + }, +}; diff --git a/src/components/Modal/Dialog.vue b/src/components/Modal/Dialog.vue index face7c085..3877f16c6 100644 --- a/src/components/Modal/Dialog.vue +++ b/src/components/Modal/Dialog.vue @@ -1,6 +1,6 @@