@tenorlab/react-dashboard 1.5.45 → 1.6.1
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/LICENSE +21 -43
- package/README.md +186 -135
- package/dist/react-dashboard.es.js +1 -1
- package/package.json +5 -7
package/LICENSE
CHANGED
|
@@ -1,43 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
redistribution of the Software's source code—whether in its original form
|
|
23
|
-
or as a modified derivative work—is STRICTLY PROHIBITED.
|
|
24
|
-
|
|
25
|
-
You may not include this Software, or any portion thereof, in any:
|
|
26
|
-
- Software Development Kit (SDK)
|
|
27
|
-
- UI Component Library or Framework
|
|
28
|
-
- Website/Dashboard Starter Template or Boilerplate
|
|
29
|
-
- "Dashboard Builder" or similar tool intended for use by other developers.
|
|
30
|
-
|
|
31
|
-
3. DERIVATIVE WORKS & MODIFICATIONS
|
|
32
|
-
You are permitted to modify and extend the Software for the sole purpose of
|
|
33
|
-
building a specific "End Product" application. Modifying the code does not
|
|
34
|
-
transfer ownership of the underlying logic, nor does it grant permission
|
|
35
|
-
to bypass the Commercial License requirement or the Redistribution Restrictions.
|
|
36
|
-
|
|
37
|
-
4. TERMINATION
|
|
38
|
-
Failure to comply with these terms automatically terminates your right to
|
|
39
|
-
use the Software. Tenorlab reserves all rights not expressly granted herein.
|
|
40
|
-
|
|
41
|
-
5. CONTACT & INQUIRIES
|
|
42
|
-
For licensing questions or professional inquiries, please visit:
|
|
43
|
-
https://www.tenorlab.com
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tenorlab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
# @tenorlab/react-dashboard
|
|
2
2
|
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.tenorlab.com)
|
|
5
|
+
[](https://react.dev/)
|
|
6
6
|
|
|
7
|
-
Foundation components for creating user-configurable, high-performance dashboards in React.
|
|
7
|
+
Foundation components for creating user-configurable, high-performance dashboards in React.
|
|
8
8
|
|
|
9
|
-
##
|
|
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
|
-
|
|
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)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
## ✨ Features
|
|
22
23
|
|
|
23
|
-
- **Type-Safe:** Deep integration with TypeScript 5.8+ for full IDE support.
|
|
24
|
-
- **State Management:** Built-in `useDashboardStore` and `useDashboardUndoService`.
|
|
25
|
-
- **User Configurable:** Ready-to-use components for adding, removing, and dragging widgets.
|
|
26
|
-
- **Themeable:** Native support for CSS Variables and Tailwind CSS.
|
|
27
|
-
- **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.
|
|
28
29
|
|
|
29
30
|
## 🚀 Quick Start
|
|
30
31
|
|
|
31
32
|
### Installation
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
### Installation
|
|
34
35
|
|
|
35
|
-
```
|
|
36
|
-
# with npm
|
|
36
|
+
```bash
|
|
37
37
|
npm i @tenorlab/react-dashboard
|
|
38
38
|
|
|
39
39
|
# with pnpm
|
|
@@ -44,9 +44,7 @@ pnpm add @tenorlab/react-dashboard
|
|
|
44
44
|
|
|
45
45
|
Import the base styles in your entry file (e.g., `main.tsx`):
|
|
46
46
|
|
|
47
|
-
TypeScript
|
|
48
|
-
|
|
49
|
-
```
|
|
47
|
+
```TypeScript
|
|
50
48
|
import '@tenorlab/react-dashboard/styles.css'
|
|
51
49
|
```
|
|
52
50
|
|
|
@@ -58,62 +56,57 @@ import '@tenorlab/react-dashboard/styles.css'
|
|
|
58
56
|
|
|
59
57
|
Widgets should be organized by their loading strategy.
|
|
60
58
|
|
|
61
|
-
- **Bundled Widgets**: Place in `src/bundled-widgets/` (loaded immediately).
|
|
62
|
-
- **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
|
+
|
|
63
64
|
|
|
64
65
|
Each widget requires a sub-directory using the `widget-name-here` convention.
|
|
65
66
|
|
|
66
67
|
#### Example: `WidgetTotalOrders`
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
Directory name `widget-total-orders`, files:
|
|
70
|
+
- WidgetTotalOrders.tsx
|
|
71
|
+
- meta.ts
|
|
72
|
+
- index.ts
|
|
69
73
|
|
|
70
|
-
```
|
|
71
|
-
// file: src/bundled-widgets/widget-total-orders/WidgetTotalOrders.tsx
|
|
72
|
-
import {
|
|
73
|
-
IDashboardWidget,
|
|
74
|
-
IDashboardWidgetProps,
|
|
75
|
-
TDashboardWidgetKey,
|
|
76
|
-
DashboardWidgetBase,
|
|
77
|
-
WrapperColumnContent
|
|
78
|
-
} from '@tenorlab/react-dashboard'
|
|
79
74
|
|
|
80
|
-
|
|
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) {
|
|
81
81
|
return (
|
|
82
|
-
<DashboardWidgetBase
|
|
83
|
-
|
|
84
|
-
title="Total Orders"
|
|
85
|
-
{...props}
|
|
86
|
-
>
|
|
87
|
-
<WrapperColumnContent>
|
|
82
|
+
<DashboardWidgetBase {...props}>
|
|
83
|
+
<div className="w-full flex flex-col gap-2 items-end">
|
|
88
84
|
<div className="dashboard-number number-xl text-primary">1,250</div>
|
|
89
85
|
<div className="text-sm">Orders this month</div>
|
|
90
|
-
</
|
|
86
|
+
</div>
|
|
91
87
|
</DashboardWidgetBase>
|
|
92
88
|
)
|
|
93
89
|
}
|
|
94
90
|
```
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
// file: src/bundled-widgets/widget-total-orders/meta.ts
|
|
92
|
+
File: src/bundled-widgets/widget-total-orders/meta.ts:
|
|
93
|
+
```typescript
|
|
100
94
|
import type { TWidgetMetaInfo } from '@tenorlab/react-dashboard'
|
|
101
|
-
import {
|
|
95
|
+
import { ReceiptIcon as ComponentIcon } from 'lucide-react'
|
|
102
96
|
|
|
97
|
+
// Define the metadata object for the plugin
|
|
103
98
|
export const WidgetTotalOrdersMeta: TWidgetMetaInfo = {
|
|
104
99
|
name: 'Total Orders',
|
|
105
100
|
categories: ['Widget'],
|
|
106
101
|
icon: ComponentIcon,
|
|
107
102
|
noDuplicatedWidgets: true,
|
|
108
103
|
description: 'Displays information about your total orders.',
|
|
109
|
-
externalDependencies: []
|
|
104
|
+
externalDependencies: [],
|
|
110
105
|
}
|
|
111
106
|
```
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
// file: src/bundled-widgets/widget-total-orders/index.ts
|
|
108
|
+
File: src/bundled-widgets/widget-total-orders/index.ts:
|
|
109
|
+
```typescript
|
|
117
110
|
import { WidgetTotalOrders } from './WidgetTotalOrders'
|
|
118
111
|
export default WidgetTotalOrders
|
|
119
112
|
```
|
|
@@ -122,39 +115,44 @@ export default WidgetTotalOrders
|
|
|
122
115
|
|
|
123
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).
|
|
124
117
|
|
|
125
|
-
|
|
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'
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
// file: src/widgets-catalog.tsx
|
|
127
|
+
// other static widgets
|
|
129
128
|
import {
|
|
129
|
+
WidgetSmallCardSample,
|
|
130
|
+
} from './other-widgets/other-widgets'
|
|
131
|
+
import { otherWidgetsMetaMap } from './other-widgets/other-widgets-meta'
|
|
132
|
+
import type {
|
|
130
133
|
IDynamicWidgetCatalogEntry,
|
|
131
134
|
TDashboardWidgetCatalog,
|
|
132
135
|
TWidgetMetaInfoBase,
|
|
133
|
-
WidgetContainerColumn,
|
|
134
|
-
WidgetContainerLarge,
|
|
135
|
-
WidgetContainerRow,
|
|
136
136
|
TWidgetFactory,
|
|
137
137
|
} from '@tenorlab/react-dashboard'
|
|
138
|
-
import {
|
|
139
|
-
createStaticEntry,
|
|
140
|
-
localWidgetDiscovery,
|
|
141
|
-
remoteWidgetDiscovery,
|
|
142
|
-
} from '@tenorlab/react-dashboard/core'
|
|
143
|
-
|
|
144
|
-
import { WidgetRecentPaymentInfo } from './other-widgets/WidgetRecentPaymentInfo'
|
|
145
|
-
//import { getWidgetsManifestUrl } from '@/utils'
|
|
146
138
|
|
|
147
139
|
const bundledWidgetsSrcPath = '/src/bundled-widgets'
|
|
148
140
|
const asyncWidgetsSrcPath = '/src/async-widgets'
|
|
149
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.
|
|
150
145
|
type TGlobModuleMap = Record<string, TWidgetFactory>
|
|
151
146
|
|
|
147
|
+
// Eagerly loaded (Non-lazy / Bundled):
|
|
152
148
|
const bundledWidgetModules = import.meta.glob('/src/bundled-widgets/*/index.ts', {
|
|
153
|
-
eager: true
|
|
149
|
+
eager: true /* we load this immediately */,
|
|
154
150
|
}) as TGlobModuleMap
|
|
155
151
|
|
|
152
|
+
// Lazy loaded (Code-split / Plugins):
|
|
156
153
|
const asyncWidgetModules = import.meta.glob('/src/async-widgets/*/index.ts') as TGlobModuleMap
|
|
157
154
|
|
|
155
|
+
// Meta modules (Always eager so titles/icons are available immediately)
|
|
158
156
|
const allMetaModules = import.meta.glob('/src/**/widget-*/meta.ts', {
|
|
159
157
|
eager: true,
|
|
160
158
|
}) as Record<string, Record<string, TWidgetMetaInfoBase>>
|
|
@@ -162,33 +160,61 @@ const allMetaModules = import.meta.glob('/src/**/widget-*/meta.ts', {
|
|
|
162
160
|
const hasPermission = (_user_: any, _permission: string) => true
|
|
163
161
|
|
|
164
162
|
export const getWidgetCatalog = async (user: any | null): Promise<TDashboardWidgetCatalog> => {
|
|
163
|
+
// A. Register Static Core Components
|
|
165
164
|
const catalogMapEntries: [string, IDynamicWidgetCatalogEntry][] = [
|
|
166
|
-
|
|
167
|
-
createStaticEntry('
|
|
168
|
-
createStaticEntry('
|
|
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
|
+
),
|
|
169
173
|
]
|
|
170
174
|
|
|
175
|
+
// B. Optional: Register Business Static Widgets manually:
|
|
176
|
+
// we could filter further by permissions and user type if needed
|
|
171
177
|
if (hasPermission(user, 'some-permission')) {
|
|
172
|
-
|
|
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
|
+
)
|
|
173
193
|
}
|
|
174
194
|
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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)
|
|
192
218
|
/*const manifestUrl = getWidgetsManifestUrl()
|
|
193
219
|
if (manifestUrl.length > 0) {
|
|
194
220
|
const remoteResponse = await remoteWidgetDiscovery(manifestUrl)
|
|
@@ -205,26 +231,26 @@ export const getWidgetCatalog = async (user: any | null): Promise<TDashboardWidg
|
|
|
205
231
|
|
|
206
232
|
Use a `dashboard-defaults.ts` file to define initial layouts based on user roles.
|
|
207
233
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
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 {
|
|
213
239
|
TDashboardWidgetKey,
|
|
214
240
|
IChildWidgetConfigEntry,
|
|
215
241
|
IDashboardConfig,
|
|
216
242
|
TDashboardWidgetCatalog,
|
|
217
243
|
} from '@tenorlab/react-dashboard'
|
|
218
|
-
import { blankDashboardConfig, cssSettingsCatalog } from '@tenorlab/react-dashboard/core'
|
|
219
|
-
import { getWidgetCatalog } from './widgets-catalog'
|
|
220
244
|
|
|
245
|
+
// reserved identifier to be used only for the default dashboard
|
|
221
246
|
const DEFAULT_DASHBOARD_ID = 'default' as const
|
|
222
247
|
const DEFAULT_DASHBOARD_NAME = 'Default' as const
|
|
223
248
|
|
|
224
|
-
|
|
249
|
+
// default dashboard config for Regular user type
|
|
250
|
+
const getDefaultDashboardForRegularUser = (
|
|
225
251
|
user: any,
|
|
226
252
|
clientAppKey: string,
|
|
227
|
-
availableWidgetKeys: TDashboardWidgetKey[]
|
|
253
|
+
availableWidgetKeys: TDashboardWidgetKey[],
|
|
228
254
|
): IDashboardConfig => {
|
|
229
255
|
const userID = user.userID || 0
|
|
230
256
|
return {
|
|
@@ -233,10 +259,21 @@ const getDefaultDashboardForCustomerUser = (
|
|
|
233
259
|
dashboardId: DEFAULT_DASHBOARD_ID,
|
|
234
260
|
dashboardName: DEFAULT_DASHBOARD_NAME,
|
|
235
261
|
zoomScale: 1,
|
|
236
|
-
responsiveGrid:
|
|
237
|
-
widgets: [
|
|
262
|
+
responsiveGrid: true,
|
|
263
|
+
widgets: [
|
|
264
|
+
'WidgetContainer_container1', // will contain other widgets specified in the childWidgetsConfig secitno below
|
|
265
|
+
'WidgetBarGradients',
|
|
266
|
+
],
|
|
238
267
|
childWidgetsConfig: [
|
|
239
|
-
|
|
268
|
+
// two widgets go into container1:
|
|
269
|
+
{
|
|
270
|
+
parentWidgetKey: 'WidgetContainer_container1',
|
|
271
|
+
widgetKey: 'WidgetTotalOrders'
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
parentWidgetKey: 'WidgetContainer_container1',
|
|
275
|
+
widgetKey: 'WidgetTotalOrders'
|
|
276
|
+
}
|
|
240
277
|
],
|
|
241
278
|
cssSettings: [...cssSettingsCatalog]
|
|
242
279
|
}
|
|
@@ -251,10 +288,16 @@ export const getDashboardDefaults = async (
|
|
|
251
288
|
}> => {
|
|
252
289
|
const widgetsCatalog = await getWidgetCatalog(user)
|
|
253
290
|
|
|
254
|
-
if (!user)
|
|
291
|
+
if (!user) {
|
|
292
|
+
return {
|
|
293
|
+
dashboardConfig: blankDashboardConfig,
|
|
294
|
+
widgetsCatalog,
|
|
295
|
+
}
|
|
296
|
+
}
|
|
255
297
|
|
|
256
298
|
return {
|
|
257
|
-
|
|
299
|
+
// Optional, you could use different routines depending on user role:
|
|
300
|
+
dashboardConfig: getDefaultDashboardForRegularUser(user, clientAppKey, [...widgetsCatalog.keys()]),
|
|
258
301
|
widgetsCatalog
|
|
259
302
|
}
|
|
260
303
|
}
|
|
@@ -264,32 +307,33 @@ export const getDashboardDefaults = async (
|
|
|
264
307
|
|
|
265
308
|
Use this for a simplified, non-editable view of the dashboard.
|
|
266
309
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
```
|
|
270
|
-
// file: src/views/DashboardReadonly.tsx
|
|
310
|
+
File: src/views/DashboardReadonly.tsx:
|
|
311
|
+
```react
|
|
271
312
|
import { useEffect, useState } from 'react'
|
|
272
|
-
import {
|
|
273
|
-
IDashboardConfig,
|
|
274
|
-
TDashboardWidgetCatalog,
|
|
275
|
-
useDashboardStore,
|
|
276
|
-
DynamicWidgetLoader,
|
|
277
|
-
DashboardGrid
|
|
278
|
-
} from '@tenorlab/react-dashboard'
|
|
313
|
+
import { useDashboardStore } from '@tenorlab/react-dashboard'
|
|
279
314
|
import {
|
|
280
315
|
blankDashboardConfig,
|
|
281
316
|
cssVarsUtils,
|
|
282
317
|
useDashboardStorageService,
|
|
283
318
|
} from '@tenorlab/react-dashboard/core'
|
|
319
|
+
import { DynamicWidgetLoader, DashboardGrid } from '@tenorlab/react-dashboard'
|
|
284
320
|
import { getDashboardDefaults } from '../dashboard-defaults'
|
|
321
|
+
import type { IDashboardConfig, TDashboardWidgetCatalog } from '@tenorlab/react-dashboard'
|
|
322
|
+
|
|
285
323
|
|
|
286
324
|
export function DashboardReadonly() {
|
|
287
325
|
const clientAppKey = 'myclientapp'
|
|
288
326
|
const user = { id: 1234 }
|
|
327
|
+
const userId = user.id
|
|
289
328
|
const dashboardStore = useDashboardStore()
|
|
290
329
|
const dashboardStorageService = useDashboardStorageService()
|
|
330
|
+
|
|
291
331
|
const { isLoading, currentDashboardConfig } = dashboardStore
|
|
332
|
+
const getTargetContainerKey = () => dashboardStore.targetContainerKey
|
|
292
333
|
|
|
334
|
+
// default dashboard config
|
|
335
|
+
const [_defaultDashboardConfig, setDefaultDashboardConfig] =
|
|
336
|
+
useState<IDashboardConfig>(blankDashboardConfig)
|
|
293
337
|
const [widgetsCatalog, setWidgetsCatalog] = useState<TDashboardWidgetCatalog>(new Map())
|
|
294
338
|
|
|
295
339
|
useEffect(() => {
|
|
@@ -305,9 +349,11 @@ export function DashboardReadonly() {
|
|
|
305
349
|
)
|
|
306
350
|
|
|
307
351
|
dashboardStore.setAllDashboardConfigs(savedConfigs)
|
|
352
|
+
// show default dashboard or first dashboard
|
|
308
353
|
const activeConfig = savedConfigs[0] || defaults.dashboardConfig
|
|
309
354
|
dashboardStore.setCurrentDashboardConfig(activeConfig)
|
|
310
355
|
setWidgetsCatalog(defaults.widgetsCatalog)
|
|
356
|
+
setDefaultDashboardConfig(defaults.dashboardConfig)
|
|
311
357
|
cssVarsUtils.restoreCssVarsFromSettings(activeConfig.cssSettings || [])
|
|
312
358
|
} finally {
|
|
313
359
|
dashboardStore.setIsLoading(false)
|
|
@@ -318,7 +364,8 @@ export function DashboardReadonly() {
|
|
|
318
364
|
|
|
319
365
|
return (
|
|
320
366
|
<div className="relative flex flex-col h-full">
|
|
321
|
-
{isLoading
|
|
367
|
+
{isLoading && <div>Loading</div>}
|
|
368
|
+
{!isLoading && (
|
|
322
369
|
<DashboardGrid
|
|
323
370
|
isEditing={false}
|
|
324
371
|
zoomScale={Number(currentDashboardConfig.zoomScale)}
|
|
@@ -328,11 +375,16 @@ export function DashboardReadonly() {
|
|
|
328
375
|
<DynamicWidgetLoader
|
|
329
376
|
key={`${widgetKey}_${index}`}
|
|
330
377
|
widgetKey={widgetKey}
|
|
378
|
+
parentWidgetKey={undefined}
|
|
379
|
+
targetContainerKey={getTargetContainerKey()}
|
|
331
380
|
index={index}
|
|
332
381
|
maxIndex={currentDashboardConfig.widgets.length - 1}
|
|
333
382
|
childWidgetsConfig={currentDashboardConfig.childWidgetsConfig}
|
|
334
383
|
widgetCatalog={widgetsCatalog as any}
|
|
335
384
|
isEditing={false}
|
|
385
|
+
onRemoveClick={() => {}}
|
|
386
|
+
onMoveClick={() => {}}
|
|
387
|
+
selectContainer={() => {}}
|
|
336
388
|
/>
|
|
337
389
|
))}
|
|
338
390
|
</DashboardGrid>
|
|
@@ -343,11 +395,9 @@ export function DashboardReadonly() {
|
|
|
343
395
|
```
|
|
344
396
|
|
|
345
397
|
|
|
346
|
-
|
|
347
398
|
#### 5. Full Editable Dashboard
|
|
348
399
|
|
|
349
|
-
For
|
|
350
|
-
|
|
400
|
+
For editable dashboard examples, including **Undo/Redo**, **Zooming**, **Catalog Flyouts**, and **Multiple Dashboards**, please refer to the [Pro Template](https://www.tenorlab.com).
|
|
351
401
|
|
|
352
402
|
|
|
353
403
|
------
|
|
@@ -356,28 +406,35 @@ For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**
|
|
|
356
406
|
|
|
357
407
|
### UI Components
|
|
358
408
|
|
|
359
|
-
- **`DashboardGrid`**: The main layout
|
|
360
|
-
- **`WidgetContainer`**:
|
|
361
|
-
- **`WidgetsCatalogFlyout`**: A slide-out panel for users to browse and add new widgets.
|
|
362
|
-
- **`DynamicWidgetLoader`**:
|
|
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.
|
|
363
413
|
|
|
364
414
|
### Hooks & State
|
|
365
415
|
|
|
366
|
-
- **`useDashboardStore`**: Access the underlying
|
|
367
|
-
- **`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).
|
|
368
418
|
|
|
369
419
|
|
|
370
420
|
------
|
|
371
421
|
|
|
372
422
|
|
|
373
423
|
## Links
|
|
424
|
+
|
|
425
|
+
### Open source core packages
|
|
374
426
|
- [@tenorlab/react-dashboard](https://www.npmjs.com/package/@tenorlab/react-dashboard): React-specific components
|
|
375
427
|
- [@tenorlab/vue-dashboard](https://www.npmjs.com/package/@tenorlab/vue-dashboard): Vue-specific components
|
|
376
|
-
|
|
428
|
+
|
|
429
|
+
### Pro Template Demos
|
|
377
430
|
- [React Demo](https://react.tenorlab.com) (built with @tenorlab/react-dashboard)
|
|
378
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
|
|
379
435
|
- [Buy a License](https://payhip.com/b/gPBpo)
|
|
380
436
|
- [Follow on BlueSky](https://bsky.app/profile/tenorlab.bsky.social)
|
|
437
|
+
- [Official Website](https://www.tenorlab.com)
|
|
381
438
|
|
|
382
439
|
|
|
383
440
|
------
|
|
@@ -385,21 +442,15 @@ For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**
|
|
|
385
442
|
|
|
386
443
|
## ⚖️ Licensing & Usage
|
|
387
444
|
|
|
388
|
-
|
|
445
|
+
**@tenorlab/vue-dashboard** is [MIT licensed](https://opensource.org/licenses/MIT).
|
|
389
446
|
|
|
390
|
-
|
|
391
|
-
If you are a student, hobbyist, or working on a non-profit open-source project, you may use this library for free under the terms of the **Polyform Non-Commercial 1.0.0** license.
|
|
447
|
+
It provides the foundational components and logic for building dashboards. You are free to use it in any project, personal or commercial.
|
|
392
448
|
|
|
393
|
-
|
|
394
|
-
A **Tenorlab Commercial License** is required if you are:
|
|
395
|
-
* Building a dashboard for company internal or external use, including all company websites and applications.
|
|
396
|
-
* Building a dashboard for a paying client.
|
|
397
|
-
* Using the library within a for-profit company or startup.
|
|
398
|
-
* Including the library in a product that is sold or requires a subscription.
|
|
449
|
+
## ⚡️ Go Pro and Save Time: Tenorlab App Template
|
|
399
450
|
|
|
400
|
-
|
|
451
|
+
A commercial license for a full-blown professional app template is available for purchase [**here**](https://www.tenorlab.com) and comes with:
|
|
401
452
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
*
|
|
405
|
-
*
|
|
453
|
+
* **Full Application Shell:** A clean, optimized Vite + TypeScript project structure (with either React, Vue or Nuxt).
|
|
454
|
+
* **Dashboard Management:** Production-ready logic for creating, listing, renaming, and deleting multiple user-defined dashboards.
|
|
455
|
+
* **Implementation Examples:** Best patterns for both "Read-Only" (Analyst view) and "User-Editable" (Admin view) dashboard modes, a dynamic dashboard menu, etc.
|
|
456
|
+
* **Tenorlab Theme Engine:** A sophisticated Tailwind-based system supporting multiple custom themes (not just Light/Dark mode).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { create as mt } from "zustand";
|
|
2
|
-
import {
|
|
2
|
+
import { blankDashboardConfig as Ce, dashboardStoreUtils as he, getDistinctCssClasses as G, ensureZoomScaleIsWithinRange as pt, parseContainerTitle as nt, getWidgetMetaFromCatalog as yt, dashboardSettingsUtils as bt } from "./core.es.js";
|
|
3
3
|
import ve, { useState as ie, useCallback as ce, useMemo as ot, forwardRef as se, useRef as vt, Suspense as xt, useEffect as Ct } from "react";
|
|
4
4
|
import { jsxs as I, jsx as t } from "react/jsx-runtime";
|
|
5
5
|
import at from "react-dom";
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenorlab/react-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Foundation components for creating user-configurable dashboards in React",
|
|
5
5
|
"author": "Damiano Fusco",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -19,8 +19,6 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"dev": "vite",
|
|
21
21
|
"clean": "rm -rf dist node_modules package-lock.json",
|
|
22
|
-
"OLD-build-types": "tsc src/ind8ex.ts --jsx react-jsx --downlevelIteration --esModuleInterop --declaration --emitDeclarationOnly --outDir dist",
|
|
23
|
-
"OLD-build": "npm run pretty; tsc --jsx react-jsx --esModuleInterop && vite build; npm run build-types",
|
|
24
22
|
"build": "npm run pretty; vite build",
|
|
25
23
|
"pub": "npm run build; npm publish --access public --otp=",
|
|
26
24
|
"test": "TESTING=true vitest run",
|
|
@@ -39,12 +37,12 @@
|
|
|
39
37
|
],
|
|
40
38
|
"exports": {
|
|
41
39
|
".": {
|
|
42
|
-
"types": "./dist/react-dashboard.d.ts",
|
|
40
|
+
"types": "./dist/react-dashboard.d.ts",
|
|
43
41
|
"import": "./dist/react-dashboard.es.js"
|
|
44
42
|
},
|
|
45
43
|
"./core": {
|
|
46
44
|
"types": "./dist/core.d.ts",
|
|
47
|
-
"import": "./dist/core.es.js"
|
|
45
|
+
"import": "./dist/core.es.js"
|
|
48
46
|
},
|
|
49
47
|
"./styles.css": "./dist/styles.css"
|
|
50
48
|
},
|
|
@@ -57,7 +55,7 @@
|
|
|
57
55
|
"zustand": "^5.0.0 || ^5.0.9"
|
|
58
56
|
},
|
|
59
57
|
"devDependencies": {
|
|
60
|
-
"@tenorlab/dashboard-core": "^1.
|
|
58
|
+
"@tenorlab/dashboard-core": "^1.6.0",
|
|
61
59
|
"@types/node": "^24.10.1",
|
|
62
60
|
"@types/react": "19.2.3",
|
|
63
61
|
"@types/react-dom": "19.2.3",
|