Browse Source

Add country flag

pull/7561/head
Thomas Kaul 3 weeks ago
parent
commit
0ed376ea41
  1. 25
      libs/common/src/lib/helper.spec.ts
  2. 11
      libs/common/src/lib/helper.ts
  3. 9
      libs/ui/src/lib/currency-selector/currency-selector.component.html
  4. 3
      libs/ui/src/lib/currency-selector/currency-selector.component.scss
  5. 99
      libs/ui/src/lib/currency-selector/currency-selector.component.stories.ts
  6. 21
      libs/ui/src/lib/currency-selector/currency-selector.component.ts

25
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<string, [], any>;

11
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 (

9
libs/ui/src/lib/currency-selector/currency-selector.component.html

@ -1,3 +1,7 @@
@if (emojiFlagOfSelectedCurrency) {
<span class="flex-shrink-0 mr-1">{{ emojiFlagOfSelectedCurrency }}</span>
}
<input
autocapitalize="off"
autocomplete="off"
@ -12,7 +16,10 @@
>
@for (currency of filteredCurrencies; track currency) {
<mat-option class="line-height-1" [value]="currency">
{{ currency }}
<span class="align-items-center d-flex">
<span class="mr-1">{{ getEmojiFlagFromCurrency(currency) }}</span>
<span>{{ currency }}</span>
</span>
</mat-option>
}
</mat-autocomplete>

3
libs/ui/src/lib/currency-selector/currency-selector.component.scss

@ -1,3 +1,4 @@
:host {
display: block;
align-items: center;
display: flex;
}

99
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<GfCurrencySelectorComponent> = {
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: `
<form [formGroup]="formGroup">
<mat-form-field appearance="outline" class="w-100">
<mat-label>Currency</mat-label>
<gf-currency-selector
formControlName="currency"
[currencies]="currencies"
/>
</mat-form-field>
</form>
`
};
}
};
export default meta;
type Story = StoryObj<GfCurrencySelectorComponent>;
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
}
};

21
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();

Loading…
Cancel
Save