Skip to content

Commit

Permalink
refactor(tabs): simplify web component by removing need for cx-tab-co…
Browse files Browse the repository at this point in the history
…ntent component
  • Loading branch information
eTallang committed Dec 5, 2024
1 parent 407c901 commit 4f42b26
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 144 deletions.
2 changes: 1 addition & 1 deletion .storybook/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const cxTheme = create({
base: 'light',

brandUrl: 'https://designsystem.computas.com',
brandImage: 'https://computas.com/content/themes/computas/assets/images/logo.svg',
brandImage: 'https://designsystem.computas.com/computas-logo.svg',
brandTarget: '_self',

colorPrimary: '#003459',
Expand Down
5 changes: 5 additions & 0 deletions public/computas-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/components/form-field/form-field.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ label.cx-form-field {

.cx-form-field__error {
display: flex;
height: 1.4rem;
height: 1.5rem;
opacity: 1;
margin-top: var(--cx-spacing-1);

Expand Down Expand Up @@ -96,6 +96,6 @@ label.cx-form-field {
opacity: 0;
transition: height var(--rubber-band-ease), opacity ease, margin ease, display;
transition-behavior: allow-discrete;
transition-duration: 500ms;
transition-duration: 400ms;
display: none;
}
1 change: 1 addition & 0 deletions src/components/input/input.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const Invalid: StoryObj = {
<input aria-describedby="error-text" type="email" />
</div>
<div class="cx-form-field__error" aria-live="polite" id="error-text">
<cx-icon name="error-circle" size="6"></cx-icon>
Please provide a valid e-mail
</div>
</label>
Expand Down
62 changes: 51 additions & 11 deletions src/components/tabs/Overview.mdx
Original file line number Diff line number Diff line change
@@ -1,23 +1,63 @@
import { Canvas, Meta, Subtitle, Title } from "@storybook/blocks";
import { Canvas, Meta, Source, Subtitle, Title } from "@storybook/blocks";

import * as stories from "./tabs.stories";

<Meta of={stories} />

<Title />
<Subtitle>
Tabs are split into two different use cases; tabs used for routing, and tabs
used for displaying different content on a route.
Tabs are used to divide content into sections and let the user navigate between one section at a time.
Use tabs when the content is at the same level of the hierarchy and are related.
It should always be one tab selected by default.
</Subtitle>

### Regular tabs

<Canvas of={stories.RegularTabs} />

### Tab link

## Tab link
Tabs are most commonly used for top level navigation. The recommended way of doing this is by using routing,
since this leverages URL state. In most cases you'll therefore have links in your application, styled
as tabs. If you use Tanstack Router, this means using the `Link` component provided by Tanstack Router.
Simply put the `.cx-tab-link` class on there, and you're good to go. The example below uses the `a` element,
but it works just the same way.
<Canvas of={stories.TabLink} />

### Web component

## Tab button
In some rare occasions, you'll want to route programmatically. This may be if you for instance need to do some
other tasks before navigating. In these scenarios, it's recommended to use a `button` element with a click event.
As with the `a` element, simply use the `.cx-tab-link` class.
<Canvas of={stories.TabButton} />

## Web component
Sometimes, tabs are not used "top level", and are only used to shuffle between blocks of content on a part of the page.
In these scenarios we provide a web component/React component. This component handles all of the accessibility and
state for you, so you only need to provide the title for the tab, and the content that goes within.

To use the web component, import the appropriate version for your application:
<Source
code={`
// Web component
import '@computas/designsystem/tabs';
// React
import { CxTabGroup, CxTab } from '@computas/designsystem/tabs/react';
`}
language="typescript"
dark
/>

Then use it as follows:
<Canvas of={stories.WebComponent} />

### Active tab index
If you need to set the tab index programmatically, please use the `activeTabIndex` prop on the `cx-tab-group` element:

<Source
code={`
// The second tab will be active by default
// Web component
<cx-tab-group activeTabIndex="1">
// React
<CxTabGroup activeTabIndex="1">
`}
language="tsx"
dark
/>
1 change: 0 additions & 1 deletion src/components/tabs/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './tab';
export * from './tab-content';
export * from './tab-group';
8 changes: 1 addition & 7 deletions src/components/tabs/react.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createComponent } from '@lit/react';
import * as React from 'react';

import { Tab, TabContent, TabGroup } from './index';
import { Tab, TabGroup } from './index';

export const CxTabGroup = createComponent({
tagName: 'cx-tab-group',
Expand All @@ -14,9 +14,3 @@ export const CxTab = createComponent({
elementClass: Tab,
react: React,
});

export const CxTabContent = createComponent({
tagName: 'cx-tab-content',
elementClass: TabContent,
react: React,
});
49 changes: 0 additions & 49 deletions src/components/tabs/tab-content.ts

This file was deleted.

17 changes: 6 additions & 11 deletions src/components/tabs/tab-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { classMap } from 'lit/directives/class-map.js';
import { provide } from '@lit/context';
import a11yStyles from '../../global-css/a11y.css?inline';
import type { Tab } from './tab';
import type { TabContent } from './tab-content';
import { activeIndexContext } from './tab-context';
import tabStyles from './tab-link.css?inline';

Expand All @@ -25,7 +24,7 @@ export class TabGroup extends LitElement {
.content-container {
display: grid;
padding-block: var(--cx-spacing-2);
padding-top: var(--cx-spacing-4);
&::slotted(*) {
grid-area: 1 / 1;
Expand All @@ -43,19 +42,16 @@ export class TabGroup extends LitElement {
activeTabIndex = 0;

@state()
private tabHeaders: Tab[] = [];

private registerTabNames(e: Event) {
const slot = e.target as HTMLSlotElement;
this.tabHeaders = slot.assignedElements({ flatten: true }) as Tab[];
}
private tabHeaders: string[] = [];

private setTabContentIndexes(e: Event) {
const slot = e.target as HTMLSlotElement;
const tabContent = slot.assignedElements({ flatten: true }) as TabContent[];
const tabContent = slot.assignedElements({ flatten: true }) as Tab[];
tabContent.forEach((tabContent, index) => {
tabContent.index = index;
});

this.tabHeaders = tabContent.map((tab) => tab.header);
}

private setActiveTabIndex(newTabIndex: number) {
Expand All @@ -81,11 +77,10 @@ export class TabGroup extends LitElement {
aria-selected=${this.activeTabIndex === index}
@click=${() => this.setActiveTabIndex(index)}
?checked=${this.activeTabIndex === index} />
${tabHeader.headerContent}
${tabHeader}
</label>
`,
)}
<slot name="header" @slotchange=${this.registerTabNames}></slot>
</header>
<div
Expand Down
46 changes: 30 additions & 16 deletions src/components/tabs/tab.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
import { consume } from '@lit/context';
import { LitElement, css, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { activeIndexContext } from './tab-context';

@customElement('cx-tab')
export class Tab extends LitElement {
static styles = css`
:host {
display: none;
div {
transition: opacity 300ms ease, translate 500ms var(--ease-spring-3), visibility 300ms;
}
.tab-content-before {
visibility: hidden;
opacity: 0;
translate: -10px 0;
}
.tab-content-after {
visibility: hidden;
opacity: 0;
translate: 10px 0;
}
`;

/**
* @description Provides the tab header
*/
@property({ type: String, reflect: true })
forContent = '';
header = 'Tab title';

@state()
headerContent = '';
@consume({ context: activeIndexContext, subscribe: true })
activeTabIndex!: number;

private onSlotChange(e: Event) {
const slot = e.target as HTMLSlotElement;
this.headerContent = slot
.assignedNodes({ flatten: true })
.map((node) => node.textContent)
.join(', ');
}
@state()
index = -1;

render() {
return html`<slot @slotchange=${this.onSlotChange}></slot>`;
return html`
<div class=${classMap({
'tab-content-before': this.index < this.activeTabIndex,
'tab-content-after': this.index > this.activeTabIndex,
})}>
<slot></slot>
</div>
`;
}
}

Expand Down
Loading

0 comments on commit 4f42b26

Please sign in to comment.