@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/normandyOr via pnpm:
pnpm add @munsonlabs/normandyDocumentation
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:
- Clone the repository.
- Navigate to the
packages/normandydirectory. - Install dependencies using
npm install. - Run the development server with
npm run dev.
License
This project is licensed under the MIT License.
