@plexui/ui 0.2.1 → 0.4.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,34 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import clsx from "clsx";
4
+ import { cloneElement, isValidElement, useId } from "react";
5
+ import { FieldError } from "../FieldError";
6
+ import s from "./Field.module.css";
7
+ export function Field({ label, description, errorMessage, size = "md", required = false, orientation = "vertical", id: idProp, className, children, }) {
8
+ const generatedId = useId();
9
+ const fieldId = idProp || `field-${generatedId}`;
10
+ const descriptionId = description ? `${fieldId}-description` : undefined;
11
+ const errorId = errorMessage ? `${fieldId}-error` : undefined;
12
+ // Build aria-describedby from description + error IDs
13
+ const ariaDescribedBy = [descriptionId, errorId].filter(Boolean).join(" ") || undefined;
14
+ const invalid = !!errorMessage;
15
+ // Props to inject into the child control
16
+ const fieldChildProps = {
17
+ id: fieldId,
18
+ "aria-describedby": ariaDescribedBy,
19
+ "aria-invalid": invalid || undefined,
20
+ };
21
+ // Resolve the children
22
+ let resolvedChildren;
23
+ if (typeof children === "function") {
24
+ resolvedChildren = children(fieldChildProps);
25
+ }
26
+ else if (isValidElement(children)) {
27
+ resolvedChildren = cloneElement(children, fieldChildProps);
28
+ }
29
+ else {
30
+ resolvedChildren = children;
31
+ }
32
+ return (_jsxs("div", { className: clsx(s.Field, className), "data-size": size, "data-orientation": orientation, children: [_jsxs("label", { className: s.Label, htmlFor: fieldId, children: [label, required && (_jsx("span", { className: s.RequiredIndicator, "aria-hidden": "true", children: "*" }))] }), description && (_jsx("div", { className: s.Description, id: descriptionId, children: description })), _jsx("div", { className: s.Control, children: resolvedChildren }), errorMessage && (_jsx(FieldError, { id: errorId, children: errorMessage }))] }));
33
+ }
34
+ //# sourceMappingURL=Field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Field.js","sourceRoot":"","sources":["../../../../src/components/Field/Field.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAE1C,OAAO,CAAC,MAAM,oBAAoB,CAAA;AA4DlC,MAAM,UAAU,KAAK,CAAC,EACpB,KAAK,EACL,WAAW,EACX,YAAY,EACZ,IAAI,GAAG,IAAI,EACX,QAAQ,GAAG,KAAK,EAChB,WAAW,GAAG,UAAU,EACxB,EAAE,EAAE,MAAM,EACV,SAAS,EACT,QAAQ,GACG;IACX,MAAM,WAAW,GAAG,KAAK,EAAE,CAAA;IAC3B,MAAM,OAAO,GAAG,MAAM,IAAI,SAAS,WAAW,EAAE,CAAA;IAChD,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,cAAc,CAAC,CAAC,CAAC,SAAS,CAAA;IACxE,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAE7D,sDAAsD;IACtD,MAAM,eAAe,GACnB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAA;IAEjE,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,CAAA;IAE9B,yCAAyC;IACzC,MAAM,eAAe,GAAoB;QACvC,EAAE,EAAE,OAAO;QACX,kBAAkB,EAAE,eAAe;QACnC,cAAc,EAAE,OAAO,IAAI,SAAS;KACrC,CAAA;IAED,uBAAuB;IACvB,IAAI,gBAAiC,CAAA;IACrC,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,gBAAgB,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAA;IAC9C,CAAC;SAAM,IAAI,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,gBAAgB,GAAG,YAAY,CAC7B,QAAuD,EACvD,eAAe,CAChB,CAAA;IACH,CAAC;SAAM,CAAC;QACN,gBAAgB,GAAG,QAAQ,CAAA;IAC7B,CAAC;IAED,OAAO,CACL,eACE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,eACxB,IAAI,sBACG,WAAW,aAE7B,iBAAO,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,aACxC,KAAK,EACL,QAAQ,IAAI,CACX,eAAM,SAAS,EAAE,CAAC,CAAC,iBAAiB,iBAAc,MAAM,kBAEjD,CACR,IACK,EACP,WAAW,IAAI,CACd,cAAK,SAAS,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,EAAE,aAAa,YAC7C,WAAW,GACR,CACP,EACD,cAAK,SAAS,EAAE,CAAC,CAAC,OAAO,YAAG,gBAAgB,GAAO,EAClD,YAAY,IAAI,CACf,KAAC,UAAU,IAAC,EAAE,EAAE,OAAO,YAAG,YAAY,GAAc,CACrD,IACG,CACP,CAAA;AACH,CAAC","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { cloneElement, isValidElement, useId } from \"react\"\n\nimport type { ControlSize } from \"../../types\"\nimport { FieldError } from \"../FieldError\"\n\nimport s from \"./Field.module.css\"\n\nexport type FieldChildProps = {\n id: string\n \"aria-describedby\"?: string\n \"aria-invalid\"?: boolean\n}\n\nexport type FieldProps = {\n /**\n * Label text for the field\n */\n label: React.ReactNode\n /**\n * Helper/description text displayed below the label.\n * Automatically linked via aria-describedby.\n */\n description?: React.ReactNode\n /**\n * Error message displayed below the control.\n * When provided, the child control receives aria-invalid=\"true\".\n * Uses the existing FieldError component internally.\n */\n errorMessage?: React.ReactNode\n /**\n * Controls the font size of the label to visually match the child control's size.\n * Matches the ControlSize scale used by Input, Select, etc.\n * @default \"md\"\n */\n size?: ControlSize\n /**\n * Display a required indicator (asterisk) after the label.\n * This is purely visual — it does not add the `required` HTML attribute.\n * @default false\n */\n required?: boolean\n /**\n * Layout direction of label and control.\n * - \"vertical\": label stacked above control (default)\n * - \"horizontal\": label beside control\n * @default \"vertical\"\n */\n orientation?: \"vertical\" | \"horizontal\"\n /**\n * Allows overriding the auto-generated `id`. When provided, this becomes\n * the id set on the child control and the `htmlFor` on the label.\n */\n id?: string\n /**\n * CSS class applied to the root wrapper\n */\n className?: string\n /**\n * The form control(s) to render.\n * - If a single ReactElement, Field clones it with { id, aria-describedby, aria-invalid }.\n * - If a function (render prop), it is called with { id, \"aria-describedby\", \"aria-invalid\" }.\n */\n children: React.ReactElement | ((fieldProps: FieldChildProps) => React.ReactNode)\n}\n\nexport function Field({\n label,\n description,\n errorMessage,\n size = \"md\",\n required = false,\n orientation = \"vertical\",\n id: idProp,\n className,\n children,\n}: FieldProps) {\n const generatedId = useId()\n const fieldId = idProp || `field-${generatedId}`\n const descriptionId = description ? `${fieldId}-description` : undefined\n const errorId = errorMessage ? `${fieldId}-error` : undefined\n\n // Build aria-describedby from description + error IDs\n const ariaDescribedBy =\n [descriptionId, errorId].filter(Boolean).join(\" \") || undefined\n\n const invalid = !!errorMessage\n\n // Props to inject into the child control\n const fieldChildProps: FieldChildProps = {\n id: fieldId,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": invalid || undefined,\n }\n\n // Resolve the children\n let resolvedChildren: React.ReactNode\n if (typeof children === \"function\") {\n resolvedChildren = children(fieldChildProps)\n } else if (isValidElement(children)) {\n resolvedChildren = cloneElement(\n children as React.ReactElement<Record<string, unknown>>,\n fieldChildProps,\n )\n } else {\n resolvedChildren = children\n }\n\n return (\n <div\n className={clsx(s.Field, className)}\n data-size={size}\n data-orientation={orientation}\n >\n <label className={s.Label} htmlFor={fieldId}>\n {label}\n {required && (\n <span className={s.RequiredIndicator} aria-hidden=\"true\">\n *\n </span>\n )}\n </label>\n {description && (\n <div className={s.Description} id={descriptionId}>\n {description}\n </div>\n )}\n <div className={s.Control}>{resolvedChildren}</div>\n {errorMessage && (\n <FieldError id={errorId}>{errorMessage}</FieldError>\n )}\n </div>\n )\n}\n"]}
@@ -0,0 +1,97 @@
1
+ @layer components {.Field {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--field-gap);
5
+
6
+ /* Override FieldError margins since Field controls spacing via gap */
7
+ --field-error-margin-top: 0;
8
+ --field-error-margin-bottom: 0;
9
+ --field-error-padding-inline: 0;
10
+ }/* =============================================
11
+ Orientation
12
+ ============================================= */.Field:where([data-orientation="horizontal"]) {
13
+ flex-direction: row;
14
+ align-items: flex-start;
15
+ }.Field:where([data-orientation="horizontal"]) .Label {
16
+ flex-shrink: 0;
17
+ padding-top: var(--field-label-horizontal-offset);
18
+ min-width: var(--field-label-horizontal-min-width, 120px);
19
+ }.Field:where([data-orientation="horizontal"]) .Control {
20
+ flex: 1;
21
+ min-width: 0;
22
+ }/* =============================================
23
+ Sizes
24
+ ============================================= */.Field:where([data-size="3xs"]),
25
+ .Field:where([data-size="2xs"]) {
26
+ --field-gap: calc(var(--spacing) * 1.5);
27
+ --field-label-font-size: var(--font-text-xs-size);
28
+ --field-label-line-height: var(--font-text-xs-line-height);
29
+ --field-description-font-size: var(--font-text-xs-size);
30
+ --field-description-line-height: var(--font-text-xs-line-height);
31
+ --field-label-horizontal-offset: 0.25rem;
32
+ }.Field:where([data-size="xs"]),
33
+ .Field:where([data-size="sm"]) {
34
+ --field-gap: calc(var(--spacing) * 1.5);
35
+ --field-label-font-size: var(--font-text-xs-size);
36
+ --field-label-line-height: var(--font-text-xs-line-height);
37
+ --field-description-font-size: var(--font-text-xs-size);
38
+ --field-description-line-height: var(--font-text-xs-line-height);
39
+ --field-label-horizontal-offset: 0.375rem;
40
+ }.Field:where([data-size="md"]) {
41
+ --field-gap: calc(var(--spacing) * 2);
42
+ --field-label-font-size: var(--font-text-sm-size);
43
+ --field-label-line-height: var(--font-text-sm-line-height);
44
+ --field-description-font-size: var(--font-text-xs-size);
45
+ --field-description-line-height: var(--font-text-xs-line-height);
46
+ --field-description-margin-top: calc(-1 * calc(var(--spacing) * 1));
47
+ --field-label-horizontal-offset: 0.5rem;
48
+ }.Field:where([data-size="lg"]),
49
+ .Field:where([data-size="xl"]) {
50
+ --field-gap: calc(var(--spacing) * 2);
51
+ --field-label-font-size: var(--font-text-sm-size);
52
+ --field-label-line-height: var(--font-text-sm-line-height);
53
+ --field-description-font-size: var(--font-text-xs-size);
54
+ --field-description-line-height: var(--font-text-xs-line-height);
55
+ --field-description-margin-top: calc(-1 * calc(var(--spacing) * 1));
56
+ --field-label-horizontal-offset: 0.625rem;
57
+ }.Field:where([data-size="2xl"]),
58
+ .Field:where([data-size="3xl"]) {
59
+ --field-gap: calc(var(--spacing) * 2.5);
60
+ --field-label-font-size: var(--font-text-md-size);
61
+ --field-label-line-height: var(--font-text-md-line-height);
62
+ --field-description-font-size: var(--font-text-sm-size);
63
+ --field-description-line-height: var(--font-text-sm-line-height);
64
+ --field-description-margin-top: calc(-1 * calc(var(--spacing) * 1));
65
+ --field-label-horizontal-offset: 0.875rem;
66
+ }/* =============================================
67
+ Label
68
+ ============================================= */.Label {
69
+ display: inline-flex;
70
+ align-items: baseline;
71
+ gap: calc(var(--spacing) * 0.5);
72
+ color: var(--color-text);
73
+ font-size: var(--field-label-font-size);
74
+ line-height: var(--field-label-line-height);
75
+ font-weight: var(--font-weight-semibold);
76
+ cursor: default;
77
+ -webkit-user-select: none;
78
+ -moz-user-select: none;
79
+ user-select: none;
80
+ }/* =============================================
81
+ Required Indicator
82
+ ============================================= */.RequiredIndicator {
83
+ color: var(--field-error-color);
84
+ font-weight: var(--font-weight-normal);
85
+ }/* =============================================
86
+ Description
87
+ ============================================= */.Description {
88
+ color: var(--color-text-secondary);
89
+ font-size: var(--field-description-font-size);
90
+ line-height: var(--field-description-line-height);
91
+ margin-top: var(--field-description-margin-top, calc(-1 * calc(var(--spacing) * 0.5)));
92
+ }/* =============================================
93
+ Control
94
+ ============================================= */.Control {
95
+ /* Container for the child form control */
96
+ }
97
+ }
@@ -0,0 +1,2 @@
1
+ export { Field } from "./Field";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/Field/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAyC,MAAM,SAAS,CAAA","sourcesContent":["export { Field, type FieldProps, type FieldChildProps } from \"./Field\"\n"]}
@@ -1,2 +1,2 @@
1
- export { SegmentedControl } from "./SegmentedControl";
1
+ export { Tabs as SegmentedControl } from "../Tabs";
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/SegmentedControl/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA","sourcesContent":["export { SegmentedControl } from \"./SegmentedControl\"\nexport type {\n SegmentedControlBadgeProp,\n SegmentedControlOptionProps,\n SegmentedControlProps,\n SizeVariant,\n} from \"./SegmentedControl\"\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/SegmentedControl/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,gBAAgB,EAAE,MAAM,SAAS,CAAA","sourcesContent":["export { Tabs as SegmentedControl } from \"../Tabs\"\nexport type {\n TabsBadgeProp as SegmentedControlBadgeProp,\n TabProps as SegmentedControlOptionProps,\n TabsProps as SegmentedControlProps,\n SizeVariant,\n} from \"../Tabs\"\n"]}
@@ -16,15 +16,20 @@ import { Reload } from "../Icon";
16
16
  import { Tooltip } from "../Tooltip";
17
17
  import s from "./Slider.module.css";
18
18
  export const Slider = memo((props) => {
19
- const { className, onChange, min, max, step, disabled, value, resetValue, resetTooltip = "Reset to default", onBlur, onFocus, unit, prefixUnit, label, marks: propMarks = [], trackColor, rangeColor, ref: forwardedRef, } = props;
19
+ const { className, onChange, min, max, step, disabled, value, onBlur, onFocus, unit, prefixUnit, label, marks: propMarks = [], trackColor, rangeColor, orientation = "horizontal", ref: forwardedRef, } = props;
20
+ const isRange = props.range === true;
21
+ const isVertical = orientation === "vertical";
20
22
  const id = useId();
21
23
  const precision = useMemo(() => String(step).split(".")[1]?.length ?? 0, [step]);
22
- const [inputValue, setInputValue] = useState(String(value.toFixed(precision)));
24
+ // Single-mode state for editable input
25
+ const singleValue = isRange ? 0 : value;
26
+ const [inputValue, setInputValue] = useState(isRange ? "" : String(value.toFixed(precision)));
23
27
  const setInputValueNumber = useCallback((nextValue) => {
24
28
  setInputValue(nextValue.toFixed(precision));
25
29
  }, [precision]);
26
30
  // prevents input from jumping around while the user is typing
27
- const debouncedOnChange = useDebounceCallback(onChange, 250);
31
+ const singleOnChange = isRange ? undefined : onChange;
32
+ const debouncedOnChange = useDebounceCallback(singleOnChange ?? (() => { }), 250);
28
33
  const [pointerDown, setPointerDown] = useState(false);
29
34
  const isMounted = useIsMounted();
30
35
  // Used to position the input over the thumb
@@ -33,24 +38,22 @@ export const Slider = memo((props) => {
33
38
  // The input width is based on the number of characters in the input / font size
34
39
  const inputWidth = Math.max(inputValue.length, 1) * (isTabletAndUp ? 7.8 : 9.5);
35
40
  // Calculate animation duration based on the distance the thumb needs to move
36
- // If the pointer is down, it means the user is dragging the thumb and we want
37
- // to disable the animation to make the thumb move in sync with the pointer.
38
- const percent = (value - min) / (max - min);
41
+ // Skip custom animation for range mode let Radix handle it
42
+ const percent = isRange ? 0 : (value - min) / (max - min);
39
43
  const previousPercent = usePrevious(percent);
40
- const animationDurationMS = !isMounted || pointerDown ? 0 : Math.max(Math.abs(percent - previousPercent) * 300, 100);
44
+ const animationDurationMS = isRange || !isMounted || pointerDown
45
+ ? 0
46
+ : Math.max(Math.abs(percent - previousPercent) * 300, 100);
41
47
  // We assume that the width of the thumb does not change from render to render
42
- // so we can avoid the overhead of watching it with a resize observer.
43
48
  const thumbRef = useRef(null);
44
- // It should be exceedingly uncommon to change marks dynamically, and they are unlikely to be a stable array reference from consumers.
45
- // We are sorting so we can make assumptions about which marks have the ability to collide with each other.
49
+ // Sort marks for collision detection
46
50
  const marks = useMemo(() => [...propMarks].sort((a, b) => a.value - b.value),
47
- // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally limiting when this stable value changes to length of marks
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
52
  [propMarks.length]);
49
- // We make assumptions about marks in order to efficiently position them
53
+ // Validate marks
50
54
  useEffect(() => {
51
- if (!marks) {
55
+ if (!marks)
52
56
  return;
53
- }
54
57
  const markValues = new Set();
55
58
  for (const mark of marks) {
56
59
  if (mark.value < min || mark.value > max) {
@@ -62,57 +65,63 @@ export const Slider = memo((props) => {
62
65
  markValues.add(mark.value);
63
66
  }
64
67
  }, [marks, max, min]);
65
- // Update the value if it is out of bounds due to min/max changes
68
+ // Update value if out of bounds (single mode only)
66
69
  const latestValue = useLatestValue(value);
67
70
  const latestOnChange = useLatestValue(onChange);
68
71
  useEffect(() => {
69
- const clamped = clamp(latestValue.current, min, max);
70
- if (clamped !== latestValue.current) {
72
+ if (isRange)
73
+ return;
74
+ const v = latestValue.current;
75
+ const clamped = clamp(v, min, max);
76
+ if (clamped !== v) {
77
+ ;
71
78
  latestOnChange.current(clamped);
72
79
  setInputValueNumber(clamped);
73
80
  }
74
- }, [max, min, latestValue, latestOnChange, setInputValueNumber]);
81
+ }, [max, min, latestValue, latestOnChange, setInputValueNumber, isRange]);
75
82
  useEffect(() => {
76
- // If the input is focused then the change came from this input we
77
- // wait until after they blur before updating the input value to be
78
- // the actual value.
83
+ if (isRange)
84
+ return;
79
85
  if (inputRef.current !== document.activeElement) {
80
86
  setInputValueNumber(value);
81
87
  }
82
- }, [value, setInputValueNumber]);
88
+ }, [value, setInputValueNumber, isRange]);
89
+ // Single-mode keyboard handler
83
90
  const handleKeyDown = (evt) => {
84
- // https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/
91
+ if (isRange || !singleOnChange)
92
+ return;
85
93
  if (evt.key === "Home") {
86
94
  evt.preventDefault();
87
- onChange(min);
95
+ singleOnChange(min);
88
96
  setInputValueNumber(min);
89
97
  }
90
98
  if (evt.key === "End") {
91
99
  evt.preventDefault();
92
- onChange(max);
100
+ singleOnChange(max);
93
101
  setInputValueNumber(max);
94
102
  }
95
103
  if (evt.key === "ArrowUp") {
96
104
  evt.preventDefault();
97
105
  const multiplier = evt.shiftKey ? 10 : 1;
98
- const next = clamp(value + step * multiplier, min, max);
99
- onChange(next);
106
+ const next = clamp(singleValue + step * multiplier, min, max);
107
+ singleOnChange(next);
100
108
  setInputValueNumber(next);
101
109
  }
102
110
  if (evt.key === "ArrowDown") {
103
111
  evt.preventDefault();
104
112
  const multiplier = evt.shiftKey ? 10 : 1;
105
- const next = clamp(value - step * multiplier, min, max);
106
- onChange(next);
113
+ const next = clamp(singleValue - step * multiplier, min, max);
114
+ singleOnChange(next);
107
115
  setInputValueNumber(next);
108
116
  }
109
- // Other keyboard actions not tied to normal slider behavior
110
117
  if (evt.key === "Enter" || evt.key === "Escape") {
111
118
  evt.preventDefault();
112
119
  evt.currentTarget.blur();
113
120
  }
114
121
  };
115
122
  const handleInputChange = (evt) => {
123
+ if (isRange || !singleOnChange)
124
+ return;
116
125
  const nextValue = evt.currentTarget.value.replace(/[^\d.]/, "").trim();
117
126
  let parsed = parseFloat(nextValue || "0");
118
127
  parsed = clamp(parsed, min, max);
@@ -123,30 +132,52 @@ export const Slider = memo((props) => {
123
132
  setInputValue(nextValue);
124
133
  };
125
134
  const handleInputBlur = (evt) => {
135
+ if (isRange || !singleOnChange)
136
+ return;
126
137
  let parsed = parseFloat(evt.target.value.trim()) || 0;
127
- // If floats are not allowed (based on `step` value) coerce to whole number
128
138
  if (step >= 1) {
129
139
  parsed = Math.floor(parsed);
130
140
  }
131
141
  else {
132
142
  parsed = round(parsed, precision);
133
143
  }
134
- // Make sure the final value is within the min/max range (and emit a change if necessary)
135
144
  parsed = clamp(parsed, min, max);
136
- if (parsed !== value) {
137
- onChange(parsed);
145
+ if (parsed !== singleValue) {
146
+ singleOnChange(parsed);
138
147
  }
139
148
  setInputValueNumber(parsed);
140
149
  onBlur?.(evt);
141
150
  };
142
- return (_jsxs("div", { className: clsx(s.SliderWrap, className), children: [_jsxs("div", { className: s.SliderLabel, children: [_jsx("label", { htmlFor: id, className: "flex-1", children: label }), resetValue !== undefined && (_jsx(Tooltip, { content: resetTooltip, compact: true, children: _jsx(Button, { size: "2xs", variant: "ghost", color: "secondary", className: s.Reset, "data-hide": disabled || (resetValue === value && !pointerDown), onClick: () => onChange(resetValue), children: _jsx(Reload, {}) }) })), _jsxs("div", { className: s.SliderValue, onClick: () => inputRef.current?.focus(), children: [prefixUnit && _jsx("span", { className: s.ValueUnit, children: prefixUnit }), _jsx("input", { id: id, className: s.ValueInput, ref: inputRef, style: { width: `${Math.ceil(inputWidth)}px` }, onKeyDown: handleKeyDown, value: inputValue, type: "text", onClick: (e) => e.stopPropagation(), onBlur: handleInputBlur, onFocus: (e) => {
151
+ // Radix value/onChange wiring
152
+ const radixValue = isRange ? value : [value];
153
+ const handleValueChange = isRange
154
+ ? (values) => onChange(values)
155
+ : (values) => onChange(values[0]);
156
+ // Single-mode props
157
+ const resetValue = !isRange ? props.resetValue : undefined;
158
+ const resetTooltip = !isRange
159
+ ? props.resetTooltip ?? "Reset to default"
160
+ : undefined;
161
+ // Range-mode props
162
+ const minStepsBetweenThumbs = isRange
163
+ ? props.minStepsBetweenThumbs
164
+ : undefined;
165
+ // Show marks only in horizontal orientation
166
+ const showMarks = marks.length > 0 && !isVertical;
167
+ // Format range display text
168
+ const rangeDisplayText = isRange
169
+ ? value
170
+ .map((v) => `${prefixUnit ?? ""}${v.toFixed(precision)}${unit ?? ""}`)
171
+ .join(" – ")
172
+ : "";
173
+ return (_jsxs("div", { className: clsx(s.SliderWrap, className), "data-orientation": orientation, children: [label && (_jsxs("div", { className: s.SliderLabel, children: [_jsx("label", { htmlFor: id, className: "flex-1", children: label }), !isRange && resetValue !== undefined && (_jsx(Tooltip, { content: resetTooltip, compact: true, children: _jsx(Button, { size: "2xs", variant: "ghost", color: "secondary", className: s.Reset, "data-hide": disabled || (resetValue === singleValue && !pointerDown), onClick: () => singleOnChange(resetValue), children: _jsx(Reload, {}) }) })), isRange ? (_jsx("span", { className: s.RangeDisplay, children: rangeDisplayText })) : (_jsxs("div", { className: s.SliderValue, onClick: () => inputRef.current?.focus(), children: [prefixUnit && _jsx("span", { className: s.ValueUnit, children: prefixUnit }), _jsx("input", { id: id, className: s.ValueInput, ref: inputRef, style: { width: `${Math.ceil(inputWidth)}px` }, onKeyDown: handleKeyDown, value: inputValue, type: "text", onClick: (e) => e.stopPropagation(), onBlur: handleInputBlur, onFocus: (e) => {
143
174
  e.currentTarget.setSelectionRange(0, e.currentTarget.value.length);
144
175
  onFocus?.(e);
145
- }, onChange: handleInputChange, disabled: disabled }), unit && _jsx("span", { className: s.ValueUnit, children: unit })] })] }), _jsxs("div", { className: s.SliderContainer, children: [_jsxs(RadixSlider.Root, { ref: forwardedRef, className: s.Slider, onValueChange: (values) => onChange(values[0]), min: min, max: max, step: step, disabled: disabled, value: [value], onBlur: onBlur, onFocus: onFocus, onPointerDown: () => setPointerDown(true), onPointerUp: () => setPointerDown(false), style: toCssVariables({
176
+ }, onChange: handleInputChange, disabled: disabled }), unit && _jsx("span", { className: s.ValueUnit, children: unit })] }))] })), _jsxs("div", { className: s.SliderContainer, children: [_jsxs(RadixSlider.Root, { ref: forwardedRef, className: s.Slider, onValueChange: handleValueChange, min: min, max: max, step: step, disabled: disabled, value: radixValue, orientation: orientation, minStepsBetweenThumbs: minStepsBetweenThumbs, onBlur: onBlur, onFocus: onFocus, onPointerDown: () => setPointerDown(true), onPointerUp: () => setPointerDown(false), style: toCssVariables({
146
177
  "slider-duration": `${animationDurationMS}ms`,
147
178
  "slider-track-color": trackColor,
148
179
  "slider-range-color": rangeColor,
149
- }), children: [_jsx(RadixSlider.Track, { className: s.Track, children: _jsx(RadixSlider.Range, { className: s.Range }) }), _jsx(RadixSlider.Thumb, { className: s.Thumb, ref: thumbRef })] }), marks && _jsx(SliderMarks, { marks: marks, thumbRef: thumbRef, min: min, max: max })] })] }));
180
+ }), children: [_jsx(RadixSlider.Track, { className: s.Track, children: _jsx(RadixSlider.Range, { className: s.Range }) }), isRange ? (value.map((_, i) => (_jsx(RadixSlider.Thumb, { className: s.Thumb }, i)))) : (_jsx(RadixSlider.Thumb, { className: s.Thumb, ref: thumbRef }))] }), showMarks && _jsx(SliderMarks, { marks: marks, thumbRef: thumbRef, min: min, max: max })] })] }));
150
181
  });
151
182
  // The minimum difference we enforce between marks, in pixels
152
183
  const MINIMUM_MARK_SPACING_PX = 16;
@@ -1 +1 @@
1
- {"version":3,"file":"Slider.js","sourceRoot":"","sources":["../../../../src/components/Slider/Slider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport clamp from \"lodash/clamp\"\nimport round from \"lodash/round\"\nimport { Slider as RadixSlider } from \"radix-ui\"\nimport {\n type ChangeEventHandler,\n type ElementRef,\n type FocusEventHandler,\n type KeyboardEventHandler,\n memo,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport { useDebounceCallback, useResizeObserver } from \"usehooks-ts\"\nimport { useBreakpoint } from \"../../hooks/useBreakpoints\"\nimport { useIsMounted } from \"../../hooks/useIsMounted\"\nimport { useLatestValue } from \"../../hooks/useLatestValue\"\nimport { usePrevious } from \"../../hooks/usePrevious\"\nimport { toCssVariables } from \"../../lib/helpers\"\nimport { Button } from \"../Button\"\nimport { Reload } from \"../Icon\"\nimport { Tooltip } from \"../Tooltip\"\nimport s from \"./Slider.module.css\"\n\nexport type SliderMark = {\n value: number\n label: string\n}\n\nexport type SliderProps = {\n /**\n * The current value of the slider\n */\n value: number\n /**\n * The minimum value the slider can have\n */\n min: number\n /**\n * The maximum value the slider can have\n */\n max: number\n /**\n * The step increment between slider values\n */\n step: number\n /**\n * Value that will be offered as a \"reset to default\" option\n */\n resetValue?: number\n /**\n * String that will be displayed in the tooltip\n * @default Reset to default\n */\n resetTooltip?: string\n /**\n * Unit to display next to the slider value (e.g., ms, px)\n */\n unit?: string\n /**\n * Unit to display to the right of the slider value (e.g., $)\n */\n prefixUnit?: string\n /**\n * Optional label for the slider, which can be a string or React node.\n */\n label?: ReactNode\n /**\n * List of marks to display below the slider track\n */\n marks?: SliderMark[]\n /**\n * Color of the slider track\n */\n trackColor?: string\n /**\n * Color of the slider progress along the track\n */\n rangeColor?: string\n className?: string\n disabled?: boolean\n /**\n * Callback function invoked when the slider value changes.\n *\n * @param value - The new value of the slider.\n */\n onChange: (value: number) => void\n onBlur?: FocusEventHandler<HTMLInputElement>\n onFocus?: FocusEventHandler<HTMLInputElement>\n ref?: React.Ref<ElementRef<typeof RadixSlider.Root> | null>\n}\n\nexport const Slider = memo((props: SliderProps) => {\n const {\n className,\n onChange,\n min,\n max,\n step,\n disabled,\n value,\n resetValue,\n resetTooltip = \"Reset to default\",\n onBlur,\n onFocus,\n unit,\n prefixUnit,\n label,\n marks: propMarks = [],\n trackColor,\n rangeColor,\n ref: forwardedRef,\n } = props\n const id = useId()\n const precision = useMemo(() => String(step).split(\".\")[1]?.length ?? 0, [step])\n const [inputValue, setInputValue] = useState<string>(String(value.toFixed(precision)))\n const setInputValueNumber = useCallback(\n (nextValue: number) => {\n setInputValue(nextValue.toFixed(precision))\n },\n [precision],\n )\n\n // prevents input from jumping around while the user is typing\n const debouncedOnChange = useDebounceCallback(onChange, 250)\n const [pointerDown, setPointerDown] = useState(false)\n const isMounted = useIsMounted()\n\n // Used to position the input over the thumb\n const isTabletAndUp = useBreakpoint(\"md\")\n const inputRef = useRef<HTMLInputElement>(null)\n\n // The input width is based on the number of characters in the input / font size\n const inputWidth = Math.max(inputValue.length, 1) * (isTabletAndUp ? 7.8 : 9.5)\n\n // Calculate animation duration based on the distance the thumb needs to move\n // If the pointer is down, it means the user is dragging the thumb and we want\n // to disable the animation to make the thumb move in sync with the pointer.\n const percent = (value - min) / (max - min)\n const previousPercent = usePrevious(percent)\n const animationDurationMS =\n !isMounted || pointerDown ? 0 : Math.max(Math.abs(percent - previousPercent) * 300, 100)\n\n // We assume that the width of the thumb does not change from render to render\n // so we can avoid the overhead of watching it with a resize observer.\n const thumbRef = useRef<HTMLDivElement>(null)\n\n // It should be exceedingly uncommon to change marks dynamically, and they are unlikely to be a stable array reference from consumers.\n // We are sorting so we can make assumptions about which marks have the ability to collide with each other.\n const marks = useMemo<SliderMark[]>(\n () => [...propMarks].sort((a, b) => a.value - b.value),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally limiting when this stable value changes to length of marks\n [propMarks.length],\n )\n\n // We make assumptions about marks in order to efficiently position them\n useEffect(() => {\n if (!marks) {\n return\n }\n\n const markValues = new Set<number>()\n\n for (const mark of marks) {\n if (mark.value < min || mark.value > max) {\n throw new Error(`Slider mark value ${mark.value} is out of bounds [${min}, ${max}]`)\n }\n if (markValues.has(mark.value)) {\n throw new Error(\"Slider marks must have unique values\")\n }\n markValues.add(mark.value)\n }\n }, [marks, max, min])\n\n // Update the value if it is out of bounds due to min/max changes\n const latestValue = useLatestValue(value)\n const latestOnChange = useLatestValue(onChange)\n useEffect(() => {\n const clamped = clamp(latestValue.current, min, max)\n if (clamped !== latestValue.current) {\n latestOnChange.current(clamped)\n setInputValueNumber(clamped)\n }\n }, [max, min, latestValue, latestOnChange, setInputValueNumber])\n\n useEffect(() => {\n // If the input is focused then the change came from this input we\n // wait until after they blur before updating the input value to be\n // the actual value.\n if (inputRef.current !== document.activeElement) {\n setInputValueNumber(value)\n }\n }, [value, setInputValueNumber])\n\n const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (evt) => {\n // https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/\n if (evt.key === \"Home\") {\n evt.preventDefault()\n onChange(min)\n setInputValueNumber(min)\n }\n if (evt.key === \"End\") {\n evt.preventDefault()\n onChange(max)\n setInputValueNumber(max)\n }\n if (evt.key === \"ArrowUp\") {\n evt.preventDefault()\n const multiplier = evt.shiftKey ? 10 : 1\n const next = clamp(value + step * multiplier, min, max)\n onChange(next)\n setInputValueNumber(next)\n }\n if (evt.key === \"ArrowDown\") {\n evt.preventDefault()\n const multiplier = evt.shiftKey ? 10 : 1\n const next = clamp(value - step * multiplier, min, max)\n onChange(next)\n setInputValueNumber(next)\n }\n\n // Other keyboard actions not tied to normal slider behavior\n if (evt.key === \"Enter\" || evt.key === \"Escape\") {\n evt.preventDefault()\n evt.currentTarget.blur()\n }\n }\n\n const handleInputChange: ChangeEventHandler<HTMLInputElement> = (evt) => {\n const nextValue = evt.currentTarget.value.replace(/[^\\d.]/, \"\").trim()\n let parsed = parseFloat(nextValue || \"0\")\n parsed = clamp(parsed, min, max)\n if (step >= 1) {\n parsed = Math.floor(parsed)\n }\n debouncedOnChange(parsed)\n setInputValue(nextValue)\n }\n\n const handleInputBlur: FocusEventHandler<HTMLInputElement> = (evt) => {\n let parsed = parseFloat(evt.target.value.trim()) || 0\n // If floats are not allowed (based on `step` value) coerce to whole number\n if (step >= 1) {\n parsed = Math.floor(parsed)\n } else {\n parsed = round(parsed, precision)\n }\n // Make sure the final value is within the min/max range (and emit a change if necessary)\n parsed = clamp(parsed, min, max)\n if (parsed !== value) {\n onChange(parsed)\n }\n setInputValueNumber(parsed)\n onBlur?.(evt)\n }\n\n return (\n <div className={clsx(s.SliderWrap, className)}>\n <div className={s.SliderLabel}>\n <label htmlFor={id} className=\"flex-1\">\n {label}\n </label>\n {resetValue !== undefined && (\n <Tooltip content={resetTooltip} compact>\n <Button\n size=\"2xs\"\n variant=\"ghost\"\n color=\"secondary\"\n className={s.Reset}\n data-hide={disabled || (resetValue === value && !pointerDown)}\n onClick={() => onChange(resetValue)}\n >\n <Reload />\n </Button>\n </Tooltip>\n )}\n <div className={s.SliderValue} onClick={() => inputRef.current?.focus()}>\n {prefixUnit && <span className={s.ValueUnit}>{prefixUnit}</span>}\n <input\n id={id}\n className={s.ValueInput}\n ref={inputRef}\n style={{ width: `${Math.ceil(inputWidth)}px` }}\n onKeyDown={handleKeyDown}\n value={inputValue}\n type=\"text\"\n onClick={(e) => e.stopPropagation()}\n onBlur={handleInputBlur}\n onFocus={(e) => {\n e.currentTarget.setSelectionRange(0, e.currentTarget.value.length)\n onFocus?.(e)\n }}\n onChange={handleInputChange}\n disabled={disabled}\n />\n {unit && <span className={s.ValueUnit}>{unit}</span>}\n </div>\n </div>\n <div className={s.SliderContainer}>\n <RadixSlider.Root\n ref={forwardedRef}\n className={s.Slider}\n onValueChange={(values) => onChange(values[0])}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n value={[value]}\n onBlur={onBlur}\n onFocus={onFocus}\n onPointerDown={() => setPointerDown(true)}\n onPointerUp={() => setPointerDown(false)}\n style={toCssVariables({\n \"slider-duration\": `${animationDurationMS}ms`,\n \"slider-track-color\": trackColor,\n \"slider-range-color\": rangeColor,\n })}\n >\n <RadixSlider.Track className={s.Track}>\n <RadixSlider.Range className={s.Range} />\n </RadixSlider.Track>\n <RadixSlider.Thumb className={s.Thumb} ref={thumbRef} />\n </RadixSlider.Root>\n {marks && <SliderMarks marks={marks} thumbRef={thumbRef} min={min} max={max} />}\n </div>\n </div>\n )\n})\n\ntype MarksProps = {\n marks: SliderMark[]\n thumbRef: React.RefObject<HTMLDivElement | null>\n min: number\n max: number\n}\n\n// The minimum difference we enforce between marks, in pixels\nconst MINIMUM_MARK_SPACING_PX = 16\n\nconst SliderMarks = memo(({ marks, thumbRef, min, max }: MarksProps) => {\n // We seperate the container and measure divs so that our resize observer\n // does not fire when we adjust the height of the marks container.\n const marksContainerRef = useRef<HTMLDivElement>(null)\n const measureRef = useRef<HTMLDivElement>(null)\n\n // We measure the width of the slider within this component so that sliders\n // without marks do not pay a performance penalty.\n // @ts-expect-error(2322) -- bug in types: https://github.com/juliencrn/usehooks-ts/issues/663\n const { width: sliderWidth } = useResizeObserver({ ref: measureRef })\n\n useLayoutEffect(() => {\n // Impossible\n if (!thumbRef.current || !marksContainerRef.current) {\n return\n }\n\n const markElements = Array.from(\n marksContainerRef.current.querySelectorAll(\"[data-mark]\"),\n ) as HTMLDivElement[]\n\n const thumbHalfWidth = thumbRef.current.getBoundingClientRect().width / 2\n\n // Wipe all styles so we can accurately determine the positions of the marks\n markElements.forEach((markEl) => {\n markEl.style.width = \"\"\n markEl.style.display = \"\"\n })\n\n const positionMarks = () => {\n if (!sliderWidth) {\n return\n }\n\n markElements.forEach((markEl, index) => {\n const markValue = marks[index].value\n const valuePercent = (markValue - min) / (max - min)\n const markWidth = markEl.getBoundingClientRect().width\n const markHalfWidth = markWidth / 2\n\n // Radix smoothes the width of the thumb over the entire track so\n // that the thumb is always within bounds. We need to duplicate\n // this smoothing so that the marks match the observed breakpoints.\n const smoothedThumb = thumbHalfWidth - valuePercent * (thumbHalfWidth / 0.5)\n\n // Calculate where we would naively put the mark based on the value\n const naiveLeftOffset = valuePercent * sliderWidth\n\n // Add the thumb smoothing, and account for the width of the mark itself\n let left = naiveLeftOffset + smoothedThumb - markHalfWidth\n\n // Clamp to the left and right bounds of the container\n left = Math.max(0, Math.min(left, sliderWidth - markWidth))\n\n markEl.style.left = `${left}px`\n })\n }\n\n const marksHaveCollisions = () => {\n for (let i = 0; i < markElements.length - 1; i++) {\n const currentRect = markElements[i].getBoundingClientRect()\n const nextRect = markElements[i + 1].getBoundingClientRect()\n\n if (currentRect.right + MINIMUM_MARK_SPACING_PX > nextRect.left) {\n return true\n }\n }\n return false\n }\n\n positionMarks()\n\n // If we have collisions, try to wrap the content\n if (marksHaveCollisions()) {\n markElements.forEach((markEl) => {\n markEl.style.width = \"min-content\"\n })\n\n positionMarks()\n\n // Hide all marks if we still have collisions\n if (marksHaveCollisions()) {\n markElements.forEach((markEl) => {\n markEl.style.display = \"none\"\n })\n }\n }\n\n const tallestHeight = Math.max(\n ...markElements.map((markEl) => markEl.getBoundingClientRect().height),\n )\n\n // Give our container a height so that we allocate space on the page\n // for the marks and push down other content when we have to wrap\n marksContainerRef.current.style.height = tallestHeight > 0 ? `${tallestHeight}px` : \"\"\n }, [marks, thumbRef, marksContainerRef, min, max, sliderWidth])\n\n return (\n <div className={s.MarksContainer} ref={marksContainerRef}>\n <div ref={measureRef}>\n {marks.map((mark) => (\n <div key={mark.value} className={s.Mark} data-mark>\n <span className={s.MarkLabel}>{mark.label}</span>\n </div>\n ))}\n </div>\n </div>\n )\n})\n"]}
1
+ {"version":3,"file":"Slider.js","sourceRoot":"","sources":["../../../../src/components/Slider/Slider.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport clamp from \"lodash/clamp\"\nimport round from \"lodash/round\"\nimport { Slider as RadixSlider } from \"radix-ui\"\nimport {\n type ChangeEventHandler,\n type ElementRef,\n type FocusEventHandler,\n type KeyboardEventHandler,\n memo,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport { useDebounceCallback, useResizeObserver } from \"usehooks-ts\"\nimport { useBreakpoint } from \"../../hooks/useBreakpoints\"\nimport { useIsMounted } from \"../../hooks/useIsMounted\"\nimport { useLatestValue } from \"../../hooks/useLatestValue\"\nimport { usePrevious } from \"../../hooks/usePrevious\"\nimport { toCssVariables } from \"../../lib/helpers\"\nimport { Button } from \"../Button\"\nimport { Reload } from \"../Icon\"\nimport { Tooltip } from \"../Tooltip\"\nimport s from \"./Slider.module.css\"\n\nexport type SliderMark = {\n value: number\n label: string\n}\n\ntype SliderBaseProps = {\n /**\n * The minimum value the slider can have\n */\n min: number\n /**\n * The maximum value the slider can have\n */\n max: number\n /**\n * The step increment between slider values\n */\n step: number\n /**\n * Unit to display next to the slider value (e.g., ms, px)\n */\n unit?: string\n /**\n * Unit to display to the right of the slider value (e.g., $)\n */\n prefixUnit?: string\n /**\n * Optional label for the slider, which can be a string or React node.\n */\n label?: ReactNode\n /**\n * List of marks to display below the slider track\n */\n marks?: SliderMark[]\n /**\n * Color of the slider track\n */\n trackColor?: string\n /**\n * Color of the slider progress along the track\n */\n rangeColor?: string\n className?: string\n disabled?: boolean\n /**\n * Orientation of the slider\n * @default \"horizontal\"\n */\n orientation?: \"horizontal\" | \"vertical\"\n onBlur?: FocusEventHandler<HTMLInputElement>\n onFocus?: FocusEventHandler<HTMLInputElement>\n ref?: React.Ref<ElementRef<typeof RadixSlider.Root> | null>\n}\n\ntype SingleSliderProps = SliderBaseProps & {\n /**\n * When false or omitted, the slider has a single thumb\n */\n range?: false\n /**\n * The current value of the slider\n */\n value: number\n /**\n * Callback function invoked when the slider value changes.\n */\n onChange: (value: number) => void\n /**\n * Value that will be offered as a \"reset to default\" option\n */\n resetValue?: number\n /**\n * String that will be displayed in the tooltip\n * @default Reset to default\n */\n resetTooltip?: string\n}\n\ntype RangeSliderProps = SliderBaseProps & {\n /**\n * When true, the slider supports multiple thumbs\n */\n range: true\n /**\n * Array of values, one per thumb\n */\n value: number[]\n /**\n * Callback function invoked when slider values change.\n */\n onChange: (value: number[]) => void\n /**\n * Minimum number of steps between thumbs\n */\n minStepsBetweenThumbs?: number\n}\n\nexport type SliderProps = SingleSliderProps | RangeSliderProps\n\nexport const Slider = memo((props: SliderProps) => {\n const {\n className,\n onChange,\n min,\n max,\n step,\n disabled,\n value,\n onBlur,\n onFocus,\n unit,\n prefixUnit,\n label,\n marks: propMarks = [],\n trackColor,\n rangeColor,\n orientation = \"horizontal\",\n ref: forwardedRef,\n } = props\n\n const isRange = props.range === true\n const isVertical = orientation === \"vertical\"\n\n const id = useId()\n const precision = useMemo(() => String(step).split(\".\")[1]?.length ?? 0, [step])\n\n // Single-mode state for editable input\n const singleValue = isRange ? 0 : (value as number)\n const [inputValue, setInputValue] = useState<string>(\n isRange ? \"\" : String((value as number).toFixed(precision)),\n )\n const setInputValueNumber = useCallback(\n (nextValue: number) => {\n setInputValue(nextValue.toFixed(precision))\n },\n [precision],\n )\n\n // prevents input from jumping around while the user is typing\n const singleOnChange = isRange ? undefined : (onChange as (v: number) => void)\n const debouncedOnChange = useDebounceCallback(singleOnChange ?? (() => {}), 250)\n const [pointerDown, setPointerDown] = useState(false)\n const isMounted = useIsMounted()\n\n // Used to position the input over the thumb\n const isTabletAndUp = useBreakpoint(\"md\")\n const inputRef = useRef<HTMLInputElement>(null)\n\n // The input width is based on the number of characters in the input / font size\n const inputWidth = Math.max(inputValue.length, 1) * (isTabletAndUp ? 7.8 : 9.5)\n\n // Calculate animation duration based on the distance the thumb needs to move\n // Skip custom animation for range mode — let Radix handle it\n const percent = isRange ? 0 : ((value as number) - min) / (max - min)\n const previousPercent = usePrevious(percent)\n const animationDurationMS =\n isRange || !isMounted || pointerDown\n ? 0\n : Math.max(Math.abs(percent - previousPercent) * 300, 100)\n\n // We assume that the width of the thumb does not change from render to render\n const thumbRef = useRef<HTMLDivElement>(null)\n\n // Sort marks for collision detection\n const marks = useMemo<SliderMark[]>(\n () => [...propMarks].sort((a, b) => a.value - b.value),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [propMarks.length],\n )\n\n // Validate marks\n useEffect(() => {\n if (!marks) return\n const markValues = new Set<number>()\n for (const mark of marks) {\n if (mark.value < min || mark.value > max) {\n throw new Error(`Slider mark value ${mark.value} is out of bounds [${min}, ${max}]`)\n }\n if (markValues.has(mark.value)) {\n throw new Error(\"Slider marks must have unique values\")\n }\n markValues.add(mark.value)\n }\n }, [marks, max, min])\n\n // Update value if out of bounds (single mode only)\n const latestValue = useLatestValue(value)\n const latestOnChange = useLatestValue(onChange)\n useEffect(() => {\n if (isRange) return\n const v = latestValue.current as number\n const clamped = clamp(v, min, max)\n if (clamped !== v) {\n ;(latestOnChange.current as (v: number) => void)(clamped)\n setInputValueNumber(clamped)\n }\n }, [max, min, latestValue, latestOnChange, setInputValueNumber, isRange])\n\n useEffect(() => {\n if (isRange) return\n if (inputRef.current !== document.activeElement) {\n setInputValueNumber(value as number)\n }\n }, [value, setInputValueNumber, isRange])\n\n // Single-mode keyboard handler\n const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (evt) => {\n if (isRange || !singleOnChange) return\n if (evt.key === \"Home\") {\n evt.preventDefault()\n singleOnChange(min)\n setInputValueNumber(min)\n }\n if (evt.key === \"End\") {\n evt.preventDefault()\n singleOnChange(max)\n setInputValueNumber(max)\n }\n if (evt.key === \"ArrowUp\") {\n evt.preventDefault()\n const multiplier = evt.shiftKey ? 10 : 1\n const next = clamp(singleValue + step * multiplier, min, max)\n singleOnChange(next)\n setInputValueNumber(next)\n }\n if (evt.key === \"ArrowDown\") {\n evt.preventDefault()\n const multiplier = evt.shiftKey ? 10 : 1\n const next = clamp(singleValue - step * multiplier, min, max)\n singleOnChange(next)\n setInputValueNumber(next)\n }\n if (evt.key === \"Enter\" || evt.key === \"Escape\") {\n evt.preventDefault()\n evt.currentTarget.blur()\n }\n }\n\n const handleInputChange: ChangeEventHandler<HTMLInputElement> = (evt) => {\n if (isRange || !singleOnChange) return\n const nextValue = evt.currentTarget.value.replace(/[^\\d.]/, \"\").trim()\n let parsed = parseFloat(nextValue || \"0\")\n parsed = clamp(parsed, min, max)\n if (step >= 1) {\n parsed = Math.floor(parsed)\n }\n debouncedOnChange(parsed)\n setInputValue(nextValue)\n }\n\n const handleInputBlur: FocusEventHandler<HTMLInputElement> = (evt) => {\n if (isRange || !singleOnChange) return\n let parsed = parseFloat(evt.target.value.trim()) || 0\n if (step >= 1) {\n parsed = Math.floor(parsed)\n } else {\n parsed = round(parsed, precision)\n }\n parsed = clamp(parsed, min, max)\n if (parsed !== singleValue) {\n singleOnChange(parsed)\n }\n setInputValueNumber(parsed)\n onBlur?.(evt)\n }\n\n // Radix value/onChange wiring\n const radixValue = isRange ? (value as number[]) : [value as number]\n const handleValueChange = isRange\n ? (values: number[]) => (onChange as (v: number[]) => void)(values)\n : (values: number[]) => (onChange as (v: number) => void)(values[0])\n\n // Single-mode props\n const resetValue = !isRange ? (props as SingleSliderProps).resetValue : undefined\n const resetTooltip = !isRange\n ? (props as SingleSliderProps).resetTooltip ?? \"Reset to default\"\n : undefined\n\n // Range-mode props\n const minStepsBetweenThumbs = isRange\n ? (props as RangeSliderProps).minStepsBetweenThumbs\n : undefined\n\n // Show marks only in horizontal orientation\n const showMarks = marks.length > 0 && !isVertical\n\n // Format range display text\n const rangeDisplayText = isRange\n ? (value as number[])\n .map((v) => `${prefixUnit ?? \"\"}${v.toFixed(precision)}${unit ?? \"\"}`)\n .join(\" – \")\n : \"\"\n\n return (\n <div\n className={clsx(s.SliderWrap, className)}\n data-orientation={orientation}\n >\n {label && (\n <div className={s.SliderLabel}>\n <label htmlFor={id} className=\"flex-1\">\n {label}\n </label>\n {!isRange && resetValue !== undefined && (\n <Tooltip content={resetTooltip!} compact>\n <Button\n size=\"2xs\"\n variant=\"ghost\"\n color=\"secondary\"\n className={s.Reset}\n data-hide={disabled || (resetValue === singleValue && !pointerDown)}\n onClick={() => singleOnChange!(resetValue)}\n >\n <Reload />\n </Button>\n </Tooltip>\n )}\n {isRange ? (\n <span className={s.RangeDisplay}>{rangeDisplayText}</span>\n ) : (\n <div className={s.SliderValue} onClick={() => inputRef.current?.focus()}>\n {prefixUnit && <span className={s.ValueUnit}>{prefixUnit}</span>}\n <input\n id={id}\n className={s.ValueInput}\n ref={inputRef}\n style={{ width: `${Math.ceil(inputWidth)}px` }}\n onKeyDown={handleKeyDown}\n value={inputValue}\n type=\"text\"\n onClick={(e) => e.stopPropagation()}\n onBlur={handleInputBlur}\n onFocus={(e) => {\n e.currentTarget.setSelectionRange(0, e.currentTarget.value.length)\n onFocus?.(e)\n }}\n onChange={handleInputChange}\n disabled={disabled}\n />\n {unit && <span className={s.ValueUnit}>{unit}</span>}\n </div>\n )}\n </div>\n )}\n <div className={s.SliderContainer}>\n <RadixSlider.Root\n ref={forwardedRef}\n className={s.Slider}\n onValueChange={handleValueChange}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n value={radixValue}\n orientation={orientation}\n minStepsBetweenThumbs={minStepsBetweenThumbs}\n onBlur={onBlur}\n onFocus={onFocus}\n onPointerDown={() => setPointerDown(true)}\n onPointerUp={() => setPointerDown(false)}\n style={toCssVariables({\n \"slider-duration\": `${animationDurationMS}ms`,\n \"slider-track-color\": trackColor,\n \"slider-range-color\": rangeColor,\n })}\n >\n <RadixSlider.Track className={s.Track}>\n <RadixSlider.Range className={s.Range} />\n </RadixSlider.Track>\n {isRange ? (\n (value as number[]).map((_, i) => (\n <RadixSlider.Thumb key={i} className={s.Thumb} />\n ))\n ) : (\n <RadixSlider.Thumb className={s.Thumb} ref={thumbRef} />\n )}\n </RadixSlider.Root>\n {showMarks && <SliderMarks marks={marks} thumbRef={thumbRef} min={min} max={max} />}\n </div>\n </div>\n )\n})\n\ntype MarksProps = {\n marks: SliderMark[]\n thumbRef: React.RefObject<HTMLDivElement | null>\n min: number\n max: number\n}\n\n// The minimum difference we enforce between marks, in pixels\nconst MINIMUM_MARK_SPACING_PX = 16\n\nconst SliderMarks = memo(({ marks, thumbRef, min, max }: MarksProps) => {\n // We seperate the container and measure divs so that our resize observer\n // does not fire when we adjust the height of the marks container.\n const marksContainerRef = useRef<HTMLDivElement>(null)\n const measureRef = useRef<HTMLDivElement>(null)\n\n // We measure the width of the slider within this component so that sliders\n // without marks do not pay a performance penalty.\n // @ts-expect-error(2322) -- bug in types: https://github.com/juliencrn/usehooks-ts/issues/663\n const { width: sliderWidth } = useResizeObserver({ ref: measureRef })\n\n useLayoutEffect(() => {\n // Impossible\n if (!thumbRef.current || !marksContainerRef.current) {\n return\n }\n\n const markElements = Array.from(\n marksContainerRef.current.querySelectorAll(\"[data-mark]\"),\n ) as HTMLDivElement[]\n\n const thumbHalfWidth = thumbRef.current.getBoundingClientRect().width / 2\n\n // Wipe all styles so we can accurately determine the positions of the marks\n markElements.forEach((markEl) => {\n markEl.style.width = \"\"\n markEl.style.display = \"\"\n })\n\n const positionMarks = () => {\n if (!sliderWidth) {\n return\n }\n\n markElements.forEach((markEl, index) => {\n const markValue = marks[index].value\n const valuePercent = (markValue - min) / (max - min)\n const markWidth = markEl.getBoundingClientRect().width\n const markHalfWidth = markWidth / 2\n\n // Radix smoothes the width of the thumb over the entire track so\n // that the thumb is always within bounds. We need to duplicate\n // this smoothing so that the marks match the observed breakpoints.\n const smoothedThumb = thumbHalfWidth - valuePercent * (thumbHalfWidth / 0.5)\n\n // Calculate where we would naively put the mark based on the value\n const naiveLeftOffset = valuePercent * sliderWidth\n\n // Add the thumb smoothing, and account for the width of the mark itself\n let left = naiveLeftOffset + smoothedThumb - markHalfWidth\n\n // Clamp to the left and right bounds of the container\n left = Math.max(0, Math.min(left, sliderWidth - markWidth))\n\n markEl.style.left = `${left}px`\n })\n }\n\n const marksHaveCollisions = () => {\n for (let i = 0; i < markElements.length - 1; i++) {\n const currentRect = markElements[i].getBoundingClientRect()\n const nextRect = markElements[i + 1].getBoundingClientRect()\n\n if (currentRect.right + MINIMUM_MARK_SPACING_PX > nextRect.left) {\n return true\n }\n }\n return false\n }\n\n positionMarks()\n\n // If we have collisions, try to wrap the content\n if (marksHaveCollisions()) {\n markElements.forEach((markEl) => {\n markEl.style.width = \"min-content\"\n })\n\n positionMarks()\n\n // Hide all marks if we still have collisions\n if (marksHaveCollisions()) {\n markElements.forEach((markEl) => {\n markEl.style.display = \"none\"\n })\n }\n }\n\n const tallestHeight = Math.max(\n ...markElements.map((markEl) => markEl.getBoundingClientRect().height),\n )\n\n // Give our container a height so that we allocate space on the page\n // for the marks and push down other content when we have to wrap\n marksContainerRef.current.style.height = tallestHeight > 0 ? `${tallestHeight}px` : \"\"\n }, [marks, thumbRef, marksContainerRef, min, max, sliderWidth])\n\n return (\n <div className={s.MarksContainer} ref={marksContainerRef}>\n <div ref={measureRef}>\n {marks.map((mark) => (\n <div key={mark.value} className={s.Mark} data-mark>\n <span className={s.MarkLabel}>{mark.label}</span>\n </div>\n ))}\n </div>\n </div>\n )\n})\n"]}
@@ -20,10 +20,36 @@
20
20
  user-select: none;
21
21
  }
22
22
 
23
- /* Dirty reach for radix internal to support animated changes */
24
- .Slider span:nth-child(2) {
23
+ /* Animate thumb position for single-mode programmatic changes */
24
+ .Slider .Thumb {
25
25
  transition: left var(--slider-duration) var(--cubic-move);
26
- }.Track {
26
+ }/* =============================================
27
+ Vertical orientation
28
+ ============================================= */.SliderWrap[data-orientation="vertical"] {
29
+ display: inline-flex;
30
+ flex-direction: column;
31
+ align-items: center;
32
+ height: var(--slider-vertical-height, 200px);
33
+ }.SliderWrap[data-orientation="vertical"] .SliderLabel {
34
+ margin-bottom: 8px;
35
+ }.SliderWrap[data-orientation="vertical"] .SliderContainer {
36
+ height: 100%;
37
+ width: auto;
38
+ }.SliderWrap[data-orientation="vertical"] .Slider {
39
+ flex-direction: column;
40
+ width: auto;
41
+ height: 100%;
42
+ padding-bottom: 0;
43
+ padding-right: 6px;
44
+ }.SliderWrap[data-orientation="vertical"] .Track {
45
+ width: 4px;
46
+ height: 100%;
47
+ }.SliderWrap[data-orientation="vertical"] .Range {
48
+ width: 100%;
49
+ height: auto;
50
+ }.SliderWrap[data-orientation="vertical"] .Thumb {
51
+ transition: none;
52
+ }.Track {
27
53
  position: relative;
28
54
  flex-grow: 1;
29
55
  width: 100%;
@@ -166,5 +192,12 @@
166
192
  overflow-wrap: break-word;
167
193
  text-align: center;
168
194
  word-wrap: break-word;
195
+ }/* =============================================
196
+ Range display (read-only value for range mode)
197
+ ============================================= */.RangeDisplay {
198
+ color: var(--color-text);
199
+ font-size: 13px;
200
+ font-variant-numeric: tabular-nums;
201
+ white-space: nowrap;
169
202
  }
170
203
  }