ITADN
tailor-platform/app-shell

版本发布 5

@tailor-platform/app-shell@1.0.2
? · 2026-04-27

### Patch Changes - 27bb5df: Remove `richColors` prop from the `Toaster` component. Toast notifications will no longer use color-coded styling for success, error, warning, and info variants. - Updated dependencies [ee7f7c7] - @tailor-platform/app-shell-vite-plugin@0.2.2

@tailor-platform/app-shell@1.0.0
? · 2026-04-24

### Major Changes - 3f31e8a: Add `DataTable` compound component. Also introduces `@tailor-platform/app-shell-sdk-plugin` — a companion SDK plugin that generates `tableMetadata` from TailorDB type definitions for use with `createColumnHelper`. ## DataTable - Sortable columns (click header to cycle Asc → Desc → off) - Filter chips with per-type editors (string, number, date, enum, boolean, uuid) - Cursor-based pagination with optional total-aware First/Last navigation - Per-row action menu (kebab menu) - Multi-row checkbox selection (current page only) - Loading, error, and empty states - Metadata-driven column inference via `createColumnHelper` and `inferColumns` ### Example with urql (Relay Cursor Connection GraphQL API) ```tsx import { gql, useQuery } from "urql"; import { DataTable, useDataTable, useCollectionVariables, createColumnHelper, } from "@tailor-platform/app-shell"; const LIST_JOURNALS = gql` query ListJournals( $after: String $before: String $first: Int $last: Int $order: [JournalOrderInput] $query: JournalQueryInput ) { journals( after: $after before: $before first: $first last: $last order: $order query: $query ) { edges { node { id contents authorID } } pageInfo { endCursor hasNextPage hasPreviousPage startCursor } total } } `; const { column } = createColumnHelper<{ id: string; contents: string; authorID: string; }>(); const columns = [ column({ field: "id", label: "ID", type: "uuid" }), column({ field: "authorID", label: "Author", type: "string" }), column({ field: "contents", label: "Contents", type: "string" }), ]; function JournalsPage() { // variables: { query, order, pagination } — maps directly to GraphQL variables. // control: holds filter/sort/pagination state and methods (addFilter, setSort, nextPage, …). // Passing it to useDataTable wires UI interactions (column clicks, filter chips, // pagination buttons) to state updates, which re-derive variables and re-run the query. const { variables, control } = useCollectionVariables({ params: { pageSize: 20 }, }); // pagination holds { first, after? } (forward) or { last, before? } (backward). const [result] = useQuery({ query: LIST_JOURNALS, variables: { first: variables.pagination.first, after: variables.pagination.after, last: variables.pagination.last, before: variables.pagination.before, query: variables.query, order: variables.order, }, }); const table = useDataTable({ columns, data: result.data ? { rows: result.data.journals.edges.map((e) => e.node), pageInfo: result.data.journals.pageInfo, total: result.data.journals.total, } : undefined, loading: result.fetching, control, }); // DataTable.Root + DataTable.Table are the only required sub-components. // DataTable.Toolbar / DataTable.Filters / DataTable.Pagination are opt-in sensible defaults. // If they don't fit, use useDataTableContext() to build your own sub-components — // it exposes the full DataTable state (rows, columns, sort, pagination, selection, etc.) // from the nearest DataTable.Root. return ( <DataTable.Root value={table}> <DataTable.Toolbar> <DataTable.Filters /> </DataTable.Toolbar> <DataTable.Table /> <DataTable.Footer> <DataTable.Pagination pageSizeOptions={[10, 20, 50]} /> </DataTable.Footer> </DataTable.Root> ); } ``` `useCollectionVariables` is intentionally decoupled from DataTable and any other UI component. The hook owns only the query state and exposes plain `variables` — how those variables are rendered is entirely up to the consumer. This means future collection-based views such as Kanban boards can adopt the same hook without modification, and any custom component you build can use a GraphQL cursor-based API as its backend with minimal wiring. ## sdk-plugin (`@tailor-platform/app-shell-sdk-plugin`) `tableMetadata` is what bridges your TailorDB schema to the DataTable. It tells `inferColumns` how to render and filter each field — for example, which fields get a date picker, which get an enum dropdown (and with what options), and which are numeric. Without it, you would need to declare all of this manually per column. The metadata is generated at SDK code-gen time from your TailorDB type definitions. Register the plugin in `tailor.config.ts` and run `tailor-sdk generate`: ```ts import { definePlugins } from "@tailor-platform/sdk"; import { appShellPlugin } from "@tailor-platform/app-shell-sdk-plugin"; export const plugins = definePlugins( appShellPlugin({ dataTable: { metadataOutputPath: "src/generated/app-shell-datatable.generated.ts", }, }) ); ``` The generated file exports `tableMetadata`, `tableNames`, and `TableName`. Pass `tableMetadata` to `inferColumns` to get type-safe column definitions with filter editors automatically configured: ```ts import { tableMetadata } from "@/generated/app-shell-datatable.generated"; import { createColumnHelper } from "@tailor-platform/app-shell"; const { column, inferColumns } = createColumnHelper<Order>(); const infer = inferColumns(tableMetadata.order); const columns = [ column(infer("title")), // string column → text filter column(infer("status")), // enum column → dropdown filter with generated values column(infer("createdAt")), // datetime column → date picker filter ]; ``` ### Typed query variables with `tableMetadata` When using typed GraphQL documents (e.g. `TypedDocumentNode` from `@graphql-typed-document-node/core` or codegen-generated types), urql and other GraphQL clients enforce strict types on the `variables` object passed to `useQuery`. In that case, passing `tableMetadata` to `useCollectionVariables` is **required** — it is what narrows `variables.query` and `variables.order` from `unknown` to the precise types expected by the generated document. Without `tableMetadata`, `variables.query` is typed as `Record<string, Record<string, unknown>> | undefined`, which will not satisfy the stricter generated variable types and will cause a TypeScript error at the `useQuery` call site. Use `sdk-plugin` to generate `tableMetadata` and pass it to `useCollectionVariables`: ```ts const { variables, control } = useCollectionVariables({ tableMetadata: tableMetadata.order, // required for typed documents params: { pageSize: 20 }, }); // variables.query is now BuildQueryVariables<typeof tableMetadata.order> // variables.order is now { field: OrderableFieldName; direction: "Asc" | "Desc" }[] // Both satisfy the types generated by GraphQL codegen. const [result] = useQuery({ query: LIST_ORDERS, // TypedDocumentNode — variables are fully type-checked variables: { ...variables.pagination, query: variables.query, order: variables.order, }, }); ```

@tailor-platform/app-shell-vite-plugin@0.2.1
? · 2026-04-09

### Patch Changes - 01984ee: Update README to document the `entrypoint` option, `[...slug]` catch-all path conversion, and the current `AppShell.WithPages` implementation.

@tailor-platform/app-shell@0.33.0
? · 2026-04-03

### Minor Changes - 6f5c23f: **Breaking:** `AsyncFetcherFn` now receives `string | null` instead of `string` as the `query` parameter. The fetcher is called with `null` when the user has not typed anything (e.g. the dropdown was just opened or the input was cleared). Return initial/default items for `null`, or return an empty array to show nothing until the user starts typing. `useAsync` also now returns an `onOpenChange` handler that triggers `fetcher(null)` on the first open, so `Combobox.Async` shows initial items immediately when the dropdown opens. ```tsx // Before const fetcher = async (query: string, { signal }) => { ... }; // After const fetcher = async (query: string | null, { signal }) => { const res = await fetch(`/api/items?q=${query ?? ""}`, { signal }); return res.json(); }; ``` - 7917328: Add `useOverrideBreadcrumb` hook for dynamically overriding breadcrumb titles from within page components. This is useful for displaying data-driven titles (e.g., record names) instead of static route-based titles. With `defineResource`: ```tsx import { useOverrideBreadcrumb } from "@tailor-platform/app-shell"; defineResource({ path: ":id", component: () => { const { data } = useQuery(GET_ORDER, { variables: { id } }); // Update breadcrumb with the order name useOverrideBreadcrumb(data?.order?.name); return <OrderDetail />; }, }); ``` With file-based routing (`pages/orders/[id]/page.tsx`): ```tsx import { useOverrideBreadcrumb, useParams } from "@tailor-platform/app-shell"; const OrderDetailPage = () => { const { id } = useParams(); const { data } = useQuery(GET_ORDER, { variables: { id } }); // Update breadcrumb with the order name useOverrideBreadcrumb(data?.order?.name); return <div>...</div>; }; export default OrderDetailPage; ``` - 58f8024: Fix guards defined via `appShellPageProps` being silently ignored in file-based routing. Guards now correctly produce route loaders for both root and non-root pages. ### Patch Changes - 1cad50d: Fix portal-based components (`Menu`, `Select`, `Combobox`, `Autocomplete`, `Tooltip`) rendering behind the sidebar by establishing a stacking context on each portal container. Centralize all z-index values into CSS custom properties (`--z-sidebar`, `--z-sidebar-rail`, `--z-popup`, `--z-overlay`) defined in `globals.css`. - afec4f7: Updated [graphql](https://www.npmjs.com/package/graphql) (^16.13.0 -> ^16.13.2)

@tailor-platform/app-shell@0.30.0
? · 2026-03-17

### Minor Changes - a8c5dcf: Export primitive UI components (`Button`, `Input`, `Table`, `Dialog`, `Sheet`, `Tooltip`) and update `@base-ui/react` to v1.3.0. ## New components ```tsx import { Button, Input, Table, Dialog, Sheet, Tooltip, } from "@tailor-platform/app-shell"; ``` ### Button Styled button with variant (`default`, `outline`, `destructive`, etc.) and size options. ```tsx <Button variant="outline" size="sm"> Click me </Button> ``` ### Input Styled text input with consistent theming. ```tsx <Input placeholder="Enter your name" /> ``` ### Dialog Modal dialog with compound component API (`Dialog.Root`, `Dialog.Content`, etc.). ```tsx <Dialog.Root> <Dialog.Trigger render={<Button />}>Open</Dialog.Trigger> <Dialog.Content> <Dialog.Title>Confirm</Dialog.Title> <Dialog.Description>Are you sure?</Dialog.Description> <Dialog.Footer> <Dialog.Close render={<Button variant="outline" />}> Cancel </Dialog.Close> <Button>Confirm</Button> </Dialog.Footer> </Dialog.Content> </Dialog.Root> ``` ### Sheet Slide-in panel backed by Drawer with native swipe-to-dismiss gesture support. ```tsx <Sheet.Root side="right"> <Sheet.Trigger render={<Button />}>Open</Sheet.Trigger> <Sheet.Content> <Sheet.Title>Settings</Sheet.Title> </Sheet.Content> </Sheet.Root> ``` ### Tooltip Hover/focus tooltip with configurable placement and delay via `Tooltip.Provider`. ```tsx <Tooltip.Root> <Tooltip.Trigger render={<Button />}>Hover me</Tooltip.Trigger> <Tooltip.Content>Help text</Tooltip.Content> </Tooltip.Root> ``` ### Table Semantic HTML table with pre-styled header, body, and footer sub-components. ```tsx <Table.Root> <Table.Header> <Table.Row> <Table.Head>Name</Table.Head> <Table.Head>Email</Table.Head> <Table.Head>Role</Table.Head> </Table.Row> </Table.Header> <Table.Body> <Table.Row> <Table.Cell>Alice</Table.Cell> <Table.Cell>alice@example.com</Table.Cell> <Table.Cell>Admin</Table.Cell> </Table.Row> </Table.Body> </Table.Root> ``` ## Other changes - `DescriptionCard`, `Layout`, and `Layout.Column` now accept an optional `style` prop for inline styles. - Fixed Dialog and Sheet overlay flashing on close animation. - Fixed missing `astw:` prefixes on sidebar utility classes that caused mobile sidebar UI bugs.