@contentrain/astro-kit 0.20.0 → 0.21.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
@@ -1257,7 +1257,7 @@
1257
1257
  },
1258
1258
  "copyright": {
1259
1259
  "type": "string",
1260
- "description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>."
1260
+ "description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>; the component also takes false for no line (the cookie settings control stays)."
1261
1261
  },
1262
1262
  "consentLabel": {
1263
1263
  "type": "string",
@@ -1278,6 +1278,11 @@
1278
1278
  "linkSize": {
1279
1279
  "type": "string",
1280
1280
  "description": "Font size of the footer's links, a CSS length such as 17.88px; absent, the footer's own."
1281
+ },
1282
+ "border": {
1283
+ "type": "boolean",
1284
+ "content": false,
1285
+ "description": "A border on the footer's top side; absent, the default tone has one and the others do not."
1281
1286
  }
1282
1287
  },
1283
1288
  "slots": {},
@@ -17,10 +17,12 @@ const footer = tv({
17
17
  columns: { inner: 'grid gap-10 md:grid-cols-[2fr_repeat(var(--footer-columns),1fr)] md:py-16' },
18
18
  },
19
19
  tone: {
20
- default: { root: 'border-t border-line bg-surface text-ink' },
20
+ default: { root: 'bg-surface text-ink' },
21
21
  muted: { root: 'bg-surface-muted text-ink' },
22
22
  inverse: { root: 'bg-ink text-surface' },
23
23
  },
24
+ // The border on the footer's top side: the default tone's own unless `border` says otherwise.
25
+ border: { on: { root: 'border-t border-line' }, off: {} },
24
26
  align: { start: {}, center: { inner: 'items-center justify-items-center text-center md:items-center [&_ul]:justify-center' } },
25
27
  // The `columns` layout's link columns: spread from the brand column over the footer's width, or packed at the end
26
28
  // edge at their own width (a theme whose footer is the brand left and its menus on the right).
@@ -33,7 +35,7 @@ const footer = tv({
33
35
  { layout: 'columns', pack: 'end', class: { inner: 'md:grid-cols-[1fr_repeat(var(--footer-columns),auto)] md:gap-x-[var(--footer-gap,2.5rem)] md:[&>div:first-child]:col-span-1' } },
34
36
  { layout: 'columns', pack: 'end', narrow: 'row', class: { inner: '[&>div:first-child]:col-span-full' } },
35
37
  ],
36
- defaultVariants: { layout: 'simple', tone: 'default', align: 'start', pack: 'spread', narrow: 'stack' },
38
+ defaultVariants: { layout: 'simple', tone: 'default', align: 'start', pack: 'spread', narrow: 'stack', border: 'off' },
37
39
  })
38
40
 
39
41
  interface Props {
@@ -46,8 +48,8 @@ interface Props {
46
48
  social?: LinkInput[] | undefined
47
49
  /** Contact details printed in an `<address>` block; each is optional, and the block is left out when none is set. */
48
50
  contact?: { address?: string | undefined, phone?: string | undefined, email?: string | undefined } | undefined
49
- /** The copyright line, e.g. "© 2026 Northwind Studio"; `{year}` is the current year. */
50
- copyright?: string | undefined
51
+ /** The copyright line, e.g. "© 2026 Northwind Studio"; `{year}` is the current year; `false` prints none. Absent: "© year Site". */
52
+ copyright?: string | false | undefined
51
53
  /** Label of the cookie settings link after the copyright line ("Cookie settings"); it reopens the consent bar, so a visitor can withdraw consent as easily as they gave it. Only with a consent bar on the page. */
52
54
  consentLabel?: string | undefined
53
55
  /** Accessible name of the footer navigation ("Footer"). */
@@ -65,16 +67,20 @@ interface Props {
65
67
  packGapNarrow?: string | undefined
66
68
  /** The links' font size, a CSS length (`17.88px`); absent, the footer's own. */
67
69
  linkSize?: string | undefined
70
+ /** A border on the footer's top side: absent, the default tone has one and the others do not. */
71
+ border?: boolean | undefined
68
72
  }
69
73
 
70
- const { siteName, tagline, columns = [], links = [], social = [], contact, copyright, consentLabel, navLabel = 'Footer', layout = 'simple', tone, align, pack, packGap, narrow, packGapNarrow, linkSize } = Astro.props
71
- const { root, inner, muted, link } = footer({ layout, tone, align, pack: layout === 'columns' ? pack : undefined, narrow: layout === 'columns' && pack === 'end' ? narrow : undefined })
74
+ const { siteName, tagline, columns = [], links = [], social = [], contact, copyright, consentLabel, navLabel = 'Footer', layout = 'simple', tone, align, pack, packGap, narrow, packGapNarrow, linkSize, border } = Astro.props
75
+ 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' })
72
76
  // Custom properties the packed columns and sized links read; none when none is set.
73
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('; ')
74
78
  const year = String(new Date().getFullYear())
75
79
  const phoneHref = contact?.phone?.replace(/[^\d+]/g, '')
76
80
  const hasContact = Boolean(contact?.address || contact?.phone || contact?.email)
77
- const line = copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
81
+ const line = copyright === false ? undefined : copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
82
+ // The line and the cookie settings control share one paragraph; neither: no paragraph.
83
+ const hasBottom = Boolean(line || consentLabel)
78
84
  ---
79
85
  <!-- The cookie settings control is a button, not a link: the Consent component's listener reopens its bar on any [data-kit-consent-open]. -->
80
86
  <footer class={root()}>
@@ -89,7 +95,7 @@ const line = copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
89
95
  {contact?.email && <a href={`mailto:${contact.email}`} class={link()}>{contact.email}</a>}
90
96
  </address>
91
97
  )}
92
- {layout === 'columns' && <p class:list={[muted(), 'mt-auto pt-6']}>{line}{consentLabel && <> · <button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>}
98
+ {layout === 'columns' && hasBottom && <p class:list={[muted(), 'mt-auto pt-6']}>{line}{consentLabel && <>{line && ' · '}<button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>}
93
99
  </div>
94
100
  {layout === 'columns'
95
101
  ? columns.map(column => (
@@ -114,7 +120,7 @@ const line = copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
114
120
  </ul>
115
121
  </nav>
116
122
  )}
117
- <p class={muted()}>{line}{consentLabel && <> · <button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>
123
+ {hasBottom && <p class={muted()}>{line}{consentLabel && <>{line && ' · '}<button type="button" data-kit-consent-open class={link()}>{consentLabel}</button></>}</p>}
118
124
  </div>
119
125
  )}
120
126
  {layout === 'columns' && social.length > 0 && (
@@ -126,7 +126,7 @@
126
126
  },
127
127
  "copyright": {
128
128
  "type": "string",
129
- "description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>."
129
+ "description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>; the component also takes false for no line (the cookie settings control stays)."
130
130
  },
131
131
  "consentLabel": {
132
132
  "type": "string",
@@ -147,6 +147,11 @@
147
147
  "linkSize": {
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
+ },
151
+ "border": {
152
+ "type": "boolean",
153
+ "content": false,
154
+ "description": "A border on the footer's top side; absent, the default tone has one and the others do not."
150
155
  }
151
156
  },
152
157
  "slots": {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/astro-kit",
3
- "version": "0.20.0",
3
+ "version": "0.21.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.38.0"
45
+ "@contentrain/types": "1.39.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.10.0",