@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 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
- "aria-live": labelAriaLive
2328
- }, selectedMonth.toLocaleDateString(locale, {
2329
- year: 'numeric',
2330
- month: 'long'
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 [monthOffset, setMonthOffset] = useState(0);
2414
- const setPrevMonth = () => setMonthOffset(monthOffset - 1);
2415
- const setNextMonth = () => setMonthOffset(monthOffset + 1);
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(focusZoneElement);
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
- !document.activeElement ||
2737
+ !activeElement ||
2667
2738
  // Previous focus is at the end of the focus zone.
2668
- !evt.shiftKey && document.activeElement === focusable.last ||
2739
+ !evt.shiftKey && activeElement === focusable.last ||
2669
2740
  // Previous focus is outside the focus zone
2670
- !focusZoneElement.contains(document.activeElement)) {
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
- document.activeElement === focusable.first) {
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 && focusZoneElement.contains(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(focusZoneElement).first) === null || _getFirstAndLastFocus === void 0 ? void 0 : _getFirstAndLastFocus.focus({
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 && (popoverRef === null || popoverRef === void 0 ? void 0 : popoverRef.current), focusElement === null || focusElement === void 0 ? void 0 : focusElement.current);
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;