nexus-shared 3.1.0 → 4.0.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 (234) hide show
  1. package/CHANGELOG.md +2268 -446
  2. package/LICENSE +21 -21
  3. package/README.md +1705 -1084
  4. package/dist/Client.Index.d.ts +17 -0
  5. package/dist/Client.Index.js +17 -0
  6. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  7. package/dist/Components/Documents/DocumentFrame.js +83 -0
  8. package/dist/Components/Documents/Menu.d.ts +4 -0
  9. package/dist/Components/Documents/Menu.js +40 -24
  10. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  11. package/dist/Components/Documents/PrintDocument.js +62 -0
  12. package/dist/Components/Documents/Tabs.d.ts +59 -0
  13. package/dist/Components/Documents/Tabs.js +70 -0
  14. package/dist/Components/Forms/ApiForm.js +19 -2
  15. package/dist/Components/Forms/Crud.d.ts +2 -2
  16. package/dist/Components/Forms/Crud.js +871 -377
  17. package/dist/Components/Forms/CrudActions.d.ts +136 -0
  18. package/dist/Components/Forms/CrudActions.js +184 -0
  19. package/dist/Components/Forms/CrudHistory.d.ts +66 -0
  20. package/dist/Components/Forms/CrudHistory.js +140 -0
  21. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  22. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  23. package/dist/Components/Forms/CrudParts.d.ts +30 -2
  24. package/dist/Components/Forms/CrudParts.js +56 -5
  25. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  26. package/dist/Components/Forms/CrudRowForms.js +114 -0
  27. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  28. package/dist/Components/Forms/CrudScopes.js +76 -0
  29. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  30. package/dist/Components/Forms/CrudTree.js +363 -0
  31. package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
  32. package/dist/Components/Forms/CrudWorkspace.js +169 -0
  33. package/dist/Components/Forms/ExcelImport.js +3 -3
  34. package/dist/Components/Forms/Form.d.ts +4 -4
  35. package/dist/Components/Forms/Form.js +125 -16
  36. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  37. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  38. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  39. package/dist/Components/Forms/RecordFlows.js +282 -0
  40. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  41. package/dist/Components/Forms/SubmitForm.js +195 -10
  42. package/dist/Components/Inputs/CheckBox.js +1 -1
  43. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  44. package/dist/Components/Inputs/ChoiceField.js +28 -2
  45. package/dist/Components/Inputs/CodeBox.d.ts +69 -0
  46. package/dist/Components/Inputs/CodeBox.js +316 -0
  47. package/dist/Components/Inputs/DatePicker.js +4 -1
  48. package/dist/Components/Inputs/DateRangePicker.js +4 -1
  49. package/dist/Components/Inputs/DateTimePicker.js +4 -1
  50. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  51. package/dist/Components/Inputs/Dropdown.js +8 -5
  52. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  53. package/dist/Components/Inputs/DropdownParts.js +16 -4
  54. package/dist/Components/Inputs/Field.d.ts +7 -1
  55. package/dist/Components/Inputs/Field.js +2 -2
  56. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  57. package/dist/Components/Inputs/GroupForm.js +10 -11
  58. package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
  59. package/dist/Components/Inputs/InputRenderer.js +39 -31
  60. package/dist/Components/Inputs/NumberBox.js +4 -2
  61. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  62. package/dist/Components/Inputs/PictureInput.js +70 -0
  63. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  64. package/dist/Components/Inputs/RadioGroup.js +8 -3
  65. package/dist/Components/Inputs/RowsInput.d.ts +1 -1
  66. package/dist/Components/Inputs/RowsInput.js +18 -3
  67. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  68. package/dist/Components/Inputs/TabularForm.js +29 -29
  69. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  70. package/dist/Components/Inputs/TextArea.js +14 -2
  71. package/dist/Components/Inputs/TextBox.js +4 -1
  72. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  73. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  74. package/dist/Components/Inputs/TimePicker.js +4 -1
  75. package/dist/Components/Inputs/TreeView.d.ts +107 -6
  76. package/dist/Components/Inputs/TreeView.js +513 -30
  77. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  78. package/dist/Components/Layouts/MessageBox.js +56 -7
  79. package/dist/Components/Layouts/Popup.d.ts +13 -6
  80. package/dist/Components/Layouts/Popup.js +37 -9
  81. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  82. package/dist/Components/Layouts/PopupDock.js +2 -1
  83. package/dist/Components/Layouts/PopupHost.js +5 -4
  84. package/dist/Components/Layouts/ThemePicker.d.ts +3 -2
  85. package/dist/Components/Layouts/ThemePicker.js +16 -4
  86. package/dist/Components/Viewers/DataTable.js +205 -61
  87. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  88. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  89. package/dist/Components/Viewers/DataTableParts.d.ts +46 -12
  90. package/dist/Components/Viewers/DataTableParts.js +62 -21
  91. package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
  92. package/dist/Components/Viewers/OwnerFiles.js +589 -0
  93. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  94. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  95. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  96. package/dist/Components/Viewers/RecordCards.js +509 -0
  97. package/dist/Helpers/ApiModules.d.ts +3 -34
  98. package/dist/Helpers/ApiModules.js +3 -3
  99. package/dist/Helpers/ApiRoutes.d.ts +68 -26
  100. package/dist/Helpers/ApiRoutes.js +31 -27
  101. package/dist/Helpers/ArrayHelpers.d.ts +0 -0
  102. package/dist/Helpers/ArrayHelpers.js +2 -0
  103. package/dist/Helpers/BusyHooks.d.ts +6 -0
  104. package/dist/Helpers/BusyHooks.js +39 -0
  105. package/dist/Helpers/CodeHelpers.d.ts +97 -0
  106. package/dist/Helpers/CodeHelpers.js +332 -0
  107. package/dist/Helpers/CrudBackend.d.ts +114 -6
  108. package/dist/Helpers/CrudBackend.js +214 -11
  109. package/dist/Helpers/CrudColumns.d.ts +98 -0
  110. package/dist/Helpers/CrudColumns.js +160 -0
  111. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  112. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  113. package/dist/Helpers/CrudHelpers.d.ts +130 -6
  114. package/dist/Helpers/CrudHelpers.js +304 -12
  115. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  116. package/dist/Helpers/CrudNarrowPick.js +157 -0
  117. package/dist/Helpers/CrudRowForms.d.ts +15 -0
  118. package/dist/Helpers/CrudRowForms.js +43 -0
  119. package/dist/Helpers/CrudScopes.d.ts +32 -0
  120. package/dist/Helpers/CrudScopes.js +79 -0
  121. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  122. package/dist/Helpers/DropdownHelpers.js +6 -2
  123. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  124. package/dist/Helpers/ExcelHelpers.js +12 -11
  125. package/dist/Helpers/FileAdapter.d.ts +78 -0
  126. package/dist/Helpers/FileAdapter.js +102 -0
  127. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  128. package/dist/Helpers/FilePrimaries.js +138 -0
  129. package/dist/Helpers/FormConfirm.d.ts +32 -0
  130. package/dist/Helpers/FormConfirm.js +40 -0
  131. package/dist/Helpers/FormStore.d.ts +90 -5
  132. package/dist/Helpers/FormStore.js +318 -22
  133. package/dist/Helpers/InputConditions.d.ts +50 -0
  134. package/dist/Helpers/InputConditions.js +168 -0
  135. package/dist/Helpers/InputMemory.d.ts +106 -0
  136. package/dist/Helpers/InputMemory.js +385 -0
  137. package/dist/Helpers/InputNarrowing.d.ts +18 -0
  138. package/dist/Helpers/InputNarrowing.js +67 -0
  139. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  140. package/dist/Helpers/InputParamsHelpers.js +162 -71
  141. package/dist/Helpers/MessageBuilder.js +2 -0
  142. package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
  143. package/dist/Helpers/NexusDriveAdapter.js +207 -0
  144. package/dist/Helpers/NexusHelpers.d.ts +0 -0
  145. package/dist/Helpers/NexusHelpers.js +1 -0
  146. package/dist/Helpers/NumberHelpers.d.ts +95 -0
  147. package/dist/Helpers/NumberHelpers.js +126 -0
  148. package/dist/Helpers/Permissions.js +2 -1
  149. package/dist/Helpers/QrCode.d.ts +26 -0
  150. package/dist/Helpers/QrCode.js +463 -0
  151. package/dist/Helpers/RecordCards.d.ts +205 -0
  152. package/dist/Helpers/RecordCards.js +378 -0
  153. package/dist/Helpers/RequestCache.d.ts +47 -0
  154. package/dist/Helpers/RequestCache.js +132 -0
  155. package/dist/Helpers/RowsStore.d.ts +37 -2
  156. package/dist/Helpers/RowsStore.js +128 -10
  157. package/dist/Helpers/ServerPreview.d.ts +69 -0
  158. package/dist/Helpers/ServerPreview.js +99 -0
  159. package/dist/Helpers/TableColumns.js +4 -3
  160. package/dist/Helpers/TableExport.d.ts +7 -1
  161. package/dist/Helpers/TableExport.js +21 -15
  162. package/dist/Helpers/TableFilters.d.ts +1 -1
  163. package/dist/Helpers/TableFilters.js +24 -23
  164. package/dist/Helpers/TableHelpers.d.ts +24 -3
  165. package/dist/Helpers/TableHelpers.js +58 -12
  166. package/dist/Helpers/TableJoins.d.ts +83 -0
  167. package/dist/Helpers/TableJoins.js +284 -0
  168. package/dist/Helpers/TableStore.d.ts +26 -1
  169. package/dist/Helpers/TableStore.js +73 -7
  170. package/dist/Helpers/TreeStore.d.ts +306 -11
  171. package/dist/Helpers/TreeStore.js +1048 -90
  172. package/dist/Helpers/UserPreferences.d.ts +8 -0
  173. package/dist/Helpers/UserPreferences.js +17 -0
  174. package/dist/Interfaces/CrudInterfaces.d.ts +512 -14
  175. package/dist/Interfaces/FileInterfaces.d.ts +162 -0
  176. package/dist/Interfaces/FileInterfaces.js +4 -0
  177. package/dist/Interfaces/FormInterfaces.d.ts +393 -29
  178. package/dist/Interfaces/FormInterfaces.js +79 -1
  179. package/dist/Interfaces/InputInterfaces.d.ts +39 -3
  180. package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
  181. package/dist/Interfaces/PreferenceInterfaces.d.ts +8 -0
  182. package/dist/Interfaces/PreferenceInterfaces.js +2 -5
  183. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  184. package/dist/Interfaces/RecordInterfaces.js +1 -0
  185. package/dist/Interfaces/TableInterfaces.d.ts +160 -5
  186. package/dist/Services/ApiProxy.d.ts +36 -1
  187. package/dist/Services/ApiProxy.js +182 -52
  188. package/dist/Services/ApiProxyRules.d.ts +203 -0
  189. package/dist/Services/ApiProxyRules.js +426 -0
  190. package/dist/Services/ExcelApi.js +5 -0
  191. package/dist/Services/FileApi.d.ts +13 -0
  192. package/dist/Services/FileApi.js +29 -0
  193. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  194. package/dist/Services/ImageEditorLoader.js +51 -0
  195. package/dist/Services/JoinSources.d.ts +80 -0
  196. package/dist/Services/JoinSources.js +150 -0
  197. package/dist/Services/ModuleImport.d.ts +7 -0
  198. package/dist/Services/ModuleImport.js +22 -0
  199. package/dist/Services/ServerApi.d.ts +13 -2
  200. package/dist/Services/ServerApi.js +8 -3
  201. package/dist/Shared.Index.d.ts +20 -0
  202. package/dist/Shared.Index.js +20 -0
  203. package/package.json +92 -85
  204. package/src/Styles/Nexus.Avatar.css +80 -79
  205. package/src/Styles/Nexus.Base.css +117 -117
  206. package/src/Styles/Nexus.Button.css +186 -180
  207. package/src/Styles/Nexus.Calendar.css +273 -273
  208. package/src/Styles/Nexus.Chat.css +1443 -1443
  209. package/src/Styles/Nexus.Choice.css +359 -343
  210. package/src/Styles/Nexus.Crud.css +542 -168
  211. package/src/Styles/Nexus.DateTime.css +81 -81
  212. package/src/Styles/Nexus.Dropdown.css +733 -725
  213. package/src/Styles/Nexus.Editor.css +295 -295
  214. package/src/Styles/Nexus.Excel.css +389 -389
  215. package/src/Styles/Nexus.Files.css +410 -0
  216. package/src/Styles/Nexus.Form.css +282 -279
  217. package/src/Styles/Nexus.Index.css +41 -37
  218. package/src/Styles/Nexus.Input.css +238 -4
  219. package/src/Styles/Nexus.Markdown.css +237 -237
  220. package/src/Styles/Nexus.Menu.css +246 -237
  221. package/src/Styles/Nexus.Popup.css +800 -724
  222. package/src/Styles/Nexus.Print.css +288 -0
  223. package/src/Styles/Nexus.QrCode.css +12 -0
  224. package/src/Styles/Nexus.Record.css +430 -0
  225. package/src/Styles/Nexus.Rows.css +637 -637
  226. package/src/Styles/Nexus.Slider.css +276 -276
  227. package/src/Styles/Nexus.Tab.Buttons.css +497 -432
  228. package/src/Styles/Nexus.Table.css +98 -35
  229. package/src/Styles/Nexus.Theme.Picker.css +59 -3
  230. package/src/Styles/Nexus.Themes.css +200 -194
  231. package/src/Styles/Nexus.Time.css +144 -144
  232. package/src/Styles/Nexus.Toast.css +263 -263
  233. package/src/Styles/Nexus.Tokens.css +94 -89
  234. package/src/Styles/Nexus.Tree.css +530 -431
@@ -1,432 +1,497 @@
1
- @layer nexus.components {
2
- /*
3
- * Tab buttons: a track, and an indicator that slides to the selected option.
4
- * Light mode: a soft tint of the text color with no frame, so the track suits any panel, and a white indicator
5
- * lifted by a soft shadow instead of a hard border.
6
- * Dark mode: the raised panel color with an edge, and an indicator lighter than the track (never the darker panel
7
- * color), so the selection reads at a glance in every theme.
8
- */
9
- .nx-tabs {
10
- --nx-tabs-height: var(--nx-control-md);
11
- --nx-tabs-pad: 3px;
12
- --nx-tabs-radius: calc(var(--nx-radius) + 2px);
13
- --nx-tabs-track: light-dark(color-mix(in oklab, var(--nx-text) 5%, transparent), var(--nx-surface-2));
14
- --nx-tabs-track-border: light-dark(transparent, var(--nx-border));
15
- --nx-tabs-track-shadow: inset 0 1px 2px light-dark(transparent, rgb(0 0 0 / 0.35));
16
- /* Lighter than the panel in light themes too, whose panels are not always white (Nord, Solarized, Dracula). */
17
- --nx-tabs-indicator: light-dark(color-mix(in oklab, var(--nx-surface), #fff 65%), color-mix(in oklab, var(--nx-surface-hover), var(--nx-text) 12%));
18
- --nx-tabs-indicator-border: light-dark(transparent, color-mix(in oklab, var(--nx-border-strong), var(--nx-text) 18%));
19
- --nx-tabs-indicator-shadow:
20
- 0 1px 2px light-dark(rgb(16 24 40 / 0.1), rgb(0 0 0 / 0.5)), 0 1px 3px light-dark(rgb(16 24 40 / 0.06), rgb(0 0 0 / 0.3)),
21
- 0 0 0 1px light-dark(rgb(16 24 40 / 0.04), transparent);
22
- --nx-tabs-hover: color-mix(in oklab, var(--nx-text) 5%, transparent);
23
- /* Unselected labels: a little darker than muted text in light mode, to keep AA contrast on the tinted track. */
24
- --nx-tabs-color: light-dark(color-mix(in oklab, var(--nx-text-muted), var(--nx-text) 20%), var(--nx-text-muted));
25
- --nx-tabs-active-color: var(--nx-text);
26
- --nx-tabs-active-icon: var(--nx-primary);
27
-
28
- position: relative;
29
- isolation: isolate;
30
- display: inline-grid;
31
- grid-auto-columns: 1fr;
32
- grid-auto-flow: column;
33
- height: var(--nx-tabs-height);
34
- padding: var(--nx-tabs-pad);
35
- border: 1px solid var(--nx-tabs-track-border);
36
- border-radius: var(--nx-tabs-radius);
37
- background: var(--nx-tabs-track);
38
- box-shadow: var(--nx-tabs-track-shadow);
39
- vertical-align: middle;
40
- }
41
-
42
- .nx-tabs--block {
43
- display: grid;
44
- width: 100%;
45
- }
46
-
47
- /* Primary: a solid primary indicator with its own text color, for a choice that matters, such as a plan. */
48
- .nx-tabs--primary {
49
- --nx-tabs-indicator: var(--nx-primary-solid);
50
- --nx-tabs-indicator-border: transparent;
51
- --nx-tabs-indicator-shadow: 0 1px 2px color-mix(in oklab, var(--nx-primary) 35%, transparent);
52
- --nx-tabs-active-color: var(--nx-primary-text);
53
- --nx-tabs-active-icon: var(--nx-primary-text);
54
- }
55
-
56
- /* One column wide; --nx-tabs-count and --nx-tabs-index come from the component. */
57
- .nx-tabs__indicator {
58
- position: absolute;
59
- z-index: -1;
60
- top: var(--nx-tabs-pad);
61
- bottom: var(--nx-tabs-pad);
62
- left: var(--nx-tabs-pad);
63
- width: calc((100% - 2 * var(--nx-tabs-pad)) / var(--nx-tabs-count));
64
- border: 1px solid var(--nx-tabs-indicator-border);
65
- border-radius: calc(var(--nx-tabs-radius) - 2px);
66
- background: var(--nx-tabs-indicator);
67
- box-shadow: var(--nx-tabs-indicator-shadow);
68
- transform: translateX(calc(100% * var(--nx-tabs-index)));
69
- transition:
70
- transform 240ms cubic-bezier(0.2, 0, 0, 1),
71
- background-color var(--nx-duration);
72
- }
73
-
74
- .nx-tabs__option {
75
- display: inline-flex;
76
- align-items: center;
77
- justify-content: center;
78
- gap: 6px;
79
- min-width: 0;
80
- padding: 0 var(--nx-space-3);
81
- border-radius: calc(var(--nx-tabs-radius) - 2px);
82
- color: var(--nx-tabs-color);
83
- font-size: var(--nx-text-sm);
84
- font-weight: 500;
85
- line-height: 1.3;
86
- white-space: nowrap;
87
- cursor: pointer;
88
- user-select: none;
89
- transition:
90
- color var(--nx-duration),
91
- background-color var(--nx-duration);
92
- }
93
-
94
- /* Room below the baseline, so the ellipsis clip never cuts letters such as y and g. */
95
- .nx-tabs__label {
96
- overflow: hidden;
97
- padding-block: 2px;
98
- text-overflow: ellipsis;
99
- }
100
-
101
- .nx-tabs__option .nx-icon {
102
- width: 1.15em;
103
- height: 1.15em;
104
- vertical-align: 0;
105
- transition: color var(--nx-duration);
106
- }
107
-
108
- .nx-tabs__option:hover:not(:has(:checked, :disabled)) {
109
- background: var(--nx-tabs-hover);
110
- color: var(--nx-text);
111
- }
112
-
113
- .nx-tabs__option:has(:checked) {
114
- color: var(--nx-tabs-active-color);
115
- cursor: default;
116
- }
117
-
118
- .nx-tabs__option:has(:checked) .nx-icon {
119
- color: var(--nx-tabs-active-icon);
120
- }
121
-
122
- .nx-tabs__option:has(:focus-visible) {
123
- box-shadow: var(--nx-focus-ring);
124
- }
125
-
126
- .nx-tabs__option:has(:disabled) {
127
- cursor: not-allowed;
128
- opacity: 0.45;
129
- }
130
-
131
- /* Sizes */
132
- .nx-tabs--sm {
133
- --nx-tabs-height: var(--nx-control-sm);
134
- --nx-tabs-pad: 2px;
135
- --nx-tabs-radius: var(--nx-radius);
136
- }
137
-
138
- .nx-tabs--sm .nx-tabs__option {
139
- gap: 4px;
140
- padding: 0 var(--nx-space-2);
141
- font-size: var(--nx-text-xs);
142
- }
143
-
144
- .nx-tabs--lg {
145
- --nx-tabs-height: var(--nx-control-lg);
146
- --nx-tabs-pad: 4px;
147
- --nx-tabs-radius: var(--nx-radius-lg);
148
- }
149
-
150
- .nx-tabs--lg .nx-tabs__option {
151
- gap: var(--nx-space-2);
152
- padding: 0 var(--nx-space-4);
153
- font-size: var(--nx-text-md);
154
- }
155
-
156
- /*
157
- * labels="selected": icons only, and the selected option grows to show its label. Options differ in width,
158
- * so the selected option draws the indicator itself, and its label column animates from 0 to full width.
159
- */
160
- .nx-tabs--labels-selected {
161
- display: inline-flex;
162
- gap: 2px;
163
- }
164
-
165
- .nx-tabs--labels-selected.nx-tabs--block {
166
- display: flex;
167
- }
168
-
169
- /* Icon-only options are square: side padding centers the 1.15em icon in the option's height. */
170
- .nx-tabs--labels-selected .nx-tabs__option {
171
- flex: 0 0 auto;
172
- gap: 0;
173
- padding: 0 calc((var(--nx-tabs-height) - 2 * var(--nx-tabs-pad) - 2px - 1.15em) / 2);
174
- transition:
175
- color var(--nx-duration),
176
- background-color var(--nx-duration),
177
- box-shadow var(--nx-duration),
178
- padding 240ms cubic-bezier(0.2, 0, 0, 1);
179
- }
180
-
181
- .nx-tabs--labels-selected .nx-tabs__option:has(:checked) {
182
- padding: 0 var(--nx-space-3);
183
- background: var(--nx-tabs-indicator);
184
- box-shadow:
185
- inset 0 0 0 1px var(--nx-tabs-indicator-border),
186
- var(--nx-tabs-indicator-shadow);
187
- }
188
-
189
- /* In a full-width group, the selected option takes the free space. */
190
- .nx-tabs--labels-selected.nx-tabs--block .nx-tabs__option:has(:checked) {
191
- flex: 1 1 auto;
192
- }
193
-
194
- /* The label column and the space before it both animate from 0, so a hidden label takes no width at all. */
195
- .nx-tabs--labels-selected .nx-tabs__label {
196
- display: grid;
197
- grid-template-columns: 0fr;
198
- margin-inline-start: 0;
199
- padding-block: 0;
200
- transition:
201
- grid-template-columns 240ms cubic-bezier(0.2, 0, 0, 1),
202
- margin 240ms cubic-bezier(0.2, 0, 0, 1);
203
- }
204
-
205
- .nx-tabs--labels-selected .nx-tabs__option:has(:checked) .nx-tabs__label {
206
- grid-template-columns: 1fr;
207
- margin-inline-start: 6px;
208
- }
209
-
210
- /* An option without an icon always shows its label. */
211
- .nx-tabs--labels-selected .nx-tabs__option:not(:has(.nx-icon)) .nx-tabs__label {
212
- grid-template-columns: 1fr;
213
- }
214
-
215
- /*
216
- * `auto`: in a container wide enough for them, every option says what it is, as the chosen one does. 900px is a data
217
- * table's bar with its Add, four lists, Reload, ⋮ and a full search on one line, and room to spare.
218
- */
219
- @container (min-width: 900px) {
220
- .nx-tabs--labels-auto .nx-tabs__option {
221
- padding: 0 var(--nx-space-3);
222
- }
223
-
224
- .nx-tabs--labels-auto .nx-tabs__label {
225
- grid-template-columns: 1fr;
226
- margin-inline-start: 6px;
227
- }
228
- }
229
-
230
- .nx-tabs--labels-selected .nx-tabs__text {
231
- min-width: 0;
232
- overflow: hidden;
233
- padding-block: 2px;
234
- text-overflow: ellipsis;
235
- }
236
-
237
- /*
238
- * multiple: several at once. There is no single place for a sliding indicator, so every chosen option is raised on
239
- * its own, exactly as the one choice's indicator is, and an option that is not chosen sits flat on the track. The
240
- * options wrap onto more rows when they do not fit, each row as tall as a one-row track's options, so a picker of
241
- * 26 letters is one track of several rows rather than a line that runs off a phone. A one-character option is
242
- * square, so a row of letters lines up.
243
- */
244
- .nx-tabs--multiple {
245
- --nx-tabs-option-height: calc(var(--nx-tabs-height) - 2 * var(--nx-tabs-pad) - 2px);
246
-
247
- display: inline-flex;
248
- flex-wrap: wrap;
249
- gap: 2px;
250
- max-width: 100%;
251
- height: auto;
252
- }
253
-
254
- .nx-tabs--multiple.nx-tabs--block {
255
- display: flex;
256
- }
257
-
258
- .nx-tabs--multiple .nx-tabs__option {
259
- flex: 0 0 auto;
260
- min-width: var(--nx-tabs-option-height);
261
- height: var(--nx-tabs-option-height);
262
- }
263
-
264
- /* A chosen option can be unchosen, so it keeps the pointer, and it keeps its raised look under the pointer too. */
265
- .nx-tabs--multiple .nx-tabs__option:has(:checked) {
266
- background: var(--nx-tabs-indicator);
267
- box-shadow:
268
- inset 0 0 0 1px var(--nx-tabs-indicator-border),
269
- var(--nx-tabs-indicator-shadow);
270
- cursor: pointer;
271
- }
272
-
273
- .nx-tabs--multiple .nx-tabs__option:has(:checked):has(:focus-visible) {
274
- box-shadow:
275
- inset 0 0 0 1px var(--nx-tabs-indicator-border),
276
- var(--nx-focus-ring);
277
- }
278
-
279
- /* A finger needs more than a small button's 22px: on a touch screen every option is a medium control's height, square at the least. */
280
- @media (pointer: coarse) {
281
- .nx-tabs--multiple {
282
- --nx-tabs-option-height: var(--nx-control-md);
283
- }
284
- }
285
-
286
- /*
287
- * design="list": a vertical list of rows, for settings pages and side panels. There is no track: rows sit on the
288
- * panel, the selected row is tinted (darker in light mode, lighter in dark mode) and marked by a bar at its start,
289
- * so the choice reads at a glance in every theme, and the label turns bold as a second cue.
290
- */
291
- .nx-tabs--list {
292
- --nx-tabs-list-row: var(--nx-control-md);
293
- --nx-tabs-list-font: var(--nx-text-md);
294
- --nx-tabs-list-pad: 6px var(--nx-space-3);
295
- --nx-tabs-list-gap: 10px;
296
- --nx-tabs-list-selected: light-dark(color-mix(in oklab, var(--nx-text) 6%, transparent), color-mix(in oklab, var(--nx-text) 10%, transparent));
297
- --nx-tabs-list-bar: var(--nx-primary);
298
- --nx-tabs-list-badge: color-mix(in oklab, var(--nx-text) 8%, transparent);
299
- --nx-tabs-list-badge-color: var(--nx-text-muted);
300
-
301
- display: grid;
302
- grid-auto-flow: row;
303
- grid-auto-columns: auto;
304
- gap: 2px;
305
- width: 100%;
306
- height: auto;
307
- padding: 0;
308
- border: 0;
309
- border-radius: 0;
310
- background: none;
311
- box-shadow: none;
312
- }
313
-
314
- .nx-tabs--list .nx-tabs__option {
315
- position: relative;
316
- justify-content: flex-start;
317
- gap: var(--nx-tabs-list-gap);
318
- min-height: var(--nx-tabs-list-row);
319
- padding: var(--nx-tabs-list-pad);
320
- border-radius: var(--nx-radius);
321
- font-size: var(--nx-tabs-list-font);
322
- text-align: left;
323
- white-space: normal;
324
- }
325
-
326
- /* The bar at the start of the selected row grows in from the middle. */
327
- .nx-tabs--list .nx-tabs__option::before {
328
- content: "";
329
- position: absolute;
330
- top: 8px;
331
- bottom: 8px;
332
- left: 0;
333
- width: 3px;
334
- border-radius: 3px;
335
- background: var(--nx-tabs-list-bar);
336
- opacity: 0;
337
- transform: scaleY(0.3);
338
- transition:
339
- opacity var(--nx-duration),
340
- transform 200ms cubic-bezier(0.2, 0, 0, 1);
341
- }
342
-
343
- .nx-tabs--list .nx-tabs__option:has(:checked) {
344
- background: var(--nx-tabs-list-selected);
345
- }
346
-
347
- /* A list of several choices: a chosen row can be unchosen, so it keeps the pointer. */
348
- .nx-tabs--list:not([role="radiogroup"]) .nx-tabs__option:has(:checked) {
349
- cursor: pointer;
350
- }
351
-
352
- .nx-tabs--list .nx-tabs__option:has(:checked)::before {
353
- opacity: 1;
354
- transform: none;
355
- }
356
-
357
- .nx-tabs--list .nx-tabs__option:has(:checked) .nx-tabs__text {
358
- font-weight: 600;
359
- }
360
-
361
- .nx-tabs--list .nx-tabs__option .nx-icon {
362
- flex-shrink: 0;
363
- }
364
-
365
- .nx-tabs--list .nx-tabs__label {
366
- display: grid;
367
- flex: 1 1 auto;
368
- min-width: 0;
369
- padding-block: 0;
370
- }
371
-
372
- .nx-tabs--list .nx-tabs__text {
373
- overflow: hidden;
374
- line-height: 1.35;
375
- text-overflow: ellipsis;
376
- white-space: nowrap;
377
- }
378
-
379
- .nx-tabs__description {
380
- color: var(--nx-text-muted);
381
- font-size: var(--nx-text-xs);
382
- font-weight: 400;
383
- line-height: 1.4;
384
- }
385
-
386
- .nx-tabs__badge {
387
- flex-shrink: 0;
388
- min-width: 20px;
389
- padding: 0 7px;
390
- border-radius: var(--nx-radius-full);
391
- background: var(--nx-tabs-list-badge);
392
- color: var(--nx-tabs-list-badge-color);
393
- font-size: var(--nx-text-xs);
394
- font-weight: 600;
395
- font-variant-numeric: tabular-nums;
396
- line-height: 20px;
397
- text-align: center;
398
- }
399
-
400
- /* Primary: the selected row takes the primary soft background and the primary text. */
401
- .nx-tabs--list.nx-tabs--primary {
402
- --nx-tabs-list-selected: var(--nx-primary-soft);
403
- --nx-tabs-active-color: var(--nx-primary);
404
- --nx-tabs-active-icon: var(--nx-primary);
405
- }
406
-
407
- .nx-tabs--list.nx-tabs--primary .nx-tabs__option:has(:checked) .nx-tabs__badge {
408
- background: var(--nx-primary-solid);
409
- color: var(--nx-primary-text);
410
- }
411
-
412
- .nx-tabs--list.nx-tabs--sm {
413
- --nx-tabs-list-row: var(--nx-control-sm);
414
- --nx-tabs-list-font: var(--nx-text-sm);
415
- --nx-tabs-list-pad: 4px var(--nx-space-2);
416
- --nx-tabs-list-gap: 8px;
417
- }
418
-
419
- .nx-tabs--list.nx-tabs--lg {
420
- --nx-tabs-list-row: var(--nx-control-lg);
421
- --nx-tabs-list-font: var(--nx-text-lg);
422
- --nx-tabs-list-pad: 8px var(--nx-space-4);
423
- --nx-tabs-list-gap: 12px;
424
- }
425
-
426
- [data-nx-motion="reduce"] .nx-tabs__indicator,
427
- [data-nx-motion="reduce"] .nx-tabs--multiple .nx-tabs__option,
428
- [data-nx-motion="reduce"] .nx-tabs--labels-selected :is(.nx-tabs__option, .nx-tabs__label),
429
- [data-nx-motion="reduce"] .nx-tabs--list .nx-tabs__option::before {
430
- transition: none;
431
- }
432
- }
1
+ @layer nexus.components {
2
+ /*
3
+ * Tab buttons: a track, and an indicator that slides to the selected option.
4
+ * Light mode: a soft tint of the text color with no frame, so the track suits any panel, and a white indicator
5
+ * lifted by a soft shadow instead of a hard border.
6
+ * Dark mode: the raised panel color with an edge, and an indicator lighter than the track (never the darker panel
7
+ * color), so the selection reads at a glance in every theme.
8
+ */
9
+ .nx-tabs {
10
+ --nx-tabs-height: var(--nx-control-md);
11
+ --nx-tabs-pad: 3px;
12
+ --nx-tabs-radius: calc(var(--nx-radius) + 2px);
13
+ --nx-tabs-track: light-dark(color-mix(in oklab, var(--nx-text) 5%, transparent), var(--nx-surface-2));
14
+ --nx-tabs-track-border: light-dark(transparent, var(--nx-border));
15
+ --nx-tabs-track-shadow: inset 0 1px 2px light-dark(transparent, rgb(0 0 0 / 0.35));
16
+ /* Lighter than the panel in light themes too, whose panels are not always white (Nord, Solarized, Dracula). */
17
+ --nx-tabs-indicator: light-dark(color-mix(in oklab, var(--nx-surface), #fff 65%), color-mix(in oklab, var(--nx-surface-hover), var(--nx-text) 12%));
18
+ --nx-tabs-indicator-border: light-dark(transparent, color-mix(in oklab, var(--nx-border-strong), var(--nx-text) 18%));
19
+ --nx-tabs-indicator-shadow:
20
+ 0 1px 2px light-dark(rgb(16 24 40 / 0.1), rgb(0 0 0 / 0.5)), 0 1px 3px light-dark(rgb(16 24 40 / 0.06), rgb(0 0 0 / 0.3)),
21
+ 0 0 0 1px light-dark(rgb(16 24 40 / 0.04), transparent);
22
+ --nx-tabs-hover: color-mix(in oklab, var(--nx-text) 5%, transparent);
23
+ /* Unselected labels: a little darker than muted text in light mode, to keep AA contrast on the tinted track. */
24
+ --nx-tabs-color: light-dark(color-mix(in oklab, var(--nx-text-muted), var(--nx-text) 20%), var(--nx-text-muted));
25
+ --nx-tabs-active-color: var(--nx-text);
26
+ --nx-tabs-active-icon: var(--nx-primary);
27
+
28
+ position: relative;
29
+ isolation: isolate;
30
+ display: inline-grid;
31
+ grid-auto-columns: 1fr;
32
+ grid-auto-flow: column;
33
+ height: var(--nx-tabs-height);
34
+ padding: var(--nx-tabs-pad);
35
+ border: 1px solid var(--nx-tabs-track-border);
36
+ border-radius: var(--nx-tabs-radius);
37
+ background: var(--nx-tabs-track);
38
+ box-shadow: var(--nx-tabs-track-shadow);
39
+ vertical-align: middle;
40
+ }
41
+
42
+ .nx-tabs--block {
43
+ display: grid;
44
+ width: 100%;
45
+ }
46
+
47
+ /* Primary: a solid primary indicator with its own text color, for a choice that matters, such as a plan. */
48
+ .nx-tabs--primary {
49
+ --nx-tabs-indicator: var(--nx-primary-solid);
50
+ --nx-tabs-indicator-border: transparent;
51
+ --nx-tabs-indicator-shadow: 0 1px 2px color-mix(in oklab, var(--nx-primary) 35%, transparent);
52
+ --nx-tabs-active-color: var(--nx-primary-text);
53
+ --nx-tabs-active-icon: var(--nx-primary-text);
54
+ }
55
+
56
+ /* One column wide; --nx-tabs-count and --nx-tabs-index come from the component. */
57
+ .nx-tabs__indicator {
58
+ position: absolute;
59
+ z-index: -1;
60
+ top: var(--nx-tabs-pad);
61
+ bottom: var(--nx-tabs-pad);
62
+ left: var(--nx-tabs-pad);
63
+ width: calc((100% - 2 * var(--nx-tabs-pad)) / var(--nx-tabs-count));
64
+ border: 1px solid var(--nx-tabs-indicator-border);
65
+ border-radius: calc(var(--nx-tabs-radius) - 2px);
66
+ background: var(--nx-tabs-indicator);
67
+ box-shadow: var(--nx-tabs-indicator-shadow);
68
+ transform: translateX(calc(100% * var(--nx-tabs-index)));
69
+ transition:
70
+ transform 240ms cubic-bezier(0.2, 0, 0, 1),
71
+ background-color var(--nx-duration);
72
+ }
73
+
74
+ .nx-tabs__option {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ gap: 6px;
79
+ min-width: 0;
80
+ padding: 0 var(--nx-space-3);
81
+ border-radius: calc(var(--nx-tabs-radius) - 2px);
82
+ color: var(--nx-tabs-color);
83
+ font-size: var(--nx-text-sm);
84
+ font-weight: 500;
85
+ line-height: 1.3;
86
+ white-space: nowrap;
87
+ cursor: pointer;
88
+ user-select: none;
89
+ transition:
90
+ color var(--nx-duration),
91
+ background-color var(--nx-duration);
92
+ }
93
+
94
+ /* Room below the baseline, so the ellipsis clip never cuts letters such as y and g. */
95
+ .nx-tabs__label {
96
+ overflow: hidden;
97
+ padding-block: 2px;
98
+ text-overflow: ellipsis;
99
+ }
100
+
101
+ .nx-tabs__option .nx-icon {
102
+ width: 1.15em;
103
+ height: 1.15em;
104
+ vertical-align: 0;
105
+ transition: color var(--nx-duration);
106
+ }
107
+
108
+ .nx-tabs__option:hover:not(:has(:checked, :disabled), [aria-selected="true"], :disabled) {
109
+ background: var(--nx-tabs-hover);
110
+ color: var(--nx-text);
111
+ }
112
+
113
+ .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) {
114
+ color: var(--nx-tabs-active-color);
115
+ cursor: default;
116
+ }
117
+
118
+ .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) .nx-icon {
119
+ color: var(--nx-tabs-active-icon);
120
+ }
121
+
122
+ .nx-tabs__option:is(:has(:focus-visible), :focus-visible) {
123
+ box-shadow: var(--nx-focus-ring);
124
+ }
125
+
126
+ .nx-tabs__option:is(:has(:disabled), :disabled) {
127
+ cursor: not-allowed;
128
+ opacity: 0.45;
129
+ }
130
+
131
+ /* Sizes */
132
+ .nx-tabs--sm {
133
+ --nx-tabs-height: var(--nx-control-sm);
134
+ --nx-tabs-pad: 2px;
135
+ --nx-tabs-radius: var(--nx-radius);
136
+ }
137
+
138
+ .nx-tabs--sm .nx-tabs__option {
139
+ gap: 4px;
140
+ padding: 0 var(--nx-space-2);
141
+ font-size: var(--nx-text-xs);
142
+ }
143
+
144
+ .nx-tabs--lg {
145
+ --nx-tabs-height: var(--nx-control-lg);
146
+ --nx-tabs-pad: 4px;
147
+ --nx-tabs-radius: var(--nx-radius-lg);
148
+ }
149
+
150
+ .nx-tabs--lg .nx-tabs__option {
151
+ gap: var(--nx-space-2);
152
+ padding: 0 var(--nx-space-4);
153
+ font-size: var(--nx-text-md);
154
+ }
155
+
156
+ /*
157
+ * labels="selected": icons only, and the selected option grows to show its label. Options differ in width,
158
+ * so the selected option draws the indicator itself, and its label column animates from 0 to full width.
159
+ */
160
+ .nx-tabs--labels-selected {
161
+ display: inline-flex;
162
+ gap: 2px;
163
+ }
164
+
165
+ .nx-tabs--labels-selected.nx-tabs--block {
166
+ display: flex;
167
+ }
168
+
169
+ /* Icon-only options are square: side padding centers the 1.15em icon in the option's height. */
170
+ .nx-tabs--labels-selected .nx-tabs__option {
171
+ flex: 0 0 auto;
172
+ gap: 0;
173
+ padding: 0 calc((var(--nx-tabs-height) - 2 * var(--nx-tabs-pad) - 2px - 1.15em) / 2);
174
+ transition:
175
+ color var(--nx-duration),
176
+ background-color var(--nx-duration),
177
+ box-shadow var(--nx-duration),
178
+ padding 240ms cubic-bezier(0.2, 0, 0, 1);
179
+ }
180
+
181
+ .nx-tabs--labels-selected .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) {
182
+ padding: 0 var(--nx-space-3);
183
+ background: var(--nx-tabs-indicator);
184
+ box-shadow:
185
+ inset 0 0 0 1px var(--nx-tabs-indicator-border),
186
+ var(--nx-tabs-indicator-shadow);
187
+ }
188
+
189
+ /* In a full-width group, the selected option takes the free space. */
190
+ .nx-tabs--labels-selected.nx-tabs--block .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) {
191
+ flex: 1 1 auto;
192
+ }
193
+
194
+ /* The label column and the space before it both animate from 0, so a hidden label takes no width at all. */
195
+ .nx-tabs--labels-selected .nx-tabs__label {
196
+ display: grid;
197
+ grid-template-columns: 0fr;
198
+ margin-inline-start: 0;
199
+ padding-block: 0;
200
+ transition:
201
+ grid-template-columns 240ms cubic-bezier(0.2, 0, 0, 1),
202
+ margin 240ms cubic-bezier(0.2, 0, 0, 1);
203
+ }
204
+
205
+ .nx-tabs--labels-selected .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) .nx-tabs__label {
206
+ grid-template-columns: 1fr;
207
+ margin-inline-start: 6px;
208
+ }
209
+
210
+ /* An option without an icon always shows its label. */
211
+ .nx-tabs--labels-selected .nx-tabs__option:not(:has(.nx-icon)) .nx-tabs__label {
212
+ grid-template-columns: 1fr;
213
+ }
214
+
215
+ /*
216
+ * `auto`: in a container wide enough for them, every option says what it is, as the chosen one does. 900px is a data
217
+ * table's bar with its Add, four lists, Reload, ⋮ and a full search on one line, and room to spare.
218
+ */
219
+ @container (min-width: 900px) {
220
+ .nx-tabs--labels-auto .nx-tabs__option {
221
+ padding: 0 var(--nx-space-3);
222
+ }
223
+
224
+ .nx-tabs--labels-auto .nx-tabs__label {
225
+ grid-template-columns: 1fr;
226
+ margin-inline-start: 6px;
227
+ }
228
+ }
229
+
230
+ .nx-tabs--labels-selected .nx-tabs__text {
231
+ min-width: 0;
232
+ overflow: hidden;
233
+ padding-block: 2px;
234
+ text-overflow: ellipsis;
235
+ }
236
+
237
+ /*
238
+ * multiple: several at once. There is no single place for a sliding indicator, so every chosen option is raised on
239
+ * its own, exactly as the one choice's indicator is, and an option that is not chosen sits flat on the track. The
240
+ * options wrap onto more rows when they do not fit, each row as tall as a one-row track's options, so a picker of
241
+ * 26 letters is one track of several rows rather than a line that runs off a phone. A one-character option is
242
+ * square, so a row of letters lines up. A choice group drawn as tabs (`RadioGroup design="tabs"`, `nx-tabs--wrap`)
243
+ * is drawn the same way: its choice lives in the inputs, not in a value the track could slide an indicator to.
244
+ */
245
+ :is(.nx-tabs--multiple, .nx-tabs--wrap) {
246
+ --nx-tabs-option-height: calc(var(--nx-tabs-height) - 2 * var(--nx-tabs-pad) - 2px);
247
+
248
+ display: inline-flex;
249
+ flex-wrap: wrap;
250
+ gap: 2px;
251
+ max-width: 100%;
252
+ height: auto;
253
+ }
254
+
255
+ :is(.nx-tabs--multiple, .nx-tabs--wrap).nx-tabs--block {
256
+ display: flex;
257
+ }
258
+
259
+ :is(.nx-tabs--multiple, .nx-tabs--wrap) .nx-tabs__option {
260
+ flex: 0 0 auto;
261
+ min-width: var(--nx-tabs-option-height);
262
+ height: var(--nx-tabs-option-height);
263
+ }
264
+
265
+ /* A chosen option keeps its raised look under the pointer too. */
266
+ :is(.nx-tabs--multiple, .nx-tabs--wrap) .nx-tabs__option:has(:checked) {
267
+ background: var(--nx-tabs-indicator);
268
+ box-shadow:
269
+ inset 0 0 0 1px var(--nx-tabs-indicator-border),
270
+ var(--nx-tabs-indicator-shadow);
271
+ }
272
+
273
+ /* One of several can be unchosen, so it keeps the pointer. */
274
+ .nx-tabs--multiple .nx-tabs__option:has(:checked) {
275
+ cursor: pointer;
276
+ }
277
+
278
+ :is(.nx-tabs--multiple, .nx-tabs--wrap) .nx-tabs__option:has(:checked):has(:focus-visible) {
279
+ box-shadow:
280
+ inset 0 0 0 1px var(--nx-tabs-indicator-border),
281
+ var(--nx-focus-ring);
282
+ }
283
+
284
+ /* A finger needs more than a small button's 22px: on a touch screen every option is a medium control's height, square at the least. */
285
+ @media (pointer: coarse) {
286
+ :is(.nx-tabs--multiple, .nx-tabs--wrap) {
287
+ --nx-tabs-option-height: var(--nx-control-md);
288
+ }
289
+ }
290
+
291
+ /*
292
+ * design="list": a vertical list of rows, for settings pages and side panels. There is no track: rows sit on the
293
+ * panel, the selected row is tinted (darker in light mode, lighter in dark mode) and marked by a bar at its start,
294
+ * so the choice reads at a glance in every theme, and the label turns bold as a second cue.
295
+ */
296
+ .nx-tabs--list {
297
+ --nx-tabs-list-row: var(--nx-control-md);
298
+ --nx-tabs-list-font: var(--nx-text-md);
299
+ --nx-tabs-list-pad: 6px var(--nx-space-3);
300
+ --nx-tabs-list-gap: 10px;
301
+ --nx-tabs-list-selected: light-dark(color-mix(in oklab, var(--nx-text) 6%, transparent), color-mix(in oklab, var(--nx-text) 10%, transparent));
302
+ --nx-tabs-list-bar: var(--nx-primary);
303
+ --nx-tabs-list-badge: color-mix(in oklab, var(--nx-text) 8%, transparent);
304
+ --nx-tabs-list-badge-color: var(--nx-text-muted);
305
+
306
+ display: grid;
307
+ grid-auto-flow: row;
308
+ grid-auto-columns: auto;
309
+ gap: 2px;
310
+ width: 100%;
311
+ height: auto;
312
+ padding: 0;
313
+ border: 0;
314
+ border-radius: 0;
315
+ background: none;
316
+ box-shadow: none;
317
+ }
318
+
319
+ .nx-tabs--list .nx-tabs__option {
320
+ position: relative;
321
+ justify-content: flex-start;
322
+ gap: var(--nx-tabs-list-gap);
323
+ min-height: var(--nx-tabs-list-row);
324
+ padding: var(--nx-tabs-list-pad);
325
+ border-radius: var(--nx-radius);
326
+ font-size: var(--nx-tabs-list-font);
327
+ text-align: left;
328
+ white-space: normal;
329
+ }
330
+
331
+ /* The bar at the start of the selected row grows in from the middle. */
332
+ .nx-tabs--list .nx-tabs__option::before {
333
+ content: "";
334
+ position: absolute;
335
+ top: 8px;
336
+ bottom: 8px;
337
+ left: 0;
338
+ width: 3px;
339
+ border-radius: 3px;
340
+ background: var(--nx-tabs-list-bar);
341
+ opacity: 0;
342
+ transform: scaleY(0.3);
343
+ transition:
344
+ opacity var(--nx-duration),
345
+ transform 200ms cubic-bezier(0.2, 0, 0, 1);
346
+ }
347
+
348
+ .nx-tabs--list .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) {
349
+ background: var(--nx-tabs-list-selected);
350
+ }
351
+
352
+ /* A list of several choices: a chosen row can be unchosen, so it keeps the pointer. */
353
+ .nx-tabs--list:not([role="radiogroup"], [role="tablist"]) .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) {
354
+ cursor: pointer;
355
+ }
356
+
357
+ .nx-tabs--list .nx-tabs__option:is(:has(:checked), [aria-selected="true"])::before {
358
+ opacity: 1;
359
+ transform: none;
360
+ }
361
+
362
+ .nx-tabs--list .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) .nx-tabs__text {
363
+ font-weight: 600;
364
+ }
365
+
366
+ .nx-tabs--list .nx-tabs__option .nx-icon {
367
+ flex-shrink: 0;
368
+ }
369
+
370
+ .nx-tabs--list .nx-tabs__label {
371
+ display: grid;
372
+ flex: 1 1 auto;
373
+ min-width: 0;
374
+ padding-block: 0;
375
+ }
376
+
377
+ .nx-tabs--list .nx-tabs__text {
378
+ overflow: hidden;
379
+ line-height: 1.35;
380
+ text-overflow: ellipsis;
381
+ white-space: nowrap;
382
+ }
383
+
384
+ .nx-tabs__description {
385
+ color: var(--nx-text-muted);
386
+ font-size: var(--nx-text-xs);
387
+ font-weight: 400;
388
+ line-height: 1.4;
389
+ }
390
+
391
+ .nx-tabs__badge {
392
+ flex-shrink: 0;
393
+ min-width: 20px;
394
+ padding: 0 7px;
395
+ border-radius: var(--nx-radius-full);
396
+ background: var(--nx-tabs-list-badge);
397
+ color: var(--nx-tabs-list-badge-color);
398
+ font-size: var(--nx-text-xs);
399
+ font-weight: 600;
400
+ font-variant-numeric: tabular-nums;
401
+ line-height: 20px;
402
+ text-align: center;
403
+ }
404
+
405
+ /* Primary: the selected row takes the primary soft background and the primary text. */
406
+ .nx-tabs--list.nx-tabs--primary {
407
+ --nx-tabs-list-selected: var(--nx-primary-soft);
408
+ --nx-tabs-active-color: var(--nx-primary);
409
+ --nx-tabs-active-icon: var(--nx-primary);
410
+ }
411
+
412
+ .nx-tabs--list.nx-tabs--primary .nx-tabs__option:is(:has(:checked), [aria-selected="true"]) .nx-tabs__badge {
413
+ background: var(--nx-primary-solid);
414
+ color: var(--nx-primary-text);
415
+ }
416
+
417
+ .nx-tabs--list.nx-tabs--sm {
418
+ --nx-tabs-list-row: var(--nx-control-sm);
419
+ --nx-tabs-list-font: var(--nx-text-sm);
420
+ --nx-tabs-list-pad: 4px var(--nx-space-2);
421
+ --nx-tabs-list-gap: 8px;
422
+ }
423
+
424
+ .nx-tabs--list.nx-tabs--lg {
425
+ --nx-tabs-list-row: var(--nx-control-lg);
426
+ --nx-tabs-list-font: var(--nx-text-lg);
427
+ --nx-tabs-list-pad: 8px var(--nx-space-4);
428
+ --nx-tabs-list-gap: 12px;
429
+ }
430
+
431
+ /*
432
+ * Tabs (`Tabs`): the same options as buttons with role="tab", marked by aria-selected rather than a checked radio.
433
+ * Only what a button brings of its own is taken away; the ring is the options' own box-shadow.
434
+ */
435
+ button.nx-tabs__option {
436
+ margin: 0;
437
+ border: 0;
438
+ background: none;
439
+ font-family: inherit;
440
+ outline: none;
441
+ }
442
+
443
+ [role="tablist"] .nx-tabs__option[aria-selected="true"] {
444
+ cursor: default;
445
+ }
446
+
447
+ /*
448
+ * design="auto": the list, until its container (a popup's workspace) is narrower than 720px; there the same rows lie
449
+ * in one strip that scrolls sideways, the bar under the chosen one. The strip keeps room for the focus ring, which a
450
+ * scrolling box would cut off.
451
+ */
452
+ @container (max-width: 719.98px) {
453
+ .nx-tabs--auto {
454
+ grid-auto-columns: max-content;
455
+ grid-auto-flow: column;
456
+ padding: 3px;
457
+ overflow-x: auto;
458
+ overscroll-behavior-x: contain;
459
+ scrollbar-width: none;
460
+ }
461
+
462
+ .nx-tabs--auto .nx-tabs__option {
463
+ white-space: nowrap;
464
+ }
465
+
466
+ .nx-tabs--auto .nx-tabs__option::before {
467
+ top: auto;
468
+ right: 10px;
469
+ bottom: 0;
470
+ left: 10px;
471
+ width: auto;
472
+ height: 3px;
473
+ transform: scaleX(0.3);
474
+ }
475
+
476
+ .nx-tabs--auto .nx-tabs__description {
477
+ display: none;
478
+ }
479
+ }
480
+
481
+ /* A tab's panel: it takes the focus when it holds nothing that does, so its ring is drawn inside it. */
482
+ .nx-tab-panel {
483
+ min-width: 0;
484
+ }
485
+
486
+ .nx-tab-panel:focus-visible {
487
+ outline: 2px solid var(--nx-primary);
488
+ outline-offset: -2px;
489
+ }
490
+
491
+ [data-nx-motion="reduce"] .nx-tabs__indicator,
492
+ [data-nx-motion="reduce"] :is(.nx-tabs--multiple, .nx-tabs--wrap) .nx-tabs__option,
493
+ [data-nx-motion="reduce"] .nx-tabs--labels-selected :is(.nx-tabs__option, .nx-tabs__label),
494
+ [data-nx-motion="reduce"] .nx-tabs--list .nx-tabs__option::before {
495
+ transition: none;
496
+ }
497
+ }