@nova-design-system/nova-vue 3.12.0 → 3.14.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,20 +5,18 @@
5
5
  - [Nova Components Vue](#nova-components-vue)
6
6
  - [Key Features](#key-features)
7
7
  - [Installation](#installation)
8
- - [Setup Using Tailwind (Recommended)](#setup-using-tailwind-recommended)
8
+ - [Setting up Tailwind](#setting-up-tailwind)
9
+ - [About Tailwind and the Nova Plugin](#about-tailwind-and-the-nova-plugin)
9
10
  - [1. Install Tailwind CSS and the Vite Plugin](#1-install-tailwind-css-and-the-vite-plugin)
10
11
  - [2. Configure the Vite Plugin](#2-configure-the-vite-plugin)
11
12
  - [3. Create `tailwind.config.ts`](#3-create-tailwindconfigts)
12
13
  - [4. Configure Tailwind and Nova Plugin in `main.css`](#4-configure-tailwind-and-nova-plugin-in-maincss)
13
14
  - [5. Register NovaComponents and include the Nova Tokens](#5-register-novacomponents-and-include-the-nova-tokens)
14
- - [6. Use Nova Components and Tailwind Utilities](#6-use-nova-components-and-tailwind-utilities)
15
+ - [6. Use Nova Components with Tailwind Utilities](#6-use-nova-components-with-tailwind-utilities)
15
16
  - [7. Setup the Nova Font](#7-setup-the-nova-font)
16
17
  - [Creating Your Own Style Components with Tailwind](#creating-your-own-style-components-with-tailwind)
17
- - [Setup Without Tailwind (Not Recommended)](#setup-without-tailwind-not-recommended)
18
- - [1. Register NovaComponents and include the Nova CSS.](#1-register-novacomponents-and-include-the-nova-css)
19
- - [2. Use Nova Components](#2-use-nova-components)
20
18
  - [Nova Font Pro Integration](#nova-font-pro-integration)
21
- - [Option 1: Import in Main Entry (Recommended)](#option-1-import-in-main-entry-recommended)
19
+ - [Option 1: Import in Global CSS (Recommended)](#option-1-import-in-global-css-recommended)
22
20
  - [Option 2: HTML Integration](#option-2-html-integration)
23
21
 
24
22
 
@@ -27,7 +25,7 @@
27
25
  ## Key Features
28
26
 
29
27
  - **Lightweight Integration**: Leverage Nova Web Components with minimal configuration in Vue.
30
- - **Customizable Styling**: Use Tailwind (recommended) or Nova’s utility classes to quickly style components.
28
+ - **Customizable Styling**: Use Tailwind’s utility classes with the Nova Tailwind theme and plugin for token-driven styling and layouts.
31
29
  - **Dark Mode Ready**: Toggle dark mode by adding the `dark` class to your `body` element.
32
30
  - **Nova Font Pro Support**: Easily integrate Nova’s custom font for a consistent design experience.
33
31
 
@@ -48,19 +46,31 @@ yarn add @nova-design-system/nova-webcomponents @nova-design-system/nova-base @n
48
46
  ```
49
47
 
50
48
  > In some case, you might experience SSL certificate issues when working on Developers' VM. As documented in the [Developers' setup guide](https://wiki.eliagroup.eu/spaces/EAing/pages/89296007/2.3.3.10+Developer+Setup#id-2.3.3.10DeveloperSetup-NPMconfig), you need to turn off the SSL certificate verification:
49
+ >
50
+ > ```bash
51
+ > npm config set strict-ssl false
52
+ > ```
51
53
 
52
- ```bash
53
- npm config set strict-ssl false
54
- ```
54
+ ---
55
55
 
56
- ## Setup Using Tailwind (Recommended)
56
+ ## Setting up Tailwind
57
57
 
58
- We highly recommend using Tailwind CSS for styling, as it ensures an optimized bundle size and a powerful utility-first workflow. Nova offers a dedicated Tailwind plugin and theme, allowing you to seamlessly integrate Nova’s design tokens with Tailwind’s utility classes for a consistent and efficient styling workflow.
58
+ Nova Vue requires Tailwind CSS for styling. Tailwind provides a powerful utility-first workflow and an optimized bundle size. Nova includes a dedicated Tailwind theme and plugin that map Nova’s design tokens to Tailwind’s theme and utilities, enabling consistent, token-driven styling across your app.
59
59
 
60
60
  > **Tailwind Version**
61
61
  > This guide is written for Tailwind v4. While compatible with v3, some features may not work as expected.
62
62
 
63
- Below is an example setup using the **vue cli with vite**. If you're using another framework, such as nuxt, please refer to the [Tailwind Installation Guide](https://tailwindcss.com/docs/installation).
63
+ ### About Tailwind and the Nova Plugin
64
+
65
+ - **What is Tailwind?** A utility-first CSS framework with low-level, composable classes (flex, grid, spacing, color, typography) to rapidly build UIs.
66
+ - **Nova Tokens**: Nova ships design tokens as CSS variables (via the Spark and Ocean themes) covering colors, spacing, typography, radii, shadows, and more.
67
+ - **Integration**:
68
+ - `novaTailwindTheme` wires Nova tokens into Tailwind’s theme scales.
69
+ - The Nova Tailwind plugin exposes utilities and variants that reference those tokens, so your Tailwind classes resolve to Nova’s token values at runtime.
70
+ - **Why import tokens CSS?** Import one token CSS file (`spark.css` or `ocean.css`) so the underlying CSS variables exist at runtime. The Tailwind utilities generated by the plugin read from these variables.
71
+ - **Do not mix with legacy utilities**: When using Tailwind, do not import `@nova-design-system/nova-base/dist/css/nova-utils.css` to avoid redundant CSS and larger bundles.
72
+
73
+ Below is an example setup using the **Vue CLI with Vite**. If you're using another framework, such as Nuxt, please refer to the [Tailwind Installation Guide](https://tailwindcss.com/docs/installation).
64
74
 
65
75
  ### 1. Install Tailwind CSS and the Vite Plugin
66
76
 
@@ -92,7 +102,6 @@ export default defineConfig({
92
102
  },
93
103
  },
94
104
  })
95
-
96
105
  ```
97
106
 
98
107
  ### 3. Create `tailwind.config.ts`
@@ -110,7 +119,7 @@ export default {
110
119
 
111
120
  ### 4. Configure Tailwind and Nova Plugin in `main.css`
112
121
 
113
- in `src/assets/main.css`:
122
+ In `src/assets/main.css`:
114
123
 
115
124
  ```css
116
125
  @import 'tailwindcss';
@@ -125,18 +134,18 @@ in `src/assets/main.css`:
125
134
  > **Dark Mode**
126
135
  > To enable dark mode, add the `dark` class to the `<body>` element.
127
136
 
128
-
129
137
  ### 5. Register NovaComponents and include the Nova Tokens
130
- Register the Nova Components Vue plugin in your `main.ts` file, and include the nova tokens (Spark or Ocean theme) css file:
131
138
 
132
- ```typescript
139
+ Register the Nova Components Vue plugin in your `main.ts` file, and include exactly one of the Nova tokens (Spark or Ocean) CSS files:
140
+
141
+ ```ts
133
142
  import './assets/main.css'
134
- import '@nova-design-system/nova-base/dist/css/spark.css'; // or ocean.css
143
+ import '@nova-design-system/nova-base/dist/css/spark.css' // or ocean.css
135
144
 
136
145
  import { createApp } from 'vue'
137
146
  import App from './App.vue'
138
147
  import router from './router'
139
- import { NovaComponents } from '@nova-design-system/nova-vue/plugin';
148
+ import { NovaComponents } from '@nova-design-system/nova-vue/plugin'
140
149
 
141
150
  const app = createApp(App)
142
151
 
@@ -146,14 +155,16 @@ app.use(NovaComponents)
146
155
  app.mount('#app')
147
156
  ```
148
157
 
149
- ### 6. Use Nova Components and Tailwind Utilities
158
+ > When using Tailwind, do not import `@nova-design-system/nova-base/dist/css/nova-utils.css`.
150
159
 
151
- ```html
160
+ ### 6. Use Nova Components with Tailwind Utilities
161
+
162
+ ```vue
152
163
  <script setup lang="ts">
153
- import { ref } from 'vue';
164
+ import { ref } from 'vue'
154
165
  import { NvButton } from '@nova-design-system/nova-vue'
155
166
 
156
- const count = ref(0);
167
+ const count = ref(0)
157
168
  </script>
158
169
 
159
170
  <template>
@@ -165,7 +176,7 @@ const count = ref(0);
165
176
  </template>
166
177
  ```
167
178
 
168
- > Note: **We have full typescript and intellisense support for Nova components.** If you do not see the autocomplete options, make sure you uninstall Volar or Vetur, and use the [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) Extension only.
179
+ > Note: **We have full TypeScript and IntelliSense support for Nova components.** If you do not see autocomplete options, uninstall Volar/Vetur and use the [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) extension only.
169
180
 
170
181
  ### 7. Setup the Nova Font
171
182
 
@@ -186,23 +197,23 @@ If you find you’re repeating the same set of utility classes for certain UI el
186
197
 
187
198
  Then in your markup, instead of:
188
199
 
189
- ```jsx
200
+ ```html
190
201
  <div class="bg-gray-50 dark:bg-gray-500 p-4 rounded-md shadow-sm">
191
- {/* Content */}
202
+ <!-- Content -->
192
203
  </div>
193
204
  <div class="bg-gray-50 dark:bg-gray-500 p-4 rounded-md shadow-sm">
194
- {/* Content */}
205
+ <!-- Content -->
195
206
  </div>
196
207
  ```
197
208
 
198
209
  You can use your new `card` class:
199
210
 
200
- ```jsx
211
+ ```html
201
212
  <div class="card">
202
- {/* Content */}
213
+ <!-- Content -->
203
214
  </div>
204
215
  <div class="card">
205
- {/* Content */}
216
+ <!-- Content -->
206
217
  </div>
207
218
  ```
208
219
 
@@ -210,82 +221,19 @@ This ensures consistent styling and keeps your markup clean. Any colors or spaci
210
221
 
211
222
  ---
212
223
 
213
- ## Setup Without Tailwind (Not Recommended)
214
-
215
- If you don’t plan to use Tailwind, Nova provides a large utility CSS file for quick prototyping. Be aware that this approach will increase your CSS bundle size, offer less options, and lacks the flexibility and optimizations of Tailwind.
216
-
217
- ### 1. Register NovaComponents and include the Nova CSS.
218
-
219
- After installing the nova packages, you'll need to include the CSS for the theme and the utils. Then you register the Nova Components Vue plugin.
220
-
221
- In your `main.ts` file:
222
-
223
- ```typescript
224
- import './assets/main.css'
225
- import '@nova-design-system/nova-base/dist/css/nova-utils.css';
226
- import '@nova-design-system/nova-base/dist/css/spark.css'; // or ocean.css
227
-
228
- import { createApp } from 'vue'
229
- import App from './App.vue'
230
- import router from './router'
231
- import { NovaComponents } from '@nova-design-system/nova-vue/plugin';
232
-
233
- const app = createApp(App)
234
-
235
- app.use(router)
236
- app.use(NovaComponents)
237
-
238
- app.mount('#app')
239
- ```
240
-
241
- ### 2. Use Nova Components
242
-
243
- ```html
244
- <script setup lang="ts">
245
- import { ref } from 'vue';
246
- import { NvButton } from '@nova-design-system/nova-vue'
247
-
248
- const count = ref(0);
249
- </script>
250
-
251
- <template>
252
- <div class="flex items-center justify-center">
253
- <NvButton danger @click="count++">
254
- Count is {{ count }}
255
- </NvButton>
256
- </div>
257
- </template>
258
- ```
259
-
260
- ---
261
-
262
224
  ## Nova Font Pro Integration
263
225
 
264
226
  > [!WARNING]
265
227
  > Nova Fonts is a protected asset and is not included in the Nova Base package. You need to include the Nova Fonts CSS file in your project.
266
- > To get the Nova Fonts URL, please contact us via Teams or get the URL in the Nova Design System [internal wiki](https://dev.azure.com/elia-digitization/Nova/_wiki/wikis/Nova.wiki/30245/Nova-Font-Pro).
267
-
268
- Once you have the URL, you can integrate it using any of these methods:
269
-
270
- ### Option 1: Import in Main Entry (Recommended)
271
- In your `main.ts`:
228
+ > To get the Nova Fonts URL, **please contact us via Teams** or get the URL in the Nova Design System [internal wiki](https://dev.azure.com/elia-digitization/Nova/_wiki/wikis/Nova.wiki/30245/Nova-Font-Pro).
272
229
 
273
- ```typescript
274
- import './assets/main.css'
275
- import '@nova-design-system/nova-base/dist/css/spark.css'; // or ocean.css
276
- import 'https://novaassets.azureedge.net/fonts/nova-fonts-pro.css';
230
+ Once you have the URL, you can integrate it using any of these methods:
277
231
 
278
- import { createApp } from 'vue'
279
- import App from './App.vue'
280
- import router from './router'
281
- import { NovaComponents } from '@nova-design-system/nova-vue/plugin';
232
+ ### Option 1: Import in Global CSS (Recommended)
233
+ In your `src/assets/main.css`:
282
234
 
283
- const app = createApp(App)
284
-
285
- app.use(router)
286
- app.use(NovaComponents)
287
-
288
- app.mount('#app')
235
+ ```css
236
+ @import url('contact-us-for-URL/nova-fonts-pro.css');
289
237
  ```
290
238
 
291
239
  ### Option 2: HTML Integration
@@ -295,7 +243,7 @@ In your `index.html`:
295
243
  <!DOCTYPE html>
296
244
  <html>
297
245
  <head>
298
- <link rel="stylesheet" href="https://novaassets.azureedge.net/fonts/nova-fonts-pro.css">
246
+ <link rel="stylesheet" href="contact-us-for-URL/nova-fonts-pro.css">
299
247
  </head>
300
248
  <body>
301
249
  <div id="app"></div>
@@ -55,6 +55,8 @@ export declare const NvIconbutton: import("vue").DefineSetupFnComponent<JSX.NvIc
55
55
  export declare const NvLoader: import("vue").DefineSetupFnComponent<JSX.NvLoader & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvLoader & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
56
56
  export declare const NvMenu: import("vue").DefineSetupFnComponent<JSX.NvMenu & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvMenu & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
57
57
  export declare const NvMenuitem: import("vue").DefineSetupFnComponent<JSX.NvMenuitem & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvMenuitem & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
58
+ export declare const NvNotification: import("vue").DefineSetupFnComponent<JSX.NvNotification & import("./vue-component-lib/utils").InputProps<boolean>, {}, {}, JSX.NvNotification & import("./vue-component-lib/utils").InputProps<boolean> & {}, import("vue").PublicProps>;
59
+ export declare const NvNotificationcontainer: import("vue").DefineSetupFnComponent<JSX.NvNotificationcontainer & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvNotificationcontainer & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
58
60
  export declare const NvPopover: import("vue").DefineSetupFnComponent<JSX.NvPopover & import("./vue-component-lib/utils").InputProps<boolean>, {}, {}, JSX.NvPopover & import("./vue-component-lib/utils").InputProps<boolean> & {}, import("vue").PublicProps>;
59
61
  export declare const NvRow: import("vue").DefineSetupFnComponent<JSX.NvRow & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvRow & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
60
62
  export declare const NvStack: import("vue").DefineSetupFnComponent<JSX.NvStack & import("./vue-component-lib/utils").InputProps<string | number | boolean>, {}, {}, JSX.NvStack & import("./vue-component-lib/utils").InputProps<string | number | boolean> & {}, import("vue").PublicProps>;
@@ -99,6 +99,8 @@ export const NvCalendar = /*@__PURE__*/ defineContainer('nv-calendar', undefined
99
99
  'showActions',
100
100
  'shortcuts',
101
101
  'showWeekNumbers',
102
+ 'cancelLabel',
103
+ 'primaryLabel',
102
104
  'singleDateChange',
103
105
  'rangeDateChange',
104
106
  'valueChanged'
@@ -532,6 +534,21 @@ export const NvMenuitem = /*@__PURE__*/ defineContainer('nv-menuitem', undefined
532
534
  'name',
533
535
  'menuitemSelected'
534
536
  ]);
537
+ export const NvNotification = /*@__PURE__*/ defineContainer('nv-notification', undefined, [
538
+ 'uid',
539
+ 'feedback',
540
+ 'emphasis',
541
+ 'heading',
542
+ 'message',
543
+ 'icon',
544
+ 'dismissible',
545
+ 'hidden',
546
+ 'initiallyHidden',
547
+ 'hiddenChanged'
548
+ ], 'hidden', 'hidden-changed');
549
+ export const NvNotificationcontainer = /*@__PURE__*/ defineContainer('nv-notificationcontainer', undefined, [
550
+ 'position'
551
+ ]);
535
552
  export const NvPopover = /*@__PURE__*/ defineContainer('nv-popover', undefined, [
536
553
  'triggerElement',
537
554
  'open',
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './generated/components';
2
2
  export * from './plugin';
3
+ export * from './providers';
3
4
  export * from '@nova-design-system/nova-webcomponents/constants';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './generated/components';
2
2
  export * from './plugin';
3
+ export * from './providers';
3
4
  export * from '@nova-design-system/nova-webcomponents/constants';
package/dist/plugin.d.ts CHANGED
@@ -8,9 +8,19 @@
8
8
  * By providing custom implementations, we ensure that event names are
9
9
  * properly transformed, allowing Stencil components to work seamlessly in Vue.
10
10
  */
11
- import { Plugin } from 'vue';
11
+ import { App } from 'vue';
12
+ import { type NotificationServiceOptions } from './providers/NotificationService';
12
13
  /**
13
- * This is the Vue plugin that is used to define the custom elements and event
14
- * handlers.
14
+ * Configuration options for the NovaComponents plugin.
15
15
  */
16
- export declare const NovaComponents: Plugin;
16
+ export interface NovaComponentsOptions {
17
+ /** Notification service configuration options. */
18
+ notifications?: NotificationServiceOptions;
19
+ }
20
+ /**
21
+ * This is the Vue plugin that is used to define the custom elements, event
22
+ * handlers, and includes the notification service.
23
+ */
24
+ export declare const NovaComponents: {
25
+ install(app: App, options?: NovaComponentsOptions): Promise<void>;
26
+ };
package/dist/plugin.js CHANGED
@@ -9,6 +9,7 @@
9
9
  * properly transformed, allowing Stencil components to work seamlessly in Vue.
10
10
  */
11
11
  import { defineCustomElements } from '@nova-design-system/nova-webcomponents/loader';
12
+ import { NvNotificationService, } from './providers/NotificationService';
12
13
  /**
13
14
  * Transforms the event name to kebab-case.
14
15
  * @param {string} eventName - The name of the event.
@@ -39,15 +40,18 @@ const ael = (el, eventName, cb, opts) => el.addEventListener(transformEventName(
39
40
  */
40
41
  const rel = (el, eventName, cb, opts) => el.removeEventListener(transformEventName(eventName), cb, opts);
41
42
  /**
42
- * This is the Vue plugin that is used to define the custom elements and event
43
- * handlers.
43
+ * This is the Vue plugin that is used to define the custom elements, event
44
+ * handlers, and includes the notification service.
44
45
  */
45
46
  export const NovaComponents = {
46
- async install() {
47
+ async install(app, options = {}) {
48
+ // Define custom elements with event handling
47
49
  defineCustomElements(window, {
48
50
  ce: (eventName, opts) => new CustomEvent(transformEventName(eventName), opts),
49
51
  ael,
50
52
  rel,
51
53
  });
54
+ // Install notification service
55
+ NvNotificationService.install(app, options.notifications);
52
56
  },
53
57
  };
@@ -0,0 +1,84 @@
1
+ import { App, type Ref, type Component } from 'vue';
2
+ import { NotificationEmphasis, FeedbackColors, NotificationPosition } from '../index';
3
+ /**
4
+ * Action callbacks for notifications. Will render buttons automatically.
5
+ */
6
+ export interface NotificationAction {
7
+ /** The label of the action. */
8
+ label: string;
9
+ /** The callback to execute when the action is clicked. */
10
+ onClick: () => void;
11
+ }
12
+ /**
13
+ * Options for creating a notification.
14
+ */
15
+ export interface NotificationOptions {
16
+ /** Unique identifier for the notification. If not provided, one will be generated. */
17
+ id?: string;
18
+ /** Short and concise text for the notification heading. */
19
+ heading?: string;
20
+ /** Main content of the notification. */
21
+ message?: string;
22
+ /** Whether the notification can be manually dismissed via close button. */
23
+ dismissible?: boolean;
24
+ /** Adjusts the emphasis to make the notification more or less visually
25
+ * prominent to users. Use this to draw attention to important actions or
26
+ * reduce focus on less critical ones */
27
+ emphasis?: `${NotificationEmphasis}`;
28
+ /** Type of the notification, used to determine the color and default icon. */
29
+ feedback?: `${FeedbackColors}`;
30
+ /** Custom icon name to override the default icon. */
31
+ icon?: string;
32
+ /** Notification actions */
33
+ actions?: NotificationAction[];
34
+ /** Custom components for the notification actions. */
35
+ actionSlot?: Component;
36
+ }
37
+ /**
38
+ * A notification with all required fields populated.
39
+ */
40
+ export interface Notification extends NotificationOptions {
41
+ /** Timestamp when the notification was created. */
42
+ createdAt: number;
43
+ }
44
+ /**
45
+ * Context value provided by the NotificationService.
46
+ */
47
+ export interface NotificationContextValue {
48
+ /** Reactive array of active notifications. */
49
+ notifications: Ref<Notification[]>;
50
+ /** Function to show a new notification. Returns the notification ID. */
51
+ show: (options: NotificationOptions) => string;
52
+ /** Function to dismiss a specific notification by ID. This will remove the
53
+ * notification after the animation completes */
54
+ dismiss: (id: string) => void;
55
+ /** Function to immediately remove a specific notification by ID. */
56
+ remove: (id: string) => void;
57
+ /** Function to remove all active notifications immediately. */
58
+ removeAll: () => void;
59
+ }
60
+ /**
61
+ * Configuration options for the NotificationService plugin.
62
+ */
63
+ export interface NotificationServiceOptions {
64
+ /** Position of the notification container on the screen. */
65
+ position?: `${NotificationPosition}`;
66
+ /** Maximum number of notifications to display at once. */
67
+ maxNotifications?: number;
68
+ /** Additional CSS class name for the notification container. */
69
+ className?: string;
70
+ }
71
+ /**
72
+ * Vue plugin for Nova notifications.
73
+ */
74
+ export declare const NvNotificationService: {
75
+ install(app: App, options?: NotificationServiceOptions): void;
76
+ };
77
+ /**
78
+ * Composable to use notifications in any component.
79
+ *
80
+ * @returns {NotificationContextValue} The notification context
81
+ * @throws {Error} If used outside of an app with NvNotificationService installed
82
+ */
83
+ export declare const useNotifications: () => NotificationContextValue;
84
+ export default NvNotificationService;
@@ -0,0 +1,215 @@
1
+ /* eslint-disable jsdoc/require-jsdoc */
2
+ import { ref, createApp, h, } from 'vue';
3
+ import { NvButton, NvNotification, NvNotificationcontainer, } from '../generated/components';
4
+ /**
5
+ * Utility function to generate unique IDs.
6
+ *
7
+ * @returns {string} A unique identifier string
8
+ */
9
+ const generateId = () => {
10
+ return `notification-${Date.now()}-${Math.random()
11
+ .toString(36)
12
+ .substr(2, 9)}`;
13
+ };
14
+ /**
15
+ * Utility function to unwrap a notification element from a ref.
16
+ * @param {NvNotificationRef} el The notification element ref
17
+ * @returns {HTMLNvNotificationElement | null} The notification element
18
+ */
19
+ const unwrapNotificationEl = (el) => {
20
+ if (!el)
21
+ return null;
22
+ return '$el' in el ? el.$el : el;
23
+ };
24
+ class NotificationManager {
25
+ notifications = ref([]);
26
+ options;
27
+ containerApp = null;
28
+ constructor(options = {}) {
29
+ this.options = {
30
+ position: options.position || 'top-right',
31
+ maxNotifications: options.maxNotifications || 50,
32
+ className: options.className || '',
33
+ };
34
+ this.createContainer();
35
+ }
36
+ // keep track of all notification refs by id
37
+ elRefs = new Map();
38
+ // small helper to bind/unbind refs per id
39
+ setElRef = (id) => (el) => {
40
+ if (el)
41
+ this.elRefs.set(id, el);
42
+ else
43
+ this.elRefs.delete(id);
44
+ };
45
+ createContainer() {
46
+ // Create container element
47
+ const containerEl = document.createElement('div');
48
+ containerEl.id = 'nova-notification-container';
49
+ document.body.appendChild(containerEl);
50
+ // Create Vue app for the container
51
+ this.containerApp = createApp({
52
+ setup: () => {
53
+ return () => {
54
+ const items = this.notifications.value.map((notification) => {
55
+ const hasCustomActions = (notification.actions && notification.actions.length > 0) ||
56
+ !!notification.actionSlot;
57
+ const actionChildren = [];
58
+ notification.actions?.forEach((action) => {
59
+ actionChildren.push(h(NvButton, { onClick: action.onClick, emphasis: 'low', size: 'sm' }, { default: () => action.label }));
60
+ });
61
+ if (notification.actionSlot) {
62
+ actionChildren.push(h(notification.actionSlot));
63
+ }
64
+ const actionContainer = hasCustomActions
65
+ ? h('div', { slot: 'actions' }, actionChildren)
66
+ : null;
67
+ return h(NvNotification, {
68
+ key: notification.id,
69
+ ref: this.setElRef(notification.id),
70
+ heading: notification.heading,
71
+ message: notification.message,
72
+ dismissible: notification.dismissible,
73
+ emphasis: notification.emphasis,
74
+ feedback: notification.feedback,
75
+ icon: notification.icon,
76
+ initiallyHidden: true,
77
+ onHiddenChanged: (event) => {
78
+ if (event.detail)
79
+ this.handleNotificationClose(notification.id);
80
+ },
81
+ }, {
82
+ default: () => (actionContainer ? [actionContainer] : null),
83
+ });
84
+ });
85
+ return h(NvNotificationcontainer, {
86
+ position: this.options.position,
87
+ class: this.options.className,
88
+ 'data-testid': 'notification-container',
89
+ }, {
90
+ // ✅ function slot avoids the warning
91
+ default: () => items,
92
+ });
93
+ };
94
+ },
95
+ });
96
+ this.containerApp.mount(containerEl);
97
+ }
98
+ handleNotificationClose(id) {
99
+ this.remove(id);
100
+ }
101
+ /**
102
+ * Show a new notification.
103
+ *
104
+ * @param {NotificationOptions} options - The notification options
105
+ * @returns {string} The notification ID
106
+ */
107
+ show = (options) => {
108
+ const id = options.id || generateId();
109
+ const notification = {
110
+ id,
111
+ heading: options.heading,
112
+ message: options.message,
113
+ dismissible: options.dismissible ?? true,
114
+ emphasis: options.emphasis ?? 'medium',
115
+ feedback: options.feedback ?? 'information',
116
+ actions: options.actions ?? [],
117
+ actionSlot: options.actionSlot,
118
+ icon: options.icon,
119
+ createdAt: Date.now(),
120
+ };
121
+ // Remove oldest notifications if we exceed max
122
+ const newNotifications = [notification, ...this.notifications.value];
123
+ if (newNotifications.length > this.options.maxNotifications) {
124
+ this.notifications.value = newNotifications.slice(0, this.options.maxNotifications);
125
+ }
126
+ else {
127
+ this.notifications.value = newNotifications;
128
+ }
129
+ setTimeout(() => {
130
+ const ref = this.elRefs.get(id);
131
+ const el = unwrapNotificationEl(ref);
132
+ el?.show();
133
+ }, 0);
134
+ return id;
135
+ };
136
+ /**
137
+ * Dismiss a specific notification by ID. This will remove the notification
138
+ * after the animation completes.
139
+ *
140
+ * @param {string} id - The notification ID to dismiss
141
+ */
142
+ dismiss = (id) => {
143
+ const ref = this.elRefs.get(id);
144
+ const el = unwrapNotificationEl(ref);
145
+ el?.dismiss();
146
+ };
147
+ /**
148
+ * Immediately remove a specific notification by ID.
149
+ *
150
+ * @param {string} id - The notification ID to dismiss
151
+ */
152
+ remove = (id) => {
153
+ this.notifications.value = this.notifications.value.filter((notification) => notification.id !== id);
154
+ this.elRefs.delete(id);
155
+ };
156
+ /**
157
+ * Clear all active notifications.
158
+ */
159
+ removeAll = () => {
160
+ this.notifications.value = [];
161
+ };
162
+ /**
163
+ * Get the current context value.
164
+ *
165
+ * @returns {NotificationContextValue} The context value
166
+ */
167
+ getContextValue = () => ({
168
+ notifications: this.notifications,
169
+ show: this.show,
170
+ remove: this.remove,
171
+ dismiss: this.dismiss,
172
+ removeAll: this.removeAll,
173
+ });
174
+ /**
175
+ * Destroy the notification manager and clean up resources.
176
+ */
177
+ destroy() {
178
+ if (this.containerApp) {
179
+ this.containerApp.unmount();
180
+ this.containerApp = null;
181
+ }
182
+ const containerEl = document.getElementById('nova-notification-container');
183
+ if (containerEl) {
184
+ document.body.removeChild(containerEl);
185
+ }
186
+ }
187
+ }
188
+ // Global notification manager instance
189
+ let notificationManager = null;
190
+ /**
191
+ * Vue plugin for Nova notifications.
192
+ */
193
+ export const NvNotificationService = {
194
+ install(app, options = {}) {
195
+ notificationManager = new NotificationManager(options);
196
+ // Provide global property
197
+ app.config.globalProperties.$notifications =
198
+ notificationManager.getContextValue();
199
+ // Provide for composition API
200
+ app.provide('notifications', notificationManager.getContextValue());
201
+ },
202
+ };
203
+ /**
204
+ * Composable to use notifications in any component.
205
+ *
206
+ * @returns {NotificationContextValue} The notification context
207
+ * @throws {Error} If used outside of an app with NvNotificationService installed
208
+ */
209
+ export const useNotifications = () => {
210
+ if (!notificationManager) {
211
+ throw new Error('useNotifications must be used in an app with NovaComponents installed. Use app.use(NovaComponents) in your main.ts');
212
+ }
213
+ return notificationManager.getContextValue();
214
+ };
215
+ export default NvNotificationService;
@@ -0,0 +1,2 @@
1
+ export { NvNotificationService, useNotifications, default as NotificationService, } from './NotificationService';
2
+ export type { NotificationOptions, Notification, NotificationContextValue, NotificationServiceOptions, } from './NotificationService';
@@ -0,0 +1 @@
1
+ export { NvNotificationService, useNotifications, default as NotificationService, } from './NotificationService';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nova-design-system/nova-vue",
3
- "version": "3.12.0",
3
+ "version": "3.14.0-beta.0",
4
4
  "description": "Nova is a design system created by Elia Group to empower creators to efficiently build solutions that people love to use.",
5
5
  "author": "Elia Group",
6
6
  "homepage": "https://nova.eliagroup.io",
@@ -31,7 +31,9 @@
31
31
  },
32
32
  "scripts": {
33
33
  "build": "npm run tsc",
34
+ "build:watch": "npm run tsc:watch",
34
35
  "tsc": "tsc -p . --outDir ./dist",
36
+ "tsc:watch": "tsc -p . --outDir ./dist --watch",
35
37
  "storybook": "storybook dev -p 6008",
36
38
  "storybook.build": "storybook build -o ../../storybook-static/vue",
37
39
  "clean": "rimraf dist lib/generated",
@@ -41,7 +43,7 @@
41
43
  "@nova-design-system/nova-webcomponents": "*"
42
44
  },
43
45
  "devDependencies": {
44
- "vue": "3.4.36",
46
+ "vue": "3.5.17",
45
47
  "nova-utils": "*",
46
48
  "nova-storybook-utils": "*"
47
49
  }