FieldSelectCreate

Поле выбора поверх FieldSelect из @cloud-ru/ds-fields со встроенной формой создания опции. Кнопка + Создать <entityName> показывается под полем и в футере дроплиста; по клику открывается форма в Drawer или Modal. Пропсы поля передаются через selectProps, состояния списка и поиск — из @cloud-ru/ds-list.

Когда использовать

  • Выбор из списка, где пользователь может тут же создать новый объект, если нужного нет.
  • Асинхронная подгрузка с состояниями загрузки/ошибки/пустого результата.

Когда не нужен FieldSelectCreate:

  • Простой выбор без создания — используйте FieldSelect из @cloud-ru/ds-fields.

Анатомия

Действие «Создать»

Кнопка + Создать <entityName> рендерится в двух местах: под полем и в футере дроплиста (SelectFooter). По клику открывается форма создания:

  • Drawer (по умолчанию) или Modal — задаётся через createLayoutType.
  • Содержимое формы и заголовок задаются в createLayoutProps (content — форма, title — заголовок).
  • Кнопки «Создать» / «Отмена» добавляет сам компонент; на «Создать» вызывается submitHandler.
  • submitHandler возвращает value новой опции — в single-режиме она выбирается в поле; afterClose — колбэк после закрытия.

entityName

Название сущности в единственном (single, вин. падеж) и множественном (plural) числе. Из них собираются тексты: кнопка «Создать single», пустые состояния «plural не обнаружены / не найдены», ошибка «Не удалось загрузить plural».

Права (permission, default canCreate)

Ограничивает доступ к чтению и созданию:

  • canCreate — доступны и выбор, и создание.
  • canRead — поле активно, но кнопки «Создать» недоступны (с tooltip причины).
  • none — поле недоступно (с tooltip).

Состояния списка

Управляются @cloud-ru/ds-list по флагам loading / dataError / dataFiltered (передаются через selectProps):

  • data — список опций.
  • error — «Не удалось загрузить plural» + кнопка «Обновить» (onRefetch).
  • noData — «plural не обнаружены»; иконку задаёт entityIcon (по умолчанию — поиск).
  • noResult — «plural не найдены. Измените запрос или создайте single».

Режим выбора (selectProps.selection, default single)

Наследуется от FieldSelect: single — одна опция, multiple — несколько. В multiple возвращённое из submitHandler значение не выбирается автоматически — выбор оставляется потребителю.

Size (default m)

Размер поля задаётся через selectProps.size: s, m, l.

Открытие на выбранном элементе (selectProps.scrollToSelectedItem)

FieldSelect проксирует scrollToSelectedItem из @cloud-ru/ds-list в дроплист — при открытии список прокручивается до выбранной опции:

<FieldSelectCreate selectProps={{ scrollToSelectedItem: true }}  />

Работает и с selectProps={{ virtualized: true }}.

Примеры использования

Базовый

Базовый«Создать» открывает форму создания; submitHandler добавляет опцию и выбирает её.
tsx
import { FieldText } from '@cloud-ru/ds-fields';
import { ItemId } from '@cloud-ru/ds-list';
import { FieldSelectCreate } from '@cloud-ru/ds-uikit-product-fields-predefined';
import { useState } from 'react';

export function FieldSelectCreateBasic() {
  const [items, setItems] = useState([
    { id: '1', content: { label: 'Production' } },
    { id: '2', content: { label: 'Staging' } },
  ]);
  const [value, setValue] = useState<ItemId>();
  const [name, setName] = useState('');

  const submitHandler = () => {
    const id = String(items.length + 1);
    setItems(prev => [...prev, { id, content: { label: name.trim() || `Окружение ${id}` } }]);
    setName('');
    return Promise.resolve(id);
  };

  return (
    <div style={{ width: 320 }}>
      <FieldSelectCreate
        entityName={{ single: 'Окружение', plural: 'Окружения' }}
        selectProps={{ label: 'Окружение', items, value, onChange: setValue }}
        createLayoutProps={{
          title: 'Создание окружения',
          content: <FieldText label='Название' value={name} onChange={setName} />,
        }}
        submitHandler={submitHandler}
      />
    </div>
  );
}

Асинхронная загрузка

Асинхронная загрузкаСостояние ошибки с кнопкой «Обновить» (onRefetch).
tsx
import { ItemId } from '@cloud-ru/ds-list';
import { FieldSelectCreate } from '@cloud-ru/ds-uikit-product-fields-predefined';
import { useState } from 'react';

export function FieldSelectCreateAsync() {
  const [value, setValue] = useState<ItemId>();
  const [loading, setLoading] = useState(false);
  const [dataError, setDataError] = useState(true);

  const load = () => {
    setDataError(false);
    setLoading(true);
    setTimeout(() => setLoading(false), 800);
  };

  return (
    <div style={{ width: 320 }}>
      <FieldSelectCreate
        entityName={{ single: 'Окружение', plural: 'Окружения' }}
        selectProps={{
          label: 'Окружение',
          items: [{ id: '1', content: { label: 'Production' } }],
          value,
          onChange: setValue,
          loading,
          dataError,
        }}
        onRefetch={load}
        createLayoutProps={{ title: 'Создание окружения', content: 'Форма создания' }}
        submitHandler={() => Promise.resolve('1')}
      />
    </div>
  );
}

Форма в модальном окне

Форма в модальном окнеcreateLayoutType=modal — форма создания открывается в модальном окне.
tsx
import { FieldText } from '@cloud-ru/ds-fields';
import { ItemId } from '@cloud-ru/ds-list';
import { CREATE_LAYOUT_TYPE, FieldSelectCreate } from '@cloud-ru/ds-uikit-product-fields-predefined';
import { useState } from 'react';

export function FieldSelectCreateModal() {
  const [items, setItems] = useState([
    { id: '1', content: { label: 'Production' } },
    { id: '2', content: { label: 'Staging' } },
  ]);
  const [value, setValue] = useState<ItemId>();
  const [name, setName] = useState('');

  const submitHandler = () => {
    const id = String(items.length + 1);
    setItems(prev => [...prev, { id, content: { label: name.trim() || `Окружение ${id}` } }]);
    setName('');
    return Promise.resolve(id);
  };

  return (
    <div style={{ width: 320 }}>
      <FieldSelectCreate
        entityName={{ single: 'Окружение', plural: 'Окружения' }}
        selectProps={{ label: 'Окружение', items, value, onChange: setValue }}
        createLayoutType={CREATE_LAYOUT_TYPE.Modal}
        createLayoutProps={{
          title: 'Создание окружения',
          content: <FieldText label='Название' value={name} onChange={setName} />,
        }}
        submitHandler={submitHandler}
      />
    </div>
  );
}

Props

Types

PropsFieldSelectCreateProps
PropTypeDefaultRequiredDescription
afterClose(() => void)noКолбэк после закрытия модалки/дровера создания.
classNamestringnoCSS-класс корневой обёртки.
createLayoutPropsOmit<ModalProps, ManagedLayoutKeys> | (Omit<DrawerProps, "position" | ManagedLayoutKeys> & { ...; })yesПропсы модалки создания (`content` — форма создания опции). Пропсы дровера создания (`content` — форма создания опции); `position` по умолчанию `right`.
createLayoutType"drawer" | "modal"drawernoПо клику на «Создать» открывается модальное окно. По клику на «Создать» открывается дровер (по умолчанию).
data-test-idstringno
entityIconJSXElementConstructor<{ size?: number; className?: string; }> | undefinednoИконка пустого состояния `noData` (по умолчанию — иконка поиска).
entityNameEntityNameyesНазвание сущности в единственном и множественном числе — для кнопок и пустых состояний.
onRefetch(() => void)noПовтор загрузки в состоянии ошибки (кнопка «Обновить» в футере `errorDataState`).
permission"canCreate" | "canRead" | "none"canCreatenoПрава пользователя: - `canCreate` (по умолчанию) — доступны и выбор, и создание; - `canRead` — поле активно, но создание недоступно (кнопки «Создать» с tooltip); - `none` — поле недоступно (с tooltip).
selectPropsSelectFieldPropsyesПропсы, прокидываемые в `FieldSelect` (`@cloud-ru/ds-fields`).
submitHandler() => Promise<string | void>yesДействие создания опции: возвращает `value` новой опции, которая выбирается в поле (single-режим).

Unions

Types

FieldSelectCreateProps

Unions

Адаптивность

Дроплист — адаптивный Droplist из @cloud-ru/ds-list: на desktop анкорный popover, на mobile — список в BottomSheet. Раскладку даёт AdaptiveProvider (@cloud-ru/ds-adaptive); отдельного пропа layoutType нет.

size на всех раскладках — база m. Автофокус поля выключается на mobile (наследуется из FieldSelect / @cloud-ru/ds-fields); вернуть — через selectProps={{ layoutPresets: { mobile: { autoFocus: true } } }}.

Storybook

Figma