@cccsaurora/howler-ui 2.17.0-dev.563 → 2.17.0-dev.600

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 (169) hide show
  1. package/api/index.d.ts +2 -0
  2. package/api/index.js +4 -2
  3. package/api/search/case.d.ts +4 -0
  4. package/api/search/case.js +8 -0
  5. package/api/search/index.d.ts +2 -1
  6. package/api/search/index.js +2 -1
  7. package/api/v2/case/index.d.ts +6 -0
  8. package/api/v2/case/index.js +18 -0
  9. package/api/v2/index.d.ts +4 -0
  10. package/api/v2/index.js +6 -0
  11. package/api/v2/search/facet.d.ts +3 -0
  12. package/api/v2/search/facet.js +12 -0
  13. package/api/v2/search/index.d.ts +5 -0
  14. package/api/v2/search/index.js +24 -0
  15. package/commons/components/leftnav/LeftNavDrawer.js +1 -1
  16. package/components/app/App.js +14 -0
  17. package/components/app/providers/FavouritesProvider.js +2 -2
  18. package/components/app/providers/HitSearchProvider.d.ts +0 -1
  19. package/components/app/providers/HitSearchProvider.js +6 -11
  20. package/components/app/providers/HitSearchProvider.test.js +11 -32
  21. package/components/app/providers/ParameterProvider.d.ts +9 -2
  22. package/components/app/providers/ParameterProvider.js +165 -240
  23. package/components/app/providers/ParameterProvider.test.js +307 -14
  24. package/components/{routes/overviews/OverviewEditor.js → elements/MarkdownEditor.js} +3 -3
  25. package/components/elements/ObjectDetails.d.ts +6 -0
  26. package/components/elements/{hit/HitDetails.js → ObjectDetails.js} +17 -17
  27. package/components/elements/PluginTypography.d.ts +2 -1
  28. package/components/elements/PluginTypography.js +3 -2
  29. package/components/elements/UserList.d.ts +5 -2
  30. package/components/elements/UserList.js +14 -5
  31. package/components/elements/addons/search/phrase/Phrase.js +1 -1
  32. package/components/elements/case/CaseCard.d.ts +8 -0
  33. package/components/elements/case/CaseCard.js +39 -0
  34. package/components/elements/case/CasePreview.d.ts +6 -0
  35. package/components/elements/case/CasePreview.js +17 -0
  36. package/components/elements/case/StatusIcon.d.ts +5 -0
  37. package/components/elements/case/StatusIcon.js +13 -0
  38. package/components/elements/display/ChipPopper.d.ts +1 -0
  39. package/components/elements/display/ChipPopper.js +2 -2
  40. package/components/elements/display/HowlerCard.js +1 -1
  41. package/components/elements/display/Modal.js +1 -0
  42. package/components/elements/hit/HitBanner.js +28 -48
  43. package/components/elements/hit/HitCard.js +1 -1
  44. package/components/elements/hit/HitLinks.js +5 -3
  45. package/components/elements/hit/{HitQuickSearch.d.ts → HitPreview.d.ts} +3 -3
  46. package/components/elements/hit/{HitQuickSearch.js → HitPreview.js} +10 -4
  47. package/components/elements/hit/HitRelated.d.ts +1 -1
  48. package/components/elements/hit/HitRelated.js +30 -3
  49. package/components/elements/hit/elements/AnalyticLink.d.ts +8 -0
  50. package/components/elements/hit/elements/AnalyticLink.js +22 -0
  51. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  52. package/components/elements/hit/related/RelatedRecords.js +63 -0
  53. package/components/elements/observable/ObservableCard.d.ts +5 -0
  54. package/components/elements/observable/ObservableCard.js +7 -0
  55. package/components/elements/observable/ObservablePreview.d.ts +6 -0
  56. package/components/elements/observable/ObservablePreview.js +12 -0
  57. package/components/elements/view/ViewTitle.js +1 -1
  58. package/components/hooks/useHitActions.d.ts +1 -1
  59. package/components/hooks/useHitActions.js +2 -2
  60. package/components/hooks/useHitSelection.js +3 -24
  61. package/components/hooks/useMyPreferences.js +10 -1
  62. package/components/hooks/useMySearch.js +2 -2
  63. package/components/hooks/useMySitemap.js +4 -1
  64. package/components/hooks/useMyTheme.js +9 -2
  65. package/components/hooks/useRelatedRecords.d.ts +13 -0
  66. package/components/hooks/useRelatedRecords.js +32 -0
  67. package/components/routes/action/view/ActionSearch.js +1 -1
  68. package/components/routes/advanced/QueryBuilder.js +1 -1
  69. package/components/routes/analytics/AnalyticDetails.js +2 -2
  70. package/components/routes/analytics/AnalyticSearch.js +1 -1
  71. package/components/routes/cases/CaseViewer.d.ts +2 -0
  72. package/components/routes/cases/CaseViewer.js +24 -0
  73. package/components/routes/cases/Cases.d.ts +2 -0
  74. package/components/routes/cases/Cases.js +101 -0
  75. package/components/routes/cases/constants.d.ts +5 -0
  76. package/components/routes/cases/constants.js +5 -0
  77. package/components/routes/cases/detail/AlertPanel.d.ts +6 -0
  78. package/components/routes/cases/detail/AlertPanel.js +32 -0
  79. package/components/routes/cases/detail/CaseDashboard.d.ts +7 -0
  80. package/components/routes/cases/detail/CaseDashboard.js +49 -0
  81. package/components/routes/cases/detail/CaseDetails.d.ts +6 -0
  82. package/components/routes/cases/detail/CaseDetails.js +61 -0
  83. package/components/routes/cases/detail/CaseOverview.d.ts +7 -0
  84. package/components/routes/cases/detail/CaseOverview.js +43 -0
  85. package/components/routes/cases/detail/CaseSidebar.d.ts +6 -0
  86. package/components/routes/cases/detail/CaseSidebar.js +36 -0
  87. package/components/routes/cases/detail/CaseTask.d.ts +11 -0
  88. package/components/routes/cases/detail/CaseTask.js +57 -0
  89. package/components/routes/cases/detail/ItemPage.d.ts +6 -0
  90. package/components/routes/cases/detail/ItemPage.js +93 -0
  91. package/components/routes/cases/detail/RelatedCasePanel.d.ts +6 -0
  92. package/components/routes/cases/detail/RelatedCasePanel.js +31 -0
  93. package/components/routes/cases/detail/TaskPanel.d.ts +7 -0
  94. package/components/routes/cases/detail/TaskPanel.js +52 -0
  95. package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +12 -0
  96. package/components/routes/cases/detail/aggregates/CaseAggregate.js +19 -0
  97. package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +6 -0
  98. package/components/routes/cases/detail/aggregates/SourceAggregate.js +27 -0
  99. package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +12 -0
  100. package/components/routes/cases/detail/sidebar/CaseFolder.js +179 -0
  101. package/components/routes/cases/detail/sidebar/types.d.ts +3 -0
  102. package/components/routes/cases/hooks/useCase.d.ts +13 -0
  103. package/components/routes/cases/hooks/useCase.js +38 -0
  104. package/components/routes/cases/modals/ResolveModal.d.ts +7 -0
  105. package/components/routes/cases/modals/ResolveModal.js +59 -0
  106. package/components/routes/help/ApiDocumentation.js +1 -1
  107. package/components/routes/help/HitDocumentation.js +1 -3
  108. package/components/routes/hits/search/HitContextMenu.js +3 -2
  109. package/components/routes/hits/search/InformationPane.d.ts +1 -0
  110. package/components/routes/hits/search/InformationPane.js +6 -28
  111. package/components/routes/hits/search/QuerySettings.js +2 -1
  112. package/components/routes/hits/search/QuerySettings.test.js +14 -9
  113. package/components/routes/hits/search/SearchPane.js +7 -32
  114. package/components/routes/hits/search/ViewLink.js +1 -1
  115. package/components/routes/hits/search/grid/EnhancedCell.js +1 -1
  116. package/components/routes/hits/search/shared/IndexPicker.d.ts +2 -0
  117. package/components/routes/hits/search/shared/IndexPicker.js +20 -0
  118. package/components/routes/hits/view/HitViewer.js +3 -4
  119. package/components/routes/home/ViewCard.js +1 -1
  120. package/components/routes/observables/ObservableViewer.d.ts +7 -0
  121. package/components/routes/observables/ObservableViewer.js +27 -0
  122. package/components/routes/overviews/OverviewViewer.js +2 -2
  123. package/locales/en/translation.json +437 -398
  124. package/locales/fr/translation.json +442 -408
  125. package/models/WithMetadata.d.ts +2 -1
  126. package/models/entities/generated/AttachmentsFile.d.ts +12 -0
  127. package/models/entities/generated/Case.d.ts +28 -0
  128. package/models/entities/generated/DestinationOriginal.d.ts +19 -0
  129. package/models/entities/generated/EmailAttachment.d.ts +8 -0
  130. package/models/entities/generated/EmailParent.d.ts +19 -0
  131. package/models/entities/generated/Enrichments.d.ts +7 -0
  132. package/models/entities/generated/EnrichmentsIndicator.d.ts +21 -0
  133. package/models/entities/generated/Howler.d.ts +0 -4
  134. package/models/entities/generated/HttpResponse.d.ts +11 -0
  135. package/models/entities/generated/Item.d.ts +9 -0
  136. package/models/entities/generated/Observable.d.ts +84 -0
  137. package/models/entities/generated/ObservableCloud.d.ts +20 -0
  138. package/models/entities/generated/ObservableDestination.d.ts +23 -0
  139. package/models/entities/generated/ObservableEmail.d.ts +30 -0
  140. package/models/entities/generated/ObservableFile.d.ts +36 -0
  141. package/models/entities/generated/ObservableHowler.d.ts +44 -0
  142. package/models/entities/generated/ObservableHttp.d.ts +11 -0
  143. package/models/entities/generated/ObservableObserver.d.ts +21 -0
  144. package/models/entities/generated/ObservableOrganization.d.ts +7 -0
  145. package/models/entities/generated/ObservableProcess.d.ts +34 -0
  146. package/models/entities/generated/ObservableSource.d.ts +23 -0
  147. package/models/entities/generated/ObservableThreat.d.ts +21 -0
  148. package/models/entities/generated/ObservableTls.d.ts +12 -0
  149. package/models/entities/generated/ObserverIngress.d.ts +9 -0
  150. package/models/entities/generated/Rule.d.ts +2 -10
  151. package/models/entities/generated/Task.d.ts +10 -0
  152. package/models/entities/generated/Threat.d.ts +2 -2
  153. package/models/entities/generated/{Enrichment.d.ts → ThreatEnrichment.d.ts} +1 -1
  154. package/package.json +16 -1
  155. package/plugins/clue/components/ClueTypography.js +2 -2
  156. package/plugins/clue/utils.d.ts +2 -1
  157. package/utils/constants.d.ts +3 -3
  158. package/utils/typeUtils.d.ts +7 -0
  159. package/utils/typeUtils.js +18 -0
  160. package/components/elements/display/icons/BundleButton.d.ts +0 -6
  161. package/components/elements/display/icons/BundleButton.js +0 -32
  162. package/components/routes/help/BundleDocumentation.d.ts +0 -3
  163. package/components/routes/help/BundleDocumentation.js +0 -12
  164. package/components/routes/help/markdown/en/bundles.md.js +0 -1
  165. package/components/routes/help/markdown/fr/bundles.md.js +0 -1
  166. package/components/routes/hits/search/BundleParentMenu.d.ts +0 -6
  167. package/components/routes/hits/search/BundleParentMenu.js +0 -32
  168. /package/components/{routes/overviews/OverviewEditor.d.ts → elements/MarkdownEditor.d.ts} +0 -0
  169. /package/components/elements/hit/{HitDetails.d.ts → related/RelatedRecords.d.ts} +0 -0
@@ -272,17 +272,17 @@ describe('ParameterContext', () => {
272
272
  });
273
273
  });
274
274
  });
275
- describe('clearFilters', () => {
275
+ describe('resetFilters', () => {
276
276
  it('should clear all filters', async () => {
277
277
  mockSearchParams = new URLSearchParams();
278
278
  mockSearchParams.append('filter', 'filter1');
279
279
  mockSearchParams.append('filter', 'filter2');
280
280
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
281
281
  filters: ctx.filters,
282
- clearFilters: ctx.clearFilters
282
+ resetFilters: ctx.resetFilters
283
283
  })), { wrapper: Wrapper });
284
284
  await act(async () => {
285
- hook.result.current.clearFilters();
285
+ hook.result.current.resetFilters();
286
286
  });
287
287
  await waitFor(() => {
288
288
  expect(hook.result.current.filters).toEqual([]);
@@ -291,10 +291,10 @@ describe('ParameterContext', () => {
291
291
  it('should be no-op when already empty', async () => {
292
292
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
293
293
  filters: ctx.filters,
294
- clearFilters: ctx.clearFilters
294
+ resetFilters: ctx.resetFilters
295
295
  })), { wrapper: Wrapper });
296
296
  await act(async () => {
297
- hook.result.current.clearFilters();
297
+ hook.result.current.resetFilters();
298
298
  });
299
299
  await waitFor(() => {
300
300
  expect(hook.result.current.filters).toEqual([]);
@@ -441,10 +441,10 @@ describe('ParameterContext', () => {
441
441
  mockSearchParams.append('filter', 'filter1');
442
442
  mockSearchParams.append('filter', 'filter2');
443
443
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
444
- clearFilters: ctx.clearFilters
444
+ resetFilters: ctx.resetFilters
445
445
  })), { wrapper: Wrapper });
446
446
  await act(async () => {
447
- hook.result.current.clearFilters();
447
+ hook.result.current.resetFilters();
448
448
  });
449
449
  await waitFor(() => {
450
450
  expect(mockSetParams).toHaveBeenCalled();
@@ -706,6 +706,299 @@ describe('ParameterContext', () => {
706
706
  });
707
707
  });
708
708
  });
709
+ describe('indexes (multi-index support)', () => {
710
+ it('should initialize with default ["hit"] when no index params are present', async () => {
711
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
712
+ expect(hook.result.current).toEqual(['hit']);
713
+ });
714
+ it('should initialize with single index from URL', async () => {
715
+ mockSearchParams = new URLSearchParams({ index: 'observable' });
716
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
717
+ expect(hook.result.current).toEqual(['observable']);
718
+ });
719
+ it('should initialize with multiple indexes from URL', async () => {
720
+ mockSearchParams = new URLSearchParams();
721
+ mockSearchParams.append('index', 'hit');
722
+ mockSearchParams.append('index', 'observable');
723
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
724
+ expect(hook.result.current).toEqual(['hit', 'observable']);
725
+ });
726
+ it('should deduplicate repeated index values from URL', async () => {
727
+ mockSearchParams = new URLSearchParams();
728
+ mockSearchParams.append('index', 'hit');
729
+ mockSearchParams.append('index', 'hit');
730
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
731
+ expect(hook.result.current).toEqual(['hit']);
732
+ });
733
+ describe('addIndex', () => {
734
+ it('should add an index to the default array', async () => {
735
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
736
+ indexes: ctx.indexes,
737
+ addIndex: ctx.addIndex
738
+ })), { wrapper: Wrapper });
739
+ await act(async () => {
740
+ hook.result.current.addIndex('observable');
741
+ });
742
+ await waitFor(() => {
743
+ expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
744
+ });
745
+ });
746
+ it('should not add a duplicate index', async () => {
747
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
748
+ indexes: ctx.indexes,
749
+ addIndex: ctx.addIndex
750
+ })), { wrapper: Wrapper });
751
+ await act(async () => {
752
+ hook.result.current.addIndex('hit');
753
+ });
754
+ await waitFor(() => {
755
+ expect(hook.result.current.indexes).toEqual(['hit']);
756
+ });
757
+ });
758
+ });
759
+ describe('removeIndex', () => {
760
+ it('should remove an index from the list', async () => {
761
+ mockSearchParams = new URLSearchParams();
762
+ mockSearchParams.append('index', 'hit');
763
+ mockSearchParams.append('index', 'observable');
764
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
765
+ indexes: ctx.indexes,
766
+ removeIndex: ctx.removeIndex
767
+ })), { wrapper: Wrapper });
768
+ await act(async () => {
769
+ hook.result.current.removeIndex('hit');
770
+ });
771
+ await waitFor(() => {
772
+ expect(hook.result.current.indexes).toEqual(['observable']);
773
+ });
774
+ });
775
+ it('should do nothing when removing a nonexistent index', async () => {
776
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
777
+ indexes: ctx.indexes,
778
+ removeIndex: ctx.removeIndex
779
+ })), { wrapper: Wrapper });
780
+ await act(async () => {
781
+ hook.result.current.removeIndex('observable');
782
+ });
783
+ await waitFor(() => {
784
+ expect(hook.result.current.indexes).toEqual(['hit']);
785
+ });
786
+ });
787
+ it('should handle removing from empty array', async () => {
788
+ mockSearchParams = new URLSearchParams();
789
+ mockSearchParams.append('index', 'hit');
790
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
791
+ indexes: ctx.indexes,
792
+ removeIndex: ctx.removeIndex
793
+ })), { wrapper: Wrapper });
794
+ await act(async () => {
795
+ hook.result.current.removeIndex('hit');
796
+ });
797
+ await waitFor(() => {
798
+ expect(hook.result.current.indexes).toEqual([]);
799
+ });
800
+ });
801
+ });
802
+ describe('setIndex', () => {
803
+ it('should update the index at the specified position', async () => {
804
+ mockSearchParams = new URLSearchParams();
805
+ mockSearchParams.append('index', 'hit');
806
+ mockSearchParams.append('index', 'observable');
807
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
808
+ indexes: ctx.indexes,
809
+ setIndex: ctx.setIndex
810
+ })), { wrapper: Wrapper });
811
+ await act(async () => {
812
+ hook.result.current.setIndex(0, 'observable');
813
+ });
814
+ await waitFor(() => {
815
+ expect(hook.result.current.indexes).toEqual(['observable', 'observable']);
816
+ });
817
+ });
818
+ it('should do nothing when index is out of bounds', async () => {
819
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
820
+ indexes: ctx.indexes,
821
+ setIndex: ctx.setIndex
822
+ })), { wrapper: Wrapper });
823
+ await act(async () => {
824
+ hook.result.current.setIndex(5, 'observable');
825
+ });
826
+ await waitFor(() => {
827
+ expect(hook.result.current.indexes).toEqual(['hit']);
828
+ });
829
+ });
830
+ it('should do nothing when position is negative', async () => {
831
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
832
+ indexes: ctx.indexes,
833
+ setIndex: ctx.setIndex
834
+ })), { wrapper: Wrapper });
835
+ await act(async () => {
836
+ hook.result.current.setIndex(-1, 'observable');
837
+ });
838
+ await waitFor(() => {
839
+ expect(hook.result.current.indexes).toEqual(['hit']);
840
+ });
841
+ });
842
+ });
843
+ describe('setIndexes', () => {
844
+ it('should replace all indexes with the provided list', async () => {
845
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
846
+ indexes: ctx.indexes,
847
+ setIndexes: ctx.setIndexes
848
+ })), { wrapper: Wrapper });
849
+ await act(async () => {
850
+ hook.result.current.setIndexes(['observable']);
851
+ });
852
+ await waitFor(() => {
853
+ expect(hook.result.current.indexes).toEqual(['observable']);
854
+ });
855
+ });
856
+ it('should deduplicate values when setting all indexes', async () => {
857
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
858
+ indexes: ctx.indexes,
859
+ setIndexes: ctx.setIndexes
860
+ })), { wrapper: Wrapper });
861
+ await act(async () => {
862
+ hook.result.current.setIndexes(['hit', 'hit', 'observable']);
863
+ });
864
+ await waitFor(() => {
865
+ expect(hook.result.current.indexes).toEqual(['hit', 'observable']);
866
+ });
867
+ });
868
+ it('should set to empty array', async () => {
869
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
870
+ indexes: ctx.indexes,
871
+ setIndexes: ctx.setIndexes
872
+ })), { wrapper: Wrapper });
873
+ await act(async () => {
874
+ hook.result.current.setIndexes([]);
875
+ });
876
+ await waitFor(() => {
877
+ expect(hook.result.current.indexes).toEqual([]);
878
+ });
879
+ });
880
+ });
881
+ describe('resetIndexes', () => {
882
+ it('should reset indexes to default ["hit"]', async () => {
883
+ mockSearchParams = new URLSearchParams();
884
+ mockSearchParams.append('index', 'hit');
885
+ mockSearchParams.append('index', 'observable');
886
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
887
+ indexes: ctx.indexes,
888
+ resetIndexes: ctx.resetIndexes
889
+ })), { wrapper: Wrapper });
890
+ await act(async () => {
891
+ hook.result.current.resetIndexes();
892
+ });
893
+ await waitFor(() => {
894
+ expect(hook.result.current.indexes).toEqual(['hit']);
895
+ });
896
+ });
897
+ it('should reset to default even when called on empty array', async () => {
898
+ mockSearchParams = new URLSearchParams();
899
+ mockSearchParams.append('index', 'hit');
900
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
901
+ indexes: ctx.indexes,
902
+ removeIndex: ctx.removeIndex,
903
+ resetIndexes: ctx.resetIndexes
904
+ })), { wrapper: Wrapper });
905
+ // First empty it
906
+ await act(async () => {
907
+ hook.result.current.removeIndex('hit');
908
+ });
909
+ await waitFor(() => {
910
+ expect(hook.result.current.indexes).toEqual([]);
911
+ });
912
+ // Resetting always returns to default ['hit']
913
+ await act(async () => {
914
+ hook.result.current.resetIndexes();
915
+ });
916
+ await waitFor(() => {
917
+ expect(hook.result.current.indexes).toEqual(['hit']);
918
+ });
919
+ });
920
+ });
921
+ describe('URL synchronization', () => {
922
+ it('should not write the default ["hit"] index to the URL', async () => {
923
+ renderHook(() => useContextSelector(ParameterContext, ctx => ctx.indexes), { wrapper: Wrapper });
924
+ // Allow any effects to flush
925
+ await waitFor(() => {
926
+ // If setParams was called, the URL must not contain ?index=hit
927
+ if (mockSetParams.mock.calls.length > 0) {
928
+ const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
929
+ const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
930
+ expect(urlParams.getAll('index')).toEqual([]);
931
+ }
932
+ else {
933
+ // setParams not called at all is also fine
934
+ expect(true).toBe(true);
935
+ }
936
+ });
937
+ });
938
+ it('should write a non-default index to the URL', async () => {
939
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
940
+ addIndex: ctx.addIndex,
941
+ resetIndexes: ctx.resetIndexes
942
+ })), { wrapper: Wrapper });
943
+ await act(async () => {
944
+ hook.result.current.resetIndexes();
945
+ hook.result.current.addIndex('observable');
946
+ });
947
+ await waitFor(() => {
948
+ expect(mockSetParams).toHaveBeenCalled();
949
+ const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
950
+ const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
951
+ expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
952
+ });
953
+ });
954
+ it('should sync multiple indexes to URL as multiple index params', async () => {
955
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
956
+ addIndex: ctx.addIndex
957
+ })), { wrapper: Wrapper });
958
+ await act(async () => {
959
+ hook.result.current.addIndex('observable');
960
+ });
961
+ await waitFor(() => {
962
+ expect(mockSetParams).toHaveBeenCalled();
963
+ const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
964
+ const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
965
+ expect(urlParams.getAll('index')).toEqual(['hit', 'observable']);
966
+ });
967
+ });
968
+ it('should remove all index params from URL when state resets to default', async () => {
969
+ mockSearchParams = new URLSearchParams();
970
+ mockSearchParams.append('index', 'hit');
971
+ mockSearchParams.append('index', 'observable');
972
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
973
+ resetIndexes: ctx.resetIndexes
974
+ })), { wrapper: Wrapper });
975
+ await act(async () => {
976
+ hook.result.current.resetIndexes();
977
+ });
978
+ await waitFor(() => {
979
+ expect(mockSetParams).toHaveBeenCalled();
980
+ const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
981
+ const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
982
+ expect(urlParams.getAll('index')).toEqual([]);
983
+ });
984
+ });
985
+ it('should remove index param from URL when state returns to default', async () => {
986
+ mockSearchParams = new URLSearchParams({ index: 'observable' });
987
+ const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
988
+ setIndexes: ctx.setIndexes
989
+ })), { wrapper: Wrapper });
990
+ await act(async () => {
991
+ hook.result.current.setIndexes(['hit']);
992
+ });
993
+ await waitFor(() => {
994
+ expect(mockSetParams).toHaveBeenCalled();
995
+ const call = mockSetParams.mock.calls[mockSetParams.mock.calls.length - 1];
996
+ const urlParams = typeof call[0] === 'function' ? call[0](mockSearchParams) : call[0];
997
+ expect(urlParams.getAll('index')).toEqual([]);
998
+ });
999
+ });
1000
+ });
1001
+ });
709
1002
  describe('views (multi-view support)', () => {
710
1003
  it('should initialize with empty array when no view params present', async () => {
711
1004
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ctx.views), { wrapper: Wrapper });
@@ -839,17 +1132,17 @@ describe('ParameterContext', () => {
839
1132
  });
840
1133
  });
841
1134
  });
842
- describe('clearViews', () => {
1135
+ describe('resetViews', () => {
843
1136
  it('should clear all views', async () => {
844
1137
  mockSearchParams = new URLSearchParams();
845
1138
  mockSearchParams.append('view', 'view_1');
846
1139
  mockSearchParams.append('view', 'view_2');
847
1140
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
848
1141
  views: ctx.views,
849
- clearViews: ctx.clearViews
1142
+ resetViews: ctx.resetViews
850
1143
  })), { wrapper: Wrapper });
851
1144
  await act(async () => {
852
- hook.result.current.clearViews();
1145
+ hook.result.current.resetViews();
853
1146
  });
854
1147
  await waitFor(() => {
855
1148
  expect(hook.result.current.views).toEqual([]);
@@ -858,10 +1151,10 @@ describe('ParameterContext', () => {
858
1151
  it('should be no-op when already empty', async () => {
859
1152
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
860
1153
  views: ctx.views,
861
- clearViews: ctx.clearViews
1154
+ resetViews: ctx.resetViews
862
1155
  })), { wrapper: Wrapper });
863
1156
  await act(async () => {
864
- hook.result.current.clearViews();
1157
+ hook.result.current.resetViews();
865
1158
  });
866
1159
  await waitFor(() => {
867
1160
  expect(hook.result.current.views).toEqual([]);
@@ -1008,10 +1301,10 @@ describe('ParameterContext', () => {
1008
1301
  mockSearchParams.append('view', 'view_1');
1009
1302
  mockSearchParams.append('view', 'view_2');
1010
1303
  const hook = renderHook(() => useContextSelector(ParameterContext, ctx => ({
1011
- clearViews: ctx.clearViews
1304
+ resetViews: ctx.resetViews
1012
1305
  })), { wrapper: Wrapper });
1013
1306
  await act(async () => {
1014
- hook.result.current.clearViews();
1307
+ hook.result.current.resetViews();
1015
1308
  });
1016
1309
  await waitFor(() => {
1017
1310
  expect(mockSetParams).toHaveBeenCalled();
@@ -4,8 +4,8 @@ import { useTheme } from '@mui/material';
4
4
  import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
5
5
  import ThemedEditor from '@cccsaurora/howler-ui/components/elements/ThemedEditor';
6
6
  import { memo, useCallback, useContext, useEffect, useMemo } from 'react';
7
- import { conf, language } from './markdownExtendedTokenProvider';
8
- const OverviewEditor = ({ content, setContent, onMount, fontSize = 16, height = '100%', width = '100%', editorOptions = {} }) => {
7
+ import { conf, language } from '../routes/overviews/markdownExtendedTokenProvider';
8
+ const MarkdownEditor = ({ content, setContent, onMount, fontSize = 16, height = '100%', width = '100%', editorOptions = {} }) => {
9
9
  const theme = useTheme();
10
10
  const monaco = useMonaco();
11
11
  const { config } = useContext(ApiConfigContext);
@@ -53,4 +53,4 @@ const OverviewEditor = ({ content, setContent, onMount, fontSize = 16, height =
53
53
  }), [fontSize, editorOptions]);
54
54
  return (_jsx(ThemedEditor, { height: height, width: width, theme: theme.palette.mode === 'light' ? 'howler' : 'howler-dark', value: content, onChange: value => setContent(value), beforeMount: beforeEditorMount, onMount: onMount, options: options }));
55
55
  };
56
- export default memo(OverviewEditor);
56
+ export default memo(MarkdownEditor);
@@ -0,0 +1,6 @@
1
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
2
+ import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
3
+ declare const _default: import("react").NamedExoticComponent<{
4
+ obj: Hit | Observable;
5
+ }>;
6
+ export default _default;
@@ -5,12 +5,12 @@ import { ArrowDropDown, InfoOutlined } from '@mui/icons-material';
5
5
  import { Accordion, AccordionDetails, AccordionSummary, Box, Divider, Grid, Stack, TextField, Tooltip, Typography, useTheme } from '@mui/material';
6
6
  import { flatten } from 'flat';
7
7
  import Fuse from 'fuse.js';
8
- import { capitalize, groupBy, isArray, isEmpty, isNull, isObject, isPlainObject, isUndefined, max, sortBy, uniq } from 'lodash-es';
8
+ import { capitalize, groupBy, isArray, isBoolean, isEmpty, isNull, isNumber, isObject, isPlainObject, isUndefined, max, sortBy, uniq } from 'lodash-es';
9
9
  import { memo, useEffect, useMemo, useState } from 'react';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import Throttler from '@cccsaurora/howler-ui/utils/Throttler';
12
- import PluginTypography from '../PluginTypography';
13
- const ListRenderer = memo(({ hit, objKey: key, entries, maxKeyLength }) => {
12
+ import PluginTypography from './PluginTypography';
13
+ const ListRenderer = memo(({ obj, objKey: key, entries, maxKeyLength }) => {
14
14
  const theme = useTheme();
15
15
  const { t } = useTranslation();
16
16
  const allPrimitives = useMemo(() => entries.every(entry => !isObject(entry)), [entries]);
@@ -36,15 +36,15 @@ const ListRenderer = memo(({ hit, objKey: key, entries, maxKeyLength }) => {
36
36
  marginBottom: allPrimitives ? 0 : theme.spacing(1)
37
37
  }, children: allPrimitives ? key.padStart(maxKeyLength ?? key.length) : key }) }), _jsxs(Grid, { container: true, spacing: allPrimitives ? 1 : 4, ml: allPrimitives ? -1 : -4, overflow: "hidden", maxWidth: "100%", children: [uniqueEntries.map((entry, index) => {
38
38
  if (Array.isArray(entry)) {
39
- return (_jsx(Grid, { item: true, xs: "auto", maxWidth: "100%", children: _jsx(ListRenderer, { hit: hit, objKey: `${key}.${index}`, entries: entry }) }, index));
39
+ return (_jsx(Grid, { item: true, xs: "auto", maxWidth: "100%", children: _jsx(ListRenderer, { obj: obj, objKey: `${key}.${index}`, entries: entry }) }, index));
40
40
  }
41
41
  if (isPlainObject(entry)) {
42
42
  return (_jsx(Grid, { item: true, xs: 'auto', maxWidth: "100%", minWidth: "350px", children: _jsx(ObjectRenderer, { parentKey: `${key}.${index}`, indent: true, data: entry }) }, index));
43
43
  }
44
- return (_jsxs(Grid, { item: true, maxWidth: "100%", className: `${key}_${index}`.replace(/\./g, '_'), component: "code", display: "flex", flexDirection: "row", children: [_jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: entry, field: key.replace(/\.[0-9]+/g, ''), hit: hit, children: entry }), allPrimitives && index < uniqueEntries.length - 1 && _jsx("span", { children: "," })] }, entry));
44
+ return (_jsxs(Grid, { item: true, maxWidth: "100%", className: `${key}_${index}`.replace(/\./g, '_'), component: "code", display: "flex", flexDirection: "row", children: [_jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: entry, field: key.replace(/\.[0-9]+/g, ''), obj: obj, children: entry }), allPrimitives && index < uniqueEntries.length - 1 && _jsx("span", { children: "," })] }, entry));
45
45
  }), omittedDuplicates && (_jsx(Grid, { item: true, display: "flex", alignItems: "center", children: _jsx(Tooltip, { title: t('duplicates.omitted'), children: _jsx(InfoOutlined, { sx: { fontSize: '20px', ml: 1 }, color: "disabled" }) }) }))] })] }));
46
46
  });
47
- const ObjectRenderer = memo(({ hit, data, parentKey, indent = false }) => {
47
+ const ObjectRenderer = memo(({ obj: obj, data, parentKey, indent = false }) => {
48
48
  const theme = useTheme();
49
49
  const entries = useMemo(() => {
50
50
  const unsorted = Object.entries(flatten(data, { safe: true })).map(([key, val]) => [key, val]);
@@ -56,10 +56,10 @@ const ObjectRenderer = memo(({ hit, data, parentKey, indent = false }) => {
56
56
  }, [data]);
57
57
  const longestKey = useMemo(() => max(entries.map(([key]) => key.length)), [entries]);
58
58
  return (_jsxs(Stack, { direction: "row", overflow: "hidden", maxWidth: "100%", children: [indent && _jsx(Divider, { orientation: "vertical", flexItem: true, sx: { borderColor: 'primary.main', borderWidth: '2px' } }), _jsx(Stack, { flex: 1, ml: 1, maxWidth: "100%", children: entries
59
- .filter(([__, val]) => !isNull(val) && !isUndefined(val) && !isEmpty(val))
59
+ .filter(([__, val]) => !isNull(val) && !isUndefined(val) && !isEmpty(val) && !isBoolean(val) && !isNumber(val))
60
60
  .map(([key, val]) => {
61
61
  if (Array.isArray(val)) {
62
- return _jsx(ListRenderer, { hit: hit, maxKeyLength: longestKey, objKey: key, entries: val }, key);
62
+ return _jsx(ListRenderer, { obj: obj, maxKeyLength: longestKey, objKey: key, entries: val }, key);
63
63
  }
64
64
  return (_jsxs("code", { className: (parentKey ? `${parentKey}.${key}` : key).replace(/\./g, '_'), style: {
65
65
  display: 'grid',
@@ -75,14 +75,14 @@ const ObjectRenderer = memo(({ hit, data, parentKey, indent = false }) => {
75
75
  paddingRight: theme.spacing(1),
76
76
  height: '100%',
77
77
  wordWrap: 'break-word'
78
- }, children: _jsx("code", { style: { maxWidth: '100%' }, children: key }) }), _jsx(Box, { display: "flex", alignItems: "start", children: _jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: val, field: (parentKey ? parentKey.concat('.', key) : key).replace(/\.[0-9]+/g, ''), hit: hit, children: val }) })] }, key));
78
+ }, children: _jsx("code", { style: { maxWidth: '100%' }, children: key }) }), _jsx(Box, { display: "flex", alignItems: "start", children: _jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: val, field: (parentKey ? parentKey.concat('.', key) : key).replace(/\.[0-9]+/g, ''), obj: obj, children: val }) })] }, key));
79
79
  }) })] }));
80
80
  });
81
- const Collapsible = memo(({ hit, title, data, query }) => {
81
+ const Collapsible = memo(({ obj, title, data, query }) => {
82
82
  const throttler = useMemo(() => new Throttler(400), []);
83
83
  const [scores, setScores] = useState([]);
84
84
  const [results, setResults] = useState({});
85
- const options = useMemo(() => Object.entries(data).map(([key, value]) => ({ key, value })), [data]);
85
+ const options = useMemo(() => Object.entries(data).map(([key, value]) => ({ key, value: value.toString() })), [data]);
86
86
  const keys = useMemo(() => options
87
87
  .flatMap(option => (isArray(option.value) ? Object.keys(flatten(option.value)) : []))
88
88
  .map(key => key.replace(/\d+/g, 'value'))
@@ -109,20 +109,20 @@ const Collapsible = memo(({ hit, title, data, query }) => {
109
109
  if (isEmpty(results)) {
110
110
  return null;
111
111
  }
112
- return (_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ArrowDropDown, {}), children: _jsx(Typography, { children: title }) }), _jsx(AccordionDetails, { children: _jsx(Stack, { spacing: 1, justifyContent: "stretch", sx: styles, children: _jsx(ObjectRenderer, { hit: hit, showParentKey: true, data: results }) }) })] }));
112
+ return (_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ArrowDropDown, {}), sx: { my: 0 }, children: _jsx(Typography, { children: title }) }), _jsx(AccordionDetails, { children: _jsx(Stack, { spacing: 0.5, justifyContent: "stretch", sx: styles, children: _jsx(ObjectRenderer, { obj: obj, showParentKey: true, data: results }) }) })] }));
113
113
  });
114
- const HitDetails = ({ hit }) => {
114
+ const ObjectDetails = ({ obj }) => {
115
115
  const { t } = useTranslation();
116
116
  const [query, setQuery] = useState('');
117
- const groups = useMemo(() => groupBy(Object.entries(flatten(hit ?? {}, { safe: true })).filter(([key, value]) => !key.startsWith('__') &&
117
+ const groups = useMemo(() => groupBy(Object.entries(flatten(obj ?? {}, { safe: true })).filter(([key, value]) => !key.startsWith('__') &&
118
118
  key.includes('.') &&
119
119
  ['howler', 'labels'].every(prefix => !key.startsWith(prefix)) &&
120
- !isEmpty(value)), ([key]) => key.split('.')[0]), [hit]);
120
+ (!isEmpty(value) || isNumber(value) || isBoolean(value))), ([key]) => key.split('.')[0]), [obj]);
121
121
  return (_jsxs(Stack, { spacing: 1, children: [_jsx(TextField, { value: query, onChange: event => setQuery(event.target.value), label: t('overview.search') }), Object.entries(groups).map(([section, entries]) => {
122
- return (_jsx(Collapsible, { hit: hit, query: query, title: section
122
+ return (_jsx(Collapsible, { obj: obj, query: query, title: section
123
123
  .split('_')
124
124
  .map(word => capitalize(word))
125
125
  .join(' '), data: Object.fromEntries(entries) }, section));
126
126
  })] }));
127
127
  };
128
- export default memo(HitDetails);
128
+ export default memo(ObjectDetails);
@@ -1,10 +1,11 @@
1
1
  import { type TypographyProps } from '@mui/material';
2
2
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
3
+ import type { Observable } from '@cccsaurora/howler-ui/models/entities/generated/Observable';
3
4
  export type PluginTypographyProps = TypographyProps & {
4
5
  value: string;
5
6
  context: string;
6
7
  field?: string;
7
- hit?: Hit;
8
+ obj?: Hit | Observable;
8
9
  };
9
10
  declare const _default: import("react").NamedExoticComponent<PluginTypographyProps>;
10
11
  export default _default;
@@ -3,7 +3,7 @@ import { Typography } from '@mui/material';
3
3
  import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
4
4
  import { memo } from 'react';
5
5
  import { usePluginStore } from 'react-pluggable';
6
- const PluginTypography = ({ children, value, context, field, hit, ...props }) => {
6
+ const PluginTypography = ({ children, value, context, field, obj, ...props }) => {
7
7
  const pluginStore = usePluginStore();
8
8
  for (const plugin of howlerPluginStore.plugins) {
9
9
  const component = pluginStore.executeFunction(`${plugin}.typography`, {
@@ -11,7 +11,8 @@ const PluginTypography = ({ children, value, context, field, hit, ...props }) =>
11
11
  value,
12
12
  context,
13
13
  field,
14
- hit,
14
+ hit: obj,
15
+ obj,
15
16
  ...props
16
17
  });
17
18
  if (component) {
@@ -2,8 +2,11 @@ import type { SxProps, Theme } from '@mui/material';
2
2
  import type { FC } from 'react';
3
3
  declare const UserList: FC<{
4
4
  buttonSx?: SxProps<Theme>;
5
- userId: string;
6
- onChange: (userId: string) => void;
5
+ userIds: string[];
6
+ onChange: (userIds: string[]) => void;
7
7
  i18nLabel: string;
8
+ avatarHeight?: number;
9
+ disabled?: boolean;
10
+ multiple?: boolean;
8
11
  }>;
9
12
  export default UserList;
@@ -1,18 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Autocomplete, Box, IconButton, Popover, TextField, Typography } from '@mui/material';
2
+ import { Add } from '@mui/icons-material';
3
+ import { Autocomplete, AvatarGroup, Box, IconButton, Popover, Stack, TextField, Typography } from '@mui/material';
3
4
  import { UserListContext } from '@cccsaurora/howler-ui/components/app/providers/UserListProvider';
5
+ import { uniq } from 'lodash-es';
4
6
  import { useContext, useEffect, useMemo, useState } from 'react';
5
7
  import { useTranslation } from 'react-i18next';
6
8
  import HowlerAvatar from './display/HowlerAvatar';
7
- const UserList = ({ buttonSx = {}, userId, onChange, i18nLabel }) => {
9
+ const UserList = ({ buttonSx = {}, userIds, onChange, i18nLabel, avatarHeight = 32, multiple = false, disabled = false }) => {
8
10
  const { t } = useTranslation();
9
11
  const [anchorEl, setAnchorEl] = useState(null);
10
12
  const { users, searchUsers } = useContext(UserListContext);
11
- const userIds = useMemo(() => Object.keys(users), [users]);
13
+ const allUserIds = useMemo(() => Object.keys(users), [users]);
12
14
  useEffect(() => {
13
15
  searchUsers('uname:*');
14
16
  }, [searchUsers]);
15
- return (_jsxs(_Fragment, { children: [_jsx(IconButton, { sx: buttonSx, onClick: e => setAnchorEl(e.currentTarget), children: _jsx(HowlerAvatar, { userId: userId }) }), _jsx(Popover, { open: !!anchorEl, onClose: () => setAnchorEl(null), anchorEl: anchorEl, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: _jsx(Box, { sx: { p: 2 }, children: _jsx(Autocomplete, { sx: { minWidth: '300px' }, options: userIds, renderInput: params => _jsx(TextField, { ...params, label: t(i18nLabel), size: "small" }), renderOption: (props, _userId) => {
17
+ return (_jsxs(_Fragment, { children: [multiple ? (_jsxs(Stack, { direction: "row", spacing: 0.25, alignItems: "center", children: [_jsx(AvatarGroup, { children: uniq(userIds ?? [null]).map(userId => (_jsx(HowlerAvatar, { userId: userId, sx: { height: avatarHeight, width: avatarHeight } }, userId))) }), _jsx(IconButton, { size: "small", sx: buttonSx, disabled: disabled, onClick: e => setAnchorEl(e.currentTarget), children: _jsx(Add, {}) })] })) : (_jsx(IconButton, { sx: buttonSx, disabled: disabled, onClick: e => setAnchorEl(e.currentTarget), children: _jsx(HowlerAvatar, { userId: userIds[0], sx: { height: avatarHeight, width: avatarHeight } }) })), _jsx(Popover, { open: !!anchorEl, onClose: () => setAnchorEl(null), anchorEl: anchorEl, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: _jsx(Box, { sx: { p: 2 }, children: _jsx(Autocomplete, { disabled: disabled, multiple: multiple, sx: { minWidth: '300px' }, options: allUserIds, renderInput: params => _jsx(TextField, { ...params, label: t(i18nLabel), size: "small" }), renderOption: (props, _userId) => {
16
18
  const user = users[_userId];
17
19
  return (_jsx("li", { ...props, children: _jsxs(Box, { sx: {
18
20
  display: 'grid',
@@ -21,6 +23,13 @@ const UserList = ({ buttonSx = {}, userId, onChange, i18nLabel }) => {
21
23
  gridTemplateAreas: `"profile name"\n"profile email"`,
22
24
  columnGap: 1.5
23
25
  }, children: [_jsx(HowlerAvatar, { sx: { gridArea: 'profile', alignSelf: 'center', height: '32px', width: '32px' }, userId: user.username }), _jsx(Typography, { sx: { gridArea: 'name' }, variant: "body1", children: user.name }), _jsx(Typography, { sx: { gridArea: 'email' }, variant: "caption", children: user.email })] }) }));
24
- }, value: userId, onChange: (__, option) => onChange(option) }) }) })] }));
26
+ }, value: userIds, onChange: (__, options) => {
27
+ if (multiple) {
28
+ onChange(Array.isArray(options) ? options : [options]);
29
+ }
30
+ else {
31
+ onChange([Array.isArray(options) ? (options[0] ?? null) : options]);
32
+ }
33
+ } }) }) })] }));
25
34
  };
26
35
  export default UserList;
@@ -80,7 +80,7 @@ const Phrase = ({ value = '', variant = 'outlined', suggestions = [], lexer, sug
80
80
  onSelectCapture: _onSelectCapture,
81
81
  startAdornment: startAdornment && _jsx(InputAdornment, { position: "start", children: startAdornment }),
82
82
  endAdornment: endAdornment && _jsx(InputAdornment, { position: "end", children: endAdornment })
83
- } }), _jsx(Popper, { anchorEl: containerRef.current, style: { width: '100%', zIndex: 100 }, open: optionsOpen && (options.length > 0 || (debug && analysisRef.current?.tokens.length > 0)), disablePortal: true, children: _jsx(Paper, { elevation: 2, sx: { maxHeight: 200, overflow: 'auto', borderTopRightRadius: 0, borderTopLeftRadius: 0 }, children: _jsx(MenuList, { ref: menuRef, onKeyDown: _onMenuKeyDown, sx: {
83
+ } }), _jsx(Popper, { anchorEl: containerRef.current, style: { width: '100%', zIndex: 100 }, open: optionsOpen && (options.length > 0 || (debug && analysisRef.current?.tokens.length > 0)), disablePortal: true, children: _jsx(Paper, { elevation: 1, sx: { maxHeight: 200, overflow: 'auto', borderTopRightRadius: 0, borderTopLeftRadius: 0 }, children: _jsx(MenuList, { ref: menuRef, onKeyDown: _onMenuKeyDown, sx: {
84
84
  '&:focus': {
85
85
  outline: 'none'
86
86
  }
@@ -0,0 +1,8 @@
1
+ import type { Case } from '@cccsaurora/howler-ui/models/entities/generated/Case';
2
+ import { type FC } from 'react';
3
+ declare const CaseCard: FC<{
4
+ case?: Case;
5
+ caseId?: string;
6
+ className?: string;
7
+ }>;
8
+ export default CaseCard;