diff --git a/README.md b/README.md index 7108e23..67b427d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ export class AppComponent { name: 'John Doe', stories: [ { id: 101, type: 'image', content: 'https://example.com/story1.jpg' }, + { id: 101, type: 'component', content: CustomComponent }, // pass any angular component to render in stories ], }, { @@ -82,6 +83,7 @@ interface NgxStoriesOptions { * Keyboard navigation(Experimental). * Loading: A Loading svg will be shown when story buffers (SVG from Animated SVG Preloaders by SVGBackgrounds.com) * Audio controls: Toggle audio on/off for stories that have audio. +* Component based stories for best control. ## Contributing [Contributing Guide](https://github.com/Gauravdarkslayer/ngx-stories/blob/main/CONTRIBUTING.md) diff --git a/projects/ngx-stories/README.md b/projects/ngx-stories/README.md index 7108e23..67b427d 100644 --- a/projects/ngx-stories/README.md +++ b/projects/ngx-stories/README.md @@ -33,6 +33,7 @@ export class AppComponent { name: 'John Doe', stories: [ { id: 101, type: 'image', content: 'https://example.com/story1.jpg' }, + { id: 101, type: 'component', content: CustomComponent }, // pass any angular component to render in stories ], }, { @@ -82,6 +83,7 @@ interface NgxStoriesOptions { * Keyboard navigation(Experimental). * Loading: A Loading svg will be shown when story buffers (SVG from Animated SVG Preloaders by SVGBackgrounds.com) * Audio controls: Toggle audio on/off for stories that have audio. +* Component based stories for best control. ## Contributing [Contributing Guide](https://github.com/Gauravdarkslayer/ngx-stories/blob/main/CONTRIBUTING.md) diff --git a/projects/ngx-stories/package.json b/projects/ngx-stories/package.json index ee1a30f..bdd4b5a 100644 --- a/projects/ngx-stories/package.json +++ b/projects/ngx-stories/package.json @@ -1,6 +1,6 @@ { "name": "ngx-stories", - "version": "0.1.0", + "version": "0.2.0", "peerDependencies": { "@angular/common": "^18.2.0", "@angular/core": "^18.2.0", diff --git a/projects/ngx-stories/src/lib/interfaces/interfaces.ts b/projects/ngx-stories/src/lib/interfaces/interfaces.ts index 7254d20..1e62662 100644 --- a/projects/ngx-stories/src/lib/interfaces/interfaces.ts +++ b/projects/ngx-stories/src/lib/interfaces/interfaces.ts @@ -1,9 +1,11 @@ -export type StoryType = 'image' | 'video'; +import { Component, Type } from "@angular/core"; + +export type StoryType = 'image' | 'video' | 'component'; export type StoryStateType = 'playing' | 'paused' | 'holding' | 'buffering' ; export interface Story { id: number, type: StoryType, - content: string, + content: string | Type, } export interface StoryGroup { diff --git a/projects/ngx-stories/src/lib/ngx-stories.component.html b/projects/ngx-stories/src/lib/ngx-stories.component.html index 6427e46..c33104c 100644 --- a/projects/ngx-stories/src/lib/ngx-stories.component.html +++ b/projects/ngx-stories/src/lib/ngx-stories.component.html @@ -48,6 +48,8 @@
} @else if(story.type === 'video') { + } @else if(story.type === 'component') { + } diff --git a/projects/ngx-stories/src/lib/ngx-stories.component.ts b/projects/ngx-stories/src/lib/ngx-stories.component.ts index 0f3c326..f17d151 100644 --- a/projects/ngx-stories/src/lib/ngx-stories.component.ts +++ b/projects/ngx-stories/src/lib/ngx-stories.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, ElementRef, Input, Output, QueryList, ViewChildren, HostListener } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, Input, Output, QueryList, ViewChildren, HostListener, ViewChild, ViewContainerRef, Type } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { HammerModule } from '@angular/platform-browser'; import { StoryGroup, StoryStateType } from '../lib/interfaces/interfaces'; @@ -53,12 +53,14 @@ export class NgxStoriesComponent implements AfterViewInit { // Queries the story containers in the view for gesture handling @ViewChildren('storyContainer') storyContainers!: QueryList; + // Add a ViewContainerRef to inject dynamic components + @ViewChild('dynamicComponentContainer', { read: ViewContainerRef, static: false }) dynamicComponentContainer!: ViewContainerRef; constructor( - private storyService: NgxStoriesService - ) { + private storyService: NgxStoriesService, + ) { + - } @@ -96,7 +98,7 @@ export class NgxStoriesComponent implements AfterViewInit { let storyDuration = 5000; // Default duration (in milliseconds) for images if (currentStory.type === 'video') { const videoElement: HTMLVideoElement = document.createElement('video'); - videoElement.src = currentStory.content; + videoElement.src = currentStory.content as string; // Use the video duration or a default if not available videoElement.onloadedmetadata = () => { @@ -104,13 +106,20 @@ export class NgxStoriesComponent implements AfterViewInit { storyDuration = videoElement.duration * 1000; // Convert to milliseconds this.startProgressInterval(storyDuration); }; + } else if (currentStory.type === 'component') { + setTimeout(() => { + // Small delay to detect changes in DOM + this.storyService.renderComponent(this.dynamicComponentContainer, currentStory.content as Type); + this.onContentLoaded(); + this.startProgressInterval(5000); // Default duration for components + }, 100); } else { // Handling for images const imageElement = document.createElement('img'); - imageElement.src = currentStory.content; + imageElement.src = currentStory.content as string; // Check if the image is cached - if (this.storyService.isImageCached(currentStory.content)) { + if (this.storyService.isImageCached(currentStory.content as string)) { this.onContentLoaded(); // Call immediately if cached this.startProgressInterval(storyDuration); // Use default image duration } else { diff --git a/projects/ngx-stories/src/lib/ngx-stories.service.ts b/projects/ngx-stories/src/lib/ngx-stories.service.ts index d3e4f20..ca8dd7c 100644 --- a/projects/ngx-stories/src/lib/ngx-stories.service.ts +++ b/projects/ngx-stories/src/lib/ngx-stories.service.ts @@ -1,4 +1,4 @@ -import { ElementRef, Injectable, QueryList } from "@angular/core"; +import { ElementRef, Injectable, QueryList, Type, ViewContainerRef } from "@angular/core"; import { NgxStoriesOptions, StoryGroup } from "./interfaces/interfaces"; @Injectable({ providedIn: 'root', @@ -73,5 +73,20 @@ export class NgxStoriesService { img.src = src; return img.complete; } - + + /** + * Dynamically creates and injects a component into the specified ViewContainerRef. + * @param containerRef The ViewContainerRef where the component should be injected. + * @param component The component class (Type) to be dynamically created. + */ + renderComponent( + containerRef: ViewContainerRef, + component: Type, + ): T { + // Clear previous components in the container (if any) + containerRef?.clear(); + + const componentRef = containerRef.createComponent(component); + return componentRef.instance; + } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 34dca59..4166cb7 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, Type } from '@angular/core'; import { NgxStoriesComponent, NgxStoriesOptions, StoryGroup } from '../../projects/ngx-stories/src/public-api'; +import { CustomComponentComponent } from './components/custom-component/custom-component.component'; @Component({ selector: 'app-root', @@ -24,10 +25,11 @@ export class AppComponent { name: 'Steve Smith', stories: [ { id: 1, type: 'image', content: 'https://i.ibb.co/ZMVy3KN/pexels-rpnickson-2486168.jpg' }, - { id: 2, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' }, - { id: 3, type: 'video', content: 'https://www.w3schools.com/html/mov_bbb.mp4' }, - { id: 4, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4' }, - { id: 5, type: 'image', content: 'https://d38b044pevnwc9.cloudfront.net/cutout-nuxt/enhancer/3.jpg' } + { id: 2, type: 'component', content: CustomComponentComponent }, + { id: 3, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' }, + { id: 4, type: 'video', content: 'https://www.w3schools.com/html/mov_bbb.mp4' }, + { id: 5, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4' }, + { id: 6, type: 'image', content: 'https://d38b044pevnwc9.cloudfront.net/cutout-nuxt/enhancer/3.jpg' } ] }, { diff --git a/src/app/components/custom-component/custom-component.component.html b/src/app/components/custom-component/custom-component.component.html new file mode 100644 index 0000000..8cce1ea --- /dev/null +++ b/src/app/components/custom-component/custom-component.component.html @@ -0,0 +1,14 @@ +
+

Custom Component

+

This is a beautifully styled custom component, designed to showcase how dynamic rendering can be visually appealing.

+ +

Possibilities are endless. For example, here's a simple code snippet:

+
console.log('Dynamic rendering!');
+ + Example image + +

And there's more! You can extend this component further to fit your use case.

+ +
+ \ No newline at end of file diff --git a/src/app/components/custom-component/custom-component.component.scss b/src/app/components/custom-component/custom-component.component.scss new file mode 100644 index 0000000..5e458d3 --- /dev/null +++ b/src/app/components/custom-component/custom-component.component.scss @@ -0,0 +1,58 @@ +.custom-container { + width: 100%; + background: linear-gradient(to right, #ff7f50, #ff6f61); /* Coral gradient */ + padding-top: 0.6rem; + padding-bottom: 0.6rem; + margin: 0rem auto; + color: #fff; + text-align: center; + font-family: Arial, sans-serif; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + } + + .custom-container h3 { + font-size: 2rem; + margin-bottom: 2rem; + color: #333; + background-color: #f4f4f4; + } + + .custom-container p { + margin: 0.8rem 0; + font-size: 1rem; + } + + .code-block { + background-color: #f4f4f4; + color: #333; + padding: 0.8rem; + border-radius: 8px; + font-family: 'Courier New', Courier, monospace; + font-size: 0.9rem; + text-align: left; + display: inline-block; + margin: 1rem auto; + } + + .custom-image { + width: 100%; + height: auto; + // margin: 1rem 0; + border-radius: 8px; + } + + .custom-link { + display: inline-block; + margin-top: 1rem; + text-decoration: none; + color: #fff; + background-color: #ff5722; + padding: 0.5rem 1rem; + border-radius: 8px; + transition: background-color 0.3s; + } + + .custom-link:hover { + background-color: #e64a19; + } + \ No newline at end of file diff --git a/src/app/components/custom-component/custom-component.component.ts b/src/app/components/custom-component/custom-component.component.ts new file mode 100644 index 0000000..5b409a2 --- /dev/null +++ b/src/app/components/custom-component/custom-component.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-custom-component', + standalone: true, + imports: [], + templateUrl: './custom-component.component.html', + styleUrl: './custom-component.component.scss' +}) +export class CustomComponentComponent { + +}