@bigtablet/design-system 1.16.0 → 1.16.1

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.css CHANGED
@@ -689,6 +689,11 @@
689
689
  margin-top: 0;
690
690
  margin-bottom: 0.25rem;
691
691
  }
692
+ .select_list_portal {
693
+ position: fixed;
694
+ margin-top: 0;
695
+ margin-bottom: 0;
696
+ }
692
697
  .select_option {
693
698
  width: 100%;
694
699
  box-sizing: border-box;
package/dist/index.d.ts CHANGED
@@ -109,7 +109,7 @@ interface SelectProps {
109
109
  className?: string;
110
110
  textAlign?: "left" | "center";
111
111
  }
112
- declare const Select: ({ id, label, placeholder, options, value, onChange, defaultValue, disabled, size, variant, fullWidth, className, textAlign }: SelectProps) => react_jsx_runtime.JSX.Element;
112
+ declare const Select: ({ id, label, placeholder, options, value, onChange, defaultValue, disabled, size, variant, fullWidth, className, textAlign, }: SelectProps) => react_jsx_runtime.JSX.Element;
113
113
 
114
114
  interface SwitchProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
115
115
  checked?: boolean;
package/dist/index.js CHANGED
@@ -316,8 +316,10 @@ var Select = ({
316
316
  const [isOpen, setIsOpen] = React3.useState(false);
317
317
  const [activeIndex, setActiveIndex] = React3.useState(-1);
318
318
  const [dropUp, setDropUp] = React3.useState(false);
319
+ const [listPosition, setListPosition] = React3.useState({ top: 0, left: 0, width: 0 });
319
320
  const wrapperRef = React3.useRef(null);
320
321
  const controlRef = React3.useRef(null);
322
+ const listRef = React3.useRef(null);
321
323
  const currentOption = React3.useMemo(
322
324
  () => options.find((o) => o.value === currentValue) ?? null,
323
325
  [options, currentValue]
@@ -332,10 +334,10 @@ var Select = ({
332
334
  );
333
335
  React3.useEffect(() => {
334
336
  const onDocClick = (e) => {
335
- if (!wrapperRef.current) return;
336
- if (!wrapperRef.current.contains(e.target)) {
337
- setIsOpen(false);
338
- }
337
+ const target = e.target;
338
+ if (wrapperRef.current?.contains(target)) return;
339
+ if (listRef.current?.contains(target)) return;
340
+ setIsOpen(false);
339
341
  };
340
342
  document.addEventListener("mousedown", onDocClick);
341
343
  return () => document.removeEventListener("mousedown", onDocClick);
@@ -383,9 +385,7 @@ var Select = ({
383
385
  case "Home":
384
386
  e.preventDefault();
385
387
  setIsOpen(true);
386
- setActiveIndex(
387
- options.findIndex((o) => !o.disabled)
388
- );
388
+ setActiveIndex(options.findIndex((o) => !o.disabled));
389
389
  break;
390
390
  case "End":
391
391
  e.preventDefault();
@@ -405,24 +405,36 @@ var Select = ({
405
405
  };
406
406
  React3.useEffect(() => {
407
407
  if (!isOpen) return;
408
- const idx = options.findIndex(
409
- (o) => o.value === currentValue && !o.disabled
410
- );
411
- setActiveIndex(
412
- idx >= 0 ? idx : Math.max(
413
- 0,
414
- options.findIndex((o) => !o.disabled)
415
- )
416
- );
408
+ const idx = options.findIndex((o) => o.value === currentValue && !o.disabled);
409
+ setActiveIndex(idx >= 0 ? idx : Math.max(0, options.findIndex((o) => !o.disabled)));
417
410
  }, [isOpen, options, currentValue]);
418
- React3.useLayoutEffect(() => {
419
- if (!isOpen || !controlRef.current) return;
411
+ const updatePosition = React3.useCallback(() => {
412
+ if (!controlRef.current) return;
420
413
  const rect = controlRef.current.getBoundingClientRect();
421
414
  const listHeight = Math.min(options.length * 40, 288);
422
415
  const spaceBelow = window.innerHeight - rect.bottom;
423
416
  const spaceAbove = rect.top;
424
- setDropUp(spaceBelow < listHeight && spaceAbove > spaceBelow);
425
- }, [isOpen, options.length]);
417
+ const shouldDropUp = spaceBelow < listHeight && spaceAbove > spaceBelow;
418
+ setDropUp(shouldDropUp);
419
+ setListPosition({
420
+ top: shouldDropUp ? rect.top - listHeight - 4 : rect.bottom + 4,
421
+ left: rect.left,
422
+ width: rect.width
423
+ });
424
+ }, [options.length]);
425
+ React3.useLayoutEffect(() => {
426
+ if (!isOpen) return;
427
+ updatePosition();
428
+ }, [isOpen, updatePosition]);
429
+ React3.useEffect(() => {
430
+ if (!isOpen) return;
431
+ window.addEventListener("scroll", updatePosition, true);
432
+ window.addEventListener("resize", updatePosition);
433
+ return () => {
434
+ window.removeEventListener("scroll", updatePosition, true);
435
+ window.removeEventListener("resize", updatePosition);
436
+ };
437
+ }, [isOpen, updatePosition]);
426
438
  const rootClassName = ["select", className ?? ""].filter(Boolean).join(" ");
427
439
  const controlClassName = [
428
440
  "select_control",
@@ -431,93 +443,80 @@ var Select = ({
431
443
  isOpen && "is_open",
432
444
  disabled && "is_disabled"
433
445
  ].filter(Boolean).join(" ");
434
- return /* @__PURE__ */ jsxs(
435
- "div",
446
+ const renderList = () => /* @__PURE__ */ jsx(
447
+ "ul",
436
448
  {
437
- ref: wrapperRef,
438
- className: rootClassName,
439
- style: fullWidth ? { width: "100%" } : void 0,
440
- children: [
441
- label && /* @__PURE__ */ jsx(
442
- "label",
449
+ ref: listRef,
450
+ id: `${selectId}_listbox`,
451
+ role: "listbox",
452
+ className: `select_list select_list_portal${dropUp ? " select_list_up" : ""}`,
453
+ style: {
454
+ position: "fixed",
455
+ top: listPosition.top,
456
+ left: listPosition.left,
457
+ width: listPosition.width
458
+ },
459
+ children: options.map((opt, i) => {
460
+ const selected = currentValue === opt.value;
461
+ const active = i === activeIndex;
462
+ const optionClassName = [
463
+ "select_option",
464
+ selected && "is_selected",
465
+ active && "is_active",
466
+ opt.disabled && "is_disabled"
467
+ ].filter(Boolean).join(" ");
468
+ return /* @__PURE__ */ jsxs(
469
+ "li",
443
470
  {
444
- htmlFor: selectId,
445
- className: "select_label",
446
- children: label
447
- }
448
- ),
449
- /* @__PURE__ */ jsxs(
450
- "button",
451
- {
452
- ref: controlRef,
453
- id: selectId,
454
- type: "button",
455
- className: controlClassName,
456
- "aria-haspopup": "listbox",
457
- "aria-expanded": isOpen,
458
- "aria-controls": `${selectId}_listbox`,
459
- onClick: () => !disabled && setIsOpen((o) => !o),
460
- onKeyDown,
461
- disabled,
471
+ role: "option",
472
+ "aria-selected": selected,
473
+ className: optionClassName,
474
+ onMouseEnter: () => !opt.disabled && setActiveIndex(i),
475
+ onClick: () => {
476
+ if (opt.disabled) return;
477
+ setValue(opt.value);
478
+ setIsOpen(false);
479
+ },
462
480
  children: [
463
- /* @__PURE__ */ jsx(
464
- "span",
465
- {
466
- className: currentOption ? "select_value" : "select_placeholder",
467
- style: textAlign === "left" ? { textAlign: "start" } : void 0,
468
- children: currentOption ? currentOption.label : placeholder
469
- }
470
- ),
471
- /* @__PURE__ */ jsx("span", { className: "select_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDown, { size: 16 }) })
481
+ /* @__PURE__ */ jsx("span", { children: opt.label }),
482
+ selected && /* @__PURE__ */ jsx(Check, { size: 16, "aria-hidden": "true" })
472
483
  ]
473
- }
474
- ),
475
- isOpen && /* @__PURE__ */ jsx(
476
- "ul",
477
- {
478
- id: `${selectId}_listbox`,
479
- role: "listbox",
480
- className: `select_list${dropUp ? " select_list_up" : ""}`,
481
- children: options.map((opt, i) => {
482
- const selected = currentValue === opt.value;
483
- const active = i === activeIndex;
484
- const optionClassName = [
485
- "select_option",
486
- selected && "is_selected",
487
- active && "is_active",
488
- opt.disabled && "is_disabled"
489
- ].filter(Boolean).join(" ");
490
- return /* @__PURE__ */ jsxs(
491
- "li",
492
- {
493
- role: "option",
494
- "aria-selected": selected,
495
- className: optionClassName,
496
- onMouseEnter: () => !opt.disabled && setActiveIndex(i),
497
- onClick: () => {
498
- if (opt.disabled) return;
499
- setValue(opt.value);
500
- setIsOpen(false);
501
- },
502
- children: [
503
- /* @__PURE__ */ jsx("span", { children: opt.label }),
504
- selected && /* @__PURE__ */ jsx(
505
- Check,
506
- {
507
- size: 16,
508
- "aria-hidden": "true"
509
- }
510
- )
511
- ]
512
- },
513
- opt.value
514
- );
515
- })
516
- }
517
- )
518
- ]
484
+ },
485
+ opt.value
486
+ );
487
+ })
519
488
  }
520
489
  );
490
+ return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: rootClassName, style: fullWidth ? { width: "100%" } : void 0, children: [
491
+ label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "select_label", children: label }),
492
+ /* @__PURE__ */ jsxs(
493
+ "button",
494
+ {
495
+ ref: controlRef,
496
+ id: selectId,
497
+ type: "button",
498
+ className: controlClassName,
499
+ "aria-haspopup": "listbox",
500
+ "aria-expanded": isOpen,
501
+ "aria-controls": `${selectId}_listbox`,
502
+ onClick: () => !disabled && setIsOpen((o) => !o),
503
+ onKeyDown,
504
+ disabled,
505
+ children: [
506
+ /* @__PURE__ */ jsx(
507
+ "span",
508
+ {
509
+ className: currentOption ? "select_value" : "select_placeholder",
510
+ style: textAlign === "left" ? { textAlign: "start" } : void 0,
511
+ children: currentOption ? currentOption.label : placeholder
512
+ }
513
+ ),
514
+ /* @__PURE__ */ jsx("span", { className: "select_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ChevronDown, { size: 16 }) })
515
+ ]
516
+ }
517
+ ),
518
+ isOpen && typeof document !== "undefined" && createPortal(renderList(), document.body)
519
+ ] });
521
520
  };
522
521
  var Switch = ({
523
522
  checked,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigtablet/design-system",
3
- "version": "1.16.0",
3
+ "version": "1.16.1",
4
4
  "description": "Bigtablet Design System UI Components",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",