@faststore/ui 2.0.53-alpha.0 → 2.0.56-alpha.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/index.d.ts +0 -6
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/atoms/Slider/styles.scss +137 -0
- package/src/components/molecules/Modal/styles.scss +97 -0
- package/src/components/organisms/PriceRange/styles.scss +36 -0
- package/src/index.ts +0 -9
- package/src/styles/components.scss +3 -0
- package/dist/components/atoms/Slider/Slider.d.ts +0 -67
- package/dist/components/atoms/Slider/Slider.js +0 -48
- package/dist/components/atoms/Slider/Slider.js.map +0 -1
- package/dist/components/atoms/Slider/index.d.ts +0 -2
- package/dist/components/atoms/Slider/index.js +0 -2
- package/dist/components/atoms/Slider/index.js.map +0 -1
- package/dist/components/molecules/Modal/Modal.d.ts +0 -25
- package/dist/components/molecules/Modal/Modal.js +0 -31
- package/dist/components/molecules/Modal/Modal.js.map +0 -1
- package/dist/components/molecules/Modal/ModalContent.d.ts +0 -10
- package/dist/components/molecules/Modal/ModalContent.js +0 -23
- package/dist/components/molecules/Modal/ModalContent.js.map +0 -1
- package/dist/components/molecules/Modal/index.d.ts +0 -2
- package/dist/components/molecules/Modal/index.js +0 -2
- package/dist/components/molecules/Modal/index.js.map +0 -1
- package/dist/components/molecules/Modal/useTrapFocus.d.ts +0 -8
- package/dist/components/molecules/Modal/useTrapFocus.js +0 -76
- package/dist/components/molecules/Modal/useTrapFocus.js.map +0 -1
- package/dist/components/molecules/PriceRange/PriceRange.d.ts +0 -47
- package/dist/components/molecules/PriceRange/PriceRange.js +0 -28
- package/dist/components/molecules/PriceRange/PriceRange.js.map +0 -1
- package/dist/components/molecules/PriceRange/index.d.ts +0 -2
- package/dist/components/molecules/PriceRange/index.js +0 -2
- package/dist/components/molecules/PriceRange/index.js.map +0 -1
- package/src/components/atoms/Slider/Slider.tsx +0 -182
- package/src/components/atoms/Slider/index.ts +0 -2
- package/src/components/molecules/Modal/Modal.tsx +0 -82
- package/src/components/molecules/Modal/ModalContent.tsx +0 -90
- package/src/components/molecules/Modal/index.tsx +0 -2
- package/src/components/molecules/Modal/useTrapFocus.ts +0 -110
- package/src/components/molecules/PriceRange/PriceRange.tsx +0 -108
- package/src/components/molecules/PriceRange/index.ts +0 -2
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { RefObject } from 'react';
|
|
2
|
-
interface TrapFocusParams {
|
|
3
|
-
beforeElementRef: RefObject<HTMLElement>;
|
|
4
|
-
trapFocusRef: RefObject<HTMLElement>;
|
|
5
|
-
afterElementRef: RefObject<HTMLElement>;
|
|
6
|
-
}
|
|
7
|
-
declare const useTrapFocus: ({ trapFocusRef, beforeElementRef, afterElementRef, }: TrapFocusParams) => void;
|
|
8
|
-
export default useTrapFocus;
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { tabbable } from 'tabbable';
|
|
3
|
-
/*
|
|
4
|
-
* Element that will maintain the focus inside trapFocusRef, focus the first element,
|
|
5
|
-
* and focus back on the element that was in focus when useTrapFocus was triggered.
|
|
6
|
-
*
|
|
7
|
-
* Inspired by Reakit useTrapFocus https://github.com/reakit/reakit/blob/a211d94da9f3b683182568a56479b91afb1b85ae/packages/reakit/src/Dialog/__utils/useFocusTrap.ts
|
|
8
|
-
*/
|
|
9
|
-
const useTrapFocus = ({ trapFocusRef, beforeElementRef, afterElementRef, }) => {
|
|
10
|
-
const tabbableNodesRef = useRef();
|
|
11
|
-
const nodeToRestoreRef = useRef(document.hasFocus() ? document.activeElement : null);
|
|
12
|
-
// Focus back on the element that was focused when useTrapFocus is triggered.
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
const nodeToRestore = nodeToRestoreRef.current;
|
|
15
|
-
return () => {
|
|
16
|
-
nodeToRestore?.focus();
|
|
17
|
-
};
|
|
18
|
-
}, [nodeToRestoreRef]);
|
|
19
|
-
// Set focus on first tabbable element
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
if (!trapFocusRef.current) {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (!tabbableNodesRef.current) {
|
|
25
|
-
tabbableNodesRef.current = tabbable(trapFocusRef.current);
|
|
26
|
-
}
|
|
27
|
-
const [firstTabbable] = tabbableNodesRef.current;
|
|
28
|
-
if (!firstTabbable) {
|
|
29
|
-
trapFocusRef.current.focus();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
firstTabbable.focus();
|
|
33
|
-
}, [trapFocusRef]);
|
|
34
|
-
// Handle loop focus. Set keydown and focusin event listeners
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (!trapFocusRef.current ||
|
|
37
|
-
!beforeElementRef.current ||
|
|
38
|
-
!afterElementRef.current) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const beforeElement = beforeElementRef.current;
|
|
42
|
-
const afterElement = afterElementRef.current;
|
|
43
|
-
const trapFocus = trapFocusRef.current;
|
|
44
|
-
const handleLoopFocus = (nativeEvent) => {
|
|
45
|
-
if (!document.hasFocus()) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
tabbableNodesRef.current = tabbable(trapFocusRef.current);
|
|
49
|
-
if (!tabbableNodesRef.current.length) {
|
|
50
|
-
trapFocus.focus();
|
|
51
|
-
}
|
|
52
|
-
/*
|
|
53
|
-
* Handle loop focus from beforeElementRef. This node can only be focused if the user press shift tab.
|
|
54
|
-
* It will focus the last element of the trapFocusRef.
|
|
55
|
-
*/
|
|
56
|
-
if (nativeEvent.target === beforeElement) {
|
|
57
|
-
tabbableNodesRef.current[tabbableNodesRef.current.length - 1]?.focus();
|
|
58
|
-
}
|
|
59
|
-
/*
|
|
60
|
-
* Handle loop focus from afterElementRef. This node can only be focused if the user press tab.
|
|
61
|
-
* It will focus the first element of the trapFocusRef.
|
|
62
|
-
*/
|
|
63
|
-
if (nativeEvent.target === afterElement) {
|
|
64
|
-
tabbableNodesRef.current[0]?.focus();
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
beforeElement?.addEventListener('focusin', handleLoopFocus);
|
|
68
|
-
afterElement?.addEventListener('focusin', handleLoopFocus);
|
|
69
|
-
return () => {
|
|
70
|
-
beforeElement?.removeEventListener('focusin', handleLoopFocus);
|
|
71
|
-
afterElement?.removeEventListener('focusin', handleLoopFocus);
|
|
72
|
-
};
|
|
73
|
-
}, [tabbableNodesRef, afterElementRef, beforeElementRef, trapFocusRef]);
|
|
74
|
-
};
|
|
75
|
-
export default useTrapFocus;
|
|
76
|
-
//# sourceMappingURL=useTrapFocus.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useTrapFocus.js","sourceRoot":"","sources":["../../../../src/components/molecules/Modal/useTrapFocus.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { AriaAttributes } from 'react';
|
|
3
|
-
import type { PriceProps } from '@faststore/components';
|
|
4
|
-
import type { SliderProps } from '../../atoms/Slider';
|
|
5
|
-
export type PriceRangeProps = SliderProps & {
|
|
6
|
-
/**
|
|
7
|
-
* The current use case variant for prices.
|
|
8
|
-
*/
|
|
9
|
-
variant?: PriceProps['variant'];
|
|
10
|
-
/**
|
|
11
|
-
* Formatter function that transforms the raw price value and render the result.
|
|
12
|
-
*/
|
|
13
|
-
formatter: PriceProps['formatter'];
|
|
14
|
-
/**
|
|
15
|
-
* Returns the value of element's class content attribute.
|
|
16
|
-
*/
|
|
17
|
-
className?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Defines a string value that labels the current element.
|
|
20
|
-
*/
|
|
21
|
-
'aria-label'?: AriaAttributes['aria-label'];
|
|
22
|
-
};
|
|
23
|
-
type PriceRangeRefType = {
|
|
24
|
-
setPriceRangeValues: (values: {
|
|
25
|
-
min: number;
|
|
26
|
-
max: number;
|
|
27
|
-
}) => void;
|
|
28
|
-
};
|
|
29
|
-
declare const PriceRange: React.ForwardRefExoticComponent<SliderProps & {
|
|
30
|
-
/**
|
|
31
|
-
* The current use case variant for prices.
|
|
32
|
-
*/
|
|
33
|
-
variant?: PriceProps['variant'];
|
|
34
|
-
/**
|
|
35
|
-
* Formatter function that transforms the raw price value and render the result.
|
|
36
|
-
*/
|
|
37
|
-
formatter: PriceProps['formatter'];
|
|
38
|
-
/**
|
|
39
|
-
* Returns the value of element's class content attribute.
|
|
40
|
-
*/
|
|
41
|
-
className?: string | undefined;
|
|
42
|
-
/**
|
|
43
|
-
* Defines a string value that labels the current element.
|
|
44
|
-
*/
|
|
45
|
-
'aria-label'?: AriaAttributes['aria-label'];
|
|
46
|
-
} & React.RefAttributes<PriceRangeRefType | undefined>>;
|
|
47
|
-
export default PriceRange;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
|
|
2
|
-
import { Price } from '@faststore/components';
|
|
3
|
-
import Slider from '../../atoms/Slider';
|
|
4
|
-
const PriceRange = forwardRef(function PriceRange({ className, formatter, max, min, step, onChange, onEnd, testId = 'store-price-range', variant, 'aria-label': ariaLabel, }, ref) {
|
|
5
|
-
const sliderRef = useRef();
|
|
6
|
-
useImperativeHandle(ref, () => ({
|
|
7
|
-
setPriceRangeValues: (values) => {
|
|
8
|
-
onChange?.(values);
|
|
9
|
-
sliderRef.current?.setSliderValues(values);
|
|
10
|
-
},
|
|
11
|
-
}));
|
|
12
|
-
return (React.createElement("div", { "data-fs-price-range": true, "data-testid": testId, className: className },
|
|
13
|
-
React.createElement(Slider, { ref: sliderRef, min: min, max: max, step: step, onEnd: onEnd, "aria-label": ariaLabel, onChange: (value) => onChange?.(value), minValueLabelComponent: (minValue) => {
|
|
14
|
-
const minPercent = (minValue / max.absolute) * 100;
|
|
15
|
-
return (React.createElement(Price, { value: minValue, variant: variant, formatter: formatter, "data-price-range-value-label": "min", style: {
|
|
16
|
-
position: 'absolute',
|
|
17
|
-
left: `calc(${minPercent}% + (${8 - minPercent * 0.2}px))`,
|
|
18
|
-
} }));
|
|
19
|
-
}, maxValueLabelComponent: (maxValue) => {
|
|
20
|
-
const maxPercent = (maxValue / max.absolute) * 100;
|
|
21
|
-
return (React.createElement(Price, { formatter: formatter, variant: variant, value: maxValue, "data-price-range-value-label": "max", style: {
|
|
22
|
-
position: 'absolute',
|
|
23
|
-
left: `calc(${maxPercent}% + (${8 - maxPercent * 0.2}px))`,
|
|
24
|
-
} }));
|
|
25
|
-
} })));
|
|
26
|
-
});
|
|
27
|
-
export default PriceRange;
|
|
28
|
-
//# sourceMappingURL=PriceRange.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PriceRange.js","sourceRoot":"","sources":["../../../../src/components/molecules/PriceRange/PriceRange.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAGtE,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAA;AAG7C,OAAO,MAAM,MAAM,oBAAoB,CAAA;AA0BvC,MAAM,UAAU,GAAG,UAAU,CAC3B,SAAS,UAAU,CACjB,EACE,SAAS,EACT,SAAS,EACT,GAAG,EACH,GAAG,EACH,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,MAAM,GAAG,mBAAmB,EAC5B,OAAO,EACP,YAAY,EAAE,SAAS,GACxB,EACD,GAAG;IAEH,MAAM,SAAS,GAAG,MAAM,EAEpB,CAAA;IAEJ,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,mBAAmB,EAAE,CAAC,MAAoC,EAAE,EAAE;YAC5D,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAA;YAClB,SAAS,CAAC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,CAAA;QAC5C,CAAC;KACF,CAAC,CAAC,CAAA;IAEH,OAAO,CACL,yEAAsC,MAAM,EAAE,SAAS,EAAE,SAAS;QAChE,oBAAC,MAAM,IACL,GAAG,EAAE,SAAS,EACd,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,gBACA,SAAS,EACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EACtC,sBAAsB,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACnC,MAAM,UAAU,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAA;gBAElD,OAAO,CACL,oBAAC,KAAK,IACJ,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,SAAS,kCACS,KAAK,EAClC,KAAK,EAAE;wBACL,QAAQ,EAAE,UAAU;wBACpB,IAAI,EAAE,QAAQ,UAAU,QAAQ,CAAC,GAAG,UAAU,GAAG,GAAG,MAAM;qBAC3D,GACD,CACH,CAAA;YACH,CAAC,EACD,sBAAsB,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACnC,MAAM,UAAU,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAA;gBAElD,OAAO,CACL,oBAAC,KAAK,IACJ,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,QAAQ,kCACc,KAAK,EAClC,KAAK,EAAE;wBACL,QAAQ,EAAE,UAAU;wBACpB,IAAI,EAAE,QAAQ,UAAU,QAAQ,CAAC,GAAG,UAAU,GAAG,GAAG,MAAM;qBAC3D,GACD,CACH,CAAA;YACH,CAAC,GACD,CACE,CACP,CAAA;AACH,CAAC,CACF,CAAA;AAED,eAAe,UAAU,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/molecules/PriceRange/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This code is inspired by the work of [sandra-lewis](https://codesandbox.io/u/sandra-lewis)
|
|
3
|
-
*/
|
|
4
|
-
import React, {
|
|
5
|
-
useState,
|
|
6
|
-
useMemo,
|
|
7
|
-
useImperativeHandle,
|
|
8
|
-
forwardRef,
|
|
9
|
-
} from 'react'
|
|
10
|
-
import type { ReactNode } from 'react'
|
|
11
|
-
|
|
12
|
-
interface Range {
|
|
13
|
-
absolute: number
|
|
14
|
-
selected: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type SliderProps = {
|
|
18
|
-
/**
|
|
19
|
-
* The minimum value of the slider.
|
|
20
|
-
*/
|
|
21
|
-
min: Range
|
|
22
|
-
/**
|
|
23
|
-
* The maximum value of the slider.
|
|
24
|
-
*/
|
|
25
|
-
max: Range
|
|
26
|
-
/**
|
|
27
|
-
* Specifies the number interval to be used in the inputs.
|
|
28
|
-
*/
|
|
29
|
-
step?: number
|
|
30
|
-
/**
|
|
31
|
-
* ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
|
|
32
|
-
*
|
|
33
|
-
* @default 'store-slider'
|
|
34
|
-
*/
|
|
35
|
-
testId?: string
|
|
36
|
-
/**
|
|
37
|
-
* Callback that fires when the slider value changes.
|
|
38
|
-
*/
|
|
39
|
-
onChange?: (value: { min: number; max: number }) => void
|
|
40
|
-
/**
|
|
41
|
-
* Callback that fires when the slider value ends changing.
|
|
42
|
-
*/
|
|
43
|
-
onEnd?: (value: { min: number; max: number }) => void
|
|
44
|
-
/**
|
|
45
|
-
* A function used to set a human-readable value text based on the slider's current value.
|
|
46
|
-
*/
|
|
47
|
-
getAriaValueText?(value: number, thumb?: 'min' | 'max'): string
|
|
48
|
-
/**
|
|
49
|
-
* Returns the value of element's class content attribute.
|
|
50
|
-
*/
|
|
51
|
-
className?: string
|
|
52
|
-
/**
|
|
53
|
-
* Component that renders min value label above the left thumb.
|
|
54
|
-
*/
|
|
55
|
-
minValueLabelComponent?: (minValue: number) => ReactNode
|
|
56
|
-
/**
|
|
57
|
-
* Component that renders max value label above the right thumb.
|
|
58
|
-
*/
|
|
59
|
-
maxValueLabelComponent?: (maxValue: number) => ReactNode
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
type SliderRefType = {
|
|
63
|
-
setSliderValues: (values: { min: number; max: number }) => void
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const percent = (value: number, min: number, max: number) =>
|
|
67
|
-
Math.round(((value - min) / (max - min)) * 100)
|
|
68
|
-
|
|
69
|
-
const Slider = forwardRef<SliderRefType | undefined, SliderProps>(
|
|
70
|
-
function Slider(
|
|
71
|
-
{
|
|
72
|
-
min,
|
|
73
|
-
max,
|
|
74
|
-
onChange,
|
|
75
|
-
onEnd,
|
|
76
|
-
testId = 'store-slider',
|
|
77
|
-
getAriaValueText,
|
|
78
|
-
className,
|
|
79
|
-
step,
|
|
80
|
-
minValueLabelComponent,
|
|
81
|
-
maxValueLabelComponent,
|
|
82
|
-
},
|
|
83
|
-
ref
|
|
84
|
-
) {
|
|
85
|
-
const widthPercent = useMemo(
|
|
86
|
-
() => (max.absolute - min.absolute) / 100,
|
|
87
|
-
[max.absolute, min.absolute]
|
|
88
|
-
)
|
|
89
|
-
const [minPercent, setMinPercent] = useState(() =>
|
|
90
|
-
percent(min.selected, min.absolute, max.absolute)
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
const [maxPercent, setMaxPercent] = useState(() =>
|
|
94
|
-
percent(max.selected, min.absolute, max.absolute)
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
const [minVal, setMinVal] = useState(() =>
|
|
98
|
-
Math.round(min.absolute + minPercent * widthPercent)
|
|
99
|
-
)
|
|
100
|
-
const [maxVal, setMaxVal] = useState(() =>
|
|
101
|
-
Math.round(min.absolute + maxPercent * widthPercent)
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
useImperativeHandle(ref, () => ({
|
|
105
|
-
setSliderValues: (values: { min: number; max: number }) => {
|
|
106
|
-
const sliderMinValue = Math.min(Number(values.min), maxVal)
|
|
107
|
-
setMinVal(sliderMinValue)
|
|
108
|
-
setMinPercent(percent(sliderMinValue, min.absolute, max.absolute))
|
|
109
|
-
|
|
110
|
-
if (values.max > max.absolute) {
|
|
111
|
-
setMaxVal(max.absolute)
|
|
112
|
-
setMaxPercent(percent(max.absolute, min.absolute, max.absolute))
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const sliderMaxValue = Math.max(Number(values.max), minVal)
|
|
117
|
-
setMaxVal(sliderMaxValue)
|
|
118
|
-
setMaxPercent(percent(sliderMaxValue, min.absolute, max.absolute))
|
|
119
|
-
},
|
|
120
|
-
}))
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<div data-fs-slider data-testid={testId} className={className}>
|
|
124
|
-
<div
|
|
125
|
-
data-slider-range
|
|
126
|
-
style={{
|
|
127
|
-
left: `${minPercent}%`,
|
|
128
|
-
width: `${maxPercent - minPercent}%`,
|
|
129
|
-
}}
|
|
130
|
-
/>
|
|
131
|
-
<input
|
|
132
|
-
type="range"
|
|
133
|
-
min={Math.round(min.absolute)}
|
|
134
|
-
max={Math.round(max.absolute)}
|
|
135
|
-
value={minVal}
|
|
136
|
-
step={step}
|
|
137
|
-
onMouseUp={() => onEnd?.({ min: minVal, max: maxVal })}
|
|
138
|
-
onTouchEnd={() => onEnd?.({ min: minVal, max: maxVal })}
|
|
139
|
-
onChange={(event) => {
|
|
140
|
-
const minValue = Math.min(Number(event.target.value), maxVal)
|
|
141
|
-
|
|
142
|
-
setMinVal(minValue)
|
|
143
|
-
setMinPercent(percent(minValue, min.absolute, max.absolute))
|
|
144
|
-
onChange?.({ min: minValue, max: maxVal })
|
|
145
|
-
}}
|
|
146
|
-
data-slider-thumb="left"
|
|
147
|
-
aria-valuemin={min.absolute}
|
|
148
|
-
aria-valuemax={max.absolute}
|
|
149
|
-
aria-valuenow={minVal}
|
|
150
|
-
aria-label={String(minVal)}
|
|
151
|
-
aria-labelledby={getAriaValueText?.(minVal, 'min')}
|
|
152
|
-
/>
|
|
153
|
-
{minValueLabelComponent && minValueLabelComponent(minVal)}
|
|
154
|
-
<input
|
|
155
|
-
type="range"
|
|
156
|
-
min={Math.round(min.absolute)}
|
|
157
|
-
max={Math.round(max.absolute)}
|
|
158
|
-
value={maxVal}
|
|
159
|
-
step={step}
|
|
160
|
-
onMouseUp={() => onEnd?.({ min: minVal, max: maxVal })}
|
|
161
|
-
onTouchEnd={() => onEnd?.({ min: minVal, max: maxVal })}
|
|
162
|
-
onChange={(event) => {
|
|
163
|
-
const maxValue = Math.max(Number(event.target.value), minVal)
|
|
164
|
-
|
|
165
|
-
setMaxVal(maxValue)
|
|
166
|
-
setMaxPercent(percent(maxValue, min.absolute, max.absolute))
|
|
167
|
-
onChange?.({ min: minVal, max: maxValue })
|
|
168
|
-
}}
|
|
169
|
-
data-slider-thumb="right"
|
|
170
|
-
aria-valuemin={min.absolute}
|
|
171
|
-
aria-valuemax={max.absolute}
|
|
172
|
-
aria-valuenow={maxVal}
|
|
173
|
-
aria-label={String(maxVal)}
|
|
174
|
-
aria-labelledby={getAriaValueText?.(maxVal, 'max')}
|
|
175
|
-
/>
|
|
176
|
-
{maxValueLabelComponent && maxValueLabelComponent(maxVal)}
|
|
177
|
-
</div>
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
export default Slider
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AriaAttributes,
|
|
3
|
-
KeyboardEvent,
|
|
4
|
-
MouseEvent,
|
|
5
|
-
PropsWithChildren,
|
|
6
|
-
} from 'react'
|
|
7
|
-
import React from 'react'
|
|
8
|
-
import { createPortal } from 'react-dom'
|
|
9
|
-
|
|
10
|
-
import { Overlay } from '@faststore/components'
|
|
11
|
-
import type { ModalContentProps } from './ModalContent'
|
|
12
|
-
import ModalContent from './ModalContent'
|
|
13
|
-
|
|
14
|
-
export interface ModalProps extends ModalContentProps {
|
|
15
|
-
/**
|
|
16
|
-
* ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
|
|
17
|
-
*/
|
|
18
|
-
testId?: string
|
|
19
|
-
/**
|
|
20
|
-
* Identifies the element (or elements) that labels the current element.
|
|
21
|
-
* @see aria-labelledby https://www.w3.org/TR/wai-aria-1.1/#aria-labelledby
|
|
22
|
-
*/
|
|
23
|
-
'aria-labelledby'?: AriaAttributes['aria-label']
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* This function is called whenever the user hits "Escape" or clicks outside
|
|
27
|
-
* the dialog.
|
|
28
|
-
*/
|
|
29
|
-
onDismiss?: (event: MouseEvent | KeyboardEvent) => void
|
|
30
|
-
/**
|
|
31
|
-
* Controls whether or not the dialog is open.
|
|
32
|
-
*/
|
|
33
|
-
isOpen: boolean
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/*
|
|
37
|
-
* This component is based on @reach/dialog.
|
|
38
|
-
* https://github.com/reach/reach-ui/blob/main/packages/dialog/src/index.tsx
|
|
39
|
-
* https://reach.tech/dialog
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
const Modal = ({
|
|
43
|
-
isOpen,
|
|
44
|
-
children,
|
|
45
|
-
onDismiss,
|
|
46
|
-
testId = 'store-modal',
|
|
47
|
-
...otherProps
|
|
48
|
-
}: PropsWithChildren<ModalProps>) => {
|
|
49
|
-
const handleBackdropClick = (event: MouseEvent) => {
|
|
50
|
-
if (event.defaultPrevented) {
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
event.stopPropagation()
|
|
55
|
-
onDismiss?.(event)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const handleBackdropKeyDown = (event: KeyboardEvent) => {
|
|
59
|
-
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
event.stopPropagation()
|
|
64
|
-
onDismiss?.(event)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return isOpen
|
|
68
|
-
? createPortal(
|
|
69
|
-
<Overlay
|
|
70
|
-
onClick={handleBackdropClick}
|
|
71
|
-
onKeyDown={handleBackdropKeyDown}
|
|
72
|
-
>
|
|
73
|
-
<ModalContent {...otherProps} testId={testId}>
|
|
74
|
-
{children}
|
|
75
|
-
</ModalContent>
|
|
76
|
-
</Overlay>,
|
|
77
|
-
document.body
|
|
78
|
-
)
|
|
79
|
-
: null
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export default Modal
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DetailedHTMLProps,
|
|
3
|
-
HTMLAttributes,
|
|
4
|
-
MouseEvent,
|
|
5
|
-
RefObject,
|
|
6
|
-
} from 'react'
|
|
7
|
-
import React, { useRef } from 'react'
|
|
8
|
-
|
|
9
|
-
import useTrapFocus from './useTrapFocus'
|
|
10
|
-
|
|
11
|
-
interface ModalContentPureProps
|
|
12
|
-
extends Omit<
|
|
13
|
-
DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
|
|
14
|
-
'ref'
|
|
15
|
-
> {
|
|
16
|
-
beforeElementRef: RefObject<HTMLDivElement>
|
|
17
|
-
trapFocusRef: RefObject<HTMLDivElement>
|
|
18
|
-
afterElementRef: RefObject<HTMLDivElement>
|
|
19
|
-
testId?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const ModalContentPure = ({
|
|
23
|
-
beforeElementRef,
|
|
24
|
-
trapFocusRef,
|
|
25
|
-
afterElementRef,
|
|
26
|
-
testId = 'store-modal-content',
|
|
27
|
-
children,
|
|
28
|
-
...otherProps
|
|
29
|
-
}: ModalContentPureProps) => {
|
|
30
|
-
return (
|
|
31
|
-
<>
|
|
32
|
-
<div
|
|
33
|
-
ref={beforeElementRef}
|
|
34
|
-
data-testid="beforeElement"
|
|
35
|
-
tabIndex={0}
|
|
36
|
-
aria-hidden="true"
|
|
37
|
-
/>
|
|
38
|
-
<div
|
|
39
|
-
data-fs-modal-content
|
|
40
|
-
data-testid={testId}
|
|
41
|
-
ref={trapFocusRef}
|
|
42
|
-
aria-modal="true"
|
|
43
|
-
role="dialog"
|
|
44
|
-
tabIndex={-1}
|
|
45
|
-
{...otherProps}
|
|
46
|
-
>
|
|
47
|
-
{children}
|
|
48
|
-
</div>
|
|
49
|
-
<div
|
|
50
|
-
ref={afterElementRef}
|
|
51
|
-
data-testid="afterElement"
|
|
52
|
-
tabIndex={0}
|
|
53
|
-
aria-hidden="true"
|
|
54
|
-
/>
|
|
55
|
-
</>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export type ModalContentProps = Omit<
|
|
60
|
-
ModalContentPureProps,
|
|
61
|
-
'trapFocusRef' | 'onClick' | 'beforeElementRef' | 'afterElementRef'
|
|
62
|
-
>
|
|
63
|
-
|
|
64
|
-
const ModalContent = ({ children, ...otherProps }: ModalContentProps) => {
|
|
65
|
-
const trapFocusRef = useRef<HTMLDivElement>(null)
|
|
66
|
-
const beforeElementRef = useRef<HTMLDivElement>(null)
|
|
67
|
-
const afterElementRef = useRef<HTMLDivElement>(null)
|
|
68
|
-
|
|
69
|
-
useTrapFocus({
|
|
70
|
-
beforeElementRef,
|
|
71
|
-
trapFocusRef,
|
|
72
|
-
afterElementRef,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<ModalContentPure
|
|
77
|
-
{...otherProps}
|
|
78
|
-
trapFocusRef={trapFocusRef}
|
|
79
|
-
beforeElementRef={beforeElementRef}
|
|
80
|
-
afterElementRef={afterElementRef}
|
|
81
|
-
onClick={(event: MouseEvent) => {
|
|
82
|
-
event.stopPropagation()
|
|
83
|
-
}}
|
|
84
|
-
>
|
|
85
|
-
{children}
|
|
86
|
-
</ModalContentPure>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export default ModalContent
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
|
-
import type { RefObject } from 'react'
|
|
3
|
-
import type { FocusableElement } from 'tabbable'
|
|
4
|
-
import { tabbable } from 'tabbable'
|
|
5
|
-
|
|
6
|
-
interface TrapFocusParams {
|
|
7
|
-
beforeElementRef: RefObject<HTMLElement>
|
|
8
|
-
trapFocusRef: RefObject<HTMLElement>
|
|
9
|
-
afterElementRef: RefObject<HTMLElement>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
* Element that will maintain the focus inside trapFocusRef, focus the first element,
|
|
14
|
-
* and focus back on the element that was in focus when useTrapFocus was triggered.
|
|
15
|
-
*
|
|
16
|
-
* Inspired by Reakit useTrapFocus https://github.com/reakit/reakit/blob/a211d94da9f3b683182568a56479b91afb1b85ae/packages/reakit/src/Dialog/__utils/useFocusTrap.ts
|
|
17
|
-
*/
|
|
18
|
-
const useTrapFocus = ({
|
|
19
|
-
trapFocusRef,
|
|
20
|
-
beforeElementRef,
|
|
21
|
-
afterElementRef,
|
|
22
|
-
}: TrapFocusParams) => {
|
|
23
|
-
const tabbableNodesRef = useRef<FocusableElement[]>()
|
|
24
|
-
const nodeToRestoreRef = useRef<HTMLElement | null>(
|
|
25
|
-
document.hasFocus() ? (document.activeElement as HTMLElement) : null
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
// Focus back on the element that was focused when useTrapFocus is triggered.
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
const nodeToRestore = nodeToRestoreRef.current
|
|
31
|
-
|
|
32
|
-
return () => {
|
|
33
|
-
nodeToRestore?.focus()
|
|
34
|
-
}
|
|
35
|
-
}, [nodeToRestoreRef])
|
|
36
|
-
|
|
37
|
-
// Set focus on first tabbable element
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (!trapFocusRef.current) {
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!tabbableNodesRef.current) {
|
|
44
|
-
tabbableNodesRef.current = tabbable(trapFocusRef.current)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const [firstTabbable] = tabbableNodesRef.current
|
|
48
|
-
|
|
49
|
-
if (!firstTabbable) {
|
|
50
|
-
trapFocusRef.current.focus()
|
|
51
|
-
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
firstTabbable.focus()
|
|
56
|
-
}, [trapFocusRef])
|
|
57
|
-
|
|
58
|
-
// Handle loop focus. Set keydown and focusin event listeners
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
if (
|
|
61
|
-
!trapFocusRef.current ||
|
|
62
|
-
!beforeElementRef.current ||
|
|
63
|
-
!afterElementRef.current
|
|
64
|
-
) {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const beforeElement = beforeElementRef.current
|
|
69
|
-
const afterElement = afterElementRef.current
|
|
70
|
-
const trapFocus = trapFocusRef.current
|
|
71
|
-
|
|
72
|
-
const handleLoopFocus = (nativeEvent: FocusEvent) => {
|
|
73
|
-
if (!document.hasFocus()) {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
tabbableNodesRef.current = tabbable(trapFocusRef.current!)
|
|
78
|
-
|
|
79
|
-
if (!tabbableNodesRef.current.length) {
|
|
80
|
-
trapFocus.focus()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/*
|
|
84
|
-
* Handle loop focus from beforeElementRef. This node can only be focused if the user press shift tab.
|
|
85
|
-
* It will focus the last element of the trapFocusRef.
|
|
86
|
-
*/
|
|
87
|
-
if (nativeEvent.target === beforeElement) {
|
|
88
|
-
tabbableNodesRef.current[tabbableNodesRef.current.length - 1]?.focus()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/*
|
|
92
|
-
* Handle loop focus from afterElementRef. This node can only be focused if the user press tab.
|
|
93
|
-
* It will focus the first element of the trapFocusRef.
|
|
94
|
-
*/
|
|
95
|
-
if (nativeEvent.target === afterElement) {
|
|
96
|
-
tabbableNodesRef.current[0]?.focus()
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
beforeElement?.addEventListener('focusin', handleLoopFocus)
|
|
101
|
-
afterElement?.addEventListener('focusin', handleLoopFocus)
|
|
102
|
-
|
|
103
|
-
return () => {
|
|
104
|
-
beforeElement?.removeEventListener('focusin', handleLoopFocus)
|
|
105
|
-
afterElement?.removeEventListener('focusin', handleLoopFocus)
|
|
106
|
-
}
|
|
107
|
-
}, [tabbableNodesRef, afterElementRef, beforeElementRef, trapFocusRef])
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export default useTrapFocus
|