mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-06 17:46:23 +00:00
fix(web): sync channel card selection state (#5700)
This commit is contained in:
+2
-2
@@ -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)]'
|
||||||
|
|||||||
+11
-5
@@ -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: {
|
|||||||
{[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: {
|
|||||||
* 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>) {
|
|||||||
<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 = '••••'
|
|||||||
* 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> }) {
|
|||||||
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> }) {
|
|||||||
{/* 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() {
|
|||||||
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={{
|
||||||
|
|||||||
Reference in New Issue
Block a user