@jfdevelops/react-layout 0.16.0 → 0.17.1
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/create-config/define-layout.cjs +7 -3
- package/dist/create-config/define-layout.cjs.map +1 -1
- package/dist/create-config/define-layout.d.cts.map +1 -1
- package/dist/create-config/define-layout.d.mts.map +1 -1
- package/dist/create-config/define-layout.mjs +7 -3
- package/dist/create-config/define-layout.mjs.map +1 -1
- package/dist/create-config/for-resources/create-for-resources.cjs +8 -2
- package/dist/create-config/for-resources/create-for-resources.cjs.map +1 -1
- package/dist/create-config/for-resources/create-for-resources.mjs +8 -2
- package/dist/create-config/for-resources/create-for-resources.mjs.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +5 -4
- package/skills/build-react-layouts/SKILL.md +132 -0
- package/skills/build-react-layouts/agents/openai.yaml +4 -0
- package/skills/build-react-layouts/references/architecture.md +184 -0
- package/skills/build-react-layouts/references/factory-patterns.md +156 -0
- package/skills/build-react-layouts/references/routing-and-navigation.md +174 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Factory Patterns
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Direct creation
|
|
6
|
+
- Resource-scoped factories
|
|
7
|
+
- Composable detail pages
|
|
8
|
+
- Multi-resource subsets
|
|
9
|
+
- Higher-order multi-resource components
|
|
10
|
+
- Selection guide
|
|
11
|
+
|
|
12
|
+
## Create one page directly
|
|
13
|
+
|
|
14
|
+
Use direct creation for one isolated page:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
export const WaitlistPage = createResourceLayout({
|
|
18
|
+
resource: 'waitlist',
|
|
19
|
+
name: 'WaitlistPage',
|
|
20
|
+
segments: {
|
|
21
|
+
title: 'Waitlist',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Provide an explicit `name` when the surrounding factory does not establish one.
|
|
27
|
+
|
|
28
|
+
## Scope a factory to one resource
|
|
29
|
+
|
|
30
|
+
Use `forResource` when multiple page definitions share one resource:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
const createLocationsPage = createResourceLayout.forResource({
|
|
34
|
+
resource: 'locations',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const LocationsPage = createLocationsPage({
|
|
38
|
+
segments: {
|
|
39
|
+
title: 'Locations',
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Keep the scoped factory private unless several modules genuinely need it.
|
|
45
|
+
|
|
46
|
+
## Make a generated page composable
|
|
47
|
+
|
|
48
|
+
Use `makeComposable` when a page needs to retain the resource layout contract
|
|
49
|
+
while allowing consumers to address or rearrange semantic regions:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
export const LocationsDetailPage = LocationsPage.makeComposable({
|
|
53
|
+
name: 'LocationsDetailPage',
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Prefer this over copying the parent layout or manually reproducing its header
|
|
58
|
+
and content structure. Do not call `makeComposable` when consumers only need to
|
|
59
|
+
pass `children` or other declared custom props.
|
|
60
|
+
|
|
61
|
+
## Narrow a factory to several resources
|
|
62
|
+
|
|
63
|
+
Use `forResources` when a shared implementation applies to a known subset:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
const createCalendarPane =
|
|
67
|
+
resourcePaneLayout.createResourceLayout.forResources('create', 'detail');
|
|
68
|
+
|
|
69
|
+
export const createCalendarDetailPane = createCalendarPane.forResource({
|
|
70
|
+
resource: 'detail',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const createCalendarCreatePane = createCalendarPane.forResource({
|
|
74
|
+
resource: 'create',
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Narrow the resource set before creating the final resource-specific factory.
|
|
79
|
+
|
|
80
|
+
## Create a higher-order multi-resource component
|
|
81
|
+
|
|
82
|
+
Use the advanced component factory when several resources share behavior but
|
|
83
|
+
need distinct render implementations:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
const createCalendarPage = createResourceLayout
|
|
87
|
+
.forResources('appointments', 'availability')
|
|
88
|
+
.createComponent({
|
|
89
|
+
resources: {
|
|
90
|
+
appointments: {
|
|
91
|
+
segments: {
|
|
92
|
+
title: 'Appointments',
|
|
93
|
+
},
|
|
94
|
+
render: function AppointmentsRender() {
|
|
95
|
+
return <AppointmentsView />;
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
availability: {
|
|
99
|
+
segments: {
|
|
100
|
+
title: 'Availability',
|
|
101
|
+
},
|
|
102
|
+
render: function AvailabilityRender() {
|
|
103
|
+
return <AvailabilityView />;
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
render: function Render({ resource, children }, context) {
|
|
108
|
+
return (
|
|
109
|
+
<context.Root>
|
|
110
|
+
{children ??
|
|
111
|
+
(resource === 'appointments' ? (
|
|
112
|
+
<context.Appointments />
|
|
113
|
+
) : (
|
|
114
|
+
<context.Availability />
|
|
115
|
+
))}
|
|
116
|
+
</context.Root>
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
.asHOF();
|
|
121
|
+
|
|
122
|
+
export const AppointmentsPage = createCalendarPage('appointments');
|
|
123
|
+
export const AvailabilityPage = createCalendarPage('availability');
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use this pattern only when the resources share meaningful behavior, controls,
|
|
127
|
+
or state transitions. The outer renderer should own behavior shared by the
|
|
128
|
+
resource subset. Each resource renderer should own resource-specific data and
|
|
129
|
+
content.
|
|
130
|
+
|
|
131
|
+
Attach closely related states when that creates a useful page namespace:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
export const AppointmentsPage = Object.assign(
|
|
135
|
+
createCalendarPage('appointments'),
|
|
136
|
+
{
|
|
137
|
+
Loading: AppointmentsLoading,
|
|
138
|
+
Error: AppointmentsError,
|
|
139
|
+
NotFound: AppointmentsNotFound,
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Use this for components that are semantically part of the generated page
|
|
145
|
+
surface. Avoid turning the page component into an unrelated utility namespace.
|
|
146
|
+
|
|
147
|
+
## Choose the narrowest factory
|
|
148
|
+
|
|
149
|
+
- For one page, call `createResourceLayout`.
|
|
150
|
+
- For several pages belonging to one resource, use `forResource`.
|
|
151
|
+
- For several resources with the same configuration shape, use `forResources`.
|
|
152
|
+
- When consumers need semantic subcomponents, use `makeComposable`.
|
|
153
|
+
- When several resources share an outer behavior but need distinct
|
|
154
|
+
implementations, use `createComponent(...).asHOF()`.
|
|
155
|
+
- When the surface has a different structural contract, create another
|
|
156
|
+
`defineResourceLayout`.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Routing and Navigation
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Grouped resource links
|
|
6
|
+
- Resource configuration
|
|
7
|
+
- Route dispatch
|
|
8
|
+
- Runtime route validation
|
|
9
|
+
- Async route states
|
|
10
|
+
- Separation of concerns
|
|
11
|
+
|
|
12
|
+
## Generate grouped resource links
|
|
13
|
+
|
|
14
|
+
Generate navigation from the layout's resource vocabulary:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
const navigation = createResourceLinks.withGroups([
|
|
18
|
+
{
|
|
19
|
+
links: {
|
|
20
|
+
dashboard: {
|
|
21
|
+
label: 'Dashboard',
|
|
22
|
+
icon: <DashboardIcon />,
|
|
23
|
+
href: '/admin',
|
|
24
|
+
},
|
|
25
|
+
appointments: {
|
|
26
|
+
label: 'Appointments',
|
|
27
|
+
icon: <AppointmentsIcon />,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Configuration',
|
|
33
|
+
links: {
|
|
34
|
+
settings: {
|
|
35
|
+
label: 'Settings',
|
|
36
|
+
icon: <SettingsIcon />,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
]);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use groups to express information architecture. Let normal resources use the
|
|
44
|
+
shared resource route. Provide `href` only for special destinations whose route
|
|
45
|
+
does not follow the general pattern.
|
|
46
|
+
|
|
47
|
+
Render links using the generated `resource` value instead of reconstructing it
|
|
48
|
+
from labels.
|
|
49
|
+
|
|
50
|
+
## Create the resource configuration
|
|
51
|
+
|
|
52
|
+
Create a typed configuration that maps each routeable resource to its normal
|
|
53
|
+
and asynchronous states:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
export const resourcePageConfig = createResourceConfig({
|
|
57
|
+
appointments: {
|
|
58
|
+
component: <AppointmentsPage />,
|
|
59
|
+
pendingComponent: (
|
|
60
|
+
<AppointmentsPage>
|
|
61
|
+
<AppointmentsPage.Loading />
|
|
62
|
+
</AppointmentsPage>
|
|
63
|
+
),
|
|
64
|
+
errorComponent: (
|
|
65
|
+
<AppointmentsPage>
|
|
66
|
+
<AppointmentsPage.Error />
|
|
67
|
+
</AppointmentsPage>
|
|
68
|
+
),
|
|
69
|
+
notFoundComponent: (
|
|
70
|
+
<AppointmentsPage>
|
|
71
|
+
<AppointmentsPage.NotFound />
|
|
72
|
+
</AppointmentsPage>
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
locations: {
|
|
76
|
+
component: <Locations />,
|
|
77
|
+
pendingComponent: <LocationsLoading />,
|
|
78
|
+
errorComponent: <LocationsError />,
|
|
79
|
+
notFoundComponent: <LocationsNotFound />,
|
|
80
|
+
detail: {
|
|
81
|
+
component: <LocationDetail />,
|
|
82
|
+
pendingComponent: <LocationDetailLoading />,
|
|
83
|
+
errorComponent: <LocationDetailError />,
|
|
84
|
+
notFoundComponent: <LocationDetailNotFound />,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Keep this mapping declarative. Keep data fetching and mutation logic inside the
|
|
91
|
+
corresponding feature components or router loaders.
|
|
92
|
+
|
|
93
|
+
## Dispatch route components generically
|
|
94
|
+
|
|
95
|
+
Use one generic route adapter to select the configured component:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
type ResourceComponentKey =
|
|
99
|
+
| 'component'
|
|
100
|
+
| 'pendingComponent'
|
|
101
|
+
| 'errorComponent'
|
|
102
|
+
| 'notFoundComponent';
|
|
103
|
+
|
|
104
|
+
function getResourceComponent(component: ResourceComponentKey) {
|
|
105
|
+
const resource = Route.useParams({
|
|
106
|
+
select: ({ resource }) => resource,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return resourcePageConfig.getComponent.forResource({
|
|
110
|
+
resource,
|
|
111
|
+
})({
|
|
112
|
+
component,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function RouteComponent() {
|
|
117
|
+
return getResourceComponent('component');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function PendingComponent() {
|
|
121
|
+
return getResourceComponent('pendingComponent');
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Use the same mechanism for error and not-found states. Do not create resource
|
|
126
|
+
condition chains in the generic route component when the information belongs
|
|
127
|
+
in `createResourceConfig`.
|
|
128
|
+
|
|
129
|
+
## Validate route parameters at runtime
|
|
130
|
+
|
|
131
|
+
The layout provides compile-time resource constraints. The router must still
|
|
132
|
+
validate untrusted URL parameters at runtime.
|
|
133
|
+
|
|
134
|
+
Parse or validate the route parameter before passing it to generated resource
|
|
135
|
+
helpers. Reject invalid resources through the router's normal not-found
|
|
136
|
+
mechanism. Keep routing validation in the router layer instead of embedding it
|
|
137
|
+
in the shared layout renderer.
|
|
138
|
+
|
|
139
|
+
## Preserve the layout during async states
|
|
140
|
+
|
|
141
|
+
Wrap pending, error, and not-found content in the same generated page shell
|
|
142
|
+
when the shell should remain visible during those states.
|
|
143
|
+
|
|
144
|
+
Use a separate route-state surface when a detail view cannot render its normal
|
|
145
|
+
page structure without loaded data. Keep the mapping explicit so one resource's
|
|
146
|
+
state cannot appear inside another resource's layout.
|
|
147
|
+
|
|
148
|
+
## Keep ownership clear
|
|
149
|
+
|
|
150
|
+
Let the layout layer own:
|
|
151
|
+
|
|
152
|
+
- Valid resource vocabulary
|
|
153
|
+
- Shared application structure
|
|
154
|
+
- Page configuration contracts
|
|
155
|
+
- Semantic composition points
|
|
156
|
+
- Navigation metadata
|
|
157
|
+
- Resource-to-component dispatch
|
|
158
|
+
|
|
159
|
+
Let the router own:
|
|
160
|
+
|
|
161
|
+
- URL parsing
|
|
162
|
+
- Search parameter validation
|
|
163
|
+
- Redirects
|
|
164
|
+
- Loaders
|
|
165
|
+
- Route lifecycle selection
|
|
166
|
+
- Runtime not-found behavior
|
|
167
|
+
|
|
168
|
+
Let feature components own:
|
|
169
|
+
|
|
170
|
+
- Queries and mutations
|
|
171
|
+
- Feature-level loading details
|
|
172
|
+
- Tables and calendars
|
|
173
|
+
- Forms
|
|
174
|
+
- Resource-specific interactions
|