@farmzone/fz-react-ui 1.0.7 → 1.0.9

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
@@ -557,15 +557,23 @@ interface ModalHeaderProps {
557
557
  ref?: Ref<HTMLDivElement>;
558
558
  children: ReactNode;
559
559
  className?: string;
560
+ style?: CSSProperties;
560
561
  }
561
562
  declare function ModalHeader(props: ModalHeaderProps): react.JSX.Element;
562
563
 
563
564
  type ModalType = "detail" | "create" | "edit" | "search" | "info" | "delete" | "warning";
565
+ type ModalIconHeaderVariant = "default" | "system-light" | "system-dark";
566
+ type ModalIconHeaderSize = "sm" | "md";
564
567
  interface ModalIconHeaderProps {
565
568
  type: ModalType;
566
569
  title: string;
567
570
  description?: string;
568
571
  onClose: () => void;
572
+ action?: ReactNode;
573
+ variant?: ModalIconHeaderVariant;
574
+ size?: ModalIconHeaderSize;
575
+ /** default / system-light: 그라디언트·아이콘 색을 primaryColor 기준으로 자동 적용 */
576
+ primaryColor?: string;
569
577
  className?: string;
570
578
  }
571
579
  declare function ModalIconHeader(props: ModalIconHeaderProps): react.JSX.Element;
@@ -650,6 +658,8 @@ interface CheckboxInfo {
650
658
  selectedKeys: Array<string | number>;
651
659
  onSelectionChange: (keys: Array<string | number>) => void;
652
660
  fixed?: boolean;
661
+ disabledKeys?: Array<string | number>;
662
+ disabledTooltip?: React.ReactNode | ((key: string | number) => React.ReactNode);
653
663
  }
654
664
  type DragEndResult = {
655
665
  type: "row" | "column";
@@ -674,6 +684,8 @@ interface TableProps<T> {
674
684
  onColumnDragEnd?: (result: DragEndResult) => void;
675
685
  tooltipConfig?: "always" | "overflow" | false;
676
686
  isDynamicHeight?: boolean;
687
+ showHorizontalDivider?: boolean;
688
+ showVerticalDivider?: boolean;
677
689
  }
678
690
  declare function Table<T>(props: TableProps<T>): react.JSX.Element;
679
691
 
@@ -957,6 +969,7 @@ type SelectOption<P> = BaseOption<P> & {
957
969
  options: Array<SelectableOption>;
958
970
  placeholder?: string;
959
971
  containerClassName?: string;
972
+ canReset?: boolean;
960
973
  };
961
974
  type RadioOption<P> = BaseOption<P> & {
962
975
  type: "radio";
@@ -1045,6 +1058,7 @@ interface MultiTabBarProps {
1045
1058
  onTabClick: (tabId: string) => void;
1046
1059
  onTabClose: (tabId: string) => void;
1047
1060
  onTabAdd?: () => void;
1061
+ onTabsReorder?: (tabs: Array<MultiTab>) => void;
1048
1062
  variant?: MultiTabVariant;
1049
1063
  primaryColor?: string;
1050
1064
  }
@@ -1144,6 +1158,7 @@ interface SidebarProps {
1144
1158
  footer?: ReactNode;
1145
1159
  className?: string;
1146
1160
  showCollapseButton?: boolean;
1161
+ collapseButtonPlacement?: "left" | "right";
1147
1162
  onClose?: () => void;
1148
1163
  variant?: SidebarVariant;
1149
1164
  primaryColor?: string;
@@ -1241,6 +1256,10 @@ interface DetailModalFrameProps<T, S extends z.ZodTypeAny = z.ZodTypeAny> {
1241
1256
  onDirtyChange?: (isDirty: boolean) => void;
1242
1257
  /** 제공 시 ModalIconHeader로 렌더링 (read 모드: 이 type, edit 모드: "edit" 자동 적용) */
1243
1258
  headerType?: ModalType;
1259
+ /** ModalIconHeader 스타일 변형 */
1260
+ headerIconVariant?: ModalIconHeaderVariant;
1261
+ /** ModalIconHeader 사이즈 */
1262
+ headerIconSize?: ModalIconHeaderSize;
1244
1263
  }
1245
1264
  declare function DetailModalFrame<T, S extends z.ZodTypeAny = z.ZodTypeAny>(props: DetailModalFrameProps<T, S>): react.JSX.Element;
1246
1265
 
package/dist/index.d.ts CHANGED
@@ -557,15 +557,23 @@ interface ModalHeaderProps {
557
557
  ref?: Ref<HTMLDivElement>;
558
558
  children: ReactNode;
559
559
  className?: string;
560
+ style?: CSSProperties;
560
561
  }
561
562
  declare function ModalHeader(props: ModalHeaderProps): react.JSX.Element;
562
563
 
563
564
  type ModalType = "detail" | "create" | "edit" | "search" | "info" | "delete" | "warning";
565
+ type ModalIconHeaderVariant = "default" | "system-light" | "system-dark";
566
+ type ModalIconHeaderSize = "sm" | "md";
564
567
  interface ModalIconHeaderProps {
565
568
  type: ModalType;
566
569
  title: string;
567
570
  description?: string;
568
571
  onClose: () => void;
572
+ action?: ReactNode;
573
+ variant?: ModalIconHeaderVariant;
574
+ size?: ModalIconHeaderSize;
575
+ /** default / system-light: 그라디언트·아이콘 색을 primaryColor 기준으로 자동 적용 */
576
+ primaryColor?: string;
569
577
  className?: string;
570
578
  }
571
579
  declare function ModalIconHeader(props: ModalIconHeaderProps): react.JSX.Element;
@@ -650,6 +658,8 @@ interface CheckboxInfo {
650
658
  selectedKeys: Array<string | number>;
651
659
  onSelectionChange: (keys: Array<string | number>) => void;
652
660
  fixed?: boolean;
661
+ disabledKeys?: Array<string | number>;
662
+ disabledTooltip?: React.ReactNode | ((key: string | number) => React.ReactNode);
653
663
  }
654
664
  type DragEndResult = {
655
665
  type: "row" | "column";
@@ -674,6 +684,8 @@ interface TableProps<T> {
674
684
  onColumnDragEnd?: (result: DragEndResult) => void;
675
685
  tooltipConfig?: "always" | "overflow" | false;
676
686
  isDynamicHeight?: boolean;
687
+ showHorizontalDivider?: boolean;
688
+ showVerticalDivider?: boolean;
677
689
  }
678
690
  declare function Table<T>(props: TableProps<T>): react.JSX.Element;
679
691
 
@@ -957,6 +969,7 @@ type SelectOption<P> = BaseOption<P> & {
957
969
  options: Array<SelectableOption>;
958
970
  placeholder?: string;
959
971
  containerClassName?: string;
972
+ canReset?: boolean;
960
973
  };
961
974
  type RadioOption<P> = BaseOption<P> & {
962
975
  type: "radio";
@@ -1045,6 +1058,7 @@ interface MultiTabBarProps {
1045
1058
  onTabClick: (tabId: string) => void;
1046
1059
  onTabClose: (tabId: string) => void;
1047
1060
  onTabAdd?: () => void;
1061
+ onTabsReorder?: (tabs: Array<MultiTab>) => void;
1048
1062
  variant?: MultiTabVariant;
1049
1063
  primaryColor?: string;
1050
1064
  }
@@ -1144,6 +1158,7 @@ interface SidebarProps {
1144
1158
  footer?: ReactNode;
1145
1159
  className?: string;
1146
1160
  showCollapseButton?: boolean;
1161
+ collapseButtonPlacement?: "left" | "right";
1147
1162
  onClose?: () => void;
1148
1163
  variant?: SidebarVariant;
1149
1164
  primaryColor?: string;
@@ -1241,6 +1256,10 @@ interface DetailModalFrameProps<T, S extends z.ZodTypeAny = z.ZodTypeAny> {
1241
1256
  onDirtyChange?: (isDirty: boolean) => void;
1242
1257
  /** 제공 시 ModalIconHeader로 렌더링 (read 모드: 이 type, edit 모드: "edit" 자동 적용) */
1243
1258
  headerType?: ModalType;
1259
+ /** ModalIconHeader 스타일 변형 */
1260
+ headerIconVariant?: ModalIconHeaderVariant;
1261
+ /** ModalIconHeader 사이즈 */
1262
+ headerIconSize?: ModalIconHeaderSize;
1244
1263
  }
1245
1264
  declare function DetailModalFrame<T, S extends z.ZodTypeAny = z.ZodTypeAny>(props: DetailModalFrameProps<T, S>): react.JSX.Element;
1246
1265
 
package/dist/index.js CHANGED
@@ -465,12 +465,10 @@ function Input2(props) {
465
465
  const actualType = type === "password" && showPassword ? "text" : type;
466
466
  const handlePasswordToggle = () => setShowPassword((prev) => !prev);
467
467
  const finalRightIcon = rightIcon || (shouldShowPasswordToggle ? /* @__PURE__ */ jsx(
468
- "button",
468
+ "div",
469
469
  {
470
- type: "button",
471
470
  onClick: handlePasswordToggle,
472
471
  className: "text-muted-foreground hover:text-foreground cursor-pointer transition-colors",
473
- disabled,
474
472
  children: showPassword ? /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" })
475
473
  }
476
474
  ) : null);
@@ -3446,36 +3444,100 @@ var MODAL_ICONS = {
3446
3444
  delete: Trash2,
3447
3445
  warning: TriangleAlert
3448
3446
  };
3447
+ var VARIANT_CONFIG = {
3448
+ default: {
3449
+ container: "bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100",
3450
+ iconBg: "bg-blue-100 rounded-full",
3451
+ iconColor: "text-main",
3452
+ titleColor: "text-gray-900",
3453
+ descColor: "text-gray-500",
3454
+ closeBtn: "text-gray-400 group-hover:text-gray-600"
3455
+ },
3456
+ "system-light": {
3457
+ container: "bg-gray-50 border-gray-200",
3458
+ iconBg: "bg-white border border-gray-200 shadow-sm rounded-lg",
3459
+ iconColor: "text-slate-600",
3460
+ titleColor: "text-gray-900",
3461
+ descColor: "text-gray-500",
3462
+ closeBtn: "text-gray-400 group-hover:text-gray-600"
3463
+ },
3464
+ "system-dark": {
3465
+ container: "bg-slate-800 border-slate-700",
3466
+ iconBg: "bg-slate-700 border border-slate-600 shadow-sm rounded-lg",
3467
+ iconColor: "text-slate-300",
3468
+ titleColor: "text-slate-100",
3469
+ descColor: "text-slate-400",
3470
+ closeBtn: "text-slate-500 group-hover:text-slate-300"
3471
+ }
3472
+ };
3473
+ var SIZE_CONFIG = {
3474
+ md: {
3475
+ padding: "px-6 py-5",
3476
+ iconBox: "w-10 h-10",
3477
+ iconSize: 20,
3478
+ gap: "gap-3",
3479
+ titleSize: "text-xl font-bold",
3480
+ descSize: "text-sm mt-0.5",
3481
+ closeBtnBox: "w-10 h-10",
3482
+ closeIconSize: 20
3483
+ },
3484
+ sm: {
3485
+ padding: "px-4 py-4",
3486
+ iconBox: "w-8 h-8",
3487
+ iconSize: 16,
3488
+ gap: "gap-2.5",
3489
+ titleSize: "text-lg font-bold",
3490
+ descSize: "text-sm mt-0.5",
3491
+ closeBtnBox: "w-8 h-8",
3492
+ closeIconSize: 16
3493
+ }
3494
+ };
3449
3495
  function ModalIconHeader(props) {
3450
- const { type, title, description, onClose, className } = props;
3496
+ const {
3497
+ type,
3498
+ title,
3499
+ description,
3500
+ onClose,
3501
+ action,
3502
+ variant = "default",
3503
+ size: size2 = "md",
3504
+ primaryColor,
3505
+ className
3506
+ } = props;
3451
3507
  const Icon2 = MODAL_ICONS[type];
3452
- return /* @__PURE__ */ jsx(
3453
- ModalHeader,
3454
- {
3455
- className: cn(
3456
- "bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-100 p-0 text-left space-y-0",
3457
- className
3458
- ),
3459
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-5", children: [
3460
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3461
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center w-10 h-10 bg-blue-100 rounded-full shrink-0", children: /* @__PURE__ */ jsx(Icon2, { size: 20, className: "text-main" }) }),
3462
- /* @__PURE__ */ jsxs("div", { children: [
3463
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold text-gray-900", children: title }),
3464
- description && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 mt-0.5", children: description })
3465
- ] })
3466
- ] }),
3467
- /* @__PURE__ */ jsx(
3468
- "button",
3469
- {
3470
- type: "button",
3471
- onClick: onClose,
3472
- className: "flex items-center justify-center w-10 h-10 rounded-full transition-colors group cursor-pointer hover:bg-black/5 shrink-0",
3473
- children: /* @__PURE__ */ jsx(X, { size: 20, className: "text-gray-400 group-hover:text-gray-600 transition-colors" })
3474
- }
3475
- )
3508
+ const v = VARIANT_CONFIG[variant];
3509
+ const s = SIZE_CONFIG[size2];
3510
+ const hasColor = !!primaryColor;
3511
+ const isDark = variant === "system-dark";
3512
+ const containerStyle = hasColor && variant === "default" ? {
3513
+ background: `linear-gradient(to right, color-mix(in srgb, ${primaryColor} 6%, white), color-mix(in srgb, ${primaryColor} 12%, white))`,
3514
+ borderColor: `color-mix(in srgb, ${primaryColor} 25%, transparent)`
3515
+ } : void 0;
3516
+ const iconBgStyle = hasColor ? {
3517
+ backgroundColor: isDark ? `color-mix(in srgb, ${primaryColor} 20%, #334155)` : `color-mix(in srgb, ${primaryColor} 15%, white)`
3518
+ } : void 0;
3519
+ const iconColorStyle = hasColor && !isDark ? { color: primaryColor } : void 0;
3520
+ return /* @__PURE__ */ jsx(ModalHeader, { className: cn("p-0 text-left space-y-0", v.container, className), style: containerStyle, children: /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between", s.padding), children: [
3521
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center", s.gap), children: [
3522
+ /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-center shrink-0", s.iconBox, v.iconBg), style: iconBgStyle, children: /* @__PURE__ */ jsx(Icon2, { size: s.iconSize, className: hasColor && !isDark ? void 0 : v.iconColor, style: iconColorStyle }) }),
3523
+ /* @__PURE__ */ jsxs("div", { children: [
3524
+ /* @__PURE__ */ jsx("h2", { className: cn(s.titleSize, v.titleColor), children: title }),
3525
+ description && /* @__PURE__ */ jsx("p", { className: cn(s.descSize, v.descColor), children: description })
3476
3526
  ] })
3477
- }
3478
- );
3527
+ ] }),
3528
+ action ?? /* @__PURE__ */ jsx(
3529
+ "button",
3530
+ {
3531
+ type: "button",
3532
+ onClick: onClose,
3533
+ className: cn(
3534
+ "flex items-center justify-center rounded-full transition-colors group cursor-pointer hover:bg-black/5 shrink-0",
3535
+ s.closeBtnBox
3536
+ ),
3537
+ children: /* @__PURE__ */ jsx(X, { size: s.closeIconSize, className: cn("transition-colors", v.closeBtn) })
3538
+ }
3539
+ )
3540
+ ] }) });
3479
3541
  }
3480
3542
  function Modal(props) {
3481
3543
  const {
@@ -3929,8 +3991,6 @@ function ArrowIcon(props) {
3929
3991
  }
3930
3992
  );
3931
3993
  }
3932
- var DEFAULT_SORT_KEY = "";
3933
- var DEFAULT_SORT_ORDER = "asc";
3934
3994
  var DEFAULT_SORT_OPTIONS = {
3935
3995
  visibleType: "always",
3936
3996
  sortKey: "",
@@ -3979,27 +4039,18 @@ function TableHeader(props) {
3979
4039
  if (onSortChange) {
3980
4040
  const isActive = sortOption?.sortKey === columnKey;
3981
4041
  const currentOrder = sortOption?.sortOrder;
3982
- if (isActive) {
3983
- if (currentOrder === "desc") {
3984
- onSortChange(columnKey, "asc");
3985
- } else if (currentOrder === "asc") {
3986
- onSortChange(DEFAULT_SORT_KEY, DEFAULT_SORT_ORDER);
3987
- } else {
3988
- onSortChange(columnKey, "desc");
3989
- }
4042
+ if (isActive && currentOrder === "desc") {
4043
+ onSortChange(columnKey, "asc");
3990
4044
  } else {
3991
4045
  onSortChange(columnKey, "desc");
3992
4046
  }
3993
4047
  return;
3994
4048
  }
3995
4049
  setInternalSortState((prev) => {
3996
- if (prev.columnKey !== columnKey) {
3997
- return { columnKey, order: "desc" };
3998
- }
3999
- if (prev.order === "desc") {
4050
+ if (prev.columnKey === columnKey && prev.order === "desc") {
4000
4051
  return { columnKey, order: "asc" };
4001
4052
  }
4002
- return { columnKey: null, order: null };
4053
+ return { columnKey, order: "desc" };
4003
4054
  });
4004
4055
  };
4005
4056
  const renderSortIcon = (columnKey) => {
@@ -4635,9 +4686,11 @@ function Table(props) {
4635
4686
  paginationInfo,
4636
4687
  renderFooter,
4637
4688
  tooltipConfig = "overflow",
4638
- isDynamicHeight = true
4689
+ isDynamicHeight = true,
4690
+ showHorizontalDivider = true,
4691
+ showVerticalDivider = true
4639
4692
  } = props;
4640
- const { fixed = false, onSelectionChange, selectedKeys = [] } = checkboxInfo ?? {};
4693
+ const { fixed = false, onSelectionChange, selectedKeys = [], disabledKeys = [], disabledTooltip } = checkboxInfo ?? {};
4641
4694
  const scrollRef = useRef(null);
4642
4695
  const footerRef = useRef(null);
4643
4696
  const [tableHeight, setTableHeight] = useState(500);
@@ -4679,11 +4732,14 @@ function Table(props) {
4679
4732
  const footerH = footerRef.current?.getBoundingClientRect().height ?? 0;
4680
4733
  setTableHeight(window.innerHeight - top - footerH - 24);
4681
4734
  }, [showSkeleton, showEmpty]);
4682
- const isAllSelected = data.length > 0 && selectedKeys.length === data.length;
4683
4735
  const selectedKeysRef = useRef(selectedKeys);
4684
4736
  selectedKeysRef.current = selectedKeys;
4685
4737
  const onSelectionChangeRef = useRef(onSelectionChange);
4686
4738
  onSelectionChangeRef.current = onSelectionChange;
4739
+ const disabledKeysRef = useRef(disabledKeys);
4740
+ disabledKeysRef.current = disabledKeys;
4741
+ const disabledTooltipRef = useRef(disabledTooltip);
4742
+ disabledTooltipRef.current = disabledTooltip;
4687
4743
  const dataRef = useRef(data);
4688
4744
  dataRef.current = data;
4689
4745
  const onRowClickRef = useRef(onRowClick);
@@ -4698,6 +4754,8 @@ function Table(props) {
4698
4754
  },
4699
4755
  [rowKey]
4700
4756
  );
4757
+ const selectableKeys = data.map((record, index) => getRowKey(record, index)).filter((key) => !disabledKeys.includes(key));
4758
+ const isAllSelected = selectableKeys.length > 0 && selectableKeys.every((key) => selectedKeys.includes(key));
4701
4759
  const allColumns = useMemo(() => {
4702
4760
  const checkboxCol = {
4703
4761
  key: "__checkbox__",
@@ -4712,19 +4770,24 @@ function Table(props) {
4712
4770
  checked: isAllSelected,
4713
4771
  onChange: () => {
4714
4772
  if (!onSelectionChangeRef.current) return;
4715
- const allKeys = dataRef.current.map((record, index) => getRowKey(record, index));
4773
+ const allKeys = dataRef.current.map((record, index) => getRowKey(record, index)).filter((key) => !disabledKeysRef.current.includes(key));
4716
4774
  onSelectionChangeRef.current(isAllSelected ? [] : allKeys);
4717
4775
  }
4718
4776
  }
4719
4777
  ),
4720
4778
  render: (_value, record, rowIdx) => {
4721
4779
  const key = getRowKey(record, rowIdx);
4780
+ const isDisabled = disabledKeysRef.current.includes(key);
4781
+ const tooltip = disabledTooltipRef.current;
4782
+ const tooltipContent = isDisabled && tooltip ? typeof tooltip === "function" ? tooltip(key) : tooltip : void 0;
4722
4783
  return /* @__PURE__ */ jsx("div", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsx(
4723
4784
  Checkbox,
4724
4785
  {
4725
4786
  checked: selectedKeysRef.current.includes(key),
4787
+ disabled: isDisabled,
4788
+ tooltipConfig: tooltipContent ? { content: tooltipContent, position: "bottom" } : void 0,
4726
4789
  onChange: () => {
4727
- if (!onSelectionChangeRef.current) return;
4790
+ if (!onSelectionChangeRef.current || isDisabled) return;
4728
4791
  const cur = selectedKeysRef.current;
4729
4792
  const next = cur.includes(key) ? cur.filter((k) => k !== key) : [...cur, key];
4730
4793
  onSelectionChangeRef.current(next);
@@ -4985,7 +5048,7 @@ function Table(props) {
4985
5048
  id: `${col.key}-body`,
4986
5049
  "data-row-cell": "true",
4987
5050
  "data-col-key": col.key,
4988
- className: `${isCheckbox ? "p-0" : "p-3"} whitespace-nowrap text-sm text-gray-900 border-r border-b border-gray-300/70 last:border-r-0 bg-inherit group-hover:bg-gray-100 transition-all`,
5051
+ className: `${isCheckbox ? "p-0" : "p-3"} whitespace-nowrap text-sm text-gray-900 border-gray-300/70 last:border-r-0 bg-inherit group-hover:bg-gray-100 transition-all ${showVerticalDivider ? "border-r" : ""} ${showHorizontalDivider ? "border-b" : ""}`,
4989
5052
  style: {
4990
5053
  width,
4991
5054
  minWidth: minWidth || width,
@@ -7694,7 +7757,7 @@ function PageFilter(props) {
7694
7757
  onChange: handleSelectChange(optionKey),
7695
7758
  options: option.options,
7696
7759
  placeholder: "\uC120\uD0DD",
7697
- canReset: true,
7760
+ canReset: option.canReset ?? true,
7698
7761
  containerClassName: option.containerClassName ?? "w-30"
7699
7762
  }
7700
7763
  ) }, String(optionKey));
@@ -7845,16 +7908,24 @@ function easedFade(dir, opacity) {
7845
7908
  return `linear-gradient(${dir}, ${c(1)} 0%, ${c(0.72)} 20%, ${c(0.38)} 45%, ${c(0.1)} 70%, transparent 100%)`;
7846
7909
  }
7847
7910
  function MultiTabBar(props) {
7848
- const { tabs, activeTabId, onTabClick, onTabClose, onTabAdd, variant = "light", primaryColor } = props;
7911
+ const {
7912
+ tabs,
7913
+ activeTabId,
7914
+ onTabClick,
7915
+ onTabClose,
7916
+ onTabAdd,
7917
+ onTabsReorder,
7918
+ variant = "light",
7919
+ primaryColor
7920
+ } = props;
7849
7921
  const s = VARIANT_STYLES[variant];
7850
7922
  const scrollRef = useRef(null);
7851
7923
  const tabRefs = useRef(/* @__PURE__ */ new Map());
7852
7924
  const [showLeftFade, setShowLeftFade] = useState(false);
7853
7925
  const [showRightFade, setShowRightFade] = useState(false);
7854
- const isDragging = useRef(false);
7855
- const hasDragged = useRef(false);
7856
- const dragStartX = useRef(0);
7857
- const dragScrollLeft = useRef(0);
7926
+ const [draggingId, setDraggingId] = useState(null);
7927
+ const [dropTargetId, setDropTargetId] = useState(null);
7928
+ const [dropSide, setDropSide] = useState("before");
7858
7929
  useEffect(() => {
7859
7930
  if (!activeTabId || !scrollRef.current) return;
7860
7931
  const el = tabRefs.current.get(activeTabId);
@@ -7882,23 +7953,6 @@ function MultiTabBar(props) {
7882
7953
  if (scrollRef.current) obs.observe(scrollRef.current);
7883
7954
  return () => obs.disconnect();
7884
7955
  }, [tabs]);
7885
- const handleMouseDown = (e) => {
7886
- if (!scrollRef.current) return;
7887
- isDragging.current = true;
7888
- hasDragged.current = false;
7889
- dragStartX.current = e.pageX - scrollRef.current.getBoundingClientRect().left;
7890
- dragScrollLeft.current = scrollRef.current.scrollLeft;
7891
- };
7892
- const handleMouseMove = (e) => {
7893
- if (!isDragging.current || !scrollRef.current) return;
7894
- const x = e.pageX - scrollRef.current.getBoundingClientRect().left;
7895
- const walk = x - dragStartX.current;
7896
- if (Math.abs(walk) > 4) hasDragged.current = true;
7897
- scrollRef.current.scrollLeft = dragScrollLeft.current - walk;
7898
- };
7899
- const handleStopDrag = () => {
7900
- isDragging.current = false;
7901
- };
7902
7956
  useEffect(() => {
7903
7957
  const el = scrollRef.current;
7904
7958
  if (!el) return;
@@ -7909,6 +7963,46 @@ function MultiTabBar(props) {
7909
7963
  el.addEventListener("wheel", onWheel, { passive: false });
7910
7964
  return () => el.removeEventListener("wheel", onWheel);
7911
7965
  }, []);
7966
+ const handleDragStart = (e, tabId) => {
7967
+ setDraggingId(tabId);
7968
+ e.dataTransfer.effectAllowed = "move";
7969
+ };
7970
+ const handleDragOver = (e, tabId) => {
7971
+ e.preventDefault();
7972
+ if (tabId === draggingId) return;
7973
+ e.dataTransfer.dropEffect = "move";
7974
+ const rect = e.currentTarget.getBoundingClientRect();
7975
+ setDropSide(e.clientX < rect.left + rect.width / 2 ? "before" : "after");
7976
+ setDropTargetId(tabId);
7977
+ };
7978
+ const handleDrop = (e, targetTabId) => {
7979
+ e.preventDefault();
7980
+ if (!draggingId || draggingId === targetTabId) {
7981
+ setDraggingId(null);
7982
+ setDropTargetId(null);
7983
+ return;
7984
+ }
7985
+ if (onTabsReorder) {
7986
+ const fromIdx = tabs.findIndex((t) => t.id === draggingId);
7987
+ if (fromIdx !== -1) {
7988
+ const newTabs = [...tabs];
7989
+ const moved = newTabs.splice(fromIdx, 1)[0];
7990
+ let insertIdx = newTabs.findIndex((t) => t.id === targetTabId);
7991
+ if (insertIdx !== -1) {
7992
+ if (dropSide === "after") insertIdx += 1;
7993
+ newTabs.splice(insertIdx, 0, moved);
7994
+ onTabsReorder(newTabs);
7995
+ }
7996
+ }
7997
+ }
7998
+ setDraggingId(null);
7999
+ setDropTargetId(null);
8000
+ };
8001
+ const handleDragEnd = () => {
8002
+ setDraggingId(null);
8003
+ setDropTargetId(null);
8004
+ };
8005
+ const showPlus = !!onTabAdd;
7912
8006
  if (tabs.length === 0) return null;
7913
8007
  return /* @__PURE__ */ jsx("div", { className: `flex-shrink-0 w-full overflow-hidden ${s.container}`, children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-stretch", children: [
7914
8008
  showLeftFade && /* @__PURE__ */ jsx(
@@ -7923,7 +8017,7 @@ function MultiTabBar(props) {
7923
8017
  {
7924
8018
  className: "absolute top-0 bottom-0 w-10 pointer-events-none z-10",
7925
8019
  style: {
7926
- right: PLUS_BTN_WIDTH,
8020
+ right: showPlus ? PLUS_BTN_WIDTH : 0,
7927
8021
  background: easedFade("to left", s.fadeOpacity)
7928
8022
  }
7929
8023
  }
@@ -7935,12 +8029,10 @@ function MultiTabBar(props) {
7935
8029
  className: `flex-1 min-w-0 flex items-end gap-0.5 ${s.scrollPadding} overflow-x-auto [&::-webkit-scrollbar]:hidden`,
7936
8030
  style: { scrollbarWidth: "none" },
7937
8031
  onScroll: updateFades,
7938
- onMouseDown: handleMouseDown,
7939
- onMouseMove: handleMouseMove,
7940
- onMouseUp: handleStopDrag,
7941
- onMouseLeave: handleStopDrag,
7942
8032
  children: tabs.map((tab) => {
7943
8033
  const isActive = tab.id === activeTabId;
8034
+ const isDraggingThis = tab.id === draggingId;
8035
+ const isDropTarget = dropTargetId === tab.id;
7944
8036
  return /* @__PURE__ */ jsxs(
7945
8037
  "div",
7946
8038
  {
@@ -7948,15 +8040,23 @@ function MultiTabBar(props) {
7948
8040
  if (el) tabRefs.current.set(tab.id, el);
7949
8041
  else tabRefs.current.delete(tab.id);
7950
8042
  },
8043
+ draggable: !!onTabsReorder,
7951
8044
  onClick: () => {
7952
- if (hasDragged.current) return;
7953
8045
  if (!isActive) onTabClick(tab.id);
7954
8046
  },
8047
+ onDragStart: (e) => handleDragStart(e, tab.id),
8048
+ onDragOver: (e) => handleDragOver(e, tab.id),
8049
+ onDrop: (e) => handleDrop(e, tab.id),
8050
+ onDragEnd: handleDragEnd,
7955
8051
  className: `
7956
8052
  group relative flex items-center gap-1.5 ${s.tabPx} ${s.tabPy} flex-shrink-0
7957
- ${s.rounded} border border-b-0 cursor-pointer select-none
8053
+ ${s.rounded} border border-b-0 select-none
7958
8054
  transition-colors duration-100
7959
8055
  ${isActive ? s.activeTab : s.inactiveTab}
8056
+ ${onTabsReorder ? "cursor-grab" : "cursor-pointer"}
8057
+ ${isDraggingThis ? "opacity-40" : ""}
8058
+ ${isDropTarget && dropSide === "before" ? "[box-shadow:inset_2px_0_0_0_#3b82f6]" : ""}
8059
+ ${isDropTarget && dropSide === "after" ? "[box-shadow:inset_-2px_0_0_0_#3b82f6]" : ""}
7960
8060
  `,
7961
8061
  children: [
7962
8062
  isActive && /* @__PURE__ */ jsx(
@@ -7996,7 +8096,7 @@ function MultiTabBar(props) {
7996
8096
  })
7997
8097
  }
7998
8098
  ),
7999
- onTabAdd && /* @__PURE__ */ jsx(
8099
+ showPlus && /* @__PURE__ */ jsx(
8000
8100
  "div",
8001
8101
  {
8002
8102
  className: `flex-shrink-0 flex items-center justify-center ${s.plusBtnArea}`,
@@ -8993,6 +9093,7 @@ function Sidebar(props) {
8993
9093
  footer,
8994
9094
  className,
8995
9095
  showCollapseButton = false,
9096
+ collapseButtonPlacement = "right",
8996
9097
  variant = "light",
8997
9098
  primaryColor
8998
9099
  } = props;
@@ -9079,7 +9180,7 @@ function Sidebar(props) {
9079
9180
  "button",
9080
9181
  {
9081
9182
  onClick: () => setIsCollapsed((prev) => !prev),
9082
- 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}`,
9183
+ 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"}`,
9083
9184
  children: isCollapsed ? /* @__PURE__ */ jsx(PanelRightClose, { size: 18, color: primaryColor ?? s.collapseIcon }) : /* @__PURE__ */ jsx(PanelLeftClose, { size: 18, color: primaryColor ?? s.collapseIcon })
9084
9185
  }
9085
9186
  ),
@@ -9499,7 +9600,9 @@ function DetailModalFrame(props) {
9499
9600
  renderExtraContent,
9500
9601
  renderFooterExtra,
9501
9602
  onDirtyChange,
9502
- headerType
9603
+ headerType,
9604
+ headerIconVariant,
9605
+ headerIconSize
9503
9606
  } = props;
9504
9607
  const controller = useDetailController({ mode, onModeChange });
9505
9608
  useEffect(() => {
@@ -9543,7 +9646,9 @@ function DetailModalFrame(props) {
9543
9646
  {
9544
9647
  type: isEditMode ? "edit" : headerType,
9545
9648
  title,
9546
- onClose: handleClose
9649
+ onClose: handleClose,
9650
+ variant: headerIconVariant,
9651
+ size: headerIconSize
9547
9652
  }
9548
9653
  ) : /* @__PURE__ */ jsx(ModalHeader, { className: "flex flex-row items-center justify-between", children: /* @__PURE__ */ jsx("span", { className: "text-base font-semibold text-gray-900", children: title }) }),
9549
9654
  /* @__PURE__ */ jsxs(ModalBody, { className: "flex-1 min-h-0 overflow-hidden p-0 flex flex-col", children: [