Hone logo
Hone
Problems

Vue 3 Provide/Inject for Theming

This challenge will guide you through implementing Vue 3's provide/inject API to manage application-wide themes. You'll create a system where a parent component can "provide" theme data, and deeply nested child components can "inject" and utilize this data to dynamically update their styling. This pattern is crucial for building flexible and maintainable applications where certain data needs to be accessible across many components without prop drilling.

Problem Description

Your task is to build a Vue 3 application that demonstrates the provide and inject mechanism for managing an application theme.

What needs to be achieved:

  1. Theme Provider Component: Create a component that acts as the root of your theming system. This component will define the current theme (e.g., "light" or "dark") and provide this theme information, along with associated styling properties, to its descendants.
  2. Theme Consumer Components: Create several child components, some deeply nested, that can access and use the provided theme data. These components should dynamically update their appearance based on the injected theme.
  3. Theme Switching: Implement a mechanism within the provider component to allow the user to switch between themes (e.g., a button to toggle between "light" and "dark").

Key Requirements:

  • Use Vue 3's Composition API (setup function).
  • Implement provide in a parent component to make theme data available.
  • Implement inject in descendant components to receive this data.
  • Theme data should include at least a theme name (e.g., string) and a set of CSS variables or styles (e.g., object).
  • The provided theme data should be reactive, meaning changes in the provider should automatically reflect in the consumers.
  • Your solution must be written in TypeScript.

Expected Behavior:

  • When the application loads, a default theme (e.g., "light") should be active.
  • Child components should render with styles corresponding to the active theme.
  • Clicking a button (e.g., in the provider component) should switch the active theme.
  • All relevant child components should immediately update their styling to reflect the newly selected theme.

Edge Cases to Consider:

  • What happens if a component tries to inject a key that hasn't been provided? (Your implementation should handle this gracefully or document the expected behavior).

Examples

Example 1: Basic Structure

Let's imagine a simple hierarchy: App.vue (Provider) -> Content.vue -> Button.vue (Consumer).

  • App.vue (Provider):

    • Manages a theme ref, initially "light".
    • Defines lightThemeStyles and darkThemeStyles objects.
    • Provides theme and the relevant theme styles object.
    • Includes a button to toggle theme.
  • Button.vue (Consumer):

    • Injects theme and themeStyles.
    • Applies injected themeStyles.buttonBackground to its background.
    • Applies injected themeStyles.buttonColor to its text color.
// App.vue (simplified setup)
import { provide, ref } from 'vue';

const theme = ref('light');
const lightThemeStyles = {
  backgroundColor: '#ffffff',
  textColor: '#333333',
  // ... other styles
};
const darkThemeStyles = {
  backgroundColor: '#333333',
  textColor: '#ffffff',
  // ... other styles
};

const themeStyles = computed(() =>
  theme.value === 'light' ? lightThemeStyles : darkThemeStyles
);

provide('theme', theme);
provide('themeStyles', themeStyles);

function toggleTheme() {
  theme.value = theme.value === 'light' ? 'dark' : 'light';
}

// Button.vue (simplified setup)
import { inject } from 'vue';

const theme = inject('theme');
const themeStyles = inject('themeStyles');

// Template uses themeStyles.backgroundColor and themeStyles.textColor

Expected Visual Outcome: Initially, a button would be light gray with dark text. After toggling, it would become dark gray with light text.

Example 2: Deeply Nested Consumer

Consider App.vue -> Layout.vue -> Header.vue -> UserAvatar.vue (Consumer).

  • App.vue: Provides theme as in Example 1.
  • Layout.vue: Might use provided theme for its background.
  • Header.vue: Might use provided theme for its text color.
  • UserAvatar.vue: Injects themeStyles to set its borderColor based on the theme.
// UserAvatar.vue (simplified setup)
import { inject } from 'vue';

const themeStyles = inject('themeStyles');

// Template uses themeStyles.avatarBorderColor

Expected Visual Outcome: The border color of the user avatar would change dynamically as the theme is switched, even though UserAvatar.vue doesn't directly receive props from App.vue for this styling.

Constraints

  • Vue.js version: 3.x
  • Language: TypeScript
  • Must use Composition API (setup function).
  • The provided theme data must be reactive.
  • The provide keys must be unique strings.

Notes

  • Think about how to make the theme data easily extensible. What if you want to add more theme-specific properties later?
  • Consider using symbols for provide/inject keys for better encapsulation and to avoid potential key collisions, especially in larger applications. While strings are acceptable for this challenge, exploring symbols is a good next step.
  • You can use Vue's computed properties to derive reactive values that are then provided.
  • For styling, you can either provide raw CSS values (strings) or objects that can be directly bound to v-bind() in your <style> tags.
  • The inject function can accept a default value. This is useful for components that might be used outside of your theming provider context.
Loading editor...
typescript