@contentrain/astro-kit 0.29.3 → 0.30.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
@@ -2427,6 +2427,18 @@
2427
2427
  "type": "boolean",
2428
2428
  "default": false,
2429
2429
  "description": "The title in the site's link colour (--color-link), as themes that colour every link do."
2430
+ },
2431
+ "sizes": {
2432
+ "type": "string",
2433
+ "description": "The image's `sizes` hint; absent, the card's own guess. A list passes the slot its columns give."
2434
+ },
2435
+ "imageHeight": {
2436
+ "type": "string",
2437
+ "description": "The image block's height as the source sets it (a CSS length or clamp()), cropped to fit; wins over imageAspect."
2438
+ },
2439
+ "imageAspect": {
2440
+ "type": "string",
2441
+ "description": "The image block's aspect ratio (16 / 9) where the source sets an aspect ratio; absent, 16 / 9."
2430
2442
  }
2431
2443
  },
2432
2444
  "slots": {},
@@ -23,6 +23,8 @@ interface Props {
23
23
  /** Rendered width in the layout, for srcset selection. */
24
24
  sizes?: string | undefined
25
25
  class?: string | undefined
26
+ /** Inline style, for a size the source sets (a card image's height). */
27
+ style?: string | undefined
26
28
  /** The page's largest image: load it first. */
27
29
  priority?: boolean | undefined
28
30
  }
@@ -30,7 +32,7 @@ interface Props {
30
32
  // Raster files under public/, loaded only when a page shows one.
31
33
  const LOCAL = import.meta.glob<ImageMetadata>('/public/**/*.{avif,gif,jpeg,jpg,png,tif,tiff,webp,AVIF,GIF,JPEG,JPG,PNG,TIF,TIFF,WEBP}', { import: 'default' })
32
34
 
33
- const { image: given, sizes = '100vw', class: className, priority = false } = Astro.props
35
+ const { image: given, sizes = '100vw', class: className, style, priority = false } = Astro.props
34
36
  // Content stores an address; the size the migration measured comes from the site's media table.
35
37
  const measured = given.width && given.height ? undefined : mediaSize(given.src)
36
38
  const image: ImageInput = measured ? { ...given, ...measured } : given
@@ -48,7 +50,7 @@ const STEPS = [320, 640, 960, 1280, 1600, 1920, 2560]
48
50
  const widthsUpTo = (max: number) => [...STEPS.filter(step => step < max), max]
49
51
  ---
50
52
  {local
51
- ? <Image src={local} alt={image.alt ?? ''} {...size} widths={widthsUpTo(image.width ?? local.width)} sizes={sizes} class={className} loading={loading} fetchpriority={fetchpriority} />
53
+ ? <Image src={local} alt={image.alt ?? ''} {...size} widths={widthsUpTo(image.width ?? local.width)} sizes={sizes} class={className} style={style} loading={loading} fetchpriority={fetchpriority} />
52
54
  : image.width && image.height && path === undefined
53
- ? <Image src={image.src} alt={image.alt ?? ''} width={image.width} height={image.height} widths={widthsUpTo(image.width)} sizes={sizes} class={className} loading={loading} fetchpriority={fetchpriority} />
54
- : <img src={image.src} alt={image.alt ?? ''} width={image.width} height={image.height} class={className} loading={loading} fetchpriority={fetchpriority} decoding="async" />}
55
+ ? <Image src={image.src} alt={image.alt ?? ''} width={image.width} height={image.height} widths={widthsUpTo(image.width)} sizes={sizes} class={className} style={style} loading={loading} fetchpriority={fetchpriority} />
56
+ : <img src={image.src} alt={image.alt ?? ''} width={image.width} height={image.height} class={className} style={style} loading={loading} fetchpriority={fetchpriority} decoding="async" />}
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  // Site navigation. Wide screens get an inline list whose submenus open on
3
- // hover and keyboard focus; narrow screens get a toggle that opens the menu as
3
+ // hover and keyboard focus (a chevron button marks the ones that have one); narrow screens get a toggle that opens the menu as
4
4
  // a native popover (drawer) or a <details> dropdown. No JavaScript.
5
5
  import { tv } from 'tailwind-variants'
6
6
  import type { NavItem } from '../_shared/types'
@@ -57,11 +57,17 @@ const drawer = `${id}-drawer`
57
57
  <li class="group relative">
58
58
  <a href={item.href} aria-current={item.href === current ? 'page' : undefined} target={item.newTab ? '_blank' : undefined} rel={item.newTab ? 'noopener' : undefined} class={link()}>{item.label}</a>
59
59
  {item.children && item.children.length > 0 && (
60
+ <>
61
+ {/* The chevron is the submenu's handle: focusing it (a tap on a touch screen, Tab on a keyboard) opens the submenu, with no script. */}
62
+ <button type="button" aria-haspopup="true" aria-label={item.label} class="ms-1 inline-flex cursor-pointer items-center align-middle opacity-80 hover:opacity-100">
63
+ <svg aria-hidden="true" viewBox="0 0 24 24" class="size-4" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 9 6 6 6-6" /></svg>
64
+ </button>
60
65
  <ul class={sub()}>
61
66
  {item.children.map(child => (
62
67
  <li><a href={child.href} aria-current={child.href === current ? 'page' : undefined} class="block rounded-md px-3 py-2 text-sm text-ink no-underline hover:bg-surface-muted">{child.label}</a></li>
63
68
  ))}
64
69
  </ul>
70
+ </>
65
71
  )}
66
72
  </li>
67
73
  ))}
@@ -52,19 +52,26 @@ interface Props {
52
52
  byline?: 'inline' | 'below' | undefined
53
53
  /** The title in the site's link colour, as themes that colour every link do (Hello Elementor). */
54
54
  linkTitle?: boolean | undefined
55
+ /** The image's `sizes` hint; absent, the card's own guess. A list passes the slot its columns give. */
56
+ sizes?: string | undefined
57
+ /** The image block's height as the source sets it (a CSS length or `clamp()`), cropped to fit; wins over `imageAspect`. */
58
+ imageHeight?: string | undefined
59
+ /** The image block's aspect ratio (`16 / 9`) where the source sets an aspect ratio; absent, 16 / 9. */
60
+ imageAspect?: string | undefined
55
61
  }
56
62
 
57
- const { title, href, excerpt, date: rawDate, locale = 'en', image, category, author, headingLevel = 2, layout, surface, byline, linkTitle } = Astro.props
63
+ const { title, href, excerpt, date: rawDate, locale = 'en', image, category, author, headingLevel = 2, layout, surface, byline, linkTitle, sizes, imageHeight, imageAspect } = Astro.props
58
64
  const date = rawDate === undefined ? undefined : new Date(rawDate)
59
65
  const slots = card({ layout, surface, byline })
60
66
  const below = byline === 'below'
61
67
  const shown = date?.toLocaleDateString(locale, { dateStyle: 'long', timeZone: 'UTC' })
68
+ const imageStyle = imageHeight ? `height:${imageHeight}` : imageAspect ? `aspect-ratio:${imageAspect}` : undefined
62
69
  const Heading = `h${headingLevel}` as 'h2' | 'h3'
63
70
  ---
64
71
  <article class={slots.root()}>
65
72
  {image && layout !== 'minimal' && (
66
73
  <div class={slots.media()}>
67
- <KitImage image={image} sizes={layout === 'horizontal' ? '256px' : '(min-width: 1024px) 360px, 100vw'} class="aspect-[16/9] w-full object-cover transition duration-300 group-hover:scale-[1.02]" />
74
+ <KitImage image={image} sizes={sizes ?? (layout === 'horizontal' ? '256px' : '(min-width: 1024px) 360px, 100vw')} class:list={['w-full object-cover transition duration-300 group-hover:scale-[1.02]', !imageStyle && 'aspect-[16/9]']} style={imageStyle} />
68
75
  </div>
69
76
  )}
70
77
  <div class={slots.body()}>
@@ -20,7 +20,10 @@
20
20
  "category": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "href": { "type": "url", "required": true } }, "description": "Primary category and its archive." },
21
21
  "author": { "type": "string", "description": "Author name (posts.author → authors.name)." },
22
22
  "headingLevel": { "type": "integer", "min": 2, "max": 3, "default": 2, "description": "Title heading level in the page outline." },
23
- "linkTitle": { "type": "boolean", "default": false, "description": "The title in the site's link colour (--color-link), as themes that colour every link do." }
23
+ "linkTitle": { "type": "boolean", "default": false, "description": "The title in the site's link colour (--color-link), as themes that colour every link do." },
24
+ "sizes": { "type": "string", "description": "The image's `sizes` hint; absent, the card's own guess. A list passes the slot its columns give." },
25
+ "imageHeight": { "type": "string", "description": "The image block's height as the source sets it (a CSS length or clamp()), cropped to fit; wins over imageAspect." },
26
+ "imageAspect": { "type": "string", "description": "The image block's aspect ratio (16 / 9) where the source sets an aspect ratio; absent, 16 / 9." }
24
27
  },
25
28
  "slots": {},
26
29
  "js": "none",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/astro-kit",
3
- "version": "0.29.3",
3
+ "version": "0.30.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.56.1"
45
+ "@contentrain/types": "1.58.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.10.0",