goobs-frontend
Version:
A comprehensive React-based libary for building modern web applications
191 lines • 8.5 kB
TypeScript
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