mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-23 18:18:18 +00:00
Merge pull request #379 from Dispatcharr:table-column-resizing
Implement column resizing and improve table layouts
This commit is contained in:
commit
33700ccc06
14 changed files with 248 additions and 43 deletions
|
|
@ -306,6 +306,12 @@ const ChannelsTable = ({}) => {
|
|||
const [isBulkDelete, setIsBulkDelete] = useState(false);
|
||||
const [channelToDelete, setChannelToDelete] = useState(null);
|
||||
|
||||
// Column sizing state for resizable columns
|
||||
const [columnSizing, setColumnSizing] = useLocalStorage(
|
||||
'channels-table-column-sizing',
|
||||
{}
|
||||
);
|
||||
|
||||
// M3U and EPG URL configuration state
|
||||
const [m3uParams, setM3uParams] = useState({
|
||||
cachedlogos: true,
|
||||
|
|
@ -697,14 +703,17 @@ const ChannelsTable = ({}) => {
|
|||
{
|
||||
id: 'expand',
|
||||
size: 20,
|
||||
enableResizing: false,
|
||||
},
|
||||
{
|
||||
id: 'select',
|
||||
size: 30,
|
||||
enableResizing: false,
|
||||
},
|
||||
{
|
||||
id: 'enabled',
|
||||
size: 45,
|
||||
enableResizing: false,
|
||||
cell: ({ row, table }) => {
|
||||
return (
|
||||
<ChannelEnabledSwitch
|
||||
|
|
@ -718,7 +727,9 @@ const ChannelsTable = ({}) => {
|
|||
{
|
||||
id: 'channel_number',
|
||||
accessorKey: 'channel_number',
|
||||
size: 40,
|
||||
size: columnSizing.channel_number || 40,
|
||||
minSize: 30,
|
||||
maxSize: 100,
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue();
|
||||
// Format as integer if no decimal component
|
||||
|
|
@ -739,6 +750,8 @@ const ChannelsTable = ({}) => {
|
|||
{
|
||||
id: 'name',
|
||||
accessorKey: 'name',
|
||||
size: columnSizing.name || 200,
|
||||
minSize: 100,
|
||||
cell: ({ getValue }) => (
|
||||
<Box
|
||||
style={{
|
||||
|
|
@ -793,7 +806,9 @@ const ChannelsTable = ({}) => {
|
|||
</Box>
|
||||
);
|
||||
},
|
||||
size: 120,
|
||||
size: columnSizing.epg || 120,
|
||||
minSize: 80,
|
||||
maxSize: 200,
|
||||
},
|
||||
{
|
||||
id: 'channel_group',
|
||||
|
|
@ -812,7 +827,9 @@ const ChannelsTable = ({}) => {
|
|||
{getValue()}
|
||||
</Box>
|
||||
),
|
||||
size: 175,
|
||||
size: columnSizing.channel_group || 175,
|
||||
minSize: 100,
|
||||
maxSize: 300,
|
||||
},
|
||||
{
|
||||
id: 'logo',
|
||||
|
|
@ -821,6 +838,9 @@ const ChannelsTable = ({}) => {
|
|||
return row.logo_id;
|
||||
},
|
||||
size: 75,
|
||||
minSize: 50,
|
||||
maxSize: 120,
|
||||
enableResizing: false,
|
||||
header: '',
|
||||
cell: ({ getValue }) => {
|
||||
const logoId = getValue();
|
||||
|
|
@ -839,6 +859,7 @@ const ChannelsTable = ({}) => {
|
|||
{
|
||||
id: 'actions',
|
||||
size: tableSize == 'compact' ? 75 : 100,
|
||||
enableResizing: false,
|
||||
header: '',
|
||||
cell: ({ row }) => (
|
||||
<ChannelRowActions
|
||||
|
|
@ -853,7 +874,7 @@ const ChannelsTable = ({}) => {
|
|||
),
|
||||
},
|
||||
],
|
||||
[selectedProfileId, channelGroups, logos, theme]
|
||||
[selectedProfileId, channelGroups, logos, theme, columnSizing]
|
||||
);
|
||||
|
||||
const renderHeaderCell = (header) => {
|
||||
|
|
@ -953,6 +974,12 @@ const ChannelsTable = ({}) => {
|
|||
manualFiltering: true,
|
||||
enableRowSelection: true,
|
||||
onRowSelectionChange: onRowSelectionChange,
|
||||
state: {
|
||||
columnSizing,
|
||||
pagination,
|
||||
sorting,
|
||||
},
|
||||
onColumnSizingChange: setColumnSizing,
|
||||
getExpandedRowHeight: (row) => {
|
||||
return 20 + 28 * row.original.streams.length;
|
||||
},
|
||||
|
|
@ -1301,7 +1328,7 @@ const ChannelsTable = ({}) => {
|
|||
style={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
overflowX: 'auto',
|
||||
border: 'solid 1px rgb(68,68,68)',
|
||||
borderRadius: 'var(--mantine-radius-default)',
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Box, Flex } from '@mantine/core';
|
||||
import CustomTableHeader from './CustomTableHeader';
|
||||
import { useCallback, useState, useRef } from 'react';
|
||||
import { useCallback, useState, useRef, useMemo } from 'react';
|
||||
import { flexRender } from '@tanstack/react-table';
|
||||
import table from '../../../helpers/table';
|
||||
import CustomTableBody from './CustomTableBody';
|
||||
|
|
@ -9,11 +9,28 @@ import useLocalStorage from '../../../hooks/useLocalStorage';
|
|||
const CustomTable = ({ table }) => {
|
||||
const [tableSize, _] = useLocalStorage('table-size', 'default');
|
||||
|
||||
// Get column sizing state for dependency tracking
|
||||
const columnSizing = table.getState().columnSizing;
|
||||
|
||||
// Calculate minimum table width reactively based on column sizes
|
||||
const minTableWidth = useMemo(() => {
|
||||
const headerGroups = table.getHeaderGroups();
|
||||
if (!headerGroups || headerGroups.length === 0) return 0;
|
||||
|
||||
const width =
|
||||
headerGroups[0]?.headers.reduce((total, header) => {
|
||||
return total + header.getSize();
|
||||
}, 0) || 0;
|
||||
|
||||
return width;
|
||||
}, [table, columnSizing]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={`divTable table-striped table-size-${tableSize}`}
|
||||
style={{
|
||||
width: '100%',
|
||||
minWidth: `${minTableWidth}px`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Box, Flex } from '@mantine/core';
|
||||
import { VariableSizeList as List } from 'react-window';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { useMemo } from 'react';
|
||||
import table from '../../../helpers/table';
|
||||
|
||||
const CustomTableBody = ({
|
||||
|
|
@ -23,6 +24,19 @@ const CustomTableBody = ({
|
|||
|
||||
const rows = getRowModel().rows;
|
||||
|
||||
// Calculate minimum width based only on fixed-size columns
|
||||
const minTableWidth = useMemo(() => {
|
||||
if (rows.length === 0) return 0;
|
||||
|
||||
return rows[0].getVisibleCells().reduce((total, cell) => {
|
||||
// Only count columns with fixed sizes, flexible columns will expand
|
||||
const columnSize = cell.column.columnDef.size
|
||||
? cell.column.getSize()
|
||||
: cell.column.columnDef.minSize || 150; // Default min for flexible columns
|
||||
return total + columnSize;
|
||||
}, 0);
|
||||
}, [rows]);
|
||||
|
||||
const renderTableBodyContents = () => {
|
||||
const virtualized = false;
|
||||
|
||||
|
|
@ -94,6 +108,7 @@ const CustomTableBody = ({
|
|||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
minWidth: '100%', // Force full width
|
||||
...(row.getIsSelected() && {
|
||||
backgroundColor: '#163632',
|
||||
}),
|
||||
|
|
@ -101,16 +116,24 @@ const CustomTableBody = ({
|
|||
}}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
const hasFixedSize = cell.column.columnDef.size;
|
||||
const isFlexible = !hasFixedSize;
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="td"
|
||||
key={`td-${cell.id}`}
|
||||
style={{
|
||||
flex: cell.column.columnDef.size ? '0 0 auto' : '1 1 0',
|
||||
width: cell.column.columnDef.size
|
||||
? cell.column.getSize()
|
||||
: undefined,
|
||||
minWidth: 0,
|
||||
...(cell.column.columnDef.grow
|
||||
? {
|
||||
flex: '1 1 0%',
|
||||
minWidth: 0,
|
||||
}
|
||||
: {
|
||||
flex: `0 0 ${cell.column.getSize ? cell.column.getSize() : 150}px`,
|
||||
width: `${cell.column.getSize ? cell.column.getSize() : 150}px`,
|
||||
maxWidth: `${cell.column.getSize ? cell.column.getSize() : 150}px`,
|
||||
}),
|
||||
...(tableCellProps && tableCellProps({ cell })),
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Box, Center, Checkbox, Flex } from '@mantine/core';
|
||||
import { flexRender } from '@tanstack/react-table';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
const CustomTableHeader = ({
|
||||
getHeaderGroups,
|
||||
|
|
@ -40,6 +40,25 @@ const CustomTableHeader = ({
|
|||
}
|
||||
};
|
||||
|
||||
// Get header groups for dependency tracking
|
||||
const headerGroups = getHeaderGroups();
|
||||
|
||||
// Calculate minimum width based only on fixed-size columns
|
||||
const minTableWidth = useMemo(() => {
|
||||
if (!headerGroups || headerGroups.length === 0) return 0;
|
||||
|
||||
const width =
|
||||
headerGroups[0]?.headers.reduce((total, header) => {
|
||||
// Only count columns with fixed sizes, flexible columns will expand
|
||||
const columnSize = header.column.columnDef.size
|
||||
? header.getSize()
|
||||
: header.column.columnDef.minSize || 150; // Default min for flexible columns
|
||||
return total + columnSize;
|
||||
}, 0) || 0;
|
||||
|
||||
return width;
|
||||
}, [headerGroups]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="thead"
|
||||
|
|
@ -54,7 +73,11 @@ const CustomTableHeader = ({
|
|||
<Box
|
||||
className="tr"
|
||||
key={headerGroup.id}
|
||||
style={{ display: 'flex', width: '100%' }}
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
minWidth: '100%', // Force full width
|
||||
}}
|
||||
>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
|
|
@ -62,11 +85,17 @@ const CustomTableHeader = ({
|
|||
className="th"
|
||||
key={header.id}
|
||||
style={{
|
||||
flex: header.column.columnDef.size ? '0 0 auto' : '1 1 0',
|
||||
width: header.column.columnDef.size
|
||||
? header.getSize()
|
||||
: undefined,
|
||||
minWidth: 0,
|
||||
...(header.column.columnDef.grow
|
||||
? {
|
||||
flex: '1 1 0%',
|
||||
minWidth: 0,
|
||||
}
|
||||
: {
|
||||
flex: `0 0 ${header.getSize ? header.getSize() : 150}px`,
|
||||
width: `${header.getSize ? header.getSize() : 150}px`,
|
||||
maxWidth: `${header.getSize ? header.getSize() : 150}px`,
|
||||
}),
|
||||
position: 'relative',
|
||||
// ...(tableCellProps && tableCellProps({ cell: header })),
|
||||
}}
|
||||
>
|
||||
|
|
@ -76,10 +105,46 @@ const CustomTableHeader = ({
|
|||
...(header.column.columnDef.style &&
|
||||
header.column.columnDef.style),
|
||||
height: '100%',
|
||||
paddingRight: header.column.getCanResize() ? '8px' : '0px', // Add padding for resize handle
|
||||
}}
|
||||
>
|
||||
{renderHeaderCell(header)}
|
||||
</Flex>
|
||||
{header.column.getCanResize() && (
|
||||
<div
|
||||
onMouseDown={header.getResizeHandler()}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
className={`resizer ${
|
||||
header.column.getIsResizing() ? 'isResizing' : ''
|
||||
}`}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
height: '100%',
|
||||
width: '8px', // Make it slightly wider
|
||||
cursor: 'col-resize',
|
||||
userSelect: 'none',
|
||||
touchAction: 'none',
|
||||
backgroundColor: header.column.getIsResizing()
|
||||
? '#3b82f6'
|
||||
: 'transparent',
|
||||
opacity: header.column.getIsResizing() ? 1 : 0.3, // Make it more visible by default
|
||||
transition: 'opacity 0.2s',
|
||||
zIndex: 1000, // Ensure it's on top
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.target.style.opacity = '1';
|
||||
e.target.style.backgroundColor = '#6b7280';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!header.column.getIsResizing()) {
|
||||
e.target.style.opacity = '0.5';
|
||||
e.target.style.backgroundColor = 'transparent';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ const useTable = ({
|
|||
onRowSelectionChange = null,
|
||||
getExpandedRowHeight = null,
|
||||
state = [],
|
||||
columnSizing,
|
||||
setColumnSizing,
|
||||
...options
|
||||
}) => {
|
||||
const [selectedTableIds, setSelectedTableIds] = useState([]);
|
||||
|
|
@ -85,15 +87,21 @@ const useTable = ({
|
|||
|
||||
const table = useReactTable({
|
||||
defaultColumn: {
|
||||
size: undefined,
|
||||
minSize: 0,
|
||||
maxSize: Number.MAX_SAFE_INTEGER,
|
||||
size: 150,
|
||||
},
|
||||
...options,
|
||||
state: {
|
||||
data: options.data,
|
||||
...options.state,
|
||||
selectedTableIds,
|
||||
...(columnSizing && { columnSizing }),
|
||||
},
|
||||
onStateChange: options.onStateChange,
|
||||
...(setColumnSizing && { onColumnSizingChange: setColumnSizing }),
|
||||
getCoreRowModel: options.getCoreRowModel ?? getCoreRowModel(),
|
||||
enableColumnResizing: true,
|
||||
columnResizeMode: 'onChange',
|
||||
});
|
||||
|
||||
const selectedTableIdsSet = useMemo(
|
||||
|
|
|
|||
|
|
@ -187,14 +187,15 @@ const EPGsTable = () => {
|
|||
size: 200,
|
||||
},
|
||||
{
|
||||
header: 'Source Type',
|
||||
header: 'Type',
|
||||
accessorKey: 'source_type',
|
||||
size: 150,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
header: 'URL / API Key / File Path',
|
||||
accessorKey: 'url',
|
||||
enableSorting: false,
|
||||
minSize: 250,
|
||||
cell: ({ cell, row }) => {
|
||||
const value =
|
||||
cell.getValue() ||
|
||||
|
|
@ -220,7 +221,7 @@ const EPGsTable = () => {
|
|||
{
|
||||
header: 'Status',
|
||||
accessorKey: 'status',
|
||||
size: 150,
|
||||
size: 100,
|
||||
cell: ({ row }) => {
|
||||
const data = row.original;
|
||||
|
||||
|
|
@ -236,6 +237,8 @@ const EPGsTable = () => {
|
|||
header: 'Status Message',
|
||||
accessorKey: 'last_message',
|
||||
enableSorting: false,
|
||||
minSize: 250,
|
||||
grow: true,
|
||||
cell: ({ row }) => {
|
||||
const data = row.original;
|
||||
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ const LogosTable = () => {
|
|||
{
|
||||
header: 'Name',
|
||||
accessorKey: 'name',
|
||||
size: 200,
|
||||
size: 250,
|
||||
cell: ({ getValue }) => (
|
||||
<Text fw={500} size="sm">
|
||||
{getValue()}
|
||||
|
|
@ -479,6 +479,7 @@ const LogosTable = () => {
|
|||
{
|
||||
header: 'URL',
|
||||
accessorKey: 'url',
|
||||
grow: true,
|
||||
cell: ({ getValue }) => (
|
||||
<Group gap={4} style={{ alignItems: 'center' }}>
|
||||
<Box
|
||||
|
|
|
|||
|
|
@ -431,10 +431,10 @@ const M3UTable = () => {
|
|||
sortable: true,
|
||||
},
|
||||
{
|
||||
header: 'Account Type',
|
||||
header: 'Type',
|
||||
accessorKey: 'account_type',
|
||||
sortable: true,
|
||||
size: 150,
|
||||
size: 100,
|
||||
cell: ({ cell }) => {
|
||||
const value = cell.getValue();
|
||||
return value === 'XC' ? 'XC' : 'M3U';
|
||||
|
|
@ -443,6 +443,7 @@ const M3UTable = () => {
|
|||
{
|
||||
header: 'URL / File',
|
||||
accessorKey: 'server_url',
|
||||
size: 250,
|
||||
cell: ({ cell, row }) => {
|
||||
const value = cell.getValue() || row.original.file_path || '';
|
||||
return (
|
||||
|
|
@ -461,16 +462,10 @@ const M3UTable = () => {
|
|||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Max Streams',
|
||||
accessorKey: 'max_streams',
|
||||
sortable: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
accessorKey: 'status',
|
||||
size: 150,
|
||||
size: 100,
|
||||
cell: ({ cell }) => {
|
||||
const value = cell.getValue();
|
||||
if (!value) return null;
|
||||
|
|
@ -486,6 +481,8 @@ const M3UTable = () => {
|
|||
{
|
||||
header: 'Status Message',
|
||||
accessorKey: 'last_message',
|
||||
grow: true,
|
||||
minSize: 250,
|
||||
cell: ({ cell, row }) => {
|
||||
const value = cell.getValue();
|
||||
const data = row.original;
|
||||
|
|
@ -569,6 +566,12 @@ const M3UTable = () => {
|
|||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Max Streams',
|
||||
accessorKey: 'max_streams',
|
||||
sortable: true,
|
||||
size: 125,
|
||||
},
|
||||
{
|
||||
header: 'Updated',
|
||||
accessorKey: 'updated_at',
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ const StreamProfiles = () => {
|
|||
{
|
||||
header: 'Name',
|
||||
accessorKey: 'name',
|
||||
size: 150,
|
||||
size: 175,
|
||||
cell: ({ cell }) => (
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -88,7 +88,7 @@ const StreamProfiles = () => {
|
|||
{
|
||||
header: 'Command',
|
||||
accessorKey: 'command',
|
||||
size: 150,
|
||||
size: 100,
|
||||
cell: ({ cell }) => (
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -104,7 +104,7 @@ const StreamProfiles = () => {
|
|||
{
|
||||
header: 'Parameters',
|
||||
accessorKey: 'parameters',
|
||||
// size: 200,
|
||||
grow: true,
|
||||
cell: ({ cell }) => (
|
||||
<Tooltip label={cell.getValue()}>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -212,6 +212,10 @@ const StreamsTable = ({}) => {
|
|||
channel_group: '',
|
||||
m3u_account: '',
|
||||
});
|
||||
const [columnSizing, setColumnSizing] = useLocalStorage(
|
||||
'streams-table-column-sizing',
|
||||
{}
|
||||
);
|
||||
const debouncedFilters = useDebounce(filters, 500, () => {
|
||||
// Reset to first page whenever filters change to avoid "Invalid page" errors
|
||||
setPagination((prev) => ({
|
||||
|
|
@ -256,15 +260,16 @@ const StreamsTable = ({}) => {
|
|||
() => [
|
||||
{
|
||||
id: 'actions',
|
||||
size: tableSize == 'compact' ? 60 : 80,
|
||||
size: columnSizing.actions || (tableSize == 'compact' ? 60 : 80),
|
||||
},
|
||||
{
|
||||
id: 'select',
|
||||
size: 30,
|
||||
size: columnSizing.select || 30,
|
||||
},
|
||||
{
|
||||
header: 'Name',
|
||||
accessorKey: 'name',
|
||||
size: columnSizing.name || 200,
|
||||
cell: ({ getValue }) => (
|
||||
<Box
|
||||
style={{
|
||||
|
|
@ -283,6 +288,7 @@ const StreamsTable = ({}) => {
|
|||
channelGroups[row.channel_group]
|
||||
? channelGroups[row.channel_group].name
|
||||
: '',
|
||||
size: columnSizing.group || 150,
|
||||
cell: ({ getValue }) => (
|
||||
<Box
|
||||
style={{
|
||||
|
|
@ -297,7 +303,7 @@ const StreamsTable = ({}) => {
|
|||
},
|
||||
{
|
||||
id: 'm3u',
|
||||
size: 150,
|
||||
size: columnSizing.m3u || 150,
|
||||
accessorFn: (row) =>
|
||||
playlists.find((playlist) => playlist.id === row.m3u_account)?.name,
|
||||
cell: ({ getValue }) => (
|
||||
|
|
@ -315,7 +321,7 @@ const StreamsTable = ({}) => {
|
|||
),
|
||||
},
|
||||
],
|
||||
[channelGroups, playlists]
|
||||
[channelGroups, playlists, columnSizing, tableSize]
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -652,6 +658,8 @@ const StreamsTable = ({}) => {
|
|||
filters,
|
||||
pagination,
|
||||
sorting,
|
||||
columnSizing,
|
||||
setColumnSizing,
|
||||
onRowSelectionChange: onRowSelectionChange,
|
||||
manualPagination: true,
|
||||
manualSorting: true,
|
||||
|
|
@ -855,7 +863,7 @@ const StreamsTable = ({}) => {
|
|||
style={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
overflowX: 'auto',
|
||||
border: 'solid 1px rgb(68,68,68)',
|
||||
borderRadius: 'var(--mantine-radius-default)',
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -61,11 +61,13 @@ const UserAgentsTable = () => {
|
|||
{
|
||||
header: 'Name',
|
||||
accessorKey: 'name',
|
||||
size: 125,
|
||||
},
|
||||
{
|
||||
header: 'User-Agent',
|
||||
accessorKey: 'user_agent',
|
||||
enableSorting: false,
|
||||
grow: true,
|
||||
cell: ({ cell }) => (
|
||||
<div
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ const UsersTable = () => {
|
|||
{
|
||||
header: 'Email',
|
||||
accessorKey: 'email',
|
||||
grow: true,
|
||||
cell: ({ getValue }) => (
|
||||
<Box
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ html {
|
|||
}
|
||||
|
||||
.divTable {
|
||||
width: 100%;
|
||||
/* border: 1px solid lightgray; */
|
||||
/* width: fit-content; */
|
||||
/* display: flex;
|
||||
flex-direction: column; */
|
||||
}
|
||||
|
|
@ -180,3 +180,35 @@ html {
|
|||
-ms-user-select: text !important;
|
||||
cursor: text !important;
|
||||
}
|
||||
|
||||
/* Column resize handle styles */
|
||||
.resizer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
background-color: transparent;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.resizer:hover {
|
||||
opacity: 1 !important;
|
||||
background-color: #6b7280 !important;
|
||||
}
|
||||
|
||||
.resizer.isResizing {
|
||||
opacity: 1 !important;
|
||||
background-color: #3b82f6 !important;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
/* Show resize handle on header hover */
|
||||
.th:hover .resizer {
|
||||
opacity: 0.5;
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,22 @@ import { Box } from '@mantine/core';
|
|||
import { Allotment } from 'allotment';
|
||||
import { USER_LEVELS } from '../constants';
|
||||
import useAuthStore from '../store/auth';
|
||||
import useLocalStorage from '../hooks/useLocalStorage';
|
||||
|
||||
const ChannelsPage = () => {
|
||||
const authUser = useAuthStore((s) => s.user);
|
||||
const [allotmentSizes, setAllotmentSizes] = useLocalStorage(
|
||||
'channels-splitter-sizes',
|
||||
[50, 50]
|
||||
);
|
||||
|
||||
const handleSplitChange = (sizes) => {
|
||||
setAllotmentSizes(sizes);
|
||||
};
|
||||
|
||||
const handleResize = (sizes) => {
|
||||
setAllotmentSizes(sizes);
|
||||
};
|
||||
|
||||
if (!authUser.id) {
|
||||
return <></>;
|
||||
|
|
@ -30,10 +43,12 @@ const ChannelsPage = () => {
|
|||
}}
|
||||
>
|
||||
<Allotment
|
||||
defaultSizes={[50, 50]}
|
||||
defaultSizes={allotmentSizes}
|
||||
style={{ height: '100%', width: '100%', minWidth: '600px' }}
|
||||
className="custom-allotment"
|
||||
minSize={100}
|
||||
onChange={handleSplitChange}
|
||||
onResize={handleResize}
|
||||
>
|
||||
<div style={{ padding: 10, overflowX: 'auto', minWidth: '100px' }}>
|
||||
<div style={{ minWidth: '600px' }}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue