npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@munsonlabs/normandy

v0.1.3

Published

Mission-ready components for modern interfaces.

Readme

@munsonlabs/normandy

Modular, mission-ready UI components for modern web apps. Deploy elite interfaces with precision and control.


Overview

Normandy is a lightweight, flexible Vue 3 UI toolkit built for front-end developers who want responsive, composable, and production-grade components — without the bloat.

Inspired by its namesake, the Normandy delivers stealthy integration, high performance, and modular design for any mission-critical web app.


Features

  • Flexible and responsive modal components.
  • Customisable context menus with nested options.
  • Lightweight resizable panes for dynamic layouts.
  • Powerful drag-and-drop functionality.
  • Customisable notification system.

Installation

Install the package via npm:

npm install @munsonlabs/normandy

Or via pnpm:

pnpm add @munsonlabs/normandy

Documentation

The key information has been extracted and displayed below for your convenience. For detailed usage examples and API documentation, refer to the individual component README files:

Web Component

Normandy components can also be used as web components in non-Vue applications. You can interact with them using window event listeners. Import the ./web/autoload.js to automatically register web components or if you want more manual control ./web/index.js

Refer to the individual component documentation for examples (these more often than not accept the same payload as the composables and are access by using the component name prefixed with x- e.g. x-modal or x-context-menu):

Components

Overview

A flexible and lightweight resizable pane component for Vue 3 applications, supporting both horizontal and vertical resizing.

Features

  • Supports horizontal (x) and vertical (y) resizing.
  • Customisable styles and behaviour.
  • Lightweight and easy to integrate.
  • State management for pane visibility and size.

Basic Example

<script setup>
  import { Pane, PaneGroup } from '@munsonlabs/normandy'

  const panes = [
    { id: 'pane1', defaultSize: '50%' },
    { id: 'pane2', defaultSize: '50%' },
  ]
</script>

<template>
  <PaneGroup axis="x">
    <Pane v-for="pane in panes" :id="pane.id" :key="pane.id" :default-size="pane.defaultSize">
      <div>Content for {{ pane.id }}</div>
    </Pane>
  </PaneGroup>
</template>

Advanced Example with State Management

<script setup>
  import { Pane, PaneGroup, useGlobalPane } from '@munsonlabs/normandy'

  const { toggle, reset } = useGlobalPane()

  function togglePanes() {
    toggle()
  }

  function resetPanes() {
    reset()
  }
</script>

<template>
  <div>
    <button @click="togglePanes">
      Toggle Panes
    </button>
    <button @click="resetPanes">
      Reset Panes
    </button>
    <PaneGroup axis="y">
      <Pane id="pane1" default-size="30%">
        <div>Pane 1 Content</div>
      </Pane>
      <Pane id="pane2" default-size="70%">
        <div>Pane 2 Content</div>
      </Pane>
    </PaneGroup>
  </div>
</template>

Props

PaneGroup

| Prop | Type | Default | Description | |--------|-----------|---------|--------------------------------------| | axis | String | 'x' | Axis of resizing ('x' or 'y'). | | fill | Boolean | false | Whether the group fills its parent. | | centre | Boolean | false | Whether to centre the panes. |

Pane

| Prop | Type | Default | Description | |---------------|-----------|-----------|--------------------------------------------------| | id | String | null | Unique identifier for the pane. | | defaultSize | String | 'auto' | Default size of the pane (e.g., '50%'). | | minSize | String | '0' | Minimum size of the pane. | | maxSize | String | '100%' | Maximum size of the pane. | | centre | Boolean | false | Whether to centre the pane content. |

Customisation

You can customise the styles by overriding the CSS variables defined in the package. For example:

:root {
    --rpane-background-colour: #f9f9f9;
    --rpane-handle-surface-1: #cccccc;
    --rpane-handle-width: 8px;
    --rpane-handle-border-radius: 4px;
}

@media (prefers-colour-scheme: dark) {
    :root {
        --rpane-background-colour: #1e1e1e;
        --rpane-handle-surface-1: #444444;
        --rpane-handle-width: 8px;
        --rpane-handle-border-radius: 4px;
    }
}

Available CSS Variables

| Variable | Description | |-------------------------------|--------------------------------------------------| | --rpane-background-colour | Background colour of the pane. | | --rpane-handle-surface-1 | Colour of the handle surface. | | --rpane-handle-width | Width of the handle. | | --rpane-handle-border-radius| Border radius of the handle. |

Overview

A flexible and responsive modal component for Vue 3 applications, supporting both desktop and mobile layouts.

Features

  • Automatically adapts to mobile and desktop layouts.
  • Configurable snap thresholds for resizing.
  • Supports custom content, events, and slots.
  • Lightweight and easy to integrate.

Basic Example

<script setup>
  import { Modal } from '@munsonlabs/normandy'
  import { ref } from 'vue'

  const isModalOpen = ref(false)

  function openModal() {
    isModalOpen.value = true
  }
</script>

<template>
  <div>
    <button @click="openModal">
      Open Modal
    </button>
    <Modal v-model="isModalOpen" title="Example Modal">
      <p>This is the content of the modal.</p>
    </Modal>
  </div>
</template>

Advanced Example with useModal

<script setup>
  import { GlobalModal, useModal } from '@munsonlabs/normandy'

  const { open } = useModal()

  function openCustomModal() {
    open({
      component: CustomComponent,
      baseProps: { title: 'Custom Modal' },
      componentProps: { message: 'Hello from the modal!' },
    })
  }
</script>

<template>
  <div>
    <button @click="openCustomModal">
      Open Custom Modal
    </button>
    <GlobalModal />
  </div>
</template>

Web Component Example

	<x-modal>
		<h1>Modal Content</h1>
	</x-modal>
	<button id="show-button">Show Modal</button>
	<button id="hide-button">Hide Modal</button>
	<script type="module">
		import './web/autoload.js';

		const showButton = document.getElementById('show-button');
		const hideButton = document.getElementById('hide-button');

		showButton.addEventListener('click', () => {
			const event = new CustomEvent('modal:show');
			window.dispatchEvent(event);
		})

		hideButton.addEventListener('click', () => {
			const event = new CustomEvent('modal:hide');
			window.dispatchEvent(event);
		});
	</script>

Props

| Prop | Type | Default | Description | |-----------------|------------|-----------|--------------------------------------------------| | modelValue | Boolean | false | Controls the visibility of the modal. | | title | String | null | Title of the modal. | | position | String | 'y' | Position of the modal ('x' or 'y'). | | align | String | 'end' | Alignment of the modal ('start' or 'end'). | | snapThreshold | Array | [100] | Snap thresholds for resizing. | | closeThreshold| Number | 10 | Threshold for closing the modal. |

Slots

| Slot | Description | |------------|--------------------------------------------------| | default | Content to display inside the modal. | | dismiss | Custom dismiss button content. |

Customisation

You can customise the styles by overriding the CSS variables defined in the package. For example:

:root {
    --modal-surface-1: #ffffff;
    --modal-text-1: #333333;
    --modal-border-radius: 8px;
    --modal-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1);
}

@media (prefers-colour-scheme: dark) {
    :root {
        --modal-surface-1: #1e1e1e;
        --modal-text-1: #f5f5f5;
        --modal-border-radius: 8px;
        --modal-shadow: 0px 4px 16px rgba(0, 0, 0, 0.5);
    }
}

Available CSS Variables

| Variable | Description | |---------------------------|--------------------------------------------------| | --modal-surface-1 | Background colour of the modal. | | --modal-surface-2 | Background colour for modal header. | | --modal-surface-3 | Background colour for modal handle. | | --modal-text-1 | Primary text colour. | | --modal-text-2 | Secondary text colour. | | --modal-shadow | Shadow for the modal. | | --modal-border | Border colour of the modal. | | --modal-min-width | Minimum width of the modal. | | --modal-max-width | Maximum width of the modal. | | --modal-border-radius | Border radius of the modal. | | --modal-padding | Padding inside the modal. | | --modal-header-padding | Padding for the modal header. | | --modal-button-padding | Padding for modal buttons. | | --modal-content-padding | Padding for the modal content. | | --modal-font-family | Font family for the modal. | | --modal-font-size-1 | Font size for modal text. | | --modal-font-size-2 | Font size for modal titles. |

Overview

A customisable and lightweight context menu component for Vue 3 applications.

Features

  • Fully customisable styles and behaviour.
  • Supports nested menus and dynamic options.
  • Keyboard and mouse navigation.
  • Easy integration with Vue 3.

Basic Example

<script setup>
  import { ContextMenu, useContextMenu } from '@munsonlabs/normandy'

  const { show, hide } = useContextMenu()

  function showMenu(event) {
    show({
      event,
      attachToMouse: true,
      options: [
        { name: 'Option 1', callback: () => alert('Option 1 selected') },
        { name: 'Option 2', callback: () => alert('Option 2 selected') },
      ],
    })
  }

  function hideMenu() {
    hide() // Hides the context menu
  }
</script>

<template>
  <div>
    <button @contextmenu.prevent="showMenu($event)">
      Right Click Me
    </button>
    <button @click="hideMenu">
      Hide Menu
    </button>
    <ContextMenu />
  </div>
</template>

Nested Menu Example

<script setup>
  import { ContextMenu, useContextMenu } from '@munsonlabs/normandy'

  const { show } = useContextMenu()

  function showMenu(event) {
    show({
      event,
      attachToMouse: true,
      options: [
        { name: 'Option 1', callback: () => alert('Option 1 selected') },
        {
          name: 'More Options',
          options: [
            { name: 'Sub Option 1', callback: () => alert('Sub Option 1 selected') },
            { name: 'Sub Option 2', callback: () => alert('Sub Option 2 selected') },
          ]
        },
        { name: 'Option 3', callback: () => alert('Option 3 selected') },
      ],
    })
  }
</script>

<template>
  <div>
    <button @contextmenu.prevent="showMenu($event)">
      Right Click Me
    </button>
    <ContextMenu />
  </div>
</template>

Web Component Example

  <x-context-menu></x-context-menu>
	<button id="show-button">Show Menu</button>
	<script type="module">
		import './web/autoload.js';

		const showButton = document.getElementById('show-button');

		showButton.addEventListener('click', (evt) => {
			const event = new CustomEvent('context-menu:show', {
        detail: {
          event: evt,
          options: [
            { name: 'Option 1', callback: () => console.log('Option 1 selected') },
            { name: 'Option 2', callback: () => console.log('Option 2 selected') },
          ],
          attachToMouse: true,
          callback: (success) => console.log('Menu closed', success),
        },
      })
			window.dispatchEvent(event);
		})
	</script>

Props

| Prop | Type | Description | |----------------|------------|--------------------------------------| | options | Array | Array of menu options. | | callback | Function | Callback function for menu actions. | | attachToMouse| Boolean | If true, the menu will appear at the mouse pointer's position. |

Events

| Event | Description | |-------------|--------------------------------------| | selected | Triggered when a menu item is selected. |

Customisation

You can customise the styles by overriding the CSS variables defined in the package. For example:

/* Light Mode */
:root {
    --context-menu-surface-1: #ffffff;
    --context-menu-surface-2: #f0f0f0;
    --context-menu-text-1: #000000;
    --context-menu-text-2: #555555;
    --context-menu-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    --context-menu-border: #e0e0e0;
}

/* Dark Mode */
@media (prefers-color-scheme: dark) {
    :root {
        --context-menu-surface-1: #333333;
        --context-menu-surface-2: #444444;
        --context-menu-text-1: #ffffff;
        --context-menu-text-2: #bbbbbb;
        --context-menu-shadow: 0px 4px 6px rgba(0, 0, 0, 0.5);
        --context-menu-border: #555555;
    }
}

Available CSS Variables

| Variable | Description | |---------------------------------|--------------------------------------------------| | --context-menu-surface-1 | Background colour of the menu surface. | | --context-menu-surface-2 | Background colour for hover states. | | --context-menu-text-1 | Primary text colour. | | --context-menu-text-2 | Secondary text colour. | | --context-menu-shadow | Shadow for the menu. | | --context-menu-border | Border colour of the menu. | | --context-menu-min-width | Minimum width of the menu. | | --context-menu-border-radius | Border radius of the menu. | | --context-menu-padding | Padding inside the menu. | | --context-menu-font-family | Font family for the menu. | | --context-menu-font-size | Font size of the menu text. | | --context-menu-item-padding | Padding for menu items. | | --context-menu-item-gap | Gap between menu items. | | --context-menu-content-gap | Gap between content elements in a menu item. | | --context-menu-glyph-padding | Padding for glyphs in menu items. | | --context-menu-overlay-colour | Background colour for the menu overlay. | | --context-menu-transition-duration | Duration of menu transition animations. | | --context-menu-transition-timing-function | Timing function for menu transitions. | | --context-menu-z-index | Z-index of the menu. |

Overview

A lightweight and customisable notification system for Vue 3 applications.

Features

  • Supports multiple notification types (e.g., success, error, info).
  • Auto-dismiss functionality with customisable durations.
  • Pause, resume, and manual dismissal of notifications.
  • Fully customisable styles.

Basic Example

<script setup>
  import { Notification, useNotification } from '@munsonlabs/normandy'

  const { add } = useNotification()

  function showNotification() {
    add({ message: 'This is a notification!', type: 'success' })
  }
</script>

<template>
  <div>
    <button @click="showNotification">
      Show Notification
    </button>
    <Notification />
  </div>
</template>

Advanced Example with Auto-Dismiss and Custom Options

<script setup>
  import { Notification, useNotification } from '@munsonlabs/normandy'

  const { add } = useNotification()

  function showCustomNotification() {
    add({
      message: 'This notification will auto-dismiss in 5 seconds!',
      type: 'info',
      autoDismiss: true,
      remainingTime: 5000,
    })
  }
</script>

<template>
  <div>
    <button @click="showCustomNotification">
      Show Custom Notification
    </button>
    <Notification />
  </div>
</template>

Web Component Example

	<x-notification></x-notification>
	<button id="show-button">Show Notification</button>
	<script type="module">
		import './web/autoload.js';

		const showButton = document.getElementById('show-button');

		showButton.addEventListener('click', () => {
			const event = new CustomEvent('notification:action', {
				detail: {
          action: 'add', // add, remove, clear, pause, resume
          payload:{
            id: 1,
            message: 'This is a notification message',
            type: 'success',
            remainingTime: 3000,
            autoDismiss: true,
            forceDismiss: undefined,
          }
        }
      })
			window.dispatchEvent(event);
		});
  </script>

API

useNotification

Methods

| Method | Description | |--------------|--------------------------------------------------| | add | Adds a new notification. | | remove | Removes a notification by its ID. | | pause | Pauses all active notification timeouts. | | resume | Resumes all paused notification timeouts. |

add Options

| Option | Type | Default | Description | |----------------|-------------------|-------------|--------------------------------------------------| | id | String | Number | Auto-generated | Unique identifier for the notification. | | message | String | '' | The message to display in the notification. | | type | String | 'info' | Type of notification ('success', 'error', 'info', etc.). | | autoDismiss | Boolean | true | Whether the notification should auto-dismiss. | | remainingTime| Number | 5000 | Time in milliseconds before auto-dismiss. | | forceDismiss | String | Number | undefined | ID of a notification to dismiss before adding this one. |

Customisation

You can customise the styles by overriding the CSS variables defined in the package. For example:

:root {
    --notification-surface-1: #ffffff;
    --notification-text-1: #333333;
    --notification-border-radius: 8px;
    --notification-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1);
}

@media (prefers-colour-scheme: dark) {
    :root {
        --notification-surface-1: #1e1e1e;
        --notification-text-1: #f5f5f5;
        --notification-border-radius: 8px;
        --notification-shadow: 0px 4px 16px rgba(0, 0,
  }
}

Available CSS Variables

| Variable | Description | |-------------------------------|--------------------------------------------------| | --notification-surface-1 | Background color of the notification. | | --notification-text-1 | Primary text color. | | --notification-shadow | Shadow for the notification. | | --notification-border-radius| Border radius of the notification. | | --notification-padding | Padding inside the notification. | | --notification-font-family | Font family for the notification. | | --notification-font-size | Font size of the notification text. | | --notification-gap | Gap between notifications. |

Overview

A powerful and customisable drag-and-drop library for Vue 3 applications, supporting nested structures and advanced interactions.

Features

  • Drag-and-drop support for nested structures.
  • Customisable placeholders and drag previews.
  • Supports keyboard and mouse interactions.
  • Lightweight and easy to integrate.

Basic Example

<script setup>
  import { Draggable } from '@munsonlabs/normandy'

  const nestedItems = ref([
    {
      id: 1,
      name: 'Parent 1',
      children: [
        { id: 2, name: 'Child 1' },
        { id: 3, name: 'Child 2' },
      ],
    },
    {
      id: 4,
      name: 'Parent 2',
      children: [
        { id: 5, name: 'Child 3' },
      ],
    },
  ])
</script>

<template>
  <Draggable v-model="nestedItems">
    <template #default="{ item }">
      <div>{{ item.name }}</div>
    </template>
  </Draggable>
</template>

Advanced Example using Renderer Component

<script setup>
  import { Draggable } from '@munsonlabs/normandy'

  const dropStore = ref({
    exampleId: {
      components: [
        { id: 1, name: 'Component 1' },
        { id: 2, name: 'Component 2' },
      ],
    },
  })
  const renderer = resolveComponent('DraggableContent')
</script>

<template>
  <Draggable
    v-if="dropStore.exampleId"
    id="1"
    v-model="dropStore.exampleId.components"
    :renderer="renderer"
  />
</template>

Props

| Prop | Type | Default | Description | |--------------|------------|-----------|--------------------------------------------------| | modelValue | Array | [] | The data array to be rendered and manipulated. | | renderer | Component| null | Custom renderer component for items. | | group | String | null | Group identifier for drag-and-drop operations. |

Events

| Event | Description | |-------------|--------------------------------------------------| | update:modelValue | Triggered when the data array is updated. | | drop | Triggered when an item is dropped. |

Customisation

You can customise the styles by overriding the CSS variables defined in the package. For example:

:root {
    --placeholder-surface-1: rgba(106, 127, 233, 0.274);
    --placeholder-surface-2: rgb(73, 100, 241);
    --placeholder-border-radius: 0.3em;
}

@media (prefers-colour-scheme: dark) {
    :root {
        --placeholder-surface-1: rgba(106, 127, 233, 0.274);
        --placeholder-surface-2: rgb(73, 100, 241);
        --placeholder-border-radius: 0.3em;
    }
}

Available CSS Variables

| Variable | Description | |-------------------------------|--------------------------------------------------| | --placeholder-surface-1 | Background colour of the placeholder. | | --placeholder-surface-2 | Border colour of the placeholder. | | --placeholder-border-radius | Border radius of the placeholder. |

Development

To contribute to this package:

  1. Clone the repository.
  2. Navigate to the packages/normandy directory.
  3. Install dependencies using npm install.
  4. Run the development server with npm run dev.

License

This project is licensed under the MIT License.