@itwin/itwinui-react 3.0.11 → 3.1.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 (56) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/cjs/core/ButtonGroup/ButtonGroup.d.ts +2 -0
  4. package/cjs/core/ButtonGroup/ButtonGroup.js +26 -21
  5. package/cjs/core/Buttons/IconButton.d.ts +6 -0
  6. package/cjs/core/Buttons/IconButton.js +4 -2
  7. package/cjs/core/Carousel/Carousel.d.ts +110 -2
  8. package/cjs/core/Carousel/CarouselNavigation.d.ts +110 -2
  9. package/cjs/core/ComboBox/ComboBoxInput.js +17 -5
  10. package/cjs/core/FileUpload/FileUpload.d.ts +3 -1
  11. package/cjs/core/FileUpload/FileUpload.js +5 -8
  12. package/cjs/core/InputWithDecorations/InputWithDecorations.d.ts +55 -1
  13. package/cjs/core/Popover/Popover.d.ts +22 -14
  14. package/cjs/core/Popover/Popover.js +14 -2
  15. package/cjs/core/Table/Table.js +28 -2
  16. package/cjs/core/Tabs/Tabs.js +23 -3
  17. package/cjs/core/ThemeProvider/ThemeProvider.js +44 -21
  18. package/cjs/core/Tile/Tile.d.ts +61 -1
  19. package/cjs/core/Tooltip/Tooltip.js +6 -2
  20. package/cjs/core/Tree/Tree.d.ts +6 -0
  21. package/cjs/core/Tree/Tree.js +4 -3
  22. package/cjs/core/Tree/TreeContext.d.ts +4 -0
  23. package/cjs/core/Tree/TreeNodeExpander.js +4 -1
  24. package/cjs/core/utils/icons/SvgChevronRightSmall.d.ts +2 -0
  25. package/cjs/core/utils/icons/SvgChevronRightSmall.js +15 -0
  26. package/cjs/core/utils/icons/index.d.ts +1 -0
  27. package/cjs/core/utils/icons/index.js +1 -0
  28. package/cjs/styles.js +2 -2
  29. package/esm/core/ButtonGroup/ButtonGroup.d.ts +2 -0
  30. package/esm/core/ButtonGroup/ButtonGroup.js +25 -20
  31. package/esm/core/Buttons/IconButton.d.ts +6 -0
  32. package/esm/core/Buttons/IconButton.js +4 -2
  33. package/esm/core/Carousel/Carousel.d.ts +110 -2
  34. package/esm/core/Carousel/CarouselNavigation.d.ts +110 -2
  35. package/esm/core/ComboBox/ComboBoxInput.js +17 -5
  36. package/esm/core/FileUpload/FileUpload.d.ts +3 -1
  37. package/esm/core/FileUpload/FileUpload.js +6 -9
  38. package/esm/core/InputWithDecorations/InputWithDecorations.d.ts +55 -1
  39. package/esm/core/Popover/Popover.d.ts +22 -14
  40. package/esm/core/Popover/Popover.js +15 -3
  41. package/esm/core/Table/Table.js +28 -2
  42. package/esm/core/Tabs/Tabs.js +23 -3
  43. package/esm/core/ThemeProvider/ThemeProvider.js +45 -22
  44. package/esm/core/Tile/Tile.d.ts +61 -1
  45. package/esm/core/Tooltip/Tooltip.js +6 -2
  46. package/esm/core/Tree/Tree.d.ts +6 -0
  47. package/esm/core/Tree/Tree.js +4 -3
  48. package/esm/core/Tree/TreeContext.d.ts +4 -0
  49. package/esm/core/Tree/TreeNodeExpander.js +5 -2
  50. package/esm/core/utils/icons/SvgChevronRightSmall.d.ts +2 -0
  51. package/esm/core/utils/icons/SvgChevronRightSmall.js +10 -0
  52. package/esm/core/utils/icons/index.d.ts +1 -0
  53. package/esm/core/utils/icons/index.js +1 -0
  54. package/esm/styles.js +2 -2
  55. package/package.json +4 -4
  56. package/styles.css +14 -14
@@ -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, } from '../utils/index.js';
8
+ import { useMediaQuery, useMergedRefs, Box, useIsomorphicLayoutEffect, useControlledState, useLatestRef, } from '../utils/index.js';
9
9
  import { ThemeContext } from './ThemeContext.js';
10
10
  import { ToastProvider, Toaster } from '../Toast/Toaster.js';
11
11
  /**
@@ -38,27 +38,26 @@ import { ToastProvider, Toaster } from '../Toast/Toaster.js';
38
38
  */
39
39
  export const ThemeProvider = React.forwardRef((props, forwardedRef) => {
40
40
  const { theme: themeProp = 'inherit', children, themeOptions = {}, portalContainer: portalContainerProp, ...rest } = props;
41
- const [parentTheme, rootRef, parentContext] = useParentTheme();
42
- const theme = themeProp === 'inherit' ? parentTheme || 'light' : themeProp;
41
+ const [rootElement, setRootElement] = React.useState(null);
42
+ const parent = useParentThemeAndContext(rootElement);
43
+ const theme = themeProp === 'inherit' ? parent.theme || 'light' : themeProp;
43
44
  // default apply background only for topmost ThemeProvider
44
- themeOptions.applyBackground ?? (themeOptions.applyBackground = !parentTheme);
45
+ themeOptions.applyBackground ?? (themeOptions.applyBackground = !parent.theme);
45
46
  // default inherit highContrast option from parent if also inheriting base theme
46
- themeOptions.highContrast ?? (themeOptions.highContrast = themeProp === 'inherit'
47
- ? parentContext?.themeOptions?.highContrast
48
- : undefined);
47
+ themeOptions.highContrast ?? (themeOptions.highContrast = themeProp === 'inherit' ? parent.highContrast : undefined);
49
48
  /**
50
49
  * We will portal our portal container into `portalContainer` prop (if specified),
51
50
  * or inherit `portalContainer` from context (if also inheriting theme).
52
51
  */
53
52
  const portaledPortalContainer = portalContainerProp ||
54
- (themeProp === 'inherit' ? parentContext?.portalContainer : undefined);
53
+ (themeProp === 'inherit' ? parent.context?.portalContainer : undefined);
55
54
  const [portalContainer, setPortalContainer] = useControlledState(null, portaledPortalContainer);
56
55
  const contextValue = React.useMemo(() => ({ theme, themeOptions, portalContainer }),
57
56
  // we do include all dependencies below, but we want to stringify the objects as they could be different on each render
58
57
  // eslint-disable-next-line react-hooks/exhaustive-deps
59
58
  [theme, JSON.stringify(themeOptions), portalContainer]);
60
59
  return (React.createElement(ThemeContext.Provider, { value: contextValue },
61
- React.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: useMergedRefs(forwardedRef, rootRef), ...rest },
60
+ React.createElement(Root, { theme: theme, themeOptions: themeOptions, ref: useMergedRefs(forwardedRef, setRootElement), ...rest },
62
61
  React.createElement(ToastProvider, null,
63
62
  children,
64
63
  portaledPortalContainer ? (ReactDOM.createPortal(React.createElement(Toaster, null), portaledPortalContainer)) : (React.createElement("div", { ref: setPortalContainer, style: { display: 'contents' } },
@@ -77,22 +76,46 @@ const Root = React.forwardRef((props, forwardedRef) => {
77
76
  });
78
77
  // ----------------------------------------------------------------------------
79
78
  /**
80
- * Returns theme from either parent context or by reading the closest
79
+ * Returns theme information from either parent ThemeContext or by reading the closest
81
80
  * data-iui-theme attribute if context is not found.
81
+ *
82
+ * Also returns the ThemeContext itself (if found).
82
83
  */
83
- const useParentTheme = () => {
84
+ const useParentThemeAndContext = (rootElement) => {
84
85
  const parentContext = React.useContext(ThemeContext);
85
- const rootRef = React.useRef(null);
86
86
  const [parentThemeState, setParentTheme] = React.useState(parentContext?.theme);
87
+ const [parentHighContrastState, setParentHighContrastState] = React.useState(parentContext?.themeOptions?.highContrast);
88
+ const parentThemeRef = useLatestRef(parentContext?.theme);
87
89
  useIsomorphicLayoutEffect(() => {
88
- setParentTheme((old) => old ||
89
- rootRef.current?.parentElement
90
- ?.closest('[data-iui-theme]')
91
- ?.getAttribute('data-iui-theme'));
92
- }, []);
93
- return [
94
- parentContext?.theme ?? parentThemeState,
95
- rootRef,
96
- parentContext,
97
- ];
90
+ // bail if we already have theme from context
91
+ if (parentThemeRef.current) {
92
+ return;
93
+ }
94
+ // find parent theme from closest data-iui-theme attribute
95
+ const closestRoot = rootElement?.parentElement?.closest('[data-iui-theme]');
96
+ if (!closestRoot) {
97
+ return;
98
+ }
99
+ // helper function that updates state to match data attributes from closest root
100
+ const synchronizeTheme = () => {
101
+ setParentTheme(closestRoot?.getAttribute('data-iui-theme'));
102
+ setParentHighContrastState(closestRoot?.getAttribute('data-iui-contrast') === 'high');
103
+ };
104
+ // set theme for initial mount
105
+ synchronizeTheme();
106
+ // use mutation observers to listen to future updates to data attributes
107
+ const observer = new MutationObserver(() => synchronizeTheme());
108
+ observer.observe(closestRoot, {
109
+ attributes: true,
110
+ attributeFilter: ['data-iui-theme', 'data-iui-contrast'],
111
+ });
112
+ return () => {
113
+ observer.disconnect();
114
+ };
115
+ }, [rootElement, parentThemeRef]);
116
+ return {
117
+ theme: parentContext?.theme ?? parentThemeState,
118
+ highContrast: parentContext?.themeOptions?.highContrast ?? parentHighContrastState,
119
+ context: parentContext,
120
+ };
98
121
  };
@@ -249,9 +249,69 @@ export declare const Tile: PolymorphicForwardRefComponent<"div", TileLegacyProps
249
249
  */
250
250
  IconButton: PolymorphicForwardRefComponent<"button", Omit<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
251
251
  ref?: ((instance: HTMLButtonElement | null) => void) | React.RefObject<HTMLButtonElement> | null | undefined;
252
- }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "isActive" | "iconProps"> & {
252
+ }, "label" | "as" | "size" | "htmlDisabled" | "styleType" | "labelProps" | "isActive" | "iconProps"> & {
253
253
  isActive?: boolean | undefined;
254
254
  label?: React.ReactNode;
255
+ labelProps?: Omit<Omit<Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
256
+ ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject<HTMLDivElement> | null | undefined;
257
+ }, "as" | "children" | "content" | "portal" | keyof {
258
+ placement?: import("@floating-ui/utils").Placement | undefined;
259
+ visible?: boolean | undefined;
260
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
261
+ autoUpdateOptions?: {
262
+ ancestorScroll?: boolean | undefined;
263
+ ancestorResize?: boolean | undefined;
264
+ elementResize?: boolean | undefined;
265
+ animationFrame?: boolean | undefined;
266
+ layoutShift?: boolean | undefined;
267
+ } | undefined;
268
+ middleware?: {
269
+ offset?: number | undefined;
270
+ flip?: boolean | undefined;
271
+ shift?: boolean | undefined;
272
+ size?: boolean | undefined;
273
+ autoPlacement?: boolean | undefined;
274
+ hide?: boolean | undefined;
275
+ inline?: boolean | undefined;
276
+ } | undefined;
277
+ reference?: HTMLElement | null | undefined;
278
+ ariaStrategy?: "label" | "description" | "none" | undefined; /**
279
+ * Default tile variant or the folder layout.
280
+ * @default 'default'
281
+ */
282
+ id?: string | undefined;
283
+ }> & {
284
+ content: React.ReactNode;
285
+ children?: React.ReactNode;
286
+ } & import("../utils/index.js").PortalProps & {
287
+ placement?: import("@floating-ui/utils").Placement | undefined;
288
+ visible?: boolean | undefined;
289
+ onVisibleChange?: ((visible: boolean) => void) | undefined;
290
+ autoUpdateOptions?: {
291
+ ancestorScroll?: boolean | undefined;
292
+ ancestorResize?: boolean | undefined;
293
+ elementResize?: boolean | undefined;
294
+ animationFrame?: boolean | undefined;
295
+ layoutShift?: boolean | undefined;
296
+ } | undefined;
297
+ middleware?: {
298
+ offset?: number | undefined;
299
+ flip?: boolean | undefined;
300
+ shift?: boolean | undefined;
301
+ size?: boolean | undefined;
302
+ autoPlacement?: boolean | undefined;
303
+ hide?: boolean | undefined;
304
+ inline?: boolean | undefined;
305
+ } | undefined;
306
+ reference?: HTMLElement | null | undefined;
307
+ ariaStrategy?: "label" | "description" | "none" | undefined; /**
308
+ * Default tile variant or the folder layout.
309
+ * @default 'default'
310
+ */
311
+ id?: string | undefined;
312
+ } & {
313
+ as?: "div" | undefined;
314
+ }, "ref">, "children" | "content" | "reference" | "ariaStrategy"> | undefined;
255
315
  iconProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined;
256
316
  } & Omit<import("../Buttons/Button.js").ButtonProps, "startIcon" | "endIcon" | "labelProps" | "startIconProps" | "endIconProps"> & {
257
317
  as?: "button" | undefined;
@@ -57,8 +57,12 @@ const useTooltip = (options = {}) => {
57
57
  ...interactions.getReferenceProps(),
58
58
  }).forEach(([key, value]) => {
59
59
  if (typeof value === 'function') {
60
- reference.addEventListener(domEventName(key), value);
61
- cleanupValues[key] = value;
60
+ // manually add `.nativeEvent` (react concept) because floating-ui needs it
61
+ const patchedHandler = (event) => {
62
+ value({ ...event, nativeEvent: event });
63
+ };
64
+ reference.addEventListener(domEventName(key), patchedHandler);
65
+ cleanupValues[key] = patchedHandler;
62
66
  }
63
67
  else if (value) {
64
68
  cleanupValues[key] = reference.getAttribute(key);
@@ -33,6 +33,12 @@ export type NodeData<T> = {
33
33
  };
34
34
  export type NodeRenderProps<T> = Omit<NodeData<T>, 'subNodes'>;
35
35
  export type TreeProps<T> = {
36
+ /**
37
+ * Modify size of the tree.
38
+ *
39
+ * @default 'default'
40
+ */
41
+ size?: 'default' | 'small';
36
42
  /**
37
43
  * Render function that should return the node element.
38
44
  * Recommended to use `TreeNode` component.
@@ -57,7 +57,7 @@ import { TreeContext } from './TreeContext.js';
57
57
  />
58
58
  */
59
59
  export const Tree = (props) => {
60
- const { data, className, nodeRenderer, getNode, enableVirtualization = false, style, ...rest } = props;
60
+ const { data, className, nodeRenderer, getNode, size = 'default', enableVirtualization = false, style, ...rest } = props;
61
61
  const treeRef = React.useRef(null);
62
62
  const focusedIndex = React.useRef(0);
63
63
  React.useEffect(() => {
@@ -142,8 +142,9 @@ export const Tree = (props) => {
142
142
  setScrollToIndex(parentNodeIndex);
143
143
  }
144
144
  : undefined,
145
+ size,
145
146
  } }, nodeRenderer(node.nodeProps)));
146
- }, [firstLevelNodesList.length, flatNodesList, nodeRenderer]);
147
+ }, [firstLevelNodesList.length, flatNodesList, nodeRenderer, size]);
147
148
  const [scrollToIndex, setScrollToIndex] = React.useState();
148
149
  const flatNodesListRef = React.useRef(flatNodesList);
149
150
  React.useEffect(() => {
@@ -171,7 +172,7 @@ export const Tree = (props) => {
171
172
  items[focusedIndex.current]?.focus();
172
173
  }
173
174
  };
174
- return (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualizedTree, { flatNodesList: flatNodesList, itemRenderer: itemRenderer, scrollToIndex: scrollToIndex, onFocus: handleFocus, onKeyDown: handleKeyDown, ref: treeRef, className: className, style: style, ...rest })) : (React.createElement(TreeElement, { onKeyDown: handleKeyDown, onFocus: handleFocus, className: className, style: style, ref: treeRef, ...rest }, flatNodesList.map((_, i) => itemRenderer(i))))));
175
+ return (React.createElement(React.Fragment, null, enableVirtualization ? (React.createElement(VirtualizedTree, { flatNodesList: flatNodesList, itemRenderer: itemRenderer, scrollToIndex: scrollToIndex, onFocus: handleFocus, onKeyDown: handleKeyDown, ref: treeRef, className: className, style: style, ...rest })) : (React.createElement(TreeElement, { onKeyDown: handleKeyDown, onFocus: handleFocus, className: className, "data-iui-size": size === 'small' ? 'small' : undefined, style: style, ref: treeRef, ...rest }, flatNodesList.map((_, i) => itemRenderer(i))))));
175
176
  };
176
177
  const TreeElement = polymorphic.ul('iui-tree', {
177
178
  role: 'tree',
@@ -24,6 +24,10 @@ export type TreeContextProps = {
24
24
  * Function that scrolls to the node's parent node.
25
25
  */
26
26
  scrollToParent?: () => void;
27
+ /**
28
+ * Size of the tree.
29
+ */
30
+ size?: 'default' | 'small';
27
31
  };
28
32
  export declare const TreeContext: React.Context<TreeContextProps | undefined>;
29
33
  export declare const useTreeContext: () => TreeContextProps;
@@ -4,12 +4,15 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { SvgChevronRight } from '../utils/index.js';
7
+ import { SvgChevronRight, SvgChevronRightSmall } from '../utils/index.js';
8
8
  import { IconButton } from '../Buttons/IconButton.js';
9
+ import { TreeContext } from './TreeContext.js';
9
10
  export const TreeNodeExpander = React.forwardRef((props, ref) => {
10
11
  const { isExpanded, ...rest } = props;
12
+ const size = React.useContext(TreeContext)?.size ?? 'default';
13
+ const ChevronIcon = size === 'small' ? SvgChevronRightSmall : SvgChevronRight;
11
14
  return (React.createElement(IconButton, { styleType: 'borderless', size: 'small', "aria-label": isExpanded ? 'Collapse' : 'Expand', ref: ref, ...rest },
12
- React.createElement(SvgChevronRight, { className: cx('iui-tree-node-content-expander-icon', {
15
+ React.createElement(ChevronIcon, { className: cx('iui-tree-node-content-expander-icon', {
13
16
  'iui-tree-node-content-expander-icon-expanded': isExpanded,
14
17
  }) })));
15
18
  });
@@ -0,0 +1,2 @@
1
+ import * as React from 'react';
2
+ export declare const SvgChevronRightSmall: (props: React.ComponentPropsWithoutRef<'svg'>) => React.JSX.Element;
@@ -0,0 +1,10 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import * as React from 'react';
6
+ import { Svg } from './Svg.js';
7
+ export const SvgChevronRightSmall = (props) => {
8
+ return (React.createElement(Svg, { ...props },
9
+ React.createElement("path", { d: 'm5.525 2-1.05 1.05L9.425 8l-4.95 4.95L5.525 14l6-6-6-6Z' })));
10
+ };
@@ -7,6 +7,7 @@ export * from './SvgClose.js';
7
7
  export * from './SvgCloseSmall.js';
8
8
  export * from './SvgChevronLeft.js';
9
9
  export * from './SvgChevronRight.js';
10
+ export * from './SvgChevronRightSmall.js';
10
11
  export * from './SvgChevronLeftDouble.js';
11
12
  export * from './SvgChevronRightDouble.js';
12
13
  export * from './SvgCaretUpSmall.js';
@@ -11,6 +11,7 @@ export * from './SvgClose.js';
11
11
  export * from './SvgCloseSmall.js';
12
12
  export * from './SvgChevronLeft.js';
13
13
  export * from './SvgChevronRight.js';
14
+ export * from './SvgChevronRightSmall.js';
14
15
  export * from './SvgChevronLeftDouble.js';
15
16
  export * from './SvgChevronRightDouble.js';
16
17
  export * from './SvgCaretUpSmall.js';
package/esm/styles.js CHANGED
@@ -207,9 +207,9 @@ const styles = {
207
207
  "iui-overlay-exiting": "_iui3-overlay-exiting",
208
208
  closeAnimation,
209
209
  "iui-progress-indicator-radial": "_iui3-progress-indicator-radial",
210
- "iui-uu9on85": "_iui3-uu9on85",
210
+ "iui-uj1wh1i": "_iui3-uj1wh1i",
211
211
  "iui-progress-indicator-linear-label": "_iui3-progress-indicator-linear-label",
212
- "iui-uu9on8i": "_iui3-uu9on8i",
212
+ "iui-uj1wh1t": "_iui3-uj1wh1t",
213
213
  "iui-radio": "_iui3-radio",
214
214
  "iui-radio-tile": "_iui3-radio-tile",
215
215
  "iui-radio-tile-icon": "_iui3-radio-tile-icon",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/itwinui-react",
3
- "version": "3.0.11",
3
+ "version": "3.1.0",
4
4
  "author": "Bentley Systems",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -76,14 +76,14 @@
76
76
  "dev:styles": "yarn build:styles --watch"
77
77
  },
78
78
  "dependencies": {
79
- "@floating-ui/react": "^0.25.4",
79
+ "@floating-ui/react": "^0.26.3",
80
80
  "classnames": "^2.3.2",
81
81
  "react-table": "^7.8.0",
82
82
  "react-transition-group": "^4.4.5",
83
83
  "tslib": "^2.6.0"
84
84
  },
85
85
  "devDependencies": {
86
- "@itwin/itwinui-css": "^2.0.4",
86
+ "@itwin/itwinui-css": "^2.1.0",
87
87
  "@itwin/itwinui-illustrations-react": "^2.1.0",
88
88
  "@itwin/itwinui-variables": "3.0.0",
89
89
  "@swc/cli": "^0.1.62",
@@ -111,7 +111,7 @@
111
111
  "ts-jest": "^29.0.0",
112
112
  "ts-node": "^10.0.0",
113
113
  "typescript": "~5.1.6",
114
- "vite": "^4.3.8"
114
+ "vite": "^4.5.1"
115
115
  },
116
116
  "peerDependencies": {
117
117
  "@itwin/itwinui-illustrations-react": "^2.1.0",