@jfdevelops/react-layout 0.16.0 → 0.17.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jfdevelops/react-layout",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "A React library for creating layout components.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "type": "module",
10
10
  "files": [
11
- "dist"
11
+ "dist",
12
+ "skills"
12
13
  ],
13
14
  "sideEffects": false,
14
15
  "main": "./dist/index.cjs",
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: build-react-layouts
3
+ description: Build, extend, refactor, and review typed resource-based React application layouts using @jfdevelops/react-layout. Use when defining shared application shells with defineResourceLayout, generating resource pages with createResourceLayout, creating semantic composables, configuring grouped resource navigation, dispatching route components with createResourceConfig, building create or detail panes, or sharing implementations across multiple resources.
4
+ ---
5
+
6
+ # Build React Layouts
7
+
8
+ Use `@jfdevelops/react-layout` as a typed application-structure layer.
9
+
10
+ Model the application around a central resource definition, a shared layout
11
+ contract, small resource-specific page configurations, and router adapters that
12
+ dispatch through the resource configuration. Create additional layout
13
+ definitions for structurally different surfaces such as side panes.
14
+
15
+ Do not treat the library as a replacement for routing, data fetching, form
16
+ state, or application business logic.
17
+
18
+ ## Follow the workflow
19
+
20
+ 1. Inspect the existing layout definition and resource list.
21
+ 2. Identify whether the requested surface belongs to an existing layout family.
22
+ 3. Add or reuse the resource in the central definition.
23
+ 4. Define shared configuration values with `createProp`.
24
+ 5. Expose semantic layout regions through composables.
25
+ 6. Generate the narrowest suitable page factory.
26
+ 7. Connect navigation and route-state dispatch through the helpers returned by
27
+ `defineResourceLayout`.
28
+ 8. Run the application's typecheck and relevant tests.
29
+
30
+ Read [references/architecture.md](references/architecture.md) before creating
31
+ or substantially restructuring a layout definition.
32
+
33
+ Read [references/factory-patterns.md](references/factory-patterns.md) when
34
+ choosing between direct creation, `forResource`, `forResources`,
35
+ `makeComposable`, or `createComponent(...).asHOF()`.
36
+
37
+ Read
38
+ [references/routing-and-navigation.md](references/routing-and-navigation.md)
39
+ when integrating resource links, route components, pending states, errors, or
40
+ not-found states.
41
+
42
+ ## Apply the core conventions
43
+
44
+ - Import `defineResourceLayout` and `createProp` from
45
+ `@jfdevelops/react-layout`.
46
+ - Import optional preset composables from their dedicated packages.
47
+ - Export the helpers returned by the central `defineResourceLayout` call.
48
+ - Keep the authoritative resource list in the central layout definition.
49
+ - Use resource names that match the application's route vocabulary.
50
+ - Define reusable configuration in `options`.
51
+ - Use `layout.props.include` for definition-time values consumed by the shared
52
+ renderer.
53
+ - Use `layout.props.custom` for props passed when rendering the generated React
54
+ component.
55
+ - Use `createProp.component({ type: 'ReactNode' })` for React content.
56
+ - Mark genuinely optional values with `.optional()`.
57
+ - Give optional custom values defaults in the shared renderer when appropriate.
58
+ - Name composables by semantic role rather than visual implementation.
59
+ - Connect semantic composables to design-system components with `wrapWith`.
60
+ - Use resource-aware composable names so generated components remain
61
+ identifiable in React DevTools.
62
+ - Keep page configuration close to the corresponding resource feature.
63
+ - Keep resource routing generic instead of building a separate route component
64
+ for every resource.
65
+ - Confirm helper signatures against the installed package version and let the
66
+ current TypeScript types determine which resource-configuration branches are
67
+ supported.
68
+ - Reuse the consuming application's router and runtime-validation tools. Do not
69
+ add a schema dependency solely to connect a route parameter to a resource.
70
+ - Create a separate `defineResourceLayout` definition for a surface with a
71
+ meaningfully different structural contract, such as a create or detail pane.
72
+ - Preserve inferred types. Do not use `any`, broad casts, or duplicated
73
+ hand-written resource types to work around a layout mismatch.
74
+
75
+ ## Define semantic composables
76
+
77
+ Prefer names that describe application structure, such as `Layout`, `Header`,
78
+ `Content`, `ResourceLayout`, `ResourceHeader`, `ResourceHeaderActions`, and
79
+ `Breadcrumb`.
80
+
81
+ Do not expose every wrapper element as a composable. Expose a region when
82
+ consumers may need to replace it, compose it, style it, or address it through a
83
+ generated component.
84
+
85
+ ## Separate definition-time and render-time props
86
+
87
+ Use `options` for values supplied while creating a resource layout:
88
+
89
+ ```tsx
90
+ options: {
91
+ title: createProp.component({ type: 'ReactNode' }).optional(),
92
+ }
93
+ ```
94
+
95
+ Make an option available to the shared renderer through `include`:
96
+
97
+ ```tsx
98
+ layout: {
99
+ props: {
100
+ include: {
101
+ title: true,
102
+ },
103
+ },
104
+ }
105
+ ```
106
+
107
+ Use `custom` for values supplied when rendering the resulting component:
108
+
109
+ ```tsx
110
+ layout: {
111
+ props: {
112
+ custom: {
113
+ children: createProp.component({ type: 'ReactNode' }),
114
+ actions: createProp.component({ type: 'ReactNode' }).optional(),
115
+ showHeader: createProp.boolean().optional(),
116
+ },
117
+ },
118
+ }
119
+ ```
120
+
121
+ Keep these categories distinct. Do not turn a definition-time page contract
122
+ into a custom render prop merely because both eventually reach the renderer.
123
+
124
+ ## Verify the result
125
+
126
+ 1. Typecheck the consuming application.
127
+ 2. Run relevant component and route tests.
128
+ 3. Exercise at least one resource page.
129
+ 4. Exercise pending, error, and not-found dispatch if route configuration
130
+ changed.
131
+ 5. Exercise create and detail panes if pane configuration changed.
132
+ 6. Confirm grouped navigation generates the intended resource and route.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Build React Layouts"
3
+ short_description: "Build typed resource-driven React layouts"
4
+ default_prompt: "Use $build-react-layouts to build or refactor a typed resource-based React application layout."
@@ -0,0 +1,184 @@
1
+ # Application Architecture
2
+
3
+ ## Contents
4
+
5
+ - Central layout definition
6
+ - Shared layout contract
7
+ - Semantic composables
8
+ - Preset composables
9
+ - Secondary layout families
10
+ - Feature organization
11
+
12
+ ## Create a central layout definition
13
+
14
+ Create one central definition for resources that share the same application
15
+ shell. Export the generated helpers from that module:
16
+
17
+ ```tsx
18
+ import {
19
+ createProp,
20
+ defineResourceLayout,
21
+ } from '@jfdevelops/react-layout';
22
+
23
+ export const {
24
+ createResourceConfig,
25
+ createResourceLayout,
26
+ createResourceLinks,
27
+ } = defineResourceLayout({
28
+ resources: ['appointments', 'locations', 'services', 'settings'],
29
+ options: {
30
+ title: createProp.component({ type: 'ReactNode' }).optional(),
31
+ },
32
+ layout: {
33
+ // Define the shared contract here.
34
+ },
35
+ });
36
+ ```
37
+
38
+ Import these generated helpers in feature modules instead of defining parallel
39
+ resource lists or rebuilding the application shell.
40
+
41
+ ## Define the shared layout contract
42
+
43
+ Separate definition-time configuration from render-time component props:
44
+
45
+ ```tsx
46
+ layout: {
47
+ props: {
48
+ include: {
49
+ title: true,
50
+ segments: true,
51
+ },
52
+ custom: {
53
+ children: createProp.component({ type: 'ReactNode' }),
54
+ actions: createProp.component({ type: 'ReactNode' }).optional(),
55
+ showHeader: createProp.boolean().optional(),
56
+ },
57
+ },
58
+ }
59
+ ```
60
+
61
+ Use included props for values that describe the generated page. Use custom
62
+ props for content supplied when the generated page component is rendered.
63
+
64
+ Default optional behavioral props in the renderer:
65
+
66
+ ```tsx
67
+ render: (
68
+ { children, actions, showHeader = true, segments },
69
+ { composables },
70
+ ) => {
71
+ // Render the shared shell.
72
+ }
73
+ ```
74
+
75
+ ## Create semantic composables
76
+
77
+ Define named layout regions through the scoped `create` function:
78
+
79
+ ```tsx
80
+ composables: (create) => ({
81
+ Layout: create({
82
+ name: ({ capitalize, resource }) =>
83
+ `${capitalize(resource)}Layout`,
84
+ wrapWith: AppLayout,
85
+ }),
86
+ Header: create({
87
+ name: ({ capitalize, resource }) =>
88
+ `${capitalize(resource)}Header`,
89
+ wrapWith: AppHeader,
90
+ }),
91
+ Content: create({
92
+ name: ({ capitalize, resource }) =>
93
+ `${capitalize(resource)}Content`,
94
+ wrapWith: AppContent,
95
+ }),
96
+ ResourceHeaderActions: create({
97
+ name: ({ capitalize, resource }) =>
98
+ `${capitalize(resource)}ResourceHeaderActions`,
99
+ wrapWith: AppResourceHeaderActions,
100
+ }),
101
+ }),
102
+ ```
103
+
104
+ Make the composable name communicate the region's role. Use `wrapWith` to
105
+ connect that role to the application's design-system implementation.
106
+
107
+ ## Merge preset composables
108
+
109
+ Merge optional preset composables into the same semantic map:
110
+
111
+ ```tsx
112
+ import {
113
+ createBreadcrumbComposable,
114
+ } from '@jfdevelops/react-layout-composable-breadcrumb';
115
+
116
+ composables: (create) => ({
117
+ Layout: create({ name: 'Layout', wrapWith: AppLayout }),
118
+ ...createBreadcrumbComposable(({ segments }) => (
119
+ <AppBreadcrumb segments={segments} />
120
+ )),
121
+ })
122
+ ```
123
+
124
+ Include the preset's required values in `layout.props.include`. Keep
125
+ presentation in the preset renderer and segment definitions in resource page
126
+ configuration.
127
+
128
+ ## Create secondary layout families
129
+
130
+ Create another `defineResourceLayout` when a surface has a distinct structural
131
+ contract. A side pane is not merely a variation of the main page shell:
132
+
133
+ ```tsx
134
+ export const resourcePaneLayout = defineResourceLayout({
135
+ resources: ['create', 'detail'],
136
+ options: {
137
+ title: createProp.component({ type: 'ReactNode' }),
138
+ },
139
+ layout: {
140
+ props: {
141
+ include: {
142
+ title: true,
143
+ },
144
+ custom: {
145
+ children: createProp.component({ type: 'ReactNode' }),
146
+ },
147
+ },
148
+ render: function Render({ children, title }) {
149
+ return (
150
+ <Panel>
151
+ <PanelHeader>
152
+ <PanelTitle>{title}</PanelTitle>
153
+ </PanelHeader>
154
+ <PanelContent>{children}</PanelContent>
155
+ </Panel>
156
+ );
157
+ },
158
+ },
159
+ });
160
+ ```
161
+
162
+ Do not overload the main page layout with pane-specific flags and conditional
163
+ branches.
164
+
165
+ ## Organize by feature
166
+
167
+ Keep the general layout definition centralized and concrete page definitions
168
+ beside their feature:
169
+
170
+ ```text
171
+ admin/
172
+ ├── -utils/
173
+ │ └── layout.tsx
174
+ ├── $resource/
175
+ │ ├── -page-config.tsx
176
+ │ ├── -pane/
177
+ │ │ └── layout.tsx
178
+ │ ├── -appointments/
179
+ │ │ └── pages-config.ts
180
+ │ ├── -locations/
181
+ │ │ └── pages-config.ts
182
+ │ └── index.tsx
183
+ └── route.tsx
184
+ ```
@@ -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