@otl-core/next-footer 1.1.3
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/dist/index.d.ts +14 -0
- package/dist/index.js +313 -0
- package/dist/index.js.map +1 -0
- package/package.json +95 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FooterConfig, Site } from '@otl-core/cms-types';
|
|
2
|
+
import { BlockRegistry } from '@otl-core/block-registry';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
interface FooterProps {
|
|
6
|
+
footer: FooterConfig;
|
|
7
|
+
site: Site;
|
|
8
|
+
className?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
blockRegistry?: BlockRegistry;
|
|
11
|
+
}
|
|
12
|
+
declare const Footer: React.FC<FooterProps>;
|
|
13
|
+
|
|
14
|
+
export { Footer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { isResponsiveConfig } from '@otl-core/cms-utils';
|
|
2
|
+
import { cn, resolveColorToCSS } from '@otl-core/style-utils';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import ReactMarkdown from 'react-markdown';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
import { BlockRenderer as BlockRenderer$1 } from '@otl-core/block-registry';
|
|
7
|
+
|
|
8
|
+
// src/lib/footer.utils.ts
|
|
9
|
+
function resolveFooterColors(style) {
|
|
10
|
+
const resolved = {
|
|
11
|
+
background: resolveColorToCSS(style.background),
|
|
12
|
+
text: resolveColorToCSS(style.text),
|
|
13
|
+
linkColor: resolveColorToCSS(style.link.color),
|
|
14
|
+
linkHoverColor: resolveColorToCSS(style.link.hoverColor)
|
|
15
|
+
};
|
|
16
|
+
return resolved;
|
|
17
|
+
}
|
|
18
|
+
function getResponsiveBase(value) {
|
|
19
|
+
if (typeof value === "object" && value !== null && "base" in value) {
|
|
20
|
+
return value.base;
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function resolveBorder(border) {
|
|
25
|
+
if (!border) return "";
|
|
26
|
+
const base = getResponsiveBase(border);
|
|
27
|
+
if (!base) return "";
|
|
28
|
+
const width = base.width || "0";
|
|
29
|
+
const style = base.style || "solid";
|
|
30
|
+
const color = base.color ? resolveColorToCSS(base.color) : "transparent";
|
|
31
|
+
return `${width} ${style} ${color}`;
|
|
32
|
+
}
|
|
33
|
+
function generateFooterCSS(id, footer, resolvedColors) {
|
|
34
|
+
const cssBlocks = [];
|
|
35
|
+
const containerBehavior = footer.style.container || "edged";
|
|
36
|
+
const baseMargin = getResponsiveBase(footer.style.layout.margin);
|
|
37
|
+
const basePadding = getResponsiveBase(footer.style.layout.padding);
|
|
38
|
+
const baseSectionGap = getResponsiveBase(footer.style.layout.sectionGap);
|
|
39
|
+
const borderCSS = resolveBorder(footer.style.border);
|
|
40
|
+
const baseStyles = `
|
|
41
|
+
.footer-${id} {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
${resolvedColors.background ? `background-color: ${resolvedColors.background};` : ""}
|
|
45
|
+
${resolvedColors.text ? `color: ${resolvedColors.text};` : ""}
|
|
46
|
+
${baseMargin ? `margin: ${baseMargin};` : ""}
|
|
47
|
+
${basePadding ? `padding: ${basePadding};` : ""}
|
|
48
|
+
${borderCSS ? `border: ${borderCSS};` : ""}
|
|
49
|
+
${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : ""}
|
|
50
|
+
${baseSectionGap ? `gap: ${baseSectionGap};` : ""}
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
cssBlocks.push(baseStyles);
|
|
54
|
+
if (containerBehavior === "edged") {
|
|
55
|
+
cssBlocks.push(`
|
|
56
|
+
.footer-${id} > div.container {
|
|
57
|
+
width: 100%;
|
|
58
|
+
}
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
cssBlocks.push(`
|
|
62
|
+
.footer-${id} .footer-section {
|
|
63
|
+
width: 100%;
|
|
64
|
+
}
|
|
65
|
+
`);
|
|
66
|
+
cssBlocks.push(`
|
|
67
|
+
@media (max-width: 767px) {
|
|
68
|
+
.footer-${id} .footer-container-section {
|
|
69
|
+
flex-wrap: wrap;
|
|
70
|
+
}
|
|
71
|
+
.footer-${id} .footer-container-section > .footer-content-section {
|
|
72
|
+
flex: 1 1 100%;
|
|
73
|
+
width: 100%;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`);
|
|
77
|
+
cssBlocks.push(`
|
|
78
|
+
@media (min-width: 768px) and (max-width: 1023px) {
|
|
79
|
+
.footer-${id} .footer-container-section {
|
|
80
|
+
flex-wrap: wrap;
|
|
81
|
+
}
|
|
82
|
+
.footer-${id} .footer-container-section > .footer-content-section {
|
|
83
|
+
flex: 1 1 calc(50% - ${baseSectionGap || "0px"} / 2);
|
|
84
|
+
min-width: 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`);
|
|
88
|
+
cssBlocks.push(`
|
|
89
|
+
@media (min-width: 1024px) {
|
|
90
|
+
.footer-${id} .footer-container-section {
|
|
91
|
+
flex-wrap: nowrap;
|
|
92
|
+
}
|
|
93
|
+
.footer-${id} .footer-container-section > .footer-content-section {
|
|
94
|
+
flex: 1 1 auto;
|
|
95
|
+
min-width: 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
`);
|
|
99
|
+
if (resolvedColors.linkColor || resolvedColors.linkHoverColor) {
|
|
100
|
+
cssBlocks.push(`
|
|
101
|
+
.footer-${id} a {
|
|
102
|
+
${resolvedColors.linkColor ? `color: ${resolvedColors.linkColor};` : ""}
|
|
103
|
+
text-decoration: none;
|
|
104
|
+
transition: color 0.2s;
|
|
105
|
+
}
|
|
106
|
+
.footer-${id} a:hover {
|
|
107
|
+
${resolvedColors.linkHoverColor ? `color: ${resolvedColors.linkHoverColor};` : ""}
|
|
108
|
+
}
|
|
109
|
+
`);
|
|
110
|
+
}
|
|
111
|
+
return minifyCSS(cssBlocks.filter(Boolean).join(""));
|
|
112
|
+
}
|
|
113
|
+
function formatShadow(shadow) {
|
|
114
|
+
const insetStr = shadow.inset ? "inset " : "";
|
|
115
|
+
return `${insetStr}${shadow.offsetX} ${shadow.offsetY} ${shadow.blurRadius} ${shadow.spreadRadius} ${shadow.color}`;
|
|
116
|
+
}
|
|
117
|
+
function minifyCSS(css) {
|
|
118
|
+
return css.replace(/\s+/g, " ").replace(/\s*{\s*/g, "{").replace(/\s*}\s*/g, "}").replace(/\s*:\s*/g, ":").replace(/\s*;\s*/g, ";").replace(/;\s*}/g, "}").trim();
|
|
119
|
+
}
|
|
120
|
+
function isContentSection(section) {
|
|
121
|
+
return "blocks" in section && Array.isArray(section.blocks);
|
|
122
|
+
}
|
|
123
|
+
var BlockRenderer = ({ block }) => {
|
|
124
|
+
const { type, config } = block;
|
|
125
|
+
if (type === "markdown") {
|
|
126
|
+
const content = config.content || "";
|
|
127
|
+
return /* @__PURE__ */ jsx("div", { className: "footer-block footer-markdown", children: /* @__PURE__ */ jsx(ReactMarkdown, { children: content }) });
|
|
128
|
+
}
|
|
129
|
+
if (type === "link") {
|
|
130
|
+
const linkConfig = config;
|
|
131
|
+
const target = linkConfig.external ? "_blank" : void 0;
|
|
132
|
+
const rel = linkConfig.external ? "noopener noreferrer" : void 0;
|
|
133
|
+
const variantClasses = {
|
|
134
|
+
default: "footer-link hover:underline transition-colors",
|
|
135
|
+
underline: "footer-link underline",
|
|
136
|
+
"no-underline": "footer-link no-underline",
|
|
137
|
+
"button-primary": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90",
|
|
138
|
+
"button-secondary": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
139
|
+
"button-outline": "footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors border border-gray-200 text-surface-foreground hover:bg-accent hover:text-accent-foreground"
|
|
140
|
+
};
|
|
141
|
+
const className = variantClasses[linkConfig.variant || "default"];
|
|
142
|
+
return /* @__PURE__ */ jsx(
|
|
143
|
+
"a",
|
|
144
|
+
{
|
|
145
|
+
href: linkConfig.href,
|
|
146
|
+
target,
|
|
147
|
+
rel,
|
|
148
|
+
className,
|
|
149
|
+
"aria-label": linkConfig.ariaLabel || linkConfig.text,
|
|
150
|
+
children: linkConfig.text
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (type === "button") {
|
|
155
|
+
const buttonConfig = config;
|
|
156
|
+
const target = buttonConfig.newTab ? "_blank" : void 0;
|
|
157
|
+
const rel = buttonConfig.newTab ? "noopener noreferrer" : void 0;
|
|
158
|
+
return /* @__PURE__ */ jsx(
|
|
159
|
+
"a",
|
|
160
|
+
{
|
|
161
|
+
href: buttonConfig.url,
|
|
162
|
+
target,
|
|
163
|
+
rel,
|
|
164
|
+
className: "footer-block footer-button inline-flex items-center justify-center px-4 py-2 rounded-md transition-colors",
|
|
165
|
+
children: buttonConfig.text
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (type === "image") {
|
|
170
|
+
const imageConfig = config;
|
|
171
|
+
return /* @__PURE__ */ jsx("div", { className: "footer-block footer-image", children: /* @__PURE__ */ jsx(
|
|
172
|
+
"img",
|
|
173
|
+
{
|
|
174
|
+
src: imageConfig.src,
|
|
175
|
+
alt: imageConfig.alt || "",
|
|
176
|
+
width: imageConfig.width,
|
|
177
|
+
height: imageConfig.height
|
|
178
|
+
}
|
|
179
|
+
) });
|
|
180
|
+
}
|
|
181
|
+
if (type === "divider") {
|
|
182
|
+
return /* @__PURE__ */ jsx("hr", { className: "footer-block footer-divider" });
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
};
|
|
186
|
+
var FooterSectionComponent = ({
|
|
187
|
+
section,
|
|
188
|
+
footer,
|
|
189
|
+
site,
|
|
190
|
+
resolvedColors,
|
|
191
|
+
level = 0,
|
|
192
|
+
blockRegistry
|
|
193
|
+
}) => {
|
|
194
|
+
const sectionStyle = useMemo(() => {
|
|
195
|
+
const style = {
|
|
196
|
+
display: "flex",
|
|
197
|
+
flexDirection: section.type === "row" ? "row" : "column",
|
|
198
|
+
alignItems: section.style.align || "flex-start",
|
|
199
|
+
justifyContent: section.style.justify || "flex-start",
|
|
200
|
+
gap: section.style.gap || "0"
|
|
201
|
+
};
|
|
202
|
+
if (section.style.flex) {
|
|
203
|
+
style.flex = section.style.flex;
|
|
204
|
+
}
|
|
205
|
+
if (section.style.background) {
|
|
206
|
+
style.backgroundColor = resolveColorToCSS(section.style.background);
|
|
207
|
+
}
|
|
208
|
+
if (section.style.padding) {
|
|
209
|
+
const padding = typeof section.style.padding === "object" && "base" in section.style.padding ? section.style.padding.base : section.style.padding;
|
|
210
|
+
style.padding = padding;
|
|
211
|
+
}
|
|
212
|
+
if (section.style.border) {
|
|
213
|
+
const border = typeof section.style.border === "object" && "base" in section.style.border ? section.style.border.base : section.style.border;
|
|
214
|
+
if (border) {
|
|
215
|
+
style.border = `${border.width || "0"} ${border.style || "solid"} ${border.color ? resolveColorToCSS(border.color) : "transparent"}`;
|
|
216
|
+
if (border.radius) {
|
|
217
|
+
style.borderRadius = border.radius;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return style;
|
|
222
|
+
}, [section]);
|
|
223
|
+
if (isContentSection(section)) {
|
|
224
|
+
return /* @__PURE__ */ jsx(
|
|
225
|
+
"div",
|
|
226
|
+
{
|
|
227
|
+
className: "footer-section footer-content-section",
|
|
228
|
+
style: sectionStyle,
|
|
229
|
+
"data-section-id": section.id,
|
|
230
|
+
children: section?.blocks?.map(
|
|
231
|
+
(block) => blockRegistry ? /* @__PURE__ */ jsx(
|
|
232
|
+
BlockRenderer$1,
|
|
233
|
+
{
|
|
234
|
+
block,
|
|
235
|
+
blockRegistry
|
|
236
|
+
},
|
|
237
|
+
block.id
|
|
238
|
+
) : /* @__PURE__ */ jsx(BlockRenderer, { block }, block.id)
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return /* @__PURE__ */ jsx(
|
|
244
|
+
"div",
|
|
245
|
+
{
|
|
246
|
+
className: "footer-section footer-container-section",
|
|
247
|
+
style: sectionStyle,
|
|
248
|
+
"data-section-id": section.id,
|
|
249
|
+
children: section?.sections?.sort((a, b) => a.order - b.order)?.map((childSection) => /* @__PURE__ */ jsx(
|
|
250
|
+
FooterSectionComponent,
|
|
251
|
+
{
|
|
252
|
+
section: childSection,
|
|
253
|
+
footer,
|
|
254
|
+
site,
|
|
255
|
+
resolvedColors,
|
|
256
|
+
level: level + 1,
|
|
257
|
+
blockRegistry
|
|
258
|
+
},
|
|
259
|
+
childSection.id
|
|
260
|
+
))
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
var Footer = ({
|
|
265
|
+
footer,
|
|
266
|
+
site,
|
|
267
|
+
className = "",
|
|
268
|
+
id = "default",
|
|
269
|
+
blockRegistry
|
|
270
|
+
}) => {
|
|
271
|
+
const resolvedColors = resolveFooterColors(footer.style);
|
|
272
|
+
const styles = generateFooterCSS(id, footer, resolvedColors);
|
|
273
|
+
const sortedSections = [...footer.sections].sort((a, b) => a.order - b.order);
|
|
274
|
+
const containerBehavior = footer.style.container || "edged";
|
|
275
|
+
const sectionsContent = sortedSections.map((section) => {
|
|
276
|
+
const sectionElement = /* @__PURE__ */ jsx(
|
|
277
|
+
FooterSectionComponent,
|
|
278
|
+
{
|
|
279
|
+
section,
|
|
280
|
+
footer,
|
|
281
|
+
site,
|
|
282
|
+
resolvedColors,
|
|
283
|
+
blockRegistry
|
|
284
|
+
},
|
|
285
|
+
section.id
|
|
286
|
+
);
|
|
287
|
+
if (containerBehavior === "edged") {
|
|
288
|
+
return /* @__PURE__ */ jsx("div", { className: "container mx-auto", children: sectionElement }, section.id);
|
|
289
|
+
}
|
|
290
|
+
return sectionElement;
|
|
291
|
+
});
|
|
292
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
293
|
+
styles && /* @__PURE__ */ jsx("style", { children: styles }),
|
|
294
|
+
/* @__PURE__ */ jsx(
|
|
295
|
+
"footer",
|
|
296
|
+
{
|
|
297
|
+
className: cn(
|
|
298
|
+
{
|
|
299
|
+
"container mx-auto": containerBehavior === "boxed"
|
|
300
|
+
},
|
|
301
|
+
className
|
|
302
|
+
),
|
|
303
|
+
"data-footer-id": id,
|
|
304
|
+
"data-container": containerBehavior,
|
|
305
|
+
children: /* @__PURE__ */ jsx("div", { className: `footer-${id} footer-container`, children: sectionsContent })
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
] });
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export { Footer };
|
|
312
|
+
//# sourceMappingURL=index.js.map
|
|
313
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/footer.utils.ts","../src/components/blocks/block-renderer.tsx","../src/components/footer/footer-section.tsx","../src/components/footer.tsx"],"names":["resolveColorToCSS","jsx","RegistryBlockRenderer"],"mappings":"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","file":"index.js","sourcesContent":["import {\n BorderConfig,\n FooterConfig,\n FooterContentSection,\n FooterSection,\n ResponsiveValue,\n ShadowConfig,\n} from \"@otl-core/cms-types\";\nimport { isResponsiveConfig } from \"@otl-core/cms-utils\";\nimport { resolveColorToCSS } from \"@otl-core/style-utils\";\n\n/**\n * Resolve all colors in the footer configuration to CSS values\n */\nexport function resolveFooterColors(\n style: FooterConfig[\"style\"],\n): Record<string, string | undefined> {\n const resolved: Record<string, string | undefined> = {\n background: resolveColorToCSS(style.background),\n text: resolveColorToCSS(style.text),\n linkColor: resolveColorToCSS(style.link.color),\n linkHoverColor: resolveColorToCSS(style.link.hoverColor),\n };\n\n return resolved;\n}\n\n/**\n * Resolve a responsive value to get the base value\n */\nfunction getResponsiveBase<T>(value: ResponsiveValue<T> | T): T {\n if (typeof value === \"object\" && value !== null && \"base\" in value) {\n return value.base;\n }\n return value as T;\n}\n\n/**\n * Resolve border config to CSS string\n */\nfunction resolveBorder(\n border: ResponsiveValue<BorderConfig> | undefined,\n): string {\n if (!border) return \"\";\n\n const base = getResponsiveBase(border);\n if (!base) return \"\";\n\n const width = base.width || \"0\";\n const style = base.style || \"solid\";\n const color = base.color ? resolveColorToCSS(base.color) : \"transparent\";\n\n return `${width} ${style} ${color}`;\n}\n\n/**\n * Generate CSS for the footer configuration\n */\nexport function generateFooterCSS(\n id: string,\n footer: FooterConfig,\n resolvedColors: Record<string, string | undefined>,\n): string {\n const cssBlocks: string[] = [];\n const containerBehavior = footer.style.container || \"edged\";\n\n // Base footer styles\n const baseMargin = getResponsiveBase(footer.style.layout.margin);\n const basePadding = getResponsiveBase(footer.style.layout.padding);\n const baseSectionGap = getResponsiveBase(footer.style.layout.sectionGap);\n const borderCSS = resolveBorder(footer.style.border);\n\n const baseStyles = `\n .footer-${id} {\n display: flex;\n flex-direction: column;\n ${resolvedColors.background ? `background-color: ${resolvedColors.background};` : \"\"}\n ${resolvedColors.text ? `color: ${resolvedColors.text};` : \"\"}\n ${baseMargin ? `margin: ${baseMargin};` : \"\"}\n ${basePadding ? `padding: ${basePadding};` : \"\"}\n ${borderCSS ? `border: ${borderCSS};` : \"\"}\n ${footer.style.shadow ? `box-shadow: ${formatShadow(isResponsiveConfig(footer.style.shadow) ? footer.style.shadow.base : footer.style.shadow)};` : \"\"}\n ${baseSectionGap ? `gap: ${baseSectionGap};` : \"\"}\n }\n `;\n cssBlocks.push(baseStyles);\n\n // Container-specific styles\n if (containerBehavior === \"edged\") {\n // In edged mode, each container wrapper should be full width\n cssBlocks.push(`\n .footer-${id} > div.container {\n width: 100%;\n }\n `);\n }\n\n // Top-level sections (rows) should always be full width\n cssBlocks.push(`\n .footer-${id} .footer-section {\n width: 100%;\n }\n `);\n\n // Responsive layout for nested sections (columns within rows)\n // Mobile: stack all columns vertically\n cssBlocks.push(`\n @media (max-width: 767px) {\n .footer-${id} .footer-container-section {\n flex-wrap: wrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 100%;\n width: 100%;\n }\n }\n `);\n\n // Tablet: allow 2 columns per row\n cssBlocks.push(`\n @media (min-width: 768px) and (max-width: 1023px) {\n .footer-${id} .footer-container-section {\n flex-wrap: wrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 calc(50% - ${baseSectionGap || \"0px\"} / 2);\n min-width: 0;\n }\n }\n `);\n\n // Desktop: columns naturally fit based on their flex properties\n cssBlocks.push(`\n @media (min-width: 1024px) {\n .footer-${id} .footer-container-section {\n flex-wrap: nowrap;\n }\n .footer-${id} .footer-container-section > .footer-content-section {\n flex: 1 1 auto;\n min-width: 0;\n }\n }\n `);\n\n // Link styles\n if (resolvedColors.linkColor || resolvedColors.linkHoverColor) {\n cssBlocks.push(`\n .footer-${id} a {\n ${resolvedColors.linkColor ? `color: ${resolvedColors.linkColor};` : \"\"}\n text-decoration: none;\n transition: color 0.2s;\n }\n .footer-${id} a:hover {\n ${resolvedColors.linkHoverColor ? `color: ${resolvedColors.linkHoverColor};` : \"\"}\n }\n `);\n }\n\n return minifyCSS(cssBlocks.filter(Boolean).join(\"\"));\n}\n\n/**\n * Format shadow config to CSS box-shadow value\n */\nfunction formatShadow(shadow: ShadowConfig): string {\n const insetStr = shadow.inset ? \"inset \" : \"\";\n return `${insetStr}${shadow.offsetX} ${shadow.offsetY} ${shadow.blurRadius} ${shadow.spreadRadius} ${shadow.color}`;\n}\n\n/**\n * Minify CSS by removing extra whitespace\n */\nfunction minifyCSS(css: string): string {\n return css\n .replace(/\\s+/g, \" \")\n .replace(/\\s*{\\s*/g, \"{\")\n .replace(/\\s*}\\s*/g, \"}\")\n .replace(/\\s*:\\s*/g, \":\")\n .replace(/\\s*;\\s*/g, \";\")\n .replace(/;\\s*}/g, \"}\")\n .trim();\n}\n\n/**\n * Check if a section has nested sections (is a container section)\n */\nexport function isContainerSection(\n section: FooterSection | FooterContentSection,\n): section is FooterSection {\n return \"sections\" in section && Array.isArray(section.sections);\n}\n\n/**\n * Check if a section is a content section (has items)\n */\nexport function isContentSection(\n section: FooterSection | FooterContentSection,\n): section is FooterContentSection {\n return \"blocks\" in section && Array.isArray(section.blocks);\n}\n","/**\n * Block Renderer for Footer Components\n * Renders blocks within the footer context\n * This is a minimal implementation that supports the blocks commonly used in footers\n */\n\nimport { BlockInstance } from \"@otl-core/cms-types\";\nimport React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\n\ninterface BlockRendererProps {\n block: BlockInstance;\n}\n\nexport const BlockRenderer: React.FC<BlockRendererProps> = ({ block }) => {\n const { type, config } = block;\n\n // Markdown block\n if (type === \"markdown\") {\n const content = (config as { content?: string }).content || \"\";\n return (\n <div className=\"footer-block footer-markdown\">\n <ReactMarkdown>{content}</ReactMarkdown>\n </div>\n );\n }\n\n // Link block\n if (type === \"link\") {\n const linkConfig = config as {\n text?: string;\n href?: string;\n external?: boolean;\n variant?: string;\n ariaLabel?: string;\n };\n\n const target = linkConfig.external ? \"_blank\" : undefined;\n const rel = linkConfig.external ? \"noopener noreferrer\" : undefined;\n\n const variantClasses = {\n default: \"footer-link hover:underline transition-colors\",\n underline: \"footer-link underline\",\n \"no-underline\": \"footer-link no-underline\",\n \"button-primary\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90\",\n \"button-secondary\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n \"button-outline\":\n \"footer-link inline-flex items-center justify-center px-4 py-2 rounded-md font-medium transition-colors border border-gray-200 text-surface-foreground hover:bg-accent hover:text-accent-foreground\",\n };\n\n const className =\n variantClasses[\n (linkConfig.variant as keyof typeof variantClasses) || \"default\"\n ];\n\n return (\n <a\n href={linkConfig.href}\n target={target}\n rel={rel}\n className={className}\n aria-label={linkConfig.ariaLabel || linkConfig.text}\n >\n {linkConfig.text}\n </a>\n );\n }\n\n // Button block (existing button rendering logic)\n if (type === \"button\") {\n const buttonConfig = config as {\n text?: string;\n url?: string;\n variant?: string;\n size?: string;\n newTab?: boolean;\n };\n\n const target = buttonConfig.newTab ? \"_blank\" : undefined;\n const rel = buttonConfig.newTab ? \"noopener noreferrer\" : undefined;\n\n return (\n <a\n href={buttonConfig.url}\n target={target}\n rel={rel}\n className=\"footer-block footer-button inline-flex items-center justify-center px-4 py-2 rounded-md transition-colors\"\n >\n {buttonConfig.text}\n </a>\n );\n }\n\n // Image block\n if (type === \"image\") {\n const imageConfig = config as {\n src?: string;\n alt?: string;\n width?: number;\n height?: number;\n };\n\n return (\n <div className=\"footer-block footer-image\">\n <img\n src={imageConfig.src}\n alt={imageConfig.alt || \"\"}\n width={imageConfig.width}\n height={imageConfig.height}\n />\n </div>\n );\n }\n\n // Divider block\n if (type === \"divider\") {\n return <hr className=\"footer-block footer-divider\" />;\n }\n\n // Fallback for unknown block types\n return null;\n};\n","import {\n Site,\n FooterConfig,\n FooterContentSection,\n FooterSection,\n} from \"@otl-core/cms-types\";\nimport React, { useMemo } from \"react\";\nimport { resolveColorToCSS } from \"@otl-core/style-utils\";\nimport { isContentSection } from \"../../lib/footer.utils\";\nimport { BlockRenderer as LocalBlockRenderer } from \"../blocks/block-renderer\";\nimport type { BlockRegistry } from \"@otl-core/block-registry\";\nimport { BlockRenderer as RegistryBlockRenderer } from \"@otl-core/block-registry\";\n\ninterface FooterSectionProps {\n section: FooterSection | FooterContentSection;\n footer: FooterConfig;\n site: Site;\n resolvedColors: Record<string, string | undefined>;\n level?: number;\n blockRegistry?: BlockRegistry;\n}\n\nexport const FooterSectionComponent: React.FC<FooterSectionProps> = ({\n section,\n footer,\n site,\n resolvedColors,\n level = 0,\n blockRegistry,\n}) => {\n const sectionStyle: React.CSSProperties = useMemo(() => {\n const style: React.CSSProperties = {\n display: \"flex\",\n flexDirection: section.type === \"row\" ? \"row\" : \"column\",\n alignItems: section.style.align || \"flex-start\",\n justifyContent: section.style.justify || \"flex-start\",\n gap: section.style.gap || \"0\",\n };\n\n if (section.style.flex) {\n style.flex = section.style.flex;\n }\n\n // Apply section-specific background\n if (section.style.background) {\n style.backgroundColor = resolveColorToCSS(section.style.background);\n }\n\n // Apply section-specific padding\n if (section.style.padding) {\n const padding =\n typeof section.style.padding === \"object\" &&\n \"base\" in section.style.padding\n ? section.style.padding.base\n : section.style.padding;\n style.padding = padding;\n }\n\n // Apply section-specific border\n if (section.style.border) {\n const border =\n typeof section.style.border === \"object\" &&\n \"base\" in section.style.border\n ? section.style.border.base\n : section.style.border;\n\n if (border) {\n style.border = `${border.width || \"0\"} ${border.style || \"solid\"} ${\n border.color ? resolveColorToCSS(border.color) : \"transparent\"\n }`;\n if (border.radius) {\n style.borderRadius = border.radius;\n }\n }\n }\n\n return style;\n }, [section]);\n\n // Content section - render blocks\n if (isContentSection(section)) {\n return (\n <div\n className=\"footer-section footer-content-section\"\n style={sectionStyle}\n data-section-id={section.id}\n >\n {section?.blocks?.map((block) =>\n blockRegistry ? (\n <RegistryBlockRenderer\n key={block.id}\n block={block}\n blockRegistry={blockRegistry}\n />\n ) : (\n <LocalBlockRenderer key={block.id} block={block} />\n ),\n )}\n </div>\n );\n }\n\n // Container section - render nested sections recursively\n return (\n <div\n className=\"footer-section footer-container-section\"\n style={sectionStyle}\n data-section-id={section.id}\n >\n {section?.sections\n ?.sort((a, b) => a.order - b.order)\n ?.map((childSection) => (\n <FooterSectionComponent\n key={childSection.id}\n section={childSection}\n footer={footer}\n site={site}\n resolvedColors={resolvedColors}\n level={level + 1}\n blockRegistry={blockRegistry}\n />\n ))}\n </div>\n );\n};\n","import { Site, FooterConfig } from \"@otl-core/cms-types\";\nimport type { BlockRegistry } from \"@otl-core/block-registry\";\nimport React from \"react\";\nimport { generateFooterCSS, resolveFooterColors } from \"../lib/footer.utils\";\nimport { cn } from \"@otl-core/style-utils\";\nimport { FooterSectionComponent } from \"./footer/footer-section\";\n\nexport interface FooterProps {\n footer: FooterConfig;\n site: Site;\n className?: string;\n id?: string;\n blockRegistry?: BlockRegistry;\n}\n\nexport const Footer: React.FC<FooterProps> = ({\n footer,\n site,\n className = \"\",\n id = \"default\",\n blockRegistry,\n}) => {\n const resolvedColors = resolveFooterColors(footer.style);\n const styles = generateFooterCSS(id, footer, resolvedColors);\n const sortedSections = [...footer.sections].sort((a, b) => a.order - b.order);\n const containerBehavior = footer.style.container || \"edged\";\n\n // Render sections with container wrapping per section for \"edged\" mode\n const sectionsContent = sortedSections.map((section) => {\n const sectionElement = (\n <FooterSectionComponent\n key={section.id}\n section={section}\n footer={footer}\n site={site}\n resolvedColors={resolvedColors}\n blockRegistry={blockRegistry}\n />\n );\n\n // In \"edged\" mode, wrap each top-level section in its own container\n if (containerBehavior === \"edged\") {\n return (\n <div key={section.id} className=\"container mx-auto\">\n {sectionElement}\n </div>\n );\n }\n\n return sectionElement;\n });\n\n return (\n <>\n {styles && <style>{styles}</style>}\n <footer\n className={cn(\n {\n \"container mx-auto\": containerBehavior === \"boxed\",\n },\n className,\n )}\n data-footer-id={id}\n data-container={containerBehavior}\n >\n <div className={`footer-${id} footer-container`}>{sectionsContent}</div>\n </footer>\n </>\n );\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@otl-core/next-footer",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Reusable footer components for OTL CMS",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./src/index.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"rebuild": "npm run clean && npm run build",
|
|
30
|
+
"lint": "eslint src",
|
|
31
|
+
"lint:fix": "eslint src --fix",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"test:ui": "vitest --ui",
|
|
35
|
+
"typecheck": "tsc --noEmit",
|
|
36
|
+
"verify": "npm run lint && npm run typecheck && npm run test && npm run build"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"footer",
|
|
40
|
+
"react",
|
|
41
|
+
"components",
|
|
42
|
+
"cms"
|
|
43
|
+
],
|
|
44
|
+
"author": "OTL Core",
|
|
45
|
+
"license": "UNLICENSED",
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@otl-core/cms-types": "*",
|
|
48
|
+
"@radix-ui/react-focus-scope": "^1.1.7",
|
|
49
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
50
|
+
"class-variance-authority": "^0.7.1",
|
|
51
|
+
"clsx": "^2.1.1",
|
|
52
|
+
"marked": "^16.4.0",
|
|
53
|
+
"next": ">=14.0.0",
|
|
54
|
+
"react": ">=18.0.0",
|
|
55
|
+
"react-dom": ">=18.0.0",
|
|
56
|
+
"react-markdown": "^10.1.0",
|
|
57
|
+
"react-transition-group": "^4.4.5",
|
|
58
|
+
"tailwind-merge": "^3.3.1"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@otl-core/block-registry": "^1.1.3",
|
|
62
|
+
"@otl-core/cms-utils": "^1.1.3",
|
|
63
|
+
"@otl-core/style-utils": "^1.1.3"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
67
|
+
"@otl-core/cms-types": "^1.1.3",
|
|
68
|
+
"@otl-core/cms-utils": "^1.1.3",
|
|
69
|
+
"@radix-ui/react-focus-scope": "^1.1.7",
|
|
70
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
71
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
72
|
+
"@testing-library/react": "^16.0.0",
|
|
73
|
+
"@types/node": "^20.0.0",
|
|
74
|
+
"@types/react": "^19.0.0",
|
|
75
|
+
"@types/react-dom": "^19.0.0",
|
|
76
|
+
"@types/react-transition-group": "^4.4.12",
|
|
77
|
+
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
|
78
|
+
"@typescript-eslint/parser": "^8.45.0",
|
|
79
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
80
|
+
"@vitest/ui": "^4.0.0",
|
|
81
|
+
"class-variance-authority": "^0.7.1",
|
|
82
|
+
"clsx": "^2.1.1",
|
|
83
|
+
"eslint": "^9.37.0",
|
|
84
|
+
"eslint-plugin-react": "^7.37.5",
|
|
85
|
+
"eslint-plugin-react-hooks": "^6.1.1",
|
|
86
|
+
"jsdom": "^25.0.0",
|
|
87
|
+
"marked": "^16.4.0",
|
|
88
|
+
"react-markdown": "^10.1.0",
|
|
89
|
+
"react-transition-group": "^4.4.5",
|
|
90
|
+
"tailwind-merge": "^3.3.1",
|
|
91
|
+
"tsup": "^8.0.0",
|
|
92
|
+
"typescript": "^5.0.0",
|
|
93
|
+
"vitest": "^4.0.0"
|
|
94
|
+
}
|
|
95
|
+
}
|