diff --git a/client/components/anchoredBox.jsx b/client/components/anchoredBox.jsx index 7978e44d4..2f1735ebb 100644 --- a/client/components/anchoredBox.jsx +++ b/client/components/anchoredBox.jsx @@ -1,77 +1,91 @@ -import React, { useState, useRef, useEffect, forwardRef } from 'react'; +import React, { useState, useRef, forwardRef, useEffect, cloneElement, Children } from 'react'; import './anchoredBox.less'; -const AnchoredBox = ({ anchorPoint = 'center', className, children, ...props })=>{ - const [visible, setVisible] = useState(false); - const triggerRef = useRef(null); - const boxRef = useRef(null); +// Anchored is a wrapper component that must have as children an and a component. +// AnchoredTrigger must have a unique `id` prop, which is passed up to Anchored, saved in state on mount, and +// then passed down through props into AnchoredBox. The `id` is used for the CSS Anchor Positioning properties. +// **The Anchor Positioning API is not available in Firefox yet** +// So in Firefox the positioning isn't perfect but is likely sufficient, and FF team seems to be working on the API quickly. + +const Anchored = ({ children })=>{ + const [visible, setVisible] = useState(false); + const [anchorId, setAnchorId] = useState(null); + const boxRef = useRef(null); + const triggerRef = useRef(null); + + // promote trigger id to Anchored id (to pass it back down to the box as "anchorId") + useEffect(()=>{ + if(triggerRef.current){ + setAnchorId(triggerRef.current.id); + } + }, []); + + // close box on outside click or Escape key useEffect(()=>{ const handleClickOutside = (evt)=>{ if( boxRef.current && - !boxRef.current.contains(evt.target) && - triggerRef.current && - !triggerRef.current.contains(evt.target) + !boxRef.current.contains(evt.target) && + triggerRef.current && + !triggerRef.current.contains(evt.target) ) { setVisible(false); } }; - window.addEventListener('click', handleClickOutside); - const iframe = document.querySelector('iframe'); - if(iframe) { - iframe.addEventListener('load', ()=>{ - const iframeDoc = iframe.contentWindow.document; - if(iframeDoc) { - iframeDoc.addEventListener('click', handleClickOutside); - } - }); - } + const handleEscapeKey = (evt)=>{ + if(evt.key === 'Escape') setVisible(false); + }; + + window.addEventListener('click', handleClickOutside); + window.addEventListener('keydown', handleEscapeKey); return ()=>{ window.removeEventListener('click', handleClickOutside); - if(iframe?.contentWindow?.document) { - iframe.contentWindow.document.removeEventListener('click', handleClickOutside); - } + window.removeEventListener('keydown', handleEscapeKey); }; }, []); - const handleClick = ()=>{ - setVisible(!visible); - triggerRef.current?.focus(); - }; + const toggleVisibility = ()=>setVisible((prev)=>!prev); - const handleKeyDown = (evt)=>{ - if(evt.key === 'Escape') { - setVisible(false); - triggerRef.current?.focus(); + // Map children to inject necessary props + const mappedChildren = Children.map(children, (child)=>{ + if(child.type === AnchoredTrigger) { + return cloneElement(child, { ref: triggerRef, toggleVisibility, visible }); } - }; + if(child.type === AnchoredBox) { + return cloneElement(child, { ref: boxRef, visible, anchorId }); + } + return child; + }); - return ( - <> - -
handleKeyDown(evt)} - > -

{props.title}

- {children} -
- - ); + return <>{mappedChildren}; }; -const TriggerButton = forwardRef((props, ref)=>( - )); -export default AnchoredBox; +// forward ref for AnchoredBox +const AnchoredBox = forwardRef(({ visible, children, className, anchorId, ...props }, ref)=>( +
+ {children} +
+)); + +export { Anchored, AnchoredTrigger, AnchoredBox }; diff --git a/client/components/anchoredBox.less b/client/components/anchoredBox.less index 153cf84a6..69fa2d9c1 100644 --- a/client/components/anchoredBox.less +++ b/client/components/anchoredBox.less @@ -1,17 +1,13 @@ .anchored-trigger { - @supports (anchor-name: --view-settings){ - anchor-name: --view-settings; - } &.active { background-color: #444444; } - } + .anchored-box { position:absolute; top: 30px; - @supports (position-anchor: --view-settings){ - position-anchor: --view-settings; + @supports (inset-block-start: anchor(bottom)){ inset-block-start: anchor(bottom); } justify-self: anchor-center; diff --git a/client/homebrew/brewRenderer/toolBar/toolBar.jsx b/client/homebrew/brewRenderer/toolBar/toolBar.jsx index fb2c4d2ae..bab33433f 100644 --- a/client/homebrew/brewRenderer/toolBar/toolBar.jsx +++ b/client/homebrew/brewRenderer/toolBar/toolBar.jsx @@ -3,7 +3,7 @@ const React = require('react'); const { useState, useEffect } = React; const _ = require('lodash'); -import AnchoredBox from '../../../components/anchoredBox.jsx'; +import { Anchored, AnchoredBox, AnchoredTrigger} from '../../../components/anchoredBox.jsx'; // import * as ZoomIcons from '../../../icons/icon-components/zoomIcons.jsx'; const MAX_ZOOM = 300; @@ -174,17 +174,21 @@ const ToolBar = ({ onZoomChange, currentPage, onPageChange, totalPages, onStyleC > - - - - - - + + + +

Options

+ + + + +
+