Add tgui-core to package.json and merk all unused components

This commit is contained in:
ShadowLarkens
2024-09-27 22:23:49 -07:00
parent f88820729f
commit 4872b56b16
25 changed files with 13 additions and 1396 deletions
@@ -24,7 +24,6 @@
@include meta.load-css('~tgui/styles/atomic/text.scss');
// Components
@include meta.load-css('~tgui/styles/components/BlockQuote.scss');
@include meta.load-css('~tgui/styles/components/Button.scss');
@include meta.load-css('~tgui/styles/components/ColorBox.scss');
@include meta.load-css('~tgui/styles/components/Dimmer.scss');
-69
View File
@@ -1,69 +0,0 @@
import { Component } from 'react';
const DEFAULT_BLINKING_INTERVAL = 1000;
const DEFAULT_BLINKING_TIME = 1000;
export class Blink extends Component {
constructor(props) {
super(props);
this.state = {
hidden: false,
};
}
createTimer() {
const {
interval = DEFAULT_BLINKING_INTERVAL,
time = DEFAULT_BLINKING_TIME,
} = this.props;
clearInterval(this.interval);
clearTimeout(this.timer);
this.setState({
hidden: false,
});
this.interval = setInterval(() => {
this.setState({
hidden: true,
});
this.timer = setTimeout(() => {
this.setState({
hidden: false,
});
}, time);
}, interval + time);
}
componentDidMount() {
this.createTimer();
}
componentDidUpdate(prevProps) {
if (
prevProps.interval !== this.props.interval ||
prevProps.time !== this.props.time
) {
this.createTimer();
}
}
componentWillUnmount() {
clearInterval(this.interval);
clearTimeout(this.timer);
}
render() {
return (
<span
style={{
visibility: this.state.hidden ? 'hidden' : 'visible',
}}
>
{this.props.children}
</span>
);
}
}
@@ -1,15 +0,0 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { classes } from 'common/react';
import { Box, BoxProps } from './Box';
export function BlockQuote(props: BoxProps) {
const { className, ...rest } = props;
return <Box className={classes(['BlockQuote', className])} {...rest} />;
}
-72
View File
@@ -1,72 +0,0 @@
import { ReactNode, useEffect, useState } from 'react';
import { resolveAsset } from '../assets';
import { fetchRetry } from '../http';
import { BoxProps } from './Box';
import { Image } from './Image';
enum Direction {
NORTH = 1,
SOUTH = 2,
EAST = 4,
WEST = 8,
NORTHEAST = NORTH | EAST,
NORTHWEST = NORTH | WEST,
SOUTHEAST = SOUTH | EAST,
SOUTHWEST = SOUTH | WEST,
}
type Props = {
/** Required: The path of the icon */
icon: string;
/** Required: The state of the icon */
icon_state: string;
} & Partial<{
/** Facing direction. See direction enum. Default is South */
direction: Direction;
/** Fallback icon. */
fallback: ReactNode;
/** Frame number. Default is 1 */
frame: number;
/** Movement state. Default is false */
movement: boolean;
}> &
BoxProps;
let refMap: Record<string, string> | undefined;
export function DmIcon(props: Props) {
const {
className,
direction = Direction.SOUTH,
fallback,
frame = 1,
icon_state,
icon,
movement = false,
...rest
} = props;
const [iconRef, setIconRef] = useState('');
const query = `${iconRef}?state=${icon_state}&dir=${direction}&movement=${movement}&frame=${frame}`;
useEffect(() => {
async function fetchRefMap() {
const response = await fetchRetry(resolveAsset('icon_ref_map.json'));
const data = await response.json();
refMap = data;
setIconRef(data[icon]);
}
if (!refMap) {
fetchRefMap();
} else {
setIconRef(refMap[icon]);
}
}, []);
if (!iconRef) return fallback;
return <Image fixErrors src={query} {...rest} />;
}
@@ -1,52 +0,0 @@
import { Component, Fragment } from 'react';
import { Box } from './Box';
export class FakeTerminal extends Component {
constructor(props) {
super(props);
this.timer = null;
this.state = {
currentIndex: 0,
currentDisplay: [],
};
}
tick() {
const { props, state } = this;
if (state.currentIndex <= props.allMessages.length) {
this.setState((prevState) => {
return {
currentIndex: prevState.currentIndex + 1,
};
});
const { currentDisplay } = state;
currentDisplay.push(props.allMessages[state.currentIndex]);
} else {
clearTimeout(this.timer);
setTimeout(props.onFinished, props.finishedTimeout);
}
}
componentDidMount() {
const { linesPerSecond = 2.5 } = this.props;
this.timer = setInterval(() => this.tick(), 1000 / linesPerSecond);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
return (
<Box m={1}>
{this.state.currentDisplay.map((value) => (
<Fragment key={value}>
{value}
<br />
</Fragment>
))}
</Box>
);
}
}
-99
View File
@@ -1,99 +0,0 @@
import {
Component,
createRef,
HTMLAttributes,
PropsWithChildren,
RefObject,
} from 'react';
const DEFAULT_ACCEPTABLE_DIFFERENCE = 5;
type Props = {
acceptableDifference?: number;
maxWidth: number;
maxFontSize: number;
native?: HTMLAttributes<HTMLDivElement>;
} & PropsWithChildren;
type State = {
fontSize: number;
};
export class FitText extends Component<Props, State> {
ref: RefObject<HTMLDivElement> = createRef();
state: State = {
fontSize: 0,
};
constructor(props: Props) {
super(props);
this.resize = this.resize.bind(this);
window.addEventListener('resize', this.resize);
}
componentDidUpdate(prevProps) {
if (prevProps.children !== this.props.children) {
this.resize();
}
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
}
resize() {
const element = this.ref.current;
if (!element) {
return;
}
const maxWidth = this.props.maxWidth;
let start = 0;
let end = this.props.maxFontSize;
for (let _ = 0; _ < 10; _++) {
const middle = Math.round((start + end) / 2);
element.style.fontSize = `${middle}px`;
const difference = element.offsetWidth - maxWidth;
if (difference > 0) {
end = middle;
} else if (
difference <
(this.props.acceptableDifference ?? DEFAULT_ACCEPTABLE_DIFFERENCE)
) {
start = middle;
} else {
break;
}
}
this.setState({
fontSize: Math.round((start + end) / 2),
});
}
componentDidMount() {
this.resize();
}
render() {
return (
<span
ref={this.ref}
style={{
fontSize: `${this.state.fontSize}px`,
...(typeof this.props.native?.style === 'object'
? this.props.native.style
: {}),
}}
>
{this.props.children}
</span>
);
}
}
-44
View File
@@ -1,44 +0,0 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/
import { PropsWithChildren } from 'react';
import { logger } from '../logging';
import { BoxProps } from './Box';
import { Table } from './Table';
/** @deprecated Do not use. Use stack instead. */
export function Grid(props: PropsWithChildren<BoxProps>) {
const { children, ...rest } = props;
logger.error('Grid component is deprecated. Use a Stack instead.');
return (
<Table {...rest}>
<Table.Row>{children}</Table.Row>
</Table>
);
}
type Props = Partial<{
/** Width of the column in percentage. */
size: number;
}> &
BoxProps;
/** @deprecated Do not use. Use stack instead. */
export function GridColumn(props: Props) {
const { size = 1, style, ...rest } = props;
return (
<Table.Cell
style={{
width: size + '%',
...style,
}}
{...rest}
/>
);
}
Grid.Column = GridColumn;
@@ -1,192 +0,0 @@
import { Component } from 'react';
import { computeBoxProps } from './Box';
import { Button } from './Button';
import { ProgressBar } from './ProgressBar';
import { Stack } from './Stack';
const ZOOM_MIN_VAL = 0.5;
const ZOOM_MAX_VAL = 1.5;
const ZOOM_INCREMENT = 0.1;
export class InfinitePlane extends Component {
constructor(props) {
super(props);
this.state = {
mouseDown: false,
left: 0,
top: 0,
lastLeft: 0,
lastTop: 0,
zoom: 1,
};
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleZoomIncrease = this.handleZoomIncrease.bind(this);
this.handleZoomDecrease = this.handleZoomDecrease.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.doOffsetMouse = this.doOffsetMouse.bind(this);
}
componentDidMount() {
window.addEventListener('mouseup', this.onMouseUp);
window.addEventListener('mousedown', this.doOffsetMouse);
window.addEventListener('mousemove', this.doOffsetMouse);
window.addEventListener('mouseup', this.doOffsetMouse);
}
componentWillUnmount() {
window.removeEventListener('mouseup', this.onMouseUp);
window.removeEventListener('mousedown', this.doOffsetMouse);
window.removeEventListener('mousemove', this.doOffsetMouse);
window.removeEventListener('mouseup', this.doOffsetMouse);
}
doOffsetMouse(event) {
const { zoom } = this.state;
event.screenZoomX = event.screenX * Math.pow(zoom, -1);
event.screenZoomY = event.screenY * Math.pow(zoom, -1);
}
handleMouseDown(event) {
this.setState((state) => {
return {
mouseDown: true,
lastLeft: event.clientX - state.left,
lastTop: event.clientY - state.top,
};
});
}
onMouseUp() {
this.setState({
mouseDown: false,
});
}
handleZoomIncrease(event) {
const { onZoomChange } = this.props;
const { zoom } = this.state;
const newZoomValue = Math.min(zoom + ZOOM_INCREMENT, ZOOM_MAX_VAL);
this.setState({
zoom: newZoomValue,
});
if (onZoomChange) {
onZoomChange(newZoomValue);
}
}
handleZoomDecrease(event) {
const { onZoomChange } = this.props;
const { zoom } = this.state;
const newZoomValue = Math.max(zoom - ZOOM_INCREMENT, ZOOM_MIN_VAL);
this.setState({
zoom: newZoomValue,
});
if (onZoomChange) {
onZoomChange(newZoomValue);
}
}
handleMouseMove(event) {
const { onBackgroundMoved, initialLeft = 0, initialTop = 0 } = this.props;
if (this.state.mouseDown) {
let newX, newY;
this.setState((state) => {
newX = event.clientX - state.lastLeft;
newY = event.clientY - state.lastTop;
return {
left: newX,
top: newY,
};
});
if (onBackgroundMoved) {
onBackgroundMoved(newX + initialLeft, newY + initialTop);
}
}
}
render() {
const {
children,
backgroundImage,
imageWidth,
initialLeft = 0,
initialTop = 0,
...rest
} = this.props;
const { left, top, zoom } = this.state;
const finalLeft = initialLeft + left;
const finalTop = initialTop + top;
return (
<div
ref={this.ref}
{...computeBoxProps({
...rest,
style: {
...rest.style,
overflow: 'hidden',
position: 'relative',
},
})}
>
<div
onMouseDown={this.handleMouseDown}
onMouseMove={this.handleMouseMove}
style={{
position: 'fixed',
height: '100%',
width: '100%',
backgroundImage: `url("${backgroundImage}")`,
backgroundPosition: `${finalLeft}px ${finalTop}px`,
backgroundRepeat: 'repeat',
backgroundSize: `${zoom * imageWidth}px`,
}}
/>
<div
onMouseDown={this.handleMouseDown}
onMouseMove={this.handleMouseMove}
style={{
position: 'fixed',
transform: `translate(${finalLeft}px, ${finalTop}px) scale(${zoom})`,
transformOrigin: 'top left',
height: '100%',
width: '100%',
}}
>
{children}
</div>
<Stack position="absolute" width="100%">
<Stack.Item>
<Button icon="minus" onClick={this.handleZoomDecrease} />
</Stack.Item>
<Stack.Item grow={1}>
<ProgressBar
minValue={ZOOM_MIN_VAL}
value={zoom}
maxValue={ZOOM_MAX_VAL}
>
{zoom}x
</ProgressBar>
</Stack.Item>
<Stack.Item>
<Button icon="plus" onClick={this.handleZoomIncrease} />
</Stack.Item>
</Stack>
</div>
);
}
}
@@ -1,40 +0,0 @@
import { Component } from 'react';
import { KeyEvent } from '../events';
import { listenForKeyEvents } from '../hotkeys';
type KeyListenerProps = Partial<{
onKey: (key: KeyEvent) => void;
onKeyDown: (key: KeyEvent) => void;
onKeyUp: (key: KeyEvent) => void;
}>;
export class KeyListener extends Component<KeyListenerProps> {
dispose: () => void;
constructor(props) {
super(props);
this.dispose = listenForKeyEvents((key) => {
if (this.props.onKey) {
this.props.onKey(key);
}
if (key.isDown() && this.props.onKeyDown) {
this.props.onKeyDown(key);
}
if (key.isUp() && this.props.onKeyUp) {
this.props.onKeyUp(key);
}
});
}
componentWillUnmount() {
this.dispose();
}
render() {
return null;
}
}
-238
View File
@@ -1,238 +0,0 @@
/**
* @file
* @copyright 2022 Aleksej Komarov
* @license MIT
*/
import { classes } from 'common/react';
import { Component, createRef, ReactNode, RefObject } from 'react';
import { logger } from '../logging';
import { Box } from './Box';
import { Icon } from './Icon';
type MenuProps = {
children: any;
width: string;
menuRef: RefObject<HTMLElement>;
onOutsideClick: () => void;
};
class Menu extends Component<MenuProps> {
private readonly handleClick: (event) => void;
constructor(props) {
super(props);
this.handleClick = (event) => {
if (!this.props.menuRef.current) {
logger.log(`Menu.handleClick(): No ref`);
return;
}
if (this.props.menuRef.current.contains(event.target)) {
logger.log(`Menu.handleClick(): Inside`);
} else {
logger.log(`Menu.handleClick(): Outside`);
this.props.onOutsideClick();
}
};
}
// eslint-disable-next-line react/no-deprecated
componentWillMount() {
window.addEventListener('click', this.handleClick);
}
componentWillUnmount() {
window.removeEventListener('click', this.handleClick);
}
render() {
const { width, children } = this.props;
return (
<div
className={'MenuBar__menu'}
style={{
width: width,
}}
>
{children}
</div>
);
}
}
type MenuBarDropdownProps = {
open: boolean;
openWidth: string;
children: any;
disabled?: boolean;
display: any;
onMouseOver: () => void;
onClick: () => void;
onOutsideClick: () => void;
className?: string;
};
class MenuBarButton extends Component<MenuBarDropdownProps> {
private readonly menuRef: RefObject<HTMLDivElement>;
constructor(props) {
super(props);
this.menuRef = createRef();
}
render() {
const { props } = this;
const {
open,
openWidth,
children,
disabled,
display,
onMouseOver,
onClick,
onOutsideClick,
...boxProps
} = props;
const { className, ...rest } = boxProps;
return (
<div ref={this.menuRef}>
<Box
className={classes([
'MenuBar__MenuBarButton',
'MenuBar__font',
'MenuBar__hover',
className,
])}
{...rest}
onClick={disabled ? () => null : onClick}
onMouseOver={onMouseOver}
>
<span className="MenuBar__MenuBarButton-text">{display}</span>
</Box>
{open && (
<Menu
width={openWidth}
menuRef={this.menuRef}
onOutsideClick={onOutsideClick}
>
{children}
</Menu>
)}
</div>
);
}
}
type MenuBarItemProps = {
entry: string;
children: any;
openWidth: string;
display: ReactNode;
setOpenMenuBar: (entry: string | null) => void;
openMenuBar: string | null;
setOpenOnHover: (flag: boolean) => void;
openOnHover: boolean;
disabled?: boolean;
className?: string;
};
export const Dropdown = (props: MenuBarItemProps) => {
const {
entry,
children,
openWidth,
display,
setOpenMenuBar,
openMenuBar,
setOpenOnHover,
openOnHover,
disabled,
className,
} = props;
return (
<MenuBarButton
openWidth={openWidth}
display={display}
disabled={disabled}
open={openMenuBar === entry}
className={className}
onClick={() => {
const open = openMenuBar === entry ? null : entry;
setOpenMenuBar(open);
setOpenOnHover(!openOnHover);
}}
onOutsideClick={() => {
setOpenMenuBar(null);
setOpenOnHover(false);
}}
onMouseOver={() => {
if (openOnHover) {
setOpenMenuBar(entry);
}
}}
>
{children}
</MenuBarButton>
);
};
const MenuItemToggle = (props) => {
const { value, displayText, onClick, checked } = props;
return (
<Box
className={classes([
'MenuBar__font',
'MenuBar__MenuItem',
'MenuBar__MenuItemToggle',
'MenuBar__hover',
])}
onClick={() => onClick(value)}
>
<div className="MenuBar__MenuItemToggle__check">
{checked && <Icon size={1.3} name="check" />}
</div>
{displayText}
</Box>
);
};
Dropdown.MenuItemToggle = MenuItemToggle;
const MenuItem = (props) => {
const { value, displayText, onClick } = props;
return (
<Box
className={classes([
'MenuBar__font',
'MenuBar__MenuItem',
'MenuBar__hover',
])}
onClick={() => onClick(value)}
>
{displayText}
</Box>
);
};
Dropdown.MenuItem = MenuItem;
const Separator = () => {
return <div className="MenuBar__Separator" />;
};
Dropdown.Separator = Separator;
type MenuBarProps = {
children: any;
};
export const MenuBar = (props: MenuBarProps) => {
const { children } = props;
return <Box className="MenuBar">{children}</Box>;
};
MenuBar.Dropdown = Dropdown;
@@ -1,133 +0,0 @@
/**
* @file
* @copyright 2020 bobbahbrown (https://github.com/bobbahbrown)
* @license MIT
*/
import { clamp01, keyOfMatchingRange, scale } from 'common/math';
import { classes } from 'common/react';
import { AnimatedNumber } from './AnimatedNumber';
import { Box, computeBoxClassName, computeBoxProps } from './Box';
export const RoundGauge = (props) => {
const {
value,
minValue = 1,
maxValue = 1,
ranges,
alertAfter,
alertBefore,
format,
size = 1,
className,
style,
...rest
} = props;
const scaledValue = scale(value, minValue, maxValue);
const clampedValue = clamp01(scaledValue);
const scaledRanges = ranges ? {} : { primary: [0, 1] };
if (ranges) {
Object.keys(ranges).forEach((x) => {
const range = ranges[x];
scaledRanges[x] = [
scale(range[0], minValue, maxValue),
scale(range[1], minValue, maxValue),
];
});
}
const shouldShowAlert = () => {
// If both after and before alert props are set, attempt to interpret both
// in a helpful way.
if (alertAfter && alertBefore && alertAfter < alertBefore) {
// If alertAfter is before alertBefore, only display an alert if
// we're between them.
if (alertAfter < value && alertBefore > value) {
return true;
}
} else if (alertAfter < value || alertBefore > value) {
// Otherwise, we have distint ranges, or only one or neither are set.
// Either way, being on the active side of either is sufficient.
return true;
}
return false;
};
// prettier-ignore
const alertColor = shouldShowAlert()
&& keyOfMatchingRange(clampedValue, scaledRanges);
return (
<Box inline>
<div
className={classes([
'RoundGauge',
className,
computeBoxClassName(rest),
])}
{...computeBoxProps({
style: {
fontSize: size + 'em',
...style,
},
...rest,
})}
>
<svg viewBox="0 0 100 50">
{(alertAfter || alertBefore) && (
<g
className={classes([
'RoundGauge__alert',
alertColor ? `active RoundGauge__alert--${alertColor}` : '',
])}
>
<path d="M48.211,14.578C48.55,13.9 49.242,13.472 50,13.472C50.758,13.472 51.45,13.9 51.789,14.578C54.793,20.587 60.795,32.589 63.553,38.106C63.863,38.726 63.83,39.462 63.465,40.051C63.101,40.641 62.457,41 61.764,41C55.996,41 44.004,41 38.236,41C37.543,41 36.899,40.641 36.535,40.051C36.17,39.462 36.137,38.726 36.447,38.106C39.205,32.589 45.207,20.587 48.211,14.578ZM50,34.417C51.426,34.417 52.583,35.574 52.583,37C52.583,38.426 51.426,39.583 50,39.583C48.574,39.583 47.417,38.426 47.417,37C47.417,35.574 48.574,34.417 50,34.417ZM50,32.75C50,32.75 53,31.805 53,22.25C53,20.594 51.656,19.25 50,19.25C48.344,19.25 47,20.594 47,22.25C47,31.805 50,32.75 50,32.75Z" />
</g>
)}
<g>
<circle className="RoundGauge__ringTrack" cx="50" cy="50" r="45" />
</g>
<g>
{Object.keys(scaledRanges).map((x, i) => {
const col_ranges = scaledRanges[x];
return (
<circle
className={`RoundGauge__ringFill RoundGauge--color--${x}`}
key={i}
style={{
strokeDashoffset: Math.max(
(2.0 - (col_ranges[1] - col_ranges[0])) * Math.PI * 50,
0,
),
}}
transform={`rotate(${180 + 180 * col_ranges[0]} 50 50)`}
cx="50"
cy="50"
r="45"
/>
);
})}
</g>
<g
className="RoundGauge__needle"
transform={`rotate(${clampedValue * 180 - 90} 50 50)`}
>
<polygon
className="RoundGauge__needleLine"
points="46,50 50,0 54,50"
/>
<circle
className="RoundGauge__needleMiddle"
cx="50"
cy="50"
r="8"
/>
</g>
</svg>
</div>
<AnimatedNumber value={value} format={format} size={size} />
</Box>
);
};
@@ -1,30 +0,0 @@
import { PropsWithChildren, ReactNode } from 'react';
import { Box } from './Box';
type Props = Partial<{
style: Record<string, any>;
titleStyle: Record<string, any>;
textStyle: Record<string, any>;
title: ReactNode;
titleSubtext: string;
}> &
PropsWithChildren;
// The cost of flexibility and prettiness.
export const StyleableSection = (props: Props) => {
return (
<Box style={props.style}>
{/* Yes, this box (line above) is missing the "Section" class. This is very intentional, as the layout looks *ugly* with it.*/}
<Box className="Section__title" style={props.titleStyle}>
<Box className="Section__titleText" style={props.textStyle}>
{props.title}
</Box>
<div className="Section__buttons">{props.titleSubtext}</div>
</Box>
<Box className="Section__rest">
<Box className="Section__content">{props.children}</Box>
</Box>
</Box>
);
};
@@ -1,64 +0,0 @@
import { Component } from 'react';
import { formatTime } from '../format';
// AnimatedNumber Copypaste
const isSafeNumber = (value) => {
return (
typeof value === 'number' && Number.isFinite(value) && !Number.isNaN(value)
);
};
export class TimeDisplay extends Component {
constructor(props) {
super(props);
this.timer = null;
this.last_seen_value = undefined;
this.state = {
value: 0,
};
// Set initial state with value provided in props
if (isSafeNumber(props.value)) {
this.state.value = Number(props.value);
this.last_seen_value = Number(props.value);
}
}
componentDidUpdate() {
if (this.props.auto !== undefined) {
clearInterval(this.timer);
this.timer = setInterval(() => this.tick(), 1000); // every 1 s
}
}
tick() {
let current = Number(this.state.value);
if (this.props.value !== this.last_seen_value) {
this.last_seen_value = this.props.value;
current = this.props.value;
}
const mod = this.props.auto === 'up' ? 10 : -10; // Time down by default.
const value = Math.max(0, current + mod); // one sec tick
this.setState({ value });
}
componentDidMount() {
if (this.props.auto !== undefined) {
this.timer = setInterval(() => this.tick(), 1000); // every 1 s
}
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
const val = this.state.value;
// Directly display weird stuff
if (!isSafeNumber(val)) {
return this.state.value || null;
}
return formatTime(val);
}
}
@@ -1,35 +0,0 @@
import { Component, createRef, PropsWithChildren } from 'react';
type Props = {
onOutsideClick: () => void;
} & PropsWithChildren;
export class TrackOutsideClicks extends Component<Props> {
ref = createRef<HTMLDivElement>();
constructor(props) {
super(props);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
document.addEventListener('click', this.handleOutsideClick);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleOutsideClick);
}
handleOutsideClick(event: MouseEvent) {
if (!(event.target instanceof Node)) {
return;
}
if (this.ref.current && !this.ref.current.contains(event.target)) {
this.props.onOutsideClick();
}
}
render() {
return <div ref={this.ref}>{this.props.children}</div>;
}
}
@@ -1,69 +0,0 @@
import {
PropsWithChildren,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
/**
* A vertical list that renders items to fill space up to the extents of the
* current window, and then defers rendering of other items until they come
* into view.
*/
export const VirtualList = (props: PropsWithChildren) => {
const { children } = props;
const containerRef = useRef(null as HTMLDivElement | null);
const [visibleElements, setVisibleElements] = useState(1);
const [padding, setPadding] = useState(0);
const adjustExtents = useCallback(() => {
const { current } = containerRef;
if (
!children ||
!Array.isArray(children) ||
!current ||
visibleElements >= children.length
) {
return;
}
const unusedArea =
document.body.offsetHeight - current.getBoundingClientRect().bottom;
const averageItemHeight = Math.ceil(current.offsetHeight / visibleElements);
if (unusedArea > 0) {
const newVisibleElements = Math.min(
children.length,
visibleElements +
Math.max(1, Math.ceil(unusedArea / averageItemHeight)),
);
setVisibleElements(newVisibleElements);
setPadding((children.length - newVisibleElements) * averageItemHeight);
}
}, [containerRef, visibleElements, children]);
useEffect(() => {
adjustExtents();
const interval = setInterval(adjustExtents, 100);
return () => clearInterval(interval);
}, [adjustExtents]);
return (
<div className={'VirtualList'}>
<div className={'VirtualList__Container'} ref={containerRef}>
{Array.isArray(children) ? children.slice(0, visibleElements) : null}
</div>
<div
className={'VirtualList__Padding'}
style={{ paddingBottom: `${padding}px` }}
/>
</div>
);
};
-13
View File
@@ -6,8 +6,6 @@
export { AnimatedNumber } from './AnimatedNumber';
export { Autofocus } from './Autofocus';
export { Blink } from './Blink';
export { BlockQuote } from './BlockQuote';
export { Box } from './Box';
export { Button } from './Button';
export { ByondUi } from './ByondUi';
@@ -17,21 +15,15 @@ export { ColorBox } from './ColorBox';
export { Dialog } from './Dialog';
export { Dimmer } from './Dimmer';
export { Divider } from './Divider';
export { DmIcon } from './DmIcon';
export { DraggableControl } from './DraggableControl';
export { Dropdown } from './Dropdown';
export { FitText } from './FitText';
export { Flex } from './Flex';
export { Grid } from './Grid';
export { Icon } from './Icon';
export { Image } from './Image';
export { InfinitePlane } from './InfinitePlane';
export { Input } from './Input';
export { KeyListener } from './KeyListener';
export { Knob } from './Knob';
export { LabeledControls } from './LabeledControls';
export { LabeledList } from './LabeledList';
export { MenuBar } from './MenuBar';
export { Modal } from './Modal';
export { NanoMap } from './NanoMap';
export { NoticeBox } from './NoticeBox';
@@ -39,15 +31,10 @@ export { NumberInput } from './NumberInput';
export { Popper } from './Popper';
export { ProgressBar } from './ProgressBar';
export { RestrictedInput } from './RestrictedInput';
export { RoundGauge } from './RoundGauge';
export { Section } from './Section';
export { Slider } from './Slider';
export { Stack } from './Stack';
export { StyleableSection } from './StyleableSection';
export { Table } from './Table';
export { Tabs } from './Tabs';
export { TextArea } from './TextArea';
export { TimeDisplay } from './TimeDisplay';
export { Tooltip } from './Tooltip';
export { TrackOutsideClicks } from './TrackOutsideClicks';
export { VirtualList } from './VirtualList';
+1
View File
@@ -16,6 +16,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-popper": "^2.3.0",
"tgui-core": "^1.2.0",
"tgui-dev-server": "workspace:*",
"tgui-polyfill": "workspace:*"
}
@@ -1,20 +0,0 @@
/**
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
import { Blink, Section } from '../components';
export const meta = {
title: 'Blink',
render: () => <Story />,
};
const Story = (props) => {
return (
<Section>
<Blink>Blink</Blink>
</Section>
);
};
@@ -1,23 +0,0 @@
/**
* @file
* @copyright 2021 Aleksej Komarov
* @license MIT
*/
import { BlockQuote, Section } from '../components';
import { BoxWithSampleText } from './common';
export const meta = {
title: 'BlockQuote',
render: () => <Story />,
};
const Story = (props) => {
return (
<Section>
<BlockQuote>
<BoxWithSampleText />
</BlockQuote>
</Section>
);
};
@@ -1,20 +0,0 @@
/**
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
@use '../base.scss';
@use '../colors.scss';
$color-default: colors.fg(colors.$label) !default;
.BlockQuote {
color: $color-default;
border-left: base.em(2px) solid $color-default;
padding-left: 0.5em;
margin-bottom: 0.5em;
&:last-child {
margin-bottom: 0;
}
}
@@ -1,75 +0,0 @@
/**
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
@use '../base.scss';
$separator-color: base.$color-bg-section;
$background-color: base.$color-bg !default;
$dropdown-z-index: 5;
.MenuBar {
display: flex;
}
.MenuBar__font {
font-family: Verdana, sans-serif;
font-size: base.em(12px);
line-height: base.em(17px);
}
.MenuBar__hover {
&:hover {
background-color: lighten($background-color, 30%);
transition: background-color 0ms;
}
}
.MenuBar__MenuBarButton {
padding: 0.2rem 0.5rem 0.2rem 0.5rem;
}
.MenuBar__menu {
position: absolute;
z-index: $dropdown-z-index;
background-color: $background-color;
padding: 0.3rem 0.3rem 0.3rem 0.3rem;
box-shadow: 4px 6px 5px -2px rgba(0, 0, 0, 0.55);
}
.MenuBar__MenuItem {
z-index: $dropdown-z-index;
transition: background-color 100ms ease-out;
background-color: $background-color;
white-space: nowrap;
padding: 0.3rem 2rem 0.3rem 3rem;
}
.MenuBar__MenuItemToggle {
padding: 0.3rem 2rem 0.3rem 0;
}
.MenuBar__MenuItemToggle__check {
display: inline-block;
vertical-align: middle;
min-width: 3rem;
margin-left: 0.3rem;
}
.MenuBar__over {
top: auto;
bottom: 100%;
}
.MenuBar__MenuBarButton-text {
text-overflow: clip;
white-space: nowrap;
height: base.em(17px);
}
.MenuBar__Separator {
display: block;
margin: 0.3rem 0.3rem 0.3rem 2.3rem;
border-top: 1px solid $separator-color;
}
@@ -1,88 +0,0 @@
/**
* Copyright (c) 2020 bobbahbrown (https://github.com/bobbahbrown)
* SPDX-License-Identifier: MIT
*/
@use '../base.scss';
@use '../colors.scss';
@use '../functions.scss' as *;
$fg-map: colors.$fg-map !default;
$ring-color: #6a96c9 !default;
.RoundGauge {
font-size: 1rem;
width: 2.6em;
height: 1.3em;
margin: 0 auto;
margin-bottom: 0.2em;
}
$pi: 3.1416;
.RoundGauge__ringTrack {
fill: transparent;
stroke: rgba(255, 255, 255, 0.1);
stroke-width: 10;
stroke-dasharray: 50 * $pi;
stroke-dashoffset: 50 * $pi;
}
.RoundGauge__ringFill {
fill: transparent;
stroke: $ring-color;
stroke-width: 10;
stroke-dasharray: 100 * $pi;
transition: stroke 50ms ease-out;
}
.RoundGauge__needle,
.RoundGauge__ringFill {
transition: transform 50ms ease-in-out;
}
.RoundGauge__needleLine,
.RoundGauge__needleMiddle {
fill: colors.$bad;
}
.RoundGauge__alert {
fill-rule: evenodd;
clip-rule: evenodd;
stroke-linejoin: round;
stroke-miterlimit: 2;
fill: rgba(255, 255, 255, 0.1);
}
.RoundGauge__alert.max {
fill: colors.$bad;
}
@each $color-name, $color-value in $fg-map {
.RoundGauge--color--#{$color-name}.RoundGauge__ringFill {
stroke: $color-value;
}
}
@each $color-name, $color-value in $fg-map {
.RoundGauge__alert--#{$color-name} {
fill: $color-value;
transition: opacity 0.6s cubic-bezier(0.25, 1, 0.5, 1);
animation: RoundGauge__alertAnim
1s
cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
}
@keyframes RoundGauge__alertAnim {
0% {
opacity: 0.1;
}
50% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}
-3
View File
@@ -20,7 +20,6 @@
@include meta.load-css('./atomic/text.scss');
// Components
@include meta.load-css('./components/BlockQuote.scss');
@include meta.load-css('./components/Button.scss');
@include meta.load-css('./components/ColorBox.scss');
@include meta.load-css('./components/Dialog.scss');
@@ -32,13 +31,11 @@
@include meta.load-css('./components/Input.scss');
@include meta.load-css('./components/Knob.scss');
@include meta.load-css('./components/LabeledList.scss');
@include meta.load-css('./components/MenuBar.scss');
@include meta.load-css('./components/Modal.scss');
@include meta.load-css('./components/NanoMap.scss');
@include meta.load-css('./components/NoticeBox.scss');
@include meta.load-css('./components/NumberInput.scss');
@include meta.load-css('./components/ProgressBar.scss');
@include meta.load-css('./components/RoundGauge.scss');
@include meta.load-css('./components/Section.scss');
@include meta.load-css('./components/Slider.scss');
@include meta.load-css('./components/Stack.scss');
+1 -1
View File
@@ -60,7 +60,7 @@ module.exports = (env = {}, argv) => {
],
},
{
test: /\.scss$/,
test: /\.(s)?css$/,
use: [
{
loader: ExtractCssPlugin.loader,
+11
View File
@@ -8236,6 +8236,16 @@ __metadata:
languageName: unknown
linkType: soft
"tgui-core@npm:^1.2.0":
version: 1.2.0
resolution: "tgui-core@npm:1.2.0"
peerDependencies:
react: ^18.2.0
react-dom: ^18.2.0
checksum: 10c0/1eead0edbe0df5c49bfa88f0d2caa5df743057be75e9c526d0afd838b8def072c67fe60435c66cba52551c4ef70749d60d68094fa103542187008998002714f7
languageName: node
linkType: hard
"tgui-dev-server@workspace:*, tgui-dev-server@workspace:packages/tgui-dev-server":
version: 0.0.0-use.local
resolution: "tgui-dev-server@workspace:packages/tgui-dev-server"
@@ -8344,6 +8354,7 @@ __metadata:
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
react-popper: "npm:^2.3.0"
tgui-core: "npm:^1.2.0"
tgui-dev-server: "workspace:*"
tgui-polyfill: "workspace:*"
languageName: unknown