Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LG-4121: popover refactor #2520

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/angry-mirrors-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/hooks': minor
---

Add `useMergeRefs` hook for merging array of refs into a single memoized callback ref or `null`
5 changes: 5 additions & 0 deletions .changeset/loud-ears-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/popover': major
---

[LG-4447](https://jira.mongodb.org/browse/LG-4447) Remove unused `contentClassName` prop and abstract popover positioning logic into custom hooks
22 changes: 22 additions & 0 deletions .changeset/loud-items-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@lg-tools/codemods': minor
---

[LG-4525](https://jira.mongodb.org/browse/LG-4525) Add `popover-v12` codemod which can be used to refactor popover component instances. It does the following:
1. Adds an explicit `usePortal={true}` declaration if left undefined and consolidates the `usePortal` and `renderMode` props into a single `renderMode` prop for the following components:
- `Combobox`
- `Menu`
- `Popover`
- `Select`
- `SplitButton`
- `Tooltip`
2. Removes `popoverZIndex`, `portalClassName`, `portalContainer`, `portalRef`, `scrollContainer`, and `usePortal` props from the following components:
- `InfoSprinkle`
- `InlineDefinition`
- `NumberInput`
3. Removes `popoverZIndex`, `portalClassName`, `portalContainer`, `portalRef`, and `scrollContainer` props from the following components:
- `DatePicker`
- `GuideCue`
4. Removes `popoverZIndex`, `portalClassName`, `portalContainer`, `scrollContainer`, and `usePortal` props from `Code` component
5. Removes `portalClassName`, `portalContainer`, `portalRef`, `scrollContainer`, and `usePortal` props from `SearchInput` component
6. Removes `shouldTooltipUsePortal` prop from `Copyable` component
12 changes: 12 additions & 0 deletions .changeset/pink-worms-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@leafygreen-ui/popover': major
'@leafygreen-ui/inline-definition': major
'@leafygreen-ui/info-sprinkle': major
'@leafygreen-ui/date-picker': major
'@leafygreen-ui/copyable': major
'@leafygreen-ui/tooltip': major
'@leafygreen-ui/code': major
'@leafygreen-ui/menu': major
---

[LG-4516](https://jira.mongodb.org/browse/LG-4516): Deprecates justify="fit"; use justify="middle" instead
10 changes: 10 additions & 0 deletions .changeset/popular-steaks-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@leafygreen-ui/code': patch
'@leafygreen-ui/copyable': patch
'@leafygreen-ui/modal': patch
'@leafygreen-ui/popover': patch
---

[LG-4446](https://jira.mongodb.org/browse/LG-4446): In order to consolidate popover-related contexts, the `PortalContext` values for `portalContainer` and `scrollContainer` are consolidated in the `PopoverContext`.

`usePopoverPortalContainer` is replaced by `usePopoverContext` and `PortalContextProvider` is replaced by `PopoverProvider`. There are no functional changes.
29 changes: 29 additions & 0 deletions .changeset/smooth-experts-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@leafygreen-ui/leafygreen-provider': major
---

[LG-4446](https://jira.mongodb.org/browse/LG-4446): In order to consolidate popover-related contexts, the `PortalContext` values for `portalContainer` and `scrollContainer` are consolidated in the `PopoverContext`.

`usePopoverPortalContainer` is replaced by `usePopoverContext` and `PortalContextProvider` is replaced by `PopoverProvider`.

`usePopoverPortalContainer` (used internally) and `PortalContextProvider` are no longer exported. See below migration guidance.

#### Migration guide

##### Old
```js
<PortalContextProvider
popover={{
portalContainer: yourPortalContainer,
scrollContainer: yourScrollContainer,
}}
>
```

##### New
```js
<PopoverProvider
portalContainer={yourPortalContainer}
scrollContainer={yourScrollContainer}
>
```
5 changes: 5 additions & 0 deletions .changeset/tall-chefs-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/popover': minor
---

[LG-4515](https://jira.mongodb.org/browse/LG-4515) Replace internal position utils with [@floating-ui/react](https://floating-ui.com/docs/useFloating)
5 changes: 5 additions & 0 deletions .changeset/tame-islands-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/date-picker': patch
---

Remove unused popover `contentClassName` prop
5 changes: 5 additions & 0 deletions .changeset/two-pants-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/popover': minor
---

Update default value of `spacing` prop from 10px to 4px
13 changes: 13 additions & 0 deletions chat/fixed-chat-window/src/FixedChatWindow.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ const meta: StoryMetaType<typeof FixedChatWindow> = {
parameters: {
default: 'Uncontrolled',
},
decorators: [
StoryFn => (
<div
className={css`
width: 100vw;
height: 100vh;
padding: 0;
`}
>
<StoryFn />
</div>
),
],
};

export default meta;
Expand Down
4 changes: 2 additions & 2 deletions chat/input-bar/src/InputBar/InputBar.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FormEvent, ReactElement } from 'react';
import { TextareaAutosizeProps } from 'react-textarea-autosize';

import { DarkModeProps, HTMLElementProps } from '@leafygreen-ui/lib';
import { PortalControlProps } from '@leafygreen-ui/popover';
import { PopoverRenderModeProps } from '@leafygreen-ui/popover';

export type InputBarProps = HTMLElementProps<'form'> &
DarkModeProps & {
Expand Down Expand Up @@ -48,7 +48,7 @@ export type InputBarProps = HTMLElementProps<'form'> &
/**
* Props passed to the Popover that renders the suggested promps.
*/
dropdownProps?: PortalControlProps;
dropdownProps?: Omit<PopoverRenderModeProps, 'renderMode'>;
};

export type { TextareaAutosizeProps };
2 changes: 0 additions & 2 deletions packages/chip/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ or
baseFontSize={13}
disabled
onDismiss={() => {}}
popoverZIndex={1}
chipCharacterLimit={10}
chipTruncationLocation="end"
dismissButtonAriaLabel="aria-label"
Expand All @@ -49,7 +48,6 @@ or
| `label` | `React.ReactNode` | Label rendered in the chip | |
| `chipTruncationLocation` | `'end'` \| `'middle'` \| `'none'` \| `'start'` | Defines where the ellipses will appear in a Chip when the label length exceeds the `chipCharacterLimit`. If `none` is passed, the chip will not truncate. **Note**: If there is any truncation, the full label text will appear inside a tooltip on hover | `none` |
| `chipCharacterLimit` | `number` | Defines the character limit of a Chip before they start truncating. **Note**: the three ellipses dots are included in the character limit and the chip will only truncate if the chip length is greater than the `chipCharacterLimit`. | |
| `popoverZIndex` | `number` | Number that controls the z-index of the tooltip containing the full label text. | |
| `baseFontSize` | `'13'` \| `'16'` | Determines the base font-size of the chip. | |
| `variant` | `'gray'` \| `'blue'` \| `'green'` \| `'purple'` \| `'red'` \| `'yellow'` \| `'blue'` | The color of the chip. | |
| `glyph` | `React.ReactElement` | An icon glyph rendered before the text. To use a custom icon, see [Link](https://github.com/mongodb/leafygreen-ui/blob/main/packages/icon/README.md#usage-registering-custom-icon-sets) docs | |
Expand Down
1 change: 0 additions & 1 deletion packages/chip/src/Chip/Chip.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,6 @@ describe('packages/chip', () => {
baseFontSize={13}
disabled
onDismiss={() => {}}
popoverZIndex={1}
chipCharacterLimit={10}
chipTruncationLocation="end"
dismissButtonAriaLabel="deselect"
Expand Down
4 changes: 4 additions & 0 deletions packages/chip/src/Chip/Chip.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,7 @@ export const getTextStyles = (
[textDisabledStyles(theme)]: isDisabled,
[textDismissibleStyles]: isDismissible,
});

export const inlineDefinitionStyles = css`
white-space: normal;
`;
5 changes: 2 additions & 3 deletions packages/chip/src/Chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
chipTextClassName,
getTextStyles,
getWrapperStyles,
inlineDefinitionStyles,
} from './Chip.styles';
import { ChipProps, TruncationLocation, Variant } from './Chip.types';

Expand All @@ -28,7 +29,6 @@ export const Chip = React.forwardRef<HTMLSpanElement, ChipProps>(
darkMode: darkModeProp,
label,
onDismiss,
popoverZIndex,
className,
dismissButtonAriaLabel,
glyph,
Expand Down Expand Up @@ -82,8 +82,8 @@ export const Chip = React.forwardRef<HTMLSpanElement, ChipProps>(
darkMode={darkMode}
definition={label}
align="bottom"
popoverZIndex={popoverZIndex}
className={chipInlineDefinitionClassName}
tooltipClassName={inlineDefinitionStyles}
>
{truncatedName}
</InlineDefinition>
Expand Down Expand Up @@ -111,7 +111,6 @@ Chip.propTypes = {
label: PropTypes.string.isRequired,
chipCharacterLimit: PropTypes.number,
chipTruncationLocation: PropTypes.oneOf(Object.values(TruncationLocation)),
popoverZIndex: PropTypes.number,
baseFontSize: PropTypes.oneOf(Object.values(BaseFontSize)),
variant: PropTypes.oneOf(Object.values(Variant)),
onDismiss: PropTypes.func,
Expand Down
5 changes: 0 additions & 5 deletions packages/chip/src/Chip/Chip.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ export interface ChipProps
*/
chipCharacterLimit?: number;

/**
* Number that controls the z-index of the tooltip containing the full label text.
*/
popoverZIndex?: number;

/**
* Determines the base font-size of the component
*
Expand Down
5 changes: 0 additions & 5 deletions packages/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ const SomeComponent = () => <Code>{codeSnippet}</Code>;
| `highlightLines` | `Array<number` \| `[number, number]>` | An optional array of lines to highlight. The array can only contain numbers corresponding to the line numbers to highlight, and / or tuples representing a range (e.g. `[6, 10]`); | |
| `languageOptions` | `Array<LanguageOption>` (see below) | An array of language options. When provided, a LanguageSwitcher dropdown is rendered. | |
| `onChange` | `(language: LanguageOption) => void` | A change handler triggered when the language is changed. Invalid when no `languageOptions` are provided | |
| `usePortal` | `boolean` | Will position the language switcher dropdown relative to its parent without using a Portal if `usePortal` is set to false. | `true` |
| `portalContainer` | `HTMLElement` \| `null` | Sets the container used for the language switcher's dropdown's portal. | |
| `scrollContainer` | `HTMLElement` \| `null` | If the language switcher's dropdown's portal has a scrollable ancestor other than the window, this prop allows passing a reference to that element to allow the portal to position properly. | |
| `portalClassName` | `string` | Passes the given className to the language switcher's dropdown's portal container if the default portal container is being used. | |
| `popoverZIndex` | `number` | Sets the z-index CSS property for the language switcher's dropdown. | |

```
interface LanguageOption {
Expand Down
18 changes: 0 additions & 18 deletions packages/code/src/Code/Code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,6 @@ function Code({
onChange,
customActionButtons = [],
showCustomActionButtons = false,
usePortal = true,
portalClassName,
portalContainer,
scrollContainer,
popoverZIndex,
...rest
}: CodeProps) {
const scrollableElementRef = useRef<HTMLPreElement>(null);
Expand Down Expand Up @@ -205,18 +200,6 @@ function Code({
debounceScroll(e);
};

const popoverProps = {
popoverZIndex,
...(usePortal
? {
usePortal,
portalClassName,
portalContainer,
scrollContainer,
}
: { usePortal }),
} as const;

const showExpandButton = !!(
expandable &&
numOfLinesOfCode &&
Expand Down Expand Up @@ -286,7 +269,6 @@ function Code({
isMultiline={isMultiline}
customActionButtons={filteredCustomActionIconButtons}
showCustomActionButtons={showCustomActionsInPanel}
{...popoverProps}
/>
)}

Expand Down
7 changes: 7 additions & 0 deletions packages/code/src/CopyButton/CopyButton.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { css } from '@leafygreen-ui/emotion';
import { Theme } from '@leafygreen-ui/lib';
import { palette } from '@leafygreen-ui/palette';

export const tooltipStyles = css`
svg {
width: 26px;
height: 26px;
}
`;

export const copiedThemeStyle: Record<Theme, string> = {
[Theme.Light]: css`
color: ${palette.white};
Expand Down
50 changes: 34 additions & 16 deletions packages/code/src/CopyButton/CopyButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,47 @@ import CopyIcon from '@leafygreen-ui/icon/dist/Copy';
import IconButton from '@leafygreen-ui/icon-button';
import {
useDarkMode,
usePopoverPortalContainer,
usePopoverContext,
} from '@leafygreen-ui/leafygreen-provider';
import { keyMap } from '@leafygreen-ui/lib';
import Tooltip, { Align, Justify } from '@leafygreen-ui/tooltip';
import Tooltip, {
Align,
hoverDelay,
Justify,
RenderMode,
} from '@leafygreen-ui/tooltip';

import { COPIED_SUCCESS_DURATION, COPIED_TEXT, COPY_TEXT } from './constants';
import { copiedThemeStyle, copyButtonThemeStyles } from './CopyButton.styles';
import {
copiedThemeStyle,
copyButtonThemeStyles,
tooltipStyles,
} from './CopyButton.styles';
import { CopyProps } from './CopyButton.types';

function CopyButton({ onCopy, contents }: CopyProps) {
const [copied, setCopied] = useState(false);
/**
* `CopyButton` controls `open` state of tooltip because when `copied` state
* `CopyButton` controls `tooltipOpen` state because when `copied` state
* changes, it causes the tooltip to re-render
*/
const [open, setOpen] = useState(false);
const [tooltipOpen, setTooltipOpen] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const { theme } = useDarkMode();
const { portalContainer } = usePopoverPortalContainer();
const { portalContainer } = usePopoverContext();

/**
* toggles `open` state of tooltip
* toggles `tooltipOpen` state
*/
const closeTooltip = () => setOpen(false);
const openTooltip = () => setOpen(true);
const closeTooltip = () => setTooltipOpen(false);
const openTooltip = () => setTooltipOpen(true);

/**
* forcibly closes tooltip if user tabs focus on tooltip and clicks
* outside of the trigger
*/
useBackdropClick(closeTooltip, buttonRef, open);
useBackdropClick(closeTooltip, buttonRef, tooltipOpen);

useEffect(() => {
if (!buttonRef.current) {
Expand Down Expand Up @@ -90,30 +100,38 @@ function CopyButton({ onCopy, contents }: CopyProps) {
};

/**
* `handleMouseEnter` and `handleMouseLeave` are used to control `open`
* `handleMouseEnter` and `handleMouseLeave` are used to control `tooltipOpen`
* state when mouse hovers over tooltip trigger
*/
const handleMouseEnter = () => {
openTooltip();
timeoutRef.current = setTimeout(() => {
openTooltip();
}, hoverDelay);
};

const handleMouseLeave = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
closeTooltip();
};

/**
* `shouldClose` indicates to `Tooltip` component that tooltip should
* remain open even if trigger re-renders
*/
const shouldClose = () => !open;
const shouldClose = () => !tooltipOpen;

return (
<Tooltip
data-testid="code_copy-button_tooltip"
open={open}
setOpen={setOpen}
align={Align.Top}
className={tooltipStyles}
data-testid="code_copy-button_tooltip"
justify={Justify.Middle}
open={tooltipOpen}
renderMode={RenderMode.TopLayer}
setOpen={setTooltipOpen}
trigger={
<IconButton
data-testid="code_copy-button"
Expand Down
Loading
Loading