@crtobiasdelsud/portal-ui 1.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/CHANGELOG.md +71 -0
- package/README.md +829 -0
- package/package.json +43 -0
- package/src/adapters/AdaptersContext.jsx +28 -0
- package/src/components/ArticleHero/ArticleHero.jsx +73 -0
- package/src/components/ArticleHero/ArticleHero.module.scss +89 -0
- package/src/components/ArticleHero/variants/V0/V0.jsx +29 -0
- package/src/components/ArticleHero/variants/V0/V0.module.scss +51 -0
- package/src/components/ArticleHero/variants/V0Desktop/V0Desktop.jsx +29 -0
- package/src/components/ArticleHero/variants/V0Desktop/V0Desktop.module.scss +54 -0
- package/src/components/ArticleHero/variants/V0Tablet/V0Tablet.jsx +29 -0
- package/src/components/ArticleHero/variants/V0Tablet/V0Tablet.module.scss +49 -0
- package/src/components/ArticleHero/variants/V1/V1.jsx +29 -0
- package/src/components/ArticleHero/variants/V1/V1.module.scss +40 -0
- package/src/components/ArticleHero/variants/V2/V2.jsx +29 -0
- package/src/components/ArticleHero/variants/V2/V2.module.scss +44 -0
- package/src/components/ArticleHero/variants/V3/V3.jsx +25 -0
- package/src/components/ArticleHero/variants/V3/V3.module.scss +41 -0
- package/src/components/ArticleHero/variants/V4/V4.jsx +25 -0
- package/src/components/ArticleHero/variants/V4/V4.module.scss +36 -0
- package/src/components/ArticleHero/variants/V5/V5.jsx +25 -0
- package/src/components/ArticleHero/variants/V5/V5.module.scss +33 -0
- package/src/components/ArticleHeroFull/ArticleHeroFull.jsx +38 -0
- package/src/components/ArticleHeroFull/ArticleHeroFull.module.scss +116 -0
- package/src/components/ArticleSidebar/ArticleSidebar.jsx +28 -0
- package/src/components/ArticleSidebar/ArticleSidebar.module.scss +41 -0
- package/src/components/AuthorBlock/AuthorBlock.jsx +67 -0
- package/src/components/AuthorBlock/AuthorBlock.module.scss +35 -0
- package/src/components/AuthorBlock/variants/V1/V1.jsx +38 -0
- package/src/components/AuthorBlock/variants/V1/V1.module.scss +25 -0
- package/src/components/AuthorBlock/variants/V2/V2.jsx +38 -0
- package/src/components/AuthorBlock/variants/V2/V2.module.scss +10 -0
- package/src/components/AuthorBlock/variants/V3/V3.jsx +30 -0
- package/src/components/AuthorBlock/variants/V3/V3.module.scss +10 -0
- package/src/components/AuthorBlock/variants/V4/V4.jsx +30 -0
- package/src/components/AuthorBlock/variants/V4/V4.module.scss +10 -0
- package/src/components/Banner/Banner.module.scss +70 -0
- package/src/components/Banner/BannerDisplay.jsx +118 -0
- package/src/components/Banner/BannerView.jsx +35 -0
- package/src/components/Blocks/BlockColumns/BlockColumns.jsx +37 -0
- package/src/components/Blocks/BlockColumns/BlockColumns.module.scss +64 -0
- package/src/components/Blocks/BlockColumnsBajada/BlockColumnsBajada.jsx +38 -0
- package/src/components/Blocks/BlockColumnsBajada/BlockColumnsBajada.module.scss +64 -0
- package/src/components/Blocks/BlockMain/BlockMain.jsx +18 -0
- package/src/components/Blocks/BlockMain/BlockMain.module.scss +9 -0
- package/src/components/Blocks/BlockMainNarrow/BlockMainNarrow.jsx +18 -0
- package/src/components/Blocks/BlockMainNarrow/BlockMainNarrow.module.scss +9 -0
- package/src/components/Blocks/BlockMainSidebar/BlockMainSidebar.jsx +33 -0
- package/src/components/Blocks/BlockMainSidebar/BlockMainSidebar.module.scss +44 -0
- package/src/components/Blocks/BlockStack/BlockStack.jsx +8 -0
- package/src/components/Blocks/BlockStack/BlockStack.module.scss +18 -0
- package/src/components/Blocks/WidgetErrorBoundary.jsx +43 -0
- package/src/components/Breadcrumb/Breadcrumb.jsx +24 -0
- package/src/components/Breadcrumb/Breadcrumb.module.scss +33 -0
- package/src/components/Breadcrumb/variants/V1/V1.jsx +39 -0
- package/src/components/Breadcrumb/variants/V1/V1.module.scss +3 -0
- package/src/components/Breadcrumb/variants/V2/V2.jsx +28 -0
- package/src/components/Breadcrumb/variants/V2/V2.module.scss +3 -0
- package/src/components/Breadcrumb/variants/V3/V3.jsx +28 -0
- package/src/components/Breadcrumb/variants/V3/V3.module.scss +3 -0
- package/src/components/Breadcrumb/variants/V4/V4.jsx +28 -0
- package/src/components/Breadcrumb/variants/V4/V4.module.scss +3 -0
- package/src/components/Breadcrumb/variants/V5/V5.jsx +28 -0
- package/src/components/Breadcrumb/variants/V5/V5.module.scss +4 -0
- package/src/components/Cabezal/Cabezal.module.scss +82 -0
- package/src/components/Cabezal/CabezalView.jsx +87 -0
- package/src/components/Cabezal/CardCabezal/CardCabezal.module.scss +403 -0
- package/src/components/Cabezal/CardCabezal/index.jsx +25 -0
- package/src/components/Cabezal/CardCabezal/variants/Amp/Amp.jsx +20 -0
- package/src/components/Cabezal/CardCabezal/variants/Carrusel/Carrusel.jsx +39 -0
- package/src/components/Cabezal/CardCabezal/variants/Carrusel/Carrusel.module.scss +87 -0
- package/src/components/Cabezal/CardCabezal/variants/Compact/Compact.jsx +36 -0
- package/src/components/Cabezal/CardCabezal/variants/Default/Default.jsx +35 -0
- package/src/components/Cabezal/CardCabezal/variants/Featured/Featured.jsx +36 -0
- package/src/components/Cabezal/CardCabezal/variants/FeaturedDuo/FeaturedDuo.jsx +36 -0
- package/src/components/Cabezal/CardCabezal/variants/FeaturedHorizontal/FeaturedHorizontal.jsx +36 -0
- package/src/components/Cabezal/CardCabezal/variants/Medium/Medium.jsx +35 -0
- package/src/components/Cabezal/CardCabezal/variants/Ranked/Ranked.jsx +36 -0
- package/src/components/Cabezal/variants/Carrusel/Carrusel.jsx +167 -0
- package/src/components/Cabezal/variants/Carrusel/Carrusel.module.scss +145 -0
- package/src/components/Cabezal/variants/Categoria/Categoria.jsx +29 -0
- package/src/components/Cabezal/variants/Categoria/Categoria.module.scss +82 -0
- package/src/components/Cabezal/variants/CategoriaDos/CategoriaDos.jsx +29 -0
- package/src/components/Cabezal/variants/CategoriaDos/CategoriaDos.module.scss +99 -0
- package/src/components/Cabezal/variants/Compact/Compact.jsx +24 -0
- package/src/components/Cabezal/variants/Compact/Compact.module.scss +73 -0
- package/src/components/Cabezal/variants/Default/Default.jsx +24 -0
- package/src/components/Cabezal/variants/Default/Default.module.scss +71 -0
- package/src/components/Cabezal/variants/Desktop/Desktop.jsx +45 -0
- package/src/components/Cabezal/variants/Desktop/Desktop.module.scss +113 -0
- package/src/components/Cabezal/variants/Duo/Duo.jsx +24 -0
- package/src/components/Cabezal/variants/Duo/Duo.module.scss +72 -0
- package/src/components/Cabezal/variants/DuoSinCopete/DuoSinCopete.jsx +24 -0
- package/src/components/Cabezal/variants/DuoSinCopete/DuoSinCopete.module.scss +72 -0
- package/src/components/Cabezal/variants/Horizontal/Horizontal.jsx +24 -0
- package/src/components/Cabezal/variants/Horizontal/Horizontal.module.scss +64 -0
- package/src/components/Cabezal/variants/LeeAdemas/LeeAdemas.jsx +53 -0
- package/src/components/Cabezal/variants/LeeAdemas/LeeAdemas.module.scss +103 -0
- package/src/components/Cabezal/variants/LoQueSeLee/LoQueSeLee.jsx +48 -0
- package/src/components/Cabezal/variants/LoQueSeLee/LoQueSeLee.module.scss +103 -0
- package/src/components/Cabezal/variants/LoQueSeLee/LoQueSeLeeSkeleton.jsx +21 -0
- package/src/components/Cabezal/variants/LoQueSeLee/LoQueSeLeeSkeleton.module.scss +34 -0
- package/src/components/Cabezal/variants/Medium/Medium.jsx +24 -0
- package/src/components/Cabezal/variants/Medium/Medium.module.scss +78 -0
- package/src/components/Cabezal/variants/Mobile/Mobile.jsx +48 -0
- package/src/components/Cabezal/variants/Mobile/Mobile.module.scss +64 -0
- package/src/components/Cabezal/variants/Ranking/Ranking.jsx +24 -0
- package/src/components/Cabezal/variants/Ranking/Ranking.module.scss +79 -0
- package/src/components/Cabezal/variants/SeguiLeyendo/SeguiLeyendo.jsx +38 -0
- package/src/components/Cabezal/variants/SeguiLeyendo/SeguiLeyendo.module.scss +52 -0
- package/src/components/Cabezal/variants/Tablet/Tablet.jsx +45 -0
- package/src/components/Cabezal/variants/Tablet/Tablet.module.scss +113 -0
- package/src/components/Cabezal/variants/Tres/Tres.jsx +24 -0
- package/src/components/Cabezal/variants/Tres/Tres.module.scss +74 -0
- package/src/components/Cabezal/variants/UnaDetallada/UnaDetallada.jsx +29 -0
- package/src/components/Cabezal/variants/UnaDetallada/UnaDetallada.module.scss +86 -0
- package/src/components/Cards/ArticleBody/ArticleBody.module.scss +9 -0
- package/src/components/Cards/ArticleBody/ArticleBodyView.jsx +23 -0
- package/src/components/Cards/ArticleCard/ArticleCard.jsx +111 -0
- package/src/components/Cards/ArticleCard/ArticleCard.module.scss +129 -0
- package/src/components/Cards/Bajada/Bajada.jsx +41 -0
- package/src/components/Cards/Bajada/Bajada.module.scss +113 -0
- package/src/components/Cards/Bajada/variants/V1/V1.jsx +20 -0
- package/src/components/Cards/Bajada/variants/V1/V1.module.scss +2 -0
- package/src/components/Cards/Bajada/variants/V2/V2.jsx +20 -0
- package/src/components/Cards/Bajada/variants/V2/V2.module.scss +2 -0
- package/src/components/Clima/Clima.module.scss +93 -0
- package/src/components/Clima/ClimaView.jsx +45 -0
- package/src/components/DateTime/DateTime.jsx +26 -0
- package/src/components/DateTime/DateTime.module.scss +29 -0
- package/src/components/DolarTicker/DolarTicker.jsx +137 -0
- package/src/components/DolarTicker/DolarTicker.module.scss +249 -0
- package/src/components/DolarTicker/arrow_back_ios_black.svg +3 -0
- package/src/components/DolarTicker/arrow_back_ios_white.svg +3 -0
- package/src/components/DolarTicker/arrow_downward.svg +3 -0
- package/src/components/DolarTicker/arrow_upward.svg +3 -0
- package/src/components/DolarTickerOriginal/DolarTickerOriginal.jsx +137 -0
- package/src/components/DolarTickerOriginal/DolarTickerOriginal.module.scss +235 -0
- package/src/components/DolarTickerOriginal/arrow_back_ios_black.svg +3 -0
- package/src/components/DolarTickerOriginal/arrow_back_ios_white.svg +3 -0
- package/src/components/DolarTickerOriginal/arrow_downward.svg +3 -0
- package/src/components/DolarTickerOriginal/arrow_upward.svg +3 -0
- package/src/components/EditorOutput/EditorOutput.jsx +303 -0
- package/src/components/EditorOutput/EditorOutput.module.scss +310 -0
- package/src/components/EditorOutputFull/EditorOutputFull.jsx +303 -0
- package/src/components/EditorOutputFull/EditorOutputFull.module.scss +317 -0
- package/src/components/Feed/Feed.module.scss +77 -0
- package/src/components/Feed/FeedView.jsx +25 -0
- package/src/components/Feed/variants/V1/V1.jsx +34 -0
- package/src/components/Feed/variants/V1/V1.module.scss +39 -0
- package/src/components/Feed/variants/V2/V2.jsx +36 -0
- package/src/components/Feed/variants/V2/V2.module.scss +72 -0
- package/src/components/Footers/FooterSimple/FooterSimple.jsx +170 -0
- package/src/components/Footers/FooterSimple/FooterSimple.module.scss +256 -0
- package/src/components/Headers/HeaderSimple/CategoriesBar/CategoriesBar.jsx +104 -0
- package/src/components/Headers/HeaderSimple/CategoriesBar/CategoriesBar.module.scss +112 -0
- package/src/components/Headers/HeaderSimple/DrawerContext/DrawerContext.jsx +44 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleAmp/HeaderSimpleAmp.jsx +95 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleAmp/HeaderSimpleAmp.module.scss +2 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleDesktop/HeaderSimpleDesktop.jsx +93 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleDesktop/HeaderSimpleDesktop.module.scss +121 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleDesktopCompact/HeaderSimpleDesktopCompact.jsx +96 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleDesktopCompact/HeaderSimpleDesktopCompact.module.scss +137 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleDesktopCompact/SearchTrigger.jsx +17 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleMobile/HeaderSimpleMobile.jsx +89 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleMobile/HeaderSimpleMobile.module.scss +76 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleSwitch/HeaderSimpleSwitch.jsx +47 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleSwitch/HeaderSwitch.jsx +32 -0
- package/src/components/Headers/HeaderSimple/HeaderSimpleSwitch/HeaderSwitch.module.scss +46 -0
- package/src/components/Headers/HeaderSimple/LiveBanner/LiveBanner.jsx +79 -0
- package/src/components/Headers/HeaderSimple/LiveBanner/LiveBanner.module.scss +102 -0
- package/src/components/Headers/HeaderSimple/MenuDrawer/MenuDrawer.jsx +217 -0
- package/src/components/Headers/HeaderSimple/MenuDrawer/MenuDrawer.module.scss +243 -0
- package/src/components/Headers/HeaderSimple/_headerUtils.js +48 -0
- package/src/components/Headers/HeaderSimple/_logos/mendoza-claro.svg +43 -0
- package/src/components/Headers/HeaderSimple/_logos/mendoza-oscuro.svg +43 -0
- package/src/components/Hero/Hero.module.scss +131 -0
- package/src/components/Hero/HeroView.jsx +48 -0
- package/src/components/Recommended/Recommended.module.scss +119 -0
- package/src/components/Recommended/RecommendedView.jsx +23 -0
- package/src/components/ShareBlock/ShareBlock.jsx +75 -0
- package/src/components/ShareBlock/ShareBlock.module.scss +60 -0
- package/src/components/ShareBlock/variants/V1/V1.jsx +27 -0
- package/src/components/ShareBlock/variants/V1/V1.module.scss +2 -0
- package/src/components/ShareBlock/variants/V2/V2.jsx +27 -0
- package/src/components/ShareBlock/variants/V2/V2.module.scss +3 -0
- package/src/components/SpeechButton/SpeechButton.jsx +60 -0
- package/src/components/SpeechButton/SpeechButton.module.scss +49 -0
- package/src/components/SpeechPlayerBar/SpeechPlayerBar.jsx +92 -0
- package/src/components/SpeechPlayerBar/SpeechPlayerBar.module.scss +180 -0
- package/src/components/SpeechProviderWrapper/SpeechProviderWrapper.jsx +13 -0
- package/src/components/TextWrap/TextWrap.module.scss +72 -0
- package/src/components/TextWrap/TextWrapView.jsx +23 -0
- package/src/components/UI/AspectImage/AspectImage.jsx +53 -0
- package/src/components/UI/FocalImage/FocalImage.jsx +36 -0
- package/src/components/UI/Icon/Icon.jsx +40 -0
- package/src/components/UI/Icon/Icon.module.scss +82 -0
- package/src/components/UI/IconSmall/IconSmall.jsx +40 -0
- package/src/components/UI/IconSmall/IconSmall.module.scss +82 -0
- package/src/components/UI/PageWrapper/PageWrapper.jsx +9 -0
- package/src/components/UI/PageWrapper/PageWrapper.module.scss +5 -0
- package/src/components/UI/ToolTip/ToolTip.jsx +49 -0
- package/src/components/UI/ToolTip/ToolTip.module.scss +26 -0
- package/src/components/UI/index.js +6 -0
- package/src/constants/imageSizes.js +10 -0
- package/src/context/SiteConfigContext.jsx +79 -0
- package/src/context/SpeechContext.jsx +138 -0
- package/src/data/ArticlePoolContext.jsx +83 -0
- package/src/data/index.js +7 -0
- package/src/data/useArticles.js +67 -0
- package/src/index.js +101 -0
- package/src/styles/index.scss +3 -0
- package/src/styles/mixins/_helpers.scss +6 -0
- package/src/styles/mixins/_media.scss +30 -0
- package/src/styles/variables/_breakpoint.scss +4 -0
- package/src/styles/variables/_colors.scss +4 -0
- package/src/styles/variables/_spacing.scss +0 -0
- package/src/utils/colorContrast.js +64 -0
- package/src/utils/fechaHora.js +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
# @crtobias/portal-ui
|
|
2
|
+
|
|
3
|
+
Librería de componentes compartida entre el portal público (Next 15) y el
|
|
4
|
+
editor CMS (Vite). Provee:
|
|
5
|
+
|
|
6
|
+
- **Componentes UI** — Header, Footer, Hero, Feed, Cabezal, Cards, AuthorBlock,
|
|
7
|
+
Breadcrumb, ShareBlock, EditorOutput, Speech*, Banner, Clima, DolarTicker,
|
|
8
|
+
Blocks, ArticleHero, etc.
|
|
9
|
+
- **Providers** — adapters (`Image`/`Link`/`fetcher`), site config,
|
|
10
|
+
article pool, speech.
|
|
11
|
+
- **Hooks** — `useTheme`, `useSiteConfig`, `useCategories`, `useBanners`,
|
|
12
|
+
`useArticlePool`, `useArticles`, `useSpeech`, etc.
|
|
13
|
+
- **Utils** — `getFechaHora`, `contrastRatio`, `hexToCssFilter`, `ensureContrast`.
|
|
14
|
+
|
|
15
|
+
> El paquete ship-ea `.jsx` + `.scss` crudo. No hay build step propio: cada app
|
|
16
|
+
> los compila en su bundle (Next via `transpilePackages`, Vite out-of-the-box).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Índice
|
|
21
|
+
|
|
22
|
+
1. [Instalación](#instalación)
|
|
23
|
+
2. [Setup mínimo](#setup-mínimo)
|
|
24
|
+
3. [El adapter pattern — cómo funciona Next ↔ Vite](#el-adapter-pattern--cómo-funciona-next--vite)
|
|
25
|
+
4. [Workflow de desarrollo](#workflow-de-desarrollo)
|
|
26
|
+
5. [Cómo agregar un componente nuevo](#cómo-agregar-un-componente-nuevo)
|
|
27
|
+
6. [Publicar a npm](#publicar-a-npm)
|
|
28
|
+
7. [Trabajar entre varios devs](#trabajar-entre-varios-devs)
|
|
29
|
+
8. [Qué NO va en el paquete](#qué-no-va-en-el-paquete)
|
|
30
|
+
9. [Estructura](#estructura)
|
|
31
|
+
10. [API exportada](#api-exportada)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Instalación
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @crtobias/portal-ui
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`peerDependencies`:
|
|
42
|
+
- `react` ^19
|
|
43
|
+
- `react-dom` ^19
|
|
44
|
+
- `sass` ^1.98
|
|
45
|
+
|
|
46
|
+
**Next 15:** agregar a `transpilePackages` en `next.config.mjs`:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const nextConfig = {
|
|
50
|
+
transpilePackages: ['@crtobias/portal-ui'],
|
|
51
|
+
// ...
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Vite:** funciona out-of-the-box.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Setup mínimo
|
|
60
|
+
|
|
61
|
+
### Next (App Router)
|
|
62
|
+
|
|
63
|
+
```jsx
|
|
64
|
+
// src/app/PortalUIProviders.jsx
|
|
65
|
+
'use client'
|
|
66
|
+
import Image from 'next/image'
|
|
67
|
+
import Link from 'next/link'
|
|
68
|
+
import { AdaptersProvider } from '@crtobias/portal-ui'
|
|
69
|
+
import { clientFetch } from '@/lib/clientFetch' // tu fetcher con BASE_URL + tenant
|
|
70
|
+
|
|
71
|
+
export default function PortalUIProviders({ children }) {
|
|
72
|
+
return (
|
|
73
|
+
<AdaptersProvider value={{ Image, Link, fetcher: clientFetch }}>
|
|
74
|
+
{children}
|
|
75
|
+
</AdaptersProvider>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```jsx
|
|
81
|
+
// src/app/layout.jsx (server component)
|
|
82
|
+
import PortalUIProviders from './PortalUIProviders'
|
|
83
|
+
import { SiteConfigProvider } from '@crtobias/portal-ui'
|
|
84
|
+
|
|
85
|
+
export default async function RootLayout({ children }) {
|
|
86
|
+
const siteData = await fetchSiteConfig() // tu lógica
|
|
87
|
+
return (
|
|
88
|
+
<html>
|
|
89
|
+
<body>
|
|
90
|
+
<PortalUIProviders>
|
|
91
|
+
<SiteConfigProvider value={siteData}>
|
|
92
|
+
{children}
|
|
93
|
+
</SiteConfigProvider>
|
|
94
|
+
</PortalUIProviders>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Vite (CMS)
|
|
102
|
+
|
|
103
|
+
```jsx
|
|
104
|
+
// src/PortalUIProviders.jsx
|
|
105
|
+
import { AdaptersProvider } from '@crtobias/portal-ui'
|
|
106
|
+
import ImageShim from './shims/Image' // <img> plano
|
|
107
|
+
import LinkShim from './shims/Link' // <a> plano
|
|
108
|
+
import { backendFetch } from './lib/backendClient'
|
|
109
|
+
|
|
110
|
+
export default function PortalUIProviders({ children }) {
|
|
111
|
+
return (
|
|
112
|
+
<AdaptersProvider value={{ Image: ImageShim, Link: LinkShim, fetcher: backendFetch }}>
|
|
113
|
+
{children}
|
|
114
|
+
</AdaptersProvider>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```jsx
|
|
120
|
+
// src/main.jsx
|
|
121
|
+
<PortalUIProviders><App /></PortalUIProviders>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## El adapter pattern — cómo funciona Next ↔ Vite
|
|
127
|
+
|
|
128
|
+
### El problema
|
|
129
|
+
|
|
130
|
+
El portal usa **Next 15**, el CMS usa **Vite**. Ambos consumen los mismos
|
|
131
|
+
componentes. Si dentro de `portal-ui` hacemos:
|
|
132
|
+
|
|
133
|
+
```jsx
|
|
134
|
+
import Link from 'next/link'
|
|
135
|
+
import Image from 'next/image'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
…rompe en el CMS, porque Vite no resuelve esos paquetes. Y si hacemos:
|
|
139
|
+
|
|
140
|
+
```jsx
|
|
141
|
+
import { Link } from 'react-router-dom'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
…rompe en el portal, porque Next no usa react-router.
|
|
145
|
+
|
|
146
|
+
Tampoco queremos `if (process.env...)` ramificando código. Ni hacer dos copias
|
|
147
|
+
del paquete. Queremos **el mismo componente** funcionando en los dos lados.
|
|
148
|
+
|
|
149
|
+
### La solución: inyección de dependencias por contexto
|
|
150
|
+
|
|
151
|
+
`portal-ui` define una "interfaz" — tres cosas que cualquier app tiene que
|
|
152
|
+
proveer:
|
|
153
|
+
|
|
154
|
+
```jsx
|
|
155
|
+
// portal-ui/src/adapters/AdaptersContext.jsx
|
|
156
|
+
const AdaptersContext = createContext(null)
|
|
157
|
+
|
|
158
|
+
export function AdaptersProvider({ value, children }) {
|
|
159
|
+
return (
|
|
160
|
+
<AdaptersContext.Provider value={value}>
|
|
161
|
+
{children}
|
|
162
|
+
</AdaptersContext.Provider>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function useAdapters() {
|
|
167
|
+
const a = useContext(AdaptersContext)
|
|
168
|
+
if (!a) throw new Error('AdaptersProvider missing')
|
|
169
|
+
return a
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
La interfaz es: `{ Image, Link, fetcher }`.
|
|
174
|
+
|
|
175
|
+
### Cada app implementa la interfaz
|
|
176
|
+
|
|
177
|
+
**Portal (Next):**
|
|
178
|
+
```jsx
|
|
179
|
+
import Image from 'next/image' // optimización Next + lazy loading
|
|
180
|
+
import Link from 'next/link' // client-side navigation
|
|
181
|
+
import { clientFetch } from '@/lib/clientFetch' // fetch con BASE_URL + X-Tenant-ID
|
|
182
|
+
|
|
183
|
+
<AdaptersProvider value={{ Image, Link, fetcher: clientFetch }}>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**CMS (Vite):**
|
|
187
|
+
```jsx
|
|
188
|
+
// shims/Image.jsx
|
|
189
|
+
export default function Image({ src, alt, width, height, ...rest }) {
|
|
190
|
+
return <img src={src} alt={alt} width={width} height={height} {...rest} />
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// shims/Link.jsx
|
|
194
|
+
export default function Link({ href, children, ...rest }) {
|
|
195
|
+
return <a href={href} {...rest}>{children}</a>
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
<AdaptersProvider value={{ Image: ImageShim, Link: LinkShim, fetcher: backendFetch }}>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Dentro del paquete
|
|
202
|
+
|
|
203
|
+
```jsx
|
|
204
|
+
// portal-ui/src/components/ArticleCard/ArticleCard.jsx
|
|
205
|
+
'use client'
|
|
206
|
+
import { useAdapters } from '../../adapters/AdaptersContext.jsx'
|
|
207
|
+
|
|
208
|
+
export default function ArticleCard({ article }) {
|
|
209
|
+
const { Image, Link } = useAdapters() // ← traduce a Next o Vite según la app
|
|
210
|
+
return (
|
|
211
|
+
<Link href={article.slug}>
|
|
212
|
+
<Image src={article.imagen.url} alt={article.titulo} width={400} height={250} />
|
|
213
|
+
</Link>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**El componente no sabe** si está corriendo en Next o en Vite. Solo sabe que
|
|
219
|
+
hay un `Link` y un `Image` con cierta API.
|
|
220
|
+
|
|
221
|
+
### Diagrama
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
┌─────────────────────────────────────────┐
|
|
225
|
+
│ PORTAL (Next 15) │
|
|
226
|
+
│ ┌─────────────────────────────────────┐ │
|
|
227
|
+
│ │ AdaptersProvider │ │
|
|
228
|
+
│ │ value={{ │ │
|
|
229
|
+
│ │ Image: next/image, ─────┐ │ │
|
|
230
|
+
│ │ Link: next/link, ─────┤ │ │
|
|
231
|
+
│ │ fetcher: clientFetch ─────┤ │ │
|
|
232
|
+
│ │ }} │ │ │
|
|
233
|
+
│ │ ┌─────────────────────────┐ │ │ │
|
|
234
|
+
│ │ │ <ArticleCard> │ │ │ │
|
|
235
|
+
│ │ │ useAdapters() ────────┴────┘ │ │
|
|
236
|
+
│ │ │ → next/image │ │ │
|
|
237
|
+
│ │ │ → next/link │ │ │
|
|
238
|
+
│ │ │ → clientFetch │ │ │
|
|
239
|
+
│ │ └─────────────────────────┘ │ │
|
|
240
|
+
│ └─────────────────────────────────────┘ │
|
|
241
|
+
└─────────────────────────────────────────┘
|
|
242
|
+
|
|
243
|
+
┌─────────────────────────────────────────┐
|
|
244
|
+
│ CMS (Vite) │
|
|
245
|
+
│ ┌─────────────────────────────────────┐ │
|
|
246
|
+
│ │ AdaptersProvider │ │
|
|
247
|
+
│ │ value={{ │ │
|
|
248
|
+
│ │ Image: ImgShim, ─────┐ │ │
|
|
249
|
+
│ │ Link: LinkShim, ─────┤ │ │
|
|
250
|
+
│ │ fetcher: backendFetch ─────┤ │ │
|
|
251
|
+
│ │ }} │ │ │
|
|
252
|
+
│ │ ┌─────────────────────────┐ │ │ │
|
|
253
|
+
│ │ │ <ArticleCard> (MISMO) │ │ │ │
|
|
254
|
+
│ │ │ useAdapters() ────────┴────┘ │ │
|
|
255
|
+
│ │ │ → <img> │ │ │
|
|
256
|
+
│ │ │ → <a> │ │ │
|
|
257
|
+
│ │ │ → backendFetch │ │ │
|
|
258
|
+
│ │ └─────────────────────────┘ │ │
|
|
259
|
+
│ └─────────────────────────────────────┘ │
|
|
260
|
+
└─────────────────────────────────────────┘
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Por qué funciona
|
|
264
|
+
|
|
265
|
+
1. **Sin acoplamiento.** El paquete no importa nada de Next ni de Vite. Solo
|
|
266
|
+
importa `useAdapters` de su propio contexto. Cero dependencias en
|
|
267
|
+
`package.json` aparte de las peer deps.
|
|
268
|
+
|
|
269
|
+
2. **Sin if/else.** No hay ramas tipo `if (isNext) ... else ...`. El
|
|
270
|
+
componente usa una sola API uniforme.
|
|
271
|
+
|
|
272
|
+
3. **Extensible.** Si mañana hace falta otro adapter (ej. `navigate` para
|
|
273
|
+
programmatic navigation), se agrega al objeto del provider. Las dos apps
|
|
274
|
+
lo implementan a su modo.
|
|
275
|
+
|
|
276
|
+
4. **SSR-friendly.** En Next, `next/image` se renderiza server-side con
|
|
277
|
+
`<picture>` y srcset. En Vite/CMS, `<img>` plano alcanza para preview.
|
|
278
|
+
Ningún componente del paquete tiene que saberlo.
|
|
279
|
+
|
|
280
|
+
### Otros adapters internos del paquete
|
|
281
|
+
|
|
282
|
+
Además del context principal, el paquete tiene **3 contexts más** para
|
|
283
|
+
inyectar "data" en lugar de "primitives":
|
|
284
|
+
|
|
285
|
+
| Context | Qué provee la app | Ejemplo Next | Ejemplo Vite |
|
|
286
|
+
|---|---|---|---|
|
|
287
|
+
| `AdaptersContext` | `Image`, `Link`, `fetcher` | `next/image`, `next/link`, `clientFetch` | `<img>`, `<a>`, `backendFetch` |
|
|
288
|
+
| `SiteConfigContext` | `theme`, `slots`, `categories`, `banners` | desde `layout.jsx` async fetch | desde `EditableHomePreview` state |
|
|
289
|
+
| `ArticlePoolContext` | tracker de dedup de artículos | sólo en `<Home>` (request-scoped) | sólo en modo Lector del CMS |
|
|
290
|
+
| `SpeechContext` | estado de Web Speech API | montado una vez en layout | montado una vez en App |
|
|
291
|
+
|
|
292
|
+
Todos siguen el mismo patrón: `XxxProvider` arriba en el árbol, `useXxx()`
|
|
293
|
+
adentro de los componentes.
|
|
294
|
+
|
|
295
|
+
### Casos donde el adapter no alcanza
|
|
296
|
+
|
|
297
|
+
| Caso | Solución |
|
|
298
|
+
|---|---|
|
|
299
|
+
| Router programático (`router.push`) | Convertir a `<form action="...">` nativo o aceptar `onNavigate` como prop. Ya lo hicimos en `MenuDrawer`. |
|
|
300
|
+
| `next/font/google` | Cargar fonts en CSS global de cada app, no en el componente |
|
|
301
|
+
| `next/dynamic` lazy load | Cada app decide cuándo lazy-loadear el componente |
|
|
302
|
+
| Web Speech API | Wraperar en context propio (`SpeechProvider`) que sea opcional |
|
|
303
|
+
|
|
304
|
+
### Resumen
|
|
305
|
+
|
|
306
|
+
El paquete es **agnóstico al framework**. Las "diferencias", entre Next y Vite
|
|
307
|
+
viven en 5 líneas de JSX en cada `PortalUIProviders.jsx` de cada app. Todo lo
|
|
308
|
+
demás (componentes, hooks, styles) es código portable.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Workflow de desarrollo
|
|
313
|
+
|
|
314
|
+
### Local (varios escenarios)
|
|
315
|
+
|
|
316
|
+
#### A. `file:` link (recomendado para dev de día a día)
|
|
317
|
+
|
|
318
|
+
En el `package.json` de cada app:
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
"@crtobias/portal-ui": "file:../portal-ui"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`npm install` crea un symlink: `node_modules/@crtobias/portal-ui` → `../portal-ui/`. **Cualquier cambio en `portal-ui/src/` aparece al toque** en las apps con HMR. **No tenés que publicar nada.**
|
|
325
|
+
|
|
326
|
+
Cuando hagas un cambio que rompa, ambas apps lo ven en el siguiente reload. Cuando estás contento, lo publicás.
|
|
327
|
+
|
|
328
|
+
> Ojo Vite: a veces no invalida el cache de file: deps con HMR. Si parece "viejo":
|
|
329
|
+
> ```bash
|
|
330
|
+
> rm -rf node_modules/.vite && npm run dev
|
|
331
|
+
> ```
|
|
332
|
+
|
|
333
|
+
#### B. `npm link`
|
|
334
|
+
|
|
335
|
+
Mismo efecto que `file:`, pero global a tu máquina:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
cd portal-ui && npm link
|
|
339
|
+
|
|
340
|
+
cd ../editor-template-front && npm link @crtobias/portal-ui
|
|
341
|
+
cd ../cms-editor-front && npm link @crtobias/portal-ui
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Para desconectar:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
cd editor-template-front && npm unlink @crtobias/portal-ui && npm install
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### C. Versión publicada (producción / otro dev sin acceso al disco)
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
"@crtobias/portal-ui": "^1.0.0"
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
npm install
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
En este modo NO se ven cambios locales. Hay que publicar.
|
|
361
|
+
|
|
362
|
+
### ¿Cuándo necesito publicar?
|
|
363
|
+
|
|
364
|
+
| Acción | Local con `file:` / `npm link` | Producción / otro dev |
|
|
365
|
+
|---|---|---|
|
|
366
|
+
| Editar un .scss / .jsx | ✅ instantáneo | ❌ publicar nueva versión |
|
|
367
|
+
| Agregar un componente | ✅ instantáneo | ❌ publicar nueva versión |
|
|
368
|
+
| Cambiar API (props, exports) | ✅ instantáneo | ❌ publicar major / minor |
|
|
369
|
+
| Bug fix urgente en prod | — | ❌ publicar patch |
|
|
370
|
+
|
|
371
|
+
**Regla:** publicás cuando alguien (incluyendo CI) que no tiene tu `~/Desktop/portal-ui` necesita el cambio.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Cómo agregar un componente nuevo
|
|
376
|
+
|
|
377
|
+
Te tomo un caso concreto: querés agregar un componente `Newsletter` para
|
|
378
|
+
mostrar un formulario de suscripción.
|
|
379
|
+
|
|
380
|
+
### 1. Crear los archivos en `portal-ui`
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
src/components/Newsletter/
|
|
384
|
+
Newsletter.jsx
|
|
385
|
+
Newsletter.module.scss
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
```jsx
|
|
389
|
+
// src/components/Newsletter/Newsletter.jsx
|
|
390
|
+
'use client'
|
|
391
|
+
|
|
392
|
+
import { useState } from 'react'
|
|
393
|
+
import styles from './Newsletter.module.scss'
|
|
394
|
+
import { useAdapters } from '../../adapters/AdaptersContext.jsx'
|
|
395
|
+
import { useTheme } from '../../context/SiteConfigContext.jsx'
|
|
396
|
+
|
|
397
|
+
export default function Newsletter({ titulo = 'Suscribite' }) {
|
|
398
|
+
const { fetcher } = useAdapters()
|
|
399
|
+
const theme = useTheme()
|
|
400
|
+
const [email, setEmail] = useState('')
|
|
401
|
+
|
|
402
|
+
const handleSubmit = async (e) => {
|
|
403
|
+
e.preventDefault()
|
|
404
|
+
await fetcher('/api/portal/newsletter', {
|
|
405
|
+
method: 'POST',
|
|
406
|
+
body: JSON.stringify({ email }),
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<form
|
|
412
|
+
className={styles.container}
|
|
413
|
+
style={{ '--primary': theme.primary }}
|
|
414
|
+
onSubmit={handleSubmit}
|
|
415
|
+
>
|
|
416
|
+
<h3 className={styles.titulo}>{titulo}</h3>
|
|
417
|
+
<input
|
|
418
|
+
type="email"
|
|
419
|
+
value={email}
|
|
420
|
+
onChange={e => setEmail(e.target.value)}
|
|
421
|
+
placeholder="tu@email.com"
|
|
422
|
+
/>
|
|
423
|
+
<button type="submit">Suscribirme</button>
|
|
424
|
+
</form>
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```scss
|
|
430
|
+
/* src/components/Newsletter/Newsletter.module.scss */
|
|
431
|
+
@use "../../styles/index" as *;
|
|
432
|
+
|
|
433
|
+
.container { /* ... */ }
|
|
434
|
+
.titulo { color: var(--primary); }
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 2. Reglas para el código del componente
|
|
438
|
+
|
|
439
|
+
| ✅ Hacer | ❌ Evitar |
|
|
440
|
+
|---|---|
|
|
441
|
+
| `import { useAdapters }` para `Image`, `Link`, `fetcher` | `import Link from 'next/link'` |
|
|
442
|
+
| `import { useTheme }` para colores/fonts del site | acceder a `process.env` |
|
|
443
|
+
| Paths relativos: `../../adapters/AdaptersContext.jsx` | Path aliases: `@/components/...` |
|
|
444
|
+
| `'use client'` si usa hooks o estado | `async function` si va a usar hooks (ver split data/view abajo) |
|
|
445
|
+
| Para componentes que necesitan data: aceptar `articles`/`article` como prop | Hacer `await fetch(...)` dentro del componente del paquete |
|
|
446
|
+
| Para tracking client: `useAdapters().fetcher` | `process.env.NEXT_PUBLIC_X` |
|
|
447
|
+
|
|
448
|
+
### 3. Si tu componente necesita fetchear (split data/view)
|
|
449
|
+
|
|
450
|
+
Patrón: el paquete expone el **View**, cada app implementa la **data layer**.
|
|
451
|
+
|
|
452
|
+
```jsx
|
|
453
|
+
// portal-ui/src/components/Newsletter/NewsletterView.jsx
|
|
454
|
+
'use client'
|
|
455
|
+
export default function NewsletterView({ subscribers, onSubmit }) {
|
|
456
|
+
return (
|
|
457
|
+
<div>
|
|
458
|
+
<span>{subscribers.length} suscriptos</span>
|
|
459
|
+
<button onClick={onSubmit}>Suscribirme</button>
|
|
460
|
+
</div>
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
```jsx
|
|
466
|
+
// editor-template-front/src/components/Newsletter/Newsletter.jsx (data layer)
|
|
467
|
+
import { backendFetch } from '@/lib/backendClient'
|
|
468
|
+
import { NewsletterView } from '@crtobias/portal-ui'
|
|
469
|
+
|
|
470
|
+
export default async function Newsletter({ settings }) {
|
|
471
|
+
const subs = await fetchSubscribers(backendFetch)
|
|
472
|
+
return <NewsletterView subscribers={subs} />
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
```jsx
|
|
477
|
+
// cms-editor-front/src/previewHome/components/Newsletter/Newsletter.jsx (data layer)
|
|
478
|
+
import { useEffect, useState } from 'react'
|
|
479
|
+
import { backendFetch } from '@/lib/backendClient'
|
|
480
|
+
import { NewsletterView } from '@crtobias/portal-ui'
|
|
481
|
+
|
|
482
|
+
export default function Newsletter({ settings }) {
|
|
483
|
+
const [subs, setSubs] = useState([])
|
|
484
|
+
useEffect(() => { fetchSubscribers(backendFetch).then(setSubs) }, [])
|
|
485
|
+
return <NewsletterView subscribers={subs} />
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Mismos componentes que ya hicieron split data/view: `Feed`, `Hero`, `Recommended`,
|
|
490
|
+
`Cabezal`, `Banner`, `Clima`, `TextWrap`, `ArticleBody`.
|
|
491
|
+
|
|
492
|
+
### 4. Agregar al barrel
|
|
493
|
+
|
|
494
|
+
```js
|
|
495
|
+
// src/index.js
|
|
496
|
+
export { default as Newsletter } from './components/Newsletter/Newsletter.jsx'
|
|
497
|
+
// o si es split data/view:
|
|
498
|
+
export { default as NewsletterView } from './components/Newsletter/NewsletterView.jsx'
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### 5. (Opcional) Shim en las apps para no tocar callers
|
|
502
|
+
|
|
503
|
+
Si ya hay código importando `@/components/Newsletter/Newsletter`, mantené ese
|
|
504
|
+
path con un re-export:
|
|
505
|
+
|
|
506
|
+
```jsx
|
|
507
|
+
// editor-template-front/src/components/Newsletter/Newsletter.jsx
|
|
508
|
+
'use client'
|
|
509
|
+
export { Newsletter as default } from '@crtobias/portal-ui'
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Así no hay que tocar 20 callers — el caller sigue importando como antes, pero
|
|
513
|
+
termina yendo al paquete.
|
|
514
|
+
|
|
515
|
+
### 6. Probar local
|
|
516
|
+
|
|
517
|
+
Con `file:` link o `npm link` activo, las dos apps levantan con el componente
|
|
518
|
+
nuevo sin publicar:
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
cd ~/Desktop/editor-template-front && npm run dev # http://localhost:3000
|
|
522
|
+
cd ~/Desktop/cms-editor-front && npm run dev # http://localhost:5173
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### 7. Cuando funcione, publicar
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
cd ~/Desktop/portal-ui
|
|
529
|
+
npm run release:minor # componente nuevo = minor
|
|
530
|
+
git push --follow-tags
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
(Si CI hace publish automático, basta con `git push`.)
|
|
534
|
+
|
|
535
|
+
### 8. En las apps, traer la versión nueva
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
cd ~/Desktop/editor-template-front && npm install @crtobias/portal-ui@latest
|
|
539
|
+
cd ~/Desktop/cms-editor-front && npm install @crtobias/portal-ui@latest
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## Publicar a npm
|
|
545
|
+
|
|
546
|
+
### Setup una vez por máquina
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
npm login # con tu cuenta crtobiasdev
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Publicar
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
cd ~/Desktop/portal-ui
|
|
556
|
+
|
|
557
|
+
npm run release:patch # 1.0.0 → 1.0.1 bug fix
|
|
558
|
+
npm run release:minor # 1.0.0 → 1.1.0 componente nuevo / feature
|
|
559
|
+
npm run release:major # 1.0.0 → 2.0.0 breaking change (rename, remove prop)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Los scripts hacen `npm version <bump>` (commit + tag automáticos) y
|
|
563
|
+
`npm publish --access public` en una sola pasada.
|
|
564
|
+
|
|
565
|
+
Después:
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
git push --follow-tags
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
## Trabajar entre varios devs
|
|
574
|
+
|
|
575
|
+
### El problema
|
|
576
|
+
|
|
577
|
+
Si sos el único que publica, hay que decidir cómo:
|
|
578
|
+
- Los otros devs **proponen** cambios al paquete
|
|
579
|
+
- Quién **autoriza** y **publica**
|
|
580
|
+
|
|
581
|
+
### Modelo recomendado: CI publica al merge
|
|
582
|
+
|
|
583
|
+
**Setup:**
|
|
584
|
+
|
|
585
|
+
1. **Cada dev tiene cuenta de GitHub propia** (no compartir la tuya)
|
|
586
|
+
2. **Sumalos como collaborators** al repo `portal-ui` en GitHub
|
|
587
|
+
(Settings → Collaborators)
|
|
588
|
+
3. **La cuenta de npm queda SOLO tuya** — los devs no necesitan acceso a npm
|
|
589
|
+
4. **GitHub Actions publica automático** cuando vos mergeás a `main`
|
|
590
|
+
|
|
591
|
+
**Workflow para un dev nuevo:**
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
# Una vez
|
|
595
|
+
git clone git@github.com:crtobias/portal-ui.git
|
|
596
|
+
git clone git@github.com:crtobias/editor-template-front.git
|
|
597
|
+
git clone git@github.com:crtobias/cms-editor-front.git
|
|
598
|
+
|
|
599
|
+
# Asegurarse que las apps usen el paquete local
|
|
600
|
+
cd portal-ui && npm link
|
|
601
|
+
cd ../editor-template-front && npm link @crtobias/portal-ui
|
|
602
|
+
cd ../cms-editor-front && npm link @crtobias/portal-ui
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Para cada cambio:**
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
cd portal-ui
|
|
609
|
+
git checkout -b mi-feature
|
|
610
|
+
# ...editar...
|
|
611
|
+
git commit -am "feat: agregar Newsletter"
|
|
612
|
+
git push origin mi-feature
|
|
613
|
+
# Crear PR en GitHub
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Vos (mantenedor):**
|
|
617
|
+
1. Revisás el PR
|
|
618
|
+
2. Mergeás a `main`
|
|
619
|
+
3. CI corre `npm publish` automático
|
|
620
|
+
4. Avisás en Slack/Discord: "Nueva versión 1.1.0 — bumpean en las apps"
|
|
621
|
+
|
|
622
|
+
### GitHub Actions — workflow de auto-publish
|
|
623
|
+
|
|
624
|
+
Crear `.github/workflows/publish.yml`:
|
|
625
|
+
|
|
626
|
+
```yaml
|
|
627
|
+
name: Publish to npm
|
|
628
|
+
|
|
629
|
+
on:
|
|
630
|
+
push:
|
|
631
|
+
branches: [main]
|
|
632
|
+
paths:
|
|
633
|
+
- 'src/**'
|
|
634
|
+
- 'package.json'
|
|
635
|
+
|
|
636
|
+
jobs:
|
|
637
|
+
publish:
|
|
638
|
+
runs-on: ubuntu-latest
|
|
639
|
+
steps:
|
|
640
|
+
- uses: actions/checkout@v4
|
|
641
|
+
with:
|
|
642
|
+
fetch-depth: 0
|
|
643
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
644
|
+
|
|
645
|
+
- uses: actions/setup-node@v4
|
|
646
|
+
with:
|
|
647
|
+
node-version: '20'
|
|
648
|
+
registry-url: 'https://registry.npmjs.org'
|
|
649
|
+
|
|
650
|
+
- name: Bump patch version
|
|
651
|
+
run: |
|
|
652
|
+
git config user.name "github-actions[bot]"
|
|
653
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
654
|
+
npm version patch -m "chore: release %s [skip ci]"
|
|
655
|
+
git push --follow-tags
|
|
656
|
+
|
|
657
|
+
- name: Publish
|
|
658
|
+
run: npm publish --access public
|
|
659
|
+
env:
|
|
660
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Setup del token:**
|
|
664
|
+
1. En npmjs.com → Profile → Access Tokens → "Generate New Token"
|
|
665
|
+
(tipo "Automation")
|
|
666
|
+
2. Copiá el token
|
|
667
|
+
3. En GitHub repo → Settings → Secrets and variables → Actions → New secret
|
|
668
|
+
- Name: `NPM_TOKEN`
|
|
669
|
+
- Value: pegá el token
|
|
670
|
+
|
|
671
|
+
> Este workflow bumpea **patch** cada merge a main. Para minor/major, los devs
|
|
672
|
+
> ponen en el commit `[minor]` o `[major]` y agregás lógica al workflow para
|
|
673
|
+
> parsear eso. O publicás manualmente para versiones grandes.
|
|
674
|
+
|
|
675
|
+
### Si NO querés CI
|
|
676
|
+
|
|
677
|
+
Alternativa simple: los devs proponen PRs, vos publicás manualmente al mergear:
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
git checkout main && git pull
|
|
681
|
+
npm run release:minor
|
|
682
|
+
git push --follow-tags
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Permisos a tener en cuenta
|
|
686
|
+
|
|
687
|
+
| Recurso | A quién dar acceso | Cómo |
|
|
688
|
+
|---|---|---|
|
|
689
|
+
| Repo de GitHub `portal-ui` | Todos los devs | Settings → Collaborators (write para mergear, read para PRs) |
|
|
690
|
+
| Repos de las apps | Todos los devs | Igual |
|
|
691
|
+
| Cuenta npm `crtobiasdev` | Solo vos | — |
|
|
692
|
+
| `NPM_TOKEN` en GitHub Secrets | Generado por vos, accesible solo por CI | — |
|
|
693
|
+
| Bump de versión | Solo CI (o vos) | Workflow / manual |
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## Qué NO va en el paquete
|
|
698
|
+
|
|
699
|
+
| ❌ NO | Por qué | Solución |
|
|
700
|
+
|---|---|---|
|
|
701
|
+
| `next/*` (Image, Link, font, navigation) | Acopla a Next | Inyectar via `useAdapters()` |
|
|
702
|
+
| `@mui/*` | Solo CMS lo usa | Vive en `cms-editor-front` |
|
|
703
|
+
| `@dnd-kit/*` | Solo el editor lo usa | Vive en `cms-editor-front` |
|
|
704
|
+
| `fs`, env del server, secrets | No es código de cliente | Cada app |
|
|
705
|
+
| `React.cache()` | Acopla a Next runtime | Cada app lo usa con `createArticlePool` |
|
|
706
|
+
| Path alias `@/` | El paquete no controla resolver | Imports relativos |
|
|
707
|
+
| `process.env.X` | No portable | Settings via prop o context |
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Estructura
|
|
712
|
+
|
|
713
|
+
```
|
|
714
|
+
src/
|
|
715
|
+
├── adapters/
|
|
716
|
+
│ └── AdaptersContext.jsx AdaptersProvider, useAdapters
|
|
717
|
+
├── context/
|
|
718
|
+
│ ├── SiteConfigContext.jsx SiteConfigProvider + hooks
|
|
719
|
+
│ └── SpeechContext.jsx SpeechProvider, useSpeech
|
|
720
|
+
├── data/
|
|
721
|
+
│ ├── ArticlePoolContext.jsx createArticlePool, useArticlePool
|
|
722
|
+
│ ├── useArticles.js hook universal
|
|
723
|
+
│ └── index.js
|
|
724
|
+
├── constants/imageSizes.js
|
|
725
|
+
├── utils/
|
|
726
|
+
│ ├── fechaHora.js getFechaHora
|
|
727
|
+
│ └── colorContrast.js contrastRatio, hexToCssFilter, ensureContrast
|
|
728
|
+
├── styles/ SCSS partials compartidos
|
|
729
|
+
│ ├── index.scss
|
|
730
|
+
│ ├── mixins/
|
|
731
|
+
│ └── variables/
|
|
732
|
+
└── components/
|
|
733
|
+
├── UI/ AspectImage, FocalImage, Icon, IconSmall,
|
|
734
|
+
│ PageWrapper, ToolTip
|
|
735
|
+
├── DateTime/
|
|
736
|
+
├── AuthorBlock/ 4 variants
|
|
737
|
+
├── Breadcrumb/ 5 variants
|
|
738
|
+
├── ShareBlock/ 2 variants
|
|
739
|
+
├── Cards/
|
|
740
|
+
│ ├── ArticleCard/
|
|
741
|
+
│ ├── Bajada/ 2 variants
|
|
742
|
+
│ └── ArticleBody/ View only (data layer en cada app)
|
|
743
|
+
├── Headers/HeaderSimple/ HeaderSimpleSwitch (+ forceMode para CMS)
|
|
744
|
+
│ Desktop / Mobile / Compact / Amp
|
|
745
|
+
│ + sub-componentes (CategoriesBar,
|
|
746
|
+
│ LiveBanner, MenuDrawer, etc.)
|
|
747
|
+
├── Footers/FooterSimple/
|
|
748
|
+
├── Blocks/ Containers que iteran widgets via registry
|
|
749
|
+
├── ArticleHero/ 8 variants
|
|
750
|
+
├── ArticleHeroFull/
|
|
751
|
+
├── ArticleSidebar/
|
|
752
|
+
├── EditorOutput/ Renderer de Editor.js (con AMP)
|
|
753
|
+
├── EditorOutputFull/
|
|
754
|
+
├── SpeechButton/
|
|
755
|
+
├── SpeechPlayerBar/
|
|
756
|
+
├── SpeechProviderWrapper/
|
|
757
|
+
├── Feed/ FeedView (data layer en cada app)
|
|
758
|
+
├── Hero/ HeroView
|
|
759
|
+
├── Recommended/ RecommendedView
|
|
760
|
+
├── Cabezal/ CabezalView + 18 variants + 9 CardCabezal
|
|
761
|
+
├── Banner/ BannerView + BannerDisplay (tracking)
|
|
762
|
+
├── Clima/ ClimaView
|
|
763
|
+
├── TextWrap/ TextWrapView
|
|
764
|
+
├── DolarTicker/ Self-fetching client
|
|
765
|
+
└── DolarTickerOriginal/
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
## API exportada
|
|
771
|
+
|
|
772
|
+
```js
|
|
773
|
+
import {
|
|
774
|
+
// Adapters
|
|
775
|
+
AdaptersProvider, useAdapters, useOptionalAdapters,
|
|
776
|
+
|
|
777
|
+
// Site config
|
|
778
|
+
SiteConfigProvider, PreviewThemeProvider,
|
|
779
|
+
useSiteConfig, useTheme, useRawConfig,
|
|
780
|
+
useCategories, useBanners, useComputed, useInfoPages,
|
|
781
|
+
|
|
782
|
+
// Article pool
|
|
783
|
+
ArticlePoolProvider, useArticlePool, createArticlePool,
|
|
784
|
+
useArticles,
|
|
785
|
+
|
|
786
|
+
// Speech
|
|
787
|
+
SpeechProvider, useSpeech,
|
|
788
|
+
|
|
789
|
+
// Utils
|
|
790
|
+
getFechaHora,
|
|
791
|
+
contrastRatio, hexToCssFilter, ensureContrast,
|
|
792
|
+
|
|
793
|
+
// Constants
|
|
794
|
+
IMAGE_SIZES,
|
|
795
|
+
|
|
796
|
+
// UI primitives
|
|
797
|
+
AspectImage, FocalImage, Icon, IconSmall, PageWrapper, ToolTip,
|
|
798
|
+
|
|
799
|
+
// Componentes
|
|
800
|
+
DateTime, AuthorBlock, Breadcrumb, ShareBlock,
|
|
801
|
+
ArticleCard, Bajada,
|
|
802
|
+
ArticleHero, ArticleHeroFull, ArticleSidebar,
|
|
803
|
+
HeaderSimpleSwitch, HeaderSimpleDesktop, HeaderSimpleDesktopCompact,
|
|
804
|
+
HeaderSimpleMobile, HeaderSimpleAmp,
|
|
805
|
+
FooterSimple,
|
|
806
|
+
BlockColumns, BlockColumnsBajada, BlockMain, BlockMainNarrow,
|
|
807
|
+
BlockMainSidebar, BlockStack, WidgetErrorBoundary,
|
|
808
|
+
EditorOutput, EditorBlocks, EditorOutputFull, EditorBlocksFull,
|
|
809
|
+
SpeechButton, SpeechPlayerBar, SpeechProviderWrapper,
|
|
810
|
+
DolarTicker, DolarTickerOriginal,
|
|
811
|
+
|
|
812
|
+
// Views (data layer en cada app)
|
|
813
|
+
FeedView, HeroView, RecommendedView, CabezalView,
|
|
814
|
+
BannerView, BannerDisplay, ClimaView,
|
|
815
|
+
TextWrapView, ArticleBodyView,
|
|
816
|
+
} from '@crtobias/portal-ui'
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## Convención de versiones
|
|
822
|
+
|
|
823
|
+
- **patch** (`1.0.0 → 1.0.1`): bug fix sin cambios de API
|
|
824
|
+
- **minor** (`1.0.0 → 1.1.0`): componente nuevo, prop opcional nueva
|
|
825
|
+
- **major** (`1.0.0 → 2.0.0`): breaking change (rename, remove prop,
|
|
826
|
+
cambio de shape, cambio de signature de hook)
|
|
827
|
+
|
|
828
|
+
Pinear con `^` para auto-update minor/patch en las apps. Para producción
|
|
829
|
+
estable, pinear exact (`"@crtobias/portal-ui": "1.1.2"`).
|