@undevy-org/tablewright 0.9.0 → 0.10.0

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.
@@ -1,14 +1,17 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "../ui/dropdown-menu.js";
4
- import { ChevronDown, ListFilter, EyeOff, ArrowUp, ArrowDown, Check } from "lucide-react";
4
+ import { ChevronDown, ListFilter, EyeOff, Pin, Check, PinOff, ArrowUp, ArrowDown } from "lucide-react";
5
5
  const DEFAULT_LABELS = {
6
6
  sort: "Sort",
7
7
  sortByColumn: (label) => `Sort by ${label}`,
8
8
  ascending: "Ascending",
9
9
  descending: "Descending",
10
10
  filterByColumn: "Filter by this column",
11
- hideColumn: "Hide column"
11
+ hideColumn: "Hide column",
12
+ pinLeft: "Pin left",
13
+ pinRight: "Pin right",
14
+ unpin: "Unpin"
12
15
  };
13
16
  const sectionLabelClass = "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--text-tertiary)] first:pt-1";
14
17
  const itemClass = "gap-2.5 rounded-md px-2.5 py-2";
@@ -60,6 +63,10 @@ function ColumnHeaderMenu({
60
63
  hasFilter,
61
64
  onFilterClick,
62
65
  onHide,
66
+ pinSide = false,
67
+ onPinLeft,
68
+ onPinRight,
69
+ onUnpin,
63
70
  labels: labelOverrides,
64
71
  menuWidth = "w-[200px]"
65
72
  }) {
@@ -68,7 +75,15 @@ function ColumnHeaderMenu({
68
75
  const isStacked = !!primaryLabel;
69
76
  const isSingleSortable = !!label && !!onSort;
70
77
  const isSortable = isStacked || isSingleSortable;
71
- const hasActions = hasFilter || !!onHide;
78
+ const showFilterItem = Boolean(hasFilter && onFilterClick);
79
+ const showHideItem = Boolean(onHide);
80
+ const showPinLeftItem = Boolean(onPinLeft);
81
+ const showPinRightItem = Boolean(onPinRight);
82
+ const showUnpinItem = Boolean(onUnpin && pinSide);
83
+ const hasPinSection = showPinLeftItem || showPinRightItem || showUnpinItem;
84
+ const hasFilterOrHide = showFilterItem || showHideItem;
85
+ const showSeparatorAfterSort = isSortable && (hasFilterOrHide || hasPinSection);
86
+ const showSeparatorBeforePin = hasPinSection && hasFilterOrHide;
72
87
  const labelContent = isStacked ? /* @__PURE__ */ jsxs("span", { className: "flex flex-col leading-none", children: [
73
88
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-[var(--text-primary)]", children: [
74
89
  primaryLabel,
@@ -127,14 +142,29 @@ function ColumnHeaderMenu({
127
142
  labels
128
143
  }
129
144
  ),
130
- isSortable && hasActions && /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-[var(--border-subtle)]" }),
131
- hasFilter && onFilterClick && /* @__PURE__ */ jsxs(DropdownMenuItem, { className: itemClass, onClick: onFilterClick, children: [
145
+ showSeparatorAfterSort && /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-[var(--border-subtle)]" }),
146
+ showFilterItem && /* @__PURE__ */ jsxs(DropdownMenuItem, { className: itemClass, onClick: onFilterClick, children: [
132
147
  /* @__PURE__ */ jsx(ListFilter, { className: iconClass }),
133
148
  labels.filterByColumn
134
149
  ] }),
135
- onHide && /* @__PURE__ */ jsxs(DropdownMenuItem, { className: itemClass, onClick: onHide, children: [
150
+ showHideItem && /* @__PURE__ */ jsxs(DropdownMenuItem, { className: itemClass, onClick: onHide, children: [
136
151
  /* @__PURE__ */ jsx(EyeOff, { className: iconClass }),
137
152
  labels.hideColumn
153
+ ] }),
154
+ showSeparatorBeforePin && /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-[var(--border-subtle)]" }),
155
+ showPinLeftItem && /* @__PURE__ */ jsxs(DropdownMenuItem, { className: itemClass, onClick: onPinLeft, children: [
156
+ /* @__PURE__ */ jsx(Pin, { className: iconClass }),
157
+ labels.pinLeft,
158
+ pinSide === "left" && /* @__PURE__ */ jsx(Check, { className: "ml-auto h-3 w-3 text-[var(--text-tertiary)]" })
159
+ ] }),
160
+ showPinRightItem && /* @__PURE__ */ jsxs(DropdownMenuItem, { className: itemClass, onClick: onPinRight, children: [
161
+ /* @__PURE__ */ jsx(Pin, { className: `${iconClass} rotate-90` }),
162
+ labels.pinRight,
163
+ pinSide === "right" && /* @__PURE__ */ jsx(Check, { className: "ml-auto h-3 w-3 text-[var(--text-tertiary)]" })
164
+ ] }),
165
+ showUnpinItem && /* @__PURE__ */ jsxs(DropdownMenuItem, { className: itemClass, onClick: onUnpin, children: [
166
+ /* @__PURE__ */ jsx(PinOff, { className: iconClass }),
167
+ labels.unpin
138
168
  ] })
139
169
  ] })
140
170
  ] });
@@ -19,15 +19,25 @@ function applyColumnPinningToMeta(baseMeta, pinning, options) {
19
19
  for (const id of order) {
20
20
  const meta = result[id];
21
21
  if (meta?.sticky !== "left") continue;
22
- result[id] = { ...meta, stickyOffset: leftOffset };
23
- leftOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
22
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
23
+ if (pinnedLeft.has(id)) {
24
+ result[id] = { ...meta, stickyOffset: leftOffset };
25
+ leftOffset += width;
26
+ } else {
27
+ leftOffset = (meta.stickyOffset ?? 0) + width;
28
+ }
24
29
  }
25
30
  let rightOffset = 0;
26
31
  for (const id of [...order].reverse()) {
27
32
  const meta = result[id];
28
33
  if (meta?.sticky !== "right") continue;
29
- result[id] = { ...meta, stickyOffset: rightOffset };
30
- rightOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
34
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
35
+ if (pinnedRight.has(id)) {
36
+ result[id] = { ...meta, stickyOffset: rightOffset };
37
+ rightOffset += width;
38
+ } else {
39
+ rightOffset = (meta.stickyOffset ?? 0) + width;
40
+ }
31
41
  }
32
42
  return result;
33
43
  }
package/dist/styles.css CHANGED
@@ -402,6 +402,9 @@
402
402
  .collapse {
403
403
  visibility: collapse
404
404
  }
405
+ .static {
406
+ position: static
407
+ }
405
408
  .fixed {
406
409
  position: fixed
407
410
  }
@@ -784,6 +787,10 @@
784
787
  --tw-rotate: 180deg;
785
788
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
786
789
  }
790
+ .rotate-90 {
791
+ --tw-rotate: 90deg;
792
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
793
+ }
787
794
  .cursor-col-resize {
788
795
  cursor: col-resize
789
796
  }
@@ -952,15 +952,25 @@ function applyColumnPinningToMeta(baseMeta, pinning, options) {
952
952
  for (const id of order) {
953
953
  const meta = result[id];
954
954
  if (meta?.sticky !== "left") continue;
955
- result[id] = { ...meta, stickyOffset: leftOffset };
956
- leftOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
955
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
956
+ if (pinnedLeft.has(id)) {
957
+ result[id] = { ...meta, stickyOffset: leftOffset };
958
+ leftOffset += width;
959
+ } else {
960
+ leftOffset = (meta.stickyOffset ?? 0) + width;
961
+ }
957
962
  }
958
963
  let rightOffset = 0;
959
964
  for (const id of [...order].reverse()) {
960
965
  const meta = result[id];
961
966
  if (meta?.sticky !== "right") continue;
962
- result[id] = { ...meta, stickyOffset: rightOffset };
963
- rightOffset += columnWidthPx(id, baseMeta, options?.columnWidths);
967
+ const width = columnWidthPx(id, baseMeta, options?.columnWidths);
968
+ if (pinnedRight.has(id)) {
969
+ result[id] = { ...meta, stickyOffset: rightOffset };
970
+ rightOffset += width;
971
+ } else {
972
+ rightOffset = (meta.stickyOffset ?? 0) + width;
973
+ }
964
974
  }
965
975
  return result;
966
976
  }
@@ -2672,7 +2682,10 @@ const DEFAULT_LABELS$4 = {
2672
2682
  ascending: "Ascending",
2673
2683
  descending: "Descending",
2674
2684
  filterByColumn: "Filter by this column",
2675
- hideColumn: "Hide column"
2685
+ hideColumn: "Hide column",
2686
+ pinLeft: "Pin left",
2687
+ pinRight: "Pin right",
2688
+ unpin: "Unpin"
2676
2689
  };
2677
2690
  const sectionLabelClass = "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.12em] text-[var(--text-tertiary)] first:pt-1";
2678
2691
  const itemClass = "gap-2.5 rounded-md px-2.5 py-2";
@@ -2724,6 +2737,10 @@ function ColumnHeaderMenu({
2724
2737
  hasFilter,
2725
2738
  onFilterClick,
2726
2739
  onHide,
2740
+ pinSide = false,
2741
+ onPinLeft,
2742
+ onPinRight,
2743
+ onUnpin,
2727
2744
  labels: labelOverrides,
2728
2745
  menuWidth = "w-[200px]"
2729
2746
  }) {
@@ -2732,7 +2749,15 @@ function ColumnHeaderMenu({
2732
2749
  const isStacked = !!primaryLabel;
2733
2750
  const isSingleSortable = !!label && !!onSort;
2734
2751
  const isSortable = isStacked || isSingleSortable;
2735
- const hasActions = hasFilter || !!onHide;
2752
+ const showFilterItem = Boolean(hasFilter && onFilterClick);
2753
+ const showHideItem = Boolean(onHide);
2754
+ const showPinLeftItem = Boolean(onPinLeft);
2755
+ const showPinRightItem = Boolean(onPinRight);
2756
+ const showUnpinItem = Boolean(onUnpin && pinSide);
2757
+ const hasPinSection = showPinLeftItem || showPinRightItem || showUnpinItem;
2758
+ const hasFilterOrHide = showFilterItem || showHideItem;
2759
+ const showSeparatorAfterSort = isSortable && (hasFilterOrHide || hasPinSection);
2760
+ const showSeparatorBeforePin = hasPinSection && hasFilterOrHide;
2736
2761
  const labelContent = isStacked ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col leading-none", children: [
2737
2762
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-[var(--text-primary)]", children: [
2738
2763
  primaryLabel,
@@ -2791,14 +2816,29 @@ function ColumnHeaderMenu({
2791
2816
  labels
2792
2817
  }
2793
2818
  ),
2794
- isSortable && hasActions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 border-t border-[var(--border-subtle)]" }),
2795
- hasFilter && onFilterClick && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { className: itemClass, onClick: onFilterClick, children: [
2819
+ showSeparatorAfterSort && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 border-t border-[var(--border-subtle)]" }),
2820
+ showFilterItem && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { className: itemClass, onClick: onFilterClick, children: [
2796
2821
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListFilter, { className: iconClass }),
2797
2822
  labels.filterByColumn
2798
2823
  ] }),
2799
- onHide && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { className: itemClass, onClick: onHide, children: [
2824
+ showHideItem && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { className: itemClass, onClick: onHide, children: [
2800
2825
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: iconClass }),
2801
2826
  labels.hideColumn
2827
+ ] }),
2828
+ showSeparatorBeforePin && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-1 border-t border-[var(--border-subtle)]" }),
2829
+ showPinLeftItem && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { className: itemClass, onClick: onPinLeft, children: [
2830
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pin, { className: iconClass }),
2831
+ labels.pinLeft,
2832
+ pinSide === "left" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "ml-auto h-3 w-3 text-[var(--text-tertiary)]" })
2833
+ ] }),
2834
+ showPinRightItem && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { className: itemClass, onClick: onPinRight, children: [
2835
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pin, { className: `${iconClass} rotate-90` }),
2836
+ labels.pinRight,
2837
+ pinSide === "right" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "ml-auto h-3 w-3 text-[var(--text-tertiary)]" })
2838
+ ] }),
2839
+ showUnpinItem && /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuItem, { className: itemClass, onClick: onUnpin, children: [
2840
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PinOff, { className: iconClass }),
2841
+ labels.unpin
2802
2842
  ] })
2803
2843
  ] })
2804
2844
  ] });
@@ -250,6 +250,9 @@
250
250
  .collapse {
251
251
  visibility: collapse
252
252
  }
253
+ .static {
254
+ position: static
255
+ }
253
256
  .fixed {
254
257
  position: fixed
255
258
  }
@@ -632,6 +635,10 @@
632
635
  --tw-rotate: 180deg;
633
636
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
634
637
  }
638
+ .rotate-90 {
639
+ --tw-rotate: 90deg;
640
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))
641
+ }
635
642
  .cursor-col-resize {
636
643
  cursor: col-resize
637
644
  }
@@ -6,6 +6,9 @@ export interface ColumnHeaderMenuLabels {
6
6
  descending: string;
7
7
  filterByColumn: string;
8
8
  hideColumn: string;
9
+ pinLeft: string;
10
+ pinRight: string;
11
+ unpin: string;
9
12
  }
10
13
  interface ColumnHeaderMenuProps {
11
14
  label?: string;
@@ -20,8 +23,12 @@ interface ColumnHeaderMenuProps {
20
23
  hasFilter?: boolean;
21
24
  onFilterClick?: () => void;
22
25
  onHide?: () => void;
26
+ pinSide?: "left" | "right" | false;
27
+ onPinLeft?: () => void;
28
+ onPinRight?: () => void;
29
+ onUnpin?: () => void;
23
30
  labels?: Partial<ColumnHeaderMenuLabels>;
24
31
  menuWidth?: string;
25
32
  }
26
- export declare function ColumnHeaderMenu({ label, primaryLabel, secondaryLabel, sorted, onSort, primarySorted, onPrimarySort, secondarySorted, onSecondarySort, hasFilter, onFilterClick, onHide, labels: labelOverrides, menuWidth, }: ColumnHeaderMenuProps): import("react").JSX.Element;
33
+ export declare function ColumnHeaderMenu({ label, primaryLabel, secondaryLabel, sorted, onSort, primarySorted, onPrimarySort, secondarySorted, onSecondarySort, hasFilter, onFilterClick, onHide, pinSide, onPinLeft, onPinRight, onUnpin, labels: labelOverrides, menuWidth, }: ColumnHeaderMenuProps): import("react").JSX.Element;
27
34
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undevy-org/tablewright",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "React data-table and application-shell components: filters, drawers, row controls, sortable and resizable columns, built on Radix UI and TanStack Table.",
5
5
  "license": "MIT",
6
6
  "repository": {