mirror of
https://github.com/tabler/tabler.git
synced 2026-08-07 03:42:27 +04:00
f27c70ad38
`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.
157 lines
4.2 KiB
Plaintext
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>
|
|
)
|
|
}
|