1
0
mirror of https://github.com/tabler/tabler.git synced 2026-08-07 03:42:27 +04:00
Files
tabler/shared/ui/Select.astro
codecalm f27c70ad38 Fix Prettier formatting regression and Astro type-check errors
`Render Modal as real markup` (#2742) reformatted ~140 files back to
tabs/semicolons, breaking the Prettier config added by the Astro
quality gates (#2749). Re-run prettier --write to restore it, and fix
the type errors it had been masking: HTMLElement casts and `this`
typing in ChangePasswordModalContent/ConfirmDeleteModalContent
scripts, plain-JS syntax in the inline-injected progress.astro script,
narrowed prop unions in Button/Check/Spinner, nullable icon svg casts
in Icons/Rating, and a duplicate firstLetters/invalid `placeholder`
attribute in Select.astro. Also fix shared/vitest.config.mts's stale
`astro/lib/**/*.test.ts` include glob left over from the shared source
restructure, which made `pnpm test` report zero test files.
2026-08-03 01:26:19 +02:00

157 lines
4.2 KiB
Plaintext

---
// Renders a <select id="select-{id}"> populated from @data/selects.json (+ people)
// and its TomSelect init script.
// TODO: optgroup / flag / label indicator branches (unused on the modals page).
import selects from '@data/selects.json';
import people from '@data/people.json';
import { firstLetters } from '@shared/lib/string-format';
import CaptureScript from '../components/CaptureScript.astro';
interface Props {
id?: string;
key?: string;
value?: string;
class?: string;
state?: string;
placeholder?: string;
indicator?: string;
multiple?: boolean;
values?: string[];
showSearch?: boolean;
}
const {
id: idProp,
key: keyProp,
value = '',
class: className,
state,
placeholder,
indicator,
multiple,
values,
showSearch,
} = Astro.props;
const id = idProp ?? keyProp;
const key = keyProp ?? 'people';
const data = (selects as Record<string, any>)[key] ?? {};
const selectClass = [
'form-select',
className,
state && `is-${state}`,
]
const isMultiple = Boolean(multiple || data.multiple);
interface Person {
id: string;
full_name: string;
photo?: string;
}
// Build the option list.
type Opt = { value: string; text: string; custom?: string; selected?: boolean };
type Group = { label: string; options: Opt[] };
let options: Opt[] = [];
let optgroups: Group[] = [];
if (values) {
options = values.map((v) => ({ value: v, text: v }));
} else if (data.data === 'people') {
options = (people as Person[]).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>`;
}
return { value: person.id, text: person.full_name, custom };
});
} 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 })),
}));
} else if (Array.isArray(data.options)) {
options = (data.options as string[]).map((option) => ({ value: option, text: option }));
} else if (data.options) {
// object of { key: { name, flag?, label?, 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>`;
return { value: optKey, text: v.name, custom, selected: Boolean(v.selected) };
});
}
const selectId = id ? `select-${id}` : undefined;
---
{
id && (
<select
class:list={selectClass}
data-placeholder={placeholder}
id={`select-${id}`}
value={value}
multiple={isMultiple ? '' : undefined}
>
{
optgroups.map((group) => (
<optgroup label={group.label}>
{group.options.map((option) => (
<option value={option.value}>{option.text}</option>
))}
</optgroup>
))
}
{
options.map((option) => (
<option
value={option.value}
data-custom-properties={option.custom}
selected={option.selected || undefined}
>
{option.text}
</option>
))
}
</select>
)
}
{
id && (
<CaptureScript>
<!-- BEGIN SELECT -->
<script define:vars={{ selectId, showSearch }}>
function initSelect() {
window.tabler_select ??= {};
const renderOption = (data, escape) => {
if (data.customProperties) {
return `<div><span class="dropdown-item-indicator">${data.customProperties}</span>${escape(data.text)}</div>`;
}
return `<div>${escape(data.text)}</div>`;
};
window.TomSelect &&
(window.tabler_select[selectId] = new TomSelect(document.getElementById(selectId), {
copyClassesToDropdown: false,
dropdownParent: 'body',
...(showSearch ? {} : { controlInput: '<input>' }),
render: {
item: renderOption,
option: renderOption,
},
}));
}
document.readyState !== 'loading' ? initSelect() : document.addEventListener('DOMContentLoaded', initSelect, { once: true });
</script>
<!-- END SELECT -->
</CaptureScript>
)
}