Skip to content

Commit 9ea92fb

Browse files
committed
Add <l2-tab-bar /> to display a list of links as tabs.
- Active is externally controlled by the URL - Supports drag and drop features - Adds entry and exit animation for indicator, as url may match no tabs
1 parent 8b46e04 commit 9ea92fb

File tree

10 files changed

+791
-0
lines changed

10 files changed

+791
-0
lines changed

.changeset/smart-cycles-return.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@kurrent-ui/layout': minor
3+
---
4+
5+
New Component: `<l2-tab-bar />`
6+
Displays a set of links as tabs, matching the style of `<c2-tabs />`

packages/layout/src/components.d.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { PanelMode } from "./components/panel";
1313
import { LoadingBarStatus } from "./components/loading-bar/types";
1414
import { NavNode, NavTree } from "./components/nav/types";
1515
import { ClosedMode, PanelDetailsListener, PanelMode as PanelMode1, TargetableArea, TargetableEdge } from "./components/panel/types";
16+
import { RouteTab } from "./components/tab-bar/types";
1617
export { Crumb } from "./components/breadcrumb/types";
1718
export { DisplayErrorVariant } from "./components/display-error/types";
1819
export { EmptyStateLayout } from "./components/empty-state/types";
@@ -21,6 +22,7 @@ export { PanelMode } from "./components/panel";
2122
export { LoadingBarStatus } from "./components/loading-bar/types";
2223
export { NavNode, NavTree } from "./components/nav/types";
2324
export { ClosedMode, PanelDetailsListener, PanelMode as PanelMode1, TargetableArea, TargetableEdge } from "./components/panel/types";
25+
export { RouteTab } from "./components/tab-bar/types";
2426
export namespace Components {
2527
/**
2628
* A list of breadcrumbs to the current page
@@ -397,6 +399,23 @@ export namespace Components {
397399
*/
398400
"start"?: TargetableEdge;
399401
}
402+
/**
403+
* A navigating tab bar.
404+
*/
405+
interface L2TabBar {
406+
/**
407+
* Icon to be rendered between each tab.
408+
*/
409+
"interTabIcon"?: IconDescription;
410+
/**
411+
* thu size of the icon to be rendered between each tab.
412+
*/
413+
"interTabIconSize": number;
414+
/**
415+
* A list of tabs.
416+
*/
417+
"tabs": RouteTab[];
418+
}
400419
/**
401420
* A theme picker dropdown for the header
402421
*/
@@ -418,6 +437,10 @@ export interface L2PanelCustomEvent<T> extends CustomEvent<T> {
418437
detail: T;
419438
target: HTMLL2PanelElement;
420439
}
440+
export interface L2TabBarCustomEvent<T> extends CustomEvent<T> {
441+
detail: T;
442+
target: HTMLL2TabBarElement;
443+
}
421444
declare global {
422445
/**
423446
* A list of breadcrumbs to the current page
@@ -631,6 +654,26 @@ declare global {
631654
prototype: HTMLL2SizedPanelElement;
632655
new (): HTMLL2SizedPanelElement;
633656
};
657+
interface HTMLL2TabBarElementEventMap {
658+
"tabChange": string;
659+
}
660+
/**
661+
* A navigating tab bar.
662+
*/
663+
interface HTMLL2TabBarElement extends Components.L2TabBar, HTMLStencilElement {
664+
addEventListener<K extends keyof HTMLL2TabBarElementEventMap>(type: K, listener: (this: HTMLL2TabBarElement, ev: L2TabBarCustomEvent<HTMLL2TabBarElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
665+
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
666+
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
667+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
668+
removeEventListener<K extends keyof HTMLL2TabBarElementEventMap>(type: K, listener: (this: HTMLL2TabBarElement, ev: L2TabBarCustomEvent<HTMLL2TabBarElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
669+
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
670+
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
671+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
672+
}
673+
var HTMLL2TabBarElement: {
674+
prototype: HTMLL2TabBarElement;
675+
new (): HTMLL2TabBarElement;
676+
};
634677
/**
635678
* A theme picker dropdown for the header
636679
*/
@@ -678,6 +721,7 @@ declare global {
678721
"l2-sidebar": HTMLL2SidebarElement;
679722
"l2-sidebar-dropdown": HTMLL2SidebarDropdownElement;
680723
"l2-sized-panel": HTMLL2SizedPanelElement;
724+
"l2-tab-bar": HTMLL2TabBarElement;
681725
"l2-theme-dropdown": HTMLL2ThemeDropdownElement;
682726
"l2-theme-picker": HTMLL2ThemePickerElement;
683727
"l2-toolbar": HTMLL2ToolbarElement;
@@ -1053,6 +1097,27 @@ declare namespace LocalJSX {
10531097
*/
10541098
"start"?: TargetableEdge;
10551099
}
1100+
/**
1101+
* A navigating tab bar.
1102+
*/
1103+
interface L2TabBar {
1104+
/**
1105+
* Icon to be rendered between each tab.
1106+
*/
1107+
"interTabIcon"?: IconDescription;
1108+
/**
1109+
* thu size of the icon to be rendered between each tab.
1110+
*/
1111+
"interTabIconSize"?: number;
1112+
/**
1113+
* Triggered when the active tab is changed. `detail` is the newly active tab.
1114+
*/
1115+
"onTabChange"?: (event: L2TabBarCustomEvent<string>) => void;
1116+
/**
1117+
* A list of tabs.
1118+
*/
1119+
"tabs": RouteTab[];
1120+
}
10561121
/**
10571122
* A theme picker dropdown for the header
10581123
*/
@@ -1088,6 +1153,7 @@ declare namespace LocalJSX {
10881153
"l2-sidebar": L2Sidebar;
10891154
"l2-sidebar-dropdown": L2SidebarDropdown;
10901155
"l2-sized-panel": L2SizedPanel;
1156+
"l2-tab-bar": L2TabBar;
10911157
"l2-theme-dropdown": L2ThemeDropdown;
10921158
"l2-theme-picker": L2ThemePicker;
10931159
"l2-toolbar": L2Toolbar;
@@ -1177,6 +1243,10 @@ declare module "@stencil/core" {
11771243
* Automatically sets the relevant layout var based on it's size.
11781244
*/
11791245
"l2-sized-panel": LocalJSX.L2SizedPanel & JSXBase.HTMLAttributes<HTMLL2SizedPanelElement>;
1246+
/**
1247+
* A navigating tab bar.
1248+
*/
1249+
"l2-tab-bar": LocalJSX.L2TabBar & JSXBase.HTMLAttributes<HTMLL2TabBarElement>;
11801250
/**
11811251
* A theme picker dropdown for the header
11821252
*/
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Component, Host, h } from '@stencil/core';
2+
import type { RouteTab } from '../types';
3+
import { Link, Route, Switch, router } from '@kurrent-ui/router';
4+
5+
/** Tab bar */
6+
@Component({
7+
tag: 'tab-bar-demo',
8+
styleUrl: './tabs-demo.css',
9+
shadow: true,
10+
})
11+
export class Demo {
12+
componentWillLoad() {
13+
router.init({
14+
root: '/tab-bar-demo/',
15+
});
16+
}
17+
18+
render() {
19+
return (
20+
<Host>
21+
<l2-tab-bar tabs={this.tabs} />
22+
<Switch>
23+
<Route
24+
url={'/tab-1'}
25+
routeRender={() => <p>{'I am in tab 1'}</p>}
26+
/>
27+
<Route
28+
url={'/tab-2'}
29+
routeRender={() => <p>{'Welcome to tab 2!'}</p>}
30+
/>
31+
<Route
32+
url={'/tab-three'}
33+
routeRender={() => (
34+
<p>{'Hello 👋. You have reached tab 3.'}</p>
35+
)}
36+
/>
37+
<Route
38+
url={'/tab-4'}
39+
routeRender={() => (
40+
<div>
41+
<p>{'Tab 4 now'}</p>
42+
<br />
43+
<Link url={'/tab-1'}>{'go to one'}</Link>
44+
<br />
45+
<Link url={'/tab-20'}>{'go nowhere'}</Link>
46+
</div>
47+
)}
48+
/>
49+
</Switch>
50+
</Host>
51+
);
52+
}
53+
54+
private tabs: RouteTab[] = [
55+
{
56+
id: 'tab-1',
57+
title: 'One',
58+
},
59+
{
60+
id: 'tab-2',
61+
title: 'Two',
62+
badge: () => true,
63+
},
64+
{
65+
id: 'tab-3',
66+
title: 'Three',
67+
url: '/tab-three',
68+
},
69+
{
70+
id: 'tab-4',
71+
title: 'Four',
72+
},
73+
];
74+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:host {
2+
display: block;
3+
grid-area: body;
4+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# c2-tab-bar
2+
3+
4+
5+
<!-- Auto Generated Below -->
6+
7+
8+
## Overview
9+
10+
A navigating tab bar.
11+
12+
## Usage
13+
14+
### Example
15+
16+
```tsx
17+
import type { RouteTab } from '@kurrent-ui/layout';
18+
import { Link, Route, Switch } from '@kurrent-ui/router';
19+
20+
const tabs: RouteTab[] = [
21+
{
22+
id: 'tab-1',
23+
title: 'One',
24+
},
25+
{
26+
id: 'tab-2',
27+
title: 'Two',
28+
badge: () => true,
29+
},
30+
{
31+
id: 'tab-3',
32+
title: 'Three',
33+
url: '/tab-three',
34+
},
35+
{
36+
id: 'tab-4',
37+
title: 'Four',
38+
},
39+
];
40+
41+
export default () => (
42+
<>
43+
<l2-tab-bar tabs={tabs} />
44+
<Switch>
45+
<Route
46+
url={'/tab-1'}
47+
routeRender={() => <p>{'I am in tab 1'}</p>}
48+
/>
49+
<Route
50+
url={'/tab-2'}
51+
routeRender={() => <p>{'Welcome to tab 2!'}</p>}
52+
/>
53+
<Route
54+
url={'/tab-three'}
55+
routeRender={() => <p>{'Hello 👋. You have reached tab 3.'}</p>}
56+
/>
57+
<Route
58+
url={'/tab-4'}
59+
routeRender={() => (
60+
<div>
61+
<p>{'Tab 4 now'}</p>
62+
<br />
63+
<Link url={'/tab-1'}>{'go to one'}</Link>
64+
<br />
65+
<Link url={'/tab-20'}>{'go nowhere'}</Link>
66+
</div>
67+
)}
68+
/>
69+
</Switch>
70+
</>
71+
);
72+
```
73+
74+
75+
76+
## Properties
77+
78+
| Property | Attribute | Description | Type | Default |
79+
| ------------------- | --------------------- | ----------------------------------------------------- | -------------------------------------------------------------------- | ----------- |
80+
| `interTabIcon` | `inter-tab-icon` | Icon to be rendered between each tab. | `[namespace: string \| symbol, name: string] \| string \| undefined` | `undefined` |
81+
| `interTabIconSize` | `inter-tab-icon-size` | thu size of the icon to be rendered between each tab. | `number` | `20` |
82+
| `tabs` _(required)_ | -- | A list of tabs. | `RouteTab[]` | `undefined` |
83+
84+
85+
## Events
86+
87+
| Event | Description | Type |
88+
| ----------- | --------------------------------------------------------------------------- | --------------------- |
89+
| `tabChange` | Triggered when the active tab is changed. `detail` is the newly active tab. | `CustomEvent<string>` |
90+
91+
92+
## Shadow Parts
93+
94+
| Part | Description |
95+
| ------------------ | --------------------------------- |
96+
| `"[tabName]"` | The tab. |
97+
| `"active"` | The active tab. |
98+
| `"indicator"` | The sliding indicatior bar. |
99+
| `"inter-tab-icon"` | Icon between tabs (if specified). |
100+
| `"tab"` | Tabs. |
101+
102+
103+
## CSS Custom Properties
104+
105+
| Name | Description |
106+
| ------------------------- | -------------------------------------------------------------------- |
107+
| `--active-color` | The active text color of the tab, and color of the active indicator; |
108+
| `--border-color` | The color of border surrounding the tab content; |
109+
| `--focus-color` | The focused text color of the tab; |
110+
| `--focus-indicator-color` | The color of the focus indicator; |
111+
| `--inactive-color` | The inactive text color of the tab; |
112+
113+
114+
## Dependencies
115+
116+
### Depends on
117+
118+
- c2-badge
119+
- c2-icon
120+
121+
### Graph
122+
```mermaid
123+
graph TD;
124+
l2-tab-bar --> c2-badge
125+
l2-tab-bar --> c2-icon
126+
c2-badge --> c2-counter
127+
style l2-tab-bar fill:#f9f,stroke:#333,stroke-width:4px
128+
```
129+
130+
----------------------------------------------
131+
132+

0 commit comments

Comments
 (0)