@xcelsior/ui-spreadsheets 1.1.12 → 1.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +190 -187
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +180 -177
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Spreadsheet.stories.tsx +285 -25
- package/src/components/Spreadsheet.tsx +21 -19
- package/src/components/SpreadsheetCell.tsx +5 -3
- package/src/components/SpreadsheetHeader.tsx +5 -3
- package/src/hooks/useSpreadsheetPinning.ts +8 -3
package/package.json
CHANGED
|
@@ -1614,10 +1614,123 @@ interface StockData {
|
|
|
1614
1614
|
profit: number;
|
|
1615
1615
|
}
|
|
1616
1616
|
|
|
1617
|
-
const tickers = [
|
|
1618
|
-
|
|
1617
|
+
const tickers = [
|
|
1618
|
+
'AAPL',
|
|
1619
|
+
'MSFT',
|
|
1620
|
+
'GOOGL',
|
|
1621
|
+
'AMZN',
|
|
1622
|
+
'META',
|
|
1623
|
+
'TSLA',
|
|
1624
|
+
'NVDA',
|
|
1625
|
+
'JPM',
|
|
1626
|
+
'V',
|
|
1627
|
+
'JNJ',
|
|
1628
|
+
'WMT',
|
|
1629
|
+
'PG',
|
|
1630
|
+
'MA',
|
|
1631
|
+
'UNH',
|
|
1632
|
+
'HD',
|
|
1633
|
+
'DIS',
|
|
1634
|
+
'BAC',
|
|
1635
|
+
'XOM',
|
|
1636
|
+
'PFE',
|
|
1637
|
+
'KO',
|
|
1638
|
+
'PEP',
|
|
1639
|
+
'CSCO',
|
|
1640
|
+
'ADBE',
|
|
1641
|
+
'NFLX',
|
|
1642
|
+
'INTC',
|
|
1643
|
+
'CRM',
|
|
1644
|
+
'ABT',
|
|
1645
|
+
'NKE',
|
|
1646
|
+
'MRK',
|
|
1647
|
+
'TMO',
|
|
1648
|
+
'ORCL',
|
|
1649
|
+
'ACN',
|
|
1650
|
+
'MDT',
|
|
1651
|
+
'COST',
|
|
1652
|
+
'LLY',
|
|
1653
|
+
'AVGO',
|
|
1654
|
+
'TXN',
|
|
1655
|
+
'NEE',
|
|
1656
|
+
'UNP',
|
|
1657
|
+
'DHR',
|
|
1658
|
+
'QCOM',
|
|
1659
|
+
'PM',
|
|
1660
|
+
'BMY',
|
|
1661
|
+
'RTX',
|
|
1662
|
+
'HON',
|
|
1663
|
+
'UPS',
|
|
1664
|
+
'LOW',
|
|
1665
|
+
'AMGN',
|
|
1666
|
+
'SBUX',
|
|
1667
|
+
'IBM',
|
|
1668
|
+
];
|
|
1669
|
+
const companies = [
|
|
1670
|
+
'Apple Inc.',
|
|
1671
|
+
'Microsoft Corp.',
|
|
1672
|
+
'Alphabet Inc.',
|
|
1673
|
+
'Amazon.com Inc.',
|
|
1674
|
+
'Meta Platforms',
|
|
1675
|
+
'Tesla Inc.',
|
|
1676
|
+
'NVIDIA Corp.',
|
|
1677
|
+
'JPMorgan Chase',
|
|
1678
|
+
'Visa Inc.',
|
|
1679
|
+
'Johnson & Johnson',
|
|
1680
|
+
'Walmart Inc.',
|
|
1681
|
+
'Procter & Gamble',
|
|
1682
|
+
'Mastercard',
|
|
1683
|
+
'UnitedHealth',
|
|
1684
|
+
'Home Depot',
|
|
1685
|
+
'Walt Disney',
|
|
1686
|
+
'Bank of America',
|
|
1687
|
+
'Exxon Mobil',
|
|
1688
|
+
'Pfizer Inc.',
|
|
1689
|
+
'Coca-Cola',
|
|
1690
|
+
'PepsiCo Inc.',
|
|
1691
|
+
'Cisco Systems',
|
|
1692
|
+
'Adobe Inc.',
|
|
1693
|
+
'Netflix Inc.',
|
|
1694
|
+
'Intel Corp.',
|
|
1695
|
+
'Salesforce',
|
|
1696
|
+
'Abbott Labs',
|
|
1697
|
+
'Nike Inc.',
|
|
1698
|
+
'Merck & Co.',
|
|
1699
|
+
'Thermo Fisher',
|
|
1700
|
+
'Oracle Corp.',
|
|
1701
|
+
'Accenture',
|
|
1702
|
+
'Medtronic',
|
|
1703
|
+
'Costco',
|
|
1704
|
+
'Eli Lilly',
|
|
1705
|
+
'Broadcom',
|
|
1706
|
+
'Texas Instruments and technology and HSBC',
|
|
1707
|
+
'NextEra Energy',
|
|
1708
|
+
'Union Pacific',
|
|
1709
|
+
'Danaher',
|
|
1710
|
+
'Qualcomm',
|
|
1711
|
+
'Philip Morris',
|
|
1712
|
+
'Bristol-Myers',
|
|
1713
|
+
'Raytheon',
|
|
1714
|
+
'Honeywell',
|
|
1715
|
+
'UPS',
|
|
1716
|
+
'Lowes',
|
|
1717
|
+
'Amgen',
|
|
1718
|
+
'Starbucks',
|
|
1719
|
+
'IBM',
|
|
1720
|
+
];
|
|
1619
1721
|
const sectors = ['Technology', 'Healthcare', 'Financials', 'Consumer', 'Energy', 'Industrials'];
|
|
1620
|
-
const industries = [
|
|
1722
|
+
const industries = [
|
|
1723
|
+
'Software',
|
|
1724
|
+
'Hardware',
|
|
1725
|
+
'Biotech',
|
|
1726
|
+
'Banking',
|
|
1727
|
+
'Retail',
|
|
1728
|
+
'Semiconductors',
|
|
1729
|
+
'Pharma',
|
|
1730
|
+
'Media',
|
|
1731
|
+
'Oil & Gas',
|
|
1732
|
+
'Aerospace',
|
|
1733
|
+
];
|
|
1621
1734
|
|
|
1622
1735
|
const sampleStocks: StockData[] = Array.from({ length: 50 }, (_, i) => ({
|
|
1623
1736
|
id: i + 1,
|
|
@@ -1644,31 +1757,171 @@ const sampleStocks: StockData[] = Array.from({ length: 50 }, (_, i) => ({
|
|
|
1644
1757
|
const stockColumns: SpreadsheetColumn<StockData>[] = [
|
|
1645
1758
|
{ id: 'ticker', label: 'Ticker', width: 80, sortable: true, filterable: true },
|
|
1646
1759
|
{ id: 'company', label: 'Company', width: 180, sortable: true, filterable: true },
|
|
1647
|
-
{
|
|
1760
|
+
{
|
|
1761
|
+
id: 'sector',
|
|
1762
|
+
label: 'Sector',
|
|
1763
|
+
width: 120,
|
|
1764
|
+
sortable: true,
|
|
1765
|
+
filterable: true,
|
|
1766
|
+
type: 'select',
|
|
1767
|
+
options: sectors,
|
|
1768
|
+
},
|
|
1648
1769
|
{ id: 'industry', label: 'Industry', width: 140, sortable: true, filterable: true },
|
|
1649
|
-
{
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1770
|
+
{
|
|
1771
|
+
id: 'marketCap',
|
|
1772
|
+
label: 'Market Cap',
|
|
1773
|
+
width: 130,
|
|
1774
|
+
sortable: true,
|
|
1775
|
+
type: 'number',
|
|
1776
|
+
align: 'right',
|
|
1777
|
+
render: (v) => `$${(v / 1e9).toFixed(1)}B`,
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
id: 'price',
|
|
1781
|
+
label: 'Price',
|
|
1782
|
+
width: 90,
|
|
1783
|
+
sortable: true,
|
|
1784
|
+
type: 'number',
|
|
1785
|
+
align: 'right',
|
|
1786
|
+
render: (v) => `$${v.toFixed(2)}`,
|
|
1787
|
+
},
|
|
1788
|
+
{
|
|
1789
|
+
id: 'change',
|
|
1790
|
+
label: 'Change',
|
|
1791
|
+
width: 90,
|
|
1792
|
+
sortable: true,
|
|
1793
|
+
type: 'number',
|
|
1794
|
+
align: 'right',
|
|
1795
|
+
render: (v) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}`,
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
id: 'changePercent',
|
|
1799
|
+
label: 'Change %',
|
|
1800
|
+
width: 100,
|
|
1801
|
+
sortable: true,
|
|
1802
|
+
type: 'number',
|
|
1803
|
+
align: 'right',
|
|
1804
|
+
render: (v) => `${v >= 0 ? '+' : ''}${v.toFixed(2)}%`,
|
|
1805
|
+
},
|
|
1806
|
+
{
|
|
1807
|
+
id: 'volume',
|
|
1808
|
+
label: 'Volume',
|
|
1809
|
+
width: 110,
|
|
1810
|
+
sortable: true,
|
|
1811
|
+
type: 'number',
|
|
1812
|
+
align: 'right',
|
|
1813
|
+
render: (v) => `${(v / 1e6).toFixed(1)}M`,
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
id: 'avgVolume',
|
|
1817
|
+
label: 'Avg Volume',
|
|
1818
|
+
width: 110,
|
|
1819
|
+
sortable: true,
|
|
1820
|
+
type: 'number',
|
|
1821
|
+
align: 'right',
|
|
1822
|
+
render: (v) => `${(v / 1e6).toFixed(1)}M`,
|
|
1823
|
+
},
|
|
1655
1824
|
{ id: 'pe', label: 'P/E', width: 80, sortable: true, type: 'number', align: 'right' },
|
|
1656
|
-
{
|
|
1657
|
-
|
|
1825
|
+
{
|
|
1826
|
+
id: 'eps',
|
|
1827
|
+
label: 'EPS',
|
|
1828
|
+
width: 80,
|
|
1829
|
+
sortable: true,
|
|
1830
|
+
type: 'number',
|
|
1831
|
+
align: 'right',
|
|
1832
|
+
render: (v) => `$${v.toFixed(2)}`,
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
id: 'dividend',
|
|
1836
|
+
label: 'Dividend %',
|
|
1837
|
+
width: 100,
|
|
1838
|
+
sortable: true,
|
|
1839
|
+
type: 'number',
|
|
1840
|
+
align: 'right',
|
|
1841
|
+
render: (v) => `${v.toFixed(2)}%`,
|
|
1842
|
+
},
|
|
1658
1843
|
{ id: 'beta', label: 'Beta', width: 70, sortable: true, type: 'number', align: 'right' },
|
|
1659
|
-
{
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1844
|
+
{
|
|
1845
|
+
id: 'week52High',
|
|
1846
|
+
label: '52W High',
|
|
1847
|
+
width: 100,
|
|
1848
|
+
sortable: true,
|
|
1849
|
+
type: 'number',
|
|
1850
|
+
align: 'right',
|
|
1851
|
+
render: (v) => `$${v.toFixed(2)}`,
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
id: 'week52Low',
|
|
1855
|
+
label: '52W Low',
|
|
1856
|
+
width: 100,
|
|
1857
|
+
sortable: true,
|
|
1858
|
+
type: 'number',
|
|
1859
|
+
align: 'right',
|
|
1860
|
+
render: (v) => `$${v.toFixed(2)}`,
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
id: 'revenue',
|
|
1864
|
+
label: 'Revenue',
|
|
1865
|
+
width: 120,
|
|
1866
|
+
sortable: true,
|
|
1867
|
+
type: 'number',
|
|
1868
|
+
align: 'right',
|
|
1869
|
+
render: (v) => `$${(v / 1e9).toFixed(1)}B`,
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
id: 'profit',
|
|
1873
|
+
label: 'Net Income',
|
|
1874
|
+
width: 120,
|
|
1875
|
+
sortable: true,
|
|
1876
|
+
type: 'number',
|
|
1877
|
+
align: 'right',
|
|
1878
|
+
render: (v) => `$${(v / 1e9).toFixed(1)}B`,
|
|
1879
|
+
},
|
|
1663
1880
|
];
|
|
1664
1881
|
|
|
1665
1882
|
const stockColumnGroups: SpreadsheetColumnGroup[] = [
|
|
1666
|
-
{
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1883
|
+
{
|
|
1884
|
+
id: 'identity',
|
|
1885
|
+
label: 'Identity',
|
|
1886
|
+
columns: ['ticker', 'company'],
|
|
1887
|
+
collapsible: true,
|
|
1888
|
+
headerColor: '#dbeafe',
|
|
1889
|
+
},
|
|
1890
|
+
{
|
|
1891
|
+
id: 'classification',
|
|
1892
|
+
label: 'Classification',
|
|
1893
|
+
columns: ['sector', 'industry'],
|
|
1894
|
+
collapsible: true,
|
|
1895
|
+
headerColor: '#fef3c7',
|
|
1896
|
+
},
|
|
1897
|
+
{
|
|
1898
|
+
id: 'market',
|
|
1899
|
+
label: 'Market Data',
|
|
1900
|
+
columns: ['marketCap', 'price', 'change', 'changePercent', 'volume', 'avgVolume'],
|
|
1901
|
+
collapsible: true,
|
|
1902
|
+
headerColor: '#d1fae5',
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
id: 'fundamentals',
|
|
1906
|
+
label: 'Fundamentals',
|
|
1907
|
+
columns: ['pe', 'eps', 'dividend', 'beta'],
|
|
1908
|
+
collapsible: true,
|
|
1909
|
+
headerColor: '#fce7f3',
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
id: 'range',
|
|
1913
|
+
label: '52-Week Range',
|
|
1914
|
+
columns: ['week52High', 'week52Low'],
|
|
1915
|
+
collapsible: true,
|
|
1916
|
+
headerColor: '#e0e7ff',
|
|
1917
|
+
},
|
|
1918
|
+
{
|
|
1919
|
+
id: 'financials',
|
|
1920
|
+
label: 'Financials',
|
|
1921
|
+
columns: ['revenue', 'profit'],
|
|
1922
|
+
collapsible: true,
|
|
1923
|
+
headerColor: '#fef9c3',
|
|
1924
|
+
},
|
|
1672
1925
|
];
|
|
1673
1926
|
|
|
1674
1927
|
export const PinnedColumnsWithGroups: Story = {
|
|
@@ -1686,10 +1939,17 @@ export const PinnedColumnsWithGroups: Story = {
|
|
|
1686
1939
|
columns.
|
|
1687
1940
|
</p>
|
|
1688
1941
|
<ul className="text-sm text-blue-700 space-y-1 ml-4 list-disc">
|
|
1689
|
-
<li
|
|
1690
|
-
|
|
1942
|
+
<li>
|
|
1943
|
+
<strong>Ticker & Company</strong> are pinned to the left
|
|
1944
|
+
</li>
|
|
1945
|
+
<li>
|
|
1946
|
+
<strong>19 columns</strong> across 6 collapsible groups
|
|
1947
|
+
</li>
|
|
1691
1948
|
<li>Click the pin icon on any column header to pin/unpin</li>
|
|
1692
|
-
<li>
|
|
1949
|
+
<li>
|
|
1950
|
+
Collapse groups to verify pinned columns from collapsed groups still
|
|
1951
|
+
work
|
|
1952
|
+
</li>
|
|
1693
1953
|
</ul>
|
|
1694
1954
|
</div>
|
|
1695
1955
|
|
|
@@ -21,12 +21,18 @@ import {
|
|
|
21
21
|
useSpreadsheetPinning,
|
|
22
22
|
ROW_INDEX_COLUMN_WIDTH,
|
|
23
23
|
ROW_INDEX_COLUMN_ID,
|
|
24
|
+
MIN_PINNED_COLUMN_WIDTH,
|
|
24
25
|
} from '../hooks/useSpreadsheetPinning';
|
|
25
26
|
import { useSpreadsheetComments } from '../hooks/useSpreadsheetComments';
|
|
26
27
|
import { useSpreadsheetUndoRedo } from '../hooks/useSpreadsheetUndoRedo';
|
|
27
28
|
import { useSpreadsheetKeyboardShortcuts } from '../hooks/useSpreadsheetKeyboardShortcuts';
|
|
28
29
|
import { useSpreadsheetSelection } from '../hooks/useSpreadsheetSelection';
|
|
29
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
CellEdit,
|
|
32
|
+
SpreadsheetColumn,
|
|
33
|
+
SpreadsheetColumnGroup,
|
|
34
|
+
SpreadsheetProps,
|
|
35
|
+
} from '../types';
|
|
30
36
|
|
|
31
37
|
type SingleCellEdit = {
|
|
32
38
|
rowId: string | number;
|
|
@@ -645,7 +651,11 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
645
651
|
}
|
|
646
652
|
|
|
647
653
|
type ColumnItem = { type: 'column'; column: SpreadsheetColumn<T> };
|
|
648
|
-
type PlaceholderItem = {
|
|
654
|
+
type PlaceholderItem = {
|
|
655
|
+
type: 'collapsed-placeholder';
|
|
656
|
+
groupId: string;
|
|
657
|
+
headerColor?: string;
|
|
658
|
+
};
|
|
649
659
|
type RenderItem = ColumnItem | PlaceholderItem;
|
|
650
660
|
|
|
651
661
|
const leftPinnedItems: ColumnItem[] = [];
|
|
@@ -683,9 +693,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
683
693
|
}
|
|
684
694
|
|
|
685
695
|
// Add any columns not in any group
|
|
686
|
-
const allGroupedIds = new Set(
|
|
687
|
-
columnGroups.flatMap((g) => g.columns)
|
|
688
|
-
);
|
|
696
|
+
const allGroupedIds = new Set(columnGroups.flatMap((g) => g.columns));
|
|
689
697
|
for (const col of visibleColumns) {
|
|
690
698
|
if (!allGroupedIds.has(col.id)) {
|
|
691
699
|
const pinSide = pinnedColumns.get(col.id);
|
|
@@ -842,7 +850,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
842
850
|
zoom: zoom / 100,
|
|
843
851
|
}}
|
|
844
852
|
>
|
|
845
|
-
<table className="
|
|
853
|
+
<table className="border-separate border-spacing-0 text-xs select-none">
|
|
846
854
|
<thead>
|
|
847
855
|
{/* Column Group Headers */}
|
|
848
856
|
{columnGroups && groupHeaderItems && (
|
|
@@ -859,10 +867,9 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
859
867
|
/>
|
|
860
868
|
{groupHeaderItems.map((item) => {
|
|
861
869
|
if (item.type === 'pinned-column') {
|
|
862
|
-
const col = columns.find(
|
|
863
|
-
(c) => c.id === item.columnId
|
|
864
|
-
);
|
|
870
|
+
const col = columns.find((c) => c.id === item.columnId);
|
|
865
871
|
const isPinnedLeft = item.pinSide === 'left';
|
|
872
|
+
const pinnedWidth = Math.max(col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH);
|
|
866
873
|
return (
|
|
867
874
|
<th
|
|
868
875
|
key={`pinned-group-${item.columnId}`}
|
|
@@ -872,8 +879,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
872
879
|
)}
|
|
873
880
|
style={{
|
|
874
881
|
backgroundColor:
|
|
875
|
-
item.headerColor ||
|
|
876
|
-
'rgb(243 244 246)',
|
|
882
|
+
item.headerColor || 'rgb(243 244 246)',
|
|
877
883
|
position: 'sticky',
|
|
878
884
|
left: isPinnedLeft
|
|
879
885
|
? `${getColumnLeftOffset(item.columnId)}px`
|
|
@@ -881,12 +887,9 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
881
887
|
right: !isPinnedLeft
|
|
882
888
|
? `${getColumnRightOffset(item.columnId)}px`
|
|
883
889
|
: undefined,
|
|
884
|
-
minWidth:
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
col?.minWidth || col?.width,
|
|
888
|
-
maxWidth:
|
|
889
|
-
col?.minWidth || col?.width,
|
|
890
|
+
minWidth: pinnedWidth,
|
|
891
|
+
width: pinnedWidth,
|
|
892
|
+
maxWidth: pinnedWidth,
|
|
890
893
|
}}
|
|
891
894
|
/>
|
|
892
895
|
);
|
|
@@ -947,8 +950,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
947
950
|
className="border border-gray-200 px-2 py-1 text-center text-gray-400"
|
|
948
951
|
style={{
|
|
949
952
|
backgroundColor:
|
|
950
|
-
item.headerColor ||
|
|
951
|
-
'rgb(243 244 246)',
|
|
953
|
+
item.headerColor || 'rgb(243 244 246)',
|
|
952
954
|
minWidth: '30px',
|
|
953
955
|
}}
|
|
954
956
|
>
|
|
@@ -4,6 +4,7 @@ import { AiFillHighlight } from 'react-icons/ai';
|
|
|
4
4
|
import { FaComment, FaRegComment } from 'react-icons/fa';
|
|
5
5
|
import { cn } from '../utils';
|
|
6
6
|
import type { SpreadsheetCellProps } from '../types';
|
|
7
|
+
import { MIN_PINNED_COLUMN_WIDTH } from '../hooks/useSpreadsheetPinning';
|
|
7
8
|
|
|
8
9
|
const cellPaddingCompact = 'px-1 py-px';
|
|
9
10
|
const cellPaddingNormal = 'px-2 py-1';
|
|
@@ -273,10 +274,11 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
273
274
|
style={{
|
|
274
275
|
backgroundColor: isInSelection ? 'rgb(239 246 255)' : getBackgroundColor(),
|
|
275
276
|
minWidth: column.minWidth || column.width,
|
|
276
|
-
// Pinned columns
|
|
277
|
+
// Pinned columns must have a fixed width so sticky offsets stay correct.
|
|
278
|
+
// Enforce MIN_PINNED_COLUMN_WIDTH so header actions always fit.
|
|
277
279
|
...(isPinned && {
|
|
278
|
-
width: column.minWidth || column.width,
|
|
279
|
-
maxWidth: column.minWidth || column.width,
|
|
280
|
+
width: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
|
|
281
|
+
maxWidth: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
|
|
280
282
|
}),
|
|
281
283
|
...positionStyles,
|
|
282
284
|
...selectionBorderStyles,
|
|
@@ -3,6 +3,7 @@ import { HiChevronDown, HiChevronUp } from 'react-icons/hi';
|
|
|
3
3
|
import { cn } from '../utils';
|
|
4
4
|
import type { SpreadsheetHeaderProps } from '../types';
|
|
5
5
|
import { ColumnHeaderActions } from './ColumnHeaderActions';
|
|
6
|
+
import { MIN_PINNED_COLUMN_WIDTH } from '../hooks/useSpreadsheetPinning';
|
|
6
7
|
|
|
7
8
|
const cellPaddingCompact = 'px-1 py-0.5';
|
|
8
9
|
const cellPaddingNormal = 'px-2 py-1.5';
|
|
@@ -71,10 +72,11 @@ export const SpreadsheetHeader: React.FC<
|
|
|
71
72
|
style={{
|
|
72
73
|
backgroundColor: highlightColor || 'rgb(243 244 246)', // gray-100
|
|
73
74
|
minWidth: column.minWidth || column.width,
|
|
74
|
-
// Pinned columns
|
|
75
|
+
// Pinned columns must have a fixed width so sticky offsets stay correct.
|
|
76
|
+
// Enforce MIN_PINNED_COLUMN_WIDTH so header actions (pin/filter/highlight) always fit.
|
|
75
77
|
...(isPinned && {
|
|
76
|
-
width: column.minWidth || column.width,
|
|
77
|
-
maxWidth: column.minWidth || column.width,
|
|
78
|
+
width: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
|
|
79
|
+
maxWidth: Math.max(column.minWidth || column.width || MIN_PINNED_COLUMN_WIDTH, MIN_PINNED_COLUMN_WIDTH),
|
|
78
80
|
}),
|
|
79
81
|
top: 0, // For sticky header
|
|
80
82
|
...positionStyles,
|
|
@@ -4,6 +4,8 @@ import type { SpreadsheetColumn, SpreadsheetColumnGroup } from '../types';
|
|
|
4
4
|
// Special column ID for row index
|
|
5
5
|
export const ROW_INDEX_COLUMN_ID = '__row_index__';
|
|
6
6
|
export const ROW_INDEX_COLUMN_WIDTH = 80;
|
|
7
|
+
// Minimum width for any pinned column to ensure header actions (pin, filter, highlight icons) fit
|
|
8
|
+
export const MIN_PINNED_COLUMN_WIDTH = 100;
|
|
7
9
|
|
|
8
10
|
export interface UseSpreadsheetPinningOptions<T> {
|
|
9
11
|
columns: SpreadsheetColumn<T>[];
|
|
@@ -178,8 +180,10 @@ export function useSpreadsheetPinning<T>({
|
|
|
178
180
|
let offset = baseOffset;
|
|
179
181
|
for (let i = 0; i < index; i++) {
|
|
180
182
|
const col = columns.find((c) => c.id === pinnedLeft[i]);
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
+
// Pinned columns are clamped to at least MIN_PINNED_COLUMN_WIDTH
|
|
184
|
+
// so that header actions (pin, filter, highlight icons) always fit
|
|
185
|
+
const configuredWidth = col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH;
|
|
186
|
+
offset += Math.max(configuredWidth, MIN_PINNED_COLUMN_WIDTH);
|
|
183
187
|
}
|
|
184
188
|
return offset;
|
|
185
189
|
},
|
|
@@ -218,7 +222,8 @@ export function useSpreadsheetPinning<T>({
|
|
|
218
222
|
let offset = 0;
|
|
219
223
|
for (let i = pinnedRight.length - 1; i > index; i--) {
|
|
220
224
|
const col = columns.find((c) => c.id === pinnedRight[i]);
|
|
221
|
-
|
|
225
|
+
const configuredWidth = col?.minWidth || col?.width || MIN_PINNED_COLUMN_WIDTH;
|
|
226
|
+
offset += Math.max(configuredWidth, MIN_PINNED_COLUMN_WIDTH);
|
|
222
227
|
}
|
|
223
228
|
return offset;
|
|
224
229
|
},
|