nitro_kit 2.0.0.alpha.2 → 2.0.0.alpha.4

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +229 -1
  3. data/README.md +25 -10
  4. data/STYLE_GUIDE.md +47 -5
  5. data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +3 -3
  6. data/app/assets/stylesheets/nitro_kit.css +2820 -1816
  7. data/app/components/nitro_kit/accordion.rb +2 -26
  8. data/app/components/nitro_kit/alert.rb +1 -11
  9. data/app/components/nitro_kit/app_navigation.rb +45 -11
  10. data/app/components/nitro_kit/app_shell.rb +1 -7
  11. data/app/components/nitro_kit/appearance_picker.rb +11 -12
  12. data/app/components/nitro_kit/avatar.rb +6 -1
  13. data/app/components/nitro_kit/avatar_stack.rb +3 -0
  14. data/app/components/nitro_kit/badge.rb +13 -4
  15. data/app/components/nitro_kit/button_group.rb +2 -2
  16. data/app/components/nitro_kit/checkbox.rb +7 -4
  17. data/app/components/nitro_kit/checkbox_group.rb +0 -25
  18. data/app/components/nitro_kit/combobox.rb +4 -33
  19. data/app/components/nitro_kit/command_palette.rb +42 -59
  20. data/app/components/nitro_kit/component.rb +57 -5
  21. data/app/components/nitro_kit/danger_zone.rb +5 -1
  22. data/app/components/nitro_kit/data_section.rb +28 -4
  23. data/app/components/nitro_kit/details_table.rb +19 -8
  24. data/app/components/nitro_kit/dialog.rb +23 -19
  25. data/app/components/nitro_kit/dropdown.rb +1 -6
  26. data/app/components/nitro_kit/dropzone.rb +4 -15
  27. data/app/components/nitro_kit/empty_state.rb +21 -2
  28. data/app/components/nitro_kit/field.rb +35 -11
  29. data/app/components/nitro_kit/form_builder.rb +1 -2
  30. data/app/components/nitro_kit/progressive_image.rb +2 -2
  31. data/app/components/nitro_kit/radio_button.rb +7 -4
  32. data/app/components/nitro_kit/radio_button_group.rb +0 -25
  33. data/app/components/nitro_kit/select.rb +4 -17
  34. data/app/components/nitro_kit/settings_layout.rb +17 -4
  35. data/app/components/nitro_kit/{form_section.rb → settings_section.rb} +18 -11
  36. data/app/components/nitro_kit/sheet.rb +29 -20
  37. data/app/components/nitro_kit/stat_grid.rb +18 -2
  38. data/app/components/nitro_kit/table.rb +22 -3
  39. data/app/components/nitro_kit/tabs.rb +2 -13
  40. data/app/components/nitro_kit/toast.rb +4 -2
  41. data/app/components/nitro_kit/toolbar.rb +2 -0
  42. data/app/components/nitro_kit/tooltip.rb +7 -10
  43. data/app/javascript/controllers/nk/app_shell_controller.js +3 -3
  44. data/app/javascript/controllers/nk/appearance_controller.js +6 -0
  45. data/app/javascript/controllers/nk/combobox_controller.js +51 -1
  46. data/app/javascript/controllers/nk/command_palette_controller.js +25 -11
  47. data/app/javascript/controllers/nk/dialog_controller.js +54 -0
  48. data/app/javascript/controllers/nk/dropdown_controller.js +45 -0
  49. data/app/javascript/controllers/nk/dropzone_controller.js +2 -2
  50. data/app/javascript/controllers/nk/progressive_image_controller.js +11 -7
  51. data/app/javascript/controllers/nk/tabs_controller.js +21 -2
  52. data/app/javascript/controllers/nk/tooltip_controller.js +9 -0
  53. data/config/locales/en.yml +4 -1
  54. data/docs/agent_guide.md +63 -123
  55. data/docs/agent_native_spec.md +72 -383
  56. data/docs/browser_support.md +82 -0
  57. data/docs/component_contracts.md +108 -82
  58. data/docs/customization.md +689 -91
  59. data/docs/hotwire.md +50 -76
  60. data/docs/initialization_prompt.md +26 -31
  61. data/docs/migration_1_to_2.md +88 -30
  62. data/docs/patterns/application_foundation.md +45 -119
  63. data/docs/patterns/crud_resource.md +44 -126
  64. data/docs/patterns/destructive_action.md +45 -77
  65. data/docs/patterns/flash_and_toast.md +18 -36
  66. data/docs/patterns/inline_edit.md +21 -44
  67. data/docs/patterns/queryable_collection.md +65 -161
  68. data/docs/patterns/resource_form.md +33 -81
  69. data/docs/rails_conventions.md +38 -75
  70. data/docs/rails_integration.md +96 -446
  71. data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +12 -0
  72. data/lib/nitro_kit/installation.rb +20 -7
  73. data/lib/nitro_kit/migration_inventory.rb +275 -8
  74. data/lib/nitro_kit/upgrade_smoke_test.rb +10 -12
  75. data/lib/nitro_kit/version.rb +1 -1
  76. data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +10 -3
  77. data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +4 -0
  78. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +13 -1
  79. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +32 -15
  80. data/src/stylesheets/nitro_kit/components/accordion.css +44 -44
  81. data/src/stylesheets/nitro_kit/components/alert.css +27 -36
  82. data/src/stylesheets/nitro_kit/components/app_navigation.css +92 -20
  83. data/src/stylesheets/nitro_kit/components/app_shell.css +199 -196
  84. data/src/stylesheets/nitro_kit/components/appearance_picker.css +61 -39
  85. data/src/stylesheets/nitro_kit/components/auth_shell.css +2 -2
  86. data/src/stylesheets/nitro_kit/components/avatar.css +15 -11
  87. data/src/stylesheets/nitro_kit/components/avatar_stack.css +48 -29
  88. data/src/stylesheets/nitro_kit/components/badge.css +13 -9
  89. data/src/stylesheets/nitro_kit/components/button.css +121 -79
  90. data/src/stylesheets/nitro_kit/components/button_group.css +19 -14
  91. data/src/stylesheets/nitro_kit/components/card.css +12 -6
  92. data/src/stylesheets/nitro_kit/components/checkbox.css +54 -67
  93. data/src/stylesheets/nitro_kit/components/checkbox_group.css +16 -16
  94. data/src/stylesheets/nitro_kit/components/combobox.css +39 -29
  95. data/src/stylesheets/nitro_kit/components/command_palette.css +110 -51
  96. data/src/stylesheets/nitro_kit/components/container.css +6 -6
  97. data/src/stylesheets/nitro_kit/components/control_group.css +61 -67
  98. data/src/stylesheets/nitro_kit/components/danger_zone.css +8 -8
  99. data/src/stylesheets/nitro_kit/components/data_section.css +4 -4
  100. data/src/stylesheets/nitro_kit/components/details_table.css +17 -15
  101. data/src/stylesheets/nitro_kit/components/dialog.css +8 -5
  102. data/src/stylesheets/nitro_kit/components/dropdown.css +36 -30
  103. data/src/stylesheets/nitro_kit/components/dropzone.css +78 -35
  104. data/src/stylesheets/nitro_kit/components/empty_state.css +7 -7
  105. data/src/stylesheets/nitro_kit/components/field.css +32 -28
  106. data/src/stylesheets/nitro_kit/components/field_group.css +13 -0
  107. data/src/stylesheets/nitro_kit/components/fieldset.css +8 -2
  108. data/src/stylesheets/nitro_kit/components/flex.css +1 -1
  109. data/src/stylesheets/nitro_kit/components/grid.css +1 -1
  110. data/src/stylesheets/nitro_kit/components/icon.css +5 -5
  111. data/src/stylesheets/nitro_kit/components/input.css +42 -8
  112. data/src/stylesheets/nitro_kit/components/layout.css +165 -165
  113. data/src/stylesheets/nitro_kit/components/page_header.css +7 -7
  114. data/src/stylesheets/nitro_kit/components/pagination.css +58 -49
  115. data/src/stylesheets/nitro_kit/components/pagination_bar.css +9 -9
  116. data/src/stylesheets/nitro_kit/components/palette.css +145 -77
  117. data/src/stylesheets/nitro_kit/components/progressive_image.css +45 -55
  118. data/src/stylesheets/nitro_kit/components/radio_button.css +48 -46
  119. data/src/stylesheets/nitro_kit/components/radio_button_group.css +49 -49
  120. data/src/stylesheets/nitro_kit/components/rich_text_area.css +16 -1
  121. data/src/stylesheets/nitro_kit/components/select.css +24 -15
  122. data/src/stylesheets/nitro_kit/components/settings_layout.css +26 -23
  123. data/src/stylesheets/nitro_kit/components/settings_section.css +84 -0
  124. data/src/stylesheets/nitro_kit/components/sheet.css +30 -10
  125. data/src/stylesheets/nitro_kit/components/stat_grid.css +11 -10
  126. data/src/stylesheets/nitro_kit/components/switch.css +60 -62
  127. data/src/stylesheets/nitro_kit/components/table.css +43 -35
  128. data/src/stylesheets/nitro_kit/components/tabs.css +48 -40
  129. data/src/stylesheets/nitro_kit/components/textarea.css +20 -5
  130. data/src/stylesheets/nitro_kit/components/toast.css +11 -47
  131. data/src/stylesheets/nitro_kit/components/toolbar.css +16 -41
  132. data/src/stylesheets/nitro_kit/components/tooltip.css +38 -29
  133. data/src/stylesheets/nitro_kit/components/typeset.css +150 -4
  134. data/src/stylesheets/nitro_kit/reset.css +15 -15
  135. data/src/stylesheets/nitro_kit/tokens.css +501 -79
  136. metadata +4 -4
  137. data/docs/new_app_strategy.md +0 -22
  138. data/src/stylesheets/nitro_kit/components/form_section.css +0 -71
@@ -1,11 +1,11 @@
1
1
  @layer nitro-kit.base {
2
2
  :where([data-nk="button"]) {
3
3
  --_nk-button-height: var(--nk-control-height-md);
4
- --_nk-button-padding: calc(var(--nk-space) * 4);
4
+ --_nk-button-padding: var(--nk-control-padding-inline);
5
5
  --_nk-button-icon-padding: calc(var(--nk-space) * 3);
6
- --_nk-button-radius: var(--nk-radius-lg);
6
+ --_nk-button-radius: var(--nk-button-radius, var(--nk-radius-lg));
7
7
  --_nk-button-font-size: var(--nk-text-sm);
8
- --_nk-button-line-height: 1.25rem;
8
+ --_nk-button-line-height: var(--nk-leading-normal);
9
9
  --_nk-button-background: var(--nk-button-default-background);
10
10
  --_nk-button-hover-background: var(--nk-button-default-hover-background);
11
11
  --_nk-button-foreground: var(--nk-button-default-foreground);
@@ -19,7 +19,7 @@
19
19
  ),
20
20
  var(--nk-button-default-border)
21
21
  );
22
- --_nk-button-shadow: 0 1px 2px 0 oklch(0 0 0 / 0.05);
22
+ --_nk-button-shadow: var(--nk-shadow-xs);
23
23
 
24
24
  position: relative;
25
25
  display: inline-flex;
@@ -52,10 +52,10 @@
52
52
  }
53
53
 
54
54
  :where(
55
- [data-nk="button"] > [data-slot="button-icon-start"],
56
- [data-nk="button"] > [data-slot="button-icon-end"],
57
- [data-nk="button"] > [data-slot="button-spinner"]
58
- ) {
55
+ [data-nk="button"] > [data-slot="button-icon-start"],
56
+ [data-nk="button"] > [data-slot="button-icon-end"],
57
+ [data-nk="button"] > [data-slot="button-spinner"]
58
+ ) {
59
59
  display: inline-flex;
60
60
  flex: none;
61
61
  }
@@ -66,18 +66,27 @@
66
66
  }
67
67
 
68
68
  :where([data-nk="button"] > [data-slot="button-label"]) {
69
+ display: inline-flex;
70
+ gap: calc(var(--nk-space) * 2);
71
+ align-items: center;
69
72
  min-inline-size: 0;
70
73
  overflow: hidden;
71
74
  text-overflow: ellipsis;
72
75
  }
73
76
 
77
+ /* text-overflow never applies inside a flex container, so text-only labels
78
+ become blocks to keep a visible ellipsis under pressure. */
79
+ :where([data-nk="button"] > [data-slot="button-label"]:not(:has(*))) {
80
+ display: block;
81
+ }
82
+
74
83
  :where(
75
- [data-nk="button"]:has(> [data-slot="button-icon-start"]),
76
- [data-nk="button"]:has(> [data-slot="button-spinner"]),
77
- [data-nk="button"]:has(
78
- > [data-slot="button-submission-spinner"][data-state="visible"]
79
- )
80
- ) {
84
+ [data-nk="button"]:has(> [data-slot="button-icon-start"]),
85
+ [data-nk="button"]:has(> [data-slot="button-spinner"]),
86
+ [data-nk="button"]:has(
87
+ > [data-slot="button-submission-spinner"][data-state="visible"]
88
+ )
89
+ ) {
81
90
  padding-inline-start: var(--_nk-button-icon-padding);
82
91
  }
83
92
 
@@ -86,15 +95,30 @@
86
95
  }
87
96
 
88
97
  :where(
89
- [data-nk="button"]:not(:has(> [data-slot="button-label"])):has(
90
- > [data-slot="button-icon-start"],
91
- > [data-slot="button-icon-end"],
92
- > [data-slot="button-spinner"]
93
- )
94
- ) {
98
+ [data-nk="button"]:not(:has(> [data-slot="button-label"])):has(
99
+ > [data-slot="button-icon-start"],
100
+ > [data-slot="button-icon-end"],
101
+ > [data-slot="button-spinner"]
102
+ )
103
+ ) {
95
104
  inline-size: var(--_nk-button-height);
96
105
  padding-inline: 0;
97
106
  }
107
+
108
+ /* Touch guidance wants a 44px minimum target. The rendered button keeps
109
+ its size; an invisible extension expands the tappable area to the large
110
+ control step on coarse pointers. */
111
+ @media (pointer: coarse) {
112
+ :where([data-nk="button"])::after {
113
+ position: absolute;
114
+ inset-block-start: 50%;
115
+ inset-inline-start: 50%;
116
+ inline-size: max(100%, var(--nk-control-height-lg));
117
+ block-size: max(100%, var(--nk-control-height-lg));
118
+ content: "";
119
+ translate: -50% -50%;
120
+ }
121
+ }
98
122
  }
99
123
 
100
124
  @layer nitro-kit.variant {
@@ -102,32 +126,33 @@
102
126
  --_nk-button-background: var(--nk-color-primary);
103
127
  --_nk-button-hover-background: var(--nk-color-primary-hover);
104
128
  --_nk-button-foreground: var(--nk-color-primary-foreground);
105
- --_nk-button-border: light-dark(oklch(0 0 0 / 0.1), transparent);
129
+ --_nk-button-border: light-dark(
130
+ color-mix(in oklab, var(--nk-black) 10%, transparent),
131
+ transparent
132
+ );
106
133
  --_nk-button-border-bottom: var(--_nk-button-border);
107
- --_nk-button-shadow: inset 0 1px oklch(1 0 0 / 0.2);
134
+ --_nk-button-shadow: inset 0 1px
135
+ color-mix(in oklab, var(--nk-white) 20%, transparent);
108
136
  }
109
137
 
110
138
  :where([data-nk="button"][data-variant="destructive"]) {
111
- --_nk-button-background: var(--nk-color-danger);
112
- --_nk-button-hover-background: var(--nk-color-danger-hover);
113
- --_nk-button-foreground: var(--nk-color-danger-foreground);
139
+ --_nk-button-background: var(--nk-color-destructive);
140
+ --_nk-button-hover-background: var(--nk-color-destructive-hover);
141
+ --_nk-button-foreground: var(--nk-color-destructive-foreground);
114
142
  --_nk-button-border-width: 0;
115
143
  --_nk-button-border: transparent;
116
144
  --_nk-button-border-bottom: transparent;
117
- --_nk-button-shadow:
118
- inset 0 1px var(--nk-color-danger), inset 0 2px oklch(1 0 0 / 0.15);
145
+ --_nk-button-shadow: inset 0 1px var(--nk-color-destructive),
146
+ inset 0 2px color-mix(in oklab, var(--nk-white) 15%, transparent);
119
147
  }
120
148
 
121
149
  :where([data-nk="button"][data-variant="ghost"]) {
122
150
  --_nk-button-background: transparent;
123
151
  --_nk-button-hover-background: light-dark(
124
- oklch(0.274 0.006 286.033 / 0.05),
125
- oklch(1 0 0 / 0.15)
126
- );
127
- --_nk-button-foreground: light-dark(
128
- oklch(0.274 0.006 286.033),
129
- oklch(1 0 0)
152
+ color-mix(in oklab, var(--nk-zinc-800) 5%, transparent),
153
+ color-mix(in oklab, var(--nk-white) 15%, transparent)
130
154
  );
155
+ --_nk-button-foreground: var(--nk-color-foreground);
131
156
  --_nk-button-border-width: 0;
132
157
  --_nk-button-border: transparent;
133
158
  --_nk-button-border-bottom: transparent;
@@ -136,18 +161,18 @@
136
161
 
137
162
  @media (prefers-color-scheme: dark) {
138
163
  :where(
139
- :root:not([data-theme])
140
- [data-nk="button"]:is(
141
- [data-variant="primary"],
142
- [data-variant="destructive"]
143
- )
144
- ) {
164
+ :root:not([data-theme])
165
+ [data-nk="button"]:is(
166
+ [data-variant="primary"],
167
+ [data-variant="destructive"]
168
+ )
169
+ ) {
145
170
  --_nk-button-border-width: 0;
146
171
  }
147
172
 
148
173
  :where(
149
- :root:not([data-theme]) [data-nk="button"][data-variant="destructive"]
150
- ) {
174
+ :root:not([data-theme]) [data-nk="button"][data-variant="destructive"]
175
+ ) {
151
176
  --_nk-button-shadow: none;
152
177
  }
153
178
  }
@@ -166,24 +191,23 @@
166
191
  --_nk-button-height: var(--nk-control-height-xs);
167
192
  --_nk-button-padding: calc(var(--nk-space) * 2);
168
193
  --_nk-button-icon-padding: var(--nk-space);
169
- --_nk-button-radius: var(--nk-radius-md);
194
+ --_nk-button-radius: var(--nk-button-radius, var(--nk-radius-md));
170
195
  --_nk-button-font-size: var(--nk-text-xs);
171
- --_nk-button-line-height: 1rem;
172
- --_nk-button-avatar-size: 1rem;
196
+ --_nk-button-avatar-size: calc(var(--nk-space) * 4);
173
197
  --_nk-button-avatar-font-size: calc(var(--nk-text-xs) * 0.75);
174
198
  }
175
199
 
176
200
  :where([data-nk="button"][data-size="sm"]) {
177
- --_nk-button-height: calc(var(--nk-space) * 8);
201
+ --_nk-button-height: var(--nk-control-height-sm);
178
202
  --_nk-button-padding: calc(var(--nk-space) * 3);
179
203
  --_nk-button-icon-padding: calc(var(--nk-space) * 2);
180
- --_nk-button-radius: var(--nk-radius-md);
181
- --_nk-button-avatar-size: 1.25rem;
204
+ --_nk-button-radius: var(--nk-button-radius, var(--nk-radius-md));
205
+ --_nk-button-avatar-size: calc(var(--nk-space) * 5);
182
206
  --_nk-button-avatar-font-size: calc(var(--nk-text-xs) * 0.875);
183
207
  }
184
208
 
185
209
  :where([data-nk="button"][data-size="md"]) {
186
- --_nk-button-avatar-size: 1.5rem;
210
+ --_nk-button-avatar-size: calc(var(--nk-space) * 6);
187
211
  --_nk-button-avatar-font-size: calc(var(--nk-text-xs) * 0.875);
188
212
  }
189
213
 
@@ -191,7 +215,7 @@
191
215
  --_nk-button-height: var(--nk-control-height-lg);
192
216
  --_nk-button-padding: calc(var(--nk-space) * 5);
193
217
  --_nk-button-icon-padding: calc(var(--nk-space) * 4);
194
- --_nk-button-avatar-size: 1.75rem;
218
+ --_nk-button-avatar-size: calc(var(--nk-space) * 7);
195
219
  --_nk-button-avatar-font-size: var(--nk-text-xs);
196
220
  }
197
221
 
@@ -199,10 +223,9 @@
199
223
  --_nk-button-height: var(--nk-control-height-xl);
200
224
  --_nk-button-padding: calc(var(--nk-space) * 8);
201
225
  --_nk-button-icon-padding: calc(var(--nk-space) * 7);
202
- --_nk-button-radius: var(--nk-radius-xl);
226
+ --_nk-button-radius: var(--nk-button-radius, var(--nk-radius-xl));
203
227
  --_nk-button-font-size: var(--nk-text-base);
204
- --_nk-button-line-height: 1.5rem;
205
- --_nk-button-avatar-size: 2rem;
228
+ --_nk-button-avatar-size: calc(var(--nk-space) * 8);
206
229
  --_nk-button-avatar-font-size: var(--nk-text-xs);
207
230
  gap: calc(var(--nk-space) * 4);
208
231
  }
@@ -221,12 +244,44 @@
221
244
  }
222
245
 
223
246
  :where(
224
- [data-nk="button"]:disabled,
225
- [data-nk="button"][aria-disabled="true"]
226
- ) {
247
+ [data-nk="button"]:disabled,
248
+ [data-nk="button"][aria-disabled="true"]
249
+ ) {
227
250
  pointer-events: none;
228
251
  cursor: default;
229
- opacity: 0.75;
252
+ opacity: var(--nk-disabled-opacity);
253
+ }
254
+
255
+ /* Dark surfaces need more than reduced opacity to read a saturated
256
+ destructive control as disabled. */
257
+ @media (prefers-color-scheme: dark) {
258
+ :where(
259
+ :root:not([data-theme])
260
+ [data-nk="button"][data-variant="destructive"]:is(
261
+ :disabled,
262
+ [aria-disabled="true"]
263
+ )
264
+ ) {
265
+ --_nk-button-background: color-mix(
266
+ in oklab,
267
+ var(--nk-color-destructive) 40%,
268
+ var(--nk-color-surface)
269
+ );
270
+ }
271
+ }
272
+
273
+ :where(
274
+ [data-theme="dark"]
275
+ [data-nk="button"][data-variant="destructive"]:is(
276
+ :disabled,
277
+ [aria-disabled="true"]
278
+ )
279
+ ) {
280
+ --_nk-button-background: color-mix(
281
+ in oklab,
282
+ var(--nk-color-destructive) 40%,
283
+ var(--nk-color-surface)
284
+ );
230
285
  }
231
286
 
232
287
  :where([data-nk="button"][aria-busy="true"] > [data-slot="button-spinner"]) {
@@ -234,9 +289,9 @@
234
289
  }
235
290
 
236
291
  :where(
237
- [data-nk="button"]
238
- > [data-slot="button-submission-spinner"][data-state="visible"]
239
- ) {
292
+ [data-nk="button"]
293
+ > [data-slot="button-submission-spinner"][data-state="visible"]
294
+ ) {
240
295
  display: inline-flex;
241
296
  animation: nk-button-spin 1s linear infinite;
242
297
  }
@@ -249,32 +304,19 @@
249
304
 
250
305
  @media (prefers-reduced-motion: reduce) {
251
306
  :where(
252
- [data-nk="button"][aria-busy="true"] > [data-slot="button-spinner"],
253
- [data-nk="button"]
254
- > [data-slot="button-submission-spinner"][data-state="visible"]
255
- ) {
307
+ [data-nk="button"][aria-busy="true"] > [data-slot="button-spinner"],
308
+ [data-nk="button"]
309
+ > [data-slot="button-submission-spinner"][data-state="visible"]
310
+ ) {
256
311
  animation: none;
257
312
  }
258
313
  }
259
314
  }
260
315
 
261
316
  @layer nitro-kit.compound {
262
- :where(
263
- [data-nk="button"]
264
- > [data-slot="button-label"]:has(> [data-nk="avatar"])
265
- ) {
266
- display: inline-flex;
267
- gap: calc(var(--nk-space) * 2);
268
- align-items: center;
269
- }
270
-
271
- :where(
272
- [data-nk="button"]
273
- > [data-slot="button-label"]
274
- > [data-nk="avatar"]
275
- ) {
276
- width: min(var(--_nk-avatar-size), var(--_nk-button-avatar-size));
277
- height: min(var(--_nk-avatar-size), var(--_nk-button-avatar-size));
317
+ :where([data-nk="button"] > [data-slot="button-label"] > [data-nk="avatar"]) {
318
+ inline-size: min(var(--_nk-avatar-size), var(--_nk-button-avatar-size));
319
+ block-size: min(var(--_nk-avatar-size), var(--_nk-button-avatar-size));
278
320
  font-size: min(
279
321
  var(--_nk-avatar-font-size),
280
322
  var(--_nk-button-avatar-font-size)
@@ -5,6 +5,8 @@
5
5
  align-items: stretch;
6
6
  max-inline-size: 100%;
7
7
  overflow-x: auto;
8
+ scrollbar-width: thin;
9
+ scrollbar-color: var(--nk-color-border) transparent;
8
10
  vertical-align: middle;
9
11
  }
10
12
 
@@ -16,38 +18,41 @@
16
18
  }
17
19
 
18
20
  :where(
19
- [data-nk="button-group"]
20
- > [data-slot="button-group-button"]:not(:first-child)
21
- ) {
21
+ [data-nk="button-group"]
22
+ > [data-slot="button-group-button"]:not(:first-child)
23
+ ) {
22
24
  margin-inline-start: calc(var(--nk-border-width) * -1);
23
25
  }
24
26
 
25
27
  :where(
26
- [data-nk="button-group"]
27
- > [data-slot="button-group-button"]:not(:only-child)
28
- ) {
28
+ [data-nk="button-group"]
29
+ > [data-slot="button-group-button"]:not(:only-child)
30
+ ) {
29
31
  border-radius: 0;
30
32
  }
31
33
 
32
34
  :where(
33
- [data-nk="button-group"]
34
- > [data-slot="button-group-button"]:first-child:not(:only-child)
35
- ) {
35
+ [data-nk="button-group"]
36
+ > [data-slot="button-group-button"]:first-child:not(:only-child)
37
+ ) {
36
38
  border-start-start-radius: var(--_nk-button-radius);
37
39
  border-end-start-radius: var(--_nk-button-radius);
38
40
  }
39
41
 
40
42
  :where(
41
- [data-nk="button-group"]
42
- > [data-slot="button-group-button"]:last-child:not(:only-child)
43
- ) {
43
+ [data-nk="button-group"]
44
+ > [data-slot="button-group-button"]:last-child:not(:only-child)
45
+ ) {
44
46
  border-start-end-radius: var(--_nk-button-radius);
45
47
  border-end-end-radius: var(--_nk-button-radius);
46
48
  }
47
49
 
48
50
  :where(
49
- [data-nk="button-group"] > [data-slot="button-group-button"]:focus-visible
50
- ) {
51
+ [data-nk="button-group"] > [data-slot="button-group-button"]:focus-visible
52
+ ) {
51
53
  z-index: 1;
54
+ /* The scroll container would clip an offset ring at its edges, so member
55
+ focus rings draw inside the member. */
56
+ outline-offset: calc(var(--nk-focus-offset) * -1);
52
57
  }
53
58
  }
@@ -15,8 +15,8 @@
15
15
  }
16
16
 
17
17
  :where([data-nk="card"] > [data-slot="card-title"]) {
18
- font-size: var(--nk-text-lg);
19
- font-weight: var(--nk-font-weight-semibold);
18
+ font-size: var(--nk-title-surface-size);
19
+ font-weight: var(--nk-title-surface-weight);
20
20
  line-height: var(--nk-leading-tight);
21
21
  text-wrap: balance;
22
22
  }
@@ -53,13 +53,19 @@
53
53
  border-start-end-radius: calc(var(--nk-radius-lg) - var(--nk-border-width));
54
54
  }
55
55
 
56
+ :where([data-nk="card"] > [data-slot="card-full"]:last-child) {
57
+ margin-block-end: calc(var(--_nk-card-padding) * -1);
58
+ border-end-start-radius: calc(var(--nk-radius-lg) - var(--nk-border-width));
59
+ border-end-end-radius: calc(var(--nk-radius-lg) - var(--nk-border-width));
60
+ }
61
+
56
62
  /* Full-width media fills the card edge to edge, so the card owns the corner
57
63
  radius and the media drops its own. */
58
64
  :where(
59
- [data-nk="card"]
60
- > [data-slot="card-full"]
61
- :is(img, [data-nk="progressive-image"])
62
- ) {
65
+ [data-nk="card"]
66
+ > [data-slot="card-full"]
67
+ :is(img, [data-nk="progressive-image"])
68
+ ) {
63
69
  border-radius: 0;
64
70
  }
65
71
 
@@ -1,39 +1,36 @@
1
1
  @layer nitro-kit.base {
2
+ /* Every box dimension derives from one choice size, so the two sizes are one
3
+ rule apart and the glyph keeps its proportions at either. The check stroke
4
+ is two border widths and its optical nudge is one, at every size. */
2
5
  :where([data-nk="checkbox"]) {
6
+ --_nk-choice-size: var(--nk-choice-size-md);
7
+
3
8
  display: inline-grid;
4
9
  min-inline-size: var(--nk-control-height-md);
5
10
  min-block-size: var(--nk-control-height-md);
6
11
  color: var(--nk-color-foreground);
7
12
  }
8
13
 
14
+ :where([data-nk="checkbox"][data-size="lg"]) {
15
+ --_nk-choice-size: var(--nk-choice-size-lg);
16
+ }
17
+
9
18
  :where([data-nk="checkbox"] > [data-slot="checkbox-label"]) {
10
19
  display: grid;
11
- grid-template-columns: 1.125rem minmax(0, 1fr);
20
+ grid-template-columns: var(--_nk-choice-size) minmax(0, 1fr);
12
21
  gap: calc(var(--nk-space) * 2);
13
22
  align-items: center;
14
23
  min-block-size: var(--nk-control-height-md);
15
24
  cursor: pointer;
16
25
  }
17
26
 
18
- :where([data-nk="checkbox"][data-size="lg"] > [data-slot="checkbox-label"]) {
19
- grid-template-columns: 1.5rem minmax(0, 1fr);
20
- }
21
-
22
27
  :where(
23
- [data-nk="checkbox"] [data-slot="checkbox-control"],
24
- [data-nk="checkbox"] [data-slot="checkbox-indicator"]
25
- ) {
28
+ [data-nk="checkbox"] [data-slot="checkbox-control"],
29
+ [data-nk="checkbox"] [data-slot="checkbox-indicator"]
30
+ ) {
26
31
  grid-area: 1 / 1;
27
- inline-size: 1.125rem;
28
- block-size: 1.125rem;
29
- }
30
-
31
- :where(
32
- [data-nk="checkbox"][data-size="lg"] [data-slot="checkbox-control"],
33
- [data-nk="checkbox"][data-size="lg"] [data-slot="checkbox-indicator"]
34
- ) {
35
- inline-size: 1.5rem;
36
- block-size: 1.5rem;
32
+ inline-size: var(--_nk-choice-size);
33
+ block-size: var(--_nk-choice-size);
37
34
  }
38
35
 
39
36
  :where([data-nk="checkbox"] [data-slot="checkbox-control"]) {
@@ -58,33 +55,24 @@
58
55
  }
59
56
 
60
57
  :where([data-nk="checkbox"] [data-slot="checkbox-indicator"])::after {
61
- inline-size: 0.5rem;
62
- block-size: 0.3rem;
58
+ inline-size: calc(var(--_nk-choice-size) * 0.44);
59
+ block-size: calc(var(--_nk-choice-size) * 0.27);
63
60
  content: "";
64
- border-block-end: 0.125rem solid currentcolor;
65
- border-inline-start: 0.125rem solid currentcolor;
61
+ border-block-end: calc(var(--nk-border-width) * 2) solid currentcolor;
62
+ border-inline-start: calc(var(--nk-border-width) * 2) solid currentcolor;
66
63
  opacity: 0;
67
- transform: translateY(-0.0625rem) rotate(-45deg);
68
- }
69
-
70
- :where(
71
- [data-nk="checkbox"][data-size="lg"] [data-slot="checkbox-indicator"]
72
- )::after {
73
- inline-size: 0.625rem;
74
- block-size: 0.375rem;
64
+ transform: translateY(calc(var(--nk-border-width) * -1)) rotate(-45deg);
75
65
  }
76
66
 
77
67
  :where([data-nk="checkbox"] [data-slot="checkbox-label-text"]) {
78
68
  display: block;
79
- }
80
-
81
- :where([data-nk="checkbox"] [data-slot="checkbox-content"]) {
82
69
  grid-column: 2;
83
70
  }
84
71
 
85
- :where([data-nk="checkbox"] [data-slot="checkbox-description"]) {
72
+ :where([data-nk="checkbox"] > [data-slot="checkbox-description"]) {
86
73
  display: block;
87
74
  margin-block-start: var(--nk-space);
75
+ padding-inline-start: calc(var(--_nk-choice-size) + (var(--nk-space) * 2));
88
76
  color: var(--nk-color-muted-foreground);
89
77
  font-size: var(--nk-text-sm);
90
78
  text-wrap: pretty;
@@ -93,68 +81,67 @@
93
81
 
94
82
  @layer nitro-kit.state {
95
83
  :where(
96
- [data-nk="checkbox"]
97
- [data-slot="checkbox-control"]:checked
98
- + [data-slot="checkbox-indicator"],
99
- [data-nk="checkbox"][data-state="indeterminate"]
100
- [data-slot="checkbox-indicator"]
101
- ) {
84
+ [data-nk="checkbox"]
85
+ [data-slot="checkbox-control"]:checked
86
+ + [data-slot="checkbox-indicator"],
87
+ [data-nk="checkbox"][data-state="indeterminate"]
88
+ [data-slot="checkbox-indicator"]
89
+ ) {
102
90
  background: var(--nk-color-primary);
103
91
  border-color: var(--nk-color-primary);
104
92
  }
105
93
 
106
94
  :where(
107
- [data-nk="checkbox"]
108
- [data-slot="checkbox-control"]:checked
109
- + [data-slot="checkbox-indicator"]
110
- )::after {
95
+ [data-nk="checkbox"]
96
+ [data-slot="checkbox-control"]:checked
97
+ + [data-slot="checkbox-indicator"]
98
+ )::after {
111
99
  opacity: 1;
112
100
  }
113
101
 
114
102
  :where(
115
- [data-nk="checkbox"][data-state="indeterminate"]
116
- [data-slot="checkbox-indicator"]
117
- )::after {
118
- inline-size: 0.55rem;
103
+ [data-nk="checkbox"][data-state="indeterminate"]
104
+ [data-slot="checkbox-indicator"]
105
+ )::after {
106
+ inline-size: calc(var(--_nk-choice-size) * 0.49);
119
107
  block-size: 0;
120
- border-block-end: 0.125rem solid currentcolor;
108
+ border-block-end: calc(var(--nk-border-width) * 2) solid currentcolor;
121
109
  border-inline-start: 0;
122
110
  opacity: 1;
123
111
  transform: none;
124
112
  }
125
113
 
126
114
  :where(
127
- [data-nk="checkbox"][data-size="lg"][data-state="indeterminate"]
128
- [data-slot="checkbox-indicator"]
129
- )::after {
130
- inline-size: 0.7rem;
131
- }
132
-
133
- :where(
134
- [data-nk="checkbox"]
135
- [data-slot="checkbox-control"]:focus-visible
136
- + [data-slot="checkbox-indicator"]
137
- ) {
115
+ [data-nk="checkbox"]
116
+ [data-slot="checkbox-control"]:focus-visible
117
+ + [data-slot="checkbox-indicator"]
118
+ ) {
138
119
  outline: var(--nk-focus-width) solid var(--nk-color-focus);
139
120
  outline-offset: var(--nk-focus-offset);
140
121
  }
141
122
 
142
123
  :where([data-nk="checkbox"][data-disabled="true"]) {
143
124
  cursor: not-allowed;
144
- opacity: 0.6;
125
+ opacity: var(--nk-disabled-opacity);
145
126
  }
146
127
 
147
128
  /* A server-rendered error describes the value that was submitted. Once the
148
129
  user has interacted with the control and it satisfies its own constraints,
149
- that error is stale, so the danger border stands down and the native
130
+ that error is stale, so the destructive border stands down and the native
150
131
  checked treatment takes over again. `:user-valid` never matches before the
151
132
  user touches the control, so an untouched invalid checkbox keeps its
152
133
  border. */
153
134
  :where(
154
- [data-nk="checkbox"]
155
- [data-slot="checkbox-control"][aria-invalid="true"]:not(:user-valid)
156
- + [data-slot="checkbox-indicator"]
157
- ) {
158
- border-color: var(--nk-color-danger);
135
+ [data-nk="checkbox"]
136
+ [data-slot="checkbox-control"][aria-invalid="true"]:not(:user-valid)
137
+ + [data-slot="checkbox-indicator"]
138
+ ) {
139
+ border-color: var(--nk-color-destructive);
140
+ }
141
+
142
+ @media (prefers-reduced-motion: reduce) {
143
+ :where([data-nk="checkbox"] [data-slot="checkbox-indicator"]) {
144
+ transition-duration: 0.01ms;
145
+ }
159
146
  }
160
147
  }