Skip to content

Commit

Permalink
Merge branch 'main' into 1526-doc-internet-header-import
Browse files Browse the repository at this point in the history
  • Loading branch information
gfellerph authored Aug 24, 2023
2 parents c89d245 + 61a47e8 commit 1aa554c
Show file tree
Hide file tree
Showing 46 changed files with 1,212 additions and 339 deletions.
7 changes: 7 additions & 0 deletions .changeset/shy-cooks-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@swisspost/design-system-documentation': minor
'@swisspost/design-system-components': minor
'@swisspost/design-system-styles': patch
---

Added a new post-tabs component.
6 changes: 4 additions & 2 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners

* @gfellerph @oliverschuerch @alizedebray @imagoiq
# Maintainers have to sign off PRs to main as required in branch protection rules
* @swisspost/design-system-maintainers

/packages/documentation @b1aserlu @davidritter-dotcom
# For the duration of the practical year
/packages/documentation @swisspost/design-system-maintainers @b1aserlu @davidritter-dotcom
19 changes: 19 additions & 0 deletions .github/workflows/stale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: 'Mark stale issues'
permissions:
issues: write
on:
workflow_dispatch:
schedule:
- cron: '9 22 * * *' # the job will run every day at 22:09

jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
days-before-stale: 60
days-before-close: -1
stale-issue-label: 'stale'
exempt-milestones: 'next'
operations-per-run: 100
11 changes: 6 additions & 5 deletions packages/components/cypress/e2e/collapsible.cy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
describe('collapsible', () => {
describe('default', () => {
beforeEach(() => {
cy.registerCollapsibleFrom('/iframe.html?id=components-collapsible--default');
cy.getComponent('collapsible');
cy.get('@collapsible').find('.collapse').as('collapse');
cy.get('@collapsible').find('.accordion-header').as('header');
cy.get('@collapsible').find('.accordion-body').as('body');
});
Expand Down Expand Up @@ -62,9 +63,8 @@ describe('collapsible', () => {

describe('initially collapsed', () => {
beforeEach(() => {
cy.registerCollapsibleFrom(
'/iframe.html?id=components-collapsible--initially-collapsed',
);
cy.getComponent('collapsible', 'initially-collapsed');
cy.get('@collapsible').find('.collapse').as('collapse');
cy.get('@collapsible').find('.accordion-header').as('header');
});

Expand All @@ -89,7 +89,8 @@ describe('collapsible', () => {

describe('custom trigger', () => {
beforeEach(() => {
cy.registerCollapsibleFrom('/iframe.html?id=components-collapsible--custom-trigger');
cy.getComponent('collapsible', 'custom-trigger');
cy.get('@collapsible').find('.collapse').as('collapse');
cy.get('[aria-controls="collapsible-example--custom-trigger"]').as('controls');
cy.get('@controls').contains('Toggle').as('toggle');
cy.get('@controls').contains('Show').as('show');
Expand Down
144 changes: 144 additions & 0 deletions packages/components/cypress/e2e/tabs.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
describe('tabs', () => {
describe('default', () => {
beforeEach(() => {
cy.getComponent('tabs');
cy.get('post-tab-header').as('headers');
});

it('should render', () => {
cy.get('@tabs').should('exist');
});

it('should show three tab headers', () => {
cy.get('@headers').should('have.length', 3);
});

it('should only show the first tab header as active', () => {
cy.get('@headers').each(($header, index) => {
cy.wrap($header).find('.active').should(index === 0 ? 'exist' : 'not.exist');
});
});

it('should only show the tab panel associated with the first tab header', () => {
cy.get('post-tab-panel:visible').as('panel');
cy.get('@panel').should('have.length', 1);
cy.get('@headers').first().invoke('attr', 'panel').then(panel => {
cy.get('@panel').invoke('attr', 'name').should('equal', panel);
});
});

it('should activate a clicked tab header and deactivate the tab header that was previously activated', () => {
cy.get('@headers').last().click();

cy.get('@headers').first().find('.active').should('not.exist');
cy.get('@headers').last().find('.active').should('exist');
});

it('should show the panel associated with a clicked tab header and hide the panel that was previously shown', () => {
cy.get('@headers').last().click();

// wait for the fade out animation to complete
cy.wait(200);

cy.get('post-tab-panel:visible').as('panel');
cy.get('@panel').should('have.length', 1);
cy.get('@headers').last().invoke('attr', 'panel').then(panel => {
cy.get('@panel').invoke('attr', 'name').should('equal', panel);
});
});
});

describe('active panel', () => {
beforeEach(() => {
cy.getComponent('tabs', 'active-panel');
cy.get('post-tab-header').as('headers');
cy.get('post-tab-panel:visible').as('panel');
});

it('should only show the requested active tab panel', () => {
cy.get('@panel').should('have.length', 1);
cy.get('@tabs').invoke('attr', 'active-panel').then(activePanel => {
cy.get('@panel').invoke('attr', 'name').should('equal', activePanel);
});
});

it('should show as active only the tab header associated with the requested active tab panel', () => {
cy.get('@tabs').invoke('attr', 'active-panel').then(activePanel => {
cy.get('@headers').each($header => {
cy.wrap($header).invoke('attr', 'panel').then(panel => {
cy.wrap($header).find('.active').should(panel === activePanel ? 'exist' : 'not.exist');
});
});
});
});
});

describe('async', () => {
beforeEach(() => {
cy.getComponent('tabs', 'async');
cy.get('post-tab-header').as('headers');
});

it('should add a tab header', () => {
cy.get('#add-tab').click();
cy.get('@headers').should('have.length', 4);
});

it('should still show the tab panel associated with the first tab header after adding new tab', () => {
cy.get('#add-tab').click();

cy.get('post-tab-panel:visible').as('panel');
cy.get('@panel').should('have.length', 1);
cy.get('@headers').first().invoke('attr', 'panel').then(panel => {
cy.get('@panel').invoke('attr', 'name').should('equal', panel);
});
});

it('should activate the newly added tab header after clicking on it', () => {
cy.get('#add-tab').click();

cy.get('post-tab-header').as('headers');
cy.get('@headers').last().click();

cy.get('@headers').first().find('.active').should('not.exist');
cy.get('@headers').last().find('.active').should('exist');
});

it('should display the tab panel associated with the newly added tab after clicking on it', () => {
cy.get('#add-tab').click();

cy.get('post-tab-header').last().as('new-panel');
cy.get('@new-panel').click();

// wait for the fade out animation to complete
cy.wait(200);

cy.get('post-tab-panel:visible').as('panel');
cy.get('@panel').should('have.length', 1);
cy.get('@new-panel').invoke('attr', 'panel').then(panel => {
cy.get('@panel').invoke('attr', 'name').should('equal', panel);
});
});

it('should remove a tab header', () => {
cy.get('.tab-title.active').then(() => {
cy.get('#remove-active-tab').click();
cy.get('@headers').should('have.length', 2);
});
});

it('should still show an active tab header after removing the active tab', () => {
cy.get('.tab-title.active').then(() => {
cy.get('#remove-active-tab').click();
cy.get('.tab-title.active').should('exist');
});
});

it('should still show a tab panel after removing the active tab', () => {
cy.get('.tab-title.active').then(() => {
cy.get('#remove-active-tab').click();
cy.get('post-tab-panel:visible').should('exist');
});
});
});
});
7 changes: 3 additions & 4 deletions packages/components/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ export const isInViewport = function (_chai: Chai.ChaiStatic) {

chai.use(isInViewport);

Cypress.Commands.add('registerCollapsibleFrom', (url: string) => {
cy.visit(url);
cy.get('post-collapsible').as('collapsible');
cy.get('@collapsible').find('.collapse').as('collapse');
Cypress.Commands.add('getComponent', (component: string, story = 'default') => {
cy.visit(`/iframe.html?id=components-${component}--${story}`);
cy.get(`post-${component}`).as(component);
});

Cypress.Commands.add('checkVisibility', (visibility: 'visible' | 'hidden') => {
Expand Down
2 changes: 1 addition & 1 deletion packages/components/cypress/support/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
declare global {
namespace Cypress {
interface Chainable {
registerCollapsibleFrom(url: string): Chainable<any>;
getComponent(component: string, story?: string): Chainable<any>;
checkVisibility(visibility: 'visible' | 'hidden'): Chainable<any>;
checkAriaExpanded(isExpanded: 'true' | 'false'): Chainable<any>;
}
Expand Down
13 changes: 13 additions & 0 deletions packages/components/src/animations/fade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const fadeDuration = 200;
const fadedOutKeyFrame = {opacity: '0'};
const fadedInKeyFrame = {opacity: '1'};

export const fadeIn = (el: Element): Animation => el.animate(
[ fadedOutKeyFrame, fadedInKeyFrame ],
{ duration: fadeDuration }
);

export const fadeOut = (el: Element): Animation => el.animate(
[ fadedInKeyFrame, fadedOutKeyFrame ],
{ duration: fadeDuration }
);
1 change: 1 addition & 0 deletions packages/components/src/animations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './fade';
75 changes: 75 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ export namespace Components {
*/
"scale"?: number | null;
}
interface PostTabHeader {
/**
* The name of the panel controlled by the tab header.
*/
"panel": HTMLPostTabPanelElement['name'];
}
interface PostTabPanel {
/**
* The name of the panel, used to associate it with a tab header.
*/
"name": string;
}
interface PostTabs {
/**
* The name of the panel that is initially shown. If not specified, it defaults to the panel associated with the first tab. **Changing this value after initialization has no effect.**
*/
"activePanel": HTMLPostTabPanelElement['name'];
/**
* Shows the panel with the given name and selects its associated tab. Any other panel that was previously shown becomes hidden and its associated tab is unselected.
*/
"show": (panelName: string) => Promise<void>;
}
}
export interface PostTabsCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLPostTabsElement;
}
declare global {
interface HTMLPostCollapsibleElement extends Components.PostCollapsible, HTMLStencilElement {
Expand All @@ -70,9 +96,30 @@ declare global {
prototype: HTMLPostIconElement;
new (): HTMLPostIconElement;
};
interface HTMLPostTabHeaderElement extends Components.PostTabHeader, HTMLStencilElement {
}
var HTMLPostTabHeaderElement: {
prototype: HTMLPostTabHeaderElement;
new (): HTMLPostTabHeaderElement;
};
interface HTMLPostTabPanelElement extends Components.PostTabPanel, HTMLStencilElement {
}
var HTMLPostTabPanelElement: {
prototype: HTMLPostTabPanelElement;
new (): HTMLPostTabPanelElement;
};
interface HTMLPostTabsElement extends Components.PostTabs, HTMLStencilElement {
}
var HTMLPostTabsElement: {
prototype: HTMLPostTabsElement;
new (): HTMLPostTabsElement;
};
interface HTMLElementTagNameMap {
"post-collapsible": HTMLPostCollapsibleElement;
"post-icon": HTMLPostIconElement;
"post-tab-header": HTMLPostTabHeaderElement;
"post-tab-panel": HTMLPostTabPanelElement;
"post-tabs": HTMLPostTabsElement;
}
}
declare namespace LocalJSX {
Expand Down Expand Up @@ -119,9 +166,34 @@ declare namespace LocalJSX {
*/
"scale"?: number | null;
}
interface PostTabHeader {
/**
* The name of the panel controlled by the tab header.
*/
"panel"?: HTMLPostTabPanelElement['name'];
}
interface PostTabPanel {
/**
* The name of the panel, used to associate it with a tab header.
*/
"name"?: string;
}
interface PostTabs {
/**
* The name of the panel that is initially shown. If not specified, it defaults to the panel associated with the first tab. **Changing this value after initialization has no effect.**
*/
"activePanel"?: HTMLPostTabPanelElement['name'];
/**
* An event emitted after the active tab changes, when the fade in transition of its associated panel is finished. The payload is the name of the newly shown panel.
*/
"onTabChange"?: (event: PostTabsCustomEvent<HTMLPostTabPanelElement['name']>) => void;
}
interface IntrinsicElements {
"post-collapsible": PostCollapsible;
"post-icon": PostIcon;
"post-tab-header": PostTabHeader;
"post-tab-panel": PostTabPanel;
"post-tabs": PostTabs;
}
}
export { LocalJSX as JSX };
Expand All @@ -133,6 +205,9 @@ declare module "@stencil/core" {
* @class PostIcon - representing a stencil component
*/
"post-icon": LocalJSX.PostIcon & JSXBase.HTMLAttributes<HTMLPostIconElement>;
"post-tab-header": LocalJSX.PostTabHeader & JSXBase.HTMLAttributes<HTMLPostTabHeaderElement>;
"post-tab-panel": LocalJSX.PostTabPanel & JSXBase.HTMLAttributes<HTMLPostTabPanelElement>;
"post-tabs": LocalJSX.PostTabs & JSXBase.HTMLAttributes<HTMLPostTabsElement>;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@use '@swisspost/design-system-styles/components/tabs/tab-title';

:host {
display: block;
}
Loading

0 comments on commit 1aa554c

Please sign in to comment.