From a3e249eb1cf1937453074759056c5282bed7ce21 Mon Sep 17 00:00:00 2001 From: ckibsen Date: Thu, 2 Jul 2020 14:47:18 +0200 Subject: [PATCH] Feature/kirby flag (#1088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added Flag Component * ✅ Add Flag Component tests * Fixed styling to please tests * 📝 Update flag examples * 💄 📝 Fix showcase-properties inputValues formatting * 💄 📝 Add wrapping to avatar color examples * 💄 Add `xxxxs` size * 💄 Replace hardcoded px values with tokens * 📝 Use flag component in item examples * 💄 Refactor styling * ♻️ Replace size directive with HostBinding * ✅ Update tests * ⏪ Revert * 🎨 Clean up Co-authored-by: Christian K. Ibsen Co-authored-by: Jakob Engelbrecht --- .../avatar-example/examples/colors.ts | 2 + .../src/app/examples/examples.common.ts | 2 + .../src/app/examples/examples.module.ts | 3 + .../src/app/examples/examples.routes.ts | 5 + .../examples/flag-example/examples/colors.ts | 19 +++ .../examples/flag-examples.shared.scss | 40 +++++ .../examples/flag-example/examples/sizes.ts | 17 +++ .../flag-example/flag-example.component.html | 5 + .../flag-example/flag-example.component.scss | 1 + .../flag-example/flag-example.component.ts | 8 + .../flag-example/flag-example.module.ts | 16 ++ .../item-example/examples/avatar/flagged.ts | 9 +- .../examples/item-example/examples/flagged.ts | 9 +- .../app/page/side-nav/side-nav.component.html | 1 + .../showcase-properties.component.html | 7 +- .../showcase-properties/showcase-property.ts | 1 + .../flag-showcase.component.html | 13 ++ .../flag-showcase.component.scss | 1 + .../flag-showcase/flag-showcase.component.ts | 25 ++++ .../segmented-control-showcase.component.ts | 1 + .../src/app/showcase/showcase.common.ts | 2 + .../src/app/showcase/showcase.routes.ts | 5 + .../lib/components/flag/flag.component.scss | 42 ++++++ .../components/flag/flag.component.spec.ts | 139 ++++++++++++++++++ .../src/lib/components/flag/flag.component.ts | 20 +++ libs/designsystem/src/lib/kirby.module.ts | 2 + .../src/lib/scss/base/_variables.scss | 1 + 27 files changed, 382 insertions(+), 14 deletions(-) create mode 100644 apps/cookbook/src/app/examples/flag-example/examples/colors.ts create mode 100644 apps/cookbook/src/app/examples/flag-example/examples/flag-examples.shared.scss create mode 100644 apps/cookbook/src/app/examples/flag-example/examples/sizes.ts create mode 100644 apps/cookbook/src/app/examples/flag-example/flag-example.component.html create mode 100644 apps/cookbook/src/app/examples/flag-example/flag-example.component.scss create mode 100644 apps/cookbook/src/app/examples/flag-example/flag-example.component.ts create mode 100644 apps/cookbook/src/app/examples/flag-example/flag-example.module.ts create mode 100644 apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.html create mode 100644 apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.scss create mode 100644 apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.ts create mode 100644 libs/designsystem/src/lib/components/flag/flag.component.scss create mode 100644 libs/designsystem/src/lib/components/flag/flag.component.spec.ts create mode 100644 libs/designsystem/src/lib/components/flag/flag.component.ts diff --git a/apps/cookbook/src/app/examples/avatar-example/examples/colors.ts b/apps/cookbook/src/app/examples/avatar-example/examples/colors.ts index 4a0b065803..781e7f7d77 100644 --- a/apps/cookbook/src/app/examples/avatar-example/examples/colors.ts +++ b/apps/cookbook/src/app/examples/avatar-example/examples/colors.ts @@ -47,6 +47,8 @@ const config = { selector: config.selector, template: config.template, styleUrls: ['./avatar-examples.shared.scss'], + // tslint:disable-next-line: no-host-metadata-property + host: { '[class.wrap]': 'true' }, }) export class AvatarExampleColorsComponent { template: string = config.template; diff --git a/apps/cookbook/src/app/examples/examples.common.ts b/apps/cookbook/src/app/examples/examples.common.ts index 13e966eb0a..22d404ac65 100644 --- a/apps/cookbook/src/app/examples/examples.common.ts +++ b/apps/cookbook/src/app/examples/examples.common.ts @@ -49,6 +49,7 @@ import { ReorderListExampleComponent } from './reorder-list/reorder-list-example import { DropdownExampleComponent } from '~/app/examples/dropdown-example/dropdown-example.component'; import { StockChartExampleComponent } from './stock-chart-example/stock-chart-example.component'; import { ProgressCircleExampleComponent } from './progress-circle-example/progress-circle-example.component'; +import { FlagExampleComponent } from './flag-example/flag-example.component'; // Example of "custom" icons export const iconSettings: IconSettings = { @@ -114,6 +115,7 @@ export const COMPONENT_DECLARATIONS: any[] = [ DropdownExampleComponent, StockChartExampleComponent, ProgressCircleExampleComponent, + FlagExampleComponent, ]; // Configure custom icons (used by example to show the usage of custom icons) diff --git a/apps/cookbook/src/app/examples/examples.module.ts b/apps/cookbook/src/app/examples/examples.module.ts index 44f1560ca6..2a92e4032e 100644 --- a/apps/cookbook/src/app/examples/examples.module.ts +++ b/apps/cookbook/src/app/examples/examples.module.ts @@ -16,6 +16,7 @@ import { SegmentedControlExampleModule } from './segmented-control-example/segme import { ChartExampleModule } from './chart-example/chart-example.module'; import { ProgressCircleExampleModule } from './progress-circle-example/progress-circle-example.module'; import { AvatarExampleModule } from './avatar-example/avatar-example.module'; +import { FlagExampleModule } from './flag-example/flag-example.module'; @NgModule({ imports: [ @@ -29,6 +30,7 @@ import { AvatarExampleModule } from './avatar-example/avatar-example.module'; ChartExampleModule, ProgressCircleExampleModule, AvatarExampleModule, + FlagExampleModule, ], declarations: COMPONENT_DECLARATIONS, exports: [ @@ -41,6 +43,7 @@ import { AvatarExampleModule } from './avatar-example/avatar-example.module'; ChartExampleModule, ProgressCircleExampleModule, AvatarExampleModule, + FlagExampleModule, ], providers: [PROVIDER_DECLARATIONS], entryComponents: [ diff --git a/apps/cookbook/src/app/examples/examples.routes.ts b/apps/cookbook/src/app/examples/examples.routes.ts index 440b466bcf..a5a4036e4a 100644 --- a/apps/cookbook/src/app/examples/examples.routes.ts +++ b/apps/cookbook/src/app/examples/examples.routes.ts @@ -51,6 +51,7 @@ import { ReorderListExampleComponent } from './reorder-list/reorder-list-example import { DropdownExampleComponent } from '~/app/examples/dropdown-example/dropdown-example.component'; import { StockChartExampleComponent } from './stock-chart-example/stock-chart-example.component'; import { ProgressCircleExampleComponent } from './progress-circle-example/progress-circle-example.component'; +import { FlagExampleComponent } from './flag-example/flag-example.component'; export const routes: Routes = [ { @@ -263,6 +264,10 @@ export const routes: Routes = [ path: 'badge', component: BadgeExampleComponent, }, + { + path: 'flag', + component: FlagExampleComponent, + }, { path: 'icon', component: IconExampleComponent, diff --git a/apps/cookbook/src/app/examples/flag-example/examples/colors.ts b/apps/cookbook/src/app/examples/flag-example/examples/colors.ts new file mode 100644 index 0000000000..03757581a2 --- /dev/null +++ b/apps/cookbook/src/app/examples/flag-example/examples/colors.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; + +const config = { + selector: 'cookbook-flag-example-colors', + template: `Success +Warning +Danger +Semi-Light +Transparent`, +}; + +@Component({ + selector: config.selector, + template: config.template, + styleUrls: ['./flag-examples.shared.scss'], +}) +export class FlagExampleColorsComponent { + template: string = config.template; +} diff --git a/apps/cookbook/src/app/examples/flag-example/examples/flag-examples.shared.scss b/apps/cookbook/src/app/examples/flag-example/examples/flag-examples.shared.scss new file mode 100644 index 0000000000..1f65c9e478 --- /dev/null +++ b/apps/cookbook/src/app/examples/flag-example/examples/flag-examples.shared.scss @@ -0,0 +1,40 @@ +@import './libs/designsystem/src/lib/scss/utils'; + +:host { + display: flex; + align-items: flex-end; + margin-bottom: size('s'); + + > * { + margin-right: size('s'); + margin-bottom: size('s'); + } + + flex-wrap: wrap; + + &.align-top { + align-items: flex-start; + } +} + +kirby-flag { + position: relative; +} + +kirby-flag[title] { + margin-bottom: size('s'); + + &::before { + content: ''; + width: 100%; + position: absolute; + bottom: 0; + transform: translateY(100%); + font-size: font-size('xs'); + text-align: center; + } + + &[title='(default)']::before { + content: '(default)'; + } +} diff --git a/apps/cookbook/src/app/examples/flag-example/examples/sizes.ts b/apps/cookbook/src/app/examples/flag-example/examples/sizes.ts new file mode 100644 index 0000000000..69c4e99c62 --- /dev/null +++ b/apps/cookbook/src/app/examples/flag-example/examples/sizes.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; + +const config = { + selector: 'cookbook-flag-example-sizes', + template: `Extra Small (xs) +Small (sm) +Medium (md)`, +}; + +@Component({ + selector: config.selector, + template: config.template, + styleUrls: ['./flag-examples.shared.scss'], +}) +export class FlagExampleSizesComponent { + template: string = config.template; +} diff --git a/apps/cookbook/src/app/examples/flag-example/flag-example.component.html b/apps/cookbook/src/app/examples/flag-example/flag-example.component.html new file mode 100644 index 0000000000..0d83fbd77d --- /dev/null +++ b/apps/cookbook/src/app/examples/flag-example/flag-example.component.html @@ -0,0 +1,5 @@ +

Colors

+ + +

Sizes

+ diff --git a/apps/cookbook/src/app/examples/flag-example/flag-example.component.scss b/apps/cookbook/src/app/examples/flag-example/flag-example.component.scss new file mode 100644 index 0000000000..52735df8e0 --- /dev/null +++ b/apps/cookbook/src/app/examples/flag-example/flag-example.component.scss @@ -0,0 +1 @@ +@import '../examples.shared'; diff --git a/apps/cookbook/src/app/examples/flag-example/flag-example.component.ts b/apps/cookbook/src/app/examples/flag-example/flag-example.component.ts new file mode 100644 index 0000000000..a40bb25b64 --- /dev/null +++ b/apps/cookbook/src/app/examples/flag-example/flag-example.component.ts @@ -0,0 +1,8 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'cookbook-flag-example', + templateUrl: './flag-example.component.html', + styleUrls: ['./flag-example.component.scss'], +}) +export class FlagExampleComponent {} diff --git a/apps/cookbook/src/app/examples/flag-example/flag-example.module.ts b/apps/cookbook/src/app/examples/flag-example/flag-example.module.ts new file mode 100644 index 0000000000..56e35f1560 --- /dev/null +++ b/apps/cookbook/src/app/examples/flag-example/flag-example.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { KirbyModule } from '@kirbydesign/designsystem'; + +import { FlagExampleColorsComponent } from './examples/colors'; +import { FlagExampleSizesComponent } from './examples/sizes'; + +const COMPONENT_DECLARATIONS = [FlagExampleColorsComponent, FlagExampleSizesComponent]; + +@NgModule({ + imports: [CommonModule, KirbyModule], + declarations: COMPONENT_DECLARATIONS, + exports: COMPONENT_DECLARATIONS, +}) +export class FlagExampleModule {} diff --git a/apps/cookbook/src/app/examples/item-example/examples/avatar/flagged.ts b/apps/cookbook/src/app/examples/item-example/examples/avatar/flagged.ts index bab5778a3f..898cdb02c0 100755 --- a/apps/cookbook/src/app/examples/item-example/examples/avatar/flagged.ts +++ b/apps/cookbook/src/app/examples/item-example/examples/avatar/flagged.ts @@ -17,7 +17,9 @@ const config = {

Lorem ipsum quam notem andamus gepulowitzh onga bonga bimmelon sid est insula

Phone, Internet, Streaming services og other

- EUR 300,00 + + EUR 300,00 + `, styles: [ `.flag { @@ -34,11 +36,6 @@ const config = { `.flag:not(:last-child) { margin-bottom: 2px; }`, - `data { - background-color: var(--kirby-success); - border-radius: 4px; - padding: 2px 8px; - }`, ], }; diff --git a/apps/cookbook/src/app/examples/item-example/examples/flagged.ts b/apps/cookbook/src/app/examples/item-example/examples/flagged.ts index 5fba88d04e..f85596805b 100755 --- a/apps/cookbook/src/app/examples/item-example/examples/flagged.ts +++ b/apps/cookbook/src/app/examples/item-example/examples/flagged.ts @@ -11,9 +11,9 @@ const config = {

Title

Detail

- + 60 - + `, styles: [ `.flag { @@ -30,11 +30,6 @@ const config = { `.flag:not(:last-child) { margin-bottom: 2px; }`, - `data { - background-color: var(--kirby-success); - border-radius: 4px; - padding: 2px 8px; - }`, ], }; diff --git a/apps/cookbook/src/app/page/side-nav/side-nav.component.html b/apps/cookbook/src/app/page/side-nav/side-nav.component.html index e0b1d41d89..7820b3fbdb 100644 --- a/apps/cookbook/src/app/page/side-nav/side-nav.component.html +++ b/apps/cookbook/src/app/page/side-nav/side-nav.component.html @@ -91,6 +91,7 @@
  • Avatar
  • Badge
  • +
  • Flag
  • Charts
  • Stock Chart
  • Colors
  • diff --git a/apps/cookbook/src/app/shared/showcase-properties/showcase-properties.component.html b/apps/cookbook/src/app/shared/showcase-properties/showcase-properties.component.html index 89a14281bd..b9a21edafe 100644 --- a/apps/cookbook/src/app/shared/showcase-properties/showcase-properties.component.html +++ b/apps/cookbook/src/app/shared/showcase-properties/showcase-properties.component.html @@ -12,7 +12,12 @@ {{ prop.description }} -
    {{ prop.inputValues ? prop.inputValues.join(' | ') : '' }}
    + + {{ prop.inputValues ? prop.inputValues.join(' | ') : '' }} + +
    {{ prop.inputValues ? prop.inputValues.join(' | ') : '' }}
    {{ prop.defaultValue }} diff --git a/apps/cookbook/src/app/shared/showcase-properties/showcase-property.ts b/apps/cookbook/src/app/shared/showcase-properties/showcase-property.ts index 01eeca7ad6..df7e71c240 100644 --- a/apps/cookbook/src/app/shared/showcase-properties/showcase-property.ts +++ b/apps/cookbook/src/app/shared/showcase-properties/showcase-property.ts @@ -3,4 +3,5 @@ export interface ShowcaseProperty { defaultValue?: string; description?: string; inputValues?: string[]; + preserveInputValuesWhitespaces?: boolean; } diff --git a/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.html b/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.html new file mode 100644 index 0000000000..5e85d296a8 --- /dev/null +++ b/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.html @@ -0,0 +1,13 @@ +
    +

    Colors

    + + + +

    Sizes

    + + + + +

    Properties:

    + +
    diff --git a/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.scss b/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.scss new file mode 100644 index 0000000000..0fa976b4ef --- /dev/null +++ b/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.scss @@ -0,0 +1 @@ +@import '../showcase.shared'; diff --git a/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.ts b/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.ts new file mode 100644 index 0000000000..f6989e3c6e --- /dev/null +++ b/apps/cookbook/src/app/showcase/flag-showcase/flag-showcase.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; + +import { ShowcaseProperty } from '~/app/shared/showcase-properties/showcase-property'; + +@Component({ + selector: 'cookbook-flag-showcase', + templateUrl: './flag-showcase.component.html', + styleUrls: ['./flag-showcase.component.scss'], +}) +export class FlagShowcaseComponent { + properties: ShowcaseProperty[] = [ + { + name: 'themeColor', + description: 'Sets which color the flag should use.', + defaultValue: 'transparent', + inputValues: ['success', 'warning', 'danger', 'semi-light', 'transparent'], + }, + { + name: 'size', + description: 'Sets the size of the flag.', + defaultValue: 'md', + inputValues: ['xs', 'sm', 'md'], + }, + ]; +} diff --git a/apps/cookbook/src/app/showcase/segmented-control-showcase/segmented-control-showcase.component.ts b/apps/cookbook/src/app/showcase/segmented-control-showcase/segmented-control-showcase.component.ts index 3b30979f5b..8ee191ac65 100644 --- a/apps/cookbook/src/app/showcase/segmented-control-showcase/segmented-control-showcase.component.ts +++ b/apps/cookbook/src/app/showcase/segmented-control-showcase/segmented-control-showcase.component.ts @@ -37,6 +37,7 @@ export class SegmentedControlShowcaseComponent { } }]`, ], + preserveInputValuesWhitespaces: true, }, { name: 'value', diff --git a/apps/cookbook/src/app/showcase/showcase.common.ts b/apps/cookbook/src/app/showcase/showcase.common.ts index aeaa5abbd5..443ef0365c 100644 --- a/apps/cookbook/src/app/showcase/showcase.common.ts +++ b/apps/cookbook/src/app/showcase/showcase.common.ts @@ -38,6 +38,7 @@ import { DividerShowcaseComponent } from '../showcase/divider-showcase/divider-s import { DropdownShowcaseComponent } from '~/app/showcase/dropdown-showcase/dropdown-showcase.component'; import { ReorderListShowcaseComponent } from '../showcase/reorder-list-showcase/reorder-list-showcase.component'; import { StockChartShowcaseComponent } from './stock-chart-showcase/stock-chart-showcase.component'; +import { FlagShowcaseComponent } from './flag-showcase/flag-showcase.component'; export const COMPONENT_IMPORTS: any[] = [ExamplesModule, ShowcaseRoutingModule]; @@ -79,6 +80,7 @@ export const COMPONENT_EXPORTS: any[] = [ DropdownShowcaseComponent, StockChartShowcaseComponent, ProgressCircleShowcaseComponent, + FlagShowcaseComponent, ]; export const COMPONENT_DECLARATIONS: any[] = [...COMPONENT_EXPORTS, ShowcaseComponent]; diff --git a/apps/cookbook/src/app/showcase/showcase.routes.ts b/apps/cookbook/src/app/showcase/showcase.routes.ts index 38929ceb9b..1025b76214 100644 --- a/apps/cookbook/src/app/showcase/showcase.routes.ts +++ b/apps/cookbook/src/app/showcase/showcase.routes.ts @@ -38,6 +38,7 @@ import { DropdownShowcaseComponent } from '~/app/showcase/dropdown-showcase/drop import { ReorderListShowcaseComponent } from './reorder-list-showcase/reorder-list-showcase.component'; import { StockChartShowcaseComponent } from './stock-chart-showcase/stock-chart-showcase.component'; import { ProgressCircleShowcaseComponent } from './progress-circle-showcase/progress-circle-showcase.component'; +import { FlagShowcaseComponent } from './flag-showcase/flag-showcase.component'; export const routes: Routes = [ { @@ -149,6 +150,10 @@ export const routes: Routes = [ path: 'badge', component: BadgeShowcaseComponent, }, + { + path: 'flag', + component: FlagShowcaseComponent, + }, { path: 'icon', component: IconShowcaseComponent, diff --git a/libs/designsystem/src/lib/components/flag/flag.component.scss b/libs/designsystem/src/lib/components/flag/flag.component.scss new file mode 100644 index 0000000000..f2288f16a6 --- /dev/null +++ b/libs/designsystem/src/lib/components/flag/flag.component.scss @@ -0,0 +1,42 @@ +@import '../../scss/utils'; + +:host { + display: inline-block; + background-color: var(--kirby-flag-background-color, transparent); + color: var(--kirby-flag-color, get-color('white-contrast')); + border: 1px solid var(--kirby-flag-border-color, get-color('medium')); + border-radius: size('xxxs'); + + font-size: font-size('n'); + + padding-top: size('xxxxs'); + padding-bottom: size('xxxxs'); + padding-left: size('xxs'); + padding-right: size('xxs'); + + &.sm { + font-size: font-size('s'); + } + + &.xs { + font-size: font-size('xs'); + padding-left: size('xxxs'); + padding-right: size('xxxs'); + } +} + +@each $color-name, + $color-value + in map-merge( + $notification-colors, + ( + 'semi-light': get-color('semi-light'), + ) + ) +{ + :host(.#{$color-name}) { + --kirby-flag-background-color: #{get-color($color-name)}; + --kirby-flag-color: #{get-color($color-name + '-contrast')}; + --kirby-flag-border-color: #{get-color($color-name)}; + } +} diff --git a/libs/designsystem/src/lib/components/flag/flag.component.spec.ts b/libs/designsystem/src/lib/components/flag/flag.component.spec.ts new file mode 100644 index 0000000000..b6a7ea639c --- /dev/null +++ b/libs/designsystem/src/lib/components/flag/flag.component.spec.ts @@ -0,0 +1,139 @@ +import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; + +import { DesignTokenHelper, ThemeColorExtended } from '../../helpers/design-token-helper'; +import { FlagComponent } from './flag.component'; + +const getColor = DesignTokenHelper.getColor; +const size = DesignTokenHelper.size; +const fontSize = DesignTokenHelper.fontSize; + +describe('FlagComponent', () => { + let spectator: SpectatorHost; + let element: HTMLElement; + + const createHost = createHostFactory({ + component: FlagComponent, + }); + + beforeEach(() => { + spectator = createHost('Value'); + element = spectator.element as HTMLElement; + }); + + it('should create', () => { + expect(spectator.component).toBeTruthy(); + }); + + it('should render with correct display', () => { + expect(element).toHaveComputedStyle({ + display: 'inline-block', + }); + }); + + it('should render with transparent background-color', () => { + expect(element).toHaveComputedStyle({ + 'background-color': 'transparent', + }); + }); + + it('should render with correct color', () => { + expect(element).toHaveComputedStyle({ color: getColor('white', 'contrast') }); + }); + + it('should render with correct border', () => { + expect(element).toHaveComputedStyle({ + 'border-color': getColor('medium'), + 'border-width': '1px', + 'border-style': 'solid', + }); + }); + + it('should render with correct border-radius', () => { + expect(element).toHaveComputedStyle({ 'border-radius': '4px' }); + }); + + it('should render with correct font-size', () => { + expect(element).toHaveComputedStyle({ 'font-size': fontSize('n') }); + }); + + it('should render with correct padding', () => { + expect(element).toHaveComputedStyle({ + 'padding-left': size('xxs'), + 'padding-right': size('xxs'), + 'padding-top': size('xxxxs'), + 'padding-bottom': size('xxxxs'), + }); + }); + + describe('when configured with size', () => { + describe('and size = xs', () => { + beforeEach(() => { + spectator.component.size = 'xs'; + spectator.detectChanges(); + }); + + it('should render with correct font-size', () => { + expect(element).toHaveComputedStyle({ 'font-size': fontSize('xs') }); + }); + + it('should render with correct padding', () => { + expect(element).toHaveComputedStyle({ + 'padding-left': size('xxxs'), + 'padding-right': size('xxxs'), + 'padding-top': size('xxxxs'), + 'padding-bottom': size('xxxxs'), + }); + }); + }); + + describe('and size = sm', () => { + beforeEach(() => { + spectator.component.size = 'sm'; + spectator.detectChanges(); + }); + + it('should render with correct font-size', () => { + expect(element).toHaveComputedStyle({ 'font-size': fontSize('s') }); + }); + + it('should render with correct padding', () => { + expect(element).toHaveComputedStyle({ + 'padding-left': size('xxs'), + 'padding-right': size('xxs'), + 'padding-top': size('xxxxs'), + 'padding-bottom': size('xxxxs'), + }); + }); + }); + }); + + describe(`when configured with themeColor`, () => { + const allowedThemeColors = ['success', 'warning', 'danger', 'semi-light'] as const; + type FlagThemeColor = typeof allowedThemeColors[number]; + const themeColors = allowedThemeColors.map((color) => getColor(color)); + themeColors.forEach((color) => { + it(`should render with correct colors when themeColor = '${color.name}'`, async () => { + spectator.component.themeColor = color.name as FlagThemeColor; + spectator.detectChanges(); + + expect(element).toHaveComputedStyle({ + 'background-color': color, + color: getColor(color.name as ThemeColorExtended, 'contrast'), + }); + }); + }); + + it(`should render with correct colors when themeColor = 'transparent'`, async () => { + spectator.component.themeColor = 'transparent'; + spectator.detectChanges(); + + expect(element).toHaveComputedStyle({ + 'background-color': 'transparent', + color: getColor('white', 'contrast'), + 'border-color': getColor('medium'), + 'border-width': '1px', + 'border-style': 'solid', + }); + }); + }); +}); diff --git a/libs/designsystem/src/lib/components/flag/flag.component.ts b/libs/designsystem/src/lib/components/flag/flag.component.ts new file mode 100644 index 0000000000..20720ab585 --- /dev/null +++ b/libs/designsystem/src/lib/components/flag/flag.component.ts @@ -0,0 +1,20 @@ +import { Component, Input, ChangeDetectionStrategy, HostBinding } from '@angular/core'; + +@Component({ + selector: 'kirby-flag', + template: ` + + `, + styleUrls: ['./flag.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FlagComponent { + @Input() size: 'xs' | 'sm' | 'md' = 'md'; + @Input() themeColor: 'success' | 'warning' | 'danger' | 'semi-light' | 'transparent' = + 'transparent'; + + @HostBinding('class') + get _cssClass() { + return [this.themeColor, this.size]; + } +} diff --git a/libs/designsystem/src/lib/kirby.module.ts b/libs/designsystem/src/lib/kirby.module.ts index 189a75befc..692a641ac3 100644 --- a/libs/designsystem/src/lib/kirby.module.ts +++ b/libs/designsystem/src/lib/kirby.module.ts @@ -74,6 +74,7 @@ import { ResizeObserverFactory } from './components/shared/resize-observer/resiz import { ResizeObserverService } from './components/shared/resize-observer/resize-observer.service'; import { ProgressCircleComponent } from './components/progress-circle/progress-circle.component'; import { ProgressCircleRingComponent } from './components/progress-circle/progress-circle-ring.component'; +import { FlagComponent } from './components/flag/flag.component'; const exportedDeclarations = [ CardComponent, @@ -120,6 +121,7 @@ const exportedDeclarations = [ InfiniteScrollDirective, LoadingOverlayComponent, ProgressCircleComponent, + FlagComponent, ]; const declarations = [ diff --git a/libs/designsystem/src/lib/scss/base/_variables.scss b/libs/designsystem/src/lib/scss/base/_variables.scss index 82f98b4760..0c54ca0fb0 100644 --- a/libs/designsystem/src/lib/scss/base/_variables.scss +++ b/libs/designsystem/src/lib/scss/base/_variables.scss @@ -11,6 +11,7 @@ $sizes: ( xs: 12px, xxs: 8px, xxxs: 4px, + xxxxs: 2px, ) !default; /*