Hone logo
Hone
Problems

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 variant prop (e.g., 'primary', 'secondary', 'danger') to control its appearance.
    • Accepts a disabled prop (boolean) to disable user interaction.
    • Emits a click event when the button is clicked.
    • Supports passing content via slots (e.g., text, icons).
    • Should have basic styling for different variants.
  • Modal Component (BaseModal.vue):
    • Accepts an isOpen prop (boolean) to control its visibility.
    • Accepts a title prop (string) for the modal header.
    • Emits a close event 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.
  • 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/components directory.
  • Exporting: Create an index.ts file within the src/components directory to export all components, making them easily importable.

Expected Behavior:

  • Buttons should render with appropriate styles based on their variant and be clickable or disabled as expected.
  • Modals should appear and disappear based on the isOpen prop, display their title, and allow content to be injected via slots. Clicking the overlay or close button should trigger the close event.

Edge Cases to Consider:

  • What happens if no variant is 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">&times;</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 setup syntax 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 defineProps and defineEmits within script setup for robust type checking.
  • For the modal, think about how to trap focus and manage it when the modal opens and closes.
  • The index.ts file 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.
Loading editor...
typescript