Partially reverts popper component [no gbp] (#80930)

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request
As requested by @mothblocks I implemented react-popper instead.

Perks:
1. Seemingly handles the positioning bug that I wrestled with in #80883.
2. Performance seems just as good.
3. Should fix issue presented in
[NovaSector#434](https://github.com/NovaSector/NovaSector/issues/434)

Cons: 
1. Tiny amount more code

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game
Bug fixes
Mothblocks asked for it
Fixes #80929
<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and it's effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
fix: Poppers (like the prefs menu options) shouldn't be hidden beneath
the character preview anymore.
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->
This commit is contained in:
Jeremiah
2024-01-15 07:05:22 +01:00
committed by GitHub
parent 88a1b72b66
commit e6c791bd2c
10 changed files with 214 additions and 50 deletions
+8 -1
View File
@@ -735,7 +735,14 @@ to fine tune the value, or single click it to manually type a number.
### `Popper`
Popper lets you position elements so that they don't go out of the bounds of the window. See [react-tiny-popover](https://github.com/alexkatz/react-tiny-popover) for more information.
Popper lets you position elements so that they don't go out of the bounds of the window. See [popper.js](https://popper.js.org/) for more information.
**Props:**
- `content: ReactNode` - The content that will be put inside the popper.
- `isOpen: boolean` - Whether or not the popper is open.
- `onClickOutside?: (e) => void` - A function that will be called when the user clicks outside of the popper.
- `placement?: string` - The placement of the popper. See [https://popper.js.org/docs/v2/constructors/#placement]
### `ProgressBar`
+6 -22
View File
@@ -1,10 +1,10 @@
import { classes } from 'common/react';
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { Popover } from 'react-tiny-popover';
import { BoxProps, unit } from './Box';
import { Button } from './Button';
import { Icon } from './Icon';
import { Popper } from './Popper';
type DropdownEntry = {
displayText: ReactNode;
@@ -74,8 +74,7 @@ export function Dropdown(props: Props) {
width,
} = props;
const [opacity, setOpacity] = useState(0);
const [open, setOpen] = useState(true);
const [open, setOpen] = useState(false);
const adjustedOpen = over ? !open : open;
const innerRef = useRef<HTMLDivElement>(null);
@@ -108,21 +107,6 @@ export function Dropdown(props: Props) {
[disabled, onSelected, options, selected],
);
/**
* HACK: Just like the original dropdown,
* the menu does not propagate correctly on the first event.
* This tricks it into getting the parent component's position
* so there is no flickering on open.
*/
useEffect(() => {
const timer = setTimeout(() => {
setOpen(false);
setOpacity(1);
}, 1);
return () => clearTimeout(timer);
}, []);
/** Allows the menu to be scrollable on open */
useEffect(() => {
if (!open) return;
@@ -131,14 +115,14 @@ export function Dropdown(props: Props) {
}, [open]);
return (
<Popover
<Popper
isOpen={open}
onClickOutside={() => setOpen(false)}
positions={over ? 'top' : 'bottom'}
placement={over ? 'top-start' : 'bottom-start'}
content={
<div
className="Layout Dropdown__menu"
style={{ minWidth: menuWidth, opacity }}
style={{ minWidth: menuWidth }}
ref={innerRef}
>
{options.length === 0 && (
@@ -231,6 +215,6 @@ export function Dropdown(props: Props) {
)}
</div>
</div>
</Popover>
</Popper>
);
}
+96
View File
@@ -0,0 +1,96 @@
import { Placement } from '@popperjs/core';
import {
PropsWithChildren,
ReactNode,
useEffect,
useRef,
useState,
} from 'react';
import { usePopper } from 'react-popper';
type RequiredProps = {
/** The content to display in the popper */
content: ReactNode;
/** Whether the popper is open */
isOpen: boolean;
};
type OptionalProps = Partial<{
/** Called when the user clicks outside the popper */
onClickOutside: () => void;
/** Where to place the popper relative to the reference element */
placement: Placement;
}>;
type Props = RequiredProps & OptionalProps;
/**
* ## Popper
* Popper lets you position elements so that they don't go out of the bounds of the window.
* @url https://popper.js.org/react-popper/ for more information.
*/
export function Popper(props: PropsWithChildren<Props>) {
const { children, content, isOpen, onClickOutside, placement } = props;
const [referenceElement, setReferenceElement] =
useState<HTMLDivElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null,
);
// One would imagine we could just use useref here, but it's against react-popper documentation and causes a positioning bug
// We still need them to call focus and clickoutside events :(
const popperRef = useRef<HTMLDivElement | null>(null);
const parentRef = useRef<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement,
});
/** Close the popper when the user clicks outside */
function handleClickOutside(event: MouseEvent) {
if (
!popperRef.current?.contains(event.target as Node) &&
!parentRef.current?.contains(event.target as Node)
) {
onClickOutside?.();
}
}
useEffect(() => {
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
return (
<>
<div
ref={(node) => {
setReferenceElement(node);
parentRef.current = node;
}}
>
{children}
</div>
{isOpen && (
<div
ref={(node) => {
setPopperElement(node);
popperRef.current = node;
}}
style={{ ...styles.popper, zIndex: 5 }}
{...attributes.popper}
>
{content}
</div>
)}
</>
);
}
+1
View File
@@ -34,6 +34,7 @@ export { MenuBar } from './MenuBar';
export { Modal } from './Modal';
export { NoticeBox } from './NoticeBox';
export { NumberInput } from './NumberInput';
export { Popper } from './Popper';
export { ProgressBar } from './ProgressBar';
export { RestrictedInput } from './RestrictedInput';
export { RoundGauge } from './RoundGauge';
@@ -1,7 +1,6 @@
import { filterMap, sortBy } from 'common/collections';
import { classes } from 'common/react';
import { useState } from 'react';
import { Popover } from 'react-tiny-popover';
import { sendAct, useBackend } from '../../backend';
import {
@@ -10,6 +9,7 @@ import {
Button,
Flex,
LabeledList,
Popper,
Stack,
} from '../../components';
import { CharacterPreview } from '../common/CharacterPreview';
@@ -193,10 +193,10 @@ const GenderButton = (props: {
const [genderMenuOpen, setGenderMenuOpen] = useState(false);
return (
<Popover
<Popper
isOpen={genderMenuOpen}
onClickOutside={() => setGenderMenuOpen(false)}
positions="right"
placement="right-end"
content={
<Stack backgroundColor="white" ml={0.5} p={0.3}>
{[Gender.Male, Gender.Female, Gender.Other, Gender.Other2].map(
@@ -230,7 +230,7 @@ const GenderButton = (props: {
tooltip="Gender"
tooltipPosition="top"
/>
</Popover>
</Popper>
);
};
@@ -263,10 +263,10 @@ const MainFeature = (props: {
const supplementalFeature = catalog.supplemental_feature;
return (
<Popover
positions="bottom"
onClickOutside={() => handleClose()}
<Popper
placement="bottom-start"
isOpen={isOpen}
onClickOutside={handleClose}
content={
<ChoicedSelection
name={catalog.name}
@@ -335,7 +335,7 @@ const MainFeature = (props: {
/>
)}
</Button>
</Popover>
</Popper>
);
};
@@ -1,9 +1,8 @@
import { filterMap } from 'common/collections';
import { useState } from 'react';
import { Popover } from 'react-tiny-popover';
import { useBackend } from '../../backend';
import { Box, Button, Icon, Stack, Tooltip } from '../../components';
import { Box, Button, Icon, Popper, Stack, Tooltip } from '../../components';
import { PreferencesMenuData, Quirk, RandomSetting, ServerData } from './data';
import { getRandomization, PreferenceList } from './MainPage';
import { ServerPreferencesFetcher } from './ServerPreferencesFetcher';
@@ -212,8 +211,8 @@ function QuirkPopper(props: QuirkPopperProps) {
Object.entries(customization_options).length > 0;
return (
<Popover
positions="bottom"
<Popper
placement="bottom-end"
onClickOutside={() => setCustomizationExpanded(false)}
isOpen={customizationExpanded}
content={
@@ -274,7 +273,7 @@ function QuirkPopper(props: QuirkPopperProps) {
/>
)}
</div>
</Popover>
</Popper>
);
}
@@ -5,10 +5,9 @@
*/
import { decodeHtmlEntities } from 'common/string';
import { useState } from 'react';
import { Popover } from 'react-tiny-popover';
import { useBackend, useLocalState } from '../backend';
import { Button, Input, Section, Table } from '../components';
import { Button, Input, Popper, Section, Table } from '../components';
import { Window } from '../layouts';
export const RequestManager = (props) => {
@@ -140,8 +139,8 @@ const FilterPanel = (props) => {
);
return (
<Popover
positions="bottom"
<Popper
placement="bottom-end"
content={
<div
className="RequestManager__filterPanel"
@@ -178,6 +177,6 @@ const FilterPanel = (props) => {
Type Filter
</Button>
</div>
</Popover>
</Popper>
);
};
+1 -1
View File
@@ -15,7 +15,7 @@
"marked": "^4.2.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-tiny-popover": "^8.0.4",
"react-popper": "^2.3.0",
"tgui-dev-server": "workspace:*",
"tgui-polyfill": "workspace:*"
}
@@ -0,0 +1,58 @@
import { Box, Popper } from '../components';
export const meta = {
title: 'Popper',
render: () => <Story />,
};
const Story = () => {
return (
<>
<Popper
isOpen
content={
<Box
style={{
background: 'white',
border: '2px solid blue',
}}
>
Loogatme!
</Box>
}
placement="bottom"
>
<Box
style={{
border: '5px solid white',
height: '300px',
width: '200px',
}}
/>
</Popper>
<Popper
isOpen
content={
<Box
style={{
background: 'white',
border: '2px solid blue',
}}
>
I am on the right!
</Box>
}
placement="right"
>
<Box
style={{
border: '5px solid white',
height: '500px',
width: '100px',
}}
/>
</Popper>
</>
);
};
+28 -8
View File
@@ -6632,7 +6632,7 @@ __metadata:
languageName: node
linkType: hard
"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0":
version: 1.4.0
resolution: "loose-envify@npm:1.4.0"
dependencies:
@@ -7762,6 +7762,13 @@ __metadata:
languageName: node
linkType: hard
"react-fast-compare@npm:^3.0.1":
version: 3.2.2
resolution: "react-fast-compare@npm:3.2.2"
checksum: 2071415b4f76a3e6b55c84611c4d24dcb12ffc85811a2840b5a3f1ff2d1a99be1020d9437ee7c6e024c9f4cbb84ceb35e48cf84f28fcb00265ad2dfdd3947704
languageName: node
linkType: hard
"react-is@npm:^16.13.1":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@@ -7776,13 +7783,17 @@ __metadata:
languageName: node
linkType: hard
"react-tiny-popover@npm:^8.0.4":
version: 8.0.4
resolution: "react-tiny-popover@npm:8.0.4"
"react-popper@npm:^2.3.0":
version: 2.3.0
resolution: "react-popper@npm:2.3.0"
dependencies:
react-fast-compare: ^3.0.1
warning: ^4.0.2
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: 1c7bcd0227e616326240cc97f112def0cd6e6edc94a56f763b20e82bf9ef1efdcc3e9bfff4bc9417a16e948aa3f2cfb960504135760b8f9e55fc73851d177bc3
"@popperjs/core": ^2.0.0
react: ^16.8.0 || ^17 || ^18
react-dom: ^16.8.0 || ^17 || ^18
checksum: 837111c98738011c69b3069a464ea5bdcbf487105b6148e8faf90cb7337e134edb1b98b8824322941c378756cca30a15c18c25f558e53b85ed5762fa0dc8e6b2
languageName: node
linkType: hard
@@ -9162,7 +9173,7 @@ resolve@^1.20.0:
marked: ^4.2.12
react: ^18.2.0
react-dom: ^18.2.0
react-tiny-popover: ^8.0.4
react-popper: ^2.3.0
tgui-dev-server: "workspace:*"
tgui-polyfill: "workspace:*"
languageName: unknown
@@ -9673,6 +9684,15 @@ resolve@^1.20.0:
languageName: node
linkType: hard
"warning@npm:^4.0.2":
version: 4.0.3
resolution: "warning@npm:4.0.3"
dependencies:
loose-envify: ^1.0.0
checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c
languageName: node
linkType: hard
"watchpack@npm:^2.4.0":
version: 2.4.0
resolution: "watchpack@npm:2.4.0"