From bf22eb6123cb85e70a1b4aaf86cb2d325dc74f2f Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 11 Dec 2017 19:43:28 +0100 Subject: Rename popup-dialog to modal --- .../javascripts/groups/components/item_actions.vue | 14 +-- .../account/components/delete_account_modal.vue | 8 +- .../repo/components/new_dropdown/modal.vue | 8 +- .../repo/components/repo_commit_section.vue | 16 +-- .../repo/components/repo_edit_button.vue | 6 +- .../javascripts/vue_shared/components/modal.vue | 131 +++++++++++++++++++++ .../vue_shared/components/popup_dialog.vue | 131 --------------------- .../vue_shared/components/recaptcha_dialog.vue | 8 +- app/assets/stylesheets/framework/modal.scss | 11 +- app/assets/stylesheets/pages/repo.scss | 13 -- spec/features/profile_spec.rb | 4 +- .../projects/tree/create_directory_spec.rb | 2 +- spec/features/projects/tree/create_file_spec.rb | 2 +- .../groups/components/item_actions_spec.js | 24 ++-- .../vue_shared/components/modal_spec.js | 12 ++ .../vue_shared/components/popup_dialog_spec.js | 12 -- 16 files changed, 198 insertions(+), 204 deletions(-) create mode 100644 app/assets/javascripts/vue_shared/components/modal.vue delete mode 100644 app/assets/javascripts/vue_shared/components/popup_dialog.vue create mode 100644 spec/javascripts/vue_shared/components/modal_spec.js delete mode 100644 spec/javascripts/vue_shared/components/popup_dialog_spec.js diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue index 09cb79c1afd..58ba5aff7cf 100644 --- a/app/assets/javascripts/groups/components/item_actions.vue +++ b/app/assets/javascripts/groups/components/item_actions.vue @@ -1,7 +1,7 @@ diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue index d3344d0c8dc..4e0178072cb 100644 --- a/app/assets/javascripts/repo/components/repo_commit_section.vue +++ b/app/assets/javascripts/repo/components/repo_commit_section.vue @@ -2,12 +2,12 @@ import { mapGetters, mapState, mapActions } from 'vuex'; import tooltip from '../../vue_shared/directives/tooltip'; import icon from '../../vue_shared/components/icon.vue'; -import PopupDialog from '../../vue_shared/components/popup_dialog.vue'; +import modal from '../../vue_shared/components/modal.vue'; import commitFilesList from './commit_sidebar/list.vue'; export default { components: { - PopupDialog, + modal, icon, commitFilesList, }, @@ -16,7 +16,7 @@ export default { }, data() { return { - showNewBranchDialog: false, + showNewBranchModal: false, submitCommitsLoading: false, startNewMR: false, commitMessage: '', @@ -58,7 +58,7 @@ export default { start_branch: createNewBranch ? this.currentBranch : undefined, }; - this.showNewBranchDialog = false; + this.showNewBranchModal = false; this.submitCommitsLoading = true; this.commitChanges({ payload, newMr: this.startNewMR }) @@ -76,7 +76,7 @@ export default { this.checkCommitStatus() .then((branchChanged) => { if (branchChanged) { - this.showNewBranchDialog = true; + this.showNewBranchModal = true; } else { this.makeCommit(); } @@ -99,13 +99,13 @@ export default { 'is-collapsed': collapsed, }" > - - +export default { + name: 'modal', + + props: { + title: { + type: String, + required: false, + }, + text: { + type: String, + required: false, + }, + hideFooter: { + type: Boolean, + required: false, + default: false, + }, + kind: { + type: String, + required: false, + default: 'primary', + }, + modalDialogClass: { + type: String, + required: false, + default: '', + }, + closeKind: { + type: String, + required: false, + default: 'default', + }, + closeButtonLabel: { + type: String, + required: false, + default: 'Cancel', + }, + primaryButtonLabel: { + type: String, + required: false, + default: '', + }, + submitDisabled: { + type: Boolean, + required: false, + default: false, + }, + }, + + computed: { + btnKindClass() { + return { + [`btn-${this.kind}`]: true, + }; + }, + btnCancelKindClass() { + return { + [`btn-${this.closeKind}`]: true, + }; + }, + }, + + methods: { + close() { + this.$emit('toggle', false); + }, + emitSubmit(status) { + this.$emit('submit', status); + }, + }, +}; + + + diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue deleted file mode 100644 index 6d15bbd84ba..00000000000 --- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue +++ /dev/null @@ -1,131 +0,0 @@ - - - diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue b/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue index 3ec50f14eb4..06fc75889b7 100644 --- a/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue +++ b/app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue @@ -1,5 +1,5 @@ diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index ce551e6b7ce..90088bea343 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -44,8 +44,15 @@ body.modal-open { } } -.modal.popup-dialog { - display: block; +.modal { + background-color: $black-transparent; + z-index: 2100; + + @media (min-width: $screen-md-min) { + .modal-dialog { + margin: 30px auto; + } + } } .recaptcha-dialog .recaptcha-form { diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 402412eae71..6eb92c7baee 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -1,16 +1,3 @@ -.modal.popup-dialog { - display: block; - background-color: $black-transparent; - z-index: 2100; - - @media (min-width: $screen-md-min) { - .modal-dialog { - width: 600px; - margin: 30px auto; - } - } -} - .project-refs-form, .project-refs-target-form { display: inline-block; diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index c60883911f7..0848857ed1e 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -25,7 +25,7 @@ describe 'Profile account page', :js do fill_in 'password', with: '12345678' - page.within '.popup-dialog' do + page.within '.modal' do click_button 'Delete account' end @@ -38,7 +38,7 @@ describe 'Profile account page', :js do fill_in 'password', with: 'testing123' - page.within '.popup-dialog' do + page.within '.modal' do click_button 'Delete account' end diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb index 156293289dd..8f06328962e 100644 --- a/spec/features/projects/tree/create_directory_spec.rb +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -20,7 +20,7 @@ feature 'Multi-file editor new directory', :js do click_link('New directory') - page.within('.popup-dialog') do + page.within('.modal') do find('.form-control').set('foldername') click_button('Create directory') diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb index 8fb8476e631..bdebc12ef47 100644 --- a/spec/features/projects/tree/create_file_spec.rb +++ b/spec/features/projects/tree/create_file_spec.rb @@ -20,7 +20,7 @@ feature 'Multi-file editor new file', :js do click_link('New file') - page.within('.popup-dialog') do + page.within('.modal') do find('.form-control').set('filename') click_button('Create file') diff --git a/spec/javascripts/groups/components/item_actions_spec.js b/spec/javascripts/groups/components/item_actions_spec.js index 2ce1a749a96..7a5c1da4d1d 100644 --- a/spec/javascripts/groups/components/item_actions_spec.js +++ b/spec/javascripts/groups/components/item_actions_spec.js @@ -36,27 +36,27 @@ describe('ItemActionsComponent', () => { describe('methods', () => { describe('onLeaveGroup', () => { - it('should change `dialogStatus` prop to `true` which shows confirmation dialog', () => { - expect(vm.dialogStatus).toBeFalsy(); + it('should change `modalStatus` prop to `true` which shows confirmation dialog', () => { + expect(vm.modalStatus).toBeFalsy(); vm.onLeaveGroup(); - expect(vm.dialogStatus).toBeTruthy(); + expect(vm.modalStatus).toBeTruthy(); }); }); describe('leaveGroup', () => { - it('should change `dialogStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { + it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { spyOn(eventHub, '$emit'); - vm.dialogStatus = true; + vm.modalStatus = true; vm.leaveGroup(true); - expect(vm.dialogStatus).toBeFalsy(); + expect(vm.modalStatus).toBeFalsy(); expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup); }); - it('should change `dialogStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { + it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { spyOn(eventHub, '$emit'); - vm.dialogStatus = true; + vm.modalStatus = true; vm.leaveGroup(false); - expect(vm.dialogStatus).toBeFalsy(); + expect(vm.modalStatus).toBeFalsy(); expect(eventHub.$emit).not.toHaveBeenCalled(); }); }); @@ -99,9 +99,9 @@ describe('ItemActionsComponent', () => { newVm.$destroy(); }); - it('should show modal dialog when `dialogStatus` is set to `true`', () => { - vm.dialogStatus = true; - const modalDialogEl = vm.$el.querySelector('.modal.popup-dialog'); + it('should show modal dialog when `modalStatus` is set to `true`', () => { + vm.modalStatus = true; + const modalDialogEl = vm.$el.querySelector('.modal'); expect(modalDialogEl).toBeDefined(); expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave'); diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js new file mode 100644 index 00000000000..721f4044659 --- /dev/null +++ b/spec/javascripts/vue_shared/components/modal_spec.js @@ -0,0 +1,12 @@ +import Vue from 'vue'; +import modal from '~/vue_shared/components/modal.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Modal', () => { + it('does not render a primary button if no primaryButtonLabel', () => { + const modalComponent = Vue.extend(modal); + const vm = mountComponent(modalComponent); + + expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); + }); +}); diff --git a/spec/javascripts/vue_shared/components/popup_dialog_spec.js b/spec/javascripts/vue_shared/components/popup_dialog_spec.js deleted file mode 100644 index 5c1d2a196f4..00000000000 --- a/spec/javascripts/vue_shared/components/popup_dialog_spec.js +++ /dev/null @@ -1,12 +0,0 @@ -import Vue from 'vue'; -import PopupDialog from '~/vue_shared/components/popup_dialog.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('PopupDialog', () => { - it('does not render a primary button if no primaryButtonLabel', () => { - const popupDialog = Vue.extend(PopupDialog); - const vm = mountComponent(popupDialog); - - expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); - }); -}); -- cgit v1.2.1 From d5d2d7b2e007e7d1c8675e07cb198bec9b31b962 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 11 Dec 2017 20:06:07 +0100 Subject: Rename recaptcha-dialog to recaptcha-modal --- .../javascripts/issue_show/components/app.vue | 10 +-- .../issue_show/components/description.vue | 6 +- .../vue_shared/components/recaptcha_dialog.vue | 85 ---------------------- .../vue_shared/components/recaptcha_modal.vue | 85 ++++++++++++++++++++++ .../mixins/recaptcha_dialog_implementor.js | 36 --------- .../mixins/recaptcha_modal_implementor.js | 36 +++++++++ app/assets/stylesheets/framework/modal.scss | 2 +- spec/javascripts/issue_show/components/app_spec.js | 6 +- .../issue_show/components/description_spec.js | 4 +- 9 files changed, 135 insertions(+), 135 deletions(-) delete mode 100644 app/assets/javascripts/vue_shared/components/recaptcha_dialog.vue create mode 100644 app/assets/javascripts/vue_shared/components/recaptcha_modal.vue delete mode 100644 app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js create mode 100644 app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index fd1a50dd533..25ebe5314e0 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -9,7 +9,7 @@ import titleComponent from './title.vue'; import descriptionComponent from './description.vue'; import editedComponent from './edited.vue'; import formComponent from './form.vue'; -import RecaptchaDialogImplementor from '../../vue_shared/mixins/recaptcha_dialog_implementor'; +import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor'; export default { props: { @@ -152,7 +152,7 @@ export default { }, mixins: [ - RecaptchaDialogImplementor, + recaptchaModalImplementor, ], methods: { @@ -197,7 +197,7 @@ export default { }); }, - closeRecaptchaDialog() { + closeRecaptchaModal() { this.store.setFormState({ updateLoading: false, }); @@ -273,10 +273,10 @@ export default { :enable-autocomplete="enableAutocomplete" /> -
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue index feb73481422..c3f2bf130bb 100644 --- a/app/assets/javascripts/issue_show/components/description.vue +++ b/app/assets/javascripts/issue_show/components/description.vue @@ -1,12 +1,12 @@ - - diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue new file mode 100644 index 00000000000..8053c65d498 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue @@ -0,0 +1,85 @@ + + + diff --git a/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js b/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js deleted file mode 100644 index ef70f9432e3..00000000000 --- a/app/assets/javascripts/vue_shared/mixins/recaptcha_dialog_implementor.js +++ /dev/null @@ -1,36 +0,0 @@ -import RecaptchaDialog from '../components/recaptcha_dialog.vue'; - -export default { - data() { - return { - showRecaptcha: false, - recaptchaHTML: '', - }; - }, - - components: { - RecaptchaDialog, - }, - - methods: { - openRecaptcha() { - this.showRecaptcha = true; - }, - - closeRecaptcha() { - this.showRecaptcha = false; - }, - - checkForSpam(data) { - if (!data.recaptcha_html) return data; - - this.recaptchaHTML = data.recaptcha_html; - - const spamError = new Error(data.error_message); - spamError.name = 'SpamError'; - spamError.message = 'SpamError'; - - throw spamError; - }, - }, -}; diff --git a/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js b/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js new file mode 100644 index 00000000000..ff1f565e79a --- /dev/null +++ b/app/assets/javascripts/vue_shared/mixins/recaptcha_modal_implementor.js @@ -0,0 +1,36 @@ +import recaptchaModal from '../components/recaptcha_modal.vue'; + +export default { + data() { + return { + showRecaptcha: false, + recaptchaHTML: '', + }; + }, + + components: { + recaptchaModal, + }, + + methods: { + openRecaptcha() { + this.showRecaptcha = true; + }, + + closeRecaptcha() { + this.showRecaptcha = false; + }, + + checkForSpam(data) { + if (!data.recaptcha_html) return data; + + this.recaptchaHTML = data.recaptcha_html; + + const spamError = new Error(data.error_message); + spamError.name = 'SpamError'; + spamError.message = 'SpamError'; + + throw spamError; + }, + }, +}; diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 90088bea343..1be66d0ab21 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -55,7 +55,7 @@ body.modal-open { } } -.recaptcha-dialog .recaptcha-form { +.recaptcha-modal .recaptcha-form { display: inline-block; .recaptcha { diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 729c3c29f22..7159148f8fa 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -272,10 +272,10 @@ describe('Issuable output', () => { }); }); - it('opens recaptcha dialog if update rejected as spam', (done) => { + it('opens recaptcha modal if update rejected as spam', (done) => { function mockScriptSrc() { const recaptchaChild = vm.$children - .find(child => child.$options._componentTag === 'recaptcha-dialog'); // eslint-disable-line no-underscore-dangle + .find(child => child.$options._componentTag === 'recaptcha-modal'); // eslint-disable-line no-underscore-dangle recaptchaChild.scriptSrc = '//scriptsrc'; } @@ -302,7 +302,7 @@ describe('Issuable output', () => { .then(promise) .then(() => setTimeoutPromise()) .then(() => { - modal = vm.$el.querySelector('.js-recaptcha-dialog'); + modal = vm.$el.querySelector('.js-recaptcha-modal'); expect(modal.style.display).not.toEqual('none'); expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html'); diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js index 2e000a1063f..0da25bdca9c 100644 --- a/spec/javascripts/issue_show/components/description_spec.js +++ b/spec/javascripts/issue_show/components/description_spec.js @@ -54,7 +54,7 @@ describe('Description component', () => { it('opens recaptcha dialog if update rejected as spam', (done) => { let modal; const recaptchaChild = vm.$children - .find(child => child.$options._componentTag === 'recaptcha-dialog'); // eslint-disable-line no-underscore-dangle + .find(child => child.$options._componentTag === 'recaptcha-modal'); // eslint-disable-line no-underscore-dangle recaptchaChild.scriptSrc = '//scriptsrc'; @@ -64,7 +64,7 @@ describe('Description component', () => { vm.$nextTick() .then(() => { - modal = vm.$el.querySelector('.js-recaptcha-dialog'); + modal = vm.$el.querySelector('.js-recaptcha-modal'); expect(modal.style.display).not.toEqual('none'); expect(modal.querySelector('.g-recaptcha').textContent).toEqual('recaptcha_html'); -- cgit v1.2.1 From 16a8a594f42f4d1f74dab21656ab98e7f9204fd7 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 11 Dec 2017 20:50:24 +0100 Subject: Update UX guide --- doc/development/ux_guide/components.md | 18 +++++++++--------- doc/development/ux_guide/copy.md | 12 +++++++----- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 16a811dbc74..d396964e7c1 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -10,7 +10,7 @@ * [Tables](#tables) * [Blocks](#blocks) * [Panels](#panels) -* [Dialog modals](#dialog-modals) +* [Modals](#modals) * [Alerts](#alerts) * [Forms](#forms) * [Search box](#search-box) @@ -255,18 +255,18 @@ Skeleton loading can replace any existing UI elements for the period in which th --- -## Dialog modals +## Modals -Dialog modals are only used for having a conversation and confirmation with the user. The user is not able to access the features on the main page until closing the modal. +Modals are only used for having a conversation and confirmation with the user. The user is not able to access the features on the main page until closing the modal. ### Usage -* When the action is irreversible, dialog modals provide the details and confirm with the user before they take an advanced action. -* When the action will affect privacy or authorization, dialog modals provide advanced information and confirm with the user. +* When the action is irreversible, modals provide the details and confirm with the user before they take an advanced action. +* When the action will affect privacy or authorization, modals provide advanced information and confirm with the user. ### Style -* Dialog modals contain the header, body, and actions. +* Modals contain the header, body, and actions. * **Header(1):** The header title is a question instead of a descriptive phrase. * **Body(2):** The content in body should never be ambiguous and unclear. It provides specific information. * **Actions(3):** Contains a affirmative action, a dismissive action, and an extra action. The order of actions from left to right: Dismissive action → Extra action → Affirmative action @@ -277,13 +277,13 @@ Dialog modals are only used for having a conversation and confirmation with the ### Placement -* Dialog modals should always be the center of the screen horizontally and be positioned **72px** from the top. +* Modals should always be the center of the screen horizontally and be positioned **72px** from the top. -| Dialog with 2 actions | Dialog with 3 actions | Special confirmation | +| Modal with 2 actions | Modal with 3 actions | Special confirmation | | --------------------- | --------------------- | -------------------- | | ![two-actions](img/modals-general-confimation-dialog.png) | ![three-actions](img/modals-three-buttons.png) | ![spcial-confirmation](img/modals-special-confimation-dialog.png) | -> TODO: Special case for dialog modal. +> TODO: Special case for modal. --- diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md index 12e8d0a31bb..af842da7f62 100644 --- a/doc/development/ux_guide/copy.md +++ b/doc/development/ux_guide/copy.md @@ -46,11 +46,11 @@ Avoid using periods in solitary sentences in these elements: * Labels * Hover text * Bulleted lists -* Dialog body text +* Modal body text Periods should be used for: -* Lists or dialogs with multiple sentences +* Lists or modals with multiple sentences * Any sentence followed by a link | :white_check_mark: **Do** place periods after sentences followed by a link | :no_entry_sign: **Don’t** place periods after a link if it‘s not followed by a sentence | @@ -80,7 +80,7 @@ Omit punctuation after phrases and labels to create a cleaner and more readable | Punctuation mark | Copy and paste | HTML entity | Unicode | Mac shortcut | Windows shortcut | Description | |---|---|---|---|---|---|---| -| Period | **.** | | | | | Omit for single sentences in affordances like labels, hover text, bulleted lists, and dialog body text.

Use in lists or dialogs with multiple sentences, and any sentence followed by a link or inline code.

Place inside quotation marks unless you’re telling the reader what to enter and it’s ambiguous whether to include the period. | +| Period | **.** | | | | | Omit for single sentences in affordances like labels, hover text, bulleted lists, and modal body text.

Use in lists or modals with multiple sentences, and any sentence followed by a link or inline code.

Place inside quotation marks unless you’re telling the reader what to enter and it’s ambiguous whether to include the period. | | Comma | **,** | | | | | Place inside quotation marks.

Use a [serial comma][serial comma] in lists of three or more terms. | | Exclamation point | **!** | | | | | Avoid exclamation points as they tend to come across as shouting. Some exceptions include greetings or congratulatory messages. | | Colon | **:** | `:` | `\u003A` | | | Omit from labels, for example, in the labels for fields in a form. | @@ -88,7 +88,7 @@ Omit punctuation after phrases and labels to create a cleaner and more readable | Quotation marks | **“**

**”**

**‘**

**’** | `“`

`”`

`‘`

`’` | `\u201C`

`\u201D`

`\u2018`

`\u2019` | ⌥ Option+[

⌥ Option+⇧ Shift+[

⌥ Option+]

⌥ Option+⇧ Shift+] | Alt+0 1 4 7

Alt+0 1 4 8

Alt+0 1 4 5

Alt+0 1 4 6 | Use proper quotation marks (also known as smart quotes, curly quotes, or typographer’s quotes) for quotes. Single quotation marks are used for quotes inside of quotes.

The right single quotation mark symbol is also used for apostrophes.

Don’t use primes, straight quotes, or free-standing accents for quotation marks. | | Primes | **′**

**″** | `′`

`″` | `\u2032`

`\u2033` | | Alt+8 2 4 2

Alt+8 2 4 3 | Use prime (′) only in abbreviations for feet, arcminutes, and minutes: 3° 15′

Use double-prime (″) only in abbreviations for inches, arcseconds, and seconds: 3° 15′ 35″

Don’t use quotation marks, straight quotes, or free-standing accents for primes. | | Straight quotes and accents | **"**

**'**

**`**

**´** | `"`

`'`

```

`´` | `\u0022`

`\u0027`

`\u0060`

`\u00B4` | | | Don’t use straight quotes or free-standing accents for primes or quotation marks.

Proper typography never uses straight quotes. They are left over from the age of typewriters and their only modern use is for code. | -| Ellipsis | **…** | `…` | | ⌥ Option+; | Alt+0 1 3 3 | Use to indicate an action in progress (“Downloading…”) or incomplete or truncated text. No space before the ellipsis.

Omit from menu items or buttons that open a dialog or start some other process. | +| Ellipsis | **…** | `…` | | ⌥ Option+; | Alt+0 1 3 3 | Use to indicate an action in progress (“Downloading…”) or incomplete or truncated text. No space before the ellipsis.

Omit from menu items or buttons that open a modal or start some other process. | | Chevrons | **«**

**»**

**‹**

**›**

**<**

**>** | `«`

`»`

`‹`

`›`

`<`

`>` | `\u00AB`

`\u00BB`

`\u2039`

`\u203A`

`\u003C`

`\u003E`

| | | Omit from links or buttons that open another page or move to the next or previous step in a process. Also known as angle brackets, angular quote brackets, or guillemets. | | Em dash | **—** | `—` | `\u2014` | ⌥ Option+⇧ Shift+- | Alt+0 1 5 1 | Avoid using dashes to separate text. If you must use dashes for this purpose — like this — use an em dash surrounded by spaces. | | En dash | **–** | `–` | `\u2013` | ⌥ Option+- | Alt+0 1 5 0 | Use an en dash without spaces instead of a hyphen to indicate a range of values, such as numbers, times, and dates: “3–5 kg”, “8:00 AM–12:30 PM”, “10–17 Jan” | @@ -175,7 +175,7 @@ A **comment** is a written piece of text that users of GitLab can create. Commen #### Discussion A **discussion** is a group of 1 or more comments. A discussion can include subdiscussions. Some discussions have the special capability of being able to be **resolved**. Both the comments in the discussion and the discussion itself can be resolved. -## Confirmation dialogs +## Modals - Destruction buttons should be clear and always say what they are destroying. E.g., `Delete page` instead of just `Delete`. @@ -184,6 +184,8 @@ A **discussion** is a group of 1 or more comments. A discussion can include subd - Avoid the word `cancel` or `canceled` in the descriptive copy. It can be confusing when you then see the `Cancel` button. +see also: guidelines for [modal components](components.md#modals) + --- Portions of this page are modifications based on work created and shared by the [Android Open Source Project][android project] and used according to terms described in the [Creative Commons 2.5 Attribution License][creative commons]. -- cgit v1.2.1