Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic component support for Sheet #538

Open
1 of 2 tasks
hillin opened this issue Dec 21, 2024 · 1 comment
Open
1 of 2 tasks

Dynamic component support for Sheet #538

hillin opened this issue Dec 21, 2024 · 1 comment
Labels
enhancement New feature or request

Comments

@hillin
Copy link
Contributor

hillin commented Dec 21, 2024

Which scope/s are relevant/related to the feature request?

sheet

Information

It would be great if we can use dynamic component in the sheet component, just like we can do in the dialog component - after all, sheet is just another form of dialog!

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No
@hillin hillin added the enhancement New feature or request label Dec 21, 2024
@hillin
Copy link
Contributor Author

hillin commented Dec 21, 2024

I've got a working implementation. Since I suppose there should be some magnitude of rework/refactor to the sheet component to be done in order to get this properly implemented, I'm posting the code here rather than creating a PR for now. Please feel free to grab my code, and let me know if I can help to put a PR together.

hlm-sheet-dynamic-content.component.ts

Ideally we should reuse the HlmSheetContentComponent, but it requires injection of ExposesStateProvider and ExposesSideProvider. Some refactors have to be done before we can reuse it. For now we just create a copy without the injections for dynamic use.

import { Component, ElementRef, Renderer2, computed, effect, inject, input } from '@angular/core';
import { lucideX } from '@ng-icons/lucide';
import { BrnSheetCloseDirective } from '@spartan-ng/brain/sheet';
import { hlm } from '@spartan-ng/ui-core';
import { HlmIconComponent, provideIcons } from '@spartan-ng/ui-icon-helm';
import type { ClassValue } from 'clsx';
import { HlmSheetCloseDirective } from './hlm-sheet-close.directive';
import { BrnDialogRef, injectBrnDialogContext } from '@spartan-ng/brain/dialog';
import { sheetVariants } from './hlm-sheet-content.component';
import { NgComponentOutlet } from '@angular/common';

@Component({
   selector: 'hlm-sheet-dynamic-content',
   standalone: true,
   imports: [NgComponentOutlet, HlmSheetCloseDirective, BrnSheetCloseDirective, HlmIconComponent],
   providers: [provideIcons({ lucideX })],
   host: {
   	'[class]': '_computedClass()',
   	'[attr.data-state]': 'state()',
   },
   template: `
   	@if (component) {
   		<ng-container [ngComponentOutlet]="component" />
   	} @else {
   		<ng-content />
   	}
   	<button brnSheetClose hlm>
   		<span class="sr-only">Close</span>
   		<hlm-icon class="flex h-4 w-4" size="100%" name="lucideX" />
   	</button>
   `,
})
export class HlmSheetDynamicContentComponent {
   private readonly _dialogRef = inject(BrnDialogRef);
   private readonly _dialogContext = injectBrnDialogContext({ optional: true });
   public readonly component = this._dialogContext?.$component;

   public readonly state = computed(() => this._dialogRef?.state() ?? 'closed');
   private readonly _renderer = inject(Renderer2);
   private readonly _element = inject(ElementRef);

   constructor() {
   	effect(() => {
   		this._renderer.setAttribute(this._element.nativeElement, 'data-state', this.state());
   	});
   }

   public readonly userClass = input<ClassValue>('', { alias: 'class' });

   // dirty: need to figure out how to pass the side option here
   protected _computedClass = computed(() => hlm(sheetVariants({ side: (this._dialogRef as any)._options.side }), this.userClass()));

}
hlm-sheet.service.ts
import type { ComponentType } from '@angular/cdk/portal';
import { Injectable, type TemplateRef, inject } from '@angular/core';
import {
	type BrnDialogOptions,
	BrnDialogService,
	DEFAULT_BRN_DIALOG_OPTIONS,
	cssClassesToArray,
} from '@spartan-ng/brain/dialog';
import { HlmSheetContentComponent } from './hlm-sheet-content.component';
import { hlmSheetOverlayClass } from './hlm-sheet-overlay.directive';
import { HlmDialogOptions } from '@spartan-ng/ui-dialog-helm';
import { OverlayPositionBuilder } from '@angular/cdk/overlay';
import { HlmSheetDynamicContentComponent } from './hlm-sheet-dynamic-content.component';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type HlmSheetOptions<DialogContext = any> = HlmDialogOptions<DialogContext> & {
	side: 'top' | 'bottom' | 'left' | 'right';
};

@Injectable({
	providedIn: 'root',
})
export class HlmSheetService {
	private readonly _brnDialogService = inject(BrnDialogService);
	private readonly _positionBuilder = inject(OverlayPositionBuilder);

	private getPositionStrategy(side?: 'top' | 'bottom' | 'left' | 'right') {
		switch (side) {
			case 'top':
				return this._positionBuilder.global().top();
			case 'bottom':
				return this._positionBuilder.global().bottom();
			case 'left':
				return this._positionBuilder.global().left();
			case 'right':
			default:
				return this._positionBuilder.global().right();
		}
	}

	public open(component: ComponentType<unknown> | TemplateRef<unknown>, options?: Partial<HlmSheetOptions>) {
		const mergedOptions = {
			...DEFAULT_BRN_DIALOG_OPTIONS,
			closeDelay: 100,
			positionStrategy: this.getPositionStrategy(options?.side),

			...(options ?? {}),
			backdropClass: cssClassesToArray(`${hlmSheetOverlayClass} ${options?.backdropClass ?? ''}`),
			context: { ...options?.context, $component: component, $dynamicComponentClass: options?.contentClass },
		};

		return this._brnDialogService.open(HlmSheetDynamicContentComponent, undefined, mergedOptions.context, mergedOptions);
	}
}

Usage:

  private readonly _sheets = inject(HlmSheetService);

  const sheetRef = this._sheets.open(MySheetComponent, {
    contentClass: "!w-[540px]",
    side: 'left'
  });

  sheetRef.closed$.subscribe((result) => {
    // ...
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant