@faststore/components 2.0.53-alpha.0 → 2.0.54-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.
@@ -0,0 +1,71 @@
1
+ /**
2
+ * This code is inspired by the work of [sandra-lewis](https://codesandbox.io/u/sandra-lewis)
3
+ */
4
+ import React from 'react';
5
+ import type { ReactNode } from 'react';
6
+ interface Range {
7
+ absolute: number;
8
+ selected: number;
9
+ }
10
+ interface RangeLabel {
11
+ min: string | ReactNode;
12
+ max: string | ReactNode;
13
+ }
14
+ export type SliderProps = {
15
+ /**
16
+ * ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
17
+ *
18
+ * @default 'fs-slider'
19
+ */
20
+ testId?: string;
21
+ /**
22
+ * The minimum value of the slider.
23
+ */
24
+ min: Range;
25
+ /**
26
+ * The maximum value of the slider.
27
+ */
28
+ max: Range;
29
+ /**
30
+ * Specifies the number interval to be used in the inputs.
31
+ */
32
+ step?: number;
33
+ /**
34
+ * Specifies the absolute values labels.
35
+ */
36
+ absoluteValuesLabel: RangeLabel;
37
+ /**
38
+ * Callback that fires when the slider value changes.
39
+ */
40
+ onChange?: (value: {
41
+ min: number;
42
+ max: number;
43
+ }) => void;
44
+ /**
45
+ * Callback that fires when the slider value ends changing.
46
+ */
47
+ onEnd?: (value: {
48
+ min: number;
49
+ max: number;
50
+ }) => void;
51
+ /**
52
+ * A function used to set a human-readable value text based on the slider's current value.
53
+ */
54
+ getAriaValueText?(value: number, thumb?: 'min' | 'max'): string;
55
+ /**
56
+ * Component that renders min value label above the left thumb.
57
+ */
58
+ minValueLabelComponent?: (minValue: number) => ReactNode;
59
+ /**
60
+ * Component that renders max value label above the right thumb.
61
+ */
62
+ maxValueLabelComponent?: (maxValue: number) => ReactNode;
63
+ };
64
+ type SliderRefType = {
65
+ setSliderValues: (values: {
66
+ min: number;
67
+ max: number;
68
+ }) => void;
69
+ };
70
+ declare const Slider: React.ForwardRefExoticComponent<SliderProps & React.RefAttributes<SliderRefType | undefined>>;
71
+ export default Slider;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * This code is inspired by the work of [sandra-lewis](https://codesandbox.io/u/sandra-lewis)
3
+ */
4
+ import React, { useState, useMemo, useImperativeHandle, forwardRef, } from 'react';
5
+ const percent = (value, min, max) => Math.round(((value - min) / (max - min)) * 100);
6
+ const Slider = forwardRef(function Slider({ min, max, absoluteValuesLabel, onChange, onEnd, testId = 'fs-slider', getAriaValueText, step, minValueLabelComponent, maxValueLabelComponent, ...otherProps }, ref) {
7
+ const widthPercent = useMemo(() => (max.absolute - min.absolute) / 100, [max.absolute, min.absolute]);
8
+ const [minPercent, setMinPercent] = useState(() => percent(min.selected, min.absolute, max.absolute));
9
+ const [maxPercent, setMaxPercent] = useState(() => percent(max.selected, min.absolute, max.absolute));
10
+ const [minVal, setMinVal] = useState(() => Math.floor(min.absolute + minPercent * widthPercent));
11
+ const [maxVal, setMaxVal] = useState(() => Math.round(min.absolute + maxPercent * widthPercent));
12
+ const percentage = (value) => (value / max.absolute) * 100;
13
+ useImperativeHandle(ref, () => ({
14
+ setSliderValues: (values) => {
15
+ const sliderMinValue = Math.min(Number(values.min), maxVal);
16
+ setMinVal(sliderMinValue);
17
+ setMinPercent(percent(sliderMinValue, min.absolute, max.absolute));
18
+ if (values.max > max.absolute) {
19
+ setMaxVal(max.absolute);
20
+ setMaxPercent(percent(max.absolute, min.absolute, max.absolute));
21
+ return;
22
+ }
23
+ const sliderMaxValue = Math.max(Number(values.max), minVal);
24
+ setMaxVal(sliderMaxValue);
25
+ setMaxPercent(percent(sliderMaxValue, min.absolute, max.absolute));
26
+ },
27
+ }));
28
+ return (React.createElement("div", { "data-fs-slider": true, "data-testid": testId },
29
+ React.createElement("div", { "data-fs-slider-absolute-values": true },
30
+ React.createElement("span", null, absoluteValuesLabel.min),
31
+ React.createElement("span", null, absoluteValuesLabel.max)),
32
+ React.createElement("div", { "data-fs-slider-wrapper": true },
33
+ React.createElement("div", { "data-fs-slider-range": true, style: {
34
+ left: `${minPercent}%`,
35
+ width: `${maxPercent - minPercent}%`,
36
+ } }),
37
+ React.createElement("input", { type: "range", min: Math.floor(min.absolute), max: Math.round(max.absolute), value: minVal, step: step, onMouseUp: () => onEnd?.({ min: minVal, max: maxVal }), onTouchEnd: () => onEnd?.({ min: minVal, max: maxVal }), onChange: (event) => {
38
+ const minValue = Math.min(Number(event.target.value), maxVal);
39
+ setMinVal(minValue);
40
+ setMinPercent(percent(minValue, min.absolute, max.absolute));
41
+ onChange?.({ min: minValue, max: maxVal });
42
+ }, "data-fs-slider-thumb": "left", "aria-valuemin": min.absolute, "aria-valuemax": max.absolute, "aria-valuenow": minVal, "aria-label": String(minVal), "aria-labelledby": getAriaValueText?.(minVal, 'min'), ...otherProps }),
43
+ minValueLabelComponent && (React.createElement("span", { "data-fs-slider-value-label": "min", style: {
44
+ left: `calc(${percentage(minVal)}% + (${8 - percentage(minVal) * 0.2}px))`,
45
+ } }, minValueLabelComponent(minVal))),
46
+ React.createElement("input", { type: "range", min: Math.floor(min.absolute), max: Math.round(max.absolute), value: maxVal, step: step, onMouseUp: () => onEnd?.({ min: minVal, max: maxVal }), onTouchEnd: () => onEnd?.({ min: minVal, max: maxVal }), onChange: (event) => {
47
+ const maxValue = Math.max(Number(event.target.value), minVal);
48
+ setMaxVal(maxValue);
49
+ setMaxPercent(percent(maxValue, min.absolute, max.absolute));
50
+ onChange?.({ min: minVal, max: maxValue });
51
+ }, "data-fs-slider-thumb": "right", "aria-valuemin": min.absolute, "aria-valuemax": max.absolute, "aria-valuenow": maxVal, "aria-label": String(maxVal), "aria-labelledby": getAriaValueText?.(maxVal, 'max') }),
52
+ maxValueLabelComponent && (React.createElement("span", { "data-fs-slider-value-label": "max", style: {
53
+ left: `calc(${percentage(maxVal)}% + (${8 - percentage(maxVal) * 0.2}px))`,
54
+ } }, maxValueLabelComponent(maxVal))))));
55
+ });
56
+ export default Slider;
57
+ //# sourceMappingURL=Slider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Slider.js","sourceRoot":"","sources":["../../../src/atoms/Slider/Slider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { default } from './Slider';
2
+ export type { SliderProps } from './Slider';
@@ -0,0 +1,2 @@
1
+ export { default } from './Slider';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/atoms/Slider/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA"}
package/dist/index.d.ts CHANGED
@@ -25,6 +25,8 @@ export { default as Radio } from './atoms/Radio';
25
25
  export type { RadioProps } from './atoms/Radio';
26
26
  export { default as Select } from './atoms/Select';
27
27
  export type { SelectProps } from './atoms/Select';
28
+ export { default as Slider } from './atoms/Slider';
29
+ export type { SliderProps } from './atoms/Slider';
28
30
  export { default as SROnly } from './atoms/SROnly';
29
31
  export { default as Accordion, AccordionItem, AccordionButton, AccordionPanel, } from './molecules/Accordion';
30
32
  export type { AccordionProps, AccordionItemProps, AccordionButtonProps, AccordionPanelProps, } from './molecules/Accordion';
@@ -65,3 +67,5 @@ export { default as QuantitySelector } from './molecules/QuantitySelector';
65
67
  export type { QuantitySelectorProps } from './molecules/QuantitySelector';
66
68
  export { default as Hero, HeroImage, HeroHeading } from './organisms/Hero';
67
69
  export type { HeroProps, HeroImageProps, HeroHeadingProps, } from './organisms/Hero';
70
+ export { default as PriceRange } from './organisms/PriceRange';
71
+ export type { PriceRangeProps } from './organisms/PriceRange';
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export { default as Overlay } from './atoms/Overlay';
14
14
  export { default as Price } from './atoms/Price';
15
15
  export { default as Radio } from './atoms/Radio';
16
16
  export { default as Select } from './atoms/Select';
17
+ export { default as Slider } from './atoms/Slider';
17
18
  export { default as SROnly } from './atoms/SROnly';
18
19
  // Molecules
19
20
  export { default as Accordion, AccordionItem, AccordionButton, AccordionPanel, } from './molecules/Accordion';
@@ -37,4 +38,5 @@ export { default as ToggleField } from './molecules/ToggleField';
37
38
  export { default as QuantitySelector } from './molecules/QuantitySelector';
38
39
  // Organisms
39
40
  export { default as Hero, HeroImage, HeroHeading } from './organisms/Hero';
41
+ export { default as PriceRange } from './organisms/PriceRange';
40
42
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import type { AriaAttributes } from 'react';
3
+ import type { PriceProps, SliderProps } from '../../index';
4
+ export type PriceRangeProps = Omit<SliderProps, 'absoluteValuesLabel'> & {
5
+ /**
6
+ * The current use case variant for prices.
7
+ */
8
+ variant?: PriceProps['variant'];
9
+ /**
10
+ * Formatter function that transforms the raw price value and render the result.
11
+ */
12
+ formatter: PriceProps['formatter'];
13
+ /**
14
+ * Defines a string value that labels the current element.
15
+ */
16
+ 'aria-label'?: AriaAttributes['aria-label'];
17
+ };
18
+ type PriceRangeRefType = {
19
+ setPriceRangeValues: (values: {
20
+ min: number;
21
+ max: number;
22
+ }) => void;
23
+ };
24
+ declare const PriceRange: React.ForwardRefExoticComponent<Omit<SliderProps, "absoluteValuesLabel"> & {
25
+ /**
26
+ * The current use case variant for prices.
27
+ */
28
+ variant?: PriceProps['variant'];
29
+ /**
30
+ * Formatter function that transforms the raw price value and render the result.
31
+ */
32
+ formatter: PriceProps['formatter'];
33
+ /**
34
+ * Defines a string value that labels the current element.
35
+ */
36
+ 'aria-label'?: AriaAttributes['aria-label'];
37
+ } & React.RefAttributes<PriceRangeRefType | undefined>>;
38
+ export default PriceRange;
@@ -0,0 +1,75 @@
1
+ import React, { useRef, useState, useImperativeHandle, forwardRef } from 'react';
2
+ import { Price, Slider, InputField } from '../../index';
3
+ const PriceRange = forwardRef(function PriceRange({ formatter, max, min, step = 1, onChange, onEnd, testId = 'fs-price-range', variant, 'aria-label': ariaLabel, ...otherProps }, ref) {
4
+ const sliderRef = useRef();
5
+ useImperativeHandle(ref, () => ({
6
+ setPriceRangeValues: (values) => {
7
+ onChange?.(values);
8
+ sliderRef.current?.setSliderValues(values);
9
+ },
10
+ }));
11
+ const inputMinRef = useRef(null);
12
+ const inputMaxRef = useRef(null);
13
+ const [inputMinError, setInputMinError] = useState();
14
+ const [inputMaxError, setInputMaxError] = useState();
15
+ const [priceRange, setPriceRange] = useState({
16
+ min: Math.floor(min.selected),
17
+ max: Math.round(max.selected),
18
+ });
19
+ function onChangePriceRange(value) {
20
+ setInputMinError(undefined);
21
+ setInputMaxError(undefined);
22
+ setPriceRange({ min: value.min, max: value.max });
23
+ if (inputMinRef.current?.value) {
24
+ inputMinRef.current.value = String(value.min);
25
+ }
26
+ if (inputMaxRef.current?.value) {
27
+ inputMaxRef.current.value = String(value.max);
28
+ }
29
+ }
30
+ function onChangeInputMin(value) {
31
+ setInputMinError(undefined);
32
+ if (Number(value) < Math.floor(min.absolute)) {
33
+ return;
34
+ }
35
+ if (Number(value) > Math.floor(priceRange.max)) {
36
+ setInputMinError(`Min price can't be greater than max`);
37
+ }
38
+ setPriceRange({ ...priceRange, min: Number(value) });
39
+ sliderRef.current?.setSliderValues({
40
+ ...priceRange,
41
+ min: Number(value),
42
+ });
43
+ }
44
+ function onChangeInputMax(value) {
45
+ setInputMaxError(undefined);
46
+ if (Number(value) > Math.round(max.absolute)) {
47
+ return;
48
+ }
49
+ if (Number(value) < Math.round(priceRange.min)) {
50
+ setInputMaxError(`Max price can't be smaller than min`);
51
+ }
52
+ setPriceRange({ ...priceRange, max: Number(value) });
53
+ sliderRef.current?.setSliderValues({
54
+ ...priceRange,
55
+ max: Number(value),
56
+ });
57
+ }
58
+ return (React.createElement("div", { "data-fs-price-range": true, "data-testid": testId, ...otherProps },
59
+ React.createElement(Slider, { ref: sliderRef, min: min, max: max, step: step, onEnd: (value) => {
60
+ onEnd?.(value);
61
+ onChangePriceRange(value);
62
+ }, "aria-label": ariaLabel, onChange: (value) => onChange?.(value), absoluteValuesLabel: {
63
+ min: (React.createElement(Price, { value: Math.floor(min.absolute), variant: variant, formatter: formatter })),
64
+ max: (React.createElement(Price, { value: Math.round(max.absolute), variant: variant, formatter: formatter })),
65
+ }, minValueLabelComponent: (minValue) => {
66
+ return (React.createElement(Price, { value: minValue, variant: variant, formatter: formatter }));
67
+ }, maxValueLabelComponent: (maxValue) => {
68
+ return (React.createElement(Price, { value: maxValue, variant: variant, formatter: formatter }));
69
+ } }),
70
+ React.createElement("div", { "data-fs-price-range-inputs": true },
71
+ React.createElement(InputField, { id: "price-range-min", step: step, label: "Min", type: "number", inputMode: "numeric", error: inputMinError, inputRef: inputMinRef, min: Math.floor(min.absolute), max: priceRange.max, value: priceRange.min, onChange: (e) => onChangeInputMin(e.target.value), onBlur: () => !inputMinError && onEnd?.(priceRange) }),
72
+ React.createElement(InputField, { id: "price-range-max", label: "Max", step: step, type: "number", inputMode: "numeric", error: inputMaxError, inputRef: inputMaxRef, max: Math.round(max.absolute), min: priceRange.min, value: priceRange.max, onChange: (e) => onChangeInputMax(e.target.value), onBlur: () => !inputMaxError && onEnd?.(priceRange) }))));
73
+ });
74
+ export default PriceRange;
75
+ //# sourceMappingURL=PriceRange.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PriceRange.js","sourceRoot":"","sources":["../../../src/organisms/PriceRange/PriceRange.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { default } from './PriceRange';
2
+ export type { PriceRangeProps } from './PriceRange';
@@ -0,0 +1,2 @@
1
+ export { default } from './PriceRange';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/organisms/PriceRange/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/components",
3
- "version": "2.0.53-alpha.0",
3
+ "version": "2.0.54-alpha.0",
4
4
  "module": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
6
6
  "author": "Emerson Laurentino @emersonlaurentino",
@@ -30,5 +30,5 @@
30
30
  "node": "16.18.0",
31
31
  "yarn": "1.19.1"
32
32
  },
33
- "gitHead": "a9a211450d2173e08fd8b8dcf4a6da4d78590a71"
33
+ "gitHead": "7173ea0bec153905a6b800d002401b5c27fd3213"
34
34
  }
@@ -0,0 +1,220 @@
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
+ interface RangeLabel {
18
+ min: string | ReactNode
19
+ max: string | ReactNode
20
+ }
21
+
22
+ export type SliderProps = {
23
+ /**
24
+ * ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
25
+ *
26
+ * @default 'fs-slider'
27
+ */
28
+ testId?: string
29
+ /**
30
+ * The minimum value of the slider.
31
+ */
32
+ min: Range
33
+ /**
34
+ * The maximum value of the slider.
35
+ */
36
+ max: Range
37
+ /**
38
+ * Specifies the number interval to be used in the inputs.
39
+ */
40
+ step?: number
41
+ /**
42
+ * Specifies the absolute values labels.
43
+ */
44
+ absoluteValuesLabel: RangeLabel
45
+ /**
46
+ * Callback that fires when the slider value changes.
47
+ */
48
+ onChange?: (value: { min: number; max: number }) => void
49
+ /**
50
+ * Callback that fires when the slider value ends changing.
51
+ */
52
+ onEnd?: (value: { min: number; max: number }) => void
53
+ /**
54
+ * A function used to set a human-readable value text based on the slider's current value.
55
+ */
56
+ getAriaValueText?(value: number, thumb?: 'min' | 'max'): string
57
+ /**
58
+ * Component that renders min value label above the left thumb.
59
+ */
60
+ minValueLabelComponent?: (minValue: number) => ReactNode
61
+ /**
62
+ * Component that renders max value label above the right thumb.
63
+ */
64
+ maxValueLabelComponent?: (maxValue: number) => ReactNode
65
+ }
66
+
67
+ type SliderRefType = {
68
+ setSliderValues: (values: { min: number; max: number }) => void
69
+ }
70
+
71
+ const percent = (value: number, min: number, max: number) =>
72
+ Math.round(((value - min) / (max - min)) * 100)
73
+
74
+ const Slider = forwardRef<SliderRefType | undefined, SliderProps>(
75
+ function Slider(
76
+ {
77
+ min,
78
+ max,
79
+ absoluteValuesLabel,
80
+ onChange,
81
+ onEnd,
82
+ testId = 'fs-slider',
83
+ getAriaValueText,
84
+ step,
85
+ minValueLabelComponent,
86
+ maxValueLabelComponent,
87
+ ...otherProps
88
+ },
89
+ ref
90
+ ) {
91
+ const widthPercent = useMemo(
92
+ () => (max.absolute - min.absolute) / 100,
93
+ [max.absolute, min.absolute]
94
+ )
95
+ const [minPercent, setMinPercent] = useState(() =>
96
+ percent(min.selected, min.absolute, max.absolute)
97
+ )
98
+
99
+ const [maxPercent, setMaxPercent] = useState(() =>
100
+ percent(max.selected, min.absolute, max.absolute)
101
+ )
102
+
103
+ const [minVal, setMinVal] = useState(() =>
104
+ Math.floor(min.absolute + minPercent * widthPercent)
105
+ )
106
+ const [maxVal, setMaxVal] = useState(() =>
107
+ Math.round(min.absolute + maxPercent * widthPercent)
108
+ )
109
+
110
+ const percentage = (value: number) => (value / max.absolute) * 100
111
+
112
+ useImperativeHandle(ref, () => ({
113
+ setSliderValues: (values: { min: number; max: number }) => {
114
+ const sliderMinValue = Math.min(Number(values.min), maxVal)
115
+ setMinVal(sliderMinValue)
116
+ setMinPercent(percent(sliderMinValue, min.absolute, max.absolute))
117
+
118
+ if (values.max > max.absolute) {
119
+ setMaxVal(max.absolute)
120
+ setMaxPercent(percent(max.absolute, min.absolute, max.absolute))
121
+ return
122
+ }
123
+
124
+ const sliderMaxValue = Math.max(Number(values.max), minVal)
125
+ setMaxVal(sliderMaxValue)
126
+ setMaxPercent(percent(sliderMaxValue, min.absolute, max.absolute))
127
+ },
128
+ }))
129
+
130
+ return (
131
+ <div data-fs-slider data-testid={testId}>
132
+ <div data-fs-slider-absolute-values>
133
+ <span>{absoluteValuesLabel.min}</span>
134
+ <span>{absoluteValuesLabel.max}</span>
135
+ </div>
136
+ <div data-fs-slider-wrapper>
137
+ <div
138
+ data-fs-slider-range
139
+ style={{
140
+ left: `${minPercent}%`,
141
+ width: `${maxPercent - minPercent}%`,
142
+ }}
143
+ />
144
+ <input
145
+ type="range"
146
+ min={Math.floor(min.absolute)}
147
+ max={Math.round(max.absolute)}
148
+ value={minVal}
149
+ step={step}
150
+ onMouseUp={() => onEnd?.({ min: minVal, max: maxVal })}
151
+ onTouchEnd={() => onEnd?.({ min: minVal, max: maxVal })}
152
+ onChange={(event) => {
153
+ const minValue = Math.min(Number(event.target.value), maxVal)
154
+
155
+ setMinVal(minValue)
156
+ setMinPercent(percent(minValue, min.absolute, max.absolute))
157
+ onChange?.({ min: minValue, max: maxVal })
158
+ }}
159
+ data-fs-slider-thumb="left"
160
+ aria-valuemin={min.absolute}
161
+ aria-valuemax={max.absolute}
162
+ aria-valuenow={minVal}
163
+ aria-label={String(minVal)}
164
+ aria-labelledby={getAriaValueText?.(minVal, 'min')}
165
+ {...otherProps}
166
+ />
167
+ {minValueLabelComponent && (
168
+ <span
169
+ data-fs-slider-value-label="min"
170
+ style={{
171
+ left: `calc(${percentage(minVal)}% + (${
172
+ 8 - percentage(minVal) * 0.2
173
+ }px))`,
174
+ }}
175
+ >
176
+ {minValueLabelComponent(minVal)}
177
+ </span>
178
+ )}
179
+
180
+ <input
181
+ type="range"
182
+ min={Math.floor(min.absolute)}
183
+ max={Math.round(max.absolute)}
184
+ value={maxVal}
185
+ step={step}
186
+ onMouseUp={() => onEnd?.({ min: minVal, max: maxVal })}
187
+ onTouchEnd={() => onEnd?.({ min: minVal, max: maxVal })}
188
+ onChange={(event) => {
189
+ const maxValue = Math.max(Number(event.target.value), minVal)
190
+
191
+ setMaxVal(maxValue)
192
+ setMaxPercent(percent(maxValue, min.absolute, max.absolute))
193
+ onChange?.({ min: minVal, max: maxValue })
194
+ }}
195
+ data-fs-slider-thumb="right"
196
+ aria-valuemin={min.absolute}
197
+ aria-valuemax={max.absolute}
198
+ aria-valuenow={maxVal}
199
+ aria-label={String(maxVal)}
200
+ aria-labelledby={getAriaValueText?.(maxVal, 'max')}
201
+ />
202
+ {maxValueLabelComponent && (
203
+ <span
204
+ data-fs-slider-value-label="max"
205
+ style={{
206
+ left: `calc(${percentage(maxVal)}% + (${
207
+ 8 - percentage(maxVal) * 0.2
208
+ }px))`,
209
+ }}
210
+ >
211
+ {maxValueLabelComponent(maxVal)}
212
+ </span>
213
+ )}
214
+ </div>
215
+ </div>
216
+ )
217
+ }
218
+ )
219
+
220
+ export default Slider
@@ -0,0 +1,2 @@
1
+ export { default } from './Slider'
2
+ export type { SliderProps } from './Slider'
package/src/index.ts CHANGED
@@ -28,6 +28,8 @@ export { default as Radio } from './atoms/Radio'
28
28
  export type { RadioProps } from './atoms/Radio'
29
29
  export { default as Select } from './atoms/Select'
30
30
  export type { SelectProps } from './atoms/Select'
31
+ export { default as Slider } from './atoms/Slider'
32
+ export type { SliderProps } from './atoms/Slider'
31
33
  export { default as SROnly } from './atoms/SROnly'
32
34
 
33
35
  // Molecules
@@ -124,3 +126,6 @@ export type {
124
126
  HeroImageProps,
125
127
  HeroHeadingProps,
126
128
  } from './organisms/Hero'
129
+
130
+ export { default as PriceRange } from './organisms/PriceRange'
131
+ export type { PriceRangeProps } from './organisms/PriceRange'
@@ -0,0 +1,188 @@
1
+ import React, { useRef, useState, useImperativeHandle, forwardRef } from 'react'
2
+ import type { AriaAttributes } from 'react'
3
+
4
+ import { Price, Slider, InputField } from '../../index'
5
+ import type { PriceProps, SliderProps } from '../../index'
6
+
7
+ export type PriceRangeProps = Omit<SliderProps, 'absoluteValuesLabel'> & {
8
+ /**
9
+ * The current use case variant for prices.
10
+ */
11
+ variant?: PriceProps['variant']
12
+ /**
13
+ * Formatter function that transforms the raw price value and render the result.
14
+ */
15
+ formatter: PriceProps['formatter']
16
+ /**
17
+ * Defines a string value that labels the current element.
18
+ */
19
+ 'aria-label'?: AriaAttributes['aria-label']
20
+ }
21
+
22
+ type PriceRangeRefType = {
23
+ setPriceRangeValues: (values: { min: number; max: number }) => void
24
+ }
25
+
26
+ const PriceRange = forwardRef<PriceRangeRefType | undefined, PriceRangeProps>(
27
+ function PriceRange(
28
+ {
29
+ formatter,
30
+ max,
31
+ min,
32
+ step = 1,
33
+ onChange,
34
+ onEnd,
35
+ testId = 'fs-price-range',
36
+ variant,
37
+ 'aria-label': ariaLabel,
38
+ ...otherProps
39
+ },
40
+ ref
41
+ ) {
42
+ const sliderRef = useRef<{
43
+ setSliderValues: (values: { min: number; max: number }) => void
44
+ }>()
45
+
46
+ useImperativeHandle(ref, () => ({
47
+ setPriceRangeValues: (values: { min: number; max: number }) => {
48
+ onChange?.(values)
49
+ sliderRef.current?.setSliderValues(values)
50
+ },
51
+ }))
52
+
53
+ const inputMinRef = useRef<HTMLInputElement>(null)
54
+ const inputMaxRef = useRef<HTMLInputElement>(null)
55
+
56
+ const [inputMinError, setInputMinError] = useState<string>()
57
+ const [inputMaxError, setInputMaxError] = useState<string>()
58
+ const [priceRange, setPriceRange] = useState<{ min: number; max: number }>({
59
+ min: Math.floor(min.selected),
60
+ max: Math.round(max.selected),
61
+ })
62
+
63
+ function onChangePriceRange(value: { min: number; max: number }) {
64
+ setInputMinError(undefined)
65
+ setInputMaxError(undefined)
66
+ setPriceRange({ min: value.min, max: value.max })
67
+
68
+ if (inputMinRef.current?.value) {
69
+ inputMinRef.current.value = String(value.min)
70
+ }
71
+
72
+ if (inputMaxRef.current?.value) {
73
+ inputMaxRef.current.value = String(value.max)
74
+ }
75
+ }
76
+
77
+ function onChangeInputMin(value: string) {
78
+ setInputMinError(undefined)
79
+
80
+ if (Number(value) < Math.floor(min.absolute)) {
81
+ return
82
+ }
83
+
84
+ if (Number(value) > Math.floor(priceRange.max)) {
85
+ setInputMinError(`Min price can't be greater than max`)
86
+ }
87
+
88
+ setPriceRange({ ...priceRange, min: Number(value) })
89
+ sliderRef.current?.setSliderValues({
90
+ ...priceRange,
91
+ min: Number(value),
92
+ })
93
+ }
94
+
95
+ function onChangeInputMax(value: string) {
96
+ setInputMaxError(undefined)
97
+
98
+ if (Number(value) > Math.round(max.absolute)) {
99
+ return
100
+ }
101
+
102
+ if (Number(value) < Math.round(priceRange.min)) {
103
+ setInputMaxError(`Max price can't be smaller than min`)
104
+ }
105
+
106
+ setPriceRange({ ...priceRange, max: Number(value) })
107
+ sliderRef.current?.setSliderValues({
108
+ ...priceRange,
109
+ max: Number(value),
110
+ })
111
+ }
112
+
113
+ return (
114
+ <div data-fs-price-range data-testid={testId} {...otherProps}>
115
+ <Slider
116
+ ref={sliderRef}
117
+ min={min}
118
+ max={max}
119
+ step={step}
120
+ onEnd={(value) => {
121
+ onEnd?.(value)
122
+ onChangePriceRange(value)
123
+ }}
124
+ aria-label={ariaLabel}
125
+ onChange={(value) => onChange?.(value)}
126
+ absoluteValuesLabel={{
127
+ min: (
128
+ <Price
129
+ value={Math.floor(min.absolute)}
130
+ variant={variant}
131
+ formatter={formatter}
132
+ />
133
+ ),
134
+ max: (
135
+ <Price
136
+ value={Math.round(max.absolute)}
137
+ variant={variant}
138
+ formatter={formatter}
139
+ />
140
+ ),
141
+ }}
142
+ minValueLabelComponent={(minValue) => {
143
+ return (
144
+ <Price value={minValue} variant={variant} formatter={formatter} />
145
+ )
146
+ }}
147
+ maxValueLabelComponent={(maxValue) => {
148
+ return (
149
+ <Price value={maxValue} variant={variant} formatter={formatter} />
150
+ )
151
+ }}
152
+ />
153
+ <div data-fs-price-range-inputs>
154
+ <InputField
155
+ id="price-range-min"
156
+ step={step}
157
+ label="Min"
158
+ type="number"
159
+ inputMode="numeric"
160
+ error={inputMinError}
161
+ inputRef={inputMinRef}
162
+ min={Math.floor(min.absolute)}
163
+ max={priceRange.max}
164
+ value={priceRange.min}
165
+ onChange={(e) => onChangeInputMin(e.target.value)}
166
+ onBlur={() => !inputMinError && onEnd?.(priceRange)}
167
+ />
168
+ <InputField
169
+ id="price-range-max"
170
+ label="Max"
171
+ step={step}
172
+ type="number"
173
+ inputMode="numeric"
174
+ error={inputMaxError}
175
+ inputRef={inputMaxRef}
176
+ max={Math.round(max.absolute)}
177
+ min={priceRange.min}
178
+ value={priceRange.max}
179
+ onChange={(e) => onChangeInputMax(e.target.value)}
180
+ onBlur={() => !inputMaxError && onEnd?.(priceRange)}
181
+ />
182
+ </div>
183
+ </div>
184
+ )
185
+ }
186
+ )
187
+
188
+ export default PriceRange
@@ -0,0 +1,2 @@
1
+ export { default } from './PriceRange'
2
+ export type { PriceRangeProps } from './PriceRange'