@dashforge/tw 0.6.0-beta → 0.8.0-beta
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 +451 -0
- package/THEME-AUDIT.md +175 -0
- package/dist/index.esm.js +2009 -87
- package/dist/src/components/Accordion/accordion.variants.d.ts.map +1 -1
- package/dist/src/components/Box/box.variants.d.ts.map +1 -1
- package/dist/src/components/DataGrid/DataGrid.d.ts +29 -0
- package/dist/src/components/DataGrid/DataGrid.d.ts.map +1 -0
- package/dist/src/components/DataGrid/dataGrid.types.d.ts +214 -0
- package/dist/src/components/DataGrid/dataGrid.types.d.ts.map +1 -0
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts +222 -0
- package/dist/src/components/DataGrid/dataGrid.variants.d.ts.map +1 -0
- package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts +23 -0
- package/dist/src/components/DataGrid/filters/ColumnFilters.d.ts.map +1 -0
- package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts +50 -0
- package/dist/src/components/DataGrid/reorder/useColumnReorder.d.ts.map +1 -0
- package/dist/src/components/DataGrid/resize/useColumnResize.d.ts +32 -0
- package/dist/src/components/DataGrid/resize/useColumnResize.d.ts.map +1 -0
- package/dist/src/components/DataGrid/visibility/ColumnVisibilityDialog.d.ts +33 -0
- package/dist/src/components/DataGrid/visibility/ColumnVisibilityDialog.d.ts.map +1 -0
- package/dist/src/components/Dialog/dialog.variants.d.ts.map +1 -1
- package/dist/src/components/Divider/divider.variants.d.ts.map +1 -1
- package/dist/src/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/src/components/Pagination/pagination.variants.d.ts.map +1 -1
- package/dist/src/components/Popover/popover.variants.d.ts.map +1 -1
- package/dist/src/components/Skeleton/skeleton.variants.d.ts.map +1 -1
- package/dist/src/components/Table/Table.d.ts.map +1 -1
- package/dist/src/components/Table/table.types.d.ts +77 -5
- package/dist/src/components/Table/table.types.d.ts.map +1 -1
- package/dist/src/components/Tabs/tabs.variants.d.ts.map +1 -1
- package/dist/src/components/Tooltip/tooltip.variants.d.ts.map +1 -1
- package/dist/src/components/Typography/typography.variants.d.ts.map +1 -1
- package/dist/src/components/_shared/data/getNestedValue.d.ts.map +1 -0
- package/dist/src/components/{Table/_internal → _shared/data}/useColumnAutoDetect.d.ts +1 -1
- package/dist/src/components/_shared/data/useColumnAutoDetect.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useControllableState.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useDebouncedValue.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useTableFilter.d.ts +36 -0
- package/dist/src/components/_shared/data/useTableFilter.d.ts.map +1 -0
- package/dist/src/components/{Table/_internal → _shared/data}/useTableSearch.d.ts +1 -1
- package/dist/src/components/_shared/data/useTableSearch.d.ts.map +1 -0
- package/dist/src/components/{Table/_internal → _shared/data}/useTableSelection.d.ts +1 -1
- package/dist/src/components/_shared/data/useTableSelection.d.ts.map +1 -0
- package/dist/src/components/{Table/_internal → _shared/data}/useTableSort.d.ts +1 -1
- package/dist/src/components/_shared/data/useTableSort.d.ts.map +1 -0
- package/dist/src/components/_shared/data/useVirtualizer.d.ts +107 -0
- package/dist/src/components/_shared/data/useVirtualizer.d.ts.map +1 -0
- package/dist/src/index.d.ts +6 -3
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/components/Accordion/accordion.variants.ts +5 -3
- package/src/components/Box/Box.test.tsx +32 -11
- package/src/components/Box/box.variants.ts +23 -4
- package/src/components/DataGrid/DataGrid.test.tsx +917 -0
- package/src/components/DataGrid/DataGrid.tsx +1199 -0
- package/src/components/DataGrid/dataGrid.types.ts +238 -0
- package/src/components/DataGrid/dataGrid.variants.ts +152 -0
- package/src/components/DataGrid/filters/ColumnFilters.tsx +432 -0
- package/src/components/DataGrid/reorder/useColumnReorder.test.ts +38 -0
- package/src/components/DataGrid/reorder/useColumnReorder.ts +144 -0
- package/src/components/DataGrid/resize/useColumnResize.ts +106 -0
- package/src/components/DataGrid/visibility/ColumnVisibilityDialog.tsx +200 -0
- package/src/components/Dialog/dialog.variants.ts +10 -6
- package/src/components/Divider/Divider.test.tsx +3 -1
- package/src/components/Divider/Divider.tsx +1 -1
- package/src/components/Divider/divider.variants.ts +2 -1
- package/src/components/Pagination/Pagination.tsx +3 -1
- package/src/components/Pagination/pagination.variants.ts +19 -13
- package/src/components/Popover/popover.variants.ts +10 -4
- package/src/components/Skeleton/skeleton.variants.ts +2 -1
- package/src/components/Table/Table.tsx +22 -8
- package/src/components/Table/table.types.ts +83 -5
- package/src/components/Tabs/tabs.variants.ts +19 -8
- package/src/components/Tooltip/tooltip.variants.ts +12 -2
- package/src/components/Typography/Typography.test.tsx +3 -1
- package/src/components/Typography/typography.variants.ts +6 -2
- package/src/components/{Table/_internal → _shared/data}/useColumnAutoDetect.test.ts +1 -1
- package/src/components/{Table/_internal → _shared/data}/useColumnAutoDetect.ts +1 -1
- package/src/components/_shared/data/useTableFilter.test.ts +166 -0
- package/src/components/_shared/data/useTableFilter.ts +118 -0
- package/src/components/{Table/_internal → _shared/data}/useTableSearch.test.ts +1 -1
- package/src/components/{Table/_internal → _shared/data}/useTableSearch.ts +1 -1
- package/src/components/{Table/_internal → _shared/data}/useTableSelection.ts +1 -1
- package/src/components/{Table/_internal → _shared/data}/useTableSort.test.ts +1 -1
- package/src/components/{Table/_internal → _shared/data}/useTableSort.ts +1 -1
- package/src/components/_shared/data/useVirtualizer.test.ts +158 -0
- package/src/components/_shared/data/useVirtualizer.ts +199 -0
- package/src/components/_shared/themeIdentity.test.ts +113 -0
- package/src/index.ts +20 -2
- package/dist/src/components/Table/_internal/getNestedValue.d.ts.map +0 -1
- package/dist/src/components/Table/_internal/useColumnAutoDetect.d.ts.map +0 -1
- package/dist/src/components/Table/_internal/useControllableState.d.ts.map +0 -1
- package/dist/src/components/Table/_internal/useDebouncedValue.d.ts.map +0 -1
- package/dist/src/components/Table/_internal/useTableFilter.d.ts +0 -13
- package/dist/src/components/Table/_internal/useTableFilter.d.ts.map +0 -1
- package/dist/src/components/Table/_internal/useTableSearch.d.ts.map +0 -1
- package/dist/src/components/Table/_internal/useTableSelection.d.ts.map +0 -1
- package/dist/src/components/Table/_internal/useTableSort.d.ts.map +0 -1
- package/src/components/Table/_internal/themeIdentity.test.ts +0 -84
- package/src/components/Table/_internal/useTableFilter.ts +0 -37
- /package/dist/src/components/{Table/_internal → _shared/data}/getNestedValue.d.ts +0 -0
- /package/dist/src/components/{Table/_internal → _shared/data}/useControllableState.d.ts +0 -0
- /package/dist/src/components/{Table/_internal → _shared/data}/useDebouncedValue.d.ts +0 -0
- /package/src/components/{Table/_internal → _shared/data}/getNestedValue.test.ts +0 -0
- /package/src/components/{Table/_internal → _shared/data}/getNestedValue.ts +0 -0
- /package/src/components/{Table/_internal → _shared/data}/useControllableState.ts +0 -0
- /package/src/components/{Table/_internal → _shared/data}/useDebouncedValue.ts +0 -0
- /package/src/components/{Table/_internal → _shared/data}/useTableSelection.test.ts +0 -0
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import { useMemo, forwardRef, useContext, useId, useRef, useEffect, useCallback, useState, Fragment, createContext, Children, isValidElement, cloneElement } from 'react';
|
|
2
|
+
import { useMemo, forwardRef, useContext, useId, useRef, useEffect, useCallback, useState, Fragment, createContext, Children, isValidElement, cloneElement, useLayoutEffect } from 'react';
|
|
3
3
|
import { Slot } from '@radix-ui/react-slot';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -5292,8 +5292,12 @@ const SnackbarContext = /*#__PURE__*/ createContext(null);
|
|
|
5292
5292
|
warning: 'text-warning-700 dark:text-warning-400',
|
|
5293
5293
|
danger: 'text-danger-700 dark:text-danger-400',
|
|
5294
5294
|
info: 'text-info-700 dark:text-info-400',
|
|
5295
|
-
|
|
5296
|
-
|
|
5295
|
+
// Neutral palette auto-inverts via the dashforgePreset() CSS-var
|
|
5296
|
+
// swap — no `dark:` variants. `text-neutral-900` resolves to
|
|
5297
|
+
// dark text in light mode and light text in dark mode; same
|
|
5298
|
+
// applies to `text-neutral-600` (muted).
|
|
5299
|
+
neutral: 'text-neutral-900',
|
|
5300
|
+
muted: 'text-neutral-600'
|
|
5297
5301
|
},
|
|
5298
5302
|
/*
|
|
5299
5303
|
* `weight` overrides the variant's default weight. When unset, the
|
|
@@ -5494,7 +5498,13 @@ Typography.displayName = 'Typography';
|
|
|
5494
5498
|
*/ variant: {
|
|
5495
5499
|
plain: '',
|
|
5496
5500
|
outlined: 'border',
|
|
5497
|
-
|
|
5501
|
+
// `bg-white` is hardcoded (does NOT auto-invert via CSS vars),
|
|
5502
|
+
// so the `dark:` variant is required. Target `dark:bg-neutral-100`
|
|
5503
|
+
// resolves to `#171717` via the inversion → one elevation tier
|
|
5504
|
+
// above the page surface (`bg-neutral-50` = `#0a0a0a`) in dark
|
|
5505
|
+
// mode. `dark:bg-neutral-900` was wrong (resolved to near-white
|
|
5506
|
+
// in dark mode — overpowering elevation).
|
|
5507
|
+
elevated: 'bg-white dark:bg-neutral-100',
|
|
5498
5508
|
soft: '',
|
|
5499
5509
|
solid: ''
|
|
5500
5510
|
},
|
|
@@ -5666,7 +5676,11 @@ Typography.displayName = 'Typography';
|
|
|
5666
5676
|
{
|
|
5667
5677
|
variant: 'outlined',
|
|
5668
5678
|
color: 'neutral',
|
|
5669
|
-
|
|
5679
|
+
// border-neutral-200 auto-inverts via CSS var swap (no `dark:`
|
|
5680
|
+
// needed). bg-white is hardcoded — `dark:bg-neutral-100` keeps
|
|
5681
|
+
// elevation tier above page surface in dark mode (NOT
|
|
5682
|
+
// `dark:bg-neutral-900` which renders near-white).
|
|
5683
|
+
class: 'border-neutral-200 bg-white dark:bg-neutral-100'
|
|
5670
5684
|
},
|
|
5671
5685
|
// ─── soft × color ─────────────────────────────────────────────────
|
|
5672
5686
|
{
|
|
@@ -5702,7 +5716,9 @@ Typography.displayName = 'Typography';
|
|
|
5702
5716
|
{
|
|
5703
5717
|
variant: 'soft',
|
|
5704
5718
|
color: 'neutral',
|
|
5705
|
-
|
|
5719
|
+
// Both neutral classes auto-invert via the dashforgePreset() CSS
|
|
5720
|
+
// var swap — no `dark:` variants needed.
|
|
5721
|
+
class: 'bg-neutral-100 text-neutral-900'
|
|
5706
5722
|
},
|
|
5707
5723
|
// ─── solid × color ────────────────────────────────────────────────
|
|
5708
5724
|
{
|
|
@@ -5738,7 +5754,14 @@ Typography.displayName = 'Typography';
|
|
|
5738
5754
|
{
|
|
5739
5755
|
variant: 'solid',
|
|
5740
5756
|
color: 'neutral',
|
|
5741
|
-
|
|
5757
|
+
// Solid neutral is the high-emphasis "always-foreground accent"
|
|
5758
|
+
// variant. Both classes auto-invert: dark surface + light text
|
|
5759
|
+
// in light mode (`#171717` / `#fafafa`) flips to light surface +
|
|
5760
|
+
// dark text in dark mode (`#f5f5f5` / `#0a0a0a`). The
|
|
5761
|
+
// accent inverts WITH the page surface — consistent with the
|
|
5762
|
+
// identity rule. Previously used `text-white` (no auto-invert)
|
|
5763
|
+
// which forced double-inversion to keep readable.
|
|
5764
|
+
class: 'bg-neutral-900 text-neutral-50'
|
|
5742
5765
|
}
|
|
5743
5766
|
],
|
|
5744
5767
|
defaultVariants: {
|
|
@@ -6526,7 +6549,8 @@ Container.displayName = 'Container';
|
|
|
6526
6549
|
dotted: 'border-dotted'
|
|
6527
6550
|
},
|
|
6528
6551
|
color: {
|
|
6529
|
-
|
|
6552
|
+
// Neutral palette auto-inverts via CSS-var swap (no `dark:`).
|
|
6553
|
+
neutral: 'border-neutral-200',
|
|
6530
6554
|
primary: 'border-primary-300 dark:border-primary-700',
|
|
6531
6555
|
secondary: 'border-secondary-300 dark:border-secondary-700',
|
|
6532
6556
|
success: 'border-success-300 dark:border-success-700',
|
|
@@ -6668,7 +6692,7 @@ Container.displayName = 'Container';
|
|
|
6668
6692
|
className: cn(lineCommon, leftStubClass)
|
|
6669
6693
|
}),
|
|
6670
6694
|
jsx("span", {
|
|
6671
|
-
className: cn('shrink-0 text-sm text-neutral-500
|
|
6695
|
+
className: cn('shrink-0 text-sm text-neutral-500', labelPadding),
|
|
6672
6696
|
children: children
|
|
6673
6697
|
}),
|
|
6674
6698
|
jsx("span", {
|
|
@@ -6821,21 +6845,25 @@ VisuallyHidden.displayName = 'VisuallyHidden';
|
|
|
6821
6845
|
],
|
|
6822
6846
|
content: [
|
|
6823
6847
|
'fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2',
|
|
6824
|
-
|
|
6825
|
-
|
|
6848
|
+
// bg-white hardcoded (doesn't auto-invert) so `dark:` is needed.
|
|
6849
|
+
// Target `dark:bg-neutral-100` = #171717 in dark mode (one
|
|
6850
|
+
// elevation tier above page surface).
|
|
6851
|
+
'w-full bg-white dark:bg-neutral-100 rounded-lg shadow-xl',
|
|
6852
|
+
// border-neutral-200 auto-inverts — no `dark:` needed.
|
|
6853
|
+
'border border-neutral-200',
|
|
6826
6854
|
'p-6 flex flex-col gap-4 max-h-[90vh]',
|
|
6827
6855
|
'focus:outline-none',
|
|
6828
6856
|
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
6829
6857
|
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
6830
6858
|
'motion-reduce:transition-none motion-reduce:duration-0'
|
|
6831
6859
|
],
|
|
6832
|
-
|
|
6833
|
-
|
|
6860
|
+
// All neutral text classes auto-invert via CSS-var swap.
|
|
6861
|
+
title: 'text-lg font-semibold leading-tight text-neutral-900',
|
|
6862
|
+
description: 'text-sm text-neutral-600',
|
|
6834
6863
|
closeButton: [
|
|
6835
6864
|
'absolute top-4 right-4 inline-flex items-center justify-center',
|
|
6836
6865
|
'h-8 w-8 rounded-md text-neutral-500 hover:text-neutral-900',
|
|
6837
|
-
'
|
|
6838
|
-
'hover:bg-neutral-100 dark:hover:bg-neutral-800',
|
|
6866
|
+
'hover:bg-neutral-100',
|
|
6839
6867
|
'focus:outline-none focus:ring-2 focus:ring-primary-500',
|
|
6840
6868
|
'transition-colors'
|
|
6841
6869
|
],
|
|
@@ -6952,21 +6980,32 @@ const tabsVariants = tv({
|
|
|
6952
6980
|
variants: {
|
|
6953
6981
|
variant: {
|
|
6954
6982
|
underline: {
|
|
6955
|
-
|
|
6983
|
+
// Neutral palette auto-inverts via CSS-var swap.
|
|
6984
|
+
list: 'border-b border-neutral-200',
|
|
6956
6985
|
trigger: [
|
|
6957
|
-
'px-3 py-2 border-b-2 border-transparent text-neutral-600
|
|
6958
|
-
'hover:text-neutral-900
|
|
6986
|
+
'px-3 py-2 border-b-2 border-transparent text-neutral-600',
|
|
6987
|
+
'hover:text-neutral-900',
|
|
6988
|
+
// Primary palette does NOT auto-invert — `dark:` shift is
|
|
6989
|
+
// an intentional tone refinement (kept).
|
|
6959
6990
|
'data-[state=active]:border-primary-500 data-[state=active]:text-primary-700',
|
|
6960
6991
|
'dark:data-[state=active]:text-primary-400'
|
|
6961
6992
|
]
|
|
6962
6993
|
},
|
|
6963
6994
|
pill: {
|
|
6964
|
-
|
|
6995
|
+
// Pill list bg uses the elevation tier — auto-inverts.
|
|
6996
|
+
list: 'rounded-lg bg-neutral-100 p-1',
|
|
6965
6997
|
trigger: [
|
|
6966
|
-
'px-3 py-1.5 rounded-md text-neutral-600
|
|
6967
|
-
'hover:text-neutral-900
|
|
6968
|
-
|
|
6969
|
-
|
|
6998
|
+
'px-3 py-1.5 rounded-md text-neutral-600',
|
|
6999
|
+
'hover:text-neutral-900',
|
|
7000
|
+
// Active pill is one tier above the list bg
|
|
7001
|
+
// (`bg-neutral-100`-inverted = #171717 in dark).
|
|
7002
|
+
// `bg-white` is hardcoded so a `dark:` is required —
|
|
7003
|
+
// target `dark:bg-neutral-200` (= #262626 in dark) sits
|
|
7004
|
+
// ONE TIER above the list bg, preserving the elevation
|
|
7005
|
+
// semantic from light mode (where `bg-white` sits above
|
|
7006
|
+
// `bg-neutral-100` = #f5f5f5).
|
|
7007
|
+
'data-[state=active]:bg-white dark:data-[state=active]:bg-neutral-200',
|
|
7008
|
+
'data-[state=active]:text-neutral-900',
|
|
6970
7009
|
'data-[state=active]:shadow-sm'
|
|
6971
7010
|
]
|
|
6972
7011
|
}
|
|
@@ -7042,14 +7081,24 @@ const tooltipVariants = tv({
|
|
|
7042
7081
|
slots: {
|
|
7043
7082
|
content: [
|
|
7044
7083
|
'z-50 max-w-xs rounded-md px-3 py-1.5 text-xs font-medium',
|
|
7084
|
+
// Auto-invert via the dashforgePreset() CSS-var swap:
|
|
7085
|
+
// - Light mode: `bg-neutral-900` = #171717 (dark surface) +
|
|
7086
|
+
// `text-neutral-50` = #fafafa (light text) — DARK tooltip
|
|
7087
|
+
// on the light page (high contrast).
|
|
7088
|
+
// - Dark mode: `bg-neutral-900` flips to #f5f5f5 (light) +
|
|
7089
|
+
// `text-neutral-50` flips to #0a0a0a (dark text) — LIGHT
|
|
7090
|
+
// tooltip on the dark page (high contrast against page).
|
|
7091
|
+
// Adding `dark:bg-neutral-50 dark:text-neutral-900` (the old
|
|
7092
|
+
// pattern) would force the dark-mode tooltip back to a dark
|
|
7093
|
+
// surface — invisible against the dark page.
|
|
7045
7094
|
'bg-neutral-900 text-neutral-50',
|
|
7046
|
-
'dark:bg-neutral-50 dark:text-neutral-900',
|
|
7047
7095
|
'shadow-md',
|
|
7048
7096
|
'data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95',
|
|
7049
7097
|
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
7050
7098
|
'motion-reduce:transition-none motion-reduce:duration-0'
|
|
7051
7099
|
],
|
|
7052
|
-
|
|
7100
|
+
// Arrow tracks the content bg — auto-inverts the same way.
|
|
7101
|
+
arrow: 'fill-neutral-900'
|
|
7053
7102
|
}
|
|
7054
7103
|
});
|
|
7055
7104
|
|
|
@@ -7114,15 +7163,21 @@ const tooltipVariants = tv({
|
|
|
7114
7163
|
const popoverVariants = tv({
|
|
7115
7164
|
slots: {
|
|
7116
7165
|
content: [
|
|
7117
|
-
|
|
7118
|
-
'
|
|
7119
|
-
|
|
7166
|
+
// Neutral border auto-inverts via CSS-var swap.
|
|
7167
|
+
'z-50 rounded-md border border-neutral-200',
|
|
7168
|
+
// bg-white hardcoded (no auto-invert) so `dark:` is required.
|
|
7169
|
+
// Target `dark:bg-neutral-100` resolves to #171717 in dark mode
|
|
7170
|
+
// (one elevation tier above the page surface).
|
|
7171
|
+
'bg-white dark:bg-neutral-100 p-4 shadow-lg',
|
|
7172
|
+
// Text auto-inverts.
|
|
7173
|
+
'text-sm text-neutral-900',
|
|
7120
7174
|
'focus:outline-none',
|
|
7121
7175
|
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
|
7122
7176
|
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
7123
7177
|
'motion-reduce:transition-none motion-reduce:duration-0'
|
|
7124
7178
|
],
|
|
7125
|
-
|
|
7179
|
+
// Arrow follows content bg: `fill-white` in light + matching dark target.
|
|
7180
|
+
arrow: 'fill-white dark:fill-neutral-100'
|
|
7126
7181
|
}
|
|
7127
7182
|
});
|
|
7128
7183
|
|
|
@@ -7173,11 +7228,13 @@ const popoverVariants = tv({
|
|
|
7173
7228
|
const accordionVariants = tv({
|
|
7174
7229
|
slots: {
|
|
7175
7230
|
root: 'w-full',
|
|
7176
|
-
|
|
7231
|
+
// Neutral palette auto-inverts via the dashforgePreset() CSS-var
|
|
7232
|
+
// swap — no `dark:` variant needed (would double-invert).
|
|
7233
|
+
item: 'border-b border-neutral-200',
|
|
7177
7234
|
header: 'flex',
|
|
7178
7235
|
trigger: [
|
|
7179
7236
|
'flex flex-1 items-center justify-between py-3 text-left',
|
|
7180
|
-
'text-sm font-medium text-neutral-900
|
|
7237
|
+
'text-sm font-medium text-neutral-900',
|
|
7181
7238
|
'transition-all hover:underline',
|
|
7182
7239
|
'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:rounded',
|
|
7183
7240
|
'[&[data-state=open]>svg]:rotate-180',
|
|
@@ -7185,7 +7242,7 @@ const accordionVariants = tv({
|
|
|
7185
7242
|
'motion-reduce:transition-none'
|
|
7186
7243
|
],
|
|
7187
7244
|
content: [
|
|
7188
|
-
'overflow-hidden text-sm text-neutral-700
|
|
7245
|
+
'overflow-hidden text-sm text-neutral-700',
|
|
7189
7246
|
'data-[state=open]:animate-accordion-down',
|
|
7190
7247
|
'data-[state=closed]:animate-accordion-up',
|
|
7191
7248
|
'motion-reduce:animate-none'
|
|
@@ -7316,7 +7373,8 @@ const accordionVariants = tv({
|
|
|
7316
7373
|
slots: {
|
|
7317
7374
|
root: [
|
|
7318
7375
|
'block',
|
|
7319
|
-
|
|
7376
|
+
// Neutral palette auto-inverts via CSS-var swap (no `dark:`).
|
|
7377
|
+
'bg-neutral-200',
|
|
7320
7378
|
'motion-reduce:animate-none'
|
|
7321
7379
|
]
|
|
7322
7380
|
},
|
|
@@ -7430,28 +7488,34 @@ const accordionVariants = tv({
|
|
|
7430
7488
|
*/ const paginationVariants = tv({
|
|
7431
7489
|
slots: {
|
|
7432
7490
|
root: 'flex flex-wrap items-center gap-3',
|
|
7433
|
-
|
|
7491
|
+
// All neutral classes auto-invert via the dashforgePreset() CSS-var
|
|
7492
|
+
// swap. `bg-neutral-50` provides the button surface (matches page
|
|
7493
|
+
// surface; the border gives the separation). `hover:bg-neutral-100`
|
|
7494
|
+
// is the one-tier elevation that works in both modes.
|
|
7495
|
+
summary: 'text-neutral-600',
|
|
7434
7496
|
list: 'inline-flex items-center gap-1',
|
|
7435
7497
|
pageButton: [
|
|
7436
7498
|
'inline-flex items-center justify-center rounded-md',
|
|
7437
|
-
'border border-neutral-300
|
|
7438
|
-
'bg-
|
|
7439
|
-
'text-neutral-700
|
|
7440
|
-
'hover:bg-neutral-
|
|
7499
|
+
'border border-neutral-300',
|
|
7500
|
+
'bg-neutral-50',
|
|
7501
|
+
'text-neutral-700',
|
|
7502
|
+
'hover:bg-neutral-100',
|
|
7441
7503
|
'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
|
|
7442
7504
|
'transition-colors motion-reduce:transition-none',
|
|
7443
7505
|
'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none'
|
|
7444
7506
|
],
|
|
7445
7507
|
activeButton: [
|
|
7508
|
+
// Primary palette does NOT auto-invert — `dark:` shift is a
|
|
7509
|
+
// design choice for tone refinement (kept).
|
|
7446
7510
|
'bg-primary-500 text-white border-primary-500',
|
|
7447
7511
|
'hover:bg-primary-600 dark:hover:bg-primary-400'
|
|
7448
7512
|
],
|
|
7449
7513
|
navButton: [
|
|
7450
7514
|
'inline-flex items-center justify-center rounded-md',
|
|
7451
|
-
'border border-neutral-300
|
|
7452
|
-
'bg-
|
|
7453
|
-
'text-neutral-700
|
|
7454
|
-
'hover:bg-neutral-
|
|
7515
|
+
'border border-neutral-300',
|
|
7516
|
+
'bg-neutral-50',
|
|
7517
|
+
'text-neutral-700',
|
|
7518
|
+
'hover:bg-neutral-100',
|
|
7455
7519
|
'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
|
|
7456
7520
|
'transition-colors motion-reduce:transition-none',
|
|
7457
7521
|
'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none'
|
|
@@ -7459,12 +7523,12 @@ const accordionVariants = tv({
|
|
|
7459
7523
|
ellipsis: 'inline-flex items-center justify-center text-neutral-500 px-1',
|
|
7460
7524
|
pageSizeSelector: [
|
|
7461
7525
|
'inline-flex items-center gap-2',
|
|
7462
|
-
'text-neutral-700
|
|
7526
|
+
'text-neutral-700'
|
|
7463
7527
|
],
|
|
7464
7528
|
jumpInput: [
|
|
7465
|
-
'rounded-md border border-neutral-300
|
|
7466
|
-
'bg-
|
|
7467
|
-
'text-neutral-900
|
|
7529
|
+
'rounded-md border border-neutral-300',
|
|
7530
|
+
'bg-neutral-50',
|
|
7531
|
+
'text-neutral-900',
|
|
7468
7532
|
'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
|
|
7469
7533
|
'transition-colors motion-reduce:transition-none'
|
|
7470
7534
|
]
|
|
@@ -7601,7 +7665,7 @@ function range(start, end) {
|
|
|
7601
7665
|
}, (_, i)=>start + i);
|
|
7602
7666
|
}
|
|
7603
7667
|
|
|
7604
|
-
const DEFAULT_LABELS$
|
|
7668
|
+
const DEFAULT_LABELS$2 = {
|
|
7605
7669
|
first: 'First',
|
|
7606
7670
|
prev: 'Previous',
|
|
7607
7671
|
next: 'Next',
|
|
@@ -7649,7 +7713,7 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [
|
|
|
7649
7713
|
*/ function Pagination(props) {
|
|
7650
7714
|
var _slotProps_root, _slotProps_summary, _slotProps_summary1, _slotProps_list, _slotProps_navButton, _slotProps_navButton1, _slotProps_navButton2, _slotProps_navButton3, _slotProps_pageSizeSelector, _slotProps_jumpInput;
|
|
7651
7715
|
const { page, pageSize, totalCount, onPageChange, onPageSizeChange, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, siblingCount = 1, boundaryCount = 1, showFirstLast = true, showJumpInput = true, disabled = false, labels: labelsProp, variant = 'default', size = 'md', sx, slotProps } = props;
|
|
7652
|
-
const labels = _extends({}, DEFAULT_LABELS$
|
|
7716
|
+
const labels = _extends({}, DEFAULT_LABELS$2, labelsProp);
|
|
7653
7717
|
const v = paginationVariants({
|
|
7654
7718
|
variant,
|
|
7655
7719
|
size
|
|
@@ -7807,7 +7871,9 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [
|
|
|
7807
7871
|
onChange: handlePageSize,
|
|
7808
7872
|
disabled: disabled,
|
|
7809
7873
|
"aria-label": labels.perPage,
|
|
7810
|
-
className: cn(
|
|
7874
|
+
className: cn(// Auto-invert via CSS-var swap — no `dark:` variants needed
|
|
7875
|
+
// on neutral palette.
|
|
7876
|
+
'rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500'),
|
|
7811
7877
|
children: pageSizeOptions.map((opt)=>jsx("option", {
|
|
7812
7878
|
value: opt,
|
|
7813
7879
|
children: opt
|
|
@@ -8193,28 +8259,99 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [
|
|
|
8193
8259
|
}
|
|
8194
8260
|
|
|
8195
8261
|
/**
|
|
8196
|
-
*
|
|
8197
|
-
*
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8262
|
+
* Coerce a value to a number for range comparison. Returns
|
|
8263
|
+
* `null` if not coercible.
|
|
8264
|
+
*/ function toNumber(value) {
|
|
8265
|
+
if (value == null) return null;
|
|
8266
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
8267
|
+
if (typeof value === 'bigint') return Number(value);
|
|
8268
|
+
if (typeof value === 'string') {
|
|
8269
|
+
const n = Number(value);
|
|
8270
|
+
return Number.isFinite(n) ? n : null;
|
|
8271
|
+
}
|
|
8272
|
+
return null;
|
|
8273
|
+
}
|
|
8274
|
+
/**
|
|
8275
|
+
* Coerce a value to a Date's epoch ms for date-range comparison.
|
|
8276
|
+
* Accepts `Date` instances + ISO 8601 strings. Returns `null` if
|
|
8277
|
+
* not coercible.
|
|
8278
|
+
*/ function toDateMs(value) {
|
|
8279
|
+
if (value == null) return null;
|
|
8280
|
+
if (value instanceof Date) return value.getTime();
|
|
8281
|
+
if (typeof value === 'string') {
|
|
8282
|
+
const d = new Date(value);
|
|
8283
|
+
const t = d.getTime();
|
|
8284
|
+
return Number.isNaN(t) ? null : t;
|
|
8285
|
+
}
|
|
8286
|
+
return null;
|
|
8287
|
+
}
|
|
8288
|
+
/**
|
|
8289
|
+
* Test one row × one filter. Returns `true` if the row passes the
|
|
8290
|
+
* filter. The operator decides the predicate:
|
|
8291
|
+
*
|
|
8292
|
+
* - `contains` → substring match on stringified value (text filter)
|
|
8293
|
+
* - `equals` → strict equality (boolean filter mostly)
|
|
8294
|
+
* - `between` → `value: [min, max]` tuple. Each end can be `null`
|
|
8295
|
+
* for an open range. Used by number range + date range filters.
|
|
8296
|
+
*
|
|
8297
|
+
* Empty filter is a no-op (passes every row).
|
|
8298
|
+
*/ function passesFilter(rowValue, op, filterValue) {
|
|
8299
|
+
switch(op){
|
|
8300
|
+
case 'contains':
|
|
8301
|
+
{
|
|
8302
|
+
const needle = typeof filterValue === 'string' ? filterValue.trim().toLowerCase() : '';
|
|
8303
|
+
if (!needle) return true;
|
|
8304
|
+
return stringifyForSearch(rowValue).toLowerCase().includes(needle);
|
|
8305
|
+
}
|
|
8306
|
+
case 'equals':
|
|
8307
|
+
{
|
|
8308
|
+
if (filterValue == null) return true;
|
|
8309
|
+
return rowValue === filterValue;
|
|
8310
|
+
}
|
|
8311
|
+
case 'between':
|
|
8312
|
+
{
|
|
8313
|
+
if (!Array.isArray(filterValue) || filterValue.length !== 2) return true;
|
|
8314
|
+
const [min, max] = filterValue;
|
|
8315
|
+
if (min == null && max == null) return true;
|
|
8316
|
+
// Try number range first.
|
|
8317
|
+
const rowNum = toNumber(rowValue);
|
|
8318
|
+
const minNum = min == null ? null : toNumber(min);
|
|
8319
|
+
const maxNum = max == null ? null : toNumber(max);
|
|
8320
|
+
if (rowNum != null && (minNum != null || maxNum != null)) {
|
|
8321
|
+
if (minNum != null && rowNum < minNum) return false;
|
|
8322
|
+
if (maxNum != null && rowNum > maxNum) return false;
|
|
8323
|
+
return true;
|
|
8324
|
+
}
|
|
8325
|
+
// Fall through to date range.
|
|
8326
|
+
const rowMs = toDateMs(rowValue);
|
|
8327
|
+
const minMs = min == null ? null : toDateMs(min);
|
|
8328
|
+
const maxMs = max == null ? null : toDateMs(max);
|
|
8329
|
+
if (rowMs != null && (minMs != null || maxMs != null)) {
|
|
8330
|
+
if (minMs != null && rowMs < minMs) return false;
|
|
8331
|
+
if (maxMs != null && rowMs > maxMs) return false;
|
|
8332
|
+
return true;
|
|
8333
|
+
}
|
|
8334
|
+
// If neither coercion worked, let the row pass (defensive).
|
|
8335
|
+
return true;
|
|
8336
|
+
}
|
|
8337
|
+
default:
|
|
8338
|
+
return true;
|
|
8339
|
+
}
|
|
8340
|
+
}
|
|
8341
|
+
/**
|
|
8342
|
+
* Filters `rows` by the per-column filter model. AND semantics
|
|
8343
|
+
* across columns — a row must pass EVERY active filter.
|
|
8344
|
+
*
|
|
8345
|
+
* Each filter item specifies an `op` (`'contains'` / `'equals'` /
|
|
8346
|
+
* `'between'`) and a `value` whose shape matches the operator
|
|
8347
|
+
* (string for contains; primitive for equals; `[min, max]` tuple
|
|
8348
|
+
* for between).
|
|
8204
8349
|
*/ function useTableFilter(rows, filterModel) {
|
|
8205
8350
|
return useMemo(()=>{
|
|
8206
8351
|
if (!filterModel || filterModel.length === 0) return rows;
|
|
8207
8352
|
return rows.filter((row)=>filterModel.every((filter)=>{
|
|
8208
8353
|
const value = getNestedValue(row, filter.field);
|
|
8209
|
-
|
|
8210
|
-
const needle = filter.value.trim().toLowerCase();
|
|
8211
|
-
if (!needle) return true; // empty filter == no-op
|
|
8212
|
-
switch(filter.op){
|
|
8213
|
-
case 'contains':
|
|
8214
|
-
return stringValue.includes(needle);
|
|
8215
|
-
default:
|
|
8216
|
-
return true;
|
|
8217
|
-
}
|
|
8354
|
+
return passesFilter(value, filter.op, filter.value);
|
|
8218
8355
|
}));
|
|
8219
8356
|
}, [
|
|
8220
8357
|
rows,
|
|
@@ -8510,7 +8647,7 @@ function inferType(value) {
|
|
|
8510
8647
|
return col.monospace === true;
|
|
8511
8648
|
}
|
|
8512
8649
|
|
|
8513
|
-
const DEFAULT_LABELS = {
|
|
8650
|
+
const DEFAULT_LABELS$1 = {
|
|
8514
8651
|
searchPlaceholder: 'Search...',
|
|
8515
8652
|
noData: 'No data',
|
|
8516
8653
|
loading: 'Loading…',
|
|
@@ -8526,10 +8663,23 @@ const DEFAULT_LABELS = {
|
|
|
8526
8663
|
filterColumn: 'Filter',
|
|
8527
8664
|
filterApply: 'Apply',
|
|
8528
8665
|
filterClear: 'Clear',
|
|
8666
|
+
filterMin: 'Min',
|
|
8667
|
+
filterMax: 'Max',
|
|
8668
|
+
filterFrom: 'From',
|
|
8669
|
+
filterTo: 'To',
|
|
8670
|
+
filterAll: 'All',
|
|
8671
|
+
filterTrue: 'True',
|
|
8672
|
+
filterFalse: 'False',
|
|
8529
8673
|
density: 'Density',
|
|
8530
8674
|
densityCompact: 'Compact',
|
|
8531
8675
|
densityComfortable: 'Comfortable',
|
|
8532
|
-
densitySpacious: 'Spacious'
|
|
8676
|
+
densitySpacious: 'Spacious',
|
|
8677
|
+
columnsButton: 'Columns',
|
|
8678
|
+
columnsTitle: 'Manage columns',
|
|
8679
|
+
columnsDescription: 'Show or hide columns. Required columns cannot be toggled.',
|
|
8680
|
+
columnsShowAll: 'Show all',
|
|
8681
|
+
columnsHideAll: 'Hide all',
|
|
8682
|
+
columnsDone: 'Done'
|
|
8533
8683
|
};
|
|
8534
8684
|
const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
8535
8685
|
/**
|
|
@@ -8556,7 +8706,7 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
|
8556
8706
|
var _slotProps_root, _slotProps_toolbar, _slotProps_search, _slotProps_scroll, _slotProps_table, _slotProps_thead, _slotProps_headerRow, _slotProps_headerCell, _slotProps_selectionCell, _slotProps_headerCell1, _slotProps_expandToggleCell, _slotProps_headerCell2, _slotProps_tbody, _slotProps_row, _slotProps_cell, _slotProps_emptyState, _slotProps_bulkActionFooter;
|
|
8557
8707
|
const { rows, cols, getRowId = DEFAULT_GET_ROW_ID, // State props (controllable)
|
|
8558
8708
|
sortModel: sortModelProp, onSortChange, defaultSortModel, enableSearch = false, searchQuery: searchQueryProp, onSearchQueryChange, searchPlaceholder, searchDebounceMs = 200, filterModel: filterModelProp, onFilterChange, rowSelection = 'none', selectedRowIds: selectedRowIdsProp, onSelectionChange, bulkActions, expandable, rowActions, loading = false, loadingRowCount = 5, emptyState, stickyHeader = true, caption, showCaption = false, access: tableAccess, labels: labelsProp, variant, size, density, sx, slotProps } = props;
|
|
8559
|
-
const labels = _extends({}, DEFAULT_LABELS, labelsProp);
|
|
8709
|
+
const labels = _extends({}, DEFAULT_LABELS$1, labelsProp);
|
|
8560
8710
|
const tableAccessState = useAccessState(tableAccess);
|
|
8561
8711
|
// ───── Controllable state machinery ─────
|
|
8562
8712
|
const [sortModel, setSortModel] = useControllableState({
|
|
@@ -8629,7 +8779,7 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
|
8629
8779
|
className: "sr-only",
|
|
8630
8780
|
children: labels.searchPlaceholder
|
|
8631
8781
|
}),
|
|
8632
|
-
jsx(SearchIcon, {
|
|
8782
|
+
jsx(SearchIcon$1, {
|
|
8633
8783
|
className: "ml-2 shrink-0 text-neutral-500"
|
|
8634
8784
|
}),
|
|
8635
8785
|
jsx("input", {
|
|
@@ -8681,10 +8831,10 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
|
8681
8831
|
}),
|
|
8682
8832
|
visibleCols.map((col)=>{
|
|
8683
8833
|
var _slotProps_headerCell;
|
|
8684
|
-
return jsx(HeaderCell, {
|
|
8834
|
+
return jsx(HeaderCell$1, {
|
|
8685
8835
|
col: col,
|
|
8686
8836
|
sortModel: sortModel,
|
|
8687
|
-
onSortClick: (e)=>handleSortClick(col, e, sortModel, setSortModel),
|
|
8837
|
+
onSortClick: (e)=>handleSortClick$1(col, e, sortModel, setSortModel),
|
|
8688
8838
|
inferredType: columnTypes.get(col.field),
|
|
8689
8839
|
labels: labels,
|
|
8690
8840
|
className: cn(v.headerCell(), slotProps == null ? void 0 : (_slotProps_headerCell = slotProps.headerCell) == null ? void 0 : _slotProps_headerCell.className),
|
|
@@ -8702,7 +8852,7 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
|
8702
8852
|
}),
|
|
8703
8853
|
jsx("tbody", {
|
|
8704
8854
|
className: cn(v.tbody(), slotProps == null ? void 0 : (_slotProps_tbody = slotProps.tbody) == null ? void 0 : _slotProps_tbody.className),
|
|
8705
|
-
children: loading ? renderLoadingRows({
|
|
8855
|
+
children: loading ? renderLoadingRows$1({
|
|
8706
8856
|
count: loadingRowCount,
|
|
8707
8857
|
visibleCols,
|
|
8708
8858
|
showSelectionColumn,
|
|
@@ -8710,7 +8860,7 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
|
8710
8860
|
showRowActionsColumn,
|
|
8711
8861
|
rowClass: cn(v.row(), slotProps == null ? void 0 : (_slotProps_row = slotProps.row) == null ? void 0 : _slotProps_row.className),
|
|
8712
8862
|
cellClass: cn(v.cell(), slotProps == null ? void 0 : (_slotProps_cell = slotProps.cell) == null ? void 0 : _slotProps_cell.className)
|
|
8713
|
-
}) : sortedRows.length === 0 ? renderEmptyState({
|
|
8863
|
+
}) : sortedRows.length === 0 ? renderEmptyState$1({
|
|
8714
8864
|
totalColumnCount,
|
|
8715
8865
|
emptyState,
|
|
8716
8866
|
fallback: labels.noData,
|
|
@@ -8782,7 +8932,7 @@ const DEFAULT_GET_ROW_ID = (_row, index)=>String(index);
|
|
|
8782
8932
|
});
|
|
8783
8933
|
}
|
|
8784
8934
|
// ───── Header cell (with sort button) ─────
|
|
8785
|
-
function HeaderCell(props) {
|
|
8935
|
+
function HeaderCell$1(props) {
|
|
8786
8936
|
const { col, sortModel, onSortClick, inferredType, labels, className, buttonClassName, disabled } = props;
|
|
8787
8937
|
const align = resolveAlign(col, inferredType);
|
|
8788
8938
|
const alignClass = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left';
|
|
@@ -8810,7 +8960,7 @@ function HeaderCell(props) {
|
|
|
8810
8960
|
jsx("span", {
|
|
8811
8961
|
children: headerContent
|
|
8812
8962
|
}),
|
|
8813
|
-
jsx(SortIcon, {
|
|
8963
|
+
jsx(SortIcon$1, {
|
|
8814
8964
|
direction: sortEntry == null ? void 0 : sortEntry.direction
|
|
8815
8965
|
})
|
|
8816
8966
|
]
|
|
@@ -8858,7 +9008,7 @@ function Row(props) {
|
|
|
8858
9008
|
children: jsx(ChevronRightIcon, {})
|
|
8859
9009
|
})
|
|
8860
9010
|
}),
|
|
8861
|
-
visibleCols.map((col)=>jsx(DataCell, {
|
|
9011
|
+
visibleCols.map((col)=>jsx(DataCell$1, {
|
|
8862
9012
|
col: col,
|
|
8863
9013
|
row: row,
|
|
8864
9014
|
rowIndex: rowIndex,
|
|
@@ -8885,7 +9035,7 @@ function Row(props) {
|
|
|
8885
9035
|
});
|
|
8886
9036
|
}
|
|
8887
9037
|
// ───── Data cell ─────
|
|
8888
|
-
function DataCell(props) {
|
|
9038
|
+
function DataCell$1(props) {
|
|
8889
9039
|
const { col, row, rowIndex, isSelected, isExpanded, inferredType, cellClass } = props;
|
|
8890
9040
|
const value = getNestedValue(row, col.field);
|
|
8891
9041
|
const align = resolveAlign(col, inferredType);
|
|
@@ -8910,7 +9060,7 @@ function DataCell(props) {
|
|
|
8910
9060
|
isSelected,
|
|
8911
9061
|
isExpanded,
|
|
8912
9062
|
access: columnAccessState
|
|
8913
|
-
}) : renderDefaultCell(value);
|
|
9063
|
+
}) : renderDefaultCell$1(value);
|
|
8914
9064
|
return jsx("td", {
|
|
8915
9065
|
className: cn(cellClass, alignClass, tabularNums && 'tabular-nums', monospace && 'font-mono'),
|
|
8916
9066
|
style: {
|
|
@@ -8921,19 +9071,19 @@ function DataCell(props) {
|
|
|
8921
9071
|
});
|
|
8922
9072
|
}
|
|
8923
9073
|
// ───── Helpers ─────
|
|
8924
|
-
function renderDefaultCell(value) {
|
|
9074
|
+
function renderDefaultCell$1(value) {
|
|
8925
9075
|
if (value == null) return null;
|
|
8926
9076
|
if (typeof value === 'boolean') return value ? '✓' : '✗';
|
|
8927
9077
|
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
8928
9078
|
if (typeof value === 'object') return JSON.stringify(value);
|
|
8929
9079
|
return value;
|
|
8930
9080
|
}
|
|
8931
|
-
function handleSortClick(col, event, current, setSortModel) {
|
|
9081
|
+
function handleSortClick$1(col, event, current, setSortModel) {
|
|
8932
9082
|
if (!col.sortable) return;
|
|
8933
9083
|
const multi = event.shiftKey;
|
|
8934
9084
|
setSortModel((prev)=>cycleSortFor(prev, col.field, multi));
|
|
8935
9085
|
}
|
|
8936
|
-
function renderLoadingRows(args) {
|
|
9086
|
+
function renderLoadingRows$1(args) {
|
|
8937
9087
|
const { count, visibleCols, showSelectionColumn, showExpandColumn, showRowActionsColumn, rowClass, cellClass } = args;
|
|
8938
9088
|
return Array.from({
|
|
8939
9089
|
length: count
|
|
@@ -8975,7 +9125,7 @@ function renderLoadingRows(args) {
|
|
|
8975
9125
|
]
|
|
8976
9126
|
}, `loading-${i}`));
|
|
8977
9127
|
}
|
|
8978
|
-
function renderEmptyState(args) {
|
|
9128
|
+
function renderEmptyState$1(args) {
|
|
8979
9129
|
var _args_emptyState;
|
|
8980
9130
|
return jsx("tr", {
|
|
8981
9131
|
children: jsx("td", {
|
|
@@ -8986,7 +9136,7 @@ function renderEmptyState(args) {
|
|
|
8986
9136
|
});
|
|
8987
9137
|
}
|
|
8988
9138
|
// ───── Icons ─────
|
|
8989
|
-
function SortIcon(props) {
|
|
9139
|
+
function SortIcon$1(props) {
|
|
8990
9140
|
return jsx("svg", {
|
|
8991
9141
|
width: "0.75em",
|
|
8992
9142
|
height: "0.75em",
|
|
@@ -9028,7 +9178,7 @@ function ChevronRightIcon() {
|
|
|
9028
9178
|
})
|
|
9029
9179
|
});
|
|
9030
9180
|
}
|
|
9031
|
-
function SearchIcon(props) {
|
|
9181
|
+
function SearchIcon$1(props) {
|
|
9032
9182
|
return jsxs("svg", {
|
|
9033
9183
|
width: "0.875em",
|
|
9034
9184
|
height: "0.875em",
|
|
@@ -9393,6 +9543,1778 @@ function RowActionMenuItem(props) {
|
|
|
9393
9543
|
});
|
|
9394
9544
|
}
|
|
9395
9545
|
|
|
9546
|
+
/**
|
|
9547
|
+
* `<DataGrid>` variant recipe.
|
|
9548
|
+
*
|
|
9549
|
+
* Sibling of `tableVariants` — shares the same visual axes (variant /
|
|
9550
|
+
* size / density) and the same identity-rule patterns:
|
|
9551
|
+
* - No `dark:` variants on neutral palette (auto-invert via the
|
|
9552
|
+
* dashforgePreset() CSS var swap).
|
|
9553
|
+
* - Selected row uses LeftNav `itemActive` pattern
|
|
9554
|
+
* (`bg-primary-100 text-primary-900`) — guaranteed contrast.
|
|
9555
|
+
* - Hover uses one-tier elevation (`bg-neutral-100`).
|
|
9556
|
+
*
|
|
9557
|
+
* DataGrid-specific slot additions vs Table:
|
|
9558
|
+
* - `stickyLeftCell` — column that uses `cols[i].sticky === 'left'`.
|
|
9559
|
+
* Z-index ordering: `thead` (z-10) < `stickyLeftCell` (z-11) <
|
|
9560
|
+
* `header × stickyLeft top-left corner cell` (z-20).
|
|
9561
|
+
* - `paginationFooter` — wraps the optional `<Pagination>`.
|
|
9562
|
+
*/ const dataGridVariants = tv({
|
|
9563
|
+
slots: {
|
|
9564
|
+
root: 'w-full',
|
|
9565
|
+
toolbar: 'flex flex-wrap items-center gap-3 mb-3',
|
|
9566
|
+
search: [
|
|
9567
|
+
'inline-flex items-center gap-2',
|
|
9568
|
+
'w-full max-w-sm rounded-md',
|
|
9569
|
+
'border border-neutral-300',
|
|
9570
|
+
'bg-neutral-50',
|
|
9571
|
+
'focus-within:ring-2 focus-within:ring-primary-500',
|
|
9572
|
+
'transition-colors motion-reduce:transition-none'
|
|
9573
|
+
],
|
|
9574
|
+
// The scroll container needs an explicit bounded height for
|
|
9575
|
+
// virtualization to compute the window. The consumer passes
|
|
9576
|
+
// `height` prop (inline style) or sets it via `sx` / parent
|
|
9577
|
+
// layout — DataGrid does NOT default a height (would be wrong
|
|
9578
|
+
// for most layouts).
|
|
9579
|
+
scroll: 'relative w-full overflow-auto rounded-md',
|
|
9580
|
+
table: 'w-full caption-bottom text-left',
|
|
9581
|
+
thead: 'bg-neutral-50 sticky top-0 z-10',
|
|
9582
|
+
tbody: '',
|
|
9583
|
+
headerRow: '',
|
|
9584
|
+
headerCell: [
|
|
9585
|
+
'text-neutral-700',
|
|
9586
|
+
'font-semibold whitespace-nowrap select-none',
|
|
9587
|
+
'border-b border-neutral-200',
|
|
9588
|
+
'bg-neutral-50'
|
|
9589
|
+
],
|
|
9590
|
+
headerCellButton: [
|
|
9591
|
+
'group inline-flex items-center gap-1 select-none',
|
|
9592
|
+
'hover:text-neutral-900',
|
|
9593
|
+
'focus:outline-none focus-visible:underline',
|
|
9594
|
+
'transition-colors motion-reduce:transition-none'
|
|
9595
|
+
],
|
|
9596
|
+
row: [
|
|
9597
|
+
'group/row transition-colors motion-reduce:transition-none',
|
|
9598
|
+
'hover:bg-neutral-100',
|
|
9599
|
+
'data-[selected=true]:bg-primary-100',
|
|
9600
|
+
'data-[selected=true]:text-primary-900',
|
|
9601
|
+
'data-[selected=true]:font-medium'
|
|
9602
|
+
],
|
|
9603
|
+
cell: [
|
|
9604
|
+
'text-neutral-900',
|
|
9605
|
+
'border-b border-neutral-200',
|
|
9606
|
+
'align-middle'
|
|
9607
|
+
],
|
|
9608
|
+
selectionCell: 'w-10 text-center',
|
|
9609
|
+
rowActionsCell: [
|
|
9610
|
+
'w-12 text-right',
|
|
9611
|
+
'opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100',
|
|
9612
|
+
'transition-opacity motion-reduce:transition-none'
|
|
9613
|
+
],
|
|
9614
|
+
emptyState: 'py-12 text-center text-neutral-500',
|
|
9615
|
+
loadingOverlay: 'pointer-events-none',
|
|
9616
|
+
bulkActionFooter: [
|
|
9617
|
+
'sticky bottom-0 z-20',
|
|
9618
|
+
'flex flex-wrap items-center justify-between gap-3',
|
|
9619
|
+
'px-3 py-2',
|
|
9620
|
+
'border-t border-neutral-200',
|
|
9621
|
+
'bg-neutral-50 shadow-md'
|
|
9622
|
+
],
|
|
9623
|
+
// Sticky LEFT column — uses `position: sticky; left: 0` + z-index
|
|
9624
|
+
// so the column stays pinned during horizontal scroll. The
|
|
9625
|
+
// `bg-neutral-50` ensures the column doesn't bleed when content
|
|
9626
|
+
// scrolls underneath.
|
|
9627
|
+
stickyLeftCell: 'sticky left-0 z-[1] bg-neutral-50',
|
|
9628
|
+
// Sticky left × header intersection (top-left corner): higher
|
|
9629
|
+
// z-index than both header (`z-10`) and sticky col (`z-[1]`).
|
|
9630
|
+
stickyLeftHeaderCell: 'sticky left-0 z-20 bg-neutral-50',
|
|
9631
|
+
// Sticky RIGHT column — mirror of stickyLeftCell. The first
|
|
9632
|
+
// right-sticky column gets a `border-l` to separate it visually
|
|
9633
|
+
// from the scrollable area (the consumer can override via
|
|
9634
|
+
// slotProps if they don't want it).
|
|
9635
|
+
stickyRightCell: 'sticky right-0 z-[1] bg-neutral-50 border-l border-neutral-200',
|
|
9636
|
+
stickyRightHeaderCell: 'sticky right-0 z-20 bg-neutral-50 border-l border-neutral-200',
|
|
9637
|
+
paginationFooter: 'flex justify-end mt-3'
|
|
9638
|
+
},
|
|
9639
|
+
variants: {
|
|
9640
|
+
variant: {
|
|
9641
|
+
plain: {},
|
|
9642
|
+
lines: {},
|
|
9643
|
+
striped: {
|
|
9644
|
+
row: 'odd:bg-neutral-100/60'
|
|
9645
|
+
},
|
|
9646
|
+
bordered: {
|
|
9647
|
+
table: 'border border-neutral-200',
|
|
9648
|
+
cell: 'border-r last:border-r-0 border-neutral-200',
|
|
9649
|
+
headerCell: 'border-r last:border-r-0 border-neutral-200'
|
|
9650
|
+
}
|
|
9651
|
+
},
|
|
9652
|
+
size: {
|
|
9653
|
+
sm: {
|
|
9654
|
+
headerCell: 'text-xs',
|
|
9655
|
+
cell: 'text-xs',
|
|
9656
|
+
search: 'text-xs'
|
|
9657
|
+
},
|
|
9658
|
+
md: {
|
|
9659
|
+
headerCell: 'text-sm',
|
|
9660
|
+
cell: 'text-sm',
|
|
9661
|
+
search: 'text-sm'
|
|
9662
|
+
},
|
|
9663
|
+
lg: {
|
|
9664
|
+
headerCell: 'text-base',
|
|
9665
|
+
cell: 'text-base',
|
|
9666
|
+
search: 'text-base'
|
|
9667
|
+
}
|
|
9668
|
+
},
|
|
9669
|
+
density: {
|
|
9670
|
+
compact: {
|
|
9671
|
+
headerCell: 'px-2 py-1',
|
|
9672
|
+
cell: 'px-2 py-1'
|
|
9673
|
+
},
|
|
9674
|
+
comfortable: {
|
|
9675
|
+
headerCell: 'px-3 py-2',
|
|
9676
|
+
cell: 'px-3 py-2'
|
|
9677
|
+
},
|
|
9678
|
+
spacious: {
|
|
9679
|
+
headerCell: 'px-4 py-3',
|
|
9680
|
+
cell: 'px-4 py-3'
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
9683
|
+
},
|
|
9684
|
+
defaultVariants: {
|
|
9685
|
+
variant: 'lines',
|
|
9686
|
+
size: 'md',
|
|
9687
|
+
density: 'comfortable'
|
|
9688
|
+
}
|
|
9689
|
+
});
|
|
9690
|
+
|
|
9691
|
+
/**
|
|
9692
|
+
* Compute the visible window of rows + the spacer heights given the
|
|
9693
|
+
* current scroll position. Pure function — exported for testability.
|
|
9694
|
+
*
|
|
9695
|
+
* @example
|
|
9696
|
+
* ```ts
|
|
9697
|
+
* computeWindow({ totalCount: 1000, rowHeight: 48, viewportHeight: 600,
|
|
9698
|
+
* scrollTop: 0, overscan: 5 });
|
|
9699
|
+
* // → { startIndex: 0, endIndex: 17, paddingTop: 0, paddingBottom: 47136 }
|
|
9700
|
+
* ```
|
|
9701
|
+
*/ function computeWindow(args) {
|
|
9702
|
+
const { totalCount, rowHeight, viewportHeight, scrollTop, overscan = 5 } = args;
|
|
9703
|
+
if (totalCount <= 0 || rowHeight <= 0 || viewportHeight <= 0) {
|
|
9704
|
+
return {
|
|
9705
|
+
startIndex: 0,
|
|
9706
|
+
endIndex: -1,
|
|
9707
|
+
paddingTop: 0,
|
|
9708
|
+
paddingBottom: 0
|
|
9709
|
+
};
|
|
9710
|
+
}
|
|
9711
|
+
// Raw visible range from scrollTop alone.
|
|
9712
|
+
const rawStart = Math.floor(scrollTop / rowHeight);
|
|
9713
|
+
const rawEnd = Math.ceil((scrollTop + viewportHeight) / rowHeight);
|
|
9714
|
+
// Apply overscan + clamp to [0, totalCount - 1].
|
|
9715
|
+
const startIndex = Math.max(0, rawStart - overscan);
|
|
9716
|
+
const endIndex = Math.min(totalCount - 1, rawEnd + overscan);
|
|
9717
|
+
const paddingTop = startIndex * rowHeight;
|
|
9718
|
+
const paddingBottom = Math.max(0, (totalCount - 1 - endIndex) * rowHeight);
|
|
9719
|
+
return {
|
|
9720
|
+
startIndex,
|
|
9721
|
+
endIndex,
|
|
9722
|
+
paddingTop,
|
|
9723
|
+
paddingBottom
|
|
9724
|
+
};
|
|
9725
|
+
}
|
|
9726
|
+
/**
|
|
9727
|
+
* `useVirtualizer` — viewport-based row windowing for `<DataGrid>`.
|
|
9728
|
+
*
|
|
9729
|
+
* Hand-rolled (no `@tanstack/react-virtual` dep — constraint memorized
|
|
9730
|
+
* in `feedback_dashforge_preset_is_identity` companion: "no new
|
|
9731
|
+
* external runtime deps"). Uses scroll event + `requestAnimationFrame`
|
|
9732
|
+
* debounce + `ResizeObserver` on the container. Fixed row height
|
|
9733
|
+
* only in v1 — variable-height with measurement cache deferred to
|
|
9734
|
+
* v1-bis.
|
|
9735
|
+
*
|
|
9736
|
+
* Returns:
|
|
9737
|
+
* - `scrollRef`: attach to the scroll container `<div>`.
|
|
9738
|
+
* - `window`: which row indices to mount + top/bottom spacer heights
|
|
9739
|
+
* to preserve scroll-area size.
|
|
9740
|
+
* - `viewportHeight`: current container height (re-measured on
|
|
9741
|
+
* `ResizeObserver` ticks).
|
|
9742
|
+
*
|
|
9743
|
+
* The DataGrid renders `rows.slice(window.startIndex, window.endIndex + 1)`
|
|
9744
|
+
* between two spacer `<tr style={{ height: paddingTop }} />` elements
|
|
9745
|
+
* (above and below). This preserves `<table>` semantics — screen
|
|
9746
|
+
* readers see a continuous structure with `aria-hidden` spacers.
|
|
9747
|
+
*
|
|
9748
|
+
* @example
|
|
9749
|
+
* ```tsx
|
|
9750
|
+
* const { scrollRef, window } = useVirtualizer({
|
|
9751
|
+
* totalCount: rows.length,
|
|
9752
|
+
* rowHeight: 48,
|
|
9753
|
+
* overscan: 5,
|
|
9754
|
+
* });
|
|
9755
|
+
*
|
|
9756
|
+
* return (
|
|
9757
|
+
* <div ref={scrollRef} style={{ height: 600, overflow: 'auto' }}>
|
|
9758
|
+
* <table>
|
|
9759
|
+
* <tbody>
|
|
9760
|
+
* <tr aria-hidden="true" style={{ height: window.paddingTop }} />
|
|
9761
|
+
* {rows.slice(window.startIndex, window.endIndex + 1).map(...)}
|
|
9762
|
+
* <tr aria-hidden="true" style={{ height: window.paddingBottom }} />
|
|
9763
|
+
* </tbody>
|
|
9764
|
+
* </table>
|
|
9765
|
+
* </div>
|
|
9766
|
+
* );
|
|
9767
|
+
* ```
|
|
9768
|
+
*/ function useVirtualizer(args) {
|
|
9769
|
+
const { totalCount, rowHeight, overscan = 5, initialViewportHeight = 400 } = args;
|
|
9770
|
+
const scrollRef = useRef(null);
|
|
9771
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
9772
|
+
const [viewportHeight, setViewportHeight] = useState(initialViewportHeight);
|
|
9773
|
+
// Debounce scroll handler via requestAnimationFrame so we never
|
|
9774
|
+
// re-render more than once per paint frame even when the scroll
|
|
9775
|
+
// event fires rapidly during fast scrolling.
|
|
9776
|
+
const rafIdRef = useRef(null);
|
|
9777
|
+
const handleScroll = useCallback(()=>{
|
|
9778
|
+
if (rafIdRef.current != null) return;
|
|
9779
|
+
rafIdRef.current = requestAnimationFrame(()=>{
|
|
9780
|
+
rafIdRef.current = null;
|
|
9781
|
+
const el = scrollRef.current;
|
|
9782
|
+
if (!el) return;
|
|
9783
|
+
setScrollTop(el.scrollTop);
|
|
9784
|
+
});
|
|
9785
|
+
}, []);
|
|
9786
|
+
// Attach scroll listener.
|
|
9787
|
+
useEffect(()=>{
|
|
9788
|
+
const el = scrollRef.current;
|
|
9789
|
+
if (!el) return;
|
|
9790
|
+
el.addEventListener('scroll', handleScroll, {
|
|
9791
|
+
passive: true
|
|
9792
|
+
});
|
|
9793
|
+
return ()=>{
|
|
9794
|
+
el.removeEventListener('scroll', handleScroll);
|
|
9795
|
+
if (rafIdRef.current != null) {
|
|
9796
|
+
cancelAnimationFrame(rafIdRef.current);
|
|
9797
|
+
rafIdRef.current = null;
|
|
9798
|
+
}
|
|
9799
|
+
};
|
|
9800
|
+
}, [
|
|
9801
|
+
handleScroll
|
|
9802
|
+
]);
|
|
9803
|
+
// Track container viewport height via ResizeObserver — re-virtualize
|
|
9804
|
+
// when the container size changes (window resize, sidebar collapse,
|
|
9805
|
+
// etc.). Use `useLayoutEffect` so the first measurement happens
|
|
9806
|
+
// before paint to avoid a flash of stale window.
|
|
9807
|
+
//
|
|
9808
|
+
// **Defensive**: only accept measurements > 0. jsdom (test env)
|
|
9809
|
+
// returns `clientHeight === 0` for unmounted containers, which would
|
|
9810
|
+
// collapse the window to empty. The hook keeps the
|
|
9811
|
+
// `initialViewportHeight` fallback in that case.
|
|
9812
|
+
useLayoutEffect(()=>{
|
|
9813
|
+
const el = scrollRef.current;
|
|
9814
|
+
if (!el) return;
|
|
9815
|
+
if (el.clientHeight > 0) {
|
|
9816
|
+
setViewportHeight(el.clientHeight);
|
|
9817
|
+
}
|
|
9818
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
9819
|
+
const observer = new ResizeObserver((entries)=>{
|
|
9820
|
+
const entry = entries[0];
|
|
9821
|
+
if (!entry || entry.contentRect.height <= 0) return;
|
|
9822
|
+
setViewportHeight(entry.contentRect.height);
|
|
9823
|
+
});
|
|
9824
|
+
observer.observe(el);
|
|
9825
|
+
return ()=>observer.disconnect();
|
|
9826
|
+
}, []);
|
|
9827
|
+
const window = computeWindow({
|
|
9828
|
+
totalCount,
|
|
9829
|
+
rowHeight,
|
|
9830
|
+
viewportHeight,
|
|
9831
|
+
scrollTop,
|
|
9832
|
+
overscan
|
|
9833
|
+
});
|
|
9834
|
+
return {
|
|
9835
|
+
scrollRef,
|
|
9836
|
+
window,
|
|
9837
|
+
viewportHeight
|
|
9838
|
+
};
|
|
9839
|
+
}
|
|
9840
|
+
|
|
9841
|
+
/**
|
|
9842
|
+
* Per-column filter UI for DataGrid.
|
|
9843
|
+
*
|
|
9844
|
+
* `<ColumnFilterTrigger>` renders the filter icon button in the
|
|
9845
|
+
* column header — when clicked, opens a Popover with the
|
|
9846
|
+
* appropriate filter UI for the column type:
|
|
9847
|
+
*
|
|
9848
|
+
* - `text` → `<TextFilter>` with single input (op: `contains`)
|
|
9849
|
+
* - `number` → `<NumberRangeFilter>` with min/max inputs (op: `between`)
|
|
9850
|
+
* - `boolean` → `<BooleanFilter>` with true/false/all radio (op: `equals`)
|
|
9851
|
+
* - `date` → `<DateRangeFilter>` with min/max date inputs (op: `between`)
|
|
9852
|
+
*
|
|
9853
|
+
* Each filter UI has Apply + Clear actions; Apply commits the
|
|
9854
|
+
* filter to the model, Clear removes any existing filter for that
|
|
9855
|
+
* column.
|
|
9856
|
+
*/ // ───── Helper: read/write filter model for a single column ─────
|
|
9857
|
+
function findFilter(model, field) {
|
|
9858
|
+
return model.find((f)=>f.field === field);
|
|
9859
|
+
}
|
|
9860
|
+
function setFilter(model, field, next) {
|
|
9861
|
+
const rest = model.filter((f)=>f.field !== field);
|
|
9862
|
+
if (next == null) return rest;
|
|
9863
|
+
return [
|
|
9864
|
+
...rest,
|
|
9865
|
+
next
|
|
9866
|
+
];
|
|
9867
|
+
}
|
|
9868
|
+
const DEFAULT_FILTER_LABELS = {
|
|
9869
|
+
filterColumn: 'Filter',
|
|
9870
|
+
filterApply: 'Apply',
|
|
9871
|
+
filterClear: 'Clear',
|
|
9872
|
+
filterMin: 'Min',
|
|
9873
|
+
filterMax: 'Max',
|
|
9874
|
+
filterFrom: 'From',
|
|
9875
|
+
filterTo: 'To',
|
|
9876
|
+
filterAll: 'All',
|
|
9877
|
+
filterTrue: 'True',
|
|
9878
|
+
filterFalse: 'False'
|
|
9879
|
+
};
|
|
9880
|
+
function ColumnFilterTrigger(props) {
|
|
9881
|
+
const { field, filterType, filterModel, onFilterChange, disabled, labels: labelsProp } = props;
|
|
9882
|
+
const labels = _extends({}, DEFAULT_FILTER_LABELS, labelsProp);
|
|
9883
|
+
const [open, setOpen] = useState(false);
|
|
9884
|
+
const active = findFilter(filterModel, field) != null;
|
|
9885
|
+
return jsx(Popover, {
|
|
9886
|
+
open: open,
|
|
9887
|
+
onOpenChange: setOpen,
|
|
9888
|
+
side: "bottom",
|
|
9889
|
+
align: "end",
|
|
9890
|
+
content: jsx(FilterPanel, {
|
|
9891
|
+
field: field,
|
|
9892
|
+
filterType: filterType,
|
|
9893
|
+
filterModel: filterModel,
|
|
9894
|
+
onFilterChange: (next)=>{
|
|
9895
|
+
onFilterChange(next);
|
|
9896
|
+
setOpen(false);
|
|
9897
|
+
},
|
|
9898
|
+
labels: labels
|
|
9899
|
+
}),
|
|
9900
|
+
children: jsx("button", {
|
|
9901
|
+
type: "button",
|
|
9902
|
+
"aria-label": labels.filterColumn,
|
|
9903
|
+
"aria-pressed": active,
|
|
9904
|
+
disabled: disabled,
|
|
9905
|
+
className: cn('inline-flex items-center justify-center h-6 w-6 rounded', 'text-neutral-500 hover:text-neutral-900', active && 'text-primary-700', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500', 'transition-colors motion-reduce:transition-none', 'disabled:opacity-50 disabled:cursor-not-allowed'),
|
|
9906
|
+
children: jsx(FilterIcon, {})
|
|
9907
|
+
})
|
|
9908
|
+
});
|
|
9909
|
+
}
|
|
9910
|
+
// ───── Panel — dispatches to the right filter UI ─────
|
|
9911
|
+
function FilterPanel(props) {
|
|
9912
|
+
const { field, filterType, filterModel, onFilterChange, labels } = props;
|
|
9913
|
+
const current = findFilter(filterModel, field);
|
|
9914
|
+
const commit = (next)=>{
|
|
9915
|
+
onFilterChange(setFilter(filterModel, field, next));
|
|
9916
|
+
};
|
|
9917
|
+
switch(filterType){
|
|
9918
|
+
case 'number':
|
|
9919
|
+
return jsx(NumberRangeFilter, {
|
|
9920
|
+
field: field,
|
|
9921
|
+
current: current,
|
|
9922
|
+
onCommit: commit,
|
|
9923
|
+
labels: labels
|
|
9924
|
+
});
|
|
9925
|
+
case 'boolean':
|
|
9926
|
+
return jsx(BooleanFilter, {
|
|
9927
|
+
field: field,
|
|
9928
|
+
current: current,
|
|
9929
|
+
onCommit: commit,
|
|
9930
|
+
labels: labels
|
|
9931
|
+
});
|
|
9932
|
+
case 'date':
|
|
9933
|
+
return jsx(DateRangeFilter, {
|
|
9934
|
+
field: field,
|
|
9935
|
+
current: current,
|
|
9936
|
+
onCommit: commit,
|
|
9937
|
+
labels: labels
|
|
9938
|
+
});
|
|
9939
|
+
case 'text':
|
|
9940
|
+
default:
|
|
9941
|
+
return jsx(TextFilter, {
|
|
9942
|
+
field: field,
|
|
9943
|
+
current: current,
|
|
9944
|
+
onCommit: commit,
|
|
9945
|
+
labels: labels
|
|
9946
|
+
});
|
|
9947
|
+
}
|
|
9948
|
+
}
|
|
9949
|
+
// ───── Text filter ─────
|
|
9950
|
+
function TextFilter(props) {
|
|
9951
|
+
const { field, current, onCommit, labels } = props;
|
|
9952
|
+
const [value, setValue] = useState(typeof (current == null ? void 0 : current.value) === 'string' ? current.value : '');
|
|
9953
|
+
return jsxs("div", {
|
|
9954
|
+
className: "flex flex-col gap-2 min-w-[200px]",
|
|
9955
|
+
children: [
|
|
9956
|
+
jsx("input", {
|
|
9957
|
+
type: "text",
|
|
9958
|
+
value: value,
|
|
9959
|
+
onChange: (e)=>setValue(e.target.value),
|
|
9960
|
+
placeholder: "\u2026",
|
|
9961
|
+
autoFocus: true,
|
|
9962
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
9963
|
+
}),
|
|
9964
|
+
jsx(FilterActions, {
|
|
9965
|
+
onApply: ()=>onCommit(value.trim() ? {
|
|
9966
|
+
field,
|
|
9967
|
+
op: 'contains',
|
|
9968
|
+
value
|
|
9969
|
+
} : null),
|
|
9970
|
+
onClear: ()=>{
|
|
9971
|
+
setValue('');
|
|
9972
|
+
onCommit(null);
|
|
9973
|
+
},
|
|
9974
|
+
labels: labels
|
|
9975
|
+
})
|
|
9976
|
+
]
|
|
9977
|
+
});
|
|
9978
|
+
}
|
|
9979
|
+
// ───── Number range filter ─────
|
|
9980
|
+
function NumberRangeFilter(props) {
|
|
9981
|
+
const { field, current, onCommit, labels } = props;
|
|
9982
|
+
const currentValue = current == null ? void 0 : current.value;
|
|
9983
|
+
const currentTuple = Array.isArray(currentValue) ? currentValue : [
|
|
9984
|
+
null,
|
|
9985
|
+
null
|
|
9986
|
+
];
|
|
9987
|
+
const [min, setMin] = useState(currentTuple[0] != null ? String(currentTuple[0]) : '');
|
|
9988
|
+
const [max, setMax] = useState(currentTuple[1] != null ? String(currentTuple[1]) : '');
|
|
9989
|
+
const apply = ()=>{
|
|
9990
|
+
const minN = min.trim() ? Number(min) : null;
|
|
9991
|
+
const maxN = max.trim() ? Number(max) : null;
|
|
9992
|
+
if ((minN == null || Number.isFinite(minN)) && (maxN == null || Number.isFinite(maxN))) {
|
|
9993
|
+
if (minN == null && maxN == null) {
|
|
9994
|
+
onCommit(null);
|
|
9995
|
+
} else {
|
|
9996
|
+
onCommit({
|
|
9997
|
+
field,
|
|
9998
|
+
op: 'between',
|
|
9999
|
+
value: [
|
|
10000
|
+
minN,
|
|
10001
|
+
maxN
|
|
10002
|
+
]
|
|
10003
|
+
});
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
};
|
|
10007
|
+
return jsxs("div", {
|
|
10008
|
+
className: "flex flex-col gap-2 min-w-[220px]",
|
|
10009
|
+
children: [
|
|
10010
|
+
jsxs("div", {
|
|
10011
|
+
className: "grid grid-cols-2 gap-2",
|
|
10012
|
+
children: [
|
|
10013
|
+
jsxs("label", {
|
|
10014
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10015
|
+
children: [
|
|
10016
|
+
jsx("span", {
|
|
10017
|
+
children: labels.filterMin
|
|
10018
|
+
}),
|
|
10019
|
+
jsx("input", {
|
|
10020
|
+
type: "number",
|
|
10021
|
+
value: min,
|
|
10022
|
+
onChange: (e)=>setMin(e.target.value),
|
|
10023
|
+
autoFocus: true,
|
|
10024
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10025
|
+
})
|
|
10026
|
+
]
|
|
10027
|
+
}),
|
|
10028
|
+
jsxs("label", {
|
|
10029
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10030
|
+
children: [
|
|
10031
|
+
jsx("span", {
|
|
10032
|
+
children: labels.filterMax
|
|
10033
|
+
}),
|
|
10034
|
+
jsx("input", {
|
|
10035
|
+
type: "number",
|
|
10036
|
+
value: max,
|
|
10037
|
+
onChange: (e)=>setMax(e.target.value),
|
|
10038
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10039
|
+
})
|
|
10040
|
+
]
|
|
10041
|
+
})
|
|
10042
|
+
]
|
|
10043
|
+
}),
|
|
10044
|
+
jsx(FilterActions, {
|
|
10045
|
+
onApply: apply,
|
|
10046
|
+
onClear: ()=>{
|
|
10047
|
+
setMin('');
|
|
10048
|
+
setMax('');
|
|
10049
|
+
onCommit(null);
|
|
10050
|
+
},
|
|
10051
|
+
labels: labels
|
|
10052
|
+
})
|
|
10053
|
+
]
|
|
10054
|
+
});
|
|
10055
|
+
}
|
|
10056
|
+
// ───── Boolean filter ─────
|
|
10057
|
+
function BooleanFilter(props) {
|
|
10058
|
+
const { field, current, onCommit, labels } = props;
|
|
10059
|
+
const initial = (current == null ? void 0 : current.value) === true ? 'true' : (current == null ? void 0 : current.value) === false ? 'false' : 'all';
|
|
10060
|
+
const [value, setValue] = useState(initial);
|
|
10061
|
+
const apply = ()=>{
|
|
10062
|
+
if (value === 'all') onCommit(null);
|
|
10063
|
+
else onCommit({
|
|
10064
|
+
field,
|
|
10065
|
+
op: 'equals',
|
|
10066
|
+
value: value === 'true'
|
|
10067
|
+
});
|
|
10068
|
+
};
|
|
10069
|
+
return jsxs("div", {
|
|
10070
|
+
className: "flex flex-col gap-2 min-w-[160px]",
|
|
10071
|
+
children: [
|
|
10072
|
+
jsx("fieldset", {
|
|
10073
|
+
className: "flex flex-col gap-1",
|
|
10074
|
+
children: [
|
|
10075
|
+
'all',
|
|
10076
|
+
'true',
|
|
10077
|
+
'false'
|
|
10078
|
+
].map((opt)=>jsxs("label", {
|
|
10079
|
+
className: "flex items-center gap-2 text-sm cursor-pointer",
|
|
10080
|
+
children: [
|
|
10081
|
+
jsx("input", {
|
|
10082
|
+
type: "radio",
|
|
10083
|
+
name: `filter-${field}`,
|
|
10084
|
+
value: opt,
|
|
10085
|
+
checked: value === opt,
|
|
10086
|
+
onChange: ()=>setValue(opt),
|
|
10087
|
+
className: "cursor-pointer"
|
|
10088
|
+
}),
|
|
10089
|
+
jsx("span", {
|
|
10090
|
+
children: opt === 'all' ? labels.filterAll : opt === 'true' ? labels.filterTrue : labels.filterFalse
|
|
10091
|
+
})
|
|
10092
|
+
]
|
|
10093
|
+
}, opt))
|
|
10094
|
+
}),
|
|
10095
|
+
jsx(FilterActions, {
|
|
10096
|
+
onApply: apply,
|
|
10097
|
+
onClear: ()=>{
|
|
10098
|
+
setValue('all');
|
|
10099
|
+
onCommit(null);
|
|
10100
|
+
},
|
|
10101
|
+
labels: labels
|
|
10102
|
+
})
|
|
10103
|
+
]
|
|
10104
|
+
});
|
|
10105
|
+
}
|
|
10106
|
+
// ───── Date range filter ─────
|
|
10107
|
+
function DateRangeFilter(props) {
|
|
10108
|
+
const { field, current, onCommit, labels } = props;
|
|
10109
|
+
const currentValue = current == null ? void 0 : current.value;
|
|
10110
|
+
const currentTuple = Array.isArray(currentValue) ? currentValue : [
|
|
10111
|
+
null,
|
|
10112
|
+
null
|
|
10113
|
+
];
|
|
10114
|
+
const [from, setFrom] = useState(typeof currentTuple[0] === 'string' ? currentTuple[0] : '');
|
|
10115
|
+
const [to, setTo] = useState(typeof currentTuple[1] === 'string' ? currentTuple[1] : '');
|
|
10116
|
+
const apply = ()=>{
|
|
10117
|
+
if (!from && !to) {
|
|
10118
|
+
onCommit(null);
|
|
10119
|
+
} else {
|
|
10120
|
+
onCommit({
|
|
10121
|
+
field,
|
|
10122
|
+
op: 'between',
|
|
10123
|
+
value: [
|
|
10124
|
+
from || null,
|
|
10125
|
+
to || null
|
|
10126
|
+
]
|
|
10127
|
+
});
|
|
10128
|
+
}
|
|
10129
|
+
};
|
|
10130
|
+
return jsxs("div", {
|
|
10131
|
+
className: "flex flex-col gap-2 min-w-[220px]",
|
|
10132
|
+
children: [
|
|
10133
|
+
jsxs("div", {
|
|
10134
|
+
className: "grid grid-cols-2 gap-2",
|
|
10135
|
+
children: [
|
|
10136
|
+
jsxs("label", {
|
|
10137
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10138
|
+
children: [
|
|
10139
|
+
jsx("span", {
|
|
10140
|
+
children: labels.filterFrom
|
|
10141
|
+
}),
|
|
10142
|
+
jsx("input", {
|
|
10143
|
+
type: "date",
|
|
10144
|
+
value: from,
|
|
10145
|
+
onChange: (e)=>setFrom(e.target.value),
|
|
10146
|
+
autoFocus: true,
|
|
10147
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10148
|
+
})
|
|
10149
|
+
]
|
|
10150
|
+
}),
|
|
10151
|
+
jsxs("label", {
|
|
10152
|
+
className: "flex flex-col gap-1 text-xs text-neutral-700",
|
|
10153
|
+
children: [
|
|
10154
|
+
jsx("span", {
|
|
10155
|
+
children: labels.filterTo
|
|
10156
|
+
}),
|
|
10157
|
+
jsx("input", {
|
|
10158
|
+
type: "date",
|
|
10159
|
+
value: to,
|
|
10160
|
+
onChange: (e)=>setTo(e.target.value),
|
|
10161
|
+
className: "rounded-md border border-neutral-300 bg-neutral-50 px-2 py-1.5 text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
|
10162
|
+
})
|
|
10163
|
+
]
|
|
10164
|
+
})
|
|
10165
|
+
]
|
|
10166
|
+
}),
|
|
10167
|
+
jsx(FilterActions, {
|
|
10168
|
+
onApply: apply,
|
|
10169
|
+
onClear: ()=>{
|
|
10170
|
+
setFrom('');
|
|
10171
|
+
setTo('');
|
|
10172
|
+
onCommit(null);
|
|
10173
|
+
},
|
|
10174
|
+
labels: labels
|
|
10175
|
+
})
|
|
10176
|
+
]
|
|
10177
|
+
});
|
|
10178
|
+
}
|
|
10179
|
+
// ───── Apply + Clear actions ─────
|
|
10180
|
+
function FilterActions(props) {
|
|
10181
|
+
return jsxs("div", {
|
|
10182
|
+
className: "flex justify-end gap-2 pt-1 border-t border-neutral-200",
|
|
10183
|
+
children: [
|
|
10184
|
+
jsx("button", {
|
|
10185
|
+
type: "button",
|
|
10186
|
+
onClick: props.onClear,
|
|
10187
|
+
className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline px-2 py-1",
|
|
10188
|
+
children: props.labels.filterClear
|
|
10189
|
+
}),
|
|
10190
|
+
jsx("button", {
|
|
10191
|
+
type: "button",
|
|
10192
|
+
onClick: props.onApply,
|
|
10193
|
+
className: "text-xs bg-primary-500 text-white rounded-md hover:bg-primary-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 px-3 py-1",
|
|
10194
|
+
children: props.labels.filterApply
|
|
10195
|
+
})
|
|
10196
|
+
]
|
|
10197
|
+
});
|
|
10198
|
+
}
|
|
10199
|
+
// ───── Icon ─────
|
|
10200
|
+
function FilterIcon() {
|
|
10201
|
+
return jsx("svg", {
|
|
10202
|
+
width: "0.875em",
|
|
10203
|
+
height: "0.875em",
|
|
10204
|
+
viewBox: "0 0 16 16",
|
|
10205
|
+
fill: "none",
|
|
10206
|
+
stroke: "currentColor",
|
|
10207
|
+
strokeWidth: "1.5",
|
|
10208
|
+
strokeLinecap: "round",
|
|
10209
|
+
strokeLinejoin: "round",
|
|
10210
|
+
"aria-hidden": "true",
|
|
10211
|
+
children: jsx("path", {
|
|
10212
|
+
d: "M2 3h12M4 8h8M6 13h4"
|
|
10213
|
+
})
|
|
10214
|
+
});
|
|
10215
|
+
}
|
|
10216
|
+
|
|
10217
|
+
const DEFAULT_VISIBILITY_LABELS = {
|
|
10218
|
+
columnsButton: 'Columns',
|
|
10219
|
+
columnsTitle: 'Manage columns',
|
|
10220
|
+
columnsDescription: 'Show or hide columns. Required columns cannot be toggled.',
|
|
10221
|
+
columnsShowAll: 'Show all',
|
|
10222
|
+
columnsHideAll: 'Hide all',
|
|
10223
|
+
columnsDone: 'Done'
|
|
10224
|
+
};
|
|
10225
|
+
function ColumnVisibilityTrigger(props) {
|
|
10226
|
+
const { cols, hiddenColumns, onChange, disabled, labels: labelsProp } = props;
|
|
10227
|
+
const labels = _extends({}, DEFAULT_VISIBILITY_LABELS, labelsProp);
|
|
10228
|
+
const [open, setOpen] = useState(false);
|
|
10229
|
+
// Hideable cols are the only ones the user can toggle. `hideable`
|
|
10230
|
+
// defaults to `true` — a column is hideable unless explicitly opted
|
|
10231
|
+
// out with `hideable: false` (structurally required column).
|
|
10232
|
+
const hideableCols = cols.filter((c)=>c.hideable !== false);
|
|
10233
|
+
return jsxs(Fragment$1, {
|
|
10234
|
+
children: [
|
|
10235
|
+
jsxs("button", {
|
|
10236
|
+
type: "button",
|
|
10237
|
+
onClick: ()=>setOpen(true),
|
|
10238
|
+
disabled: disabled,
|
|
10239
|
+
className: cn('inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-md', 'text-sm text-neutral-700 hover:text-neutral-900', 'border border-neutral-300 bg-neutral-50 hover:bg-neutral-100', 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500', 'transition-colors motion-reduce:transition-none', 'disabled:opacity-50 disabled:cursor-not-allowed'),
|
|
10240
|
+
children: [
|
|
10241
|
+
jsx(ColumnsIcon, {}),
|
|
10242
|
+
jsx("span", {
|
|
10243
|
+
children: labels.columnsButton
|
|
10244
|
+
})
|
|
10245
|
+
]
|
|
10246
|
+
}),
|
|
10247
|
+
jsx(Dialog, {
|
|
10248
|
+
open: open,
|
|
10249
|
+
onOpenChange: setOpen,
|
|
10250
|
+
title: labels.columnsTitle,
|
|
10251
|
+
description: labels.columnsDescription,
|
|
10252
|
+
size: "sm",
|
|
10253
|
+
children: jsx(ColumnVisibilityBody, {
|
|
10254
|
+
cols: hideableCols,
|
|
10255
|
+
hiddenColumns: hiddenColumns,
|
|
10256
|
+
onChange: onChange,
|
|
10257
|
+
onDone: ()=>setOpen(false),
|
|
10258
|
+
labels: labels
|
|
10259
|
+
})
|
|
10260
|
+
})
|
|
10261
|
+
]
|
|
10262
|
+
});
|
|
10263
|
+
}
|
|
10264
|
+
// ───── Dialog body ─────
|
|
10265
|
+
function ColumnVisibilityBody(props) {
|
|
10266
|
+
const { cols, hiddenColumns, onChange, onDone, labels } = props;
|
|
10267
|
+
// Local draft so the user can stage changes; commit on Done.
|
|
10268
|
+
const [draft, setDraft] = useState(hiddenColumns);
|
|
10269
|
+
// Re-sync when the dialog re-opens with a different external state.
|
|
10270
|
+
useEffect(()=>{
|
|
10271
|
+
setDraft(hiddenColumns);
|
|
10272
|
+
}, [
|
|
10273
|
+
hiddenColumns
|
|
10274
|
+
]);
|
|
10275
|
+
const toggle = (field)=>{
|
|
10276
|
+
setDraft((prev)=>prev.includes(field) ? prev.filter((f)=>f !== field) : [
|
|
10277
|
+
...prev,
|
|
10278
|
+
field
|
|
10279
|
+
]);
|
|
10280
|
+
};
|
|
10281
|
+
const showAll = ()=>setDraft([]);
|
|
10282
|
+
const hideAll = ()=>setDraft(cols.map((c)=>c.field));
|
|
10283
|
+
return jsxs("div", {
|
|
10284
|
+
className: "flex flex-col gap-3",
|
|
10285
|
+
children: [
|
|
10286
|
+
jsxs("div", {
|
|
10287
|
+
className: "flex items-center gap-2 pb-2 border-b border-neutral-200",
|
|
10288
|
+
children: [
|
|
10289
|
+
jsx("button", {
|
|
10290
|
+
type: "button",
|
|
10291
|
+
onClick: showAll,
|
|
10292
|
+
className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline",
|
|
10293
|
+
children: labels.columnsShowAll
|
|
10294
|
+
}),
|
|
10295
|
+
jsx("span", {
|
|
10296
|
+
className: "text-neutral-300",
|
|
10297
|
+
children: "\u00B7"
|
|
10298
|
+
}),
|
|
10299
|
+
jsx("button", {
|
|
10300
|
+
type: "button",
|
|
10301
|
+
onClick: hideAll,
|
|
10302
|
+
className: "text-xs text-neutral-600 hover:text-neutral-900 underline-offset-2 hover:underline focus:outline-none focus-visible:underline",
|
|
10303
|
+
children: labels.columnsHideAll
|
|
10304
|
+
})
|
|
10305
|
+
]
|
|
10306
|
+
}),
|
|
10307
|
+
jsx("ul", {
|
|
10308
|
+
className: "flex flex-col gap-1.5 max-h-72 overflow-auto",
|
|
10309
|
+
children: cols.map((col)=>{
|
|
10310
|
+
const field = col.field;
|
|
10311
|
+
const hidden = draft.includes(field);
|
|
10312
|
+
const header = typeof col.header === 'function' ? col.header() : col.header;
|
|
10313
|
+
return jsx("li", {
|
|
10314
|
+
children: jsxs("label", {
|
|
10315
|
+
className: "flex items-center gap-2 cursor-pointer py-1 px-1 rounded hover:bg-neutral-100",
|
|
10316
|
+
children: [
|
|
10317
|
+
jsx("input", {
|
|
10318
|
+
type: "checkbox",
|
|
10319
|
+
checked: !hidden,
|
|
10320
|
+
onChange: ()=>toggle(field),
|
|
10321
|
+
className: "cursor-pointer"
|
|
10322
|
+
}),
|
|
10323
|
+
jsx("span", {
|
|
10324
|
+
className: "text-sm text-neutral-900",
|
|
10325
|
+
children: header
|
|
10326
|
+
})
|
|
10327
|
+
]
|
|
10328
|
+
})
|
|
10329
|
+
}, field);
|
|
10330
|
+
})
|
|
10331
|
+
}),
|
|
10332
|
+
jsx("div", {
|
|
10333
|
+
className: "flex justify-end gap-2 pt-2 border-t border-neutral-200",
|
|
10334
|
+
children: jsx("button", {
|
|
10335
|
+
type: "button",
|
|
10336
|
+
onClick: ()=>{
|
|
10337
|
+
onChange(draft);
|
|
10338
|
+
onDone();
|
|
10339
|
+
},
|
|
10340
|
+
className: "text-sm bg-primary-500 text-white rounded-md hover:bg-primary-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 px-3 py-1.5",
|
|
10341
|
+
children: labels.columnsDone
|
|
10342
|
+
})
|
|
10343
|
+
})
|
|
10344
|
+
]
|
|
10345
|
+
});
|
|
10346
|
+
}
|
|
10347
|
+
// ───── Icon ─────
|
|
10348
|
+
function ColumnsIcon() {
|
|
10349
|
+
return jsxs("svg", {
|
|
10350
|
+
width: "0.875em",
|
|
10351
|
+
height: "0.875em",
|
|
10352
|
+
viewBox: "0 0 16 16",
|
|
10353
|
+
fill: "none",
|
|
10354
|
+
stroke: "currentColor",
|
|
10355
|
+
strokeWidth: "1.5",
|
|
10356
|
+
strokeLinecap: "round",
|
|
10357
|
+
strokeLinejoin: "round",
|
|
10358
|
+
"aria-hidden": "true",
|
|
10359
|
+
children: [
|
|
10360
|
+
jsx("rect", {
|
|
10361
|
+
x: "2",
|
|
10362
|
+
y: "3",
|
|
10363
|
+
width: "3.5",
|
|
10364
|
+
height: "10",
|
|
10365
|
+
rx: "0.5"
|
|
10366
|
+
}),
|
|
10367
|
+
jsx("rect", {
|
|
10368
|
+
x: "6.25",
|
|
10369
|
+
y: "3",
|
|
10370
|
+
width: "3.5",
|
|
10371
|
+
height: "10",
|
|
10372
|
+
rx: "0.5"
|
|
10373
|
+
}),
|
|
10374
|
+
jsx("rect", {
|
|
10375
|
+
x: "10.5",
|
|
10376
|
+
y: "3",
|
|
10377
|
+
width: "3.5",
|
|
10378
|
+
height: "10",
|
|
10379
|
+
rx: "0.5"
|
|
10380
|
+
})
|
|
10381
|
+
]
|
|
10382
|
+
});
|
|
10383
|
+
}
|
|
10384
|
+
|
|
10385
|
+
function useColumnResize(args) {
|
|
10386
|
+
const { widths, onChange, minWidth = 40, maxWidth = 1200 } = args;
|
|
10387
|
+
// We need the LATEST widths inside the pointermove closure. Stick
|
|
10388
|
+
// them in a ref so the closure captured at pointerdown stays valid
|
|
10389
|
+
// for the duration of the drag.
|
|
10390
|
+
const widthsRef = useRef(widths);
|
|
10391
|
+
widthsRef.current = widths;
|
|
10392
|
+
const startResize = useCallback((field, startWidth, clampMin, clampMax)=>{
|
|
10393
|
+
return (e)=>{
|
|
10394
|
+
// Only respond to the primary mouse button (or touch / pen).
|
|
10395
|
+
if (e.pointerType === 'mouse' && e.button !== 0) return;
|
|
10396
|
+
e.preventDefault();
|
|
10397
|
+
e.stopPropagation();
|
|
10398
|
+
const startX = e.clientX;
|
|
10399
|
+
const target = e.currentTarget;
|
|
10400
|
+
try {
|
|
10401
|
+
target.setPointerCapture(e.pointerId);
|
|
10402
|
+
} catch (unused) {
|
|
10403
|
+
// jsdom or older browsers — fine to fall back to global listeners.
|
|
10404
|
+
}
|
|
10405
|
+
const effectiveMin = Math.max(minWidth, clampMin);
|
|
10406
|
+
const effectiveMax = Math.min(maxWidth, clampMax);
|
|
10407
|
+
const handleMove = (ev)=>{
|
|
10408
|
+
const delta = ev.clientX - startX;
|
|
10409
|
+
const next = clamp(startWidth + delta, effectiveMin, effectiveMax);
|
|
10410
|
+
onChange(_extends({}, widthsRef.current, {
|
|
10411
|
+
[field]: next
|
|
10412
|
+
}));
|
|
10413
|
+
};
|
|
10414
|
+
const handleEnd = (ev)=>{
|
|
10415
|
+
try {
|
|
10416
|
+
target.releasePointerCapture(ev.pointerId);
|
|
10417
|
+
} catch (unused) {
|
|
10418
|
+
/* noop */ }
|
|
10419
|
+
target.removeEventListener('pointermove', handleMove);
|
|
10420
|
+
target.removeEventListener('pointerup', handleEnd);
|
|
10421
|
+
target.removeEventListener('pointercancel', handleEnd);
|
|
10422
|
+
};
|
|
10423
|
+
target.addEventListener('pointermove', handleMove);
|
|
10424
|
+
target.addEventListener('pointerup', handleEnd);
|
|
10425
|
+
target.addEventListener('pointercancel', handleEnd);
|
|
10426
|
+
};
|
|
10427
|
+
}, [
|
|
10428
|
+
onChange,
|
|
10429
|
+
minWidth,
|
|
10430
|
+
maxWidth
|
|
10431
|
+
]);
|
|
10432
|
+
return {
|
|
10433
|
+
startResize
|
|
10434
|
+
};
|
|
10435
|
+
}
|
|
10436
|
+
function clamp(n, min, max) {
|
|
10437
|
+
if (n < min) return min;
|
|
10438
|
+
if (n > max) return max;
|
|
10439
|
+
return n;
|
|
10440
|
+
}
|
|
10441
|
+
|
|
10442
|
+
function useColumnReorder(args) {
|
|
10443
|
+
const { order, onChange } = args;
|
|
10444
|
+
const sourceRef = useRef(null);
|
|
10445
|
+
const [dropTarget, setDropTarget] = useState(null);
|
|
10446
|
+
const [draggingField, setDraggingField] = useState(null);
|
|
10447
|
+
const dragStart = useCallback((field)=>(e)=>{
|
|
10448
|
+
sourceRef.current = field;
|
|
10449
|
+
setDraggingField(field);
|
|
10450
|
+
try {
|
|
10451
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
10452
|
+
// Some browsers refuse to start a drag if no data is set.
|
|
10453
|
+
e.dataTransfer.setData('text/plain', field);
|
|
10454
|
+
} catch (unused) {
|
|
10455
|
+
/* noop — jsdom doesn't implement dataTransfer fully. */ }
|
|
10456
|
+
}, []);
|
|
10457
|
+
const dragOver = useCallback((field)=>(e)=>{
|
|
10458
|
+
if (sourceRef.current == null) return;
|
|
10459
|
+
if (field === sourceRef.current) return;
|
|
10460
|
+
e.preventDefault();
|
|
10461
|
+
e.dataTransfer.dropEffect = 'move';
|
|
10462
|
+
// Determine LEFT vs RIGHT half of the target.
|
|
10463
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
10464
|
+
const midpoint = rect.left + rect.width / 2;
|
|
10465
|
+
const side = e.clientX < midpoint ? 'left' : 'right';
|
|
10466
|
+
setDropTarget((prev)=>prev && prev.field === field && prev.side === side ? prev : {
|
|
10467
|
+
field,
|
|
10468
|
+
side
|
|
10469
|
+
});
|
|
10470
|
+
}, []);
|
|
10471
|
+
const drop = useCallback((field)=>(e)=>{
|
|
10472
|
+
e.preventDefault();
|
|
10473
|
+
const source = sourceRef.current;
|
|
10474
|
+
sourceRef.current = null;
|
|
10475
|
+
setDraggingField(null);
|
|
10476
|
+
const target = dropTarget;
|
|
10477
|
+
setDropTarget(null);
|
|
10478
|
+
if (!source || source === field) return;
|
|
10479
|
+
const side = (target == null ? void 0 : target.field) === field ? target.side : 'right';
|
|
10480
|
+
const next = moveColumn(order, source, field, side);
|
|
10481
|
+
if (!arraysEqual(next, order)) onChange(next);
|
|
10482
|
+
}, [
|
|
10483
|
+
order,
|
|
10484
|
+
onChange,
|
|
10485
|
+
dropTarget
|
|
10486
|
+
]);
|
|
10487
|
+
const dragEnd = useCallback(()=>{
|
|
10488
|
+
sourceRef.current = null;
|
|
10489
|
+
setDraggingField(null);
|
|
10490
|
+
setDropTarget(null);
|
|
10491
|
+
}, []);
|
|
10492
|
+
return {
|
|
10493
|
+
dropTarget,
|
|
10494
|
+
draggingField,
|
|
10495
|
+
dragStart,
|
|
10496
|
+
dragOver,
|
|
10497
|
+
drop,
|
|
10498
|
+
dragEnd
|
|
10499
|
+
};
|
|
10500
|
+
}
|
|
10501
|
+
/**
|
|
10502
|
+
* Move `source` next to `target` in the `order` list.
|
|
10503
|
+
* `side='left'` inserts before; `side='right'` inserts after.
|
|
10504
|
+
* Exposed for unit testing.
|
|
10505
|
+
*/ function moveColumn(order, source, target, side) {
|
|
10506
|
+
const withoutSource = order.filter((f)=>f !== source);
|
|
10507
|
+
const targetIndex = withoutSource.indexOf(target);
|
|
10508
|
+
if (targetIndex === -1) return order;
|
|
10509
|
+
const insertAt = side === 'left' ? targetIndex : targetIndex + 1;
|
|
10510
|
+
return [
|
|
10511
|
+
...withoutSource.slice(0, insertAt),
|
|
10512
|
+
source,
|
|
10513
|
+
...withoutSource.slice(insertAt)
|
|
10514
|
+
];
|
|
10515
|
+
}
|
|
10516
|
+
function arraysEqual(a, b) {
|
|
10517
|
+
if (a === b) return true;
|
|
10518
|
+
if (a.length !== b.length) return false;
|
|
10519
|
+
for(let i = 0; i < a.length; i++){
|
|
10520
|
+
if (a[i] !== b[i]) return false;
|
|
10521
|
+
}
|
|
10522
|
+
return true;
|
|
10523
|
+
}
|
|
10524
|
+
|
|
10525
|
+
const DEFAULT_LABELS = {
|
|
10526
|
+
searchPlaceholder: 'Search...',
|
|
10527
|
+
noData: 'No data',
|
|
10528
|
+
loading: 'Loading…',
|
|
10529
|
+
ariaSortNone: 'Not sorted. Click to sort ascending.',
|
|
10530
|
+
ariaSortAscending: 'Sorted ascending. Click to sort descending.',
|
|
10531
|
+
ariaSortDescending: 'Sorted descending. Click to clear sort.',
|
|
10532
|
+
ariaSelectRow: 'Select row',
|
|
10533
|
+
ariaSelectAllRows: 'Select all rows',
|
|
10534
|
+
ariaExpandRow: 'Expand row',
|
|
10535
|
+
ariaCollapseRow: 'Collapse row',
|
|
10536
|
+
selectedCount: '{count} selected',
|
|
10537
|
+
clearSelection: 'Clear selection',
|
|
10538
|
+
filterColumn: 'Filter',
|
|
10539
|
+
filterApply: 'Apply',
|
|
10540
|
+
filterClear: 'Clear',
|
|
10541
|
+
filterMin: 'Min',
|
|
10542
|
+
filterMax: 'Max',
|
|
10543
|
+
filterFrom: 'From',
|
|
10544
|
+
filterTo: 'To',
|
|
10545
|
+
filterAll: 'All',
|
|
10546
|
+
filterTrue: 'True',
|
|
10547
|
+
filterFalse: 'False',
|
|
10548
|
+
density: 'Density',
|
|
10549
|
+
densityCompact: 'Compact',
|
|
10550
|
+
densityComfortable: 'Comfortable',
|
|
10551
|
+
densitySpacious: 'Spacious',
|
|
10552
|
+
columnsButton: 'Columns',
|
|
10553
|
+
columnsTitle: 'Manage columns',
|
|
10554
|
+
columnsDescription: 'Show or hide columns. Required columns cannot be toggled.',
|
|
10555
|
+
columnsShowAll: 'Show all',
|
|
10556
|
+
columnsHideAll: 'Hide all',
|
|
10557
|
+
columnsDone: 'Done'
|
|
10558
|
+
};
|
|
10559
|
+
/**
|
|
10560
|
+
* Dashforge TW `<DataGrid>` — virtualized data table.
|
|
10561
|
+
*
|
|
10562
|
+
* Companion to `<Table>` for large data sets (~500+ rows up to
|
|
10563
|
+
* millions via virtualization). Same column model, sort/search/
|
|
10564
|
+
* filter/selection logic (shared via `_shared/data/` helpers) —
|
|
10565
|
+
* differs in render path: only the visible window of rows is mounted
|
|
10566
|
+
* in DOM, with spacer `<tr>` above and below to preserve scrollbar
|
|
10567
|
+
* size.
|
|
10568
|
+
*
|
|
10569
|
+
* Virtualization: hand-rolled (`useVirtualizer`) using
|
|
10570
|
+
* `IntersectionObserver` + scroll-event + `requestAnimationFrame`
|
|
10571
|
+
* debounce + `ResizeObserver`. Zero new runtime deps.
|
|
10572
|
+
*
|
|
10573
|
+
* Server-side mode (4 independent opt-in flags):
|
|
10574
|
+
* - `serverSideSort` — emits `onSortChange` but does NOT sort locally
|
|
10575
|
+
* - `serverSideFilter` — emits `onFilterChange` but does NOT filter
|
|
10576
|
+
* - `serverSideSearch` — emits `onSearchQueryChange` (debounced)
|
|
10577
|
+
* - `serverSidePagination` — `totalCount` prop sizes the virtual
|
|
10578
|
+
* scrollbar; `rows` is the page slice from the server
|
|
10579
|
+
*
|
|
10580
|
+
* Selection at scale: `selectAllScope`:
|
|
10581
|
+
* - `'allLoaded'` (default) — header checkbox toggles all rows in
|
|
10582
|
+
* `rows` prop
|
|
10583
|
+
* - `'visible'` — toggles only the current viewport window
|
|
10584
|
+
*/ function DataGrid(props) {
|
|
10585
|
+
var _slotProps_root, _slotProps_toolbar, _slotProps_search, _slotProps_scroll, _slotProps_table, _slotProps_thead, _slotProps_headerRow, _slotProps_headerCell, _slotProps_selectionCell, _slotProps_headerCell1, _slotProps_tbody, _slotProps_row, _slotProps_cell, _slotProps_emptyState, _slotProps_row1, _slotProps_cell1, _slotProps_cell2, _slotProps_selectionCell1, _slotProps_cell3, _slotProps_rowActionsCell, _slotProps_bulkActionFooter, _slotProps_pagination;
|
|
10586
|
+
const { rows, cols, getRowId, totalCount: totalCountProp, rowHeight, overscan = 5, height, sortModel: sortModelProp, onSortChange, defaultSortModel, serverSideSort = false, enableSearch = false, searchQuery: searchQueryProp, onSearchQueryChange, searchPlaceholder, searchDebounceMs = 200, serverSideSearch = false, filterModel: filterModelProp, onFilterChange, serverSideFilter = false, hiddenColumns: hiddenColumnsProp, onHiddenColumnsChange, enableColumnVisibility = true, columnWidths: columnWidthsProp, onColumnWidthsChange, enableColumnResize = true, columnOrder: columnOrderProp, onColumnOrderChange, enableColumnReorder = true, rowSelection = 'none', selectedRowIds: selectedRowIdsProp, onSelectionChange, bulkActions, selectAllScope = 'allLoaded', pagination, serverSidePagination = false, rowActions, loading = false, loadingRowCount = 8, emptyState, stickyHeader = true, caption, showCaption = false, access: gridAccess, labels: labelsProp, variant, size, density, sx, slotProps } = props;
|
|
10587
|
+
const labels = _extends({}, DEFAULT_LABELS, labelsProp);
|
|
10588
|
+
const gridAccessState = useAccessState(gridAccess);
|
|
10589
|
+
// ───── Controllable state ─────
|
|
10590
|
+
const [sortModel, setSortModel] = useControllableState({
|
|
10591
|
+
controlledValue: sortModelProp,
|
|
10592
|
+
defaultValue: defaultSortModel != null ? defaultSortModel : [],
|
|
10593
|
+
onChange: onSortChange
|
|
10594
|
+
});
|
|
10595
|
+
const [searchQuery, setSearchQuery] = useControllableState({
|
|
10596
|
+
controlledValue: searchQueryProp,
|
|
10597
|
+
defaultValue: '',
|
|
10598
|
+
onChange: onSearchQueryChange
|
|
10599
|
+
});
|
|
10600
|
+
// filterModel: in v1-bis the per-column header UI commits filter
|
|
10601
|
+
// items to this controllable state. Consumer can lift it via
|
|
10602
|
+
// filterModelProp + onFilterChange to drive server-side filtering.
|
|
10603
|
+
const [filterModel, setFilterModel] = useControllableState({
|
|
10604
|
+
controlledValue: filterModelProp,
|
|
10605
|
+
defaultValue: [],
|
|
10606
|
+
onChange: onFilterChange
|
|
10607
|
+
});
|
|
10608
|
+
const [selectedRowIds, setSelectedRowIds] = useControllableState({
|
|
10609
|
+
controlledValue: selectedRowIdsProp,
|
|
10610
|
+
defaultValue: [],
|
|
10611
|
+
onChange: onSelectionChange
|
|
10612
|
+
});
|
|
10613
|
+
// ───── Column visibility ─────
|
|
10614
|
+
// Seed default hidden set from `defaultHidden` flag on columns.
|
|
10615
|
+
// Memoized to avoid the seed shifting on every render — the
|
|
10616
|
+
// controllable state keeps internal state stable thereafter.
|
|
10617
|
+
const defaultHiddenColumns = useMemo(()=>cols.filter((c)=>c.defaultHidden === true).map((c)=>c.field), [
|
|
10618
|
+
cols
|
|
10619
|
+
]);
|
|
10620
|
+
const [hiddenColumns, setHiddenColumns] = useControllableState({
|
|
10621
|
+
controlledValue: hiddenColumnsProp,
|
|
10622
|
+
defaultValue: defaultHiddenColumns,
|
|
10623
|
+
onChange: onHiddenColumnsChange
|
|
10624
|
+
});
|
|
10625
|
+
// ───── Column widths (resize state) ─────
|
|
10626
|
+
// Seed from any `col.width` declared statically. Internal state is
|
|
10627
|
+
// applied only when the user actually starts dragging; until then
|
|
10628
|
+
// we fall back to the static width from the column def.
|
|
10629
|
+
const defaultColumnWidths = useMemo(()=>{
|
|
10630
|
+
const map = {};
|
|
10631
|
+
for (const c of cols){
|
|
10632
|
+
if (typeof c.width === 'number') {
|
|
10633
|
+
map[c.field] = c.width;
|
|
10634
|
+
}
|
|
10635
|
+
}
|
|
10636
|
+
return map;
|
|
10637
|
+
}, [
|
|
10638
|
+
cols
|
|
10639
|
+
]);
|
|
10640
|
+
const [columnWidths, setColumnWidths] = useControllableState({
|
|
10641
|
+
controlledValue: columnWidthsProp,
|
|
10642
|
+
defaultValue: defaultColumnWidths,
|
|
10643
|
+
onChange: onColumnWidthsChange
|
|
10644
|
+
});
|
|
10645
|
+
const { startResize } = useColumnResize({
|
|
10646
|
+
widths: columnWidths,
|
|
10647
|
+
onChange: setColumnWidths
|
|
10648
|
+
});
|
|
10649
|
+
// ───── Column order (reorder state) ─────
|
|
10650
|
+
const defaultColumnOrder = useMemo(()=>cols.map((c)=>c.field), [
|
|
10651
|
+
cols
|
|
10652
|
+
]);
|
|
10653
|
+
const [columnOrder, setColumnOrder] = useControllableState({
|
|
10654
|
+
controlledValue: columnOrderProp,
|
|
10655
|
+
defaultValue: defaultColumnOrder,
|
|
10656
|
+
onChange: onColumnOrderChange
|
|
10657
|
+
});
|
|
10658
|
+
const reorderHandlers = useColumnReorder({
|
|
10659
|
+
order: columnOrder,
|
|
10660
|
+
onChange: setColumnOrder
|
|
10661
|
+
});
|
|
10662
|
+
// Apply the active order to the columns list. Cols not in the
|
|
10663
|
+
// order array (e.g. newly-added columns at runtime) are appended
|
|
10664
|
+
// in their original position from `cols`.
|
|
10665
|
+
const orderedCols = useMemo(()=>{
|
|
10666
|
+
if (!columnOrder || columnOrder.length === 0) return cols;
|
|
10667
|
+
const byField = new Map(cols.map((c)=>[
|
|
10668
|
+
c.field,
|
|
10669
|
+
c
|
|
10670
|
+
]));
|
|
10671
|
+
const seen = new Set();
|
|
10672
|
+
const result = [];
|
|
10673
|
+
for (const field of columnOrder){
|
|
10674
|
+
const c = byField.get(field);
|
|
10675
|
+
if (c) {
|
|
10676
|
+
result.push(c);
|
|
10677
|
+
seen.add(field);
|
|
10678
|
+
}
|
|
10679
|
+
}
|
|
10680
|
+
for (const c of cols){
|
|
10681
|
+
if (!seen.has(c.field)) result.push(c);
|
|
10682
|
+
}
|
|
10683
|
+
return result;
|
|
10684
|
+
}, [
|
|
10685
|
+
cols,
|
|
10686
|
+
columnOrder
|
|
10687
|
+
]);
|
|
10688
|
+
// ───── RBAC + visibility + reorder: derive final column list ─────
|
|
10689
|
+
// - `access.onUnauthorized === 'hide'` removes a column entirely.
|
|
10690
|
+
// - `hiddenColumns` is the user-toggled hidden set from the
|
|
10691
|
+
// column-visibility dialog.
|
|
10692
|
+
// - `orderedCols` is the user-reordered version of `cols`.
|
|
10693
|
+
// NOTE: filtering happens on `orderedCols` so the active order is
|
|
10694
|
+
// preserved through visibility changes.
|
|
10695
|
+
const visibleCols = useMemo(()=>orderedCols.filter((col)=>{
|
|
10696
|
+
if (col.access && col.access.onUnauthorized === 'hide') return false;
|
|
10697
|
+
if (hiddenColumns.includes(col.field)) return false;
|
|
10698
|
+
return true;
|
|
10699
|
+
}), [
|
|
10700
|
+
orderedCols,
|
|
10701
|
+
hiddenColumns
|
|
10702
|
+
]);
|
|
10703
|
+
// ───── Smart-default column types ─────
|
|
10704
|
+
const columnTypes = useColumnAutoDetect(rows, visibleCols);
|
|
10705
|
+
// ───── Pipeline: search → filter → sort (skip when server-side) ─────
|
|
10706
|
+
//
|
|
10707
|
+
// Each pipeline hook is ALWAYS called (React hooks rules). The
|
|
10708
|
+
// server-side flags decide whether to use the local-computed result
|
|
10709
|
+
// or pass the upstream rows through unchanged. The hooks are
|
|
10710
|
+
// `useMemo`-based internally so they're cheap when their inputs
|
|
10711
|
+
// are unchanged.
|
|
10712
|
+
const debouncedSearchQuery = useDebouncedValue(searchQuery, searchDebounceMs);
|
|
10713
|
+
const localSearched = useTableSearch(rows, visibleCols, debouncedSearchQuery);
|
|
10714
|
+
const searchedRows = serverSideSearch ? rows : localSearched;
|
|
10715
|
+
const localFiltered = useTableFilter(searchedRows, filterModel);
|
|
10716
|
+
const filteredRows = serverSideFilter ? searchedRows : localFiltered;
|
|
10717
|
+
const localSorted = useTableSort(filteredRows, visibleCols, sortModel);
|
|
10718
|
+
const sortedRows = serverSideSort ? filteredRows : localSorted;
|
|
10719
|
+
// ───── Virtualization ─────
|
|
10720
|
+
// Effective row count for the virtual scrollbar:
|
|
10721
|
+
// - Client-side: actual sorted rows count
|
|
10722
|
+
// - Server-side pagination: caller-provided totalCount
|
|
10723
|
+
const effectiveTotalCount = serverSidePagination ? totalCountProp != null ? totalCountProp : sortedRows.length : sortedRows.length;
|
|
10724
|
+
const initialViewportHeight = height ? parseInt(height, 10) || 400 : 400;
|
|
10725
|
+
const { scrollRef, window: vWindow } = useVirtualizer({
|
|
10726
|
+
totalCount: effectiveTotalCount,
|
|
10727
|
+
rowHeight,
|
|
10728
|
+
overscan,
|
|
10729
|
+
initialViewportHeight
|
|
10730
|
+
});
|
|
10731
|
+
// The rows actually mounted in DOM.
|
|
10732
|
+
const renderedRows = useMemo(()=>{
|
|
10733
|
+
if (serverSidePagination) {
|
|
10734
|
+
// In server-side pagination, `rows` IS the page slice; we
|
|
10735
|
+
// render all of it (virtualization within the page is still
|
|
10736
|
+
// useful for very large page sizes).
|
|
10737
|
+
return rows.slice(vWindow.startIndex, vWindow.endIndex + 1);
|
|
10738
|
+
}
|
|
10739
|
+
return sortedRows.slice(vWindow.startIndex, vWindow.endIndex + 1);
|
|
10740
|
+
}, [
|
|
10741
|
+
serverSidePagination,
|
|
10742
|
+
rows,
|
|
10743
|
+
sortedRows,
|
|
10744
|
+
vWindow.startIndex,
|
|
10745
|
+
vWindow.endIndex
|
|
10746
|
+
]);
|
|
10747
|
+
// ───── Selection helpers ─────
|
|
10748
|
+
// For `selectAllScope='visible'`, the select-all scope is the
|
|
10749
|
+
// current window. For `'allLoaded'`, it's the whole sorted set.
|
|
10750
|
+
const selectAllPool = useMemo(()=>{
|
|
10751
|
+
if (selectAllScope === 'visible') {
|
|
10752
|
+
return renderedRows.map((row, idx)=>getRowId(row, vWindow.startIndex + idx));
|
|
10753
|
+
}
|
|
10754
|
+
return sortedRows.map((row, idx)=>getRowId(row, idx));
|
|
10755
|
+
}, [
|
|
10756
|
+
selectAllScope,
|
|
10757
|
+
renderedRows,
|
|
10758
|
+
sortedRows,
|
|
10759
|
+
getRowId,
|
|
10760
|
+
vWindow.startIndex
|
|
10761
|
+
]);
|
|
10762
|
+
const selection = useTableSelection(rowSelection, selectedRowIds, setSelectedRowIds);
|
|
10763
|
+
const selectedRows = useMemo(()=>sortedRows.filter((row, idx)=>selection.isSelected(getRowId(row, idx))), [
|
|
10764
|
+
sortedRows,
|
|
10765
|
+
selection,
|
|
10766
|
+
getRowId
|
|
10767
|
+
]);
|
|
10768
|
+
// ───── Variant recipe ─────
|
|
10769
|
+
const v = dataGridVariants({
|
|
10770
|
+
variant,
|
|
10771
|
+
size,
|
|
10772
|
+
density
|
|
10773
|
+
});
|
|
10774
|
+
// ───── Render ─────
|
|
10775
|
+
if (!gridAccessState.visible) return null;
|
|
10776
|
+
const isInteractive = !gridAccessState.disabled;
|
|
10777
|
+
const showSelectionColumn = rowSelection !== 'none';
|
|
10778
|
+
const showRowActionsColumn = rowActions != null;
|
|
10779
|
+
const totalColumnCount = visibleCols.length + (showSelectionColumn ? 1 : 0) + (showRowActionsColumn ? 1 : 0);
|
|
10780
|
+
return jsxs("div", {
|
|
10781
|
+
className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
|
|
10782
|
+
"data-disabled": !isInteractive ? 'true' : undefined,
|
|
10783
|
+
children: [
|
|
10784
|
+
(enableSearch || enableColumnVisibility) && jsxs("div", {
|
|
10785
|
+
className: cn(v.toolbar(), slotProps == null ? void 0 : (_slotProps_toolbar = slotProps.toolbar) == null ? void 0 : _slotProps_toolbar.className),
|
|
10786
|
+
children: [
|
|
10787
|
+
enableSearch && jsxs("label", {
|
|
10788
|
+
className: cn(v.search(), slotProps == null ? void 0 : (_slotProps_search = slotProps.search) == null ? void 0 : _slotProps_search.className),
|
|
10789
|
+
children: [
|
|
10790
|
+
jsx("span", {
|
|
10791
|
+
className: "sr-only",
|
|
10792
|
+
children: labels.searchPlaceholder
|
|
10793
|
+
}),
|
|
10794
|
+
jsx(SearchIcon, {
|
|
10795
|
+
className: "ml-2 shrink-0 text-neutral-500"
|
|
10796
|
+
}),
|
|
10797
|
+
jsx("input", {
|
|
10798
|
+
type: "text",
|
|
10799
|
+
value: searchQuery,
|
|
10800
|
+
onChange: (e)=>setSearchQuery(e.target.value),
|
|
10801
|
+
placeholder: searchPlaceholder != null ? searchPlaceholder : labels.searchPlaceholder,
|
|
10802
|
+
disabled: !isInteractive,
|
|
10803
|
+
className: cn('flex-1 bg-transparent border-0 outline-none', 'px-2 py-1.5', 'placeholder:text-neutral-400', 'disabled:opacity-50 disabled:cursor-not-allowed')
|
|
10804
|
+
})
|
|
10805
|
+
]
|
|
10806
|
+
}),
|
|
10807
|
+
enableColumnVisibility && jsx(ColumnVisibilityTrigger, {
|
|
10808
|
+
cols: cols.filter((c)=>// RBAC-hidden columns are never offered in the dialog.
|
|
10809
|
+
!c.access || c.access.onUnauthorized !== 'hide'),
|
|
10810
|
+
hiddenColumns: hiddenColumns,
|
|
10811
|
+
onChange: setHiddenColumns,
|
|
10812
|
+
disabled: !isInteractive,
|
|
10813
|
+
labels: {
|
|
10814
|
+
columnsButton: labels.columnsButton,
|
|
10815
|
+
columnsTitle: labels.columnsTitle,
|
|
10816
|
+
columnsDescription: labels.columnsDescription,
|
|
10817
|
+
columnsShowAll: labels.columnsShowAll,
|
|
10818
|
+
columnsHideAll: labels.columnsHideAll,
|
|
10819
|
+
columnsDone: labels.columnsDone
|
|
10820
|
+
}
|
|
10821
|
+
})
|
|
10822
|
+
]
|
|
10823
|
+
}),
|
|
10824
|
+
jsx("div", {
|
|
10825
|
+
ref: scrollRef,
|
|
10826
|
+
className: cn(v.scroll(), slotProps == null ? void 0 : (_slotProps_scroll = slotProps.scroll) == null ? void 0 : _slotProps_scroll.className),
|
|
10827
|
+
style: height ? {
|
|
10828
|
+
height,
|
|
10829
|
+
maxHeight: height
|
|
10830
|
+
} : undefined,
|
|
10831
|
+
children: jsxs("table", {
|
|
10832
|
+
className: cn(v.table(), slotProps == null ? void 0 : (_slotProps_table = slotProps.table) == null ? void 0 : _slotProps_table.className),
|
|
10833
|
+
children: [
|
|
10834
|
+
caption != null && jsx("caption", {
|
|
10835
|
+
className: cn(!showCaption && 'sr-only', 'py-2 text-sm text-neutral-500'),
|
|
10836
|
+
children: caption
|
|
10837
|
+
}),
|
|
10838
|
+
jsx("thead", {
|
|
10839
|
+
className: cn(stickyHeader ? v.thead() : 'bg-neutral-50', slotProps == null ? void 0 : (_slotProps_thead = slotProps.thead) == null ? void 0 : _slotProps_thead.className),
|
|
10840
|
+
children: jsxs("tr", {
|
|
10841
|
+
className: cn(v.headerRow(), slotProps == null ? void 0 : (_slotProps_headerRow = slotProps.headerRow) == null ? void 0 : _slotProps_headerRow.className),
|
|
10842
|
+
children: [
|
|
10843
|
+
showSelectionColumn && jsx("th", {
|
|
10844
|
+
scope: "col",
|
|
10845
|
+
className: cn(v.headerCell(), v.selectionCell(), v.stickyLeftHeaderCell(), slotProps == null ? void 0 : (_slotProps_headerCell = slotProps.headerCell) == null ? void 0 : _slotProps_headerCell.className, slotProps == null ? void 0 : (_slotProps_selectionCell = slotProps.selectionCell) == null ? void 0 : _slotProps_selectionCell.className),
|
|
10846
|
+
children: rowSelection === 'multiple' && jsx("input", {
|
|
10847
|
+
type: "checkbox",
|
|
10848
|
+
"aria-label": labels.ariaSelectAllRows,
|
|
10849
|
+
checked: selection.isAllSelected(selectAllPool),
|
|
10850
|
+
ref: (el)=>{
|
|
10851
|
+
if (el) el.indeterminate = selection.isIndeterminate(selectAllPool);
|
|
10852
|
+
},
|
|
10853
|
+
onChange: ()=>selection.toggleAll(selectAllPool),
|
|
10854
|
+
disabled: !isInteractive,
|
|
10855
|
+
className: "cursor-pointer"
|
|
10856
|
+
})
|
|
10857
|
+
}),
|
|
10858
|
+
visibleCols.map((col)=>{
|
|
10859
|
+
var _slotProps_headerCell;
|
|
10860
|
+
return jsx(HeaderCell, {
|
|
10861
|
+
col: col,
|
|
10862
|
+
sortModel: sortModel,
|
|
10863
|
+
onSortClick: (e)=>handleSortClick(col, e, sortModel, setSortModel),
|
|
10864
|
+
inferredType: columnTypes.get(col.field),
|
|
10865
|
+
filterModel: filterModel,
|
|
10866
|
+
onFilterChange: setFilterModel,
|
|
10867
|
+
effectiveWidth: resolveColumnWidth(col, columnWidths),
|
|
10868
|
+
resizable: enableColumnResize && col.resizable !== false,
|
|
10869
|
+
startResize: startResize,
|
|
10870
|
+
reorderable: enableColumnReorder && col.reorderable !== false,
|
|
10871
|
+
reorderHandlers: reorderHandlers,
|
|
10872
|
+
labels: labels,
|
|
10873
|
+
className: cn(v.headerCell(), col.sticky === 'left' && v.stickyLeftHeaderCell(), col.sticky === 'right' && v.stickyRightHeaderCell(), slotProps == null ? void 0 : (_slotProps_headerCell = slotProps.headerCell) == null ? void 0 : _slotProps_headerCell.className),
|
|
10874
|
+
buttonClassName: v.headerCellButton(),
|
|
10875
|
+
disabled: !isInteractive
|
|
10876
|
+
}, col.field);
|
|
10877
|
+
}),
|
|
10878
|
+
showRowActionsColumn && jsx("th", {
|
|
10879
|
+
scope: "col",
|
|
10880
|
+
"aria-hidden": "true",
|
|
10881
|
+
className: cn(v.headerCell(), 'w-12', slotProps == null ? void 0 : (_slotProps_headerCell1 = slotProps.headerCell) == null ? void 0 : _slotProps_headerCell1.className)
|
|
10882
|
+
})
|
|
10883
|
+
]
|
|
10884
|
+
})
|
|
10885
|
+
}),
|
|
10886
|
+
jsx("tbody", {
|
|
10887
|
+
className: cn(v.tbody(), slotProps == null ? void 0 : (_slotProps_tbody = slotProps.tbody) == null ? void 0 : _slotProps_tbody.className),
|
|
10888
|
+
children: loading ? renderLoadingRows({
|
|
10889
|
+
count: loadingRowCount,
|
|
10890
|
+
visibleCols,
|
|
10891
|
+
showSelectionColumn,
|
|
10892
|
+
showRowActionsColumn,
|
|
10893
|
+
rowHeight,
|
|
10894
|
+
rowClass: cn(v.row(), slotProps == null ? void 0 : (_slotProps_row = slotProps.row) == null ? void 0 : _slotProps_row.className),
|
|
10895
|
+
cellClass: cn(v.cell(), slotProps == null ? void 0 : (_slotProps_cell = slotProps.cell) == null ? void 0 : _slotProps_cell.className)
|
|
10896
|
+
}) : sortedRows.length === 0 ? renderEmptyState({
|
|
10897
|
+
totalColumnCount,
|
|
10898
|
+
emptyState,
|
|
10899
|
+
fallback: labels.noData,
|
|
10900
|
+
cellClass: cn(v.cell(), v.emptyState(), slotProps == null ? void 0 : (_slotProps_emptyState = slotProps.emptyState) == null ? void 0 : _slotProps_emptyState.className)
|
|
10901
|
+
}) : renderVirtualizedBody({
|
|
10902
|
+
vWindow,
|
|
10903
|
+
renderedRows,
|
|
10904
|
+
visibleCols,
|
|
10905
|
+
columnTypes,
|
|
10906
|
+
columnWidths,
|
|
10907
|
+
selection,
|
|
10908
|
+
rowSelection,
|
|
10909
|
+
showSelectionColumn,
|
|
10910
|
+
showRowActionsColumn,
|
|
10911
|
+
rowActions,
|
|
10912
|
+
getRowId,
|
|
10913
|
+
isInteractive,
|
|
10914
|
+
labels,
|
|
10915
|
+
classes: {
|
|
10916
|
+
row: cn(v.row(), slotProps == null ? void 0 : (_slotProps_row1 = slotProps.row) == null ? void 0 : _slotProps_row1.className),
|
|
10917
|
+
cell: cn(v.cell(), slotProps == null ? void 0 : (_slotProps_cell1 = slotProps.cell) == null ? void 0 : _slotProps_cell1.className),
|
|
10918
|
+
selectionCell: cn(v.cell(), v.selectionCell(), v.stickyLeftCell(), slotProps == null ? void 0 : (_slotProps_cell2 = slotProps.cell) == null ? void 0 : _slotProps_cell2.className, slotProps == null ? void 0 : (_slotProps_selectionCell1 = slotProps.selectionCell) == null ? void 0 : _slotProps_selectionCell1.className),
|
|
10919
|
+
stickyLeftCell: v.stickyLeftCell(),
|
|
10920
|
+
stickyRightCell: v.stickyRightCell(),
|
|
10921
|
+
rowActionsCell: cn(v.cell(), v.rowActionsCell(), slotProps == null ? void 0 : (_slotProps_cell3 = slotProps.cell) == null ? void 0 : _slotProps_cell3.className, slotProps == null ? void 0 : (_slotProps_rowActionsCell = slotProps.rowActionsCell) == null ? void 0 : _slotProps_rowActionsCell.className)
|
|
10922
|
+
}
|
|
10923
|
+
})
|
|
10924
|
+
})
|
|
10925
|
+
]
|
|
10926
|
+
})
|
|
10927
|
+
}),
|
|
10928
|
+
bulkActions && selectedRows.length > 0 && jsxs("div", {
|
|
10929
|
+
className: cn(v.bulkActionFooter(), slotProps == null ? void 0 : (_slotProps_bulkActionFooter = slotProps.bulkActionFooter) == null ? void 0 : _slotProps_bulkActionFooter.className),
|
|
10930
|
+
role: "region",
|
|
10931
|
+
"aria-label": labels.selectedCount.replace('{count}', String(selectedRows.length)),
|
|
10932
|
+
children: [
|
|
10933
|
+
jsx("span", {
|
|
10934
|
+
className: "text-sm text-neutral-700",
|
|
10935
|
+
children: labels.selectedCount.replace('{count}', String(selectedRows.length))
|
|
10936
|
+
}),
|
|
10937
|
+
jsxs("div", {
|
|
10938
|
+
className: "flex items-center gap-2",
|
|
10939
|
+
children: [
|
|
10940
|
+
bulkActions(selectedRows),
|
|
10941
|
+
jsx("button", {
|
|
10942
|
+
type: "button",
|
|
10943
|
+
onClick: selection.clearSelection,
|
|
10944
|
+
className: cn('text-sm text-neutral-500 hover:text-neutral-900', 'underline-offset-2 hover:underline', 'focus:outline-none focus-visible:underline'),
|
|
10945
|
+
children: labels.clearSelection
|
|
10946
|
+
})
|
|
10947
|
+
]
|
|
10948
|
+
})
|
|
10949
|
+
]
|
|
10950
|
+
}),
|
|
10951
|
+
pagination && jsx("div", {
|
|
10952
|
+
className: cn(v.paginationFooter(), slotProps == null ? void 0 : (_slotProps_pagination = slotProps.pagination) == null ? void 0 : _slotProps_pagination.className),
|
|
10953
|
+
children: jsx(Pagination, {
|
|
10954
|
+
page: pagination.page,
|
|
10955
|
+
pageSize: pagination.pageSize,
|
|
10956
|
+
totalCount: totalCountProp != null ? totalCountProp : sortedRows.length,
|
|
10957
|
+
onPageChange: pagination.onPageChange,
|
|
10958
|
+
onPageSizeChange: pagination.onPageSizeChange,
|
|
10959
|
+
pageSizeOptions: pagination.pageSizeOptions
|
|
10960
|
+
})
|
|
10961
|
+
})
|
|
10962
|
+
]
|
|
10963
|
+
});
|
|
10964
|
+
}
|
|
10965
|
+
// ───── Header cell ─────
|
|
10966
|
+
function HeaderCell(props) {
|
|
10967
|
+
const { col, sortModel, onSortClick, inferredType, filterModel, onFilterChange, effectiveWidth, resizable, startResize, reorderable, reorderHandlers, labels, className, buttonClassName, disabled } = props;
|
|
10968
|
+
const align = resolveAlign(col, inferredType);
|
|
10969
|
+
const alignClass = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left';
|
|
10970
|
+
const sortEntry = sortModel.find((s)=>s.field === col.field);
|
|
10971
|
+
const ariaSort = (sortEntry == null ? void 0 : sortEntry.direction) === 'asc' ? 'ascending' : (sortEntry == null ? void 0 : sortEntry.direction) === 'desc' ? 'descending' : 'none';
|
|
10972
|
+
const headerContent = typeof col.header === 'function' ? col.header() : col.header;
|
|
10973
|
+
const cellStyle = {
|
|
10974
|
+
width: effectiveWidth != null ? `${effectiveWidth}px` : undefined,
|
|
10975
|
+
minWidth: col.minWidth != null ? `${col.minWidth}px` : undefined,
|
|
10976
|
+
flex: effectiveWidth != null ? undefined : col.flex,
|
|
10977
|
+
position: resizable || reorderable ? 'relative' : undefined
|
|
10978
|
+
};
|
|
10979
|
+
// Drop indicator for reorder: a 2px vertical bar at the LEFT or
|
|
10980
|
+
// RIGHT edge of this th when it's the active drop target.
|
|
10981
|
+
const dropTarget = reorderHandlers.dropTarget;
|
|
10982
|
+
const dropIndicatorClass = dropTarget != null && dropTarget.field === col.field ? dropTarget.side === 'left' ? 'before:absolute before:top-0 before:bottom-0 before:left-0 before:w-0.5 before:bg-primary-500' : 'after:absolute after:top-0 after:bottom-0 after:right-0 after:w-0.5 after:bg-primary-500' : '';
|
|
10983
|
+
const isDragging = reorderHandlers.draggingField === col.field;
|
|
10984
|
+
// Drag attributes only attach when reorderable; we DO NOT make the
|
|
10985
|
+
// entire th `draggable` if it isn't — keeps the keyboard focus
|
|
10986
|
+
// behavior unchanged.
|
|
10987
|
+
const dragAttrs = reorderable ? {
|
|
10988
|
+
draggable: true,
|
|
10989
|
+
onDragStart: reorderHandlers.dragStart(col.field),
|
|
10990
|
+
onDragOver: reorderHandlers.dragOver(col.field),
|
|
10991
|
+
onDrop: reorderHandlers.drop(col.field),
|
|
10992
|
+
onDragEnd: reorderHandlers.dragEnd
|
|
10993
|
+
} : {};
|
|
10994
|
+
const resizeHandle = resizable ? jsx("span", {
|
|
10995
|
+
role: "separator",
|
|
10996
|
+
"aria-orientation": "vertical",
|
|
10997
|
+
"aria-label": `Resize ${typeof col.header === 'string' ? col.header : col.field}`,
|
|
10998
|
+
onPointerDown: (e)=>{
|
|
10999
|
+
var _ref, _col_minWidth, _col_maxWidth;
|
|
11000
|
+
const handle = e.currentTarget;
|
|
11001
|
+
const th = handle.parentElement;
|
|
11002
|
+
const startWidth = (_ref = effectiveWidth != null ? effectiveWidth : th == null ? void 0 : th.getBoundingClientRect().width) != null ? _ref : 120;
|
|
11003
|
+
const min = (_col_minWidth = col.minWidth) != null ? _col_minWidth : 40;
|
|
11004
|
+
const max = (_col_maxWidth = col.maxWidth) != null ? _col_maxWidth : 1200;
|
|
11005
|
+
startResize(col.field, startWidth, min, max)(e);
|
|
11006
|
+
},
|
|
11007
|
+
className: cn('absolute top-0 bottom-0 right-0 w-1.5', 'cursor-col-resize select-none touch-none', 'hover:bg-primary-300 active:bg-primary-400', 'transition-colors motion-reduce:transition-none')
|
|
11008
|
+
}) : null;
|
|
11009
|
+
const filterType = col.filterable ? resolveFilterType(col, inferredType) : null;
|
|
11010
|
+
const filterTrigger = col.filterable && filterType ? jsx(ColumnFilterTrigger, {
|
|
11011
|
+
field: col.field,
|
|
11012
|
+
filterType: filterType,
|
|
11013
|
+
filterModel: filterModel,
|
|
11014
|
+
onFilterChange: onFilterChange,
|
|
11015
|
+
disabled: disabled,
|
|
11016
|
+
labels: {
|
|
11017
|
+
filterColumn: labels.filterColumn,
|
|
11018
|
+
filterApply: labels.filterApply,
|
|
11019
|
+
filterClear: labels.filterClear,
|
|
11020
|
+
filterMin: labels.filterMin,
|
|
11021
|
+
filterMax: labels.filterMax,
|
|
11022
|
+
filterFrom: labels.filterFrom,
|
|
11023
|
+
filterTo: labels.filterTo,
|
|
11024
|
+
filterAll: labels.filterAll,
|
|
11025
|
+
filterTrue: labels.filterTrue,
|
|
11026
|
+
filterFalse: labels.filterFalse
|
|
11027
|
+
}
|
|
11028
|
+
}) : null;
|
|
11029
|
+
// Layout strategy:
|
|
11030
|
+
// - sortable only → sort button is the cell content
|
|
11031
|
+
// - filterable only → header text + filter trigger in a flex row
|
|
11032
|
+
// - both → sort button + filter trigger in a flex row
|
|
11033
|
+
// - neither → plain header text
|
|
11034
|
+
const wrapperJustify = align === 'right' ? 'justify-end' : align === 'center' ? 'justify-center' : 'justify-start';
|
|
11035
|
+
if (col.sortable) {
|
|
11036
|
+
const sortButton = jsxs("button", {
|
|
11037
|
+
type: "button",
|
|
11038
|
+
onClick: onSortClick,
|
|
11039
|
+
disabled: disabled,
|
|
11040
|
+
"aria-label": ariaSort === 'ascending' ? labels.ariaSortAscending : ariaSort === 'descending' ? labels.ariaSortDescending : labels.ariaSortNone,
|
|
11041
|
+
className: cn(buttonClassName, align === 'right' && 'flex-row-reverse', 'disabled:cursor-not-allowed disabled:opacity-50'),
|
|
11042
|
+
children: [
|
|
11043
|
+
jsx("span", {
|
|
11044
|
+
children: headerContent
|
|
11045
|
+
}),
|
|
11046
|
+
jsx(SortIcon, {
|
|
11047
|
+
direction: sortEntry == null ? void 0 : sortEntry.direction
|
|
11048
|
+
})
|
|
11049
|
+
]
|
|
11050
|
+
});
|
|
11051
|
+
return jsxs("th", _extends({
|
|
11052
|
+
scope: "col",
|
|
11053
|
+
"aria-sort": ariaSort,
|
|
11054
|
+
className: cn(className, alignClass, dropIndicatorClass, isDragging && 'opacity-50'),
|
|
11055
|
+
style: cellStyle
|
|
11056
|
+
}, dragAttrs, {
|
|
11057
|
+
children: [
|
|
11058
|
+
filterTrigger ? jsxs("div", {
|
|
11059
|
+
className: cn('flex items-center gap-1', wrapperJustify),
|
|
11060
|
+
children: [
|
|
11061
|
+
sortButton,
|
|
11062
|
+
filterTrigger
|
|
11063
|
+
]
|
|
11064
|
+
}) : sortButton,
|
|
11065
|
+
resizeHandle
|
|
11066
|
+
]
|
|
11067
|
+
}));
|
|
11068
|
+
}
|
|
11069
|
+
return jsxs("th", _extends({
|
|
11070
|
+
scope: "col",
|
|
11071
|
+
className: cn(className, alignClass, dropIndicatorClass, isDragging && 'opacity-50'),
|
|
11072
|
+
style: cellStyle
|
|
11073
|
+
}, dragAttrs, {
|
|
11074
|
+
children: [
|
|
11075
|
+
filterTrigger ? jsxs("div", {
|
|
11076
|
+
className: cn('flex items-center gap-1', wrapperJustify),
|
|
11077
|
+
children: [
|
|
11078
|
+
jsx("span", {
|
|
11079
|
+
children: headerContent
|
|
11080
|
+
}),
|
|
11081
|
+
filterTrigger
|
|
11082
|
+
]
|
|
11083
|
+
}) : headerContent,
|
|
11084
|
+
resizeHandle
|
|
11085
|
+
]
|
|
11086
|
+
}));
|
|
11087
|
+
}
|
|
11088
|
+
/**
|
|
11089
|
+
* Resolve the effective width (px) for a column. User-driven resize
|
|
11090
|
+
* (`columnWidths[field]`) wins over the static `col.width`. Returns
|
|
11091
|
+
* `undefined` if neither source has a width set — the consumer falls
|
|
11092
|
+
* back to the column's `flex` value or browser auto-layout.
|
|
11093
|
+
*/ function resolveColumnWidth(col, columnWidths) {
|
|
11094
|
+
const resized = columnWidths[col.field];
|
|
11095
|
+
if (resized != null) return resized;
|
|
11096
|
+
return col.width;
|
|
11097
|
+
}
|
|
11098
|
+
/**
|
|
11099
|
+
* Resolve the filter UI type for a column. Explicit `col.filterType`
|
|
11100
|
+
* wins; otherwise we map the auto-detected primitive type:
|
|
11101
|
+
* - `number` → number range
|
|
11102
|
+
* - `boolean` → true/false/all select
|
|
11103
|
+
* - `date` → date range
|
|
11104
|
+
* - anything else (string / unknown) → text contains
|
|
11105
|
+
*/ function resolveFilterType(col, inferred) {
|
|
11106
|
+
if (col.filterType) return col.filterType;
|
|
11107
|
+
switch(inferred){
|
|
11108
|
+
case 'number':
|
|
11109
|
+
return 'number';
|
|
11110
|
+
case 'boolean':
|
|
11111
|
+
return 'boolean';
|
|
11112
|
+
case 'date':
|
|
11113
|
+
return 'date';
|
|
11114
|
+
default:
|
|
11115
|
+
return 'text';
|
|
11116
|
+
}
|
|
11117
|
+
}
|
|
11118
|
+
// ───── Virtualized body ─────
|
|
11119
|
+
function renderVirtualizedBody(args) {
|
|
11120
|
+
const { vWindow, renderedRows, visibleCols, columnTypes, columnWidths, selection, rowSelection, showSelectionColumn, showRowActionsColumn, rowActions, getRowId, isInteractive, labels, classes } = args;
|
|
11121
|
+
return jsxs(Fragment$1, {
|
|
11122
|
+
children: [
|
|
11123
|
+
vWindow.paddingTop > 0 && jsx("tr", {
|
|
11124
|
+
"aria-hidden": "true",
|
|
11125
|
+
style: {
|
|
11126
|
+
height: vWindow.paddingTop
|
|
11127
|
+
}
|
|
11128
|
+
}),
|
|
11129
|
+
renderedRows.map((row, localIdx)=>{
|
|
11130
|
+
const absoluteIdx = vWindow.startIndex + localIdx;
|
|
11131
|
+
const rowId = getRowId(row, absoluteIdx);
|
|
11132
|
+
const isSelected = selection.isSelected(rowId);
|
|
11133
|
+
return jsxs("tr", {
|
|
11134
|
+
"data-selected": isSelected ? 'true' : 'false',
|
|
11135
|
+
"aria-selected": rowSelection !== 'none' ? isSelected : undefined,
|
|
11136
|
+
className: classes.row,
|
|
11137
|
+
children: [
|
|
11138
|
+
showSelectionColumn && jsx("td", {
|
|
11139
|
+
className: classes.selectionCell,
|
|
11140
|
+
children: jsx("input", {
|
|
11141
|
+
type: "checkbox",
|
|
11142
|
+
"aria-label": labels.ariaSelectRow,
|
|
11143
|
+
checked: isSelected,
|
|
11144
|
+
onChange: ()=>selection.toggleRow(rowId),
|
|
11145
|
+
disabled: !isInteractive,
|
|
11146
|
+
className: "cursor-pointer"
|
|
11147
|
+
})
|
|
11148
|
+
}),
|
|
11149
|
+
visibleCols.map((col)=>jsx(DataCell, {
|
|
11150
|
+
col: col,
|
|
11151
|
+
row: row,
|
|
11152
|
+
rowIndex: absoluteIdx,
|
|
11153
|
+
isSelected: isSelected,
|
|
11154
|
+
inferredType: columnTypes.get(col.field),
|
|
11155
|
+
effectiveWidth: resolveColumnWidth(col, columnWidths),
|
|
11156
|
+
cellClass: classes.cell,
|
|
11157
|
+
stickyLeftClass: classes.stickyLeftCell,
|
|
11158
|
+
stickyRightClass: classes.stickyRightCell
|
|
11159
|
+
}, col.field)),
|
|
11160
|
+
showRowActionsColumn && rowActions && jsx("td", {
|
|
11161
|
+
className: classes.rowActionsCell,
|
|
11162
|
+
children: rowActions(row)
|
|
11163
|
+
})
|
|
11164
|
+
]
|
|
11165
|
+
}, rowId);
|
|
11166
|
+
}),
|
|
11167
|
+
vWindow.paddingBottom > 0 && jsx("tr", {
|
|
11168
|
+
"aria-hidden": "true",
|
|
11169
|
+
style: {
|
|
11170
|
+
height: vWindow.paddingBottom
|
|
11171
|
+
}
|
|
11172
|
+
})
|
|
11173
|
+
]
|
|
11174
|
+
});
|
|
11175
|
+
}
|
|
11176
|
+
// ───── Data cell ─────
|
|
11177
|
+
function DataCell(props) {
|
|
11178
|
+
const { col, row, rowIndex, isSelected, inferredType, effectiveWidth, cellClass, stickyLeftClass, stickyRightClass } = props;
|
|
11179
|
+
const value = getNestedValue(row, col.field);
|
|
11180
|
+
const align = resolveAlign(col, inferredType);
|
|
11181
|
+
const tabularNums = resolveTabularNums(col, inferredType);
|
|
11182
|
+
const monospace = resolveMonospace(col);
|
|
11183
|
+
const alignClass = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left';
|
|
11184
|
+
const columnAccessState = col.access ? {
|
|
11185
|
+
hidden: col.access.onUnauthorized === 'hide',
|
|
11186
|
+
disabled: col.access.onUnauthorized === 'disable',
|
|
11187
|
+
readonly: col.access.onUnauthorized === 'readonly'
|
|
11188
|
+
} : null;
|
|
11189
|
+
const content = col.cellRenderer ? col.cellRenderer({
|
|
11190
|
+
row,
|
|
11191
|
+
value,
|
|
11192
|
+
rowIndex,
|
|
11193
|
+
isSelected,
|
|
11194
|
+
isExpanded: false,
|
|
11195
|
+
access: columnAccessState
|
|
11196
|
+
}) : renderDefaultCell(value);
|
|
11197
|
+
return jsx("td", {
|
|
11198
|
+
className: cn(cellClass, alignClass, tabularNums && 'tabular-nums', monospace && 'font-mono', col.sticky === 'left' && stickyLeftClass, col.sticky === 'right' && stickyRightClass),
|
|
11199
|
+
style: {
|
|
11200
|
+
width: effectiveWidth != null ? `${effectiveWidth}px` : undefined,
|
|
11201
|
+
minWidth: col.minWidth != null ? `${col.minWidth}px` : undefined
|
|
11202
|
+
},
|
|
11203
|
+
children: content
|
|
11204
|
+
});
|
|
11205
|
+
}
|
|
11206
|
+
// ───── Helpers ─────
|
|
11207
|
+
function renderDefaultCell(value) {
|
|
11208
|
+
if (value == null) return null;
|
|
11209
|
+
if (typeof value === 'boolean') return value ? '✓' : '✗';
|
|
11210
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
11211
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
11212
|
+
return value;
|
|
11213
|
+
}
|
|
11214
|
+
function handleSortClick(col, event, current, setSortModel) {
|
|
11215
|
+
if (!col.sortable) return;
|
|
11216
|
+
const multi = event.shiftKey;
|
|
11217
|
+
setSortModel((prev)=>cycleSortFor(prev, col.field, multi));
|
|
11218
|
+
}
|
|
11219
|
+
function renderLoadingRows(args) {
|
|
11220
|
+
const { count, visibleCols, showSelectionColumn, showRowActionsColumn, rowHeight, rowClass, cellClass } = args;
|
|
11221
|
+
return Array.from({
|
|
11222
|
+
length: count
|
|
11223
|
+
}).map((_, i)=>jsxs("tr", {
|
|
11224
|
+
className: rowClass,
|
|
11225
|
+
"aria-busy": "true",
|
|
11226
|
+
style: {
|
|
11227
|
+
height: rowHeight
|
|
11228
|
+
},
|
|
11229
|
+
children: [
|
|
11230
|
+
showSelectionColumn && jsx("td", {
|
|
11231
|
+
className: cellClass,
|
|
11232
|
+
children: jsx(Skeleton, {
|
|
11233
|
+
variant: "rectangle",
|
|
11234
|
+
width: "16px",
|
|
11235
|
+
height: "16px"
|
|
11236
|
+
})
|
|
11237
|
+
}),
|
|
11238
|
+
visibleCols.map((col)=>jsx("td", {
|
|
11239
|
+
className: cellClass,
|
|
11240
|
+
children: jsx(Skeleton, {
|
|
11241
|
+
variant: "text",
|
|
11242
|
+
width: "80%"
|
|
11243
|
+
})
|
|
11244
|
+
}, col.field)),
|
|
11245
|
+
showRowActionsColumn && jsx("td", {
|
|
11246
|
+
className: cellClass,
|
|
11247
|
+
children: jsx(Skeleton, {
|
|
11248
|
+
variant: "rectangle",
|
|
11249
|
+
width: "24px",
|
|
11250
|
+
height: "24px"
|
|
11251
|
+
})
|
|
11252
|
+
})
|
|
11253
|
+
]
|
|
11254
|
+
}, `loading-${i}`));
|
|
11255
|
+
}
|
|
11256
|
+
function renderEmptyState(args) {
|
|
11257
|
+
var _args_emptyState;
|
|
11258
|
+
return jsx("tr", {
|
|
11259
|
+
children: jsx("td", {
|
|
11260
|
+
colSpan: args.totalColumnCount,
|
|
11261
|
+
className: args.cellClass,
|
|
11262
|
+
children: (_args_emptyState = args.emptyState) != null ? _args_emptyState : args.fallback
|
|
11263
|
+
})
|
|
11264
|
+
});
|
|
11265
|
+
}
|
|
11266
|
+
// ───── Icons ─────
|
|
11267
|
+
function SortIcon(props) {
|
|
11268
|
+
return jsx("svg", {
|
|
11269
|
+
width: "0.75em",
|
|
11270
|
+
height: "0.75em",
|
|
11271
|
+
viewBox: "0 0 16 16",
|
|
11272
|
+
fill: "currentColor",
|
|
11273
|
+
"aria-hidden": "true",
|
|
11274
|
+
className: "shrink-0 opacity-60 group-hover:opacity-100",
|
|
11275
|
+
children: props.direction === 'asc' ? jsx("path", {
|
|
11276
|
+
d: "M8 4l4 6H4z"
|
|
11277
|
+
}) : props.direction === 'desc' ? jsx("path", {
|
|
11278
|
+
d: "M8 12L4 6h8z"
|
|
11279
|
+
}) : jsxs(Fragment$1, {
|
|
11280
|
+
children: [
|
|
11281
|
+
jsx("path", {
|
|
11282
|
+
d: "M8 3l3 4H5z",
|
|
11283
|
+
opacity: "0.5"
|
|
11284
|
+
}),
|
|
11285
|
+
jsx("path", {
|
|
11286
|
+
d: "M8 13l-3-4h6z",
|
|
11287
|
+
opacity: "0.5"
|
|
11288
|
+
})
|
|
11289
|
+
]
|
|
11290
|
+
})
|
|
11291
|
+
});
|
|
11292
|
+
}
|
|
11293
|
+
function SearchIcon(props) {
|
|
11294
|
+
return jsxs("svg", {
|
|
11295
|
+
width: "0.875em",
|
|
11296
|
+
height: "0.875em",
|
|
11297
|
+
viewBox: "0 0 16 16",
|
|
11298
|
+
fill: "none",
|
|
11299
|
+
stroke: "currentColor",
|
|
11300
|
+
strokeWidth: "1.5",
|
|
11301
|
+
strokeLinecap: "round",
|
|
11302
|
+
strokeLinejoin: "round",
|
|
11303
|
+
"aria-hidden": "true",
|
|
11304
|
+
className: props.className,
|
|
11305
|
+
children: [
|
|
11306
|
+
jsx("circle", {
|
|
11307
|
+
cx: "7",
|
|
11308
|
+
cy: "7",
|
|
11309
|
+
r: "4.5"
|
|
11310
|
+
}),
|
|
11311
|
+
jsx("path", {
|
|
11312
|
+
d: "M11 11l3 3"
|
|
11313
|
+
})
|
|
11314
|
+
]
|
|
11315
|
+
});
|
|
11316
|
+
}
|
|
11317
|
+
|
|
9396
11318
|
/**
|
|
9397
11319
|
* @dashforge/tw
|
|
9398
11320
|
*
|
|
@@ -9420,6 +11342,6 @@ function RowActionMenuItem(props) {
|
|
|
9420
11342
|
*/ // ───── Components ─────
|
|
9421
11343
|
/**
|
|
9422
11344
|
* Package version (synced with `package.json` at publish time).
|
|
9423
|
-
*/ const VERSION = '0.
|
|
11345
|
+
*/ const VERSION = '0.7.0-beta';
|
|
9424
11346
|
|
|
9425
|
-
export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Checkbox, ConfirmDialogProvider, Container, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };
|
|
11347
|
+
export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Checkbox, ConfirmDialogProvider, Container, DataGrid, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };
|