Skip to content

Commit 746d27d

Browse files
MartinCastroAlvarezmartin-castro-laminr-aiclaude
authored
fix(spa): frozen columns track row hover/selection background (#613) (#616)
Sticky body cells need an explicit background-color (so scrolling content doesn't bleed through), but the fixed colour was OVERRIDING the row's :hover and selection highlight — the frozen prefix stayed white/dark while the rest of the row lit up, leaving the columns visually disconnected. Drive the cell bg from a `--dar-row-bg` custom property declared on the <tr>, flipped on :hover and `data-selected="true"`, and consumed by sticky cells via the new `dar-sticky-cell` class. One declaration per state, one rule per cell — easy to extend if more row states (striping, etc.) are added. Header cells stay `bg-gray-50` — <thead> has no hover and selection doesn't propagate up to it, so the existing flow is correct there. Also: add the Django Packages badge to the README, bump 1.4.9 → 1.4.10. Co-authored-by: Martin Castro Laminrs <mcastro@laminr.ai> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f4dabd5 commit 746d27d

4 files changed

Lines changed: 56 additions & 6 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# django-admin-react
22

3+
[![Latest on Django Packages](https://img.shields.io/badge/PyPI-django--admin--react-tags-8c3c26.svg)](https://djangopackages.org/packages/p/django-admin-react/)
4+
35
A drop-in **React single-page admin** for any Django 5+ project. Same
46
`pip install`, same `INSTALLED_APPS`, same `urls.py include()` — and
57
your `ModelAdmin` classes drive everything. No React code on your side.

frontend/apps/web/src/index.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,38 @@ body,
9898
.dark .hover\:text-gray-100:hover {
9999
color: #fff !important;
100100
}
101+
102+
/* Frozen-column row-state propagation (#613). Sticky body cells need
103+
an explicit background-color so scrolling content doesn't bleed
104+
through — but that fixed colour then OVERRIDES the row's :hover /
105+
selected highlight, leaving the frozen prefix visually disconnected
106+
from the rest of the row. Fix: drive the cell bg from a CSS custom
107+
property declared on the <tr>, and flip the property on :hover /
108+
data-selected. One declaration per state, one rule per cell —
109+
easy to extend if more row states are added (striping etc.). */
110+
.dar-table tbody tr {
111+
--dar-row-bg: #fff;
112+
}
113+
.dar-table tbody tr:hover {
114+
--dar-row-bg: #f9fafb; /* matches Tailwind hover:bg-gray-50 */
115+
}
116+
.dar-table tbody tr[data-selected='true'] {
117+
--dar-row-bg: #eff6ff; /* light blue-50 — selection */
118+
119+
background-color: var(--dar-row-bg);
120+
}
121+
.dar-sticky-cell {
122+
background-color: var(--dar-row-bg);
123+
}
124+
.dark .dar-table tbody tr {
125+
--dar-row-bg: #111827; /* same as the bg-white .dark remap */
126+
}
127+
.dark .dar-table tbody tr:hover {
128+
--dar-row-bg: #1f2937; /* same as hover:bg-gray-50 .dark remap */
129+
}
130+
.dark .dar-table tbody tr[data-selected='true'] {
131+
--dar-row-bg: #1e3a8a;
132+
}
101133
/* Form controls have no explicit bg utility (the @dar/ui Input + the
102134
filter selects render on the browser-default white), so the bg-white
103135
remap above misses them — they'd stay white in dark mode. Style them

frontend/packages/ui/src/Table.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,16 @@ export function Table<Row>({
204204
? ''
205205
: `${base} z-10 ${key === lastStickyKey ? 'shadow-[2px_0_0_-1px_rgba(0,0,0,0.06)]' : ''}`;
206206
}
207+
// Body-cell sticky background tracks the row's :hover / selection
208+
// state via the `--dar-row-bg` custom property (apps/web/src/index.css
209+
// #613). Header cells stay `bg-gray-50` — header has no hover and
210+
// selection doesn't propagate up to <thead>, so the simpler
211+
// `stickyClass` flow is fine there.
212+
function stickyBodyClass(key: string): string {
213+
return stickyLefts[key] === undefined
214+
? ''
215+
: `dar-sticky-cell z-10 ${key === lastStickyKey ? 'shadow-[2px_0_0_-1px_rgba(0,0,0,0.06)]' : ''}`;
216+
}
207217

208218
const startResize = (key: string, e: ReactMouseEvent): void => {
209219
e.preventDefault();
@@ -227,7 +237,7 @@ export function Table<Row>({
227237

228238
return (
229239
<div className="overflow-x-auto" aria-busy={loading || undefined}>
230-
<table className={`min-w-full text-sm ${hasWidths ? 'table-fixed' : ''}`}>
240+
<table className={`dar-table min-w-full text-sm ${hasWidths ? 'table-fixed' : ''}`}>
231241
{hasWidths && (
232242
<colgroup>
233243
{selectable && <col style={{ width: '2.5rem' }} />}
@@ -338,7 +348,7 @@ export function Table<Row>({
338348
<tr key={`dar-skeleton-${i}`}>
339349
{selectable && (
340350
<td
341-
className={`w-10 px-4 py-2 ${stickyClass('__select', 'bg-white')}`}
351+
className={`w-10 px-4 py-2 ${stickyBodyClass('__select')}`}
342352
style={stickyStyle('__select')}
343353
>
344354
<Skeleton className="h-4 w-4" />
@@ -347,7 +357,7 @@ export function Table<Row>({
347357
{columns.map((col) => (
348358
<td
349359
key={col.key}
350-
className={`px-4 py-2 ${ALIGN_CLASSES[col.align ?? 'left']} ${col.sticky ? stickyClass(col.key, 'bg-white') : ''}`}
360+
className={`px-4 py-2 ${ALIGN_CLASSES[col.align ?? 'left']} ${col.sticky ? stickyBodyClass(col.key) : ''}`}
351361
style={col.sticky ? stickyStyle(col.key) : undefined}
352362
>
353363
<Skeleton className="h-4 w-full max-w-[12rem]" />
@@ -357,15 +367,21 @@ export function Table<Row>({
357367
))
358368
: rows.map((row) => {
359369
const key = rowKey(row);
370+
const isSelected = selectable && selected.has(key);
360371
return (
361372
<tr
362373
key={key}
363374
onClick={onRowClick ? () => onRowClick(row) : undefined}
375+
// `data-selected` propagates the checkbox state to
376+
// both the row's bg AND the frozen-cells' bg via
377+
// the `--dar-row-bg` custom property (apps/web/
378+
// src/index.css #613).
379+
data-selected={isSelected ? 'true' : undefined}
364380
className={onRowClick ? 'cursor-pointer hover:bg-gray-50' : ''}
365381
>
366382
{selectable && (
367383
<td
368-
className={`w-10 px-4 py-2 ${stickyClass('__select', 'bg-white')}`}
384+
className={`w-10 px-4 py-2 ${stickyBodyClass('__select')}`}
369385
style={stickyStyle('__select')}
370386
onClick={(e) => e.stopPropagation()}
371387
>
@@ -379,7 +395,7 @@ export function Table<Row>({
379395
{columns.map((col, ci) => (
380396
<td
381397
key={col.key}
382-
className={`px-4 py-2 ${ALIGN_CLASSES[col.align ?? 'left']} ${col.sticky ? stickyClass(col.key, 'bg-white') : ''}`}
398+
className={`px-4 py-2 ${ALIGN_CLASSES[col.align ?? 'left']} ${col.sticky ? stickyBodyClass(col.key) : ''}`}
383399
style={col.sticky ? stickyStyle(col.key) : undefined}
384400
>
385401
{/* Cap very wide cells and truncate with an

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-admin-react"
3-
version = "1.4.9"
3+
version = "1.4.10"
44
description = "A drop-in React single-page admin for Django, driven entirely by ModelAdmin."
55
authors = ["django-admin-react contributors"]
66
license = "MIT"

0 commit comments

Comments
 (0)