@kenlo/sds-design-system 1.1.1 → 1.2.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.
package/README.md CHANGED
@@ -210,8 +210,9 @@ pedido e os ramos pessoais são bloqueados. Falta quem administra o pages.kenlo.
210
210
  automático: a esteira do `kenlo-pages` montar o portal a partir deste repositório, ou a esteira daqui
211
211
  publicar depois de cada merge.
212
212
 
213
- **Enquanto isso:** a esteira daqui já tem o passo `deploy-portal`, que publica o portal num bucket a cada
214
- merge na `main`, parado à espera de duas variáveis (`SDS_DOCS_BUCKET` e `SDS_GCP_SA_KEY`). Com
213
+ **Enquanto isso:** a esteira daqui publica o portal a cada merge na `main` em
214
+ https://storage.googleapis.com/kenlo-open-docs/sds-design-system/index.html, no mesmo bucket do portal
215
+ do KDLS e com a mesma chave (`GCP_SA_KEY_PRD`; decisão do Rafael Leobas, 13/11/2026). Com
215
216
  `base: './'` em `portal/vite.config.js`, o portal montado funciona em qualquer pasta (bucket ou
216
217
  pages.kenlo.io).
217
218
 
@@ -184,3 +184,10 @@ details.sds-accordion > summary::marker {
184
184
  details.sds-accordion.sds-accordion--closing > .sds-accordion__body-wrap {
185
185
  grid-template-rows: 0fr;
186
186
  }
187
+
188
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
189
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
190
+ (or the static border) to stay visible. */
191
+ [data-theme="dark"] .sds-accordion__header:hover {
192
+ background-color: var(--color-neutral-20);
193
+ }
@@ -379,3 +379,10 @@
379
379
  color: CanvasText;
380
380
  }
381
381
  }
382
+
383
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
384
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
385
+ (or the static border) to stay visible. */
386
+ [data-theme="dark"] .sds-badge--neutral.sds-badge--tone-10 {
387
+ background: var(--color-neutral-20);
388
+ }
@@ -304,14 +304,14 @@
304
304
  }
305
305
  [data-theme="dark"] .sds-button:disabled,
306
306
  [data-theme="dark"] .sds-button[aria-disabled="true"] {
307
- background-color: var(--color-neutral-30) !important;
307
+ background-color: var(--color-neutral-40) !important;
308
308
  border-color: var(--border-interactive-color) !important;
309
- color: var(--color-neutral-40) !important;
309
+ color: var(--color-neutral-50) !important;
310
310
  }
311
311
  [data-theme="dark"] .sds-button--link:disabled,
312
312
  [data-theme="dark"] .sds-button--link[aria-disabled="true"] {
313
313
  background-color: transparent !important;
314
- color: var(--color-neutral-40) !important;
314
+ color: var(--color-neutral-50) !important;
315
315
  border-color: transparent !important;
316
316
  }
317
317
  [data-theme="dark"] .sds-button--ghost:disabled,
@@ -320,7 +320,7 @@
320
320
  [data-theme="dark"] .sds-button--pill[aria-disabled="true"] {
321
321
  background-color: transparent !important;
322
322
  border-color: var(--border-interactive-color) !important;
323
- color: var(--color-neutral-40) !important;
323
+ color: var(--color-neutral-50) !important;
324
324
  }
325
325
  /* ──────────────────────────────────────────────
326
326
  NEUTRAL SCHEME DARK MODE OVERRIDE
@@ -331,7 +331,7 @@
331
331
  background-color: transparent;
332
332
  }
333
333
  [data-theme="dark"] .sds-button--pill:active:not(:disabled):not(.sds-button--loading) {
334
- background-color: var(--color-neutral-30);
334
+ background-color: var(--color-neutral-40);
335
335
  }
336
336
  /* Four more dead rules stood here, and they looked at first like a design intention worth
337
337
  keeping: they set the primary button's own FILL in the dark theme, to `--color-neutral-80`
@@ -140,7 +140,7 @@
140
140
  border-color: var(--border-static-color);
141
141
  }
142
142
  [data-theme="dark"] .sds-checkbox-container {
143
- border-color: var(--color-neutral-60);
143
+ border-color: var(--color-neutral-80);
144
144
  }
145
145
  /* Hint */
146
146
  .sds-checkbox-wrapper {
@@ -285,7 +285,7 @@
285
285
  border-bottom-color: var(--border-static-color);
286
286
  }
287
287
  [data-theme="dark"] .sds-datepicker__day--other {
288
- color: var(--color-neutral-50);
288
+ color: var(--color-neutral-70);
289
289
  }
290
290
  [data-theme="dark"] .sds-datepicker__footer {
291
291
  border-top-color: var(--border-static-color);
@@ -449,3 +449,10 @@
449
449
  var(--color-primary-50) border-box;
450
450
  border-color: transparent;
451
451
  }
452
+
453
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
454
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
455
+ (or the static border) to stay visible. */
456
+ [data-theme="dark"] .sds-datepicker__weekdays {
457
+ background: var(--color-neutral-20);
458
+ }
@@ -254,7 +254,7 @@
254
254
  }
255
255
  .sds-header__notification-time {
256
256
  font-size: 11px;
257
- color: var(--color-neutral-40);
257
+ color: var(--color-neutral-50);
258
258
  white-space: nowrap;
259
259
  flex-shrink: 0;
260
260
  margin-top: var(--spacing-0-5);
@@ -455,3 +455,19 @@
455
455
  [data-radius="soft"] .sds-header__icon-btn { border-radius: 11px; }
456
456
  [data-radius="medium"] .sds-header__icon-btn { border-radius: 15px; }
457
457
  [data-radius="rounded"] .sds-header__icon-btn { border-radius: var(--radius-full); }
458
+
459
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
460
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
461
+ (or the static border) to stay visible. */
462
+ [data-theme="dark"] .sds-header__products-item:hover,
463
+ [data-theme="dark"] .sds-header__notification-item:hover,
464
+ [data-theme="dark"] .sds-header__profile-item:hover,
465
+ [data-theme="dark"] .sds-header__mobile-row:hover {
466
+ background: var(--color-neutral-20);
467
+ }
468
+ [data-theme="dark"] .sds-header__notification-item {
469
+ border-bottom-color: var(--border-static-color);
470
+ }
471
+ [data-theme="dark"] .sds-header__mobile-divider {
472
+ border-top-color: var(--border-static-color);
473
+ }
@@ -82,15 +82,15 @@
82
82
  ────────────────────────────────────────────── */
83
83
 
84
84
  [data-theme="dark"] .sds-incrementer__btn:hover:not(:disabled) {
85
- background: var(--color-neutral-30);
85
+ background: var(--color-neutral-40);
86
86
  }
87
87
  [data-theme="dark"] .sds-incrementer__btn:active:not(:disabled) {
88
- background: var(--color-neutral-40);
88
+ background: var(--color-neutral-50);
89
89
  }
90
90
  [data-theme="dark"] .sds-incrementer--disabled {
91
- background: var(--color-neutral-30);
91
+ background: var(--color-neutral-40);
92
92
  border-color: var(--border-interactive-color);
93
- color: var(--color-neutral-40);
93
+ color: var(--color-neutral-50);
94
94
  }
95
95
 
96
96
  /* ── The site's gradient, on the stroke ────────────────────────────────────────
@@ -391,19 +391,50 @@
391
391
  .sds-input__hint--disabled { color: var(--color-neutral-40); }
392
392
  /* ── Textbox (textarea) variant ── */
393
393
  .sds-input--textbox {
394
- height: auto;
394
+ height: var(--sds-textarea-height, auto);
395
395
  min-height: var(--sds-textarea-min-height, 114px);
396
396
  align-items: flex-start;
397
- resize: vertical;
398
397
  overflow: hidden;
399
398
  }
400
- /* Field: fixed padding-top — guarantees constant height regardless of state */
399
+ /* Field: fixed padding-top — guarantees constant height regardless of state.
400
+ The textarea runs down to the bottom border, as in KDLS: past the last row the start of
401
+ the next line shows cut at the edge, so a longer text reads as one that goes on. */
401
402
  .sds-input--textbox .sds-input__field {
402
403
  min-height: 0;
403
404
  padding-top: var(--spacing-7);
404
- padding-bottom: var(--spacing-3);
405
+ padding-bottom: 0;
405
406
  align-self: stretch;
406
407
  }
408
+ /* The target grows with the corner radius so the rounded corner never eats into its 24px,
409
+ and the grip moves in with it so it stays inside the curve. */
410
+ .sds-input__resize-handle {
411
+ position: absolute;
412
+ right: 0;
413
+ bottom: 0;
414
+ width: max(var(--spacing-6), calc(var(--radius-md) * 0.5 + var(--spacing-4)));
415
+ height: max(var(--spacing-6), calc(var(--radius-md) * 0.5 + var(--spacing-4)));
416
+ display: flex;
417
+ align-items: flex-end;
418
+ justify-content: flex-end;
419
+ padding: 0 max(var(--spacing-1), calc(var(--radius-md) * 0.3)) max(var(--spacing-1), calc(var(--radius-md) * 0.3)) 0;
420
+ box-sizing: border-box;
421
+ color: var(--color-neutral-50);
422
+ cursor: ns-resize;
423
+ touch-action: none;
424
+ z-index: 1;
425
+ outline: none;
426
+ transition: color 120ms ease;
427
+ }
428
+ .sds-input__resize-handle:hover,
429
+ .sds-input--resizing .sds-input__resize-handle {
430
+ color: var(--color-primary-50-solid);
431
+ }
432
+ .sds-input__resize-handle:focus-visible {
433
+ box-shadow: var(--focus-ring-inset);
434
+ }
435
+ .sds-input--resizing {
436
+ user-select: none;
437
+ }
407
438
  .sds-input--textbox .sds-input__label {
408
439
  top: 26px;
409
440
  transform: translateY(-50%);
@@ -420,18 +451,26 @@
420
451
  font-size: var(--font-size-label-sm);
421
452
  line-height: var(--lh-label-sm);
422
453
  }
454
+ /* The textarea also runs over half of the field's right padding, so its scrollbar sits 8px
455
+ from the right border instead of 16px inside the text's room, as in KDLS. The gutter is
456
+ kept even without a bar, so the text ends at the same place with or without one. Its own
457
+ 8px at the end is the bottom room, reached when it scrolls to the end. */
423
458
  .sds-input__native--textarea {
459
+ box-sizing: border-box;
424
460
  padding-top: 0;
425
- padding-bottom: 0;
461
+ padding-right: 0;
462
+ padding-bottom: var(--spacing-2);
426
463
  resize: none;
427
464
  flex: 1;
428
465
  line-height: var(--lh-body-md);
429
466
  overflow-y: auto;
430
- width: 100%;
467
+ scrollbar-gutter: stable;
468
+ width: calc(100% + var(--spacing-2));
469
+ margin-right: calc(-1 * var(--spacing-2));
431
470
  }
432
471
  /* ── Dark mode ── */
433
472
  [data-theme="dark"] .sds-input__country {
434
- background-color: var(--color-neutral-20);
473
+ background-color: var(--color-neutral-30);
435
474
  }
436
475
  /* ── Phone country dropdown ── */
437
476
  .sds-input-phone-wrap {
@@ -572,3 +611,10 @@
572
611
  var(--color-primary-50) border-box;
573
612
  border-color: transparent;
574
613
  }
614
+
615
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
616
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
617
+ (or the static border) to stay visible. */
618
+ [data-theme="dark"] .sds-phone-dropdown__item:hover {
619
+ background: var(--color-neutral-20);
620
+ }
@@ -47,6 +47,9 @@ import { assertInputType, assertFeedback, assertWidth, formatValue, nativeTypeFo
47
47
  import { validateOnFor, SUBMIT_EVENT } from "../Form/form-context.js";
48
48
  import { rehyphenate } from "../../foundations/attrs.js";
49
49
 
50
+ static const TEXTBOX_LINE = 24;
51
+ static const TEXTBOX_CHROME = 42;
52
+
50
53
  class {
51
54
  onCreate(input) {
52
55
  this.state = {
@@ -56,6 +59,8 @@ class {
56
59
  showPassword: false,
57
60
  error: null,
58
61
  touched: false,
62
+ textboxHeight: null,
63
+ resizing: false,
59
64
  };
60
65
  }
61
66
 
@@ -137,9 +142,58 @@ class {
137
142
  }
138
143
 
139
144
  onDestroy() {
145
+ this.stopResize?.();
140
146
  this.getEl("field-root")?.removeEventListener(SUBMIT_EVENT, this.onFormSubmit);
141
147
  }
142
148
 
149
+ get textboxMinHeight() {
150
+ return (this.input.rows ?? 3) * TEXTBOX_LINE + TEXTBOX_CHROME;
151
+ }
152
+
153
+ clampTextboxHeight(value) {
154
+ return Math.max(this.textboxMinHeight, Math.round(value));
155
+ }
156
+
157
+ currentTextboxHeight() {
158
+ return this.state.textboxHeight
159
+ ?? (this.getEl("box")?.getBoundingClientRect().height || this.textboxMinHeight);
160
+ }
161
+
162
+ startResize(event) {
163
+ if (event.button !== 0) return;
164
+ event.preventDefault();
165
+ const startY = event.clientY;
166
+ const startHeight = this.currentTextboxHeight();
167
+ this.state.resizing = true;
168
+
169
+ const onMove = (moveEvent) => {
170
+ this.state.textboxHeight = this.clampTextboxHeight(startHeight + moveEvent.clientY - startY);
171
+ };
172
+ const onUp = () => {
173
+ this.stopResize = null;
174
+ this.state.resizing = false;
175
+ document.removeEventListener("pointermove", onMove);
176
+ document.removeEventListener("pointerup", onUp);
177
+ document.removeEventListener("pointercancel", onUp);
178
+ };
179
+ this.stopResize = onUp;
180
+ document.addEventListener("pointermove", onMove);
181
+ document.addEventListener("pointerup", onUp);
182
+ document.addEventListener("pointercancel", onUp);
183
+ }
184
+
185
+ resizeByKey(event) {
186
+ const keys = {
187
+ ArrowDown: () => this.currentTextboxHeight() + TEXTBOX_LINE,
188
+ ArrowUp: () => this.currentTextboxHeight() - TEXTBOX_LINE,
189
+ Home: () => this.textboxMinHeight,
190
+ };
191
+ const next = keys[event.key];
192
+ if (!next) return;
193
+ event.preventDefault();
194
+ this.state.textboxHeight = this.clampTextboxHeight(next());
195
+ }
196
+
143
197
  get currentValue() {
144
198
  return this.input.value !== undefined ? this.input.value : this.state.value;
145
199
  }
@@ -306,6 +360,7 @@ $ {
306
360
  // KDLS marks only the textbox. A modifier per type produced ten classes no rule
307
361
  // matched — dead weight in every field's class attribute.
308
362
  type === "textbox" ? "sds-input--textbox" : "",
363
+ state.resizing ? "sds-input--resizing" : "",
309
364
  effectiveFeedback !== "none" ? `sds-input--feedback-${effectiveFeedback}` : "",
310
365
  disabled ? "sds-input--disabled" : "",
311
366
  loading ? "sds-input--loading" : "",
@@ -348,9 +403,13 @@ $ {
348
403
  // that reads it and lost the line that writes it. 24px a line plus the 42px the label
349
404
  // and padding take, which is KDLS's arithmetic.
350
405
  <div
406
+ key="box"
351
407
  class=fieldBoxClass
352
408
  aria-busy=(loading ? "true" : null)
353
- style=(isTextarea ? `--sds-textarea-min-height: ${rows * 24 + 42}px` : null)
409
+ style=(isTextarea
410
+ ? `--sds-textarea-min-height: ${component.textboxMinHeight}px`
411
+ + (state.textboxHeight !== null ? `; --sds-textarea-height: ${state.textboxHeight}px` : "")
412
+ : null)
354
413
  >
355
414
  <if(showsCountry)>
356
415
  <!-- The flag opens the list and the dial code sits beside it, which is the whole
@@ -465,6 +524,24 @@ $ {
465
524
  </button>
466
525
  </if>
467
526
 
527
+ <if(isTextarea && !disabled)>
528
+ <div
529
+ class="sds-input__resize-handle"
530
+ role="separator"
531
+ aria-orientation="horizontal"
532
+ aria-label="Redimensionar campo"
533
+ aria-valuemin=component.textboxMinHeight
534
+ aria-valuenow=(state.textboxHeight ?? component.textboxMinHeight)
535
+ tabindex="0"
536
+ on-pointerdown("startResize")
537
+ on-keydown("resizeByKey")
538
+ >
539
+ <svg width="10" height="10" viewBox="0 0 10 10" fill="none" aria-hidden="true">
540
+ <path d="M9 1L1 9M9 5L5 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
541
+ </svg>
542
+ </div>
543
+ </if>
544
+
468
545
  <if(showsIncrementer)>
469
546
  <div class="sds-input__spinner">
470
547
  <button type="button" class="sds-input__spinner-btn" tabindex="-1"
@@ -99,9 +99,9 @@
99
99
  }
100
100
  [data-theme="dark"] .sds-pill:disabled,
101
101
  [data-theme="dark"] .sds-pill[aria-disabled="true"] {
102
- background-color: var(--color-neutral-30) !important;
102
+ background-color: var(--color-neutral-40) !important;
103
103
  border-color: var(--border-interactive-color) !important;
104
- color: var(--color-neutral-40) !important;
104
+ color: var(--color-neutral-50) !important;
105
105
  }
106
106
  [data-theme="dark"] .sds-pill--off:disabled,
107
107
  [data-theme="dark"] .sds-pill--off[aria-disabled="true"] {
@@ -180,7 +180,7 @@
180
180
  }
181
181
  [data-theme="dark"] .sds-pill:disabled .sds-notification,
182
182
  [data-theme="dark"] .sds-pill[aria-disabled="true"] .sds-notification {
183
- color: var(--color-neutral-30);
183
+ color: var(--color-neutral-40);
184
184
  }
185
185
  /* ──────────────────────────────────────────────
186
186
  DARK MODE
@@ -189,7 +189,7 @@
189
189
  background-color: transparent;
190
190
  }
191
191
  [data-theme="dark"] .sds-pill--off:active:not(:disabled):not(.sds-pill--loading) {
192
- background-color: var(--color-neutral-30);
192
+ background-color: var(--color-neutral-40);
193
193
  }
194
194
 
195
195
  /* ── The site's gradient, on the stroke ────────────────────────────────────────
@@ -151,7 +151,7 @@
151
151
  border-color: var(--border-static-color);
152
152
  }
153
153
  [data-theme="dark"] .sds-radio-container {
154
- border-color: var(--color-neutral-60);
154
+ border-color: var(--color-neutral-80);
155
155
  }
156
156
  /* Wrapper — vertical stack with tight gap */
157
157
  .sds-radio-wrapper {
@@ -179,3 +179,10 @@
179
179
  .sds-search-bar--fill {
180
180
  width: 100%;
181
181
  }
182
+
183
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
184
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
185
+ (or the static border) to stay visible. */
186
+ [data-theme="dark"] .sds-search-bar--disabled {
187
+ background-color: var(--color-neutral-20);
188
+ }
@@ -361,7 +361,7 @@
361
361
  }
362
362
  [data-theme="dark"] .sds-select__option:hover,
363
363
  [data-theme="dark"] .sds-select__option--selected {
364
- background: var(--color-neutral-20);
364
+ background: var(--color-neutral-30);
365
365
  }
366
366
 
367
367
  /* The clear control.
@@ -445,3 +445,11 @@
445
445
  [data-radius="soft"] .sds-select__option { border-radius: 11px; }
446
446
  [data-radius="medium"] .sds-select__option { border-radius: 15px; }
447
447
  [data-radius="rounded"] .sds-select__option { border-radius: var(--radius-full); }
448
+
449
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
450
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
451
+ (or the static border) to stay visible. */
452
+ [data-theme="dark"] .sds-select__option:focus-visible,
453
+ [data-theme="dark"] .sds-select__list:focus .sds-select__option[data-active="true"] {
454
+ background: var(--color-neutral-20);
455
+ }
@@ -98,3 +98,18 @@
98
98
  background-size: 400% 100%;
99
99
  animation: sds-skeleton-shimmer 1.4s ease infinite;
100
100
  }
101
+
102
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
103
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
104
+ (or the static border) to stay visible. */
105
+ [data-theme="dark"] .sds-skeleton__avatar,
106
+ [data-theme="dark"] .sds-skeleton__button,
107
+ [data-theme="dark"] .sds-skeleton__input,
108
+ [data-theme="dark"] .sds-skeleton__image,
109
+ [data-theme="dark"] .sds-skeleton__line {
110
+ background: var(--color-neutral-20);
111
+ }
112
+ [data-theme="dark"] .sds-skeleton--active {
113
+ background: linear-gradient(90deg, var(--color-neutral-20) 25%, var(--color-neutral-30) 37%, var(--color-neutral-20) 63%);
114
+ background-size: 400% 100%;
115
+ }
@@ -60,10 +60,10 @@
60
60
  background-color: var(--color-neutral-40);
61
61
  }
62
62
  [data-theme="dark"] .sds-switch--disabled .sds-switch__track {
63
- background-color: var(--color-neutral-40) !important;
63
+ background-color: var(--color-neutral-50) !important;
64
64
  }
65
65
  [data-theme="dark"] .sds-switch--disabled .sds-switch__thumb {
66
- background-color: var(--color-neutral-20);
66
+ background-color: var(--color-neutral-30);
67
67
  }
68
68
  .sds-switch--disabled .sds-switch__label {
69
69
  color: var(--color-neutral-40);
@@ -117,7 +117,7 @@
117
117
  border-color: var(--border-static-color);
118
118
  }
119
119
  [data-theme="dark"] .sds-switch-container {
120
- border-color: var(--color-neutral-60);
120
+ border-color: var(--color-neutral-80);
121
121
  }
122
122
  /* Wrapper — vertical stack with tight gap */
123
123
  .sds-switch-wrapper {
@@ -140,10 +140,10 @@
140
140
  .sds-switch-container + .sds-switch__hint { padding-left: var(--spacing-4); }
141
141
  /* Dark mode */
142
142
  [data-theme="dark"] .sds-switch--off .sds-switch__track {
143
- background-color: var(--color-neutral-40);
143
+ background-color: var(--color-neutral-50);
144
144
  }
145
145
  [data-theme="dark"] .sds-switch--off:not(.sds-switch--disabled):hover .sds-switch__track {
146
- background-color: var(--color-neutral-50);
146
+ background-color: var(--color-neutral-70);
147
147
  }
148
148
  [data-theme="dark"] .sds-switch-container--disabled {
149
149
  border-color: var(--border-interactive-color);
@@ -230,6 +230,11 @@
230
230
  opacity: 0;
231
231
  transition: opacity 120ms ease;
232
232
  }
233
+ /* Dark: neutral 40 would drop to #626262 (2.8:1 on the card); neutral 50 keeps
234
+ about the grey this hint had before the eleven-step ramp. */
235
+ [data-theme="dark"] .sds-table__sort--compact {
236
+ color: var(--color-neutral-50);
237
+ }
233
238
  .sds-table__sort--compact .sds-icon + .sds-icon {
234
239
  margin-top: calc(-1 * var(--spacing-2));
235
240
  }
@@ -339,3 +344,11 @@
339
344
  display: contents;
340
345
  padding: 0;
341
346
  }
347
+
348
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
349
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
350
+ (or the static border) to stay visible. */
351
+ [data-theme="dark"] .sds-table__row:hover:not(.sds-table__row--header),
352
+ [data-theme="dark"] .sds-table__row--selected:hover {
353
+ background: var(--color-neutral-20);
354
+ }
@@ -3,7 +3,7 @@
3
3
  by scripts/port-css.mjs. Divergences beyond the mechanical rename are noted inline.
4
4
  *//* ============================================================
5
5
  SDS Toast
6
- Light: --color-neutral-90 bg (#1A1A1A) + --color-neutral-00 text (#FFFFFF)
6
+ Light: --color-neutral-90 bg (#1C1C1C) + --color-neutral-00 text (#FFFFFF)
7
7
  Dark: tokens auto-invert → light bg + dark text
8
8
  ============================================================ */
9
9
 
@@ -37,11 +37,11 @@
37
37
  color: var(--color-neutral-40);
38
38
  }
39
39
  [data-theme="dark"] .sds-toggle--disabled {
40
- background: var(--color-neutral-30);
40
+ background: var(--color-neutral-40);
41
41
  border-color: var(--border-interactive-color);
42
42
  }
43
43
  [data-theme="dark"] .sds-toggle--disabled .sds-toggle__item--active {
44
- background: var(--color-neutral-40);
44
+ background: var(--color-neutral-50);
45
45
  color: var(--component-bg-color);
46
46
  }
47
47
  /* ── Item (default = 40px track height) ────────────────────────── */
@@ -207,3 +207,10 @@
207
207
  border: none;
208
208
  filter: brightness(1.1);
209
209
  }
210
+
211
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
212
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
213
+ (or the static border) to stay visible. */
214
+ [data-theme="dark"] .sds-toggle__item:hover:not(:disabled) {
215
+ background: var(--color-neutral-20);
216
+ }
@@ -196,3 +196,10 @@ button.sds-uploader {
196
196
  var(--color-primary-50) border-box;
197
197
  border-color: transparent;
198
198
  }
199
+
200
+ /* Dark theme on the eleven-step ramp: neutral 10 resolves to the component ground
201
+ (#1c1c1c), so a fill or divider painted with it on a component steps to neutral 20
202
+ (or the static border) to stay visible. */
203
+ [data-theme="dark"] .sds-uploader {
204
+ background: var(--color-neutral-20);
205
+ }
@@ -137,10 +137,15 @@
137
137
  aliases. All of it is removed; the mapping below is lifted verbatim from that
138
138
  file's own [data-color-scheme="neutral"] block. */
139
139
  :root {
140
- --color-primary-10: var(--color-neutral-10);
141
- --color-primary-20: var(--color-neutral-20);
142
- --color-primary-30: var(--color-neutral-30);
143
- --color-primary-40: var(--color-neutral-50);
140
+ /* The tints (10-40) use the ten-step pair names on purpose. They mirror one step short
141
+ (10 <-> 80), so in the dark theme they stay a step above the card (#1c1c1c):
142
+ primary-10 is #323232, and a selected row or a hover stays visible. The short names
143
+ mirror to the full 100 - N, which would put primary-10 on the card colour itself.
144
+ The light theme is the same either way. */
145
+ --color-primary-10: var(--color-neutral-10-80);
146
+ --color-primary-20: var(--color-neutral-20-70);
147
+ --color-primary-30: var(--color-neutral-30-60);
148
+ --color-primary-40: var(--color-neutral-50-40);
144
149
  /* The principal tone. It points at neutral-80 rather than neutral-50, and it is the only
145
150
  scheme where the 50 is not the family's own 50: a mid grey has no presence as the
146
151
  colour of a button. */
@@ -164,10 +169,11 @@
164
169
 
165
170
  /* --------------------------------COLOR SCHEMES--------------------------------- */
166
171
  [data-color-scheme="neutral"] {
167
- --color-primary-10: var(--color-neutral-10);
168
- --color-primary-20: var(--color-neutral-20);
169
- --color-primary-30: var(--color-neutral-30);
170
- --color-primary-40: var(--color-neutral-50);
172
+ /* Same tints as the floor above, for the same reason. */
173
+ --color-primary-10: var(--color-neutral-10-80);
174
+ --color-primary-20: var(--color-neutral-20-70);
175
+ --color-primary-30: var(--color-neutral-30-60);
176
+ --color-primary-40: var(--color-neutral-50-40);
171
177
  --color-primary-50: var(--color-neutral-80);
172
178
  --color-primary-60: var(--color-neutral-90);
173
179
  --color-primary-70: var(--color-neutral-90);
@@ -285,60 +291,83 @@
285
291
  }
286
292
 
287
293
  /* ------------------------------------------------------------------------------ *//* -----------------------------------NEUTRAL------------------------------------ */
294
+ /* The neutral ramp has eleven steps, 00 to 100 (Rafael Leobas, UX, 2026-11-13), the
295
+ same values as KDLS. Steps 10–90 invert: in the dark theme step N shows the light value
296
+ of step 100 − N, which is what the `N-(100-N)` pair names say. 00 and 100 stay the
297
+ absolutes declared above (white and black in both themes).
298
+
299
+ Two kinds of pair live here:
300
+ - `N-(100-N)`: the ramp itself, literals in both themes. The label-contrast generator
301
+ reads these.
302
+ - `N-(90-N)`: the names of the ten-step ramp, kept so nothing that reads one breaks.
303
+ Light = the new value of N, dark = the new value of 90 − N. `00-90` stays a literal
304
+ because it is the component ground (`--component-bg-color`); the rest point at the
305
+ ramp. */
306
+ :root {
307
+ --color-neutral-10-90: #ebebeb;
308
+ --color-neutral-20-80: #d0d0d0;
309
+ --color-neutral-30-70: #b6b6b6;
310
+ --color-neutral-40-60: #9d9d9d;
311
+ --color-neutral-50-50: #848484;
312
+ --color-neutral-60-40: #626262;
313
+ --color-neutral-70-30: #494949;
314
+ --color-neutral-80-20: #323232;
315
+ --color-neutral-90-10: #1c1c1c;
316
+ --color-neutral-00-100: var(--color-white);
317
+ --color-neutral-100-00: var(--color-black);
318
+ }
319
+
320
+ [data-theme="dark"] {
321
+ --color-neutral-10-90: #1c1c1c;
322
+ --color-neutral-20-80: #323232;
323
+ --color-neutral-30-70: #494949;
324
+ --color-neutral-40-60: #626262;
325
+ --color-neutral-50-50: #848484;
326
+ --color-neutral-60-40: #9d9d9d;
327
+ --color-neutral-70-30: #b6b6b6;
328
+ --color-neutral-80-20: #d0d0d0;
329
+ --color-neutral-90-10: #ebebeb;
330
+ --color-neutral-00-100: var(--color-black);
331
+ --color-neutral-100-00: var(--color-white);
332
+ }
333
+
334
+ /* The ten-step pair names. */
288
335
  :root {
289
336
  --color-neutral-00-90: #ffffff;
290
- --color-neutral-10-80: #f2f2f2;
291
- --color-neutral-20-70: #e6e6e6;
292
- --color-neutral-30-60: #d8d8d8;
293
- --color-neutral-40-50: #b8b8b8;
294
- --color-neutral-50-40: #808080;
295
- --color-neutral-60-30: #595959;
296
- --color-neutral-70-20: #404040;
297
- --color-neutral-80-10: #262626;
298
- --color-neutral-90-00: #1a1a1a;
337
+ --color-neutral-10-80: var(--color-neutral-10-90);
338
+ --color-neutral-20-70: var(--color-neutral-20-80);
339
+ --color-neutral-30-60: var(--color-neutral-30-70);
340
+ --color-neutral-40-50: var(--color-neutral-40-60);
341
+ --color-neutral-50-40: var(--color-neutral-50-50);
342
+ --color-neutral-60-30: var(--color-neutral-60-40);
343
+ --color-neutral-70-20: var(--color-neutral-70-30);
344
+ --color-neutral-80-10: var(--color-neutral-80-20);
345
+ --color-neutral-90-00: var(--color-neutral-90-10);
299
346
  }
300
347
 
301
348
  [data-theme="dark"] {
302
- --color-neutral-00-90: #1a1a1a;
303
- --color-neutral-10-80: #262626;
304
- --color-neutral-20-70: #404040;
305
- --color-neutral-30-60: #595959;
306
- --color-neutral-40-50: #808080;
307
- --color-neutral-50-40: #b8b8b8;
308
- --color-neutral-60-30: #d8d8d8;
309
- --color-neutral-70-20: #e6e6e6;
310
- --color-neutral-80-10: #f2f2f2;
311
- --color-neutral-90-00: #ffffff;
349
+ --color-neutral-00-90: #1c1c1c;
350
+ --color-neutral-10-80: var(--color-neutral-20-80);
351
+ --color-neutral-20-70: var(--color-neutral-30-70);
352
+ --color-neutral-30-60: var(--color-neutral-40-60);
353
+ --color-neutral-40-50: var(--color-neutral-50-50);
354
+ --color-neutral-50-40: var(--color-neutral-60-40);
355
+ --color-neutral-60-30: var(--color-neutral-70-30);
356
+ --color-neutral-70-20: var(--color-neutral-80-20);
357
+ --color-neutral-80-10: var(--color-neutral-90-10);
358
+ --color-neutral-90-00: var(--color-white);
312
359
  }
313
360
 
314
361
  :root {
315
- --color-neutral-10: var(
316
- --color-neutral-10-80
317
- );
318
- --color-neutral-20: var(
319
- --color-neutral-20-70
320
- );
321
- --color-neutral-30: var(
322
- --color-neutral-30-60
323
- );
324
- --color-neutral-40: var(
325
- --color-neutral-40-50
326
- );
327
- --color-neutral-50: var(
328
- --color-neutral-50-40
329
- );
330
- --color-neutral-60: var(
331
- --color-neutral-60-30
332
- );
333
- --color-neutral-70: var(
334
- --color-neutral-70-20
335
- );
336
- --color-neutral-80: var(
337
- --color-neutral-80-10
338
- );
339
- --color-neutral-90: var(
340
- --color-neutral-90-00
341
- );
362
+ --color-neutral-10: var(--color-neutral-10-90);
363
+ --color-neutral-20: var(--color-neutral-20-80);
364
+ --color-neutral-30: var(--color-neutral-30-70);
365
+ --color-neutral-40: var(--color-neutral-40-60);
366
+ --color-neutral-50: var(--color-neutral-50-50);
367
+ --color-neutral-60: var(--color-neutral-60-40);
368
+ --color-neutral-70: var(--color-neutral-70-30);
369
+ --color-neutral-80: var(--color-neutral-80-20);
370
+ --color-neutral-90: var(--color-neutral-90-10);
342
371
  }
343
372
 
344
373
  /* ------------------------------------------------------------------------------ *//* -----------------------------------PRIMARY------------------------------------ */
@@ -1210,7 +1239,8 @@
1210
1239
 
1211
1240
  /* ------------------------------------------------------------------------------ *//* -----------------------------PAGE BACKGROUND----------------------------- */
1212
1241
  /* KDLS wrote both of these as literals. They are the palette values they always meant:
1213
- #f2f2f2 is --color-neutral-10 in the light theme, and #000000 is --color-black. */
1242
+ --color-neutral-10 in the light theme (#ebebeb since the eleven-step ramp; it was
1243
+ #f2f2f2), and --color-black in the dark one. */
1214
1244
  :root {
1215
1245
  --page-bg-color: var(--color-neutral-10);
1216
1246
  }
@@ -2407,7 +2437,7 @@
2407
2437
  --color-neutral-20-on: var(--color-neutral-90);
2408
2438
  --color-neutral-30-on: var(--color-neutral-90);
2409
2439
  --color-neutral-40-on: var(--color-neutral-90);
2410
- --color-neutral-50-on: var(--color-black);
2440
+ --color-neutral-50-on: var(--color-neutral-90);
2411
2441
  --color-neutral-60-on: var(--color-neutral-10);
2412
2442
  --color-neutral-70-on: var(--color-neutral-10);
2413
2443
  --color-neutral-80-on: var(--color-neutral-10);
@@ -2526,7 +2556,7 @@
2526
2556
  --color-neutral-10-on: var(--color-neutral-90);
2527
2557
  --color-neutral-20-on: var(--color-neutral-90);
2528
2558
  --color-neutral-30-on: var(--color-neutral-90);
2529
- --color-neutral-40-on: var(--color-black);
2559
+ --color-neutral-40-on: var(--color-neutral-90);
2530
2560
  --color-neutral-50-on: var(--color-neutral-10);
2531
2561
  --color-neutral-60-on: var(--color-neutral-10);
2532
2562
  --color-neutral-70-on: var(--color-neutral-10);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenlo/sds-design-system",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "SDS, the Site Design System \u2014 Marko 5 components for Kenlo's public products (Site, Landing Page, Blog)",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {