@c-code/c-code-fw 1.2.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/fesm2022/c-code-c-code-fw-ui.mjs +1260 -0
- package/fesm2022/c-code-c-code-fw-ui.mjs.map +1 -0
- package/package.json +9 -2
- package/ui/index.d.ts +5 -0
- package/ui/lib/components/button/button.component.d.ts +24 -0
- package/ui/lib/components/category-menu/category-menu.component.d.ts +28 -0
- package/ui/lib/components/faq/faq.component.d.ts +23 -0
- package/ui/lib/components/faq-item/faq-item.component.d.ts +21 -0
- package/ui/lib/components/gallery/gallery.component.d.ts +31 -0
- package/ui/lib/components/info-item/info-item.component.d.ts +26 -0
- package/ui/lib/components/lightbox/lightbox.component.d.ts +41 -0
- package/ui/lib/components/notice/notice.component.d.ts +17 -0
- package/ui/lib/components/page-banner/page-banner.component.d.ts +29 -0
- package/ui/lib/components/plan-card/plan-card.component.d.ts +46 -0
- package/ui/lib/components/plan-catalog/plan-catalog.component.d.ts +102 -0
- package/ui/lib/components/plan-details/plan-details.component.d.ts +59 -0
- package/ui/lib/components/plan-filter-form/plan-filter-form.component.d.ts +38 -0
- package/ui/lib/components/promo-modal/promo-modal.component.d.ts +56 -0
- package/ui/lib/components/search-box/search-box.component.d.ts +23 -0
- package/ui/lib/components/section-heading/section-heading.component.d.ts +34 -0
- package/ui/lib/components/social-links/social-links.component.d.ts +21 -0
- package/ui/lib/components/whatsapp-button/whatsapp-button.component.d.ts +29 -0
- package/ui/lib/models/plan.models.d.ts +50 -0
- package/ui/lib/models/ui.models.d.ts +24 -0
- package/ui/lib/services/plan-catalog.service.d.ts +43 -0
- package/ui/lib/services/seo.service.d.ts +39 -0
- package/ui/lib/utils/plan.utils.d.ts +16 -0
- package/ui/lib/utils/text.utils.d.ts +25 -0
- package/ui/public-api.d.ts +24 -0
- package/ui/theme/component-base.css +136 -0
- package/ui/theme/tokens.css +111 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { GalleryImage } from '../../models/ui.models';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Full-screen image viewer. It is open while `index` is not null.
|
|
5
|
+
* Arrow keys and swipe move between images; Escape and the backdrop close it.
|
|
6
|
+
* Focus moves into the viewer on open, stays inside while open, and returns to the
|
|
7
|
+
* element that opened it on close.
|
|
8
|
+
*
|
|
9
|
+
* ```html
|
|
10
|
+
* <cc-lightbox [images]="images" [(index)]="openIndex" />
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Tokens: --cc-lightbox-backdrop, --cc-lightbox-control-bg, --cc-lightbox-color.
|
|
14
|
+
*/
|
|
15
|
+
export declare class LightboxComponent {
|
|
16
|
+
readonly images: import("@angular/core").InputSignal<GalleryImage[]>;
|
|
17
|
+
/** Index of the open image, or null when closed. Use `[(index)]`. */
|
|
18
|
+
readonly index: import("@angular/core").ModelSignal<number | null>;
|
|
19
|
+
readonly closeLabel: import("@angular/core").InputSignal<string>;
|
|
20
|
+
readonly prevLabel: import("@angular/core").InputSignal<string>;
|
|
21
|
+
readonly nextLabel: import("@angular/core").InputSignal<string>;
|
|
22
|
+
/** Text of the position counter, for example "3 de 18". */
|
|
23
|
+
readonly counterSeparator: import("@angular/core").InputSignal<string>;
|
|
24
|
+
protected readonly current: import("@angular/core").Signal<GalleryImage | null>;
|
|
25
|
+
/** Changes only on open/close, not when moving between images. */
|
|
26
|
+
private readonly isOpen;
|
|
27
|
+
private readonly document;
|
|
28
|
+
private readonly dialog;
|
|
29
|
+
private readonly closeButton;
|
|
30
|
+
private touchStartX;
|
|
31
|
+
constructor();
|
|
32
|
+
close(): void;
|
|
33
|
+
next(): void;
|
|
34
|
+
prev(): void;
|
|
35
|
+
/** Keeps Tab and Shift+Tab inside the viewer. */
|
|
36
|
+
protected trapFocus(event: KeyboardEvent): void;
|
|
37
|
+
protected onTouchStart(event: TouchEvent): void;
|
|
38
|
+
protected onTouchEnd(event: TouchEvent): void;
|
|
39
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LightboxComponent, never>;
|
|
40
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LightboxComponent, "cc-lightbox", never, { "images": { "alias": "images"; "required": true; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; "prevLabel": { "alias": "prevLabel"; "required": false; "isSignal": true; }; "nextLabel": { "alias": "nextLabel"; "required": false; "isSignal": true; }; "counterSeparator": { "alias": "counterSeparator"; "required": false; "isSignal": true; }; }, { "index": "indexChange"; }, never, never, true, never>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* Highlighted note for important conditions, warnings or tips. The content is projected.
|
|
4
|
+
*
|
|
5
|
+
* ```html
|
|
6
|
+
* <cc-notice tone="warning" title="Importante">Su bono tiene vigencia de 30 días.</cc-notice>
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Tokens: --cc-notice-bg, --cc-notice-border, --cc-notice-color.
|
|
10
|
+
*/
|
|
11
|
+
export declare class NoticeComponent {
|
|
12
|
+
/** `info` uses the brand colors, `warning` a red accent, `success` the primary color. */
|
|
13
|
+
readonly tone: import("@angular/core").InputSignal<"info" | "warning" | "success">;
|
|
14
|
+
readonly title: import("@angular/core").InputSignal<string>;
|
|
15
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NoticeComponent, never>;
|
|
16
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<NoticeComponent, "cc-notice", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LinkTarget } from '../../models/ui.models';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Title strip at the top of a page, with an optional back link and icon.
|
|
5
|
+
* Renders the page's `<h1>`.
|
|
6
|
+
*
|
|
7
|
+
* ```html
|
|
8
|
+
* <cc-page-banner title="Reserva tu plan - Pareja" />
|
|
9
|
+
* <cc-page-banner [title]="plan.name" backLink="/planes" iconSrc="assets/icons/hoja.png" size="lg" italic />
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Tokens: --cc-banner-bg, --cc-banner-color, --cc-banner-padding.
|
|
13
|
+
*/
|
|
14
|
+
export declare class PageBannerComponent {
|
|
15
|
+
readonly title: import("@angular/core").InputSignal<string>;
|
|
16
|
+
readonly subtitle: import("@angular/core").InputSignal<string>;
|
|
17
|
+
/** `band` draws a surface-colored strip; `plain` has no background. */
|
|
18
|
+
readonly variant: import("@angular/core").InputSignal<"band" | "plain">;
|
|
19
|
+
readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg">;
|
|
20
|
+
readonly italic: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
|
|
21
|
+
readonly align: import("@angular/core").InputSignal<"start" | "center">;
|
|
22
|
+
readonly iconSrc: import("@angular/core").InputSignal<string>;
|
|
23
|
+
/** Shows a back arrow that links here. */
|
|
24
|
+
readonly backLink: import("@angular/core").InputSignal<LinkTarget | null>;
|
|
25
|
+
readonly backIconSrc: import("@angular/core").InputSignal<string>;
|
|
26
|
+
readonly backLabel: import("@angular/core").InputSignal<string>;
|
|
27
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PageBannerComponent, never>;
|
|
28
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PageBannerComponent, "cc-page-banner", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "italic": { "alias": "italic"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "backLink": { "alias": "backLink"; "required": false; "isSignal": true; }; "backIconSrc": { "alias": "backIconSrc"; "required": false; "isSignal": true; }; "backLabel": { "alias": "backLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { LinkTarget } from '../../models/ui.models';
|
|
2
|
+
import { PriceFormat } from '../../utils/text.utils';
|
|
3
|
+
import { ButtonVariant } from '../button/button.component';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* Plan card: photo, name, details, price and a call to action.
|
|
7
|
+
* With `link`, the whole card is clickable and navigates with the router; without it,
|
|
8
|
+
* the button emits `ctaClick`. With `skeleton`, it renders a loading placeholder.
|
|
9
|
+
*
|
|
10
|
+
* ```html
|
|
11
|
+
* <cc-plan-card [name]="plan.name" [imageSrc]="plan.imgPath" [price]="plan.price"
|
|
12
|
+
* meta="2 h · 2 personas" [link]="['/planes', slug]" />
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Tokens: --cc-plan-card-bg, --cc-plan-card-color, --cc-plan-card-border,
|
|
16
|
+
* --cc-plan-card-radius, --cc-plan-card-shadow, --cc-plan-card-image-ratio,
|
|
17
|
+
* --cc-plan-card-price-color.
|
|
18
|
+
*/
|
|
19
|
+
export declare class PlanCardComponent {
|
|
20
|
+
readonly name: import("@angular/core").InputSignal<string>;
|
|
21
|
+
readonly imageSrc: import("@angular/core").InputSignal<string>;
|
|
22
|
+
/** Defaults to `name`. */
|
|
23
|
+
readonly imageAlt: import("@angular/core").InputSignal<string>;
|
|
24
|
+
/** Short line under the name, for example "2 h 20 min · 2 personas". */
|
|
25
|
+
readonly meta: import("@angular/core").InputSignal<string>;
|
|
26
|
+
/** Price to show. Leave it null to hide it. */
|
|
27
|
+
readonly price: import("@angular/core").InputSignal<number | null>;
|
|
28
|
+
/** Text before the price, for example "Desde". */
|
|
29
|
+
readonly priceLabel: import("@angular/core").InputSignal<string>;
|
|
30
|
+
readonly priceFormat: import("@angular/core").InputSignal<PriceFormat>;
|
|
31
|
+
readonly link: import("@angular/core").InputSignal<LinkTarget | null>;
|
|
32
|
+
/** Query params of `link`, for example `{ categoria: 'pareja' }`. */
|
|
33
|
+
readonly queryParams: import("@angular/core").InputSignal<Record<string, string> | null>;
|
|
34
|
+
readonly ctaLabel: import("@angular/core").InputSignal<string>;
|
|
35
|
+
readonly ctaVariant: import("@angular/core").InputSignal<ButtonVariant>;
|
|
36
|
+
/** `filled` uses the surface color, `outlined` a border on white, `plain` no box. */
|
|
37
|
+
readonly appearance: import("@angular/core").InputSignal<"plain" | "filled" | "outlined">;
|
|
38
|
+
/** Heading level of the name inside the page outline. */
|
|
39
|
+
readonly headingLevel: import("@angular/core").InputSignal<"h2" | "h3">;
|
|
40
|
+
/** Renders a loading placeholder instead of the content. */
|
|
41
|
+
readonly skeleton: import("@angular/core").InputSignal<boolean>;
|
|
42
|
+
readonly ctaClick: import("@angular/core").OutputEmitterRef<void>;
|
|
43
|
+
protected readonly formattedPrice: import("@angular/core").Signal<string>;
|
|
44
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PlanCardComponent, never>;
|
|
45
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PlanCardComponent, "cc-plan-card", never, { "name": { "alias": "name"; "required": false; "isSignal": true; }; "imageSrc": { "alias": "imageSrc"; "required": false; "isSignal": true; }; "imageAlt": { "alias": "imageAlt"; "required": false; "isSignal": true; }; "meta": { "alias": "meta"; "required": false; "isSignal": true; }; "price": { "alias": "price"; "required": false; "isSignal": true; }; "priceLabel": { "alias": "priceLabel"; "required": false; "isSignal": true; }; "priceFormat": { "alias": "priceFormat"; "required": false; "isSignal": true; }; "link": { "alias": "link"; "required": false; "isSignal": true; }; "queryParams": { "alias": "queryParams"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; "ctaVariant": { "alias": "ctaVariant"; "required": false; "isSignal": true; }; "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "skeleton": { "alias": "skeleton"; "required": false; "isSignal": true; }; }, { "ctaClick": "ctaClick"; }, never, never, true, never>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { TemplateRef } from '@angular/core';
|
|
2
|
+
import { AdditionalService, CategoryOption, Plan, PlanCategory, PlanFilter, PriceRange } from '../../models/plan.models';
|
|
3
|
+
import { LinkTarget } from '../../models/ui.models';
|
|
4
|
+
import { PriceFormat } from '../../utils/text.utils';
|
|
5
|
+
import { ButtonVariant } from '../button/button.component';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
/** Context of a custom card template: `<ng-template ccPlanCard let-plan let-link="link">`. */
|
|
8
|
+
export interface PlanCardContext {
|
|
9
|
+
$implicit: Plan;
|
|
10
|
+
link: LinkTarget;
|
|
11
|
+
}
|
|
12
|
+
/** Marks an `<ng-template>` inside `cc-plan-catalog` as the card to render for each plan. */
|
|
13
|
+
export declare class PlanCardTemplateDirective {
|
|
14
|
+
static ngTemplateContextGuard(_dir: PlanCardTemplateDirective, ctx: unknown): ctx is PlanCardContext;
|
|
15
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PlanCardTemplateDirective, never>;
|
|
16
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<PlanCardTemplateDirective, "ng-template[ccPlanCard]", never, {}, {}, never, never, true, never>;
|
|
17
|
+
}
|
|
18
|
+
/** Default detail line of a card: "2 horas 20 minutos · 2 personas". */
|
|
19
|
+
export declare function defaultPlanMeta(plan: Plan): string;
|
|
20
|
+
/**
|
|
21
|
+
* Full plan list page: title, name search, category menu, filter and card grid.
|
|
22
|
+
* It only needs the plans; the rest has Spanish defaults you can override.
|
|
23
|
+
*
|
|
24
|
+
* - `plans` set to `null` means "loading" and shows placeholder cards.
|
|
25
|
+
* - `[(category)]` is two-way, so a page can keep it in the URL (`?categoria=pareja`).
|
|
26
|
+
* - Selecting a category shows its plans. Searching by name or filtering looks across
|
|
27
|
+
* every category; clearing the search goes back to the selected category.
|
|
28
|
+
*
|
|
29
|
+
* ```html
|
|
30
|
+
* <cc-plan-catalog [plans]="plans()" [services]="services()" [priceRanges]="ranges()" [(category)]="category" />
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Tokens: --cc-catalog-panel-bg, --cc-catalog-sidebar-width, --cc-catalog-column-min,
|
|
34
|
+
* --cc-catalog-gap.
|
|
35
|
+
*/
|
|
36
|
+
export declare class PlanCatalogComponent {
|
|
37
|
+
/** The plans, or `null` while they load. */
|
|
38
|
+
readonly plans: import("@angular/core").InputSignal<Plan[] | null>;
|
|
39
|
+
readonly categories: import("@angular/core").InputSignal<CategoryOption<number>[]>;
|
|
40
|
+
/** Selected category. Use `[(category)]` to bind it; `null` means `initialCategory`. */
|
|
41
|
+
readonly category: import("@angular/core").ModelSignal<number | null>;
|
|
42
|
+
readonly initialCategory: import("@angular/core").InputSignal<number>;
|
|
43
|
+
/** Shows how many plans each category has, counted from `plans`. */
|
|
44
|
+
readonly showCounts: import("@angular/core").InputSignal<boolean>;
|
|
45
|
+
/** Options for the filter form. The filter is hidden when both are empty. */
|
|
46
|
+
readonly services: import("@angular/core").InputSignal<AdditionalService[]>;
|
|
47
|
+
readonly priceRanges: import("@angular/core").InputSignal<PriceRange[]>;
|
|
48
|
+
/** Where each card links to. Defaults to `/planes/<slug>`. */
|
|
49
|
+
readonly detailsLink: import("@angular/core").InputSignal<(plan: Plan) => LinkTarget>;
|
|
50
|
+
readonly showPrice: import("@angular/core").InputSignal<boolean>;
|
|
51
|
+
readonly priceLabel: import("@angular/core").InputSignal<string>;
|
|
52
|
+
readonly priceFormat: import("@angular/core").InputSignal<PriceFormat>;
|
|
53
|
+
/** Detail line of each card. Defaults to duration and number of people. */
|
|
54
|
+
readonly planMeta: import("@angular/core").InputSignal<(plan: Plan) => string>;
|
|
55
|
+
readonly cardAppearance: import("@angular/core").InputSignal<"plain" | "filled" | "outlined">;
|
|
56
|
+
readonly ctaLabel: import("@angular/core").InputSignal<string>;
|
|
57
|
+
readonly ctaVariant: import("@angular/core").InputSignal<ButtonVariant>;
|
|
58
|
+
/** Added after the plan name in each image's alt text. */
|
|
59
|
+
readonly imageAltSuffix: import("@angular/core").InputSignal<string>;
|
|
60
|
+
readonly skeletonCount: import("@angular/core").InputSignal<number>;
|
|
61
|
+
readonly showBanner: import("@angular/core").InputSignal<boolean>;
|
|
62
|
+
readonly showSearch: import("@angular/core").InputSignal<boolean>;
|
|
63
|
+
readonly titlePrefix: import("@angular/core").InputSignal<string>;
|
|
64
|
+
readonly resultsTitle: import("@angular/core").InputSignal<string>;
|
|
65
|
+
readonly categoriesTitle: import("@angular/core").InputSignal<string>;
|
|
66
|
+
readonly categoriesLayout: import("@angular/core").InputSignal<"auto" | "list" | "chips">;
|
|
67
|
+
readonly searchPlaceholder: import("@angular/core").InputSignal<string>;
|
|
68
|
+
readonly searchIconSrc: import("@angular/core").InputSignal<string>;
|
|
69
|
+
readonly filterTitle: import("@angular/core").InputSignal<string>;
|
|
70
|
+
readonly filterIconSrc: import("@angular/core").InputSignal<string>;
|
|
71
|
+
readonly emptyMessage: import("@angular/core").InputSignal<string>;
|
|
72
|
+
readonly emptyActionLabel: import("@angular/core").InputSignal<string>;
|
|
73
|
+
/** Optional contact link shown in the empty state, for example WhatsApp. */
|
|
74
|
+
readonly contactHref: import("@angular/core").InputSignal<string>;
|
|
75
|
+
readonly contactLabel: import("@angular/core").InputSignal<string>;
|
|
76
|
+
protected readonly cardTemplate: import("@angular/core").Signal<TemplateRef<any> | undefined>;
|
|
77
|
+
private readonly filterForm;
|
|
78
|
+
protected readonly query: import("@angular/core").WritableSignal<string>;
|
|
79
|
+
protected readonly filter: import("@angular/core").WritableSignal<PlanFilter | null>;
|
|
80
|
+
protected readonly filterOpen: import("@angular/core").WritableSignal<boolean>;
|
|
81
|
+
/** Set when the user searched or filtered; results then cover every category. */
|
|
82
|
+
protected readonly searching: import("@angular/core").WritableSignal<boolean>;
|
|
83
|
+
protected readonly loading: import("@angular/core").Signal<boolean>;
|
|
84
|
+
protected readonly skeletons: import("@angular/core").Signal<number[]>;
|
|
85
|
+
/** Category shown in the menu: none while searching. */
|
|
86
|
+
protected readonly activeCategory: import("@angular/core").Signal<number | null>;
|
|
87
|
+
protected readonly categoryOptions: import("@angular/core").Signal<CategoryOption<number>[]>;
|
|
88
|
+
protected readonly hasFilter: import("@angular/core").Signal<boolean>;
|
|
89
|
+
protected readonly visiblePlans: import("@angular/core").Signal<Plan[]>;
|
|
90
|
+
protected readonly title: import("@angular/core").Signal<string>;
|
|
91
|
+
protected selectCategory(value: PlanCategory | number | null): void;
|
|
92
|
+
protected onSearch(query: string): void;
|
|
93
|
+
protected onFilter(filter: PlanFilter): void;
|
|
94
|
+
/** Clears search and filter and shows the selected category again. */
|
|
95
|
+
protected showAll(): void;
|
|
96
|
+
protected linkFor(plan: Plan): LinkTarget;
|
|
97
|
+
protected metaFor(plan: Plan): string;
|
|
98
|
+
private clearSearch;
|
|
99
|
+
private updateSearching;
|
|
100
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PlanCatalogComponent, never>;
|
|
101
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PlanCatalogComponent, "cc-plan-catalog", never, { "plans": { "alias": "plans"; "required": true; "isSignal": true; }; "categories": { "alias": "categories"; "required": false; "isSignal": true; }; "category": { "alias": "category"; "required": false; "isSignal": true; }; "initialCategory": { "alias": "initialCategory"; "required": false; "isSignal": true; }; "showCounts": { "alias": "showCounts"; "required": false; "isSignal": true; }; "services": { "alias": "services"; "required": false; "isSignal": true; }; "priceRanges": { "alias": "priceRanges"; "required": false; "isSignal": true; }; "detailsLink": { "alias": "detailsLink"; "required": false; "isSignal": true; }; "showPrice": { "alias": "showPrice"; "required": false; "isSignal": true; }; "priceLabel": { "alias": "priceLabel"; "required": false; "isSignal": true; }; "priceFormat": { "alias": "priceFormat"; "required": false; "isSignal": true; }; "planMeta": { "alias": "planMeta"; "required": false; "isSignal": true; }; "cardAppearance": { "alias": "cardAppearance"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; "ctaVariant": { "alias": "ctaVariant"; "required": false; "isSignal": true; }; "imageAltSuffix": { "alias": "imageAltSuffix"; "required": false; "isSignal": true; }; "skeletonCount": { "alias": "skeletonCount"; "required": false; "isSignal": true; }; "showBanner": { "alias": "showBanner"; "required": false; "isSignal": true; }; "showSearch": { "alias": "showSearch"; "required": false; "isSignal": true; }; "titlePrefix": { "alias": "titlePrefix"; "required": false; "isSignal": true; }; "resultsTitle": { "alias": "resultsTitle"; "required": false; "isSignal": true; }; "categoriesTitle": { "alias": "categoriesTitle"; "required": false; "isSignal": true; }; "categoriesLayout": { "alias": "categoriesLayout"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "searchIconSrc": { "alias": "searchIconSrc"; "required": false; "isSignal": true; }; "filterTitle": { "alias": "filterTitle"; "required": false; "isSignal": true; }; "filterIconSrc": { "alias": "filterIconSrc"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "emptyActionLabel": { "alias": "emptyActionLabel"; "required": false; "isSignal": true; }; "contactHref": { "alias": "contactHref"; "required": false; "isSignal": true; }; "contactLabel": { "alias": "contactLabel"; "required": false; "isSignal": true; }; }, { "category": "categoryChange"; }, ["cardTemplate"], never, true, never>;
|
|
102
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Plan } from '../../models/plan.models';
|
|
2
|
+
import { LinkTarget } from '../../models/ui.models';
|
|
3
|
+
import { PriceFormat } from '../../utils/text.utils';
|
|
4
|
+
import { ButtonVariant } from '../button/button.component';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
/**
|
|
7
|
+
* Plan detail view: title with a back link, photo, price with an early booking button,
|
|
8
|
+
* description, duration, number of people, included services and a gift box.
|
|
9
|
+
* On screens below 1024px a bar with the price and the booking button stays fixed at
|
|
10
|
+
* the bottom while the plan is on screen. It only renders; the page loads the plan and
|
|
11
|
+
* handles SEO.
|
|
12
|
+
*
|
|
13
|
+
* ```html
|
|
14
|
+
* <cc-plan-details [plan]="plan" [bookingUrl]="bookingFor" imageAltSuffix=" en Laurel Spa" />
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Tokens: --cc-details-panel-bg, --cc-details-price-bg, --cc-details-price-color,
|
|
18
|
+
* --cc-details-gift-border, --cc-details-bar-bg, --cc-details-image-ratio.
|
|
19
|
+
*/
|
|
20
|
+
export declare class PlanDetailsComponent {
|
|
21
|
+
readonly plan: import("@angular/core").InputSignal<Plan>;
|
|
22
|
+
/** Booking link, usually WhatsApp. A function receives the plan, to mention it in the message. */
|
|
23
|
+
readonly bookingUrl: import("@angular/core").InputSignal<string | ((plan: Plan) => string)>;
|
|
24
|
+
readonly bookingLabel: import("@angular/core").InputSignal<string>;
|
|
25
|
+
/** Shorter label for the bottom bar on small screens. */
|
|
26
|
+
readonly barBookingLabel: import("@angular/core").InputSignal<string>;
|
|
27
|
+
readonly bookingVariant: import("@angular/core").InputSignal<ButtonVariant>;
|
|
28
|
+
/** Keeps a bar with price and booking button at the bottom on small screens. */
|
|
29
|
+
readonly stickyBar: import("@angular/core").InputSignal<boolean>;
|
|
30
|
+
readonly showBanner: import("@angular/core").InputSignal<boolean>;
|
|
31
|
+
readonly backLink: import("@angular/core").InputSignal<LinkTarget | null>;
|
|
32
|
+
readonly backLabel: import("@angular/core").InputSignal<string>;
|
|
33
|
+
readonly backIconSrc: import("@angular/core").InputSignal<string>;
|
|
34
|
+
readonly titleIconSrc: import("@angular/core").InputSignal<string>;
|
|
35
|
+
/** Added after the plan name in the photo's alt text. */
|
|
36
|
+
readonly imageAltSuffix: import("@angular/core").InputSignal<string>;
|
|
37
|
+
readonly priceFormat: import("@angular/core").InputSignal<PriceFormat>;
|
|
38
|
+
/** Small text under the price. Defaults to "Precio por plan". */
|
|
39
|
+
readonly priceNote: import("@angular/core").InputSignal<string>;
|
|
40
|
+
readonly durationIconSrc: import("@angular/core").InputSignal<string>;
|
|
41
|
+
readonly personIconSrc: import("@angular/core").InputSignal<string>;
|
|
42
|
+
readonly peopleIconSrc: import("@angular/core").InputSignal<string>;
|
|
43
|
+
readonly durationLabel: import("@angular/core").InputSignal<string>;
|
|
44
|
+
readonly peopleCaption: import("@angular/core").InputSignal<string>;
|
|
45
|
+
readonly personLabel: import("@angular/core").InputSignal<string>;
|
|
46
|
+
readonly peopleLabel: import("@angular/core").InputSignal<string>;
|
|
47
|
+
readonly includesLabel: import("@angular/core").InputSignal<string>;
|
|
48
|
+
/** Gift box. It is hidden when `perks` is empty. */
|
|
49
|
+
readonly perksTitle: import("@angular/core").InputSignal<string>;
|
|
50
|
+
readonly perks: import("@angular/core").InputSignal<string[]>;
|
|
51
|
+
readonly perksImageSrc: import("@angular/core").InputSignal<string>;
|
|
52
|
+
readonly perksImageAlt: import("@angular/core").InputSignal<string>;
|
|
53
|
+
protected readonly people: import("@angular/core").Signal<number>;
|
|
54
|
+
protected readonly services: import("@angular/core").Signal<import("@c-code/c-code-fw/ui").AdditionalService[]>;
|
|
55
|
+
protected readonly price: import("@angular/core").Signal<string>;
|
|
56
|
+
protected readonly booking: import("@angular/core").Signal<string>;
|
|
57
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PlanDetailsComponent, never>;
|
|
58
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PlanDetailsComponent, "cc-plan-details", never, { "plan": { "alias": "plan"; "required": true; "isSignal": true; }; "bookingUrl": { "alias": "bookingUrl"; "required": true; "isSignal": true; }; "bookingLabel": { "alias": "bookingLabel"; "required": false; "isSignal": true; }; "barBookingLabel": { "alias": "barBookingLabel"; "required": false; "isSignal": true; }; "bookingVariant": { "alias": "bookingVariant"; "required": false; "isSignal": true; }; "stickyBar": { "alias": "stickyBar"; "required": false; "isSignal": true; }; "showBanner": { "alias": "showBanner"; "required": false; "isSignal": true; }; "backLink": { "alias": "backLink"; "required": false; "isSignal": true; }; "backLabel": { "alias": "backLabel"; "required": false; "isSignal": true; }; "backIconSrc": { "alias": "backIconSrc"; "required": false; "isSignal": true; }; "titleIconSrc": { "alias": "titleIconSrc"; "required": false; "isSignal": true; }; "imageAltSuffix": { "alias": "imageAltSuffix"; "required": false; "isSignal": true; }; "priceFormat": { "alias": "priceFormat"; "required": false; "isSignal": true; }; "priceNote": { "alias": "priceNote"; "required": false; "isSignal": true; }; "durationIconSrc": { "alias": "durationIconSrc"; "required": false; "isSignal": true; }; "personIconSrc": { "alias": "personIconSrc"; "required": false; "isSignal": true; }; "peopleIconSrc": { "alias": "peopleIconSrc"; "required": false; "isSignal": true; }; "durationLabel": { "alias": "durationLabel"; "required": false; "isSignal": true; }; "peopleCaption": { "alias": "peopleCaption"; "required": false; "isSignal": true; }; "personLabel": { "alias": "personLabel"; "required": false; "isSignal": true; }; "peopleLabel": { "alias": "peopleLabel"; "required": false; "isSignal": true; }; "includesLabel": { "alias": "includesLabel"; "required": false; "isSignal": true; }; "perksTitle": { "alias": "perksTitle"; "required": false; "isSignal": true; }; "perks": { "alias": "perks"; "required": false; "isSignal": true; }; "perksImageSrc": { "alias": "perksImageSrc"; "required": false; "isSignal": true; }; "perksImageAlt": { "alias": "perksImageAlt"; "required": false; "isSignal": true; }; }, {}, never, ["[ccDetailsMedia]", "*"], true, never>;
|
|
59
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AdditionalService, PlanFilter, PriceRange } from '../../models/plan.models';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Filter by included service and price range. The options come in as inputs,
|
|
5
|
+
* so the form does not load data by itself.
|
|
6
|
+
*
|
|
7
|
+
* `mode`:
|
|
8
|
+
* - `instant` (default): emits `filterChange` as soon as an option changes.
|
|
9
|
+
* - `submit`: emits when the user presses the submit button.
|
|
10
|
+
*
|
|
11
|
+
* ```html
|
|
12
|
+
* <cc-plan-filter-form [services]="services" [priceRanges]="ranges" (filterChange)="apply($event)" />
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Tokens: --cc-filter-field-bg, --cc-filter-field-border, --cc-filter-label-color.
|
|
16
|
+
*/
|
|
17
|
+
export declare class PlanFilterFormComponent {
|
|
18
|
+
readonly services: import("@angular/core").InputSignal<AdditionalService[]>;
|
|
19
|
+
readonly priceRanges: import("@angular/core").InputSignal<PriceRange[]>;
|
|
20
|
+
readonly mode: import("@angular/core").InputSignal<"instant" | "submit">;
|
|
21
|
+
readonly serviceLabel: import("@angular/core").InputSignal<string>;
|
|
22
|
+
readonly priceLabel: import("@angular/core").InputSignal<string>;
|
|
23
|
+
readonly anyLabel: import("@angular/core").InputSignal<string>;
|
|
24
|
+
readonly submitLabel: import("@angular/core").InputSignal<string>;
|
|
25
|
+
readonly resetLabel: import("@angular/core").InputSignal<string>;
|
|
26
|
+
/** Emits the current filter, and an empty filter on reset. */
|
|
27
|
+
readonly filterChange: import("@angular/core").OutputEmitterRef<PlanFilter>;
|
|
28
|
+
protected readonly serviceIndex: import("@angular/core").WritableSignal<number>;
|
|
29
|
+
protected readonly rangeIndex: import("@angular/core").WritableSignal<number>;
|
|
30
|
+
protected readonly hasSelection: import("@angular/core").Signal<boolean>;
|
|
31
|
+
protected onServiceChange(event: Event): void;
|
|
32
|
+
protected onRangeChange(event: Event): void;
|
|
33
|
+
submit(): void;
|
|
34
|
+
reset(): void;
|
|
35
|
+
private toIndex;
|
|
36
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PlanFilterFormComponent, never>;
|
|
37
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PlanFilterFormComponent, "cc-plan-filter-form", never, { "services": { "alias": "services"; "required": false; "isSignal": true; }; "priceRanges": { "alias": "priceRanges"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "serviceLabel": { "alias": "serviceLabel"; "required": false; "isSignal": true; }; "priceLabel": { "alias": "priceLabel"; "required": false; "isSignal": true; }; "anyLabel": { "alias": "anyLabel"; "required": false; "isSignal": true; }; "submitLabel": { "alias": "submitLabel"; "required": false; "isSignal": true; }; "resetLabel": { "alias": "resetLabel"; "required": false; "isSignal": true; }; }, { "filterChange": "filterChange"; }, never, never, true, never>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ButtonVariant } from '../button/button.component';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Modal over the page, for example a promotion. Shows `imageSrc`, or the projected
|
|
5
|
+
* content when there is no image, plus an optional call to action.
|
|
6
|
+
*
|
|
7
|
+
* It decides by itself when to open:
|
|
8
|
+
* - `autoOpen` opens it in the browser after `delayMs` (never during SSR, so it is not
|
|
9
|
+
* part of the prerendered page).
|
|
10
|
+
* - `rememberKey` stores the dismissal in localStorage for `rememberDays` days, so it
|
|
11
|
+
* does not reopen on every visit.
|
|
12
|
+
*
|
|
13
|
+
* Closes with the ✕ button, a backdrop click or Escape. Focus moves into the modal,
|
|
14
|
+
* stays inside while open and returns to the previous element on close.
|
|
15
|
+
*
|
|
16
|
+
* ```html
|
|
17
|
+
* <cc-promo-modal imageSrc="assets/images/pop.jpeg" imageAlt="Promoción" [delayMs]="6000"
|
|
18
|
+
* rememberKey="promo-octubre" ctaLabel="Reservar la promo" [ctaHref]="whatsapp" />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Tokens: --cc-modal-backdrop, --cc-modal-radius, --cc-modal-close-bg, --cc-modal-close-color.
|
|
22
|
+
*/
|
|
23
|
+
export declare class PromoModalComponent {
|
|
24
|
+
/** Whether the modal is visible. Use `[(open)]` to control it from outside. */
|
|
25
|
+
readonly open: import("@angular/core").ModelSignal<boolean>;
|
|
26
|
+
/** Opens the modal by itself in the browser after `delayMs`. */
|
|
27
|
+
readonly autoOpen: import("@angular/core").InputSignal<boolean>;
|
|
28
|
+
readonly delayMs: import("@angular/core").InputSignal<number>;
|
|
29
|
+
/** localStorage key to remember the dismissal. Change it for each new promotion. */
|
|
30
|
+
readonly rememberKey: import("@angular/core").InputSignal<string>;
|
|
31
|
+
readonly rememberDays: import("@angular/core").InputSignal<number>;
|
|
32
|
+
readonly imageSrc: import("@angular/core").InputSignal<string>;
|
|
33
|
+
readonly imageAlt: import("@angular/core").InputSignal<string>;
|
|
34
|
+
/** Makes the image a link. */
|
|
35
|
+
readonly link: import("@angular/core").InputSignal<string>;
|
|
36
|
+
/** Button under the image. Hidden when `ctaHref` is empty. */
|
|
37
|
+
readonly ctaLabel: import("@angular/core").InputSignal<string>;
|
|
38
|
+
readonly ctaHref: import("@angular/core").InputSignal<string>;
|
|
39
|
+
readonly ctaVariant: import("@angular/core").InputSignal<ButtonVariant>;
|
|
40
|
+
readonly closeOnBackdrop: import("@angular/core").InputSignal<boolean>;
|
|
41
|
+
readonly closeLabel: import("@angular/core").InputSignal<string>;
|
|
42
|
+
readonly closed: import("@angular/core").OutputEmitterRef<void>;
|
|
43
|
+
private readonly document;
|
|
44
|
+
private readonly box;
|
|
45
|
+
private readonly closeButton;
|
|
46
|
+
private readonly isOpen;
|
|
47
|
+
constructor();
|
|
48
|
+
close(): void;
|
|
49
|
+
/** Keeps Tab and Shift+Tab inside the modal. */
|
|
50
|
+
protected trapFocus(event: KeyboardEvent): void;
|
|
51
|
+
private storage;
|
|
52
|
+
private wasDismissed;
|
|
53
|
+
private remember;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PromoModalComponent, never>;
|
|
55
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<PromoModalComponent, "cc-promo-modal", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "autoOpen": { "alias": "autoOpen"; "required": false; "isSignal": true; }; "delayMs": { "alias": "delayMs"; "required": false; "isSignal": true; }; "rememberKey": { "alias": "rememberKey"; "required": false; "isSignal": true; }; "rememberDays": { "alias": "rememberDays"; "required": false; "isSignal": true; }; "imageSrc": { "alias": "imageSrc"; "required": false; "isSignal": true; }; "imageAlt": { "alias": "imageAlt"; "required": false; "isSignal": true; }; "link": { "alias": "link"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; "ctaHref": { "alias": "ctaHref"; "required": false; "isSignal": true; }; "ctaVariant": { "alias": "ctaVariant"; "required": false; "isSignal": true; }; "closeOnBackdrop": { "alias": "closeOnBackdrop"; "required": false; "isSignal": true; }; "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "closed": "closed"; }, never, ["*"], true, never>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* Text field with a search button. Emits `search` on Enter or on the button.
|
|
4
|
+
*
|
|
5
|
+
* ```html
|
|
6
|
+
* <cc-search-box placeholder="Buscar plan" [(value)]="query" (search)="onSearch($event)" />
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* Tokens: --cc-search-bg, --cc-search-border, --cc-search-btn-bg, --cc-search-btn-color,
|
|
10
|
+
* --cc-search-radius.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SearchBoxComponent {
|
|
13
|
+
readonly value: import("@angular/core").ModelSignal<string>;
|
|
14
|
+
readonly placeholder: import("@angular/core").InputSignal<string>;
|
|
15
|
+
/** Accessible name of the field. Defaults to `placeholder`. */
|
|
16
|
+
readonly label: import("@angular/core").InputSignal<string>;
|
|
17
|
+
readonly buttonLabel: import("@angular/core").InputSignal<string>;
|
|
18
|
+
readonly iconSrc: import("@angular/core").InputSignal<string>;
|
|
19
|
+
readonly search: import("@angular/core").OutputEmitterRef<string>;
|
|
20
|
+
onInput(event: Event): void;
|
|
21
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SearchBoxComponent, never>;
|
|
22
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SearchBoxComponent, "cc-search-box", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "buttonLabel": { "alias": "buttonLabel"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "search": "search"; }, never, never, true, never>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* Section title with optional eyebrow, icon, subtitle and decorative rule.
|
|
4
|
+
* Use one per page section so every page shares the same heading rhythm.
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <cc-section-heading title="Planes" subtitle="Para ti, en pareja o en grupo" iconSrc="assets/images/trebol.png" />
|
|
8
|
+
* <cc-section-heading title="Contacto" tone="inverse" align="center" level="h3" />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Tokens: --cc-section-heading-color, --cc-section-heading-size, --cc-section-heading-rule,
|
|
12
|
+
* --cc-section-heading-gap, --cc-section-heading-icon-size.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SectionHeadingComponent {
|
|
15
|
+
readonly title: import("@angular/core").InputSignal<string>;
|
|
16
|
+
readonly subtitle: import("@angular/core").InputSignal<string>;
|
|
17
|
+
/** Small text above the title. */
|
|
18
|
+
readonly eyebrow: import("@angular/core").InputSignal<string>;
|
|
19
|
+
readonly iconSrc: import("@angular/core").InputSignal<string>;
|
|
20
|
+
/** Heading level. The visual size does not change with the level. */
|
|
21
|
+
readonly level: import("@angular/core").InputSignal<"h1" | "h2" | "h3">;
|
|
22
|
+
readonly align: import("@angular/core").InputSignal<"start" | "center">;
|
|
23
|
+
/** `default` for light backgrounds, `inverse` for dark ones, `accent` for brand-colored titles. */
|
|
24
|
+
readonly tone: import("@angular/core").InputSignal<"inverse" | "default" | "accent">;
|
|
25
|
+
/** Shows a short accent line under the title. */
|
|
26
|
+
readonly rule: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
|
|
27
|
+
/** `sm` for sub-sections, `md` (default) for page sections, `lg` for heroes. */
|
|
28
|
+
readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg">;
|
|
29
|
+
readonly italic: import("@angular/core").InputSignalWithTransform<boolean, unknown>;
|
|
30
|
+
/** Id of the heading element, for anchors. */
|
|
31
|
+
readonly headingId: import("@angular/core").InputSignal<string | null>;
|
|
32
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SectionHeadingComponent, never>;
|
|
33
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SectionHeadingComponent, "cc-section-heading", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "eyebrow": { "alias": "eyebrow"; "required": false; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "level": { "alias": "level"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "rule": { "alias": "rule"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "italic": { "alias": "italic"; "required": false; "isSignal": true; }; "headingId": { "alias": "headingId"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SocialLink } from '../../models/ui.models';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* Row of social network icons that open in a new tab. Each link has a 44px tap area
|
|
5
|
+
* even when the icon is smaller.
|
|
6
|
+
*
|
|
7
|
+
* ```html
|
|
8
|
+
* <cc-social-links [links]="socials" size="sm" tone="inverse" />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Tokens: --cc-social-icon-size, --cc-social-gap.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SocialLinksComponent {
|
|
14
|
+
readonly links: import("@angular/core").InputSignal<SocialLink[]>;
|
|
15
|
+
/** Icon size: `sm` 1rem, `md` 1.5rem, `lg` 2.5rem. */
|
|
16
|
+
readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg">;
|
|
17
|
+
/** `inverse` for dark backgrounds: changes the hover and focus colors. */
|
|
18
|
+
readonly tone: import("@angular/core").InputSignal<"inverse" | "default">;
|
|
19
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SocialLinksComponent, never>;
|
|
20
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SocialLinksComponent, "cc-social-links", never, { "links": { "alias": "links"; "required": true; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* Floating WhatsApp button, fixed to a corner of the screen.
|
|
4
|
+
*
|
|
5
|
+
* `variant`:
|
|
6
|
+
* - `icon` (default): round button with the logo.
|
|
7
|
+
* - `extended`: pill with the logo and `label` as visible text.
|
|
8
|
+
*
|
|
9
|
+
* ```html
|
|
10
|
+
* <cc-whatsapp-button [href]="whatsappLink" />
|
|
11
|
+
* <cc-whatsapp-button [href]="whatsappLink" variant="extended" label="Reservar" position="left" />
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Tokens: --cc-whatsapp, --cc-whatsapp-hover, --cc-whatsapp-size, --cc-whatsapp-offset-x,
|
|
15
|
+
* --cc-whatsapp-offset-y.
|
|
16
|
+
*/
|
|
17
|
+
export declare class WhatsappButtonComponent {
|
|
18
|
+
/** WhatsApp link. Build it with `whatsappUrl(phone, message)`. */
|
|
19
|
+
readonly href: import("@angular/core").InputSignal<string>;
|
|
20
|
+
/** Custom icon. Without it, the WhatsApp logo is drawn as SVG. */
|
|
21
|
+
readonly iconSrc: import("@angular/core").InputSignal<string>;
|
|
22
|
+
/** Accessible name, and the visible text in the `extended` variant. */
|
|
23
|
+
readonly label: import("@angular/core").InputSignal<string>;
|
|
24
|
+
readonly variant: import("@angular/core").InputSignal<"icon" | "extended">;
|
|
25
|
+
readonly size: import("@angular/core").InputSignal<"sm" | "md">;
|
|
26
|
+
readonly position: import("@angular/core").InputSignal<"right" | "left">;
|
|
27
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<WhatsappButtonComponent, never>;
|
|
28
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<WhatsappButtonComponent, "cc-whatsapp-button", never, { "href": { "alias": "href"; "required": true; "isSignal": true; }; "iconSrc": { "alias": "iconSrc"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "position": { "alias": "position"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan category. The numeric values match the `category` field stored in the sites' `plans.json`.
|
|
3
|
+
*/
|
|
4
|
+
export declare enum PlanCategory {
|
|
5
|
+
Individual = 0,
|
|
6
|
+
Couple = 1,
|
|
7
|
+
Group = 2,
|
|
8
|
+
None = 3
|
|
9
|
+
}
|
|
10
|
+
/** Add-on service that a plan includes (an entry in `additionals.json`). */
|
|
11
|
+
export interface AdditionalService {
|
|
12
|
+
id: number;
|
|
13
|
+
iconPath: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}
|
|
16
|
+
/** A spa plan (an entry in `plans.json`). */
|
|
17
|
+
export interface Plan {
|
|
18
|
+
id: number;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
duration: string;
|
|
22
|
+
price: number;
|
|
23
|
+
/** Number of people the plan is for. The spelling matches the existing JSON files. */
|
|
24
|
+
cuantity: number;
|
|
25
|
+
imgPath: string;
|
|
26
|
+
category: PlanCategory | number;
|
|
27
|
+
additionalServicesId: number[];
|
|
28
|
+
/** Filled in by `PlanCatalogService` by joining `additionalServicesId` with `additionals.json`. */
|
|
29
|
+
additionalServices?: AdditionalService[];
|
|
30
|
+
}
|
|
31
|
+
/** Price range option for the filter form (an entry in `priceRanges.json`). */
|
|
32
|
+
export interface PriceRange {
|
|
33
|
+
description: string;
|
|
34
|
+
min: number;
|
|
35
|
+
max: number;
|
|
36
|
+
}
|
|
37
|
+
/** Filter produced by `cc-plan-filter-form`. Empty fields are not applied. */
|
|
38
|
+
export interface PlanFilter {
|
|
39
|
+
service?: AdditionalService | null;
|
|
40
|
+
priceRange?: PriceRange | null;
|
|
41
|
+
}
|
|
42
|
+
/** Entry in `cc-category-menu`. */
|
|
43
|
+
export interface CategoryOption<T = PlanCategory | number> {
|
|
44
|
+
value: T;
|
|
45
|
+
label: string;
|
|
46
|
+
/** Number shown next to the label. Leave it empty to hide it. */
|
|
47
|
+
count?: number;
|
|
48
|
+
}
|
|
49
|
+
/** Default Spanish labels for the plan categories. */
|
|
50
|
+
export declare const DEFAULT_PLAN_CATEGORIES: CategoryOption[];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Image for `cc-gallery` and `cc-lightbox`. */
|
|
2
|
+
export interface GalleryImage {
|
|
3
|
+
src: string;
|
|
4
|
+
/** Smaller version for the grid. Defaults to `src`. */
|
|
5
|
+
thumb?: string;
|
|
6
|
+
/** Alternative text. Defaults to `caption`. */
|
|
7
|
+
alt?: string;
|
|
8
|
+
/** Text shown under the image in the lightbox. */
|
|
9
|
+
caption?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Link for `cc-social-links`. */
|
|
12
|
+
export interface SocialLink {
|
|
13
|
+
href: string;
|
|
14
|
+
iconSrc: string;
|
|
15
|
+
/** Accessible name, for example "Instagram de Laurel Spa". */
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
18
|
+
/** Question for `cc-faq`. */
|
|
19
|
+
export interface FaqItem {
|
|
20
|
+
question: string;
|
|
21
|
+
answer: string;
|
|
22
|
+
}
|
|
23
|
+
/** Router link accepted by the components: a path string or a commands array. */
|
|
24
|
+
export type LinkTarget = string | any[];
|