diff --git a/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js b/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js new file mode 100644 index 0000000000000000000000000000000000000000..864850cefcdc3663fec7bdc83c8fdab196516d2c --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/multi_step_form_template.stories.js @@ -0,0 +1,57 @@ +import { GlButton } from '@gitlab/ui'; +import MultiStepFormTemplate from './multi_step_form_template.vue'; + +export default { + component: MultiStepFormTemplate, + title: 'vue_shared/multi_step_form_template', + argTypes: { + title: { + control: 'text', + }, + currentStep: { + control: 'number', + }, + stepsTotal: { + control: 'number', + }, + }, +}; + +const Template = (args, { argTypes }) => ({ + components: { MultiStepFormTemplate, GlButton }, + props: Object.keys(argTypes), + template: ` + + + + + `, +}); + +export const Default = Template.bind({}); +Default.args = { + title: 'Create new project', + currentStep: 1, + stepsTotal: 2, +}; +export const NumberOfStepsIsNotDefined = Template.bind({}); +NumberOfStepsIsNotDefined.args = { + title: 'Create new project', + currentStep: 1, +}; diff --git a/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue b/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue new file mode 100644 index 0000000000000000000000000000000000000000..c88978784150ac6f1cdb40b0687bc88270f3ada2 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/multi_step_form_template.vue @@ -0,0 +1,50 @@ + + diff --git a/app/assets/stylesheets/components/_index.scss b/app/assets/stylesheets/components/_index.scss index 018b1fc49e932e3260411cac0dde9002c6c6a208..983e1236a39f17ef8d666aa33c54cce9c00283ba 100644 --- a/app/assets/stylesheets/components/_index.scss +++ b/app/assets/stylesheets/components/_index.scss @@ -3,6 +3,7 @@ @import './content_editor'; @import './deployment_instance'; @import './detail_page'; +@import './multi_step_form_template'; @import './multiple_choice_selector'; @import './ref_selector'; @import './related_items_list'; diff --git a/app/assets/stylesheets/components/multi_step_form_template.scss b/app/assets/stylesheets/components/multi_step_form_template.scss new file mode 100644 index 0000000000000000000000000000000000000000..5af5618b4079400d7088a87cb6c98276c8a9441f --- /dev/null +++ b/app/assets/stylesheets/components/multi_step_form_template.scss @@ -0,0 +1,3 @@ +.multi-step-form { + max-width: 38rem; +} diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3f4f6eda46ff1ad27e8a26d51a61249df01ba4b6..7841051c6eab938ddc428da782368970e1793577 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -55228,9 +55228,15 @@ msgstr "" msgid "StatusPage|your status page frontend." msgstr "" +msgid "Step %{currentStep}" +msgstr "" + msgid "Step %{currentStep} of %{stepCount}" msgstr "" +msgid "Step %{currentStep} of %{stepsTotal}" +msgstr "" + msgid "Step %{step}" msgstr "" diff --git a/spec/frontend/vue_shared/components/multi_step_form_template_spec.js b/spec/frontend/vue_shared/components/multi_step_form_template_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..86ed77d899252508e83de12e4f230c7005cb330c --- /dev/null +++ b/spec/frontend/vue_shared/components/multi_step_form_template_spec.js @@ -0,0 +1,88 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; + +describe('MultiStepFormTemplate', () => { + let wrapper; + const defaultProps = { + title: 'Form title', + currentStep: 1, + }; + + const createComponent = (props = {}, slots) => { + wrapper = shallowMountExtended(MultiStepFormTemplate, { + propsData: { + ...defaultProps, + ...props, + }, + slots, + }); + }; + + const findTitle = () => wrapper.findByTestId('multi-step-form-title'); + const findContent = () => wrapper.findByTestId('multi-step-form-content'); + const findSteps = () => wrapper.findByTestId('multi-step-form-steps'); + const findActions = () => wrapper.findByTestId('multi-step-form-action'); + const findFooter = () => wrapper.findByTestId('multi-step-form-footer'); + + it('renders title', () => { + createComponent(); + + expect(findTitle().text()).toBe('Form title'); + }); + + describe('step display', () => { + it('displays step X of N when stepsTotal is provided', () => { + createComponent({ stepsTotal: 2 }); + + expect(findSteps().text()).toBe('Step 1 of 2'); + }); + + it('displays only step X when stepsTotal is not provided', () => { + createComponent(); + + expect(findSteps().text()).toBe('Step 1'); + }); + }); + + describe('slots', () => { + it('renders form slot content', () => { + createComponent({}, { form: '
Form Content
' }); + + expect(findContent().exists()).toBe(true); + expect(findContent().find('.test-form').exists()).toBe(true); + }); + + it('renders action buttons correctly when back and next slots are provided', () => { + createComponent( + { + currentStep: 3, + }, + { + back: '', + next: '', + }, + ); + + expect(findActions().find('button.back').exists()).toBe(true); + expect(findActions().find('button.next').exists()).toBe(true); + }); + + it('renders footer slot content when provided', () => { + createComponent( + {}, + { + footer: '', + }, + ); + + expect(findFooter().exists()).toBe(true); + expect(findFooter().find('.test-footer').exists()).toBe(true); + }); + + it('does not render footer section when no footer slot is provided', () => { + createComponent(); + + expect(findFooter().exists()).toBe(false); + }); + }); +});