@tenorlab/react-dashboard 1.6.0 → 1.6.2

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
@@ -6,35 +6,34 @@
6
6
 
7
7
  Foundation components for creating user-configurable, high-performance dashboards in React.
8
8
 
9
- ## 🏗 Relationship to Core
9
+ ## Relationship to Core
10
10
 
11
11
  This package extends **@tenorlab/dashboard-core**. It provides the React implementation of the core logic, including specialized hooks, state management via **Zustand**, and a suite of UI components.
12
12
 
13
13
  > **Note**: This package re-exports all types and utilities from `@tenorlab/dashboard-core`. You do not need to install the core package separately.
14
14
 
15
15
 
16
- ## Demos
17
-
18
- * [**React Demo**](https://react.tenorlab.com) (built with **@tenorlab/react-dashboard**)
19
- * [**Vue Demo**](https://vue.tenorlab.com) (built with **@tenorlab/vue-dashboard**)
16
+ ## Pro Template Demos
17
+ - [React Demo](https://react.tenorlab.com) (built with @tenorlab/react-dashboard)
18
+ - [Vue Demo](https://vue.tenorlab.com) (built with @tenorlab/vue-dashboard)
19
+ - [Nuxt Demo](https://nuxt.tenorlab.com) (built with @tenorlab/vue-dashboard)
20
20
 
21
21
 
22
22
  ## ✨ Features
23
23
 
24
- - **Type-Safe:** Deep integration with TypeScript 5.8+ for full IDE support.
25
- - **State Management:** Built-in `useDashboardStore` and `useDashboardUndoService`.
26
- - **User Configurable:** Ready-to-use components for adding, removing, and dragging widgets.
27
- - **Themeable:** Native support for CSS Variables and Tailwind CSS.
28
- - **Vite Optimized:** Full ESM support and tree-shakeable.
24
+ - **Type-Safe:** Deep integration with TypeScript 5.8+ for full IDE support.
25
+ - **State Management:** Built-in `useDashboardStore` and `useDashboardUndoService`.
26
+ - **User Configurable:** Ready-to-use components for adding, removing, and dragging widgets.
27
+ - **Themeable:** Native support for CSS Variables and Tailwind CSS.
28
+ - **Vite Optimized:** Full ESM support and tree-shakeable.
29
29
 
30
30
  ## 🚀 Quick Start
31
31
 
32
32
  ### Installation
33
33
 
34
- Bash
34
+ ### Installation
35
35
 
36
- ```
37
- # with npm
36
+ ```bash
38
37
  npm i @tenorlab/react-dashboard
39
38
 
40
39
  # with pnpm
@@ -45,9 +44,7 @@ pnpm add @tenorlab/react-dashboard
45
44
 
46
45
  Import the base styles in your entry file (e.g., `main.tsx`):
47
46
 
48
- TypeScript
49
-
50
- ```
47
+ ```TypeScript
51
48
  import '@tenorlab/react-dashboard/styles.css'
52
49
  ```
53
50
 
@@ -59,62 +56,57 @@ import '@tenorlab/react-dashboard/styles.css'
59
56
 
60
57
  Widgets should be organized by their loading strategy.
61
58
 
62
- - **Bundled Widgets**: Place in `src/bundled-widgets/` (loaded immediately).
63
- - **Async Widgets**: Place in `src/async-widgets/` (lazy-loaded).
59
+ - **Bundled Widgets**: Place in `src/bundled-widgets/` (loaded immediately).
60
+ - **Async Widgets**: Place in `src/async-widgets/` (lazy-loaded).
61
+
62
+ *(NOTE: These directory names are suggestions; you can use different names, or put the widgets under src/components if you prefer)*
63
+
64
64
 
65
65
  Each widget requires a sub-directory using the `widget-name-here` convention.
66
66
 
67
67
  #### Example: `WidgetTotalOrders`
68
68
 
69
- TypeScript
69
+ Directory name `widget-total-orders`, files:
70
+ - WidgetTotalOrders.tsx
71
+ - meta.ts
72
+ - index.ts
70
73
 
71
- ```
72
- // file: src/bundled-widgets/widget-total-orders/WidgetTotalOrders.tsx
73
- import {
74
- IDashboardWidget,
75
- IDashboardWidgetProps,
76
- TDashboardWidgetKey,
77
- DashboardWidgetBase,
78
- WrapperColumnContent
79
- } from '@tenorlab/react-dashboard'
80
74
 
81
- export function WidgetTotalOrders(props: IDashboardWidgetProps): IDashboardWidget {
75
+ File: src/bundled-widgets/widget-total-orders/WidgetTotalOrders.tsx:
76
+ ```react
77
+ import { IDashboardWidgetProps } from '@tenorlab/react-dashboard'
78
+ import { DashboardWidgetBase } from '@tenorlab/react-dashboard'
79
+
80
+ export function WidgetTotalOrders(props: IDashboardWidgetProps) {
82
81
  return (
83
- <DashboardWidgetBase
84
- widgetKey="WidgetTotalOrders"
85
- title="Total Orders"
86
- {...props}
87
- >
88
- <WrapperColumnContent>
82
+ <DashboardWidgetBase {...props}>
83
+ <div className="w-full flex flex-col gap-2 items-end">
89
84
  <div className="dashboard-number number-xl text-primary">1,250</div>
90
85
  <div className="text-sm">Orders this month</div>
91
- </WrapperColumnContent>
86
+ </div>
92
87
  </DashboardWidgetBase>
93
88
  )
94
89
  }
95
90
  ```
96
91
 
97
- TypeScript
98
-
99
- ```
100
- // file: src/bundled-widgets/widget-total-orders/meta.ts
92
+ File: src/bundled-widgets/widget-total-orders/meta.ts:
93
+ ```typescript
101
94
  import type { TWidgetMetaInfo } from '@tenorlab/react-dashboard'
102
- import { MonitorIcon as ComponentIcon } from '@tenorlab/react-dashboard'
95
+ import { ReceiptIcon as ComponentIcon } from 'lucide-react'
103
96
 
97
+ // Define the metadata object for the plugin
104
98
  export const WidgetTotalOrdersMeta: TWidgetMetaInfo = {
105
99
  name: 'Total Orders',
106
100
  categories: ['Widget'],
107
101
  icon: ComponentIcon,
108
102
  noDuplicatedWidgets: true,
109
103
  description: 'Displays information about your total orders.',
110
- externalDependencies: []
104
+ externalDependencies: [],
111
105
  }
112
106
  ```
113
107
 
114
- TypeScript
115
-
116
- ```
117
- // file: src/bundled-widgets/widget-total-orders/index.ts
108
+ File: src/bundled-widgets/widget-total-orders/index.ts:
109
+ ```typescript
118
110
  import { WidgetTotalOrders } from './WidgetTotalOrders'
119
111
  export default WidgetTotalOrders
120
112
  ```
@@ -123,39 +115,44 @@ export default WidgetTotalOrders
123
115
 
124
116
  Create `src/widgets-catalog.tsx` in your project root. This file manages how widgets are discovered (locally via Vite's `import.meta.glob` or remotely via CDN).
125
117
 
126
- TypeScript
118
+ File: src/widgets-catalog.ts:
119
+ ```typescript
120
+ import { WidgetContainerColumn, WidgetContainerLarge, WidgetContainerRow } from '@tenorlab/react-dashboard'
121
+ import {
122
+ createStaticEntry,
123
+ localWidgetDiscovery,
124
+ remoteWidgetDiscovery,
125
+ } from '@tenorlab/react-dashboard/core'
127
126
 
128
- ```
129
- // file: src/widgets-catalog.tsx
127
+ // other static widgets
130
128
  import {
129
+ WidgetSmallCardSample,
130
+ } from './other-widgets/other-widgets'
131
+ import { otherWidgetsMetaMap } from './other-widgets/other-widgets-meta'
132
+ import type {
131
133
  IDynamicWidgetCatalogEntry,
132
134
  TDashboardWidgetCatalog,
133
135
  TWidgetMetaInfoBase,
134
- WidgetContainerColumn,
135
- WidgetContainerLarge,
136
- WidgetContainerRow,
137
136
  TWidgetFactory,
138
137
  } from '@tenorlab/react-dashboard'
139
- import {
140
- createStaticEntry,
141
- localWidgetDiscovery,
142
- remoteWidgetDiscovery,
143
- } from '@tenorlab/react-dashboard/core'
144
-
145
- import { WidgetRecentPaymentInfo } from './other-widgets/WidgetRecentPaymentInfo'
146
- //import { getWidgetsManifestUrl } from '@/utils'
147
138
 
148
139
  const bundledWidgetsSrcPath = '/src/bundled-widgets'
149
140
  const asyncWidgetsSrcPath = '/src/async-widgets'
150
141
 
142
+ // Use Vite's Glob Import
143
+ // This creates an object where the keys are file paths, and the values are the TWidgetFactory functions.
144
+ // We target the 'index.ts' files within the widgets subdirectories.
151
145
  type TGlobModuleMap = Record<string, TWidgetFactory>
152
146
 
147
+ // Eagerly loaded (Non-lazy / Bundled):
153
148
  const bundledWidgetModules = import.meta.glob('/src/bundled-widgets/*/index.ts', {
154
- eager: true
149
+ eager: true /* we load this immediately */,
155
150
  }) as TGlobModuleMap
156
151
 
152
+ // Lazy loaded (Code-split / Plugins):
157
153
  const asyncWidgetModules = import.meta.glob('/src/async-widgets/*/index.ts') as TGlobModuleMap
158
154
 
155
+ // Meta modules (Always eager so titles/icons are available immediately)
159
156
  const allMetaModules = import.meta.glob('/src/**/widget-*/meta.ts', {
160
157
  eager: true,
161
158
  }) as Record<string, Record<string, TWidgetMetaInfoBase>>
@@ -163,33 +160,61 @@ const allMetaModules = import.meta.glob('/src/**/widget-*/meta.ts', {
163
160
  const hasPermission = (_user_: any, _permission: string) => true
164
161
 
165
162
  export const getWidgetCatalog = async (user: any | null): Promise<TDashboardWidgetCatalog> => {
163
+ // A. Register Static Core Components
166
164
  const catalogMapEntries: [string, IDynamicWidgetCatalogEntry][] = [
167
- createStaticEntry('WidgetContainer', WidgetContainerColumn),
168
- createStaticEntry('WidgetContainerRow', WidgetContainerRow),
169
- createStaticEntry('WidgetContainerLarge', WidgetContainerLarge),
165
+ // everyone has access to the containers:
166
+ createStaticEntry('WidgetContainer', WidgetContainerColumn, otherWidgetsMetaMap['WidgetContainer']),
167
+ createStaticEntry('WidgetContainerRow', WidgetContainerRow, otherWidgetsMetaMap['WidgetContainerRow']),
168
+ createStaticEntry(
169
+ 'WidgetContainerLarge',
170
+ WidgetContainerLarge,
171
+ otherWidgetsMetaMap['WidgetContainerLarge'],
172
+ ),
170
173
  ]
171
174
 
175
+ // B. Optional: Register Business Static Widgets manually:
176
+ // we could filter further by permissions and user type if needed
172
177
  if (hasPermission(user, 'some-permission')) {
173
- catalogMapEntries.push(createStaticEntry('WidgetRecentPaymentInfo', WidgetRecentPaymentInfo))
178
+ // i.e.:
179
+ // catalogMapEntries.push(
180
+ // createStaticEntry(
181
+ // 'WidgetThatRequiresPermissions',
182
+ // WidgetThatRequiresPermissions,
183
+ // otherWidgetsMetaMap['WidgetThatRequiresPermissions'],
184
+ // ),
185
+ // )
186
+ catalogMapEntries.push(
187
+ createStaticEntry(
188
+ 'WidgetSmallCardSample',
189
+ WidgetSmallCardSample,
190
+ otherWidgetsMetaMap['WidgetSmallCardSample'],
191
+ ),
192
+ )
174
193
  }
175
194
 
176
- // add bundled widgets (non-lazy)
177
- catalogMapEntries.push(...localWidgetDiscovery(
178
- bundledWidgetsSrcPath,
179
- bundledWidgetModules,
180
- allMetaModules,
181
- false, // lazy: false
182
- ))
183
-
184
- // add async-widgets (lazy)
185
- catalogMapEntries.push(...localWidgetDiscovery(
186
- asyncWidgetsSrcPath,
187
- asyncWidgetModules,
188
- allMetaModules,
189
- true, // lazy: true
190
- ))
191
-
192
- // Optional: Remote discovery of -pre-built widgets hosted on a CDN
195
+ // C. Register widgets automatically with the localWidgetDiscovery helper:
196
+ // (bundled widgets are included always, non-lazy)
197
+ catalogMapEntries.push(
198
+ ...localWidgetDiscovery(
199
+ bundledWidgetsSrcPath,
200
+ bundledWidgetModules,
201
+ allMetaModules,
202
+ false, // lazy: false
203
+ )
204
+ )
205
+
206
+ // D. Register "lazy" widgets automatically with the localWidgetDiscovery helper:
207
+ // (async widgets are not incuded, they are lazy loaded at run time)
208
+ catalogMapEntries.push(
209
+ ...localWidgetDiscovery(
210
+ asyncWidgetsSrcPath,
211
+ asyncWidgetModules,
212
+ allMetaModules,
213
+ true, // lazy: true
214
+ )
215
+ )
216
+
217
+ // E. Optional: Remote discovery of -pre-built widgets hosted on a CDN (requires advance importMaps setup and other configuration)
193
218
  /*const manifestUrl = getWidgetsManifestUrl()
194
219
  if (manifestUrl.length > 0) {
195
220
  const remoteResponse = await remoteWidgetDiscovery(manifestUrl)
@@ -206,26 +231,26 @@ export const getWidgetCatalog = async (user: any | null): Promise<TDashboardWidg
206
231
 
207
232
  Use a `dashboard-defaults.ts` file to define initial layouts based on user roles.
208
233
 
209
- TypeScript
210
-
211
- ```
212
- // file: src/dashboard-defaults.ts
213
- import {
234
+ File: src/dashboard-defaults.ts:
235
+ ```typescript
236
+ import { blankDashboardConfig, cssSettingsCatalog } from '@tenorlab/react-dashboard/core'
237
+ import { getWidgetCatalog } from './widgets-catalog'
238
+ import type {
214
239
  TDashboardWidgetKey,
215
240
  IChildWidgetConfigEntry,
216
241
  IDashboardConfig,
217
242
  TDashboardWidgetCatalog,
218
243
  } from '@tenorlab/react-dashboard'
219
- import { blankDashboardConfig, cssSettingsCatalog } from '@tenorlab/react-dashboard/core'
220
- import { getWidgetCatalog } from './widgets-catalog'
221
244
 
245
+ // reserved identifier to be used only for the default dashboard
222
246
  const DEFAULT_DASHBOARD_ID = 'default' as const
223
247
  const DEFAULT_DASHBOARD_NAME = 'Default' as const
224
248
 
225
- const getDefaultDashboardForCustomerUser = (
249
+ // default dashboard config for Regular user type
250
+ const getDefaultDashboardForRegularUser = (
226
251
  user: any,
227
252
  clientAppKey: string,
228
- availableWidgetKeys: TDashboardWidgetKey[]
253
+ availableWidgetKeys: TDashboardWidgetKey[],
229
254
  ): IDashboardConfig => {
230
255
  const userID = user.userID || 0
231
256
  return {
@@ -234,10 +259,21 @@ const getDefaultDashboardForCustomerUser = (
234
259
  dashboardId: DEFAULT_DASHBOARD_ID,
235
260
  dashboardName: DEFAULT_DASHBOARD_NAME,
236
261
  zoomScale: 1,
237
- responsiveGrid: false,
238
- widgets: ['WidgetContainer_container1'],
262
+ responsiveGrid: true,
263
+ widgets: [
264
+ 'WidgetContainer_container1', // will contain other widgets specified in the childWidgetsConfig secitno below
265
+ 'WidgetBarGradients',
266
+ ],
239
267
  childWidgetsConfig: [
240
- { parentWidgetKey: 'WidgetContainer_container1', widgetKey: 'WidgetRecentPaymentInfo' }
268
+ // two widgets go into container1:
269
+ {
270
+ parentWidgetKey: 'WidgetContainer_container1',
271
+ widgetKey: 'WidgetTotalOrders'
272
+ },
273
+ {
274
+ parentWidgetKey: 'WidgetContainer_container1',
275
+ widgetKey: 'WidgetTotalOrders'
276
+ }
241
277
  ],
242
278
  cssSettings: [...cssSettingsCatalog]
243
279
  }
@@ -252,10 +288,16 @@ export const getDashboardDefaults = async (
252
288
  }> => {
253
289
  const widgetsCatalog = await getWidgetCatalog(user)
254
290
 
255
- if (!user) return { dashboardConfig: blankDashboardConfig, widgetsCatalog }
291
+ if (!user) {
292
+ return {
293
+ dashboardConfig: blankDashboardConfig,
294
+ widgetsCatalog,
295
+ }
296
+ }
256
297
 
257
298
  return {
258
- dashboardConfig: getDefaultDashboardForCustomerUser(user, clientAppKey, [...widgetsCatalog.keys()]),
299
+ // Optional, you could use different routines depending on user role:
300
+ dashboardConfig: getDefaultDashboardForRegularUser(user, clientAppKey, [...widgetsCatalog.keys()]),
259
301
  widgetsCatalog
260
302
  }
261
303
  }
@@ -265,32 +307,33 @@ export const getDashboardDefaults = async (
265
307
 
266
308
  Use this for a simplified, non-editable view of the dashboard.
267
309
 
268
- TypeScript
269
-
270
- ```
271
- // file: src/views/DashboardReadonly.tsx
310
+ File: src/views/DashboardReadonly.tsx:
311
+ ```react
272
312
  import { useEffect, useState } from 'react'
273
- import {
274
- IDashboardConfig,
275
- TDashboardWidgetCatalog,
276
- useDashboardStore,
277
- DynamicWidgetLoader,
278
- DashboardGrid
279
- } from '@tenorlab/react-dashboard'
313
+ import { useDashboardStore } from '@tenorlab/react-dashboard'
280
314
  import {
281
315
  blankDashboardConfig,
282
316
  cssVarsUtils,
283
317
  useDashboardStorageService,
284
318
  } from '@tenorlab/react-dashboard/core'
319
+ import { DynamicWidgetLoader, DashboardGrid } from '@tenorlab/react-dashboard'
285
320
  import { getDashboardDefaults } from '../dashboard-defaults'
321
+ import type { IDashboardConfig, TDashboardWidgetCatalog } from '@tenorlab/react-dashboard'
322
+
286
323
 
287
324
  export function DashboardReadonly() {
288
325
  const clientAppKey = 'myclientapp'
289
326
  const user = { id: 1234 }
327
+ const userId = user.id
290
328
  const dashboardStore = useDashboardStore()
291
329
  const dashboardStorageService = useDashboardStorageService()
330
+
292
331
  const { isLoading, currentDashboardConfig } = dashboardStore
332
+ const getTargetContainerKey = () => dashboardStore.targetContainerKey
293
333
 
334
+ // default dashboard config
335
+ const [_defaultDashboardConfig, setDefaultDashboardConfig] =
336
+ useState<IDashboardConfig>(blankDashboardConfig)
294
337
  const [widgetsCatalog, setWidgetsCatalog] = useState<TDashboardWidgetCatalog>(new Map())
295
338
 
296
339
  useEffect(() => {
@@ -306,9 +349,11 @@ export function DashboardReadonly() {
306
349
  )
307
350
 
308
351
  dashboardStore.setAllDashboardConfigs(savedConfigs)
352
+ // show default dashboard or first dashboard
309
353
  const activeConfig = savedConfigs[0] || defaults.dashboardConfig
310
354
  dashboardStore.setCurrentDashboardConfig(activeConfig)
311
355
  setWidgetsCatalog(defaults.widgetsCatalog)
356
+ setDefaultDashboardConfig(defaults.dashboardConfig)
312
357
  cssVarsUtils.restoreCssVarsFromSettings(activeConfig.cssSettings || [])
313
358
  } finally {
314
359
  dashboardStore.setIsLoading(false)
@@ -319,7 +364,8 @@ export function DashboardReadonly() {
319
364
 
320
365
  return (
321
366
  <div className="relative flex flex-col h-full">
322
- {isLoading ? <div>Loading</div> : (
367
+ {isLoading && <div>Loading</div>}
368
+ {!isLoading && (
323
369
  <DashboardGrid
324
370
  isEditing={false}
325
371
  zoomScale={Number(currentDashboardConfig.zoomScale)}
@@ -329,11 +375,16 @@ export function DashboardReadonly() {
329
375
  <DynamicWidgetLoader
330
376
  key={`${widgetKey}_${index}`}
331
377
  widgetKey={widgetKey}
378
+ parentWidgetKey={undefined}
379
+ targetContainerKey={getTargetContainerKey()}
332
380
  index={index}
333
381
  maxIndex={currentDashboardConfig.widgets.length - 1}
334
382
  childWidgetsConfig={currentDashboardConfig.childWidgetsConfig}
335
383
  widgetCatalog={widgetsCatalog as any}
336
384
  isEditing={false}
385
+ onRemoveClick={() => {}}
386
+ onMoveClick={() => {}}
387
+ selectContainer={() => {}}
337
388
  />
338
389
  ))}
339
390
  </DashboardGrid>
@@ -344,11 +395,9 @@ export function DashboardReadonly() {
344
395
  ```
345
396
 
346
397
 
347
-
348
398
  #### 5. Full Editable Dashboard
349
399
 
350
- For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**, and **Multiple Dashboards**, please refer to the [Full Implementation Example](https://github.com/tenorlab/react-dashboard-sample/blob/main/views/DashboardFullExample.tsx).
351
-
400
+ For editable dashboard examples, including **Undo/Redo**, **Zooming**, **Catalog Flyouts**, and **Multiple Dashboards**, please refer to the [Pro Template](https://www.tenorlab.com).
352
401
 
353
402
 
354
403
  ------
@@ -357,15 +406,15 @@ For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**
357
406
 
358
407
  ### UI Components
359
408
 
360
- - **`DashboardGrid`**: The main layout engine for positioning widgets.
361
- - **`WidgetContainer`**: Wrapper providing common widget UI (headers, actions, loading states).
362
- - **`WidgetsCatalogFlyout`**: A draggable panel for users to browse and add new widgets.
363
- - **`DynamicWidgetLoader`**: The core widget loader component.
409
+ - **`DashboardGrid`**: The main dashboard layout that position widgets within a responsive grid.
410
+ - **`WidgetContainer`**: A special "widget" that is a container for other widgets.
411
+ - **`WidgetsCatalogFlyout`**: A slide-out panel for users to browse and add new widgets on editable dashboards.
412
+ - **`DynamicWidgetLoader`**: The core lazy-loading widget loader that renders the widgets within the grid.
364
413
 
365
414
  ### Hooks & State
366
415
 
367
- - **`useDashboardStore`**: Access the underlying Zustand store to manage widget state, layout, and configuration.
368
- - **`useDashboardUndoService`**: Provides `undo` and `redo` functionality for user layout changes.
416
+ - **`useDashboardStore`**: Access the underlying reactive store to manage widget state, layout, and configuration.
417
+ - **`useDashboardUndoService`**: Provides `undo` and `redo` functionality for user layout changes in editable dashboard (optional).
369
418
 
370
419
 
371
420
  ------
@@ -373,13 +422,19 @@ For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**
373
422
 
374
423
  ## Links
375
424
 
425
+ ### Open source core packages
376
426
  - [@tenorlab/react-dashboard](https://www.npmjs.com/package/@tenorlab/react-dashboard): React-specific components
377
427
  - [@tenorlab/vue-dashboard](https://www.npmjs.com/package/@tenorlab/vue-dashboard): Vue-specific components
378
- - [Official Website](https://www.tenorlab.com)
379
- - [React Demo](https://react.tenorlab.com)
380
- - [Vue Demo](https://vue.tenorlab.com)
381
- - [Get the Pro Template](https://payhip.com/b/gPBpo)
428
+
429
+ ### Pro Template Demos
430
+ - [React Demo](https://react.tenorlab.com) (built with @tenorlab/react-dashboard)
431
+ - [Vue Demo](https://vue.tenorlab.com) (built with @tenorlab/vue-dashboard)
432
+ - [Nuxt Demo](https://nuxt.tenorlab.com) (built with @tenorlab/vue-dashboard)
433
+
434
+ ### Others
435
+ - [Buy a License](https://payhip.com/b/gPBpo)
382
436
  - [Follow on BlueSky](https://bsky.app/profile/tenorlab.bsky.social)
437
+ - [Official Website](https://www.tenorlab.com)
383
438
 
384
439
 
385
440
  ------
@@ -387,7 +442,7 @@ For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**
387
442
 
388
443
  ## ⚖️ Licensing & Usage
389
444
 
390
- **@tenorlab/react-dashboard** is [MIT licensed](https://opensource.org/licenses/MIT).
445
+ **@tenorlab/vue-dashboard** is [MIT licensed](https://opensource.org/licenses/MIT).
391
446
 
392
447
  It provides the foundational components and logic for building dashboards. You are free to use it in any project, personal or commercial.
393
448
 
@@ -395,10 +450,7 @@ It provides the foundational components and logic for building dashboards. You a
395
450
 
396
451
  A commercial license for a full-blown professional app template is available for purchase [**here**](https://www.tenorlab.com) and comes with:
397
452
 
398
- * **Full Application Shell:** A clean, optimized Vite + TypeScript project structure (with either React or Vue).
453
+ * **Full Application Shell:** A clean, optimized Vite + TypeScript project structure (with either React, Vue or Nuxt).
399
454
  * **Dashboard Management:** Production-ready logic for creating, listing, renaming, and deleting multiple user-defined dashboards.
400
- * **Implementation Examples:** Expert patterns for both "Read-Only" (Analyst view) and "User-Editable" (Admin view) dashboard modes.
455
+ * **Implementation Examples:** Best patterns for both "Read-Only" (Analyst view) and "User-Editable" (Admin view) dashboard modes, a dynamic dashboard menu, etc.
401
456
  * **Tenorlab Theme Engine:** A sophisticated Tailwind-based system supporting multiple custom themes (not just Light/Dark mode).
402
-
403
-
404
- [**Live React Demo**](https://react.tenorlab.com), [**Live Vue Demo**](https://vue.tenorlab.com)
package/dist/core.d.ts CHANGED
@@ -275,6 +275,9 @@ export declare interface IDashboardWidgetPropsBase<TExtraProps = any> {
275
275
  size?: TWidgetSize;
276
276
  borderCssClasses?: string;
277
277
  backgroundCssClasses?: string;
278
+ addCssClasses?: string;
279
+ overrideCssClasses?: string;
280
+ tags?: string[];
278
281
  hideTitle?: boolean;
279
282
  noShadow?: boolean;
280
283
  noBorder?: boolean;
package/dist/core.es.js CHANGED
@@ -106,16 +106,12 @@ const W = [
106
106
  else {
107
107
  l.forEach((d) => {
108
108
  d.value = (d.value || "").replace(/NaN/g, "");
109
- const c = a.cssSettings.find(
110
- (g) => g.key === d.key
111
- );
109
+ const c = a.cssSettings.find((g) => g.key === d.key);
112
110
  c && (Object.keys(c).forEach((g) => {
113
111
  g in d || (d[g] = c[g]);
114
112
  }), d.step = c.step, d.minValue = c.minValue, d.defaultValue = c.defaultValue, d.defaultUnit = c.defaultUnit, /\d+/g.test(d.value) === !1 && (d.value = c ? c.value : "1.0rem"));
115
113
  });
116
- const o = a.cssSettings.filter((d) => !l.some(
117
- (c) => c.key === d.key
118
- ));
114
+ const o = a.cssSettings.filter((d) => !l.some((c) => c.key === d.key));
119
115
  s.cssSettings = [...l, ...o];
120
116
  }
121
117
  s.widgets = s.widgets.filter(
@@ -172,16 +168,12 @@ const W = [
172
168
  (r) => r.parentWidgetKey === i
173
169
  );
174
170
  if (!a || a.length === 0)
175
- return t.widgets = t.widgets.filter(
176
- (r) => r !== i
177
- ), !1;
171
+ return t.widgets = t.widgets.filter((r) => r !== i), !1;
178
172
  }
179
173
  return !0;
180
174
  }), t;
181
175
  }, x = (e) => {
182
- const t = e.widgets.filter(
183
- (a) => a.includes("WidgetContainer")
184
- ), i = {};
176
+ const t = e.widgets.filter((a) => a.includes("WidgetContainer")), i = {};
185
177
  return t.forEach((a, r) => {
186
178
  const n = `${a.split("_container")[0]}_container${r + 1}`;
187
179
  i[a] = n;
@@ -394,9 +386,7 @@ const W = [
394
386
  updatedDashboardConfig: o
395
387
  };
396
388
  } else {
397
- const n = (e.widgets || []).filter(
398
- (l) => `${l}`.trim().toLowerCase() !== a
399
- ), s = e.childWidgetsConfig.filter(
389
+ const n = (e.widgets || []).filter((l) => `${l}`.trim().toLowerCase() !== a), s = e.childWidgetsConfig.filter(
400
390
  (l) => `${l.parentWidgetKey}`.trim().toLowerCase() !== a
401
391
  );
402
392
  return {
@@ -214,6 +214,9 @@ export declare interface IDashboardWidgetPropsBase<TExtraProps = any> {
214
214
  size?: TWidgetSize;
215
215
  borderCssClasses?: string;
216
216
  backgroundCssClasses?: string;
217
+ addCssClasses?: string;
218
+ overrideCssClasses?: string;
219
+ tags?: string[];
217
220
  hideTitle?: boolean;
218
221
  noShadow?: boolean;
219
222
  noBorder?: boolean;