@block-auth.io/angular 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +37 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +82 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +10 -0
- package/ng-package.json +6 -0
- package/package.json +50 -0
- package/scripts/css-to-ts.mjs +14 -0
- package/src/lib/__tests__/integration.test.ts +45 -0
- package/src/lib/block-auth.service.test.ts +69 -0
- package/src/lib/block-auth.service.ts +81 -0
- package/src/lib/components/btns/btn-base-provider.component.test.ts +22 -0
- package/src/lib/components/btns/btn-base-provider.component.ts +55 -0
- package/src/lib/components/btns/btn-block-auth.component.test.ts +79 -0
- package/src/lib/components/btns/btn-block-auth.component.ts +104 -0
- package/src/lib/components/btns/btn-gmail.component.test.ts +16 -0
- package/src/lib/components/btns/btn-gmail.component.ts +41 -0
- package/src/lib/components/btns/btn-google-base.component.test.ts +16 -0
- package/src/lib/components/btns/btn-google-base.component.ts +73 -0
- package/src/lib/components/btns/btn-metamask.component.test.ts +20 -0
- package/src/lib/components/btns/btn-metamask.component.ts +98 -0
- package/src/lib/components/btns/btn-microsoft.component.test.ts +16 -0
- package/src/lib/components/btns/btn-microsoft.component.ts +77 -0
- package/src/lib/components/btns/btn-phantom.component.test.ts +20 -0
- package/src/lib/components/btns/btn-phantom.component.ts +93 -0
- package/src/lib/components/btns/btn-signin.component.test.ts +55 -0
- package/src/lib/components/btns/btn-signin.component.ts +108 -0
- package/src/lib/components/btns/btn-wallet-connect.component.test.ts +20 -0
- package/src/lib/components/btns/btn-wallet-connect.component.ts +109 -0
- package/src/lib/components/btns/index.ts +11 -0
- package/src/lib/components/contents/content-signin.component.test.ts +33 -0
- package/src/lib/components/contents/content-signin.component.ts +68 -0
- package/src/lib/components/contents/content-signup.component.test.ts +78 -0
- package/src/lib/components/contents/content-signup.component.ts +225 -0
- package/src/lib/components/dds/change-account.component.test.ts +66 -0
- package/src/lib/components/dds/change-account.component.ts +112 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.test.ts +66 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.ts +185 -0
- package/src/lib/components/dds/dropdown-profile-item.component.test.ts +75 -0
- package/src/lib/components/dds/dropdown-profile-item.component.ts +74 -0
- package/src/lib/components/dds/dropdown-profile.component.test.ts +98 -0
- package/src/lib/components/dds/dropdown-profile.component.ts +258 -0
- package/src/lib/components/flow-block-auth.component.test.ts +93 -0
- package/src/lib/components/flow-block-auth.component.ts +226 -0
- package/src/lib/components/modals/modal-overlay.component.test.ts +81 -0
- package/src/lib/components/modals/modal-overlay.component.ts +43 -0
- package/src/lib/components/modals/modal-restoration-expired.component.test.ts +71 -0
- package/src/lib/components/modals/modal-restoration-expired.component.ts +83 -0
- package/src/lib/components/modals/modal-restore.component.test.ts +74 -0
- package/src/lib/components/modals/modal-restore.component.ts +104 -0
- package/src/lib/components/modals/modal-signin.component.test.ts +66 -0
- package/src/lib/components/modals/modal-signin.component.ts +44 -0
- package/src/lib/components/modals/modal-signup.component.test.ts +56 -0
- package/src/lib/components/modals/modal-signup.component.ts +40 -0
- package/src/lib/components/sections/footer-section.component.test.ts +19 -0
- package/src/lib/components/sections/footer-section.component.ts +49 -0
- package/src/lib/components/sections/internals/providers-buttons.component.test.ts +27 -0
- package/src/lib/components/sections/internals/providers-buttons.component.ts +290 -0
- package/src/lib/components/sections/internals/providers-filters.component.test.ts +25 -0
- package/src/lib/components/sections/internals/providers-filters.component.ts +139 -0
- package/src/lib/components/sections/internals/selected-button.component.test.ts +36 -0
- package/src/lib/components/sections/internals/selected-button.component.ts +92 -0
- package/src/lib/components/sections/login-section.component.test.ts +49 -0
- package/src/lib/components/sections/login-section.component.ts +120 -0
- package/src/lib/components/sections/providers-section.component.test.ts +22 -0
- package/src/lib/components/sections/providers-section.component.ts +187 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.test.ts +14 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.ts +35 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.test.ts +72 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.ts +187 -0
- package/src/lib/components/txns/internals/txn-error.component.test.ts +100 -0
- package/src/lib/components/txns/internals/txn-error.component.ts +121 -0
- package/src/lib/components/txns/internals/txn-type.component.test.ts +52 -0
- package/src/lib/components/txns/internals/txn-type.component.ts +51 -0
- package/src/lib/components/txns/internals/txn.component.test.ts +188 -0
- package/src/lib/components/txns/internals/txn.component.ts +298 -0
- package/src/lib/components/txns/txns-overview.component.test.ts +107 -0
- package/src/lib/components/txns/txns-overview.component.ts +229 -0
- package/src/lib/icons/ai-outline-disconnect.component.ts +21 -0
- package/src/lib/icons/bi-logo-gmail.component.ts +21 -0
- package/src/lib/icons/cg-profile.component.ts +31 -0
- package/src/lib/icons/check-icon.component.ts +27 -0
- package/src/lib/icons/ci-clock2.component.ts +31 -0
- package/src/lib/icons/fa-arrow-right.component.ts +21 -0
- package/src/lib/icons/fa-times.component.ts +21 -0
- package/src/lib/icons/fa-undo.component.ts +21 -0
- package/src/lib/icons/icons.test.ts +25 -0
- package/src/lib/icons/index.ts +22 -0
- package/src/lib/icons/io-ios-arrow-back.component.ts +21 -0
- package/src/lib/icons/io-ios-arrow-forward.component.ts +21 -0
- package/src/lib/icons/io-md-arrow-back.component.ts +19 -0
- package/src/lib/icons/io-md-arrow-forward.component.ts +19 -0
- package/src/lib/icons/pi-warning-fill.component.ts +21 -0
- package/src/lib/icons/tb-arrows-exchange2.component.ts +30 -0
- package/src/lib/icons/tb-copy-check-filled.component.ts +21 -0
- package/src/lib/icons/tb-copy.component.ts +34 -0
- package/src/lib/icons/ti-vendor-microsoft.component.ts +21 -0
- package/src/lib/inject-store.test.ts +26 -0
- package/src/lib/inject-store.ts +16 -0
- package/src/lib/primitives/arrow-icon.component.test.ts +12 -0
- package/src/lib/primitives/arrow-icon.component.ts +38 -0
- package/src/lib/primitives/btn-arrow-dir.component.test.ts +22 -0
- package/src/lib/primitives/btn-arrow-dir.component.ts +65 -0
- package/src/lib/primitives/btn-base.component.test.ts +38 -0
- package/src/lib/primitives/btn-base.component.ts +140 -0
- package/src/lib/primitives/divider-base.component.test.ts +12 -0
- package/src/lib/primitives/divider-base.component.ts +74 -0
- package/src/lib/primitives/dropdown-arrow.component.test.ts +12 -0
- package/src/lib/primitives/dropdown-arrow.component.ts +46 -0
- package/src/lib/primitives/dropdown-base.component.test.ts +49 -0
- package/src/lib/primitives/dropdown-base.component.ts +329 -0
- package/src/lib/primitives/helpers.ts +151 -0
- package/src/lib/primitives/horizontal-loader.component.test.ts +19 -0
- package/src/lib/primitives/horizontal-loader.component.ts +70 -0
- package/src/lib/primitives/icon-base.component.test.ts +19 -0
- package/src/lib/primitives/icon-base.component.ts +58 -0
- package/src/lib/primitives/img-loader.component.test.ts +12 -0
- package/src/lib/primitives/img-loader.component.ts +118 -0
- package/src/lib/primitives/index.ts +29 -0
- package/src/lib/primitives/input-base.component.test.ts +20 -0
- package/src/lib/primitives/input-base.component.ts +210 -0
- package/src/lib/primitives/lifetime-slider-base.component.test.ts +88 -0
- package/src/lib/primitives/lifetime-slider-base.component.ts +75 -0
- package/src/lib/primitives/oval-spinner.component.test.ts +9 -0
- package/src/lib/primitives/oval-spinner.component.ts +158 -0
- package/src/lib/primitives/pickers/dropdown-picker-animated-base.component.ts +220 -0
- package/src/lib/primitives/pickers/picker-button.component.ts +74 -0
- package/src/lib/primitives/pickers/time-picker-dropdown-selector.component.ts +188 -0
- package/src/lib/primitives/pickers/time-picker.component.ts +124 -0
- package/src/lib/primitives/pickers/time-unit-selector.component.ts +82 -0
- package/src/lib/primitives/progressbar.component.test.ts +18 -0
- package/src/lib/primitives/progressbar.component.ts +93 -0
- package/src/lib/primitives/providersStyles.ts +268 -0
- package/src/lib/primitives/slider-base.component.ts +56 -0
- package/src/lib/provide-block-auth.test.ts +43 -0
- package/src/lib/provide-block-auth.ts +43 -0
- package/src/lib/runtime/block-auth-runtime.directive.test.ts +89 -0
- package/src/lib/runtime/block-auth-runtime.directive.ts +68 -0
- package/src/lib/teleport-body.directive.test.ts +29 -0
- package/src/lib/teleport-body.directive.ts +21 -0
- package/src/lib/testing.ts +15 -0
- package/src/lib/transitions.css +23 -0
- package/src/public-api.ts +85 -0
- package/src/vitest.setup.ts +8 -0
- package/tailwind.config.cjs +6 -0
- package/tsconfig.json +15 -0
- package/tsconfig.lib.json +9 -0
- package/vite.config.mts +44 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { render, screen } from '@testing-library/angular';
|
|
4
|
+
import { FooterSectionComponent } from './footer-section.component';
|
|
5
|
+
|
|
6
|
+
// Port de packages/react/src/components/sections/FooterSection.test.tsx.
|
|
7
|
+
describe('FooterSection', () => {
|
|
8
|
+
it('renderiza el footer con el enlace', async () => {
|
|
9
|
+
@Component({
|
|
10
|
+
standalone: true,
|
|
11
|
+
imports: [FooterSectionComponent],
|
|
12
|
+
template: `<ba-footer-section text1="Powered by" text2="Block-Auth" href="https://block-auth.io" />`,
|
|
13
|
+
})
|
|
14
|
+
class HostComponent {}
|
|
15
|
+
await render(HostComponent);
|
|
16
|
+
const link = screen.getByText('Block-Auth');
|
|
17
|
+
expect(link.getAttribute('href')).toBe('https://block-auth.io');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Port literal de packages/react/src/components/sections/FooterSection.tsx
|
|
5
|
+
* (mapa: packages/vue/.../sections/FooterSection.vue). Footer presentacional
|
|
6
|
+
* puro, sin core logic ni stores.
|
|
7
|
+
*/
|
|
8
|
+
@Component({
|
|
9
|
+
selector: 'ba-footer-section',
|
|
10
|
+
standalone: true,
|
|
11
|
+
// Hijo directo del grid de content-signup: el host debe ser transparente al
|
|
12
|
+
// layout para que el <footer> interno sea el grid item real (ver
|
|
13
|
+
// providers-section para el detalle). Sin esto el layout del modal se solapa.
|
|
14
|
+
host: { style: 'display: contents' },
|
|
15
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
16
|
+
template: `
|
|
17
|
+
<footer
|
|
18
|
+
data-testid="footer-section"
|
|
19
|
+
[class]="
|
|
20
|
+
'bas-group bas-box-border bas-flex bas-h-12 bas-items-end bas-justify-start bas-py-1 bas-text-left bas-text-gray-500 ' +
|
|
21
|
+
getFooterClassNames(isEditable(), device())
|
|
22
|
+
"
|
|
23
|
+
>
|
|
24
|
+
<p class="bas-ba-transition-2 bas-m-0 bas-text-left bas-text-gray-500 group-hover:bas-text-slate-600">
|
|
25
|
+
{{ text1() }}
|
|
26
|
+
<a
|
|
27
|
+
[href]="href()"
|
|
28
|
+
target="_blank"
|
|
29
|
+
rel="noreferrer"
|
|
30
|
+
class="bal-ba-transition-2 bas-font-semibold bas-leading-6 bas-text-[var(--bal-primary-900)] group-hover:bas-text-[var(--bal-primary-600)]"
|
|
31
|
+
>{{ text2() }}</a
|
|
32
|
+
>
|
|
33
|
+
</p>
|
|
34
|
+
</footer>
|
|
35
|
+
`,
|
|
36
|
+
})
|
|
37
|
+
export class FooterSectionComponent {
|
|
38
|
+
isEditable = input(false);
|
|
39
|
+
device = input<'desktop' | 'phone'>('desktop');
|
|
40
|
+
text1 = input('');
|
|
41
|
+
text2 = input('');
|
|
42
|
+
href = input('');
|
|
43
|
+
|
|
44
|
+
getFooterClassNames(editable: boolean, dev: string): string {
|
|
45
|
+
if (!editable) return 'bas-text-xxs xsm:bas-text-xs xsm1:bas-text-sm sm1:bas-col-span-2';
|
|
46
|
+
if (dev === 'phone') return 'bas-text-xs';
|
|
47
|
+
return 'bas-text-sm bas-col-span-2';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
4
|
+
import { ProvidersButtonsComponent } from './providers-buttons.component';
|
|
5
|
+
|
|
6
|
+
// Port de packages/react/src/components/sections/internals/ProvidersButtons.test.tsx.
|
|
7
|
+
// ProvidersButtons no inyecta el client (renderiza <ba-btn-base-provider>, que
|
|
8
|
+
// tampoco lo hace), así que no requiere provideBlockAuthClientForTest.
|
|
9
|
+
describe('ProvidersButtons', () => {
|
|
10
|
+
it('renderiza un boton por proveedor y propaga la seleccion', async () => {
|
|
11
|
+
const onSelected = vi.fn();
|
|
12
|
+
@Component({
|
|
13
|
+
standalone: true,
|
|
14
|
+
imports: [ProvidersButtonsComponent],
|
|
15
|
+
template: `<ba-providers-buttons [showedProviders]="providers" (selectedServiceChange)="onSelected($event)" />`,
|
|
16
|
+
})
|
|
17
|
+
class HostComponent {
|
|
18
|
+
providers = ['metamask', 'phantom'];
|
|
19
|
+
onSelected = onSelected;
|
|
20
|
+
}
|
|
21
|
+
await render(HostComponent);
|
|
22
|
+
const buttons = screen.getAllByRole('button');
|
|
23
|
+
expect(buttons.length).toBe(2);
|
|
24
|
+
await fireEvent.click(buttons[0]);
|
|
25
|
+
expect(onSelected).toHaveBeenCalledWith('metamask');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
DestroyRef,
|
|
5
|
+
ElementRef,
|
|
6
|
+
PLATFORM_ID,
|
|
7
|
+
effect,
|
|
8
|
+
inject,
|
|
9
|
+
input,
|
|
10
|
+
output,
|
|
11
|
+
signal,
|
|
12
|
+
viewChild,
|
|
13
|
+
} from '@angular/core';
|
|
14
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
15
|
+
import { BtnArrowDirComponent } from '../../../primitives/btn-arrow-dir.component';
|
|
16
|
+
import { providersStyles } from '../../../primitives/providersStyles';
|
|
17
|
+
import { BtnBaseProviderComponent } from '../../btns/btn-base-provider.component';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Grid de proveedores: tarjetas blancas bordeadas, cada una con el icono del
|
|
21
|
+
* provider sobre fondo tintado y el borde/hover de `providersStyles`.
|
|
22
|
+
*
|
|
23
|
+
* Diferencia de arquitectura respecto a react (idéntica al port Vue): allí
|
|
24
|
+
* `providersWrapperRef`/`providersContainerRef` los crea el padre
|
|
25
|
+
* (`ProvidersSection`) para medir `scrollWidth`/`clientWidth` y derivar
|
|
26
|
+
* `isProvidersScroll`. En Angular los template refs son locales al componente
|
|
27
|
+
* que los declara, así que ese efecto de medición se internaliza aquí (refs
|
|
28
|
+
* propios + estado `isProvidersScroll`). `setSelectedService` (callback) ->
|
|
29
|
+
* output `selectedServiceChange`. El shake de framer-motion -> clase `.ba-shake`.
|
|
30
|
+
*/
|
|
31
|
+
@Component({
|
|
32
|
+
selector: 'ba-providers-buttons',
|
|
33
|
+
standalone: true,
|
|
34
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
35
|
+
imports: [BtnArrowDirComponent, BtnBaseProviderComponent],
|
|
36
|
+
template: `
|
|
37
|
+
<div
|
|
38
|
+
#wrapper
|
|
39
|
+
data-testid="providers-buttons"
|
|
40
|
+
[class]="
|
|
41
|
+
'bas-relative bas-z-30 bas-box-border bas-w-full ' +
|
|
42
|
+
getProvidersWrapperClassNames(providersFiltersEnabled(), isMobile(), isProvidersScroll(), isEditable(), device()) +
|
|
43
|
+
(isEditable() && isEditing() && !isOpenProvidersBtnsEditable ? ' ba-shake' : '')
|
|
44
|
+
"
|
|
45
|
+
>
|
|
46
|
+
@if (isMobile() && isProvidersScroll()) {
|
|
47
|
+
<div class="bas-box-border bas-flex bas-h-full bas-w-full bas-items-center bas-justify-start bas-pb-4">
|
|
48
|
+
<ba-btn-arrow-dir direction="back" [disabled]="false" arrowType="arrow" (clicked)="onScrollArrowClicked('left')" />
|
|
49
|
+
</div>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
#container
|
|
54
|
+
[attr.draggable]="isProvidersScroll()"
|
|
55
|
+
[class]="
|
|
56
|
+
'bal-ba-scroll-base bas-box-border bas-w-full bas-items-start ' +
|
|
57
|
+
(isMobile() && isProvidersScroll() ? '' : 'bas-justify-center') + ' ' +
|
|
58
|
+
(isEditable() && device() === 'phone' ? 'bal-ba-scroll-horizontal' : 'bal-ba-max-659-scroll-horizontal') + ' ' +
|
|
59
|
+
providersContainerClassNames(
|
|
60
|
+
uiConfigProvidersInfo().providersColumns ?? 3,
|
|
61
|
+
providersFiltersEnabled(),
|
|
62
|
+
showedProviders(),
|
|
63
|
+
isMobile(),
|
|
64
|
+
isEditable(),
|
|
65
|
+
device()
|
|
66
|
+
)
|
|
67
|
+
"
|
|
68
|
+
>
|
|
69
|
+
@for (item of showedProviders(); track $index) {
|
|
70
|
+
<div [class]="getBtnBaseProviderContainerClassNames(isEditable(), device())">
|
|
71
|
+
<ba-btn-base-provider
|
|
72
|
+
[text]="getBtnProviderText(item, uiConfigProvidersInfo().providersColumns ?? 3, isMobile(), uiConfigProvidersInfo().showProviderNameOnMobile)"
|
|
73
|
+
[sizeClassNames]="getBtnProviderSize(isMobile(), uiConfigProvidersInfo().showProviderNameOnMobile, isEditable(), device())"
|
|
74
|
+
[borderClassNames]="getBtnProviderBorderClassNames(item, selectedService())"
|
|
75
|
+
[isEditable]="isEditable()"
|
|
76
|
+
[device]="device()"
|
|
77
|
+
(providerClicked)="selectedServiceChange.emit(item)"
|
|
78
|
+
>
|
|
79
|
+
@if (providersStyles[item]) {
|
|
80
|
+
<img
|
|
81
|
+
slot="icon"
|
|
82
|
+
[src]="providersStyles[item].imgSrc"
|
|
83
|
+
[alt]="providersStyles[item].name"
|
|
84
|
+
[class]="getIconSizeClassNames(item, isEditable(), device()) + ' bas-rounded-lg'"
|
|
85
|
+
/>
|
|
86
|
+
}
|
|
87
|
+
</ba-btn-base-provider>
|
|
88
|
+
</div>
|
|
89
|
+
}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
@if (isMobile() && isProvidersScroll()) {
|
|
93
|
+
<div class="bas-box-border bas-flex bas-h-full bas-w-full bas-items-center bas-justify-end bas-pb-4">
|
|
94
|
+
<ba-btn-arrow-dir direction="forward" [disabled]="false" arrowType="arrow" (clicked)="onScrollArrowClicked('right')" />
|
|
95
|
+
</div>
|
|
96
|
+
}
|
|
97
|
+
</div>
|
|
98
|
+
`,
|
|
99
|
+
styles: [
|
|
100
|
+
`
|
|
101
|
+
@keyframes ba-shake {
|
|
102
|
+
0% { transform: rotate(0deg); }
|
|
103
|
+
35% { transform: rotate(-0.4deg); }
|
|
104
|
+
70% { transform: rotate(0.6deg); }
|
|
105
|
+
100% { transform: rotate(0deg); }
|
|
106
|
+
}
|
|
107
|
+
.ba-shake { animation: ba-shake 0.35s infinite; }
|
|
108
|
+
`,
|
|
109
|
+
],
|
|
110
|
+
})
|
|
111
|
+
export class ProvidersButtonsComponent {
|
|
112
|
+
providersFiltersEnabled = input(false);
|
|
113
|
+
isMobile = input(false);
|
|
114
|
+
uiConfigProvidersInfo = input<{ providersColumns?: number; showProviderNameOnMobile?: boolean }>({
|
|
115
|
+
providersColumns: 3,
|
|
116
|
+
showProviderNameOnMobile: true,
|
|
117
|
+
});
|
|
118
|
+
showedProviders = input<string[]>([]);
|
|
119
|
+
selectedService = input('');
|
|
120
|
+
isEditable = input(false);
|
|
121
|
+
isEditing = input(false);
|
|
122
|
+
device = input<'desktop' | 'phone'>('desktop');
|
|
123
|
+
|
|
124
|
+
selectedServiceChange = output<string>();
|
|
125
|
+
|
|
126
|
+
// Alias del mapa de estilos para poder referenciarlo desde el template.
|
|
127
|
+
protected readonly providersStyles = providersStyles;
|
|
128
|
+
// `isOpenProvidersBtnsEditable` (useState local nunca mutado) — conservado
|
|
129
|
+
// por paridad de API del shake.
|
|
130
|
+
protected readonly isOpenProvidersBtnsEditable = false;
|
|
131
|
+
|
|
132
|
+
private readonly wrapperRef = viewChild<ElementRef<HTMLDivElement>>('wrapper');
|
|
133
|
+
private readonly containerRef = viewChild<ElementRef<HTMLDivElement>>('container');
|
|
134
|
+
protected readonly isProvidersScroll = signal(false);
|
|
135
|
+
|
|
136
|
+
private readonly platformId = inject(PLATFORM_ID);
|
|
137
|
+
private readonly destroyRef = inject(DestroyRef);
|
|
138
|
+
|
|
139
|
+
constructor() {
|
|
140
|
+
// Port 1:1 del efecto de medición de ProvidersSection.tsx (líneas 85-103),
|
|
141
|
+
// internalizado aquí (ver comentario de cabecera).
|
|
142
|
+
effect((onCleanup) => {
|
|
143
|
+
// Dependencias reactivas del efecto original.
|
|
144
|
+
const mobile = this.isMobile();
|
|
145
|
+
this.showedProviders();
|
|
146
|
+
this.isEditable();
|
|
147
|
+
this.isEditing();
|
|
148
|
+
this.device();
|
|
149
|
+
if (!mobile) {
|
|
150
|
+
this.isProvidersScroll.set(false);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!isPlatformBrowser(this.platformId)) return;
|
|
154
|
+
this.measureScroll();
|
|
155
|
+
const t = setTimeout(() => this.measureScroll(), 500);
|
|
156
|
+
const onResize = () => this.measureScroll();
|
|
157
|
+
window.addEventListener('resize', onResize);
|
|
158
|
+
onCleanup(() => {
|
|
159
|
+
clearTimeout(t);
|
|
160
|
+
window.removeEventListener('resize', onResize);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private measureScroll(): void {
|
|
166
|
+
const container = this.containerRef()?.nativeElement;
|
|
167
|
+
const wrapper = this.wrapperRef()?.nativeElement;
|
|
168
|
+
if (!container || !wrapper) return;
|
|
169
|
+
this.isProvidersScroll.set(container.scrollWidth > wrapper.clientWidth - 24);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
onScrollArrowClicked(direction: 'left' | 'right'): void {
|
|
173
|
+
const el = this.containerRef()?.nativeElement;
|
|
174
|
+
if (!el) return;
|
|
175
|
+
if (direction === 'left') el.scrollLeft -= 128;
|
|
176
|
+
else el.scrollLeft += 128;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ------ CLASSNAMES (port literal) ------
|
|
180
|
+
providersContainerClassNames(
|
|
181
|
+
providersCols: number,
|
|
182
|
+
filtersEnabled: boolean,
|
|
183
|
+
providers: string[],
|
|
184
|
+
mobile: boolean,
|
|
185
|
+
editable: boolean,
|
|
186
|
+
dev: string,
|
|
187
|
+
): string {
|
|
188
|
+
let classNames = '';
|
|
189
|
+
if (!editable)
|
|
190
|
+
classNames += ' bas-flex bas-flex-row bas-overflow-x-auto sm1:bas-grid sm1:bas-overflow-y-auto ';
|
|
191
|
+
else {
|
|
192
|
+
if (dev === 'phone') classNames += ' bas-flex bas-flex-row bas-overflow-x-auto ';
|
|
193
|
+
else classNames += ' bas-grid bas-overflow-y-auto ';
|
|
194
|
+
}
|
|
195
|
+
const height = mobile ? 'bas-h-full' : 'bas-h-fit';
|
|
196
|
+
const hPrClassNames = `bas-h-full ${!editable ? 'sm1:bas-pr-1.5' : dev !== 'phone' ? 'bas-pr-1.5' : ''}`;
|
|
197
|
+
const getHPrClassNames = (length: number) => {
|
|
198
|
+
if (providers.length > length) classNames += ` ${hPrClassNames} `;
|
|
199
|
+
else classNames += ` ${height} `;
|
|
200
|
+
};
|
|
201
|
+
const gridClassNames = (classWithSm1: string, classWithoutSm1: string) => {
|
|
202
|
+
if (!editable) return classWithSm1;
|
|
203
|
+
if (dev !== 'phone') return classWithoutSm1;
|
|
204
|
+
return '';
|
|
205
|
+
};
|
|
206
|
+
if (providersCols === 1) {
|
|
207
|
+
classNames += ` bas-gap-2 ${gridClassNames('sm1:bas-grid-cols-1', 'bas-grid-cols-1')} `;
|
|
208
|
+
if (filtersEnabled) getHPrClassNames(2);
|
|
209
|
+
else getHPrClassNames(3);
|
|
210
|
+
} else if (providersCols === 2) {
|
|
211
|
+
classNames += ` bas-gap-2.5 ${gridClassNames('sm1:bas-grid-cols-2', 'bas-grid-cols-2')} `;
|
|
212
|
+
if (filtersEnabled) getHPrClassNames(4);
|
|
213
|
+
else getHPrClassNames(6);
|
|
214
|
+
} else if (providersCols === 3) {
|
|
215
|
+
classNames += ` bas-gap-x-2 bas-gap-y-2.5 ${gridClassNames('sm1:bas-grid-cols-3', 'bas-grid-cols-3')} `;
|
|
216
|
+
if (filtersEnabled) getHPrClassNames(6);
|
|
217
|
+
else getHPrClassNames(9);
|
|
218
|
+
}
|
|
219
|
+
if (!mobile && filtersEnabled) {
|
|
220
|
+
if (!editable) classNames += ' sm1:bas-mt-4 sm1:bas-max-h-[calc(100%-16px)] ';
|
|
221
|
+
else if (dev !== 'phone') classNames += ' bas-mt-4 bas-max-h-[calc(100%-16px)] ';
|
|
222
|
+
}
|
|
223
|
+
return classNames;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
getBtnProviderText(item: string, providerCols: number, mobile: boolean, showOnMobile?: boolean): string {
|
|
227
|
+
if ((providerCols === 1 && !mobile) || (mobile && showOnMobile)) return item;
|
|
228
|
+
return '';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
getBtnProviderSize(mobile: boolean, showOnMobile: boolean | undefined, editable: boolean, dev: string): string {
|
|
232
|
+
let classNames = '';
|
|
233
|
+
if (!editable) classNames += ' sm1:bas-h-16 sm1:bas-w-full ';
|
|
234
|
+
else if (dev !== 'phone') classNames += ' bas-h-16 bas-w-full ';
|
|
235
|
+
if (!mobile) return classNames;
|
|
236
|
+
if (showOnMobile) classNames += ' bas-min-w-[128px] bas-h-12 ';
|
|
237
|
+
classNames += ' bas-w-16 bas-h-12 ';
|
|
238
|
+
return classNames;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getBtnProviderBorderClassNames(item: string, selected: string): string {
|
|
242
|
+
const style = providersStyles[item];
|
|
243
|
+
let classNames = `${style ? style.hoverBorderStyles : 'hover:bas-border-slate-400'}`;
|
|
244
|
+
if (item === 'blockauth') classNames += ' bas-border-[3px] ';
|
|
245
|
+
else classNames += ' bas-border-2 ';
|
|
246
|
+
if (selected === item && style) classNames += ` ${style.borderStyles} `;
|
|
247
|
+
else classNames += ' bas-border-slate-300 ';
|
|
248
|
+
return classNames;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
getBtnBaseProviderContainerClassNames(editable: boolean, dev: string): string {
|
|
252
|
+
if (!editable) return 'bas-h-fit bas-w-fit sm1:bas-h-16 sm1:bas-w-full';
|
|
253
|
+
if (dev === 'phone') return 'bas-h-fit bas-w-fit';
|
|
254
|
+
return 'bas-h-16 bas-w-full';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getIconSizeClassNames(item: string, editable: boolean, dev: string): string {
|
|
258
|
+
if (item === 'blockauth') {
|
|
259
|
+
if (!editable) return 'bas-h-9 bas-w-9 sm1:bas-h-12 sm1:bas-w-12';
|
|
260
|
+
if (dev === 'phone') return 'bas-h-9 bas-w-9';
|
|
261
|
+
return 'bas-h-12 bas-w-12';
|
|
262
|
+
}
|
|
263
|
+
if (!editable) return 'bas-h-8 bas-w-8 sm1:bas-h-10 sm1:bas-w-10';
|
|
264
|
+
if (dev === 'phone') return 'bas-h-8 bas-w-8';
|
|
265
|
+
return 'bas-h-10 bas-w-10';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
getProvidersWrapperClassNames(
|
|
269
|
+
filtersEnabled: boolean,
|
|
270
|
+
mobile: boolean,
|
|
271
|
+
providersScroll: boolean,
|
|
272
|
+
editable: boolean,
|
|
273
|
+
dev: string,
|
|
274
|
+
): string {
|
|
275
|
+
const isMobileIsProvidersScrollClassNames = `${
|
|
276
|
+
mobile && providersScroll ? 'bas-grid bas-grid-cols-[40px_1fr_40px] bas-px-2' : 'bas-px-3'
|
|
277
|
+
}`;
|
|
278
|
+
if (!editable) {
|
|
279
|
+
return `bas-py-1.5 bas-col-span-2 bas-h-[76px] sm1:bas-col-span-1
|
|
280
|
+
sm1:bas-px-2 sm1:bas-py-0 ${filtersEnabled ? 'sm1:bas-h-[188px]' : 'sm1:bas-h-[230px]'} ${isMobileIsProvidersScrollClassNames}`;
|
|
281
|
+
}
|
|
282
|
+
if (dev === 'phone') {
|
|
283
|
+
return `bas-py-1.5 bas-col-span-2 bas-h-[76px]
|
|
284
|
+
${isMobileIsProvidersScrollClassNames}`;
|
|
285
|
+
}
|
|
286
|
+
return `bas-col-span-1 bas-px-2 bas-py-0 ${filtersEnabled ? 'bas-h-[188px]' : 'bas-h-[230px]'} ${
|
|
287
|
+
mobile && providersScroll ? 'bas-grid bas-grid-cols-[40px_1fr_40px]' : ''
|
|
288
|
+
}`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
4
|
+
import { ProvidersFiltersComponent } from './providers-filters.component';
|
|
5
|
+
|
|
6
|
+
// Port de packages/react/src/components/sections/internals/ProvidersFilters.test.tsx.
|
|
7
|
+
// El i18n se lee del store `$locale` del core (singleton), así que no hace
|
|
8
|
+
// falta un client falso para este componente presentacional.
|
|
9
|
+
describe('ProvidersFilters', () => {
|
|
10
|
+
it('renderiza el buscador y propaga el cambio', async () => {
|
|
11
|
+
const onChangeProviderSearch = vi.fn();
|
|
12
|
+
@Component({
|
|
13
|
+
standalone: true,
|
|
14
|
+
imports: [ProvidersFiltersComponent],
|
|
15
|
+
template: `<ba-providers-filters (changeProviderSearch)="onChangeProviderSearch($event)" />`,
|
|
16
|
+
})
|
|
17
|
+
class HostComponent {
|
|
18
|
+
onChangeProviderSearch = onChangeProviderSearch;
|
|
19
|
+
}
|
|
20
|
+
await render(HostComponent);
|
|
21
|
+
const input = screen.getByTestId('provider-search');
|
|
22
|
+
await fireEvent.input(input, { target: { value: 'meta' } });
|
|
23
|
+
expect(onChangeProviderSearch).toHaveBeenCalledWith('meta');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
|
|
2
|
+
import { $locale, t as translate, type TextKey } from '@block-auth.io/core';
|
|
3
|
+
import { injectStore } from '../../../inject-store';
|
|
4
|
+
import { InputBaseComponent } from '../../../primitives/input-base.component';
|
|
5
|
+
import { DropdownBaseComponent, type DropdownItem } from '../../../primitives/dropdown-base.component';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fila de búsqueda de proveedores + filtro por web-type. El buscador es el
|
|
9
|
+
* `<ba-input-base>` absorbido; el filtro "Todos" es el `<ba-dropdown-base>`.
|
|
10
|
+
* El shake de framer-motion se traduce a la clase CSS `.ba-shake` (mismo
|
|
11
|
+
* patrón que el port Vue). Callback props -> outputs:
|
|
12
|
+
* `setOpenWebType` -> `openWebTypeChange`, `onChangeProviderSearch` ->
|
|
13
|
+
* `changeProviderSearch`, `onChangeWebType` -> `changeWebType`.
|
|
14
|
+
*
|
|
15
|
+
* `containerWithActiveClickOutsideRef` (React ref) -> input `container`
|
|
16
|
+
* reenviado a `<ba-dropdown-base>`. i18n vía el store `$locale` del core
|
|
17
|
+
* (mismo patrón que BtnBlockAuth), no vía BlockAuthService, para no exigir
|
|
18
|
+
* un client completo a este componente presentacional.
|
|
19
|
+
*/
|
|
20
|
+
// Extiende DropdownItem (incluye su index signature `[key: string]: unknown`)
|
|
21
|
+
// para poder pasarse a `<ba-dropdown-base [items]/[selectedItem]>` bajo
|
|
22
|
+
// strictTemplates de ng-packagr; `name`/`value` quedan explícitos por claridad.
|
|
23
|
+
export interface WebTypeItem extends DropdownItem {
|
|
24
|
+
name: string;
|
|
25
|
+
value: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Ítems localizados del filtro de web-type ("Web 2"/"Web 3" iguales en ambos idiomas). */
|
|
29
|
+
export const makeWebTypeItems = (t: (key: TextKey) => string): WebTypeItem[] => [
|
|
30
|
+
{ name: t('webTypeAll'), value: 'all' },
|
|
31
|
+
{ name: 'Web 2', value: 'web2' },
|
|
32
|
+
{ name: 'Web 3', value: 'web3' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
@Component({
|
|
36
|
+
selector: 'ba-providers-filters',
|
|
37
|
+
standalone: true,
|
|
38
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
39
|
+
imports: [InputBaseComponent, DropdownBaseComponent],
|
|
40
|
+
template: `
|
|
41
|
+
<div
|
|
42
|
+
data-testid="providers-filters"
|
|
43
|
+
[class]="
|
|
44
|
+
'bas-relative bas-z-50 bas-box-border bas-h-fit bas-w-full bas-items-end bas-justify-end bas-gap-2 bas-px-2 ' +
|
|
45
|
+
(bothEnabled() ? 'bas-grid bas-grid-cols-[1fr_100px]' : 'bas-flex') +
|
|
46
|
+
(isEditable() && isEditing() && !isOpenFiltersEditable ? ' ba-shake' : '')
|
|
47
|
+
"
|
|
48
|
+
>
|
|
49
|
+
@if (uiConfigProvidersInfo().enableSearch) {
|
|
50
|
+
<ba-input-base
|
|
51
|
+
size="small"
|
|
52
|
+
[labelText]="searchLabelText() ?? t('search')"
|
|
53
|
+
inputId="search-provider-label"
|
|
54
|
+
inputTestId="provider-search"
|
|
55
|
+
[enableError]="false"
|
|
56
|
+
[isInputValueManaged]="true"
|
|
57
|
+
[className]="bothEnabled() ? '' : 'bas-max-w-[254px]'"
|
|
58
|
+
[inputValue]="searchProviderValue()"
|
|
59
|
+
(changedInputValue)="changeProviderSearch.emit($event)"
|
|
60
|
+
borderRadiusClassNames="bas-rounded-xl"
|
|
61
|
+
[blueStyles]="true"
|
|
62
|
+
/>
|
|
63
|
+
}
|
|
64
|
+
@if (uiConfigProvidersInfo().enableWebTypeFilter) {
|
|
65
|
+
<div class="bas-w-full bas-max-w-[254px]">
|
|
66
|
+
<ba-dropdown-base
|
|
67
|
+
btnId="btn-web-type-filter"
|
|
68
|
+
[isOpen]="openWebType()"
|
|
69
|
+
(isOpenChange)="openWebTypeChange.emit($event)"
|
|
70
|
+
[showTitle]="false"
|
|
71
|
+
size="small"
|
|
72
|
+
[items]="items()"
|
|
73
|
+
[selectedItem]="selected()"
|
|
74
|
+
keyName="value"
|
|
75
|
+
(changeSelectedItem)="onDropdownChangeSelectedItem($event)"
|
|
76
|
+
width="100%"
|
|
77
|
+
[dropdownHeight]="122"
|
|
78
|
+
borderRadiusClassNames="bas-rounded-xl"
|
|
79
|
+
[container]="container()"
|
|
80
|
+
(clickOutside)="openWebTypeChange.emit(false)"
|
|
81
|
+
[showStatusIndicator]="true"
|
|
82
|
+
[showImg]="false"
|
|
83
|
+
[showCheckWhenActive]="true"
|
|
84
|
+
[manageActiveWithIsOptionSelected]="false"
|
|
85
|
+
[blueStyles]="true"
|
|
86
|
+
[disabled]="isEditable() && isEditing()"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
}
|
|
90
|
+
</div>
|
|
91
|
+
`,
|
|
92
|
+
styles: [
|
|
93
|
+
`
|
|
94
|
+
@keyframes ba-shake {
|
|
95
|
+
0% { transform: rotate(0deg); }
|
|
96
|
+
35% { transform: rotate(-0.4deg); }
|
|
97
|
+
70% { transform: rotate(0.6deg); }
|
|
98
|
+
100% { transform: rotate(0deg); }
|
|
99
|
+
}
|
|
100
|
+
.ba-shake { animation: ba-shake 0.35s infinite; }
|
|
101
|
+
`,
|
|
102
|
+
],
|
|
103
|
+
})
|
|
104
|
+
export class ProvidersFiltersComponent {
|
|
105
|
+
container = input<HTMLElement | null>(null);
|
|
106
|
+
uiConfigProvidersInfo = input<{ enableSearch?: boolean; enableWebTypeFilter?: boolean }>({
|
|
107
|
+
enableSearch: true,
|
|
108
|
+
enableWebTypeFilter: true,
|
|
109
|
+
});
|
|
110
|
+
searchProviderValue = input('');
|
|
111
|
+
openWebType = input(false);
|
|
112
|
+
selectedWebType = input<WebTypeItem | undefined>(undefined);
|
|
113
|
+
webTypesItems = input<WebTypeItem[] | undefined>(undefined);
|
|
114
|
+
isEditable = input(false);
|
|
115
|
+
isEditing = input(false);
|
|
116
|
+
device = input<'desktop' | 'tablet' | 'phone'>('desktop');
|
|
117
|
+
searchLabelText = input<string | undefined>(undefined);
|
|
118
|
+
|
|
119
|
+
openWebTypeChange = output<boolean>();
|
|
120
|
+
changeProviderSearch = output<string>();
|
|
121
|
+
changeWebType = output<WebTypeItem>();
|
|
122
|
+
|
|
123
|
+
private readonly locale = injectStore($locale);
|
|
124
|
+
protected t = (key: TextKey): string => translate(key, this.locale());
|
|
125
|
+
|
|
126
|
+
// `isOpenFiltersEditable` (useState local nunca mutado en el JSX pegado):
|
|
127
|
+
// se conserva por paridad de API del cálculo del shake.
|
|
128
|
+
protected readonly isOpenFiltersEditable = false;
|
|
129
|
+
|
|
130
|
+
protected items = computed<WebTypeItem[]>(() => this.webTypesItems() ?? makeWebTypeItems(this.t));
|
|
131
|
+
protected selected = computed<WebTypeItem>(() => this.selectedWebType() ?? this.items()[0]);
|
|
132
|
+
protected bothEnabled = computed(
|
|
133
|
+
() => !!this.uiConfigProvidersInfo().enableSearch && !!this.uiConfigProvidersInfo().enableWebTypeFilter,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
onDropdownChangeSelectedItem(item: DropdownItem): void {
|
|
137
|
+
this.changeWebType.emit({ name: item.name, value: item.value });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { render, screen } from '@testing-library/angular';
|
|
4
|
+
import { map } from 'nanostores';
|
|
5
|
+
import { SelectedButtonComponent } from './selected-button.component';
|
|
6
|
+
import { provideBlockAuthClientForTest } from '../../../testing';
|
|
7
|
+
|
|
8
|
+
vi.mock('@wagmi/core', () => ({
|
|
9
|
+
createConfig: vi.fn(() => ({ connectors: [] })),
|
|
10
|
+
connect: vi.fn(), disconnect: vi.fn(), getAccount: vi.fn(() => ({})),
|
|
11
|
+
signMessage: vi.fn(), watchAccount: vi.fn(() => () => undefined), http: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
vi.mock('@wagmi/connectors', () => ({
|
|
14
|
+
injected: vi.fn(() => ({ id: 'injected' })), walletConnect: vi.fn(() => ({ id: 'walletConnect' })),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
function makeClient() {
|
|
18
|
+
return {
|
|
19
|
+
web3: { signup: vi.fn() },
|
|
20
|
+
state: { $ui: map({}), $wallet: map({}) },
|
|
21
|
+
} as any;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Port de packages/react/src/components/sections/internals/SelectedButton.test.tsx.
|
|
25
|
+
describe('SelectedButton', () => {
|
|
26
|
+
it('renderiza el boton del servicio seleccionado (metamask)', async () => {
|
|
27
|
+
@Component({
|
|
28
|
+
standalone: true,
|
|
29
|
+
imports: [SelectedButtonComponent],
|
|
30
|
+
template: `<ba-selected-button selectedService="metamask" />`,
|
|
31
|
+
})
|
|
32
|
+
class HostComponent {}
|
|
33
|
+
await render(HostComponent, { providers: [provideBlockAuthClientForTest(makeClient())] });
|
|
34
|
+
expect(screen.getByText('MetaMask')).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
});
|