Skip to content

Commit

Permalink
fix(entities-plugins): plugin form UX phase 2
Browse files Browse the repository at this point in the history
  • Loading branch information
sumimakito committed Apr 2, 2024
1 parent 8de453e commit f49f23e
Show file tree
Hide file tree
Showing 57 changed files with 1,313 additions and 872 deletions.
92 changes: 88 additions & 4 deletions packages/core/forms/src/generator/FormGenerator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,31 @@
:key="`group-${i}`"
>
<KCollapse
v-if="group.collapsible"
:model-value="group.collapsedByDefault === undefined ? false : group.collapsedByDefault"
:title="group.legend"
v-if="group.collapsible !== undefined && group.collapsible !== false"
class="root-level-collapse"
:model-value="false"
:title="group.collapsible.title"
>
<template
v-if="group.collapsible.description"
#visible-content
>
{{ group.collapsible.description }}
</template>

<slot
v-if="group.slots?.beforeContent"
:name="group.slots?.beforeContent"
/>

<slot
v-if="group.fields.length === 0 && group.slots?.emptyState"
:name="group.slots?.emptyState"
/>

<component
:is="tag"
v-else
:class="getFieldRowClasses(group)"
>
<template
Expand All @@ -55,6 +74,37 @@
/>
</template>
</component>

<KCollapse
v-if="group.collapsible !== true && group.collapsible.nestedCollapsible && group.collapsible.nestedCollapsible.fields.length > 0"
class="nested-collapse"
:model-value="collapseStates[`group-${i}-nested`] ?? true"
trigger-alignment="leading"
:trigger-label="(collapseStates[`group-${i}-nested`] ?? true) ? group.collapsible.nestedCollapsible.triggerLabel.expand : group.collapsible.nestedCollapsible.triggerLabel.collapse"
@update:model-value="(value) => collapseStates[`group-${i}-nested`] = value"
>
<component
:is="tag"
:class="getFieldRowClasses(group)"
>
<template
v-for="field in group.collapsible.nestedCollapsible.fields"
:key="field.model"
>
<form-group
v-if="fieldVisible(field)"
ref="children"
:errors="errors"
:field="field"
:model="model"
:options="options"
:vfg="vfg"
@model-updated="onModelUpdated"
@validated="onFieldValidated"
/>
</template>
</component>
</KCollapse>
</KCollapse>

<component
Expand Down Expand Up @@ -87,6 +137,17 @@
</template>

<script>
/**
* @typedef {import('./types').FGCollapsibleOptions} FGCollapsibleOptions
* @typedef {import('./types').FGSlots} FGSlots
*
* @typedef PartialGroup
* @prop {FGCollapsibleOptions=} collapsible
* @prop {FGSlots=} slots
*
* @typedef {Record<string, any> & PartialGroup} Group
*/
import { get as objGet, forEach, isFunction, isNil, isArray } from 'lodash'
import formMixin from './FormMixin.vue'
import formGroup from './FormGroup.vue'
Expand Down Expand Up @@ -148,6 +209,7 @@ export default {
vfg: this,
errors: [], // Validation errors,
children: ref([]),
collapseStates: {},
}
},
Expand All @@ -163,6 +225,7 @@ export default {
return res
},
groups() {
/** @type {Group[]} */
const res = []
if (this.schema && this.schema.groups) {
forEach(this.schema.groups.slice(0), group => {
Expand Down Expand Up @@ -201,7 +264,7 @@ export default {
mounted() {
this.$nextTick(() => {
if (this.model) {
// First load, running validation if neccessary
// First load, running validation if necessary
if (this.options.validateAfterLoad === true && this.isNewModel !== true) {
this.validate()
} else {
Expand Down Expand Up @@ -304,6 +367,10 @@ export default {
box-sizing: border-box;
}
.hidden-field {
display: none;
}
.form-group {
margin-bottom: 24px;
Expand Down Expand Up @@ -530,6 +597,7 @@ export default {
.vue-form-generator label {
font-weight: 500;
}
.vue-form-generator .form-group input[type=radio] {
align-items: center;
appearance: none;
Expand All @@ -552,4 +620,20 @@ export default {
background-color: currentColor;
}
}
.vue-form-generator {
.root-level-collapse {
.k-collapse-heading {
margin-bottom: 0 !important;
}
.k-collapse-visible-content {
color: $kui-color-text-neutral;
}
}
.nested-collapse .k-collapse-heading {
margin-bottom: $kui-space-80 !important;
}
}
</style>
16 changes: 16 additions & 0 deletions packages/core/forms/src/generator/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type FGCollapsibleOptions = boolean | {
title?: string
description?: string
nestedCollapsible?: {
fields: any[],
triggerLabel: {
expand: string
collapse: string
}
}
}

export interface FGSlots {
beforeContent?: string
emptyState?: string
}
1 change: 1 addition & 0 deletions packages/core/forms/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ export const getSharedFormName = (modelName: string, enabledAcmeCustomTemplate =
}

export * from './const'
export * from './types'
export * as abstractField from './generator/fields/abstractField'
1 change: 1 addition & 0 deletions packages/core/forms/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './generator/types'
3 changes: 2 additions & 1 deletion packages/core/forms/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"cypress",
"cypress/vue",
"../../../cypress/support"
]
],
"allowJs": true
},
"include": [
"src/**/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<div class="name-cell-wrapper">
<PluginIcon
class="plugin-icon"
:name="pluginMetaData.getImageName(getPropValue('rowValue', slotProps))"
:name="getPropValue('rowValue', slotProps)"
:width="24"
/>
<span class="info-name">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,20 @@
:options="formOptions"
:schema="formSchema"
@model-updated="onModelUpdated"
/>
>
<template #plugin-config-empty-state>
<div class="plugin-config-empty-state">
{{ t('plugins.form.grouping.plugin_configuration.empty') }}
</div>
</template>

<template
v-if="PLUGIN_METADATA[formModel.name] && PLUGIN_METADATA[formModel.name].fieldRules"
#plugin-config-before-content
>
<PluginFieldRuleAlerts :rules="PLUGIN_METADATA[formModel.name].fieldRules!" />
</template>
</VueFormGenerator>
</div>
</div>
</template>
Expand All @@ -42,12 +55,15 @@ import '@kong-ui-public/forms/dist/style.css'
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import { computed, defineComponent, onBeforeMount, provide, reactive, ref, watch, type PropType } from 'vue'
import composables from '../composables'
import useI18n from '../composables/useI18n'
import { PLUGIN_METADATA } from '../definitions/metadata'
import endpoints from '../plugins-endpoints'
import {
type KongManagerPluginFormConfig,
type KonnectPluginFormConfig,
type PluginEntityInfo,
} from '../types'
import PluginFieldRuleAlerts from './PluginFieldRuleAlerts.vue'
// Must explicitly specify these as components since they are rendered dynamically
export default defineComponent({
Expand Down Expand Up @@ -126,6 +142,7 @@ const { parseSchema } = composables.useSchemas(props.entityMap.focusedEntity?.id
const { convertToDotNotation, unFlattenObject, isObjectEmpty, unsetNullForeignKey } = composables.usePluginHelpers()
const { objectsAreEqual } = useHelpers()
const { i18n: { t } } = useI18n()
// define endpoints for use by KFG
const buildGetOneUrl = (entityType: string, entityId: string): string => {
Expand Down Expand Up @@ -617,6 +634,15 @@ onBeforeMount(() => {
opacity: 0;
}
.entity-form {
.plugin-config-empty-state {
color: $kui-color-text-neutral;
font-size: $kui-font-size-30;
font-style: italic;
margin-bottom: $kui-space-60;
}
}
:deep(.vue-form-generator) {
.k-collapse:not(:last-child) {
border-bottom: $kui-border-width-10 solid $kui-color-border;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<template>
<KAlert class="plugin-field-rule-alerts">
<ul>
<li
v-for="(alert, i) in formattedSimpleAlerts"
:key="`simple-alert-${i}`"
>
{{ alert }}
</li>

<li v-if="props.rules.onlyOneOfMutuallyRequired && props.rules.onlyOneOfMutuallyRequired.length > 0">
<div
v-for="(combinations, i) in props.rules.onlyOneOfMutuallyRequired"
:key="`only-one-mutually-alert-${i}`"
>
<div>{{ t('plugins.form.field_rules.only_one_of_mutually_required') }}</div>
<ul>
<li
v-for="(fields, j) in combinations"
:key="`only-one-mutually-alert-${i}-combination-${j}`"
>
{{ formatFields(fields) }}
</li>
</ul>
</div>
</li>
</ul>
</KAlert>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import composables from '../composables'
import type { FieldRules } from '../types'
const props = defineProps<{
rules: FieldRules
}>()
const { formatPluginFieldLabel } = composables.usePluginHelpers()
const { i18n: { t } } = composables.useI18n()
const formatFields = (fields: string[]) =>
fields.map((field) =>
field.replace(/^[cC]onfig\./, '').split('.').map((s) => formatPluginFieldLabel(s)).join('.'),
).join(', ')
const formatAlert = (translateKey: Parameters<typeof t>[0], fields: string[]) =>
t(translateKey, { parameters: formatFields(fields) })
const formattedSimpleAlerts = computed<string[]>(() => {
const alerts = []
if (props.rules.atLeastOneOf) {
for (const fields of props.rules.atLeastOneOf) {
alerts.push(formatAlert('plugins.form.field_rules.at_least_one_of', fields))
}
}
if (props.rules.onlyOneOf) {
for (const fields of props.rules.onlyOneOf) {
alerts.push(formatAlert('plugins.form.field_rules.only_one_of', fields))
}
}
if (props.rules.mutuallyRequired) {
for (const fields of props.rules.mutuallyRequired) {
alerts.push(formatAlert('plugins.form.field_rules.mutually_required', fields))
}
}
return alerts
})
</script>

<style scoped lang="scss">
.plugin-field-rule-alerts {
ul {
margin: 0;
padding-inline-start: $kui-space-80;
}
margin-bottom: $kui-space-60;
margin-top: $kui-space-40;
}
</style>
Loading

0 comments on commit f49f23e

Please sign in to comment.