codexly-ui 0.10.58 → 0.10.59
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/fesm2022/codexly-ui.mjs +130 -200
- package/fesm2022/codexly-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/types/codexly-ui.d.ts +1 -7
package/fesm2022/codexly-ui.mjs
CHANGED
|
@@ -13034,43 +13034,18 @@ class ClxPageEmptyComponent {
|
|
|
13034
13034
|
ctaLabel = input('', ...(ngDevMode ? [{ debugName: "ctaLabel" }] : /* istanbul ignore next */ []));
|
|
13035
13035
|
ctaIcon = input('add', ...(ngDevMode ? [{ debugName: "ctaIcon" }] : /* istanbul ignore next */ []));
|
|
13036
13036
|
ctaColor = input(undefined, ...(ngDevMode ? [{ debugName: "ctaColor" }] : /* istanbul ignore next */ []));
|
|
13037
|
-
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
13038
13037
|
_ctaColor = computed(() => this.ctaColor() ?? this._themeSvc.config().primaryColor, ...(ngDevMode ? [{ debugName: "_ctaColor" }] : /* istanbul ignore next */ []));
|
|
13039
13038
|
cta = output();
|
|
13040
|
-
_iconWrap() {
|
|
13041
|
-
const s = this.size();
|
|
13042
|
-
const sz = s === 'sm' ? 'w-12 h-12' : s === 'lg' ? 'w-20 h-20' : 'w-16 h-16';
|
|
13043
|
-
return `${sz} rounded-2xl bg-clx-surface-2 flex items-center justify-center mb-4`;
|
|
13044
|
-
}
|
|
13045
|
-
_iconSize() {
|
|
13046
|
-
const s = this.size();
|
|
13047
|
-
return s === 'sm' ? 'md' : s === 'lg' ? 'xl' : 'lg';
|
|
13048
|
-
}
|
|
13049
|
-
_titleCls() {
|
|
13050
|
-
const s = this.size();
|
|
13051
|
-
return s === 'sm'
|
|
13052
|
-
? 'text-sm font-semibold text-clx-text-label mb-1'
|
|
13053
|
-
: s === 'lg'
|
|
13054
|
-
? 'text-xl font-semibold text-clx-text-label mb-2'
|
|
13055
|
-
: 'text-base font-semibold text-clx-text-label mb-1';
|
|
13056
|
-
}
|
|
13057
|
-
_descCls() {
|
|
13058
|
-
const s = this.size();
|
|
13059
|
-
return `${s === 'lg' ? 'text-sm' : 'text-xs'} text-clx-text-subtle max-w-xs mb-5`;
|
|
13060
|
-
}
|
|
13061
|
-
_ctaCls() {
|
|
13062
|
-
return this.size() === 'sm' ? 'mt-3' : 'mt-5';
|
|
13063
|
-
}
|
|
13064
13039
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxPageEmptyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13065
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageEmptyComponent, isStandalone: true, selector: "clx-page-empty", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, ctaLabel: { classPropertyName: "ctaLabel", publicName: "ctaLabel", isSignal: true, isRequired: false, transformFunction: null }, ctaIcon: { classPropertyName: "ctaIcon", publicName: "ctaIcon", isSignal: true, isRequired: false, transformFunction: null }, ctaColor: { classPropertyName: "ctaColor", publicName: "ctaColor", isSignal: true, isRequired: false, transformFunction: null }
|
|
13066
|
-
<div
|
|
13067
|
-
<span clx-icon [name]="icon()"
|
|
13040
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageEmptyComponent, isStandalone: true, selector: "clx-page-empty", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, ctaLabel: { classPropertyName: "ctaLabel", publicName: "ctaLabel", isSignal: true, isRequired: false, transformFunction: null }, ctaIcon: { classPropertyName: "ctaIcon", publicName: "ctaIcon", isSignal: true, isRequired: false, transformFunction: null }, ctaColor: { classPropertyName: "ctaColor", publicName: "ctaColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cta: "cta" }, host: { classAttribute: "flex flex-col items-center justify-center w-full px-6 text-center text-sm" }, ngImport: i0, template: `
|
|
13041
|
+
<div class="w-20 h-20 rounded-2xl bg-clx-surface-2 flex items-center justify-center mb-6">
|
|
13042
|
+
<span clx-icon [name]="icon()" size="xl" class="text-clx-text-faint"></span>
|
|
13068
13043
|
</div>
|
|
13069
13044
|
|
|
13070
|
-
<
|
|
13045
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13071
13046
|
|
|
13072
13047
|
@if (description()) {
|
|
13073
|
-
<p
|
|
13048
|
+
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13074
13049
|
}
|
|
13075
13050
|
|
|
13076
13051
|
@if (ctaLabel()) {
|
|
@@ -13080,7 +13055,6 @@ class ClxPageEmptyComponent {
|
|
|
13080
13055
|
[color]="_ctaColor()"
|
|
13081
13056
|
variant="solid"
|
|
13082
13057
|
[icon]="ctaIcon()"
|
|
13083
|
-
[class]="_ctaCls()"
|
|
13084
13058
|
(click)="cta.emit()">
|
|
13085
13059
|
{{ ctaLabel() }}
|
|
13086
13060
|
</button>
|
|
@@ -13095,16 +13069,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
13095
13069
|
imports: [ClxButtonComponent, ClxIconComponent],
|
|
13096
13070
|
encapsulation: ViewEncapsulation.None,
|
|
13097
13071
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
13098
|
-
host: { class: 'flex flex-col items-center justify-center w-full px-6 text-center' },
|
|
13072
|
+
host: { class: 'flex flex-col items-center justify-center w-full px-6 text-center text-sm' },
|
|
13099
13073
|
template: `
|
|
13100
|
-
<div
|
|
13101
|
-
<span clx-icon [name]="icon()"
|
|
13074
|
+
<div class="w-20 h-20 rounded-2xl bg-clx-surface-2 flex items-center justify-center mb-6">
|
|
13075
|
+
<span clx-icon [name]="icon()" size="xl" class="text-clx-text-faint"></span>
|
|
13102
13076
|
</div>
|
|
13103
13077
|
|
|
13104
|
-
<
|
|
13078
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13105
13079
|
|
|
13106
13080
|
@if (description()) {
|
|
13107
|
-
<p
|
|
13081
|
+
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13108
13082
|
}
|
|
13109
13083
|
|
|
13110
13084
|
@if (ctaLabel()) {
|
|
@@ -13114,14 +13088,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
13114
13088
|
[color]="_ctaColor()"
|
|
13115
13089
|
variant="solid"
|
|
13116
13090
|
[icon]="ctaIcon()"
|
|
13117
|
-
[class]="_ctaCls()"
|
|
13118
13091
|
(click)="cta.emit()">
|
|
13119
13092
|
{{ ctaLabel() }}
|
|
13120
13093
|
</button>
|
|
13121
13094
|
}
|
|
13122
13095
|
`,
|
|
13123
13096
|
}]
|
|
13124
|
-
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], ctaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ctaLabel", required: false }] }], ctaIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "ctaIcon", required: false }] }], ctaColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "ctaColor", required: false }] }],
|
|
13097
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], ctaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ctaLabel", required: false }] }], ctaIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "ctaIcon", required: false }] }], ctaColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "ctaColor", required: false }] }], cta: [{ type: i0.Output, args: ["cta"] }] } });
|
|
13125
13098
|
|
|
13126
13099
|
class ClxTableComponent {
|
|
13127
13100
|
_themeSvc = inject(ClxThemeService);
|
|
@@ -13416,7 +13389,7 @@ class ClxTableComponent {
|
|
|
13416
13389
|
</div>
|
|
13417
13390
|
}
|
|
13418
13391
|
}
|
|
13419
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ClxCheckboxComponent, selector: "clx-checkbox", inputs: ["color", "variant", "size", "label", "value", "disabled"] }, { kind: "component", type: ClxInputComponent, selector: "clx-input", inputs: ["label", "placeholder", "type", "color", "hint", "prefixIcon", "suffixIcon", "prefixText", "suffixText", "status", "statusMessage", "size", "value", "disabled", "autocomplete"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxPaginationComponent, selector: "clx-pagination", inputs: ["totalItems", "color", "size", "showPageSize", "pageSizeOptions", "currentPage", "pageSize"], outputs: ["currentPageChange", "pageSizeChange", "pageChange", "sizeChange"] }, { kind: "component", type: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "title", "description", "ctaLabel", "ctaIcon", "ctaColor"
|
|
13392
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ClxCheckboxComponent, selector: "clx-checkbox", inputs: ["color", "variant", "size", "label", "value", "disabled"] }, { kind: "component", type: ClxInputComponent, selector: "clx-input", inputs: ["label", "placeholder", "type", "color", "hint", "prefixIcon", "suffixIcon", "prefixText", "suffixText", "status", "statusMessage", "size", "value", "disabled", "autocomplete"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxPaginationComponent, selector: "clx-pagination", inputs: ["totalItems", "color", "size", "showPageSize", "pageSizeOptions", "currentPage", "pageSize"], outputs: ["currentPageChange", "pageSizeChange", "pageChange", "sizeChange"] }, { kind: "component", type: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "title", "description", "ctaLabel", "ctaIcon", "ctaColor"], outputs: ["cta"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
13420
13393
|
}
|
|
13421
13394
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxTableComponent, decorators: [{
|
|
13422
13395
|
type: Component,
|
|
@@ -13576,13 +13549,13 @@ class ClxPageNotFoundComponent {
|
|
|
13576
13549
|
backLabel = input('Volver al inicio', ...(ngDevMode ? [{ debugName: "backLabel" }] : /* istanbul ignore next */ []));
|
|
13577
13550
|
back = output();
|
|
13578
13551
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxPageNotFoundComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13579
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageNotFoundComponent, isStandalone: true, selector: "clx-page-not-found", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back" }, host: { classAttribute: "flex flex-col items-center justify-center min-h-[60vh] px-6 text-center" }, ngImport: i0, template: `
|
|
13552
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageNotFoundComponent, isStandalone: true, selector: "clx-page-not-found", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back" }, host: { classAttribute: "flex flex-col items-center justify-center min-h-[60vh] px-6 text-center text-sm" }, ngImport: i0, template: `
|
|
13580
13553
|
<div class="w-20 h-20 rounded-2xl bg-indigo-50 flex items-center justify-center mb-6">
|
|
13581
13554
|
<span clx-icon name="search_off" size="xl" class="text-indigo-400"></span>
|
|
13582
13555
|
</div>
|
|
13583
13556
|
|
|
13584
|
-
<p class="text-
|
|
13585
|
-
<h1 class="text-
|
|
13557
|
+
<p class="text-[50px] font-black text-clx-text-faint leading-none select-none mb-2">404</p>
|
|
13558
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13586
13559
|
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13587
13560
|
|
|
13588
13561
|
@if (backLabel()) {
|
|
@@ -13606,14 +13579,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
13606
13579
|
imports: [ClxButtonComponent, ClxIconComponent],
|
|
13607
13580
|
encapsulation: ViewEncapsulation.None,
|
|
13608
13581
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
13609
|
-
host: { class: 'flex flex-col items-center justify-center min-h-[60vh] px-6 text-center' },
|
|
13582
|
+
host: { class: 'flex flex-col items-center justify-center min-h-[60vh] px-6 text-center text-sm' },
|
|
13610
13583
|
template: `
|
|
13611
13584
|
<div class="w-20 h-20 rounded-2xl bg-indigo-50 flex items-center justify-center mb-6">
|
|
13612
13585
|
<span clx-icon name="search_off" size="xl" class="text-indigo-400"></span>
|
|
13613
13586
|
</div>
|
|
13614
13587
|
|
|
13615
|
-
<p class="text-
|
|
13616
|
-
<h1 class="text-
|
|
13588
|
+
<p class="text-[50px] font-black text-clx-text-faint leading-none select-none mb-2">404</p>
|
|
13589
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13617
13590
|
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13618
13591
|
|
|
13619
13592
|
@if (backLabel()) {
|
|
@@ -13640,13 +13613,13 @@ class ClxPageUnauthorizedComponent {
|
|
|
13640
13613
|
login = output();
|
|
13641
13614
|
back = output();
|
|
13642
13615
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxPageUnauthorizedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13643
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageUnauthorizedComponent, isStandalone: true, selector: "clx-page-unauthorized", inputs: { code: { classPropertyName: "code", publicName: "code", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, loginLabel: { classPropertyName: "loginLabel", publicName: "loginLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { login: "login", back: "back" }, host: { classAttribute: "flex flex-col items-center justify-center min-h-[60vh] px-6 text-center" }, ngImport: i0, template: `
|
|
13616
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageUnauthorizedComponent, isStandalone: true, selector: "clx-page-unauthorized", inputs: { code: { classPropertyName: "code", publicName: "code", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, loginLabel: { classPropertyName: "loginLabel", publicName: "loginLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { login: "login", back: "back" }, host: { classAttribute: "flex flex-col items-center justify-center min-h-[60vh] px-6 text-center text-sm" }, ngImport: i0, template: `
|
|
13644
13617
|
<div class="w-20 h-20 rounded-2xl bg-amber-50 flex items-center justify-center mb-6">
|
|
13645
13618
|
<span clx-icon name="lock" size="xl" class="text-amber-400"></span>
|
|
13646
13619
|
</div>
|
|
13647
13620
|
|
|
13648
|
-
<p class="text-
|
|
13649
|
-
<h1 class="text-
|
|
13621
|
+
<p class="text-[50px] font-black text-clx-text-faint leading-none select-none mb-2">{{ code() }}</p>
|
|
13622
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13650
13623
|
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13651
13624
|
|
|
13652
13625
|
<div class="flex items-center gap-3">
|
|
@@ -13683,14 +13656,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
13683
13656
|
imports: [ClxButtonComponent, ClxIconComponent],
|
|
13684
13657
|
encapsulation: ViewEncapsulation.None,
|
|
13685
13658
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
13686
|
-
host: { class: 'flex flex-col items-center justify-center min-h-[60vh] px-6 text-center' },
|
|
13659
|
+
host: { class: 'flex flex-col items-center justify-center min-h-[60vh] px-6 text-center text-sm' },
|
|
13687
13660
|
template: `
|
|
13688
13661
|
<div class="w-20 h-20 rounded-2xl bg-amber-50 flex items-center justify-center mb-6">
|
|
13689
13662
|
<span clx-icon name="lock" size="xl" class="text-amber-400"></span>
|
|
13690
13663
|
</div>
|
|
13691
13664
|
|
|
13692
|
-
<p class="text-
|
|
13693
|
-
<h1 class="text-
|
|
13665
|
+
<p class="text-[50px] font-black text-clx-text-faint leading-none select-none mb-2">{{ code() }}</p>
|
|
13666
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13694
13667
|
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13695
13668
|
|
|
13696
13669
|
<div class="flex items-center gap-3">
|
|
@@ -13730,13 +13703,13 @@ class ClxPageServerErrorComponent {
|
|
|
13730
13703
|
retry = output();
|
|
13731
13704
|
back = output();
|
|
13732
13705
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxPageServerErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13733
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageServerErrorComponent, isStandalone: true, selector: "clx-page-server-error", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, retryLabel: { classPropertyName: "retryLabel", publicName: "retryLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, errorId: { classPropertyName: "errorId", publicName: "errorId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retry: "retry", back: "back" }, host: { classAttribute: "flex flex-col items-center justify-center min-h-[60vh] px-6 text-center" }, ngImport: i0, template: `
|
|
13706
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxPageServerErrorComponent, isStandalone: true, selector: "clx-page-server-error", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, retryLabel: { classPropertyName: "retryLabel", publicName: "retryLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, errorId: { classPropertyName: "errorId", publicName: "errorId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retry: "retry", back: "back" }, host: { classAttribute: "flex flex-col items-center justify-center min-h-[60vh] px-6 text-center text-sm" }, ngImport: i0, template: `
|
|
13734
13707
|
<div class="w-20 h-20 rounded-2xl bg-red-50 flex items-center justify-center mb-6">
|
|
13735
13708
|
<span clx-icon name="error_outline" size="xl" class="text-red-400"></span>
|
|
13736
13709
|
</div>
|
|
13737
13710
|
|
|
13738
|
-
<p class="text-
|
|
13739
|
-
<h1 class="text-
|
|
13711
|
+
<p class="text-[50px] font-black text-clx-text-faint leading-none select-none mb-2">500</p>
|
|
13712
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13740
13713
|
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13741
13714
|
|
|
13742
13715
|
@if (errorId()) {
|
|
@@ -13777,14 +13750,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
13777
13750
|
imports: [ClxButtonComponent, ClxIconComponent],
|
|
13778
13751
|
encapsulation: ViewEncapsulation.None,
|
|
13779
13752
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
13780
|
-
host: { class: 'flex flex-col items-center justify-center min-h-[60vh] px-6 text-center' },
|
|
13753
|
+
host: { class: 'flex flex-col items-center justify-center min-h-[60vh] px-6 text-center text-sm' },
|
|
13781
13754
|
template: `
|
|
13782
13755
|
<div class="w-20 h-20 rounded-2xl bg-red-50 flex items-center justify-center mb-6">
|
|
13783
13756
|
<span clx-icon name="error_outline" size="xl" class="text-red-400"></span>
|
|
13784
13757
|
</div>
|
|
13785
13758
|
|
|
13786
|
-
<p class="text-
|
|
13787
|
-
<h1 class="text-
|
|
13759
|
+
<p class="text-[50px] font-black text-clx-text-faint leading-none select-none mb-2">500</p>
|
|
13760
|
+
<h1 class="text-base font-semibold text-clx-text-label mb-2">{{ title() }}</h1>
|
|
13788
13761
|
<p class="text-clx-text-subtle max-w-sm mb-8">{{ description() }}</p>
|
|
13789
13762
|
|
|
13790
13763
|
@if (errorId()) {
|
|
@@ -14292,10 +14265,10 @@ class ClxProductComponent {
|
|
|
14292
14265
|
|
|
14293
14266
|
<!-- Price -->
|
|
14294
14267
|
<div class="flex items-baseline gap-2 mt-2">
|
|
14295
|
-
<span [class]="_priceClass()">{{ _price() | currency:'COP':'
|
|
14268
|
+
<span [class]="_priceClass()">{{ _price() | currency:'COP':'$':'1.0-0' }}</span>
|
|
14296
14269
|
@if (product()?.has_discount && product()?.discount_price) {
|
|
14297
14270
|
<span class="text-sm text-clx-text-subtle line-through">
|
|
14298
|
-
{{ product()?.discount_price | currency:'COP':'
|
|
14271
|
+
{{ product()?.discount_price | currency:'COP':'$':'1.0-0' }}
|
|
14299
14272
|
</span>
|
|
14300
14273
|
}
|
|
14301
14274
|
</div>
|
|
@@ -14427,10 +14400,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
14427
14400
|
|
|
14428
14401
|
<!-- Price -->
|
|
14429
14402
|
<div class="flex items-baseline gap-2 mt-2">
|
|
14430
|
-
<span [class]="_priceClass()">{{ _price() | currency:'COP':'
|
|
14403
|
+
<span [class]="_priceClass()">{{ _price() | currency:'COP':'$':'1.0-0' }}</span>
|
|
14431
14404
|
@if (product()?.has_discount && product()?.discount_price) {
|
|
14432
14405
|
<span class="text-sm text-clx-text-subtle line-through">
|
|
14433
|
-
{{ product()?.discount_price | currency:'COP':'
|
|
14406
|
+
{{ product()?.discount_price | currency:'COP':'$':'1.0-0' }}
|
|
14434
14407
|
</span>
|
|
14435
14408
|
}
|
|
14436
14409
|
</div>
|
|
@@ -14559,7 +14532,7 @@ class ClxProductDetailComponent {
|
|
|
14559
14532
|
specsLeft(specs) { return specs.slice(0, Math.ceil(specs.length / 2)); }
|
|
14560
14533
|
specsRight(specs) { return specs.slice(Math.ceil(specs.length / 2)); }
|
|
14561
14534
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14562
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxProductDetailComponent, isStandalone: true, selector: "clx-product-detail", inputs: { product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, initialVariationId: { classPropertyName: "initialVariationId", publicName: "initialVariationId", isSignal: true, isRequired: false, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addToCart: "addToCart", buyNow: "buyNow", wishlist: "wishlist", variationChange: "variationChange" }, viewQueries: [{ propertyName: "_carousel", first: true, predicate: ClxCarouselComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- \u2550\u2550 SKELETON \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (loading()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 p-6\">\n <div class=\"space-y-3\">\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"420px\" class=\"rounded-2xl\"></clx-skeleton>\n <div class=\"flex gap-2\">\n @for (t of [1,2,3,4]; track t) {\n <clx-skeleton variant=\"rectangular\" width=\"80px\" height=\"80px\" class=\"rounded-xl\"></clx-skeleton>\n }\n </div>\n </div>\n <div class=\"space-y-4 pt-2\">\n <clx-skeleton variant=\"text\" size=\"lg\" width=\"70%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"sm\" width=\"40%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"xl\" width=\"50%\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"100px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n </div>\n </div>\n}\n\n<!-- \u2550\u2550 CONTENT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (!loading() && product(); as p) {\n @let v = activeVariation();\n <div class=\"space-y-8\">\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 md:items-start\">\n\n <!-- \u2500\u2500 Column 1: Image Gallery \u2014 each column sizes to its own content (no forced equal\n height, no position:sticky). A `sticky` gallery reads great in isolation, but a host app\n that already has its own `position: sticky` header (a full-width top bar, as opposed to\n this library's own `clx-app-layout` sidebar, which is `fixed` and never competes) forces\n the browser to keep recomputing both stickies' viewport-relative offsets together on every\n resize \u2014 confirmed as the source of a persistent resize-lag bug in codexly-store's product\n page. Not worth reintroducing until sticky positioning is made robust to an arbitrary host\n header. \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"relative aspect-4/3\">\n <clx-carousel [clxColor]=\"clxColor()\" aspectRatio=\"4/3\" [loop]=\"true\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <ng-template clxSlide>\n <div class=\"w-full h-full p-5 sm:p-12.5 md:p-5 lg:p-12.5\">\n <img [src]=\"img\" [alt]=\"p.product_name\" class=\"w-full h-full object-contain\"\n [loading]=\"i === 0 ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"i === 0 ? 'high' : null\" />\n </div>\n </ng-template>\n }\n </clx-carousel>\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"clxColor()\" clxTooltipPosition=\"left\"\n (click)=\"wishlist.emit()\">\n <span clx-icon name=\"favorite_border\" size=\"sm\"></span>\n </button>\n </div>\n </div>\n <div class=\"flex flex-wrap gap-2\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <img [src]=\"img\" [alt]=\"p.product_name\"\n class=\"w-20 h-20 rounded-xl object-cover border-[3px] cursor-pointer shrink-0 transition-[border-color,opacity]\"\n [class]=\"_carousel()?.currentPageIndex() === i\n ? 'border-' + clxColor() + '-500 opacity-100'\n : 'border-transparent opacity-60 hover:opacity-90'\"\n loading=\"lazy\"\n (click)=\"_carousel()?.goTo(i)\" />\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Column 2: Purchase Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"space-y-2.5\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge [variant]=\"v.in_stock ? 'light' : 'solid'\" [color]=\"v.in_stock ? clxColor() : 'green'\" size=\"sm\" shape=\"pill\">\n @if (v.in_stock) {\n @if (v.stock_quantity != null) {\n {{ v.stock_quantity }} en stock\n } @else {\n En stock\n }\n } @else {\n Sin stock\n }\n </span>\n }\n\n <!-- Tags (st_products.tags) -->\n <div class=\"flex flex-wrap gap-1\">\n @for (tag of p.tags; track tag) {\n <span clx-badge variant=\"light\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">{{ tag }}</span>\n }\n </div>\n\n <!-- Title -->\n <div>\n <p class=\"text-sm text-clx-text-subtle uppercase tracking-wide font-medium\">{{ p.trademark }}</p>\n <h1 class=\"text-2xl font-bold text-clx-text-label leading-tight mt-0.5\">{{ v?.name || p.product_name }}</h1>\n @if (p.product_detail) {\n <p class=\"text-clx-text-muted text-sm mt-1\">{{ p.product_detail }}</p>\n }\n </div>\n\n <!-- Rating -->\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <span class=\"text-sm text-clx-text-subtle\">({{ p.review_count }} reviews)</span>\n </div>\n\n <!-- Price block -->\n <div>\n <div class=\"flex items-baseline gap-3\">\n <span class=\"text-2xl font-black text-clx-text-label\">\n {{ finalPrice() | currency:currency():'symbol':'1.0-0' }}\n </span>\n @if (p.has_discount && p.discount_price) {\n <span class=\"text-base text-clx-text-subtle line-through\">\n {{ p.discount_price | currency:currency():'symbol':'1.0-0' }}\n </span>\n <span clx-badge variant=\"solid\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">\n -{{ discount() }}%\n </span>\n }\n </div>\n </div>\n\n <!-- Selectores de variaci\u00F3n (st_spec_types + st_spec_options) \u2014 opciones no disponibles\n para la combinaci\u00F3n actual (ej. Talla S con Color Rojo) se ocultan por completo en vez\n de mostrarse deshabilitadas; reaparecen si el usuario cambia a una combinaci\u00F3n donde s\u00ED\n aplican, ya que isOptionAvailable() se reeval\u00FAa contra selectedSpecs() en cada cambio. -->\n @for (specType of p.spec_types; track specType.id) {\n @if (specType.input_type === 'color') {\n <clx-button-group [label]=\"specType.name\" shape=\"circular\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button\n type=\"button\"\n class=\"w-7 h-7 rounded-full border-2 transition-[transform,box-shadow] shrink-0 cursor-pointer\"\n [class]=\"isSpecSelected(specType.id, opt.id) ? 'scale-110 shadow-md' : 'opacity-90 hover:opacity-100'\"\n [style.background-color]=\"opt.value\"\n [style.border-color]=\"isSpecSelected(specType.id, opt.id) ? 'var(--clx-text-label, #111)' : 'transparent'\"\n [clxTooltip]=\"opt.label ?? opt.value\"\n clxTooltipPosition=\"top\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n </button>\n }\n }\n </clx-button-group>\n } @else {\n <clx-button-group [label]=\"specType.name\" shape=\"flat\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button clx-button\n type=\"button\"\n [variant]=\"isSpecSelected(specType.id, opt.id) ? 'solid' : 'outline'\"\n [color]=\"clxColor()\" size=\"sm\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n {{ opt.label ?? opt.value }}\n </button>\n }\n }\n </clx-button-group>\n }\n }\n\n <!-- Quantity -->\n <clx-number label=\"Quantity:\" variant=\"stepper\" [color]=\"clxColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitAddToCart()\">Agrega al carrito</button>\n </div>\n\n </div>\n </div>\n\n @if (!compact()) {\n <!-- \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <clx-tabs [tabs]=\"tabItems\" [clxColor]=\"clxColor()\" [(activeTab)]=\"activeTab\">\n\n <!-- Tab: Caracter\u00EDsticas generales (st_product_specs \u2014 ficha t\u00E9cnica global) -->\n <ng-template [clxTabPanel]=\"'general'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsLeft(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsRight(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Especificaciones (st_spec_types \u2014 specs de la variaci\u00F3n activa) -->\n <ng-template [clxTabPanel]=\"'specs'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsLeft(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsRight(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Descripci\u00F3n (st_product_descriptions) -->\n <ng-template [clxTabPanel]=\"'description'\">\n <div class=\"space-y-5\">\n @for (block of p.descriptions; track block.id) {\n <div>\n @if (block.title) {\n <h3 class=\"text-base font-semibold text-clx-text-label mb-1\">{{ block.title }}</h3>\n }\n <p class=\"text-sm text-clx-text-muted leading-relaxed\">{{ block.body }}</p>\n </div>\n }\n </div>\n </ng-template>\n\n </clx-tabs>\n\n <!-- \u2500\u2500 Reviews \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"grid grid-cols-1 xl:grid-cols-3 gap-8 border-t border-clx-border pt-8\">\n\n <div class=\"space-y-3\">\n <h2 class=\"text-base font-bold text-clx-text-label\">Customer Reviews</h2>\n <div class=\"flex items-end gap-3\">\n <span class=\"text-5xl font-black text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <div class=\"pb-1 space-y-1\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <p class=\"text-xs text-clx-text-muted\">{{ p.review_count }} verified ratings</p>\n </div>\n </div>\n <p class=\"text-sm font-semibold\" [class]=\"'text-' + clxColor() + '-600'\">Very Good</p>\n <div class=\"pt-2 space-y-2\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\">\n Leave Us a Review\n </button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"flag\">\n Report abuse\n </button>\n </div>\n </div>\n\n <div class=\"lg:col-span-2 space-y-4\">\n @for (review of p.reviews ?? []; track review.id) {\n <div class=\"rounded-2xl overflow-hidden shadow-sm\">\n <div class=\"flex items-center gap-3 px-4 py-3\">\n <div class=\"w-10 h-10 rounded-full bg-clx-surface-2 flex items-center justify-center shrink-0\">\n <span class=\"text-sm font-bold text-clx-text-muted\">\n {{ review.author_name.substring(0,1) }}{{ review.author_name.split(' ')[1]?.substring(0,1) ?? '' }}\n </span>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"font-semibold text-sm text-clx-text-label truncate\">{{ review.author_name }}</p>\n <p class=\"text-xs text-clx-text-subtle\">{{ review.created_at }}</p>\n </div>\n @if (review.is_verified) {\n <span clx-badge variant=\"light\" color=\"emerald\" size=\"sm\" shape=\"pill\">Verified</span>\n }\n </div>\n <div class=\"px-4 pb-4 space-y-2\">\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"review.rating\" [color]=\"clxColor()\"></clx-rating>\n @if (review.title) {\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ review.title }}</span>\n }\n </div>\n @if (review.body) {\n <p class=\"text-sm text-clx-text-muted\">{{ review.body }}</p>\n }\n <div class=\"flex items-center gap-3 pt-1\">\n <span class=\"text-xs text-clx-text-subtle\">Helpful?</span>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_up\">{{ review.helpful }}</button>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_down\">{{ review.not_helpful }}</button>\n </div>\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxButtonGroupComponent, selector: "clx-button-group", inputs: ["label", "shape", "attached", "orientation"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxRatingComponent, selector: "clx-rating", inputs: ["value", "max", "color"] }, { kind: "component", type: ClxCarouselComponent, selector: "clx-carousel", inputs: ["clxColor", "autoPlay", "interval", "loop", "aspectRatio", "transparent"] }, { kind: "directive", type: ClxCarouselDirective, selector: "[clxSlide]" }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTab"], outputs: ["activeTabChange"] }, { kind: "directive", type: ClxTabDirective, selector: "[clxTabPanel]", inputs: ["clxTabPanel"] }, { kind: "component", type: ClxNumberComponent, selector: "clx-number", inputs: ["variant", "size", "color", "label", "placeholder", "hint", "prefixIcon", "min", "max", "step", "value", "disabled"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }, { kind: "component", type: ClxListComponent, selector: "clx-list", inputs: ["color", "variant", "size", "numbered"] }, { kind: "component", type: ClxListItemComponent, selector: "clx-list-item", inputs: ["label", "sublabel", "icon", "meta", "metaColor", "disabled"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14535
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxProductDetailComponent, isStandalone: true, selector: "clx-product-detail", inputs: { product: { classPropertyName: "product", publicName: "product", isSignal: true, isRequired: false, transformFunction: null }, initialVariationId: { classPropertyName: "initialVariationId", publicName: "initialVariationId", isSignal: true, isRequired: false, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addToCart: "addToCart", buyNow: "buyNow", wishlist: "wishlist", variationChange: "variationChange" }, viewQueries: [{ propertyName: "_carousel", first: true, predicate: ClxCarouselComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- \u2550\u2550 SKELETON \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (loading()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 p-6\">\n <div class=\"space-y-3\">\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"420px\" class=\"rounded-2xl\"></clx-skeleton>\n <div class=\"flex gap-2\">\n @for (t of [1,2,3,4]; track t) {\n <clx-skeleton variant=\"rectangular\" width=\"80px\" height=\"80px\" class=\"rounded-xl\"></clx-skeleton>\n }\n </div>\n </div>\n <div class=\"space-y-4 pt-2\">\n <clx-skeleton variant=\"text\" size=\"lg\" width=\"70%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"sm\" width=\"40%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"xl\" width=\"50%\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"100px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n </div>\n </div>\n}\n\n<!-- \u2550\u2550 CONTENT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (!loading() && product(); as p) {\n @let v = activeVariation();\n <div class=\"space-y-8\">\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 md:items-start\">\n\n <!-- \u2500\u2500 Column 1: Image Gallery \u2014 each column sizes to its own content (no forced equal\n height, no position:sticky). A `sticky` gallery reads great in isolation, but a host app\n that already has its own `position: sticky` header (a full-width top bar, as opposed to\n this library's own `clx-app-layout` sidebar, which is `fixed` and never competes) forces\n the browser to keep recomputing both stickies' viewport-relative offsets together on every\n resize \u2014 confirmed as the source of a persistent resize-lag bug in codexly-store's product\n page. Not worth reintroducing until sticky positioning is made robust to an arbitrary host\n header. \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"relative aspect-4/3\">\n <clx-carousel [clxColor]=\"clxColor()\" aspectRatio=\"4/3\" [loop]=\"true\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <ng-template clxSlide>\n <div class=\"w-full h-full p-5 sm:p-12.5 md:p-5 lg:p-12.5\">\n <img [src]=\"img\" [alt]=\"p.product_name\" class=\"w-full h-full object-contain\"\n [loading]=\"i === 0 ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"i === 0 ? 'high' : null\" />\n </div>\n </ng-template>\n }\n </clx-carousel>\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"clxColor()\" clxTooltipPosition=\"left\"\n (click)=\"wishlist.emit()\">\n <span clx-icon name=\"favorite_border\" size=\"sm\"></span>\n </button>\n </div>\n </div>\n <div class=\"flex flex-wrap gap-2\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <img [src]=\"img\" [alt]=\"p.product_name\"\n class=\"w-20 h-20 rounded-xl object-cover border-[3px] cursor-pointer shrink-0 transition-[border-color,opacity]\"\n [class]=\"_carousel()?.currentPageIndex() === i\n ? 'border-' + clxColor() + '-500 opacity-100'\n : 'border-transparent opacity-60 hover:opacity-90'\"\n loading=\"lazy\"\n (click)=\"_carousel()?.goTo(i)\" />\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Column 2: Purchase Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"space-y-2.5\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge [variant]=\"v.in_stock ? 'light' : 'solid'\" [color]=\"v.in_stock ? clxColor() : 'green'\" size=\"sm\" shape=\"pill\">\n @if (v.in_stock) {\n @if (v.stock_quantity != null) {\n {{ v.stock_quantity }} en stock\n } @else {\n En stock\n }\n } @else {\n Sin stock\n }\n </span>\n }\n\n <!-- Tags (st_products.tags) -->\n <div class=\"flex flex-wrap gap-1\">\n @for (tag of p.tags; track tag) {\n <span clx-badge variant=\"light\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">{{ tag }}</span>\n }\n </div>\n\n <!-- Title -->\n <div>\n <p class=\"text-sm text-clx-text-subtle uppercase tracking-wide font-medium\">{{ p.trademark }}</p>\n <h1 class=\"text-2xl font-bold text-clx-text-label leading-tight mt-0.5\">{{ v?.name || p.product_name }}</h1>\n @if (p.product_detail) {\n <p class=\"text-clx-text-muted text-sm mt-1\">{{ p.product_detail }}</p>\n }\n </div>\n\n <!-- Rating -->\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <span class=\"text-sm text-clx-text-subtle\">({{ p.review_count }} reviews)</span>\n </div>\n\n <!-- Price block -->\n <div>\n <div class=\"flex items-baseline gap-3\">\n <span class=\"text-2xl font-black text-clx-text-label\">\n {{ finalPrice() | currency:currency():'$':'1.0-0' }}\n </span>\n @if (p.has_discount && p.discount_price) {\n <span class=\"text-base text-clx-text-subtle line-through\">\n {{ p.discount_price | currency:currency():'$':'1.0-0' }}\n </span>\n <span clx-badge variant=\"solid\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">\n -{{ discount() }}%\n </span>\n }\n </div>\n </div>\n\n <!-- Selectores de variaci\u00F3n (st_spec_types + st_spec_options) \u2014 opciones no disponibles\n para la combinaci\u00F3n actual (ej. Talla S con Color Rojo) se ocultan por completo en vez\n de mostrarse deshabilitadas; reaparecen si el usuario cambia a una combinaci\u00F3n donde s\u00ED\n aplican, ya que isOptionAvailable() se reeval\u00FAa contra selectedSpecs() en cada cambio. -->\n @for (specType of p.spec_types; track specType.id) {\n @if (specType.input_type === 'color') {\n <clx-button-group [label]=\"specType.name\" shape=\"circular\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button\n type=\"button\"\n class=\"w-7 h-7 rounded-full border-2 transition-[transform,box-shadow] shrink-0 cursor-pointer\"\n [class]=\"isSpecSelected(specType.id, opt.id) ? 'scale-110 shadow-md' : 'opacity-90 hover:opacity-100'\"\n [style.background-color]=\"opt.value\"\n [style.border-color]=\"isSpecSelected(specType.id, opt.id) ? 'var(--clx-text-label, #111)' : 'transparent'\"\n [clxTooltip]=\"opt.label ?? opt.value\"\n clxTooltipPosition=\"top\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n </button>\n }\n }\n </clx-button-group>\n } @else {\n <clx-button-group [label]=\"specType.name\" shape=\"flat\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button clx-button\n type=\"button\"\n [variant]=\"isSpecSelected(specType.id, opt.id) ? 'solid' : 'outline'\"\n [color]=\"clxColor()\" size=\"sm\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n {{ opt.label ?? opt.value }}\n </button>\n }\n }\n </clx-button-group>\n }\n }\n\n <!-- Quantity -->\n <clx-number label=\"Quantity:\" variant=\"stepper\" [color]=\"clxColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitAddToCart()\">Agrega al carrito</button>\n </div>\n\n </div>\n </div>\n\n @if (!compact()) {\n <!-- \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <clx-tabs [tabs]=\"tabItems\" [clxColor]=\"clxColor()\" [(activeTab)]=\"activeTab\">\n\n <!-- Tab: Caracter\u00EDsticas generales (st_product_specs \u2014 ficha t\u00E9cnica global) -->\n <ng-template [clxTabPanel]=\"'general'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsLeft(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsRight(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Especificaciones (st_spec_types \u2014 specs de la variaci\u00F3n activa) -->\n <ng-template [clxTabPanel]=\"'specs'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsLeft(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsRight(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Descripci\u00F3n (st_product_descriptions) -->\n <ng-template [clxTabPanel]=\"'description'\">\n <div class=\"space-y-5\">\n @for (block of p.descriptions; track block.id) {\n <div>\n @if (block.title) {\n <h3 class=\"text-base font-semibold text-clx-text-label mb-1\">{{ block.title }}</h3>\n }\n <p class=\"text-sm text-clx-text-muted leading-relaxed\">{{ block.body }}</p>\n </div>\n }\n </div>\n </ng-template>\n\n </clx-tabs>\n\n <!-- \u2500\u2500 Reviews \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"grid grid-cols-1 xl:grid-cols-3 gap-8 border-t border-clx-border pt-8\">\n\n <div class=\"space-y-3\">\n <h2 class=\"text-base font-bold text-clx-text-label\">Customer Reviews</h2>\n <div class=\"flex items-end gap-3\">\n <span class=\"text-5xl font-black text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <div class=\"pb-1 space-y-1\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <p class=\"text-xs text-clx-text-muted\">{{ p.review_count }} verified ratings</p>\n </div>\n </div>\n <p class=\"text-sm font-semibold\" [class]=\"'text-' + clxColor() + '-600'\">Very Good</p>\n <div class=\"pt-2 space-y-2\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\">\n Leave Us a Review\n </button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"flag\">\n Report abuse\n </button>\n </div>\n </div>\n\n <div class=\"lg:col-span-2 space-y-4\">\n @for (review of p.reviews ?? []; track review.id) {\n <div class=\"rounded-2xl overflow-hidden shadow-sm\">\n <div class=\"flex items-center gap-3 px-4 py-3\">\n <div class=\"w-10 h-10 rounded-full bg-clx-surface-2 flex items-center justify-center shrink-0\">\n <span class=\"text-sm font-bold text-clx-text-muted\">\n {{ review.author_name.substring(0,1) }}{{ review.author_name.split(' ')[1]?.substring(0,1) ?? '' }}\n </span>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"font-semibold text-sm text-clx-text-label truncate\">{{ review.author_name }}</p>\n <p class=\"text-xs text-clx-text-subtle\">{{ review.created_at }}</p>\n </div>\n @if (review.is_verified) {\n <span clx-badge variant=\"light\" color=\"emerald\" size=\"sm\" shape=\"pill\">Verified</span>\n }\n </div>\n <div class=\"px-4 pb-4 space-y-2\">\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"review.rating\" [color]=\"clxColor()\"></clx-rating>\n @if (review.title) {\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ review.title }}</span>\n }\n </div>\n @if (review.body) {\n <p class=\"text-sm text-clx-text-muted\">{{ review.body }}</p>\n }\n <div class=\"flex items-center gap-3 pt-1\">\n <span class=\"text-xs text-clx-text-subtle\">Helpful?</span>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_up\">{{ review.helpful }}</button>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_down\">{{ review.not_helpful }}</button>\n </div>\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxButtonGroupComponent, selector: "clx-button-group", inputs: ["label", "shape", "attached", "orientation"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxRatingComponent, selector: "clx-rating", inputs: ["value", "max", "color"] }, { kind: "component", type: ClxCarouselComponent, selector: "clx-carousel", inputs: ["clxColor", "autoPlay", "interval", "loop", "aspectRatio", "transparent"] }, { kind: "directive", type: ClxCarouselDirective, selector: "[clxSlide]" }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTab"], outputs: ["activeTabChange"] }, { kind: "directive", type: ClxTabDirective, selector: "[clxTabPanel]", inputs: ["clxTabPanel"] }, { kind: "component", type: ClxNumberComponent, selector: "clx-number", inputs: ["variant", "size", "color", "label", "placeholder", "hint", "prefixIcon", "min", "max", "step", "value", "disabled"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }, { kind: "component", type: ClxListComponent, selector: "clx-list", inputs: ["color", "variant", "size", "numbered"] }, { kind: "component", type: ClxListItemComponent, selector: "clx-list-item", inputs: ["label", "sublabel", "icon", "meta", "metaColor", "disabled"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14563
14536
|
}
|
|
14564
14537
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductDetailComponent, decorators: [{
|
|
14565
14538
|
type: Component,
|
|
@@ -14581,7 +14554,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
14581
14554
|
ClxTooltipDirective,
|
|
14582
14555
|
ClxListComponent,
|
|
14583
14556
|
ClxListItemComponent,
|
|
14584
|
-
], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- \u2550\u2550 SKELETON \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (loading()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 p-6\">\n <div class=\"space-y-3\">\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"420px\" class=\"rounded-2xl\"></clx-skeleton>\n <div class=\"flex gap-2\">\n @for (t of [1,2,3,4]; track t) {\n <clx-skeleton variant=\"rectangular\" width=\"80px\" height=\"80px\" class=\"rounded-xl\"></clx-skeleton>\n }\n </div>\n </div>\n <div class=\"space-y-4 pt-2\">\n <clx-skeleton variant=\"text\" size=\"lg\" width=\"70%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"sm\" width=\"40%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"xl\" width=\"50%\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"100px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n </div>\n </div>\n}\n\n<!-- \u2550\u2550 CONTENT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (!loading() && product(); as p) {\n @let v = activeVariation();\n <div class=\"space-y-8\">\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 md:items-start\">\n\n <!-- \u2500\u2500 Column 1: Image Gallery \u2014 each column sizes to its own content (no forced equal\n height, no position:sticky). A `sticky` gallery reads great in isolation, but a host app\n that already has its own `position: sticky` header (a full-width top bar, as opposed to\n this library's own `clx-app-layout` sidebar, which is `fixed` and never competes) forces\n the browser to keep recomputing both stickies' viewport-relative offsets together on every\n resize \u2014 confirmed as the source of a persistent resize-lag bug in codexly-store's product\n page. Not worth reintroducing until sticky positioning is made robust to an arbitrary host\n header. \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"relative aspect-4/3\">\n <clx-carousel [clxColor]=\"clxColor()\" aspectRatio=\"4/3\" [loop]=\"true\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <ng-template clxSlide>\n <div class=\"w-full h-full p-5 sm:p-12.5 md:p-5 lg:p-12.5\">\n <img [src]=\"img\" [alt]=\"p.product_name\" class=\"w-full h-full object-contain\"\n [loading]=\"i === 0 ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"i === 0 ? 'high' : null\" />\n </div>\n </ng-template>\n }\n </clx-carousel>\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"clxColor()\" clxTooltipPosition=\"left\"\n (click)=\"wishlist.emit()\">\n <span clx-icon name=\"favorite_border\" size=\"sm\"></span>\n </button>\n </div>\n </div>\n <div class=\"flex flex-wrap gap-2\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <img [src]=\"img\" [alt]=\"p.product_name\"\n class=\"w-20 h-20 rounded-xl object-cover border-[3px] cursor-pointer shrink-0 transition-[border-color,opacity]\"\n [class]=\"_carousel()?.currentPageIndex() === i\n ? 'border-' + clxColor() + '-500 opacity-100'\n : 'border-transparent opacity-60 hover:opacity-90'\"\n loading=\"lazy\"\n (click)=\"_carousel()?.goTo(i)\" />\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Column 2: Purchase Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"space-y-2.5\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge [variant]=\"v.in_stock ? 'light' : 'solid'\" [color]=\"v.in_stock ? clxColor() : 'green'\" size=\"sm\" shape=\"pill\">\n @if (v.in_stock) {\n @if (v.stock_quantity != null) {\n {{ v.stock_quantity }} en stock\n } @else {\n En stock\n }\n } @else {\n Sin stock\n }\n </span>\n }\n\n <!-- Tags (st_products.tags) -->\n <div class=\"flex flex-wrap gap-1\">\n @for (tag of p.tags; track tag) {\n <span clx-badge variant=\"light\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">{{ tag }}</span>\n }\n </div>\n\n <!-- Title -->\n <div>\n <p class=\"text-sm text-clx-text-subtle uppercase tracking-wide font-medium\">{{ p.trademark }}</p>\n <h1 class=\"text-2xl font-bold text-clx-text-label leading-tight mt-0.5\">{{ v?.name || p.product_name }}</h1>\n @if (p.product_detail) {\n <p class=\"text-clx-text-muted text-sm mt-1\">{{ p.product_detail }}</p>\n }\n </div>\n\n <!-- Rating -->\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <span class=\"text-sm text-clx-text-subtle\">({{ p.review_count }} reviews)</span>\n </div>\n\n <!-- Price block -->\n <div>\n <div class=\"flex items-baseline gap-3\">\n <span class=\"text-2xl font-black text-clx-text-label\">\n {{ finalPrice() | currency:currency():'symbol':'1.0-0' }}\n </span>\n @if (p.has_discount && p.discount_price) {\n <span class=\"text-base text-clx-text-subtle line-through\">\n {{ p.discount_price | currency:currency():'symbol':'1.0-0' }}\n </span>\n <span clx-badge variant=\"solid\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">\n -{{ discount() }}%\n </span>\n }\n </div>\n </div>\n\n <!-- Selectores de variaci\u00F3n (st_spec_types + st_spec_options) \u2014 opciones no disponibles\n para la combinaci\u00F3n actual (ej. Talla S con Color Rojo) se ocultan por completo en vez\n de mostrarse deshabilitadas; reaparecen si el usuario cambia a una combinaci\u00F3n donde s\u00ED\n aplican, ya que isOptionAvailable() se reeval\u00FAa contra selectedSpecs() en cada cambio. -->\n @for (specType of p.spec_types; track specType.id) {\n @if (specType.input_type === 'color') {\n <clx-button-group [label]=\"specType.name\" shape=\"circular\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button\n type=\"button\"\n class=\"w-7 h-7 rounded-full border-2 transition-[transform,box-shadow] shrink-0 cursor-pointer\"\n [class]=\"isSpecSelected(specType.id, opt.id) ? 'scale-110 shadow-md' : 'opacity-90 hover:opacity-100'\"\n [style.background-color]=\"opt.value\"\n [style.border-color]=\"isSpecSelected(specType.id, opt.id) ? 'var(--clx-text-label, #111)' : 'transparent'\"\n [clxTooltip]=\"opt.label ?? opt.value\"\n clxTooltipPosition=\"top\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n </button>\n }\n }\n </clx-button-group>\n } @else {\n <clx-button-group [label]=\"specType.name\" shape=\"flat\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button clx-button\n type=\"button\"\n [variant]=\"isSpecSelected(specType.id, opt.id) ? 'solid' : 'outline'\"\n [color]=\"clxColor()\" size=\"sm\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n {{ opt.label ?? opt.value }}\n </button>\n }\n }\n </clx-button-group>\n }\n }\n\n <!-- Quantity -->\n <clx-number label=\"Quantity:\" variant=\"stepper\" [color]=\"clxColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitAddToCart()\">Agrega al carrito</button>\n </div>\n\n </div>\n </div>\n\n @if (!compact()) {\n <!-- \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <clx-tabs [tabs]=\"tabItems\" [clxColor]=\"clxColor()\" [(activeTab)]=\"activeTab\">\n\n <!-- Tab: Caracter\u00EDsticas generales (st_product_specs \u2014 ficha t\u00E9cnica global) -->\n <ng-template [clxTabPanel]=\"'general'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsLeft(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsRight(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Especificaciones (st_spec_types \u2014 specs de la variaci\u00F3n activa) -->\n <ng-template [clxTabPanel]=\"'specs'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsLeft(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsRight(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Descripci\u00F3n (st_product_descriptions) -->\n <ng-template [clxTabPanel]=\"'description'\">\n <div class=\"space-y-5\">\n @for (block of p.descriptions; track block.id) {\n <div>\n @if (block.title) {\n <h3 class=\"text-base font-semibold text-clx-text-label mb-1\">{{ block.title }}</h3>\n }\n <p class=\"text-sm text-clx-text-muted leading-relaxed\">{{ block.body }}</p>\n </div>\n }\n </div>\n </ng-template>\n\n </clx-tabs>\n\n <!-- \u2500\u2500 Reviews \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"grid grid-cols-1 xl:grid-cols-3 gap-8 border-t border-clx-border pt-8\">\n\n <div class=\"space-y-3\">\n <h2 class=\"text-base font-bold text-clx-text-label\">Customer Reviews</h2>\n <div class=\"flex items-end gap-3\">\n <span class=\"text-5xl font-black text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <div class=\"pb-1 space-y-1\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <p class=\"text-xs text-clx-text-muted\">{{ p.review_count }} verified ratings</p>\n </div>\n </div>\n <p class=\"text-sm font-semibold\" [class]=\"'text-' + clxColor() + '-600'\">Very Good</p>\n <div class=\"pt-2 space-y-2\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\">\n Leave Us a Review\n </button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"flag\">\n Report abuse\n </button>\n </div>\n </div>\n\n <div class=\"lg:col-span-2 space-y-4\">\n @for (review of p.reviews ?? []; track review.id) {\n <div class=\"rounded-2xl overflow-hidden shadow-sm\">\n <div class=\"flex items-center gap-3 px-4 py-3\">\n <div class=\"w-10 h-10 rounded-full bg-clx-surface-2 flex items-center justify-center shrink-0\">\n <span class=\"text-sm font-bold text-clx-text-muted\">\n {{ review.author_name.substring(0,1) }}{{ review.author_name.split(' ')[1]?.substring(0,1) ?? '' }}\n </span>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"font-semibold text-sm text-clx-text-label truncate\">{{ review.author_name }}</p>\n <p class=\"text-xs text-clx-text-subtle\">{{ review.created_at }}</p>\n </div>\n @if (review.is_verified) {\n <span clx-badge variant=\"light\" color=\"emerald\" size=\"sm\" shape=\"pill\">Verified</span>\n }\n </div>\n <div class=\"px-4 pb-4 space-y-2\">\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"review.rating\" [color]=\"clxColor()\"></clx-rating>\n @if (review.title) {\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ review.title }}</span>\n }\n </div>\n @if (review.body) {\n <p class=\"text-sm text-clx-text-muted\">{{ review.body }}</p>\n }\n <div class=\"flex items-center gap-3 pt-1\">\n <span class=\"text-xs text-clx-text-subtle\">Helpful?</span>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_up\">{{ review.helpful }}</button>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_down\">{{ review.not_helpful }}</button>\n </div>\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n\n </div>\n}\n" }]
|
|
14557
|
+
], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- \u2550\u2550 SKELETON \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (loading()) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 p-6\">\n <div class=\"space-y-3\">\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"420px\" class=\"rounded-2xl\"></clx-skeleton>\n <div class=\"flex gap-2\">\n @for (t of [1,2,3,4]; track t) {\n <clx-skeleton variant=\"rectangular\" width=\"80px\" height=\"80px\" class=\"rounded-xl\"></clx-skeleton>\n }\n </div>\n </div>\n <div class=\"space-y-4 pt-2\">\n <clx-skeleton variant=\"text\" size=\"lg\" width=\"70%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"sm\" width=\"40%\"></clx-skeleton>\n <clx-skeleton variant=\"text\" size=\"xl\" width=\"50%\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"100px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n <clx-skeleton variant=\"rectangular\" width=\"100%\" height=\"52px\" class=\"rounded-xl\"></clx-skeleton>\n </div>\n </div>\n}\n\n<!-- \u2550\u2550 CONTENT \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (!loading() && product(); as p) {\n @let v = activeVariation();\n <div class=\"space-y-8\">\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-8 md:items-start\">\n\n <!-- \u2500\u2500 Column 1: Image Gallery \u2014 each column sizes to its own content (no forced equal\n height, no position:sticky). A `sticky` gallery reads great in isolation, but a host app\n that already has its own `position: sticky` header (a full-width top bar, as opposed to\n this library's own `clx-app-layout` sidebar, which is `fixed` and never competes) forces\n the browser to keep recomputing both stickies' viewport-relative offsets together on every\n resize \u2014 confirmed as the source of a persistent resize-lag bug in codexly-store's product\n page. Not worth reintroducing until sticky positioning is made robust to an arbitrary host\n header. \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"relative aspect-4/3\">\n <clx-carousel [clxColor]=\"clxColor()\" aspectRatio=\"4/3\" [loop]=\"true\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <ng-template clxSlide>\n <div class=\"w-full h-full p-5 sm:p-12.5 md:p-5 lg:p-12.5\">\n <img [src]=\"img\" [alt]=\"p.product_name\" class=\"w-full h-full object-contain\"\n [loading]=\"i === 0 ? 'eager' : 'lazy'\"\n [attr.fetchpriority]=\"i === 0 ? 'high' : null\" />\n </div>\n </ng-template>\n }\n </clx-carousel>\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"clxColor()\" clxTooltipPosition=\"left\"\n (click)=\"wishlist.emit()\">\n <span clx-icon name=\"favorite_border\" size=\"sm\"></span>\n </button>\n </div>\n </div>\n <div class=\"flex flex-wrap gap-2\">\n @for (img of v?.images ?? []; track i; let i = $index) {\n <img [src]=\"img\" [alt]=\"p.product_name\"\n class=\"w-20 h-20 rounded-xl object-cover border-[3px] cursor-pointer shrink-0 transition-[border-color,opacity]\"\n [class]=\"_carousel()?.currentPageIndex() === i\n ? 'border-' + clxColor() + '-500 opacity-100'\n : 'border-transparent opacity-60 hover:opacity-90'\"\n loading=\"lazy\"\n (click)=\"_carousel()?.goTo(i)\" />\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Column 2: Purchase Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"space-y-2.5\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge [variant]=\"v.in_stock ? 'light' : 'solid'\" [color]=\"v.in_stock ? clxColor() : 'green'\" size=\"sm\" shape=\"pill\">\n @if (v.in_stock) {\n @if (v.stock_quantity != null) {\n {{ v.stock_quantity }} en stock\n } @else {\n En stock\n }\n } @else {\n Sin stock\n }\n </span>\n }\n\n <!-- Tags (st_products.tags) -->\n <div class=\"flex flex-wrap gap-1\">\n @for (tag of p.tags; track tag) {\n <span clx-badge variant=\"light\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">{{ tag }}</span>\n }\n </div>\n\n <!-- Title -->\n <div>\n <p class=\"text-sm text-clx-text-subtle uppercase tracking-wide font-medium\">{{ p.trademark }}</p>\n <h1 class=\"text-2xl font-bold text-clx-text-label leading-tight mt-0.5\">{{ v?.name || p.product_name }}</h1>\n @if (p.product_detail) {\n <p class=\"text-clx-text-muted text-sm mt-1\">{{ p.product_detail }}</p>\n }\n </div>\n\n <!-- Rating -->\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <span class=\"text-sm text-clx-text-subtle\">({{ p.review_count }} reviews)</span>\n </div>\n\n <!-- Price block -->\n <div>\n <div class=\"flex items-baseline gap-3\">\n <span class=\"text-2xl font-black text-clx-text-label\">\n {{ finalPrice() | currency:currency():'$':'1.0-0' }}\n </span>\n @if (p.has_discount && p.discount_price) {\n <span class=\"text-base text-clx-text-subtle line-through\">\n {{ p.discount_price | currency:currency():'$':'1.0-0' }}\n </span>\n <span clx-badge variant=\"solid\" [color]=\"clxColor()\" size=\"sm\" shape=\"pill\">\n -{{ discount() }}%\n </span>\n }\n </div>\n </div>\n\n <!-- Selectores de variaci\u00F3n (st_spec_types + st_spec_options) \u2014 opciones no disponibles\n para la combinaci\u00F3n actual (ej. Talla S con Color Rojo) se ocultan por completo en vez\n de mostrarse deshabilitadas; reaparecen si el usuario cambia a una combinaci\u00F3n donde s\u00ED\n aplican, ya que isOptionAvailable() se reeval\u00FAa contra selectedSpecs() en cada cambio. -->\n @for (specType of p.spec_types; track specType.id) {\n @if (specType.input_type === 'color') {\n <clx-button-group [label]=\"specType.name\" shape=\"circular\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button\n type=\"button\"\n class=\"w-7 h-7 rounded-full border-2 transition-[transform,box-shadow] shrink-0 cursor-pointer\"\n [class]=\"isSpecSelected(specType.id, opt.id) ? 'scale-110 shadow-md' : 'opacity-90 hover:opacity-100'\"\n [style.background-color]=\"opt.value\"\n [style.border-color]=\"isSpecSelected(specType.id, opt.id) ? 'var(--clx-text-label, #111)' : 'transparent'\"\n [clxTooltip]=\"opt.label ?? opt.value\"\n clxTooltipPosition=\"top\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n </button>\n }\n }\n </clx-button-group>\n } @else {\n <clx-button-group [label]=\"specType.name\" shape=\"flat\" [attached]=\"false\">\n @for (opt of specType.options; track opt.id) {\n @if (isOptionAvailable(specType.id, opt.id)) {\n <button clx-button\n type=\"button\"\n [variant]=\"isSpecSelected(specType.id, opt.id) ? 'solid' : 'outline'\"\n [color]=\"clxColor()\" size=\"sm\"\n (click)=\"selectSpec(specType.id, opt.id)\">\n {{ opt.label ?? opt.value }}\n </button>\n }\n }\n </clx-button-group>\n }\n }\n\n <!-- Quantity -->\n <clx-number label=\"Quantity:\" variant=\"stepper\" [color]=\"clxColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock\" (click)=\"_emitAddToCart()\">Agrega al carrito</button>\n </div>\n\n </div>\n </div>\n\n @if (!compact()) {\n <!-- \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <clx-tabs [tabs]=\"tabItems\" [clxColor]=\"clxColor()\" [(activeTab)]=\"activeTab\">\n\n <!-- Tab: Caracter\u00EDsticas generales (st_product_specs \u2014 ficha t\u00E9cnica global) -->\n <ng-template [clxTabPanel]=\"'general'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsLeft(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (spec of specsRight(p.product_specs); track spec.id) {\n <clx-list-item [icon]=\"spec.icon ?? ''\">\n <div clxContent>\n <p class=\"text-sm font-medium text-clx-text-label\">{{ spec.name }}:</p>\n <p class=\"text-sm text-clx-text-muted mt-0.5\">{{ spec.value }}</p>\n </div>\n </clx-list-item>\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Especificaciones (st_spec_types \u2014 specs de la variaci\u00F3n activa) -->\n <ng-template [clxTabPanel]=\"'specs'\">\n <div class=\"grid grid-cols-1 sm:grid-cols-2 gap-x-8\">\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsLeft(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n <clx-list [color]=\"clxColor()\" variant=\"flush\" size=\"sm\">\n @for (st of specsRight(p.spec_types); track st.id) {\n <clx-list-item [label]=\"st.name + ':'\" [sublabel]=\"selectedOptionLabel(st)\" [icon]=\"st.icon ?? ''\" />\n }\n </clx-list>\n </div>\n </ng-template>\n\n <!-- Tab: Descripci\u00F3n (st_product_descriptions) -->\n <ng-template [clxTabPanel]=\"'description'\">\n <div class=\"space-y-5\">\n @for (block of p.descriptions; track block.id) {\n <div>\n @if (block.title) {\n <h3 class=\"text-base font-semibold text-clx-text-label mb-1\">{{ block.title }}</h3>\n }\n <p class=\"text-sm text-clx-text-muted leading-relaxed\">{{ block.body }}</p>\n </div>\n }\n </div>\n </ng-template>\n\n </clx-tabs>\n\n <!-- \u2500\u2500 Reviews \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"grid grid-cols-1 xl:grid-cols-3 gap-8 border-t border-clx-border pt-8\">\n\n <div class=\"space-y-3\">\n <h2 class=\"text-base font-bold text-clx-text-label\">Customer Reviews</h2>\n <div class=\"flex items-end gap-3\">\n <span class=\"text-5xl font-black text-clx-text-label\">{{ p.rating | number:'1.1-1' }}</span>\n <div class=\"pb-1 space-y-1\">\n <clx-rating [value]=\"p.rating\" [color]=\"clxColor()\"></clx-rating>\n <p class=\"text-xs text-clx-text-muted\">{{ p.review_count }} verified ratings</p>\n </div>\n </div>\n <p class=\"text-sm font-semibold\" [class]=\"'text-' + clxColor() + '-600'\">Very Good</p>\n <div class=\"pt-2 space-y-2\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\">\n Leave Us a Review\n </button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"clxColor()\" size=\"md\" [block]=\"true\" icon=\"flag\">\n Report abuse\n </button>\n </div>\n </div>\n\n <div class=\"lg:col-span-2 space-y-4\">\n @for (review of p.reviews ?? []; track review.id) {\n <div class=\"rounded-2xl overflow-hidden shadow-sm\">\n <div class=\"flex items-center gap-3 px-4 py-3\">\n <div class=\"w-10 h-10 rounded-full bg-clx-surface-2 flex items-center justify-center shrink-0\">\n <span class=\"text-sm font-bold text-clx-text-muted\">\n {{ review.author_name.substring(0,1) }}{{ review.author_name.split(' ')[1]?.substring(0,1) ?? '' }}\n </span>\n </div>\n <div class=\"flex-1 min-w-0\">\n <p class=\"font-semibold text-sm text-clx-text-label truncate\">{{ review.author_name }}</p>\n <p class=\"text-xs text-clx-text-subtle\">{{ review.created_at }}</p>\n </div>\n @if (review.is_verified) {\n <span clx-badge variant=\"light\" color=\"emerald\" size=\"sm\" shape=\"pill\">Verified</span>\n }\n </div>\n <div class=\"px-4 pb-4 space-y-2\">\n <div class=\"flex items-center gap-2\">\n <clx-rating [value]=\"review.rating\" [color]=\"clxColor()\"></clx-rating>\n @if (review.title) {\n <span class=\"text-sm font-semibold text-clx-text-label\">{{ review.title }}</span>\n }\n </div>\n @if (review.body) {\n <p class=\"text-sm text-clx-text-muted\">{{ review.body }}</p>\n }\n <div class=\"flex items-center gap-3 pt-1\">\n <span class=\"text-xs text-clx-text-subtle\">Helpful?</span>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_up\">{{ review.helpful }}</button>\n <button clx-button type=\"button\" variant=\"ghost\" color=\"slate\" size=\"xs\" icon=\"thumb_down\">{{ review.not_helpful }}</button>\n </div>\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n\n </div>\n}\n" }]
|
|
14585
14558
|
}], ctorParameters: () => [], propDecorators: { product: [{ type: i0.Input, args: [{ isSignal: true, alias: "product", required: false }] }], initialVariationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialVariationId", required: false }] }], clxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "clxColor", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], addToCart: [{ type: i0.Output, args: ["addToCart"] }], buyNow: [{ type: i0.Output, args: ["buyNow"] }], wishlist: [{ type: i0.Output, args: ["wishlist"] }], variationChange: [{ type: i0.Output, args: ["variationChange"] }], _carousel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ClxCarouselComponent), { isSignal: true }] }] } });
|
|
14586
14559
|
|
|
14587
14560
|
class ClxProductQuickViewComponent {
|
|
@@ -14758,22 +14731,19 @@ class ClxWishlistComponent {
|
|
|
14758
14731
|
<!-- ── Info Bar ─────────────────────────────────────────────────────── -->
|
|
14759
14732
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
|
14760
14733
|
<div class="flex items-center gap-2">
|
|
14761
|
-
<
|
|
14762
|
-
<span
|
|
14763
|
-
<span clx-badge variant="light" [color]="color()" size="sm">items</span>
|
|
14764
|
-
<span class="text-clx-text-muted text-sm">to your wishlist</span>
|
|
14734
|
+
<h2 class="text-base font-semibold text-clx-text-label">Productos</h2>
|
|
14735
|
+
<span clx-badge variant="light" [color]="color()" size="sm">{{ _totalItems() }}</span>
|
|
14765
14736
|
</div>
|
|
14766
14737
|
<button
|
|
14767
14738
|
clx-button
|
|
14768
14739
|
type="button"
|
|
14769
14740
|
variant="light"
|
|
14770
14741
|
[color]="color()"
|
|
14771
|
-
size="sm"
|
|
14772
14742
|
icon="arrow_forward"
|
|
14773
14743
|
iconPosition="right"
|
|
14774
14744
|
[disabled]="_totalItems() === 0"
|
|
14775
14745
|
(click)="handleCheckoutAll()"
|
|
14776
|
-
>
|
|
14746
|
+
>Agregar todo al carrito</button>
|
|
14777
14747
|
</div>
|
|
14778
14748
|
|
|
14779
14749
|
<!-- ── List ─────────────────────────────────────────────────────────── -->
|
|
@@ -14797,11 +14767,14 @@ class ClxWishlistComponent {
|
|
|
14797
14767
|
|
|
14798
14768
|
} @else if (_pagedItems().length === 0) {
|
|
14799
14769
|
|
|
14800
|
-
<
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14770
|
+
<clx-page-empty
|
|
14771
|
+
icon="favorite"
|
|
14772
|
+
title="Sin favoritos"
|
|
14773
|
+
description="No tienes productos guardados."
|
|
14774
|
+
[ctaColor]="color()"
|
|
14775
|
+
ctaIcon="storefront"
|
|
14776
|
+
ctaLabel="Ir a la tienda">
|
|
14777
|
+
</clx-page-empty>
|
|
14805
14778
|
|
|
14806
14779
|
} @else {
|
|
14807
14780
|
|
|
@@ -14845,10 +14818,10 @@ class ClxWishlistComponent {
|
|
|
14845
14818
|
|
|
14846
14819
|
<!-- Price -->
|
|
14847
14820
|
<div class="flex items-baseline gap-2 mt-2">
|
|
14848
|
-
<span [class]="_priceClass">{{ (item.sale_price ?? item.product_sale_price) | currency:'COP':'
|
|
14821
|
+
<span [class]="_priceClass">{{ (item.sale_price ?? item.product_sale_price) | currency:'COP':'$':'1.0-0' }}</span>
|
|
14849
14822
|
@if (item.has_discount && item.discount_price) {
|
|
14850
14823
|
<span class="text-sm text-clx-text-subtle line-through">
|
|
14851
|
-
{{ item.discount_price | currency:'COP':'
|
|
14824
|
+
{{ item.discount_price | currency:'COP':'$':'1.0-0' }}
|
|
14852
14825
|
</span>
|
|
14853
14826
|
}
|
|
14854
14827
|
</div>
|
|
@@ -14910,7 +14883,7 @@ class ClxWishlistComponent {
|
|
|
14910
14883
|
}
|
|
14911
14884
|
|
|
14912
14885
|
</div>
|
|
14913
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type:
|
|
14886
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxPaginationComponent, selector: "clx-pagination", inputs: ["totalItems", "color", "size", "showPageSize", "pageSizeOptions", "currentPage", "pageSize"], outputs: ["currentPageChange", "pageSizeChange", "pageChange", "sizeChange"] }, { kind: "component", type: ClxInputComponent, selector: "clx-input", inputs: ["label", "placeholder", "type", "color", "hint", "prefixIcon", "suffixIcon", "prefixText", "suffixText", "status", "statusMessage", "size", "value", "disabled", "autocomplete"] }, { kind: "component", type: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "async", "loadOptions", "debounceMs", "minChars"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }, { kind: "component", type: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "title", "description", "ctaLabel", "ctaIcon", "ctaColor"], outputs: ["cta"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14914
14887
|
}
|
|
14915
14888
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxWishlistComponent, decorators: [{
|
|
14916
14889
|
type: Component,
|
|
@@ -14922,12 +14895,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
14922
14895
|
FormsModule,
|
|
14923
14896
|
ClxButtonComponent,
|
|
14924
14897
|
ClxBadgeComponent,
|
|
14925
|
-
ClxIconComponent,
|
|
14926
14898
|
ClxSkeletonComponent,
|
|
14927
14899
|
ClxPaginationComponent,
|
|
14928
14900
|
ClxInputComponent,
|
|
14929
14901
|
ClxSelectComponent,
|
|
14930
|
-
ClxTooltipDirective
|
|
14902
|
+
ClxTooltipDirective,
|
|
14903
|
+
ClxPageEmptyComponent
|
|
14931
14904
|
],
|
|
14932
14905
|
template: `
|
|
14933
14906
|
<div class="flex flex-col gap-6">
|
|
@@ -14960,22 +14933,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
14960
14933
|
<!-- ── Info Bar ─────────────────────────────────────────────────────── -->
|
|
14961
14934
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
|
14962
14935
|
<div class="flex items-center gap-2">
|
|
14963
|
-
<
|
|
14964
|
-
<span
|
|
14965
|
-
<span clx-badge variant="light" [color]="color()" size="sm">items</span>
|
|
14966
|
-
<span class="text-clx-text-muted text-sm">to your wishlist</span>
|
|
14936
|
+
<h2 class="text-base font-semibold text-clx-text-label">Productos</h2>
|
|
14937
|
+
<span clx-badge variant="light" [color]="color()" size="sm">{{ _totalItems() }}</span>
|
|
14967
14938
|
</div>
|
|
14968
14939
|
<button
|
|
14969
14940
|
clx-button
|
|
14970
14941
|
type="button"
|
|
14971
14942
|
variant="light"
|
|
14972
14943
|
[color]="color()"
|
|
14973
|
-
size="sm"
|
|
14974
14944
|
icon="arrow_forward"
|
|
14975
14945
|
iconPosition="right"
|
|
14976
14946
|
[disabled]="_totalItems() === 0"
|
|
14977
14947
|
(click)="handleCheckoutAll()"
|
|
14978
|
-
>
|
|
14948
|
+
>Agregar todo al carrito</button>
|
|
14979
14949
|
</div>
|
|
14980
14950
|
|
|
14981
14951
|
<!-- ── List ─────────────────────────────────────────────────────────── -->
|
|
@@ -14999,11 +14969,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
14999
14969
|
|
|
15000
14970
|
} @else if (_pagedItems().length === 0) {
|
|
15001
14971
|
|
|
15002
|
-
<
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
14972
|
+
<clx-page-empty
|
|
14973
|
+
icon="favorite"
|
|
14974
|
+
title="Sin favoritos"
|
|
14975
|
+
description="No tienes productos guardados."
|
|
14976
|
+
[ctaColor]="color()"
|
|
14977
|
+
ctaIcon="storefront"
|
|
14978
|
+
ctaLabel="Ir a la tienda">
|
|
14979
|
+
</clx-page-empty>
|
|
15007
14980
|
|
|
15008
14981
|
} @else {
|
|
15009
14982
|
|
|
@@ -15047,10 +15020,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15047
15020
|
|
|
15048
15021
|
<!-- Price -->
|
|
15049
15022
|
<div class="flex items-baseline gap-2 mt-2">
|
|
15050
|
-
<span [class]="_priceClass">{{ (item.sale_price ?? item.product_sale_price) | currency:'COP':'
|
|
15023
|
+
<span [class]="_priceClass">{{ (item.sale_price ?? item.product_sale_price) | currency:'COP':'$':'1.0-0' }}</span>
|
|
15051
15024
|
@if (item.has_discount && item.discount_price) {
|
|
15052
15025
|
<span class="text-sm text-clx-text-subtle line-through">
|
|
15053
|
-
{{ item.discount_price | currency:'COP':'
|
|
15026
|
+
{{ item.discount_price | currency:'COP':'$':'1.0-0' }}
|
|
15054
15027
|
</span>
|
|
15055
15028
|
}
|
|
15056
15029
|
</div>
|
|
@@ -15190,11 +15163,14 @@ class ClxCartComponent {
|
|
|
15190
15163
|
<!-- ── Empty ───────────────────────────────────────────────────────────────── -->
|
|
15191
15164
|
} @else if (items().length === 0) {
|
|
15192
15165
|
|
|
15193
|
-
<
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15166
|
+
<clx-page-empty
|
|
15167
|
+
icon="remove_shopping_cart"
|
|
15168
|
+
title="Carrito vacio"
|
|
15169
|
+
description="Tu carrito esta vacio. Agrega productos para continuar."
|
|
15170
|
+
[ctaColor]="color()"
|
|
15171
|
+
ctaIcon="storefront"
|
|
15172
|
+
ctaLabel="Ir a la tienda">
|
|
15173
|
+
</clx-page-empty>
|
|
15198
15174
|
|
|
15199
15175
|
<!-- ── Main layout ─────────────────────────────────────────────────────────── -->
|
|
15200
15176
|
} @else {
|
|
@@ -15207,19 +15183,19 @@ class ClxCartComponent {
|
|
|
15207
15183
|
<!-- Top bar -->
|
|
15208
15184
|
<div class="flex items-center justify-between">
|
|
15209
15185
|
<div class="flex items-center gap-2">
|
|
15210
|
-
<h2 class="text-base font-semibold text-clx-text-label">
|
|
15186
|
+
<h2 class="text-base font-semibold text-clx-text-label">Productos</h2>
|
|
15211
15187
|
<span clx-badge variant="light" [color]="color()" size="sm">{{ _totalUnits() }}</span>
|
|
15212
15188
|
</div>
|
|
15213
15189
|
<button
|
|
15214
|
-
clx-button type="button" variant="
|
|
15190
|
+
clx-button type="button" variant="light" color="red" icon="delete"
|
|
15215
15191
|
(click)="onClearCart.emit()">
|
|
15216
|
-
Vaciar
|
|
15192
|
+
Vaciar carrito
|
|
15217
15193
|
</button>
|
|
15218
15194
|
</div>
|
|
15219
15195
|
|
|
15220
15196
|
<!-- Item cards -->
|
|
15221
15197
|
@for (item of items(); track item.id) {
|
|
15222
|
-
<article class="bg-clx-surface rounded-2xl shadow-sm flex gap-0 overflow-hidden group transition-[box-shadow,transform] duration-300 hover:shadow-
|
|
15198
|
+
<article class="bg-clx-surface rounded-2xl shadow-sm flex gap-0 overflow-hidden group transition-[box-shadow,transform] duration-300 hover:shadow-md hover:-translate-y-0.5">
|
|
15223
15199
|
|
|
15224
15200
|
<!-- Image — fixed square -->
|
|
15225
15201
|
<div class="relative shrink-0 w-28 h-28 lg:w-36 lg:h-36 overflow-hidden bg-clx-surface-2">
|
|
@@ -15264,54 +15240,29 @@ class ClxCartComponent {
|
|
|
15264
15240
|
<p class="text-xs font-mono text-clx-text-subtle mt-0.5">{{ item.sku }}</p>
|
|
15265
15241
|
</div>
|
|
15266
15242
|
|
|
15267
|
-
@if (item.rating) {
|
|
15268
|
-
<div class="flex items-center gap-1.5">
|
|
15269
|
-
<clx-rating [value]="item.rating" [max]="5" color="amber" />
|
|
15270
|
-
@if (item.review_count) {
|
|
15271
|
-
<span class="text-xs text-clx-text-subtle">({{ item.review_count }})</span>
|
|
15272
|
-
}
|
|
15273
|
-
</div>
|
|
15274
|
-
}
|
|
15275
|
-
|
|
15276
15243
|
<!-- Price + Quantity row -->
|
|
15277
15244
|
<div class="flex items-center justify-between gap-2 mt-auto flex-wrap">
|
|
15278
15245
|
|
|
15279
15246
|
<!-- Price -->
|
|
15280
15247
|
<div class="flex items-baseline gap-1.5">
|
|
15281
15248
|
<span class="text-base font-bold text-clx-text-label">
|
|
15282
|
-
{{ item.unit_price | currency:'COP':'
|
|
15249
|
+
{{ item.unit_price | currency:'COP':'$':'1.0-0' }}
|
|
15283
15250
|
</span>
|
|
15284
15251
|
@if (item.has_discount && item.original_price) {
|
|
15285
15252
|
<span class="text-xs text-clx-text-subtle line-through">
|
|
15286
|
-
{{ item.original_price | currency:'COP':'
|
|
15253
|
+
{{ item.original_price | currency:'COP':'$':'1.0-0' }}
|
|
15287
15254
|
</span>
|
|
15288
15255
|
}
|
|
15289
15256
|
</div>
|
|
15290
15257
|
|
|
15291
15258
|
<!-- Quantity control -->
|
|
15292
|
-
<
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
<span class="w-8 text-center text-sm font-semibold text-clx-text-label select-none">
|
|
15300
|
-
{{ item.quantity }}
|
|
15301
|
-
</span>
|
|
15302
|
-
<button
|
|
15303
|
-
clx-button type="button" variant="ghost" [color]="color()" size="sm"
|
|
15304
|
-
icon="add" [iconOnly]="true"
|
|
15305
|
-
[disabled]="!item.in_stock || (!!item.max_quantity && item.quantity >= item.max_quantity)"
|
|
15306
|
-
(click)="onQuantityChange.emit({ item, quantity: item.quantity + 1 })">
|
|
15307
|
-
</button>
|
|
15308
|
-
</div>
|
|
15309
|
-
|
|
15310
|
-
<!-- Line total -->
|
|
15311
|
-
<p class="text-sm font-bold text-clx-text-label">
|
|
15312
|
-
{{ (item.unit_price * item.quantity) | currency:'COP':'symbol':'1.0-0' }}
|
|
15313
|
-
</p>
|
|
15314
|
-
|
|
15259
|
+
<clx-number
|
|
15260
|
+
variant="stepper" size="sm" [color]="color()"
|
|
15261
|
+
[min]="1" [max]="item.max_quantity ?? null"
|
|
15262
|
+
[disabled]="!item.in_stock"
|
|
15263
|
+
[ngModel]="item.quantity"
|
|
15264
|
+
(ngModelChange)="onQuantityChange.emit({ item, quantity: $event })">
|
|
15265
|
+
</clx-number>
|
|
15315
15266
|
</div>
|
|
15316
15267
|
</div>
|
|
15317
15268
|
|
|
@@ -15376,12 +15327,12 @@ class ClxCartComponent {
|
|
|
15376
15327
|
<dl class="flex flex-col gap-2.5 text-sm">
|
|
15377
15328
|
<div class="flex justify-between text-clx-text-muted">
|
|
15378
15329
|
<dt>Subtotal <span class="text-clx-text-subtle text-xs">({{ _totalUnits() }} items)</span></dt>
|
|
15379
|
-
<dd class="font-medium">{{ _subtotal() | currency:'COP':'
|
|
15330
|
+
<dd class="font-medium">{{ _subtotal() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15380
15331
|
</div>
|
|
15381
15332
|
@if (_savingsAmount() > 0) {
|
|
15382
15333
|
<div class="flex justify-between text-emerald-600">
|
|
15383
15334
|
<dt>Descuento productos</dt>
|
|
15384
|
-
<dd class="font-medium">-{{ _savingsAmount() | currency:'COP':'
|
|
15335
|
+
<dd class="font-medium">-{{ _savingsAmount() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15385
15336
|
</div>
|
|
15386
15337
|
}
|
|
15387
15338
|
@if (coupon()?.valid) {
|
|
@@ -15390,18 +15341,18 @@ class ClxCartComponent {
|
|
|
15390
15341
|
Cupón
|
|
15391
15342
|
<code class="bg-emerald-50 px-1 rounded text-xs ml-1">{{ coupon()!.code }}</code>
|
|
15392
15343
|
</dt>
|
|
15393
|
-
<dd class="font-medium">-{{ _couponDiscount() | currency:'COP':'
|
|
15344
|
+
<dd class="font-medium">-{{ _couponDiscount() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15394
15345
|
</div>
|
|
15395
15346
|
}
|
|
15396
15347
|
<div class="flex justify-between border-t border-clx-border-soft pt-2.5">
|
|
15397
15348
|
<dt class="font-bold text-clx-text-label">Total</dt>
|
|
15398
|
-
<dd class="text-
|
|
15349
|
+
<dd class="text-lg font-black text-clx-text-label">{{ _total() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15399
15350
|
</div>
|
|
15400
15351
|
</dl>
|
|
15401
15352
|
|
|
15402
15353
|
<!-- Checkout -->
|
|
15403
15354
|
<button
|
|
15404
|
-
clx-button type="button" [color]="color()" variant="solid" size="
|
|
15355
|
+
clx-button type="button" [color]="color()" variant="solid" size="md"
|
|
15405
15356
|
icon="arrow_forward" iconPosition="right" [block]="true"
|
|
15406
15357
|
(click)="onCheckout.emit()">
|
|
15407
15358
|
Proceder al pago
|
|
@@ -15420,7 +15371,7 @@ class ClxCartComponent {
|
|
|
15420
15371
|
</div>
|
|
15421
15372
|
|
|
15422
15373
|
}
|
|
15423
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxInputComponent, selector: "clx-input", inputs: ["label", "placeholder", "type", "color", "hint", "prefixIcon", "suffixIcon", "prefixText", "suffixText", "status", "statusMessage", "size", "value", "disabled", "autocomplete"] }, { kind: "component", type:
|
|
15374
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxInputComponent, selector: "clx-input", inputs: ["label", "placeholder", "type", "color", "hint", "prefixIcon", "suffixIcon", "prefixText", "suffixText", "status", "statusMessage", "size", "value", "disabled", "autocomplete"] }, { kind: "component", type: ClxNumberComponent, selector: "clx-number", inputs: ["variant", "size", "color", "label", "placeholder", "hint", "prefixIcon", "min", "max", "step", "value", "disabled"] }, { kind: "component", type: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "title", "description", "ctaLabel", "ctaIcon", "ctaColor"], outputs: ["cta"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
15424
15375
|
}
|
|
15425
15376
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxCartComponent, decorators: [{
|
|
15426
15377
|
type: Component,
|
|
@@ -15435,7 +15386,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15435
15386
|
ClxIconComponent,
|
|
15436
15387
|
ClxSkeletonComponent,
|
|
15437
15388
|
ClxInputComponent,
|
|
15438
|
-
|
|
15389
|
+
ClxNumberComponent,
|
|
15390
|
+
ClxPageEmptyComponent
|
|
15439
15391
|
],
|
|
15440
15392
|
encapsulation: ViewEncapsulation.None,
|
|
15441
15393
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -15470,11 +15422,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15470
15422
|
<!-- ── Empty ───────────────────────────────────────────────────────────────── -->
|
|
15471
15423
|
} @else if (items().length === 0) {
|
|
15472
15424
|
|
|
15473
|
-
<
|
|
15474
|
-
|
|
15475
|
-
|
|
15476
|
-
|
|
15477
|
-
|
|
15425
|
+
<clx-page-empty
|
|
15426
|
+
icon="remove_shopping_cart"
|
|
15427
|
+
title="Carrito vacio"
|
|
15428
|
+
description="Tu carrito esta vacio. Agrega productos para continuar."
|
|
15429
|
+
[ctaColor]="color()"
|
|
15430
|
+
ctaIcon="storefront"
|
|
15431
|
+
ctaLabel="Ir a la tienda">
|
|
15432
|
+
</clx-page-empty>
|
|
15478
15433
|
|
|
15479
15434
|
<!-- ── Main layout ─────────────────────────────────────────────────────────── -->
|
|
15480
15435
|
} @else {
|
|
@@ -15487,19 +15442,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15487
15442
|
<!-- Top bar -->
|
|
15488
15443
|
<div class="flex items-center justify-between">
|
|
15489
15444
|
<div class="flex items-center gap-2">
|
|
15490
|
-
<h2 class="text-base font-semibold text-clx-text-label">
|
|
15445
|
+
<h2 class="text-base font-semibold text-clx-text-label">Productos</h2>
|
|
15491
15446
|
<span clx-badge variant="light" [color]="color()" size="sm">{{ _totalUnits() }}</span>
|
|
15492
15447
|
</div>
|
|
15493
15448
|
<button
|
|
15494
|
-
clx-button type="button" variant="
|
|
15449
|
+
clx-button type="button" variant="light" color="red" icon="delete"
|
|
15495
15450
|
(click)="onClearCart.emit()">
|
|
15496
|
-
Vaciar
|
|
15451
|
+
Vaciar carrito
|
|
15497
15452
|
</button>
|
|
15498
15453
|
</div>
|
|
15499
15454
|
|
|
15500
15455
|
<!-- Item cards -->
|
|
15501
15456
|
@for (item of items(); track item.id) {
|
|
15502
|
-
<article class="bg-clx-surface rounded-2xl shadow-sm flex gap-0 overflow-hidden group transition-[box-shadow,transform] duration-300 hover:shadow-
|
|
15457
|
+
<article class="bg-clx-surface rounded-2xl shadow-sm flex gap-0 overflow-hidden group transition-[box-shadow,transform] duration-300 hover:shadow-md hover:-translate-y-0.5">
|
|
15503
15458
|
|
|
15504
15459
|
<!-- Image — fixed square -->
|
|
15505
15460
|
<div class="relative shrink-0 w-28 h-28 lg:w-36 lg:h-36 overflow-hidden bg-clx-surface-2">
|
|
@@ -15544,54 +15499,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15544
15499
|
<p class="text-xs font-mono text-clx-text-subtle mt-0.5">{{ item.sku }}</p>
|
|
15545
15500
|
</div>
|
|
15546
15501
|
|
|
15547
|
-
@if (item.rating) {
|
|
15548
|
-
<div class="flex items-center gap-1.5">
|
|
15549
|
-
<clx-rating [value]="item.rating" [max]="5" color="amber" />
|
|
15550
|
-
@if (item.review_count) {
|
|
15551
|
-
<span class="text-xs text-clx-text-subtle">({{ item.review_count }})</span>
|
|
15552
|
-
}
|
|
15553
|
-
</div>
|
|
15554
|
-
}
|
|
15555
|
-
|
|
15556
15502
|
<!-- Price + Quantity row -->
|
|
15557
15503
|
<div class="flex items-center justify-between gap-2 mt-auto flex-wrap">
|
|
15558
15504
|
|
|
15559
15505
|
<!-- Price -->
|
|
15560
15506
|
<div class="flex items-baseline gap-1.5">
|
|
15561
15507
|
<span class="text-base font-bold text-clx-text-label">
|
|
15562
|
-
{{ item.unit_price | currency:'COP':'
|
|
15508
|
+
{{ item.unit_price | currency:'COP':'$':'1.0-0' }}
|
|
15563
15509
|
</span>
|
|
15564
15510
|
@if (item.has_discount && item.original_price) {
|
|
15565
15511
|
<span class="text-xs text-clx-text-subtle line-through">
|
|
15566
|
-
{{ item.original_price | currency:'COP':'
|
|
15512
|
+
{{ item.original_price | currency:'COP':'$':'1.0-0' }}
|
|
15567
15513
|
</span>
|
|
15568
15514
|
}
|
|
15569
15515
|
</div>
|
|
15570
15516
|
|
|
15571
15517
|
<!-- Quantity control -->
|
|
15572
|
-
<
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
<span class="w-8 text-center text-sm font-semibold text-clx-text-label select-none">
|
|
15580
|
-
{{ item.quantity }}
|
|
15581
|
-
</span>
|
|
15582
|
-
<button
|
|
15583
|
-
clx-button type="button" variant="ghost" [color]="color()" size="sm"
|
|
15584
|
-
icon="add" [iconOnly]="true"
|
|
15585
|
-
[disabled]="!item.in_stock || (!!item.max_quantity && item.quantity >= item.max_quantity)"
|
|
15586
|
-
(click)="onQuantityChange.emit({ item, quantity: item.quantity + 1 })">
|
|
15587
|
-
</button>
|
|
15588
|
-
</div>
|
|
15589
|
-
|
|
15590
|
-
<!-- Line total -->
|
|
15591
|
-
<p class="text-sm font-bold text-clx-text-label">
|
|
15592
|
-
{{ (item.unit_price * item.quantity) | currency:'COP':'symbol':'1.0-0' }}
|
|
15593
|
-
</p>
|
|
15594
|
-
|
|
15518
|
+
<clx-number
|
|
15519
|
+
variant="stepper" size="sm" [color]="color()"
|
|
15520
|
+
[min]="1" [max]="item.max_quantity ?? null"
|
|
15521
|
+
[disabled]="!item.in_stock"
|
|
15522
|
+
[ngModel]="item.quantity"
|
|
15523
|
+
(ngModelChange)="onQuantityChange.emit({ item, quantity: $event })">
|
|
15524
|
+
</clx-number>
|
|
15595
15525
|
</div>
|
|
15596
15526
|
</div>
|
|
15597
15527
|
|
|
@@ -15656,12 +15586,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15656
15586
|
<dl class="flex flex-col gap-2.5 text-sm">
|
|
15657
15587
|
<div class="flex justify-between text-clx-text-muted">
|
|
15658
15588
|
<dt>Subtotal <span class="text-clx-text-subtle text-xs">({{ _totalUnits() }} items)</span></dt>
|
|
15659
|
-
<dd class="font-medium">{{ _subtotal() | currency:'COP':'
|
|
15589
|
+
<dd class="font-medium">{{ _subtotal() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15660
15590
|
</div>
|
|
15661
15591
|
@if (_savingsAmount() > 0) {
|
|
15662
15592
|
<div class="flex justify-between text-emerald-600">
|
|
15663
15593
|
<dt>Descuento productos</dt>
|
|
15664
|
-
<dd class="font-medium">-{{ _savingsAmount() | currency:'COP':'
|
|
15594
|
+
<dd class="font-medium">-{{ _savingsAmount() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15665
15595
|
</div>
|
|
15666
15596
|
}
|
|
15667
15597
|
@if (coupon()?.valid) {
|
|
@@ -15670,18 +15600,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15670
15600
|
Cupón
|
|
15671
15601
|
<code class="bg-emerald-50 px-1 rounded text-xs ml-1">{{ coupon()!.code }}</code>
|
|
15672
15602
|
</dt>
|
|
15673
|
-
<dd class="font-medium">-{{ _couponDiscount() | currency:'COP':'
|
|
15603
|
+
<dd class="font-medium">-{{ _couponDiscount() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15674
15604
|
</div>
|
|
15675
15605
|
}
|
|
15676
15606
|
<div class="flex justify-between border-t border-clx-border-soft pt-2.5">
|
|
15677
15607
|
<dt class="font-bold text-clx-text-label">Total</dt>
|
|
15678
|
-
<dd class="text-
|
|
15608
|
+
<dd class="text-lg font-black text-clx-text-label">{{ _total() | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15679
15609
|
</div>
|
|
15680
15610
|
</dl>
|
|
15681
15611
|
|
|
15682
15612
|
<!-- Checkout -->
|
|
15683
15613
|
<button
|
|
15684
|
-
clx-button type="button" [color]="color()" variant="solid" size="
|
|
15614
|
+
clx-button type="button" [color]="color()" variant="solid" size="md"
|
|
15685
15615
|
icon="arrow_forward" iconPosition="right" [block]="true"
|
|
15686
15616
|
(click)="onCheckout.emit()">
|
|
15687
15617
|
Proceder al pago
|
|
@@ -15744,7 +15674,7 @@ class ClxCartSummaryDrawer {
|
|
|
15744
15674
|
<p class="text-xs text-clx-text-subtle">x{{ item.quantity }}</p>
|
|
15745
15675
|
</div>
|
|
15746
15676
|
<span class="text-sm font-semibold text-clx-text-label shrink-0">
|
|
15747
|
-
{{ (item.unit_price * item.quantity) | currency:'COP':'
|
|
15677
|
+
{{ (item.unit_price * item.quantity) | currency:'COP':'$':'1.0-0' }}
|
|
15748
15678
|
</span>
|
|
15749
15679
|
</li>
|
|
15750
15680
|
}
|
|
@@ -15757,12 +15687,12 @@ class ClxCartSummaryDrawer {
|
|
|
15757
15687
|
<dl class="space-y-2">
|
|
15758
15688
|
<div class="flex justify-between text-sm text-clx-text-muted">
|
|
15759
15689
|
<dt>Subtotal</dt>
|
|
15760
|
-
<dd class="font-medium">{{ data.subtotal | currency:'COP':'
|
|
15690
|
+
<dd class="font-medium">{{ data.subtotal | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15761
15691
|
</div>
|
|
15762
15692
|
@if (data.savingsAmount > 0) {
|
|
15763
15693
|
<div class="flex justify-between text-sm text-emerald-600">
|
|
15764
15694
|
<dt>Descuento</dt>
|
|
15765
|
-
<dd class="font-medium">-{{ data.savingsAmount | currency:'COP':'
|
|
15695
|
+
<dd class="font-medium">-{{ data.savingsAmount | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15766
15696
|
</div>
|
|
15767
15697
|
}
|
|
15768
15698
|
@if (data.coupon?.valid) {
|
|
@@ -15771,12 +15701,12 @@ class ClxCartSummaryDrawer {
|
|
|
15771
15701
|
Cupón
|
|
15772
15702
|
<code class="bg-emerald-50 px-1 rounded text-xs ml-1">{{ data.coupon!.code }}</code>
|
|
15773
15703
|
</dt>
|
|
15774
|
-
<dd class="font-medium">-{{ data.couponDiscount | currency:'COP':'
|
|
15704
|
+
<dd class="font-medium">-{{ data.couponDiscount | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15775
15705
|
</div>
|
|
15776
15706
|
}
|
|
15777
15707
|
<div class="border-t border-clx-border-soft pt-2 flex justify-between">
|
|
15778
15708
|
<dt class="font-bold text-clx-text-label">Total</dt>
|
|
15779
|
-
<dd class="text-xl font-black text-clx-text-label">{{ data.total | currency:'COP':'
|
|
15709
|
+
<dd class="text-xl font-black text-clx-text-label">{{ data.total | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15780
15710
|
</div>
|
|
15781
15711
|
</dl>
|
|
15782
15712
|
|
|
@@ -15833,7 +15763,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15833
15763
|
<p class="text-xs text-clx-text-subtle">x{{ item.quantity }}</p>
|
|
15834
15764
|
</div>
|
|
15835
15765
|
<span class="text-sm font-semibold text-clx-text-label shrink-0">
|
|
15836
|
-
{{ (item.unit_price * item.quantity) | currency:'COP':'
|
|
15766
|
+
{{ (item.unit_price * item.quantity) | currency:'COP':'$':'1.0-0' }}
|
|
15837
15767
|
</span>
|
|
15838
15768
|
</li>
|
|
15839
15769
|
}
|
|
@@ -15846,12 +15776,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15846
15776
|
<dl class="space-y-2">
|
|
15847
15777
|
<div class="flex justify-between text-sm text-clx-text-muted">
|
|
15848
15778
|
<dt>Subtotal</dt>
|
|
15849
|
-
<dd class="font-medium">{{ data.subtotal | currency:'COP':'
|
|
15779
|
+
<dd class="font-medium">{{ data.subtotal | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15850
15780
|
</div>
|
|
15851
15781
|
@if (data.savingsAmount > 0) {
|
|
15852
15782
|
<div class="flex justify-between text-sm text-emerald-600">
|
|
15853
15783
|
<dt>Descuento</dt>
|
|
15854
|
-
<dd class="font-medium">-{{ data.savingsAmount | currency:'COP':'
|
|
15784
|
+
<dd class="font-medium">-{{ data.savingsAmount | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15855
15785
|
</div>
|
|
15856
15786
|
}
|
|
15857
15787
|
@if (data.coupon?.valid) {
|
|
@@ -15860,12 +15790,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
15860
15790
|
Cupón
|
|
15861
15791
|
<code class="bg-emerald-50 px-1 rounded text-xs ml-1">{{ data.coupon!.code }}</code>
|
|
15862
15792
|
</dt>
|
|
15863
|
-
<dd class="font-medium">-{{ data.couponDiscount | currency:'COP':'
|
|
15793
|
+
<dd class="font-medium">-{{ data.couponDiscount | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15864
15794
|
</div>
|
|
15865
15795
|
}
|
|
15866
15796
|
<div class="border-t border-clx-border-soft pt-2 flex justify-between">
|
|
15867
15797
|
<dt class="font-bold text-clx-text-label">Total</dt>
|
|
15868
|
-
<dd class="text-xl font-black text-clx-text-label">{{ data.total | currency:'COP':'
|
|
15798
|
+
<dd class="text-xl font-black text-clx-text-label">{{ data.total | currency:'COP':'$':'1.0-0' }}</dd>
|
|
15869
15799
|
</div>
|
|
15870
15800
|
</dl>
|
|
15871
15801
|
|