From 79c4662a26f95f420020ea72a9b3f1be0e5c0267 Mon Sep 17 00:00:00 2001 From: Maria Garcia Luque Date: Thu, 23 Jul 2026 10:59:15 +0200 Subject: [PATCH 1/5] [FIR-296] Extend ff-avatar with numeric size, name-derived initials, round/cornerRadius and tone Adds a literal pixel size, automatic initials from `name` (explicit `initials` still overrides it), an optional square shape via `round`/`cornerRadius`, and a decorative `tone` background palette mirroring ff-badge's color axis. Additive only: default rendering (size 'md', round circle, no tone) is unchanged. --- packages/design-system/src/index.ts | 2 +- .../ff-avatar/ff-avatar.component.html | 2 +- .../ff-avatar/ff-avatar.component.scss | 51 ++++++ .../ff-avatar/ff-avatar.component.spec.ts | 146 +++++++++++++++++ .../ff-avatar/ff-avatar.component.ts | 148 ++++++++++++++++-- .../src/lib/primitives/ff-avatar/index.ts | 2 +- 6 files changed, 335 insertions(+), 16 deletions(-) diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts index 0912997..b77e0f0 100644 --- a/packages/design-system/src/index.ts +++ b/packages/design-system/src/index.ts @@ -54,7 +54,7 @@ export type { FfChipVariant, FfChipSize } from './lib/primitives/ff-chip'; export { FfLinkComponent } from './lib/primitives/ff-link'; export type { FfLinkVariant, FfLinkTarget } from './lib/primitives/ff-link'; export { FfAvatarComponent } from './lib/primitives/ff-avatar'; -export type { FfAvatarSize } from './lib/primitives/ff-avatar'; +export type { FfAvatarSize, FfAvatarTone } from './lib/primitives/ff-avatar'; export { FfTooltipComponent } from './lib/primitives/ff-tooltip'; export type { FfTooltipPosition } from './lib/primitives/ff-tooltip'; export { FfIconComponent, FF_ICONS, provideFfIcons } from './lib/primitives/ff-icon'; diff --git a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.html b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.html index 66c106a..84a4c9c 100644 --- a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.html +++ b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.html @@ -6,5 +6,5 @@ (error)="onImgError()" /> } @else { - {{ displayInitials }} + {{ displayInitials() }} } diff --git a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.scss b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.scss index aae2c35..d71ce6f 100644 --- a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.scss +++ b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.scss @@ -53,4 +53,55 @@ &--lg .ff-avatar__initials { font-size: var(--ff-font-size-md); } + + // A numeric `size` sets width/height inline on the host; the class only + // owns the initials font size, scaled proportionally from the component. + &--custom .ff-avatar__initials { + font-size: var(--ff-avatar-initials-size, var(--ff-font-size-sm)); + } + + // --- Shape --- + + // `round="false"`: the corner radius comes from `cornerRadius` (component + // token with a fallback, same override pattern as the background/color). + &--square { + border-radius: var(--ff-avatar-radius, var(--ff-radius-md)); + } + + // --- Tones (decorative background palette, independent of content) --- + + &--tone-primary { + background-color: var(--ff-avatar-tone-primary-bg, var(--ff-color-primary-100)); + color: var(--ff-avatar-tone-primary-color, var(--ff-color-primary-700)); + } + + &--tone-secondary { + background-color: var(--ff-avatar-tone-secondary-bg, var(--ff-color-secondary-100)); + color: var(--ff-avatar-tone-secondary-color, var(--ff-color-secondary-700)); + } + + &--tone-success { + background-color: var(--ff-avatar-tone-success-bg, var(--ff-color-success-100)); + color: var(--ff-avatar-tone-success-color, var(--ff-color-success-700)); + } + + &--tone-warning { + background-color: var(--ff-avatar-tone-warning-bg, var(--ff-color-warning-100)); + color: var(--ff-avatar-tone-warning-color, var(--ff-color-warning-700)); + } + + &--tone-error { + background-color: var(--ff-avatar-tone-error-bg, var(--ff-color-error-100)); + color: var(--ff-avatar-tone-error-color, var(--ff-color-error-700)); + } + + &--tone-info { + background-color: var(--ff-avatar-tone-info-bg, var(--ff-color-info-100)); + color: var(--ff-avatar-tone-info-color, var(--ff-color-info-700)); + } + + &--tone-neutral { + background-color: var(--ff-avatar-tone-neutral-bg, var(--ff-color-neutral-100)); + color: var(--ff-avatar-tone-neutral-color, var(--ff-color-neutral-700)); + } } diff --git a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts index 97aa3f6..21682d2 100644 --- a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts +++ b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts @@ -138,4 +138,150 @@ describe('FfAvatarComponent', () => { expect(initials).toBeTruthy(); expect(initials.textContent.trim()).toBe('JD'); }); + + describe('name → initials derivation', () => { + function initialsText(): string { + const el = fixture.nativeElement.querySelector('.ff-avatar__initials'); + return el.textContent.trim(); + } + + it('should derive a single initial from a one-word name', () => { + fixture.componentRef.setInput('name', 'Madonna'); + fixture.detectChanges(); + + expect(initialsText()).toBe('M'); + }); + + it('should derive first + last initials from a two-word name', () => { + fixture.componentRef.setInput('name', 'Jane Doe'); + fixture.detectChanges(); + + expect(initialsText()).toBe('JD'); + }); + + it('should derive first + last initials from a compound (3+ word) name, ignoring middle words', () => { + fixture.componentRef.setInput('name', 'Maria Garcia Luque'); + fixture.detectChanges(); + + expect(initialsText()).toBe('ML'); + }); + + it('should collapse extra/irregular whitespace between words', () => { + fixture.componentRef.setInput('name', ' Jane Doe '); + fixture.detectChanges(); + + expect(initialsText()).toBe('JD'); + }); + + it('should uppercase derived initials', () => { + fixture.componentRef.setInput('name', 'jane doe'); + fixture.detectChanges(); + + expect(initialsText()).toBe('JD'); + }); + + it('should derive initials from unicode names', () => { + fixture.componentRef.setInput('name', 'Émile Zola'); + fixture.detectChanges(); + + expect(initialsText()).toBe('ÉZ'); + }); + + it('should render empty initials for an empty name', () => { + fixture.componentRef.setInput('name', ''); + fixture.detectChanges(); + + expect(initialsText()).toBe(''); + }); + + it('should render empty initials for a whitespace-only name', () => { + fixture.componentRef.setInput('name', ' '); + fixture.detectChanges(); + + expect(initialsText()).toBe(''); + }); + + it('should let the explicit initials input override the name derivation', () => { + fixture.componentRef.setInput('name', 'Jane Doe'); + fixture.componentRef.setInput('initials', 'XX'); + fixture.detectChanges(); + + expect(initialsText()).toBe('XX'); + }); + }); + + describe('numeric size', () => { + it('should apply the "custom" size class for a numeric size', () => { + fixture.componentRef.setInput('size', 72); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.classList.contains('ff-avatar--custom')).toBe(true); + }); + + it('should set inline width/height for a numeric size', () => { + fixture.componentRef.setInput('size', 72); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.style.width).toBe('72px'); + expect(hostEl.style.height).toBe('72px'); + }); + + it('should not set inline width/height for a predefined size', () => { + fixture.componentRef.setInput('size', 'lg'); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.style.width).toBe(''); + expect(hostEl.style.height).toBe(''); + }); + + it('should set a proportional initials font size for a numeric size', () => { + fixture.componentRef.setInput('size', 100); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.style.getPropertyValue('--ff-avatar-initials-size').trim()).toBe('40px'); + }); + }); + + describe('round / cornerRadius', () => { + it('should default to round (no square modifier class)', () => { + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.classList.contains('ff-avatar--square')).toBe(false); + }); + + it('should apply the square modifier class when round is false', () => { + fixture.componentRef.setInput('round', false); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.classList.contains('ff-avatar--square')).toBe(true); + }); + + it('should set the corner radius custom property when provided', () => { + fixture.componentRef.setInput('round', false); + fixture.componentRef.setInput('cornerRadius', '12px'); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.style.getPropertyValue('--ff-avatar-radius').trim()).toBe('12px'); + }); + }); + + describe('tone', () => { + it('should not apply a tone class by default', () => { + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.className).not.toContain('ff-avatar--tone-'); + }); + + it('should apply the matching tone class when set', () => { + fixture.componentRef.setInput('tone', 'success'); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.classList.contains('ff-avatar--tone-success')).toBe(true); + }); + }); }); diff --git a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts index d990b91..458be92 100644 --- a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts +++ b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts @@ -2,23 +2,67 @@ import { ChangeDetectionStrategy, Component, ViewEncapsulation, + computed, input, signal, } from '@angular/core'; -/** Predefined size of the avatar. */ -export type FfAvatarSize = 'sm' | 'md' | 'lg'; +/** + * Predefined size of the avatar (`'sm'` 32px, `'md'` 40px, `'lg'` 56px), or a + * literal pixel number for an arbitrary custom size. + */ +export type FfAvatarSize = 'sm' | 'md' | 'lg' | number; + +/** + * Decorative background tone of the avatar, independent of image/initials + * content. Mirrors the semantic palette axis of `ff-badge`'s `color` input + * (brand + status palettes) so the two primitives read the same "tone + * vocabulary" across a page. + */ +export type FfAvatarTone = + | 'primary' + | 'secondary' + | 'success' + | 'warning' + | 'error' + | 'info' + | 'neutral'; + +/** + * Derives up to two initials from a full name: the first grapheme of the + * first word and, when there is more than one word, the first grapheme of + * the last word. Extra whitespace is ignored and the result is uppercased. + * + * @param name - Full name (e.g. `'Maria Garcia Luque'`). May be empty. + * @returns The derived initials (`''`, 1 or 2 characters). + */ +function deriveInitialsFromName(name: string): string { + const words = name.trim().split(/\s+/).filter(Boolean); + if (words.length === 0) return ''; + + const firstChar = Array.from(words[0])[0] ?? ''; + if (words.length === 1) return firstChar.toUpperCase(); + + const lastChar = Array.from(words[words.length - 1])[0] ?? ''; + return (firstChar + lastChar).toUpperCase(); +} /** * Firefly avatar atom. * - * Circular avatar displaying an image or initials as fallback. - * Falls back to initials automatically when the image fails to load. + * Circular (or square, via `round`) avatar displaying an image, or falling + * back to initials when `src` is empty or the image fails to load. Initials + * come from the explicit `initials` input when set, otherwise they are + * derived automatically from `name` (first + last word, uppercased). `size` + * accepts the predefined tokens or a literal pixel number; `tone` applies a + * decorative background palette independent of the image/initials content. * * @example * ```html * - * + * + * + * * ``` */ @Component({ @@ -29,31 +73,109 @@ export type FfAvatarSize = 'sm' | 'md' | 'lg'; changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { - '[class]': '"ff-avatar ff-avatar--" + size()', + '[class]': 'hostClasses()', 'role': 'img', - '[attr.aria-label]': 'alt() || initials() || null', + '[attr.aria-label]': 'alt() || displayInitials() || null', + '[style.width.px]': 'numericSizePx()', + '[style.height.px]': 'numericSizePx()', + '[style.--ff-avatar-initials-size]': 'initialsFontSize()', + '[style.--ff-avatar-radius]': 'cornerRadius() ?? null', }, }) export class FfAvatarComponent { /** Image URL. When empty, initials are displayed. */ readonly src = input(''); - /** Fallback text (1–2 characters). Displayed when `src` is empty or fails to load. */ + /** + * Explicit fallback text (1–2 characters). Takes precedence over the + * initials derived from `name`. Displayed when `src` is empty or fails to + * load. + */ readonly initials = input(''); + /** + * Full name used to derive initials automatically (first + last word, + * uppercased) when `initials` is not set. + * + * @example + * ```html + * + * ``` + */ + readonly name = input(''); + /** Alt text for the image. Also used as aria-label on the host. */ readonly alt = input(''); - /** Avatar size: `'sm'` (32px) | `'md'` (40px) | `'lg'` (56px). Defaults to `'md'`. */ + /** + * Avatar size: `'sm'` (32px) | `'md'` (40px) | `'lg'` (56px), or a literal + * pixel number for an arbitrary size. Defaults to `'md'`. Initials font + * size scales proportionally when a numeric size is used. + */ readonly size = input('md'); + /** + * Whether the avatar is fully rounded (`true`, default) or square with + * rounded corners (`false`, sized via `cornerRadius`). + */ + readonly round = input(true); + + /** + * Corner radius applied when `round` is `false`, as a CSS length (e.g. + * `'8px'`, `'20%'`). Falls back to `--ff-radius-md` when unset. Has no + * effect while `round` is `true`. + */ + readonly cornerRadius = input(); + + /** + * Decorative background tone, independent of the image/initials content. + * When unset, the avatar keeps its default neutral background. + */ + readonly tone = input(); + /** @internal Whether the image failed to load. */ readonly imgError = signal(false); - /** @internal Truncated initials (max 2 chars, uppercase). */ - get displayInitials(): string { - return this.initials().slice(0, 2).toUpperCase(); - } + /** @internal Pixel value of `size` when it is a literal number, else `null`. */ + protected readonly numericSizePx = computed(() => { + const s = this.size(); + return typeof s === 'number' ? s : null; + }); + + /** + * @internal `font-size` (px) for the initials label, proportional to a + * numeric `size`; `null` when `size` is a predefined token (CSS classes + * own the font size in that case). + */ + protected readonly initialsFontSize = computed(() => { + const px = this.numericSizePx(); + return px === null ? null : `${Math.round(px * 0.4)}px`; + }); + + /** @internal BEM size modifier: the token itself, or `'custom'` for a numeric size. */ + protected readonly sizeClass = computed(() => { + const s = this.size(); + return typeof s === 'number' ? 'custom' : s; + }); + + /** @internal Host BEM classes derived from the active axes. */ + protected readonly hostClasses = computed(() => { + const classes = ['ff-avatar', `ff-avatar--${this.sizeClass()}`]; + if (!this.round()) classes.push('ff-avatar--square'); + const tone = this.tone(); + if (tone) classes.push(`ff-avatar--tone-${tone}`); + return classes.join(' '); + }); + + /** + * @internal Truncated, uppercased initials (max 2 chars): the explicit + * `initials` input when set, otherwise derived from `name`. + */ + protected readonly displayInitials = computed(() => { + const explicit = this.initials(); + if (explicit) return explicit.slice(0, 2).toUpperCase(); + return deriveInitialsFromName(this.name()); + }); /** @internal Whether to show the image. */ get showImage(): boolean { diff --git a/packages/design-system/src/lib/primitives/ff-avatar/index.ts b/packages/design-system/src/lib/primitives/ff-avatar/index.ts index f336eb2..8ebfdd0 100644 --- a/packages/design-system/src/lib/primitives/ff-avatar/index.ts +++ b/packages/design-system/src/lib/primitives/ff-avatar/index.ts @@ -1,2 +1,2 @@ export { FfAvatarComponent } from './ff-avatar.component'; -export type { FfAvatarSize } from './ff-avatar.component'; +export type { FfAvatarSize, FfAvatarTone } from './ff-avatar.component'; From 5f9878a8cfedbca14c4455a7d003928fdedcc681 Mon Sep 17 00:00:00 2001 From: Maria Garcia Luque Date: Thu, 23 Jul 2026 10:59:22 +0200 Subject: [PATCH 2/5] [FIR-296] Update AvatarContract for the new ff-avatar inputs Widens size to accept a literal pixel number and adds name, round, cornerRadius and tone, matching the ff-avatar implementation. --- .../src/lib/primitives/avatar.contract.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/design-system-contract/src/lib/primitives/avatar.contract.ts b/packages/design-system-contract/src/lib/primitives/avatar.contract.ts index 9df128e..5116ff0 100644 --- a/packages/design-system-contract/src/lib/primitives/avatar.contract.ts +++ b/packages/design-system-contract/src/lib/primitives/avatar.contract.ts @@ -3,9 +3,12 @@ import type { DsComponentContract } from '../contract.types'; /** * Contract of the `ff-avatar` primitive. * - * Circular avatar displaying an image, falling back automatically to - * `initials` (max 2 characters, uppercased) when `src` is empty or the - * image fails to load. + * Circular (or square, via `round`) avatar displaying an image, falling back + * automatically to initials when `src` is empty or the image fails to load. + * Initials come from the explicit `initials` input when set, otherwise they + * are derived from `name` (first + last word, max 2 characters, uppercased). + * `size` accepts the predefined tokens or a literal pixel number, and `tone` + * applies a decorative background palette independent of the content. */ export const AvatarContract: DsComponentContract = { selector: 'ff-avatar', @@ -13,15 +16,23 @@ export const AvatarContract: DsComponentContract = { inputs: { src: { type: 'string', required: false, default: "''" }, initials: { type: 'string', required: false, default: "''" }, + name: { type: 'string', required: false, default: "''" }, alt: { type: 'string', required: false, default: "''" }, - size: { type: "'sm' | 'md' | 'lg'", required: false, default: "'md'" }, + size: { type: "'sm' | 'md' | 'lg' | number", required: false, default: "'md'" }, + round: { type: 'boolean', required: false, default: 'true' }, + cornerRadius: { type: 'string | undefined', required: false, default: 'undefined' }, + tone: { + type: "'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'neutral' | undefined", + required: false, + default: 'undefined', + }, }, outputs: {}, behavior: { hostAttributeOwnership: ['class', 'role', 'aria-label'], aria: [ 'host has role="img"', - 'host aria-label is derived from alt (falling back to initials)', + 'host aria-label is derived from alt (falling back to the resolved initials — explicit `initials` or derived from `name`)', ], }, }; From 9e17ca71deee47a2574737fe98c0aba0851d738b Mon Sep 17 00:00:00 2001 From: Maria Garcia Luque Date: Thu, 23 Jul 2026 10:59:34 +0200 Subject: [PATCH 3/5] [FIR-296] Add catalog demo sections for ff-avatar's new axes Covers numeric size, name-derived initials (with explicit override), round/cornerRadius and all FfAvatarTone values, following the existing DemoSection idiom. --- .../src/app/pages/catalog/avatar-page.ts | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/apps/playground/src/app/pages/catalog/avatar-page.ts b/apps/playground/src/app/pages/catalog/avatar-page.ts index fd5fb99..003cab5 100644 --- a/apps/playground/src/app/pages/catalog/avatar-page.ts +++ b/apps/playground/src/app/pages/catalog/avatar-page.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FfAvatarComponent, FfAvatarSize, + FfAvatarTone, } from '@fireflyframework/design-system'; import { DemoSection } from '../../shared/demo-section'; @@ -29,6 +30,48 @@ import { DemoSection } from '../../shared/demo-section'; } + + @for (px of numericSizes; track px) { + + } + + + + + + + + ← the last one keeps "XX": the explicit initials input wins over name + + + + + + + + + + @for (t of tones; track t) { + + } + + `, + numericSize: ``, + name: ` +`, + shape: ` + +`, + tones: ` +`, fallback: ``, }; } From f17432178d6473213ec55452bcd626bc5a4f06a3 Mon Sep 17 00:00:00 2001 From: Maria Garcia Luque Date: Thu, 23 Jul 2026 10:59:39 +0200 Subject: [PATCH 4/5] [FIR-296] Document ff-avatar's numeric size/name/round/tone extension Adds Unreleased entries to design-system and design-system-contract changelogs for the additive ff-avatar API. --- packages/design-system-contract/CHANGELOG.md | 3 +++ packages/design-system/CHANGELOG.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/design-system-contract/CHANGELOG.md b/packages/design-system-contract/CHANGELOG.md index da22c3a..30bf952 100644 --- a/packages/design-system-contract/CHANGELOG.md +++ b/packages/design-system-contract/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- `AvatarContract`: `size` type widened to accept a literal pixel number; new `name`, `round`, `cornerRadius` and `tone` inputs; the `aria` clause documents that the resolved initials (explicit `initials`, else derived from `name`) drive the aria-label fallback + ## [0.2.0] - 2026-07-21 ### Added diff --git a/packages/design-system/CHANGELOG.md b/packages/design-system/CHANGELOG.md index d4617f6..2241e2b 100644 --- a/packages/design-system/CHANGELOG.md +++ b/packages/design-system/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- `ff-avatar`: `size` now also accepts a literal pixel number (proportional initials font size); `name` derives initials automatically (first + last word, uppercased) when the explicit `initials` input is unset; `round`/`cornerRadius` for a square shape with a custom corner radius; `tone` decorative background palette mirroring `ff-badge`'s `color` axis; new `FfAvatarTone` type + ## [0.4.0] - 2026-07-21 ### Added From 813d68c99050943f31707362eaf7ce4b010383f1 Mon Sep 17 00:00:00 2001 From: Maria Garcia Luque Date: Thu, 23 Jul 2026 11:08:48 +0200 Subject: [PATCH 5/5] [FIR-296] Trim the tone JSDoc and cover the round-wins-over-cornerRadius contract --- .../lib/primitives/ff-avatar/ff-avatar.component.spec.ts | 8 ++++++++ .../src/lib/primitives/ff-avatar/ff-avatar.component.ts | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts index 21682d2..fa97e57 100644 --- a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts +++ b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.spec.ts @@ -268,6 +268,14 @@ describe('FfAvatarComponent', () => { const hostEl = fixture.nativeElement as HTMLElement; expect(hostEl.style.getPropertyValue('--ff-avatar-radius').trim()).toBe('12px'); }); + + it('should keep the round shape when cornerRadius is set but round stays true', () => { + fixture.componentRef.setInput('cornerRadius', '12px'); + fixture.detectChanges(); + + const hostEl = fixture.nativeElement as HTMLElement; + expect(hostEl.classList.contains('ff-avatar--square')).toBe(false); + }); }); describe('tone', () => { diff --git a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts index 458be92..817127f 100644 --- a/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts +++ b/packages/design-system/src/lib/primitives/ff-avatar/ff-avatar.component.ts @@ -15,9 +15,7 @@ export type FfAvatarSize = 'sm' | 'md' | 'lg' | number; /** * Decorative background tone of the avatar, independent of image/initials - * content. Mirrors the semantic palette axis of `ff-badge`'s `color` input - * (brand + status palettes) so the two primitives read the same "tone - * vocabulary" across a page. + * content. The seven values are the semantic palette of `FfBadgeColor`. */ export type FfAvatarTone = | 'primary'