New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

nuxt-componentsbook-module

Package Overview
Dependencies
Maintainers
0
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nuxt-componentsbook-module

This module provides a Storybook-like experience for Nuxt components

  • 1.2.3
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
863
increased by516.43%
Maintainers
0
Weekly downloads
 
Created
Source

Components Book Module for Nuxt

header

Overview

This module provides a Storybook-like experience for Nuxt components, allowing you to document and test your Vue components using .stories.vue files. It automatically scans a specified directory for story files, generates dynamic routes, and creates an interactive UI for viewing and testing components.

Unlike Storybook, which can be complex and heavy, this module is lightweight and seamlessly integrates into Nuxt, making it easy to set up and use. All stories are written as standard Vue components, ensuring a smooth and intuitive development experience.

img1 img2

Features

  • 📦 Automatic scanning of .stories.vue files and registration as pages.
  • Live reloading with file-watching support.
  • 🛠 Extracts and displays component props dynamically.
  • 🏗 Nuxt DevTools Integration for quick access.
  • 📋 Built-in component previewing with EnhancedPreview.
  • 🔄 Supports dynamic prop manipulation and slot usage.
  • 🚀 Flexible component embedding with event handling support.

Installation

npm install --save-dev nuxt-componentsbook-module

or

yarn add --dev nuxt-componentsbook-module

Setup

1. Register the Module

Add the module to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: [
    'nuxt-componentsbook-module',
  ],
  componentsBook: {
    componentsDir: 'components', // Directory where `.stories.vue` files are located
    disabled: false,
    cache: true,
  },
})

2. Creating a Story

To document a component, create a .stories.vue file in your components directory:

MyInput.stories.vue (Example with EnhancedPreview)
<script setup>
import { ref } from '#imports'
import CustomInput from './MyInput.vue'

const modelValue = ref('')
const label = ref('Enter Text')
const type = ref<'text' | 'password' | 'email' | 'number'>('text')
const placeholder = ref('Type something...')
const disabled = ref(false)
const readonly = ref(false)
const helperText = ref('This is a helper text.')
const size = ref<'sm' | 'md' | 'lg'>('md')
</script>

<template>
  <h1>🟢 CustomInput Component</h1>
  <p>
    The <code>CustomInput</code> component is a versatile input field with multiple configurations.
  </p>

  <h2>🛠 Interactive Controls</h2>
  <div class="controls">
    <label>
      Label:
      <input v-model="label" type="text">
    </label>

    <label>
      Type:
      <select v-model="type">
        <option value="text">Text</option>
        <option value="password">Password</option>
        <option value="email">Email</option>
        <option value="number">Number</option>
      </select>
    </label>
  </div>

  <h2>🔹 Preview</h2>
  <EnhancedPreview
    v-model="modelValue"
    :component="CustomInput"
    :props="{ label, type, placeholder, disabled, readonly, 'helper-text': helperText, size }"
    :emits="['click']"
    @click="console.log('Clicked!')"
  />
</template>

3. Running the Components Book

Start your Nuxt development server:

npm run dev

Visit /componentsbook in your browser to see the list of stories.


📌 Using EnhancedPreview

The EnhancedPreview component is the recommended way to embed and test your components interactively. It allows for dynamic prop manipulation, event handling, and slot usage.

Example Usage

<EnhancedPreview
  v-model="modelValue"
  :component="CustomInput"
  :props="{
    label: 'Enter Text',
    type: 'text',
    placeholder: 'Type something...',
    disabled: false,
    readonly: false,
    'helper-text': 'This is a helper text.',
    size: 'md',
  }"
  :emits="['click']"
  @click="handleClick"
>
  <template #append>
    test slot
  </template>
</EnhancedPreview>

Because it is so flexible, you can create a near-complete “in-component Storybook experience,” connecting your state management (Vuex, Pinia, or custom Refs/Reactives) and a wide range of events to a single preview component.

Key Features of EnhancedPreview

  • Supports v-model: Automatically binds v-model values.
  • Handles events dynamically: Passes events such as @click, @hover, and custom events.
  • Slot support: Allows injecting content into component slots.
  • Live preview: Updates props and re-renders instantly.
  • Code generation: Displays and copies usage examples.

Enhanced usage(useEnhancedPreview)

Below is an advanced example of how you can leverage useEnhancedPreview to gain complete control over a component’s state, events, and display. This approach requires a bit more work, but it allows you to integrate fully custom store logic or any other advanced patterns you need.

Overview

The useEnhancedPreview composable is an extended utility that goes beyond a simple component preview. It provides you with:

  1. Reactive props and v-model binding
  2. Event forwarding (emits)
  3. Additional event listeners (e.g., onClick, onFocus, etc.)
  4. Slot serialization (for generating code snippets)
  5. Code copying / freezing features

Signature

useEnhancedPreview(
  props: UseEnhancedPreviewProps,
  emit: (event: string, ...args: unknown[]) => void,
)

Parameters

props (object) includes the following fields:

FieldTypeDescription
componentDefineComponent | string | unknownThe main component to render.
This can be:
1) A string representing a native HTML tag (e.g., "button")
2) A Vue component (e.g., markRaw(MyComponent))
3) A dynamic reference to a component loaded at runtime.
modelValuestring | number | boolean | object | array | null | Ref<unknown>Value used for two-way data binding. If defined, the composable automatically sets up the v-model logic (i.e., modelValue + onUpdate:modelValue). This can be a ref or a direct value.
propsRecord<string, unknown>Additional props that should be passed to the rendered component. This can include normal props or specialized keys like 'v-model:checked', 'v-model:foo', etc. The composable internally wires these up to update events.
emitsstring[]An array of event names that the component might emit. If you list ['click', 'myEvent'], for example, the composable will handle them via its internal emitEvent logic. You can also see these events reflected in the generated code snippet.
listenersRecord<string, (...args: unknown[]) => void>A dictionary of additional event handlers. This can be either:
- Keys without on prefix, e.g. { click: () => {...} }
- Keys with on prefix, e.g. { onClick: () => {...} }.
These listeners are attached directly to the rendered component in Vue 3 style (onClick, onFocus, etc.).

emit is a function with the signature:

(event: string, ...args: unknown[]) => void

Typically this is defineEmits from within the parent <script setup>.


Returned Properties

The composable returns a set of reactive values and computed properties that you can integrate into your template:

PropertyTypeDescription
renderedComponentComputedRef<VNode>The actual Vue node that you can render via <component :is="renderedComponent" />. It combines all the props, events, and listeners into a single component instance.
dynamicPropsComputedRef<Record<string, unknown>>Internal object of all processed props. You usually won’t render this directly, but it’s accessible if you need to debug or pass them somewhere else.
generatedCodeComputedRef<string>An auto-generated code snippet that shows how to use the component with the currently bound props, events, and (optionally) slot placeholders. This can be displayed to the user or used for copying to the clipboard.
copyButtonTextRef<string>The text on a “Copy” button. It updates automatically to ✅ Copied! when the user copies the snippet, then reverts back to 📋 Copy.
isFrozenRef<boolean>Indicates whether the code snippet is “frozen.” When frozen, the generatedCode no longer reacts to prop changes. Useful for capturing a stable snippet even while you continue changing the actual component’s props in the UI.
toggleFreeze() => voidToggles the isFrozen state. If unfrozen, calling toggleFreeze captures the current code snippet and stops future updates. If frozen, calling it again releases the freeze.
copyCode() => Promise<void>Copies the current generatedCode to the user’s clipboard. Sets copyButtonText to “✅ Copied!” for a few seconds as feedback.

Advanced Examples

Below is a comprehensive example of how to integrate the composable. It demonstrates:

  1. Marking a component as markRaw to avoid Vue reactivity overhead.
  2. Using reactive to handle multiple fields and watchers within a single object.
  3. Providing listeners for custom event handling.
  4. Using emits to specify which events should be recognized and forwarded.

Example: Textarea + Badge

<script setup lang="ts">
import CustomTextarea from './CustomTextarea.vue'
import CustomBadge from './CustomBadge.vue'
import { ref, reactive, markRaw } from '#imports'
import { useEnhancedPreview } from '@/runtime/composables/useEnhancedPreview'

// We have a text area and a badge, each with their own props
const modelValue = ref('')
const placeholder = ref('Type here...')
const text = ref('Badge Label')
const variant = ref<'primary' | 'secondary'>('primary')

// Additional handler just to show we can do custom logic
const handleInput = () => {
  console.log('Text entered:', modelValue.value)
}

// Define an `emit` for v-model updates or custom emits
const emit = defineEmits(['update:modelValue'])

// 1) Setup for CustomTextarea
const {
  copyButtonText,
  isFrozen,
  toggleFreeze,
  copyCode,
  renderedComponent,
  generatedCode,
} = useEnhancedPreview(
  reactive({
    component: markRaw(CustomTextarea),    // Mark the component as raw
    modelValue,                            // Pass a ref directly
    props: {
      placeholder: placeholder.value,      // Normal prop
    },
    emits: ['update:modelValue'],          // We'll forward this event
    listeners: {
      // You can use either `update:modelValue` or `onUpdate:modelValue`
      'update:modelValue': (value) => {
        modelValue.value = value as string
      },
    },
  }),
  emit as (event: string, ...args: unknown[]) => void
)

// 2) Setup for CustomBadge
const {
  copyButtonText: copyButtonTextBadge,
  isFrozen: isFrozenBadge,
  toggleFreeze: toggleFreezeBadge,
  copyCode: copyCodeBadge,
  renderedComponent: renderedBadge,
  generatedCode: generatedCodeBadge,
} = useEnhancedPreview(
  reactive({
    component: markRaw(CustomBadge),
    // No need for modelValue here; just passing some props
    props: {
      text: text.value,
      variant: variant.value,
    },
  }),
  emit as (event: string, ...args: unknown[]) => void
)
</script>

<template>
  <!-- Render the Textarea -->
  <p>
    The <code>CustomTextarea</code> component provides a multi-line text input.
  </p>

  <component :is="renderedComponent" />

  <PreviewSpoiler>
    <PreviewCodeBlock
      :code="generatedCode"
      :show-frozen="true"
      :is-frozen="isFrozen"
      :copy-button-text="copyButtonText"
      @toggle-freeze="toggleFreeze"
      @copy="copyCode"
    />
  </PreviewSpoiler>

  <!-- Render the Badge -->
  <component :is="renderedBadge" @update:model-value="handleInput" />

  <PreviewSpoiler>
    <PreviewCodeBlock
      :code="generatedCodeBadge"
      :show-frozen="true"
      :is-frozen="isFrozenBadge"
      :copy-button-text="copyButtonTextBadge"
      @toggle-freeze="toggleFreezeBadge"
      @copy="copyCodeBadge"
    />
  </PreviewSpoiler>
</template>

Explanation

  1. markRaw(CustomTextarea)
    We wrap our Vue component in markRaw() so that Vue does not convert the component object into a reactive proxy. This prevents warnings and extra overhead.

  2. Using reactive(...)
    We pass an object that bundles up our refs (modelValue) and literal values (props) together. This allows them to be watched for changes. The composable will reflect those changes in the code snippet automatically.

  3. listeners
    In the first setup, we provide a listener for 'update:modelValue'. This ensures that whenever CustomTextarea emits that event, we update modelValue.value accordingly.

  • Alternatively, you could have used 'onUpdate:modelValue' or 'onClick' if you prefer the Vue 3 naming style.
  1. Multiple Instances
    We show useEnhancedPreview used twice — once for the textarea, once for the badge. Each instance returns a unique set of computed properties and reactive states.

  2. Rendering
    Instead of writing <CustomTextarea v-model="modelValue" :placeholder="placeholder" />, you simply do:

    <component :is="renderedComponent" />
    

    The composable already merges the props, the v-model logic, and the event listeners for you.


Additional Notes

  • modelValue can be optional: If you don’t need two-way binding, just omit it.
  • Merging emits and listeners: The composable merges your declared emits (which you might want to track or show in generatedCode) and additional raw event handlers in listeners.
  • Slots: Any slots passed to <component :is="renderedComponent"> are captured and reflected in the generatedCode snippet. In actual usage, you might specify them inline:
    <component :is="renderedComponent">
      <template #append>
        <div>Some appended slot content</div>
      </template>
    </component>
    
  • Performance: Mark your component with markRaw(...) if it’s a DefineComponent object to avoid Vue’s deep reactivity overhead.
  • Frozen Code: Toggling freeze is useful if you need to “lock in” a snippet while continuing to change props or watchers.

Recommendations

  • Start with simple usage (just the component prop, maybe a modelValue) before introducing advanced store logic or multiple watchers.
  • Always wrap large or complex component objects with markRaw() if you pass them to useEnhancedPreview in a reactive context.
  • If you only need standard props and events, consider the simpler usage with EnhancedPreview in .stories.vue. This advanced integration is primarily for scenarios where you need deeper control.

Summary

useEnhancedPreview is an advanced composable that provides a flexible, high-powered way to preview your components with interactive props, event forwarding, and snippet generation. It’s ideal when you need a level of control that goes beyond simple in-component previews, such as fully custom store integrations or specialized event handling.

By carefully configuring props, modelValue, emits, and listeners, you can build a robust, dynamic “mini Storybook” experience directly within your Nuxt app.


How It Works

  1. The module scans the specified componentsDir for .stories.vue files.
  2. Generates dynamic Vue pages for each story and registers them with Nuxt.
  3. Provides a UI layout for previewing and testing components interactively.
  4. Supports real-time editing with automatic updates when files are modified.
  5. Enhances DevTools, adding a new tab called Components Book.

nuxt-i18n-micro integration

Below is an updated note clarifying why you should place nuxt-componentsbook-module before nuxt-i18n-micro in your modules array, given that in this specific setup, having the locale prefix added to Components Book routes can cause problems.


Integration with nuxt-i18n-micro

Example nuxt.config.ts

import { defineNuxtConfig } from 'nuxt/config'

export default defineNuxtConfig({
  extends: './basic',

  // Order here is important:
  // 1) 'nuxt-componentsbook-module' (Components Book)
  // 2) 'nuxt-i18n-micro'
  modules: [
    'nuxt-componentsbook-module',
    'nuxt-i18n-micro',
  ],
})

Why the order matters here

  • When nuxt-i18n-micro is after the Components Book module, it will attempt to apply locale prefixes (e.g., /en/) to the already-registered Components Book routes.
  • In this particular setup, adding the prefix can break your story routes (for example, /en/componentsbook/... might conflict with how the Components Book module is generating or managing its pages).
  • By listing nuxt-componentsbook-module first and nuxt-i18n-micro second, you avoid having a locale prefix automatically prepended to the Components Book routes, which prevents potential route conflicts.

🛠 DevTools Integration

When running in development mode, a Components Book tab appears in Nuxt DevTools, providing an iframe-based UI for exploring stories.


More Resources

  • Live Demo – Explore the module in action and see various sample stories.
  • Usage Examples – View additional .stories.vue files illustrating different configurations and patterns.

You can use these references to learn more advanced usage patterns, get inspired by existing stories, and see how they integrate with the rest of your Nuxt app.

🤝 Contributing

Feel free to submit issues and pull requests to improve the module.

📜 License

MIT License

Keywords

FAQs

Package last updated on 21 Feb 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc