@payfit/unity-components 2.55.13 → 2.55.15
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/dist/esm/components/autocomplete/parts/AutocompletePanel.js +1 -1
- package/dist/esm/components/avatar/Avatar.context.js +1 -1
- package/dist/esm/components/bottom-sheet/parts/BottomSheetDragIndicator.js +1 -1
- package/dist/esm/components/breadcrumbs/Breadcrumbs.context.js +1 -1
- package/dist/esm/components/breadcrumbs/Breadcrumbs.js +1 -1
- package/dist/esm/components/carousel/Carousel.options.js +1 -1
- package/dist/esm/components/carousel/hooks/useCarouselState.js +8 -4
- package/dist/esm/components/client-side-pagination/ClientSidePagination.js +1 -1
- package/dist/esm/components/client-side-pagination/parts/RawPaginationLink.js +1 -1
- package/dist/esm/components/client-side-pagination/utils/pagination-window.js +18 -19
- package/dist/esm/components/code/Code.js +1 -1
- package/dist/esm/components/collapsible/Collapsible.js +1 -1
- package/dist/esm/components/dialog/test-utils.js +4 -2
- package/dist/esm/components/error-state/initConfig.js +1 -1
- package/dist/esm/components/filter/parts/FilterButton.js +1 -1
- package/dist/esm/components/filter/parts/FilterLabel.js +1 -1
- package/dist/esm/components/filter/utils/value-formatters.js +1 -1
- package/dist/esm/components/filter-toolbar/FilterToolbar.js +27 -27
- package/dist/esm/components/filter-toolbar/parts/AddFilterItem.js +1 -1
- package/dist/esm/components/form/Form.context.js +1 -1
- package/dist/esm/components/form-field/FormField.context.js +1 -1
- package/dist/esm/components/inline-field/parts/InlineFieldEditView.js +7 -4
- package/dist/esm/components/inline-field-group/InlineFieldGroup.js +14 -8
- package/dist/esm/components/multi-select/MultiSelect.js +2 -2
- package/dist/esm/components/pagination/utils/pagination-window.js +25 -26
- package/dist/esm/components/phone-number/parts/PhoneNumberItem.js +1 -1
- package/dist/esm/components/popover/Popover.js +1 -1
- package/dist/esm/components/radio-button-group/parts/RadioButton.js +1 -1
- package/dist/esm/components/select/parts/SelectOption.context.js +1 -1
- package/dist/esm/components/select-list/helpers.js +2 -2
- package/dist/esm/components/select-list/parts/SelectListOptGroup.js +1 -1
- package/dist/esm/components/selectable-card/internals/Content.js +1 -1
- package/dist/esm/components/selectable-card/internals/Description.js +1 -1
- package/dist/esm/components/selectable-card/internals/Illustration.js +1 -1
- package/dist/esm/components/selectable-card/internals/SelectedIndicator.js +1 -1
- package/dist/esm/components/side-panel/parts/SidePanelDragIndicator.js +1 -1
- package/dist/esm/components/stepper/Stepper.context.js +1 -1
- package/dist/esm/components/table/Table.context.js +1 -1
- package/dist/esm/components/table/hooks/useTableKeyboardNavigation.js +10 -8
- package/dist/esm/components/tabs/Tabs.context.js +1 -1
- package/dist/esm/components/task-menu/TaskMenu.context.js +1 -1
- package/dist/esm/components/timeline/Timeline.context.js +1 -1
- package/dist/esm/components/timeline/parts/TimelineStepContent.context.js +1 -1
- package/dist/esm/hooks/use-has-scroll.js +3 -1
- package/dist/esm/index.js +2 -2
- package/package.json +14 -14
- package/skills/unity-data-table/SKILL.md +24 -240
- package/skills/unity-data-table/references/patterns.md +237 -0
- package/skills/unity-find-component/SKILL.md +20 -105
- package/skills/unity-find-component/references/component-catalog.md +94 -0
- package/skills/unity-layout-and-styling/SKILL.md +14 -141
- package/skills/unity-layout-and-styling/references/patterns.md +139 -0
- package/skills/unity-migrate-from-midnight/SKILL.md +9 -9
- package/skills/unity-navigation/SKILL.md +12 -116
- package/skills/unity-navigation/references/patterns.md +115 -0
- package/skills/unity-overlays/SKILL.md +11 -132
- package/skills/unity-overlays/references/patterns.md +131 -0
- package/skills/unity-setup-feature-plugin/SKILL.md +4 -3
- package/skills/unity-tanstack-form/SKILL.md +15 -102
- package/skills/unity-tanstack-form/references/patterns.md +102 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Unity layout and styling patterns
|
|
2
|
+
|
|
3
|
+
### Flex for 1D, Grid for 2D
|
|
4
|
+
|
|
5
|
+
`Flex` is for one-dimensional rows/columns; `Grid` is for the 12-column (or 6-column)
|
|
6
|
+
two-dimensional layout. Each exposes layout props; everything else goes via
|
|
7
|
+
`className` with `uy:` utilities.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Flex, FlexItem, Grid, GridItem } from '@payfit/unity-components'
|
|
11
|
+
|
|
12
|
+
// Flex props: asElement, inline, direction, isReversed, wrap,
|
|
13
|
+
// gap, gapX, gapY, justify, align, alignContent, className
|
|
14
|
+
<Flex direction="row" gap="200" justify="between" align="center">
|
|
15
|
+
<FlexItem grow="1">Left</FlexItem>
|
|
16
|
+
<FlexItem>Right</FlexItem>
|
|
17
|
+
</Flex>
|
|
18
|
+
|
|
19
|
+
// Grid props: asElement, inline, cols (6 | 12), rows, areas, flow,
|
|
20
|
+
// justifyItems, alignItems, className
|
|
21
|
+
// GridItem positions via colSpan/colStart/colEnd OR area (mutually exclusive)
|
|
22
|
+
<Grid cols={12} className="uy:gap-200">
|
|
23
|
+
<GridItem colSpan={8}>Main</GridItem>
|
|
24
|
+
<GridItem colSpan={4}>Aside</GridItem>
|
|
25
|
+
</Grid>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Responsive classes via uy:md:
|
|
29
|
+
|
|
30
|
+
There is no responsive prop-object API. Responsive behavior is driven by
|
|
31
|
+
TailwindCSS v4 modifiers on `className`.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Flex gap="100" className="uy:md:gap-200 uy:lg:gap-300">
|
|
35
|
+
<span>Item</span>
|
|
36
|
+
</Flex>
|
|
37
|
+
|
|
38
|
+
<Grid cols={12} className="uy:grid-cols-1 uy:md:grid-cols-2 uy:lg:grid-cols-3" />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Variants with uyTv
|
|
42
|
+
|
|
43
|
+
`uyTv` from `@payfit/unity-themes` is the pre-configured tailwind-variants
|
|
44
|
+
factory. It applies the Unity `twMergeConfig` so variant collisions resolve
|
|
45
|
+
against Unity tokens. Export the variant function and derive its typed props
|
|
46
|
+
with `VariantProps`.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import type { VariantProps } from '@payfit/unity-themes'
|
|
50
|
+
|
|
51
|
+
import { uyTv } from '@payfit/unity-themes'
|
|
52
|
+
|
|
53
|
+
export const callout = uyTv({
|
|
54
|
+
base: 'uy:inline-flex uy:items-center uy:gap-100 uy:rounded-100 uy:px-200 uy:py-100',
|
|
55
|
+
variants: {
|
|
56
|
+
intent: {
|
|
57
|
+
info: 'uy:bg-surface-primary-default uy:text-content-inverted-default',
|
|
58
|
+
danger: 'uy:bg-surface-danger-default uy:text-content-inverted-default',
|
|
59
|
+
neutral: 'uy:bg-surface-neutral-default uy:text-content-neutral-default',
|
|
60
|
+
},
|
|
61
|
+
size: {
|
|
62
|
+
sm: 'uy:typography-body-small',
|
|
63
|
+
md: 'uy:typography-body',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: { intent: 'info', size: 'md' },
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
export type CalloutVariantProps = VariantProps<typeof callout>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Class merging with uyMerge
|
|
73
|
+
|
|
74
|
+
`uyMerge` is `tailwind-merge` configured with Unity's class groups. Use it
|
|
75
|
+
whenever an external `className` may collide with internal classes.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { uyMerge } from '@payfit/unity-themes'
|
|
79
|
+
|
|
80
|
+
uyMerge('uy:p-100', 'uy:p-200') // → 'uy:p-200'
|
|
81
|
+
uyMerge('uy:bg-surface-primary-default', 'uy:bg-surface-danger-default')
|
|
82
|
+
// → 'uy:bg-surface-danger-default'
|
|
83
|
+
uyMerge('uy:p-100', 'uy:px-200', 'uy:py-300') // p, px, py don't collide
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Conditional classes with cn / classNames / clsx
|
|
87
|
+
|
|
88
|
+
`cn`, `classNames`, and `clsx` are aliases for the same Unity-configured helper
|
|
89
|
+
in `@payfit/unity-themes`. Use them for ad-hoc conditional strings — not for
|
|
90
|
+
component-scoped variant APIs.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { cn } from '@payfit/unity-themes'
|
|
94
|
+
|
|
95
|
+
function Row({
|
|
96
|
+
isActive,
|
|
97
|
+
className,
|
|
98
|
+
}: {
|
|
99
|
+
isActive: boolean
|
|
100
|
+
className?: string
|
|
101
|
+
}) {
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
className={cn(
|
|
105
|
+
'uy:flex uy:items-center uy:px-200 uy:py-100',
|
|
106
|
+
isActive && 'uy:bg-surface-primary-default',
|
|
107
|
+
className,
|
|
108
|
+
)}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Typography with `<Text>`
|
|
115
|
+
|
|
116
|
+
Use `<Text variant=...>` instead of a `<div>` + typography class. `Text`
|
|
117
|
+
picks a semantic element from the variant (e.g. `variant="h1"` → `<h1>`),
|
|
118
|
+
applies the typography variant, and exposes `color`, `isTruncated`,
|
|
119
|
+
`lineClamp`, and `maxWidthCh`.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { Text } from '@payfit/unity-components'
|
|
123
|
+
|
|
124
|
+
<Text variant="h1" color="content.primary">Title</Text>
|
|
125
|
+
<Text variant="body" color="content.neutral">Description</Text>
|
|
126
|
+
// Override the semantic element when needed:
|
|
127
|
+
<Text variant="h1" asElement="h2">Visual h1, semantic h2</Text>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Data-attribute pseudo-states
|
|
131
|
+
|
|
132
|
+
Several Unity components expose internal state via `data-hovered`,
|
|
133
|
+
`data-pressed`, `data-focus-visible`, etc. Target the component-owned state
|
|
134
|
+
attribute rather than the native CSS pseudo-state.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { ListViewItem } from '@payfit/unity-components'
|
|
138
|
+
;<ListViewItem className="uy:data-[hovered=true]:bg-surface-primary-hover" />
|
|
139
|
+
```
|
|
@@ -4,9 +4,10 @@ description: >
|
|
|
4
4
|
Load when replacing @payfit/midnight UI with Unity. Use it to choose Unity
|
|
5
5
|
equivalents, avoid obsolete Midnight patterns, and route unclear component
|
|
6
6
|
mappings to unity-find-component.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
metadata:
|
|
8
|
+
type: lifecycle
|
|
9
|
+
library: '@payfit/unity-components'
|
|
10
|
+
library_version: '2.x'
|
|
10
11
|
sources:
|
|
11
12
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/tooltip/Tooltip.tsx'
|
|
12
13
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/button/Button.tsx'
|
|
@@ -71,10 +72,9 @@ Unity enforces several WCAG rules that Midnight allowed:
|
|
|
71
72
|
|
|
72
73
|
Midnight-era screens commonly use React Hook Form. When porting, replace
|
|
73
74
|
the form with `useTanstackUnityForm` and the Tanstack-bound `*Field`
|
|
74
|
-
components (`TextField`, `SelectField`, `NumberField`, etc.). The legacy
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
RHF intermediate step. See `unity-tanstack-form`.
|
|
75
|
+
components (`TextField`, `SelectField`, `NumberField`, etc.). The legacy RHF
|
|
76
|
+
`useUnityForm` + `*Field` wrappers in the same package index are deprecated.
|
|
77
|
+
Do not pause on the RHF intermediate step. See `unity-tanstack-form`.
|
|
78
78
|
|
|
79
79
|
## Common Mistakes
|
|
80
80
|
|
|
@@ -106,7 +106,7 @@ rule that disabled controls must not have tooltips because they are not
|
|
|
106
106
|
keyboard-focusable, so the tooltip is unreachable — Unity's `Tooltip`
|
|
107
107
|
component rejects this pairing at the library level.
|
|
108
108
|
|
|
109
|
-
Source: libs/shared/unity/components/src/components/tooltip/Tooltip.tsx
|
|
109
|
+
Source: libs/shared/unity/components/src/components/tooltip/Tooltip.tsx
|
|
110
110
|
|
|
111
111
|
### HIGH Assume Midnight props map 1:1 to Unity
|
|
112
112
|
|
|
@@ -151,7 +151,7 @@ Midnight and Unity ship divergent theme tokens — colors, spacing, and
|
|
|
151
151
|
typography differ — so a mixed screen renders with mismatched scales and
|
|
152
152
|
the visual regression lands silently because both stylesheets are valid.
|
|
153
153
|
|
|
154
|
-
Source: AGENTS.md migration awareness
|
|
154
|
+
Source: AGENTS.md migration awareness
|
|
155
155
|
|
|
156
156
|
### MEDIUM Port Midnight transition utility instead of using uy: classes
|
|
157
157
|
|
|
@@ -4,9 +4,10 @@ description: >
|
|
|
4
4
|
Load when building Unity links, tabs, breadcrumbs, nav, or pagination. Use it
|
|
5
5
|
to choose router-aware Tanstack Router components versus Raw* href
|
|
6
6
|
primitives.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
metadata:
|
|
8
|
+
type: core
|
|
9
|
+
library: '@payfit/unity-components'
|
|
10
|
+
library_version: '2.x'
|
|
10
11
|
sources:
|
|
11
12
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/link/RawLink.tsx'
|
|
12
13
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/breadcrumbs/Breadcrumbs.tsx'
|
|
@@ -73,119 +74,14 @@ export function EmployeesShell() {
|
|
|
73
74
|
|
|
74
75
|
## Core Patterns
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
- Use router-aware integration exports in TanStack Router features and `Raw*`
|
|
78
|
+
primitives for explicit `href` navigation.
|
|
79
|
+
- Preserve the required Breadcrumbs, Pagination, and Tabs composition.
|
|
80
|
+
- In non-TanStack applications, provide the appropriate router adapter before
|
|
81
|
+
using `Raw*` navigation components.
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
| `RawLink` | `Link` | `href` (raw) / `to` (integration) |
|
|
81
|
-
| `RawNavItem` | `NavItem` | same |
|
|
82
|
-
| `RawBreadcrumbLink` | `BreadcrumbLink` | same |
|
|
83
|
-
| `RawPaginationLink` | `PaginationLink` | same |
|
|
84
|
-
| `RawTab` (+ `Tabs`) | `Tab` (+ `Tabs`) | route-aware `to` selects the active tab |
|
|
85
|
-
| `RawLinkButton` | `LinkButton` | same |
|
|
86
|
-
|
|
87
|
-
The integration components are produced via `createLink(RawLink)`. They render
|
|
88
|
-
exactly the same DOM as the raw versions but accept Tanstack's typed `to`,
|
|
89
|
-
`params`, `search`, and `preload` props.
|
|
90
|
-
|
|
91
|
-
### Compose Breadcrumbs / Pagination / Tabs
|
|
92
|
-
|
|
93
|
-
Each navigation composite has a strict child contract. Direct children of the
|
|
94
|
-
container must be the wrapper part — loose children are silently filtered.
|
|
95
|
-
|
|
96
|
-
```tsx
|
|
97
|
-
import {
|
|
98
|
-
Breadcrumb,
|
|
99
|
-
BreadcrumbLink,
|
|
100
|
-
Breadcrumbs,
|
|
101
|
-
Pagination,
|
|
102
|
-
PaginationContent,
|
|
103
|
-
PaginationItem,
|
|
104
|
-
RawPaginationLink,
|
|
105
|
-
RawTab,
|
|
106
|
-
TabList,
|
|
107
|
-
TabPanel,
|
|
108
|
-
Tabs,
|
|
109
|
-
} from '@payfit/unity-components'
|
|
110
|
-
|
|
111
|
-
export function CompositionExamples({
|
|
112
|
-
page,
|
|
113
|
-
pageCount,
|
|
114
|
-
onPageChange,
|
|
115
|
-
}: {
|
|
116
|
-
page: number
|
|
117
|
-
pageCount: number
|
|
118
|
-
onPageChange: (next: number, prev: number, dir: -1 | 1) => void
|
|
119
|
-
}) {
|
|
120
|
-
return (
|
|
121
|
-
<>
|
|
122
|
-
<Breadcrumbs>
|
|
123
|
-
<Breadcrumb>
|
|
124
|
-
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
|
125
|
-
</Breadcrumb>
|
|
126
|
-
<Breadcrumb>
|
|
127
|
-
<BreadcrumbLink href="/employees">Employees</BreadcrumbLink>
|
|
128
|
-
</Breadcrumb>
|
|
129
|
-
</Breadcrumbs>
|
|
130
|
-
|
|
131
|
-
<Pagination
|
|
132
|
-
currentPage={page}
|
|
133
|
-
pageCount={pageCount}
|
|
134
|
-
onPageChange={onPageChange}
|
|
135
|
-
>
|
|
136
|
-
<PaginationContent>
|
|
137
|
-
{Array.from({ length: pageCount }, (_, i) => i + 1).map(n => (
|
|
138
|
-
<PaginationItem key={n}>
|
|
139
|
-
<RawPaginationLink value={n} isActive={n === page}>
|
|
140
|
-
{n}
|
|
141
|
-
</RawPaginationLink>
|
|
142
|
-
</PaginationItem>
|
|
143
|
-
))}
|
|
144
|
-
</PaginationContent>
|
|
145
|
-
</Pagination>
|
|
146
|
-
|
|
147
|
-
<Tabs>
|
|
148
|
-
<TabList>
|
|
149
|
-
<RawTab id="active">Active</RawTab>
|
|
150
|
-
<RawTab id="archived">Archived</RawTab>
|
|
151
|
-
</TabList>
|
|
152
|
-
<TabPanel id="active">Active rows</TabPanel>
|
|
153
|
-
<TabPanel id="archived">Archived rows</TabPanel>
|
|
154
|
-
</Tabs>
|
|
155
|
-
</>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### RouterProvider for Raw\* in non-Tanstack apps
|
|
161
|
-
|
|
162
|
-
When you use `Raw*` components in an app that uses a different router (e.g.
|
|
163
|
-
React Router), wrap the app once in `RouterProvider` so `isActive` and
|
|
164
|
-
client-side navigation work. The Tanstack integration entry does NOT need
|
|
165
|
-
this — it reads from Tanstack hooks directly.
|
|
166
|
-
|
|
167
|
-
```tsx
|
|
168
|
-
import { RouterProvider } from '@payfit/unity-components'
|
|
169
|
-
import { matchPath, useLocation, useNavigate } from 'react-router-dom'
|
|
170
|
-
|
|
171
|
-
export function UnityRouterBridge({ children }: { children: React.ReactNode }) {
|
|
172
|
-
const navigate = useNavigate()
|
|
173
|
-
const location = useLocation()
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<RouterProvider
|
|
177
|
-
navigate={to => navigate(to)}
|
|
178
|
-
isActive={(to, isExact) =>
|
|
179
|
-
Boolean(
|
|
180
|
-
matchPath({ path: to, end: Boolean(isExact) }, location.pathname),
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
>
|
|
184
|
-
{children}
|
|
185
|
-
</RouterProvider>
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
```
|
|
83
|
+
Read [references/patterns.md](references/patterns.md) for complete compositions
|
|
84
|
+
and provider examples.
|
|
189
85
|
|
|
190
86
|
## Common Mistakes
|
|
191
87
|
|
|
@@ -260,7 +156,7 @@ import { Pagination, PaginationContent, PaginationItem, RawPaginationLink }
|
|
|
260
156
|
|
|
261
157
|
v2 Pagination is compositional; passing currentPage/pageCount with no children renders an empty container. The old monolithic component (ClientSidePagination) now lives only inside DataTable as an internal — there is no public monolithic export. For standalone pagination, compose it yourself; for paginated tables, use DataTable which has it built in.
|
|
262
158
|
|
|
263
|
-
Source: components/pagination/Pagination.tsx; index.ts
|
|
159
|
+
Source: components/pagination/Pagination.tsx; index.ts
|
|
264
160
|
|
|
265
161
|
### MEDIUM Forget to wrap BreadcrumbLink in Breadcrumb
|
|
266
162
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Unity navigation patterns
|
|
2
|
+
|
|
3
|
+
### Raw and integration map
|
|
4
|
+
|
|
5
|
+
| Base entry (`@payfit/unity-components`) | Integration (`@payfit/unity-components/integrations/tanstack-router`) | Prop |
|
|
6
|
+
| --------------------------------------- | --------------------------------------------------------------------- | --------------------------------------- |
|
|
7
|
+
| `RawLink` | `Link` | `href` (raw) / `to` (integration) |
|
|
8
|
+
| `RawNavItem` | `NavItem` | same |
|
|
9
|
+
| `RawBreadcrumbLink` | `BreadcrumbLink` | same |
|
|
10
|
+
| `RawPaginationLink` | `PaginationLink` | same |
|
|
11
|
+
| `RawTab` (+ `Tabs`) | `Tab` (+ `Tabs`) | route-aware `to` selects the active tab |
|
|
12
|
+
| `RawLinkButton` | `LinkButton` | same |
|
|
13
|
+
|
|
14
|
+
The integration components are produced via `createLink(RawLink)`. They render
|
|
15
|
+
exactly the same DOM as the raw versions but accept Tanstack's typed `to`,
|
|
16
|
+
`params`, `search`, and `preload` props.
|
|
17
|
+
|
|
18
|
+
### Compose Breadcrumbs / Pagination / Tabs
|
|
19
|
+
|
|
20
|
+
Each navigation composite has a strict child contract. Direct children of the
|
|
21
|
+
container must be the wrapper part — loose children are silently filtered.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import {
|
|
25
|
+
Breadcrumb,
|
|
26
|
+
BreadcrumbLink,
|
|
27
|
+
Breadcrumbs,
|
|
28
|
+
Pagination,
|
|
29
|
+
PaginationContent,
|
|
30
|
+
PaginationItem,
|
|
31
|
+
RawPaginationLink,
|
|
32
|
+
RawTab,
|
|
33
|
+
TabList,
|
|
34
|
+
TabPanel,
|
|
35
|
+
Tabs,
|
|
36
|
+
} from '@payfit/unity-components'
|
|
37
|
+
|
|
38
|
+
export function CompositionExamples({
|
|
39
|
+
page,
|
|
40
|
+
pageCount,
|
|
41
|
+
onPageChange,
|
|
42
|
+
}: {
|
|
43
|
+
page: number
|
|
44
|
+
pageCount: number
|
|
45
|
+
onPageChange: (next: number, prev: number, dir: -1 | 1) => void
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<Breadcrumbs>
|
|
50
|
+
<Breadcrumb>
|
|
51
|
+
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
|
52
|
+
</Breadcrumb>
|
|
53
|
+
<Breadcrumb>
|
|
54
|
+
<BreadcrumbLink href="/employees">Employees</BreadcrumbLink>
|
|
55
|
+
</Breadcrumb>
|
|
56
|
+
</Breadcrumbs>
|
|
57
|
+
|
|
58
|
+
<Pagination
|
|
59
|
+
currentPage={page}
|
|
60
|
+
pageCount={pageCount}
|
|
61
|
+
onPageChange={onPageChange}
|
|
62
|
+
>
|
|
63
|
+
<PaginationContent>
|
|
64
|
+
{Array.from({ length: pageCount }, (_, i) => i + 1).map(n => (
|
|
65
|
+
<PaginationItem key={n}>
|
|
66
|
+
<RawPaginationLink value={n} isActive={n === page}>
|
|
67
|
+
{n}
|
|
68
|
+
</RawPaginationLink>
|
|
69
|
+
</PaginationItem>
|
|
70
|
+
))}
|
|
71
|
+
</PaginationContent>
|
|
72
|
+
</Pagination>
|
|
73
|
+
|
|
74
|
+
<Tabs>
|
|
75
|
+
<TabList>
|
|
76
|
+
<RawTab id="active">Active</RawTab>
|
|
77
|
+
<RawTab id="archived">Archived</RawTab>
|
|
78
|
+
</TabList>
|
|
79
|
+
<TabPanel id="active">Active rows</TabPanel>
|
|
80
|
+
<TabPanel id="archived">Archived rows</TabPanel>
|
|
81
|
+
</Tabs>
|
|
82
|
+
</>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### RouterProvider for Raw\* in non-Tanstack apps
|
|
88
|
+
|
|
89
|
+
When you use `Raw*` components in an app that uses a different router (e.g.
|
|
90
|
+
React Router), wrap the app once in `RouterProvider` so `isActive` and
|
|
91
|
+
client-side navigation work. The Tanstack integration entry does NOT need
|
|
92
|
+
this — it reads from Tanstack hooks directly.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { RouterProvider } from '@payfit/unity-components'
|
|
96
|
+
import { matchPath, useLocation, useNavigate } from 'react-router-dom'
|
|
97
|
+
|
|
98
|
+
export function UnityRouterBridge({ children }: { children: React.ReactNode }) {
|
|
99
|
+
const navigate = useNavigate()
|
|
100
|
+
const location = useLocation()
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<RouterProvider
|
|
104
|
+
navigate={to => navigate(to)}
|
|
105
|
+
isActive={(to, isExact) =>
|
|
106
|
+
Boolean(
|
|
107
|
+
matchPath({ path: to, end: Boolean(isExact) }, location.pathname),
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
>
|
|
111
|
+
{children}
|
|
112
|
+
</RouterProvider>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
```
|
|
@@ -4,9 +4,10 @@ description: >
|
|
|
4
4
|
Load when adding Unity dialogs, side panels, bottom sheets, tooltips,
|
|
5
5
|
popovers, or menus. Use it to choose modal versus non-modal overlays and keep
|
|
6
6
|
disabled-control tooltip behavior accessible.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
metadata:
|
|
8
|
+
type: core
|
|
9
|
+
library: '@payfit/unity-components'
|
|
10
|
+
library_version: '2.x'
|
|
10
11
|
sources:
|
|
11
12
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/dialog/Dialog.tsx'
|
|
12
13
|
- 'PayFit/hr-apps:libs/shared/unity/components/src/components/side-panel/SidePanel.tsx'
|
|
@@ -73,135 +74,13 @@ manage focus, restoration, scroll lock, and Escape automatically.
|
|
|
73
74
|
|
|
74
75
|
## Core Patterns
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
- Compose blocking decisions with `DialogTrigger`, `Dialog`, and
|
|
78
|
+
`DialogActions`.
|
|
79
|
+
- Use `SidePanel` for side-anchored detail or editing flows.
|
|
80
|
+
- Use `Popover` for focusable informational content and `Menu` for action
|
|
81
|
+
lists.
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
import {
|
|
80
|
-
Button,
|
|
81
|
-
Dialog,
|
|
82
|
-
DialogActions,
|
|
83
|
-
DialogButton,
|
|
84
|
-
DialogContent,
|
|
85
|
-
DialogTitle,
|
|
86
|
-
DialogTrigger,
|
|
87
|
-
} from '@payfit/unity-components'
|
|
88
|
-
|
|
89
|
-
export function ArchiveDialog({ onArchive }: { onArchive: () => void }) {
|
|
90
|
-
return (
|
|
91
|
-
<DialogTrigger>
|
|
92
|
-
<Button>Archive</Button>
|
|
93
|
-
<Dialog size="md">
|
|
94
|
-
<DialogTitle>Archive this employee?</DialogTitle>
|
|
95
|
-
<DialogContent>
|
|
96
|
-
They will no longer appear in active lists. You can restore them
|
|
97
|
-
later.
|
|
98
|
-
</DialogContent>
|
|
99
|
-
<DialogActions>
|
|
100
|
-
<DialogButton variant="close">Cancel</DialogButton>
|
|
101
|
-
<DialogButton variant="confirm" onPress={onArchive}>
|
|
102
|
-
Archive
|
|
103
|
-
</DialogButton>
|
|
104
|
-
</DialogActions>
|
|
105
|
-
</Dialog>
|
|
106
|
-
</DialogTrigger>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### SidePanel for detail views
|
|
112
|
-
|
|
113
|
-
```tsx
|
|
114
|
-
import { useState } from 'react'
|
|
115
|
-
|
|
116
|
-
import {
|
|
117
|
-
Button,
|
|
118
|
-
SidePanel,
|
|
119
|
-
SidePanelContent,
|
|
120
|
-
SidePanelFooter,
|
|
121
|
-
SidePanelHeader,
|
|
122
|
-
} from '@payfit/unity-components'
|
|
123
|
-
|
|
124
|
-
export function EmployeeDetailPanel({ employee }: { employee: Employee }) {
|
|
125
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<>
|
|
129
|
-
<Button onPress={() => setIsOpen(true)}>View details</Button>
|
|
130
|
-
<SidePanel isOpen={isOpen} onOpenChange={setIsOpen}>
|
|
131
|
-
<SidePanelHeader>{employee.name}</SidePanelHeader>
|
|
132
|
-
<SidePanelContent>
|
|
133
|
-
<p>{employee.position}</p>
|
|
134
|
-
<p>{employee.team}</p>
|
|
135
|
-
</SidePanelContent>
|
|
136
|
-
<SidePanelFooter>
|
|
137
|
-
<Button variant="secondary" onPress={() => setIsOpen(false)}>
|
|
138
|
-
Close
|
|
139
|
-
</Button>
|
|
140
|
-
</SidePanelFooter>
|
|
141
|
-
</SidePanel>
|
|
142
|
-
</>
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Popover for interactive informational content
|
|
148
|
-
|
|
149
|
-
`title` is required (use `isTitleSrOnly` if you don't want it visible).
|
|
150
|
-
Children may be focusable; the Popover is non-modal so the page stays
|
|
151
|
-
interactive behind it.
|
|
152
|
-
|
|
153
|
-
```tsx
|
|
154
|
-
import { Button, Popover, PopoverTrigger } from '@payfit/unity-components'
|
|
155
|
-
|
|
156
|
-
export function HelpPopover() {
|
|
157
|
-
return (
|
|
158
|
-
<PopoverTrigger>
|
|
159
|
-
<Button variant="tertiary">What is a working agreement?</Button>
|
|
160
|
-
<Popover title="Working agreement">
|
|
161
|
-
<p>
|
|
162
|
-
A working agreement defines when, where and how an employee works. See
|
|
163
|
-
the <a href="/handbook/working-agreement">handbook</a> for details.
|
|
164
|
-
</p>
|
|
165
|
-
</Popover>
|
|
166
|
-
</PopoverTrigger>
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Menu for action lists
|
|
172
|
-
|
|
173
|
-
`Menu` expects exactly two children: a trigger and a content block. Use
|
|
174
|
-
`MenuTrigger` / `MenuContent` / `RawMenuItem` for the standard composition.
|
|
175
|
-
|
|
176
|
-
```tsx
|
|
177
|
-
import {
|
|
178
|
-
IconButton,
|
|
179
|
-
Menu,
|
|
180
|
-
MenuContent,
|
|
181
|
-
MenuTrigger,
|
|
182
|
-
RawMenuItem,
|
|
183
|
-
} from '@payfit/unity-components'
|
|
184
|
-
|
|
185
|
-
export function RowActions({
|
|
186
|
-
onEdit,
|
|
187
|
-
onArchive,
|
|
188
|
-
}: {
|
|
189
|
-
onEdit: () => void
|
|
190
|
-
onArchive: () => void
|
|
191
|
-
}) {
|
|
192
|
-
return (
|
|
193
|
-
<Menu>
|
|
194
|
-
<MenuTrigger>
|
|
195
|
-
<IconButton icon="MoreFilled" title="Actions" />
|
|
196
|
-
</MenuTrigger>
|
|
197
|
-
<MenuContent>
|
|
198
|
-
<RawMenuItem onAction={onEdit}>Edit</RawMenuItem>
|
|
199
|
-
<RawMenuItem onAction={onArchive}>Archive</RawMenuItem>
|
|
200
|
-
</MenuContent>
|
|
201
|
-
</Menu>
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
|
-
```
|
|
83
|
+
Read [references/patterns.md](references/patterns.md) for complete examples.
|
|
205
84
|
|
|
206
85
|
## Common Mistakes
|
|
207
86
|
|
|
@@ -224,7 +103,7 @@ Correct:
|
|
|
224
103
|
|
|
225
104
|
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.
|
|
226
105
|
|
|
227
|
-
Source: components/tooltip/Tooltip.tsx
|
|
106
|
+
Source: components/tooltip/Tooltip.tsx
|
|
228
107
|
|
|
229
108
|
### MEDIUM Use Dialog for a non-blocking hint
|
|
230
109
|
|