Replace remaining React ARIA components with Compound components (#2576)

* Fix issues detected by Knip

Including cleaning up some unused code and dependencies, using a React hook that we unintentionally stopped using, and also adding some previously undeclared dependencies.

* Replace remaining React ARIA components with Compound components

* fix button position

* disable scrollbars to resolve overlapping button

---------

Co-authored-by: Timo <toger5@hotmail.de>
This commit is contained in:
Robin
2024-08-28 08:44:39 -04:00
committed by GitHub
parent 7bca541cb6
commit 0db51d9dfd
62 changed files with 668 additions and 2603 deletions

View File

@@ -1,5 +1,5 @@
/*
Copyright 2022 New Vector Ltd
Copyright 2024 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,60 +21,16 @@ limitations under the License.
}
.tabList {
display: flex;
list-style: none;
padding: 0;
margin: 0 auto 24px auto;
gap: 16px;
overflow-y: auto;
max-width: 100%;
/*no scrollbars*/
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
.tab {
height: 32px;
box-sizing: border-box;
border-radius: 8px;
background-color: transparent;
display: flex;
align-items: center;
padding: 0 8px;
border: none;
cursor: pointer;
font-size: var(--font-size-body);
}
.tab > * {
color: var(--cpd-color-text-secondary);
margin: 0 8px 0 0;
}
.tab svg * {
fill: var(--cpd-color-text-secondary);
}
.tab > :last-child {
margin-right: 0;
}
.tab.selected {
background-color: var(--cpd-color-text-action-accent);
}
.tab.selected * {
color: var(--stopgap-color-on-solid-accent);
}
.tab.selected svg * {
fill: var(--stopgap-color-on-solid-accent);
}
.tab.disabled {
}
.tabPanel {
display: flex;
flex-direction: column;
flex: 1;
padding: 0;
overflow-y: auto;
.tabList::-webkit-scrollbar {
/*no scrollbars*/
background: transparent; /* Chrome/Safari/Webkit */
width: 0px;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2022 New Vector Ltd
Copyright 2024 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,74 +14,53 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { useRef } from "react";
import { useTabList, useTab, useTabPanel } from "@react-aria/tabs";
import { Item } from "@react-stately/collections";
import { useTabListState, TabListState } from "@react-stately/tabs";
import classNames from "classnames";
import { AriaTabPanelProps, TabListProps } from "@react-types/tabs";
import { Node } from "@react-types/shared";
import { Key, ReactNode, useId } from "react";
import { NavBar, NavItem } from "@vector-im/compound-web";
import styles from "./Tabs.module.css";
interface TabContainerProps<T> extends TabListProps<T> {
className?: string;
export interface Tab<K extends Key> {
key: K;
name: string;
content: ReactNode;
}
export function TabContainer<T extends object>(
props: TabContainerProps<T>,
): JSX.Element {
const state = useTabListState<T>(props);
const ref = useRef<HTMLUListElement>(null);
const { tabListProps } = useTabList(props, state, ref);
interface Props<K extends Key> {
label: string;
tab: K;
onTabChange: (key: K) => void;
tabs: Tab<K>[];
}
export function TabContainer<K extends Key>({
label,
tab,
onTabChange,
tabs,
}: Props<K>): ReactNode {
const idPrefix = useId();
return (
<div className={classNames(styles.tabContainer, props.className)}>
<ul {...tabListProps} ref={ref} className={styles.tabList}>
{[...state.collection].map((item) => (
<Tab item={item} state={state} key={item.key} />
<div className={styles.tabContainer}>
<NavBar role="tablist" aria-label={label} className={styles.tabList}>
{tabs.map(({ key, name }) => (
<NavItem
aria-controls={`${idPrefix}[${key}]`}
onClick={() => onTabChange(key)}
active={key === tab}
>
{name}
</NavItem>
))}
</ul>
<TabPanel key={state.selectedItem?.key} state={state} />
</NavBar>
{tabs.map(({ key, content }) => (
<div
id={`${idPrefix}[${key}]`}
style={{ display: key === tab ? undefined : "none" }}
>
{content}
</div>
))}
</div>
);
}
interface TabProps<T> {
item: Node<T>;
state: TabListState<T>;
}
function Tab<T>({ item, state }: TabProps<T>): JSX.Element {
const { key, rendered } = item;
const ref = useRef<HTMLLIElement>(null);
const { tabProps } = useTab({ key }, state, ref);
return (
<li
{...tabProps}
ref={ref}
className={classNames(styles.tab, {
[styles.selected]: state.selectedKey === key,
[styles.disabled]: state.disabledKeys.has(key),
})}
>
{rendered}
</li>
);
}
interface TabPanelProps<T> extends AriaTabPanelProps {
state: TabListState<T>;
}
function TabPanel<T>({ state, ...props }: TabPanelProps<T>): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const { tabPanelProps } = useTabPanel(props, state, ref);
return (
<div {...tabPanelProps} ref={ref} className={styles.tabPanel}>
{state.selectedItem?.props.children}
</div>
);
}
export const TabItem = Item;