@tenorlab/react-dashboard 1.4.3 → 1.4.5

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
@@ -1,49 +1,373 @@
1
1
  # @tenorlab/react-dashboard
2
2
 
3
- Foundation components for creating user-configurable, high-performance dashboards in React.
3
+ Foundation components for creating user-configurable, high-performance dashboards in React. Built on top of **@tenorlab/dashboard-core**.
4
4
 
5
- ---
5
+ ## 🏗 Relationship to Core
6
6
 
7
- ### Part of the Built With JavaScript Ecosystem
8
- **Tenorlab** is the specialized software foundry for the [@builtwithjavascript](https://github.com/builtwithjavascript) ecosystem, focusing on modular, type-safe utilities and UI kits.
7
+ 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.
9
8
 
10
- ---
9
+ > **Note**: This package re-exports all types and utilities from `@tenorlab/dashboard-core`. You do not need to install the core package separately.
10
+
11
+ ## ✨ Features
12
+
13
+ - **Type-Safe:** Deep integration with TypeScript 5.8+ for full IDE support.
14
+ - **State Management:** Built-in `useDashboardStore` (Zustand) and `useDashboardUndoService`.
15
+ - **User Configurable:** Ready-to-use components for adding, removing, and dragging widgets.
16
+ - **Themeable:** Native support for CSS Variables and Tailwind CSS.
17
+ - **Vite Optimized:** Full ESM support and tree-shakeable.
11
18
 
12
19
  ## 🚀 Quick Start
13
20
 
14
21
  ### Installation
15
22
 
16
- Install the package via npm or pnpm:
23
+ Bash
17
24
 
18
- ```bash
19
- npm install @tenorlab/react-dashboard
20
- # or
25
+ ```
26
+ # with npm
27
+ npm i @tenorlab/react-dashboard
28
+
29
+ # with pnpm
21
30
  pnpm add @tenorlab/react-dashboard
22
31
  ```
23
32
 
24
- ## Basic Usage
25
- Import the styles in your entry tsx file (usually main.tsx):
26
- ```typescript
27
- import '@tenorlab/react-dashboard/styles.css'
33
+ ### 1. Global Styles
34
+
35
+ Import the base styles in your entry file (e.g., `main.tsx`):
36
+
37
+ TypeScript
38
+
39
+ ```
40
+ import '@tenorlab/react-dashboard/dist/style.css'
41
+ ```
42
+
43
+ ------
44
+
45
+ ## 🛠 Developer Guide
46
+
47
+ ### 1. Creating a Widget
48
+
49
+ Widgets should be organized by their loading strategy.
50
+
51
+ - **Bundled Widgets**: Place in `src/bundled-widgets/` (loaded immediately).
52
+ - **Async Widgets**: Place in `src/async-widgets/` (lazy-loaded).
53
+
54
+ Each widget requires a sub-directory using the `widget-name-here` convention.
55
+
56
+ #### Example: `WidgetTotalOrders`
57
+
58
+ TypeScript
59
+
60
+ ```
61
+ // file: src/bundled-widgets/widget-total-orders/WidgetTotalOrders.tsx
62
+ import {
63
+ IDashboardWidget,
64
+ IDashboardWidgetProps,
65
+ TDashboardWidgetKey,
66
+ DashboardWidgetBase,
67
+ WrapperColumnContent
68
+ } from '@tenorlab/react-dashboard'
69
+
70
+ const WidgetKey: TDashboardWidgetKey = 'WidgetTotalOrders'
71
+
72
+ export function WidgetTotalOrders(props: IDashboardWidgetProps): IDashboardWidget {
73
+ return (
74
+ <DashboardWidgetBase
75
+ widgetKey={WidgetKey}
76
+ title="Total Orders"
77
+ parentWidgetKey={props.parentWidgetKey}
78
+ index={props.index}
79
+ maxIndex={props.maxIndex}
80
+ isEditing={props.isEditing}
81
+ onRemoveClick={props.onRemoveClick}
82
+ onMoveClick={props.onMoveClick}
83
+ >
84
+ <WrapperColumnContent>
85
+ <div className="dashboard-number number-xl text-primary">1,250</div>
86
+ <div className="text-sm">Orders this month</div>
87
+ </WrapperColumnContent>
88
+ </DashboardWidgetBase>
89
+ )
90
+ }
91
+ ```
92
+
93
+ TypeScript
94
+
95
+ ```
96
+ // file: src/bundled-widgets/widget-total-orders/meta.ts
97
+ import type { TWidgetMetaInfo } from '@tenorlab/react-dashboard'
98
+ import { MonitorIcon as ComponentIcon } from '@tenorlab/react-dashboard'
99
+
100
+ export const WidgetTotalOrdersMeta: TWidgetMetaInfo = {
101
+ name: 'Total Orders',
102
+ categories: ['Widget'],
103
+ icon: ComponentIcon,
104
+ noDuplicatedWidgets: true,
105
+ description: 'Displays information about your total orders.',
106
+ externalDependencies: []
107
+ }
108
+ ```
109
+
110
+ TypeScript
111
+
112
+ ```
113
+ // file: src/bundled-widgets/widget-total-orders/index.ts
114
+ import { WidgetTotalOrders } from './WidgetTotalOrders'
115
+ export default WidgetTotalOrders
116
+ ```
117
+
118
+ ### 2. Creating the Widgets Catalog
119
+
120
+ 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).
121
+
122
+ TypeScript
123
+
124
+ ```
125
+ // file: src/widgets-catalog.tsx
126
+ import {
127
+ IDynamicWidgetCatalogEntry,
128
+ TDashboardWidgetCatalog,
129
+ TWidgetMetaInfoBase,
130
+ WidgetContainerColumn,
131
+ WidgetContainerLarge,
132
+ WidgetContainerRow,
133
+ TWidgetFactory,
134
+ } from '@tenorlab/react-dashboard'
135
+ import {
136
+ createStaticEntry,
137
+ localWidgetDiscovery,
138
+ remoteWidgetDiscovery,
139
+ } from '@tenorlab/react-dashboard/core'
140
+
141
+ import { WidgetRecentPaymentInfo } from './other-widgets/WidgetRecentPaymentInfo'
142
+ //import { getWidgetsManifestUrl } from '@/utils'
143
+
144
+ const bundledWidgetsSrcPath = '/src/bundled-widgets'
145
+ const asyncWidgetsSrcPath = '/src/async-widgets'
146
+
147
+ type TGlobModuleMap = Record<string, TWidgetFactory>
148
+
149
+ const bundledWidgetModules = import.meta.glob('/src/bundled-widgets/*/index.ts', {
150
+ eager: true
151
+ }) as TGlobModuleMap
152
+
153
+ const asyncWidgetModules = import.meta.glob('/src/async-widgets/*/index.ts') as TGlobModuleMap
154
+
155
+ const allMetaModules = import.meta.glob('/src/**/widget-*/meta.ts', {
156
+ eager: true,
157
+ }) as Record<string, Record<string, TWidgetMetaInfoBase>>
158
+
159
+ const hasPermission = (_user_: any, _permission: string) => true
160
+
161
+ export const getWidgetCatalog = async (user: any | null): Promise<TDashboardWidgetCatalog> => {
162
+ const catalogMapEntries: [string, IDynamicWidgetCatalogEntry][] = [
163
+ createStaticEntry('WidgetContainer', WidgetContainerColumn),
164
+ createStaticEntry('WidgetContainerRow', WidgetContainerRow),
165
+ createStaticEntry('WidgetContainerLarge', WidgetContainerLarge),
166
+ ]
167
+
168
+ if (hasPermission(user, 'some-permission')) {
169
+ catalogMapEntries.push(createStaticEntry('WidgetRecentPaymentInfo', WidgetRecentPaymentInfo))
170
+ }
171
+
172
+ catalogMapEntries.push(...localWidgetDiscovery(
173
+ bundledWidgetsSrcPath,
174
+ bundledWidgetModules,
175
+ allMetaModules,
176
+ false
177
+ ))
178
+
179
+ catalogMapEntries.push(...localWidgetDiscovery(
180
+ asyncWidgetsSrcPath,
181
+ asyncWidgetModules,
182
+ allMetaModules,
183
+ true
184
+ ))
185
+
186
+ // Optional: Remote discovery of -pre-built widgets hosted on a CDN
187
+ /*const manifestUrl = getWidgetsManifestUrl()
188
+ if (manifestUrl.length > 0) {
189
+ const remoteResponse = await remoteWidgetDiscovery(manifestUrl)
190
+ if (!remoteResponse.message) {
191
+ catalogMapEntries.push(...(remoteResponse.entries || []))
192
+ }
193
+ }*/
194
+
195
+ return new Map(catalogMapEntries)
196
+ }
197
+ ```
198
+
199
+ ### 3. Defining Dashboard Defaults
200
+
201
+ Use a `dashboard-defaults.ts` file to define initial layouts based on user roles.
202
+
203
+ TypeScript
204
+
28
205
  ```
206
+ // file: src/dashboard-defaults.ts
207
+ import {
208
+ TDashboardWidgetKey,
209
+ IChildWidgetConfigEntry,
210
+ IDashboardConfig,
211
+ TDashboardWidgetCatalog,
212
+ } from '@tenorlab/react-dashboard'
213
+ import { blankDashboardConfig, cssSettingsCatalog } from '@tenorlab/react-dashboard/core'
214
+ import { getWidgetCatalog } from './widgets-catalog'
215
+
216
+ const DEFAULT_DASHBOARD_ID = 'default' as const
217
+ const DEFAULT_DASHBOARD_NAME = 'Default' as const
218
+
219
+ const getDefaultDashboardForCustomerUser = (
220
+ user: any,
221
+ clientAppKey: string,
222
+ availableWidgetKeys: TDashboardWidgetKey[]
223
+ ): IDashboardConfig => {
224
+ const userID = user.userID || 0
225
+ return {
226
+ userID,
227
+ clientAppKey,
228
+ dashboardId: DEFAULT_DASHBOARD_ID,
229
+ dashboardName: DEFAULT_DASHBOARD_NAME,
230
+ zoomScale: 1,
231
+ responsiveGrid: false,
232
+ widgets: ['WidgetContainer_container1'],
233
+ childWidgetsConfig: [
234
+ { parentWidgetKey: 'WidgetContainer_container1', widgetKey: 'WidgetRecentPaymentInfo' }
235
+ ],
236
+ cssSettings: [...cssSettingsCatalog]
237
+ }
238
+ }
239
+
240
+ export const getDashboardDefaults = async (
241
+ user: any | null,
242
+ clientAppKey: string
243
+ ): Promise<{
244
+ dashboardConfig: IDashboardConfig
245
+ widgetsCatalog: TDashboardWidgetCatalog
246
+ }> => {
247
+ const widgetsCatalog = await getWidgetCatalog(user)
248
+
249
+ if (!user) return { dashboardConfig: blankDashboardConfig, widgetsCatalog }
250
+
251
+ return {
252
+ dashboardConfig: getDefaultDashboardForCustomerUser(user, clientAppKey, [...widgetsCatalog.keys()]),
253
+ widgetsCatalog
254
+ }
255
+ }
256
+ ```
257
+
258
+ ### 4. Implementation Example: Read-Only Dashboard
259
+
260
+ Use this for a simplified, non-editable view of the dashboard.
261
+
262
+ TypeScript
263
+
264
+ ```
265
+ // file: src/views/DashboardReadonly.tsx
266
+ import { useEffect, useState } from 'react'
267
+ import {
268
+ IDashboardConfig,
269
+ TDashboardWidgetCatalog,
270
+ useDashboardStore,
271
+ DynamicWidgetLoader,
272
+ DashboardGrid
273
+ } from '@tenorlab/react-dashboard'
274
+ import {
275
+ blankDashboardConfig,
276
+ cssVarsUtils,
277
+ useDashboardStorageService,
278
+ } from '@tenorlab/react-dashboard/core'
279
+ import { getDashboardDefaults } from '../dashboard-defaults'
280
+
281
+ export function DashboardReadonly() {
282
+ const clientAppKey = 'myclientapp'
283
+ const user = { id: 1234 }
284
+ const dashboardStore = useDashboardStore()
285
+ const dashboardStorageService = useDashboardStorageService()
286
+ const { isLoading, currentDashboardConfig } = dashboardStore
287
+
288
+ const [widgetsCatalog, setWidgetsCatalog] = useState<TDashboardWidgetCatalog>(new Map())
289
+
290
+ useEffect(() => {
291
+ async function initDashboard() {
292
+ dashboardStore.setIsLoading(true)
293
+ try {
294
+ const defaults = await getDashboardDefaults(user, clientAppKey)
295
+ const savedConfigs = await dashboardStorageService.getSavedDashboards(
296
+ user.id,
297
+ clientAppKey,
298
+ defaults.widgetsCatalog,
299
+ defaults.dashboardConfig
300
+ )
301
+
302
+ dashboardStore.setAllDashboardConfigs(savedConfigs)
303
+ const activeConfig = savedConfigs[0] || defaults.dashboardConfig
304
+ dashboardStore.setCurrentDashboardConfig(activeConfig)
305
+ setWidgetsCatalog(defaults.widgetsCatalog)
306
+ cssVarsUtils.restoreCssVarsFromSettings(activeConfig.cssSettings || [])
307
+ } finally {
308
+ dashboardStore.setIsLoading(false)
309
+ }
310
+ }
311
+ initDashboard()
312
+ }, [])
313
+
314
+ return (
315
+ <div className="relative flex flex-col h-full">
316
+ {isLoading ? <div>Loading</div> : (
317
+ <DashboardGrid
318
+ isEditing={false}
319
+ zoomScale={Number(currentDashboardConfig.zoomScale)}
320
+ responsiveGrid={currentDashboardConfig.responsiveGrid}
321
+ >
322
+ {currentDashboardConfig.widgets.map((widgetKey, index) => (
323
+ <DynamicWidgetLoader
324
+ key={`${widgetKey}_${index}`}
325
+ widgetKey={widgetKey}
326
+ index={index}
327
+ maxIndex={currentDashboardConfig.widgets.length - 1}
328
+ childWidgetsConfig={currentDashboardConfig.childWidgetsConfig}
329
+ widgetCatalog={widgetsCatalog as any}
330
+ isEditing={false}
331
+ />
332
+ ))}
333
+ </DashboardGrid>
334
+ )}
335
+ </div>
336
+ )
337
+ }
338
+ ```
339
+
340
+
341
+
342
+ #### 5. Full Editable Dashboard
343
+
344
+ 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).
345
+
346
+
347
+
348
+ ------
29
349
 
30
- TODO:
350
+ ## 🧩 Components & Services
31
351
 
352
+ ### UI Components
32
353
 
33
- ## Features
354
+ - **`DashboardGrid`**: The main layout engine for positioning widgets.
355
+ - **`WidgetContainer`**: Wrapper providing common widget UI (headers, actions, loading states).
356
+ - **`WidgetsCatalogFlyout`**: A slide-out panel for users to browse and add new widgets.
357
+ - **`DynamicWidgetLoader`**: Lazy-loading utility for high-performance dashboards.
34
358
 
35
- - **Type-Safe:** Built with TypeScript for excellent IDE support and error catching.
36
- - **Configurable:** Allow end-users to add/remove widgets.
37
- - **Vite Optimized:** Tree-shakeable and lightweight for modern build pipelines.
38
- - **Themeable:** Easy integration with Tailwind CSS or CSS Variables.
359
+ ### Hooks & State
39
360
 
361
+ - **`useDashboardStore`**: Access the underlying Zustand store to manage widget state, layout, and configuration.
362
+ - **`useDashboardUndoService`**: Provides `undo` and `redo` functionality for user layout changes.
40
363
 
364
+ ------
41
365
 
42
- ## Licensing
366
+ ## ⚖️ Licensing
43
367
 
44
368
  This project is dual-licensed:
45
369
 
46
- 1. **Non-Commercial / Personal Use:** Licensed under the [Polyform Non-Commercial 1.0.0](https://polyformproject.org/licenses/non-commercial/1.0.0/). Free to use for students, hobbyists, and open-source projects.
370
+ 1. **Non-Commercial / Personal Use:** Licensed under the [Polyform Non-Commercial 1.0.0](https://polyformproject.org/licenses/non-commercial/1.0.0/). Free for students, hobbyists, and open-source projects.
47
371
  2. **Commercial Use:** Requires a **Tenorlab Commercial License**.
48
372
 
49
- If you are using this library to build a product that generates revenue, or within a commercial entity, please visit [tenorlab.com/license](https://tenorlab.com/license) to purchase a commercial seat.
373
+ If you are using this library to build a revenue-generating product or within a commercial entity, please visit [tenorlab.com/license](https://tenorlab.com/license).
@@ -97,6 +97,11 @@ export declare interface IDashboardConfig {
97
97
  _stateDescription?: string;
98
98
  }
99
99
 
100
+ /**
101
+ * @name IDashboardGridProps
102
+ * @description Dashboard grid properties interface specific to React framework.
103
+ * This interface extends the base dashboard grid properties interface with optional children.
104
+ */
100
105
  export declare interface IDashboardGridProps extends IDashboardGridPropsBase {
101
106
  children?: ReactNode;
102
107
  }
@@ -124,9 +129,20 @@ export declare interface IDashboardStorageService {
124
129
  saveDashboards: TSaveDashboards;
125
130
  }
126
131
 
132
+ /**
133
+ * @name IDashboardWidget
134
+ * @description A type representing a React dashboard widget element.
135
+ * This type is defined as a JSX.Element.
136
+ */
127
137
  export declare interface IDashboardWidget extends JSX.Element {
128
138
  }
129
139
 
140
+ /**
141
+ * @name IDashboardWidgetProps
142
+ * @description Dashboard widget properties interface specific to React framework.
143
+ * This interface extends the base dashboard widget properties interface with additional React-specific properties.
144
+ * @template TExtraProps - Additional properties specific to the widget.
145
+ */
130
146
  export declare interface IDashboardWidgetProps<TExtraProps = any> extends IDashboardWidgetPropsBase<TExtraProps> {
131
147
  children?: ReactNode;
132
148
  onRemoveClick?: (widgetKey: TDashboardWidgetKey, parentWidgetKey?: TDashboardWidgetKey) => void;
@@ -154,6 +170,13 @@ export declare interface IDashboardWidgetPropsBase<TExtraProps = any> {
154
170
  extraProps?: TExtraProps;
155
171
  }
156
172
 
173
+ /**
174
+ * @name IDynamicWidgetCatalogEntry
175
+ * @description Dynamic widget catalog entry interface specific to React framework.
176
+ * This interface extends the base dynamic widget catalog entry interface with React-specific element and component types.
177
+ * @template TFrameworkElementType - The React element type.
178
+ * @template TFrameworkComponentType - The React component type.
179
+ */
157
180
  export declare interface IDynamicWidgetCatalogEntry extends IDynamicWidgetCatalogEntryBase<TFrameworkElementType, TFrameworkComponentType> {
158
181
  }
159
182
 
@@ -281,6 +304,13 @@ export declare type TDashboardUndoStatus = {
281
304
  _historyLength?: number;
282
305
  };
283
306
 
307
+ /**
308
+ * @name TDashboardWidgetCatalog
309
+ * @description Dashboard widget catalog type specific to React framework.
310
+ * This type extends the base dashboard widget catalog type with React-specific element and component types.
311
+ * @template TFrameworkElementType - The React element type.
312
+ * @template TFrameworkComponentType - The React component type.
313
+ */
284
314
  export declare type TDashboardWidgetCatalog = TDashboardWidgetCatalogBase<TFrameworkElementType, TFrameworkComponentType>;
285
315
 
286
316
  export declare type TDashboardWidgetCatalogBase<TFrameworkElementType = any, TFrameworkComponentType = any> = Map<TDashboardWidgetKey, IDynamicWidgetCatalogEntryBase<TFrameworkElementType, TFrameworkComponentType>>;
@@ -307,8 +337,16 @@ declare type TDynamicWidgetLoaderProps<TExtraProps = any> = {
307
337
  */
308
338
  export declare const TextField: default_2.FC<ITextFieldProps>;
309
339
 
340
+ /**
341
+ * @name TFrameworkComponentType
342
+ * @description A type representing a React component type.
343
+ */
310
344
  declare type TFrameworkComponentType = React.ComponentType<any>;
311
345
 
346
+ /**
347
+ * @name TFrameworkElementType
348
+ * @description A type representing a React element type.
349
+ */
312
350
  declare type TFrameworkElementType = React.ElementType;
313
351
 
314
352
  declare type TGetSavedDashboards = (userID: number | string, clientAppKey: string, widgetCatalog: TDashboardWidgetCatalogBase, defaultDashboardConfig: IDashboardConfig) => Promise<IDashboardConfig[]>;
@@ -358,6 +396,12 @@ export declare type TWidgetCategory = 'Widget' | 'Chart' | 'Container';
358
396
 
359
397
  export declare type TWidgetDirection = 'row' | 'column';
360
398
 
399
+ /**
400
+ * @name TWidgetFactory
401
+ * @description A type representing a widget factory function specific to React framework.
402
+ * This type extends the base widget factory type with React-specific component type.
403
+ * @template TFrameworkComponentType - The React component type.
404
+ */
361
405
  export declare type TWidgetFactory = TWidgetFactoryBase<TFrameworkComponentType>;
362
406
 
363
407
  /**
@@ -372,6 +416,11 @@ export declare type TWidgetFactoryBase<TFrameworkComponent = any> = () => Promis
372
416
  default: TFrameworkComponent;
373
417
  }>;
374
418
 
419
+ /**
420
+ * @name TWidgetMetaInfo
421
+ * @description Widget meta information type specific to React framework.
422
+ * This type extends the base widget meta information type with React-specific element type.
423
+ */
375
424
  export declare type TWidgetMetaInfo = TWidgetMetaInfoBase<TFrameworkElementType | undefined>;
376
425
 
377
426
  export declare type TWidgetMetaInfoBase<TFrameworkElementType = any> = {