@ogidor/dashboard 1.0.5 → 1.0.7

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,20 +1,38 @@
1
1
  # @ogidor/dashboard
2
2
 
3
- A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
4
- Drop it into any project, feed it your own card content, and get a fully functional multi-workspace grid with persistent layout, pop-out windows, and live cross-tab sync — out of the box.
3
+ A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
4
+ Drop it into any project, supply your own card content, and get a fully featured
5
+ multi-workspace grid with persistent layout, pop-out windows, and live cross-tab
6
+ sync — out of the box.
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ 1. [Features](#features)
13
+ 2. [Installation](#installation)
14
+ 3. [Quick Start](#quick-start)
15
+ 4. [API Reference](#api-reference)
16
+ 5. [Models](#models)
17
+ 6. [Theming](#theming)
18
+ 7. [Pop-out Windows](#pop-out-windows)
19
+ 8. [Persisting Layouts](#persisting-layouts)
20
+ 9. [Full Integration Example](#full-integration-example)
21
+ 10. [Development](#development)
22
+ 11. [License](#license)
5
23
 
6
24
  ---
7
25
 
8
26
  ## Features
9
27
 
10
- - **Drag & Drop Grid** — smooth cursor-following drag with animated collision resolution and auto-compaction
11
- - **Resize** — live resize with a bottom-right handle; other cards shift out of the way in real time
12
- - **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent widget layout
13
- - **Independent Pop-out Layouts** — each window keeps its own card positions; drag cards in Window B and Window A is completely unaffected
14
- - **Structural Sync** — adding, removing, or editing a widget in any window immediately appears in all other open windows
15
- - **Persistent Layout** — layout is saved to `localStorage` and restored on reload
16
- - **Content-Agnostic Cards** — you own the card body; render any Angular component, chart, or HTML inside
17
- - **Themeable** — override colors and fonts via a theme input or CSS custom properties
28
+ - **Drag & Drop Grid** — smooth cursor-following drag with collision resolution and auto-compaction.
29
+ - **Live Resize** — bottom-right resize handle; surrounding cards shift out of the way in real time.
30
+ - **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent layout.
31
+ - **Independent Pop-out Layouts** — pop a workspace into its own window; dragging there never affects the main window.
32
+ - **Structural Sync** — adding, removing, or renaming a widget in any window instantly appears in all open windows via `BroadcastChannel`.
33
+ - **Persistent Layout** — state is saved to `localStorage` on every change and restored on reload.
34
+ - **Content-Agnostic Cards** — you own the card body; stamp any Angular component, chart, or markup inside via `ng-template`.
35
+ - **Fully Themeable** — 20 colour tokens plus font family, controllable via a typed `DashboardTheme` input or CSS custom properties.
18
36
 
19
37
  ---
20
38
 
@@ -24,7 +42,7 @@ Drop it into any project, feed it your own card content, and get a fully functio
24
42
  npm install @ogidor/dashboard line-awesome
25
43
  ```
26
44
 
27
- Add the Line Awesome icon font to your global styles (`angular.json` or `styles.scss`):
45
+ Add Line Awesome to your global styles (`styles.scss` or `angular.json`):
28
46
 
29
47
  ```scss
30
48
  @import "line-awesome/dist/line-awesome/css/line-awesome.css";
@@ -34,7 +52,7 @@ Add the Line Awesome icon font to your global styles (`angular.json` or `styles.
34
52
 
35
53
  ## Quick Start
36
54
 
37
- ### 1. Import the module
55
+ ### 1. Import `DashboardModule`
38
56
 
39
57
  ```typescript
40
58
  import { NgModule } from '@angular/core';
@@ -48,14 +66,14 @@ import { DashboardModule } from '@ogidor/dashboard';
48
66
  export class AppModule {}
49
67
  ```
50
68
 
51
- ### 2. Use the component
69
+ ### 2. Add the component to your template
52
70
 
53
71
  ```html
54
72
  <app-dashboard
55
73
  (addWidgetRequested)="onAddWidget()"
56
74
  (editWidgetRequested)="onEditWidget($event)"
57
75
  >
58
- <!-- Optional: custom card body rendered inside every widget -->
76
+ <!-- Stamp your own content inside every card -->
59
77
  <ng-template gridCell let-widget="widget">
60
78
  <div style="padding: 8px; color: white;">
61
79
  {{ widget.data | json }}
@@ -64,32 +82,28 @@ export class AppModule {}
64
82
  </app-dashboard>
65
83
  ```
66
84
 
67
- ### 3. Add widgets programmatically
68
-
69
- Inject `DashboardStateService` anywhere in your app:
85
+ ### 3. Add and edit widgets programmatically
70
86
 
71
87
  ```typescript
72
88
  import { DashboardStateService, Widget } from '@ogidor/dashboard';
73
89
 
74
- @Component({ ... })
75
90
  export class AppComponent {
76
91
  constructor(private dashboard: DashboardStateService) {}
77
92
 
78
93
  onAddWidget() {
79
94
  this.dashboard.addWidget({
80
- title: 'Sales Overview',
95
+ title: 'Revenue',
81
96
  cols: 6,
82
97
  rows: 4,
83
- cardColor: '#1a1a2e', // optional — overrides the theme card color
84
- data: { metric: 'revenue', period: 'Q1' }, // any shape you need
98
+ cardColor: '#1a1a2e', // optional — overrides the global card color
99
+ data: { metric: 'revenue' },
85
100
  });
86
101
  }
87
102
 
88
103
  onEditWidget(widget: Widget) {
89
- // open your own dialog, then:
104
+ // open your own dialog, then patch:
90
105
  this.dashboard.updateWidget(widget.id, {
91
106
  title: 'Updated Title',
92
- cardColor: '#0f3460',
93
107
  data: { metric: 'users' },
94
108
  });
95
109
  }
@@ -100,34 +114,36 @@ export class AppComponent {
100
114
 
101
115
  ## API Reference
102
116
 
103
- ### `<app-dashboard>`
117
+ ### `app-dashboard`
118
+
119
+ The main component. Handles tabs, the Add Widget button, and the grid.
104
120
 
105
121
  #### Inputs
106
122
 
107
- | Input | Type | Default | Description |
108
- |---|---|---|---|
109
- | `initialLayout` | `string` | | JSON string from a previous `serializeLayout()` call. Restores a full layout on load. |
110
- | `theme` | `DashboardTheme` | See below | Override default colors and font. |
123
+ | Input | Type | Description |
124
+ |---|---|---|
125
+ | `theme` | `DashboardTheme` | Override any colour token or font. All fields optional. |
126
+ | `initialLayout` | `string` | JSON from a previous `serializeLayout()` call. Restores the full layout on load. |
111
127
 
112
128
  #### Outputs
113
129
 
114
130
  | Output | Payload | Description |
115
131
  |---|---|---|
116
132
  | `addWidgetRequested` | `void` | Fires when the user clicks **Add Widget**. Open your own dialog here. |
117
- | `editWidgetRequested` | `Widget` | Fires when the user clicks the edit icon on a card. Receives the full widget object. |
133
+ | `editWidgetRequested` | `Widget` | Fires when the user clicks the edit icon on a card. |
118
134
 
119
135
  #### Content children
120
136
 
121
137
  | Selector | Description |
122
138
  |---|---|
123
- | `<ng-template gridCell let-widget="widget">` | Optional template rendered inside every card body. The `widget` context variable gives you the full `Widget` object including your `data`. |
139
+ | `ng-template[gridCell]` | Rendered inside every card body. Template context exposes `widget: Widget`. |
124
140
 
125
141
  #### Methods (via `@ViewChild`)
126
142
 
127
143
  ```typescript
128
144
  @ViewChild(DashboardComponent) dash!: DashboardComponent;
129
145
 
130
- // Serialize the current layout to a JSON string (e.g. to save to a backend)
146
+ // Returns the current layout as a JSON string
131
147
  const json = this.dash.serializeLayout();
132
148
  ```
133
149
 
@@ -135,7 +151,7 @@ const json = this.dash.serializeLayout();
135
151
 
136
152
  ### `DashboardStateService`
137
153
 
138
- The service is provided at root level. Inject it directly for full programmatic control.
154
+ Provided at root. Inject it anywhere for full programmatic control.
139
155
 
140
156
  ```typescript
141
157
  constructor(private state: DashboardStateService) {}
@@ -145,14 +161,15 @@ constructor(private state: DashboardStateService) {}
145
161
 
146
162
  | Method | Signature | Description |
147
163
  |---|---|---|
148
- | `addWidget` | `(widget: Partial<Widget> & { title: string }) => Widget` | Adds a widget to the active workspace. Returns the created widget with its generated `id`. |
149
- | `updateWidget` | `(id: string, patch: Partial<Widget>) => void` | Update any widget field except `id`, `x`, `y`, `cols`, `rows`. |
164
+ | `addWidget` | `(widget: Partial<Widget> & { title: string }) => Widget` | Add a widget to the active workspace. Returns the new widget with its generated `id`. |
165
+ | `updateWidget` | `(id: string, patch: Partial<Omit<Widget, 'id'&#124;'x'&#124;'y'&#124;'cols'&#124;'rows'>>) => void` | Update title, cardColor, or data. Use `updateWidgetPosition` for positional fields. |
150
166
  | `removeWidget` | `(id: string) => void` | Remove a widget from the active workspace. |
151
- | `updateWidgetPosition` | `(pageId, widgetId, x, y, cols, rows) => void` | Manually reposition/resize a widget. |
167
+ | `updateWidgetPosition` | `(pageId, widgetId, x, y, cols, rows) => void` | Manually reposition or resize a widget. |
168
+ | `getActivePage` | `() => Page \| undefined` | Returns the currently active `Page` object. |
152
169
  | `addPage` | `(name: string) => void` | Add a new workspace tab. |
153
- | `removePage` | `(id: string) => void` | Remove a workspace tab (minimum 1 kept). |
154
- | `setActivePage` | `(id: string) => void` | Switch to a workspace by id. |
155
- | `popOutPage` | `(id: string) => void` | Open a workspace in its own browser window. |
170
+ | `removePage` | `(id: string) => void` | Remove a workspace tab (at least one is always kept). |
171
+ | `setActivePage` | `(id: string) => void` | Switch the active workspace by id. |
172
+ | `popOutPage` | `(id: string) => void` | Open a workspace in a separate browser window. |
156
173
  | `loadLayout` | `(config: DashboardConfig) => void` | Load a full layout object (parsed from `serializeLayout`). |
157
174
  | `serializeLayout` | `() => string` | Serialize the full layout to a JSON string. |
158
175
 
@@ -160,92 +177,245 @@ constructor(private state: DashboardStateService) {}
160
177
 
161
178
  | Observable | Type | Description |
162
179
  |---|---|---|
163
- | `pages$` | `Observable<Page[]>` | Emits the full page list on every change. |
164
- | `activePageId$` | `Observable<string>` | Emits the active workspace id on every change. |
180
+ | `pages$` | `Observable<Page[]>` | Emits whenever the page list changes. |
181
+ | `activePageId$` | `Observable<string>` | Emits whenever the active workspace changes. |
182
+
183
+ ---
184
+
185
+ ### `app-grid` (Advanced)
186
+
187
+ The raw grid engine, exported for advanced use cases. `app-dashboard` uses this internally.
188
+
189
+ ```html
190
+ <app-grid
191
+ [widgets]="myWidgets"
192
+ [columns]="12"
193
+ [gap]="16"
194
+ [rowHeight]="80"
195
+ (itemChanged)="onItemChanged($event)"
196
+ (layoutChanged)="onLayoutChanged($event)"
197
+ >
198
+ <ng-template gridCell let-widget="widget">
199
+ <!-- your card here -->
200
+ </ng-template>
201
+ </app-grid>
202
+ ```
203
+
204
+ #### Inputs
205
+
206
+ | Input | Type | Default | Description |
207
+ |---|---|---|---|
208
+ | `widgets` | `Widget[]` | `[]` | The widgets to render. |
209
+ | `columns` | `number` | `12` | Number of grid columns. |
210
+ | `gap` | `number` | `16` | Gap between cells in pixels. |
211
+ | `rowHeight` | `number` | `80` | Height of one grid row in pixels. |
212
+ | `minItemCols` | `number` | `1` | Minimum column span during resize. |
213
+ | `minItemRows` | `number` | `1` | Minimum row span during resize. |
214
+
215
+ #### Outputs
216
+
217
+ | Output | Payload | Description |
218
+ |---|---|---|
219
+ | `itemChanged` | `Widget` | Fires after a single widget is moved or resized. |
220
+ | `layoutChanged` | `Widget[]` | Fires after any move or resize with the full updated list. |
165
221
 
166
222
  ---
167
223
 
224
+ ### Public Exports
225
+
226
+ ```typescript
227
+ export { DashboardModule } from '@ogidor/dashboard';
228
+ export { DashboardComponent } from '@ogidor/dashboard';
229
+ export { WidgetRendererComponent } from '@ogidor/dashboard';
230
+ export { CustomGridComponent } from '@ogidor/dashboard';
231
+ export { GridCellDirective } from '@ogidor/dashboard';
232
+ export { DashboardStateService } from '@ogidor/dashboard';
233
+ export { Widget, Page, DashboardConfig, DashboardTheme } from '@ogidor/dashboard';
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Models
239
+
168
240
  ### `Widget`
169
241
 
170
242
  ```typescript
171
243
  export interface Widget {
172
- id: string; // auto-generated — do not set manually
173
- x: number; // grid column (0-based)
174
- y: number; // grid row (0-based)
175
- cols: number; // column span (default: 4)
176
- rows: number; // row span (default: 3)
177
- title: string; // displayed in the card header
178
- cardColor?: string; // optional background color for this card
179
- data?: any; // your custom payload — stored & synced, never inspected
244
+ id: string; // auto-generated — do not set manually
245
+ x: number; // grid column, 0-based
246
+ y: number; // grid row, 0-based
247
+ cols: number; // column span (default: 4)
248
+ rows: number; // row span (default: 3)
249
+ title: string; // shown in the card header
250
+ cardColor?: string; // per-card background overrides the global widgetCardColor
251
+ data?: any; // custom payload — stored and synced, never inspected
180
252
  }
181
253
  ```
182
254
 
183
- ---
255
+ ### `Page`
184
256
 
185
- ### `DashboardTheme`
257
+ ```typescript
258
+ export interface Page {
259
+ id: string;
260
+ name: string;
261
+ widgets: Widget[];
262
+ }
263
+ ```
264
+
265
+ ### `DashboardConfig`
266
+
267
+ ```typescript
268
+ export interface DashboardConfig {
269
+ pages: Page[];
270
+ activePageId: string;
271
+ }
272
+ ```
186
273
 
187
- All fields are optional. Any omitted field falls back to its default.
274
+ ### `DashboardTheme`
188
275
 
189
276
  ```typescript
190
277
  export interface DashboardTheme {
191
- backgroundColor?: string; // wrapper background default: #000000
192
- panelColor?: string; // main panel background default: #1c1c1e
193
- widgetCardColor?: string; // card background default: #2c2c2e
194
- foreColor?: string; // text / labels default: #8e8e93
195
- accentColor?: string; // buttons, active states default: #0a84ff
196
- dangerColor?: string; // remove / close actions default: #ff453a
197
- fontFamily?: string; // applied globally default: system-ui stack
278
+ // Layout
279
+ backgroundColor?: string; // outer wrapper default: #000000
280
+ panelColor?: string; // main panel default: #1c1c1e
281
+ widgetCardColor?: string; // card background default: #2c2c2e
282
+ foreColor?: string; // text / labels default: #8e8e93
283
+ accentColor?: string; // active states, buttons default: #0a84ff
284
+ dangerColor?: string; // destructive actions default: #ff453a
285
+ fontFamily?: string; // global font stack default: system-ui
286
+
287
+ // Header & Tabs
288
+ tabsContainerColor?: string; // pill background default: rgba(44,44,46,0.6)
289
+ tabActiveColor?: string; // active tab bg default: #3a3a3c
290
+ tabActiveTextColor?: string; // active tab text default: #ffffff
291
+ tabHoverTextColor?: string; // hovered tab text default: #e5e5ea
292
+ addWidgetButtonTextColor?: string; // Add Widget label default: #ffffff
293
+
294
+ // Pop-out Window
295
+ popoutTitleColor?: string; // pop-out title text default: #ffffff
296
+
297
+ // Widget Card
298
+ widgetTitleColor?: string; // card title default: #ffffff
299
+ dragHandleColor?: string; // drag dots default: rgba(255,255,255,0.2)
300
+ widgetBorderColor?: string; // card border default: rgba(255,255,255,0.07)
301
+ widgetButtonBgColor?: string; // icon button bg default: rgba(255,255,255,0.07)
302
+ widgetButtonColor?: string; // icon button colour default: rgba(255,255,255,0.5)
303
+
304
+ // Grid
305
+ placeholderColor?: string; // drag ghost overlay default: #0a84ff
306
+ resizeHandleColor?: string; // resize icon default: rgba(255,255,255,0.25)
198
307
  }
199
308
  ```
200
309
 
310
+ > **Per-card colour:** set `cardColor` on any `Widget` to override `widgetCardColor` for just that one card.
311
+
201
312
  ---
202
313
 
203
- ## CSS Custom Properties
314
+ ## Theming
315
+
316
+ ### Theme Input
317
+
318
+ Pass a partial `DashboardTheme` to `[theme]`. Only provided fields are overridden.
319
+
320
+ ```typescript
321
+ theme: DashboardTheme = {
322
+ accentColor: '#6c63ff',
323
+ tabActiveColor: '#2a2a38',
324
+ tabActiveTextColor: '#ffffff',
325
+ widgetTitleColor: '#f0f0f0',
326
+ placeholderColor: '#6c63ff',
327
+ };
328
+ ```
329
+
330
+ ```html
331
+ <app-dashboard [theme]="theme"></app-dashboard>
332
+ ```
333
+
334
+ ### CSS Custom Properties
204
335
 
205
- You can also theme the dashboard entirely via CSS in your global stylesheet:
336
+ Every token is also available as a CSS variable. Use them in your global stylesheet:
206
337
 
207
338
  ```css
208
339
  app-dashboard {
209
- --dash-bg: #0d0d0d;
210
- --dash-panel-bg: #161616;
211
- --dash-card-bg: #1f1f1f;
212
- --dash-fore-color: #a0a0a0;
213
- --dash-accent-color: #6c63ff;
214
- --dash-danger-color: #e84545;
215
- --dash-font-family: 'Inter', sans-serif;
340
+ /* Layout */
341
+ --dash-bg: #0d0d0d;
342
+ --dash-panel-bg: #161616;
343
+ --dash-card-bg: #1f1f1f;
344
+ --dash-fore-color: #a0a0a0;
345
+ --dash-accent-color: #6c63ff;
346
+ --dash-danger-color: #e84545;
347
+ --dash-font-family: 'Inter', sans-serif;
348
+
349
+ /* Header / Tabs */
350
+ --dash-tabs-container-color: rgba(30,30,32,0.7);
351
+ --dash-tab-active-color: #2a2a2e;
352
+ --dash-tab-active-text: #ffffff;
353
+ --dash-tab-hover-text: #d0d0d5;
354
+ --dash-add-widget-text: #ffffff;
355
+
356
+ /* Pop-out */
357
+ --dash-popout-title-color: #ffffff;
358
+
359
+ /* Widget card */
360
+ --dash-widget-title-color: #ffffff;
361
+ --dash-drag-handle-color: rgba(255,255,255,0.2);
362
+ --dash-widget-border-color: rgba(255,255,255,0.07);
363
+ --dash-widget-btn-bg: rgba(255,255,255,0.07);
364
+ --dash-widget-btn-color: rgba(255,255,255,0.5);
365
+
366
+ /* Grid */
367
+ --dash-placeholder-color: #6c63ff;
368
+ --dash-resize-handle-color: rgba(255,255,255,0.25);
216
369
  }
217
370
  ```
218
371
 
372
+ ### CSS Variable Reference
373
+
374
+ | CSS Variable | `DashboardTheme` field | Default |
375
+ |---|---|---|
376
+ | `--dash-bg` | `backgroundColor` | `#000000` |
377
+ | `--dash-panel-bg` | `panelColor` | `#1c1c1e` |
378
+ | `--dash-card-bg` | `widgetCardColor` | `#2c2c2e` |
379
+ | `--dash-fore-color` | `foreColor` | `#8e8e93` |
380
+ | `--dash-accent-color` | `accentColor` | `#0a84ff` |
381
+ | `--dash-danger-color` | `dangerColor` | `#ff453a` |
382
+ | `--dash-font-family` | `fontFamily` | system-ui stack |
383
+ | `--dash-tabs-container-color` | `tabsContainerColor` | `rgba(44,44,46,0.6)` |
384
+ | `--dash-tab-active-color` | `tabActiveColor` | `#3a3a3c` |
385
+ | `--dash-tab-active-text` | `tabActiveTextColor` | `#ffffff` |
386
+ | `--dash-tab-hover-text` | `tabHoverTextColor` | `#e5e5ea` |
387
+ | `--dash-add-widget-text` | `addWidgetButtonTextColor` | `#ffffff` |
388
+ | `--dash-popout-title-color` | `popoutTitleColor` | `#ffffff` |
389
+ | `--dash-widget-title-color` | `widgetTitleColor` | `#ffffff` |
390
+ | `--dash-drag-handle-color` | `dragHandleColor` | `rgba(255,255,255,0.2)` |
391
+ | `--dash-widget-border-color` | `widgetBorderColor` | `rgba(255,255,255,0.07)` |
392
+ | `--dash-widget-btn-bg` | `widgetButtonBgColor` | `rgba(255,255,255,0.07)` |
393
+ | `--dash-widget-btn-color` | `widgetButtonColor` | `rgba(255,255,255,0.5)` |
394
+ | `--dash-placeholder-color` | `placeholderColor` | `#0a84ff` |
395
+ | `--dash-resize-handle-color` | `resizeHandleColor` | `rgba(255,255,255,0.25)` |
396
+
219
397
  ---
220
398
 
221
399
  ## Pop-out Windows
222
400
 
223
- Every workspace tab has a **pop-out** button (the arrow icon, visible on hover). Clicking it opens that workspace in a separate browser window.
401
+ Every workspace tab shows a pop-out button (arrow icon, visible on hover). Clicking it opens that workspace in a new browser window.
224
402
 
225
- The popped-out window:
226
- - shows only that one workspace — no tabs, no "Add Widget" bar, just the grid
227
- - has its **own independent card layout** — dragging and resizing cards there does not affect any other window
228
- - **does** receive structural changes from other windows in real time
403
+ The pop-out window:
229
404
 
230
- ### What syncs and what doesn't
405
+ - Shows only that workspace — no tabs, no Add Widget button, just the grid.
406
+ - Has its **own independent layout** — moving cards there never affects the main window.
407
+ - **Receives** structural changes (add/remove widget, metadata edits) from all other windows in real time.
231
408
 
232
- The library separates two kinds of state:
409
+ ### Sync behaviour
233
410
 
234
- | State | Synced across windows? | Stored where |
411
+ | State | Synced across windows? | Stored in |
235
412
  |---|---|---|
236
- | Page list (add / remove / rename workspace) | Yes | `ogidor_shared` in localStorage |
237
- | Widget list (add / remove a card) | Yes | `ogidor_shared` in localStorage |
238
- | Widget metadata (title, card color, data) | Yes | `ogidor_shared` in localStorage |
239
- | Card positions and sizes (drag / resize) | No — local only | `ogidor_positions_<windowId>` |
240
-
241
- **Example:** you have your main dashboard open in Tab A, and you pop out "Workspace 2" into Window B.
242
-
243
- - You drag a card in **Window B** → it stays where you put it in B; Tab A is completely unaffected
244
- - You add a widget from **Tab A** → the new card appears in **Window B** at its default position
245
- - You delete a widget in **Window B** → it disappears from **Tab A** immediately
246
- - Each window remembers its own layout between page reloads
413
+ | Page list (add / remove / rename) | Yes | `ogidor_shared` |
414
+ | Widget list (add / remove) | Yes | `ogidor_shared` |
415
+ | Widget metadata (title, cardColor, data) | Yes | `ogidor_shared` |
416
+ | Card positions and sizes | No — per window | `ogidor_positions_<windowId>` |
247
417
 
248
- To pop out a workspace programmatically:
418
+ To pop out programmatically:
249
419
 
250
420
  ```typescript
251
421
  this.state.popOutPage(pageId);
@@ -255,32 +425,39 @@ this.state.popOutPage(pageId);
255
425
 
256
426
  ## Persisting Layouts
257
427
 
258
- The library automatically persists state to `localStorage` on every change using two keys:
428
+ State is saved to `localStorage` automatically on every change.
259
429
 
260
- | Key | Contains |
430
+ | Key | Contents |
261
431
  |---|---|
262
- | `ogidor_shared` | Page list and widget metadata (titles, colors, data). Shared across all windows. |
263
- | `ogidor_positions_main` | Card positions/sizes for the main window. |
264
- | `ogidor_positions_<pageId>` | Card positions/sizes for each pop-out window, keyed by the workspace id. |
432
+ | `ogidor_shared` | Page list and widget metadata. Shared across all windows. |
433
+ | `ogidor_positions_main` | Grid positions for the main window. |
434
+ | `ogidor_positions_<pageId>` | Grid positions for each pop-out window. |
265
435
 
266
- To save the full current layout to a backend and restore it later:
436
+ To save and restore via a backend:
267
437
 
268
438
  ```typescript
269
- // Save (includes this window's positions)
439
+ // Save
270
440
  const json = this.dash.serializeLayout();
271
- await myApi.saveLayout(json);
441
+ await api.save(json);
272
442
 
273
443
  // Restore
274
- const json = await myApi.loadLayout();
444
+ const json = await api.load();
275
445
  this.dash.stateService.loadLayout(JSON.parse(json));
276
446
  ```
277
447
 
278
448
  ---
279
449
 
280
- ## Example: Full Integration
450
+ ## Full Integration Example
281
451
 
282
452
  ```typescript
283
- // app.component.ts
453
+ import { Component, ViewChild } from '@angular/core';
454
+ import {
455
+ DashboardComponent,
456
+ DashboardStateService,
457
+ DashboardTheme,
458
+ Widget,
459
+ } from '@ogidor/dashboard';
460
+
284
461
  @Component({
285
462
  selector: 'app-root',
286
463
  template: `
@@ -294,32 +471,38 @@ this.dash.stateService.loadLayout(JSON.parse(json));
294
471
  </ng-template>
295
472
  </app-dashboard>
296
473
 
297
- <!-- Your own add/edit dialogs -->
298
- <my-add-widget-dialog
474
+ <my-add-dialog
299
475
  *ngIf="addOpen"
300
476
  (confirmed)="onAddConfirmed($event)"
301
477
  (cancelled)="addOpen = false"
302
- ></my-add-widget-dialog>
478
+ ></my-add-dialog>
303
479
 
304
- <my-edit-widget-dialog
480
+ <my-edit-dialog
305
481
  *ngIf="editTarget"
306
482
  [widget]="editTarget"
307
483
  (confirmed)="onEditConfirmed($event)"
308
484
  (cancelled)="editTarget = null"
309
- ></my-edit-widget-dialog>
485
+ ></my-edit-dialog>
310
486
  `,
311
487
  })
312
488
  export class AppComponent {
313
- theme: DashboardTheme = { accentColor: '#6c63ff' };
489
+ @ViewChild(DashboardComponent) dash!: DashboardComponent;
490
+
491
+ theme: DashboardTheme = {
492
+ accentColor: '#6c63ff',
493
+ tabActiveColor: '#2a2a38',
494
+ widgetTitleColor: '#f0f0f0',
495
+ };
496
+
314
497
  addOpen = false;
315
498
  editTarget: Widget | null = null;
316
499
 
317
500
  constructor(private state: DashboardStateService) {}
318
501
 
319
- openAddDialog() { this.addOpen = true; }
320
- openEditDialog(w: Widget) { this.editTarget = w; }
502
+ openAddDialog() { this.addOpen = true; }
503
+ openEditDialog(w: Widget) { this.editTarget = w; }
321
504
 
322
- onAddConfirmed(payload: { title: string; data: any; cols: number; rows: number }) {
505
+ onAddConfirmed(payload: Partial<Widget> & { title: string }) {
323
506
  this.state.addWidget(payload);
324
507
  this.addOpen = false;
325
508
  }
@@ -328,11 +511,30 @@ export class AppComponent {
328
511
  this.state.updateWidget(this.editTarget!.id, patch);
329
512
  this.editTarget = null;
330
513
  }
514
+
515
+ saveLayout() {
516
+ return this.dash.serializeLayout();
517
+ }
331
518
  }
332
519
  ```
333
520
 
334
521
  ---
335
522
 
523
+ ## Development
524
+
525
+ ```bash
526
+ # Install dependencies
527
+ npm install
528
+
529
+ # Build the library
530
+ npm run build:lib
531
+
532
+ # Run the local demo app
533
+ npm start
534
+ ```
535
+
536
+ ---
537
+
336
538
  ## License
337
539
 
338
540
  MIT
package/app/models.d.ts CHANGED
@@ -50,4 +50,30 @@ export interface DashboardTheme {
50
50
  dangerColor?: string;
51
51
  /** Font family applied to the whole dashboard. Default: system-ui stack */
52
52
  fontFamily?: string;
53
+ /** Background of the tabs pill container. Default: `rgba(44,44,46,0.6)` */
54
+ tabsContainerColor?: string;
55
+ /** Background of the active/selected tab. Default: `#3a3a3c` */
56
+ tabActiveColor?: string;
57
+ /** Text color of the active tab. Default: `#ffffff` */
58
+ tabActiveTextColor?: string;
59
+ /** Text color of hovered (inactive) tabs. Default: `#e5e5ea` */
60
+ tabHoverTextColor?: string;
61
+ /** Text color of the "Add Widget" button. Default: `#ffffff` */
62
+ addWidgetButtonTextColor?: string;
63
+ /** Text color of the pop-out page title. Default: `#ffffff` */
64
+ popoutTitleColor?: string;
65
+ /** Text color of the widget card title. Default: `#ffffff` */
66
+ widgetTitleColor?: string;
67
+ /** Color of the drag-handle dots on a widget card. Default: `rgba(255,255,255,0.2)` */
68
+ dragHandleColor?: string;
69
+ /** Border color of widget cards. Default: `rgba(255,255,255,0.07)` */
70
+ widgetBorderColor?: string;
71
+ /** Background of the icon buttons (edit/remove) on widget hover. Default: `rgba(255,255,255,0.07)` */
72
+ widgetButtonBgColor?: string;
73
+ /** Default text color of icon buttons. Default: `rgba(255,255,255,0.5)` */
74
+ widgetButtonColor?: string;
75
+ /** Color of the drag/resize placeholder overlay. Default: same as accentColor */
76
+ placeholderColor?: string;
77
+ /** Color of the resize handle icon. Default: `rgba(255,255,255,0.25)` */
78
+ resizeHandleColor?: string;
53
79
  }