baseui 0.0.0-next-96b59fd → 0.0.0-next-ab3501a
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/es/segmented-control/constants.js +13 -0
- package/es/segmented-control/index.js +16 -0
- package/es/segmented-control/segment.js +10 -0
- package/es/segmented-control/segmented-control.js +368 -0
- package/es/segmented-control/stateful-segmented-control.js +58 -0
- package/es/segmented-control/styled-components.js +222 -0
- package/es/segmented-control/types.js +1 -0
- package/es/segmented-control/utils.js +12 -0
- package/es/themes/dark-theme/color-semantic-tokens.js +5 -1
- package/es/themes/light-theme/color-semantic-tokens.js +5 -1
- package/esm/segmented-control/constants.js +13 -0
- package/esm/segmented-control/index.js +16 -0
- package/esm/segmented-control/segment.js +10 -0
- package/esm/segmented-control/segmented-control.js +447 -0
- package/esm/segmented-control/stateful-segmented-control.js +80 -0
- package/esm/segmented-control/styled-components.js +239 -0
- package/esm/segmented-control/types.js +1 -0
- package/esm/segmented-control/utils.js +22 -0
- package/esm/themes/dark-theme/color-semantic-tokens.js +5 -1
- package/esm/themes/light-theme/color-semantic-tokens.js +5 -1
- package/package.json +1 -1
- package/segmented-control/constants.d.ts +7 -0
- package/segmented-control/constants.js +22 -0
- package/segmented-control/constants.js.flow +21 -0
- package/segmented-control/index.d.ts +14 -0
- package/segmented-control/index.js +96 -0
- package/segmented-control/index.js.flow +20 -0
- package/segmented-control/package.json +4 -0
- package/segmented-control/segment.d.ts +2 -0
- package/segmented-control/segment.js +17 -0
- package/segmented-control/segment.js.flow +18 -0
- package/segmented-control/segmented-control.d.ts +3 -0
- package/segmented-control/segmented-control.js +460 -0
- package/segmented-control/segmented-control.js.flow +427 -0
- package/segmented-control/stateful-segmented-control.d.ts +3 -0
- package/segmented-control/stateful-segmented-control.js +89 -0
- package/segmented-control/stateful-segmented-control.js.flow +59 -0
- package/segmented-control/styled-components.d.ts +27 -0
- package/segmented-control/styled-components.js +251 -0
- package/segmented-control/styled-components.js.flow +222 -0
- package/segmented-control/types.d.ts +59 -0
- package/segmented-control/types.js +5 -0
- package/segmented-control/types.js.flow +85 -0
- package/segmented-control/utils.d.ts +7 -0
- package/segmented-control/utils.js +44 -0
- package/segmented-control/utils.js.flow +24 -0
- package/themes/dark-theme/color-semantic-tokens.js +5 -1
- package/themes/dark-theme/color-semantic-tokens.js.flow +4 -0
- package/themes/light-theme/color-semantic-tokens.js +5 -1
- package/themes/light-theme/color-semantic-tokens.js.flow +4 -0
- package/themes/types.d.ts +4 -0
- package/themes/types.js.flow +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) Uber Technologies, Inc.
|
|
3
|
+
|
|
4
|
+
This source code is licensed under the MIT license found in the
|
|
5
|
+
LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
export const FILL = {
|
|
8
|
+
fixed: 'fixed',
|
|
9
|
+
intrinsic: 'intrinsic'
|
|
10
|
+
};
|
|
11
|
+
export const STATE_CHANGE_TYPE = {
|
|
12
|
+
change: 'change'
|
|
13
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) Uber Technologies, Inc.
|
|
3
|
+
|
|
4
|
+
This source code is licensed under the MIT license found in the
|
|
5
|
+
LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
export * from './constants';
|
|
8
|
+
export * from './stateful-segmented-control';
|
|
9
|
+
export * from './styled-components';
|
|
10
|
+
export * from './segment';
|
|
11
|
+
export * from './segmented-control';
|
|
12
|
+
export * from './utils';
|
|
13
|
+
export * from './types';
|
|
14
|
+
/** @deprecated use StatefulSegmentedControlState instead. To be removed in future versions.*/
|
|
15
|
+
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) Uber Technologies, Inc.
|
|
3
|
+
|
|
4
|
+
This source code is licensed under the MIT license found in the
|
|
5
|
+
LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
|
+
export function Segment(props) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Copyright (c) Uber Technologies, Inc.
|
|
5
|
+
|
|
6
|
+
This source code is licensed under the MIT license found in the
|
|
7
|
+
LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* global window */
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import { useUID } from 'react-uid';
|
|
13
|
+
import { useStyletron } from '../styles';
|
|
14
|
+
import { getOverrides } from '../helpers/overrides';
|
|
15
|
+
import { isFocusVisible, forkFocus, forkBlur } from '../utils/focusVisible';
|
|
16
|
+
import { FILL } from './constants';
|
|
17
|
+
import { StyledRoot, StyledSegment, StyledArtworkContainer, StyledActive, StyledSegmentList, StyledLabel, StyledDescription } from './styled-components';
|
|
18
|
+
import { getSegmentId, isRTL } from './utils';
|
|
19
|
+
const KEYBOARD_ACTION = {
|
|
20
|
+
next: 'next',
|
|
21
|
+
previous: 'previous'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getLayoutParams = el => {
|
|
25
|
+
if (!el) {
|
|
26
|
+
return {
|
|
27
|
+
length: 0,
|
|
28
|
+
distance: 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
width
|
|
34
|
+
} = el.getBoundingClientRect();
|
|
35
|
+
width = Math.floor(width); // Note we're using getBoundingClientRect to take into account
|
|
36
|
+
// the borders.
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
length: width,
|
|
40
|
+
distance: el.offsetLeft
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const scrollParentToCentreTarget = targetNode => {
|
|
45
|
+
const {
|
|
46
|
+
x: parentX,
|
|
47
|
+
y: parentY,
|
|
48
|
+
width: parentWidth,
|
|
49
|
+
height: parentHeight
|
|
50
|
+
} = targetNode.parentNode.getBoundingClientRect();
|
|
51
|
+
const {
|
|
52
|
+
x: childX,
|
|
53
|
+
y: childY,
|
|
54
|
+
width: childWidth,
|
|
55
|
+
height: childHeight
|
|
56
|
+
} = targetNode.getBoundingClientRect(); // get the position of the child centre, relative to parent
|
|
57
|
+
|
|
58
|
+
const childCentre = {
|
|
59
|
+
x: childX - parentX + childWidth / 2,
|
|
60
|
+
y: childY - parentY + childHeight / 2
|
|
61
|
+
}; // aim for the centre of the child to be the centre of the parent
|
|
62
|
+
|
|
63
|
+
const {
|
|
64
|
+
scrollLeft,
|
|
65
|
+
scrollTop
|
|
66
|
+
} = targetNode.parentNode;
|
|
67
|
+
const target = {
|
|
68
|
+
x: scrollLeft + childCentre.x - parentWidth / 2,
|
|
69
|
+
y: scrollTop + childCentre.y - parentHeight / 2
|
|
70
|
+
}; // ignore out of bounds, the browser will manage this for us
|
|
71
|
+
|
|
72
|
+
targetNode.parentNode.scroll(target.x, target.y);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function SegmentedControl({
|
|
76
|
+
activeKey = '0',
|
|
77
|
+
disabled = false,
|
|
78
|
+
children,
|
|
79
|
+
fill = FILL.intrinsic,
|
|
80
|
+
activateOnFocus = true,
|
|
81
|
+
onChange,
|
|
82
|
+
overrides = {},
|
|
83
|
+
uid: customUid = null,
|
|
84
|
+
width,
|
|
85
|
+
height
|
|
86
|
+
}) {
|
|
87
|
+
// Create unique id prefix for this segments component
|
|
88
|
+
const generatedUid = useUID();
|
|
89
|
+
const uid = customUid || generatedUid; // Unpack overrides
|
|
90
|
+
|
|
91
|
+
const {
|
|
92
|
+
Root: RootOverrides,
|
|
93
|
+
Active: ActiveOverrides
|
|
94
|
+
} = overrides;
|
|
95
|
+
const [Root, RootProps] = getOverrides(RootOverrides, StyledRoot);
|
|
96
|
+
const [Active, ActiveProps] = getOverrides(ActiveOverrides, StyledActive);
|
|
97
|
+
const [SegmentList, SegmentListProps] = getOverrides(overrides.SegmentList, StyledSegmentList); // Count key updates
|
|
98
|
+
// We disable a few things until after first mount:
|
|
99
|
+
// - the highlight animation, avoiding an initial slide-in
|
|
100
|
+
// - smooth scrolling active segment into view
|
|
101
|
+
|
|
102
|
+
const [keyUpdated, setKeyUpdated] = React.useState(0);
|
|
103
|
+
React.useEffect(() => {
|
|
104
|
+
setKeyUpdated(keyUpdated + 1);
|
|
105
|
+
}, [activeKey]); // Positioning the highlight.
|
|
106
|
+
|
|
107
|
+
const activeSegmentRef = React.useRef();
|
|
108
|
+
const [highlightLayout, setHighlightLayout] = React.useState({
|
|
109
|
+
length: 0,
|
|
110
|
+
distance: 0
|
|
111
|
+
}); // Create a shared, memoized callback for segments to call on resize.
|
|
112
|
+
|
|
113
|
+
const updateHighlight = React.useCallback(() => {
|
|
114
|
+
if (activeSegmentRef.current) {
|
|
115
|
+
setHighlightLayout(getLayoutParams(activeSegmentRef.current));
|
|
116
|
+
}
|
|
117
|
+
}, [activeSegmentRef.current]); // Update highlight on key
|
|
118
|
+
|
|
119
|
+
React.useEffect(updateHighlight, [activeSegmentRef.current]); // Scroll active segment into view when the parent has scrollbar on mount and
|
|
120
|
+
// on key change (smooth scroll). Note, if the active key changes while
|
|
121
|
+
// the segment is not in view, the page will scroll it into view.
|
|
122
|
+
// TODO: replace with custom scrolling logic.
|
|
123
|
+
|
|
124
|
+
React.useEffect(() => {
|
|
125
|
+
// Flow needs this condition pulled out.
|
|
126
|
+
if (activeSegmentRef.current) {
|
|
127
|
+
if ( // @ts-expect-error todo(flow->ts) maybe parentElement?
|
|
128
|
+
activeSegmentRef.current.parentNode.scrollWidth > // @ts-expect-error todo(flow->ts) maybe parentElement?
|
|
129
|
+
activeSegmentRef.current.parentNode.clientWidth) {
|
|
130
|
+
if (keyUpdated > 1) {
|
|
131
|
+
activeSegmentRef.current.scrollIntoView({
|
|
132
|
+
behavior: 'smooth',
|
|
133
|
+
block: 'nearest',
|
|
134
|
+
inline: 'nearest'
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
scrollParentToCentreTarget(activeSegmentRef.current);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [activeSegmentRef.current]); // Collect shared styling props
|
|
142
|
+
|
|
143
|
+
const sharedStylingProps = {
|
|
144
|
+
$fill: fill
|
|
145
|
+
}; // Helper for parsing directional keys
|
|
146
|
+
// TODO(WPT-6473): move to universal keycode aliases
|
|
147
|
+
|
|
148
|
+
const [, theme] = useStyletron();
|
|
149
|
+
const parseKeyDown = React.useCallback(event => {
|
|
150
|
+
if (isRTL(theme.direction)) {
|
|
151
|
+
switch (event.keyCode) {
|
|
152
|
+
case 39:
|
|
153
|
+
return KEYBOARD_ACTION.previous;
|
|
154
|
+
|
|
155
|
+
case 37:
|
|
156
|
+
return KEYBOARD_ACTION.next;
|
|
157
|
+
|
|
158
|
+
default:
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
switch (event.keyCode) {
|
|
163
|
+
case 37:
|
|
164
|
+
return KEYBOARD_ACTION.previous;
|
|
165
|
+
|
|
166
|
+
case 39:
|
|
167
|
+
return KEYBOARD_ACTION.next;
|
|
168
|
+
|
|
169
|
+
default:
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}, [theme.direction]);
|
|
174
|
+
return /*#__PURE__*/React.createElement(Root, _extends({}, sharedStylingProps, RootProps, {
|
|
175
|
+
$width: width,
|
|
176
|
+
$height: height
|
|
177
|
+
}), /*#__PURE__*/React.createElement(SegmentList, _extends({
|
|
178
|
+
"data-baseweb": "segmented-list",
|
|
179
|
+
role: "listbox",
|
|
180
|
+
"aria-label": "segmented control"
|
|
181
|
+
}, SegmentListProps), React.Children.map(children, (child, index) => {
|
|
182
|
+
if (!child) return;
|
|
183
|
+
return /*#__PURE__*/React.createElement(InternalSegment, _extends({
|
|
184
|
+
childKey: child.key,
|
|
185
|
+
childIndex: index,
|
|
186
|
+
activeKey: activeKey,
|
|
187
|
+
activeSegmentRef: activeSegmentRef,
|
|
188
|
+
updateHighlight: updateHighlight,
|
|
189
|
+
parseKeyDown: parseKeyDown,
|
|
190
|
+
activateOnFocus: activateOnFocus,
|
|
191
|
+
uid: uid,
|
|
192
|
+
disabled: disabled,
|
|
193
|
+
sharedStylingProps: sharedStylingProps,
|
|
194
|
+
onChange: onChange
|
|
195
|
+
}, child.props));
|
|
196
|
+
}), /*#__PURE__*/React.createElement(Active, _extends({
|
|
197
|
+
"data-baseweb": "segment-highlight",
|
|
198
|
+
$length: highlightLayout.length,
|
|
199
|
+
$distance: highlightLayout.distance // This avoids the segment sliding in from the side on mount
|
|
200
|
+
,
|
|
201
|
+
$animate: keyUpdated > 1,
|
|
202
|
+
"aria-hidden": "true",
|
|
203
|
+
role: "presentation"
|
|
204
|
+
}, sharedStylingProps, ActiveProps))));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function InternalSegment({
|
|
208
|
+
childKey,
|
|
209
|
+
childIndex,
|
|
210
|
+
activeKey,
|
|
211
|
+
activeSegmentRef,
|
|
212
|
+
updateHighlight,
|
|
213
|
+
parseKeyDown,
|
|
214
|
+
activateOnFocus,
|
|
215
|
+
uid,
|
|
216
|
+
disabled,
|
|
217
|
+
sharedStylingProps,
|
|
218
|
+
onChange,
|
|
219
|
+
...props
|
|
220
|
+
}) {
|
|
221
|
+
const key = childKey || String(childIndex);
|
|
222
|
+
const isActive = key == activeKey;
|
|
223
|
+
const {
|
|
224
|
+
artwork: Artwork,
|
|
225
|
+
overrides = {},
|
|
226
|
+
segmentRef,
|
|
227
|
+
onClick,
|
|
228
|
+
label,
|
|
229
|
+
description,
|
|
230
|
+
...restProps
|
|
231
|
+
} = props; // A way to share our internal activeSegmentRef via the "segmentRef" prop.
|
|
232
|
+
|
|
233
|
+
const ref = React.useRef();
|
|
234
|
+
React.useImperativeHandle(segmentRef, () => {
|
|
235
|
+
return isActive ? activeSegmentRef.current : ref.current;
|
|
236
|
+
}); // Track segment dimensions in a ref after each render
|
|
237
|
+
// This is used to compare params when the resize observer fires
|
|
238
|
+
|
|
239
|
+
const segmentLayoutParams = React.useRef({
|
|
240
|
+
length: 0,
|
|
241
|
+
distance: 0
|
|
242
|
+
});
|
|
243
|
+
React.useEffect(() => {
|
|
244
|
+
segmentLayoutParams.current = getLayoutParams(isActive ? activeSegmentRef.current : ref.current);
|
|
245
|
+
}); // We need to potentially update the active segment highlight when the width or
|
|
246
|
+
// placement changes for a segment so we listen for resize updates in each segment.
|
|
247
|
+
|
|
248
|
+
React.useEffect(() => {
|
|
249
|
+
if (window.ResizeObserver) {
|
|
250
|
+
const observer = new window.ResizeObserver(entries => {
|
|
251
|
+
if (entries[0] && entries[0].target) {
|
|
252
|
+
const segmentLayoutParamsAfterResize = getLayoutParams(entries[0].target);
|
|
253
|
+
|
|
254
|
+
if (segmentLayoutParamsAfterResize.length !== segmentLayoutParams.current.length || segmentLayoutParamsAfterResize.distance !== segmentLayoutParams.current.distance) {
|
|
255
|
+
updateHighlight();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
observer.observe(isActive ? activeSegmentRef.current : ref.current);
|
|
260
|
+
return () => {
|
|
261
|
+
observer.disconnect();
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}, [activeKey]);
|
|
265
|
+
React.useEffect(updateHighlight, [label]); // Collect overrides
|
|
266
|
+
|
|
267
|
+
const {
|
|
268
|
+
Segment: SegmentOverrides,
|
|
269
|
+
ArtworkContainer: ArtworkContainerOverrides,
|
|
270
|
+
Label: LabelOverrides,
|
|
271
|
+
Description: DescriptionOverrides
|
|
272
|
+
} = overrides;
|
|
273
|
+
const [Segment, SegmentProps] = getOverrides(SegmentOverrides, StyledSegment);
|
|
274
|
+
const [ArtworkContainer, ArtworkContainerProps] = getOverrides(ArtworkContainerOverrides, StyledArtworkContainer);
|
|
275
|
+
const [LabelContainer, LabelContainerProps] = getOverrides(LabelOverrides, StyledLabel);
|
|
276
|
+
const [DescriptionContainer, DescriptionContainerProps] = getOverrides(DescriptionOverrides, StyledDescription); // Keyboard focus styling
|
|
277
|
+
|
|
278
|
+
const [focusVisible, setFocusVisible] = React.useState(false);
|
|
279
|
+
const handleFocus = React.useCallback(event => {
|
|
280
|
+
if (isFocusVisible(event)) {
|
|
281
|
+
setFocusVisible(true);
|
|
282
|
+
}
|
|
283
|
+
}, []);
|
|
284
|
+
const handleBlur = React.useCallback( // eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
285
|
+
event => {
|
|
286
|
+
if (focusVisible !== false) {
|
|
287
|
+
setFocusVisible(false);
|
|
288
|
+
}
|
|
289
|
+
}, [focusVisible]); // Keyboard focus management
|
|
290
|
+
// @ts-expect-error todo(flow->ts): deps are required
|
|
291
|
+
|
|
292
|
+
const handleKeyDown = React.useCallback(event => {
|
|
293
|
+
// WAI-ARIA 1.1
|
|
294
|
+
// https://www.w3.org/TR/wai-aria-practices-1.1/#segmentpanel
|
|
295
|
+
// We use directional keys to iterate focus through SegmentedControl.
|
|
296
|
+
// Find all segments eligible for focus
|
|
297
|
+
const availableSegmentedControl = [...event.target.parentNode.childNodes].filter(node => !node.disabled && node.getAttribute('role') === 'option'); // Exit early if there are no other segments available
|
|
298
|
+
|
|
299
|
+
if (availableSegmentedControl.length === 1) return; // Find segment to focus, looping to start/end of list if necessary
|
|
300
|
+
|
|
301
|
+
const currentSegmentIndex = availableSegmentedControl.indexOf(event.target);
|
|
302
|
+
const action = parseKeyDown(event);
|
|
303
|
+
|
|
304
|
+
if (action) {
|
|
305
|
+
let nextSegment;
|
|
306
|
+
|
|
307
|
+
if (action === KEYBOARD_ACTION.previous) {
|
|
308
|
+
if (availableSegmentedControl[currentSegmentIndex - 1]) {
|
|
309
|
+
nextSegment = availableSegmentedControl[currentSegmentIndex - 1];
|
|
310
|
+
} else {
|
|
311
|
+
nextSegment = availableSegmentedControl[availableSegmentedControl.length - 1];
|
|
312
|
+
}
|
|
313
|
+
} else if (action === KEYBOARD_ACTION.next) {
|
|
314
|
+
if (availableSegmentedControl[currentSegmentIndex + 1]) {
|
|
315
|
+
nextSegment = availableSegmentedControl[currentSegmentIndex + 1];
|
|
316
|
+
} else {
|
|
317
|
+
nextSegment = availableSegmentedControl[0];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (nextSegment) {
|
|
322
|
+
// Focus the segment
|
|
323
|
+
nextSegment.focus(); // Optionally activate the segment
|
|
324
|
+
|
|
325
|
+
if (activateOnFocus) {
|
|
326
|
+
nextSegment.click();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
return /*#__PURE__*/React.createElement(Segment, _extends({
|
|
332
|
+
"data-baseweb": "segment",
|
|
333
|
+
key: key,
|
|
334
|
+
id: getSegmentId(uid, key),
|
|
335
|
+
role: "option",
|
|
336
|
+
onKeyDown: handleKeyDown,
|
|
337
|
+
"aria-selected": isActive,
|
|
338
|
+
tabIndex: isActive ? '0' : '-1',
|
|
339
|
+
ref: isActive ? activeSegmentRef : ref,
|
|
340
|
+
disabled: !isActive && disabled,
|
|
341
|
+
type: "button" // so it doesn't trigger a submit when used inside forms
|
|
342
|
+
,
|
|
343
|
+
$focusVisible: focusVisible,
|
|
344
|
+
$isActive: isActive,
|
|
345
|
+
$hasArtwork: !!Artwork,
|
|
346
|
+
$hasLabel: !!label
|
|
347
|
+
}, sharedStylingProps, restProps, SegmentProps, {
|
|
348
|
+
onClick: event => {
|
|
349
|
+
if (typeof onChange === 'function') onChange({
|
|
350
|
+
activeKey: key
|
|
351
|
+
});
|
|
352
|
+
if (typeof onClick === 'function') onClick(event);
|
|
353
|
+
},
|
|
354
|
+
onFocus: forkFocus({ ...restProps,
|
|
355
|
+
...SegmentProps
|
|
356
|
+
}, handleFocus),
|
|
357
|
+
onBlur: forkBlur({ ...restProps,
|
|
358
|
+
...SegmentProps
|
|
359
|
+
}, handleBlur)
|
|
360
|
+
}), Artwork ? /*#__PURE__*/React.createElement(ArtworkContainer, _extends({
|
|
361
|
+
"data-baseweb": "artwork-container"
|
|
362
|
+
}, sharedStylingProps, ArtworkContainerProps), /*#__PURE__*/React.createElement(Artwork, {
|
|
363
|
+
size: 20,
|
|
364
|
+
color: "contentPrimary"
|
|
365
|
+
})) : null, label ? /*#__PURE__*/React.createElement(LabelContainer, _extends({
|
|
366
|
+
$hasArtwork: !!Artwork
|
|
367
|
+
}, LabelContainerProps), label ? label : key) : null, description ? /*#__PURE__*/React.createElement(DescriptionContainer, _extends({}, DescriptionOverrides, DescriptionContainerProps), description) : null);
|
|
368
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Copyright (c) Uber Technologies, Inc.
|
|
5
|
+
|
|
6
|
+
This source code is licensed under the MIT license found in the
|
|
7
|
+
LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
import { SegmentedControl } from './segmented-control';
|
|
11
|
+
import { STATE_CHANGE_TYPE } from './constants';
|
|
12
|
+
|
|
13
|
+
const getInitialState = (children, initialState) => {
|
|
14
|
+
if (initialState && initialState.activeKey) {
|
|
15
|
+
return initialState;
|
|
16
|
+
} else {
|
|
17
|
+
const firstKey = React.Children.map(children, // @ts-expect-error todo(flow->ts) child might be not a ReactElement, theoretically including null
|
|
18
|
+
(child, index) => child.key || String(index))[0];
|
|
19
|
+
return {
|
|
20
|
+
activeKey: firstKey
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const defaultStateReducer = (state, action) => {
|
|
26
|
+
if (action.type === STATE_CHANGE_TYPE.change) {
|
|
27
|
+
return {
|
|
28
|
+
activeKey: action.payload
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return state;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function StatefulSegmentedControl(props) {
|
|
36
|
+
const {
|
|
37
|
+
children,
|
|
38
|
+
initialState,
|
|
39
|
+
stateReducer = defaultStateReducer,
|
|
40
|
+
onChange,
|
|
41
|
+
...restProps
|
|
42
|
+
} = props;
|
|
43
|
+
const [state, dispatch] = React.useReducer(stateReducer, getInitialState(children, initialState));
|
|
44
|
+
const handleChange = React.useCallback(params => {
|
|
45
|
+
const {
|
|
46
|
+
activeKey
|
|
47
|
+
} = params;
|
|
48
|
+
dispatch({
|
|
49
|
+
type: STATE_CHANGE_TYPE.change,
|
|
50
|
+
payload: activeKey
|
|
51
|
+
});
|
|
52
|
+
if (typeof onChange === 'function') onChange(params);
|
|
53
|
+
}, []);
|
|
54
|
+
return /*#__PURE__*/React.createElement(SegmentedControl, _extends({}, restProps, {
|
|
55
|
+
activeKey: state.activeKey,
|
|
56
|
+
onChange: handleChange
|
|
57
|
+
}), children);
|
|
58
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) Uber Technologies, Inc.
|
|
3
|
+
|
|
4
|
+
This source code is licensed under the MIT license found in the
|
|
5
|
+
LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { styled } from '../styles';
|
|
8
|
+
import { FILL } from './constants';
|
|
9
|
+
import { isIntrinsic, isFixed } from './utils';
|
|
10
|
+
export const StyledRoot = styled('div', ({
|
|
11
|
+
$theme,
|
|
12
|
+
$fill = FILL.intrinsic,
|
|
13
|
+
$height = 'fit-content',
|
|
14
|
+
$width = 'fit-content'
|
|
15
|
+
}) => {
|
|
16
|
+
const style = {
|
|
17
|
+
backgroundColor: $theme.colors.backgroundTertiary,
|
|
18
|
+
borderRadius: $theme.sizing.scale500,
|
|
19
|
+
boxSizing: 'border-box',
|
|
20
|
+
isolation: 'isolate',
|
|
21
|
+
padding: $theme.sizing.scale100,
|
|
22
|
+
position: 'relative',
|
|
23
|
+
overflow: 'hidden',
|
|
24
|
+
height: $height,
|
|
25
|
+
width: $width
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (isIntrinsic($fill)) {
|
|
29
|
+
style['::-webkit-scrollbar'] = {
|
|
30
|
+
display: 'none'
|
|
31
|
+
};
|
|
32
|
+
style['-ms-overflow-style'] = 'none';
|
|
33
|
+
style.scrollbarWidth = 'none';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return style;
|
|
37
|
+
});
|
|
38
|
+
StyledRoot.displayName = "StyledRoot";
|
|
39
|
+
StyledRoot.displayName = 'StyledRoot';
|
|
40
|
+
export const StyledSegmentList = styled('div', ({
|
|
41
|
+
$theme
|
|
42
|
+
}) => {
|
|
43
|
+
const style = {
|
|
44
|
+
backgroundColor: $theme.colors.backgroundTertiary,
|
|
45
|
+
boxSizing: 'border-box',
|
|
46
|
+
display: 'flex',
|
|
47
|
+
height: '100%',
|
|
48
|
+
justifyContent: 'space-evenly',
|
|
49
|
+
position: 'relative',
|
|
50
|
+
width: '100%',
|
|
51
|
+
zIndex: -2,
|
|
52
|
+
flexDirection: 'row',
|
|
53
|
+
overflow: 'hidden',
|
|
54
|
+
minHeight: '40px'
|
|
55
|
+
};
|
|
56
|
+
return style;
|
|
57
|
+
});
|
|
58
|
+
StyledSegmentList.displayName = "StyledSegmentList";
|
|
59
|
+
StyledSegmentList.displayName = 'StyledSegmentList';
|
|
60
|
+
export const StyledSegment = styled('button', ({
|
|
61
|
+
$theme,
|
|
62
|
+
$fill = FILL.intrinsic,
|
|
63
|
+
$focusVisible = false,
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
65
|
+
$isActive = false,
|
|
66
|
+
$hasArtwork = false,
|
|
67
|
+
$hasLabel = true
|
|
68
|
+
}) => {
|
|
69
|
+
const style = {
|
|
70
|
+
cursor: 'pointer',
|
|
71
|
+
position: 'relative',
|
|
72
|
+
WebkitAppearance: 'none',
|
|
73
|
+
marginLeft: '0',
|
|
74
|
+
marginRight: '0',
|
|
75
|
+
marginTop: '0',
|
|
76
|
+
marginBottom: '0',
|
|
77
|
+
boxSizing: 'border-box',
|
|
78
|
+
display: 'grid',
|
|
79
|
+
gridAutoRows: 'auto',
|
|
80
|
+
gridTemplateColumns: 'repeat(2, auto)',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
paddingInline: $theme.sizing.scale600,
|
|
83
|
+
paddingBlock: $theme.sizing.scale300,
|
|
84
|
+
borderLeftWidth: 0,
|
|
85
|
+
borderTopWidth: 0,
|
|
86
|
+
borderRightWidth: 0,
|
|
87
|
+
borderBottomWidth: 0,
|
|
88
|
+
borderRadius: $theme.sizing.scale300,
|
|
89
|
+
color: $theme.colors.contentPrimary,
|
|
90
|
+
backgroundColor: 'transparent',
|
|
91
|
+
flexGrow: 1,
|
|
92
|
+
transitionProperty: 'box-shadow',
|
|
93
|
+
transitionDuration: $theme.animation.timing200,
|
|
94
|
+
transitionTimingFunction: $theme.animation.linearCurve,
|
|
95
|
+
outline: 'none',
|
|
96
|
+
outlineOffset: '-3px',
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
overflow: 'hidden',
|
|
99
|
+
':hover': {
|
|
100
|
+
boxShadow: $isActive ? 'none' : $theme.lighting.overlay100,
|
|
101
|
+
zIndex: $isActive ? 'none' : -2
|
|
102
|
+
},
|
|
103
|
+
':disabled': {
|
|
104
|
+
cursor: 'not-allowed',
|
|
105
|
+
color: $theme.colors.contentStateDisabled,
|
|
106
|
+
':hover': {
|
|
107
|
+
boxShadow: 'none'
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
':disabled:hover': {
|
|
111
|
+
background: 'none'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if ($focusVisible) {
|
|
116
|
+
style.outline = `3px solid ${$theme.colors.accent}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (isFixed($fill)) {
|
|
120
|
+
style.flexGrow = 1;
|
|
121
|
+
style.flexBasis = 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if ($hasArtwork === true && $hasLabel === true) {
|
|
125
|
+
style.gridColumnGap = $theme.sizing.scale300;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return style;
|
|
129
|
+
});
|
|
130
|
+
StyledSegment.displayName = "StyledSegment";
|
|
131
|
+
StyledSegment.displayName = 'StyledSegment';
|
|
132
|
+
export const StyledArtworkContainer = styled('div', ({}) => {
|
|
133
|
+
const style = {
|
|
134
|
+
display: 'flex',
|
|
135
|
+
height: '16px',
|
|
136
|
+
width: '16px',
|
|
137
|
+
alignItems: 'center'
|
|
138
|
+
};
|
|
139
|
+
return style;
|
|
140
|
+
});
|
|
141
|
+
StyledArtworkContainer.displayName = "StyledArtworkContainer";
|
|
142
|
+
StyledArtworkContainer.displayName = 'StyledArtworkContainer';
|
|
143
|
+
export const StyledActive = styled('div', ({
|
|
144
|
+
$theme,
|
|
145
|
+
$length = 0,
|
|
146
|
+
$distance = 0,
|
|
147
|
+
$animate = false
|
|
148
|
+
}) => {
|
|
149
|
+
const style = {
|
|
150
|
+
position: 'absolute',
|
|
151
|
+
borderRadius: $theme.sizing.scale300,
|
|
152
|
+
boxSizing: 'border-box',
|
|
153
|
+
height: '100%',
|
|
154
|
+
width: '100%',
|
|
155
|
+
border: `${$theme.sizing.scale0} solid ${$theme.colors.primary}`,
|
|
156
|
+
backgroundColor: $theme.colors.backgroundPrimary,
|
|
157
|
+
zIndex: -1
|
|
158
|
+
};
|
|
159
|
+
style.bottom = '0px';
|
|
160
|
+
style.left = '0px';
|
|
161
|
+
style.width = `${$length}px`;
|
|
162
|
+
style.transform = `translateX(${$distance}px)`;
|
|
163
|
+
|
|
164
|
+
if ($animate) {
|
|
165
|
+
style.transitionProperty = 'all';
|
|
166
|
+
style.transitionDuration = $theme.animation.timing400;
|
|
167
|
+
style.transitionTimingFunction = $theme.animation.easeInOutQuinticCurve;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return style;
|
|
171
|
+
});
|
|
172
|
+
StyledActive.displayName = "StyledActive";
|
|
173
|
+
StyledActive.displayName = 'StyledActive';
|
|
174
|
+
export const StyledSegmentPanel = styled('div', ({
|
|
175
|
+
$theme,
|
|
176
|
+
$pad = true
|
|
177
|
+
}) => {
|
|
178
|
+
const style = {
|
|
179
|
+
flexGrow: 1,
|
|
180
|
+
outline: 'none'
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if ($pad) {
|
|
184
|
+
style.paddingTop = $theme.sizing.scale600;
|
|
185
|
+
style.paddingRight = $theme.sizing.scale600;
|
|
186
|
+
style.paddingBottom = $theme.sizing.scale600;
|
|
187
|
+
style.paddingLeft = $theme.sizing.scale600;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return style;
|
|
191
|
+
});
|
|
192
|
+
StyledSegmentPanel.displayName = "StyledSegmentPanel";
|
|
193
|
+
StyledSegmentPanel.displayName = 'StyledSegmentPanel';
|
|
194
|
+
export const StyledLabel = styled('div', ({
|
|
195
|
+
$theme,
|
|
196
|
+
$hasArtwork
|
|
197
|
+
}) => {
|
|
198
|
+
const style = { ...$theme.typography.LabelSmall,
|
|
199
|
+
overflow: 'hidden',
|
|
200
|
+
whiteSpace: 'nowrap',
|
|
201
|
+
textOverflow: 'ellipsis'
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (!$hasArtwork) {
|
|
205
|
+
style.gridColumn = '1 / 3';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return style;
|
|
209
|
+
});
|
|
210
|
+
StyledLabel.displayName = "StyledLabel";
|
|
211
|
+
export const StyledDescription = styled('div', ({
|
|
212
|
+
$theme
|
|
213
|
+
}) => ({ ...$theme.typography.ParagraphXSmall,
|
|
214
|
+
padding: 0,
|
|
215
|
+
margin: 0,
|
|
216
|
+
gridColumn: '1 / 3',
|
|
217
|
+
gridRow: '2 / 3',
|
|
218
|
+
overflow: 'hidden',
|
|
219
|
+
whiteSpace: 'nowrap',
|
|
220
|
+
textOverflow: 'ellipsis'
|
|
221
|
+
}));
|
|
222
|
+
StyledDescription.displayName = "StyledDescription";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|