nexus-shared 3.2.0 → 4.1.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 (271) hide show
  1. package/CHANGELOG.md +2359 -0
  2. package/README.md +1911 -1123
  3. package/dist/Client.Index.d.ts +27 -0
  4. package/dist/Client.Index.js +27 -0
  5. package/dist/Components/Chats/ChatComposer.d.ts +7 -1
  6. package/dist/Components/Chats/ChatComposer.js +9 -6
  7. package/dist/Components/Chats/ChatParts.d.ts +15 -2
  8. package/dist/Components/Chats/ChatParts.js +15 -4
  9. package/dist/Components/Chats/ChatPopup.d.ts +46 -0
  10. package/dist/Components/Chats/ChatPopup.js +34 -0
  11. package/dist/Components/Chats/ChatThread.d.ts +9 -1
  12. package/dist/Components/Chats/ChatThread.js +54 -6
  13. package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
  14. package/dist/Components/Dashboards/DashboardBoard.js +594 -0
  15. package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
  16. package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
  17. package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
  18. package/dist/Components/Dashboards/WidgetFrame.js +45 -0
  19. package/dist/Components/Documents/Button.d.ts +6 -1
  20. package/dist/Components/Documents/Button.js +2 -2
  21. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  22. package/dist/Components/Documents/DocumentFrame.js +83 -0
  23. package/dist/Components/Documents/Menu.d.ts +4 -0
  24. package/dist/Components/Documents/Menu.js +40 -24
  25. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  26. package/dist/Components/Documents/PrintDocument.js +62 -0
  27. package/dist/Components/Documents/TabButtons.d.ts +6 -0
  28. package/dist/Components/Documents/TabButtons.js +5 -4
  29. package/dist/Components/Documents/Tabs.d.ts +59 -0
  30. package/dist/Components/Documents/Tabs.js +70 -0
  31. package/dist/Components/Domains/DomainCard.d.ts +98 -0
  32. package/dist/Components/Domains/DomainCard.js +182 -0
  33. package/dist/Components/Forms/ApiForm.js +24 -3
  34. package/dist/Components/Forms/Crud.d.ts +2 -2
  35. package/dist/Components/Forms/Crud.js +628 -247
  36. package/dist/Components/Forms/CrudActions.d.ts +137 -0
  37. package/dist/Components/Forms/CrudActions.js +214 -0
  38. package/dist/Components/Forms/CrudHistory.d.ts +6 -1
  39. package/dist/Components/Forms/CrudHistory.js +14 -6
  40. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  41. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  42. package/dist/Components/Forms/CrudParts.d.ts +37 -2
  43. package/dist/Components/Forms/CrudParts.js +123 -7
  44. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  45. package/dist/Components/Forms/CrudRowForms.js +174 -0
  46. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  47. package/dist/Components/Forms/CrudScopes.js +76 -0
  48. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  49. package/dist/Components/Forms/CrudTree.js +363 -0
  50. package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
  51. package/dist/Components/Forms/CrudWorkspace.js +177 -0
  52. package/dist/Components/Forms/ExcelImport.js +3 -3
  53. package/dist/Components/Forms/Form.d.ts +6 -5
  54. package/dist/Components/Forms/Form.js +134 -21
  55. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  56. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  57. package/dist/Components/Forms/FormQuestion.d.ts +6 -0
  58. package/dist/Components/Forms/FormQuestion.js +28 -0
  59. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  60. package/dist/Components/Forms/RecordFlows.js +282 -0
  61. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  62. package/dist/Components/Forms/SubmitForm.js +182 -10
  63. package/dist/Components/Inputs/CheckBox.js +1 -1
  64. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  65. package/dist/Components/Inputs/ChoiceField.js +28 -2
  66. package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
  67. package/dist/Components/Inputs/ComputedInput.js +179 -0
  68. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  69. package/dist/Components/Inputs/Dropdown.js +8 -6
  70. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  71. package/dist/Components/Inputs/DropdownParts.js +16 -4
  72. package/dist/Components/Inputs/Field.d.ts +7 -1
  73. package/dist/Components/Inputs/Field.js +2 -2
  74. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  75. package/dist/Components/Inputs/GroupForm.js +12 -12
  76. package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
  77. package/dist/Components/Inputs/InputRenderer.js +37 -32
  78. package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
  79. package/dist/Components/Inputs/MarkdownEditor.js +32 -6
  80. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  81. package/dist/Components/Inputs/PictureInput.js +75 -0
  82. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  83. package/dist/Components/Inputs/RadioGroup.js +8 -3
  84. package/dist/Components/Inputs/RowsInput.d.ts +15 -2
  85. package/dist/Components/Inputs/RowsInput.js +96 -8
  86. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  87. package/dist/Components/Inputs/TabularForm.js +45 -32
  88. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  89. package/dist/Components/Inputs/TextArea.js +11 -2
  90. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  91. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  92. package/dist/Components/Inputs/TreeView.d.ts +76 -2
  93. package/dist/Components/Inputs/TreeView.js +227 -16
  94. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  95. package/dist/Components/Layouts/MessageBox.js +56 -7
  96. package/dist/Components/Layouts/Popup.d.ts +34 -7
  97. package/dist/Components/Layouts/Popup.js +143 -13
  98. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  99. package/dist/Components/Layouts/PopupDock.js +2 -1
  100. package/dist/Components/Layouts/PopupHost.js +5 -4
  101. package/dist/Components/Messages/MailClient.d.ts +50 -0
  102. package/dist/Components/Messages/MailClient.js +410 -0
  103. package/dist/Components/Messages/MailColumns.d.ts +21 -0
  104. package/dist/Components/Messages/MailColumns.js +92 -0
  105. package/dist/Components/Messages/MailCompose.d.ts +46 -0
  106. package/dist/Components/Messages/MailCompose.js +154 -0
  107. package/dist/Components/Messages/MailReader.d.ts +43 -0
  108. package/dist/Components/Messages/MailReader.js +207 -0
  109. package/dist/Components/Messages/MailThread.d.ts +71 -0
  110. package/dist/Components/Messages/MailThread.js +185 -0
  111. package/dist/Components/Messages/MessageBell.d.ts +41 -0
  112. package/dist/Components/Messages/MessageBell.js +76 -0
  113. package/dist/Components/Viewers/DataTable.js +153 -49
  114. package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
  115. package/dist/Components/Viewers/DataTableColumns.js +2 -1
  116. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  117. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  118. package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
  119. package/dist/Components/Viewers/DataTableParts.js +26 -14
  120. package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
  121. package/dist/Components/Viewers/OwnerFiles.js +700 -0
  122. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  123. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  124. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  125. package/dist/Components/Viewers/RecordCards.js +509 -0
  126. package/dist/Helpers/ApiModules.js +4 -0
  127. package/dist/Helpers/AuditEntries.d.ts +50 -0
  128. package/dist/Helpers/AuditEntries.js +96 -0
  129. package/dist/Helpers/BusyHooks.d.ts +6 -0
  130. package/dist/Helpers/BusyHooks.js +39 -0
  131. package/dist/Helpers/ChatBackend.d.ts +8 -1
  132. package/dist/Helpers/ChatBackend.js +18 -0
  133. package/dist/Helpers/ChatHelpers.d.ts +2 -0
  134. package/dist/Helpers/ChatHelpers.js +4 -1
  135. package/dist/Helpers/CrudBackend.d.ts +20 -2
  136. package/dist/Helpers/CrudBackend.js +46 -2
  137. package/dist/Helpers/CrudColumns.d.ts +91 -6
  138. package/dist/Helpers/CrudColumns.js +148 -7
  139. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  140. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  141. package/dist/Helpers/CrudHelpers.d.ts +116 -2
  142. package/dist/Helpers/CrudHelpers.js +256 -0
  143. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  144. package/dist/Helpers/CrudNarrowPick.js +157 -0
  145. package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
  146. package/dist/Helpers/CrudPickerPlan.js +46 -0
  147. package/dist/Helpers/CrudRowForms.d.ts +32 -0
  148. package/dist/Helpers/CrudRowForms.js +76 -0
  149. package/dist/Helpers/CrudScopes.d.ts +32 -0
  150. package/dist/Helpers/CrudScopes.js +79 -0
  151. package/dist/Helpers/DashboardHelpers.d.ts +115 -0
  152. package/dist/Helpers/DashboardHelpers.js +305 -0
  153. package/dist/Helpers/DomainCards.d.ts +140 -0
  154. package/dist/Helpers/DomainCards.js +370 -0
  155. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  156. package/dist/Helpers/DropdownHelpers.js +6 -2
  157. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  158. package/dist/Helpers/ExcelHelpers.js +12 -11
  159. package/dist/Helpers/FileAdapter.d.ts +158 -0
  160. package/dist/Helpers/FileAdapter.js +173 -0
  161. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  162. package/dist/Helpers/FilePrimaries.js +138 -0
  163. package/dist/Helpers/FormConfirm.d.ts +32 -0
  164. package/dist/Helpers/FormConfirm.js +40 -0
  165. package/dist/Helpers/FormStore.d.ts +100 -5
  166. package/dist/Helpers/FormStore.js +368 -25
  167. package/dist/Helpers/InputConditions.d.ts +56 -0
  168. package/dist/Helpers/InputConditions.js +192 -0
  169. package/dist/Helpers/InputMemory.d.ts +106 -0
  170. package/dist/Helpers/InputMemory.js +395 -0
  171. package/dist/Helpers/InputNarrowing.d.ts +50 -0
  172. package/dist/Helpers/InputNarrowing.js +148 -0
  173. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  174. package/dist/Helpers/InputParamsHelpers.js +157 -74
  175. package/dist/Helpers/MailClientHelpers.d.ts +96 -0
  176. package/dist/Helpers/MailClientHelpers.js +211 -0
  177. package/dist/Helpers/MessageBackend.d.ts +152 -0
  178. package/dist/Helpers/MessageBackend.js +329 -0
  179. package/dist/Helpers/MessageHelpers.d.ts +160 -0
  180. package/dist/Helpers/MessageHelpers.js +573 -0
  181. package/dist/Helpers/MessageUnread.d.ts +95 -0
  182. package/dist/Helpers/MessageUnread.js +245 -0
  183. package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
  184. package/dist/Helpers/NexusDriveAdapter.js +236 -0
  185. package/dist/Helpers/NumberHelpers.d.ts +156 -0
  186. package/dist/Helpers/NumberHelpers.js +242 -0
  187. package/dist/Helpers/PagedPicker.d.ts +42 -0
  188. package/dist/Helpers/PagedPicker.js +85 -0
  189. package/dist/Helpers/Permissions.js +2 -1
  190. package/dist/Helpers/QrCode.d.ts +26 -0
  191. package/dist/Helpers/QrCode.js +463 -0
  192. package/dist/Helpers/RecordCards.d.ts +205 -0
  193. package/dist/Helpers/RecordCards.js +378 -0
  194. package/dist/Helpers/RequestCache.d.ts +14 -2
  195. package/dist/Helpers/RequestCache.js +22 -3
  196. package/dist/Helpers/RowsStore.d.ts +61 -2
  197. package/dist/Helpers/RowsStore.js +203 -16
  198. package/dist/Helpers/ServerPreview.d.ts +69 -0
  199. package/dist/Helpers/ServerPreview.js +99 -0
  200. package/dist/Helpers/TableColumns.js +4 -3
  201. package/dist/Helpers/TableExport.d.ts +7 -1
  202. package/dist/Helpers/TableExport.js +9 -3
  203. package/dist/Helpers/TableFilters.d.ts +1 -1
  204. package/dist/Helpers/TableFilters.js +24 -23
  205. package/dist/Helpers/TableHelpers.d.ts +38 -4
  206. package/dist/Helpers/TableHelpers.js +78 -10
  207. package/dist/Helpers/TableJoins.d.ts +13 -0
  208. package/dist/Helpers/TableJoins.js +34 -0
  209. package/dist/Helpers/TableStore.d.ts +28 -4
  210. package/dist/Helpers/TableStore.js +73 -11
  211. package/dist/Helpers/ToolbarFit.d.ts +23 -0
  212. package/dist/Helpers/ToolbarFit.js +30 -0
  213. package/dist/Helpers/TreeStore.d.ts +156 -6
  214. package/dist/Helpers/TreeStore.js +547 -18
  215. package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
  216. package/dist/Interfaces/ApiInterfaces.js +10 -1
  217. package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
  218. package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
  219. package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
  220. package/dist/Interfaces/DashboardInterfaces.js +1 -0
  221. package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
  222. package/dist/Interfaces/DomainInterfaces.js +1 -0
  223. package/dist/Interfaces/FileInterfaces.d.ts +183 -0
  224. package/dist/Interfaces/FileInterfaces.js +6 -0
  225. package/dist/Interfaces/FormInterfaces.d.ts +460 -32
  226. package/dist/Interfaces/FormInterfaces.js +79 -1
  227. package/dist/Interfaces/InputInterfaces.d.ts +22 -3
  228. package/dist/Interfaces/MailInterfaces.d.ts +192 -0
  229. package/dist/Interfaces/MailInterfaces.js +1 -0
  230. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  231. package/dist/Interfaces/RecordInterfaces.js +1 -0
  232. package/dist/Interfaces/TableInterfaces.d.ts +102 -3
  233. package/dist/Services/ExcelApi.js +5 -0
  234. package/dist/Services/FileApi.d.ts +13 -0
  235. package/dist/Services/FileApi.js +29 -0
  236. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  237. package/dist/Services/ImageEditorLoader.js +51 -0
  238. package/dist/Services/JoinSources.d.ts +79 -6
  239. package/dist/Services/JoinSources.js +132 -21
  240. package/dist/Services/MessageApi.d.ts +18 -0
  241. package/dist/Services/MessageApi.js +64 -0
  242. package/dist/Services/ModuleImport.d.ts +7 -0
  243. package/dist/Services/ModuleImport.js +22 -0
  244. package/dist/Shared.Index.d.ts +28 -0
  245. package/dist/Shared.Index.js +28 -0
  246. package/package.json +13 -2
  247. package/src/Styles/Nexus.Avatar.css +6 -5
  248. package/src/Styles/Nexus.Button.css +39 -4
  249. package/src/Styles/Nexus.Chat.css +114 -22
  250. package/src/Styles/Nexus.Choice.css +16 -0
  251. package/src/Styles/Nexus.Crud.css +486 -21
  252. package/src/Styles/Nexus.Dashboard.css +763 -0
  253. package/src/Styles/Nexus.Domain.css +384 -0
  254. package/src/Styles/Nexus.Dropdown.css +8 -0
  255. package/src/Styles/Nexus.Editor.css +20 -0
  256. package/src/Styles/Nexus.Files.css +437 -0
  257. package/src/Styles/Nexus.Form.css +5 -2
  258. package/src/Styles/Nexus.Index.css +7 -0
  259. package/src/Styles/Nexus.Input.css +73 -0
  260. package/src/Styles/Nexus.Mail.css +1062 -0
  261. package/src/Styles/Nexus.Menu.css +10 -1
  262. package/src/Styles/Nexus.Popup.css +135 -1
  263. package/src/Styles/Nexus.Print.css +288 -0
  264. package/src/Styles/Nexus.QrCode.css +12 -0
  265. package/src/Styles/Nexus.Record.css +430 -0
  266. package/src/Styles/Nexus.Tab.Buttons.css +87 -22
  267. package/src/Styles/Nexus.Table.css +1458 -1387
  268. package/src/Styles/Nexus.Theme.Picker.css +1 -0
  269. package/src/Styles/Nexus.Themes.css +12 -6
  270. package/src/Styles/Nexus.Tokens.css +8 -3
  271. package/src/Styles/Nexus.Tree.css +41 -1
@@ -1,1387 +1,1458 @@
1
- @layer nexus.components {
2
- /*
3
- * Data table: a bar (tabs, search, buttons), a frame holding the table (a header that stays while rows scroll, a
4
- * checkbox column on the left and the actions on the right that stay while columns scroll), and a footer with the
5
- * range and the pager. While rows are selected, a bar with their actions covers the column headings.
6
- */
7
- .nx-table {
8
- --nx-table-bg: var(--nx-surface);
9
- --nx-table-head-bg: color-mix(in oklab, var(--nx-text) 4%, var(--nx-surface));
10
- --nx-table-hover: color-mix(in oklab, var(--nx-text) 3%, var(--nx-surface));
11
- --nx-table-selected: color-mix(in oklab, var(--nx-primary) 11%, var(--nx-surface));
12
- --nx-table-selected-hover: color-mix(in oklab, var(--nx-primary) 16%, var(--nx-surface));
13
- --nx-table-flash: color-mix(in oklab, var(--nx-success) 18%, var(--nx-surface));
14
- --nx-table-line: var(--nx-border);
15
- --nx-table-head-h: calc(var(--nx-row) + 8px);
16
- /* The band of group headings above the columns' own, when any column names a group. */
17
- --nx-table-group-h: 30px;
18
- /* How many rows the header has: 1, or 2 while a band stands above the headings (`data-head-rows`). */
19
- --nx-table-head-rows: 1;
20
- /* The whole header, which the loading bar and the selection bar are measured from. */
21
- --nx-table-head-all: calc(var(--nx-table-head-h) + (var(--nx-table-head-rows) - 1) * var(--nx-table-group-h));
22
- --nx-table-row-h: calc(var(--nx-row) + 12px);
23
- --nx-table-pad-x: 12px;
24
- --nx-table-select-w: 44px;
25
- --nx-table-font: var(--nx-text-md);
26
- --nx-table-edge: light-dark(rgb(16 24 40 / 0.14), rgb(0 0 0 / 0.55));
27
-
28
- container: nx-table / inline-size;
29
- display: flex;
30
- flex-direction: column;
31
- min-width: 0;
32
- overflow: clip;
33
- /* One frame round the bar, the rows and the footer. */
34
- border: 1px solid var(--nx-table-line);
35
- border-radius: var(--nx-radius-lg);
36
- background: var(--nx-table-bg);
37
- box-shadow: var(--nx-shadow-sm);
38
- color: var(--nx-text);
39
- }
40
-
41
- .nx-table[data-head-rows="2"] {
42
- --nx-table-head-rows: 2;
43
- }
44
-
45
- .nx-table--sm {
46
- --nx-table-head-h: calc(var(--nx-row) + 2px);
47
- --nx-table-group-h: 26px;
48
- --nx-table-row-h: calc(var(--nx-row) + 4px);
49
- --nx-table-pad-x: 10px;
50
- --nx-table-select-w: 38px;
51
- --nx-table-font: var(--nx-text-sm);
52
- }
53
-
54
- /*
55
- * The bar, and a line under it. One line on a wide table: the Add section (the table's Add and the page's own
56
- * buttons) and the lists on the left; Reload, the options (⋮) and the search, rows per page inside it, on the right.
57
- * A narrower table moves whole parts onto lines of their own, never a lone item: the search takes the top line
58
- * under 670px (with nothing on the left, the buttons' line), Reload goes into the options menu under 375px, and,
59
- * beside lists, the Add section takes the last line under 325px. On the admin shell, whose menu leaves the screen
60
- * at 900px, those are windows of 695, 400 and 350px. The lists show their labels from 900px (TabButtons
61
- * `labels="auto"`), where the line holds them with room to spare.
62
- */
63
- .nx-table__bar {
64
- display: flex;
65
- flex-wrap: wrap;
66
- align-items: center;
67
- gap: var(--nx-space-2);
68
- margin: 6px var(--nx-space-2);
69
- }
70
-
71
- .nx-table__bar:not(:has(:not(.nx-table__bar-end):not(:empty))) {
72
- display: none;
73
- }
74
-
75
- .nx-table__bar:has(:not(.nx-table__bar-end):not(:empty)) + .nx-table__frame {
76
- border-top: 1px solid var(--nx-table-line);
77
- }
78
-
79
- .nx-table__bar-add,
80
- .nx-table__bar-start,
81
- .nx-table__bar-end {
82
- display: flex;
83
- align-items: center;
84
- gap: var(--nx-space-2);
85
- min-width: 0;
86
- }
87
-
88
- /* A page's own tabs and filters may run long: they wrap inside their part. */
89
- .nx-table__bar-start {
90
- flex-wrap: wrap;
91
- }
92
-
93
- /* A part whose page content renders nothing (an empty fragment) is no part: no room, no gap, no step. */
94
- .nx-table__bar-add:empty,
95
- .nx-table__bar-start:empty {
96
- display: none;
97
- }
98
-
99
- /* The right side takes the room left on its line, so it ends at the bar's end. */
100
- .nx-table__bar-end {
101
- margin-inline-start: auto;
102
- }
103
-
104
- /* 19rem where there is room. A line breaks on the 12rem basis, so the search narrows before the bar takes a line. */
105
- .nx-table__search {
106
- flex: 1 1 12rem;
107
- min-width: 0;
108
- max-width: 19rem;
109
- }
110
-
111
- /* Rows per page inside the search box, at its start, with a line between the two. */
112
- .nx-table__search .nx-field__actions--start {
113
- align-self: stretch;
114
- padding-right: 0;
115
- padding-left: 2px;
116
- border-right: 1px solid var(--nx-field-border);
117
- }
118
-
119
- .nx-table__page-size--field.nx-btn {
120
- min-width: 0;
121
- height: calc(var(--nx-field-height) - 8px);
122
- gap: 0;
123
- padding: 0 2px 0 4px;
124
- color: var(--nx-text-muted);
125
- font-variant-numeric: tabular-nums;
126
- }
127
-
128
- .nx-table__page-size--field .nx-icon {
129
- width: 14px;
130
- height: 14px;
131
- }
132
-
133
- .nx-table__page-size--field:hover {
134
- color: var(--nx-text);
135
- }
136
-
137
- /* The search comes first and takes the room: the top line beside an Add section or lists, else the buttons' line. */
138
- @container nx-table (width < 670px) {
139
- .nx-table__search {
140
- order: -1;
141
- max-width: none;
142
- }
143
-
144
- .nx-table__bar:has(> :is(.nx-table__bar-add, .nx-table__bar-start):not(:empty)) > .nx-table__search {
145
- flex-basis: 100%;
146
- }
147
- }
148
-
149
- /* DataTable reads this to offer Reload in the options menu. */
150
- @container nx-table (width < 375px) {
151
- .nx-table__reload {
152
- display: none;
153
- }
154
- }
155
-
156
- /*
157
- * Beside lists, the Add section goes last and takes its line. Its buttons share it equally (the Add is a `block`
158
- * button, so its label stays centred), or each takes a line of its own when they do not fit side by side.
159
- */
160
- @container nx-table (width < 325px) {
161
- .nx-table__bar:has(> .nx-table__bar-start:not(:empty)) > .nx-table__bar-add {
162
- flex-basis: 100%;
163
- flex-wrap: wrap;
164
- order: 1;
165
- }
166
-
167
- .nx-table__bar:has(> .nx-table__bar-start:not(:empty)) > .nx-table__bar-add > * {
168
- flex: 1 1 0;
169
- }
170
- }
171
-
172
- /*
173
- * The same steps for a bar whose parts are wider than the widths above expect (a long Add label, the page's own
174
- * buttons, cozy density): DataTable squeezes it (`data-squeeze`) one step at a time, each keeping the ones before.
175
- */
176
- .nx-table__bar[data-squeeze] > .nx-table__search {
177
- order: -1;
178
- max-width: none;
179
- }
180
-
181
- .nx-table__bar[data-squeeze]:has(> :is(.nx-table__bar-add, .nx-table__bar-start):not(:empty)) > .nx-table__search {
182
- flex-basis: 100%;
183
- }
184
-
185
- .nx-table__bar[data-squeeze~="reload"] .nx-table__reload {
186
- display: none;
187
- }
188
-
189
- .nx-table__bar[data-squeeze~="add"] > .nx-table__bar-add {
190
- flex-basis: 100%;
191
- flex-wrap: wrap;
192
- order: 1;
193
- }
194
-
195
- .nx-table__bar[data-squeeze~="add"] > .nx-table__bar-add > * {
196
- flex: 1 1 0;
197
- }
198
-
199
- /*
200
- * `fill`: the table takes the height it is given instead of growing with its rows. The bar and the footer keep their
201
- * own height, the frame takes the rest, and the rows scroll inside it. Nothing below the table moves when a page
202
- * holds fewer rows, and the page behind it never gains a scrollbar.
203
- */
204
- .nx-table--fill {
205
- height: 100%;
206
- min-height: 0;
207
- }
208
-
209
- .nx-table--fill > .nx-table__frame {
210
- display: flex;
211
- flex: 1 1 auto;
212
- flex-direction: column;
213
- min-height: 0;
214
- }
215
-
216
- .nx-table--fill .nx-table__scroll {
217
- flex: 1 1 auto;
218
- min-height: 0;
219
- /* The room for the scrollbar is kept, so the headings do not shift the moment the rows need one. */
220
- scrollbar-gutter: stable;
221
- }
222
-
223
- /* The rows' part of the frame: the selection bar and the loading bar are placed in it. */
224
- .nx-table__frame {
225
- position: relative;
226
- min-width: 0;
227
- overflow: hidden;
228
- }
229
-
230
- .nx-table__scroll {
231
- overflow: auto;
232
- overscroll-behavior-x: contain;
233
- scrollbar-width: thin;
234
- scrollbar-color: color-mix(in oklab, var(--nx-text-subtle) 75%, transparent) transparent;
235
- }
236
-
237
- .nx-table__table {
238
- width: 100%;
239
- border-collapse: separate;
240
- border-spacing: 0;
241
- font-size: var(--nx-table-font);
242
- line-height: 1.45;
243
- }
244
-
245
- /* Cells */
246
- .nx-table__cell,
247
- .nx-table__heading {
248
- box-sizing: border-box;
249
- padding: 4px var(--nx-table-pad-x);
250
- text-align: start;
251
- vertical-align: middle;
252
- }
253
-
254
- .nx-table__cell {
255
- height: var(--nx-table-row-h);
256
- border-bottom: 1px solid var(--nx-table-line);
257
- background: var(--nx-row-bg);
258
- }
259
-
260
- .nx-table__row {
261
- --nx-row-bg: var(--nx-table-bg);
262
- }
263
-
264
- /* The last row's line would double the frame. */
265
- .nx-table__row:last-child > .nx-table__cell {
266
- border-bottom: 0;
267
- }
268
-
269
- .nx-table__row:not(.nx-table__row--message, .nx-table__row--skeleton):hover {
270
- --nx-row-bg: var(--nx-table-hover);
271
- }
272
-
273
- /* A row a click opens says so under the pointer. Only the pointer changes: nothing in the row moves on hover. */
274
- .nx-table__row[data-open] {
275
- cursor: pointer;
276
- }
277
-
278
- .nx-table__row[data-selected] {
279
- --nx-row-bg: var(--nx-table-selected);
280
- }
281
-
282
- .nx-table__row[data-selected]:hover {
283
- --nx-row-bg: var(--nx-table-selected-hover);
284
- }
285
-
286
- /* Muted text on a selected row's tint moves toward the text color, so it keeps 4.5:1 in every theme. */
287
- .nx-table__row[data-selected] :is(.nx-table__number, .nx-table__dash, .nx-table__no) {
288
- color: color-mix(in oklab, var(--nx-text-muted), var(--nx-text) 45%);
289
- }
290
-
291
- /* Columns scroll under the pinned ones: a soft shadow shows there is more on that side. */
292
- .nx-table__frame[data-scroll-start] {
293
- --nx-table-start-edge: 8px 0 8px -8px var(--nx-table-edge);
294
- }
295
-
296
- .nx-table__frame[data-scroll-end] {
297
- --nx-table-end-edge: -8px 0 8px -8px var(--nx-table-edge);
298
- }
299
-
300
- .nx-table__row[data-busy] > .nx-table__cell:not(.nx-table__cell--actions) {
301
- color: var(--nx-text-muted);
302
- }
303
-
304
- /* A row just added or saved: a green wash that fades (a steady one with reduced motion, until the mark goes). */
305
- .nx-table__row[data-flash] > .nx-table__cell {
306
- animation: nx-table-flash 1600ms ease-out;
307
- }
308
-
309
- @keyframes nx-table-flash {
310
- from {
311
- background-color: var(--nx-table-flash);
312
- }
313
- }
314
-
315
- .nx-table__cell--start {
316
- text-align: start;
317
- }
318
-
319
- .nx-table__cell--center {
320
- text-align: center;
321
- }
322
-
323
- .nx-table__cell--end {
324
- text-align: end;
325
- font-variant-numeric: tabular-nums;
326
- }
327
-
328
- /* A value ends in "…" at the column's width (24rem when the column sets none). */
329
- .nx-table__value {
330
- display: block;
331
- max-width: var(--nx-col-max, 24rem);
332
- overflow: hidden;
333
- text-overflow: ellipsis;
334
- white-space: nowrap;
335
- }
336
-
337
- .nx-table__cell--wrap .nx-table__value {
338
- max-width: var(--nx-col-max, 32rem);
339
- white-space: normal;
340
- overflow-wrap: anywhere;
341
- }
342
-
343
- .nx-table__cell--end .nx-table__value {
344
- margin-left: auto;
345
- }
346
-
347
- .nx-table__cell--center .nx-table__value {
348
- margin-inline: auto;
349
- }
350
-
351
- /*
352
- * Pinned columns stay while the others scroll: the numbers and checkbox column and the actions always, and
353
- * any column the user pins from its menu. Their distance from the edge is measured from the headings before them.
354
- */
355
- .nx-table__cell[data-pin],
356
- .nx-table__heading[data-pin] {
357
- position: sticky;
358
- z-index: 1;
359
- background: var(--nx-row-bg);
360
- }
361
-
362
- .nx-table__heading[data-pin] {
363
- z-index: 3;
364
- background: var(--nx-table-head-bg);
365
- }
366
-
367
- /* The last pinned column of a side carries the shadow the others scroll under. */
368
- .nx-table__cell[data-pin="left"][data-pin-edge],
369
- .nx-table__heading[data-pin="left"][data-pin-edge] {
370
- box-shadow: var(--nx-table-start-edge, 0 0 #0000);
371
- }
372
-
373
- .nx-table__cell[data-pin="right"][data-pin-edge],
374
- .nx-table__heading[data-pin="right"][data-pin-edge] {
375
- box-shadow: var(--nx-table-end-edge, 0 0 #0000);
376
- }
377
-
378
- /* The first column: the row's number, with its checkbox in the same place. */
379
- .nx-table__cell--index {
380
- width: var(--nx-table-select-w);
381
- min-width: var(--nx-table-select-w);
382
- padding: 0;
383
- text-align: center;
384
- }
385
-
386
- .nx-table__number {
387
- display: inline-flex;
388
- align-items: center;
389
- justify-content: center;
390
- width: 100%;
391
- color: var(--nx-text-muted);
392
- font-size: var(--nx-text-xs);
393
- font-variant-numeric: tabular-nums;
394
- font-weight: 500;
395
- }
396
-
397
- .nx-table__check {
398
- display: inline-flex;
399
- align-items: center;
400
- justify-content: center;
401
- width: 100%;
402
- height: 100%;
403
- min-height: 28px;
404
- }
405
-
406
- .nx-table__check .nx-choice__control {
407
- margin: 0;
408
- }
409
-
410
- /* Both in the one cell: the number, until the row is under the pointer or anything is selected. */
411
- .nx-table__cell--index[data-swap] > .nx-table__number,
412
- .nx-table__cell--index[data-swap] > .nx-table__check {
413
- position: absolute;
414
- inset: 0;
415
- width: auto;
416
- transition: opacity var(--nx-duration);
417
- }
418
-
419
- .nx-table__cell--index[data-swap] > .nx-table__check {
420
- opacity: 0;
421
- }
422
-
423
- .nx-table__row:hover > .nx-table__cell--index[data-swap] > .nx-table__check,
424
- /* The heading's own cell may stand in the band row, so the whole header counts as hovering it. */
425
- .nx-table__thead:hover .nx-table__cell--index[data-swap] > .nx-table__check,
426
- .nx-table__cell--index[data-swap]:focus-within > .nx-table__check,
427
- .nx-table[data-selecting] .nx-table__cell--index[data-swap] > .nx-table__check {
428
- opacity: 1;
429
- }
430
-
431
- .nx-table__row:hover > .nx-table__cell--index[data-swap] > .nx-table__number,
432
- .nx-table__thead:hover .nx-table__cell--index[data-swap] > .nx-table__number,
433
- .nx-table__cell--index[data-swap]:focus-within > .nx-table__number,
434
- .nx-table[data-selecting] .nx-table__cell--index[data-swap] > .nx-table__number {
435
- opacity: 0;
436
- }
437
-
438
- /* A touch screen has no pointer to hover with, so the checkbox stays. */
439
- @media (hover: none) {
440
- .nx-table__cell--index[data-swap] > .nx-table__check {
441
- opacity: 1;
442
- }
443
-
444
- .nx-table__cell--index[data-swap] > .nx-table__number {
445
- opacity: 0;
446
- }
447
- }
448
-
449
- /*
450
- * The sections above the page: a row across the table whose button folds a set of the user's records open and shut -
451
- * the ones they pinned, their favourites - and the rows themselves, which show in the list below as well. A section's
452
- * row wears the section's icon where the rows of the page show their number.
453
- */
454
- .nx-table__section-cell {
455
- padding: 0;
456
- border-bottom: 1px solid var(--nx-table-line);
457
- background: var(--nx-table-head-bg);
458
- }
459
-
460
- .nx-table__section-toggle {
461
- display: flex;
462
- align-items: center;
463
- gap: var(--nx-space-2);
464
- width: 100%;
465
- min-height: var(--nx-table-head-h);
466
- padding: 0 var(--nx-table-pad-x);
467
- border: 0;
468
- background: none;
469
- color: var(--nx-text);
470
- font: inherit;
471
- font-size: var(--nx-table-font);
472
- font-weight: 600;
473
- text-align: start;
474
- cursor: pointer;
475
- transition: background-color var(--nx-duration);
476
- }
477
-
478
- .nx-table__section-toggle:hover {
479
- background: color-mix(in oklab, var(--nx-text) 5%, transparent);
480
- }
481
-
482
- .nx-table__section-toggle:focus-visible {
483
- outline: none;
484
- box-shadow: inset 0 0 0 2px var(--nx-primary);
485
- }
486
-
487
- .nx-table__section-caret,
488
- .nx-table__section-icon,
489
- .nx-table__mark {
490
- flex-shrink: 0;
491
- width: 15px;
492
- height: 15px;
493
- color: var(--nx-text-muted);
494
- }
495
-
496
- /* A row's marks, where its number would be: one under the other, in the line a single mark takes. */
497
- .nx-table__marks {
498
- display: inline-flex;
499
- flex-direction: column;
500
- align-items: center;
501
- gap: 2px;
502
- }
503
-
504
- .nx-table__section-caret {
505
- transition: transform var(--nx-duration);
506
- }
507
-
508
- .nx-table__section[data-open] .nx-table__section-caret {
509
- transform: rotate(90deg);
510
- }
511
-
512
- .nx-table__section-count {
513
- color: var(--nx-text-muted);
514
- font-weight: 400;
515
- font-variant-numeric: tabular-nums;
516
- }
517
-
518
- .nx-table__section-loading {
519
- width: 14px;
520
- height: 14px;
521
- color: var(--nx-text-muted);
522
- }
523
-
524
- [data-nx-motion="reduce"] .nx-table__section-caret,
525
- [data-nx-motion="reduce"] .nx-table__section-toggle {
526
- transition: none;
527
- }
528
-
529
- .nx-table__cell--actions {
530
- width: 1px;
531
- padding-inline: 4px 8px;
532
- white-space: nowrap;
533
- }
534
-
535
- /*
536
- * Once the user has given a column a width, the table lays its columns out from the list of widths, so dragging one
537
- * edge moves that edge only. The filler column takes whatever space the others leave.
538
- */
539
- .nx-table[data-sized] .nx-table__table {
540
- table-layout: fixed;
541
- }
542
-
543
- .nx-table__cell--filler {
544
- padding: 0;
545
- background: var(--nx-row-bg);
546
- }
547
-
548
- .nx-table__heading.nx-table__cell--filler {
549
- background: var(--nx-table-head-bg);
550
- }
551
-
552
- .nx-table__actions {
553
- display: flex;
554
- align-items: center;
555
- justify-content: flex-end;
556
- gap: 2px;
557
- }
558
-
559
- /* Header: stays at the top while rows scroll. */
560
- .nx-table__heading {
561
- position: sticky;
562
- top: 0;
563
- z-index: 2;
564
- height: var(--nx-table-head-h);
565
- border-bottom: 1px solid var(--nx-table-line);
566
- background: var(--nx-table-head-bg);
567
- color: var(--nx-text);
568
- font-size: var(--nx-text-xs);
569
- font-weight: 650;
570
- white-space: nowrap;
571
- }
572
-
573
- /* A heading that holds the sort button, the column's menu, and the edge to drag keeps no padding of its own. */
574
- .nx-table__heading:has(> .nx-table__heading-inner) {
575
- padding: 0;
576
- }
577
-
578
- /*
579
- * Columns in bands: a group's heading spans the columns next to each other that name it, and the columns with no
580
- * group keep one heading as tall as both rows. Both rows stay while the rows scroll, the band on top.
581
- */
582
- .nx-table__head--leaves > .nx-table__heading {
583
- top: var(--nx-table-group-h);
584
- }
585
-
586
- .nx-table__heading--group {
587
- height: var(--nx-table-group-h);
588
- border-bottom: 0;
589
- text-align: center;
590
- }
591
-
592
- /* The line under a band stops short of its edges, so two bands beside each other read as two, not as one rule. */
593
- .nx-table__heading--group::after {
594
- content: "";
595
- position: absolute;
596
- right: var(--nx-table-pad-x);
597
- bottom: 0;
598
- left: var(--nx-table-pad-x);
599
- height: 1px;
600
- background: var(--nx-table-line);
601
- }
602
-
603
- .nx-table__group {
604
- display: block;
605
- overflow: hidden;
606
- color: var(--nx-text);
607
- font-size: var(--nx-text-xs);
608
- font-weight: 700;
609
- letter-spacing: 0.04em;
610
- text-overflow: ellipsis;
611
- text-transform: uppercase;
612
- }
613
-
614
- .nx-table__heading-inner {
615
- display: flex;
616
- align-items: center;
617
- min-width: 0;
618
- height: 100%;
619
- }
620
-
621
- .nx-table__heading-label {
622
- min-width: 0;
623
- overflow: hidden;
624
- text-overflow: ellipsis;
625
- }
626
-
627
- .nx-table__heading-inner > .nx-table__heading-label {
628
- flex: 1 1 auto;
629
- padding: 0 var(--nx-table-pad-x);
630
- }
631
-
632
- /* A sortable heading is one button across its cell, beside the column's menu. */
633
- .nx-table__sort {
634
- display: flex;
635
- flex: 1 1 auto;
636
- align-items: center;
637
- gap: 6px;
638
- min-width: 0;
639
- height: 100%;
640
- padding: 0 4px 0 var(--nx-table-pad-x);
641
- border: 0;
642
- background: transparent;
643
- color: inherit;
644
- font: inherit;
645
- text-align: inherit;
646
- cursor: pointer;
647
- transition: background-color var(--nx-duration);
648
- }
649
-
650
- .nx-table__cell--end .nx-table__sort {
651
- flex-direction: row-reverse;
652
- padding: 0 var(--nx-table-pad-x) 0 4px;
653
- }
654
-
655
- .nx-table__cell--center .nx-table__sort {
656
- justify-content: center;
657
- }
658
-
659
- /* The column's menu (pin, move, fit, hide): at the end of the heading, and shown while it is hovered or focused. */
660
- .nx-table__col-menu.nx-btn {
661
- flex: 0 0 auto;
662
- width: 20px;
663
- min-width: 20px;
664
- height: calc(var(--nx-table-head-h) - 12px);
665
- margin-right: 2px;
666
- padding: 0;
667
- border-radius: var(--nx-radius-sm);
668
- opacity: 0;
669
- transition: opacity var(--nx-duration);
670
- }
671
-
672
- .nx-table__col-menu .nx-icon {
673
- width: 14px;
674
- height: 14px;
675
- }
676
-
677
- .nx-table__heading:hover .nx-table__col-menu,
678
- .nx-table__col-menu:is(:focus, :focus-visible),
679
- .nx-table__col-menu[aria-expanded="true"] {
680
- opacity: 1;
681
- }
682
-
683
- @media (hover: none) {
684
- .nx-table__col-menu.nx-btn {
685
- opacity: 1;
686
- }
687
- }
688
-
689
- /* The edge of a heading: drag it for the column's width, double-click it to fit the content. */
690
- .nx-table__resizer {
691
- position: absolute;
692
- top: 0;
693
- right: -2px;
694
- z-index: 4;
695
- width: 8px;
696
- height: 100%;
697
- touch-action: none;
698
- cursor: col-resize;
699
- }
700
-
701
- .nx-table__resizer::after {
702
- content: "";
703
- position: absolute;
704
- top: 7px;
705
- bottom: 7px;
706
- left: 3px;
707
- width: 2px;
708
- border-radius: 1px;
709
- background: transparent;
710
- transition: background-color var(--nx-duration);
711
- }
712
-
713
- .nx-table__heading:hover > .nx-table__resizer::after,
714
- .nx-table__resizer[data-active]::after {
715
- background: var(--nx-primary);
716
- }
717
-
718
- .nx-table__resizer[data-active]::after {
719
- top: 0;
720
- bottom: 0;
721
- }
722
-
723
- /* Dragging a heading to another place: the heading being moved, and a line where it will land. */
724
- .nx-table__heading[data-dragging] {
725
- background: color-mix(in oklab, var(--nx-primary) 14%, var(--nx-table-head-bg));
726
- }
727
-
728
- .nx-table__heading[data-drop-before]::before,
729
- .nx-table__heading[data-drop-after]::after {
730
- content: "";
731
- position: absolute;
732
- top: 0;
733
- bottom: 0;
734
- z-index: 5;
735
- width: 2px;
736
- background: var(--nx-primary);
737
- }
738
-
739
- .nx-table__heading[data-drop-before]::before {
740
- left: 0;
741
- }
742
-
743
- .nx-table__heading[data-drop-after]::after {
744
- right: 0;
745
- }
746
-
747
- .nx-table[data-col-drag] .nx-table__head {
748
- user-select: none;
749
- }
750
-
751
- .nx-table[data-col-drag] :is(.nx-table__heading, .nx-table__sort, .nx-table__col-menu, .nx-table__resizer) {
752
- cursor: grabbing;
753
- }
754
-
755
- .nx-table[data-col-resize] :is(.nx-table__heading, .nx-table__sort, .nx-table__cell) {
756
- cursor: col-resize;
757
- }
758
-
759
- .nx-table__sort:hover:not(:disabled) {
760
- background: color-mix(in oklab, var(--nx-text) 5%, transparent);
761
- }
762
-
763
- .nx-table__sort:focus-visible {
764
- outline: none;
765
- box-shadow: inset 0 0 0 2px var(--nx-primary);
766
- }
767
-
768
- .nx-table__sort:disabled {
769
- cursor: default;
770
- }
771
-
772
- .nx-table__sort-icon {
773
- flex-shrink: 0;
774
- width: 14px;
775
- height: 14px;
776
- color: var(--nx-text-muted);
777
- opacity: 0;
778
- transition: opacity var(--nx-duration);
779
- }
780
-
781
- .nx-table__sort[data-sorted] .nx-table__sort-icon {
782
- color: var(--nx-text);
783
- opacity: 1;
784
- }
785
-
786
- .nx-table__sort:is(:hover, :focus-visible):not(:disabled) .nx-table__sort-icon {
787
- opacity: 1;
788
- }
789
-
790
- @media (hover: none) {
791
- .nx-table__sort:not(:disabled) .nx-table__sort-icon {
792
- opacity: 0.7;
793
- }
794
- }
795
-
796
- /* Values */
797
- .nx-table__dash,
798
- .nx-table__no {
799
- color: var(--nx-text-muted);
800
- }
801
-
802
- .nx-table__yes {
803
- display: inline-flex;
804
- color: var(--nx-success);
805
- }
806
-
807
- .nx-table__yes .nx-icon {
808
- width: 16px;
809
- height: 16px;
810
- }
811
-
812
- /* An option with a tone: a tag with a dot of its color; the text keeps the text color, readable on every tint. */
813
- .nx-table__tag {
814
- --nx-tag-tone: var(--nx-text-muted);
815
- display: inline-flex;
816
- align-items: center;
817
- gap: 6px;
818
- max-width: 100%;
819
- padding: 1px 8px;
820
- border-radius: var(--nx-radius-full);
821
- background: color-mix(in oklab, var(--nx-tag-tone) 14%, var(--nx-surface));
822
- color: var(--nx-text);
823
- font-size: var(--nx-text-xs);
824
- font-weight: 500;
825
- line-height: 1.6;
826
- vertical-align: middle;
827
- }
828
-
829
- .nx-table__tag::before {
830
- content: "";
831
- flex-shrink: 0;
832
- width: 6px;
833
- height: 6px;
834
- border-radius: 50%;
835
- background: var(--nx-tag-tone);
836
- }
837
-
838
- .nx-table__tag[data-tone="primary"] {
839
- --nx-tag-tone: var(--nx-primary);
840
- }
841
-
842
- .nx-table__tag[data-tone="success"] {
843
- --nx-tag-tone: var(--nx-success);
844
- }
845
-
846
- .nx-table__tag[data-tone="warning"] {
847
- --nx-tag-tone: var(--nx-warning);
848
- }
849
-
850
- .nx-table__tag[data-tone="danger"] {
851
- --nx-tag-tone: var(--nx-danger);
852
- }
853
-
854
- .nx-table__tag[data-tone="info"] {
855
- --nx-tag-tone: var(--nx-info);
856
- }
857
-
858
- /* A tag after a value (a column's `tag`): "Asia" then "Trashed", a word's space apart. */
859
- .nx-table__tag--after {
860
- margin-inline-start: var(--nx-space-2);
861
- }
862
-
863
- /* Placeholders while the first rows load. */
864
- .nx-table__bone {
865
- display: block;
866
- height: 0.75em;
867
- border-radius: var(--nx-radius-sm);
868
- background: color-mix(in oklab, var(--nx-text) 9%, transparent);
869
- animation: nx-table-pulse 1.6s ease-in-out infinite;
870
- }
871
-
872
- .nx-table__cell--end .nx-table__bone {
873
- margin-left: auto;
874
- }
875
-
876
- @keyframes nx-table-pulse {
877
- 50% {
878
- opacity: 0.45;
879
- }
880
- }
881
-
882
- /* A message across the table: no rows, no matches, a failed load. */
883
- .nx-table__message-cell {
884
- padding: 0;
885
- background: var(--nx-table-bg);
886
- }
887
-
888
- .nx-table__message {
889
- display: flex;
890
- flex-direction: column;
891
- align-items: center;
892
- gap: 4px;
893
- padding: 40px var(--nx-space-4);
894
- text-align: center;
895
- }
896
-
897
- .nx-table__message-icon {
898
- width: 28px;
899
- height: 28px;
900
- margin-bottom: 6px;
901
- color: var(--nx-text-muted);
902
- }
903
-
904
- .nx-table__message[data-tone="danger"] .nx-table__message-icon {
905
- color: var(--nx-danger);
906
- }
907
-
908
- .nx-table__message-title {
909
- margin: 0;
910
- font-size: var(--nx-text-md);
911
- font-weight: 600;
912
- }
913
-
914
- .nx-table__message-detail {
915
- max-width: 36rem;
916
- margin: 0;
917
- color: var(--nx-text-muted);
918
- font-size: var(--nx-text-sm);
919
- }
920
-
921
- .nx-table__message-actions {
922
- display: flex;
923
- flex-wrap: wrap;
924
- justify-content: center;
925
- gap: var(--nx-space-2);
926
- margin-top: var(--nx-space-3);
927
- }
928
-
929
- /* A thin bar under the header while a load runs with rows on screen. The rows dim only if it takes a moment. */
930
- .nx-table__progress {
931
- position: absolute;
932
- top: calc(var(--nx-table-head-all) - 1px);
933
- right: 0;
934
- left: 0;
935
- z-index: 4;
936
- height: 2px;
937
- overflow: hidden;
938
- opacity: 0;
939
- pointer-events: none;
940
- transition: opacity var(--nx-duration);
941
- }
942
-
943
- .nx-table__progress::before {
944
- content: "";
945
- position: absolute;
946
- inset: 0 auto 0 0;
947
- width: 35%;
948
- background: var(--nx-primary);
949
- animation: nx-table-progress 1.1s ease-in-out infinite;
950
- }
951
-
952
- @keyframes nx-table-progress {
953
- from {
954
- transform: translateX(-100%);
955
- }
956
- to {
957
- transform: translateX(290%);
958
- }
959
- }
960
-
961
- .nx-table[data-refreshing] .nx-table__progress {
962
- opacity: 1;
963
- }
964
-
965
- .nx-table__table > tbody {
966
- transition: opacity var(--nx-duration);
967
- }
968
-
969
- .nx-table[data-refreshing] .nx-table__table > tbody {
970
- opacity: 0.55;
971
- transition-delay: 180ms;
972
- }
973
-
974
- /* The selection bar covers the column headings, so the table does not move when it shows. */
975
- .nx-table__selection {
976
- position: absolute;
977
- top: 0;
978
- right: 0;
979
- left: var(--nx-table-index-w, var(--nx-table-select-w));
980
- z-index: 5;
981
- display: flex;
982
- align-items: center;
983
- gap: var(--nx-space-2);
984
- height: calc(var(--nx-table-head-all) - 1px);
985
- padding: 0 var(--nx-space-2) 0 2px;
986
- background: var(--nx-table-selected);
987
- }
988
-
989
- .nx-table__frame:has(.nx-table__selection) .nx-table__heading.nx-table__cell--index {
990
- background: var(--nx-table-selected);
991
- }
992
-
993
- .nx-table__selection-count {
994
- flex-shrink: 0;
995
- padding-right: var(--nx-space-2);
996
- font-size: var(--nx-text-sm);
997
- font-weight: 600;
998
- font-variant-numeric: tabular-nums;
999
- white-space: nowrap;
1000
- }
1001
-
1002
- /* With ways to select by what the rows are, the count is the button that opens them. */
1003
- .nx-table__selection-pick .nx-table__selection-count {
1004
- padding-right: 0;
1005
- }
1006
-
1007
- .nx-table__selection-actions {
1008
- display: flex;
1009
- flex: 1 1 auto;
1010
- align-items: center;
1011
- gap: 2px;
1012
- min-width: 0;
1013
- overflow: hidden;
1014
- }
1015
-
1016
- /* Every action on the selected rows in one split button: the name of the one shown must stay readable. */
1017
- .nx-table__mass {
1018
- min-width: 0;
1019
- }
1020
-
1021
- .nx-table__mass .nx-btn__label,
1022
- .nx-table__mass .nx-split__main .nx-btn__label {
1023
- overflow: hidden;
1024
- text-overflow: ellipsis;
1025
- white-space: nowrap;
1026
- }
1027
-
1028
- .nx-table__selection-clear {
1029
- flex-shrink: 0;
1030
- margin-left: auto;
1031
- }
1032
-
1033
- /* A narrow table keeps the action's name and drops the word "Clear"; it stays for screen readers and in the tooltip. */
1034
- @container nx-table (max-width: 560px) {
1035
- .nx-table__selection-clear.nx-btn {
1036
- width: var(--nx-btn-height);
1037
- padding: 0;
1038
- }
1039
-
1040
- .nx-table__selection-clear .nx-btn__label {
1041
- position: absolute;
1042
- width: 1px;
1043
- height: 1px;
1044
- overflow: hidden;
1045
- clip-path: inset(50%);
1046
- white-space: nowrap;
1047
- }
1048
- }
1049
-
1050
- /* Footer: the frame's bottom band, the range on the left and the pager on the right. */
1051
- .nx-table__footer {
1052
- display: flex;
1053
- flex-wrap: wrap;
1054
- align-items: center;
1055
- justify-content: space-between;
1056
- gap: var(--nx-space-2) var(--nx-space-4);
1057
- min-height: var(--nx-footbar);
1058
- padding: 6px var(--nx-space-2) 6px var(--nx-table-pad-x);
1059
- border-top: 1px solid var(--nx-table-line);
1060
- }
1061
-
1062
- .nx-table__range {
1063
- margin: 0;
1064
- color: var(--nx-text-muted);
1065
- font-size: var(--nx-text-sm);
1066
- font-variant-numeric: tabular-nums;
1067
- }
1068
-
1069
- /* The numbers themselves read as the text colour, so the line is skimmed at a glance. */
1070
- .nx-table__range b {
1071
- color: var(--nx-text);
1072
- font-weight: 600;
1073
- }
1074
-
1075
- .nx-table__page-info {
1076
- color: var(--nx-text-muted);
1077
- font-size: var(--nx-text-sm);
1078
- font-variant-numeric: tabular-nums;
1079
- white-space: nowrap;
1080
- }
1081
-
1082
- .nx-table__paging {
1083
- display: flex;
1084
- flex-wrap: wrap;
1085
- align-items: center;
1086
- gap: var(--nx-space-2);
1087
- margin-left: auto;
1088
- }
1089
-
1090
- .nx-table__page-size {
1091
- color: var(--nx-text-muted);
1092
- font-variant-numeric: tabular-nums;
1093
- }
1094
-
1095
- .nx-table__pager {
1096
- display: flex;
1097
- align-items: center;
1098
- gap: 2px;
1099
- }
1100
-
1101
- .nx-table__page-list {
1102
- display: flex;
1103
- align-items: center;
1104
- gap: 2px;
1105
- }
1106
-
1107
- .nx-table__page {
1108
- min-width: var(--nx-control-sm);
1109
- height: var(--nx-control-sm);
1110
- padding: 0 6px;
1111
- border: 0;
1112
- border-radius: var(--nx-radius);
1113
- background: transparent;
1114
- color: var(--nx-text);
1115
- font: inherit;
1116
- font-size: var(--nx-text-sm);
1117
- font-variant-numeric: tabular-nums;
1118
- cursor: pointer;
1119
- transition: background-color var(--nx-duration);
1120
- }
1121
-
1122
- .nx-table__page:hover:not(:disabled, [aria-current]) {
1123
- background: var(--nx-surface-hover);
1124
- }
1125
-
1126
- .nx-table__page:focus-visible {
1127
- outline: none;
1128
- box-shadow: var(--nx-focus-ring);
1129
- }
1130
-
1131
- .nx-table__page[aria-current] {
1132
- background: var(--nx-primary-solid);
1133
- color: var(--nx-primary-text);
1134
- font-weight: 600;
1135
- cursor: default;
1136
- }
1137
-
1138
- .nx-table__page:disabled {
1139
- opacity: 0.5;
1140
- cursor: default;
1141
- }
1142
-
1143
- .nx-table__page-gap {
1144
- min-width: 20px;
1145
- color: var(--nx-text-muted);
1146
- text-align: center;
1147
- }
1148
-
1149
- /* A narrow table drops the page buttons: "Page 6 of 24" and the arrows are enough. */
1150
- @container nx-table (max-width: 520px) {
1151
- .nx-table__page-list {
1152
- display: none;
1153
- }
1154
- }
1155
-
1156
- [data-nx-motion="reduce"] .nx-table__bone {
1157
- animation: none;
1158
- }
1159
-
1160
- [data-nx-motion="reduce"] .nx-table__row[data-flash] > .nx-table__cell {
1161
- animation: none;
1162
- }
1163
-
1164
- [data-nx-motion="reduce"] .nx-table__row[data-flash] {
1165
- --nx-row-bg: var(--nx-table-flash);
1166
- }
1167
-
1168
- [data-nx-motion="reduce"] .nx-table__progress::before {
1169
- width: 100%;
1170
- animation: none;
1171
- opacity: 0.6;
1172
- }
1173
-
1174
- /*
1175
- * The panels under the table's options button (⋮), Columns and Filters: a head, a body that scrolls when the window
1176
- * is short, and a foot. They share the .nx-popover panel with the menu, and hold controls rather than actions, so a
1177
- * click inside never closes them.
1178
- */
1179
- .nx-table-panel {
1180
- --nx-popover-pad: var(--nx-space-2);
1181
-
1182
- position: fixed;
1183
- inset: auto;
1184
- outline: none;
1185
- }
1186
-
1187
- .nx-table-panel__head {
1188
- display: flex;
1189
- align-items: baseline;
1190
- justify-content: space-between;
1191
- gap: var(--nx-space-3);
1192
- padding: 2px var(--nx-space-2) 6px;
1193
- }
1194
-
1195
- .nx-table-panel__title {
1196
- font-weight: 600;
1197
- }
1198
-
1199
- .nx-table-panel__count {
1200
- color: var(--nx-text-muted);
1201
- font-size: var(--nx-text-xs);
1202
- font-variant-numeric: tabular-nums;
1203
- }
1204
-
1205
- .nx-table-panel__foot {
1206
- display: flex;
1207
- align-items: center;
1208
- justify-content: flex-end;
1209
- margin-top: var(--nx-space-2);
1210
- padding-top: var(--nx-space-2);
1211
- border-top: 1px solid var(--nx-border);
1212
- }
1213
-
1214
- /* The Columns panel: a checkbox per column and a handle to drag it into another place. */
1215
- .nx-columns {
1216
- --nx-columns-row-h: var(--nx-row);
1217
- min-width: 232px;
1218
- max-width: min(320px, calc(100% - 16px));
1219
- }
1220
-
1221
- .nx-columns--sm {
1222
- --nx-columns-row-h: calc(var(--nx-row) - 4px);
1223
- font-size: var(--nx-text-sm);
1224
- }
1225
-
1226
- .nx-columns__list {
1227
- display: flex;
1228
- flex-direction: column;
1229
- margin: 0;
1230
- padding: 0;
1231
- list-style: none;
1232
- }
1233
-
1234
- .nx-columns__row {
1235
- display: flex;
1236
- align-items: center;
1237
- gap: 6px;
1238
- min-height: var(--nx-columns-row-h);
1239
- padding: 0 var(--nx-space-2) 0 2px;
1240
- border: 1px solid transparent;
1241
- border-radius: var(--nx-radius);
1242
- }
1243
-
1244
- .nx-columns__row:hover {
1245
- background: var(--nx-surface-hover);
1246
- }
1247
-
1248
- /* The row being dragged stays in place and goes quiet; a line shows where it would land. */
1249
- .nx-columns__row[data-held] {
1250
- background: var(--nx-surface-hover);
1251
- opacity: 0.6;
1252
- }
1253
-
1254
- .nx-columns__row[data-drop] {
1255
- border-color: var(--nx-primary);
1256
- }
1257
-
1258
- /* A band: its own line, with the columns under it indented along a line that says where the band ends. */
1259
- .nx-columns__row--band {
1260
- display: block;
1261
- padding: 0;
1262
- }
1263
-
1264
- .nx-columns__row--band:hover {
1265
- background: none;
1266
- }
1267
-
1268
- .nx-columns__band {
1269
- display: flex;
1270
- align-items: center;
1271
- gap: 6px;
1272
- min-height: var(--nx-columns-row-h);
1273
- padding: 0 var(--nx-space-2) 0 2px;
1274
- border-radius: var(--nx-radius);
1275
- }
1276
-
1277
- .nx-columns__band:hover {
1278
- background: var(--nx-surface-hover);
1279
- }
1280
-
1281
- .nx-columns__band .nx-columns__label {
1282
- font-weight: 600;
1283
- }
1284
-
1285
- .nx-columns__band-count {
1286
- flex: none;
1287
- color: var(--nx-text-subtle);
1288
- font-size: var(--nx-text-xs);
1289
- font-variant-numeric: tabular-nums;
1290
- }
1291
-
1292
- .nx-columns__children {
1293
- margin: 0 0 2px 13px;
1294
- padding: 0 0 0 9px;
1295
- border-left: 1px solid var(--nx-border);
1296
- list-style: none;
1297
- }
1298
-
1299
- /* A column whose band is hidden stays readable but plainly off. */
1300
- .nx-columns__row--child:has(input:disabled) .nx-columns__label {
1301
- color: var(--nx-text-subtle);
1302
- }
1303
-
1304
- .nx-columns__handle {
1305
- display: inline-flex;
1306
- flex: none;
1307
- align-items: center;
1308
- justify-content: center;
1309
- width: 20px;
1310
- height: var(--nx-columns-row-h);
1311
- padding: 0;
1312
- border: 0;
1313
- border-radius: var(--nx-radius-sm);
1314
- background: none;
1315
- color: var(--nx-text-subtle);
1316
- cursor: grab;
1317
- touch-action: none;
1318
- }
1319
-
1320
- .nx-columns__handle:hover {
1321
- color: var(--nx-text);
1322
- }
1323
-
1324
- .nx-columns__handle:focus-visible {
1325
- outline: none;
1326
- box-shadow: var(--nx-focus-ring);
1327
- }
1328
-
1329
- .nx-columns[data-dragging] .nx-columns__handle {
1330
- cursor: grabbing;
1331
- }
1332
-
1333
- .nx-columns__handle--fixed {
1334
- cursor: default;
1335
- }
1336
-
1337
- .nx-columns__choice {
1338
- flex: 1 1 auto;
1339
- min-width: 0;
1340
- padding: 2px 0;
1341
- }
1342
-
1343
- .nx-columns__label {
1344
- overflow: hidden;
1345
- text-overflow: ellipsis;
1346
- white-space: nowrap;
1347
- }
1348
-
1349
- .nx-columns__pin {
1350
- display: inline-flex;
1351
- flex: none;
1352
- color: var(--nx-text-subtle);
1353
- }
1354
-
1355
- .nx-columns__pin .nx-icon {
1356
- width: 14px;
1357
- height: 14px;
1358
- }
1359
-
1360
- .nx-columns__note {
1361
- margin: 6px 0 0;
1362
- padding: 0 var(--nx-space-2);
1363
- color: var(--nx-text-muted);
1364
- font-size: var(--nx-text-xs);
1365
- line-height: 1.4;
1366
- }
1367
-
1368
- /* The Filters panel: one column of small inputs (the form's grid spans every cell this narrow), and the rows left. */
1369
- .nx-table-filters {
1370
- --nx-form-row-gap: var(--nx-space-3);
1371
- width: min(22rem, calc(100vw - 16px));
1372
- font-size: var(--nx-text-sm);
1373
- }
1374
-
1375
- /* In line with the head, with room for a focus ring inside the scroller. */
1376
- .nx-table-filters__form {
1377
- padding: 2px var(--nx-space-2);
1378
- }
1379
-
1380
- .nx-table-filters__rows {
1381
- margin-right: auto;
1382
- padding-left: var(--nx-space-2);
1383
- color: var(--nx-text-muted);
1384
- font-size: var(--nx-text-xs);
1385
- font-variant-numeric: tabular-nums;
1386
- }
1387
- }
1
+ @layer nexus.components {
2
+ /*
3
+ * Data table: a bar (tabs, search, buttons), a frame holding the table (a header that stays while rows scroll, a
4
+ * checkbox column on the left and the actions on the right that stay while columns scroll), and a footer with the
5
+ * range and the pager. While rows are selected, a bar with their actions covers the column headings.
6
+ */
7
+ .nx-table {
8
+ --nx-table-bg: var(--nx-surface);
9
+ --nx-table-head-bg: color-mix(in oklab, var(--nx-text) 4%, var(--nx-surface));
10
+ --nx-table-hover: color-mix(in oklab, var(--nx-text) 3%, var(--nx-surface));
11
+ --nx-table-selected: color-mix(in oklab, var(--nx-primary) 11%, var(--nx-surface));
12
+ --nx-table-selected-hover: color-mix(in oklab, var(--nx-primary) 16%, var(--nx-surface));
13
+ --nx-table-flash: color-mix(in oklab, var(--nx-success) 18%, var(--nx-surface));
14
+ --nx-table-line: var(--nx-border);
15
+ --nx-table-head-h: calc(var(--nx-row) + 8px);
16
+ /* The band of group headings above the columns' own, when any column names a group. */
17
+ --nx-table-group-h: 30px;
18
+ /* How many rows the header has: 1, or 2 while a band stands above the headings (`data-head-rows`). */
19
+ --nx-table-head-rows: 1;
20
+ /* The whole header, which the loading bar and the selection bar are measured from. */
21
+ --nx-table-head-all: calc(var(--nx-table-head-h) + (var(--nx-table-head-rows) - 1) * var(--nx-table-group-h));
22
+ --nx-table-row-h: calc(var(--nx-row) + 12px);
23
+ --nx-table-pad-x: 12px;
24
+ --nx-table-select-w: 44px;
25
+ --nx-table-font: var(--nx-text-md);
26
+ --nx-table-edge: light-dark(rgb(16 24 40 / 0.14), rgb(0 0 0 / 0.55));
27
+
28
+ container: nx-table / inline-size;
29
+ display: flex;
30
+ flex-direction: column;
31
+ min-width: 0;
32
+ overflow: clip;
33
+ /* One frame round the bar, the rows and the footer. */
34
+ border: 1px solid var(--nx-table-line);
35
+ border-radius: var(--nx-radius-lg);
36
+ background: var(--nx-table-bg);
37
+ box-shadow: var(--nx-shadow-sm);
38
+ color: var(--nx-text);
39
+ }
40
+
41
+ .nx-table[data-head-rows="2"] {
42
+ --nx-table-head-rows: 2;
43
+ }
44
+
45
+ .nx-table--sm {
46
+ --nx-table-head-h: calc(var(--nx-row) + 2px);
47
+ --nx-table-group-h: 26px;
48
+ --nx-table-row-h: calc(var(--nx-row) + 4px);
49
+ --nx-table-pad-x: 10px;
50
+ --nx-table-select-w: 38px;
51
+ --nx-table-font: var(--nx-text-sm);
52
+ }
53
+
54
+ /*
55
+ * The bar, and a line under it. One line on a wide table: the Add section (the table's Add and the page's own
56
+ * buttons) and the lists on the left; Reload, the options (⋮) and the search, rows per page inside it, on the right.
57
+ * A narrower table moves whole parts onto lines of their own, never a lone item: the search takes the top line
58
+ * under 670px (with nothing on the left, the buttons' line), Reload goes into the options menu under 375px, and,
59
+ * beside lists, the Add section takes the last line under 325px. On the admin shell, whose menu leaves the screen
60
+ * at 900px, those are windows of 695, 400 and 350px. The lists show their labels from 900px (TabButtons
61
+ * `labels="auto"`), where the line holds them with room to spare.
62
+ */
63
+ .nx-table__bar {
64
+ display: flex;
65
+ flex-wrap: wrap;
66
+ align-items: center;
67
+ gap: var(--nx-space-2);
68
+ margin: 6px var(--nx-space-2);
69
+ }
70
+
71
+ .nx-table__bar:not(:has(:not(.nx-table__bar-end):not(:empty))) {
72
+ display: none;
73
+ }
74
+
75
+ /* What the rows are (`caption`): a line over the bar, inside the frame, so a table beside another starts level with it. */
76
+ .nx-table__caption {
77
+ margin: var(--nx-space-2) var(--nx-space-3) 0;
78
+ color: var(--nx-text-muted);
79
+ font-size: var(--nx-text-md);
80
+ line-height: 1.5;
81
+ overflow-wrap: anywhere;
82
+ }
83
+
84
+ .nx-table__bar:has(:not(.nx-table__bar-end):not(:empty)) + .nx-table__frame {
85
+ border-top: 1px solid var(--nx-table-line);
86
+ }
87
+
88
+ .nx-table__bar-add,
89
+ .nx-table__bar-start,
90
+ .nx-table__bar-end {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: var(--nx-space-2);
94
+ min-width: 0;
95
+ }
96
+
97
+ /* A page's own tabs and filters may run long: they wrap inside their part. */
98
+ .nx-table__bar-start {
99
+ flex-wrap: wrap;
100
+ }
101
+
102
+ /*
103
+ * The Add section's items keep their own widths on a shared line: none shrinks to its longest word (a switch's
104
+ * label) or runs into the lists, so a bar that does not fit takes its next step instead. On the line the section
105
+ * takes of its own (below) they share it, and one that would not fit its share takes a line of its own, whole. The
106
+ * Add is `block` for that line; beside other parts it is as wide as its label.
107
+ */
108
+ .nx-table__bar-add > * {
109
+ flex: none;
110
+ min-width: fit-content;
111
+ }
112
+
113
+ .nx-table__bar-add > :is(.nx-btn--block, .nx-split--block) {
114
+ width: auto;
115
+ }
116
+
117
+ /* A part whose page content renders nothing (an empty fragment) is no part: no room, no gap, no step. */
118
+ .nx-table__bar-add:empty,
119
+ .nx-table__bar-start:empty {
120
+ display: none;
121
+ }
122
+
123
+ /* The right side takes the room left on its line, so it ends at the bar's end. */
124
+ .nx-table__bar-end {
125
+ margin-inline-start: auto;
126
+ }
127
+
128
+ /* 19rem where there is room. A line breaks on the 12rem basis, so the search narrows before the bar takes a line. */
129
+ .nx-table__search {
130
+ flex: 1 1 12rem;
131
+ min-width: 0;
132
+ max-width: 19rem;
133
+ }
134
+
135
+ /* Rows per page inside the search box, at its start, with a line between the two. */
136
+ .nx-table__search .nx-field__actions--start {
137
+ align-self: stretch;
138
+ padding-right: 0;
139
+ padding-left: 2px;
140
+ border-right: 1px solid var(--nx-field-border);
141
+ }
142
+
143
+ .nx-table__page-size--field.nx-btn {
144
+ min-width: 0;
145
+ height: calc(var(--nx-field-height) - 8px);
146
+ gap: 0;
147
+ padding: 0 2px 0 4px;
148
+ color: var(--nx-text-muted);
149
+ font-variant-numeric: tabular-nums;
150
+ }
151
+
152
+ .nx-table__page-size--field .nx-icon {
153
+ width: 14px;
154
+ height: 14px;
155
+ }
156
+
157
+ .nx-table__page-size--field:hover {
158
+ color: var(--nx-text);
159
+ }
160
+
161
+ /* The search comes first and takes the room: the top line beside an Add section or lists, else the buttons' line. */
162
+ @container nx-table (width < 670px) {
163
+ .nx-table__search {
164
+ order: -1;
165
+ max-width: none;
166
+ }
167
+
168
+ .nx-table__bar:has(> :is(.nx-table__bar-add, .nx-table__bar-start):not(:empty)) > .nx-table__search {
169
+ flex-basis: 100%;
170
+ }
171
+ }
172
+
173
+ /* DataTable reads this to offer Reload in the options menu. */
174
+ @container nx-table (width < 375px) {
175
+ .nx-table__reload {
176
+ display: none;
177
+ }
178
+ }
179
+
180
+ /*
181
+ * Beside lists, the Add section goes last and takes its line. Its buttons share it equally (the Add is a `block`
182
+ * button, so its label stays centred), or each takes a line of its own when they do not fit side by side.
183
+ */
184
+ @container nx-table (width < 325px) {
185
+ .nx-table__bar:has(> .nx-table__bar-start:not(:empty)) > .nx-table__bar-add {
186
+ flex-basis: 100%;
187
+ flex-wrap: wrap;
188
+ order: 1;
189
+ }
190
+
191
+ .nx-table__bar:has(> .nx-table__bar-start:not(:empty)) > .nx-table__bar-add > * {
192
+ flex: 1 1 0;
193
+ }
194
+ }
195
+
196
+ /*
197
+ * The same steps for a bar whose parts are wider than the widths above expect (a long Add label, the page's own
198
+ * buttons, cozy density): DataTable squeezes it (`data-squeeze`) one step at a time, each keeping the ones before.
199
+ */
200
+ .nx-table__bar[data-squeeze] > .nx-table__search {
201
+ order: -1;
202
+ max-width: none;
203
+ }
204
+
205
+ .nx-table__bar[data-squeeze]:has(> :is(.nx-table__bar-add, .nx-table__bar-start):not(:empty)) > .nx-table__search {
206
+ flex-basis: 100%;
207
+ }
208
+
209
+ .nx-table__bar[data-squeeze~="reload"] .nx-table__reload {
210
+ display: none;
211
+ }
212
+
213
+ .nx-table__bar[data-squeeze~="add"] > .nx-table__bar-add {
214
+ flex-basis: 100%;
215
+ flex-wrap: wrap;
216
+ order: 1;
217
+ }
218
+
219
+ .nx-table__bar[data-squeeze~="add"] > .nx-table__bar-add > * {
220
+ flex: 1 1 0;
221
+ }
222
+
223
+ /*
224
+ * `fill`: the table takes the height it is given instead of growing with its rows. The bar and the footer keep their
225
+ * own height, the frame takes the rest, and the rows scroll inside it. Nothing below the table moves when a page
226
+ * holds fewer rows, and the page behind it never gains a scrollbar.
227
+ */
228
+ .nx-table--fill {
229
+ height: 100%;
230
+ min-height: 0;
231
+ }
232
+
233
+ .nx-table--fill > .nx-table__frame {
234
+ display: flex;
235
+ flex: 1 1 auto;
236
+ flex-direction: column;
237
+ min-height: 0;
238
+ }
239
+
240
+ .nx-table--fill .nx-table__scroll {
241
+ flex: 1 1 auto;
242
+ min-height: 0;
243
+ /* The room for the scrollbar is kept, so the headings do not shift the moment the rows need one. */
244
+ scrollbar-gutter: stable;
245
+ }
246
+
247
+ /* The rows' part of the frame: the selection bar and the loading bar are placed in it. */
248
+ .nx-table__frame {
249
+ position: relative;
250
+ min-width: 0;
251
+ overflow: hidden;
252
+ }
253
+
254
+ .nx-table__scroll {
255
+ overflow: auto;
256
+ overscroll-behavior-x: contain;
257
+ scrollbar-width: thin;
258
+ scrollbar-color: color-mix(in oklab, var(--nx-text-subtle) 75%, transparent) transparent;
259
+ }
260
+
261
+ .nx-table__table {
262
+ width: 100%;
263
+ border-collapse: separate;
264
+ border-spacing: 0;
265
+ font-size: var(--nx-table-font);
266
+ line-height: 1.45;
267
+ }
268
+
269
+ /* Cells */
270
+ .nx-table__cell,
271
+ .nx-table__heading {
272
+ box-sizing: border-box;
273
+ padding: 4px var(--nx-table-pad-x);
274
+ text-align: start;
275
+ vertical-align: middle;
276
+ }
277
+
278
+ .nx-table__cell {
279
+ height: var(--nx-table-row-h);
280
+ border-bottom: 1px solid var(--nx-table-line);
281
+ background: var(--nx-row-bg);
282
+ }
283
+
284
+ .nx-table__row {
285
+ --nx-row-bg: var(--nx-table-bg);
286
+ }
287
+
288
+ /* The last row's line would double the frame. */
289
+ .nx-table__row:last-child > .nx-table__cell {
290
+ border-bottom: 0;
291
+ }
292
+
293
+ .nx-table__row:not(.nx-table__row--message, .nx-table__row--skeleton):hover {
294
+ --nx-row-bg: var(--nx-table-hover);
295
+ }
296
+
297
+ /*
298
+ * A row that reads strong (`rowStrong`): an unread thread. Its cells' text heavier and in the text colour; nothing
299
+ * else, so the row keeps its height and reads plain again once it is read.
300
+ */
301
+ .nx-table__row[data-strong] > .nx-table__cell {
302
+ color: var(--nx-text);
303
+ font-weight: 600;
304
+ }
305
+
306
+ /* A row a click opens or picks says so under the pointer. Only the pointer changes: nothing in the row moves on hover. */
307
+ .nx-table__row:is([data-open], [data-pick]) {
308
+ cursor: pointer;
309
+ }
310
+
311
+ /* A selected row, and the picked one (`onPick`): the same tint, which the checkbox or the list beside tells apart. */
312
+ .nx-table__row:is([data-selected], [data-picked]) {
313
+ --nx-row-bg: var(--nx-table-selected);
314
+ }
315
+
316
+ .nx-table__row:is([data-selected], [data-picked]):hover {
317
+ --nx-row-bg: var(--nx-table-selected-hover);
318
+ }
319
+
320
+ /* Muted text on a selected row's tint moves toward the text color, so it keeps 4.5:1 in every theme. */
321
+ .nx-table__row:is([data-selected], [data-picked]) :is(.nx-table__number, .nx-table__dash, .nx-table__no) {
322
+ color: color-mix(in oklab, var(--nx-text-muted), var(--nx-text) 45%);
323
+ }
324
+
325
+ /*
326
+ * The keyboard's focus on a row's radio button (`onPick`), which is out of sight: a ring round the row, drawn on its
327
+ * cells as the tree's rows draw theirs, so a pinned cell carries its part of it.
328
+ */
329
+ .nx-table__row:has(.nx-table__pick:focus-visible) > .nx-table__cell {
330
+ box-shadow:
331
+ inset 0 2px 0 var(--nx-primary),
332
+ inset 0 -2px 0 var(--nx-primary);
333
+ }
334
+
335
+ .nx-table__row:has(.nx-table__pick:focus-visible) > .nx-table__cell:first-child {
336
+ box-shadow:
337
+ inset 2px 0 0 var(--nx-primary),
338
+ inset 0 2px 0 var(--nx-primary),
339
+ inset 0 -2px 0 var(--nx-primary);
340
+ }
341
+
342
+ .nx-table__row:has(.nx-table__pick:focus-visible) > .nx-table__cell:last-child {
343
+ box-shadow:
344
+ inset -2px 0 0 var(--nx-primary),
345
+ inset 0 2px 0 var(--nx-primary),
346
+ inset 0 -2px 0 var(--nx-primary);
347
+ }
348
+
349
+ /* Columns scroll under the pinned ones: a soft shadow shows there is more on that side. */
350
+ .nx-table__frame[data-scroll-start] {
351
+ --nx-table-start-edge: 8px 0 8px -8px var(--nx-table-edge);
352
+ }
353
+
354
+ .nx-table__frame[data-scroll-end] {
355
+ --nx-table-end-edge: -8px 0 8px -8px var(--nx-table-edge);
356
+ }
357
+
358
+ .nx-table__row[data-busy] > .nx-table__cell:not(.nx-table__cell--actions) {
359
+ color: var(--nx-text-muted);
360
+ }
361
+
362
+ /* A row just added or saved: a green wash that fades (a steady one with reduced motion, until the mark goes). */
363
+ .nx-table__row[data-flash] > .nx-table__cell {
364
+ animation: nx-table-flash 1600ms ease-out;
365
+ }
366
+
367
+ @keyframes nx-table-flash {
368
+ from {
369
+ background-color: var(--nx-table-flash);
370
+ }
371
+ }
372
+
373
+ .nx-table__cell--start {
374
+ text-align: start;
375
+ }
376
+
377
+ .nx-table__cell--center {
378
+ text-align: center;
379
+ }
380
+
381
+ .nx-table__cell--end {
382
+ text-align: end;
383
+ font-variant-numeric: tabular-nums;
384
+ }
385
+
386
+ /* A value ends in "…" at the column's width (24rem when the column sets none). */
387
+ .nx-table__value {
388
+ display: block;
389
+ max-width: var(--nx-col-max, 24rem);
390
+ overflow: hidden;
391
+ text-overflow: ellipsis;
392
+ white-space: nowrap;
393
+ }
394
+
395
+ .nx-table__cell--wrap .nx-table__value {
396
+ max-width: var(--nx-col-max, 32rem);
397
+ white-space: normal;
398
+ overflow-wrap: anywhere;
399
+ }
400
+
401
+ .nx-table__cell--end .nx-table__value {
402
+ margin-left: auto;
403
+ }
404
+
405
+ .nx-table__cell--center .nx-table__value {
406
+ margin-inline: auto;
407
+ }
408
+
409
+ /*
410
+ * Pinned columns stay while the others scroll: the numbers and checkbox column and the actions always, and
411
+ * any column the user pins from its menu. Their distance from the edge is measured from the headings before them.
412
+ */
413
+ .nx-table__cell[data-pin],
414
+ .nx-table__heading[data-pin] {
415
+ position: sticky;
416
+ z-index: 1;
417
+ background: var(--nx-row-bg);
418
+ }
419
+
420
+ .nx-table__heading[data-pin] {
421
+ z-index: 3;
422
+ background: var(--nx-table-head-bg);
423
+ }
424
+
425
+ /* The last pinned column of a side carries the shadow the others scroll under. */
426
+ .nx-table__cell[data-pin="left"][data-pin-edge],
427
+ .nx-table__heading[data-pin="left"][data-pin-edge] {
428
+ box-shadow: var(--nx-table-start-edge, 0 0 #0000);
429
+ }
430
+
431
+ .nx-table__cell[data-pin="right"][data-pin-edge],
432
+ .nx-table__heading[data-pin="right"][data-pin-edge] {
433
+ box-shadow: var(--nx-table-end-edge, 0 0 #0000);
434
+ }
435
+
436
+ /* The first column: the row's number, with its checkbox in the same place. */
437
+ .nx-table__cell--index {
438
+ width: var(--nx-table-select-w);
439
+ min-width: var(--nx-table-select-w);
440
+ padding: 0;
441
+ text-align: center;
442
+ }
443
+
444
+ .nx-table__number {
445
+ display: inline-flex;
446
+ align-items: center;
447
+ justify-content: center;
448
+ width: 100%;
449
+ color: var(--nx-text-muted);
450
+ font-size: var(--nx-text-xs);
451
+ font-variant-numeric: tabular-nums;
452
+ font-weight: 500;
453
+ }
454
+
455
+ .nx-table__check {
456
+ display: inline-flex;
457
+ align-items: center;
458
+ justify-content: center;
459
+ width: 100%;
460
+ height: 100%;
461
+ min-height: 28px;
462
+ }
463
+
464
+ .nx-table__check .nx-choice__control {
465
+ margin: 0;
466
+ }
467
+
468
+ /* Both in the one cell: the number, until the row is under the pointer or anything is selected. */
469
+ .nx-table__cell--index[data-swap] > .nx-table__number,
470
+ .nx-table__cell--index[data-swap] > .nx-table__check {
471
+ position: absolute;
472
+ inset: 0;
473
+ width: auto;
474
+ transition: opacity var(--nx-duration);
475
+ }
476
+
477
+ .nx-table__cell--index[data-swap] > .nx-table__check {
478
+ opacity: 0;
479
+ }
480
+
481
+ .nx-table__row:hover > .nx-table__cell--index[data-swap] > .nx-table__check,
482
+ /* The heading's own cell may stand in the band row, so the whole header counts as hovering it. */
483
+ .nx-table__thead:hover .nx-table__cell--index[data-swap] > .nx-table__check,
484
+ .nx-table__cell--index[data-swap]:focus-within > .nx-table__check,
485
+ .nx-table[data-selecting] .nx-table__cell--index[data-swap] > .nx-table__check {
486
+ opacity: 1;
487
+ }
488
+
489
+ .nx-table__row:hover > .nx-table__cell--index[data-swap] > .nx-table__number,
490
+ .nx-table__thead:hover .nx-table__cell--index[data-swap] > .nx-table__number,
491
+ .nx-table__cell--index[data-swap]:focus-within > .nx-table__number,
492
+ .nx-table[data-selecting] .nx-table__cell--index[data-swap] > .nx-table__number {
493
+ opacity: 0;
494
+ }
495
+
496
+ /* A touch screen has no pointer to hover with, so the checkbox stays. */
497
+ @media (hover: none) {
498
+ .nx-table__cell--index[data-swap] > .nx-table__check {
499
+ opacity: 1;
500
+ }
501
+
502
+ .nx-table__cell--index[data-swap] > .nx-table__number {
503
+ opacity: 0;
504
+ }
505
+ }
506
+
507
+ /*
508
+ * The sections above the page: a row across the table whose button folds a set of the user's records open and shut -
509
+ * the ones they pinned, their favourites - and the rows themselves, which show in the list below as well. A section's
510
+ * row wears the section's icon where the rows of the page show their number.
511
+ */
512
+ .nx-table__section-cell {
513
+ padding: 0;
514
+ border-bottom: 1px solid var(--nx-table-line);
515
+ background: var(--nx-table-head-bg);
516
+ }
517
+
518
+ .nx-table__section-toggle {
519
+ display: flex;
520
+ align-items: center;
521
+ gap: var(--nx-space-2);
522
+ width: 100%;
523
+ min-height: var(--nx-table-head-h);
524
+ padding: 0 var(--nx-table-pad-x);
525
+ border: 0;
526
+ background: none;
527
+ color: var(--nx-text);
528
+ font: inherit;
529
+ font-size: var(--nx-table-font);
530
+ font-weight: 600;
531
+ text-align: start;
532
+ cursor: pointer;
533
+ transition: background-color var(--nx-duration);
534
+ }
535
+
536
+ .nx-table__section-toggle:hover {
537
+ background: color-mix(in oklab, var(--nx-text) 5%, transparent);
538
+ }
539
+
540
+ .nx-table__section-toggle:focus-visible {
541
+ outline: none;
542
+ box-shadow: inset 0 0 0 2px var(--nx-primary);
543
+ }
544
+
545
+ .nx-table__section-caret,
546
+ .nx-table__section-icon,
547
+ .nx-table__mark {
548
+ flex-shrink: 0;
549
+ width: 15px;
550
+ height: 15px;
551
+ color: var(--nx-text-muted);
552
+ }
553
+
554
+ /* A row's marks, where its number would be: one under the other, in the line a single mark takes. */
555
+ .nx-table__marks {
556
+ display: inline-flex;
557
+ flex-direction: column;
558
+ align-items: center;
559
+ gap: 2px;
560
+ }
561
+
562
+ .nx-table__section-caret {
563
+ transition: transform var(--nx-duration);
564
+ }
565
+
566
+ .nx-table__section[data-open] .nx-table__section-caret {
567
+ transform: rotate(90deg);
568
+ }
569
+
570
+ .nx-table__section-count {
571
+ color: var(--nx-text-muted);
572
+ font-weight: 400;
573
+ font-variant-numeric: tabular-nums;
574
+ }
575
+
576
+ .nx-table__section-loading {
577
+ width: 14px;
578
+ height: 14px;
579
+ color: var(--nx-text-muted);
580
+ }
581
+
582
+ [data-nx-motion="reduce"] .nx-table__section-caret,
583
+ [data-nx-motion="reduce"] .nx-table__section-toggle {
584
+ transition: none;
585
+ }
586
+
587
+ .nx-table__cell--actions {
588
+ width: 1px;
589
+ padding-inline: 4px 8px;
590
+ white-space: nowrap;
591
+ }
592
+
593
+ /*
594
+ * Once the user has given a column a width, the table lays its columns out from the list of widths, so dragging one
595
+ * edge moves that edge only. The filler column takes whatever space the others leave.
596
+ */
597
+ .nx-table[data-sized] .nx-table__table {
598
+ table-layout: fixed;
599
+ }
600
+
601
+ .nx-table__cell--filler {
602
+ padding: 0;
603
+ background: var(--nx-row-bg);
604
+ }
605
+
606
+ .nx-table__heading.nx-table__cell--filler {
607
+ background: var(--nx-table-head-bg);
608
+ }
609
+
610
+ .nx-table__actions {
611
+ display: flex;
612
+ align-items: center;
613
+ justify-content: flex-end;
614
+ gap: 2px;
615
+ }
616
+
617
+ /* Header: stays at the top while rows scroll. */
618
+ .nx-table__heading {
619
+ position: sticky;
620
+ top: 0;
621
+ z-index: 2;
622
+ height: var(--nx-table-head-h);
623
+ border-bottom: 1px solid var(--nx-table-line);
624
+ background: var(--nx-table-head-bg);
625
+ color: var(--nx-text);
626
+ font-size: var(--nx-text-xs);
627
+ font-weight: 650;
628
+ white-space: nowrap;
629
+ }
630
+
631
+ /* A heading that holds the sort button, the column's menu, and the edge to drag keeps no padding of its own. */
632
+ .nx-table__heading:has(> .nx-table__heading-inner) {
633
+ padding: 0;
634
+ }
635
+
636
+ /*
637
+ * Columns in bands: a group's heading spans the columns next to each other that name it, and the columns with no
638
+ * group keep one heading as tall as both rows. Both rows stay while the rows scroll, the band on top.
639
+ */
640
+ .nx-table__head--leaves > .nx-table__heading {
641
+ top: var(--nx-table-group-h);
642
+ }
643
+
644
+ .nx-table__heading--group {
645
+ height: var(--nx-table-group-h);
646
+ border-bottom: 0;
647
+ text-align: center;
648
+ }
649
+
650
+ /* The line under a band stops short of its edges, so two bands beside each other read as two, not as one rule. */
651
+ .nx-table__heading--group::after {
652
+ content: "";
653
+ position: absolute;
654
+ right: var(--nx-table-pad-x);
655
+ bottom: 0;
656
+ left: var(--nx-table-pad-x);
657
+ height: 1px;
658
+ background: var(--nx-table-line);
659
+ }
660
+
661
+ .nx-table__group {
662
+ display: block;
663
+ overflow: hidden;
664
+ color: var(--nx-text);
665
+ font-size: var(--nx-text-xs);
666
+ font-weight: 700;
667
+ letter-spacing: 0.04em;
668
+ text-overflow: ellipsis;
669
+ text-transform: uppercase;
670
+ }
671
+
672
+ .nx-table__heading-inner {
673
+ display: flex;
674
+ align-items: center;
675
+ min-width: 0;
676
+ height: 100%;
677
+ }
678
+
679
+ .nx-table__heading-label {
680
+ min-width: 0;
681
+ overflow: hidden;
682
+ text-overflow: ellipsis;
683
+ }
684
+
685
+ .nx-table__heading-inner > .nx-table__heading-label {
686
+ flex: 1 1 auto;
687
+ padding: 0 var(--nx-table-pad-x);
688
+ }
689
+
690
+ /* A sortable heading is one button across its cell, beside the column's menu. */
691
+ .nx-table__sort {
692
+ display: flex;
693
+ flex: 1 1 auto;
694
+ align-items: center;
695
+ gap: 6px;
696
+ min-width: 0;
697
+ height: 100%;
698
+ padding: 0 4px 0 var(--nx-table-pad-x);
699
+ border: 0;
700
+ background: transparent;
701
+ color: inherit;
702
+ font: inherit;
703
+ text-align: inherit;
704
+ cursor: pointer;
705
+ transition: background-color var(--nx-duration);
706
+ }
707
+
708
+ .nx-table__cell--end .nx-table__sort {
709
+ flex-direction: row-reverse;
710
+ padding: 0 var(--nx-table-pad-x) 0 4px;
711
+ }
712
+
713
+ .nx-table__cell--center .nx-table__sort {
714
+ justify-content: center;
715
+ }
716
+
717
+ /* The column's menu (pin, move, fit, hide): at the end of the heading, and shown while it is hovered or focused. */
718
+ .nx-table__col-menu.nx-btn {
719
+ flex: 0 0 auto;
720
+ width: 20px;
721
+ min-width: 20px;
722
+ height: calc(var(--nx-table-head-h) - 12px);
723
+ margin-right: 2px;
724
+ padding: 0;
725
+ border-radius: var(--nx-radius-sm);
726
+ opacity: 0;
727
+ transition: opacity var(--nx-duration);
728
+ }
729
+
730
+ .nx-table__col-menu .nx-icon {
731
+ width: 14px;
732
+ height: 14px;
733
+ }
734
+
735
+ .nx-table__heading:hover .nx-table__col-menu,
736
+ .nx-table__col-menu:is(:focus, :focus-visible),
737
+ .nx-table__col-menu[aria-expanded="true"] {
738
+ opacity: 1;
739
+ }
740
+
741
+ @media (hover: none) {
742
+ .nx-table__col-menu.nx-btn {
743
+ opacity: 1;
744
+ }
745
+ }
746
+
747
+ /* The edge of a heading: drag it for the column's width, double-click it to fit the content. */
748
+ .nx-table__resizer {
749
+ position: absolute;
750
+ top: 0;
751
+ right: -2px;
752
+ z-index: 4;
753
+ width: 8px;
754
+ height: 100%;
755
+ touch-action: none;
756
+ cursor: col-resize;
757
+ }
758
+
759
+ .nx-table__resizer::after {
760
+ content: "";
761
+ position: absolute;
762
+ top: 7px;
763
+ bottom: 7px;
764
+ left: 3px;
765
+ width: 2px;
766
+ border-radius: 1px;
767
+ background: transparent;
768
+ transition: background-color var(--nx-duration);
769
+ }
770
+
771
+ .nx-table__heading:hover > .nx-table__resizer::after,
772
+ .nx-table__resizer[data-active]::after {
773
+ background: var(--nx-primary);
774
+ }
775
+
776
+ .nx-table__resizer[data-active]::after {
777
+ top: 0;
778
+ bottom: 0;
779
+ }
780
+
781
+ /* Dragging a heading to another place: the heading being moved, and a line where it will land. */
782
+ .nx-table__heading[data-dragging] {
783
+ background: color-mix(in oklab, var(--nx-primary) 14%, var(--nx-table-head-bg));
784
+ }
785
+
786
+ .nx-table__heading[data-drop-before]::before,
787
+ .nx-table__heading[data-drop-after]::after {
788
+ content: "";
789
+ position: absolute;
790
+ top: 0;
791
+ bottom: 0;
792
+ z-index: 5;
793
+ width: 2px;
794
+ background: var(--nx-primary);
795
+ }
796
+
797
+ .nx-table__heading[data-drop-before]::before {
798
+ left: 0;
799
+ }
800
+
801
+ .nx-table__heading[data-drop-after]::after {
802
+ right: 0;
803
+ }
804
+
805
+ .nx-table[data-col-drag] .nx-table__head {
806
+ user-select: none;
807
+ }
808
+
809
+ .nx-table[data-col-drag] :is(.nx-table__heading, .nx-table__sort, .nx-table__col-menu, .nx-table__resizer) {
810
+ cursor: grabbing;
811
+ }
812
+
813
+ .nx-table[data-col-resize] :is(.nx-table__heading, .nx-table__sort, .nx-table__cell) {
814
+ cursor: col-resize;
815
+ }
816
+
817
+ .nx-table__sort:hover:not(:disabled) {
818
+ background: color-mix(in oklab, var(--nx-text) 5%, transparent);
819
+ }
820
+
821
+ .nx-table__sort:focus-visible {
822
+ outline: none;
823
+ box-shadow: inset 0 0 0 2px var(--nx-primary);
824
+ }
825
+
826
+ .nx-table__sort:disabled {
827
+ cursor: default;
828
+ }
829
+
830
+ .nx-table__sort-icon {
831
+ flex-shrink: 0;
832
+ width: 14px;
833
+ height: 14px;
834
+ color: var(--nx-text-muted);
835
+ opacity: 0;
836
+ transition: opacity var(--nx-duration);
837
+ }
838
+
839
+ .nx-table__sort[data-sorted] .nx-table__sort-icon {
840
+ color: var(--nx-text);
841
+ opacity: 1;
842
+ }
843
+
844
+ .nx-table__sort:is(:hover, :focus-visible):not(:disabled) .nx-table__sort-icon {
845
+ opacity: 1;
846
+ }
847
+
848
+ @media (hover: none) {
849
+ .nx-table__sort:not(:disabled) .nx-table__sort-icon {
850
+ opacity: 0.7;
851
+ }
852
+ }
853
+
854
+ /* Values */
855
+ .nx-table__dash,
856
+ .nx-table__no {
857
+ color: var(--nx-text-muted);
858
+ }
859
+
860
+ .nx-table__yes {
861
+ display: inline-flex;
862
+ color: var(--nx-success);
863
+ }
864
+
865
+ .nx-table__yes .nx-icon {
866
+ width: 16px;
867
+ height: 16px;
868
+ }
869
+
870
+ /* An option with a tone: a tag with a dot of its color; the text keeps the text color, readable on every tint. */
871
+ .nx-table__tag {
872
+ --nx-tag-tone: var(--nx-text-muted);
873
+ display: inline-flex;
874
+ align-items: center;
875
+ gap: 6px;
876
+ max-width: 100%;
877
+ padding: 1px 8px;
878
+ border-radius: var(--nx-radius-full);
879
+ background: color-mix(in oklab, var(--nx-tag-tone) 14%, var(--nx-surface));
880
+ color: var(--nx-text);
881
+ font-size: var(--nx-text-xs);
882
+ font-weight: 500;
883
+ line-height: 1.6;
884
+ vertical-align: middle;
885
+ }
886
+
887
+ .nx-table__tag::before {
888
+ content: "";
889
+ flex-shrink: 0;
890
+ width: 6px;
891
+ height: 6px;
892
+ border-radius: 50%;
893
+ background: var(--nx-tag-tone);
894
+ }
895
+
896
+ .nx-table__tag[data-tone="primary"] {
897
+ --nx-tag-tone: var(--nx-primary);
898
+ }
899
+
900
+ .nx-table__tag[data-tone="success"] {
901
+ --nx-tag-tone: var(--nx-success);
902
+ }
903
+
904
+ .nx-table__tag[data-tone="warning"] {
905
+ --nx-tag-tone: var(--nx-warning);
906
+ }
907
+
908
+ .nx-table__tag[data-tone="danger"] {
909
+ --nx-tag-tone: var(--nx-danger);
910
+ }
911
+
912
+ .nx-table__tag[data-tone="info"] {
913
+ --nx-tag-tone: var(--nx-info);
914
+ }
915
+
916
+ /* A tag after a value (a column's `tag`): "Asia" then "Trashed", a word's space apart. */
917
+ .nx-table__tag--after {
918
+ margin-inline-start: var(--nx-space-2);
919
+ }
920
+
921
+ /* A word with no state to tell (a keyword in a record's card): the tag without its dot. */
922
+ .nx-table__tag--plain::before {
923
+ content: none;
924
+ }
925
+
926
+ /* Placeholders while the first rows load. */
927
+ .nx-table__bone {
928
+ display: block;
929
+ height: 0.75em;
930
+ border-radius: var(--nx-radius-sm);
931
+ background: color-mix(in oklab, var(--nx-text) 9%, transparent);
932
+ animation: nx-table-pulse 1.6s ease-in-out infinite;
933
+ }
934
+
935
+ .nx-table__cell--end .nx-table__bone {
936
+ margin-left: auto;
937
+ }
938
+
939
+ @keyframes nx-table-pulse {
940
+ 50% {
941
+ opacity: 0.45;
942
+ }
943
+ }
944
+
945
+ /* A message across the table: no rows, no matches, a failed load. */
946
+ .nx-table__message-cell {
947
+ padding: 0;
948
+ background: var(--nx-table-bg);
949
+ }
950
+
951
+ /*
952
+ * The cell spans every column, so on a table wider than its frame the message is held to the part on screen:
953
+ * sticky at the start edge and as wide as the scroller shows (DataTable measures it into --nx-table-view-w).
954
+ */
955
+ .nx-table__message {
956
+ position: sticky;
957
+ inset-inline-start: 0;
958
+ box-sizing: border-box;
959
+ width: var(--nx-table-view-w, 100%);
960
+ display: flex;
961
+ flex-direction: column;
962
+ align-items: center;
963
+ gap: 4px;
964
+ padding: 40px var(--nx-space-4);
965
+ text-align: center;
966
+ }
967
+
968
+ .nx-table__message-icon {
969
+ width: 28px;
970
+ height: 28px;
971
+ margin-bottom: 6px;
972
+ color: var(--nx-text-muted);
973
+ }
974
+
975
+ .nx-table__message[data-tone="danger"] .nx-table__message-icon {
976
+ color: var(--nx-danger);
977
+ }
978
+
979
+ .nx-table__message-title {
980
+ margin: 0;
981
+ font-size: var(--nx-text-md);
982
+ font-weight: 600;
983
+ }
984
+
985
+ .nx-table__message-detail {
986
+ max-width: 36rem;
987
+ margin: 0;
988
+ color: var(--nx-text-muted);
989
+ font-size: var(--nx-text-sm);
990
+ }
991
+
992
+ .nx-table__message-actions {
993
+ display: flex;
994
+ flex-wrap: wrap;
995
+ justify-content: center;
996
+ gap: var(--nx-space-2);
997
+ margin-top: var(--nx-space-3);
998
+ }
999
+
1000
+ /* A thin bar under the header while a load runs with rows on screen. The rows dim only if it takes a moment. */
1001
+ .nx-table__progress {
1002
+ position: absolute;
1003
+ top: calc(var(--nx-table-head-all) - 1px);
1004
+ right: 0;
1005
+ left: 0;
1006
+ z-index: 4;
1007
+ height: 2px;
1008
+ overflow: hidden;
1009
+ opacity: 0;
1010
+ pointer-events: none;
1011
+ transition: opacity var(--nx-duration);
1012
+ }
1013
+
1014
+ .nx-table__progress::before {
1015
+ content: "";
1016
+ position: absolute;
1017
+ inset: 0 auto 0 0;
1018
+ width: 35%;
1019
+ background: var(--nx-primary);
1020
+ animation: nx-table-progress 1.1s ease-in-out infinite;
1021
+ }
1022
+
1023
+ @keyframes nx-table-progress {
1024
+ from {
1025
+ transform: translateX(-100%);
1026
+ }
1027
+ to {
1028
+ transform: translateX(290%);
1029
+ }
1030
+ }
1031
+
1032
+ .nx-table[data-refreshing] .nx-table__progress {
1033
+ opacity: 1;
1034
+ }
1035
+
1036
+ .nx-table__table > tbody {
1037
+ transition: opacity var(--nx-duration);
1038
+ }
1039
+
1040
+ .nx-table[data-refreshing] .nx-table__table > tbody {
1041
+ opacity: 0.55;
1042
+ transition-delay: 180ms;
1043
+ }
1044
+
1045
+ /* The selection bar covers the column headings, so the table does not move when it shows. */
1046
+ .nx-table__selection {
1047
+ position: absolute;
1048
+ top: 0;
1049
+ right: 0;
1050
+ left: var(--nx-table-index-w, var(--nx-table-select-w));
1051
+ z-index: 5;
1052
+ display: flex;
1053
+ align-items: center;
1054
+ gap: var(--nx-space-2);
1055
+ height: calc(var(--nx-table-head-all) - 1px);
1056
+ padding: 0 var(--nx-space-2) 0 2px;
1057
+ background: var(--nx-table-selected);
1058
+ }
1059
+
1060
+ .nx-table__frame:has(.nx-table__selection) .nx-table__heading.nx-table__cell--index {
1061
+ background: var(--nx-table-selected);
1062
+ }
1063
+
1064
+ .nx-table__selection-count {
1065
+ flex-shrink: 0;
1066
+ padding-right: var(--nx-space-2);
1067
+ font-size: var(--nx-text-sm);
1068
+ font-weight: 600;
1069
+ font-variant-numeric: tabular-nums;
1070
+ white-space: nowrap;
1071
+ }
1072
+
1073
+ /* With ways to select by what the rows are, the count is the button that opens them. */
1074
+ .nx-table__selection-pick .nx-table__selection-count {
1075
+ padding-right: 0;
1076
+ }
1077
+
1078
+ .nx-table__selection-actions {
1079
+ display: flex;
1080
+ flex: 1 1 auto;
1081
+ align-items: center;
1082
+ gap: 2px;
1083
+ min-width: 0;
1084
+ overflow: hidden;
1085
+ }
1086
+
1087
+ /* Every action on the selected rows in one split button: the name of the one shown must stay readable. */
1088
+ .nx-table__mass {
1089
+ min-width: 0;
1090
+ }
1091
+
1092
+ .nx-table__mass .nx-btn__label,
1093
+ .nx-table__mass .nx-split__main .nx-btn__label {
1094
+ overflow: hidden;
1095
+ text-overflow: ellipsis;
1096
+ white-space: nowrap;
1097
+ }
1098
+
1099
+ .nx-table__selection-clear {
1100
+ flex-shrink: 0;
1101
+ margin-left: auto;
1102
+ }
1103
+
1104
+ /* A narrow table keeps the action's name and drops the word "Clear"; it stays for screen readers and in the tooltip. */
1105
+ @container nx-table (max-width: 560px) {
1106
+ .nx-table__selection-clear.nx-btn {
1107
+ width: var(--nx-btn-height);
1108
+ padding: 0;
1109
+ }
1110
+
1111
+ .nx-table__selection-clear .nx-btn__label {
1112
+ position: absolute;
1113
+ width: 1px;
1114
+ height: 1px;
1115
+ overflow: hidden;
1116
+ clip-path: inset(50%);
1117
+ white-space: nowrap;
1118
+ }
1119
+ }
1120
+
1121
+ /* Footer: the frame's bottom band, the range on the left and the pager on the right. */
1122
+ .nx-table__footer {
1123
+ display: flex;
1124
+ flex-wrap: wrap;
1125
+ align-items: center;
1126
+ justify-content: space-between;
1127
+ gap: var(--nx-space-2) var(--nx-space-4);
1128
+ min-height: var(--nx-footbar);
1129
+ padding: 6px var(--nx-space-2) 6px var(--nx-table-pad-x);
1130
+ border-top: 1px solid var(--nx-table-line);
1131
+ }
1132
+
1133
+ .nx-table__range {
1134
+ margin: 0;
1135
+ color: var(--nx-text-muted);
1136
+ font-size: var(--nx-text-sm);
1137
+ font-variant-numeric: tabular-nums;
1138
+ }
1139
+
1140
+ /* The numbers themselves read as the text colour, so the line is skimmed at a glance. */
1141
+ .nx-table__range b {
1142
+ color: var(--nx-text);
1143
+ font-weight: 600;
1144
+ }
1145
+
1146
+ .nx-table__page-info {
1147
+ color: var(--nx-text-muted);
1148
+ font-size: var(--nx-text-sm);
1149
+ font-variant-numeric: tabular-nums;
1150
+ white-space: nowrap;
1151
+ }
1152
+
1153
+ .nx-table__paging {
1154
+ display: flex;
1155
+ flex-wrap: wrap;
1156
+ align-items: center;
1157
+ gap: var(--nx-space-2);
1158
+ margin-left: auto;
1159
+ }
1160
+
1161
+ .nx-table__page-size {
1162
+ color: var(--nx-text-muted);
1163
+ font-variant-numeric: tabular-nums;
1164
+ }
1165
+
1166
+ .nx-table__pager {
1167
+ display: flex;
1168
+ align-items: center;
1169
+ gap: 2px;
1170
+ }
1171
+
1172
+ .nx-table__page-list {
1173
+ display: flex;
1174
+ align-items: center;
1175
+ gap: 2px;
1176
+ }
1177
+
1178
+ .nx-table__page {
1179
+ min-width: var(--nx-control-sm);
1180
+ height: var(--nx-control-sm);
1181
+ padding: 0 6px;
1182
+ border: 0;
1183
+ border-radius: var(--nx-radius);
1184
+ background: transparent;
1185
+ color: var(--nx-text);
1186
+ font: inherit;
1187
+ font-size: var(--nx-text-sm);
1188
+ font-variant-numeric: tabular-nums;
1189
+ cursor: pointer;
1190
+ transition: background-color var(--nx-duration);
1191
+ }
1192
+
1193
+ .nx-table__page:hover:not(:disabled, [aria-current]) {
1194
+ background: var(--nx-surface-hover);
1195
+ }
1196
+
1197
+ .nx-table__page:focus-visible {
1198
+ outline: none;
1199
+ box-shadow: var(--nx-focus-ring);
1200
+ }
1201
+
1202
+ .nx-table__page[aria-current] {
1203
+ background: var(--nx-primary-solid);
1204
+ color: var(--nx-primary-text);
1205
+ font-weight: 600;
1206
+ cursor: default;
1207
+ }
1208
+
1209
+ .nx-table__page:disabled {
1210
+ opacity: 0.5;
1211
+ cursor: default;
1212
+ }
1213
+
1214
+ .nx-table__page-gap {
1215
+ min-width: 20px;
1216
+ color: var(--nx-text-muted);
1217
+ text-align: center;
1218
+ }
1219
+
1220
+ /* A narrow table drops the page buttons: "Page 6 of 24" and the arrows are enough. */
1221
+ @container nx-table (max-width: 520px) {
1222
+ .nx-table__page-list {
1223
+ display: none;
1224
+ }
1225
+ }
1226
+
1227
+ [data-nx-motion="reduce"] .nx-table__bone {
1228
+ animation: none;
1229
+ }
1230
+
1231
+ [data-nx-motion="reduce"] .nx-table__row[data-flash] > .nx-table__cell {
1232
+ animation: none;
1233
+ }
1234
+
1235
+ [data-nx-motion="reduce"] .nx-table__row[data-flash] {
1236
+ --nx-row-bg: var(--nx-table-flash);
1237
+ }
1238
+
1239
+ [data-nx-motion="reduce"] .nx-table__progress::before {
1240
+ width: 100%;
1241
+ animation: none;
1242
+ opacity: 0.6;
1243
+ }
1244
+
1245
+ /*
1246
+ * The panels under the table's options button (⋮), Columns and Filters: a head, a body that scrolls when the window
1247
+ * is short, and a foot. They share the .nx-popover panel with the menu, and hold controls rather than actions, so a
1248
+ * click inside never closes them.
1249
+ */
1250
+ .nx-table-panel {
1251
+ --nx-popover-pad: var(--nx-space-2);
1252
+
1253
+ position: fixed;
1254
+ inset: auto;
1255
+ outline: none;
1256
+ }
1257
+
1258
+ .nx-table-panel__head {
1259
+ display: flex;
1260
+ align-items: baseline;
1261
+ justify-content: space-between;
1262
+ gap: var(--nx-space-3);
1263
+ padding: 2px var(--nx-space-2) 6px;
1264
+ }
1265
+
1266
+ .nx-table-panel__title {
1267
+ font-weight: 600;
1268
+ }
1269
+
1270
+ .nx-table-panel__count {
1271
+ color: var(--nx-text-muted);
1272
+ font-size: var(--nx-text-xs);
1273
+ font-variant-numeric: tabular-nums;
1274
+ }
1275
+
1276
+ .nx-table-panel__foot {
1277
+ display: flex;
1278
+ align-items: center;
1279
+ justify-content: flex-end;
1280
+ margin-top: var(--nx-space-2);
1281
+ padding-top: var(--nx-space-2);
1282
+ border-top: 1px solid var(--nx-border);
1283
+ }
1284
+
1285
+ /* The Columns panel: a checkbox per column and a handle to drag it into another place. */
1286
+ .nx-columns {
1287
+ --nx-columns-row-h: var(--nx-row);
1288
+ min-width: 232px;
1289
+ max-width: min(320px, calc(100% - 16px));
1290
+ }
1291
+
1292
+ .nx-columns--sm {
1293
+ --nx-columns-row-h: calc(var(--nx-row) - 4px);
1294
+ font-size: var(--nx-text-sm);
1295
+ }
1296
+
1297
+ .nx-columns__list {
1298
+ display: flex;
1299
+ flex-direction: column;
1300
+ margin: 0;
1301
+ padding: 0;
1302
+ list-style: none;
1303
+ }
1304
+
1305
+ .nx-columns__row {
1306
+ display: flex;
1307
+ align-items: center;
1308
+ gap: 6px;
1309
+ min-height: var(--nx-columns-row-h);
1310
+ padding: 0 var(--nx-space-2) 0 2px;
1311
+ border: 1px solid transparent;
1312
+ border-radius: var(--nx-radius);
1313
+ }
1314
+
1315
+ .nx-columns__row:hover {
1316
+ background: var(--nx-surface-hover);
1317
+ }
1318
+
1319
+ /* The row being dragged stays in place and goes quiet; a line shows where it would land. */
1320
+ .nx-columns__row[data-held] {
1321
+ background: var(--nx-surface-hover);
1322
+ opacity: 0.6;
1323
+ }
1324
+
1325
+ .nx-columns__row[data-drop] {
1326
+ border-color: var(--nx-primary);
1327
+ }
1328
+
1329
+ /* A band: its own line, with the columns under it indented along a line that says where the band ends. */
1330
+ .nx-columns__row--band {
1331
+ display: block;
1332
+ padding: 0;
1333
+ }
1334
+
1335
+ .nx-columns__row--band:hover {
1336
+ background: none;
1337
+ }
1338
+
1339
+ .nx-columns__band {
1340
+ display: flex;
1341
+ align-items: center;
1342
+ gap: 6px;
1343
+ min-height: var(--nx-columns-row-h);
1344
+ padding: 0 var(--nx-space-2) 0 2px;
1345
+ border-radius: var(--nx-radius);
1346
+ }
1347
+
1348
+ .nx-columns__band:hover {
1349
+ background: var(--nx-surface-hover);
1350
+ }
1351
+
1352
+ .nx-columns__band .nx-columns__label {
1353
+ font-weight: 600;
1354
+ }
1355
+
1356
+ .nx-columns__band-count {
1357
+ flex: none;
1358
+ color: var(--nx-text-subtle);
1359
+ font-size: var(--nx-text-xs);
1360
+ font-variant-numeric: tabular-nums;
1361
+ }
1362
+
1363
+ .nx-columns__children {
1364
+ margin: 0 0 2px 13px;
1365
+ padding: 0 0 0 9px;
1366
+ border-left: 1px solid var(--nx-border);
1367
+ list-style: none;
1368
+ }
1369
+
1370
+ /* A column whose band is hidden stays readable but plainly off. */
1371
+ .nx-columns__row--child:has(input:disabled) .nx-columns__label {
1372
+ color: var(--nx-text-subtle);
1373
+ }
1374
+
1375
+ .nx-columns__handle {
1376
+ display: inline-flex;
1377
+ flex: none;
1378
+ align-items: center;
1379
+ justify-content: center;
1380
+ width: 20px;
1381
+ height: var(--nx-columns-row-h);
1382
+ padding: 0;
1383
+ border: 0;
1384
+ border-radius: var(--nx-radius-sm);
1385
+ background: none;
1386
+ color: var(--nx-text-subtle);
1387
+ cursor: grab;
1388
+ touch-action: none;
1389
+ }
1390
+
1391
+ .nx-columns__handle:hover {
1392
+ color: var(--nx-text);
1393
+ }
1394
+
1395
+ .nx-columns__handle:focus-visible {
1396
+ outline: none;
1397
+ box-shadow: var(--nx-focus-ring);
1398
+ }
1399
+
1400
+ .nx-columns[data-dragging] .nx-columns__handle {
1401
+ cursor: grabbing;
1402
+ }
1403
+
1404
+ .nx-columns__handle--fixed {
1405
+ cursor: default;
1406
+ }
1407
+
1408
+ .nx-columns__choice {
1409
+ flex: 1 1 auto;
1410
+ min-width: 0;
1411
+ padding: 2px 0;
1412
+ }
1413
+
1414
+ .nx-columns__label {
1415
+ overflow: hidden;
1416
+ text-overflow: ellipsis;
1417
+ white-space: nowrap;
1418
+ }
1419
+
1420
+ .nx-columns__pin {
1421
+ display: inline-flex;
1422
+ flex: none;
1423
+ color: var(--nx-text-subtle);
1424
+ }
1425
+
1426
+ .nx-columns__pin .nx-icon {
1427
+ width: 14px;
1428
+ height: 14px;
1429
+ }
1430
+
1431
+ .nx-columns__note {
1432
+ margin: 6px 0 0;
1433
+ padding: 0 var(--nx-space-2);
1434
+ color: var(--nx-text-muted);
1435
+ font-size: var(--nx-text-xs);
1436
+ line-height: 1.4;
1437
+ }
1438
+
1439
+ /* The Filters panel: one column of small inputs (the form's grid spans every cell this narrow), and the rows left. */
1440
+ .nx-table-filters {
1441
+ --nx-form-row-gap: var(--nx-space-3);
1442
+ width: min(22rem, calc(100vw - 16px));
1443
+ font-size: var(--nx-text-sm);
1444
+ }
1445
+
1446
+ /* In line with the head, with room for a focus ring inside the scroller. */
1447
+ .nx-table-filters__form {
1448
+ padding: 2px var(--nx-space-2);
1449
+ }
1450
+
1451
+ .nx-table-filters__rows {
1452
+ margin-right: auto;
1453
+ padding-left: var(--nx-space-2);
1454
+ color: var(--nx-text-muted);
1455
+ font-size: var(--nx-text-xs);
1456
+ font-variant-numeric: tabular-nums;
1457
+ }
1458
+ }