Releases: tailor-platform/app-shell
Release list
@tailor-platform/app-shell@1.9.0
Minor Changes
-
423ae75: Add an
xs(16px) size toAvatarAvatar.Rootnow acceptssize="xs"for a 16px circle, extending the existingsm/default/lgscale:sizecircle xs16px sm24px default28px lg40px xsis intended for compact/inline contexts and single-glyph or icon content; two-letter initials will be cramped at this size. Purely additive — the default size is unchanged. -
6f767f5: Add QuickBooks-style keyboard shortcuts to
DateField/DatePickerWhen any date segment is focused, the field now supports whole-date navigation (all case-insensitive):
t— todaym/h— first / last day of the entered month (or the current month when no date is entered)y/r— first / last day of the entered yearw/k— first / last day of the week (locale-aware week start)-— previous day;=/+— next day. Both step across month and year boundaries (e.g. 1 Jan − 1 day → 31 Dec of the prior year).+works whether or not Shift is held./— commit the current segment as-is and advance to the next (e.g. typing1then/means "January", not the start of1x)Alt+↓— open the calendar popover (DatePickeronly)
A 1–2 digit year is also expanded to the 2000s on blur (
26→2026).Both
DateFieldandDatePickernow accept an optionalfirstDayOfWeekprop to override the locale's week start for thew/kshortcuts (previouslyDatePicker-only).The shortcuts also work while the calendar popover is open — there they move the highlighted day (like the arrow keys), and
Enterconfirms. Targets are clamped tominValue/maxValuein both the field and the calendar, so a shortcut can't jump to an out-of-range date; unavailable days can be highlighted but not confirmed. -
0817cf1: AppShell now bundles and injects a small default favicon set instead of a single 32×32 icon. When no
faviconprop is passed (and the host page declares no<link rel="icon">), AppShell renders 16×16 and 32×32 PNG tab icons plus a 180×180 Apple touch icon — all embedded as data URIs, so no asset-copy step is needed. The Apple touch icon covers "Add to Home Screen" on both iOS and Android; the legacy.icoand PWAandroid-chrome-*icons are intentionally omitted to keep the bundle small (this is not a PWA).Behavior for consumers is unchanged: passing
faviconstill replaces the whole set with your single href, and an existing host-page favicon is still respected. -
2497a2d: Add
useOpenCommandPalette()for opening the built-in command palette from application code.const openCommandPalette = useOpenCommandPalette(); openCommandPalette(); openCommandPalette({ search: "PO: alice" });
-
57a3870: Add composable
Timelineprimitives for building time-based layouts, including axis guides/levels, row backgrounds, intervals, and dependency links.Timeline.Rootdefines the shared start/end range.Timeline.Viewportrenders the axis, decorations, scrolling area, and link layer.Timeline.Rowdefines one timeline lane and owns row height/background.Timeline.Intervalpositions content across a time range.Timeline.Linkdraws dependency lines between timeline items.
import { Timeline } from "@tailor-platform/app-shell"; <Timeline.Root start={0} end={100}> <Timeline.Viewport axis={{ guides: [{ at: 0 }, { at: 50 }, { at: 100 }], levels: [ { kind: "spans", items: [ { start: 0, end: 50, label: "Phase 1" }, { start: 50, end: 100, label: "Phase 2" }, ], }, ], }} > <Timeline.Row height={40}> <Timeline.Interval start={10} end={35}> <div className="h-full rounded-md bg-primary" /> </Timeline.Interval> </Timeline.Row> </Timeline.Viewport> </Timeline.Root>;
Tooltip.Contentalso now acceptsnoArrowfor popover-like timeline labels and custom overlays.
Patch Changes
-
ca1c8c7: Raise the
@tailor-platform/auth-public-clientdependency floor from^0.5.0to^0.5.1so fresh installs cannot resolve the broken0.5.0release. -
6e61cc2: Fix
DateField/DatePickernot validating typed input againstminValue,maxValue, orisDateUnavailable. A date entered by typing (or via a keyboard shortcut) that fell outside the allowed range was silently accepted. It's now flagged invalid (aria-invalid+ a built-in message) while the value still flows throughonChange— matching how the calendar already behaves and the react-aria validation contract.- Keyboard shortcuts (
t,m,y, …) no longer clamp their target to the boundary; like typing, an out-of-range result is surfaced as invalid instead of being silently coerced. isDateUnavailablenow also drives field validation (e.g. weekends or specific blackout dates typed into the field are flagged), not just calendar selection.- Consumer
errorMessagestill takes precedence over the built-in messages.
- Keyboard shortcuts (
@tailor-platform/app-shell@1.8.0
Minor Changes
-
a4f735c: Add DateField, DatePicker, and Calendar components (@internationalized/date + Base UI implementation)
Introduces three accessible date-input components, implemented on
@internationalized/date(value layer) and Base UI (Popover) with hand-rolled segmented-input and calendar-grid behaviour:DateField— segmented date/time input with label, description, and error messageDatePicker— date field with a popover calendarCalendar— standalone calendar grid
All three accept
LocalizedStringlabels/descriptions and resolve locale + timezone from the AppShell context. The@internationalized/datevalue types (CalendarDate,CalendarDateTime,ZonedDateTime, …) and helpers (parseDate,getLocalTimeZone, …) are re-exported from@tailor-platform/app-shell.New AppShell context hooks:
useResolvedLocale()— full BCP-47 locale (e.g."en-GB") plus the language codeuseTimeZone()— returns aTimeZoneobject with.value(IANA string),.today(), and.now()bound to the configured timezone
AppShell now accepts an optional
timeZoneprop.This is the
@internationalized/date+ Base UI variant — the lighter foundation tracked in the design proposal (§9). Net-new dependency is just@internationalized/date(~11 KB gz); Base UI is already in the bundle. Public API and accessibility contract are identical to the react-aria variant.Known limitation: the segmented input is built from
role="spinbutton"elements that aren'tcontentEditable, so on-screen-keyboard typing on touch devices is limited — the calendar popover is the touch-friendly path (desktop keyboard entry works fully). The APG behaviour is unit-tested but not yet screen-reader-audited. -
ef5c788: DataTable now scrolls internally: add
fillprop toLayoutto pin page chrome and scroll only the table rows.<Layout fill> <Layout.Header title="Products" /> <Layout.Column> <DataTable.Root value={table}> <DataTable.Toolbar>…</DataTable.Toolbar> <DataTable.Table /> <DataTable.Footer> <DataTable.Pagination /> </DataTable.Footer> </DataTable.Root> </Layout.Column> </Layout>
With
fill, the page title, table toolbar, column header row (sticky), and footer stay visible at all heights — only the rows region scrolls vertically. Withoutfill, pages grow and scroll as before. Tables short enough to fit render without a scrollbar or layout shift.Behavior change: the AppShell layout is now viewport-bounded (
h-svhinstead ofmin-h-svh), so page content scrolls inside the content area rather than on the document. Code relying onwindow/document scroll position should target the content area instead.Also fixes the empty/error state reserving
pageSize-worth of height — it is now capped at 5 rows, so large page sizes no longer produce a huge blank region below "No data". -
ef5c788: Add
useAppShellScrollContainer()— a supported handle to the content scroll containerNow that the shell is viewport-bounded (
h-svh) and page content scrolls inside the content area rather than on the document, consumers that previously relied onwindow/document scroll need a stable way to reach the element that actually scrolls.useAppShellScrollContainer()returns a ref to that element:const scrollRef = useAppShellScrollContainer(); useEffect(() => { const el = scrollRef.current; if (!el) return; const onScroll = () => setProgress(el.scrollTop / (el.scrollHeight - el.clientHeight)); el.addEventListener("scroll", onScroll, { passive: true }); return () => el.removeEventListener("scroll", onScroll); }, [scrollRef]);
Use it wherever you would previously have reached for
window.scrollY, awindowscrolllistener,window.scrollTo(...), or anIntersectionObserverwith the default viewport root.- On a
<Layout fill>page the element does not scroll (its children manage their own scrolling). - Outside a
SidebarLayoutthe ref'scurrentis alwaysnull. - The container also carries a
data-appshell-scroll-containerattribute for non-React access (CSS, tests,document.querySelector).
- On a
-
aa9306a: Add a
headerslot toSidebarLayoutand aSidebarLayout.DefaultHeaderbuilding blockSidebarLayoutnow accepts a full-regionheader?: ReactNodeprop, mirroring the existingsidebarslot. It defaults to the built-in header (SidebarLayout.DefaultHeader), so existing usage is unchanged.To extend the built-in header (e.g. add a notification bell or user menu) without reconstructing the trigger and breadcrumb, use
SidebarLayout.DefaultHeaderwith itsactionsslot:<SidebarLayout header={ <SidebarLayout.DefaultHeader actions={[ <NotificationBell key="bell" />, <AppearanceSwitcher key="appearance" />, ]} /> } />
DefaultHeader'sactionsprop (single node or array) controls the entire right-hand cluster, laid out in a horizontal, vertically-centered row.actionsdefaults to[<AppearanceSwitcher />], and passingactionsreplaces the whole cluster including the switcher — include<AppearanceSwitcher />explicitly to keep it. This keeps the API a single slot instead of accumulating one-off props.DefaultHeaderis available asSidebarLayout.DefaultHeaderand as a top-levelDefaultHeaderexport.DefaultSidebaris now also exposed asSidebarLayout.DefaultSidebarfor symmetry; the top-levelDefaultSidebarexport is retained for backwards compatibility.
This provides an official, composable extension point for the top bar, replacing fragile consumer workarounds that queried the header DOM and injected a React portal.
-
078130e: Add a
sizeprop toTabsand makecapsuleicon-only tabs squareTabs.Rootnow acceptssize?: "xs" | "sm" | "default" | "lg"(default"default"), mirroringButton's height tiers. It sets a minimum height on thecapsulevariant — taller content still grows — so a capsule track sits flush next to aButton:sizecapsule track matches Button xs28px size="xs"sm32px size="sm"default36px default lg40px size="lg"Two
capsulefixes from the same change:- The list no longer hard-locks to
h-10(40px); its height derives from the tab size floor. - A tab whose only child is an icon now renders square (
min-widthfollowsmin-height), instead of the previous wider-than-tall, shrunken look — ideal for icon-only view toggles.
The default
capsuleheight moves from 40px to 36px to align withButton's default height.sizeonly affects thecapsulevariant;lineanddefaultare unchanged. - The list no longer hard-locks to
Patch Changes
-
eeb10ab: Remove the
streamoption fromuseAIChat()andAIGatewayChatRequest.AppShell now selects the appropriate transport (streaming vs JSON) automatically based on the model. Passing
streamis no longer needed.
@tailor-platform/app-shell@1.7.0
Minor Changes
-
7f33506: Allow the standalone
Select,Combobox, andAutocomplete(including their.Asyncvariants) to receive an accessible name viaaria-label,aria-labelledby, andid. Previously these props were silently dropped, leaving the combobox with only its current value as an accessible name — a WCAG 4.1.2 issue for filters and toolbars used outside aForm. The props are now forwarded to the underlying trigger/input.// Announced as "Direction filter, combobox" instead of just its value <Select items={items} value={value} onValueChange={setValue} aria-label="Direction filter" /> // Or point at a visible label <span id="dir-label">From</span> <Combobox items={items} aria-labelledby="dir-label" />
Patch Changes
-
ff30974: Fix
@tailor-platform/app-shell/stylesso consumer Tailwind utilities likebg-card,border-border, andtext-muted-foregroundresolve again without requiringtheme.css.theme.cssremains a no-op compatibility shim, whilestylesnow restores the Tailwind theme bridge and keeps the precompiled AppShell component CSS importable from a single entrypoint.
@tailor-platform/app-shell@1.6.1
Patch Changes
-
147aacf: Fix Vitest and other Node-evaluated test setups that import
@tailor-platform/app-shellby keeping the package's JS entry free of CSS imports.@tailor-platform/app-shell/stylescontinues to ship the default AppShell styles and bundled Inter font assets. Applications should import that stylesheet explicitly, as shown in the docs and examples.
@tailor-platform/app-shell@1.6.0
Minor Changes
-
820b75b: Add
DocumentProgressCard— a generic, presentational card for a document's lifecycle/fulfilment state: an optional headline percentage, a stacked progress bar, and a status legend driven by arbitrarysegments. It's domain-agnostic, so it stretches to any status breakdown (shipped / cancelled / pending, PO received / returned / yet-to-receive, etc.).import { DocumentProgressCard } from "@tailor-platform/app-shell"; <DocumentProgressCard title="Shipment status" percent={60} segments={[ { label: "Shipped", value: 30, color: "green" }, { label: "Returned", value: 3, color: "red" }, { label: "Pending", value: 17, color: "neutral" }, ]} />;
Each segment is
{ label, value, color }. Pass an explicitpercent, an optionaltotal(leave a value larger than the segment sum to render an unfilled track remainder), and an optionallegendoverride for when the legend should differ from the bar (e.g. overlapping buckets). Percentage and any domain-specific breakdown are derived in the consumer — see the docs for a purchase-order fulfilment recipe. -
d1cac39: Add
Grid— a generic, presentational CSS-Grid layout primitive for arranging arbitrary children into equal or custom-width columns, with responsive reflow, gap control, auto-fit, and optionalGrid.Itemspanning. ComplementsLayout(page scaffold) by handling content-level grids.import { Grid } from "@tailor-platform/app-shell"; // Responsive KPI grid: 1 → 2 → 4 columns <Grid columns={{ initial: 1, sm: 2, lg: 4 }} gap={4}> <Card.Root>…</Card.Root> </Grid> // Auto-fitting gallery — no breakpoints needed <Grid minChildWidth={240} gap={6}>{items}</Grid> // Custom column widths + spanning <Grid columns="280px 1fr" gap={6}> <Grid.Item colSpan={2}>Wide</Grid.Item> </Grid>
Props:
columns,rows,gap/gapX/gapY(defaults to3/ 12px),minChildWidth,flow,align,justify.Grid.ItemsupportscolSpan,rowSpan,colStart,colEnd— all responsive. -
2beb757: Add
align?: "left" | "center" | "right"toTable.HeadandTable.Cellso raw table primitives can align numeric and status columns without relying on consumer Tailwind utility overrides.<Table.Head align="right">Amount</Table.Head> <Table.Cell align="right">123</Table.Cell> <Table.Head align="center">Status</Table.Head> <Table.Cell align="center">Active</Table.Cell>
-
116d2cc: Add a low-level AI Gateway client and a simple text-only chat hook for AppShell.
import { createAIGatewayClient, useAIChat } from "@tailor-platform/app-shell"; const aiClient = createAIGatewayClient({ gatewayUri, authClient }); const { messages, sendMessage, stop } = useAIChat({ client: aiClient, model: "gpt-5-mini", });
-
ae2396b: Add
useURLCollectionVariablesfor wiring collection state (filters, sort, page size) to the URL query string in a single call. It seeds initial state from the current router search params and writes changes back as the user filters, sorts, or pages.const { variables, control } = useURLCollectionVariables({ tableMetadata, params: { pageSize: 20 }, });
For cases that need URL persistence without react-router's
useSearchParams(e.g. a custom binding), the purewithURLCollectionState(options, [searchParams, setSearchParams])decorator is also exported and can be composed withuseCollectionVariablesdirectly.useCollectionVariablesnow reports state updates throughonParamsChangeusing the sameparamsshape it accepts.
Patch Changes
-
40e0f1c: Restore the
@tailor-platform/app-shell/theme.cssexport as a no-op compatibility shim.Apps that still import
@tailor-platform/app-shell/theme.cssalongside@tailor-platform/app-shell/stylesnow keep building while the real theme tokens continue to come fromstyles.
@tailor-platform/app-shell@1.5.0
Minor Changes
-
00f9aa7: Manage the browser tab from AppShell — title and favicon.
- Title: AppShell now keeps
document.titlein sync with the active route as"<page> · <title>", where<page>is the current breadcrumb leaf (including anyuseOverrideBreadcrumboverride, so detail pages show their record name automatically) and<title>is thetitleprop passed to<AppShell>. Works for every page with no per-page wiring; when notitleis set the tab shows just the page name. - Favicon: new
faviconprop on<AppShell>sets the document favicon (any<link rel="icon">href — a public-path URL or a data URI). When omitted it defaults to the bundled Tailor favicon, so apps get correct branding out of the box.
Both are rendered declaratively (React 19 hoists
<title>/<link rel="icon">into<head>), so it works in client-only apps, streaming SSR, and Server Components.If your app already updates
document.titlemanually, you can drop that wiring and let AppShell own the tab title. A host-page<link rel="icon">can remain in place unless you explicitly pass the newfaviconprop. - Title: AppShell now keeps
-
5353303: Introduce theming support — ColorTheme axis, static theme palettes via CSS imports, a new
AppearanceSwitchercomponent, and bundled Inter variable fonts.ColorTheme (end-user preference, persisted)
ColorTheme("light" | "dark" | "system") is the end-user color mode preference. Applied to<html>as.light/.darkclass.<AppShell defaultColorTheme="system">sets the initial preference; user choice is persisted to localStorage.useTheme()hook returns{ theme, resolvedTheme, setTheme }.
Theme Palettes (static CSS imports)
Each palette (
default,cream,bloom) ships both light and dark variants.
Select a palette by importing its CSS file — no prop needed:import "@tailor-platform/app-shell/themes/cream";
The default palette is included automatically via
@tailor-platform/app-shell/styles.AppearanceSwitcher
- New
<AppearanceSwitcher />component for toggling color theme (light / dark / system).
Patch Changes
- 3d48b24: Remove
loaderfrom file-based page definitions (Page.appShellPageProps). This removes an accidentally exposed incomplete API and makesguardsthe single source of page-level route behavior in file-based routing. - 3138c6a: Fix DataTable checkbox vertical alignment. Remove unnecessary wrapper div and
translate-y-[2px]hack so checkboxes align correctly with row content viaalign-middle. - 5353303: Set explicit
--accentvalues per palette and mode, reorganise palette CSS into numbered tiers with a_template.cssauthoring guide, add--alert-*semantic tokens to the default palette, and wireAlertvariants to those tokens (including lighter dark-mode foregrounds). - Updated dependencies [3d48b24]
- @tailor-platform/app-shell-vite-plugin@0.2.3
@tailor-platform/app-shell-vite-plugin@0.2.3
Patch Changes
- 3d48b24: Remove
loaderfrom file-based page definitions (Page.appShellPageProps). This removes an accidentally exposed incomplete API and makesguardsthe single source of page-level route behavior in file-based routing.
@tailor-platform/app-shell@1.4.1
Patch Changes
-
f176477: Fix cursor consistency across
ButtonandBadge:Button: restore the pointer cursor. Tailwind v4 dropped the Preflight
rule that gavebutton/[role="button"]acursor: pointer, so the base
Buttonshowed the default arrow cursor while components that hardcode
astw:cursor-pointer(e.g.ActionPanel) did not. Addedastw:cursor-pointer
to thebuttonVariantsbase classes so allButtoninstances are consistent.
Disabled buttons keep the default cursor via the existing
astw:disabled:pointer-events-none.Badge: pinastw:cursor-default. ABadgeis non-interactive but
renders a<div>with no cursor of its own, so it inheritedcursor: pointer
from clickable ancestors (e.g. aDataTablerow withonClickRow). Explicitly
setting the default cursor stops a badge from signalling clickability.
@tailor-platform/app-shell@1.4.0
Minor Changes
-
5a251fd: Add
Alertcompound component withneutral,success,warning,error, andinfovariants. Each variant renders a contextual icon automatically.import { Alert } from "@tailor-platform/app-shell"; <Alert.Root variant="success"> <Alert.Title>Saved</Alert.Title> <Alert.Description>Your changes have been saved.</Alert.Description> </Alert.Root>;
-
49f10e3: Improve Sheet component customizability:
- Add
sizeprop toSheet.Contentfor width variations (sm,md,lg,xl,full) - Add
actionprop toSheet.Headerfor placing action buttons to the right of the title - Increase
Sheet.Titlefont size totext-lgfor better visibility - Move close button to the left side of the header, with title inline next to it
- Header now has a bottom border, Footer has a top border for visual separation
<Sheet.Root side="right"> <Sheet.Trigger render={<Button />}>Open</Sheet.Trigger> <Sheet.Content size="lg"> <Sheet.Header action={<Button size="sm">Save</Button>}> <Sheet.Title>Edit Record</Sheet.Title> </Sheet.Header> </Sheet.Content> </Sheet.Root>
- Add
Patch Changes
-
58b76ce: Add
infoandsubtle-infobadge variants for informational or in-progress status labels (blue palette, matching the existingoutline-infodot color). Also extend the primitives demo to show all five outline variants (previously onlyoutline-successwas rendered).<Badge variant="info">New</Badge> <Badge variant="subtle-info">In Progress</Badge>
-
98270c5: Add optional
variantprop toActionItemfor theActionPanelcomponent. Usevariant: "destructive"to style dangerous actions (e.g., delete) with red text and hover background.
@tailor-platform/app-shell@1.3.0
Minor Changes
-
902fad2: Add
Tabscompound component for tab-based navigation, backed by Base UI's Tabs primitive.import { Tabs } from "@tailor-platform/app-shell"; <Tabs.Root defaultValue="overview"> <Tabs.List> <Tabs.Tab value="overview">Overview</Tabs.Tab> <Tabs.Tab value="projects">Projects</Tabs.Tab> </Tabs.List> <Tabs.Panel value="overview">Overview content</Tabs.Panel> <Tabs.Panel value="projects">Projects content</Tabs.Panel> </Tabs.Root>;
-
2197c3f: Add array badge support to DataTable and DescriptionCard with shared
BadgeListrendering and overflow popover.// DataTable — badge column with array accessor column({ ...infer("tags"), type: "badge", typeOptions: { badgeVariantMap: { Premium: "warning", Office: "outline-info" }, maxVisible: 2, }, }) // DescriptionCard — array badges with maxVisible <DescriptionCard data={{ tags: ["urgent", "fragile", "international"] }} fields={[{ key: "tags", label: "Tags", type: "badge", meta: { badgeVariantMap: { urgent: "error", fragile: "warning", international: "outline-info" }, maxVisible: 2, }, }]} />
Additional changes:
- Unify badge variant resolution into shared
resolveBadgeVariant()utility with"outline-neutral"as the default variant (previously"neutral"in DataTable) - Export
BadgeVariantandBadgeOptionstypes from the public API inferColumns()no longer sets a defaultrenderfunction — columns without an explicittypeorrendernow display—for null/empty values (aligns with typed-column behavior)- Deprecate
BadgeVariantTypein favor ofBadgeVariant
- Unify badge variant resolution into shared
Patch Changes
-
4998df4: Fix and improve
DescriptionCardcomponent:- Fix relative date formatting producing incorrect output for future dates (negative time diff)
- Add i18n label support for relative date strings
- Use react-router
<Link>for internal navigation in link and reference fields instead of plain<a>tags
-
b70e3d5: Updated @base-ui/react (1.4.1 -> 1.5.0)