mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-18 03:26:36 +00:00
feat(tabs): proof of concept for keyed tabs
This commit is contained in:
parent
e9016a5f8d
commit
43f06f2be3
8 changed files with 466 additions and 3 deletions
|
@ -1,6 +1,6 @@
|
|||
# Component Index
|
||||
|
||||
> 155 components exported from carbon-components-svelte@0.25.1.
|
||||
> 156 components exported from carbon-components-svelte@0.25.1.
|
||||
|
||||
## Components
|
||||
|
||||
|
@ -134,6 +134,7 @@
|
|||
- [`TableRow`](#tablerow)
|
||||
- [`Tabs`](#tabs)
|
||||
- [`TabsSkeleton`](#tabsskeleton)
|
||||
- [`TabsV2`](#tabsv2)
|
||||
- [`Tag`](#tag)
|
||||
- [`TagSkeleton`](#tagskeleton)
|
||||
- [`TextArea`](#textarea)
|
||||
|
@ -3582,6 +3583,44 @@ None.
|
|||
| mouseenter | forwarded | -- |
|
||||
| mouseleave | forwarded | -- |
|
||||
|
||||
## `TabsV2`
|
||||
|
||||
### Types
|
||||
|
||||
```ts
|
||||
export type TabsV2ItemId = number | string;
|
||||
|
||||
export interface TabsV2Item {
|
||||
id: TabsV2ItemId;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
| Prop name | Kind | Reactive | Type | Default value | Description |
|
||||
| :-------------- | :--------------- | :------- | :---------------------------------------- | -------------------------------- | ------------------------------------------- |
|
||||
| selectedId | <code>let</code> | Yes | <code>TabsV2ItemId</code> | -- | Specify the selected tab id |
|
||||
| selectedIndex | <code>let</code> | Yes | <code>number</code> | <code>0</code> | Specify the selected tab index |
|
||||
| items | <code>let</code> | No | <code>TabsV2Item[]</code> | <code>[]</code> | Provide the tab items |
|
||||
| type | <code>let</code> | No | <code>"default" | "container"</code> | <code>"default"</code> | Specify the type of tabs |
|
||||
| iconDescription | <code>let</code> | No | <code>string</code> | <code>"Show menu options"</code> | Specify the ARIA label for the chevron icon |
|
||||
| triggerHref | <code>let</code> | No | <code>string</code> | <code>"#"</code> | Specify the tab trigger href attribute |
|
||||
|
||||
### Slots
|
||||
|
||||
| Slot name | Default | Props | Fallback |
|
||||
| :-------- | :------ | :------------------------------------------------------------------- | :------------------------ |
|
||||
| -- | Yes | <code>{ id: TabsV2ItemId; index: number; item: TabsV2Item; } </code> | -- |
|
||||
| tab | No | -- | <code>{item.label}</code> |
|
||||
|
||||
### Events
|
||||
|
||||
| Event name | Type | Detail |
|
||||
| :--------- | :--------- | :---------------------------------------------------------------------------------------- |
|
||||
| change | dispatched | <code>{ selectedIndex: number; selectedId: TabsV2ItemId; currentItem: TabsV2Item }</code> |
|
||||
|
||||
## `Tag`
|
||||
|
||||
### Props
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"total": 155,
|
||||
"total": 156,
|
||||
"components": [
|
||||
{
|
||||
"moduleName": "SkeletonText",
|
||||
|
@ -7637,6 +7637,104 @@
|
|||
"typedefs": [],
|
||||
"rest_props": { "type": "Element", "name": "div" }
|
||||
},
|
||||
{
|
||||
"moduleName": "TabsV2",
|
||||
"filePath": "/src/Tabs/TabsV2.svelte",
|
||||
"props": [
|
||||
{
|
||||
"name": "items",
|
||||
"kind": "let",
|
||||
"description": "Provide the tab items",
|
||||
"type": "TabsV2Item[]",
|
||||
"value": "[]",
|
||||
"isFunction": false,
|
||||
"constant": false,
|
||||
"reactive": false
|
||||
},
|
||||
{
|
||||
"name": "selectedIndex",
|
||||
"kind": "let",
|
||||
"description": "Specify the selected tab index",
|
||||
"type": "number",
|
||||
"value": "0",
|
||||
"isFunction": false,
|
||||
"constant": false,
|
||||
"reactive": true
|
||||
},
|
||||
{
|
||||
"name": "selectedId",
|
||||
"kind": "let",
|
||||
"description": "Specify the selected tab id",
|
||||
"type": "TabsV2ItemId",
|
||||
"isFunction": false,
|
||||
"constant": false,
|
||||
"reactive": true
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"kind": "let",
|
||||
"description": "Specify the type of tabs",
|
||||
"type": "\"default\" | \"container\"",
|
||||
"value": "\"default\"",
|
||||
"isFunction": false,
|
||||
"constant": false,
|
||||
"reactive": false
|
||||
},
|
||||
{
|
||||
"name": "iconDescription",
|
||||
"kind": "let",
|
||||
"description": "Specify the ARIA label for the chevron icon",
|
||||
"type": "string",
|
||||
"value": "\"Show menu options\"",
|
||||
"isFunction": false,
|
||||
"constant": false,
|
||||
"reactive": false
|
||||
},
|
||||
{
|
||||
"name": "triggerHref",
|
||||
"kind": "let",
|
||||
"description": "Specify the tab trigger href attribute",
|
||||
"type": "string",
|
||||
"value": "\"#\"",
|
||||
"isFunction": false,
|
||||
"constant": false,
|
||||
"reactive": false
|
||||
}
|
||||
],
|
||||
"slots": [
|
||||
{
|
||||
"name": "__default__",
|
||||
"default": true,
|
||||
"slot_props": "{ id: TabsV2ItemId; index: number; item: TabsV2Item; }"
|
||||
},
|
||||
{
|
||||
"name": "tab",
|
||||
"default": false,
|
||||
"fallback": "{item.label}",
|
||||
"slot_props": "{}"
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"type": "dispatched",
|
||||
"name": "change",
|
||||
"detail": "{ selectedIndex: number; selectedId: TabsV2ItemId; currentItem: TabsV2Item }"
|
||||
}
|
||||
],
|
||||
"typedefs": [
|
||||
{
|
||||
"type": "number | string",
|
||||
"name": "TabsV2ItemId",
|
||||
"ts": "type TabsV2ItemId = number | string"
|
||||
},
|
||||
{
|
||||
"type": "{ id: TabsV2ItemId; label?: string; disabled?: boolean; }",
|
||||
"name": "TabsV2Item",
|
||||
"ts": "interface TabsV2Item { id: TabsV2ItemId; label?: string; disabled?: boolean; }"
|
||||
}
|
||||
],
|
||||
"rest_props": { "type": "Element", "name": "div" }
|
||||
},
|
||||
{
|
||||
"moduleName": "TagSkeleton",
|
||||
"filePath": "/src/Tag/TagSkeleton.svelte",
|
||||
|
|
86
docs/src/pages/framed/Tabs/TabsV2.svelte
Normal file
86
docs/src/pages/framed/Tabs/TabsV2.svelte
Normal file
|
@ -0,0 +1,86 @@
|
|||
<script>
|
||||
import { TabsV2, Button } from "carbon-components-svelte";
|
||||
import Add16 from "carbon-icons-svelte/lib/Add16";
|
||||
import { tick } from "svelte";
|
||||
|
||||
const initialItems = [
|
||||
{ id: "id" + 0, label: "Tab 1" },
|
||||
{ id: "id" + 1, label: "Tab 2", disabled: true },
|
||||
{ id: "id" + 3, label: "Tab 3" },
|
||||
];
|
||||
|
||||
const differentItems = [
|
||||
{ id: "id" + -1, label: "Diff Tab 0" },
|
||||
{ id: "id" + 0, label: "Diff Tab 1" },
|
||||
{ id: "id" + 1, label: "Diff Tab 2" },
|
||||
{ id: "id" + 3, label: "Diff Tab 3" },
|
||||
{ id: "id" + 4, label: "Diff Tab 4" },
|
||||
];
|
||||
|
||||
let selectedIndex;
|
||||
let selectedId;
|
||||
let items = initialItems;
|
||||
</script>
|
||||
|
||||
<div><strong>selectedIndex:</strong> {selectedIndex}</div>
|
||||
|
||||
<div><strong>selectedId:</strong> {selectedId}</div>
|
||||
|
||||
<Button
|
||||
on:click="{async () => {
|
||||
items = differentItems;
|
||||
tick().then(() => {
|
||||
selectedId = 'id4';
|
||||
});
|
||||
}}"
|
||||
>
|
||||
Update items
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
on:click="{() => {
|
||||
items = initialItems;
|
||||
}}"
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
on:click="{() => {
|
||||
selectedIndex = 1;
|
||||
}}"
|
||||
>
|
||||
Update selectedIndex
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
on:click="{() => {
|
||||
selectedId = 'id3';
|
||||
}}"
|
||||
>
|
||||
Update selectedId
|
||||
</Button>
|
||||
|
||||
<TabsV2
|
||||
bind:selectedId
|
||||
bind:selectedIndex
|
||||
items="{items}"
|
||||
let:item
|
||||
let:id
|
||||
let:index
|
||||
on:change="{(e) => {
|
||||
console.log('change', e.detail);
|
||||
}}"
|
||||
>
|
||||
<span slot="tab" style="{!item.disabled && 'color: blue'}">
|
||||
{#if index === 1}
|
||||
<Add16 />
|
||||
{/if}
|
||||
{item.label}
|
||||
</span>
|
||||
{#if selectedIndex === 0}Tab content {id} {index}{/if}
|
||||
{#if selectedIndex === 1}Tab content {id} {index}{/if}
|
||||
{#if selectedIndex === 2}Tab content {id} {index}{/if}
|
||||
{#if selectedIndex === 3}Tab content {id} {index}{/if}
|
||||
{#if selectedIndex === 4}Tab content {id} {index}{/if}
|
||||
</TabsV2>
|
178
src/Tabs/TabsV2.svelte
Normal file
178
src/Tabs/TabsV2.svelte
Normal file
|
@ -0,0 +1,178 @@
|
|||
<script>
|
||||
/**
|
||||
* @typedef {number | string} TabsV2ItemId
|
||||
* @typedef {{ id: TabsV2ItemId; label?: string; disabled?: boolean; }} TabsV2Item
|
||||
* @event {{ selectedIndex: number; selectedId: TabsV2ItemId; currentItem: TabsV2Item }} change
|
||||
* @slot {{ id: TabsV2ItemId; index: number; item: TabsV2Item; }}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provide the tab items
|
||||
* @type {TabsV2Item[]}
|
||||
*/
|
||||
export let items = [];
|
||||
|
||||
/** Specify the selected tab index */
|
||||
export let selectedIndex = 0;
|
||||
|
||||
/**
|
||||
* Specify the selected tab id
|
||||
* @type {TabsV2ItemId}
|
||||
*/
|
||||
export let selectedId = undefined;
|
||||
|
||||
/**
|
||||
* Specify the type of tabs
|
||||
* @type {"default" | "container"}
|
||||
*/
|
||||
export let type = "default";
|
||||
|
||||
/**
|
||||
* Specify the ARIA label for the chevron icon
|
||||
* @type {string}
|
||||
*/
|
||||
export let iconDescription = "Show menu options";
|
||||
|
||||
/** Specify the tab trigger href attribute */
|
||||
export let triggerHref = "#";
|
||||
|
||||
import { createEventDispatcher, afterUpdate } from "svelte";
|
||||
import ChevronDownGlyph from "carbon-icons-svelte/lib/ChevronDownGlyph/ChevronDownGlyph.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let dropdownHidden = true;
|
||||
let prevSelectedIndex = -1;
|
||||
|
||||
$: itemIds = items.map((item) => item.id);
|
||||
$: if (items[selectedIndex] === undefined) {
|
||||
// if the items array shrinks, `selectedIndex` could be greater than the number of items
|
||||
selectedIndex = items.length - 1;
|
||||
}
|
||||
$: currentItem = items[selectedIndex];
|
||||
$: selectedId = currentItem.id;
|
||||
|
||||
afterUpdate(() => {
|
||||
if (currentItem.id !== selectedId) {
|
||||
selectedIndex = itemIds.indexOf(selectedId);
|
||||
}
|
||||
|
||||
if (prevSelectedIndex !== selectedIndex) {
|
||||
// only dispatch the "change" event when the current item changes
|
||||
dispatch("change", { selectedIndex, selectedId, currentItem });
|
||||
prevSelectedIndex = selectedIndex;
|
||||
}
|
||||
});
|
||||
|
||||
function changeIndex(direction) {
|
||||
let index = selectedIndex + direction;
|
||||
|
||||
if (index < 0) {
|
||||
index = items.length - 1;
|
||||
} else if (index >= items.length) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
let disabled = items[index].disabled;
|
||||
|
||||
while (disabled) {
|
||||
index = index + direction;
|
||||
|
||||
if (index < 0) {
|
||||
index = items.length - 1;
|
||||
} else if (index >= items.length) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
disabled = items[index].disabled;
|
||||
}
|
||||
|
||||
selectedIndex = index;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
role="navigation"
|
||||
class:bx--tabs="{true}"
|
||||
class:bx--tabs--container="{type === 'container'}"
|
||||
{...$$restProps}
|
||||
>
|
||||
<div
|
||||
role="listbox"
|
||||
tabindex="0"
|
||||
class:bx--tabs-trigger="{true}"
|
||||
aria-label="{$$props['aria-label'] || 'listbox'}"
|
||||
on:click="{() => {
|
||||
dropdownHidden = !dropdownHidden;
|
||||
}}"
|
||||
on:keydown="{() => {
|
||||
dropdownHidden = !dropdownHidden;
|
||||
}}"
|
||||
>
|
||||
<a
|
||||
tabindex="-1"
|
||||
class:bx--tabs-trigger-text="{true}"
|
||||
href="{triggerHref}"
|
||||
on:click="{() => {
|
||||
dropdownHidden = !dropdownHidden;
|
||||
}}"
|
||||
>
|
||||
{#if currentItem}{currentItem.label}{/if}
|
||||
</a>
|
||||
<ChevronDownGlyph aria-hidden="true" title="{iconDescription}" />
|
||||
</div>
|
||||
<ul
|
||||
role="tablist"
|
||||
class:bx--tabs__nav="{true}"
|
||||
class:bx--tabs__nav--hidden="{dropdownHidden}"
|
||||
>
|
||||
{#each items as item, i (item.id)}
|
||||
<li
|
||||
tabindex="-1"
|
||||
role="presentation"
|
||||
class:bx--tabs__nav-item="{true}"
|
||||
class:bx--tabs__nav-item--disabled="{item.disabled}"
|
||||
class:bx--tabs__nav-item--selected="{item.id === currentItem.id}"
|
||||
{...$$restProps}
|
||||
on:click|preventDefault="{() => {
|
||||
if (!item.disabled) selectedIndex = i;
|
||||
dropdownHidden = true;
|
||||
}}"
|
||||
on:keydown="{({ key }) => {
|
||||
if (item.disabled || !dropdownHidden) return;
|
||||
console.log(key);
|
||||
if (key === 'ArrowRight') {
|
||||
changeIndex(1);
|
||||
} else if (key === 'ArrowLeft') {
|
||||
changeIndex(-1);
|
||||
}
|
||||
}}"
|
||||
>
|
||||
<a
|
||||
role="tab"
|
||||
tabindex="{item.disabled ? '-1' : item.tabindex || '0'}"
|
||||
aria-selected="{item.id === currentItem.id}"
|
||||
aria-disabled="{item.disabled}"
|
||||
id="{item.id}"
|
||||
href="{item.href || '#'}"
|
||||
class:bx--tabs__nav-link="{true}"
|
||||
>
|
||||
<slot name="tab">{item.label}</slot>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{#each items as item, i (item.id)}
|
||||
<div
|
||||
role="tabpanel"
|
||||
aria-labelledby="{item.id}"
|
||||
aria-hidden="{item.id !== currentItem.id}"
|
||||
hidden="{item.id === currentItem.id ? undefined : 'true'}"
|
||||
id="tabpanel-{item.id}"
|
||||
class:bx--tab-content="{true}"
|
||||
>
|
||||
<slot id="{item.id}" index="{i}" item="{item}" />
|
||||
</div>
|
||||
{/each}
|
|
@ -2,3 +2,4 @@ export { default as Tabs } from "./Tabs.svelte";
|
|||
export { default as Tab } from "./Tab.svelte";
|
||||
export { default as TabContent } from "./TabContent.svelte";
|
||||
export { default as TabsSkeleton } from "./TabsSkeleton.svelte";
|
||||
export { default as TabsV2 } from "./TabsV2.svelte";
|
||||
|
|
|
@ -96,7 +96,7 @@ export {
|
|||
StructuredListRow,
|
||||
StructuredListInput,
|
||||
} from "./StructuredList";
|
||||
export { Tabs, Tab, TabContent, TabsSkeleton } from "./Tabs";
|
||||
export { Tabs, Tab, TabContent, TabsSkeleton, TabsV2 } from "./Tabs";
|
||||
export { Tag, TagSkeleton } from "./Tag";
|
||||
export { TextArea, TextAreaSkeleton } from "./TextArea";
|
||||
export { TextInput, TextInputSkeleton, PasswordInput } from "./TextInput";
|
||||
|
|
60
types/Tabs/TabsV2.d.ts
vendored
Normal file
60
types/Tabs/TabsV2.d.ts
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
/// <reference types="svelte" />
|
||||
|
||||
export type TabsV2ItemId = number | string;
|
||||
|
||||
export interface TabsV2Item {
|
||||
id: TabsV2ItemId;
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface TabsV2Props extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["div"]> {
|
||||
/**
|
||||
* Provide the tab items
|
||||
* @default []
|
||||
*/
|
||||
items?: TabsV2Item[];
|
||||
|
||||
/**
|
||||
* Specify the selected tab index
|
||||
* @default 0
|
||||
*/
|
||||
selectedIndex?: number;
|
||||
|
||||
/**
|
||||
* Specify the selected tab id
|
||||
*/
|
||||
selectedId?: TabsV2ItemId;
|
||||
|
||||
/**
|
||||
* Specify the type of tabs
|
||||
* @default "default"
|
||||
*/
|
||||
type?: "default" | "container";
|
||||
|
||||
/**
|
||||
* Specify the ARIA label for the chevron icon
|
||||
* @default "Show menu options"
|
||||
*/
|
||||
iconDescription?: string;
|
||||
|
||||
/**
|
||||
* Specify the tab trigger href attribute
|
||||
* @default "#"
|
||||
*/
|
||||
triggerHref?: string;
|
||||
}
|
||||
|
||||
export default class TabsV2 {
|
||||
$$prop_def: TabsV2Props;
|
||||
$$slot_def: {
|
||||
default: { id: TabsV2ItemId; index: number; item: TabsV2Item };
|
||||
tab: {};
|
||||
};
|
||||
|
||||
$on(
|
||||
eventname: "change",
|
||||
cb: (event: CustomEvent<{ selectedIndex: number; selectedId: TabsV2ItemId; currentItem: TabsV2Item }>) => void
|
||||
): () => void;
|
||||
$on(eventname: string, cb: (event: Event) => void): () => void;
|
||||
}
|
1
types/index.d.ts
vendored
1
types/index.d.ts
vendored
|
@ -110,6 +110,7 @@ export { default as Tabs } from "./Tabs/Tabs";
|
|||
export { default as Tab } from "./Tabs/Tab";
|
||||
export { default as TabContent } from "./Tabs/TabContent";
|
||||
export { default as TabsSkeleton } from "./Tabs/TabsSkeleton";
|
||||
export { default as TabsV2 } from "./Tabs/TabsV2";
|
||||
export { default as TagSkeleton } from "./Tag/TagSkeleton";
|
||||
export { default as Tag } from "./Tag/Tag";
|
||||
export { default as TextArea } from "./TextArea/TextArea";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue