nexus-shared 2.0.0 → 3.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 (176) hide show
  1. package/CHANGELOG.md +446 -0
  2. package/LICENSE +21 -21
  3. package/README.md +1084 -903
  4. package/dist/Client.Index.d.ts +14 -0
  5. package/dist/Client.Index.js +14 -0
  6. package/dist/Components/Chats/Chat.d.ts +28 -0
  7. package/dist/Components/Chats/Chat.js +19 -0
  8. package/dist/Components/Chats/ChatButton.d.ts +26 -0
  9. package/dist/Components/Chats/ChatButton.js +41 -0
  10. package/dist/Components/Chats/ChatComposer.d.ts +41 -0
  11. package/dist/Components/Chats/ChatComposer.js +177 -0
  12. package/dist/Components/Chats/ChatConversations.d.ts +35 -0
  13. package/dist/Components/Chats/ChatConversations.js +38 -0
  14. package/dist/Components/Chats/ChatPanel.d.ts +66 -0
  15. package/dist/Components/Chats/ChatPanel.js +94 -0
  16. package/dist/Components/Chats/ChatParts.d.ts +87 -0
  17. package/dist/Components/Chats/ChatParts.js +96 -0
  18. package/dist/Components/Chats/ChatThread.d.ts +22 -0
  19. package/dist/Components/Chats/ChatThread.js +96 -0
  20. package/dist/Components/Documents/Button.d.ts +6 -1
  21. package/dist/Components/Documents/Button.js +2 -2
  22. package/dist/Components/Documents/Menu.js +44 -28
  23. package/dist/Components/Documents/SplitButton.d.ts +3 -1
  24. package/dist/Components/Documents/SplitButton.js +12 -8
  25. package/dist/Components/Documents/TabButtons.d.ts +29 -8
  26. package/dist/Components/Documents/TabButtons.js +24 -5
  27. package/dist/Components/Forms/ApiForm.d.ts +6 -4
  28. package/dist/Components/Forms/ApiForm.js +15 -14
  29. package/dist/Components/Forms/Crud.js +342 -71
  30. package/dist/Components/Forms/CrudParts.d.ts +5 -1
  31. package/dist/Components/Forms/CrudParts.js +37 -5
  32. package/dist/Components/Forms/ExcelImport.d.ts +58 -0
  33. package/dist/Components/Forms/ExcelImport.js +319 -0
  34. package/dist/Components/Forms/Form.js +9 -13
  35. package/dist/Components/Forms/SubmitForm.d.ts +11 -2
  36. package/dist/Components/Forms/SubmitForm.js +23 -12
  37. package/dist/Components/Inputs/DateTimePicker.js +2 -1
  38. package/dist/Components/Inputs/DropdownParts.d.ts +1 -1
  39. package/dist/Components/Inputs/DropdownParts.js +4 -6
  40. package/dist/Components/Inputs/GroupForm.js +4 -2
  41. package/dist/Components/Inputs/InputRenderer.d.ts +2 -0
  42. package/dist/Components/Inputs/InputRenderer.js +1 -1
  43. package/dist/Components/Inputs/NumberBox.js +24 -2
  44. package/dist/Components/Inputs/ReadOnlyNotice.js +2 -1
  45. package/dist/Components/Inputs/RowsInput.d.ts +12 -1
  46. package/dist/Components/Inputs/RowsInput.js +55 -6
  47. package/dist/Components/Inputs/Slider.js +4 -1
  48. package/dist/Components/Inputs/TabularForm.d.ts +7 -3
  49. package/dist/Components/Inputs/TabularForm.js +117 -20
  50. package/dist/Components/Inputs/TimePanel.js +4 -3
  51. package/dist/Components/Inputs/TimePicker.js +2 -1
  52. package/dist/Components/Layouts/Popup.js +1 -4
  53. package/dist/Components/Layouts/ThemePicker.d.ts +20 -3
  54. package/dist/Components/Layouts/ThemePicker.js +77 -6
  55. package/dist/Components/Layouts/ThemeScript.d.ts +4 -2
  56. package/dist/Components/Layouts/ThemeScript.js +4 -2
  57. package/dist/Components/Layouts/ThemeSwitcher.d.ts +6 -3
  58. package/dist/Components/Layouts/ThemeSwitcher.js +39 -18
  59. package/dist/Components/Viewers/Avatar.d.ts +21 -0
  60. package/dist/Components/Viewers/Avatar.js +21 -0
  61. package/dist/Components/Viewers/DataTable.js +262 -60
  62. package/dist/Components/Viewers/DataTableColumns.d.ts +32 -0
  63. package/dist/Components/Viewers/DataTableColumns.js +200 -0
  64. package/dist/Components/Viewers/DataTableFilters.d.ts +21 -0
  65. package/dist/Components/Viewers/DataTableFilters.js +68 -0
  66. package/dist/Components/Viewers/DataTableParts.d.ts +8 -4
  67. package/dist/Components/Viewers/DataTableParts.js +24 -12
  68. package/dist/Helpers/AnimationHelpers.d.ts +11 -0
  69. package/dist/Helpers/AnimationHelpers.js +20 -2
  70. package/dist/Helpers/ApiClient.d.ts +22 -0
  71. package/dist/Helpers/ApiClient.js +4 -1
  72. package/dist/Helpers/ApiFormHelpers.d.ts +27 -4
  73. package/dist/Helpers/ApiFormHelpers.js +73 -9
  74. package/dist/Helpers/ApiModules.d.ts +0 -4
  75. package/dist/Helpers/ApiModules.js +1 -0
  76. package/dist/Helpers/ApiResponses.d.ts +14 -4
  77. package/dist/Helpers/ApiResponses.js +116 -25
  78. package/dist/Helpers/ApiRoutes.d.ts +0 -10
  79. package/dist/Helpers/ApiRoutes.js +2 -10
  80. package/dist/Helpers/ChatBackend.d.ts +134 -0
  81. package/dist/Helpers/ChatBackend.js +332 -0
  82. package/dist/Helpers/ChatHelpers.d.ts +189 -0
  83. package/dist/Helpers/ChatHelpers.js +486 -0
  84. package/dist/Helpers/ChatHooks.d.ts +81 -0
  85. package/dist/Helpers/ChatHooks.js +175 -0
  86. package/dist/Helpers/ChatStore.d.ts +132 -0
  87. package/dist/Helpers/ChatStore.js +394 -0
  88. package/dist/Helpers/ChatSummaries.d.ts +39 -0
  89. package/dist/Helpers/ChatSummaries.js +118 -0
  90. package/dist/Helpers/CrudBackend.d.ts +54 -9
  91. package/dist/Helpers/CrudBackend.js +103 -6
  92. package/dist/Helpers/CrudHelpers.d.ts +39 -4
  93. package/dist/Helpers/CrudHelpers.js +76 -11
  94. package/dist/Helpers/ExcelBackend.d.ts +106 -0
  95. package/dist/Helpers/ExcelBackend.js +296 -0
  96. package/dist/Helpers/ExcelHelpers.d.ts +126 -0
  97. package/dist/Helpers/ExcelHelpers.js +344 -0
  98. package/dist/Helpers/FormStore.d.ts +11 -5
  99. package/dist/Helpers/FormStore.js +44 -6
  100. package/dist/Helpers/MessageBuilder.js +1 -2
  101. package/dist/Helpers/NumberHelpers.d.ts +7 -1
  102. package/dist/Helpers/NumberHelpers.js +41 -3
  103. package/dist/Helpers/PopoverHelpers.d.ts +46 -9
  104. package/dist/Helpers/PopoverHelpers.js +163 -24
  105. package/dist/Helpers/PopoverHooks.d.ts +24 -0
  106. package/dist/Helpers/PopoverHooks.js +62 -0
  107. package/dist/Helpers/RowsStore.d.ts +14 -3
  108. package/dist/Helpers/RowsStore.js +37 -3
  109. package/dist/Helpers/TableColumns.d.ts +7 -0
  110. package/dist/Helpers/TableColumns.js +41 -13
  111. package/dist/Helpers/TableExport.js +12 -12
  112. package/dist/Helpers/TableFilters.d.ts +25 -0
  113. package/dist/Helpers/TableFilters.js +162 -0
  114. package/dist/Helpers/TableHelpers.d.ts +15 -4
  115. package/dist/Helpers/TableHelpers.js +28 -4
  116. package/dist/Helpers/TableStore.d.ts +11 -3
  117. package/dist/Helpers/TableStore.js +16 -5
  118. package/dist/Helpers/ThemeHelpers.d.ts +21 -4
  119. package/dist/Helpers/ThemeHelpers.js +46 -9
  120. package/dist/Helpers/UserPreferences.d.ts +14 -0
  121. package/dist/Helpers/UserPreferences.js +99 -0
  122. package/dist/Interfaces/ApiInterfaces.d.ts +29 -4
  123. package/dist/Interfaces/ApiInterfaces.js +16 -10
  124. package/dist/Interfaces/ChatInterfaces.d.ts +240 -0
  125. package/dist/Interfaces/ChatInterfaces.js +1 -0
  126. package/dist/Interfaces/CrudInterfaces.d.ts +92 -12
  127. package/dist/Interfaces/ExcelInterfaces.d.ts +227 -0
  128. package/dist/Interfaces/ExcelInterfaces.js +45 -0
  129. package/dist/Interfaces/FormInterfaces.d.ts +85 -7
  130. package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
  131. package/dist/Interfaces/PreferenceInterfaces.d.ts +43 -0
  132. package/dist/Interfaces/PreferenceInterfaces.js +28 -0
  133. package/dist/Interfaces/TableInterfaces.d.ts +116 -9
  134. package/dist/Interfaces/ThemeInterfaces.d.ts +25 -0
  135. package/dist/Interfaces/ThemeInterfaces.js +15 -0
  136. package/dist/Services/BrowserApi.d.ts +18 -1
  137. package/dist/Services/BrowserApi.js +18 -0
  138. package/dist/Services/ChatApi.d.ts +37 -0
  139. package/dist/Services/ChatApi.js +98 -0
  140. package/dist/Services/ExcelApi.d.ts +48 -0
  141. package/dist/Services/ExcelApi.js +196 -0
  142. package/dist/Services/ServerApi.d.ts +6 -1
  143. package/dist/Services/ServerApi.js +3 -0
  144. package/dist/Services/ThemeService.d.ts +18 -2
  145. package/dist/Services/ThemeService.js +80 -16
  146. package/dist/Shared.Index.d.ts +10 -0
  147. package/dist/Shared.Index.js +10 -0
  148. package/package.json +85 -82
  149. package/src/Styles/Nexus.Avatar.css +79 -0
  150. package/src/Styles/Nexus.Base.css +117 -70
  151. package/src/Styles/Nexus.Button.css +180 -151
  152. package/src/Styles/Nexus.Calendar.css +273 -275
  153. package/src/Styles/Nexus.Chat.css +1443 -0
  154. package/src/Styles/Nexus.Choice.css +343 -345
  155. package/src/Styles/Nexus.Crud.css +168 -84
  156. package/src/Styles/Nexus.DateTime.css +81 -81
  157. package/src/Styles/Nexus.Dropdown.css +725 -774
  158. package/src/Styles/Nexus.Editor.css +295 -297
  159. package/src/Styles/Nexus.Excel.css +389 -0
  160. package/src/Styles/Nexus.Form.css +279 -298
  161. package/src/Styles/Nexus.Index.css +37 -34
  162. package/src/Styles/Nexus.Input.css +602 -637
  163. package/src/Styles/Nexus.Markdown.css +237 -265
  164. package/src/Styles/Nexus.Menu.css +237 -249
  165. package/src/Styles/Nexus.Popup.css +724 -731
  166. package/src/Styles/Nexus.RichText.css +2 -32
  167. package/src/Styles/Nexus.Rows.css +637 -566
  168. package/src/Styles/Nexus.Slider.css +276 -278
  169. package/src/Styles/Nexus.Tab.Buttons.css +432 -364
  170. package/src/Styles/Nexus.Table.css +1386 -1014
  171. package/src/Styles/Nexus.Theme.Picker.css +286 -159
  172. package/src/Styles/Nexus.Themes.css +194 -194
  173. package/src/Styles/Nexus.Time.css +144 -147
  174. package/src/Styles/Nexus.Toast.css +263 -265
  175. package/src/Styles/Nexus.Tokens.css +89 -72
  176. package/src/Styles/Nexus.Tree.css +431 -435
@@ -1,774 +1,725 @@
1
- @layer nexus.components {
2
- /*
3
- * Dropdown: the field shell holds the chosen value (or chips for several) over a search input, and the list opens in
4
- * a popover placed by the component. Rows are flex rows, or, with columns, subgrid rows of one grid, so every column
5
- * lines up down the list like a table. Current values that cannot be picked again carry a tag and a dashed chip.
6
- */
7
- .nx-field--dropdown {
8
- --nx-dropdown-chip: 22px;
9
- --nx-dropdown-value-media: 20px;
10
- }
11
-
12
- .nx-field--dropdown.nx-field--sm {
13
- --nx-dropdown-chip: 18px;
14
- --nx-dropdown-value-media: 16px;
15
- }
16
-
17
- .nx-field--dropdown.nx-field--lg {
18
- --nx-dropdown-chip: 26px;
19
- --nx-dropdown-value-media: 22px;
20
- }
21
-
22
- /* The box */
23
- .nx-dropdown__control {
24
- position: relative;
25
- flex: 1 1 auto;
26
- align-self: stretch;
27
- min-width: 0;
28
- cursor: pointer;
29
- }
30
-
31
- .nx-field[data-disabled] .nx-dropdown__control {
32
- cursor: not-allowed;
33
- }
34
-
35
- .nx-field[data-readonly] .nx-dropdown__control {
36
- cursor: default;
37
- }
38
-
39
- .nx-dropdown__control:not([data-searchable]) .nx-dropdown__input {
40
- caret-color: transparent;
41
- cursor: inherit;
42
- }
43
-
44
- /* One value: the value and the input share one cell; typing hides the value and shows the search. */
45
- .nx-dropdown__control--single {
46
- display: grid;
47
- }
48
-
49
- .nx-dropdown__control--single > :is(.nx-dropdown__value, .nx-dropdown__input) {
50
- grid-area: 1 / 1;
51
- }
52
-
53
- .nx-dropdown__value {
54
- display: flex;
55
- align-items: center;
56
- gap: var(--nx-space-2);
57
- min-width: 0;
58
- padding: 0 var(--nx-field-pad-x);
59
- color: var(--nx-text);
60
- font-size: var(--nx-field-font);
61
- line-height: 1.4;
62
- pointer-events: none;
63
- }
64
-
65
- .nx-dropdown__value-text {
66
- min-width: 0;
67
- overflow: hidden;
68
- text-overflow: ellipsis;
69
- white-space: nowrap;
70
- }
71
-
72
- .nx-dropdown__control--single:has(.nx-dropdown__input:not(:placeholder-shown)) .nx-dropdown__value {
73
- visibility: hidden;
74
- }
75
-
76
- .nx-field--inbox .nx-dropdown__value {
77
- padding-top: var(--nx-field-inbox-pad);
78
- }
79
-
80
- .nx-field__icon:not(.nx-field__icon--end) + .nx-dropdown__control :is(.nx-dropdown__value, .nx-dropdown__input) {
81
- padding-left: var(--nx-space-2);
82
- }
83
-
84
- .nx-dropdown__control:has(~ .nx-field__actions) :is(.nx-dropdown__value, .nx-dropdown__input) {
85
- padding-right: var(--nx-space-1);
86
- }
87
-
88
- .nx-dropdown__value .nx-dropdown-avatar {
89
- --nx-dropdown-media: var(--nx-dropdown-value-media);
90
- }
91
-
92
- .nx-dropdown__value .nx-dropdown-image {
93
- height: calc(var(--nx-dropdown-value-media) * 0.72);
94
- }
95
-
96
- /* Several values: chips wrap, and the search input takes the rest of the last line. */
97
- .nx-dropdown__control--multiple {
98
- display: flex;
99
- flex-wrap: wrap;
100
- align-content: center;
101
- align-items: center;
102
- gap: 4px;
103
- padding: 4px var(--nx-field-pad-x);
104
- }
105
-
106
- .nx-field--inbox .nx-dropdown__control--multiple {
107
- padding-top: calc(var(--nx-field-inbox-pad) + 1px);
108
- }
109
-
110
- .nx-field__icon:not(.nx-field__icon--end) + .nx-dropdown__control--multiple {
111
- padding-left: var(--nx-space-2);
112
- }
113
-
114
- .nx-dropdown__control--multiple:has(~ .nx-field__actions) {
115
- padding-right: var(--nx-space-1);
116
- }
117
-
118
- .nx-field .nx-dropdown__control--multiple .nx-dropdown__input {
119
- flex: 1 1 2.5rem;
120
- align-self: center;
121
- width: 2.5rem;
122
- min-width: 2.5rem;
123
- height: var(--nx-dropdown-chip);
124
- padding: 0;
125
- }
126
-
127
- /*
128
- * With chips, the search input needs room only while someone can type: at rest (or without search) it shrinks to the
129
- * end of the last line, so chips that fill a line leave no empty line under them.
130
- */
131
- .nx-field .nx-dropdown__control--multiple[data-has-value]:is(:not(:focus-within), :not([data-searchable])) .nx-dropdown__input {
132
- flex-basis: 1px;
133
- width: 1px;
134
- min-width: 1px;
135
- }
136
-
137
- .nx-dropdown-chip {
138
- --nx-dropdown-media: calc(var(--nx-dropdown-chip) - 4px);
139
-
140
- display: inline-flex;
141
- align-items: center;
142
- gap: 5px;
143
- min-width: 0;
144
- max-width: 100%;
145
- height: var(--nx-dropdown-chip);
146
- padding: 0 7px;
147
- border: 1px solid transparent;
148
- border-radius: var(--nx-radius-sm);
149
- background: color-mix(in oklab, var(--nx-text) 8%, var(--nx-field-bg));
150
- color: var(--nx-text);
151
- font-size: var(--nx-text-sm);
152
- line-height: 1;
153
- white-space: nowrap;
154
- cursor: default;
155
- }
156
-
157
- .nx-field--sm .nx-dropdown-chip {
158
- gap: 4px;
159
- padding: 0 5px;
160
- font-size: var(--nx-text-xs);
161
- }
162
-
163
- .nx-dropdown-chip:has(.nx-dropdown-chip__remove) {
164
- padding-right: 1px;
165
- }
166
-
167
- .nx-dropdown-chip:has(> .nx-dropdown-avatar) {
168
- padding-left: 2px;
169
- }
170
-
171
- .nx-dropdown-chip__label {
172
- min-width: 0;
173
- overflow: hidden;
174
- text-overflow: ellipsis;
175
- }
176
-
177
- .nx-dropdown-chip .nx-dropdown-image {
178
- height: calc(var(--nx-dropdown-chip) - 10px);
179
- }
180
-
181
- .nx-dropdown-chip .nx-dropdown-icon {
182
- color: var(--nx-text-muted);
183
- }
184
-
185
- /* A current value that cannot be picked again: an outline instead of a fill. The tooltip says why. */
186
- .nx-dropdown-chip[data-state] {
187
- border-style: dashed;
188
- border-color: var(--nx-text-subtle);
189
- background: transparent;
190
- color: var(--nx-text-muted);
191
- }
192
-
193
- /* "+12 more": the values past maxChips. Its tooltip names them. */
194
- .nx-dropdown-chip--more {
195
- font-variant-numeric: tabular-nums;
196
- font-weight: 500;
197
- }
198
-
199
- .nx-dropdown-chip__remove {
200
- display: grid;
201
- place-items: center;
202
- flex-shrink: 0;
203
- width: calc(var(--nx-dropdown-chip) - 4px);
204
- height: calc(var(--nx-dropdown-chip) - 4px);
205
- padding: 0;
206
- border: 0;
207
- border-radius: 3px;
208
- background: none;
209
- color: var(--nx-text-muted);
210
- cursor: pointer;
211
- transition:
212
- background-color var(--nx-duration),
213
- color var(--nx-duration);
214
- }
215
-
216
- .nx-dropdown-chip__remove:hover {
217
- background: color-mix(in oklab, var(--nx-text) 12%, transparent);
218
- color: var(--nx-text);
219
- }
220
-
221
- .nx-dropdown-chip__remove .nx-icon {
222
- width: 12px;
223
- height: 12px;
224
- vertical-align: 0;
225
- }
226
-
227
- .nx-dropdown__spinner {
228
- display: grid;
229
- place-items: center;
230
- width: calc(var(--nx-field-base) - 10px);
231
- height: calc(var(--nx-field-base) - 10px);
232
- color: var(--nx-text-muted);
233
- }
234
-
235
- .nx-dropdown__spinner .nx-icon {
236
- width: var(--nx-field-icon);
237
- height: var(--nx-field-icon);
238
- vertical-align: 0;
239
- }
240
-
241
- .nx-dropdown__toggle .nx-icon {
242
- transition: transform 160ms ease;
243
- }
244
-
245
- .nx-dropdown__toggle[aria-expanded="true"] {
246
- color: var(--nx-field-accent);
247
- }
248
-
249
- .nx-dropdown__toggle[aria-expanded="true"] .nx-icon {
250
- transform: rotate(180deg);
251
- }
252
-
253
- /* The panel */
254
- .nx-dropdown-panel {
255
- --nx-dropdown-row: 34px;
256
- --nx-dropdown-font: var(--nx-text-md);
257
- --nx-dropdown-media: 24px;
258
- --nx-dropdown-check: 16px;
259
- --nx-dropdown-pad-x: var(--nx-space-2);
260
- --nx-dropdown-bg: light-dark(var(--nx-surface), var(--nx-surface-2));
261
- --nx-dropdown-scrollbar: color-mix(in oklab, var(--nx-text-subtle) 75%, transparent);
262
-
263
- position: fixed;
264
- inset: auto;
265
- width: max-content;
266
- max-width: min(640px, calc(100vw - 16px));
267
- overflow: hidden;
268
- }
269
-
270
- /* Two classes: the shared panel (.nx-popover) loads after this file, and its padding would wrap the list in 16px. */
271
- .nx-popover.nx-dropdown-panel {
272
- padding: 0;
273
- }
274
-
275
- .nx-dropdown-panel--sm {
276
- --nx-dropdown-row: 30px;
277
- --nx-dropdown-font: var(--nx-text-sm);
278
- --nx-dropdown-media: 20px;
279
- --nx-dropdown-check: 14px;
280
- }
281
-
282
- .nx-dropdown-panel--lg {
283
- --nx-dropdown-row: 40px;
284
- --nx-dropdown-font: var(--nx-text-lg);
285
- --nx-dropdown-media: 28px;
286
- --nx-dropdown-check: 18px;
287
- --nx-dropdown-pad-x: 10px;
288
- }
289
-
290
- /* Only while open: a display on the popover itself would show it closed. */
291
- .nx-dropdown-panel:popover-open {
292
- display: flex;
293
- flex-direction: column;
294
- animation: nx-dropdown-in 120ms ease-out;
295
- }
296
-
297
- @keyframes nx-dropdown-in {
298
- from {
299
- opacity: 0;
300
- transform: translateY(-4px);
301
- }
302
- }
303
-
304
- /*
305
- * Rows sit 10px from the top and bottom and 8px from the sides. Where scroll bars take room (Windows), the gutter
306
- * kept for one is already space on the right, so the right padding gives it back (the component measures it).
307
- */
308
- .nx-dropdown-list {
309
- position: relative;
310
- flex: 1 1 auto;
311
- min-height: 0;
312
- padding: 10px 8px;
313
- padding-right: max(0px, 8px - var(--nx-dropdown-gutter, 0px));
314
- overflow: hidden auto;
315
- overscroll-behavior: contain;
316
- outline: none;
317
- color: var(--nx-text);
318
- font-size: var(--nx-dropdown-font);
319
- line-height: 1.35;
320
- scrollbar-gutter: stable;
321
- scrollbar-width: thin;
322
- scrollbar-color: var(--nx-dropdown-scrollbar) transparent;
323
- }
324
-
325
- /*
326
- * Columns: one grid for the whole list; rows and the header are subgrids, so columns line up across rows. Subgrid rows
327
- * take no side padding (Chromium does not widen the edge tracks for it): the check cell carries the start inset, and
328
- * an empty last track the end.
329
- */
330
- .nx-dropdown-list--columns {
331
- display: grid;
332
- align-content: start;
333
- column-gap: 10px;
334
- padding-top: 0;
335
- }
336
-
337
- .nx-dropdown-list--columns > * {
338
- grid-column: 1 / -1;
339
- }
340
-
341
- .nx-dropdown-list--columns > :is(.nx-dropdown-option, .nx-dropdown-list__header) {
342
- display: grid;
343
- grid-template-columns: subgrid;
344
- align-items: center;
345
- }
346
-
347
- .nx-dropdown-list--columns > .nx-dropdown-list__header + *,
348
- .nx-dropdown-list--columns > :first-child:not(.nx-dropdown-list__header) {
349
- margin-top: 4px;
350
- }
351
-
352
- .nx-dropdown-list__header {
353
- position: sticky;
354
- z-index: 1;
355
- top: 0;
356
- padding: 7px 0 6px;
357
- border-bottom: 1px solid var(--nx-border);
358
- background: var(--nx-dropdown-bg);
359
- color: var(--nx-text-muted);
360
- font-size: var(--nx-text-xs);
361
- font-weight: 600;
362
- line-height: 1.3;
363
- white-space: nowrap;
364
- }
365
-
366
- .nx-dropdown-list__heading {
367
- min-width: 0;
368
- overflow: hidden;
369
- text-overflow: ellipsis;
370
- }
371
-
372
- .nx-dropdown-list__group {
373
- padding: 6px var(--nx-dropdown-pad-x) 3px;
374
- color: var(--nx-text-muted);
375
- font-size: var(--nx-text-xs);
376
- font-weight: 600;
377
- letter-spacing: 0.04em;
378
- text-transform: uppercase;
379
- }
380
-
381
- .nx-dropdown-list__separator {
382
- height: 1px;
383
- margin: 4px 0;
384
- background: var(--nx-border);
385
- }
386
-
387
- /* Rows */
388
- .nx-dropdown-option {
389
- display: flex;
390
- align-items: center;
391
- gap: var(--nx-space-2);
392
- min-height: var(--nx-dropdown-row);
393
- padding: 5px var(--nx-dropdown-pad-x);
394
- border-radius: var(--nx-radius);
395
- cursor: pointer;
396
- user-select: none;
397
- }
398
-
399
- .nx-dropdown-list--columns > .nx-dropdown-option {
400
- padding-inline: 0;
401
- }
402
-
403
- .nx-dropdown-list--columns > * > .nx-dropdown-option__check,
404
- .nx-dropdown-list--columns > .nx-dropdown-list__header > :first-child {
405
- box-sizing: content-box;
406
- padding-left: var(--nx-dropdown-pad-x);
407
- }
408
-
409
- .nx-dropdown-option[data-active] {
410
- background: var(--nx-surface-hover);
411
- }
412
-
413
- /* The chosen row of a single choice: a faint primary tint, a check, and a heavier label. */
414
- .nx-dropdown-list:not([aria-multiselectable]) .nx-dropdown-option[aria-selected="true"] {
415
- background: color-mix(in oklab, var(--nx-primary) 10%, transparent);
416
- }
417
-
418
- .nx-dropdown-list:not([aria-multiselectable]) .nx-dropdown-option[aria-selected="true"][data-active] {
419
- background: color-mix(in oklab, var(--nx-primary) 17%, transparent);
420
- }
421
-
422
- .nx-dropdown-list:not([aria-multiselectable]) .nx-dropdown-option[aria-selected="true"] :is(.nx-dropdown-option__text, .nx-dropdown-cell__text) {
423
- font-weight: 500;
424
- }
425
-
426
- .nx-dropdown-option[aria-disabled="true"] {
427
- background: none;
428
- cursor: default;
429
- }
430
-
431
- .nx-dropdown-option[aria-disabled="true"] > * {
432
- opacity: 0.5;
433
- }
434
-
435
- /* Rows of an earlier search, while the next one loads. */
436
- .nx-dropdown-list[data-stale] .nx-dropdown-option {
437
- opacity: 0.55;
438
- pointer-events: none;
439
- }
440
-
441
- .nx-dropdown-option__check {
442
- display: grid;
443
- flex-shrink: 0;
444
- place-items: center;
445
- width: var(--nx-dropdown-check);
446
- color: var(--nx-primary);
447
- }
448
-
449
- .nx-dropdown-option__check .nx-icon {
450
- width: var(--nx-dropdown-check);
451
- height: var(--nx-dropdown-check);
452
- vertical-align: 0;
453
- }
454
-
455
- /*
456
- * The checkbox of a multiple list, drawn like the checkbox input. The list panel is lighter than a page panel in dark
457
- * mode, so the edge takes the muted color there to keep 3:1, and the box takes the row color instead of a darker fill.
458
- */
459
- .nx-dropdown-checkbox {
460
- display: grid;
461
- place-content: center;
462
- width: var(--nx-dropdown-check);
463
- height: var(--nx-dropdown-check);
464
- border: 1px solid light-dark(var(--nx-text-subtle), var(--nx-text-muted));
465
- border-radius: 4px;
466
- background: transparent;
467
- color: var(--nx-primary-text);
468
- transition:
469
- background-color var(--nx-duration),
470
- border-color var(--nx-duration);
471
- }
472
-
473
- .nx-dropdown-checkbox::before {
474
- content: "";
475
- width: calc(var(--nx-dropdown-check) - 2px);
476
- height: calc(var(--nx-dropdown-check) - 2px);
477
- background: currentColor;
478
- mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5l9.5 -10'/%3E%3C/svg%3E") center / 100% no-repeat;
479
- transform: scale(0);
480
- transition: transform var(--nx-duration) ease-out;
481
- }
482
-
483
- .nx-dropdown-option:hover .nx-dropdown-checkbox {
484
- border-color: light-dark(var(--nx-text-muted), var(--nx-text));
485
- }
486
-
487
- .nx-dropdown-option[aria-selected="true"] .nx-dropdown-checkbox {
488
- border-color: var(--nx-primary-solid);
489
- background: var(--nx-primary-solid);
490
- }
491
-
492
- .nx-dropdown-option[aria-selected="true"] .nx-dropdown-checkbox::before {
493
- transform: scale(1);
494
- }
495
-
496
- .nx-dropdown-option__icon {
497
- display: grid;
498
- flex-shrink: 0;
499
- place-items: center;
500
- width: 1.15em;
501
- color: var(--nx-text-muted);
502
- }
503
-
504
- .nx-dropdown-option__icon .nx-icon,
505
- .nx-dropdown-icon .nx-icon {
506
- width: 1.15em;
507
- height: 1.15em;
508
- vertical-align: 0;
509
- }
510
-
511
- .nx-dropdown-option__body {
512
- display: grid;
513
- flex: 1 1 auto;
514
- min-width: 0;
515
- }
516
-
517
- .nx-dropdown-option__label {
518
- display: flex;
519
- align-items: center;
520
- gap: 6px;
521
- min-width: 0;
522
- }
523
-
524
- .nx-dropdown-option__text {
525
- min-width: 0;
526
- overflow: hidden;
527
- text-overflow: ellipsis;
528
- white-space: nowrap;
529
- }
530
-
531
- .nx-dropdown-option__description {
532
- color: var(--nx-text-muted);
533
- font-size: var(--nx-text-xs);
534
- line-height: 1.4;
535
- }
536
-
537
- /* Cells */
538
- .nx-dropdown-cell {
539
- display: flex;
540
- align-items: center;
541
- gap: 6px;
542
- min-width: 0;
543
- white-space: nowrap;
544
- }
545
-
546
- .nx-dropdown-cell__text {
547
- min-width: 0;
548
- overflow: hidden;
549
- text-overflow: ellipsis;
550
- }
551
-
552
- .nx-dropdown-cell--muted {
553
- color: var(--nx-text-muted);
554
- }
555
-
556
- .nx-dropdown-cell--code {
557
- color: var(--nx-text-muted);
558
- font-size: 0.93em;
559
- font-variant-numeric: tabular-nums;
560
- font-weight: 500;
561
- letter-spacing: 0.03em;
562
- }
563
-
564
- .nx-dropdown-cell--media {
565
- display: grid;
566
- place-items: center;
567
- }
568
-
569
- /* A bare value, with no fields for the columns. */
570
- .nx-dropdown-cell--span {
571
- grid-column: 2 / -1;
572
- }
573
-
574
- .nx-dropdown-icon {
575
- display: inline-grid;
576
- flex-shrink: 0;
577
- place-items: center;
578
- color: var(--nx-text-muted);
579
- }
580
-
581
- .nx-dropdown-avatar {
582
- position: relative;
583
- display: inline-grid;
584
- flex-shrink: 0;
585
- place-items: center;
586
- width: var(--nx-dropdown-media);
587
- height: var(--nx-dropdown-media);
588
- overflow: hidden;
589
- border-radius: 50%;
590
- background: color-mix(in oklab, var(--nx-text) 10%, var(--nx-surface));
591
- color: var(--nx-text);
592
- font-size: max(8px, calc(var(--nx-dropdown-media) * 0.4));
593
- font-weight: 600;
594
- line-height: 1;
595
- }
596
-
597
- .nx-dropdown-avatar[data-tone="primary"] {
598
- background: var(--nx-primary-soft);
599
- }
600
-
601
- .nx-dropdown-avatar[data-tone="info"] {
602
- background: var(--nx-info-soft);
603
- }
604
-
605
- .nx-dropdown-avatar[data-tone="success"] {
606
- background: var(--nx-success-soft);
607
- }
608
-
609
- .nx-dropdown-avatar[data-tone="warning"] {
610
- background: var(--nx-warning-soft);
611
- }
612
-
613
- .nx-dropdown-avatar img {
614
- position: absolute;
615
- inset: 0;
616
- width: 100%;
617
- height: 100%;
618
- object-fit: cover;
619
- }
620
-
621
- /* A flag or a logo: 4:3, with a faint edge so white flags do not melt into the panel. */
622
- .nx-dropdown-image {
623
- display: block;
624
- flex-shrink: 0;
625
- width: auto;
626
- height: calc(var(--nx-dropdown-media) * 0.66);
627
- aspect-ratio: 4 / 3;
628
- border-radius: 2px;
629
- box-shadow: 0 0 0 1px color-mix(in oklab, var(--nx-text) 14%, transparent);
630
- object-fit: cover;
631
- }
632
-
633
- .nx-dropdown-image--empty {
634
- background: color-mix(in oklab, var(--nx-text) 8%, transparent);
635
- }
636
-
637
- /* Why a current value cannot be picked again. The text color, not muted: muted text on a tint misses 4.5:1 in most themes. */
638
- .nx-dropdown-tag {
639
- flex-shrink: 0;
640
- padding: 1px 6px;
641
- border-radius: var(--nx-radius-full);
642
- background: color-mix(in oklab, var(--nx-text) 9%, transparent);
643
- color: var(--nx-text);
644
- font-size: var(--nx-text-xs);
645
- font-weight: 500;
646
- line-height: 1.35;
647
- white-space: nowrap;
648
- }
649
-
650
- .nx-dropdown-option[aria-disabled="true"] .nx-dropdown-tag {
651
- opacity: 1;
652
- }
653
-
654
- /* Placeholders while options load */
655
- .nx-dropdown-option--skeleton {
656
- cursor: default;
657
- }
658
-
659
- .nx-dropdown-skeleton {
660
- display: inline-block;
661
- width: 6em;
662
- max-width: 100%;
663
- height: 0.7em;
664
- border-radius: var(--nx-radius-full);
665
- background: color-mix(in oklab, var(--nx-text) 11%, transparent);
666
- vertical-align: middle;
667
- animation: nx-dropdown-pulse 1.2s ease-in-out infinite;
668
- }
669
-
670
- .nx-dropdown-skeleton--avatar {
671
- width: var(--nx-dropdown-media);
672
- height: var(--nx-dropdown-media);
673
- border-radius: 50%;
674
- }
675
-
676
- .nx-dropdown-skeleton--image {
677
- width: calc(var(--nx-dropdown-media) * 0.88);
678
- height: calc(var(--nx-dropdown-media) * 0.66);
679
- border-radius: 2px;
680
- }
681
-
682
- .nx-dropdown-skeleton--icon {
683
- width: 1.15em;
684
- height: 1.15em;
685
- border-radius: var(--nx-radius-sm);
686
- }
687
-
688
- @keyframes nx-dropdown-pulse {
689
- 50% {
690
- opacity: 0.45;
691
- }
692
- }
693
-
694
- /* Loading more, a failed load, no matches */
695
- .nx-dropdown-list__status {
696
- display: flex;
697
- align-items: center;
698
- justify-content: center;
699
- flex-wrap: wrap;
700
- gap: var(--nx-space-2);
701
- padding: 10px var(--nx-space-3);
702
- color: var(--nx-text-muted);
703
- font-size: var(--nx-text-sm);
704
- text-align: center;
705
- }
706
-
707
- .nx-dropdown-list__status > .nx-icon {
708
- flex-shrink: 0;
709
- width: 16px;
710
- height: 16px;
711
- vertical-align: 0;
712
- }
713
-
714
- .nx-dropdown-list__status--error > .nx-icon {
715
- color: var(--nx-danger);
716
- }
717
-
718
- /* The footer never gives up height: a panel short of room shrinks its list instead. */
719
- .nx-dropdown-panel__foot {
720
- display: flex;
721
- flex: none;
722
- flex-wrap: wrap;
723
- align-items: center;
724
- justify-content: space-between;
725
- gap: 4px var(--nx-space-3);
726
- min-height: 40px;
727
- padding: 4px var(--nx-space-2) 4px var(--nx-space-3);
728
- border-top: 1px solid var(--nx-border);
729
- color: var(--nx-text-muted);
730
- font-size: var(--nx-text-xs);
731
- }
732
-
733
- /*
734
- * A multiple choice's one footer: Select all and Clear on the left, where their hover tint lines up with the rows'
735
- * and their text with the checkboxes; the count and Done on the right. A narrow panel moves the right pair to a
736
- * second line.
737
- */
738
- .nx-dropdown-panel__foot--actions {
739
- padding-left: var(--nx-space-2);
740
- }
741
-
742
- .nx-dropdown-panel__actions,
743
- .nx-dropdown-panel__end {
744
- display: flex;
745
- align-items: center;
746
- }
747
-
748
- .nx-dropdown-panel__actions {
749
- gap: 2px;
750
- }
751
-
752
- .nx-dropdown-panel__end {
753
- gap: var(--nx-space-3);
754
- margin-left: auto;
755
- }
756
-
757
- .nx-dropdown-panel__count {
758
- font-variant-numeric: tabular-nums;
759
- white-space: nowrap;
760
- }
761
-
762
- @media (prefers-reduced-motion: reduce) {
763
- .nx-dropdown-panel:popover-open,
764
- .nx-dropdown-skeleton {
765
- animation: none;
766
- }
767
-
768
- .nx-dropdown__toggle .nx-icon,
769
- .nx-dropdown-checkbox,
770
- .nx-dropdown-checkbox::before {
771
- transition: none;
772
- }
773
- }
774
- }
1
+ @layer nexus.components {
2
+ /*
3
+ * Dropdown: the field shell holds the chosen value (or chips for several) over a search input, and the list opens in
4
+ * a popover placed by the component. Rows are flex rows, or, with columns, subgrid rows of one grid, so every column
5
+ * lines up down the list like a table. Current values that cannot be picked again carry a tag and a dashed chip.
6
+ */
7
+ .nx-field--dropdown {
8
+ --nx-dropdown-chip: 22px;
9
+ --nx-dropdown-value-media: 20px;
10
+ }
11
+
12
+ .nx-field--dropdown.nx-field--sm {
13
+ --nx-dropdown-chip: 18px;
14
+ --nx-dropdown-value-media: 16px;
15
+ }
16
+
17
+ .nx-field--dropdown.nx-field--lg {
18
+ --nx-dropdown-chip: 26px;
19
+ --nx-dropdown-value-media: 22px;
20
+ }
21
+
22
+ /* The box */
23
+ .nx-dropdown__control {
24
+ position: relative;
25
+ flex: 1 1 auto;
26
+ align-self: stretch;
27
+ min-width: 0;
28
+ cursor: pointer;
29
+ }
30
+
31
+ .nx-field[data-disabled] .nx-dropdown__control {
32
+ cursor: not-allowed;
33
+ }
34
+
35
+ .nx-field[data-readonly] .nx-dropdown__control {
36
+ cursor: default;
37
+ }
38
+
39
+ .nx-dropdown__control:not([data-searchable]) .nx-dropdown__input {
40
+ caret-color: transparent;
41
+ cursor: inherit;
42
+ }
43
+
44
+ /* One value: the value and the input share one cell; typing hides the value and shows the search. */
45
+ .nx-dropdown__control--single {
46
+ display: grid;
47
+ }
48
+
49
+ .nx-dropdown__control--single > :is(.nx-dropdown__value, .nx-dropdown__input) {
50
+ grid-area: 1 / 1;
51
+ }
52
+
53
+ .nx-dropdown__value {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: var(--nx-space-2);
57
+ min-width: 0;
58
+ padding: 0 var(--nx-field-pad-x);
59
+ color: var(--nx-text);
60
+ font-size: var(--nx-field-font);
61
+ line-height: 1.4;
62
+ pointer-events: none;
63
+ }
64
+
65
+ .nx-dropdown__value-text {
66
+ min-width: 0;
67
+ overflow: hidden;
68
+ text-overflow: ellipsis;
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .nx-dropdown__control--single:has(.nx-dropdown__input:not(:placeholder-shown)) .nx-dropdown__value {
73
+ visibility: hidden;
74
+ }
75
+
76
+ .nx-field--inbox .nx-dropdown__value {
77
+ padding-top: var(--nx-field-inbox-pad);
78
+ }
79
+
80
+ .nx-field__icon:not(.nx-field__icon--end) + .nx-dropdown__control :is(.nx-dropdown__value, .nx-dropdown__input) {
81
+ padding-left: var(--nx-space-2);
82
+ }
83
+
84
+ .nx-dropdown__control:has(~ .nx-field__actions) :is(.nx-dropdown__value, .nx-dropdown__input) {
85
+ padding-right: var(--nx-space-1);
86
+ }
87
+
88
+ .nx-dropdown__value .nx-dropdown-avatar {
89
+ --nx-dropdown-media: var(--nx-dropdown-value-media);
90
+ }
91
+
92
+ .nx-dropdown__value .nx-dropdown-image {
93
+ height: calc(var(--nx-dropdown-value-media) * 0.72);
94
+ }
95
+
96
+ /* Several values: chips wrap, and the search input takes the rest of the last line. */
97
+ .nx-dropdown__control--multiple {
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ align-content: center;
101
+ align-items: center;
102
+ gap: 4px;
103
+ padding: 4px var(--nx-field-pad-x);
104
+ }
105
+
106
+ .nx-field--inbox .nx-dropdown__control--multiple {
107
+ padding-top: calc(var(--nx-field-inbox-pad) + 1px);
108
+ }
109
+
110
+ .nx-field__icon:not(.nx-field__icon--end) + .nx-dropdown__control--multiple {
111
+ padding-left: var(--nx-space-2);
112
+ }
113
+
114
+ .nx-dropdown__control--multiple:has(~ .nx-field__actions) {
115
+ padding-right: var(--nx-space-1);
116
+ }
117
+
118
+ .nx-field .nx-dropdown__control--multiple .nx-dropdown__input {
119
+ flex: 1 1 2.5rem;
120
+ align-self: center;
121
+ width: 2.5rem;
122
+ min-width: 2.5rem;
123
+ height: var(--nx-dropdown-chip);
124
+ padding: 0;
125
+ }
126
+
127
+ /*
128
+ * With chips, the search input needs room only while someone can type: at rest (or without search) it shrinks to the
129
+ * end of the last line, so chips that fill a line leave no empty line under them.
130
+ */
131
+ .nx-field .nx-dropdown__control--multiple[data-has-value]:is(:not(:focus-within), :not([data-searchable])) .nx-dropdown__input {
132
+ flex-basis: 1px;
133
+ width: 1px;
134
+ min-width: 1px;
135
+ }
136
+
137
+ .nx-dropdown-chip {
138
+ --nx-dropdown-media: calc(var(--nx-dropdown-chip) - 4px);
139
+
140
+ display: inline-flex;
141
+ align-items: center;
142
+ gap: 5px;
143
+ min-width: 0;
144
+ max-width: 100%;
145
+ height: var(--nx-dropdown-chip);
146
+ padding: 0 7px;
147
+ border: 1px solid transparent;
148
+ border-radius: var(--nx-radius-sm);
149
+ background: color-mix(in oklab, var(--nx-text) 8%, var(--nx-field-bg));
150
+ color: var(--nx-text);
151
+ font-size: var(--nx-text-sm);
152
+ line-height: 1;
153
+ white-space: nowrap;
154
+ cursor: default;
155
+ }
156
+
157
+ .nx-field--sm .nx-dropdown-chip {
158
+ gap: 4px;
159
+ padding: 0 5px;
160
+ font-size: var(--nx-text-xs);
161
+ }
162
+
163
+ .nx-dropdown-chip:has(.nx-dropdown-chip__remove) {
164
+ padding-right: 1px;
165
+ }
166
+
167
+ .nx-dropdown-chip:has(> .nx-dropdown-avatar) {
168
+ padding-left: 2px;
169
+ }
170
+
171
+ .nx-dropdown-chip__label {
172
+ min-width: 0;
173
+ overflow: hidden;
174
+ text-overflow: ellipsis;
175
+ }
176
+
177
+ .nx-dropdown-chip .nx-dropdown-image {
178
+ height: calc(var(--nx-dropdown-chip) - 10px);
179
+ }
180
+
181
+ .nx-dropdown-chip .nx-dropdown-icon {
182
+ color: var(--nx-text-muted);
183
+ }
184
+
185
+ /* A current value that cannot be picked again: an outline instead of a fill. The tooltip says why. */
186
+ .nx-dropdown-chip[data-state] {
187
+ border-style: dashed;
188
+ border-color: var(--nx-text-subtle);
189
+ background: transparent;
190
+ color: var(--nx-text-muted);
191
+ }
192
+
193
+ /* "+12 more": the values past maxChips. Its tooltip names them. */
194
+ .nx-dropdown-chip--more {
195
+ font-variant-numeric: tabular-nums;
196
+ font-weight: 500;
197
+ }
198
+
199
+ .nx-dropdown-chip__remove {
200
+ display: grid;
201
+ place-items: center;
202
+ flex-shrink: 0;
203
+ width: calc(var(--nx-dropdown-chip) - 4px);
204
+ height: calc(var(--nx-dropdown-chip) - 4px);
205
+ padding: 0;
206
+ border: 0;
207
+ border-radius: 3px;
208
+ background: none;
209
+ color: var(--nx-text-muted);
210
+ cursor: pointer;
211
+ transition:
212
+ background-color var(--nx-duration),
213
+ color var(--nx-duration);
214
+ }
215
+
216
+ .nx-dropdown-chip__remove:hover {
217
+ background: color-mix(in oklab, var(--nx-text) 12%, transparent);
218
+ color: var(--nx-text);
219
+ }
220
+
221
+ .nx-dropdown-chip__remove .nx-icon {
222
+ width: 12px;
223
+ height: 12px;
224
+ vertical-align: 0;
225
+ }
226
+
227
+ .nx-dropdown__spinner {
228
+ display: grid;
229
+ place-items: center;
230
+ width: calc(var(--nx-field-base) - 10px);
231
+ height: calc(var(--nx-field-base) - 10px);
232
+ color: var(--nx-text-muted);
233
+ }
234
+
235
+ .nx-dropdown__spinner .nx-icon {
236
+ width: var(--nx-field-icon);
237
+ height: var(--nx-field-icon);
238
+ vertical-align: 0;
239
+ }
240
+
241
+ .nx-dropdown__toggle .nx-icon {
242
+ transition: transform 160ms ease;
243
+ }
244
+
245
+ .nx-dropdown__toggle[aria-expanded="true"] {
246
+ color: var(--nx-field-accent);
247
+ }
248
+
249
+ .nx-dropdown__toggle[aria-expanded="true"] .nx-icon {
250
+ transform: rotate(180deg);
251
+ }
252
+
253
+ /* The panel */
254
+ .nx-dropdown-panel {
255
+ --nx-dropdown-row: calc(var(--nx-row) + 2px);
256
+ --nx-dropdown-font: var(--nx-text-md);
257
+ --nx-dropdown-media: 24px;
258
+ --nx-dropdown-check: 16px;
259
+ --nx-dropdown-pad-x: var(--nx-space-2);
260
+ --nx-dropdown-bg: light-dark(var(--nx-surface), var(--nx-surface-2));
261
+
262
+ position: fixed;
263
+ inset: auto;
264
+ width: max-content;
265
+ max-width: min(640px, calc(100vw - 16px));
266
+ overflow: hidden;
267
+ }
268
+
269
+ /* Two classes: the shared panel (.nx-popover) loads after this file, and its padding would wrap the list in 16px. */
270
+ .nx-popover.nx-dropdown-panel {
271
+ padding: 0;
272
+ }
273
+
274
+ .nx-dropdown-panel--sm {
275
+ --nx-dropdown-row: calc(var(--nx-row) - 2px);
276
+ --nx-dropdown-font: var(--nx-text-sm);
277
+ --nx-dropdown-media: 20px;
278
+ --nx-dropdown-check: 14px;
279
+ }
280
+
281
+ .nx-dropdown-panel--lg {
282
+ --nx-dropdown-row: calc(var(--nx-row) + 8px);
283
+ --nx-dropdown-font: var(--nx-text-lg);
284
+ --nx-dropdown-media: 28px;
285
+ --nx-dropdown-check: 18px;
286
+ --nx-dropdown-pad-x: 10px;
287
+ }
288
+
289
+ /* Only while open: a display on the popover itself would show it closed. */
290
+ .nx-dropdown-panel:popover-open {
291
+ display: flex;
292
+ flex-direction: column;
293
+ }
294
+
295
+ /*
296
+ * Rows sit 10px from the top and bottom and 8px from the sides. Where scroll bars take room (Windows), the gutter
297
+ * kept for one is already space on the right, so the right padding gives it back (the component measures it).
298
+ */
299
+ .nx-dropdown-list {
300
+ position: relative;
301
+ flex: 1 1 auto;
302
+ min-height: 0;
303
+ padding: 10px 8px;
304
+ padding-right: max(0px, 8px - var(--nx-dropdown-gutter, 0px));
305
+ overflow: hidden auto;
306
+ overscroll-behavior: contain;
307
+ outline: none;
308
+ color: var(--nx-text);
309
+ font-size: var(--nx-dropdown-font);
310
+ line-height: 1.35;
311
+ scrollbar-gutter: stable;
312
+ }
313
+
314
+ /*
315
+ * Columns: one grid for the whole list; rows and the header are subgrids, so columns line up across rows. Subgrid rows
316
+ * take no side padding (Chromium does not widen the edge tracks for it): the check cell carries the start inset, and
317
+ * an empty last track the end.
318
+ */
319
+ .nx-dropdown-list--columns {
320
+ display: grid;
321
+ align-content: start;
322
+ column-gap: 10px;
323
+ padding-top: 0;
324
+ }
325
+
326
+ .nx-dropdown-list--columns > * {
327
+ grid-column: 1 / -1;
328
+ }
329
+
330
+ .nx-dropdown-list--columns > :is(.nx-dropdown-option, .nx-dropdown-list__header) {
331
+ display: grid;
332
+ grid-template-columns: subgrid;
333
+ align-items: center;
334
+ }
335
+
336
+ .nx-dropdown-list--columns > .nx-dropdown-list__header + *,
337
+ .nx-dropdown-list--columns > :first-child:not(.nx-dropdown-list__header) {
338
+ margin-top: 4px;
339
+ }
340
+
341
+ .nx-dropdown-list__header {
342
+ position: sticky;
343
+ z-index: 1;
344
+ top: 0;
345
+ padding: 7px 0 6px;
346
+ border-bottom: 1px solid var(--nx-border);
347
+ background: var(--nx-dropdown-bg);
348
+ color: var(--nx-text-muted);
349
+ font-size: var(--nx-text-xs);
350
+ font-weight: 600;
351
+ line-height: 1.3;
352
+ white-space: nowrap;
353
+ }
354
+
355
+ .nx-dropdown-list__heading {
356
+ min-width: 0;
357
+ overflow: hidden;
358
+ text-overflow: ellipsis;
359
+ }
360
+
361
+ .nx-dropdown-list__group {
362
+ padding: 6px var(--nx-dropdown-pad-x) 3px;
363
+ color: var(--nx-text-muted);
364
+ font-size: var(--nx-text-xs);
365
+ font-weight: 600;
366
+ letter-spacing: 0.04em;
367
+ text-transform: uppercase;
368
+ }
369
+
370
+ .nx-dropdown-list__separator {
371
+ height: 1px;
372
+ margin: 4px 0;
373
+ background: var(--nx-border);
374
+ }
375
+
376
+ /* Rows */
377
+ .nx-dropdown-option {
378
+ display: flex;
379
+ align-items: center;
380
+ gap: var(--nx-space-2);
381
+ min-height: var(--nx-dropdown-row);
382
+ padding: 5px var(--nx-dropdown-pad-x);
383
+ border-radius: var(--nx-radius);
384
+ cursor: pointer;
385
+ user-select: none;
386
+ }
387
+
388
+ .nx-dropdown-list--columns > .nx-dropdown-option {
389
+ padding-inline: 0;
390
+ }
391
+
392
+ .nx-dropdown-list--columns > * > .nx-dropdown-option__check,
393
+ .nx-dropdown-list--columns > .nx-dropdown-list__header > :first-child {
394
+ box-sizing: content-box;
395
+ padding-left: var(--nx-dropdown-pad-x);
396
+ }
397
+
398
+ .nx-dropdown-option[data-active] {
399
+ background: var(--nx-surface-hover);
400
+ }
401
+
402
+ /* The chosen row of a single choice: a faint primary tint, a check, and a heavier label. */
403
+ .nx-dropdown-list:not([aria-multiselectable]) .nx-dropdown-option[aria-selected="true"] {
404
+ background: color-mix(in oklab, var(--nx-primary) 10%, transparent);
405
+ }
406
+
407
+ .nx-dropdown-list:not([aria-multiselectable]) .nx-dropdown-option[aria-selected="true"][data-active] {
408
+ background: color-mix(in oklab, var(--nx-primary) 17%, transparent);
409
+ }
410
+
411
+ .nx-dropdown-list:not([aria-multiselectable]) .nx-dropdown-option[aria-selected="true"] :is(.nx-dropdown-option__text, .nx-dropdown-cell__text) {
412
+ font-weight: 500;
413
+ }
414
+
415
+ .nx-dropdown-option[aria-disabled="true"] {
416
+ background: none;
417
+ cursor: default;
418
+ }
419
+
420
+ .nx-dropdown-option[aria-disabled="true"] > * {
421
+ opacity: 0.5;
422
+ }
423
+
424
+ /* Rows of an earlier search, while the next one loads. */
425
+ .nx-dropdown-list[data-stale] .nx-dropdown-option {
426
+ opacity: 0.55;
427
+ pointer-events: none;
428
+ }
429
+
430
+ .nx-dropdown-option__check {
431
+ display: grid;
432
+ flex-shrink: 0;
433
+ place-items: center;
434
+ width: var(--nx-dropdown-check);
435
+ color: var(--nx-primary);
436
+ }
437
+
438
+ .nx-dropdown-option__check .nx-icon {
439
+ width: var(--nx-dropdown-check);
440
+ height: var(--nx-dropdown-check);
441
+ vertical-align: 0;
442
+ }
443
+
444
+ /*
445
+ * The checkbox of a multiple list, drawn like the checkbox input. The list panel is lighter than a page panel in dark
446
+ * mode, so the edge takes the muted color there to keep 3:1, and the box takes the row color instead of a darker fill.
447
+ */
448
+ .nx-dropdown-checkbox {
449
+ display: grid;
450
+ place-content: center;
451
+ width: var(--nx-dropdown-check);
452
+ height: var(--nx-dropdown-check);
453
+ border: 1px solid light-dark(var(--nx-text-subtle), var(--nx-text-muted));
454
+ border-radius: 4px;
455
+ background: transparent;
456
+ color: var(--nx-primary-text);
457
+ transition:
458
+ background-color var(--nx-duration),
459
+ border-color var(--nx-duration);
460
+ }
461
+
462
+ .nx-dropdown-checkbox::before {
463
+ content: "";
464
+ width: calc(var(--nx-dropdown-check) - 2px);
465
+ height: calc(var(--nx-dropdown-check) - 2px);
466
+ background: currentColor;
467
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5l9.5 -10'/%3E%3C/svg%3E") center / 100% no-repeat;
468
+ transform: scale(0);
469
+ transition: transform var(--nx-duration) ease-out;
470
+ }
471
+
472
+ .nx-dropdown-option:hover .nx-dropdown-checkbox {
473
+ border-color: light-dark(var(--nx-text-muted), var(--nx-text));
474
+ }
475
+
476
+ .nx-dropdown-option[aria-selected="true"] .nx-dropdown-checkbox {
477
+ border-color: var(--nx-primary-solid);
478
+ background: var(--nx-primary-solid);
479
+ }
480
+
481
+ .nx-dropdown-option[aria-selected="true"] .nx-dropdown-checkbox::before {
482
+ transform: scale(1);
483
+ }
484
+
485
+ .nx-dropdown-option__icon {
486
+ display: grid;
487
+ flex-shrink: 0;
488
+ place-items: center;
489
+ width: 1.15em;
490
+ color: var(--nx-text-muted);
491
+ }
492
+
493
+ .nx-dropdown-option__icon .nx-icon,
494
+ .nx-dropdown-icon .nx-icon {
495
+ width: 1.15em;
496
+ height: 1.15em;
497
+ vertical-align: 0;
498
+ }
499
+
500
+ .nx-dropdown-option__body {
501
+ display: grid;
502
+ flex: 1 1 auto;
503
+ min-width: 0;
504
+ }
505
+
506
+ .nx-dropdown-option__label {
507
+ display: flex;
508
+ align-items: center;
509
+ gap: 6px;
510
+ min-width: 0;
511
+ }
512
+
513
+ .nx-dropdown-option__text {
514
+ min-width: 0;
515
+ overflow: hidden;
516
+ text-overflow: ellipsis;
517
+ white-space: nowrap;
518
+ }
519
+
520
+ .nx-dropdown-option__description {
521
+ color: var(--nx-text-muted);
522
+ font-size: var(--nx-text-xs);
523
+ line-height: 1.4;
524
+ }
525
+
526
+ /* Cells */
527
+ .nx-dropdown-cell {
528
+ display: flex;
529
+ align-items: center;
530
+ gap: 6px;
531
+ min-width: 0;
532
+ white-space: nowrap;
533
+ }
534
+
535
+ .nx-dropdown-cell__text {
536
+ min-width: 0;
537
+ overflow: hidden;
538
+ text-overflow: ellipsis;
539
+ }
540
+
541
+ .nx-dropdown-cell--muted {
542
+ color: var(--nx-text-muted);
543
+ }
544
+
545
+ .nx-dropdown-cell--code {
546
+ color: var(--nx-text-muted);
547
+ font-size: 0.93em;
548
+ font-variant-numeric: tabular-nums;
549
+ font-weight: 500;
550
+ letter-spacing: 0.03em;
551
+ }
552
+
553
+ .nx-dropdown-cell--media {
554
+ display: grid;
555
+ place-items: center;
556
+ }
557
+
558
+ /* A bare value, with no fields for the columns. */
559
+ .nx-dropdown-cell--span {
560
+ grid-column: 2 / -1;
561
+ }
562
+
563
+ .nx-dropdown-icon {
564
+ display: inline-grid;
565
+ flex-shrink: 0;
566
+ place-items: center;
567
+ color: var(--nx-text-muted);
568
+ }
569
+
570
+ /* The shared avatar, at the size of the dropdown's pictures. */
571
+ .nx-dropdown-avatar {
572
+ --nx-avatar-size: var(--nx-dropdown-media);
573
+ }
574
+
575
+ /* A flag or a logo: 4:3, with a faint edge so white flags do not melt into the panel. */
576
+ .nx-dropdown-image {
577
+ display: block;
578
+ flex-shrink: 0;
579
+ width: auto;
580
+ height: calc(var(--nx-dropdown-media) * 0.66);
581
+ aspect-ratio: 4 / 3;
582
+ border-radius: 2px;
583
+ box-shadow: 0 0 0 1px color-mix(in oklab, var(--nx-text) 14%, transparent);
584
+ object-fit: cover;
585
+ }
586
+
587
+ .nx-dropdown-image--empty {
588
+ background: color-mix(in oklab, var(--nx-text) 8%, transparent);
589
+ }
590
+
591
+ /* Why a current value cannot be picked again. The text color, not muted: muted text on a tint misses 4.5:1 in most themes. */
592
+ .nx-dropdown-tag {
593
+ flex-shrink: 0;
594
+ padding: 1px 6px;
595
+ border-radius: var(--nx-radius-full);
596
+ background: color-mix(in oklab, var(--nx-text) 9%, transparent);
597
+ color: var(--nx-text);
598
+ font-size: var(--nx-text-xs);
599
+ font-weight: 500;
600
+ line-height: 1.35;
601
+ white-space: nowrap;
602
+ }
603
+
604
+ .nx-dropdown-option[aria-disabled="true"] .nx-dropdown-tag {
605
+ opacity: 1;
606
+ }
607
+
608
+ /* Placeholders while options load */
609
+ .nx-dropdown-option--skeleton {
610
+ cursor: default;
611
+ }
612
+
613
+ .nx-dropdown-skeleton {
614
+ display: inline-block;
615
+ width: 6em;
616
+ max-width: 100%;
617
+ height: 0.7em;
618
+ border-radius: var(--nx-radius-full);
619
+ background: color-mix(in oklab, var(--nx-text) 11%, transparent);
620
+ vertical-align: middle;
621
+ animation: nx-dropdown-pulse 1.2s ease-in-out infinite;
622
+ }
623
+
624
+ .nx-dropdown-skeleton--avatar {
625
+ width: var(--nx-dropdown-media);
626
+ height: var(--nx-dropdown-media);
627
+ border-radius: 50%;
628
+ }
629
+
630
+ .nx-dropdown-skeleton--image {
631
+ width: calc(var(--nx-dropdown-media) * 0.88);
632
+ height: calc(var(--nx-dropdown-media) * 0.66);
633
+ border-radius: 2px;
634
+ }
635
+
636
+ .nx-dropdown-skeleton--icon {
637
+ width: 1.15em;
638
+ height: 1.15em;
639
+ border-radius: var(--nx-radius-sm);
640
+ }
641
+
642
+ @keyframes nx-dropdown-pulse {
643
+ 50% {
644
+ opacity: 0.45;
645
+ }
646
+ }
647
+
648
+ /* Loading more, a failed load, no matches */
649
+ .nx-dropdown-list__status {
650
+ display: flex;
651
+ align-items: center;
652
+ justify-content: center;
653
+ flex-wrap: wrap;
654
+ gap: var(--nx-space-2);
655
+ padding: 10px var(--nx-space-3);
656
+ color: var(--nx-text-muted);
657
+ font-size: var(--nx-text-sm);
658
+ text-align: center;
659
+ }
660
+
661
+ .nx-dropdown-list__status > .nx-icon {
662
+ flex-shrink: 0;
663
+ width: 16px;
664
+ height: 16px;
665
+ vertical-align: 0;
666
+ }
667
+
668
+ .nx-dropdown-list__status--error > .nx-icon {
669
+ color: var(--nx-danger);
670
+ }
671
+
672
+ /* The footer never gives up height: a panel short of room shrinks its list instead. */
673
+ .nx-dropdown-panel__foot {
674
+ display: flex;
675
+ flex: none;
676
+ flex-wrap: wrap;
677
+ align-items: center;
678
+ justify-content: space-between;
679
+ gap: 4px var(--nx-space-3);
680
+ min-height: 40px;
681
+ padding: 4px var(--nx-space-2) 4px var(--nx-space-3);
682
+ border-top: 1px solid var(--nx-border);
683
+ color: var(--nx-text-muted);
684
+ font-size: var(--nx-text-xs);
685
+ }
686
+
687
+ /*
688
+ * A multiple choice's one footer: Select all and Clear on the left, where their hover tint lines up with the rows'
689
+ * and their text with the checkboxes; the count and Done on the right. A narrow panel moves the right pair to a
690
+ * second line.
691
+ */
692
+ .nx-dropdown-panel__foot--actions {
693
+ padding-left: var(--nx-space-2);
694
+ }
695
+
696
+ .nx-dropdown-panel__actions,
697
+ .nx-dropdown-panel__end {
698
+ display: flex;
699
+ align-items: center;
700
+ }
701
+
702
+ .nx-dropdown-panel__actions {
703
+ gap: 2px;
704
+ }
705
+
706
+ .nx-dropdown-panel__end {
707
+ gap: var(--nx-space-3);
708
+ margin-left: auto;
709
+ }
710
+
711
+ .nx-dropdown-panel__count {
712
+ font-variant-numeric: tabular-nums;
713
+ white-space: nowrap;
714
+ }
715
+
716
+ [data-nx-motion="reduce"] .nx-dropdown-skeleton {
717
+ animation: none;
718
+ }
719
+
720
+ [data-nx-motion="reduce"] .nx-dropdown__toggle .nx-icon,
721
+ [data-nx-motion="reduce"] .nx-dropdown-checkbox,
722
+ [data-nx-motion="reduce"] .nx-dropdown-checkbox::before {
723
+ transition: none;
724
+ }
725
+ }