feat: add Tabs component with Tab, TabItem, and TabContent for improved navigation
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
@use '../../styles/tokens/typography' as *;
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--nds-spacing-xs);
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: fit-content;
|
||||
cursor: pointer;
|
||||
padding: var(--nds-spacing-2xs);
|
||||
@include text-base;
|
||||
|
||||
&--active {
|
||||
color: var(--nds-primary);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 120%;
|
||||
height: 1px;
|
||||
background-color: var(--nds-primary);
|
||||
border-radius: var(--nds-radius-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
export interface Props {
|
||||
id: string;
|
||||
defaultActive?: string;
|
||||
}
|
||||
|
||||
const { id, defaultActive } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="tab" data-tab-group id={id} data-default-active={defaultActive}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll<HTMLElement>('[data-tab-group]').forEach((group) => {
|
||||
const defaultActive = group.dataset.defaultActive;
|
||||
|
||||
function setActive(itemId: string) {
|
||||
const groupId = group.id;
|
||||
|
||||
group.querySelectorAll<HTMLElement>('[data-tab-item]').forEach((item) => {
|
||||
item.classList.toggle('tab__item--active', item.dataset.tabItem === itemId);
|
||||
item.setAttribute('aria-selected', String(item.dataset.tabItem === itemId));
|
||||
});
|
||||
|
||||
document.querySelectorAll<HTMLElement>(`[data-tab-content][data-tab-id="${groupId}"]`).forEach((content) => {
|
||||
content.hidden = content.dataset.tabItemId !== itemId;
|
||||
});
|
||||
|
||||
group.dispatchEvent(new CustomEvent('tab:change', {
|
||||
detail: { itemId },
|
||||
bubbles: true,
|
||||
}));
|
||||
}
|
||||
|
||||
const firstItem = group.querySelector<HTMLElement>('[data-tab-item]');
|
||||
const initialId = defaultActive ?? firstItem?.dataset.tabItem ?? '';
|
||||
if (initialId) setActive(initialId);
|
||||
|
||||
group.addEventListener('tab-item:click', (e: Event) => {
|
||||
setActive((e as CustomEvent<{ itemId: string }>).detail.itemId);
|
||||
});
|
||||
|
||||
group.addEventListener('tab:set', (e: Event) => {
|
||||
setActive((e as CustomEvent<{ itemId: string }>).detail.itemId);
|
||||
});
|
||||
|
||||
(group as any).setActiveTab = (itemId: string) => setActive(itemId);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use './tab';
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
export interface Props {
|
||||
id: string;
|
||||
itemId: string;
|
||||
}
|
||||
|
||||
const { id, itemId } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
data-tab-content
|
||||
data-tab-id={id}
|
||||
data-tab-item-id={itemId}
|
||||
hidden
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use './tab';
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
export interface Props {
|
||||
id: string;
|
||||
}
|
||||
|
||||
const { id } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class="tab__item"
|
||||
data-tab-item={id}
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
tabindex="0"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll<HTMLElement>('[data-tab-item]').forEach((item) => {
|
||||
function notify() {
|
||||
const group = item.closest('[data-tab-group]');
|
||||
if (!group) return;
|
||||
group.dispatchEvent(new CustomEvent('tab-item:click', {
|
||||
detail: { itemId: item.dataset.tabItem },
|
||||
}));
|
||||
}
|
||||
|
||||
item.addEventListener('click', notify);
|
||||
item.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
notify();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use './tab';
|
||||
</style>
|
||||
@@ -2,4 +2,7 @@
|
||||
|
||||
export * from './Icons/index.ts';
|
||||
export { default as Notification } from './Notifications/notification.astro';
|
||||
export { default as Toggle } from './Toggle/toggle.astro';
|
||||
export { default as Toggle } from './Toggle/toggle.astro';
|
||||
export { default as Tab } from './Tabs/tab.astro';
|
||||
export { default as TabItem } from './Tabs/tabItem.astro';
|
||||
export { default as TabContent } from './Tabs/tabContent.astro';
|
||||
Reference in New Issue
Block a user