@open-mercato/ui 0.10.1-develop.7702.1.f991ecf392 → 0.10.1-develop.7707.1.61845a3ef7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.10.1-develop.7702.1.f991ecf392",
3
+ "version": "0.10.1-develop.7707.1.61845a3ef7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -155,14 +155,14 @@
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.10.1-develop.7702.1.f991ecf392",
158
+ "@open-mercato/shared": "0.10.1-develop.7707.1.61845a3ef7",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
164
  "@figma/code-connect": "^1.3.4",
165
- "@open-mercato/shared": "0.10.1-develop.7702.1.f991ecf392",
165
+ "@open-mercato/shared": "0.10.1-develop.7707.1.61845a3ef7",
166
166
  "@testing-library/dom": "^10.4.1",
167
167
  "@testing-library/jest-dom": "^7.0.1",
168
168
  "@testing-library/react": "^16.3.3",
@@ -553,6 +553,14 @@ const STICKY_RIGHT_SHADOW_CLASS =
553
553
  'md:before:absolute md:before:inset-y-0 md:before:-left-2 md:before:w-2 md:before:bg-gradient-to-l md:before:from-foreground/8 md:before:to-transparent md:before:pointer-events-none'
554
554
  const STICKY_LEFT_SHADOW_CLASS =
555
555
  'md:after:absolute md:after:inset-y-0 md:after:-right-2 md:after:w-2 md:after:bg-gradient-to-r md:after:from-foreground/8 md:after:to-transparent md:after:pointer-events-none'
556
+ // Pinned cells need an opaque background so scrolled columns don't show through,
557
+ // which also hides the translucent tints painted behind them (the `bg-muted/40`
558
+ // header strip on `<thead>`, the `bg-muted/30` row hover on `<tr>`). A flat
559
+ // gradient (equal stops) layers the same tint over the opaque surface, so the
560
+ // pinned cell composites to exactly what the transparent cells show (#7414).
561
+ const STICKY_HEADER_TINT_CLASS = 'md:bg-linear-to-r md:from-muted/40 md:to-muted/40'
562
+ const STICKY_ROW_HOVER_TINT_CLASS =
563
+ 'md:group-hover/row:bg-linear-to-r md:group-hover/row:from-muted/30 md:group-hover/row:to-muted/30'
556
564
 
557
565
  export type BulkActionExecuteResult = {
558
566
  ok: boolean
@@ -3591,6 +3599,11 @@ function DataTableImpl<T extends RowData>({
3591
3599
  // `isolate` keeps the z-index of pinned cells local to the table so they
3592
3600
  // scroll beneath the sticky app top bar instead of over it.
3593
3601
  const tableScrollWrapperClassName = embedded ? 'isolate' : 'isolate overflow-auto'
3602
+ // Non-embedded tables sit on the `bg-card` container above; embedded ones
3603
+ // render on whatever surface hosts them, so they keep the page background.
3604
+ const stickySurfaceClass = embedded ? 'md:bg-background' : 'md:bg-card'
3605
+ const stickyHeaderSurfaceClass = `${stickySurfaceClass} ${STICKY_HEADER_TINT_CLASS}`
3606
+ const stickyBodySurfaceClass = `${stickySurfaceClass} ${STICKY_ROW_HOVER_TINT_CLASS}`
3594
3607
 
3595
3608
  const virtualScrollRef = React.useRef<HTMLDivElement>(null)
3596
3609
  // Measure the horizontal scroll viewport so the empty and error states can center within
@@ -3764,7 +3777,7 @@ function DataTableImpl<T extends RowData>({
3764
3777
  const columnMeta = (header.column.columnDef as any)?.meta
3765
3778
  const priority = resolvePriority(header.column)
3766
3779
  const isFirstDataColumn = headerIndex === 0
3767
- const stickyClass = stickyFirstColumn && isFirstDataColumn ? ` md:sticky md:left-0 md:z-10 md:bg-background ${STICKY_LEFT_SHADOW_CLASS}` : ''
3780
+ const stickyClass = stickyFirstColumn && isFirstDataColumn ? ` md:sticky md:left-0 md:z-10 ${stickyHeaderSurfaceClass} ${STICKY_LEFT_SHADOW_CLASS}` : ''
3768
3781
  const isColumnSortable = sortable && !!header.column.getCanSort?.()
3769
3782
  const headerContent = header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
3770
3783
  // Columns that can't be sorted (e.g. a manual "select" checkbox column) may render
@@ -3820,7 +3833,7 @@ function DataTableImpl<T extends RowData>({
3820
3833
  <TableHead
3821
3834
  className={cn(
3822
3835
  actionsColumnAlign === 'center' ? 'w-0 text-center' : 'w-0 text-right',
3823
- stickyActionsColumn && `md:sticky md:right-0 md:z-20 md:bg-background ${STICKY_RIGHT_SHADOW_CLASS}`,
3836
+ stickyActionsColumn && `md:sticky md:right-0 md:z-20 ${stickyHeaderSurfaceClass} ${STICKY_RIGHT_SHADOW_CLASS}`,
3824
3837
  )}
3825
3838
  >
3826
3839
  {t('ui.dataTable.actionsColumn', 'Actions')}
@@ -3871,7 +3884,7 @@ function DataTableImpl<T extends RowData>({
3871
3884
  <TableRow
3872
3885
  key={row.id}
3873
3886
  data-state={row.getIsSelected() && 'selected'}
3874
- className={isClickable ? 'cursor-pointer hover:bg-muted/50 transition-colors' : ''}
3887
+ className={cn('group/row', isClickable && 'cursor-pointer hover:bg-muted/50 transition-colors')}
3875
3888
  onClick={isClickable ? (e) => {
3876
3889
  // Don't trigger row click if clicking on actions cell
3877
3890
  if ((e.target as HTMLElement).closest('[data-actions-cell]')) {
@@ -3986,7 +3999,7 @@ function DataTableImpl<T extends RowData>({
3986
3999
  return (
3987
4000
  <TableCell
3988
4001
  key={cell.id}
3989
- className={responsiveClass(priority, columnMeta?.hidden) + (isStickyCell ? ` md:sticky md:left-0 md:z-10 md:bg-background ${STICKY_LEFT_SHADOW_CLASS}` : '')}
4002
+ className={responsiveClass(priority, columnMeta?.hidden) + (isStickyCell ? ` md:sticky md:left-0 md:z-10 ${stickyBodySurfaceClass} ${STICKY_LEFT_SHADOW_CLASS}` : '')}
3990
4003
  style={typeof sizedWidth === 'number' ? { width: sizedWidth, minWidth: sizedWidth, maxWidth: sizedWidth } : undefined}
3991
4004
  >
3992
4005
  {expandAffordance ? (
@@ -4002,7 +4015,7 @@ function DataTableImpl<T extends RowData>({
4002
4015
  <TableCell
4003
4016
  className={cn(
4004
4017
  actionsColumnAlign === 'center' ? 'text-center whitespace-nowrap' : 'text-right whitespace-nowrap',
4005
- stickyActionsColumn && `md:sticky md:right-0 md:z-10 md:bg-background ${STICKY_RIGHT_SHADOW_CLASS}`,
4018
+ stickyActionsColumn && `md:sticky md:right-0 md:z-10 ${stickyBodySurfaceClass} ${STICKY_RIGHT_SHADOW_CLASS}`,
4006
4019
  )}
4007
4020
  data-actions-cell
4008
4021
  >
@@ -28,7 +28,7 @@ const tokensOf = (el: Element | null): string[] =>
28
28
  // `md` breakpoint up, so narrow viewports fall back to the documented plain
29
29
  // horizontal-scroll behavior where every column can be swiped into view.
30
30
  describe('DataTable sticky columns are viewport-gated', () => {
31
- function renderStickyTable() {
31
+ function renderStickyTable({ embedded = false }: { embedded?: boolean } = {}) {
32
32
  const columns: ColumnDef<Row>[] = [
33
33
  { accessorKey: 'title', header: 'Title' },
34
34
  { accessorKey: 'status', header: 'Status' },
@@ -40,6 +40,7 @@ describe('DataTable sticky columns are viewport-gated', () => {
40
40
  <DataTable
41
41
  columns={columns}
42
42
  data={[{ id: '1', title: 'Solar rollout', status: 'open' }]}
43
+ embedded={embedded}
43
44
  stickyFirstColumn
44
45
  stickyActionsColumn
45
46
  rowActions={() => (
@@ -62,7 +63,7 @@ describe('DataTable sticky columns are viewport-gated', () => {
62
63
  for (const cell of [firstHeader, firstBodyCell]) {
63
64
  const tokens = tokensOf(cell)
64
65
  expect(tokens).toEqual(
65
- expect.arrayContaining(['md:sticky', 'md:left-0', 'md:bg-background', 'md:after:absolute']),
66
+ expect.arrayContaining(['md:sticky', 'md:left-0', 'md:bg-card', 'md:after:absolute']),
66
67
  )
67
68
  expect(tokens).not.toContain('sticky')
68
69
  expect(tokens).not.toContain('left-0')
@@ -104,7 +105,7 @@ describe('DataTable sticky columns are viewport-gated', () => {
104
105
  for (const cell of [actionsHeader, actionsBodyCell]) {
105
106
  const tokens = tokensOf(cell)
106
107
  expect(tokens).toEqual(
107
- expect.arrayContaining(['md:sticky', 'md:right-0', 'md:bg-background', 'md:before:absolute']),
108
+ expect.arrayContaining(['md:sticky', 'md:right-0', 'md:bg-card', 'md:before:absolute']),
108
109
  )
109
110
  expect(tokens).not.toContain('sticky')
110
111
  expect(tokens).not.toContain('right-0')
@@ -115,4 +116,62 @@ describe('DataTable sticky columns are viewport-gated', () => {
115
116
  queryClient.clear()
116
117
  }
117
118
  })
119
+
120
+ // A non-embedded table sits on its `bg-card` container. Pinned cells used to
121
+ // paint `bg-background`, a darker band in the dark theme, and their opaque
122
+ // fill hid the header strip and the row-hover tint behind them (#7414).
123
+ it('paints pinned cells with the card surface and carries the header strip and row hover through', () => {
124
+ const { container, queryClient } = renderStickyTable()
125
+ try {
126
+ const headerCells = Array.from(container.querySelectorAll('thead th'))
127
+ const pinnedHeaders = [headerCells[0], headerCells[headerCells.length - 1]]
128
+ const bodyRow = container.querySelector('tbody tr')
129
+ const pinnedBodyCells = [
130
+ container.querySelector('tbody tr td'),
131
+ container.querySelector('tbody tr td[data-actions-cell]'),
132
+ ]
133
+
134
+ for (const cell of pinnedHeaders) {
135
+ const tokens = tokensOf(cell)
136
+ expect(tokens).toEqual(
137
+ expect.arrayContaining(['md:bg-card', 'md:bg-linear-to-r', 'md:from-muted/40', 'md:to-muted/40']),
138
+ )
139
+ expect(tokens).not.toContain('md:bg-background')
140
+ }
141
+
142
+ expect(tokensOf(bodyRow)).toContain('group/row')
143
+ for (const cell of pinnedBodyCells) {
144
+ const tokens = tokensOf(cell)
145
+ expect(tokens).toEqual(
146
+ expect.arrayContaining([
147
+ 'md:bg-card',
148
+ 'md:group-hover/row:bg-linear-to-r',
149
+ 'md:group-hover/row:from-muted/30',
150
+ 'md:group-hover/row:to-muted/30',
151
+ ]),
152
+ )
153
+ expect(tokens).not.toContain('md:bg-background')
154
+ }
155
+ } finally {
156
+ queryClient.clear()
157
+ }
158
+ })
159
+
160
+ it('keeps the page surface on pinned cells of an embedded table but still carries the tints', () => {
161
+ const { container, queryClient } = renderStickyTable({ embedded: true })
162
+ try {
163
+ const actionsHeader = Array.from(container.querySelectorAll('thead th')).pop() ?? null
164
+ const actionsBodyCell = container.querySelector('tbody tr td[data-actions-cell]')
165
+
166
+ expect(tokensOf(actionsHeader)).toEqual(
167
+ expect.arrayContaining(['md:bg-background', 'md:from-muted/40']),
168
+ )
169
+ expect(tokensOf(actionsBodyCell)).toEqual(
170
+ expect.arrayContaining(['md:bg-background', 'md:group-hover/row:from-muted/30']),
171
+ )
172
+ expect(tokensOf(actionsBodyCell)).not.toContain('md:bg-card')
173
+ } finally {
174
+ queryClient.clear()
175
+ }
176
+ })
118
177
  })