⭐️

【Polaris和訳】Components/Forms②

2021/11/06に公開約16,300字

この記事について

この記事は、Polaris/Components/Formsの記事を和訳したものです。

記事内で使用する画像は、公式ドキュメント内の画像を引用して使用させていただいております。

Shopify アプリのご紹介

Shopify アプリである、「商品ページ発売予告アプリ | リテリア Coming Soon」は、商品ページを買えない状態のまま、発売日時の予告をすることができるアプリです。Shopify で Coming Soon 機能を実現することができます。

https://apps.shopify.com/shopify-application-314?locale=ja&from=daniel

Shopify アプリである、「らくらく日本語フォント設定|リテリア Font Picker」は、ノーコードで日本語フォントを使用できるアプリです。日本語フォントを導入することでブランドを演出することができます。

https://apps.shopify.com/font-picker-1?locale=ja&from=daniel

Forms②

Combobox

Comboboxコンポーネントは、Aria 1.2 の comboboxの仕様の一部を、テキストフィールドとリストボックスを含むポップオーバーに実装しています。Autocompleteと同様に、Comboboxはマーチャントが大量の選択肢の中から素早く検索して選択できるようにします。

Basic autocomplete

マーチャントが選択肢の中から素早くテキスト入力を行うために使用します。

React
React
function ComboboxExample() {
  const deselectedOptions = useMemo(
    () => [
      {value: 'rustic', label: 'Rustic'},
      {value: 'antique', label: 'Antique'},
      {value: 'vinyl', label: 'Vinyl'},
      {value: 'vintage', label: 'Vintage'},
      {value: 'refurbished', label: 'Refurbished'},
    ],
    [],
  );

  const [selectedOption, setSelectedOption] = useState();
  const [inputValue, setInputValue] = useState('');
  const [options, setOptions] = useState(deselectedOptions);

  const updateText = useCallback(
    (value) => {
      setInputValue(value);

      if (value === '') {
        setOptions(deselectedOptions);
        return;
      }

      const filterRegex = new RegExp(value, 'i');
      const resultOptions = deselectedOptions.filter((option) =>
        option.label.match(filterRegex),
      );
      setOptions(resultOptions);
    },
    [deselectedOptions],
  );

  const updateSelection = useCallback(
    (selected) => {
      const matchedOption = options.find((option) => {
        return option.value.match(selected);
      });

      setSelectedOption(selected);
      setInputValue((matchedOption && matchedOption.label) || '');
    },
    [options],
  );

  const optionsMarkup =
    options.length > 0
      ? options.map((option) => {
          const {label, value} = option;

          return (
            <Listbox.Option
              key={`${value}`}
              value={value}
              selected={selectedOption === value}
              accessibilityLabel={label}
            >
              {label}
            </Listbox.Option>
          );
        })
      : null;

  return (
    <div style={{height: '225px'}}>
      <Combobox
        activator={
          <Combobox.TextField
            prefix={<Icon source={SearchMinor} color="inkLighter" />}
            onChange={updateText}
            label="Search customers"
            labelHidden
            value={inputValue}
            placeholder="Search customers"
          />
        }
      >
        {options.length > 0 ? (
          <Listbox onSelect={updateSelection}>{optionsMarkup}</Listbox>
        ) : null}
      </Combobox>
    </div>
  );
}
HTML
HTML
<div>
  <div style="height: 225px;">
    <div>
      <div class="Polaris-Labelled--hidden">
        <div class="Polaris-Labelled__LabelWrapper">
          <div class="Polaris-Label"><label id="PolarisComboboxTextField2Label" for="PolarisComboboxTextField2" class="Polaris-Label__Text">Search customers</label></div>
        </div>
        <div class="Polaris-Connected">
          <div class="Polaris-Connected__Item Polaris-Connected__Item--primary">
            <div class="Polaris-TextField">
              <div class="Polaris-TextField__Prefix" id="PolarisComboboxTextField2Prefix"><span class="Polaris-Icon Polaris-Icon--applyColor"><span class="Polaris-VisuallyHidden"></span><svg viewBox="0 0 20 20" class="Polaris-Icon__Svg" focusable="false" aria-hidden="true">
                    <path d="M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm9.707 4.293-4.82-4.82A5.968 5.968 0 0 0 14 8 6 6 0 0 0 2 8a6 6 0 0 0 6 6 5.968 5.968 0 0 0 3.473-1.113l4.82 4.82a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414z"></path>
                  </svg></span></div><input id="PolarisComboboxTextField2" role="combobox" placeholder="Search customers" class="Polaris-TextField__Input" aria-labelledby="PolarisComboboxTextField2Label PolarisComboboxTextField2Prefix" aria-invalid="false" aria-autocomplete="list" aria-expanded="false" value="" tabindex="0" aria-controls="Polarispopover2" aria-owns="Polarispopover2">
              <div class="Polaris-TextField__Backdrop"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div id="PolarisPortalsContainer">
    <div data-portal-id="popover-Polarisportal1"></div>
  </div>
</div>

Multiple tags autocomplete

マーチャントが、テキスト入力で作成されたリストから複数のオプションを選択する際に使用します。

React
React
function MultiComboboxExample() {
  const deselectedOptions = useMemo(
    () => [
      {value: 'rustic', label: 'Rustic'},
      {value: 'antique', label: 'Antique'},
      {value: 'vinyl', label: 'Vinyl'},
      {value: 'vintage', label: 'Vintage'},
      {value: 'refurbished', label: 'Refurbished'},
    ],
    [],
  );

  const [selectedOptions, setSelectedOptions] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const [options, setOptions] = useState(deselectedOptions);

  const updateText = useCallback(
    (value) => {
      setInputValue(value);

      if (value === '') {
        setOptions(deselectedOptions);
        return;
      }

      const filterRegex = new RegExp(value, 'i');
      const resultOptions = deselectedOptions.filter((option) =>
        option.label.match(filterRegex),
      );
      setOptions(resultOptions);
    },
    [deselectedOptions],
  );

  const updateSelection = useCallback(
    (selected) => {
      if (selectedOptions.includes(selected)) {
        setSelectedOptions(
          selectedOptions.filter((option) => option !== selected),
        );
      } else {
        setSelectedOptions([...selectedOptions, selected]);
      }

      const matchedOption = options.find((option) => {
        return option.value.match(selected);
      });
      setInputValue((matchedOption && matchedOption.label) || '');
    },
    [options, selectedOptions],
  );

  const removeTag = useCallback(
    (tag) => () => {
      const options = [...selectedOptions];
      options.splice(options.indexOf(tag), 1);
      setSelectedOptions(options);
    },
    [selectedOptions],
  );

  const tagsMarkup = selectedOptions.map((option) => {
    let tagLabel = '';
    tagLabel = option.replace('_', ' ');
    tagLabel = titleCase(tagLabel);
    return (
      <Tag key={`option${option}`} onRemove={removeTag(option)}>
        {tagLabel}
      </Tag>
    );
  });

  const optionsMarkup =
    options.length > 0
      ? options.map((option) => {
          const {label, value} = option;

          return (
            <Listbox.Option
              key={`${value}`}
              value={value}
              selected={selectedOptions.includes(value)}
              accessibilityLabel={label}
            >
              {label}
            </Listbox.Option>
          );
        })
      : null;

  return (
    <div style={{height: '225px'}}>
      <Combobox
        allowMultiple
        activator={
          <Combobox.TextField
            prefix={<Icon source={SearchMinor} color="inkLighter" />}
            onChange={updateText}
            label="Search customers"
            labelHidden
            value={inputValue}
            placeholder="Search customers"
          />
        }
      >
        {optionsMarkup ? (
          <Listbox onSelect={updateSelection}>{optionsMarkup}</Listbox>
        ) : null}
      </Combobox>
      <TextContainer>
        <Stack>{tagsMarkup}</Stack>
      </TextContainer>
    </div>
  );

  function titleCase(string) {
    return string
      .toLowerCase()
      .split(' ')
      .map((word) => word.replace(word[0], word[0].toUpperCase()))
      .join('');
  }
}
HTML
HTML
<div>
  <div style="height: 225px;">
    <div>
      <div class="Polaris-Labelled--hidden">
        <div class="Polaris-Labelled__LabelWrapper">
          <div class="Polaris-Label"><label id="PolarisComboboxTextField4Label" for="PolarisComboboxTextField4" class="Polaris-Label__Text">Search customers</label></div>
        </div>
        <div class="Polaris-Connected">
          <div class="Polaris-Connected__Item Polaris-Connected__Item--primary">
            <div class="Polaris-TextField">
              <div class="Polaris-TextField__Prefix" id="PolarisComboboxTextField4Prefix"><span class="Polaris-Icon Polaris-Icon--applyColor"><span class="Polaris-VisuallyHidden"></span><svg viewBox="0 0 20 20" class="Polaris-Icon__Svg" focusable="false" aria-hidden="true">
                    <path d="M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm9.707 4.293-4.82-4.82A5.968 5.968 0 0 0 14 8 6 6 0 0 0 2 8a6 6 0 0 0 6 6 5.968 5.968 0 0 0 3.473-1.113l4.82 4.82a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414z"></path>
                  </svg></span></div><input id="PolarisComboboxTextField4" role="combobox" placeholder="Search customers" class="Polaris-TextField__Input" aria-labelledby="PolarisComboboxTextField4Label PolarisComboboxTextField4Prefix" aria-invalid="false" aria-autocomplete="list" aria-expanded="false" value="" tabindex="0" aria-controls="Polarispopover4" aria-owns="Polarispopover4">
              <div class="Polaris-TextField__Backdrop"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="Polaris-TextContainer">
      <div class="Polaris-Stack"></div>
    </div>
  </div>
  <div id="PolarisPortalsContainer">
    <div data-portal-id="popover-Polarisportal2"></div>
  </div>
</div>

Autocomplete with loading

オプションデータの処理中、マーチャントにロード状態を示すために使用します。

React
React
function LoadingAutocompleteExample() {
  const deselectedOptions = useMemo(
    () => [
      {value: 'rustic', label: 'Rustic'},
      {value: 'antique', label: 'Antique'},
      {value: 'vinyl', label: 'Vinyl'},
      {value: 'vintage', label: 'Vintage'},
      {value: 'refurbished', label: 'Refurbished'},
    ],
    [],
  );

  const [selectedOption, setSelectedOption] = useState();
  const [inputValue, setInputValue] = useState('');
  const [options, setOptions] = useState(deselectedOptions);
  const [loading, setLoading] = useState(false);

  const updateText = useCallback(
    (value) => {
      setInputValue(value);

      if (!loading) {
        setLoading(true);
      }

      setTimeout(() => {
        if (value === '') {
          setOptions(deselectedOptions);
          setLoading(false);
          return;
        }
        const filterRegex = new RegExp(value, 'i');
        const resultOptions = options.filter((option) =>
          option.label.match(filterRegex),
        );
        setOptions(resultOptions);
        setLoading(false);
      }, 300);
    },
    [deselectedOptions, loading, options],
  );

  const updateSelection = useCallback(
    (selected) => {
      const matchedOption = options.find((option) => {
        return option.value.match(selected);
      });

      setSelectedOption(selected);
      setInputValue((matchedOption && matchedOption.label) || '');
    },
    [options],
  );

  const optionsMarkup =
    options.length > 0
      ? options.map((option) => {
          const {label, value} = option;

          return (
            <Listbox.Option
              key={`${value}`}
              value={value}
              selected={selectedOption === value}
              accessibilityLabel={label}
            >
              {label}
            </Listbox.Option>
          );
        })
      : null;

  const loadingMarkup = loading ? <Listbox.Loading /> : null;

  const listboxMarkup =
    optionsMarkup || loadingMarkup ? (
      <Listbox onSelect={updateSelection}>
        {optionsMarkup && !loading ? optionsMarkup : null}
        {loadingMarkup}
      </Listbox>
    ) : null;

  return (
    <Combobox
      activator={
        <Combobox.TextField
          prefix={<Icon source={SearchMinor} color="inkLighter" />}
          onChange={updateText}
          label="Search customers"
          labelHidden
          value={inputValue}
          placeholder="Search customers"
        />
      }
    >
      {listboxMarkup}
    </Combobox>
  );
}
HTML
HTML
<div>
  <div>
    <div class="Polaris-Labelled--hidden">
      <div class="Polaris-Labelled__LabelWrapper">
        <div class="Polaris-Label"><label id="PolarisComboboxTextField10Label" for="PolarisComboboxTextField10" class="Polaris-Label__Text">Search customers</label></div>
      </div>
      <div class="Polaris-Connected">
        <div class="Polaris-Connected__Item Polaris-Connected__Item--primary">
          <div class="Polaris-TextField">
            <div class="Polaris-TextField__Prefix" id="PolarisComboboxTextField10Prefix"><span class="Polaris-Icon Polaris-Icon--applyColor"><span class="Polaris-VisuallyHidden"></span><svg viewBox="0 0 20 20" class="Polaris-Icon__Svg" focusable="false" aria-hidden="true">
                  <path d="M8 12a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm9.707 4.293-4.82-4.82A5.968 5.968 0 0 0 14 8 6 6 0 0 0 2 8a6 6 0 0 0 6 6 5.968 5.968 0 0 0 3.473-1.113l4.82 4.82a.997.997 0 0 0 1.414 0 .999.999 0 0 0 0-1.414z"></path>
                </svg></span></div><input id="PolarisComboboxTextField10" role="combobox" placeholder="Search customers" class="Polaris-TextField__Input" aria-labelledby="PolarisComboboxTextField10Label PolarisComboboxTextField10Prefix" aria-invalid="false" aria-autocomplete="list" aria-expanded="false" value="" tabindex="0" aria-controls="Polarispopover10" aria-owns="Polarispopover10">
            <div class="Polaris-TextField__Backdrop"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div id="PolarisPortalsContainer">
    <div data-portal-id="popover-Polarisportal5"></div>
  </div>
</div>

Props

activator(Required)
ReactElement

allowMultiple
boolean

children
ReactElement | null

preferredPosition
"above" | "below" | "mostSpace"

onScrolledToBottom
() => void

アクセシビリティ

構造

Comboboxコンポーネントは、ARIA 1.2 の combobox パターンに基づいています。これは、一行のTextFieldPopoverを組み合わせたものです。現在の実装では、Listboxコンポーネントが使用されることを想定しています。

Comboboxのポップオーバーは、デフォルトではテキストフィールドやその他のコントロールの下に表示されるので、マーチャントが発見して使用するのは簡単です。しかし、preferredPositionプロップで位置を変更することができます。

Comboboxの機能は、視覚、運動、および認知に障害のあるマーチャントにとって難しい場合があります。ベストプラクティスに基づいて構築されていても、一部の支援技術ではこれらの機能を使用することが困難な場合があります。マーチャントは、コンボボックスに頼らずに、常に検索、データ入力、またはその他のアクティビティを実行できる必要があります。

キーボードサポート

  • コンボボックスのテキスト入力に、tabキーでキーボードフォーカスを与える(逆方向のタブ操作の場合は、shift+tabキー)。

ベストプラクティス

Comboboxコンポーネントは以下のことに注意してください

  • どのような種類のオプションが利用できるのか、マーチャントにわかるように明確に表示すること。
  • ポップオーバーの中では使用しない
  • オプションのデータが入力されている間、マーチャントに読み込み中であることを示す。

コンテンツガイドライン

Comboboxの入力フィールドは、テキストフィールドのコンテンツガイドラインに従うべきです。

関連コンポーネント

Shopify アプリのご紹介

Shopify アプリである、「商品ページ発売予告アプリ | リテリア Coming Soon」は、商品ページを買えない状態のまま、発売日時の予告をすることができるアプリです。Shopify で Coming Soon 機能を実現することができます。

https://apps.shopify.com/shopify-application-314?locale=ja&from=daniel

Shopify アプリである、「らくらく日本語フォント設定|リテリア Font Picker」は、ノーコードで日本語フォントを使用できるアプリです。日本語フォントを導入することでブランドを演出することができます。

https://apps.shopify.com/font-picker-1?locale=ja&from=daniel

Discussion

ログインするとコメントできます