Разработка кастомных компонентов ввода (Input Components) Sanity

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомных компонентов ввода (Input Components) Sanity
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомных компонентов ввода (Input Components) Sanity

Sanity позволяет заменить стандартный UI поля на кастомный React компонент. Используется для сложных типов ввода: карты, тегов с автодополнением, цветовых палитр, нестандартных редакторов.

Базовый кастомный Input

// components/studio/ColorPickerInput.tsx
import { useCallback } from 'react'
import { set, unset } from 'sanity'
import type { StringInputProps } from 'sanity'

const PRESET_COLORS = ['#FF5733', '#33FF57', '#3357FF', '#FF33A8', '#FFAA00', '#00AAFF', '#9B59B6', '#1ABC9C']

export function ColorPickerInput(props: StringInputProps) {
  const { value, onChange, readOnly, elementProps } = props

  const handleSelect = useCallback(
    (color: string) => {
      onChange(color ? set(color) : unset())
    },
    [onChange]
  )

  return (
    <div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 8 }}>
        {PRESET_COLORS.map(color => (
          <div
            key={color}
            title={color}
            onClick={() => !readOnly && handleSelect(color)}
            style={{
              width: 28,
              height: 28,
              borderRadius: '50%',
              background: color,
              cursor: readOnly ? 'default' : 'pointer',
              border: value === color ? '3px solid var(--card-focus-ring-color)' : '2px solid transparent',
              outline: value === color ? '2px solid white' : 'none',
              boxSizing: 'border-box',
            }}
          />
        ))}
      </div>
      <input
        {...elementProps}
        type="text"
        value={value || ''}
        onChange={e => handleSelect(e.target.value)}
        placeholder="#000000 или rgb(0,0,0)"
        style={{ width: '100%' }}
      />
      {value && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
          <div style={{ width: 20, height: 20, background: value, borderRadius: 3 }} />
          <span style={{ fontSize: 12 }}>{value}</span>
        </div>
      )}
    </div>
  )
}
// Подключение в схеме
defineField({
  name: 'brandColor',
  type: 'string',
  components: {
    input: ColorPickerInput,
  },
})

Tag Input с автодополнением

// components/studio/TagInput.tsx
import { useState, useCallback } from 'react'
import { set, insert, remove } from 'sanity'
import type { ArrayInputProps, StringInputProps } from 'sanity'

const SUGGESTED_TAGS = ['TypeScript', 'React', 'Next.js', 'DevOps', 'Python', 'Docker']

export function TagInput(props: ArrayInputProps) {
  const { value = [], onChange, readOnly } = props
  const [input, setInput] = useState('')

  const addTag = useCallback(
    (tag: string) => {
      const trimmed = tag.trim()
      if (!trimmed || (value as string[]).includes(trimmed)) return

      onChange(insert([trimmed], 'after', [-1]))
      setInput('')
    },
    [value, onChange]
  )

  const removeTag = useCallback(
    (index: number) => {
      onChange(remove([{ _key: `tag-${index}` }]))
    },
    [onChange]
  )

  const suggestions = SUGGESTED_TAGS.filter(
    t => t.toLowerCase().includes(input.toLowerCase()) && !(value as string[]).includes(t)
  )

  return (
    <div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
        {(value as string[]).map((tag, i) => (
          <span
            key={i}
            style={{
              padding: '2px 10px',
              background: 'var(--card-border-color)',
              borderRadius: 12,
              fontSize: 13,
              display: 'flex',
              alignItems: 'center',
              gap: 4,
            }}
          >
            {tag}
            {!readOnly && (
              <button onClick={() => removeTag(i)} style={{ background: 'none', border: 'none', cursor: 'pointer' }}>
                ×
              </button>
            )}
          </span>
        ))}
      </div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); addTag(input) } }}
        placeholder="Добавить тег..."
        disabled={readOnly}
      />
      {suggestions.length > 0 && input && (
        <div style={{ border: '1px solid var(--card-border-color)', borderRadius: 4 }}>
          {suggestions.map(tag => (
            <div key={tag} onClick={() => addTag(tag)} style={{ padding: '6px 12px', cursor: 'pointer' }}>
              {tag}
            </div>
          ))}
        </div>
      )}
    </div>
  )
}

Slug с preview URL

// components/studio/SlugInput.tsx
import { useCallback } from 'react'
import { SlugInput as DefaultSlugInput } from 'sanity'
import type { SlugInputProps } from 'sanity'

export function SlugWithPreview(props: SlugInputProps) {
  const { value } = props
  const slug = value?.current

  return (
    <div>
      <DefaultSlugInput {...props} />
      {slug && (
        <div style={{ marginTop: 8, fontSize: 12, color: 'var(--card-muted-fg-color)' }}>
          URL:{' '}
          <a
            href={`${process.env.SANITY_STUDIO_PREVIEW_URL}/posts/${slug}`}
            target="_blank"
            rel="noreferrer"
          >
            /posts/{slug}
          </a>
        </div>
      )}
    </div>
  )
}

Поле с внешними данными

// components/studio/RegionSelect.tsx
import { useState, useEffect } from 'react'
import { set } from 'sanity'
import type { StringInputProps } from 'sanity'

export function RegionSelect(props: StringInputProps) {
  const { value, onChange } = props
  const [regions, setRegions] = useState<{ id: string; name: string }[]>([])

  useEffect(() => {
    fetch('/api/regions').then(r => r.json()).then(setRegions)
  }, [])

  return (
    <select value={value || ''} onChange={e => onChange(set(e.target.value))}>
      <option value="">— Выберите регион —</option>
      {regions.map(r => (
        <option key={r.id} value={r.id}>{r.name}</option>
      ))}
    </select>
  )
}

Сроки

Разработка 3–5 кастомных input компонентов — 2–3 дня.