@visns-studio/visns-components 6.6.3 → 6.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +1658 -18
  2. package/package.json +10 -4
  3. package/src/components/DataGrid.jsx +619 -158
  4. package/src/components/Fetch.jsx +80 -3
  5. package/src/components/Form.jsx +9 -0
  6. package/src/components/Navigation.jsx +109 -50
  7. package/src/components/Notification.jsx +279 -9
  8. package/src/components/TableFilter.jsx +14 -1
  9. package/src/components/auth/AuthBrandPanel.jsx +43 -0
  10. package/src/components/auth/AuthLoading.jsx +78 -0
  11. package/src/components/auth/AuthShell.jsx +59 -0
  12. package/src/components/auth/ClientAuth.jsx +161 -0
  13. package/src/components/auth/ClientAuthFrame.jsx +59 -0
  14. package/src/components/auth/ClientLogin.jsx +266 -55
  15. package/src/components/auth/ClientOTPVerify.jsx +587 -115
  16. package/src/components/auth/ImpersonateGate.jsx +254 -0
  17. package/src/components/auth/Login.jsx +134 -41
  18. package/src/components/auth/LogoutScreen.jsx +112 -0
  19. package/src/components/auth/Reset.jsx +134 -77
  20. package/src/components/auth/TwoFactorAuth.jsx +475 -297
  21. package/src/components/auth/Verify.jsx +237 -126
  22. package/src/components/auth/authEndpoints.js +105 -0
  23. package/src/components/auth/authFont.js +23 -0
  24. package/src/components/auth/authHelpers.js +465 -0
  25. package/src/components/auth/clientAuthProtocols.js +240 -0
  26. package/src/components/auth/useOptionalRouter.js +49 -0
  27. package/src/components/callQueue/CallQueuePop.jsx +1502 -0
  28. package/src/components/callQueue/CallQueueSettings.jsx +508 -0
  29. package/src/components/callQueue/callQueueHelpers.js +280 -0
  30. package/src/components/callQueue/callQueueSettingsHelpers.js +75 -0
  31. package/src/components/columns/AutoGrowCell.jsx +141 -0
  32. package/src/components/columns/ColumnRenderers.jsx +53 -7
  33. package/src/components/generic/GenericAuth.jsx +163 -96
  34. package/src/components/generic/GenericDetail.jsx +215 -90
  35. package/src/components/generic/GenericIndex.jsx +20 -47
  36. package/src/components/generic/GenericMain.jsx +5 -0
  37. package/src/components/generic/GroupedReportRenderer.jsx +1 -5
  38. package/src/components/generic/StandardModal.jsx +17 -5
  39. package/src/components/generic/reportSemanticSteps/SemanticEntityStep.jsx +1 -6
  40. package/src/components/generic/reportSemanticSteps/SemanticFieldsStep.jsx +4 -11
  41. package/src/components/generic/reportSemanticSteps/SemanticFiltersStep.jsx +9 -27
  42. package/src/components/generic/reportSemanticSteps/SemanticGroupingStep.jsx +5 -13
  43. package/src/components/generic/reportSemanticSteps/SemanticParameterPrompt.jsx +5 -13
  44. package/src/components/generic/reportSemanticSteps/SemanticPreviewStep.jsx +14 -31
  45. package/src/components/generic/reportSemanticSteps/SemanticRelationsStep.jsx +3 -9
  46. package/src/components/generic/reportSemanticSteps/SemanticValueInput.jsx +2 -15
  47. package/src/components/navActive.js +60 -0
  48. package/src/components/notify/desktopNotifications.js +256 -0
  49. package/src/components/sketch/SketchField.jsx +12 -2
  50. package/src/components/sms/SmsComposeModal.jsx +495 -0
  51. package/src/components/sms/SmsInbox.jsx +596 -0
  52. package/src/components/sms/SmsInboxBadge.jsx +468 -0
  53. package/src/components/sms/SmsLineSettings.jsx +854 -0
  54. package/src/components/sms/SmsThreadPanel.jsx +1184 -0
  55. package/src/components/sms/smsEndpoints.js +56 -0
  56. package/src/components/sms/smsHelpers.js +1075 -0
  57. package/src/components/sms/smsLiveState.js +132 -0
  58. package/src/components/sms/useSmsLive.js +307 -0
  59. package/src/components/styles/CallQueuePop.module.scss +646 -0
  60. package/src/components/styles/CallQueueSettings.module.scss +460 -0
  61. package/src/components/styles/ClientAuth.module.scss +229 -0
  62. package/src/components/styles/DataGrid.module.scss +10 -0
  63. package/src/components/styles/GenericDetail.module.scss +153 -26
  64. package/src/components/styles/GenericIndex.module.scss +13 -0
  65. package/src/components/styles/GenericMain.module.scss +19 -2
  66. package/src/components/styles/ImpersonateGate.module.scss +85 -0
  67. package/src/components/styles/Login.module.scss +13 -122
  68. package/src/components/styles/LogoutScreen.module.scss +89 -0
  69. package/src/components/styles/Navigation.module.scss +509 -223
  70. package/src/components/styles/Notification.module.scss +103 -0
  71. package/src/components/styles/Reset.module.scss +52 -186
  72. package/src/components/styles/Sms.module.scss +2382 -0
  73. package/src/components/styles/TableFilter.module.scss +118 -98
  74. package/src/components/styles/TwoFactorAuth.module.scss +115 -176
  75. package/src/components/styles/Vault.module.scss +1983 -0
  76. package/src/components/styles/Verify.module.scss +57 -180
  77. package/src/components/styles/_authBrandPanel.scss +163 -0
  78. package/src/components/styles/_authShell.scss +369 -0
  79. package/src/components/styles/global-datagrid.css +29 -0
  80. package/src/components/utils/buildEnv.js +67 -0
  81. package/src/components/utils/displayValue.js +94 -0
  82. package/src/components/utils/useDensity.js +345 -9
  83. package/src/components/vault/OtpChip.jsx +208 -0
  84. package/src/components/vault/PasswordGenerator.jsx +145 -0
  85. package/src/components/vault/QrScanner.jsx +326 -0
  86. package/src/components/vault/VaultAccessLog.jsx +225 -0
  87. package/src/components/vault/VaultConfirmPanel.jsx +113 -0
  88. package/src/components/vault/VaultEntryForm.jsx +740 -0
  89. package/src/components/vault/VaultManager.jsx +1000 -0
  90. package/src/components/vault/VaultQuickSearch.jsx +681 -0
  91. package/src/components/vault/qrDecode.js +257 -0
  92. package/src/components/vault/useDebouncedValue.js +22 -0
  93. package/src/components/vault/useVaultReveal.js +160 -0
  94. package/src/components/vault/vaultClipboard.js +33 -0
  95. package/src/components/vault/vaultEndpoints.js +40 -0
  96. package/src/components/vault/vaultFit.js +116 -0
  97. package/src/components/vault/vaultHelpers.js +547 -0
  98. package/src/components/vault/vaultNavigation.jsx +58 -0
  99. package/src/components/vault/vaultOtp.js +105 -0
  100. package/src/index.js +233 -1
@@ -63,6 +63,10 @@ import {
63
63
  X as CircleX,
64
64
  } from 'lucide-react';
65
65
  import { confirmDialog } from '../utils/ConfirmDialog';
66
+ import {
67
+ isBlankValue,
68
+ overviewFieldLabel,
69
+ } from '../utils/displayValue';
66
70
  import OcrTemplateEnhanced from './OcrTemplateEnhanced';
67
71
 
68
72
  import 'react-big-calendar/lib/css/react-big-calendar.css';
@@ -269,9 +273,15 @@ function GenericDetail({
269
273
  /** Window Width State for Responsive Modal */
270
274
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);
271
275
 
272
- // Handle window resize for responsive modal
276
+ // Handle window resize for responsive modal — and for the grid height
277
+ // below, which is 65% of the window. `setWindowHeight` was never called,
278
+ // so a detail page opened on one screen and dragged to another (or a
279
+ // window resized at all) kept the grid it was born with.
273
280
  useEffect(() => {
274
- const handleResize = () => setWindowWidth(window.innerWidth);
281
+ const handleResize = () => {
282
+ setWindowWidth(window.innerWidth);
283
+ setWindowHeight(window.innerHeight);
284
+ };
275
285
  window.addEventListener('resize', handleResize);
276
286
  return () => window.removeEventListener('resize', handleResize);
277
287
  }, []);
@@ -2778,6 +2788,55 @@ function GenericDetail({
2778
2788
  <span>
2779
2789
  {section.title}
2780
2790
  </span>
2791
+ {/* Optional right-hand meta on a section heading —
2792
+ the same item shape as `content`, rendered as
2793
+ quiet "label value" pairs. It is for the one or
2794
+ two facts that describe the whole card (a count,
2795
+ a last-updated stamp), which otherwise have to
2796
+ be spent as full-width fields among the real
2797
+ ones. */}
2798
+ {Array.isArray(
2799
+ section.meta
2800
+ ) &&
2801
+ section.meta
2802
+ .length >
2803
+ 0 && (
2804
+ <span
2805
+ className={
2806
+ styles.gridtxt__meta
2807
+ }
2808
+ >
2809
+ {section.meta.map(
2810
+ (
2811
+ metaItem,
2812
+ metaKey
2813
+ ) => {
2814
+ const metaValue =
2815
+ renderValue(
2816
+ metaItem,
2817
+ data
2818
+ );
2819
+
2820
+ return (
2821
+ <span
2822
+ key={`meta-${activeTabConfig.id}-${sectionIndex}-${metaKey}`}
2823
+ >
2824
+ {metaItem.label
2825
+ ? `${overviewFieldLabel(
2826
+ metaItem.label
2827
+ )} `
2828
+ : ''}
2829
+ {isBlankValue(
2830
+ metaValue
2831
+ )
2832
+ ? '—'
2833
+ : metaValue}
2834
+ </span>
2835
+ );
2836
+ }
2837
+ )}
2838
+ </span>
2839
+ )}
2781
2840
  </div>
2782
2841
  {section.content &&
2783
2842
  section.content
@@ -2791,41 +2850,76 @@ function GenericDetail({
2791
2850
  (
2792
2851
  item,
2793
2852
  itemKey
2794
- ) => (
2795
- <li
2796
- key={`li-${item.id}-${itemKey}`}
2797
- className={
2798
- item.size ===
2799
- 'full'
2800
- ? item.type ===
2801
- 'ocrTemplate'
2802
- ? styles[
2803
- 'fw-grid-item'
2804
- ]
2805
- : `${styles['fw-grid-item']} ${styles.notecolor}`
2806
- : null
2807
- }
2808
- >
2809
- {item.type !==
2810
- 'ocrTemplate' && (
2811
- <strong>
2812
- {item.label
2813
- ? `${item.label}:`
2814
- : null}
2815
- </strong>
2816
- )}
2817
- {item.type !==
2853
+ ) => {
2854
+ const liClass =
2855
+ item.size ===
2856
+ 'full'
2857
+ ? item.type ===
2858
+ 'ocrTemplate'
2859
+ ? styles[
2860
+ 'fw-grid-item'
2861
+ ]
2862
+ : `${styles['fw-grid-item']} ${styles.notecolor}`
2863
+ : null;
2864
+
2865
+ // An embedded editor owns its whole
2866
+ // cell — no stacked label, no
2867
+ // placeholder.
2868
+ if (
2869
+ item.type ===
2818
2870
  'ocrTemplate'
2819
- ? ' '
2820
- : ''}
2821
- {item
2871
+ ) {
2872
+ return (
2873
+ <li
2874
+ key={`li-${item.id}-${itemKey}`}
2875
+ className={
2876
+ liClass
2877
+ }
2878
+ >
2879
+ {renderValue(
2880
+ item,
2881
+ data
2882
+ )}
2883
+ </li>
2884
+ );
2885
+ }
2886
+
2887
+ const rendered =
2888
+ renderValue(
2889
+ item,
2890
+ data
2891
+ );
2892
+
2893
+ const hasHref =
2894
+ item
2822
2895
  .href
2823
2896
  ?.url &&
2824
2897
  item
2825
2898
  .href
2826
2899
  ?.key &&
2827
- item.type !== 'link' &&
2828
- item.type !== 'file' ? (
2900
+ item.type !==
2901
+ 'link' &&
2902
+ item.type !==
2903
+ 'file';
2904
+
2905
+ // A field with no value used to
2906
+ // render as a label followed by
2907
+ // nothing, which reads as a broken
2908
+ // page rather than as "not set" —
2909
+ // and an href wrapped that nothing
2910
+ // in a link with no text to click.
2911
+ const body =
2912
+ isBlankValue(
2913
+ rendered
2914
+ ) ? (
2915
+ <span
2916
+ className={
2917
+ styles.overviewEmpty
2918
+ }
2919
+ >
2920
+ —
2921
+ </span>
2922
+ ) : hasHref ? (
2829
2923
  <Link
2830
2924
  to={`${
2831
2925
  item
@@ -2839,21 +2933,44 @@ function GenericDetail({
2839
2933
  ]
2840
2934
  }`}
2841
2935
  >
2842
- {renderValue(
2843
- item,
2844
- data
2845
- )}
2936
+ {
2937
+ rendered
2938
+ }
2846
2939
  </Link>
2847
2940
  ) : (
2848
- <>
2849
- {renderValue(
2850
- item,
2851
- data
2852
- )}
2853
- </>
2854
- )}
2855
- </li>
2856
- )
2941
+ rendered
2942
+ );
2943
+
2944
+ return (
2945
+ <li
2946
+ key={`li-${item.id}-${itemKey}`}
2947
+ className={
2948
+ liClass
2949
+ }
2950
+ >
2951
+ {item.label && (
2952
+ <strong
2953
+ className={
2954
+ styles.overviewLabel
2955
+ }
2956
+ >
2957
+ {overviewFieldLabel(
2958
+ item.label
2959
+ )}
2960
+ </strong>
2961
+ )}
2962
+ <span
2963
+ className={
2964
+ styles.overviewValue
2965
+ }
2966
+ >
2967
+ {
2968
+ body
2969
+ }
2970
+ </span>
2971
+ </li>
2972
+ );
2973
+ }
2857
2974
  )}
2858
2975
  </ul>
2859
2976
  )}
@@ -2869,17 +2986,14 @@ function GenericDetail({
2869
2986
  {!inPlaceEditing ? (
2870
2987
  <button
2871
2988
  className={styles.btn}
2872
- title="Edit"
2873
- aria-label="Edit"
2874
- style={{
2875
- display: 'inline-flex',
2876
- alignItems: 'center',
2877
- justifyContent:
2878
- 'center',
2879
- gap: '7px',
2880
- minWidth: '92px',
2881
- height: '42px',
2882
- }}
2989
+ title={
2990
+ activeTabConfig.form
2991
+ .editLabel || 'Edit'
2992
+ }
2993
+ aria-label={
2994
+ activeTabConfig.form
2995
+ .editLabel || 'Edit'
2996
+ }
2883
2997
  onClick={() => {
2884
2998
  if (
2885
2999
  activeTabConfig.form
@@ -2902,25 +3016,27 @@ function GenericDetail({
2902
3016
  size={18}
2903
3017
  strokeWidth={2.25}
2904
3018
  />
2905
- <span>Edit</span>
3019
+ <span>
3020
+ {activeTabConfig.form
3021
+ .editLabel ||
3022
+ 'Edit'}
3023
+ </span>
2906
3024
  </button>
2907
3025
  ) : (
2908
3026
  editingTabId ===
2909
3027
  activeTabConfig.id && (
2910
3028
  <>
3029
+ {/* Cancel is the way back out of editing, not a
3030
+ destructive act — it was painted in error red,
3031
+ the loudest thing on the page, next to the
3032
+ Save it competes with. Ghost secondary. */}
2911
3033
  <button
2912
3034
  className={
2913
- styles.btn
3035
+ styles.btnSecondary
2914
3036
  }
2915
3037
  onClick={
2916
3038
  cancelInPlaceEditing
2917
3039
  }
2918
- style={{
2919
- backgroundColor:
2920
- '#fff0f0',
2921
- color: '#d32f2f',
2922
- border: '1px solid #ffcdd2',
2923
- }}
2924
3040
  >
2925
3041
  Cancel
2926
3042
  </button>
@@ -3010,14 +3126,6 @@ function GenericDetail({
3010
3126
  {activeTabConfig.export && (
3011
3127
  <button
3012
3128
  className={styles.btn}
3013
- style={{
3014
- display: 'inline-flex',
3015
- alignItems: 'center',
3016
- justifyContent: 'center',
3017
- gap: '7px',
3018
- minWidth: '92px',
3019
- height: '42px',
3020
- }}
3021
3129
  onClick={() =>
3022
3130
  handleExport(
3023
3131
  activeTabConfig.export
@@ -3032,14 +3140,6 @@ function GenericDetail({
3032
3140
  {activeTabConfig.customAction && (
3033
3141
  <button
3034
3142
  className={styles.btn}
3035
- style={{
3036
- display: 'inline-flex',
3037
- alignItems: 'center',
3038
- justifyContent: 'center',
3039
- gap: '7px',
3040
- minWidth: '92px',
3041
- height: '42px',
3042
- }}
3043
3143
  onClick={() =>
3044
3144
  handleCustomAction(
3045
3145
  activeTabConfig.customAction
@@ -3057,14 +3157,6 @@ function GenericDetail({
3057
3157
  <button
3058
3158
  key={index}
3059
3159
  className={styles.btn}
3060
- style={{
3061
- display: 'inline-flex',
3062
- alignItems: 'center',
3063
- justifyContent: 'center',
3064
- gap: '7px',
3065
- minWidth: '92px',
3066
- height: '42px',
3067
- }}
3068
3160
  onClick={() =>
3069
3161
  handleCustomAction(customAction)
3070
3162
  }
@@ -3305,6 +3397,14 @@ function GenericDetail({
3305
3397
  <Table
3306
3398
  {...processedConfig}
3307
3399
  ref={gridRef}
3400
+ /* Deliberately a
3401
+ fraction of the window,
3402
+ not the fill-the-window
3403
+ measurement: a detail
3404
+ page stacks several
3405
+ grids down one tab and
3406
+ they cannot all own the
3407
+ viewport. */
3308
3408
  gridHeight={
3309
3409
  windowHeight * 0.65
3310
3410
  }
@@ -3854,6 +3954,17 @@ function GenericDetail({
3854
3954
  setEditingTabId(null);
3855
3955
  };
3856
3956
 
3957
+ /**
3958
+ * `page.tabLayout: "underline"` lays the tabs out as a horizontal strip
3959
+ * above the content instead of a rail down its left edge.
3960
+ *
3961
+ * The rail costs a fifth of the page width, which is worth paying when
3962
+ * there are seven tabs to keep in view and is not when there are two — a
3963
+ * two-item rail beside a wide panel reads as an empty column. Opt-in, so
3964
+ * every existing page keeps the rail it was designed against.
3965
+ */
3966
+ const tabsUnderlined = page?.tabLayout === 'underline';
3967
+
3857
3968
  return (
3858
3969
  <>
3859
3970
  <div className={styles.grid}>
@@ -4046,9 +4157,23 @@ function GenericDetail({
4046
4157
  )}
4047
4158
 
4048
4159
  <div className={styles.grid}>
4049
- <div className={styles.grid__subrow}>
4050
- <div className={styles.grid__subnav}>
4051
- <TableFilter filters={subnav} setFilters={setSubnav} />
4160
+ <div
4161
+ className={`${styles.grid__subrow} ${
4162
+ tabsUnderlined ? styles['grid__subrow--stacked'] : ''
4163
+ }`}
4164
+ >
4165
+ <div
4166
+ className={`${styles.grid__subnav} ${
4167
+ tabsUnderlined
4168
+ ? styles['grid__subnav--underline']
4169
+ : ''
4170
+ }`}
4171
+ >
4172
+ <TableFilter
4173
+ filters={subnav}
4174
+ setFilters={setSubnav}
4175
+ variant={tabsUnderlined ? 'underline' : undefined}
4176
+ />
4052
4177
  </div>
4053
4178
  <div className={styles.grid__subcontent}>
4054
4179
  {config ? renderContent() : null}
@@ -16,26 +16,10 @@ import TableFilter from '../TableFilter';
16
16
  import GenericGrid from './GenericGrid';
17
17
  import GenericReportForm from './GenericReportForm';
18
18
  import StandardModal from './StandardModal';
19
- import { useDensity, useAvailableHeight } from '../utils/useDensity';
19
+ import { useDensity } from '../utils/useDensity';
20
20
 
21
21
  import styles from '../styles/GenericIndex.module.scss';
22
22
 
23
- function useWindowDimensions() {
24
- const [windowHeight, setWindowHeight] = useState(window.innerHeight);
25
- const [windowWidth, setWindowWidth] = useState(window.innerWidth);
26
-
27
- useEffect(() => {
28
- const handleResize = () => {
29
- setWindowHeight(window.innerHeight);
30
- setWindowWidth(window.innerWidth);
31
- };
32
- window.addEventListener('resize', handleResize);
33
- return () => window.removeEventListener('resize', handleResize);
34
- }, []);
35
-
36
- return { windowHeight, windowWidth };
37
- }
38
-
39
23
  /**
40
24
  * Returns a copy of `filters` with the `show` flags adjusted so the subnav
41
25
  * item (or child) whose id matches `tabId` is active, enabling deep-links
@@ -301,24 +285,21 @@ function GenericIndex({
301
285
  const [customActionModalShow, setCustomActionModalShow] = useState(false);
302
286
  const [customActionFormData, setCustomActionFormData] = useState({});
303
287
  const [customActionConfig, setCustomActionConfig] = useState(null);
304
- const { windowHeight, windowWidth } = useWindowDimensions();
305
-
306
- // Grid height. The long-standing behaviour is a flat 80% of the viewport,
307
- // which leaves a dead band below the table; at large density the screen is
308
- // the point, so measure what is actually left below the grid instead.
309
- // `useAvailableHeight` returns null until it can measure (and always when
310
- // disabled), so the 80% rule stays the fallback and remains exactly what
311
- // every non-density app gets.
288
+ // Grid height is NOT set here any more.
289
+ //
290
+ // This used to measure the wrapper below and hand the result to <Table> as
291
+ // `gridHeight`. The wrapper contains the whole Table — the search/create
292
+ // bar and the colour legend as well as the grid — but `gridHeight` is
293
+ // applied to the datagrid alone, so every index page asked for the room
294
+ // below the search bar *and* rendered the search bar on top of it. The
295
+ // grid overflowed the window by the height of its own toolbar, the page
296
+ // grew a scrollbar and the pager sat below the fold (reported on /clients).
297
+ //
298
+ // The datagrid measures itself from `.dataGridContainer`, which starts
299
+ // below that toolbar, so leaving `gridHeight` unset is both correct and one
300
+ // code path instead of two. Pages that genuinely want a fixed height (a
301
+ // grid inside a modal, a secondary grid on a detail page) still pass one.
312
302
  const { isLarge } = useDensity();
313
- const gridWrapperRef = useRef(null);
314
- const measuredGridHeight = useAvailableHeight(gridWrapperRef, {
315
- enabled: isLarge,
316
- recomputeKey: windowHeight,
317
- });
318
- const gridHeight =
319
- isLarge && measuredGridHeight != null
320
- ? measuredGridHeight
321
- : windowHeight * 0.8;
322
303
 
323
304
  // Toggle a body-level "tablet-mode" class when this view opts in via
324
305
  // setting.tabletMode. Lets consumer projects style the whole UI
@@ -686,7 +667,6 @@ function GenericIndex({
686
667
  {...configWithStyle}
687
668
  ref={gridRef}
688
669
  hoverColor={configWithStyle.style?.hoverColor}
689
- gridHeight={gridHeight}
690
670
  setConfig={setConfig}
691
671
  setTableData={setTableData}
692
672
  setTotal={setTotal}
@@ -696,17 +676,11 @@ function GenericIndex({
696
676
  />
697
677
  );
698
678
 
699
- // The measuring wrapper only exists in large density — every other
700
- // app renders the exact tree it always has.
701
- return isLarge ? (
702
- <div
703
- ref={gridWrapperRef}
704
- style={{ flex: '1 1 auto', minHeight: 0 }}
705
- >
706
- {table}
707
- </div>
708
- ) : (
709
- table
679
+ // A plain flex cell. It no longer measures anything — the grid
680
+ // measures itself — but it still has to be allowed to shrink, or
681
+ // the flex parent floors it at its content height.
682
+ return (
683
+ <div style={{ flex: '1 1 auto', minHeight: 0 }}>{table}</div>
710
684
  );
711
685
  };
712
686
 
@@ -724,7 +698,6 @@ function GenericIndex({
724
698
  }, [
725
699
  config,
726
700
  subnav,
727
- gridHeight,
728
701
  isLarge,
729
702
  userProfile,
730
703
  setConfig,
@@ -35,6 +35,9 @@ const GenericMain = ({
35
35
  // attribute that actually drives the CSS is written by GenericAuth, and
36
36
  // nothing in here reads the value yet.
37
37
  density = 'default',
38
+ // Passed straight to Navigation: per-id renderers for account actions.
39
+ // See the `renderers` note in Navigation.jsx.
40
+ navRenderers = {},
38
41
  }) => {
39
42
  const navigate = useNavigate();
40
43
  const location = useLocation();
@@ -147,6 +150,7 @@ const GenericMain = ({
147
150
  setSystemAuth={setSystemAuth}
148
151
  themeType={themeType}
149
152
  userProfile={userProfile}
153
+ renderers={navRenderers}
150
154
  />
151
155
  </header>
152
156
  {navigation.hasOwnProperty('quickActions') && (
@@ -177,6 +181,7 @@ const GenericMain = ({
177
181
  setSystemAuth={setSystemAuth}
178
182
  themeType={themeType}
179
183
  userProfile={userProfile}
184
+ renderers={navRenderers}
180
185
  >
181
186
  <div className={styles['content-main']} id="app">
182
187
  <div className={styles.paddingSimple}>
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import SectionGroupedReport from './SectionGroupedReport';
4
4
  import './groupedReport.css';
5
5
 
6
- const GroupedReportRenderer = ({ data, config, columns, onExport }) => {
6
+ const GroupedReportRenderer = ({ data, config, columns, onExport = null }) => {
7
7
  const renderByType = () => {
8
8
  switch (config?.type) {
9
9
  case 'sections':
@@ -81,8 +81,4 @@ GroupedReportRenderer.propTypes = {
81
81
  onExport: PropTypes.func
82
82
  };
83
83
 
84
- GroupedReportRenderer.defaultProps = {
85
- onExport: null
86
- };
87
-
88
84
  export default GroupedReportRenderer;
@@ -138,10 +138,19 @@ const StandardModal = ({
138
138
  borderRadius: '8px',
139
139
  boxShadow: '0 10px 30px rgba(0, 0, 0, 0.3)',
140
140
  maxHeight: isMobile || isTablet ? '85vh' : '90vh',
141
- overflowY: 'auto',
142
- ...customStyles.modal
141
+ overflowY: 'auto'
143
142
  };
144
143
 
144
+ /*
145
+ * customStyles.modal is spread LAST, after the size preset, because a
146
+ * caller that bothers to pass one is overriding the default rather
147
+ * than asking to be overridden by it. Spread before the preset (as it
148
+ * once was) the width/minWidth/maxWidth a size sets could never be
149
+ * touched - which is what the SMS compose modal needs: a form-shaped
150
+ * width, and a visible overflow so its templates menu can open past
151
+ * the modal's own edge.
152
+ */
153
+
145
154
  // Apply size-based styles with device considerations
146
155
  switch (size) {
147
156
  case 'small':
@@ -149,14 +158,16 @@ const StandardModal = ({
149
158
  ...baseStyle,
150
159
  width: isMobile ? '95vw' : '400px',
151
160
  minWidth: isMobile ? '300px' : '400px',
152
- maxWidth: isMobile ? '95vw' : '600px'
161
+ maxWidth: isMobile ? '95vw' : '600px',
162
+ ...customStyles.modal
153
163
  };
154
164
  case 'large':
155
165
  return {
156
166
  ...baseStyle,
157
167
  width: '95vw',
158
168
  minWidth: isMobile ? '300px' : '800px',
159
- maxWidth: isMobile ? '95vw' : '1600px'
169
+ maxWidth: isMobile ? '95vw' : '1600px',
170
+ ...customStyles.modal
160
171
  };
161
172
  case 'medium':
162
173
  default:
@@ -164,7 +175,8 @@ const StandardModal = ({
164
175
  ...baseStyle,
165
176
  width: isMobile ? '95vw' : '80vw',
166
177
  minWidth: isMobile ? '300px' : '600px',
167
- maxWidth: isMobile ? '95vw' : '1200px'
178
+ maxWidth: isMobile ? '95vw' : '1200px',
179
+ ...customStyles.modal
168
180
  };
169
181
  }
170
182
  };
@@ -10,7 +10,7 @@ import styles from '../../styles/ReportSemantic.module.scss';
10
10
  * Only business labels and descriptions are shown — the entity id is never
11
11
  * rendered, because it is an opaque server handle.
12
12
  */
13
- const SemanticEntityStep = ({ model, selectedEntity, onSelect }) => {
13
+ const SemanticEntityStep = ({ model = null, selectedEntity = '', onSelect }) => {
14
14
  const entities = listEntities(model);
15
15
 
16
16
  if (entities.length === 0) {
@@ -73,9 +73,4 @@ SemanticEntityStep.propTypes = {
73
73
  onSelect: PropTypes.func.isRequired,
74
74
  };
75
75
 
76
- SemanticEntityStep.defaultProps = {
77
- model: null,
78
- selectedEntity: '',
79
- };
80
-
81
76
  export default SemanticEntityStep;