Skip to content

Commit

Permalink
Merge pull request #1194 from jetstreamapp/bug/fix-popover-focus
Browse files Browse the repository at this point in the history
Fix popover text selection
  • Loading branch information
paustint authored Feb 20, 2025
2 parents cad3681 + 04a764e commit b634d12
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 137 deletions.
83 changes: 37 additions & 46 deletions apps/landing/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Dialog, Transition } from '@headlessui/react';
import { Bars3Icon } from '@heroicons/react/24/outline';
import { Dialog, DialogBackdrop, TransitionChild } from '@headlessui/react';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { Fragment } from 'react';

export interface ModalProps {
Expand All @@ -18,51 +18,42 @@ export const Modal = ({
onClose,
}: ModalProps) => {
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="fixed z-10 inset-0 overflow-y-auto" onClose={() => onClose()}>
<div className={className}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>

{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className={bodyClassName}>
<div className="hidden sm:block absolute top-0 right-0 pt-2 pr-2">
<button
type="button"
className="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
onClick={() => onClose()}
>
<span className="sr-only">Close</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
{children}
<Dialog as="div" className="fixed z-10 inset-0 overflow-y-auto" open={isOpen} onClose={() => onClose()}>
<DialogBackdrop
transition
className="fixed inset-0 bg-gray-500/75 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in"
onClick={() => onClose()}
/>
<div className={className}>
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<TransitionChild
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className={bodyClassName}>
<div className="hidden sm:block absolute top-0 right-0 pt-2 pr-2">
<button
type="button"
className="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
onClick={() => onClose()}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
{children}
</div>
</TransitionChild>
</div>
</Dialog>
);
};

Expand Down
10 changes: 3 additions & 7 deletions apps/landing/pages/pricing/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RadioGroup } from '@headlessui/react';
import { Radio, RadioGroup } from '@headlessui/react';
import { CheckIcon } from '@heroicons/react/20/solid';
import classNames from 'classnames';
import Link from 'next/link';
Expand Down Expand Up @@ -91,13 +91,9 @@ export default function Page() {
className="grid grid-cols-2 gap-x-1 rounded-full bg-white/5 p-1 text-center text-xs/5 font-semibold text-white"
>
{frequencies.map((option) => (
<RadioGroup.Option
key={option.value}
value={option}
className="cursor-pointer rounded-full px-2.5 py-1 aria-checked:bg-cyan-500"
>
<Radio key={option.value} value={option} className="cursor-pointer rounded-full px-2.5 py-1 aria-checked:bg-cyan-500">
{option.label}
</RadioGroup.Option>
</Radio>
))}
</RadioGroup>
</fieldset>
Expand Down
6 changes: 3 additions & 3 deletions libs/ui/src/lib/data-table/DataTableEditors.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { FocusTrap } from '@headlessui/react';
import { logger } from '@jetstream/shared/client-logger';
import { SFDC_BLANK_PICKLIST_VALUE } from '@jetstream/shared/constants';
import { describeSObject, query } from '@jetstream/shared/data';
import { isEnterKey, isEscapeKey } from '@jetstream/shared/ui-utils';
import { ListItem, SalesforceOrgUi } from '@jetstream/types';
import { FocusScope } from '@react-aria/focus';
import { formatISO } from 'date-fns/formatISO';
import { parseISO } from 'date-fns/parseISO';
import isNil from 'lodash/isNil';
Expand Down Expand Up @@ -71,9 +71,9 @@ function DataTableEditorPopover({
}}
>
{referenceElement && (
<FocusTrap>
<FocusScope contain restoreFocus autoFocus>
<div className="slds-p-around_x-small">{children}</div>
</FocusTrap>
</FocusScope>
)}
</PopoverContainer>
</OutsideClickHandler>
Expand Down
2 changes: 1 addition & 1 deletion libs/ui/src/lib/data-table/DataTableRenderers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export const HeaderFilter = memo(({ columnKey, filters, filterSetValues, portalR
>
<Icon
type="utility"
icon="filter"
icon="filterList"
className={classNames('slds-button__icon slds-icon_x-small', {
'slds-text-color_brand': active,
})}
Expand Down
6 changes: 3 additions & 3 deletions libs/ui/src/lib/form/context-menu/ContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import { css } from '@emotion/react';
import { FocusTrap } from '@headlessui/react';
import { IconName, IconType } from '@jetstream/icon-factory';
import {
KeyBuffer,
Expand All @@ -13,6 +12,7 @@ import {
selectMenuItemFromKeyboard,
} from '@jetstream/shared/ui-utils';
import { ContextMenuItem } from '@jetstream/types';
import { FocusScope } from '@react-aria/focus';
import isNumber from 'lodash/isNumber';
import isString from 'lodash/isString';
import React, { Fragment, FunctionComponent, KeyboardEvent, RefObject, createRef, useEffect, useRef, useState } from 'react';
Expand Down Expand Up @@ -144,7 +144,7 @@ export const ContextMenu: FunctionComponent<ContextMenuProps> = ({ parentElement

return (
<OutsideClickHandler onOutsideClick={() => onClose()}>
<FocusTrap>
<FocusScope contain restoreFocus autoFocus>
<div
ref={setPopperElement}
// Selectively picked from `slds-dropdown slds-dropdown_small`
Expand Down Expand Up @@ -203,7 +203,7 @@ export const ContextMenu: FunctionComponent<ContextMenuProps> = ({ parentElement
))}
</ul>
</div>
</FocusTrap>
</FocusScope>
</OutsideClickHandler>
);
};
111 changes: 56 additions & 55 deletions libs/ui/src/lib/popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { css, SerializedStyles } from '@emotion/react';
import { FocusTrap, Popover as HeadlessPopover } from '@headlessui/react';
import { Popover as HeadlessPopover, PopoverButton, PopoverPanel } from '@headlessui/react';
import { FullWidth, sizeXLarge, SmallMediumLarge } from '@jetstream/types';
import classNames from 'classnames';
import { CSSProperties, forwardRef, Fragment, MouseEvent, ReactNode, useCallback, useImperativeHandle, useRef, useState } from 'react';
Expand Down Expand Up @@ -128,7 +128,7 @@ export const Popover = forwardRef<PopoverRef, PopoverProps>(
<Fragment>
{open && (
<ConditionalPortal omitPortal={omitPortal} portalRef={portalRef}>
<HeadlessPopover.Panel
<PopoverPanel
ref={setPopperElement as any}
data-testid={testId}
style={{ ...styles.popper, ...panelStyle }}
Expand Down Expand Up @@ -194,67 +194,68 @@ export const Popover = forwardRef<PopoverRef, PopoverProps>(
`}
{...panelProps}
>
<FocusTrap features={FocusTrap.features.All}>
{/* CLOSE BUTTON */}
<HeadlessPopover.Button
ref={setCloseElement}
className={classNames(
'slds-button slds-button_icon slds-button_icon-small slds-float_right slds-popover__close',
{
'slds-button_icon-inverse': inverseIcons,
},
closeBtnClassName
)}
title="Close dialog"
>
<Icon type="utility" icon="close" className="slds-button__icon" omitContainer />
<span className="slds-assistive-text">Close dialog</span>
</HeadlessPopover.Button>
{/* CONTENT */}
{header}
<div css={bodyStyle} className={bodyClassName}>
{content}
</div>
{footer}
{/* ARROW */}
<div
css={css`
{/* FIXME: adding any type of focus lock/trap etc.. causes all selection in the popover to stop working */}
{/* <FocusScope contain restoreFocus autoFocus> */}
{/* CLOSE BUTTON */}
<PopoverButton
ref={setCloseElement}
className={classNames(
'slds-button slds-button_icon slds-button_icon-small slds-float_right slds-popover__close',
{
'slds-button_icon-inverse': inverseIcons,
},
closeBtnClassName
)}
title="Close dialog"
>
<Icon type="utility" icon="close" className="slds-button__icon" omitContainer />
<span className="slds-assistive-text">Close dialog</span>
</PopoverButton>
{/* CONTENT */}
{header}
<div css={bodyStyle} className={bodyClassName}>
{content}
</div>
{footer}
{/* ARROW */}
<div
css={css`
position: absolute;
width: 1rem;
height: 1rem;
background: inherit;
visibility: hidden;
&::before {
visibility: visible;
content: '';
transform: rotate(45deg);
position: absolute;
width: 1rem;
height: 1rem;
background: inherit;
visibility: hidden;
&::before {
visibility: visible;
content: '';
transform: rotate(45deg);
position: absolute;
width: 1rem;
height: 1rem;
background: inherit;
}
&::after {
visibility: visible;
content: '';
transform: rotate(45deg);
position: absolute;
width: 1rem;
height: 1rem;
/* background-color: inherit; */
}
`}
className="popover-arrow"
ref={setArrowElement}
style={styles.arrow}
></div>
</FocusTrap>
</HeadlessPopover.Panel>
}
&::after {
visibility: visible;
content: '';
transform: rotate(45deg);
position: absolute;
width: 1rem;
height: 1rem;
/* background-color: inherit; */
}
`}
className="popover-arrow"
ref={setArrowElement}
style={styles.arrow}
></div>
{/* </FocusScope> */}
</PopoverPanel>
</ConditionalPortal>
)}

<HeadlessPopover.Button ref={setReferenceElement} {...(buttonProps as typeof HeadlessPopover.Button)} style={buttonStyle}>
<PopoverButton ref={setReferenceElement} {...(buttonProps as typeof PopoverButton)} style={buttonStyle}>
{children}
</HeadlessPopover.Button>
</PopoverButton>
</Fragment>
);
}}
Expand Down
12 changes: 3 additions & 9 deletions libs/ui/src/lib/sobject-field-list/SobjectFieldListFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,7 @@ export const SobjectFieldListFilter: FunctionComponent<SobjectFieldListFilterPro
title: 'open filters menu',
}}
>
<Icon
type="utility"
icon="filter"
description="Open filters menu"
className="slds-button__icon slds-button__icon_large"
omitContainer
/>
<Icon type="utility" icon="filterList" description="Open filters menu" className="slds-button__icon" omitContainer />
{!!filterSelectedCount && (
<div
title="Reset all filters"
Expand All @@ -182,8 +176,8 @@ export const SobjectFieldListFilter: FunctionComponent<SobjectFieldListFilterPro
top: -0.8rem;
right: -0.5rem;
border-radius: 50%;
width: 1.25rem;
height: 1.25rem;
width: 1rem;
height: 1rem;
display: flex;
align-items: center;
justify-content: center;
Expand Down
8 changes: 4 additions & 4 deletions libs/ui/src/lib/sobject-list/SobjectListFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ export const SobjectFieldListFilter: FunctionComponent<SobjectFieldListFilterPro
>
<Icon
type="utility"
icon="filter"
icon="filterList"
description="Open filters menu"
className="slds-button__icon slds-button__icon_large"
className="slds-button__icon slds-button__icon_medium"
omitContainer
/>
{!!hasFiltersApplied && (
Expand All @@ -233,8 +233,8 @@ export const SobjectFieldListFilter: FunctionComponent<SobjectFieldListFilterPro
css={css`
position: absolute;
background-color: #ba0517;
top: -0.125rem;
right: -0.5rem;
top: -0.4rem;
right: -0.4rem;
border-radius: 50%;
width: 0.75rem;
height: 0.75rem;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@
"@express-rate-limit/cluster-memory-store": "^0.3.0",
"@fullhuman/postcss-purgecss": "^2.2.0",
"@grpc/grpc-js": "^1.10.6",
"@headlessui/react": "^1.7.19",
"@headlessui/react": "^2.2.0",
"@heroicons/react": "^2.2.0",
"@hookform/resolvers": "^3.9.0",
"@jetstreamapp/soql-parser-js": "^6.1.0",
Expand Down
Loading

0 comments on commit b634d12

Please sign in to comment.