@contentrain/astro-kit 0.23.5 → 0.24.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/catalog.json CHANGED
@@ -1280,6 +1280,10 @@
1280
1280
  "type": "string",
1281
1281
  "description": "Font size of the footer's links, a CSS length such as 17.88px; absent, the footer's own."
1282
1282
  },
1283
+ "linkSizeNarrow": {
1284
+ "type": "string",
1285
+ "description": "Font size of the footer's links below the md breakpoint, a CSS length such as 16.4px; absent, linkSize holds at every width."
1286
+ },
1283
1287
  "border": {
1284
1288
  "type": "boolean",
1285
1289
  "content": false,
@@ -1308,7 +1312,8 @@
1308
1312
  },
1309
1313
  "a11y": "One <footer> landmark; each link group is a <nav> named by its title. Social links open in a new tab with rel=me.",
1310
1314
  "files": [
1311
- "Footer.astro"
1315
+ "Footer.astro",
1316
+ "footer.ts"
1312
1317
  ]
1313
1318
  },
1314
1319
  {
@@ -4,8 +4,7 @@
4
4
  // StudioForm): its fields, spam protection and notifications live in Studio,
5
5
  // and submissions arrive there for review — no form plugin, no mail server.
6
6
  // Until the site is bound to a Studio project the form renders nothing and the
7
- // contact details still show — and a section left with its heading alone is not
8
- // drawn (a heading over a form that is not there). When the owner chose another home for forms at
7
+ // contact details still show, and so do the section's heading and introduction (only the form goes). When the owner chose another home for forms at
9
8
  // migration (site.config.ts `features.forms`), the same place holds a form
10
9
  // that posts to their form service, an address to write to, or a link to the
11
10
  // form on WordPress.
@@ -71,7 +70,7 @@ interface Props {
71
70
  const { model, heading, intro, formHeading, details = [], layout, align, tone, id = 'contact' } = Astro.props
72
71
  // Nothing to render (Studio not bound yet): a split would leave an empty column — the details stand alone.
73
72
  const formShown = formHome(model) !== null
74
- const bare = contactSectionBare({ formShown, intro, details })
73
+ const bare = contactSectionBare({ formShown, heading, intro, details })
75
74
  const s = contact({ layout: formShown ? layout : 'stacked', align, tone })
76
75
  const headingId = heading ? `${id}-title` : undefined
77
76
  // A description list needs a term for every detail; an icon list carried from the source has values only.
@@ -1,8 +1,9 @@
1
1
  // When the contact section has nothing to show. Its form is drawn only where a home for it exists (Studio bound, the
2
2
  // WordPress page known, a service address that is https); until then the section stands on its heading, introduction
3
- // and details. A section left with a heading ALONE titles a form that is not there — the page would print "Tell us about
4
- // your project" over empty space — so it is not drawn at all, and appears with its form once the form has a home.
3
+ // and details. The source's words are never dropped with the form: a page whose contact section holds only a heading
4
+ // ("Bize yazın") keeps it, because the text is the site's own and a fidelity gate compares it. Only a section with no
5
+ // heading, introduction or details at all — nothing to print — is left out, and appears with its form once it has a home.
5
6
 
6
- export function contactSectionBare(input: { formShown: boolean, intro?: string | undefined, details?: readonly unknown[] | undefined }): boolean {
7
- return !input.formShown && !input.intro && !(input.details?.length ?? 0)
7
+ export function contactSectionBare(input: { formShown: boolean, heading?: string | undefined, intro?: string | undefined, details?: readonly unknown[] | undefined }): boolean {
8
+ return !input.formShown && !input.heading && !input.intro && !(input.details?.length ?? 0)
8
9
  }
@@ -3,6 +3,7 @@
3
3
  // links and the copyright line.
4
4
  import { tv } from 'tailwind-variants'
5
5
  import type { LinkInput } from '../_shared/types'
6
+ import { footerLinkSizeClasses, footerVars } from './footer'
6
7
 
7
8
  const footer = tv({
8
9
  slots: {
@@ -67,14 +68,16 @@ interface Props {
67
68
  packGapNarrow?: string | undefined
68
69
  /** The links' font size, a CSS length (`17.88px`); absent, the footer's own. */
69
70
  linkSize?: string | undefined
71
+ /** The links' font size below `md`, a CSS length; absent, `linkSize` holds at every width. */
72
+ linkSizeNarrow?: string | undefined
70
73
  /** A border on the footer's top side: absent, the default tone has one and the others do not. */
71
74
  border?: boolean | undefined
72
75
  }
73
76
 
74
- const { siteName, tagline, columns = [], links = [], social = [], contact, copyright, consentLabel, navLabel = 'Footer', layout = 'simple', tone, align, pack, packGap, narrow, packGapNarrow, linkSize, border } = Astro.props
77
+ const { siteName, tagline, columns = [], links = [], social = [], contact, copyright, consentLabel, navLabel = 'Footer', layout = 'simple', tone, align, pack, packGap, narrow, packGapNarrow, linkSize, linkSizeNarrow, border } = Astro.props
75
78
  const { root, inner, muted, link } = footer({ layout, tone, align, pack: layout === 'columns' ? pack : undefined, narrow: layout === 'columns' && pack === 'end' ? narrow : undefined, border: (border ?? (tone ?? 'default') === 'default') ? 'on' : 'off' })
76
79
  // Custom properties the packed columns and sized links read; none when none is set.
77
- const vars = [layout === 'columns' ? `--footer-columns: ${Math.max(columns.length, 1)}` : '', layout === 'columns' && pack === 'end' && packGap ? `--footer-gap: ${packGap}` : '', layout === 'columns' && pack === 'end' && narrow === 'row' && packGapNarrow ? `--footer-gap-narrow: ${packGapNarrow}` : '', linkSize ? `--footer-link-size: ${linkSize}` : ''].filter(Boolean).join('; ')
80
+ const vars = footerVars({ layout, columnCount: columns.length, pack, narrow, packGap, packGapNarrow, linkSize, linkSizeNarrow })
78
81
  const year = String(new Date().getFullYear())
79
82
  const phoneHref = contact?.phone?.replace(/[^\d+]/g, '')
80
83
  const hasContact = Boolean(contact?.address || contact?.phone || contact?.email)
@@ -84,7 +87,7 @@ const hasBottom = Boolean(line || consentLabel)
84
87
  ---
85
88
  <!-- The cookie settings control is a button, not a link: the Consent component's listener reopens its bar on any [data-kit-consent-open]. -->
86
89
  <footer class={root()}>
87
- <div class:list={[inner(), linkSize && '[&_nav_a]:text-[length:var(--footer-link-size)]']} data-kit-footer style={vars || undefined}>
90
+ <div class:list={[inner(), ...footerLinkSizeClasses({ linkSize, linkSizeNarrow })]} data-kit-footer style={vars || undefined}>
88
91
  <div class="flex flex-col gap-3">
89
92
  <p class="text-base font-semibold" data-kit-brand>{siteName}</p>
90
93
  {tagline && <p class={muted()} data-kit-tagline>{tagline}</p>}
@@ -5,5 +5,6 @@
5
5
  { "name": "centered", "props": { "siteName": "Golden Agency", "tagline": "Websites for makers", "align": "center", "copyright": "© 2026 Golden Agency", "links": [{ "label": "Services", "href": "/services/" }, { "label": "Contact", "href": "/contact/" }], "social": [{ "label": "Instagram", "href": "https://instagram.com/" }] } },
6
6
  { "name": "consent-settings", "props": { "siteName": "Golden Agency", "copyright": "© 2026 Golden Agency", "consentLabel": "Cookie settings", "links": [{ "label": "Privacy", "href": "/privacy/" }] } },
7
7
  { "name": "columns-end", "props": { "siteName": "Golden Works", "tagline": "Websites for makers", "layout": "columns", "pack": "end", "packGap": "128px", "linkSize": "17.88px", "copyright": "© 2026 Golden Works", "columns": [{ "label": "Studio", "links": [{ "label": "About", "href": "/about/" }, { "label": "Work", "href": "/work/" }] }, { "label": "Contact", "links": [{ "label": "Email", "href": "mailto:hello@example.com" }, { "label": "Press", "href": "/press/" }] }] } },
8
- { "name": "columns-end-row", "props": { "siteName": "Golden Works", "layout": "columns", "pack": "end", "narrow": "row", "packGap": "128px", "packGapNarrow": "48px", "linkSize": "17.88px", "copyright": "© 2026 Golden Works", "columns": [{ "label": "Studio", "links": [{ "label": "About", "href": "/about/" }, { "label": "Work", "href": "/work/" }] }, { "label": "Contact", "links": [{ "label": "Email", "href": "mailto:hello@example.com" }] }] } }
8
+ { "name": "columns-end-row", "props": { "siteName": "Golden Works", "layout": "columns", "pack": "end", "narrow": "row", "packGap": "128px", "packGapNarrow": "48px", "linkSize": "17.88px", "copyright": "© 2026 Golden Works", "columns": [{ "label": "Studio", "links": [{ "label": "About", "href": "/about/" }, { "label": "Work", "href": "/work/" }] }, { "label": "Contact", "links": [{ "label": "Email", "href": "mailto:hello@example.com" }] }] } },
9
+ { "name": "columns-end-row-narrow-size", "props": { "siteName": "Golden Works", "layout": "columns", "pack": "end", "narrow": "row", "packGap": "128px", "packGapNarrow": "48px", "linkSize": "17.88px", "linkSizeNarrow": "16.4px", "copyright": "© 2026 Golden Works", "columns": [{ "label": "Studio", "links": [{ "label": "About", "href": "/about/" }, { "label": "Work", "href": "/work/" }] }, { "label": "Contact", "links": [{ "label": "Email", "href": "mailto:hello@example.com" }] }] } }
9
10
  ]
@@ -0,0 +1,34 @@
1
+ export interface FooterVarsInput {
2
+ layout: 'simple' | 'columns'
3
+ columnCount: number
4
+ pack?: 'spread' | 'end' | undefined
5
+ narrow?: 'stack' | 'row' | undefined
6
+ packGap?: string | undefined
7
+ packGapNarrow?: string | undefined
8
+ linkSize?: string | undefined
9
+ linkSizeNarrow?: string | undefined
10
+ }
11
+
12
+ /**
13
+ * The custom properties the packed columns and sized links read, as one `style` value (empty: none). `linkSizeNarrow` is
14
+ * its own property, applied below `md` by the class below whether the columns stack or stay in a row.
15
+ */
16
+ export function footerVars(i: FooterVarsInput): string {
17
+ const columns = i.layout === 'columns'
18
+ const packed = columns && i.pack === 'end'
19
+ return [
20
+ columns ? `--footer-columns: ${Math.max(i.columnCount, 1)}` : '',
21
+ packed && i.packGap ? `--footer-gap: ${i.packGap}` : '',
22
+ packed && i.narrow === 'row' && i.packGapNarrow ? `--footer-gap-narrow: ${i.packGapNarrow}` : '',
23
+ i.linkSize ? `--footer-link-size: ${i.linkSize}` : '',
24
+ i.linkSizeNarrow ? `--footer-link-size-narrow: ${i.linkSizeNarrow}` : '',
25
+ ].filter(Boolean).join('; ')
26
+ }
27
+
28
+ /** The link-size classes: the wide size everywhere, the narrow one (when given) overriding it below `md`. */
29
+ export function footerLinkSizeClasses(i: Pick<FooterVarsInput, 'linkSize' | 'linkSizeNarrow'>): string[] {
30
+ return [
31
+ i.linkSize ? '[&_nav_a]:text-[length:var(--footer-link-size)]' : '',
32
+ i.linkSizeNarrow ? 'max-md:[&_nav_a]:text-[length:var(--footer-link-size-narrow)]' : '',
33
+ ].filter(Boolean)
34
+ }
@@ -148,6 +148,10 @@
148
148
  "type": "string",
149
149
  "description": "Font size of the footer's links, a CSS length such as 17.88px; absent, the footer's own."
150
150
  },
151
+ "linkSizeNarrow": {
152
+ "type": "string",
153
+ "description": "Font size of the footer's links below the md breakpoint, a CSS length such as 16.4px; absent, linkSize holds at every width."
154
+ },
151
155
  "border": {
152
156
  "type": "boolean",
153
157
  "content": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/astro-kit",
3
- "version": "0.23.5",
3
+ "version": "0.24.0",
4
4
  "license": "MIT",
5
5
  "description": "Copy-in Astro components for Contentrain sites (shadcn model): typed props, tailwind-variants, zero JavaScript by default — with a machine-readable catalog",
6
6
  "type": "module",
@@ -42,7 +42,7 @@
42
42
  "mapping"
43
43
  ],
44
44
  "dependencies": {
45
- "@contentrain/types": "1.47.0"
45
+ "@contentrain/types": "1.48.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.10.0",