@caratlogic/design-system 0.1.10 → 0.1.12

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 (42) hide show
  1. package/dist/components/layouts/AppShell/AppShell.types.d.ts +14 -1
  2. package/dist/components/layouts/AppShell/AppShell.types.d.ts.map +1 -1
  3. package/dist/components/layouts/AppShell/DashboardShell.d.ts.map +1 -1
  4. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.accessibility.d.ts +4 -0
  5. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.accessibility.d.ts.map +1 -0
  6. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.d.ts +8 -0
  7. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.d.ts.map +1 -0
  8. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.types.d.ts +22 -0
  9. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.types.d.ts.map +1 -0
  10. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.utils.d.ts +4 -0
  11. package/dist/components/molecules/InAppNotificationCard/InAppNotificationCard.utils.d.ts.map +1 -0
  12. package/dist/components/molecules/InAppNotificationCard/index.d.ts +4 -0
  13. package/dist/components/molecules/InAppNotificationCard/index.d.ts.map +1 -0
  14. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.accessibility.d.ts +9 -0
  15. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.accessibility.d.ts.map +1 -0
  16. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.d.ts +9 -0
  17. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.d.ts.map +1 -0
  18. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.hooks.d.ts +12 -0
  19. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.hooks.d.ts.map +1 -0
  20. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.types.d.ts +41 -0
  21. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.types.d.ts.map +1 -0
  22. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.utils.d.ts +4 -0
  23. package/dist/components/organisms/InAppNotificationsPanel/InAppNotificationsPanel.utils.d.ts.map +1 -0
  24. package/dist/components/organisms/InAppNotificationsPanel/index.d.ts +5 -0
  25. package/dist/components/organisms/InAppNotificationsPanel/index.d.ts.map +1 -0
  26. package/dist/components/organisms/MediaGallery/MediaGallery.d.ts +1 -1
  27. package/dist/components/organisms/MediaGallery/MediaGallery.d.ts.map +1 -1
  28. package/dist/components/organisms/MediaGallery/MediaGallery.utils.d.ts +6 -0
  29. package/dist/components/organisms/MediaGallery/MediaGallery.utils.d.ts.map +1 -1
  30. package/dist/components/organisms/Navbar/Navbar.d.ts +1 -1
  31. package/dist/components/organisms/Navbar/Navbar.d.ts.map +1 -1
  32. package/dist/constants/filePatterns.d.ts +3 -0
  33. package/dist/constants/filePatterns.d.ts.map +1 -1
  34. package/dist/index.cjs.js +184 -19
  35. package/dist/index.cjs.js.map +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.esm.js +180 -20
  39. package/dist/index.esm.js.map +1 -1
  40. package/dist/utils/relativeTime.d.ts +6 -0
  41. package/dist/utils/relativeTime.d.ts.map +1 -0
  42. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -43,6 +43,7 @@ export * from './components/molecules/DropdownMenu';
43
43
  export * from './components/molecules/EntityHeader';
44
44
  export * from './components/molecules/Field';
45
45
  export * from './components/molecules/FilterSelect';
46
+ export * from './components/molecules/InAppNotificationCard';
46
47
  export * from './components/molecules/FormSectionCard';
47
48
  export * from './components/molecules/InfoBanner';
48
49
  export * from './components/molecules/InventoryComboBox';
@@ -82,6 +83,7 @@ export * from './components/organisms/AdvancedSearchDrawer';
82
83
  export * from './components/organisms/AuditHistoryPanel';
83
84
  export * from './components/organisms/CustomizeColumnsDrawer';
84
85
  export * from './components/organisms/FileUploader';
86
+ export * from './components/organisms/InAppNotificationsPanel';
85
87
  export * from './components/organisms/InventoryItemCard';
86
88
  export * from './components/organisms/MediaGallery';
87
89
  export * from './components/organisms/Navbar';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -2668,12 +2668,12 @@ const createLucideIcon = (iconName, iconNode) => {
2668
2668
  */
2669
2669
 
2670
2670
 
2671
- const __iconNode$17 = [
2671
+ const __iconNode$18 = [
2672
2672
  ["rect", { width: "20", height: "5", x: "2", y: "3", rx: "1", key: "1wp1u1" }],
2673
2673
  ["path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8", key: "1s80jp" }],
2674
2674
  ["path", { d: "M10 12h4", key: "a56b0p" }]
2675
2675
  ];
2676
- const Archive = createLucideIcon("archive", __iconNode$17);
2676
+ const Archive = createLucideIcon("archive", __iconNode$18);
2677
2677
 
2678
2678
  /**
2679
2679
  * @license lucide-react v0.539.0 - ISC
@@ -2683,11 +2683,11 @@ const Archive = createLucideIcon("archive", __iconNode$17);
2683
2683
  */
2684
2684
 
2685
2685
 
2686
- const __iconNode$16 = [
2686
+ const __iconNode$17 = [
2687
2687
  ["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
2688
2688
  ["path", { d: "M19 12H5", key: "x3x0zl" }]
2689
2689
  ];
2690
- const ArrowLeft = createLucideIcon("arrow-left", __iconNode$16);
2690
+ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$17);
2691
2691
 
2692
2692
  /**
2693
2693
  * @license lucide-react v0.539.0 - ISC
@@ -2697,11 +2697,11 @@ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$16);
2697
2697
  */
2698
2698
 
2699
2699
 
2700
- const __iconNode$15 = [
2700
+ const __iconNode$16 = [
2701
2701
  ["path", { d: "M5 12h14", key: "1ays0h" }],
2702
2702
  ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
2703
2703
  ];
2704
- const ArrowRight = createLucideIcon("arrow-right", __iconNode$15);
2704
+ const ArrowRight = createLucideIcon("arrow-right", __iconNode$16);
2705
2705
 
2706
2706
  /**
2707
2707
  * @license lucide-react v0.539.0 - ISC
@@ -2711,11 +2711,32 @@ const ArrowRight = createLucideIcon("arrow-right", __iconNode$15);
2711
2711
  */
2712
2712
 
2713
2713
 
2714
- const __iconNode$14 = [
2714
+ const __iconNode$15 = [
2715
2715
  ["path", { d: "M4.929 4.929 19.07 19.071", key: "196cmz" }],
2716
2716
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }]
2717
2717
  ];
2718
- const Ban = createLucideIcon("ban", __iconNode$14);
2718
+ const Ban = createLucideIcon("ban", __iconNode$15);
2719
+
2720
+ /**
2721
+ * @license lucide-react v0.539.0 - ISC
2722
+ *
2723
+ * This source code is licensed under the ISC license.
2724
+ * See the LICENSE file in the root directory of this source tree.
2725
+ */
2726
+
2727
+
2728
+ const __iconNode$14 = [
2729
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0", key: "vwvbt9" }],
2730
+ [
2731
+ "path",
2732
+ {
2733
+ d: "M13.916 2.314A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.74 7.327A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673 9 9 0 0 1-.585-.665",
2734
+ key: "1tip0g"
2735
+ }
2736
+ ],
2737
+ ["circle", { cx: "18", cy: "8", r: "3", key: "1g0gzu" }]
2738
+ ];
2739
+ const BellDot = createLucideIcon("bell-dot", __iconNode$14);
2719
2740
 
2720
2741
  /**
2721
2742
  * @license lucide-react v0.539.0 - ISC
@@ -28227,6 +28248,53 @@ const FilterSelect = ({ label, value, onValueChange, options, resetValue = 'all'
28227
28248
  return (jsxs(Select, { value: value, onValueChange: onValueChange, keepOpenOnSelect: true, children: [jsxs(SelectTrigger, { className: cn(TRIGGER_BASE, active && TRIGGER_ACTIVE, triggerClassName), children: [jsx(Funnel, { className: cn('h-4 w-4 shrink-0', active ? 'text-primary' : 'text-muted-foreground'), "aria-hidden": "true" }), jsxs("span", { className: "flex items-center gap-1", children: [jsx(SelectValue, { placeholder: label }), active && (jsx("span", { className: "ml-1 inline-flex h-4 min-w-4 items-center justify-center rounded bg-primary/10 px-1 text-[10px] font-semibold leading-none text-primary", children: activeCount }))] })] }), jsxs(SelectContent, { className: cn(CONTENT_BASE, contentClassName), searchable: searchable, children: [jsx(SelectItem, { value: resetValue, withCheckbox: true, children: label }), options.map((opt) => (jsx(SelectItem, { value: opt.value, searchValue: opt.searchValue, withCheckbox: true, children: opt.label }, opt.value)))] })] }));
28228
28249
  };
28229
28250
 
28251
+ /**
28252
+ * Compact "time ago" label, e.g. "5m ago". Falls back to a locale date past a
28253
+ * week so older entries stay scannable.
28254
+ */
28255
+ function formatRelativeTime(value) {
28256
+ if (!value)
28257
+ return '';
28258
+ const then = value instanceof Date ? value.getTime() : new Date(value).getTime();
28259
+ if (Number.isNaN(then))
28260
+ return '';
28261
+ const minutes = Math.floor((Date.now() - then) / 60000);
28262
+ if (minutes < 1)
28263
+ return 'Just now';
28264
+ if (minutes < 60)
28265
+ return `${minutes}m ago`;
28266
+ const hours = Math.floor(minutes / 60);
28267
+ if (hours < 24)
28268
+ return `${hours}h ago`;
28269
+ const days = Math.floor(hours / 24);
28270
+ if (days < 7)
28271
+ return `${days}d ago`;
28272
+ return new Date(then).toLocaleDateString();
28273
+ }
28274
+
28275
+ /** Read entries sit flat on the surface; unread ones get a primary wash. */
28276
+ function getInAppNotificationCardClasses(isRead, className) {
28277
+ return cn('flex h-[5.125rem] w-full cursor-pointer gap-2.5 rounded-lg border px-3 py-3 text-left transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50', isRead
28278
+ ? 'border-border bg-card hover:bg-muted/40'
28279
+ : 'border-primary/20 bg-primary/5 hover:bg-primary/10', className);
28280
+ }
28281
+ function getInAppNotificationIconClasses(isRead) {
28282
+ return cn('mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full', isRead ? 'bg-muted text-muted-foreground' : 'bg-primary/10 text-primary');
28283
+ }
28284
+
28285
+ const inAppNotificationCardA11y = {
28286
+ unreadLabel: 'Unread',
28287
+ };
28288
+
28289
+ /**
28290
+ * One row in a notification list. Unread state is signalled by a dot, a tinted
28291
+ * surface and a heavier title, so it never relies on colour alone.
28292
+ */
28293
+ const InAppNotificationCard = ({ notification, onSelect, defaultIcon, className, }) => {
28294
+ const { title, message, createdAt, isRead, subject, icon } = notification;
28295
+ return (jsxs("button", { type: "button", onClick: () => onSelect(notification), className: getInAppNotificationCardClasses(isRead, className), children: [jsx("span", { className: getInAppNotificationIconClasses(isRead), children: icon ?? defaultIcon ?? (jsx(BellDot, { className: "size-4", "aria-hidden": "true" })) }), jsxs("span", { className: "min-w-0 flex-1", children: [jsxs("span", { className: "flex items-baseline gap-2", children: [!isRead && (jsx("span", { role: "img", "aria-label": inAppNotificationCardA11y.unreadLabel, className: "size-1.5 shrink-0 rounded-full bg-primary" })), jsx("span", { className: cn('min-w-0 flex-1 truncate text-sm text-foreground', isRead ? 'font-medium' : 'font-semibold'), children: title }), jsx("time", { className: "shrink-0 text-[0.6875rem] text-muted-foreground tabular-nums", children: formatRelativeTime(createdAt) })] }), jsx("span", { className: "mt-1 line-clamp-1 text-xs text-secondary-foreground", children: message }), subject && (jsx("span", { className: "mt-0.5 block truncate text-[0.6875rem] font-medium text-muted-foreground", children: subject }))] })] }));
28296
+ };
28297
+
28230
28298
  const HEADER_TONE_CLASSES = {
28231
28299
  neutral: 'bg-[var(--section-header)]',
28232
28300
  gem: 'bg-primary/5 dark:bg-[#173626]',
@@ -64641,6 +64709,8 @@ const FILE_TYPE_PATTERNS = {
64641
64709
  DOCUMENT: /\.(doc|docx|xls|xlsx|ppt|pptx)($|\?)/i,
64642
64710
  VIDEO: /\.(mp4|m4v|avi|mov|wmv|flv|webm|mkv|ogv|mpe?g|3gp)($|\?)/i,
64643
64711
  AUDIO: /\.(mp3|wav|flac|aac|ogg)($|\?)/i,
64712
+ /** Interactive viewer pages (supplier 360° spinners), not media files. */
64713
+ EMBED: /\.html?($|\?|#)/i,
64644
64714
  };
64645
64715
  const isFileType = {
64646
64716
  pdf: (url) => FILE_TYPE_PATTERNS.PDF.test(url),
@@ -64648,6 +64718,7 @@ const isFileType = {
64648
64718
  document: (url) => FILE_TYPE_PATTERNS.DOCUMENT.test(url),
64649
64719
  video: (url) => FILE_TYPE_PATTERNS.VIDEO.test(url),
64650
64720
  audio: (url) => FILE_TYPE_PATTERNS.AUDIO.test(url),
64721
+ embed: (url) => FILE_TYPE_PATTERNS.EMBED.test(url),
64651
64722
  };
64652
64723
 
64653
64724
  /** Seek to ~1st frame for static video thumbnails (HTML5 media fragment). */
@@ -64663,6 +64734,12 @@ const createVideoMediaItem = (src, index, idPrefix = 'vid') => ({
64663
64734
  src,
64664
64735
  thumbSrc: videoThumbnailSrc(src),
64665
64736
  });
64737
+ /**
64738
+ * True when a "video" is really an interactive viewer page. Supplier 360°
64739
+ * spinners (Vision360, Varni) serve `text/html`, so `<video>` renders nothing
64740
+ * and there is no frame to seek for a thumbnail — they need an iframe.
64741
+ */
64742
+ const isEmbedMediaItem = (item) => item.kind === 'video' && isFileType.embed(item.src);
64666
64743
  const isPdfMediaItem = (item) => item.isPdf === true ||
64667
64744
  isFileType.pdf(item.src) ||
64668
64745
  (item.kind === 'certificate' && !isFileType.image(item.src));
@@ -65616,6 +65693,71 @@ const FileUploader = ({ accept, multiple = true, maxSize = 50, maxFiles = 5, lab
65616
65693
  return (jsxs("div", { className: cn('flex flex-col gap-4', className), children: [label && (jsx("label", { className: "text-sm font-medium text-foreground", children: label })), jsx(DropZone, { uploadSubject: uploadSubject, supportedFormats: supportedFormats, maxSize: maxSize, maxFiles: maxFiles, accept: accept, multiple: multiple, disabled: disabled, onFiles: handlePickerFiles }), files.length > 0 && (jsxs("div", { className: "space-y-2", children: [jsx("p", { className: "text-[13px] text-label", children: "Uploaded File" }), jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: onDragEnd, children: jsx(SortableContext, { items: files.map((f) => f.id), strategy: rectSortingStrategy, children: jsx("div", { className: "flex flex-wrap gap-3", children: files.map((file) => (jsx(FileCard, { file: file, sortable: sortable, disabled: disabled, onRemove: handleRemove, onPreview: previewFile }, file.id))) }) }) })] })), enableUrlInput && (jsxs(Fragment$2, { children: [jsxs("div", { className: "flex items-center gap-3 text-sm font-normal text-label", children: [jsx("span", { className: "h-px flex-1 bg-border" }), jsx("span", { children: "or" }), jsx("span", { className: "h-px flex-1 bg-border" })] }), jsx(UrlImportRow, { disabled: disabled, onAdd: handleAddUrl })] })), cropImages && currentCropFile && cropPreviewUrl && (jsx(ImageCropModal, { open: true, imageUrl: cropPreviewUrl, fileName: currentCropFile.name, fileType: currentCropFile.type, onCancel: handleCropCancel, onConfirm: handleCropConfirm }))] }));
65617
65694
  };
65618
65695
 
65696
+ /** Falls back to counting the current page when the caller has no total. */
65697
+ function resolveUnreadCount(notifications, unreadCount) {
65698
+ if (typeof unreadCount === 'number')
65699
+ return Math.max(0, unreadCount);
65700
+ return notifications.filter((notification) => !notification.isRead).length;
65701
+ }
65702
+
65703
+ // 1. React
65704
+ /** Derived display state for the panel header and body. */
65705
+ function useInAppNotificationsPanel(notifications, unreadCount) {
65706
+ return useMemo(() => {
65707
+ const resolved = resolveUnreadCount(notifications, unreadCount);
65708
+ return {
65709
+ unreadCount: resolved,
65710
+ hasUnread: resolved > 0,
65711
+ isEmpty: notifications.length === 0,
65712
+ };
65713
+ }, [notifications, unreadCount]);
65714
+ }
65715
+ /**
65716
+ * Fires `onLoadMore` when the sentinel scrolls into view inside `rootRef`.
65717
+ * No-ops where IntersectionObserver is unavailable (jsdom, older browsers), so
65718
+ * the list simply stops auto-loading rather than breaking.
65719
+ */
65720
+ function useInfiniteScrollSentinel(rootRef, onLoadMore, enabled) {
65721
+ const sentinelRef = useRef(null);
65722
+ useEffect(() => {
65723
+ const sentinel = sentinelRef.current;
65724
+ if (!sentinel || !enabled || !onLoadMore)
65725
+ return;
65726
+ if (typeof IntersectionObserver === 'undefined')
65727
+ return;
65728
+ const observer = new IntersectionObserver((entries) => {
65729
+ if (entries.some((entry) => entry.isIntersecting))
65730
+ onLoadMore();
65731
+ },
65732
+ // Start fetching slightly before the sentinel is actually visible.
65733
+ { root: rootRef.current, rootMargin: '80px' });
65734
+ observer.observe(sentinel);
65735
+ return () => observer.disconnect();
65736
+ }, [rootRef, onLoadMore, enabled]);
65737
+ return sentinelRef;
65738
+ }
65739
+
65740
+ const inAppNotificationsPanelA11y = {
65741
+ panelLabel: 'Notifications',
65742
+ listLabel: 'Notification list',
65743
+ markAllReadLabel: 'Mark all notifications as read',
65744
+ retryLabel: 'Retry loading notifications',
65745
+ };
65746
+ /** Non-modal dropdown, so `dialog` rather than `alertdialog`. */
65747
+ const IN_APP_NOTIFICATIONS_PANEL_ROLE = 'dialog';
65748
+
65749
+ /**
65750
+ * Notification list with header actions and loading / error / empty states.
65751
+ * Positioning is the caller's concern — render it inside a popover anchored to
65752
+ * whatever opens it (see `DashboardShell`'s `notificationsPanel` slot).
65753
+ */
65754
+ const InAppNotificationsPanel = ({ notifications, onSelect, unreadCount, totalCount, isLoading = false, hasNextPage = false, isFetchingNextPage = false, onLoadMore, isError = false, onRetry, onMarkAllRead, isMarkingAllRead = false, title = inAppNotificationsPanelA11y.panelLabel, emptyTitle = 'No notifications yet', emptyDescription, emptyIcon, emptyState, errorMessage = "Couldn't load notifications.", defaultIcon, className, }) => {
65755
+ const { unreadCount: resolvedUnread, hasUnread, isEmpty, } = useInAppNotificationsPanel(notifications, unreadCount);
65756
+ const listRef = useRef(null);
65757
+ const sentinelRef = useInfiniteScrollSentinel(listRef, onLoadMore, hasNextPage && !isLoading);
65758
+ return (jsxs("div", { role: IN_APP_NOTIFICATIONS_PANEL_ROLE, "aria-label": inAppNotificationsPanelA11y.panelLabel, className: cn('flex w-full flex-col overflow-hidden', className), children: [jsxs("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-border px-2 py-3", children: [jsx("div", { className: "min-w-0", children: jsxs("h2", { className: "flex min-w-0 items-center gap-2 text-sm font-semibold text-foreground", children: [jsx("span", { className: "truncate", children: title }), typeof totalCount === 'number' && (jsx("span", { className: "shrink-0 rounded-full bg-muted px-2 py-0.5 text-[0.6875rem] font-semibold text-muted-foreground tabular-nums", children: totalCount }))] }) }), jsx("span", { className: "flex shrink-0 items-center gap-2", children: hasUnread && onMarkAllRead && (jsx(Button$1, { variant: "ghost", size: "sm", onClick: onMarkAllRead, disabled: isMarkingAllRead, "aria-label": inAppNotificationsPanelA11y.markAllReadLabel, className: "h-7 px-2 text-xs font-medium text-primary hover:text-primary", children: "Mark all as read" })) })] }), jsx("div", { ref: listRef, className: "min-h-0 max-h-[16.75rem] flex-1 overflow-y-auto p-2", children: isLoading ? (jsxs("div", { className: "flex items-center justify-center gap-2 py-10 text-sm text-muted-foreground", children: [jsx(LoaderCircle, { className: "size-4 animate-spin", "aria-hidden": "true" }), "Loading notifications..."] })) : isError ? (jsxs("div", { className: "flex flex-col items-center gap-2 py-10 text-center", children: [jsx("p", { className: "text-sm text-muted-foreground", children: errorMessage }), onRetry && (jsx(Button$1, { variant: "outline", size: "sm", onClick: onRetry, "aria-label": inAppNotificationsPanelA11y.retryLabel, className: "h-7 px-3 text-xs", children: "Try again" }))] })) : isEmpty ? ((emptyState ?? (jsxs("div", { className: "flex flex-col items-center gap-2 px-6 py-10 text-center", children: [jsx("span", { className: "flex size-10 items-center justify-center rounded-full bg-muted", children: emptyIcon ?? (jsx(Bell, { className: "size-5 text-muted-foreground", "aria-hidden": "true" })) }), jsx("p", { className: "text-sm font-medium text-foreground", children: emptyTitle }), emptyDescription && (jsx("p", { className: "max-w-[16rem] text-xs text-muted-foreground", children: emptyDescription }))] })))) : (jsxs("div", { role: "list", "aria-label": inAppNotificationsPanelA11y.listLabel, className: "flex flex-col gap-1.5", children: [notifications.map((notification) => (jsx(InAppNotificationCard, { notification: notification, onSelect: onSelect, defaultIcon: defaultIcon }, notification.id))), hasNextPage && (jsx("div", { ref: sentinelRef, "aria-hidden": "true", className: "h-px" })), isFetchingNextPage && (jsxs("div", { className: "flex items-center justify-center gap-2 py-3 text-xs text-muted-foreground", children: [jsx(LoaderCircle, { className: "size-3.5 animate-spin", "aria-hidden": "true" }), "Loading more..."] }))] })) })] }));
65759
+ };
65760
+
65619
65761
  const stop = (fn) => (e) => {
65620
65762
  e.stopPropagation();
65621
65763
  fn?.();
@@ -65770,9 +65912,22 @@ const PdfPreview = ({ item }) => {
65770
65912
  const docsViewer = `https://docs.google.com/gview?embedded=1&url=${encodeURIComponent(item.src)}`;
65771
65913
  return (jsx("object", { data: item.src, type: "application/pdf", className: "h-full w-full bg-card", "aria-label": item.name ?? 'Certificate PDF', children: jsx("iframe", { title: item.name ?? 'Certificate PDF', className: "h-full w-full border-0 bg-card", src: docsViewer }) }));
65772
65914
  };
65915
+ /**
65916
+ * Videos arrive as either a real file or a supplier's 360° viewer page. The URL
65917
+ * settles most cases, but not every viewer ends in `.html` (v360, loupe360),
65918
+ * so a `<video>` that fails to load falls back to the iframe as well. Remounted
65919
+ * per selection via `key`, which resets that fallback.
65920
+ */
65921
+ const VideoOrEmbed = ({ item }) => {
65922
+ const [useEmbed, setUseEmbed] = useState(() => isEmbedMediaItem(item));
65923
+ if (useEmbed) {
65924
+ return (jsx("iframe", { src: item.src, title: item.name ?? '360\u00B0 viewer', className: "h-full w-full border-0 bg-card", allowFullScreen: true }));
65925
+ }
65926
+ return (jsx("video", { src: item.src, controls: true, onError: () => setUseEmbed(true), className: "h-full w-full object-cover", "aria-label": item.name ?? 'Video' }));
65927
+ };
65773
65928
  const MainMedia = ({ item }) => {
65774
65929
  if (item.kind === 'video') {
65775
- return (jsx("video", { src: item.src, controls: true, className: "h-full w-full object-cover", "aria-label": item.name ?? 'Video' }));
65930
+ return jsx(VideoOrEmbed, { item: item });
65776
65931
  }
65777
65932
  if (isPdfMediaItem(item)) {
65778
65933
  return jsx(PdfPreview, { item: item });
@@ -65782,7 +65937,7 @@ const MainMedia = ({ item }) => {
65782
65937
  const Thumbnail = ({ item, isActive, onSelect }) => {
65783
65938
  const Icon = KIND_ICON[item.kind];
65784
65939
  const isPdf = isPdfMediaItem(item);
65785
- const isVideoWithoutThumb = item.kind === 'video' && !item.thumbSrc;
65940
+ const isVideoWithoutThumb = item.kind === 'video' && (!item.thumbSrc || isEmbedMediaItem(item));
65786
65941
  return (jsxs("button", { type: "button", onClick: onSelect, "aria-label": `Select ${item.name ?? item.kind}`, "aria-pressed": isActive, className: cn('group relative flex aspect-square w-full cursor-pointer overflow-hidden rounded-lg border transition-colors', isActive
65787
65942
  ? 'border-primary ring-2 ring-primary/30'
65788
65943
  : 'border-border hover:border-primary/40'), children: [isPdf ? (jsxs("span", { className: "flex h-full w-full flex-col items-center justify-center gap-1 bg-muted p-2 text-muted-foreground", children: [jsx(Icon, { size: 20 }), jsx("span", { className: "text-[10px] font-medium uppercase", children: "PDF" })] })) : isVideoWithoutThumb ? (jsx("div", { className: "h-full w-full flex items-center justify-center bg-muted", children: jsx(Icon, { size: 24, className: "text-muted-foreground" }) })) : item.kind === 'video' ? (jsx("video", { src: item.thumbSrc ?? item.src, muted: true, playsInline: true, preload: "metadata", className: "h-full w-full object-cover", "aria-hidden": "true" })) : (jsx("img", { src: item.thumbSrc ?? item.src, alt: "", className: "h-full w-full object-cover" })), !isPdf && item.kind !== 'image' && !isVideoWithoutThumb && (jsx("span", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/30 text-white", children: jsx(Icon, { size: 20 }) })), isVideoWithoutThumb && (jsx("span", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/30" }))] }));
@@ -65803,7 +65958,7 @@ const MediaGallery = ({ items, thumbsVisible = 4, hideTabs = false, mainAspectCl
65803
65958
  (kinds.length > 0 ? (jsx(Tabs, { value: activeKind ?? undefined, onValueChange: (v) => setActiveKind(v), tabStyle: "boxed", size: "sm", children: jsx(TabsList, { children: kinds.map((kind) => {
65804
65959
  const Icon = KIND_ICON[kind];
65805
65960
  return (jsx(TabsTrigger, { value: kind, icon: jsx(Icon, { size: 14 }), children: MEDIA_KIND_LABEL[kind] }, kind));
65806
- }) }) })) : (jsx("div", { className: "h-9", "aria-hidden": "true" }))), selected && isZoomableImage ? (jsxs("div", { className: "relative", children: [jsx(ZoomSplitViewer, { src: selected.src, alt: selected.name ?? 'Media' }), !hideDownload && (jsx("div", { className: "absolute right-3 top-3 z-20", children: jsx("button", { type: "button", onClick: handleDownload, "aria-label": "Download media", className: "inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-md border border-border bg-card text-foreground transition-all hover:border-primary hover:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:border-primary focus-visible:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:outline-none", children: jsx(Download, { className: "h-4 w-4" }) }) }))] })) : selected ? (jsxs("div", { className: cn('relative overflow-hidden rounded-xl border border-border bg-muted', mainAspectClassName), children: [jsx(MainMedia, { item: selected }), !hideDownload && (jsx("div", { className: "absolute right-3 top-3", children: jsx("button", { type: "button", onClick: handleDownload, "aria-label": "Download media", className: "inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-md border border-border bg-card text-foreground transition-all hover:border-primary hover:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:border-primary focus-visible:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:outline-none", children: jsx(Download, { className: "h-4 w-4" }) }) }))] })) : (jsx("div", { className: cn('flex items-center justify-center rounded-xl border border-dashed border-border bg-muted/40 text-sm text-muted-foreground', mainAspectClassName), children: emptyText })), visibleItems.length > 1 && (jsx("div", { className: "grid gap-3", style: {
65961
+ }) }) })) : (jsx("div", { className: "h-9", "aria-hidden": "true" }))), selected && isZoomableImage ? (jsxs("div", { className: "relative", children: [jsx(ZoomSplitViewer, { src: selected.src, alt: selected.name ?? 'Media' }), !hideDownload && (jsx("div", { className: "absolute right-3 top-3 z-20", children: jsx("button", { type: "button", onClick: handleDownload, "aria-label": "Download media", className: "inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-md border border-border bg-card text-foreground transition-all hover:border-primary hover:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:border-primary focus-visible:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:outline-none", children: jsx(Download, { className: "h-4 w-4" }) }) }))] })) : selected ? (jsxs("div", { className: cn('relative overflow-hidden rounded-xl border border-border bg-muted', mainAspectClassName), children: [jsx(MainMedia, { item: selected }, selected.id), !hideDownload && !isEmbedMediaItem(selected) && (jsx("div", { className: "absolute right-3 top-3", children: jsx("button", { type: "button", onClick: handleDownload, "aria-label": "Download media", className: "inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-md border border-border bg-card text-foreground transition-all hover:border-primary hover:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:border-primary focus-visible:shadow-[0_0_0_4px_rgba(49,122,81,0.10)] focus-visible:outline-none", children: jsx(Download, { className: "h-4 w-4" }) }) }))] })) : (jsx("div", { className: cn('flex items-center justify-center rounded-xl border border-dashed border-border bg-muted/40 text-sm text-muted-foreground', mainAspectClassName), children: emptyText })), visibleItems.length > 1 && (jsx("div", { className: "grid gap-3", style: {
65807
65962
  gridTemplateColumns: `repeat(${Math.min(visibleItems.length, thumbsVisible)}, minmax(0, 1fr))`,
65808
65963
  }, children: visibleItems.slice(0, thumbsVisible).map((item) => (jsx(Thumbnail, { item: item, isActive: selected?.id === item.id, onSelect: () => setSelected(item) }, item.id))) }))] }));
65809
65964
  };
@@ -65866,7 +66021,7 @@ const navbarIconButtonVariants = cva([
65866
66021
  });
65867
66022
  /** Tiny solid-pill badge ("8") absolutely positioned on the icon button. */
65868
66023
  const navbarIndicatorCountVariants = cva([
65869
- 'absolute -top-1.5 -right-1.5 inline-flex items-center justify-center',
66024
+ 'absolute -top-2.5 -right-2.5 inline-flex items-center justify-center',
65870
66025
  'min-w-4 h-4 px-1 rounded-full ring-2 ring-card',
65871
66026
  'text-[10px] font-medium leading-none tabular-nums text-white',
65872
66027
  ].join(' '), {
@@ -65953,10 +66108,11 @@ const NavbarTitle = ({ title, items, level = 1, className, ...props }) => {
65953
66108
  // ─── Right-side actions container ────────────────────────────────────────────
65954
66109
  const NavbarActions = ({ gap = 'md', className, children, ...props }) => (jsx("div", { className: cn(navbarActionsVariants({ gap }), className), ...props, children: children }));
65955
66110
  // ─── Icon button (with optional dot or count badge) ──────────────────────────
65956
- const NavbarIconButton = ({ appearance = 'plain', size = 'md', indicator = false, className, children, type = 'button', ...props }) => {
66111
+ const NavbarIconButton = forwardRef(({ appearance = 'plain', size = 'md', indicator = false, className, children, type = 'button', ...props }, ref) => {
65957
66112
  const resolved = resolveIndicator(indicator);
65958
- return (jsxs("button", { type: type, className: cn(navbarIconButtonVariants({ appearance, size }), className), ...props, children: [jsx("span", { "aria-hidden": "true", className: "inline-flex", children: children }), resolved && resolved.count !== null && (jsx("span", { role: "img", "aria-label": navbarA11y.notificationIndicatorLabel, className: cn(navbarIndicatorCountVariants({ tone: resolved.tone })), children: resolved.count })), resolved && resolved.count === null && (jsx("span", { role: "img", "aria-label": navbarA11y.notificationIndicatorLabel, className: cn(navbarIndicatorDotVariants({ tone: resolved.tone })) }))] }));
65959
- };
66113
+ return (jsxs("button", { ref: ref, type: type, className: cn(navbarIconButtonVariants({ appearance, size }), className), ...props, children: [jsx("span", { "aria-hidden": "true", className: "inline-flex", children: children }), resolved && resolved.count !== null && (jsx("span", { role: "img", "aria-label": navbarA11y.notificationIndicatorLabel, className: cn(navbarIndicatorCountVariants({ tone: resolved.tone })), children: resolved.count })), resolved && resolved.count === null && (jsx("span", { role: "img", "aria-label": navbarA11y.notificationIndicatorLabel, className: cn(navbarIndicatorDotVariants({ tone: resolved.tone })) }))] }));
66114
+ });
66115
+ NavbarIconButton.displayName = 'NavbarIconButton';
65960
66116
  // ─── Search input (matches Figma 288×36 muted pill) ──────────────────────────
65961
66117
  const NavbarSearch = ({ placeholder = 'Search', value, onChange, onClear, className, containerClassName, ...rest }) => {
65962
66118
  const isControlled = value !== undefined;
@@ -66040,7 +66196,8 @@ const NavLeaf = ({ item, currentPath, onNavigate }) => {
66040
66196
  const Icon = item.icon;
66041
66197
  const active = isPathActive(currentPath, item.path);
66042
66198
  const { collapsed, expandSidebar } = useCollapsedSidebar();
66043
- return (jsxs(SidebarMenuItem, { className: cn(active && 'flex items-center gap-1.5 group-data-[collapsible=icon]:block'), children: [active && jsx(ActiveRail, {}), jsxs(SidebarMenuButton, { isActive: active, tooltip: item.label, onClick: () => {
66199
+ return (jsxs(SidebarMenuItem, { className: cn(active &&
66200
+ 'flex items-center gap-1.5 group-data-[collapsible=icon]:block'), children: [active && jsx(ActiveRail, {}), jsxs(SidebarMenuButton, { isActive: active, tooltip: item.label, onClick: () => {
66044
66201
  if (collapsed)
66045
66202
  expandSidebar();
66046
66203
  if (item.path)
@@ -66090,12 +66247,15 @@ function resolveBreadcrumb(pageMeta, leafOverride) {
66090
66247
  return crumbs;
66091
66248
  return crumbs.map((crumb, index) => index === crumbs.length - 1 ? { ...crumb, label: leafOverride } : crumb);
66092
66249
  }
66093
- const ShellHeader = ({ pageMeta, breadcrumbLeafOverride, user, profileMenuItems, onLogout, showThemeToggle, isDark, onToggleTheme, showNotifications, notificationsIndicator, onNotificationsClick, headerActionsSlot, }) => {
66250
+ const ShellHeader = ({ pageMeta, breadcrumbLeafOverride, user, profileMenuItems, onLogout, showThemeToggle, isDark, onToggleTheme, showNotifications, notificationsIndicator, onNotificationsClick, notificationsPanel, notificationsOpen, onNotificationsOpenChange, headerActionsSlot, }) => {
66094
66251
  const { isMobile } = useSidebar();
66095
66252
  const breadcrumb = resolveBreadcrumb(pageMeta, breadcrumbLeafOverride);
66096
- return (jsxs(Navbar, { "aria-label": "Top navigation", children: [jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [isMobile && jsx(SidebarTrigger, {}), breadcrumb ? (jsx(NavbarTitle, { items: breadcrumb })) : (pageMeta?.title && jsx(NavbarTitle, { title: pageMeta.title }))] }), jsxs(NavbarActions, { gap: "lg", children: [headerActionsSlot, showThemeToggle && (jsx(NavbarIconButton, { "aria-label": isDark ? 'Switch to light theme' : 'Switch to dark theme', size: "md", onClick: onToggleTheme, children: isDark ? jsx(Sun, { size: 20 }) : jsx(Moon, { size: 20 }) })), showNotifications && (jsx(NavbarIconButton, { "aria-label": "Notifications", size: "md", indicator: notificationsIndicator || undefined, onClick: onNotificationsClick, children: jsx(Bell, { size: 20 }) })), jsx(ProfileMenu, { user: user, profileMenuItems: profileMenuItems, onLogout: onLogout })] })] }));
66253
+ return (jsxs(Navbar, { "aria-label": "Top navigation", children: [jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [isMobile && jsx(SidebarTrigger, {}), breadcrumb ? (jsx(NavbarTitle, { items: breadcrumb })) : (pageMeta?.title && jsx(NavbarTitle, { title: pageMeta.title }))] }), jsxs(NavbarActions, { gap: "lg", children: [headerActionsSlot, showThemeToggle && (jsx(NavbarIconButton, { "aria-label": isDark ? 'Switch to light theme' : 'Switch to dark theme', size: "md", onClick: onToggleTheme, children: isDark ? jsx(Sun, { size: 20 }) : jsx(Moon, { size: 20 }) })), showNotifications &&
66254
+ (notificationsPanel ? (jsxs(Popover, { open: notificationsOpen, onOpenChange: onNotificationsOpenChange, children: [jsx(PopoverTrigger, { asChild: true, children: jsx(NavbarIconButton, { "aria-label": "Notifications", size: "md", indicator: notificationsIndicator || undefined, onClick: onNotificationsClick, children: jsx(Bell, { size: 20 }) }) }), jsx(PopoverContent, { align: "end", sideOffset: 8, className: "w-[min(24rem,calc(100vw-2rem))] p-0", children: notificationsPanel })] })) : (jsx(NavbarIconButton, { "aria-label": "Notifications", size: "md", indicator: notificationsIndicator || undefined, onClick: onNotificationsClick, children: jsx(Bell, { size: 20 }) }))), jsx(ProfileMenu, { user: user, profileMenuItems: profileMenuItems, onLogout: onLogout })] })] }));
66097
66255
  };
66098
- const DashboardShell = ({ brand, navGroups, currentPath, onNavigate, pageMeta, breadcrumbLeafOverride, user, profileMenuItems, onLogout, showThemeToggle, isDark, onToggleTheme, showNotifications, notificationsIndicator, onNotificationsClick, headerActionsSlot, sidebarWidth, children, }) => (jsxs(SidebarProvider, { className: "h-screen min-h-0", style: { '--sidebar-width': sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH }, children: [jsx(ShellSidebar, { brand: brand, navGroups: navGroups, currentPath: currentPath, onNavigate: onNavigate }), jsxs(SidebarInset, { className: "flex h-screen flex-col overflow-hidden bg-background", children: [jsx(ShellHeader, { pageMeta: pageMeta, breadcrumbLeafOverride: breadcrumbLeafOverride, user: user, profileMenuItems: profileMenuItems, onLogout: onLogout, showThemeToggle: showThemeToggle, isDark: isDark, onToggleTheme: onToggleTheme, showNotifications: showNotifications, notificationsIndicator: notificationsIndicator, onNotificationsClick: onNotificationsClick, headerActionsSlot: headerActionsSlot }), jsx("main", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto", children: children })] })] }));
66256
+ const DashboardShell = ({ brand, navGroups, currentPath, onNavigate, pageMeta, breadcrumbLeafOverride, user, profileMenuItems, onLogout, showThemeToggle, isDark, onToggleTheme, showNotifications, notificationsIndicator, onNotificationsClick, notificationsPanel, notificationsOpen, onNotificationsOpenChange, headerActionsSlot, sidebarWidth, children, }) => (jsxs(SidebarProvider, { className: "h-screen min-h-0", style: {
66257
+ '--sidebar-width': sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH,
66258
+ }, children: [jsx(ShellSidebar, { brand: brand, navGroups: navGroups, currentPath: currentPath, onNavigate: onNavigate }), jsxs(SidebarInset, { className: "flex h-screen flex-col overflow-hidden bg-background", children: [jsx(ShellHeader, { pageMeta: pageMeta, breadcrumbLeafOverride: breadcrumbLeafOverride, user: user, profileMenuItems: profileMenuItems, onLogout: onLogout, showThemeToggle: showThemeToggle, isDark: isDark, onToggleTheme: onToggleTheme, showNotifications: showNotifications, notificationsIndicator: notificationsIndicator, onNotificationsClick: onNotificationsClick, notificationsPanel: notificationsPanel, notificationsOpen: notificationsOpen, onNotificationsOpenChange: onNotificationsOpenChange, headerActionsSlot: headerActionsSlot }), jsx("main", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto", children: children })] })] }));
66099
66259
 
66100
66260
  function CreateFormPageHeader({ title, onBack, backLabel = 'Go back', }) {
66101
66261
  return (jsxs("div", { className: "mb-4 flex items-center justify-between gap-4", children: [jsx("h1", { className: "min-w-0 text-xl font-semibold leading-7 text-foreground", children: title }), jsx("div", { className: "flex shrink-0 items-center gap-2", children: jsxs(Button$1, { type: "button", variant: "ghost", onClick: onBack, className: "h-8 shrink-0 gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-secondary-foreground hover:text-secondary-foreground", children: [jsx(ChevronLeft, { className: "h-4 w-4", "aria-hidden": true }), backLabel] }) })] }));
@@ -68106,5 +68266,5 @@ var RichTextAreaImpl = /*#__PURE__*/Object.freeze({
68106
68266
  RichTextarea: RichTextarea
68107
68267
  });
68108
68268
 
68109
- export { ActionBadge, ActionButtonWithTooltip, AdvancedSearchDrawer, Alert, ApiComboBox, AppSidebar, AuditEntryCard, AuditHistoryPanel, AuditTimeline, AuditTimelineShimmer, Badge, BadgeType, BusinessContactStatusFilter, Button, CUSTOMER_AUDIT_CONFIG, CertificateRowList, ChangePill, Collapsible, CollapsibleCard, CollapsibleContent, CollapsibleTrigger, ConfirmDialog, ContactTypeFilter, CopyButton, CreateFormPageHeader, CreateFormPageShell, CurrencyDisplayTabs, CurrencyInput, CurrencyToggle, CustomCombobox, CustomTable, CustomizeColumnsDrawer, DIAMOND_AUDIT_CONFIG, DashboardShell, DataEditableTable, DataTable, DateInput, DetailChip, DetailPageLayout, DetailPageSkeleton, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EntityHeader, Field, FieldSkeleton, FileUploader, FilterMultiSelect, FilterSelect, FormSectionCard, FormSectionCardSkeleton, GEM_AUDIT_CONFIG, HistoryEntryCard, HistoryTimeline, HistoryTimelineShimmer, INVOICE_AUDIT_CONFIG, IconButton, IdHyperlinkText, InfoBanner, InventoryComboBox, InventoryItemCard, InventoryItemRow, InvoiceComboBox, KYCStatusFilter, KeyValueGrid, LAYOUT_AUDIT_CONFIG, LabeledToggle, MEMO_IN_AUDIT_CONFIG, MEMO_OUT_AUDIT_CONFIG, MediaGallery, MemoInComboBox, MetricCard, MetricRow, MetricTile, Modal, MoreInfoPanel, MoreInfoTrigger, Navbar, NavbarActions, NavbarAvatar, NavbarIconButton, NavbarSearch, NavbarTitle, NumberInput, NumberedStepCard, OtpInput, OwnershipFilter, OwnershipSplitCard, PROFORMA_AUDIT_CONFIG, PageHeader, PageTabs, Pagination, PaginationSelect, PartnerShareCard, PriceInput, Protected, PurchaseOrderComboBox, QUOTATION_AUDIT_CONFIG, RadioCardGroup, ReadMoreText, RefreshButton, RichTextarea$1 as RichTextarea, SEARCHABLE_SELECT_PAGE_SIZE, SafeImage, SearchBox, SearchInput, SearchableSelect, SectionCard, SectionIcon, SegmentedControl, ShareDialog, ShareMenu, ShimmerTable, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SlideOut, SlideOutClose, SlideOutContent, SlideOutDescription, SlideOutHeader, SlideOutOverlay, SlideOutPortal, SlideOutTitle, SlideOutTrigger, Slider, SpecList, Spinner, SquareSkeleton, StatusBadge$1 as StatusBadge, StatusFilter, Stepper, StockTypeFilter, TablePagination, TableSkeleton, Tabs, TabsContent, TabsList, TabsTrigger, TagsInput, TextHyperlink, TimePicker, ToggleSwitch, TruncateTooltip, WarehouseComboBox, WarehouseFilter, ZoomSplitViewer, badgeVariants, buildBulkShareTitle, buildInventoryPublicUrl, buildInventoryRowProps, buildInventoryShareTitle, buildWhatsappUrl, buttonVariants, cn, createVideoMediaItem, dropdownMenuItemDestructiveClass, formatFileSize, generateActionDescription, generateTitle, getBadgeTypeForStatus, getChangePills, getInitials, iconButtonVariants, isPathActive, notify, releaseStaleOverlayBodyLock, renderBadge, useAuditShowMore, useBadge, useButton, useDropdownMenu, useNavbar, useSidebar, videoThumbnailSrc, watchOverlayBodyLock };
68269
+ export { ActionBadge, ActionButtonWithTooltip, AdvancedSearchDrawer, Alert, ApiComboBox, AppSidebar, AuditEntryCard, AuditHistoryPanel, AuditTimeline, AuditTimelineShimmer, Badge, BadgeType, BusinessContactStatusFilter, Button, CUSTOMER_AUDIT_CONFIG, CertificateRowList, ChangePill, Collapsible, CollapsibleCard, CollapsibleContent, CollapsibleTrigger, ConfirmDialog, ContactTypeFilter, CopyButton, CreateFormPageHeader, CreateFormPageShell, CurrencyDisplayTabs, CurrencyInput, CurrencyToggle, CustomCombobox, CustomTable, CustomizeColumnsDrawer, DIAMOND_AUDIT_CONFIG, DashboardShell, DataEditableTable, DataTable, DateInput, DetailChip, DetailPageLayout, DetailPageSkeleton, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EntityHeader, Field, FieldSkeleton, FileUploader, FilterMultiSelect, FilterSelect, FormSectionCard, FormSectionCardSkeleton, GEM_AUDIT_CONFIG, HistoryEntryCard, HistoryTimeline, HistoryTimelineShimmer, INVOICE_AUDIT_CONFIG, IconButton, IdHyperlinkText, InAppNotificationCard, InAppNotificationsPanel, InfoBanner, InventoryComboBox, InventoryItemCard, InventoryItemRow, InvoiceComboBox, KYCStatusFilter, KeyValueGrid, LAYOUT_AUDIT_CONFIG, LabeledToggle, MEMO_IN_AUDIT_CONFIG, MEMO_OUT_AUDIT_CONFIG, MediaGallery, MemoInComboBox, MetricCard, MetricRow, MetricTile, Modal, MoreInfoPanel, MoreInfoTrigger, Navbar, NavbarActions, NavbarAvatar, NavbarIconButton, NavbarSearch, NavbarTitle, NumberInput, NumberedStepCard, OtpInput, OwnershipFilter, OwnershipSplitCard, PROFORMA_AUDIT_CONFIG, PageHeader, PageTabs, Pagination, PaginationSelect, PartnerShareCard, PriceInput, Protected, PurchaseOrderComboBox, QUOTATION_AUDIT_CONFIG, RadioCardGroup, ReadMoreText, RefreshButton, RichTextarea$1 as RichTextarea, SEARCHABLE_SELECT_PAGE_SIZE, SafeImage, SearchBox, SearchInput, SearchableSelect, SectionCard, SectionIcon, SegmentedControl, ShareDialog, ShareMenu, ShimmerTable, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SlideOut, SlideOutClose, SlideOutContent, SlideOutDescription, SlideOutHeader, SlideOutOverlay, SlideOutPortal, SlideOutTitle, SlideOutTrigger, Slider, SpecList, Spinner, SquareSkeleton, StatusBadge$1 as StatusBadge, StatusFilter, Stepper, StockTypeFilter, TablePagination, TableSkeleton, Tabs, TabsContent, TabsList, TabsTrigger, TagsInput, TextHyperlink, TimePicker, ToggleSwitch, TruncateTooltip, WarehouseComboBox, WarehouseFilter, ZoomSplitViewer, badgeVariants, buildBulkShareTitle, buildInventoryPublicUrl, buildInventoryRowProps, buildInventoryShareTitle, buildWhatsappUrl, buttonVariants, cn, createVideoMediaItem, dropdownMenuItemDestructiveClass, formatFileSize, generateActionDescription, generateTitle, getBadgeTypeForStatus, getChangePills, getInitials, iconButtonVariants, inAppNotificationCardA11y, inAppNotificationsPanelA11y, isPathActive, notify, releaseStaleOverlayBodyLock, renderBadge, useAuditShowMore, useBadge, useButton, useDropdownMenu, useInAppNotificationsPanel, useNavbar, useSidebar, videoThumbnailSrc, watchOverlayBodyLock };
68110
68270
  //# sourceMappingURL=index.esm.js.map