mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-10 18:38:22 +00:00
Enhance EnumInput and Forms
- Soft-Nesting Layout to show structure without too much x-overhead - Forms now use subgrid where possible - Generalized form style to cover more cases and remove redundancy - Minor definitions fix - Made NicerForm a global component - Updated Test Page to use global components
This commit is contained in:
parent
864ca6defd
commit
bc83309d6d
6 changed files with 47 additions and 40 deletions
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Index, MaybeIndex, equals } from '../../util';
|
||||
import NicerForm, { Fields } from './NicerForm.vue';
|
||||
import { Fields } from './NicerForm.vue';
|
||||
|
||||
export type Variant = {
|
||||
fields?: Fields,
|
||||
|
@ -21,9 +21,11 @@ const props = withDefaults(defineProps<{
|
|||
|
||||
// One-Way Bindings
|
||||
variants?: Variants,
|
||||
label?: string,
|
||||
}>(), {
|
||||
modelValue: null,
|
||||
variants: () => ({}),
|
||||
label: '',
|
||||
});
|
||||
const { variants } = $(props);
|
||||
|
||||
|
@ -64,14 +66,15 @@ watchEffect(() => {
|
|||
|
||||
</script>
|
||||
<template>
|
||||
<div class="enum-input">
|
||||
<div class="form">
|
||||
<label v-text="label"/>
|
||||
<div class="pillbar">
|
||||
<button class="variant" v-for="[index, variant] of Object.entries(variants)" :key="index" :class="{selected: currentVariant === index}" @click="() => currentVariant = index">
|
||||
<button v-for="[index, variant] of Object.entries(variants)" :key="index" :class="{selected: currentVariant === index}" @click="() => currentVariant = index">
|
||||
<component v-if="variant.icon" :is="variant.icon"/>
|
||||
<template v-else>{{ variant.display }}</template>
|
||||
</button>
|
||||
</div>
|
||||
<NicerForm class="enum-fields" v-if="currentVariant && variants[currentVariant]?.fields" :fields="variants[currentVariant].fields" v-model="formValue" :key="currentVariant"/>
|
||||
<NicerForm v-if="currentVariant && variants[currentVariant]?.fields" :fields="variants[currentVariant].fields" v-model="formValue" :key="currentVariant"/>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
|
@ -79,9 +82,6 @@ watchEffect(() => {
|
|||
flex-flow: nowrap;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.variant { padding: 0.25rem; gap: 0.25rem; }
|
||||
.selected { background-color: var(--cl-bg-sl); }
|
||||
.enum-fields {
|
||||
padding-left: 0px;
|
||||
}
|
||||
.pillbar > button { padding: 0.25rem; gap: 0.25rem; }
|
||||
.pillbar > .selected { background-color: var(--cl-bg-sl); }
|
||||
</style>
|
|
@ -50,21 +50,12 @@ watch($$(modelValue), (val) => {
|
|||
|
||||
<template>
|
||||
<div class="form">
|
||||
<component v-if="heading" :is="`h${headingLevel}`" :text="heading"/>
|
||||
<template v-for="[index, field] of Object.entries(fields)" :key="index">
|
||||
<label v-if="field.label" v-text="field.label"/>
|
||||
<component :is="field.is" v-model="modelValue[index]" v-bind="field.props"/>
|
||||
</template>
|
||||
<component v-if="heading" :is="`h${headingLevel}`">{{ heading }}</component>
|
||||
<div class="form inner-form">
|
||||
<template v-for="[index, field] of Object.entries(fields)" :key="index">
|
||||
<label v-if="field.label && field.is !== 'EnumInput'" v-text="field.label"/>
|
||||
<component :is="field.is" v-model="modelValue[index]" v-bind="field.is === 'EnumInput' ? Object.assign({label: field.label}, field.props) : field.props"/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
label::after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
label {
|
||||
border-left: 1px solid var(--cl-fg);
|
||||
border-bottom: 1px solid var(--cl-fg);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -49,7 +49,7 @@ const GetAddresses: SearchProvider = async (o) => {
|
|||
};
|
||||
|
||||
const GetServices: SearchProvider = async (o) => {
|
||||
let res = await apiCall('Object.services.list', {});
|
||||
let res = await apiCall('object.services.list', {});
|
||||
if (res.Error === null) {
|
||||
console.debug('services', res.Data);
|
||||
let obj = {} as Options;
|
||||
|
|
|
@ -25,19 +25,32 @@ input {
|
|||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
form, .form {
|
||||
/* Universal Form Style-Component */
|
||||
:is(form, .form) {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
:is(form, .form) > :is(button, .button, h1) {
|
||||
grid-column: 1 / 3;
|
||||
:is(form, .form) :is(form, .form) { /* Subform and EnumInput */
|
||||
grid-column: 1 / 3; /* Maintenance: This column-end must match the column count of the host form, +1 */
|
||||
grid-template-columns: subgrid; /* All descendants of a form align to that same form */
|
||||
padding: 0px; /* To keep alignment, no padding is needed for those descendants */
|
||||
}
|
||||
:is(form, .form) :is(form, .form) > .inner-form { /* Soft-Nesting for Subform and EnumInput */
|
||||
border-left: 1px solid var(--cl-fg);
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
:is(form, .form) > :is(button, .button, h1, h2, h3, h4, h5, h6) {
|
||||
grid-column: 1 / 3; /* Full-Span children */
|
||||
}
|
||||
:is(form, .form) > label {
|
||||
grid-column: 1;
|
||||
grid-column: 1; /* Re-align unbalanced grid => allows the last column(s) to be optional */
|
||||
padding: 0.25rem;
|
||||
}
|
||||
:is(form, .form) > label::after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
|
|
@ -5,6 +5,7 @@ import './global-styles/mlfe.css';
|
|||
import './global-styles/transitions.css';
|
||||
import 'vue-toast-notification/dist/theme-default.css';
|
||||
|
||||
import NicerForm from './components/inputs/NicerForm.vue';
|
||||
import PillBar from './components/inputs/PillBar.vue';
|
||||
import TextBox from './components/inputs/TextBox.vue';
|
||||
import EnumInput from './components/inputs/EnumInput.vue';
|
||||
|
@ -34,6 +35,7 @@ app.use(head);
|
|||
app.use(ToastPlugin);
|
||||
|
||||
// Global Components
|
||||
app.component('NicerForm', NicerForm);
|
||||
app.component('PillBar', PillBar);
|
||||
app.component('TextBox', TextBox);
|
||||
app.component('NumberBox', NumberBox);
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import SingleSelect from '../components/inputs/SingleSelect.vue';
|
||||
import { SearchProvider, Options } from '../components/inputs/DropdownInput.vue';
|
||||
import MultiSelect from '../components/inputs/MultiSelect.vue';
|
||||
import NicerForm from '../components/inputs/NicerForm.vue';
|
||||
import EnumInput from '../components/inputs/EnumInput.vue';
|
||||
import TextBox from '../components/inputs/TextBox.vue';
|
||||
|
||||
const testValues: Options = {
|
||||
1: { display: 'Option 1' },
|
||||
|
@ -28,18 +23,24 @@ function genSP(indexIsChar: boolean): SearchProvider {
|
|||
<div>
|
||||
<PageHeader title="Test Page"/>
|
||||
<NicerForm :fields="{
|
||||
Single: { is: SingleSelect, label: 'SingleSelect', props: { options: testValues, searchProvider: genSP(true) } },
|
||||
Multiple: { is: MultiSelect, label: 'Multiselect', props: { options: testValues, searchProvider: genSP(false) } },
|
||||
IP: { is: EnumInput, label: 'IP Address', props: { variants: {
|
||||
Single: { is: 'SingleSelect', label: 'SingleSelect', props: { options: testValues, searchProvider: genSP(true) } },
|
||||
Multiple: { is: 'MultiSelect', label: 'Multiselect', props: { options: testValues, searchProvider: genSP(false) } },
|
||||
IP: { is: 'EnumInput', label: 'IP Address', props: { variants: {
|
||||
'dhcp': { display: 'DHCP' },
|
||||
'static-ipv4': {
|
||||
display: 'Static (IPV4)',
|
||||
fields: {
|
||||
ip: { is: TextBox, label: 'IP Address + CIDR' },
|
||||
gw: { is: TextBox, label: 'Gateway Address' },
|
||||
ip: { is: 'TextBox', label: 'IP Address (CIDR)' },
|
||||
gw: { is: 'TextBox', label: 'Gateway Address' },
|
||||
},
|
||||
},
|
||||
}}},
|
||||
Subform: { is: 'NicerForm', props: { heading: 'Subform', fields: {
|
||||
Text: { is: 'TextBox', label: 'Text' },
|
||||
Subform2: { is: 'NicerForm', props: { heading: 'Subform2', fields: {
|
||||
Text: { is: 'TextBox', label: 'Text' },
|
||||
} } },
|
||||
} } },
|
||||
}" v-model="vm"/>
|
||||
{{ vm }}
|
||||
<button @click="() => { vm.Multiple = [1]; }">Click me</button>
|
||||
|
|
Loading…
Add table
Reference in a new issue