mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
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:
@@ -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`
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user