From ec827c0d2065ec96f3b3659ec62dde7eb8e9a0cc Mon Sep 17 00:00:00 2001 From: Pegah Date: Tue, 28 Nov 2023 19:22:50 -0800 Subject: [PATCH] keyboard fixes --- .../components/ButtonClose/ButtonClose.scss | 3 ++ .../components/ButtonClose/ButtonClose.tsx | 7 +-- web/src/components/Footer/Footer.tsx | 4 +- web/src/components/Header/Header.tsx | 13 ++--- .../MapViewCreateOutcome.component.tsx | 2 +- web/src/components/Modal/Modal.tsx | 52 +++++++++++++++++-- web/src/components/Toast/Toast.tsx | 1 + web/src/routes/App.component.tsx | 12 +++++ web/src/routes/App.connector.ts | 4 ++ .../ProjectView/MapView/MapView.component.tsx | 2 +- 10 files changed, 83 insertions(+), 17 deletions(-) diff --git a/web/src/components/ButtonClose/ButtonClose.scss b/web/src/components/ButtonClose/ButtonClose.scss index 97d32ab7..8b36ba17 100644 --- a/web/src/components/ButtonClose/ButtonClose.scss +++ b/web/src/components/ButtonClose/ButtonClose.scss @@ -2,6 +2,9 @@ border-radius: 50%; width: fit-content; transition: 0.2s all ease; + border: none; + background: none; + padding: 0; &:hover { background-color: var(--bg-color-hover); diff --git a/web/src/components/ButtonClose/ButtonClose.tsx b/web/src/components/ButtonClose/ButtonClose.tsx index 854d6552..431b7bd1 100644 --- a/web/src/components/ButtonClose/ButtonClose.tsx +++ b/web/src/components/ButtonClose/ButtonClose.tsx @@ -6,22 +6,23 @@ import './ButtonClose.scss' export type ButtonCloseProps = { size: 'small' | 'medium' | 'large' onClick: () => void + disabled?: boolean } const ButtonClose: React.FC = ({ size = 'medium', onClick, + disabled, }) => { return ( -
+
+ ) } diff --git a/web/src/components/Footer/Footer.tsx b/web/src/components/Footer/Footer.tsx index 4fe312a8..46554670 100644 --- a/web/src/components/Footer/Footer.tsx +++ b/web/src/components/Footer/Footer.tsx @@ -17,6 +17,7 @@ export type FooterProps = { hiddenAchievedOutcomes: CellIdString[] hiddenSmallOutcomes: CellIdString[] selectedLayeringAlgo: string + unselectAll: () => void showSmallOutcomes: (projectCellId: CellIdString) => void hideSmallOutcomes: (projectCellId: CellIdString) => void showAchievedOutcomes: (projectCellId: CellIdString) => void @@ -29,6 +30,7 @@ const Footer: React.FC = ({ hiddenAchievedOutcomes, hiddenSmallOutcomes, selectedLayeringAlgo, + unselectAll, showSmallOutcomes, hideSmallOutcomes, showAchievedOutcomes, @@ -115,7 +117,7 @@ const Footer: React.FC = ({
{isSyncing && } {mapPage && ( -
+
unselectAll()}> {/* If map viewing options is open */} > goToOutcome: (outcomeActionHash: ActionHashB64) => void + unselectAll: () => void // holochain updateStatus: (statusString: Profile['status']) => Promise } const Header: React.FC = ({ - // for update bar showUpdateBar, - setShowUpdateBar, hasMigratedSharedProject, whoami, - setModalState, - updateStatus, activeEntryPoints, project, - goToOutcome, members, presentMembers, + unselectAll, + setShowUpdateBar, + setModalState, + updateStatus, + goToOutcome, }) => { const [status, setStatus] = useState( // @ts-ignore @@ -80,7 +81,7 @@ const Header: React.FC = ({ } return ( -
+
unselectAll()}> {/* Update Bar */}
= ({ isChecked={isSmallScopeChecked} onChange={(newState) => setIsSmallScopeChecked(newState)} icon={} - text={'This Outcome is Small Scope'} + text={'Small Scope'} />
diff --git a/web/src/components/Modal/Modal.tsx b/web/src/components/Modal/Modal.tsx index f7684591..d3a3fcbb 100644 --- a/web/src/components/Modal/Modal.tsx +++ b/web/src/components/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect, useRef } from 'react' import { CSSTransition } from 'react-transition-group' import Icon from '../Icon/Icon' @@ -102,9 +102,53 @@ const Modal: React.FC = ({ onClose, children, }) => { + const modalRef = useRef(null) + + const trapFocus = (event: KeyboardEvent) => { + if (!modalRef.current || event.key !== 'Tab') { + return + } + + const focusableModalElements = modalRef.current.querySelectorAll( + 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' + ) as NodeListOf + + const firstElement = focusableModalElements[0] + const lastElement = + focusableModalElements[focusableModalElements.length - 1] + + if (event.shiftKey && document.activeElement === firstElement) { + lastElement.focus() + event.preventDefault() + } else if (!event.shiftKey && document.activeElement === lastElement) { + firstElement.focus() + event.preventDefault() + } + } + + useEffect(() => { + if (active) { + document.addEventListener('keydown', trapFocus) + + // Focus the first focusable element + // const focusableModalElements = modalRef.current?.querySelectorAll( + // 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' + // ) + // ;(focusableModalElements?.[0] as HTMLElement)?.focus() + } + + return () => { + document.removeEventListener('keydown', trapFocus) + } + }, [active]) + + if (!active) { + return null + } + return ( -
+
{/* TODO: figure out how to implement onclickoutside */} {/* without imapcting the styling for the modal */} {/* problem seen on Profile Setting after implementation */} @@ -115,9 +159,7 @@ const Modal: React.FC = ({
)} -
- {children} -
+
{children}
{/* */}
diff --git a/web/src/components/Toast/Toast.tsx b/web/src/components/Toast/Toast.tsx index e41c46d2..41f397d1 100644 --- a/web/src/components/Toast/Toast.tsx +++ b/web/src/components/Toast/Toast.tsx @@ -24,6 +24,7 @@ const Toast: React.FC = ({ toastState, setToastState }) => {
{recentText}
setToastState({ id: ShowToast.No })} /> diff --git a/web/src/routes/App.component.tsx b/web/src/routes/App.component.tsx index 4de162a2..2c1b6900 100644 --- a/web/src/routes/App.component.tsx +++ b/web/src/routes/App.component.tsx @@ -92,6 +92,7 @@ export type AppDispatchProps = { hideSmallOutcomes: (projectCellId: CellIdString) => void showAchievedOutcomes: (projectCellId: CellIdString) => void hideAchievedOutcomes: (projectCellId: CellIdString) => void + unselectAll: () => void } export type AppMergeProps = { @@ -139,6 +140,7 @@ const App: React.FC = ({ hideAchievedOutcomes, setSelectedLayeringAlgo, uninstallProject, + unselectAll, }) => { const [networkInfoOpen, setNetworkInfoOpen] = useState(false) @@ -182,6 +184,14 @@ const App: React.FC = ({ } }, [updateVersionInfo.newReleaseVersion]) + // when opening a modal, unselect any Outcomes that are selected + useEffect(() => { + if (modalState.id !== OpenModal.None) { + // unselect any selected outcomes + unselectAll() + } + }, [modalState]) + useEffect(() => { // add event listener for pressing the 'i' key with the ctrl key // to open the network info modal @@ -276,6 +286,7 @@ const App: React.FC = ({ showUpdateBar, setShowUpdateBar, hasMigratedSharedProject, + unselectAll, }} /> )} @@ -353,6 +364,7 @@ const App: React.FC = ({ hideAchievedOutcomes={hideAchievedOutcomes} selectedLayeringAlgo={selectedLayeringAlgo} setSelectedLayeringAlgo={setSelectedLayeringAlgo} + unselectAll={unselectAll} /> )} diff --git a/web/src/routes/App.connector.ts b/web/src/routes/App.connector.ts index c1ff6100..12f16d24 100644 --- a/web/src/routes/App.connector.ts +++ b/web/src/routes/App.connector.ts @@ -27,6 +27,7 @@ import { import ProjectsZomeApi from '../api/projectsApi' import { updateProjectMeta } from '../redux/persistent/projects/project-meta/actions' import { uninstallProject } from '../projects/uninstallProject' +import { unselectAll } from '../redux/ephemeral/selection/actions' function mapStateToProps(state: RootState): AppStateProps { const { @@ -120,6 +121,9 @@ function mapDispatchToProps(dispatch): AppDispatchProps { hideAchievedOutcomes: (projectCellId) => { return dispatch(hideAchievedOutcomes(projectCellId)) }, + unselectAll: () => { + return dispatch(unselectAll()) + } } } diff --git a/web/src/routes/ProjectView/MapView/MapView.component.tsx b/web/src/routes/ProjectView/MapView/MapView.component.tsx index fc99934e..d0fefba5 100644 --- a/web/src/routes/ProjectView/MapView/MapView.component.tsx +++ b/web/src/routes/ProjectView/MapView/MapView.component.tsx @@ -161,7 +161,7 @@ const MapView: React.FC = ({ ) // don't bother with outcome statement tooltips if the zoom level is >= 0.7 // because it displays the full Outcome statement - const outcomeStatementTooltipVisible = hoveredOutcome && zoomLevel < 0.7 + const outcomeStatementTooltipVisible = hoveredOutcome && zoomLevel < 0.5 // don't display the 'collapse/expand' contextmenu item if // the Outcome doesn't have children