@publier/shell 2.1.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.
Files changed (141) hide show
  1. package/README.md +1 -0
  2. package/dist/build-integration.d.mts +7 -0
  3. package/dist/build-integration.mjs +1 -0
  4. package/dist/index-DWtFsw7E.d.mts +1033 -0
  5. package/dist/index.d.mts +33 -0
  6. package/dist/index.mjs +1 -0
  7. package/dist/integration-BIJIcnAT.mjs +99 -0
  8. package/dist/integration-CKjtSkwa.d.mts +183 -0
  9. package/dist/integration.d.mts +2 -0
  10. package/dist/integration.mjs +1 -0
  11. package/dist/loaders/index.d.mts +54 -0
  12. package/dist/loaders/index.mjs +1 -0
  13. package/dist/plugins/remark-asides.d.mts +8 -0
  14. package/dist/plugins/remark-asides.mjs +2 -0
  15. package/dist/plugins/remark-snippets.d.mts +11 -0
  16. package/dist/plugins/remark-snippets.mjs +1 -0
  17. package/dist/plugins/remark-structure.d.mts +13 -0
  18. package/dist/plugins/remark-structure.mjs +1 -0
  19. package/dist/plugins/remark-vars.d.mts +7 -0
  20. package/dist/plugins/remark-vars.mjs +1 -0
  21. package/dist/presets-C7z73xlB.d.mts +16 -0
  22. package/dist/presets-DL0qjtya.mjs +1 -0
  23. package/dist/runtime/code-group-sync.d.mts +30 -0
  24. package/dist/runtime/code-group-sync.mjs +103 -0
  25. package/dist/runtime/lazy-upgrade-registry.d.mts +29 -0
  26. package/dist/runtime/lazy-upgrade-registry.mjs +1 -0
  27. package/dist/runtime/tabs-sync.d.mts +25 -0
  28. package/dist/runtime/tabs-sync.mjs +106 -0
  29. package/dist/search/index.d.mts +92 -0
  30. package/dist/search/index.mjs +1 -0
  31. package/dist/tailwind/css-plugin.d.mts +16 -0
  32. package/dist/tailwind/css-plugin.mjs +1 -0
  33. package/dist/tailwind/index.d.mts +6 -0
  34. package/dist/tailwind/index.mjs +1 -0
  35. package/dist/tailwind/loader.d.mts +94 -0
  36. package/dist/tailwind/loader.mjs +2 -0
  37. package/dist/theme-toggle-element-DzFjxwpS.mjs +1 -0
  38. package/dist/themes/almond.css +115 -0
  39. package/dist/themes/aspen.css +95 -0
  40. package/dist/themes/catppuccin.css +98 -0
  41. package/dist/themes/dark.css +98 -0
  42. package/dist/themes/dusk.css +98 -0
  43. package/dist/themes/emerald.css +95 -0
  44. package/dist/themes/light.css +95 -0
  45. package/dist/themes/maple.css +119 -0
  46. package/dist/themes/neutral.css +73 -0
  47. package/dist/themes/ocean.css +98 -0
  48. package/dist/themes/purple.css +95 -0
  49. package/dist/themes/ruby.css +95 -0
  50. package/dist/themes/solar.css +98 -0
  51. package/dist/themes/vitepress.css +95 -0
  52. package/package.json +189 -0
  53. package/publier-gate +0 -0
  54. package/src/astro-modules.d.ts +20 -0
  55. package/src/components/LastModified.astro +25 -0
  56. package/src/components/announcement-banner.astro +25 -0
  57. package/src/components/aside.astro +17 -0
  58. package/src/components/ask-ai.tsx +146 -0
  59. package/src/components/badge.astro +18 -0
  60. package/src/components/breadcrumbs.astro +23 -0
  61. package/src/components/callouts/caution.astro +13 -0
  62. package/src/components/callouts/check.astro +13 -0
  63. package/src/components/callouts/danger.astro +13 -0
  64. package/src/components/callouts/info.astro +13 -0
  65. package/src/components/callouts/note.astro +13 -0
  66. package/src/components/callouts/tip.astro +13 -0
  67. package/src/components/callouts/warning.astro +13 -0
  68. package/src/components/card-grid.astro +14 -0
  69. package/src/components/card.astro +18 -0
  70. package/src/components/code-group.astro +55 -0
  71. package/src/components/columns.astro +18 -0
  72. package/src/components/docs-layout.astro +25 -0
  73. package/src/components/file-tree-node.astro +13 -0
  74. package/src/components/file-tree.astro +9 -0
  75. package/src/components/icon.astro +18 -0
  76. package/src/components/index.ts +155 -0
  77. package/src/components/link-button.astro +21 -0
  78. package/src/components/link-card.astro +21 -0
  79. package/src/components/open-in-ai.astro +13 -0
  80. package/src/components/package-install.astro +17 -0
  81. package/src/components/panels.astro +16 -0
  82. package/src/components/search-button.astro +21 -0
  83. package/src/components/sidebar.astro +12 -0
  84. package/src/components/skip-link.astro +12 -0
  85. package/src/components/steps.astro +13 -0
  86. package/src/components/table-of-contents.astro +22 -0
  87. package/src/components/tabs.astro +17 -0
  88. package/src/components/theme-storage.ts +5 -0
  89. package/src/components/theme-toggle-element.ts +85 -0
  90. package/src/components/theme-toggle.astro +25 -0
  91. package/src/components/tile-grid.astro +13 -0
  92. package/src/components/tile.astro +17 -0
  93. package/src/components/top-nav-mobile.astro +11 -0
  94. package/src/components/top-nav.astro +20 -0
  95. package/src/components/types.ts +510 -0
  96. package/src/components/ui/blur-image.astro +60 -0
  97. package/src/components/ui/changelog-entry.astro +56 -0
  98. package/src/components/ui/cta-band.astro +30 -0
  99. package/src/components/ui/feature-grid.astro +38 -0
  100. package/src/components/ui/feature-section.astro +85 -0
  101. package/src/components/ui/frame.astro +52 -0
  102. package/src/components/ui/hero.astro +47 -0
  103. package/src/components/ui/jobs-list.astro +53 -0
  104. package/src/components/ui/logo-cloud.astro +68 -0
  105. package/src/components/ui/press-gallery.astro +52 -0
  106. package/src/components/ui/pricing-comparison-table.astro +73 -0
  107. package/src/components/ui/pricing-section.astro +113 -0
  108. package/src/components/ui/pricing-table.astro +54 -0
  109. package/src/components/ui/status-indicator.astro +38 -0
  110. package/src/components/ui/team-grid.astro +63 -0
  111. package/src/components/ui/testimonial-card.astro +42 -0
  112. package/src/components/ui/types.ts +323 -0
  113. package/src/components/update-badge.astro +15 -0
  114. package/src/components/version-switcher.astro +20 -0
  115. package/src/icons/index.tsx +246 -0
  116. package/src/icons/resolve.tsx +45 -0
  117. package/src/layouts/base-layout.astro +63 -0
  118. package/src/qwik.ts +3 -0
  119. package/src/routes/blog-index.astro +20 -0
  120. package/src/routes/blog-rss.xml.ts +40 -0
  121. package/src/routes/blog-slug.astro +32 -0
  122. package/src/routes/changelog-index.astro +25 -0
  123. package/src/routes/changelog-rss.xml.ts +47 -0
  124. package/src/routes/docs-slug.astro +31 -0
  125. package/src/routes/not-found.astro +14 -0
  126. package/src/runtime/banner-init.ts +9 -0
  127. package/src/runtime/lazy-upgrade-init.ts +5 -0
  128. package/src/runtime/sidebar-scroll-init.ts +3 -0
  129. package/src/runtime/theme-init.ts +16 -0
  130. package/src/schemas/blog.ts +37 -0
  131. package/src/schemas/changelog.ts +28 -0
  132. package/src/schemas/common.ts +82 -0
  133. package/src/schemas/docs.ts +101 -0
  134. package/src/schemas/index.ts +14 -0
  135. package/src/schemas/pages.ts +22 -0
  136. package/src/styles/base.css +627 -0
  137. package/src/styles/expressive-code.css +41 -0
  138. package/src/styles/rules.css +66 -0
  139. package/src/styles/tailwind-sources.css +17 -0
  140. package/src/tailwind/preset.css +193 -0
  141. package/src/virtual-modules.d.ts +164 -0
@@ -0,0 +1,85 @@
1
+ ---
2
+ interface Props {
3
+ eyebrow?: string;
4
+ title: string;
5
+ subtitle?: string;
6
+ bullets?: string[];
7
+ primaryCta?: { label: string; href: string };
8
+ reverse?: boolean;
9
+ class?: string;
10
+ }
11
+
12
+ const {
13
+ eyebrow,
14
+ title,
15
+ subtitle,
16
+ bullets,
17
+ primaryCta,
18
+ reverse = false,
19
+ class: cls,
20
+ } = Astro.props;
21
+
22
+ const items = Array.isArray(bullets) ? bullets : [];
23
+ const hasBullets = items.length > 0;
24
+ const visualOrder = reverse ? 'md:order-first' : '';
25
+ ---
26
+
27
+ <section class={`py-14 md:py-20 ${cls ?? ''}`}>
28
+ <div class="max-w-6xl mx-auto px-6 grid gap-10 md:grid-cols-2 md:items-center">
29
+ <div>
30
+ {eyebrow && (
31
+ <span class="badge badge-outline badge-primary">{eyebrow}</span>
32
+ )}
33
+ <h2 class="text-3xl md:text-4xl font-bold text-base-content">{title}</h2>
34
+ {subtitle && <p class="mt-3 text-lg text-base-content/70">{subtitle}</p>}
35
+ {hasBullets && (
36
+ <ul class="mt-6 space-y-2">
37
+ {items.map((bullet) => (
38
+ <li class="flex items-start gap-2 text-base-content">
39
+ <svg
40
+ class="text-primary shrink-0 mt-1"
41
+ width="16"
42
+ height="16"
43
+ viewBox="0 0 16 16"
44
+ fill="none"
45
+ stroke="currentColor"
46
+ stroke-width="2"
47
+ aria-hidden="true"
48
+ >
49
+ <path d="M3 8.5l3 3 7-7" />
50
+ </svg>
51
+ <span>{bullet}</span>
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ )}
56
+ {primaryCta && (
57
+ <a class="link link-primary inline-flex items-center gap-2 group mt-6" href={primaryCta.href}>
58
+ <span>{primaryCta.label}</span>
59
+ <svg
60
+ class="transition-transform group-hover:translate-x-1"
61
+ width="16"
62
+ height="16"
63
+ viewBox="0 0 16 16"
64
+ fill="none"
65
+ stroke="currentColor"
66
+ stroke-width="2"
67
+ aria-hidden="true"
68
+ >
69
+ <path d="M3 8h10m-4-4 4 4-4 4" />
70
+ </svg>
71
+ </a>
72
+ )}
73
+ </div>
74
+ <div
75
+ class:list={[
76
+ 'card bg-base-200 border border-base-300',
77
+ visualOrder,
78
+ ]}
79
+ >
80
+ <div class="card-body flex items-center justify-center">
81
+ <slot />
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </section>
@@ -0,0 +1,52 @@
1
+ ---
2
+ interface Props {
3
+ caption?: string;
4
+ hint?: string;
5
+ video?: boolean;
6
+ src?: string;
7
+ autoPlay?: boolean;
8
+ loop?: boolean;
9
+ muted?: boolean;
10
+ poster?: string;
11
+ }
12
+
13
+ const {
14
+ caption,
15
+ hint,
16
+ video = false,
17
+ src,
18
+ autoPlay = false,
19
+ loop = false,
20
+ muted,
21
+ poster,
22
+ } = Astro.props;
23
+
24
+ const resolvedMuted = muted !== undefined ? muted : Boolean(autoPlay);
25
+ const renderVideo = video && src;
26
+ const hasCaption = Boolean(caption) || Boolean(hint);
27
+ ---
28
+
29
+ <figure class="mockup-window bg-base-200 border border-base-300 my-6">
30
+ <div class="bg-base-100 p-0 overflow-hidden rounded-b-box">
31
+ {renderVideo ? (
32
+ <video
33
+ class="w-full h-auto block"
34
+ src={src}
35
+ autoplay={autoPlay}
36
+ loop={loop}
37
+ muted={resolvedMuted}
38
+ poster={poster}
39
+ controls={!autoPlay}
40
+ playsinline
41
+ />
42
+ ) : (
43
+ <slot />
44
+ )}
45
+ </div>
46
+ {hasCaption && (
47
+ <figcaption class="flex flex-wrap items-baseline gap-2 px-4 py-2 text-sm text-base-content/70 border-t border-base-300">
48
+ {caption && <span>{caption}</span>}
49
+ {hint && <span class="text-xs text-base-content/50 italic">{hint}</span>}
50
+ </figcaption>
51
+ )}
52
+ </figure>
@@ -0,0 +1,47 @@
1
+ ---
2
+ export type HeroAlign = 'center' | 'left';
3
+
4
+ interface Props {
5
+ eyebrow?: string;
6
+ title: string;
7
+ subtitle?: string;
8
+ primaryCta?: { label: string; href: string };
9
+ secondaryCta?: { label: string; href: string };
10
+ align?: HeroAlign;
11
+ }
12
+
13
+ const { eyebrow, title, subtitle, primaryCta, secondaryCta, align = 'center' } = Astro.props;
14
+
15
+ const alignCls = align === 'center' ? 'text-center items-center' : 'text-left items-start';
16
+ const ctasCls = align === 'center' ? 'justify-center' : 'justify-start';
17
+ ---
18
+
19
+ <section class="hero py-16 md:py-24">
20
+ <div class={`hero-content max-w-4xl mx-auto flex-col px-6 ${alignCls}`}>
21
+ {eyebrow && (
22
+ <div class="badge badge-outline badge-primary mb-3">{eyebrow}</div>
23
+ )}
24
+ <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-base-content">{title}</h1>
25
+ {subtitle && (
26
+ <p class="mt-4 text-lg md:text-xl text-base-content/70 max-w-2xl">{subtitle}</p>
27
+ )}
28
+ {(primaryCta || secondaryCta) && (
29
+ <div class={`mt-8 flex flex-wrap gap-3 ${ctasCls}`}>
30
+ {primaryCta && (
31
+ <a class="btn btn-primary" href={primaryCta.href}>
32
+ {primaryCta.label}
33
+ </a>
34
+ )}
35
+ {secondaryCta && (
36
+ <a
37
+ class="btn btn-outline"
38
+ href={secondaryCta.href}
39
+ >
40
+ {secondaryCta.label}
41
+ </a>
42
+ )}
43
+ </div>
44
+ )}
45
+ <slot />
46
+ </div>
47
+ </section>
@@ -0,0 +1,53 @@
1
+ ---
2
+ interface Item {
3
+ title: string;
4
+ department: string;
5
+ location: string;
6
+ type?: string;
7
+ href: string;
8
+ }
9
+
10
+ interface Props {
11
+ jobs: Item[];
12
+ heading?: string;
13
+ emptyMessage?: string;
14
+ }
15
+
16
+ const { jobs, heading, emptyMessage = "We're not hiring right now." } = Astro.props;
17
+
18
+ const groups: Record<string, Item[]> = {};
19
+ const departmentOrder: string[] = [];
20
+ for (const job of jobs) {
21
+ if (!groups[job.department]) {
22
+ groups[job.department] = [];
23
+ departmentOrder.push(job.department);
24
+ }
25
+ groups[job.department]?.push(job);
26
+ }
27
+ const isEmpty = jobs.length === 0;
28
+ ---
29
+
30
+ <section>
31
+ {heading && <h2 class="text-2xl md:text-3xl font-bold text-base-content mb-6">{heading}</h2>}
32
+ {isEmpty ? (
33
+ <p class="text-base-content/60 italic">{emptyMessage}</p>
34
+ ) : (
35
+ departmentOrder.map((dept) => (
36
+ <div class="mb-8">
37
+ <h3 class="text-xs uppercase tracking-wider text-base-content/60 mb-2">{dept}</h3>
38
+ <ul class="menu menu-compact bg-base-100 border border-base-300 rounded-box">
39
+ {groups[dept]?.map((job) => (
40
+ <li>
41
+ <a href={job.href} class="justify-between">
42
+ <span class="font-semibold">{job.title}</span>
43
+ <span class="text-xs text-base-content/60">
44
+ {job.location}{job.type ? ` · ${job.type}` : ''}
45
+ </span>
46
+ </a>
47
+ </li>
48
+ ))}
49
+ </ul>
50
+ </div>
51
+ ))
52
+ )}
53
+ </section>
@@ -0,0 +1,68 @@
1
+ ---
2
+ interface Item {
3
+ name: string;
4
+ src: string;
5
+ alt?: string;
6
+ href?: string;
7
+ width?: number;
8
+ height?: number;
9
+ }
10
+
11
+ interface Props {
12
+ items: Item[];
13
+ title?: string;
14
+ subtitle?: string;
15
+ columns?: 3 | 4 | 5 | 6;
16
+ }
17
+
18
+ const { items, title, subtitle, columns = 5 } = Astro.props;
19
+ const hasHeader = Boolean(title) || Boolean(subtitle);
20
+
21
+ const colCls = columns === 3
22
+ ? 'grid-cols-2 md:grid-cols-3'
23
+ : columns === 4
24
+ ? 'grid-cols-2 md:grid-cols-4'
25
+ : columns === 6
26
+ ? 'grid-cols-2 md:grid-cols-3 lg:grid-cols-6'
27
+ : 'grid-cols-2 md:grid-cols-3 lg:grid-cols-5';
28
+ ---
29
+
30
+ <section class="py-12">
31
+ {hasHeader && (
32
+ <header class="max-w-2xl mx-auto text-center mb-8 px-6">
33
+ {title && <h2 class="text-2xl md:text-3xl font-bold text-base-content">{title}</h2>}
34
+ {subtitle && <p class="mt-2 text-base-content/70">{subtitle}</p>}
35
+ </header>
36
+ )}
37
+ <ul class={`list-none grid gap-8 items-center px-6 max-w-6xl mx-auto ${colCls}`}>
38
+ {items.map((item) => {
39
+ const img = (
40
+ <img
41
+ class="max-h-10 w-auto mx-auto opacity-70 grayscale hover:opacity-100 hover:grayscale-0 transition"
42
+ src={item.src}
43
+ alt={item.alt ?? item.name}
44
+ width={item.width ?? 144}
45
+ height={item.height ?? 40}
46
+ loading="lazy"
47
+ decoding="async"
48
+ />
49
+ );
50
+ return (
51
+ <li class="flex items-center justify-center">
52
+ {item.href ? (
53
+ <a
54
+ class="block"
55
+ href={item.href}
56
+ aria-label={item.name}
57
+ rel={item.href.startsWith('http') ? 'noopener' : undefined}
58
+ >
59
+ {img}
60
+ </a>
61
+ ) : (
62
+ img
63
+ )}
64
+ </li>
65
+ );
66
+ })}
67
+ </ul>
68
+ </section>
@@ -0,0 +1,52 @@
1
+ ---
2
+ interface Item {
3
+ outlet: string;
4
+ logoUrl?: string;
5
+ quote?: string;
6
+ href: string;
7
+ date?: string;
8
+ }
9
+
10
+ interface Props {
11
+ items: Item[];
12
+ }
13
+
14
+ const { items } = Astro.props;
15
+ ---
16
+
17
+ <ul class="list-none grid gap-4 md:grid-cols-2 lg:grid-cols-3">
18
+ {items.map((item) => (
19
+ <li>
20
+ <a
21
+ class="card bg-base-100 border border-base-300 hover:shadow-md transition-shadow h-full"
22
+ href={item.href}
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ >
26
+ <div class="card-body">
27
+ {item.logoUrl ? (
28
+ <img
29
+ class="max-h-8 w-auto"
30
+ src={item.logoUrl}
31
+ alt={`${item.outlet} logo`}
32
+ width="144"
33
+ height="32"
34
+ loading="lazy"
35
+ />
36
+ ) : (
37
+ <span class="font-semibold text-base-content">{item.outlet}</span>
38
+ )}
39
+ {item.quote && (
40
+ <blockquote class="italic text-base-content/80 mt-2">&ldquo;{item.quote}&rdquo;</blockquote>
41
+ )}
42
+ <footer class="mt-3 flex items-center justify-between text-sm text-base-content/60">
43
+ <span>{item.outlet}</span>
44
+ {item.date && (
45
+ <time datetime={item.date}>{item.date}</time>
46
+ )}
47
+ </footer>
48
+ </div>
49
+ </a>
50
+ </li>
51
+ ))}
52
+ </ul>
@@ -0,0 +1,73 @@
1
+ ---
2
+ type CellValue = boolean | string;
3
+
4
+ interface Row {
5
+ feature: string;
6
+ values: CellValue[];
7
+ }
8
+
9
+ interface Category {
10
+ title: string;
11
+ rows: Row[];
12
+ }
13
+
14
+ interface Props {
15
+ tiers: string[];
16
+ categories: Category[];
17
+ }
18
+
19
+ const { tiers, categories } = Astro.props;
20
+ ---
21
+
22
+ <div class="max-w-6xl mx-auto px-6 space-y-12 overflow-x-auto">
23
+ {categories.map((category) => (
24
+ <table class="table table-zebra w-full min-w-[32rem]">
25
+ <caption class="text-left text-xs uppercase tracking-wider font-semibold text-primary/70 mb-1 caption-top">{category.title}</caption>
26
+ <thead>
27
+ <tr>
28
+ <th scope="col" class="w-1/3">
29
+ <span class="sr-only">Feature</span>
30
+ </th>
31
+ {tiers.map((tier) => (
32
+ <th scope="col" class="text-center font-semibold">{tier}</th>
33
+ ))}
34
+ </tr>
35
+ </thead>
36
+ <tbody>
37
+ {category.rows.map((row) => (
38
+ <tr>
39
+ <th scope="row" class="font-normal text-base-content">{row.feature}</th>
40
+ {row.values.map((value) => (
41
+ <td class="text-center">
42
+ {value === true ? (
43
+ <>
44
+ <svg
45
+ class="inline text-primary"
46
+ width="16"
47
+ height="16"
48
+ viewBox="0 0 16 16"
49
+ fill="none"
50
+ stroke="currentColor"
51
+ stroke-width="2"
52
+ aria-hidden="true"
53
+ >
54
+ <path d="M3 8.5l3 3 7-7" />
55
+ </svg>
56
+ <span class="sr-only">Included</span>
57
+ </>
58
+ ) : value === false ? (
59
+ <>
60
+ <span aria-hidden="true" class="text-base-content/40">—</span>
61
+ <span class="sr-only">Not included</span>
62
+ </>
63
+ ) : (
64
+ <span class="text-sm text-base-content/80">{value}</span>
65
+ )}
66
+ </td>
67
+ ))}
68
+ </tr>
69
+ ))}
70
+ </tbody>
71
+ </table>
72
+ ))}
73
+ </div>
@@ -0,0 +1,113 @@
1
+ ---
2
+ import PricingTable from './pricing-table.astro';
3
+ import type { PricingSectionProps } from './types.ts';
4
+
5
+ const {
6
+ title,
7
+ subtitle,
8
+ monthlyTiers,
9
+ yearlyTiers,
10
+ defaultPeriod = 'monthly',
11
+ yearlyDiscountLabel,
12
+ } = Astro.props as PricingSectionProps;
13
+
14
+ const hasToggle = Array.isArray(yearlyTiers) && yearlyTiers.length > 0;
15
+
16
+ const groupId = Math.random().toString(36).slice(2, 9);
17
+ const monthlyId = `publier-pricing-${groupId}-monthly`;
18
+ const yearlyId = `publier-pricing-${groupId}-yearly`;
19
+ ---
20
+
21
+ <section class="pt-4 pb-14 md:pb-20">
22
+ <div class="max-w-6xl mx-auto px-6">
23
+ {(title || subtitle) && (
24
+ <header class="max-w-2xl mx-auto text-center mb-8">
25
+ {title && <h2 class="text-3xl md:text-4xl font-bold text-base-content">{title}</h2>}
26
+ {subtitle && <p class="mt-3 text-lg text-base-content/70">{subtitle}</p>}
27
+ </header>
28
+ )}
29
+
30
+ {hasToggle ? (
31
+ <div class="publier-pricing-toggle" data-default={defaultPeriod}>
32
+ <input
33
+ type="radio"
34
+ name={`publier-pricing-${groupId}`}
35
+ id={monthlyId}
36
+ class="publier-pricing-radio"
37
+ value="monthly"
38
+ checked={defaultPeriod === 'monthly'}
39
+ />
40
+ <input
41
+ type="radio"
42
+ name={`publier-pricing-${groupId}`}
43
+ id={yearlyId}
44
+ class="publier-pricing-radio"
45
+ value="yearly"
46
+ checked={defaultPeriod === 'yearly'}
47
+ />
48
+
49
+ <div class="flex justify-center mb-8" role="radiogroup" aria-label="Billing period">
50
+ <div class="tabs tabs-box bg-base-200 rounded-full p-1">
51
+ <label class="tab rounded-full tab-monthly" for={monthlyId}>Monthly</label>
52
+ <label class="tab rounded-full tab-yearly" for={yearlyId}>
53
+ Yearly
54
+ {yearlyDiscountLabel && (
55
+ <span class="badge badge-success badge-sm ml-2">{yearlyDiscountLabel}</span>
56
+ )}
57
+ </label>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="publier-pricing-panel" data-period="monthly">
62
+ <PricingTable tiers={monthlyTiers} />
63
+ </div>
64
+ <div class="publier-pricing-panel" data-period="yearly">
65
+ <PricingTable tiers={yearlyTiers!} />
66
+ </div>
67
+ </div>
68
+ ) : (
69
+ <PricingTable tiers={monthlyTiers} />
70
+ )}
71
+ </div>
72
+ </section>
73
+
74
+ <style>
75
+ /* Hide the raw radio inputs; labels above drive the state. */
76
+ .publier-pricing-toggle > .publier-pricing-radio {
77
+ position: absolute;
78
+ width: 1px;
79
+ height: 1px;
80
+ padding: 0;
81
+ margin: -1px;
82
+ overflow: hidden;
83
+ clip: rect(0, 0, 0, 0);
84
+ white-space: nowrap;
85
+ border: 0;
86
+ }
87
+
88
+ /* Panel visibility — driven by which radio is checked. */
89
+ .publier-pricing-toggle .publier-pricing-panel { display: none; }
90
+
91
+ /* biome-ignore-start lint: CSS adjacency + attribute selectors. */
92
+ .publier-pricing-toggle > input[value="monthly"]:checked
93
+ ~ .publier-pricing-panel[data-period="monthly"],
94
+ .publier-pricing-toggle > input[value="yearly"]:checked
95
+ ~ .publier-pricing-panel[data-period="yearly"] {
96
+ display: block;
97
+ }
98
+
99
+ /* Active-tab styling — tie `.tab-active` to the matching radio. */
100
+ .publier-pricing-toggle > input[value="monthly"]:checked ~ * .tab-monthly,
101
+ .publier-pricing-toggle > input[value="yearly"]:checked ~ * .tab-yearly {
102
+ /* Applies daisyUI's active-tab treatment in v5 (bg-base-100). */
103
+ background-color: var(--color-base-100);
104
+ color: var(--color-base-content);
105
+ font-weight: 600;
106
+ }
107
+
108
+ /* Keyboard focus ring for the label (covers no-pointer focus). */
109
+ .publier-pricing-toggle > input:focus-visible ~ * .tab {
110
+ outline-offset: 2px;
111
+ }
112
+ /* biome-ignore-end lint */
113
+ </style>
@@ -0,0 +1,54 @@
1
+ ---
2
+ import type { PricingTableProps } from './types.ts';
3
+
4
+ const { tiers } = Astro.props as PricingTableProps;
5
+ ---
6
+
7
+ <ul class="list-none grid gap-6 md:grid-cols-2 lg:grid-cols-3">
8
+ {tiers.map((tier) => {
9
+ const cardCls = tier.featured
10
+ ? 'card bg-base-100 border-2 border-primary shadow-lg relative'
11
+ : 'card bg-base-100 border border-base-300';
12
+ const ctaCls = tier.featured ? 'btn btn-primary btn-block' : 'btn btn-outline btn-block';
13
+ return (
14
+ <li class={cardCls}>
15
+ {tier.featured && (
16
+ <span class="badge badge-success absolute -top-3 left-1/2 -translate-x-1/2">
17
+ Most popular
18
+ </span>
19
+ )}
20
+ <div class="card-body">
21
+ <h3 class="text-sm uppercase tracking-wider font-medium text-base-content/70">{tier.name}</h3>
22
+ <div class="text-3xl font-bold text-base-content">{tier.price}</div>
23
+ {tier.tagline && <p class="text-sm text-base-content/60">{tier.tagline}</p>}
24
+ <ul class="list-none space-y-2 mt-3 mb-6">
25
+ {tier.features.map((feat) => (
26
+ <li class="flex items-start gap-2 text-sm text-base-content">
27
+ <svg
28
+ class="text-primary shrink-0 mt-1"
29
+ width="16"
30
+ height="16"
31
+ viewBox="0 0 16 16"
32
+ fill="none"
33
+ stroke="currentColor"
34
+ stroke-width="2"
35
+ aria-hidden="true"
36
+ >
37
+ <path d="M3 8.5l3 3 7-7" />
38
+ </svg>
39
+ <span>{feat}</span>
40
+ </li>
41
+ ))}
42
+ </ul>
43
+ <a
44
+ class={ctaCls}
45
+ href={tier.cta.href}
46
+ aria-label={tier.featured ? `${tier.cta.label} — ${tier.name} (recommended)` : tier.cta.label}
47
+ >
48
+ {tier.cta.label}
49
+ </a>
50
+ </div>
51
+ </li>
52
+ );
53
+ })}
54
+ </ul>
@@ -0,0 +1,38 @@
1
+ ---
2
+ type StatusLevel = 'operational' | 'degraded' | 'outage' | 'maintenance';
3
+
4
+ interface Props {
5
+ status: StatusLevel;
6
+ label?: string;
7
+ service?: string;
8
+ }
9
+
10
+ const DEFAULT_LABELS: Record<StatusLevel, string> = {
11
+ operational: 'Operational',
12
+ degraded: 'Degraded performance',
13
+ outage: 'Major outage',
14
+ maintenance: 'Under maintenance',
15
+ };
16
+
17
+ const STATUS_COLOR: Record<StatusLevel, string> = {
18
+ operational: 'bg-success',
19
+ degraded: 'bg-warning',
20
+ outage: 'bg-error',
21
+ maintenance: 'bg-info',
22
+ };
23
+
24
+ const { status, label, service } = Astro.props;
25
+
26
+ const text = label ?? DEFAULT_LABELS[status];
27
+ const ariaLabel = service ? `${service} ${text.toLowerCase()}` : text;
28
+ ---
29
+
30
+ <span
31
+ class="inline-flex items-center gap-2 text-sm text-base-content"
32
+ role="status"
33
+ aria-label={ariaLabel}
34
+ >
35
+ <span class={`inline-block w-2 h-2 rounded-full ${STATUS_COLOR[status]}`} aria-hidden="true"></span>
36
+ {service && <span class="font-semibold">{service}</span>}
37
+ <span>{text}</span>
38
+ </span>