uibee 3.1.4 → 3.1.6

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.
@@ -2731,7 +2731,19 @@ const VARIANT_CONTAINER = {
2731
2731
  };
2732
2732
  const VARIANT_THEAD = {
2733
2733
  original: "bg-login-700",
2734
- modern: "bg-transparent border-b border-login-500/40"
2734
+ modern: "bg-transparent"
2735
+ };
2736
+ const VARIANT_HEAD_BG = {
2737
+ original: "bg-login-700",
2738
+ modern: ""
2739
+ };
2740
+ const VARIANT_HEAD_BORDER = {
2741
+ original: "border-b border-login-600",
2742
+ modern: "border-b border-login-500/40"
2743
+ };
2744
+ const VARIANT_ROW_BORDER = {
2745
+ original: "border-b border-login-600",
2746
+ modern: "border-b border-login-600/15"
2735
2747
  };
2736
2748
  const VARIANT_THEAD_TH = {
2737
2749
  original: "text-login-200",
@@ -2764,17 +2776,16 @@ function Header({ columns, sort, onSort, hasMenu, hasSelect, hasExpand, allSelec
2764
2776
  });
2765
2777
  }
2766
2778
  return /* @__PURE__ */ jsx("thead", {
2767
- className: `block w-full sticky top-0 z-10 ${VARIANT_THEAD[variant]}`,
2768
- children: /* @__PURE__ */ jsxs("tr", {
2769
- className: "flex w-full",
2770
- children: [
2771
- hasSelect && /* @__PURE__ */ jsx("th", {
2779
+ className: VARIANT_THEAD[variant],
2780
+ children: /* @__PURE__ */ jsxs("tr", { children: [
2781
+ hasSelect && /* @__PURE__ */ jsx("th", {
2782
+ className: `sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}`,
2783
+ style: {
2784
+ width: "3rem",
2785
+ minWidth: "3rem"
2786
+ },
2787
+ children: /* @__PURE__ */ jsx("div", {
2772
2788
  className: "flex items-center justify-center",
2773
- style: {
2774
- width: "3rem",
2775
- minWidth: "3rem",
2776
- flexShrink: 0
2777
- },
2778
2789
  children: /* @__PURE__ */ jsxs("button", {
2779
2790
  type: "button",
2780
2791
  "aria-label": allSelected ? "Deselect all" : "Select all",
@@ -2796,62 +2807,58 @@ function Header({ columns, sort, onSort, hasMenu, hasSelect, hasExpand, allSelec
2796
2807
  })
2797
2808
  })]
2798
2809
  })
2799
- }),
2800
- columns.map((col) => {
2801
- const sortable = col.sortable !== false;
2802
- const isActive = sort?.column === col.key;
2803
- const ariaSort = isActive ? sort.order === "asc" ? "ascending" : "descending" : "none";
2804
- const alignClass = col.align === "right" ? "justify-end" : col.align === "center" ? "justify-center" : "";
2805
- return /* @__PURE__ */ jsx("th", {
2806
- "aria-sort": sortable ? ariaSort : void 0,
2807
- style: col.width ? {
2808
- width: col.width,
2809
- flexShrink: 0
2810
- } : void 0,
2811
- className: `
2812
- ${col.width ? "" : "flex-1"}
2810
+ })
2811
+ }),
2812
+ columns.map((col) => {
2813
+ const sortable = col.sortable !== false;
2814
+ const isActive = sort?.column === col.key;
2815
+ const ariaSort = isActive ? sort.order === "asc" ? "ascending" : "descending" : "none";
2816
+ const alignClass = col.align === "right" ? "justify-end" : col.align === "center" ? "justify-center" : "";
2817
+ return /* @__PURE__ */ jsx("th", {
2818
+ "aria-sort": sortable ? ariaSort : void 0,
2819
+ style: col.width ? { width: col.width } : void 0,
2820
+ className: `
2821
+ sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}
2813
2822
  text-xs font-medium uppercase tracking-wider
2814
2823
  ${DENSITY_TH[density]} ${VARIANT_THEAD_TH[variant]}
2815
2824
  `,
2816
- children: sortable ? /* @__PURE__ */ jsxs("button", {
2817
- type: "button",
2818
- className: `group inline-flex items-center gap-1.5 w-full cursor-pointer ${alignClass}`,
2819
- onClick: () => handleSort(col.key),
2820
- children: [/* @__PURE__ */ jsx("span", {
2821
- className: "whitespace-nowrap",
2822
- children: formatLabel(col.key, col.label)
2823
- }), /* @__PURE__ */ jsx("span", {
2824
- className: "shrink-0 text-current",
2825
- children: isActive ? sort.order === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-3.5 w-3.5 opacity-0 group-hover:opacity-35 transition-opacity" })
2826
- })]
2827
- }) : /* @__PURE__ */ jsx("span", {
2828
- className: `flex w-full ${alignClass}`,
2825
+ children: sortable ? /* @__PURE__ */ jsxs("button", {
2826
+ type: "button",
2827
+ className: `group inline-flex items-center gap-1.5 w-full cursor-pointer ${alignClass}`,
2828
+ onClick: () => handleSort(col.key),
2829
+ children: [/* @__PURE__ */ jsx("span", {
2830
+ className: "whitespace-nowrap",
2829
2831
  children: formatLabel(col.key, col.label)
2830
- })
2831
- }, col.key);
2832
- }),
2833
- hasExpand && /* @__PURE__ */ jsx("th", {
2834
- className: "flex items-center justify-center",
2835
- style: {
2836
- width: "2.5rem",
2837
- minWidth: "2.5rem",
2838
- flexShrink: 0
2839
- },
2840
- children: /* @__PURE__ */ jsx("span", {
2841
- className: `text-xs font-medium tracking-wider uppercase opacity-50 ${VARIANT_THEAD_TH[variant]}`,
2842
- children: "+"
2832
+ }), /* @__PURE__ */ jsx("span", {
2833
+ className: "shrink-0 text-current",
2834
+ children: isActive ? sort.order === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-3.5 w-3.5 opacity-0 group-hover:opacity-35 transition-opacity" })
2835
+ })]
2836
+ }) : /* @__PURE__ */ jsx("span", {
2837
+ className: `flex w-full whitespace-nowrap ${alignClass}`,
2838
+ children: formatLabel(col.key, col.label)
2843
2839
  })
2844
- }),
2845
- hasMenu && /* @__PURE__ */ jsx("th", {
2846
- "aria-hidden": "true",
2847
- style: {
2848
- width: "3.5rem",
2849
- minWidth: "3.5rem",
2850
- flexShrink: 0
2851
- }
2840
+ }, col.key);
2841
+ }),
2842
+ hasExpand && /* @__PURE__ */ jsx("th", {
2843
+ className: `sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}`,
2844
+ style: {
2845
+ width: "2.5rem",
2846
+ minWidth: "2.5rem"
2847
+ },
2848
+ children: /* @__PURE__ */ jsx("span", {
2849
+ className: `flex items-center justify-center text-xs font-medium tracking-wider uppercase opacity-50 ${VARIANT_THEAD_TH[variant]}`,
2850
+ children: "+"
2852
2851
  })
2853
- ]
2854
- })
2852
+ }),
2853
+ hasMenu && /* @__PURE__ */ jsx("th", {
2854
+ "aria-hidden": "true",
2855
+ className: `sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}`,
2856
+ style: {
2857
+ width: "3.5rem",
2858
+ minWidth: "3.5rem"
2859
+ }
2860
+ })
2861
+ ] })
2855
2862
  });
2856
2863
  }
2857
2864
  //#endregion
@@ -2997,7 +3004,7 @@ function resolveRedirectUrl(redirectPath, row, id) {
2997
3004
  const rid = cfg.key ? String(row[cfg.key] ?? id) : id;
2998
3005
  return cfg.path.includes("?") ? `${cfg.path}${rid}` : `${cfg.path}/${rid}`;
2999
3006
  }
3000
- function Cell({ col, row, density }) {
3007
+ function Cell({ col, row, density, variant }) {
3001
3008
  const value = row[col.key];
3002
3009
  const shouldTruncate = col.truncate === true;
3003
3010
  const align = col.align ?? "left";
@@ -3025,16 +3032,13 @@ function Cell({ col, row, density }) {
3025
3032
  return /* @__PURE__ */ jsx("td", {
3026
3033
  style: col.width ? {
3027
3034
  width: col.width,
3028
- flexShrink: 0
3035
+ maxWidth: col.width
3029
3036
  } : void 0,
3030
3037
  className: `
3031
- ${col.width ? "" : "flex-1"} ${shouldTruncate ? "min-w-0" : ""} flex items-center text-sm text-login-75
3032
- ${DENSITY_TD[density]}
3038
+ align-middle text-sm text-login-75 ${wrapperAlign}
3039
+ ${DENSITY_TD[density]} ${VARIANT_ROW_BORDER[variant]}
3033
3040
  `,
3034
- children: /* @__PURE__ */ jsx("div", {
3035
- className: `min-w-0 w-full ${wrapperAlign}`,
3036
- children: content
3037
- })
3041
+ children: content
3038
3042
  });
3039
3043
  }
3040
3044
  function Body({ data, columns, idKey, variant, density, striped, redirectPath, onRowClick, renderExpandedRow, selectable, selectedIds, onSelectionChange, menuItems }) {
@@ -3043,16 +3047,14 @@ function Body({ data, columns, idKey, variant, density, striped, redirectPath, o
3043
3047
  const [anchor, setAnchor] = useState(null);
3044
3048
  const [expandedId, setExpandedId] = useState(null);
3045
3049
  const menuRef = useRef(null);
3046
- const tbodyRef = useRef(null);
3047
3050
  const menuWasOpenOnMouseDown = useRef(false);
3048
3051
  const closeMenu = useCallback(() => setOpenMenuId(null), []);
3049
3052
  useClickOutside(menuRef, closeMenu, openMenuId !== null);
3050
3053
  useEffect(() => {
3051
- const el = tbodyRef.current;
3052
- if (!el) return;
3053
- el.addEventListener("scroll", closeMenu);
3054
- return () => el.removeEventListener("scroll", closeMenu);
3055
- }, [closeMenu]);
3054
+ if (openMenuId === null) return;
3055
+ document.addEventListener("scroll", closeMenu, true);
3056
+ return () => document.removeEventListener("scroll", closeMenu, true);
3057
+ }, [closeMenu, openMenuId]);
3056
3058
  function openMenu(id, coords) {
3057
3059
  setAnchor(coords);
3058
3060
  setOpenMenuId(id);
@@ -3064,8 +3066,7 @@ function Body({ data, columns, idKey, variant, density, striped, redirectPath, o
3064
3066
  const hasMenu = Boolean(menuItems);
3065
3067
  const hasExpand = Boolean(renderExpandedRow);
3066
3068
  return /* @__PURE__ */ jsx("tbody", {
3067
- ref: tbodyRef,
3068
- className: `block overflow-y-auto flex-1 min-h-0 divide-y ${VARIANT_TBODY[variant]}`,
3069
+ className: VARIANT_TBODY[variant],
3069
3070
  children: data.map((row, rowIdx) => {
3070
3071
  const id = resolveId(row, idKey, columns);
3071
3072
  const url = resolveRedirectUrl(redirectPath, row, id);
@@ -3075,7 +3076,7 @@ function Body({ data, columns, idKey, variant, density, striped, redirectPath, o
3075
3076
  const isSelected = selectable && selectedSet.has(id);
3076
3077
  const expandedContent = renderExpandedRow?.(row);
3077
3078
  const rowClass = [
3078
- "flex w-full transition-colors duration-100",
3079
+ "transition-colors duration-100",
3079
3080
  isClickable ? "cursor-pointer" : "",
3080
3081
  VARIANT_ROW_HOVER[variant],
3081
3082
  striped ? VARIANT_ROW_STRIPED[variant] : "",
@@ -3114,35 +3115,37 @@ function Body({ data, columns, idKey, variant, density, striped, redirectPath, o
3114
3115
  },
3115
3116
  children: [
3116
3117
  selectable && /* @__PURE__ */ jsx("td", {
3117
- className: "flex items-center justify-center",
3118
+ className: `align-middle ${VARIANT_ROW_BORDER[variant]}`,
3118
3119
  style: {
3119
3120
  width: "3rem",
3120
- minWidth: "3rem",
3121
- flexShrink: 0
3121
+ minWidth: "3rem"
3122
3122
  },
3123
- children: /* @__PURE__ */ jsx("button", {
3124
- type: "button",
3125
- "aria-label": isSelected ? "Deselect row" : "Select row",
3126
- "aria-checked": isSelected,
3127
- role: "checkbox",
3128
- onClick: (e) => {
3129
- e.stopPropagation();
3130
- toggleSelect(id);
3131
- },
3132
- className: `
3123
+ children: /* @__PURE__ */ jsx("div", {
3124
+ className: "flex items-center justify-center",
3125
+ children: /* @__PURE__ */ jsx("button", {
3126
+ type: "button",
3127
+ "aria-label": isSelected ? "Deselect row" : "Select row",
3128
+ "aria-checked": isSelected,
3129
+ role: "checkbox",
3130
+ onClick: (e) => {
3131
+ e.stopPropagation();
3132
+ toggleSelect(id);
3133
+ },
3134
+ className: `
3133
3135
  h-4 w-4 rounded border flex items-center justify-center transition-colors cursor-pointer
3134
3136
  ${isSelected ? "bg-login border-login text-white" : "border-login-400 bg-transparent hover:border-login-100"}
3135
3137
  `,
3136
- children: isSelected && /* @__PURE__ */ jsx("svg", {
3137
- className: "h-3 w-3",
3138
- viewBox: "0 0 12 12",
3139
- fill: "none",
3140
- children: /* @__PURE__ */ jsx("path", {
3141
- d: "M2 6l3 3 5-5",
3142
- stroke: "currentColor",
3143
- strokeWidth: "1.5",
3144
- strokeLinecap: "round",
3145
- strokeLinejoin: "round"
3138
+ children: isSelected && /* @__PURE__ */ jsx("svg", {
3139
+ className: "h-3 w-3",
3140
+ viewBox: "0 0 12 12",
3141
+ fill: "none",
3142
+ children: /* @__PURE__ */ jsx("path", {
3143
+ d: "M2 6l3 3 5-5",
3144
+ stroke: "currentColor",
3145
+ strokeWidth: "1.5",
3146
+ strokeLinecap: "round",
3147
+ strokeLinejoin: "round"
3148
+ })
3146
3149
  })
3147
3150
  })
3148
3151
  })
@@ -3150,29 +3153,31 @@ function Body({ data, columns, idKey, variant, density, striped, redirectPath, o
3150
3153
  columns.map((col) => /* @__PURE__ */ jsx(Cell, {
3151
3154
  col,
3152
3155
  row,
3153
- density
3156
+ density,
3157
+ variant
3154
3158
  }, col.key)),
3155
3159
  hasExpand && /* @__PURE__ */ jsx("td", {
3156
- className: "flex items-center justify-center",
3160
+ className: `align-middle ${VARIANT_ROW_BORDER[variant]}`,
3157
3161
  style: {
3158
3162
  width: "2.5rem",
3159
- minWidth: "2.5rem",
3160
- flexShrink: 0
3163
+ minWidth: "2.5rem"
3161
3164
  },
3162
- children: /* @__PURE__ */ jsx(ChevronDown, { className: `
3165
+ children: /* @__PURE__ */ jsx("div", {
3166
+ className: "flex items-center justify-center",
3167
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: `
3163
3168
  h-4 w-4 transition-transform duration-200
3164
3169
  ${isExpanded ? "rotate-180 text-login" : "text-login-400"}
3165
3170
  ` })
3171
+ })
3166
3172
  }),
3167
3173
  hasMenu && /* @__PURE__ */ jsx("td", {
3168
- className: "flex items-center justify-end pr-3",
3174
+ className: `align-middle pr-3 ${VARIANT_ROW_BORDER[variant]}`,
3169
3175
  style: {
3170
3176
  width: "3.5rem",
3171
- minWidth: "3.5rem",
3172
- flexShrink: 0
3177
+ minWidth: "3.5rem"
3173
3178
  },
3174
3179
  children: /* @__PURE__ */ jsxs("div", {
3175
- className: "relative",
3180
+ className: "relative flex items-center justify-end",
3176
3181
  children: [/* @__PURE__ */ jsx("button", {
3177
3182
  type: "button",
3178
3183
  "aria-label": "Row actions",
@@ -3205,9 +3210,10 @@ function Body({ data, columns, idKey, variant, density, striped, redirectPath, o
3205
3210
  })
3206
3211
  ]
3207
3212
  }), hasExpand && isExpanded && /* @__PURE__ */ jsx("tr", {
3208
- className: "flex w-full bg-login-700/25 border-b border-login-600/20",
3213
+ className: "bg-login-700/25",
3209
3214
  children: /* @__PURE__ */ jsx("td", {
3210
- className: "w-full px-6 py-4",
3215
+ colSpan: 999,
3216
+ className: "px-6 py-4 border-b border-login-600/20",
3211
3217
  onClick: (e) => e.stopPropagation(),
3212
3218
  children: expandedContent
3213
3219
  })
@@ -3219,81 +3225,58 @@ function Body({ data, columns, idKey, variant, density, striped, redirectPath, o
3219
3225
  //#region src/components/table/skeleton.tsx
3220
3226
  function Skeleton({ columns, rows, variant, density, hasMenu, hasSelect }) {
3221
3227
  return /* @__PURE__ */ jsx("tbody", {
3222
- className: `block divide-y divide-login-600/15 ${VARIANT_TBODY[variant]}`,
3223
- children: Array.from({ length: rows }).map((_, rowIdx) => /* @__PURE__ */ jsxs("tr", {
3224
- className: "flex w-full",
3225
- children: [
3226
- hasSelect && /* @__PURE__ */ jsx("td", {
3227
- className: "flex items-center justify-center",
3228
- style: {
3229
- width: "3rem",
3230
- minWidth: "3rem",
3231
- flexShrink: 0
3232
- },
3233
- children: /* @__PURE__ */ jsx("span", { className: "block h-4 w-4 rounded animate-shimmer" })
3234
- }),
3235
- columns.map((col, colIdx) => /* @__PURE__ */ jsx("td", {
3236
- style: col.width ? {
3237
- width: col.width,
3238
- flexShrink: 0
3239
- } : void 0,
3240
- className: `
3241
- flex-1 min-w-0 flex items-center
3242
- ${DENSITY_TD[density]}
3243
- `,
3244
- children: /* @__PURE__ */ jsx("span", {
3245
- className: "block h-4 rounded animate-shimmer",
3246
- style: {
3247
- width: colIdx === 0 ? `${55 + (rowIdx * 17 + colIdx * 31) % 30}%` : `${40 + (rowIdx * 13 + colIdx * 23) % 40}%`,
3248
- animationDelay: `${(rowIdx * columns.length + colIdx) * 40}ms`
3249
- }
3250
- })
3251
- }, col.key)),
3252
- hasMenu && /* @__PURE__ */ jsx("td", {
3253
- className: "flex items-center justify-end pr-3",
3228
+ className: VARIANT_TBODY[variant],
3229
+ children: Array.from({ length: rows }).map((_, rowIdx) => /* @__PURE__ */ jsxs("tr", { children: [
3230
+ hasSelect && /* @__PURE__ */ jsx("td", {
3231
+ className: `align-middle ${VARIANT_ROW_BORDER[variant]}`,
3232
+ style: {
3233
+ width: "3rem",
3234
+ minWidth: "3rem"
3235
+ },
3236
+ children: /* @__PURE__ */ jsx("span", { className: "block h-4 w-4 mx-auto rounded animate-shimmer" })
3237
+ }),
3238
+ columns.map((col, colIdx) => /* @__PURE__ */ jsx("td", {
3239
+ style: col.width ? { width: col.width } : void 0,
3240
+ className: `align-middle ${DENSITY_TD[density]} ${VARIANT_ROW_BORDER[variant]}`,
3241
+ children: /* @__PURE__ */ jsx("span", {
3242
+ className: "block h-4 rounded animate-shimmer",
3254
3243
  style: {
3255
- width: "3.5rem",
3256
- minWidth: "3.5rem",
3257
- flexShrink: 0
3258
- },
3259
- children: /* @__PURE__ */ jsx("span", { className: "block h-5 w-5 rounded animate-shimmer" })
3244
+ width: colIdx === 0 ? `${8 + (rowIdx * 17 + colIdx * 31) % 5}rem` : `${5 + (rowIdx * 13 + colIdx * 23) % 5}rem`,
3245
+ animationDelay: `${(rowIdx * columns.length + colIdx) * 40}ms`
3246
+ }
3260
3247
  })
3261
- ]
3262
- }, rowIdx))
3248
+ }, col.key)),
3249
+ hasMenu && /* @__PURE__ */ jsx("td", {
3250
+ className: `align-middle pr-3 ${VARIANT_ROW_BORDER[variant]}`,
3251
+ style: {
3252
+ width: "3.5rem",
3253
+ minWidth: "3.5rem"
3254
+ },
3255
+ children: /* @__PURE__ */ jsx("span", { className: "block h-5 w-5 ml-auto rounded animate-shimmer" })
3256
+ })
3257
+ ] }, rowIdx))
3263
3258
  });
3264
3259
  }
3265
3260
  //#endregion
3266
3261
  //#region src/components/table/empty.tsx
3267
3262
  function Empty({ emptyState }) {
3268
- if (emptyState) return /* @__PURE__ */ jsx("tbody", {
3269
- className: "block w-full",
3270
- children: /* @__PURE__ */ jsx("tr", {
3271
- className: "flex w-full",
3272
- children: /* @__PURE__ */ jsx("td", {
3273
- className: "w-full",
3274
- children: /* @__PURE__ */ jsx("div", {
3275
- className: "flex items-center justify-center min-h-[160px] py-8 px-6",
3276
- children: emptyState
3277
- })
3278
- })
3263
+ if (emptyState) return /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
3264
+ colSpan: 999,
3265
+ children: /* @__PURE__ */ jsx("div", {
3266
+ className: "flex items-center justify-center min-h-[160px] py-8 px-6",
3267
+ children: emptyState
3279
3268
  })
3280
- });
3281
- return /* @__PURE__ */ jsx("tbody", {
3282
- className: "block w-full",
3283
- children: /* @__PURE__ */ jsx("tr", {
3284
- className: "flex w-full",
3285
- children: /* @__PURE__ */ jsx("td", {
3286
- className: "w-full",
3287
- children: /* @__PURE__ */ jsxs("div", {
3288
- className: "flex flex-col items-center justify-center gap-3 min-h-[160px] py-8 text-login-300",
3289
- children: [/* @__PURE__ */ jsx(TableIcon, { className: "h-10 w-10 opacity-30" }), /* @__PURE__ */ jsx("span", {
3290
- className: "text-sm",
3291
- children: "No data found"
3292
- })]
3293
- })
3294
- })
3269
+ }) }) });
3270
+ return /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
3271
+ colSpan: 999,
3272
+ children: /* @__PURE__ */ jsxs("div", {
3273
+ className: "flex flex-col items-center justify-center gap-3 min-h-[160px] py-8 text-login-300",
3274
+ children: [/* @__PURE__ */ jsx(TableIcon, { className: "h-10 w-10 opacity-30" }), /* @__PURE__ */ jsx("span", {
3275
+ className: "text-sm",
3276
+ children: "No data found"
3277
+ })]
3295
3278
  })
3296
- });
3279
+ }) }) });
3297
3280
  }
3298
3281
  //#endregion
3299
3282
  //#region src/components/table/pagination.tsx
@@ -3443,9 +3426,9 @@ function TableShell({ data, columns, idKey, variant, density, striped, loading,
3443
3426
  className
3444
3427
  ].filter(Boolean).join(" "),
3445
3428
  children: [/* @__PURE__ */ jsx("div", {
3446
- className: "flex-1 min-h-0 overflow-x-auto",
3429
+ className: "flex-1 min-h-0 overflow-auto",
3447
3430
  children: /* @__PURE__ */ jsxs("table", {
3448
- className: "min-w-full w-max flex flex-col flex-1 min-h-0 divide-y divide-login-600/20",
3431
+ className: "min-w-full table-auto border-separate border-spacing-0",
3449
3432
  children: [/* @__PURE__ */ jsx(Header, {
3450
3433
  columns,
3451
3434
  sort,
package/dist/style.css CHANGED
@@ -1580,6 +1580,9 @@
1580
1580
  .ml-1 {
1581
1581
  margin-left: var(--spacing);
1582
1582
  }
1583
+ .ml-auto {
1584
+ margin-left: auto;
1585
+ }
1583
1586
  .noscroll {
1584
1587
  -ms-overflow-style: none;
1585
1588
  scrollbar-width: none;
@@ -1777,9 +1780,6 @@
1777
1780
  .w-full {
1778
1781
  width: 100%;
1779
1782
  }
1780
- .w-max {
1781
- width: max-content;
1782
- }
1783
1783
  .max-w-2xl {
1784
1784
  max-width: var(--container-2xl);
1785
1785
  }
@@ -1837,6 +1837,17 @@
1837
1837
  .shrink-0 {
1838
1838
  flex-shrink: 0;
1839
1839
  }
1840
+ .table-auto {
1841
+ table-layout: auto;
1842
+ }
1843
+ .border-separate {
1844
+ border-collapse: separate;
1845
+ }
1846
+ .border-spacing-0 {
1847
+ --tw-border-spacing-x: 0;
1848
+ --tw-border-spacing-y: 0;
1849
+ border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
1850
+ }
1840
1851
  .origin-center {
1841
1852
  transform-origin: center;
1842
1853
  }
@@ -2013,14 +2024,6 @@
2013
2024
  }
2014
2025
  }
2015
2026
  }
2016
- .divide-login-600\/20 {
2017
- :where(& > :not(:last-child)) {
2018
- border-color: color-mix(in srgb, #212121 20%, transparent);
2019
- @supports (color: color-mix(in lab, red, red)) {
2020
- border-color: color-mix(in oklab, var(--color-login-600) 20%, transparent);
2021
- }
2022
- }
2023
- }
2024
2027
  .truncate {
2025
2028
  overflow: hidden;
2026
2029
  text-overflow: ellipsis;
@@ -2032,9 +2035,6 @@
2032
2035
  .overflow-hidden {
2033
2036
  overflow: hidden;
2034
2037
  }
2035
- .overflow-x-auto {
2036
- overflow-x: auto;
2037
- }
2038
2038
  .overflow-y-auto {
2039
2039
  overflow-y: auto;
2040
2040
  }
@@ -2143,6 +2143,12 @@
2143
2143
  .border-login-600 {
2144
2144
  border-color: var(--color-login-600);
2145
2145
  }
2146
+ .border-login-600\/15 {
2147
+ border-color: color-mix(in srgb, #212121 15%, transparent);
2148
+ @supports (color: color-mix(in lab, red, red)) {
2149
+ border-color: color-mix(in oklab, var(--color-login-600) 15%, transparent);
2150
+ }
2151
+ }
2146
2152
  .border-login-600\/20 {
2147
2153
  border-color: color-mix(in srgb, #212121 20%, transparent);
2148
2154
  @supports (color: color-mix(in lab, red, red)) {
@@ -3826,6 +3832,16 @@ input::-ms-reveal,
3826
3832
  input::-ms-clear {
3827
3833
  display: none !important;
3828
3834
  }
3835
+ @property --tw-border-spacing-x {
3836
+ syntax: "<length>";
3837
+ inherits: false;
3838
+ initial-value: 0;
3839
+ }
3840
+ @property --tw-border-spacing-y {
3841
+ syntax: "<length>";
3842
+ inherits: false;
3843
+ initial-value: 0;
3844
+ }
3829
3845
  @property --tw-translate-x {
3830
3846
  syntax: "*";
3831
3847
  inherits: false;
@@ -4137,6 +4153,8 @@ input::-ms-clear {
4137
4153
  @layer properties {
4138
4154
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
4139
4155
  *, ::before, ::after, ::backdrop {
4156
+ --tw-border-spacing-x: 0;
4157
+ --tw-border-spacing-y: 0;
4140
4158
  --tw-translate-x: 0;
4141
4159
  --tw-translate-y: 0;
4142
4160
  --tw-translate-z: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "3.1.4",
3
+ "version": "3.1.6",
4
4
  "description": "Shared components, functions and hooks for reuse across Login projects",
5
5
  "homepage": "https://github.com/Login-Linjeforening-for-IT/uibee#readme",
6
6
  "bugs": {
@@ -4,7 +4,7 @@ import React, { useState, useRef, useEffect, useCallback, useMemo, type RefObjec
4
4
  import { useRouter } from 'next/navigation'
5
5
  import { EllipsisVertical, ChevronDown } from 'lucide-react'
6
6
  import type { Column, Density, TableVariant, MenuAnchor, TableShellProps } from './types'
7
- import { HIGHLIGHT, DENSITY_TD, VARIANT_TBODY, VARIANT_ROW_HOVER, VARIANT_ROW_STRIPED } from './constants'
7
+ import { HIGHLIGHT, DENSITY_TD, VARIANT_TBODY, VARIANT_ROW_HOVER, VARIANT_ROW_STRIPED, VARIANT_ROW_BORDER } from './constants'
8
8
  import { formatValue } from './format'
9
9
  import { resolveId } from './utils'
10
10
  import Menu from './menu'
@@ -38,13 +38,14 @@ function Cell<T extends Record<string, unknown>>({
38
38
  col,
39
39
  row,
40
40
  density,
41
+ variant,
41
42
  }: {
42
43
  col: Column<T>
43
44
  row: T
44
45
  density: Density
46
+ variant: TableVariant
45
47
  }) {
46
48
  const value = row[col.key]
47
- // Truncate: opt-in, only applied when col.truncate === true
48
49
  const shouldTruncate = col.truncate === true
49
50
  const align = col.align ?? 'left'
50
51
 
@@ -55,7 +56,6 @@ function Cell<T extends Record<string, unknown>>({
55
56
  let content: React.ReactNode
56
57
 
57
58
  if (col.render) {
58
- // Custom renderer: caller is responsible for alignment
59
59
  content = col.render(value, row)
60
60
  } else if (col.highlight) {
61
61
  const colorName = col.highlight[String(value)] ?? col.highlight['default']
@@ -84,16 +84,13 @@ function Cell<T extends Record<string, unknown>>({
84
84
 
85
85
  return (
86
86
  <td
87
- style={col.width ? { width: col.width, flexShrink: 0 } : undefined}
87
+ style={col.width ? { width: col.width, maxWidth: col.width } : undefined}
88
88
  className={`
89
- ${col.width ? '' : 'flex-1'} ${shouldTruncate ? 'min-w-0' : ''} flex items-center text-sm text-login-75
90
- ${DENSITY_TD[density]}
89
+ align-middle text-sm text-login-75 ${wrapperAlign}
90
+ ${DENSITY_TD[density]} ${VARIANT_ROW_BORDER[variant]}
91
91
  `}
92
92
  >
93
- {/* w-full fills the column; text-align handles alignment */}
94
- <div className={`min-w-0 w-full ${wrapperAlign}`}>
95
- {content}
96
- </div>
93
+ {content}
97
94
  </td>
98
95
  )
99
96
  }
@@ -118,18 +115,16 @@ export default function Body<T extends Record<string, unknown>>({
118
115
  const [expandedId, setExpandedId] = useState<string | null>(null)
119
116
 
120
117
  const menuRef = useRef<HTMLDivElement>(null)
121
- const tbodyRef = useRef<HTMLTableSectionElement>(null)
122
118
  const menuWasOpenOnMouseDown = useRef(false)
123
119
 
124
120
  const closeMenu = useCallback(() => setOpenMenuId(null), [])
125
121
  useClickOutside(menuRef as RefObject<HTMLElement>, closeMenu, openMenuId !== null)
126
122
 
127
123
  useEffect(() => {
128
- const el = tbodyRef.current
129
- if (!el) return
130
- el.addEventListener('scroll', closeMenu)
131
- return () => el.removeEventListener('scroll', closeMenu)
132
- }, [closeMenu])
124
+ if (openMenuId === null) return
125
+ document.addEventListener('scroll', closeMenu, true)
126
+ return () => document.removeEventListener('scroll', closeMenu, true)
127
+ }, [closeMenu, openMenuId])
133
128
 
134
129
  function openMenu(id: string, coords: MenuAnchor) {
135
130
  setAnchor(coords)
@@ -149,10 +144,7 @@ export default function Body<T extends Record<string, unknown>>({
149
144
  const hasExpand = Boolean(renderExpandedRow)
150
145
 
151
146
  return (
152
- <tbody
153
- ref={tbodyRef}
154
- className={`block overflow-y-auto flex-1 min-h-0 divide-y ${VARIANT_TBODY[variant]}`}
155
- >
147
+ <tbody className={VARIANT_TBODY[variant]}>
156
148
  {data.map((row, rowIdx) => {
157
149
  const id = resolveId(row as Record<string, unknown>, idKey as string | undefined, columns as Column<Record<string, unknown>>[])
158
150
  const url = resolveRedirectUrl(redirectPath, row as Record<string, unknown>, id)
@@ -163,7 +155,7 @@ export default function Body<T extends Record<string, unknown>>({
163
155
  const expandedContent = renderExpandedRow?.(row)
164
156
 
165
157
  const rowClass = [
166
- 'flex w-full transition-colors duration-100',
158
+ 'transition-colors duration-100',
167
159
  isClickable ? 'cursor-pointer' : '',
168
160
  VARIANT_ROW_HOVER[variant],
169
161
  striped ? VARIANT_ROW_STRIPED[variant] : '',
@@ -197,8 +189,9 @@ export default function Body<T extends Record<string, unknown>>({
197
189
  }}
198
190
  >
199
191
  {selectable && (
200
- <td className='flex items-center justify-center'
201
- style={{ width: '3rem', minWidth: '3rem', flexShrink: 0 }}>
192
+ <td className={`align-middle ${VARIANT_ROW_BORDER[variant]}`}
193
+ style={{ width: '3rem', minWidth: '3rem' }}>
194
+ <div className='flex items-center justify-center'>
202
195
  <button
203
196
  type='button'
204
197
  aria-label={isSelected ? 'Deselect row' : 'Select row'}
@@ -219,27 +212,30 @@ export default function Body<T extends Record<string, unknown>>({
219
212
  </svg>
220
213
  )}
221
214
  </button>
215
+ </div>
222
216
  </td>
223
217
  )}
224
218
 
225
219
  {columns.map((col) => (
226
- <Cell key={col.key} col={col} row={row} density={density} />
220
+ <Cell key={col.key} col={col} row={row} density={density} variant={variant} />
227
221
  ))}
228
222
 
229
223
  {hasExpand && (
230
- <td className='flex items-center justify-center'
231
- style={{ width: '2.5rem', minWidth: '2.5rem', flexShrink: 0 }}>
224
+ <td className={`align-middle ${VARIANT_ROW_BORDER[variant]}`}
225
+ style={{ width: '2.5rem', minWidth: '2.5rem' }}>
226
+ <div className='flex items-center justify-center'>
232
227
  <ChevronDown className={`
233
228
  h-4 w-4 transition-transform duration-200
234
229
  ${isExpanded ? 'rotate-180 text-login' : 'text-login-400'}
235
230
  `} />
231
+ </div>
236
232
  </td>
237
233
  )}
238
234
 
239
235
  {hasMenu && (
240
- <td className='flex items-center justify-end pr-3'
241
- style={{ width: '3.5rem', minWidth: '3.5rem', flexShrink: 0 }}>
242
- <div className='relative'>
236
+ <td className={`align-middle pr-3 ${VARIANT_ROW_BORDER[variant]}`}
237
+ style={{ width: '3.5rem', minWidth: '3.5rem' }}>
238
+ <div className='relative flex items-center justify-end'>
243
239
  <button
244
240
  type='button'
245
241
  aria-label='Row actions'
@@ -276,9 +272,10 @@ export default function Body<T extends Record<string, unknown>>({
276
272
  </tr>
277
273
 
278
274
  {hasExpand && isExpanded && (
279
- <tr className='flex w-full bg-login-700/25 border-b border-login-600/20'>
275
+ <tr className='bg-login-700/25'>
280
276
  <td
281
- className='w-full px-6 py-4'
277
+ colSpan={999}
278
+ className='px-6 py-4 border-b border-login-600/20'
282
279
  onClick={(e) => e.stopPropagation()}
283
280
  >
284
281
  {expandedContent}
@@ -29,7 +29,22 @@ export const VARIANT_CONTAINER: Record<TableVariant, string> = {
29
29
 
30
30
  export const VARIANT_THEAD: Record<TableVariant, string> = {
31
31
  original: 'bg-login-700',
32
- modern: 'bg-transparent border-b border-login-500/40',
32
+ modern: 'bg-transparent',
33
+ }
34
+
35
+ export const VARIANT_HEAD_BG: Record<TableVariant, string> = {
36
+ original: 'bg-login-700',
37
+ modern: '',
38
+ }
39
+
40
+ export const VARIANT_HEAD_BORDER: Record<TableVariant, string> = {
41
+ original: 'border-b border-login-600',
42
+ modern: 'border-b border-login-500/40',
43
+ }
44
+
45
+ export const VARIANT_ROW_BORDER: Record<TableVariant, string> = {
46
+ original: 'border-b border-login-600',
47
+ modern: 'border-b border-login-600/15',
33
48
  }
34
49
 
35
50
  export const VARIANT_THEAD_TH: Record<TableVariant, string> = {
@@ -4,9 +4,9 @@ import type { ReactNode } from 'react'
4
4
  export default function Empty({ emptyState }: { emptyState?: ReactNode }) {
5
5
  if (emptyState) {
6
6
  return (
7
- <tbody className='block w-full'>
8
- <tr className='flex w-full'>
9
- <td className='w-full'>
7
+ <tbody>
8
+ <tr>
9
+ <td colSpan={999}>
10
10
  <div className='flex items-center justify-center min-h-[160px] py-8 px-6'>
11
11
  {emptyState}
12
12
  </div>
@@ -17,9 +17,9 @@ export default function Empty({ emptyState }: { emptyState?: ReactNode }) {
17
17
  }
18
18
 
19
19
  return (
20
- <tbody className='block w-full'>
21
- <tr className='flex w-full'>
22
- <td className='w-full'>
20
+ <tbody>
21
+ <tr>
22
+ <td colSpan={999}>
23
23
  <div className='flex flex-col items-center justify-center gap-3 min-h-[160px] py-8 text-login-300'>
24
24
  <TableIcon className='h-10 w-10 opacity-30' />
25
25
  <span className='text-sm'>No data found</span>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
4
4
  import type { Column, SortState, Density, TableVariant } from './types'
5
- import { DENSITY_TH, VARIANT_THEAD, VARIANT_THEAD_TH } from './constants'
5
+ import { DENSITY_TH, VARIANT_THEAD, VARIANT_THEAD_TH, VARIANT_HEAD_BORDER, VARIANT_HEAD_BG } from './constants'
6
6
 
7
7
  type HeaderProps<T extends Record<string, unknown>> = {
8
8
  columns: Column<T>[]
@@ -43,12 +43,13 @@ export default function Header<T extends Record<string, unknown>>({
43
43
  }
44
44
 
45
45
  return (
46
- <thead className={`block w-full sticky top-0 z-10 ${VARIANT_THEAD[variant]}`}>
47
- <tr className='flex w-full'>
46
+ <thead className={VARIANT_THEAD[variant]}>
47
+ <tr>
48
48
 
49
49
  {hasSelect && (
50
- <th className='flex items-center justify-center'
51
- style={{ width: '3rem', minWidth: '3rem', flexShrink: 0 }}>
50
+ <th className={`sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}`}
51
+ style={{ width: '3rem', minWidth: '3rem' }}>
52
+ <div className='flex items-center justify-center'>
52
53
  <button
53
54
  type='button'
54
55
  aria-label={allSelected ? 'Deselect all' : 'Select all'}
@@ -70,6 +71,7 @@ export default function Header<T extends Record<string, unknown>>({
70
71
  </svg>
71
72
  )}
72
73
  </button>
74
+ </div>
73
75
  </th>
74
76
  )}
75
77
 
@@ -88,9 +90,9 @@ export default function Header<T extends Record<string, unknown>>({
88
90
  <th
89
91
  key={col.key}
90
92
  aria-sort={sortable ? ariaSort : undefined}
91
- style={col.width ? { width: col.width, flexShrink: 0 } : undefined}
93
+ style={col.width ? { width: col.width } : undefined}
92
94
  className={`
93
- ${col.width ? '' : 'flex-1'}
95
+ sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}
94
96
  text-xs font-medium uppercase tracking-wider
95
97
  ${DENSITY_TH[density]} ${VARIANT_THEAD_TH[variant]}
96
98
  `}
@@ -113,7 +115,7 @@ export default function Header<T extends Record<string, unknown>>({
113
115
  </span>
114
116
  </button>
115
117
  ) : (
116
- <span className={`flex w-full ${alignClass}`}>
118
+ <span className={`flex w-full whitespace-nowrap ${alignClass}`}>
117
119
  {formatLabel(col.key, col.label)}
118
120
  </span>
119
121
  )}
@@ -122,9 +124,9 @@ export default function Header<T extends Record<string, unknown>>({
122
124
  })}
123
125
 
124
126
  {hasExpand && (
125
- <th className='flex items-center justify-center'
126
- style={{ width: '2.5rem', minWidth: '2.5rem', flexShrink: 0 }}>
127
- <span className={`text-xs font-medium tracking-wider uppercase opacity-50 ${VARIANT_THEAD_TH[variant]}`}>
127
+ <th className={`sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}`}
128
+ style={{ width: '2.5rem', minWidth: '2.5rem' }}>
129
+ <span className={`flex items-center justify-center text-xs font-medium tracking-wider uppercase opacity-50 ${VARIANT_THEAD_TH[variant]}`}>
128
130
  +
129
131
  </span>
130
132
  </th>
@@ -132,7 +134,8 @@ export default function Header<T extends Record<string, unknown>>({
132
134
 
133
135
  {hasMenu && (
134
136
  <th aria-hidden='true'
135
- style={{ width: '3.5rem', minWidth: '3.5rem', flexShrink: 0 }} />
137
+ className={`sticky top-0 z-10 ${VARIANT_HEAD_BG[variant]} ${VARIANT_HEAD_BORDER[variant]}`}
138
+ style={{ width: '3.5rem', minWidth: '3.5rem' }} />
136
139
  )}
137
140
  </tr>
138
141
  </thead>
@@ -1,7 +1,7 @@
1
1
  import type { Density, TableVariant } from './types'
2
2
 
3
3
  type Column = { key: string; width?: string }
4
- import { DENSITY_TD, DENSITY_TH, VARIANT_TBODY } from './constants'
4
+ import { DENSITY_TD, VARIANT_TBODY, VARIANT_ROW_BORDER } from './constants'
5
5
 
6
6
  type SkeletonProps = {
7
7
  columns: Column[]
@@ -14,39 +14,36 @@ type SkeletonProps = {
14
14
 
15
15
  export default function Skeleton({ columns, rows, variant, density, hasMenu, hasSelect }: SkeletonProps) {
16
16
  return (
17
- <tbody className={`block divide-y divide-login-600/15 ${VARIANT_TBODY[variant]}`}>
17
+ <tbody className={VARIANT_TBODY[variant]}>
18
18
  {Array.from({ length: rows }).map((_, rowIdx) => (
19
- <tr key={rowIdx} className='flex w-full'>
19
+ <tr key={rowIdx}>
20
20
  {hasSelect && (
21
- <td className='flex items-center justify-center'
22
- style={{ width: '3rem', minWidth: '3rem', flexShrink: 0 }}>
23
- <span className='block h-4 w-4 rounded animate-shimmer' />
21
+ <td className={`align-middle ${VARIANT_ROW_BORDER[variant]}`}
22
+ style={{ width: '3rem', minWidth: '3rem' }}>
23
+ <span className='block h-4 w-4 mx-auto rounded animate-shimmer' />
24
24
  </td>
25
25
  )}
26
26
  {columns.map((col, colIdx) => (
27
27
  <td
28
28
  key={col.key}
29
- style={col.width ? { width: col.width, flexShrink: 0 } : undefined}
30
- className={`
31
- flex-1 min-w-0 flex items-center
32
- ${DENSITY_TD[density]}
33
- `}
29
+ style={col.width ? { width: col.width } : undefined}
30
+ className={`align-middle ${DENSITY_TD[density]} ${VARIANT_ROW_BORDER[variant]}`}
34
31
  >
35
32
  <span
36
33
  className='block h-4 rounded animate-shimmer'
37
34
  style={{
38
35
  width: colIdx === 0
39
- ? `${55 + ((rowIdx * 17 + colIdx * 31) % 30)}%`
40
- : `${40 + ((rowIdx * 13 + colIdx * 23) % 40)}%`,
36
+ ? `${8 + ((rowIdx * 17 + colIdx * 31) % 5)}rem`
37
+ : `${5 + ((rowIdx * 13 + colIdx * 23) % 5)}rem`,
41
38
  animationDelay: `${(rowIdx * columns.length + colIdx) * 40}ms`,
42
39
  }}
43
40
  />
44
41
  </td>
45
42
  ))}
46
43
  {hasMenu && (
47
- <td className='flex items-center justify-end pr-3'
48
- style={{ width: '3.5rem', minWidth: '3.5rem', flexShrink: 0 }}>
49
- <span className='block h-5 w-5 rounded animate-shimmer' />
44
+ <td className={`align-middle pr-3 ${VARIANT_ROW_BORDER[variant]}`}
45
+ style={{ width: '3.5rem', minWidth: '3.5rem' }}>
46
+ <span className='block h-5 w-5 ml-auto rounded animate-shimmer' />
50
47
  </td>
51
48
  )}
52
49
  </tr>
@@ -45,8 +45,8 @@ function TableShell<T extends Record<string, unknown>>({
45
45
 
46
46
  return (
47
47
  <div className={['flex flex-col min-h-0 w-full', VARIANT_CONTAINER[variant], className].filter(Boolean).join(' ')}>
48
- <div className='flex-1 min-h-0 overflow-x-auto'>
49
- <table className='min-w-full w-max flex flex-col flex-1 min-h-0 divide-y divide-login-600/20'>
48
+ <div className='flex-1 min-h-0 overflow-auto'>
49
+ <table className='min-w-full table-auto border-separate border-spacing-0'>
50
50
  <Header
51
51
  columns={columns}
52
52
  sort={sort}