@lucasvu/scope-ui
v0.1.7
Published
Bộ UI package dùng chung cho các app/micro-frontend trong workspace.
Readme
@siraya/scope-ui
Bộ UI package dùng chung cho các app/micro-frontend trong workspace.
Public surface chính:
@siraya/scope-ui: React component surface@siraya/scope-ui/core: theme/runtime/contracts và AI metadata@siraya/scope-ui/styles.css: base stylesheet@siraya/scope-ui/themes/*: bundled theme layersscope-ui-init: CLI bootstrapAGENTS.md, theme override và layout preset
Cách dùng nhanh
- Import stylesheet ở app entry:
import '@siraya/scope-ui/styles.css'
import '@siraya/scope-ui/themes/theme-ui-new-main-fe.css'- Nếu theme layer dùng
data-client-theme, khởi tạo ở app entry:
import {
applyDocumentClientTheme,
initializeDocumentColorMode,
} from '@siraya/scope-ui/core'
initializeDocumentColorMode()
applyDocumentClientTheme('neo-slate')- Dùng component từ root package:
import { Button, Card, CardHeader, CardTitle, CardContent, DataTable, Field, Input } from '@siraya/scope-ui'
export function Example() {
return (
<Card>
<CardHeader>
<CardTitle>Form đăng ký</CardTitle>
</CardHeader>
<CardContent className="ui-grid ui-grid--two">
<Field label="Email" required>
<Input type="email" placeholder="[email protected]" />
</Field>
<Field label="Gói sử dụng">
<DataTable
data={[{ name: 'Starter', price: '$19' }]}
rowKey="name"
columns={[
{ key: 'name', title: 'Tên', sortable: true },
{ key: 'price', title: 'Giá' },
]}
/>
</Field>
</CardContent>
<Button block>Tiếp tục</Button>
</Card>
)
}Thành phần có sẵn
- Nút:
Button(variantdefault | secondary | ghost | outline | destructive | link | confirm | create, sizesm | md | lg | icon, hỗ trợblock) - Badge:
Badge(solid, outline, success, warning) - Thẻ:
Card,CardHeader,CardTitle,CardDescription,CardContent,CardFooter,CardAction - Form:
Form,Field,Item,Label,Control,Description,Message,Input,Textarea,Select,SearchableSelect,Combobox,AsyncCombobox,MultiSelect - Numeric:
NumericInput(hỗ trợ integer/decimal, parse an toàn, format/clamp khi blur, dùng cho price/amount) - Tabs:
Tabsvới mảngitems{ value, label, content, badge? } - Thông báo:
Alert(tone info/success/warning/danger) - Số liệu:
Stat(label, value, delta, trend up/down/flat) - Bảng:
DataTable<T>vớicolumnsvàrendertuỳ biến - Tooltip:
Tooltip,OverflowTooltip,LineClampTooltip(hiển thị tooltip khi bị cắt theo dòng) - Lưới tiện dụng: class
ui-grid,ui-grid--two,ui-grid--threeđể xếp block nhanh - Legacy compatibility:
MainFe.*và các type aliasMainFe*Props
NumericInput
NumericInput dùng cho các ô số cần giữ trải nghiệm nhập liệu mượt (không nhảy caret, chấp nhận trạng thái tạm như . hoặc 12.), đồng thời parse/format có kiểm soát.
Public API
type NumericInputProps = {
value?: string | number;
defaultValue?: string | number;
onValueChange?: (raw: string) => void;
onNumberChange?: (num: number | null) => void;
mode?: 'integer' | 'decimal'; // default: 'decimal'
decimalScale?: number;
maxDecimalScale?: number;
min?: number;
max?: number;
allowNegative?: boolean; // default: false
decimalSeparator?: '.' | ',' | 'auto'; // default: 'auto'
formatOnBlur?: 'none' | 'trim' | 'fixed'; // default: 'trim'
clampOnBlur?: boolean; // default: false
disabled?: boolean;
readOnly?: boolean;
name?: string;
onBlur?: (e) => void;
onFocus?: (e) => void;
// + các input props khác: className, placeholder, ...
};Rule chính
- Luôn giữ
rawValuetheo đúng ký tự user nhập. onValueChangeluôn bắn khi user gõ.onNumberChangetrảnumberkhi parse được, ngược lại trảnull.- Không tự chèn dấu phân tách hàng nghìn trong lúc gõ.
- Có hỗ trợ paste chuỗi như
1,234.50hoặc1.234,50(decimalSeparator="auto"). - Với
maxDecimalScale, input sẽ chặn vượt số chữ số phần thập phân.
Ví dụ dùng
import { NumericInput } from '@siraya/scope-ui';
// 1) Integer qty
<NumericInput
mode="integer"
min={0}
value={qty}
onValueChange={setQty}
/>;
// 2) Money
<NumericInput
mode="decimal"
decimalScale={2}
maxDecimalScale={2}
formatOnBlur="fixed"
value={price}
onValueChange={setPrice}
/>;
// 3) Percent
<NumericInput
mode="decimal"
decimalScale={2}
maxDecimalScale={2}
min={0}
max={100}
clampOnBlur
value={percent}
onValueChange={setPercent}
/>;DataTable
import type { DataTableColumn, DataTableSortState } from '@siraya/scope-ui'Tối thiểu
data: mảng recordcolumns: cấu hình cộtrowKey: key của record (string/number) hoặc hàm(record) => key
Props chính
type DataTableProps<T> = {
columns: DataTableColumn<T>[];
data: T[];
rowKey: keyof T | ((record: T) => string | number);
loading?: boolean;
emptyText?: ReactNode;
pagination?: {
page: number;
pageSize: number;
total: number;
onChange: (page: number) => void;
pageSizeOptions?: number[];
onPageSizeChange?: (pageSize: number) => void;
};
sort?: DataTableSortState | null;
onSortChange?: (sort: DataTableSortState | null) => void;
sortMode?: 'client' | 'server';
onRowClick?: (record: T) => void;
rowSelection?: {
selectedRowKeys: Array<string | number>;
onChange: (keys: Array<string | number>) => void;
};
renderActions?: (record: T) => ReactNode;
className?: string;
};Cấu hình cột
type DataTableColumn<T> = {
key: string; // khóa duy nhất của cột
title: ReactNode; // tiêu đề hiển thị ở header
dataIndex?: keyof T; // map dữ liệu (mặc định lấy theo key)
width?: number | string; // độ rộng ưu tiên (vd: 180, '180px', '20%')
render?: (value, record, index) => ReactNode; // custom cell
sortable?: boolean; // bật/tắt sort cho cột (ưu tiên cao nhất)
sorter?: (a, b) => number; // so sánh asc/desc
sortValue?: (record) => string | number | Date | null;
};Độ rộng cột (width)
- Nếu cần giữ độ rộng cố định cho một cột, đặt
widthtrong cấu hình cột. - Cột có
widthsẽ ưu tiên theo giá trị này; các cột còn lại vẫn tự đo theo nội dung.
const columns: DataTableColumn<User>[] = [
{ key: 'name', title: 'Name', dataIndex: 'name', width: 180 },
{ key: 'email', title: 'Email', dataIndex: 'email' },
];Bật/tắt nhanh
- Sort: set
sortable: true(hoặcsorter/sortValue); muốn tắt hẳn thìsortable: false. - Checkbox: truyền
rowSelectionđể hiện; bỏrowSelectionđể ẩn. - Actions column: truyền
renderActionsđể hiện; bỏrenderActionsđể ẩn. - Pagination: truyền
paginationđể hiện; bỏpaginationđể ẩn. - Page size dropdown: truyền
pagination.onPageSizeChange(kèmpageSizeOptionsnếu cần).
Sort (client)
Chỉ cột có sortable, sorter, hoặc sortValue mới hiện icon sort. Nếu muốn tắt hẳn, đặt sortable: false. Thứ tự click: asc -> desc -> bỏ sort.
const columns: DataTableColumn<User>[] = [
{ key: 'name', title: 'Name', dataIndex: 'name', sortable: true },
{
key: 'createdAt',
title: 'Created',
dataIndex: 'createdAt',
sortValue: (row) => new Date(row.createdAt),
},
{
key: 'status',
title: 'Status',
sorter: (a, b) => a.status.localeCompare(b.status),
},
];Sort (server)
Khi gọi API để sort/paginate, dùng sortMode="server" để DataTable không tự sort dữ liệu. onSortChange trả về { key, direction } | null, bạn tự gọi API và set data mới.
const [sort, setSort] = useState<DataTableSortState | null>(null);
<DataTable
data={rows}
columns={columns}
rowKey="id"
sort={sort}
sortMode="server"
onSortChange={setSort}
/>;Checkbox chọn dòng
Checkbox chỉ hiện khi truyền rowSelection. Không truyền thì sẽ ẩn.
<DataTable
data={rows}
columns={columns}
rowKey="id"
rowSelection={{
selectedRowKeys,
onChange: setSelectedRowKeys,
}}
/>;Pagination + chọn page size
Nếu truyền onPageSizeChange, DataTable sẽ hiện dropdown chọn số dòng mỗi trang.
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
<DataTable
data={rows}
columns={columns}
rowKey="id"
pagination={{
page,
pageSize,
total,
onChange: setPage,
pageSizeOptions: [10, 20, 50, 100],
onPageSizeChange: (size) => {
setPageSize(size);
setPage(1);
},
}}
/>;Actions + click row
<DataTable
data={rows}
columns={columns}
rowKey="id"
onRowClick={(row) => console.log(row)}
renderActions={(row) => <ActionMenu row={row} />}
/>;Loading + empty
<DataTable
data={rows}
columns={columns}
rowKey="id"
loading={isLoading}
emptyText="No data"
/>;Sticky header khi scroll
Nếu muốn header dính khi scroll trang, không đặt overflow ở container cha. Nếu muốn scroll trong bảng, đặt max-height + overflow-auto cho DataTable.
<DataTable className="max-h-[60vh] overflow-auto" ... />Scroll ngang trên màn nhỏ
DataTable tự hỗ trợ scroll ngang khi nội dung vượt chiều rộng (đặc biệt trên màn nhỏ). Không cần cấu hình thêm.
Lưu ý
rowKeyphải ổn định và unique, đặc biệt khi dùngrowSelection.- Nếu truyền
sort(controlled), bạn cần cập nhật lại state trongonSortChange. sortMode="server"chỉ đổi trạng thái UI, dữ liệu phải tự fetch/sort từ API.pagination.onChangedùng page bắt đầu từ 1.- Dropdown page size chỉ hiện khi có
pagination.onPageSizeChange.
LineClampTooltip
Dùng khi cần hiển thị tối đa N dòng, nếu text bị cắt sẽ hiện tooltip đầy đủ khi hover.
import { LineClampTooltip } from '@siraya/scope-ui'
<LineClampTooltip
text={record.apiEndpoint}
lineClamp={3}
className="w-[320px]"
side="top"
wrap
portal
/>Tuỳ chỉnh theme
- Dùng
npx scope-ui-init --list-themesđể xem preset được support. - Dùng
npx scope-ui-init --theme sunsetđể generateAGENTS.md,src/styles/ui-theme.cssvàsrc/layout-presets/workspace-admin-v1.tsở repo consumer. - Nếu cần theme runtime hoặc metadata cho AI/dev tool, import từ
@siraya/scope-ui/core. - Các lớp
ui-*đã được namespaced để tránh va chạm với CSS hiện tại.
Gợi ý tích hợp
- Import
@siraya/scope-ui/styles.cssở host để các remote có thể tái sử dụng cùng theme. - Nếu muốn kết hợp với Tailwind/shadcn gốc, giữ nguyên class
ui-*và thêmcontenttrỏ tớipackages/ui/src/**/*trongtailwind.config.
