UNPKG

goobs-frontend

Version:

A comprehensive React-based libary for building modern web applications

191 lines 8.5 kB
import { default as React } from 'react'; /** * Capability profile for a single tab. Consumed by tooling that needs * to reason about what each tab supports (e.g. the auto-CRUD test * scaffolder gates create/update/delete phase tests on these flags * instead of inferring from JSX patterns). * * All fields optional — tabs that don't declare capabilities are * treated as "unknown" by tooling and fall back to heuristic detection. */ export interface TabsItemCapabilities { /** Tab supports creating entities (Create button visible). */ create?: boolean; /** Tab supports updating entities (Edit button visible). */ update?: boolean; /** Tab supports deleting entities (Delete button visible). */ delete?: boolean; } export interface TabsItem { /** * Title / label may be a ReactNode (allows status dots or other * inline glyphs alongside the text — see account-information tabs). * When a non-string is supplied the auto-CRUD scaffolder can no * longer fall back to kebabing it for a `data-tab-id`; pass the * stable `id` field explicitly in that case. */ title?: string | React.ReactNode; label?: string | React.ReactNode; route?: string; trigger?: 'route' | 'onClick'; onClick?: () => void; /** * Stable identifier surfaced as `data-tab-id` on the rendered button * AND on the corresponding panel (when a `<TabPanel>` is used). * Tests target tabs by this id rather than by visible label so * label-text changes don't break the test contract: * `page.locator('[data-tab-id="categories"]').click()` * `expect(page.locator('[data-tab-active="true"][data-tab-id="categories"]')).toBeVisible()` * * Falls back to a kebab-cased `label`/`title` when omitted. */ id?: string; /** * Singular entity noun for THIS tab — `"category"` not `"contract"` * for the Categories tab on a contracts route. Read by the auto-CRUD * scaffolder to build per-tab fixtures * (`Audit Test ${capitalize(subject)}`) so tests in nested * describe.serial blocks bind to the right entity. */ subject?: string; /** Per-tab CRUD capability flags — see `TabsItemCapabilities`. */ capabilities?: TabsItemCapabilities; /** * Actual labels rendered in this tab's metric strip, if the tab has * its own metrics. The scaffolder's `verifyStatus` template reads * this to build a permissive regex (e.g. * `/(Contracts|With Docs|Employees|Customers|Categories)/i`) * instead of hardcoding `"Total"`. */ metricLabels?: string[]; /** Optional count badge rendered after the label (matches the * pattern most workspaces hand-rolled before adopting the shared * shell). Hidden when undefined or null. */ count?: number | null; /** * Optional leading glyph / icon node rendered before the label. Use * for hand-rolled tab strips migrating to goobs that previously * embedded an icon character in their label text (e.g. `"☰ Inbox"`). * Pass either a string glyph (rendered with `aria-hidden`) or a * ReactNode for full control. */ icon?: React.ReactNode; } export interface TabsProps { items: TabsItem[]; activeTab?: number; onChange?: (index: number) => void; alignment?: 'left' | 'center' | 'right' | 'justify'; /** * Visual treatment of the tab strip. `'underline'` (default) is the * classic bottom-border tab row; `'chips'` renders each tab as a rounded * pill (bordered, tonal fill when active) and drops the strip's bottom * border. Purely cosmetic — `role="tab"`/`role="tablist"` semantics, the * count badge, keyboard roving nav, and every `data-*` selector are * identical in both appearances, so existing tests and AT are unaffected. */ appearance?: 'underline' | 'chips'; /** * `aria-label` on the tablist. Defaults to `"Workspace sections"`. * Override when several `<Tabs>` render on the same page. */ ariaLabel?: string; styles?: { theme?: string; padding?: string; gap?: string; borderBottom?: string; height?: string; tabLeftBorder?: string | boolean; tabRightBorder?: string | boolean; backgroundColor?: string; }; } /** * Accessible WAI-ARIA tablist that renders a set of tab items with full * keyboard navigation, optional count badges and leading icons, and route- or * click-based activation. Emits `nav.change` diagnostics and stable * `data-tab-id`/`data-tab-subject` selectors; pairs with the exported `Tab` and * `TabPanel`. */ declare const Tabs: React.FC<TabsProps>; /** * Compute the panel id that matches a given tab id. This is the SINGLE * source of truth for the tab↔panel ARIA pairing: `<Tabs>` uses it for * each tab's `aria-controls` and `<TabPanel>` uses it for its `id`, so * the two link by construction. Call it directly when hand-rolling a * panel element instead of using `<TabPanel>`. * * Ids are derived purely from the tab id (`tabpanel-${tabId}`), matching * the unscoped `tab-${tabId}` button ids. When several `<Tabs>` render * on the same page, give their items distinct `id`s (the same contract * the tab button ids already require — see `TabsProps.ariaLabel`). */ export declare function tabPanelId(tabId: string): string; export interface TabProps { /** * Tab content. String when the caller passed plain text; ReactNode * when they need inline status glyphs / icons alongside the text. * The rendered `<button>` includes the node directly. Tests should * locate tabs by `data-tab-id` / `data-tab-subject` rather than * label text, since rich-node labels can't be matched with a * simple text selector. */ label: string | React.ReactNode; isActive: boolean; onClick: () => void; disabled?: boolean; /** * Visual theme — surfaced as `data-theme` on the rendered `<button>` so * the CSS module's light/dark overrides apply. Defaults to `'sacred'` * (the historical hardcoded sacred-gold look). Forwarded by `<Tabs>` * from `styles.theme`. */ theme?: string; /** Stable identifier surfaced as `data-tab-id`. Forwarded by the * parent `<Tabs>` from `TabsItem.id` (or its kebab-cased label). */ tabId?: string; /** `id` of the corresponding `<TabPanel>` for `aria-controls`. The * parent `<Tabs>` computes this via `tabPanelId(tabId)` and hands it * down so screenreader navigation lands on the right region. */ panelId?: string; /** Singular entity noun for THIS tab — emitted as `data-tab-subject` * so tests can locate a tab by the entity it manages even when its * visible label changes. Forwarded from `TabsItem.subject`. */ subject?: string; /** Optional count badge rendered after the label. */ count?: number | null | undefined; /** Optional leading glyph / icon — see TabsItem.icon. */ icon?: React.ReactNode; /** Forwarded ref to the underlying `<button>` so the parent can * programmatically focus a tab on keyboard nav. */ buttonRef?: (el: HTMLButtonElement | null) => void; /** Forwarded keyboard handler — parent owns the arrow-key/home/end * routing across the tablist. */ onKeyDown?: (event: React.KeyboardEvent<HTMLButtonElement>) => void; } export declare const Tab: React.FC<TabProps>; /** * Optional companion to `<Tabs>` — wraps tab content with the right * ARIA wiring (`role="tabpanel"`, `id` linked to the active tab's * `aria-controls`, and `data-tab-id` so tests can wait for the * correct panel to mount). * * Use it directly under `<Tabs>` and conditionally render whichever * panel matches the active tab. Tests can wait for the panel to * become visible: * `await expect(page.locator('[role="tabpanel"][data-tab-id="categories"]')).toBeVisible()` */ export interface TabPanelProps { /** Must match the corresponding `<TabsItem>.id` so ARIA wiring lines up. */ tabId: string; /** True when this is the currently-active tab; the panel mounts but * the parent component should still gate its rendering. The flag is * used to drive `data-tab-active` and `tabIndex`. */ isActive?: boolean; children: React.ReactNode; style?: React.CSSProperties; } export declare const TabPanel: React.FC<TabPanelProps>; export default Tabs; //# sourceMappingURL=index.d.ts.map