@androbinco/library-cli 0.2.0 → 0.3.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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/src/commands/add.js +107 -0
  3. package/src/commands/list.js +51 -0
  4. package/src/index.js +38 -73
  5. package/src/templates/carousel/components/navigation-buttons.tsx +1 -0
  6. package/src/templates/carousel/components/pagination/bullet.pagination.carousel.tsx +69 -0
  7. package/src/templates/carousel/components/pagination/number.pagination.carousel.tsx +30 -0
  8. package/src/templates/carousel/components/pagination/progress/progress.pagination.carousel.tsx +99 -0
  9. package/src/templates/carousel/components/pagination/progress/use-slide-progress.tsx +31 -0
  10. package/src/templates/carousel/components/pagination.tsx +47 -82
  11. package/src/templates/faqs-accordion/examples/faqs-showcase.tsx +42 -0
  12. package/src/templates/faqs-accordion/faqs-accordion.tsx +70 -0
  13. package/src/templates/faqs-accordion/mock-data.ts +38 -0
  14. package/src/templates/faqs-accordion/types.ts +18 -0
  15. package/src/templates/in-view/data.in-view.ts +1 -1
  16. package/src/templates/in-view/in-view-animation.tsx +7 -7
  17. package/src/templates/in-view/in-view-grid.tsx +22 -20
  18. package/src/templates/in-view/in-view-hidden-text.tsx +7 -7
  19. package/src/templates/in-view/in-view-stroke-line.tsx +6 -5
  20. package/src/templates/lenis/examples/providers.tsx +23 -0
  21. package/src/templates/lenis/lenis-provider.tsx +46 -0
  22. package/src/templates/scroll-components/hooks/use-client-dimensions.ts +21 -0
  23. package/src/templates/scroll-components/parallax/examples/parallax-showcase.tsx +87 -0
  24. package/src/templates/scroll-components/parallax/parallax.css +36 -0
  25. package/src/templates/scroll-components/parallax/parallax.tsx +67 -0
  26. package/src/templates/scroll-components/scale-gallery/components/expanding-element.tsx +40 -0
  27. package/src/templates/scroll-components/scale-gallery/examples/scale-gallery-showcase.tsx +68 -0
  28. package/src/templates/scroll-components/scale-gallery/scale-gallery.tsx +57 -0
  29. package/src/templates/scroll-components/scroll-tracker-showcase.tsx +44 -0
  30. package/src/templates/strapi-dynamic-zone/README.md +157 -0
  31. package/src/templates/strapi-dynamic-zone/dynamic-zone.tsx +113 -0
  32. package/src/templates/strapi-dynamic-zone/examples/page.tsx +53 -0
  33. package/src/templates/strapi-dynamic-zone/examples/renderers.tsx +74 -0
  34. package/src/templates/strapi-dynamic-zone/examples/types.ts +41 -0
  35. package/src/templates/strapi-dynamic-zone/index.ts +11 -0
  36. package/src/templates/strapi-dynamic-zone/types.ts +73 -0
  37. package/src/utils/components.js +187 -5
  38. package/src/utils/files.js +13 -12
  39. package/src/templates/scroll-tracker/examples/scroll-tracker-showcase.tsx +0 -90
  40. /package/src/templates/{scroll-tracker → scroll-components}/scroll-tracker-provider.tsx +0 -0
@@ -0,0 +1,40 @@
1
+ import { motion, useTransform } from 'motion/react';
2
+
3
+ import { cn } from '@/common/utils/classname-builder';
4
+ import { useScrollTrackerContext } from '../../scroll-tracker-provider';
5
+
6
+ export const ExpandingElement = ({
7
+ scaleOffset,
8
+ height,
9
+ children,
10
+ widthOffset,
11
+ className,
12
+ }: {
13
+ scaleOffset: [number, number, number];
14
+ widthOffset: [number, number, number];
15
+ height: number | string;
16
+ className?: string;
17
+ children?: React.ReactNode;
18
+ }) => {
19
+ const { scrollYProgress } = useScrollTrackerContext();
20
+
21
+ const width = useTransform(scrollYProgress, scaleOffset, widthOffset);
22
+
23
+ return (
24
+ <div className="absolute inset-1/2 -translate-x-1/2 -translate-y-1/2" style={{ height }}>
25
+ <motion.div
26
+ className="absolute inset-1/2 flex h-full -translate-x-1/2 -translate-y-1/2 items-center justify-center overflow-hidden"
27
+ style={{ width, height }}
28
+ >
29
+ <div
30
+ className={cn(
31
+ 'absolute inset-1/2 h-full w-full min-w-100 -translate-x-1/2 -translate-y-1/2 object-cover',
32
+ className,
33
+ )}
34
+ >
35
+ {children}
36
+ </div>
37
+ </motion.div>
38
+ </div>
39
+ );
40
+ };
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { Text } from "@/common/ui/text/text";
3
+
4
+ import { ScrollTrackerProvider } from "../scroll-tracker-provider";
5
+ import { ExpandingElement } from "../scale-gallery/components/expanding-element";
6
+ import { useClientDimensions } from "../hooks/use-client-dimensions";
7
+
8
+ export const ScaleGalleryShowcase = () => {
9
+ const { width, height } = useClientDimensions();
10
+
11
+ return (
12
+ <div className="space-y-12">
13
+ <div className="h-screen flex items-center justify-center bg-bg-primary">
14
+ <Text variant="title.4">Scale Gallery Example</Text>
15
+ </div>
16
+
17
+ <ScrollTrackerProvider height={500} offset={["0 0", "1 1"]}>
18
+ <Text className="max-lg:px-4 px-8 mb-8" variant="title.6">
19
+ Gallery with Scale Animation
20
+ </Text>
21
+ <div className="flex h-full w-full flex-col items-center justify-center">
22
+ <ExpandingElement
23
+ className="flex flex-col items-center justify-between gap-2"
24
+ height={height * 0.5}
25
+ scaleOffset={[0.05, 0.2, 0.4]}
26
+ widthOffset={[0, width / 2, width * 0.8]}
27
+ >
28
+ <img
29
+ alt="gallery-1"
30
+ className="h-full w-full object-cover rounded-lg"
31
+ src="https://images.pexels.com/photos/34533069/pexels-photo-34533069.jpeg"
32
+ />
33
+ </ExpandingElement>
34
+
35
+ <ExpandingElement
36
+ className="flex items-center justify-center"
37
+ height={height * 0.5}
38
+ scaleOffset={[0.3, 0.5, 0.7]}
39
+ widthOffset={[0, width / 2, width * 0.8]}
40
+ >
41
+ <img
42
+ alt="gallery-2"
43
+ className="h-full w-full object-cover rounded-lg"
44
+ src="https://images.pexels.com/photos/34712722/pexels-photo-34712722.jpeg"
45
+ />
46
+ </ExpandingElement>
47
+
48
+ <ExpandingElement
49
+ className="flex items-center justify-center"
50
+ height={height * 0.5}
51
+ scaleOffset={[0.5, 0.8, 0.97]}
52
+ widthOffset={[0, width / 2, width * 0.8]}
53
+ >
54
+ <img
55
+ alt="gallery-3"
56
+ className="h-full w-full object-cover rounded-lg"
57
+ src="https://images.pexels.com/photos/3833517/pexels-photo-3833517.jpeg"
58
+ />
59
+ </ExpandingElement>
60
+ </div>
61
+ </ScrollTrackerProvider>
62
+
63
+ <div className="h-screen flex items-center justify-center bg-bg-primary">
64
+ <Text variant="title.4">Gallery ends here</Text>
65
+ </div>
66
+ </div>
67
+ );
68
+ };
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+ import { Text } from '@/common/ui/text/text';
3
+
4
+ import { ScrollTrackerProvider } from '../scroll-tracker-provider';
5
+ import { useClientDimensions } from '../hooks/use-client-dimensions';
6
+
7
+ import { ExpandingElement } from './components/expanding-element';
8
+
9
+ export const ScaleGallery = () => {
10
+ const { width, height } = useClientDimensions();
11
+
12
+ return (
13
+ <ScrollTrackerProvider height={500} offset={['0 0', '1 1']}>
14
+ <Text className="max-lg:px-4 px-8" variant="title.6">
15
+ Scale Gallery
16
+ </Text>
17
+ <div className='flex h-full w-full flex-col items-center justify-center'>
18
+ <ExpandingElement
19
+ className="flex flex-col items-center justify-between gap-2"
20
+ height={height * 0.5}
21
+ scaleOffset={[0.05, 0.2, 0.4]}
22
+ widthOffset={[0, width / 2, width * 0.8]}
23
+ >
24
+ <img
25
+ alt="gallery-image-1"
26
+ className="h-full w-full object-cover"
27
+ src="/img/gallery/gallery-example1.jpg"
28
+ />
29
+ </ExpandingElement>
30
+ <ExpandingElement
31
+ className="flex items-center justify-center"
32
+ height={height * 0.5}
33
+ scaleOffset={[0.3, 0.5, 0.7]}
34
+ widthOffset={[0, width / 2, width * 0.8]}
35
+ >
36
+ <img
37
+ alt="gallery-image-2"
38
+ className="h-full w-full object-cover"
39
+ src="https://images.pexels.com/photos/34533069/pexels-photo-34533069.jpeg"
40
+ />
41
+ </ExpandingElement>
42
+ <ExpandingElement
43
+ className="flex items-center justify-center"
44
+ height={height * 0.5}
45
+ scaleOffset={[0.5, 0.8, 0.97]}
46
+ widthOffset={[0, width / 2, width * 0.8]}
47
+ >
48
+ <img
49
+ alt="gallery-image-3"
50
+ className="h-full w-full object-cover"
51
+ src="https://images.pexels.com/photos/34712722/pexels-photo-34712722.jpeg"
52
+ />
53
+ </ExpandingElement>
54
+ </div>
55
+ </ScrollTrackerProvider>
56
+ );
57
+ };
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+ import { motion, useTransform } from 'motion/react';
3
+ import { ScrollTrackerProvider, useScrollTrackerContext } from './scroll-tracker-provider';
4
+ import { Text } from '@/common/ui/text/text';
5
+
6
+ const ScrollProgressBar = () => {
7
+ const { scrollYProgress } = useScrollTrackerContext();
8
+
9
+ return (
10
+ <motion.div
11
+ className="fixed top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-500 to-purple-500 origin-left"
12
+ style={{ scaleX: scrollYProgress }}
13
+ />
14
+ );
15
+ };
16
+
17
+ const ScrollContent = () => {
18
+ const { scrollYProgress } = useScrollTrackerContext();
19
+ const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0.3, 1, 0.3]);
20
+ const scale = useTransform(scrollYProgress, [0, 0.5, 1], [0.8, 1, 0.8]);
21
+
22
+ return (
23
+ <motion.div
24
+ className="flex flex-col items-center justify-center h-screen gap-4"
25
+ style={{ opacity, scale }}
26
+ >
27
+ <Text className="text-center" variant="title.6">
28
+ Scroll Progress: {scrollYProgress}
29
+ </Text>
30
+ <div className="w-64 h-64 bg-gradient-to-br from-cyan-400 to-blue-500 rounded-lg" />
31
+ </motion.div>
32
+ );
33
+ };
34
+
35
+ export const ScrollTrackerShowcase = () => {
36
+ return (
37
+ <ScrollTrackerProvider height={500}>
38
+ <ScrollProgressBar />
39
+ <div className='flex h-full w-full flex-col items-center justify-center'>
40
+ <ScrollContent />
41
+ </div>
42
+ </ScrollTrackerProvider>
43
+ );
44
+ };
@@ -0,0 +1,157 @@
1
+ # Strapi Dynamic Zone Renderer
2
+
3
+ A type-safe, composable component for rendering Strapi Dynamic Zones in Next.js applications.
4
+
5
+ ## Features
6
+
7
+ - **Full TypeScript support** - Leverages discriminated unions for type-safe component mapping
8
+ - **Single source of truth** - Your Strapi types define both data shape and rendering
9
+ - **Composable** - Mix and match components, add extra props, customize wrappers
10
+ - **Zero dependencies** - Just React
11
+
12
+ ## Installation
13
+
14
+ Copy the `strapi-dynamic-renderer` folder to your project (e.g., `src/lib/strapi-dynamic-renderer`).
15
+
16
+ ## Quick Start
17
+
18
+ ### 1. Define your component types
19
+
20
+ ```typescript
21
+ // types/page-components.ts
22
+ export type HeroComponent = {
23
+ __component: 'hero.hero';
24
+ id: number;
25
+ title: string;
26
+ subtitle: string;
27
+ };
28
+
29
+ export type CTAComponent = {
30
+ __component: 'cta.cta';
31
+ id: number;
32
+ heading: string;
33
+ buttonText: string;
34
+ };
35
+
36
+ // Union of all dynamic zone components
37
+ export type PageComponent = HeroComponent | CTAComponent;
38
+ ```
39
+
40
+ ### 2. Create your renderers
41
+
42
+ ```typescript
43
+ // renderers/page-renderers.tsx
44
+ import type { ComponentRendererMap } from '@/lib/strapi-dynamic-renderer';
45
+ import type { PageComponent } from '@/types/page-components';
46
+
47
+ export const pageRenderers: ComponentRendererMap<PageComponent> = {
48
+ 'hero.hero': ({ title, subtitle }) => (
49
+ <Hero title={title} subtitle={subtitle} />
50
+ ),
51
+ 'cta.cta': ({ heading, buttonText }) => (
52
+ <CTA heading={heading} buttonText={buttonText} />
53
+ ),
54
+ };
55
+ ```
56
+
57
+ ### 3. Use in your pages
58
+
59
+ ```tsx
60
+ // app/page.tsx
61
+ import { DynamicZone } from '@/lib/strapi-dynamic-renderer';
62
+ import { pageRenderers } from '@/renderers/page-renderers';
63
+
64
+ export default function Page({ data }) {
65
+ return (
66
+ <DynamicZone
67
+ components={data.dynamicZone}
68
+ renderers={pageRenderers}
69
+ />
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## API Reference
75
+
76
+ ### `DynamicZone`
77
+
78
+ Renders an array of Strapi dynamic zone components.
79
+
80
+ ```tsx
81
+ <DynamicZone
82
+ components={PageComponent[]} // Required: Array of components
83
+ renderers={ComponentRendererMap} // Required: Renderer map
84
+ getExtraProps={(comp, idx) => {}} // Optional: Extra props per component
85
+ fallback={(comp) => <div />} // Optional: Fallback for unknown types
86
+ getKey={(comp, idx) => string} // Optional: Custom key generator
87
+ />
88
+ ```
89
+
90
+ ### `createDynamicZone`
91
+
92
+ Factory function to create a pre-bound DynamicZone component.
93
+
94
+ ```tsx
95
+ // Create once
96
+ export const PageZone = createDynamicZone(pageRenderers);
97
+
98
+ // Use anywhere
99
+ <PageZone components={data.Page} />
100
+ ```
101
+
102
+ ## Type Utilities
103
+
104
+ ### `ExtractComponentProps<TUnion, TKey>`
105
+
106
+ Extracts props for a specific component type:
107
+
108
+ ```typescript
109
+ type HeroProps = ExtractComponentProps<PageComponent, 'hero.hero'>;
110
+ // { id: number; title: string; subtitle: string }
111
+ ```
112
+
113
+ ### `ComponentRendererMap<TUnion, TExtra>`
114
+
115
+ Creates a type-safe renderer map:
116
+
117
+ ```typescript
118
+ const renderers: ComponentRendererMap<PageComponent, { index: number }> = {
119
+ 'hero.hero': ({ title, index }) => <Hero title={title} position={index} />,
120
+ };
121
+ ```
122
+
123
+ ## Patterns
124
+
125
+ ### Adding extra props to all renderers
126
+
127
+ ```tsx
128
+ type ExtraProps = { index: number; dataSource: string };
129
+
130
+ const renderers: ComponentRendererMap<PageComponent, ExtraProps> = {
131
+ 'hero.hero': ({ title, index, dataSource }) => (
132
+ <Hero title={title} position={index} source={dataSource} />
133
+ ),
134
+ };
135
+
136
+ <DynamicZone
137
+ components={data}
138
+ renderers={renderers}
139
+ getExtraProps={(_, index) => ({ index, dataSource: 'homepage' })}
140
+ />
141
+ ```
142
+
143
+ ### Development fallback
144
+
145
+ ```tsx
146
+ <DynamicZone
147
+ components={data}
148
+ renderers={renderers}
149
+ fallback={(comp) => (
150
+ process.env.NODE_ENV === 'development' ? (
151
+ <div className="border-2 border-dashed border-red-500 p-4">
152
+ Missing renderer: {comp.__component}
153
+ </div>
154
+ ) : null
155
+ )}
156
+ />
157
+ ```
@@ -0,0 +1,113 @@
1
+ import { Fragment } from "react";
2
+ import type { StrapiComponent, DynamicZoneProps } from "./types";
3
+
4
+ /**
5
+ * Renders an array of Strapi dynamic zone components.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <DynamicZone
10
+ * components={page.dynamicZone}
11
+ * renderers={componentRenderers}
12
+ * getExtraProps={(_, index) => ({ index })}
13
+ * />
14
+ * ```
15
+ */
16
+ export function DynamicZone<
17
+ TUnion extends StrapiComponent,
18
+ TExtra extends object = object
19
+ >({
20
+ components,
21
+ renderers,
22
+ getExtraProps,
23
+ fallback,
24
+ getKey,
25
+ }: DynamicZoneProps<TUnion, TExtra>): React.ReactElement | null {
26
+ if (!components || components.length === 0) {
27
+ return null;
28
+ }
29
+
30
+ const defaultGetKey = (component: TUnion, index: number) =>
31
+ component.id ?? `dynamic-${index}`;
32
+
33
+ const keyGenerator = getKey ?? defaultGetKey;
34
+
35
+ const content = components.map((component, index) => {
36
+ const key = keyGenerator(component, index);
37
+ const extraProps = getExtraProps?.(component, index) as TExtra | undefined;
38
+ const { __component, ...componentProps } = component;
39
+
40
+ const Renderer = renderers[__component as keyof typeof renderers] as
41
+ | ((props: Record<string, unknown> & TExtra) => React.ReactElement | null)
42
+ | undefined;
43
+
44
+ if (!Renderer) {
45
+ if (fallback) {
46
+ return <Fragment key={key}>{fallback(component)}</Fragment>;
47
+ }
48
+
49
+ if (process.env.NODE_ENV === "development") {
50
+ console.warn(
51
+ `[DynamicZone] No renderer found for component: "${__component}"`,
52
+ component
53
+ );
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ const props = {
60
+ ...componentProps,
61
+ ...extraProps,
62
+ } as Parameters<typeof Renderer>[0];
63
+
64
+ return (
65
+ <Fragment key={key}>
66
+ <Renderer {...props} />
67
+ </Fragment>
68
+ );
69
+ });
70
+
71
+ return <>{content}</>;
72
+ }
73
+
74
+ /**
75
+ * Creates a type-safe DynamicZone component bound to specific renderers.
76
+ * Useful when you want to pre-configure renderers and reuse across the app.
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * // In your renderers file
81
+ * export const PageZone = createDynamicZone(pageRenderers);
82
+ *
83
+ * // In your page
84
+ * <PageZone
85
+ * components={data.Page}
86
+ * getExtraProps={(_, i) => ({ index: i })}
87
+ * />
88
+ * ```
89
+ */
90
+ export function createDynamicZone<
91
+ TUnion extends StrapiComponent,
92
+ TExtra extends object = object
93
+ >(
94
+ renderers: DynamicZoneProps<TUnion, TExtra>["renderers"],
95
+ defaultFallback?: DynamicZoneProps<TUnion, TExtra>["fallback"]
96
+ ) {
97
+ return function BoundDynamicZone({
98
+ components,
99
+ getExtraProps,
100
+ fallback = defaultFallback,
101
+ getKey,
102
+ }: Omit<DynamicZoneProps<TUnion, TExtra>, "renderers">) {
103
+ return (
104
+ <DynamicZone
105
+ components={components}
106
+ renderers={renderers}
107
+ getExtraProps={getExtraProps}
108
+ fallback={fallback}
109
+ getKey={getKey}
110
+ />
111
+ );
112
+ };
113
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Example: Using DynamicZone in a Next.js page
3
+ */
4
+
5
+ import { DynamicZone } from "../dynamic-zone";
6
+ import { pageRenderers } from "./renderers";
7
+ import type { PageComponent } from "./types";
8
+
9
+ // Example page data type
10
+ type PageData = {
11
+ title: string;
12
+ dynamicZone: PageComponent[];
13
+ };
14
+
15
+ /**
16
+ * Alternative: Create a pre-bound component in renderers.tsx for cleaner usage
17
+ */
18
+
19
+ // Then use it more concisely:
20
+ import { PageDynamicZone } from "./renderers";
21
+
22
+ export function ExamplePageAlt({ data }: { data: PageData }) {
23
+ return (
24
+ <main>
25
+ <h1>{data.title}</h1>
26
+
27
+ <PageDynamicZone
28
+ components={data.dynamicZone}
29
+ getExtraProps={(_, index) => ({ index })}
30
+ />
31
+ </main>
32
+ );
33
+ }
34
+
35
+ /**
36
+ * With custom fallback for unknown components
37
+ */
38
+ export function ExamplePageWithFallback({ data }: { data: PageData }) {
39
+ return (
40
+ <main>
41
+ <DynamicZone
42
+ components={data.dynamicZone}
43
+ renderers={pageRenderers}
44
+ getExtraProps={(_, index) => ({ index })}
45
+ fallback={(component) => (
46
+ <div style={{ padding: 16, background: "#fee", borderRadius: 8 }}>
47
+ Unknown component: {component.__component}
48
+ </div>
49
+ )}
50
+ />
51
+ </main>
52
+ );
53
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Example: Define your component renderers here.
3
+ * Each key matches a __component value from your Strapi types.
4
+ */
5
+
6
+ import type { ComponentRendererMap } from "../types";
7
+ import type { PageComponent, PageExtraProps } from "./types";
8
+
9
+ // Example placeholder components - replace with your actual components
10
+ const Hero = ({ title, subtitle }: { title: string; subtitle: string }) => (
11
+ <section>
12
+ <h1>{title}</h1>
13
+ <p>{subtitle}</p>
14
+ </section>
15
+ );
16
+
17
+ const CTA = ({
18
+ heading,
19
+ buttonText,
20
+ buttonLink,
21
+ }: {
22
+ heading: string;
23
+ buttonText: string;
24
+ buttonLink: string;
25
+ }) => (
26
+ <section>
27
+ <h2>{heading}</h2>
28
+ <a href={buttonLink}>{buttonText}</a>
29
+ </section>
30
+ );
31
+
32
+ const FeatureGrid = ({
33
+ title,
34
+ features,
35
+ }: {
36
+ title: string;
37
+ features: Array<{ id: number; title: string; description: string }>;
38
+ }) => (
39
+ <section>
40
+ <h2>{title}</h2>
41
+ <div>
42
+ {features.map((f) => (
43
+ <div key={f.id}>
44
+ <h3>{f.title}</h3>
45
+ <p>{f.description}</p>
46
+ </div>
47
+ ))}
48
+ </div>
49
+ </section>
50
+ );
51
+
52
+ /**
53
+ * Component renderer map - fully type-safe!
54
+ * TypeScript will error if:
55
+ * - You miss a component type
56
+ * - You use wrong props for a component
57
+ * - You add a component that doesn't exist in the union
58
+ */
59
+ const pageRenderers: ComponentRendererMap<PageComponent, PageExtraProps> = {
60
+ "hero.hero": ({ title, subtitle, index }) => (
61
+ <Hero title={title} subtitle={subtitle} key={index} />
62
+ ),
63
+
64
+ "cta.cta": ({ heading, buttonText, buttonLink }) => (
65
+ <CTA heading={heading} buttonText={buttonText} buttonLink={buttonLink} />
66
+ ),
67
+
68
+ "features.grid": ({ title, features }) => (
69
+ <FeatureGrid title={title} features={features} />
70
+ ),
71
+ };
72
+ // Create a pre-bound component for cleaner usage
73
+ import { createDynamicZone } from "../dynamic-zone";
74
+ export const PageDynamicZone = createDynamicZone(pageRenderers);
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Example: Define your Strapi component types here.
3
+ * This is your single source of truth for component shapes.
4
+ */
5
+
6
+ // Example component types - replace with your actual Strapi types
7
+ export type HeroComponent = {
8
+ __component: 'hero.hero';
9
+ id: number;
10
+ title: string;
11
+ subtitle: string;
12
+ backgroundImage: { url: string };
13
+ };
14
+
15
+ export type CTAComponent = {
16
+ __component: 'cta.cta';
17
+ id: number;
18
+ heading: string;
19
+ buttonText: string;
20
+ buttonLink: string;
21
+ };
22
+
23
+ export type FeatureGridComponent = {
24
+ __component: 'features.grid';
25
+ id: number;
26
+ title: string;
27
+ features: Array<{
28
+ id: number;
29
+ title: string;
30
+ description: string;
31
+ icon: string;
32
+ }>;
33
+ };
34
+
35
+ // Union of all possible dynamic zone components
36
+ export type PageComponent = HeroComponent | CTAComponent | FeatureGridComponent;
37
+
38
+ // Extra props that get passed to every renderer
39
+ export type PageExtraProps = {
40
+ index: number;
41
+ };
@@ -0,0 +1,11 @@
1
+ // Types
2
+ export type {
3
+ StrapiComponent,
4
+ ExtractComponentProps,
5
+ ComponentRendererMap,
6
+ ComponentKeys,
7
+ DynamicZoneProps,
8
+ } from './types';
9
+
10
+ // Components
11
+ export { DynamicZone, createDynamicZone } from './dynamic-zone';