@kenlo/sds-design-system 1.1.1 → 1.1.2

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.
@@ -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,14 +451,22 @@
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 {
@@ -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"
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.1.2",
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": {