Files
tabler/shared/components/demo/DataSelect.astro
T

94 lines
3.8 KiB
Plaintext

---
// Demo select fed from @data/selects.json (+ people); builds the option list and delegates rendering to ui/Select.
import selects from '@data/selects.json';
import { people } from '@shared/lib/people'
import type { FormValidationState } from '@shared/lib/tokens';
import { firstLetters } from '@shared/lib/string-format';
import Select from '@ui/Select.astro';
import type { SelectOption, SelectOptgroup } from '@ui/Select.astro';
interface Props {
id?: string;
/** key into @data/selects.json (default 'people') */
key?: string;
class?: string;
state?: FormValidationState | undefined;
placeholder?: string;
/** option decoration: 'avatar' | 'flag' | 'label' | 'signal' */
indicator?: string;
/** plain value list — overrides the selects.json lookup */
values?: string[];
}
const {
id: idProp,
key: keyProp,
class: className,
state,
placeholder,
indicator,
values,
} = Astro.props;
const id = idProp ?? keyProp;
if (!id) {
throw new Error('DataSelect: pass an `id` or a `key` — the select needs one for its id');
}
const key = keyProp ?? 'people';
let data = (selects as Record<string, any>)[key];
if (!data && !values) {
throw new Error(`DataSelect: unknown key "${key}" — not in @data/selects.json`);
}
// `values` alone needs no selects.json entry.
data ??= {};
const isMultiple = Boolean(data.multiple);
// `value` in selects.json marks the preselected option(s): a string/number or an array of them.
const preselected = new Set<string>(
(Array.isArray(data.value) ? data.value : data.value === undefined ? [] : [data.value]).map(String),
);
const isSelected = (value: string) => preselected.has(value);
// Same markup as ui/Signal.astro, as a string: TomSelect renders the indicator from an attribute.
const signalHtml = (value: number, color?: string) =>
`<span class="signal${color ? ` signal-${color}` : ''}" aria-hidden="true">${[1, 2, 3].map((bar) => `<span class="signal-bar${bar <= value ? ' active' : ''}"></span>`).join('')}</span>`;
// Build the option list.
let options: SelectOption[] = [];
let optgroups: SelectOptgroup[] = [];
if (values) {
options = values.map((v) => ({ value: v, text: v }));
} else if (data.data === 'people') {
options = people.slice(0, 20).map((person) => {
let custom: string | undefined;
if (indicator === 'avatar') {
custom = person.photo
? `<span class="avatar avatar-xs" style="background-image: url(./${person.photo})"></span>`
: `<span class="avatar avatar-xs">${firstLetters(person.full_name ?? '')}</span>`;
}
const value = String(person.id);
return { value, text: person.full_name ?? '', custom, selected: isSelected(value) };
});
} else if (data.data === 'optgroup') {
optgroups = (data.options as { title: string; options: string[] }[]).map((group) => ({
label: group.title,
options: group.options.map((option) => ({ value: option, text: option, selected: isSelected(option) })),
}));
} else if (Array.isArray(data.options)) {
options = (data.options as string[]).map((option) => ({ value: option, text: option, selected: isSelected(option) }));
} else if (data.options) {
// object of { key: { name, flag?, label?, signal?, color?, selected? } } — with optional indicator
options = Object.entries(data.options as Record<string, any>).map(([optKey, v]) => {
let custom: string | undefined;
if (indicator === 'flag') custom = `<span class="flag flag-xs flag-country-${v.flag}"></span>`;
else if (indicator === 'label') custom = `<span class="badge bg-primary-lt">${v.label}</span>`;
else if (indicator === 'signal') custom = signalHtml(v.signal, v.color);
return { value: optKey, text: v.name, custom, selected: Boolean(v.selected) || isSelected(optKey) };
});
}
---
<Select id={id} class={className} state={state} placeholder={placeholder} multiple={isMultiple} options={options} optgroups={optgroups} />