nexus-shared 3.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 (121) hide show
  1. package/CHANGELOG.md +446 -262
  2. package/LICENSE +21 -21
  3. package/README.md +1084 -1000
  4. package/dist/Client.Index.d.ts +3 -0
  5. package/dist/Client.Index.js +3 -0
  6. package/dist/Components/Chats/ChatParts.d.ts +2 -2
  7. package/dist/Components/Chats/ChatParts.js +3 -7
  8. package/dist/Components/Chats/ChatThread.js +3 -3
  9. package/dist/Components/Documents/Button.d.ts +6 -1
  10. package/dist/Components/Documents/Button.js +2 -2
  11. package/dist/Components/Documents/Menu.js +21 -9
  12. package/dist/Components/Documents/SplitButton.d.ts +3 -1
  13. package/dist/Components/Documents/SplitButton.js +7 -5
  14. package/dist/Components/Documents/TabButtons.d.ts +5 -2
  15. package/dist/Components/Documents/TabButtons.js +2 -2
  16. package/dist/Components/Forms/ApiForm.d.ts +1 -1
  17. package/dist/Components/Forms/ApiForm.js +1 -1
  18. package/dist/Components/Forms/Crud.js +177 -58
  19. package/dist/Components/Forms/CrudParts.d.ts +5 -1
  20. package/dist/Components/Forms/CrudParts.js +37 -5
  21. package/dist/Components/Forms/ExcelImport.d.ts +23 -7
  22. package/dist/Components/Forms/ExcelImport.js +189 -60
  23. package/dist/Components/Forms/Form.js +5 -13
  24. package/dist/Components/Forms/SubmitForm.d.ts +11 -2
  25. package/dist/Components/Forms/SubmitForm.js +23 -12
  26. package/dist/Components/Inputs/DropdownParts.d.ts +1 -1
  27. package/dist/Components/Inputs/DropdownParts.js +4 -6
  28. package/dist/Components/Inputs/NumberBox.js +24 -2
  29. package/dist/Components/Inputs/RowsInput.js +2 -2
  30. package/dist/Components/Inputs/Slider.js +4 -1
  31. package/dist/Components/Inputs/TabularForm.js +42 -10
  32. package/dist/Components/Inputs/TimePanel.js +4 -3
  33. package/dist/Components/Layouts/Popup.js +1 -4
  34. package/dist/Components/Layouts/ThemePicker.d.ts +20 -3
  35. package/dist/Components/Layouts/ThemePicker.js +77 -6
  36. package/dist/Components/Layouts/ThemeScript.d.ts +4 -2
  37. package/dist/Components/Layouts/ThemeScript.js +4 -2
  38. package/dist/Components/Layouts/ThemeSwitcher.d.ts +6 -3
  39. package/dist/Components/Layouts/ThemeSwitcher.js +9 -8
  40. package/dist/Components/Viewers/Avatar.d.ts +21 -0
  41. package/dist/Components/Viewers/Avatar.js +21 -0
  42. package/dist/Components/Viewers/DataTable.js +192 -44
  43. package/dist/Components/Viewers/DataTableColumns.d.ts +7 -3
  44. package/dist/Components/Viewers/DataTableColumns.js +25 -68
  45. package/dist/Components/Viewers/DataTableFilters.d.ts +21 -0
  46. package/dist/Components/Viewers/DataTableFilters.js +68 -0
  47. package/dist/Components/Viewers/DataTableParts.d.ts +2 -2
  48. package/dist/Components/Viewers/DataTableParts.js +3 -3
  49. package/dist/Helpers/AnimationHelpers.d.ts +11 -0
  50. package/dist/Helpers/AnimationHelpers.js +20 -2
  51. package/dist/Helpers/CrudBackend.d.ts +7 -4
  52. package/dist/Helpers/CrudBackend.js +4 -2
  53. package/dist/Helpers/ExcelBackend.d.ts +3 -3
  54. package/dist/Helpers/ExcelBackend.js +8 -2
  55. package/dist/Helpers/ExcelHelpers.d.ts +36 -8
  56. package/dist/Helpers/ExcelHelpers.js +55 -13
  57. package/dist/Helpers/FormStore.d.ts +9 -5
  58. package/dist/Helpers/FormStore.js +25 -5
  59. package/dist/Helpers/MessageBuilder.js +1 -0
  60. package/dist/Helpers/NumberHelpers.d.ts +7 -1
  61. package/dist/Helpers/NumberHelpers.js +41 -3
  62. package/dist/Helpers/PopoverHelpers.d.ts +17 -5
  63. package/dist/Helpers/PopoverHelpers.js +52 -17
  64. package/dist/Helpers/PopoverHooks.d.ts +24 -0
  65. package/dist/Helpers/PopoverHooks.js +62 -0
  66. package/dist/Helpers/RowsStore.d.ts +7 -2
  67. package/dist/Helpers/RowsStore.js +15 -3
  68. package/dist/Helpers/TableExport.js +12 -12
  69. package/dist/Helpers/TableFilters.d.ts +25 -0
  70. package/dist/Helpers/TableFilters.js +162 -0
  71. package/dist/Helpers/TableHelpers.d.ts +6 -3
  72. package/dist/Helpers/TableHelpers.js +8 -4
  73. package/dist/Helpers/TableStore.d.ts +11 -3
  74. package/dist/Helpers/TableStore.js +16 -5
  75. package/dist/Helpers/ThemeHelpers.d.ts +21 -4
  76. package/dist/Helpers/ThemeHelpers.js +46 -9
  77. package/dist/Helpers/UserPreferences.d.ts +14 -0
  78. package/dist/Helpers/UserPreferences.js +99 -0
  79. package/dist/Interfaces/CrudInterfaces.d.ts +20 -1
  80. package/dist/Interfaces/ExcelInterfaces.d.ts +12 -0
  81. package/dist/Interfaces/FormInterfaces.d.ts +33 -5
  82. package/dist/Interfaces/PreferenceInterfaces.d.ts +43 -0
  83. package/dist/Interfaces/PreferenceInterfaces.js +28 -0
  84. package/dist/Interfaces/TableInterfaces.d.ts +59 -12
  85. package/dist/Interfaces/ThemeInterfaces.d.ts +25 -0
  86. package/dist/Interfaces/ThemeInterfaces.js +15 -0
  87. package/dist/Services/ExcelApi.d.ts +3 -1
  88. package/dist/Services/ExcelApi.js +1 -1
  89. package/dist/Services/ThemeService.d.ts +18 -2
  90. package/dist/Services/ThemeService.js +80 -16
  91. package/dist/Shared.Index.d.ts +2 -0
  92. package/dist/Shared.Index.js +2 -0
  93. package/package.json +85 -85
  94. package/src/Styles/Nexus.Avatar.css +79 -0
  95. package/src/Styles/Nexus.Base.css +117 -70
  96. package/src/Styles/Nexus.Button.css +180 -153
  97. package/src/Styles/Nexus.Calendar.css +273 -275
  98. package/src/Styles/Nexus.Chat.css +1443 -1489
  99. package/src/Styles/Nexus.Choice.css +343 -345
  100. package/src/Styles/Nexus.Crud.css +168 -161
  101. package/src/Styles/Nexus.DateTime.css +81 -81
  102. package/src/Styles/Nexus.Dropdown.css +725 -774
  103. package/src/Styles/Nexus.Editor.css +295 -297
  104. package/src/Styles/Nexus.Excel.css +389 -370
  105. package/src/Styles/Nexus.Form.css +279 -321
  106. package/src/Styles/Nexus.Index.css +37 -36
  107. package/src/Styles/Nexus.Input.css +602 -637
  108. package/src/Styles/Nexus.Markdown.css +237 -265
  109. package/src/Styles/Nexus.Menu.css +237 -259
  110. package/src/Styles/Nexus.Popup.css +724 -731
  111. package/src/Styles/Nexus.RichText.css +2 -32
  112. package/src/Styles/Nexus.Rows.css +637 -596
  113. package/src/Styles/Nexus.Slider.css +276 -278
  114. package/src/Styles/Nexus.Tab.Buttons.css +432 -419
  115. package/src/Styles/Nexus.Table.css +1386 -1296
  116. package/src/Styles/Nexus.Theme.Picker.css +286 -232
  117. package/src/Styles/Nexus.Themes.css +194 -194
  118. package/src/Styles/Nexus.Time.css +144 -147
  119. package/src/Styles/Nexus.Toast.css +263 -265
  120. package/src/Styles/Nexus.Tokens.css +89 -72
  121. package/src/Styles/Nexus.Tree.css +431 -435
@@ -1,345 +1,343 @@
1
- @layer nexus.components {
2
- /*
3
- * Checkbox, radio, and switch. Each is the native input drawn with CSS (appearance: none), so an option is one
4
- * element that forms, keyboards, and screen readers already understand. Marks are masks filled with theme colors.
5
- */
6
- .nx-choice {
7
- --nx-choice-size: 16px;
8
- --nx-choice-font: var(--nx-text-md);
9
- --nx-choice-gap: var(--nx-space-2);
10
- /* The box edge meets 3:1 contrast on the panel in every theme (text-subtle is checked for it). */
11
- --nx-choice-border: var(--nx-text-subtle);
12
- --nx-choice-border-hover: var(--nx-text-muted);
13
- --nx-choice-bg: var(--nx-surface);
14
- --nx-choice-accent: var(--nx-primary-solid);
15
- --nx-choice-mark: var(--nx-primary-text);
16
-
17
- display: inline-flex;
18
- align-items: flex-start;
19
- gap: var(--nx-choice-gap);
20
- min-width: 0;
21
- color: var(--nx-text);
22
- font-size: var(--nx-choice-font);
23
- line-height: 1.45;
24
- cursor: pointer;
25
- -webkit-tap-highlight-color: transparent;
26
- }
27
-
28
- .nx-choice--sm {
29
- --nx-choice-size: 14px;
30
- --nx-choice-font: var(--nx-text-sm);
31
- --nx-choice-gap: 6px;
32
- }
33
-
34
- .nx-choice--lg {
35
- --nx-choice-size: 20px;
36
- --nx-choice-font: var(--nx-text-lg);
37
- --nx-choice-gap: 10px;
38
- }
39
-
40
- /* Label first, control at the end of the row: settings lists. */
41
- .nx-choice--start {
42
- flex-direction: row-reverse;
43
- justify-content: space-between;
44
- width: 100%;
45
- }
46
-
47
- .nx-choice__control {
48
- appearance: none;
49
- display: grid;
50
- place-content: center;
51
- flex-shrink: 0;
52
- width: var(--nx-choice-size);
53
- height: var(--nx-choice-size);
54
- /* Centered on the first line of the label. */
55
- margin: calc((1lh - var(--nx-choice-size)) / 2) 0 0;
56
- border: 1px solid var(--nx-choice-border);
57
- border-radius: 4px;
58
- background: var(--nx-choice-bg);
59
- color: var(--nx-choice-mark);
60
- font: inherit;
61
- cursor: inherit;
62
- transition:
63
- background-color var(--nx-duration),
64
- border-color var(--nx-duration);
65
- }
66
-
67
- .nx-choice__control::before {
68
- content: "";
69
- width: calc(var(--nx-choice-size) - 2px);
70
- height: calc(var(--nx-choice-size) - 2px);
71
- background: currentColor;
72
- transform: scale(0);
73
- transition: transform var(--nx-duration) ease-out;
74
- }
75
-
76
- .nx-choice:hover .nx-choice__control:not(:checked, :indeterminate, :disabled) {
77
- border-color: var(--nx-choice-border-hover);
78
- }
79
-
80
- .nx-choice__control:focus-visible {
81
- outline: 2px solid var(--nx-primary);
82
- outline-offset: 2px;
83
- }
84
-
85
- /* Checkbox */
86
- .nx-choice__control--checkbox::before {
87
- mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5l9.5 -10'/%3E%3C/svg%3E") center / 100% no-repeat;
88
- }
89
-
90
- .nx-choice__control--checkbox:indeterminate::before {
91
- mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
92
- }
93
-
94
- .nx-choice__control--checkbox:is(:checked, :indeterminate) {
95
- border-color: var(--nx-choice-accent);
96
- background: var(--nx-choice-accent);
97
- }
98
-
99
- .nx-choice__control--checkbox:is(:checked, :indeterminate)::before {
100
- transform: scale(1);
101
- }
102
-
103
- /* Radio */
104
- .nx-choice__control--radio {
105
- border-radius: 50%;
106
- }
107
-
108
- .nx-choice__control--radio::before {
109
- width: calc(var(--nx-choice-size) * 0.375);
110
- height: calc(var(--nx-choice-size) * 0.375);
111
- border-radius: 50%;
112
- }
113
-
114
- .nx-choice__control--radio:checked {
115
- border-color: var(--nx-choice-accent);
116
- background: var(--nx-choice-accent);
117
- }
118
-
119
- .nx-choice__control--radio:checked::before {
120
- transform: scale(1);
121
- }
122
-
123
- /*
124
- * Switch: a track and a thumb. Off, the track is the subtle text color, so its edge keeps 3:1 contrast;
125
- * on, it is the primary color. The thumb looks the same in both states: the panel color when off, and the text
126
- * color for primary when on, which is white in light themes and near black in dark ones, like the panel.
127
- */
128
- .nx-choice__control--switch {
129
- --nx-switch-height: calc(var(--nx-choice-size) + 2px);
130
- --nx-switch-width: calc(var(--nx-switch-height) * 1.8);
131
- --nx-switch-thumb: calc(var(--nx-switch-height) - 4px);
132
-
133
- place-content: center start;
134
- width: var(--nx-switch-width);
135
- height: var(--nx-switch-height);
136
- margin-top: calc((1lh - var(--nx-switch-height)) / 2);
137
- padding: 0 2px;
138
- border: 0;
139
- border-radius: var(--nx-radius-full);
140
- background: var(--nx-choice-border);
141
- }
142
-
143
- .nx-choice__control--switch::before {
144
- width: var(--nx-switch-thumb);
145
- height: var(--nx-switch-thumb);
146
- border-radius: 50%;
147
- background: var(--nx-surface);
148
- box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
149
- transform: none;
150
- }
151
-
152
- .nx-choice:hover .nx-choice__control--switch:not(:checked, :disabled) {
153
- background: var(--nx-choice-border-hover);
154
- }
155
-
156
- .nx-choice__control--switch:checked {
157
- background: var(--nx-choice-accent);
158
- }
159
-
160
- .nx-choice__control--switch:checked::before {
161
- background: var(--nx-choice-mark);
162
- transform: translateX(calc(var(--nx-switch-width) - var(--nx-switch-height)));
163
- }
164
-
165
- /* Text beside the control */
166
- .nx-choice__text {
167
- display: grid;
168
- gap: 1px;
169
- min-width: 0;
170
- }
171
-
172
- .nx-choice__description {
173
- color: var(--nx-text-muted);
174
- font-size: calc(var(--nx-choice-font) - 1px);
175
- line-height: 1.4;
176
- }
177
-
178
- .nx-choice__icon {
179
- display: flex;
180
- align-items: center;
181
- height: 1lh;
182
- color: var(--nx-text-muted);
183
- }
184
-
185
- .nx-choice__icon .nx-icon {
186
- width: calc(var(--nx-choice-size) + 2px);
187
- height: calc(var(--nx-choice-size) + 2px);
188
- }
189
-
190
- /* States */
191
- .nx-choice:has(> .nx-choice__control:disabled) {
192
- cursor: not-allowed;
193
- opacity: 0.55;
194
- }
195
-
196
- [data-readonly] .nx-choice {
197
- cursor: default;
198
- }
199
-
200
- [data-readonly] .nx-choice__control:not(:checked, :indeterminate) {
201
- --nx-choice-bg: var(--nx-surface-2);
202
- }
203
-
204
- [data-readonly] .nx-choice__control:is(:checked, :indeterminate) {
205
- --nx-choice-accent: color-mix(in oklab, var(--nx-primary-solid) 70%, var(--nx-surface));
206
- }
207
-
208
- [data-invalid] .nx-choice__control:not(:checked, :indeterminate) {
209
- --nx-choice-border: var(--nx-danger);
210
- --nx-choice-border-hover: var(--nx-danger);
211
- }
212
-
213
- /* A single checkbox or switch, with its message line. */
214
- .nx-choice-field {
215
- display: flex;
216
- flex-direction: column;
217
- align-items: flex-start;
218
- min-width: 0;
219
- }
220
-
221
- .nx-choice-field:has(> .nx-choice--start) {
222
- align-items: stretch;
223
- }
224
-
225
- /* Groups */
226
- .nx-choices {
227
- --nx-field-label-font: var(--nx-text-sm);
228
- --nx-choices-gap-x: var(--nx-space-5);
229
- --nx-choices-gap-y: var(--nx-space-2);
230
-
231
- min-width: 0;
232
- margin: 0;
233
- padding: 0;
234
- border: 0;
235
- }
236
-
237
- .nx-choices--sm {
238
- --nx-field-label-font: var(--nx-text-xs);
239
- --nx-choices-gap-y: 6px;
240
- }
241
-
242
- .nx-choices--lg {
243
- --nx-field-label-font: var(--nx-text-md);
244
- --nx-choices-gap-y: var(--nx-space-3);
245
- }
246
-
247
- .nx-choices__legend {
248
- margin-bottom: var(--nx-space-2);
249
- padding: 0;
250
- }
251
-
252
- /*
253
- * Options fill up to --nx-choices-columns columns, and wrap to fewer when a column would be narrower than 160px,
254
- * so three columns on a desktop become one on a phone without a media query.
255
- */
256
- .nx-choices__options {
257
- display: grid;
258
- grid-template-columns: repeat(
259
- auto-fill,
260
- minmax(min(100%, max(160px, (100% - (var(--nx-choices-columns, 1) - 1) * var(--nx-choices-gap-x)) / var(--nx-choices-columns, 1))), 1fr)
261
- );
262
- align-items: start;
263
- gap: var(--nx-choices-gap-y) var(--nx-choices-gap-x);
264
- }
265
-
266
- .nx-choices--horizontal .nx-choices__options {
267
- display: flex;
268
- flex-wrap: wrap;
269
- }
270
-
271
- .nx-choices__all {
272
- display: flex;
273
- margin-bottom: var(--nx-choices-gap-y);
274
- padding-bottom: var(--nx-choices-gap-y);
275
- border-bottom: 1px solid var(--nx-border);
276
- font-weight: 500;
277
- }
278
-
279
- .nx-choices > .nx-field__footer {
280
- margin-top: var(--nx-space-2);
281
- }
282
-
283
- /* Cards: each option is a card; the chosen one takes a primary edge and a light primary tint. */
284
- .nx-choices--cards {
285
- --nx-choices-gap-x: var(--nx-space-3);
286
- --nx-choices-gap-y: var(--nx-space-3);
287
- }
288
-
289
- .nx-choices--cards .nx-choices__options {
290
- align-items: stretch;
291
- }
292
-
293
- .nx-choices--cards .nx-choice {
294
- padding: var(--nx-space-3) var(--nx-space-4) var(--nx-space-3) var(--nx-space-3);
295
- border: 1px solid var(--nx-border);
296
- border-radius: var(--nx-radius-lg);
297
- background: var(--nx-surface);
298
- box-shadow: var(--nx-shadow-sm);
299
- transition:
300
- border-color var(--nx-duration),
301
- background-color var(--nx-duration),
302
- box-shadow var(--nx-duration);
303
- }
304
-
305
- .nx-choices--cards .nx-choice__label {
306
- font-weight: 500;
307
- }
308
-
309
- .nx-choices--cards .nx-choice:hover:not(:has(> :disabled)) {
310
- border-color: var(--nx-border-strong);
311
- }
312
-
313
- /* The inset ring doubles the edge without moving the content. */
314
- .nx-choices--cards .nx-choice:has(> .nx-choice__control:checked) {
315
- border-color: var(--nx-choice-accent);
316
- background: color-mix(in oklab, var(--nx-choice-accent) 7%, var(--nx-surface));
317
- box-shadow: inset 0 0 0 1px var(--nx-choice-accent);
318
- }
319
-
320
- .nx-choices--cards .nx-choice:has(> .nx-choice__control:checked) .nx-choice__icon {
321
- color: var(--nx-choice-accent);
322
- }
323
-
324
- /* The focus ring goes around the whole card rather than the small control. */
325
- .nx-choices--cards .nx-choice:has(> .nx-choice__control:focus-visible) {
326
- outline: 2px solid var(--nx-primary);
327
- outline-offset: 2px;
328
- }
329
-
330
- .nx-choices--cards .nx-choice__control:focus-visible {
331
- outline: none;
332
- }
333
-
334
- .nx-choices--cards[data-invalid] .nx-choice {
335
- border-color: var(--nx-danger);
336
- }
337
-
338
- @media (prefers-reduced-motion: reduce) {
339
- .nx-choice__control,
340
- .nx-choice__control::before,
341
- .nx-choices--cards .nx-choice {
342
- transition: none;
343
- }
344
- }
345
- }
1
+ @layer nexus.components {
2
+ /*
3
+ * Checkbox, radio, and switch. Each is the native input drawn with CSS (appearance: none), so an option is one
4
+ * element that forms, keyboards, and screen readers already understand. Marks are masks filled with theme colors.
5
+ */
6
+ .nx-choice {
7
+ --nx-choice-size: 16px;
8
+ --nx-choice-font: var(--nx-text-md);
9
+ --nx-choice-gap: var(--nx-space-2);
10
+ /* The box edge meets 3:1 contrast on the panel in every theme (text-subtle is checked for it). */
11
+ --nx-choice-border: var(--nx-text-subtle);
12
+ --nx-choice-border-hover: var(--nx-text-muted);
13
+ --nx-choice-bg: var(--nx-surface);
14
+ --nx-choice-accent: var(--nx-primary-solid);
15
+ --nx-choice-mark: var(--nx-primary-text);
16
+
17
+ display: inline-flex;
18
+ align-items: flex-start;
19
+ gap: var(--nx-choice-gap);
20
+ min-width: 0;
21
+ color: var(--nx-text);
22
+ font-size: var(--nx-choice-font);
23
+ line-height: 1.45;
24
+ cursor: pointer;
25
+ -webkit-tap-highlight-color: transparent;
26
+ }
27
+
28
+ .nx-choice--sm {
29
+ --nx-choice-size: 14px;
30
+ --nx-choice-font: var(--nx-text-sm);
31
+ --nx-choice-gap: 6px;
32
+ }
33
+
34
+ .nx-choice--lg {
35
+ --nx-choice-size: 20px;
36
+ --nx-choice-font: var(--nx-text-lg);
37
+ --nx-choice-gap: 10px;
38
+ }
39
+
40
+ /* Label first, control at the end of the row: settings lists. */
41
+ .nx-choice--start {
42
+ flex-direction: row-reverse;
43
+ justify-content: space-between;
44
+ width: 100%;
45
+ }
46
+
47
+ .nx-choice__control {
48
+ appearance: none;
49
+ display: grid;
50
+ place-content: center;
51
+ flex-shrink: 0;
52
+ width: var(--nx-choice-size);
53
+ height: var(--nx-choice-size);
54
+ /* Centered on the first line of the label. */
55
+ margin: calc((1lh - var(--nx-choice-size)) / 2) 0 0;
56
+ border: 1px solid var(--nx-choice-border);
57
+ border-radius: 4px;
58
+ background: var(--nx-choice-bg);
59
+ color: var(--nx-choice-mark);
60
+ font: inherit;
61
+ cursor: inherit;
62
+ transition:
63
+ background-color var(--nx-duration),
64
+ border-color var(--nx-duration);
65
+ }
66
+
67
+ .nx-choice__control::before {
68
+ content: "";
69
+ width: calc(var(--nx-choice-size) - 2px);
70
+ height: calc(var(--nx-choice-size) - 2px);
71
+ background: currentColor;
72
+ transform: scale(0);
73
+ transition: transform var(--nx-duration) ease-out;
74
+ }
75
+
76
+ .nx-choice:hover .nx-choice__control:not(:checked, :indeterminate, :disabled) {
77
+ border-color: var(--nx-choice-border-hover);
78
+ }
79
+
80
+ .nx-choice__control:focus-visible {
81
+ outline: 2px solid var(--nx-primary);
82
+ outline-offset: 2px;
83
+ }
84
+
85
+ /* Checkbox */
86
+ .nx-choice__control--checkbox::before {
87
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5l9.5 -10'/%3E%3C/svg%3E") center / 100% no-repeat;
88
+ }
89
+
90
+ .nx-choice__control--checkbox:indeterminate::before {
91
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3.2' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
92
+ }
93
+
94
+ .nx-choice__control--checkbox:is(:checked, :indeterminate) {
95
+ border-color: var(--nx-choice-accent);
96
+ background: var(--nx-choice-accent);
97
+ }
98
+
99
+ .nx-choice__control--checkbox:is(:checked, :indeterminate)::before {
100
+ transform: scale(1);
101
+ }
102
+
103
+ /* Radio */
104
+ .nx-choice__control--radio {
105
+ border-radius: 50%;
106
+ }
107
+
108
+ .nx-choice__control--radio::before {
109
+ width: calc(var(--nx-choice-size) * 0.375);
110
+ height: calc(var(--nx-choice-size) * 0.375);
111
+ border-radius: 50%;
112
+ }
113
+
114
+ .nx-choice__control--radio:checked {
115
+ border-color: var(--nx-choice-accent);
116
+ background: var(--nx-choice-accent);
117
+ }
118
+
119
+ .nx-choice__control--radio:checked::before {
120
+ transform: scale(1);
121
+ }
122
+
123
+ /*
124
+ * Switch: a track and a thumb. Off, the track is the subtle text color, so its edge keeps 3:1 contrast;
125
+ * on, it is the primary color. The thumb looks the same in both states: the panel color when off, and the text
126
+ * color for primary when on, which is white in light themes and near black in dark ones, like the panel.
127
+ */
128
+ .nx-choice__control--switch {
129
+ --nx-switch-height: calc(var(--nx-choice-size) + 2px);
130
+ --nx-switch-width: calc(var(--nx-switch-height) * 1.8);
131
+ --nx-switch-thumb: calc(var(--nx-switch-height) - 4px);
132
+
133
+ place-content: center start;
134
+ width: var(--nx-switch-width);
135
+ height: var(--nx-switch-height);
136
+ margin-top: calc((1lh - var(--nx-switch-height)) / 2);
137
+ padding: 0 2px;
138
+ border: 0;
139
+ border-radius: var(--nx-radius-full);
140
+ background: var(--nx-choice-border);
141
+ }
142
+
143
+ .nx-choice__control--switch::before {
144
+ width: var(--nx-switch-thumb);
145
+ height: var(--nx-switch-thumb);
146
+ border-radius: 50%;
147
+ background: var(--nx-surface);
148
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
149
+ transform: none;
150
+ }
151
+
152
+ .nx-choice:hover .nx-choice__control--switch:not(:checked, :disabled) {
153
+ background: var(--nx-choice-border-hover);
154
+ }
155
+
156
+ .nx-choice__control--switch:checked {
157
+ background: var(--nx-choice-accent);
158
+ }
159
+
160
+ .nx-choice__control--switch:checked::before {
161
+ background: var(--nx-choice-mark);
162
+ transform: translateX(calc(var(--nx-switch-width) - var(--nx-switch-height)));
163
+ }
164
+
165
+ /* Text beside the control */
166
+ .nx-choice__text {
167
+ display: grid;
168
+ gap: 1px;
169
+ min-width: 0;
170
+ }
171
+
172
+ .nx-choice__description {
173
+ color: var(--nx-text-muted);
174
+ font-size: calc(var(--nx-choice-font) - 1px);
175
+ line-height: 1.4;
176
+ }
177
+
178
+ .nx-choice__icon {
179
+ display: flex;
180
+ align-items: center;
181
+ height: 1lh;
182
+ color: var(--nx-text-muted);
183
+ }
184
+
185
+ .nx-choice__icon .nx-icon {
186
+ width: calc(var(--nx-choice-size) + 2px);
187
+ height: calc(var(--nx-choice-size) + 2px);
188
+ }
189
+
190
+ /* States */
191
+ .nx-choice:has(> .nx-choice__control:disabled) {
192
+ cursor: not-allowed;
193
+ opacity: 0.55;
194
+ }
195
+
196
+ [data-readonly] .nx-choice {
197
+ cursor: default;
198
+ }
199
+
200
+ [data-readonly] .nx-choice__control:not(:checked, :indeterminate) {
201
+ --nx-choice-bg: var(--nx-surface-2);
202
+ }
203
+
204
+ [data-readonly] .nx-choice__control:is(:checked, :indeterminate) {
205
+ --nx-choice-accent: color-mix(in oklab, var(--nx-primary-solid) 70%, var(--nx-surface));
206
+ }
207
+
208
+ [data-invalid] .nx-choice__control:not(:checked, :indeterminate) {
209
+ --nx-choice-border: var(--nx-danger);
210
+ --nx-choice-border-hover: var(--nx-danger);
211
+ }
212
+
213
+ /* A single checkbox or switch, with its message line. */
214
+ .nx-choice-field {
215
+ display: flex;
216
+ flex-direction: column;
217
+ align-items: flex-start;
218
+ min-width: 0;
219
+ }
220
+
221
+ .nx-choice-field:has(> .nx-choice--start) {
222
+ align-items: stretch;
223
+ }
224
+
225
+ /* Groups */
226
+ .nx-choices {
227
+ --nx-field-label-font: var(--nx-text-sm);
228
+ --nx-choices-gap-x: var(--nx-space-5);
229
+ --nx-choices-gap-y: var(--nx-space-2);
230
+
231
+ min-width: 0;
232
+ margin: 0;
233
+ padding: 0;
234
+ border: 0;
235
+ }
236
+
237
+ .nx-choices--sm {
238
+ --nx-field-label-font: var(--nx-text-xs);
239
+ --nx-choices-gap-y: 6px;
240
+ }
241
+
242
+ .nx-choices--lg {
243
+ --nx-field-label-font: var(--nx-text-md);
244
+ --nx-choices-gap-y: var(--nx-space-3);
245
+ }
246
+
247
+ .nx-choices__legend {
248
+ margin-bottom: var(--nx-space-2);
249
+ padding: 0;
250
+ }
251
+
252
+ /*
253
+ * Options fill up to --nx-choices-columns columns, and wrap to fewer when a column would be narrower than 160px,
254
+ * so three columns on a desktop become one on a phone without a media query.
255
+ */
256
+ .nx-choices__options {
257
+ display: grid;
258
+ grid-template-columns: repeat(
259
+ auto-fill,
260
+ minmax(min(100%, max(160px, (100% - (var(--nx-choices-columns, 1) - 1) * var(--nx-choices-gap-x)) / var(--nx-choices-columns, 1))), 1fr)
261
+ );
262
+ align-items: start;
263
+ gap: var(--nx-choices-gap-y) var(--nx-choices-gap-x);
264
+ }
265
+
266
+ .nx-choices--horizontal .nx-choices__options {
267
+ display: flex;
268
+ flex-wrap: wrap;
269
+ }
270
+
271
+ .nx-choices__all {
272
+ display: flex;
273
+ margin-bottom: var(--nx-choices-gap-y);
274
+ padding-bottom: var(--nx-choices-gap-y);
275
+ border-bottom: 1px solid var(--nx-border);
276
+ font-weight: 500;
277
+ }
278
+
279
+ .nx-choices > .nx-field__footer {
280
+ margin-top: var(--nx-space-2);
281
+ }
282
+
283
+ /* Cards: each option is a card; the chosen one takes a primary edge and a light primary tint. */
284
+ .nx-choices--cards {
285
+ --nx-choices-gap-x: var(--nx-space-3);
286
+ --nx-choices-gap-y: var(--nx-space-3);
287
+ }
288
+
289
+ .nx-choices--cards .nx-choices__options {
290
+ align-items: stretch;
291
+ }
292
+
293
+ .nx-choices--cards .nx-choice {
294
+ padding: var(--nx-space-3) var(--nx-space-4) var(--nx-space-3) var(--nx-space-3);
295
+ border: 1px solid var(--nx-border);
296
+ border-radius: var(--nx-radius-lg);
297
+ background: var(--nx-surface);
298
+ box-shadow: var(--nx-shadow-sm);
299
+ transition:
300
+ border-color var(--nx-duration),
301
+ background-color var(--nx-duration),
302
+ box-shadow var(--nx-duration);
303
+ }
304
+
305
+ .nx-choices--cards .nx-choice__label {
306
+ font-weight: 500;
307
+ }
308
+
309
+ .nx-choices--cards .nx-choice:hover:not(:has(> :disabled)) {
310
+ border-color: var(--nx-border-strong);
311
+ }
312
+
313
+ /* The inset ring doubles the edge without moving the content. */
314
+ .nx-choices--cards .nx-choice:has(> .nx-choice__control:checked) {
315
+ border-color: var(--nx-choice-accent);
316
+ background: color-mix(in oklab, var(--nx-choice-accent) 7%, var(--nx-surface));
317
+ box-shadow: inset 0 0 0 1px var(--nx-choice-accent);
318
+ }
319
+
320
+ .nx-choices--cards .nx-choice:has(> .nx-choice__control:checked) .nx-choice__icon {
321
+ color: var(--nx-choice-accent);
322
+ }
323
+
324
+ /* The focus ring goes around the whole card rather than the small control. */
325
+ .nx-choices--cards .nx-choice:has(> .nx-choice__control:focus-visible) {
326
+ outline: 2px solid var(--nx-primary);
327
+ outline-offset: 2px;
328
+ }
329
+
330
+ .nx-choices--cards .nx-choice__control:focus-visible {
331
+ outline: none;
332
+ }
333
+
334
+ .nx-choices--cards[data-invalid] .nx-choice {
335
+ border-color: var(--nx-danger);
336
+ }
337
+
338
+ [data-nx-motion="reduce"] .nx-choice__control,
339
+ [data-nx-motion="reduce"] .nx-choice__control::before,
340
+ [data-nx-motion="reduce"] .nx-choices--cards .nx-choice {
341
+ transition: none;
342
+ }
343
+ }