Building a Reusable Vue 3 Component Library with TypeScript
This challenge will guide you through the process of creating a small, yet functional, component library for Vue 3 using TypeScript. A well-structured component library is crucial for building maintainable and scalable Vue applications, enabling code reuse and ensuring consistent UI across projects.
Problem Description
Your task is to develop a set of reusable UI components that can be easily imported and used in any Vue 3 project. You will focus on creating a basic button component and a simple modal component, demonstrating how to handle props, events, and slots effectively within a TypeScript environment.
Key Requirements:
- Button Component (
BaseButton.vue):- Accepts a
variantprop (e.g., 'primary', 'secondary', 'danger') to control its appearance. - Accepts a
disabledprop (boolean) to disable user interaction. - Emits a
clickevent when the button is clicked. - Supports passing content via slots (e.g., text, icons).
- Should have basic styling for different variants.
- Accepts a
- Modal Component (
BaseModal.vue):- Accepts an
isOpenprop (boolean) to control its visibility. - Accepts a
titleprop (string) for the modal header. - Emits a
closeevent when the close button inside the modal is clicked or when the overlay is clicked. - Uses slots for the modal content and potentially for custom footer actions.
- Should include a close button and a semi-transparent overlay.
- Accepts an
- TypeScript Integration: All components should be written in TypeScript, leveraging Vue 3's script setup syntax for better type safety and developer experience.
- Directory Structure: Organize your components within a dedicated
src/componentsdirectory. - Exporting: Create an
index.tsfile within thesrc/componentsdirectory to export all components, making them easily importable.
Expected Behavior:
- Buttons should render with appropriate styles based on their
variantand be clickable or disabled as expected. - Modals should appear and disappear based on the
isOpenprop, display their title, and allow content to be injected via slots. Clicking the overlay or close button should trigger thecloseevent.
Edge Cases to Consider:
- What happens if no
variantis provided for the button? - How should the modal handle empty content slots?
- Ensure proper accessibility considerations (e.g., focus management for the modal).
Examples
Example 1: Button Component Usage
Input (Vue SFC):
<template>
<div>
<BaseButton variant="primary" @click="handleClick">Click Me</BaseButton>
<BaseButton variant="secondary" disabled>Disabled Button</BaseButton>
<BaseButton>
<img src="/path/to/icon.svg" alt="Icon">
With Icon
</BaseButton>
</div>
</template>
<script setup lang="ts">
import BaseButton from './components/BaseButton.vue';
const handleClick = () => {
console.log('Button clicked!');
};
</script>
Output (Rendered HTML - conceptual):
<div>
<button class="base-button primary">Click Me</button>
<button class="base-button secondary" disabled>Disabled Button</button>
<button class="base-button">
<img src="/path/to/icon.svg" alt="Icon">
With Icon
</button>
</div>
Explanation: The BaseButton component is used with different variants and a disabled state. The click handler is attached to the primary button, and a slot is used to include an image alongside text in the third button.
Example 2: Modal Component Usage
Input (Vue SFC):
<template>
<div>
<BaseButton @click="isModalOpen = true">Open Modal</BaseButton>
<BaseModal title="My Awesome Modal" :isOpen="isModalOpen" @close="isModalOpen = false">
<p>This is the content of the modal.</p>
<template #footer>
<BaseButton @click="isModalOpen = false">Close</BaseButton>
</template>
</BaseModal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BaseButton from './components/BaseButton.vue';
import BaseModal from './components/BaseModal.vue';
const isModalOpen = ref(false);
</script>
Output (Rendered HTML - conceptual, when isModalOpen is true):
<div>
<button class="base-button primary">Open Modal</button>
<div class="modal-overlay">
<div class="modal-wrapper">
<div class="modal-header">
<h3>My Awesome Modal</h3>
<button class="close-button">×</button>
</div>
<div class="modal-body">
<p>This is the content of the modal.</p>
</div>
<div class="modal-footer">
<button class="base-button">Close</button>
</div>
</div>
</div>
</div>
Explanation: The BaseModal is controlled by the isModalOpen reactive variable. It receives a title, and its content is provided via the default slot. A custom footer is also provided using a named slot. Clicking the "Open Modal" button sets isModalOpen to true, rendering the modal. Clicking the "Close" button in the footer or the overlay (if implemented) will set isModalOpen back to false, closing the modal.
Constraints
- Vue 3 is required.
- TypeScript must be used for all component logic and type definitions.
- The
script setupsyntax is encouraged for its conciseness and type safety. - Basic CSS should be included within the SFCs for styling; no external CSS frameworks are allowed for this challenge.
- Focus on the core functionality of the components. Advanced animations or complex accessibility features are out of scope for this initial challenge.
Notes
- Consider using Vue's
definePropsanddefineEmitswithinscript setupfor robust type checking. - For the modal, think about how to trap focus and manage it when the modal opens and closes.
- The
index.tsfile is crucial for creating a distributable library. It should export components like:export { default as BaseButton } from './BaseButton.vue';. - This is an exercise in building the components themselves. You don't need to set up a full build process for publishing to npm, but aim for a structure that would facilitate it.