From a76b593ac298de556d353fb3f3390493a462a056 Mon Sep 17 00:00:00 2001 From: Trevor Burch Date: Wed, 2 Oct 2024 10:39:53 -0400 Subject: [PATCH 1/6] ci: remove requirement for commits in single commit PRs to match the PR title (#2793) --- .github/workflows/validate-pr.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index c92094fb1d..abe40b57e4 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -91,5 +91,3 @@ jobs: sdk/eslint-config sdk/prettier-schematics sdk/testing - validateSingleCommit: true - validateSingleCommitMatchesPrTitle: true From 01e3fa836f201e7995cbe6c0991c033cf2d85c27 Mon Sep 17 00:00:00 2001 From: John White <750350+johnhwhite@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:09:07 -0400 Subject: [PATCH 2/6] feat(components/pages): add `sky-page-links` component (#2781) [AB#3033891](https://dev.azure.com/blackbaud/f565481a-7bc9-4083-95d5-4f953da6d499/_workitems/edit/3033891) --- .../e2e/page-layout-blocks.component.cy.ts | 49 ++++++++++--------- .../src/e2e/page-layout-fit.component.cy.ts | 25 ++++++---- .../src/e2e/page-layout-list.component.cy.ts | 25 ++++++---- .../src/e2e/page-layout-tabs.component.cy.ts | 25 ++++++---- .../e2e/pages-storybook/src/app/app.module.ts | 5 +- .../blocks-page/blocks-page.component.html | 3 ++ .../blocks-page.component.stories.ts | 7 ++- .../blocks-page/blocks-page.component.ts | 6 ++- .../layouts/fit-page/fit-page.component.html | 3 ++ .../fit-page/fit-page.component.stories.ts | 7 ++- .../layouts/fit-page/fit-page.component.ts | 9 +++- .../list-page/list-page.component.html | 3 ++ .../list-page/list-page.component.stories.ts | 7 ++- .../layouts/list-page/list-page.component.ts | 6 ++- .../src/app/page/layouts/routes.ts | 10 ++++ .../tabs-page/tabs-page.component.html | 3 ++ .../tabs-page/tabs-page.component.stories.ts | 7 ++- .../layouts/tabs-page/tabs-page.component.ts | 6 ++- libs/components/pages/src/index.ts | 1 + .../modules/page/page-links.component.spec.ts | 34 +++++++++++++ .../lib/modules/page/page-links.component.ts | 18 +++++++ .../lib/modules/page/page.component.spec.ts | 2 +- .../pages/src/lib/modules/page/page.module.ts | 10 +++- .../theme/src/lib/styles/_layout-host.scss | 41 ++++++++++++++++ 24 files changed, 248 insertions(+), 64 deletions(-) create mode 100644 libs/components/pages/src/lib/modules/page/page-links.component.spec.ts create mode 100644 libs/components/pages/src/lib/modules/page/page-links.component.ts diff --git a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-blocks.component.cy.ts b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-blocks.component.cy.ts index b46a116547..70a9710fc6 100644 --- a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-blocks.component.cy.ts +++ b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-blocks.component.cy.ts @@ -14,30 +14,35 @@ Cypress.on( describe(`pages-storybook`, () => { E2eVariations.forEachTheme((theme) => { describe(`in ${theme} theme`, () => { - beforeEach(() => - cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), - ); - it('should render the component', () => { - cy.get('app-blocks-page sky-page') - .should('exist') - .should('be.visible') - .screenshot(`${ID}-${theme}`); - cy.get('app-blocks-page sky-page').percySnapshot(`${ID}-${theme}`); - }); + [ + ['block-layout', ID], + ['block-layout-with-links', `${ID}-with-links`], + ].forEach(([_, ID]) => { + describe(`${_}`, () => { + beforeEach(() => + cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), + ); + + it('should render the component', () => { + cy.get('app-blocks-page sky-page') + .should('exist') + .should('be.visible'); + cy.window().screenshot(`${ID}-${theme}`); + cy.window().percySnapshot(`${ID}-${theme}`); + }); - it('should render the component on mobile', () => { - cy.viewport(E2eVariations.MOBILE_WIDTHS[0], 800); + it('should render the component on mobile', () => { + cy.viewport(E2eVariations.MOBILE_WIDTHS[0], 800); - cy.get('app-blocks-page sky-page') - .should('exist') - .should('be.visible') - .screenshot(`${ID}-${theme}-mobile`); - cy.get('app-blocks-page sky-page').percySnapshot( - `${ID}-${theme}-mobile`, - { - widths: E2eVariations.MOBILE_WIDTHS, - }, - ); + cy.get('app-blocks-page sky-page') + .should('exist') + .should('be.visible'); + cy.window().screenshot(`${ID}-${theme}-mobile`); + cy.window().percySnapshot(`${ID}-${theme}-mobile`, { + widths: E2eVariations.MOBILE_WIDTHS, + }); + }); + }); }); }); }); diff --git a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-fit.component.cy.ts b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-fit.component.cy.ts index 90beecde6c..ddecedd6d3 100644 --- a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-fit.component.cy.ts +++ b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-fit.component.cy.ts @@ -5,15 +5,22 @@ const ID = 'fitpagecomponent-fitpage--fit-page'; describe(`pages-storybook`, () => { E2eVariations.forEachTheme((theme) => { describe(`in ${theme} theme`, () => { - beforeEach(() => - cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), - ); - it('should render the component', () => { - cy.get('app-fit-page sky-page') - .should('exist') - .should('be.visible') - .screenshot(`${ID}-${theme}`); - cy.get('app-fit-page sky-page').percySnapshot(`${ID}-${theme}`); + [ + ['block-layout', ID], + ['block-layout-with-links', `${ID}-with-links`], + ].forEach(([_, ID]) => { + describe(`${_}`, () => { + beforeEach(() => + cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), + ); + it('should render the component', () => { + cy.get('app-fit-page sky-page') + .should('exist') + .should('be.visible'); + cy.window().screenshot(`${ID}-${theme}`); + cy.window().percySnapshot(`${ID}-${theme}`); + }); + }); }); }); }); diff --git a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-list.component.cy.ts b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-list.component.cy.ts index 7344863db4..44dffdb499 100644 --- a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-list.component.cy.ts +++ b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-list.component.cy.ts @@ -5,15 +5,22 @@ const ID = 'listpagecomponent-listpage--list-page'; describe(`pages-storybook`, () => { E2eVariations.forEachTheme((theme) => { describe(`in ${theme} theme`, () => { - beforeEach(() => - cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), - ); - it('should render the component', () => { - cy.get('app-list-page sky-page') - .should('exist') - .should('be.visible') - .screenshot(`${ID}-${theme}`); - cy.get('app-list-page sky-page').percySnapshot(`${ID}-${theme}`); + [ + ['block-layout', ID], + ['block-layout-with-links', `${ID}-with-links`], + ].forEach(([_, ID]) => { + describe(`${_}`, () => { + beforeEach(() => + cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), + ); + it('should render the component', () => { + cy.get('app-list-page sky-page') + .should('exist') + .should('be.visible'); + cy.window().screenshot(`${ID}-${theme}`); + cy.window().percySnapshot(`${ID}-${theme}`); + }); + }); }); }); }); diff --git a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-tabs.component.cy.ts b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-tabs.component.cy.ts index f4846cac2e..89ad41f521 100644 --- a/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-tabs.component.cy.ts +++ b/apps/e2e/pages-storybook-e2e/src/e2e/page-layout-tabs.component.cy.ts @@ -5,15 +5,22 @@ const ID = 'tabspagecomponent-tabspage--tabs-page'; describe(`pages-storybook`, () => { E2eVariations.forEachTheme((theme) => { describe(`in ${theme} theme`, () => { - beforeEach(() => - cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), - ); - it('should render the component', () => { - cy.get('app-tabs-page sky-page') - .should('exist') - .should('be.visible') - .screenshot(`${ID}-${theme}`); - cy.get('app-tabs-page sky-page').percySnapshot(`${ID}-${theme}`); + [ + ['block-layout', ID], + ['block-layout-with-links', `${ID}-with-links`], + ].forEach(([_, ID]) => { + describe(`${_}`, () => { + beforeEach(() => + cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`), + ); + it('should render the component', () => { + cy.get('app-tabs-page sky-page') + .should('exist') + .should('be.visible'); + cy.window().screenshot(`${ID}-${theme}`); + cy.window().percySnapshot(`${ID}-${theme}`); + }); + }); }); }); }); diff --git a/apps/e2e/pages-storybook/src/app/app.module.ts b/apps/e2e/pages-storybook/src/app/app.module.ts index 302fa2c5e0..72f264486d 100644 --- a/apps/e2e/pages-storybook/src/app/app.module.ts +++ b/apps/e2e/pages-storybook/src/app/app.module.ts @@ -23,7 +23,10 @@ if (routes.length > 0 && routes.findIndex((r) => r.path === '') === -1) { declarations: [AppComponent], imports: [ NoopAnimationsModule, - RouterModule.forRoot(routes, { initialNavigation: 'enabledBlocking' }), + RouterModule.forRoot(routes, { + initialNavigation: 'enabledBlocking', + bindToComponentInputs: true, + }), ], bootstrap: [AppComponent], }) diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html index 1658f7f72e..0ef3a09ffd 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html @@ -46,4 +46,7 @@ + @if (showLinks()) { + Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.stories.ts b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.stories.ts index dcf07059e0..fc5fe84cb8 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.stories.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.stories.ts @@ -1,5 +1,5 @@ -import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; +import { moduleMetadata } from '@storybook/angular'; import BlocksPageComponent from './blocks-page.component'; @@ -16,3 +16,8 @@ export default { type Story = StoryObj; export const BlocksPage: Story = {}; BlocksPage.args = {}; + +export const BlocksPageWithLinks: Story = {}; +BlocksPageWithLinks.args = { + showLinks: true, +}; diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts index 140b9a3cbc..d16794e868 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { SkyAvatarModule } from '@skyux/avatar'; import { SkyAlertModule, SkyLabelModule } from '@skyux/indicators'; import { SkyBoxModule, SkyFluidGridModule } from '@skyux/layout'; @@ -20,4 +20,6 @@ import { SkyPageModule } from '@skyux/pages'; templateUrl: './blocks-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class BlocksPageComponent {} +export default class BlocksPageComponent { + public readonly showLinks = input(false); +} diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html index 254fdb33ef..8ab0dfc8a4 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html @@ -119,4 +119,7 @@ + @if (showLinks()) { + Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.stories.ts b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.stories.ts index 743c4d5984..4582d93281 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.stories.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.stories.ts @@ -1,5 +1,5 @@ -import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; +import { moduleMetadata } from '@storybook/angular'; import FitPageComponent from './fit-page.component'; @@ -16,3 +16,8 @@ export default { type Story = StoryObj; export const FitPage: Story = {}; FitPage.args = {}; + +export const FitPageWithLinks: Story = {}; +FitPageWithLinks.args = { + showLinks: true, +}; diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts index 08397ae4e6..bf0f1fe9c2 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts @@ -1,5 +1,10 @@ import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + inject, + input, +} from '@angular/core'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { SkySummaryActionBarModule } from '@skyux/action-bars'; import { SkyAvatarModule } from '@skyux/avatar'; @@ -46,6 +51,8 @@ interface WorkspaceItem { changeDetection: ChangeDetectionStrategy.OnPush, }) export default class FitPageComponent { + public readonly showLinks = input(false); + protected set activeIndex(value: number) { this.#_activeIndex = value; this.activeRecord = this.items[this.#_activeIndex]; diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html index 7d0f4809f7..2266134a89 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html @@ -5,4 +5,7 @@ List content + @if (showLinks()) { + Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.stories.ts b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.stories.ts index 7dd98740bd..5e7dfea04e 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.stories.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.stories.ts @@ -1,5 +1,5 @@ -import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; +import { moduleMetadata } from '@storybook/angular'; import ListPageComponent from './list-page.component'; @@ -16,3 +16,8 @@ export default { type Story = StoryObj; export const ListPage: Story = {}; ListPage.args = {}; + +export const ListPageWithLinks: Story = {}; +ListPageWithLinks.args = { + showLinks: true, +}; diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts index 158dc11b56..06e1469ad1 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { SkyPageModule } from '@skyux/pages'; @Component({ @@ -9,4 +9,6 @@ import { SkyPageModule } from '@skyux/pages'; templateUrl: './list-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class ListPageComponent {} +export default class ListPageComponent { + public readonly showLinks = input(false); +} diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts b/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts index 562867a5f7..a4e166b8d8 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts @@ -10,6 +10,16 @@ export default [ library: 'pages', }, }, + { + path: 'blocks-with-links', + loadComponent: () => import('./blocks-page/blocks-page.component'), + data: { + name: 'Page (Blocks)', + icon: 'list', + library: 'pages', + showLinks: true, + }, + }, { path: 'fit', loadComponent: () => import('./fit-page/fit-page.component'), diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html index 35cff8d95e..e865a58ab7 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html @@ -17,4 +17,7 @@ + @if (showLinks()) { + Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.stories.ts b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.stories.ts index 63764b5132..ab914c38b6 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.stories.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.stories.ts @@ -1,5 +1,5 @@ -import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; +import { moduleMetadata } from '@storybook/angular'; import TabsPageComponent from './tabs-page.component'; @@ -16,3 +16,8 @@ export default { type Story = StoryObj; export const TabsPage: Story = {}; TabsPage.args = {}; + +export const TabsPageWithLinks: Story = {}; +TabsPageWithLinks.args = { + showLinks: true, +}; diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts index 8e0fa58f2d..8940872d06 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { SkyPageModule } from '@skyux/pages'; import { SkyTabsModule } from '@skyux/tabs'; @@ -10,4 +10,6 @@ import { SkyTabsModule } from '@skyux/tabs'; templateUrl: './tabs-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class TabsPageComponent {} +export default class TabsPageComponent { + public readonly showLinks = input(false); +} diff --git a/libs/components/pages/src/index.ts b/libs/components/pages/src/index.ts index c03a11f80e..5cbbf20d76 100644 --- a/libs/components/pages/src/index.ts +++ b/libs/components/pages/src/index.ts @@ -26,4 +26,5 @@ export { SkyPageHeaderAvatarComponent as λ10 } from './lib/modules/page-header/ export { SkyPageHeaderDetailsComponent as λ8 } from './lib/modules/page-header/page-header-details.component'; export { SkyPageHeaderComponent as λ4 } from './lib/modules/page-header/page-header.component'; export { SkyPageContentComponent as λ7 } from './lib/modules/page/page-content.component'; +export { SkyPageLinksComponent as λ12 } from './lib/modules/page/page-links.component'; export { SkyPageComponent as λ6 } from './lib/modules/page/page.component'; diff --git a/libs/components/pages/src/lib/modules/page/page-links.component.spec.ts b/libs/components/pages/src/lib/modules/page/page-links.component.spec.ts new file mode 100644 index 0000000000..21255d9344 --- /dev/null +++ b/libs/components/pages/src/lib/modules/page/page-links.component.spec.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SkyPageLinksComponent } from './page-links.component'; + +@Component({ + standalone: true, + selector: 'sky-page-links-test', + imports: [SkyPageLinksComponent], + template: `Links.`, +}) +class SkyPageLinksTestComponent {} + +describe('PageLinksComponent', () => { + let component: SkyPageLinksTestComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SkyPageLinksTestComponent], + }); + + fixture = TestBed.createComponent(SkyPageLinksTestComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should sky-page-links as block element', () => { + expect(component).toBeTruthy(); + const pageLinksEl = fixture.nativeElement.querySelector('sky-page-links'); + expect(getComputedStyle(pageLinksEl).display).toBe('block'); + expect(pageLinksEl.textContent).toContain('Links.'); + }); +}); diff --git a/libs/components/pages/src/lib/modules/page/page-links.component.ts b/libs/components/pages/src/lib/modules/page/page-links.component.ts new file mode 100644 index 0000000000..8aca6e17ff --- /dev/null +++ b/libs/components/pages/src/lib/modules/page/page-links.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; + +/** + * Displays page links within a block page layout. + * @internal + */ +@Component({ + standalone: true, + selector: 'sky-page-links', + template: '', + styles: ` + :host { + display: var(--sky-layout-host-links-display, block); + margin: var(--sky-layout-host-links-spacing, 0); + } + `, +}) +export class SkyPageLinksComponent {} diff --git a/libs/components/pages/src/lib/modules/page/page.component.spec.ts b/libs/components/pages/src/lib/modules/page/page.component.spec.ts index 677530668a..b9d265fc48 100644 --- a/libs/components/pages/src/lib/modules/page/page.component.spec.ts +++ b/libs/components/pages/src/lib/modules/page/page.component.spec.ts @@ -20,7 +20,7 @@ describe('Page component', () => { layout: SkyPageLayoutType | undefined, expectedCssClass: string, ): void { - fixture.componentInstance.layout = layout; + fixture.componentRef.setInput('layout', layout); fixture.detectChanges(); expect(fixture.nativeElement).toHaveCssClass(expectedCssClass); diff --git a/libs/components/pages/src/lib/modules/page/page.module.ts b/libs/components/pages/src/lib/modules/page/page.module.ts index 283b797c39..ca0f80e780 100644 --- a/libs/components/pages/src/lib/modules/page/page.module.ts +++ b/libs/components/pages/src/lib/modules/page/page.module.ts @@ -4,11 +4,17 @@ import { NgModule } from '@angular/core'; import { SkyPageHeaderModule } from '../page-header/page-header.module'; import { SkyPageContentComponent } from './page-content.component'; +import { SkyPageLinksComponent } from './page-links.component'; import { SkyPageComponent } from './page.component'; @NgModule({ declarations: [SkyPageComponent, SkyPageContentComponent], - imports: [CommonModule], - exports: [SkyPageComponent, SkyPageHeaderModule, SkyPageContentComponent], + imports: [CommonModule, SkyPageLinksComponent], + exports: [ + SkyPageComponent, + SkyPageHeaderModule, + SkyPageContentComponent, + SkyPageLinksComponent, + ], }) export class SkyPageModule {} diff --git a/libs/components/theme/src/lib/styles/_layout-host.scss b/libs/components/theme/src/lib/styles/_layout-host.scss index 20aa499fee..78deb3ac00 100644 --- a/libs/components/theme/src/lib/styles/_layout-host.scss +++ b/libs/components/theme/src/lib/styles/_layout-host.scss @@ -6,6 +6,29 @@ &-list, &-tabs { display: block; + + &:has(> sky-page-content):has(> sky-page-links) { + --sky-layout-host-links-spacing: 10px; + + display: grid; + grid-template: + 'header' + 'content' + 'links'; + grid-template-columns: 1fr; + + > sky-page-header { + grid-area: header; + } + + > sky-page-content { + grid-area: content; + } + + > sky-page-links { + grid-area: links; + } + } } &-fit, @@ -22,6 +45,7 @@ --sky-layout-host-content-flex-grow: 1; --sky-layout-host-content-overflow: auto; --sky-layout-host-content-position: relative; + --sky-layout-host-links-display: none; } &-blocks, @@ -47,6 +71,21 @@ } @include mixins.sky-host-responsive-container-sm-min(false) { + .sky-layout-host { + &-blocks, + &-list, + &-tabs { + &:has(> sky-page-content):has(> sky-page-links) { + --sky-layout-host-links-spacing: 20px; + + grid-template: + 'header header' + 'content links'; + grid-template-columns: 3fr 1fr; + } + } + } + .sky-layout-host { &-blocks { --sky-layout-host-header-spacing: 20px 20px 0 20px; @@ -72,12 +111,14 @@ &-blocks { --sky-layout-host-header-spacing: 30px 30px 0 30px; --sky-layout-host-content-spacing: 30px; + --sky-layout-host-links-spacing: 30px 30px 30px 0; } &-fit, &-list, &-tabs { --sky-layout-host-header-spacing: 30px 30px 20px 30px; + --sky-layout-host-links-spacing: 30px; } &-fit, From 3d9e73f4179c4a84e20645aebff38c0bd17717f6 Mon Sep 17 00:00:00 2001 From: Paul Crowder Date: Wed, 2 Oct 2024 16:33:31 -0400 Subject: [PATCH 3/6] fix(components/forms): delay error display to allow clicks to content below to complete (#2777) --- .../modal/modal-required.component.ts | 68 +++++++++++++++++++ .../modal/modal-visual.component.html | 8 +++ .../modal/modal-visual.component.ts | 5 ++ .../form-error/form-error.component.ts | 14 ++++ 4 files changed, 95 insertions(+) create mode 100644 apps/playground/src/app/components/modal/modal-required.component.ts diff --git a/apps/playground/src/app/components/modal/modal-required.component.ts b/apps/playground/src/app/components/modal/modal-required.component.ts new file mode 100644 index 0000000000..8a21333c91 --- /dev/null +++ b/apps/playground/src/app/components/modal/modal-required.component.ts @@ -0,0 +1,68 @@ +import { Component, inject } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { SkyInputBoxModule } from '@skyux/forms'; +import { SkyModalInstance, SkyModalModule } from '@skyux/modals'; + +function round(val: number) { + return Math.round(val * 10) / 10; +} + +@Component({ + standalone: true, + imports: [ReactiveFormsModule, SkyInputBoxModule, SkyModalModule], + template: ` + + + + + + + + + + + + + + + + + `, +}) +export class ModalRequiredComponent { + protected readonly instance = inject(SkyModalInstance); + + protected readonly requiredField1 = new FormControl('', Validators.required); + protected readonly requiredField2 = new FormControl('', Validators.required); + protected readonly requiredField3 = new FormControl('', Validators.required); + + #clickStart = 0; + #clicks: number[] = []; + + protected onMousedown(): void { + this.#clickStart = performance.now(); + } + + protected onMouseup(): void { + const duration = round(performance.now() - this.#clickStart); + this.#clicks.push(duration); + + const averageDuration = round( + this.#clicks.reduce((prev, cur) => prev + cur, 0) / this.#clicks.length, + ); + + console.log( + `Click duration: ${duration}ms (average: ${averageDuration}ms)`, + ); + } +} diff --git a/apps/playground/src/app/components/modal/modal-visual.component.html b/apps/playground/src/app/components/modal/modal-visual.component.html index a4246934ab..6d06ec8e17 100644 --- a/apps/playground/src/app/components/modal/modal-visual.component.html +++ b/apps/playground/src/app/components/modal/modal-visual.component.html @@ -120,4 +120,12 @@ > Open error modal + + diff --git a/apps/playground/src/app/components/modal/modal-visual.component.ts b/apps/playground/src/app/components/modal/modal-visual.component.ts index 04828daed5..70035f7160 100644 --- a/apps/playground/src/app/components/modal/modal-visual.component.ts +++ b/apps/playground/src/app/components/modal/modal-visual.component.ts @@ -15,6 +15,7 @@ import { ModalErrorComponent } from './modal-error.component'; import { ModalFormDemoComponent } from './modal-form-demo.component'; import { ModalFullPageDemoComponent } from './modal-full-page-demo.component'; import { ModalLookupComponent } from './modal-lookup.component'; +import { ModalRequiredComponent } from './modal-required.component'; import { ModalTiledDemoComponent } from './modal-tiled-demo.component'; @Component({ @@ -125,6 +126,10 @@ export class ModalVisualComponent { this.openModalInstance(ModalErrorComponent); } + protected openRequiredFieldModal(): void { + this.openModalInstance(ModalRequiredComponent); + } + public hideButtons(): void { this.buttonsHidden = true; } diff --git a/libs/components/forms/src/lib/modules/form-error/form-error.component.ts b/libs/components/forms/src/lib/modules/form-error/form-error.component.ts index 63c42a1be7..10ca2d59b5 100644 --- a/libs/components/forms/src/lib/modules/form-error/form-error.component.ts +++ b/libs/components/forms/src/lib/modules/form-error/form-error.component.ts @@ -31,7 +31,21 @@ import { SKY_FORM_ERRORS_ENABLED } from './form-errors-enabled-token'; `, styles: [ ` + @keyframes sky-modal-error { + 0%, + 50% { + max-height: 0; + margin-top: 0; + opacity: 0; + } + 100% { + max-height: 500px; + opacity: 1; + } + } + :host { + animation: sky-modal-error 300ms ease-out 1; display: block; margin-top: var(--sky-margin-stacked-xs); } From ba68482b5ad62f18d91a232f976da91e6fa8bd46 Mon Sep 17 00:00:00 2001 From: John White <750350+johnhwhite@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:00:25 -0400 Subject: [PATCH 4/6] fix(sdk/testing): adjust async matchers (#2795) --- libs/sdk/testing/src/lib/matchers/matchers.ts | 246 ++++++++---------- 1 file changed, 103 insertions(+), 143 deletions(-) diff --git a/libs/sdk/testing/src/lib/matchers/matchers.ts b/libs/sdk/testing/src/lib/matchers/matchers.ts index 3ad62d2df6..db3593b542 100644 --- a/libs/sdk/testing/src/lib/matchers/matchers.ts +++ b/libs/sdk/testing/src/lib/matchers/matchers.ts @@ -2,8 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { SkyAppResourcesService, SkyLibResourcesService } from '@skyux/i18n'; import axe from 'axe-core'; -import { Observable } from 'rxjs'; -import { take } from 'rxjs/operators'; +import { firstValueFrom } from 'rxjs'; import { SkyA11yAnalyzer } from '../a11y/a11y-analyzer'; import { SkyA11yAnalyzerConfig } from '../a11y/a11y-analyzer-config'; @@ -12,20 +11,14 @@ import { SkyToBeVisibleOptions } from './to-be-visible-options'; const windowRef: any = window; -function getResourcesObservable( - name: string, - args: any[] = [], -): Observable { +function getResources(name: string, args: any[] = []): Promise { const resourcesService = TestBed.inject(SkyAppResourcesService); - return resourcesService.getString(name, ...args); + return firstValueFrom(resourcesService.getString(name, ...args)); } -function getLibResourcesObservable( - name: string, - args: any[] = [], -): Observable { +function getLibResources(name: string, args: any[] = []): Promise { const resourcesService = TestBed.inject(SkyLibResourcesService); - return resourcesService.getString(name, ...args); + return firstValueFrom(resourcesService.getString(name, ...args)); } function isTemplateMatch(sample: string, template: string): boolean { @@ -255,18 +248,16 @@ const matchers: jasmine.CustomMatcherFactories = { args?: any[], callback?: () => void, ): jasmine.CustomMatcherResult { - getResourcesObservable(name, args) - .toPromise() - .then((message) => { - /*istanbul ignore else*/ - if (actual !== message) { - windowRef.fail(`Expected "${actual}" to equal "${message}"`); - } - /*istanbul ignore else*/ - if (callback) { - callback(); - } - }); + getResources(name, args).then((message) => { + /*istanbul ignore else*/ + if (actual !== message) { + windowRef.fail(`Expected "${actual}" to equal "${message}"`); + } + /*istanbul ignore else*/ + if (callback) { + callback(); + } + }); // Asynchronous matchers are currently unsupported, but // the method above works to fail the specific test in the @@ -298,19 +289,17 @@ const matchers: jasmine.CustomMatcherFactories = { actual = actual.trim(); } - getResourcesObservable(name, args) - .toPromise() - .then((message) => { - if (actual !== message) { - windowRef.fail( - `Expected element's inner text "${el.textContent}" to be "${message}"`, - ); - } - /*istanbul ignore else*/ - if (callback) { - callback(); - } - }); + getResources(name, args).then((message) => { + if (actual !== message) { + windowRef.fail( + `Expected element's inner text "${el.textContent}" to be "${message}"`, + ); + } + /*istanbul ignore else*/ + if (callback) { + callback(); + } + }); // Asynchronous matchers are currently unsupported, but // the method above works to fail the specific test in the @@ -336,19 +325,17 @@ const matchers: jasmine.CustomMatcherFactories = { ): jasmine.CustomMatcherResult { const actual = el.textContent; - getResourcesObservable(name) - .pipe(take(1)) - .subscribe((message) => { - if (!isTemplateMatch(actual, message)) { - windowRef.fail( - `Expected element's text "${actual}" to match "${message}"`, - ); - } - /*istanbul ignore else*/ - if (callback) { - callback(); - } - }); + getResources(name).then((message) => { + if (!isTemplateMatch(actual, message)) { + windowRef.fail( + `Expected element's text "${actual}" to match "${message}"`, + ); + } + /*istanbul ignore else*/ + if (callback) { + callback(); + } + }); // Asynchronous matchers are currently unsupported, but // the method above works to fail the specific test in the @@ -398,21 +385,17 @@ const asyncMatchers: jasmine.CustomAsyncMatcherFactories = { name: string, args?: any[], ): Promise { - return new Promise((resolve) => { - getResourcesObservable(name, args) - .toPromise() - .then((message) => { - if (actual === message) { - resolve({ - pass: true, - }); - } else { - resolve({ - pass: false, - message: `Expected "${actual}" to equal "${message}"`, - }); - } - }); + return getResources(name, args).then((message) => { + if (actual === message) { + return { + pass: true, + }; + } else { + return { + pass: false, + message: `Expected "${actual}" to equal "${message}"`, + }; + } }); }, }; @@ -425,21 +408,17 @@ const asyncMatchers: jasmine.CustomAsyncMatcherFactories = { name: string, args?: any[], ): Promise { - return new Promise((resolve) => { - getLibResourcesObservable(name, args) - .toPromise() - .then((message) => { - if (actual === message) { - resolve({ - pass: true, - }); - } else { - resolve({ - pass: false, - message: `Expected "${actual}" to equal "${message}"`, - }); - } - }); + return getLibResources(name, args).then((message) => { + if (actual === message) { + return { + pass: true, + }; + } else { + return { + pass: false, + message: `Expected "${actual}" to equal "${message}"`, + }; + } }); }, }; @@ -453,26 +432,22 @@ const asyncMatchers: jasmine.CustomAsyncMatcherFactories = { args?: any[], trimWhitespace = true, ): Promise { - return new Promise((resolve) => { + return getResources(name, args).then((message) => { let actual = element.textContent; if (trimWhitespace) { actual = actual.trim(); } - getResourcesObservable(name, args) - .toPromise() - .then((message) => { - if (actual === message) { - resolve({ - pass: true, - }); - } else { - resolve({ - pass: false, - message: `Expected element's inner text "${actual}" to be "${message}"`, - }); - } - }); + if (actual === message) { + return { + pass: true, + }; + } else { + return { + pass: false, + message: `Expected element's inner text "${actual}" to be "${message}"`, + }; + } }); }, }; @@ -486,26 +461,21 @@ const asyncMatchers: jasmine.CustomAsyncMatcherFactories = { args?: any[], trimWhitespace = true, ): Promise { - return new Promise((resolve) => { + return getLibResources(name, args).then((message) => { let actual = element.textContent; if (trimWhitespace) { actual = actual.trim(); } - - getLibResourcesObservable(name, args) - .toPromise() - .then((message) => { - if (actual === message) { - resolve({ - pass: true, - }); - } else { - resolve({ - pass: false, - message: `Expected element's inner text "${actual}" to be "${message}"`, - }); - } - }); + if (actual === message) { + return { + pass: true, + }; + } else { + return { + pass: false, + message: `Expected element's inner text "${actual}" to be "${message}"`, + }; + } }); }, }; @@ -517,23 +487,18 @@ const asyncMatchers: jasmine.CustomAsyncMatcherFactories = { element: any, name: string, ): Promise { - return new Promise((resolve) => { + return getResources(name).then((message) => { const actual = element.textContent; - - getResourcesObservable(name) - .pipe(take(1)) - .subscribe((message) => { - if (isTemplateMatch(actual, message)) { - resolve({ - pass: true, - }); - } else { - resolve({ - pass: false, - message: `Expected element's text "${actual}" to match "${message}"`, - }); - } - }); + if (isTemplateMatch(actual, message)) { + return { + pass: true, + }; + } else { + return { + pass: false, + message: `Expected element's text "${actual}" to match "${message}"`, + }; + } }); }, }; @@ -545,23 +510,18 @@ const asyncMatchers: jasmine.CustomAsyncMatcherFactories = { element: any, name: string, ): Promise { - return new Promise((resolve) => { + return getLibResources(name).then((message) => { const actual = element.textContent; - - getLibResourcesObservable(name) - .pipe(take(1)) - .subscribe((message) => { - if (isTemplateMatch(actual, message)) { - resolve({ - pass: true, - }); - } else { - resolve({ - pass: false, - message: `Expected element's text "${actual}" to match "${message}"`, - }); - } - }); + if (isTemplateMatch(actual, message)) { + return { + pass: true, + }; + } else { + return { + pass: false, + message: `Expected element's text "${actual}" to match "${message}"`, + }; + } }); }, }; From c6ab957d126c3c153829257436356df4073728ab Mon Sep 17 00:00:00 2001 From: Paul Crowder Date: Wed, 2 Oct 2024 19:15:16 -0400 Subject: [PATCH 5/6] fix(components/tiles): remove horizontal spacing from tile dashboard in blocks page layout (#2792) --- ...blocks-tile-dashboard-page.component.cy.ts | 28 ++++++++ .../tabs-tile-dashboard-page.component.cy.ts | 28 ++++++++ ...s-tile-dashboard-page.component.stories.ts | 12 ++++ .../blocks-tile-dashboard-page.component.ts | 60 ++++++++++++++++ .../src/app/page/layouts/routes.ts | 12 ++++ .../shared/tiles/tile-dashboard.component.ts | 58 +++++++++++++++ .../layouts/shared/tiles/tile1.component.ts | 21 ++++++ .../layouts/shared/tiles/tile2.component.ts | 19 +++++ ...s-tile-dashboard-page.component.stories.ts | 12 ++++ .../tabs-tile-dashboard-page.component.ts | 32 +++++++++ .../blocks-page-tile-dashboard.component.ts | 39 ++++++++++ .../app/components/pages/layouts/routes.ts | 12 ++++ .../shared/tiles/tile-dashboard.component.ts | 71 +++++++++++++++++++ .../layouts/shared/tiles/tile1.component.ts | 21 ++++++ .../layouts/shared/tiles/tile2.component.ts | 19 +++++ .../layouts/shared/tiles/tile3.component.ts | 19 +++++ .../tabs-page/tabs-page.component.html | 3 + .../layouts/tabs-page/tabs-page.component.ts | 2 + .../tile-dashboard-column.component.scss | 19 +++++ .../tile-dashboard.component.html | 10 ++- .../tile-dashboard.component.scss | 4 ++ .../tile-dashboard.component.ts | 25 ++++--- 22 files changed, 517 insertions(+), 9 deletions(-) create mode 100644 apps/e2e/pages-storybook-e2e/src/e2e/blocks-tile-dashboard-page.component.cy.ts create mode 100644 apps/e2e/pages-storybook-e2e/src/e2e/tabs-tile-dashboard-page.component.cy.ts create mode 100644 apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.stories.ts create mode 100644 apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.ts create mode 100644 apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile-dashboard.component.ts create mode 100644 apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile1.component.ts create mode 100644 apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile2.component.ts create mode 100644 apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.stories.ts create mode 100644 apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.ts create mode 100644 apps/playground/src/app/components/pages/layouts/blocks-page-tile-dashboard/blocks-page-tile-dashboard.component.ts create mode 100644 apps/playground/src/app/components/pages/layouts/shared/tiles/tile-dashboard.component.ts create mode 100644 apps/playground/src/app/components/pages/layouts/shared/tiles/tile1.component.ts create mode 100644 apps/playground/src/app/components/pages/layouts/shared/tiles/tile2.component.ts create mode 100644 apps/playground/src/app/components/pages/layouts/shared/tiles/tile3.component.ts diff --git a/apps/e2e/pages-storybook-e2e/src/e2e/blocks-tile-dashboard-page.component.cy.ts b/apps/e2e/pages-storybook-e2e/src/e2e/blocks-tile-dashboard-page.component.cy.ts new file mode 100644 index 0000000000..2681098797 --- /dev/null +++ b/apps/e2e/pages-storybook-e2e/src/e2e/blocks-tile-dashboard-page.component.cy.ts @@ -0,0 +1,28 @@ +import { E2eVariations } from '@skyux-sdk/e2e-schematics'; + +const ID = 'blocks-tile-dashboard-pagecomponent--blocks-tile-dashboard-page'; + +const SELECTOR_PAGE = 'app-blocks-tile-dashboard-page'; + +describe(`pages-storybook-blocks-tile-dashboard`, () => { + E2eVariations.forEachTheme((theme) => { + describe(`in ${theme} theme`, () => { + E2eVariations.RESPONSIVE_WIDTHS.forEach((width) => { + describe(`at ${width}px`, () => { + beforeEach(() => { + cy.viewport(width, 960); + cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`); + }); + + it('should render the component', () => { + cy.get(SELECTOR_PAGE) + .should('exist') + .should('be.visible') + .screenshot(`${ID}-${theme}`); + cy.get(SELECTOR_PAGE).percySnapshot(`${ID}-${theme}`); + }); + }); + }); + }); + }); +}); diff --git a/apps/e2e/pages-storybook-e2e/src/e2e/tabs-tile-dashboard-page.component.cy.ts b/apps/e2e/pages-storybook-e2e/src/e2e/tabs-tile-dashboard-page.component.cy.ts new file mode 100644 index 0000000000..9c3678e9be --- /dev/null +++ b/apps/e2e/pages-storybook-e2e/src/e2e/tabs-tile-dashboard-page.component.cy.ts @@ -0,0 +1,28 @@ +import { E2eVariations } from '@skyux-sdk/e2e-schematics'; + +const ID = 'tabs-tile-dashboard-pagecomponent--tabs-tile-dashboard-page'; + +const SELECTOR_PAGE = 'app-tabs-tile-dashboard-page'; + +describe(`pages-storybook-tabs-tile-dashboard`, () => { + E2eVariations.forEachTheme((theme) => { + describe(`in ${theme} theme`, () => { + E2eVariations.RESPONSIVE_WIDTHS.forEach((width) => { + describe(`at ${width}px`, () => { + beforeEach(() => { + cy.viewport(width, 960); + cy.visit(`/iframe.html?globals=theme:${theme}&id=${ID}`); + }); + + it('should render the component', () => { + cy.get(SELECTOR_PAGE) + .should('exist') + .should('be.visible') + .screenshot(`${ID}-${theme}`); + cy.get(SELECTOR_PAGE).percySnapshot(`${ID}-${theme}`); + }); + }); + }); + }); + }); +}); diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.stories.ts b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.stories.ts new file mode 100644 index 0000000000..14e594f9bd --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.stories.ts @@ -0,0 +1,12 @@ +import type { Meta, StoryObj } from '@storybook/angular'; + +import BlocksTileDashboardPageComponent from './blocks-tile-dashboard-page.component'; + +export default { + id: 'blocks-tile-dashboard-pagecomponent', + title: 'Components/Page/Layouts/Blocks Tile Dashboard', + component: BlocksTileDashboardPageComponent, +} as Meta; +type Story = StoryObj; +export const BlocksTileDashboardPage: Story = {}; +BlocksTileDashboardPage.args = {}; diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.ts new file mode 100644 index 0000000000..643e32664a --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-tile-dashboard-page/blocks-tile-dashboard-page.component.ts @@ -0,0 +1,60 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { SkyAvatarModule } from '@skyux/avatar'; +import { SkyAlertModule, SkyLabelModule } from '@skyux/indicators'; +import { SkyPageModule } from '@skyux/pages'; + +import { PageLayoutTileDashboardComponent } from '../shared/tiles/tile-dashboard.component'; + +@Component({ + standalone: true, + imports: [ + CommonModule, + PageLayoutTileDashboardComponent, + SkyAlertModule, + SkyAvatarModule, + SkyLabelModule, + SkyPageModule, + ], + selector: 'app-blocks-tile-dashboard-page', + template: ` + + + + Urgent information about the page. + + + + + Important information + + + + + + + + + + + + + + `, + styles: ` + :host { + display: block; + } + `, +}) +export default class BlocksTileDashboardPageComponent {} diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts b/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts index a4e166b8d8..6b72cbbb35 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/routes.ts @@ -20,6 +20,18 @@ export default [ showLinks: true, }, }, + { + path: 'blocks-tile-dashboard', + loadComponent: () => + import( + './blocks-tile-dashboard-page/blocks-tile-dashboard-page.component' + ), + data: { + name: 'Page (Blocks - tile dashboard)', + icon: 'list', + library: 'pages', + }, + }, { path: 'fit', loadComponent: () => import('./fit-page/fit-page.component'), diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile-dashboard.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile-dashboard.component.ts new file mode 100644 index 0000000000..10285ee291 --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile-dashboard.component.ts @@ -0,0 +1,58 @@ +import { Component } from '@angular/core'; +import { SkyTileDashboardConfig, SkyTilesModule } from '@skyux/tiles'; + +import { BlocksTileDashboardPageTile1Component } from './tile1.component'; +import { BlocksTileDashboardPageTile2Component } from './tile2.component'; + +@Component({ + selector: 'app-page-layout-tile-dashboard', + standalone: true, + imports: [SkyTilesModule], + template: ``, +}) +export class PageLayoutTileDashboardComponent { + protected dashboardConfig: SkyTileDashboardConfig = { + tiles: [ + { + id: 'tile1', + componentType: BlocksTileDashboardPageTile1Component, + }, + { + id: 'tile2', + componentType: BlocksTileDashboardPageTile2Component, + }, + ], + layout: { + singleColumn: { + tiles: [ + { + id: 'tile2', + isCollapsed: false, + }, + { + id: 'tile1', + isCollapsed: true, + }, + ], + }, + multiColumn: [ + { + tiles: [ + { + id: 'tile1', + isCollapsed: true, + }, + ], + }, + { + tiles: [ + { + id: 'tile2', + isCollapsed: false, + }, + ], + }, + ], + }, + }; +} diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile1.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile1.component.ts new file mode 100644 index 0000000000..e80875ffe7 --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile1.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { SkyTilesModule } from '@skyux/tiles'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'div.page-blocks-tile1', + standalone: true, + imports: [SkyTilesModule], + template: ` + Tile 1 + Tile summary + + Hi + + `, +}) +export class BlocksTileDashboardPageTile1Component {} diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile2.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile2.component.ts new file mode 100644 index 0000000000..2650ee0fa9 --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/page/layouts/shared/tiles/tile2.component.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; +import { SkyHelpInlineModule } from '@skyux/help-inline'; +import { SkyTilesModule } from '@skyux/tiles'; + +@Component({ + standalone: true, + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'div.page-blocks-tile2', + imports: [SkyHelpInlineModule, SkyTilesModule], + template: ` + Tile 2 + + Section 1 + Section 2 + Section 3 + + `, +}) +export class BlocksTileDashboardPageTile2Component {} diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.stories.ts b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.stories.ts new file mode 100644 index 0000000000..0af14b5c14 --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.stories.ts @@ -0,0 +1,12 @@ +import type { Meta, StoryObj } from '@storybook/angular'; + +import { TabsTileDashboardPageComponent } from './tabs-tile-dashboard-page.component'; + +export default { + id: 'tabs-tile-dashboard-pagecomponent', + title: 'Components/Page/Layouts/Tabs Tile Dashboard', + component: TabsTileDashboardPageComponent, +} as Meta; +type Story = StoryObj; +export const TabsTileDashboardPage: Story = {}; +TabsTileDashboardPage.args = {}; diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.ts new file mode 100644 index 0000000000..1aed1dac7c --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-tile-dashboard-page/tabs-tile-dashboard-page.component.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; +import { SkyPageModule } from '@skyux/pages'; +import { SkyTabsModule } from '@skyux/tabs'; + +import { PageLayoutTileDashboardComponent } from '../shared/tiles/tile-dashboard.component'; + +@Component({ + standalone: true, + imports: [PageLayoutTileDashboardComponent, SkyPageModule, SkyTabsModule], + selector: 'app-tabs-tile-dashboard-page', + template: ` + + + + + + + + + + + + + + `, + styles: ` + :host { + display: block; + } + `, +}) +export class TabsTileDashboardPageComponent {} diff --git a/apps/playground/src/app/components/pages/layouts/blocks-page-tile-dashboard/blocks-page-tile-dashboard.component.ts b/apps/playground/src/app/components/pages/layouts/blocks-page-tile-dashboard/blocks-page-tile-dashboard.component.ts new file mode 100644 index 0000000000..d08c774d4b --- /dev/null +++ b/apps/playground/src/app/components/pages/layouts/blocks-page-tile-dashboard/blocks-page-tile-dashboard.component.ts @@ -0,0 +1,39 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { SkyFluidGridModule } from '@skyux/layout'; +import { SkyPageModule } from '@skyux/pages'; + +import { PageLayoutTileDashboardComponent } from '../shared/tiles/tile-dashboard.component'; + +@Component({ + standalone: true, + imports: [ + SkyFluidGridModule, + SkyPageModule, + PageLayoutTileDashboardComponent, + ], + template: ` + + + + + + + + Other content + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export default class BlocksTileDashboardPageComponent {} diff --git a/apps/playground/src/app/components/pages/layouts/routes.ts b/apps/playground/src/app/components/pages/layouts/routes.ts index 33dd36423c..ecc42033b3 100644 --- a/apps/playground/src/app/components/pages/layouts/routes.ts +++ b/apps/playground/src/app/components/pages/layouts/routes.ts @@ -10,6 +10,18 @@ export default [ library: 'pages', }, }, + { + path: 'blocks-tile-dashboard-page', + loadComponent: () => + import( + './blocks-page-tile-dashboard/blocks-page-tile-dashboard.component' + ), + data: { + name: 'Page (Blocks - tile dashboard)', + icon: 'square-o', + library: 'pages', + }, + }, { path: 'fit-page', loadComponent: () => import('./fit-page/fit-page.component'), diff --git a/apps/playground/src/app/components/pages/layouts/shared/tiles/tile-dashboard.component.ts b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile-dashboard.component.ts new file mode 100644 index 0000000000..45d5628d66 --- /dev/null +++ b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile-dashboard.component.ts @@ -0,0 +1,71 @@ +import { Component } from '@angular/core'; +import { SkyTileDashboardConfig, SkyTilesModule } from '@skyux/tiles'; + +import { BlocksTileDashboardPageTile1Component } from './tile1.component'; +import { BlocksTileDashboardPageTile2Component } from './tile2.component'; +import { BlocksTileDashboardPageTile3Component } from './tile3.component'; + +@Component({ + selector: 'app-page-layout-tile-dashboard', + standalone: true, + imports: [SkyTilesModule], + template: ``, +}) +export class PageLayoutTileDashboardComponent { + protected dashboardConfig: SkyTileDashboardConfig = { + tiles: [ + { + id: 'tile1', + componentType: BlocksTileDashboardPageTile1Component, + }, + { + id: 'tile2', + componentType: BlocksTileDashboardPageTile2Component, + }, + { + id: 'tile3', + componentType: BlocksTileDashboardPageTile3Component, + }, + ], + layout: { + singleColumn: { + tiles: [ + { + id: 'tile2', + isCollapsed: false, + }, + { + id: 'tile1', + isCollapsed: true, + }, + { + id: 'tile3', + isCollapsed: true, + }, + ], + }, + multiColumn: [ + { + tiles: [ + { + id: 'tile1', + isCollapsed: true, + }, + ], + }, + { + tiles: [ + { + id: 'tile2', + isCollapsed: false, + }, + { + id: 'tile3', + isCollapsed: true, + }, + ], + }, + ], + }, + }; +} diff --git a/apps/playground/src/app/components/pages/layouts/shared/tiles/tile1.component.ts b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile1.component.ts new file mode 100644 index 0000000000..f1785c03bb --- /dev/null +++ b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile1.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { SkyTilesModule } from '@skyux/tiles'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'div.page-blocks-tile1', + standalone: true, + imports: [SkyTilesModule], + template: ` + Tile 1 + Tile summary + + Hi + + `, +}) +export class BlocksTileDashboardPageTile1Component {} diff --git a/apps/playground/src/app/components/pages/layouts/shared/tiles/tile2.component.ts b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile2.component.ts new file mode 100644 index 0000000000..2650ee0fa9 --- /dev/null +++ b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile2.component.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; +import { SkyHelpInlineModule } from '@skyux/help-inline'; +import { SkyTilesModule } from '@skyux/tiles'; + +@Component({ + standalone: true, + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'div.page-blocks-tile2', + imports: [SkyHelpInlineModule, SkyTilesModule], + template: ` + Tile 2 + + Section 1 + Section 2 + Section 3 + + `, +}) +export class BlocksTileDashboardPageTile2Component {} diff --git a/apps/playground/src/app/components/pages/layouts/shared/tiles/tile3.component.ts b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile3.component.ts new file mode 100644 index 0000000000..80ad0a3fbc --- /dev/null +++ b/apps/playground/src/app/components/pages/layouts/shared/tiles/tile3.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { SkyTilesModule } from '@skyux/tiles'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SkyTilesModule], + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'div.page-blocks-tile2', + standalone: true, + template: ` + + Tile 3 + + Content here. + + + `, +}) +export class BlocksTileDashboardPageTile3Component {} diff --git a/apps/playground/src/app/components/pages/layouts/tabs-page/tabs-page.component.html b/apps/playground/src/app/components/pages/layouts/tabs-page/tabs-page.component.html index 684d0849ce..07fb2fa41b 100644 --- a/apps/playground/src/app/components/pages/layouts/tabs-page/tabs-page.component.html +++ b/apps/playground/src/app/components/pages/layouts/tabs-page/tabs-page.component.html @@ -14,6 +14,9 @@ + + + Tab 1 Content {{ tileMovedReport }} - @for (column of config.layout.multiColumn; track column) { + @for ( + column of config.layout.multiColumn; + track column; + let first = $first, last = $last + ) { { + let layoutClassName = this.layoutClassName(); + if ( args === SkyMediaBreakpoints.xs || args === SkyMediaBreakpoints.sm ) { - this.layoutClassName = 'sky-tile-dashboard-single-column'; + layoutClassName = 'sky-tile-dashboard-single-column'; } else { - this.layoutClassName = 'sky-tile-dashboard-multi-column'; + layoutClassName = 'sky-tile-dashboard-multi-column'; } if (args === SkyMediaBreakpoints.xs) { - this.layoutClassName += ' sky-tile-dashboard-xs'; + layoutClassName += ' sky-tile-dashboard-xs'; } else { - this.layoutClassName += ' sky-tile-dashboard-gt-xs'; + layoutClassName += ' sky-tile-dashboard-gt-xs'; + } + + if (layoutClassName !== this.layoutClassName()) { + this.layoutClassName.set(layoutClassName); } }), ); From 21dba3b5cc1eefa1493bf2f70ed2ad9184ad4aeb Mon Sep 17 00:00:00 2001 From: Paul Crowder Date: Thu, 3 Oct 2024 09:46:43 -0400 Subject: [PATCH 6/6] fix(components/pages): display dropdowns as inline and fix margins around wrapped buttons (#2794) --- .../blocks-page/blocks-page.component.html | 46 +++++++++++++++++++ .../blocks-page/blocks-page.component.ts | 2 + .../page-header-actions.component.html | 1 - .../page-header-actions.component.scss | 25 ++++++++-- .../page-header-actions.component.ts | 5 +- 5 files changed, 71 insertions(+), 8 deletions(-) delete mode 100644 libs/components/pages/src/lib/modules/page-header/page-header-actions.component.html diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html index 0ef3a09ffd..50e43a1c34 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html @@ -23,6 +23,52 @@ + + Group action one + + + + + + + + Group action two + + + + + + + Group action three + + + + + diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts index d16794e868..563e99d012 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts @@ -4,6 +4,7 @@ import { SkyAvatarModule } from '@skyux/avatar'; import { SkyAlertModule, SkyLabelModule } from '@skyux/indicators'; import { SkyBoxModule, SkyFluidGridModule } from '@skyux/layout'; import { SkyPageModule } from '@skyux/pages'; +import { SkyDropdownModule } from '@skyux/popovers'; @Component({ selector: 'app-blocks-page', @@ -13,6 +14,7 @@ import { SkyPageModule } from '@skyux/pages'; SkyAlertModule, SkyAvatarModule, SkyBoxModule, + SkyDropdownModule, SkyFluidGridModule, SkyLabelModule, SkyPageModule, diff --git a/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.html b/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.html deleted file mode 100644 index 40b3726403..0000000000 --- a/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.scss b/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.scss index c684219681..e0e710f6c5 100644 --- a/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.scss +++ b/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.scss @@ -1,13 +1,28 @@ -:host { +sky-page-header-actions { display: block; &:not(:empty) { margin-top: var(--sky-margin-stacked-xl); + // Negate the bottom margin of the last row of buttons so it doesn't create extra + // vertical space between the actions and the page content. + margin-bottom: calc(var(--sky-margin-stacked-sm) * -1); + } + + > sky-dropdown { + display: inline-block; + } + + // Only style direct descendants to prevent adding additional margin to the button + // inside the dropdown. + > :is(.sky-btn, sky-dropdown) { + // Vertical space between rows of buttons that have wrapped. + margin-bottom: var(--sky-margin-stacked-sm); - ::ng-deep { - .sky-btn + .sky-btn { - margin-left: var(--sky-margin-inline-sm); - } + &:not(:last-child) { + // Horizontal space between buttons on the same row. Use margin-right so that + // extra horizontal space is not added to the first button that wraps to the + // next line. + margin-right: var(--sky-margin-inline-sm); } } } diff --git a/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.ts b/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.ts index 70a1e99c02..937ffdb16c 100644 --- a/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.ts +++ b/libs/components/pages/src/lib/modules/page-header/page-header-actions.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, ViewEncapsulation } from '@angular/core'; /** * Displays buttons within the page header for page actions and applies spacing between buttons. @@ -6,7 +6,8 @@ import { Component } from '@angular/core'; */ @Component({ selector: 'sky-page-header-actions', - templateUrl: './page-header-actions.component.html', + template: ``, styleUrls: ['./page-header-actions.component.scss'], + encapsulation: ViewEncapsulation.None, }) export class SkyPageHeaderActionsComponent {}