@bubo-squared/ui-framework 0.1.97 → 0.1.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -382,11 +382,9 @@ interface SearchInputProps extends Omit<React$1.InputHTMLAttributes<HTMLInputEle
382
382
  declare const SearchInput: React$1.FC<SearchInputProps>;
383
383
 
384
384
  type SliderDisplay = "flat" | "numeric" | "tooltip";
385
- type SliderType = "single" | "multi";
386
385
  type SliderTooltipPlacement = "top" | "bottom";
387
- interface SliderProps {
386
+ type SliderBaseProps = {
388
387
  display?: SliderDisplay;
389
- type?: SliderType;
390
388
  tooltipPlacement?: SliderTooltipPlacement;
391
389
  /**
392
390
  * Custom formatter for tooltip / numeric / accessibility text.
@@ -404,18 +402,19 @@ interface SliderProps {
404
402
  max?: number;
405
403
  step?: number;
406
404
  disabled?: boolean;
407
- /**
408
- * Current value(s). For `type="single"`, use a single-element array, e.g. `[30]`.
409
- * For `type="multi"`, use two values, e.g. `[0, 25]`.
410
- */
411
- value?: number[];
412
- /**
413
- * Initial value(s) for uncontrolled usage.
414
- */
415
- defaultValue?: number[];
416
- onValueChange?: (value: number[]) => void;
417
405
  className?: string;
418
- }
406
+ };
407
+ type SliderSingleProps = SliderBaseProps & {
408
+ value?: number;
409
+ defaultValue?: number;
410
+ onValueChange?: (value: number) => void;
411
+ };
412
+ type SliderRangeProps = SliderBaseProps & {
413
+ value?: [number, number];
414
+ defaultValue?: [number, number];
415
+ onValueChange?: (value: [number, number]) => void;
416
+ };
417
+ type SliderProps = SliderSingleProps | SliderRangeProps;
419
418
  declare const Slider: React$1.FC<SliderProps>;
420
419
 
421
420
  type TextAreaType = "responsive" | "character-limit" | "plain";
package/dist/index.d.ts CHANGED
@@ -382,11 +382,9 @@ interface SearchInputProps extends Omit<React$1.InputHTMLAttributes<HTMLInputEle
382
382
  declare const SearchInput: React$1.FC<SearchInputProps>;
383
383
 
384
384
  type SliderDisplay = "flat" | "numeric" | "tooltip";
385
- type SliderType = "single" | "multi";
386
385
  type SliderTooltipPlacement = "top" | "bottom";
387
- interface SliderProps {
386
+ type SliderBaseProps = {
388
387
  display?: SliderDisplay;
389
- type?: SliderType;
390
388
  tooltipPlacement?: SliderTooltipPlacement;
391
389
  /**
392
390
  * Custom formatter for tooltip / numeric / accessibility text.
@@ -404,18 +402,19 @@ interface SliderProps {
404
402
  max?: number;
405
403
  step?: number;
406
404
  disabled?: boolean;
407
- /**
408
- * Current value(s). For `type="single"`, use a single-element array, e.g. `[30]`.
409
- * For `type="multi"`, use two values, e.g. `[0, 25]`.
410
- */
411
- value?: number[];
412
- /**
413
- * Initial value(s) for uncontrolled usage.
414
- */
415
- defaultValue?: number[];
416
- onValueChange?: (value: number[]) => void;
417
405
  className?: string;
418
- }
406
+ };
407
+ type SliderSingleProps = SliderBaseProps & {
408
+ value?: number;
409
+ defaultValue?: number;
410
+ onValueChange?: (value: number) => void;
411
+ };
412
+ type SliderRangeProps = SliderBaseProps & {
413
+ value?: [number, number];
414
+ defaultValue?: [number, number];
415
+ onValueChange?: (value: [number, number]) => void;
416
+ };
417
+ type SliderProps = SliderSingleProps | SliderRangeProps;
419
418
  declare const Slider: React$1.FC<SliderProps>;
420
419
 
421
420
  type TextAreaType = "responsive" | "character-limit" | "plain";
package/dist/index.js CHANGED
@@ -2288,10 +2288,16 @@ SearchInput.displayName = "SearchInput";
2288
2288
  import * as React29 from "react";
2289
2289
  import { jsx as jsx31, jsxs as jsxs21 } from "react/jsx-runtime";
2290
2290
  var wrapperBase2 = "flex flex-col gap-2 items-start";
2291
+ var isRangeProps = (props) => {
2292
+ return Array.isArray(props.value) || Array.isArray(props.defaultValue);
2293
+ };
2294
+ var toArray = (value) => {
2295
+ if (value === void 0) return void 0;
2296
+ return Array.isArray(value) ? value : [value];
2297
+ };
2291
2298
  var Slider = (props) => {
2292
2299
  const {
2293
2300
  display = "flat",
2294
- type = "single",
2295
2301
  tooltipPlacement = "top",
2296
2302
  tooltipFormatter,
2297
2303
  showPercentage = true,
@@ -2301,11 +2307,11 @@ var Slider = (props) => {
2301
2307
  disabled = false,
2302
2308
  value,
2303
2309
  defaultValue,
2304
- onValueChange,
2305
2310
  className
2306
2311
  } = props;
2312
+ const isRange = isRangeProps(props);
2307
2313
  const isControlled = value !== void 0;
2308
- const expectedLength = type === "multi" ? 2 : 1;
2314
+ const expectedLength = isRange ? 2 : 1;
2309
2315
  const normalizeArray = React29.useCallback(
2310
2316
  (arr, fallback) => {
2311
2317
  if (!arr || arr.length === 0) return fallback;
@@ -2319,22 +2325,25 @@ var Slider = (props) => {
2319
2325
  [expectedLength, max]
2320
2326
  );
2321
2327
  const defaultInternal = React29.useMemo(() => {
2322
- if (defaultValue) return normalizeArray(defaultValue, []);
2323
- if (type === "multi") return [min, Math.min(min + (max - min) / 4, max)];
2328
+ const defaultValueArray = toArray(defaultValue);
2329
+ if (defaultValueArray) return normalizeArray(defaultValueArray, []);
2330
+ if (isRange) return [min, Math.min(min + (max - min) / 4, max)];
2324
2331
  return [min + (max - min) / 3];
2325
- }, [defaultValue, min, max, type, normalizeArray]);
2332
+ }, [defaultValue, min, max, isRange, normalizeArray]);
2326
2333
  const [internalValue, setInternalValue] = React29.useState(
2327
- () => normalizeArray(isControlled ? value : defaultInternal, defaultInternal)
2334
+ () => normalizeArray(isControlled ? toArray(value) : defaultInternal, defaultInternal)
2328
2335
  );
2329
2336
  React29.useEffect(() => {
2330
2337
  if (isControlled) {
2331
2338
  setInternalValue(
2332
- (current2) => normalizeArray(value, current2.length ? current2 : defaultInternal)
2339
+ (current2) => normalizeArray(toArray(value), current2.length ? current2 : defaultInternal)
2333
2340
  );
2334
2341
  }
2335
2342
  }, [isControlled, value, normalizeArray, defaultInternal]);
2336
2343
  const current = internalValue;
2337
2344
  const trackRef = React29.useRef(null);
2345
+ const [draggingThumbIndex, setDraggingThumbIndex] = React29.useState(null);
2346
+ const [hoveredThumbIndex, setHoveredThumbIndex] = React29.useState(null);
2338
2347
  const clamp = (val) => {
2339
2348
  if (val < min) return min;
2340
2349
  if (val > max) return max;
@@ -2344,13 +2353,13 @@ var Slider = (props) => {
2344
2353
  if (!isControlled) {
2345
2354
  setInternalValue((prev) => {
2346
2355
  const clamped = prev.map((v) => clamp(v));
2347
- if (type === "multi" && clamped.length === 2 && step > 0) {
2356
+ if (isRange && clamped.length === 2 && step > 0) {
2348
2357
  return enforceMinGap(clamped, prev);
2349
2358
  }
2350
2359
  return clamped;
2351
2360
  });
2352
2361
  }
2353
- }, [isControlled, min, max]);
2362
+ }, [isControlled, min, max, isRange]);
2354
2363
  const snap = (val) => {
2355
2364
  const range = max - min;
2356
2365
  if (range <= 0 || step <= 0) return clamp(val);
@@ -2358,7 +2367,7 @@ var Slider = (props) => {
2358
2367
  return clamp(min + stepsFromMin * step);
2359
2368
  };
2360
2369
  const enforceMinGap = (next, prev) => {
2361
- if (type !== "multi" || next.length !== 2 || step <= 0) return next;
2370
+ if (!isRange || next.length !== 2 || step <= 0) return next;
2362
2371
  let [low, high] = next;
2363
2372
  const [prevLow, prevHigh] = prev.length === 2 ? prev : next;
2364
2373
  if (low > high) {
@@ -2383,13 +2392,17 @@ var Slider = (props) => {
2383
2392
  };
2384
2393
  const updateValue = (next) => {
2385
2394
  let normalized = normalizeArray(next, current);
2386
- if (type === "multi" && normalized.length === 2) {
2395
+ if (isRange && normalized.length === 2) {
2387
2396
  normalized = enforceMinGap(normalized, current);
2388
2397
  }
2389
2398
  if (!isControlled) {
2390
2399
  setInternalValue(normalized);
2391
2400
  }
2392
- onValueChange?.(normalized);
2401
+ if (isRangeProps(props)) {
2402
+ props.onValueChange?.([normalized[0] ?? min, normalized[1] ?? max]);
2403
+ } else {
2404
+ props.onValueChange?.(normalized[0] ?? min);
2405
+ }
2393
2406
  };
2394
2407
  const getSnappedValueFromClientX = (clientX, track) => {
2395
2408
  const rect = track.getBoundingClientRect();
@@ -2402,10 +2415,11 @@ var Slider = (props) => {
2402
2415
  if (disabled) return;
2403
2416
  const track = trackRef.current;
2404
2417
  if (!track) return;
2418
+ setDraggingThumbIndex(thumbIndex);
2405
2419
  const handlePointerMove = (event) => {
2406
2420
  if (disabled) return;
2407
2421
  const snapped = getSnappedValueFromClientX(event.clientX, track);
2408
- if (type === "multi" && current.length === 2) {
2422
+ if (isRange && current.length === 2) {
2409
2423
  const [a, b] = current;
2410
2424
  if (thumbIndex === 0) {
2411
2425
  updateValue([snapped, b]);
@@ -2419,9 +2433,11 @@ var Slider = (props) => {
2419
2433
  const handlePointerUp = () => {
2420
2434
  window.removeEventListener("pointermove", handlePointerMove);
2421
2435
  window.removeEventListener("pointerup", handlePointerUp);
2436
+ window.removeEventListener("pointercancel", handlePointerUp);
2437
+ setDraggingThumbIndex(null);
2422
2438
  };
2423
2439
  const initialSnapped = getSnappedValueFromClientX(clientX, track);
2424
- if (type === "multi" && current.length === 2) {
2440
+ if (isRange && current.length === 2) {
2425
2441
  const [a, b] = current;
2426
2442
  if (thumbIndex === 0) {
2427
2443
  updateValue([initialSnapped, b]);
@@ -2433,6 +2449,7 @@ var Slider = (props) => {
2433
2449
  }
2434
2450
  window.addEventListener("pointermove", handlePointerMove);
2435
2451
  window.addEventListener("pointerup", handlePointerUp);
2452
+ window.addEventListener("pointercancel", handlePointerUp);
2436
2453
  };
2437
2454
  const handleTrackPointerDown = (event) => {
2438
2455
  if (disabled) return;
@@ -2441,7 +2458,7 @@ var Slider = (props) => {
2441
2458
  if (!track) return;
2442
2459
  const snapped = getSnappedValueFromClientX(event.clientX, track);
2443
2460
  let thumbIndex = 0;
2444
- if (type === "multi" && current.length === 2) {
2461
+ if (isRange && current.length === 2) {
2445
2462
  const [a, b] = current;
2446
2463
  const distToA = Math.abs(snapped - a);
2447
2464
  const distToB = Math.abs(snapped - b);
@@ -2479,7 +2496,7 @@ var Slider = (props) => {
2479
2496
  event.preventDefault();
2480
2497
  }
2481
2498
  };
2482
- const [primary, secondary] = type === "multi" && current.length === 2 ? [current[0], current[1]] : [current[0], void 0];
2499
+ const [primary, secondary] = isRange && current.length === 2 ? [current[0], current[1]] : [current[0], void 0];
2483
2500
  const valueToPercent = (val) => {
2484
2501
  if (val === void 0) return 0;
2485
2502
  if (max === min) return 0;
@@ -2512,7 +2529,7 @@ var Slider = (props) => {
2512
2529
  return formatNumber(value2);
2513
2530
  };
2514
2531
  const formatNumericLabel = () => {
2515
- if (type === "multi" && secondary !== void 0) {
2532
+ if (isRange && secondary !== void 0) {
2516
2533
  if (!tooltipFormatter && showPercentage && display === "numeric") {
2517
2534
  const first = formatNumber(valueToPercent(primary));
2518
2535
  const second = formatNumber(valueToPercent(secondary));
@@ -2525,10 +2542,14 @@ var Slider = (props) => {
2525
2542
  const trackHeight = 32;
2526
2543
  const thumbWidth = 18;
2527
2544
  const thumbRadius = thumbWidth / 2;
2528
- const renderTooltipBubble = (key, percent, labelText) => /* @__PURE__ */ jsx31(
2545
+ const renderTooltipBubble = (key, percent, labelText, isVisible) => /* @__PURE__ */ jsx31(
2529
2546
  "div",
2530
2547
  {
2531
- className: "absolute -translate-x-1/2 flex flex-col items-center",
2548
+ className: cn(
2549
+ "absolute -translate-x-1/2 flex flex-col items-center",
2550
+ "transition-[opacity,transform] duration-150",
2551
+ isVisible ? "opacity-100 scale-100 pointer-events-auto" : "opacity-0 scale-95 pointer-events-none"
2552
+ ),
2532
2553
  style: {
2533
2554
  left: `${percent}%`,
2534
2555
  bottom: isTooltipAbove ? "100%" : void 0,
@@ -2539,17 +2560,14 @@ var Slider = (props) => {
2539
2560
  children: /* @__PURE__ */ jsxs21(
2540
2561
  "div",
2541
2562
  {
2542
- className: cn(
2543
- "relative rounded-4 shadow-card-md px-(--space-8) py-(--space-4)",
2544
- disabled ? "bg-(--background-primary-disabled)" : "bg-(--background-neutral)"
2545
- ),
2563
+ className: cn("relative rounded-4 shadow-card-md px-(--space-8) py-(--space-4) bg-(--background-tooltip)"),
2546
2564
  children: [
2547
2565
  /* @__PURE__ */ jsx31(
2548
2566
  "p",
2549
2567
  {
2550
2568
  className: cn(
2551
2569
  "paragraph-s",
2552
- disabled ? "text-primary-disabled" : "text-primary"
2570
+ disabled ? "text-secondary" : "text-primary"
2553
2571
  ),
2554
2572
  children: labelText
2555
2573
  }
@@ -2558,8 +2576,7 @@ var Slider = (props) => {
2558
2576
  "div",
2559
2577
  {
2560
2578
  className: cn(
2561
- "absolute left-1/2 -translate-x-1/2 w-2 h-2 rotate-45",
2562
- disabled ? "bg-(--background-primary-disabled)" : "bg-(--background-neutral)",
2579
+ "absolute left-1/2 -translate-x-1/2 w-2 h-2 rotate-45 bg-(--background-tooltip)",
2563
2580
  isTooltipAbove ? "-bottom-1" : "-top-1"
2564
2581
  )
2565
2582
  }
@@ -2572,6 +2589,7 @@ var Slider = (props) => {
2572
2589
  );
2573
2590
  const renderHandle = (index, percent, ariaValueText) => {
2574
2591
  const val = index === 0 ? primary : secondary;
2592
+ const isDragging = draggingThumbIndex === index;
2575
2593
  return /* @__PURE__ */ jsx31(
2576
2594
  "button",
2577
2595
  {
@@ -2585,12 +2603,21 @@ var Slider = (props) => {
2585
2603
  tabIndex: disabled ? -1 : 0,
2586
2604
  className: cn(
2587
2605
  "absolute -translate-x-1/2 flex items-center justify-center",
2588
- "h-8 w-[18px] rounded-4",
2606
+ "h-8 w-4.5 rounded-4",
2607
+ "transition-shadow duration-150",
2608
+ !disabled && (isDragging ? "shadow-[0_0_0_12px_var(--slider-halo-color)]" : "hover:shadow-[0_0_0_8px_var(--slider-halo-color)]"),
2589
2609
  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"
2590
2610
  ),
2591
2611
  style: {
2592
2612
  left: `${percent}%`,
2593
- top: `calc(50% - ${trackHeight / 2}px)`
2613
+ top: `calc(50% - ${trackHeight / 2}px)`,
2614
+ ["--slider-halo-color"]: "color-mix(in srgb, var(--color-brand) 10%, transparent)"
2615
+ },
2616
+ onPointerEnter: () => {
2617
+ setHoveredThumbIndex(index);
2618
+ },
2619
+ onPointerLeave: () => {
2620
+ setHoveredThumbIndex((prev) => prev === index ? null : prev);
2594
2621
  },
2595
2622
  onPointerDown: (event) => {
2596
2623
  if (disabled) return;
@@ -2603,51 +2630,68 @@ var Slider = (props) => {
2603
2630
  index
2604
2631
  );
2605
2632
  };
2606
- return /* @__PURE__ */ jsx31("div", { className: wrapperBase2, children: /* @__PURE__ */ jsxs21("div", { className: cn("w-full flex flex-col gap-1", className), children: [
2607
- /* @__PURE__ */ jsxs21("div", { className: "relative w-full", children: [
2608
- showTooltip && primary !== void 0 && renderTooltipBubble("primary", primaryPercent, formatDisplayValue(primary)),
2609
- showTooltip && type === "multi" && secondary !== void 0 && renderTooltipBubble("secondary", secondaryPercent, formatDisplayValue(secondary)),
2610
- /* @__PURE__ */ jsxs21(
2611
- "div",
2612
- {
2613
- className: cn(
2614
- "relative w-full flex items-center rounded-4",
2615
- disabled ? "bg-(--background-primary-disabled) cursor-default" : "bg-(--background-secondary) cursor-pointer"
2633
+ return /* @__PURE__ */ jsx31(
2634
+ "div",
2635
+ {
2636
+ className: wrapperBase2,
2637
+ style: { marginInline: `${thumbRadius}px` },
2638
+ children: /* @__PURE__ */ jsxs21("div", { className: cn("w-full flex flex-col gap-1", className), children: [
2639
+ /* @__PURE__ */ jsxs21("div", { className: "relative w-full", children: [
2640
+ showTooltip && primary !== void 0 && renderTooltipBubble(
2641
+ "primary",
2642
+ primaryPercent,
2643
+ formatDisplayValue(primary),
2644
+ hoveredThumbIndex === 0 || draggingThumbIndex === 0
2616
2645
  ),
2617
- style: { height: `${trackHeight}px` },
2618
- ref: trackRef,
2619
- onPointerDown: handleTrackPointerDown,
2620
- children: [
2621
- /* @__PURE__ */ jsx31(
2622
- "div",
2623
- {
2624
- className: cn(
2625
- "absolute h-full rounded-4",
2626
- disabled ? "bg-(--background-primary-disabled)" : "bg-(--background-secondary)"
2646
+ showTooltip && isRange && secondary !== void 0 && renderTooltipBubble(
2647
+ "secondary",
2648
+ secondaryPercent,
2649
+ formatDisplayValue(secondary),
2650
+ hoveredThumbIndex === 1 || draggingThumbIndex === 1
2651
+ ),
2652
+ /* @__PURE__ */ jsxs21(
2653
+ "div",
2654
+ {
2655
+ className: cn(
2656
+ "relative w-full flex items-center rounded-4",
2657
+ disabled ? "bg-(--background-primary-disabled) cursor-default" : "bg-(--background-secondary) cursor-pointer"
2658
+ ),
2659
+ style: { height: `${trackHeight}px` },
2660
+ ref: trackRef,
2661
+ onPointerDown: handleTrackPointerDown,
2662
+ children: [
2663
+ /* @__PURE__ */ jsx31(
2664
+ "div",
2665
+ {
2666
+ className: cn(
2667
+ "absolute h-full rounded-4",
2668
+ disabled ? "bg-(--background-primary-disabled)" : "bg-(--background-secondary)"
2669
+ ),
2670
+ style: {
2671
+ width: `calc(100% + ${thumbWidth}px)`,
2672
+ left: `-${thumbRadius}px`
2673
+ }
2674
+ }
2627
2675
  ),
2628
- style: {
2629
- width: `calc(100% + ${thumbWidth}px)`,
2630
- left: `-${thumbRadius}px`
2631
- }
2632
- }
2676
+ renderHandle(0, primaryPercent, formatDisplayValue(primary)),
2677
+ isRange && secondary !== void 0 && renderHandle(1, secondaryPercent, formatDisplayValue(secondary))
2678
+ ]
2679
+ }
2680
+ )
2681
+ ] }),
2682
+ showNumeric && /* @__PURE__ */ jsx31(
2683
+ "p",
2684
+ {
2685
+ className: cn(
2686
+ "paragraph-s text-primary",
2687
+ disabled && "text-primary-disabled"
2633
2688
  ),
2634
- renderHandle(0, primaryPercent, formatDisplayValue(primary)),
2635
- type === "multi" && secondary !== void 0 && renderHandle(1, secondaryPercent, formatDisplayValue(secondary))
2636
- ]
2637
- }
2638
- )
2639
- ] }),
2640
- showNumeric && /* @__PURE__ */ jsx31(
2641
- "p",
2642
- {
2643
- className: cn(
2644
- "paragraph-s text-primary",
2645
- disabled && "text-primary-disabled"
2646
- ),
2647
- children: formatNumericLabel()
2648
- }
2649
- )
2650
- ] }) });
2689
+ children: formatNumericLabel()
2690
+ }
2691
+ )
2692
+ ] })
2693
+ }
2694
+ );
2651
2695
  };
2652
2696
  Slider.displayName = "Slider";
2653
2697