@contentrain/astro-kit 0.3.1 → 0.5.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/README.md CHANGED
@@ -66,6 +66,8 @@ theme applies them to the kit's markers, and only when the site sets them:
66
66
  - `--text-body`: `[data-kit-text]`, the lead and body paragraphs of hero, CTA
67
67
  and card grid.
68
68
  - `--text-nav`: the header navigation, `[data-cr-part="nav-header"]`.
69
+ - `--text-footer`: the footer's text and links, `[data-kit-footer]` (the kit's
70
+ own size is `text-sm`).
69
71
  - `--spacing-section`: the distance between two consecutive sections; each
70
72
  `[data-kit-section]` gets half of it as vertical padding, except
71
73
  where `data-kit-spacing="none"`.
package/catalog.json CHANGED
@@ -22,6 +22,7 @@
22
22
  "--radius-image",
23
23
  "--spacing-gutter",
24
24
  "--text-nav",
25
+ "--text-footer",
25
26
  "--text-heading-1",
26
27
  "--text-heading-2",
27
28
  "--text-heading-3",
@@ -563,7 +564,7 @@
563
564
  "plain"
564
565
  ],
565
566
  "default": "lines",
566
- "description": "lines: rows divided by rules. cards: each question in a rounded box. plain: the browser's own disclosure triangle without rules or boxes (WordPress's details block)."
567
+ "description": "lines: rows divided by rules. cards: each question in a rounded box. plain: the browser's own disclosure triangle without rules or boxes, questions at the body size (WordPress's details block)."
567
568
  },
568
569
  "tone": {
569
570
  "options": [
@@ -739,7 +740,7 @@
739
740
  },
740
741
  "copyright": {
741
742
  "type": "string",
742
- "description": "Copyright line; defaults to © <year> <siteName>."
743
+ "description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>."
743
744
  },
744
745
  "navLabel": {
745
746
  "type": "string",
@@ -937,6 +938,19 @@
937
938
  "required": true,
938
939
  "description": "Site title (site.title)."
939
940
  },
941
+ "tagline": {
942
+ "type": "string",
943
+ "description": "A line under the site name, as themes that print the tagline in the header do (site.tagline)."
944
+ },
945
+ "brandSize": {
946
+ "type": "string",
947
+ "description": "The site name's size where the source theme sets its own (a CSS length)."
948
+ },
949
+ "linkNav": {
950
+ "type": "boolean",
951
+ "default": false,
952
+ "description": "The navigation in the site's link colour, as themes that colour every link do."
953
+ },
940
954
  "logo": {
941
955
  "type": "object",
942
956
  "fields": {
@@ -1223,6 +1237,14 @@
1223
1237
  ],
1224
1238
  "default": "compact",
1225
1239
  "description": "Gap and text size of the inline list."
1240
+ },
1241
+ "color": {
1242
+ "options": [
1243
+ "current",
1244
+ "link"
1245
+ ],
1246
+ "default": "current",
1247
+ "description": "current: the surrounding text, dimmed until hovered. link: the site's link colour (--color-link, else the accent) at full strength."
1226
1248
  }
1227
1249
  },
1228
1250
  "props": {
@@ -1575,6 +1597,11 @@
1575
1597
  "max": 3,
1576
1598
  "default": 2,
1577
1599
  "description": "Title heading level in the page outline."
1600
+ },
1601
+ "linkTitle": {
1602
+ "type": "boolean",
1603
+ "default": false,
1604
+ "description": "The title in the site's link colour (--color-link), as themes that colour every link do."
1578
1605
  }
1579
1606
  },
1580
1607
  "slots": {},
@@ -26,7 +26,7 @@ const faq = tv({
26
26
  lines: { list: 'divide-y divide-line border-y border-line' },
27
27
  cards: { list: 'gap-3', item: 'rounded-card border border-line px-5', summary: 'py-5' },
28
28
  // WordPress's details block: the browser's own disclosure triangle, no rules or box.
29
- plain: { list: 'gap-2', summary: 'list-item list-[revert] py-1 text-base [font-weight:inherit] [&::-webkit-details-marker]:inline', marker: 'hidden', answer: 'pt-2 pl-5' },
29
+ plain: { list: 'gap-2', summary: 'list-item list-[revert] py-1 text-[length:var(--text-body,1rem)] [font-weight:inherit] [&::-webkit-details-marker]:inline', marker: 'hidden', answer: 'pt-2 pl-5' },
30
30
  },
31
31
  tone: { default: {}, muted: { item: 'bg-surface' }, inverse: { answer: 'prose-invert' }, accent: { answer: 'prose-invert' } },
32
32
  },
@@ -6,7 +6,7 @@
6
6
  "uses": [],
7
7
  "shared": ["Section.astro"],
8
8
  "variants": {
9
- "style": { "options": ["lines", "cards", "plain"], "default": "lines", "description": "lines: rows divided by rules. cards: each question in a rounded box. plain: the browser's own disclosure triangle without rules or boxes (WordPress's details block)." },
9
+ "style": { "options": ["lines", "cards", "plain"], "default": "lines", "description": "lines: rows divided by rules. cards: each question in a rounded box. plain: the browser's own disclosure triangle without rules or boxes, questions at the body size (WordPress's details block)." },
10
10
  "tone": { "options": ["default", "muted", "inverse", "accent"], "default": "default", "description": "Section background." }
11
11
  },
12
12
  "props": {
@@ -33,7 +33,7 @@ interface Props {
33
33
  /** A single row of links (the footer menu). */
34
34
  links?: LinkInput[] | undefined
35
35
  social?: LinkInput[] | undefined
36
- /** The copyright line, e.g. "© 2026 Northwind Studio". */
36
+ /** The copyright line, e.g. "© 2026 Northwind Studio"; `{year}` is the current year. */
37
37
  copyright?: string | undefined
38
38
  /** Accessible name of the footer navigation ("Footer"). */
39
39
  navLabel?: string | undefined
@@ -43,10 +43,11 @@ interface Props {
43
43
 
44
44
  const { siteName, tagline, columns = [], links = [], social = [], copyright, navLabel = 'Footer', layout = 'simple', tone } = Astro.props
45
45
  const { root, inner, muted, link } = footer({ layout, tone })
46
- const line = copyright ?? `© ${new Date().getFullYear()} ${siteName}`
46
+ const year = String(new Date().getFullYear())
47
+ const line = copyright?.replaceAll('{year}', year) ?? `© ${year} ${siteName}`
47
48
  ---
48
49
  <footer class={root()}>
49
- <div class={inner()} style={layout === 'columns' ? `--footer-columns: ${Math.max(columns.length, 1)}` : undefined}>
50
+ <div class={inner()} data-kit-footer style={layout === 'columns' ? `--footer-columns: ${Math.max(columns.length, 1)}` : undefined}>
50
51
  <div class="flex flex-col gap-3">
51
52
  <p class="text-base font-semibold" data-kit-brand>{siteName}</p>
52
53
  {tagline && <p class={muted()} data-kit-tagline>{tagline}</p>}
@@ -93,7 +93,7 @@
93
93
  },
94
94
  "copyright": {
95
95
  "type": "string",
96
- "description": "Copyright line; defaults to © <year> <siteName>."
96
+ "description": "Copyright line; {year} is the current year. Defaults to © <year> <siteName>."
97
97
  },
98
98
  "navLabel": {
99
99
  "type": "string",
@@ -31,6 +31,12 @@ const header = tv({
31
31
 
32
32
  interface Props {
33
33
  siteName: string
34
+ /** A line under the site name, as themes that print the site's tagline in the header do. */
35
+ tagline?: string | undefined
36
+ /** The site name's size, where the source theme sets its own (a CSS length). */
37
+ brandSize?: string | undefined
38
+ /** The navigation in the site's link colour, as themes that colour every link do (Hello Elementor). */
39
+ linkNav?: boolean | undefined
34
40
  logo?: ImageInput | undefined
35
41
  /** Show the site name next to the logo. */
36
42
  showName?: boolean | undefined
@@ -45,17 +51,20 @@ interface Props {
45
51
  position?: 'static' | 'sticky' | undefined
46
52
  }
47
53
 
48
- const { siteName, logo, showName = true, homeHref = '/', items, action, navLabel, menuLabel, closeLabel, layout, tone, position } = Astro.props
54
+ const { siteName, tagline, brandSize, linkNav, logo, showName = true, homeHref = '/', items, action, navLabel, menuLabel, closeLabel, layout, tone, position } = Astro.props
49
55
  const { root, inner, brand } = header({ layout, tone, position })
50
56
  ---
51
57
  <header class={root()}>
52
58
  <div class={inner()}>
53
- <a href={homeHref} class={brand()} aria-label={showName ? undefined : siteName} data-kit-brand>
54
- {logo && <KitImage image={logo} priority class="h-8 w-auto" sizes="160px" />}
55
- {showName && <span>{siteName}</span>}
56
- </a>
59
+ <div class="flex flex-col gap-1">
60
+ <a href={homeHref} class={brand()} aria-label={showName ? undefined : siteName} style={brandSize ? `font-size: ${brandSize}` : undefined} data-kit-brand>
61
+ {logo && <KitImage image={logo} priority class="h-8 w-auto" sizes="160px" />}
62
+ {showName && <span>{siteName}</span>}
63
+ </a>
64
+ {tagline && <p class="opacity-75" data-kit-tagline>{tagline}</p>}
65
+ </div>
57
66
  <div class:list={['flex items-center gap-4', layout === 'centered' ? 'justify-center' : 'ml-auto']}>
58
- <Nav items={items} label={navLabel} menuLabel={menuLabel} closeLabel={closeLabel} part="nav-header" align={layout === 'centered' ? 'center' : 'end'} />
67
+ <Nav items={items} label={navLabel} menuLabel={menuLabel} closeLabel={closeLabel} part="nav-header" color={linkNav ? 'link' : 'current'} align={layout === 'centered' ? 'center' : 'end'} />
59
68
  {action && <Button action={action} size="sm" class="hidden md:inline-flex" />}
60
69
  </div>
61
70
  </div>
@@ -12,6 +12,9 @@
12
12
  },
13
13
  "props": {
14
14
  "siteName": { "type": "string", "required": true, "description": "Site title (site.title)." },
15
+ "tagline": { "type": "string", "description": "A line under the site name, as themes that print the tagline in the header do (site.tagline)." },
16
+ "brandSize": { "type": "string", "description": "The site name's size where the source theme sets its own (a CSS length)." },
17
+ "linkNav": { "type": "boolean", "default": false, "description": "The navigation in the site's link colour, as themes that colour every link do." },
15
18
  "logo": { "type": "object", "fields": { "src": { "type": "image", "required": true }, "alt": { "type": "string", "required": true }, "width": { "type": "integer" }, "height": { "type": "integer" } }, "description": "Logo image (site.logo)." },
16
19
  "showName": { "type": "boolean", "default": true, "description": "Show the site name next to the logo." },
17
20
  "homeHref": { "type": "url", "default": "/", "description": "Where the brand links." },
@@ -8,7 +8,7 @@ import type { NavItem } from '../_shared/types'
8
8
  const nav = tv({
9
9
  slots: {
10
10
  list: 'hidden items-center md:flex',
11
- link: 'text-current no-underline opacity-80 hover:opacity-100 aria-[current=page]:font-semibold aria-[current=page]:opacity-100',
11
+ link: 'no-underline aria-[current=page]:font-semibold',
12
12
  // hidden, not invisible: a closed submenu takes no space, so one under the last item cannot widen the page.
13
13
  sub: 'absolute top-full z-30 hidden min-w-48 rounded-lg border border-line bg-surface p-2 text-ink shadow-lg group-hover:block group-focus-within:block',
14
14
  toggle: 'inline-flex cursor-pointer list-none items-center gap-2 rounded-md px-3 py-2 text-sm font-medium hover:bg-current/10 md:hidden [&::-webkit-details-marker]:hidden',
@@ -17,8 +17,14 @@ const nav = tv({
17
17
  // Submenus open towards the page: leftwards under an end-aligned menu.
18
18
  align: { start: { list: 'justify-start', sub: 'left-0' }, end: { list: 'justify-end', sub: 'right-0' }, center: { list: 'justify-center', sub: 'left-0' } },
19
19
  density: { compact: { list: 'gap-5 text-sm' }, comfortable: { list: 'gap-8 text-base' } },
20
+ // current: the surrounding text, dimmed until hovered. link: the site's link colour at full strength, as themes that
21
+ // colour every link draw their menu; the toggle keeps the surrounding text.
22
+ color: {
23
+ current: { link: 'text-current opacity-80 hover:opacity-100 aria-[current=page]:opacity-100' },
24
+ link: { link: 'text-[color:var(--color-link,var(--color-accent))]' },
25
+ },
20
26
  },
21
- defaultVariants: { align: 'end', density: 'compact' },
27
+ defaultVariants: { align: 'end', density: 'compact', color: 'current' },
22
28
  })
23
29
 
24
30
  interface Props {
@@ -32,14 +38,15 @@ interface Props {
32
38
  mobile?: 'popover' | 'details' | undefined
33
39
  align?: 'start' | 'end' | 'center' | undefined
34
40
  density?: 'compact' | 'comfortable' | undefined
41
+ color?: 'current' | 'link' | undefined
35
42
  /** Unique per page when a page has two navigations. */
36
43
  id?: string | undefined
37
44
  /** What the navigation is, for checks that find a page's parts (`data-cr-part`, e.g. "nav-header"). */
38
45
  part?: string | undefined
39
46
  }
40
47
 
41
- const { items, label = 'Main', menuLabel = 'Menu', closeLabel = 'Close', mobile = 'popover', align, density, id = 'site-nav', part } = Astro.props
42
- const { list, link, sub, toggle } = nav({ align, density })
48
+ const { items, label = 'Main', menuLabel = 'Menu', closeLabel = 'Close', mobile = 'popover', align, density, color, id = 'site-nav', part } = Astro.props
49
+ const { list, link, sub, toggle } = nav({ align, density, color })
43
50
  const current = Astro.url.pathname
44
51
  const drawer = `${id}-drawer`
45
52
  ---
@@ -61,6 +61,7 @@
61
61
  "props": {
62
62
  "id": "fx-nav-c",
63
63
  "align": "center",
64
+ "color": "link",
64
65
  "items": [
65
66
  {
66
67
  "label": "Work",
@@ -32,6 +32,14 @@
32
32
  ],
33
33
  "default": "compact",
34
34
  "description": "Gap and text size of the inline list."
35
+ },
36
+ "color": {
37
+ "options": [
38
+ "current",
39
+ "link"
40
+ ],
41
+ "default": "current",
42
+ "description": "current: the surrounding text, dimmed until hovered. link: the site's link colour (--color-link, else the accent) at full strength."
35
43
  }
36
44
  },
37
45
  "props": {
@@ -37,9 +37,11 @@ interface Props {
37
37
  author?: string | undefined
38
38
  headingLevel?: 2 | 3 | undefined
39
39
  layout?: 'vertical' | 'horizontal' | 'minimal' | undefined
40
+ /** The title in the site's link colour, as themes that colour every link do (Hello Elementor). */
41
+ linkTitle?: boolean | undefined
40
42
  }
41
43
 
42
- const { title, href, excerpt, date: rawDate, locale = 'en', image, category, author, headingLevel = 2, layout } = Astro.props
44
+ const { title, href, excerpt, date: rawDate, locale = 'en', image, category, author, headingLevel = 2, layout, linkTitle } = Astro.props
43
45
  const date = rawDate === undefined ? undefined : new Date(rawDate)
44
46
  const slots = card({ layout })
45
47
  const Heading = `h${headingLevel}` as 'h2' | 'h3'
@@ -58,7 +60,7 @@ const Heading = `h${headingLevel}` as 'h2' | 'h3'
58
60
  {date && <time datetime={date.toISOString()}>{date.toLocaleDateString(locale, { dateStyle: 'long', timeZone: 'UTC' })}</time>}
59
61
  </p>
60
62
  )}
61
- <Heading class={slots.title()}>
63
+ <Heading class={slots.title({ class: linkTitle ? 'text-[color:var(--color-link,var(--color-accent))]' : undefined })}>
62
64
  <a href={href} class="text-current no-underline after:absolute after:inset-0 after:content-[''] hover:text-accent">{title}</a>
63
65
  </Heading>
64
66
  {excerpt && <p class={slots.excerpt()}>{excerpt}</p>}
@@ -17,7 +17,8 @@
17
17
  "image": { "type": "object", "fields": { "src": { "type": "image", "required": true }, "alt": { "type": "string", "required": true }, "width": { "type": "integer" }, "height": { "type": "integer" } }, "description": "Cover (posts.cover → media)." },
18
18
  "category": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "href": { "type": "url", "required": true } }, "description": "Primary category and its archive." },
19
19
  "author": { "type": "string", "description": "Author name (posts.author → authors.name)." },
20
- "headingLevel": { "type": "integer", "min": 2, "max": 3, "default": 2, "description": "Title heading level in the page outline." }
20
+ "headingLevel": { "type": "integer", "min": 2, "max": 3, "default": 2, "description": "Title heading level in the page outline." },
21
+ "linkTitle": { "type": "boolean", "default": false, "description": "The title in the site's link colour (--color-link), as themes that colour every link do." }
21
22
  },
22
23
  "slots": {},
23
24
  "js": "none",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/astro-kit",
3
- "version": "0.3.1",
3
+ "version": "0.5.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.27.0"
45
+ "@contentrain/types": "1.29.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.10.0",