From 4e1820ad1a53412deba9b9551407eb2171ca708e Mon Sep 17 00:00:00 2001 From: John Coburn Date: Fri, 8 Mar 2024 15:21:51 -0600 Subject: [PATCH] STCOM-1238 Accordion vs overlay z-index issues. (#2240) * Accordions retain their z-index after being blurred, and assume the highest z-index when focus enters them. * log changes * remove unnecessary addition to z-index * semi * add comments to code --- .storybook/stcom-webpack.config.js | 2 ++ CHANGELOG.md | 1 + lib/Accordion/Accordion.js | 45 +++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/.storybook/stcom-webpack.config.js b/.storybook/stcom-webpack.config.js index 58894b1be..654145739 100644 --- a/.storybook/stcom-webpack.config.js +++ b/.storybook/stcom-webpack.config.js @@ -12,6 +12,8 @@ const { babelOptions } = require('@folio/stripes-cli'); const adjustedBabelOptions = Object.assign(babelOptions, { plugins: babelOptions.plugins.filter((p) => !p.includes('react-refresh')) }); module.exports = async (config) => { + + config.devtool="eval-cheap-source-map"; // Replace Storybook's own CSS config // get index of their css loading rule... const cssRuleIndex = config.module.rules.findIndex(r => { const t = new RegExp(r.test); return t.test('m.css'); }); diff --git a/CHANGELOG.md b/CHANGELOG.md index e092cd426..fce85894c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ * Accessible grouping for filter group checkboxes via `role="group"`. Refs STCOM-1192. * Fix `` does not have correct `aria-label` when `label` prop type is string. Fixes STCOM-1271. * Add `number-generator` icon. Refs STCOM-1269. +* Accordions retain their z-index after being blurred, and assume the highest z-index when focus enters them. Refs STCOM-1238. ## [12.0.0](https://github.com/folio-org/stripes-components/tree/v12.0.0) (2023-10-11) [Full Changelog](https://github.com/folio-org/stripes-components/compare/v11.0.0...v12.0.0) diff --git a/lib/Accordion/Accordion.js b/lib/Accordion/Accordion.js index 8e4b4183a..f4a84664d 100644 --- a/lib/Accordion/Accordion.js +++ b/lib/Accordion/Accordion.js @@ -64,6 +64,28 @@ function getWrapClass(open) { ); } +/* The z-index requirements for accordions: +* Accordions should not overlap any overlays/dropdowns from previous/next accordions. +* Accordions/overlays should not be overlapped if focus left the accordion or went to another pane... +* +*/ + +// loops through other accordions rendered within the UI to find the highest z-index among them all. +const getHighestStackOrder = () => { + const accordions = Array.from(document.querySelectorAll(`.${css['content-wrap']}`)); + const highest = accordions?.reduce((acc, cur) => { + let currentZ = getComputedStyle(cur).getPropertyValue('z-index'); + // skip any that might have non-integer z-index settings. + if (currentZ === 'auto' || currentZ === null) return acc; + currentZ = parseInt(currentZ, 10); + // this will prevent duplicated highest z-index, since with matching z-index, the tag + // that's ordered last will overlap. + if (currentZ === acc) currentZ += 1; + return currentZ > acc ? currentZ : acc; + }, 2); + return highest; +} + const Accordion = (props) => { const { accordionSet, @@ -102,15 +124,29 @@ const Accordion = (props) => { const getRef = useRef(() => toggle.current).current; const [isOpen, updateOpen] = useState(open || !closedByDefault); const [registered, updateRegistered] = useState(!accordionSet); - const [stackOrder, updateStackOrder] = useState(1); const [zIndex, updateZIndex] = useState(1); + const [focused, updateFocused] = useState(false); const uncontrolledToggle = useRef(() => { updateOpen(current => !current); }).current; - const handleFocusIn = () => updateZIndex(accordionSet?.getNumberOfAccordions() || 2); - const handleFocusOut = () => updateZIndex(stackOrder); + // Affecting z-index when accordions are focused within. + // We only update the accordion z-index if it does not contain focus _and_ if it's not + // already the highest z-index among other accordions. + const handleFocusIn = () => { + if (!focused) { + updateFocused(true); + const highest = getHighestStackOrder(); + if (zIndex !== highest) { + updateZIndex(highest); + } + } + } + + const handleFocusOut = () => { + updateFocused(false); + } const onToggle = (toggleArgs) => { if (typeof open === 'undefined') { @@ -128,12 +164,13 @@ const Accordion = (props) => { } }, [open]); + // At registration, accordions are assigned a z-index that, in most cases, + // will render in reverse order. useEffect(() => { // eslint-disable-line function registrationCallback(isRegistered) { updateRegistered(isRegistered); if (accordionSet) { const defaultZIndex = accordionSet.getStackOrder(trackingId); - updateStackOrder(defaultZIndex); updateZIndex(defaultZIndex); } }