@c-code/c-code-fw 1.3.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +118 -104
  2. package/fesm2022/c-code-c-code-fw-ui.mjs +622 -141
  3. package/fesm2022/c-code-c-code-fw-ui.mjs.map +1 -1
  4. package/package.json +1 -1
  5. package/ui/lib/components/button/button.component.d.ts +24 -0
  6. package/ui/lib/components/category-menu/category-menu.component.d.ts +12 -3
  7. package/ui/lib/components/faq/faq.component.d.ts +11 -4
  8. package/ui/lib/components/faq-item/faq-item.component.d.ts +21 -0
  9. package/ui/lib/components/gallery/gallery.component.d.ts +7 -3
  10. package/ui/lib/components/info-item/info-item.component.d.ts +10 -2
  11. package/ui/lib/components/lightbox/lightbox.component.d.ts +18 -3
  12. package/ui/lib/components/notice/notice.component.d.ts +17 -0
  13. package/ui/lib/components/page-banner/page-banner.component.d.ts +9 -3
  14. package/ui/lib/components/plan-card/plan-card.component.d.ts +29 -6
  15. package/ui/lib/components/plan-catalog/plan-catalog.component.d.ts +43 -19
  16. package/ui/lib/components/plan-details/plan-details.component.d.ts +26 -11
  17. package/ui/lib/components/plan-filter-form/plan-filter-form.component.d.ts +15 -5
  18. package/ui/lib/components/promo-modal/promo-modal.component.d.ts +39 -7
  19. package/ui/lib/components/search-box/search-box.component.d.ts +6 -1
  20. package/ui/lib/components/section-heading/section-heading.component.d.ts +34 -0
  21. package/ui/lib/components/social-links/social-links.component.d.ts +10 -6
  22. package/ui/lib/components/whatsapp-button/whatsapp-button.component.d.ts +12 -2
  23. package/ui/lib/models/promo.models.d.ts +25 -0
  24. package/ui/lib/utils/promo.utils.d.ts +18 -0
  25. package/ui/lib/utils/text.utils.d.ts +10 -0
  26. package/ui/public-api.d.ts +6 -0
  27. package/ui/theme/component-base.css +136 -0
  28. package/ui/theme/tokens.css +111 -45
package/README.md CHANGED
@@ -190,110 +190,124 @@ The `ScreenWidthEventService` uses Tailwind CSS breakpoints for responsive behav
190
190
 
191
191
  ---
192
192
 
193
- ## UI components: `@c-code/c-code-fw/ui`
194
-
195
- Presentational components for spa and catalog sites. They are standalone Angular 19 components (signal inputs, OnPush) and work with SSR and prerendering. Styling is plain CSS driven by CSS variables, so a site needs no Tailwind configuration to use them.
196
-
197
- ### Setup
198
-
199
- ```ts
200
- // app.config.ts
201
- import { provideHttpClient, withFetch } from '@angular/common/http';
202
- import { providePlanCatalog, provideSeo } from '@c-code/c-code-fw/ui';
203
-
204
- export const appConfig: ApplicationConfig = {
205
- providers: [
206
- provideHttpClient(withFetch()),
207
- providePlanCatalog(), // reads assets/data/{plans,additionals,priceRanges}.json
208
- provideSeo({ siteUrl: 'https://www.example.com', defaultImage: '/assets/images/og.jpeg' }),
209
- ],
210
- };
211
- ```
212
-
213
- Set the site palette in `styles.css`. Every variable is optional:
214
-
215
- ```css
216
- :root {
217
- --cc-primary: #4C6B4A;
218
- --cc-primary-light: #A0BA9E;
219
- --cc-primary-dark: #022B04;
220
- --cc-secondary: #CEAB5D;
221
- --cc-secondary-light: #f8f4ee;
222
- --cc-secondary-dark: #9A7521;
223
- --cc-bg: #F0FFEF;
224
- }
225
- ```
226
-
227
- A list of every token is in `node_modules/@c-code/c-code-fw/ui/theme/tokens.css`. You can add that file to `angular.json` → `styles`, or copy the variables you need.
228
-
229
- Roles: by default each role follows a palette color. Set a role only when the site needs it to differ:
230
-
231
- | Role | Used for | Default |
232
- |---|---|---|
233
- | `--cc-surface` | cards, sidebar, detail panel | `--cc-secondary-light` |
234
- | `--cc-text` | text inside components | `--cc-primary` |
235
- | `--cc-heading` | titles, price badge | `--cc-primary` |
236
- | `--cc-accent` | buttons, highlighted titles | `--cc-secondary` |
237
- | `--cc-accent-hover` | button hover | `--cc-secondary-dark` |
238
- | `--cc-on-accent` | text on buttons and badges | `--cc-bg` |
239
- | `--cc-font-heading` | title font | `inherit` |
240
-
241
- ### Components
242
-
243
- All text has Spanish defaults, and every label can be changed through an input.
244
-
245
- | Component | Main inputs | Outputs / notes |
246
- |---|---|---|
247
- | `<cc-plan-catalog>` | `plans`*, `services`, `priceRanges`, `categories`, `initialCategory`, `detailsLink`, `showCounts`, `showBanner`, `showSearch`, `titlePrefix`, `imageAltSuffix`, `emptyMessage`, `searchIconSrc`, `filterIconSrc` | `categoryChange`. The full plan list page. For a custom card: `<ng-template ccPlanCard let-plan let-link="link">` (import `PlanCardTemplateDirective`). |
248
- | `<cc-plan-details>` | `plan`*, `bookingUrl`*, `bookingLabel`, `backLink`, `perks`, `perksTitle`, `perksImageSrc`, `titleIconSrc`, `durationIconSrc`, `personIconSrc`, `peopleIconSrc`, `currencyCode`, `currencyDisplay`, `imageAltSuffix` | Content in `<ng-content>` goes above the gift box; an element with the `ccDetailsMedia` attribute goes under the photo. |
249
- | `<cc-plan-card>` | `name`*, `imageSrc`*, `link`, `ctaLabel`, `subtitle`, `imageAlt` | `ctaClick` |
250
- | `<cc-category-menu>` | `options`*, `[(selected)]`, `title` | |
251
- | `<cc-search-box>` | `[(value)]`, `placeholder`, `iconSrc` | `search` |
252
- | `<cc-plan-filter-form>` | `services`, `priceRanges`, labels | `filterChange` |
253
- | `<cc-page-banner>` | `title`*, `variant` (`band` \| `plain`), `iconSrc`, `backLink` | Renders the page `<h1>`. |
254
- | `<cc-gallery>` | `images`*, `backgroundImage` | Opens `cc-lightbox`. `numberedImages(18, i => \`assets/galery/${i}.jpeg\`)` builds the list. |
255
- | `<cc-lightbox>` | `images`*, `[(index)]` | Arrow keys and Escape. |
256
- | `<cc-info-item>` | `title`*, `text` | Projects extra content (lists). |
257
- | `<cc-faq>` | `items`*, `title` | Native `<details>`, prerendered. |
258
- | `<cc-whatsapp-button>` | `href`*, `iconSrc`, `position`, `label` | Floating button. Without `iconSrc` it draws the WhatsApp logo. |
259
- | `<cc-promo-modal>` | `[(open)]`, `imageSrc`, `imageAlt`, `link`, `closeOnBackdrop` | `closed`. Without `imageSrc` it shows the projected content. |
260
- | `<cc-social-links>` | `links`*, `size`, `gap` | |
261
-
262
- `*` required.
263
-
264
- Component variables (they default to the roles above): `--cc-plan-card-width`, `--cc-plan-card-width-lg`, `--cc-plan-card-height`, `--cc-plan-card-bg`, `--cc-plan-card-cta-bg`, `--cc-catalog-column-min`, `--cc-catalog-panel-bg`, `--cc-category-active-bg`, `--cc-category-active-color`, `--cc-details-panel-bg`, `--cc-details-price-bg`, `--cc-details-gift-border`, `--cc-banner-bg`, `--cc-banner-color`, `--cc-banner-font-size`, `--cc-info-title-color`, `--cc-info-text-color`, `--cc-faq-bg`, `--cc-gallery-thumb-width`, `--cc-gallery-thumb-width-lg`, `--cc-gallery-columns-lg`, `--cc-whatsapp-size`, `--cc-whatsapp-size-lg`, `--cc-whatsapp-offset-x`, `--cc-whatsapp-offset-y`, `--cc-modal-backdrop`, `--cc-lightbox-backdrop`.
265
-
266
- ### Services and utilities
267
-
268
- - `PlanCatalogService`: `getPlans()` (with `additionalServices` joined), `getAdditionalServices()`, `getPriceRanges()`, `getPlanBySlug()`, `getPlansByCategory()`, `getPlansByFilter()`, `getPlansByName()`. Each JSON file is requested once and cached. Configure the paths with `providePlanCatalog({ plansUrl, additionalsUrl, priceRangesUrl })`; pass `priceRangesUrl: null` if the site has no price ranges.
269
- - `SeoService`: `update({ title, description, path, image })` sets the title, description, canonical URL and Open Graph/Twitter tags. `setJsonLd(id, data)` and `removeJsonLd(id)` manage JSON-LD blocks.
270
- - Pure functions: `slugify`, `planSlug`, `findPlanBySlug`, `filterPlans`, `filterPlansByCategory`, `searchPlansByName`, `withCategoryCounts`, `joinAdditionalServices`, `whatsappUrl(phone, message)`, `titleCase`, `truncateText`.
271
- - Models: `Plan`, `AdditionalService`, `PriceRange`, `PlanFilter`, `PlanCategory`, `CategoryOption`, `GalleryImage`, `SocialLink`, `FaqItem`.
272
-
273
- ### Example: plan list page
274
-
275
- ```ts
276
- import { toSignal } from '@angular/core/rxjs-interop';
277
- import { PlanCatalogComponent, PlanCatalogService } from '@c-code/c-code-fw/ui';
278
-
279
- @Component({
280
- imports: [PlanCatalogComponent],
281
- template: `<cc-plan-catalog [plans]="plans()" [services]="services()" [priceRanges]="ranges()" />`,
282
- })
283
- export class PlanListComponent {
284
- private catalog = inject(PlanCatalogService);
285
- plans = toSignal(this.catalog.getPlans(), { initialValue: [] });
286
- services = toSignal(this.catalog.getAdditionalServices(), { initialValue: [] });
287
- ranges = toSignal(this.catalog.getPriceRanges(), { initialValue: [] });
288
- }
289
- ```
290
-
291
- ### Development
292
-
293
- `npx ng serve showcase` runs a playground with every component at http://localhost:4200. It loads the data and images from `../medellin-spa/src/assets`, and `?theme=xora` switches to a second palette.
294
-
295
- ---
296
-
193
+ ## UI components: `@c-code/c-code-fw/ui`
194
+
195
+ Presentational components for spa and catalog sites: standalone Angular 19 components with signal inputs and OnPush, safe for SSR and prerendering. Styling is plain CSS driven by CSS variables, so a site needs no Tailwind configuration to use them.
196
+
197
+ ### Who owns what
198
+
199
+ - **The library owns structure:** type scale, spacing, radii, shadows, control heights, motion and layers. Each component also exposes props to choose how it looks (`variant`, `tone`, `size`, `layout`, `appearance`, `italic`…).
200
+ - **The site owns identity:** colors and fonts. The library ships **no palette**. Components read a contract of semantic roles that each site maps from its own variables. Without a site theme, components render in neutral grays.
201
+
202
+ ### Setup
203
+
204
+ ```ts
205
+ // app.config.ts
206
+ import { provideHttpClient, withFetch } from '@angular/common/http';
207
+ import { providePlanCatalog, provideSeo } from '@c-code/c-code-fw/ui';
208
+
209
+ export const appConfig: ApplicationConfig = {
210
+ providers: [
211
+ provideHttpClient(withFetch()),
212
+ providePlanCatalog(), // reads assets/data/{plans,additionals,priceRanges}.json
213
+ provideSeo({ siteUrl: 'https://www.example.com', defaultImage: '/assets/images/og.jpeg' }),
214
+ ],
215
+ };
216
+ ```
217
+
218
+ Theme the site in `styles.css`, using any names for your brand variables:
219
+
220
+ ```css
221
+ :root {
222
+ /* Brand, owned by the site */
223
+ --laurel-green: #4c6b4a;
224
+ --laurel-gold: #ceab5d;
225
+
226
+ /* Roles read by the components */
227
+ --cc-heading: var(--laurel-green);
228
+ --cc-text: var(--laurel-green);
229
+ --cc-accent: var(--laurel-gold);
230
+ --cc-on-accent: #022b04;
231
+ --cc-font-heading: 'El Messiri', serif;
232
+ }
233
+ ```
234
+
235
+ Optionally add `node_modules/@c-code/c-code-fw/ui/theme/tokens.css` to `angular.json` → `styles` to use the same scales in the site's own markup, for example by mapping Tailwind's `fontSize` to `var(--cc-text-*)`.
236
+
237
+ ### Color and font roles (set by the site)
238
+
239
+ | Role | Used for | Neutral default |
240
+ |---|---|---|
241
+ | `--cc-canvas` | page background behind components, input fields | `#ffffff` |
242
+ | `--cc-surface` | cards, panels, sidebar | `#f5f5f4` |
243
+ | `--cc-surface-alt` | alternative bands, image placeholders | `#fafaf9` |
244
+ | `--cc-text` | body text | `#292524` |
245
+ | `--cc-text-muted` | counters, metadata | `--cc-text` at 72% |
246
+ | `--cc-heading` | titles | `#1c1917` |
247
+ | `--cc-accent` / `--cc-accent-hover` | primary buttons, active items | `#1c1917` / `#44403c` |
248
+ | `--cc-accent-text` | accent used as text (needs 4.5:1) | `--cc-heading` |
249
+ | `--cc-on-accent` | text on the accent (needs 4.5:1) | `#ffffff` |
250
+ | `--cc-inverse` / `--cc-inverse-hover` / `--cc-on-inverse` | dark blocks: price badge, dark buttons | `#1c1917` / `#000` / `#fff` |
251
+ | `--cc-border` / `--cc-border-strong` | dividers / input borders (3:1) | `#d6d3d1` / `#78716c` |
252
+ | `--cc-focus-ring` | keyboard focus | `--cc-heading` |
253
+ | `--cc-overlay` | modal and lightbox backdrop | `rgb(0 0 0 / 0.7)` |
254
+ | `--cc-danger`, `--cc-danger-surface`, `--cc-on-danger-surface` | warnings (`cc-notice tone="warning"`) | reds |
255
+ | `--cc-whatsapp`, `--cc-whatsapp-hover`, `--cc-on-whatsapp` | WhatsApp buttons | WhatsApp green |
256
+ | `--cc-font-body`, `--cc-font-heading` | font families | `inherit` |
257
+ | `--cc-heading-style`, `--cc-heading-transform`, `--cc-heading-tracking` | italic, uppercase, letter spacing of titles | `normal`, `none`, `normal` |
258
+
259
+ ### Structure tokens (owned by the library)
260
+
261
+ All of them can be overridden, but they already have values: `--cc-text-xs…4xl`, `--cc-leading-*`, `--cc-weight-*`, `--cc-space-1…24` (4px base, same steps as Tailwind), `--cc-radius`, `--cc-radius-sm|md|lg|pill`, `--cc-shadow-sm|…|xl`, `--cc-focus-width|offset`, `--cc-control-height` (44px) and its `-sm`/`-lg` versions, `--cc-duration(-fast)`, `--cc-ease`, `--cc-z-float|sticky|overlay`. Breakpoints: 640, 768 and 1024px.
262
+
263
+ ### Components
264
+
265
+ All text has Spanish defaults, and every label is an input.
266
+
267
+ | Component | Main inputs | Notes |
268
+ |---|---|---|
269
+ | `a[ccButton]`, `button[ccButton]` | `variant` (`primary`, `secondary`, `ghost`, `whatsapp`, `inverse`), `size` (`sm`, `md`, `lg`), `block` | The shared call-to-action style used by every component. |
270
+ | `<cc-plan-catalog>` | `plans`* (`null` = loading), `[(category)]`, `initialCategory`, `services`, `priceRanges`, `categories`, `categoriesLayout` (`auto`, `list`, `chips`), `showPrice`, `priceFormat`, `planMeta`, `cardAppearance`, `ctaLabel`, `ctaVariant`, `detailsLink`, `contactHref`, `emptyMessage`, `emptyActionLabel` | Full plan list page. It shows skeletons while loading, an empty state with actions, and a custom card through `<ng-template ccPlanCard let-plan let-link="link">`. |
271
+ | `<cc-plan-details>` | `plan`*, `bookingUrl`* (string or `(plan) => string`), `bookingLabel`, `barBookingLabel`, `bookingVariant`, `stickyBar`, `priceFormat`, `priceNote`, `perks`, `perksTitle`, `perksImageSrc`, icon inputs | It puts a booking button near the price and another at the end, and shows a fixed price and booking bar below 1024px. |
272
+ | `<cc-plan-card>` | `name`, `imageSrc`, `price`, `priceLabel`, `priceFormat`, `meta`, `link`, `queryParams`, `ctaLabel`, `ctaVariant`, `appearance` (`filled`, `outlined`, `plain`), `headingLevel`, `skeleton` | The whole card is clickable when it has a `link`. |
273
+ | `<cc-category-menu>` | `options`*, `[(selected)]`, `title`, `layout` | Toggle buttons with `aria-pressed`. |
274
+ | `<cc-search-box>` | `[(value)]`, `placeholder`, `label`, `iconSrc` | `search` output. |
275
+ | `<cc-plan-filter-form>` | `services`, `priceRanges`, `mode` (`instant`, `submit`), labels | `filterChange` output. |
276
+ | `<cc-page-banner>` | `title`*, `subtitle`, `variant` (`band`, `plain`), `size`, `italic`, `align`, `iconSrc`, `backLink` | Renders the page `<h1>`. |
277
+ | `<cc-section-heading>` | `title`*, `subtitle`, `eyebrow`, `iconSrc`, `level`, `size`, `italic`, `tone` (`default`, `inverse`, `accent`), `align`, `rule`, `headingId` | One heading pattern for every page section. |
278
+ | `<cc-gallery>` | `images`*, `columns`, `backgroundImage` | Opens `cc-lightbox`. `numberedImages(18, i => …)` builds the list. |
279
+ | `<cc-lightbox>` | `images`*, `[(index)]` | Arrows, swipe and Escape. Focus is trapped while open and restored on close. |
280
+ | `<cc-info-item>` | `title`*, `text`, `tone`, `size`, `headingLevel`, `headingId` | Projects extra content. |
281
+ | `<cc-notice>` | `tone` (`info`, `warning`, `success`), `title` | Highlighted note; content is projected. |
282
+ | `<cc-faq>` + `<cc-faq-item>` | `items` or projected `<cc-faq-item question="…">` | Native `<details>`, so answers are prerendered. Items can contain links. |
283
+ | `<cc-whatsapp-button>` | `href`*, `variant` (`icon`, `extended`), `label`, `size`, `position`, `iconSrc` | Floating button. |
284
+ | `<cc-promo-modal>` | `[(open)]`, `autoOpen`, `delayMs`, `rememberKey`, `rememberDays`, `imageSrc`, `imageAlt`, `link`, `ctaLabel`, `ctaHref` | It opens by itself in the browser and remembers the dismissal. |
285
+ | `<cc-social-links>` | `links`*, `size` (`sm`, `md`, `lg`), `tone` (`default`, `inverse`) | 44px tap area per link. |
286
+
287
+ `*` required. Each component also exposes its own `--cc-<component>-*` variables, which are listed in its JSDoc (`Tokens: …`).
288
+
289
+ ### Services and utilities
290
+
291
+ - `PlanCatalogService`: `getPlans()` (with `additionalServices` joined), `getAdditionalServices()`, `getPriceRanges()`, `getPlanBySlug()`, `getPlansByCategory()`, `getPlansByFilter()`, `getPlansByName()`. Each JSON file is requested once and cached. Configure the paths with `providePlanCatalog({ plansUrl, additionalsUrl, priceRangesUrl })`.
292
+ - `SeoService`: `update({ title, description, path, image })`, `setJsonLd(id, data)`, `removeJsonLd(id)`, `absoluteUrl(path)`.
293
+ - Pure functions (no Angular): `formatPrice(value, { locale, currency, digits })` (default `es-CO`/`COP`: `$ 259.900`), `slugify`, `planSlug`, `findPlanBySlug`, `filterPlans`, `filterPlansByCategory`, `searchPlansByName`, `withCategoryCounts`, `joinAdditionalServices`, `defaultPlanMeta`, `whatsappUrl(phone, message)`, `titleCase`, `truncateText`.
294
+
295
+ ### Migrating from 1.3
296
+
297
+ - The library no longer defines `--cc-primary`, `--cc-secondary`, `--cc-bg` or the other palette variables. Set the roles listed above from your own brand variables instead.
298
+ - Default colors are now neutral grays, and `--cc-on-accent` must be set when the accent is light.
299
+ - `cc-social-links`: `size` is now `sm`, `md` or `lg`, and `gap` became the `--cc-social-gap` token.
300
+ - `cc-plan-catalog`: `plans` accepts `null` (loading), and the category is the `[(category)]` model. The old `categoryChange` output still works, because it is the model's change event.
301
+ - `cc-promo-modal`: `open` now defaults to `false`; use `autoOpen` to open it by itself.
302
+ - `cc-plan-details`: prices use `formatPrice` (`priceFormat` input) instead of the currency pipe inputs.
303
+
304
+ ### Development
305
+
306
+ - `npx ng serve showcase` runs a playground with every component. It loads the data and images from `../medellin-spa/src/assets`. Add `?theme=laurel` or `?theme=xora` to the URL to try a site theme; without it, the neutral defaults show.
307
+ - `npm run lint:ui` checks the architecture rules in `.claude/rules/ui-components.md`.
308
+
309
+ ---
310
+
297
311
  ## Contributing
298
312
 
299
313
  Feel free to contribute to this library by submitting issues or pull requests.