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