-
Notifications
You must be signed in to change notification settings - Fork 434
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core):
BreakpointService
add new service (#3806)
Co-authored-by: Alex Inkin <alexander@inkin.ru> Co-authored-by: Nikita Barsukov <nikita.s.barsukov@gmail.com> Co-authored-by: Maksim Ivanov <omaxphp@yandex.ru>
- Loading branch information
1 parent
78d546b
commit 6451a66
Showing
15 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import {Inject, Injectable} from '@angular/core'; | ||
import {WINDOW} from '@ng-web-apis/common'; | ||
import {TuiMedia} from '@taiga-ui/core/interfaces'; | ||
import {TUI_MEDIA} from '@taiga-ui/core/tokens'; | ||
import {fromEvent, merge, Observable} from 'rxjs'; | ||
import {map, share, startWith} from 'rxjs/operators'; | ||
|
||
/** | ||
* Service to provide the current breakpoint based on Taiga UI's media queries | ||
*/ | ||
@Injectable({ | ||
providedIn: `root`, | ||
}) | ||
export class TuiBreakpointService extends Observable<MediaKey | null> { | ||
constructor(@Inject(TUI_MEDIA) media: TuiMedia, @Inject(WINDOW) windowRef: Window) { | ||
const breakpoints = getBreakpoints(media); | ||
const events$ = breakpoints.map(({query}) => | ||
fromEvent<MediaQueryListEvent>(windowRef.matchMedia(query), `change`), | ||
); | ||
const media$ = merge(...events$).pipe( | ||
map(() => currentBreakpoint(breakpoints, windowRef.innerWidth).name), | ||
startWith(currentBreakpoint(breakpoints, windowRef.innerWidth).name), | ||
share(), | ||
); | ||
|
||
super(subscriber => media$.subscribe(subscriber)); | ||
} | ||
} | ||
|
||
type MediaKey = Omit<keyof TuiMedia, 'tablet'>; | ||
|
||
interface Breakpoint { | ||
name: MediaKey; | ||
query: string; | ||
width: number; | ||
} | ||
|
||
function getBreakpoints(media: TuiMedia): Breakpoint[] { | ||
return Object.entries(media).map(([name, width]) => ({ | ||
name: name as MediaKey, | ||
/** | ||
* @note: | ||
* min-width query in css is inclusive, but in window.matchMedia it is exclusive | ||
* so we need to subtract 1px to get the same result | ||
*/ | ||
query: `(max-width: ${width - 1}px)`, | ||
width, | ||
})); | ||
} | ||
|
||
function currentBreakpoint(breakpoints: Breakpoint[], innerWidth: number): Breakpoint { | ||
return breakpoints.find(({width}) => innerWidth < width) ?? breakpoints.slice(-1)[0]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import {TestBed} from '@angular/core/testing'; | ||
import {WINDOW} from '@ng-web-apis/common'; | ||
import {configureTestSuite} from '@taiga-ui/testing'; | ||
|
||
import {TuiMedia} from '../../interfaces'; | ||
import {TUI_MEDIA} from '../../tokens'; | ||
import {TuiBreakpointService} from '../breakpoint.service'; | ||
|
||
describe(`TuiBreakpointService`, () => { | ||
const mock: HTMLDivElement = document.createElement(`div`); | ||
let service: TuiBreakpointService; | ||
const mediaMock: TuiMedia = { | ||
mobile: 768, | ||
desktopSmall: 1024, | ||
desktopLarge: 1280, | ||
}; | ||
|
||
const windowMock: any = { | ||
matchMedia: jest | ||
.fn() | ||
.mockReturnValue({...mock, matches: true, media: `(max-width: 767px)`}), | ||
innerWidth: 700, | ||
}; | ||
|
||
configureTestSuite(() => { | ||
TestBed.configureTestingModule({ | ||
providers: [ | ||
TuiBreakpointService, | ||
{ | ||
provide: TUI_MEDIA, | ||
useValue: mediaMock, | ||
}, | ||
{ | ||
provide: WINDOW, | ||
useValue: windowMock, | ||
}, | ||
], | ||
}); | ||
}); | ||
|
||
beforeEach(() => { | ||
service = TestBed.inject(TuiBreakpointService); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it(`should create`, () => { | ||
expect(service).toBeTruthy(); | ||
}); | ||
|
||
it(`should emit the current breakpoint name when subscribed to`, () => { | ||
const observerMock = jest.fn(); | ||
|
||
const subscription = service.subscribe(observerMock); | ||
|
||
expect(observerMock).toHaveBeenCalledWith(`mobile`); | ||
subscription.unsubscribe(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
projects/demo-integrations/cypress/tests/core/breakpoint-service/breakpoint-serivce.cy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
describe(`Breakpoint service`, () => { | ||
for (const {width, height} of [ | ||
{width: 768, height: 900}, | ||
{width: 1024, height: 900}, | ||
{width: 1280, height: 900}, | ||
]) { | ||
it(`${width}x${height}`, () => { | ||
cy.viewport(width, height); | ||
cy.tuiVisit(`/services/breakpoint-service`); | ||
|
||
cy.get(`tui-doc-example[heading="Basic"]`) | ||
.findByAutomationId(`tui-doc-example`) | ||
.tuiScrollIntoView() | ||
.tuiWaitBeforeScreenshot() | ||
.matchImageSnapshot(`breakpoint_${width}x${height}`); | ||
}); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
projects/demo/src/modules/services/breakpoint/breakpoint.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {Component} from '@angular/core'; | ||
import {changeDetection} from '@demo/emulate/change-detection'; | ||
import {TuiDocExample} from '@taiga-ui/addon-doc'; | ||
|
||
@Component({ | ||
selector: 'example-tui-breakpoint', | ||
templateUrl: './breakpoint.template.html', | ||
changeDetection, | ||
}) | ||
export class ExampleTuiBreakpointComponent { | ||
provideService = import('./examples/provide-service.md?raw'); | ||
injectService = import('./examples/inject-service.md?raw'); | ||
|
||
readonly example: TuiDocExample = { | ||
TypeScript: import('./examples/1/component.ts?raw'), | ||
HTML: import('./examples/1/template.html?raw'), | ||
}; | ||
} |
18 changes: 18 additions & 0 deletions
18
projects/demo/src/modules/services/breakpoint/breakpoint.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {CommonModule} from '@angular/common'; | ||
import {NgModule} from '@angular/core'; | ||
import {RouterModule} from '@angular/router'; | ||
import {TuiAddonDocModule, tuiGenerateRoutes} from '@taiga-ui/addon-doc'; | ||
|
||
import {ExampleTuiBreakpointComponent} from './breakpoint.component'; | ||
import {TuiBreakpointExample} from './examples/1/component'; | ||
|
||
@NgModule({ | ||
imports: [ | ||
CommonModule, | ||
TuiAddonDocModule, | ||
RouterModule.forChild(tuiGenerateRoutes(ExampleTuiBreakpointComponent)), | ||
], | ||
declarations: [ExampleTuiBreakpointComponent, TuiBreakpointExample], | ||
exports: [ExampleTuiBreakpointComponent], | ||
}) | ||
export class ExampleTuiBreakpointModule {} |
45 changes: 45 additions & 0 deletions
45
projects/demo/src/modules/services/breakpoint/breakpoint.template.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<tui-doc-page | ||
header="BreakpointService" | ||
package="CORE" | ||
path="core/services/breakpoint.service.ts" | ||
> | ||
<ng-template pageTab> | ||
<p i18n>Service to observe changes in the current breakpoint.</p> | ||
<tui-doc-example | ||
id="basic" | ||
i18n-heading | ||
heading="Basic" | ||
[content]="example" | ||
> | ||
<tui-breakpoint-example></tui-breakpoint-example> | ||
</tui-doc-example> | ||
</ng-template> | ||
<ng-template pageTab="Setup"> | ||
<ol class="b-demo-steps"> | ||
<li> | ||
<p i18n> | ||
Add | ||
<code>TuiBreakpointService</code> | ||
to the providers of your component: | ||
</p> | ||
|
||
<tui-doc-code | ||
filename="myComponent.component.ts" | ||
[code]="provideService" | ||
></tui-doc-code> | ||
</li> | ||
<li> | ||
<p i18n> | ||
Inject | ||
<code>TuiBreakpointService</code> | ||
into your component: | ||
</p> | ||
|
||
<tui-doc-code | ||
filename="myComponent.component.ts" | ||
[code]="injectService" | ||
></tui-doc-code> | ||
</li> | ||
</ol> | ||
</ng-template> | ||
</tui-doc-page> |
18 changes: 18 additions & 0 deletions
18
projects/demo/src/modules/services/breakpoint/examples/1/component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import {Component, Inject} from '@angular/core'; | ||
import {changeDetection} from '@demo/emulate/change-detection'; | ||
import {encapsulation} from '@demo/emulate/encapsulation'; | ||
import {TuiBreakpointService} from '@taiga-ui/core'; | ||
|
||
@Component({ | ||
selector: `tui-breakpoint-example`, | ||
templateUrl: `./template.html`, | ||
styleUrls: [`./style.less`], | ||
changeDetection, | ||
encapsulation, | ||
}) | ||
export class TuiBreakpointExample { | ||
constructor( | ||
@Inject(TuiBreakpointService) | ||
readonly breakpoint$: TuiBreakpointService, | ||
) {} | ||
} |
59 changes: 59 additions & 0 deletions
59
projects/demo/src/modules/services/breakpoint/examples/1/style.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
@import 'taiga-ui-local'; | ||
|
||
:host { | ||
display: block; | ||
} | ||
|
||
table { | ||
width: 100%; | ||
border-spacing: 0; | ||
} | ||
|
||
th, | ||
td { | ||
text-align: left; | ||
border: 1px solid var(--tui-base-03); | ||
height: 3.375rem; | ||
padding: 0 1rem; | ||
vertical-align: middle; | ||
} | ||
|
||
.mobile { | ||
display: block; | ||
} | ||
|
||
.desktop-small { | ||
display: none; | ||
} | ||
|
||
.desktop-large { | ||
display: none; | ||
} | ||
|
||
@media @tui-tablet-min { | ||
.mobile { | ||
display: none; | ||
} | ||
|
||
.desktop-small { | ||
display: block; | ||
} | ||
|
||
.desktop-large { | ||
display: none; | ||
} | ||
} | ||
|
||
@media @tui-desktop-min { | ||
.mobile { | ||
display: none; | ||
} | ||
|
||
.desktop-small { | ||
display: none; | ||
} | ||
|
||
.desktop-large { | ||
display: block; | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
projects/demo/src/modules/services/breakpoint/examples/1/template.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<p>Change the viewport of this window to see changes in breakpoint</p> | ||
|
||
<table class="tui-space_top-4"> | ||
<thead> | ||
<tr> | ||
<th>CSS</th> | ||
<th>Breakpoint service</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<td> | ||
<div class="mobile">Mobile breakpoint</div> | ||
<div class="desktop-small">Desktop small breakpoint</div> | ||
<div class="desktop-large">Desktop large breakpoint</div> | ||
</td> | ||
<td> | ||
<ng-container *ngIf="breakpoint$ | async as breakpoint"> | ||
<div *ngIf="breakpoint === 'mobile'">Mobile breakpoint</div> | ||
<div *ngIf="breakpoint === 'desktopSmall'">Desktop small breakpoint</div> | ||
<div *ngIf="breakpoint === 'desktopLarge'">Desktop large breakpoint</div> | ||
</ng-container> | ||
</td> | ||
</tbody> | ||
</table> |
12 changes: 12 additions & 0 deletions
12
projects/demo/src/modules/services/breakpoint/examples/inject-service.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
```ts | ||
import {Inject} from '@angular/core'; | ||
import {TuiBreakpointService} from '@taiga-ui/core'; | ||
|
||
// ... | ||
export class MyComponent { | ||
constructor( | ||
@Inject(TuiBreakpointService) | ||
private readonly breakpoint$: TuiBreakpointService, | ||
) {} | ||
} | ||
``` |
10 changes: 10 additions & 0 deletions
10
projects/demo/src/modules/services/breakpoint/examples/provide-service.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
```ts | ||
import {Component} from '@angular/core'; | ||
import {TuiBreakpointService} from '@taiga-ui/core'; | ||
|
||
@Component({ | ||
// ... | ||
providers: [TuiBreakpointService], | ||
}) | ||
export class MyComponent {} | ||
``` |