From c50ce18b3ad754466a9bb782aa56b923afb3fbad Mon Sep 17 00:00:00 2001 From: Oleg Pimenov Date: Tue, 17 Sep 2024 17:18:52 +0300 Subject: [PATCH] fix(avatar): supported SSR --- .commitlintrc.cjs | 1 + .../src/components/mdx/PropsTable.astro | 4 +-- .../content/primitives/components/avatar.mdx | 16 ++++++++++++ .../avatar/src/avatar-fallback.directive.ts | 25 ++++++++++++------- .../avatar/src/avatar-image.directive.ts | 19 ++++++++------ 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/.commitlintrc.cjs b/.commitlintrc.cjs index 7c9fb788..15b2e520 100644 --- a/.commitlintrc.cjs +++ b/.commitlintrc.cjs @@ -28,6 +28,7 @@ const config = { [ 'primitives', 'accordion', + 'avatar', 'collapsible', 'dialog', 'radio', diff --git a/apps/radix-docs/src/components/mdx/PropsTable.astro b/apps/radix-docs/src/components/mdx/PropsTable.astro index 6fd619e2..003cf956 100644 --- a/apps/radix-docs/src/components/mdx/PropsTable.astro +++ b/apps/radix-docs/src/components/mdx/PropsTable.astro @@ -5,9 +5,9 @@ const { name } = Astro.props; const directive = getDirectiveByName(name); -const { inputsClass = [], propertiesClass = [] } = (directive ?? getComponentByName(name)) || {}; +const { inputsClass = [], propertiesClass = [], outputsClass = [] } = (directive ?? getComponentByName(name)) || {}; -const data = [...inputsClass, ...propertiesClass]; +const data = [...inputsClass, ...propertiesClass, ...outputsClass]; ---
diff --git a/apps/radix-docs/src/content/primitives/components/avatar.mdx b/apps/radix-docs/src/content/primitives/components/avatar.mdx index 67ecef2e..0e706b28 100644 --- a/apps/radix-docs/src/content/primitives/components/avatar.mdx +++ b/apps/radix-docs/src/content/primitives/components/avatar.mdx @@ -26,4 +26,20 @@ Import the component. ## API Reference +### Image +`RdxAvatarImageDirective` + +The image to render. +By default it will only render when it has loaded. You can use the `onLoadingStatusChange` handler if you need more control. + + + +### Fallback +`RdxAvatarFallbackDirective` + +An element that renders when the image hasn't loaded. This means whilst it's loading, or if there was an error. +If you notice a flash during loading, you can provide a `delayMs` prop to delay its rendering +so it only renders for those with slower connections. + +For more control, use the `onLoadingStatusChange` handler on `rdxAvatarImage`. diff --git a/packages/primitives/avatar/src/avatar-fallback.directive.ts b/packages/primitives/avatar/src/avatar-fallback.directive.ts index f8cab40f..525c789b 100644 --- a/packages/primitives/avatar/src/avatar-fallback.directive.ts +++ b/packages/primitives/avatar/src/avatar-fallback.directive.ts @@ -1,4 +1,5 @@ -import { Directive, inject, Input, NgZone, numberAttribute, OnDestroy, OnInit } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; +import { Directive, inject, Input, NgZone, numberAttribute, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { injectAvatar } from './avatar-root.directive'; import { injectAvatarConfig } from './avatar.config'; @@ -21,6 +22,8 @@ export class RdxAvatarFallbackDirective implements RdxAvatarFallbackProps, OnIni private readonly ngZone = inject(NgZone); + private readonly platformId = inject(PLATFORM_ID); + /** * Define a delay before the fallback is shown. * This is useful to only show the fallback for those with slower connections. @@ -40,16 +43,20 @@ export class RdxAvatarFallbackDirective implements RdxAvatarFallbackProps, OnIni private timeoutId: number | null = null; ngOnInit(): void { - this.ngZone.runOutsideAngular(() => { - this.timeoutId = window.setTimeout(() => (this.delayElapsed = true), this.delayMs); - }); + if (isPlatformBrowser(this.platformId)) { + this.ngZone.runOutsideAngular(() => { + this.timeoutId = globalThis.setTimeout(() => { + this.ngZone.run(() => { + this.delayElapsed = true; + }); + }, this.delayMs); + }); + } } ngOnDestroy(): void { - this.ngZone.run(() => { - if (this.timeoutId) { - window.clearTimeout(this.timeoutId); - } - }); + if (isPlatformBrowser(this.platformId) && this.timeoutId !== null) { + globalThis.clearTimeout(this.timeoutId); + } } } diff --git a/packages/primitives/avatar/src/avatar-image.directive.ts b/packages/primitives/avatar/src/avatar-image.directive.ts index 33f3a24f..08b85acd 100644 --- a/packages/primitives/avatar/src/avatar-image.directive.ts +++ b/packages/primitives/avatar/src/avatar-image.directive.ts @@ -10,8 +10,8 @@ export interface RdxAvatarImageProps { exportAs: 'rdxAvatarImage', standalone: true, host: { - '(load)': '_onLoad()', - '(error)': '_onError()' + '(load)': 'onLoad()', + '(error)': 'onError()' } }) export class RdxAvatarImageDirective implements RdxAvatarImageProps, OnInit { @@ -19,7 +19,8 @@ export class RdxAvatarImageDirective implements RdxAvatarImageProps, OnInit { private readonly elementRef = inject>(ElementRef); - /* By default, it will only render when it has loaded. + /** + * By default, it will only render when it has loaded. * You can use the `onLoadingStatusChange` handler if you need more control. */ @Output() onLoadingStatusChange = new EventEmitter(); @@ -27,24 +28,28 @@ export class RdxAvatarImageDirective implements RdxAvatarImageProps, OnInit { ngOnInit(): void { this.avatar._setState('loading'); - if (!this.elementRef.nativeElement.src) { + if (!this.nativeElement.src) { this.avatar._setState('error'); } - if (this.elementRef.nativeElement.complete) { + if (this.nativeElement.complete) { this.avatar._setState('loaded'); } this.onLoadingStatusChange.emit(this.avatar._state()); } - _onLoad(): void { + protected onLoad(): void { this.avatar._setState('loaded'); this.onLoadingStatusChange.emit('loaded'); } - _onError(): void { + protected onError(): void { this.avatar._setState('error'); this.onLoadingStatusChange.emit('error'); } + + get nativeElement() { + return this.elementRef.nativeElement; + } }