Skip to content

Implementing a New Component in Design Kit

When adding a new component to our project, it's important to follow a structured approach to ensure consistency and integration with our existing design system.

Consult the Figma Design Kit

Begin by examining the Figma Design Kit to understand the design and functionality of the component you're about to implement.

This kit serves as the source of truth for our design principles and aesthetics.

Review and Collaboration

If uncertain about the component's structure or implementation, consult with our amazing Product Team.

Collaboration is key to ensuring our components integrate seamlessly with the broader design system.

Create Component in Design Kit Directory

Following the naming conventions and folder structure outlined in Design Kit Introduction, create your new component within the resources/design-kit directory.

Ensure that the directory path mirrors the hierarchy found in the Figma Design Kit.

Folder Structure and Naming

Adhere to the established folder structure and naming conventions.

This consistency aids in the maintainability and navigability of our codebase.

Write Stories and Tests

Next, focus on writing stories and tests for your component.

Stories will help document the various states and variations of your component, while tests ensure its functionality meets the expected criteria.

Full Example

Here's an example of a progress bar component for the Design Kit, including stories and tests.

Please note that this example is for illustrative purposes only, and may not reflect the actual implementation of a progress bar component.

resources/design-kit/molecules/c-progress-bar/CProgressBar.vue

<template>
    <div class="relative w-full h-6 bg-gray-200 rounded-full">
        <div
            class="absolute top-0 left-0 h-full bg-primary-kit-300 rounded-full"
            :style="{ width: progress + '%' }"
        ></div>
        <p class="text-center z-10 absolute w-full h-full flex items-center justify-center">
            {{ progressLabel }}
        </p>
    </div>
</template>

<script>
export default {
    name: 'CProgressBar',

    props: {
        progress: {
            type: Number,
            required: true,
        },
    },

    computed: {
        // Components with computed properties are considered stateless.
        // This is an example to illustrate that computed properties are allowed.
        progressLabel() {
            return this.progress + '%';
        },
    },
};
</script>

resources/design-kit/molecules/c-progress-bar/CProgressBar.stories.js

import CProgressBar from './CProgressBar.vue';

export default {
    // Add the component to the Design Kit category in Storybook
    title: 'Design Kit/Molecules/ProgressBar',

    // Add the component to the story
    component: CProgressBar,

    // Add the autodocs tag to generate documentation automatically
    tags: ['autodocs'],

    // Add the argTypes to provide controls and documentation for the props and slots
    argTypes: {
        progress: {
            control: {
                type: 'range',
                min: 0,
                max: 100,
            },
        },
    },

    // Add the args to set the default values for the props and slots
    args: {
        progress: 0,
    },

    // Add the render function to provide a template for the stories
    render: (args) => ({
        components: { CProgressBar },
        props: Object.keys(args),
        template: `
            <c-progress-bar :progress="progress" />
        `,
    }),
};

// Use Component Story Format (CSF)
export const Empty = {
    args: {
        progress: 0,
    },
};

export const Incomplete = {
    args: {
        progress: 25,
    },
}

export const Half = {
    args: {
        progress: 50,
    },
};

export const Complete = {
    args: {
        progress: 100,
    },
};

resources/design-kit/molecules/c-progress-bar/CProgressBar.spec.js

import { shallowMount } from '@vue/test-utils';

import CProgressBar from './CProgressBar.vue';

describe('CProgressBar', () => {
    it('renders a progress bar with the correct width', () => {
        const wrapper = shallowMount(CProgressBar, {
            propsData: {
                progress: 50,
            },
        });

        expect(wrapper.find('div > div').element.style.width).toBe('50%');
        expect(wrapper.text()).toBe('50%');
    });

    it('renders a progress bar with the correct width when progress is 100%', () => {
        const wrapper = shallowMount(CProgressBar, {
            propsData: {
                progress: 100,
            },
        });

        expect(wrapper.find('div > div').element.style.width).toBe('100%');
        expect(wrapper.text()).toBe('100%');
    });

    it('renders a progress bar with the correct width when progress is 25%', () => {
        const wrapper = shallowMount(CProgressBar, {
            propsData: {
                progress: 25,
            },
        });

        expect(wrapper.find('div > div').element.style.width).toBe('25%');
        expect(wrapper.text()).toBe('25%');
    });

    it('renders a progress bar with the correct width when progress is 0%', () => {
        const wrapper = shallowMount(CProgressBar, {
            propsData: {
                progress: 0,
            },
        });

        expect(wrapper.find('div > div').element.style.width).toBe('0%');
        expect(wrapper.text()).toBe('0%');
    });
});

resources/design-kit/molecules/c-progress-bar/CProgressBarContainer.vue

<template>
    <c-progress-bar :progress="progress" />
</template>

<script>
import CProgressBar from './CProgressBar.vue';

export default {
    name: 'CProgressBarContainer',

    components: {
        CProgressBar,
    },

    data() {
        return {
            progress: 0,
        };
    },

    mounted() {
        this.startProgress();
    },

    methods: {
        startProgress() {
            // We're using a simple interval here for illustrative purposes,
            // in a real-world scenario, you'd likely use a more sophisticated
            // approach like using a store, AJAX, subscribing to a WebSocket, etc.
            const interval = setInterval(() => {
                if (this.progress < 100) {
                    this.progress += 10;
                } else {
                    clearInterval(interval);
                }
            }, 1000);
        },
    },
};
</script>

This approach ensures that the component provides a clear documentation using Storybook for the Development Team to follow, and can be visually tested by the Product Team.

It also ensures that the component is thoroughly tested using Jest and that it works as expected in various states and variations, ensuring that tests are being run on the CI/CD pipeline.

Finally, by it's modular design you could use the CProgressBar component statically or dynamically in other projects, while the CProgressBarContainer component implements a specific use case.

Next Steps

Now that you understand the structure and organization of the Design Kit, you can move on to implementing a new component or explore the existing components in the Design Kit.

Also consider reviewing the Design Kit Global Plugins to understand how to implement global plugins in the Design Kit.