@cloud-ru/ds-fields 2.2.7-preview-d90ac2be.0 → 2.3.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 (63) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -0
  3. package/dist/cjs/components/FieldColor/FieldColor.js +1 -1
  4. package/dist/cjs/components/FieldColor/styles.module.css +9 -9
  5. package/dist/cjs/components/FieldCombo/FieldCombo.js +1 -1
  6. package/dist/cjs/components/FieldDate/styles.module.css +3 -3
  7. package/dist/cjs/components/FieldSecure/FieldSecure.js +1 -1
  8. package/dist/cjs/components/FieldSelect/FieldSelect.js +2 -2
  9. package/dist/cjs/components/FieldSelect/styles.module.css +15 -18
  10. package/dist/cjs/components/FieldSelect/types.d.ts +1 -1
  11. package/dist/cjs/components/FieldSlider/FieldSlider.js +1 -1
  12. package/dist/cjs/components/FieldSlider/styles.module.css +0 -7
  13. package/dist/cjs/components/FieldStepper/FieldStepper.js +2 -2
  14. package/dist/cjs/components/FieldStepper/styles.module.css +6 -52
  15. package/dist/cjs/components/FieldTextArea/FieldTextArea.js +1 -1
  16. package/dist/cjs/components/FieldTextArea/styles.module.css +39 -36
  17. package/dist/cjs/components/FieldTime/FieldTime.js +1 -1
  18. package/dist/cjs/components/shared/helperComponents/FieldElementButton/styles.module.css +22 -22
  19. package/dist/cjs/components/shared/helperComponents/FieldShell/FieldShell.d.ts +3 -1
  20. package/dist/cjs/components/shared/helperComponents/FieldShell/FieldShell.js +2 -2
  21. package/dist/cjs/components/shared/styles.module.css +151 -124
  22. package/dist/cjs/style.css +245 -271
  23. package/dist/esm/components/FieldColor/FieldColor.js +1 -1
  24. package/dist/esm/components/FieldColor/styles.module.css +9 -9
  25. package/dist/esm/components/FieldCombo/FieldCombo.js +1 -1
  26. package/dist/esm/components/FieldDate/styles.module.css +3 -3
  27. package/dist/esm/components/FieldSecure/FieldSecure.js +1 -1
  28. package/dist/esm/components/FieldSelect/FieldSelect.js +2 -2
  29. package/dist/esm/components/FieldSelect/styles.module.css +15 -18
  30. package/dist/esm/components/FieldSelect/types.d.ts +1 -1
  31. package/dist/esm/components/FieldSlider/FieldSlider.js +2 -2
  32. package/dist/esm/components/FieldSlider/styles.module.css +0 -7
  33. package/dist/esm/components/FieldStepper/FieldStepper.js +2 -2
  34. package/dist/esm/components/FieldStepper/styles.module.css +6 -52
  35. package/dist/esm/components/FieldTextArea/FieldTextArea.js +1 -1
  36. package/dist/esm/components/FieldTextArea/styles.module.css +39 -36
  37. package/dist/esm/components/FieldTime/FieldTime.js +1 -1
  38. package/dist/esm/components/shared/helperComponents/FieldElementButton/styles.module.css +22 -22
  39. package/dist/esm/components/shared/helperComponents/FieldShell/FieldShell.d.ts +3 -1
  40. package/dist/esm/components/shared/helperComponents/FieldShell/FieldShell.js +2 -2
  41. package/dist/esm/components/shared/styles.module.css +151 -124
  42. package/dist/esm/style.css +245 -271
  43. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  44. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  45. package/package.json +20 -20
  46. package/src/components/FieldColor/FieldColor.tsx +1 -1
  47. package/src/components/FieldColor/styles.module.scss +5 -7
  48. package/src/components/FieldCombo/FieldCombo.tsx +1 -1
  49. package/src/components/FieldDate/styles.module.scss +1 -1
  50. package/src/components/FieldSecure/FieldSecure.tsx +2 -1
  51. package/src/components/FieldSelect/FieldSelect.tsx +2 -0
  52. package/src/components/FieldSelect/styles.module.scss +7 -5
  53. package/src/components/FieldSelect/types.ts +1 -0
  54. package/src/components/FieldSlider/FieldSlider.tsx +3 -3
  55. package/src/components/FieldSlider/styles.module.scss +0 -8
  56. package/src/components/FieldStepper/FieldStepper.tsx +46 -41
  57. package/src/components/FieldStepper/styles.module.scss +10 -60
  58. package/src/components/FieldTextArea/FieldTextArea.tsx +2 -1
  59. package/src/components/FieldTextArea/styles.module.scss +33 -22
  60. package/src/components/FieldTime/FieldTime.tsx +1 -1
  61. package/src/components/shared/helperComponents/FieldElementButton/styles.module.scss +2 -2
  62. package/src/components/shared/helperComponents/FieldShell/FieldShell.tsx +4 -0
  63. package/src/components/shared/styles.module.scss +58 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-ru/ds-fields",
3
- "version": "2.2.7-preview-d90ac2be.0",
3
+ "version": "2.3.0",
4
4
  "description": "Поля ввода с единой обёрткой label/caption/hint/error и общими осями size/validationState — текст, пароль, число, ползунок, дата, время, выбор, цвет.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,24 +39,24 @@
39
39
  "classnames": "^2.5.1",
40
40
  "merge-refs": "^2.0.0",
41
41
  "react-textarea-autosize": "^8.5.9",
42
- "@cloud-ru/ds-adaptive": "1.0.1",
43
- "@cloud-ru/ds-bottom-sheet": "1.1.3-preview-d90ac2be.0",
44
- "@cloud-ru/ds-button": "1.0.2",
45
- "@cloud-ru/ds-calendar": "1.0.19-preview-d90ac2be.0",
46
- "@cloud-ru/ds-color-picker": "1.0.8-preview-d90ac2be.0",
47
- "@cloud-ru/ds-divider": "1.0.1",
48
- "@cloud-ru/ds-dropdown": "1.0.10-preview-d90ac2be.0",
49
- "@cloud-ru/ds-field-decorator": "1.0.7-preview-d90ac2be.0",
50
- "@cloud-ru/ds-icons": "1.2.0",
51
- "@cloud-ru/ds-input-private": "1.0.5",
52
- "@cloud-ru/ds-list": "2.2.1-preview-d90ac2be.0",
53
- "@cloud-ru/ds-loader": "1.0.1",
54
- "@cloud-ru/ds-materials": "1.0.1",
55
- "@cloud-ru/ds-scroll": "1.0.3-preview-d90ac2be.0",
56
- "@cloud-ru/ds-skeleton": "1.0.1",
57
- "@cloud-ru/ds-slider": "1.0.5",
58
- "@cloud-ru/ds-tag": "1.1.3-preview-d90ac2be.0",
59
- "@cloud-ru/ds-tooltip": "1.0.5",
60
- "@cloud-ru/ds-utils": "1.1.0"
42
+ "@cloud-ru/ds-adaptive": "^1.0.1",
43
+ "@cloud-ru/ds-button": "^1.0.3",
44
+ "@cloud-ru/ds-color-picker": "^1.0.8",
45
+ "@cloud-ru/ds-divider": "^1.0.1",
46
+ "@cloud-ru/ds-dropdown": "^1.0.10",
47
+ "@cloud-ru/ds-field-decorator": "^1.0.7",
48
+ "@cloud-ru/ds-icons": "^1.2.1",
49
+ "@cloud-ru/ds-input-private": "^1.0.6",
50
+ "@cloud-ru/ds-list": "^2.2.1",
51
+ "@cloud-ru/ds-loader": "^1.0.2",
52
+ "@cloud-ru/ds-materials": "^2.0.0",
53
+ "@cloud-ru/ds-scroll": "^1.0.3",
54
+ "@cloud-ru/ds-skeleton": "^1.0.1",
55
+ "@cloud-ru/ds-slider": "^1.0.6",
56
+ "@cloud-ru/ds-tag": "^1.1.3",
57
+ "@cloud-ru/ds-utils": "^1.1.0",
58
+ "@cloud-ru/ds-tooltip": "^1.0.6",
59
+ "@cloud-ru/ds-calendar": "^1.0.19",
60
+ "@cloud-ru/ds-bottom-sheet": "^1.1.3"
61
61
  }
62
62
  }
@@ -329,8 +329,8 @@ export const FieldColor = forwardRef<HTMLInputElement, FieldColorProps>(function
329
329
  {...restInputProps}
330
330
  data-test-id={TEST_IDS.fieldColorInput}
331
331
  />
332
- {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
333
332
  </div>
333
+ {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
334
334
  </div>
335
335
  {/* Chevron присутствует во всех состояниях, включая readonly — паритет с Figma
336
336
  readonly-варианты тоже показывают chevron-down рядом с copy). */}
@@ -1,14 +1,12 @@
1
1
  @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
2
  @use '@cloud-ru/ds-figma-variables/build/scss/components/colorPicker.module' as colorPicker;
3
3
 
4
- // FieldColor size → colorPicker previewColor size (Figma fieldSelectColor):
5
- // s → colorPicker s (16px / radius 4), m → colorPicker m (24px / radius 8),
6
- // l → colorPicker m (24px / radius 8) — в макете L-поле референсит m-токен previewColor,
7
- // swatch не увеличивается до 32.
4
+ // FieldColor size → размер previewColor в токенах colorPicker (Figma fieldSelectColor):
5
+ // s → xs (16), m и l → s (24); у L-поля образец не растёт до 32.
8
6
  $swatch-sizes: (
9
- 's': 's',
10
- 'm': 'm',
11
- 'l': 'm',
7
+ 's': 'xs',
8
+ 'm': 's',
9
+ 'l': 's',
12
10
  );
13
11
 
14
12
  .trigger {
@@ -453,8 +453,8 @@ export const FieldCombo = forwardRef<HTMLInputElement, FieldComboProps>(function
453
453
  autoFocus={resolvedAutoFocus}
454
454
  data-test-id={innerTestIds?.input ?? TEST_IDS.fieldComboInput}
455
455
  />
456
- {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
457
456
  </div>
457
+ {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
458
458
  {postfix && <span className={fieldStyles.postfix}>{postfix}</span>}
459
459
  </div>
460
460
  {iconAfter && <div className={fieldStyles.iconSlot}>{iconAfter}</div>}
@@ -27,7 +27,7 @@ $sizes: 's', 'm', 'l';
27
27
 
28
28
  @each $size in $sizes {
29
29
  [data-size='#{$size}'] & {
30
- gap: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'contentWrapper', 'inputLine', 'gap');
30
+ gap: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'contentWrapper', 'inputWrapper', 'gap');
31
31
  }
32
32
  }
33
33
  }
@@ -346,8 +346,8 @@ export const FieldSecure = forwardRef<HTMLInputElement, FieldSecureProps>(functi
346
346
  data-test-id={TEST_IDS.fieldSecureInput}
347
347
  />
348
348
  </WithSkeleton>
349
- {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
350
349
  </div>
350
+ {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
351
351
  </div>
352
352
  </div>
353
353
  </div>
@@ -358,6 +358,7 @@ export const FieldSecure = forwardRef<HTMLInputElement, FieldSecureProps>(functi
358
358
  </div>
359
359
  <FieldElementButton
360
360
  ref={hideButtonRef}
361
+ className={fieldStyles.elementButtonInheritDisabled}
361
362
  variant='after'
362
363
  size={size}
363
364
  action={hidden ? <EyeSVG /> : <EyeClosedSVG />}
@@ -92,6 +92,7 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
92
92
  enableFuzzySearch = true,
93
93
  selectedOptionFormatter,
94
94
  footer,
95
+ footerDivider,
95
96
  footerActiveElementsRefs,
96
97
  loading,
97
98
  noDataState,
@@ -704,6 +705,7 @@ export const FieldSelect = forwardRef<HTMLInputElement, FieldSelectProps>(functi
704
705
  selection={droplistSelection}
705
706
  closeDroplistOnItemClick={closeDroplistOnItemClick}
706
707
  footer={footer}
708
+ footerDivider={footerDivider}
707
709
  footerActiveElementsRefs={footerActiveElementsRefs}
708
710
  loading={loading}
709
711
  noDataState={noDataState}
@@ -54,14 +54,16 @@ $chip-sizes: (
54
54
 
55
55
  @each $size, $tagSize in $chip-sizes {
56
56
  [data-size='#{$size}'] & {
57
+ $contentPaddingTop: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'contentWrapper', 'padding-top');
58
+ $contentPaddingBottom: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'contentWrapper', 'padding-bottom');
59
+ $inputWrapper: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'contentWrapper', 'inputWrapper');
57
60
  // gap = tag.tagRow.gap по mapped tag size (Figma fieldSelectMultiple chips: 2 для xs/s) —
58
61
  // одновременно column- и row-gap при flex-wrap (отступ между чипами и между рядами).
59
62
  gap: base.simple-var(tag.$tag, 'anatomy', 'size', $tagSize, 'tagRow', 'gap');
60
- // Вертикальные отступы строк чипов (Figma fieldContainer.contentWrapper.verticalPadding) —
61
- // shared-поле их не применяет (single-line центрируется), а мультиселекту они нужны, чтобы
62
- // ряды чипов не липли к краям контейнера (пропавшие отступы по макету).
63
- padding-top: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'contentWrapper', 'padding-top');
64
- padding-bottom: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'contentWrapper', 'padding-bottom');
63
+
64
+ // Отступы рядов чипов по макету: вертикальные — contentWrapper.verticalPadding, горизонтальные — inputWrapper.
65
+ padding: $contentPaddingTop base.simple-var($inputWrapper, 'padding-right') $contentPaddingBottom
66
+ base.simple-var($inputWrapper, 'padding-left');
65
67
  }
66
68
  }
67
69
  }
@@ -23,6 +23,7 @@ type FieldSelectDecoratorProps = Omit<FieldDecoratorProps, 'children' | 'validat
23
23
  type DroplistPassthrough = Pick<
24
24
  DroplistProps,
25
25
  | 'footer'
26
+ | 'footerDivider'
26
27
  | 'footerActiveElementsRefs'
27
28
  | 'loading'
28
29
  | 'noDataState'
@@ -1,7 +1,7 @@
1
1
  import { Button } from '@cloud-ru/ds-button';
2
2
  import { FieldDecorator, FieldDecoratorProps, SIZE, VALIDATION_STATE } from '@cloud-ru/ds-field-decorator';
3
3
  import { CheckSVG, CopySVG } from '@cloud-ru/ds-icons/interface/system';
4
- import { INPUT_MODE, InputPrivate, TYPE } from '@cloud-ru/ds-input-private';
4
+ import { BUTTON_SIZE_MAP, INPUT_MODE, InputPrivate, TYPE } from '@cloud-ru/ds-input-private';
5
5
  import { Slider, SliderProps } from '@cloud-ru/ds-slider';
6
6
  import { extractSupportProps, useValueControl } from '@cloud-ru/ds-utils';
7
7
  import cn from 'classnames';
@@ -438,13 +438,13 @@ export const FieldSlider = forwardRef<HTMLInputElement, FieldSliderProps>(functi
438
438
  />
439
439
  </div>
440
440
  {showCopyUi && (
441
- <span className={styles.copyButton}>
441
+ <span className={fieldStyles.postfixButtonsSlot}>
442
442
  <Button
443
443
  innerRef={copyButtonRef}
444
444
  type='button'
445
445
  view='function'
446
446
  appearance='neutral'
447
- size={size}
447
+ size={BUTTON_SIZE_MAP[size]}
448
448
  icon={copied ? <CheckSVG /> : <CopySVG />}
449
449
  onClick={handleCopy}
450
450
  onKeyDown={handleCopyButtonKeyDown}
@@ -37,11 +37,3 @@ $sizes: 's', 'm', 'l';
37
37
 
38
38
  color: base.$sn-theme-color-available-version-textTertiary;
39
39
  }
40
-
41
- // Copy-кнопка (readonly) центрируется в строке значения.
42
- .copyButton {
43
- display: flex;
44
- flex-shrink: 0;
45
- align-items: center;
46
- justify-content: center;
47
- }
@@ -260,7 +260,7 @@ export const FieldStepper = forwardRef<HTMLInputElement, FieldStepperProps>(func
260
260
 
261
261
  // Ширина инпута по числу символов значения (минимум 2ch), задаётся через `ch`-юниты в CSS-переменной —
262
262
  // без px-хардкода и подгонки под ширину глифа.
263
- const inputWidthCh = Math.max(2, String(value).length);
263
+ const inputWidthCh = Math.max(1, String(value).length);
264
264
 
265
265
  const handleMouseEnter = useCallback(() => {
266
266
  if (!readOnly) {
@@ -374,51 +374,56 @@ export const FieldStepper = forwardRef<HTMLInputElement, FieldStepperProps>(func
374
374
  </div>
375
375
  <div className={fieldStyles.fieldContainer}>
376
376
  <div className={fieldStyles.contentWrapper}>
377
- <div className={styles.valueArea}>
378
- {prefix && <span className={styles.affix}>{prefix}</span>}
379
- <div className={styles.inputWrapper} style={{ '--field-stepper-input-ch': inputWidthCh } as CSSProperties}>
380
- <InputPrivate
381
- ref={mergeRefs(ref, localRef)}
382
- className={cn(fieldStyles.fieldInput, styles.input)}
383
- value={String(value)}
384
- type={TYPE.Number}
385
- disabled={disabled}
386
- readonly={readOnly}
387
- onChange={handleInputChange}
388
- onFocus={handleFocus}
389
- onBlur={handleBlur}
390
- onKeyDown={handleInputKeyDown}
391
- id={id}
392
- name={name}
393
- min={min}
394
- max={max}
395
- step={step}
396
- autoFocus={resolvedAutoFocus}
397
- data-test-id={TEST_IDS.fieldStepperInput}
398
- {...extractSupportProps(rest)}
399
- />
377
+ <div className={cn(fieldStyles.inputLine, styles.inputLine)}>
378
+ {prefix && <span className={fieldStyles.prefix}>{prefix}</span>}
379
+ <div className={cn(fieldStyles.inputArea, styles.inputArea)}>
380
+ <div
381
+ className={styles.inputWrapper}
382
+ style={{ '--field-stepper-input-ch': inputWidthCh } as CSSProperties}
383
+ >
384
+ <InputPrivate
385
+ ref={mergeRefs(ref, localRef)}
386
+ className={cn(fieldStyles.fieldInput, styles.input)}
387
+ value={String(value)}
388
+ type={TYPE.Number}
389
+ disabled={disabled}
390
+ readonly={readOnly}
391
+ onChange={handleInputChange}
392
+ onFocus={handleFocus}
393
+ onBlur={handleBlur}
394
+ onKeyDown={handleInputKeyDown}
395
+ id={id}
396
+ name={name}
397
+ min={min}
398
+ max={max}
399
+ step={step}
400
+ autoFocus={resolvedAutoFocus}
401
+ data-test-id={TEST_IDS.fieldStepperInput}
402
+ {...extractSupportProps(rest)}
403
+ />
404
+ </div>
400
405
  </div>
401
- {postfix && <span className={styles.affix}>{postfix}</span>}
406
+ {postfix && <span className={fieldStyles.postfix}>{postfix}</span>}
407
+ {showCopyUi && (
408
+ <span className={fieldStyles.postfixButtonsSlot}>
409
+ <Button
410
+ innerRef={copyButtonRef}
411
+ type='button'
412
+ view='function'
413
+ appearance='neutral'
414
+ size={BUTTON_SIZE_MAP[size]}
415
+ icon={copied ? <CheckSVG /> : <CopySVG />}
416
+ onClick={handleCopy}
417
+ onKeyDown={handleCopyButtonKeyDown}
418
+ tabIndex={-1}
419
+ data-test-id={TEST_IDS.fieldStepperCopyButton}
420
+ />
421
+ </span>
422
+ )}
402
423
  </div>
403
424
  </div>
404
425
  </div>
405
426
  <div className={fieldStyles.elementWrapperAfter}>
406
- {showCopyUi && (
407
- <span className={styles.copyButton}>
408
- <Button
409
- innerRef={copyButtonRef}
410
- type='button'
411
- view='function'
412
- appearance='neutral'
413
- size={BUTTON_SIZE_MAP[size]}
414
- icon={copied ? <CheckSVG /> : <CopySVG />}
415
- onClick={handleCopy}
416
- onKeyDown={handleCopyButtonKeyDown}
417
- tabIndex={-1}
418
- data-test-id={TEST_IDS.fieldStepperCopyButton}
419
- />
420
- </span>
421
- )}
422
427
  <div className={fieldStyles.lineWrapper} data-outline={background || undefined}>
423
428
  <Divider orientation='vertical' variant='regular' />
424
429
  </div>
@@ -1,15 +1,4 @@
1
1
  @use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
2
- @use '@cloud-ru/ds-figma-variables/build/scss/components/field.module' as field;
3
-
4
- $sizes: 's', 'm', 'l';
5
-
6
- // Figma-typo-мост: stepper inputLine.gap для size=m ссылается на токен size=s (=2px),
7
- // а не на size=m (=4px). s→s(2), m→s(2), l→l(6).
8
- $inputLineGapSizes: (
9
- 's': 's',
10
- 'm': 's',
11
- 'l': 'l',
12
- );
13
2
 
14
3
  // Tooltip оборачивает −/+ в block-триггер: растягиваем его на всю высоту слота,
15
4
  // чтобы buttonField-кнопка внутри занимала полную высоту поля (как в FieldText).
@@ -19,56 +8,30 @@ $inputLineGapSizes: (
19
8
  height: 100%;
20
9
  }
21
10
 
22
- // Copy-кнопка (readonly) центрируется во всю высоту слота, как и step-триггеры.
23
- .copyButton {
24
- display: flex;
25
- align-items: center;
26
- height: 100%;
27
- }
28
-
29
- .valueArea {
30
- display: flex;
31
- flex: 1 1 auto;
32
- align-items: baseline;
11
+ // В макете префикс, значение и постфикс — группа по центру поля, обёртка значения по содержимому.
12
+ .inputLine {
33
13
  justify-content: center;
14
+ }
34
15
 
35
- min-width: 0;
36
-
37
- color: base.$sn-theme-color-available-version-textMain;
38
- text-align: center;
39
-
40
- @each $size, $tokenSize in $inputLineGapSizes {
41
- [data-size='#{$size}'] & {
42
- gap: base.simple-var(
43
- field.$field,
44
- 'anatomy',
45
- 'size',
46
- $tokenSize,
47
- 'fieldContainer',
48
- 'contentWrapper',
49
- 'inputLine',
50
- 'gap'
51
- );
52
- }
53
- }
16
+ .inputArea {
17
+ flex: 0 1 auto;
54
18
  }
55
19
 
56
20
  .inputWrapper {
57
21
  display: flex;
58
22
  flex: 0 0 auto;
59
-
60
- // ширина по числу символов значения (var выставляется компонентом), минимум 2ch.
61
- // +1ch — запас под каретку и субпиксельное округление: без него значение шириной ровно
62
- // N символов (напр. «100» = 3ch) обрезается ellipsis'ом инпута до «1…».
63
- width: calc((var(--field-stepper-input-ch, 2) + 1) * 1ch);
64
23
  max-width: 100%;
65
24
  }
66
25
 
67
26
  .input {
68
27
  flex: 0 0 auto;
69
28
 
70
- width: 100%;
29
+ // Ширина по числу символов значения (var выставляется компонентом). ch считается от шрифта
30
+ // самого input (типографика по размеру поля); цифры SB Sans моноширинные — 1 символ = 1ch.
31
+ // Запас dimension-2 — под каретку и округление, иначе «100» обрезается до «1…».
32
+ width: calc(var(--field-stepper-input-ch, 1) * 1ch + #{base.$sn-primitive-dimension-2});
71
33
  min-width: 1ch;
34
+ max-width: 100%;
72
35
 
73
36
  text-align: center;
74
37
 
@@ -83,16 +46,3 @@ $inputLineGapSizes: (
83
46
  -webkit-appearance: none;
84
47
  }
85
48
  }
86
-
87
- .affix {
88
- flex-shrink: 0;
89
- color: base.$sn-theme-color-available-version-textTertiary;
90
-
91
- // DS-типографика по size из корневого fieldWrapper. Без этого фолбэк на Nunito Sans 16/24
92
- // визуально не сходится с инпутом.
93
- @each $size in $sizes {
94
- [data-size='#{$size}'] & {
95
- @include base.composite-var(base.$sn-regular, 'body', $size);
96
- }
97
- }
98
- }
@@ -314,6 +314,7 @@ export const FieldTextArea = forwardRef<HTMLTextAreaElement, FieldTextAreaProps>
314
314
  hover={hover}
315
315
  header={header}
316
316
  footer={footer}
317
+ resizable={isResizable}
317
318
  style={{ '--field-textarea-min-rows': minRows, '--field-textarea-max-rows': maxRows } as CSSProperties}
318
319
  onMouseEnter={handleMouseEnter}
319
320
  onMouseLeave={handleMouseLeave}
@@ -335,7 +336,7 @@ export const FieldTextArea = forwardRef<HTMLTextAreaElement, FieldTextAreaProps>
335
336
  overflow={{ x: 'hidden' }}
336
337
  data-test-id={TEST_IDS.fieldTextAreaScrollArea}
337
338
  >
338
- <div className={fieldStyles.inputLine}>
339
+ <div className={fieldStyles.inputArea}>
339
340
  <TextareaAutosize
340
341
  ref={mergeRefs(ref, localRef)}
341
342
  className={styles.textarea}
@@ -20,6 +20,13 @@ $scrollbar-width: base.simple-var(scroll.$scroll, 'anatomy', 'container', 'size'
20
20
  @each $size in $sizes {
21
21
  [data-size='#{$size}'] & {
22
22
  --field-textarea-row-height: #{base.simple-var(base.$sn-regular, 'body', $size, 'line-height')};
23
+
24
+ // Полоса скролла в мастере прижата к правому краю поля и накрывает его боковой паддинг
25
+ // (`resizeWrapper` — absolute, anchor right). Скролл рисуется по краю контейнера-хоста,
26
+ // поэтому хост выводим за паддинг отрицательным отступом.
27
+ margin-right: calc(
28
+ -1 * #{base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'padding-right')}
29
+ );
23
30
  }
24
31
  }
25
32
  }
@@ -64,13 +71,12 @@ $scrollbar-width: base.simple-var(scroll.$scroll, 'anatomy', 'container', 'size'
64
71
  color: base.$sn-theme-color-available-version-textTertiary;
65
72
  }
66
73
 
67
- // DS-типографика + min-height multiLine из field-токенов per-size.
68
- // Без этого textarea наследует Nunito Sans 16/24 от Storybook root и не имеет минимальной высоты.
74
+ // DS-типографика per-size (без неё textarea наследует шрифт Storybook root).
75
+ // Минимальную высоту держит контейнер оболочки: одна строка, у resizable — multiLine-токен.
69
76
  @each $size in $sizes {
70
77
  [data-size='#{$size}'] & {
71
- // Резерв справа под колонку clear/copy-кнопок (квадрат centeredWrapper) + скроллбар
72
- // + горизонтальный паддинг inputLine (отступ слота кнопок от скроллбара по Figma):
73
- // кнопки позиционируются абсолютно поверх скролл-области (см. .postfixColumn).
78
+ // Резерв справа под колонку clear/copy-кнопок: в мастере текст упирается в неё, а полоса
79
+ // скролла идёт поверх. Кнопки позиционируются абсолютно (см. `.postfixColumn`).
74
80
  $button-slot: base.simple-var(
75
81
  field.$field,
76
82
  'anatomy',
@@ -78,21 +84,13 @@ $scrollbar-width: base.simple-var(scroll.$scroll, 'anatomy', 'container', 'size'
78
84
  $size,
79
85
  'fieldContainer',
80
86
  'centeredWrapper',
81
- 'max-height'
82
- );
83
- $input-line-padding: base.simple-var(
84
- field.$field,
85
- 'anatomy',
86
- 'size',
87
- $size,
88
- 'fieldContainer',
89
- 'contentWrapper',
90
- 'inputLine',
91
- 'padding-right'
87
+ 'width'
92
88
  );
89
+ // Хост скролла вынесен за правый паддинг поля (см. `.scrollContainer`), поэтому резерв
90
+ // текста добирает его обратно. Паддинг строки уже даёт `.inputArea`, второй раз не считаем.
91
+ $container-padding: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'padding-right');
93
92
 
94
- min-height: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'multiLine', 'min-height');
95
- padding-right: calc(#{$button-slot} + #{$scrollbar-width} + #{$input-line-padding});
93
+ padding-right: calc(#{$button-slot} + #{$container-padding});
96
94
 
97
95
  @include base.composite-var(base.$sn-regular, 'body', $size);
98
96
  }
@@ -132,7 +130,7 @@ $scrollbar-width: base.simple-var(scroll.$scroll, 'anatomy', 'container', 'size'
132
130
  }
133
131
 
134
132
  // Кнопки clear/copy — поверх скролл-области, левее скроллбара (паритет с Figma и легаси:
135
- // порядок справа налево — край поля, скроллбар, inputLine-паддинг, кнопки). Место под них
133
+ // порядок справа налево — край поля, скроллбар, inputWrapper-паддинг, кнопки). Место под них
136
134
  // резервирует padding-right textarea выше. Grid-ячейки клампят слот каждой кнопки до
137
135
  // centeredWrapper-квадрата: сама кнопка крупнее слота и симметрично выступает за него
138
136
  // (тот же приём, что .postfixButtonsSlot в shared/styles для FieldText), первый слот
@@ -157,7 +155,7 @@ $scrollbar-width: base.simple-var(scroll.$scroll, 'anatomy', 'container', 'size'
157
155
  $size,
158
156
  'fieldContainer',
159
157
  'centeredWrapper',
160
- 'max-height'
158
+ 'height'
161
159
  );
162
160
  $input-line-padding: base.simple-var(
163
161
  field.$field,
@@ -166,7 +164,7 @@ $scrollbar-width: base.simple-var(scroll.$scroll, 'anatomy', 'container', 'size'
166
164
  $size,
167
165
  'fieldContainer',
168
166
  'contentWrapper',
169
- 'inputLine',
167
+ 'inputWrapper',
170
168
  'padding-right'
171
169
  );
172
170
  $line-height: base.simple-var(base.$sn-regular, 'body', $size, 'line-height');
@@ -183,8 +181,21 @@ $scrollbar-width: base.simple-var(scroll.$scroll, 'anatomy', 'container', 'size'
183
181
  // Кнопка центрируется по первой строке текста: verticalPadding (отступ контента сверху)
184
182
  // + центровка слота в line-height. Так 32px-кнопка верхним краем садится ровно на
185
183
  // внутреннюю границу поля (Figma), а не выступает над ней.
184
+ $slot-width: base.simple-var(field.$field, 'anatomy', 'size', $size, 'fieldContainer', 'centeredWrapper', 'width');
185
+ $button-size: 'm';
186
+
187
+ @if $size == 's' {
188
+ $button-size: 's';
189
+ }
190
+
191
+ // Ширина кнопки — слот centeredWrapper из макета (16/24/24), высота остаётся высотой кнопки.
192
+ --sn-button-anatomy-size-#{$button-size}-container-iconOnly-minWidth: #{$slot-width};
193
+
186
194
  top: calc(#{$vertical-padding} + (#{$line-height} - #{$square}) / 2);
187
- right: calc(#{$scrollbar-width} + #{$input-line-padding});
195
+
196
+ // Правый край кнопки совпадает с внутренней границей поля (Figma): колонку уже кнопки
197
+ // на половину её выступа, поэтому компенсируем его.
198
+ right: calc((#{$slot-width} - #{$square}) / 2);
188
199
  grid-auto-rows: $square;
189
200
  width: $square;
190
201
  }
@@ -345,8 +345,8 @@ export const FieldTime = forwardRef<HTMLInputElement, FieldTimeProps>(function F
345
345
  {...extractSupportProps(rest)}
346
346
  data-test-id={TEST_IDS.fieldTimeInput}
347
347
  />
348
- {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
349
348
  </div>
349
+ {postfixButtons && <span className={fieldStyles.postfixButtonsSlot}>{postfixButtons}</span>}
350
350
  </div>
351
351
  {/* Иконка часов — декоративная; дроплист открывается кликом по полю. */}
352
352
  <div className={fieldStyles.iconSlot} data-test-id={TEST_IDS.fieldTimeIcon} aria-hidden>
@@ -92,10 +92,10 @@ $sizes: 's', 'm', 'l';
92
92
  // s → icon-xs (16), m/l → icon-s (24). max-* (как в @cloud-ru/ds-button): @cloud-ru/ds-icons задают инлайн
93
93
  // `style="width:24"`, который перебивает width, но не max-width — поэтому клампится без JS-пропа.
94
94
  .elementWrapper svg {
95
- $iconSize: base.$sn-density-size-icon-s;
95
+ $iconSize: base.$sn-density-icon-s;
96
96
 
97
97
  @if $size == 's' {
98
- $iconSize: base.$sn-density-size-icon-xs;
98
+ $iconSize: base.$sn-density-icon-xs;
99
99
  }
100
100
 
101
101
  max-width: $iconSize;
@@ -31,6 +31,8 @@ export type FieldShellProps = WithSupportProps<{
31
31
  header?: ReactNode;
32
32
  /** Ряд элементов под контентом (действия, счётчик) */
33
33
  footer?: ReactNode;
34
+ /** Многострочное поле с ручным изменением высоты — поднимает минимальную высоту контейнера */
35
+ resizable?: boolean;
34
36
  /** CSS-класс оболочки (fieldWrapper) */
35
37
  className?: string;
36
38
  /** Inline-стиль оболочки (например, CSS-переменные высоты) */
@@ -58,6 +60,7 @@ export function FieldShell({
58
60
  hover,
59
61
  header,
60
62
  footer,
63
+ resizable,
61
64
  className,
62
65
  style,
63
66
  onMouseEnter,
@@ -75,6 +78,7 @@ export function FieldShell({
75
78
  data-withbackground={background || undefined}
76
79
  data-focusvisible={focusVisible || undefined}
77
80
  data-hover={!readOnly && hover ? true : undefined}
81
+ data-resizable={resizable || undefined}
78
82
  style={style}
79
83
  onMouseEnter={onMouseEnter}
80
84
  onMouseLeave={onMouseLeave}