@martinsura/ui 0.1.5 → 0.1.7

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.js CHANGED
@@ -511,7 +511,7 @@ var Textarea = ({ rows = 4, ...props }) => {
511
511
  placeholder: props.placeholder,
512
512
  disabled: props.disabled,
513
513
  onChange: (e) => props.onChange?.(e.target.value),
514
- className: twMerge(inputBaseClass, "min-h-24 py-2", errorDisplay && "border-(--ui-danger)")
514
+ className: twMerge(inputBaseClass, "min-h-24 px-(--ui-px-md) py-2 [font-size:var(--ui-text-md)] rounded-(--ui-radius-md)", errorDisplay && "border-(--ui-danger)")
515
515
  }
516
516
  ),
517
517
  errorDisplay && /* @__PURE__ */ jsx(InputError, { error: String(errorDisplay) })
@@ -4494,6 +4494,374 @@ var MultiSelectInput = ({
4494
4494
  errorDisplay && /* @__PURE__ */ jsx(InputError, { error: String(errorDisplay), className: props.classNames?.error })
4495
4495
  ] });
4496
4496
  };
4497
+ function calcPosition3(el) {
4498
+ const rect = el.getBoundingClientRect();
4499
+ const maxDropdownHeight = 320;
4500
+ const spaceBelow = window.innerHeight - rect.bottom;
4501
+ const openAbove = spaceBelow < maxDropdownHeight && rect.top > spaceBelow;
4502
+ return {
4503
+ top: openAbove ? rect.top - maxDropdownHeight - 2 : rect.bottom + 2,
4504
+ left: rect.left,
4505
+ width: rect.width,
4506
+ openAbove
4507
+ };
4508
+ }
4509
+ function flattenTree(options, parentLabels = [], parentKey = "") {
4510
+ return options.flatMap((option, index) => {
4511
+ const key = parentKey === "" ? String(index) : `${parentKey}.${index}`;
4512
+ const path = [...parentLabels, option.label];
4513
+ const current = {
4514
+ key,
4515
+ label: option.label,
4516
+ pathLabel: path.join(" / "),
4517
+ value: option.value,
4518
+ selectable: option.selectable
4519
+ };
4520
+ return [current, ...flattenTree(option.children ?? [], path, key)];
4521
+ });
4522
+ }
4523
+ var TreeSelectDropdown = (props) => {
4524
+ const [search, setSearch] = useState("");
4525
+ const [pos, setPos] = useState(null);
4526
+ const [activePath, setActivePath] = useState([]);
4527
+ const searchRef = useRef(null);
4528
+ const popupRef = useRef(null);
4529
+ const flattenedOptions = useMemo(() => flattenTree(props.options), [props.options]);
4530
+ useEffect(() => {
4531
+ if (!props.isOpen || !props.anchorRef.current) {
4532
+ return;
4533
+ }
4534
+ setPos(calcPosition3(props.anchorRef.current));
4535
+ setSearch("");
4536
+ setActivePath([]);
4537
+ if (props.showSearch !== false) {
4538
+ setTimeout(() => searchRef.current?.focus(), 0);
4539
+ }
4540
+ }, [props.isOpen, props.anchorRef, props.showSearch]);
4541
+ const handleMouseDown = useCallback(
4542
+ (e) => {
4543
+ if (popupRef.current && !popupRef.current.contains(e.target) && props.anchorRef.current && !props.anchorRef.current.contains(e.target)) {
4544
+ props.onClose();
4545
+ }
4546
+ },
4547
+ [props]
4548
+ );
4549
+ useEffect(() => {
4550
+ if (!props.isOpen) {
4551
+ return () => document.removeEventListener("mousedown", handleMouseDown);
4552
+ }
4553
+ document.addEventListener("mousedown", handleMouseDown);
4554
+ const handleKeyDown = (e) => {
4555
+ if (e.key === "Escape" && isTopmostFloatingRoot(popupRef.current)) {
4556
+ e.preventDefault();
4557
+ e.stopPropagation();
4558
+ e.stopImmediatePropagation();
4559
+ props.onClose();
4560
+ }
4561
+ };
4562
+ document.addEventListener("keydown", handleKeyDown);
4563
+ return () => {
4564
+ document.removeEventListener("mousedown", handleMouseDown);
4565
+ document.removeEventListener("keydown", handleKeyDown);
4566
+ };
4567
+ }, [props.isOpen, handleMouseDown, props]);
4568
+ if (!props.isOpen || !pos) {
4569
+ return null;
4570
+ }
4571
+ const searchTerm = search.trim().toLowerCase();
4572
+ const isSearching = searchTerm !== "";
4573
+ const filteredOptions = isSearching ? flattenedOptions.filter((option) => option.selectable && option.pathLabel.toLowerCase().includes(searchTerm)) : [];
4574
+ const columns = [];
4575
+ if (!isSearching) {
4576
+ let currentLevel = props.options;
4577
+ columns.push(currentLevel);
4578
+ for (const index of activePath) {
4579
+ const activeOption = currentLevel[index];
4580
+ if (!activeOption?.children?.length) {
4581
+ break;
4582
+ }
4583
+ currentLevel = activeOption.children;
4584
+ columns.push(currentLevel);
4585
+ }
4586
+ }
4587
+ const handleOptionClick = (option, depth, index) => {
4588
+ if (option.children?.length) {
4589
+ setActivePath((prev) => [...prev.slice(0, depth), index]);
4590
+ return;
4591
+ }
4592
+ if (!option.selectable) {
4593
+ return;
4594
+ }
4595
+ props.onSelect(option.value);
4596
+ };
4597
+ return createPortal(
4598
+ /* @__PURE__ */ jsxs(
4599
+ "div",
4600
+ {
4601
+ ref: popupRef,
4602
+ "data-ui-floating-root": "",
4603
+ style: { top: pos.top, left: pos.left, minWidth: pos.width },
4604
+ className: "fixed z-1002 flex max-h-80 flex-col overflow-hidden rounded-(--ui-radius-lg) border border-(--ui-border) bg-white shadow-lg",
4605
+ children: [
4606
+ props.showSearch !== false && /* @__PURE__ */ jsxs(
4607
+ "div",
4608
+ {
4609
+ className: twMerge(
4610
+ "flex items-center border-b border-(--ui-border)",
4611
+ popupLayoutClasses.compactHeader,
4612
+ "gap-(--ui-popup-gap)"
4613
+ ),
4614
+ children: [
4615
+ /* @__PURE__ */ jsx(IconSearch, { size: 13, strokeWidth: 1.5, className: twMerge("shrink-0", neutralIconClasses.default) }),
4616
+ /* @__PURE__ */ jsx(
4617
+ "input",
4618
+ {
4619
+ ref: searchRef,
4620
+ type: "text",
4621
+ value: search,
4622
+ onChange: (e) => setSearch(e.target.value),
4623
+ placeholder: "Hledat...",
4624
+ className: "flex-1 bg-transparent outline-none [font-size:var(--ui-popup-text)] placeholder:text-(--ui-text-soft)"
4625
+ }
4626
+ )
4627
+ ]
4628
+ }
4629
+ ),
4630
+ /* @__PURE__ */ jsx("div", { className: "flex max-h-64 overflow-hidden", children: props.loading ? /* @__PURE__ */ jsx("div", { className: "flex min-h-40 min-w-80 items-center justify-center px-6 py-6", children: /* @__PURE__ */ jsx(Spinner, { size: "middle", color: "primary" }) }) : isSearching ? filteredOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: twMerge("flex min-h-40 min-w-80 items-center justify-center px-6 py-4 text-xs", neutralTextClasses.soft), children: "\u017D\xE1dn\xE9 v\xFDsledky" }) : /* @__PURE__ */ jsx("div", { className: "max-h-64 min-w-80 overflow-y-auto", children: filteredOptions.map((option) => {
4631
+ const isSelected = props.selectedValue === option.value;
4632
+ return /* @__PURE__ */ jsxs(
4633
+ "div",
4634
+ {
4635
+ onMouseDown: (e) => e.preventDefault(),
4636
+ onClick: () => props.onSelect(option.value),
4637
+ className: twMerge(
4638
+ "flex cursor-pointer items-center justify-between select-none",
4639
+ popupLayoutClasses.compactRow,
4640
+ isSelected ? "font-medium text-(--ui-text-strong)" : "text-(--ui-text) hover:bg-(--ui-surface-subtle)"
4641
+ ),
4642
+ children: [
4643
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: option.pathLabel }),
4644
+ isSelected && /* @__PURE__ */ jsx(IconCheck, { size: 13, strokeWidth: 2, className: "ml-3 shrink-0 text-(--ui-text)" })
4645
+ ]
4646
+ },
4647
+ option.key
4648
+ );
4649
+ }) }) : columns.map((column, depth) => /* @__PURE__ */ jsx(
4650
+ "div",
4651
+ {
4652
+ className: twMerge(
4653
+ "min-w-72 overflow-y-auto",
4654
+ depth > 0 && "border-l border-(--ui-border)"
4655
+ ),
4656
+ children: column.map((option, index) => {
4657
+ const isSelected = props.selectedValue === option.value;
4658
+ const isActiveBranch = activePath[depth] === index;
4659
+ const hasChildren = (option.children?.length ?? 0) > 0;
4660
+ return /* @__PURE__ */ jsxs(
4661
+ "div",
4662
+ {
4663
+ onMouseDown: (e) => e.preventDefault(),
4664
+ onMouseEnter: () => {
4665
+ if (hasChildren) {
4666
+ setActivePath((prev) => [...prev.slice(0, depth), index]);
4667
+ }
4668
+ },
4669
+ onClick: () => handleOptionClick(option, depth, index),
4670
+ className: twMerge(
4671
+ "flex select-none items-center justify-between",
4672
+ popupLayoutClasses.compactRow,
4673
+ hasChildren || option.selectable ? "cursor-pointer" : "cursor-default",
4674
+ isSelected ? "font-medium text-(--ui-text-strong)" : isActiveBranch ? "bg-(--ui-surface-subtle) text-(--ui-text-strong)" : "text-(--ui-text) hover:bg-(--ui-surface-subtle)",
4675
+ !option.selectable && !hasChildren && "text-(--ui-text-soft)"
4676
+ ),
4677
+ children: [
4678
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: option.label }),
4679
+ /* @__PURE__ */ jsxs("span", { className: "ml-3 flex shrink-0 items-center gap-2", children: [
4680
+ isSelected && /* @__PURE__ */ jsx(IconCheck, { size: 13, strokeWidth: 2, className: "text-(--ui-text)" }),
4681
+ hasChildren && /* @__PURE__ */ jsx(IconChevronRight, { size: 13, strokeWidth: 1.5, className: twMerge(neutralIconClasses.default) })
4682
+ ] })
4683
+ ]
4684
+ },
4685
+ `${depth}-${index}-${String(option.value)}`
4686
+ );
4687
+ })
4688
+ },
4689
+ depth
4690
+ )) })
4691
+ ]
4692
+ }
4693
+ ),
4694
+ document.body
4695
+ );
4696
+ };
4697
+ function buildTreeOptions(options, labelKey, valueKey, childrenKey, disableBranchSelection, isOptionSelectable) {
4698
+ return options.map((option) => {
4699
+ const children = option[childrenKey];
4700
+ const nestedOptions = Array.isArray(children) ? buildTreeOptions(children, labelKey, valueKey, childrenKey, disableBranchSelection, isOptionSelectable) : void 0;
4701
+ const hasChildren = (nestedOptions?.length ?? 0) > 0;
4702
+ return {
4703
+ label: String(option[labelKey]),
4704
+ value: option[valueKey],
4705
+ selectable: isOptionSelectable ? isOptionSelectable(option) : !(disableBranchSelection && hasChildren),
4706
+ children: nestedOptions
4707
+ };
4708
+ });
4709
+ }
4710
+ function findSelectedOption(options, value) {
4711
+ for (const option of options) {
4712
+ if (option.value === value) {
4713
+ return option;
4714
+ }
4715
+ const nested = option.children ? findSelectedOption(option.children, value) : null;
4716
+ if (nested) {
4717
+ return nested;
4718
+ }
4719
+ }
4720
+ return null;
4721
+ }
4722
+ function findSelectedPathLabel(options, value, parentLabels = []) {
4723
+ for (const option of options) {
4724
+ const path = [...parentLabels, option.label];
4725
+ if (option.value === value) {
4726
+ return path.join(" / ");
4727
+ }
4728
+ if (option.children?.length) {
4729
+ const nested = findSelectedPathLabel(option.children, value, path);
4730
+ if (nested) {
4731
+ return nested;
4732
+ }
4733
+ }
4734
+ }
4735
+ return null;
4736
+ }
4737
+ var TreeSelectInput = ({
4738
+ options,
4739
+ labelKey = "label",
4740
+ valueKey = "value",
4741
+ childrenKey = "children",
4742
+ size = "middle",
4743
+ showSearch = true,
4744
+ disableBranchSelection = true,
4745
+ selectedLabelMode = "path",
4746
+ ...props
4747
+ }) => {
4748
+ const [isOpen, setIsOpen] = useState(false);
4749
+ const triggerRef = useRef(null);
4750
+ const allOptions = useMemo(
4751
+ () => buildTreeOptions(
4752
+ options ?? [],
4753
+ labelKey,
4754
+ valueKey,
4755
+ childrenKey,
4756
+ disableBranchSelection,
4757
+ props.isOptionSelectable
4758
+ ),
4759
+ [options, labelKey, valueKey, childrenKey, disableBranchSelection, props.isOptionSelectable]
4760
+ );
4761
+ const effectiveValue = props.treatZeroAsEmpty && props.value === 0 ? null : props.value ?? null;
4762
+ const selectedOption = effectiveValue === null || effectiveValue === void 0 ? null : findSelectedOption(allOptions, effectiveValue);
4763
+ const selectedPathLabel = effectiveValue === null || effectiveValue === void 0 ? null : findSelectedPathLabel(allOptions, effectiveValue);
4764
+ const hasValue = effectiveValue !== null && effectiveValue !== void 0;
4765
+ const allowClear = props.allowClear ?? false;
4766
+ const resolveError = useErrorResolver();
4767
+ const resolvedErrors = props.errorName ? resolveError(props.errorName) : [];
4768
+ const errorDisplay = props.error ?? props.customError ?? (resolvedErrors.length > 0 ? resolvedErrors.join(", ") : void 0);
4769
+ const hasError = !!errorDisplay;
4770
+ const handleSelect = (value) => {
4771
+ props.onChange?.(value);
4772
+ setIsOpen(false);
4773
+ };
4774
+ const handleClear = (e) => {
4775
+ e.stopPropagation();
4776
+ props.onChange?.(null);
4777
+ };
4778
+ const handleTriggerClick = () => {
4779
+ if (!props.disabled) {
4780
+ setIsOpen((current) => !current);
4781
+ }
4782
+ };
4783
+ return /* @__PURE__ */ jsxs(InputField, { noMargin: props.noMargin, className: props.className, children: [
4784
+ props.label && /* @__PURE__ */ jsx(
4785
+ InputLabel,
4786
+ {
4787
+ label: props.label,
4788
+ required: props.required,
4789
+ className: props.classNames?.label,
4790
+ requiredMarkClassName: props.classNames?.requiredMark
4791
+ }
4792
+ ),
4793
+ /* @__PURE__ */ jsxs(
4794
+ "div",
4795
+ {
4796
+ ref: triggerRef,
4797
+ onClick: handleTriggerClick,
4798
+ className: twMerge(
4799
+ "flex cursor-pointer select-none items-center gap-1 border bg-white transition-colors",
4800
+ triggerSizeClasses[size],
4801
+ isOpen ? triggerBorderClasses.open : triggerBorderClasses.default,
4802
+ props.disabled && triggerBorderClasses.disabled,
4803
+ props.classNames?.trigger
4804
+ ),
4805
+ style: hasError ? { borderColor: "var(--ui-danger)" } : void 0,
4806
+ children: [
4807
+ /* @__PURE__ */ jsx(
4808
+ "span",
4809
+ {
4810
+ className: twMerge(
4811
+ "flex min-w-0 flex-1 items-center truncate leading-none",
4812
+ !selectedOption && neutralTextClasses.soft,
4813
+ props.classNames?.value
4814
+ ),
4815
+ children: selectedLabelMode === "path" ? selectedPathLabel ?? selectedOption?.label ?? (props.placeholder ?? "") : selectedOption?.label ?? (props.placeholder ?? "")
4816
+ }
4817
+ ),
4818
+ allowClear && hasValue && !props.disabled && /* @__PURE__ */ jsx(
4819
+ "button",
4820
+ {
4821
+ type: "button",
4822
+ onMouseDown: (e) => e.preventDefault(),
4823
+ onClick: handleClear,
4824
+ className: twMerge(
4825
+ "flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center self-center",
4826
+ neutralIconClasses.default,
4827
+ neutralIconClasses.hover,
4828
+ props.classNames?.clearButton
4829
+ ),
4830
+ children: /* @__PURE__ */ jsx(IconX, { size: 12, strokeWidth: 2 })
4831
+ }
4832
+ ),
4833
+ /* @__PURE__ */ jsx(
4834
+ IconChevronDown,
4835
+ {
4836
+ size: 13,
4837
+ strokeWidth: 1.5,
4838
+ className: twMerge(
4839
+ "shrink-0 self-center transition-transform",
4840
+ neutralIconClasses.default,
4841
+ isOpen && "rotate-180",
4842
+ props.classNames?.icon
4843
+ )
4844
+ }
4845
+ )
4846
+ ]
4847
+ }
4848
+ ),
4849
+ /* @__PURE__ */ jsx(
4850
+ TreeSelectDropdown,
4851
+ {
4852
+ anchorRef: triggerRef,
4853
+ isOpen,
4854
+ options: allOptions,
4855
+ selectedValue: effectiveValue,
4856
+ onSelect: handleSelect,
4857
+ onClose: () => setIsOpen(false),
4858
+ loading: props.loading,
4859
+ showSearch
4860
+ }
4861
+ ),
4862
+ errorDisplay && /* @__PURE__ */ jsx(InputError, { error: String(errorDisplay), className: props.classNames?.error })
4863
+ ] });
4864
+ };
4497
4865
 
4498
4866
  // src/notification/useNotification.ts
4499
4867
  var useNotification = () => {
@@ -4603,7 +4971,7 @@ var SkeletonTable = ({ rows = 5, columns = 4, className }) => /* @__PURE__ */ js
4603
4971
  ] }) });
4604
4972
  var GAP2 = 10;
4605
4973
  var VIEWPORT_MARGIN2 = 8;
4606
- function calcPosition3(trigger, tooltip, placement) {
4974
+ function calcPosition4(trigger, tooltip, placement) {
4607
4975
  let resolvedPlacement = placement;
4608
4976
  if (placement === "top" && trigger.top < tooltip.height + GAP2 + VIEWPORT_MARGIN2) {
4609
4977
  resolvedPlacement = "bottom";
@@ -4687,7 +5055,7 @@ var Tooltip = ({
4687
5055
  }
4688
5056
  const triggerRect = triggerRef.current.getBoundingClientRect();
4689
5057
  const tooltipRect = tooltipRef.current.getBoundingClientRect();
4690
- const next = calcPosition3(triggerRect, tooltipRect, placement);
5058
+ const next = calcPosition4(triggerRect, tooltipRect, placement);
4691
5059
  setPosition(next);
4692
5060
  };
4693
5061
  updatePosition();
@@ -4909,6 +5277,6 @@ function initUI(config) {
4909
5277
  r.style.setProperty("--ui-empty-compact-description", layout.empty.compactDescription);
4910
5278
  }
4911
5279
 
4912
- export { ActionBar, ActiveTag, Alert, Avatar, Badge, Button, CheckboxInput, ColorInput, ConfirmButton, DateInput, DescriptionList, Drawer, DrawerContent, DrawerFooter, DrawerTitle, Dropdown, Empty, EmptyState, EntityHeader, ErrorProvider, Fieldset, FilterCheckboxInput, FilterDateInput, FilterDateRangePopover, FilterNumberInput, FilterSelectGroupPopover, FilterSelectInput, FilterTextInput, FlagTag, FormActions, FormSection, Grid, GridFilters, HtmlInput, InlineEdit, InputError, InputField, InputLabel, KeyValue, Modal, ModalContent, ModalFooter, ModalTitle, MultiSelectInput, NotificationProvider, NumberInput, PageHeader, Panel, Popconfirm, Pretty, RadioGroup, SearchInput, SectionHeader, SelectInput, ServerError, Skeleton, SkeletonTable, SortDirection, Spinner, StatCard, Stepper, SwitchInput, Table, Tabs, Tag, TextInput, Textarea, Timeline, Toolbar, Tooltip, UploadInput, UploadProvider, getIcon, initUI, notification, registerIcons, uiTheme, useDrawer, useGrid, useModal, useNotification, useUploadConfig };
5280
+ export { ActionBar, ActiveTag, Alert, Avatar, Badge, Button, CheckboxInput, ColorInput, ConfirmButton, DateInput, DescriptionList, Drawer, DrawerContent, DrawerFooter, DrawerTitle, Dropdown, Empty, EmptyState, EntityHeader, ErrorProvider, Fieldset, FilterCheckboxInput, FilterDateInput, FilterDateRangePopover, FilterNumberInput, FilterSelectGroupPopover, FilterSelectInput, FilterTextInput, FlagTag, FormActions, FormSection, Grid, GridFilters, HtmlInput, InlineEdit, InputError, InputField, InputLabel, KeyValue, Modal, ModalContent, ModalFooter, ModalTitle, MultiSelectInput, NotificationProvider, NumberInput, PageHeader, Panel, Popconfirm, Pretty, RadioGroup, SearchInput, SectionHeader, SelectInput, ServerError, Skeleton, SkeletonTable, SortDirection, Spinner, StatCard, Stepper, SwitchInput, Table, Tabs, Tag, TextInput, Textarea, Timeline, Toolbar, Tooltip, TreeSelectInput, UploadInput, UploadProvider, getIcon, initUI, notification, registerIcons, uiTheme, useDrawer, useGrid, useModal, useNotification, useUploadConfig };
4913
5281
  //# sourceMappingURL=index.js.map
4914
5282
  //# sourceMappingURL=index.js.map