feat(blocks): add columns footer block

This commit is contained in:
George Cushen committed 2026-02-18 22:09:10 +00:00
1 parent bf41049de2
commit ab44db81d7
9 files changed
+245 -8

No files matched your search

+2 -2
View File
@@ -18,8 +18,8 @@ function FeatureItem({ item, iconSvg }) {
return (
<div>
{iconSvg && (
<div class="flex justify-center items-center mb-4 w-10 h-10 rounded-full bg-primary-100 lg:h-12 lg:w-12 dark:bg-primary-900 text-primary-600 dark:text-primary-300">
<Icon svg={iconSvg} attributes={{ class: "w-5 h-5 lg:w-6 lg:h-6" }} />
<div class="flex justify-center items-center mb-4 w-12 h-12 rounded-full bg-primary-100 lg:h-14 lg:w-14 dark:bg-primary-900 text-primary-600 dark:text-primary-300">
<Icon svg={iconSvg} attributes={{ class: "flex items-center justify-center w-6 h-6 lg:w-8 lg:h-8" }} />
</div>
)}
{item.name && (
@@ -5,6 +5,13 @@ import { h } from "preact";
* Icon component
* Renders an SVG icon from a raw SVG string passed from Hugo.
* Decodes JSON-escaped sequences (\u003c) and HTML entities (&lt;, &quot;, &#34;).
*
* SVG sizing strategy:
* - By default, icons size to 1em (matching current font size) — works universally
* for inline icons in buttons, text, lists, etc.
* - If the caller provides explicit height via style attribute (e.g. style="height: 36px"),
* the SVG fills that container with height:100%;width:auto instead.
* - If the caller provides explicit w-/h- Tailwind classes, those take precedence.
*/
export const Icon = ({ svg, attributes }) => {
if (!svg) return null;
@@ -24,9 +31,19 @@ export const Icon = ({ svg, attributes }) => {
const hasWrapper = /<svg[\s>]/i.test(decoded);
if (hasWrapper) {
// Determine SVG sizing: if caller provides explicit height (via style attr),
// use 100% to fill the container; otherwise default to 1em for self-sizing.
const callerStyle = attributes?.style || "";
const hasExplicitHeight = /height\s*:/i.test(String(callerStyle));
const svgStyle = hasExplicitHeight
? "height:100%;width:auto"
: "height:1em;width:auto";
decoded = decoded.replace(/^(<svg\b)/i, `$1 style="${svgStyle}"`);
const wrapperProps = {
...attributes,
class: `flex items-center justify-center ${attributes?.class || ""}`.trim(),
class: attributes?.class || "",
};
// eslint-disable-next-line lint/security/noDangerouslySetInnerHtml
@@ -0,0 +1,82 @@
{{ $branding := partialCached "functions/get_branding" . "branding" }}
{{- $logo_height := site.Params.hugoblox.footer.logo_height | default 36 -}}
{{ $logo := partialCached "functions/get_logo" (dict "constraint" "max_height" "size" $logo_height) "footer_logo" }}
{{- $show_logo := site.Params.hugoblox.footer.show_logo | default true -}}
{{- $show_title := site.Params.hugoblox.footer.show_title | default true -}}
{{- $show_tagline := site.Params.hugoblox.footer.show_tagline | default true -}}
{{- $footer_title := site.Params.hugoblox.footer.title | default $branding.name -}}
<footer aria-labelledby="footer-heading">
<span id="footer-heading" class="sr-only">Footer</span>
<div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-16">
<div class="xl:grid xl:grid-cols-3 xl:gap-8 xl:items-start">
<div class="xl:col-span-1">
{{ if $show_logo }}
{{ with $logo.resource }}
{{ if $logo.is_svg }}
<div class="footer-logo" style="height: {{ $logo_height }}px;">
{{- .Content | safeHTML -}}
</div>
<style>.footer-logo svg { height: 100%; width: auto; }</style>
{{ else }}
<img
decoding="async"
width="{{ .Width }}"
height="{{ .Height }}"
src="{{ .RelPermalink }}"
alt="{{ $branding.name }}" />
{{ end }}
{{ end }}
{{ end }}
{{ if $show_title }}
<a class="text-lg font-bold tracking-tighter text-primary-600 transition duration-500 ease-in-out lg:pr-8 dark:text-primary-200" href="{{site.Home.RelPermalink}}">
{{ $footer_title }}
</a>
{{ end }}
{{ if $show_tagline }}
<p class="mt-2 w-1/2 text-sm text-gray-500 dark:text-gray-300">{{ site.Params.hugoblox.identity.tagline | default "" }}</p>
{{ end }}
{{- $show_social := site.Params.hugoblox.footer.show_social | default true -}}
{{ if $show_social }}
{{ with site.Params.hugoblox.identity.social.links }}
<div class="mt-4 text-xl">
{{ partial "social_links" (dict "Params" (dict "profiles" .)) }}
</div>
{{ end }}
{{ end }}
</div>
<div class="mt-12 grid grid-cols-1 gap-8 xl:col-span-2 xl:mt-0 md:grid-cols-{{ len .Site.Menus.footer }}">
{{ $menu := .Site.Menus.footer }}
{{ range $index, $menuItem := $menu }}
<div>
<h3 class="text-sm font-bold tracking-wider text-primary-500 uppercase dark:text-primary-300">{{ $menuItem.Name }}</h3>
<ul role="list" class="mt-4 space-y-2">
{{ range $childIndex, $childMenuItem := $menuItem.Children }}
{{ $url := .URL | relLangURL }}
<li>
<a href="{{ $url }}"
class="text-base font-normal text-gray-500 hover:text-primary-600 dark:text-gray-300 dark:hover:text-primary-300"
{{ if findRE `^http` .URL }}
target="_blank" rel="noopener"
{{ end }}
>
{{ $childMenuItem.Name }}
</a>
</li>
{{ end }}
</ul>
</div>
{{ end }}
</div>
</div>
</div>
<div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-16">
<div class="flex flex-wrap items-baseline">
<span class="mt-2 text-sm font-light text-gray-500 dark:text-gray-300">
{{/* Display copyright license. */}}
{{ partial "site_footer_license" . }}
</span>
</div>
</div>
</footer>
@@ -6,7 +6,9 @@
{{- $block_id := "" -}}
{{- $using_legacy := false -}}
{{- if $config.block -}}
{{- if $config.style -}}
{{- $block_id = $config.style -}}
{{- else if $config.block -}}
{{- $block_id = $config.block -}}
{{- else if $config.blox -}}
{{- $block_id = $config.blox -}}
@@ -12,9 +12,83 @@ main:
- name: Solutions
url: /#solutions
weight: 30
- name: Changelog
url: /changelog/
weight: 35
- name: Resources
weight: 40
hasChildren: true
- parent: Resources
name: Blog
url: blog/
# Footer links (4-column layout)
# Top-level items become column headings, children become links.
footer:
- name: Product
weight: 10
- parent: Product
name: Features
url: /#features
weight: 10
- parent: Product
name: Solutions
url: /#solutions
weight: 20
- parent: Product
name: Pricing
url: "#"
weight: 30
- parent: Product
name: Changelog
url: /changelog/
weight: 40
- name: Company
weight: 20
- parent: Company
name: About
url: "#"
weight: 10
- parent: Company
name: Blog
url: /blog/
weight: 20
- parent: Company
name: Careers
url: "#"
weight: 30
- parent: Company
name: Contact
url: "#"
weight: 40
- name: Resources
weight: 30
- parent: Resources
name: Documentation
url: "#"
weight: 10
- parent: Resources
name: Help Center
url: "#"
weight: 20
- parent: Resources
name: Community
url: "#"
weight: 30
- parent: Resources
name: Status
url: "#"
weight: 40
- name: Legal
weight: 40
- parent: Legal
name: Privacy Policy
url: /privacy/
weight: 10
- parent: Legal
name: Terms of Service
url: /terms/
weight: 20
@@ -19,12 +19,22 @@ hugoblox:
# Schema.org type for structured data
type: organization # person | organization | local_business
# Short tagline displayed on your site
tagline: ""
tagline: "The framework for Hugo."
# Site meta description for search engines and social sharing
description: "The highly-customizable Hugo Academic theme powered by HugoBlox Kit. Easily create your personal academic website."
# Social accounts (used for meta tags like Twitter Cards)
social:
twitter: "MakeOwnable" # Twitter/X handle for Twitter Cards (without @)
# Site-wide social links (rendered in footer, etc.)
links:
- icon: brands/x
url: https://x.com/MakeOwnable
- icon: brands/github
url: https://github.com/HugoBlox/kit
- icon: brands/discord
url: https://discord.gg/z8wNYzb
- icon: brands/linkedin
url: https://linkedin.com/company/example
# ────────────────────────────────────────────────────────────────────────────
# THEME
@@ -111,8 +121,15 @@ hugoblox:
# ────────────────────────────────────────────────────────────────────────────
footer:
# Footer style variant
style: minimal # minimal | columns | centered
style: columns # minimal | columns | centered
show_logo: true
show_title: true
show_tagline: true
# Override footer title (defaults to identity.name)
title: ""
language_switcher: true
# Show social icons from identity.social.links
show_social: true
# Custom footer text (HTML supported)
text: ""
@@ -122,9 +139,9 @@ hugoblox:
# ────────────────────────────────────────────────────────────────────────────
copyright:
# Copyright notice - supports {year}, {name}, {license} placeholders
notice: "© {year} {name}. This work is licensed under {license}"
notice: "© {year} {name}."
license:
type: cc # cc | custom
type: custom # cc | custom
allow_derivatives: false
share_alike: true
allow_commercial: false
@@ -0,0 +1,4 @@
---
title: Changelog
view: date-title-summary
---
@@ -0,0 +1,19 @@
---
title: "v1.0 — Initial Release"
date: 2026-01-15
summary: "The first stable release of our platform, featuring the page builder, block system, and Tailwind 4 integration."
version: "1.0.0"
---
## What's New
- **Page Builder** — Build landing pages with drag-and-drop blocks
- **Block System** — Hero, Features, CTA, Testimonials, Stats, and more
- **Tailwind CSS 4** — Modern utility-first styling with dark mode support
- **Preact Components** — Fast, lightweight client-side interactivity
- **SEO Optimized** — Automatic sitemaps, RSS feeds, and structured data
- **Multi-language** — 35+ language packs out of the box
## Getting Started
Check out the [documentation](https://docs.hugoblox.com/) to get started with your first site.
@@ -0,0 +1,22 @@
---
title: "v1.1 — Footer & Icon Improvements"
date: 2026-02-18
summary: "Multi-column footer with social icons, improved icon rendering, and changelog section."
version: "1.1.0"
---
## What's New
- **Multi-column Footer** — New `columns` footer style with logo, tagline, and navigation
- **Social Icons** — Site-wide social links rendered in the footer via `identity.social.links`
- **Icon Rendering** — Robust SVG sizing strategy with `1em` default for inline icons
## Improvements
- Icons now self-size to `1em` by default, eliminating 0px collapse bugs
- Footer branding supports configurable logo height and SVG scaling
- Feature block icons bumped to larger size for better visual presence
## Content
- **Changelog Section** — New standalone section at `/changelog/` using the `date-title-summary` view