forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #233 from angular/main
Create a new pull request by comparing changes across two branches
- Loading branch information
Showing
83 changed files
with
2,104 additions
and
1,786 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
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
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,49 @@ | ||
load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library") | ||
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") | ||
|
||
package(default_visibility = ["//visibility:private"]) | ||
|
||
ng_module( | ||
name = "top-level-banner", | ||
srcs = [ | ||
"top-level-banner.component.ts", | ||
], | ||
assets = [ | ||
":top-level-banner.component.css", | ||
"top-level-banner.component.html", | ||
], | ||
visibility = [ | ||
"//adev/shared-docs/components:__pkg__", | ||
], | ||
deps = [ | ||
"//adev/shared-docs/components/icon", | ||
"//adev/shared-docs/directives", | ||
"//adev/shared-docs/providers", | ||
"//packages/common", | ||
"//packages/core", | ||
], | ||
) | ||
|
||
sass_binary( | ||
name = "style", | ||
src = "top-level-banner.component.scss", | ||
) | ||
|
||
ts_library( | ||
name = "test_lib", | ||
testonly = True, | ||
srcs = glob( | ||
["*.spec.ts"], | ||
), | ||
deps = [ | ||
":top-level-banner", | ||
"//adev/shared-docs/providers", | ||
"//packages/core", | ||
"//packages/core/testing", | ||
], | ||
) | ||
|
||
karma_web_test_suite( | ||
name = "test", | ||
deps = [":test_lib"], | ||
) |
15 changes: 15 additions & 0 deletions
15
adev/shared-docs/components/top-level-banner/top-level-banner.component.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,15 @@ | ||
@if (!hasClosed()) { | ||
@if (link()) { | ||
<a [href]="link()" class="docs-top-level-banner"> | ||
<h1 tabindex="-1" class="docs-top-level-banner-cta">{{ text() }}</h1> | ||
</a> | ||
} @else { | ||
<div class="docs-top-level-banner"> | ||
<h1 tabindex="-1" class="docs-top-level-banner-cta">{{ text() }}</h1> | ||
</div> | ||
} | ||
|
||
<button class="docs-top-level-banner-close" type="button" (click)="close()"> | ||
<docs-icon class="docs-icon_high-contrast">close</docs-icon> | ||
</button> | ||
} |
76 changes: 76 additions & 0 deletions
76
adev/shared-docs/components/top-level-banner/top-level-banner.component.scss
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,76 @@ | ||
:host { | ||
&:not(:empty) { | ||
z-index: 50; | ||
position: fixed; | ||
height: 2rem; | ||
width: 100vw; | ||
border-bottom: 1px solid var(--septenary-contrast); | ||
text-align: center; | ||
align-content: center; | ||
backdrop-filter: blur(16px); | ||
background-color: color-mix(in srgb, var(--page-background) 70%, transparent); | ||
} | ||
|
||
a.docs-top-level-banner { | ||
width: 100%; | ||
display: inherit; | ||
} | ||
|
||
h1.docs-top-level-banner-cta { | ||
display: inline; | ||
position: relative; | ||
font-size: 0.875rem; | ||
margin: 0; | ||
background-image: var(--red-to-pink-to-purple-horizontal-gradient); | ||
background-clip: text; | ||
-webkit-background-clip: text; | ||
color: transparent; | ||
width: fit-content; | ||
font-weight: 500; | ||
|
||
&::after { | ||
content: ''; | ||
position: absolute; | ||
width: 100%; | ||
transform: scaleX(0); | ||
height: 1px; | ||
bottom: -2px; | ||
left: 0; | ||
background: var(--tertiary-contrast); | ||
animation-name: shimmer; | ||
-webkit-animation-duration: 5s; | ||
-moz-animation-duration: 5s; | ||
animation-duration: 5s; | ||
-webkit-animation-iteration-count: infinite; | ||
-moz-animation-iteration-count: infinite; | ||
animation-iteration-count: infinite; | ||
} | ||
} | ||
|
||
&:hover { | ||
h1.docs-top-level-banner-cta { | ||
&::after { | ||
transform: scaleX(1); | ||
transform-origin: bottom left; | ||
} | ||
} | ||
} | ||
|
||
.docs-top-level-banner-close { | ||
position: absolute; | ||
top: 0.25rem; | ||
right: 0.5rem; | ||
color: var(--primary-contrast); | ||
} | ||
} | ||
|
||
@keyframes shimmer { | ||
0% { | ||
transform: scaleX(0); | ||
transform-origin: bottom right; | ||
} | ||
100% { | ||
transform: scaleX(1); | ||
transform-origin: bottom left; | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
adev/shared-docs/components/top-level-banner/top-level-banner.component.spec.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,99 @@ | ||
import {ComponentFixture, TestBed} from '@angular/core/testing'; | ||
|
||
import {STORAGE_KEY_PREFIX, TopLevelBannerComponent} from './top-level-banner.component'; | ||
import {LOCAL_STORAGE} from '../../providers'; | ||
|
||
describe('TopLevelBannerComponent', () => { | ||
let component: TopLevelBannerComponent; | ||
let fixture: ComponentFixture<TopLevelBannerComponent>; | ||
let mockLocalStorage: jasmine.SpyObj<Storage>; | ||
|
||
const EXAMPLE_TEXT = 'Click Here'; | ||
const EXAMPLE_LINK = 'https://example.com'; | ||
const EXAMPLE_ID = 'banner-id'; | ||
|
||
beforeEach(async () => { | ||
mockLocalStorage = jasmine.createSpyObj('Storage', ['getItem', 'setItem']); | ||
|
||
await TestBed.configureTestingModule({ | ||
imports: [TopLevelBannerComponent], | ||
providers: [{provide: LOCAL_STORAGE, useValue: mockLocalStorage}], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(TopLevelBannerComponent); | ||
fixture.componentRef.setInput('text', EXAMPLE_TEXT); | ||
fixture.componentRef.setInput('id', EXAMPLE_ID); | ||
|
||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should render an anchor element when link is provided', () => { | ||
fixture.componentRef.setInput('text', EXAMPLE_TEXT); | ||
fixture.componentRef.setInput('link', EXAMPLE_LINK); | ||
fixture.detectChanges(); | ||
|
||
const bannerElement = fixture.nativeElement.querySelector('a.adev-top-level-banner'); | ||
expect(bannerElement).toBeTruthy(); | ||
expect(bannerElement.getAttribute('href')).toBe(EXAMPLE_LINK); | ||
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT); | ||
}); | ||
|
||
it('should render a div element when link is not provided', () => { | ||
const EXAMPLE_TEXT = 'No Link Available'; | ||
|
||
fixture.componentRef.setInput('text', EXAMPLE_TEXT); | ||
fixture.detectChanges(); | ||
|
||
const bannerElement = fixture.nativeElement.querySelector('div.adev-top-level-banner'); | ||
expect(bannerElement).toBeTruthy(); | ||
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT); | ||
}); | ||
|
||
it('should correctly render the text input', () => { | ||
const EXAMPLE_TEXT = 'Lorem ipsum dolor...'; | ||
|
||
fixture.componentRef.setInput('text', EXAMPLE_TEXT); | ||
fixture.detectChanges(); | ||
|
||
const bannerElement = fixture.nativeElement.querySelector('.adev-top-level-banner-cta'); | ||
expect(bannerElement).toBeTruthy(); | ||
expect(bannerElement.textContent).toBe(EXAMPLE_TEXT); | ||
}); | ||
|
||
it('should set hasClosed to true if the banner was closed before', () => { | ||
mockLocalStorage.getItem.and.returnValue('true'); | ||
|
||
component.ngOnInit(); | ||
|
||
expect(component.hasClosed()).toBeTrue(); | ||
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`); | ||
}); | ||
|
||
it('should set hasClosed to false if the banner was not closed before', () => { | ||
mockLocalStorage.getItem.and.returnValue('false'); | ||
|
||
component.ngOnInit(); | ||
|
||
expect(component.hasClosed()).toBeFalse(); | ||
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`); | ||
}); | ||
|
||
it('should set hasClosed to false if accessing localStorage throws an error', () => { | ||
mockLocalStorage.getItem.and.throwError('Local storage error'); | ||
|
||
component.ngOnInit(); | ||
|
||
expect(component.hasClosed()).toBeFalse(); | ||
}); | ||
|
||
it('should set the banner as closed in localStorage and update hasClosed', () => { | ||
component.close(); | ||
|
||
expect(mockLocalStorage.setItem).toHaveBeenCalledWith( | ||
`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`, | ||
'true', | ||
); | ||
expect(component.hasClosed()).toBeTrue(); | ||
}); | ||
}); |
52 changes: 52 additions & 0 deletions
52
adev/shared-docs/components/top-level-banner/top-level-banner.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,52 @@ | ||
import {ChangeDetectionStrategy, Component, inject, input, OnInit, signal} from '@angular/core'; | ||
import {ExternalLink} from '../../directives'; | ||
import {LOCAL_STORAGE} from '../../providers'; | ||
import {IconComponent} from '../icon/icon.component'; | ||
|
||
export const STORAGE_KEY_PREFIX = 'docs-was-closed-top-banner-'; | ||
|
||
@Component({ | ||
selector: 'docs-top-level-banner', | ||
standalone: true, | ||
imports: [ExternalLink, IconComponent], | ||
templateUrl: './top-level-banner.component.html', | ||
styleUrl: './top-level-banner.component.scss', | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
}) | ||
export class TopLevelBannerComponent implements OnInit { | ||
private readonly localStorage = inject(LOCAL_STORAGE); | ||
|
||
/** | ||
* Unique identifier for the banner. This ID is required to ensure that | ||
* the state of the banner (e.g., whether it has been closed) is tracked | ||
* separately for different events or instances. Without a unique ID, | ||
* closing one banner could inadvertently hide other banners for different events. | ||
*/ | ||
id = input.required<string>(); | ||
// Optional URL link that the banner should navigate to when clicked. | ||
link = input<string>(); | ||
// Text content to be displayed in the banner. | ||
text = input.required<string>(); | ||
|
||
// Whether the user has closed the banner. | ||
hasClosed = signal<boolean>(false); | ||
|
||
ngOnInit(): void { | ||
// Needs to be in a try/catch, because some browsers will | ||
// throw when using `localStorage` in private mode. | ||
try { | ||
this.hasClosed.set(this.localStorage?.getItem(this.getBannerStorageKey()) === 'true'); | ||
} catch { | ||
this.hasClosed.set(false); | ||
} | ||
} | ||
|
||
close(): void { | ||
this.localStorage?.setItem(this.getBannerStorageKey(), 'true'); | ||
this.hasClosed.set(true); | ||
} | ||
|
||
private getBannerStorageKey(): string { | ||
return `${STORAGE_KEY_PREFIX}${this.id()}`; | ||
} | ||
} |
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
Oops, something went wrong.