@itwin/itwinui-react 3.3.4 → 3.4.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/cjs/core/Carousel/CarouselSlider.js +1 -1
  3. package/cjs/core/ComboBox/ComboBox.js +1 -1
  4. package/cjs/core/DatePicker/DatePicker.d.ts +8 -0
  5. package/cjs/core/DatePicker/DatePicker.js +9 -4
  6. package/cjs/core/Dialog/DialogMain.js +1 -1
  7. package/cjs/core/FileUpload/FileUploadTemplate.d.ts +2 -1
  8. package/cjs/core/FileUpload/FileUploadTemplate.js +2 -1
  9. package/cjs/core/LinkAction/LinkAction.d.ts +3 -2
  10. package/cjs/core/LinkAction/LinkAction.js +33 -1
  11. package/cjs/core/Modal/Modal.d.ts +12 -0
  12. package/cjs/core/Modal/Modal.js +4 -4
  13. package/cjs/core/Popover/Popover.js +1 -1
  14. package/cjs/core/ProgressIndicators/ProgressLinear.d.ts +15 -7
  15. package/cjs/core/ProgressIndicators/ProgressLinear.js +14 -6
  16. package/cjs/core/ProgressIndicators/ProgressRadial.d.ts +3 -2
  17. package/cjs/core/ProgressIndicators/ProgressRadial.js +3 -1
  18. package/cjs/core/Table/Table.js +1 -1
  19. package/cjs/core/Tabs/Tabs.js +17 -16
  20. package/cjs/core/Tag/Tag.d.ts +29 -7
  21. package/cjs/core/Tag/Tag.js +12 -5
  22. package/cjs/core/ThemeProvider/ThemeProvider.js +2 -2
  23. package/cjs/core/utils/components/VirtualScroll.js +7 -7
  24. package/cjs/core/utils/hooks/useIsomorphicLayoutEffect.d.ts +4 -1
  25. package/cjs/core/utils/hooks/useIsomorphicLayoutEffect.js +5 -2
  26. package/cjs/core/utils/hooks/useMediaQuery.js +1 -1
  27. package/cjs/core/utils/hooks/useOverflow.js +3 -3
  28. package/esm/core/Carousel/CarouselSlider.js +2 -2
  29. package/esm/core/ComboBox/ComboBox.js +2 -2
  30. package/esm/core/DatePicker/DatePicker.d.ts +8 -0
  31. package/esm/core/DatePicker/DatePicker.js +9 -4
  32. package/esm/core/Dialog/DialogMain.js +2 -2
  33. package/esm/core/FileUpload/FileUploadTemplate.d.ts +2 -1
  34. package/esm/core/FileUpload/FileUploadTemplate.js +2 -1
  35. package/esm/core/LinkAction/LinkAction.d.ts +3 -2
  36. package/esm/core/LinkAction/LinkAction.js +7 -1
  37. package/esm/core/Modal/Modal.d.ts +12 -0
  38. package/esm/core/Modal/Modal.js +4 -4
  39. package/esm/core/Popover/Popover.js +2 -2
  40. package/esm/core/ProgressIndicators/ProgressLinear.d.ts +15 -7
  41. package/esm/core/ProgressIndicators/ProgressLinear.js +14 -6
  42. package/esm/core/ProgressIndicators/ProgressRadial.d.ts +3 -2
  43. package/esm/core/ProgressIndicators/ProgressRadial.js +3 -1
  44. package/esm/core/Table/Table.js +2 -2
  45. package/esm/core/Tabs/Tabs.js +18 -17
  46. package/esm/core/Tag/Tag.d.ts +29 -7
  47. package/esm/core/Tag/Tag.js +13 -6
  48. package/esm/core/ThemeProvider/ThemeProvider.js +3 -3
  49. package/esm/core/utils/components/VirtualScroll.js +8 -8
  50. package/esm/core/utils/hooks/useIsomorphicLayoutEffect.d.ts +4 -1
  51. package/esm/core/utils/hooks/useIsomorphicLayoutEffect.js +4 -1
  52. package/esm/core/utils/hooks/useMediaQuery.js +2 -2
  53. package/esm/core/utils/hooks/useOverflow.js +4 -4
  54. package/package.json +2 -2
  55. package/styles.css +8 -8
@@ -64,7 +64,7 @@ const useOverflow = (items, disabled = false, orientation = 'horizontal') => {
64
64
  const updateContainerSize = React.useCallback(({ width, height }) => setContainerSize(orientation === 'horizontal' ? width : height), [orientation]);
65
65
  const [resizeRef, observer] = (0, useResizeObserver_js_1.useResizeObserver)(updateContainerSize);
66
66
  const resizeObserverRef = React.useRef(observer);
67
- (0, useIsomorphicLayoutEffect_js_1.useIsomorphicLayoutEffect)(() => {
67
+ (0, useIsomorphicLayoutEffect_js_1.useLayoutEffect)(() => {
68
68
  if (disabled) {
69
69
  setVisibleCount(items.length);
70
70
  }
@@ -74,7 +74,7 @@ const useOverflow = (items, disabled = false, orientation = 'horizontal') => {
74
74
  }
75
75
  }, [containerSize, disabled, items]);
76
76
  const mergedRefs = (0, useMergedRefs_js_1.useMergedRefs)(containerRef, resizeRef);
77
- (0, useIsomorphicLayoutEffect_js_1.useIsomorphicLayoutEffect)(() => {
77
+ (0, useIsomorphicLayoutEffect_js_1.useLayoutEffect)(() => {
78
78
  if (!containerRef.current || disabled) {
79
79
  resizeObserverRef.current?.disconnect();
80
80
  return;
@@ -103,7 +103,7 @@ const useOverflow = (items, disabled = false, orientation = 'horizontal') => {
103
103
  }
104
104
  needsFullRerender.current = false;
105
105
  }, [containerSize, visibleCount, disabled, items.length, orientation]);
106
- (0, useIsomorphicLayoutEffect_js_1.useIsomorphicLayoutEffect)(() => {
106
+ (0, useIsomorphicLayoutEffect_js_1.useLayoutEffect)(() => {
107
107
  previousContainerSize.current = containerSize;
108
108
  }, [containerSize]);
109
109
  return [mergedRefs, visibleCount];
@@ -5,7 +5,7 @@
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
7
  import { CarouselContext } from './CarouselContext.js';
8
- import { getWindow, useMergedRefs, useIsomorphicLayoutEffect, Box, } from '../utils/index.js';
8
+ import { getWindow, useMergedRefs, useLayoutEffect, Box, } from '../utils/index.js';
9
9
  /**
10
10
  * `CarouselSlider` is the scrollable list that should consist of `CarouselSlide` components.
11
11
  */
@@ -23,7 +23,7 @@ export const CarouselSlider = React.forwardRef((props, ref) => {
23
23
  index,
24
24
  })
25
25
  : child) ?? [], [children, idPrefix]);
26
- useIsomorphicLayoutEffect(() => {
26
+ useLayoutEffect(() => {
27
27
  setSlideCount(items.length);
28
28
  }, [items.length, setSlideCount]);
29
29
  const sliderRef = React.useRef(null);
@@ -6,7 +6,7 @@ import * as React from 'react';
6
6
  import { MenuExtraContent } from '../Menu/MenuExtraContent.js';
7
7
  import { SelectTag } from '../Select/SelectTag.js';
8
8
  import { Text } from '../Typography/Text.js';
9
- import { mergeRefs, useLatestRef, useIsomorphicLayoutEffect, AutoclearingHiddenLiveRegion, useId, } from '../utils/index.js';
9
+ import { mergeRefs, useLatestRef, useLayoutEffect, AutoclearingHiddenLiveRegion, useId, } from '../utils/index.js';
10
10
  import { usePopover } from '../Popover/Popover.js';
11
11
  import { ComboBoxActionContext, comboBoxReducer, ComboBoxRefsContext, ComboBoxStateContext, } from './helpers.js';
12
12
  import { ComboBoxEndIcon } from './ComboBoxEndIcon.js';
@@ -94,7 +94,7 @@ export const ComboBox = React.forwardRef((props, forwardedRef) => {
94
94
  dispatch({ type: 'close' });
95
95
  onHideRef.current?.();
96
96
  }, [onHideRef]);
97
- useIsomorphicLayoutEffect(() => {
97
+ useLayoutEffect(() => {
98
98
  // When the dropdown opens
99
99
  if (isOpen) {
100
100
  inputRef.current?.focus(); // Focus the input
@@ -96,6 +96,14 @@ type DatePickerProps = {
96
96
  * @default true
97
97
  */
98
98
  applyBackground?: boolean;
99
+ /**
100
+ * Whether dates outside the current month should be displayed (in a muted text style).
101
+ *
102
+ * It is recommended to set this to false. Currently it defaults to true for backward compatibility.
103
+ *
104
+ * @default true
105
+ */
106
+ showDatesOutsideMonth?: boolean;
99
107
  } & DateRangePickerProps & Omit<TimePickerProps, 'date' | 'onChange' | 'setFocusHour'>;
100
108
  /**
101
109
  * Date picker component
@@ -110,7 +110,7 @@ export const generateLocalizedStrings = (locale) => {
110
110
  * <DatePicker date={new Date()} onChange={(e) => console.log('New date value: ' + e)} />
111
111
  */
112
112
  export const DatePicker = React.forwardRef((props, forwardedRef) => {
113
- const { date, onChange, localizedNames, className, setFocus = false, showTime = false, use12Hours = false, precision, hourStep, minuteStep, secondStep, useCombinedRenderer, combinedRenderer, hourRenderer, minuteRenderer, secondRenderer, meridiemRenderer, showYearSelection = false, enableRangeSelect = false, startDate, endDate, monthYearProps, calendarProps, monthProps, weekDayProps, dayProps, weekProps, isDateDisabled, applyBackground = true, ...rest } = props;
113
+ const { date, onChange, localizedNames, className, setFocus = false, showTime = false, use12Hours = false, precision, hourStep, minuteStep, secondStep, useCombinedRenderer, combinedRenderer, hourRenderer, minuteRenderer, secondRenderer, meridiemRenderer, showYearSelection = false, enableRangeSelect = false, startDate, endDate, monthYearProps, calendarProps, monthProps, weekDayProps, dayProps, weekProps, isDateDisabled, applyBackground = true, showDatesOutsideMonth = true, ...rest } = props;
114
114
  const monthNames = localizedNames?.months ?? defaultMonths;
115
115
  const shortDays = localizedNames?.shortDays ?? defaultShortDays;
116
116
  const longDays = localizedNames?.days ?? defaultLongDays;
@@ -152,8 +152,9 @@ export const DatePicker = React.forwardRef((props, forwardedRef) => {
152
152
  }, [date, setMonthAndYear, startDate, endDate, enableRangeSelect]);
153
153
  const days = React.useMemo(() => {
154
154
  let offsetToFirst = new Date(displayedYear, displayedMonthIndex, 1).getDay();
155
- // if its sunday, show one week before
156
- if (0 === offsetToFirst) {
155
+ // If it's sunday, show one week before, but only if dates outside month are shown.
156
+ // (We do not want empty space at the top if dates outside month are not shown.)
157
+ if (0 === offsetToFirst && showDatesOutsideMonth) {
157
158
  offsetToFirst = 7;
158
159
  }
159
160
  const daysInMonth = [];
@@ -163,7 +164,7 @@ export const DatePicker = React.forwardRef((props, forwardedRef) => {
163
164
  daysInMonth.push(new Date(displayedYear, displayedMonthIndex, adjustedDay));
164
165
  }
165
166
  return daysInMonth;
166
- }, [displayedMonthIndex, displayedYear]);
167
+ }, [displayedMonthIndex, displayedYear, showDatesOutsideMonth]);
167
168
  const weeks = React.useMemo(() => {
168
169
  const weeksInMonth = [];
169
170
  const weekCount = Math.ceil(days.length / 7);
@@ -353,6 +354,10 @@ export const DatePicker = React.forwardRef((props, forwardedRef) => {
353
354
  return (React.createElement(Box, { as: 'div', key: `week-${displayedMonthIndex}-${weekIndex}`, ...weekProps, className: cx('iui-calendar-week', weekProps?.className) }, weekDays.map((weekDay, dayIndex) => {
354
355
  const dateValue = weekDay.getDate();
355
356
  const isDisabled = isDateDisabled?.(weekDay);
357
+ const isOutsideMonth = weekDay.getMonth() !== displayedMonthIndex;
358
+ if (isOutsideMonth && !showDatesOutsideMonth) {
359
+ return (React.createElement(Box, { key: `day-${displayedMonthIndex}-${dayIndex}`, className: cx(getDayClass(weekDay), dayProps?.className), "aria-hidden": true }));
360
+ }
356
361
  return (React.createElement(Box, { as: 'div', key: `day-${displayedMonthIndex}-${dayIndex}`, onClick: () => !isDisabled && onDayClick(weekDay), role: 'option', tabIndex: isSameDay(weekDay, focusedDay) ? 0 : -1, "aria-disabled": isDisabled ? 'true' : undefined, ref: (element) => isSameDay(weekDay, focusedDay) &&
357
362
  needFocus.current &&
358
363
  element?.focus(), ...dayProps, className: cx(getDayClass(weekDay), dayProps?.className) }, dateValue));
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { FocusTrap, getTranslateValuesFromElement, Resizer, useMergedRefs, useIsomorphicLayoutEffect, Box, } from '../utils/index.js';
7
+ import { FocusTrap, getTranslateValuesFromElement, Resizer, useMergedRefs, useLayoutEffect, Box, } from '../utils/index.js';
8
8
  import { useDialogContext } from './DialogContext.js';
9
9
  import { CSSTransition } from 'react-transition-group';
10
10
  import { DialogDragContext } from './DialogDragContext.js';
@@ -81,7 +81,7 @@ export const DialogMain = React.forwardRef((props, ref) => {
81
81
  }
82
82
  }, [isDraggable, onPointerDown]);
83
83
  // Prevents dialog from moving when window is being resized
84
- useIsomorphicLayoutEffect(() => {
84
+ useLayoutEffect(() => {
85
85
  if (!isDraggable || !isOpen) {
86
86
  return;
87
87
  }
@@ -32,7 +32,8 @@ type FileUploadTemplateProps = {
32
32
  children?: React.ReactNode;
33
33
  };
34
34
  /**
35
- * @deprecated Use `FileUploadCard` instead.
35
+ * @deprecated Use [`FileUploadCard`](https://itwinui.bentley.com/docs/fileupload#fileuploadcard) instead.
36
+ *
36
37
  * Default template to be used with the `FileUpload` wrapper component.
37
38
  * Contains a hidden input with styled labels (customizable).
38
39
  * @example
@@ -7,7 +7,8 @@ import cx from 'classnames';
7
7
  import { SvgUpload, Box } from '../utils/index.js';
8
8
  import { Anchor } from '../Typography/Anchor.js';
9
9
  /**
10
- * @deprecated Use `FileUploadCard` instead.
10
+ * @deprecated Use [`FileUploadCard`](https://itwinui.bentley.com/docs/fileupload#fileuploadcard) instead.
11
+ *
11
12
  * Default template to be used with the `FileUpload` wrapper component.
12
13
  * Contains a hidden input with styled labels (customizable).
13
14
  * @example
@@ -1,3 +1,4 @@
1
+ import type { PolymorphicForwardRefComponent } from '../utils/props.js';
1
2
  /**
2
3
  * Polymorphic link action component.
3
4
  * It is rendered as `a` by default.
@@ -8,7 +9,7 @@
8
9
  * </Surface>
9
10
  * </LinkBox>
10
11
  */
11
- export declare const LinkAction: import("../utils/props.js").PolymorphicForwardRefComponent<"a", {}>;
12
+ export declare const LinkAction: PolymorphicForwardRefComponent<"a", {}>;
12
13
  /**
13
14
  * Polymorphic link box component.
14
15
  * Used to wrap around your component to use LinkAction.
@@ -20,4 +21,4 @@ export declare const LinkAction: import("../utils/props.js").PolymorphicForwardR
20
21
  * </Surface>
21
22
  * </LinkBox>
22
23
  */
23
- export declare const LinkBox: import("../utils/props.js").PolymorphicForwardRefComponent<"div", {}>;
24
+ export declare const LinkBox: PolymorphicForwardRefComponent<"div", {}>;
@@ -2,7 +2,10 @@
2
2
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
+ import * as React from 'react';
6
+ import cx from 'classnames';
5
7
  import { polymorphic } from '../utils/functions/polymorphic.js';
8
+ import { Box } from '../utils/components/index.js';
6
9
  /**
7
10
  * Polymorphic link action component.
8
11
  * It is rendered as `a` by default.
@@ -13,7 +16,10 @@ import { polymorphic } from '../utils/functions/polymorphic.js';
13
16
  * </Surface>
14
17
  * </LinkBox>
15
18
  */
16
- export const LinkAction = polymorphic.a('iui-link-action');
19
+ export const LinkAction = React.forwardRef((props, forwardedRef) => {
20
+ const { as: asProp = (!!props.href ? 'a' : 'button') } = props;
21
+ return (React.createElement(Box, { ...props, as: asProp, className: cx('iui-link-action', props.className), ref: forwardedRef }));
22
+ });
17
23
  LinkAction.displayName = 'LinkAction';
18
24
  /**
19
25
  * Polymorphic link box component.
@@ -42,6 +42,18 @@ type ModalProps = {
42
42
  * Content of the modal.
43
43
  */
44
44
  children: React.ReactNode;
45
+ /**
46
+ * Props for customizing the title bar element.
47
+ */
48
+ titleBarProps?: React.ComponentPropsWithRef<'div'>;
49
+ /**
50
+ * Props for customizing the dialog-wrapper element.
51
+ */
52
+ wrapperProps?: React.ComponentPropsWithoutRef<'div'>;
53
+ /**
54
+ * Props for customizing the backdrop element.
55
+ */
56
+ backdropProps?: React.ComponentPropsWithRef<'div'>;
45
57
  } & Pick<DialogMainProps, 'isOpen' | 'styleType'>;
46
58
  /**
47
59
  * Modal component which can wrap any content.
@@ -26,11 +26,11 @@ import { Dialog } from '../Dialog/Dialog.js';
26
26
  * </Modal>
27
27
  */
28
28
  export const Modal = React.forwardRef((props, forwardedRef) => {
29
- const { isOpen = false, isDismissible = true, closeOnEsc = true, closeOnExternalClick = true, onClose, title, children, portal = true, ...rest } = props;
30
- return (React.createElement(Dialog, { isOpen: isOpen, closeOnEsc: closeOnEsc, closeOnExternalClick: closeOnExternalClick, isDismissible: isDismissible, onClose: onClose, preventDocumentScroll: true, trapFocus: true, setFocus: true, ref: forwardedRef, portal: portal },
31
- React.createElement(Dialog.Backdrop, null),
29
+ const { isOpen = false, isDismissible = true, closeOnEsc = true, closeOnExternalClick = true, onClose, title, children, portal = true, wrapperProps, backdropProps, titleBarProps, ...rest } = props;
30
+ return (React.createElement(Dialog, { isOpen: isOpen, closeOnEsc: closeOnEsc, closeOnExternalClick: closeOnExternalClick, isDismissible: isDismissible, onClose: onClose, preventDocumentScroll: true, trapFocus: true, setFocus: true, ref: forwardedRef, portal: portal, ...wrapperProps },
31
+ React.createElement(Dialog.Backdrop, { ...backdropProps }),
32
32
  React.createElement(Dialog.Main, { "aria-modal": true, ...rest },
33
- React.createElement(Dialog.TitleBar, { titleText: title }),
33
+ React.createElement(Dialog.TitleBar, { titleText: title, ...titleBarProps }),
34
34
  children)));
35
35
  });
36
36
  export default Modal;
@@ -5,7 +5,7 @@
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
7
  import { useFloating, useClick, useDismiss, useInteractions, size, autoUpdate, offset, flip, shift, autoPlacement, inline, hide, FloatingFocusManager, useHover, useFocus, safePolygon, useRole, FloatingPortal, } from '@floating-ui/react';
8
- import { Box, cloneElementWithRef, useControlledState, useId, useIsomorphicLayoutEffect, useMergedRefs, } from '../utils/index.js';
8
+ import { Box, cloneElementWithRef, useControlledState, useId, useLayoutEffect, useMergedRefs, } from '../utils/index.js';
9
9
  import { Portal } from '../utils/components/Portal.js';
10
10
  import { ThemeProvider } from '../ThemeProvider/ThemeProvider.js';
11
11
  // ----------------------------------------------------------------------------
@@ -101,7 +101,7 @@ export const Popover = React.forwardRef((props, forwardedRef) => {
101
101
  const popoverRef = useMergedRefs(popover.refs.setFloating, forwardedRef, setPopoverElement);
102
102
  const triggerId = `${useId()}-trigger`;
103
103
  const hasAriaLabel = !!props['aria-labelledby'] || !!props['aria-label'];
104
- useIsomorphicLayoutEffect(() => {
104
+ useLayoutEffect(() => {
105
105
  if (!positionReference) {
106
106
  return;
107
107
  }
@@ -23,7 +23,7 @@ type ProgressLinearProps = {
23
23
  /**
24
24
  * Status of progress.
25
25
  */
26
- status?: 'positive' | 'negative';
26
+ status?: 'positive' | 'negative' | 'warning';
27
27
  /**
28
28
  * Pass props to ProgressLinear label group.
29
29
  */
@@ -32,16 +32,24 @@ type ProgressLinearProps = {
32
32
  /**
33
33
  * Shows progress on a linear bar
34
34
  * @example
35
- * Determinate
35
+ * // Determinate
36
36
  * <ProgressLinear value={25}/>
37
- * Indeterminate
37
+ * // Indeterminate
38
38
  * <ProgressLinear indeterminate={true}/>
39
- * Labeled - Center
39
+ * // Labeled - Center
40
40
  * <ProgressLinear value={50} labels={['Centered Label']} />
41
- * Labeled - Left & Right
41
+ * // Labeled - Left & Right
42
42
  * <ProgressLinear value={50} labels={['Loading...', '50%']} />
43
- * Positive / Negative.
44
- * <ProgressLinear status='positive' labels={['Upload done!', <SvgStatusSuccessHollow />]} />
43
+ * // Status
44
+ * <ProgressLinear
45
+ * status='positive'
46
+ * labels={[
47
+ * 'Upload done!',
48
+ * <Icon key='icon'>
49
+ * <SvgStatusSuccess />{' '}
50
+ * </Icon>
51
+ * ]}
52
+ * />
45
53
  * <ProgressLinear status='negative' />
46
54
  */
47
55
  export declare const ProgressLinear: PolymorphicForwardRefComponent<"div", ProgressLinearProps>;
@@ -8,16 +8,24 @@ import { Box, getBoundedValue } from '../utils/index.js';
8
8
  /**
9
9
  * Shows progress on a linear bar
10
10
  * @example
11
- * Determinate
11
+ * // Determinate
12
12
  * <ProgressLinear value={25}/>
13
- * Indeterminate
13
+ * // Indeterminate
14
14
  * <ProgressLinear indeterminate={true}/>
15
- * Labeled - Center
15
+ * // Labeled - Center
16
16
  * <ProgressLinear value={50} labels={['Centered Label']} />
17
- * Labeled - Left & Right
17
+ * // Labeled - Left & Right
18
18
  * <ProgressLinear value={50} labels={['Loading...', '50%']} />
19
- * Positive / Negative.
20
- * <ProgressLinear status='positive' labels={['Upload done!', <SvgStatusSuccessHollow />]} />
19
+ * // Status
20
+ * <ProgressLinear
21
+ * status='positive'
22
+ * labels={[
23
+ * 'Upload done!',
24
+ * <Icon key='icon'>
25
+ * <SvgStatusSuccess />{' '}
26
+ * </Icon>
27
+ * ]}
28
+ * />
21
29
  * <ProgressLinear status='negative' />
22
30
  */
23
31
  export const ProgressLinear = React.forwardRef((props, forwardedRef) => {
@@ -14,7 +14,7 @@ type ProgressRadialProps = {
14
14
  /**
15
15
  * Status of Progress. Positive status always has 100% value.
16
16
  */
17
- status?: 'positive' | 'negative';
17
+ status?: 'positive' | 'negative' | 'warning';
18
18
  /**
19
19
  * Size of the progress indicator. Defaults to medium size.
20
20
  * @default ''
@@ -32,9 +32,10 @@ type ProgressRadialProps = {
32
32
  * <ProgressRadial value={50} />
33
33
  * Indeterminate
34
34
  * <ProgressRadial indeterminate />
35
- * Positive / Negative
35
+ * // Positive / Negative / Warning
36
36
  * <ProgressRadial status='positive' />
37
37
  * <ProgressRadial status='negative' />
38
+ * <ProgressRadial status='warning' />
38
39
  * Centered Content
39
40
  * <ProgressRadial value={63}>63</ProgressRadial>
40
41
  * Small
@@ -12,9 +12,10 @@ import { SvgCheckmarkSmall, SvgImportantSmall, Box, getBoundedValue, } from '../
12
12
  * <ProgressRadial value={50} />
13
13
  * Indeterminate
14
14
  * <ProgressRadial indeterminate />
15
- * Positive / Negative
15
+ * // Positive / Negative / Warning
16
16
  * <ProgressRadial status='positive' />
17
17
  * <ProgressRadial status='negative' />
18
+ * <ProgressRadial status='warning' />
18
19
  * Centered Content
19
20
  * <ProgressRadial value={63}>63</ProgressRadial>
20
21
  * Small
@@ -25,6 +26,7 @@ export const ProgressRadial = React.forwardRef((props, forwardedRef) => {
25
26
  const statusMap = {
26
27
  negative: React.createElement(SvgImportantSmall, { "aria-hidden": true }),
27
28
  positive: React.createElement(SvgCheckmarkSmall, { "aria-hidden": true }),
29
+ warning: React.createElement(SvgImportantSmall, { "aria-hidden": true }),
28
30
  };
29
31
  return (React.createElement(Box, { className: cx('iui-progress-indicator-radial', className), "data-iui-size": size, "data-iui-status": status, "data-iui-indeterminate": indeterminate ? 'true' : undefined, ref: forwardedRef, style: {
30
32
  ...(value !== undefined && {
@@ -7,7 +7,7 @@ import * as ReactDOM from 'react-dom';
7
7
  import cx from 'classnames';
8
8
  import { actions as TableActions, useFlexLayout, useFilters, useRowSelect, useSortBy, useTable, useExpanded, usePagination, useColumnOrder, useGlobalFilter, } from 'react-table';
9
9
  import { ProgressRadial } from '../ProgressIndicators/ProgressRadial.js';
10
- import { useGlobals, useResizeObserver, SvgSortDown, SvgSortUp, useIsomorphicLayoutEffect, Box, createWarningLogger, } from '../utils/index.js';
10
+ import { useGlobals, useResizeObserver, SvgSortDown, SvgSortUp, useLayoutEffect, Box, createWarningLogger, } from '../utils/index.js';
11
11
  import { getCellStyle, getStickyStyle, getSubRowStyle } from './utils.js';
12
12
  import { TableRowMemoized } from './TableRowMemoized.js';
13
13
  import { FilterToggle } from './filters/index.js';
@@ -328,7 +328,7 @@ export const Table = (props) => {
328
328
  const [headerScrollWidth, setHeaderScrollWidth] = React.useState(0);
329
329
  const [headerClientWidth, setHeaderClientWidth] = React.useState(0);
330
330
  // Flexbox handles columns resize so we take new column widths before browser repaints.
331
- useIsomorphicLayoutEffect(() => {
331
+ useLayoutEffect(() => {
332
332
  if (state.isTableResizing) {
333
333
  const newColumnWidths = {};
334
334
  flatHeaders.forEach((column) => {
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import cx from 'classnames';
6
6
  import * as React from 'react';
7
- import { useSafeContext, Box, polymorphic, useIsClient, useIsomorphicLayoutEffect, useMergedRefs, useContainerWidth, ButtonBase, mergeEventHandlers, useControlledState, useId, useLatestRef, } from '../utils/index.js';
7
+ import { useSafeContext, Box, polymorphic, useIsClient, useLayoutEffect, useMergedRefs, useContainerWidth, ButtonBase, mergeEventHandlers, useControlledState, useId, useLatestRef, } from '../utils/index.js';
8
8
  import { Icon } from '../Icon/Icon.js';
9
9
  const TabsWrapper = React.forwardRef((props, ref) => {
10
10
  const { className, children, orientation = 'horizontal', type = 'default', focusActivationMode = 'auto', color = 'blue', defaultValue, value: activeValueProp, onValueChange, ...rest } = props;
@@ -53,27 +53,27 @@ const Tab = React.forwardRef((props, forwardedRef) => {
53
53
  const isActive = activeValue === value;
54
54
  const isActiveRef = useLatestRef(isActive);
55
55
  // Scroll to active tab only on initial render
56
- useIsomorphicLayoutEffect(() => {
56
+ useLayoutEffect(() => {
57
57
  if (isActiveRef.current) {
58
58
  tabRef.current?.parentElement?.scrollTo({
59
59
  [orientation === 'horizontal' ? 'left' : 'top']: tabRef.current?.[orientation === 'horizontal' ? 'offsetLeft' : 'offsetTop'] - 4,
60
60
  behavior: 'instant', // not using 'smooth' to reduce layout shift on page load
61
61
  });
62
62
  }
63
- }, []);
64
- const updateStripe = () => {
65
- const currentTabRect = tabRef.current?.getBoundingClientRect();
66
- setStripeProperties({
67
- '--iui-tabs-stripe-size': orientation === 'horizontal'
68
- ? `${currentTabRect?.width}px`
69
- : `${currentTabRect?.height}px`,
70
- '--iui-tabs-stripe-position': orientation === 'horizontal'
71
- ? `${tabRef.current?.offsetLeft}px`
72
- : `${tabRef.current?.offsetTop}px`,
73
- });
74
- };
63
+ }, [isActiveRef, orientation]);
75
64
  // CSS custom properties to place the active stripe
76
- useIsomorphicLayoutEffect(() => {
65
+ useLayoutEffect(() => {
66
+ const updateStripe = () => {
67
+ const currentTabRect = tabRef.current?.getBoundingClientRect();
68
+ setStripeProperties({
69
+ '--iui-tabs-stripe-size': orientation === 'horizontal'
70
+ ? `${currentTabRect?.width}px`
71
+ : `${currentTabRect?.height}px`,
72
+ '--iui-tabs-stripe-position': orientation === 'horizontal'
73
+ ? `${tabRef.current?.offsetLeft}px`
74
+ : `${tabRef.current?.offsetTop}px`,
75
+ });
76
+ };
77
77
  if (type !== 'default' && isActive) {
78
78
  updateStripe();
79
79
  }
@@ -81,7 +81,8 @@ const Tab = React.forwardRef((props, forwardedRef) => {
81
81
  type,
82
82
  orientation,
83
83
  isActive,
84
- tabsWidth, // to fix visual artifact on initial render
84
+ tabsWidth,
85
+ setStripeProperties,
85
86
  ]);
86
87
  const onKeyDown = (event) => {
87
88
  if (event.altKey) {
@@ -157,7 +158,7 @@ TabLabel.displayName = 'Tabs.TabLabel';
157
158
  const TabDescription = React.forwardRef((props, ref) => {
158
159
  const { className, children, ...rest } = props;
159
160
  const { hasSublabel, setHasSublabel } = useSafeContext(TabsContext);
160
- useIsomorphicLayoutEffect(() => {
161
+ useLayoutEffect(() => {
161
162
  if (!hasSublabel) {
162
163
  setHasSublabel(true);
163
164
  }
@@ -2,22 +2,44 @@ import * as React from 'react';
2
2
  import type { PolymorphicForwardRefComponent } from '../utils/index.js';
3
3
  type TagProps = {
4
4
  /**
5
- * Callback function that handles click on close icon.
6
- * Close icon is shown only when this function is passed.
7
- * Use only with 'default' Tag.
5
+ * Text inside the tag.
6
+ */
7
+ children: React.ReactNode;
8
+ /**
9
+ * Callback invoked when the tag is clicked.
10
+ *
11
+ * When this prop is passed, the tag will be rendered as a button.
12
+ */
13
+ onClick?: React.MouseEventHandler;
14
+ /**
15
+ * Callback function that handles click on the remove ("❌") button.
16
+ * If not passed, the remove button will not be shown.
17
+ *
18
+ * If both `onClick` and `onRemove` are passed, then the tag label (rather than the tag itself)
19
+ * will be rendered as a button, to avoid invalid markup (nested buttons).
8
20
  */
9
21
  onRemove?: React.MouseEventHandler;
10
22
  /**
11
- * Text inside the tag.
23
+ * Props for customizing the remove ("❌") button.
12
24
  */
13
- children: React.ReactNode;
25
+ removeButtonProps?: React.ComponentPropsWithRef<'button'>;
26
+ } & ({
14
27
  /**
15
28
  * Variant of tag.
16
29
  * Basic tags don't have an outline.
17
30
  * @default 'default'
18
31
  */
19
- variant?: 'default' | 'basic';
20
- };
32
+ variant?: 'default';
33
+ /**
34
+ * Props for customizing the label.
35
+ *
36
+ * Only relevant for the 'default' Tag.
37
+ */
38
+ labelProps?: React.ComponentPropsWithRef<'span'>;
39
+ } | {
40
+ variant?: 'basic';
41
+ labelProps?: never;
42
+ });
21
43
  /**
22
44
  * Tag for showing categories, filters etc.
23
45
  * @example
@@ -4,8 +4,9 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import cx from 'classnames';
6
6
  import * as React from 'react';
7
- import { SvgCloseSmall, Box } from '../utils/index.js';
7
+ import { SvgCloseSmall, Box, ButtonBase } from '../utils/index.js';
8
8
  import { IconButton } from '../Buttons/IconButton.js';
9
+ import { LinkAction, LinkBox } from '../LinkAction/LinkAction.js';
9
10
  /**
10
11
  * Tag for showing categories, filters etc.
11
12
  * @example
@@ -13,13 +14,19 @@ import { IconButton } from '../Buttons/IconButton.js';
13
14
  * <Tag variant='basic'>Basic tag</Tag>
14
15
  */
15
16
  export const Tag = React.forwardRef((props, forwardedRef) => {
16
- const { className, variant = 'default', children, onRemove, ...rest } = props;
17
- return (React.createElement(Box, { as: 'span', className: cx({
17
+ const { className, variant = 'default', children, onRemove, onClick, labelProps, removeButtonProps, ...rest } = props;
18
+ // If both onClick and onRemove are passed, we want to render the label as a button
19
+ // to avoid invalid markup (nested buttons). LinkAction ensures that clicking anywhere outside
20
+ // the remove button (including padding) will still trigger the main onClick callback.
21
+ const shouldUseLinkAction = !!onClick && !!onRemove;
22
+ return (React.createElement(Box, { as: shouldUseLinkAction ? LinkBox : !!onClick ? ButtonBase : 'span', className: cx({
18
23
  'iui-tag-basic': variant === 'basic',
19
24
  'iui-tag': variant === 'default',
20
- }, className), ref: forwardedRef, ...rest },
21
- variant === 'default' ? (React.createElement(Box, { as: 'span', className: 'iui-tag-label' }, children)) : (children),
22
- onRemove && (React.createElement(IconButton, { styleType: 'borderless', size: 'small', onClick: onRemove, "aria-label": 'Delete tag', className: 'iui-tag-button' },
25
+ }, className),
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- ref's type doesn't matter internally
27
+ ref: forwardedRef, onClick: !shouldUseLinkAction ? onClick : undefined, ...rest },
28
+ variant === 'default' ? (React.createElement(Box, { as: (shouldUseLinkAction ? LinkAction : 'span'), onClick: shouldUseLinkAction ? onClick : undefined, ...labelProps, className: cx('iui-tag-label', labelProps?.className) }, children)) : (children),
29
+ onRemove && (React.createElement(IconButton, { styleType: 'borderless', size: 'small', onClick: onRemove, "aria-label": 'Delete tag', ...removeButtonProps, className: cx('iui-tag-button', removeButtonProps?.className) },
23
30
  React.createElement(SvgCloseSmall, { "aria-hidden": true })))));
24
31
  });
25
32
  export default Tag;
@@ -5,7 +5,7 @@
5
5
  import * as React from 'react';
6
6
  import * as ReactDOM from 'react-dom';
7
7
  import cx from 'classnames';
8
- import { useMediaQuery, useMergedRefs, Box, useIsomorphicLayoutEffect, useControlledState, useLatestRef, importCss, isUnitTest, } from '../utils/index.js';
8
+ import { useMediaQuery, useMergedRefs, Box, useLayoutEffect, useControlledState, useLatestRef, importCss, isUnitTest, } from '../utils/index.js';
9
9
  import { ThemeContext } from './ThemeContext.js';
10
10
  import { ToastProvider, Toaster } from '../Toast/Toaster.js';
11
11
  /**
@@ -87,7 +87,7 @@ const useParentThemeAndContext = (rootElement) => {
87
87
  const [parentThemeState, setParentTheme] = React.useState(parentContext?.theme);
88
88
  const [parentHighContrastState, setParentHighContrastState] = React.useState(parentContext?.themeOptions?.highContrast);
89
89
  const parentThemeRef = useLatestRef(parentContext?.theme);
90
- useIsomorphicLayoutEffect(() => {
90
+ useLayoutEffect(() => {
91
91
  // bail if we already have theme from context
92
92
  if (parentThemeRef.current) {
93
93
  return;
@@ -126,7 +126,7 @@ const useParentThemeAndContext = (rootElement) => {
126
126
  * dynamically import it (if possible) and fallback to loading it from a CDN.
127
127
  */
128
128
  const FallbackStyles = ({ root }) => {
129
- useIsomorphicLayoutEffect(() => {
129
+ useLayoutEffect(() => {
130
130
  // bail if styles are already loaded
131
131
  if (getComputedStyle(root).getPropertyValue('--_iui-v3-loaded') === 'yes') {
132
132
  return;