@contentrain/astro-kit 0.49.3 → 0.50.1

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
@@ -3566,7 +3566,7 @@
3566
3566
  {
3567
3567
  "id": "header",
3568
3568
  "name": "Header",
3569
- "description": "Site header: brand (logo and/or name), navigation and an optional call-to-action button.",
3569
+ "description": "Site header: brand (logo and/or name), navigation, an optional call-to-action button and an optional utility row above the bar (phone, mail, address, hours).",
3570
3570
  "category": "chrome",
3571
3571
  "uses": [
3572
3572
  "nav",
@@ -3739,7 +3739,23 @@
3739
3739
  ]
3740
3740
  }
3741
3741
  },
3742
- "description": "Header button, shown on wide screens."
3742
+ "description": "Header button, on every screen width."
3743
+ },
3744
+ "strip": {
3745
+ "type": "array",
3746
+ "items": {
3747
+ "type": "object",
3748
+ "fields": {
3749
+ "label": {
3750
+ "type": "string",
3751
+ "required": true
3752
+ },
3753
+ "href": {
3754
+ "type": "url"
3755
+ }
3756
+ }
3757
+ },
3758
+ "description": "A row above the bar for what the source's header prints next to its menu: a phone number, a mail address, the address, the opening hours. A line with an href is a link (tel:, mailto:, a page). On a phone it folds into the menu drawer after the menu; with no menu it stays above the bar."
3743
3759
  },
3744
3760
  "search": {
3745
3761
  "type": "object",
@@ -4664,6 +4680,22 @@
4664
4680
  "part": {
4665
4681
  "type": "string",
4666
4682
  "description": "What the navigation is, for checks that find a page's parts (data-cr-part), e.g. \"nav-header\"."
4683
+ },
4684
+ "extra": {
4685
+ "type": "array",
4686
+ "items": {
4687
+ "type": "object",
4688
+ "fields": {
4689
+ "label": {
4690
+ "type": "string",
4691
+ "required": true
4692
+ },
4693
+ "href": {
4694
+ "type": "url"
4695
+ }
4696
+ }
4697
+ },
4698
+ "description": "Lines after the menu in the narrow-screen menu (a header's phone, address, hours); a line with an href is a link."
4667
4699
  }
4668
4700
  },
4669
4701
  "slots": {},
@@ -1,6 +1,6 @@
1
1
  ---
2
- // Site header: brand (logo and/or name), navigation and an optional call to
3
- // action.
2
+ // Site header: brand (logo and/or name), navigation, an optional call to
3
+ // action and an optional utility row above the bar (phone, mail, address, hours).
4
4
  import { tv } from 'tailwind-variants'
5
5
  import Button from '../_shared/Button.astro'
6
6
  import KitImage from '../_shared/KitImage.astro'
@@ -63,7 +63,14 @@ interface Props {
63
63
  showName?: boolean | undefined
64
64
  homeHref?: string | undefined
65
65
  items: NavItem[]
66
+ /** The header's button, on every screen width. */
66
67
  action?: ActionInput | undefined
68
+ /**
69
+ * A row above the bar for what the source's header prints next to its menu: a phone number, a mail address, the
70
+ * address, the opening hours. A line with an `href` is a link (`tel:`, `mailto:`, a page). On a phone the row folds
71
+ * into the menu drawer, after the menu; with no menu it stays above the bar.
72
+ */
73
+ strip?: Array<{ label: string, href?: string | undefined }> | undefined
67
74
  /** A search field in the bar (a GET form to the site's search page); below the bar until lg. */
68
75
  search?: { label?: string | undefined, placeholder?: string | undefined, submitLabel?: string | undefined, action?: string | undefined } | undefined
69
76
  navLabel?: string | undefined
@@ -76,7 +83,7 @@ interface Props {
76
83
  menu?: 'flyout' | 'mega' | undefined
77
84
  }
78
85
 
79
- const { siteName, tagline, brandSize, brandWeight, brandTracking, bordered, offsetTop, padTop, padBottom, linkNav, brandColor, logo, showName = true, homeHref = '/', items, action, search, navLabel, menuLabel, closeLabel, layout, tone, position, menu } = Astro.props
86
+ const { siteName, tagline, brandSize, brandWeight, brandTracking, bordered, offsetTop, padTop, padBottom, linkNav, brandColor, logo, showName = true, homeHref = '/', items, action, strip = [], search, navLabel, menuLabel, closeLabel, layout, tone, position, menu } = Astro.props
80
87
  const { root, inner, brand } = header({ layout, tone, position, bordered })
81
88
  // The header's search field: `header-search` on a page, a second header's its own (its label points at its own input).
82
89
  const searchId = search ? instanceId(Astro.locals, undefined, 'header-search') : undefined
@@ -84,11 +91,22 @@ const rootStyle = offsetTop ? `margin-top: ${offsetTop}` : undefined
84
91
  const innerStyle = [padTop && `padding-top: ${padTop}`, padBottom && `padding-bottom: ${padBottom}`].filter(Boolean).join('; ') || undefined
85
92
  const brandColour = brandColorValue(brandColor)
86
93
  const brandStyle = [brandSize && `font-size: ${brandSize}`, brandWeight && `font-weight: ${brandWeight}`, brandTracking && `letter-spacing: ${brandTracking}`, brandColour && `color: ${brandColour}`].filter(Boolean).join('; ') || undefined
94
+ // The row above the bar folds into the drawer only where there is one: Nav prints nothing without items.
95
+ const stripFolds = items.length > 0
87
96
  // A tagline under the name keeps its line on a phone, as themes print it: the navigation wraps below.
88
97
  // The search field sits in the bar from lg; at md the bar already holds the navigation, so the field keeps its own row.
89
98
  const wrap = search ? 'flex-wrap lg:flex-nowrap' : tagline ? 'flex-wrap md:flex-nowrap' : undefined
90
99
  ---
91
100
  <header class={root()} style={rootStyle}>
101
+ {strip.length > 0 && (
102
+ <div class:list={['border-b border-current/10 text-sm', stripFolds && 'hidden md:block']} data-kit-strip>
103
+ <ul class={`container-page flex flex-wrap items-center justify-end gap-x-6 gap-y-1 py-2 ${muted}`}>
104
+ {strip.map(line => (
105
+ <li>{line.href ? <a href={safeHref(line.href)} class="text-current no-underline hover:underline">{line.label}</a> : line.label}</li>
106
+ ))}
107
+ </ul>
108
+ </div>
109
+ )}
92
110
  <div class={inner({ class: wrap })} style={innerStyle}>
93
111
  {tagline
94
112
  ? (
@@ -112,8 +130,8 @@ const wrap = search ? 'flex-wrap lg:flex-nowrap' : tagline ? 'flex-wrap md:flex-
112
130
  </div>
113
131
  )}
114
132
  <div class:list={['flex items-center gap-4', layout === 'centered' ? 'justify-center' : 'ms-auto']}>
115
- <Nav items={items} menu={menu} label={navLabel} menuLabel={menuLabel} closeLabel={closeLabel} part="nav-header" color={linkNav ? 'link' : 'current'} align={layout === 'centered' ? 'center' : 'end'} />
116
- {action && <Button action={action} size="sm" class="hidden md:inline-flex" />}
133
+ <Nav items={items} menu={menu} label={navLabel} menuLabel={menuLabel} closeLabel={closeLabel} part="nav-header" extra={stripFolds ? strip : undefined} color={linkNav ? 'link' : 'current'} align={layout === 'centered' ? 'center' : 'end'} />
134
+ {action && <Button action={action} size="sm" />}
117
135
  </div>
118
136
  </div>
119
137
  </header>
@@ -20,7 +20,16 @@
20
20
  "action": {
21
21
  "label": "Subscribe",
22
22
  "href": "/subscribe/"
23
- }
23
+ },
24
+ "strip": [
25
+ {
26
+ "label": "(417) 522-7711",
27
+ "href": "tel:+14175227711"
28
+ },
29
+ {
30
+ "label": "Mon–Fri 9:00–17:00"
31
+ }
32
+ ]
24
33
  }
25
34
  },
26
35
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "header",
3
3
  "name": "Header",
4
- "description": "Site header: brand (logo and/or name), navigation and an optional call-to-action button.",
4
+ "description": "Site header: brand (logo and/or name), navigation, an optional call-to-action button and an optional utility row above the bar (phone, mail, address, hours).",
5
5
  "category": "chrome",
6
6
  "uses": ["nav", "search-box"],
7
7
  "shared": ["Button.astro", "KitImage.astro", "image-widths.ts", "instance-id.ts", "muted.ts", "safe-url.ts", "types.ts"],
@@ -27,7 +27,8 @@
27
27
  "showName": { "type": "boolean", "default": true, "description": "Show the site name next to the logo." },
28
28
  "homeHref": { "type": "url", "default": "/", "description": "Where the brand links." },
29
29
  "items": { "type": "array", "required": true, "items": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "href": { "type": "url", "required": true }, "newTab": { "type": "boolean" }, "children": { "type": "array" } } }, "description": "Primary menu (menus → menu-items)." },
30
- "action": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "href": { "type": "url", "required": true }, "style": { "type": "select", "options": ["primary", "secondary", "ghost"] } }, "description": "Header button, shown on wide screens." },
30
+ "action": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "href": { "type": "url", "required": true }, "style": { "type": "select", "options": ["primary", "secondary", "ghost"] } }, "description": "Header button, on every screen width." },
31
+ "strip": { "type": "array", "items": { "type": "object", "fields": { "label": { "type": "string", "required": true }, "href": { "type": "url" } } }, "description": "A row above the bar for what the source's header prints next to its menu: a phone number, a mail address, the address, the opening hours. A line with an href is a link (tel:, mailto:, a page). On a phone it folds into the menu drawer after the menu; with no menu it stays above the bar." },
31
32
  "search": { "type": "object", "fields": { "label": { "type": "string" }, "placeholder": { "type": "string" }, "submitLabel": { "type": "string" }, "action": { "type": "url" } }, "description": "A search field in the bar: a GET form to the site's search page (default /search); below the bar on a phone." },
32
33
  "navLabel": { "type": "string", "description": "Accessible name of the navigation (ui-strings nav.label)." },
33
34
  "menuLabel": { "type": "string", "description": "Narrow-screen toggle text (ui-strings nav.menu)." },
@@ -50,11 +50,13 @@ interface Props {
50
50
  color?: 'current' | 'link' | undefined
51
51
  /** Unique per page when a page has two navigations. */
52
52
  id?: string | undefined
53
+ /** Lines after the menu in the narrow-screen menu (a header's phone, address, hours); a line with an `href` is a link. */
54
+ extra?: Array<{ label: string, href?: string | undefined }> | undefined
53
55
  /** What the navigation is, for checks that find a page's parts (`data-cr-part`, e.g. "nav-header"). */
54
56
  part?: string | undefined
55
57
  }
56
58
 
57
- const { items, label = 'Main', menuLabel = 'Menu', closeLabel = 'Close', mobile = 'popover', menu, align, density, color, id: givenId, part } = Astro.props
59
+ const { items, label = 'Main', menuLabel = 'Menu', closeLabel = 'Close', mobile = 'popover', menu, align, density, color, extra = [], id: givenId, part } = Astro.props
58
60
  // Two on one page never share an id: their labels and controls would point into each other.
59
61
  const id = instanceId(Astro.locals, givenId, 'site-nav')
60
62
  const { list, link, sub, toggle } = nav({ menu, align, density, color })
@@ -117,6 +119,13 @@ const real = (href: string | undefined) => !!href && href !== '#'
117
119
  {item.children && item.children.length > 0 && <NavTree items={item.children} current={current} kind="drawer" />}
118
120
  </li>
119
121
  ))}
122
+ {extra.map((line, index) => (
123
+ <li class={index === 0 ? 'mt-4 border-t border-line pt-4' : undefined}>
124
+ {line.href
125
+ ? <a href={safeHref(line.href)} class="block rounded-md px-3 py-2 text-sm text-ink no-underline hover:bg-surface-muted">{line.label}</a>
126
+ : <span class="block px-3 py-2 text-sm text-ink">{line.label}</span>}
127
+ </li>
128
+ ))}
120
129
  </ul>
121
130
  </div>
122
131
  </>
@@ -136,6 +145,13 @@ const real = (href: string | undefined) => !!href && href !== '#'
136
145
  {item.children && item.children.length > 0 && <NavTree items={item.children} current={current} kind="drawer" />}
137
146
  </li>
138
147
  ))}
148
+ {extra.map((line, index) => (
149
+ <li class={index === 0 ? 'mt-2 border-t border-line pt-2' : undefined}>
150
+ {line.href
151
+ ? <a href={safeHref(line.href)} class="block rounded-md px-3 py-2 text-sm text-ink no-underline hover:bg-surface-muted">{line.label}</a>
152
+ : <span class="block px-3 py-2 text-sm text-ink">{line.label}</span>}
153
+ </li>
154
+ ))}
139
155
  </ul>
140
156
  </details>
141
157
  )}
@@ -97,6 +97,22 @@
97
97
  "part": {
98
98
  "type": "string",
99
99
  "description": "What the navigation is, for checks that find a page's parts (data-cr-part), e.g. \"nav-header\"."
100
+ },
101
+ "extra": {
102
+ "type": "array",
103
+ "items": {
104
+ "type": "object",
105
+ "fields": {
106
+ "label": {
107
+ "type": "string",
108
+ "required": true
109
+ },
110
+ "href": {
111
+ "type": "url"
112
+ }
113
+ }
114
+ },
115
+ "description": "Lines after the menu in the narrow-screen menu (a header's phone, address, hours); a line with an href is a link."
100
116
  }
101
117
  },
102
118
  "slots": {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/astro-kit",
3
- "version": "0.49.3",
3
+ "version": "0.50.1",
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.75.0"
45
+ "@contentrain/types": "1.77.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.10.0",