@itwin/itwinui-react 3.3.3 → 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 +20 -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 +5 -4
  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 +23 -10
  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 +6 -5
  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 +24 -11
  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 +9 -9
@@ -116,7 +116,7 @@ const useParentThemeAndContext = (rootElement) => {
116
116
  const [parentThemeState, setParentTheme] = React.useState(parentContext?.theme);
117
117
  const [parentHighContrastState, setParentHighContrastState] = React.useState(parentContext?.themeOptions?.highContrast);
118
118
  const parentThemeRef = (0, index_js_1.useLatestRef)(parentContext?.theme);
119
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
119
+ (0, index_js_1.useLayoutEffect)(() => {
120
120
  // bail if we already have theme from context
121
121
  if (parentThemeRef.current) {
122
122
  return;
@@ -155,7 +155,7 @@ const useParentThemeAndContext = (rootElement) => {
155
155
  * dynamically import it (if possible) and fallback to loading it from a CDN.
156
156
  */
157
157
  const FallbackStyles = ({ root }) => {
158
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
158
+ (0, index_js_1.useLayoutEffect)(() => {
159
159
  // bail if styles are already loaded
160
160
  if (getComputedStyle(root).getPropertyValue('--_iui-v3-loaded') === 'yes') {
161
161
  return;
@@ -172,17 +172,17 @@ const useVirtualization = (props) => {
172
172
  const [resizeRef, resizeObserver] = (0, index_js_1.useResizeObserver)(onResize);
173
173
  // Find scrollable parent
174
174
  // Needed only on init
175
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
175
+ (0, index_js_1.useLayoutEffect)(() => {
176
176
  const scrollableParent = getScrollableParent(parentRef.current, parentRef.current?.ownerDocument);
177
177
  scrollContainer.current = scrollableParent;
178
178
  resizeRef(scrollableParent);
179
179
  }, [resizeRef]);
180
180
  // Stop watching resize, when virtual scroll is unmounted
181
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
181
+ (0, index_js_1.useLayoutEffect)(() => {
182
182
  return () => resizeObserver?.disconnect();
183
183
  }, [resizeObserver]);
184
184
  // Get child height when children available
185
- (0, index_js_1.useIsomorphicLayoutEffect)(() => updateChildHeight(), [updateChildHeight]);
185
+ (0, index_js_1.useLayoutEffect)(() => updateChildHeight(), [updateChildHeight]);
186
186
  const updateVirtualScroll = React.useCallback(() => {
187
187
  const scrollableContainer = getScrollableContainer();
188
188
  if (!scrollableContainer) {
@@ -215,7 +215,7 @@ const useVirtualization = (props) => {
215
215
  : scrollContainer.current.removeEventListener('scroll', onScrollRef.current);
216
216
  }, []);
217
217
  // Add event listener to the scrollable container.
218
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
218
+ (0, index_js_1.useLayoutEffect)(() => {
219
219
  removeScrollListener();
220
220
  onScrollRef.current = onScroll;
221
221
  if (!scrollContainer.current ||
@@ -227,7 +227,7 @@ const useVirtualization = (props) => {
227
227
  }
228
228
  return removeScrollListener;
229
229
  }, [onScroll, removeScrollListener]);
230
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
230
+ (0, index_js_1.useLayoutEffect)(() => {
231
231
  if (!isMounted) {
232
232
  return;
233
233
  }
@@ -282,8 +282,8 @@ const useVirtualization = (props) => {
282
282
  });
283
283
  }
284
284
  }
285
- }, [scrollToIndex, isMounted]);
286
- (0, index_js_1.useIsomorphicLayoutEffect)(() => {
285
+ }, [scrollToIndex, isMounted, itemsLength]);
286
+ (0, index_js_1.useLayoutEffect)(() => {
287
287
  if (!scrollContainerHeight) {
288
288
  return;
289
289
  }
@@ -2,6 +2,9 @@ import * as React from 'react';
2
2
  /**
3
3
  * SSR-safe version of `useLayoutEffect` that replaces it with `useEffect` on the server.
4
4
  *
5
+ * Exported as `useLayoutEffect` so that the react hooks linter correctly identifies the necessary dependencies.
6
+ *
5
7
  * @see https://fb.me/react-uselayouteffect-ssr
6
8
  */
7
- export declare const useIsomorphicLayoutEffect: typeof React.useEffect;
9
+ declare const useIsomorphicLayoutEffect: typeof React.useEffect;
10
+ export { useIsomorphicLayoutEffect as useLayoutEffect };
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.useIsomorphicLayoutEffect = void 0;
26
+ exports.useLayoutEffect = void 0;
27
27
  /*---------------------------------------------------------------------------------------------
28
28
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
29
29
  * See LICENSE.md in the project root for license terms and full copyright notice.
@@ -32,6 +32,9 @@ const React = __importStar(require("react"));
32
32
  /**
33
33
  * SSR-safe version of `useLayoutEffect` that replaces it with `useEffect` on the server.
34
34
  *
35
+ * Exported as `useLayoutEffect` so that the react hooks linter correctly identifies the necessary dependencies.
36
+ *
35
37
  * @see https://fb.me/react-uselayouteffect-ssr
36
38
  */
37
- exports.useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
39
+ const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
40
+ exports.useLayoutEffect = useIsomorphicLayoutEffect;
@@ -33,7 +33,7 @@ const index_js_1 = require("../functions/index.js");
33
33
  const useIsomorphicLayoutEffect_js_1 = require("./useIsomorphicLayoutEffect.js");
34
34
  const useMediaQuery = (queryString) => {
35
35
  const [matches, setMatches] = React.useState();
36
- (0, useIsomorphicLayoutEffect_js_1.useIsomorphicLayoutEffect)(() => {
36
+ (0, useIsomorphicLayoutEffect_js_1.useLayoutEffect)(() => {
37
37
  const mediaQueryList = (0, index_js_1.getWindow)()?.matchMedia?.(queryString);
38
38
  const handleChange = ({ matches }) => setMatches(matches);
39
39
  if (mediaQueryList != undefined) {
@@ -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,11 +4,12 @@
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';
11
11
  import useDragAndDrop from '../utils/hooks/useDragAndDrop.js';
12
+ import styles from '../../styles.js';
12
13
  /**
13
14
  * Dialog component which can wrap any content.
14
15
  * @example
@@ -80,7 +81,7 @@ export const DialogMain = React.forwardRef((props, ref) => {
80
81
  }
81
82
  }, [isDraggable, onPointerDown]);
82
83
  // Prevents dialog from moving when window is being resized
83
- useIsomorphicLayoutEffect(() => {
84
+ useLayoutEffect(() => {
84
85
  if (!isDraggable || !isOpen) {
85
86
  return;
86
87
  }
@@ -104,7 +105,6 @@ export const DialogMain = React.forwardRef((props, ref) => {
104
105
  const content = (React.createElement(Box, { className: cx('iui-dialog', {
105
106
  'iui-dialog-default': styleType === 'default',
106
107
  'iui-dialog-full-page': styleType === 'fullPage',
107
- 'iui-dialog-visible': isOpen,
108
108
  'iui-dialog-draggable': isDraggable,
109
109
  }, className), role: 'dialog', ref: useMergedRefs(dialogRef, ref), onKeyDown: handleKeyDown, tabIndex: -1, "data-iui-placement": placement, style: {
110
110
  transform,
@@ -119,8 +119,9 @@ export const DialogMain = React.forwardRef((props, ref) => {
119
119
  }, onResizeEnd: setResizeStyle })),
120
120
  children));
121
121
  return (React.createElement(CSSTransition, { in: isOpen, classNames: {
122
- enter: 'iui-dialog-animation-enter',
123
- enterActive: 'iui-dialog-animation-enter-active',
122
+ enter: styles['iui-dialog-animation-enter'],
123
+ enterActive: styles['iui-dialog-animation-enter-active'],
124
+ enterDone: styles['iui-dialog-visible'],
124
125
  }, timeout: { exit: 600 },
125
126
  // Focuses dialog when opened
126
127
  onEntered: () => {
@@ -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';
@@ -287,10 +287,19 @@ export const Table = (props) => {
287
287
  instance.selectedFlatRows,
288
288
  selectionMode,
289
289
  ]);
290
+ const headerRef = React.useRef(null);
291
+ const bodyRef = React.useRef(null);
290
292
  const { scrollToIndex, tableRowRef } = useScrollToRow({ ...props, page });
291
293
  const columnRefs = React.useRef({});
292
294
  const previousTableWidth = React.useRef(0);
293
295
  const onTableResize = React.useCallback(({ width }) => {
296
+ // Handle header properties, regardless of whether the table is resizable
297
+ setHeaderScrollWidth(headerRef.current?.scrollWidth ?? 0);
298
+ setHeaderClientWidth(headerRef.current?.clientWidth ?? 0);
299
+ // Handle table properties, but only when table is resizable
300
+ if (!isResizable) {
301
+ return;
302
+ }
294
303
  instance.tableWidth = width;
295
304
  if (width === previousTableWidth.current) {
296
305
  return;
@@ -308,10 +317,18 @@ export const Table = (props) => {
308
317
  return;
309
318
  }
310
319
  dispatch({ type: tableResizeStartAction });
311
- }, [dispatch, state.columnResizing.columnWidths, flatHeaders, instance]);
320
+ }, [
321
+ dispatch,
322
+ state.columnResizing.columnWidths,
323
+ flatHeaders,
324
+ instance,
325
+ isResizable,
326
+ ]);
312
327
  const [resizeRef] = useResizeObserver(onTableResize);
328
+ const [headerScrollWidth, setHeaderScrollWidth] = React.useState(0);
329
+ const [headerClientWidth, setHeaderClientWidth] = React.useState(0);
313
330
  // Flexbox handles columns resize so we take new column widths before browser repaints.
314
- useIsomorphicLayoutEffect(() => {
331
+ useLayoutEffect(() => {
315
332
  if (state.isTableResizing) {
316
333
  const newColumnWidths = {};
317
334
  flatHeaders.forEach((column) => {
@@ -323,8 +340,6 @@ export const Table = (props) => {
323
340
  dispatch({ type: tableResizeEndAction, columnWidths: newColumnWidths });
324
341
  }
325
342
  });
326
- const headerRef = React.useRef(null);
327
- const bodyRef = React.useRef(null);
328
343
  const getPreparedRow = React.useCallback((index) => {
329
344
  const row = page[index];
330
345
  prepareRow(row);
@@ -374,9 +389,7 @@ export const Table = (props) => {
374
389
  return (React.createElement(React.Fragment, null,
375
390
  React.createElement(Box, { ref: (element) => {
376
391
  ownerDocument.current = element?.ownerDocument;
377
- if (isResizable) {
378
- resizeRef(element);
379
- }
392
+ resizeRef(element);
380
393
  }, id: id, ...getTableProps({
381
394
  className: cx('iui-table', className),
382
395
  style: {
@@ -468,12 +481,12 @@ export const Table = (props) => {
468
481
  }, tabIndex: -1, "aria-multiselectable": (isSelectable && selectionMode === 'multi') || undefined },
469
482
  React.createElement(ShadowTemplate, null,
470
483
  React.createElement("slot", null),
471
- React.createElement("div", { "aria-hidden": true, style: {
484
+ rows.length === 0 && headerScrollWidth > headerClientWidth && (React.createElement("div", { "aria-hidden": true, style: {
472
485
  // This ensures that the table-body is always the same width as the table-header,
473
486
  // even if the table has no rows. See https://github.com/iTwin/iTwinUI/pull/1725
474
- width: headerRef.current?.scrollWidth,
487
+ width: headerScrollWidth,
475
488
  height: 0.1,
476
- } })),
489
+ } }))),
477
490
  data.length !== 0 && (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualScroll, { itemsLength: page.length, itemRenderer: virtualizedItemRenderer, scrollToIndex: scrollToIndex })) : (page.map((_, index) => getPreparedRow(index))))),
478
491
  isLoading && data.length === 0 && (React.createElement(Box, { as: 'div', ...emptyTableContentProps, className: cx('iui-table-empty', emptyTableContentProps?.className) },
479
492
  React.createElement(ProgressRadial, { indeterminate: true }))),