Headless core
Mỗi component có trạng thái chỉ là lớp bind mỏng bên trên một state machine độc lập framework trong @thl-ui/core. Bạn có thể dùng trực tiếp chúng để tự dựng component — với bất kỳ framework nào, hoặc không cần framework.
Store
Mọi machine đều có một store quan sát được rất nhỏ:
ts
interface Store<T> {
get(): T;
set(next: T | ((prev: T) => T)): void;
subscribe(fn: (value: T, prev: T) => void): () => void;
}Kết nối với framework chỉ cần một dòng:
ts
// React
const state = useStore(machine.state); // exported by @thl-ui/react
// Vue
const state = useStore(machine.state); // exported by @thl-ui/vue → Ref<T>
// Angular
readonly state = storeSignal(machine.state); // exported by @thl-ui/angular → Signal<T>Các machine
| Factory | Điểm nổi bật |
|---|---|
createTable({ data, columns, pageSize, rowKey, manual }) | sort, search, filters, paging, selection, inline edit, server mode |
createSelect({ options, multiple, searchable, creatable }) | keyboard navigation, groups, creatable tags |
createTabs({ items, active }) | roving tabindex, disabled skip |
createSteps({ count, current, linear }) | status per step |
createCarousel({ count, interval, loop }) | autoplay, pause |
createDisclosure() / createAccordion() | collapse, panels |
createSidebar({ breakpoint }) | mini / mobile / user-mini state, resize aware |
createToastManager({ duration, max }) | positions, sticky, max queue |
createUpload({ accept, maxSize, request }) | validation (i18n), progress, previews |
createIdleTimer({ warnAfter, expireAfter }) | activity events |
paginate({ page, total, pageSize, maxPages }) | page items with ellipsis |
monthGrid(), weekdayNames(), presetRanges() | Intl-aware calendars |
rules.*, validate(), validateAll() | returns i18n keys |
Ví dụ: bảng tự dựng bằng TypeScript thuần
ts
import { createTable, createThl } from '@thl-ui/core';
const thl = createThl({ locale: 'vi' });
const table = createTable({
data: employees,
columns: [{ key: 'name', sortable: true }, { key: 'salary', sortable: true }],
pageSize: 5,
rowKey: 'id',
});
table.state.subscribe(render);
table.toggleSort('salary');
table.setSearch('tokyo');
function render() {
const { rows, total, page, pageCount } = table.view();
footer.textContent = thl.t('pagination.showing', { from: (page - 1) * 5 + 1, to: Math.min(page * 5, total), total });
// …render rows.map(r => r.row)
}Luật validation
ts
import { rules, validate, validateAll } from '@thl-ui/core';
validate('', [rules.required()]); // { key: 'validation.required' }
validate('ab', [rules.minLength(3)]); // { key: 'validation.minLength', params: { min: 3 } }
validateAll({ pw: 'x', confirm: 'y' }, { confirm: [rules.match('pw')] });Có sẵn: required, email, minLength, maxLength, min, max, number, pattern(re), match(field). Một rule chỉ là (value, allValues) => { key, params } | null, nên viết rule riêng rất đơn giản — và thông báo cũng được dịch như rule có sẵn.
Tiện ích
bem(block, prefix) · cx(...classes) · uid() · focusTrap(el) · scrollLock() · clickOutside(els, fn) · computePosition(anchor, float, placement) · breakpoints · skins · palette.