UNPKG

@payfit/unity-components

Version:

271 lines (213 loc) 9.18 kB
--- name: unity-find-component description: > Load when selecting a Unity component is unclear or before creating a custom UI primitive that may already exist in @payfit/unity-components. Use it to find the existing component, then fall back to React Aria plus uy: classes only when Unity has no fit. metadata: type: core library: '@payfit/unity-components' library_version: '2.x' --- Routing skill for selecting a Unity component before writing UI code. Walk the catalog, then the decision tree, then the commonly-confused pairs. ## Component catalog Inspect the canonical [component catalog](../../src/agent-references/component-catalog.json) before searching component source. It contains exact component ids, titles, descriptions, keywords, sections, tags, related entries, and Storybook preview metadata. There is no separate canonical Markdown catalog. For a targeted lookup, search the full JSON and compare all plausible matches. Use whatever reliable local search tool is available (for example `rg`, a JSON-aware CLI, or a short Node script); do not assume `jq` is installed: ```sh rg -i -C 2 'dialog|confirmation|modal' \ libs/shared/unity/components/src/agent-references/component-catalog.json ``` ## Standard operating procedure 1. Build an inventory of distinct interface needs from all task inputs, including the primary workflow, surrounding controls, navigation, status information, and secondary actions. Do not silently omit peripheral UI. 2. Expand every identified need into synonyms, interaction patterns, and adjacent component terms. 3. Search the complete catalog for every inventory item; do not stop after finding components for the primary workflow or at the first plausible hit. 4. Rank candidates by intent fit, description, keywords, tags, section, and related entries. 5. Return the best match first. Mention an alternative only when the evidence is genuinely ambiguous. 6. Search `libs/shared/unity/components/src` to confirm implementation details or locate nearby source. 7. Inspect Storybook stories only for props, API details, or usage examples after a likely component has been identified. When the Storybook MCP is available, use its curated component manifest or matching Storybook entry as a complementary source for rendered examples; the canonical JSON catalog remains the source of truth for discovery. 8. Walk the decision tree below for every identified need before creating a custom primitive. 9. Before implementation, perform a coverage check: every inventoried interface need must map to a Unity component, an explicit React Aria fallback, or a deliberate omission requested by the user. ## Decision Tree For every UI need, walk these three levels in order. Stop at the first that fits. ### Level 1 — Use Unity directly If a named export covers the use case, import it. No abstractions over the top. ```tsx import { Button, Dialog, DialogActions, DialogContent, Pill, } from '@payfit/unity-components' export function ConfirmDelete({ isOpen, onClose, }: { isOpen: boolean onClose: () => void }) { return ( <Dialog isOpen={isOpen} onOpenChange={onClose}> <DialogContent> <Pill color="danger">Destructive</Pill> </DialogContent> <DialogActions> <Button variant="secondary" onPress={onClose}> Cancel </Button> <Button color="danger" onPress={onClose}> Delete </Button> </DialogActions> </Dialog> ) } ``` ### Level 2 — Fall back to React Aria + uy:\* classes Build your own primitive only when Unity has no equivalent. Compose React Aria primitives, style with the `uy:` prefix, and merge classes with `uyMerge` / `uyTv`. ```tsx import { uyTv } from '@payfit/unity-themes' import { ToggleButton } from 'react-aria-components' const toggleStyles = uyTv({ base: 'uy:inline-flex uy:items-center uy:gap-100 uy:rounded-100 uy:px-200 uy:py-100', variants: { isSelected: { true: 'uy:bg-surface-action uy:text-content-on-action', false: 'uy:bg-surface-secondary uy:text-content-neutral', }, }, }) export function CustomPivot({ label }: { label: string }) { return ( <ToggleButton className={({ isSelected }) => toggleStyles({ isSelected })}> {label} </ToggleButton> ) } ``` ### Level 3 — Midnight (last resort, deprecated) Only when (a) Unity has no equivalent, (b) React Aria + `uy:*` cannot realistically rebuild it, and (c) the feature ships against a deadline. Open a follow-up to migrate. Per `AGENTS.md`, Midnight is deprecated; never import it into a new module. ## Commonly Confused Pairs - `Badge` vs `Pill`: `Badge` is a numeric/dot indicator anchored to another element (notification count). `Pill` is a standalone label/status chip with text content. - `Card` vs `SelectableCard...` vs `NavigationCard`: `Card` is the generic container. `SelectableCardCheckboxGroup` / `SelectableCardRadioGroup` wrap cards as form inputs. `NavigationCard` wraps a card as a router-aware link. - `Button` vs `IconButton` vs `RawLinkButton`: `Button` for actions with text. `IconButton` / `CircularIconButton` for icon-only actions (requires `aria-label`). `RawLinkButton` renders as an anchor but styled like a button — use when the action navigates. - `Search` vs `TextField`: use `Search` for query, lookup, and filtering controls with search affordances and clearing behavior. Use `TextField` for general textual data entry. - `Menu` vs `Popover`: `Menu` is a list of actionable items keyed by keyboard (Enter/Arrow). `Popover` is a free-form floating panel and requires a `title`. - `Dialog` vs `PromoDialog`: `Dialog` for confirmation / edit flows. `PromoDialog` for marketing / onboarding announcements; requires `PromoDialogHero`. - `Table` vs `DataTable`: `Table` is the layout primitive (header, body, rows, cells) with no behavior. `DataTable` wires Tanstack Table for sorting, filtering, pagination, virtualization, bulk actions. - `ErrorState` vs `Alert`: `ErrorState` is a full-area empty-replacement for "this section failed to load." `Alert` is an inline banner that coexists with surrounding content. ## Icon selection Choose the component here (`Icon`, `IconButton`, a prefix/suffix icon, and so on), then use `unity-icons` to select an exact sprite name, type shared icon props, or migrate a Midnight icon. Do not duplicate or guess the icon catalog from the Components package. ## Forms Notice Tanstack Form (`useTanstackUnityForm`) is the only supported form system. The React Hook Form path — `useUnityForm` plus the legacy `TextField` / `SelectField` / `NumberField` etc. RHF wrappers exported from the same index — is deprecated. Do not author new code with `useUnityForm`. See `unity-tanstack-form`. ## Common Mistakes ### HIGH Hand-roll component that already exists Wrong: ```tsx const Tag = ({ children }) => ( <span className="uy:rounded-full uy:px-200 uy:py-100 uy:bg-surface-primary"> {children} </span> ) ``` Correct: ```tsx import { Pill } from '@payfit/unity-components' ;<Pill>{children}</Pill> ``` The hand-rolled span re-derives Pill's tokens by guesswork and drifts from the design-system source-of-truth on every theme update. Source: libs/shared/unity/components/src/components/pill/Pill.tsx ### HIGH Use React Aria primitive directly when Unity wraps it Wrong: ```tsx import { Button as AriaButton } from 'react-aria-components' ;<AriaButton>Click</AriaButton> ``` Correct: ```tsx import { Button } from '@payfit/unity-components' ;<Button variant="primary">Click</Button> ``` The bare React Aria Button has no Unity theming, intl, or styling defaults; you ship an unstyled element with no `uy:*` classes. Source: libs/shared/unity/components/src/components/button/Button.tsx ### HIGH Reach for Midnight when Unity has an equivalent Wrong: ```tsx import { Button, Modal } from '@payfit/midnight' ``` Correct: ```tsx import { Button, Dialog } from '@payfit/unity-components' ``` Midnight is deprecated; new screens that import it cannot match the Unity theme tokens and will require a migration pass later anyway. Source: AGENTS.md "Do NOT use (deprecated)" ### MEDIUM Combine Input + FormField when \*Field exists Wrong: ```tsx <FormField label="Name" error={errors.name}> <Input {...register('name')} /> </FormField> ``` Correct: ```tsx <form.AppField name="name"> {field => <field.TextField label="Name" />} </form.AppField> ``` Manual `FormField` + `Input` skips the label-for/aria-describedby wiring, the `field.state.meta` error plumbing, and the required-state inference that the `*Field` components handle. Source: libs/shared/unity/components/src/components/form-field/FormField.tsx; index.ts:205-223 ## See also - For projects not yet configured for Unity, use the repository's `@payfit/nx-tools:setup-unity` generator - `unity-migrate-from-midnight`when Level 3 fallback hits a Midnight screen, follow this skill to replace it - `unity-icons` — select and type the exact icon after choosing the component - `unity-tanstack-form` — the only supported form authoring path