@lumx/react 3.6.6 → 3.6.7-alpha-with-popover-change.0
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/index.d.ts +15 -0
- package/index.js +110 -25
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/date-picker/DatePicker.test.tsx +3 -0
- package/src/components/date-picker/DatePicker.tsx +4 -7
- package/src/components/date-picker/DatePickerControlled.test.tsx +46 -2
- package/src/components/date-picker/DatePickerControlled.tsx +80 -5
- package/src/components/date-picker/DatePickerField.test.tsx +3 -0
- package/src/components/message/Message.stories.tsx +16 -0
- package/src/components/message/Message.test.tsx +25 -3
- package/src/components/message/Message.tsx +26 -3
- package/src/components/popover/Popover.tsx +5 -1
- package/src/hooks/useFocusTrap.ts +15 -7
- package/src/stories/generated/Message/Demos.stories.tsx +1 -0
- package/src/utils/date/getYearDisplayName.test.ts +20 -0
- package/src/utils/date/getYearDisplayName.ts +12 -0
- package/src/utils/focus/getFirstAndLastFocusable.ts +1 -1
- package/src/utils/focus/getFocusableElements.ts +1 -1
package/index.d.ts
CHANGED
|
@@ -586,6 +586,8 @@ interface DatePickerControlledProps extends DatePickerProps {
|
|
|
586
586
|
onPrevMonthChange(): void;
|
|
587
587
|
/** On next month change callback. */
|
|
588
588
|
onNextMonthChange(): void;
|
|
589
|
+
/** On month/year change callback. */
|
|
590
|
+
onMonthChange?: (newMonth: Date) => void;
|
|
589
591
|
}
|
|
590
592
|
/**
|
|
591
593
|
* DatePickerControlled component.
|
|
@@ -792,6 +794,8 @@ interface PopoverProps extends GenericProps, HasTheme {
|
|
|
792
794
|
placement?: Placement;
|
|
793
795
|
/** Whether the popover should be rendered into a DOM node that exists outside the DOM hierarchy of the parent component. */
|
|
794
796
|
usePortal?: boolean;
|
|
797
|
+
/** The element in which the focus trap should be set. Default to popover. */
|
|
798
|
+
focusTrapZoneElement?: RefObject<HTMLElement>;
|
|
795
799
|
/** Z-axis position. */
|
|
796
800
|
zIndex?: number;
|
|
797
801
|
/** On close callback (on click away or Escape pressed). */
|
|
@@ -1646,6 +1650,17 @@ interface MessageProps extends GenericProps {
|
|
|
1646
1650
|
kind?: Kind;
|
|
1647
1651
|
/** Message custom icon SVG path. */
|
|
1648
1652
|
icon?: string;
|
|
1653
|
+
/**
|
|
1654
|
+
* Displays a close button.
|
|
1655
|
+
*
|
|
1656
|
+
* NB: only available if `kind === 'info' && hasBackground === true`
|
|
1657
|
+
*/
|
|
1658
|
+
closeButtonProps?: {
|
|
1659
|
+
/** The callback called when the button is clicked */
|
|
1660
|
+
onClick: () => void;
|
|
1661
|
+
/** The label of the close button. */
|
|
1662
|
+
label: string;
|
|
1663
|
+
};
|
|
1649
1664
|
}
|
|
1650
1665
|
/**
|
|
1651
1666
|
* Message component.
|
package/index.js
CHANGED
|
@@ -2262,6 +2262,21 @@ function usePreviousValue(value) {
|
|
|
2262
2262
|
return prevValue;
|
|
2263
2263
|
}
|
|
2264
2264
|
|
|
2265
|
+
const getYearDisplayName = locale => {
|
|
2266
|
+
let label;
|
|
2267
|
+
try {
|
|
2268
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2269
|
+
// @ts-ignore
|
|
2270
|
+
const displayNames = new Intl.DisplayNames(locale, {
|
|
2271
|
+
type: 'dateTimeField'
|
|
2272
|
+
});
|
|
2273
|
+
label = displayNames.of('year');
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
label = '';
|
|
2276
|
+
}
|
|
2277
|
+
return label;
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2265
2280
|
/**
|
|
2266
2281
|
* Defines the props of the component.
|
|
2267
2282
|
*/
|
|
@@ -2279,6 +2294,7 @@ const COMPONENT_NAME$e = 'DatePickerControlled';
|
|
|
2279
2294
|
* @return React element.
|
|
2280
2295
|
*/
|
|
2281
2296
|
const DatePickerControlled = /*#__PURE__*/forwardRef((props, ref) => {
|
|
2297
|
+
var _RegExp$exec;
|
|
2282
2298
|
const {
|
|
2283
2299
|
locale = getCurrentLocale(),
|
|
2284
2300
|
maxDate,
|
|
@@ -2290,7 +2306,8 @@ const DatePickerControlled = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
2290
2306
|
previousButtonProps,
|
|
2291
2307
|
selectedMonth,
|
|
2292
2308
|
todayOrSelectedDateRef,
|
|
2293
|
-
value
|
|
2309
|
+
value,
|
|
2310
|
+
onMonthChange
|
|
2294
2311
|
} = props;
|
|
2295
2312
|
const {
|
|
2296
2313
|
weeks,
|
|
@@ -2299,6 +2316,37 @@ const DatePickerControlled = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
2299
2316
|
const localeObj = parseLocale(locale);
|
|
2300
2317
|
return getMonthCalendar(localeObj, selectedMonth, minDate, maxDate);
|
|
2301
2318
|
}, [locale, minDate, maxDate, selectedMonth]);
|
|
2319
|
+
const selectedYear = selectedMonth.toLocaleDateString(locale, {
|
|
2320
|
+
year: 'numeric'
|
|
2321
|
+
}).slice(0, 4);
|
|
2322
|
+
const [textFieldYearValue, setTextFieldYearValue] = React.useState(selectedYear);
|
|
2323
|
+
const isYearValid = Number(textFieldYearValue) > 0 && Number(textFieldYearValue) <= 9999;
|
|
2324
|
+
|
|
2325
|
+
// Updates month offset when validating year. Adds or removes 12 months per year when updating year value.
|
|
2326
|
+
const updateMonthOffset = React.useCallback(() => {
|
|
2327
|
+
if (isYearValid) {
|
|
2328
|
+
const yearNumber = selectedMonth.getFullYear();
|
|
2329
|
+
const offset = (Number(textFieldYearValue) - yearNumber) * 12;
|
|
2330
|
+
if (onMonthChange) {
|
|
2331
|
+
onMonthChange(addMonthResetDay(selectedMonth, offset));
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}, [isYearValid, selectedMonth, textFieldYearValue, onMonthChange]);
|
|
2335
|
+
const monthYear = selectedMonth.toLocaleDateString(locale, {
|
|
2336
|
+
year: 'numeric',
|
|
2337
|
+
month: 'long'
|
|
2338
|
+
});
|
|
2339
|
+
|
|
2340
|
+
// Year can only be validatd by pressing Enter key or on Blur. The below handles the press Enter key case
|
|
2341
|
+
const handleKeyPress = React.useMemo(() => onEnterPressed(updateMonthOffset), [updateMonthOffset]);
|
|
2342
|
+
|
|
2343
|
+
// Required to update year in the TextField when the user changes year by using prev next month arrows
|
|
2344
|
+
React.useEffect(() => {
|
|
2345
|
+
if (Number(textFieldYearValue) !== selectedMonth.getFullYear()) {
|
|
2346
|
+
setTextFieldYearValue(selectedMonth.getFullYear().toString());
|
|
2347
|
+
}
|
|
2348
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2349
|
+
}, [selectedMonth]);
|
|
2302
2350
|
const prevSelectedMonth = usePreviousValue(selectedMonth);
|
|
2303
2351
|
const monthHasChanged = prevSelectedMonth && !isSameDay(selectedMonth, prevSelectedMonth);
|
|
2304
2352
|
|
|
@@ -2307,6 +2355,7 @@ const DatePickerControlled = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
2307
2355
|
React.useEffect(() => {
|
|
2308
2356
|
if (monthHasChanged) setLabelAriaLive('polite');
|
|
2309
2357
|
}, [monthHasChanged]);
|
|
2358
|
+
const label = getYearDisplayName(locale);
|
|
2310
2359
|
return /*#__PURE__*/React.createElement("div", {
|
|
2311
2360
|
ref: ref,
|
|
2312
2361
|
className: `${CLASSNAME$c}`
|
|
@@ -2322,13 +2371,32 @@ const DatePickerControlled = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
2322
2371
|
icon: mdiChevronLeft,
|
|
2323
2372
|
onClick: onPrevMonthChange
|
|
2324
2373
|
})),
|
|
2325
|
-
label: /*#__PURE__*/React.createElement("span", {
|
|
2374
|
+
label: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
|
2375
|
+
"aria-live": labelAriaLive,
|
|
2376
|
+
className: onMonthChange ? 'visually-hidden' : '',
|
|
2377
|
+
dir: "auto"
|
|
2378
|
+
}, monthYear), onMonthChange && /*#__PURE__*/React.createElement(FlexBox, {
|
|
2326
2379
|
className: `${CLASSNAME$c}__month`,
|
|
2327
|
-
"
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2380
|
+
orientation: "horizontal",
|
|
2381
|
+
hAlign: "center",
|
|
2382
|
+
gap: "regular",
|
|
2383
|
+
vAlign: "center",
|
|
2384
|
+
dir: "auto"
|
|
2385
|
+
}, (_RegExp$exec = RegExp(`(.*)(${selectedYear})(.*)`).exec(monthYear)) === null || _RegExp$exec === void 0 ? void 0 : _RegExp$exec.slice(1).filter(part => part !== '').map(part => part === selectedYear ? /*#__PURE__*/React.createElement(TextField, {
|
|
2386
|
+
value: textFieldYearValue,
|
|
2387
|
+
"aria-label": label,
|
|
2388
|
+
onChange: setTextFieldYearValue,
|
|
2389
|
+
type: "number",
|
|
2390
|
+
max: 9999,
|
|
2391
|
+
min: 0,
|
|
2392
|
+
onBlur: updateMonthOffset,
|
|
2393
|
+
onKeyPress: handleKeyPress,
|
|
2394
|
+
key: "year",
|
|
2395
|
+
className: `${CLASSNAME$c}__year`
|
|
2396
|
+
}) : /*#__PURE__*/React.createElement(Text, {
|
|
2397
|
+
as: "p",
|
|
2398
|
+
key: part
|
|
2399
|
+
}, part))))
|
|
2332
2400
|
}), /*#__PURE__*/React.createElement("div", {
|
|
2333
2401
|
className: `${CLASSNAME$c}__calendar`
|
|
2334
2402
|
}, /*#__PURE__*/React.createElement("div", {
|
|
@@ -2410,14 +2478,12 @@ const DatePicker = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
2410
2478
|
console.warn(`[@lumx/react/DatePicker] Invalid date provided ${referenceDate}`);
|
|
2411
2479
|
referenceDate = new Date();
|
|
2412
2480
|
}
|
|
2413
|
-
const [
|
|
2414
|
-
const setPrevMonth = () =>
|
|
2415
|
-
const setNextMonth = () =>
|
|
2481
|
+
const [selectedMonth, setSelectedMonth] = useState(referenceDate);
|
|
2482
|
+
const setPrevMonth = () => setSelectedMonth(current => addMonthResetDay(current, -1));
|
|
2483
|
+
const setNextMonth = () => setSelectedMonth(current => addMonthResetDay(current, +1));
|
|
2416
2484
|
const onDatePickerChange = newDate => {
|
|
2417
2485
|
onChange(newDate);
|
|
2418
|
-
setMonthOffset(0);
|
|
2419
2486
|
};
|
|
2420
|
-
const selectedMonth = addMonthResetDay(referenceDate, monthOffset);
|
|
2421
2487
|
return /*#__PURE__*/React.createElement(DatePickerControlled, _extends({
|
|
2422
2488
|
ref: ref
|
|
2423
2489
|
}, forwardedProps, {
|
|
@@ -2427,7 +2493,8 @@ const DatePicker = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
2427
2493
|
onPrevMonthChange: setPrevMonth,
|
|
2428
2494
|
onNextMonthChange: setNextMonth,
|
|
2429
2495
|
selectedMonth: selectedMonth,
|
|
2430
|
-
onChange: onDatePickerChange
|
|
2496
|
+
onChange: onDatePickerChange,
|
|
2497
|
+
onMonthChange: setSelectedMonth
|
|
2431
2498
|
}));
|
|
2432
2499
|
});
|
|
2433
2500
|
DatePicker.displayName = COMPONENT_NAME$d;
|
|
@@ -2646,6 +2713,9 @@ function useFocusTrap(focusZoneElement, focusElement) {
|
|
|
2646
2713
|
return undefined;
|
|
2647
2714
|
}
|
|
2648
2715
|
|
|
2716
|
+
// Use the shadow root as focusZoneElement when available
|
|
2717
|
+
const focusZoneElementOrShadowRoot = focusZoneElement.shadowRoot || focusZoneElement;
|
|
2718
|
+
|
|
2649
2719
|
// Trap 'Tab' key down focus switch into the focus zone.
|
|
2650
2720
|
const trapTabFocusInFocusZone = evt => {
|
|
2651
2721
|
const {
|
|
@@ -2654,20 +2724,21 @@ function useFocusTrap(focusZoneElement, focusElement) {
|
|
|
2654
2724
|
if (key !== 'Tab') {
|
|
2655
2725
|
return;
|
|
2656
2726
|
}
|
|
2657
|
-
const focusable = getFirstAndLastFocusable(
|
|
2727
|
+
const focusable = getFirstAndLastFocusable(focusZoneElementOrShadowRoot);
|
|
2658
2728
|
|
|
2659
2729
|
// Prevent focus switch if no focusable available.
|
|
2660
2730
|
if (!focusable.first) {
|
|
2661
2731
|
evt.preventDefault();
|
|
2662
2732
|
return;
|
|
2663
2733
|
}
|
|
2734
|
+
const activeElement = focusZoneElement.shadowRoot ? focusZoneElement.shadowRoot.activeElement : document.activeElement;
|
|
2664
2735
|
if (
|
|
2665
2736
|
// No previous focus
|
|
2666
|
-
!
|
|
2737
|
+
!activeElement ||
|
|
2667
2738
|
// Previous focus is at the end of the focus zone.
|
|
2668
|
-
!evt.shiftKey &&
|
|
2739
|
+
!evt.shiftKey && activeElement === focusable.last ||
|
|
2669
2740
|
// Previous focus is outside the focus zone
|
|
2670
|
-
!
|
|
2741
|
+
!focusZoneElementOrShadowRoot.contains(activeElement)) {
|
|
2671
2742
|
focusable.first.focus();
|
|
2672
2743
|
evt.preventDefault();
|
|
2673
2744
|
return;
|
|
@@ -2676,7 +2747,7 @@ function useFocusTrap(focusZoneElement, focusElement) {
|
|
|
2676
2747
|
// Focus order reversed
|
|
2677
2748
|
evt.shiftKey &&
|
|
2678
2749
|
// Previous focus is at the start of the focus zone.
|
|
2679
|
-
|
|
2750
|
+
activeElement === focusable.first) {
|
|
2680
2751
|
focusable.last.focus();
|
|
2681
2752
|
evt.preventDefault();
|
|
2682
2753
|
}
|
|
@@ -2687,7 +2758,7 @@ function useFocusTrap(focusZoneElement, focusElement) {
|
|
|
2687
2758
|
};
|
|
2688
2759
|
|
|
2689
2760
|
// SETUP:
|
|
2690
|
-
if (focusElement &&
|
|
2761
|
+
if (focusElement && focusZoneElementOrShadowRoot.contains(focusElement)) {
|
|
2691
2762
|
// Focus the given element.
|
|
2692
2763
|
focusElement.focus({
|
|
2693
2764
|
preventScroll: true
|
|
@@ -2695,7 +2766,7 @@ function useFocusTrap(focusZoneElement, focusElement) {
|
|
|
2695
2766
|
} else {
|
|
2696
2767
|
var _getFirstAndLastFocus;
|
|
2697
2768
|
// Focus the first focusable element in the zone.
|
|
2698
|
-
(_getFirstAndLastFocus = getFirstAndLastFocusable(
|
|
2769
|
+
(_getFirstAndLastFocus = getFirstAndLastFocusable(focusZoneElementOrShadowRoot).first) === null || _getFirstAndLastFocus === void 0 ? void 0 : _getFirstAndLastFocus.focus({
|
|
2699
2770
|
preventScroll: true
|
|
2700
2771
|
});
|
|
2701
2772
|
}
|
|
@@ -6471,7 +6542,7 @@ function usePopoverStyle(_ref5) {
|
|
|
6471
6542
|
};
|
|
6472
6543
|
}
|
|
6473
6544
|
|
|
6474
|
-
const _excluded$m = ["anchorRef", "as", "children", "className", "closeOnClickAway", "closeOnEscape", "elevation", "focusElement", "hasArrow", "isOpen", "onClose", "parentElement", "usePortal", "focusAnchorOnClose", "withFocusTrap", "boundaryRef", "fitToAnchorWidth", "fitWithinViewportHeight", "offset", "placement", "style", "theme", "zIndex"];
|
|
6545
|
+
const _excluded$m = ["anchorRef", "as", "children", "className", "closeOnClickAway", "closeOnEscape", "elevation", "focusElement", "hasArrow", "isOpen", "onClose", "parentElement", "usePortal", "focusAnchorOnClose", "withFocusTrap", "boundaryRef", "fitToAnchorWidth", "fitWithinViewportHeight", "focusTrapZoneElement", "offset", "placement", "style", "theme", "zIndex"];
|
|
6475
6546
|
|
|
6476
6547
|
/**
|
|
6477
6548
|
* Defines the props of the component.
|
|
@@ -6524,6 +6595,7 @@ const _InnerPopover = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
6524
6595
|
boundaryRef,
|
|
6525
6596
|
fitToAnchorWidth,
|
|
6526
6597
|
fitWithinViewportHeight,
|
|
6598
|
+
focusTrapZoneElement,
|
|
6527
6599
|
offset,
|
|
6528
6600
|
placement,
|
|
6529
6601
|
style,
|
|
@@ -6557,11 +6629,12 @@ const _InnerPopover = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
6557
6629
|
anchorRef,
|
|
6558
6630
|
parentElement
|
|
6559
6631
|
}, popperElement);
|
|
6632
|
+
const focusZoneElement = (focusTrapZoneElement === null || focusTrapZoneElement === void 0 ? void 0 : focusTrapZoneElement.current) || (popoverRef === null || popoverRef === void 0 ? void 0 : popoverRef.current);
|
|
6560
6633
|
useCallbackOnEscape(onClose, isOpen && closeOnEscape);
|
|
6561
6634
|
|
|
6562
6635
|
/** Only set focus within if the focus trap is disabled as they interfere with one another. */
|
|
6563
6636
|
useFocus(focusElement === null || focusElement === void 0 ? void 0 : focusElement.current, !withFocusTrap && isOpen && isPositioned);
|
|
6564
|
-
useFocusTrap(withFocusTrap && isOpen &&
|
|
6637
|
+
useFocusTrap(withFocusTrap && isOpen && focusZoneElement, focusElement === null || focusElement === void 0 ? void 0 : focusElement.current);
|
|
6565
6638
|
const clickAwayRefs = useRef([popoverRef, anchorRef]);
|
|
6566
6639
|
const mergedRefs = useMergeRefs(setPopperElement, ref, popoverRef);
|
|
6567
6640
|
return isOpen ? renderPopover( /*#__PURE__*/React.createElement(Component, _extends({}, forwardedProps, {
|
|
@@ -8233,7 +8306,7 @@ const ListSubheader = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
8233
8306
|
ListSubheader.displayName = COMPONENT_NAME$F;
|
|
8234
8307
|
ListSubheader.className = CLASSNAME$C;
|
|
8235
8308
|
|
|
8236
|
-
const _excluded$H = ["children", "className", "hasBackground", "kind", "icon"];
|
|
8309
|
+
const _excluded$H = ["children", "className", "hasBackground", "kind", "icon", "closeButtonProps"];
|
|
8237
8310
|
|
|
8238
8311
|
/**
|
|
8239
8312
|
* Defines the props of the component.
|
|
@@ -8284,13 +8357,19 @@ const Message = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
8284
8357
|
className,
|
|
8285
8358
|
hasBackground,
|
|
8286
8359
|
kind,
|
|
8287
|
-
icon: customIcon
|
|
8360
|
+
icon: customIcon,
|
|
8361
|
+
closeButtonProps
|
|
8288
8362
|
} = props,
|
|
8289
8363
|
forwardedProps = _objectWithoutProperties(props, _excluded$H);
|
|
8290
8364
|
const {
|
|
8291
8365
|
color,
|
|
8292
8366
|
icon
|
|
8293
8367
|
} = CONFIG$1[kind] || {};
|
|
8368
|
+
const {
|
|
8369
|
+
onClick,
|
|
8370
|
+
label: closeButtonLabel
|
|
8371
|
+
} = closeButtonProps || {};
|
|
8372
|
+
const isCloseButtonDisplayed = hasBackground && kind === 'info' && onClick && closeButtonLabel;
|
|
8294
8373
|
return /*#__PURE__*/React.createElement("div", _extends({
|
|
8295
8374
|
ref: ref,
|
|
8296
8375
|
className: classnames(className, handleBasicClasses({
|
|
@@ -8305,7 +8384,13 @@ const Message = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
8305
8384
|
color: color
|
|
8306
8385
|
}), /*#__PURE__*/React.createElement("div", {
|
|
8307
8386
|
className: `${CLASSNAME$D}__text`
|
|
8308
|
-
}, children)
|
|
8387
|
+
}, children), isCloseButtonDisplayed && /*#__PURE__*/React.createElement(IconButton, {
|
|
8388
|
+
className: `${CLASSNAME$D}__close-button`,
|
|
8389
|
+
icon: mdiClose,
|
|
8390
|
+
onClick: onClick,
|
|
8391
|
+
label: closeButtonLabel,
|
|
8392
|
+
emphasis: Emphasis.low
|
|
8393
|
+
}));
|
|
8309
8394
|
});
|
|
8310
8395
|
Message.displayName = COMPONENT_NAME$G;
|
|
8311
8396
|
Message.className = CLASSNAME$D;
|