@majoo-ui/react 2.1.3 → 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.
- package/components/dropdownMenu/DropdownMenu.esm.js +153 -23
- package/components/form/components/InputSelect/InputSelect.esm.js +22 -27
- package/components/form/components/InputSelectCheck/InputSelectCheck.esm.js +166 -158
- package/components/mapPicker/MapPicker.esm.js +5 -3
- package/components/mapPicker/hooks/useMapSearch.esm.js +24 -5
- package/components/periodSelect/PeriodSelect.esm.js +212 -0
- package/components/popover/Popover.esm.js +12 -3
- package/components/popover/middleware.esm.js +43 -0
- package/index.esm.css +245 -245
- package/index.esm.js +2 -1
- package/package.json +1 -1
- package/src/components/index.d.ts +1 -0
- package/src/components/periodSelect/PeriodSelect.d.ts +83 -0
- package/src/components/periodSelect/index.d.ts +2 -0
- package/utils/calendar.esm.js +5 -1
|
@@ -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:
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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__*/
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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
|
-
|
|
1177
|
-
|
|
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(
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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:
|
|
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
|
-
|
|
1260
|
-
height: '16px',
|
|
1261
|
-
border: '1px solid $bgBorder',
|
|
1259
|
+
backgroundColor: highlightedIndex === virtualRow.index ? '$bgGray' : '$bgWhite',
|
|
1262
1260
|
cursor: item.isDisabled ? 'not-allowed' : 'pointer',
|
|
1263
|
-
|
|
1261
|
+
color: item.isDisabled ? '$textDisable' : 'textPrimary',
|
|
1262
|
+
fontWeight: 600
|
|
1264
1263
|
},
|
|
1265
|
-
children:
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
-
|
|
1287
|
-
|
|
1288
|
-
})
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
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
|
-
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import 'core-js/modules/es.iterator.constructor.js';
|
|
2
|
+
import 'core-js/modules/es.iterator.map.js';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
import React__default, { useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { CaretDownOutline, ChevronRightOutline } from '@majoo-ui/icons';
|
|
7
|
+
import { styled, colors } from '../../config/stitches.config.esm.js';
|
|
8
|
+
import { weekdayNamesFullEng, weekdayNamesFullId } from '../../utils/calendar.esm.js';
|
|
9
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubTrigger, RightSlot, DropdownMenuSubContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem } from '../dropdownMenu/DropdownMenu.esm.js';
|
|
10
|
+
|
|
11
|
+
const Trigger = styled('button', {
|
|
12
|
+
all: 'unset',
|
|
13
|
+
boxSizing: 'border-box',
|
|
14
|
+
display: 'inline-flex',
|
|
15
|
+
alignItems: 'center',
|
|
16
|
+
gap: '$spacing-02',
|
|
17
|
+
padding: '0 $spacing-04',
|
|
18
|
+
border: '1px solid $bgOutline',
|
|
19
|
+
borderRadius: '$lg',
|
|
20
|
+
backgroundColor: '$bgWhite',
|
|
21
|
+
color: '$textPrimary',
|
|
22
|
+
fontFamily: 'inherit',
|
|
23
|
+
fontSize: '$short-content-regular',
|
|
24
|
+
lineHeight: '$short-content-regular',
|
|
25
|
+
letterSpacing: '$short-content-regular',
|
|
26
|
+
cursor: 'pointer',
|
|
27
|
+
'&:hover': {
|
|
28
|
+
borderColor: '$primary600'
|
|
29
|
+
},
|
|
30
|
+
'&[data-state="open"]': {
|
|
31
|
+
borderColor: '$primary400'
|
|
32
|
+
},
|
|
33
|
+
variants: {
|
|
34
|
+
size: {
|
|
35
|
+
sm: {
|
|
36
|
+
height: 32
|
|
37
|
+
},
|
|
38
|
+
lg: {
|
|
39
|
+
height: 40
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
// Named apart from the DOM attribute so Stitches does not swallow it as a variant —
|
|
43
|
+
// the button still needs a real `disabled` to block the menu from opening.
|
|
44
|
+
isDisabled: {
|
|
45
|
+
true: {
|
|
46
|
+
backgroundColor: '$formDisable',
|
|
47
|
+
color: '$textDisable',
|
|
48
|
+
cursor: 'not-allowed',
|
|
49
|
+
'&:hover': {
|
|
50
|
+
borderColor: '$bgOutline'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
defaultVariants: {
|
|
56
|
+
size: 'lg'
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const TriggerLabel = styled('span', {
|
|
60
|
+
fontWeight: 600
|
|
61
|
+
});
|
|
62
|
+
const TriggerRange = styled('span', {
|
|
63
|
+
color: '$textDisable'
|
|
64
|
+
});
|
|
65
|
+
const MENU_ITEM_CSS = {
|
|
66
|
+
paddingLeft: '$spacing-04',
|
|
67
|
+
paddingRight: '$spacing-04'
|
|
68
|
+
};
|
|
69
|
+
const DEFAULT_PERIODS = ['day', 'week', 'month'];
|
|
70
|
+
// Listed Monday first, the way the week reads in Indonesian — not in Date#getDay order.
|
|
71
|
+
const START_DAY_ORDER = [1, 2, 3, 4, 5, 6, 0];
|
|
72
|
+
const PeriodSelect = /*#__PURE__*/React__default.forwardRef(({
|
|
73
|
+
value,
|
|
74
|
+
onChange,
|
|
75
|
+
startDay,
|
|
76
|
+
onStartDayChange,
|
|
77
|
+
defaultStartDay = 1,
|
|
78
|
+
resetStartDayOnPeriodChange = true,
|
|
79
|
+
periods = DEFAULT_PERIODS,
|
|
80
|
+
showRange = true,
|
|
81
|
+
disabled = false,
|
|
82
|
+
size = 'lg',
|
|
83
|
+
side = 'bottom',
|
|
84
|
+
align = 'start',
|
|
85
|
+
periodLabels,
|
|
86
|
+
startWeekLabel,
|
|
87
|
+
formatWeekRange,
|
|
88
|
+
css
|
|
89
|
+
}, ref) => {
|
|
90
|
+
const {
|
|
91
|
+
t,
|
|
92
|
+
i18n: {
|
|
93
|
+
language
|
|
94
|
+
}
|
|
95
|
+
} = useTranslation(['MajooUI/periodSelect', 'translation']);
|
|
96
|
+
const [open, setOpen] = useState(false);
|
|
97
|
+
const [internalStartDay, setInternalStartDay] = useState(defaultStartDay);
|
|
98
|
+
const activeStartDay = startDay !== null && startDay !== void 0 ? startDay : internalStartDay;
|
|
99
|
+
const weekdayNames = language === 'en' ? weekdayNamesFullEng : weekdayNamesFullId;
|
|
100
|
+
// A week always spans 7 days, so the last one is always 6 days after the first.
|
|
101
|
+
const rangeLabel = (day, place) => {
|
|
102
|
+
const endDay = (day + 6) % 7;
|
|
103
|
+
const range = {
|
|
104
|
+
startDay: day,
|
|
105
|
+
endDay,
|
|
106
|
+
startName: weekdayNames[day],
|
|
107
|
+
endName: weekdayNames[endDay],
|
|
108
|
+
place
|
|
109
|
+
};
|
|
110
|
+
return formatWeekRange ? formatWeekRange(range) : `${range.startName} - ${range.endName}`;
|
|
111
|
+
};
|
|
112
|
+
const showWeekRange = showRange && value === 'week';
|
|
113
|
+
const defaultPeriodLabels = {
|
|
114
|
+
day: t('day', 'Hari'),
|
|
115
|
+
week: t('week', 'Minggu'),
|
|
116
|
+
month: t('month', 'Bulan')
|
|
117
|
+
};
|
|
118
|
+
const periodLabel = period => {
|
|
119
|
+
var _a;
|
|
120
|
+
return (_a = periodLabels === null || periodLabels === void 0 ? void 0 : periodLabels[period]) !== null && _a !== void 0 ? _a : defaultPeriodLabels[period];
|
|
121
|
+
};
|
|
122
|
+
const applyStartDay = day => {
|
|
123
|
+
setInternalStartDay(day);
|
|
124
|
+
onStartDayChange === null || onStartDayChange === void 0 ? void 0 : onStartDayChange(day);
|
|
125
|
+
};
|
|
126
|
+
const selectPeriod = period => {
|
|
127
|
+
onChange(period);
|
|
128
|
+
if (resetStartDayOnPeriodChange && period !== 'week' && activeStartDay !== defaultStartDay) {
|
|
129
|
+
applyStartDay(defaultStartDay);
|
|
130
|
+
}
|
|
131
|
+
setOpen(false);
|
|
132
|
+
};
|
|
133
|
+
const renderPeriodItem = period => {
|
|
134
|
+
if (period !== 'week') {
|
|
135
|
+
return jsx(DropdownMenuItem, {
|
|
136
|
+
selected: value === period,
|
|
137
|
+
onSelect: () => selectPeriod(period),
|
|
138
|
+
css: MENU_ITEM_CSS,
|
|
139
|
+
children: periodLabel(period)
|
|
140
|
+
}, period);
|
|
141
|
+
}
|
|
142
|
+
return jsxs(DropdownMenuSub, {
|
|
143
|
+
children: [jsxs(DropdownMenuSubTrigger, {
|
|
144
|
+
selected: value === 'week',
|
|
145
|
+
onClick: () => selectPeriod('week'),
|
|
146
|
+
css: MENU_ITEM_CSS,
|
|
147
|
+
children: [periodLabel('week'), jsx(RightSlot, {
|
|
148
|
+
children: jsx(ChevronRightOutline, {
|
|
149
|
+
size: "16",
|
|
150
|
+
color: "currentColor"
|
|
151
|
+
})
|
|
152
|
+
})]
|
|
153
|
+
}), jsxs(DropdownMenuSubContent, {
|
|
154
|
+
sideOffset: 8,
|
|
155
|
+
alignOffset: -8,
|
|
156
|
+
css: {
|
|
157
|
+
padding: '$spacing-04 $spacing-05'
|
|
158
|
+
},
|
|
159
|
+
children: [jsx(DropdownMenuLabel, {
|
|
160
|
+
children: startWeekLabel !== null && startWeekLabel !== void 0 ? startWeekLabel : t('startWeekFrom', 'Mulai minggu dari:')
|
|
161
|
+
}), jsx(DropdownMenuRadioGroup, {
|
|
162
|
+
value: String(activeStartDay),
|
|
163
|
+
onValueChange: day => {
|
|
164
|
+
applyStartDay(Number(day));
|
|
165
|
+
onChange('week');
|
|
166
|
+
},
|
|
167
|
+
children: START_DAY_ORDER.map(day => jsx(DropdownMenuRadioItem, {
|
|
168
|
+
value: String(day),
|
|
169
|
+
children: rangeLabel(day, 'option')
|
|
170
|
+
}, day))
|
|
171
|
+
})]
|
|
172
|
+
})]
|
|
173
|
+
}, period);
|
|
174
|
+
};
|
|
175
|
+
return jsxs(DropdownMenu, {
|
|
176
|
+
open: open,
|
|
177
|
+
onOpenChange: setOpen,
|
|
178
|
+
side: side,
|
|
179
|
+
align: align,
|
|
180
|
+
modal: false,
|
|
181
|
+
children: [jsx(DropdownMenuTrigger, {
|
|
182
|
+
asChild: true,
|
|
183
|
+
children: jsxs(Trigger, {
|
|
184
|
+
ref: ref,
|
|
185
|
+
size: size,
|
|
186
|
+
isDisabled: disabled,
|
|
187
|
+
disabled: disabled,
|
|
188
|
+
css: css,
|
|
189
|
+
type: "button",
|
|
190
|
+
children: [jsxs(TriggerLabel, {
|
|
191
|
+
children: [periodLabel(value), showWeekRange && ',']
|
|
192
|
+
}), showWeekRange && jsx(TriggerRange, {
|
|
193
|
+
children: rangeLabel(activeStartDay, 'trigger')
|
|
194
|
+
}), jsx(CaretDownOutline, {
|
|
195
|
+
size: "16",
|
|
196
|
+
color: disabled ? colors.iconDisable : colors.iconPrimary
|
|
197
|
+
})]
|
|
198
|
+
})
|
|
199
|
+
}), jsx(DropdownMenuContent, {
|
|
200
|
+
sideOffset: 4,
|
|
201
|
+
css: {
|
|
202
|
+
width: 'max-content',
|
|
203
|
+
minWidth: 160,
|
|
204
|
+
maxWidth: 'none'
|
|
205
|
+
},
|
|
206
|
+
children: periods.map(renderPeriodItem)
|
|
207
|
+
})]
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
PeriodSelect.displayName = 'PeriodSelect';
|
|
211
|
+
|
|
212
|
+
export { PeriodSelect as default };
|