diff --git a/packages/vue-vanilla/src/layouts/CategorizationRenderer.vue b/packages/vue-vanilla/src/layouts/CategorizationRenderer.vue new file mode 100644 index 0000000000..561539ca56 --- /dev/null +++ b/packages/vue-vanilla/src/layouts/CategorizationRenderer.vue @@ -0,0 +1,73 @@ + + + diff --git a/packages/vue-vanilla/src/layouts/CategorizationStepperRenderer.vue b/packages/vue-vanilla/src/layouts/CategorizationStepperRenderer.vue new file mode 100644 index 0000000000..c34ee0d4ad --- /dev/null +++ b/packages/vue-vanilla/src/layouts/CategorizationStepperRenderer.vue @@ -0,0 +1,120 @@ + + + diff --git a/packages/vue-vanilla/src/layouts/index.ts b/packages/vue-vanilla/src/layouts/index.ts index f85b8e7c40..be5ffa4127 100644 --- a/packages/vue-vanilla/src/layouts/index.ts +++ b/packages/vue-vanilla/src/layouts/index.ts @@ -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, +]; diff --git a/packages/vue-vanilla/src/styles/defaultStyles.ts b/packages/vue-vanilla/src/styles/defaultStyles.ts index 2c623c45a0..cc467fde1d 100644 --- a/packages/vue-vanilla/src/styles/defaultStyles.ts +++ b/packages/vue-vanilla/src/styles/defaultStyles.ts @@ -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', + }, }; diff --git a/packages/vue-vanilla/src/styles/styles.ts b/packages/vue-vanilla/src/styles/styles.ts index 8b8eeed940..a054c9cc56 100644 --- a/packages/vue-vanilla/src/styles/styles.ts +++ b/packages/vue-vanilla/src/styles/styles.ts @@ -12,6 +12,7 @@ const createEmptyStyles = (): Styles => ({ label: {}, dialog: {}, oneOf: {}, + categorization: {}, }); export interface Styles { @@ -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) => { diff --git a/packages/vue-vanilla/tests/unit/layouts/CategorizationRenderer.spec.ts b/packages/vue-vanilla/tests/unit/layouts/CategorizationRenderer.spec.ts new file mode 100644 index 0000000000..7c1bfc2e9c --- /dev/null +++ b/packages/vue-vanilla/tests/unit/layouts/CategorizationRenderer.spec.ts @@ -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); + }); +}); diff --git a/packages/vue-vanilla/tests/unit/layouts/CategorizationStepperRenderer.spec.ts b/packages/vue-vanilla/tests/unit/layouts/CategorizationStepperRenderer.spec.ts new file mode 100644 index 0000000000..9fd0f01aec --- /dev/null +++ b/packages/vue-vanilla/tests/unit/layouts/CategorizationStepperRenderer.spec.ts @@ -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; + }); +}); diff --git a/packages/vue-vanilla/vanilla.css b/packages/vue-vanilla/vanilla.css index 725eb57d24..403cb85750 100644 --- a/packages/vue-vanilla/vanilla.css +++ b/packages/vue-vanilla/vanilla.css @@ -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; +} diff --git a/packages/vue/src/jsonFormsCompositions.ts b/packages/vue/src/jsonFormsCompositions.ts index 7d8d80e1e4..a9ffc6b324 100644 --- a/packages/vue/src/jsonFormsCompositions.ts +++ b/packages/vue/src/jsonFormsCompositions.ts @@ -35,6 +35,7 @@ import { mapDispatchToMultiEnumProps, mapStateToLabelProps, LabelElement, + Categorization, } from '@jsonforms/core'; import { PropType, @@ -474,3 +475,25 @@ export const useJsonFormsDispatchCell = (props: ControlProps) => { ); return { cell: control, ...other }; }; + +/** + * Provides bindings for 'Categorization' elements. + * + * Access bindings via the provided `categories` array with reactive category objects. + */ +export const useJsonFormsCategorization = (props: LayoutProps) => { + const { layout, ...other } = useJsonFormsLayout(props); + + const categories = (layout.value.uischema as Categorization).elements.map( + (category) => { + const categoryProps: LayoutProps = { + ...props, + uischema: category, + }; + + return useJsonFormsLayout(categoryProps).layout; + } + ); + + return { layout, categories, ...other }; +};