@payfit/unity-components 2.35.4 → 2.35.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/esm/components/app-menu/parts/AppMenuHeader.js +1 -1
- package/package.json +15 -10
- package/skills/unity-data-table/SKILL.md +512 -0
- package/skills/unity-find-component/SKILL.md +377 -0
- package/skills/unity-layout-and-styling/SKILL.md +400 -0
- package/skills/unity-migrate-from-midnight/SKILL.md +190 -0
- package/skills/unity-migrate-from-midnight/references/midnight-component-map.md +180 -0
- package/skills/unity-navigation/SKILL.md +331 -0
- package/skills/unity-overlays/SKILL.md +352 -0
- package/skills/unity-setup-feature-plugin/SKILL.md +55 -0
- package/skills/unity-tanstack-form/SKILL.md +349 -0
- package/skills/unity-tanstack-form/references/bound-field-components.md +67 -0
- package/skills/unity-tanstack-form/references/schema-adapters.md +108 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-find-component
|
|
3
|
+
description: >
|
|
4
|
+
Find or build the right Unity component. Catalog of ~226 named exports
|
|
5
|
+
in @payfit/unity-components grouped by purpose (layout: Flex/Grid/Card,
|
|
6
|
+
navigation: RawLink/Nav/Tabs/Breadcrumbs/Pagination, form fields:
|
|
7
|
+
TextField/SelectField/NumberField/CheckboxField/etc., buttons: Button/
|
|
8
|
+
IconButton/RawLinkButton, overlays: Dialog/Popover/Menu/Tooltip,
|
|
9
|
+
content: Pill/Badge/Alert/DataTable/Carousel). Decision tree: prefer
|
|
10
|
+
Unity → fall back to React Aria + uy:* classes → Midnight as last resort.
|
|
11
|
+
Typed UnityIcon src convention. RHF deprecated; Tanstack Form everywhere.
|
|
12
|
+
type: core
|
|
13
|
+
library: '@payfit/unity-components'
|
|
14
|
+
library_version: '2.x'
|
|
15
|
+
sources:
|
|
16
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/index.ts'
|
|
17
|
+
- 'PayFit/hr-apps:libs/shared/unity/icons/src/components/icon/parts/IconSprite.tsx'
|
|
18
|
+
- 'PayFit/hr-apps:libs/shared/unity/icons/src/generated/index.ts'
|
|
19
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/OVERVIEW.md'
|
|
20
|
+
- 'PayFit/hr-apps:AGENTS.md'
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
Routing skill for selecting a Unity component before writing UI code. Walk
|
|
24
|
+
the catalog, then the decision tree, then the commonly-confused pairs.
|
|
25
|
+
|
|
26
|
+
## Quick Reference
|
|
27
|
+
|
|
28
|
+
All exports come from a single entry: `@payfit/unity-components`. There are
|
|
29
|
+
no sub-paths for runtime components; the only sub-paths are
|
|
30
|
+
`@payfit/unity-components/integrations/tanstack-router` (router-aware
|
|
31
|
+
navigation) and `@payfit/unity-components/i18n/<locale>.json` (message
|
|
32
|
+
bundles). Names below are grouped by purpose.
|
|
33
|
+
|
|
34
|
+
### Layout
|
|
35
|
+
|
|
36
|
+
`Flex`, `FlexItem`, `Grid`, `GridItem`, `Card`, `CardTitle`,
|
|
37
|
+
`CardContent`, `SelectableCard...`, `NavigationCard`, `NavigationCardGroup`,
|
|
38
|
+
`Page`, `PageHeader`, `PageHeading`, `AppLayout`, `AppMenu`,
|
|
39
|
+
`FunnelLayout`, `FunnelPage`, `FunnelBody`, `FunnelSidebar`, `FunnelTopBar`,
|
|
40
|
+
`FunnelPageHeader`, `FunnelPageContent`, `FunnelPageFooter`,
|
|
41
|
+
`FunnelPageActions`, `FunnelBackButton`.
|
|
42
|
+
|
|
43
|
+
### Navigation
|
|
44
|
+
|
|
45
|
+
Router-agnostic (base entry): `RawLink`, `RawNavItem`, `RawBreadcrumbLink`,
|
|
46
|
+
`RawPaginationLink`, `RawPaginationPrevious`, `RawPaginationNext`, `RawTab`,
|
|
47
|
+
`Nav`, `NavGroup`, `Breadcrumbs`, `Breadcrumb`, `Pagination`,
|
|
48
|
+
`PaginationContent`, `PaginationItem`, `PaginationEllipsis`, `Tabs`,
|
|
49
|
+
`TabList`, `TabPanel`, `SkipLinks`, `TaskMenu`, `RawTask`, `RawSubTask`,
|
|
50
|
+
`TaskGroup`, `ListView`, `RawListViewItem`, `ListViewSection`,
|
|
51
|
+
`ListViewItemLabel`, `ListViewItemText`.
|
|
52
|
+
|
|
53
|
+
Router-aware (from
|
|
54
|
+
`@payfit/unity-components/integrations/tanstack-router`): `Link`,
|
|
55
|
+
`NavItem`, `BreadcrumbLink`, `PaginationLink`, `Tab`.
|
|
56
|
+
|
|
57
|
+
### Form fields (Tanstack Form — current)
|
|
58
|
+
|
|
59
|
+
Bound via `form.AppField` then `field.<Name>`: `TextField`, `SelectField`,
|
|
60
|
+
`NumberField`, `CheckboxField`, `CheckboxGroupField`, `DatePickerField`,
|
|
61
|
+
`DateRangePickerField`, `MultiSelectField`, `RadioButtonGroupField`,
|
|
62
|
+
`SelectableButtonGroupField`, `SelectableCardCheckboxGroupField`,
|
|
63
|
+
`SelectableCardRadioGroupField`, `ToggleSwitchField`,
|
|
64
|
+
`ToggleSwitchGroupField`, `PasswordField`.
|
|
65
|
+
|
|
66
|
+
### Form primitives (no form state)
|
|
67
|
+
|
|
68
|
+
`Input`, `NumberInput`, `Select`, `SelectButton`, `SelectOption`,
|
|
69
|
+
`SelectOptionGroup`, `SelectOptionHelper`, `MultiSelect`,
|
|
70
|
+
`MultiSelectOption`, `MultiSelectOptGroup`, `Checkbox`, `CheckboxStandalone`,
|
|
71
|
+
`CheckboxGroup`, `RadioButtonGroup`, `RadioButton`, `RadioButtonHelper`,
|
|
72
|
+
`TextArea`, `ToggleSwitch`, `ToggleSwitchGroup`, `SegmentedButtonGroup`,
|
|
73
|
+
`ToggleButton`, `SelectableButtonGroup`, `SelectableButton`, `Search`,
|
|
74
|
+
`PhoneNumberInput`, `DatePicker`, `DateCalendar`, `DateRangePicker`,
|
|
75
|
+
`DateRangeCalendar`, `Autocomplete`, `AutocompleteItem`,
|
|
76
|
+
`AutocompleteItemGroup`, `Fieldset`, `FieldGroup`, `Label`, `FormField`,
|
|
77
|
+
`FormControl`, `FormLabel`, `FormHelperText`, `FormFeedbackText`.
|
|
78
|
+
|
|
79
|
+
### Buttons and actions
|
|
80
|
+
|
|
81
|
+
`Button`, `IconButton`, `CircularIconButton`, `RawLinkButton`, `Actionable`,
|
|
82
|
+
`ActionBar`, `ActionBarRoot`, `ActionBarButton`, `ActionBarIconButton`,
|
|
83
|
+
`FloatingActionBar`, `Anchor`.
|
|
84
|
+
|
|
85
|
+
### Overlays
|
|
86
|
+
|
|
87
|
+
Modal: `Dialog`, `DialogContent`, `DialogTitle`, `DialogActions`,
|
|
88
|
+
`DialogButton`, `PromoDialog`, `PromoDialogHero`, `PromoDialogTitle`,
|
|
89
|
+
`PromoDialogSubtitle`, `PromoDialogContent`, `PromoDialogActions`,
|
|
90
|
+
`SidePanel`, `SidePanelHeader`, `SidePanelContent`, `SidePanelFooter`,
|
|
91
|
+
`BottomSheet`, `BottomSheetHeader`, `BottomSheetContent`,
|
|
92
|
+
`BottomSheetFooter`.
|
|
93
|
+
|
|
94
|
+
Non-modal: `Tooltip`, `DefinitionTooltip`, `Popover`, `Menu`, `MenuTrigger`,
|
|
95
|
+
`MenuContent`, `MenuHeader`, `RawMenuItem`, `MenuSeparator`.
|
|
96
|
+
|
|
97
|
+
### Content and data
|
|
98
|
+
|
|
99
|
+
`Text`, `Icon`, `Pill`, `Badge`, `Alert`, `AlertTitle`, `AlertContent`,
|
|
100
|
+
`AlertActions`, `Avatar`, `AvatarFallback`, `AvatarIcon`, `AvatarImage`,
|
|
101
|
+
`AvatarPair`, `DataTable`, `DataTableRoot`, `DataTableBulkActions`,
|
|
102
|
+
`Table`, `TableBody`, `TableHeader`, `TableColumnHeader`, `TableRow`,
|
|
103
|
+
`TableCell`, `TableEmptyState`, `TablePagination`, `Filter`,
|
|
104
|
+
`FilterToolbar`, `Carousel`, `CarouselHeader`, `CarouselContent`,
|
|
105
|
+
`CarouselSlide`, `CarouselNav`, `Collapsible`, `CollapsibleTitle`,
|
|
106
|
+
`CollapsibleContent`, `Timeline`, `TimelineStep`, `TimelineStepHeader`,
|
|
107
|
+
`TimelineStepDescription`.
|
|
108
|
+
|
|
109
|
+
### Status and loading
|
|
110
|
+
|
|
111
|
+
`Spinner`, `ProgressBar`, `FullPageLoader`, `EmptyState`, `EmptyStateIcon`,
|
|
112
|
+
`EmptyStateContent`, `EmptyStateActions`, `EmptyStateGetStarted`,
|
|
113
|
+
`EmptyStateWaitingForData`, `EmptyStateGoodJob`,
|
|
114
|
+
`EmptyStateUpgradeRequired`, `EmptyStateNoSearchResults`,
|
|
115
|
+
`EmptyStateUseDesktop`, `ErrorState`, `ToastManager`, `toast`.
|
|
116
|
+
|
|
117
|
+
### Semantic and brand
|
|
118
|
+
|
|
119
|
+
`PayFitBrand`, `PayFitPreprod`.
|
|
120
|
+
|
|
121
|
+
## Decision Tree
|
|
122
|
+
|
|
123
|
+
For every UI need, walk these three levels in order. Stop at the first that
|
|
124
|
+
fits.
|
|
125
|
+
|
|
126
|
+
### Level 1 — Use Unity directly
|
|
127
|
+
|
|
128
|
+
If a named export covers the use case, import it. No abstractions over the
|
|
129
|
+
top.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import {
|
|
133
|
+
Button,
|
|
134
|
+
Dialog,
|
|
135
|
+
DialogActions,
|
|
136
|
+
DialogContent,
|
|
137
|
+
Pill,
|
|
138
|
+
} from '@payfit/unity-components'
|
|
139
|
+
|
|
140
|
+
export function ConfirmDelete({
|
|
141
|
+
isOpen,
|
|
142
|
+
onClose,
|
|
143
|
+
}: {
|
|
144
|
+
isOpen: boolean
|
|
145
|
+
onClose: () => void
|
|
146
|
+
}) {
|
|
147
|
+
return (
|
|
148
|
+
<Dialog isOpen={isOpen} onOpenChange={onClose}>
|
|
149
|
+
<DialogContent>
|
|
150
|
+
<Pill color="danger">Destructive</Pill>
|
|
151
|
+
</DialogContent>
|
|
152
|
+
<DialogActions>
|
|
153
|
+
<Button variant="secondary" onPress={onClose}>
|
|
154
|
+
Cancel
|
|
155
|
+
</Button>
|
|
156
|
+
<Button color="danger" onPress={onClose}>
|
|
157
|
+
Delete
|
|
158
|
+
</Button>
|
|
159
|
+
</DialogActions>
|
|
160
|
+
</Dialog>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Level 2 — Fall back to React Aria + uy:\* classes
|
|
166
|
+
|
|
167
|
+
Build your own primitive only when Unity has no equivalent. Compose React
|
|
168
|
+
Aria primitives, style with the `uy:` prefix, and merge classes with
|
|
169
|
+
`uyMerge` / `uyTv`.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { uyTv } from '@payfit/unity-themes'
|
|
173
|
+
import { ToggleButton } from 'react-aria-components'
|
|
174
|
+
|
|
175
|
+
const toggleStyles = uyTv({
|
|
176
|
+
base: 'uy:inline-flex uy:items-center uy:gap-100 uy:rounded-100 uy:px-200 uy:py-100',
|
|
177
|
+
variants: {
|
|
178
|
+
isSelected: {
|
|
179
|
+
true: 'uy:bg-surface-action uy:text-content-on-action',
|
|
180
|
+
false: 'uy:bg-surface-secondary uy:text-content-neutral',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
export function CustomPivot({ label }: { label: string }) {
|
|
186
|
+
return (
|
|
187
|
+
<ToggleButton className={({ isSelected }) => toggleStyles({ isSelected })}>
|
|
188
|
+
{label}
|
|
189
|
+
</ToggleButton>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Level 3 — Midnight (last resort, deprecated)
|
|
195
|
+
|
|
196
|
+
Only when (a) Unity has no equivalent, (b) React Aria + `uy:*` cannot
|
|
197
|
+
realistically rebuild it, and (c) the feature ships against a deadline. Open
|
|
198
|
+
a follow-up to migrate. Per `AGENTS.md`, Midnight is deprecated; never
|
|
199
|
+
import it into a new module.
|
|
200
|
+
|
|
201
|
+
## Commonly Confused Pairs
|
|
202
|
+
|
|
203
|
+
- `Badge` vs `Pill`: `Badge` is a numeric/dot indicator anchored to another
|
|
204
|
+
element (notification count). `Pill` is a standalone label/status chip
|
|
205
|
+
with text content.
|
|
206
|
+
- `Card` vs `SelectableCard...` vs `NavigationCard`: `Card` is the generic
|
|
207
|
+
container. `SelectableCardCheckboxGroup` / `SelectableCardRadioGroup`
|
|
208
|
+
wrap cards as form inputs. `NavigationCard` wraps a card as a router-aware
|
|
209
|
+
link.
|
|
210
|
+
- `Button` vs `IconButton` vs `RawLinkButton`: `Button` for actions with
|
|
211
|
+
text. `IconButton` / `CircularIconButton` for icon-only actions
|
|
212
|
+
(requires `aria-label`). `RawLinkButton` renders as an anchor but styled
|
|
213
|
+
like a button — use when the action navigates.
|
|
214
|
+
- `Menu` vs `Popover`: `Menu` is a list of actionable items keyed by
|
|
215
|
+
keyboard (Enter/Arrow). `Popover` is a free-form floating panel and
|
|
216
|
+
requires a `title`.
|
|
217
|
+
- `Dialog` vs `PromoDialog`: `Dialog` for confirmation / edit flows.
|
|
218
|
+
`PromoDialog` for marketing / onboarding announcements; requires
|
|
219
|
+
`PromoDialogHero`.
|
|
220
|
+
- `Table` vs `DataTable`: `Table` is the layout primitive (header, body,
|
|
221
|
+
rows, cells) with no behavior. `DataTable` wires Tanstack Table for
|
|
222
|
+
sorting, filtering, pagination, virtualization, bulk actions.
|
|
223
|
+
- `ErrorState` vs `Alert`: `ErrorState` is a full-area empty-replacement
|
|
224
|
+
for "this section failed to load." `Alert` is an inline banner that
|
|
225
|
+
coexists with surrounding content.
|
|
226
|
+
|
|
227
|
+
## Icon Source Convention
|
|
228
|
+
|
|
229
|
+
`Icon` takes a typed `src` prop of type `UnityIcon` — a literal union of
|
|
230
|
+
PascalCase names with a `Filled` or `Outlined` suffix (~310 values, from
|
|
231
|
+
`@payfit/unity-icons`). Strings outside that union are a type error. Do
|
|
232
|
+
not cast to `UnityIcon`; the cast bypasses the sprite-id guard and the
|
|
233
|
+
icon silently renders empty.
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
import type { UnityIcon } from '@payfit/unity-icons'
|
|
237
|
+
|
|
238
|
+
import { Icon } from '@payfit/unity-components'
|
|
239
|
+
|
|
240
|
+
const icon: UnityIcon = 'MagnifyingGlassOutlined'
|
|
241
|
+
;<Icon src={icon} size={20} />
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Forms Notice
|
|
245
|
+
|
|
246
|
+
Tanstack Form (`useTanstackUnityForm`) is the only supported form system.
|
|
247
|
+
The React Hook Form path — `useUnityForm` plus the legacy `TextField` /
|
|
248
|
+
`SelectField` / `NumberField` etc. RHF wrappers exported from the same
|
|
249
|
+
index — is deprecated and scheduled for removal after the rebrand. Do not
|
|
250
|
+
author new code with `useUnityForm`. See `unity-tanstack-form`.
|
|
251
|
+
|
|
252
|
+
## Common Mistakes
|
|
253
|
+
|
|
254
|
+
### HIGH Hand-roll component that already exists
|
|
255
|
+
|
|
256
|
+
Wrong:
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
const Tag = ({ children }) => (
|
|
260
|
+
<span className="uy:rounded-full uy:px-200 uy:py-100 uy:bg-surface-primary">
|
|
261
|
+
{children}
|
|
262
|
+
</span>
|
|
263
|
+
)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Correct:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import { Pill } from '@payfit/unity-components'
|
|
270
|
+
|
|
271
|
+
;<Pill>{children}</Pill>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The hand-rolled span re-derives Pill's tokens by guesswork and drifts from
|
|
275
|
+
the design-system source-of-truth on every theme update.
|
|
276
|
+
|
|
277
|
+
Source: libs/shared/unity/components/src/components/pill/Pill.tsx
|
|
278
|
+
|
|
279
|
+
### HIGH Use React Aria primitive directly when Unity wraps it
|
|
280
|
+
|
|
281
|
+
Wrong:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { Button as AriaButton } from 'react-aria-components'
|
|
285
|
+
|
|
286
|
+
;<AriaButton>Click</AriaButton>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Correct:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
import { Button } from '@payfit/unity-components'
|
|
293
|
+
|
|
294
|
+
;<Button variant="primary">Click</Button>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The bare React Aria Button has no Unity theming, intl, or styling
|
|
298
|
+
defaults; you ship an unstyled element with no `uy:*` classes.
|
|
299
|
+
|
|
300
|
+
Source: libs/shared/unity/components/src/components/button/Button.tsx
|
|
301
|
+
|
|
302
|
+
### HIGH Reach for Midnight when Unity has an equivalent
|
|
303
|
+
|
|
304
|
+
Wrong:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
import { Button, Modal } from '@payfit/midnight'
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Correct:
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import { Button, Dialog } from '@payfit/unity-components'
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Midnight is deprecated; new screens that import it cannot match the Unity
|
|
317
|
+
theme tokens and will require a migration pass later anyway.
|
|
318
|
+
|
|
319
|
+
Source: AGENTS.md "Do NOT use (deprecated)"; maintainer interview
|
|
320
|
+
|
|
321
|
+
### MEDIUM Combine Input + FormField when \*Field exists
|
|
322
|
+
|
|
323
|
+
Wrong:
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
<FormField label="Name" error={errors.name}>
|
|
327
|
+
<Input {...register('name')} />
|
|
328
|
+
</FormField>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Correct:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
<form.AppField name="name">
|
|
335
|
+
{field => <field.TextField label="Name" />}
|
|
336
|
+
</form.AppField>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Manual `FormField` + `Input` skips the label-for/aria-describedby wiring,
|
|
340
|
+
the `field.state.meta` error plumbing, and the required-state inference
|
|
341
|
+
that the `*Field` components handle.
|
|
342
|
+
|
|
343
|
+
Source: libs/shared/unity/components/src/components/form-field/FormField.tsx; index.ts:205-223
|
|
344
|
+
|
|
345
|
+
### HIGH Pass an untyped string to Icon src and guess the name
|
|
346
|
+
|
|
347
|
+
Wrong:
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
<Icon src="search" size={20} />
|
|
351
|
+
<Icon src="trash-filled" />
|
|
352
|
+
const name: string = 'trash'
|
|
353
|
+
<Icon src={name as UnityIcon} />
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Correct:
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
import { Icon } from '@payfit/unity-components'
|
|
360
|
+
import type { UnityIcon } from '@payfit/unity-icons'
|
|
361
|
+
<Icon src="MagnifyingGlassOutlined" size={20} />
|
|
362
|
+
<Icon src="TrashFilled" />
|
|
363
|
+
type Props = { icon: UnityIcon }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The `UnityIcon` literal union encodes the exact sprite ids; lowercase or
|
|
367
|
+
kebab-case strings have no matching `<symbol id>` in the injected sprite,
|
|
368
|
+
so `<use href="#search">` resolves to nothing and the SVG renders empty.
|
|
369
|
+
|
|
370
|
+
Source: libs/shared/unity/icons/src/components/icon/parts/IconSprite.tsx; generated/index.ts (UnityIcon type); maintainer interview
|
|
371
|
+
|
|
372
|
+
## See also
|
|
373
|
+
|
|
374
|
+
- `unity-setup-feature-plugin` — required before any Unity import will work
|
|
375
|
+
- `unity-migrate-from-midnight` — when Level 3 fallback hits a Midnight
|
|
376
|
+
screen, follow this skill to replace it
|
|
377
|
+
- `unity-tanstack-form` — the only supported form authoring path
|