Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
182fce2
feat(pie-tabs): DSW-3169 added files from generator
thejfreitas Aug 5, 2025
4c8a914
feat(pie-tabs): DSW-3169 added changeset entry
thejfreitas Aug 5, 2025
503bf25
feat(pie-tabs): DSW-3303 implemented pie-tab-panel sub component
thejfreitas Aug 5, 2025
0a96f63
feat(pie-tabs): DSW-3303 implemented default properties
thejfreitas Aug 5, 2025
99da21d
feat(pie-tabs): DSW-3303 implemented basic styling
thejfreitas Aug 5, 2025
9573f3f
feat(pie-tabs): DSW-3303 implemented basic capabilities for tabs
thejfreitas Aug 5, 2025
a7c0ed9
feat(pie-tabs): DSW-3303 added controls into storybook
thejfreitas Aug 5, 2025
0723a5c
feat(pie-tabs): DSW-3303 removed wrong file
thejfreitas Aug 5, 2025
e489173
feat(pie-tabs): DSW-3303 added basic tests
thejfreitas Aug 5, 2025
bb56a12
feat(pie-tabs): DSW-3303 added changeset entry
thejfreitas Aug 5, 2025
0abf29b
Merge branch 'main' into dsw-3169-pie-tabs-create-markup
thejfreitas Aug 11, 2025
5ca9ad9
Merge branch 'dsw-3169-pie-tabs-create-markup' into dsw-3303-pie-tabs…
thejfreitas Aug 11, 2025
8ab9822
Merge branch 'main' into dsw-3169-pie-tabs-create-markup
thejfreitas Aug 12, 2025
c13af3b
Merge branch 'dsw-3169-pie-tabs-create-markup' into dsw-3303-pie-tabs…
thejfreitas Aug 12, 2025
0b02431
Merge branch 'main' into dsw-3303-pie-tabs-implement-basic-funcitonal…
thejfreitas Aug 13, 2025
76aba40
Merge branch 'main' into dsw-3303-pie-tabs-implement-basic-funcitonal…
thejfreitas Aug 14, 2025
7c12a6d
Merge branch 'main' into dsw-3303-pie-tabs-implement-basic-funcitonal…
thejfreitas Sep 4, 2025
35451bd
Merge branch 'main' into dsw-3303-pie-tabs-implement-basic-funcitonal…
thejfreitas Sep 19, 2025
eea54ec
Merge branch 'main' into dsw-3303-pie-tabs-implement-basic-funcitonal…
thejfreitas Nov 21, 2025
2a2f087
Merge branch 'main' into dsw-3303-pie-tabs-implement-basic-funcitonal…
thejfreitas Dec 11, 2025
ad53a53
Merge branch 'main' into dsw-3303-pie-tabs-implement-basic-funcitonal…
thejfreitas Jan 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/two-points-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@justeattakeaway/pie-tabs": minor
"@justeattakeaway/pie-storybook": minor
---

[Added] - basic styling and default properties into pie-tabs
58 changes: 48 additions & 10 deletions apps/pie-storybook/stories/pie-tabs.stories.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,72 @@
import { html } from 'lit';
import { type Meta } from '@storybook/web-components';
import { type TabsProps as TabsPropsBase } from '@justeattakeaway/pie-tabs';
import { ifDefined } from 'lit/directives/if-defined.js';

import '@justeattakeaway/pie-tabs';
import { type TabsProps } from '@justeattakeaway/pie-tabs';
import '@justeattakeaway/pie-tabs/dist/pie-tab-panel';

import { createStory } from '../utilities';
import { type SlottedComponentProps } from '../types';
import { createStory, sanitizeAndRenderHTML } from '../utilities';

type TabsProps = SlottedComponentProps<TabsPropsBase>;
type TabsStoryMeta = Meta<TabsProps>;

const defaultArgs: TabsProps = {};
const slot = `
<pie-tab-panel title="Tab 1">Content 1</pie-tab-panel>
<pie-tab-panel title="Tab 2">Content 2</pie-tab-panel>
<pie-tab-panel title="Tab 3" disabled>Content 3</pie-tab-panel>
<pie-tab-panel title="Tab 4">Content 4</pie-tab-panel>
`;

const defaultArgs: TabsProps = {
slot,
};

const tabsStoryMeta: TabsStoryMeta = {
title: 'Components/Tabs',
title: 'Tabs',
component: 'pie-tabs',
argTypes: {},
argTypes: {
variant: {
description: 'Set the variant of the tabs.',
control: 'select',
options: ['global', 'contained'],
defaultValue: {
summary: 'global',
},
},
orientation: {
description: 'Set the orientation of the tabs.',
control: 'select',
options: ['horizontal', 'vertical'],
defaultValue: {
summary: 'horizontal',
},
},
slot: {
description: 'The default slot is used to pass `pie-tab-panel` elements. You must provide at least one `pie-tab-panel` element for the tabs to be visible.',
control: 'text',
},
},
args: defaultArgs,
parameters: {
design: {
type: 'figma',
url: '',
url: 'https://www.figma.com/design/pPSC73rPin4csb8DiK1CRr/branch/Mn7rERbBnNmaO2UAHT8qDz/%E2%9C%A8--Core--Web-Components--PIE-3-?node-id=12502-11771',
},
},
};

export default tabsStoryMeta;

// TODO: remove the eslint-disable rule when props are added
// eslint-disable-next-line no-empty-pattern
const Template = ({}: TabsProps) => html`
<pie-tabs></pie-tabs>
const Template = ({ slot, variant, orientation }: TabsProps) => html`
<pie-tabs
variant=${ifDefined(variant)}
orientation=${ifDefined(orientation)}
data-test-id="pie-tabs"
>
${sanitizeAndRenderHTML(slot, { ALLOWED_TAGS: ['pie-tab-panel'] })}
</pie-tabs>
`;

export const Default = createStory<TabsProps>(Template, defaultArgs)();
64 changes: 55 additions & 9 deletions apps/pie-storybook/stories/testing/pie-tabs.test.stories.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,80 @@
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { type Meta } from '@storybook/web-components';

import '@justeattakeaway/pie-tabs';
import { type TabsProps } from '@justeattakeaway/pie-tabs';
import { type TabsProps as TabsPropsBase } from '@justeattakeaway/pie-tabs';

import { createStory } from '../../utilities';
import { createStory, createVariantStory, sanitizeAndRenderHTML } from '../../utilities';
import { type SlottedComponentProps } from '../../types';

type TabsProps = SlottedComponentProps<TabsPropsBase>;
type TabsStoryMeta = Meta<TabsProps>;

const defaultArgs: TabsProps = {};
const slot = `
<pie-tab-panel title="Tab 1">Content 1</pie-tab-panel>
<pie-tab-panel title="Tab 2">Content 2</pie-tab-panel>
<pie-tab-panel title="Tab 3" disabled>Content 3</pie-tab-panel>
<pie-tab-panel title="Tab 4">Content 4</pie-tab-panel>
`;

const defaultArgs: TabsProps = {
slot,
};

const tabsStoryMeta: TabsStoryMeta = {
title: 'Tabs',
component: 'pie-tabs',
argTypes: {},
argTypes: {
variant: {
description: 'Set the variant of the tabs.',
control: 'select',
options: ['global', 'contained'],
defaultValue: {
summary: 'global',
},
},
orientation: {
description: 'Set the orientation of the tabs.',
control: 'select',
options: ['horizontal', 'vertical'],
defaultValue: {
summary: 'horizontal',
},
},
slot: {
description: 'The default slot is used to pass `pie-tab-panel` elements. You must provide at least one `pie-tab-panel` element for the tabs to be visible.',
control: 'text',
},
},
args: defaultArgs,
parameters: {
design: {
type: 'figma',
url: '',
url: 'https://www.figma.com/design/pPSC73rPin4csb8DiK1CRr/branch/Mn7rERbBnNmaO2UAHT8qDz/%E2%9C%A8--Core--Web-Components--PIE-3-?node-id=12502-11771',
},
},
};

export default tabsStoryMeta;

// TODO: remove the eslint-disable rule when props are added
// eslint-disable-next-line no-empty-pattern
const Template = ({}: TabsProps) => html`
<pie-tabs></pie-tabs>
const Template = ({ slot, variant, orientation }: TabsProps) => html`
<pie-tabs
variant=${ifDefined(variant)}
orientation=${ifDefined(orientation)}
data-test-id="pie-tabs"
>
${sanitizeAndRenderHTML(slot, { ALLOWED_TAGS: ['pie-tab-panel'] })}
</pie-tabs>
`;

export const Default = createStory<TabsProps>(Template, defaultArgs)();

const sharedProperties = {
orientation: ['horizontal', 'vertical'],
variant: ['global', 'contained'],
slot: [slot],
};

export const DefaultPropVariations = createVariantStory<TabsProps>(Template, sharedProperties);

28 changes: 25 additions & 3 deletions packages/components/pie-tabs/src/defs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
// TODO - please remove the eslint disable comment below when you add props to this interface
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TabsProps {}
import { type ComponentDefaultProps } from '@justeattakeaway/pie-webc-core';

export const variants = ['global', 'contained'] as const;
export const orientations = ['horizontal', 'vertical'] as const;

export interface TabsProps {
/**
* Optional variant for styling the tabs component.
* Default is 'global'.
*/
variant?: typeof variants[number];
/**
* Optional property to set the orientation of the tabs.
* Default is 'horizontal'.
*/
orientation?: typeof orientations[number];
}

export type DefaultProps = ComponentDefaultProps<TabsProps>;

export const defaultProps: DefaultProps = {
variant: 'global',
orientation: 'horizontal',
};

116 changes: 109 additions & 7 deletions packages/components/pie-tabs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,124 @@
import { html, unsafeCSS } from 'lit';
import { PieElement } from '@justeattakeaway/pie-webc-core/src/internals/PieElement';
import { RtlMixin, safeCustomElement } from '@justeattakeaway/pie-webc-core';
import { safeCustomElement, validPropertyValues } from '@justeattakeaway/pie-webc-core';
import { classMap } from 'lit/directives/class-map.js';
import { property, queryAssignedElements } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';

import styles from './tabs.scss?inline';
import { type TabsProps } from './defs';
import {
type TabsProps,
variants,
defaultProps,
orientations,
} from './defs';
import { type TabPanelProps } from './pie-tab-panel/defs';

const componentSelector = 'pie-tabs';

// Valid values available to consumers
export * from './defs';

const componentSelector = 'pie-tabs';

/**
* @tagname pie-tabs
*/
@safeCustomElement('pie-tabs')
export class PieTabs extends RtlMixin(PieElement) implements TabsProps {
@safeCustomElement(componentSelector)
export class PieTabs extends PieElement implements TabsProps {
@property({ type: String })
@validPropertyValues(componentSelector, variants, defaultProps.variant)
public variant = defaultProps.variant;

@property({ type: String })
@validPropertyValues(componentSelector, orientations, defaultProps.orientation)
public orientation = defaultProps.orientation;

@queryAssignedElements() _pieTabPanelSlots!: Array<HTMLElement & TabPanelProps>;

private _selectedTab = 0;

firstUpdated (): void {
this.requestUpdate();
}

updated (): void {
this.updateSelectedPanel();
}

/**
* Handles the click event on a tab.
* This method updates the selected tab index and updates the displayed panel accordingly.
*
* @param index The index of the tab that was clicked.
*
* @private
*/
private handleTabClick (index: number) {
this._selectedTab = index;
this.updateSelectedPanel();
this.requestUpdate();
}

/**
* Updates the selected state of each tab panel based on the currently selected tab index.
* This method iterates through all tab panels and sets the `selected` property accordingly.
*
* @private
*/
private updateSelectedPanel () {
this._pieTabPanelSlots.forEach((panel, index) => {
panel.selected = index === this._selectedTab;
});
}

render () {
return html`<h1 data-test-id="pie-tabs">Hello world!</h1>`;
const classes = {
'c-tabs': true,
'c-tabs-variant--contained': this.variant === 'contained',
[`c-tabs-orientation--${this.orientation}`]: true,
};

return html`
<div
data-test-id="pie-tabs"
class="${classMap(classes)}"
>
${this._pieTabPanelSlots && this._pieTabPanelSlots.length > 0 && (html`
<nav class="c-tabs-navigation">
<ul role="tablist">
${
/* eslint-disable indent */
repeat(
this._pieTabPanelSlots,
(element, index) => html`
<li
@click=${() => {
if (element.disabled) {
return;
}
this.handleTabClick(index);
}}
role="tab"
tabindex="${index}"
class="${classMap({
selected: this._selectedTab === index,
[`c-tabs-navigation-item--${this.orientation}`]: true,
[`c-tabs-navigation-item-variant--${this.variant}`]: true,
disabled: !!element.disabled,
})}"
>
<span>${element.title}</span>
</li>
`,
/* eslint-enable indent */
)}
</ul>
</nav>
`)}
<div class="c-tabs-panels">
<slot></slot>
</div>
</div>
`;
}

// Renders a `CSSResult` generated from SCSS by Vite
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type React from 'react';

export type ReactBaseType = React.HTMLAttributes<HTMLLIElement>
17 changes: 17 additions & 0 deletions packages/components/pie-tabs/src/pie-tab-panel/defs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface TabPanelProps {
/**
* Tab's title.
* This is used to display the title of the tab in the tab list.
*/
title: string;
/**
* Optional property to indicate if the tab panel is selected.
* If true, the tab panel will be displayed as selected.
*/
selected?: boolean;
/**
* Optional property to indicate if the tab panel is disabled.
* If true, the tab panel will be displayed as disabled and not selectable.
*/
disabled?: boolean;
}
Loading
Loading