@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,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-layout-and-styling
|
|
3
|
+
description: >
|
|
4
|
+
Compose layouts and apply styles in @payfit/unity-components. Flex (1D)
|
|
5
|
+
and Grid (12-col 2D). Every utility class MUST use the uy: prefix
|
|
6
|
+
(uy:flex, uy:gap-100). Modifier order is uy:md:gap-200, uy:hover:bg-…,
|
|
7
|
+
uy:data-[hovered=true]:bg-… (preferred when components expose state
|
|
8
|
+
attributes). Merge classes with uyMerge, build variants with uyTv,
|
|
9
|
+
conditional class strings with cn/classNames/clsx — all from
|
|
10
|
+
@payfit/unity-themes. Use <Text variant> for typography. Verify token
|
|
11
|
+
names exist in the @theme block; do not hallucinate (uy:bg-primary-500
|
|
12
|
+
is NOT a Unity token).
|
|
13
|
+
type: core
|
|
14
|
+
library: '@payfit/unity-components, @payfit/unity-themes'
|
|
15
|
+
library_version: '2.x'
|
|
16
|
+
sources:
|
|
17
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/flex/Flex.tsx'
|
|
18
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/grid/Grid.tsx'
|
|
19
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/text/Text.tsx'
|
|
20
|
+
- 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/tailwind-merge.ts'
|
|
21
|
+
- 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/tailwind-variants.ts'
|
|
22
|
+
- 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/cn.ts'
|
|
23
|
+
- 'PayFit/hr-apps:libs/shared/unity/themes/src/scripts/build.ts'
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
Layout primitives, the `uy:` utility-class system, and the variant/merge tools
|
|
27
|
+
used inside `@payfit/unity-components`.
|
|
28
|
+
|
|
29
|
+
## Setup
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { Card, Flex, Grid, Text } from '@payfit/unity-components'
|
|
33
|
+
|
|
34
|
+
export function PayslipSummary() {
|
|
35
|
+
return (
|
|
36
|
+
<Card>
|
|
37
|
+
<Flex direction="col" gap="200" className="uy:p-300">
|
|
38
|
+
<Text variant="h3" asElement="h2">
|
|
39
|
+
Payslip
|
|
40
|
+
</Text>
|
|
41
|
+
<Grid cols={12} className="uy:gap-200 uy:md:gap-300">
|
|
42
|
+
<Flex direction="col" className="uy:col-span-12 uy:md:col-span-6">
|
|
43
|
+
<Text variant="overline">Gross</Text>
|
|
44
|
+
<Text variant="bodyLargeStrong">€ 4,200.00</Text>
|
|
45
|
+
</Flex>
|
|
46
|
+
<Flex direction="col" className="uy:col-span-12 uy:md:col-span-6">
|
|
47
|
+
<Text variant="overline">Net</Text>
|
|
48
|
+
<Text variant="bodyLargeStrong">€ 3,150.00</Text>
|
|
49
|
+
</Flex>
|
|
50
|
+
</Grid>
|
|
51
|
+
</Flex>
|
|
52
|
+
</Card>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Core Patterns
|
|
58
|
+
|
|
59
|
+
### Flex for 1D, Grid for 2D
|
|
60
|
+
|
|
61
|
+
`Flex` is for one-dimensional rows/columns; `Grid` is for the 12-column (or 6-column)
|
|
62
|
+
two-dimensional layout. Each exposes layout props; everything else goes via
|
|
63
|
+
`className` with `uy:` utilities.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Flex, FlexItem, Grid, GridItem } from '@payfit/unity-components'
|
|
67
|
+
|
|
68
|
+
// Flex props: asElement, inline, direction, isReversed, wrap,
|
|
69
|
+
// gap, gapX, gapY, justify, align, alignContent, className
|
|
70
|
+
<Flex direction="row" gap="200" justify="between" align="center">
|
|
71
|
+
<FlexItem grow="1">Left</FlexItem>
|
|
72
|
+
<FlexItem>Right</FlexItem>
|
|
73
|
+
</Flex>
|
|
74
|
+
|
|
75
|
+
// Grid props: asElement, inline, cols (6 | 12), rows, areas, flow,
|
|
76
|
+
// justifyItems, alignItems, className
|
|
77
|
+
// GridItem positions via colSpan/colStart/colEnd OR area (mutually exclusive)
|
|
78
|
+
<Grid cols={12} className="uy:gap-200">
|
|
79
|
+
<GridItem colSpan={8}>Main</GridItem>
|
|
80
|
+
<GridItem colSpan={4}>Aside</GridItem>
|
|
81
|
+
</Grid>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Responsive classes via uy:md:
|
|
85
|
+
|
|
86
|
+
There is no responsive prop-object API. Responsive behavior is driven by
|
|
87
|
+
TailwindCSS v4 modifiers on `className`.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Flex gap="100" className="uy:md:gap-200 uy:lg:gap-300">
|
|
91
|
+
<span>Item</span>
|
|
92
|
+
</Flex>
|
|
93
|
+
|
|
94
|
+
<Grid cols={12} className="uy:grid-cols-1 uy:md:grid-cols-2 uy:lg:grid-cols-3" />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Variants with uyTv
|
|
98
|
+
|
|
99
|
+
`uyTv` from `@payfit/unity-themes` is the pre-configured tailwind-variants
|
|
100
|
+
factory. It applies the Unity `twMergeConfig` so variant collisions resolve
|
|
101
|
+
against Unity tokens. Export the variant function and derive its typed props
|
|
102
|
+
with `VariantProps`.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import type { VariantProps } from '@payfit/unity-themes'
|
|
106
|
+
|
|
107
|
+
import { uyTv } from '@payfit/unity-themes'
|
|
108
|
+
|
|
109
|
+
export const callout = uyTv({
|
|
110
|
+
base: 'uy:inline-flex uy:items-center uy:gap-100 uy:rounded-100 uy:px-200 uy:py-100',
|
|
111
|
+
variants: {
|
|
112
|
+
intent: {
|
|
113
|
+
info: 'uy:bg-surface-primary-default uy:text-content-inverted-default',
|
|
114
|
+
danger: 'uy:bg-surface-danger-default uy:text-content-inverted-default',
|
|
115
|
+
neutral: 'uy:bg-surface-neutral-default uy:text-content-neutral-default',
|
|
116
|
+
},
|
|
117
|
+
size: {
|
|
118
|
+
sm: 'uy:typography-body-small',
|
|
119
|
+
md: 'uy:typography-body',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
defaultVariants: { intent: 'info', size: 'md' },
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
export type CalloutVariantProps = VariantProps<typeof callout>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Class merging with uyMerge
|
|
129
|
+
|
|
130
|
+
`uyMerge` is `tailwind-merge` configured with Unity's class groups. Use it
|
|
131
|
+
whenever an external `className` may collide with internal classes.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { uyMerge } from '@payfit/unity-themes'
|
|
135
|
+
|
|
136
|
+
uyMerge('uy:p-100', 'uy:p-200') // → 'uy:p-200'
|
|
137
|
+
uyMerge('uy:bg-surface-primary-default', 'uy:bg-surface-danger-default')
|
|
138
|
+
// → 'uy:bg-surface-danger-default'
|
|
139
|
+
uyMerge('uy:p-100', 'uy:px-200', 'uy:py-300') // p, px, py don't collide
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Conditional classes with cn / classNames / clsx
|
|
143
|
+
|
|
144
|
+
`cn`, `classNames`, and `clsx` are aliases for the same Unity-configured helper
|
|
145
|
+
in `@payfit/unity-themes`. Use them for ad-hoc conditional strings — not for
|
|
146
|
+
component-scoped variant APIs.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { cn } from '@payfit/unity-themes'
|
|
150
|
+
|
|
151
|
+
function Row({
|
|
152
|
+
isActive,
|
|
153
|
+
className,
|
|
154
|
+
}: {
|
|
155
|
+
isActive: boolean
|
|
156
|
+
className?: string
|
|
157
|
+
}) {
|
|
158
|
+
return (
|
|
159
|
+
<div
|
|
160
|
+
className={cn(
|
|
161
|
+
'uy:flex uy:items-center uy:px-200 uy:py-100',
|
|
162
|
+
isActive && 'uy:bg-surface-primary-default',
|
|
163
|
+
className,
|
|
164
|
+
)}
|
|
165
|
+
/>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Typography with `<Text>`
|
|
171
|
+
|
|
172
|
+
Use `<Text variant=...>` instead of a `<div>` + typography class. `Text`
|
|
173
|
+
picks a semantic element from the variant (e.g. `variant="h1"` → `<h1>`),
|
|
174
|
+
applies the typography variant, and exposes `color`, `isTruncated`,
|
|
175
|
+
`lineClamp`, and `maxWidthCh`.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { Text } from '@payfit/unity-components'
|
|
179
|
+
|
|
180
|
+
<Text variant="h1" color="content.primary">Title</Text>
|
|
181
|
+
<Text variant="body" color="content.neutral">Description</Text>
|
|
182
|
+
// Override the semantic element when needed:
|
|
183
|
+
<Text variant="h1" asElement="h2">Visual h1, semantic h2</Text>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Data-attribute pseudo-states
|
|
187
|
+
|
|
188
|
+
Several Unity components expose internal state via `data-hovered`,
|
|
189
|
+
`data-pressed`, `data-focus-visible`, etc. Target the component-owned state
|
|
190
|
+
attribute rather than the native CSS pseudo-state.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { ListViewItem } from '@payfit/unity-components'
|
|
194
|
+
|
|
195
|
+
;<ListViewItem className="uy:data-[hovered=true]:bg-surface-primary-hover" />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Common Mistakes
|
|
199
|
+
|
|
200
|
+
### CRITICAL Use bare Tailwind classes without uy: prefix
|
|
201
|
+
|
|
202
|
+
Wrong:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<Flex className="flex gap-4 p-3"> … </Flex>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Correct:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<Flex gap="100" className="uy:p-300">
|
|
212
|
+
{' '}
|
|
213
|
+
…{' '}
|
|
214
|
+
</Flex>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Unity CSS is built with `prefix(uy)`; bare classes are not in the compiled stylesheet and produce no styling.
|
|
218
|
+
|
|
219
|
+
Source: themes/src/scripts/build.ts:298 (prefix(uy) import)
|
|
220
|
+
|
|
221
|
+
### HIGH Pass responsive prop objects (v0.x style)
|
|
222
|
+
|
|
223
|
+
Wrong:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<Flex gap={{ initial: '100', md: '200' }} />
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Correct:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
<Flex gap="100" className="uy:md:gap-200" />
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
v1.x removed the responsive prop-object API. Use className with uy:md: et al.
|
|
236
|
+
|
|
237
|
+
Source: components/flex/Flex.tsx:33; themes/docs/files/MIGRATION-v1.md
|
|
238
|
+
|
|
239
|
+
### HIGH Import twMerge from tailwind-merge directly
|
|
240
|
+
|
|
241
|
+
Wrong:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
import { twMerge } from 'tailwind-merge'
|
|
245
|
+
|
|
246
|
+
twMerge('uy:p-100', 'uy:p-200')
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Correct:
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
import { uyMerge } from '@payfit/unity-themes'
|
|
253
|
+
|
|
254
|
+
uyMerge('uy:p-100', 'uy:p-200') // → 'uy:p-200'
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The unconfigured twMerge has no knowledge of Unity tokens; class conflicts on uy:bg-surface-primary-default vs uy:bg-surface-danger-default are not resolved.
|
|
258
|
+
|
|
259
|
+
Source: themes/src/utils/tailwind-merge.ts:1-7,75-77
|
|
260
|
+
|
|
261
|
+
### HIGH Import tv from tailwind-variants directly
|
|
262
|
+
|
|
263
|
+
Wrong:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { tv } from 'tailwind-variants'
|
|
267
|
+
export const button = tv({ base: 'uy:px-200', variants: {...} })
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Correct:
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import { uyTv } from '@payfit/unity-themes'
|
|
274
|
+
export const button = uyTv({ base: 'uy:px-200', variants: {...} })
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
tv() is unconfigured; uyTv pre-applies the Unity twMergeConfig so variant conflict resolution understands Unity tokens.
|
|
278
|
+
|
|
279
|
+
Source: themes/src/utils/tailwind-variants.ts:48-51
|
|
280
|
+
|
|
281
|
+
### MEDIUM Use <div> + typography class instead of <Text>
|
|
282
|
+
|
|
283
|
+
Wrong:
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
<div className="uy:typography-h1 uy:text-content-primary">Title</div>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Correct:
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
<Text variant="h1" color="content.primary">
|
|
293
|
+
Title
|
|
294
|
+
</Text>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`<Text variant="h1">` auto-selects the correct semantic element (h1) and applies the Unity typography variant; `<div>` loses the semantics and the variant API.
|
|
298
|
+
|
|
299
|
+
Source: components/text/Text.tsx:60-104,137-139
|
|
300
|
+
|
|
301
|
+
### MEDIUM Use uy:hover: when component exposes data-\* state
|
|
302
|
+
|
|
303
|
+
Wrong:
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
<ListViewItem className="uy:hover:bg-surface-primary-hover" />
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Correct:
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
<ListViewItem className="uy:data-[hovered=true]:bg-surface-primary-hover" />
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Some Unity components manage state via data-hovered, data-selected, etc. `uy:data-[hovered=true]:` targets the component-managed state and avoids drift.
|
|
316
|
+
|
|
317
|
+
Source: themes/src/scripts/build.ts:303-307 (custom-variant for data attrs)
|
|
318
|
+
|
|
319
|
+
### HIGH Hallucinate token names that look plausible but do not exist
|
|
320
|
+
|
|
321
|
+
Wrong:
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
<div className="uy:bg-primary-500 uy:text-gray-900 uy:border-blue-600" />
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Correct:
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
// Use Unity's semantic token names (verify against the live class index
|
|
331
|
+
// in themes docs or the @theme block in dist/css/unity.css):
|
|
332
|
+
<div className="uy:bg-surface-primary-default uy:text-content-primary uy:border-surface-primary-active" />
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Agents generate names that match standard Tailwind conventions but are not in the Unity token set; the class is absent from the compiled stylesheet, the element silently renders with no style.
|
|
336
|
+
|
|
337
|
+
Source: maintainer interview; themes/dist/css/unity.css (@theme block enumerates valid tokens)
|
|
338
|
+
|
|
339
|
+
### MEDIUM Reach for cn() to compose variant classes when uyTv fits
|
|
340
|
+
|
|
341
|
+
Wrong:
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import { cn } from '@payfit/unity-themes'
|
|
345
|
+
|
|
346
|
+
function Pill({
|
|
347
|
+
size,
|
|
348
|
+
color,
|
|
349
|
+
}: {
|
|
350
|
+
size: 'sm' | 'lg'
|
|
351
|
+
color: 'primary' | 'danger'
|
|
352
|
+
}) {
|
|
353
|
+
return (
|
|
354
|
+
<span
|
|
355
|
+
className={cn(
|
|
356
|
+
'uy:inline-flex uy:items-center',
|
|
357
|
+
size === 'sm' && 'uy:px-100 uy:text-xs',
|
|
358
|
+
size === 'lg' && 'uy:px-200 uy:text-sm',
|
|
359
|
+
color === 'primary' && 'uy:bg-surface-primary-default',
|
|
360
|
+
color === 'danger' && 'uy:bg-surface-danger-default',
|
|
361
|
+
)}
|
|
362
|
+
/>
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Correct:
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
import type { VariantProps } from '@payfit/unity-themes'
|
|
371
|
+
|
|
372
|
+
import { uyTv } from '@payfit/unity-themes'
|
|
373
|
+
|
|
374
|
+
const pill = uyTv({
|
|
375
|
+
base: 'uy:inline-flex uy:items-center',
|
|
376
|
+
variants: {
|
|
377
|
+
size: { sm: 'uy:px-100 uy:text-xs', lg: 'uy:px-200 uy:text-sm' },
|
|
378
|
+
color: {
|
|
379
|
+
primary: 'uy:bg-surface-primary-default',
|
|
380
|
+
danger: 'uy:bg-surface-danger-default',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
})
|
|
384
|
+
type PillProps = VariantProps<typeof pill>
|
|
385
|
+
function Pill(props: PillProps) {
|
|
386
|
+
return <span className={pill(props)} />
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
cn() / classNames / clsx are for ad-hoc conditional class strings; component-scoped variant APIs with multiple axes (size × color × intent) belong in `uyTv`, which gives a typed `VariantProps` signature and pre-applied conflict resolution.
|
|
391
|
+
|
|
392
|
+
Source: maintainer interview; themes/src/utils/tailwind-variants.ts
|
|
393
|
+
|
|
394
|
+
## See also
|
|
395
|
+
|
|
396
|
+
- `unity-find-component` — the decision tree's "React Aria + uy: classes" branch
|
|
397
|
+
when no Unity component fits.
|
|
398
|
+
- `unity-contribute-component` — `uyTv` is the contributor's variant tool.
|
|
399
|
+
- `unity-themes-tokens-and-docs` — token discipline if you need a new token
|
|
400
|
+
rather than reusing an existing one.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unity-migrate-from-midnight
|
|
3
|
+
description: >
|
|
4
|
+
Migrate a legacy screen from @payfit/midnight to Unity. Component
|
|
5
|
+
equivalency map (Button, Box→Flex, Modal→Dialog, Link→RawLink,
|
|
6
|
+
Heading→Text). Prop renames (visual→color; onClick→onPress).
|
|
7
|
+
A11y deltas Unity enforces (tooltip-on-disabled-control is rejected at
|
|
8
|
+
the library level). RHF was used in Midnight-era code; replace with
|
|
9
|
+
Tanstack Form (see unity-tanstack-form). Full ~85-row component map
|
|
10
|
+
in references/midnight-component-map.md.
|
|
11
|
+
type: lifecycle
|
|
12
|
+
library: '@payfit/unity-components'
|
|
13
|
+
library_version: '2.x'
|
|
14
|
+
sources:
|
|
15
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/tooltip/Tooltip.tsx'
|
|
16
|
+
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/button/Button.tsx'
|
|
17
|
+
- 'PayFit/hr-apps:AGENTS.md'
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Convert a legacy `@payfit/midnight` screen to Unity. The table below
|
|
21
|
+
covers the highest-traffic mappings; the exhaustive ~85-row map lives
|
|
22
|
+
in `references/midnight-component-map.md`. When a Midnight component is
|
|
23
|
+
not in either, fall through to `unity-find-component`.
|
|
24
|
+
|
|
25
|
+
## Equivalency Map (high-traffic)
|
|
26
|
+
|
|
27
|
+
| Midnight | Unity | Notes |
|
|
28
|
+
| ---------------------- | -------------------------------------------- | -------------------------------------------------------------------- |
|
|
29
|
+
| `Button` | `Button` | Prop renames apply (see below) |
|
|
30
|
+
| `Box` | `Flex` (or `Grid` for 2D) | No 1:1 layout box; use `Flex` with `direction`/`gap`/`align` |
|
|
31
|
+
| `Modal` | `Dialog` + `DialogContent` + `DialogActions` | Use `PromoDialog` for marketing dialogs (requires `PromoDialogHero`) |
|
|
32
|
+
| `Link` | `RawLink` (base) or `Link` (tanstack-router) | Pick the router-aware one when navigating inside the app |
|
|
33
|
+
| `Heading` | `Text` with `variant="heading*"` | No dedicated `Heading`; typography is a `Text` variant |
|
|
34
|
+
| `Badge` | `Badge` | `color` → `variant`; Unity Badge is the chip with intent colors |
|
|
35
|
+
| `Pill` | `Pill` | Unity Pill is numeric-only with fewer colors; same indicator intent |
|
|
36
|
+
| `transition('smooth')` | `uy:transition-*` / `uy:duration-*` classes | No JS helper; use TailwindCSS utilities |
|
|
37
|
+
|
|
38
|
+
For everything else, see `references/midnight-component-map.md`. When a
|
|
39
|
+
target is unclear, switch to `unity-find-component` rather than
|
|
40
|
+
guessing. Do not invent Unity component names from Midnight ones.
|
|
41
|
+
|
|
42
|
+
## Prop renames
|
|
43
|
+
|
|
44
|
+
Confirmed renames from `Button.tsx` and the React Aria base classes Unity
|
|
45
|
+
extends:
|
|
46
|
+
|
|
47
|
+
| Midnight | Unity | Mechanism |
|
|
48
|
+
| ----------------- | ---------------- | ------------------------------------------------------ |
|
|
49
|
+
| `visual="danger"` | `color="danger"` | Color/intent prop renamed on `Button`, `Pill`, `Alert` |
|
|
50
|
+
| `onClick={fn}` | `onPress={fn}` | React Aria press events (touch/keyboard parity) |
|
|
51
|
+
| `disabled` | `isDisabled` | React Aria boolean convention |
|
|
52
|
+
|
|
53
|
+
Do not auto-extend this table. If a prop is not listed, look at the Unity
|
|
54
|
+
component source — many props are unchanged and adding speculative
|
|
55
|
+
renames produces silent default behavior.
|
|
56
|
+
|
|
57
|
+
## Accessibility deltas
|
|
58
|
+
|
|
59
|
+
Unity enforces several WCAG rules that Midnight allowed:
|
|
60
|
+
|
|
61
|
+
- **Tooltip on disabled control — rejected at the library level.**
|
|
62
|
+
`Tooltip` ignores or removes the trigger when the wrapped control is
|
|
63
|
+
disabled, because disabled controls are not focusable and therefore the
|
|
64
|
+
tooltip is unreachable by keyboard or screen reader. See the
|
|
65
|
+
`## Common Mistakes` entry below for the replacement.
|
|
66
|
+
- **Dialog focus management.** Unity `Dialog` traps focus, restores it on
|
|
67
|
+
close, and requires an accessible title. Midnight `Modal` was looser
|
|
68
|
+
about both.
|
|
69
|
+
- **Autocomplete keyboard navigation.** Unity `Autocomplete` follows the
|
|
70
|
+
ARIA combobox pattern (Arrow keys, Home/End, Escape to dismiss). If
|
|
71
|
+
Midnight code relied on click-only interactions, those still work, but
|
|
72
|
+
do not strip the keyboard handlers when porting.
|
|
73
|
+
|
|
74
|
+
## Forms
|
|
75
|
+
|
|
76
|
+
Midnight-era screens commonly use React Hook Form. When porting, replace
|
|
77
|
+
the form with `useTanstackUnityForm` and the Tanstack-bound `*Field`
|
|
78
|
+
components (`TextField`, `SelectField`, `NumberField`, etc.). The legacy
|
|
79
|
+
RHF `useUnityForm` + `*Field` wrappers in the same package index are
|
|
80
|
+
deprecated and will be removed after the rebrand — do not pause on the
|
|
81
|
+
RHF intermediate step. See `unity-tanstack-form`.
|
|
82
|
+
|
|
83
|
+
## Common Mistakes
|
|
84
|
+
|
|
85
|
+
### CRITICAL Wrap disabled button in Tooltip (Midnight-era pattern)
|
|
86
|
+
|
|
87
|
+
Wrong:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<Tooltip title="Disabled because …">
|
|
91
|
+
<Button isDisabled>Submit</Button>
|
|
92
|
+
</Tooltip>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Correct:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<Button isDisabled>Submit</Button>
|
|
99
|
+
<Text variant="bodySmall" color="content.neutral.low">
|
|
100
|
+
Disabled because …
|
|
101
|
+
</Text>
|
|
102
|
+
// Or, if you must explain inline, gate the tooltip on enabled state:
|
|
103
|
+
{isDisabled
|
|
104
|
+
? <Button isDisabled>Submit</Button>
|
|
105
|
+
: <Tooltip title="…"><Button>Submit</Button></Tooltip>}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Midnight allowed `Tooltip` on disabled buttons; Unity enforces the WCAG
|
|
109
|
+
rule that disabled controls must not have tooltips because they are not
|
|
110
|
+
keyboard-focusable, so the tooltip is unreachable — Unity's `Tooltip`
|
|
111
|
+
component rejects this pairing at the library level.
|
|
112
|
+
|
|
113
|
+
Source: libs/shared/unity/components/src/components/tooltip/Tooltip.tsx; maintainer interview (Unity enforces)
|
|
114
|
+
|
|
115
|
+
### HIGH Assume Midnight props map 1:1 to Unity
|
|
116
|
+
|
|
117
|
+
Wrong:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<Button visual="danger" onClick={handleDelete}>
|
|
121
|
+
Delete
|
|
122
|
+
</Button>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Correct:
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
<Button color="danger" onPress={handleDelete}>
|
|
129
|
+
Delete
|
|
130
|
+
</Button>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`visual` is the Midnight intent prop and `color` is the Unity one; `onClick`
|
|
134
|
+
still type-checks on the underlying DOM node but bypasses React Aria's
|
|
135
|
+
touch and keyboard press handling, so keyboard activations silently no-op.
|
|
136
|
+
|
|
137
|
+
Source: libs/shared/unity/components/src/components/button/Button.tsx
|
|
138
|
+
|
|
139
|
+
### MEDIUM Leave Midnight + Unity side-by-side in the same screen
|
|
140
|
+
|
|
141
|
+
Wrong:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { Button } from '@payfit/midnight'
|
|
145
|
+
import { Dialog } from '@payfit/unity-components'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Correct:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { Button, Dialog } from '@payfit/unity-components'
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Midnight and Unity ship divergent theme tokens — colors, spacing, and
|
|
155
|
+
typography differ — so a mixed screen renders with mismatched scales and
|
|
156
|
+
the visual regression lands silently because both stylesheets are valid.
|
|
157
|
+
|
|
158
|
+
Source: AGENTS.md migration awareness; maintainer interview
|
|
159
|
+
|
|
160
|
+
### MEDIUM Port Midnight transition utility instead of using uy: classes
|
|
161
|
+
|
|
162
|
+
Wrong:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { transition } from '@payfit/midnight'
|
|
166
|
+
<Flex transition={transition('smooth')}>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Correct:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<Flex className="uy:transition-all uy:duration-200 uy:ease-out">
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Unity has no JS transition helper; the migration target is TailwindCSS 4
|
|
176
|
+
transition utilities under the `uy:` prefix, and porting the Midnight
|
|
177
|
+
function leaves a dead import that the new theme tokens never honor.
|
|
178
|
+
|
|
179
|
+
Source: no Unity equivalent for Midnight transition helpers
|
|
180
|
+
|
|
181
|
+
## See also
|
|
182
|
+
|
|
183
|
+
- `references/midnight-component-map.md` — exhaustive Midnight → Unity
|
|
184
|
+
map (v1 + v2 surfaces, ~85 rows, grouped by category)
|
|
185
|
+
- `unity-find-component` — decision tree for any Midnight component
|
|
186
|
+
still ambiguous after consulting the full map
|
|
187
|
+
- `unity-overlays` — full a11y constraints for `Dialog`, `Tooltip`,
|
|
188
|
+
`Popover`, `Menu`
|
|
189
|
+
- `unity-tanstack-form` — replacement path for RHF forms in the migrated
|
|
190
|
+
screen
|