@stampui/blocks 1.0.0 → 1.1.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/dist/manifests.js +267 -0
- package/package.json +1 -1
- package/src/components/blocks/announcement-bar.tsx +103 -0
- package/src/components/blocks/changelog-feed.tsx +115 -0
- package/src/components/blocks/cookie-consent.tsx +73 -0
- package/src/components/blocks/cta-banner.tsx +77 -0
- package/src/components/blocks/dashboard-shell.tsx +135 -0
- package/src/components/blocks/empty-state.tsx +64 -0
- package/src/components/blocks/error-page.tsx +69 -0
- package/src/components/blocks/faq-accordion.tsx +94 -0
- package/src/components/blocks/feature-comparison.tsx +110 -0
- package/src/components/blocks/metrics-grid.tsx +138 -0
- package/src/components/blocks/site-footer.tsx +196 -0
- package/src/components/blocks/social-proof-bar.tsx +83 -0
- package/src/components/blocks/team-grid.tsx +167 -0
- package/src/components/blocks/testimonials-wall.tsx +110 -0
- package/src/components/blocks/waitlist-section.tsx +102 -0
- package/src/manifests.ts +283 -0
package/dist/manifests.js
CHANGED
|
@@ -1662,5 +1662,272 @@ exports.manifests = {
|
|
|
1662
1662
|
supportsLightMode: true,
|
|
1663
1663
|
promptReady: true,
|
|
1664
1664
|
},
|
|
1665
|
+
// ─── New Blocks ───────────────────────────────────────────────────────────
|
|
1666
|
+
"waitlist-section": {
|
|
1667
|
+
slug: "waitlist-section",
|
|
1668
|
+
title: "Waitlist Section",
|
|
1669
|
+
description: "Email signup section with a waitlist counter and thank-you state.",
|
|
1670
|
+
category: "Marketing",
|
|
1671
|
+
tags: ["waitlist", "email", "signup", "landing"],
|
|
1672
|
+
version: "1.0.0",
|
|
1673
|
+
updatedAt: "2026-05-17",
|
|
1674
|
+
changelog: ["Initial release"],
|
|
1675
|
+
status: "free",
|
|
1676
|
+
difficulty: "beginner",
|
|
1677
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1678
|
+
dependencies: [],
|
|
1679
|
+
files: [{ path: "components/blocks/waitlist-section.tsx", type: "block" }],
|
|
1680
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1681
|
+
supportsDarkMode: true,
|
|
1682
|
+
supportsLightMode: false,
|
|
1683
|
+
promptReady: true,
|
|
1684
|
+
},
|
|
1685
|
+
"changelog-feed": {
|
|
1686
|
+
slug: "changelog-feed",
|
|
1687
|
+
title: "Changelog Feed",
|
|
1688
|
+
description: "Vertical timeline of versioned product updates with type tags.",
|
|
1689
|
+
category: "Display",
|
|
1690
|
+
tags: ["changelog", "timeline", "updates", "versioning"],
|
|
1691
|
+
version: "1.0.0",
|
|
1692
|
+
updatedAt: "2026-05-17",
|
|
1693
|
+
changelog: ["Initial release"],
|
|
1694
|
+
status: "free",
|
|
1695
|
+
difficulty: "beginner",
|
|
1696
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1697
|
+
dependencies: [],
|
|
1698
|
+
files: [{ path: "components/blocks/changelog-feed.tsx", type: "block" }],
|
|
1699
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1700
|
+
supportsDarkMode: true,
|
|
1701
|
+
supportsLightMode: false,
|
|
1702
|
+
promptReady: true,
|
|
1703
|
+
},
|
|
1704
|
+
"faq-accordion": {
|
|
1705
|
+
slug: "faq-accordion",
|
|
1706
|
+
title: "FAQ Accordion",
|
|
1707
|
+
description: "Expandable FAQ section built without external accordion dependencies.",
|
|
1708
|
+
category: "Marketing",
|
|
1709
|
+
tags: ["faq", "accordion", "help", "landing"],
|
|
1710
|
+
version: "1.0.0",
|
|
1711
|
+
updatedAt: "2026-05-17",
|
|
1712
|
+
changelog: ["Initial release"],
|
|
1713
|
+
status: "free",
|
|
1714
|
+
difficulty: "beginner",
|
|
1715
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1716
|
+
dependencies: ["lucide-react"],
|
|
1717
|
+
files: [{ path: "components/blocks/faq-accordion.tsx", type: "block" }],
|
|
1718
|
+
tokens: ["foreground", "muted-foreground", "border"],
|
|
1719
|
+
supportsDarkMode: true,
|
|
1720
|
+
supportsLightMode: false,
|
|
1721
|
+
promptReady: true,
|
|
1722
|
+
},
|
|
1723
|
+
"cta-banner": {
|
|
1724
|
+
slug: "cta-banner",
|
|
1725
|
+
title: "CTA Banner",
|
|
1726
|
+
description: "End-of-page call-to-action section with primary and secondary buttons.",
|
|
1727
|
+
category: "Marketing",
|
|
1728
|
+
tags: ["cta", "call-to-action", "landing", "conversion"],
|
|
1729
|
+
version: "1.0.0",
|
|
1730
|
+
updatedAt: "2026-05-17",
|
|
1731
|
+
changelog: ["Initial release"],
|
|
1732
|
+
status: "free",
|
|
1733
|
+
difficulty: "beginner",
|
|
1734
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1735
|
+
dependencies: [],
|
|
1736
|
+
files: [{ path: "components/blocks/cta-banner.tsx", type: "block" }],
|
|
1737
|
+
tokens: ["foreground", "muted-foreground", "border"],
|
|
1738
|
+
supportsDarkMode: true,
|
|
1739
|
+
supportsLightMode: false,
|
|
1740
|
+
promptReady: true,
|
|
1741
|
+
},
|
|
1742
|
+
"site-footer": {
|
|
1743
|
+
slug: "site-footer",
|
|
1744
|
+
title: "Site Footer",
|
|
1745
|
+
description: "Full site footer with brand, grouped nav links, social icons, and copyright.",
|
|
1746
|
+
category: "Navigation",
|
|
1747
|
+
tags: ["footer", "navigation", "links", "social"],
|
|
1748
|
+
version: "1.0.0",
|
|
1749
|
+
updatedAt: "2026-05-17",
|
|
1750
|
+
changelog: ["Initial release"],
|
|
1751
|
+
status: "free",
|
|
1752
|
+
difficulty: "beginner",
|
|
1753
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1754
|
+
dependencies: ["lucide-react"],
|
|
1755
|
+
files: [{ path: "components/blocks/site-footer.tsx", type: "block" }],
|
|
1756
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1757
|
+
supportsDarkMode: true,
|
|
1758
|
+
supportsLightMode: false,
|
|
1759
|
+
promptReady: true,
|
|
1760
|
+
},
|
|
1761
|
+
"testimonials-wall": {
|
|
1762
|
+
slug: "testimonials-wall",
|
|
1763
|
+
title: "Testimonials Wall",
|
|
1764
|
+
description: "Responsive grid of testimonial cards with author avatars and quotes.",
|
|
1765
|
+
category: "Marketing",
|
|
1766
|
+
tags: ["testimonials", "social-proof", "quotes", "grid"],
|
|
1767
|
+
version: "1.0.0",
|
|
1768
|
+
updatedAt: "2026-05-17",
|
|
1769
|
+
changelog: ["Initial release"],
|
|
1770
|
+
status: "free",
|
|
1771
|
+
difficulty: "beginner",
|
|
1772
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1773
|
+
dependencies: [],
|
|
1774
|
+
files: [{ path: "components/blocks/testimonials-wall.tsx", type: "block" }],
|
|
1775
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1776
|
+
supportsDarkMode: true,
|
|
1777
|
+
supportsLightMode: false,
|
|
1778
|
+
promptReady: true,
|
|
1779
|
+
},
|
|
1780
|
+
"metrics-grid": {
|
|
1781
|
+
slug: "metrics-grid",
|
|
1782
|
+
title: "Metrics Grid",
|
|
1783
|
+
description: "Dashboard KPI grid with trend indicators, icons, and configurable columns.",
|
|
1784
|
+
category: "Dashboard",
|
|
1785
|
+
tags: ["metrics", "kpi", "dashboard", "stats", "analytics"],
|
|
1786
|
+
version: "1.0.0",
|
|
1787
|
+
updatedAt: "2026-05-17",
|
|
1788
|
+
changelog: ["Initial release"],
|
|
1789
|
+
status: "free",
|
|
1790
|
+
difficulty: "intermediate",
|
|
1791
|
+
frameworks: ["nextjs", "react"],
|
|
1792
|
+
dependencies: ["lucide-react"],
|
|
1793
|
+
files: [{ path: "components/blocks/metrics-grid.tsx", type: "block" }],
|
|
1794
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1795
|
+
supportsDarkMode: true,
|
|
1796
|
+
supportsLightMode: false,
|
|
1797
|
+
promptReady: true,
|
|
1798
|
+
},
|
|
1799
|
+
"announcement-bar": {
|
|
1800
|
+
slug: "announcement-bar",
|
|
1801
|
+
title: "Announcement Bar",
|
|
1802
|
+
description: "Dismissible top-of-page announcement banner with optional CTA.",
|
|
1803
|
+
category: "Feedback",
|
|
1804
|
+
tags: ["announcement", "banner", "notification", "top-bar"],
|
|
1805
|
+
version: "1.0.0",
|
|
1806
|
+
updatedAt: "2026-05-17",
|
|
1807
|
+
changelog: ["Initial release"],
|
|
1808
|
+
status: "free",
|
|
1809
|
+
difficulty: "beginner",
|
|
1810
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1811
|
+
dependencies: ["lucide-react"],
|
|
1812
|
+
files: [{ path: "components/blocks/announcement-bar.tsx", type: "block" }],
|
|
1813
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1814
|
+
supportsDarkMode: true,
|
|
1815
|
+
supportsLightMode: false,
|
|
1816
|
+
promptReady: true,
|
|
1817
|
+
},
|
|
1818
|
+
"social-proof-bar": {
|
|
1819
|
+
slug: "social-proof-bar",
|
|
1820
|
+
title: "Social Proof Bar",
|
|
1821
|
+
description: "Horizontal logo strip showing companies or teams using your product.",
|
|
1822
|
+
category: "Marketing",
|
|
1823
|
+
tags: ["logos", "social-proof", "companies", "trust"],
|
|
1824
|
+
version: "1.0.0",
|
|
1825
|
+
updatedAt: "2026-05-17",
|
|
1826
|
+
changelog: ["Initial release"],
|
|
1827
|
+
status: "free",
|
|
1828
|
+
difficulty: "beginner",
|
|
1829
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1830
|
+
dependencies: [],
|
|
1831
|
+
files: [{ path: "components/blocks/social-proof-bar.tsx", type: "block" }],
|
|
1832
|
+
tokens: ["muted-foreground", "border"],
|
|
1833
|
+
supportsDarkMode: true,
|
|
1834
|
+
supportsLightMode: false,
|
|
1835
|
+
promptReady: true,
|
|
1836
|
+
},
|
|
1837
|
+
"feature-comparison": {
|
|
1838
|
+
slug: "feature-comparison",
|
|
1839
|
+
title: "Feature Comparison",
|
|
1840
|
+
description: "Plan comparison table with highlighted column, checkmarks, and custom values.",
|
|
1841
|
+
category: "Marketing",
|
|
1842
|
+
tags: ["pricing", "comparison", "table", "features", "plans"],
|
|
1843
|
+
version: "1.0.0",
|
|
1844
|
+
updatedAt: "2026-05-17",
|
|
1845
|
+
changelog: ["Initial release"],
|
|
1846
|
+
status: "free",
|
|
1847
|
+
difficulty: "intermediate",
|
|
1848
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1849
|
+
dependencies: [],
|
|
1850
|
+
files: [{ path: "components/blocks/feature-comparison.tsx", type: "block" }],
|
|
1851
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1852
|
+
supportsDarkMode: true,
|
|
1853
|
+
supportsLightMode: false,
|
|
1854
|
+
promptReady: true,
|
|
1855
|
+
},
|
|
1856
|
+
"team-grid": {
|
|
1857
|
+
slug: "team-grid",
|
|
1858
|
+
title: "Team Grid",
|
|
1859
|
+
description: "Responsive grid of team member cards with avatars, roles, bios, and social links.",
|
|
1860
|
+
category: "Display",
|
|
1861
|
+
tags: ["team", "about", "members", "grid", "people"],
|
|
1862
|
+
version: "1.0.0",
|
|
1863
|
+
updatedAt: "2026-05-17",
|
|
1864
|
+
changelog: ["Initial release"],
|
|
1865
|
+
status: "free",
|
|
1866
|
+
difficulty: "beginner",
|
|
1867
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1868
|
+
dependencies: ["lucide-react"],
|
|
1869
|
+
files: [{ path: "components/blocks/team-grid.tsx", type: "block" }],
|
|
1870
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1871
|
+
supportsDarkMode: true,
|
|
1872
|
+
supportsLightMode: false,
|
|
1873
|
+
promptReady: true,
|
|
1874
|
+
},
|
|
1875
|
+
"empty-state": {
|
|
1876
|
+
slug: "empty-state",
|
|
1877
|
+
title: "Empty State",
|
|
1878
|
+
description: "Centered empty state with icon, title, description, and optional action.",
|
|
1879
|
+
category: "Feedback",
|
|
1880
|
+
tags: ["empty", "no-data", "placeholder", "state"],
|
|
1881
|
+
version: "1.0.0",
|
|
1882
|
+
updatedAt: "2026-05-17",
|
|
1883
|
+
changelog: ["Initial release"],
|
|
1884
|
+
status: "free",
|
|
1885
|
+
difficulty: "beginner",
|
|
1886
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1887
|
+
dependencies: ["lucide-react"],
|
|
1888
|
+
files: [{ path: "components/blocks/empty-state.tsx", type: "block" }],
|
|
1889
|
+
tokens: ["foreground", "muted-foreground", "border"],
|
|
1890
|
+
supportsDarkMode: true,
|
|
1891
|
+
supportsLightMode: false,
|
|
1892
|
+
promptReady: true,
|
|
1893
|
+
},
|
|
1894
|
+
"error-page": {
|
|
1895
|
+
slug: "error-page",
|
|
1896
|
+
title: "Error Page",
|
|
1897
|
+
description: "Minimal full-page error display (404/500) with code, title, and back button.",
|
|
1898
|
+
category: "Feedback",
|
|
1899
|
+
tags: ["error", "404", "500", "not-found", "page"],
|
|
1900
|
+
version: "1.0.0",
|
|
1901
|
+
updatedAt: "2026-05-17",
|
|
1902
|
+
changelog: ["Initial release"],
|
|
1903
|
+
status: "free",
|
|
1904
|
+
difficulty: "beginner",
|
|
1905
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1906
|
+
dependencies: [],
|
|
1907
|
+
files: [{ path: "components/blocks/error-page.tsx", type: "block" }],
|
|
1908
|
+
tokens: ["foreground", "muted-foreground"],
|
|
1909
|
+
supportsDarkMode: true,
|
|
1910
|
+
supportsLightMode: false,
|
|
1911
|
+
promptReady: true,
|
|
1912
|
+
},
|
|
1913
|
+
"cookie-consent": {
|
|
1914
|
+
slug: "cookie-consent",
|
|
1915
|
+
title: "Cookie Consent",
|
|
1916
|
+
description: "GDPR-compliant fixed-bottom cookie consent banner with accept/decline/customize actions.",
|
|
1917
|
+
category: "UI",
|
|
1918
|
+
tags: ["cookie", "gdpr", "consent", "privacy", "banner"],
|
|
1919
|
+
version: "1.0.0",
|
|
1920
|
+
updatedAt: "2026-05-17",
|
|
1921
|
+
changelog: ["Initial release"],
|
|
1922
|
+
status: "free",
|
|
1923
|
+
difficulty: "beginner",
|
|
1924
|
+
frameworks: ["nextjs", "react", "vite"],
|
|
1925
|
+
dependencies: [],
|
|
1926
|
+
files: [{ path: "components/blocks/cookie-consent.tsx", type: "block" }],
|
|
1927
|
+
tokens: ["foreground", "muted-foreground", "border", "surface-2"],
|
|
1928
|
+
supportsDarkMode: true,
|
|
1929
|
+
supportsLightMode: false,
|
|
1930
|
+
promptReady: true,
|
|
1931
|
+
},
|
|
1665
1932
|
};
|
|
1666
1933
|
exports.blockList = Object.values(exports.manifests);
|
package/package.json
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { X } from "lucide-react"
|
|
5
|
+
import { cx } from "@/lib/cx"
|
|
6
|
+
|
|
7
|
+
export interface AnnouncementCTA {
|
|
8
|
+
label: string
|
|
9
|
+
href?: string
|
|
10
|
+
onClick?: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AnnouncementBarProps {
|
|
14
|
+
message: string
|
|
15
|
+
cta?: AnnouncementCTA
|
|
16
|
+
variant?: "info" | "warning" | "success"
|
|
17
|
+
dismissible?: boolean
|
|
18
|
+
className?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const variantStyles: Record<
|
|
22
|
+
NonNullable<AnnouncementBarProps["variant"]>,
|
|
23
|
+
{ bar: string; label: string }
|
|
24
|
+
> = {
|
|
25
|
+
info: {
|
|
26
|
+
bar: "bg-[#09090B] border-b border-[#23252A]",
|
|
27
|
+
label: "text-muted-foreground",
|
|
28
|
+
},
|
|
29
|
+
warning: {
|
|
30
|
+
bar: "bg-[#09090B] border-b border-[#23252A]",
|
|
31
|
+
label: "text-[#FAFAFA] font-medium",
|
|
32
|
+
},
|
|
33
|
+
success: {
|
|
34
|
+
bar: "bg-[#09090B] border-b border-[#23252A]",
|
|
35
|
+
label: "text-muted-foreground",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function AnnouncementBar({
|
|
40
|
+
message,
|
|
41
|
+
cta,
|
|
42
|
+
variant = "info",
|
|
43
|
+
dismissible = false,
|
|
44
|
+
className,
|
|
45
|
+
}: AnnouncementBarProps) {
|
|
46
|
+
const [dismissed, setDismissed] = React.useState(false)
|
|
47
|
+
|
|
48
|
+
if (dismissed) return null
|
|
49
|
+
|
|
50
|
+
const styles = variantStyles[variant]
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
className={cx(
|
|
55
|
+
"relative w-full px-4 py-2.5",
|
|
56
|
+
styles.bar,
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
role="banner"
|
|
60
|
+
>
|
|
61
|
+
<div className="mx-auto flex max-w-5xl items-center justify-center gap-3">
|
|
62
|
+
<p className={cx("text-xs leading-snug", styles.label)}>
|
|
63
|
+
{message}
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
{cta && (
|
|
67
|
+
<>
|
|
68
|
+
<span className="shrink-0 text-[#23252A]" aria-hidden>
|
|
69
|
+
·
|
|
70
|
+
</span>
|
|
71
|
+
{cta.href ? (
|
|
72
|
+
<a
|
|
73
|
+
href={cta.href}
|
|
74
|
+
className="shrink-0 text-xs font-semibold text-[#FAFAFA] underline-offset-2 hover:underline transition-colors duration-150 ease-out"
|
|
75
|
+
>
|
|
76
|
+
{cta.label}
|
|
77
|
+
</a>
|
|
78
|
+
) : (
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
onClick={cta.onClick}
|
|
82
|
+
className="shrink-0 text-xs font-semibold text-[#FAFAFA] underline-offset-2 hover:underline transition-colors duration-150 ease-out"
|
|
83
|
+
>
|
|
84
|
+
{cta.label}
|
|
85
|
+
</button>
|
|
86
|
+
)}
|
|
87
|
+
</>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{dismissible && (
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
aria-label="Dismiss announcement"
|
|
95
|
+
onClick={() => setDismissed(true)}
|
|
96
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground transition-colors duration-150 ease-out hover:bg-[#101114] hover:text-[#FAFAFA]"
|
|
97
|
+
>
|
|
98
|
+
<X size={13} strokeWidth={2} />
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cx } from "@/lib/cx"
|
|
5
|
+
|
|
6
|
+
export interface ChangelogEntry {
|
|
7
|
+
version: string
|
|
8
|
+
date: string
|
|
9
|
+
title: string
|
|
10
|
+
description: string
|
|
11
|
+
tags?: string[]
|
|
12
|
+
type: "feature" | "fix" | "breaking"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ChangelogFeedProps {
|
|
16
|
+
entries?: ChangelogEntry[]
|
|
17
|
+
className?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TYPE_STYLES: Record<ChangelogEntry["type"], string> = {
|
|
21
|
+
feature: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20",
|
|
22
|
+
fix: "bg-sky-500/10 text-sky-400 border-sky-500/20",
|
|
23
|
+
breaking: "bg-red-500/10 text-red-400 border-red-500/20",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const TYPE_LABELS: Record<ChangelogEntry["type"], string> = {
|
|
27
|
+
feature: "Feature",
|
|
28
|
+
fix: "Fix",
|
|
29
|
+
breaking: "Breaking",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULT_ENTRIES: ChangelogEntry[] = [
|
|
33
|
+
{
|
|
34
|
+
version: "v1.4.0",
|
|
35
|
+
date: "May 14, 2025",
|
|
36
|
+
title: "Block registry search",
|
|
37
|
+
description: "Full-text search across all blocks with keyboard navigation and instant previews.",
|
|
38
|
+
tags: ["search", "dx"],
|
|
39
|
+
type: "feature",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
version: "v1.3.2",
|
|
43
|
+
date: "May 8, 2025",
|
|
44
|
+
title: "Fix dark mode flicker on hydration",
|
|
45
|
+
description: "Resolved a flash of unstyled content when the page first loads in dark mode.",
|
|
46
|
+
tags: ["ssr"],
|
|
47
|
+
type: "fix",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
version: "v1.3.0",
|
|
51
|
+
date: "Apr 29, 2025",
|
|
52
|
+
title: "Stamp CLI v2",
|
|
53
|
+
description: "Redesigned CLI with interactive prompts, dry-run mode, and conflict detection.",
|
|
54
|
+
tags: ["cli"],
|
|
55
|
+
type: "breaking",
|
|
56
|
+
},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
export function ChangelogFeed({
|
|
60
|
+
entries = DEFAULT_ENTRIES,
|
|
61
|
+
className,
|
|
62
|
+
}: ChangelogFeedProps) {
|
|
63
|
+
return (
|
|
64
|
+
<section className={cx("w-full py-16 px-6", className)}>
|
|
65
|
+
<div className="max-w-2xl mx-auto">
|
|
66
|
+
<h2 className="text-2xl font-bold tracking-tight text-[#FAFAFA] mb-10">
|
|
67
|
+
Changelog
|
|
68
|
+
</h2>
|
|
69
|
+
|
|
70
|
+
<div className="relative border-l border-[#23252A] pl-8 flex flex-col gap-10">
|
|
71
|
+
{entries.map((entry, i) => (
|
|
72
|
+
<div key={i} className="relative">
|
|
73
|
+
<span className="absolute -left-[calc(2rem+5px)] top-1.5 w-2.5 h-2.5 rounded-full bg-[#23252A] border border-[#23252A] ring-4 ring-[#070708]" />
|
|
74
|
+
|
|
75
|
+
<div className="flex flex-wrap items-center gap-2 mb-2">
|
|
76
|
+
<span className="text-xs font-mono font-medium text-[#FAFAFA] bg-[#101114] border border-[#23252A] px-2 py-0.5 rounded-md">
|
|
77
|
+
{entry.version}
|
|
78
|
+
</span>
|
|
79
|
+
<span
|
|
80
|
+
className={cx(
|
|
81
|
+
"text-xs font-medium border px-2 py-0.5 rounded-md",
|
|
82
|
+
TYPE_STYLES[entry.type]
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{TYPE_LABELS[entry.type]}
|
|
86
|
+
</span>
|
|
87
|
+
<span className="text-xs text-muted-foreground">{entry.date}</span>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<h3 className="text-base font-semibold text-[#FAFAFA] leading-snug">
|
|
91
|
+
{entry.title}
|
|
92
|
+
</h3>
|
|
93
|
+
<p className="mt-1.5 text-sm text-muted-foreground leading-relaxed">
|
|
94
|
+
{entry.description}
|
|
95
|
+
</p>
|
|
96
|
+
|
|
97
|
+
{entry.tags && entry.tags.length > 0 && (
|
|
98
|
+
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
99
|
+
{entry.tags.map((tag) => (
|
|
100
|
+
<span
|
|
101
|
+
key={tag}
|
|
102
|
+
className="text-xs font-mono text-muted-foreground bg-[#09090B] border border-[#23252A] px-2 py-0.5 rounded-md"
|
|
103
|
+
>
|
|
104
|
+
{tag}
|
|
105
|
+
</span>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</section>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Button } from "@/components/core/button"
|
|
5
|
+
import { cx } from "@/lib/cx"
|
|
6
|
+
|
|
7
|
+
export interface CookieConsentProps {
|
|
8
|
+
onAccept: () => void
|
|
9
|
+
onDecline: () => void
|
|
10
|
+
onCustomize?: () => void
|
|
11
|
+
title?: string
|
|
12
|
+
description?: string
|
|
13
|
+
className?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function CookieConsent({
|
|
17
|
+
onAccept,
|
|
18
|
+
onDecline,
|
|
19
|
+
onCustomize,
|
|
20
|
+
title = "We use cookies",
|
|
21
|
+
description = "We use cookies to improve your experience, analyze site traffic, and serve personalized content. You can accept all cookies or manage your preferences.",
|
|
22
|
+
className,
|
|
23
|
+
}: CookieConsentProps) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
role="dialog"
|
|
27
|
+
aria-label="Cookie consent"
|
|
28
|
+
className={cx(
|
|
29
|
+
"fixed bottom-0 left-0 right-0 z-50",
|
|
30
|
+
"border-t border-[#23252A] bg-[#09090B]",
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
<div className="mx-auto max-w-5xl px-6 py-4">
|
|
35
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-6">
|
|
36
|
+
<div className="flex-1 min-w-0">
|
|
37
|
+
<p className="text-sm font-medium text-[#FAFAFA] mb-0.5">{title}</p>
|
|
38
|
+
<p className="text-xs text-muted-foreground leading-relaxed">{description}</p>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
42
|
+
{onCustomize && (
|
|
43
|
+
<Button
|
|
44
|
+
variant="ghost"
|
|
45
|
+
size="sm"
|
|
46
|
+
onClick={onCustomize}
|
|
47
|
+
className="text-xs"
|
|
48
|
+
>
|
|
49
|
+
Customize
|
|
50
|
+
</Button>
|
|
51
|
+
)}
|
|
52
|
+
<Button
|
|
53
|
+
variant="outline"
|
|
54
|
+
size="sm"
|
|
55
|
+
onClick={onDecline}
|
|
56
|
+
className="text-xs"
|
|
57
|
+
>
|
|
58
|
+
Decline
|
|
59
|
+
</Button>
|
|
60
|
+
<Button
|
|
61
|
+
variant="primary"
|
|
62
|
+
size="sm"
|
|
63
|
+
onClick={onAccept}
|
|
64
|
+
className="text-xs"
|
|
65
|
+
>
|
|
66
|
+
Accept all
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Button } from "@/components/core/button"
|
|
5
|
+
import { cx } from "@/lib/cx"
|
|
6
|
+
|
|
7
|
+
export interface CtaAction {
|
|
8
|
+
label: string
|
|
9
|
+
href?: string
|
|
10
|
+
onClick?: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CtaBannerProps {
|
|
14
|
+
headline?: string
|
|
15
|
+
subtext?: string
|
|
16
|
+
primaryCta?: CtaAction
|
|
17
|
+
secondaryCta?: CtaAction
|
|
18
|
+
className?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function CtaBanner({
|
|
22
|
+
headline = "Ready to stamp your first block?",
|
|
23
|
+
subtext = "Copy production-ready components into your project in seconds. No lock-in, no runtime.",
|
|
24
|
+
primaryCta = { label: "Get Started", href: "/blocks" },
|
|
25
|
+
secondaryCta = { label: "Read the Docs", href: "/docs" },
|
|
26
|
+
className,
|
|
27
|
+
}: CtaBannerProps) {
|
|
28
|
+
return (
|
|
29
|
+
<section
|
|
30
|
+
className={cx(
|
|
31
|
+
"w-full border-t border-[#23252A] py-20 px-6",
|
|
32
|
+
"flex flex-col items-center text-center",
|
|
33
|
+
className
|
|
34
|
+
)}
|
|
35
|
+
>
|
|
36
|
+
<h2 className="text-3xl sm:text-4xl font-bold tracking-tight text-[#FAFAFA] max-w-xl leading-tight">
|
|
37
|
+
{headline}
|
|
38
|
+
</h2>
|
|
39
|
+
|
|
40
|
+
{subtext && (
|
|
41
|
+
<p className="mt-4 text-base text-muted-foreground max-w-md leading-relaxed">
|
|
42
|
+
{subtext}
|
|
43
|
+
</p>
|
|
44
|
+
)}
|
|
45
|
+
|
|
46
|
+
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
|
|
47
|
+
{primaryCta && (
|
|
48
|
+
<Button
|
|
49
|
+
asChild={!!primaryCta.href}
|
|
50
|
+
onClick={primaryCta.onClick}
|
|
51
|
+
size="lg"
|
|
52
|
+
>
|
|
53
|
+
{primaryCta.href ? (
|
|
54
|
+
<a href={primaryCta.href}>{primaryCta.label}</a>
|
|
55
|
+
) : (
|
|
56
|
+
<span>{primaryCta.label}</span>
|
|
57
|
+
)}
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
{secondaryCta && (
|
|
61
|
+
<Button
|
|
62
|
+
asChild={!!secondaryCta.href}
|
|
63
|
+
onClick={secondaryCta.onClick}
|
|
64
|
+
variant="outline"
|
|
65
|
+
size="lg"
|
|
66
|
+
>
|
|
67
|
+
{secondaryCta.href ? (
|
|
68
|
+
<a href={secondaryCta.href}>{secondaryCta.label}</a>
|
|
69
|
+
) : (
|
|
70
|
+
<span>{secondaryCta.label}</span>
|
|
71
|
+
)}
|
|
72
|
+
</Button>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
76
|
+
)
|
|
77
|
+
}
|