Commit de0d6ac9 by yyhhyyyyyy Committed by GitHub

fix(web): sync channel card selection state (#5700)

parent f4473d96
...@@ -61,7 +61,7 @@ export { ...@@ -61,7 +61,7 @@ export {
export { useDebouncedColumnFilter } from './hooks/use-debounced-column-filter' export { useDebouncedColumnFilter } from './hooks/use-debounced-column-filter'
export const DISABLED_ROW_DESKTOP = export const DISABLED_ROW_DESKTOP =
'[--data-table-card-bg:var(--table-disabled)] hover:[--data-table-card-bg:var(--table-disabled-hover)] [background-color:var(--table-disabled)] hover:[background-color:var(--table-disabled-hover)] [&>td:first-child]:[border-left-color:var(--table-disabled-border)] [&>td:first-child]:border-l-4 [&>td:first-child]:pl-1' '[--data-table-card-bg:var(--table-disabled)] hover:[--data-table-card-bg:var(--table-disabled-hover)] data-[state=selected]:![--data-table-card-bg:var(--table-disabled)] data-[state=selected]:hover:![--data-table-card-bg:var(--table-disabled-hover)] [background-color:var(--table-disabled)] hover:[background-color:var(--table-disabled-hover)] [&>td:first-child]:[border-left-color:var(--table-disabled-border)] [&>td:first-child]:border-l-4 [&>td:first-child]:pl-1'
export const DISABLED_ROW_MOBILE = export const DISABLED_ROW_MOBILE =
'[--data-table-card-bg:var(--table-disabled)] [background-color:var(--table-disabled)]' '[--data-table-card-bg:var(--table-disabled)] data-[state=selected]:![--data-table-card-bg:var(--table-disabled)] [background-color:var(--table-disabled)]'
...@@ -41,6 +41,10 @@ export type DataTableCardHelpers = { ...@@ -41,6 +41,10 @@ export type DataTableCardHelpers = {
* Provided so custom renderers can match the default layout decision. * Provided so custom renderers can match the default layout decision.
*/ */
compact: boolean compact: boolean
/**
* Row selection state captured before entering memoized custom card renderers.
*/
isSelected: boolean
} }
export interface DataTableCardGridProps<TData> { export interface DataTableCardGridProps<TData> {
...@@ -80,7 +84,7 @@ function CardGridSkeleton(props: { ...@@ -80,7 +84,7 @@ function CardGridSkeleton(props: {
{[1, 2, 3, 4, 5, 6].map((i) => ( {[1, 2, 3, 4, 5, 6].map((i) => (
<div <div
key={`${prefix}-${i}`} key={`${prefix}-${i}`}
className='space-y-3 rounded-lg border [background-color:var(--table-row)] p-3' className='space-y-3 rounded-lg border bg-(--table-row) p-3'
> >
<div className='flex items-center justify-between gap-2'> <div className='flex items-center justify-between gap-2'>
<Skeleton className='h-4 w-32' /> <Skeleton className='h-4 w-32' />
...@@ -108,8 +112,8 @@ function CardGridSkeleton(props: { ...@@ -108,8 +112,8 @@ function CardGridSkeleton(props: {
* the card view reusable across any table with zero per-feature work while * the card view reusable across any table with zero per-feature work while
* still allowing a bespoke card design when desired. * still allowing a bespoke card design when desired.
* *
* Selection (the `select` column) is intentionally not rendered in card mode; * The default generic card omits the `select` column. Custom `renderCard`
* bulk selection remains a table-mode capability. * implementations can use `helpers.isSelected` to keep selection UI in sync.
*/ */
export function DataTableCardGrid<TData>(props: DataTableCardGridProps<TData>) { export function DataTableCardGrid<TData>(props: DataTableCardGridProps<TData>) {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -156,17 +160,19 @@ export function DataTableCardGrid<TData>(props: DataTableCardGridProps<TData>) { ...@@ -156,17 +160,19 @@ export function DataTableCardGrid<TData>(props: DataTableCardGridProps<TData>) {
<div className={props.gridClassName ?? DEFAULT_GRID_CLASSNAME}> <div className={props.gridClassName ?? DEFAULT_GRID_CLASSNAME}>
{rows.map((row) => { {rows.map((row) => {
const key = props.getRowKey ? props.getRowKey(row) : row.id const key = props.getRowKey ? props.getRowKey(row) : row.id
const isSelected = row.getIsSelected()
return ( return (
<div <div
key={key} key={key}
data-slot='data-table-card' data-slot='data-table-card'
data-state={isSelected ? 'selected' : undefined}
className={cn( className={cn(
'rounded-lg border [background-color:var(--data-table-card-bg,var(--table-row))] px-3 py-2.5', 'rounded-lg border bg-(--data-table-card-bg,var(--table-row)) px-3 py-2.5 transition-[background-color,border-color] duration-150 data-[state=selected]:[--data-table-card-bg:color-mix(in_oklch,var(--primary)_7%,var(--table-row))] data-[state=selected]:border-primary/40',
props.getRowClassName?.(row) props.getRowClassName?.(row)
)} )}
> >
{props.renderCard ? ( {props.renderCard ? (
props.renderCard(row, { compact }) props.renderCard(row, { compact, isSelected })
) : ( ) : (
<CardRowContent row={row} compact={compact} /> <CardRowContent row={row} compact={compact} />
)} )}
......
...@@ -37,7 +37,13 @@ const SENSITIVE_MASK = '••••' ...@@ -37,7 +37,13 @@ const SENSITIVE_MASK = '••••'
* priority/weight spinners, balance refresh, response/test times, tag * priority/weight spinners, balance refresh, response/test times, tag
* expand-collapse, and the per-row (or per-tag) actions menu. * expand-collapse, and the per-row (or per-tag) actions menu.
*/ */
function ChannelCardComponent({ row }: { row: Row<Channel> }) { function ChannelCardComponent({
row,
isSelected,
}: {
row: Row<Channel>
isSelected: boolean
}) {
const { t } = useTranslation() const { t } = useTranslation()
const { sensitiveVisible } = useChannels() const { sensitiveVisible } = useChannels()
const isTagRow = isTagAggregateRow(row.original) const isTagRow = isTagAggregateRow(row.original)
...@@ -81,16 +87,19 @@ function ChannelCardComponent({ row }: { row: Row<Channel> }) { ...@@ -81,16 +87,19 @@ function ChannelCardComponent({ row }: { row: Row<Channel> }) {
row.original.status !== CHANNEL_STATUS.MANUAL_DISABLED) row.original.status !== CHANNEL_STATUS.MANUAL_DISABLED)
return ( return (
<div className='flex flex-col gap-3'> <div
data-state={isSelected ? 'selected' : undefined}
className='flex flex-col gap-3'
>
{/* Row 1: selection + type, with status badge + actions menu */} {/* Row 1: selection + type, with status badge + actions menu */}
<div className='flex items-center justify-between gap-2'> <div className='flex items-center justify-between gap-2'>
<div className='flex min-w-0 flex-1 items-center gap-2'> <div className='flex min-w-0 flex-1 items-center gap-2'>
{!isTagRow && selectCell && ( {!isTagRow && selectCell && (
<span className='flex-shrink-0'>{selectCell}</span> <span className='shrink-0'>{selectCell}</span>
)} )}
<div className='min-w-0 overflow-hidden'>{typeCell}</div> <div className='min-w-0 overflow-hidden'>{typeCell}</div>
</div> </div>
<div className='flex flex-shrink-0 items-center gap-1.5'> <div className='flex shrink-0 items-center gap-1.5'>
{showStatusBadge && statusCell} {showStatusBadge && statusCell}
<ChannelRowActionsLayoutContext.Provider value='card'> <ChannelRowActionsLayoutContext.Provider value='card'>
{actionsCell} {actionsCell}
...@@ -122,7 +131,7 @@ function ChannelCardComponent({ row }: { row: Row<Channel> }) { ...@@ -122,7 +131,7 @@ function ChannelCardComponent({ row }: { row: Row<Channel> }) {
{/* Right column (sits on the right, content left-aligned). A single {/* Right column (sits on the right, content left-aligned). A single
grid with content-sized columns keeps Priority/Weight and grid with content-sized columns keeps Priority/Weight and
Response/Last Tested aligned without wasting horizontal space. */} Response/Last Tested aligned without wasting horizontal space. */}
<div className='grid flex-shrink-0 grid-cols-[auto_auto] items-center gap-x-3 gap-y-1'> <div className='grid shrink-0 grid-cols-[auto_auto] items-center gap-x-3 gap-y-1'>
<span className={labelClass}>{t('Priority')}</span> <span className={labelClass}>{t('Priority')}</span>
<span className={labelClass}>{t('Weight')}</span> <span className={labelClass}>{t('Weight')}</span>
<div className='flex justify-start'>{priorityCell}</div> <div className='flex justify-start'>{priorityCell}</div>
......
...@@ -370,7 +370,9 @@ export function ChannelsTable() { ...@@ -370,7 +370,9 @@ export function ChannelsTable() {
skeletonKeyPrefix='channel-skeleton' skeletonKeyPrefix='channel-skeleton'
enableCardView enableCardView
viewModeStorageKey={CHANNELS_VIEW_MODE_STORAGE_KEY} viewModeStorageKey={CHANNELS_VIEW_MODE_STORAGE_KEY}
renderCard={(row) => <ChannelCard row={row} />} renderCard={(row, { isSelected }) => (
<ChannelCard row={row} isSelected={isSelected} />
)}
cardGridClassName='grid grid-cols-1 gap-3 sm:gap-4 lg:grid-cols-3' cardGridClassName='grid grid-cols-1 gap-3 sm:gap-4 lg:grid-cols-3'
applyHeaderSize applyHeaderSize
toolbarProps={{ toolbarProps={{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment