Currency Converter Widget
blockfinanceWise and Revolut-style real-time multi-currency exchange calculator and transfer fee estimator with live mid-market rates, transparent fee breakdown rail, bank savings comparison, and quick-rate ticker.
Installation
$pnpm dlx uipkge-ng@latest add currency-converter-widget$npx uipkge-ng@latest add currency-converter-widget$yarn dlx uipkge-ng@latest add currency-converter-widget$bunx uipkge-ng@latest add currency-converter-widgetManifest:
https://uipkge.dev/r/angular/currency-converter-widget.jsonInstalls to:src/app/components/blocks/currency-converter-widget/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
CurrencyOptioninterface CurrencyOption {
code: string
name: string
symbol: string
flag: string
rateToBase: number // USD base = 1.0
deliverySpeed: string
deliveryNetwork: string
}PopularPairinterface PopularPair {
id: string
from: string
to: string
change24h: number
trend: 'up' | 'down'
}TransferPayloadinterface TransferPayload {
sendAmount: number
fromCurrency: string
toCurrency: string
recipientGets: number
fee: number
rate: number
}Files installed (2)
src/app/components/blocks/currency-converter-widget/currency-converter-widget.component.ts25.9 kB
// Wise and Revolut-style real-time multi-currency exchange calculator and transfer fee // estimator with live mid-market rates, transparent fee breakdown rail, bank savings // comparison, and quick-rate ticker. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core' import { ArrowDownRight, ArrowRight, ArrowUpDown, ArrowUpRight, Building2, Coins, Lock, LucideAngularModule, Minus, RefreshCw, ShieldCheck, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' import { UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, } from '@/ui/select/select.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiInputComponent } from '@/ui/input/input.component' export interface CurrencyOption { code: string name: string symbol: string flag: string rateToBase: number // USD base = 1.0 deliverySpeed: string deliveryNetwork: string } export interface PopularPair { id: string from: string to: string change24h: number trend: 'up' | 'down' } export interface TransferPayload { sendAmount: number fromCurrency: string toCurrency: string recipientGets: number fee: number rate: number } const CURRENCIES: CurrencyOption[] = [ { code: 'USD', name: 'US Dollar', symbol: '$', flag: '🇺🇸', rateToBase: 1.0, deliverySpeed: 'in minutes', deliveryNetwork: 'FedNow / RTP', }, { code: 'EUR', name: 'Euro', symbol: '€', flag: '🇪🇺', rateToBase: 0.9245, deliverySpeed: 'in minutes', deliveryNetwork: 'SEPA Instant', }, { code: 'GBP', name: 'British Pound', symbol: '£', flag: '🇬🇧', rateToBase: 0.7892, deliverySpeed: 'in seconds', deliveryNetwork: 'Faster Payments', }, { code: 'JPY', name: 'Japanese Yen', symbol: '¥', flag: '🇯🇵', rateToBase: 154.6, deliverySpeed: 'within 4 hours', deliveryNetwork: 'Zengin System', }, { code: 'CAD', name: 'Canadian Dollar', symbol: 'CA$', flag: '🇨🇦', rateToBase: 1.378, deliverySpeed: 'in minutes', deliveryNetwork: 'Interac Direct', }, { code: 'AUD', name: 'Australian Dollar', symbol: 'A$', flag: '🇦🇺', rateToBase: 1.523, deliverySpeed: 'in minutes', deliveryNetwork: 'NPP / PayID', }, { code: 'SGD', name: 'Singapore Dollar', symbol: 'S$', flag: '🇸🇬', rateToBase: 1.348, deliverySpeed: 'instant', deliveryNetwork: 'FAST Network', }, { code: 'CHF', name: 'Swiss Franc', symbol: 'CHF', flag: '🇨🇭', rateToBase: 0.898, deliverySpeed: 'in minutes', deliveryNetwork: 'SIC Instant', }, ] const POPULAR_PAIRS: PopularPair[] = [ { id: 'eur-usd', from: 'EUR', to: 'USD', change24h: 0.18, trend: 'up' }, { id: 'gbp-usd', from: 'GBP', to: 'USD', change24h: 0.24, trend: 'up' }, { id: 'usd-jpy', from: 'USD', to: 'JPY', change24h: -0.32, trend: 'down' }, { id: 'usd-cad', from: 'USD', to: 'CAD', change24h: 0.09, trend: 'up' }, { id: 'usd-eur', from: 'USD', to: 'EUR', change24h: -0.18, trend: 'down' }, { id: 'gbp-eur', from: 'GBP', to: 'EUR', change24h: 0.05, trend: 'up' }, ] function formatAmount(val: number, currencyCode: string): string { const isZeroDecimal = currencyCode === 'JPY' return new Intl.NumberFormat('en-US', { minimumFractionDigits: isZeroDecimal ? 0 : 2, maximumFractionDigits: isZeroDecimal ? 0 : 2, }).format(val) } function getPairRate(fromCode: string, toCode: string): string { const f = CURRENCIES.find((c) => c.code === fromCode)?.rateToBase || 1 const t = CURRENCIES.find((c) => c.code === toCode)?.rateToBase || 1 const rate = t / f return rate >= 100 ? rate.toFixed(2) : rate.toFixed(4) } @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-currency-converter-widget, [ui-currency-converter-widget]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiInputComponent, UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, UiSeparatorComponent, ], template: ` <div data-slot="currency-converter-widget" [class]="rootClass"> <!-- Header Section --> <div class="space-y-2"> <div class="flex flex-wrap items-center justify-between gap-2"> <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl"> Currency Converter & International Transfer </h2> <span ui-badge variant="outline" class="gap-1.5 py-1 text-xs font-normal"> <span class="bg-success size-2 animate-pulse rounded-full"></span> Live Market Rates </span> </div> <p class="text-muted-foreground text-sm"> Real-time mid-market exchange rates with transparent fees. No hidden markups. </p> <!-- Refresh & Current Mid-market rate banner --> <div class="border-border bg-muted/40 text-muted-foreground flex flex-wrap items-center justify-between gap-2 rounded-lg border px-3.5 py-2 text-xs" > <div class="flex items-center gap-2"> <span class="text-foreground font-medium"> Mid-market rate: 1 {{ fromCurrencyObj().code }} = {{ formattedExchangeRate() }} {{ toCurrencyObj().code }} </span> <span>Updated 1m ago</span> </div> <button type="button" aria-label="Refresh exchange rates" class="text-foreground hover:text-primary focus-visible:ring-ring inline-flex min-h-6 items-center gap-1 rounded px-1.5 py-0.5 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" (click)="handleRefresh()" > <lucide-icon [img]="RefreshCw" [class]="refreshIconClass" /> <span>{{ refreshSuccess() ? 'Refreshed!' : 'Refresh' }}</span> </button> </div> </div> <!-- Main Converter Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-content class="space-y-4 p-5 sm:p-6"> <!-- Payment method tab pill --> <div class="flex items-center justify-between gap-2"> <div class="border-border bg-muted/50 inline-flex rounded-lg border p-1 text-xs"> <button type="button" [class]="methodTabClass('bank')" (click)="paymentMethod.set('bank')"> Bank Transfer (Lowest Fee) </button> <button type="button" [class]="methodTabClass('card')" (click)="paymentMethod.set('card')"> Debit / Credit Card </button> </div> <span class="text-muted-foreground hidden text-xs sm:inline-flex"> Guaranteed rate for 24h </span> </div> <!-- "You send" Input Container --> <div class="border-input bg-card focus-within:border-ring focus-within:ring-ring/20 rounded-xl border p-3.5 shadow-2xs transition-colors focus-within:ring-2" > <div class="text-muted-foreground mb-1.5 flex items-center justify-between text-xs"> <label for="angular-send-amount-input" class="text-foreground font-medium">You send</label> <span>Max limit: $100,000 / transfer</span> </div> <div class="flex flex-wrap items-center gap-3"> <div class="flex flex-1 items-baseline gap-1"> <span class="text-muted-foreground text-xl font-semibold select-none sm:text-2xl"> {{ fromCurrencyObj().symbol }} </span> <ui-input id="angular-send-amount-input" type="number" [min]="1" [value]="sendAmount()" (valueChange)="onAmountChange($event)" placeholder="1,000.00" class="text-foreground placeholder:text-muted-foreground/40 w-full bg-transparent text-2xl font-bold tracking-tight tabular-nums outline-none sm:text-3xl" /> </div> <!-- From Currency Selector --> <div class="w-36 shrink-0"> <ui-select [value]="fromCurrency()" (valueChange)="fromCurrency.set($event)"> <button ui-select-trigger class="bg-muted/40 h-11 w-full text-xs font-medium sm:text-sm"> <ui-select-value placeholder="Select currency" /> </button> <ui-select-content class="max-h-64"> @for (curr of currencies; track curr.code) { <ui-select-item [value]="curr.code"> <div class="flex items-center gap-2"> <span class="text-base">{{ curr.flag }}</span> <span class="font-semibold">{{ curr.code }}</span> <span class="text-muted-foreground truncate text-xs">({{ curr.name }})</span> </div> </ui-select-item> } </ui-select-content> </ui-select> </div> </div> </div> <!-- Transfer Breakdown Rail (Vertical Connector) --> <div class="before:bg-border relative my-1 space-y-3.5 py-2 pl-6 before:absolute before:top-2 before:bottom-2 before:left-2.5 before:w-px" > <!-- Step 1: Fee --> <div class="relative flex items-center justify-between text-xs"> <div class="border-border bg-card text-muted-foreground absolute -left-[19px] flex size-3.5 items-center justify-center rounded-full border" > <lucide-icon [img]="Minus" class="size-2.5" /> </div> <div class="flex items-center gap-1.5"> <span class="text-foreground font-semibold tabular-nums"> -{{ fromCurrencyObj().symbol }}{{ formatAmount(totalFee(), fromCurrencyObj().code) }} {{ fromCurrencyObj().code }} </span> <span class="text-muted-foreground">Transfer fee</span> <span ui-badge variant="secondary" class="text-muted-foreground h-4.5 px-1.5 text-xs font-normal"> {{ paymentMethod() === 'card' ? '0.65% + $1.20' : '0.35% + $1.20' }} </span> </div> <span class="text-muted-foreground hidden sm:inline">Transparent fixed rate</span> </div> <!-- Step 2: Amount to convert --> <div class="relative flex items-center justify-between text-xs"> <div class="border-border bg-card text-muted-foreground absolute -left-[19px] flex size-3.5 items-center justify-center rounded-full border" > <span class="text-xs leading-none font-bold">=</span> </div> <div class="flex items-center gap-1.5"> <span class="text-foreground font-semibold tabular-nums"> {{ fromCurrencyObj().symbol }}{{ formatAmount(amountToConvert(), fromCurrencyObj().code) }} {{ fromCurrencyObj().code }} </span> <span class="text-muted-foreground">Amount to convert</span> </div> <span class="text-muted-foreground hidden sm:inline">Net transfer base</span> </div> <!-- Step 3: Guaranteed Exchange Rate & Swap Button Line --> <div class="relative flex flex-wrap items-center justify-between gap-2 text-xs"> <div class="border-primary/50 bg-primary text-primary-foreground absolute -left-[19px] flex size-3.5 items-center justify-center rounded-full border" > <lucide-icon [img]="Lock" class="size-2" /> </div> <div class="flex flex-wrap items-center gap-2"> <span class="text-foreground font-semibold tabular-nums"> × {{ formattedExchangeRate() }} </span> <span class="text-muted-foreground">Guaranteed mid-market rate</span> <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success gap-1 py-0.5 text-xs" > <lucide-icon [img]="Lock" class="size-2.5" /> 24h Guarantee </span> </div> <!-- Swap Currencies Button --> <button type="button" aria-label="Swap currencies" class="border-border bg-card text-foreground hover:bg-accent hover:border-primary/50 focus-visible:ring-ring inline-flex min-h-6 items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium shadow-2xs transition-[background-color,border-color,box-shadow,scale] focus-visible:ring-2 focus-visible:outline-none active:scale-95" (click)="handleSwap()" > <lucide-icon [img]="ArrowUpDown" class="text-primary size-3" /> <span>Swap</span> </button> </div> </div> <!-- "Recipient gets" Input / Output Container --> <div class="border-input bg-card focus-within:border-ring focus-within:ring-ring/20 rounded-xl border p-3.5 shadow-2xs transition-colors focus-within:ring-2" > <div class="text-muted-foreground mb-1.5 flex items-center justify-between text-xs"> <label class="text-foreground font-medium">Recipient gets</label> <span class="text-success flex items-center gap-1 font-medium"> <lucide-icon [img]="Zap" class="size-3" /> {{ toCurrencyObj().deliverySpeed }} </span> </div> <div class="flex flex-wrap items-center gap-3"> <div class="flex min-w-0 flex-1 items-baseline gap-1"> <span class="text-muted-foreground text-xl font-semibold select-none sm:text-2xl"> {{ toCurrencyObj().symbol }} </span> <div class="text-foreground w-full truncate text-2xl font-bold tracking-tight tabular-nums select-all sm:text-3xl" > {{ formatAmount(recipientGets(), toCurrencyObj().code) }} </div> </div> <!-- To Currency Selector --> <div class="w-36 shrink-0"> <ui-select [value]="toCurrency()" (valueChange)="toCurrency.set($event)"> <button ui-select-trigger class="bg-muted/40 h-11 w-full text-xs font-medium sm:text-sm"> <ui-select-value placeholder="Select currency" /> </button> <ui-select-content class="max-h-64"> @for (curr of currencies; track curr.code) { <ui-select-item [value]="curr.code"> <div class="flex items-center gap-2"> <span class="text-base">{{ curr.flag }}</span> <span class="font-semibold">{{ curr.code }}</span> <span class="text-muted-foreground truncate text-xs">({{ curr.name }})</span> </div> </ui-select-item> } </ui-select-content> </ui-select> </div> </div> </div> <!-- Delivery Speed & Savings Breakdown Banner --> <div class="border-border bg-muted/30 space-y-3 rounded-lg border p-4"> <div class="flex flex-wrap items-center justify-between gap-2"> <div class="flex items-center gap-2 text-xs"> <span class="bg-primary/10 text-primary flex size-6 items-center justify-center rounded-full"> <lucide-icon [img]="Zap" class="size-3.5" /> </span> <div> <p class="text-foreground font-semibold">Estimated delivery: {{ toCurrencyObj().deliverySpeed }}</p> <p class="text-muted-foreground">Via {{ toCurrencyObj().deliveryNetwork }}</p> </div> </div> <span ui-badge variant="outline" class="border-warning/30 bg-warning/10 text-warning gap-1 py-1 text-xs"> <lucide-icon [img]="Coins" class="size-3" /> Save ~{{ fromCurrencyObj().symbol }}{{ formatAmount(estimatedSavings(), fromCurrencyObj().code) }} {{ fromCurrencyObj().code }} </span> </div> <ui-separator /> <!-- Comparison vs Standard High-Street Bank --> <div class="grid grid-cols-1 gap-3 text-xs sm:grid-cols-2"> <div class="bg-card/60 border-border flex items-start gap-2 rounded-md border p-2.5"> <lucide-icon [img]="ShieldCheck" class="text-success mt-0.5 size-4 shrink-0" /> <div> <p class="text-foreground font-medium">UIPKGE Transfer</p> <p class="text-muted-foreground text-xs"> Fee: {{ fromCurrencyObj().symbol }}{{ formatAmount(totalFee(), fromCurrencyObj().code) }} · 0% exchange spread </p> </div> </div> <div class="bg-card/60 border-border flex items-start gap-2 rounded-md border p-2.5 opacity-75"> <lucide-icon [img]="Building2" class="text-muted-foreground mt-0.5 size-4 shrink-0" /> <div> <p class="text-foreground font-medium">Standard Bank Wire</p> <p class="text-muted-foreground text-xs"> Fee: ~{{ fromCurrencyObj().symbol }}{{ formatAmount(bankTotalCost(), fromCurrencyObj().code) }} · ~3.2% hidden spread </p> </div> </div> </div> </div> <!-- Primary Action Button --> <button ui-button class="h-11 w-full gap-2 text-base font-semibold shadow-sm" (click)="handleTransfer()"> <span >Get Started / Send {{ fromCurrencyObj().symbol }}{{ formatAmount(sendAmount(), fromCurrencyObj().code) }}</span > <lucide-icon [img]="ArrowRight" class="size-4" /> </button> <p class="text-muted-foreground flex items-center justify-center gap-1.5 text-center text-xs"> <lucide-icon [img]="Lock" class="text-muted-foreground size-3" /> <span>Rates locked for 24 hours. Licensed & regulated financial institution.</span> </p> </div> </div> <!-- Popular Currency Pairs Quick Rate Ticker --> <div class="space-y-3"> <div class="flex items-center justify-between"> <h3 class="text-foreground text-sm font-semibold">Popular Currency Pairs</h3> <span class="text-muted-foreground text-xs">Click pair to load</span> </div> <div class="grid grid-cols-2 gap-2.5 sm:grid-cols-3"> @for (pair of popularPairs; track pair.id) { <button type="button" [class]="pairCardClass(pair)" (click)="handleSelectPair(pair)"> <div class="flex w-full items-center justify-between text-xs"> <span class="text-foreground font-semibold">{{ pair.from }} / {{ pair.to }}</span> <span [class]="pairTrendClass(pair)"> @if (pair.trend === 'up') { <lucide-icon [img]="ArrowUpRight" class="size-3" /> } @else { <lucide-icon [img]="ArrowDownRight" class="size-3" /> } {{ pair.change24h > 0 ? '+' + pair.change24h + '%' : pair.change24h + '%' }} </span> </div> <div class="text-foreground text-sm font-bold tabular-nums"> {{ getPairRate(pair.from, pair.to) }} </div> </button> } </div> </div> </div> `, }) export class UiCurrencyConverterWidgetComponent { protected readonly ArrowDownRight = ArrowDownRight protected readonly ArrowRight = ArrowRight protected readonly ArrowUpDown = ArrowUpDown protected readonly ArrowUpRight = ArrowUpRight protected readonly Building2 = Building2 protected readonly Coins = Coins protected readonly Lock = Lock protected readonly Minus = Minus protected readonly RefreshCw = RefreshCw protected readonly ShieldCheck = ShieldCheck protected readonly Zap = Zap protected readonly currencies = CURRENCIES protected readonly popularPairs = POPULAR_PAIRS @Input() set initialSendAmount(v: number | undefined) { if (v !== undefined) this.sendAmount.set(v) } @Input() set initialFromCurrency(v: string | undefined) { if (v) this.fromCurrency.set(v) } @Input() set initialToCurrency(v: string | undefined) { if (v) this.toCurrency.set(v) } @Input('class') className?: string @Output() transfer = new EventEmitter<TransferPayload>() readonly sendAmount = signal(1000) readonly fromCurrency = signal('USD') readonly toCurrency = signal('EUR') readonly paymentMethod = signal<'bank' | 'card'>('bank') readonly isRefreshing = signal(false) readonly refreshSuccess = signal(false) readonly fromCurrencyObj = computed(() => CURRENCIES.find((c) => c.code === this.fromCurrency()) || CURRENCIES[0]!) readonly toCurrencyObj = computed(() => CURRENCIES.find((c) => c.code === this.toCurrency()) || CURRENCIES[1]!) // Dynamic mid-market rate calculation readonly exchangeRate = computed(() => { const from = this.fromCurrencyObj() const to = this.toCurrencyObj() if (!from || !to) return 1 return to.rateToBase / from.rateToBase }) readonly inverseExchangeRate = computed(() => { if (this.exchangeRate() === 0) return 0 return 1 / this.exchangeRate() }) readonly formattedExchangeRate = computed(() => { const r = this.exchangeRate() if (r >= 100) return r.toFixed(2) if (r >= 1) return r.toFixed(4) return r.toFixed(5) }) // Fee breakdown logic readonly fixedBaseFee = computed(() => { // Base fixed fee equivalent to ~$1.20 USD const fromRate = this.fromCurrencyObj().rateToBase const base = 1.2 * fromRate return this.paymentMethod() === 'card' ? base * 1.6 : base }) readonly variableRate = computed(() => (this.paymentMethod() === 'card' ? 0.0065 : 0.0035)) readonly variableFee = computed(() => { const amt = Number(this.sendAmount()) || 0 return amt * this.variableRate() }) readonly totalFee = computed(() => this.fixedBaseFee() + this.variableFee()) readonly amountToConvert = computed(() => { const amt = Number(this.sendAmount()) || 0 return Math.max(0, amt - this.totalFee()) }) readonly recipientGets = computed(() => this.amountToConvert() * this.exchangeRate()) // Bank comparison calculation readonly bankWireFee = computed(() => 20 * this.fromCurrencyObj().rateToBase) private readonly bankMarkupSpread = 0.032 // 3.2% average hidden bank margin readonly bankTotalCost = computed(() => { const amt = Number(this.sendAmount()) || 0 return this.bankWireFee() + amt * this.bankMarkupSpread }) readonly estimatedSavings = computed(() => Math.max(0, this.bankTotalCost() - this.totalFee())) get rootClass(): string { return cn('mx-auto w-full max-w-2xl space-y-6', this.className) } get refreshIconClass(): string { return cn('size-3.5 transition-transform duration-500', this.isRefreshing() ? 'text-primary animate-spin' : '') } methodTabClass(method: 'bank' | 'card'): string { return cn( 'min-h-6 rounded-md px-3 py-1 font-medium transition-colors', this.paymentMethod() === method ? 'bg-card text-foreground shadow-xs' : 'text-muted-foreground hover:text-foreground', ) } pairCardClass(pair: PopularPair): string { const selected = this.fromCurrency() === pair.from && this.toCurrency() === pair.to return cn( 'hover:bg-accent focus-visible:ring-ring flex flex-col items-start gap-1 rounded-lg border p-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none', selected ? 'border-primary bg-primary/5 ring-primary/20 ring-1' : 'border-border bg-card', ) } pairTrendClass(pair: PopularPair): string { return cn('flex items-center text-xs font-medium', pair.trend === 'up' ? 'text-success' : 'text-destructive') } formatAmount(val: number, currencyCode: string): string { return formatAmount(val, currencyCode) } getPairRate(fromCode: string, toCode: string): string { return getPairRate(fromCode, toCode) } onAmountChange(raw: string): void { this.sendAmount.set(Number.parseFloat(raw) || 0) } handleSwap(): void { const temp = this.fromCurrency() this.fromCurrency.set(this.toCurrency()) this.toCurrency.set(temp) } handleSelectPair(pair: PopularPair): void { this.fromCurrency.set(pair.from) this.toCurrency.set(pair.to) } handleRefresh(): void { if (this.isRefreshing()) return this.isRefreshing.set(true) setTimeout(() => { this.isRefreshing.set(false) this.refreshSuccess.set(true) setTimeout(() => { this.refreshSuccess.set(false) }, 2000) }, 450) } handleTransfer(): void { this.transfer.emit({ sendAmount: Number(this.sendAmount()) || 0, fromCurrency: this.fromCurrency(), toCurrency: this.toCurrency(), recipientGets: this.recipientGets(), fee: this.totalFee(), rate: this.exchangeRate(), }) } }src/app/components/blocks/currency-converter-widget/index.ts0.2 kB
export { UiCurrencyConverterWidgetComponent, type CurrencyOption, type PopularPair, type TransferPayload, } from './currency-converter-widget.component'
Raw manifest:https://uipkge.dev/r/angular/currency-converter-widget.json