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,637 +1,602 @@
1
- @layer nexus.components {
2
- /*
3
- * Field: the frame of every input. Label, a box holding icons, the control, and buttons, then a hint or error line.
4
- * Sizes, designs, and states only change the --nx-field-* variables below; the rules after them read those.
5
- */
6
- .nx-field {
7
- --nx-field-base: var(--nx-control-md);
8
- --nx-field-extra: 0px;
9
- --nx-field-height: calc(var(--nx-field-base) + var(--nx-field-extra));
10
- --nx-field-pad-x: 10px;
11
- --nx-field-pad-y: 7px;
12
- --nx-field-font: var(--nx-text-md);
13
- --nx-field-label-font: var(--nx-text-sm);
14
- --nx-field-icon: 16px;
15
- --nx-field-bg: var(--nx-surface);
16
- --nx-field-border: var(--nx-border-strong);
17
- --nx-field-border-hover: color-mix(in oklab, var(--nx-border-strong), var(--nx-text) 22%);
18
- --nx-field-accent: var(--nx-primary);
19
- --nx-field-ring: var(--nx-focus-ring);
20
- --nx-field-label-color: var(--nx-text-muted);
21
- --nx-field-scrollbar: color-mix(in oklab, var(--nx-text-subtle) 75%, transparent);
22
- --nx-field-scrollbar-hover: var(--nx-text-muted);
23
-
24
- display: flex;
25
- flex-direction: column;
26
- min-width: 0;
27
- }
28
-
29
- .nx-field--sm {
30
- --nx-field-base: var(--nx-control-sm);
31
- --nx-field-pad-x: var(--nx-space-2);
32
- --nx-field-pad-y: 4px;
33
- --nx-field-font: var(--nx-text-sm);
34
- --nx-field-label-font: var(--nx-text-xs);
35
- --nx-field-icon: 14px;
36
- }
37
-
38
- .nx-field--lg {
39
- --nx-field-base: var(--nx-control-lg);
40
- --nx-field-pad-x: var(--nx-space-3);
41
- --nx-field-pad-y: 9px;
42
- --nx-field-font: var(--nx-text-lg);
43
- --nx-field-label-font: var(--nx-text-md);
44
- --nx-field-icon: 18px;
45
- }
46
-
47
- /* States */
48
- .nx-field:focus-within {
49
- --nx-field-label-color: var(--nx-field-accent);
50
- }
51
-
52
- .nx-field[data-invalid] {
53
- --nx-field-border: var(--nx-danger);
54
- --nx-field-border-hover: var(--nx-danger);
55
- --nx-field-accent: var(--nx-danger);
56
- --nx-field-ring: 0 0 0 3px color-mix(in oklab, var(--nx-danger) 30%, transparent);
57
- --nx-field-label-color: var(--nx-danger);
58
- }
59
-
60
- /* Read-only and disabled boxes sit lower than the panel: lighter gray in light mode, the page color in dark mode. */
61
- .nx-field:is([data-readonly], [data-disabled]) {
62
- --nx-field-bg: light-dark(var(--nx-surface-2), var(--nx-bg));
63
- --nx-field-border-hover: var(--nx-field-border);
64
- }
65
-
66
- .nx-field:is([data-readonly], [data-disabled]) .nx-field__box {
67
- box-shadow: none;
68
- }
69
-
70
- .nx-field[data-disabled] {
71
- opacity: 0.6;
72
- }
73
-
74
- .nx-field[data-disabled] :is(.nx-field__label, .nx-field__box, .nx-field__control) {
75
- cursor: not-allowed;
76
- }
77
-
78
- /* Label above the box */
79
- .nx-field__label {
80
- color: var(--nx-text);
81
- font-size: var(--nx-field-label-font);
82
- font-weight: 500;
83
- line-height: 1.3;
84
- }
85
-
86
- .nx-field--standard > .nx-field__label {
87
- align-self: flex-start;
88
- margin-bottom: 6px;
89
- }
90
-
91
- .nx-field__required {
92
- margin-left: 3px;
93
- color: var(--nx-danger);
94
- }
95
-
96
- /* Box */
97
- .nx-field__box {
98
- position: relative;
99
- display: flex;
100
- align-items: center;
101
- min-height: var(--nx-field-height);
102
- border: 1px solid var(--nx-field-border);
103
- border-radius: var(--nx-radius);
104
- background: var(--nx-field-bg);
105
- box-shadow: var(--nx-shadow-sm);
106
- font-size: var(--nx-field-font);
107
- transition:
108
- border-color var(--nx-duration),
109
- box-shadow var(--nx-duration);
110
- }
111
-
112
- .nx-field__box:hover {
113
- border-color: var(--nx-field-border-hover);
114
- }
115
-
116
- .nx-field__box:focus-within {
117
- border-color: var(--nx-field-accent);
118
- box-shadow: var(--nx-field-ring);
119
- }
120
-
121
- .nx-field__control {
122
- flex: 1 1 auto;
123
- align-self: stretch;
124
- width: 100%;
125
- min-width: 0;
126
- margin: 0;
127
- padding: 0 var(--nx-field-pad-x);
128
- border: 0;
129
- border-radius: inherit;
130
- outline: none;
131
- background: transparent;
132
- color: var(--nx-text);
133
- font-size: var(--nx-field-font);
134
- line-height: 1.4;
135
- text-overflow: ellipsis;
136
- }
137
-
138
- /* Numbers line up digit under digit. */
139
- .nx-field__control--number {
140
- font-variant-numeric: tabular-nums;
141
- }
142
-
143
- .nx-field__control::placeholder {
144
- color: var(--nx-text-subtle);
145
- opacity: 1;
146
- }
147
-
148
- /* Browser extras that the field replaces with its own buttons. */
149
- .nx-field__control::-webkit-search-cancel-button,
150
- .nx-field__control::-webkit-search-decoration {
151
- appearance: none;
152
- }
153
-
154
- .nx-field__control::-ms-reveal,
155
- .nx-field__control::-ms-clear {
156
- display: none;
157
- }
158
-
159
- /* Autofilled values keep the field's own colors. */
160
- .nx-field__control:is(:autofill, :-webkit-autofill) {
161
- -webkit-text-fill-color: var(--nx-text);
162
- caret-color: var(--nx-text);
163
- box-shadow: 0 0 0 100px var(--nx-field-bg) inset;
164
- }
165
-
166
- /* Icons */
167
- .nx-field__icon {
168
- display: flex;
169
- align-items: center;
170
- flex-shrink: 0;
171
- padding-left: var(--nx-field-pad-x);
172
- color: var(--nx-text-subtle);
173
- pointer-events: none;
174
- transition: color var(--nx-duration);
175
- }
176
-
177
- .nx-field__icon .nx-icon,
178
- .nx-field__action .nx-icon,
179
- .nx-field__lock .nx-icon {
180
- width: var(--nx-field-icon);
181
- height: var(--nx-field-icon);
182
- vertical-align: 0;
183
- }
184
-
185
- .nx-field__icon--end {
186
- padding-right: var(--nx-field-pad-x);
187
- padding-left: 0;
188
- }
189
-
190
- .nx-field__box:focus-within .nx-field__icon,
191
- .nx-field[data-invalid] .nx-field__icon {
192
- color: var(--nx-field-accent);
193
- }
194
-
195
- .nx-field__icon:not(.nx-field__icon--end) + .nx-field__control {
196
- padding-left: var(--nx-space-2);
197
- }
198
-
199
- .nx-field__control:has(~ .nx-field__actions, ~ .nx-field__icon--end) {
200
- padding-right: var(--nx-space-1);
201
- }
202
-
203
- /* Text before and after the value: a currency, a unit, a domain. Muted, so the value reads first. */
204
- .nx-field__affix {
205
- display: flex;
206
- align-items: center;
207
- align-self: stretch;
208
- flex-shrink: 0;
209
- padding-left: var(--nx-field-pad-x);
210
- color: var(--nx-text-muted);
211
- line-height: 1.4;
212
- white-space: nowrap;
213
- user-select: none;
214
- }
215
-
216
- .nx-field__affix--end {
217
- padding-right: var(--nx-field-pad-x);
218
- padding-left: 0;
219
- }
220
-
221
- .nx-field__icon:not(.nx-field__icon--end) + .nx-field__affix {
222
- padding-left: var(--nx-space-2);
223
- }
224
-
225
- .nx-field__affix:not(.nx-field__affix--end) + .nx-field__control {
226
- padding-left: 6px;
227
- }
228
-
229
- .nx-field__control:has(~ .nx-field__affix--end) {
230
- padding-right: 6px;
231
- }
232
-
233
- .nx-field__affix--end:has(~ .nx-field__actions, ~ .nx-field__icon--end) {
234
- padding-right: var(--nx-space-1);
235
- }
236
-
237
- /* Buttons inside the box: clear, show password, − and + */
238
- .nx-field__actions {
239
- display: flex;
240
- align-items: center;
241
- flex-shrink: 0;
242
- gap: 2px;
243
- padding-right: 4px;
244
- }
245
-
246
- .nx-field__actions--start {
247
- padding-right: 0;
248
- padding-left: 4px;
249
- }
250
-
251
- .nx-field__actions + .nx-field__icon--end {
252
- padding-left: var(--nx-space-1);
253
- }
254
-
255
- .nx-field__action {
256
- display: grid;
257
- place-items: center;
258
- width: calc(var(--nx-field-base) - 10px);
259
- height: calc(var(--nx-field-base) - 10px);
260
- padding: 0;
261
- border: 0;
262
- border-radius: var(--nx-radius-sm);
263
- background: none;
264
- color: var(--nx-text-subtle);
265
- cursor: pointer;
266
- transition:
267
- background-color var(--nx-duration),
268
- color var(--nx-duration);
269
- }
270
-
271
- .nx-field__action:hover:not(:disabled, [aria-disabled="true"]) {
272
- background: var(--nx-surface-hover);
273
- color: var(--nx-text);
274
- }
275
-
276
- .nx-field__action:focus-visible {
277
- outline: 2px solid var(--nx-primary);
278
- outline-offset: -2px;
279
- }
280
-
281
- .nx-field__action[aria-pressed="true"] {
282
- color: var(--nx-field-accent);
283
- }
284
-
285
- .nx-field__action:disabled {
286
- cursor: not-allowed;
287
- }
288
-
289
- /* − and +: stronger than the clear button, since they are the main way to change the value by pointer. */
290
- .nx-field__action--step {
291
- color: var(--nx-text-muted);
292
- touch-action: manipulation;
293
- user-select: none;
294
- -webkit-touch-callout: none;
295
- }
296
-
297
- /* At a limit: dimmed, and the press does nothing. Not `disabled`, which would lose the pointer while it is held. */
298
- .nx-field__action[aria-disabled="true"] {
299
- opacity: 0.4;
300
- cursor: default;
301
- }
302
-
303
- /* Split stepper: − before and + after a centered value, as in a guest or quantity picker. */
304
- .nx-field--split .nx-field__control {
305
- padding-inline: var(--nx-space-1);
306
- text-align: center;
307
- }
308
-
309
- /*
310
- * Read-only lock: at the end of the box, where a button that no longer works would be (a date picker's calendar, a
311
- * number box's − and +), so a click that does nothing does not look like a bug. Muted rather than subtle, since it
312
- * is the only sign of the state: at least 3:1 on the box in every theme. On an attempt to change the field it turns
313
- * to the text color and nudges.
314
- */
315
- .nx-field__lock {
316
- display: grid;
317
- place-items: center;
318
- width: calc(var(--nx-field-base) - 10px);
319
- height: calc(var(--nx-field-base) - 10px);
320
- color: var(--nx-text-muted);
321
- cursor: default;
322
- }
323
-
324
- /* After the label of a field without a box: a checkbox, a switch, a choice group, a slider. Sized to the label text. */
325
- .nx-field__lock--label {
326
- display: inline-block;
327
- width: auto;
328
- height: auto;
329
- margin-left: 0.35em;
330
- }
331
-
332
- .nx-field__lock--label .nx-icon {
333
- width: 1em;
334
- height: 1em;
335
- }
336
-
337
- .nx-field__lock[data-nudge] {
338
- color: var(--nx-text);
339
- animation: nx-field-nudge 320ms ease-in-out;
340
- }
341
-
342
- @keyframes nx-field-nudge {
343
- 20%,
344
- 60% {
345
- transform: translateX(-2px);
346
- }
347
-
348
- 40%,
349
- 80% {
350
- transform: translateX(2px);
351
- }
352
- }
353
-
354
- /* The note under a read-only box, in the top layer and placed by the component. Inverted, like a tooltip. */
355
- .nx-field-notice {
356
- position: fixed;
357
- inset: auto;
358
- display: flex;
359
- align-items: center;
360
- gap: 6px;
361
- max-width: min(320px, calc(100vw - 16px));
362
- margin: 0;
363
- padding: 6px 10px 6px 8px;
364
- overflow: visible;
365
- border: 0;
366
- border-radius: var(--nx-radius);
367
- background: var(--nx-text);
368
- color: var(--nx-surface);
369
- box-shadow: var(--nx-shadow);
370
- font-size: var(--nx-text-xs);
371
- font-weight: 500;
372
- line-height: 1.35;
373
- pointer-events: none;
374
- }
375
-
376
- .nx-field-notice:popover-open {
377
- animation: nx-field-notice-in 120ms ease-out;
378
- }
379
-
380
- @keyframes nx-field-notice-in {
381
- from {
382
- opacity: 0;
383
- transform: translateY(-4px);
384
- }
385
- }
386
-
387
- .nx-field-notice .nx-icon {
388
- flex-shrink: 0;
389
- width: 14px;
390
- height: 14px;
391
- vertical-align: 0;
392
- }
393
-
394
- /* Nothing to clear in an empty field. The control always has a placeholder, so :placeholder-shown means empty. */
395
- .nx-field__box:has(.nx-field__control:placeholder-shown) .nx-field__action--clear {
396
- display: none;
397
- }
398
-
399
- /* Hint, error, and count under the box */
400
- .nx-field__footer {
401
- display: flex;
402
- align-items: flex-start;
403
- gap: var(--nx-space-3);
404
- margin-top: 5px;
405
- font-size: var(--nx-text-xs);
406
- line-height: 1.35;
407
- }
408
-
409
- .nx-field__message {
410
- display: flex;
411
- gap: 5px;
412
- min-width: 0;
413
- margin: 0;
414
- color: var(--nx-text-muted);
415
- }
416
-
417
- .nx-field__message .nx-icon {
418
- flex-shrink: 0;
419
- width: 1.35em;
420
- height: 1.35em;
421
- vertical-align: 0;
422
- }
423
-
424
- .nx-field__message--error {
425
- color: var(--nx-danger);
426
- font-weight: 500;
427
- }
428
-
429
- .nx-field__aside {
430
- margin-left: auto;
431
- color: var(--nx-text-muted);
432
- font-variant-numeric: tabular-nums;
433
- white-space: nowrap;
434
- }
435
-
436
- /* Outlined and inbox: the label rests inside the box like a placeholder and rises on focus or once there is a value. */
437
- :is(.nx-field--outlined, .nx-field--inbox) .nx-field__label {
438
- position: absolute;
439
- z-index: 1;
440
- top: 50%;
441
- left: var(--nx-field-pad-x);
442
- max-width: calc(100% - 2 * var(--nx-field-pad-x));
443
- overflow: hidden;
444
- color: var(--nx-field-label-color);
445
- font-size: var(--nx-field-font);
446
- font-weight: 400;
447
- line-height: 1.2;
448
- text-overflow: ellipsis;
449
- white-space: nowrap;
450
- transform: translateY(-50%);
451
- pointer-events: none;
452
- transition:
453
- top var(--nx-duration),
454
- left var(--nx-duration),
455
- font-size var(--nx-duration),
456
- color var(--nx-duration),
457
- transform var(--nx-duration);
458
- }
459
-
460
- /* After a start icon, the resting label lines up with the text. */
461
- .nx-field__icon:not(.nx-field__icon--end) ~ .nx-field__label {
462
- left: calc(var(--nx-field-pad-x) + var(--nx-field-icon) + var(--nx-space-2));
463
- }
464
-
465
- /* On a multi-line box, the resting label sits on the first line. */
466
- .nx-field--multiline .nx-field__label {
467
- top: calc(var(--nx-field-pad-y) + var(--nx-field-font) * 0.7);
468
- }
469
-
470
- /* Outlined: the raised label sits on the top border. Its backing covers only the lower half, so it blends with any panel. */
471
- .nx-field--outlined:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__label {
472
- top: 0;
473
- left: calc(var(--nx-field-pad-x) - 4px);
474
- padding: 0 4px;
475
- background: linear-gradient(to bottom, transparent calc(50% - 1px), var(--nx-field-bg) calc(50% - 1px));
476
- font-size: var(--nx-text-xs);
477
- font-weight: 500;
478
- }
479
-
480
- /* Inbox: a taller box with the raised label inside its top edge. */
481
- .nx-field--inbox {
482
- --nx-field-extra: 12px;
483
- --nx-field-inbox-pad: 17px;
484
- }
485
-
486
- .nx-field--inbox.nx-field--sm {
487
- --nx-field-inbox-pad: 15px;
488
- }
489
-
490
- .nx-field--inbox.nx-field--lg {
491
- --nx-field-inbox-pad: 19px;
492
- }
493
-
494
- .nx-field--inbox .nx-field__control {
495
- padding-top: var(--nx-field-inbox-pad);
496
- }
497
-
498
- /*
499
- * Beside the value: while the label rests in the middle, icons, text, and buttons line up with it. Once the label
500
- * rises, the value sits low in the box, and they move down to its line. Buttons move halfway, so their hover
501
- * background keeps clear of the border.
502
- */
503
- .nx-field--inbox :is(.nx-field__icon, .nx-field__affix, .nx-field__actions) {
504
- align-self: stretch;
505
- transition: padding-top var(--nx-duration);
506
- }
507
-
508
- .nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) :is(.nx-field__icon, .nx-field__affix) {
509
- padding-top: var(--nx-field-inbox-pad);
510
- }
511
-
512
- .nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__actions {
513
- padding-top: calc(var(--nx-field-inbox-pad) / 2);
514
- }
515
-
516
- .nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__label {
517
- top: 5px;
518
- font-size: var(--nx-text-xs);
519
- font-weight: 500;
520
- transform: none;
521
- }
522
-
523
- /*
524
- * Split stepper: the label sits centered over the centered value, between − and +.
525
- * It must come after the raised-label rule above: both have the same specificity, so the later one wins.
526
- */
527
- .nx-field--inbox.nx-field--split[data-raised] .nx-field__label {
528
- left: 50%;
529
- max-width: calc(100% - 2 * var(--nx-field-base));
530
- transform: translateX(-50%);
531
- }
532
-
533
- /* Multi-line */
534
- .nx-field--multiline .nx-field__box {
535
- align-items: stretch;
536
- }
537
-
538
- .nx-field--multiline .nx-field__control {
539
- padding: var(--nx-field-pad-y) var(--nx-field-pad-x);
540
- font-family: inherit;
541
- resize: vertical;
542
- }
543
-
544
- /* Buttons and the read-only lock sit at the top end, centered on the first line (line height 1.4). */
545
- .nx-field--multiline .nx-field__actions {
546
- align-items: flex-start;
547
- padding-top: calc(var(--nx-field-pad-y) + 0.7em - (var(--nx-field-base) - 10px) / 2);
548
- }
549
-
550
- /*
551
- * Thin scrollbar: 8px instead of the default 15px, a rounded thumb in theme colors, no arrow buttons.
552
- * Chromium ignores ::-webkit-scrollbar once scrollbar-width is set, so the standard properties go only to
553
- * browsers without it (Firefox, where "thin" is also 8px).
554
- */
555
- .nx-field--multiline .nx-field__control::-webkit-scrollbar {
556
- width: 8px;
557
- height: 8px;
558
- }
559
-
560
- .nx-field--multiline .nx-field__control::-webkit-scrollbar-track,
561
- .nx-field--multiline .nx-field__control::-webkit-scrollbar-corner {
562
- background: transparent;
563
- }
564
-
565
- .nx-field--multiline .nx-field__control::-webkit-scrollbar-thumb {
566
- border: 2px solid transparent;
567
- border-radius: var(--nx-radius-full);
568
- background: var(--nx-field-scrollbar) padding-box;
569
- }
570
-
571
- .nx-field--multiline .nx-field__control::-webkit-scrollbar-thumb:hover {
572
- background-color: var(--nx-field-scrollbar-hover);
573
- }
574
-
575
- /* A styled scrollbar drops the browser's resize grip, which turns light in dark mode: draw two grip lines instead. */
576
- .nx-field--multiline .nx-field__control::-webkit-resizer {
577
- background: linear-gradient(
578
- 135deg,
579
- transparent 0 48%,
580
- var(--nx-field-scrollbar) 48% 56%,
581
- transparent 56% 70%,
582
- var(--nx-field-scrollbar) 70% 78%,
583
- transparent 78%
584
- );
585
- }
586
-
587
- @supports not selector(::-webkit-scrollbar) {
588
- .nx-field--multiline .nx-field__control {
589
- scrollbar-width: thin;
590
- scrollbar-color: var(--nx-field-scrollbar) transparent;
591
- }
592
- }
593
-
594
- .nx-field--multiline.nx-field--inbox .nx-field__control {
595
- padding-top: calc(var(--nx-field-pad-y) + var(--nx-field-inbox-pad) - 4px);
596
- }
597
-
598
- .nx-field--multiline .nx-field__icon {
599
- align-items: flex-start;
600
- padding-top: calc(var(--nx-field-pad-y) + (1.4em - var(--nx-field-icon)) / 2);
601
- }
602
-
603
- .nx-field--multiline.nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__icon {
604
- padding-top: calc(var(--nx-field-pad-y) + var(--nx-field-inbox-pad) - 4px + (1.4em - var(--nx-field-icon)) / 2);
605
- }
606
-
607
- .nx-field--multiline.nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__actions {
608
- padding-top: calc(var(--nx-field-pad-y) + var(--nx-field-inbox-pad) - 4px + 0.7em - (var(--nx-field-base) - 10px) / 2);
609
- }
610
-
611
- /* Grows with its text between --nx-field-rows and --nx-field-max-rows lines. */
612
- .nx-field__control--auto {
613
- field-sizing: content;
614
- min-height: calc(var(--nx-field-rows, 3) * 1lh + 2 * var(--nx-field-pad-y));
615
- max-height: calc(var(--nx-field-max-rows, 12) * 1lh + 2 * var(--nx-field-pad-y));
616
- resize: none;
617
- }
618
-
619
- @supports not (field-sizing: content) {
620
- .nx-field__control--auto {
621
- max-height: none;
622
- resize: vertical;
623
- }
624
- }
625
-
626
- @media (prefers-reduced-motion: reduce) {
627
- .nx-field__label,
628
- .nx-field--inbox :is(.nx-field__icon, .nx-field__affix, .nx-field__actions) {
629
- transition: none;
630
- }
631
-
632
- .nx-field__lock[data-nudge],
633
- .nx-field-notice:popover-open {
634
- animation: none;
635
- }
636
- }
637
- }
1
+ @layer nexus.components {
2
+ /*
3
+ * Field: the frame of every input. Label, a box holding icons, the control, and buttons, then a hint or error line.
4
+ * Sizes, designs, and states only change the --nx-field-* variables below; the rules after them read those.
5
+ */
6
+ .nx-field {
7
+ --nx-field-base: var(--nx-control-md);
8
+ --nx-field-extra: 0px;
9
+ --nx-field-height: calc(var(--nx-field-base) + var(--nx-field-extra));
10
+ --nx-field-pad-x: 10px;
11
+ --nx-field-pad-y: 7px;
12
+ --nx-field-font: var(--nx-text-md);
13
+ --nx-field-label-font: var(--nx-text-sm);
14
+ --nx-field-icon: 16px;
15
+ --nx-field-bg: var(--nx-surface);
16
+ --nx-field-border: var(--nx-border-strong);
17
+ --nx-field-border-hover: color-mix(in oklab, var(--nx-border-strong), var(--nx-text) 22%);
18
+ --nx-field-accent: var(--nx-primary);
19
+ --nx-field-ring: var(--nx-focus-ring);
20
+ --nx-field-label-color: var(--nx-text-muted);
21
+ --nx-field-grip: color-mix(in oklab, var(--nx-text-subtle) 75%, transparent);
22
+
23
+ display: flex;
24
+ flex-direction: column;
25
+ min-width: 0;
26
+ }
27
+
28
+ .nx-field--sm {
29
+ --nx-field-base: var(--nx-control-sm);
30
+ --nx-field-pad-x: var(--nx-space-2);
31
+ --nx-field-pad-y: 4px;
32
+ --nx-field-font: var(--nx-text-sm);
33
+ --nx-field-label-font: var(--nx-text-xs);
34
+ --nx-field-icon: 14px;
35
+ }
36
+
37
+ .nx-field--lg {
38
+ --nx-field-base: var(--nx-control-lg);
39
+ --nx-field-pad-x: var(--nx-space-3);
40
+ --nx-field-pad-y: 9px;
41
+ --nx-field-font: var(--nx-text-lg);
42
+ --nx-field-label-font: var(--nx-text-md);
43
+ --nx-field-icon: 18px;
44
+ }
45
+
46
+ /* States */
47
+ .nx-field:focus-within {
48
+ --nx-field-label-color: var(--nx-field-accent);
49
+ }
50
+
51
+ .nx-field[data-invalid] {
52
+ --nx-field-border: var(--nx-danger);
53
+ --nx-field-border-hover: var(--nx-danger);
54
+ --nx-field-accent: var(--nx-danger);
55
+ --nx-field-ring: 0 0 0 3px color-mix(in oklab, var(--nx-danger) 30%, transparent);
56
+ --nx-field-label-color: var(--nx-danger);
57
+ }
58
+
59
+ /* Read-only and disabled boxes sit lower than the panel: lighter gray in light mode, the page color in dark mode. */
60
+ .nx-field:is([data-readonly], [data-disabled]) {
61
+ --nx-field-bg: light-dark(var(--nx-surface-2), var(--nx-bg));
62
+ --nx-field-border-hover: var(--nx-field-border);
63
+ }
64
+
65
+ .nx-field:is([data-readonly], [data-disabled]) .nx-field__box {
66
+ box-shadow: none;
67
+ }
68
+
69
+ .nx-field[data-disabled] {
70
+ opacity: 0.6;
71
+ }
72
+
73
+ .nx-field[data-disabled] :is(.nx-field__label, .nx-field__box, .nx-field__control) {
74
+ cursor: not-allowed;
75
+ }
76
+
77
+ /* Label above the box */
78
+ .nx-field__label {
79
+ color: var(--nx-text);
80
+ font-size: var(--nx-field-label-font);
81
+ font-weight: 500;
82
+ line-height: 1.3;
83
+ }
84
+
85
+ .nx-field--standard > .nx-field__label {
86
+ align-self: flex-start;
87
+ margin-bottom: 6px;
88
+ }
89
+
90
+ .nx-field__required {
91
+ margin-left: 3px;
92
+ color: var(--nx-danger);
93
+ }
94
+
95
+ /* Box */
96
+ .nx-field__box {
97
+ position: relative;
98
+ display: flex;
99
+ align-items: center;
100
+ min-height: var(--nx-field-height);
101
+ border: 1px solid var(--nx-field-border);
102
+ border-radius: var(--nx-radius);
103
+ background: var(--nx-field-bg);
104
+ box-shadow: var(--nx-shadow-sm);
105
+ font-size: var(--nx-field-font);
106
+ transition:
107
+ border-color var(--nx-duration),
108
+ box-shadow var(--nx-duration);
109
+ }
110
+
111
+ .nx-field__box:hover {
112
+ border-color: var(--nx-field-border-hover);
113
+ }
114
+
115
+ .nx-field__box:focus-within {
116
+ border-color: var(--nx-field-accent);
117
+ box-shadow: var(--nx-field-ring);
118
+ }
119
+
120
+ .nx-field__control {
121
+ flex: 1 1 auto;
122
+ align-self: stretch;
123
+ width: 100%;
124
+ min-width: 0;
125
+ margin: 0;
126
+ padding: 0 var(--nx-field-pad-x);
127
+ border: 0;
128
+ border-radius: inherit;
129
+ outline: none;
130
+ background: transparent;
131
+ color: var(--nx-text);
132
+ font-size: var(--nx-field-font);
133
+ line-height: 1.4;
134
+ text-overflow: ellipsis;
135
+ }
136
+
137
+ /* Numbers line up digit under digit. */
138
+ .nx-field__control--number {
139
+ font-variant-numeric: tabular-nums;
140
+ }
141
+
142
+ .nx-field__control::placeholder {
143
+ color: var(--nx-text-subtle);
144
+ opacity: 1;
145
+ }
146
+
147
+ /* Browser extras that the field replaces with its own buttons. */
148
+ .nx-field__control::-webkit-search-cancel-button,
149
+ .nx-field__control::-webkit-search-decoration {
150
+ appearance: none;
151
+ }
152
+
153
+ .nx-field__control::-ms-reveal,
154
+ .nx-field__control::-ms-clear {
155
+ display: none;
156
+ }
157
+
158
+ /* Autofilled values keep the field's own colors. */
159
+ .nx-field__control:is(:autofill, :-webkit-autofill) {
160
+ -webkit-text-fill-color: var(--nx-text);
161
+ caret-color: var(--nx-text);
162
+ box-shadow: 0 0 0 100px var(--nx-field-bg) inset;
163
+ }
164
+
165
+ /* Icons */
166
+ .nx-field__icon {
167
+ display: flex;
168
+ align-items: center;
169
+ flex-shrink: 0;
170
+ padding-left: var(--nx-field-pad-x);
171
+ color: var(--nx-text-subtle);
172
+ pointer-events: none;
173
+ transition: color var(--nx-duration);
174
+ }
175
+
176
+ .nx-field__icon .nx-icon,
177
+ .nx-field__action .nx-icon,
178
+ .nx-field__lock .nx-icon {
179
+ width: var(--nx-field-icon);
180
+ height: var(--nx-field-icon);
181
+ vertical-align: 0;
182
+ }
183
+
184
+ .nx-field__icon--end {
185
+ padding-right: var(--nx-field-pad-x);
186
+ padding-left: 0;
187
+ }
188
+
189
+ .nx-field__box:focus-within .nx-field__icon,
190
+ .nx-field[data-invalid] .nx-field__icon {
191
+ color: var(--nx-field-accent);
192
+ }
193
+
194
+ .nx-field__icon:not(.nx-field__icon--end) + .nx-field__control {
195
+ padding-left: var(--nx-space-2);
196
+ }
197
+
198
+ .nx-field__control:has(~ .nx-field__actions, ~ .nx-field__icon--end) {
199
+ padding-right: var(--nx-space-1);
200
+ }
201
+
202
+ /* Text before and after the value: a currency, a unit, a domain. Muted, so the value reads first. */
203
+ .nx-field__affix {
204
+ display: flex;
205
+ align-items: center;
206
+ align-self: stretch;
207
+ flex-shrink: 0;
208
+ padding-left: var(--nx-field-pad-x);
209
+ color: var(--nx-text-muted);
210
+ line-height: 1.4;
211
+ white-space: nowrap;
212
+ user-select: none;
213
+ }
214
+
215
+ .nx-field__affix--end {
216
+ padding-right: var(--nx-field-pad-x);
217
+ padding-left: 0;
218
+ }
219
+
220
+ .nx-field__icon:not(.nx-field__icon--end) + .nx-field__affix {
221
+ padding-left: var(--nx-space-2);
222
+ }
223
+
224
+ .nx-field__affix:not(.nx-field__affix--end) + .nx-field__control {
225
+ padding-left: 6px;
226
+ }
227
+
228
+ .nx-field__control:has(~ .nx-field__affix--end) {
229
+ padding-right: 6px;
230
+ }
231
+
232
+ .nx-field__affix--end:has(~ .nx-field__actions, ~ .nx-field__icon--end) {
233
+ padding-right: var(--nx-space-1);
234
+ }
235
+
236
+ /* Buttons inside the box: clear, show password, − and + */
237
+ .nx-field__actions {
238
+ display: flex;
239
+ align-items: center;
240
+ flex-shrink: 0;
241
+ gap: 2px;
242
+ padding-right: 4px;
243
+ }
244
+
245
+ .nx-field__actions--start {
246
+ padding-right: 0;
247
+ padding-left: 4px;
248
+ }
249
+
250
+ .nx-field__actions + .nx-field__icon--end {
251
+ padding-left: var(--nx-space-1);
252
+ }
253
+
254
+ .nx-field__action {
255
+ display: grid;
256
+ place-items: center;
257
+ width: calc(var(--nx-field-base) - 10px);
258
+ height: calc(var(--nx-field-base) - 10px);
259
+ padding: 0;
260
+ border: 0;
261
+ border-radius: var(--nx-radius-sm);
262
+ background: none;
263
+ color: var(--nx-text-subtle);
264
+ cursor: pointer;
265
+ transition:
266
+ background-color var(--nx-duration),
267
+ color var(--nx-duration);
268
+ }
269
+
270
+ .nx-field__action:hover:not(:disabled, [aria-disabled="true"]) {
271
+ background: var(--nx-surface-hover);
272
+ color: var(--nx-text);
273
+ }
274
+
275
+ .nx-field__action:focus-visible {
276
+ outline: 2px solid var(--nx-primary);
277
+ outline-offset: -2px;
278
+ }
279
+
280
+ .nx-field__action[aria-pressed="true"] {
281
+ color: var(--nx-field-accent);
282
+ }
283
+
284
+ .nx-field__action:disabled {
285
+ cursor: not-allowed;
286
+ }
287
+
288
+ /* − and +: stronger than the clear button, since they are the main way to change the value by pointer. */
289
+ .nx-field__action--step {
290
+ color: var(--nx-text-muted);
291
+ touch-action: manipulation;
292
+ user-select: none;
293
+ -webkit-touch-callout: none;
294
+ }
295
+
296
+ /* At a limit: dimmed, and the press does nothing. Not `disabled`, which would lose the pointer while it is held. */
297
+ .nx-field__action[aria-disabled="true"] {
298
+ opacity: 0.4;
299
+ cursor: default;
300
+ }
301
+
302
+ /* Split stepper: − before and + after a centered value, as in a guest or quantity picker. */
303
+ .nx-field--split .nx-field__control {
304
+ padding-inline: var(--nx-space-1);
305
+ text-align: center;
306
+ }
307
+
308
+ /*
309
+ * Read-only lock: at the end of the box, where a button that no longer works would be (a date picker's calendar, a
310
+ * number box's − and +), so a click that does nothing does not look like a bug. Muted rather than subtle, since it
311
+ * is the only sign of the state: at least 3:1 on the box in every theme. On an attempt to change the field it turns
312
+ * to the text color and nudges.
313
+ */
314
+ .nx-field__lock {
315
+ display: grid;
316
+ place-items: center;
317
+ width: calc(var(--nx-field-base) - 10px);
318
+ height: calc(var(--nx-field-base) - 10px);
319
+ color: var(--nx-text-muted);
320
+ cursor: default;
321
+ }
322
+
323
+ /* After the label of a field without a box: a checkbox, a switch, a choice group, a slider. Sized to the label text. */
324
+ .nx-field__lock--label {
325
+ display: inline-block;
326
+ width: auto;
327
+ height: auto;
328
+ margin-left: 0.35em;
329
+ }
330
+
331
+ .nx-field__lock--label .nx-icon {
332
+ width: 1em;
333
+ height: 1em;
334
+ }
335
+
336
+ .nx-field__lock[data-nudge] {
337
+ color: var(--nx-text);
338
+ animation: nx-field-nudge 320ms ease-in-out;
339
+ }
340
+
341
+ @keyframes nx-field-nudge {
342
+ 20%,
343
+ 60% {
344
+ transform: translateX(-2px);
345
+ }
346
+
347
+ 40%,
348
+ 80% {
349
+ transform: translateX(2px);
350
+ }
351
+ }
352
+
353
+ /* The note under a read-only box, in the top layer and placed by the component. Inverted, like a tooltip. */
354
+ .nx-field-notice {
355
+ position: fixed;
356
+ inset: auto;
357
+ display: flex;
358
+ align-items: center;
359
+ gap: 6px;
360
+ max-width: min(320px, calc(100vw - 16px));
361
+ margin: 0;
362
+ padding: 6px 10px 6px 8px;
363
+ overflow: visible;
364
+ border: 0;
365
+ border-radius: var(--nx-radius);
366
+ background: var(--nx-text);
367
+ color: var(--nx-surface);
368
+ box-shadow: var(--nx-shadow);
369
+ font-size: var(--nx-text-xs);
370
+ font-weight: 500;
371
+ line-height: 1.35;
372
+ pointer-events: none;
373
+ }
374
+
375
+ .nx-field-notice:popover-open {
376
+ animation: nx-field-notice-in 120ms ease-out;
377
+ }
378
+
379
+ @keyframes nx-field-notice-in {
380
+ from {
381
+ opacity: 0;
382
+ transform: translateY(-4px);
383
+ }
384
+ }
385
+
386
+ .nx-field-notice .nx-icon {
387
+ flex-shrink: 0;
388
+ width: 14px;
389
+ height: 14px;
390
+ vertical-align: 0;
391
+ }
392
+
393
+ /* Nothing to clear in an empty field. The control always has a placeholder, so :placeholder-shown means empty. */
394
+ .nx-field__box:has(.nx-field__control:placeholder-shown) .nx-field__action--clear {
395
+ display: none;
396
+ }
397
+
398
+ /* Hint, error, and count under the box */
399
+ .nx-field__footer {
400
+ display: flex;
401
+ align-items: flex-start;
402
+ gap: var(--nx-space-3);
403
+ margin-top: 5px;
404
+ font-size: var(--nx-text-xs);
405
+ line-height: 1.35;
406
+ }
407
+
408
+ .nx-field__message {
409
+ display: flex;
410
+ gap: 5px;
411
+ min-width: 0;
412
+ margin: 0;
413
+ color: var(--nx-text-muted);
414
+ }
415
+
416
+ .nx-field__message .nx-icon {
417
+ flex-shrink: 0;
418
+ width: 1.35em;
419
+ height: 1.35em;
420
+ vertical-align: 0;
421
+ }
422
+
423
+ .nx-field__message--error {
424
+ color: var(--nx-danger);
425
+ font-weight: 500;
426
+ }
427
+
428
+ .nx-field__aside {
429
+ margin-left: auto;
430
+ color: var(--nx-text-muted);
431
+ font-variant-numeric: tabular-nums;
432
+ white-space: nowrap;
433
+ }
434
+
435
+ /* Outlined and inbox: the label rests inside the box like a placeholder and rises on focus or once there is a value. */
436
+ :is(.nx-field--outlined, .nx-field--inbox) .nx-field__label {
437
+ position: absolute;
438
+ z-index: 1;
439
+ top: 50%;
440
+ left: var(--nx-field-pad-x);
441
+ max-width: calc(100% - 2 * var(--nx-field-pad-x));
442
+ overflow: hidden;
443
+ color: var(--nx-field-label-color);
444
+ font-size: var(--nx-field-font);
445
+ font-weight: 400;
446
+ line-height: 1.2;
447
+ text-overflow: ellipsis;
448
+ white-space: nowrap;
449
+ transform: translateY(-50%);
450
+ pointer-events: none;
451
+ transition:
452
+ top var(--nx-duration),
453
+ left var(--nx-duration),
454
+ font-size var(--nx-duration),
455
+ color var(--nx-duration),
456
+ transform var(--nx-duration);
457
+ }
458
+
459
+ /* After a start icon, the resting label lines up with the text. */
460
+ .nx-field__icon:not(.nx-field__icon--end) ~ .nx-field__label {
461
+ left: calc(var(--nx-field-pad-x) + var(--nx-field-icon) + var(--nx-space-2));
462
+ }
463
+
464
+ /* On a multi-line box, the resting label sits on the first line. */
465
+ .nx-field--multiline .nx-field__label {
466
+ top: calc(var(--nx-field-pad-y) + var(--nx-field-font) * 0.7);
467
+ }
468
+
469
+ /* Outlined: the raised label sits on the top border. Its backing covers only the lower half, so it blends with any panel. */
470
+ .nx-field--outlined:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__label {
471
+ top: 0;
472
+ left: calc(var(--nx-field-pad-x) - 4px);
473
+ padding: 0 4px;
474
+ background: linear-gradient(to bottom, transparent calc(50% - 1px), var(--nx-field-bg) calc(50% - 1px));
475
+ font-size: var(--nx-text-xs);
476
+ font-weight: 500;
477
+ }
478
+
479
+ /* Inbox: a taller box with the raised label inside its top edge. */
480
+ .nx-field--inbox {
481
+ --nx-field-extra: 12px;
482
+ --nx-field-inbox-pad: 17px;
483
+ }
484
+
485
+ .nx-field--inbox.nx-field--sm {
486
+ --nx-field-inbox-pad: 15px;
487
+ }
488
+
489
+ .nx-field--inbox.nx-field--lg {
490
+ --nx-field-inbox-pad: 19px;
491
+ }
492
+
493
+ .nx-field--inbox .nx-field__control {
494
+ padding-top: var(--nx-field-inbox-pad);
495
+ }
496
+
497
+ /*
498
+ * Beside the value: while the label rests in the middle, icons, text, and buttons line up with it. Once the label
499
+ * rises, the value sits low in the box, and they move down to its line. Buttons move halfway, so their hover
500
+ * background keeps clear of the border.
501
+ */
502
+ .nx-field--inbox :is(.nx-field__icon, .nx-field__affix, .nx-field__actions) {
503
+ align-self: stretch;
504
+ transition: padding-top var(--nx-duration);
505
+ }
506
+
507
+ .nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) :is(.nx-field__icon, .nx-field__affix) {
508
+ padding-top: var(--nx-field-inbox-pad);
509
+ }
510
+
511
+ .nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__actions {
512
+ padding-top: calc(var(--nx-field-inbox-pad) / 2);
513
+ }
514
+
515
+ .nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__label {
516
+ top: 5px;
517
+ font-size: var(--nx-text-xs);
518
+ font-weight: 500;
519
+ transform: none;
520
+ }
521
+
522
+ /*
523
+ * Split stepper: the label sits centered over the centered value, between − and +.
524
+ * It must come after the raised-label rule above: both have the same specificity, so the later one wins.
525
+ */
526
+ .nx-field--inbox.nx-field--split[data-raised] .nx-field__label {
527
+ left: 50%;
528
+ max-width: calc(100% - 2 * var(--nx-field-base));
529
+ transform: translateX(-50%);
530
+ }
531
+
532
+ /* Multi-line */
533
+ .nx-field--multiline .nx-field__box {
534
+ align-items: stretch;
535
+ }
536
+
537
+ .nx-field--multiline .nx-field__control {
538
+ padding: var(--nx-field-pad-y) var(--nx-field-pad-x);
539
+ font-family: inherit;
540
+ resize: vertical;
541
+ }
542
+
543
+ /* Buttons and the read-only lock sit at the top end, centered on the first line (line height 1.4). */
544
+ .nx-field--multiline .nx-field__actions {
545
+ align-items: flex-start;
546
+ padding-top: calc(var(--nx-field-pad-y) + 0.7em - (var(--nx-field-base) - 10px) / 2);
547
+ }
548
+
549
+ /* The browser's own resize grip ignores the theme (white lines in dark mode): draw two grip lines instead. */
550
+ .nx-field--multiline .nx-field__control::-webkit-resizer {
551
+ background: linear-gradient(
552
+ 135deg,
553
+ transparent 0 48%,
554
+ var(--nx-field-grip) 48% 56%,
555
+ transparent 56% 70%,
556
+ var(--nx-field-grip) 70% 78%,
557
+ transparent 78%
558
+ );
559
+ }
560
+
561
+ .nx-field--multiline.nx-field--inbox .nx-field__control {
562
+ padding-top: calc(var(--nx-field-pad-y) + var(--nx-field-inbox-pad) - 4px);
563
+ }
564
+
565
+ .nx-field--multiline .nx-field__icon {
566
+ align-items: flex-start;
567
+ padding-top: calc(var(--nx-field-pad-y) + (1.4em - var(--nx-field-icon)) / 2);
568
+ }
569
+
570
+ .nx-field--multiline.nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__icon {
571
+ padding-top: calc(var(--nx-field-pad-y) + var(--nx-field-inbox-pad) - 4px + (1.4em - var(--nx-field-icon)) / 2);
572
+ }
573
+
574
+ .nx-field--multiline.nx-field--inbox:is([data-raised], :focus-within, :has(.nx-field__control:not(:placeholder-shown))) .nx-field__actions {
575
+ padding-top: calc(var(--nx-field-pad-y) + var(--nx-field-inbox-pad) - 4px + 0.7em - (var(--nx-field-base) - 10px) / 2);
576
+ }
577
+
578
+ /* Grows with its text between --nx-field-rows and --nx-field-max-rows lines. */
579
+ .nx-field__control--auto {
580
+ field-sizing: content;
581
+ min-height: calc(var(--nx-field-rows, 3) * 1lh + 2 * var(--nx-field-pad-y));
582
+ max-height: calc(var(--nx-field-max-rows, 12) * 1lh + 2 * var(--nx-field-pad-y));
583
+ resize: none;
584
+ }
585
+
586
+ @supports not (field-sizing: content) {
587
+ .nx-field__control--auto {
588
+ max-height: none;
589
+ resize: vertical;
590
+ }
591
+ }
592
+
593
+ [data-nx-motion="reduce"] .nx-field__label,
594
+ [data-nx-motion="reduce"] .nx-field--inbox :is(.nx-field__icon, .nx-field__affix, .nx-field__actions) {
595
+ transition: none;
596
+ }
597
+
598
+ [data-nx-motion="reduce"] .nx-field__lock[data-nudge],
599
+ [data-nx-motion="reduce"] .nx-field-notice:popover-open {
600
+ animation: none;
601
+ }
602
+ }