【Polaris和訳】Components/Forms②
この記事について
この記事は、Polaris/Components/Formsの記事を和訳したものです。
記事内で使用する画像は、公式ドキュメント内の画像を引用して使用させていただいております。
Shopify アプリのご紹介
Shopify アプリである、「商品ページ発売予告アプリ | リテリア Coming Soon」は、商品ページを買えない状態のまま、発売日時の予告をすることができるアプリです。Shopify で Coming Soon 機能を実現することができます。
Shopify アプリである、「らくらく日本語フォント設定|リテリア Font Picker」は、ノーコードで日本語フォントを使用できるアプリです。日本語フォントを導入することでブランドを演出することができます。
Forms②
Combobox
Combobox
コンポーネントは、Aria 1.2 の comboboxの仕様の一部を、テキストフィールドとリストボックスを含むポップオーバーに実装しています。Autocomplete
と同様に、Combobox
はマーチャントが大量の選択肢の中から素早く検索して選択できるようにします。
例
Basic autocomplete
マーチャントが選択肢の中から素早くテキスト入力を行うために使用します。
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
<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
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
<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
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
<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 パターンに基づいています。これは、一行のTextField
とPopover
を組み合わせたものです。現在の実装では、Listboxコンポーネントが使用されることを想定しています。
Combobox
のポップオーバーは、デフォルトではテキストフィールドやその他のコントロールの下に表示されるので、マーチャントが発見して使用するのは簡単です。しかし、preferredPosition
プロップで位置を変更することができます。
Combobox
の機能は、視覚、運動、および認知に障害のあるマーチャントにとって難しい場合があります。ベストプラクティスに基づいて構築されていても、一部の支援技術ではこれらの機能を使用することが困難な場合があります。マーチャントは、コンボボックスに頼らずに、常に検索、データ入力、またはその他のアクティビティを実行できる必要があります。
キーボードサポート
- コンボボックスのテキスト入力に、tabキーでキーボードフォーカスを与える(逆方向のタブ操作の場合は、shift+tabキー)。
ベストプラクティス
Combobox
コンポーネントは以下のことに注意してください
- どのような種類のオプションが利用できるのか、マーチャントにわかるように明確に表示すること。
- ポップオーバーの中では使用しない
- オプションのデータが入力されている間、マーチャントに読み込み中であることを示す。
コンテンツガイドライン
Combobox
の入力フィールドは、テキストフィールドのコンテンツガイドラインに従うべきです。
関連コンポーネント
- 選択肢が表示されない入力フィールドには、text field コンポーネントを使用します。
- 入力フィールドにリンクされていない、選択可能なオプションのリストには、list box コンポーネントを使用します。
-
Autocompleteは、
Combobox
やListbox
の代わりに便利なラッパーとして使用できます。
Shopify アプリのご紹介
Shopify アプリである、「商品ページ発売予告アプリ | リテリア Coming Soon」は、商品ページを買えない状態のまま、発売日時の予告をすることができるアプリです。Shopify で Coming Soon 機能を実現することができます。
Shopify アプリである、「らくらく日本語フォント設定|リテリア Font Picker」は、ノーコードで日本語フォントを使用できるアプリです。日本語フォントを導入することでブランドを演出することができます。
Discussion