From de34fbced589c2ff95348751ad93252ac056f391 Mon Sep 17 00:00:00 2001 From: yu-i-i Date: Sun, 4 May 2025 16:55:45 +0200 Subject: [PATCH] Symbol Palette: get rid of @reach/tabs --- .../components/symbol-palette-body.js | 41 ++++++--- .../components/symbol-palette-close-button.js | 2 +- .../components/symbol-palette-content.js | 20 +++-- .../components/symbol-palette-search.js | 2 +- .../components/symbol-palette-tabs.js | 84 +++++++++++++++---- .../stylesheets/modules/symbol-palette.scss | 2 +- services/web/package.json | 1 - 7 files changed, 112 insertions(+), 40 deletions(-) diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js index c4f47e325d..3f9eb7fc5f 100644 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js +++ b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-body.js @@ -1,4 +1,3 @@ -import { TabPanels, TabPanel } from '@reach/tabs' import { useTranslation } from 'react-i18next' import PropTypes from 'prop-types' import SymbolPaletteItems from './symbol-palette-items' @@ -9,6 +8,7 @@ export default function SymbolPaletteBody({ filteredSymbols, handleSelect, focusInput, + activeCategoryId, }) { const { t } = useTranslation() @@ -17,7 +17,7 @@ export default function SymbolPaletteBody({ // note: include empty tab panels so that aria-controls on tabs can still reference the panel ids if (filteredSymbols) { return ( - <> +
{filteredSymbols.length ? ( {t('no_symbols_found')}
)} - - {categories.map(category => ( - - ))} - - + {categories.map(category => ( + ) } // not searching: show the symbols grouped by category return ( - - {categories.map(category => ( - +
+ {categories.map((category) => ( + ))} - +
) + + } SymbolPaletteBody.propTypes = { categories: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -58,4 +72,5 @@ SymbolPaletteBody.propTypes = { filteredSymbols: PropTypes.arrayOf(PropTypes.object), handleSelect: PropTypes.func.isRequired, focusInput: PropTypes.func.isRequired, + activeCategoryId: PropTypes.string.isRequired, } diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-close-button.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-close-button.js index 839b5d1cd5..dbde7ca775 100644 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-close-button.js +++ b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-close-button.js @@ -11,7 +11,7 @@ export default function SymbolPaletteCloseButton() { type="button" className="btn-close symbol-palette-close-button" onClick={toggleSymbolPalette} - aria-label={t('clear_search')} + aria-label={t('close')} > diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js index cb5a9e3029..b395cac53d 100644 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js +++ b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-content.js @@ -1,4 +1,3 @@ -import { Tabs } from '@reach/tabs' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import PropTypes from 'prop-types' @@ -10,8 +9,6 @@ import SymbolPaletteBody from './symbol-palette-body' import SymbolPaletteTabs from './symbol-palette-tabs' import SymbolPaletteCloseButton from './symbol-palette-close-button' -import '@reach/tabs/styles.css' - export default function SymbolPaletteContent({ handleSelect }) { const [input, setInput] = useState('') @@ -19,6 +16,7 @@ export default function SymbolPaletteContent({ handleSelect }) { // build the list of categories with translated labels const categories = useMemo(() => createCategories(t), [t]) + const [activeCategoryId, setActiveCategoryId] = useState(categories[0]?.id) // group the symbols by category const categorisedSymbols = useMemo( @@ -59,18 +57,23 @@ export default function SymbolPaletteContent({ handleSelect }) { inputRef.current.focus() } }, []) - return ( - +
- +
-
+
+ +
- +
) } SymbolPaletteContent.propTypes = { diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js index 7d52a82874..75d01f0119 100644 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js +++ b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-search.js @@ -42,4 +42,4 @@ export default function SymbolPaletteSearch({ setInput, inputRef }) { SymbolPaletteSearch.propTypes = { setInput: PropTypes.func.isRequired, inputRef: PropTypes.object.isRequired, -}; +} diff --git a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js index d53cd93ac0..80f0421f27 100644 --- a/services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js +++ b/services/web/frontend/js/features/symbol-palette/components/symbol-palette-tabs.js @@ -1,22 +1,76 @@ -import { TabList, Tab } from '@reach/tabs' import PropTypes from 'prop-types' +import { useState, useRef } from 'react' + + +export default function SymbolPaletteTabs({ + categories, + activeCategoryId, + setActiveCategoryId, +}) { + + const buttonRefs = useRef([]) + const focusTab = (index) => { + setActiveCategoryId(categories[index].id) + buttonRefs.current[index]?.focus() + } + + const handleKeyDown = (e, index) => { + switch (e.key) { + case 'ArrowRight': + focusTab((index + 1) % categories.length) + break + case 'ArrowLeft': + focusTab((index - 1 + categories.length) % categories.length) + break + case 'Home': + case 'PageUp': + focusTab(0) + break + case 'End': + case 'PageDown': + focusTab(categories.length - 1) + break + default: + break + } + } -export default function SymbolPaletteTabs({ categories }) { return ( - - {categories.map(category => ( - - {category.label} - - ))} - +
+ {categories.map((category, index) => { + const selected = activeCategoryId === category.id + return ( + + ) + })} +
) } + SymbolPaletteTabs.propTypes = { - categories: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - }) - ).isRequired, + categories: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + })).isRequired, + activeCategoryId: PropTypes.string.isRequired, + setActiveCategoryId: PropTypes.func.isRequired, } diff --git a/services/web/frontend/stylesheets/modules/symbol-palette.scss b/services/web/frontend/stylesheets/modules/symbol-palette.scss index 2841b374e1..41d2c7de70 100644 --- a/services/web/frontend/stylesheets/modules/symbol-palette.scss +++ b/services/web/frontend/stylesheets/modules/symbol-palette.scss @@ -155,7 +155,7 @@ .symbol-palette-close-button-outer { display: flex; align-items: center; - margin-right: var(--spacing-01); + margin-right: var(--spacing-05); } .symbol-palette-close-button { diff --git a/services/web/package.json b/services/web/package.json index 2868ff76d3..6d6f1c939f 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -232,7 +232,6 @@ "@pollyjs/core": "^6.0.6", "@pollyjs/persister-fs": "^6.0.6", "@prettier/plugin-pug": "^3.4.0", - "@reach/tabs": "0.18.0", "@replit/codemirror-emacs": "overleaf/codemirror-emacs#4394c03858f27053f8768258e9493866e06e938e", "@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#371ce3b56f453a392eb0d3b85ab019c185c68e1f", "@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",