tgui-core 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/components → components}/AnimatedNumber.tsx +185 -185
- package/{src/components → components}/BlockQuote.tsx +15 -15
- package/{src/components → components}/BodyZoneSelector.tsx +149 -149
- package/{src/components → components}/Box.tsx +255 -255
- package/{src/components → components}/Button.tsx +415 -415
- package/{src/components → components}/ByondUi.jsx +121 -121
- package/{src/components → components}/Chart.tsx +160 -160
- package/{src/components → components}/ColorBox.tsx +30 -30
- package/{src/components → components}/Dimmer.tsx +19 -19
- package/{src/components → components}/Divider.tsx +26 -26
- package/{src/components → components}/DmIcon.tsx +72 -72
- package/{src/components → components}/DraggableControl.jsx +282 -282
- package/{src/components → components}/Dropdown.tsx +246 -246
- package/{src/components → components}/Flex.tsx +105 -105
- package/{src/components → components}/Icon.tsx +91 -91
- package/{src/components → components}/Input.tsx +181 -181
- package/{src/components → components}/KeyListener.tsx +40 -40
- package/{src/components → components}/Knob.tsx +185 -185
- package/{src/components → components}/LabeledList.tsx +130 -130
- package/{src/components → components}/MenuBar.tsx +233 -238
- package/{src/components → components}/Modal.tsx +25 -25
- package/{src/components → components}/NoticeBox.tsx +48 -48
- package/{src/components → components}/NumberInput.tsx +328 -328
- package/{src/components → components}/ProgressBar.tsx +79 -79
- package/{src/components → components}/RestrictedInput.jsx +301 -301
- package/{src/components → components}/RoundGauge.tsx +189 -189
- package/{src/components → components}/Section.tsx +125 -125
- package/{src/components → components}/Slider.tsx +173 -173
- package/{src/components → components}/Stack.tsx +101 -101
- package/{src/components → components}/Table.tsx +90 -90
- package/{src/components → components}/Tabs.tsx +90 -90
- package/{src/components → components}/TextArea.tsx +198 -198
- package/{src/components → components}/TimeDisplay.jsx +64 -64
- package/components/index.ts +51 -0
- package/{src/debug/KitchenSink.jsx → debug/KitchenSink.tsx} +56 -56
- package/{src/debug/actions.js → debug/actions.ts} +11 -11
- package/{src/debug/hooks.js → debug/hooks.ts} +10 -10
- package/{src/debug/middleware.js → debug/middleware.ts} +67 -86
- package/{src/debug/reducer.js → debug/reducer.ts} +27 -22
- package/{src/debug/selectors.js → debug/selectors.ts} +7 -7
- package/{src/layouts → layouts}/Layout.tsx +75 -75
- package/{src/layouts → layouts}/NtosWindow.tsx +162 -162
- package/{src/layouts → layouts}/Pane.tsx +56 -56
- package/{src/layouts → layouts}/Window.tsx +227 -227
- package/layouts/index.ts +10 -0
- package/package.json +3 -2
- package/src/assets.ts +43 -43
- package/src/backend.ts +368 -369
- package/src/drag.ts +280 -280
- package/src/events.ts +237 -237
- package/src/hotkeys.ts +212 -212
- package/src/renderer.ts +50 -50
- package/stories/Blink.stories.tsx +20 -0
- package/stories/BlockQuote.stories.tsx +23 -0
- package/stories/Box.stories.tsx +27 -0
- package/stories/Button.stories.tsx +68 -0
- package/stories/ByondUi.stories.tsx +45 -0
- package/stories/Collapsible.stories.tsx +23 -0
- package/stories/Flex.stories.tsx +68 -0
- package/stories/Input.stories.tsx +124 -0
- package/stories/LabeledList.stories.tsx +73 -0
- package/stories/Popper.stories.tsx +58 -0
- package/stories/ProgressBar.stories.tsx +58 -0
- package/stories/Stack.stories.tsx +55 -0
- package/stories/Storage.stories.tsx +46 -0
- package/stories/Themes.stories.tsx +30 -0
- package/stories/Tooltip.stories.tsx +48 -0
- package/stories/common.tsx +19 -0
- package/tsconfig.json +0 -21
- package/src/components/Grid.tsx +0 -44
- /package/{src/common → common}/collections.ts +0 -0
- /package/{src/common → common}/color.ts +0 -0
- /package/{src/common → common}/events.ts +0 -0
- /package/{src/common → common}/exhaustive.ts +0 -0
- /package/{src/common → common}/fp.ts +0 -0
- /package/{src/common → common}/keycodes.ts +0 -0
- /package/{src/common → common}/keys.ts +0 -0
- /package/{src/common → common}/math.ts +0 -0
- /package/{src/common → common}/perf.ts +0 -0
- /package/{src/common → common}/random.ts +0 -0
- /package/{src/common → common}/react.ts +0 -0
- /package/{src/common → common}/redux.ts +0 -0
- /package/{src/common → common}/storage.js +0 -0
- /package/{src/common → common}/string.ts +0 -0
- /package/{src/common → common}/timer.ts +0 -0
- /package/{src/common → common}/type-utils.ts +0 -0
- /package/{src/common → common}/types.ts +0 -0
- /package/{src/common → common}/uuid.ts +0 -0
- /package/{src/common → common}/vector.ts +0 -0
- /package/{src/components → components}/Autofocus.tsx +0 -0
- /package/{src/components → components}/Blink.jsx +0 -0
- /package/{src/components → components}/Collapsible.tsx +0 -0
- /package/{src/components → components}/Dialog.tsx +0 -0
- /package/{src/components → components}/FakeTerminal.jsx +0 -0
- /package/{src/components → components}/FitText.tsx +0 -0
- /package/{src/components → components}/Image.tsx +0 -0
- /package/{src/components → components}/InfinitePlane.jsx +0 -0
- /package/{src/components → components}/LabeledControls.tsx +0 -0
- /package/{src/components → components}/Popper.tsx +0 -0
- /package/{src/components → components}/StyleableSection.tsx +0 -0
- /package/{src/components → components}/Tooltip.tsx +0 -0
- /package/{src/components → components}/TrackOutsideClicks.tsx +0 -0
- /package/{src/components → components}/VirtualList.tsx +0 -0
- /package/{src/debug → debug}/index.ts +0 -0
- /package/{src/styles → styles}/base.scss +0 -0
- /package/{src/styles → styles}/colors.scss +0 -0
- /package/{src/styles → styles}/components/BlockQuote.scss +0 -0
- /package/{src/styles → styles}/components/Button.scss +0 -0
- /package/{src/styles → styles}/components/ColorBox.scss +0 -0
- /package/{src/styles → styles}/components/Dialog.scss +0 -0
- /package/{src/styles → styles}/components/Dimmer.scss +0 -0
- /package/{src/styles → styles}/components/Divider.scss +0 -0
- /package/{src/styles → styles}/components/Dropdown.scss +0 -0
- /package/{src/styles → styles}/components/Flex.scss +0 -0
- /package/{src/styles → styles}/components/Icon.scss +0 -0
- /package/{src/styles → styles}/components/Input.scss +0 -0
- /package/{src/styles → styles}/components/Knob.scss +0 -0
- /package/{src/styles → styles}/components/LabeledList.scss +0 -0
- /package/{src/styles → styles}/components/MenuBar.scss +0 -0
- /package/{src/styles → styles}/components/Modal.scss +0 -0
- /package/{src/styles → styles}/components/NoticeBox.scss +0 -0
- /package/{src/styles → styles}/components/NumberInput.scss +0 -0
- /package/{src/styles → styles}/components/ProgressBar.scss +0 -0
- /package/{src/styles → styles}/components/RoundGauge.scss +0 -0
- /package/{src/styles → styles}/components/Section.scss +0 -0
- /package/{src/styles → styles}/components/Slider.scss +0 -0
- /package/{src/styles → styles}/components/Stack.scss +0 -0
- /package/{src/styles → styles}/components/Table.scss +0 -0
- /package/{src/styles → styles}/components/Tabs.scss +0 -0
- /package/{src/styles → styles}/components/TextArea.scss +0 -0
- /package/{src/styles → styles}/components/Tooltip.scss +0 -0
- /package/{src/styles → styles}/functions.scss +0 -0
- /package/{src/styles → styles}/layouts/Layout.scss +0 -0
- /package/{src/styles → styles}/layouts/NtosHeader.scss +0 -0
- /package/{src/styles → styles}/layouts/NtosWindow.scss +0 -0
- /package/{src/styles → styles}/layouts/TitleBar.scss +0 -0
- /package/{src/styles → styles}/layouts/Window.scss +0 -0
- /package/{src/styles → styles}/main.scss +0 -0
- /package/{src/styles → styles}/reset.scss +0 -0
- /package/{src/styles → styles}/themes/abductor.scss +0 -0
- /package/{src/styles → styles}/themes/admin.scss +0 -0
- /package/{src/styles → styles}/themes/cardtable.scss +0 -0
- /package/{src/styles → styles}/themes/hackerman.scss +0 -0
- /package/{src/styles → styles}/themes/malfunction.scss +0 -0
- /package/{src/styles → styles}/themes/neutral.scss +0 -0
- /package/{src/styles → styles}/themes/ntOS95.scss +0 -0
- /package/{src/styles → styles}/themes/ntos.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_cat.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_darkmode.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_lightmode.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_spooky.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_synth.scss +0 -0
- /package/{src/styles → styles}/themes/ntos_terminal.scss +0 -0
- /package/{src/styles → styles}/themes/paper.scss +0 -0
- /package/{src/styles → styles}/themes/retro.scss +0 -0
- /package/{src/styles → styles}/themes/spookyconsole.scss +0 -0
- /package/{src/styles → styles}/themes/syndicate.scss +0 -0
- /package/{src/styles → styles}/themes/wizard.scss +0 -0
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file
|
|
3
|
-
* @copyright 2020 Aleksej Komarov
|
|
4
|
-
* @license MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { BooleanLike, classes } from '../common/react';
|
|
8
|
-
import {
|
|
9
|
-
createElement,
|
|
10
|
-
KeyboardEventHandler,
|
|
11
|
-
MouseEventHandler,
|
|
12
|
-
ReactNode,
|
|
13
|
-
UIEventHandler,
|
|
14
|
-
} from 'react';
|
|
15
|
-
|
|
16
|
-
import { CSS_COLORS } from '../constants';
|
|
17
|
-
|
|
18
|
-
type BooleanProps = Partial<Record<keyof typeof booleanStyleMap, boolean>>;
|
|
19
|
-
type StringProps = Partial<
|
|
20
|
-
Record<keyof typeof stringStyleMap, string | BooleanLike>
|
|
21
|
-
>;
|
|
22
|
-
|
|
23
|
-
export type EventHandlers = Partial<{
|
|
24
|
-
onClick: MouseEventHandler<HTMLDivElement>;
|
|
25
|
-
onContextMenu: MouseEventHandler<HTMLDivElement>;
|
|
26
|
-
onDoubleClick: MouseEventHandler<HTMLDivElement>;
|
|
27
|
-
onKeyDown: KeyboardEventHandler<HTMLDivElement>;
|
|
28
|
-
onKeyUp: KeyboardEventHandler<HTMLDivElement>;
|
|
29
|
-
onMouseDown: MouseEventHandler<HTMLDivElement>;
|
|
30
|
-
onMouseMove: MouseEventHandler<HTMLDivElement>;
|
|
31
|
-
onMouseOver: MouseEventHandler<HTMLDivElement>;
|
|
32
|
-
onMouseUp: MouseEventHandler<HTMLDivElement>;
|
|
33
|
-
onScroll: UIEventHandler<HTMLDivElement>;
|
|
34
|
-
}>;
|
|
35
|
-
|
|
36
|
-
export type BoxProps = Partial<{
|
|
37
|
-
as: string;
|
|
38
|
-
children: ReactNode;
|
|
39
|
-
className: string | BooleanLike;
|
|
40
|
-
style: Partial<CSSStyleDeclaration>;
|
|
41
|
-
}> &
|
|
42
|
-
BooleanProps &
|
|
43
|
-
StringProps &
|
|
44
|
-
EventHandlers;
|
|
45
|
-
|
|
46
|
-
// Don't you dare put this elsewhere
|
|
47
|
-
type DangerDoNotUse = {
|
|
48
|
-
dangerouslySetInnerHTML?: {
|
|
49
|
-
__html: any;
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Coverts our rem-like spacing unit into a CSS unit.
|
|
55
|
-
*/
|
|
56
|
-
export const unit = (value: unknown) => {
|
|
57
|
-
if (typeof value === 'string') {
|
|
58
|
-
// Transparently convert pixels into rem units
|
|
59
|
-
if (value.endsWith('px')) {
|
|
60
|
-
return parseFloat(value) / 12 + 'rem';
|
|
61
|
-
}
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
if (typeof value === 'number') {
|
|
65
|
-
return value + 'rem';
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Same as `unit`, but half the size for integers numbers.
|
|
71
|
-
*/
|
|
72
|
-
export const halfUnit = (value: unknown) => {
|
|
73
|
-
if (typeof value === 'string') {
|
|
74
|
-
return unit(value);
|
|
75
|
-
}
|
|
76
|
-
if (typeof value === 'number') {
|
|
77
|
-
return unit(value * 0.5);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const isColorCode = (str: unknown) => !isColorClass(str);
|
|
82
|
-
|
|
83
|
-
const isColorClass = (str: unknown): boolean => {
|
|
84
|
-
return typeof str === 'string' && CSS_COLORS.includes(str as any);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const mapRawPropTo = (attrName) => (style, value) => {
|
|
88
|
-
if (typeof value === 'number' || typeof value === 'string') {
|
|
89
|
-
style[attrName] = value;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const mapUnitPropTo = (attrName, unit) => (style, value) => {
|
|
94
|
-
if (typeof value === 'number' || typeof value === 'string') {
|
|
95
|
-
style[attrName] = unit(value);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const mapBooleanPropTo = (attrName, attrValue) => (style, value) => {
|
|
100
|
-
if (value) {
|
|
101
|
-
style[attrName] = attrValue;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const mapDirectionalUnitPropTo = (attrName, unit, dirs) => (style, value) => {
|
|
106
|
-
if (typeof value === 'number' || typeof value === 'string') {
|
|
107
|
-
for (let i = 0; i < dirs.length; i++) {
|
|
108
|
-
style[attrName + '-' + dirs[i]] = unit(value);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const mapColorPropTo = (attrName) => (style, value) => {
|
|
114
|
-
if (isColorCode(value)) {
|
|
115
|
-
style[attrName] = value;
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// String / number props
|
|
120
|
-
const stringStyleMap = {
|
|
121
|
-
align: mapRawPropTo('textAlign'),
|
|
122
|
-
bottom: mapUnitPropTo('bottom', unit),
|
|
123
|
-
fontFamily: mapRawPropTo('fontFamily'),
|
|
124
|
-
fontSize: mapUnitPropTo('fontSize', unit),
|
|
125
|
-
fontWeight: mapRawPropTo('fontWeight'),
|
|
126
|
-
height: mapUnitPropTo('height', unit),
|
|
127
|
-
left: mapUnitPropTo('left', unit),
|
|
128
|
-
maxHeight: mapUnitPropTo('maxHeight', unit),
|
|
129
|
-
maxWidth: mapUnitPropTo('maxWidth', unit),
|
|
130
|
-
minHeight: mapUnitPropTo('minHeight', unit),
|
|
131
|
-
minWidth: mapUnitPropTo('minWidth', unit),
|
|
132
|
-
opacity: mapRawPropTo('opacity'),
|
|
133
|
-
overflow: mapRawPropTo('overflow'),
|
|
134
|
-
overflowX: mapRawPropTo('overflowX'),
|
|
135
|
-
overflowY: mapRawPropTo('overflowY'),
|
|
136
|
-
position: mapRawPropTo('position'),
|
|
137
|
-
right: mapUnitPropTo('right', unit),
|
|
138
|
-
textAlign: mapRawPropTo('textAlign'),
|
|
139
|
-
top: mapUnitPropTo('top', unit),
|
|
140
|
-
verticalAlign: mapRawPropTo('verticalAlign'),
|
|
141
|
-
width: mapUnitPropTo('width', unit),
|
|
142
|
-
|
|
143
|
-
lineHeight: (style, value) => {
|
|
144
|
-
if (typeof value === 'number') {
|
|
145
|
-
style['lineHeight'] = value;
|
|
146
|
-
} else if (typeof value === 'string') {
|
|
147
|
-
style['lineHeight'] = unit(value);
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
// Margin
|
|
151
|
-
m: mapDirectionalUnitPropTo('margin', halfUnit, [
|
|
152
|
-
'Top',
|
|
153
|
-
'Bottom',
|
|
154
|
-
'Left',
|
|
155
|
-
'Right',
|
|
156
|
-
]),
|
|
157
|
-
mb: mapUnitPropTo('marginBottom', halfUnit),
|
|
158
|
-
ml: mapUnitPropTo('marginLeft', halfUnit),
|
|
159
|
-
mr: mapUnitPropTo('marginRight', halfUnit),
|
|
160
|
-
mt: mapUnitPropTo('marginTop', halfUnit),
|
|
161
|
-
mx: mapDirectionalUnitPropTo('margin', halfUnit, ['Left', 'Right']),
|
|
162
|
-
my: mapDirectionalUnitPropTo('margin', halfUnit, ['Top', 'Bottom']),
|
|
163
|
-
// Padding
|
|
164
|
-
p: mapDirectionalUnitPropTo('padding', halfUnit, [
|
|
165
|
-
'Top',
|
|
166
|
-
'Bottom',
|
|
167
|
-
'Left',
|
|
168
|
-
'Right',
|
|
169
|
-
]),
|
|
170
|
-
pb: mapUnitPropTo('paddingBottom', halfUnit),
|
|
171
|
-
pl: mapUnitPropTo('paddingLeft', halfUnit),
|
|
172
|
-
pr: mapUnitPropTo('paddingRight', halfUnit),
|
|
173
|
-
pt: mapUnitPropTo('paddingTop', halfUnit),
|
|
174
|
-
px: mapDirectionalUnitPropTo('padding', halfUnit, ['Left', 'Right']),
|
|
175
|
-
py: mapDirectionalUnitPropTo('padding', halfUnit, ['Top', 'Bottom']),
|
|
176
|
-
// Color props
|
|
177
|
-
color: mapColorPropTo('color'),
|
|
178
|
-
textColor: mapColorPropTo('color'),
|
|
179
|
-
backgroundColor: mapColorPropTo('backgroundColor'),
|
|
180
|
-
} as const;
|
|
181
|
-
|
|
182
|
-
// Boolean props
|
|
183
|
-
const booleanStyleMap = {
|
|
184
|
-
bold: mapBooleanPropTo('fontWeight', 'bold'),
|
|
185
|
-
fillPositionedParent: (style, value) => {
|
|
186
|
-
if (value) {
|
|
187
|
-
style['position'] = 'absolute';
|
|
188
|
-
style['top'] = 0;
|
|
189
|
-
style['bottom'] = 0;
|
|
190
|
-
style['left'] = 0;
|
|
191
|
-
style['right'] = 0;
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
inline: mapBooleanPropTo('display', 'inline-block'),
|
|
195
|
-
italic: mapBooleanPropTo('fontStyle', 'italic'),
|
|
196
|
-
nowrap: mapBooleanPropTo('whiteSpace', 'nowrap'),
|
|
197
|
-
preserveWhitespace: mapBooleanPropTo('whiteSpace', 'pre-wrap'),
|
|
198
|
-
} as const;
|
|
199
|
-
|
|
200
|
-
export const computeBoxProps = (props) => {
|
|
201
|
-
const computedProps: Record<string, any> = {};
|
|
202
|
-
const computedStyles: Record<string, string | number> = {};
|
|
203
|
-
|
|
204
|
-
// Compute props
|
|
205
|
-
for (let propName of Object.keys(props)) {
|
|
206
|
-
if (propName === 'style') {
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const propValue = props[propName];
|
|
211
|
-
|
|
212
|
-
const mapPropToStyle =
|
|
213
|
-
stringStyleMap[propName] || booleanStyleMap[propName];
|
|
214
|
-
|
|
215
|
-
if (mapPropToStyle) {
|
|
216
|
-
mapPropToStyle(computedStyles, propValue);
|
|
217
|
-
} else {
|
|
218
|
-
computedProps[propName] = propValue;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Merge computed styles and any directly provided styles
|
|
223
|
-
computedProps.style = { ...computedStyles, ...props.style };
|
|
224
|
-
|
|
225
|
-
return computedProps;
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
export const computeBoxClassName = (props: BoxProps) => {
|
|
229
|
-
const color = props.textColor || props.color;
|
|
230
|
-
const backgroundColor = props.backgroundColor;
|
|
231
|
-
return classes([
|
|
232
|
-
isColorClass(color) && 'color-' + color,
|
|
233
|
-
isColorClass(backgroundColor) && 'color-bg-' + backgroundColor,
|
|
234
|
-
]);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
export const Box = (props: BoxProps & DangerDoNotUse) => {
|
|
238
|
-
const { as = 'div', className, children, ...rest } = props;
|
|
239
|
-
|
|
240
|
-
// Compute class name and styles
|
|
241
|
-
const computedClassName = className
|
|
242
|
-
? `${className} ${computeBoxClassName(rest)}`
|
|
243
|
-
: computeBoxClassName(rest);
|
|
244
|
-
const computedProps = computeBoxProps(rest);
|
|
245
|
-
|
|
246
|
-
// Render the component
|
|
247
|
-
return createElement(
|
|
248
|
-
typeof as === 'string' ? as : 'div',
|
|
249
|
-
{
|
|
250
|
-
...computedProps,
|
|
251
|
-
className: computedClassName,
|
|
252
|
-
},
|
|
253
|
-
children
|
|
254
|
-
);
|
|
255
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @copyright 2020 Aleksej Komarov
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BooleanLike, classes } from '../common/react';
|
|
8
|
+
import {
|
|
9
|
+
createElement,
|
|
10
|
+
KeyboardEventHandler,
|
|
11
|
+
MouseEventHandler,
|
|
12
|
+
ReactNode,
|
|
13
|
+
UIEventHandler,
|
|
14
|
+
} from 'react';
|
|
15
|
+
|
|
16
|
+
import { CSS_COLORS } from '../src/constants';
|
|
17
|
+
|
|
18
|
+
type BooleanProps = Partial<Record<keyof typeof booleanStyleMap, boolean>>;
|
|
19
|
+
type StringProps = Partial<
|
|
20
|
+
Record<keyof typeof stringStyleMap, string | BooleanLike>
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export type EventHandlers = Partial<{
|
|
24
|
+
onClick: MouseEventHandler<HTMLDivElement>;
|
|
25
|
+
onContextMenu: MouseEventHandler<HTMLDivElement>;
|
|
26
|
+
onDoubleClick: MouseEventHandler<HTMLDivElement>;
|
|
27
|
+
onKeyDown: KeyboardEventHandler<HTMLDivElement>;
|
|
28
|
+
onKeyUp: KeyboardEventHandler<HTMLDivElement>;
|
|
29
|
+
onMouseDown: MouseEventHandler<HTMLDivElement>;
|
|
30
|
+
onMouseMove: MouseEventHandler<HTMLDivElement>;
|
|
31
|
+
onMouseOver: MouseEventHandler<HTMLDivElement>;
|
|
32
|
+
onMouseUp: MouseEventHandler<HTMLDivElement>;
|
|
33
|
+
onScroll: UIEventHandler<HTMLDivElement>;
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
export type BoxProps = Partial<{
|
|
37
|
+
as: string;
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
className: string | BooleanLike;
|
|
40
|
+
style: Partial<CSSStyleDeclaration>;
|
|
41
|
+
}> &
|
|
42
|
+
BooleanProps &
|
|
43
|
+
StringProps &
|
|
44
|
+
EventHandlers;
|
|
45
|
+
|
|
46
|
+
// Don't you dare put this elsewhere
|
|
47
|
+
type DangerDoNotUse = {
|
|
48
|
+
dangerouslySetInnerHTML?: {
|
|
49
|
+
__html: any;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Coverts our rem-like spacing unit into a CSS unit.
|
|
55
|
+
*/
|
|
56
|
+
export const unit = (value: unknown) => {
|
|
57
|
+
if (typeof value === 'string') {
|
|
58
|
+
// Transparently convert pixels into rem units
|
|
59
|
+
if (value.endsWith('px')) {
|
|
60
|
+
return parseFloat(value) / 12 + 'rem';
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === 'number') {
|
|
65
|
+
return value + 'rem';
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Same as `unit`, but half the size for integers numbers.
|
|
71
|
+
*/
|
|
72
|
+
export const halfUnit = (value: unknown) => {
|
|
73
|
+
if (typeof value === 'string') {
|
|
74
|
+
return unit(value);
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === 'number') {
|
|
77
|
+
return unit(value * 0.5);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const isColorCode = (str: unknown) => !isColorClass(str);
|
|
82
|
+
|
|
83
|
+
const isColorClass = (str: unknown): boolean => {
|
|
84
|
+
return typeof str === 'string' && CSS_COLORS.includes(str as any);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const mapRawPropTo = (attrName) => (style, value) => {
|
|
88
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
89
|
+
style[attrName] = value;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const mapUnitPropTo = (attrName, unit) => (style, value) => {
|
|
94
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
95
|
+
style[attrName] = unit(value);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const mapBooleanPropTo = (attrName, attrValue) => (style, value) => {
|
|
100
|
+
if (value) {
|
|
101
|
+
style[attrName] = attrValue;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const mapDirectionalUnitPropTo = (attrName, unit, dirs) => (style, value) => {
|
|
106
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
107
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
108
|
+
style[attrName + '-' + dirs[i]] = unit(value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const mapColorPropTo = (attrName) => (style, value) => {
|
|
114
|
+
if (isColorCode(value)) {
|
|
115
|
+
style[attrName] = value;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// String / number props
|
|
120
|
+
const stringStyleMap = {
|
|
121
|
+
align: mapRawPropTo('textAlign'),
|
|
122
|
+
bottom: mapUnitPropTo('bottom', unit),
|
|
123
|
+
fontFamily: mapRawPropTo('fontFamily'),
|
|
124
|
+
fontSize: mapUnitPropTo('fontSize', unit),
|
|
125
|
+
fontWeight: mapRawPropTo('fontWeight'),
|
|
126
|
+
height: mapUnitPropTo('height', unit),
|
|
127
|
+
left: mapUnitPropTo('left', unit),
|
|
128
|
+
maxHeight: mapUnitPropTo('maxHeight', unit),
|
|
129
|
+
maxWidth: mapUnitPropTo('maxWidth', unit),
|
|
130
|
+
minHeight: mapUnitPropTo('minHeight', unit),
|
|
131
|
+
minWidth: mapUnitPropTo('minWidth', unit),
|
|
132
|
+
opacity: mapRawPropTo('opacity'),
|
|
133
|
+
overflow: mapRawPropTo('overflow'),
|
|
134
|
+
overflowX: mapRawPropTo('overflowX'),
|
|
135
|
+
overflowY: mapRawPropTo('overflowY'),
|
|
136
|
+
position: mapRawPropTo('position'),
|
|
137
|
+
right: mapUnitPropTo('right', unit),
|
|
138
|
+
textAlign: mapRawPropTo('textAlign'),
|
|
139
|
+
top: mapUnitPropTo('top', unit),
|
|
140
|
+
verticalAlign: mapRawPropTo('verticalAlign'),
|
|
141
|
+
width: mapUnitPropTo('width', unit),
|
|
142
|
+
|
|
143
|
+
lineHeight: (style, value) => {
|
|
144
|
+
if (typeof value === 'number') {
|
|
145
|
+
style['lineHeight'] = value;
|
|
146
|
+
} else if (typeof value === 'string') {
|
|
147
|
+
style['lineHeight'] = unit(value);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
// Margin
|
|
151
|
+
m: mapDirectionalUnitPropTo('margin', halfUnit, [
|
|
152
|
+
'Top',
|
|
153
|
+
'Bottom',
|
|
154
|
+
'Left',
|
|
155
|
+
'Right',
|
|
156
|
+
]),
|
|
157
|
+
mb: mapUnitPropTo('marginBottom', halfUnit),
|
|
158
|
+
ml: mapUnitPropTo('marginLeft', halfUnit),
|
|
159
|
+
mr: mapUnitPropTo('marginRight', halfUnit),
|
|
160
|
+
mt: mapUnitPropTo('marginTop', halfUnit),
|
|
161
|
+
mx: mapDirectionalUnitPropTo('margin', halfUnit, ['Left', 'Right']),
|
|
162
|
+
my: mapDirectionalUnitPropTo('margin', halfUnit, ['Top', 'Bottom']),
|
|
163
|
+
// Padding
|
|
164
|
+
p: mapDirectionalUnitPropTo('padding', halfUnit, [
|
|
165
|
+
'Top',
|
|
166
|
+
'Bottom',
|
|
167
|
+
'Left',
|
|
168
|
+
'Right',
|
|
169
|
+
]),
|
|
170
|
+
pb: mapUnitPropTo('paddingBottom', halfUnit),
|
|
171
|
+
pl: mapUnitPropTo('paddingLeft', halfUnit),
|
|
172
|
+
pr: mapUnitPropTo('paddingRight', halfUnit),
|
|
173
|
+
pt: mapUnitPropTo('paddingTop', halfUnit),
|
|
174
|
+
px: mapDirectionalUnitPropTo('padding', halfUnit, ['Left', 'Right']),
|
|
175
|
+
py: mapDirectionalUnitPropTo('padding', halfUnit, ['Top', 'Bottom']),
|
|
176
|
+
// Color props
|
|
177
|
+
color: mapColorPropTo('color'),
|
|
178
|
+
textColor: mapColorPropTo('color'),
|
|
179
|
+
backgroundColor: mapColorPropTo('backgroundColor'),
|
|
180
|
+
} as const;
|
|
181
|
+
|
|
182
|
+
// Boolean props
|
|
183
|
+
const booleanStyleMap = {
|
|
184
|
+
bold: mapBooleanPropTo('fontWeight', 'bold'),
|
|
185
|
+
fillPositionedParent: (style, value) => {
|
|
186
|
+
if (value) {
|
|
187
|
+
style['position'] = 'absolute';
|
|
188
|
+
style['top'] = 0;
|
|
189
|
+
style['bottom'] = 0;
|
|
190
|
+
style['left'] = 0;
|
|
191
|
+
style['right'] = 0;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
inline: mapBooleanPropTo('display', 'inline-block'),
|
|
195
|
+
italic: mapBooleanPropTo('fontStyle', 'italic'),
|
|
196
|
+
nowrap: mapBooleanPropTo('whiteSpace', 'nowrap'),
|
|
197
|
+
preserveWhitespace: mapBooleanPropTo('whiteSpace', 'pre-wrap'),
|
|
198
|
+
} as const;
|
|
199
|
+
|
|
200
|
+
export const computeBoxProps = (props) => {
|
|
201
|
+
const computedProps: Record<string, any> = {};
|
|
202
|
+
const computedStyles: Record<string, string | number> = {};
|
|
203
|
+
|
|
204
|
+
// Compute props
|
|
205
|
+
for (let propName of Object.keys(props)) {
|
|
206
|
+
if (propName === 'style') {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const propValue = props[propName];
|
|
211
|
+
|
|
212
|
+
const mapPropToStyle =
|
|
213
|
+
stringStyleMap[propName] || booleanStyleMap[propName];
|
|
214
|
+
|
|
215
|
+
if (mapPropToStyle) {
|
|
216
|
+
mapPropToStyle(computedStyles, propValue);
|
|
217
|
+
} else {
|
|
218
|
+
computedProps[propName] = propValue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Merge computed styles and any directly provided styles
|
|
223
|
+
computedProps.style = { ...computedStyles, ...props.style };
|
|
224
|
+
|
|
225
|
+
return computedProps;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const computeBoxClassName = (props: BoxProps) => {
|
|
229
|
+
const color = props.textColor || props.color;
|
|
230
|
+
const backgroundColor = props.backgroundColor;
|
|
231
|
+
return classes([
|
|
232
|
+
isColorClass(color) && 'color-' + color,
|
|
233
|
+
isColorClass(backgroundColor) && 'color-bg-' + backgroundColor,
|
|
234
|
+
]);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const Box = (props: BoxProps & DangerDoNotUse) => {
|
|
238
|
+
const { as = 'div', className, children, ...rest } = props;
|
|
239
|
+
|
|
240
|
+
// Compute class name and styles
|
|
241
|
+
const computedClassName = className
|
|
242
|
+
? `${className} ${computeBoxClassName(rest)}`
|
|
243
|
+
: computeBoxClassName(rest);
|
|
244
|
+
const computedProps = computeBoxProps(rest);
|
|
245
|
+
|
|
246
|
+
// Render the component
|
|
247
|
+
return createElement(
|
|
248
|
+
typeof as === 'string' ? as : 'div',
|
|
249
|
+
{
|
|
250
|
+
...computedProps,
|
|
251
|
+
className: computedClassName,
|
|
252
|
+
},
|
|
253
|
+
children
|
|
254
|
+
);
|
|
255
|
+
};
|