Skip to content

Commit

Permalink
chore(Tabs): Rework component
Browse files Browse the repository at this point in the history
  • Loading branch information
Barsnes committed Jan 14, 2025
1 parent cfef668 commit 8888d9c
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 65 deletions.
134 changes: 71 additions & 63 deletions packages/css/src/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,88 @@
--dsc-tabs-content-padding: var(--ds-size-5);
--dsc-tabs-content-color: var(--ds-color-neutral-text-default);
--dsc-tabs-list-border-color: var(--ds-color-neutral-border-subtle);
}

.ds-tabs__panel {
padding: var(--dsc-tabs-content-padding);
color: var(--dsc-tabs-content-color);
}
& > [role='tabpanel'] {
padding: var(--dsc-tabs-content-padding);
color: var(--dsc-tabs-content-color);

.ds-tabs__tablist {
display: flex;
flex-direction: row;
border-bottom: var(--ds-border-width-default) solid var(--dsc-tabs-list-border-color);
position: relative;
}
@composes ds-focus from './base.css';
}

.ds-tabs__tab {
align-items: center;
background: none;
border: 0;
box-sizing: border-box;
color: var(--dsc-tabs-tab-color);
cursor: pointer;
display: flex;
flex-direction: row;
font-family: inherit;
font-size: inherit;
gap: var(--ds-size-1);
justify-content: center;
line-height: var(--ds-line-height-sm);
margin: 0;
padding: var(--dsc-tabs-tab-padding);
position: relative;
text-align: center;
& > [role='tablist'] {
flex-direction: row;
border-bottom: var(--ds-border-width-default) solid var(--dsc-tabs-list-border-color);
position: relative;

&:not([data-size]) {
font-size: inherit; /* Ensure inheriting font-size when <button> */
}
&:not([hidden]) {
display: flex;
}

& :where(img, svg) {
flex-shrink: 0; /* Never shrink icon */
font-size: 1.25em; /* Auto scale icon based on font-size */
}
& > button {
align-items: center;
background: none;
border: 0;
box-sizing: border-box;
color: var(--dsc-tabs-tab-color);
cursor: pointer;
flex-direction: row;
font-family: inherit;
font-size: inherit;
gap: var(--ds-size-1);
justify-content: center;
line-height: var(--ds-line-height-sm);
margin: 0;
padding: var(--dsc-tabs-tab-padding);
position: relative;
text-align: center;

&[aria-selected='true'] {
--dsc-tabs-tab-bottom-border-color: var(--ds-color-base-default);
--dsc-tabs-tab-color: var(--ds-color-text-subtle);
&:not([hidden]) {
display: flex;
}

@media (forced-colors: active) {
--dsc-tabs-tab-color: CanvasText;
border-bottom: 2px solid CanvasText;
}
}
&:not([data-size]) {
font-size: inherit; /* Ensure inheriting font-size when <button> */
}

@composes ds-focus from './base.css';
& :where(img, svg) {
flex-shrink: 0; /* Never shrink icon */
font-size: 1.25em; /* Auto scale icon based on font-size */
}

/* We set z-index to make sure the active line does not bleed over the focus indicator */
&:focus-visible {
z-index: 2;
}
&[aria-selected='true'] {
--dsc-tabs-tab-bottom-border-color: var(--ds-color-base-default);
--dsc-tabs-tab-color: var(--ds-color-text-subtle);

&::after {
content: '';
display: block;
height: .15em; /* Scale with font */
width: 100%;
background-color: var(--dsc-tabs-tab-bottom-border-color);
position: absolute;
bottom: 0;
left: 0;
}
@media (forced-colors: active) {
--dsc-tabs-tab-color: CanvasText;
border-bottom: 2px solid CanvasText;
}
}

@composes ds-focus from './base.css';

/* We set z-index to make sure the active line does not bleed over the focus indicator */
&:focus-visible {
z-index: 2;
}

&::after {
content: '';
display: block;
height: .15em; /* Scale with font */
width: 100%;
background-color: var(--dsc-tabs-tab-bottom-border-color);
position: absolute;
bottom: 0;
left: 0;
}

@media (hover: hover) and (pointer: fine) {
&:hover:not([aria-selected='true']) {
--dsc-tabs-tab-bottom-border-color: var(--ds-color-neutral-border-subtle);
--dsc-tabs-tab-color: var(--ds-color-neutral-text-default);
@media (hover: hover) and (pointer: fine) {
&:hover:not([aria-selected='true']) {
--dsc-tabs-tab-bottom-border-color: var(--ds-color-neutral-border-subtle);
--dsc-tabs-tab-color: var(--ds-color-neutral-text-default);
}
}
}
}
}
25 changes: 23 additions & 2 deletions packages/react/src/components/Tabs/TabsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import cl from 'clsx/lite';
import type { HTMLAttributes } from 'react';
import { forwardRef, useContext } from 'react';
import { forwardRef, useContext, useEffect, useRef, useState } from 'react';

import { useMergeRefs } from '@floating-ui/react';
import { Context } from './Tabs';

export type TabsPanelProps = {
Expand All @@ -21,10 +22,30 @@ export const TabsPanel = forwardRef<HTMLDivElement, TabsPanelProps>(
const { value: tabsValue } = useContext(Context);
const active = value === tabsValue;

const [hasTabbableElement, setHasTabbableElement] = useState(false);

const internalRef = useRef<HTMLDivElement>(null);
const mergedRef = useMergeRefs([ref, internalRef]);

/* Check if the panel has any tabbable elements */
useEffect(() => {
if (!internalRef.current) return;
const tabbableElements = internalRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
setHasTabbableElement(tabbableElements.length > 0);
}, [children]);

return (
<>
{active && (
<div className={cl('ds-tabs__panel', className)} ref={ref} {...rest}>
<div
ref={mergedRef}
role='tabpanel'
className={cl('ds-tabs__panel', className)}
tabIndex={hasTabbableElement ? undefined : 0}
{...rest}
>
{children}
</div>
)}
Expand Down

0 comments on commit 8888d9c

Please sign in to comment.