Skip to content

Commit

Permalink
Merge pull request #5932 from nextcloud-libraries/feat/allow-dialog-form
Browse files Browse the repository at this point in the history
feat(NcDialog): Allow to make the dialog a form
  • Loading branch information
susnux authored Aug 8, 2024
2 parents 7b6f86a + 1946683 commit cafa799
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 8 deletions.
107 changes: 103 additions & 4 deletions src/components/NcDialog/NcDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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"
:open.sync="showDialog"
@submit="currentName = newName"
@closing="newName = ''">
<NcTextField label="New name"
placeholder="Min. 6 characters"
required
minlength="6"
:value.sync="newName" />
</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 @@ -224,6 +280,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 @@ -299,7 +365,7 @@ export default defineComponent({
},
},
emits: ['closing', 'update:open'],
emits: ['closing', 'update:open', 'submit'],
setup(props, { emit, slots }) {
/**
Expand Down Expand Up @@ -347,7 +413,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 @@ -356,6 +448,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 @@ -400,6 +496,9 @@ export default defineComponent({
}))
return {
dialogElement,
dialogListeners,
dialogTagName,
handleButtonClose,
handleClosing,
handleClosed,
Expand Down
18 changes: 16 additions & 2 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 Down Expand Up @@ -42,7 +43,8 @@ export default defineComponent({
*/
callback: {
type: Function,
required: true,
required: false,
default: () => {},
},
/**
Expand Down Expand Up @@ -70,7 +72,19 @@ export default defineComponent({
type: String,
required: false,
default: 'secondary',
validator: (type) => typeof type === 'string' && ['primary', 'secondary', 'error', 'warning', 'success'].includes(type),
validator: (type) => typeof type === 'string' && ['primary', 'secondary', 'tertiary', 'error', 'warning', 'success'].includes(type),
},
/**
* See `nativeType` of `NcButton`
*/
nativeType: {
type: String,
required: false,
default: 'button',
validator(value) {
return ['submit', 'reset', 'button'].includes(value)
},
},
/**
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 @@ -391,8 +391,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;
// Reset padding offset when focused
--input-border-width-offset: 0px;
Expand Down Expand Up @@ -422,7 +422,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 cafa799

Please sign in to comment.