Add Card component helper and update CardForm to use it

This commit is contained in:
Axolotle 2020-12-16 12:12:37 +01:00
parent 9e747579e1
commit 694bdb6ea1
4 changed files with 126 additions and 42 deletions

View file

@ -0,0 +1,92 @@
<template>
<b-card v-bind="$attrs" :no-body="collapsable ? true : $attrs['no-body']">
<template #header>
<div class="w-100 d-flex align-items-center flex-wrap custom-header">
<slot name="header">
<component :is="titleTag" class="custom-header-title">
<icon v-if="icon" :iname="icon" class="mr-2" />{{ title }}
</component>
</slot>
<div v-if="hasButtons" class="mt-2 w-100 custom-header-buttons" :class="{ [`ml-${buttonUnbreak}-auto mt-${buttonUnbreak}-0 w-${buttonUnbreak}-auto`]: buttonUnbreak }">
<slot name="header-buttons" />
</div>
</div>
<b-button
v-if="collapsable" @click="visible = !visible"
size="sm" variant="outline-secondary"
class="align-self-center ml-auto" :class="{ 'not-collapsed': visible, 'collapsed': !visible, [`ml-${buttonUnbreak}-2`]: buttonUnbreak }"
>
<icon iname="chevron-right" />
<span class="sr-only">{{ $t('words.collapse') }}</span>
</b-button>
</template>
<b-collapse v-if="collapsable" :visible="visible">
<slot v-if="('no-body' in $attrs)" name="default" />
<b-card-body v-else>
<slot name="default" />
</b-card-body>
</b-collapse>
<slot v-else name="default" slot="default" />
<slot name="footer" slot="footer">
<slot name="buttons" />
</slot>
</b-card>
</template>
<script>
export default {
name: 'Card',
props: {
id: { type: String, default: 'ynh-form' },
title: { type: String, default: null },
titleTag: { type: String, default: 'h2' },
icon: { type: String, default: null },
collapsable: { type: Boolean, default: false },
collapsed: { type: Boolean, default: false },
buttonUnbreak: { type: String, default: 'md' }
},
data () {
return {
visible: !this.collapsed
}
},
computed: {
hasButtons () {
return 'header-buttons' in this.$slots
}
}
}
</script>
<style lang="scss" scoped>
.card-header {
display: flex;
.custom-header {
& > :first-child {
margin-right: 1rem;
}
.btn + .btn {
margin-left: .5rem;
}
}
}
.card-footer {
display: flex;
justify-content: flex-end;
& > *:not(:first-child) {
margin-left: .5rem;
}
}
</style>

View file

@ -1,30 +1,29 @@
<template> <template>
<b-card class="card-form"> <card v-bind="$attrs" class="card-form">
<template v-slot:header> <template #default>
<component :is="titleTag"><icon v-if="icon" :iname="icon" /> {{ title }}</component> <slot name="disclaimer" />
<b-form
:id="id" :inline="inline" :class="formClasses"
@submit.prevent="onSubmit" novalidate
>
<slot name="default" />
<slot name="server-error">
<b-alert variant="danger" :show="serverError !== ''" v-html="serverError" />
</slot>
</b-form>
</template> </template>
<slot name="disclaimer" /> <slot name="buttons" slot="buttons">
<b-button
<b-form :id="id" @submit.prevent="onSubmit" novalidate> type="submit" variant="success"
<slot name="default" /> :form="id" :disabled="disabled"
>
<slot name="server-error"> {{ submitText ? submitText : $t('save') }}
<b-alert variant="danger" :show="serverError !== ''" v-html="serverError" /> </b-button>
</slot> </slot>
</b-form> </card>
<template v-if="!noFooter" v-slot:footer>
<slot name="buttons">
<b-button
type="submit" variant="success"
:form="id" :disabled="disabled"
>
{{ submitText ? submitText : $t('save') }}
</b-button>
</slot>
</template>
</b-card>
</template> </template>
<script> <script>
@ -34,13 +33,11 @@ export default {
props: { props: {
id: { type: String, default: 'ynh-form' }, id: { type: String, default: 'ynh-form' },
title: { type: String, required: true },
titleTag: { type: String, default: 'h2' },
icon: { type: String, default: null },
submitText: { type: String, default: null }, submitText: { type: String, default: null },
noFooter: { type: Boolean, default: false },
validation: { type: Object, default: null }, validation: { type: Object, default: null },
serverError: { type: String, default: '' } serverError: { type: String, default: '' },
inline: { type: Boolean, default: false },
formClasses: { type: [Array, String, Object], default: null }
}, },
computed: { computed: {
@ -63,12 +60,4 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.card-form .card-footer {
display: flex;
justify-content: flex-end;
& > *:not(:first-child) {
margin-left: .5rem;
}
}
</style> </style>

View file

@ -35,7 +35,10 @@
</b-link> </b-link>
</div> </div>
<div v-if="description" v-html="description" :class="{ ['alert p-1 px-2 alert-' + descriptionVariant]: descriptionVariant }"/> <div
v-if="description" v-html="description"
:class="{ ['alert p-1 px-2 alert-' + descriptionVariant]: descriptionVariant }"
/>
</template> </template>
<!-- Slot available to overwrite the one above --> <!-- Slot available to overwrite the one above -->
<slot name="description" /> <slot name="description" />

View file

@ -1,5 +1,5 @@
<template> <template>
<b-button-toolbar :aria-label="label" id="view-top-bar"> <b-button-toolbar :aria-label="label" id="top-bar">
<div id="top-bar-left" class="top-bar-group" v-if="hasLeftSlot"> <div id="top-bar-left" class="top-bar-group" v-if="hasLeftSlot">
<slot name="group-left" /> <slot name="group-left" />
</div> </div>
@ -16,7 +16,7 @@
<script> <script>
export default { export default {
name: 'ViewTopBar', name: 'TopBar',
props: { props: {
label: { type: String, default: null }, label: { type: String, default: null },
@ -24,7 +24,7 @@ export default {
type: Object, type: Object,
default: null, default: null,
validator (value) { validator (value) {
return ['text', 'to'].forEach(prop => (prop in value)) return ['text', 'to'].every(prop => (prop in value))
} }
} }
}, },
@ -46,7 +46,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
#view-top-bar { #top-bar {
margin-bottom: 2rem; margin-bottom: 2rem;
flex-wrap: wrap-reverse; flex-wrap: wrap-reverse;