Skip to content

Commit

Permalink
Merge branch 'develop' into housekeeping/improve-nav-structure
Browse files Browse the repository at this point in the history
  • Loading branch information
RasmusKjeldgaard authored Nov 29, 2024
2 parents 7a16c62 + d77d5d5 commit 327a140
Show file tree
Hide file tree
Showing 15 changed files with 204 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ <h2>Avatar with badge</h2>

<h2>Avatar with image</h2>
<cookbook-avatar-example-image></cookbook-avatar-example-image>
<h3>Sizes</h3>
<cookbook-avatar-example-image-size></cookbook-avatar-example-image-size>
<h3>Lazy loaded image</h3>
<cookbook-avatar-example-image-loazy-loading></cookbook-avatar-example-image-loazy-loading>
<h3>Fallback image</h3>
<cookbook-avatar-example-image-error></cookbook-avatar-example-image-error>
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { AvatarExampleBadgeComponent } from './examples/badge';
import { AvatarExampleImageComponent } from './examples/image';
import { AvatarExampleImageSizeComponent } from './examples/image-sizes';
import { AvatarExampleImageErrorComponent } from './examples/image-error';
import { AvatarExampleImageLazyLoadingComponent } from './examples/image-lazy-loading';

const COMPONENT_DECLARATIONS = [
AvatarExampleComponent,
Expand All @@ -25,6 +26,7 @@ const COMPONENT_DECLARATIONS = [
AvatarExampleImageComponent,
AvatarExampleImageSizeComponent,
AvatarExampleImageErrorComponent,
AvatarExampleImageLazyLoadingComponent,
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';

const config = {
selector: 'cookbook-avatar-example-image-loazy-loading',
template: `<kirby-avatar imageSrc="/assets/images/woman.png" imageLoading="lazy" size="lg"></kirby-avatar>`,
};

@Component({
selector: config.selector,
template: config.template,
styleUrls: ['./avatar-examples.shared.scss'],
})
export class AvatarExampleImageLazyLoadingComponent {
template: string = config.template;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

import { KirbyModule } from '@kirbydesign/designsystem';

import { ButtonComponent } from '@kirbydesign/designsystem/button';
import { CardModule } from '@kirbydesign/designsystem/card';
import { DropdownModule } from '@kirbydesign/designsystem/dropdown';
import { IconModule } from '@kirbydesign/designsystem/icon';
import { CheckboxComponent } from '@kirbydesign/designsystem/checkbox';
import { ToastController, ToastHelper } from '@kirbydesign/designsystem/toast';
import { DropdownExampleConfigurationComponent } from './dropdown-example-configuration-component/dropdown-example-configuration.component';
import { DropdownExampleAttentionLevelComponent } from './examples/attention-level';
import { DropdownExampleCustomItemTemplateComponent } from './examples/custom-item-template';
Expand All @@ -14,8 +18,10 @@ import { DropdownExampleNgFormsComponent } from './examples/ng-forms';
import { DropdownExamplePreSelectedComponent } from './examples/pre-selected';
import { DropdownExampleRightAlignedComponent } from './examples/right-aligned';
import { DropdownExampleScrollComponent } from './examples/scroll';
import { DropdownExampleComponent } from './dropdown-example.component';

const COMPONENT_DECLARATIONS = [
DropdownExampleComponent,
DropdownExampleConfigurationComponent,
DropdownExampleDefaultComponent,
DropdownExampleScrollComponent,
Expand All @@ -29,7 +35,16 @@ const COMPONENT_DECLARATIONS = [
];

@NgModule({
imports: [CommonModule, KirbyModule, ReactiveFormsModule],
imports: [
CommonModule,
ReactiveFormsModule,
ButtonComponent,
CardModule,
DropdownModule,
IconModule,
CheckboxComponent,
],
providers: [ToastHelper, ToastController],
declarations: COMPONENT_DECLARATIONS,
exports: COMPONENT_DECLARATIONS,
})
Expand Down
2 changes: 0 additions & 2 deletions apps/cookbook/src/app/examples/examples.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { TabExampleMenuComponent } from './tabs-example/tab/tab-example-menu.com
import { TabsExampleComponent } from './tabs-example/tabs-example.component';
import { ToastExampleComponent } from './toast-example/toast-example.component';
import { PagePullToRefreshExampleComponent } from './page-example/pull-to-refresh/page-pull-to-refresh-example.component';
import { DropdownExampleComponent } from './dropdown-example/dropdown-example.component';
import { LoadingOverlayServiceExampleComponent } from './loading-overlay-example/service/loading-overlay-service-example.component';
import { HeaderWithActionGroupExampleComponent } from './header-example/examples/action-group';
import { HeaderWithEmphasizedActionGroupExampleComponent } from './header-example/examples/emphasize-actions';
Expand Down Expand Up @@ -81,7 +80,6 @@ export const COMPONENT_DECLARATIONS: any[] = [
TabExampleMenuComponent,
DividerExampleComponent,
ReorderListExampleComponent,
DropdownExampleComponent,
ProgressCircleExampleComponent,
FlagExampleComponent,
SlidesSimpleExampleComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,33 @@ <h2>Avatar with image</h2>
<cookbook-example-viewer [html]="imageExample.template">
<cookbook-avatar-example-image #imageExample></cookbook-avatar-example-image>
</cookbook-example-viewer>
<h3>Sizes</h3>
<cookbook-example-viewer [html]="imageSizeExample.template">
<cookbook-avatar-example-image-size #imageSizeExample></cookbook-avatar-example-image-size>
</cookbook-example-viewer>

<h2>Avatar with fallback image</h2>
<h3>Lazy loaded image</h3>
<p>
If you want to defer the loading of the avatar image set
<code>imageLoading="lazy"</code>
.
</p>
<p>
<a
class="kirby-external-icon"
target="_blank"
href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#loading"
>
Read more about lazy loading images on MDN
</a>
</p>
<cookbook-example-viewer [html]="imageLazyLoadingExample.template">
<cookbook-avatar-example-image-loazy-loading
#imageLazyLoadingExample
></cookbook-avatar-example-image-loazy-loading>
</cookbook-example-viewer>

<h3>Fallback image</h3>
<p>
The image avatar will emit an
<code>ErrorEvent</code>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,32 @@ import { ApiDescriptionProperty } from '~/app/shared/api-description/api-descrip
})
export class AvatarShowcaseComponent {
properties: ApiDescriptionProperty[] = [
{
name: 'imageSrc',
description: 'Points to the src of the image location',
defaultValue: 'null',
type: ['string'],
},
{
name: 'size',
description: 'Sets the size of the avatar.',
defaultValue: AvatarSize.SM,
type: Object.values(AvatarSize),
},
{
name: 'imageSrc',
description: 'The path to the image you want to embed in the avatar.',
defaultValue: 'undefined',
type: ['string'],
},
{
name: 'altText',
description:
'Must be filled out - its the alt text attribute that screenreaders use when "viewing" the image.',
defaultValue: 'null',
'The alt text attribute that screenreaders use when "viewing" the image. Mandatory when using the avatar with an image.',
defaultValue: 'undefined',
type: ['string'],
},
{
name: 'imageLoading',
description:
'Sets the loading attribute of the image.\n\nSee: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#loading',
defaultValue: 'undefined',
type: ['eager', 'lazy'],
},
{
name: 'overlay',
description:
Expand Down
1 change: 0 additions & 1 deletion libs/core/src/scss/interaction-state/_focus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,4 @@
#{$shadow},
0 0 0 $gap #{utils.get-color('background-color')},
0 0 0 $gap + $stroke-width utils.$focus-ring-color;
z-index: utils.z('default');
}
8 changes: 7 additions & 1 deletion libs/designsystem/avatar/src/avatar.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<div class="avatar" [ngClass]="{ overlay: overlay, stroke: stroke }">
<img *ngIf="imageSrc" [src]="imageSrc" [attr.alt]="altText" (error)="onImageError($event)" />
<img
*ngIf="imageSrc"
[src]="imageSrc"
[attr.alt]="altText"
[attr.loading]="imageLoading"
(error)="onImageError($event)"
/>
<ng-content *ngIf="!text" select="kirby-icon"></ng-content>
<span class="avatar-text" *ngIf="text">{{ text }}</span>
</div>
Expand Down
77 changes: 62 additions & 15 deletions libs/designsystem/avatar/src/avatar.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('AvatarComponent', () => {
expect(spectator.component).toBeTruthy();
});

it('should render with correct default colors', async () => {
it('should render with correct default colors', () => {
spectator = createHost(`
<kirby-avatar>
<kirby-icon name="qr"></kirby-icon>
Expand All @@ -47,23 +47,70 @@ describe('AvatarComponent', () => {
});
});

it('should emit error when rendering avatar with invalid image source', async () => {
spectator = createHost(`
<kirby-avatar imageSrc="failingSrc.png"></kirby-avatar>
`);
spyOn(spectator.component.imageError, 'emit');

let errorEvent: ErrorEvent;
const img = spectator.query<HTMLImageElement>('img');
const waitForImageError = new Promise<HTMLImageElement>((resolve) => {
img.addEventListener('error', (ev) => {
errorEvent = ev;
resolve(img);
describe('when rendering Avatar with imageSrc set', () => {
describe('by default', () => {
beforeEach(() => {
spectator = createHost(`
<kirby-avatar imageSrc="/assets/images/woman.png"></kirby-avatar>`);
});

it('should render image', () => {
const img = spectator.query<HTMLImageElement>('img');
expect(img).toExist();
});

it('should render image with configured imageSrc', () => {
const img = spectator.query<HTMLImageElement>('img');
expect(img.src.endsWith('/assets/images/woman.png')).toBeTrue();
});

it('should not set alt attribute on img when altText is not set', () => {
const img = spectator.query<HTMLImageElement>('img');
expect(img).not.toHaveAttribute('alt');
});

it('should not set loading attribute on img when imageLoading is not set', () => {
const img = spectator.query<HTMLImageElement>('img');
expect(img).not.toHaveAttribute('loading');
});
});
await waitForImageError;

expect(spectator.component.imageError.emit).toHaveBeenCalledOnceWith(errorEvent);
it('should set alt attribute on image when altText is set', () => {
spectator = createHost(`
<kirby-avatar imageSrc="/assets/images/woman.png" altText="Test"></kirby-avatar>
`);

const img = spectator.query<HTMLImageElement>('img');
expect(img).toHaveAttribute('alt', 'Test');
});

it('should defer loading of image when imageLoading="lazy"', () => {
spectator = createHost(`
<kirby-avatar imageSrc="/assets/images/woman.png" imageLoading="lazy"></kirby-avatar>
`);

const img = spectator.query<HTMLImageElement>('img');
expect(img).toHaveAttribute('loading', 'lazy');
});

it('should emit error when rendering avatar with invalid image source', async () => {
spectator = createHost(`
<kirby-avatar imageSrc="failingSrc.png"></kirby-avatar>
`);
spyOn(spectator.component.imageError, 'emit');

let errorEvent: ErrorEvent;
const img = spectator.query<HTMLImageElement>('img');
const waitForImageError = new Promise<HTMLImageElement>((resolve) => {
img.addEventListener('error', (ev) => {
errorEvent = ev;
resolve(img);
});
});
await waitForImageError;

expect(spectator.component.imageError.emit).toHaveBeenCalledOnceWith(errorEvent);
});
});

describe('when rendering Avatar within Progress Circle', () => {
Expand Down
1 change: 1 addition & 0 deletions libs/designsystem/avatar/src/avatar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum AvatarSize {
})
export class AvatarComponent {
@Input() imageSrc: string;
@Input() imageLoading: 'eager' | 'lazy' | undefined;
@Input() altText: string;
@Input() stroke: boolean;
@Input() text: string;
Expand Down
5 changes: 5 additions & 0 deletions libs/designsystem/calendar/src/calendar.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ td {
height: $day-width;
margin: utils.size('xxxs') 0;
font-size: utils.font-size('n');

&:focus {
// In narrow viewports where buttons sit side-by-side, ensure focus ring is on top of adjacent buttons.
z-index: utils.z('default');
}
}

button[aria-disabled='true'] {
Expand Down
43 changes: 40 additions & 3 deletions libs/designsystem/dropdown/src/dropdown.component.stories.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { type Meta, moduleMetadata, type StoryObj } from '@storybook/angular';
import { argsToTemplate, type Meta, moduleMetadata, type StoryObj } from '@storybook/angular';
import { userEvent, within } from '@storybook/test';

import { DropdownComponent, DropdownModule } from '@kirbydesign/designsystem/dropdown';

import { ButtonComponent } from '@kirbydesign/designsystem/button';
import { DropdownExampleModule } from '~/app/examples/dropdown-example/dropdown-example.module';

const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];

const meta: Meta<DropdownComponent> = {
component: DropdownComponent,
title: 'Components / Dropdown',
decorators: [
moduleMetadata({
imports: [DropdownModule],
imports: [DropdownModule, ButtonComponent, DropdownExampleModule],
}),
],
argTypes: {
Expand All @@ -27,7 +34,7 @@ type Story = StoryObj<DropdownComponent>;

export const Dropdown: Story = {
args: {
items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'],
items: items,
placeholder: 'Please select:',
itemTextProperty: 'text',
attentionLevel: '3',
Expand Down Expand Up @@ -61,3 +68,33 @@ export const Dropdown: Story = {
},
},
};

export const DropdownOpened: Story = {
args: {
items: items,
selectedIndex: 0,
},
render: (args) => ({
props: args,
template: `
<kirby-dropdown ${argsToTemplate(args)}></kirby-dropdown>
<br />
<button kirby-button>Button - below</button>
`,
}),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

const dropdownToOpen = canvas.getByRole('button', {
name: 'Item 1',
});

await userEvent.click(dropdownToOpen);
},
};

export const CookbookExample: Story = {
render: () => ({
template: `<cookbook-dropdown-example></cookbook-dropdown-example>`,
}),
};
Loading

0 comments on commit 327a140

Please sign in to comment.