diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index 669c42e327..6cc0901703 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -4,6 +4,7 @@ import { } from '@ghostfolio/common/config'; import { extractNumberFromString, + getCountryCodeFromCurrency, getNumberFormatGroup, getStringOrNull, getStringOrUndefined, @@ -77,6 +78,30 @@ describe('Helper', () => { }); }); + describe('Get country code from currency', () => { + it('ISO 4217 currency code', () => { + expect(getCountryCodeFromCurrency('CHF')).toEqual('CH'); + expect(getCountryCodeFromCurrency('USD')).toEqual('US'); + }); + + it('Currency of the European Union', () => { + expect(getCountryCodeFromCurrency('EUR')).toEqual('EU'); + }); + + it('Derived currency', () => { + expect(getCountryCodeFromCurrency('GBp')).toEqual('GB'); + }); + + it('Supranational currency', () => { + expect(getCountryCodeFromCurrency('XAU')).toEqual(''); + expect(getCountryCodeFromCurrency('XOF')).toEqual(''); + }); + + it('Empty currency', () => { + expect(getCountryCodeFromCurrency('')).toEqual(''); + }); + }); + describe('Get number format group', () => { let languageGetter: jest.SpyInstance; diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 0778f84f15..44fd89aa26 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -279,6 +279,17 @@ export function getCurrencyFromSymbol(aSymbol = '') { return aSymbol.replace(DEFAULT_CURRENCY, ''); } +export function getCountryCodeFromCurrency(aCurrency = '') { + // An ISO 4217 currency code is composed of the ISO 3166-1 alpha-2 country + // code and the initial of the currency itself, except for the supranational + // currencies, which are prefixed with X (like XAU or XOF) + if (aCurrency.startsWith('X')) { + return ''; + } + + return aCurrency.slice(0, 2).toUpperCase(); +} + export function getCountryName({ code }: { code: string }): string { try { return ( diff --git a/libs/ui/src/lib/currency-selector/currency-selector.component.html b/libs/ui/src/lib/currency-selector/currency-selector.component.html index e07101f9ab..0b9be5954c 100644 --- a/libs/ui/src/lib/currency-selector/currency-selector.component.html +++ b/libs/ui/src/lib/currency-selector/currency-selector.component.html @@ -1,3 +1,7 @@ +@if (emojiFlagOfSelectedCurrency) { + {{ emojiFlagOfSelectedCurrency }} +} + @for (currency of filteredCurrencies; track currency) { - {{ currency }} + + {{ getEmojiFlagFromCurrency(currency) }} + {{ currency }} + } diff --git a/libs/ui/src/lib/currency-selector/currency-selector.component.scss b/libs/ui/src/lib/currency-selector/currency-selector.component.scss index 5d4e87f30f..07be4a7c34 100644 --- a/libs/ui/src/lib/currency-selector/currency-selector.component.scss +++ b/libs/ui/src/lib/currency-selector/currency-selector.component.scss @@ -1,3 +1,4 @@ :host { - display: block; + align-items: center; + display: flex; } diff --git a/libs/ui/src/lib/currency-selector/currency-selector.component.stories.ts b/libs/ui/src/lib/currency-selector/currency-selector.component.stories.ts new file mode 100644 index 0000000000..beb63e369e --- /dev/null +++ b/libs/ui/src/lib/currency-selector/currency-selector.component.stories.ts @@ -0,0 +1,99 @@ +import { ANIMATION_MODULE_TYPE } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import '@angular/localize/init'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; + +import { GfCurrencySelectorComponent } from './currency-selector.component'; + +const CURRENCIES = [ + 'AUD', + 'CHF', + 'EUR', + 'GBP', + 'GBp', + 'JPY', + 'USD', + 'XAU', + 'ZAR' +]; + +const meta: Meta = { + title: 'Currency Selector', + component: GfCurrencySelectorComponent, + decorators: [ + moduleMetadata({ + imports: [ + GfCurrencySelectorComponent, + MatFormFieldModule, + ReactiveFormsModule + ], + providers: [ + { + provide: ANIMATION_MODULE_TYPE, + useValue: 'NoopAnimations' + } + ] + }) + ], + render: ({ currencies, value }) => { + return { + props: { + currencies, + formGroup: new FormGroup({ + currency: new FormControl(value) + }) + }, + template: ` +
+ + Currency + + +
+ ` + }; + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + currencies: CURRENCIES, + value: 'CHF' + } +}; + +export const CurrencyOfEuropeanUnion: Story = { + args: { + currencies: CURRENCIES, + value: 'EUR' + } +}; + +export const DerivedCurrency: Story = { + args: { + currencies: CURRENCIES, + value: 'GBp' + } +}; + +export const SupranationalCurrency: Story = { + args: { + currencies: CURRENCIES, + value: 'XAU' + } +}; + +export const WithoutValue: Story = { + args: { + currencies: CURRENCIES, + value: null + } +}; diff --git a/libs/ui/src/lib/currency-selector/currency-selector.component.ts b/libs/ui/src/lib/currency-selector/currency-selector.component.ts index 724e867128..fa4af10677 100644 --- a/libs/ui/src/lib/currency-selector/currency-selector.component.ts +++ b/libs/ui/src/lib/currency-selector/currency-selector.component.ts @@ -1,3 +1,8 @@ +import { + getCountryCodeFromCurrency, + getEmojiFlag +} from '@ghostfolio/common/helper'; + import { FocusMonitor } from '@angular/cdk/a11y'; import { CUSTOM_ELEMENTS_SCHEMA, @@ -39,7 +44,8 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field'; changeDetection: ChangeDetectionStrategy.OnPush, host: { '[attr.aria-describedBy]': 'describedBy', - '[id]': 'id' + '[id]': 'id', + class: 'align-items-center d-flex' }, imports: [ FormsModule, @@ -56,7 +62,6 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field'; ], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-currency-selector', - styleUrls: ['./currency-selector.component.scss'], templateUrl: 'currency-selector.component.html' }) export class GfCurrencySelectorComponent @@ -86,6 +91,14 @@ export class GfCurrencySelectorComponent this.controlType = 'currency-selector'; } + public get emojiFlagOfSelectedCurrency() { + const selectedCurrency = this.currencies().find((currency) => { + return currency === this.control.value; + }); + + return this.getEmojiFlagFromCurrency(selectedCurrency); + } + public override get empty() { return this.input().empty; } @@ -99,6 +112,10 @@ export class GfCurrencySelectorComponent this.input().focus(); } + public getEmojiFlagFromCurrency(aCurrency = '') { + return getEmojiFlag(getCountryCodeFromCurrency(aCurrency)); + } + public ngOnInit() { if (this.disabled) { this.control.disable();