Примитивы
14

Select

Селект на настоящем листбоксе: группы, описания и опции, которые вы рисуете сами, — нативный <select> не умеет ничего из этого, поэтому в библиотеке именно такой. Триггер — <button role="combobox">, и фокус его не покидает: курсор по списку виртуальный, поэтому нечего запирать и нечего возвращать.

чистая разметка — библиотеки на странице нет
отрендерил aura-react
отрендерил aura-react
отрендерил aura-react
отрендерил aura-react
  • Apple
  • CherryIn season

Код

index.html
<div class="aura-field-group">
  <label class="aura-label" id="s-label" for="s">Fruit</label>
  <button id="s" type="button" class="aura-combobox" role="combobox" aria-haspopup="listbox"
          aria-expanded="true" aria-controls="s-listbox" aria-labelledby="s-label s"
          aria-activedescendant="s-opt-0" data-state="open">
    <span class="aura-combobox__value">Apple</span>
    <span class="aura-combobox__chevron" aria-hidden="true"></span>
  </button>
</div>
<!-- Здесь нет data-side. Этот атрибут пишет place(), и именно .aura-popup[data-side] делает панель
     position: fixed — статически привязываться не к чему, поэтому фиксированная панель просто
     схлопнулась бы в угол окна. Без него панель остаётся в потоке и показывает, как выглядит
     список; в настоящем Select place() выдаёт и атрибут, и отступы в том же кадре, в котором панель
     монтируется. -->
<div class="aura-popup" data-state="open">
  <ul id="s-listbox" class="aura-listbox" role="listbox" aria-labelledby="s-label">
    <li id="s-opt-0" class="aura-listbox__option" role="option" aria-selected="true" data-active="true">
      <span class="aura-listbox__label">Apple</span>
    </li>
    <li id="s-opt-1" class="aura-listbox__option" role="option" aria-selected="false" data-active="false">
      <span class="aura-listbox__label">Cherry<span class="aura-listbox__description">In season</span></span>
    </li>
  </ul>
</div>
Demo.tsx
import { Select, Stack } from '@ilomee/aura-react';

const OPTIONS = [
  { value: 'apple', label: 'Apple', group: 'Pome' },
  { value: 'pear', label: 'Pear', group: 'Pome' },
  { value: 'cherry', label: 'Cherry', description: 'In season', group: 'Stone' },
  { value: 'durian', label: 'Durian', description: 'Out of stock', group: 'Stone', disabled: true },
];

/**
 * `minWidth` — так же, как в демо Field: панель превью сжимается по содержимому, и комбобокс
 * шириной со свой плейсхолдер менял бы размер, стоит выбрать что-то длиннее. В форме об этом
 * заботится `inline-size: 100%` самого контрола, но у превью нет формы, которую можно заполнить.
 */
export default function Demo() {
  return (
    <Stack style={{ minWidth: 260 }}>
      <Select label="Fruit" placeholder="Pick one" options={OPTIONS} />
    </Stack>
  );
}
Demo.vue
<script setup lang="ts">
import { Select } from '@ilomee/aura-vue';
const fruit = ref('');
</script>

<template>
  <Select v-model="fruit" label="Fruit" placeholder="Pick one" :options="options" />
</template>
Demo.svelte
<script lang="ts">
  import { Select } from '@ilomee/aura-svelte';
  let fruit = $state('');
</script>

<Select bind:value={fruit} label="Fruit" placeholder="Pick one" {options} />

Используется как aura-select.

demo.component.ts
import { AuraSelect } from '@ilomee/aura-angular';

@Component({
  standalone: true,
  imports: [AuraSelect],
  template: `<aura-select label="Fruit" placeholder="Pick one" [options]="options" [(value)]="fruit" />`,
})
export class DemoComponent {
  fruit = '';
}

Пропсы

У чистой разметки нет пропсов — классы ниже и есть весь API. Четыре пакета не делают ничего сверх того, что проставляют вам те же самые классы.
ИмяТипПо умолчаниюПримечания
optionsSelectOption[] | nullNullable — рисует пустой список, а не бросает. group и description — строки, icon — ReactNode.
valueдвустороннийstring
defaultValuestring''
onValueChangeсобытие(value: string) => void
placeholderstring
hintstringПостоянная подсказка под контролом, показывается всегда.
errorstringЧто не так прямо сейчас. Ставит контролу aria-invalid и добавляет сообщение в его aria-describedby — после подсказки. Без живого региона: role="alert" объявляет себя при монтировании, поэтому форма с тремя ошибками, отрисованная на сервере, перебила бы читателя трижды ещё до первого поля.
emptyLabelstring'No results'
openдвустороннийbooleanНеконтролируемо — через defaultOpen.
onOpenChangeсобытие(open: boolean) => void
renderOption(option, state) => ReactNodeЗаменяет содержимое строки — но не сам <li>. Роль, id, aria-selected и класс остаются за Aura, поэтому хук не может сломать доступность. state — это { index, active, selected, disabled }.
ИмяТипПо умолчаниюПримечания
optionsSelectOption[] | null
modelValueдвустороннийstring
update:modelValueсобытие(value: string) => void
placeholderstring
hintstringПостоянная подсказка под контролом, показывается всегда.
errorstringЧто не так прямо сейчас. Ставит контролу aria-invalid и добавляет сообщение в его aria-describedby — после подсказки. Без живого региона: role="alert" объявляет себя при монтировании, поэтому форма с тремя ошибками, отрисованная на сервере, перебила бы читателя трижды ещё до первого поля.
emptyLabelstring'No results'
openдвустороннийboolean
optionслотslot({ option, index, active, selected, disabled })Люк во Vue — scoped slot, а не проп renderOption, как в React: полезная нагрузка та же, идиома у каждого своя. Он заполняет строку опции; сам <li> остаётся за Aura.
ИмяТипПо умолчаниюПримечания
optionsSelectOption[] | null
valueдвустороннийstringBindable — поэтому defaultValue нет: один проп покрывает оба режима там, где React требует пары.
onchangeсобытие(value: string) => void
placeholderstring
hintstringПостоянная подсказка под контролом, показывается всегда.
errorstringЧто не так прямо сейчас. Ставит контролу aria-invalid и добавляет сообщение в его aria-describedby — после подсказки. Без живого региона: role="alert" объявляет себя при монтировании, поэтому форма с тремя ошибками, отрисованная на сервере, перебила бы читателя трижды ещё до первого поля.
emptyLabelstring'No results'
openдвустороннийboolean
optionслотSnippet<[SelectOption, AuraOptionRenderState]>
ИмяТипПо умолчаниюПримечания
optionsSelectOption[] | null
valueдвустороннийstring
valueChangeсобытие(value: string) => void
placeholderstring
hintstringПостоянная подсказка под контролом, показывается всегда.
errorstringЧто не так прямо сейчас. Ставит контролу aria-invalid и добавляет сообщение в его aria-describedby — после подсказки. Без живого региона: role="alert" объявляет себя при монтировании, поэтому форма с тремя ошибками, отрисованная на сервере, перебила бы читателя трижды ещё до первого поля.
emptyLabelstring'No results'
openдвустороннийboolean
optionTemplateTemplateRefСвязанный TemplateRef, а не запрос через <ng-content> — идиома, которую уже использует AuraDataTable: шаблон объявляется там, где его область видимости. Контекст: let-option let-state="state". Он заполняет строку опции; сам <li> остаётся за Aura.

Какие классы отдаёт

Все фреймворки отдают одни и те же классы — именно поэтому четыре библиотеки выглядят одинаково. Их можно использовать напрямую, вообще без библиотеки компонентов.

КлассРольПримечания
.aura-comboboxбазаОбязательный. Без него модификатор не работает.
.aura-combobox__valueэлемент
.aura-combobox__chevronэлемент
.aura-popupбазаОбязательный. Без него модификатор не работает.
.aura-listboxбазаОбязательный. Без него модификатор не работает.
.aura-listbox__optionэлемент

Токены и правила

components.css
рамка
var(--field-border) · var(--field-radius) — min-block-size, not height
слой панели
var(--z-dropdown) — under a modal, on purpose
зазор до якоря
var(--popover-offset)
панель
var(--shadow-3) · max-block-size: min(320px, 60vh)