@ainias42/react-bootstrap-mobile 0.1.14 → 0.1.16
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/bin/updateCopies.js +8 -5
- package/bootstrapReactMobile.ts +12 -2
- package/dist/bootstrapReactMobile.d.ts +12 -2
- package/dist/bootstrapReactMobile.js +841 -329
- package/dist/bootstrapReactMobile.js.map +1 -1
- package/dist/src/Components/Clickable/Clickable.d.ts +5 -3
- package/dist/src/Components/Dialog/DialogBackground.d.ts +4 -5
- package/dist/src/Components/Dialog/DialogContainer.d.ts +7 -3
- package/dist/src/Components/FormElements/Button/Button.d.ts +6 -4
- package/dist/src/Components/FormElements/Button/ButtonType.d.ts +4 -0
- package/dist/src/Components/FormElements/Input/FileInput/FileInput.d.ts +12 -0
- package/dist/src/Components/FormElements/Input/FileInput/FileType.d.ts +7 -0
- package/dist/src/Components/FormElements/Input/FileInput/MultipleFileInput.d.ts +17 -0
- package/dist/src/Components/FormElements/Select/Select.d.ts +2 -1
- package/dist/src/Components/FormElements/Switch/Switch.d.ts +4 -4
- package/dist/src/Components/Hooks/useMousePosition.d.ts +5 -0
- package/dist/src/Components/Hooks/useWindowDimensions.d.ts +4 -0
- package/dist/src/Components/Icon/Icon.d.ts +2 -3
- package/dist/src/Components/Layout/Grid/Grid.d.ts +2 -1
- package/dist/src/Components/Layout/Grid/GridItem.d.ts +4 -1
- package/dist/src/Components/Menu/HoverMenu.d.ts +7 -0
- package/dist/src/Components/Menu/Menu.d.ts +15 -7
- package/dist/src/Components/Menu/MenuCloseContext.d.ts +3 -0
- package/dist/src/Components/Menu/MenuDivider.d.ts +2 -0
- package/dist/src/Components/Menu/MenuItem.d.ts +23 -0
- package/dist/src/Components/Menu/Submenu.d.ts +10 -0
- package/dist/src/Components/Menu/useMenu.d.ts +1 -1
- package/dist/src/Components/RbmComponentProps.d.ts +4 -0
- package/dist/src/ListRow/ListRow.d.ts +1 -0
- package/package.json +8 -7
- package/src/Components/Clickable/Clickable.tsx +95 -13
- package/src/Components/Dialog/DialogBackground.tsx +5 -8
- package/src/Components/Dialog/DialogContainer.tsx +12 -8
- package/src/Components/Dialog/DialogContext.ts +1 -2
- package/src/Components/Dialog/dialogBackground.scss +5 -1
- package/src/Components/DragAndDrop/DropArea.tsx +2 -1
- package/src/Components/FormElements/Button/Button.tsx +15 -7
- package/src/Components/FormElements/Button/ButtonType.ts +4 -0
- package/src/Components/FormElements/Button/button.scss +22 -5
- package/src/Components/FormElements/Input/FileInput/FileInput.tsx +55 -0
- package/src/Components/FormElements/Input/FileInput/FileType.ts +1 -0
- package/src/Components/FormElements/Input/FileInput/MultipleFileInput.tsx +281 -0
- package/src/Components/FormElements/{ImageInput/imageInput.scss → Input/FileInput/fileInput.scss} +37 -7
- package/src/Components/FormElements/SearchSelectInput/SearchSelectInput.tsx +2 -2
- package/src/Components/FormElements/Select/Select.tsx +3 -2
- package/src/Components/FormElements/Select/select.scss +5 -0
- package/src/Components/FormElements/Switch/Switch.tsx +9 -8
- package/src/Components/FormElements/Switch/switch.scss +1 -0
- package/src/Components/Hooks/useMousePosition.ts +13 -0
- package/src/Components/Hooks/useWindowDimensions.ts +17 -0
- package/src/Components/Icon/Icon.tsx +8 -7
- package/src/Components/Icon/icon.scss +4 -0
- package/src/Components/Layout/Grid/Grid.tsx +3 -2
- package/src/Components/Layout/Grid/GridItem.tsx +16 -1
- package/src/Components/Layout/Grid/grid.scss +88 -17
- package/src/Components/Menu/HoverMenu.tsx +71 -0
- package/src/Components/Menu/Menu.tsx +78 -47
- package/src/Components/Menu/MenuCloseContext.ts +10 -0
- package/src/Components/Menu/MenuDivider.tsx +22 -0
- package/src/Components/Menu/MenuItem.tsx +95 -0
- package/src/Components/Menu/Submenu.tsx +95 -0
- package/src/Components/Menu/menu.scss +95 -10
- package/src/Components/Menu/useMenu.ts +1 -1
- package/src/Components/RbmComponentProps.ts +6 -0
- package/src/ListRow/ListRow.tsx +20 -0
- package/src/WrongChildError.ts +0 -2
- package/src/scss/_flavorMixin.scss +3 -0
- package/dist/src/Components/FormElements/ImageInput/ImageInput.d.ts +0 -18
- package/dist/src/Components/FormElements/ImageInput/MultipleFileInput.d.ts +0 -21
- package/src/Components/FormElements/ImageInput/ImageInput.tsx +0 -98
- package/src/Components/FormElements/ImageInput/MultipleFileInput.tsx +0 -240
|
@@ -5,6 +5,9 @@ import { Override } from '../../TypeHelpers';
|
|
|
5
5
|
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
|
6
6
|
import { withMemo } from '../../helper/withMemo';
|
|
7
7
|
import { IconDefinition } from '@fortawesome/free-regular-svg-icons';
|
|
8
|
+
import classNames from "classnames";
|
|
9
|
+
|
|
10
|
+
import styles from "./icon.scss";
|
|
8
11
|
|
|
9
12
|
export type IconSource = IconProp | string | IconDefinition;
|
|
10
13
|
|
|
@@ -12,13 +15,14 @@ export type IconProps = RbmComponentProps<
|
|
|
12
15
|
Override<
|
|
13
16
|
FontAwesomeIconProps,
|
|
14
17
|
{
|
|
18
|
+
noMargin?: boolean
|
|
15
19
|
icon: IconSource;
|
|
16
20
|
alt?: string;
|
|
17
21
|
}
|
|
18
22
|
>
|
|
19
23
|
>;
|
|
20
24
|
|
|
21
|
-
function Icon({ icon, alt, className, style, title, ...props }: IconProps) {
|
|
25
|
+
export const Icon = withMemo(function Icon({ icon, alt, className, noMargin=true, style, title, ...props }: IconProps) {
|
|
22
26
|
// Variables
|
|
23
27
|
|
|
24
28
|
// States
|
|
@@ -37,10 +41,7 @@ function Icon({ icon, alt, className, style, title, ...props }: IconProps) {
|
|
|
37
41
|
// Render Functions
|
|
38
42
|
|
|
39
43
|
if (typeof icon === 'string' && icon.indexOf('.') !== -1) {
|
|
40
|
-
return <img src={icon} alt={alt} className={className} style={style} title={title} />;
|
|
44
|
+
return <img src={icon} alt={alt} className={classNames(className, {[styles.margin]: !noMargin})} style={style} title={title} />;
|
|
41
45
|
}
|
|
42
|
-
return <FontAwesomeIcon {...props} icon={icon as IconProp} className={className} style={style} title={title} />;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const IconMemo = withMemo(Icon);
|
|
46
|
-
export { IconMemo as Icon };
|
|
46
|
+
return <FontAwesomeIcon {...props} icon={icon as IconProp} className={classNames(className, {[styles.margin]: !noMargin})} style={style} title={title} />;
|
|
47
|
+
}, styles);
|
|
@@ -10,9 +10,10 @@ import { useMemo } from 'react';
|
|
|
10
10
|
export type GridProps = RbmComponentProps<{
|
|
11
11
|
columns?: number;
|
|
12
12
|
rows?: number;
|
|
13
|
+
useContainerWidth?: boolean;
|
|
13
14
|
}>;
|
|
14
15
|
|
|
15
|
-
function Grid({ style, children, columns = 12, rows = 1, className, __allowChildren }: GridProps) {
|
|
16
|
+
function Grid({ style, children, columns = 12, rows = 1, useContainerWidth = false, className, __allowChildren }: GridProps) {
|
|
16
17
|
// Variables
|
|
17
18
|
const appliedStyle = useMemo(
|
|
18
19
|
() => ({
|
|
@@ -40,7 +41,7 @@ function Grid({ style, children, columns = 12, rows = 1, className, __allowChild
|
|
|
40
41
|
return (
|
|
41
42
|
<Block
|
|
42
43
|
style={appliedStyle}
|
|
43
|
-
className={classNames(styles.grid, className)}
|
|
44
|
+
className={classNames(styles.grid, className, {[styles.useContainerWidth]: useContainerWidth})}
|
|
44
45
|
__allowChildren={__allowChildren as 'all'}
|
|
45
46
|
>
|
|
46
47
|
{children}
|
|
@@ -8,12 +8,14 @@ import styles from './grid.scss';
|
|
|
8
8
|
|
|
9
9
|
export type GridItemProps = RbmComponentProps<{
|
|
10
10
|
size: number;
|
|
11
|
+
xs?: number;
|
|
11
12
|
sm?: number;
|
|
12
13
|
md?: number;
|
|
13
14
|
lg?: number;
|
|
14
15
|
xl?: number;
|
|
15
16
|
xxl?: number;
|
|
16
17
|
print?: number;
|
|
18
|
+
startXxs?: number;
|
|
17
19
|
startXs?: number;
|
|
18
20
|
startSm?: number;
|
|
19
21
|
startMd?: number;
|
|
@@ -21,6 +23,7 @@ export type GridItemProps = RbmComponentProps<{
|
|
|
21
23
|
startXl?: number;
|
|
22
24
|
startXxl?: number;
|
|
23
25
|
startPrint?: number;
|
|
26
|
+
orderXxs?: number;
|
|
24
27
|
orderXs?: number;
|
|
25
28
|
orderSm?: number;
|
|
26
29
|
orderMd?: number;
|
|
@@ -36,12 +39,14 @@ function GridItem({
|
|
|
36
39
|
className,
|
|
37
40
|
__allowChildren,
|
|
38
41
|
size,
|
|
42
|
+
xs,
|
|
39
43
|
sm,
|
|
40
44
|
md,
|
|
41
45
|
lg,
|
|
42
46
|
xl,
|
|
43
47
|
xxl,
|
|
44
48
|
print,
|
|
49
|
+
startXxs,
|
|
45
50
|
startXs,
|
|
46
51
|
startMd,
|
|
47
52
|
startSm,
|
|
@@ -49,6 +54,7 @@ function GridItem({
|
|
|
49
54
|
startXl,
|
|
50
55
|
startXxl,
|
|
51
56
|
startPrint,
|
|
57
|
+
orderXxs,
|
|
52
58
|
orderXs,
|
|
53
59
|
orderSm,
|
|
54
60
|
orderMd,
|
|
@@ -59,7 +65,10 @@ function GridItem({
|
|
|
59
65
|
}: GridItemProps) {
|
|
60
66
|
// Variables
|
|
61
67
|
|
|
62
|
-
const classes = [`item-
|
|
68
|
+
const classes = [`item-xxs-${size}`];
|
|
69
|
+
if (xs) {
|
|
70
|
+
classes.push(`item-xs-${xs}`);
|
|
71
|
+
}
|
|
63
72
|
if (sm) {
|
|
64
73
|
classes.push(`item-sm-${sm}`);
|
|
65
74
|
}
|
|
@@ -79,6 +88,9 @@ function GridItem({
|
|
|
79
88
|
classes.push(`item-print-${print}`);
|
|
80
89
|
}
|
|
81
90
|
|
|
91
|
+
if (startXxs) {
|
|
92
|
+
classes.push(`start-xxs-${startXxs}`);
|
|
93
|
+
}
|
|
82
94
|
if (startXs) {
|
|
83
95
|
classes.push(`start-xs-${startXs}`);
|
|
84
96
|
}
|
|
@@ -101,6 +113,9 @@ function GridItem({
|
|
|
101
113
|
classes.push(`start-print-${startPrint}`);
|
|
102
114
|
}
|
|
103
115
|
|
|
116
|
+
if (orderXxs) {
|
|
117
|
+
classes.push(`order-xxs-${orderXxs}`);
|
|
118
|
+
}
|
|
104
119
|
if (orderXs) {
|
|
105
120
|
classes.push(`order-xs-${orderXs}`);
|
|
106
121
|
}
|
|
@@ -1,8 +1,43 @@
|
|
|
1
1
|
@import "../../../scss/variables";
|
|
2
2
|
@import "bootstrap/scss/mixins/breakpoints";
|
|
3
3
|
|
|
4
|
+
$grid-breakpoints: (
|
|
5
|
+
xxs: 0,
|
|
6
|
+
xs: 320px,
|
|
7
|
+
sm: 576px,
|
|
8
|
+
md: 768px,
|
|
9
|
+
lg: 992px,
|
|
10
|
+
xl: 1200px,
|
|
11
|
+
xxl: 1400px
|
|
12
|
+
);
|
|
13
|
+
|
|
4
14
|
$columns: 12;
|
|
5
15
|
$breakpoints: $grid-breakpoints;
|
|
16
|
+
$containerBreakpoints: $grid-breakpoints;
|
|
17
|
+
|
|
18
|
+
@mixin contentMin($breakpointName) {
|
|
19
|
+
@for $i from 1 through $columns {
|
|
20
|
+
.item-#{$breakpointName}-#{$i} {
|
|
21
|
+
grid-column: auto / span $i;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Start with `1` because `0` is and invalid value.
|
|
26
|
+
// Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
|
|
27
|
+
@for $i from 1 through ($columns - 1) {
|
|
28
|
+
.start-#{$breakpointName}-#{$i} {
|
|
29
|
+
grid-column-start: $i;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@mixin contentOnly($breakpointName) {
|
|
35
|
+
@for $i from -10 through 10 {
|
|
36
|
+
.order-#{$breakpointName}-#{$i} {
|
|
37
|
+
order: $i;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
6
41
|
|
|
7
42
|
.grid {
|
|
8
43
|
display: grid;
|
|
@@ -14,28 +49,64 @@ $breakpoints: $grid-breakpoints;
|
|
|
14
49
|
padding: 4px;
|
|
15
50
|
}
|
|
16
51
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
52
|
+
&.useContainerWidth {
|
|
53
|
+
container-type: inline-size;
|
|
54
|
+
|
|
55
|
+
@each $breakpoint in map-keys($containerBreakpoints) {
|
|
56
|
+
$min: breakpoint-min($breakpoint, $containerBreakpoints);
|
|
57
|
+
@if $min {
|
|
58
|
+
@container (min-width: #{$min}) {
|
|
59
|
+
@include contentMin($breakpoint);
|
|
60
|
+
}
|
|
61
|
+
} @else {
|
|
62
|
+
@include contentMin($breakpoint);
|
|
23
63
|
}
|
|
24
64
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
65
|
+
// Add classes for reordering
|
|
66
|
+
$min: breakpoint-min($breakpoint, $containerBreakpoints);
|
|
67
|
+
$next: breakpoint-next($breakpoint, $containerBreakpoints);
|
|
68
|
+
$max: breakpoint-max($next, $containerBreakpoints);
|
|
69
|
+
|
|
70
|
+
@if $min != null and $max != null {
|
|
71
|
+
@container (min-width: #{$min}) and (max-width: #{$max}) {
|
|
72
|
+
@include contentOnly($breakpoint);
|
|
73
|
+
}
|
|
74
|
+
} @else if $max == null {
|
|
75
|
+
@container (min-width: #{$min}) {
|
|
76
|
+
@include contentOnly($breakpoint);
|
|
77
|
+
}
|
|
78
|
+
} @else if $min == null {
|
|
79
|
+
@container (max-width: #{$max}) {
|
|
80
|
+
@include contentOnly($breakpoint);
|
|
81
|
+
}
|
|
31
82
|
}
|
|
32
83
|
}
|
|
84
|
+
}
|
|
33
85
|
|
|
34
|
-
|
|
35
|
-
@
|
|
36
|
-
@
|
|
37
|
-
|
|
38
|
-
|
|
86
|
+
&:not(.useContainerWidth) {
|
|
87
|
+
@each $breakpoint in map-keys($breakpoints) {
|
|
88
|
+
@include media-breakpoint-up($breakpoint, $breakpoints) {
|
|
89
|
+
@for $i from 1 through $columns {
|
|
90
|
+
.item-#{$breakpoint}-#{$i} {
|
|
91
|
+
grid-column: auto / span $i;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Start with `1` because `0` is and invalid value.
|
|
96
|
+
// Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
|
|
97
|
+
@for $i from 1 through ($columns - 1) {
|
|
98
|
+
.start-#{$breakpoint}-#{$i} {
|
|
99
|
+
grid-column-start: $i;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Add classes for reordering
|
|
105
|
+
@include media-breakpoint-only($breakpoint, $breakpoints) {
|
|
106
|
+
@for $i from -10 through 10 {
|
|
107
|
+
.order-#{$breakpoint}-#{$i} {
|
|
108
|
+
order: $i;
|
|
109
|
+
}
|
|
39
110
|
}
|
|
40
111
|
}
|
|
41
112
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {withMemo} from "../../helper/withMemo";
|
|
2
|
+
import React, {useCallback, useRef, useState} from "react";
|
|
3
|
+
import {Clickable} from "../Clickable/Clickable";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
import styles from "./menu.scss";
|
|
6
|
+
import {RbmChildWithoutString, RbmComponentProps, WithNoStringAndChildrenProps} from "../RbmComponentProps";
|
|
7
|
+
import {Menu} from "./Menu";
|
|
8
|
+
|
|
9
|
+
export type HoverMenuProps = RbmComponentProps<{
|
|
10
|
+
items: RbmChildWithoutString,
|
|
11
|
+
onClick?: () => void;
|
|
12
|
+
}, WithNoStringAndChildrenProps>;
|
|
13
|
+
|
|
14
|
+
export const HoverMenu = withMemo(function HoverMenu({
|
|
15
|
+
children,
|
|
16
|
+
items,
|
|
17
|
+
className,
|
|
18
|
+
style,
|
|
19
|
+
onClick,
|
|
20
|
+
}: HoverMenuProps) {
|
|
21
|
+
// Refs
|
|
22
|
+
|
|
23
|
+
// States/Variables/Selectors
|
|
24
|
+
const hoverItemRef = useRef<HTMLDivElement>(null);
|
|
25
|
+
|
|
26
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
27
|
+
const [position, setPosition] = useState({x: 0, y: 0});
|
|
28
|
+
const [offset, setOffset] = useState({x: 0, y: 0});
|
|
29
|
+
|
|
30
|
+
// Dispatch
|
|
31
|
+
|
|
32
|
+
// Callbacks
|
|
33
|
+
const recalculatePosition = useCallback(() => {
|
|
34
|
+
if (!hoverItemRef.current) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const {left, bottom, width, height} = hoverItemRef.current.getBoundingClientRect();
|
|
38
|
+
setPosition({x: left, y: bottom});
|
|
39
|
+
setOffset({x: -width, y: height});
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const close = useCallback(() => {
|
|
43
|
+
setIsOpen(false);
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const open = useCallback(() => {
|
|
47
|
+
recalculatePosition();
|
|
48
|
+
setIsOpen(true);
|
|
49
|
+
}, [recalculatePosition]);
|
|
50
|
+
|
|
51
|
+
// Effects
|
|
52
|
+
|
|
53
|
+
// Other
|
|
54
|
+
|
|
55
|
+
// RenderFunctions
|
|
56
|
+
|
|
57
|
+
return <Clickable
|
|
58
|
+
onMouseEnter={open}
|
|
59
|
+
onMouseLeave={close}
|
|
60
|
+
onClick={onClick}
|
|
61
|
+
className={classNames(styles.hoverMenu, {[styles.open]: isOpen}, className)}
|
|
62
|
+
style={style}
|
|
63
|
+
ref={hoverItemRef}
|
|
64
|
+
__allowChildren="all"
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
<Menu x={position.x} y={position.y} isOpen={true} onClose={close} offsetX={offset.x} offsetY={offset.y}>
|
|
68
|
+
{items}
|
|
69
|
+
</Menu>
|
|
70
|
+
</Clickable>;
|
|
71
|
+
}, styles);
|
|
@@ -1,31 +1,51 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import {withMemo} from '../../helper/withMemo';
|
|
3
|
+
import {RbmComponentProps} from '../RbmComponentProps';
|
|
4
|
+
import {IconSource} from '../Icon/Icon';
|
|
5
|
+
import {Block} from '../Layout/Block';
|
|
6
6
|
import classNames from 'classnames';
|
|
7
|
-
|
|
8
7
|
import styles from './menu.scss';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import {useEffect, useLayoutEffect, useRef, useState} from 'react';
|
|
9
|
+
import {withRenderBrowserOnly} from '../../helper/withRenderBrowserOnly';
|
|
10
|
+
import {useWindow} from '../../WindowContext/WindowContext';
|
|
11
|
+
import {MenuItem} from "./MenuItem";
|
|
12
|
+
import {MenuCloseContextProvider} from "./MenuCloseContext";
|
|
14
13
|
|
|
15
|
-
export type
|
|
14
|
+
export type MenuItemType = {
|
|
16
15
|
label: string;
|
|
17
|
-
|
|
18
|
-
icon?: IconSource;
|
|
16
|
+
icon?: IconSource | { icon: IconSource, color: string };
|
|
19
17
|
key: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
callback: () => void;
|
|
20
|
+
onMouseEnter?: () => void;
|
|
21
|
+
onMouseLeave?: () => void;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export type MenuProps = RbmComponentProps<
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
+
{
|
|
26
|
+
items?: MenuItemType[];
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
isOpen: boolean;
|
|
30
|
+
onClose: () => void,
|
|
31
|
+
offsetX?: number,
|
|
32
|
+
offsetY?: number
|
|
33
|
+
}
|
|
25
34
|
>;
|
|
26
35
|
|
|
27
36
|
export const Menu = withMemo(
|
|
28
|
-
withRenderBrowserOnly(function Menu({
|
|
37
|
+
withRenderBrowserOnly(function Menu({
|
|
38
|
+
className,
|
|
39
|
+
style,
|
|
40
|
+
items,
|
|
41
|
+
y,
|
|
42
|
+
x,
|
|
43
|
+
isOpen,
|
|
44
|
+
onClose,
|
|
45
|
+
children,
|
|
46
|
+
offsetY = 0,
|
|
47
|
+
offsetX = 0,
|
|
48
|
+
}: MenuProps) {
|
|
29
49
|
// Variables
|
|
30
50
|
|
|
31
51
|
// Refs
|
|
@@ -39,13 +59,6 @@ export const Menu = withMemo(
|
|
|
39
59
|
// Selectors
|
|
40
60
|
|
|
41
61
|
// Callbacks
|
|
42
|
-
const callItemCallback = useCallback(
|
|
43
|
-
(_: any, cb: () => void) => {
|
|
44
|
-
onClose();
|
|
45
|
-
cb();
|
|
46
|
-
},
|
|
47
|
-
[onClose]
|
|
48
|
-
);
|
|
49
62
|
|
|
50
63
|
// Effects
|
|
51
64
|
useEffect(() => {
|
|
@@ -55,8 +68,8 @@ export const Menu = withMemo(
|
|
|
55
68
|
onClose();
|
|
56
69
|
}
|
|
57
70
|
};
|
|
58
|
-
window?.addEventListener('mousedown', listener, {
|
|
59
|
-
return () => window?.removeEventListener('mousedown', listener, {
|
|
71
|
+
window?.addEventListener('mousedown', listener, {capture: true});
|
|
72
|
+
return () => window?.removeEventListener('mousedown', listener, {capture: true});
|
|
60
73
|
}
|
|
61
74
|
return undefined;
|
|
62
75
|
}, [isOpen, onClose, window]);
|
|
@@ -65,10 +78,18 @@ export const Menu = withMemo(
|
|
|
65
78
|
if (!menuRef.current) {
|
|
66
79
|
return;
|
|
67
80
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
81
|
+
const width = parseFloat(getComputedStyle(menuRef.current).width);
|
|
82
|
+
let newX = x;
|
|
83
|
+
if (newX > (window?.innerWidth ?? 0) - width) {
|
|
84
|
+
newX -= width+offsetX;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (newX < 0) {
|
|
88
|
+
newX = 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
70
91
|
setInnerX(newX);
|
|
71
|
-
}, [window?.innerWidth, x]);
|
|
92
|
+
}, [offsetX, window?.innerWidth, x]);
|
|
72
93
|
|
|
73
94
|
useLayoutEffect(() => {
|
|
74
95
|
if (!menuRef.current) {
|
|
@@ -77,10 +98,15 @@ export const Menu = withMemo(
|
|
|
77
98
|
const height = parseFloat(getComputedStyle(menuRef.current).height);
|
|
78
99
|
let newY = y;
|
|
79
100
|
if (newY > (window?.innerHeight ?? 0) - height) {
|
|
80
|
-
newY -= height;
|
|
101
|
+
newY -= height+offsetY;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (newY < 0) {
|
|
105
|
+
newY = 0;
|
|
81
106
|
}
|
|
107
|
+
|
|
82
108
|
setInnerY(newY);
|
|
83
|
-
}, [window?.innerHeight, y]);
|
|
109
|
+
}, [offsetY, window?.innerHeight, y]);
|
|
84
110
|
|
|
85
111
|
// Other
|
|
86
112
|
|
|
@@ -90,23 +116,28 @@ export const Menu = withMemo(
|
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
return (
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
<MenuCloseContextProvider value={onClose}>
|
|
120
|
+
<Block
|
|
121
|
+
className={classNames(className, styles.menu)}
|
|
122
|
+
style={{...style, top: innerY, left: innerX}}
|
|
123
|
+
ref={menuRef}
|
|
124
|
+
__allowChildren="all"
|
|
125
|
+
>
|
|
126
|
+
{items?.map((item) => {
|
|
127
|
+
const icon = (!!item.icon && typeof item.icon === "object" && "color" in item.icon) ? item.icon.icon : item.icon;
|
|
128
|
+
const iconColor = (!!item.icon && typeof item.icon === "object" && "color" in item.icon) ? item.icon.color : undefined;
|
|
129
|
+
|
|
130
|
+
return <MenuItem key={item.key}
|
|
131
|
+
onClick={item.callback}
|
|
132
|
+
className={classNames(styles.item, item.className)}
|
|
133
|
+
onMouseEnter={item.onMouseEnter}
|
|
134
|
+
icon={icon}
|
|
135
|
+
iconColor={iconColor}
|
|
136
|
+
onMouseLeave={item.onMouseLeave}>{item.label}</MenuItem>;
|
|
137
|
+
})}
|
|
138
|
+
{children}
|
|
139
|
+
</Block>
|
|
140
|
+
</MenuCloseContextProvider>
|
|
110
141
|
);
|
|
111
142
|
}),
|
|
112
143
|
styles
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, {useContext} from "react";
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
4
|
+
const MenuCloseContext = React.createContext<() => void>(() => {});
|
|
5
|
+
|
|
6
|
+
export const MenuCloseContextProvider = MenuCloseContext.Provider;
|
|
7
|
+
|
|
8
|
+
export function useMenuClose() {
|
|
9
|
+
return useContext(MenuCloseContext);
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {withMemo} from "../../helper/withMemo";
|
|
2
|
+
import {Block} from "../Layout/Block";
|
|
3
|
+
import styles from "./menu.scss";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
export const MenuDivider = withMemo(function MenuDivider() {
|
|
7
|
+
// Refs
|
|
8
|
+
|
|
9
|
+
// States/Variables/Selectors
|
|
10
|
+
|
|
11
|
+
// Dispatch
|
|
12
|
+
|
|
13
|
+
// Callbacks
|
|
14
|
+
|
|
15
|
+
// Effects
|
|
16
|
+
|
|
17
|
+
// Other
|
|
18
|
+
|
|
19
|
+
// RenderFunctions
|
|
20
|
+
|
|
21
|
+
return <Block className={styles.divider}/>;
|
|
22
|
+
}, styles);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {Icon, IconSource} from "../Icon/Icon";
|
|
2
|
+
import {withMemo} from "../../helper/withMemo";
|
|
3
|
+
import {Clickable} from "../Clickable/Clickable";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
import styles from "./menu.scss";
|
|
6
|
+
import {Text} from "../Text/Text";
|
|
7
|
+
import React, {ReactNode, useCallback} from "react";
|
|
8
|
+
import {RbmComponentProps, WithChildren} from "../RbmComponentProps";
|
|
9
|
+
import Element = React.JSX.Element;
|
|
10
|
+
import {useMenuClose} from "./MenuCloseContext";
|
|
11
|
+
import {Block} from "../Layout/Block";
|
|
12
|
+
|
|
13
|
+
export type MenuItemProps<Item = undefined> = RbmComponentProps<{
|
|
14
|
+
icon?: IconSource;
|
|
15
|
+
iconColor?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
children: string | ReactNode
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
active?: boolean;
|
|
20
|
+
} & ({
|
|
21
|
+
onClick: (item: Item) => void;
|
|
22
|
+
onMouseEnter?: (item: Item) => void;
|
|
23
|
+
onMouseLeave?: (item: Item) => void;
|
|
24
|
+
item?: undefined
|
|
25
|
+
} | {
|
|
26
|
+
onClick: (item: Item) => void;
|
|
27
|
+
onMouseEnter?: (item: Item) => void;
|
|
28
|
+
onMouseLeave?: (item: Item) => void;
|
|
29
|
+
item: Item
|
|
30
|
+
}), WithChildren>
|
|
31
|
+
|
|
32
|
+
export const MenuItem = withMemo(function MenuItem<Item>({
|
|
33
|
+
children,
|
|
34
|
+
icon,
|
|
35
|
+
iconColor,
|
|
36
|
+
className,
|
|
37
|
+
onClick,
|
|
38
|
+
onMouseEnter,
|
|
39
|
+
onMouseLeave,
|
|
40
|
+
active,
|
|
41
|
+
item,
|
|
42
|
+
disabled = false,
|
|
43
|
+
...props
|
|
44
|
+
}: MenuItemProps<Item>) {
|
|
45
|
+
// Refs
|
|
46
|
+
|
|
47
|
+
// States/Variables/Selectors
|
|
48
|
+
const close = useMenuClose();
|
|
49
|
+
|
|
50
|
+
// Dispatch
|
|
51
|
+
|
|
52
|
+
// Callbacks
|
|
53
|
+
const onClickInner = useCallback(() => {
|
|
54
|
+
if (disabled) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
onClick(item as Item);
|
|
58
|
+
close();
|
|
59
|
+
}, [close, disabled, item, onClick]);
|
|
60
|
+
|
|
61
|
+
const onMouseEnterInner = useCallback(() => {
|
|
62
|
+
if (disabled) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
onMouseEnter?.(item as Item);
|
|
66
|
+
}, [disabled, item, onMouseEnter]);
|
|
67
|
+
|
|
68
|
+
const onMouseLeaveInner = useCallback(() => {
|
|
69
|
+
if (disabled) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
onMouseLeave?.(item as Item);
|
|
73
|
+
}, [disabled, item, onMouseLeave]);
|
|
74
|
+
|
|
75
|
+
// Effects
|
|
76
|
+
|
|
77
|
+
// Other
|
|
78
|
+
const childElements = typeof children === "string" ? <Text>{children}</Text> : children as Element;
|
|
79
|
+
|
|
80
|
+
// RenderFunctions
|
|
81
|
+
|
|
82
|
+
return <Clickable
|
|
83
|
+
className={classNames(styles.item, {[styles.disabled]: disabled, [styles.active]: active}, className)}
|
|
84
|
+
{...props}
|
|
85
|
+
onClick={onClickInner}
|
|
86
|
+
onMouseEnter={onMouseEnterInner}
|
|
87
|
+
onMouseLeave={onMouseLeaveInner}
|
|
88
|
+
__allowChildren="all"
|
|
89
|
+
>
|
|
90
|
+
<Block className={classNames(styles.itemChildren)}>
|
|
91
|
+
{!!icon && <Icon icon={icon} color={iconColor} className={styles.icon}/>}
|
|
92
|
+
{childElements}
|
|
93
|
+
</Block>
|
|
94
|
+
</Clickable>;
|
|
95
|
+
}, styles, "text");
|