mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-24 01:29:35 +02:00
Symbol Palette: get rid of @reach/tabs
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<div className="symbol-palette-panels">
|
||||
{filteredSymbols.length ? (
|
||||
<SymbolPaletteItems
|
||||
items={filteredSymbols}
|
||||
@@ -28,29 +28,43 @@ export default function SymbolPaletteBody({
|
||||
<div className="symbol-palette-empty">{t('no_symbols_found')}</div>
|
||||
)}
|
||||
|
||||
<TabPanels>
|
||||
{categories.map(category => (
|
||||
<TabPanel key={category.id} tabIndex={-1} />
|
||||
))}
|
||||
</TabPanels>
|
||||
</>
|
||||
{categories.map(category => (
|
||||
<div
|
||||
key={category.id}
|
||||
role="tabpanel"
|
||||
className="symbol-palette-panel"
|
||||
id={`symbol-palette-panel-${category.id}`}
|
||||
aria-labelledby={`symbol-palette-tab-${category.id}`}
|
||||
hidden
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// not searching: show the symbols grouped by category
|
||||
return (
|
||||
<TabPanels>
|
||||
{categories.map(category => (
|
||||
<TabPanel key={category.id} tabIndex={-1}>
|
||||
<div className="symbol-palette-panels">
|
||||
{categories.map((category) => (
|
||||
<div
|
||||
key={category.id}
|
||||
id={`symbol-palette-panel-${category.id}`}
|
||||
className="symbol-palette-panel"
|
||||
role="tabpanel"
|
||||
aria-labelledby={`symbol-palette-tab-${category.id}`}
|
||||
hidden={category.id !== activeCategoryId}
|
||||
>
|
||||
<SymbolPaletteItems
|
||||
items={categorisedSymbols[category.id]}
|
||||
handleSelect={handleSelect}
|
||||
focusInput={focusInput}
|
||||
/>
|
||||
</TabPanel>
|
||||
</div>
|
||||
))}
|
||||
</TabPanels>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -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')}
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<Tabs className="symbol-palette-container">
|
||||
<div className="symbol-palette-container">
|
||||
<div className="symbol-palette">
|
||||
<div className="symbol-palette-header-outer">
|
||||
<div className="symbol-palette-header">
|
||||
<SymbolPaletteTabs categories={categories} />
|
||||
<SymbolPaletteTabs
|
||||
categories={categories}
|
||||
activeCategoryId={activeCategoryId}
|
||||
setActiveCategoryId={setActiveCategoryId}
|
||||
/>
|
||||
<div className="symbol-palette-header-group">
|
||||
<SymbolPaletteSearch setInput={setInput} inputRef={inputRef} />
|
||||
<SymbolPaletteCloseButton />
|
||||
</div>
|
||||
</div>
|
||||
<div className="symbol-palette-header-group">
|
||||
<SymbolPaletteCloseButton />
|
||||
</div>
|
||||
</div>
|
||||
<div className="symbol-palette-body">
|
||||
<SymbolPaletteBody
|
||||
@@ -79,10 +82,11 @@ export default function SymbolPaletteContent({ handleSelect }) {
|
||||
filteredSymbols={filteredSymbols}
|
||||
handleSelect={handleSelect}
|
||||
focusInput={focusInput}
|
||||
activeCategoryId={activeCategoryId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
SymbolPaletteContent.propTypes = {
|
||||
|
||||
@@ -42,4 +42,4 @@ export default function SymbolPaletteSearch({ setInput, inputRef }) {
|
||||
SymbolPaletteSearch.propTypes = {
|
||||
setInput: PropTypes.func.isRequired,
|
||||
inputRef: PropTypes.object.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<TabList aria-label="Symbol Categories" className="symbol-palette-tab-list">
|
||||
{categories.map(category => (
|
||||
<Tab key={category.id} className="symbol-palette-tab">
|
||||
{category.label}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
<div
|
||||
role="tablist"
|
||||
aria-label="Symbol Categories"
|
||||
className="symbol-palette-tab-list"
|
||||
tabIndex={0}
|
||||
>
|
||||
{categories.map((category, index) => {
|
||||
const selected = activeCategoryId === category.id
|
||||
return (
|
||||
<button
|
||||
key={category.id}
|
||||
role="tab"
|
||||
type="button"
|
||||
className="symbol-palette-tab"
|
||||
id={`symbol-palette-tab-${category.id}`}
|
||||
aria-controls={`symbol-palette-panel-${category.id}`}
|
||||
aria-selected={selected}
|
||||
tabIndex={selected ? 0 : -1}
|
||||
ref={(el) => (buttonRefs.current[index] = el)}
|
||||
onClick={() => setActiveCategoryId(category.id)}
|
||||
onKeyDown={(e) => handleKeyDown(e, index)}
|
||||
>
|
||||
{category.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -207,7 +207,6 @@
|
||||
"@pollyjs/adapter-node-http": "^6.0.6",
|
||||
"@pollyjs/core": "^6.0.6",
|
||||
"@pollyjs/persister-fs": "^6.0.6",
|
||||
"@reach/tabs": "0.18.0",
|
||||
"@replit/codemirror-emacs": "overleaf/codemirror-emacs#4394c03858f27053f8768258e9493866e06e938e",
|
||||
"@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#78264032eb286bc47871569ae87bff5ca1c6c161",
|
||||
"@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",
|
||||
|
||||
Reference in New Issue
Block a user