@farmzone/fz-react-ui 1.0.8 → 1.0.10

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
@@ -3478,36 +3478,100 @@ var MODAL_ICONS = {
3478
3478
  delete: lucideReact.Trash2,
3479
3479
  warning: lucideReact.TriangleAlert
3480
3480
  };
3481
+ var VARIANT_CONFIG = {
3482
+ default: {
3483
+ container: "bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100",
3484
+ iconBg: "bg-blue-100 rounded-full",
3485
+ iconColor: "text-main",
3486
+ titleColor: "text-gray-900",
3487
+ descColor: "text-gray-500",
3488
+ closeBtn: "text-gray-400 group-hover:text-gray-600"
3489
+ },
3490
+ "system-light": {
3491
+ container: "bg-[#ededed] border-gray-200",
3492
+ iconBg: "bg-white border border-gray-200 shadow-xs rounded-lg",
3493
+ iconColor: "text-slate-600",
3494
+ titleColor: "text-gray-900",
3495
+ descColor: "text-gray-500",
3496
+ closeBtn: "text-gray-400 group-hover:text-gray-600"
3497
+ },
3498
+ "system-dark": {
3499
+ container: "bg-slate-800 border-slate-700",
3500
+ iconBg: "bg-slate-700 border border-slate-600 shadow-sm rounded-lg",
3501
+ iconColor: "text-slate-300",
3502
+ titleColor: "text-slate-100",
3503
+ descColor: "text-slate-400",
3504
+ closeBtn: "text-slate-500 group-hover:text-slate-300"
3505
+ }
3506
+ };
3507
+ var SIZE_CONFIG = {
3508
+ md: {
3509
+ padding: "px-6 py-5",
3510
+ iconBox: "w-10 h-10",
3511
+ iconSize: 20,
3512
+ gap: "gap-3",
3513
+ titleSize: "text-xl font-bold",
3514
+ descSize: "text-sm mt-0.5",
3515
+ closeBtnBox: "w-10 h-10",
3516
+ closeIconSize: 20
3517
+ },
3518
+ sm: {
3519
+ padding: "px-4 py-4",
3520
+ iconBox: "w-8 h-8",
3521
+ iconSize: 16,
3522
+ gap: "gap-2.5",
3523
+ titleSize: "text-lg font-bold",
3524
+ descSize: "text-sm mt-0.5",
3525
+ closeBtnBox: "w-8 h-8",
3526
+ closeIconSize: 16
3527
+ }
3528
+ };
3481
3529
  function ModalIconHeader(props) {
3482
- const { type, title, description, onClose, className } = props;
3530
+ const {
3531
+ type,
3532
+ title,
3533
+ description,
3534
+ onClose,
3535
+ action,
3536
+ variant = "default",
3537
+ size: size2 = "md",
3538
+ primaryColor,
3539
+ className
3540
+ } = props;
3483
3541
  const Icon2 = MODAL_ICONS[type];
3484
- return /* @__PURE__ */ jsxRuntime.jsx(
3485
- ModalHeader,
3486
- {
3487
- className: cn(
3488
- "bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100 p-0 text-left space-y-0",
3489
- className
3490
- ),
3491
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-5", children: [
3492
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3493
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-10 h-10 bg-blue-100 rounded-full shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(Icon2, { size: 20, className: "text-main" }) }),
3494
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3495
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-bold text-gray-900", children: title }),
3496
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600 mt-0.5", children: description })
3497
- ] })
3498
- ] }),
3499
- /* @__PURE__ */ jsxRuntime.jsx(
3500
- "button",
3501
- {
3502
- type: "button",
3503
- onClick: onClose,
3504
- className: "flex items-center justify-center w-10 h-10 rounded-full transition-colors group cursor-pointer hover:bg-black/5 shrink-0",
3505
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 20, className: "text-gray-400 group-hover:text-gray-600 transition-colors" })
3506
- }
3507
- )
3542
+ const v = VARIANT_CONFIG[variant];
3543
+ const s = SIZE_CONFIG[size2];
3544
+ const hasColor = !!primaryColor;
3545
+ const isDark = variant === "system-dark";
3546
+ const containerStyle = hasColor && variant === "default" ? {
3547
+ background: `linear-gradient(to right, color-mix(in srgb, ${primaryColor} 6%, white), color-mix(in srgb, ${primaryColor} 12%, white))`,
3548
+ borderColor: `color-mix(in srgb, ${primaryColor} 25%, transparent)`
3549
+ } : void 0;
3550
+ const iconBgStyle = hasColor ? {
3551
+ backgroundColor: isDark ? `color-mix(in srgb, ${primaryColor} 20%, #334155)` : `color-mix(in srgb, ${primaryColor} 15%, white)`
3552
+ } : void 0;
3553
+ const iconColorStyle = hasColor && !isDark ? { color: primaryColor } : void 0;
3554
+ return /* @__PURE__ */ jsxRuntime.jsx(ModalHeader, { className: cn("p-0 text-left space-y-0", v.container, className), style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center justify-between", s.padding), children: [
3555
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center", s.gap), children: [
3556
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex items-center justify-center shrink-0", s.iconBox, v.iconBg), style: iconBgStyle, children: /* @__PURE__ */ jsxRuntime.jsx(Icon2, { size: s.iconSize, className: hasColor && !isDark ? void 0 : v.iconColor, style: iconColorStyle }) }),
3557
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3558
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: cn(s.titleSize, v.titleColor), children: title }),
3559
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn(s.descSize, v.descColor), children: description })
3508
3560
  ] })
3509
- }
3510
- );
3561
+ ] }),
3562
+ action ?? /* @__PURE__ */ jsxRuntime.jsx(
3563
+ "button",
3564
+ {
3565
+ type: "button",
3566
+ onClick: onClose,
3567
+ className: cn(
3568
+ "flex items-center justify-center rounded-full transition-colors group cursor-pointer hover:bg-black/5 shrink-0",
3569
+ s.closeBtnBox
3570
+ ),
3571
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: s.closeIconSize, className: cn("transition-colors", v.closeBtn) })
3572
+ }
3573
+ )
3574
+ ] }) });
3511
3575
  }
3512
3576
  function Modal(props) {
3513
3577
  const {
@@ -7726,8 +7790,8 @@ function PageFilter(props) {
7726
7790
  value: currentValue,
7727
7791
  onChange: handleSelectChange(optionKey),
7728
7792
  options: option.options,
7729
- placeholder: "\uC120\uD0DD",
7730
- canReset: true,
7793
+ placeholder: option.placeholder || "\uC120\uD0DD",
7794
+ canReset: option.canReset ?? true,
7731
7795
  containerClassName: option.containerClassName ?? "w-30"
7732
7796
  }
7733
7797
  ) }, String(optionKey));
@@ -7878,16 +7942,24 @@ function easedFade(dir, opacity) {
7878
7942
  return `linear-gradient(${dir}, ${c(1)} 0%, ${c(0.72)} 20%, ${c(0.38)} 45%, ${c(0.1)} 70%, transparent 100%)`;
7879
7943
  }
7880
7944
  function MultiTabBar(props) {
7881
- const { tabs, activeTabId, onTabClick, onTabClose, onTabAdd, variant = "light", primaryColor } = props;
7945
+ const {
7946
+ tabs,
7947
+ activeTabId,
7948
+ onTabClick,
7949
+ onTabClose,
7950
+ onTabAdd,
7951
+ onTabsReorder,
7952
+ variant = "light",
7953
+ primaryColor
7954
+ } = props;
7882
7955
  const s = VARIANT_STYLES[variant];
7883
7956
  const scrollRef = React6.useRef(null);
7884
7957
  const tabRefs = React6.useRef(/* @__PURE__ */ new Map());
7885
7958
  const [showLeftFade, setShowLeftFade] = React6.useState(false);
7886
7959
  const [showRightFade, setShowRightFade] = React6.useState(false);
7887
- const isDragging = React6.useRef(false);
7888
- const hasDragged = React6.useRef(false);
7889
- const dragStartX = React6.useRef(0);
7890
- const dragScrollLeft = React6.useRef(0);
7960
+ const [draggingId, setDraggingId] = React6.useState(null);
7961
+ const [dropTargetId, setDropTargetId] = React6.useState(null);
7962
+ const [dropSide, setDropSide] = React6.useState("before");
7891
7963
  React6.useEffect(() => {
7892
7964
  if (!activeTabId || !scrollRef.current) return;
7893
7965
  const el = tabRefs.current.get(activeTabId);
@@ -7915,23 +7987,6 @@ function MultiTabBar(props) {
7915
7987
  if (scrollRef.current) obs.observe(scrollRef.current);
7916
7988
  return () => obs.disconnect();
7917
7989
  }, [tabs]);
7918
- const handleMouseDown = (e) => {
7919
- if (!scrollRef.current) return;
7920
- isDragging.current = true;
7921
- hasDragged.current = false;
7922
- dragStartX.current = e.pageX - scrollRef.current.getBoundingClientRect().left;
7923
- dragScrollLeft.current = scrollRef.current.scrollLeft;
7924
- };
7925
- const handleMouseMove = (e) => {
7926
- if (!isDragging.current || !scrollRef.current) return;
7927
- const x = e.pageX - scrollRef.current.getBoundingClientRect().left;
7928
- const walk = x - dragStartX.current;
7929
- if (Math.abs(walk) > 4) hasDragged.current = true;
7930
- scrollRef.current.scrollLeft = dragScrollLeft.current - walk;
7931
- };
7932
- const handleStopDrag = () => {
7933
- isDragging.current = false;
7934
- };
7935
7990
  React6.useEffect(() => {
7936
7991
  const el = scrollRef.current;
7937
7992
  if (!el) return;
@@ -7942,6 +7997,64 @@ function MultiTabBar(props) {
7942
7997
  el.addEventListener("wheel", onWheel, { passive: false });
7943
7998
  return () => el.removeEventListener("wheel", onWheel);
7944
7999
  }, []);
8000
+ const handleDragStart = (e, tabId) => {
8001
+ setDraggingId(tabId);
8002
+ e.dataTransfer.effectAllowed = "move";
8003
+ };
8004
+ const handleDragOver = (e, tabId) => {
8005
+ e.preventDefault();
8006
+ if (tabId === draggingId) return;
8007
+ e.dataTransfer.dropEffect = "move";
8008
+ const rect = e.currentTarget.getBoundingClientRect();
8009
+ setDropSide(e.clientX < rect.left + rect.width / 2 ? "before" : "after");
8010
+ setDropTargetId(tabId);
8011
+ };
8012
+ const handleDrop = (e, targetTabId) => {
8013
+ e.preventDefault();
8014
+ e.stopPropagation();
8015
+ if (!draggingId || draggingId === targetTabId) {
8016
+ setDraggingId(null);
8017
+ setDropTargetId(null);
8018
+ return;
8019
+ }
8020
+ if (onTabsReorder) {
8021
+ const fromIdx = tabs.findIndex((t) => t.id === draggingId);
8022
+ if (fromIdx !== -1) {
8023
+ const newTabs = [...tabs];
8024
+ const moved = newTabs.splice(fromIdx, 1)[0];
8025
+ let insertIdx = newTabs.findIndex((t) => t.id === targetTabId);
8026
+ if (insertIdx !== -1) {
8027
+ if (dropSide === "after") insertIdx += 1;
8028
+ newTabs.splice(insertIdx, 0, moved);
8029
+ onTabsReorder(newTabs);
8030
+ }
8031
+ }
8032
+ }
8033
+ setDraggingId(null);
8034
+ setDropTargetId(null);
8035
+ };
8036
+ const handleContainerDrop = (e) => {
8037
+ e.preventDefault();
8038
+ if (!draggingId || !onTabsReorder) {
8039
+ setDraggingId(null);
8040
+ setDropTargetId(null);
8041
+ return;
8042
+ }
8043
+ const fromIdx = tabs.findIndex((t) => t.id === draggingId);
8044
+ if (fromIdx !== -1 && fromIdx < tabs.length - 1) {
8045
+ const newTabs = [...tabs];
8046
+ const moved = newTabs.splice(fromIdx, 1)[0];
8047
+ newTabs.push(moved);
8048
+ onTabsReorder(newTabs);
8049
+ }
8050
+ setDraggingId(null);
8051
+ setDropTargetId(null);
8052
+ };
8053
+ const handleDragEnd = () => {
8054
+ setDraggingId(null);
8055
+ setDropTargetId(null);
8056
+ };
8057
+ const showPlus = !!onTabAdd;
7945
8058
  if (tabs.length === 0) return null;
7946
8059
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex-shrink-0 w-full overflow-hidden ${s.container}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-stretch", children: [
7947
8060
  showLeftFade && /* @__PURE__ */ jsxRuntime.jsx(
@@ -7956,7 +8069,7 @@ function MultiTabBar(props) {
7956
8069
  {
7957
8070
  className: "absolute top-0 bottom-0 w-10 pointer-events-none z-10",
7958
8071
  style: {
7959
- right: PLUS_BTN_WIDTH,
8072
+ right: showPlus ? PLUS_BTN_WIDTH : 0,
7960
8073
  background: easedFade("to left", s.fadeOpacity)
7961
8074
  }
7962
8075
  }
@@ -7968,12 +8081,14 @@ function MultiTabBar(props) {
7968
8081
  className: `flex-1 min-w-0 flex items-end gap-0.5 ${s.scrollPadding} overflow-x-auto [&::-webkit-scrollbar]:hidden`,
7969
8082
  style: { scrollbarWidth: "none" },
7970
8083
  onScroll: updateFades,
7971
- onMouseDown: handleMouseDown,
7972
- onMouseMove: handleMouseMove,
7973
- onMouseUp: handleStopDrag,
7974
- onMouseLeave: handleStopDrag,
8084
+ onDragOver: (e) => {
8085
+ if (draggingId) e.preventDefault();
8086
+ },
8087
+ onDrop: handleContainerDrop,
7975
8088
  children: tabs.map((tab) => {
7976
8089
  const isActive = tab.id === activeTabId;
8090
+ const isDraggingThis = tab.id === draggingId;
8091
+ const isDropTarget = dropTargetId === tab.id;
7977
8092
  return /* @__PURE__ */ jsxRuntime.jsxs(
7978
8093
  "div",
7979
8094
  {
@@ -7981,15 +8096,23 @@ function MultiTabBar(props) {
7981
8096
  if (el) tabRefs.current.set(tab.id, el);
7982
8097
  else tabRefs.current.delete(tab.id);
7983
8098
  },
8099
+ draggable: !!onTabsReorder,
7984
8100
  onClick: () => {
7985
- if (hasDragged.current) return;
7986
8101
  if (!isActive) onTabClick(tab.id);
7987
8102
  },
8103
+ onDragStart: (e) => handleDragStart(e, tab.id),
8104
+ onDragOver: (e) => handleDragOver(e, tab.id),
8105
+ onDrop: (e) => handleDrop(e, tab.id),
8106
+ onDragEnd: handleDragEnd,
7988
8107
  className: `
7989
8108
  group relative flex items-center gap-1.5 ${s.tabPx} ${s.tabPy} flex-shrink-0
7990
- ${s.rounded} border border-b-0 cursor-pointer select-none
8109
+ ${s.rounded} border border-b-0 select-none
7991
8110
  transition-colors duration-100
7992
8111
  ${isActive ? s.activeTab : s.inactiveTab}
8112
+ ${onTabsReorder ? "cursor-grab" : "cursor-pointer"}
8113
+ ${isDraggingThis ? "opacity-40" : ""}
8114
+ ${isDropTarget && dropSide === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""}
8115
+ ${isDropTarget && dropSide === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}
7993
8116
  `,
7994
8117
  children: [
7995
8118
  isActive && /* @__PURE__ */ jsxRuntime.jsx(
@@ -8029,7 +8152,7 @@ function MultiTabBar(props) {
8029
8152
  })
8030
8153
  }
8031
8154
  ),
8032
- onTabAdd && /* @__PURE__ */ jsxRuntime.jsx(
8155
+ showPlus && /* @__PURE__ */ jsxRuntime.jsx(
8033
8156
  "div",
8034
8157
  {
8035
8158
  className: `flex-shrink-0 flex items-center justify-center ${s.plusBtnArea}`,
@@ -9026,6 +9149,7 @@ function Sidebar(props) {
9026
9149
  footer,
9027
9150
  className,
9028
9151
  showCollapseButton = false,
9152
+ collapseButtonPlacement = "right",
9029
9153
  variant = "light",
9030
9154
  primaryColor
9031
9155
  } = props;
@@ -9112,7 +9236,7 @@ function Sidebar(props) {
9112
9236
  "button",
9113
9237
  {
9114
9238
  onClick: () => setIsCollapsed((prev) => !prev),
9115
- className: `absolute top-4 right-2 z-20 w-6.5 h-6.5 rounded-full flex items-center justify-center transition-colors cursor-pointer ${s.collapseBtn}`,
9239
+ className: `absolute top-4 z-20 w-6.5 h-6.5 rounded-full flex items-center justify-center transition-colors cursor-pointer ${s.collapseBtn} ${collapseButtonPlacement === "left" ? "left-2" : "right-2"}`,
9116
9240
  children: isCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelRightClose, { size: 18, color: primaryColor ?? s.collapseIcon }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelLeftClose, { size: 18, color: primaryColor ?? s.collapseIcon })
9117
9241
  }
9118
9242
  ),
@@ -9532,7 +9656,9 @@ function DetailModalFrame(props) {
9532
9656
  renderExtraContent,
9533
9657
  renderFooterExtra,
9534
9658
  onDirtyChange,
9535
- headerType
9659
+ headerType,
9660
+ headerIconVariant,
9661
+ headerIconSize
9536
9662
  } = props;
9537
9663
  const controller = useDetailController({ mode, onModeChange });
9538
9664
  React6.useEffect(() => {
@@ -9576,7 +9702,9 @@ function DetailModalFrame(props) {
9576
9702
  {
9577
9703
  type: isEditMode ? "edit" : headerType,
9578
9704
  title,
9579
- onClose: handleClose
9705
+ onClose: handleClose,
9706
+ variant: headerIconVariant,
9707
+ size: headerIconSize
9580
9708
  }
9581
9709
  ) : /* @__PURE__ */ jsxRuntime.jsx(ModalHeader, { className: "flex flex-row items-center justify-between", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-semibold text-gray-900", children: title }) }),
9582
9710
  /* @__PURE__ */ jsxRuntime.jsxs(ModalBody, { className: "flex-1 min-h-0 overflow-hidden p-0 flex flex-col", children: [