Skip to content

Commit e18fd58

Browse files
committed
refactor(sidebar): signal inputs, host bindings, cleanup, use inert attribute
1 parent c7c4035 commit e18fd58

File tree

1 file changed

+93
-70
lines changed

1 file changed

+93
-70
lines changed

projects/coreui-angular/src/lib/sidebar/sidebar/sidebar.component.ts

+93-70
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import {
22
booleanAttribute,
33
Component,
4-
EventEmitter,
5-
HostBinding,
4+
computed,
5+
effect,
66
inject,
7-
Input,
7+
input,
8+
linkedSignal,
89
OnChanges,
910
OnDestroy,
1011
OnInit,
11-
Output,
12+
output,
1213
Renderer2,
14+
signal,
1315
SimpleChanges
1416
} from '@angular/core';
1517
import { DOCUMENT } from '@angular/common';
@@ -23,7 +25,11 @@ import { SidebarBackdropService } from '../sidebar-backdrop/sidebar-backdrop.ser
2325
selector: 'c-sidebar',
2426
exportAs: 'cSidebar',
2527
template: '<ng-content />',
26-
host: { class: 'sidebar' }
28+
host: {
29+
class: 'sidebar',
30+
'[class]': 'hostClasses()',
31+
'[attr.inert]': '!this.sidebarState.visible || null'
32+
}
2733
})
2834
export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
2935
readonly #document = inject<Document>(DOCUMENT);
@@ -32,14 +38,13 @@ export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
3238
readonly #sidebarService = inject(SidebarService);
3339
readonly #backdropService = inject(SidebarBackdropService);
3440

35-
#visible = false;
3641
#onMobile = false;
3742
#layoutChangeSubscription!: Subscription;
3843
#stateToggleSubscription!: Subscription;
3944

40-
state: ISidebarAction = {
45+
readonly state = signal<ISidebarAction>({
4146
sidebar: this
42-
};
47+
});
4348

4449
#stateInitial = {
4550
narrow: false,
@@ -48,105 +53,120 @@ export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
4853
};
4954

5055
/**
51-
* Sets if the color of text should be colored for a light or dark background. [docs]
52-
*
53-
* @type 'dark' | 'light'
56+
* Sets if the color of text should be colored for a light or dark background.
57+
* @return 'dark' | 'light'
5458
*/
55-
@Input() colorScheme?: 'dark' | 'light';
59+
readonly colorScheme = input<'dark' | 'light'>();
5660

5761
/**
58-
* Sets html attribute id. [docs]
59-
*
60-
* @type string
62+
* Sets html attribute id.
63+
* @return string
6164
*/
62-
@Input() id?: string;
65+
readonly id = input<string>();
6366

6467
/**
65-
* Make sidebar narrow. [docs]
66-
* @type boolean
68+
* Make sidebar narrow.
69+
* @return boolean
6770
* @default false
6871
*/
69-
@Input({ transform: booleanAttribute }) narrow: boolean = false;
72+
readonly narrowInput = input(false, { transform: booleanAttribute, alias: 'narrow' });
73+
74+
readonly #narrow = linkedSignal(this.narrowInput);
75+
76+
set narrow(value) {
77+
this.#narrow.set(value);
78+
}
79+
80+
get narrow() {
81+
return this.#narrow();
82+
}
7083

7184
/**
7285
* Set sidebar to overlaid variant.
73-
* @type boolean
86+
* @return boolean
7487
* @default false
7588
*/
76-
@Input({ transform: booleanAttribute }) overlaid: boolean = false;
89+
readonly overlaid = input(false, { transform: booleanAttribute });
7790

7891
/**
79-
* Components placement, there’s no default placement. [docs]
80-
* @type 'start' | 'end'
92+
* Components placement, there’s no default placement.
93+
* @return 'start' | 'end'
8194
*/
82-
@Input() placement?: 'start' | 'end';
95+
readonly placement = input<'start' | 'end'>();
8396

8497
/**
85-
* Place sidebar in non-static positions. [docs]
98+
* Place sidebar in non-static positions.
99+
* @return 'fixed' | 'sticky'
86100
* @default 'fixed'
87101
*/
88-
@Input() position: 'fixed' | 'sticky' = 'fixed';
102+
readonly position = input<'fixed' | 'sticky'>('fixed');
89103

90104
/**
91-
* Size the component small, large, or extra large. [docs]
105+
* Size the component small, large, or extra large.
106+
* @return 'sm' | 'lg' | 'xl'
92107
*/
93-
@Input() size?: 'sm' | 'lg' | 'xl';
108+
readonly size = input<'sm' | 'lg' | 'xl'>();
94109

95110
/**
96-
* Expand narrowed sidebar on hover. [docs]
111+
* Expand narrowed sidebar on hover.
97112
* @type boolean
98113
* @default false
99114
*/
100-
@Input({ transform: booleanAttribute }) unfoldable: boolean = false;
115+
readonly unfoldableInput = input(false, { transform: booleanAttribute, alias: 'unfoldable' });
116+
117+
readonly unfoldable = linkedSignal({
118+
source: this.unfoldableInput,
119+
computation: (value) => value
120+
});
101121

102122
/**
103-
* Toggle the visibility of sidebar component. [docs]
123+
* Toggle the visibility of sidebar component.
104124
* @type boolean
105125
* @default false
106126
*/
107-
@Input({ transform: booleanAttribute })
127+
readonly visibleInput = input(false, { transform: booleanAttribute, alias: 'visible' });
128+
129+
readonly #visible = linkedSignal(this.visibleInput);
130+
131+
readonly #visibleEffect = effect(() => {
132+
this.visibleChange.emit(this.#visible());
133+
});
134+
108135
set visible(value: boolean) {
109-
const visible = value;
110-
if (this.#visible !== visible) {
111-
this.#visible = visible;
112-
this.visibleChange.emit(this.#visible);
113-
}
136+
this.#visible.set(value);
114137
}
115138

116139
get visible() {
117-
return this.#visible;
140+
return this.#visible();
118141
}
119142

120143
/**
121-
* Event emitted on visibility change. [docs]
122-
* @type boolean
144+
* Event emitted on visibility change.
145+
* @return boolean
123146
*/
124-
@Output() visibleChange = new EventEmitter<boolean>();
147+
readonly visibleChange = output<boolean>();
125148

126149
set sidebarState(value: ISidebarAction) {
127150
const newState = value;
128151
if ('toggle' in newState) {
129152
if (newState.toggle === 'visible') {
130-
newState.visible = !this.state.visible;
131-
this.visible = newState.visible;
153+
newState.visible = !this.state().visible;
154+
this.#visible.set(newState.visible);
132155
} else if (newState.toggle === 'unfoldable') {
133-
newState.unfoldable = !this.state.unfoldable;
134-
this.unfoldable = newState.unfoldable;
156+
newState.unfoldable = !this.state().unfoldable;
157+
this.unfoldable.set(newState.unfoldable);
135158
}
136159
} else {
137-
this.visible = (newState.visible ?? this.visible) && !this.overlaid;
160+
this.#visible.update((visible) => (newState.visible ?? visible) && !this.overlaid());
138161
}
139-
this.state = {
140-
...this.state,
141-
...newState
142-
};
143-
this.state.mobile && this.state.visible
162+
this.state.update((state) => ({ ...state, ...newState }));
163+
this.state().mobile && this.state().visible
144164
? this.#backdropService.setBackdrop(this)
145165
: this.#backdropService.clearBackdrop();
146166
}
147167

148168
get sidebarState(): ISidebarAction {
149-
return this.state;
169+
return { ...this.state() };
150170
}
151171

152172
get getMobileBreakpoint(): string {
@@ -164,23 +184,26 @@ export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
164184
this.#backdropService.renderer = this.#renderer;
165185
}
166186

167-
@HostBinding('class')
168-
get getClasses(): any {
169-
const { mobile, visible } = this.sidebarState;
187+
readonly hostClasses = computed(() => {
188+
const { mobile, visible } = { ...this.sidebarState };
189+
const unfoldable = this.unfoldable();
190+
const placement = this.placement();
191+
const colorScheme = this.colorScheme();
192+
const size = this.size();
170193
return {
171194
sidebar: true,
172-
'sidebar-fixed': this.position === 'fixed' && !mobile,
173-
'sidebar-narrow': this.narrow && !this.unfoldable,
174-
'sidebar-narrow-unfoldable': this.unfoldable,
175-
'sidebar-overlaid': this.overlaid,
176-
[`sidebar-${this.placement}`]: !!this.placement,
177-
[`sidebar-${this.colorScheme}`]: !!this.colorScheme,
178-
[`sidebar-${this.size}`]: !!this.size,
195+
'sidebar-fixed': this.position() === 'fixed' && !mobile,
196+
'sidebar-narrow': this.#narrow() && !unfoldable,
197+
'sidebar-narrow-unfoldable': unfoldable,
198+
'sidebar-overlaid': this.overlaid(),
199+
[`sidebar-${placement}`]: !!placement,
200+
[`sidebar-${colorScheme}`]: !!colorScheme,
201+
[`sidebar-${size}`]: !!size,
179202
show: visible,
180203
// show: visible && this.#onMobile, //todo: check
181204
hide: !visible
182205
};
183-
}
206+
});
184207

185208
ngOnInit(): void {
186209
this.setInitialState();
@@ -194,7 +217,7 @@ export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
194217
}
195218

196219
ngOnChanges(changes: SimpleChanges): void {
197-
const oldStateMap = new Map(Object.entries(this.state));
220+
const oldStateMap = new Map(Object.entries(this.state()));
198221
const newStateMap = new Map();
199222
newStateMap.set('sidebar', this);
200223

@@ -219,9 +242,9 @@ export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
219242

220243
setInitialState(): void {
221244
this.#stateInitial = {
222-
narrow: this.narrow,
223-
visible: this.visible,
224-
unfoldable: this.unfoldable
245+
narrow: this.#narrow(),
246+
visible: this.#visible(),
247+
unfoldable: this.unfoldable()
225248
};
226249
this.#sidebarService.toggle({
227250
...this.#stateInitial,
@@ -232,8 +255,8 @@ export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
232255
private stateToggleSubscribe(subscribe: boolean = true): void {
233256
if (subscribe) {
234257
this.#stateToggleSubscription = this.#sidebarService.sidebarState$.subscribe((state) => {
235-
if (this === state.sidebar || this.id === state.id) {
236-
this.sidebarState = state;
258+
if (this === state.sidebar || this.id() === state.id) {
259+
this.sidebarState = { ...state };
237260
}
238261
});
239262
} else {
@@ -249,7 +272,7 @@ export class SidebarComponent implements OnChanges, OnDestroy, OnInit {
249272

250273
this.#layoutChangeSubscription = layoutChanges.subscribe((result: BreakpointState) => {
251274
const isOnMobile = result.breakpoints[onMobile];
252-
const isUnfoldable = isOnMobile ? false : this.unfoldable;
275+
const isUnfoldable = isOnMobile ? false : this.unfoldable();
253276
if (this.#onMobile !== isOnMobile) {
254277
this.#onMobile = isOnMobile;
255278
this.#sidebarService.toggle({

0 commit comments

Comments
 (0)