@visns-studio/visns-components 6.17.2 → 6.20.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 (83) hide show
  1. package/package.json +10 -7
  2. package/src/components/AssociationManager.jsx +344 -177
  3. package/src/components/AsyncSelect.jsx +2 -2
  4. package/src/components/Autocomplete.jsx +57 -12
  5. package/src/components/Breadcrumb.jsx +23 -4
  6. package/src/components/ClientAssociationManager.jsx +321 -145
  7. package/src/components/DataGrid.jsx +625 -20
  8. package/src/components/DropZone.jsx +4 -1
  9. package/src/components/Form.jsx +233 -6
  10. package/src/components/MultiSelect.jsx +2 -2
  11. package/src/components/Navigation.jsx +39 -23
  12. package/src/components/TableFilter.jsx +172 -12
  13. package/src/components/UniversalSearch.jsx +390 -0
  14. package/src/components/auth/Login.jsx +164 -8
  15. package/src/components/auth/Profile.jsx +209 -22
  16. package/src/components/auth/authEndpoints.js +4 -0
  17. package/src/components/auth/webauthn.js +194 -0
  18. package/src/components/columns/ColumnRenderers.jsx +6 -1
  19. package/src/components/controls/DataGridSearch.jsx +11 -5
  20. package/src/components/generic/GenericAuth.jsx +6 -0
  21. package/src/components/generic/GenericDashboard.jsx +93 -36
  22. package/src/components/generic/GenericDetail.jsx +146 -61
  23. package/src/components/generic/GenericIndex.jsx +134 -38
  24. package/src/components/generic/GenericSettings.jsx +220 -0
  25. package/src/components/generic/StandardModal.jsx +165 -8
  26. package/src/components/generic/dashboardChartDefaults.js +230 -0
  27. package/src/components/generic/shared/indexLayout.js +38 -0
  28. package/src/components/settings/IntegrationsManager.jsx +474 -0
  29. package/src/components/sms/SmsLineSettings.jsx +16 -12
  30. package/src/components/styles/AssociationManager.module.scss +4 -363
  31. package/src/components/styles/AuditLog.module.scss +6 -6
  32. package/src/components/styles/AuditLogs.module.scss +3 -3
  33. package/src/components/styles/Autocomplete.module.scss +7 -7
  34. package/src/components/styles/BusinessCardOcr.module.scss +6 -1
  35. package/src/components/styles/CallQueueSettings.module.scss +4 -1
  36. package/src/components/styles/CameraPlacement.module.scss +19 -19
  37. package/src/components/styles/CategorizedDropZone.module.scss +6 -1
  38. package/src/components/styles/ClientAssociationManager.module.scss +5 -289
  39. package/src/components/styles/ClientAuth.module.scss +1 -1
  40. package/src/components/styles/DataGrid.module.scss +455 -8
  41. package/src/components/styles/Field.module.scss +32 -24
  42. package/src/components/styles/Form.module.scss +123 -3
  43. package/src/components/styles/GenericDashboard.module.scss +612 -825
  44. package/src/components/styles/GenericDetail.module.scss +406 -61
  45. package/src/components/styles/GenericDynamic.module.scss +25 -25
  46. package/src/components/styles/GenericEditableTable.module.scss +33 -33
  47. package/src/components/styles/GenericFormBuilder.module.scss +119 -119
  48. package/src/components/styles/GenericIndex.module.scss +483 -38
  49. package/src/components/styles/GenericMain.module.scss +6 -3
  50. package/src/components/styles/GenericQuote.module.scss +399 -81
  51. package/src/components/styles/GenericReport.module.scss +8 -8
  52. package/src/components/styles/GenericSettings.module.scss +209 -0
  53. package/src/components/styles/GenericSort.module.scss +9 -9
  54. package/src/components/styles/Integrations.module.scss +321 -0
  55. package/src/components/styles/List.module.scss +4 -4
  56. package/src/components/styles/Login.module.scss +57 -0
  57. package/src/components/styles/MultiSelect.module.scss +4 -4
  58. package/src/components/styles/Navigation.module.scss +12 -12
  59. package/src/components/styles/Notification.module.scss +12 -8
  60. package/src/components/styles/NotificationList.module.scss +5 -5
  61. package/src/components/styles/Profile.module.scss +5 -5
  62. package/src/components/styles/QrCode.module.scss +4 -4
  63. package/src/components/styles/QuickAction.module.scss +20 -20
  64. package/src/components/styles/SortableQuoteItems.module.scss +4 -4
  65. package/src/components/styles/TableFilter.module.scss +128 -17
  66. package/src/components/styles/TicketConversation.module.scss +497 -0
  67. package/src/components/styles/UniversalSearch.module.scss +229 -0
  68. package/src/components/styles/Vault.module.scss +413 -0
  69. package/src/components/styles/_associationManager.scss +467 -0
  70. package/src/components/styles/_authBrandPanel.scss +15 -9
  71. package/src/components/styles/_controls.scss +43 -39
  72. package/src/components/styles/global-datagrid.css +196 -0
  73. package/src/components/styles/global.css +168 -17
  74. package/src/components/tickets/TicketConversation.jsx +455 -0
  75. package/src/components/utils/rowActionSettings.js +117 -0
  76. package/src/components/vault/VaultClientPicker.jsx +183 -0
  77. package/src/components/vault/VaultEntryForm.jsx +23 -0
  78. package/src/components/vault/VaultManager.jsx +382 -34
  79. package/src/components/vault/VaultShareModal.jsx +454 -0
  80. package/src/components/vault/vaultEndpoints.js +16 -2
  81. package/src/components/vault/vaultShares.js +247 -0
  82. package/src/index.js +35 -0
  83. package/src/utils/fetchUtil.js +38 -14
@@ -109,7 +109,7 @@
109
109
 
110
110
  #smartTableSearchInput {
111
111
  background: #eeeeee;
112
- border: 1px solid rgba(var(--table-color-rgb), 1.1);
112
+ border: 1px solid rgba(var(--table-color-rgb, 88, 214, 188), 1);
113
113
  border-radius: var(--br);
114
114
  color: var(--paragraph-color);
115
115
  padding: 10px 10px 10px 35px;
@@ -190,7 +190,7 @@
190
190
 
191
191
  .modal__header {
192
192
  width: 100%;
193
- border-bottom: 1px solid rgba(var(--primary-rgb), 0.1);
193
+ border-bottom: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.1);
194
194
  display: flex;
195
195
  align-items: center;
196
196
  justify-content: space-between;
@@ -228,7 +228,7 @@
228
228
  min-width: 2rem;
229
229
  min-height: 2rem;
230
230
  box-sizing: border-box;
231
- background: rgba(var(--paragraph-rgb), 0.05);
231
+ background: rgba(var(--paragraph-rgb, 43, 43, 43), 0.05);
232
232
  cursor: pointer;
233
233
  padding: 0.25rem;
234
234
  margin: 0;
@@ -242,7 +242,7 @@
242
242
  transition: all 0.2s ease;
243
243
 
244
244
  &:hover {
245
- background: rgba(var(--paragraph-rgb), 0.1);
245
+ background: rgba(var(--paragraph-rgb, 43, 43, 43), 0.1);
246
246
  }
247
247
 
248
248
  svg {
@@ -286,7 +286,7 @@
286
286
  margin-bottom: 12px;
287
287
  padding: 10px 12px;
288
288
  background-color: #f0f7ff;
289
- border: 1px solid rgba(var(--primary-rgb), 0.2);
289
+ border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.2);
290
290
  border-radius: 6px;
291
291
  color: var(--secondary-color);
292
292
  font-size: 0.9rem;
@@ -441,7 +441,7 @@
441
441
  padding: 1.5rem;
442
442
  margin: 1rem auto;
443
443
  background: var(--tertiary-color);
444
- border: 1px solid rgba(var(--primary-rgb), 0.15);
444
+ border: 1px solid rgba(var(--primary-rgb, 27, 57, 51), 0.15);
445
445
  border-radius: var(--br);
446
446
  max-width: 90%;
447
447
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
@@ -810,7 +810,7 @@
810
810
  padding: 0 20px;
811
811
  margin-left: 0;
812
812
  font-size: 0.9rem;
813
- border: 1px solid rgba(var(--primary-rgb, 59, 130, 246), 1.1);
813
+ border: 1px solid rgba(var(--primary-rgb, 59, 130, 246), 1);
814
814
  box-sizing: border-box;
815
815
  background-color: var(--primary-color, #3b82f6);
816
816
  color: var(--tertiary-color, white);
@@ -820,7 +820,7 @@
820
820
  &:hover {
821
821
  color: var(--tertiary-color, white);
822
822
  background-color: var(--secondary-color, #2563eb);
823
- border: 1px solid rgba(var(--secondary-rgb, 37, 99, 235), 1.05);
823
+ border: 1px solid rgba(var(--secondary-rgb, 37, 99, 235), 1);
824
824
  }
825
825
 
826
826
  &:focus,
@@ -1695,3 +1695,450 @@
1695
1695
  text-overflow: ellipsis;
1696
1696
  white-space: nowrap;
1697
1697
  }
1698
+
1699
+ // ============================================================================
1700
+ // 2026 pass — the grid's own chrome
1701
+ // ----------------------------------------------------------------------------
1702
+ // Appended rather than folded into the rules above, deliberately. The grid
1703
+ // itself is the vendored @visns-studio/visns-datagrid-* fork and its cell
1704
+ // markup is not ours to restyle; what IS ours is the frame around it — the
1705
+ // toolbar, the container, the action cell and the pagination bar — and that is
1706
+ // where the dated look actually lives: square corners, an edge-to-edge slab,
1707
+ // a search field with no affordance and a primary button welded to its edge.
1708
+ //
1709
+ // Everything here is a targeted override of a rule above, so the cascade order
1710
+ // is doing real work. Tokens carry fallbacks so a host that declares none
1711
+ // still renders.
1712
+ // ============================================================================
1713
+
1714
+ .dataGridContainer {
1715
+ --dg-surface: var(--background-color, #fff);
1716
+ --dg-ink: var(--paragraph-color, #2b2b2b);
1717
+ // Defers to the app's hairline token first, so the grid's edge is the same
1718
+ // line as every card around it rather than a second opinion 12% of the way
1719
+ // to the ink colour. The local expression stays as the fallback.
1720
+ --dg-line: var(
1721
+ --border-color,
1722
+ color-mix(in srgb, var(--dg-ink) 12%, transparent)
1723
+ );
1724
+ --dg-muted: color-mix(in srgb, var(--dg-ink) 62%, transparent);
1725
+ --dg-quiet: color-mix(in srgb, var(--dg-ink) 3%, var(--dg-surface));
1726
+ --dg-hover: color-mix(in srgb, var(--primary-color, #1b3933) 5%, var(--dg-surface));
1727
+ // 14px was the grid's own idea of a card corner while every other card on
1728
+ // the page used `--br` (5px). `--dg-card-radius` is the hook an app points
1729
+ // at its one card radius.
1730
+ --dg-radius: var(--dg-card-radius, var(--card-radius, 14px));
1731
+ --dg-motion: var(--speed, 160ms) var(--ease, cubic-bezier(0.4, 0, 0.2, 1));
1732
+
1733
+ border: 1px solid var(--dg-line);
1734
+ border-radius: var(--dg-radius);
1735
+ background: var(--dg-surface);
1736
+ // One contact shadow plus one ambient, as on the dashboard card — a single
1737
+ // large blur reads as a smudge. Deferred to `--shadow-sm` so there is one
1738
+ // elevation in the app; this pair is the fallback.
1739
+ box-shadow: var(
1740
+ --shadow-sm,
1741
+ 0 1px 2px color-mix(in srgb, var(--primary-color, #1b3933) 8%, transparent),
1742
+ 0 8px 24px -12px color-mix(in srgb, var(--primary-color, #1b3933) 18%, transparent)
1743
+ );
1744
+ overflow: hidden;
1745
+ }
1746
+
1747
+ // -- toolbar -----------------------------------------------------------------
1748
+
1749
+ .dataGridTopContainer {
1750
+ /* The `--dg-*` tokens are declared on `.dataGridContainer`, and the
1751
+ toolbar is NOT inside it — measured: `container.contains(searchInput)`
1752
+ is false. So every `var(--dg-line)` in here resolved to nothing, and a
1753
+ `border: 1px solid var(--dg-line)` with no fallback is invalid at
1754
+ computed-value time, which discards THE WHOLE DECLARATION. The result
1755
+ was a search field with no border and no background: an invisible box
1756
+ with a magnifier floating beside it.
1757
+
1758
+ This is the trap this file's own header warns about, so the tokens are
1759
+ redeclared here rather than hoped for, and the uses below carry literal
1760
+ fallbacks as well. */
1761
+ --dg-surface: var(--background-color, #fff);
1762
+ --dg-ink: var(--paragraph-color, #2b2b2b);
1763
+ --dg-line: var(
1764
+ --border-color,
1765
+ color-mix(in srgb, var(--dg-ink) 12%, transparent)
1766
+ );
1767
+ --dg-muted: color-mix(in srgb, var(--dg-ink) 62%, transparent);
1768
+ --dg-quiet: color-mix(in srgb, var(--dg-ink) 3%, var(--dg-surface));
1769
+ --dg-motion: var(--speed, 160ms) var(--ease, cubic-bezier(0.4, 0, 0.2, 1));
1770
+
1771
+ /* ONE source of truth for control height inside the toolbar.
1772
+
1773
+ The search field hardcoded 34px while every button resolved
1774
+ `--control-height`, whose default is 2.5rem — so the field was 34px, the
1775
+ Add New button beside it was 40px, and the button read as oversized
1776
+ because it was. Scoped here rather than changed globally: retuning
1777
+ `--control-height` for the whole library would resize every button in
1778
+ every view to fix one toolbar. */
1779
+ --control-height: var(--dg-control-h, 34px);
1780
+
1781
+ display: flex;
1782
+ flex-wrap: wrap;
1783
+ align-items: center;
1784
+ justify-content: space-between;
1785
+ gap: 0.75rem;
1786
+ padding: 0.75rem;
1787
+ border-bottom: 1px solid var(--dg-line);
1788
+ background: var(--dg-surface);
1789
+ }
1790
+
1791
+ .dataGridTopLeftContainer,
1792
+ .dataGridTopRightContainer {
1793
+ display: flex;
1794
+ flex-wrap: wrap;
1795
+ align-items: center;
1796
+ gap: 0.5rem;
1797
+ min-width: 0;
1798
+ }
1799
+
1800
+ .dataGridTopLeftContainer { flex: 1 1 18rem; }
1801
+
1802
+ // The search field was a full-bleed strip with no border and no radius, which
1803
+ // read as part of the table rather than as a control.
1804
+ .searchContainer,
1805
+ .searchInputWrapper {
1806
+ position: relative;
1807
+ display: flex;
1808
+ align-items: center;
1809
+ flex: 1 1 auto;
1810
+ min-width: 0;
1811
+ max-width: 32rem;
1812
+ }
1813
+
1814
+ /* The field and the primary action were flush against each other inside a
1815
+ white, shadowed container, which read as ONE welded pill — a text box with a
1816
+ dark blob fused to its right edge — rather than as a field and a button.
1817
+ They are two different controls doing two different things, so they are
1818
+ separated and the container stops drawing a card around them. */
1819
+ .searchContainer {
1820
+ gap: 0.5rem;
1821
+ background: none;
1822
+ box-shadow: none;
1823
+ border: none;
1824
+ padding: 0;
1825
+ }
1826
+
1827
+ /* The focus ring belongs to the FIELD, not to the pair.
1828
+ *
1829
+ * The pre-2026 rule put `box-shadow: 0 0 0 2px` on `.searchContainer:focus-within`,
1830
+ * and that container wraps the search field AND the Add New button. Once the two
1831
+ * were separated by a gap, focusing the field drew a ring around both of them —
1832
+ * a stray line bridging the space between the input and the button, visible only
1833
+ * while focused. `:focus-within` outspecifies the plain `.searchContainer` above,
1834
+ * so it has to be turned off at matching specificity rather than by resetting the
1835
+ * container.
1836
+ *
1837
+ * `.searchInput:focus` already carries its own ring, which is the correct target:
1838
+ * the thing that has focus is the thing that should show it.
1839
+ */
1840
+ .searchContainer:focus-within {
1841
+ box-shadow: none;
1842
+ border-color: transparent;
1843
+ }
1844
+
1845
+ /* The toolbar's action buttons.
1846
+ *
1847
+ * These rules predate the 2026 pass and outspecified it: `.actionButtons >
1848
+ * .btn` (0,2,0) hardcoded `height: 38px` and `border-radius: 0`, which beat
1849
+ * `button-base`'s `min-height: var(--control-height)` and
1850
+ * `border-radius: var(--radius-sm)` no matter what those tokens said. That is
1851
+ * why the Add New button sat 4px taller than the field beside it with square
1852
+ * corners, and why it read as oversized: it WAS oversized.
1853
+ *
1854
+ * `gap: 0` is the other half of the welded look — two adjacent actions with no
1855
+ * space between them read as one wide control split by a hairline.
1856
+ */
1857
+ .actionButtons,
1858
+ .actionButtonsStandalone {
1859
+ /* Was a separate `--grid-toolbar-height` at 38px, a second source of truth
1860
+ for a height the rest of the toolbar already had a token for. */
1861
+ height: var(--control-height, 34px);
1862
+ gap: 0.5rem;
1863
+
1864
+ > .btn {
1865
+ height: var(--control-height, 34px);
1866
+ /* The same radius token the search field beside it uses. `--radius-sm`
1867
+ resolved to 4px here where the field's `--btn-br` gave 5px, and a
1868
+ 1px difference between two controls on one row is the kind of thing
1869
+ you cannot name but can see. */
1870
+ border-radius: var(--btn-br, var(--radius-sm, 8px));
1871
+ /* 0.9rem here against 0.8125rem on every other control in the row. */
1872
+ font-size: var(--font-size-sm, 0.8125rem);
1873
+ padding: 0 var(--spacing-md, 1rem);
1874
+ }
1875
+ }
1876
+
1877
+ .searchInput,
1878
+ .filterInput {
1879
+ width: 100%;
1880
+ /* Same token the buttons beside it resolve — see .dataGridTopContainer. */
1881
+ height: var(--control-height, 34px);
1882
+ padding: 0 0.75rem 0 2rem;
1883
+ /* Literal fallbacks, not decoration: this field renders OUTSIDE
1884
+ `.dataGridContainer`, so before the token block was added to
1885
+ `.dataGridTopContainer` above, each of these three was a bare `var()`
1886
+ resolving to nothing — and one invalid var() discards its whole
1887
+ declaration, which is how the field lost its border AND its background
1888
+ at once. */
1889
+ border: 1px solid var(--dg-line, rgba(43, 43, 43, 0.14));
1890
+ border-radius: var(--btn-br, 8px);
1891
+ background: var(--dg-quiet, #fafafa);
1892
+ color: var(--dg-ink, #2b2b2b);
1893
+ font: inherit;
1894
+ font-size: 0.8125rem;
1895
+ transition: border-color var(--dg-motion), background var(--dg-motion);
1896
+ }
1897
+
1898
+ .searchInput::placeholder,
1899
+ .filterInput::placeholder { color: var(--dg-muted, #6b7280); }
1900
+
1901
+ /* The focus ring. Literal fallbacks so an undeclared token cannot discard the
1902
+ whole declaration — this field renders outside `.dataGridContainer`, which
1903
+ is where the `--dg-*` tokens are declared. */
1904
+ .searchInput:focus,
1905
+ .filterInput:focus {
1906
+ outline: none;
1907
+ background: var(--dg-surface, #fff);
1908
+ border-color: color-mix(
1909
+ in srgb,
1910
+ var(--primary-color, #1b3933) 45%,
1911
+ var(--dg-line, rgba(43, 43, 43, 0.14))
1912
+ );
1913
+ box-shadow: 0 0 0 3px
1914
+ color-mix(
1915
+ in srgb,
1916
+ var(--highlight-color, #3cbf7d) 22%,
1917
+ transparent
1918
+ );
1919
+ }
1920
+
1921
+ .icon-search {
1922
+ position: absolute;
1923
+ /* Paired with the input's `padding-left` above: the glyph sits in the
1924
+ indent the field leaves for it. Change one and change the other. */
1925
+ left: 0.6rem;
1926
+ display: flex;
1927
+ align-items: center;
1928
+ color: var(--dg-muted, #6b7280);
1929
+ pointer-events: none;
1930
+ }
1931
+
1932
+ // -- action cell -------------------------------------------------------------
1933
+ // The per-row actions were bare 12px glyphs with no hit area — under the 24px
1934
+ // minimum a pointer target wants, and invisible until you knew to look.
1935
+
1936
+ .tdactions {
1937
+ display: flex;
1938
+ align-items: center;
1939
+ justify-content: flex-end;
1940
+ gap: 0.15rem;
1941
+ }
1942
+
1943
+ .tdaction {
1944
+ display: inline-flex;
1945
+ align-items: center;
1946
+ justify-content: center;
1947
+ width: 28px;
1948
+ height: 28px;
1949
+ padding: 0;
1950
+ border: 0;
1951
+ border-radius: var(--radius-sm, 6px);
1952
+ background: none;
1953
+ color: var(--dg-muted);
1954
+ line-height: 1;
1955
+ cursor: pointer;
1956
+ transition: background var(--dg-motion), color var(--dg-motion);
1957
+ }
1958
+
1959
+ .tdaction:hover {
1960
+ background: var(--dg-hover);
1961
+ color: var(--primary-color, #1b3933);
1962
+ }
1963
+
1964
+ .tdaction:focus-visible {
1965
+ outline: 2px solid var(--highlight-color, #3cbf7d);
1966
+ outline-offset: 1px;
1967
+ }
1968
+
1969
+ /* The wrapper around one action icon. Carries no look of its own — it exists
1970
+ so the quick-assign menu's outside-click dismissal can recognise the
1971
+ trigger — but it must not add a line box that pushes the icon off centre. */
1972
+ .rowActionTrigger {
1973
+ display: inline-flex;
1974
+ align-items: center;
1975
+ }
1976
+
1977
+ /* -- quick-assign menu -------------------------------------------------------
1978
+ Rendered at the root of the component and positioned `fixed` from the
1979
+ trigger's rect, because the grid's cells clip their overflow. Literal
1980
+ fallbacks throughout: it renders outside `.dataGridContainer`, where the
1981
+ `--dg-*` tokens are declared, so an unresolved token would discard the whole
1982
+ declaration and leave a transparent, borderless panel. */
1983
+ .rowActionPicker {
1984
+ display: flex;
1985
+ flex-direction: column;
1986
+ overflow: hidden;
1987
+ border: 1px solid var(--dg-line, rgba(43, 43, 43, 0.14));
1988
+ border-radius: var(--radius-md, 8px);
1989
+ background: var(--dg-surface, #fff);
1990
+ box-shadow: 0 10px 28px rgba(0, 0, 0, 0.16);
1991
+ font-size: 0.8125rem;
1992
+ text-align: left;
1993
+ }
1994
+
1995
+ .rowActionPickerHeader {
1996
+ display: flex;
1997
+ align-items: center;
1998
+ gap: 0.4rem;
1999
+ padding: 0.45rem 0.5rem;
2000
+ border-bottom: 1px solid var(--dg-line, rgba(43, 43, 43, 0.14));
2001
+ background: var(--dg-quiet, #fafafa);
2002
+ color: var(--dg-muted, #6b7280);
2003
+ }
2004
+
2005
+ /* This field IS the header strip, not a box inside it — a bordered input
2006
+ inside a bordered panel reads as two menus. global.css's blanket input rule
2007
+ would give it a border, a 1rem padding and a white ground, and that rule
2008
+ sits at roughly (0,13,1) so no sane selector here outranks it. It carries an
2009
+ opt-out list for exactly this, matched on a class-name SUBSTRING, and
2010
+ `rowActionPickerSearch` is now on it — see the long note there. */
2011
+ .rowActionPickerSearch {
2012
+ flex: 1;
2013
+ min-width: 0;
2014
+ height: auto;
2015
+ margin: 0;
2016
+ padding: 0;
2017
+ border: 0;
2018
+ background: none;
2019
+ font: inherit;
2020
+ font-size: 0.8125rem;
2021
+ color: var(--dg-ink, #2b2b2b);
2022
+ }
2023
+
2024
+ .rowActionPickerSearch:focus {
2025
+ outline: none;
2026
+ }
2027
+
2028
+ .rowActionPickerSearch::placeholder {
2029
+ color: var(--dg-muted, #6b7280);
2030
+ }
2031
+
2032
+ .rowActionPickerClose {
2033
+ display: inline-flex;
2034
+ align-items: center;
2035
+ justify-content: center;
2036
+ width: 20px;
2037
+ height: 20px;
2038
+ padding: 0;
2039
+ border: 0;
2040
+ border-radius: var(--radius-sm, 6px);
2041
+ background: none;
2042
+ color: var(--dg-muted, #6b7280);
2043
+ cursor: pointer;
2044
+ }
2045
+
2046
+ .rowActionPickerClose:hover {
2047
+ background: var(--dg-hover, rgba(27, 57, 51, 0.05));
2048
+ color: var(--primary-color, #1b3933);
2049
+ }
2050
+
2051
+ .rowActionPickerList {
2052
+ flex: 1;
2053
+ overflow-y: auto;
2054
+ padding: 0.25rem;
2055
+ }
2056
+
2057
+ .rowActionPickerOption {
2058
+ display: block;
2059
+ width: 100%;
2060
+ padding: 0.4rem 0.5rem;
2061
+ border: 0;
2062
+ border-radius: var(--radius-sm, 6px);
2063
+ background: none;
2064
+ color: var(--dg-ink, #2b2b2b);
2065
+ font: inherit;
2066
+ text-align: left;
2067
+ /* Long names truncate rather than wrap: the menu is a fixed width and a
2068
+ two-line row makes the list jump as you type. */
2069
+ white-space: nowrap;
2070
+ overflow: hidden;
2071
+ text-overflow: ellipsis;
2072
+ cursor: pointer;
2073
+ }
2074
+
2075
+ .rowActionPickerOption:hover,
2076
+ .rowActionPickerOption:focus-visible {
2077
+ outline: none;
2078
+ background: var(--dg-hover, rgba(27, 57, 51, 0.05));
2079
+ color: var(--primary-color, #1b3933);
2080
+ }
2081
+
2082
+ .rowActionPickerClear {
2083
+ color: var(--dg-muted, #6b7280);
2084
+ font-style: italic;
2085
+ }
2086
+
2087
+ .rowActionPickerEmpty {
2088
+ padding: 0.5rem;
2089
+ color: var(--dg-muted, #6b7280);
2090
+ text-align: center;
2091
+ }
2092
+
2093
+ // -- pagination --------------------------------------------------------------
2094
+
2095
+ .dataGridBottomContainer {
2096
+ display: flex;
2097
+ flex-wrap: wrap;
2098
+ align-items: center;
2099
+ gap: 0.75rem;
2100
+ padding: 0.6rem 0.75rem;
2101
+ border-top: 1px solid var(--dg-line);
2102
+ background: var(--dg-quiet);
2103
+ color: var(--dg-muted);
2104
+ font-size: 0.75rem;
2105
+ // A row of counts and page numbers: tabular so the digits stop jittering
2106
+ // as you page through.
2107
+ font-variant-numeric: tabular-nums;
2108
+ }
2109
+
2110
+ .dataGridBottomContainer button {
2111
+ display: inline-flex;
2112
+ align-items: center;
2113
+ justify-content: center;
2114
+ min-width: 28px;
2115
+ height: 28px;
2116
+ border: 0;
2117
+ border-radius: var(--radius-sm, 6px);
2118
+ background: none;
2119
+ color: var(--dg-muted);
2120
+ cursor: pointer;
2121
+ transition: background var(--dg-motion), color var(--dg-motion);
2122
+ }
2123
+
2124
+ .dataGridBottomContainer button:hover:not(:disabled) {
2125
+ background: var(--dg-hover);
2126
+ color: var(--primary-color, #1b3933);
2127
+ }
2128
+
2129
+ .dataGridBottomContainer button:disabled {
2130
+ opacity: 0.4;
2131
+ cursor: default;
2132
+ }
2133
+
2134
+ // -- empty state -------------------------------------------------------------
2135
+
2136
+ .noDataMessage {
2137
+ display: flex;
2138
+ align-items: center;
2139
+ justify-content: center;
2140
+ min-height: 8rem;
2141
+ padding: 2rem 1rem;
2142
+ color: var(--dg-muted);
2143
+ font-size: 0.875rem;
2144
+ }
@@ -48,9 +48,9 @@
48
48
  justify-content: center;
49
49
  min-width: 42px;
50
50
  min-height: 3.1rem;
51
- background: rgba(var(--primary-color-rgb), 0.08);
51
+ background: rgba(var(--primary-color-rgb, 27, 57, 51), 0.08);
52
52
  color: var(--primary-color);
53
- border: 1px solid rgba(var(--primary-color-rgb), 0.25);
53
+ border: 1px solid rgba(var(--primary-color-rgb, 27, 57, 51), 0.25);
54
54
  border-left: none;
55
55
  border-top-right-radius: var(--br);
56
56
  border-bottom-right-radius: var(--br);
@@ -68,7 +68,7 @@
68
68
  left: 0;
69
69
  width: 1px;
70
70
  height: 60%;
71
- background: rgba(var(--primary-color-rgb), 0.15);
71
+ background: rgba(var(--primary-color-rgb, 27, 57, 51), 0.15);
72
72
  }
73
73
 
74
74
  .buttonRight:hover {
@@ -82,7 +82,7 @@
82
82
  }
83
83
 
84
84
  .buttonRight:focus {
85
- box-shadow: 0 0 0 2px rgba(var(--primary-color-rgb), 0.2);
85
+ box-shadow: 0 0 0 2px rgba(var(--primary-color-rgb, 27, 57, 51), 0.2);
86
86
  }
87
87
 
88
88
  /* Fallback for browsers that don't support :has() */
@@ -164,17 +164,20 @@
164
164
  font-size: 0.85rem;
165
165
  background: white;
166
166
  border-radius: var(--br);
167
- // border: 1px solid rgba(var(--paragraph-rgb), 0.15);
167
+ // border: 1px solid rgba(var(--paragraph-rgb, 43, 43, 43), 0.15);
168
168
  color: var(--paragraph-color);
169
169
  box-sizing: border-box;
170
170
  transition: box-shadow 0.15s linear, border 0.15s linear;
171
171
 
172
- :hover {
173
- border: 1px solid rgba(var(--paragraph-color-rgb), 0.85);
172
+ /* Were `:hover` / `:focus-within` without the `&`, which in Sass is a
173
+ DESCENDANT selector — they matched children of the field, never the
174
+ field itself, so its hover and focus border never applied. */
175
+ &:hover {
176
+ border: 1px solid rgba(var(--paragraph-color-rgb, 43, 43, 43), 0.85);
174
177
  }
175
178
 
176
- :focus-within {
177
- border: 1px solid rgba(var(--paragraph-color-rgb), 0.85);
179
+ &:focus-within {
180
+ border: 1px solid rgba(var(--paragraph-color-rgb, 43, 43, 43), 0.85);
178
181
  }
179
182
  }
180
183
 
@@ -212,7 +215,7 @@
212
215
  appearance: none;
213
216
  width: 14px;
214
217
  height: 14px;
215
- border: 1px solid rgba(var(--paragraph-color-rgb), 0.5);
218
+ border: 1px solid rgba(var(--paragraph-color-rgb, 43, 43, 43), 0.5);
216
219
  outline: none;
217
220
  position: relative;
218
221
  cursor: pointer;
@@ -308,7 +311,7 @@ select:not(:placeholder-shown) + .fi__span {
308
311
  background: var(--tertiary-color);
309
312
  font-weight: 400;
310
313
  transition: all 0.5s cubic-bezier(0.5, 0.5, 0, 1);
311
- color: rgba(var(--paragraph-rgb), 0.65);
314
+ color: rgba(var(--paragraph-rgb, 43, 43, 43), 0.65);
312
315
  }
313
316
 
314
317
  .prefocus {
@@ -322,7 +325,7 @@ select:not(:placeholder-shown) + .fi__span {
322
325
  font-weight: 400;
323
326
  transition: all 0.5s cubic-bezier(0.5, 0.5, 0, 1);
324
327
  z-index: 1;
325
- color: rgba(var(--paragraph-rgb), 0.65);
328
+ color: rgba(var(--paragraph-rgb, 43, 43, 43), 0.65);
326
329
  padding: 0 0.25rem;
327
330
  }
328
331
 
@@ -337,7 +340,7 @@ select:not(:placeholder-shown) + .fi__span {
337
340
  font-weight: 400;
338
341
  transition: all 0.5s cubic-bezier(0.5, 0.5, 0, 1);
339
342
  z-index: 1;
340
- color: rgba(var(--paragraph-rgb), 0.65);
343
+ color: rgba(var(--paragraph-rgb, 43, 43, 43), 0.65);
341
344
  padding: 0 0.25rem;
342
345
  }
343
346
 
@@ -395,7 +398,7 @@ select:not(:placeholder-shown) + .fi__span {
395
398
  ):not([type='radio']):not(.inovua-react-toolkit-combo-box__input):not(
396
399
  .inovua-react-toolkit-date-input__input
397
400
  ):not(.inovua-react-toolkit-numeric-input__input):hover {
398
- border: 1px solid rgba(var(--paragraph-color-rgb), 0.85);
401
+ border: 1px solid rgba(var(--paragraph-color-rgb, 43, 43, 43), 0.85);
399
402
  transition: border 0.15s linear;
400
403
  }
401
404
 
@@ -407,7 +410,7 @@ select:not(:placeholder-shown) + .fi__span {
407
410
  ):not([type='radio']):not(.inovua-react-toolkit-combo-box__input):not(
408
411
  .inovua-react-toolkit-date-input__input
409
412
  ):not(.inovua-react-toolkit-numeric-input__input):focus {
410
- border: 1px solid rgba(var(--paragraph-color-rgb), 0.85);
413
+ border: 1px solid rgba(var(--paragraph-color-rgb, 43, 43, 43), 0.85);
411
414
  }
412
415
 
413
416
  .formItem textarea {
@@ -546,7 +549,7 @@ input[type='file']:hover {
546
549
  }
547
550
 
548
551
  .contentTable tbody tr:nth-of-type(even) {
549
- background: rgba(var(--item-color-rgb), 1.02);
552
+ background: rgba(var(--item-color-rgb, 255, 255, 255), 1);
550
553
  }
551
554
 
552
555
  .gridtxt__header {
@@ -696,7 +699,7 @@ input[type='file']:hover {
696
699
  &__header {
697
700
  width: 100%;
698
701
  background: var(--primary-color);
699
- border: 2px solid rgba(var(--primary-color), 1.05);
702
+ border: 2px solid color-mix(in srgb, var(--primary-color, #1b3933) 100%, transparent);
700
703
  border-top-left-radius: var(--br);
701
704
  border-top-right-radius: var(--br);
702
705
  box-sizing: border-box;
@@ -719,7 +722,7 @@ input[type='file']:hover {
719
722
  right: 5px;
720
723
  top: 5px;
721
724
  box-sizing: border-box;
722
- /* Was `rgba(var(--primary-color), 1.08)`, which is not a
725
+ /* Was `color-mix(in srgb, var(--primary-color, #1b3933) 100%, transparent)`, which is not a
723
726
  colour: `--primary-color` is a hex, rgba() wants three
724
727
  channels, and an alpha above 1 is out of range either way.
725
728
  The declaration was invalid and dropped, so this button had
@@ -769,7 +772,7 @@ input[type='file']:hover {
769
772
 
770
773
  .btn {
771
774
  background: var(--primary-color);
772
- border: 1px solid rgba(var(--primary-color), 1.05);
775
+ border: 1px solid color-mix(in srgb, var(--primary-color, #1b3933) 100%, transparent);
773
776
 
774
777
  &:hover {
775
778
  background: var(--secondary-color);
@@ -842,9 +845,9 @@ input[type='file']:hover {
842
845
  }
843
846
 
844
847
  .drawactive {
845
- border: 1px solid rgba(var(--paragraph-color-rgb), 1.4);
846
- background: rgba(var(--paragraph-color-rgb), 0.2);
847
- color: rgba(var(--paragraph-color-rgb), 1.05);
848
+ border: 1px solid rgba(var(--paragraph-color-rgb, 43, 43, 43), 1);
849
+ background: rgba(var(--paragraph-color-rgb, 43, 43, 43), 0.2);
850
+ color: rgba(var(--paragraph-color-rgb, 43, 43, 43), 1);
848
851
  }
849
852
  }
850
853
  }
@@ -853,7 +856,7 @@ input[type='file']:hover {
853
856
  display: block;
854
857
  flex: 0 1 80%;
855
858
  margin: 0;
856
- border: 1px dashed rgba(var(--primary-color-rgb), 1.2);
859
+ border: 1px dashed rgba(var(--primary-color-rgb, 27, 57, 51), 1);
857
860
  padding: 5px;
858
861
  overflow: hidden;
859
862
  box-sizing: border-box;
@@ -1676,7 +1679,12 @@ input[type='file']:hover {
1676
1679
  }
1677
1680
 
1678
1681
  /* Dark mode support */
1679
- @media (prefers-color-scheme: dark) {
1682
+ // The app ships a single light theme. These dark overrides used to fire
1683
+ // straight off the OS setting, which left the app half-dark at night: only 4
1684
+ // of 72 stylesheets carry dark rules, so dark surfaces ended up wearing
1685
+ // light-theme text. They now apply only when `data-theme="dark"` is opted
1686
+ // into explicitly, so the work survives for a real theme toggle later.
1687
+ :root[data-theme='dark'] {
1680
1688
  .emailSuggestions {
1681
1689
  background: var(--dark-item-color, #2d3748);
1682
1690
  border-color: var(--dark-border-color, #4a5568);