Skip to content

Commit

Permalink
Merge branch 'main' into rohan/layered-dropdown-menu
Browse files Browse the repository at this point in the history
  • Loading branch information
r100-stack committed Aug 19, 2024
2 parents e93d3c0 + c977e9f commit c60c296
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 75 deletions.
7 changes: 7 additions & 0 deletions .changeset/young-mayflies-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@itwin/itwinui-react': minor
---

`Popover` now enables the [`size` middleware](https://floating-ui.com/docs/size) to prevent it from overflowing the viewport.
- This also affects other popover-like components (e.g. `Select`, `ComboBox`, `DropdownMenu`).
- `Popover` now also sets a default max-height of `400px` to prevent it from becoming too large. This can be customized using the `middleware.size.maxHeight` prop.
212 changes: 145 additions & 67 deletions apps/css-workshop/src/pages/menu.astro

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/website/src/content/docs/popover.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Popover handles positioning using an external library called [Floating UI](float
There are some advanced positioning options available.

- The `positionReference` prop can be used to position the popover relative to a different element than the trigger. This can be useful, for example, when the trigger is part of a larger group of elements that should all be considered together.
- The `middleware` prop exposes more granular control over the positioning logic. By default, `Popover` enables the [`flip`](https://floating-ui.com/docs/flip) and [`shift`](https://floating-ui.com/docs/shift) middlewares. You might also find the [`offset`](https://floating-ui.com/docs/offset) middleware useful for adding a gap between the trigger and the popover.
- The `middleware` prop exposes more granular control over the positioning logic. By default, `Popover` enables the [`flip`](https://floating-ui.com/docs/flip), [`shift`](https://floating-ui.com/docs/shift) and [`size`](https://floating-ui.com/docs/size) middlewares. You might also find the [`offset`](https://floating-ui.com/docs/offset) middleware useful for adding a gap between the trigger and the popover.

### Portals

Expand Down
2 changes: 1 addition & 1 deletion packages/itwinui-css/src/menu/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ $iui-active-outline: 1px solid var(--iui-color-border-accent);
margin-block-start: -1px;
}

&:where([data-iui-actionable='true'], .iui-menu-item):where(:hover),
&:where([data-iui-actionable='true']:hover),
&:where(:has(.iui-link-action):hover) {
cursor: pointer;
background-color: var(--iui-color-background-hover);
Expand Down
1 change: 1 addition & 0 deletions packages/itwinui-react/src/core/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ export const ComboBox = React.forwardRef(
visible: isOpen,
onVisibleChange: (open) => (open ? show() : hide()),
matchWidth: true,
middleware: { size: { maxHeight: 'var(--iui-menu-max-height)' } },
closeOnOutsideClick: true,
interactions: { click: false, focus: true },
});
Expand Down
4 changes: 4 additions & 0 deletions packages/itwinui-react/src/core/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ export const Menu = React.forwardRef((props, ref) => {
...restInteractionsProps,
},
...restPopoverProps,
middleware: {
size: { maxHeight: 'var(--iui-menu-max-height)' },
...restPopoverProps.middleware,
},
});

const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions(
Expand Down
43 changes: 37 additions & 6 deletions packages/itwinui-react/src/core/Popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,23 @@ type PopoverOptions = {
/**
* Middleware options.
*
* By default, `flip` and `shift` are enabled.
* By default, `flip`, `shift` and `size` are enabled.
*
* @see https://floating-ui.com/docs/middleware
*/
middleware?: {
offset?: number;
flip?: boolean;
shift?: boolean;
size?:
| boolean
| {
/**
* Override the maximum height of the popover. Must be a CSS-compatible value.
* @default "400px"
*/
maxHeight?: string;
};
autoPlacement?: boolean;
hide?: boolean;
inline?: boolean;
Expand Down Expand Up @@ -176,10 +185,13 @@ export const usePopover = (options: PopoverOptions & PopoverInternalProps) => {
const tree = useFloatingTree();

const middleware = React.useMemo(
() => ({ flip: true, shift: true, ...options.middleware }),
() => ({ flip: true, shift: true, size: true, ...options.middleware }),
[options.middleware],
);

const maxHeight =
typeof middleware.size === 'boolean' ? '400px' : middleware.size?.maxHeight;

const [open, onOpenChange] = useControlledState(
false,
visible,
Expand All @@ -204,11 +216,17 @@ export const usePopover = (options: PopoverOptions & PopoverInternalProps) => {
middleware.offset !== undefined && offset(middleware.offset),
middleware.flip && flip({ padding: 4 }),
middleware.shift && shift({ padding: 4 }),
matchWidth &&
(matchWidth || middleware.size) &&
size({
padding: 4,
apply: ({ rects }) => {
setReferenceWidth(rects.reference.width);
apply: ({ rects, availableHeight }) => {
if (middleware.size) {
setAvailableHeight(Math.round(availableHeight));
}

if (matchWidth) {
setReferenceWidth(rects.reference.width);
}
},
} as SizeOptions),
middleware.autoPlacement && autoPlacement({ padding: 4 }),
Expand Down Expand Up @@ -251,13 +269,18 @@ export const usePopover = (options: PopoverOptions & PopoverInternalProps) => {
]);

const [referenceWidth, setReferenceWidth] = React.useState<number>();
const [availableHeight, setAvailableHeight] = React.useState<number>();

const getFloatingProps = React.useCallback(
(userProps?: React.HTMLProps<HTMLElement>) =>
interactions.getFloatingProps({
...userProps,
style: {
...floating.floatingStyles,
...(middleware.size &&
availableHeight && {
maxBlockSize: `min(${availableHeight}px, ${maxHeight})`,
}),
zIndex: 9999,
...(matchWidth && referenceWidth
? {
Expand All @@ -268,7 +291,15 @@ export const usePopover = (options: PopoverOptions & PopoverInternalProps) => {
...userProps?.style,
},
}),
[floating.floatingStyles, interactions, matchWidth, referenceWidth],
[
floating.floatingStyles,
interactions,
matchWidth,
referenceWidth,
middleware.size,
availableHeight,
maxHeight,
],
);

const getReferenceProps = React.useCallback(
Expand Down
1 change: 1 addition & 0 deletions packages/itwinui-react/src/core/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ const CustomSelect = React.forwardRef((props, forwardedRef) => {
visible: isOpen,
matchWidth: true,
closeOnOutsideClick: true,
middleware: { size: { maxHeight: 'var(--iui-menu-max-height)' } },
...popoverProps,
onVisibleChange: (open) => (open ? show() : hide()),
});
Expand Down

0 comments on commit c60c296

Please sign in to comment.