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