@kaizen/components 3.1.5 → 3.2.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.
- package/dist/cjs/src/Avatar/Avatar.cjs +5 -1
- package/dist/cjs/src/DateInput/DateInput/DateInput.cjs +1 -2
- package/dist/cjs/src/DatePicker/DatePicker.cjs +6 -4
- package/dist/cjs/src/Tabs/subcomponents/TabList/TabList.cjs +8 -1
- package/dist/cjs/src/TimeField/TimeField.cjs +9 -4
- package/dist/cjs/src/TimeField/subcomponents/TimeSegment/TimeSegment.cjs +6 -4
- package/dist/esm/src/Avatar/Avatar.mjs +5 -1
- package/dist/esm/src/DateInput/DateInput/DateInput.mjs +1 -2
- package/dist/esm/src/DatePicker/DatePicker.mjs +6 -4
- package/dist/esm/src/Tabs/subcomponents/TabList/TabList.mjs +8 -1
- package/dist/esm/src/TimeField/TimeField.mjs +9 -4
- package/dist/esm/src/TimeField/subcomponents/TimeSegment/TimeSegment.mjs +6 -4
- package/dist/types/DateInput/DateInputWithIconButton/DateInputWithIconButton.d.ts +2 -2
- package/dist/types/DatePicker/DatePicker.d.ts +3 -2
- package/dist/types/Input/Input/Input.d.ts +1 -1
- package/dist/types/TextArea/TextArea.d.ts +1 -1
- package/dist/types/TimeField/TimeField.d.ts +1 -0
- package/dist/types/TimeField/subcomponents/TimeSegment/TimeSegment.d.ts +3 -1
- package/package.json +7 -7
- package/src/Avatar/Avatar.tsx +4 -1
- package/src/DateInput/DateInput/DateInput.tsx +1 -2
- package/src/DateInput/DateInputWithIconButton/DateInputWithIconButton.tsx +2 -2
- package/src/DatePicker/DatePicker.tsx +6 -3
- package/src/Input/Input/Input.tsx +1 -1
- package/src/Tabs/_docs/Tabs.spec.stories.tsx +39 -0
- package/src/Tabs/_docs/Tabs.stories.tsx +38 -2
- package/src/Tabs/subcomponents/TabList/TabList.tsx +9 -1
- package/src/TextArea/TextArea.tsx +1 -1
- package/src/TimeField/TimeField.tsx +17 -15
- package/src/TimeField/subcomponents/TimeSegment/TimeSegment.tsx +6 -3
|
@@ -61,9 +61,13 @@ var renderInitials = function (fullName, alt, size, disableInitials) {
|
|
|
61
61
|
title: alt
|
|
62
62
|
}, isLongName ?
|
|
63
63
|
// Only called if 3 or more initials, fits text width for long names
|
|
64
|
+
//
|
|
65
|
+
// Ignore Chromatic diffs since the font-size calculation has shown itself to be slightly non-deterministic,
|
|
66
|
+
// causing flaky tests.
|
|
64
67
|
React__default.default.createElement(reactTextfit.Textfit, {
|
|
65
68
|
mode: "single",
|
|
66
|
-
max: getMaxFontSizePixels(size)
|
|
69
|
+
max: getMaxFontSizePixels(size),
|
|
70
|
+
"data-chromatic": "ignore"
|
|
67
71
|
}, initials) : getInitials(fullName, size === 'small'));
|
|
68
72
|
};
|
|
69
73
|
/**
|
|
@@ -7,7 +7,6 @@ var Input = require('../../Input/Input/Input.cjs');
|
|
|
7
7
|
require('../../Input/InputRange/InputRange.cjs');
|
|
8
8
|
require('../../Input/InputSearch/InputSearch.cjs');
|
|
9
9
|
var Label = require('../../Label/Label.cjs');
|
|
10
|
-
var isRefObject = require('../../utils/isRefObject.cjs');
|
|
11
10
|
var DateInput_module = require('./DateInput.module.css.cjs');
|
|
12
11
|
function _interopDefault(e) {
|
|
13
12
|
return e && e.__esModule ? e : {
|
|
@@ -33,7 +32,7 @@ var DateInput = React__default.default.forwardRef(function (_a, ref) {
|
|
|
33
32
|
reversed: isReversed,
|
|
34
33
|
disabled: disabled
|
|
35
34
|
}), React__default.default.createElement(Input.Input, tslib.__assign({
|
|
36
|
-
inputRef:
|
|
35
|
+
inputRef: ref,
|
|
37
36
|
id: id,
|
|
38
37
|
type: "text",
|
|
39
38
|
autoComplete: "off",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var i18nReactIntl = require('@cultureamp/i18n-react-intl');
|
|
6
|
+
var utils = require('@react-aria/utils');
|
|
6
7
|
var reactFocusOn = require('react-focus-on');
|
|
7
8
|
var CalendarSingle = require('../Calendar/CalendarSingle/CalendarSingle.cjs');
|
|
8
9
|
require('../Calendar/CalendarRange/CalendarRange.cjs');
|
|
@@ -33,6 +34,7 @@ var React__default = /*#__PURE__*/_interopDefault(React);
|
|
|
33
34
|
*/
|
|
34
35
|
var DatePicker = function (_a) {
|
|
35
36
|
var propsId = _a.id,
|
|
37
|
+
propsInputRef = _a.inputRef,
|
|
36
38
|
propsButtonRef = _a.buttonRef,
|
|
37
39
|
_b = _a.locale,
|
|
38
40
|
propsLocale = _b === void 0 ? 'en-AU' : _b,
|
|
@@ -54,7 +56,7 @@ var DatePicker = function (_a) {
|
|
|
54
56
|
onButtonClick = _a.onButtonClick,
|
|
55
57
|
onDayChange = _a.onDayChange,
|
|
56
58
|
onValidate = _a.onValidate,
|
|
57
|
-
restDateInputFieldProps = tslib.__rest(_a, ["id", "buttonRef", "locale", "disabledDates", "disabledDaysOfWeek", "disabledRange", "disabledBeforeAfter", "disabledBefore", "disabledAfter", "weekStartsOn", "defaultMonth", "selectedDay", "status", "validationMessage", "onInputClick", "onInputFocus", "onInputChange", "onInputBlur", "onButtonClick", "onDayChange", "onValidate"]);
|
|
59
|
+
restDateInputFieldProps = tslib.__rest(_a, ["id", "inputRef", "buttonRef", "locale", "disabledDates", "disabledDaysOfWeek", "disabledRange", "disabledBeforeAfter", "disabledBefore", "disabledAfter", "weekStartsOn", "defaultMonth", "selectedDay", "status", "validationMessage", "onInputClick", "onInputFocus", "onInputChange", "onInputBlur", "onButtonClick", "onDayChange", "onValidate"]);
|
|
58
60
|
var formatMessage = i18nReactIntl.useIntl().formatMessage;
|
|
59
61
|
var calendarLabelDesc = formatMessage({
|
|
60
62
|
id: 'datePicker.calendarLabelDescription',
|
|
@@ -64,11 +66,11 @@ var DatePicker = function (_a) {
|
|
|
64
66
|
var reactId = React.useId();
|
|
65
67
|
var id = propsId !== null && propsId !== void 0 ? propsId : reactId;
|
|
66
68
|
var containerRef = React.useRef(null);
|
|
67
|
-
var
|
|
69
|
+
var internalInputRef = React.useRef(null);
|
|
68
70
|
var fallbackButtonRef = React.useRef(null);
|
|
69
71
|
var buttonRef = propsButtonRef !== null && propsButtonRef !== void 0 ? propsButtonRef : fallbackButtonRef;
|
|
70
72
|
var dateInputRefs = React.useRef({
|
|
71
|
-
inputRef:
|
|
73
|
+
inputRef: utils.mergeRefs(internalInputRef, propsInputRef),
|
|
72
74
|
buttonRef: buttonRef
|
|
73
75
|
});
|
|
74
76
|
var _c = React.useState(''),
|
|
@@ -177,7 +179,7 @@ var DatePicker = function (_a) {
|
|
|
177
179
|
var handleReturnFocus = function () {
|
|
178
180
|
var _a, _b;
|
|
179
181
|
if (lastTrigger === 'inputKeydown' || lastTrigger === 'inputFocus') {
|
|
180
|
-
return (_a =
|
|
182
|
+
return (_a = internalInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
181
183
|
}
|
|
182
184
|
(_b = buttonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
|
|
183
185
|
};
|
|
@@ -46,6 +46,7 @@ var TabList = function (props) {
|
|
|
46
46
|
setContainerElement = _f[1];
|
|
47
47
|
var tabListContext = React.useContext(reactAriaComponents.TabListStateContext);
|
|
48
48
|
var selectedKey = tabListContext === null || tabListContext === void 0 ? void 0 : tabListContext.selectedKey;
|
|
49
|
+
var prevSelectedKey = React.useRef(selectedKey);
|
|
49
50
|
React.useEffect(function () {
|
|
50
51
|
if (!isDocumentReady) {
|
|
51
52
|
setIsDocumentReady(true);
|
|
@@ -95,7 +96,13 @@ var TabList = function (props) {
|
|
|
95
96
|
if (!isDocumentReady) {
|
|
96
97
|
return;
|
|
97
98
|
}
|
|
98
|
-
//
|
|
99
|
+
// Only scroll the selected tab into view when the selection actually changes
|
|
100
|
+
// (i.e. user interaction). Skipping the no-op runs avoids scrolling the page
|
|
101
|
+
// on mount when the Tabs sit below the fold.
|
|
102
|
+
if (prevSelectedKey.current === selectedKey) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
prevSelectedKey.current = selectedKey;
|
|
99
106
|
(_a = containerElement === null || containerElement === void 0 ? void 0 : containerElement.querySelector('[role="tab"][data-selected=true]')) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
100
107
|
block: 'nearest',
|
|
101
108
|
inline: 'center'
|
|
@@ -38,7 +38,8 @@ var TimeFieldComponent = function (_a) {
|
|
|
38
38
|
validationMessage = _a.validationMessage,
|
|
39
39
|
isDisabled = _a.isDisabled,
|
|
40
40
|
classNameOverride = _a.classNameOverride,
|
|
41
|
-
|
|
41
|
+
inputRef = _a.inputRef,
|
|
42
|
+
restProps = tslib.__rest(_a, ["id", "label", "locale", "onChange", "value", "status", "validationMessage", "isDisabled", "classNameOverride", "inputRef"]);
|
|
42
43
|
var reactId = React.useId();
|
|
43
44
|
var id = propsId !== null && propsId !== void 0 ? propsId : reactId;
|
|
44
45
|
var handleOnChange = function (timeValue) {
|
|
@@ -64,14 +65,17 @@ var TimeFieldComponent = function (_a) {
|
|
|
64
65
|
}));
|
|
65
66
|
var hasError = !!validationMessage && status === 'error';
|
|
66
67
|
var descriptionId = hasError ? "".concat(id, "-field-message") : undefined;
|
|
67
|
-
var
|
|
68
|
+
var internalRef = React__default.default.useRef(null);
|
|
68
69
|
var _c = datepicker$1.useTimeField(tslib.__assign(tslib.__assign({}, restProps), {
|
|
69
70
|
label: label,
|
|
70
71
|
isDisabled: isDisabled,
|
|
71
72
|
'aria-describedby': descriptionId
|
|
72
|
-
}), state,
|
|
73
|
+
}), state, internalRef),
|
|
73
74
|
fieldProps = _c.fieldProps,
|
|
74
75
|
labelProps = _c.labelProps;
|
|
76
|
+
var firstEditableIndex = state.segments.findIndex(function (s) {
|
|
77
|
+
return s.isEditable;
|
|
78
|
+
});
|
|
75
79
|
return React__default.default.createElement("div", {
|
|
76
80
|
className: classNameOverride
|
|
77
81
|
}, React__default.default.createElement(Label.Label, tslib.__assign({
|
|
@@ -82,13 +86,14 @@ var TimeFieldComponent = function (_a) {
|
|
|
82
86
|
className: TimeField_module.wrapper
|
|
83
87
|
}, React__default.default.createElement("div", tslib.__assign({}, fieldProps, {
|
|
84
88
|
id: id,
|
|
85
|
-
ref:
|
|
89
|
+
ref: internalRef,
|
|
86
90
|
className: classnames__default.default(TimeField_module.input, state.isDisabled && TimeField_module.isDisabled, status === 'error' && TimeField_module.error)
|
|
87
91
|
}), state.segments.map(function (segment, i) {
|
|
88
92
|
return React__default.default.createElement(TimeSegment.TimeSegment, {
|
|
89
93
|
key: i,
|
|
90
94
|
segment: segment,
|
|
91
95
|
state: state,
|
|
96
|
+
inputRef: i === firstEditableIndex ? inputRef : undefined,
|
|
92
97
|
hasPadding: ![8294, 8297].includes(segment.text.charCodeAt(0))
|
|
93
98
|
});
|
|
94
99
|
}), React__default.default.createElement("div", {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var tslib = require('tslib');
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var datepicker = require('@react-aria/datepicker');
|
|
6
|
+
var utils = require('@react-aria/utils');
|
|
6
7
|
var classnames = require('classnames');
|
|
7
8
|
var generateSegmentDisplayText = require('./utils/generateSegmentDisplayText.cjs');
|
|
8
9
|
var TimeSegment_module = require('./TimeSegment.module.css.cjs');
|
|
@@ -17,9 +18,10 @@ var TimeSegment = function (_a) {
|
|
|
17
18
|
var segment = _a.segment,
|
|
18
19
|
state = _a.state,
|
|
19
20
|
_b = _a.hasPadding,
|
|
20
|
-
hasPadding = _b === void 0 ? true : _b
|
|
21
|
-
|
|
22
|
-
var
|
|
21
|
+
hasPadding = _b === void 0 ? true : _b,
|
|
22
|
+
inputRef = _a.inputRef;
|
|
23
|
+
var internalRef = React__default.default.useRef(null);
|
|
24
|
+
var segmentProps = datepicker.useDateSegment(segment, state, internalRef).segmentProps;
|
|
23
25
|
// Chrome has a bug where `contenteditable` elements receive focus from external clicks.
|
|
24
26
|
// This (in combination with the invisible character ​) creates boundaries
|
|
25
27
|
// for the element.
|
|
@@ -27,7 +29,7 @@ var TimeSegment = function (_a) {
|
|
|
27
29
|
return React__default.default.createElement("span", {
|
|
28
30
|
className: TimeSegment_module.timeSegmentWrapper
|
|
29
31
|
}, "\u200B", React__default.default.createElement("span", tslib.__assign({}, segmentProps, {
|
|
30
|
-
ref:
|
|
32
|
+
ref: utils.mergeRefs(internalRef, inputRef),
|
|
31
33
|
className: classnames__default.default(TimeSegment_module.timeSegment, segment.type === 'literal' && TimeSegment_module.literal, segment.isPlaceholder && TimeSegment_module.placeholder, segment.type === 'dayPeriod' && TimeSegment_module.dayPeriod, hasPadding && TimeSegment_module.hasPadding)
|
|
32
34
|
}), generateSegmentDisplayText.generateSegmentDisplayText(segment)), "\u200B");
|
|
33
35
|
};
|
|
@@ -53,9 +53,13 @@ var renderInitials = function (fullName, alt, size, disableInitials) {
|
|
|
53
53
|
}, isLongName ? (
|
|
54
54
|
/*#__PURE__*/
|
|
55
55
|
// Only called if 3 or more initials, fits text width for long names
|
|
56
|
+
//
|
|
57
|
+
// Ignore Chromatic diffs since the font-size calculation has shown itself to be slightly non-deterministic,
|
|
58
|
+
// causing flaky tests.
|
|
56
59
|
React.createElement(Textfit, {
|
|
57
60
|
mode: "single",
|
|
58
|
-
max: getMaxFontSizePixels(size)
|
|
61
|
+
max: getMaxFontSizePixels(size),
|
|
62
|
+
"data-chromatic": "ignore"
|
|
59
63
|
}, initials)) : getInitials(fullName, size === 'small')));
|
|
60
64
|
};
|
|
61
65
|
/**
|
|
@@ -5,7 +5,6 @@ import { Input } from '../../Input/Input/Input.mjs';
|
|
|
5
5
|
import '../../Input/InputRange/InputRange.mjs';
|
|
6
6
|
import '../../Input/InputSearch/InputSearch.mjs';
|
|
7
7
|
import { Label } from '../../Label/Label.mjs';
|
|
8
|
-
import { isRefObject } from '../../utils/isRefObject.mjs';
|
|
9
8
|
import modules_30be64ed from './DateInput.module.css.mjs';
|
|
10
9
|
const DateInput = /*#__PURE__*/function () {
|
|
11
10
|
const DateInput = /*#__PURE__*/React.forwardRef(function (_a, ref) {
|
|
@@ -25,7 +24,7 @@ const DateInput = /*#__PURE__*/function () {
|
|
|
25
24
|
reversed: isReversed,
|
|
26
25
|
disabled: disabled
|
|
27
26
|
}), /*#__PURE__*/React.createElement(Input, __assign({
|
|
28
|
-
inputRef:
|
|
27
|
+
inputRef: ref,
|
|
29
28
|
id: id,
|
|
30
29
|
type: "text",
|
|
31
30
|
autoComplete: "off",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __rest, __assign } from 'tslib';
|
|
2
2
|
import React, { useId, useRef, useState, useEffect } from 'react';
|
|
3
3
|
import { useIntl } from '@cultureamp/i18n-react-intl';
|
|
4
|
+
import { mergeRefs } from '@react-aria/utils';
|
|
4
5
|
import { FocusOn } from 'react-focus-on';
|
|
5
6
|
import { CalendarSingle } from '../Calendar/CalendarSingle/CalendarSingle.mjs';
|
|
6
7
|
import '../Calendar/CalendarRange/CalendarRange.mjs';
|
|
@@ -26,6 +27,7 @@ import { validateDate } from './utils/validateDate.mjs';
|
|
|
26
27
|
const DatePicker = /*#__PURE__*/function () {
|
|
27
28
|
const DatePicker = function (_a) {
|
|
28
29
|
var propsId = _a.id,
|
|
30
|
+
propsInputRef = _a.inputRef,
|
|
29
31
|
propsButtonRef = _a.buttonRef,
|
|
30
32
|
_b = _a.locale,
|
|
31
33
|
propsLocale = _b === void 0 ? 'en-AU' : _b,
|
|
@@ -47,7 +49,7 @@ const DatePicker = /*#__PURE__*/function () {
|
|
|
47
49
|
onButtonClick = _a.onButtonClick,
|
|
48
50
|
onDayChange = _a.onDayChange,
|
|
49
51
|
onValidate = _a.onValidate,
|
|
50
|
-
restDateInputFieldProps = __rest(_a, ["id", "buttonRef", "locale", "disabledDates", "disabledDaysOfWeek", "disabledRange", "disabledBeforeAfter", "disabledBefore", "disabledAfter", "weekStartsOn", "defaultMonth", "selectedDay", "status", "validationMessage", "onInputClick", "onInputFocus", "onInputChange", "onInputBlur", "onButtonClick", "onDayChange", "onValidate"]);
|
|
52
|
+
restDateInputFieldProps = __rest(_a, ["id", "inputRef", "buttonRef", "locale", "disabledDates", "disabledDaysOfWeek", "disabledRange", "disabledBeforeAfter", "disabledBefore", "disabledAfter", "weekStartsOn", "defaultMonth", "selectedDay", "status", "validationMessage", "onInputClick", "onInputFocus", "onInputChange", "onInputBlur", "onButtonClick", "onDayChange", "onValidate"]);
|
|
51
53
|
var formatMessage = useIntl().formatMessage;
|
|
52
54
|
var calendarLabelDesc = formatMessage({
|
|
53
55
|
id: 'datePicker.calendarLabelDescription',
|
|
@@ -57,11 +59,11 @@ const DatePicker = /*#__PURE__*/function () {
|
|
|
57
59
|
var reactId = useId();
|
|
58
60
|
var id = propsId !== null && propsId !== void 0 ? propsId : reactId;
|
|
59
61
|
var containerRef = useRef(null);
|
|
60
|
-
var
|
|
62
|
+
var internalInputRef = useRef(null);
|
|
61
63
|
var fallbackButtonRef = useRef(null);
|
|
62
64
|
var buttonRef = propsButtonRef !== null && propsButtonRef !== void 0 ? propsButtonRef : fallbackButtonRef;
|
|
63
65
|
var dateInputRefs = useRef({
|
|
64
|
-
inputRef:
|
|
66
|
+
inputRef: mergeRefs(internalInputRef, propsInputRef),
|
|
65
67
|
buttonRef: buttonRef
|
|
66
68
|
});
|
|
67
69
|
var _c = useState(''),
|
|
@@ -170,7 +172,7 @@ const DatePicker = /*#__PURE__*/function () {
|
|
|
170
172
|
var handleReturnFocus = function () {
|
|
171
173
|
var _a, _b;
|
|
172
174
|
if (lastTrigger === 'inputKeydown' || lastTrigger === 'inputFocus') {
|
|
173
|
-
return (_a =
|
|
175
|
+
return (_a = internalInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
174
176
|
}
|
|
175
177
|
(_b = buttonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
|
|
176
178
|
};
|
|
@@ -37,6 +37,7 @@ var TabList = function (props) {
|
|
|
37
37
|
setContainerElement = _f[1];
|
|
38
38
|
var tabListContext = useContext(TabListStateContext);
|
|
39
39
|
var selectedKey = tabListContext === null || tabListContext === void 0 ? void 0 : tabListContext.selectedKey;
|
|
40
|
+
var prevSelectedKey = useRef(selectedKey);
|
|
40
41
|
useEffect(function () {
|
|
41
42
|
if (!isDocumentReady) {
|
|
42
43
|
setIsDocumentReady(true);
|
|
@@ -86,7 +87,13 @@ var TabList = function (props) {
|
|
|
86
87
|
if (!isDocumentReady) {
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
|
-
//
|
|
90
|
+
// Only scroll the selected tab into view when the selection actually changes
|
|
91
|
+
// (i.e. user interaction). Skipping the no-op runs avoids scrolling the page
|
|
92
|
+
// on mount when the Tabs sit below the fold.
|
|
93
|
+
if (prevSelectedKey.current === selectedKey) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
prevSelectedKey.current = selectedKey;
|
|
90
97
|
(_a = containerElement === null || containerElement === void 0 ? void 0 : containerElement.querySelector('[role="tab"][data-selected=true]')) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
91
98
|
block: 'nearest',
|
|
92
99
|
inline: 'center'
|
|
@@ -33,7 +33,8 @@ const TimeFieldComponent = /*#__PURE__*/function () {
|
|
|
33
33
|
validationMessage = _a.validationMessage,
|
|
34
34
|
isDisabled = _a.isDisabled,
|
|
35
35
|
classNameOverride = _a.classNameOverride,
|
|
36
|
-
|
|
36
|
+
inputRef = _a.inputRef,
|
|
37
|
+
restProps = __rest(_a, ["id", "label", "locale", "onChange", "value", "status", "validationMessage", "isDisabled", "classNameOverride", "inputRef"]);
|
|
37
38
|
var reactId = useId();
|
|
38
39
|
var id = propsId !== null && propsId !== void 0 ? propsId : reactId;
|
|
39
40
|
var handleOnChange = function (timeValue) {
|
|
@@ -59,14 +60,17 @@ const TimeFieldComponent = /*#__PURE__*/function () {
|
|
|
59
60
|
}));
|
|
60
61
|
var hasError = !!validationMessage && status === 'error';
|
|
61
62
|
var descriptionId = hasError ? "".concat(id, "-field-message") : undefined;
|
|
62
|
-
var
|
|
63
|
+
var internalRef = React.useRef(null);
|
|
63
64
|
var _c = useTimeField(__assign(__assign({}, restProps), {
|
|
64
65
|
label: label,
|
|
65
66
|
isDisabled: isDisabled,
|
|
66
67
|
'aria-describedby': descriptionId
|
|
67
|
-
}), state,
|
|
68
|
+
}), state, internalRef),
|
|
68
69
|
fieldProps = _c.fieldProps,
|
|
69
70
|
labelProps = _c.labelProps;
|
|
71
|
+
var firstEditableIndex = state.segments.findIndex(function (s) {
|
|
72
|
+
return s.isEditable;
|
|
73
|
+
});
|
|
70
74
|
return /*#__PURE__*/React.createElement("div", {
|
|
71
75
|
className: classNameOverride
|
|
72
76
|
}, /*#__PURE__*/React.createElement(Label, __assign({
|
|
@@ -77,13 +81,14 @@ const TimeFieldComponent = /*#__PURE__*/function () {
|
|
|
77
81
|
className: modules_0db24a3e.wrapper
|
|
78
82
|
}, /*#__PURE__*/React.createElement("div", __assign({}, fieldProps, {
|
|
79
83
|
id: id,
|
|
80
|
-
ref:
|
|
84
|
+
ref: internalRef,
|
|
81
85
|
className: classnames(modules_0db24a3e.input, state.isDisabled && modules_0db24a3e.isDisabled, status === 'error' && modules_0db24a3e.error)
|
|
82
86
|
}), state.segments.map(function (segment, i) {
|
|
83
87
|
return /*#__PURE__*/React.createElement(TimeSegment, {
|
|
84
88
|
key: i,
|
|
85
89
|
segment: segment,
|
|
86
90
|
state: state,
|
|
91
|
+
inputRef: i === firstEditableIndex ? inputRef : undefined,
|
|
87
92
|
hasPadding: ![8294, 8297].includes(segment.text.charCodeAt(0))
|
|
88
93
|
});
|
|
89
94
|
}), /*#__PURE__*/React.createElement("div", {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __assign } from 'tslib';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { useDateSegment } from '@react-aria/datepicker';
|
|
4
|
+
import { mergeRefs } from '@react-aria/utils';
|
|
4
5
|
import classnames from 'classnames';
|
|
5
6
|
import { generateSegmentDisplayText } from './utils/generateSegmentDisplayText.mjs';
|
|
6
7
|
import modules_0ac56f95 from './TimeSegment.module.css.mjs';
|
|
@@ -9,9 +10,10 @@ const TimeSegment = /*#__PURE__*/function () {
|
|
|
9
10
|
var segment = _a.segment,
|
|
10
11
|
state = _a.state,
|
|
11
12
|
_b = _a.hasPadding,
|
|
12
|
-
hasPadding = _b === void 0 ? true : _b
|
|
13
|
-
|
|
14
|
-
var
|
|
13
|
+
hasPadding = _b === void 0 ? true : _b,
|
|
14
|
+
inputRef = _a.inputRef;
|
|
15
|
+
var internalRef = React.useRef(null);
|
|
16
|
+
var segmentProps = useDateSegment(segment, state, internalRef).segmentProps;
|
|
15
17
|
// Chrome has a bug where `contenteditable` elements receive focus from external clicks.
|
|
16
18
|
// This (in combination with the invisible character ​) creates boundaries
|
|
17
19
|
// for the element.
|
|
@@ -19,7 +21,7 @@ const TimeSegment = /*#__PURE__*/function () {
|
|
|
19
21
|
return /*#__PURE__*/React.createElement("span", {
|
|
20
22
|
className: modules_0ac56f95.timeSegmentWrapper
|
|
21
23
|
}, "\u200B", /*#__PURE__*/React.createElement("span", __assign({}, segmentProps, {
|
|
22
|
-
ref:
|
|
24
|
+
ref: mergeRefs(internalRef, inputRef),
|
|
23
25
|
className: classnames(modules_0ac56f95.timeSegment, segment.type === 'literal' && modules_0ac56f95.literal, segment.isPlaceholder && modules_0ac56f95.placeholder, segment.type === 'dayPeriod' && modules_0ac56f95.dayPeriod, hasPadding && modules_0ac56f95.hasPadding)
|
|
24
26
|
}), generateSegmentDisplayText(segment)), "\u200B");
|
|
25
27
|
};
|
|
@@ -7,8 +7,8 @@ export type DateInputWithIconButtonProps = {
|
|
|
7
7
|
onButtonClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
8
8
|
} & Omit<DateInputProps, 'startIconAdornment' | 'endIconAdornment'>;
|
|
9
9
|
export type DateInputWithIconButtonRefs = {
|
|
10
|
-
inputRef?: React.
|
|
11
|
-
buttonRef?: React.
|
|
10
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
11
|
+
buttonRef?: React.Ref<HTMLButtonElement>;
|
|
12
12
|
};
|
|
13
13
|
export declare const DateInputWithIconButton: React.ForwardRefExoticComponent<{
|
|
14
14
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RefObject } from 'react';
|
|
1
|
+
import React, { type RefObject } from 'react';
|
|
2
2
|
import { type CalendarSingleProps, type DisabledDayMatchers } from "../Calendar";
|
|
3
3
|
import { type DateInputFieldProps } from './subcomponents/DateInputField';
|
|
4
4
|
import type { ValidationResponse } from './types';
|
|
@@ -6,6 +6,7 @@ import { type DatePickerSupportedLocales } from './utils/getLocale';
|
|
|
6
6
|
type OmittedDateInputFieldProps = 'onClick' | 'onFocus' | 'onChange' | 'onBlur' | 'onButtonClick' | 'value' | 'locale' | 'id';
|
|
7
7
|
export type DatePickerProps = {
|
|
8
8
|
id?: string;
|
|
9
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
9
10
|
buttonRef?: RefObject<HTMLButtonElement>;
|
|
10
11
|
onInputClick?: DateInputFieldProps['onClick'];
|
|
11
12
|
onInputFocus?: DateInputFieldProps['onFocus'];
|
|
@@ -49,7 +50,7 @@ export type DatePickerProps = {
|
|
|
49
50
|
* {@link https://cultureamp.design/?path=/docs/components-date-controls-datepicker--docs Storybook}
|
|
50
51
|
*/
|
|
51
52
|
export declare const DatePicker: {
|
|
52
|
-
({ id: propsId, buttonRef: propsButtonRef, locale: propsLocale, disabledDates, disabledDaysOfWeek, disabledRange, disabledBeforeAfter, disabledBefore, disabledAfter, weekStartsOn, defaultMonth, selectedDay, status, validationMessage, onInputClick, onInputFocus, onInputChange, onInputBlur, onButtonClick, onDayChange, onValidate, ...restDateInputFieldProps }: DatePickerProps): JSX.Element;
|
|
53
|
+
({ id: propsId, inputRef: propsInputRef, buttonRef: propsButtonRef, locale: propsLocale, disabledDates, disabledDaysOfWeek, disabledRange, disabledBeforeAfter, disabledBefore, disabledAfter, weekStartsOn, defaultMonth, selectedDay, status, validationMessage, onInputClick, onInputFocus, onInputChange, onInputBlur, onButtonClick, onDayChange, onValidate, ...restDateInputFieldProps }: DatePickerProps): JSX.Element;
|
|
53
54
|
displayName: string;
|
|
54
55
|
};
|
|
55
56
|
export {};
|
|
@@ -4,7 +4,7 @@ import { type InputStatus, type InputTypes } from './types';
|
|
|
4
4
|
export type InputType = (typeof InputTypes)[number];
|
|
5
5
|
export type InputStatusType = (typeof InputStatus)[number];
|
|
6
6
|
export type InputProps = {
|
|
7
|
-
inputRef?: React.
|
|
7
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
8
8
|
status?: InputStatusType;
|
|
9
9
|
startIconAdornment?: React.ReactNode;
|
|
10
10
|
endIconAdornment?: React.ReactNode;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { type TextareaHTMLAttributes } from 'react';
|
|
2
2
|
import { type OverrideClassName } from "../types/OverrideClassName";
|
|
3
3
|
export type TextAreaProps = {
|
|
4
|
-
textAreaRef?: React.
|
|
4
|
+
textAreaRef?: React.Ref<HTMLTextAreaElement>;
|
|
5
5
|
status?: 'default' | 'error' | 'caution';
|
|
6
6
|
/**
|
|
7
7
|
* Grows the input height as more content is added
|
|
@@ -17,6 +17,7 @@ export type TimeFieldProps = {
|
|
|
17
17
|
value: ValueType | null;
|
|
18
18
|
status?: StatusType;
|
|
19
19
|
validationMessage?: React.ReactNode;
|
|
20
|
+
inputRef?: React.Ref<HTMLSpanElement>;
|
|
20
21
|
} & OverrideClassName<Omit<TimeFieldStateOptions, OmittedTimeFieldProps>>;
|
|
21
22
|
export declare const TimeField: {
|
|
22
23
|
(props: TimeFieldProps): JSX.Element;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { type DateFieldState, type DateSegment } from '@react-stately/datepicker';
|
|
2
3
|
export type TimeSegmentProps = {
|
|
3
4
|
segment: DateSegment;
|
|
4
5
|
state: DateFieldState;
|
|
5
6
|
hasPadding?: boolean;
|
|
7
|
+
inputRef?: React.Ref<HTMLSpanElement>;
|
|
6
8
|
};
|
|
7
9
|
export declare const TimeSegment: {
|
|
8
|
-
({ segment, state, hasPadding, }: TimeSegmentProps): JSX.Element;
|
|
10
|
+
({ segment, state, hasPadding, inputRef, }: TimeSegmentProps): JSX.Element;
|
|
9
11
|
displayName: string;
|
|
10
12
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaizen/components",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Kaizen component library",
|
|
5
5
|
"author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
|
|
6
6
|
"homepage": "https://cultureamp.design",
|
|
@@ -99,20 +99,20 @@
|
|
|
99
99
|
"@react-stately/select": "^3.6.14",
|
|
100
100
|
"@react-types/shared": "^3.30.0",
|
|
101
101
|
"classnames": "^2.5.1",
|
|
102
|
-
"date-fns": "^4.
|
|
102
|
+
"date-fns": "^4.4.0",
|
|
103
103
|
"lodash.debounce": "^4.0.8",
|
|
104
104
|
"nanobus": "^4.5.0",
|
|
105
105
|
"prosemirror-commands": "^1.7.1",
|
|
106
106
|
"prosemirror-history": "^1.5.0",
|
|
107
107
|
"prosemirror-inputrules": "^1.5.1",
|
|
108
108
|
"prosemirror-keymap": "^1.2.3",
|
|
109
|
-
"prosemirror-model": "^1.25.
|
|
109
|
+
"prosemirror-model": "^1.25.8",
|
|
110
110
|
"prosemirror-schema-basic": "^1.2.4",
|
|
111
111
|
"prosemirror-schema-list": "^1.5.1",
|
|
112
112
|
"prosemirror-state": "^1.4.4",
|
|
113
113
|
"prosemirror-transform": "^1.12.0",
|
|
114
114
|
"prosemirror-utils": "^1.2.2",
|
|
115
|
-
"prosemirror-view": "^1.41.
|
|
115
|
+
"prosemirror-view": "^1.41.9",
|
|
116
116
|
"react-animate-height": "^3.2.4",
|
|
117
117
|
"react-aria": "^3.41.1",
|
|
118
118
|
"react-aria-components": "^1.10.1",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
},
|
|
131
131
|
"devDependencies": {
|
|
132
132
|
"@cultureamp/frontend-apis": "13.3.0",
|
|
133
|
-
"@cultureamp/i18n-react-intl": "^4.1.
|
|
133
|
+
"@cultureamp/i18n-react-intl": "^4.1.5",
|
|
134
134
|
"@cultureamp/package-bundler": "^4.0.1",
|
|
135
135
|
"cssnano": "^7.1.9",
|
|
136
136
|
"@testing-library/dom": "^10.4.1",
|
|
@@ -155,14 +155,14 @@
|
|
|
155
155
|
"react-dom": "^19.2.7",
|
|
156
156
|
"react-highlight": "^0.15.0",
|
|
157
157
|
"react-intl": "^10.1.13",
|
|
158
|
-
"rollup": "^4.
|
|
158
|
+
"rollup": "^4.61.1",
|
|
159
159
|
"sass": "1.79.6",
|
|
160
160
|
"serialize-query-params": "^2.0.4",
|
|
161
161
|
"svgo": "^4.0.1",
|
|
162
162
|
"ts-patch": "^3.3.0",
|
|
163
163
|
"tslib": "^2.8.1",
|
|
164
164
|
"tsx": "^4.22.4",
|
|
165
|
-
"@kaizen/design-tokens": "11.0.
|
|
165
|
+
"@kaizen/design-tokens": "11.0.11"
|
|
166
166
|
},
|
|
167
167
|
"devDependenciesComments": {
|
|
168
168
|
"sass": "Prevent deprecation warnings introduced in 1.80 as we plan to move away from sass",
|
package/src/Avatar/Avatar.tsx
CHANGED
|
@@ -97,7 +97,10 @@ const renderInitials = (
|
|
|
97
97
|
<abbr className={classnames(styles.initials, isLongName && styles.longName)} title={alt}>
|
|
98
98
|
{isLongName ? (
|
|
99
99
|
// Only called if 3 or more initials, fits text width for long names
|
|
100
|
-
|
|
100
|
+
//
|
|
101
|
+
// Ignore Chromatic diffs since the font-size calculation has shown itself to be slightly non-deterministic,
|
|
102
|
+
// causing flaky tests.
|
|
103
|
+
<Textfit mode="single" max={getMaxFontSizePixels(size)} data-chromatic="ignore">
|
|
101
104
|
{initials}
|
|
102
105
|
</Textfit>
|
|
103
106
|
) : (
|
|
@@ -2,7 +2,6 @@ import React from 'react'
|
|
|
2
2
|
import classnames from 'classnames'
|
|
3
3
|
import { Input, type InputProps } from '~components/Input'
|
|
4
4
|
import { Label } from '~components/Label'
|
|
5
|
-
import { isRefObject } from '~components/utils/isRefObject'
|
|
6
5
|
import styles from './DateInput.module.css'
|
|
7
6
|
|
|
8
7
|
type OmittedInputProps = 'reversed' | 'type' | 'inputRef'
|
|
@@ -23,7 +22,7 @@ export const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
|
|
|
23
22
|
disabled={disabled}
|
|
24
23
|
/>
|
|
25
24
|
<Input
|
|
26
|
-
inputRef={
|
|
25
|
+
inputRef={ref}
|
|
27
26
|
id={id}
|
|
28
27
|
type="text"
|
|
29
28
|
autoComplete="off"
|
|
@@ -13,8 +13,8 @@ export type DateInputWithIconButtonProps = {
|
|
|
13
13
|
} & Omit<DateInputProps, 'startIconAdornment' | 'endIconAdornment'>
|
|
14
14
|
|
|
15
15
|
export type DateInputWithIconButtonRefs = {
|
|
16
|
-
inputRef?: React.
|
|
17
|
-
buttonRef?: React.
|
|
16
|
+
inputRef?: React.Ref<HTMLInputElement>
|
|
17
|
+
buttonRef?: React.Ref<HTMLButtonElement>
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const DateInputWithIconButton = React.forwardRef<
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useId, useRef, useState, type RefObject } from 'react'
|
|
2
2
|
import { useIntl } from '@cultureamp/i18n-react-intl'
|
|
3
|
+
import { mergeRefs } from '@react-aria/utils'
|
|
3
4
|
import { type DayEventHandler } from 'react-day-picker'
|
|
4
5
|
import { FocusOn } from 'react-focus-on'
|
|
5
6
|
import {
|
|
@@ -35,6 +36,7 @@ type OmittedDateInputFieldProps =
|
|
|
35
36
|
|
|
36
37
|
export type DatePickerProps = {
|
|
37
38
|
id?: string
|
|
39
|
+
inputRef?: React.Ref<HTMLInputElement>
|
|
38
40
|
buttonRef?: RefObject<HTMLButtonElement>
|
|
39
41
|
onInputClick?: DateInputFieldProps['onClick']
|
|
40
42
|
onInputFocus?: DateInputFieldProps['onFocus']
|
|
@@ -81,6 +83,7 @@ export type DatePickerProps = {
|
|
|
81
83
|
*/
|
|
82
84
|
export const DatePicker = ({
|
|
83
85
|
id: propsId,
|
|
86
|
+
inputRef: propsInputRef,
|
|
84
87
|
buttonRef: propsButtonRef,
|
|
85
88
|
locale: propsLocale = 'en-AU',
|
|
86
89
|
disabledDates,
|
|
@@ -115,11 +118,11 @@ export const DatePicker = ({
|
|
|
115
118
|
const id = propsId ?? reactId
|
|
116
119
|
|
|
117
120
|
const containerRef = useRef<HTMLInputElement>(null)
|
|
118
|
-
const
|
|
121
|
+
const internalInputRef = useRef<HTMLInputElement>(null)
|
|
119
122
|
const fallbackButtonRef = useRef<HTMLButtonElement>(null)
|
|
120
123
|
const buttonRef = propsButtonRef ?? fallbackButtonRef
|
|
121
124
|
const dateInputRefs = useRef({
|
|
122
|
-
inputRef,
|
|
125
|
+
inputRef: mergeRefs<HTMLInputElement>(internalInputRef, propsInputRef),
|
|
123
126
|
buttonRef,
|
|
124
127
|
})
|
|
125
128
|
const [inputValue, setInputValue] = useState<string>('')
|
|
@@ -239,7 +242,7 @@ export const DatePicker = ({
|
|
|
239
242
|
|
|
240
243
|
const handleReturnFocus = (): void => {
|
|
241
244
|
if (lastTrigger === 'inputKeydown' || lastTrigger === 'inputFocus') {
|
|
242
|
-
return
|
|
245
|
+
return internalInputRef.current?.focus()
|
|
243
246
|
}
|
|
244
247
|
buttonRef.current?.focus()
|
|
245
248
|
}
|
|
@@ -8,7 +8,7 @@ export type InputType = (typeof InputTypes)[number]
|
|
|
8
8
|
export type InputStatusType = (typeof InputStatus)[number]
|
|
9
9
|
|
|
10
10
|
export type InputProps = {
|
|
11
|
-
inputRef?: React.
|
|
11
|
+
inputRef?: React.Ref<HTMLInputElement>
|
|
12
12
|
status?: InputStatusType
|
|
13
13
|
startIconAdornment?: React.ReactNode
|
|
14
14
|
endIconAdornment?: React.ReactNode
|
|
@@ -117,6 +117,45 @@ export const ArrowsShowingAndHidingRTL: Story = {
|
|
|
117
117
|
},
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
let scrollIntoViewCalls = 0
|
|
121
|
+
const originalScrollIntoView = HTMLElement.prototype.scrollIntoView
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The selected tab must NOT be scrolled into view on mount (KZN-3363) — only when
|
|
125
|
+
* the selection changes via user interaction. This protects users who render Tabs
|
|
126
|
+
* below the fold from having the page jump on load.
|
|
127
|
+
*/
|
|
128
|
+
export const ScrollsIntoViewOnSelectionOnly: Story = {
|
|
129
|
+
name: 'Scrolls into view on selection only',
|
|
130
|
+
beforeEach: () => {
|
|
131
|
+
scrollIntoViewCalls = 0
|
|
132
|
+
HTMLElement.prototype.scrollIntoView = function (): void {
|
|
133
|
+
scrollIntoViewCalls += 1
|
|
134
|
+
}
|
|
135
|
+
return () => {
|
|
136
|
+
HTMLElement.prototype.scrollIntoView = originalScrollIntoView
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
render: (args) => (
|
|
140
|
+
<div style={{ maxWidth: '500px' }}>
|
|
141
|
+
<Tabs defaultSelectedKey="one" {...args} />
|
|
142
|
+
</div>
|
|
143
|
+
),
|
|
144
|
+
play: async ({ canvasElement, step }) => {
|
|
145
|
+
const canvas = within(canvasElement.parentElement!)
|
|
146
|
+
|
|
147
|
+
await step('No scroll into view on mount', async () => {
|
|
148
|
+
expect(scrollIntoViewCalls).toBe(0)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
await step('Scrolls into view when a tab is selected', async () => {
|
|
152
|
+
const tabFour = await canvas.findByRole('tab', { name: 'Tab 4' })
|
|
153
|
+
await userEvent.click(tabFour)
|
|
154
|
+
await waitFor(() => expect(scrollIntoViewCalls).toBeGreaterThan(0))
|
|
155
|
+
})
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
|
|
120
159
|
export const AsyncLoaded: Story = {
|
|
121
160
|
render: () => {
|
|
122
161
|
const [selectedKey, setSelectedKey] = useState<Key>(0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { type Meta, type StoryObj } from '@storybook/react'
|
|
3
|
-
import { Button } from '~components/
|
|
3
|
+
import { Button } from '~components/Button'
|
|
4
4
|
import { Text } from '~components/Text'
|
|
5
5
|
import { Tab, TabList, TabPanel, Tabs, type Key } from '../index'
|
|
6
6
|
|
|
@@ -82,8 +82,44 @@ export const Controlled: Story = {
|
|
|
82
82
|
<Text variant="body">Content 2</Text>
|
|
83
83
|
</TabPanel>
|
|
84
84
|
</Tabs>
|
|
85
|
-
<Button
|
|
85
|
+
<Button onClick={(): void => setSelectedKey('two')}>Switch to tab 2</Button>
|
|
86
86
|
</>
|
|
87
87
|
)
|
|
88
88
|
},
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* When a tab is selected via user interaction, `TabList` scrolls the selected tab
|
|
93
|
+
* into view within its horizontal strip. It deliberately does *not* do this on
|
|
94
|
+
* mount — so when `Tabs` render below the fold, landing on the page does not scroll
|
|
95
|
+
* the tabs into view or jump the page.
|
|
96
|
+
*
|
|
97
|
+
* This story pushes the `Tabs` below the fold with a full-viewport spacer. On load
|
|
98
|
+
* the page stays at the top. Scroll down and select a tab — only then does the
|
|
99
|
+
* selected tab scroll into view within the strip.
|
|
100
|
+
*/
|
|
101
|
+
export const BelowTheFold: Story = {
|
|
102
|
+
render: () => (
|
|
103
|
+
<div style={{ maxWidth: '760px' }}>
|
|
104
|
+
<div style={{ height: '100dvh' }}>
|
|
105
|
+
<Text variant="body">Scroll down — the tabs are rendered below the fold.</Text>
|
|
106
|
+
</div>
|
|
107
|
+
<Tabs defaultSelectedKey="tab-0">
|
|
108
|
+
<TabList aria-label="Lorem ipsum tabs">
|
|
109
|
+
{Array.from({ length: 8 }, (_, i) => (
|
|
110
|
+
<Tab key={i} id={`tab-${i}`}>
|
|
111
|
+
Lorem {i + 1}
|
|
112
|
+
</Tab>
|
|
113
|
+
))}
|
|
114
|
+
</TabList>
|
|
115
|
+
{Array.from({ length: 8 }, (_, i) => (
|
|
116
|
+
<TabPanel key={i} id={`tab-${i}`} className="p-24">
|
|
117
|
+
<Text variant="body">
|
|
118
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit — panel {i + 1}.
|
|
119
|
+
</Text>
|
|
120
|
+
</TabPanel>
|
|
121
|
+
))}
|
|
122
|
+
</Tabs>
|
|
123
|
+
</div>
|
|
124
|
+
),
|
|
125
|
+
}
|
|
@@ -44,6 +44,7 @@ export const TabList = (props: TabListProps): JSX.Element => {
|
|
|
44
44
|
const [containerElement, setContainerElement] = useState<HTMLElement | null>()
|
|
45
45
|
const tabListContext = useContext(TabListStateContext)
|
|
46
46
|
const selectedKey = tabListContext?.selectedKey
|
|
47
|
+
const prevSelectedKey = useRef(selectedKey)
|
|
47
48
|
|
|
48
49
|
useEffect(() => {
|
|
49
50
|
if (!isDocumentReady) {
|
|
@@ -107,7 +108,14 @@ export const TabList = (props: TabListProps): JSX.Element => {
|
|
|
107
108
|
return
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
//
|
|
111
|
+
// Only scroll the selected tab into view when the selection actually changes
|
|
112
|
+
// (i.e. user interaction). Skipping the no-op runs avoids scrolling the page
|
|
113
|
+
// on mount when the Tabs sit below the fold.
|
|
114
|
+
if (prevSelectedKey.current === selectedKey) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
prevSelectedKey.current = selectedKey
|
|
118
|
+
|
|
111
119
|
containerElement
|
|
112
120
|
?.querySelector('[role="tab"][data-selected=true]')
|
|
113
121
|
?.scrollIntoView({ block: 'nearest', inline: 'center' })
|
|
@@ -4,7 +4,7 @@ import { type OverrideClassName } from '~components/types/OverrideClassName'
|
|
|
4
4
|
import styles from './TextArea.module.css'
|
|
5
5
|
|
|
6
6
|
export type TextAreaProps = {
|
|
7
|
-
textAreaRef?: React.
|
|
7
|
+
textAreaRef?: React.Ref<HTMLTextAreaElement>
|
|
8
8
|
status?: 'default' | 'error' | 'caution'
|
|
9
9
|
/**
|
|
10
10
|
* Grows the input height as more content is added
|
|
@@ -27,6 +27,7 @@ export type TimeFieldProps = {
|
|
|
27
27
|
value: ValueType | null
|
|
28
28
|
status?: StatusType
|
|
29
29
|
validationMessage?: React.ReactNode
|
|
30
|
+
inputRef?: React.Ref<HTMLSpanElement>
|
|
30
31
|
} & OverrideClassName<Omit<TimeFieldStateOptions, OmittedTimeFieldProps>>
|
|
31
32
|
|
|
32
33
|
// This needed to be placed directly below the props because
|
|
@@ -49,6 +50,7 @@ const TimeFieldComponent = ({
|
|
|
49
50
|
validationMessage,
|
|
50
51
|
isDisabled,
|
|
51
52
|
classNameOverride,
|
|
53
|
+
inputRef,
|
|
52
54
|
...restProps
|
|
53
55
|
}: TimeFieldProps): JSX.Element => {
|
|
54
56
|
const reactId = useId()
|
|
@@ -79,7 +81,7 @@ const TimeFieldComponent = ({
|
|
|
79
81
|
const hasError = !!validationMessage && status === 'error'
|
|
80
82
|
const descriptionId = hasError ? `${id}-field-message` : undefined
|
|
81
83
|
|
|
82
|
-
const
|
|
84
|
+
const internalRef = React.useRef<HTMLDivElement>(null)
|
|
83
85
|
const { fieldProps, labelProps } = useTimeField(
|
|
84
86
|
{
|
|
85
87
|
...restProps,
|
|
@@ -88,36 +90,36 @@ const TimeFieldComponent = ({
|
|
|
88
90
|
'aria-describedby': descriptionId,
|
|
89
91
|
},
|
|
90
92
|
state,
|
|
91
|
-
|
|
93
|
+
internalRef,
|
|
92
94
|
)
|
|
95
|
+
const firstEditableIndex = state.segments.findIndex((s) => s.isEditable)
|
|
96
|
+
|
|
93
97
|
return (
|
|
94
98
|
<div className={classNameOverride}>
|
|
95
99
|
<Label disabled={state.isDisabled} {...labelProps} classNameOverride={styles.label}>
|
|
96
100
|
{label}
|
|
97
101
|
</Label>
|
|
98
102
|
<div className={styles.wrapper}>
|
|
99
|
-
{}
|
|
100
103
|
<div
|
|
101
104
|
{...fieldProps}
|
|
102
105
|
id={id}
|
|
103
|
-
ref={
|
|
106
|
+
ref={internalRef}
|
|
104
107
|
className={classnames(
|
|
105
108
|
styles.input,
|
|
106
109
|
state.isDisabled && styles.isDisabled,
|
|
107
110
|
status === 'error' && styles.error,
|
|
108
111
|
)}
|
|
109
112
|
>
|
|
110
|
-
{state.segments.map((segment, i) =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
})}
|
|
113
|
+
{state.segments.map((segment, i) => (
|
|
114
|
+
<TimeSegment
|
|
115
|
+
key={i}
|
|
116
|
+
segment={segment}
|
|
117
|
+
state={state}
|
|
118
|
+
inputRef={i === firstEditableIndex ? inputRef : undefined}
|
|
119
|
+
hasPadding={![8294, 8297].includes(segment.text.charCodeAt(0))}
|
|
120
|
+
// ^react-aria includes these characters to ensure correct RTL behaviour, but we want to avoid these adding random spacing
|
|
121
|
+
/>
|
|
122
|
+
))}
|
|
121
123
|
<div className={styles.focusRing} />
|
|
122
124
|
</div>
|
|
123
125
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { useDateSegment } from '@react-aria/datepicker'
|
|
3
|
+
import { mergeRefs } from '@react-aria/utils'
|
|
3
4
|
import { type DateFieldState, type DateSegment } from '@react-stately/datepicker'
|
|
4
5
|
import classnames from 'classnames'
|
|
5
6
|
import { generateSegmentDisplayText } from './utils/generateSegmentDisplayText'
|
|
@@ -9,15 +10,17 @@ export type TimeSegmentProps = {
|
|
|
9
10
|
segment: DateSegment
|
|
10
11
|
state: DateFieldState
|
|
11
12
|
hasPadding?: boolean
|
|
13
|
+
inputRef?: React.Ref<HTMLSpanElement>
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const TimeSegment = ({
|
|
15
17
|
segment,
|
|
16
18
|
state,
|
|
17
19
|
hasPadding = true,
|
|
20
|
+
inputRef,
|
|
18
21
|
}: TimeSegmentProps): JSX.Element => {
|
|
19
|
-
const
|
|
20
|
-
const { segmentProps } = useDateSegment(segment, state,
|
|
22
|
+
const internalRef = React.useRef<HTMLSpanElement>(null)
|
|
23
|
+
const { segmentProps } = useDateSegment(segment, state, internalRef)
|
|
21
24
|
|
|
22
25
|
// Chrome has a bug where `contenteditable` elements receive focus from external clicks.
|
|
23
26
|
// This (in combination with the invisible character ​) creates boundaries
|
|
@@ -28,7 +31,7 @@ export const TimeSegment = ({
|
|
|
28
31
|
​
|
|
29
32
|
<span
|
|
30
33
|
{...segmentProps}
|
|
31
|
-
ref={
|
|
34
|
+
ref={mergeRefs<HTMLSpanElement>(internalRef, inputRef)}
|
|
32
35
|
className={classnames(
|
|
33
36
|
styles.timeSegment,
|
|
34
37
|
segment.type === 'literal' && styles.literal,
|