@payfit/unity-components 2.35.4 → 2.35.5
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/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,352 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-overlays
|
|
3
|
+
description: >
|
|
4
|
+
Use the right overlay in @payfit/unity-components. Modal (Dialog,
|
|
5
|
+
SidePanel, BottomSheet, PromoDialog) — focus trap, scroll lock, backdrop,
|
|
6
|
+
Escape closes. Non-modal (Tooltip, Popover, DefinitionTooltip, Menu) —
|
|
7
|
+
no focus trap, can coexist with page. Unity ENFORCES "no tooltip on
|
|
8
|
+
disabled control" at the library level (a11y rule that Midnight allowed).
|
|
9
|
+
PromoDialog asserts PromoDialogHero; Popover requires title; Tooltip
|
|
10
|
+
children must accept ref.
|
|
11
|
+
type: core
|
|
12
|
+
library: '@payfit/unity-components'
|
|
13
|
+
library_version: '2.x'
|
|
14
|
+
sources:
|
|
15
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/dialog/Dialog.tsx'
|
|
16
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/side-panel/SidePanel.tsx'
|
|
17
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/tooltip/Tooltip.tsx'
|
|
18
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/popover/Popover.tsx'
|
|
19
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/menu/Menu.tsx'
|
|
20
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/promo-dialog/PromoDialog.tsx'
|
|
21
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/bottom-sheet/BottomSheet.tsx'
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
Overlays do not require a global provider. Each overlay owns its open state
|
|
27
|
+
through its own trigger (`DialogTrigger`, `PopoverTrigger`, `MenuTrigger`,
|
|
28
|
+
`TooltipTrigger`) or via the controlled `isOpen` / `onOpenChange` pair. Mount
|
|
29
|
+
the trigger and the overlay side-by-side as siblings — `DialogTrigger` reads
|
|
30
|
+
exactly two children: the trigger element and the overlay.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import {
|
|
34
|
+
Button,
|
|
35
|
+
Dialog,
|
|
36
|
+
DialogActions,
|
|
37
|
+
DialogButton,
|
|
38
|
+
DialogContent,
|
|
39
|
+
DialogTitle,
|
|
40
|
+
DialogTrigger,
|
|
41
|
+
} from '@payfit/unity-components'
|
|
42
|
+
|
|
43
|
+
export function ConfirmDelete({ onConfirm }: { onConfirm: () => void }) {
|
|
44
|
+
return (
|
|
45
|
+
<DialogTrigger>
|
|
46
|
+
<Button variant="danger">Delete</Button>
|
|
47
|
+
<Dialog>
|
|
48
|
+
<DialogTitle>Delete employee?</DialogTitle>
|
|
49
|
+
<DialogContent>This cannot be undone.</DialogContent>
|
|
50
|
+
<DialogActions>
|
|
51
|
+
<DialogButton variant="close">Cancel</DialogButton>
|
|
52
|
+
<DialogButton variant="danger" onPress={onConfirm}>
|
|
53
|
+
Delete
|
|
54
|
+
</DialogButton>
|
|
55
|
+
</DialogActions>
|
|
56
|
+
</Dialog>
|
|
57
|
+
</DialogTrigger>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Modal vs non-modal — picking the right one
|
|
63
|
+
|
|
64
|
+
| Need | Component | Reason |
|
|
65
|
+
| ------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------ |
|
|
66
|
+
| Confirmation, blocking form, decision required | `Dialog` | modal: focus trap, scroll lock, backdrop, Escape closes |
|
|
67
|
+
| Detail view that should not interrupt page flow | `SidePanel` | modal but side-anchored; same focus trap and Escape behavior |
|
|
68
|
+
| Mobile-first sheet, edit on small screens | `BottomSheet` | modal anchored to viewport bottom |
|
|
69
|
+
| Feature announcement with hero illustration | `PromoDialog` | modal with required `PromoDialogHero` slot |
|
|
70
|
+
| Hover/focus hint on an enabled control | `Tooltip` | non-interactive, hover/focus activated, single string |
|
|
71
|
+
| Interactive informational content (links, fields) | `Popover` | non-modal, focusable content, requires `title` |
|
|
72
|
+
| Dropdown list of actions | `Menu` (+ `MenuTrigger`/`MenuContent`/`RawMenuItem`) | non-modal, keyboard-navigable list |
|
|
73
|
+
| Inline term definition | `DefinitionTooltip` | non-modal, inline anchor |
|
|
74
|
+
|
|
75
|
+
Modal overlays all build on `react-aria-components`' `ModalOverlay` — they
|
|
76
|
+
manage focus, restoration, scroll lock, and Escape automatically.
|
|
77
|
+
|
|
78
|
+
## Core Patterns
|
|
79
|
+
|
|
80
|
+
### Dialog with DialogTrigger + DialogActions
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import {
|
|
84
|
+
Button,
|
|
85
|
+
Dialog,
|
|
86
|
+
DialogActions,
|
|
87
|
+
DialogButton,
|
|
88
|
+
DialogContent,
|
|
89
|
+
DialogTitle,
|
|
90
|
+
DialogTrigger,
|
|
91
|
+
} from '@payfit/unity-components'
|
|
92
|
+
|
|
93
|
+
export function ArchiveDialog({ onArchive }: { onArchive: () => void }) {
|
|
94
|
+
return (
|
|
95
|
+
<DialogTrigger>
|
|
96
|
+
<Button>Archive</Button>
|
|
97
|
+
<Dialog size="md">
|
|
98
|
+
<DialogTitle>Archive this employee?</DialogTitle>
|
|
99
|
+
<DialogContent>
|
|
100
|
+
They will no longer appear in active lists. You can restore them
|
|
101
|
+
later.
|
|
102
|
+
</DialogContent>
|
|
103
|
+
<DialogActions>
|
|
104
|
+
<DialogButton variant="close">Cancel</DialogButton>
|
|
105
|
+
<DialogButton variant="confirm" onPress={onArchive}>
|
|
106
|
+
Archive
|
|
107
|
+
</DialogButton>
|
|
108
|
+
</DialogActions>
|
|
109
|
+
</Dialog>
|
|
110
|
+
</DialogTrigger>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### SidePanel for detail views
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { useState } from 'react'
|
|
119
|
+
|
|
120
|
+
import {
|
|
121
|
+
Button,
|
|
122
|
+
SidePanel,
|
|
123
|
+
SidePanelContent,
|
|
124
|
+
SidePanelFooter,
|
|
125
|
+
SidePanelHeader,
|
|
126
|
+
} from '@payfit/unity-components'
|
|
127
|
+
|
|
128
|
+
export function EmployeeDetailPanel({ employee }: { employee: Employee }) {
|
|
129
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<>
|
|
133
|
+
<Button onPress={() => setIsOpen(true)}>View details</Button>
|
|
134
|
+
<SidePanel isOpen={isOpen} onOpenChange={setIsOpen}>
|
|
135
|
+
<SidePanelHeader>{employee.name}</SidePanelHeader>
|
|
136
|
+
<SidePanelContent>
|
|
137
|
+
<p>{employee.position}</p>
|
|
138
|
+
<p>{employee.team}</p>
|
|
139
|
+
</SidePanelContent>
|
|
140
|
+
<SidePanelFooter>
|
|
141
|
+
<Button variant="secondary" onPress={() => setIsOpen(false)}>
|
|
142
|
+
Close
|
|
143
|
+
</Button>
|
|
144
|
+
</SidePanelFooter>
|
|
145
|
+
</SidePanel>
|
|
146
|
+
</>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Popover for interactive informational content
|
|
152
|
+
|
|
153
|
+
`title` is required (use `isTitleSrOnly` if you don't want it visible).
|
|
154
|
+
Children may be focusable; the Popover is non-modal so the page stays
|
|
155
|
+
interactive behind it.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { Button, Popover, PopoverTrigger } from '@payfit/unity-components'
|
|
159
|
+
|
|
160
|
+
export function HelpPopover() {
|
|
161
|
+
return (
|
|
162
|
+
<PopoverTrigger>
|
|
163
|
+
<Button variant="tertiary">What is a working agreement?</Button>
|
|
164
|
+
<Popover title="Working agreement">
|
|
165
|
+
<p>
|
|
166
|
+
A working agreement defines when, where and how an employee works. See
|
|
167
|
+
the <a href="/handbook/working-agreement">handbook</a> for details.
|
|
168
|
+
</p>
|
|
169
|
+
</Popover>
|
|
170
|
+
</PopoverTrigger>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Menu for action lists
|
|
176
|
+
|
|
177
|
+
`Menu` expects exactly two children: a trigger and a content block. Use
|
|
178
|
+
`MenuTrigger` / `MenuContent` / `RawMenuItem` for the standard composition.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import {
|
|
182
|
+
IconButton,
|
|
183
|
+
Menu,
|
|
184
|
+
MenuContent,
|
|
185
|
+
MenuTrigger,
|
|
186
|
+
RawMenuItem,
|
|
187
|
+
} from '@payfit/unity-components'
|
|
188
|
+
|
|
189
|
+
export function RowActions({
|
|
190
|
+
onEdit,
|
|
191
|
+
onArchive,
|
|
192
|
+
}: {
|
|
193
|
+
onEdit: () => void
|
|
194
|
+
onArchive: () => void
|
|
195
|
+
}) {
|
|
196
|
+
return (
|
|
197
|
+
<Menu>
|
|
198
|
+
<MenuTrigger>
|
|
199
|
+
<IconButton icon="MoreFilled" title="Actions" />
|
|
200
|
+
</MenuTrigger>
|
|
201
|
+
<MenuContent>
|
|
202
|
+
<RawMenuItem onAction={onEdit}>Edit</RawMenuItem>
|
|
203
|
+
<RawMenuItem onAction={onArchive}>Archive</RawMenuItem>
|
|
204
|
+
</MenuContent>
|
|
205
|
+
</Menu>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Common Mistakes
|
|
211
|
+
|
|
212
|
+
### CRITICAL Wrap a disabled control in Tooltip
|
|
213
|
+
|
|
214
|
+
Wrong:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
<Tooltip title="Disabled because …">
|
|
218
|
+
<Button isDisabled>Submit</Button>
|
|
219
|
+
</Tooltip>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Correct:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
<Button isDisabled>Submit</Button>
|
|
226
|
+
<Text variant="bodySmall" color="content.neutral.low">Disabled because …</Text>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Unity enforces the WCAG rule that disabled controls must not carry tooltips (the disabled element is not keyboard-focusable, so the tooltip is unreachable). This is enforced at the library level, not just policy.
|
|
230
|
+
|
|
231
|
+
Source: components/tooltip/Tooltip.tsx; maintainer interview (enforced by Unity)
|
|
232
|
+
|
|
233
|
+
### MEDIUM Use Dialog for a non-blocking hint
|
|
234
|
+
|
|
235
|
+
Wrong:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
<DialogTrigger>
|
|
239
|
+
<Button>Info</Button>
|
|
240
|
+
<Dialog>
|
|
241
|
+
<DialogContent>FYI…</DialogContent>
|
|
242
|
+
</Dialog>
|
|
243
|
+
</DialogTrigger>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Correct:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<PopoverTrigger>
|
|
250
|
+
<Button>Info</Button>
|
|
251
|
+
<Popover title="Info">FYI…</Popover>
|
|
252
|
+
</PopoverTrigger>
|
|
253
|
+
// or, for hover-only:
|
|
254
|
+
<Tooltip title="FYI…"><Button>Info</Button></Tooltip>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Dialog is modal (focus trap, scroll lock, backdrop) — heavy for a simple informational popover.
|
|
258
|
+
|
|
259
|
+
Source: components/dialog/Dialog.tsx:162-196 (ModalOverlay)
|
|
260
|
+
|
|
261
|
+
### MEDIUM Use Tooltip for interactive content
|
|
262
|
+
|
|
263
|
+
Wrong:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
<Tooltip
|
|
267
|
+
title={
|
|
268
|
+
<>
|
|
269
|
+
<Button>Delete</Button>
|
|
270
|
+
<Button>Edit</Button>
|
|
271
|
+
</>
|
|
272
|
+
}
|
|
273
|
+
>
|
|
274
|
+
<IconButton icon={MoreFilled} />
|
|
275
|
+
</Tooltip>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Correct:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
<Menu>
|
|
282
|
+
<MenuTrigger>
|
|
283
|
+
<IconButton icon={MoreFilled} />
|
|
284
|
+
</MenuTrigger>
|
|
285
|
+
<MenuContent>
|
|
286
|
+
<RawMenuItem>Delete</RawMenuItem>
|
|
287
|
+
<RawMenuItem>Edit</RawMenuItem>
|
|
288
|
+
</MenuContent>
|
|
289
|
+
</Menu>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Tooltip is display-only; placing interactive elements inside breaks focus and event handling.
|
|
293
|
+
|
|
294
|
+
Source: components/tooltip/Tooltip.tsx (no interactive support); components/menu/Menu.tsx
|
|
295
|
+
|
|
296
|
+
### MEDIUM Fight the modal focus trap with custom useEffect focus moves
|
|
297
|
+
|
|
298
|
+
Wrong:
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
useEffect(() => {
|
|
302
|
+
if (open) triggerRef.current?.focus()
|
|
303
|
+
}, [open])
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Correct:
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
// Let Dialog manage focus; use autoFocus on a specific element inside if needed:
|
|
310
|
+
<Dialog isOpen={open} onOpenChange={setOpen}>
|
|
311
|
+
<input autoFocus />
|
|
312
|
+
</Dialog>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Dialog manages focus and restoration via React Aria. Calling triggerRef.focus() inside a useEffect produces unpredictable jumps.
|
|
316
|
+
|
|
317
|
+
Source: components/dialog/Dialog.tsx:162-196
|
|
318
|
+
|
|
319
|
+
### MEDIUM PromoDialog without PromoDialogHero
|
|
320
|
+
|
|
321
|
+
Wrong:
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
<PromoDialog>
|
|
325
|
+
<PromoDialogTitle>Feature</PromoDialogTitle>
|
|
326
|
+
<PromoDialogContent>…</PromoDialogContent>
|
|
327
|
+
</PromoDialog>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Correct:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
<PromoDialog>
|
|
334
|
+
<PromoDialogHero>
|
|
335
|
+
<Illustration src={MyIllustration} />
|
|
336
|
+
</PromoDialogHero>
|
|
337
|
+
<PromoDialogTitle>Feature</PromoDialogTitle>
|
|
338
|
+
<PromoDialogContent>…</PromoDialogContent>
|
|
339
|
+
</PromoDialog>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
PromoDialog validates at render and console.errors if the hero is missing; dialog still renders but the layout breaks.
|
|
343
|
+
|
|
344
|
+
Source: components/promo-dialog/PromoDialog.tsx:230-236 (console.error guard)
|
|
345
|
+
|
|
346
|
+
## See also
|
|
347
|
+
|
|
348
|
+
- `unity-migrate-from-midnight` — the disabled+tooltip trap is the most
|
|
349
|
+
common Midnight-era pattern that Unity blocks; the migration skill covers
|
|
350
|
+
the rewrite.
|
|
351
|
+
- `unity-layout-and-styling` — overlay content uses the same `uy:` utility
|
|
352
|
+
classes (Flex/Grid, spacing, `uy:data-[hovered=true]:…`).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-setup-feature-plugin
|
|
3
|
+
description: >
|
|
4
|
+
Wire Unity into a feature plugin in the hr-apps monorepo. Run the
|
|
5
|
+
setup-unity nx generator (installs packages, configures TailwindCSS 4,
|
|
6
|
+
sets up TS types, adds providers). Provider order at app root: IntlProvider
|
|
7
|
+
→ I18nProvider (react-aria-components) → UnityIconsProvider. Import
|
|
8
|
+
@payfit/unity-themes/css/unity.css once. Merge @payfit/unity-components/i18n
|
|
9
|
+
JSON into the IntlProvider messages, keyed by locale.
|
|
10
|
+
type: lifecycle
|
|
11
|
+
library: '@payfit/unity-components'
|
|
12
|
+
library_version: '2.x'
|
|
13
|
+
sources:
|
|
14
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/docs/tutorials/Getting Started.mdx'
|
|
15
|
+
- 'PayFit/hr-apps:libs/shared/unity/icons/src/components/icons-provider/UnityIconsProvider.tsx'
|
|
16
|
+
- 'PayFit/hr-apps:libs/shared/unity/themes/docs/files/MIGRATION-v1.md'
|
|
17
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/i18n/'
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Bootstrap Unity for a feature plugin once. After this skill completes you
|
|
21
|
+
can import any export from `@payfit/unity-components` without further
|
|
22
|
+
wiring.
|
|
23
|
+
|
|
24
|
+
## If in hr-apps, use this generator
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm nx g @payfit/nx-tools:setup-unity \
|
|
28
|
+
--project=<YOUR_PROJECT> \
|
|
29
|
+
--withIcons --withIllustrations
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The generator installs `@payfit/unity-components`, `@payfit/unity-themes`,
|
|
33
|
+
`@payfit/unity-icons`, and (with `--withIllustrations`)
|
|
34
|
+
`@payfit/unity-illustrations` from the workspace catalog; wires the
|
|
35
|
+
TailwindCSS 4 plugin with the Unity theme preset; adds the
|
|
36
|
+
`@payfit/unity-themes` reference to the project `tsconfig`; and
|
|
37
|
+
mounts the providers in the correct order at the project entry. Do not
|
|
38
|
+
hand-edit those files afterwards — re-run the generator.
|
|
39
|
+
|
|
40
|
+
Companion generators:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Component dev sandbox
|
|
44
|
+
pnpm nx g @payfit/nx-tools:sandbox
|
|
45
|
+
|
|
46
|
+
# Storybook for the project
|
|
47
|
+
pnpm nx g @payfit/nx-tools:storybook
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Setup (manual fallback)
|
|
51
|
+
|
|
52
|
+
Use only when the generator cannot run (e.g. setting up a sample app
|
|
53
|
+
outside the monorepo).
|
|
54
|
+
|
|
55
|
+
All details in guide: libs/shared/unity/components/src/docs/tutorials/Getting Started.mdx
|