@contentrain/astro-kit 0.40.0 → 0.42.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 +16 -0
- package/catalog.json +123 -15
- package/components/_shared/Lines.astro +12 -0
- package/components/_shared/Section.astro +13 -9
- package/components/_shared/SectionHeader.astro +3 -2
- package/components/_shared/bleed.ts +8 -0
- package/components/_shared/instance-id.ts +24 -0
- package/components/_shared/muted.ts +19 -0
- package/components/band/Band.astro +10 -7
- package/components/band/meta.json +2 -0
- package/components/behavior/Behavior.astro +7 -2
- package/components/behavior/fixtures.json +2 -2
- package/components/behavior/meta.json +7 -0
- package/components/card-grid/CardGrid.astro +84 -24
- package/components/card-grid/fixtures.json +4 -1
- package/components/card-grid/meta.json +21 -0
- package/components/contact-details/ContactDetails.astro +7 -3
- package/components/contact-details/meta.json +2 -0
- package/components/contact-form/ContactForm.astro +11 -7
- package/components/contact-form/meta.json +2 -0
- package/components/cta/Cta.astro +14 -10
- package/components/cta/meta.json +2 -2
- package/components/dialog/Dialog.astro +6 -3
- package/components/dialog/meta.json +2 -0
- package/components/faq/Faq.astro +8 -4
- package/components/faq/meta.json +1 -1
- package/components/feature-list/FeatureList.astro +10 -6
- package/components/feature-list/meta.json +3 -1
- package/components/footer/Footer.astro +4 -3
- package/components/footer/meta.json +1 -0
- package/components/gallery/Gallery.astro +8 -4
- package/components/gallery/meta.json +2 -0
- package/components/header/Header.astro +8 -4
- package/components/header/meta.json +1 -1
- package/components/hero/Hero.astro +44 -15
- package/components/hero/fixtures.json +7 -1
- package/components/hero/meta.json +30 -1
- package/components/language-switcher/LanguageSwitcher.astro +3 -2
- package/components/language-switcher/meta.json +1 -1
- package/components/logo-cloud/LogoCloud.astro +9 -5
- package/components/logo-cloud/meta.json +3 -1
- package/components/nav/Nav.astro +10 -6
- package/components/nav/meta.json +2 -0
- package/components/newsletter/Newsletter.astro +9 -5
- package/components/newsletter/meta.json +2 -0
- package/components/post-list/PostList.astro +7 -3
- package/components/post-list/meta.json +2 -0
- package/components/pricing/Pricing.astro +15 -11
- package/components/pricing/meta.json +3 -1
- package/components/search-box/SearchBox.astro +4 -1
- package/components/search-box/meta.json +1 -1
- package/components/slider/Slider.astro +4 -1
- package/components/slider/SliderTrack.astro +11 -5
- package/components/slider/meta.json +1 -1
- package/components/social-links/SocialLinks.astro +2 -1
- package/components/social-links/meta.json +3 -2
- package/components/split/Split.astro +16 -11
- package/components/split/fixtures.json +4 -1
- package/components/split/meta.json +9 -3
- package/components/stats/Stats.astro +11 -7
- package/components/stats/meta.json +3 -1
- package/components/steps/Steps.astro +9 -5
- package/components/steps/meta.json +3 -1
- package/components/tabs/Tabs.astro +8 -4
- package/components/tabs/meta.json +1 -1
- package/components/team/Team.astro +10 -6
- package/components/team/meta.json +4 -2
- package/components/testimonial/Testimonial.astro +9 -5
- package/components/testimonial/fixtures.json +1 -1
- package/components/testimonial/meta.json +3 -1
- package/package.json +1 -1
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"Button.astro",
|
|
12
12
|
"KitImage.astro",
|
|
13
13
|
"Section.astro",
|
|
14
|
+
"bleed.ts",
|
|
15
|
+
"instance-id.ts",
|
|
14
16
|
"measured.ts",
|
|
17
|
+
"muted.ts",
|
|
15
18
|
"safe-url.ts",
|
|
16
19
|
"types.ts"
|
|
17
20
|
],
|
|
@@ -45,10 +48,13 @@
|
|
|
45
48
|
"options": [
|
|
46
49
|
"none",
|
|
47
50
|
"rounded",
|
|
48
|
-
"circle"
|
|
51
|
+
"circle",
|
|
52
|
+
"arch",
|
|
53
|
+
"chamfer",
|
|
54
|
+
"blob"
|
|
49
55
|
],
|
|
50
56
|
"default": "rounded",
|
|
51
|
-
"description": "The image's
|
|
57
|
+
"description": "The image's outline: square, the theme's rounding, a circle, or a fixed mask: an arch over the image, a cut corner (chamfer), a soft blob."
|
|
52
58
|
},
|
|
53
59
|
"imageBleed": {
|
|
54
60
|
"options": [
|
|
@@ -339,7 +345,7 @@
|
|
|
339
345
|
"id": {
|
|
340
346
|
"type": "string",
|
|
341
347
|
"content": false,
|
|
342
|
-
"description": "Anchor id of the section; the heading gets `<id>-title`."
|
|
348
|
+
"description": "Anchor id of the section; the heading gets `<id>-title`. Without one, `split`, `split-2`, … in render order, so every section on a page has its own."
|
|
343
349
|
},
|
|
344
350
|
"sticky": {
|
|
345
351
|
"type": "boolean",
|
|
@@ -7,9 +7,11 @@ import { tv } from 'tailwind-variants'
|
|
|
7
7
|
import BehaviorScript from '../_shared/BehaviorScript.astro'
|
|
8
8
|
import KitImage from '../_shared/KitImage.astro'
|
|
9
9
|
import Section, { type Tone } from '../_shared/Section.astro'
|
|
10
|
+
import { instanceId } from '../_shared/instance-id'
|
|
10
11
|
import { imageOf, type ImageSource } from '../_shared/image'
|
|
11
12
|
import { measuredGrid } from '../_shared/measured'
|
|
12
13
|
import type { MeasuredStyle } from '../_shared/types'
|
|
14
|
+
import { muted, mutedReset } from '../_shared/muted'
|
|
13
15
|
|
|
14
16
|
const stats = tv({
|
|
15
17
|
slots: {
|
|
@@ -20,7 +22,7 @@ const stats = tv({
|
|
|
20
22
|
// height comes with valueSize too: a font-size class merged in later would drop one set here.
|
|
21
23
|
value: 'text-[length:var(--stat-max,2.25rem)] [font-weight:var(--font-weight-heading,700)] tracking-tight tabular-nums whitespace-nowrap [--stat-max-md:3rem] md:text-[length:var(--stat-max-md)]',
|
|
22
24
|
icon: 'mb-3 block size-10 object-contain',
|
|
23
|
-
label:
|
|
25
|
+
label: `text-sm ${muted}`,
|
|
24
26
|
},
|
|
25
27
|
variants: {
|
|
26
28
|
// grid: figures in columns. inline: one line that wraps, for two to four short figures.
|
|
@@ -43,7 +45,7 @@ const stats = tv({
|
|
|
43
45
|
},
|
|
44
46
|
style: {
|
|
45
47
|
plain: {},
|
|
46
|
-
card: { item:
|
|
48
|
+
card: { item: `rounded-[var(--radius-card)] bg-surface p-6 text-ink ring-1 ring-line/60 ${mutedReset}` },
|
|
47
49
|
// Hairlines between the figures, none before the first, no box: across on a wide screen, above each on a phone.
|
|
48
50
|
// Meant for a single row (figures no more than the columns); a wrapped row starts with a line.
|
|
49
51
|
divided: { item: 'border-t border-line pt-6 first:border-t-0 first:pt-0 md:border-t-0 md:pt-0 md:border-s md:ps-6 md:first:border-s-0 md:first:ps-0' },
|
|
@@ -57,7 +59,7 @@ const stats = tv({
|
|
|
57
59
|
display: { value: '[--stat-max:3.75rem] [--stat-max-md:4.5rem] leading-none' },
|
|
58
60
|
},
|
|
59
61
|
// accent/accent-ink is only ~5:1 — dimming text on it drops it under 4.5:1 (WCAG 1.4.3).
|
|
60
|
-
tone: { default: {}, muted: {}, inverse: {}, accent: {
|
|
62
|
+
tone: { default: {}, muted: {}, inverse: {}, accent: {} },
|
|
61
63
|
// The label under each figure: small, body size, or large (a counter title set like a subheading).
|
|
62
64
|
labelSize: { sm: {}, base: { label: 'text-base' }, lg: { label: 'text-lg' } },
|
|
63
65
|
// Which comes first in the page's text: the source's own order (a Gutenberg figure is a heading over its label, an
|
|
@@ -95,23 +97,25 @@ interface Props {
|
|
|
95
97
|
id?: string | undefined
|
|
96
98
|
}
|
|
97
99
|
|
|
98
|
-
const { heading, intro, items, layout, columns, style, align, valueSize, labelSize, order, motion, tone, measured, id } = Astro.props
|
|
100
|
+
const { heading, intro, items, layout, columns, style, align, valueSize, labelSize, order, motion, tone, measured, id: givenId } = Astro.props
|
|
101
|
+
// Every section has an id, so a theme or a design layer can address this one: the page's, or `stats`, `stats-2`, … in render order.
|
|
102
|
+
const id = instanceId(Astro.locals, givenId, 'stats')
|
|
99
103
|
const count = motion === 'count'
|
|
100
104
|
// A count-up island (behaviors.ts `countup`): the root names the kind, each figure's digits are their own span.
|
|
101
105
|
const island = count ? { 'data-cr-behavior': 'countup', 'data-cr-items': '[data-kit-count]' } : {}
|
|
102
106
|
const s = stats({ layout, columns, style, align, valueSize, labelSize, order, tone })
|
|
103
107
|
const grid = (layout ?? 'grid') === 'grid' ? measuredGrid(measured) : undefined
|
|
104
|
-
const headingId = heading
|
|
108
|
+
const headingId = heading ? `${id}-title` : undefined
|
|
105
109
|
const iconOf = (item: Item) => imageOf(item.icon, '')
|
|
106
110
|
// The figure's width in characters, for the size it may take in a narrow cell.
|
|
107
111
|
const widthOf = (item: Item) => `--stat-chars:${(item.prefix ?? '').length + item.value.length + (item.suffix ?? '').length}`
|
|
108
112
|
// A <dl> names its term first, so figure-first is a plain list, read in the order it is drawn (WCAG 1.3.2).
|
|
109
113
|
---
|
|
110
|
-
<Section measured={measured} tone={tone} id={id} aria-labelledby={headingId}>
|
|
114
|
+
<Section kit="stats" measured={measured} tone={tone} id={id} aria-labelledby={headingId}>
|
|
111
115
|
{(heading || intro) && (
|
|
112
116
|
<header class="mb-10 flex max-w-2xl flex-col gap-3">
|
|
113
117
|
{heading && <h2 id={headingId} class="text-3xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-4xl">{heading}</h2>}
|
|
114
|
-
{intro && <p class:list={['text-lg',
|
|
118
|
+
{intro && <p class:list={['text-lg', muted]} data-kit-text>{intro}</p>}
|
|
115
119
|
</header>
|
|
116
120
|
)}
|
|
117
121
|
{order === 'value-first' ? (
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"embed-frame.ts",
|
|
13
13
|
"embed-target.ts",
|
|
14
14
|
"image.ts",
|
|
15
|
+
"instance-id.ts",
|
|
15
16
|
"measured.ts",
|
|
17
|
+
"muted.ts",
|
|
16
18
|
"safe-url.ts",
|
|
17
19
|
"types.ts"
|
|
18
20
|
],
|
|
@@ -261,7 +263,7 @@
|
|
|
261
263
|
"id": {
|
|
262
264
|
"type": "string",
|
|
263
265
|
"content": false,
|
|
264
|
-
"description": "Anchor id of the section; the heading gets `<id>-title`."
|
|
266
|
+
"description": "Anchor id of the section; the heading gets `<id>-title`. Without one, `stats`, `stats-2`, … in render order, so every section on a page has its own."
|
|
265
267
|
}
|
|
266
268
|
},
|
|
267
269
|
"slots": {},
|
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
import { tv } from 'tailwind-variants'
|
|
7
7
|
import KitImage from '../_shared/KitImage.astro'
|
|
8
8
|
import Section from '../_shared/Section.astro'
|
|
9
|
+
import { instanceId } from '../_shared/instance-id'
|
|
9
10
|
import { measuredGrid } from '../_shared/measured'
|
|
10
11
|
import type { MeasuredStyle } from '../_shared/types'
|
|
11
12
|
import { imageOf, type ImageSource } from '../_shared/image'
|
|
13
|
+
import { muted } from '../_shared/muted'
|
|
12
14
|
|
|
13
15
|
const steps = tv({
|
|
14
16
|
slots: {
|
|
15
17
|
header: 'mb-10 flex max-w-2xl flex-col gap-3',
|
|
16
18
|
heading: 'text-3xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-4xl',
|
|
17
|
-
intro:
|
|
19
|
+
intro: `text-lg ${muted}`,
|
|
18
20
|
list: 'grid gap-8 [counter-reset:kit-step]',
|
|
19
21
|
step: 'flex gap-4 [counter-increment:kit-step]',
|
|
20
22
|
date: 'text-sm font-semibold tabular-nums text-accent-text',
|
|
@@ -22,7 +24,7 @@ const steps = tv({
|
|
|
22
24
|
badge: 'shrink-0',
|
|
23
25
|
media: 'mb-2 overflow-hidden rounded-[var(--radius-image,var(--radius-card))]',
|
|
24
26
|
title: 'text-lg leading-snug [font-weight:var(--font-weight-heading,600)] text-balance',
|
|
25
|
-
text:
|
|
27
|
+
text: `text-pretty ${muted}`,
|
|
26
28
|
},
|
|
27
29
|
variants: {
|
|
28
30
|
layout: {
|
|
@@ -79,18 +81,20 @@ interface Props {
|
|
|
79
81
|
id?: string | undefined
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
const { heading, intro, items, kind = 'steps', layout: givenLayout, marker, connector, currentLabel = 'Current', style, measured, id } = Astro.props
|
|
84
|
+
const { heading, intro, items, kind = 'steps', layout: givenLayout, marker, connector, currentLabel = 'Current', style, measured, id: givenId } = Astro.props
|
|
85
|
+
// Every section has an id, so a theme or a design layer can address this one: the page's, or `steps`, `steps-2`, … in render order.
|
|
86
|
+
const id = instanceId(Astro.locals, givenId, 'steps')
|
|
83
87
|
// A timeline defaults to the rail layout and, without numbers, to the plain dot marker.
|
|
84
88
|
const layout = givenLayout ?? (kind === 'timeline' ? 'rail' : 'list')
|
|
85
89
|
const s = steps({ layout, marker: marker ?? (kind === 'timeline' ? 'dot' : 'number'), connector, style })
|
|
86
90
|
// A bare year or an ISO date is machine-readable; anything else ("Spring 2021") stays text.
|
|
87
91
|
const isoDate = (date: string) => /^\d{4}(-\d{2}(-\d{2})?)?$/.test(date.trim()) ? date.trim() : undefined
|
|
88
|
-
const headingId = heading
|
|
92
|
+
const headingId = heading ? `${id}-title` : undefined
|
|
89
93
|
// One level under the section heading, or the section's heading when there is none.
|
|
90
94
|
const StepHeading = heading ? 'h3' : 'h2'
|
|
91
95
|
---
|
|
92
96
|
{items.length > 0 && (
|
|
93
|
-
<Section measured={measured} id={id} aria-labelledby={headingId}>
|
|
97
|
+
<Section kit="steps" measured={measured} id={id} aria-labelledby={headingId}>
|
|
94
98
|
{(heading || intro) && (
|
|
95
99
|
<header class={s.header()}>
|
|
96
100
|
{heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"KitImage.astro",
|
|
9
9
|
"Section.astro",
|
|
10
10
|
"image.ts",
|
|
11
|
+
"instance-id.ts",
|
|
11
12
|
"measured.ts",
|
|
13
|
+
"muted.ts",
|
|
12
14
|
"safe-url.ts",
|
|
13
15
|
"types.ts"
|
|
14
16
|
],
|
|
@@ -119,7 +121,7 @@
|
|
|
119
121
|
"id": {
|
|
120
122
|
"type": "string",
|
|
121
123
|
"content": false,
|
|
122
|
-
"description": "Anchor id of the section; the heading gets `<id>-title`."
|
|
124
|
+
"description": "Anchor id of the section; the heading gets `<id>-title`. Without one, `steps`, `steps-2`, … in render order, so every section on a page has its own."
|
|
123
125
|
},
|
|
124
126
|
"currentLabel": {
|
|
125
127
|
"type": "string",
|
|
@@ -7,6 +7,8 @@ import { tv } from 'tailwind-variants'
|
|
|
7
7
|
import Section from '../_shared/Section.astro'
|
|
8
8
|
import SectionHeader from '../_shared/SectionHeader.astro'
|
|
9
9
|
import type { ActionInput, MeasuredStyle } from '../_shared/types'
|
|
10
|
+
import { instanceId } from '../_shared/instance-id'
|
|
11
|
+
import { muted } from '../_shared/muted'
|
|
10
12
|
|
|
11
13
|
export interface TabItem {
|
|
12
14
|
label: string
|
|
@@ -19,7 +21,7 @@ const tabs = tv({
|
|
|
19
21
|
heading: 'mb-8 text-2xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-3xl',
|
|
20
22
|
root: 'group/tabs flex flex-col gap-6',
|
|
21
23
|
list: 'hidden gap-1 group-data-[ready]/tabs:flex',
|
|
22
|
-
trigger:
|
|
24
|
+
trigger: `cursor-pointer rounded-md px-4 py-2 text-sm font-medium whitespace-nowrap ${muted} transition hover:text-current focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent data-[selected]:text-current`,
|
|
23
25
|
panel: 'prose-site prose max-w-none text-current',
|
|
24
26
|
title: 'mb-3 text-lg [font-weight:var(--font-weight-heading,600)] group-data-[ready]/tabs:sr-only',
|
|
25
27
|
},
|
|
@@ -32,7 +34,7 @@ const tabs = tv({
|
|
|
32
34
|
horizontal: { list: 'overflow-x-auto' },
|
|
33
35
|
vertical: { root: 'md:data-[ready]:flex-row md:data-[ready]:items-start md:data-[ready]:gap-10', list: 'md:w-56 md:shrink-0 md:flex-col', trigger: 'text-start' },
|
|
34
36
|
},
|
|
35
|
-
tone: { default: {}, muted: {}, inverse: { panel: 'prose-invert' }, accent: { panel: 'prose-invert'
|
|
37
|
+
tone: { default: {}, muted: {}, inverse: { panel: 'prose-invert' }, accent: { panel: 'prose-invert' } },
|
|
36
38
|
},
|
|
37
39
|
compoundVariants: [
|
|
38
40
|
{ style: 'underline', orientation: 'vertical', class: { list: 'md:border-b-0 md:border-s', trigger: 'md:mb-0 md:-ms-px md:border-b-0 md:border-s-2' } },
|
|
@@ -57,7 +59,9 @@ interface Props {
|
|
|
57
59
|
measured?: MeasuredStyle | undefined
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
const { items, heading, intro, eyebrow, actions, label, style, orientation = 'horizontal', tone, id
|
|
62
|
+
const { items, heading, intro, eyebrow, actions, label, style, orientation = 'horizontal', tone, id: givenId, measured } = Astro.props
|
|
63
|
+
// Two on one page never share an id: their labels and controls would point into each other.
|
|
64
|
+
const id = instanceId(Astro.locals, givenId, 'tabs')
|
|
61
65
|
const s = tabs({ style, orientation, tone })
|
|
62
66
|
const headingId = heading ? `${id}-title` : undefined
|
|
63
67
|
// One level under the section heading, or the section's heading when there is none.
|
|
@@ -65,7 +69,7 @@ const PanelHeading = heading ? 'h3' : 'h2'
|
|
|
65
69
|
const value = (i: number) => `tab-${i + 1}`
|
|
66
70
|
---
|
|
67
71
|
{items.length > 0 && (
|
|
68
|
-
<Section measured={measured} tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
|
|
72
|
+
<Section kit="tabs" measured={measured} tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
|
|
69
73
|
{intro || eyebrow || actions?.length
|
|
70
74
|
? <SectionHeader heading={heading} intro={intro} eyebrow={eyebrow} actions={actions} headingId={headingId} />
|
|
71
75
|
: heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"description": "Tabbed panels with the WAI-ARIA tabs pattern from Zag.js (arrow keys, Home/End, roving focus). Without script every panel shows in turn under its own title, so no content is ever hidden from a reader or a crawler.",
|
|
5
5
|
"category": "content",
|
|
6
6
|
"uses": [],
|
|
7
|
-
"shared": ["Button.astro", "Section.astro", "SectionHeader.astro", "measured.ts", "safe-url.ts", "types.ts"],
|
|
7
|
+
"shared": ["Button.astro", "Section.astro", "SectionHeader.astro", "instance-id.ts", "measured.ts", "muted.ts", "safe-url.ts", "types.ts"],
|
|
8
8
|
"variants": {
|
|
9
9
|
"style": { "options": ["underline", "pills"], "default": "underline", "description": "underline: a rule under the list, the selected tab underlined in accent. pills: the selected tab filled with accent." },
|
|
10
10
|
"orientation": { "options": ["horizontal", "vertical"], "default": "horizontal", "description": "vertical: the tab list beside the panel on wide screens (Elementor vertical tabs)." },
|
|
@@ -4,26 +4,28 @@
|
|
|
4
4
|
import { tv } from 'tailwind-variants'
|
|
5
5
|
import KitImage from '../_shared/KitImage.astro'
|
|
6
6
|
import Section from '../_shared/Section.astro'
|
|
7
|
+
import { instanceId } from '../_shared/instance-id'
|
|
7
8
|
import { measuredGrid } from '../_shared/measured'
|
|
8
9
|
import type { MeasuredStyle } from '../_shared/types'
|
|
9
10
|
import { imageOf, type ImageSource } from '../_shared/image'
|
|
10
11
|
import { profile } from './team'
|
|
11
12
|
import { safeHref } from '../_shared/safe-url'
|
|
13
|
+
import { muted } from '../_shared/muted'
|
|
12
14
|
|
|
13
15
|
const team = tv({
|
|
14
16
|
slots: {
|
|
15
17
|
header: 'mb-10 flex max-w-2xl flex-col gap-3',
|
|
16
18
|
heading: 'text-3xl [font-weight:var(--font-weight-heading,700)] tracking-tight text-balance md:text-4xl',
|
|
17
|
-
intro:
|
|
19
|
+
intro: `text-lg ${muted}`,
|
|
18
20
|
list: 'grid gap-6 sm:grid-cols-2 md:gap-8',
|
|
19
21
|
socials: 'relative z-10 mt-1 flex flex-wrap gap-3 text-sm',
|
|
20
22
|
social: 'rounded text-accent-text underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',
|
|
21
|
-
more:
|
|
23
|
+
more: `text-pretty ${muted} [&>summary]:text-sm [&>summary]:font-medium [&>summary]:text-accent-text`,
|
|
22
24
|
member: 'group relative flex flex-col gap-3',
|
|
23
25
|
portrait: 'overflow-hidden rounded-[var(--radius-image,var(--radius-card))]',
|
|
24
26
|
name: 'text-lg leading-snug [font-weight:var(--font-weight-heading,600)]',
|
|
25
27
|
role: 'text-sm font-medium text-accent-text',
|
|
26
|
-
bio:
|
|
28
|
+
bio: `text-pretty ${muted}`,
|
|
27
29
|
},
|
|
28
30
|
variants: {
|
|
29
31
|
columns: {
|
|
@@ -82,17 +84,19 @@ interface Props {
|
|
|
82
84
|
id?: string | undefined
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
const { heading, intro, items, columns, layout, imageShape, align, bioClamp, readMoreLabel = 'Read more', socialLabel = '{name} on {network}', style, roleStyle, measured, id } = Astro.props
|
|
87
|
+
const { heading, intro, items, columns, layout, imageShape, align, bioClamp, readMoreLabel = 'Read more', socialLabel = '{name} on {network}', style, roleStyle, measured, id: givenId } = Astro.props
|
|
88
|
+
// Every section has an id, so a theme or a design layer can address this one: the page's, or `team`, `team-2`, … in render order.
|
|
89
|
+
const id = instanceId(Astro.locals, givenId, 'team')
|
|
86
90
|
const s = team({ columns, layout, imageShape, align, style, roleStyle })
|
|
87
91
|
const clamp = { 2: 'line-clamp-2', 3: 'line-clamp-3', 4: 'line-clamp-4' } as const
|
|
88
92
|
// A bio that fits the clamp needs no disclosure: short means about 160 characters per two lines, a rough, build-time guess.
|
|
89
93
|
const longBio = (bio: string) => bioClamp !== undefined && bio.length > bioClamp * 80
|
|
90
|
-
const headingId = heading
|
|
94
|
+
const headingId = heading ? `${id}-title` : undefined
|
|
91
95
|
// One level under the section heading, or the section's heading when there is none.
|
|
92
96
|
const NameHeading = heading ? 'h3' : 'h2'
|
|
93
97
|
---
|
|
94
98
|
{items.length > 0 && (
|
|
95
|
-
<Section measured={measured} id={id} aria-labelledby={headingId}>
|
|
99
|
+
<Section kit="team" measured={measured} id={id} aria-labelledby={headingId}>
|
|
96
100
|
{(heading || intro) && (
|
|
97
101
|
<header class={s.header()}>
|
|
98
102
|
{heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
"KitImage.astro",
|
|
9
9
|
"Section.astro",
|
|
10
10
|
"image.ts",
|
|
11
|
+
"instance-id.ts",
|
|
11
12
|
"measured.ts",
|
|
12
|
-
"
|
|
13
|
+
"muted.ts",
|
|
13
14
|
"safe-url.ts",
|
|
15
|
+
"social.ts",
|
|
14
16
|
"types.ts"
|
|
15
17
|
],
|
|
16
18
|
"variants": {
|
|
@@ -131,7 +133,7 @@
|
|
|
131
133
|
"id": {
|
|
132
134
|
"type": "string",
|
|
133
135
|
"content": false,
|
|
134
|
-
"description": "Anchor id of the section; the heading gets `<id>-title`."
|
|
136
|
+
"description": "Anchor id of the section; the heading gets `<id>-title`. Without one, `team`, `team-2`, … in render order, so every section on a page has its own."
|
|
135
137
|
},
|
|
136
138
|
"bioClamp": {
|
|
137
139
|
"type": "integer",
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
import { tv } from 'tailwind-variants'
|
|
6
6
|
import KitImage from '../_shared/KitImage.astro'
|
|
7
7
|
import Section from '../_shared/Section.astro'
|
|
8
|
+
import { instanceId } from '../_shared/instance-id'
|
|
8
9
|
import { imageOf, type ImageSource } from '../_shared/image'
|
|
9
10
|
import { measuredGrid } from '../_shared/measured'
|
|
10
11
|
import type { MeasuredStyle } from '../_shared/types'
|
|
12
|
+
import { muted } from '../_shared/muted'
|
|
11
13
|
|
|
12
14
|
export interface TestimonialItem {
|
|
13
15
|
quote: string
|
|
@@ -35,7 +37,7 @@ const testimonial = tv({
|
|
|
35
37
|
avatar: 'size-12 shrink-0 rounded-full object-cover',
|
|
36
38
|
logo: 'ms-auto h-6 w-auto max-w-24 object-contain opacity-80',
|
|
37
39
|
name: 'font-semibold',
|
|
38
|
-
role:
|
|
40
|
+
role: `text-sm ${muted}`,
|
|
39
41
|
},
|
|
40
42
|
variants: {
|
|
41
43
|
layout: {
|
|
@@ -51,7 +53,7 @@ const testimonial = tv({
|
|
|
51
53
|
avatarPlace: { start: {}, top: {} },
|
|
52
54
|
// marks: the quotation marks around the text, as the source writes it. glyph: one large decorative mark above.
|
|
53
55
|
quoteMark: { marks: {}, glyph: {}, none: {} },
|
|
54
|
-
tone: { default: {}, muted: { card: 'bg-surface' }, inverse: { card: 'border-surface/20' }, accent: { card: 'border-accent-ink/30',
|
|
56
|
+
tone: { default: {}, muted: { card: 'bg-surface' }, inverse: { card: 'border-surface/20' }, accent: { card: 'border-accent-ink/30', logo: 'opacity-100' } },
|
|
55
57
|
// The quote's size; `auto` is the layout's own (body size in a grid, large for a single quote).
|
|
56
58
|
quoteSize: {
|
|
57
59
|
auto: {},
|
|
@@ -89,9 +91,11 @@ interface Props {
|
|
|
89
91
|
id?: string | undefined
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
const { items, heading, layout, quoteSize, frame, avatarSize, avatarPlace, quoteMark = 'marks', ratingLabel = '{rating} out of 5 stars', label = 'Testimonials', tone, measured, id } = Astro.props
|
|
94
|
+
const { items, heading, layout, quoteSize, frame, avatarSize, avatarPlace, quoteMark = 'marks', ratingLabel = '{rating} out of 5 stars', label = 'Testimonials', tone, measured, id: givenId } = Astro.props
|
|
95
|
+
// Every section has an id, so a theme or a design layer can address this one: the page's, or `testimonial`, `testimonial-2`, … in render order.
|
|
96
|
+
const id = instanceId(Astro.locals, givenId, 'testimonial')
|
|
93
97
|
const s = testimonial({ layout, quoteSize, frame, avatarSize, avatarPlace, quoteMark, tone })
|
|
94
|
-
const headingId = heading
|
|
98
|
+
const headingId = heading ? `${id}-title` : undefined
|
|
95
99
|
// Two quotes share the row: a third column would stand empty beside them and narrow both (IP-4 elementor launch G9).
|
|
96
100
|
const measuredList = (layout ?? 'grid') === 'grid' ? measuredGrid(measured) : undefined
|
|
97
101
|
const list = s.list({ class: measuredList ?? (items.length === 2 && (layout ?? 'grid') === 'grid' ? 'lg:grid-cols-2' : undefined) })
|
|
@@ -105,7 +109,7 @@ const spoken = (rating: number) => String(Math.round(rating * 10) / 10)
|
|
|
105
109
|
const scrolls = layout === 'row'
|
|
106
110
|
---
|
|
107
111
|
{items.length > 0 && (
|
|
108
|
-
<Section measured={measured} tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
|
|
112
|
+
<Section kit="testimonial" measured={measured} tone={tone} spacing="md" id={id} aria-labelledby={headingId}>
|
|
109
113
|
{heading && <h2 id={headingId} class={s.heading()}>{heading}</h2>}
|
|
110
114
|
<div class={list} tabindex={scrolls ? 0 : undefined} role={scrolls ? 'region' : undefined} aria-labelledby={scrolls && heading ? headingId : undefined} aria-label={scrolls && !heading ? label : undefined}>
|
|
111
115
|
{items.map((item) => {
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"name": "measured-columns",
|
|
101
101
|
"props": {
|
|
102
102
|
"heading": "What readers say",
|
|
103
|
-
"id": "readers",
|
|
103
|
+
"id": "readers-2",
|
|
104
104
|
"items": [
|
|
105
105
|
{ "quote": "The only travel site I read to the end. Every guide feels written by someone who stayed.", "name": "Ayşe Demir", "role": "Reader since 2019", "avatar": { "src": "/kit-fixtures/avatar-1.svg", "alt": "", "width": 96, "height": 96 } },
|
|
106
106
|
{ "quote": "Their Lisbon guide saved our trip.", "name": "Tom Becker", "avatar": { "src": "/kit-fixtures/avatar-2.svg", "alt": "", "width": 96, "height": 96 } },
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"KitImage.astro",
|
|
9
9
|
"Section.astro",
|
|
10
10
|
"image.ts",
|
|
11
|
+
"instance-id.ts",
|
|
11
12
|
"measured.ts",
|
|
13
|
+
"muted.ts",
|
|
12
14
|
"safe-url.ts",
|
|
13
15
|
"types.ts"
|
|
14
16
|
],
|
|
@@ -250,7 +252,7 @@
|
|
|
250
252
|
"id": {
|
|
251
253
|
"type": "string",
|
|
252
254
|
"content": false,
|
|
253
|
-
"description": "Anchor id of the section; the heading gets `<id>-title`."
|
|
255
|
+
"description": "Anchor id of the section; the heading gets `<id>-title`. Without one, `testimonial`, `testimonial-2`, … in render order, so every section on a page has its own."
|
|
254
256
|
},
|
|
255
257
|
"ratingLabel": {
|
|
256
258
|
"type": "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentrain/astro-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.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",
|