Skip to content

Commit

Permalink
feat(web-components): add tablist (#32098)
Browse files Browse the repository at this point in the history
  • Loading branch information
davatron5000 committed Aug 6, 2024
1 parent c85fe49 commit ec6b5c7
Show file tree
Hide file tree
Showing 17 changed files with 1,546 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add Tablist component",
"packageName": "@fluentui/web-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
76 changes: 76 additions & 0 deletions packages/web-components/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,30 @@ export class BaseSpinner extends FASTElement {
elementInternals: ElementInternals;
}

// @public
export class BaseTablist extends FASTElement {
activeid: string;
// @internal (undocumented)
protected activeidChanged(oldValue: string, newValue: string): void;
activetab: HTMLElement;
adjust(adjustment: number): void;
// @internal (undocumented)
connectedCallback(): void;
disabled: boolean;
// @internal
protected disabledChanged(prev: boolean, next: boolean): void;
// @internal
elementInternals: ElementInternals;
orientation: TablistOrientation;
// @internal (undocumented)
protected orientationChanged(prev: TablistOrientation, next: TablistOrientation): void;
protected setTabs(): void;
// @internal (undocumented)
tabs: HTMLElement[];
// @internal (undocumented)
protected tabsChanged(): void;
}

// @public
export class BaseTextInput extends FASTElement {
autocomplete?: string;
Expand Down Expand Up @@ -3320,6 +3344,58 @@ export interface Tab extends StartEnd {
// @public (undocumented)
export const TabDefinition: FASTElementDefinition<typeof Tab>;

// @public
export class Tablist extends BaseTablist {
activeidChanged(oldValue: string, newValue: string): void;
appearance?: TablistAppearance;
// @internal (undocumented)
protected appearanceChanged(prev: TablistAppearance, next: TablistAppearance): void;
size?: TablistSize;
// @internal (undocumented)
protected sizeChanged(prev: TablistSize, next: TablistSize): void;
tabsChanged(): void;
}

// @public
export const TablistAppearance: {
readonly subtle: "subtle";
readonly transparent: "transparent";
};

// @public
export type TablistAppearance = ValuesOf<typeof TablistAppearance>;

// @public (undocumented)
export const TablistDefinition: FASTElementDefinition<typeof Tablist>;

// @public
export const TablistOrientation: {
readonly horizontal: "horizontal"; /**
* The appearance of the component
* @public
*/
readonly vertical: "vertical";
};

// @public
export type TablistOrientation = ValuesOf<typeof TablistOrientation>;

// @public
export const TablistSize: {
readonly small: "small";
readonly medium: "medium";
readonly large: "large";
};

// @public
export type TablistSize = ValuesOf<typeof TablistSize>;

// @public (undocumented)
export const TablistStyles: ElementStyles;

// @public (undocumented)
export const TablistTemplate: ViewTemplate<Tablist, any>;

// @public
export type TabOptions = StartEndOptions<Tab>;

Expand Down
1 change: 1 addition & 0 deletions packages/web-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
"./dist/esm/switch/define.js",
"./dist/esm/tab/define.js",
"./dist/esm/tabs/define.js",
"./dist/esm/tablist/define.js",
"./dist/esm/tab-panel/define.js",
"./dist/esm/text/define.js",
"./dist/esm/text-input/define.js",
Expand Down
1 change: 1 addition & 0 deletions packages/web-components/src/index-rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import './switch/define.js';
import './tab-panel/define.js';
import './tab/define.js';
import './tabs/define.js';
import './tablist/define.js';
import './text-input/define.js';
import './text/define.js';
import './toggle-button/define.js';
10 changes: 10 additions & 0 deletions packages/web-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,16 @@ export {
TabsStyles,
TabsTemplate,
} from './tabs/index.js';
export {
BaseTablist,
Tablist,
TablistAppearance,
TablistDefinition,
TablistOrientation,
TablistSize,
TablistStyles,
TablistTemplate,
} from './tablist/index.js';
export type { TabsOptions } from './tabs/index.js';
export {
BaseTextInput,
Expand Down
4 changes: 4 additions & 0 deletions packages/web-components/src/tablist/define.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { definition } from './tablist.definition.js';

definition.define(FluentDesignSystem.registry);
5 changes: 5 additions & 0 deletions packages/web-components/src/tablist/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { definition as TablistDefinition } from './tablist.definition.js';
export { BaseTablist, Tablist } from './tablist.js';
export { TablistAppearance, TablistOrientation, TablistSize } from './tablist.options.js';
export { styles as TablistStyles } from './tablist.styles.js';
export { template as TablistTemplate } from './tablist.template.js';
100 changes: 100 additions & 0 deletions packages/web-components/src/tablist/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Tablist

Tablists allow for navigation between two or more content views and relies on text headers to articulate the different sections of content. Tablist wraps a set of Tab components and manages the roving tabindex (aka "focusgroup") around the elements.

## Design Spec

[Link to Design Spec in Figma](https://www.figma.com/file/dK5AnDvvnSTWV9lduQWeDk/TabList?node-id=3942%3A9316&t=we0hQaRaKSJc6IeM-0)

## Engineering Spec

### Inputs

| attribute | type | default | description |
| ----------- | ------------------------------ | ------------- | ----------------------------------------------------------------------- |
| activeid | string | - | sets the selected tab |
| appearance | "subtle" \| "transparent | "transparent" | - |
| disabled | boolean | - | blocks control and all tab children from all keyboard and mouse events. |
| size | "small" \| "medium" \| "large" | "medium" | |
| orientation | "vertical" \| "horizontal" | "horizontal" | sets the orientation of the tab list to vertical display |

### Outputs

- [activeid: unknown] - the selected value of the currently selected tab

### Events

- change: html event handler - event fires on keyboard or mouse click

### Slots

- default - slot for the tab controls

## Accessibility

- [x] Tabs WCAG's patterns: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
- recommended that tabs activate on focus
- content of table is preloaded
- when tab list is aria-orientation vertical: `down arrow` performs as right arrow and `up arrow` performs as left arrow
- Horizontal tab list does not listen for `down arrow` or `up arrow`
- when tabpanel does not contain any focusable elements or the first element is not focusable the tab panel should set `tabindex="0"`
- [x] Are there any accessibility elements unique to this component? yes, many see link above.
- [x] List ARIA attributes: `role, aria-labelledby, aria-label, aria-controls, aria-selected, aria-haspopup, aria-orientation`
- [x] Does the component support 400% zoom?

## Differences from Fluent UI to FAST

The Fluent/FAST web component differs from the Fluent React Control as follows:

| difference | Tabs - Fluent Web Component | TabList - Fluent React Component |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| active indicator control / id control selection | managed by control | managed by user with application state |
| keyboard and focus selection | selects active tab on arrow key focus change | reselects tab on space bar or enter after arrow refocus |
| icon slotting | favors composition (dev chooses how to slot which icon) | favors automation (dev supplies icon name and control handles the rendering of icon) |
| icon slotting filled / unfilled icons | favors composition over automated handling. requires dev to add interactivity to render filled or unfilled icon | favors automated handling of icons and provides filled and unfilled iconography on selection |
| tab-panels | requires tab panel control to set content on tab selection | does not require or include a tab panel control / template |
| reserve-selected-tab-space | has reserve selected tab space defaulting to true and gives user the option to set to false | removes attribute |

[Link to FAST Web Component API](https://www.fast.design/docs/components/tabs/#class-tab)

| fluent api name | fast api Equivalent |
| --------------- | ------------------- |
| vertical | orientation |
| selected-value | activeid |
| value | id |

## Implementation - Sample Code

### Default

By default Tablists are arranged horizontally. The developer sets `activeid` Tablist attribute. The Component handles the logic of what is shown and hidden when user clicks on the tabs. For switching to work correctly the tab list requires that the indexing of the tabs and tab-panels be organized to correspond to their matching items - the order of the tabs must match the order of the tab panels:

```html
<fluent-tablist>
<fluent-tab>One / Left</fluent-tab>
<fluent-tab>Two / Middle</fluent-tab>
<fluent-tab>Three / Right</fluent-tab>
</fluent-tablist>
```

### Controlled

If the developer wants to control the selected tab, tab values can be provided.

```html
<fluent-tablist activeid="tab-one">
<fluent-tab id="tab-one">One / Left</fluent-tab>
<fluent-tab id="tab-two">Two / Middle</fluent-tab>
<fluent-tab id="tab-three">Three / Right</fluent-tab>
</fluent-tablist>
```

### Vertical

```html
<fluent-tablist orientation="vertical">
<fluent-tab>One / Left</fluent-tab>
<fluent-tab>Two / Middle</fluent-tab>
<fluent-tab>Three / Right</fluent-tab>
</fluent-tablist>
```
26 changes: 26 additions & 0 deletions packages/web-components/src/tablist/tablist.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { definition as tabDefinition } from '../tab/tab.definition.js';
import { definition as tablistDefinition } from './tablist.definition.js';

tabDefinition.define(FluentDesignSystem.registry);
tablistDefinition.define(FluentDesignSystem.registry);

const itemRenderer = () => {
const tablist = document.createElement('fluent-tablist');
const tab = document.createElement('fluent-tab');
const tab2 = document.createElement('fluent-tab');
const tab3 = document.createElement('fluent-tab');

tab.appendChild(document.createTextNode('Tab 1'));
tab2.appendChild(document.createTextNode('Tab 2'));
tab3.appendChild(document.createTextNode('Tab 3'));

tablist.appendChild(tab);
tablist.appendChild(tab2);
tablist.appendChild(tab3);

return tablist;
};

export default itemRenderer;
export { tests } from '../utils/benchmark-wrapper.js';
15 changes: 15 additions & 0 deletions packages/web-components/src/tablist/tablist.definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FluentDesignSystem } from '../fluent-design-system.js';
import { Tablist } from './tablist.js';
import { template } from './tablist.template.js';
import { styles } from './tablist.styles.js';

/**
* @public
* @remarks
* HTML Element: \<fluent-tablist\>
*/
export const definition = Tablist.compose({
name: `${FluentDesignSystem.prefix}-tablist`,
template,
styles,
});
45 changes: 45 additions & 0 deletions packages/web-components/src/tablist/tablist.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Orientation } from '@microsoft/fast-web-utilities';
import type { ValuesOf } from '../utils/index.js';

/**
* The appearance of the component
* @public
*/
export const TablistAppearance = {
subtle: 'subtle',
transparent: 'transparent',
} as const;

/**
* The types for the Tablist appearance
* @public
*/
export type TablistAppearance = ValuesOf<typeof TablistAppearance>;

/**
* The size of the component
* @public
*/
export const TablistSize = {
small: 'small',
medium: 'medium',
large: 'large',
} as const;

/**
* The types for the Tablist size
* @public
*/
export type TablistSize = ValuesOf<typeof TablistSize>;

/**
* The orientation of the component
* @public
*/
export const TablistOrientation = Orientation;

/**
* The types for the Tablist orientation
* @public
*/
export type TablistOrientation = ValuesOf<typeof TablistOrientation>;
Loading

0 comments on commit ec6b5c7

Please sign in to comment.