codexly-ui 0.11.7 → 0.11.9
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 +72 -36
- package/fesm2022/codexly-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/types/codexly-ui.d.ts +57 -13
package/fesm2022/codexly-ui.mjs
CHANGED
|
@@ -627,6 +627,16 @@ class ClxThemeService {
|
|
|
627
627
|
bordered: t?.bordered ?? true,
|
|
628
628
|
};
|
|
629
629
|
}, ...(ngDevMode ? [{ debugName: "paginationStyle" }] : /* istanbul ignore next */ []));
|
|
630
|
+
/** Resolved style for ClxTabsComponent — activeTextColor and underlineColor each fall back to
|
|
631
|
+
* primaryColor independently when unset. Per-instance inputs on clx-tabs still take
|
|
632
|
+
* precedence over this theme-wide default. */
|
|
633
|
+
tabsStyle = computed(() => {
|
|
634
|
+
const t = this._config().tabs;
|
|
635
|
+
return {
|
|
636
|
+
activeTextColor: t?.activeTextColor ?? this._config().primaryColor,
|
|
637
|
+
underlineColor: t?.underlineColor ?? this._config().primaryColor,
|
|
638
|
+
};
|
|
639
|
+
}, ...(ngDevMode ? [{ debugName: "tabsStyle" }] : /* istanbul ignore next */ []));
|
|
630
640
|
/** Resolved style for ClxNotificationComponent — severity colors fall back to built-in
|
|
631
641
|
* green/amber/sky/red, unreadColor to primaryColor, borderColor to listBorderColor then
|
|
632
642
|
* primaryColor, radius to the app-wide borderRadius. Per-instance inputs on clx-notification
|
|
@@ -641,7 +651,9 @@ class ClxThemeService {
|
|
|
641
651
|
error: n?.severityColors?.error ?? this._config().dangerColor,
|
|
642
652
|
},
|
|
643
653
|
unreadColor: n?.unreadColor ?? this._config().primaryColor,
|
|
654
|
+
tabsColor: n?.tabsColor ?? this._config().primaryColor,
|
|
644
655
|
size: n?.size ?? 'md',
|
|
656
|
+
border: n?.border ?? true,
|
|
645
657
|
borderColor: n?.borderColor ?? this._config().listBorderColor ?? this._config().primaryColor,
|
|
646
658
|
radius: n?.radius ?? this._config().borderRadius,
|
|
647
659
|
titleWeight: n?.titleWeight ?? 600,
|
|
@@ -5235,7 +5247,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
5235
5247
|
|
|
5236
5248
|
const SWITCH_SIZE_MAP = {
|
|
5237
5249
|
xs: { track: 'w-7 h-4', thumb: 'w-3 h-3', translate: 'translate-x-3', label: 'text-xs' },
|
|
5238
|
-
sm: { track: 'w-9 h-5', thumb: 'w-4 h-4', translate: 'translate-x-4', label: 'text-
|
|
5250
|
+
sm: { track: 'w-9 h-5', thumb: 'w-4 h-4', translate: 'translate-x-4', label: 'text-sm' },
|
|
5239
5251
|
md: { track: 'w-12 h-6', thumb: 'w-5 h-5', translate: 'translate-x-6', label: 'text-sm' },
|
|
5240
5252
|
lg: { track: 'w-14 h-7', thumb: 'w-6 h-6', translate: 'translate-x-7', label: 'text-base' },
|
|
5241
5253
|
};
|
|
@@ -9598,20 +9610,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
9598
9610
|
class ClxTabsComponent {
|
|
9599
9611
|
_themeSvc = inject(ClxThemeService);
|
|
9600
9612
|
tabs = input.required(...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
9613
|
+
/** Legacy single-color override — still the shared fallback for both activeTextColor and
|
|
9614
|
+
* underlineColor when neither of those is set, so existing callers keep working unchanged. */
|
|
9601
9615
|
clxColor = input(undefined, ...(ngDevMode ? [{ debugName: "clxColor" }] : /* istanbul ignore next */ []));
|
|
9616
|
+
/** Active tab's text/icon/badge color — falls back to clxColor, then the theme's
|
|
9617
|
+
* tabs.activeTextColor (then primaryColor), when unset. */
|
|
9618
|
+
activeTextColor = input(undefined, ...(ngDevMode ? [{ debugName: "activeTextColor" }] : /* istanbul ignore next */ []));
|
|
9619
|
+
/** Active tab's bottom underline color — independent of activeTextColor, falls back to
|
|
9620
|
+
* clxColor, then the theme's tabs.underlineColor (then primaryColor), when unset. */
|
|
9621
|
+
underlineColor = input(undefined, ...(ngDevMode ? [{ debugName: "underlineColor" }] : /* istanbul ignore next */ []));
|
|
9602
9622
|
activeTab = model('', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
|
|
9603
|
-
|
|
9623
|
+
_style = this._themeSvc.tabsStyle;
|
|
9624
|
+
_activeTextColor = computed(() => this.activeTextColor() ?? this.clxColor() ?? this._style().activeTextColor, ...(ngDevMode ? [{ debugName: "_activeTextColor" }] : /* istanbul ignore next */ []));
|
|
9625
|
+
_underlineColorResolved = computed(() => this.underlineColor() ?? this.clxColor() ?? this._style().underlineColor, ...(ngDevMode ? [{ debugName: "_underlineColorResolved" }] : /* istanbul ignore next */ []));
|
|
9604
9626
|
_panels = contentChildren(ClxTabDirective, ...(ngDevMode ? [{ debugName: "_panels" }] : /* istanbul ignore next */ []));
|
|
9605
|
-
_underlineClass = computed(() => resolveColor(this.
|
|
9627
|
+
_underlineClass = computed(() => resolveColor(this._underlineColorResolved()).bg, ...(ngDevMode ? [{ debugName: "_underlineClass" }] : /* istanbul ignore next */ []));
|
|
9606
9628
|
_badgeClass(tabId) {
|
|
9607
9629
|
const active = this.activeTab() === tabId;
|
|
9608
|
-
const t = resolveColor(this.
|
|
9630
|
+
const t = resolveColor(this._activeTextColor());
|
|
9609
9631
|
return active
|
|
9610
9632
|
? `ml-0.5 px-1.5 py-0.5 rounded-full text-[10px] font-bold leading-none tabular-nums ${t.bgSubtle} ${t.textSubtle}`
|
|
9611
9633
|
: 'ml-0.5 px-1.5 py-0.5 rounded-full text-[10px] font-bold leading-none tabular-nums bg-clx-surface-2 text-clx-text-subtle';
|
|
9612
9634
|
}
|
|
9613
9635
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9614
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxTabsComponent, isStandalone: true, selector: "clx-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTab: "activeTabChange" }, queries: [{ propertyName: "_panels", predicate: ClxTabDirective, isSignal: true }], ngImport: i0, template: `
|
|
9636
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxTabsComponent, isStandalone: true, selector: "clx-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, clxColor: { classPropertyName: "clxColor", publicName: "clxColor", isSignal: true, isRequired: false, transformFunction: null }, activeTextColor: { classPropertyName: "activeTextColor", publicName: "activeTextColor", isSignal: true, isRequired: false, transformFunction: null }, underlineColor: { classPropertyName: "underlineColor", publicName: "underlineColor", isSignal: true, isRequired: false, transformFunction: null }, activeTab: { classPropertyName: "activeTab", publicName: "activeTab", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeTab: "activeTabChange" }, queries: [{ propertyName: "_panels", predicate: ClxTabDirective, isSignal: true }], ngImport: i0, template: `
|
|
9615
9637
|
<!-- Tab bar -->
|
|
9616
9638
|
<div class="flex flex-wrap items-end border-b border-clx-border gap-3" role="tablist">
|
|
9617
9639
|
@for (tab of tabs(); track tab.id) {
|
|
@@ -9620,7 +9642,7 @@ class ClxTabsComponent {
|
|
|
9620
9642
|
type="button"
|
|
9621
9643
|
role="tab"
|
|
9622
9644
|
variant="ghost"
|
|
9623
|
-
[color]="activeTab() === tab.id ?
|
|
9645
|
+
[color]="activeTab() === tab.id ? _activeTextColor() : 'slate'"
|
|
9624
9646
|
size="md"
|
|
9625
9647
|
shape="rounded"
|
|
9626
9648
|
[disabled]="tab.disabled ?? false"
|
|
@@ -9664,7 +9686,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
9664
9686
|
type="button"
|
|
9665
9687
|
role="tab"
|
|
9666
9688
|
variant="ghost"
|
|
9667
|
-
[color]="activeTab() === tab.id ?
|
|
9689
|
+
[color]="activeTab() === tab.id ? _activeTextColor() : 'slate'"
|
|
9668
9690
|
size="md"
|
|
9669
9691
|
shape="rounded"
|
|
9670
9692
|
[disabled]="tab.disabled ?? false"
|
|
@@ -9696,7 +9718,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
9696
9718
|
}
|
|
9697
9719
|
</div>
|
|
9698
9720
|
`, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["clx-tabs{display:block}\n"] }]
|
|
9699
|
-
}], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], clxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "clxColor", required: false }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }, { type: i0.Output, args: ["activeTabChange"] }], _panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ClxTabDirective), { isSignal: true }] }] } });
|
|
9721
|
+
}], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], clxColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "clxColor", required: false }] }], activeTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTextColor", required: false }] }], underlineColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "underlineColor", required: false }] }], activeTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeTab", required: false }] }, { type: i0.Output, args: ["activeTabChange"] }], _panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ClxTabDirective), { isSignal: true }] }] } });
|
|
9700
9722
|
|
|
9701
9723
|
class ClxWizardComponent {
|
|
9702
9724
|
_themeSvc = inject(ClxThemeService);
|
|
@@ -9920,7 +9942,7 @@ class ClxWizardComponent {
|
|
|
9920
9942
|
</div>
|
|
9921
9943
|
</div>
|
|
9922
9944
|
</div>
|
|
9923
|
-
`, isInline: true, styles: ["@keyframes clx-wizard-fade-slide{0%{opacity:0;transform:translate(10px)}to{opacity:1;transform:translate(0)}}.clx-wizard-step-enter{animation:clx-wizard-fade-slide .22s cubic-bezier(.4,0,.2,1) both}@keyframes clx-wizard-fill{0%{width:0%}to{width:100%}}.clx-wizard-connector-fill{animation:clx-wizard-fill .3s ease both}clx-step[data-active=false]{display:none}\n"], dependencies: [{ 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: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTab"], outputs: ["activeTabChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
9945
|
+
`, isInline: true, styles: ["@keyframes clx-wizard-fade-slide{0%{opacity:0;transform:translate(10px)}to{opacity:1;transform:translate(0)}}.clx-wizard-step-enter{animation:clx-wizard-fade-slide .22s cubic-bezier(.4,0,.2,1) both}@keyframes clx-wizard-fill{0%{width:0%}to{width:100%}}.clx-wizard-connector-fill{animation:clx-wizard-fill .3s ease both}clx-step[data-active=false]{display:none}\n"], dependencies: [{ 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: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTextColor", "underlineColor", "activeTab"], outputs: ["activeTabChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
9924
9946
|
}
|
|
9925
9947
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxWizardComponent, decorators: [{
|
|
9926
9948
|
type: Component,
|
|
@@ -11365,27 +11387,39 @@ const NOTIFICATION_SEVERITY_ICON = {
|
|
|
11365
11387
|
class ClxNotificationComponent {
|
|
11366
11388
|
_themeSvc = inject(ClxThemeService);
|
|
11367
11389
|
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
11368
|
-
color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
11369
11390
|
/** Optional third tab that filters items by ClxNotificationItem.category — e.g.
|
|
11370
11391
|
* `{ id: 'orders', label: 'Órdenes' }`. Omit for just Todas/Sin leer. */
|
|
11371
11392
|
categoryTab = input(undefined, ...(ngDevMode ? [{ debugName: "categoryTab" }] : /* istanbul ignore next */ []));
|
|
11372
|
-
// ──
|
|
11373
|
-
//
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11393
|
+
// ── Bell button — a real clx-button under the hood, so its own look-and-feel can be pinned
|
|
11394
|
+
// per instance without needing a separate trigger component. Each falls back to the
|
|
11395
|
+
// notification theme block, then a sensible built-in default. ──────────────────────────────
|
|
11396
|
+
buttonColor = input(undefined, ...(ngDevMode ? [{ debugName: "buttonColor" }] : /* istanbul ignore next */ []));
|
|
11397
|
+
buttonVariant = input('ghost', ...(ngDevMode ? [{ debugName: "buttonVariant" }] : /* istanbul ignore next */ []));
|
|
11398
|
+
buttonSize = input('md', ...(ngDevMode ? [{ debugName: "buttonSize" }] : /* istanbul ignore next */ []));
|
|
11399
|
+
buttonIcon = input('notifications', ...(ngDevMode ? [{ debugName: "buttonIcon" }] : /* istanbul ignore next */ []));
|
|
11377
11400
|
/** Unread-count badge color — falls back to the theme's notification.unreadColor (then
|
|
11378
|
-
* primaryColor) when unset
|
|
11379
|
-
* library (instance input > theme block > primaryColor). */
|
|
11401
|
+
* primaryColor) when unset. */
|
|
11380
11402
|
badgeColor = input(undefined, ...(ngDevMode ? [{ debugName: "badgeColor" }] : /* istanbul ignore next */ []));
|
|
11403
|
+
// ── Panel accents — independent from the bell button's own color. ──────────────────────────
|
|
11404
|
+
/** clx-tabs (Todas/Sin leer/...) + "Marcar todo leído" link accent — falls back to the theme's
|
|
11405
|
+
* notification.tabsColor (then primaryColor) when unset. */
|
|
11406
|
+
tabsColor = input(undefined, ...(ngDevMode ? [{ debugName: "tabsColor" }] : /* istanbul ignore next */ []));
|
|
11407
|
+
/** Whether the floating panel shows its outer border — falls back to the theme's
|
|
11408
|
+
* notification.border (then true) when unset. */
|
|
11409
|
+
border = input(undefined, ...(ngDevMode ? [{ debugName: "border" }] : /* istanbul ignore next */ []));
|
|
11410
|
+
/** Panel wrapper border color — falls back to the theme's notification.borderColor when unset. */
|
|
11411
|
+
borderColor = input(undefined, ...(ngDevMode ? [{ debugName: "borderColor" }] : /* istanbul ignore next */ []));
|
|
11381
11412
|
itemClick = output();
|
|
11382
11413
|
markRead = output();
|
|
11383
11414
|
markAllRead = output();
|
|
11384
11415
|
delete = output();
|
|
11385
11416
|
deleteAllRead = output();
|
|
11386
11417
|
_style = this._themeSvc.notificationStyle;
|
|
11387
|
-
|
|
11418
|
+
_buttonColor = computed(() => this.buttonColor() ?? this._themeSvc.config().primaryColor, ...(ngDevMode ? [{ debugName: "_buttonColor" }] : /* istanbul ignore next */ []));
|
|
11388
11419
|
_badgeColor = computed(() => this.badgeColor() ?? this._style().unreadColor, ...(ngDevMode ? [{ debugName: "_badgeColor" }] : /* istanbul ignore next */ []));
|
|
11420
|
+
_tabsColor = computed(() => this.tabsColor() ?? this._style().tabsColor, ...(ngDevMode ? [{ debugName: "_tabsColor" }] : /* istanbul ignore next */ []));
|
|
11421
|
+
_border = computed(() => this.border() ?? this._style().border, ...(ngDevMode ? [{ debugName: "_border" }] : /* istanbul ignore next */ []));
|
|
11422
|
+
_borderColor = computed(() => this.borderColor() ?? this._style().borderColor, ...(ngDevMode ? [{ debugName: "_borderColor" }] : /* istanbul ignore next */ []));
|
|
11389
11423
|
// ── CDK Overlay ─────────────────────────────────────────────────────────────
|
|
11390
11424
|
_sso = inject(ScrollStrategyOptions);
|
|
11391
11425
|
_scrollStrategy = this._sso.reposition();
|
|
@@ -11395,7 +11429,10 @@ class ClxNotificationComponent {
|
|
|
11395
11429
|
];
|
|
11396
11430
|
_isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : /* istanbul ignore next */ []));
|
|
11397
11431
|
_filter = signal('all', ...(ngDevMode ? [{ debugName: "_filter" }] : /* istanbul ignore next */ []));
|
|
11398
|
-
_panelClass = computed(() =>
|
|
11432
|
+
_panelClass = computed(() => {
|
|
11433
|
+
const borderCls = this._border() ? `border ${resolveColor(this._borderColor()).borderLight}` : 'border border-transparent';
|
|
11434
|
+
return `w-96 bg-clx-surface shadow-xl ${borderCls} overflow-hidden mt-2 flex flex-col ${resolveContainerRadius(this._style().radius)}`;
|
|
11435
|
+
}, ...(ngDevMode ? [{ debugName: "_panelClass" }] : /* istanbul ignore next */ []));
|
|
11399
11436
|
_unreadCount = computed(() => this.items().filter(i => !i.read).length, ...(ngDevMode ? [{ debugName: "_unreadCount" }] : /* istanbul ignore next */ []));
|
|
11400
11437
|
_readCount = computed(() => this.items().filter(i => i.read).length, ...(ngDevMode ? [{ debugName: "_readCount" }] : /* istanbul ignore next */ []));
|
|
11401
11438
|
_tabs = computed(() => {
|
|
@@ -11433,7 +11470,6 @@ class ClxNotificationComponent {
|
|
|
11433
11470
|
const w = this._style().groupLabelWeight;
|
|
11434
11471
|
return `px-2 pt-3 pb-1.5 text-[11px] uppercase tracking-wide text-clx-text-subtle font-[${w}]`;
|
|
11435
11472
|
}, ...(ngDevMode ? [{ debugName: "_groupLabelClass" }] : /* istanbul ignore next */ []));
|
|
11436
|
-
_linkClass = computed(() => resolveColor(this._color()).textSubtle, ...(ngDevMode ? [{ debugName: "_linkClass" }] : /* istanbul ignore next */ []));
|
|
11437
11473
|
_unreadRailClass = computed(() => resolveColor(this._style().unreadColor).bg, ...(ngDevMode ? [{ debugName: "_unreadRailClass" }] : /* istanbul ignore next */ []));
|
|
11438
11474
|
_unreadDotClass = computed(() => `w-1.5 h-1.5 rounded-full shrink-0 ${resolveColor(this._style().unreadColor).bg}`, ...(ngDevMode ? [{ debugName: "_unreadDotClass" }] : /* istanbul ignore next */ []));
|
|
11439
11475
|
_detailClass = computed(() => this._style().detailColor ? resolveColor(this._style().detailColor).textSubtle : 'text-clx-text-muted', ...(ngDevMode ? [{ debugName: "_detailClass" }] : /* istanbul ignore next */ []));
|
|
@@ -11479,17 +11515,17 @@ class ClxNotificationComponent {
|
|
|
11479
11515
|
return new Date(iso).toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit' });
|
|
11480
11516
|
}
|
|
11481
11517
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxNotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11482
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxNotificationComponent, isStandalone: true, selector: "clx-notification", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null },
|
|
11518
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxNotificationComponent, isStandalone: true, selector: "clx-notification", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, categoryTab: { classPropertyName: "categoryTab", publicName: "categoryTab", isSignal: true, isRequired: false, transformFunction: null }, buttonColor: { classPropertyName: "buttonColor", publicName: "buttonColor", isSignal: true, isRequired: false, transformFunction: null }, buttonVariant: { classPropertyName: "buttonVariant", publicName: "buttonVariant", isSignal: true, isRequired: false, transformFunction: null }, buttonSize: { classPropertyName: "buttonSize", publicName: "buttonSize", isSignal: true, isRequired: false, transformFunction: null }, buttonIcon: { classPropertyName: "buttonIcon", publicName: "buttonIcon", isSignal: true, isRequired: false, transformFunction: null }, badgeColor: { classPropertyName: "badgeColor", publicName: "badgeColor", isSignal: true, isRequired: false, transformFunction: null }, tabsColor: { classPropertyName: "tabsColor", publicName: "tabsColor", isSignal: true, isRequired: false, transformFunction: null }, border: { classPropertyName: "border", publicName: "border", isSignal: true, isRequired: false, transformFunction: null }, borderColor: { classPropertyName: "borderColor", publicName: "borderColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick", markRead: "markRead", markAllRead: "markAllRead", delete: "delete", deleteAllRead: "deleteAllRead" }, host: { classAttribute: "relative inline-flex items-center" }, ngImport: i0, template: `
|
|
11483
11519
|
<!-- Trigger -->
|
|
11484
11520
|
<button clx-button
|
|
11485
11521
|
#origin="cdkOverlayOrigin"
|
|
11486
11522
|
cdkOverlayOrigin
|
|
11487
11523
|
type="button"
|
|
11488
|
-
[variant]="
|
|
11489
|
-
[color]="
|
|
11490
|
-
[size]="
|
|
11524
|
+
[variant]="buttonVariant()"
|
|
11525
|
+
[color]="_buttonColor()"
|
|
11526
|
+
[size]="buttonSize()"
|
|
11491
11527
|
shape="circle"
|
|
11492
|
-
[icon]="
|
|
11528
|
+
[icon]="buttonIcon()"
|
|
11493
11529
|
[iconOnly]="true"
|
|
11494
11530
|
[badge]="_unreadCount() > 0 ? (_unreadCount() > 9 ? '9+' : _unreadCount()) : undefined"
|
|
11495
11531
|
[badgeColor]="_badgeColor()"
|
|
@@ -11514,7 +11550,7 @@ class ClxNotificationComponent {
|
|
|
11514
11550
|
<p class="text-base font-semibold text-clx-text-label">Notificaciones</p>
|
|
11515
11551
|
<div class="flex items-center gap-3">
|
|
11516
11552
|
@if (_unreadCount() > 0) {
|
|
11517
|
-
<button clx-button type="button" variant="ghost" [color]="
|
|
11553
|
+
<button clx-button type="button" variant="ghost" [color]="_tabsColor()" size="xs"
|
|
11518
11554
|
(click)="markAllRead.emit()">
|
|
11519
11555
|
Marcar todo leído
|
|
11520
11556
|
</button>
|
|
@@ -11526,7 +11562,7 @@ class ClxNotificationComponent {
|
|
|
11526
11562
|
|
|
11527
11563
|
<!-- Tabs -->
|
|
11528
11564
|
<div class="px-4">
|
|
11529
|
-
<clx-tabs [tabs]="_tabs()" [(activeTab)]="_filter" [clxColor]="
|
|
11565
|
+
<clx-tabs [tabs]="_tabs()" [(activeTab)]="_filter" [clxColor]="_tabsColor()" />
|
|
11530
11566
|
</div>
|
|
11531
11567
|
|
|
11532
11568
|
<!-- List -->
|
|
@@ -11585,7 +11621,7 @@ class ClxNotificationComponent {
|
|
|
11585
11621
|
}
|
|
11586
11622
|
</div>
|
|
11587
11623
|
</ng-template>
|
|
11588
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { 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: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "heading", "description", "ctaLabel", "ctaIcon", "ctaColor"], outputs: ["cta"] }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTab"], outputs: ["activeTabChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
11624
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { 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: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "heading", "description", "ctaLabel", "ctaIcon", "ctaColor"], outputs: ["cta"] }, { kind: "component", type: ClxTabsComponent, selector: "clx-tabs", inputs: ["tabs", "clxColor", "activeTextColor", "underlineColor", "activeTab"], outputs: ["activeTabChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
11589
11625
|
}
|
|
11590
11626
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxNotificationComponent, decorators: [{
|
|
11591
11627
|
type: Component,
|
|
@@ -11599,11 +11635,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
11599
11635
|
#origin="cdkOverlayOrigin"
|
|
11600
11636
|
cdkOverlayOrigin
|
|
11601
11637
|
type="button"
|
|
11602
|
-
[variant]="
|
|
11603
|
-
[color]="
|
|
11604
|
-
[size]="
|
|
11638
|
+
[variant]="buttonVariant()"
|
|
11639
|
+
[color]="_buttonColor()"
|
|
11640
|
+
[size]="buttonSize()"
|
|
11605
11641
|
shape="circle"
|
|
11606
|
-
[icon]="
|
|
11642
|
+
[icon]="buttonIcon()"
|
|
11607
11643
|
[iconOnly]="true"
|
|
11608
11644
|
[badge]="_unreadCount() > 0 ? (_unreadCount() > 9 ? '9+' : _unreadCount()) : undefined"
|
|
11609
11645
|
[badgeColor]="_badgeColor()"
|
|
@@ -11628,7 +11664,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
11628
11664
|
<p class="text-base font-semibold text-clx-text-label">Notificaciones</p>
|
|
11629
11665
|
<div class="flex items-center gap-3">
|
|
11630
11666
|
@if (_unreadCount() > 0) {
|
|
11631
|
-
<button clx-button type="button" variant="ghost" [color]="
|
|
11667
|
+
<button clx-button type="button" variant="ghost" [color]="_tabsColor()" size="xs"
|
|
11632
11668
|
(click)="markAllRead.emit()">
|
|
11633
11669
|
Marcar todo leído
|
|
11634
11670
|
</button>
|
|
@@ -11640,7 +11676,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
11640
11676
|
|
|
11641
11677
|
<!-- Tabs -->
|
|
11642
11678
|
<div class="px-4">
|
|
11643
|
-
<clx-tabs [tabs]="_tabs()" [(activeTab)]="_filter" [clxColor]="
|
|
11679
|
+
<clx-tabs [tabs]="_tabs()" [(activeTab)]="_filter" [clxColor]="_tabsColor()" />
|
|
11644
11680
|
</div>
|
|
11645
11681
|
|
|
11646
11682
|
<!-- List -->
|
|
@@ -11704,7 +11740,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
|
|
|
11704
11740
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11705
11741
|
host: { class: 'relative inline-flex items-center' },
|
|
11706
11742
|
}]
|
|
11707
|
-
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }],
|
|
11743
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], categoryTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "categoryTab", required: false }] }], buttonColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonColor", required: false }] }], buttonVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonVariant", required: false }] }], buttonSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonSize", required: false }] }], buttonIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonIcon", required: false }] }], badgeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeColor", required: false }] }], tabsColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabsColor", required: false }] }], border: [{ type: i0.Input, args: [{ isSignal: true, alias: "border", required: false }] }], borderColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderColor", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], markRead: [{ type: i0.Output, args: ["markRead"] }], markAllRead: [{ type: i0.Output, args: ["markAllRead"] }], delete: [{ type: i0.Output, args: ["delete"] }], deleteAllRead: [{ type: i0.Output, args: ["deleteAllRead"] }] } });
|
|
11708
11744
|
|
|
11709
11745
|
// ─── CDK overlay token (carries the resolved ref into the component) ──────────
|
|
11710
11746
|
const CLX_ALERT_OPTIONS = new InjectionToken('CLX_ALERT_OPTIONS');
|
|
@@ -15679,7 +15715,7 @@ class ClxProductDetailComponent {
|
|
|
15679
15715
|
specsLeft(specs) { return specs.slice(0, Math.ceil(specs.length / 2)); }
|
|
15680
15716
|
specsRight(specs) { return specs.slice(Math.ceil(specs.length / 2)); }
|
|
15681
15717
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15682
|
-
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 }, actionColor: { classPropertyName: "actionColor", publicName: "actionColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistColor: { classPropertyName: "wishlistColor", publicName: "wishlistColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistVariant: { classPropertyName: "wishlistVariant", publicName: "wishlistVariant", isSignal: true, isRequired: false, transformFunction: null }, stockBadgeColor: { classPropertyName: "stockBadgeColor", publicName: "stockBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, discountBadgeColor: { classPropertyName: "discountBadgeColor", publicName: "discountBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, specButtonColor: { classPropertyName: "specButtonColor", publicName: "specButtonColor", isSignal: true, isRequired: false, transformFunction: null }, quantityColor: { classPropertyName: "quantityColor", publicName: "quantityColor", 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]=\"_panelClass + ' flex flex-col gap-3'\">\n <div class=\"relative aspect-4/3\">\n <div [class]=\"v && !v.in_stock ? 'opacity-40 grayscale' : ''\">\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>\n @if (v && !v.in_stock) {\n <div class=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n <span clx-badge variant=\"solid\" color=\"slate\" size=\"md\" shape=\"pill\">Agotado</span>\n </div>\n }\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" [variant]=\"wishlistVariant()\" [color]=\"_wishlistColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"_wishlistColor()\" 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]=\"_panelClass + ' space-y-2.5'\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge variant=\"solid\" [color]=\"v.in_stock ? _stockBadgeColor() : 'red'\" 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]=\"_discountBadgeColor()\" 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]=\"_specButtonColor()\" 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]=\"_quantityColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n @if (v?.in_stock && !isSelectionComplete()) {\n <p class=\"text-sm text-red-500\">Selecciona {{ missingSpecNames().join(' y ') }} para continuar.</p>\n }\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_actionColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"_actionColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (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]=\"_actionColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\">\n Leave Us a Review\n </button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"_actionColor()\" 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", "wrapSublabel", "icon", "meta", "metaColor", "disabled"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
15718
|
+
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 }, actionColor: { classPropertyName: "actionColor", publicName: "actionColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistColor: { classPropertyName: "wishlistColor", publicName: "wishlistColor", isSignal: true, isRequired: false, transformFunction: null }, wishlistVariant: { classPropertyName: "wishlistVariant", publicName: "wishlistVariant", isSignal: true, isRequired: false, transformFunction: null }, stockBadgeColor: { classPropertyName: "stockBadgeColor", publicName: "stockBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, discountBadgeColor: { classPropertyName: "discountBadgeColor", publicName: "discountBadgeColor", isSignal: true, isRequired: false, transformFunction: null }, specButtonColor: { classPropertyName: "specButtonColor", publicName: "specButtonColor", isSignal: true, isRequired: false, transformFunction: null }, quantityColor: { classPropertyName: "quantityColor", publicName: "quantityColor", 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]=\"_panelClass + ' flex flex-col gap-3'\">\n <div class=\"relative aspect-4/3\">\n <div [class]=\"v && !v.in_stock ? 'opacity-40 grayscale' : ''\">\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>\n @if (v && !v.in_stock) {\n <div class=\"absolute inset-0 flex items-center justify-center pointer-events-none z-10\">\n <span clx-badge variant=\"solid\" color=\"slate\" size=\"md\" shape=\"pill\">Agotado</span>\n </div>\n }\n <div class=\"absolute top-3 right-3 z-10\">\n <button clx-button type=\"button\" [variant]=\"wishlistVariant()\" [color]=\"_wishlistColor()\" shape=\"circle\" size=\"md\"\n [iconOnly]=\"true\" clxTooltip=\"Agregar a favoritos\" [clxTooltipColor]=\"_wishlistColor()\" 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]=\"_panelClass + ' space-y-2.5'\">\n\n <!-- Stock status -->\n @if (v) {\n <span clx-badge variant=\"solid\" [color]=\"v.in_stock ? _stockBadgeColor() : 'red'\" 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]=\"_discountBadgeColor()\" 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]=\"_specButtonColor()\" 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]=\"_quantityColor()\"\n [min]=\"1\" [max]=\"99\" [ngModel]=\"quantity()\" (ngModelChange)=\"quantity.set($event)\">\n </clx-number>\n\n <!-- CTA -->\n @if (v?.in_stock && !isSelectionComplete()) {\n <p class=\"text-sm text-red-500\">Selecciona {{ missingSpecNames().join(' y ') }} para continuar.</p>\n }\n <div class=\"flex gap-3\">\n <button clx-button type=\"button\" variant=\"solid\" [color]=\"_actionColor()\" size=\"md\" [block]=\"true\" icon=\"bolt\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (click)=\"_emitBuyNow()\">Comprar ahora</button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"_actionColor()\" size=\"md\" [block]=\"true\" icon=\"shopping_cart\"\n [disabled]=\"!v?.in_stock || !isSelectionComplete()\" (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]=\"_actionColor()\" size=\"md\" [block]=\"true\" icon=\"rate_review\">\n Leave Us a Review\n </button>\n <button clx-button type=\"button\" variant=\"light\" [color]=\"_actionColor()\" 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", "activeTextColor", "underlineColor", "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", "wrapSublabel", "icon", "meta", "metaColor", "disabled"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
15683
15719
|
}
|
|
15684
15720
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxProductDetailComponent, decorators: [{
|
|
15685
15721
|
type: Component,
|