Skip to content

Commit

Permalink
feat(popover): add autoAlignBoundary for configurable collision bou…
Browse files Browse the repository at this point in the history
…ndary (#16995)

* feat(popover): add autoAlignBoundary for configurable collision boundary

* chore: yarn format

* fix(popover): improve autoAlignBoundary proptype

* fix(popover): ensure popover conforms to popovercomponent

* chore: update snaps
  • Loading branch information
tay1orjones authored Dec 2, 2024
1 parent 140794c commit dec8173
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 1 deletion.
49 changes: 49 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6273,6 +6273,55 @@ Map {
"autoAlign": Object {
"type": "bool",
},
"autoAlignBoundary": Object {
"args": Array [
Array [
Object {
"args": Array [
Array [
"clippingAncestors",
],
],
"type": "oneOf",
},
Object {
"type": "elementType",
},
Object {
"args": Array [
Object {
"type": "elementType",
},
],
"type": "arrayOf",
},
Object {
"args": Array [
Object {
"height": Object {
"isRequired": true,
"type": "number",
},
"width": Object {
"isRequired": true,
"type": "number",
},
"x": Object {
"isRequired": true,
"type": "number",
},
"y": Object {
"isRequired": true,
"type": "number",
},
},
],
"type": "exact",
},
],
],
"type": "oneOfType",
},
"caret": Object {
"type": "bool",
},
Expand Down
130 changes: 130 additions & 0 deletions packages/react/src/components/Popover/Popover.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,136 @@ export const ExperimentalAutoAlign = () => {
);
};

export const ExperimentalAutoAlignBoundary = () => {
const [open, setOpen] = useState(true);
const ref = useRef();
const [boundary, setBoundary] = useState();

useEffect(() => {
ref?.current?.scrollIntoView({ block: 'center', inline: 'center' });
});

return (
<div
style={{
display: 'grid',
placeItems: 'center',
overflow: 'scroll',
width: '800px',
height: '500px',
border: '1px',
borderStyle: 'dashed',
borderColor: 'black',
margin: '0 auto',
}}
ref={setBoundary}>
<div
style={{
width: '2100px',
height: '1px',
placeItems: 'center',
}}
/>
<div style={{ placeItems: 'center', height: '32px', width: '32px' }}>
<Popover
open={open}
align="top"
autoAlign
autoAlignBoundary={boundary}
ref={ref}>
<div className="playground-trigger">
<CheckboxIcon
onClick={() => {
setOpen(!open);
}}
/>
</div>
<PopoverContent className="p-3">
<div>
<p className="popover-title">This popover uses autoAlign</p>
<p className="popover-details">
Scroll the container up, down, left or right to observe how the
popover will automatically change its position in attempt to
stay within the viewport. This works on initial render in
addition to on scroll.
</p>
</div>
</PopoverContent>
</Popover>
<div
style={{
height: '1000px',
width: '1px',
placeItems: 'center',
}}
/>
</div>
</div>
);
};

export const Test = () => {
const [open, setOpen] = useState();
const align = document?.dir === 'rtl' ? 'bottom-right' : 'bottom-left';
const alignTwo = document?.dir === 'rtl' ? 'bottom-left' : 'bottom-right';
return (
<div style={{ display: 'flex', gap: '8rem' }}>
<OverflowMenu
flipped={document?.dir === 'rtl'}
aria-label="overflow-menu">
<OverflowMenuItem itemText="Stop app" />
<OverflowMenuItem itemText="Restart app" />
<OverflowMenuItem itemText="Rename app" />
<OverflowMenuItem itemText="Clone and move app" disabled requireTitle />
<OverflowMenuItem itemText="Edit routes and access" requireTitle />
<OverflowMenuItem hasDivider isDelete itemText="Delete app" />
</OverflowMenu>

<Popover
align={align}
open={open}
onKeyDown={(evt) => {
if (match(evt, keys.Escape)) {
setOpen(false);
}
}}
isTabTip
onRequestClose={() => setOpen(false)}>
<button
aria-label="Settings"
type="button"
aria-expanded={open}
onClick={() => {
setOpen(!open);
}}>
<Settings />
</button>
<PopoverContent className="p-3">
<RadioButtonGroup
style={{ alignItems: 'flex-start', flexDirection: 'column' }}
legendText="Row height"
name="radio-button-group"
defaultSelected="small">
<RadioButton labelText="Small" value="small" id="radio-small" />
<RadioButton labelText="Large" value="large" id="radio-large" />
</RadioButtonGroup>
<hr />
<fieldset className={`cds--fieldset`}>
<legend className={`cds--label`}>Edit columns</legend>
<Checkbox defaultChecked labelText="Name" id="checkbox-label-1" />
<Checkbox defaultChecked labelText="Type" id="checkbox-label-2" />
<Checkbox
defaultChecked
labelText="Location"
id="checkbox-label-3"
/>
</fieldset>
</PopoverContent>
</Popover>
</div>
);
};

export const TabTipExperimentalAutoAlign = () => {
const [open, setOpen] = useState(true);
const ref = useRef();
Expand Down
25 changes: 24 additions & 1 deletion packages/react/src/components/Popover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
autoUpdate,
arrow,
offset,
type Boundary,
} from '@floating-ui/react';
import { hide } from '@floating-ui/dom';
import { useFeatureFlag } from '../FeatureFlags';
Expand Down Expand Up @@ -102,6 +103,11 @@ interface PopoverBaseProps {
*/
autoAlign?: boolean;

/**
* Specify a bounding element to be used for autoAlign calculations. The viewport is used by default. This prop is currently experimental and is subject to future changes.
*/
autoAlignBoundary?: Boundary;

/**
* Specify whether a caret should be rendered
*/
Expand Down Expand Up @@ -165,6 +171,7 @@ export const Popover: PopoverComponent = React.forwardRef(
align: initialAlign = isTabTip ? 'bottom-start' : 'bottom',
as: BaseComponent = 'span' as E,
autoAlign = false,
autoAlignBoundary,
caret = isTabTip ? false : true,
className: customClassName,
children,
Expand Down Expand Up @@ -292,6 +299,7 @@ export const Popover: PopoverComponent = React.forwardRef(

fallbackStrategy: 'initialPlacement',
fallbackAxisSideDirection: 'start',
boundary: autoAlignBoundary,
}),
arrow({
element: caretRef,
Expand Down Expand Up @@ -476,7 +484,7 @@ export const Popover: PopoverComponent = React.forwardRef(
</PopoverContext.Provider>
);
}
);
) as PopoverComponent;

// Note: this displayName is temporarily set so that Storybook ArgTable
// correctly displays the name of this component
Expand Down Expand Up @@ -546,6 +554,21 @@ Popover.propTypes = {
*/
autoAlign: PropTypes.bool,

/**
* Specify a bounding element to be used for autoAlign calculations. The viewport is used by default. This prop is currently experimental and is subject to future changes.
*/
autoAlignBoundary: PropTypes.oneOfType([
PropTypes.oneOf(['clippingAncestors']),
PropTypes.elementType,
PropTypes.arrayOf(PropTypes.elementType),
PropTypes.exact({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
}),
]) as PropTypes.Validator<Boundary | null | undefined>,

/**
* Specify whether a caret should be rendered
*/
Expand Down

0 comments on commit dec8173

Please sign in to comment.