@itwin/itwinui-react 2.9.1 → 2.10.1

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 (46) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/cjs/core/Buttons/IconButton/IconButton.d.ts +11 -4
  3. package/cjs/core/Buttons/IconButton/IconButton.js +13 -5
  4. package/cjs/core/Carousel/Carousel.d.ts +2 -0
  5. package/cjs/core/Carousel/CarouselNavigation.d.ts +2 -0
  6. package/cjs/core/DatePicker/DatePicker.js +2 -13
  7. package/cjs/core/FileUpload/FileUploadCard.js +15 -4
  8. package/cjs/core/LabeledInput/LabeledInput.js +4 -3
  9. package/cjs/core/LabeledSelect/LabeledSelect.js +7 -3
  10. package/cjs/core/LabeledTextarea/LabeledTextarea.js +4 -3
  11. package/cjs/core/Select/Select.d.ts +4 -0
  12. package/cjs/core/Select/Select.js +27 -29
  13. package/cjs/core/Table/filters/DateRangeFilter/DatePickerInput.d.ts +8 -0
  14. package/cjs/core/Table/filters/DateRangeFilter/DatePickerInput.js +10 -2
  15. package/cjs/core/Table/filters/DateRangeFilter/DateRangeFilter.js +2 -2
  16. package/cjs/core/utils/components/InputContainer.d.ts +2 -0
  17. package/cjs/core/utils/components/InputContainer.js +4 -3
  18. package/cjs/core/utils/functions/date.d.ts +4 -0
  19. package/cjs/core/utils/functions/date.js +21 -0
  20. package/cjs/core/utils/functions/index.d.ts +1 -0
  21. package/cjs/core/utils/functions/index.js +1 -0
  22. package/cjs/core/utils/hooks/useId.d.ts +3 -3
  23. package/cjs/core/utils/hooks/useId.js +8 -5
  24. package/esm/core/Buttons/IconButton/IconButton.d.ts +11 -4
  25. package/esm/core/Buttons/IconButton/IconButton.js +14 -6
  26. package/esm/core/Carousel/Carousel.d.ts +2 -0
  27. package/esm/core/Carousel/CarouselNavigation.d.ts +2 -0
  28. package/esm/core/DatePicker/DatePicker.js +1 -12
  29. package/esm/core/FileUpload/FileUploadCard.js +15 -4
  30. package/esm/core/LabeledInput/LabeledInput.js +5 -4
  31. package/esm/core/LabeledSelect/LabeledSelect.js +8 -4
  32. package/esm/core/LabeledTextarea/LabeledTextarea.js +5 -4
  33. package/esm/core/Select/Select.d.ts +4 -0
  34. package/esm/core/Select/Select.js +28 -30
  35. package/esm/core/Table/filters/DateRangeFilter/DatePickerInput.d.ts +8 -0
  36. package/esm/core/Table/filters/DateRangeFilter/DatePickerInput.js +11 -3
  37. package/esm/core/Table/filters/DateRangeFilter/DateRangeFilter.js +2 -2
  38. package/esm/core/utils/components/InputContainer.d.ts +2 -0
  39. package/esm/core/utils/components/InputContainer.js +4 -3
  40. package/esm/core/utils/functions/date.d.ts +4 -0
  41. package/esm/core/utils/functions/date.js +17 -0
  42. package/esm/core/utils/functions/index.d.ts +1 -0
  43. package/esm/core/utils/functions/index.js +1 -0
  44. package/esm/core/utils/hooks/useId.d.ts +3 -3
  45. package/esm/core/utils/hooks/useId.js +7 -5
  46. package/package.json +7 -2
@@ -1,19 +1,26 @@
1
- import { ButtonProps } from '../Button';
2
- import { PolymorphicForwardRefComponent } from '../../utils';
1
+ import React from 'react';
2
+ import type { ButtonProps } from '../Button';
3
+ import type { PolymorphicForwardRefComponent } from '../../utils';
3
4
  import '@itwin/itwinui-css/css/button.css';
5
+ import '@itwin/itwinui-css/css/tooltip.css';
4
6
  export declare type IconButtonProps = {
5
7
  /**
6
8
  * Button gets active style.
7
9
  * @default false
8
10
  */
9
11
  isActive?: boolean;
12
+ /**
13
+ * Name of the button, shown in a tooltip and exposed to assistive technologies.
14
+ */
15
+ label?: React.ReactNode;
10
16
  } & Omit<ButtonProps, 'startIcon' | 'endIcon'>;
11
17
  declare type IconButtonComponent = PolymorphicForwardRefComponent<'button', IconButtonProps>;
12
18
  /**
13
19
  * Icon button
14
20
  * @example
15
- * <IconButton><SvgAdd /></IconButton>
16
- * <IconButton styleType='borderless'><SvgAdd /></IconButton>
21
+ * <IconButton label='Add'><SvgAdd /></IconButton>
22
+ * @example
23
+ * <IconButton label='Add' styleType='borderless'><SvgAdd /></IconButton>
17
24
  */
18
25
  export declare const IconButton: IconButtonComponent;
19
26
  export default IconButton;
@@ -4,18 +4,26 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import cx from 'classnames';
6
6
  import React from 'react';
7
- import { useTheme } from '../../utils';
7
+ import { useTheme, VisuallyHidden, Popover } from '../../utils';
8
8
  import '@itwin/itwinui-css/css/button.css';
9
+ import '@itwin/itwinui-css/css/tooltip.css';
9
10
  /**
10
11
  * Icon button
11
12
  * @example
12
- * <IconButton><SvgAdd /></IconButton>
13
- * <IconButton styleType='borderless'><SvgAdd /></IconButton>
13
+ * <IconButton label='Add'><SvgAdd /></IconButton>
14
+ * @example
15
+ * <IconButton label='Add' styleType='borderless'><SvgAdd /></IconButton>
14
16
  */
15
17
  export const IconButton = React.forwardRef((props, ref) => {
16
- const { isActive, children, styleType = 'default', size, type = 'button', className, as: Element = 'button', ...rest } = props;
18
+ const { isActive, children, styleType = 'default', size, type = 'button', className, as: Element = 'button', label, ...rest } = props;
17
19
  useTheme();
18
- return (React.createElement(Element, { ref: ref, className: cx('iui-button', className), "data-iui-variant": styleType !== 'default' ? styleType : undefined, "data-iui-size": size, "data-iui-active": isActive, type: type, ...rest },
19
- React.createElement("span", { className: 'iui-button-icon', "aria-hidden": true }, children)));
20
+ return (React.createElement(IconButtonTooltip, { label: label },
21
+ React.createElement(Element, { ref: ref, className: cx('iui-button', className), "data-iui-variant": styleType !== 'default' ? styleType : undefined, "data-iui-size": size, "data-iui-active": isActive, "aria-pressed": isActive, type: type, ...rest },
22
+ React.createElement("span", { className: 'iui-button-icon', "aria-hidden": true }, children),
23
+ label ? React.createElement(VisuallyHidden, null, label) : null)));
20
24
  });
25
+ const IconButtonTooltip = (props) => {
26
+ const { label, children } = props;
27
+ return label ? (React.createElement(Popover, { interactive: false, offset: [0, 4], aria: { content: null }, content: React.createElement("div", { "aria-hidden": 'true', className: 'iui-tooltip' }, label) }, children)) : (children);
28
+ };
21
29
  export default IconButton;
@@ -49,9 +49,11 @@ export declare const Carousel: React.ForwardRefExoticComponent<{
49
49
  Navigation: React.ForwardRefExoticComponent<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "key" | keyof React.HTMLAttributes<HTMLElement>> & React.RefAttributes<HTMLElement>> & {
50
50
  PreviousButton: React.ForwardRefExoticComponent<{
51
51
  isActive?: boolean | undefined;
52
+ label?: React.ReactNode;
52
53
  } & Omit<import("..").ButtonProps<"button">, "startIcon" | "endIcon"> & React.RefAttributes<HTMLButtonElement>>;
53
54
  NextButton: React.ForwardRefExoticComponent<{
54
55
  isActive?: boolean | undefined;
56
+ label?: React.ReactNode;
55
57
  } & Omit<import("..").ButtonProps<"button">, "startIcon" | "endIcon"> & React.RefAttributes<HTMLButtonElement>>;
56
58
  };
57
59
  DotsList: React.ForwardRefExoticComponent<{
@@ -8,8 +8,10 @@ import React from 'react';
8
8
  export declare const CarouselNavigation: React.ForwardRefExoticComponent<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>, "key" | keyof React.HTMLAttributes<HTMLElement>> & React.RefAttributes<HTMLElement>> & {
9
9
  PreviousButton: React.ForwardRefExoticComponent<{
10
10
  isActive?: boolean | undefined;
11
+ label?: React.ReactNode;
11
12
  } & Omit<import("../Buttons").ButtonProps<"button">, "startIcon" | "endIcon"> & React.RefAttributes<HTMLButtonElement>>;
12
13
  NextButton: React.ForwardRefExoticComponent<{
13
14
  isActive?: boolean | undefined;
15
+ label?: React.ReactNode;
14
16
  } & Omit<import("../Buttons").ButtonProps<"button">, "startIcon" | "endIcon"> & React.RefAttributes<HTMLButtonElement>>;
15
17
  };
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import cx from 'classnames';
6
6
  import React from 'react';
7
- import { useTheme, SvgChevronLeft, SvgChevronRight, SvgChevronLeftDouble, SvgChevronRightDouble, } from '../utils';
7
+ import { useTheme, SvgChevronLeft, SvgChevronRight, SvgChevronLeftDouble, SvgChevronRightDouble, isBefore, } from '../utils';
8
8
  import '@itwin/itwinui-css/css/date-picker.css';
9
9
  import { IconButton } from '../Buttons/IconButton';
10
10
  import { TimePicker } from '../TimePicker';
@@ -27,17 +27,6 @@ const isInDateRange = (date, startDate, endDate) => {
27
27
  maxDate && maxDate.setHours(0, 0, 0, 0);
28
28
  return testDate > minDate && testDate < maxDate;
29
29
  };
30
- // compares to see if one date is earlier than another
31
- const isBefore = (beforeDate, afterDate) => {
32
- if (!beforeDate || !afterDate) {
33
- return false;
34
- }
35
- const firstDate = new Date(beforeDate);
36
- const secondDate = new Date(afterDate);
37
- firstDate && firstDate.setHours(0, 0, 0, 0);
38
- secondDate && secondDate.setHours(0, 0, 0, 0);
39
- return firstDate < secondDate;
40
- };
41
30
  // Type guard for multiple did not work
42
31
  const isSingleOnChange = (onChange, enableRangeSelect) => {
43
32
  return !enableRangeSelect;
@@ -23,16 +23,19 @@ const FileUploadCardIcon = React.forwardRef((props, ref) => {
23
23
  const { children, className, ...rest } = props;
24
24
  return (React.createElement("span", { className: cx('iui-file-card-icon', className), ref: ref, ...rest }, children !== null && children !== void 0 ? children : React.createElement(SvgDocument, null)));
25
25
  });
26
+ FileUploadCardIcon.displayName = 'FileUploadCard.Icon';
26
27
  const FileUploadCardInfo = React.forwardRef((props, ref) => {
27
28
  const { children, className, ...rest } = props;
28
29
  return (React.createElement("span", { className: cx('iui-file-card-text', className), ref: ref, ...rest }, children));
29
30
  });
31
+ FileUploadCardInfo.displayName = 'FileUploadCard.Info';
30
32
  const FileUploadCardTitle = React.forwardRef((props, ref) => {
31
33
  const { children, className, ...rest } = props;
32
34
  const { files } = useSafeContext(FileUploadCardContext);
33
35
  const title = files.length > 1 ? files.length + ' files selected' : files[0].name;
34
36
  return (React.createElement("span", { className: cx('iui-file-card-title', className), ref: ref, ...rest }, children !== null && children !== void 0 ? children : title));
35
37
  });
38
+ FileUploadCardTitle.displayName = 'FileUploadCard.Title';
36
39
  const FileUploadCardDescription = React.forwardRef((props, ref) => {
37
40
  const { children, className, ...rest } = props;
38
41
  const { files } = useSafeContext(FileUploadCardContext);
@@ -44,15 +47,18 @@ const FileUploadCardDescription = React.forwardRef((props, ref) => {
44
47
  }
45
48
  return (React.createElement("span", { className: cx('iui-file-card-description', className), ref: ref, ...rest }, children !== null && children !== void 0 ? children : description));
46
49
  });
50
+ FileUploadCardDescription.displayName = 'FileUploadCard.Description';
47
51
  const FileUploadCardAction = React.forwardRef((props, ref) => {
48
52
  const { children, className, ...rest } = props;
49
53
  return (React.createElement("div", { className: cx('iui-file-card-action', className), ref: ref, ...rest }, children));
50
54
  });
55
+ FileUploadCardAction.displayName = 'FileUploadCard.Action';
51
56
  const FileUploadCardInputLabel = React.forwardRef((props, ref) => {
52
57
  const { children, className, ...rest } = props;
53
58
  const { inputId } = useSafeContext(FileUploadCardContext);
54
59
  return (React.createElement("label", { className: cx('iui-anchor', className), ref: ref, htmlFor: inputId, ...rest }, children));
55
60
  });
61
+ FileUploadCardInputLabel.displayName = 'FileUploadCard.InputLabel';
56
62
  const FileUploadCardInput = React.forwardRef((props, ref) => {
57
63
  const { children, className, onChange, id, ...rest } = props;
58
64
  const { files, onFilesChange, setInternalFiles, inputId, setInputId } = useSafeContext(FileUploadCardContext);
@@ -67,9 +73,11 @@ const FileUploadCardInput = React.forwardRef((props, ref) => {
67
73
  node.files = dataTransfer.files;
68
74
  }, [files]);
69
75
  const refs = useMergedRefs(ref, setNativeFilesRef);
70
- if (id && id !== inputId) {
71
- setInputId(id);
72
- }
76
+ React.useEffect(() => {
77
+ if (id && id !== inputId) {
78
+ setInputId(id);
79
+ }
80
+ }, [id, inputId, setInputId]);
73
81
  return (React.createElement(React.Fragment, null,
74
82
  React.createElement("input", { className: cx('iui-visually-hidden', className), type: 'file', onChange: (e) => {
75
83
  onChange === null || onChange === void 0 ? void 0 : onChange(e);
@@ -81,6 +89,7 @@ const FileUploadCardInput = React.forwardRef((props, ref) => {
81
89
  }, ref: refs, id: id !== null && id !== void 0 ? id : inputId, ...rest }),
82
90
  children));
83
91
  });
92
+ FileUploadCardInput.displayName = 'FileUploadCard.Input';
84
93
  /**
85
94
  * Default card to be used with the `FileUpload` wrapper component for single-file uploading.
86
95
  * @example
@@ -109,7 +118,8 @@ export const FileUploadCard = Object.assign(React.forwardRef((props, ref) => {
109
118
  var _a;
110
119
  const { className, children, files: filesProp, onFilesChange, emptyCard = React.createElement(FileEmptyCard, null), input, ...rest } = props;
111
120
  const [internalFiles, setInternalFiles] = React.useState();
112
- const [inputId, setInputId] = React.useState(useId());
121
+ const uid = useId();
122
+ const [inputId, setInputId] = React.useState(uid);
113
123
  const files = (_a = filesProp !== null && filesProp !== void 0 ? filesProp : internalFiles) !== null && _a !== void 0 ? _a : [];
114
124
  return (React.createElement(FileUploadCardContext.Provider, { value: {
115
125
  files,
@@ -134,5 +144,6 @@ export const FileUploadCard = Object.assign(React.forwardRef((props, ref) => {
134
144
  InputLabel: FileUploadCardInputLabel,
135
145
  Input: FileUploadCardInput,
136
146
  });
147
+ FileUploadCard.displayName = 'FileUploadCard';
137
148
  export default FileUploadCard;
138
149
  export const FileUploadCardContext = React.createContext(undefined);
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import React from 'react';
6
6
  import { Input } from '../Input/Input';
7
- import { StatusIconMap, useTheme, InputContainer } from '../utils';
7
+ import { StatusIconMap, useTheme, InputContainer, useId } from '../utils';
8
8
  import '@itwin/itwinui-css/css/input.css';
9
9
  /**
10
10
  * Basic labeled input component
@@ -15,10 +15,11 @@ import '@itwin/itwinui-css/css/input.css';
15
15
  * <LabeledInput status='negative' label='Negative' setFocus />
16
16
  */
17
17
  export const LabeledInput = React.forwardRef((props, ref) => {
18
- const { className, disabled = false, label, message, status, svgIcon, style, inputClassName, inputStyle, displayStyle = 'default', iconDisplayStyle = displayStyle === 'default' ? 'block' : 'inline', required = false, ...rest } = props;
18
+ const uid = useId();
19
+ const { className, disabled = false, label, message, status, svgIcon, style, inputClassName, inputStyle, displayStyle = 'default', iconDisplayStyle = displayStyle === 'default' ? 'block' : 'inline', required = false, id = uid, ...rest } = props;
19
20
  useTheme();
20
21
  const icon = svgIcon !== null && svgIcon !== void 0 ? svgIcon : (status && StatusIconMap[status]());
21
- return (React.createElement(InputContainer, { as: 'label', label: label, disabled: disabled, required: required, status: status, message: message, icon: icon, isLabelInline: displayStyle === 'inline', isIconInline: iconDisplayStyle === 'inline', className: className, style: style },
22
- React.createElement(Input, { disabled: disabled, className: inputClassName, style: inputStyle, required: required, ref: ref, ...rest })));
22
+ return (React.createElement(InputContainer, { label: label, disabled: disabled, required: required, status: status, message: message, icon: icon, isLabelInline: displayStyle === 'inline', isIconInline: iconDisplayStyle === 'inline', className: className, style: style, inputId: id },
23
+ React.createElement(Input, { disabled: disabled, className: inputClassName, style: inputStyle, required: required, ref: ref, id: id, ...rest })));
23
24
  });
24
25
  export default LabeledInput;
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import React from 'react';
6
6
  import { Select } from '../Select';
7
- import { StatusIconMap, useTheme, InputContainer } from '../utils';
7
+ import { StatusIconMap, useTheme, InputContainer, useId } from '../utils';
8
8
  import '@itwin/itwinui-css/css/input.css';
9
9
  /**
10
10
  * Labeled select component to select value from options.
@@ -40,8 +40,9 @@ import '@itwin/itwinui-css/css/input.css';
40
40
  * />
41
41
  */
42
42
  export const LabeledSelect = (props) => {
43
- const { className, disabled = false, label, message, status, svgIcon, displayStyle = 'default', style, selectClassName, selectStyle, required = false, ...rest } = props;
43
+ const { className, disabled = false, label, message, status, svgIcon, displayStyle = 'default', style, selectClassName, selectStyle, required = false, triggerProps, ...rest } = props;
44
44
  useTheme();
45
+ const labelId = `${useId()}-label`;
45
46
  const icon = () => {
46
47
  if (svgIcon) {
47
48
  return React.cloneElement(svgIcon, { 'aria-hidden': true });
@@ -51,7 +52,10 @@ export const LabeledSelect = (props) => {
51
52
  }
52
53
  return undefined;
53
54
  };
54
- return (React.createElement(InputContainer, { label: label, disabled: disabled, required: required, status: status, message: message, icon: displayStyle === 'default' ? icon() : undefined, isLabelInline: displayStyle === 'inline', className: className, style: style },
55
- React.createElement(Select, { disabled: disabled, className: selectClassName, style: selectStyle, ...rest })));
55
+ return (React.createElement(InputContainer, { label: label, disabled: disabled, required: required, status: status, message: message, icon: displayStyle === 'default' ? icon() : undefined, isLabelInline: displayStyle === 'inline', className: className, style: style, labelId: labelId },
56
+ React.createElement(Select, { disabled: disabled, className: selectClassName, style: selectStyle, ...rest, triggerProps: {
57
+ 'aria-labelledby': labelId,
58
+ ...triggerProps,
59
+ } })));
56
60
  };
57
61
  export default LabeledSelect;
@@ -3,7 +3,7 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import React from 'react';
6
- import { StatusIconMap, useTheme, InputContainer } from '../utils';
6
+ import { StatusIconMap, useTheme, InputContainer, useId } from '../utils';
7
7
  import { Textarea } from '../Textarea';
8
8
  import '@itwin/itwinui-css/css/input.css';
9
9
  /**
@@ -28,10 +28,11 @@ import '@itwin/itwinui-css/css/input.css';
28
28
  * />
29
29
  */
30
30
  export const LabeledTextarea = React.forwardRef((props, ref) => {
31
- const { className, style, disabled = false, label, message, status, textareaClassName, textareaStyle, displayStyle = 'default', iconDisplayStyle = displayStyle === 'default' ? 'block' : 'inline', svgIcon, required = false, ...textareaProps } = props;
31
+ const uid = useId();
32
+ const { className, style, disabled = false, label, message, status, textareaClassName, textareaStyle, displayStyle = 'default', iconDisplayStyle = displayStyle === 'default' ? 'block' : 'inline', svgIcon, required = false, id = uid, ...textareaProps } = props;
32
33
  useTheme();
33
34
  const icon = svgIcon !== null && svgIcon !== void 0 ? svgIcon : (status && StatusIconMap[status]());
34
- return (React.createElement(InputContainer, { as: 'label', label: label, disabled: disabled, required: required, status: status, message: message, icon: icon, isLabelInline: displayStyle === 'inline', isIconInline: iconDisplayStyle === 'inline', className: className, style: style },
35
- React.createElement(Textarea, { disabled: disabled, className: textareaClassName, style: textareaStyle, required: required, ...textareaProps, ref: ref })));
35
+ return (React.createElement(InputContainer, { label: label, disabled: disabled, required: required, status: status, message: message, icon: icon, isLabelInline: displayStyle === 'inline', isIconInline: iconDisplayStyle === 'inline', className: className, style: style, inputId: id },
36
+ React.createElement(Textarea, { disabled: disabled, className: textareaClassName, style: textareaStyle, required: required, id: id, ...textareaProps, ref: ref })));
36
37
  });
37
38
  export default LabeledTextarea;
@@ -111,6 +111,10 @@ export declare type SelectProps<T> = {
111
111
  * @see [tippy.js props](https://atomiks.github.io/tippyjs/v6/all-props/)
112
112
  */
113
113
  popoverProps?: Omit<PopoverProps, 'onShow' | 'onHide' | 'disabled'>;
114
+ /**
115
+ * Props to pass to the select button (trigger) element.
116
+ */
117
+ triggerProps?: React.ComponentPropsWithoutRef<'div'>;
114
118
  } & SelectMultipleTypeProps<T> & Pick<PopoverProps, 'onShow' | 'onHide'> & Omit<React.ComponentPropsWithoutRef<'div'>, 'size' | 'disabled' | 'placeholder' | 'onChange'>;
115
119
  /**
116
120
  * Select component to select value from options.
@@ -4,9 +4,8 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import React from 'react';
6
6
  import cx from 'classnames';
7
- import { DropdownMenu } from '../DropdownMenu';
8
- import { MenuItem } from '../Menu/MenuItem';
9
- import { useTheme, SvgCaretDownSmall, } from '../utils';
7
+ import { Menu, MenuItem } from '../Menu';
8
+ import { useTheme, SvgCaretDownSmall, Popover, useId, } from '../utils';
10
9
  import '@itwin/itwinui-css/css/select.css';
11
10
  import SelectTag from './SelectTag';
12
11
  import SelectTagContainer from './SelectTagContainer';
@@ -68,14 +67,12 @@ const isSingleOnChange = (onChange, multiple) => {
68
67
  */
69
68
  export const Select = (props) => {
70
69
  var _a;
71
- const { options, value, onChange, placeholder, disabled = false, size, setFocus = false, itemRenderer, selectedItemRenderer, className, style, menuClassName, menuStyle, onShow, onHide, popoverProps, multiple = false, ...rest } = props;
70
+ const uid = useId();
71
+ const { options, value, onChange, placeholder, disabled = false, size, setFocus = false, itemRenderer, selectedItemRenderer, className, style, menuClassName, menuStyle, onShow, onHide, popoverProps, multiple = false, triggerProps, ...rest } = props;
72
72
  useTheme();
73
- const [isOpen, setIsOpen] = React.useState((_a = popoverProps === null || popoverProps === void 0 ? void 0 : popoverProps.visible) !== null && _a !== void 0 ? _a : false);
74
- React.useEffect(() => {
75
- setIsOpen((open) => { var _a; return (_a = popoverProps === null || popoverProps === void 0 ? void 0 : popoverProps.visible) !== null && _a !== void 0 ? _a : open; });
76
- }, [popoverProps]);
73
+ const [isOpenState, setIsOpen] = React.useState(false);
74
+ const isOpen = (_a = popoverProps === null || popoverProps === void 0 ? void 0 : popoverProps.visible) !== null && _a !== void 0 ? _a : isOpenState;
77
75
  const [minWidth, setMinWidth] = React.useState(0);
78
- const toggle = () => setIsOpen((open) => !open);
79
76
  const selectRef = React.useRef(null);
80
77
  const toggleButtonRef = React.useRef(null);
81
78
  const onShowHandler = React.useCallback((instance) => {
@@ -83,7 +80,9 @@ export const Select = (props) => {
83
80
  onShow === null || onShow === void 0 ? void 0 : onShow(instance);
84
81
  }, [onShow]);
85
82
  const onHideHandler = React.useCallback((instance) => {
83
+ var _a;
86
84
  setIsOpen(false);
85
+ (_a = selectRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true }); // move focus back to select button
87
86
  onHide === null || onHide === void 0 ? void 0 : onHide(instance);
88
87
  }, [onHide]);
89
88
  React.useEffect(() => {
@@ -96,30 +95,29 @@ export const Select = (props) => {
96
95
  setMinWidth(selectRef.current.offsetWidth);
97
96
  }
98
97
  }, [isOpen]);
99
- const onKeyDown = (event, toggle) => {
98
+ const onKeyDown = (event) => {
100
99
  if (event.altKey) {
101
100
  return;
102
101
  }
103
102
  switch (event.key) {
104
103
  case 'Enter':
105
104
  case ' ':
106
- case 'Spacebar':
107
- if (event.target === selectRef.current) {
108
- toggle();
109
- event.preventDefault();
110
- }
105
+ case 'Spacebar': {
106
+ setIsOpen((o) => !o);
107
+ event.preventDefault();
111
108
  break;
109
+ }
112
110
  default:
113
111
  break;
114
112
  }
115
113
  };
116
- const menuItems = React.useCallback((close) => {
114
+ const menuItems = React.useMemo(() => {
117
115
  return options.map((option, index) => {
118
116
  var _a;
119
117
  const isSelected = isMultipleEnabled(value, multiple)
120
118
  ? (_a = value === null || value === void 0 ? void 0 : value.includes(option.value)) !== null && _a !== void 0 ? _a : false
121
119
  : value === option.value;
122
- const menuItem = itemRenderer ? (itemRenderer(option, { close, isSelected })) : (React.createElement(MenuItem, null, option.label));
120
+ const menuItem = itemRenderer ? (itemRenderer(option, { close: () => setIsOpen(false), isSelected })) : (React.createElement(MenuItem, null, option.label));
123
121
  const { label, ...restOption } = option;
124
122
  return React.cloneElement(menuItem, {
125
123
  key: `${label}-${index}`,
@@ -130,7 +128,7 @@ export const Select = (props) => {
130
128
  }
131
129
  if (isSingleOnChange(onChange, multiple)) {
132
130
  onChange === null || onChange === void 0 ? void 0 : onChange(option.value);
133
- close();
131
+ setIsOpen(false);
134
132
  }
135
133
  else {
136
134
  onChange === null || onChange === void 0 ? void 0 : onChange(option.value, isSelected ? 'removed' : 'added');
@@ -138,7 +136,7 @@ export const Select = (props) => {
138
136
  },
139
137
  ref: (el) => {
140
138
  if (isSelected && !multiple) {
141
- el === null || el === void 0 ? void 0 : el.scrollIntoView();
139
+ el === null || el === void 0 ? void 0 : el.scrollIntoView({ block: 'nearest' });
142
140
  }
143
141
  },
144
142
  role: 'option',
@@ -158,29 +156,29 @@ export const Select = (props) => {
158
156
  const tagRenderer = React.useCallback((item) => {
159
157
  return React.createElement(SelectTag, { key: item.label, label: item.label });
160
158
  }, []);
161
- return (React.createElement("div", { className: cx('iui-input-with-icon', className), "aria-expanded": isOpen, "aria-haspopup": 'listbox', style: style, ...rest },
162
- React.createElement(DropdownMenu, { menuItems: menuItems, placement: 'bottom-start', className: cx('iui-scroll', menuClassName), style: {
163
- minWidth,
164
- maxWidth: `min(${minWidth * 2}px, 90vw)`,
165
- ...menuStyle,
166
- }, role: 'listbox', onShow: onShowHandler, onHide: onHideHandler, disabled: disabled, ...popoverProps, visible: isOpen, onClickOutside: (_, { target }) => {
159
+ return (React.createElement("div", { className: cx('iui-input-with-icon', className), style: style, ...rest },
160
+ React.createElement(Popover, { content: React.createElement(Menu, { role: 'listbox', className: cx('iui-scroll', menuClassName), style: {
161
+ minWidth,
162
+ maxWidth: `min(${minWidth * 2}px, 90vw)`,
163
+ ...menuStyle,
164
+ }, id: `${uid}-menu`, key: `${uid}-menu` }, menuItems), placement: 'bottom-start', aria: { content: null }, onShow: onShowHandler, onHide: onHideHandler, ...popoverProps, visible: isOpen, onClickOutside: (_, { target }) => {
167
165
  var _a;
168
166
  if (!((_a = toggleButtonRef.current) === null || _a === void 0 ? void 0 : _a.contains(target))) {
169
167
  setIsOpen(false);
170
168
  }
171
169
  } },
172
- React.createElement("div", { ref: selectRef, className: cx('iui-select-button', {
170
+ React.createElement("div", { tabIndex: 0, role: 'combobox', ref: selectRef, "data-iui-size": size, onClick: () => !disabled && setIsOpen((o) => !o), onKeyDown: (e) => !disabled && onKeyDown(e), "aria-disabled": disabled, "aria-autocomplete": 'none', "aria-expanded": isOpen, "aria-haspopup": 'listbox', "aria-controls": `${uid}-menu`, ...triggerProps, className: cx('iui-select-button', {
173
171
  'iui-placeholder': (!selectedItems || selectedItems.length === 0) && !!placeholder,
174
172
  'iui-disabled': disabled,
175
- }), "data-iui-size": size, onClick: () => !disabled && toggle(), onKeyDown: (e) => !disabled && onKeyDown(e, toggle), tabIndex: !disabled ? 0 : undefined },
173
+ }, triggerProps === null || triggerProps === void 0 ? void 0 : triggerProps.className) },
176
174
  (!selectedItems || selectedItems.length === 0) && (React.createElement("span", { className: 'iui-content' }, placeholder)),
177
175
  isMultipleEnabled(selectedItems, multiple) ? (React.createElement(MultipleSelectButton, { selectedItems: selectedItems, selectedItemsRenderer: selectedItemRenderer, tagRenderer: tagRenderer })) : (React.createElement(SingleSelectButton, { selectedItem: selectedItems, selectedItemRenderer: selectedItemRenderer })))),
178
- React.createElement("span", { ref: toggleButtonRef, className: cx('iui-end-icon', {
176
+ React.createElement("span", { "aria-hidden": true, ref: toggleButtonRef, className: cx('iui-end-icon', {
179
177
  'iui-actionable': !disabled,
180
178
  'iui-disabled': disabled,
181
179
  'iui-open': isOpen,
182
- }), onClick: () => !disabled && toggle() },
183
- React.createElement(SvgCaretDownSmall, { "aria-hidden": true }))));
180
+ }), onClick: () => !disabled && setIsOpen((o) => !o) },
181
+ React.createElement(SvgCaretDownSmall, null))));
184
182
  };
185
183
  const SingleSelectButton = ({ selectedItem, selectedItemRenderer, }) => {
186
184
  return (React.createElement(React.Fragment, null,
@@ -5,6 +5,14 @@ export declare type DatePickerInputProps = {
5
5
  onChange: (date?: Date) => void;
6
6
  parseInput: (text: string) => Date;
7
7
  formatDate: (date: Date) => string;
8
+ /**
9
+ * Decides if this component is used for the 'from' or 'to' date
10
+ */
11
+ isFromOrTo?: 'from' | 'to';
12
+ /**
13
+ * The 'to' date for the 'from' DatePickerInput or the 'from' date for the 'to' DatePickerInput
14
+ */
15
+ selectedDate?: Date;
8
16
  } & Omit<LabeledInputProps, 'value' | 'onChange' | 'svgIcon' | 'displayStyle'>;
9
17
  declare const DatePickerInput: (props: DatePickerInputProps) => JSX.Element;
10
18
  export default DatePickerInput;
@@ -3,12 +3,20 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import React from 'react';
6
- import { Popover, SvgCalendar } from '../../../utils';
6
+ import { Popover, SvgCalendar, isBefore } from '../../../utils';
7
7
  import { LabeledInput } from '../../../LabeledInput';
8
8
  import { DatePicker } from '../../../DatePicker';
9
9
  import { IconButton } from '../../../Buttons';
10
10
  const DatePickerInput = (props) => {
11
- const { onChange, date, parseInput, formatDate, ...rest } = props;
11
+ const { onChange, date, parseInput, formatDate, isFromOrTo, selectedDate, ...rest } = props;
12
+ const isDateDisabled = (date) => {
13
+ if (isFromOrTo === 'to') {
14
+ return isBefore(date, selectedDate);
15
+ }
16
+ else {
17
+ return isBefore(selectedDate, date);
18
+ }
19
+ };
12
20
  const buttonRef = React.useRef(null);
13
21
  const [inputValue, setInputValue] = React.useState('');
14
22
  React.useEffect(() => {
@@ -35,7 +43,7 @@ const DatePickerInput = (props) => {
35
43
  onChange(parsedDate);
36
44
  }
37
45
  }, [onChange, parseInput]);
38
- return (React.createElement(Popover, { content: React.createElement(DatePicker, { date: date, onChange: onDateSelected, setFocus: true }), placement: 'bottom', visible: isVisible, onClickOutside: (_, e) => {
46
+ return (React.createElement(Popover, { content: React.createElement(DatePicker, { date: date, onChange: onDateSelected, setFocus: true, isDateDisabled: isDateDisabled }), placement: 'bottom', visible: isVisible, onClickOutside: (_, e) => {
39
47
  var _a;
40
48
  if (!((_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.contains(e.target))) {
41
49
  close();
@@ -59,7 +59,7 @@ export const DateRangeFilter = (props) => {
59
59
  }
60
60
  };
61
61
  return (React.createElement(BaseFilter, null,
62
- React.createElement(DatePickerInput, { label: translatedStrings.from, date: from, onChange: onFromChange, formatDate: formatDate, parseInput: parseInput, onKeyDown: onKeyDown, placeholder: placeholder, setFocus: true }),
63
- React.createElement(DatePickerInput, { label: translatedStrings.to, date: to, onChange: onToChange, formatDate: formatDate, parseInput: parseInput, onKeyDown: onKeyDown, placeholder: placeholder }),
62
+ React.createElement(DatePickerInput, { label: translatedStrings.from, date: from, onChange: onFromChange, formatDate: formatDate, parseInput: parseInput, onKeyDown: onKeyDown, placeholder: placeholder, selectedDate: to, isFromOrTo: 'from', setFocus: true }),
63
+ React.createElement(DatePickerInput, { label: translatedStrings.to, date: to, onChange: onToChange, formatDate: formatDate, parseInput: parseInput, onKeyDown: onKeyDown, placeholder: placeholder, selectedDate: from, isFromOrTo: 'to' }),
64
64
  React.createElement(FilterButtonBar, { setFilter: () => setFilter([from, to]), clearFilter: clearFilter, translatedLabels: translatedLabels })));
65
65
  };
@@ -11,6 +11,8 @@ export declare type InputContainerProps<T extends React.ElementType = 'div'> = {
11
11
  isLabelInline?: boolean;
12
12
  isIconInline?: boolean;
13
13
  statusMessage?: React.ReactNode;
14
+ inputId?: string;
15
+ labelId?: string;
14
16
  } & React.ComponentPropsWithoutRef<T>;
15
17
  /**
16
18
  * Input container to wrap inputs with label, and add optional message and icon.
@@ -11,7 +11,8 @@ import '@itwin/itwinui-css/css/utils.css';
11
11
  */
12
12
  export const InputContainer = (props) => {
13
13
  var _a;
14
- const { as: Element = 'div', label, disabled, required, status, message, icon, isLabelInline, isIconInline, children, className, style, statusMessage, ...rest } = props;
14
+ const { as: Element = 'div', label, disabled, required, status, message, icon, isLabelInline, isIconInline, children, className, style, statusMessage, inputId, labelId, ...rest } = props;
15
+ const LabelElement = inputId && Element !== 'label' ? 'label' : 'div';
15
16
  return (React.createElement(Element, { className: cx('iui-input-container', {
16
17
  'iui-disabled': disabled,
17
18
  [`iui-${status}`]: !!status,
@@ -19,9 +20,9 @@ export const InputContainer = (props) => {
19
20
  'iui-inline-icon': isIconInline,
20
21
  'iui-with-message': (!!message || !!icon || !!statusMessage) && !isLabelInline,
21
22
  }, className), style: style, ...rest },
22
- label && (React.createElement("div", { className: cx('iui-label', {
23
+ label && (React.createElement(LabelElement, { className: cx('iui-label', {
23
24
  'iui-required': required,
24
- }) }, label)),
25
+ }), htmlFor: inputId, id: labelId }, label)),
25
26
  children,
26
27
  statusMessage ? (statusMessage) : (React.createElement(React.Fragment, null,
27
28
  icon &&
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Return true if the first date is earlier than the second date
3
+ */
4
+ export declare const isBefore: (beforeDate: Date | undefined, afterDate: Date | undefined) => boolean;
@@ -0,0 +1,17 @@
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
+ /**
6
+ * Return true if the first date is earlier than the second date
7
+ */
8
+ export const isBefore = (beforeDate, afterDate) => {
9
+ if (!beforeDate || !afterDate) {
10
+ return false;
11
+ }
12
+ const firstDate = new Date(beforeDate);
13
+ const secondDate = new Date(afterDate);
14
+ firstDate && firstDate.setHours(0, 0, 0, 0);
15
+ secondDate && secondDate.setHours(0, 0, 0, 0);
16
+ return firstDate < secondDate;
17
+ };
@@ -1,3 +1,4 @@
1
+ export * from './date';
1
2
  export * from './dom';
2
3
  export * from './colors';
3
4
  export * from './numbers';
@@ -2,6 +2,7 @@
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
+ export * from './date';
5
6
  export * from './dom';
6
7
  export * from './colors';
7
8
  export * from './numbers';
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
1
  /**
3
- * Return custom useId function as a fallback for React.useId
2
+ * Wrapper around React's `useId` hook, which prefixes the id with `iui-` and uses
3
+ * a random value as fallback for older React versions which don't include `useId`.
4
4
  */
5
- export declare const useId: typeof React.useId;
5
+ export declare const useId: () => string;
@@ -6,9 +6,11 @@ var _a;
6
6
  import React from 'react';
7
7
  import { getRandomValue } from '../functions/numbers';
8
8
  /**
9
- * Return custom useId function as a fallback for React.useId
9
+ * Wrapper around React's `useId` hook, which prefixes the id with `iui-` and uses
10
+ * a random value as fallback for older React versions which don't include `useId`.
10
11
  */
11
- export const useId = (_a = React.useId) !== null && _a !== void 0 ? _a : (() => {
12
- const [id] = React.useState(() => `iui-${getRandomValue(10)}`);
13
- return id;
14
- });
12
+ export const useId = () => {
13
+ const uniqueValue = useUniqueValue();
14
+ return React.useMemo(() => `iui-${uniqueValue}`, [uniqueValue]);
15
+ };
16
+ const useUniqueValue = (_a = React.useId) !== null && _a !== void 0 ? _a : (() => getRandomValue(10));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/itwinui-react",
3
- "version": "2.9.1",
3
+ "version": "2.10.1",
4
4
  "author": "Bentley Systems",
5
5
  "license": "MIT",
6
6
  "main": "cjs/index.js",
@@ -31,6 +31,11 @@
31
31
  ],
32
32
  "description": "A react component library for iTwinUI",
33
33
  "homepage": "https://github.com/iTwin/iTwinUI",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/iTwin/iTwinUI.git",
37
+ "directory": "packages/itwinui-react"
38
+ },
34
39
  "keywords": [
35
40
  "component",
36
41
  "components",
@@ -62,7 +67,7 @@
62
67
  "dev:types": "concurrently \"tsc -p tsconfig.cjs.json --emitDeclarationOnly --watch --preserveWatchOutput\" \"tsc -p tsconfig.esm.json --emitDeclarationOnly --watch --preserveWatchOutput\""
63
68
  },
64
69
  "dependencies": {
65
- "@itwin/itwinui-css": "^1.10.1",
70
+ "@itwin/itwinui-css": "^1.10.3",
66
71
  "@itwin/itwinui-illustrations-react": "^2.0.0",
67
72
  "@itwin/itwinui-variables": "^2.0.0",
68
73
  "@tippyjs/react": "^4.2.6",