Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add categorization renderer to Vue Vanilla #2270

Merged
73 changes: 73 additions & 0 deletions packages/vue-vanilla/src/layouts/CategorizationRenderer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<div :class="styles.categorization.root">
<div :class="styles.categorization.category">
<template
v-for="(category, index) in categories"
:key="`category-${index}`"
>
<div v-if="category.value.visible" @click="selected = index">
<button
:class="[selected === index ? styles.categorization.selected : '']"
:disabled="!category.value.enabled"
>
<label>{{ category.value.label }}</label>
</button>
</div>
</template>
</div>

<div :class="styles.categorization.panel">
<DispatchRenderer
v-if="categories[selected]"
:schema="layout.schema"
:uischema="categories[selected].value.uischema"
:path="layout.path"
:enabled="layout.enabled"
:renderers="layout.renderers"
:cells="layout.cells"
/>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import type { JsonFormsRendererRegistryEntry, Layout } from '@jsonforms/core';
import {
and,
categorizationHasCategory,
isCategorization,
rankWith,
} from '@jsonforms/core';
import {
DispatchRenderer,
rendererProps,
useJsonFormsCategorization,
type RendererProps,
} from '@jsonforms/vue';
import { useVanillaLayout } from '../util';

const layoutRenderer = defineComponent({
name: 'CategorizationRenderer',
components: {
DispatchRenderer,
},
props: {
...rendererProps<Layout>(),
},
setup(props: RendererProps<Layout>) {
return useVanillaLayout(useJsonFormsCategorization(props));
},
data() {
return {
selected: 0,
};
},
});

export default layoutRenderer;
export const entry: JsonFormsRendererRegistryEntry = {
renderer: layoutRenderer,
tester: rankWith(2, and(isCategorization, categorizationHasCategory)),
};
</script>
120 changes: 120 additions & 0 deletions packages/vue-vanilla/src/layouts/CategorizationStepperRenderer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<template>
<div :class="styles.categorization.root">
<div :class="styles.categorization.stepper">
<template
v-for="(category, index) in visibleCategories"
:key="`tab-${index}`"
>
<div v-if="category.value.visible" @click="selected = index">
<button
:class="[selected === index ? styles.categorization.selected : '']"
:disabled="!category.value.enabled"
>
<span :class="styles.categorization.stepperBadge">{{
index + 1
}}</span>

<label>{{ category.value.label }}</label>
</button>
</div>

<hr
v-if="index !== visibleCategories.length - 1"
:class="styles.categorization.stepperLine"
/>
</template>
</div>

<div :class="styles.categorization.panel">
<DispatchRenderer
v-if="visibleCategories[selected]"
:schema="layout.schema"
:uischema="visibleCategories[selected].value.uischema"
:path="layout.path"
:enabled="layout.enabled"
:renderers="layout.renderers"
:cells="layout.cells"
/>
</div>

<footer
v-if="appliedOptions?.showNavButtons"
:class="styles.categorization.stepperFooter"
>
<div
v-if="selected > 0"
:class="styles.categorization.stepperButtonBack"
@click="selected = selected - 1"
>
<button :disabled="!visibleCategories[selected - 1].value.enabled">
{{ 'back' }}
</button>
</div>

<div
v-if="selected + 1 < visibleCategories.length"
:class="styles.categorization.stepperButtonNext"
@click="selected = selected + 1"
>
<button :disabled="!visibleCategories[selected + 1].value.enabled">
{{ 'next' }}
</button>
</div>
</footer>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import type { JsonFormsRendererRegistryEntry, Layout } from '@jsonforms/core';
import {
and,
categorizationHasCategory,
isCategorization,
optionIs,
rankWith,
} from '@jsonforms/core';
import {
DispatchRenderer,
rendererProps,
useJsonFormsCategorization,
type RendererProps,
} from '@jsonforms/vue';
import { useVanillaLayout } from '../util';

const layoutRenderer = defineComponent({
name: 'CategorizationStepperRenderer',
components: {
DispatchRenderer,
},
props: {
...rendererProps<Layout>(),
},
setup(props: RendererProps<Layout>) {
return useVanillaLayout(useJsonFormsCategorization(props));
},
data() {
return {
selected: 0,
};
},
computed: {
visibleCategories() {
return this.categories.filter((category) => category.value.visible);
},
},
});

export default layoutRenderer;
export const entry: JsonFormsRendererRegistryEntry = {
renderer: layoutRenderer,
tester: rankWith(
3,
and(
isCategorization,
categorizationHasCategory,
optionIs('variant', 'stepper')
)
),
};
</script>
11 changes: 10 additions & 1 deletion packages/vue-vanilla/src/layouts/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
export { default as LayoutRenderer } from './LayoutRenderer.vue';
export { default as GroupRenderer } from './GroupRenderer.vue';
export { default as CategorizationRenderer } from '../layouts/CategorizationRenderer.vue';
export { default as CategorizationStepperRenderer } from '../layouts/CategorizationStepperRenderer.vue';

import { entry as layoutRendererEntry } from './LayoutRenderer.vue';
import { entry as groupRendererEntry } from './GroupRenderer.vue';
import { entry as categorizationEntry } from '../layouts/CategorizationRenderer.vue';
import { entry as categorizationStepperEntry } from '../layouts/CategorizationStepperRenderer.vue';

export const layoutRenderers = [layoutRendererEntry, groupRendererEntry];
export const layoutRenderers = [
layoutRendererEntry,
groupRendererEntry,
categorizationEntry,
categorizationStepperEntry,
];
12 changes: 12 additions & 0 deletions packages/vue-vanilla/src/styles/defaultStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,16 @@ export const defaultStyles: Styles = {
oneOf: {
root: 'one-of',
},
categorization: {
root: 'categorization',
category: 'categorization-category',
selected: 'categorization-selected',
panel: 'categorization-panel',
stepper: 'categorization-stepper',
stepperBadge: 'categorization-stepper-badge',
stepperLine: 'categorization-stepper-line',
stepperFooter: 'categorization-stepper-footer',
stepperButtonBack: 'categorization-stepper-button-back',
stepperButtonNext: 'categorization-stepper-button-next',
},
};
13 changes: 13 additions & 0 deletions packages/vue-vanilla/src/styles/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const createEmptyStyles = (): Styles => ({
label: {},
dialog: {},
oneOf: {},
categorization: {},
});

export interface Styles {
Expand Down Expand Up @@ -71,6 +72,18 @@ export interface Styles {
oneOf: {
root?: string;
};
categorization: {
root?: string;
category?: string;
selected?: string;
panel?: string;
stepper?: string;
stepperBadge?: string;
stepperLine?: string;
stepperFooter?: string;
stepperButtonBack?: string;
stepperButtonNext?: string;
};
}

export const useStyles = (element?: UISchemaElement) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect } from 'chai';
import { mountJsonForms } from '../util';

const schema = {
type: 'string',
};
const uischema = {
type: 'Categorization',
elements: [
{
type: 'Category',
label: 'A',
elements: [
{
type: 'Control',
scope: '#',
},
],
},
{
type: 'Category',
label: 'B',
elements: [],
},
],
};

describe('CategorizationRenderer.vue', () => {
it('renders categorization', () => {
const wrapper = mountJsonForms('', schema, uischema);
expect(wrapper.find('.categorization').exists()).to.be.true;
});

it('renders 2 category items', async () => {
const wrapper = mountJsonForms('', schema, uischema);
const inputs = wrapper.findAll('.categorization-category > *');
expect(inputs.length).to.equal(2);
});

it('renders 1 panel item', async () => {
const wrapper = mountJsonForms('', schema, uischema);
const inputs = wrapper.findAll('.categorization-panel > *');
expect(inputs.length).to.equal(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect } from 'chai';
import { mountJsonForms } from '../util';

const schema = {
type: 'string',
};
const uischema = {
type: 'Categorization',
elements: [
{
type: 'Category',
label: 'A',
elements: [
{
type: 'Control',
scope: '#',
},
],
},
{
type: 'Category',
label: 'B',
elements: [],
},
],
options: {
variant: 'stepper',
},
};

const uischemaNav = {
...uischema,
options: {
variant: 'stepper',
showNavButtons: true,
},
};

describe('CategorizationStepperRenderer.vue', () => {
it('renders categorization as stepper', () => {
const wrapper = mountJsonForms('', schema, uischema);
expect(wrapper.find('.categorization-stepper').exists()).to.be.true;
});

it('renders 2 stepper items and one line', () => {
const wrapper = mountJsonForms('', schema, uischema);
const inputs = wrapper.findAll('.categorization-stepper > div');
expect(inputs.length).to.equal(2);
const lines = wrapper.findAll('.categorization-stepper > hr');
expect(lines.length).to.equal(1);
});

it('renders a next button at stepper nav bar', () => {
const wrapper = mountJsonForms('', schema, uischemaNav);
expect(
wrapper
.find(
'.categorization footer.categorization-stepper-footer > div.categorization-stepper-button-next'
)
.exists()
).to.be.true;
});
});
14 changes: 14 additions & 0 deletions packages/vue-vanilla/vanilla.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,17 @@
.array-list-item-content.expanded {
display: block;
}

.categorization .categorization-category,
.categorization .categorization-stepper {
display: flex;
}
.categorization .categorization-stepper-line {
flex-grow: 1;
height: 1px;
border-width: 0 0 1px 0;
}
.categorization .categorization-stepper-footer {
display: flex;
justify-content: flex-end;
}
Loading
Loading