@ainias42/react-bootstrap-mobile 0.1.15 → 0.1.17

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.
Files changed (81) hide show
  1. package/bin/updateCopies.js +9 -5
  2. package/bootstrapReactMobile.ts +13 -2
  3. package/dist/bootstrapReactMobile.d.ts +13 -2
  4. package/dist/bootstrapReactMobile.js +992 -395
  5. package/dist/bootstrapReactMobile.js.map +1 -1
  6. package/dist/src/Components/Clickable/Clickable.d.ts +7 -3
  7. package/dist/src/Components/Dialog/DialogBackground.d.ts +4 -5
  8. package/dist/src/Components/Dialog/DialogContainer.d.ts +7 -3
  9. package/dist/src/Components/DragAndDrop/DragItem.d.ts +1 -1
  10. package/dist/src/Components/Flavor.d.ts +4 -0
  11. package/dist/src/Components/FormElements/Button/Button.d.ts +8 -4
  12. package/dist/src/Components/FormElements/Button/ButtonType.d.ts +4 -0
  13. package/dist/src/Components/FormElements/Input/FileInput/FileInput.d.ts +12 -0
  14. package/dist/src/Components/FormElements/Input/FileInput/FileType.d.ts +7 -0
  15. package/dist/src/Components/FormElements/Input/FileInput/MultipleFileInput.d.ts +17 -0
  16. package/dist/src/Components/FormElements/Select/Select.d.ts +2 -1
  17. package/dist/src/Components/FormElements/Switch/Switch.d.ts +4 -4
  18. package/dist/src/Components/Hooks/useMousePosition.d.ts +5 -0
  19. package/dist/src/Components/Hooks/useWindowDimensions.d.ts +4 -0
  20. package/dist/src/Components/Icon/Icon.d.ts +2 -3
  21. package/dist/src/Components/Layout/Grid/Grid.d.ts +2 -1
  22. package/dist/src/Components/Layout/Grid/GridItem.d.ts +4 -1
  23. package/dist/src/Components/Menu/HoverMenu.d.ts +9 -0
  24. package/dist/src/Components/Menu/Menu.d.ts +16 -7
  25. package/dist/src/Components/Menu/MenuCloseContext.d.ts +3 -0
  26. package/dist/src/Components/Menu/MenuDivider.d.ts +2 -0
  27. package/dist/src/Components/Menu/MenuItem.d.ts +23 -0
  28. package/dist/src/Components/Menu/Submenu.d.ts +12 -0
  29. package/dist/src/Components/Menu/useMenu.d.ts +1 -1
  30. package/dist/src/Components/RbmComponentProps.d.ts +4 -0
  31. package/dist/src/Components/Text/Text.d.ts +1 -0
  32. package/dist/src/ListRow/ListRow.d.ts +1 -0
  33. package/package.json +8 -7
  34. package/src/Components/Clickable/Clickable.tsx +135 -19
  35. package/src/Components/Dialog/DialogBackground.tsx +5 -8
  36. package/src/Components/Dialog/DialogContainer.tsx +12 -8
  37. package/src/Components/Dialog/DialogContext.ts +1 -2
  38. package/src/Components/Dialog/dialogBackground.scss +5 -1
  39. package/src/Components/DragAndDrop/DragItem.tsx +7 -7
  40. package/src/Components/DragAndDrop/DropArea.tsx +2 -1
  41. package/src/Components/Flavor.ts +4 -0
  42. package/src/Components/FormElements/Button/Button.tsx +31 -13
  43. package/src/Components/FormElements/Button/ButtonType.ts +4 -0
  44. package/src/Components/FormElements/Button/button.scss +22 -5
  45. package/src/Components/FormElements/Input/FileInput/FileInput.tsx +55 -0
  46. package/src/Components/FormElements/Input/FileInput/FileType.ts +1 -0
  47. package/src/Components/FormElements/Input/FileInput/MultipleFileInput.tsx +281 -0
  48. package/src/Components/FormElements/{ImageInput/imageInput.scss → Input/FileInput/fileInput.scss} +37 -7
  49. package/src/Components/FormElements/Input/input.scss +1 -1
  50. package/src/Components/FormElements/SearchSelectInput/SearchSelectInput.tsx +2 -2
  51. package/src/Components/FormElements/SearchSelectInput/seachSelectInput.scss +1 -1
  52. package/src/Components/FormElements/Select/Select.tsx +3 -2
  53. package/src/Components/FormElements/Select/select.scss +5 -0
  54. package/src/Components/FormElements/Switch/Switch.tsx +9 -8
  55. package/src/Components/FormElements/Switch/switch.scss +1 -0
  56. package/src/Components/Hooks/useMousePosition.ts +13 -0
  57. package/src/Components/Hooks/useWindowDimensions.ts +17 -0
  58. package/src/Components/Icon/Icon.tsx +18 -14
  59. package/src/Components/Icon/icon.scss +9 -0
  60. package/src/Components/Layout/Grid/Grid.tsx +3 -2
  61. package/src/Components/Layout/Grid/GridItem.tsx +16 -1
  62. package/src/Components/Layout/Grid/grid.scss +113 -36
  63. package/src/Components/Menu/HoverMenu.tsx +82 -0
  64. package/src/Components/Menu/Menu.tsx +101 -47
  65. package/src/Components/Menu/MenuCloseContext.ts +10 -0
  66. package/src/Components/Menu/MenuDivider.tsx +22 -0
  67. package/src/Components/Menu/MenuItem.tsx +95 -0
  68. package/src/Components/Menu/Submenu.tsx +101 -0
  69. package/src/Components/Menu/menu.scss +99 -10
  70. package/src/Components/Menu/useMenu.ts +1 -1
  71. package/src/Components/RbmComponentProps.ts +6 -0
  72. package/src/Components/Text/Text.tsx +1 -0
  73. package/src/Components/Text/text.scss +13 -5
  74. package/src/ListRow/ListRow.tsx +20 -0
  75. package/src/WrongChildError.ts +0 -2
  76. package/src/scss/_colors.scss +1 -1
  77. package/src/scss/_flavorMixin.scss +10 -0
  78. package/dist/src/Components/FormElements/ImageInput/ImageInput.d.ts +0 -18
  79. package/dist/src/Components/FormElements/ImageInput/MultipleFileInput.d.ts +0 -21
  80. package/src/Components/FormElements/ImageInput/ImageInput.tsx +0 -98
  81. package/src/Components/FormElements/ImageInput/MultipleFileInput.tsx +0 -240
@@ -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-xs-${size}`];
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,68 @@
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 printClasses {
19
+ @media print {
20
+ @for $i from 1 through $columns {
21
+ .item-print-#{$i} {
22
+ grid-column: auto / span $i;
23
+ }
24
+ }
25
+
26
+ // Start with `1` because `0` is and invalid value.
27
+ // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
28
+ @for $i from 1 through ($columns - 1) {
29
+ .start-print-#{$i} {
30
+ grid-column-start: $i;
31
+ }
32
+ }
33
+
34
+ // Add classes for reordering
35
+ @for $i from -10 through 10 {
36
+ .order-print-#{$i} {
37
+ order: $i;
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ @mixin contentMin($breakpointName) {
44
+ @for $i from 1 through $columns {
45
+ .item-#{$breakpointName}-#{$i} {
46
+ grid-column: auto / span $i;
47
+ }
48
+ }
49
+
50
+ // Start with `1` because `0` is and invalid value.
51
+ // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
52
+ @for $i from 1 through ($columns - 1) {
53
+ .start-#{$breakpointName}-#{$i} {
54
+ grid-column-start: $i;
55
+ }
56
+ }
57
+ }
58
+
59
+ @mixin contentOnly($breakpointName) {
60
+ @for $i from -10 through 10 {
61
+ .order-#{$breakpointName}-#{$i} {
62
+ order: $i;
63
+ }
64
+ }
65
+ }
6
66
 
7
67
  .grid {
8
68
  display: grid;
@@ -14,53 +74,70 @@ $breakpoints: $grid-breakpoints;
14
74
  padding: 4px;
15
75
  }
16
76
 
17
- @each $breakpoint in map-keys($breakpoints) {
18
- @include media-breakpoint-up($breakpoint, $breakpoints) {
19
- @for $i from 1 through $columns {
20
- .item-#{$breakpoint}-#{$i} {
21
- grid-column: auto / span $i;
22
- }
23
- }
77
+ &.useContainerWidth {
78
+ container-type: inline-size;
24
79
 
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-#{$breakpoint}-#{$i} {
29
- grid-column-start: $i;
30
- }
80
+ @each $breakpoint in map-keys($containerBreakpoints) {
81
+ $min: breakpoint-min($breakpoint, $containerBreakpoints);
82
+ @if $min {
83
+ @container (min-width: #{$min}) {
84
+ @include contentMin($breakpoint);
85
+ }
86
+ } @else {
87
+ @include contentMin($breakpoint);
31
88
  }
32
- }
33
89
 
34
- // Add classes for reordering
35
- @include media-breakpoint-only($breakpoint, $breakpoints) {
36
- @for $i from -10 through 10 {
37
- .order-#{$breakpoint}-#{$i} {
38
- order: $i;
39
- }
90
+ // Add classes for reordering
91
+ $min: breakpoint-min($breakpoint, $containerBreakpoints);
92
+ $next: breakpoint-next($breakpoint, $containerBreakpoints);
93
+ $max: breakpoint-max($next, $containerBreakpoints);
94
+
95
+ @if $min != null and $max != null {
96
+ @container (min-width: #{$min}) and (max-width: #{$max}) {
97
+ @include contentOnly($breakpoint);
98
+ }
99
+ } @else if $max == null {
100
+ @container (min-width: #{$min}) {
101
+ @include contentOnly($breakpoint);
102
+ }
103
+ } @else if $min == null {
104
+ @container (max-width: #{$max}) {
105
+ @include contentOnly($breakpoint);
106
+ }
40
107
  }
41
108
  }
109
+
110
+ @include printClasses;
42
111
  }
43
112
 
44
- @media print {
45
- @for $i from 1 through $columns {
46
- .item-print-#{$i} {
47
- grid-column: auto / span $i;
48
- }
49
- }
113
+ &:not(.useContainerWidth) {
114
+ @each $breakpoint in map-keys($breakpoints) {
115
+ @include media-breakpoint-up($breakpoint, $breakpoints) {
116
+ @for $i from 1 through $columns {
117
+ .item-#{$breakpoint}-#{$i} {
118
+ grid-column: auto / span $i;
119
+ }
120
+ }
50
121
 
51
- // Start with `1` because `0` is and invalid value.
52
- // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
53
- @for $i from 1 through ($columns - 1) {
54
- .start-print-#{$i} {
55
- grid-column-start: $i;
122
+ // Start with `1` because `0` is and invalid value.
123
+ // Ends with `$columns - 1` because offsetting by the width of an entire row isn't possible.
124
+ @for $i from 1 through ($columns - 1) {
125
+ .start-#{$breakpoint}-#{$i} {
126
+ grid-column-start: $i;
127
+ }
128
+ }
56
129
  }
57
- }
58
130
 
59
- // Add classes for reordering
60
- @for $i from -10 through 10 {
61
- .order-print-#{$i} {
62
- order: $i;
131
+ // Add classes for reordering
132
+ @include media-breakpoint-only($breakpoint, $breakpoints) {
133
+ @for $i from -10 through 10 {
134
+ .order-#{$breakpoint}-#{$i} {
135
+ order: $i;
136
+ }
137
+ }
63
138
  }
64
139
  }
140
+
141
+ @include printClasses;
65
142
  }
66
143
  }
@@ -0,0 +1,82 @@
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
+ openToSide?: boolean;
12
+ onClick?: () => void;
13
+ onClose?: () => void;
14
+ }, WithNoStringAndChildrenProps>;
15
+
16
+ export const HoverMenu = withMemo(function HoverMenu({
17
+ children,
18
+ items,
19
+ className,
20
+ style,
21
+ onClick,
22
+ onClose,
23
+ openToSide
24
+ }: HoverMenuProps) {
25
+ // Refs
26
+
27
+ // States/Variables/Selectors
28
+ const hoverItemRef = useRef<HTMLDivElement>(null);
29
+
30
+ const [isOpen, setIsOpen] = useState(false);
31
+ const [position, setPosition] = useState({x: 0, y: 0});
32
+ const [offset, setOffset] = useState({x: 0, y: 0});
33
+
34
+ // Dispatch
35
+
36
+ // Callbacks
37
+ const recalculatePosition = useCallback(() => {
38
+ if (!hoverItemRef.current) {
39
+ return;
40
+ }
41
+ const {top, left, bottom, right, width, height} = hoverItemRef.current.getBoundingClientRect();
42
+ if (openToSide) {
43
+ setPosition({x: right, y: top});
44
+ setOffset({x: width, y: -height});
45
+ } else {
46
+ setPosition({x: left, y: bottom});
47
+ setOffset({x: -width, y: height});
48
+ }
49
+ }, [openToSide]);
50
+
51
+ const close = useCallback(() => {
52
+ setIsOpen(false);
53
+ onClose?.();
54
+ }, [onClose]);
55
+
56
+ const open = useCallback(() => {
57
+ recalculatePosition();
58
+ setIsOpen(true);
59
+ }, [recalculatePosition]);
60
+
61
+ // Effects
62
+
63
+ // Other
64
+
65
+ // RenderFunctions
66
+
67
+ return <Clickable
68
+ onMouseEnter={open}
69
+ onMouseLeave={close}
70
+ useReactOnMouseLeave={true}
71
+ onClick={onClick}
72
+ className={classNames(styles.hoverMenu, {[styles.open]: isOpen}, className)}
73
+ style={style}
74
+ ref={hoverItemRef}
75
+ __allowChildren="all"
76
+ >
77
+ {children}
78
+ <Menu x={position.x} y={position.y} isOpen={true} onClose={close} offsetX={offset.x} offsetY={offset.y} className={classNames({[styles.hidden]: !isOpen})}>
79
+ {items}
80
+ </Menu>
81
+ </Clickable>;
82
+ }, styles);
@@ -1,31 +1,54 @@
1
1
  import * as React from 'react';
2
- import { withMemo } from '../../helper/withMemo';
3
- import { RbmComponentProps, WithNoChildren } from '../RbmComponentProps';
4
- import { Icon, IconSource } from '../Icon/Icon';
5
- import { Block } from '../Layout/Block';
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 { Text } from '../Text/Text';
10
- import { Clickable } from '../Clickable/Clickable';
11
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
12
- import { withRenderBrowserOnly } from '../../helper/withRenderBrowserOnly';
13
- import { useWindow } from '../../WindowContext/WindowContext';
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";
13
+ import {createPortal} from "react-dom";
14
14
 
15
- export type MenuItem = {
15
+ export type MenuItemType = {
16
16
  label: string;
17
- callback: () => void;
18
- icon?: IconSource;
17
+ icon?: IconSource | { icon: IconSource, color: string };
19
18
  key: string;
19
+ className?: string;
20
+ callback: () => void;
21
+ onMouseEnter?: () => void;
22
+ onMouseLeave?: () => void;
20
23
  };
21
24
 
22
25
  export type MenuProps = RbmComponentProps<
23
- { items: MenuItem[]; x: number; y: number; isOpen: boolean; onClose: () => void },
24
- WithNoChildren
26
+ {
27
+ items?: MenuItemType[];
28
+ x: number;
29
+ y: number;
30
+ isOpen: boolean;
31
+ onClose: () => void,
32
+ offsetX?: number,
33
+ offsetY?: number
34
+ }
25
35
  >;
26
36
 
37
+ export const MENU_CONTAINER_CLASS = "rbm-menu-container";
38
+
27
39
  export const Menu = withMemo(
28
- withRenderBrowserOnly(function Menu({ className, style, items, y, x, isOpen, onClose }: MenuProps) {
40
+ withRenderBrowserOnly(function Menu({
41
+ className,
42
+ style,
43
+ items,
44
+ y,
45
+ x,
46
+ isOpen,
47
+ onClose,
48
+ children,
49
+ offsetY = 0,
50
+ offsetX = 0,
51
+ }: MenuProps) {
29
52
  // Variables
30
53
 
31
54
  // Refs
@@ -33,19 +56,16 @@ export const Menu = withMemo(
33
56
  const window = useWindow();
34
57
 
35
58
  // States
59
+ const [portalContainer] = useState<HTMLDivElement>(() => {
60
+ return document.createElement('div');
61
+ });
62
+
36
63
  const [innerX, setInnerX] = useState(x);
37
64
  const [innerY, setInnerY] = useState(y);
38
65
 
39
66
  // Selectors
40
67
 
41
68
  // Callbacks
42
- const callItemCallback = useCallback(
43
- (_: any, cb: () => void) => {
44
- onClose();
45
- cb();
46
- },
47
- [onClose]
48
- );
49
69
 
50
70
  // Effects
51
71
  useEffect(() => {
@@ -55,20 +75,40 @@ export const Menu = withMemo(
55
75
  onClose();
56
76
  }
57
77
  };
58
- window?.addEventListener('mousedown', listener, { capture: true });
59
- return () => window?.removeEventListener('mousedown', listener, { capture: true });
78
+ window?.addEventListener('mousedown', listener, {capture: true});
79
+ return () => window?.removeEventListener('mousedown', listener, {capture: true});
60
80
  }
61
81
  return undefined;
62
82
  }, [isOpen, onClose, window]);
63
83
 
84
+ useLayoutEffect(() => {
85
+ if (!isOpen) {
86
+ return;
87
+ }
88
+ let elem = window?.document.body.querySelector("." + MENU_CONTAINER_CLASS);
89
+ if (!elem) {
90
+ elem = window?.document.body;
91
+ }
92
+ elem?.appendChild(portalContainer)
93
+ }, [isOpen, portalContainer, window?.document.body]);
94
+
95
+
64
96
  useLayoutEffect(() => {
65
97
  if (!menuRef.current) {
66
98
  return;
67
99
  }
68
- const { width } = getComputedStyle(menuRef.current);
69
- const newX = Math.min(x, (window?.innerWidth ?? 0) - parseFloat(width));
100
+ const width = parseFloat(getComputedStyle(menuRef.current).width);
101
+ let newX = x;
102
+ if (newX > (window?.innerWidth ?? 0) - width) {
103
+ newX -= width + offsetX;
104
+ }
105
+
106
+ if (newX < 0) {
107
+ newX = 0;
108
+ }
109
+
70
110
  setInnerX(newX);
71
- }, [window?.innerWidth, x]);
111
+ }, [offsetX, window?.innerWidth, x]);
72
112
 
73
113
  useLayoutEffect(() => {
74
114
  if (!menuRef.current) {
@@ -77,10 +117,15 @@ export const Menu = withMemo(
77
117
  const height = parseFloat(getComputedStyle(menuRef.current).height);
78
118
  let newY = y;
79
119
  if (newY > (window?.innerHeight ?? 0) - height) {
80
- newY -= height;
120
+ newY -= height + offsetY;
121
+ }
122
+
123
+ if (newY < 0) {
124
+ newY = 0;
81
125
  }
126
+
82
127
  setInnerY(newY);
83
- }, [window?.innerHeight, y]);
128
+ }, [offsetY, window?.innerHeight, y]);
84
129
 
85
130
  // Other
86
131
 
@@ -90,23 +135,32 @@ export const Menu = withMemo(
90
135
  }
91
136
 
92
137
  return (
93
- <Block
94
- className={classNames(className, styles.menu)}
95
- style={{ ...style, top: innerY, left: innerX }}
96
- ref={menuRef}
97
- >
98
- {items.map((item) => (
99
- <Clickable
100
- onClick={callItemCallback}
101
- onClickData={item.callback}
102
- className={styles.item}
103
- key={item.key}
104
- >
105
- {!!item.icon && <Icon icon={item.icon} />}
106
- <Text>{item.label}</Text>
107
- </Clickable>
108
- ))}
109
- </Block>
138
+ <>
139
+ {createPortal(
140
+ <MenuCloseContextProvider value={onClose}>
141
+ <Block
142
+ className={classNames(className, styles.menu)}
143
+ style={{...style, top: innerY, left: innerX}}
144
+ ref={menuRef}
145
+ __allowChildren="all"
146
+ >
147
+ {items?.map((item) => {
148
+ const icon = (!!item.icon && typeof item.icon === "object" && "color" in item.icon) ? item.icon.icon : item.icon;
149
+ const iconColor = (!!item.icon && typeof item.icon === "object" && "color" in item.icon) ? item.icon.color : undefined;
150
+
151
+ return <MenuItem key={item.key}
152
+ onClick={item.callback}
153
+ className={classNames(styles.item, item.className)}
154
+ onMouseEnter={item.onMouseEnter}
155
+ icon={icon}
156
+ iconColor={iconColor}
157
+ onMouseLeave={item.onMouseLeave}>{item.label}</MenuItem>;
158
+ })}
159
+ {children}
160
+ </Block>
161
+ </MenuCloseContextProvider>
162
+ , portalContainer)}
163
+ </>
110
164
  );
111
165
  }),
112
166
  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");