@bubo-squared/ui-framework 0.1.97 → 0.1.98

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.cjs CHANGED
@@ -2349,10 +2349,16 @@ SearchInput.displayName = "SearchInput";
2349
2349
  var React29 = __toESM(require("react"), 1);
2350
2350
  var import_jsx_runtime31 = require("react/jsx-runtime");
2351
2351
  var wrapperBase2 = "flex flex-col gap-2 items-start";
2352
+ var isRangeProps = (props) => {
2353
+ return Array.isArray(props.value) || Array.isArray(props.defaultValue);
2354
+ };
2355
+ var toArray = (value) => {
2356
+ if (value === void 0) return void 0;
2357
+ return Array.isArray(value) ? value : [value];
2358
+ };
2352
2359
  var Slider = (props) => {
2353
2360
  const {
2354
2361
  display = "flat",
2355
- type = "single",
2356
2362
  tooltipPlacement = "top",
2357
2363
  tooltipFormatter,
2358
2364
  showPercentage = true,
@@ -2362,11 +2368,11 @@ var Slider = (props) => {
2362
2368
  disabled = false,
2363
2369
  value,
2364
2370
  defaultValue,
2365
- onValueChange,
2366
2371
  className
2367
2372
  } = props;
2373
+ const isRange = isRangeProps(props);
2368
2374
  const isControlled = value !== void 0;
2369
- const expectedLength = type === "multi" ? 2 : 1;
2375
+ const expectedLength = isRange ? 2 : 1;
2370
2376
  const normalizeArray = React29.useCallback(
2371
2377
  (arr, fallback) => {
2372
2378
  if (!arr || arr.length === 0) return fallback;
@@ -2380,22 +2386,25 @@ var Slider = (props) => {
2380
2386
  [expectedLength, max]
2381
2387
  );
2382
2388
  const defaultInternal = React29.useMemo(() => {
2383
- if (defaultValue) return normalizeArray(defaultValue, []);
2384
- if (type === "multi") return [min, Math.min(min + (max - min) / 4, max)];
2389
+ const defaultValueArray = toArray(defaultValue);
2390
+ if (defaultValueArray) return normalizeArray(defaultValueArray, []);
2391
+ if (isRange) return [min, Math.min(min + (max - min) / 4, max)];
2385
2392
  return [min + (max - min) / 3];
2386
- }, [defaultValue, min, max, type, normalizeArray]);
2393
+ }, [defaultValue, min, max, isRange, normalizeArray]);
2387
2394
  const [internalValue, setInternalValue] = React29.useState(
2388
- () => normalizeArray(isControlled ? value : defaultInternal, defaultInternal)
2395
+ () => normalizeArray(isControlled ? toArray(value) : defaultInternal, defaultInternal)
2389
2396
  );
2390
2397
  React29.useEffect(() => {
2391
2398
  if (isControlled) {
2392
2399
  setInternalValue(
2393
- (current2) => normalizeArray(value, current2.length ? current2 : defaultInternal)
2400
+ (current2) => normalizeArray(toArray(value), current2.length ? current2 : defaultInternal)
2394
2401
  );
2395
2402
  }
2396
2403
  }, [isControlled, value, normalizeArray, defaultInternal]);
2397
2404
  const current = internalValue;
2398
2405
  const trackRef = React29.useRef(null);
2406
+ const [draggingThumbIndex, setDraggingThumbIndex] = React29.useState(null);
2407
+ const [hoveredThumbIndex, setHoveredThumbIndex] = React29.useState(null);
2399
2408
  const clamp = (val) => {
2400
2409
  if (val < min) return min;
2401
2410
  if (val > max) return max;
@@ -2405,13 +2414,13 @@ var Slider = (props) => {
2405
2414
  if (!isControlled) {
2406
2415
  setInternalValue((prev) => {
2407
2416
  const clamped = prev.map((v) => clamp(v));
2408
- if (type === "multi" && clamped.length === 2 && step > 0) {
2417
+ if (isRange && clamped.length === 2 && step > 0) {
2409
2418
  return enforceMinGap(clamped, prev);
2410
2419
  }
2411
2420
  return clamped;
2412
2421
  });
2413
2422
  }
2414
- }, [isControlled, min, max]);
2423
+ }, [isControlled, min, max, isRange]);
2415
2424
  const snap = (val) => {
2416
2425
  const range = max - min;
2417
2426
  if (range <= 0 || step <= 0) return clamp(val);
@@ -2419,7 +2428,7 @@ var Slider = (props) => {
2419
2428
  return clamp(min + stepsFromMin * step);
2420
2429
  };
2421
2430
  const enforceMinGap = (next, prev) => {
2422
- if (type !== "multi" || next.length !== 2 || step <= 0) return next;
2431
+ if (!isRange || next.length !== 2 || step <= 0) return next;
2423
2432
  let [low, high] = next;
2424
2433
  const [prevLow, prevHigh] = prev.length === 2 ? prev : next;
2425
2434
  if (low > high) {
@@ -2444,13 +2453,17 @@ var Slider = (props) => {
2444
2453
  };
2445
2454
  const updateValue = (next) => {
2446
2455
  let normalized = normalizeArray(next, current);
2447
- if (type === "multi" && normalized.length === 2) {
2456
+ if (isRange && normalized.length === 2) {
2448
2457
  normalized = enforceMinGap(normalized, current);
2449
2458
  }
2450
2459
  if (!isControlled) {
2451
2460
  setInternalValue(normalized);
2452
2461
  }
2453
- onValueChange?.(normalized);
2462
+ if (isRangeProps(props)) {
2463
+ props.onValueChange?.([normalized[0] ?? min, normalized[1] ?? max]);
2464
+ } else {
2465
+ props.onValueChange?.(normalized[0] ?? min);
2466
+ }
2454
2467
  };
2455
2468
  const getSnappedValueFromClientX = (clientX, track) => {
2456
2469
  const rect = track.getBoundingClientRect();
@@ -2463,10 +2476,11 @@ var Slider = (props) => {
2463
2476
  if (disabled) return;
2464
2477
  const track = trackRef.current;
2465
2478
  if (!track) return;
2479
+ setDraggingThumbIndex(thumbIndex);
2466
2480
  const handlePointerMove = (event) => {
2467
2481
  if (disabled) return;
2468
2482
  const snapped = getSnappedValueFromClientX(event.clientX, track);
2469
- if (type === "multi" && current.length === 2) {
2483
+ if (isRange && current.length === 2) {
2470
2484
  const [a, b] = current;
2471
2485
  if (thumbIndex === 0) {
2472
2486
  updateValue([snapped, b]);
@@ -2480,9 +2494,11 @@ var Slider = (props) => {
2480
2494
  const handlePointerUp = () => {
2481
2495
  window.removeEventListener("pointermove", handlePointerMove);
2482
2496
  window.removeEventListener("pointerup", handlePointerUp);
2497
+ window.removeEventListener("pointercancel", handlePointerUp);
2498
+ setDraggingThumbIndex(null);
2483
2499
  };
2484
2500
  const initialSnapped = getSnappedValueFromClientX(clientX, track);
2485
- if (type === "multi" && current.length === 2) {
2501
+ if (isRange && current.length === 2) {
2486
2502
  const [a, b] = current;
2487
2503
  if (thumbIndex === 0) {
2488
2504
  updateValue([initialSnapped, b]);
@@ -2494,6 +2510,7 @@ var Slider = (props) => {
2494
2510
  }
2495
2511
  window.addEventListener("pointermove", handlePointerMove);
2496
2512
  window.addEventListener("pointerup", handlePointerUp);
2513
+ window.addEventListener("pointercancel", handlePointerUp);
2497
2514
  };
2498
2515
  const handleTrackPointerDown = (event) => {
2499
2516
  if (disabled) return;
@@ -2502,7 +2519,7 @@ var Slider = (props) => {
2502
2519
  if (!track) return;
2503
2520
  const snapped = getSnappedValueFromClientX(event.clientX, track);
2504
2521
  let thumbIndex = 0;
2505
- if (type === "multi" && current.length === 2) {
2522
+ if (isRange && current.length === 2) {
2506
2523
  const [a, b] = current;
2507
2524
  const distToA = Math.abs(snapped - a);
2508
2525
  const distToB = Math.abs(snapped - b);
@@ -2540,7 +2557,7 @@ var Slider = (props) => {
2540
2557
  event.preventDefault();
2541
2558
  }
2542
2559
  };
2543
- const [primary, secondary] = type === "multi" && current.length === 2 ? [current[0], current[1]] : [current[0], void 0];
2560
+ const [primary, secondary] = isRange && current.length === 2 ? [current[0], current[1]] : [current[0], void 0];
2544
2561
  const valueToPercent = (val) => {
2545
2562
  if (val === void 0) return 0;
2546
2563
  if (max === min) return 0;
@@ -2573,7 +2590,7 @@ var Slider = (props) => {
2573
2590
  return formatNumber(value2);
2574
2591
  };
2575
2592
  const formatNumericLabel = () => {
2576
- if (type === "multi" && secondary !== void 0) {
2593
+ if (isRange && secondary !== void 0) {
2577
2594
  if (!tooltipFormatter && showPercentage && display === "numeric") {
2578
2595
  const first = formatNumber(valueToPercent(primary));
2579
2596
  const second = formatNumber(valueToPercent(secondary));
@@ -2586,10 +2603,14 @@ var Slider = (props) => {
2586
2603
  const trackHeight = 32;
2587
2604
  const thumbWidth = 18;
2588
2605
  const thumbRadius = thumbWidth / 2;
2589
- const renderTooltipBubble = (key, percent, labelText) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2606
+ const renderTooltipBubble = (key, percent, labelText, isVisible) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2590
2607
  "div",
2591
2608
  {
2592
- className: "absolute -translate-x-1/2 flex flex-col items-center",
2609
+ className: cn(
2610
+ "absolute -translate-x-1/2 flex flex-col items-center",
2611
+ "transition-[opacity,transform] duration-150",
2612
+ isVisible ? "opacity-100 scale-100 pointer-events-auto" : "opacity-0 scale-95 pointer-events-none"
2613
+ ),
2593
2614
  style: {
2594
2615
  left: `${percent}%`,
2595
2616
  bottom: isTooltipAbove ? "100%" : void 0,
@@ -2600,17 +2621,14 @@ var Slider = (props) => {
2600
2621
  children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2601
2622
  "div",
2602
2623
  {
2603
- className: cn(
2604
- "relative rounded-4 shadow-card-md px-(--space-8) py-(--space-4)",
2605
- disabled ? "bg-(--background-primary-disabled)" : "bg-(--background-neutral)"
2606
- ),
2624
+ className: cn("relative rounded-4 shadow-card-md px-(--space-8) py-(--space-4) bg-(--background-tooltip)"),
2607
2625
  children: [
2608
2626
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2609
2627
  "p",
2610
2628
  {
2611
2629
  className: cn(
2612
2630
  "paragraph-s",
2613
- disabled ? "text-primary-disabled" : "text-primary"
2631
+ disabled ? "text-secondary" : "text-primary"
2614
2632
  ),
2615
2633
  children: labelText
2616
2634
  }
@@ -2619,8 +2637,7 @@ var Slider = (props) => {
2619
2637
  "div",
2620
2638
  {
2621
2639
  className: cn(
2622
- "absolute left-1/2 -translate-x-1/2 w-2 h-2 rotate-45",
2623
- disabled ? "bg-(--background-primary-disabled)" : "bg-(--background-neutral)",
2640
+ "absolute left-1/2 -translate-x-1/2 w-2 h-2 rotate-45 bg-(--background-tooltip)",
2624
2641
  isTooltipAbove ? "-bottom-1" : "-top-1"
2625
2642
  )
2626
2643
  }
@@ -2633,6 +2650,7 @@ var Slider = (props) => {
2633
2650
  );
2634
2651
  const renderHandle = (index, percent, ariaValueText) => {
2635
2652
  const val = index === 0 ? primary : secondary;
2653
+ const isDragging = draggingThumbIndex === index;
2636
2654
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2637
2655
  "button",
2638
2656
  {
@@ -2646,12 +2664,21 @@ var Slider = (props) => {
2646
2664
  tabIndex: disabled ? -1 : 0,
2647
2665
  className: cn(
2648
2666
  "absolute -translate-x-1/2 flex items-center justify-center",
2649
- "h-8 w-[18px] rounded-4",
2667
+ "h-8 w-4.5 rounded-4",
2668
+ "transition-shadow duration-150",
2669
+ !disabled && (isDragging ? "shadow-[0_0_0_12px_var(--slider-halo-color)]" : "hover:shadow-[0_0_0_8px_var(--slider-halo-color)]"),
2650
2670
  disabled ? "bg-(--icon-primary-disabled) cursor-default" : "bg-(--icon-primary-hover) outline-none focus-visible:shadow-[0_0_0_var(--focus-ring-spread)_var(--focus-primary)] cursor-pointer"
2651
2671
  ),
2652
2672
  style: {
2653
2673
  left: `${percent}%`,
2654
- top: `calc(50% - ${trackHeight / 2}px)`
2674
+ top: `calc(50% - ${trackHeight / 2}px)`,
2675
+ ["--slider-halo-color"]: "color-mix(in srgb, var(--color-brand) 10%, transparent)"
2676
+ },
2677
+ onPointerEnter: () => {
2678
+ setHoveredThumbIndex(index);
2679
+ },
2680
+ onPointerLeave: () => {
2681
+ setHoveredThumbIndex((prev) => prev === index ? null : prev);
2655
2682
  },
2656
2683
  onPointerDown: (event) => {
2657
2684
  if (disabled) return;
@@ -2666,8 +2693,18 @@ var Slider = (props) => {
2666
2693
  };
2667
2694
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: wrapperBase2, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: cn("w-full flex flex-col gap-1", className), children: [
2668
2695
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "relative w-full", children: [
2669
- showTooltip && primary !== void 0 && renderTooltipBubble("primary", primaryPercent, formatDisplayValue(primary)),
2670
- showTooltip && type === "multi" && secondary !== void 0 && renderTooltipBubble("secondary", secondaryPercent, formatDisplayValue(secondary)),
2696
+ showTooltip && primary !== void 0 && renderTooltipBubble(
2697
+ "primary",
2698
+ primaryPercent,
2699
+ formatDisplayValue(primary),
2700
+ hoveredThumbIndex === 0 || draggingThumbIndex === 0
2701
+ ),
2702
+ showTooltip && isRange && secondary !== void 0 && renderTooltipBubble(
2703
+ "secondary",
2704
+ secondaryPercent,
2705
+ formatDisplayValue(secondary),
2706
+ hoveredThumbIndex === 1 || draggingThumbIndex === 1
2707
+ ),
2671
2708
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
2672
2709
  "div",
2673
2710
  {
@@ -2693,7 +2730,7 @@ var Slider = (props) => {
2693
2730
  }
2694
2731
  ),
2695
2732
  renderHandle(0, primaryPercent, formatDisplayValue(primary)),
2696
- type === "multi" && secondary !== void 0 && renderHandle(1, secondaryPercent, formatDisplayValue(secondary))
2733
+ isRange && secondary !== void 0 && renderHandle(1, secondaryPercent, formatDisplayValue(secondary))
2697
2734
  ]
2698
2735
  }
2699
2736
  )