ngx-dev-toolbar 1.0.0-beta.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +254 -4
  2. package/eslint.config.cjs +47 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +8 -23
  5. package/project.json +37 -0
  6. package/src/components/button/button.component.scss +49 -0
  7. package/src/components/button/button.component.ts +36 -0
  8. package/src/components/card/card.component.scss +18 -0
  9. package/src/components/card/card.component.ts +30 -0
  10. package/src/components/clickable-card/clickable-card.component.scss +39 -0
  11. package/src/components/clickable-card/clickable-card.component.ts +34 -0
  12. package/src/components/icons/angular-icon.component.ts +35 -0
  13. package/src/components/icons/bug-icon.component.ts +23 -0
  14. package/src/components/icons/code-icon.component.ts +24 -0
  15. package/src/components/icons/database-icon.component.ts +27 -0
  16. package/src/components/icons/discord-icon.component.ts +23 -0
  17. package/src/components/icons/docs-icon.component.ts +23 -0
  18. package/src/components/icons/export-icon.component.ts +23 -0
  19. package/src/components/icons/gauge-icon.component.ts +27 -0
  20. package/src/components/icons/gear-icon.component.ts +27 -0
  21. package/src/components/icons/git-branch-icon.component.ts +27 -0
  22. package/src/components/icons/icon.component.ts +129 -0
  23. package/src/components/icons/icon.models.ts +27 -0
  24. package/src/components/icons/import-icon.component.ts +23 -0
  25. package/src/components/icons/layout-icon.component.ts +24 -0
  26. package/src/components/icons/lightbulb-icon.component.ts +23 -0
  27. package/src/components/icons/lighting-icon.component.ts +24 -0
  28. package/src/components/icons/moon-icon.component.ts +27 -0
  29. package/src/components/icons/network-icon.component.ts +27 -0
  30. package/src/components/icons/puzzle-icon.component.ts +27 -0
  31. package/src/components/icons/refresh-icon.component.ts +27 -0
  32. package/src/components/icons/star-icon.component.ts +27 -0
  33. package/src/components/icons/sun-icon.component.ts +27 -0
  34. package/src/components/icons/terminal-icon.component.ts +27 -0
  35. package/src/components/icons/toggle-left-icon.component.ts +27 -0
  36. package/src/components/icons/translate-icon.component.ts +23 -0
  37. package/src/components/icons/trash-icon.component.ts +23 -0
  38. package/src/components/icons/users-icon.component.ts +27 -0
  39. package/src/components/input/input.component.ts +67 -0
  40. package/src/components/link-button/link-button.component.scss +36 -0
  41. package/src/components/link-button/link-button.component.ts +29 -0
  42. package/src/components/select/select.component.scss +162 -0
  43. package/src/components/select/select.component.ts +127 -0
  44. package/src/components/tool-button/tool-button.component.scss +67 -0
  45. package/src/components/tool-button/tool-button.component.ts +126 -0
  46. package/src/components/toolbar-tool/toolbar-tool.component.scss +9 -0
  47. package/src/components/toolbar-tool/toolbar-tool.component.ts +169 -0
  48. package/src/components/toolbar-tool/toolbar-tool.models.ts +33 -0
  49. package/src/components/window/window.component.scss +95 -0
  50. package/src/components/window/window.component.ts +69 -0
  51. package/src/dev-toolbar-state.service.ts +89 -0
  52. package/src/dev-toolbar.component.scss +22 -0
  53. package/src/dev-toolbar.component.ts +105 -0
  54. package/src/index.ts +10 -0
  55. package/src/models/dev-tools.interface.ts +19 -0
  56. package/src/styles.scss +342 -0
  57. package/src/test-setup.ts +12 -0
  58. package/src/tools/feature-flags-tool/feature-flags-internal.service.ts +96 -0
  59. package/src/tools/feature-flags-tool/feature-flags-tool.component.ts +261 -0
  60. package/src/tools/feature-flags-tool/feature-flags.models.ts +10 -0
  61. package/src/tools/feature-flags-tool/feature-flags.service.ts +28 -0
  62. package/src/tools/home-tool/home-tool.component.scss +67 -0
  63. package/src/tools/home-tool/home-tool.component.ts +197 -0
  64. package/{tools/settings-tool/settings.models.d.ts → src/tools/home-tool/settings.models.ts} +1 -1
  65. package/src/tools/home-tool/settings.service.spec.ts +59 -0
  66. package/src/tools/home-tool/settings.service.ts +21 -0
  67. package/src/tools/language-tool/language-internal.service.ts +51 -0
  68. package/src/tools/language-tool/language-tool.component.scss +7 -0
  69. package/src/tools/language-tool/language-tool.component.ts +71 -0
  70. package/src/tools/language-tool/language.models.ts +4 -0
  71. package/src/tools/language-tool/language.service.ts +26 -0
  72. package/src/utils/storage.service.spec.ts +179 -0
  73. package/src/utils/storage.service.ts +80 -0
  74. package/tsconfig.json +28 -0
  75. package/tsconfig.lib.json +28 -0
  76. package/tsconfig.lib.prod.json +9 -0
  77. package/tsconfig.spec.json +29 -0
  78. package/vite.config.mts +27 -0
  79. package/components/button/button.component.d.ts +0 -12
  80. package/components/icons/angular-icon.component.d.ts +0 -5
  81. package/components/icons/bug-icon.component.d.ts +0 -6
  82. package/components/icons/code-icon.component.d.ts +0 -6
  83. package/components/icons/database-icon.component.d.ts +0 -6
  84. package/components/icons/gauge-icon.component.d.ts +0 -6
  85. package/components/icons/gear-icon.component.d.ts +0 -6
  86. package/components/icons/git-branch-icon.component.d.ts +0 -6
  87. package/components/icons/icon.component.d.ts +0 -9
  88. package/components/icons/icon.models.d.ts +0 -1
  89. package/components/icons/layout-icon.component.d.ts +0 -6
  90. package/components/icons/lighting-icon.component.d.ts +0 -6
  91. package/components/icons/moon-icon.component.d.ts +0 -6
  92. package/components/icons/network-icon.component.d.ts +0 -6
  93. package/components/icons/puzzle-icon.component.d.ts +0 -6
  94. package/components/icons/refresh-icon.component.d.ts +0 -6
  95. package/components/icons/star-icon.component.d.ts +0 -6
  96. package/components/icons/sun-icon.component.d.ts +0 -6
  97. package/components/icons/terminal-icon.component.d.ts +0 -6
  98. package/components/icons/toggle-left-icon.component.d.ts +0 -6
  99. package/components/icons/users-icon.component.d.ts +0 -6
  100. package/components/input/input.component.d.ts +0 -10
  101. package/components/select/select.component.d.ts +0 -14
  102. package/components/tool-button/tool-button.component.d.ts +0 -23
  103. package/components/toolbar-tool/toolbar-tool.component.d.ts +0 -28
  104. package/components/window/window.component.d.ts +0 -16
  105. package/components/window/window.models.d.ts +0 -20
  106. package/dev-toolbar-state.service.d.ts +0 -18
  107. package/dev-toolbar.component.d.ts +0 -17
  108. package/fesm2022/ngx-dev-toolbar.mjs +0 -2073
  109. package/fesm2022/ngx-dev-toolbar.mjs.map +0 -1
  110. package/index.d.ts +0 -3
  111. package/tools/feature-flags-tool/feature-flags-tool.component.d.ts +0 -33
  112. package/tools/feature-flags-tool/feature-flags.models.d.ts +0 -9
  113. package/tools/feature-flags-tool/feature-flags.service.d.ts +0 -35
  114. package/tools/settings-tool/settings-tool.component.d.ts +0 -15
  115. package/tools/settings-tool/settings.service.d.ts +0 -10
  116. package/utils/storage.service.d.ts +0 -9
@@ -0,0 +1,126 @@
1
+ import {
2
+ animate,
3
+ state,
4
+ style,
5
+ transition,
6
+ trigger,
7
+ } from '@angular/animations';
8
+ import {
9
+ ChangeDetectionStrategy,
10
+ Component,
11
+ ElementRef,
12
+ computed,
13
+ inject,
14
+ input,
15
+ output,
16
+ signal,
17
+ } from '@angular/core';
18
+ import { DevToolbarStateService } from '../../dev-toolbar-state.service';
19
+
20
+ @Component({
21
+ selector: 'ndt-tool-button',
22
+ standalone: true,
23
+ template: `
24
+ <button
25
+ class="tool-button"
26
+ [class.tool-button--active]="isActive()"
27
+ [class.tool-button--focus]="isFocused()"
28
+ (mouseenter)="onMouseEnter()"
29
+ (focusin)="onFocus()"
30
+ (focusout)="onBlur()"
31
+ (mouseleave)="onMouseLeave()"
32
+ (keydown.escape)="onEscape()"
33
+ >
34
+ @if (isTooltipVisible()) {
35
+ <span
36
+ class="tooltip"
37
+ [@tooltipAnimation]="tooltipState ? 'visible' : 'hidden'"
38
+ >
39
+ {{ tooltip() }}
40
+ </span>
41
+ }
42
+ <ng-content />
43
+ </button>
44
+ `,
45
+ styleUrls: ['./tool-button.component.scss'],
46
+ animations: [
47
+ trigger('tooltipAnimation', [
48
+ state(
49
+ 'hidden',
50
+ style({
51
+ opacity: 0,
52
+ transform: 'translateX(-50%) translateY(1rem)',
53
+ })
54
+ ),
55
+ state(
56
+ 'visible',
57
+ style({
58
+ opacity: 1,
59
+ transform: 'translateX(-50%) translateY(0)',
60
+ })
61
+ ),
62
+ transition('hidden => visible', [animate('200ms ease-out')]),
63
+ transition('visible => hidden', [animate('150ms ease-in')]),
64
+ ]),
65
+ ],
66
+ changeDetection: ChangeDetectionStrategy.OnPush,
67
+ })
68
+ export class DevToolbarToolButtonComponent {
69
+ // Injects
70
+ private readonly state = inject(DevToolbarStateService);
71
+ private readonly elementRef = inject(ElementRef);
72
+
73
+ // Inputs
74
+ readonly title = input.required<string>();
75
+ readonly toolId = input.required<string>();
76
+
77
+ // Outputs
78
+ readonly open = output<void>();
79
+
80
+ // Signals
81
+ readonly isActive = computed(
82
+ () => this.state.activeToolId() === this.toolId()
83
+ );
84
+ readonly isToolbarVisible = this.state.isVisible;
85
+
86
+ readonly isFocused = signal(false);
87
+ readonly tooltip = computed(
88
+ () =>
89
+ this.elementRef.nativeElement.parentElement?.getAttribute(
90
+ 'data-tooltip'
91
+ ) ?? ''
92
+ );
93
+ readonly isTooltipVisible = computed(
94
+ () => this.tooltip() && !this.isActive()
95
+ );
96
+
97
+ // Properties
98
+ protected tooltipState = false;
99
+ private readonly hideDelay = 3000;
100
+
101
+ // Public methods
102
+ onClick(): void {
103
+ this.isFocused.set(false);
104
+ this.open.emit();
105
+ }
106
+
107
+ onMouseEnter(): void {
108
+ this.tooltipState = true;
109
+ }
110
+
111
+ onMouseLeave(): void {
112
+ this.tooltipState = false;
113
+ }
114
+
115
+ onEscape(): void {
116
+ this.isFocused.set(false);
117
+ }
118
+
119
+ onFocus(): void {
120
+ this.isFocused.set(true);
121
+ }
122
+
123
+ onBlur(): void {
124
+ this.isFocused.set(false);
125
+ }
126
+ }
@@ -0,0 +1,9 @@
1
+ @use '../../styles' as devtools;
2
+
3
+ .tool {
4
+ position: relative;
5
+ }
6
+
7
+ .trigger {
8
+ cursor: pointer;
9
+ }
@@ -0,0 +1,169 @@
1
+ import { animate, style, transition, trigger } from '@angular/animations';
2
+ import { CdkConnectedOverlay, OverlayModule } from '@angular/cdk/overlay';
3
+ import {
4
+ ChangeDetectionStrategy,
5
+ Component,
6
+ ContentChild,
7
+ ElementRef,
8
+ ViewChild,
9
+ computed,
10
+ inject,
11
+ input,
12
+ } from '@angular/core';
13
+ import { DevToolbarStateService } from '../../dev-toolbar-state.service';
14
+ import { DevToolbarIconComponent } from '../icons/icon.component';
15
+ import { IconName } from '../icons/icon.models';
16
+ import { DevToolbarToolButtonComponent } from '../tool-button/tool-button.component';
17
+ import { DevToolbarWindowComponent } from '../window/window.component';
18
+ import { DevToolbarWindowOptions } from './toolbar-tool.models';
19
+
20
+ @Component({
21
+ selector: 'ndt-toolbar-tool',
22
+ standalone: true,
23
+ imports: [
24
+ CdkConnectedOverlay,
25
+ OverlayModule,
26
+ DevToolbarWindowComponent,
27
+ DevToolbarToolButtonComponent,
28
+ DevToolbarIconComponent,
29
+ ],
30
+ template: `
31
+ <div #trigger="cdkOverlayOrigin" class="dev-toolbar-tool" cdkOverlayOrigin>
32
+ <div
33
+ class="dev-toolbar-tool__icon"
34
+ (click)="onOpen()"
35
+ (keydown.enter)="onOpen()"
36
+ (keydown.space)="onOpen()"
37
+ tabindex="0"
38
+ >
39
+ <div #buttonContainer [attr.data-tooltip]="title()">
40
+ @if (icon()) {
41
+ <ndt-tool-button [title]="title()" [toolId]="options().id">
42
+ <ndt-icon [name]="icon()" />
43
+ </ndt-tool-button>
44
+ } @else {
45
+ <ng-content select="ndt-tool-button"></ng-content>
46
+ }
47
+ </div>
48
+ </div>
49
+
50
+ @if (isActive()) {
51
+ <ng-template
52
+ #contentTemplate
53
+ [cdkConnectedOverlayOrigin]="trigger"
54
+ [cdkConnectedOverlayOpen]="isActive()"
55
+ [cdkConnectedOverlayPositions]="positions()"
56
+ [cdkConnectedOverlayWidth]="width()"
57
+ [cdkConnectedOverlayHeight]="height()"
58
+ cdkConnectedOverlay
59
+ >
60
+ <ndt-window [@slideAnimation] [config]="options()" (close)="onClose()">
61
+ <ng-content />
62
+ </ndt-window>
63
+ </ng-template>
64
+ }
65
+ </div>
66
+ `,
67
+ styleUrl: './toolbar-tool.component.scss',
68
+ changeDetection: ChangeDetectionStrategy.OnPush,
69
+ animations: [
70
+ trigger('slideAnimation', [
71
+ transition(':enter', [
72
+ style({
73
+ transform: 'translateY(20px)',
74
+ opacity: 0,
75
+ }),
76
+ animate(
77
+ '400ms cubic-bezier(0.4, 0, 0.2, 1)',
78
+ style({
79
+ transform: 'translateY(0)',
80
+ opacity: 1,
81
+ })
82
+ ),
83
+ ]),
84
+ transition(':leave', [
85
+ style({
86
+ transform: 'translateY(0)',
87
+ opacity: 1,
88
+ }),
89
+ animate(
90
+ '400ms cubic-bezier(0.4, 0, 0.2, 1)',
91
+ style({
92
+ transform: 'translateY(20px)',
93
+ opacity: 0,
94
+ })
95
+ ),
96
+ ]),
97
+ ]),
98
+ ],
99
+ })
100
+ export class DevToolbarToolComponent {
101
+ state = inject(DevToolbarStateService);
102
+ @ViewChild('buttonContainer') buttonContainer!: ElementRef;
103
+
104
+ @ContentChild(DevToolbarToolButtonComponent)
105
+ buttonComponent!: DevToolbarToolButtonComponent;
106
+
107
+ options = input.required<DevToolbarWindowOptions>();
108
+ icon = input.required<IconName>();
109
+ title = input.required<string>();
110
+ isActive = computed(() => this.state.activeToolId() === this.options().id);
111
+ height = computed(() => {
112
+ switch (this.options().size) {
113
+ case 'small':
114
+ return 320;
115
+ case 'medium':
116
+ return 480;
117
+ case 'tall':
118
+ return 620;
119
+ case 'large':
120
+ return 620;
121
+ default:
122
+ return 480;
123
+ }
124
+ });
125
+
126
+ width = computed(() => {
127
+ switch (this.options().size) {
128
+ case 'small':
129
+ return 320;
130
+ case 'medium':
131
+ return 480;
132
+ case 'tall':
133
+ return 480;
134
+ case 'large':
135
+ return 620;
136
+ default:
137
+ return 400;
138
+ }
139
+ });
140
+ positions = computed(() => {
141
+ const triggerXPosition = this.getButtonContainerXPosition();
142
+ const windowCenter = window.innerWidth / 2;
143
+ const offsetX = windowCenter - triggerXPosition - 22;
144
+ return [
145
+ {
146
+ originX: 'center' as const,
147
+ originY: 'center' as const,
148
+ overlayX: 'center' as const,
149
+ overlayY: 'center' as const,
150
+ offsetY: -(Math.ceil(this.height() / 2) + 36),
151
+ offsetX,
152
+ },
153
+ ];
154
+ });
155
+
156
+ onOpen(): void {
157
+ this.state.setActiveTool(this.options().id);
158
+ }
159
+
160
+ onClose(): void {
161
+ this.state.setActiveTool(null);
162
+ }
163
+
164
+ getButtonContainerXPosition(): number {
165
+ const buttonContainerRect =
166
+ this.buttonContainer?.nativeElement?.getBoundingClientRect();
167
+ return buttonContainerRect?.left ?? 0;
168
+ }
169
+ }
@@ -0,0 +1,33 @@
1
+ import { IconName } from '../icons/icon.models';
2
+
3
+ export interface DevToolbarWindowPosition {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export interface DevToolbarWindowOptions {
8
+ id: string;
9
+ /**
10
+ * The title of the window, this can be different from the name of the tool
11
+ */
12
+ title: string;
13
+ description?: string;
14
+ isClosable?: boolean;
15
+ isMaximizable?: boolean;
16
+ isMinimizable?: boolean;
17
+ placement?:
18
+ | 'bottom-left'
19
+ | 'bottom-center'
20
+ | 'bottom-right'
21
+ | 'top-left'
22
+ | 'top-center'
23
+ | 'top-right';
24
+ size?: 'small' | 'medium' | 'tall' | 'large';
25
+ isBeta?: boolean;
26
+ }
27
+
28
+ export interface DevToolbarToolOptions {
29
+ icon: IconName;
30
+ name: string;
31
+ windowOptions: DevToolbarWindowOptions;
32
+ isBeta?: boolean;
33
+ }
@@ -0,0 +1,95 @@
1
+ @use '../../styles' as *;
2
+ @use 'sass:map';
3
+
4
+ :host {
5
+ display: block;
6
+ width: 100%;
7
+ }
8
+
9
+ .window {
10
+ box-sizing: border-box;
11
+ display: flex;
12
+ flex-direction: column;
13
+ width: 100%;
14
+ height: 100%;
15
+ background: var(--ndt-bg-primary);
16
+ border: 1px solid var(--ndt-border-primary);
17
+ border-radius: var(--ndt-border-radius-large);
18
+ padding: var(--ndt-window-padding);
19
+ font-family: $font-family;
20
+ color: var(--ndt-text-secondary);
21
+ z-index: #{map.get($z-indices, window)};
22
+ box-shadow: var(--ndt-shadow-window);
23
+ }
24
+
25
+ .header {
26
+ display: flex;
27
+ flex-direction: row;
28
+ justify-content: space-between;
29
+ align-items: flex-start;
30
+
31
+ &__title {
32
+ display: flex;
33
+ align-items: center;
34
+ gap: var(--ndt-spacing-sm);
35
+
36
+ .beta-tag {
37
+ font-size: var(--ndt-font-size-xxs, 0.65rem);
38
+ background: var(--ndt-purple, #8b5cf6);
39
+ color: var(--ndt-text-on-primary);
40
+ padding: 1px 4px;
41
+ margin-left: var(--ndt-spacing-xs);
42
+ border-radius: var(--ndt-border-radius-small);
43
+ font-weight: 500;
44
+ text-transform: uppercase;
45
+ letter-spacing: 0.5px;
46
+ }
47
+ }
48
+ &__description {
49
+ font-size: var(--ndt-font-size-sm);
50
+ color: var(--ndt-text-muted);
51
+ }
52
+
53
+ &__content {
54
+ display: flex;
55
+ flex-direction: column;
56
+ }
57
+
58
+ &__controls {
59
+ display: flex;
60
+ gap: var(--ndt-spacing-sm);
61
+ }
62
+ }
63
+
64
+ .content {
65
+ flex: 1;
66
+ overflow: auto;
67
+ }
68
+
69
+ .divider {
70
+ height: 1px;
71
+ background-color: var(--ndt-border-primary);
72
+ margin-bottom: var(--ndt-spacing-md);
73
+ margin-top: var(--ndt-spacing-md);
74
+ }
75
+
76
+ .control {
77
+ background: none;
78
+ border: none;
79
+ color: var(--ndt-text-secondary);
80
+ cursor: pointer;
81
+ padding: var(--ndt-spacing-xs) var(--ndt-spacing-sm);
82
+ border-radius: var(--ndt-border-radius-small);
83
+ font-size: var(--ndt-font-size-md);
84
+ line-height: 1;
85
+ transition: var(--ndt-transition-smooth);
86
+
87
+ &:hover {
88
+ background: var(--ndt-hover-bg);
89
+ color: var(--ndt-text-primary);
90
+ }
91
+
92
+ &--close:hover {
93
+ background: var(--ndt-hover-danger);
94
+ }
95
+ }
@@ -0,0 +1,69 @@
1
+ import { Component, computed, inject, input, output } from '@angular/core';
2
+ import { DevToolbarStateService } from '../../dev-toolbar-state.service';
3
+ import { DevToolbarWindowOptions } from '../toolbar-tool/toolbar-tool.models';
4
+
5
+ @Component({
6
+ selector: 'ndt-window',
7
+ standalone: true,
8
+ template: `
9
+ <div class="window dev-toolbar" [attr.data-theme]="theme()">
10
+ <div class="header">
11
+ <div class="header__content">
12
+ <div class="header__title">
13
+ <h1>{{ config().title }}</h1>
14
+ @if (config().isBeta) {
15
+ <span class="beta-tag">BETA</span>
16
+ }
17
+ </div>
18
+ <p class="header__description">{{ config().description }}</p>
19
+ </div>
20
+ <div class="header__controls">
21
+ @if (config().isMinimizable) {
22
+ <button aria-label="Minimize" class="control" (click)="onMinimize()">
23
+
24
+ </button>
25
+ } @if (config().isMaximizable) {
26
+ <button aria-label="Maximize" class="control" (click)="onMaximize()">
27
+
28
+ </button>
29
+ } @if (config().isClosable) {
30
+ <button
31
+ aria-label="Close"
32
+ class="control control--close"
33
+ (click)="onClose()"
34
+ >
35
+ ×
36
+ </button>
37
+ }
38
+ </div>
39
+ </div>
40
+
41
+ <div class="divider"></div>
42
+ <div class="content">
43
+ <ng-content></ng-content>
44
+ </div>
45
+ </div>
46
+ `,
47
+ styleUrls: ['./window.component.scss'],
48
+ })
49
+ export class DevToolbarWindowComponent {
50
+ readonly devToolbarStateService = inject(DevToolbarStateService);
51
+ readonly config = input.required<DevToolbarWindowOptions>();
52
+ readonly close = output<void>();
53
+ readonly maximize = output<void>();
54
+ readonly minimize = output<void>();
55
+
56
+ readonly theme = computed(() => this.devToolbarStateService.theme());
57
+
58
+ protected onClose(): void {
59
+ this.close.emit();
60
+ }
61
+
62
+ protected onMaximize(): void {
63
+ this.maximize.emit();
64
+ }
65
+
66
+ protected onMinimize(): void {
67
+ this.minimize.emit();
68
+ }
69
+ }
@@ -0,0 +1,89 @@
1
+ import { Injectable, computed, signal } from '@angular/core';
2
+
3
+ interface DevToolbarState {
4
+ isHidden: boolean;
5
+ activeToolId: string | null;
6
+ error: string | null;
7
+ theme: 'light' | 'dark';
8
+ delay: number;
9
+ }
10
+
11
+ @Injectable({
12
+ providedIn: 'root',
13
+ })
14
+ export class DevToolbarStateService {
15
+ // Initial state
16
+ private state = signal<DevToolbarState>({
17
+ isHidden: true,
18
+ activeToolId: null,
19
+ delay: 3000,
20
+ error: null,
21
+ theme: 'dark',
22
+ });
23
+
24
+ // Selectors
25
+ readonly isVisible = computed(
26
+ () => !this.state().isHidden || this.hasActiveTool()
27
+ );
28
+ readonly isDarkTheme = computed(() => this.state().theme === 'dark');
29
+ readonly activeToolId = computed(() => this.state().activeToolId);
30
+ readonly hasActiveTool = computed(() => this.state().activeToolId !== null);
31
+ readonly error = computed(() => this.state().error);
32
+ readonly theme = computed(() => this.state().theme);
33
+ /**
34
+ * The delay to hide the toolbar
35
+ */
36
+ readonly delay = computed(() => this.state().delay);
37
+
38
+ // State updates
39
+ setVisibility(isVisible: boolean): void {
40
+ if (isVisible) {
41
+ this.state.update((state) => ({
42
+ ...state,
43
+ isHidden: false,
44
+ }));
45
+ } else {
46
+ if (this.activeToolId() === null) {
47
+ setTimeout(() => {
48
+ this.state.update((state) => ({
49
+ ...state,
50
+ isHidden: true,
51
+ }));
52
+ }, this.state().delay);
53
+ }
54
+ }
55
+ }
56
+
57
+ setTheme(theme: 'light' | 'dark'): void {
58
+ this.state.update((state) => ({
59
+ ...state,
60
+ theme,
61
+ }));
62
+ }
63
+
64
+ setActiveTool(toolId: string | null): void {
65
+ this.state.update((state) => ({
66
+ ...state,
67
+ activeToolId: toolId,
68
+ }));
69
+
70
+ if (toolId === null) {
71
+ this.setVisibility(false);
72
+ } else {
73
+ this.setVisibility(true);
74
+ }
75
+ }
76
+
77
+ // Public actions
78
+ toggleTool(toolId: string | null): void {
79
+ const currentToolId = this.activeToolId();
80
+ this.setActiveTool(currentToolId === toolId ? null : toolId);
81
+ }
82
+
83
+ toggleVisibility(): void {
84
+ this.state.update((state) => ({
85
+ ...state,
86
+ isHidden: !state.isHidden,
87
+ }));
88
+ }
89
+ }
@@ -0,0 +1,22 @@
1
+ @use './styles' as devtools;
2
+ @use 'sass:map';
3
+
4
+ .dev-toolbar {
5
+ position: fixed;
6
+ bottom: 0;
7
+ left: 50%;
8
+ z-index: map.get(devtools.$z-indices, toolbar);
9
+ transform: translateX(-50%);
10
+ display: flex;
11
+ pointer-events: auto;
12
+ background: var(--ndt-bg-primary);
13
+ border: 1px solid var(--ndt-border-primary);
14
+ border-radius: map.get(map.get(devtools.$dimensions, border-radius), full);
15
+ box-shadow: var(--ndt-shadow-toolbar);
16
+ height: map.get(devtools.$dimensions, toolbar-height);
17
+ overflow: hidden;
18
+
19
+ &--active {
20
+ opacity: 1;
21
+ }
22
+ }