@itwin/itwinui-react 3.2.4 → 3.3.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.
- package/CHANGELOG.md +28 -0
- package/cjs/core/ComboBox/ComboBox.d.ts +3 -1
- package/cjs/core/ComboBox/ComboBox.js +3 -4
- package/cjs/core/ComboBox/ComboBoxInputContainer.js +1 -1
- package/cjs/core/Dialog/DialogContent.d.ts +1 -2
- package/cjs/core/Dialog/DialogContent.js +1 -42
- package/cjs/core/Dialog/DialogContext.d.ts +1 -0
- package/cjs/core/Dialog/DialogMain.js +2 -24
- package/cjs/core/ExpandableBlock/ExpandableBlock.d.ts +7 -4
- package/cjs/core/ExpandableBlock/ExpandableBlock.js +25 -12
- package/cjs/core/InputGrid/InputGrid.d.ts +7 -5
- package/cjs/core/InputGrid/InputGrid.js +175 -6
- package/cjs/core/InputGroup/InputGroup.d.ts +1 -1
- package/cjs/core/InputGroup/InputGroup.js +4 -13
- package/cjs/core/LabeledInput/LabeledInput.d.ts +1 -2
- package/cjs/core/LabeledInput/LabeledInput.js +11 -8
- package/cjs/core/LabeledSelect/LabeledSelect.d.ts +18 -0
- package/cjs/core/LabeledSelect/LabeledSelect.js +5 -20
- package/cjs/core/List/ListItem.d.ts +10 -0
- package/cjs/core/List/ListItem.js +14 -0
- package/cjs/core/Menu/MenuItem.d.ts +1 -1
- package/cjs/core/Modal/Modal.d.ts +3 -4
- package/cjs/core/Select/Select.js +2 -2
- package/cjs/core/StatusMessage/StatusMessage.d.ts +4 -2
- package/cjs/core/StatusMessage/StatusMessage.js +3 -1
- package/cjs/core/Table/Table.js +4 -0
- package/cjs/core/Table/TableCell.js +7 -22
- package/cjs/core/Table/columns/selectionColumn.d.ts +1 -1
- package/cjs/core/Table/columns/selectionColumn.js +3 -3
- package/cjs/core/Table/utils.d.ts +6 -0
- package/cjs/core/Table/utils.js +18 -1
- package/cjs/core/utils/components/InputWithIcon.d.ts +2 -0
- package/cjs/core/utils/components/InputWithIcon.js +11 -0
- package/cjs/core/utils/components/Portal.d.ts +5 -1
- package/cjs/core/utils/components/Portal.js +6 -2
- package/cjs/core/utils/components/index.d.ts +1 -0
- package/cjs/core/utils/components/index.js +1 -0
- package/esm/core/ComboBox/ComboBox.d.ts +3 -1
- package/esm/core/ComboBox/ComboBox.js +3 -3
- package/esm/core/ComboBox/ComboBoxInputContainer.js +2 -2
- package/esm/core/Dialog/DialogContent.d.ts +1 -2
- package/esm/core/Dialog/DialogContent.js +2 -17
- package/esm/core/Dialog/DialogContext.d.ts +1 -0
- package/esm/core/Dialog/DialogMain.js +3 -25
- package/esm/core/ExpandableBlock/ExpandableBlock.d.ts +7 -4
- package/esm/core/ExpandableBlock/ExpandableBlock.js +26 -13
- package/esm/core/InputGrid/InputGrid.d.ts +7 -5
- package/esm/core/InputGrid/InputGrid.js +176 -7
- package/esm/core/InputGroup/InputGroup.d.ts +1 -1
- package/esm/core/InputGroup/InputGroup.js +5 -14
- package/esm/core/LabeledInput/LabeledInput.d.ts +1 -2
- package/esm/core/LabeledInput/LabeledInput.js +8 -9
- package/esm/core/LabeledSelect/LabeledSelect.d.ts +18 -0
- package/esm/core/LabeledSelect/LabeledSelect.js +5 -19
- package/esm/core/List/ListItem.d.ts +10 -0
- package/esm/core/List/ListItem.js +14 -0
- package/esm/core/Menu/MenuItem.d.ts +1 -1
- package/esm/core/Modal/Modal.d.ts +3 -4
- package/esm/core/Select/Select.js +3 -3
- package/esm/core/StatusMessage/StatusMessage.d.ts +4 -2
- package/esm/core/StatusMessage/StatusMessage.js +3 -1
- package/esm/core/Table/Table.js +5 -1
- package/esm/core/Table/TableCell.js +8 -23
- package/esm/core/Table/columns/selectionColumn.d.ts +1 -1
- package/esm/core/Table/columns/selectionColumn.js +3 -3
- package/esm/core/Table/utils.d.ts +6 -0
- package/esm/core/Table/utils.js +16 -0
- package/esm/core/utils/components/InputWithIcon.d.ts +2 -0
- package/esm/core/utils/components/InputWithIcon.js +8 -0
- package/esm/core/utils/components/Portal.d.ts +5 -1
- package/esm/core/utils/components/Portal.js +6 -2
- package/esm/core/utils/components/index.d.ts +1 -0
- package/esm/core/utils/components/index.js +1 -0
- package/package.json +3 -3
- package/styles.css +10 -10
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import cx from 'classnames';
|
|
6
6
|
import * as React from 'react';
|
|
7
|
-
import { StatusIconMap, SvgChevronRight, Box, useSafeContext, polymorphic, mergeEventHandlers, ButtonBase, } from '../utils/index.js';
|
|
7
|
+
import { StatusIconMap, SvgChevronRight, Box, useSafeContext, polymorphic, mergeEventHandlers, ButtonBase, useId, } from '../utils/index.js';
|
|
8
8
|
import { Icon } from '../Icon/Icon.js';
|
|
9
|
+
import { LinkBox } from '../LinkAction/LinkAction.js';
|
|
9
10
|
const ExpandableBlockContext = React.createContext(undefined);
|
|
10
11
|
ExpandableBlockContext.displayName = 'ExpandableBlockContext';
|
|
11
12
|
const ExpandableBlockComponent = React.forwardRef((props, forwardedRef) => {
|
|
@@ -20,6 +21,7 @@ const ExpandableBlockWrapper = React.forwardRef((props, forwardedRef) => {
|
|
|
20
21
|
const { children, className, onToggle, style, isExpanded, status, size = 'default', styleType = 'default', disabled = false, ...rest } = props;
|
|
21
22
|
const [expandedState, setExpanded] = React.useState(isExpanded ?? false);
|
|
22
23
|
const expanded = isExpanded ?? expandedState;
|
|
24
|
+
const [descriptionId, setDescriptionId] = React.useState(undefined);
|
|
23
25
|
return (React.createElement(ExpandableBlockContext.Provider, { value: {
|
|
24
26
|
status,
|
|
25
27
|
isExpanded: expanded,
|
|
@@ -29,20 +31,16 @@ const ExpandableBlockWrapper = React.forwardRef((props, forwardedRef) => {
|
|
|
29
31
|
disabled,
|
|
30
32
|
setExpanded,
|
|
31
33
|
children,
|
|
34
|
+
descriptionId,
|
|
35
|
+
setDescriptionId,
|
|
32
36
|
} },
|
|
33
37
|
React.createElement(Box, { className: cx('iui-expandable-block', className), "data-iui-expanded": expanded, "data-iui-size": size, "data-iui-variant": styleType !== 'default' ? styleType : undefined, style: style, ref: forwardedRef, ...rest }, children)));
|
|
34
38
|
});
|
|
35
39
|
ExpandableBlockWrapper.displayName = 'ExpandableBlock.Wrapper';
|
|
36
40
|
const ExpandableBlockTrigger = React.forwardRef((props, forwardedRef) => {
|
|
37
|
-
const { className, children, label, caption,
|
|
38
|
-
const {
|
|
39
|
-
return (React.createElement(
|
|
40
|
-
if (disabled) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
setExpanded(!isExpanded);
|
|
44
|
-
onToggle?.(!isExpanded);
|
|
45
|
-
}), ref: forwardedRef, ...rest }, children ?? (React.createElement(React.Fragment, null,
|
|
41
|
+
const { className, children, label, caption, expandIcon, endIcon, ...rest } = props;
|
|
42
|
+
const { disabled, status } = useSafeContext(ExpandableBlockContext);
|
|
43
|
+
return (React.createElement(LinkBox, { className: cx('iui-expandable-header', className), "data-iui-disabled": disabled ? 'true' : undefined, ref: forwardedRef, ...rest }, children ?? (React.createElement(React.Fragment, null,
|
|
46
44
|
expandIcon ?? React.createElement(ExpandableBlock.ExpandIcon, null),
|
|
47
45
|
React.createElement(ExpandableBlock.LabelArea, null,
|
|
48
46
|
React.createElement(ExpandableBlock.Title, null, label),
|
|
@@ -64,13 +62,28 @@ ExpandableBlockLabelArea.displayName = 'ExpandableBlock.LabelArea';
|
|
|
64
62
|
// ----------------------------------------------------------------------------
|
|
65
63
|
// ExpandableBlock.Title component
|
|
66
64
|
const ExpandableBlockTitle = React.forwardRef((props, forwardedRef) => {
|
|
67
|
-
const { className, children, ...rest } = props;
|
|
68
|
-
|
|
65
|
+
const { className, children, onClick: onClickProp, ...rest } = props;
|
|
66
|
+
const { isExpanded, setExpanded, disabled, onToggle, descriptionId } = useSafeContext(ExpandableBlockContext);
|
|
67
|
+
return (React.createElement(ButtonBase, { className: cx('iui-expandable-block-title', 'iui-link-action', className), "aria-expanded": isExpanded, "aria-disabled": disabled, onClick: mergeEventHandlers(onClickProp, () => {
|
|
68
|
+
if (disabled) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setExpanded(!isExpanded);
|
|
72
|
+
onToggle?.(!isExpanded);
|
|
73
|
+
}), ref: forwardedRef, "aria-describedby": descriptionId, ...rest }, children));
|
|
69
74
|
});
|
|
70
75
|
ExpandableBlockTitle.displayName = 'ExpandableBlock.Title';
|
|
71
76
|
// ----------------------------------------------------------------------------
|
|
72
77
|
// ExpandableBlock.Caption component
|
|
73
|
-
const ExpandableBlockCaption =
|
|
78
|
+
const ExpandableBlockCaption = React.forwardRef((props, forwardedRef) => {
|
|
79
|
+
const fallbackId = useId();
|
|
80
|
+
const { setDescriptionId } = useSafeContext(ExpandableBlockContext);
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
setDescriptionId(props.id || fallbackId);
|
|
83
|
+
return () => setDescriptionId(undefined);
|
|
84
|
+
}, [props.id, fallbackId, setDescriptionId]);
|
|
85
|
+
return (React.createElement(Box, { ref: forwardedRef, id: fallbackId, ...props, className: cx('iui-expandable-block-caption', props?.className) }));
|
|
86
|
+
});
|
|
74
87
|
ExpandableBlockCaption.displayName = 'ExpandableBlock.Caption';
|
|
75
88
|
// ----------------------------------------------------------------------------
|
|
76
89
|
// ExpandableBlock.EndIcon component
|
|
@@ -10,15 +10,17 @@ type InputGridOwnProps = {
|
|
|
10
10
|
labelPlacement?: 'default' | 'inline';
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
* InputGrid component is used to display
|
|
13
|
+
* InputGrid component is used to display form fields (input, textarea, select)
|
|
14
14
|
* with label and/or status message
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* Form fields are automatically associated with the label and status message for
|
|
17
|
+
* better accessibility.
|
|
17
18
|
*
|
|
19
|
+
* @example
|
|
18
20
|
* <InputGrid>
|
|
19
|
-
* <Label
|
|
20
|
-
* <Input
|
|
21
|
-
* <StatusMessage>This is message</StatusMessage>
|
|
21
|
+
* <Label>This is a label</Label>
|
|
22
|
+
* <Input />
|
|
23
|
+
* <StatusMessage>This is a message</StatusMessage>
|
|
22
24
|
* </InputGrid>
|
|
23
25
|
*/
|
|
24
26
|
export declare const InputGrid: PolymorphicForwardRefComponent<"div", InputGridOwnProps>;
|
|
@@ -3,24 +3,193 @@
|
|
|
3
3
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import * as React from 'react';
|
|
6
|
-
import { Box } from '../utils/index.js';
|
|
6
|
+
import { Box, InputWithIcon, cloneElementWithRef, useId, } from '../utils/index.js';
|
|
7
7
|
import cx from 'classnames';
|
|
8
|
+
import { Label } from '../Label/Label.js';
|
|
9
|
+
import { Input } from '../Input/Input.js';
|
|
10
|
+
import { Textarea } from '../Textarea/Textarea.js';
|
|
11
|
+
import { StatusMessage } from '../StatusMessage/StatusMessage.js';
|
|
12
|
+
import { InputWithDecorations } from '../InputWithDecorations/InputWithDecorations.js';
|
|
13
|
+
import { ComboBox } from '../ComboBox/ComboBox.js';
|
|
14
|
+
import { Select } from '../Select/Select.js';
|
|
8
15
|
//-------------------------------------------------------------------------------
|
|
9
16
|
/**
|
|
10
|
-
* InputGrid component is used to display
|
|
17
|
+
* InputGrid component is used to display form fields (input, textarea, select)
|
|
11
18
|
* with label and/or status message
|
|
12
19
|
*
|
|
13
|
-
*
|
|
20
|
+
* Form fields are automatically associated with the label and status message for
|
|
21
|
+
* better accessibility.
|
|
14
22
|
*
|
|
23
|
+
* @example
|
|
15
24
|
* <InputGrid>
|
|
16
|
-
* <Label
|
|
17
|
-
* <Input
|
|
18
|
-
* <StatusMessage>This is message</StatusMessage>
|
|
25
|
+
* <Label>This is a label</Label>
|
|
26
|
+
* <Input />
|
|
27
|
+
* <StatusMessage>This is a message</StatusMessage>
|
|
19
28
|
* </InputGrid>
|
|
20
29
|
*/
|
|
21
30
|
export const InputGrid = React.forwardRef((props, ref) => {
|
|
22
|
-
const { children, className, labelPlacement = undefined, ...rest } = props;
|
|
31
|
+
const { children: childrenProp, className, labelPlacement = undefined, ...rest } = props;
|
|
32
|
+
const children = useChildrenWithIds(childrenProp);
|
|
23
33
|
return (React.createElement(Box, { className: cx('iui-input-grid', className), "data-iui-label-placement": labelPlacement, ref: ref, ...rest }, children));
|
|
24
34
|
});
|
|
25
35
|
//-------------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Ensures that label, input and message are properly associated
|
|
38
|
+
* with each other, for accessibility purposes.
|
|
39
|
+
*
|
|
40
|
+
* - `Select` will be associated with label using `aria-labelledby`
|
|
41
|
+
* - Other inputs will be associated with label using `htmlFor`
|
|
42
|
+
* - Message will be associated with input/select using `aria-describedby`
|
|
43
|
+
*/
|
|
44
|
+
const useChildrenWithIds = (children) => {
|
|
45
|
+
const { labelId, inputId, messageId } = useSetup(children);
|
|
46
|
+
return React.useMemo(() => React.Children.map(children, (child) => {
|
|
47
|
+
if (!React.isValidElement(child)) {
|
|
48
|
+
return child;
|
|
49
|
+
}
|
|
50
|
+
if (child.type === Label || child.type === 'label') {
|
|
51
|
+
return cloneElementWithRef(child, (child) => ({
|
|
52
|
+
...child.props,
|
|
53
|
+
htmlFor: child.props.htmlFor || inputId,
|
|
54
|
+
id: child.props.id || labelId,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
if (child.type === StatusMessage) {
|
|
58
|
+
return cloneElementWithRef(child, (child) => ({
|
|
59
|
+
...child.props,
|
|
60
|
+
id: child.props.id || messageId,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
if (isInput(child) ||
|
|
64
|
+
child.type === InputWithDecorations ||
|
|
65
|
+
child.type === InputWithIcon) {
|
|
66
|
+
return handleCloningInputs(child, {
|
|
67
|
+
labelId,
|
|
68
|
+
inputId,
|
|
69
|
+
messageId,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return child;
|
|
73
|
+
}), [children, inputId, labelId, messageId]);
|
|
74
|
+
};
|
|
75
|
+
//-------------------------------------------------------------------------------
|
|
76
|
+
/**
|
|
77
|
+
* Setup/prerequisite for `useChildrenWithIds` to gather information from children.
|
|
78
|
+
*
|
|
79
|
+
* @returns the following ids (prefers id from props, otherwise generates one)
|
|
80
|
+
* - `labelId`
|
|
81
|
+
* - `inputId`
|
|
82
|
+
* - `messageId`
|
|
83
|
+
*/
|
|
84
|
+
const useSetup = (children) => {
|
|
85
|
+
const idPrefix = useId();
|
|
86
|
+
let labelId;
|
|
87
|
+
let inputId;
|
|
88
|
+
let messageId;
|
|
89
|
+
let hasLabel = false;
|
|
90
|
+
let hasSelect = false;
|
|
91
|
+
const findInputId = (child) => {
|
|
92
|
+
if (!React.isValidElement(child)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// ComboBox input id is passed through `inputProps`
|
|
96
|
+
if (child.type === ComboBox) {
|
|
97
|
+
return child.props.inputProps?.id || `${idPrefix}--input`;
|
|
98
|
+
}
|
|
99
|
+
// Select input id would be passed through `triggerProps`, but we don't even
|
|
100
|
+
// need it because, unlike other inputs, it gets labelled using `aria-labelledby`
|
|
101
|
+
else if (child.type !== Select) {
|
|
102
|
+
return child.props.id || `${idPrefix}--input`;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
React.Children.forEach(children, (child) => {
|
|
106
|
+
if (!React.isValidElement(child)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (child.type === Label || child.type === 'label') {
|
|
110
|
+
hasLabel = true;
|
|
111
|
+
labelId || (labelId = child.props.id || `${idPrefix}--label`);
|
|
112
|
+
}
|
|
113
|
+
if (child.type === StatusMessage) {
|
|
114
|
+
messageId || (messageId = child.props.id || `${idPrefix}--message`);
|
|
115
|
+
}
|
|
116
|
+
if (child.type === InputWithDecorations || child.type === InputWithIcon) {
|
|
117
|
+
React.Children.forEach(child.props.children, (child) => {
|
|
118
|
+
if (isInput(child)) {
|
|
119
|
+
inputId || (inputId = findInputId(child));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else if (isInput(child)) {
|
|
124
|
+
inputId || (inputId = findInputId(child));
|
|
125
|
+
}
|
|
126
|
+
if (child.type === Select) {
|
|
127
|
+
hasSelect = true;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
labelId: hasSelect ? labelId : undefined,
|
|
132
|
+
inputId: hasLabel && !hasSelect ? inputId : undefined,
|
|
133
|
+
messageId,
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
//-------------------------------------------------------------------------------
|
|
137
|
+
/**
|
|
138
|
+
* Handles regular inputs, plus `InputWithDecorations`, `InputWithIcon`, `ComboBox` and `Select`.
|
|
139
|
+
*/
|
|
140
|
+
const handleCloningInputs = (child, { labelId, inputId, messageId, }) => {
|
|
141
|
+
const inputProps = (props = {}) => {
|
|
142
|
+
// Concatenate aria-describedby from props and from StatusMessage
|
|
143
|
+
const ariaDescribedBy = [props['aria-describedby'], messageId]
|
|
144
|
+
.filter(Boolean)
|
|
145
|
+
.join(' ');
|
|
146
|
+
return {
|
|
147
|
+
...props,
|
|
148
|
+
...(child.type !== Select && { id: props.id || inputId }),
|
|
149
|
+
'aria-describedby': ariaDescribedBy?.trim() || undefined,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const cloneInput = (child) => {
|
|
153
|
+
if (child.type === ComboBox) {
|
|
154
|
+
return cloneElementWithRef(child, (child) => ({
|
|
155
|
+
inputProps: inputProps(child.props.inputProps),
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
if (child.type === Select) {
|
|
159
|
+
return cloneElementWithRef(child, (child) => ({
|
|
160
|
+
triggerProps: {
|
|
161
|
+
...{ 'aria-labelledby': labelId },
|
|
162
|
+
...inputProps(child.props.triggerProps),
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
return cloneElementWithRef(child, (child) => inputProps(child.props));
|
|
167
|
+
};
|
|
168
|
+
if (child.type === InputWithDecorations || child.type === InputWithIcon) {
|
|
169
|
+
return cloneElementWithRef(child, (child) => ({
|
|
170
|
+
children: React.Children.map(child.props.children, (child) => {
|
|
171
|
+
if (React.isValidElement(child) && isInput(child)) {
|
|
172
|
+
return cloneInput(child);
|
|
173
|
+
}
|
|
174
|
+
return child;
|
|
175
|
+
}),
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
return cloneInput(child);
|
|
179
|
+
};
|
|
180
|
+
//-------------------------------------------------------------------------------
|
|
181
|
+
/** @returns true if `child` is a form element that can be associated with a label using id */
|
|
182
|
+
const isInput = (child) => {
|
|
183
|
+
return (React.isValidElement(child) &&
|
|
184
|
+
(child.type === 'input' ||
|
|
185
|
+
child.type === 'textarea' ||
|
|
186
|
+
child.type === 'select' ||
|
|
187
|
+
child.type === Input ||
|
|
188
|
+
child.type === Textarea ||
|
|
189
|
+
child.type === InputWithDecorations.Input ||
|
|
190
|
+
child.type === Select || // contains Select.triggerProps
|
|
191
|
+
child.type === ComboBox) // contains ComboBox.inputProps
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
//-------------------------------------------------------------------------------
|
|
26
195
|
export default InputGrid;
|
|
@@ -32,7 +32,7 @@ type InputGroupProps = {
|
|
|
32
32
|
/**
|
|
33
33
|
* Custom icon. If group has status, default status icon is used instead.
|
|
34
34
|
*/
|
|
35
|
-
svgIcon?:
|
|
35
|
+
svgIcon?: React.ComponentPropsWithoutRef<typeof StatusMessage>['startIcon'];
|
|
36
36
|
/**
|
|
37
37
|
* Child inputs inside group.
|
|
38
38
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
import cx from 'classnames';
|
|
7
|
-
import {
|
|
7
|
+
import { Box } from '../utils/index.js';
|
|
8
8
|
import { InputGrid } from '../InputGrid/InputGrid.js';
|
|
9
9
|
import { Label } from '../Label/Label.js';
|
|
10
10
|
import { StatusMessage } from '../StatusMessage/StatusMessage.js';
|
|
@@ -25,20 +25,11 @@ import { StatusMessage } from '../StatusMessage/StatusMessage.js';
|
|
|
25
25
|
*/
|
|
26
26
|
export const InputGroup = React.forwardRef((props, forwardedRef) => {
|
|
27
27
|
const { className, children, disabled = false, displayStyle = 'default', label, message, status, svgIcon, required = false, labelProps, messageProps, innerProps, ...rest } = props;
|
|
28
|
-
|
|
29
|
-
if (svgIcon) {
|
|
30
|
-
return React.cloneElement(svgIcon, { 'aria-hidden': true });
|
|
31
|
-
}
|
|
32
|
-
if (status && message) {
|
|
33
|
-
return React.cloneElement(StatusIconMap[status](), {
|
|
34
|
-
'aria-hidden': true,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
return undefined;
|
|
38
|
-
};
|
|
39
|
-
return (React.createElement(InputGrid, { ref: forwardedRef, as: 'div', labelPlacement: displayStyle, className: cx('iui-input-group-wrapper', className), ...rest },
|
|
28
|
+
return (React.createElement(InputGrid, { ref: forwardedRef, as: 'div', labelPlacement: displayStyle, className: cx('iui-input-group-wrapper', className), "data-iui-status": status, ...rest },
|
|
40
29
|
label && (React.createElement(Label, { as: 'label', required: required, disabled: disabled, ...labelProps }, label)),
|
|
41
30
|
React.createElement(Box, { as: 'div', ...innerProps, className: cx('iui-input-group', innerProps?.className) }, children),
|
|
42
|
-
(message || status || svgIcon) && (React.createElement(StatusMessage, {
|
|
31
|
+
(message || status || svgIcon) && (React.createElement(StatusMessage, { iconProps: {
|
|
32
|
+
'aria-hidden': true,
|
|
33
|
+
}, startIcon: svgIcon, status: status, ...messageProps }, displayStyle !== 'inline' && message))));
|
|
43
34
|
});
|
|
44
35
|
export default InputGroup;
|
|
@@ -2,7 +2,6 @@ import * as React from 'react';
|
|
|
2
2
|
import { Input } from '../Input/Input.js';
|
|
3
3
|
import type { PolymorphicForwardRefComponent } from '../utils/index.js';
|
|
4
4
|
import { InputGrid } from '../InputGrid/InputGrid.js';
|
|
5
|
-
import { InputWithDecorations } from '../InputWithDecorations/InputWithDecorations.js';
|
|
6
5
|
import { Icon } from '../Icon/Icon.js';
|
|
7
6
|
export type LabeledInputProps = {
|
|
8
7
|
/**
|
|
@@ -48,7 +47,7 @@ export type LabeledInputProps = {
|
|
|
48
47
|
/**
|
|
49
48
|
* Passes properties for input wrapper.
|
|
50
49
|
*/
|
|
51
|
-
inputWrapperProps?: React.
|
|
50
|
+
inputWrapperProps?: React.ComponentPropsWithRef<'div'>;
|
|
52
51
|
} & React.ComponentProps<typeof Input>;
|
|
53
52
|
/**
|
|
54
53
|
* Basic labeled input component
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
import { Input } from '../Input/Input.js';
|
|
7
|
-
import {
|
|
7
|
+
import { InputWithIcon, StatusIconMap } from '../utils/index.js';
|
|
8
8
|
import { InputGrid } from '../InputGrid/InputGrid.js';
|
|
9
|
-
import { InputWithDecorations } from '../InputWithDecorations/InputWithDecorations.js';
|
|
10
9
|
import { StatusMessage } from '../StatusMessage/StatusMessage.js';
|
|
11
10
|
import { Label } from '../Label/Label.js';
|
|
12
11
|
import { Icon } from '../Icon/Icon.js';
|
|
12
|
+
import cx from 'classnames';
|
|
13
13
|
/**
|
|
14
14
|
* Basic labeled input component
|
|
15
15
|
* @example
|
|
@@ -19,15 +19,14 @@ import { Icon } from '../Icon/Icon.js';
|
|
|
19
19
|
* <LabeledInput status='negative' label='Negative' />
|
|
20
20
|
*/
|
|
21
21
|
export const LabeledInput = React.forwardRef((props, ref) => {
|
|
22
|
-
const
|
|
23
|
-
const { disabled = false, label, message, status, svgIcon, wrapperProps, labelProps, messageContentProps, iconProps, inputWrapperProps, displayStyle = 'default', required = false, id = uid, ...rest } = props;
|
|
22
|
+
const { disabled = false, label, message, status, svgIcon, wrapperProps, labelProps, messageContentProps, iconProps, inputWrapperProps, displayStyle = 'default', required = false, ...rest } = props;
|
|
24
23
|
const icon = svgIcon ?? (status && StatusIconMap[status]());
|
|
25
24
|
const shouldShowIcon = svgIcon !== null && (svgIcon || (status && !message));
|
|
26
|
-
return (React.createElement(InputGrid, { labelPlacement: displayStyle, ...wrapperProps },
|
|
27
|
-
label && (React.createElement(Label, { as: 'label', required: required, disabled: disabled,
|
|
28
|
-
React.createElement(
|
|
29
|
-
React.createElement(
|
|
30
|
-
shouldShowIcon && (React.createElement(Icon, { fill:
|
|
25
|
+
return (React.createElement(InputGrid, { labelPlacement: displayStyle, "data-iui-status": status, ...wrapperProps },
|
|
26
|
+
label && (React.createElement(Label, { as: 'label', required: required, disabled: disabled, ...labelProps }, label)),
|
|
27
|
+
React.createElement(InputWithIcon, { ...inputWrapperProps },
|
|
28
|
+
React.createElement(Input, { disabled: disabled, required: required, ref: ref, ...rest }),
|
|
29
|
+
shouldShowIcon && (React.createElement(Icon, { fill: status, ...iconProps, className: cx('iui-end-icon', iconProps?.className) }, icon))),
|
|
31
30
|
typeof message === 'string' ? (React.createElement(StatusMessage, { status: status, iconProps: iconProps, contentProps: messageContentProps }, message)) : (message)));
|
|
32
31
|
});
|
|
33
32
|
export default LabeledInput;
|
|
@@ -10,6 +10,22 @@ export type LabeledSelectProps<T> = {
|
|
|
10
10
|
label?: React.ReactNode;
|
|
11
11
|
/**
|
|
12
12
|
* Message below the select. Does not apply to 'inline' select.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <caption>strings</caption>
|
|
16
|
+
* <LabeledSelect message='Positive Message' … />
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <caption>Using StatusMessage for complete customization (e.g. icon)</caption>
|
|
20
|
+
* <LabeledSelect
|
|
21
|
+
* status="positive"
|
|
22
|
+
* message={
|
|
23
|
+
* <StatusMessage status="positive" startIcon={<SvgStar />}>
|
|
24
|
+
* Help message
|
|
25
|
+
* </StatusMessage>
|
|
26
|
+
* }
|
|
27
|
+
* …
|
|
28
|
+
* />
|
|
13
29
|
*/
|
|
14
30
|
message?: React.ReactNode;
|
|
15
31
|
/**
|
|
@@ -18,6 +34,8 @@ export type LabeledSelectProps<T> = {
|
|
|
18
34
|
*/
|
|
19
35
|
status?: 'positive' | 'warning' | 'negative';
|
|
20
36
|
/**
|
|
37
|
+
* @deprecated Pass a `<StatusMessage startIcon={svgIcon} />` to the `message` prop instead.
|
|
38
|
+
*
|
|
21
39
|
* Custom svg icon. Will override status icon if specified.
|
|
22
40
|
*/
|
|
23
41
|
svgIcon?: JSX.Element;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
import { Select } from '../Select/Select.js';
|
|
7
|
-
import { StatusIconMap, useId } from '../utils/index.js';
|
|
8
7
|
import { StatusMessage } from '../StatusMessage/StatusMessage.js';
|
|
9
8
|
import { InputGrid } from '../InputGrid/InputGrid.js';
|
|
10
9
|
import { Label } from '../Label/Label.js';
|
|
@@ -43,23 +42,10 @@ import { Icon } from '../Icon/Icon.js';
|
|
|
43
42
|
* />
|
|
44
43
|
*/
|
|
45
44
|
export const LabeledSelect = React.forwardRef((props, forwardedRef) => {
|
|
46
|
-
const { className, disabled = false, label, message, status, svgIcon, displayStyle = 'default', style, required = false,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
if (status && message) {
|
|
53
|
-
return StatusIconMap[status]();
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
56
|
-
};
|
|
57
|
-
return (React.createElement(InputGrid, { labelPlacement: displayStyle, ...wrapperProps },
|
|
58
|
-
label && (React.createElement(Label, { as: 'div', required: required, disabled: disabled, id: labelId, ...labelProps }, label)),
|
|
59
|
-
React.createElement(Select, { disabled: disabled, className: className, style: style, status: status, ...rest, ref: forwardedRef, triggerProps: {
|
|
60
|
-
'aria-labelledby': labelId,
|
|
61
|
-
...triggerProps,
|
|
62
|
-
} }),
|
|
63
|
-
typeof message === 'string' ? (React.createElement(StatusMessage, { status: status, startIcon: displayStyle === 'default' ? icon() : undefined, iconProps: messageIconProps, contentProps: messageContentProps }, message)) : (message)));
|
|
45
|
+
const { className, disabled = false, label, message, status, svgIcon, displayStyle = 'default', style, required = false, wrapperProps, labelProps, messageContentProps, messageIconProps, ...rest } = props;
|
|
46
|
+
return (React.createElement(InputGrid, { labelPlacement: displayStyle, "data-iui-status": status, ...wrapperProps },
|
|
47
|
+
label && (React.createElement(Label, { as: 'div', required: required, disabled: disabled, ...labelProps }, label)),
|
|
48
|
+
React.createElement(Select, { disabled: disabled, className: className, style: style, ...rest, ref: forwardedRef }),
|
|
49
|
+
typeof message === 'string' ? (React.createElement(StatusMessage, { status: status, startIcon: svgIcon, iconProps: messageIconProps, contentProps: messageContentProps }, message)) : (message)));
|
|
64
50
|
});
|
|
65
51
|
export default LabeledSelect;
|
|
@@ -68,4 +68,14 @@ export declare const ListItem: PolymorphicForwardRefComponent<"li", ListItemOwnP
|
|
|
68
68
|
* </ListItem>
|
|
69
69
|
*/
|
|
70
70
|
Description: PolymorphicForwardRefComponent<NonNullable<keyof JSX.IntrinsicElements>, {}>;
|
|
71
|
+
/**
|
|
72
|
+
* Wrapper over [LinkAction](https://itwinui.bentley.com/docs/linkaction) which allows rendering a link inside a ListItem.
|
|
73
|
+
* This ensures that clicking anywhere on the ListItem will trigger the link.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* <ListItem>
|
|
77
|
+
* <ListItem.Action href='https://example.com'>Example link</ListItem.Action>
|
|
78
|
+
* </ListItem>
|
|
79
|
+
*/
|
|
80
|
+
Action: PolymorphicForwardRefComponent<"a", {}>;
|
|
71
81
|
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
import cx from 'classnames';
|
|
7
7
|
import { polymorphic, Box } from '../utils/index.js';
|
|
8
|
+
import { LinkAction } from '../LinkAction/LinkAction.js';
|
|
8
9
|
const ListItemComponent = React.forwardRef((props, ref) => {
|
|
9
10
|
const { size = 'default', disabled = false, active = false, actionable = false, focused = false, className, ...rest } = props;
|
|
10
11
|
return (React.createElement(Box, { as: 'li', className: cx('iui-list-item', className), "data-iui-active": active ? 'true' : undefined, "data-iui-disabled": disabled ? 'true' : undefined, "data-iui-size": size === 'large' ? 'large' : undefined, "data-iui-actionable": actionable ? 'true' : undefined, "data-iui-focused": focused ? 'true' : undefined, ref: ref, ...rest }));
|
|
@@ -20,6 +21,9 @@ ListItemContent.displayName = 'ListItem.Content';
|
|
|
20
21
|
const ListItemDescription = polymorphic('iui-list-item-description');
|
|
21
22
|
ListItemDescription.displayName = 'ListItem.Description';
|
|
22
23
|
// ----------------------------------------------------------------------------
|
|
24
|
+
const ListItemAction = LinkAction;
|
|
25
|
+
ListItemAction.displayName = 'ListItem.Action';
|
|
26
|
+
// ----------------------------------------------------------------------------
|
|
23
27
|
// Exported compound component
|
|
24
28
|
/**
|
|
25
29
|
* A generic ListItem component that can be used simply for displaying data, or as a base
|
|
@@ -61,4 +65,14 @@ export const ListItem = Object.assign(ListItemComponent, {
|
|
|
61
65
|
* </ListItem>
|
|
62
66
|
*/
|
|
63
67
|
Description: ListItemDescription,
|
|
68
|
+
/**
|
|
69
|
+
* Wrapper over [LinkAction](https://itwinui.bentley.com/docs/linkaction) which allows rendering a link inside a ListItem.
|
|
70
|
+
* This ensures that clicking anywhere on the ListItem will trigger the link.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* <ListItem>
|
|
74
|
+
* <ListItem.Action href='https://example.com'>Example link</ListItem.Action>
|
|
75
|
+
* </ListItem>
|
|
76
|
+
*/
|
|
77
|
+
Action: ListItemAction,
|
|
64
78
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import type { PolymorphicForwardRefComponent } from '../utils/index.js';
|
|
2
|
+
import type { PolymorphicForwardRefComponent, PortalProps } from '../utils/index.js';
|
|
3
3
|
import type { DialogMainProps } from '../Dialog/DialogMain.js';
|
|
4
4
|
type ModalProps = {
|
|
5
5
|
/**
|
|
@@ -33,12 +33,11 @@ type ModalProps = {
|
|
|
33
33
|
* If true, the dialog will be portaled into a <div> inside the nearest `ThemeProvider`.
|
|
34
34
|
*
|
|
35
35
|
* Can be set to an object with a `to` property to portal into a specific element.
|
|
36
|
+
* If `to`/`to()` === `null`/`undefined`, the default behavior will be used (i.e. as if `portal` is not passed).
|
|
36
37
|
*
|
|
37
38
|
* @default true
|
|
38
39
|
*/
|
|
39
|
-
portal?:
|
|
40
|
-
to: HTMLElement;
|
|
41
|
-
};
|
|
40
|
+
portal?: PortalProps['portal'];
|
|
42
41
|
/**
|
|
43
42
|
* Content of the modal.
|
|
44
43
|
*/
|
|
@@ -6,7 +6,7 @@ import * as React from 'react';
|
|
|
6
6
|
import cx from 'classnames';
|
|
7
7
|
import { Menu } from '../Menu/Menu.js';
|
|
8
8
|
import { MenuItem } from '../Menu/MenuItem.js';
|
|
9
|
-
import { SvgCaretDownSmall, useId, AutoclearingHiddenLiveRegion, Box, Portal, useMergedRefs, SvgCheckmark, useLatestRef, } from '../utils/index.js';
|
|
9
|
+
import { SvgCaretDownSmall, useId, AutoclearingHiddenLiveRegion, Box, Portal, useMergedRefs, SvgCheckmark, useLatestRef, InputWithIcon, } from '../utils/index.js';
|
|
10
10
|
import { SelectTag } from './SelectTag.js';
|
|
11
11
|
import { SelectTagContainer } from './SelectTagContainer.js';
|
|
12
12
|
import { Icon } from '../Icon/Icon.js';
|
|
@@ -69,7 +69,7 @@ const isSingleOnChange = (onChange, multiple) => {
|
|
|
69
69
|
*/
|
|
70
70
|
export const Select = React.forwardRef((props, forwardedRef) => {
|
|
71
71
|
const uid = useId();
|
|
72
|
-
const { options, value: valueProp, onChange: onChangeProp, placeholder, disabled = false, size, itemRenderer, selectedItemRenderer,
|
|
72
|
+
const { options, value: valueProp, onChange: onChangeProp, placeholder, disabled = false, size, itemRenderer, selectedItemRenderer, menuClassName, menuStyle, multiple = false, triggerProps, status, popoverProps, ...rest } = props;
|
|
73
73
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
74
74
|
const [liveRegionSelection, setLiveRegionSelection] = React.useState('');
|
|
75
75
|
const [uncontrolledValue, setUncontrolledValue] = React.useState();
|
|
@@ -160,7 +160,7 @@ export const Select = React.forwardRef((props, forwardedRef) => {
|
|
|
160
160
|
onVisibleChange: (open) => (open ? show() : hide()),
|
|
161
161
|
});
|
|
162
162
|
return (React.createElement(React.Fragment, null,
|
|
163
|
-
React.createElement(
|
|
163
|
+
React.createElement(InputWithIcon, { ...rest, ref: useMergedRefs(popover.refs.setPositionReference, forwardedRef) },
|
|
164
164
|
React.createElement(Box, { ...popover.getReferenceProps(), tabIndex: 0, role: 'combobox', "data-iui-size": size, "data-iui-status": status, "aria-disabled": disabled, "aria-autocomplete": 'none', "aria-expanded": isOpen, "aria-haspopup": 'listbox', "aria-controls": `${uid}-menu`, ...triggerProps, ref: useMergedRefs(selectRef, triggerProps?.ref, popover.refs.setReference), className: cx('iui-select-button', {
|
|
165
165
|
'iui-placeholder': (!selectedItems || selectedItems.length === 0) &&
|
|
166
166
|
!!placeholder,
|
|
@@ -4,9 +4,11 @@ import { Icon } from '../Icon/Icon.js';
|
|
|
4
4
|
type StatusMessageProps = {
|
|
5
5
|
/**
|
|
6
6
|
* Custom icon to be displayed at the beginning.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* - It will default to the `status` icon, if `status` is set.
|
|
9
|
+
* - If `startIcon` is set to `null`, no icon will be displayed, even if `status` is set.
|
|
8
10
|
*/
|
|
9
|
-
startIcon?: JSX.Element;
|
|
11
|
+
startIcon?: JSX.Element | null;
|
|
10
12
|
/**
|
|
11
13
|
* Message content.
|
|
12
14
|
*/
|
|
@@ -15,8 +15,10 @@ import { Icon } from '../Icon/Icon.js';
|
|
|
15
15
|
export const StatusMessage = React.forwardRef((props, ref) => {
|
|
16
16
|
const { children, startIcon: userStartIcon, status, className, iconProps, contentProps, ...rest } = props;
|
|
17
17
|
const icon = userStartIcon ?? (status && StatusIconMap[status]());
|
|
18
|
+
// If user passes null, we don't want to show the icon
|
|
19
|
+
const shouldShowIcon = userStartIcon !== null && !!icon;
|
|
18
20
|
return (React.createElement(Box, { className: cx('iui-status-message', className), "data-iui-status": status, ref: ref, ...rest },
|
|
19
|
-
|
|
21
|
+
shouldShowIcon ? (React.createElement(Icon, { "aria-hidden": true, ...iconProps }, icon)) : null,
|
|
20
22
|
React.createElement(Box, { ...contentProps }, children)));
|
|
21
23
|
});
|
|
22
24
|
export default StatusMessage;
|
package/esm/core/Table/Table.js
CHANGED
|
@@ -8,7 +8,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
10
|
import { useGlobals, useResizeObserver, SvgSortDown, SvgSortUp, useIsomorphicLayoutEffect, Box, createWarningLogger, } from '../utils/index.js';
|
|
11
|
-
import { getCellStyle, getStickyStyle } from './utils.js';
|
|
11
|
+
import { getCellStyle, getStickyStyle, getSubRowStyle } from './utils.js';
|
|
12
12
|
import { TableRowMemoized } from './TableRowMemoized.js';
|
|
13
13
|
import { FilterToggle } from './filters/index.js';
|
|
14
14
|
import { customFilterFunctions } from './filters/customFilterFunctions.js';
|
|
@@ -400,6 +400,9 @@ export const Table = (props) => {
|
|
|
400
400
|
React.createElement(Box, { as: 'div', ...headerProps, className: cx('iui-table-header', headerProps?.className) },
|
|
401
401
|
React.createElement(Box, { ...headerGroupProps }, headerGroup.headers.map((column, index) => {
|
|
402
402
|
const { onClick, ...restSortProps } = column.getSortByToggleProps();
|
|
403
|
+
const columnHasExpanders = hasAnySubRows &&
|
|
404
|
+
index ===
|
|
405
|
+
headerGroup.headers.findIndex((c) => c.id !== SELECTION_CELL_ID);
|
|
403
406
|
const columnProps = column.getHeaderProps({
|
|
404
407
|
...restSortProps,
|
|
405
408
|
className: cx('iui-table-cell', {
|
|
@@ -409,6 +412,7 @@ export const Table = (props) => {
|
|
|
409
412
|
}, column.columnClassName),
|
|
410
413
|
style: {
|
|
411
414
|
...getCellStyle(column, !!state.isTableResizing),
|
|
415
|
+
...(columnHasExpanders && getSubRowStyle({ density })),
|
|
412
416
|
...getStickyStyle(column, visibleColumns),
|
|
413
417
|
flexWrap: 'unset',
|
|
414
418
|
},
|