@rebasepro/ui 0.4.0 → 0.6.0

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.
Files changed (66) hide show
  1. package/README.md +124 -66
  2. package/dist/components/Autocomplete.d.ts +2 -2
  3. package/dist/components/BooleanSwitchWithLabel.d.ts +1 -1
  4. package/dist/components/Checkbox.d.ts +1 -1
  5. package/dist/components/Chip.d.ts +1 -1
  6. package/dist/components/CircularProgress.d.ts +2 -1
  7. package/dist/components/CircularProgressCenter.d.ts +2 -1
  8. package/dist/components/Collapse.d.ts +1 -1
  9. package/dist/components/ColorPicker.d.ts +2 -1
  10. package/dist/components/DebouncedTextField.d.ts +2 -1
  11. package/dist/components/Dialog.d.ts +2 -1
  12. package/dist/components/DialogActions.d.ts +1 -1
  13. package/dist/components/DialogContent.d.ts +1 -1
  14. package/dist/components/DialogTitle.d.ts +1 -1
  15. package/dist/components/ErrorBoundary.d.ts +1 -1
  16. package/dist/components/ExpandablePanel.d.ts +1 -1
  17. package/dist/components/FileUpload.d.ts +1 -1
  18. package/dist/components/InfoLabel.d.ts +1 -1
  19. package/dist/components/LoadingButton.d.ts +1 -1
  20. package/dist/components/Menu.d.ts +1 -1
  21. package/dist/components/Menubar.d.ts +16 -16
  22. package/dist/components/MultiSelect.d.ts +1 -1
  23. package/dist/components/Paper.d.ts +1 -1
  24. package/dist/components/Popover.d.ts +1 -1
  25. package/dist/components/ResizablePanels.d.ts +1 -1
  26. package/dist/components/SearchBar.d.ts +1 -1
  27. package/dist/components/Select.d.ts +2 -2
  28. package/dist/components/Separator.d.ts +1 -1
  29. package/dist/components/Skeleton.d.ts +2 -1
  30. package/dist/components/Table.d.ts +5 -5
  31. package/dist/components/Tabs.d.ts +2 -2
  32. package/dist/components/TextField.d.ts +2 -2
  33. package/dist/components/ToggleButtonGroup.d.ts +1 -1
  34. package/dist/components/Tooltip.d.ts +1 -1
  35. package/dist/components/Typography.d.ts +1 -1
  36. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +2 -1
  37. package/dist/components/common/SelectInputLabel.d.ts +1 -1
  38. package/dist/hooks/PortalContainerContext.d.ts +1 -1
  39. package/dist/icons/GitHubIcon.d.ts +2 -1
  40. package/dist/icons/HandleIcon.d.ts +1 -1
  41. package/dist/icons/index.d.ts +1 -1
  42. package/dist/index.es.js +5678 -8795
  43. package/dist/index.es.js.map +1 -1
  44. package/dist/index.umd.js +6796 -9398
  45. package/dist/index.umd.js.map +1 -1
  46. package/dist/src/index.css +126 -0
  47. package/package.json +35 -35
  48. package/src/components/BooleanSwitchWithLabel.tsx +4 -3
  49. package/src/components/DebouncedTextField.tsx +11 -9
  50. package/src/components/Dialog.tsx +6 -3
  51. package/src/components/ErrorBoundary.tsx +4 -2
  52. package/src/components/Table.tsx +21 -13
  53. package/src/components/TextField.tsx +3 -3
  54. package/src/components/Tooltip.tsx +2 -1
  55. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -1
  56. package/src/components/VirtualTable/VirtualTable.tsx +1 -2
  57. package/src/components/VirtualTable/VirtualTableHeader.tsx +34 -21
  58. package/src/hooks/useDebounceCallback.tsx +1 -1
  59. package/src/icons/Icon.tsx +1 -1
  60. package/src/icons/icon_keys.ts +1 -1
  61. package/src/icons/index.ts +1 -1
  62. package/src/scripts/fixIconSizes.cjs +7 -7
  63. package/src/scripts/fixIconSizesToConst.cjs +2 -2
  64. package/src/scripts/generateIconKeys.ts +7 -7
  65. package/src/scripts/saveIconFiles.ts +1 -1
  66. package/src/util/debounce.ts +2 -3
@@ -0,0 +1,126 @@
1
+ @theme {
2
+ /* Font Families */
3
+ --font-sans: 'Rubik', 'Roboto', 'Helvetica', 'Arial', sans-serif;
4
+ --font-headers: 'Rubik', 'Roboto', 'Helvetica', 'Arial', sans-serif;
5
+ --font-mono: 'JetBrains Mono', 'Space Mono', 'Lucida Console', monospace;
6
+
7
+ /* Colors */
8
+ --color-primary: #0070F4;
9
+ --color-primary-light: oklch(from var(--color-primary) calc(l + 0.15) c h);
10
+ --color-primary-dark: oklch(from var(--color-primary) calc(l - 0.15) c h);
11
+ --color-secondary: #FF5B79;
12
+ --color-secondary-light: oklch(from var(--color-secondary) calc(l + 0.15) c h);
13
+ --color-secondary-dark: oklch(from var(--color-secondary) calc(l - 0.15) c h);
14
+
15
+ --color-primary-bg: oklch(from var(--color-primary) l c h / 0.1);
16
+ --color-secondary-bg: oklch(from var(--color-secondary) l c h / 0.1);
17
+
18
+ /* Field Colors */
19
+ --color-field-disabled: rgb(224 224 226);
20
+ --color-field-disabled-dark: rgb(35 35 37);
21
+
22
+ /* Text Colors — using opaque values so icon strokes render solidly */
23
+ --color-text-primary: #212121;
24
+ --color-text-secondary: #757575;
25
+ --color-text-disabled: #9e9e9e;
26
+ --color-text-primary-dark: #ffffff;
27
+ --color-text-secondary-dark: #a0a0a9;
28
+ --color-text-disabled-dark: #757580;
29
+
30
+ /* Surface Colors */
31
+ --color-surface-50: #fafafa;
32
+ --color-surface-100: #f5f5f5;
33
+ --color-surface-200: #e5e5e5;
34
+ --color-surface-300: #d4d4d4;
35
+ --color-surface-400: #a3a3a3;
36
+ --color-surface-500: #737373;
37
+ --color-surface-600: #404040;
38
+ --color-surface-700: #262626;
39
+ --color-surface-800: #111111;
40
+ --color-surface-900: #0a0a0a;
41
+ --color-surface-950: #000000;
42
+
43
+ /* Surface Accent Colors */
44
+ --color-surface-accent-50: #f8fafc;
45
+ --color-surface-accent-100: #f1f5f9;
46
+ --color-surface-accent-200: #e2e8f0;
47
+ --color-surface-accent-300: #cbd5e1;
48
+ --color-surface-accent-400: #94a3b8;
49
+ --color-surface-accent-500: #64748b;
50
+ --color-surface-accent-600: #475569;
51
+ --color-surface-accent-700: #334155;
52
+ --color-surface-accent-800: #1e293b;
53
+ --color-surface-accent-900: #172033;
54
+ --color-surface-accent-950: #0f172a;
55
+ }
56
+
57
+ /* Chrome, Safari and Opera */
58
+ .no-scrollbar::-webkit-scrollbar {
59
+ display: none;
60
+ }
61
+
62
+ .no-scrollbar {
63
+ -ms-overflow-style: none; /* IE and Edge */
64
+ scrollbar-width: none; /* Firefox */
65
+ }
66
+
67
+ .typography-h1 {
68
+ @apply text-6xl font-headers font-light tracking-tight;
69
+ }
70
+
71
+ .typography-h2 {
72
+ @apply text-5xl font-headers font-light tracking-tight;
73
+ }
74
+
75
+ .typography-h3 {
76
+ @apply text-4xl font-headers font-normal tracking-tight;
77
+ }
78
+
79
+ .typography-h4 {
80
+ @apply text-3xl font-headers font-normal tracking-tight;
81
+ }
82
+
83
+ .typography-h5 {
84
+ @apply text-2xl font-headers font-normal;
85
+ }
86
+
87
+ .typography-h6 {
88
+ @apply text-xl font-headers font-normal;
89
+ }
90
+
91
+ .typography-subtitle1 {
92
+ @apply text-lg font-headers font-medium;
93
+ }
94
+
95
+ .typography-subtitle2 {
96
+ @apply text-base font-headers font-medium;
97
+ }
98
+
99
+ .typography-body1 {
100
+ @apply text-base;
101
+ }
102
+
103
+ .typography-body2 {
104
+ @apply text-sm;
105
+ }
106
+
107
+ .typography-caption {
108
+ @apply text-xs;
109
+ }
110
+
111
+ .typography-label {
112
+ @apply text-sm font-medium;
113
+ }
114
+
115
+ .typography-inherit {
116
+ @apply text-inherit;
117
+ }
118
+
119
+ .typography-button {
120
+ @apply text-sm font-semibold;
121
+ }
122
+
123
+ :focus-visible {
124
+ @apply outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-0 focus-visible:ring-offset-transparent
125
+ }
126
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/ui",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.6.0",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -46,32 +46,31 @@
46
46
  "@dnd-kit/core": "^6.3.1",
47
47
  "@dnd-kit/sortable": "^10.0.0",
48
48
  "@dnd-kit/utilities": "^3.2.2",
49
- "@radix-ui/react-checkbox": "^1.3.3",
50
- "@radix-ui/react-collapsible": "^1.1.12",
51
- "@radix-ui/react-dialog": "^1.1.15",
52
- "@radix-ui/react-dropdown-menu": "^2.1.16",
53
- "@radix-ui/react-label": "^2.1.8",
54
- "@radix-ui/react-menubar": "^1.1.16",
55
- "@radix-ui/react-popover": "^1.1.15",
56
- "@radix-ui/react-portal": "^1.1.10",
57
- "@radix-ui/react-radio-group": "^1.3.8",
58
- "@radix-ui/react-select": "^2.2.6",
59
- "@radix-ui/react-separator": "^1.1.8",
60
- "@radix-ui/react-slider": "^1.3.6",
61
- "@radix-ui/react-slot": "^1.2.4",
62
- "@radix-ui/react-tabs": "^1.1.13",
63
- "@radix-ui/react-tooltip": "^1.2.8",
64
- "@radix-ui/react-visually-hidden": "^1.2.4",
49
+ "@radix-ui/react-checkbox": "^1.3.4",
50
+ "@radix-ui/react-collapsible": "^1.1.13",
51
+ "@radix-ui/react-dialog": "^1.1.16",
52
+ "@radix-ui/react-dropdown-menu": "^2.1.17",
53
+ "@radix-ui/react-label": "^2.1.9",
54
+ "@radix-ui/react-menubar": "^1.1.17",
55
+ "@radix-ui/react-popover": "^1.1.16",
56
+ "@radix-ui/react-portal": "^1.1.11",
57
+ "@radix-ui/react-radio-group": "^1.4.0",
58
+ "@radix-ui/react-select": "^2.3.0",
59
+ "@radix-ui/react-separator": "^1.1.9",
60
+ "@radix-ui/react-slider": "^1.4.0",
61
+ "@radix-ui/react-slot": "^1.2.5",
62
+ "@radix-ui/react-tabs": "^1.1.14",
63
+ "@radix-ui/react-tooltip": "^1.2.9",
64
+ "@radix-ui/react-visually-hidden": "^1.2.5",
65
65
  "clsx": "^2.1.1",
66
66
  "cmdk": "^1.1.1",
67
67
  "fast-equals": "6.0.0",
68
- "lucide-react": "1.14.0",
69
- "markdown-it": "^14.1.1",
70
- "react-compiler-runtime": "1.0.0",
71
- "react-dropzone": "^14.4.1",
68
+ "lucide-react": "1.18.0",
69
+ "markdown-it": "^14.2.0",
70
+ "react-dropzone": "^15.0.0",
72
71
  "react-use-measure": "^2.1.7",
73
72
  "react-window": "^1.8.11",
74
- "tailwind-merge": "^2.6.1"
73
+ "tailwind-merge": "^3.6.0"
75
74
  },
76
75
  "peerDependencies": {
77
76
  "react": ">=19.0.0",
@@ -82,24 +81,25 @@
82
81
  "@testing-library/jest-dom": "^6.9.1",
83
82
  "@testing-library/react": "^16.3.2",
84
83
  "@testing-library/user-event": "^14.6.1",
85
- "@types/jest": "^29.5.14",
84
+ "@types/jest": "^30.0.0",
86
85
  "@types/markdown-it": "^14.1.2",
87
- "@types/node": "^20.19.41",
86
+ "@types/node": "^25.9.3",
88
87
  "@types/object-hash": "^3.0.6",
89
- "@types/react": "^19.2.15",
88
+ "@types/react": "^19.2.17",
90
89
  "@types/react-dom": "^19.2.3",
91
90
  "@types/react-measure": "^2.0.12",
92
- "@vitejs/plugin-react": "^4.7.0",
91
+ "@types/react-window": "^1.8.8",
92
+ "@vitejs/plugin-react": "^6.0.2",
93
93
  "babel-plugin-react-compiler": "beta",
94
- "cross-env": "^7.0.3",
95
- "jest": "^29.7.0",
94
+ "cross-env": "^10.1.0",
95
+ "jest": "^30.4.2",
96
96
  "rollup-preserve-directives": "^1.1.3",
97
- "tailwindcss": "^4.3.0",
98
- "ts-jest": "^29.4.10",
99
- "tsd": "^0.31.2",
100
- "typescript": "^5.9.3",
101
- "vite": "^7.3.3",
102
- "vite-plugin-static-copy": "^3.4.0"
97
+ "tailwindcss": "^4.3.1",
98
+ "ts-jest": "^29.4.11",
99
+ "tsd": "^0.33.0",
100
+ "typescript": "^6.0.3",
101
+ "vite": "^8.0.16",
102
+ "vite-plugin-static-copy": "^4.1.1"
103
103
  },
104
104
  "files": [
105
105
  "dist",
@@ -112,7 +112,7 @@
112
112
  },
113
113
  "scripts": {
114
114
  "watch": "vite build --watch",
115
- "build": "rm -rf dist || true && vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
115
+ "build": "rm -rf dist || true && vite build && cp dist/src/index.css dist/index.css 2>/dev/null; tsc --emitDeclarationOnly -p tsconfig.prod.json",
116
116
  "test:lint": "eslint \"src/**\" --quiet",
117
117
  "test": "jest --passWithNoTests",
118
118
  "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f",
@@ -86,9 +86,10 @@ export const BooleanSwitchWithLabel = function BooleanSwitchWithLabel({
86
86
  )}
87
87
  onClick={disabled ? undefined : (e) => {
88
88
  if (props.allowIndeterminate) {
89
- if (value === null || value === undefined) onValueChange?.(true)
90
- else if (value) onValueChange?.(false)
91
- else onValueChange?.(null as unknown as boolean); // SAFETY: null represents indeterminate state when allowIndeterminate is true
89
+ const onChange = onValueChange as ((newValue: boolean | null) => void) | undefined;
90
+ if (value === null || value === undefined) onChange?.(true)
91
+ else if (value) onChange?.(false)
92
+ else onChange?.(null);
92
93
  } else {
93
94
  onValueChange?.(!value);
94
95
  }
@@ -1,7 +1,9 @@
1
1
  "use client";
2
- import React, { ChangeEvent, useCallback, useEffect, useRef, useState } from "react";
2
+ import React, { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { TextField, TextFieldProps } from "./index";
4
4
 
5
+ type TextFieldChangeEvent = React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>;
6
+
5
7
  export function DebouncedTextField<T extends string | number>(props: TextFieldProps<T>) {
6
8
 
7
9
  const [internalValue, setInternalValue] = useState<T | string>(props.value ?? "");
@@ -24,7 +26,7 @@ export function DebouncedTextField<T extends string | number>(props: TextFieldPr
24
26
  };
25
27
  }, []);
26
28
 
27
- const flushChange = useCallback((value: T | string, event?: ChangeEvent<any>) => {
29
+ const flushChange = useCallback((value: T | string, event?: TextFieldChangeEvent) => {
28
30
  if (timerRef.current) {
29
31
  clearTimeout(timerRef.current);
30
32
  timerRef.current = undefined;
@@ -38,12 +40,12 @@ export function DebouncedTextField<T extends string | number>(props: TextFieldPr
38
40
  value: value,
39
41
  name: props.name
40
42
  }
41
- };
42
- props.onChange(e as any);
43
+ } as TextFieldChangeEvent;
44
+ props.onChange(e);
43
45
  }
44
46
  }, [props.value, props.onChange, props.name]);
45
47
 
46
- const internalOnChange = useCallback((event: ChangeEvent<any>) => {
48
+ const internalOnChange = useCallback((event: TextFieldChangeEvent) => {
47
49
  const newValue = event.target.value;
48
50
  setInternalValue(newValue);
49
51
 
@@ -58,17 +60,17 @@ export function DebouncedTextField<T extends string | number>(props: TextFieldPr
58
60
  };
59
61
 
60
62
  timerRef.current = setTimeout(() => {
61
- flushChange(newValue, eventCopy as any);
63
+ flushChange(newValue, eventCopy as TextFieldChangeEvent);
62
64
  }, 150);
63
65
  }, [flushChange]);
64
66
 
65
- const internalOnBlur = useCallback((event: React.FocusEvent<any>) => {
66
- flushChange(internalValue, event as any);
67
+ const internalOnBlur = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
68
+ flushChange(internalValue, event as TextFieldChangeEvent);
67
69
  props.onBlur?.(event);
68
70
  }, [internalValue, flushChange, props.onBlur]);
69
71
 
70
72
  return <TextField {...props}
71
73
  onChange={internalOnChange}
72
74
  onBlur={internalOnBlur}
73
- value={internalValue as T}/>
75
+ value={internalValue as T}/>;
74
76
  }
@@ -26,6 +26,7 @@ export type DialogProps = {
26
26
  */
27
27
  disableInitialFocus?: boolean;
28
28
  portalContainer?: HTMLElement | null;
29
+ "aria-describedby"?: string;
29
30
  };
30
31
 
31
32
  const widthClasses = {
@@ -60,7 +61,8 @@ export const Dialog = ({
60
61
  onPointerDownOutside,
61
62
  onInteractOutside,
62
63
  disableInitialFocus = true,
63
- portalContainer
64
+ portalContainer,
65
+ "aria-describedby": ariaDescribedby
64
66
  }: DialogProps) => {
65
67
  const [displayed, setDisplayed] = useState(false);
66
68
 
@@ -92,7 +94,7 @@ export const Dialog = ({
92
94
  <div className={cls("fixed inset-0 z-50", containerClassName)}>
93
95
 
94
96
  <DialogPrimitive.Overlay
95
- className={cls("fixed inset-0 transition-opacity ease-in-out duration-200 bg-black dark:bg-opacity-60 dark:bg-black/60 bg-opacity-50 bg-black/50 backdrop-blur-sm",
97
+ className={cls("fixed inset-0 transition-opacity ease-in-out duration-200 bg-black/50 dark:bg-black/60 backdrop-blur-sm",
96
98
  displayed && open ? "opacity-100" : "opacity-0",
97
99
  "z-50 fixed top-0 left-0 w-full h-full flex justify-center items-center"
98
100
  )}
@@ -111,7 +113,8 @@ export const Dialog = ({
111
113
  }}
112
114
  onPointerDownOutside={onPointerDownOutside}
113
115
  onInteractOutside={onInteractOutside}
114
- className={cls("h-full outline-none flex justify-center items-center z-60 opacity-100 transition-all duration-200 ease-in-out")}
116
+ aria-describedby={ariaDescribedby}
117
+ className={cls("relative h-full outline-none flex justify-center items-center z-60 opacity-100 transition-all duration-200 ease-in-out")}
115
118
  >
116
119
  <div
117
120
  className={cls(paperMixin,
@@ -48,7 +48,8 @@ interface ErrorBoundaryState {
48
48
  export class ErrorBoundary extends React.Component<PropsWithChildren<ErrorBoundaryProps>, ErrorBoundaryState> {
49
49
  constructor(props: PropsWithChildren<ErrorBoundaryProps>) {
50
50
  super(props);
51
- this.state = { error: null, showDetails: false };
51
+ this.state = { error: null,
52
+ showDetails: false };
52
53
  }
53
54
 
54
55
  static getDerivedStateFromError(error: Error) {
@@ -60,7 +61,8 @@ export class ErrorBoundary extends React.Component<PropsWithChildren<ErrorBounda
60
61
  }
61
62
 
62
63
  private handleReset = () => {
63
- this.setState({ error: null, showDetails: false });
64
+ this.setState({ error: null,
65
+ showDetails: false });
64
66
  this.props.onReset?.();
65
67
  };
66
68
 
@@ -137,6 +137,14 @@ export const TableCell = React.memo(({
137
137
  );
138
138
  });
139
139
 
140
+ interface ReactFiber {
141
+ return: ReactFiber;
142
+ type: string | Function;
143
+ elementType?: {
144
+ name?: string;
145
+ };
146
+ }
147
+
140
148
  // This is highly experimental and might break in the future
141
149
  function getParentName(element: HTMLElement | null): string | undefined {
142
150
  if (element) {
@@ -146,19 +154,19 @@ function getParentName(element: HTMLElement | null): string | undefined {
146
154
  key.startsWith("__reactInternalInstance$")
147
155
  );
148
156
  });
149
- // @ts-ignore
150
- const domFiber = element[key];
151
- // @ts-ignore
152
- const getComponentFiber = (fiber) => {
153
- let parentFiber = fiber.return;
154
- while (typeof parentFiber.type === "string") {
155
- parentFiber = parentFiber.return;
156
- }
157
- return parentFiber;
158
- };
159
- let fiber = getComponentFiber(domFiber);
160
- fiber = getComponentFiber(fiber);
161
- return fiber?.elementType?.name;
157
+ if (key) {
158
+ const domFiber = (element as unknown as Record<string, ReactFiber>)[key];
159
+ const getComponentFiber = (fiber: ReactFiber): ReactFiber => {
160
+ let parentFiber = fiber.return;
161
+ while (parentFiber && typeof parentFiber.type === "string") {
162
+ parentFiber = parentFiber.return;
163
+ }
164
+ return parentFiber;
165
+ };
166
+ let fiber = getComponentFiber(domFiber);
167
+ fiber = getComponentFiber(fiber);
168
+ return fiber?.elementType?.name;
169
+ }
162
170
  }
163
171
  return undefined;
164
172
  }
@@ -53,7 +53,7 @@ export type TextFieldProps<T extends string | number> = {
53
53
  * @default 1
54
54
  */
55
55
  minRows?: number | string;
56
- } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">;
56
+ } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "value">;
57
57
 
58
58
  export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | number>>(
59
59
  <T extends string | number>(
@@ -92,8 +92,8 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps<string | numb
92
92
  const hasValue = value !== undefined && value !== null && value !== "";
93
93
 
94
94
  useEffect(() => {
95
- // @ts-ignore
96
- if (inputRef.current && document.activeElement === inputRef.current) {
95
+ const element = inputRef && "current" in inputRef ? inputRef.current : null;
96
+ if (element && document.activeElement === element) {
97
97
  setFocused(true);
98
98
  }
99
99
  }, []);
@@ -65,7 +65,8 @@ export const Tooltip = ({
65
65
 
66
66
  return (
67
67
  <TooltipPrimitive.Provider delayDuration={delayDuration}>
68
- <TooltipPrimitive.Root {...(open !== undefined ? { open, onOpenChange } : {})} defaultOpen={defaultOpen}>
68
+ <TooltipPrimitive.Root {...(open !== undefined ? { open,
69
+ onOpenChange } : {})} defaultOpen={defaultOpen}>
69
70
  {trigger}
70
71
  <TooltipPrimitive.Portal container={finalContainer}>
71
72
  <TooltipPrimitive.Content
@@ -9,7 +9,7 @@ import { VirtualTableProps } from "./VirtualTableProps";
9
9
  // Mock VirtualTableRow to track renders
10
10
  const renderCallback = jest.fn();
11
11
  jest.mock("./VirtualTableRow", () => {
12
- // eslint-disable-next-line @typescript-eslint/no-require-imports
12
+
13
13
  const React = require("react");
14
14
  // Ensure it's memoized as in the real component, otherwise it would re-render anyway if parent renders
15
15
  // But wait, if parent passes new props, it re-renders.
@@ -4,7 +4,6 @@ import React, { createContext, forwardRef, RefObject, useCallback, useEffect, us
4
4
 
5
5
  import { deepEqual as equal } from "fast-equals"
6
6
 
7
- // @ts-expect-error -- react-window does not ship type declarations and @types/react-window is not installed
8
7
  import { FixedSizeList as List } from "react-window";
9
8
  import useMeasure from "react-use-measure";
10
9
 
@@ -320,7 +319,7 @@ export const VirtualTable = React.memo<VirtualTableProps<Record<string, unknown>
320
319
  }, [checkFilterCombination, currentSort, onFilterUpdate, sortByProperty]);
321
320
 
322
321
  const empty = !loading && (data?.length ?? 0) === 0;
323
- const customView = error
322
+ const customView = (error && (data?.length ?? 0) === 0)
324
323
  ? <CenteredView maxWidth={"2xl"}
325
324
  className="flex flex-col gap-2">
326
325
 
@@ -110,10 +110,21 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<Record<stri
110
110
  <div
111
111
  className={`flex items-center justify-${column.headerAlign} flex-row`}>
112
112
 
113
- {column.icon}
113
+ {column.icon && (
114
+ <div className="flex-shrink-0 flex items-center justify-center mr-1">
115
+ {React.isValidElement(column.icon) ? (
116
+ React.cloneElement(column.icon as React.ReactElement<any>, {
117
+ size: 14,
118
+ className: cls((column.icon as React.ReactElement<any>).props?.className, "flex-shrink-0")
119
+ })
120
+ ) : (
121
+ column.icon
122
+ )}
123
+ </div>
124
+ )}
114
125
 
115
126
  <div
116
- className="truncate -webkit-box w-full mx-1 overflow-hidden"
127
+ className="truncate -webkit-box w-full mr-1 overflow-hidden"
117
128
  style={{
118
129
  WebkitBoxOrient: "vertical",
119
130
  WebkitLineClamp: 2,
@@ -130,27 +141,29 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<Record<stri
130
141
  <AdditionalHeaderWidget onHover={onHover || openFilter}/>}
131
142
 
132
143
  {column.sortable && (sort || hovered || openFilter) &&
133
- <Badge color="secondary"
134
- invisible={!sort}>
135
- <IconButton
136
- size={"small"}
137
- className={onHover || openFilter ? "bg-white dark:bg-surface-900" : undefined}
138
- onClick={() => {
139
- onColumnSort(column.key as Extract<keyof M, string>);
140
- }}
141
- >
142
- {!sort &&
143
- <ArrowUpIcon/>}
144
- {sort === "asc" &&
145
- <ArrowUpIcon/>}
146
- {sort === "desc" &&
147
- <ArrowUpIcon className={"rotate-180"}/>}
148
- </IconButton>
149
- </Badge>
144
+ <div className="flex-shrink-0">
145
+ <Badge color="secondary"
146
+ invisible={!sort}>
147
+ <IconButton
148
+ size={"small"}
149
+ className={onHover || openFilter ? "bg-white dark:bg-surface-900" : undefined}
150
+ onClick={() => {
151
+ onColumnSort(column.key as Extract<keyof M, string>);
152
+ }}
153
+ >
154
+ {!sort &&
155
+ <ArrowUpIcon size={14} className="flex-shrink-0" />}
156
+ {sort === "asc" &&
157
+ <ArrowUpIcon size={14} className="flex-shrink-0" />}
158
+ {sort === "desc" &&
159
+ <ArrowUpIcon size={14} className="flex-shrink-0 rotate-180" />}
160
+ </IconButton>
161
+ </Badge>
162
+ </div>
150
163
  }
151
164
  </>
152
165
 
153
- {column.filter && createFilterField && <div>
166
+ {column.filter && createFilterField && <div className="flex-shrink-0">
154
167
  <Badge color="secondary"
155
168
  invisible={!filter}>
156
169
 
@@ -164,7 +177,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<Record<stri
164
177
  className={onHover || openFilter ? "bg-white dark:bg-surface-900" : undefined}
165
178
  size={"small"}
166
179
  onClick={handleSettingsClick}>
167
- <FilterIcon size={iconSize.small}/>
180
+ <FilterIcon size={14} className="flex-shrink-0"/>
168
181
  </IconButton>}
169
182
  >
170
183
  <FilterForm column={column}
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useRef } from "react";
2
2
 
3
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
+
4
4
  export function useDebounceCallback<T extends (...args: any[]) => unknown>(
5
5
  callback?: T,
6
6
  delay?: number
@@ -11,7 +11,7 @@ export const iconSize = {
11
11
  smallest: 16,
12
12
  small: 20,
13
13
  medium: 24,
14
- large: 28,
14
+ large: 28
15
15
  } as const satisfies Record<IconSize, number>;
16
16
 
17
17
  export type IconProps = {
@@ -1933,4 +1933,4 @@ export const iconKeys = [
1933
1933
  "ZodiacVirgo",
1934
1934
  "ZoomIn",
1935
1935
  "ZoomOut"
1936
- ];
1936
+ ];
@@ -125,5 +125,5 @@ export {
125
125
  VoteIcon,
126
126
  Wand2Icon,
127
127
  XCircleIcon,
128
- XIcon,
128
+ XIcon
129
129
  } from "lucide-react";
@@ -20,7 +20,7 @@ const SIZE_MAP = {
20
20
  smallest: 16,
21
21
  small: 20,
22
22
  medium: 24,
23
- large: 28,
23
+ large: 28
24
24
  };
25
25
 
26
26
  const files = process.argv.slice(2);
@@ -46,21 +46,21 @@ for (const filePath of files) {
46
46
  // We replace size={"small"} -> size={20} only when it appears on a lucide icon line
47
47
  const patterns = [
48
48
  `size={"${sizeStr}"}`,
49
- `size="${sizeStr}"`,
49
+ `size="${sizeStr}"`
50
50
  ];
51
-
51
+
52
52
  for (const pattern of patterns) {
53
53
  const replacement = `size={${sizeNum}}`;
54
-
54
+
55
55
  // Process line by line to check context
56
56
  const lines = modified.split("\n");
57
57
  for (let i = 0; i < lines.length; i++) {
58
58
  const line = lines[i];
59
59
  if (!line.includes(pattern)) continue;
60
-
60
+
61
61
  // Skip if this is IconButton, CircularProgress, or other non-lucide components
62
62
  if (/\<(IconButton|CircularProgress|Icon |AIIcon|FileIcon|IconForView|CollectionSize)/.test(line)) continue;
63
-
63
+
64
64
  // Check if this line contains a lucide-style icon component (PascalCase ending with Icon)
65
65
  if (/\<[A-Z][a-zA-Z0-9]+Icon/.test(line)) {
66
66
  const newLine = line.replace(pattern, replacement);
@@ -80,7 +80,7 @@ for (const filePath of files) {
80
80
  // Add iconSize import — but actually we're using raw numbers, not iconSize references
81
81
  // So no import needed. The migration from previous session already set up the constants.
82
82
  }
83
-
83
+
84
84
  fs.writeFileSync(filePath, modified, "utf8");
85
85
  console.log(`✅ ${path.relative(process.cwd(), filePath)}: ${fileReplacements} replacement(s)`);
86
86
  totalReplacements += fileReplacements;
@@ -16,7 +16,7 @@ const SIZE_MAP = {
16
16
  16: "iconSize.smallest",
17
17
  20: "iconSize.small",
18
18
  24: "iconSize.medium",
19
- 28: "iconSize.large",
19
+ 28: "iconSize.large"
20
20
  };
21
21
 
22
22
  // Regex to match lucide icon components with raw numeric sizes
@@ -71,7 +71,7 @@ for (const filePath of files) {
71
71
  if (!content.includes("iconSize")) {
72
72
  // This shouldn't happen since we just added it, but safety check
73
73
  }
74
-
74
+
75
75
  if (content.includes("iconSize") && !content.match(/import\s.*iconSize/)) {
76
76
  // Try to add to existing @rebasepro/ui import
77
77
  const uiImportRe = /import\s*\{([^}]+)\}\s*from\s*["']@rebasepro\/ui["']/;