@heymantle/litho 0.0.14 → 0.0.15

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 (37) hide show
  1. package/README.md +52 -0
  2. package/dist/cjs/components/Card.js +1 -1
  3. package/dist/cjs/components/Disclosure.js +46 -15
  4. package/dist/cjs/components/DropZone.js +89 -37
  5. package/dist/cjs/components/Layout.js +4 -2
  6. package/dist/cjs/components/Modal.js +14 -3
  7. package/dist/cjs/components/Popover.js +26 -9
  8. package/dist/cjs/components/Table.js +27 -11
  9. package/dist/cjs/components/Tabs.js +33 -2
  10. package/dist/cjs/playwright.config.js +114 -0
  11. package/dist/cjs/styles/Table.js +2 -7
  12. package/dist/cjs/tests/visual/stories.spec.js +637 -0
  13. package/dist/cjs/utilities/dates.js +7 -7
  14. package/dist/esm/components/Card.js +1 -1
  15. package/dist/esm/components/Disclosure.js +36 -5
  16. package/dist/esm/components/DropZone.js +89 -37
  17. package/dist/esm/components/Layout.js +4 -2
  18. package/dist/esm/components/Modal.js +14 -3
  19. package/dist/esm/components/Popover.js +26 -9
  20. package/dist/esm/components/Table.js +27 -11
  21. package/dist/esm/components/Tabs.js +33 -2
  22. package/dist/esm/playwright.config.js +104 -0
  23. package/dist/esm/styles/Table.js +2 -7
  24. package/dist/esm/tests/visual/stories.spec.js +633 -0
  25. package/dist/esm/utilities/dates.js +7 -7
  26. package/dist/types/components/Disclosure.d.ts.map +1 -1
  27. package/dist/types/components/DropZone.d.ts +2 -0
  28. package/dist/types/components/DropZone.d.ts.map +1 -1
  29. package/dist/types/components/Layout.d.ts.map +1 -1
  30. package/dist/types/components/Modal.d.ts.map +1 -1
  31. package/dist/types/components/Popover.d.ts +2 -0
  32. package/dist/types/components/Popover.d.ts.map +1 -1
  33. package/dist/types/components/Table.d.ts.map +1 -1
  34. package/dist/types/components/Tabs.d.ts +45 -1
  35. package/dist/types/components/Tabs.d.ts.map +1 -1
  36. package/dist/types/styles/Table.d.ts.map +1 -1
  37. package/package.json +12 -3
package/README.md CHANGED
@@ -107,6 +107,58 @@ Build a customer list page with search and filtering using Litho components.
107
107
 
108
108
  See **`AI_USAGE_IN_PROJECTS.md`** for detailed instructions on accessing guides and Storybook examples from projects using Litho as a dependency.
109
109
 
110
+ ## Visual Regression Testing
111
+
112
+ Litho includes automated visual regression tests using Playwright to catch unintended UI changes.
113
+
114
+ ### Running Tests
115
+
116
+ ```bash
117
+ # Run visual regression tests (uses Docker for CI parity)
118
+ npm run test:visual:docker
119
+
120
+ # Update baseline screenshots (uses Docker for CI parity)
121
+ npm run test:visual:docker:update
122
+ ```
123
+
124
+ > **Note:** The Docker commands ensure baselines match CI exactly. Native commands (`npm run test:visual`) are also available but may produce different results due to font rendering differences between macOS and Linux.
125
+
126
+ ### When Baselines Need Updating
127
+
128
+ Update baselines when you've made **intentional** visual changes:
129
+ - Modified component styles or layout
130
+ - Changed default props or variants
131
+ - Updated theme colors or typography
132
+ - Added new visual states to existing components
133
+
134
+ ### Adding New Stories
135
+
136
+ When you add a new component or story:
137
+ 1. The visual tests automatically discover all stories from Storybook
138
+ 2. Run `npm run test:visual:update` to generate baseline screenshots for new stories
139
+ 3. Commit the new baseline images in `tests/visual/stories.spec.js-snapshots/`
140
+
141
+ ### Skipped Stories
142
+
143
+ Some stories are skipped because they contain non-deterministic content:
144
+ - **Image stories** - Use random external images from picsum.photos
145
+ - Add problematic stories to `SKIP_STORIES` in `tests/visual/stories.spec.js`
146
+
147
+ ### CI Integration
148
+
149
+ Visual tests run automatically on PRs to `main`. If tests fail:
150
+ 1. Download the `visual-regression-diffs` artifact from the workflow
151
+ 2. Review the diff images to determine if changes are intentional
152
+ 3. If intentional, use the **Update Visual Baselines** workflow to regenerate baselines
153
+
154
+ ### Generating Baselines from CI
155
+
156
+ Since macOS and Linux render fonts differently, generate baselines from CI for best results:
157
+
158
+ 1. Go to **Actions** → **Update Visual Baselines** → **Run workflow**
159
+ 2. The workflow will create a PR with updated baselines
160
+ 3. Review and merge the PR
161
+
110
162
  ## License
111
163
 
112
164
  MIT
@@ -516,7 +516,7 @@ var sectionBodyStyles = (0, _tailwindvariants.tv)({
516
516
  };
517
517
  Card.Section.displayName = "Card.Section";
518
518
  var clickableRowStyles = (0, _tailwindvariants.tv)({
519
- base: "Litho-Card__Row py-1.5 px-2.5 cursor-pointer rounded-md hover:bg-tint-2 active:bg-tint-3",
519
+ base: "Litho-Card__Row py-1.5 px-2.5 @md:px-3 cursor-pointer rounded-md hover:bg-tint-2 active:bg-tint-3",
520
520
  variants: {
521
521
  disabled: {
522
522
  true: "opacity-50 cursor-not-allowed pointer-events-none"
@@ -1,3 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "default", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return _default;
9
+ }
10
+ });
11
+ var _jsxruntime = require("react/jsx-runtime");
12
+ var _tailwindvariants = require("tailwind-variants");
13
+ var styles = (0, _tailwindvariants.tv)({
14
+ base: "Litho-Disclosure relative",
15
+ variants: {
16
+ flush: {
17
+ true: "pl-4",
18
+ false: "pl-7.5 @md:pl-7"
19
+ }
20
+ },
21
+ defaultVariants: {
22
+ flush: false
23
+ }
24
+ });
25
+ var borderStyles = (0, _tailwindvariants.tv)({
26
+ base: "Litho-Disclosure__Border absolute top-0 w-px h-full bg-tint-5 dark:bg-tint-alt-5",
27
+ variants: {
28
+ flush: {
29
+ true: "left-0.5",
30
+ false: "left-[9px]"
31
+ }
32
+ },
33
+ defaultVariants: {
34
+ flush: false
35
+ }
36
+ });
1
37
  /**
2
38
  * Renders a Disclosure component that displays content with disclosure styling.
3
39
  *
@@ -18,25 +54,20 @@
18
54
  * <Disclosure className="mt-4">
19
55
  * <Text>This is disclosure content with margin</Text>
20
56
  * </Disclosure>
21
- */ "use strict";
22
- Object.defineProperty(exports, "__esModule", {
23
- value: true
24
- });
25
- Object.defineProperty(exports, "default", {
26
- enumerable: true,
27
- get: function() {
28
- return _default;
29
- }
30
- });
31
- var _jsxruntime = require("react/jsx-runtime");
32
- function Disclosure() {
57
+ */ function Disclosure() {
33
58
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
34
- var children = props.children, className = props.className;
59
+ var children = props.children, className = props.className, _props_flush = props.flush, flush = _props_flush === void 0 ? false : _props_flush;
60
+ var classes = styles({
61
+ flush: flush
62
+ });
63
+ var borderClasses = borderStyles({
64
+ flush: flush
65
+ });
35
66
  return /*#__PURE__*/ (0, _jsxruntime.jsxs)("div", {
36
- className: "relative pl-7.5 @md:pl-7".concat(className ? " ".concat(className) : ""),
67
+ className: "".concat(classes).concat(className ? " ".concat(className) : ""),
37
68
  children: [
38
69
  /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
39
- className: "absolute left-[9px] top-0 w-px h-full bg-tint-5 dark:bg-tint-alt-5"
70
+ className: borderClasses
40
71
  }),
41
72
  /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
42
73
  children: children
@@ -84,6 +84,42 @@ function _unsupported_iterable_to_array(o, minLen) {
84
84
  if (n === "Map" || n === "Set") return Array.from(n);
85
85
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
86
86
  }
87
+ var normalizeDropZoneAccept = function(accept) {
88
+ if (!accept) return [];
89
+ return typeof accept === "string" ? [
90
+ accept
91
+ ] : accept;
92
+ };
93
+ var isFileAcceptedForDropZone = function(file) {
94
+ var accept = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : [];
95
+ if (!accept || accept.length === 0) return true;
96
+ return accept.some(function(type) {
97
+ if (!type) return false;
98
+ if (type === "*/*") return true;
99
+ if (type.endsWith("/*")) {
100
+ var _type_split = _sliced_to_array(type.split("/"), 1), prefix = _type_split[0];
101
+ if (prefix === "*") return true;
102
+ return !!(file === null || file === void 0 ? void 0 : file.type) && file.type.startsWith("".concat(prefix, "/"));
103
+ }
104
+ return (file === null || file === void 0 ? void 0 : file.type) === type;
105
+ });
106
+ };
107
+ var partitionFilesByDropZoneAccept = function(files) {
108
+ var accept = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : [];
109
+ var acceptedFiles = [];
110
+ var rejectedFiles = [];
111
+ files.forEach(function(file) {
112
+ if (isFileAcceptedForDropZone(file, accept)) {
113
+ acceptedFiles.push(file);
114
+ } else {
115
+ rejectedFiles.push(file);
116
+ }
117
+ });
118
+ return {
119
+ acceptedFiles: acceptedFiles,
120
+ rejectedFiles: rejectedFiles
121
+ };
122
+ };
87
123
  var dropZoneStyles = (0, _tailwindvariants.tv)({
88
124
  base: "Litho-DropZone w-full flex items-center justify-center p-4 border border-form-border border-dashed rounded-md focus:outline-hidden",
89
125
  variants: {
@@ -145,11 +181,7 @@ var dropZoneStyles = (0, _tailwindvariants.tv)({
145
181
  */ function DropZone() {
146
182
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
147
183
  var id = props.id, label = props.label, labelHidden = props.labelHidden, labelAction = props.labelAction, tooltip = props.tooltip, labelVariant = props.labelVariant, _props_type = props.type, type = _props_type === void 0 ? "file" : _props_type, _accept = props.accept, _props_allowMultiple = props.allowMultiple, allowMultiple = _props_allowMultiple === void 0 ? false : _props_allowMultiple, _props_showDropZoneWhenDisabled = props.showDropZoneWhenDisabled, showDropZoneWhenDisabled = _props_showDropZoneWhenDisabled === void 0 ? true : _props_showDropZoneWhenDisabled, _disabled = props.disabled, onDrop = props.onDrop, onDropAccepted = props.onDropAccepted, onDropRejected = props.onDropRejected, helpText = props.helpText, error = props.error, _props_uploading = props.uploading, uploading = _props_uploading === void 0 ? false : _props_uploading, _props_uploadedFiles = props.uploadedFiles, uploadedFiles = _props_uploadedFiles === void 0 ? [] : _props_uploadedFiles, _props_uploadLabel = props.uploadLabel, uploadLabel = _props_uploadLabel === void 0 ? "Drag and drop files or click to upload" : _props_uploadLabel, _props_uploadingLabel = props.uploadingLabel, uploadingLabel = _props_uploadingLabel === void 0 ? "Uploading..." : _props_uploadingLabel, _props_dragLabel = props.dragLabel, dragLabel = _props_dragLabel === void 0 ? "Release to upload" : _props_dragLabel, tmp = props.showFilePreview, _showFilePreview = tmp === void 0 ? true : tmp, previewLoading = props.previewLoading, _props_previewRows = props.previewRows, previewRows = _props_previewRows === void 0 ? 2 : _props_previewRows, onRemove = props.onRemove, onFilePreviewClick = props.onFilePreviewClick, filename = props.filename;
148
- var accept = _accept ? typeof _accept === "string" ? [
149
- _accept
150
- ] : _accept : [
151
- "*/*"
152
- ];
184
+ var accept = normalizeDropZoneAccept(_accept);
153
185
  var inputRef = (0, _react.useRef)(null);
154
186
  var _useState = _sliced_to_array((0, _react.useState)(false), 2), hasFocusWithin = _useState[0], setHasFocusWithin = _useState[1];
155
187
  var _useState1 = _sliced_to_array((0, _react.useState)(false), 2), isDraggingOver = _useState1[0], setIsDraggingOver = _useState1[1];
@@ -210,22 +242,7 @@ var dropZoneStyles = (0, _tailwindvariants.tv)({
210
242
  setDragFileCount(0);
211
243
  if (disabled) return;
212
244
  var droppedFiles = Array.from(event.dataTransfer.files);
213
- var acceptedFiles = [];
214
- var rejectedFiles = [];
215
- droppedFiles.forEach(function(file) {
216
- var isValidType = typeAccept.length === 0 || typeAccept.some(function(type) {
217
- if (type.endsWith("/*")) {
218
- return file.type.startsWith("".concat(type.split("/")[0], "/"));
219
- }
220
- return file.type === type;
221
- });
222
- var isValid = isValidType;
223
- if (isValid) {
224
- acceptedFiles.push(file);
225
- } else {
226
- rejectedFiles.push(file);
227
- }
228
- });
245
+ var _partitionFilesByDropZoneAccept = partitionFilesByDropZoneAccept(droppedFiles, typeAccept), acceptedFiles = _partitionFilesByDropZoneAccept.acceptedFiles, rejectedFiles = _partitionFilesByDropZoneAccept.rejectedFiles;
229
246
  if (onDrop) {
230
247
  onDrop(droppedFiles);
231
248
  }
@@ -238,22 +255,7 @@ var dropZoneStyles = (0, _tailwindvariants.tv)({
238
255
  };
239
256
  var handleFileChange = function(event) {
240
257
  var selectedFiles = Array.from(event.target.files);
241
- var acceptedFiles = [];
242
- var rejectedFiles = [];
243
- selectedFiles.forEach(function(file) {
244
- var isValidType = typeAccept.length === 0 || typeAccept.some(function(type) {
245
- if (type.endsWith("/*")) {
246
- return file.type.startsWith("".concat(type.split("/")[0], "/"));
247
- }
248
- return file.type === type;
249
- });
250
- var isValid = isValidType;
251
- if (isValid) {
252
- acceptedFiles.push(file);
253
- } else {
254
- rejectedFiles.push(file);
255
- }
256
- });
258
+ var _partitionFilesByDropZoneAccept = partitionFilesByDropZoneAccept(selectedFiles, typeAccept), acceptedFiles = _partitionFilesByDropZoneAccept.acceptedFiles, rejectedFiles = _partitionFilesByDropZoneAccept.rejectedFiles;
257
259
  if (onDrop) {
258
260
  onDrop(selectedFiles);
259
261
  }
@@ -373,6 +375,56 @@ var dropZoneStyles = (0, _tailwindvariants.tv)({
373
375
  ]
374
376
  });
375
377
  }
378
+ var DropTarget = function() {
379
+ var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
380
+ var _accept = props.accept, _props_allowMultiple = props.allowMultiple, allowMultiple = _props_allowMultiple === void 0 ? true : _props_allowMultiple, _props_disabled = props.disabled, disabled = _props_disabled === void 0 ? false : _props_disabled, onDrop = props.onDrop, onDropAccepted = props.onDropAccepted, onDropRejected = props.onDropRejected, className = props.className, dragOverClassName = props.dragOverClassName, children = props.children;
381
+ var accept = normalizeDropZoneAccept(_accept);
382
+ var _useState = _sliced_to_array((0, _react.useState)(false), 2), isDraggingOver = _useState[0], setIsDraggingOver = _useState[1];
383
+ var handleDragOver = function(event) {
384
+ event.preventDefault();
385
+ event.stopPropagation();
386
+ if (!disabled) {
387
+ setIsDraggingOver(true);
388
+ }
389
+ };
390
+ var handleDragLeave = function(event) {
391
+ event.preventDefault();
392
+ event.stopPropagation();
393
+ setIsDraggingOver(false);
394
+ };
395
+ var handleDrop = function(event) {
396
+ event.preventDefault();
397
+ event.stopPropagation();
398
+ setIsDraggingOver(false);
399
+ if (disabled) return;
400
+ var droppedFiles = Array.from(event.dataTransfer.files);
401
+ if (allowMultiple === false) {
402
+ droppedFiles = droppedFiles.slice(0, 1);
403
+ }
404
+ var _partitionFilesByDropZoneAccept = partitionFilesByDropZoneAccept(droppedFiles, accept), acceptedFiles = _partitionFilesByDropZoneAccept.acceptedFiles, rejectedFiles = _partitionFilesByDropZoneAccept.rejectedFiles;
405
+ if (onDrop) {
406
+ onDrop(droppedFiles);
407
+ }
408
+ if (onDropAccepted && acceptedFiles.length > 0) {
409
+ onDropAccepted(acceptedFiles);
410
+ }
411
+ if (onDropRejected && rejectedFiles.length > 0) {
412
+ onDropRejected(rejectedFiles);
413
+ }
414
+ };
415
+ var dropTargetClassName = [
416
+ className,
417
+ isDraggingOver && dragOverClassName
418
+ ].filter(Boolean).join(" ");
419
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
420
+ className: dropTargetClassName,
421
+ onDragOver: handleDragOver,
422
+ onDragLeave: handleDragLeave,
423
+ onDrop: handleDrop,
424
+ children: children
425
+ });
426
+ };
427
+ DropZone.DropTarget = DropTarget;
376
428
  var previewContainerStyles = (0, _tailwindvariants.tv)({
377
429
  base: "flex flex-col",
378
430
  variants: {
@@ -211,11 +211,13 @@ var annotatedSectionStyles = (0, _tailwindvariants.tv)({
211
211
  var annotatedSectionClasses = annotatedSectionStyles({
212
212
  embedded: embedded
213
213
  });
214
+ var annotationColSpan = embedded ? "@md-embed:col-span-4" : "@md:col-span-4";
215
+ var contentColSpan = embedded ? "@md-embed:col-span-8" : "@md:col-span-8";
214
216
  return /*#__PURE__*/ (0, _jsxruntime.jsxs)("div", {
215
217
  className: annotatedSectionClasses,
216
218
  children: [
217
219
  /*#__PURE__*/ (0, _jsxruntime.jsxs)("div", {
218
- className: "Litho-Layout__Annotation flex flex-col gap-1 @md:col-span-4 py-2",
220
+ className: "Litho-Layout__Annotation flex flex-col gap-1 ".concat(annotationColSpan, " py-2"),
219
221
  children: [
220
222
  title && (typeof title === "string" ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_Text.default, {
221
223
  variant: "headingMd",
@@ -228,7 +230,7 @@ var annotatedSectionStyles = (0, _tailwindvariants.tv)({
228
230
  ]
229
231
  }),
230
232
  /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
231
- className: "Litho-Layout__AnnotationContent @md:col-span-8",
233
+ className: "Litho-Layout__AnnotationContent ".concat(contentColSpan),
232
234
  children: children
233
235
  })
234
236
  ]
@@ -127,7 +127,16 @@ function _unsupported_iterable_to_array(o, minLen) {
127
127
  }
128
128
  var ModalContext = /*#__PURE__*/ (0, _react.createContext)(false);
129
129
  var containerStyles = (0, _tailwindvariants.tv)({
130
- base: "Litho-ModalContainer fixed inset-0 block flex flex-col justify-end md:justify-center items-center pointer-events-none"
130
+ base: "Litho-ModalContainer fixed inset-0 block flex flex-col justify-end md:justify-center items-center pointer-events-none",
131
+ variants: {
132
+ hidden: {
133
+ true: "hidden opacity-0 pointer-events-none",
134
+ false: ""
135
+ }
136
+ },
137
+ defaultVariants: {
138
+ hidden: false
139
+ }
131
140
  });
132
141
  var styles = (0, _tailwindvariants.tv)({
133
142
  base: "Litho-Modal relative bg-surface-highest shadow-modal dark:shadow-modal-dark w-full mx-auto pointer-events-auto flex flex-col overflow-hidden",
@@ -206,7 +215,7 @@ var sectionStyles = (0, _tailwindvariants.tv)({
206
215
  * @returns {React.ReactPortal|null} The rendered modal component or null if not open.
207
216
  */ function Modal() {
208
217
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
209
- var children = props.children, title = props.title, subtitle = props.subtitle, open = props.open, onClose = props.onClose, sectioned = props.sectioned, primaryAction = props.primaryAction, secondaryAction = props.secondaryAction, _props_secondaryActions = props.secondaryActions, secondaryActions = _props_secondaryActions === void 0 ? [] : _props_secondaryActions, destructiveAction = props.destructiveAction, loading = props.loading, _props_size = props.size, size = _props_size === void 0 ? "default" : _props_size, leftAccessory = props.leftAccessory, _props_hideCloseButton = props.hideCloseButton, hideCloseButton = _props_hideCloseButton === void 0 ? false : _props_hideCloseButton, backAction = props.backAction, zIndexOverride = props.zIndexOverride, _props_autoFocusFirstInput = props.autoFocusFirstInput, autoFocusFirstInput = _props_autoFocusFirstInput === void 0 ? true : _props_autoFocusFirstInput, bodyClassName = props.bodyClassName;
218
+ var children = props.children, title = props.title, subtitle = props.subtitle, open = props.open, _props_hidden = props.hidden, hidden = _props_hidden === void 0 ? false : _props_hidden, onClose = props.onClose, sectioned = props.sectioned, primaryAction = props.primaryAction, secondaryAction = props.secondaryAction, _props_secondaryActions = props.secondaryActions, secondaryActions = _props_secondaryActions === void 0 ? [] : _props_secondaryActions, destructiveAction = props.destructiveAction, loading = props.loading, _props_size = props.size, size = _props_size === void 0 ? "default" : _props_size, leftAccessory = props.leftAccessory, _props_hideCloseButton = props.hideCloseButton, hideCloseButton = _props_hideCloseButton === void 0 ? false : _props_hideCloseButton, backAction = props.backAction, zIndexOverride = props.zIndexOverride, _props_autoFocusFirstInput = props.autoFocusFirstInput, autoFocusFirstInput = _props_autoFocusFirstInput === void 0 ? true : _props_autoFocusFirstInput, bodyClassName = props.bodyClassName;
210
219
  var setModalIsOpen = (0, _Frame.useFrame)().setModalIsOpen;
211
220
  var modalContentRef = (0, _react.useRef)(null);
212
221
  var hasChildren = !!children;
@@ -214,7 +223,9 @@ var sectionStyles = (0, _tailwindvariants.tv)({
214
223
  sectioned: sectioned,
215
224
  size: size
216
225
  });
217
- var containerClasses = containerStyles();
226
+ var containerClasses = containerStyles({
227
+ hidden: hidden
228
+ });
218
229
  var headerClasses = headerStyles({
219
230
  hasChildren: hasChildren
220
231
  });
@@ -189,18 +189,23 @@ var styles = (0, _tailwindvariants.tv)({
189
189
  * @param {boolean} [props.sectioned=false] - Whether the popover content is sectioned with padding.
190
190
  * @param {string} [props.preferredAlignment="center"] - Preferred alignment for the popover (left, center, right).
191
191
  * @param {string} [props.preferredPosition="below"] - Preferred position for the popover (above, below, cover, left, right).
192
+ * @param {number} [props.horizontalOffset] - Custom horizontal offset in pixels to adjust the popover position.
192
193
  * @param {string} [props.className] - Additional class name for the popover.
193
194
  * @param {string} [props.containerClassName] - Additional class name for the popover container.
194
195
  * @returns {JSX.Element} The rendered popover component.
195
196
  */ function Popover() {
196
197
  var props = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {};
197
- var activatorWrapper = props.activatorWrapper, _props_activatorDisplayType = props.activatorDisplayType, activatorDisplayType = _props_activatorDisplayType === void 0 ? "inline-block" : _props_activatorDisplayType, activator = props.activator, children = props.children, onClose = props.onClose, active = props.active, fixed = props.fixed, zIndexOverride = props.zIndexOverride, _props_sectioned = props.sectioned, sectioned = _props_sectioned === void 0 ? false : _props_sectioned, _props_preferredAlignment = props.preferredAlignment, preferredAlignment = _props_preferredAlignment === void 0 ? "center" : _props_preferredAlignment, _props_preferredPosition = props.preferredPosition, preferredPosition = _props_preferredPosition === void 0 ? "below" : _props_preferredPosition, _props_matchActivatorWidth = props.matchActivatorWidth, matchActivatorWidth = _props_matchActivatorWidth === void 0 ? false : _props_matchActivatorWidth, _props_maxWidth = props.maxWidth, maxWidth = _props_maxWidth === void 0 ? "default" : _props_maxWidth, className = props.className, containerClassName = props.containerClassName, _props_closeOnResize = props.closeOnResize, closeOnResize = _props_closeOnResize === void 0 ? true : _props_closeOnResize, _props_debounceResizeObserver = props.debounceResizeObserver, debounceResizeObserver = _props_debounceResizeObserver === void 0 ? false : _props_debounceResizeObserver, _props_usePortal = props.usePortal, usePortal = _props_usePortal === void 0 ? true : _props_usePortal;
198
+ var activatorWrapper = props.activatorWrapper, _props_activatorDisplayType = props.activatorDisplayType, activatorDisplayType = _props_activatorDisplayType === void 0 ? "inline-block" : _props_activatorDisplayType, activator = props.activator, children = props.children, onClose = props.onClose, activeProp = props.active, fixed = props.fixed, zIndexOverride = props.zIndexOverride, _props_sectioned = props.sectioned, sectioned = _props_sectioned === void 0 ? false : _props_sectioned, _props_preferredAlignment = props.preferredAlignment, preferredAlignment = _props_preferredAlignment === void 0 ? "center" : _props_preferredAlignment, _props_preferredPosition = props.preferredPosition, preferredPosition = _props_preferredPosition === void 0 ? "below" : _props_preferredPosition, _props_horizontalOffset = props.horizontalOffset, horizontalOffset = _props_horizontalOffset === void 0 ? 0 : _props_horizontalOffset, _props_matchActivatorWidth = props.matchActivatorWidth, matchActivatorWidth = _props_matchActivatorWidth === void 0 ? false : _props_matchActivatorWidth, _props_maxWidth = props.maxWidth, maxWidth = _props_maxWidth === void 0 ? "default" : _props_maxWidth, className = props.className, containerClassName = props.containerClassName, _props_closeOnResize = props.closeOnResize, closeOnResize = _props_closeOnResize === void 0 ? true : _props_closeOnResize, _props_debounceResizeObserver = props.debounceResizeObserver, debounceResizeObserver = _props_debounceResizeObserver === void 0 ? false : _props_debounceResizeObserver, _props_usePortal = props.usePortal, usePortal = _props_usePortal === void 0 ? true : _props_usePortal;
198
199
  var ActivatorWrapper = activatorWrapper || "div";
199
200
  var idValue = (0, _react.useId)();
200
201
  var idRef = (0, _react.useRef)(idValue);
201
202
  var initialWidthRef = (0, _react.useRef)(null);
202
203
  var popoverRef = (0, _react.useRef)(null);
203
204
  var activatorRef = (0, _react.useRef)(null);
205
+ // Check if the activator is inside an inert element (e.g., table cells covered by fixed columns)
206
+ var isActivatorInert = activatorRef.current ? activatorRef.current.closest('[inert]') !== null : false;
207
+ // When disabled, prevent popover from opening
208
+ var active = isActivatorInert ? false : activeProp;
204
209
  var _useState = _sliced_to_array((0, _react.useState)(_object_spread({
205
210
  visibility: "hidden",
206
211
  opacity: 0,
@@ -283,7 +288,7 @@ var styles = (0, _tailwindvariants.tv)({
283
288
  } else if (preferredAlignment === "right") {
284
289
  left = fixed ? activatorRect.right - popoverRect.width : activatorRect.right + window.scrollX - popoverRect.width;
285
290
  } else if (preferredAlignment === "center") {
286
- left = fixed ? activatorRect.left + activatorRect.width / 2 - popoverRect.width / 2 : activatorRect.left + window.scrollX + activatorRect.width / 2 - popoverRect.width / 2;
291
+ left = fixed ? activatorRect.left + activatorRect.width / 2 - popoverRect.width / 2 + horizontalOffset : activatorRect.left + window.scrollX + activatorRect.width / 2 - popoverRect.width / 2 + horizontalOffset;
287
292
  }
288
293
  } else if (position === "above") {
289
294
  top = fixed ? activatorRect.top - popoverRect.height - TOP_SPACING : activatorRect.top + window.scrollY - popoverRect.height - TOP_SPACING;
@@ -292,7 +297,7 @@ var styles = (0, _tailwindvariants.tv)({
292
297
  } else if (preferredAlignment === "right") {
293
298
  left = fixed ? activatorRect.right - popoverRect.width : activatorRect.right + window.scrollX - popoverRect.width;
294
299
  } else if (preferredAlignment === "center") {
295
- left = fixed ? activatorRect.left + activatorRect.width / 2 - popoverRect.width / 2 : activatorRect.left + window.scrollX + activatorRect.width / 2 - popoverRect.width / 2;
300
+ left = fixed ? activatorRect.left + activatorRect.width / 2 - popoverRect.width / 2 + horizontalOffset : activatorRect.left + window.scrollX + activatorRect.width / 2 - popoverRect.width / 2 + horizontalOffset;
296
301
  }
297
302
  } else if (position === "cover") {
298
303
  top = fixed ? activatorRect.top : activatorRect.top + window.scrollY;
@@ -301,7 +306,7 @@ var styles = (0, _tailwindvariants.tv)({
301
306
  } else if (preferredAlignment === "right") {
302
307
  left = fixed ? activatorRect.right - popoverRect.width : activatorRect.right + window.scrollX - popoverRect.width;
303
308
  } else if (preferredAlignment === "center") {
304
- left = fixed ? activatorRect.left + activatorRect.width / 2 - popoverRect.width / 2 : activatorRect.left + window.scrollX + activatorRect.width / 2 - popoverRect.width / 2;
309
+ left = fixed ? activatorRect.left + activatorRect.width / 2 - popoverRect.width / 2 + horizontalOffset : activatorRect.left + window.scrollX + activatorRect.width / 2 - popoverRect.width / 2 + horizontalOffset;
305
310
  }
306
311
  }
307
312
  // Fallback adjustments for vertical positioning (when not in left/right mode)
@@ -314,11 +319,23 @@ var styles = (0, _tailwindvariants.tv)({
314
319
  }
315
320
  }
316
321
  // Fallback adjustments for horizontal positioning
317
- if (left + popoverRect.width > windowWidth) {
318
- left = windowWidth - popoverRect.width - EDGE_SPACING;
319
- }
320
- if (left < 0) {
321
- left = EDGE_SPACING;
322
+ // Only apply edge constraints if we're not using center alignment with custom offset
323
+ // This allows center alignment to work even near edges
324
+ if (preferredAlignment !== "center" || horizontalOffset === 0) {
325
+ if (left + popoverRect.width > windowWidth) {
326
+ left = windowWidth - popoverRect.width - EDGE_SPACING;
327
+ }
328
+ if (left < 0) {
329
+ left = EDGE_SPACING;
330
+ }
331
+ } else {
332
+ // For center alignment with offset, still respect edges but try to maintain centering
333
+ if (left + popoverRect.width > windowWidth) {
334
+ left = Math.max(EDGE_SPACING, windowWidth - popoverRect.width - EDGE_SPACING);
335
+ }
336
+ if (left < 0) {
337
+ left = EDGE_SPACING;
338
+ }
322
339
  }
323
340
  // Fallback adjustments for vertical positioning (when in left/right mode)
324
341
  if (position === "left" || position === "right") {
@@ -28,6 +28,7 @@ var _Text = /*#__PURE__*/ _interop_require_default(require("./Text"));
28
28
  var _Tooltip = /*#__PURE__*/ _interop_require_default(require("./Tooltip"));
29
29
  var _Table = require("../styles/Table");
30
30
  var _useIndexResourceState = require("../utilities/useIndexResourceState");
31
+ var _useMobile = require("../utilities/useMobile");
31
32
  var _useMounted = require("../utilities/useMounted");
32
33
  var _useTableScrollState = /*#__PURE__*/ _interop_require_default(require("../utilities/useTableScrollState"));
33
34
  function _array_like_to_array(arr, len) {
@@ -502,6 +503,8 @@ var TableWrapperContext = /*#__PURE__*/ (0, _react.createContext)({});
502
503
  var alignment = cellAlignment[indexAdjusted];
503
504
  var isSortable = sortable && sortable.length > 0 && sortable[indexAdjusted];
504
505
  var sortIndex = selectable ? indexAdjusted - 1 : indexAdjusted;
506
+ // Check if this cell is covered by a fixed overlay
507
+ var isCovered = !fixed && !reverseColumns && (fixedFirstColumns > 0 && indexAdjusted < fixedFirstColumns || fixedLastColumns > 0 && indexAdjusted >= headings.length - fixedLastColumns);
505
508
  var sortContent = isSortable ? /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
506
509
  className: "min-h-5 ".concat(sort.index === sortIndex ? "opacity-100" : "opacity-0 group-hover:opacity-100"),
507
510
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_Icon.default, {
@@ -536,6 +539,7 @@ var TableWrapperContext = /*#__PURE__*/ (0, _react.createContext)({});
536
539
  ].includes(alignment) && sortContent
537
540
  ]
538
541
  });
542
+ var hasTooltip = (heading.tooltip || heading.tooltipContent) && !isCovered;
539
543
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(HeadingCell, {
540
544
  className: _Table.styles.headingStyles({
541
545
  hidden: hideCell,
@@ -553,7 +557,7 @@ var TableWrapperContext = /*#__PURE__*/ (0, _react.createContext)({});
553
557
  className: _Table.styles.headingLabelContainerStyles({
554
558
  alignment: alignment
555
559
  }),
556
- children: heading.hidden ? null : heading.tooltip || heading.tooltipContent ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_Tooltip.default, {
560
+ children: heading.hidden ? null : hasTooltip ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_Tooltip.default, {
557
561
  content: heading.tooltip || heading.tooltipContent,
558
562
  preferredPosition: "above",
559
563
  children: labelContent
@@ -669,12 +673,16 @@ var TableWrapperContext = /*#__PURE__*/ (0, _react.createContext)({});
669
673
  value: {
670
674
  columnsToRender: columnsToRender,
671
675
  reverseColumns: reverseColumns,
672
- hideCellsOnMobile: hideCellsOnMobile
676
+ hideCellsOnMobile: hideCellsOnMobile,
677
+ isFixedOverlay: fixed,
678
+ fixedFirstColumns: fixedFirstColumns,
679
+ fixedLastColumns: fixedLastColumns
673
680
  },
674
681
  children: /*#__PURE__*/ (0, _jsxruntime.jsxs)("table", {
675
682
  className: _Table.styles.tableStyles(),
676
683
  children: [
677
684
  hasHeadings && renderHeadings({
685
+ fixed: fixed,
678
686
  hidden: true,
679
687
  columnsToRender: columnsToRender,
680
688
  reverseColumns: reverseColumns,
@@ -807,7 +815,8 @@ var TableWrapperContext = /*#__PURE__*/ (0, _react.createContext)({});
807
815
  increasedTableDensity: increasedTableDensity,
808
816
  tableContainerRef: tableContainerRef,
809
817
  cellAlignment: cellAlignment,
810
- noBodyCellPadding: noBodyCellPadding
818
+ noBodyCellPadding: noBodyCellPadding,
819
+ columnWidths: columnWidths
811
820
  },
812
821
  children: /*#__PURE__*/ (0, _jsxruntime.jsxs)("div", {
813
822
  "data-id": idRef.current,
@@ -858,8 +867,7 @@ var TableWrapperContext = /*#__PURE__*/ (0, _react.createContext)({});
858
867
  children: renderTable({
859
868
  classes: "relative overflow-hidden",
860
869
  fixed: true,
861
- columnsToRender: fixedFirstColumns,
862
- hideCellsOnMobile: true
870
+ columnsToRender: fixedFirstColumns
863
871
  })
864
872
  }) : /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
865
873
  className: _Table.styles.overflowShadowStyles({
@@ -877,8 +885,7 @@ var TableWrapperContext = /*#__PURE__*/ (0, _react.createContext)({});
877
885
  classes: "relative overflow-hidden",
878
886
  fixed: true,
879
887
  columnsToRender: fixedLastColumns,
880
- reverseColumns: true,
881
- hideCellsOnMobile: true
888
+ reverseColumns: true
882
889
  })
883
890
  }) : /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
884
891
  className: _Table.styles.overflowShadowStyles({
@@ -1023,8 +1030,11 @@ Table.Row.displayName = "Table.Row";
1023
1030
  */ function Cell(param) {
1024
1031
  var tmp = param.alignment, _alignment = tmp === void 0 ? "start" : tmp, children = param.children, _param_selectionCell = param.selectionCell, selectionCell = _param_selectionCell === void 0 ? false : _param_selectionCell;
1025
1032
  var _useContext = (0, _react.useContext)(TableContext), cellAlignment = _useContext.cellAlignment, increasedTableDensity = _useContext.increasedTableDensity, selectable = _useContext.selectable, noBodyCellPadding = _useContext.noBodyCellPadding;
1026
- var _useContext1 = (0, _react.useContext)(TableWrapperContext), hideCellsOnMobile = _useContext1.hideCellsOnMobile, reverseColumns = _useContext1.reverseColumns, columnsToRender = _useContext1.columnsToRender;
1033
+ var _useContext1 = (0, _react.useContext)(TableWrapperContext), hideCellsOnMobile = _useContext1.hideCellsOnMobile, reverseColumns = _useContext1.reverseColumns, columnsToRender = _useContext1.columnsToRender, isFixedOverlay = _useContext1.isFixedOverlay, fixedFirstColumns = _useContext1.fixedFirstColumns, fixedLastColumns = _useContext1.fixedLastColumns;
1034
+ var isMobile = (0, _useMobile.useMobile)();
1027
1035
  var columnCount = (cellAlignment === null || cellAlignment === void 0 ? void 0 : cellAlignment.length) || 0;
1036
+ // Use state instead of ref so changes trigger re-render
1037
+ var _useState = _sliced_to_array((0, _react.useState)(-1), 2), cellIndex = _useState[0], setCellIndex = _useState[1];
1028
1038
  var cellIndexRef = (0, _react.useRef)(null);
1029
1039
  var setCellRef = (0, _react.useCallback)(function(node) {
1030
1040
  if (!node || cellIndexRef.current !== null) return;
@@ -1032,16 +1042,21 @@ Table.Row.displayName = "Table.Row";
1032
1042
  if ((parentRow === null || parentRow === void 0 ? void 0 : parentRow.tagName) === "TR") {
1033
1043
  var totalCells = parentRow.children.length;
1034
1044
  var index = node.cellIndex;
1035
- cellIndexRef.current = reverseColumns ? totalCells - columnsToRender + index - (totalCells - columnCount) : index;
1045
+ var calculatedIndex = reverseColumns ? totalCells - columnsToRender + index - (totalCells - columnCount) : index;
1046
+ cellIndexRef.current = calculatedIndex;
1047
+ setCellIndex(calculatedIndex);
1036
1048
  }
1037
1049
  }, [
1038
1050
  reverseColumns,
1039
1051
  columnsToRender,
1040
1052
  columnCount
1041
1053
  ]);
1042
- var _cellIndexRef_current;
1043
- var cellIndex = (_cellIndexRef_current = cellIndexRef.current) !== null && _cellIndexRef_current !== void 0 ? _cellIndexRef_current : -1;
1044
1054
  var alignment = (cellAlignment === null || cellAlignment === void 0 ? void 0 : cellAlignment[cellIndex]) || _alignment;
1055
+ // Check if this cell is covered by a fixed overlay
1056
+ // When cellIndex is unknown (-1), conservatively assume the cell might be covered
1057
+ // if there are any fixed columns. This prevents duplicate popovers during the
1058
+ // brief window between initial render and when cellIndex is calculated.
1059
+ var isCoveredByFixedOverlay = !isFixedOverlay && !reverseColumns && (cellIndex === -1 ? fixedFirstColumns > 0 || fixedLastColumns > 0 : fixedFirstColumns > 0 && cellIndex < fixedFirstColumns || fixedLastColumns > 0 && cellIndex >= columnCount - fixedLastColumns);
1045
1060
  return /*#__PURE__*/ (0, _jsxruntime.jsx)("td", {
1046
1061
  ref: setCellRef,
1047
1062
  className: _Table.styles.cellStyles({
@@ -1052,6 +1067,7 @@ Table.Row.displayName = "Table.Row";
1052
1067
  tableIsSelectable: selectable,
1053
1068
  noBodyCellPadding: noBodyCellPadding
1054
1069
  }),
1070
+ inert: !isMobile && isCoveredByFixedOverlay || undefined,
1055
1071
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
1056
1072
  className: _Table.styles.cellContentStyles({
1057
1073
  alignment: alignment