uikit-react-public 0.49.5 → 0.50.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/components/Filters/Filter.d.ts +20 -0
- package/dist/components/Filters/FilterPanel.d.ts +16 -0
- package/dist/components/Filters/Filters.context.d.ts +8 -0
- package/dist/components/Filters/Filters.d.ts +30 -0
- package/dist/components/Filters/Filters.stories.d.ts +17 -0
- package/dist/components/Filters/QuickFilter.d.ts +8 -0
- package/dist/components/Filters/__tests__/Filters.test.d.ts +1 -0
- package/dist/components/Filters/index.d.ts +6 -0
- package/dist/components/Overlay/Overlay.d.ts +3 -2
- package/dist/components/Overlay/Overlay.stories.d.ts +14 -1
- package/dist/components/Select/subcomponents/Panel.d.ts +1 -2
- package/dist/components/index.d.ts +2 -0
- package/dist/index.js +6833 -6236
- package/lib/components/Filters/Filter.tsx +327 -0
- package/lib/components/Filters/FilterPanel.tsx +152 -0
- package/lib/components/Filters/Filters.context.ts +12 -0
- package/lib/components/Filters/Filters.mdx +75 -0
- package/lib/components/Filters/Filters.stories.tsx +354 -0
- package/lib/components/Filters/Filters.tsx +316 -0
- package/lib/components/Filters/QuickFilter.tsx +89 -0
- package/lib/components/Filters/__tests__/Filters.test.tsx +230 -0
- package/lib/components/Filters/index.ts +6 -0
- package/lib/components/Overlay/Overlay.tsx +18 -7
- package/lib/components/Overlay/__tests__/Overlay.test.tsx +39 -0
- package/lib/components/Select/Select.mdx +1 -0
- package/lib/components/Select/__tests__/Select.test.tsx +137 -4
- package/lib/components/Select/subcomponents/CustomSelect.tsx +42 -43
- package/lib/components/Select/subcomponents/Panel.tsx +2 -8
- package/lib/components/index.ts +8 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useLayoutEffect,
|
|
6
6
|
useMemo,
|
|
7
7
|
useId,
|
|
8
|
+
useCallback,
|
|
8
9
|
} from 'react';
|
|
9
10
|
import { css, cx } from '@emotion/css';
|
|
10
11
|
import { VisibleField, Panel, CustomOption, FilterInput } from '.';
|
|
@@ -93,23 +94,25 @@ const CustomSelect = <T extends string | number>({
|
|
|
93
94
|
|
|
94
95
|
const internalRef = useRef<HTMLDivElement>(null);
|
|
95
96
|
const effectiveRef = internalRef;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
|
|
98
|
+
// Keep the anchor attached while the newly opened Overlay reads its ref.
|
|
99
|
+
const setRefs = useCallback(
|
|
100
|
+
(node: HTMLDivElement | null) => {
|
|
101
|
+
internalRef.current = node;
|
|
102
|
+
if (typeof ref === 'function') {
|
|
103
|
+
ref(node);
|
|
104
|
+
} else if (
|
|
105
|
+
ref &&
|
|
106
|
+
'current' in
|
|
107
|
+
(ref as React.RefObject<HTMLDivElement | HTMLSelectElement | null>)
|
|
108
|
+
) {
|
|
109
|
+
(
|
|
110
|
+
ref as React.RefObject<HTMLDivElement | HTMLSelectElement | null>
|
|
111
|
+
).current = node;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
[ref]
|
|
115
|
+
);
|
|
113
116
|
|
|
114
117
|
const [theme] = useTheme();
|
|
115
118
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -371,10 +374,6 @@ const CustomSelect = <T extends string | number>({
|
|
|
371
374
|
|
|
372
375
|
const handleClick = (event: React.MouseEvent) => {
|
|
373
376
|
if (disabled) return;
|
|
374
|
-
if (openedViaFocusRef.current) {
|
|
375
|
-
openedViaFocusRef.current = false;
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
377
|
if (!isOpen) {
|
|
379
378
|
openPanel();
|
|
380
379
|
if (filterable && filterInputRef.current) {
|
|
@@ -485,6 +484,20 @@ const CustomSelect = <T extends string | number>({
|
|
|
485
484
|
|
|
486
485
|
if (disabled) return;
|
|
487
486
|
|
|
487
|
+
const isAltGraph = event.getModifierState('AltGraph');
|
|
488
|
+
const isPrintableCharacter =
|
|
489
|
+
event.key.length === 1 &&
|
|
490
|
+
event.key !== ' ' &&
|
|
491
|
+
!event.metaKey &&
|
|
492
|
+
((!event.altKey && !event.ctrlKey) || isAltGraph);
|
|
493
|
+
|
|
494
|
+
if (filterable && !isOpen && isPrintableCharacter) {
|
|
495
|
+
event.preventDefault();
|
|
496
|
+
setFilterText(event.key);
|
|
497
|
+
openPanel();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
488
501
|
if (event.key === 'Enter') {
|
|
489
502
|
if (!isOpen) {
|
|
490
503
|
openPanel();
|
|
@@ -507,7 +520,6 @@ const CustomSelect = <T extends string | number>({
|
|
|
507
520
|
closePanel();
|
|
508
521
|
// Return focus to the combobox so the screen reader stays on the control
|
|
509
522
|
// instead of falling back to the document/page title.
|
|
510
|
-
skipOpenOnFocusRef.current = true;
|
|
511
523
|
effectiveRef.current?.focus();
|
|
512
524
|
return;
|
|
513
525
|
}
|
|
@@ -521,7 +533,6 @@ const CustomSelect = <T extends string | number>({
|
|
|
521
533
|
}
|
|
522
534
|
if (isOpen && event.key === 'Escape') {
|
|
523
535
|
closePanel();
|
|
524
|
-
skipOpenOnFocusRef.current = true;
|
|
525
536
|
event.currentTarget.focus();
|
|
526
537
|
return;
|
|
527
538
|
}
|
|
@@ -572,19 +583,6 @@ const CustomSelect = <T extends string | number>({
|
|
|
572
583
|
}
|
|
573
584
|
};
|
|
574
585
|
|
|
575
|
-
const handleFocus = (event: React.FocusEvent<HTMLDivElement>) => {
|
|
576
|
-
if (disabled) return;
|
|
577
|
-
if (skipOpenOnFocusRef.current) {
|
|
578
|
-
skipOpenOnFocusRef.current = false;
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
const isKeyboardFocus = event.currentTarget.matches(':focus-visible');
|
|
582
|
-
if (filterable && isKeyboardFocus) {
|
|
583
|
-
openedViaFocusRef.current = true;
|
|
584
|
-
openPanel();
|
|
585
|
-
if (filterInputRef.current) filterInputRef.current.focus();
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
586
|
const { md } = theme.typography.body;
|
|
589
587
|
|
|
590
588
|
const baseStyle = css`
|
|
@@ -668,7 +666,6 @@ const CustomSelect = <T extends string | number>({
|
|
|
668
666
|
<div
|
|
669
667
|
onClick={handleClick}
|
|
670
668
|
onKeyDown={handleKeyDown}
|
|
671
|
-
onFocus={handleFocus}
|
|
672
669
|
onBlur={handleBlur}
|
|
673
670
|
tabIndex={disabled || (filterable && isOpen) ? -1 : 0}
|
|
674
671
|
className={style}
|
|
@@ -710,7 +707,7 @@ const CustomSelect = <T extends string | number>({
|
|
|
710
707
|
/>
|
|
711
708
|
)}
|
|
712
709
|
</VisibleField>
|
|
713
|
-
{filterable && selectedOption && (
|
|
710
|
+
{filterable && isOpen && selectedOption && (
|
|
714
711
|
<span
|
|
715
712
|
id={selectionStatusId}
|
|
716
713
|
className={visuallyHiddenStyle}
|
|
@@ -729,12 +726,15 @@ const CustomSelect = <T extends string | number>({
|
|
|
729
726
|
<Overlay
|
|
730
727
|
reference={effectiveRef}
|
|
731
728
|
placement='bottom-start'
|
|
729
|
+
strategy='fixed'
|
|
732
730
|
flip={{ padding: overlayViewportPadding }}
|
|
733
|
-
shift={{
|
|
731
|
+
shift={({ rects }) => ({
|
|
734
732
|
padding: overlayViewportPadding,
|
|
735
|
-
mainAxis:
|
|
736
|
-
|
|
737
|
-
|
|
733
|
+
mainAxis:
|
|
734
|
+
dropdownWidth === 'content' &&
|
|
735
|
+
rects.floating.width > rects.reference.width,
|
|
736
|
+
crossAxis: false,
|
|
737
|
+
})}
|
|
738
738
|
size={{
|
|
739
739
|
matchReferenceWidth: true,
|
|
740
740
|
padding: overlayViewportPadding,
|
|
@@ -747,7 +747,6 @@ const CustomSelect = <T extends string | number>({
|
|
|
747
747
|
referenceWidth={
|
|
748
748
|
overlaySize?.referenceWidth ?? panelReferenceWidth ?? undefined
|
|
749
749
|
}
|
|
750
|
-
availableHeight={overlaySize?.availableHeight}
|
|
751
750
|
maxWidth={panelMaxWidth ?? undefined}
|
|
752
751
|
id={listboxId}
|
|
753
752
|
role='listbox'
|
|
@@ -7,7 +7,6 @@ const NAME = 'ucl-uikit-select__panel';
|
|
|
7
7
|
type PanelProps = React.ComponentPropsWithoutRef<'div'> & {
|
|
8
8
|
dropdownWidth?: SelectDropdownWidth;
|
|
9
9
|
referenceWidth?: number;
|
|
10
|
-
availableHeight?: number;
|
|
11
10
|
maxWidth?: number;
|
|
12
11
|
};
|
|
13
12
|
|
|
@@ -15,7 +14,6 @@ const Panel = ({
|
|
|
15
14
|
className,
|
|
16
15
|
dropdownWidth = 'content',
|
|
17
16
|
referenceWidth,
|
|
18
|
-
availableHeight,
|
|
19
17
|
maxWidth,
|
|
20
18
|
...props
|
|
21
19
|
}: PanelProps) => {
|
|
@@ -32,19 +30,15 @@ const Panel = ({
|
|
|
32
30
|
typeof maxWidth === 'number'
|
|
33
31
|
? `min(${maxWidth}px, calc(100vw - (${theme.margin.m32} * 2)))`
|
|
34
32
|
: `calc(100vw - (${theme.margin.m32} * 2))`;
|
|
35
|
-
const maxHeightValue =
|
|
36
|
-
typeof availableHeight === 'number'
|
|
37
|
-
? `min(400px, ${availableHeight}px)`
|
|
38
|
-
: '400px';
|
|
39
33
|
|
|
40
34
|
const baseStyle = css`
|
|
41
35
|
min-width: ${referenceWidthValue};
|
|
42
36
|
width: fit-content;
|
|
43
37
|
max-width: ${maxWidthValue};
|
|
44
|
-
max-height:
|
|
38
|
+
max-height: min(400px, var(--ucl-overlay-available-height, 400px));
|
|
45
39
|
overflow-y: auto;
|
|
46
40
|
overflow-x: hidden;
|
|
47
|
-
box-sizing:
|
|
41
|
+
box-sizing: border-box;
|
|
48
42
|
padding: ${theme.padding.p8} 0;
|
|
49
43
|
border: ${theme.border.b1} solid ${theme.colour.border.subtle};
|
|
50
44
|
background-color: ${theme.colour.fill.inverse};
|
package/lib/components/index.ts
CHANGED
|
@@ -37,6 +37,14 @@ export type { BlanketProps } from './Blanket';
|
|
|
37
37
|
export { default as Overlay } from './Overlay';
|
|
38
38
|
export type { OverlayProps } from './Overlay';
|
|
39
39
|
|
|
40
|
+
export { default as Filters, Filter, QuickFilter } from './Filters';
|
|
41
|
+
export type {
|
|
42
|
+
FiltersProps,
|
|
43
|
+
FiltersSubComponents,
|
|
44
|
+
FilterProps,
|
|
45
|
+
QuickFilterProps,
|
|
46
|
+
} from './Filters';
|
|
47
|
+
|
|
40
48
|
export { default as Snackbar } from './Snackbar';
|
|
41
49
|
export { Snackbars } from './Snackbar';
|
|
42
50
|
export type {
|