@majoo-ui/react 2.2.0 → 2.2.1

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.
@@ -219,23 +219,17 @@ const OptionWrapper = styled('div', {
219
219
  backgroundColor: '$bgWhite',
220
220
  boxShadow: '0px 2px 12px #00000014',
221
221
  width: '100%',
222
- maxHeight: '300px',
223
- position: 'absolute',
224
- float: 'left',
225
- top: '100%',
226
- left: '0',
222
+ // In-flow on purpose: Popover's flip/shift/size measure the floating element,
223
+ // which is only as tall as this wrapper. An absolute wrapper measures 0px and
224
+ // the dropdown never flips. The cap shrinks to the space Popover reports.
225
+ maxHeight: 'min(300px, var(--majoo-popover-available-height, 300px))',
226
+ position: 'relative',
227
227
  variants: {
228
+ // Kept for API compatibility only — Popover (floating-ui) owns placement now,
229
+ // treating it as a preference that flips when the preferred side lacks room.
228
230
  placement: {
229
- bottom: {
230
- top: '100%',
231
- left: '0',
232
- transform: 'none'
233
- },
234
- top: {
235
- top: '0',
236
- left: '0',
237
- transform: 'translate(0, -100%)'
238
- }
231
+ bottom: {},
232
+ top: {}
239
233
  },
240
234
  withFooter: {
241
235
  true: {
@@ -683,13 +677,8 @@ const InputSelect = /*#__PURE__*/React.forwardRef((_a, ref) => {
683
677
  position: 'absolute',
684
678
  inset: 0,
685
679
  zIndex: '$tooltip',
686
- top: item.length > 0 ? '0px' : placement === 'top' ? '-20px' : '20px',
687
- // bottom: '80px',
688
- width: '100%',
689
- height: scrollerRef.current ? scrollerRef.current.clientHeight : '40px',
690
680
  backgroundColor: '$bgWhite',
691
- opacity: 0.8,
692
- transform: placement === 'top' ? 'translate(0, -100%)' : 'none'
681
+ opacity: 0.8
693
682
  },
694
683
  children: jsx(Text, {
695
684
  variant: "caption",
@@ -699,9 +688,10 @@ const InputSelect = /*#__PURE__*/React.forwardRef((_a, ref) => {
699
688
  condition: renderFooter && typeof renderFooter === 'function',
700
689
  wrapper: child => jsxs(Box, {
701
690
  css: {
702
- position: 'absolute',
703
- float: 'left',
704
- display: isOpen ? 'block' : 'none',
691
+ position: 'relative',
692
+ display: isOpen ? 'flex' : 'none',
693
+ flexDirection: 'column',
694
+ maxHeight: 'var(--majoo-popover-available-height, calc(100vh - 160px))',
705
695
  zIndex: '$popover',
706
696
  width: '100%'
707
697
  },
@@ -714,11 +704,16 @@ const InputSelect = /*#__PURE__*/React.forwardRef((_a, ref) => {
714
704
  }, {
715
705
  suppressRefError: true
716
706
  }), {
717
- css: Object.assign({
707
+ css: Object.assign(Object.assign({
718
708
  display: isOpen ? 'block' : 'none',
719
709
  zIndex: '$popover',
720
- pointerEvents: 'auto'
721
- }, optionCss),
710
+ pointerEvents: 'auto',
711
+ minHeight: isLoading ? '40px' : undefined
712
+ }, renderFooter && typeof renderFooter === 'function' ? {
713
+ flex: 1,
714
+ minHeight: 0,
715
+ maxHeight: 'none'
716
+ } : {}), optionCss),
722
717
  withFooter: renderFooter && typeof renderFooter === 'function',
723
718
  placement: placement,
724
719
  children: [!search && embedSearch && isOpen && jsx(SelectContainer, {
@@ -36,6 +36,9 @@ import { ConditionalWrapper } from '../../../../wrapper/ConditionalWrapper.esm.j
36
36
  import OptionGroupItem from './OptionGroupItem.esm.js';
37
37
  import { useMountedLayoutEffect } from 'react-table';
38
38
  import { useVirtual } from 'react-virtual';
39
+ import { useFloating, autoUpdate } from '@floating-ui/react';
40
+ import { Portal } from '@majoo-ui/helpers';
41
+ import { createPopoverMiddleware } from '../../../popover/middleware.esm.js';
39
42
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
40
43
 
41
44
  const startTransition = React__default.startTransition || (callback => {
@@ -223,34 +226,23 @@ const TagWrapper = styled(Box, {
223
226
  margin: '10px 0',
224
227
  gap: '5px'
225
228
  });
229
+
230
+ // Positioned by floating-ui (see `useFloating` below) and portalled, so it is
231
+ // neither clipped by an `overflow: hidden` ancestor nor pushed off the viewport.
232
+ // Its children stay in-flow: floating-ui measures this element to decide flips.
226
233
  const OptionWrapper = styled('div', {
227
234
  display: 'none',
228
235
  overflow: 'hidden',
229
- zIndex: 12,
236
+ zIndex: '$tooltip',
237
+ pointerEvents: 'auto',
230
238
  borderRadius: '$sm',
231
239
  backgroundColor: '$bgWhite',
232
240
  boxShadow: '0px 2px 12px #00000014',
233
- width: '100%',
234
- // height: '160px',
235
- position: 'absolute',
236
- float: 'left',
237
- // top: '40px',
238
- left: '0px',
241
+ width: 'var(--majoo-popover-reference-width, 100%)',
239
242
  color: '$textPrimary',
240
- variants: {
241
- placement: {
242
- bottom: {
243
- top: '100%',
244
- left: '0',
245
- transform: 'none'
246
- },
247
- top: {
248
- top: '0',
249
- left: '0',
250
- transform: 'translate(0, -100%)'
251
- }
252
- }
253
- }
243
+ fontFamily: 'inherit',
244
+ fontSize: '$short-content-regular',
245
+ textAlign: 'left'
254
246
  });
255
247
  const Option = styled('div', {
256
248
  flexDirection: 'column',
@@ -258,7 +250,7 @@ const Option = styled('div', {
258
250
  borderRadius: '$lg',
259
251
  overflowY: 'auto',
260
252
  padding: '8px 16px',
261
- maxHeight: '160px',
253
+ maxHeight: 'min(160px, var(--majoo-popover-available-height, 160px))',
262
254
  height: 'auto',
263
255
  '@sm': {
264
256
  borderRadius: 'unset'
@@ -815,6 +807,20 @@ const InputSelectCheck = /*#__PURE__*/forwardRef(({
815
807
  }
816
808
  }
817
809
  });
810
+
811
+ // downshift owns open/close and outside-click; floating-ui only positions the
812
+ // menu. The floating ref is attached only while the menu is shown, so a closed
813
+ // select does not keep autoUpdate's scroll/resize listeners alive.
814
+ // `placement` is a preference: the menu flips when that side lacks room.
815
+ const isMenuShown = isOpen && disabled === false;
816
+ const floating = useFloating({
817
+ open: isMenuShown,
818
+ placement,
819
+ whileElementsMounted: autoUpdate,
820
+ middleware: createPopoverMiddleware({
821
+ sideOffset: 4
822
+ })
823
+ });
818
824
  const memoOptions = useMemo(() => {
819
825
  // Skip expensive operations for very large datasets
820
826
  const isVeryLarge = option?.length > 10000;
@@ -1019,6 +1025,7 @@ const InputSelectCheck = /*#__PURE__*/forwardRef(({
1019
1025
  mobileOnly: mobileOnly,
1020
1026
  desktopOnly: desktopOnly,
1021
1027
  children: [/*#__PURE__*/jsxs(Select, {
1028
+ ref: floating.refs.setReference,
1022
1029
  size: size,
1023
1030
  disabled: disabled || readOnly,
1024
1031
  isInvalid: isInvalid,
@@ -1162,152 +1169,153 @@ const InputSelectCheck = /*#__PURE__*/forwardRef(({
1162
1169
  },
1163
1170
  color: "brand",
1164
1171
  children: `${totalOptions ? totalOptions : selectedItems?.filter(item => item?.id !== '!all')?.length} ${labelCounter || t('labelCounterText', 'Item terpilih')}`
1165
- }), /*#__PURE__*/jsxs(OptionWrapper, {
1166
- placement: placement,
1167
- css: {
1168
- display: isOpen && disabled === false ? 'block' : 'none',
1169
- top: size === 'lg' ? '45px' : '35px',
1170
- minHeight: isLoading ? 40 : 0
1171
- },
1172
- children: [isLoading && /*#__PURE__*/jsx(Flex, {
1173
- justify: "center",
1174
- align: "center",
1172
+ }), /*#__PURE__*/jsx(Portal, {
1173
+ children: /*#__PURE__*/jsxs(OptionWrapper, {
1174
+ ref: isMenuShown ? floating.refs.setFloating : null,
1175
+ style: {
1176
+ position: floating.strategy,
1177
+ top: floating.y ?? 0,
1178
+ left: floating.x ?? 0
1179
+ },
1175
1180
  css: {
1176
- position: 'absolute',
1177
- inset: 0,
1178
- zIndex: '$tooltip',
1179
- top: items.length > 0 ? '0px' : placement === 'top' ? '-20px' : '0px',
1180
- // bottom: '80px',
1181
- width: '100%',
1182
- minHeight: 40,
1183
- height: scrollerRef.current ? scrollerRef.current.clientHeight : '40px',
1184
- backgroundColor: '$bgWhite',
1185
- opacity: 0.8,
1186
- transform: placement === 'top' ? 'translate(0, -100%)' : 'none'
1181
+ display: isMenuShown ? 'block' : 'none',
1182
+ minHeight: isLoading ? 40 : 0
1187
1183
  },
1188
- children: /*#__PURE__*/jsx(Text, {
1189
- variant: "caption",
1190
- children: "Loading...."
1191
- })
1192
- }), items.length > 0 || isLoading ? /*#__PURE__*/jsx(Option, {
1193
- ...getMenuProps({
1194
- ref: scrollerRef
1195
- }, {
1196
- suppressRefError: true
1197
- }),
1198
- css: optionListCss,
1199
- children: /*#__PURE__*/jsx(ConditionalWrapper, {
1200
- condition: !!fetchData && items.length > 0,
1201
- wrapper: child => /*#__PURE__*/jsx(InfiniteScroll, {
1202
- loadMore: () => {
1203
- if (!isLoading) {
1204
- if (scrollerRef.current) {
1205
- scrollerRef.current.scrollBy(0, -5);
1184
+ children: [isLoading && /*#__PURE__*/jsx(Flex, {
1185
+ justify: "center",
1186
+ align: "center",
1187
+ css: {
1188
+ position: 'absolute',
1189
+ inset: 0,
1190
+ zIndex: '$tooltip',
1191
+ minHeight: 40,
1192
+ backgroundColor: '$bgWhite',
1193
+ opacity: 0.8
1194
+ },
1195
+ children: /*#__PURE__*/jsx(Text, {
1196
+ variant: "caption",
1197
+ children: "Loading...."
1198
+ })
1199
+ }), items.length > 0 || isLoading ? /*#__PURE__*/jsx(Option, {
1200
+ ...getMenuProps({
1201
+ ref: scrollerRef
1202
+ }, {
1203
+ suppressRefError: true
1204
+ }),
1205
+ css: optionListCss,
1206
+ children: /*#__PURE__*/jsx(ConditionalWrapper, {
1207
+ condition: !!fetchData && items.length > 0,
1208
+ wrapper: child => /*#__PURE__*/jsx(InfiniteScroll, {
1209
+ loadMore: () => {
1210
+ if (!isLoading) {
1211
+ if (scrollerRef.current) {
1212
+ scrollerRef.current.scrollBy(0, -5);
1213
+ }
1214
+ fetchData?.({
1215
+ query
1216
+ });
1206
1217
  }
1207
- fetchData?.({
1208
- query
1209
- });
1210
- }
1211
- },
1212
- hasMore: true,
1213
- threshold: 0,
1214
- useWindow: false,
1215
- children: child
1216
- }, isLoading),
1217
- children: isOpen && shouldVirtualize ?
1218
- /*#__PURE__*/
1219
- // Virtualized rendering for large lists
1220
- jsx(Fragment, {
1221
- children: /*#__PURE__*/jsx("div", {
1222
- style: {
1223
- height: `${rowVirtualizer.totalSize}px`,
1224
- position: 'relative',
1225
- width: '100%'
1226
1218
  },
1227
- children: rowVirtualizer.virtualItems.map(virtualRow => {
1228
- const virtualItem = getItemAtIndex(virtualRow.index);
1229
- if (!virtualItem) return null;
1230
- const {
1231
- type,
1232
- item
1233
- } = virtualItem;
1234
- return /*#__PURE__*/jsx("div", {
1235
- style: {
1236
- position: 'absolute',
1237
- top: 0,
1238
- left: 0,
1239
- width: '100%',
1240
- transform: `translateY(${virtualRow.start}px)`
1241
- },
1242
- children: type === 'group' ?
1243
- /*#__PURE__*/
1244
- // Render option group header (without children, as they're rendered separately)
1245
- jsxs(ItemWrapper, {
1246
- ...getItemProps({
1247
- item,
1248
- index: virtualRow.index,
1249
- disabled: item.isDisabled
1250
- }),
1251
- css: {
1252
- backgroundColor: highlightedIndex === virtualRow.index ? '$bgGray' : '$bgWhite',
1253
- cursor: item.isDisabled ? 'not-allowed' : 'pointer',
1254
- color: item.isDisabled ? '$textDisable' : 'textPrimary',
1255
- fontWeight: 600
1219
+ hasMore: true,
1220
+ threshold: 0,
1221
+ useWindow: false,
1222
+ children: child
1223
+ }, isLoading),
1224
+ children: isOpen && shouldVirtualize ?
1225
+ /*#__PURE__*/
1226
+ // Virtualized rendering for large lists
1227
+ jsx(Fragment, {
1228
+ children: /*#__PURE__*/jsx("div", {
1229
+ style: {
1230
+ height: `${rowVirtualizer.totalSize}px`,
1231
+ position: 'relative',
1232
+ width: '100%'
1233
+ },
1234
+ children: rowVirtualizer.virtualItems.map(virtualRow => {
1235
+ const virtualItem = getItemAtIndex(virtualRow.index);
1236
+ if (!virtualItem) return null;
1237
+ const {
1238
+ type,
1239
+ item
1240
+ } = virtualItem;
1241
+ return /*#__PURE__*/jsx("div", {
1242
+ style: {
1243
+ position: 'absolute',
1244
+ top: 0,
1245
+ left: 0,
1246
+ width: '100%',
1247
+ transform: `translateY(${virtualRow.start}px)`
1256
1248
  },
1257
- children: [/*#__PURE__*/jsx(IconWrapper, {
1249
+ children: type === 'group' ?
1250
+ /*#__PURE__*/
1251
+ // Render option group header (without children, as they're rendered separately)
1252
+ jsxs(ItemWrapper, {
1253
+ ...getItemProps({
1254
+ item,
1255
+ index: virtualRow.index,
1256
+ disabled: item.isDisabled
1257
+ }),
1258
1258
  css: {
1259
- width: '16px',
1260
- height: '16px',
1261
- border: '1px solid $bgBorder',
1259
+ backgroundColor: highlightedIndex === virtualRow.index ? '$bgGray' : '$bgWhite',
1262
1260
  cursor: item.isDisabled ? 'not-allowed' : 'pointer',
1263
- ...checkBoxCss
1261
+ color: item.isDisabled ? '$textDisable' : 'textPrimary',
1262
+ fontWeight: 600
1264
1263
  },
1265
- children: !item?.hideCheck && item.isDisabled ? /*#__PURE__*/jsx(CheckOutline, {
1266
- color: colors.iconDisable
1267
- }) : item?.children?.filter(e => selectedValueSet.has(e.id) && !e.isDisabled)?.length === item?.children?.filter(e => !e.isDisabled)?.length ? /*#__PURE__*/jsx(CheckOutline, {
1268
- color: colors.iconBrand
1269
- }) : item?.children?.filter(e => selectedValueSet.has(e.id) && !e.isDisabled)?.length > 0 ? /*#__PURE__*/jsx(IndeterminateFilled, {
1270
- children: "-"
1271
- }) : null
1272
- }), /*#__PURE__*/jsx(Item, {
1273
- css: {
1274
- backgroundColor: 'unset',
1275
- width: '100%'
1276
- },
1277
- children: item.render ? item.render?.(item.name) : item.name
1278
- })]
1279
- }) :
1280
- // Render regular item or child item
1281
- renderOptionItem(item, virtualRow.index, type === 'child')
1282
- }, `${type}-${item.id}-${virtualRow.index}`);
1264
+ children: [/*#__PURE__*/jsx(IconWrapper, {
1265
+ css: {
1266
+ width: '16px',
1267
+ height: '16px',
1268
+ border: '1px solid $bgBorder',
1269
+ cursor: item.isDisabled ? 'not-allowed' : 'pointer',
1270
+ ...checkBoxCss
1271
+ },
1272
+ children: !item?.hideCheck && item.isDisabled ? /*#__PURE__*/jsx(CheckOutline, {
1273
+ color: colors.iconDisable
1274
+ }) : item?.children?.filter(e => selectedValueSet.has(e.id) && !e.isDisabled)?.length === item?.children?.filter(e => !e.isDisabled)?.length ? /*#__PURE__*/jsx(CheckOutline, {
1275
+ color: colors.iconBrand
1276
+ }) : item?.children?.filter(e => selectedValueSet.has(e.id) && !e.isDisabled)?.length > 0 ? /*#__PURE__*/jsx(IndeterminateFilled, {
1277
+ children: "-"
1278
+ }) : null
1279
+ }), /*#__PURE__*/jsx(Item, {
1280
+ css: {
1281
+ backgroundColor: 'unset',
1282
+ width: '100%'
1283
+ },
1284
+ children: item.render ? item.render?.(item.name) : item.name
1285
+ })]
1286
+ }) :
1287
+ // Render regular item or child item
1288
+ renderOptionItem(item, virtualRow.index, type === 'child')
1289
+ }, `${type}-${item.id}-${virtualRow.index}`);
1290
+ })
1283
1291
  })
1284
- })
1285
- }) :
1286
- // Non-virtualized rendering for small lists
1287
- isOpen && items.map((item, index) => item?.children ? renderOptionGroupItem(item) : renderOptionItem(item, index))
1288
- })
1289
- }) : /*#__PURE__*/jsx(Option, {
1290
- css: {
1291
- height: 'auto'
1292
- },
1293
- ...getMenuProps({}, {
1294
- suppressRefError: true
1295
- }),
1296
- children: /*#__PURE__*/jsxs(StyledDiv, {
1297
- children: [/*#__PURE__*/jsx("div", {
1298
- style: {
1299
- marginRight: '6px'
1300
- },
1301
- children: /*#__PURE__*/jsx(CircleInfoOutline, {
1302
- size: "16px",
1303
- color: colors.iconDisable,
1292
+ }) :
1293
+ // Non-virtualized rendering for small lists
1294
+ isOpen && items.map((item, index) => item?.children ? renderOptionGroupItem(item) : renderOptionItem(item, index))
1295
+ })
1296
+ }) : /*#__PURE__*/jsx(Option, {
1297
+ css: {
1298
+ height: 'auto'
1299
+ },
1300
+ ...getMenuProps({}, {
1301
+ suppressRefError: true
1302
+ }),
1303
+ children: /*#__PURE__*/jsxs(StyledDiv, {
1304
+ children: [/*#__PURE__*/jsx("div", {
1304
1305
  style: {
1305
- verticalAlign: 'text-bottom'
1306
- }
1307
- })
1308
- }), textEmptyItem ?? t('emptyItemText', 'Item tidak ditemukan')]
1309
- })
1310
- })]
1306
+ marginRight: '6px'
1307
+ },
1308
+ children: /*#__PURE__*/jsx(CircleInfoOutline, {
1309
+ size: "16px",
1310
+ color: colors.iconDisable,
1311
+ style: {
1312
+ verticalAlign: 'text-bottom'
1313
+ }
1314
+ })
1315
+ }), textEmptyItem ?? t('emptyItemText', 'Item tidak ditemukan')]
1316
+ })
1317
+ })]
1318
+ })
1311
1319
  })]
1312
1320
  });
1313
1321
  });
@@ -34,7 +34,6 @@ function MapPicker(props) {
34
34
  const isDesktop = useMediaQuery('(min-width: 767px)');
35
35
  const {
36
36
  center,
37
- setCenter,
38
37
  places,
39
38
  searchQuery,
40
39
  searchResult,
@@ -49,12 +48,15 @@ function MapPicker(props) {
49
48
  showSelectButton,
50
49
  onChange
51
50
  });
51
+ // Keyed on the coordinates, not the object: a parent re-rendering with a new but equal
52
+ // `value` must not pull the map back from where the user dragged it. handleSearchLatLng is
53
+ // left out because it changes with every new `onChange`, which InputMapPicker passes inline.
52
54
  useEffect(() => {
53
55
  if (value && (value === null || value === void 0 ? void 0 : value.lat) && (value === null || value === void 0 ? void 0 : value.lng)) {
54
- setCenter(value);
55
56
  handleSearchLatLng(value);
56
57
  }
57
- }, [value, setCenter]);
58
+ // eslint-disable-next-line react-hooks/exhaustive-deps
59
+ }, [value === null || value === void 0 ? void 0 : value.lat, value === null || value === void 0 ? void 0 : value.lng]);
58
60
  function handleMarkerClick() {
59
61
  if (places.length > 0) {
60
62
  const mapObject = {
@@ -82,7 +82,19 @@ function useMapSearch({
82
82
  }, abortControllerRef.current.signal);
83
83
  // getFullAddress returns null if aborted
84
84
  if (fullAddress) {
85
- handleSelect(fullAddress);
85
+ // Keep the chosen item's coordinates; the reverse geocode only supplies the address.
86
+ const {
87
+ display_name,
88
+ place_id,
89
+ address
90
+ } = fullAddress;
91
+ handleSelect({
92
+ lat: item.lat,
93
+ lon: item.lon,
94
+ display_name,
95
+ place_id,
96
+ address
97
+ });
86
98
  }
87
99
  }), [getFullAddress, handleSelect]);
88
100
  const handleSearchLatLng = useCallback(latLng => __awaiter(this, void 0, void 0, function* () {
@@ -90,19 +102,26 @@ function useMapSearch({
90
102
  // Cancel any previous pending request
91
103
  (_b = abortControllerRef.current) === null || _b === void 0 ? void 0 : _b.abort();
92
104
  abortControllerRef.current = new AbortController();
105
+ // Move the center now, so a re-render while the address loads (or a failed lookup)
106
+ // does not send the map back to the previous point.
107
+ setCenter({
108
+ lat: Number(latLng.lat),
109
+ lng: Number(latLng.lng)
110
+ });
93
111
  const fullAddress = yield getFullAddress(latLng, abortControllerRef.current.signal);
94
112
  // getFullAddress returns null if aborted
95
113
  if (fullAddress) {
114
+ // Nominatim answers with the coordinates of the OSM object it matched (for a large
115
+ // polygon such as a golf course, its centroid), not the point that was asked about.
116
+ // Keep the requested point; the reverse geocode only supplies the address.
96
117
  const {
97
- lat,
98
- lon,
99
118
  display_name,
100
119
  place_id,
101
120
  address
102
121
  } = fullAddress;
103
122
  handleSelect({
104
- lat,
105
- lon,
123
+ lat: String(latLng.lat),
124
+ lon: String(latLng.lng),
106
125
  display_name,
107
126
  place_id,
108
127
  address
@@ -1,9 +1,10 @@
1
- import { useFloating, offset, shift, flip, useInteractions, useClick, useDismiss, FloatingFocusManager, FloatingPortal } from '@floating-ui/react';
1
+ import { useFloating, autoUpdate, useInteractions, useClick, useDismiss, FloatingFocusManager, FloatingPortal } from '@floating-ui/react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useState, useEffect, useContext, cloneElement, createContext } from 'react';
4
4
  import Flex from '../../layout/flex/Flex.esm.js';
5
5
  import { foundations } from '@majoo-ui/core';
6
6
  import { ConditionalWrapper } from '../../wrapper/ConditionalWrapper.esm.js';
7
+ import { createPopoverMiddleware } from './middleware.esm.js';
7
8
  import { jsx, Fragment } from 'react/jsx-runtime';
8
9
 
9
10
  const {
@@ -17,7 +18,7 @@ const Popover = ({
17
18
  placement,
18
19
  sideOffset = 5,
19
20
  hideOnScroll = false,
20
- flip: flip$1 = true,
21
+ flip = true,
21
22
  onClose
22
23
  }) => {
23
24
  const [openState, setOpenState] = useState(false);
@@ -27,9 +28,17 @@ const Popover = ({
27
28
  useEffect(() => {
28
29
  if (onOpenChange) onOpenChange(openState);
29
30
  }, [openState, onOpenChange]);
31
+
32
+ // floating-ui measures the element PopoverContent renders, so its children must
33
+ // be in-flow (see createPopoverMiddleware). autoUpdate recomputes when content
34
+ // height changes after mount (async options, a menu toggling display).
30
35
  const floatingHook = useFloating({
31
36
  open: openState,
32
- middleware: [offset(sideOffset), shift(), flip$1 && flip()],
37
+ whileElementsMounted: autoUpdate,
38
+ middleware: createPopoverMiddleware({
39
+ sideOffset,
40
+ flip
41
+ }),
33
42
  onOpenChange: isOpen => {
34
43
  setOpenState(isOpen);
35
44
  !isOpen && onClose?.();
@@ -0,0 +1,43 @@
1
+ import { offset, flip, shift, size } from '@floating-ui/react';
2
+
3
+ /**
4
+ * The floating-ui middleware every majoo-ui dropdown shares, so Popover and the
5
+ * components that drive floating-ui themselves (InputSelectCheck) place content
6
+ * the same way.
7
+ *
8
+ * - `flip` treats `placement` as a preference: the other side is used when the
9
+ * preferred one lacks room.
10
+ * - `size` writes two CSS variables on the floating element, which consumers opt
11
+ * into: `--majoo-popover-available-height` (room left on the chosen side) and
12
+ * `--majoo-popover-reference-width` (width of the trigger).
13
+ *
14
+ * floating-ui measures the floating element itself, so its content must be
15
+ * in-flow — an all-absolute child measures 0px and flip/shift never fire.
16
+ */
17
+ // Floor for the reported available height. Without it, flip and size lock each
18
+ // other: a menu that opens while still `display: none` measures 0px, flip keeps a
19
+ // side with almost no room, size reports ~0px, the menu's max-height collapses to
20
+ // 0 and never changes size again — so autoUpdate never re-runs flip. With a floor
21
+ // the box always has real height, flip moves it to the roomier side, and size then
22
+ // grows it from there.
23
+ const MIN_AVAILABLE_HEIGHT = 120;
24
+ const createPopoverMiddleware = ({
25
+ sideOffset = 5,
26
+ flip: flip$1 = true
27
+ } = {}) => [offset(sideOffset), flip$1 && flip({
28
+ padding: 8
29
+ }), shift({
30
+ padding: 8
31
+ }), size({
32
+ padding: 8,
33
+ apply({
34
+ availableHeight,
35
+ rects,
36
+ elements
37
+ }) {
38
+ elements.floating.style.setProperty('--majoo-popover-available-height', `${Math.max(MIN_AVAILABLE_HEIGHT, availableHeight)}px`);
39
+ elements.floating.style.setProperty('--majoo-popover-reference-width', `${rects.reference.width}px`);
40
+ }
41
+ })].filter(Boolean);
42
+
43
+ export { createPopoverMiddleware };
package/index.esm.css CHANGED
@@ -2,6 +2,251 @@
2
2
  outline: none;
3
3
  }
4
4
 
5
+ .ReactCrop {
6
+ position: relative;
7
+ display: inline-block;
8
+ cursor: crosshair;
9
+ overflow: hidden;
10
+ max-width: 100%;
11
+ }
12
+ .ReactCrop *,
13
+ .ReactCrop *::before,
14
+ .ReactCrop *::after {
15
+ box-sizing: border-box;
16
+ }
17
+ .ReactCrop--disabled,
18
+ .ReactCrop--locked {
19
+ cursor: inherit;
20
+ }
21
+ .ReactCrop__child-wrapper {
22
+ max-height: inherit;
23
+ }
24
+ .ReactCrop__child-wrapper > img,
25
+ .ReactCrop__child-wrapper > video {
26
+ display: block;
27
+ max-width: 100%;
28
+ max-height: inherit;
29
+ touch-action: none;
30
+ }
31
+ .ReactCrop__crop-selection {
32
+ position: absolute;
33
+ top: 0;
34
+ left: 0;
35
+ transform: translate3d(0, 0, 0);
36
+ cursor: move;
37
+ box-shadow: 0 0 0 9999em rgba(0, 0, 0, 0.5);
38
+ touch-action: none;
39
+ border: 1px dashed #fff;
40
+ }
41
+ .ReactCrop--disabled .ReactCrop__crop-selection {
42
+ cursor: inherit;
43
+ }
44
+ .ReactCrop--circular-crop .ReactCrop__crop-selection {
45
+ border-radius: 50%;
46
+ box-shadow: 0px 0px 1px 1px #fff, 0 0 0 9999em rgba(0, 0, 0, 0.5);
47
+ }
48
+ .ReactCrop__crop-selection:focus {
49
+ outline: none;
50
+ border-color: blue;
51
+ border-style: solid;
52
+ }
53
+ .ReactCrop--invisible-crop .ReactCrop__crop-selection {
54
+ display: none;
55
+ }
56
+ .ReactCrop__rule-of-thirds-vt::before,
57
+ .ReactCrop__rule-of-thirds-vt::after,
58
+ .ReactCrop__rule-of-thirds-hz::before,
59
+ .ReactCrop__rule-of-thirds-hz::after {
60
+ content: '';
61
+ display: block;
62
+ position: absolute;
63
+ background-color: rgba(255, 255, 255, 0.4);
64
+ }
65
+ .ReactCrop__rule-of-thirds-vt::before,
66
+ .ReactCrop__rule-of-thirds-vt::after {
67
+ width: 1px;
68
+ height: 100%;
69
+ }
70
+ .ReactCrop__rule-of-thirds-vt::before {
71
+ left: 33.3333%;
72
+ left: 33.3333333333%;
73
+ }
74
+ .ReactCrop__rule-of-thirds-vt::after {
75
+ left: 66.6666%;
76
+ left: 66.6666666667%;
77
+ }
78
+ .ReactCrop__rule-of-thirds-hz::before,
79
+ .ReactCrop__rule-of-thirds-hz::after {
80
+ width: 100%;
81
+ height: 1px;
82
+ }
83
+ .ReactCrop__rule-of-thirds-hz::before {
84
+ top: 33.3333%;
85
+ top: 33.3333333333%;
86
+ }
87
+ .ReactCrop__rule-of-thirds-hz::after {
88
+ top: 66.6666%;
89
+ top: 66.6666666667%;
90
+ }
91
+ .ReactCrop__drag-handle {
92
+ position: absolute;
93
+ }
94
+ .ReactCrop__drag-handle::after {
95
+ position: absolute;
96
+ content: '';
97
+ display: block;
98
+ width: 10px;
99
+ height: 10px;
100
+ background-color: rgba(0, 0, 0, 0.2);
101
+ border: 1px solid rgba(255, 255, 255, 0.7);
102
+ outline: 1px solid rgba(0, 0, 0, 0);
103
+ }
104
+ .ReactCrop__drag-handle:focus::after {
105
+ border-color: blue;
106
+ background: #2dbfff;
107
+ }
108
+ .ReactCrop .ord-nw {
109
+ top: 0;
110
+ left: 0;
111
+ margin-top: -5px;
112
+ margin-left: -5px;
113
+ cursor: nw-resize;
114
+ }
115
+ .ReactCrop .ord-nw::after {
116
+ top: 0;
117
+ left: 0;
118
+ }
119
+ .ReactCrop .ord-n {
120
+ top: 0;
121
+ left: 50%;
122
+ margin-top: -5px;
123
+ margin-left: -5px;
124
+ cursor: n-resize;
125
+ }
126
+ .ReactCrop .ord-n::after {
127
+ top: 0;
128
+ }
129
+ .ReactCrop .ord-ne {
130
+ top: 0;
131
+ right: 0;
132
+ margin-top: -5px;
133
+ margin-right: -5px;
134
+ cursor: ne-resize;
135
+ }
136
+ .ReactCrop .ord-ne::after {
137
+ top: 0;
138
+ right: 0;
139
+ }
140
+ .ReactCrop .ord-e {
141
+ top: 50%;
142
+ right: 0;
143
+ margin-top: -5px;
144
+ margin-right: -5px;
145
+ cursor: e-resize;
146
+ }
147
+ .ReactCrop .ord-e::after {
148
+ right: 0;
149
+ }
150
+ .ReactCrop .ord-se {
151
+ bottom: 0;
152
+ right: 0;
153
+ margin-bottom: -5px;
154
+ margin-right: -5px;
155
+ cursor: se-resize;
156
+ }
157
+ .ReactCrop .ord-se::after {
158
+ bottom: 0;
159
+ right: 0;
160
+ }
161
+ .ReactCrop .ord-s {
162
+ bottom: 0;
163
+ left: 50%;
164
+ margin-bottom: -5px;
165
+ margin-left: -5px;
166
+ cursor: s-resize;
167
+ }
168
+ .ReactCrop .ord-s::after {
169
+ bottom: 0;
170
+ }
171
+ .ReactCrop .ord-sw {
172
+ bottom: 0;
173
+ left: 0;
174
+ margin-bottom: -5px;
175
+ margin-left: -5px;
176
+ cursor: sw-resize;
177
+ }
178
+ .ReactCrop .ord-sw::after {
179
+ bottom: 0;
180
+ left: 0;
181
+ }
182
+ .ReactCrop .ord-w {
183
+ top: 50%;
184
+ left: 0;
185
+ margin-top: -5px;
186
+ margin-left: -5px;
187
+ cursor: w-resize;
188
+ }
189
+ .ReactCrop .ord-w::after {
190
+ left: 0;
191
+ }
192
+ .ReactCrop__disabled .ReactCrop__drag-handle {
193
+ cursor: inherit;
194
+ }
195
+ .ReactCrop__drag-bar {
196
+ position: absolute;
197
+ }
198
+ .ReactCrop__drag-bar.ord-n {
199
+ top: 0;
200
+ left: 0;
201
+ width: 100%;
202
+ height: 6px;
203
+ margin-top: -3px;
204
+ }
205
+ .ReactCrop__drag-bar.ord-e {
206
+ right: 0;
207
+ top: 0;
208
+ width: 6px;
209
+ height: 100%;
210
+ margin-right: -3px;
211
+ }
212
+ .ReactCrop__drag-bar.ord-s {
213
+ bottom: 0;
214
+ left: 0;
215
+ width: 100%;
216
+ height: 6px;
217
+ margin-bottom: -3px;
218
+ }
219
+ .ReactCrop__drag-bar.ord-w {
220
+ top: 0;
221
+ left: 0;
222
+ width: 6px;
223
+ height: 100%;
224
+ margin-left: -3px;
225
+ }
226
+ .ReactCrop--new-crop .ReactCrop__drag-bar,
227
+ .ReactCrop--new-crop .ReactCrop__drag-handle,
228
+ .ReactCrop--fixed-aspect .ReactCrop__drag-bar {
229
+ display: none;
230
+ }
231
+ .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-n,
232
+ .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-e,
233
+ .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-s,
234
+ .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-w {
235
+ display: none;
236
+ }
237
+ @media (pointer: coarse) {
238
+ .ReactCrop .ord-n,
239
+ .ReactCrop .ord-e,
240
+ .ReactCrop .ord-s,
241
+ .ReactCrop .ord-w {
242
+ display: none;
243
+ }
244
+ .ReactCrop__drag-handle {
245
+ width: 24px;
246
+ height: 24px;
247
+ }
248
+ }
249
+
5
250
  .rdw-option-wrapper {
6
251
  padding: 5px;
7
252
  min-width: 25px;
@@ -1048,248 +1293,3 @@
1048
1293
  .public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset {
1049
1294
  counter-reset: ol4;
1050
1295
  }
1051
-
1052
- .ReactCrop {
1053
- position: relative;
1054
- display: inline-block;
1055
- cursor: crosshair;
1056
- overflow: hidden;
1057
- max-width: 100%;
1058
- }
1059
- .ReactCrop *,
1060
- .ReactCrop *::before,
1061
- .ReactCrop *::after {
1062
- box-sizing: border-box;
1063
- }
1064
- .ReactCrop--disabled,
1065
- .ReactCrop--locked {
1066
- cursor: inherit;
1067
- }
1068
- .ReactCrop__child-wrapper {
1069
- max-height: inherit;
1070
- }
1071
- .ReactCrop__child-wrapper > img,
1072
- .ReactCrop__child-wrapper > video {
1073
- display: block;
1074
- max-width: 100%;
1075
- max-height: inherit;
1076
- touch-action: none;
1077
- }
1078
- .ReactCrop__crop-selection {
1079
- position: absolute;
1080
- top: 0;
1081
- left: 0;
1082
- transform: translate3d(0, 0, 0);
1083
- cursor: move;
1084
- box-shadow: 0 0 0 9999em rgba(0, 0, 0, 0.5);
1085
- touch-action: none;
1086
- border: 1px dashed #fff;
1087
- }
1088
- .ReactCrop--disabled .ReactCrop__crop-selection {
1089
- cursor: inherit;
1090
- }
1091
- .ReactCrop--circular-crop .ReactCrop__crop-selection {
1092
- border-radius: 50%;
1093
- box-shadow: 0px 0px 1px 1px #fff, 0 0 0 9999em rgba(0, 0, 0, 0.5);
1094
- }
1095
- .ReactCrop__crop-selection:focus {
1096
- outline: none;
1097
- border-color: blue;
1098
- border-style: solid;
1099
- }
1100
- .ReactCrop--invisible-crop .ReactCrop__crop-selection {
1101
- display: none;
1102
- }
1103
- .ReactCrop__rule-of-thirds-vt::before,
1104
- .ReactCrop__rule-of-thirds-vt::after,
1105
- .ReactCrop__rule-of-thirds-hz::before,
1106
- .ReactCrop__rule-of-thirds-hz::after {
1107
- content: '';
1108
- display: block;
1109
- position: absolute;
1110
- background-color: rgba(255, 255, 255, 0.4);
1111
- }
1112
- .ReactCrop__rule-of-thirds-vt::before,
1113
- .ReactCrop__rule-of-thirds-vt::after {
1114
- width: 1px;
1115
- height: 100%;
1116
- }
1117
- .ReactCrop__rule-of-thirds-vt::before {
1118
- left: 33.3333%;
1119
- left: 33.3333333333%;
1120
- }
1121
- .ReactCrop__rule-of-thirds-vt::after {
1122
- left: 66.6666%;
1123
- left: 66.6666666667%;
1124
- }
1125
- .ReactCrop__rule-of-thirds-hz::before,
1126
- .ReactCrop__rule-of-thirds-hz::after {
1127
- width: 100%;
1128
- height: 1px;
1129
- }
1130
- .ReactCrop__rule-of-thirds-hz::before {
1131
- top: 33.3333%;
1132
- top: 33.3333333333%;
1133
- }
1134
- .ReactCrop__rule-of-thirds-hz::after {
1135
- top: 66.6666%;
1136
- top: 66.6666666667%;
1137
- }
1138
- .ReactCrop__drag-handle {
1139
- position: absolute;
1140
- }
1141
- .ReactCrop__drag-handle::after {
1142
- position: absolute;
1143
- content: '';
1144
- display: block;
1145
- width: 10px;
1146
- height: 10px;
1147
- background-color: rgba(0, 0, 0, 0.2);
1148
- border: 1px solid rgba(255, 255, 255, 0.7);
1149
- outline: 1px solid rgba(0, 0, 0, 0);
1150
- }
1151
- .ReactCrop__drag-handle:focus::after {
1152
- border-color: blue;
1153
- background: #2dbfff;
1154
- }
1155
- .ReactCrop .ord-nw {
1156
- top: 0;
1157
- left: 0;
1158
- margin-top: -5px;
1159
- margin-left: -5px;
1160
- cursor: nw-resize;
1161
- }
1162
- .ReactCrop .ord-nw::after {
1163
- top: 0;
1164
- left: 0;
1165
- }
1166
- .ReactCrop .ord-n {
1167
- top: 0;
1168
- left: 50%;
1169
- margin-top: -5px;
1170
- margin-left: -5px;
1171
- cursor: n-resize;
1172
- }
1173
- .ReactCrop .ord-n::after {
1174
- top: 0;
1175
- }
1176
- .ReactCrop .ord-ne {
1177
- top: 0;
1178
- right: 0;
1179
- margin-top: -5px;
1180
- margin-right: -5px;
1181
- cursor: ne-resize;
1182
- }
1183
- .ReactCrop .ord-ne::after {
1184
- top: 0;
1185
- right: 0;
1186
- }
1187
- .ReactCrop .ord-e {
1188
- top: 50%;
1189
- right: 0;
1190
- margin-top: -5px;
1191
- margin-right: -5px;
1192
- cursor: e-resize;
1193
- }
1194
- .ReactCrop .ord-e::after {
1195
- right: 0;
1196
- }
1197
- .ReactCrop .ord-se {
1198
- bottom: 0;
1199
- right: 0;
1200
- margin-bottom: -5px;
1201
- margin-right: -5px;
1202
- cursor: se-resize;
1203
- }
1204
- .ReactCrop .ord-se::after {
1205
- bottom: 0;
1206
- right: 0;
1207
- }
1208
- .ReactCrop .ord-s {
1209
- bottom: 0;
1210
- left: 50%;
1211
- margin-bottom: -5px;
1212
- margin-left: -5px;
1213
- cursor: s-resize;
1214
- }
1215
- .ReactCrop .ord-s::after {
1216
- bottom: 0;
1217
- }
1218
- .ReactCrop .ord-sw {
1219
- bottom: 0;
1220
- left: 0;
1221
- margin-bottom: -5px;
1222
- margin-left: -5px;
1223
- cursor: sw-resize;
1224
- }
1225
- .ReactCrop .ord-sw::after {
1226
- bottom: 0;
1227
- left: 0;
1228
- }
1229
- .ReactCrop .ord-w {
1230
- top: 50%;
1231
- left: 0;
1232
- margin-top: -5px;
1233
- margin-left: -5px;
1234
- cursor: w-resize;
1235
- }
1236
- .ReactCrop .ord-w::after {
1237
- left: 0;
1238
- }
1239
- .ReactCrop__disabled .ReactCrop__drag-handle {
1240
- cursor: inherit;
1241
- }
1242
- .ReactCrop__drag-bar {
1243
- position: absolute;
1244
- }
1245
- .ReactCrop__drag-bar.ord-n {
1246
- top: 0;
1247
- left: 0;
1248
- width: 100%;
1249
- height: 6px;
1250
- margin-top: -3px;
1251
- }
1252
- .ReactCrop__drag-bar.ord-e {
1253
- right: 0;
1254
- top: 0;
1255
- width: 6px;
1256
- height: 100%;
1257
- margin-right: -3px;
1258
- }
1259
- .ReactCrop__drag-bar.ord-s {
1260
- bottom: 0;
1261
- left: 0;
1262
- width: 100%;
1263
- height: 6px;
1264
- margin-bottom: -3px;
1265
- }
1266
- .ReactCrop__drag-bar.ord-w {
1267
- top: 0;
1268
- left: 0;
1269
- width: 6px;
1270
- height: 100%;
1271
- margin-left: -3px;
1272
- }
1273
- .ReactCrop--new-crop .ReactCrop__drag-bar,
1274
- .ReactCrop--new-crop .ReactCrop__drag-handle,
1275
- .ReactCrop--fixed-aspect .ReactCrop__drag-bar {
1276
- display: none;
1277
- }
1278
- .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-n,
1279
- .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-e,
1280
- .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-s,
1281
- .ReactCrop--fixed-aspect .ReactCrop__drag-handle.ord-w {
1282
- display: none;
1283
- }
1284
- @media (pointer: coarse) {
1285
- .ReactCrop .ord-n,
1286
- .ReactCrop .ord-e,
1287
- .ReactCrop .ord-s,
1288
- .ReactCrop .ord-w {
1289
- display: none;
1290
- }
1291
- .ReactCrop__drag-handle {
1292
- width: 24px;
1293
- height: 24px;
1294
- }
1295
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majoo-ui/react",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "main": "./index.esm.js",
5
5
  "sideEffects": [
6
6
  "*.css",