Skip to content

Commit

Permalink
Merge pull request #5945 from nextcloud-libraries/backport/5932/next
Browse files Browse the repository at this point in the history
[next] feat(NcDialog): Allow to make the dialog a form
  • Loading branch information
susnux committed Aug 22, 2024
2 parents e0b39e7 + 54201a3 commit f7c8b5b
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/components/NcButton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export enum ButtonType {
TertiaryOnPrimary = 'tertiary-on-primary',
Error = 'error',
/**
* @deprecated Designwise not recommended for new code
* @deprecated Design-wise not recommended for new code
*/
Warning = 'warning',
Success = 'success',
Expand Down
111 changes: 105 additions & 6 deletions src/components/NcDialog/NcDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ You can also use the default slot to inject custom content.
</template>
<div style="color: red; font-weight: bold;">This is serious</div>
</NcDialog>
<NcDialog :open.sync="showLongDialog" name="Lorem Ipsum">
<NcDialog v-model:open="showLongDialog" name="Lorem Ipsum">
<p v-for="i in new Array(63)" :key="i">Lorem ipsum dolor sit amet.</p>
</NcDialog>
</div>
Expand All @@ -76,6 +76,58 @@ export default {
}
</script>
```

### Form example
It is also possible to use the dialog for small forms.
This can be used when asking for a password, a name or similar to have native form validation.

To make the dialog a form the `is-form` prop needs to be set.
When using the form variant you can also pass buttons with `nativeType` prop to add a native `submit` button.

Note that this is not possible if the dialog contains a navigation!

```vue
<template>
<div>
<NcButton @click="showDialog = true">Show dialog</NcButton>
<NcDialog is-form
:buttons="buttons"
name="Choose a name"
v-model:open="showDialog"
@submit="currentName = newName"
@closing="newName = ''">
<NcTextField v-model="newName"
label="New name"
minlength="6"
placeholder="Min. 6 characters"
required />
</NcDialog>
<p>New name: {{ currentName }}</p>
</div>
</template>
<script>
import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
import IconCheck from '@mdi/svg/svg/check.svg?raw'
export default {
data() {
return {
showDialog: false,
newName: '',
currentName: 'none yet.',
buttons: [
{
label: 'Submit',
type: 'primary',
nativeType: 'submit',
icon: IconCheck,
}
]
}
},
}
</script>
```
</docs>

<template>
Expand All @@ -88,7 +140,11 @@ export default {
@update:show="handleClosing">
<!-- The dialog name / header -->
<h2 :id="navigationId" class="dialog__name" v-text="name" />
<div class="dialog" :class="dialogClasses">
<component :is="dialogTagName"
ref="dialogElement"
class="dialog"
:class="dialogClasses"
v-on="dialogListeners">
<div ref="wrapper" :class="['dialog__wrapper', { 'dialog__wrapper--collapsed': isNavigationCollapsed }]">
<!-- When the navigation is collapsed (too small dialog) it is displayed above the main content, otherwise on the inline start -->
<nav v-if="hasNavigation"
Expand Down Expand Up @@ -116,7 +172,7 @@ export default {
@click="handleButtonClose" />
</slot>
</div>
</div>
</component>
</NcModal>
</template>

Expand Down Expand Up @@ -158,7 +214,7 @@ export default defineComponent({
/** Additional elements to add to the focus trap */
additionalTrapElements: {
type: Array as PropType<(string|HTMLElement)[]>,
validator: (arr) => {
validator: (arr: unknown) => {
return (
Array.isArray(arr) && arr.every(
(element) =>
Expand Down Expand Up @@ -232,6 +288,16 @@ export default defineComponent({
default: false,
},
/**
* Make the dialog wrapper a HTML form element.
* The buttons will be wrapped within the form so they can be used as submit / reset buttons.
* Please note that when using the property the `navigation` should not be used.
*/
isForm: {
type: Boolean,
default: false,
},
/**
* Declare if hiding the modal should be animated
* @default false
Expand Down Expand Up @@ -307,7 +373,7 @@ export default defineComponent({
},
},
emits: ['closing', 'update:open'],
emits: ['closing', 'update:open', 'submit'],
setup(props, { emit, slots }) {
/**
Expand Down Expand Up @@ -354,7 +420,33 @@ export default defineComponent({
})
/**
* If the underlaying modal is shown
* @type {import('vue').Ref<HTMLFormElement|undefined>}
*/
const dialogElement = ref()
/**
* The HTML element to use for the dialog wrapper - either form or plain div
*/
const dialogTagName = computed(() => props.isForm && !hasNavigation.value ? 'form' : 'div')
/**
* Listener to assign to the dialog element
* This only sets the `@submit` listener if the dialog element is a form
*/
const dialogListeners = computed(() => dialogTagName.value === 'form'
? {
/**
* @param {SubmitEvent} event Form submit event
*/
submit(event) {
event.preventDefault()
/** Forwarded HTMLFormElement submit event (only if `is-form` is set) */
emit('submit', event)
},
}
: {},
)
/**
* If the underlying modal is shown
*/
const showModal = ref(true)
Expand All @@ -363,6 +455,10 @@ export default defineComponent({
* Handle clicking a dialog button -> should close
*/
const handleButtonClose = () => {
// Skip close if invalid dialog
if (dialogTagName.value === 'form' && !dialogElement.value.reportValidity()) {
return
}
handleClosing()
window.setTimeout(() => handleClosed(), 300)
}
Expand Down Expand Up @@ -407,6 +503,9 @@ export default defineComponent({
}))
return {
dialogElement,
dialogListeners,
dialogTagName,
handleButtonClose,
handleClosing,
handleClosed,
Expand Down
25 changes: 21 additions & 4 deletions src/components/NcDialogButton/NcDialogButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Dialog button component used by NcDialog in the actions slot to display the butt
<template>
<NcButton :aria-label="label"
:disabled="disabled"
:native-type="nativeType"
:type="type"
@click="handleClick">
{{ label }}
Expand All @@ -24,7 +25,7 @@ Dialog button component used by NcDialog in the actions slot to display the butt

<script lang="ts">
import { defineComponent, type PropType } from 'vue'
import NcButton, { ButtonType } from '../NcButton/index'
import NcButton, { ButtonNativeType, ButtonType } from '../NcButton/index'
import NcIconSvgWrapper from '../NcIconSvgWrapper/index.js'
export default defineComponent({
Expand All @@ -42,7 +43,8 @@ export default defineComponent({
*/
callback: {
type: Function,
required: true,
required: false,
default: () => {},
},
/**
Expand Down Expand Up @@ -71,8 +73,23 @@ export default defineComponent({
default: ButtonType.Secondary,
required: false,
validator(value: string) {
return Object.values(ButtonType).includes(value as ButtonType)
}
return typeof value === 'string'
&& Object.values(ButtonType).includes(value as ButtonType)
},
},
/**
* The native type of the button, see `NcButton`
* @type {'button'|'submit'|'reset'}
*/
nativeType: {
type: String as PropType<ButtonNativeType>,
required: false,
default: 'button',
validator(value) {
return typeof value === 'string'
&& Object.values(ButtonNativeType).includes(value as ButtonNativeType)
},
},
/**
Expand Down
5 changes: 3 additions & 2 deletions src/components/NcInputField/NcInputField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,8 @@ export default {
&:active:not([disabled]),
&:hover:not([disabled]),
&:focus:not([disabled]) {
border-color: var(--color-main-text);
border-width: var(--border-width-input-focused, 2px);
border-color: var(--color-main-text) !important;
box-shadow: 0 0 0 2px var(--color-main-background) !important;
}
Expand Down Expand Up @@ -421,7 +421,8 @@ export default {
}
}
&--error {
&--error,
&:invalid {
border-color: var(--color-error) !important; //Override hover border color
&:focus-visible {
box-shadow: rgb(248, 250, 252) 0px 0px 0px 2px, var(--color-primary-element) 0px 0px 0px 4px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px
Expand Down

0 comments on commit f7c8b5b

Please sign in to comment.