@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.
@@ -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