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,731 +1,724 @@
1
- @layer nexus.components {
2
- /*
3
- * Popup: a native <dialog> shown modally. The panel is the dialog itself, so a click on the backdrop reaches the
4
- * dialog element and nothing else. The header and footer stay while the body scrolls.
5
- */
6
- .nx-popup {
7
- --nx-popup-width: 560px;
8
-
9
- /* Percentages, not vw: the viewport width here leaves out the scrollbar. */
10
- width: min(var(--nx-popup-width), calc(100% - 32px));
11
- max-width: none;
12
- max-height: min(calc(100vh - 48px), calc(100dvh - 48px));
13
- margin: auto;
14
- padding: 0;
15
- overflow: hidden;
16
- border: 1px solid var(--nx-border);
17
- border-radius: var(--nx-radius-lg);
18
- background: var(--nx-surface);
19
- color: var(--nx-text);
20
- box-shadow: var(--nx-shadow);
21
- outline: none;
22
- /* Where the title bar moved it, from the center (set by the Popup while dragging). */
23
- translate: var(--nx-popup-x, 0px) var(--nx-popup-y, 0px);
24
- }
25
-
26
- .nx-popup[open] {
27
- display: flex;
28
- flex-direction: column;
29
- animation: nx-popup-in 180ms cubic-bezier(0.2, 0, 0, 1);
30
- }
31
-
32
- .nx-popup::backdrop {
33
- background: rgb(0 0 0 / 0.5);
34
- animation: nx-popup-fade-in 180ms ease-out;
35
- }
36
-
37
- /* A closing popup takes no more clicks, so a double-click on Save does not submit twice. */
38
- .nx-popup--closing[open] {
39
- animation: nx-popup-out 140ms ease-in forwards;
40
- pointer-events: none;
41
- }
42
-
43
- .nx-popup--closing::backdrop {
44
- animation: nx-popup-fade-out 140ms ease-in forwards;
45
- }
46
-
47
- @keyframes nx-popup-in {
48
- from {
49
- opacity: 0;
50
- transform: translateY(10px) scale(0.98);
51
- }
52
- }
53
-
54
- @keyframes nx-popup-out {
55
- to {
56
- opacity: 0;
57
- transform: translateY(6px) scale(0.98);
58
- }
59
- }
60
-
61
- @keyframes nx-popup-fade-in {
62
- from {
63
- opacity: 0;
64
- }
65
- }
66
-
67
- @keyframes nx-popup-fade-out {
68
- to {
69
- opacity: 0;
70
- }
71
- }
72
-
73
- /* The page behind a modal popup stops scrolling; the Popup sets --nx-scrollbar-gap to the scrollbar's width, so nothing shifts. */
74
- :root:has(.nx-popup:modal) {
75
- overflow: hidden;
76
- padding-right: var(--nx-scrollbar-gap, 0px);
77
- }
78
-
79
- /* Sizes */
80
- .nx-popup--xs {
81
- --nx-popup-width: 360px;
82
- }
83
-
84
- .nx-popup--sm {
85
- --nx-popup-width: 440px;
86
- }
87
-
88
- .nx-popup--lg {
89
- --nx-popup-width: 720px;
90
- }
91
-
92
- .nx-popup--xl {
93
- --nx-popup-width: 960px;
94
- }
95
-
96
- .nx-popup--full {
97
- --nx-popup-width: calc(100% - 32px);
98
-
99
- height: calc(100vh - 32px);
100
- max-height: none;
101
- }
102
-
103
- /* Maximized: the whole window, edge to edge, wherever it was moved. Two classes, to win over the sizes and phones. */
104
- .nx-popup.nx-popup--maximized {
105
- width: 100%;
106
- height: 100%;
107
- max-height: none;
108
- border-width: 0;
109
- border-radius: 0;
110
- translate: none;
111
- }
112
-
113
- /* Minimizing: the page comes back while the popup flies to the dock. */
114
- .nx-popup[data-nx-minimizing] {
115
- pointer-events: none;
116
- }
117
-
118
- .nx-popup[data-nx-minimizing]::backdrop {
119
- animation: nx-popup-fade-out 200ms ease-in forwards;
120
- }
121
-
122
- /* Header, either design: the title bar moves the popup, so it selects no text and a touch drags instead of scrolling. */
123
- .nx-popup__head {
124
- display: flex;
125
- flex-shrink: 0;
126
- }
127
-
128
- .nx-popup--movable .nx-popup__head {
129
- cursor: move;
130
- touch-action: none;
131
- user-select: none;
132
- }
133
-
134
- .nx-popup__title,
135
- .nx-popup__description {
136
- margin: 0;
137
- }
138
-
139
- .nx-popup__controls {
140
- display: flex;
141
- flex-shrink: 0;
142
- margin-left: auto;
143
- }
144
-
145
- .nx-popup__control {
146
- display: grid;
147
- place-items: center;
148
- padding: 0;
149
- border: 0;
150
- background: none;
151
- color: var(--nx-text-muted);
152
- cursor: pointer;
153
- transition:
154
- background-color var(--nx-duration),
155
- color var(--nx-duration);
156
- }
157
-
158
- .nx-popup__control .nx-icon {
159
- vertical-align: 0;
160
- }
161
-
162
- /* Close turns red on hover, the one button that throws away what the popup holds. */
163
- .nx-popup__close:hover {
164
- background: var(--nx-danger-solid);
165
- color: var(--nx-danger-text);
166
- }
167
-
168
- /*
169
- * Window (the default): one low title bar, as on a desktop window. The icon and the title on the left, the
170
- * description after the title in muted text, and the window buttons filling the top right corner.
171
- */
172
- .nx-popup--window {
173
- --nx-popup-bar: 32px;
174
- }
175
-
176
- .nx-popup--window .nx-popup__head {
177
- box-sizing: content-box;
178
- align-items: center;
179
- gap: var(--nx-space-2);
180
- height: var(--nx-popup-bar);
181
- padding-left: var(--nx-space-3);
182
- border-bottom: 1px solid var(--nx-border);
183
- background: color-mix(in oklab, var(--nx-surface), var(--nx-text) 4%);
184
- }
185
-
186
- .nx-popup--window .nx-popup__icon {
187
- display: grid;
188
- flex-shrink: 0;
189
- place-items: center;
190
- color: var(--nx-primary);
191
- }
192
-
193
- .nx-popup--window .nx-popup__icon .nx-icon {
194
- width: 16px;
195
- height: 16px;
196
- vertical-align: 0;
197
- }
198
-
199
- /* One line: the title keeps its room, the description takes what is left, and both end in … when cut. */
200
- .nx-popup--window .nx-popup__heading {
201
- display: flex;
202
- flex: 1 1 auto;
203
- align-items: baseline;
204
- gap: var(--nx-space-2);
205
- min-width: 0;
206
- white-space: nowrap;
207
- }
208
-
209
- .nx-popup--window .nx-popup__title {
210
- flex: 0 1 auto;
211
- min-width: 0;
212
- overflow: hidden;
213
- font-size: var(--nx-text-sm);
214
- font-weight: 600;
215
- line-height: var(--nx-popup-bar);
216
- text-overflow: ellipsis;
217
- }
218
-
219
- .nx-popup--window .nx-popup__description {
220
- flex: 1 1 0;
221
- min-width: 0;
222
- overflow: hidden;
223
- color: var(--nx-text-muted);
224
- font-size: var(--nx-text-xs);
225
- text-overflow: ellipsis;
226
- }
227
-
228
- .nx-popup--window .nx-popup__controls {
229
- align-self: stretch;
230
- }
231
-
232
- .nx-popup--window .nx-popup__control {
233
- width: 46px;
234
- height: 100%;
235
- }
236
-
237
- .nx-popup--window .nx-popup__control:hover {
238
- background: color-mix(in oklab, var(--nx-text) 9%, transparent);
239
- color: var(--nx-text);
240
- }
241
-
242
- .nx-popup--window .nx-popup__control:active {
243
- background: color-mix(in oklab, var(--nx-text) 15%, transparent);
244
- }
245
-
246
- .nx-popup--window .nx-popup__close:hover {
247
- background: var(--nx-danger-solid);
248
- color: var(--nx-danger-text);
249
- }
250
-
251
- .nx-popup--window .nx-popup__close:active {
252
- background: var(--nx-danger-hover);
253
- }
254
-
255
- /* The buttons touch the popup's edges, where an outer ring would be cut off, so the ring is drawn inside. */
256
- .nx-popup--window .nx-popup__control:focus-visible {
257
- outline: none;
258
- box-shadow: inset 0 0 0 2px var(--nx-primary);
259
- }
260
-
261
- /* Thin lines, as desktop window buttons have; the square glyphs fill more of their box, so they are drawn smaller. */
262
- .nx-popup--window .nx-popup__control .nx-icon {
263
- width: 16px;
264
- height: 16px;
265
- stroke-width: 1.5;
266
- }
267
-
268
- .nx-popup--window .nx-popup__maximize .nx-icon {
269
- width: 13px;
270
- height: 13px;
271
- stroke-width: 1.75;
272
- }
273
-
274
- /* Touch screens get taller buttons. */
275
- @media (pointer: coarse) {
276
- .nx-popup--window {
277
- --nx-popup-bar: 40px;
278
- }
279
-
280
- .nx-popup--window .nx-popup__control {
281
- width: 48px;
282
- }
283
- }
284
-
285
- /*
286
- * Dialog (message boxes): a larger heading, the icon in a circle that carries the tone, the description under the
287
- * title, and a small close button.
288
- */
289
- .nx-popup--dialog .nx-popup__head {
290
- align-items: flex-start;
291
- gap: var(--nx-space-3);
292
- padding: var(--nx-space-5) var(--nx-space-5) 0;
293
- }
294
-
295
- .nx-popup--dialog .nx-popup__icon {
296
- display: grid;
297
- flex-shrink: 0;
298
- place-items: center;
299
- width: 40px;
300
- height: 40px;
301
- border-radius: 50%;
302
- background: var(--nx-primary-soft);
303
- color: var(--nx-primary);
304
- }
305
-
306
- .nx-popup--dialog .nx-popup__icon .nx-icon {
307
- width: 22px;
308
- height: 22px;
309
- vertical-align: 0;
310
- }
311
-
312
- .nx-popup--dialog .nx-popup__heading {
313
- flex: 1 1 auto;
314
- min-width: 0;
315
- padding-top: 2px;
316
- }
317
-
318
- .nx-popup--dialog .nx-popup__title {
319
- font-size: var(--nx-text-lg);
320
- font-weight: 600;
321
- line-height: 1.35;
322
- overflow-wrap: anywhere;
323
- }
324
-
325
- .nx-popup--dialog .nx-popup__description {
326
- margin-top: 4px;
327
- color: var(--nx-text-muted);
328
- font-size: var(--nx-text-sm);
329
- line-height: 1.45;
330
- }
331
-
332
- .nx-popup--dialog .nx-popup__controls {
333
- gap: 2px;
334
- margin: -6px -8px 0 auto;
335
- }
336
-
337
- .nx-popup--dialog .nx-popup__control {
338
- width: 30px;
339
- height: 30px;
340
- border-radius: var(--nx-radius);
341
- }
342
-
343
- .nx-popup--dialog .nx-popup__control:not(.nx-popup__close):hover {
344
- background: var(--nx-surface-hover);
345
- color: var(--nx-text);
346
- }
347
-
348
- .nx-popup--dialog .nx-popup__control:focus-visible {
349
- outline: none;
350
- box-shadow: var(--nx-focus-ring);
351
- }
352
-
353
- .nx-popup--dialog .nx-popup__control .nx-icon {
354
- width: 18px;
355
- height: 18px;
356
- }
357
-
358
- .nx-popup--dialog .nx-popup__maximize .nx-icon {
359
- width: 15px;
360
- height: 15px;
361
- }
362
-
363
- /* Body: scrolls when the content is taller than the window allows. */
364
- .nx-popup__body {
365
- flex: 1 1 auto;
366
- min-height: 0;
367
- padding: var(--nx-space-4) var(--nx-space-5) var(--nx-space-5);
368
- overflow: auto;
369
- overscroll-behavior: contain;
370
- }
371
-
372
- .nx-popup__head + .nx-popup__body:empty {
373
- padding-top: var(--nx-space-2);
374
- }
375
-
376
- .nx-popup__body:only-child,
377
- .nx-popup--window .nx-popup__body {
378
- padding-top: var(--nx-space-5);
379
- }
380
-
381
- .nx-popup--flush .nx-popup__body {
382
- padding: 0;
383
- }
384
-
385
- /* Footer: buttons at the end. */
386
- .nx-popup__foot {
387
- display: flex;
388
- flex-shrink: 0;
389
- flex-wrap: wrap;
390
- align-items: center;
391
- justify-content: flex-end;
392
- gap: var(--nx-space-2);
393
- padding: var(--nx-space-3) var(--nx-space-5);
394
- border-top: 1px solid var(--nx-border);
395
- }
396
-
397
- /* Message boxes: a small popup whose icon carries the tone. */
398
- .nx-popup--message .nx-popup__body {
399
- padding-top: var(--nx-space-2);
400
- }
401
-
402
- .nx-popup--message .nx-popup__body:empty {
403
- padding: var(--nx-space-3) 0 0;
404
- }
405
-
406
- .nx-message__text {
407
- color: var(--nx-text-muted);
408
- line-height: 1.5;
409
- }
410
-
411
- .nx-message__form {
412
- display: grid;
413
- gap: var(--nx-space-4);
414
- }
415
-
416
- .nx-popup--tone-info .nx-popup__icon {
417
- background: var(--nx-info-soft);
418
- color: var(--nx-info);
419
- }
420
-
421
- .nx-popup--tone-success .nx-popup__icon {
422
- background: var(--nx-success-soft);
423
- color: var(--nx-success);
424
- }
425
-
426
- .nx-popup--tone-warning .nx-popup__icon {
427
- background: var(--nx-warning-soft);
428
- color: var(--nx-warning);
429
- }
430
-
431
- .nx-popup--tone-danger .nx-popup__icon {
432
- background: var(--nx-danger-soft);
433
- color: var(--nx-danger);
434
- }
435
-
436
- /*
437
- * The dock: minimized popups as bars at the bottom right. Several stack behind the newest, with cards peeking out
438
- * above it and a count that opens the whole list. It sits on the page, so an open popup's backdrop covers it.
439
- */
440
- .nx-dock {
441
- --nx-dock-edge: 16px;
442
-
443
- position: fixed;
444
- right: calc(var(--nx-dock-edge) + env(safe-area-inset-right, 0px));
445
- bottom: calc(var(--nx-dock-edge) + env(safe-area-inset-bottom, 0px));
446
- z-index: 1000;
447
- display: flex;
448
- flex-direction: column;
449
- align-items: flex-end;
450
- gap: var(--nx-space-2);
451
- width: min(300px, calc(100% - 2 * var(--nx-dock-edge)));
452
- max-height: calc(100dvh - 2 * var(--nx-dock-edge));
453
- /* Only the bars and the count take clicks, not the space between them. */
454
- pointer-events: none;
455
- }
456
-
457
- .nx-dock > * {
458
- pointer-events: auto;
459
- }
460
-
461
- /* While a popup locks the page, its scrollbar's space is padding; the dock keeps its place beside it. */
462
- :root:has(.nx-popup:modal) .nx-dock {
463
- right: calc(var(--nx-dock-edge) + var(--nx-scrollbar-gap, 0px) + env(safe-area-inset-right, 0px));
464
- }
465
-
466
- .nx-dock__toggle {
467
- display: inline-flex;
468
- flex-shrink: 0;
469
- align-items: center;
470
- gap: 6px;
471
- height: 28px;
472
- padding: 0 10px 0 4px;
473
- border: 1px solid var(--nx-border);
474
- border-radius: var(--nx-radius-full);
475
- background: light-dark(var(--nx-surface), var(--nx-surface-2));
476
- color: var(--nx-text);
477
- box-shadow: var(--nx-shadow-sm);
478
- font: inherit;
479
- font-size: var(--nx-text-sm);
480
- font-weight: 600;
481
- cursor: pointer;
482
- transition: background-color var(--nx-duration);
483
- }
484
-
485
- .nx-dock__toggle:hover {
486
- background: var(--nx-surface-hover);
487
- }
488
-
489
- .nx-dock__toggle:focus-visible {
490
- outline: none;
491
- box-shadow: var(--nx-focus-ring);
492
- }
493
-
494
- .nx-dock__toggle .nx-icon {
495
- width: 16px;
496
- height: 16px;
497
- color: var(--nx-text-muted);
498
- }
499
-
500
- .nx-dock__count {
501
- display: grid;
502
- place-items: center;
503
- min-width: 20px;
504
- height: 20px;
505
- padding: 0 6px;
506
- border-radius: var(--nx-radius-full);
507
- background: var(--nx-primary-solid);
508
- color: var(--nx-primary-text);
509
- font-size: var(--nx-text-xs);
510
- font-variant-numeric: tabular-nums;
511
- line-height: 1;
512
- }
513
-
514
- .nx-dock__list {
515
- position: relative;
516
- isolation: isolate;
517
- display: flex;
518
- flex-direction: column;
519
- gap: 6px;
520
- width: 100%;
521
- min-height: 0;
522
- margin: 0;
523
- padding: 0;
524
- list-style: none;
525
- }
526
-
527
- /* Open: the whole list, oldest at the top; it scrolls when taller than the window, with room for the shadows. */
528
- .nx-dock--open .nx-dock__list {
529
- margin: -8px;
530
- padding: 8px;
531
- width: calc(100% + 16px);
532
- overflow-y: auto;
533
- overscroll-behavior: contain;
534
- }
535
-
536
- /* Stacked: one or two cards peek out above the newest bar; a click on them opens the list. */
537
- .nx-dock__list[data-depth] {
538
- margin-top: calc(var(--nx-dock-depth) * 5px);
539
- cursor: pointer;
540
- }
541
-
542
- .nx-dock__list[data-depth="1"] {
543
- --nx-dock-depth: 1;
544
- }
545
-
546
- .nx-dock__list[data-depth="2"] {
547
- --nx-dock-depth: 2;
548
- }
549
-
550
- .nx-dock__list[data-depth]::before,
551
- .nx-dock__list[data-depth="2"]::after {
552
- content: "";
553
- position: absolute;
554
- top: -5px;
555
- right: 8px;
556
- left: 8px;
557
- z-index: -1;
558
- height: 24px;
559
- border: 1px solid var(--nx-border);
560
- border-radius: var(--nx-radius-lg);
561
- background: light-dark(var(--nx-surface), var(--nx-surface-2));
562
- box-shadow: var(--nx-shadow-sm);
563
- }
564
-
565
- .nx-dock__list[data-depth="2"]::after {
566
- top: -10px;
567
- right: 16px;
568
- left: 16px;
569
- z-index: -2;
570
- }
571
-
572
- .nx-dock__item {
573
- display: flex;
574
- flex-shrink: 0;
575
- align-items: center;
576
- gap: 2px;
577
- height: 44px;
578
- padding-right: 6px;
579
- border: 1px solid var(--nx-border);
580
- border-radius: var(--nx-radius-lg);
581
- background: light-dark(var(--nx-surface), var(--nx-surface-2));
582
- color: var(--nx-text);
583
- box-shadow: var(--nx-shadow);
584
- cursor: default;
585
- transition: background-color var(--nx-duration);
586
- animation: nx-dock-in 180ms cubic-bezier(0.2, 0, 0, 1);
587
- }
588
-
589
- .nx-dock__item:has(.nx-dock__window:hover) {
590
- background: var(--nx-surface-hover);
591
- }
592
-
593
- .nx-dock__item:has(.nx-dock__window:focus-visible) {
594
- box-shadow: var(--nx-focus-ring), var(--nx-shadow);
595
- }
596
-
597
- /* The bar's title part brings the popup back. */
598
- .nx-dock__window {
599
- display: flex;
600
- flex: 1 1 auto;
601
- align-items: center;
602
- gap: 10px;
603
- min-width: 0;
604
- height: 100%;
605
- padding: 0 6px 0 7px;
606
- border: 0;
607
- border-radius: inherit;
608
- background: none;
609
- color: inherit;
610
- font: inherit;
611
- text-align: start;
612
- cursor: pointer;
613
- }
614
-
615
- .nx-dock__window:focus-visible {
616
- outline: none;
617
- }
618
-
619
- .nx-dock__icon {
620
- display: grid;
621
- flex-shrink: 0;
622
- place-items: center;
623
- width: 28px;
624
- height: 28px;
625
- border-radius: var(--nx-radius);
626
- background: var(--nx-primary-soft);
627
- color: var(--nx-primary);
628
- }
629
-
630
- .nx-dock__icon .nx-icon {
631
- width: 16px;
632
- height: 16px;
633
- vertical-align: 0;
634
- }
635
-
636
- .nx-dock__title {
637
- flex: 1 1 auto;
638
- min-width: 0;
639
- overflow: hidden;
640
- font-size: var(--nx-text-md);
641
- font-weight: 600;
642
- line-height: 1.3;
643
- text-overflow: ellipsis;
644
- white-space: nowrap;
645
- }
646
-
647
- .nx-dock__restore {
648
- flex-shrink: 0;
649
- width: 18px;
650
- height: 18px;
651
- color: var(--nx-text-muted);
652
- }
653
-
654
- .nx-dock__window:hover .nx-dock__restore {
655
- color: var(--nx-text);
656
- }
657
-
658
- .nx-dock__close {
659
- display: grid;
660
- flex-shrink: 0;
661
- place-items: center;
662
- width: 30px;
663
- height: 30px;
664
- padding: 0;
665
- border: 0;
666
- border-radius: var(--nx-radius);
667
- background: none;
668
- color: var(--nx-text-muted);
669
- cursor: pointer;
670
- transition:
671
- background-color var(--nx-duration),
672
- color var(--nx-duration);
673
- }
674
-
675
- .nx-dock__close:hover {
676
- background: var(--nx-danger-solid);
677
- color: var(--nx-danger-text);
678
- }
679
-
680
- .nx-dock__close:focus-visible {
681
- outline: none;
682
- box-shadow: var(--nx-focus-ring);
683
- }
684
-
685
- .nx-dock__close .nx-icon {
686
- width: 16px;
687
- height: 16px;
688
- vertical-align: 0;
689
- }
690
-
691
- @keyframes nx-dock-in {
692
- from {
693
- opacity: 0;
694
- transform: translateY(8px);
695
- }
696
- }
697
-
698
- @media (max-width: 600px) {
699
- .nx-popup {
700
- width: calc(100% - 16px);
701
- max-height: min(calc(100vh - 16px), calc(100dvh - 16px));
702
- }
703
-
704
- .nx-popup--dialog .nx-popup__head {
705
- padding: var(--nx-space-4) var(--nx-space-4) 0;
706
- }
707
-
708
- .nx-popup__body {
709
- padding: var(--nx-space-3) var(--nx-space-4) var(--nx-space-4);
710
- }
711
-
712
- .nx-popup--window:not(.nx-popup--flush) .nx-popup__body {
713
- padding-top: var(--nx-space-4);
714
- }
715
-
716
- .nx-popup__foot {
717
- padding: var(--nx-space-3) var(--nx-space-4);
718
- }
719
- }
720
-
721
- @media (prefers-reduced-motion: reduce) {
722
- .nx-popup[open],
723
- .nx-popup--closing[open],
724
- .nx-popup::backdrop,
725
- .nx-popup--closing::backdrop,
726
- .nx-popup[data-nx-minimizing]::backdrop,
727
- .nx-dock__item {
728
- animation: none;
729
- }
730
- }
731
- }
1
+ @layer nexus.components {
2
+ /*
3
+ * Popup: a native <dialog> shown modally. The panel is the dialog itself, so a click on the backdrop reaches the
4
+ * dialog element and nothing else. The header and footer stay while the body scrolls.
5
+ */
6
+ .nx-popup {
7
+ --nx-popup-width: 560px;
8
+
9
+ /* Percentages, not vw: the viewport width here leaves out the scrollbar. */
10
+ width: min(var(--nx-popup-width), calc(100% - 32px));
11
+ max-width: none;
12
+ max-height: min(calc(100vh - 48px), calc(100dvh - 48px));
13
+ margin: auto;
14
+ padding: 0;
15
+ overflow: hidden;
16
+ border: 1px solid var(--nx-border);
17
+ border-radius: var(--nx-radius-lg);
18
+ background: var(--nx-surface);
19
+ color: var(--nx-text);
20
+ box-shadow: var(--nx-shadow);
21
+ outline: none;
22
+ /* Where the title bar moved it, from the center (set by the Popup while dragging). */
23
+ translate: var(--nx-popup-x, 0px) var(--nx-popup-y, 0px);
24
+ }
25
+
26
+ /*
27
+ * Opening and closing are a plain fade, the popup and its backdrop together: it appears where it stays, at the size
28
+ * it keeps, so nothing in it moves while it fades. Closing waits for its fade (`afterAnimation`), and takes no clicks
29
+ * meanwhile, so a double-click on Save does not submit twice; with animations off there is no fade to wait for.
30
+ */
31
+ .nx-popup[open] {
32
+ display: flex;
33
+ flex-direction: column;
34
+ animation: nx-popup-in 160ms ease-out;
35
+ }
36
+
37
+ .nx-popup::backdrop {
38
+ background: rgb(0 0 0 / 0.5);
39
+ animation: nx-popup-in 160ms ease-out;
40
+ }
41
+
42
+ .nx-popup--closing[open] {
43
+ animation: nx-popup-out 120ms ease-in forwards;
44
+ pointer-events: none;
45
+ }
46
+
47
+ .nx-popup--closing::backdrop {
48
+ animation: nx-popup-out 120ms ease-in forwards;
49
+ }
50
+
51
+ @keyframes nx-popup-in {
52
+ from {
53
+ opacity: 0;
54
+ }
55
+ }
56
+
57
+ @keyframes nx-popup-out {
58
+ to {
59
+ opacity: 0;
60
+ }
61
+ }
62
+
63
+ /* The page behind a modal popup stops scrolling; the Popup sets --nx-scrollbar-gap to the scrollbar's width, so nothing shifts. */
64
+ :root:has(.nx-popup:modal) {
65
+ overflow: hidden;
66
+ padding-right: var(--nx-scrollbar-gap, 0px);
67
+ }
68
+
69
+ /* Sizes */
70
+ .nx-popup--xs {
71
+ --nx-popup-width: 360px;
72
+ }
73
+
74
+ .nx-popup--sm {
75
+ --nx-popup-width: 440px;
76
+ }
77
+
78
+ .nx-popup--lg {
79
+ --nx-popup-width: 720px;
80
+ }
81
+
82
+ .nx-popup--xl {
83
+ --nx-popup-width: 960px;
84
+ }
85
+
86
+ .nx-popup--full {
87
+ --nx-popup-width: calc(100% - 32px);
88
+
89
+ height: calc(100vh - 32px);
90
+ max-height: none;
91
+ }
92
+
93
+ /* Maximized: the whole window, edge to edge, wherever it was moved. Two classes, to win over the sizes and phones. */
94
+ .nx-popup.nx-popup--maximized {
95
+ width: 100%;
96
+ height: 100%;
97
+ max-height: none;
98
+ border-width: 0;
99
+ border-radius: 0;
100
+ translate: none;
101
+ }
102
+
103
+ /* Minimizing: the page comes back while the popup flies to the dock. */
104
+ .nx-popup[data-nx-minimizing] {
105
+ pointer-events: none;
106
+ }
107
+
108
+ .nx-popup[data-nx-minimizing]::backdrop {
109
+ animation: nx-popup-out 200ms ease-in forwards;
110
+ }
111
+
112
+ /* Header, either design: the title bar moves the popup, so it selects no text and a touch drags instead of scrolling. */
113
+ .nx-popup__head {
114
+ display: flex;
115
+ flex-shrink: 0;
116
+ }
117
+
118
+ .nx-popup--movable .nx-popup__head {
119
+ cursor: move;
120
+ touch-action: none;
121
+ user-select: none;
122
+ }
123
+
124
+ .nx-popup__title,
125
+ .nx-popup__description {
126
+ margin: 0;
127
+ }
128
+
129
+ .nx-popup__controls {
130
+ display: flex;
131
+ flex-shrink: 0;
132
+ margin-left: auto;
133
+ }
134
+
135
+ .nx-popup__control {
136
+ display: grid;
137
+ place-items: center;
138
+ padding: 0;
139
+ border: 0;
140
+ background: none;
141
+ color: var(--nx-text-muted);
142
+ cursor: pointer;
143
+ transition:
144
+ background-color var(--nx-duration),
145
+ color var(--nx-duration);
146
+ }
147
+
148
+ .nx-popup__control .nx-icon {
149
+ vertical-align: 0;
150
+ }
151
+
152
+ /* Close turns red on hover, the one button that throws away what the popup holds. */
153
+ .nx-popup__close:hover {
154
+ background: var(--nx-danger-solid);
155
+ color: var(--nx-danger-text);
156
+ }
157
+
158
+ /*
159
+ * Window (the default): one low title bar, as on a desktop window. The icon and the title on the left, the
160
+ * description after the title in muted text, and the window buttons filling the top right corner.
161
+ */
162
+ .nx-popup--window {
163
+ --nx-popup-bar: 32px;
164
+ }
165
+
166
+ .nx-popup--window .nx-popup__head {
167
+ box-sizing: content-box;
168
+ align-items: center;
169
+ gap: var(--nx-space-2);
170
+ height: var(--nx-popup-bar);
171
+ padding-left: var(--nx-space-3);
172
+ border-bottom: 1px solid var(--nx-border);
173
+ background: color-mix(in oklab, var(--nx-surface), var(--nx-text) 4%);
174
+ }
175
+
176
+ .nx-popup--window .nx-popup__icon {
177
+ display: grid;
178
+ flex-shrink: 0;
179
+ place-items: center;
180
+ color: var(--nx-primary);
181
+ }
182
+
183
+ .nx-popup--window .nx-popup__icon .nx-icon {
184
+ width: 16px;
185
+ height: 16px;
186
+ vertical-align: 0;
187
+ }
188
+
189
+ /* One line: the title keeps its room, the description takes what is left, and both end in … when cut. */
190
+ .nx-popup--window .nx-popup__heading {
191
+ display: flex;
192
+ flex: 1 1 auto;
193
+ align-items: baseline;
194
+ gap: var(--nx-space-2);
195
+ min-width: 0;
196
+ white-space: nowrap;
197
+ }
198
+
199
+ .nx-popup--window .nx-popup__title {
200
+ flex: 0 1 auto;
201
+ min-width: 0;
202
+ overflow: hidden;
203
+ font-size: var(--nx-text-sm);
204
+ font-weight: 600;
205
+ line-height: var(--nx-popup-bar);
206
+ text-overflow: ellipsis;
207
+ }
208
+
209
+ .nx-popup--window .nx-popup__description {
210
+ flex: 1 1 0;
211
+ min-width: 0;
212
+ overflow: hidden;
213
+ color: var(--nx-text-muted);
214
+ font-size: var(--nx-text-xs);
215
+ text-overflow: ellipsis;
216
+ }
217
+
218
+ .nx-popup--window .nx-popup__controls {
219
+ align-self: stretch;
220
+ }
221
+
222
+ .nx-popup--window .nx-popup__control {
223
+ width: 46px;
224
+ height: 100%;
225
+ }
226
+
227
+ .nx-popup--window .nx-popup__control:hover {
228
+ background: color-mix(in oklab, var(--nx-text) 9%, transparent);
229
+ color: var(--nx-text);
230
+ }
231
+
232
+ .nx-popup--window .nx-popup__control:active {
233
+ background: color-mix(in oklab, var(--nx-text) 15%, transparent);
234
+ }
235
+
236
+ .nx-popup--window .nx-popup__close:hover {
237
+ background: var(--nx-danger-solid);
238
+ color: var(--nx-danger-text);
239
+ }
240
+
241
+ .nx-popup--window .nx-popup__close:active {
242
+ background: var(--nx-danger-hover);
243
+ }
244
+
245
+ /* The buttons touch the popup's edges, where an outer ring would be cut off, so the ring is drawn inside. */
246
+ .nx-popup--window .nx-popup__control:focus-visible {
247
+ outline: none;
248
+ box-shadow: inset 0 0 0 2px var(--nx-primary);
249
+ }
250
+
251
+ /* Thin lines, as desktop window buttons have; the square glyphs fill more of their box, so they are drawn smaller. */
252
+ .nx-popup--window .nx-popup__control .nx-icon {
253
+ width: 16px;
254
+ height: 16px;
255
+ stroke-width: 1.5;
256
+ }
257
+
258
+ .nx-popup--window .nx-popup__maximize .nx-icon {
259
+ width: 13px;
260
+ height: 13px;
261
+ stroke-width: 1.75;
262
+ }
263
+
264
+ /* Touch screens get taller buttons. */
265
+ @media (pointer: coarse) {
266
+ .nx-popup--window {
267
+ --nx-popup-bar: 40px;
268
+ }
269
+
270
+ .nx-popup--window .nx-popup__control {
271
+ width: 48px;
272
+ }
273
+ }
274
+
275
+ /*
276
+ * Dialog (message boxes): a larger heading, the icon in a circle that carries the tone, the description under the
277
+ * title, and a small close button.
278
+ */
279
+ .nx-popup--dialog .nx-popup__head {
280
+ align-items: flex-start;
281
+ gap: var(--nx-space-3);
282
+ padding: var(--nx-space-5) var(--nx-space-5) 0;
283
+ }
284
+
285
+ .nx-popup--dialog .nx-popup__icon {
286
+ display: grid;
287
+ flex-shrink: 0;
288
+ place-items: center;
289
+ width: 40px;
290
+ height: 40px;
291
+ border-radius: 50%;
292
+ background: var(--nx-primary-soft);
293
+ color: var(--nx-primary);
294
+ }
295
+
296
+ .nx-popup--dialog .nx-popup__icon .nx-icon {
297
+ width: 22px;
298
+ height: 22px;
299
+ vertical-align: 0;
300
+ }
301
+
302
+ .nx-popup--dialog .nx-popup__heading {
303
+ flex: 1 1 auto;
304
+ min-width: 0;
305
+ padding-top: 2px;
306
+ }
307
+
308
+ .nx-popup--dialog .nx-popup__title {
309
+ font-size: var(--nx-text-lg);
310
+ font-weight: 600;
311
+ line-height: 1.35;
312
+ overflow-wrap: anywhere;
313
+ }
314
+
315
+ .nx-popup--dialog .nx-popup__description {
316
+ margin-top: 4px;
317
+ color: var(--nx-text-muted);
318
+ font-size: var(--nx-text-sm);
319
+ line-height: 1.45;
320
+ }
321
+
322
+ .nx-popup--dialog .nx-popup__controls {
323
+ gap: 2px;
324
+ margin: -6px -8px 0 auto;
325
+ }
326
+
327
+ .nx-popup--dialog .nx-popup__control {
328
+ width: 30px;
329
+ height: 30px;
330
+ border-radius: var(--nx-radius);
331
+ }
332
+
333
+ .nx-popup--dialog .nx-popup__control:not(.nx-popup__close):hover {
334
+ background: var(--nx-surface-hover);
335
+ color: var(--nx-text);
336
+ }
337
+
338
+ .nx-popup--dialog .nx-popup__control:focus-visible {
339
+ outline: none;
340
+ box-shadow: var(--nx-focus-ring);
341
+ }
342
+
343
+ .nx-popup--dialog .nx-popup__control .nx-icon {
344
+ width: 18px;
345
+ height: 18px;
346
+ }
347
+
348
+ .nx-popup--dialog .nx-popup__maximize .nx-icon {
349
+ width: 15px;
350
+ height: 15px;
351
+ }
352
+
353
+ /* Body: scrolls when the content is taller than the window allows. */
354
+ .nx-popup__body {
355
+ flex: 1 1 auto;
356
+ min-height: 0;
357
+ padding: var(--nx-space-4) var(--nx-space-5) var(--nx-space-5);
358
+ overflow: auto;
359
+ overscroll-behavior: contain;
360
+ }
361
+
362
+ .nx-popup__head + .nx-popup__body:empty {
363
+ padding-top: var(--nx-space-2);
364
+ }
365
+
366
+ .nx-popup__body:only-child,
367
+ .nx-popup--window .nx-popup__body {
368
+ padding-top: var(--nx-space-5);
369
+ }
370
+
371
+ .nx-popup--flush .nx-popup__body {
372
+ padding: 0;
373
+ }
374
+
375
+ /* Footer: buttons at the end. */
376
+ .nx-popup__foot {
377
+ display: flex;
378
+ flex-shrink: 0;
379
+ flex-wrap: wrap;
380
+ align-items: center;
381
+ justify-content: flex-end;
382
+ gap: var(--nx-space-2);
383
+ padding: var(--nx-space-3) var(--nx-space-5);
384
+ border-top: 1px solid var(--nx-border);
385
+ }
386
+
387
+ /* A form's bar fills the foot, so its status line sits beside the buttons instead of on a row of its own. */
388
+ .nx-popup__foot > .nx-form-buttons {
389
+ flex: 1 1 auto;
390
+ }
391
+
392
+ /* Message boxes: a small popup whose icon carries the tone. */
393
+ .nx-popup--message .nx-popup__body {
394
+ padding-top: var(--nx-space-2);
395
+ }
396
+
397
+ .nx-popup--message .nx-popup__body:empty {
398
+ padding: var(--nx-space-3) 0 0;
399
+ }
400
+
401
+ .nx-message__text {
402
+ color: var(--nx-text-muted);
403
+ line-height: 1.5;
404
+ }
405
+
406
+ .nx-message__form {
407
+ display: grid;
408
+ gap: var(--nx-space-4);
409
+ }
410
+
411
+ .nx-popup--tone-info .nx-popup__icon {
412
+ background: var(--nx-info-soft);
413
+ color: var(--nx-info);
414
+ }
415
+
416
+ .nx-popup--tone-success .nx-popup__icon {
417
+ background: var(--nx-success-soft);
418
+ color: var(--nx-success);
419
+ }
420
+
421
+ .nx-popup--tone-warning .nx-popup__icon {
422
+ background: var(--nx-warning-soft);
423
+ color: var(--nx-warning);
424
+ }
425
+
426
+ .nx-popup--tone-danger .nx-popup__icon {
427
+ background: var(--nx-danger-soft);
428
+ color: var(--nx-danger);
429
+ }
430
+
431
+ /*
432
+ * The dock: minimized popups as bars at the bottom right. Several stack behind the newest, with cards peeking out
433
+ * above it and a count that opens the whole list. It sits on the page, so an open popup's backdrop covers it.
434
+ */
435
+ .nx-dock {
436
+ --nx-dock-edge: 16px;
437
+
438
+ position: fixed;
439
+ right: calc(var(--nx-dock-edge) + env(safe-area-inset-right, 0px));
440
+ bottom: calc(var(--nx-dock-edge) + env(safe-area-inset-bottom, 0px));
441
+ z-index: 1000;
442
+ display: flex;
443
+ flex-direction: column;
444
+ align-items: flex-end;
445
+ gap: var(--nx-space-2);
446
+ width: min(300px, calc(100% - 2 * var(--nx-dock-edge)));
447
+ max-height: calc(100dvh - 2 * var(--nx-dock-edge));
448
+ /* Only the bars and the count take clicks, not the space between them. */
449
+ pointer-events: none;
450
+ }
451
+
452
+ .nx-dock > * {
453
+ pointer-events: auto;
454
+ }
455
+
456
+ /* While a popup locks the page, its scrollbar's space is padding; the dock keeps its place beside it. */
457
+ :root:has(.nx-popup:modal) .nx-dock {
458
+ right: calc(var(--nx-dock-edge) + var(--nx-scrollbar-gap, 0px) + env(safe-area-inset-right, 0px));
459
+ }
460
+
461
+ .nx-dock__toggle {
462
+ display: inline-flex;
463
+ flex-shrink: 0;
464
+ align-items: center;
465
+ gap: 6px;
466
+ height: 28px;
467
+ padding: 0 10px 0 4px;
468
+ border: 1px solid var(--nx-border);
469
+ border-radius: var(--nx-radius-full);
470
+ background: light-dark(var(--nx-surface), var(--nx-surface-2));
471
+ color: var(--nx-text);
472
+ box-shadow: var(--nx-shadow-sm);
473
+ font: inherit;
474
+ font-size: var(--nx-text-sm);
475
+ font-weight: 600;
476
+ cursor: pointer;
477
+ transition: background-color var(--nx-duration);
478
+ }
479
+
480
+ .nx-dock__toggle:hover {
481
+ background: var(--nx-surface-hover);
482
+ }
483
+
484
+ .nx-dock__toggle:focus-visible {
485
+ outline: none;
486
+ box-shadow: var(--nx-focus-ring);
487
+ }
488
+
489
+ .nx-dock__toggle .nx-icon {
490
+ width: 16px;
491
+ height: 16px;
492
+ color: var(--nx-text-muted);
493
+ }
494
+
495
+ .nx-dock__count {
496
+ display: grid;
497
+ place-items: center;
498
+ min-width: 20px;
499
+ height: 20px;
500
+ padding: 0 6px;
501
+ border-radius: var(--nx-radius-full);
502
+ background: var(--nx-primary-solid);
503
+ color: var(--nx-primary-text);
504
+ font-size: var(--nx-text-xs);
505
+ font-variant-numeric: tabular-nums;
506
+ line-height: 1;
507
+ }
508
+
509
+ .nx-dock__list {
510
+ position: relative;
511
+ isolation: isolate;
512
+ display: flex;
513
+ flex-direction: column;
514
+ gap: 6px;
515
+ width: 100%;
516
+ min-height: 0;
517
+ margin: 0;
518
+ padding: 0;
519
+ list-style: none;
520
+ }
521
+
522
+ /* Open: the whole list, oldest at the top; it scrolls when taller than the window, with room for the shadows. */
523
+ .nx-dock--open .nx-dock__list {
524
+ margin: -8px;
525
+ padding: 8px;
526
+ width: calc(100% + 16px);
527
+ overflow-y: auto;
528
+ overscroll-behavior: contain;
529
+ }
530
+
531
+ /* Stacked: one or two cards peek out above the newest bar; a click on them opens the list. */
532
+ .nx-dock__list[data-depth] {
533
+ margin-top: calc(var(--nx-dock-depth) * 5px);
534
+ cursor: pointer;
535
+ }
536
+
537
+ .nx-dock__list[data-depth="1"] {
538
+ --nx-dock-depth: 1;
539
+ }
540
+
541
+ .nx-dock__list[data-depth="2"] {
542
+ --nx-dock-depth: 2;
543
+ }
544
+
545
+ .nx-dock__list[data-depth]::before,
546
+ .nx-dock__list[data-depth="2"]::after {
547
+ content: "";
548
+ position: absolute;
549
+ top: -5px;
550
+ right: 8px;
551
+ left: 8px;
552
+ z-index: -1;
553
+ height: 24px;
554
+ border: 1px solid var(--nx-border);
555
+ border-radius: var(--nx-radius-lg);
556
+ background: light-dark(var(--nx-surface), var(--nx-surface-2));
557
+ box-shadow: var(--nx-shadow-sm);
558
+ }
559
+
560
+ .nx-dock__list[data-depth="2"]::after {
561
+ top: -10px;
562
+ right: 16px;
563
+ left: 16px;
564
+ z-index: -2;
565
+ }
566
+
567
+ .nx-dock__item {
568
+ display: flex;
569
+ flex-shrink: 0;
570
+ align-items: center;
571
+ gap: 2px;
572
+ height: 44px;
573
+ padding-right: 6px;
574
+ border: 1px solid var(--nx-border);
575
+ border-radius: var(--nx-radius-lg);
576
+ background: light-dark(var(--nx-surface), var(--nx-surface-2));
577
+ color: var(--nx-text);
578
+ box-shadow: var(--nx-shadow);
579
+ cursor: default;
580
+ transition: background-color var(--nx-duration);
581
+ animation: nx-dock-in 180ms cubic-bezier(0.2, 0, 0, 1);
582
+ }
583
+
584
+ .nx-dock__item:has(.nx-dock__window:hover) {
585
+ background: var(--nx-surface-hover);
586
+ }
587
+
588
+ .nx-dock__item:has(.nx-dock__window:focus-visible) {
589
+ box-shadow: var(--nx-focus-ring), var(--nx-shadow);
590
+ }
591
+
592
+ /* The bar's title part brings the popup back. */
593
+ .nx-dock__window {
594
+ display: flex;
595
+ flex: 1 1 auto;
596
+ align-items: center;
597
+ gap: 10px;
598
+ min-width: 0;
599
+ height: 100%;
600
+ padding: 0 6px 0 7px;
601
+ border: 0;
602
+ border-radius: inherit;
603
+ background: none;
604
+ color: inherit;
605
+ font: inherit;
606
+ text-align: start;
607
+ cursor: pointer;
608
+ }
609
+
610
+ .nx-dock__window:focus-visible {
611
+ outline: none;
612
+ }
613
+
614
+ .nx-dock__icon {
615
+ display: grid;
616
+ flex-shrink: 0;
617
+ place-items: center;
618
+ width: 28px;
619
+ height: 28px;
620
+ border-radius: var(--nx-radius);
621
+ background: var(--nx-primary-soft);
622
+ color: var(--nx-primary);
623
+ }
624
+
625
+ .nx-dock__icon .nx-icon {
626
+ width: 16px;
627
+ height: 16px;
628
+ vertical-align: 0;
629
+ }
630
+
631
+ .nx-dock__title {
632
+ flex: 1 1 auto;
633
+ min-width: 0;
634
+ overflow: hidden;
635
+ font-size: var(--nx-text-md);
636
+ font-weight: 600;
637
+ line-height: 1.3;
638
+ text-overflow: ellipsis;
639
+ white-space: nowrap;
640
+ }
641
+
642
+ .nx-dock__restore {
643
+ flex-shrink: 0;
644
+ width: 18px;
645
+ height: 18px;
646
+ color: var(--nx-text-muted);
647
+ }
648
+
649
+ .nx-dock__window:hover .nx-dock__restore {
650
+ color: var(--nx-text);
651
+ }
652
+
653
+ .nx-dock__close {
654
+ display: grid;
655
+ flex-shrink: 0;
656
+ place-items: center;
657
+ width: 30px;
658
+ height: 30px;
659
+ padding: 0;
660
+ border: 0;
661
+ border-radius: var(--nx-radius);
662
+ background: none;
663
+ color: var(--nx-text-muted);
664
+ cursor: pointer;
665
+ transition:
666
+ background-color var(--nx-duration),
667
+ color var(--nx-duration);
668
+ }
669
+
670
+ .nx-dock__close:hover {
671
+ background: var(--nx-danger-solid);
672
+ color: var(--nx-danger-text);
673
+ }
674
+
675
+ .nx-dock__close:focus-visible {
676
+ outline: none;
677
+ box-shadow: var(--nx-focus-ring);
678
+ }
679
+
680
+ .nx-dock__close .nx-icon {
681
+ width: 16px;
682
+ height: 16px;
683
+ vertical-align: 0;
684
+ }
685
+
686
+ @keyframes nx-dock-in {
687
+ from {
688
+ opacity: 0;
689
+ transform: translateY(8px);
690
+ }
691
+ }
692
+
693
+ @media (max-width: 600px) {
694
+ .nx-popup {
695
+ width: calc(100% - 16px);
696
+ max-height: min(calc(100vh - 16px), calc(100dvh - 16px));
697
+ }
698
+
699
+ .nx-popup--dialog .nx-popup__head {
700
+ padding: var(--nx-space-4) var(--nx-space-4) 0;
701
+ }
702
+
703
+ .nx-popup__body {
704
+ padding: var(--nx-space-3) var(--nx-space-4) var(--nx-space-4);
705
+ }
706
+
707
+ .nx-popup--window:not(.nx-popup--flush) .nx-popup__body {
708
+ padding-top: var(--nx-space-4);
709
+ }
710
+
711
+ .nx-popup__foot {
712
+ padding: var(--nx-space-3) var(--nx-space-4);
713
+ }
714
+ }
715
+
716
+ [data-nx-motion="reduce"] .nx-popup[open],
717
+ [data-nx-motion="reduce"] .nx-popup--closing[open],
718
+ [data-nx-motion="reduce"] .nx-popup::backdrop,
719
+ [data-nx-motion="reduce"] .nx-popup--closing::backdrop,
720
+ [data-nx-motion="reduce"] .nx-popup[data-nx-minimizing]::backdrop,
721
+ [data-nx-motion="reduce"] .nx-dock__item {
722
+ animation: none;
723
+ }
724
+ }