@exxatdesignux/ui 0.2.16 → 0.2.18
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/CHANGELOG.md +26 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +149 -4
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/accessibility.md +142 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/coach-marks.md +169 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +382 -0
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +38 -0
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +56 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +19 -0
- package/consumer-extras/patterns/data-views-pattern.md +2 -0
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +57 -0
- package/consumer-extras/patterns/shell-surface-elevation-pattern.md +52 -0
- package/package.json +3 -3
- package/src/components/ui/banner.tsx +2 -0
- package/src/components/ui/chart.tsx +57 -2
- package/src/components/ui/sidebar.tsx +3 -2
- package/src/globals.css +65 -14
- package/src/theme.css +3 -3
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +1 -1
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -1
- package/template/.cursor/rules/exxat-mono-ids.mdc +30 -0
- package/template/AGENTS.md +27 -17
- package/template/app/(app)/data-list/page.tsx +2 -2
- package/template/app/(app)/error.tsx +22 -6
- package/template/app/(app)/layout.tsx +13 -6
- package/template/app/(app)/question-bank/layout.tsx +18 -5
- package/template/app/(app)/question-bank/new/page.tsx +58 -0
- package/template/app/global-error.tsx +63 -0
- package/template/app/globals.css +151 -14
- package/template/app/layout.tsx +43 -5
- package/template/components/app-sidebar.tsx +68 -33
- package/template/components/ask-leo-sidebar.tsx +0 -2
- package/template/components/brand-color-picker.tsx +344 -0
- package/template/components/compliance-list-view.tsx +33 -51
- package/template/components/compliance-table.tsx +4 -0
- package/template/components/data-table/index.tsx +99 -91
- package/template/components/data-table/pagination.tsx +0 -1
- package/template/components/data-table/types.ts +4 -1
- package/template/components/data-table/use-table-state.ts +276 -100
- package/template/components/data-views/data-row-list.tsx +183 -0
- package/template/components/data-views/index.ts +7 -3
- package/template/components/data-views/os-folder-glyph.tsx +8 -0
- package/template/components/dev-chunk-load-recovery.tsx +41 -0
- package/template/components/export-drawer.tsx +1 -1
- package/template/components/exxat-product-logo.tsx +168 -317
- package/template/components/invite-collaborators-drawer.tsx +5 -3
- package/template/components/key-metrics.tsx +122 -62
- package/template/components/new-placement-form.tsx +4 -2
- package/template/components/new-question-composer.tsx +2208 -0
- package/template/components/page-breadcrumb-trail.tsx +131 -0
- package/template/components/page-header.tsx +2 -1
- package/template/components/{data-views/placement-board-card.tsx → placement-board-card.tsx} +2 -2
- package/template/components/placement-detail.tsx +1 -1
- package/template/components/placements-board-view.tsx +1 -1
- package/template/components/{data-list-client.tsx → placements-client.tsx} +9 -7
- package/template/components/placements-list-view.tsx +19 -133
- package/template/components/{data-list-table-cells.test.tsx → placements-table-cells.test.tsx} +2 -2
- package/template/components/{data-list-table-cells.tsx → placements-table-cells.tsx} +1 -1
- package/template/components/placements-table-columns.tsx +2 -2
- package/template/components/{data-list-table.tsx → placements-table.tsx} +42 -66
- package/template/components/product-switcher.tsx +24 -7
- package/template/components/product-wordmark.tsx +282 -0
- package/template/components/question-bank-client.tsx +20 -2
- package/template/components/question-bank-hub-client.tsx +105 -115
- package/template/components/question-bank-list-view.tsx +30 -54
- package/template/components/question-bank-new-folder-sheet.tsx +1 -1
- package/template/components/question-bank-secondary-nav.tsx +0 -3
- package/template/components/question-bank-table.tsx +19 -6
- package/template/components/rotations-empty-state.tsx +3 -0
- package/template/components/secondary-panel.tsx +23 -3
- package/template/components/settings-appearance-card.tsx +584 -141
- package/template/components/sidebar-shell.tsx +2 -1
- package/template/components/site-header.tsx +36 -31
- package/template/components/sites-list-view.tsx +31 -36
- package/template/components/sites-table.tsx +4 -0
- package/template/components/table-properties/drawer-button.tsx +38 -20
- package/template/components/table-properties/drawer.tsx +17 -14
- package/template/components/team-client.tsx +1 -1
- package/template/components/team-list-view.tsx +34 -50
- package/template/components/team-table.tsx +8 -3
- package/template/components/templates/list-page.tsx +12 -9
- package/template/components/templates/nested-secondary-panel-shell.tsx +10 -4
- package/template/components/ui/dot-pattern.tsx +50 -26
- package/template/components/ui/leo-icon.tsx +23 -3
- package/template/contexts/product-context.tsx +70 -7
- package/template/contexts/system-banner-context.tsx +112 -4
- package/template/docs/data-views-pattern.md +2 -0
- package/template/docs/kpi-flat-band-pattern.md +57 -0
- package/template/docs/kpi-strip-max-four-pattern.md +1 -0
- package/template/docs/shell-surface-elevation-pattern.md +52 -0
- package/template/eslint.config.mjs +18 -0
- package/template/hooks/use-sidebar-reflow-zoom.ts +21 -11
- package/template/lib/chunk-load-error.ts +13 -0
- package/template/lib/conditional-rule-match.ts +87 -22
- package/template/lib/data-list-persistence.ts +57 -257
- package/template/lib/data-list-view.ts +6 -0
- package/template/lib/dev-log.test.ts +6 -5
- package/template/lib/exxat-palette.json +1462 -0
- package/template/lib/exxat-palette.ts +136 -0
- package/template/lib/list-page-table-properties.ts +1 -1
- package/template/lib/list-status-badges.ts +1 -1
- package/template/lib/mailto.ts +29 -0
- package/template/lib/placement-board-card-layout.ts +1 -1
- package/template/lib/product-brand.ts +268 -0
- package/template/lib/question-bank-authoring.ts +308 -0
- package/template/lib/question-bank-nav.ts +44 -0
- package/template/lib/raf-throttle.ts +45 -0
- package/template/lib/sidebar-state-cookie.ts +9 -0
- package/template/lib/table-state-lifecycle.ts +521 -0
- package/template/next.config.mjs +156 -0
- package/template/package.json +3 -3
- package/template/stores/app-store.ts +46 -1
|
@@ -27,6 +27,7 @@ import * as React from "react"
|
|
|
27
27
|
import { useTheme } from "next-themes"
|
|
28
28
|
import { createPortal } from "react-dom"
|
|
29
29
|
import { cn } from "@/lib/utils"
|
|
30
|
+
import { rafThrottle } from "@/lib/raf-throttle"
|
|
30
31
|
import { Button } from "@/components/ui/button"
|
|
31
32
|
import { Input } from "@/components/ui/input"
|
|
32
33
|
import { Kbd, KbdGroup } from "@/components/ui/kbd"
|
|
@@ -55,7 +56,8 @@ import {
|
|
|
55
56
|
TooltipTrigger,
|
|
56
57
|
} from "@/components/ui/tooltip"
|
|
57
58
|
import { OPERATOR_LABELS } from "@/components/table-properties/types"
|
|
58
|
-
import type { ActiveFilter
|
|
59
|
+
import type { ActiveFilter } from "@/components/table-properties/types"
|
|
60
|
+
import { getConditionalCellBackground } from "@/lib/conditional-rule-match"
|
|
59
61
|
import { formatYmdForDisplay } from "@/lib/date-filter"
|
|
60
62
|
import { FilterDateCalendar } from "@/components/data-table/filter-date-calendar"
|
|
61
63
|
import { FilterTextValueInput } from "@/components/data-table/filter-text-value-input"
|
|
@@ -80,26 +82,6 @@ function resolvedColumnLabel<TData>(col: ColumnDef<TData>): string {
|
|
|
80
82
|
return defaultColumnHeaderLabel(col.key) ?? col.key
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
function conditionalTextMatches(
|
|
84
|
-
cellVal: string,
|
|
85
|
-
needle: string,
|
|
86
|
-
op: "contains" | "not_contains",
|
|
87
|
-
textMask: FilterTextMask | undefined,
|
|
88
|
-
) {
|
|
89
|
-
const v = cellVal.trim()
|
|
90
|
-
const n = needle.trim()
|
|
91
|
-
if (!n) return op === "not_contains"
|
|
92
|
-
if (textMask === "phone" || textMask === "zip") {
|
|
93
|
-
const nd = n.replace(/\D/g, "")
|
|
94
|
-
const hay = v.replace(/\D/g, "")
|
|
95
|
-
if (!nd) return op === "not_contains"
|
|
96
|
-
const hit = hay.includes(nd)
|
|
97
|
-
return op === "contains" ? hit : !hit
|
|
98
|
-
}
|
|
99
|
-
const hit = v.toLowerCase().includes(n.toLowerCase())
|
|
100
|
-
return op === "contains" ? hit : !hit
|
|
101
|
-
}
|
|
102
|
-
|
|
103
85
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
86
|
// Internal sub-components
|
|
105
87
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -734,23 +716,29 @@ function useBulkBarFixedToTableScrollEl(
|
|
|
734
716
|
})
|
|
735
717
|
}
|
|
736
718
|
apply()
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
719
|
+
// rAF-coalesce so a single frame handles bursts of capture-phase scroll
|
|
720
|
+
// events plus the ResizeObserver firing — instead of N getBoundingClientRect
|
|
721
|
+
// + setState per second.
|
|
722
|
+
const scheduled = rafThrottle(apply)
|
|
723
|
+
const ro = new ResizeObserver(scheduled)
|
|
740
724
|
ro.observe(el)
|
|
741
|
-
window.addEventListener("resize",
|
|
742
|
-
window.addEventListener("scroll",
|
|
725
|
+
window.addEventListener("resize", scheduled, { passive: true })
|
|
726
|
+
window.addEventListener("scroll", scheduled, { passive: true, capture: true })
|
|
743
727
|
return () => {
|
|
728
|
+
scheduled.cancel()
|
|
744
729
|
ro.disconnect()
|
|
745
|
-
window.removeEventListener("resize",
|
|
746
|
-
window.removeEventListener("scroll",
|
|
730
|
+
window.removeEventListener("resize", scheduled)
|
|
731
|
+
window.removeEventListener("scroll", scheduled, { capture: true })
|
|
747
732
|
}
|
|
748
733
|
}, [active, fullWidth, scrollRef])
|
|
749
734
|
return style
|
|
750
735
|
}
|
|
751
736
|
|
|
752
737
|
function DataTableInner<TData extends Record<string, unknown>>({
|
|
753
|
-
data
|
|
738
|
+
// `data` / `defaultSort` flow into `useTableState` upstream; the inner table
|
|
739
|
+
// reads them via `state` and never directly here. Keep the prop slots so
|
|
740
|
+
// the public `DataTable<TData>` API stays unchanged.
|
|
741
|
+
data: _data,
|
|
754
742
|
columns,
|
|
755
743
|
getRowId: getRowIdProp,
|
|
756
744
|
getRowSelectionLabel,
|
|
@@ -758,7 +746,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
758
746
|
searchable = true,
|
|
759
747
|
emptyState,
|
|
760
748
|
onRowClick,
|
|
761
|
-
defaultSort,
|
|
749
|
+
defaultSort: _defaultSort,
|
|
762
750
|
toolbarSlot,
|
|
763
751
|
bulkActionsSlot,
|
|
764
752
|
addRowLabel = false,
|
|
@@ -769,7 +757,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
769
757
|
state,
|
|
770
758
|
}: DataTableInnerProps<TData>) {
|
|
771
759
|
const {
|
|
772
|
-
|
|
760
|
+
setSortRules,
|
|
773
761
|
sortKey, sortDir,
|
|
774
762
|
handleSortByKey,
|
|
775
763
|
addFilter,
|
|
@@ -777,7 +765,6 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
777
765
|
colMenuSearch, setColMenuSearch,
|
|
778
766
|
selected, setSelected, toggleRow, toggleAll, getRowId,
|
|
779
767
|
colWidths, startResize,
|
|
780
|
-
colOrder,
|
|
781
768
|
colPins, lockedPins,
|
|
782
769
|
pinColumn, unpinColumn,
|
|
783
770
|
colWrap, toggleWrap,
|
|
@@ -785,7 +772,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
785
772
|
handleDragStart, handleDragOver, handleDrop, handleDragEnd,
|
|
786
773
|
scrollRef, handleScroll, checkOverflow,
|
|
787
774
|
isOverflowing,
|
|
788
|
-
|
|
775
|
+
setHoveredRow,
|
|
789
776
|
rows, pagedRows, groupedRows,
|
|
790
777
|
effectivePins, displayCols,
|
|
791
778
|
isReflowViewport,
|
|
@@ -796,12 +783,19 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
796
783
|
setSheetOpen,
|
|
797
784
|
} = state
|
|
798
785
|
|
|
799
|
-
// Mount overflow check
|
|
786
|
+
// Mount overflow check + scrollport width for sticky group headers on horizontal scroll.
|
|
800
787
|
React.useEffect(() => {
|
|
801
|
-
|
|
788
|
+
const syncScrollport = () => {
|
|
789
|
+
const el = scrollRef.current
|
|
790
|
+
if (el) {
|
|
791
|
+
el.style.setProperty("--dt-scrollport-width", `${el.clientWidth}px`)
|
|
792
|
+
}
|
|
793
|
+
checkOverflow()
|
|
794
|
+
}
|
|
795
|
+
syncScrollport()
|
|
802
796
|
const el = scrollRef.current
|
|
803
797
|
if (!el) return
|
|
804
|
-
const ro = new ResizeObserver(
|
|
798
|
+
const ro = new ResizeObserver(syncScrollport)
|
|
805
799
|
ro.observe(el)
|
|
806
800
|
return () => ro.disconnect()
|
|
807
801
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -843,6 +837,24 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
843
837
|
const lastLeftPinKey = [...displayCols].reverse().find(c => effectivePins[c.key] === "left")?.key
|
|
844
838
|
const firstRightPinKey = displayCols.find(c => effectivePins[c.key] === "right")?.key
|
|
845
839
|
|
|
840
|
+
function floatingHeaderPinnedStyle(key: string): React.CSSProperties | undefined {
|
|
841
|
+
const pin = effectivePins[key]
|
|
842
|
+
if (!pin) return undefined
|
|
843
|
+
|
|
844
|
+
const visibleWidth =
|
|
845
|
+
typeof floatingHeaderStyle?.width === "number"
|
|
846
|
+
? floatingHeaderStyle.width
|
|
847
|
+
: tableWrapRef.current?.clientWidth ?? floatingHeaderTableWidth
|
|
848
|
+
const maxScroll = Math.max(0, floatingHeaderTableWidth - visibleWidth)
|
|
849
|
+
const translateX = pin === "left"
|
|
850
|
+
? headerScrollLeft
|
|
851
|
+
: headerScrollLeft - maxScroll
|
|
852
|
+
|
|
853
|
+
// The floating sticky header is horizontally translated as one table.
|
|
854
|
+
// Counter-translate pinned header cells so they remain locked to the viewport edge.
|
|
855
|
+
return { position: "relative", transform: `translateX(${translateX}px)` }
|
|
856
|
+
}
|
|
857
|
+
|
|
846
858
|
// Row IDs for the current visible rows
|
|
847
859
|
const allRowIds = rows.map((r, i) => getRowId(r, i, getRowIdProp))
|
|
848
860
|
const allSelected = rows.length > 0 && selected.size === rows.length
|
|
@@ -864,6 +876,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
864
876
|
const [headerIsStuck, setHeaderIsStuck] = React.useState(false)
|
|
865
877
|
const [headerScrollLeft, setHeaderScrollLeft] = React.useState(0)
|
|
866
878
|
const [floatingHeaderStyle, setFloatingHeaderStyle] = React.useState<React.CSSProperties | undefined>(undefined)
|
|
879
|
+
const [floatingHeaderTableWidth, setFloatingHeaderTableWidth] = React.useState(totalWidth)
|
|
867
880
|
const [isClient, setIsClient] = React.useState(false)
|
|
868
881
|
|
|
869
882
|
React.useEffect(() => {
|
|
@@ -888,11 +901,16 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
888
901
|
}
|
|
889
902
|
|
|
890
903
|
update()
|
|
891
|
-
|
|
892
|
-
|
|
904
|
+
// rAF-coalesce: capture-phase scroll fires for every ancestor (sidebar,
|
|
905
|
+
// dashboard panels, anchored sheets), so a single getBoundingClientRect
|
|
906
|
+
// per frame is more than enough to keep the sticky header aligned.
|
|
907
|
+
const scheduled = rafThrottle(update)
|
|
908
|
+
window.addEventListener("scroll", scheduled, { passive: true, capture: true })
|
|
909
|
+
window.addEventListener("resize", scheduled, { passive: true })
|
|
893
910
|
return () => {
|
|
894
|
-
|
|
895
|
-
window.removeEventListener("
|
|
911
|
+
scheduled.cancel()
|
|
912
|
+
window.removeEventListener("scroll", scheduled, { capture: true })
|
|
913
|
+
window.removeEventListener("resize", scheduled)
|
|
896
914
|
}
|
|
897
915
|
}, [showColumnHeaders, rows.length, displayCols.length])
|
|
898
916
|
|
|
@@ -915,6 +933,11 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
915
933
|
const borderLeft = parseFloat(cs.borderLeftWidth) || 0
|
|
916
934
|
const borderRight = parseFloat(cs.borderRightWidth) || 0
|
|
917
935
|
const visibleWidth = Math.max(0, wrapEl.clientWidth - borderLeft - borderRight)
|
|
936
|
+
const renderedTableWidth = Math.max(
|
|
937
|
+
totalWidth,
|
|
938
|
+
visibleWidth,
|
|
939
|
+
wrapEl.querySelector("table")?.getBoundingClientRect().width ?? 0,
|
|
940
|
+
)
|
|
918
941
|
setFloatingHeaderStyle({
|
|
919
942
|
position: "fixed",
|
|
920
943
|
top: headerOffset,
|
|
@@ -922,18 +945,21 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
922
945
|
width: visibleWidth,
|
|
923
946
|
zIndex: 50,
|
|
924
947
|
})
|
|
948
|
+
setFloatingHeaderTableWidth(renderedTableWidth)
|
|
925
949
|
setHeaderScrollLeft(wrapEl.scrollLeft)
|
|
926
950
|
}
|
|
927
951
|
|
|
928
952
|
apply()
|
|
929
|
-
const
|
|
953
|
+
const scheduled = rafThrottle(apply)
|
|
954
|
+
const ro = new ResizeObserver(scheduled)
|
|
930
955
|
ro.observe(wrapEl)
|
|
931
|
-
window.addEventListener("scroll",
|
|
932
|
-
window.addEventListener("resize",
|
|
956
|
+
window.addEventListener("scroll", scheduled, { passive: true, capture: true })
|
|
957
|
+
window.addEventListener("resize", scheduled, { passive: true })
|
|
933
958
|
return () => {
|
|
959
|
+
scheduled.cancel()
|
|
934
960
|
ro.disconnect()
|
|
935
|
-
window.removeEventListener("scroll",
|
|
936
|
-
window.removeEventListener("resize",
|
|
961
|
+
window.removeEventListener("scroll", scheduled, { capture: true })
|
|
962
|
+
window.removeEventListener("resize", scheduled)
|
|
937
963
|
}
|
|
938
964
|
}, [headerIsStuck, showColumnHeaders, totalWidth, displayCols.length])
|
|
939
965
|
|
|
@@ -968,7 +994,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
968
994
|
<div style={{ transform: `translateX(${-headerScrollLeft}px)` }}>
|
|
969
995
|
<table
|
|
970
996
|
className="w-full text-sm border-separate border-spacing-0"
|
|
971
|
-
style={{ tableLayout: "fixed", width:
|
|
997
|
+
style={{ tableLayout: "fixed", width: floatingHeaderTableWidth }}
|
|
972
998
|
>
|
|
973
999
|
<colgroup>
|
|
974
1000
|
{displayCols.map(col => (
|
|
@@ -984,6 +1010,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
984
1010
|
<th
|
|
985
1011
|
key={col.key}
|
|
986
1012
|
scope="col"
|
|
1013
|
+
style={floatingHeaderPinnedStyle(col.key)}
|
|
987
1014
|
className={cn(
|
|
988
1015
|
"h-9 px-3 text-left align-middle select-none",
|
|
989
1016
|
"text-xs font-medium text-muted-foreground tracking-wide",
|
|
@@ -992,6 +1019,8 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
992
1019
|
? "border-r border-border last:border-r-0"
|
|
993
1020
|
: "last:border-r-0"),
|
|
994
1021
|
isPinned ? "z-40" : "z-30",
|
|
1022
|
+
isPinned && "relative",
|
|
1023
|
+
isEdgePinCol && stickyShadow(effectivePins[col.key]),
|
|
995
1024
|
)}
|
|
996
1025
|
>
|
|
997
1026
|
<div className="flex items-center justify-between gap-1 min-w-0">
|
|
@@ -1058,7 +1087,11 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
1058
1087
|
>
|
|
1059
1088
|
<table
|
|
1060
1089
|
className="w-full text-sm border-separate border-spacing-0"
|
|
1061
|
-
style={{
|
|
1090
|
+
style={{
|
|
1091
|
+
tableLayout: "fixed",
|
|
1092
|
+
minWidth: totalWidth,
|
|
1093
|
+
width: headerIsStuck ? floatingHeaderTableWidth : undefined,
|
|
1094
|
+
}}
|
|
1062
1095
|
>
|
|
1063
1096
|
<colgroup>
|
|
1064
1097
|
{displayCols.map(col => (
|
|
@@ -1306,25 +1339,25 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
1306
1339
|
<React.Fragment key={groupKey ?? "__all__"}>
|
|
1307
1340
|
{groupLabel && (
|
|
1308
1341
|
<tr>
|
|
1309
|
-
<td
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1342
|
+
<td colSpan={displayCols.length} className="p-0 border-b border-border bg-dt-group-bg">
|
|
1343
|
+
<div
|
|
1344
|
+
className={cn(
|
|
1345
|
+
"sticky left-0 z-[25] px-4 py-1.5 text-xs font-semibold text-muted-foreground tracking-wide bg-dt-group-bg select-none",
|
|
1346
|
+
!isReflowViewport && "shadow-[4px_0_8px_-4px_var(--sticky-edge-fade)]",
|
|
1347
|
+
)}
|
|
1348
|
+
style={{ width: "var(--dt-scrollport-width, 100%)" }}
|
|
1349
|
+
>
|
|
1350
|
+
{groupLabel}
|
|
1351
|
+
<span className="ml-2 font-normal normal-case opacity-60 tracking-normal">
|
|
1352
|
+
{groupRows.length} record{groupRows.length !== 1 ? "s" : ""}
|
|
1353
|
+
</span>
|
|
1354
|
+
</div>
|
|
1321
1355
|
</td>
|
|
1322
1356
|
</tr>
|
|
1323
1357
|
)}
|
|
1324
1358
|
{groupRows.map((row, rowIndex) => {
|
|
1325
1359
|
const rowId = getRowId(row, rowIndex, getRowIdProp)
|
|
1326
1360
|
const isSelected = selected.has(rowId)
|
|
1327
|
-
const isHovered = hoveredRow === rowId
|
|
1328
1361
|
const rowClickable = Boolean(onRowClick) || selectable
|
|
1329
1362
|
function handleRowClick(e: React.MouseEvent<HTMLTableRowElement>) {
|
|
1330
1363
|
if (!rowClickable) return
|
|
@@ -1375,37 +1408,12 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
1375
1408
|
]
|
|
1376
1409
|
)
|
|
1377
1410
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
const textMask =
|
|
1385
|
-
ruleCol?.filter?.type === "text" ? ruleCol.filter.textMask : undefined
|
|
1386
|
-
switch (rule.operator) {
|
|
1387
|
-
case "is":
|
|
1388
|
-
return rule.values.length > 0 && rule.values.includes(v)
|
|
1389
|
-
case "is_not":
|
|
1390
|
-
return rule.values.length > 0 && !rule.values.includes(v)
|
|
1391
|
-
case "contains":
|
|
1392
|
-
return (
|
|
1393
|
-
rule.values.length > 0 &&
|
|
1394
|
-
rule.values.some(val =>
|
|
1395
|
-
conditionalTextMatches(v, val, "contains", textMask),
|
|
1396
|
-
)
|
|
1397
|
-
)
|
|
1398
|
-
case "not_contains":
|
|
1399
|
-
return (
|
|
1400
|
-
rule.values.length > 0 &&
|
|
1401
|
-
!rule.values.some(val =>
|
|
1402
|
-
conditionalTextMatches(v, val, "contains", textMask),
|
|
1403
|
-
)
|
|
1404
|
-
)
|
|
1405
|
-
default:
|
|
1406
|
-
return false
|
|
1407
|
-
}
|
|
1408
|
-
})?.bgColor
|
|
1411
|
+
const conditionalBg = getConditionalCellBackground(
|
|
1412
|
+
row,
|
|
1413
|
+
col.key,
|
|
1414
|
+
conditionalRules,
|
|
1415
|
+
columns,
|
|
1416
|
+
)
|
|
1409
1417
|
|
|
1410
1418
|
const tdStyle = conditionalBg
|
|
1411
1419
|
? { ...cs, background: conditionalBg }
|
|
@@ -49,7 +49,10 @@ export interface ColumnDef<TData> {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
// `TData` is part of the public surface so callers can write
|
|
53
|
+
// `CellContext<Placement>` for symmetry with column-def renderers, even
|
|
54
|
+
// though the interface body doesn't currently reference it.
|
|
55
|
+
export interface CellContext<_TData> {
|
|
53
56
|
rowIndex: number
|
|
54
57
|
selected: boolean
|
|
55
58
|
onSelect: (selected: boolean) => void
|