@burdenoff/website-sdk 2026.703.2 → 2026.709.2

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 (37) hide show
  1. package/dist/components/electron-download.d.mts +32 -0
  2. package/dist/components/electron-download.d.ts +32 -0
  3. package/dist/components/electron-download.js +131 -0
  4. package/dist/components/electron-download.js.map +1 -0
  5. package/dist/components/electron-download.mjs +108 -0
  6. package/dist/components/electron-download.mjs.map +1 -0
  7. package/dist/components/launch-countdown.d.mts +15 -0
  8. package/dist/components/launch-countdown.d.ts +15 -0
  9. package/dist/components/launch-countdown.js +89 -0
  10. package/dist/components/launch-countdown.js.map +1 -0
  11. package/dist/components/launch-countdown.mjs +87 -0
  12. package/dist/components/launch-countdown.mjs.map +1 -0
  13. package/dist/components/problems-solved.d.mts +37 -0
  14. package/dist/components/problems-solved.d.ts +37 -0
  15. package/dist/components/problems-solved.js +157 -0
  16. package/dist/components/problems-solved.js.map +1 -0
  17. package/dist/components/problems-solved.mjs +135 -0
  18. package/dist/components/problems-solved.mjs.map +1 -0
  19. package/dist/components/website-switcher.d.mts +27 -0
  20. package/dist/components/website-switcher.d.ts +27 -0
  21. package/dist/components/website-switcher.js +168 -0
  22. package/dist/components/website-switcher.js.map +1 -0
  23. package/dist/components/website-switcher.mjs +146 -0
  24. package/dist/components/website-switcher.mjs.map +1 -0
  25. package/dist/content/index.d.mts +24 -1
  26. package/dist/content/index.d.ts +24 -1
  27. package/dist/content/index.js +459 -149
  28. package/dist/content/index.js.map +1 -1
  29. package/dist/content/index.mjs +378 -87
  30. package/dist/content/index.mjs.map +1 -1
  31. package/dist/index.d.mts +15 -2
  32. package/dist/index.d.ts +15 -2
  33. package/dist/index.js +817 -86
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +812 -88
  36. package/dist/index.mjs.map +1 -1
  37. package/package.json +3 -1
@@ -0,0 +1,37 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ComponentType, ReactNode } from 'react';
3
+
4
+ interface ProblemSolved {
5
+ /** The pain point or before-state. */
6
+ problem: string;
7
+ /** How the product addresses the pain point. */
8
+ solution: string;
9
+ /** Lucide-style icon component. */
10
+ icon: ComponentType<{
11
+ className?: string;
12
+ "aria-hidden"?: boolean;
13
+ }>;
14
+ /** Optional use-case story slug for linking to /use-cases/:slug. */
15
+ storySlug?: string;
16
+ }
17
+ interface ProblemsSolvedSectionProps {
18
+ /** Section eyebrow text. */
19
+ eyebrow?: string;
20
+ /** Section heading. */
21
+ title?: string;
22
+ /** Section subtitle. */
23
+ subtitle?: string;
24
+ /** Problem/solution pairs to render. */
25
+ problems: ProblemSolved[];
26
+ /** Base path for use-case story links. */
27
+ storyBasePath?: string;
28
+ /** Label for story links. */
29
+ storyLabel?: string;
30
+ /** Optional additional CSS class for the outer section. */
31
+ className?: string;
32
+ /** Optional child element rendered at the bottom of the section. */
33
+ children?: ReactNode;
34
+ }
35
+ declare function ProblemsSolvedSection({ eyebrow, title, subtitle, problems, storyBasePath, storyLabel, className, children, }: ProblemsSolvedSectionProps): react_jsx_runtime.JSX.Element;
36
+
37
+ export { type ProblemSolved, ProblemsSolvedSection, type ProblemsSolvedSectionProps };
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+
3
+ var lucideReact = require('lucide-react');
4
+ var React = require('react');
5
+ var reactSlot = require('@radix-ui/react-slot');
6
+ var classVarianceAuthority = require('class-variance-authority');
7
+ var clsx = require('clsx');
8
+ var tailwindMerge = require('tailwind-merge');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
30
+
31
+ function cn(...inputs) {
32
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
33
+ }
34
+ var buttonVariants = classVarianceAuthority.cva(
35
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
36
+ {
37
+ variants: {
38
+ variant: {
39
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
40
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
41
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
42
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
43
+ ghost: "hover:bg-accent hover:text-accent-foreground",
44
+ link: "text-primary underline-offset-4 hover:underline"
45
+ },
46
+ size: {
47
+ default: "h-9 px-4 py-2",
48
+ sm: "h-8 rounded-md px-3 text-xs",
49
+ lg: "h-10 rounded-md px-8",
50
+ icon: "h-9 w-9"
51
+ }
52
+ },
53
+ defaultVariants: {
54
+ variant: "default",
55
+ size: "default"
56
+ }
57
+ }
58
+ );
59
+ var Button = React__namespace.forwardRef(
60
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
61
+ const Comp = asChild ? reactSlot.Slot : "button";
62
+ return /* @__PURE__ */ jsxRuntime.jsx(
63
+ Comp,
64
+ {
65
+ className: cn(buttonVariants({ variant, size, className })),
66
+ ref,
67
+ ...props
68
+ }
69
+ );
70
+ }
71
+ );
72
+ Button.displayName = "Button";
73
+ function ProblemsSolvedSection({
74
+ eyebrow,
75
+ title,
76
+ subtitle,
77
+ problems,
78
+ storyBasePath = "/use-cases",
79
+ storyLabel = "See the story",
80
+ className,
81
+ children
82
+ }) {
83
+ return /* @__PURE__ */ jsxRuntime.jsx(
84
+ "section",
85
+ {
86
+ className: cn(
87
+ "py-16 md:py-24 px-4 sm:px-6 lg:px-8 bg-muted/30",
88
+ className
89
+ ),
90
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto", children: [
91
+ (eyebrow || title || subtitle) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-3xl mx-auto text-center mb-12 md:mb-16", children: [
92
+ eyebrow && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-mono uppercase tracking-widest text-primary mb-4", children: eyebrow }),
93
+ title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-3xl sm:text-4xl font-bold text-foreground mb-4", children: title }),
94
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg text-muted-foreground", children: subtitle })
95
+ ] }),
96
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: problems.map((item, index) => {
97
+ const Icon = item.icon;
98
+ const storyHref = item.storySlug ? `${storyBasePath.replace(/\/$/, "")}/${item.storySlug}` : null;
99
+ return /* @__PURE__ */ jsxRuntime.jsxs(
100
+ "div",
101
+ {
102
+ className: cn(
103
+ "group relative flex flex-col rounded-2xl border border-border/40 bg-card p-6 transition-colors",
104
+ "hover:border-primary/30"
105
+ ),
106
+ children: [
107
+ /* @__PURE__ */ jsxRuntime.jsx(
108
+ "div",
109
+ {
110
+ className: cn(
111
+ "mb-5 inline-flex h-12 w-12 items-center justify-center rounded-xl",
112
+ "bg-primary/10 text-primary"
113
+ ),
114
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-6 w-6", "aria-hidden": true })
115
+ }
116
+ ),
117
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-foreground mb-2", children: item.problem }),
118
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground leading-relaxed mb-4 flex-1", children: item.solution }),
119
+ storyHref && /* @__PURE__ */ jsxRuntime.jsx(
120
+ Button,
121
+ {
122
+ variant: "ghost",
123
+ className: "justify-start px-0 h-auto py-0 text-sm group/link",
124
+ asChild: true,
125
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
126
+ "a",
127
+ {
128
+ href: storyHref,
129
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
130
+ children: [
131
+ storyLabel,
132
+ /* @__PURE__ */ jsxRuntime.jsx(
133
+ lucideReact.ArrowRight,
134
+ {
135
+ className: "h-3.5 w-3.5 transition-transform group-hover/link:translate-x-0.5",
136
+ "aria-hidden": "true"
137
+ }
138
+ )
139
+ ]
140
+ }
141
+ )
142
+ }
143
+ )
144
+ ]
145
+ },
146
+ `${item.problem}-${index}`
147
+ );
148
+ }) }),
149
+ children && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-12", children })
150
+ ] })
151
+ }
152
+ );
153
+ }
154
+
155
+ exports.ProblemsSolvedSection = ProblemsSolvedSection;
156
+ //# sourceMappingURL=problems-solved.js.map
157
+ //# sourceMappingURL=problems-solved.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/ui/button.tsx","../../src/components/problems-solved.tsx"],"names":["twMerge","clsx","cva","React","Slot","jsx","jsxs","ArrowRight"],"mappings":"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","file":"problems-solved.js","sourcesContent":["/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx and tailwind-merge for optimal class handling\n */\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge multiple class names intelligently\n * - Handles conditional classes via clsx\n * - Deduplicates and resolves Tailwind conflicts via tailwind-merge\n *\n * @example\n * ```ts\n * cn('px-4 py-2', 'bg-blue-500', { 'text-white': isActive })\n * // => 'px-4 py-2 bg-blue-500 text-white' (if isActive is true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"../utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","\"use client\";\n\n/**\n * ProblemsSolvedSection Component\n *\n * A creative, conversion-oriented section that pairs a problem with the\n * product's solution. Each card highlights the pain point, the fix, and an\n * optional link to a deeper use-case story.\n *\n * @example\n * ```tsx\n * import { ProblemsSolvedSection } from '@burdenoff/website-sdk'\n *\n * <ProblemsSolvedSection\n * eyebrow=\"Why teams switch\"\n * title=\"Stop fighting your tools\"\n * subtitle=\"We built VibeControls around the friction that actually slows engineering teams down.\"\n * problems={[...]}\n * />\n * ```\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\n\nimport { ArrowRight } from \"lucide-react\";\n\nimport { Button } from \"../ui/button\";\nimport { cn } from \"../utils/cn\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ProblemSolved {\n /** The pain point or before-state. */\n problem: string;\n /** How the product addresses the pain point. */\n solution: string;\n /** Lucide-style icon component. */\n icon: ComponentType<{ className?: string; \"aria-hidden\"?: boolean }>;\n /** Optional use-case story slug for linking to /use-cases/:slug. */\n storySlug?: string;\n}\n\nexport interface ProblemsSolvedSectionProps {\n /** Section eyebrow text. */\n eyebrow?: string;\n /** Section heading. */\n title?: string;\n /** Section subtitle. */\n subtitle?: string;\n /** Problem/solution pairs to render. */\n problems: ProblemSolved[];\n /** Base path for use-case story links. */\n storyBasePath?: string;\n /** Label for story links. */\n storyLabel?: string;\n /** Optional additional CSS class for the outer section. */\n className?: string;\n /** Optional child element rendered at the bottom of the section. */\n children?: ReactNode;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ProblemsSolvedSection({\n eyebrow,\n title,\n subtitle,\n problems,\n storyBasePath = \"/use-cases\",\n storyLabel = \"See the story\",\n className,\n children,\n}: ProblemsSolvedSectionProps) {\n return (\n <section\n className={cn(\n \"py-16 md:py-24 px-4 sm:px-6 lg:px-8 bg-muted/30\",\n className,\n )}\n >\n <div className=\"max-w-7xl mx-auto\">\n {(eyebrow || title || subtitle) && (\n <div className=\"max-w-3xl mx-auto text-center mb-12 md:mb-16\">\n {eyebrow && (\n <p className=\"text-xs font-mono uppercase tracking-widest text-primary mb-4\">\n {eyebrow}\n </p>\n )}\n {title && (\n <h2 className=\"text-3xl sm:text-4xl font-bold text-foreground mb-4\">\n {title}\n </h2>\n )}\n {subtitle && (\n <p className=\"text-lg text-muted-foreground\">{subtitle}</p>\n )}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {problems.map((item, index) => {\n const Icon = item.icon;\n const storyHref = item.storySlug\n ? `${storyBasePath.replace(/\\/$/, \"\")}/${item.storySlug}`\n : null;\n\n return (\n <div\n key={`${item.problem}-${index}`}\n className={cn(\n \"group relative flex flex-col rounded-2xl border border-border/40 bg-card p-6 transition-colors\",\n \"hover:border-primary/30\",\n )}\n >\n <div\n className={cn(\n \"mb-5 inline-flex h-12 w-12 items-center justify-center rounded-xl\",\n \"bg-primary/10 text-primary\",\n )}\n >\n <Icon className=\"h-6 w-6\" aria-hidden={true} />\n </div>\n\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">\n {item.problem}\n </h3>\n\n <p className=\"text-sm text-muted-foreground leading-relaxed mb-4 flex-1\">\n {item.solution}\n </p>\n\n {storyHref && (\n <Button\n variant=\"ghost\"\n className=\"justify-start px-0 h-auto py-0 text-sm group/link\"\n asChild\n >\n <a\n href={storyHref}\n className=\"inline-flex items-center gap-1 text-primary hover:underline\"\n >\n {storyLabel}\n <ArrowRight\n className=\"h-3.5 w-3.5 transition-transform group-hover/link:translate-x-0.5\"\n aria-hidden=\"true\"\n />\n </a>\n </Button>\n )}\n </div>\n );\n })}\n </div>\n\n {children && <div className=\"mt-12\">{children}</div>}\n </div>\n </section>\n );\n}\n"]}
@@ -0,0 +1,135 @@
1
+ import { ArrowRight } from 'lucide-react';
2
+ import * as React from 'react';
3
+ import { Slot } from '@radix-ui/react-slot';
4
+ import { cva } from 'class-variance-authority';
5
+ import { clsx } from 'clsx';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { jsx, jsxs } from 'react/jsx-runtime';
8
+
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ var buttonVariants = cva(
13
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
14
+ {
15
+ variants: {
16
+ variant: {
17
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
18
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
19
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
20
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
21
+ ghost: "hover:bg-accent hover:text-accent-foreground",
22
+ link: "text-primary underline-offset-4 hover:underline"
23
+ },
24
+ size: {
25
+ default: "h-9 px-4 py-2",
26
+ sm: "h-8 rounded-md px-3 text-xs",
27
+ lg: "h-10 rounded-md px-8",
28
+ icon: "h-9 w-9"
29
+ }
30
+ },
31
+ defaultVariants: {
32
+ variant: "default",
33
+ size: "default"
34
+ }
35
+ }
36
+ );
37
+ var Button = React.forwardRef(
38
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
39
+ const Comp = asChild ? Slot : "button";
40
+ return /* @__PURE__ */ jsx(
41
+ Comp,
42
+ {
43
+ className: cn(buttonVariants({ variant, size, className })),
44
+ ref,
45
+ ...props
46
+ }
47
+ );
48
+ }
49
+ );
50
+ Button.displayName = "Button";
51
+ function ProblemsSolvedSection({
52
+ eyebrow,
53
+ title,
54
+ subtitle,
55
+ problems,
56
+ storyBasePath = "/use-cases",
57
+ storyLabel = "See the story",
58
+ className,
59
+ children
60
+ }) {
61
+ return /* @__PURE__ */ jsx(
62
+ "section",
63
+ {
64
+ className: cn(
65
+ "py-16 md:py-24 px-4 sm:px-6 lg:px-8 bg-muted/30",
66
+ className
67
+ ),
68
+ children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto", children: [
69
+ (eyebrow || title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "max-w-3xl mx-auto text-center mb-12 md:mb-16", children: [
70
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "text-xs font-mono uppercase tracking-widest text-primary mb-4", children: eyebrow }),
71
+ title && /* @__PURE__ */ jsx("h2", { className: "text-3xl sm:text-4xl font-bold text-foreground mb-4", children: title }),
72
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: subtitle })
73
+ ] }),
74
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: problems.map((item, index) => {
75
+ const Icon = item.icon;
76
+ const storyHref = item.storySlug ? `${storyBasePath.replace(/\/$/, "")}/${item.storySlug}` : null;
77
+ return /* @__PURE__ */ jsxs(
78
+ "div",
79
+ {
80
+ className: cn(
81
+ "group relative flex flex-col rounded-2xl border border-border/40 bg-card p-6 transition-colors",
82
+ "hover:border-primary/30"
83
+ ),
84
+ children: [
85
+ /* @__PURE__ */ jsx(
86
+ "div",
87
+ {
88
+ className: cn(
89
+ "mb-5 inline-flex h-12 w-12 items-center justify-center rounded-xl",
90
+ "bg-primary/10 text-primary"
91
+ ),
92
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-6 w-6", "aria-hidden": true })
93
+ }
94
+ ),
95
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground mb-2", children: item.problem }),
96
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground leading-relaxed mb-4 flex-1", children: item.solution }),
97
+ storyHref && /* @__PURE__ */ jsx(
98
+ Button,
99
+ {
100
+ variant: "ghost",
101
+ className: "justify-start px-0 h-auto py-0 text-sm group/link",
102
+ asChild: true,
103
+ children: /* @__PURE__ */ jsxs(
104
+ "a",
105
+ {
106
+ href: storyHref,
107
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
108
+ children: [
109
+ storyLabel,
110
+ /* @__PURE__ */ jsx(
111
+ ArrowRight,
112
+ {
113
+ className: "h-3.5 w-3.5 transition-transform group-hover/link:translate-x-0.5",
114
+ "aria-hidden": "true"
115
+ }
116
+ )
117
+ ]
118
+ }
119
+ )
120
+ }
121
+ )
122
+ ]
123
+ },
124
+ `${item.problem}-${index}`
125
+ );
126
+ }) }),
127
+ children && /* @__PURE__ */ jsx("div", { className: "mt-12", children })
128
+ ] })
129
+ }
130
+ );
131
+ }
132
+
133
+ export { ProblemsSolvedSection };
134
+ //# sourceMappingURL=problems-solved.mjs.map
135
+ //# sourceMappingURL=problems-solved.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/ui/button.tsx","../../src/components/problems-solved.tsx"],"names":["jsx"],"mappings":"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","file":"problems-solved.mjs","sourcesContent":["/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx and tailwind-merge for optimal class handling\n */\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge multiple class names intelligently\n * - Handles conditional classes via clsx\n * - Deduplicates and resolves Tailwind conflicts via tailwind-merge\n *\n * @example\n * ```ts\n * cn('px-4 py-2', 'bg-blue-500', { 'text-white': isActive })\n * // => 'px-4 py-2 bg-blue-500 text-white' (if isActive is true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"../utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","\"use client\";\n\n/**\n * ProblemsSolvedSection Component\n *\n * A creative, conversion-oriented section that pairs a problem with the\n * product's solution. Each card highlights the pain point, the fix, and an\n * optional link to a deeper use-case story.\n *\n * @example\n * ```tsx\n * import { ProblemsSolvedSection } from '@burdenoff/website-sdk'\n *\n * <ProblemsSolvedSection\n * eyebrow=\"Why teams switch\"\n * title=\"Stop fighting your tools\"\n * subtitle=\"We built VibeControls around the friction that actually slows engineering teams down.\"\n * problems={[...]}\n * />\n * ```\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\n\nimport { ArrowRight } from \"lucide-react\";\n\nimport { Button } from \"../ui/button\";\nimport { cn } from \"../utils/cn\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ProblemSolved {\n /** The pain point or before-state. */\n problem: string;\n /** How the product addresses the pain point. */\n solution: string;\n /** Lucide-style icon component. */\n icon: ComponentType<{ className?: string; \"aria-hidden\"?: boolean }>;\n /** Optional use-case story slug for linking to /use-cases/:slug. */\n storySlug?: string;\n}\n\nexport interface ProblemsSolvedSectionProps {\n /** Section eyebrow text. */\n eyebrow?: string;\n /** Section heading. */\n title?: string;\n /** Section subtitle. */\n subtitle?: string;\n /** Problem/solution pairs to render. */\n problems: ProblemSolved[];\n /** Base path for use-case story links. */\n storyBasePath?: string;\n /** Label for story links. */\n storyLabel?: string;\n /** Optional additional CSS class for the outer section. */\n className?: string;\n /** Optional child element rendered at the bottom of the section. */\n children?: ReactNode;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ProblemsSolvedSection({\n eyebrow,\n title,\n subtitle,\n problems,\n storyBasePath = \"/use-cases\",\n storyLabel = \"See the story\",\n className,\n children,\n}: ProblemsSolvedSectionProps) {\n return (\n <section\n className={cn(\n \"py-16 md:py-24 px-4 sm:px-6 lg:px-8 bg-muted/30\",\n className,\n )}\n >\n <div className=\"max-w-7xl mx-auto\">\n {(eyebrow || title || subtitle) && (\n <div className=\"max-w-3xl mx-auto text-center mb-12 md:mb-16\">\n {eyebrow && (\n <p className=\"text-xs font-mono uppercase tracking-widest text-primary mb-4\">\n {eyebrow}\n </p>\n )}\n {title && (\n <h2 className=\"text-3xl sm:text-4xl font-bold text-foreground mb-4\">\n {title}\n </h2>\n )}\n {subtitle && (\n <p className=\"text-lg text-muted-foreground\">{subtitle}</p>\n )}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {problems.map((item, index) => {\n const Icon = item.icon;\n const storyHref = item.storySlug\n ? `${storyBasePath.replace(/\\/$/, \"\")}/${item.storySlug}`\n : null;\n\n return (\n <div\n key={`${item.problem}-${index}`}\n className={cn(\n \"group relative flex flex-col rounded-2xl border border-border/40 bg-card p-6 transition-colors\",\n \"hover:border-primary/30\",\n )}\n >\n <div\n className={cn(\n \"mb-5 inline-flex h-12 w-12 items-center justify-center rounded-xl\",\n \"bg-primary/10 text-primary\",\n )}\n >\n <Icon className=\"h-6 w-6\" aria-hidden={true} />\n </div>\n\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">\n {item.problem}\n </h3>\n\n <p className=\"text-sm text-muted-foreground leading-relaxed mb-4 flex-1\">\n {item.solution}\n </p>\n\n {storyHref && (\n <Button\n variant=\"ghost\"\n className=\"justify-start px-0 h-auto py-0 text-sm group/link\"\n asChild\n >\n <a\n href={storyHref}\n className=\"inline-flex items-center gap-1 text-primary hover:underline\"\n >\n {storyLabel}\n <ArrowRight\n className=\"h-3.5 w-3.5 transition-transform group-hover/link:translate-x-0.5\"\n aria-hidden=\"true\"\n />\n </a>\n </Button>\n )}\n </div>\n );\n })}\n </div>\n\n {children && <div className=\"mt-12\">{children}</div>}\n </div>\n </section>\n );\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface WebsiteSwitcherProduct {
4
+ /** Stable identifier for the product. */
5
+ slug: string;
6
+ /** Human-readable product name. */
7
+ name: string;
8
+ /** URL to the product logo (absolute or relative). */
9
+ logoUrl?: string | null;
10
+ /** Product marketing site URL. */
11
+ websiteUrl: string;
12
+ }
13
+ interface WebsiteSwitcherProps {
14
+ /** All Burdenoff ecosystem products to display. */
15
+ products: WebsiteSwitcherProduct[];
16
+ /** Accessible label for the trigger. */
17
+ triggerLabel?: string;
18
+ /** Placeholder text for the search input. */
19
+ searchPlaceholder?: string;
20
+ /** Empty-state text when filtering returns no results. */
21
+ emptyText?: string;
22
+ /** Optional additional CSS class for the trigger. */
23
+ className?: string;
24
+ }
25
+ declare function WebsiteSwitcher({ products, triggerLabel, searchPlaceholder, emptyText, className, }: WebsiteSwitcherProps): react_jsx_runtime.JSX.Element;
26
+
27
+ export { WebsiteSwitcher, type WebsiteSwitcherProduct, type WebsiteSwitcherProps };
@@ -0,0 +1,27 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface WebsiteSwitcherProduct {
4
+ /** Stable identifier for the product. */
5
+ slug: string;
6
+ /** Human-readable product name. */
7
+ name: string;
8
+ /** URL to the product logo (absolute or relative). */
9
+ logoUrl?: string | null;
10
+ /** Product marketing site URL. */
11
+ websiteUrl: string;
12
+ }
13
+ interface WebsiteSwitcherProps {
14
+ /** All Burdenoff ecosystem products to display. */
15
+ products: WebsiteSwitcherProduct[];
16
+ /** Accessible label for the trigger. */
17
+ triggerLabel?: string;
18
+ /** Placeholder text for the search input. */
19
+ searchPlaceholder?: string;
20
+ /** Empty-state text when filtering returns no results. */
21
+ emptyText?: string;
22
+ /** Optional additional CSS class for the trigger. */
23
+ className?: string;
24
+ }
25
+ declare function WebsiteSwitcher({ products, triggerLabel, searchPlaceholder, emptyText, className, }: WebsiteSwitcherProps): react_jsx_runtime.JSX.Element;
26
+
27
+ export { WebsiteSwitcher, type WebsiteSwitcherProduct, type WebsiteSwitcherProps };
@@ -0,0 +1,168 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var DropdownMenu = require('@radix-ui/react-dropdown-menu');
5
+ var lucideReact = require('lucide-react');
6
+ var clsx = require('clsx');
7
+ var tailwindMerge = require('tailwind-merge');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
29
+ var DropdownMenu__namespace = /*#__PURE__*/_interopNamespace(DropdownMenu);
30
+
31
+ function cn(...inputs) {
32
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
33
+ }
34
+ var Input = React__namespace.forwardRef(
35
+ ({ className, type, ...props }, ref) => {
36
+ return /* @__PURE__ */ jsxRuntime.jsx(
37
+ "input",
38
+ {
39
+ type,
40
+ className: cn(
41
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
42
+ className
43
+ ),
44
+ ref,
45
+ ...props
46
+ }
47
+ );
48
+ }
49
+ );
50
+ Input.displayName = "Input";
51
+ function normalizeSearch(value) {
52
+ return value.toLowerCase().trim();
53
+ }
54
+ function filterProducts(products, query) {
55
+ const normalized = normalizeSearch(query);
56
+ if (!normalized) return products;
57
+ return products.filter((p) => normalizeSearch(p.name).includes(normalized));
58
+ }
59
+ function WebsiteSwitcher({
60
+ products,
61
+ triggerLabel = "Explore products",
62
+ searchPlaceholder = "Find a product\u2026",
63
+ emptyText = "No products match your search.",
64
+ className
65
+ }) {
66
+ const [open, setOpen] = React.useState(false);
67
+ const [query, setQuery] = React.useState("");
68
+ const filtered = React.useMemo(
69
+ () => filterProducts(products, query),
70
+ [products, query]
71
+ );
72
+ return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu__namespace.Root, { open, onOpenChange: setOpen, children: [
73
+ /* @__PURE__ */ jsxRuntime.jsxs(
74
+ DropdownMenu__namespace.Trigger,
75
+ {
76
+ className: cn(
77
+ "inline-flex items-center gap-1.5 text-sm font-medium transition-colors",
78
+ "text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-md",
79
+ className
80
+ ),
81
+ "aria-label": triggerLabel,
82
+ children: [
83
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { className: "h-4 w-4", "aria-hidden": "true" }),
84
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: triggerLabel }),
85
+ /* @__PURE__ */ jsxRuntime.jsx(
86
+ lucideReact.ChevronDown,
87
+ {
88
+ className: cn(
89
+ "h-3.5 w-3.5 transition-transform duration-200",
90
+ open && "rotate-180"
91
+ ),
92
+ "aria-hidden": "true"
93
+ }
94
+ )
95
+ ]
96
+ }
97
+ ),
98
+ /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Portal, { children: /* @__PURE__ */ jsxRuntime.jsxs(
99
+ DropdownMenu__namespace.Content,
100
+ {
101
+ className: "z-50 min-w-[16rem] max-w-[20rem] rounded-lg border border-border/60 bg-popover p-2 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2",
102
+ sideOffset: 8,
103
+ align: "end",
104
+ avoidCollisions: true,
105
+ children: [
106
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-2 pb-2 pt-1", children: [
107
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: "Burdenoff ecosystem" }),
108
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
109
+ /* @__PURE__ */ jsxRuntime.jsx(
110
+ lucideReact.Search,
111
+ {
112
+ className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground",
113
+ "aria-hidden": "true"
114
+ }
115
+ ),
116
+ /* @__PURE__ */ jsxRuntime.jsx(
117
+ Input,
118
+ {
119
+ type: "search",
120
+ value: query,
121
+ onChange: (e) => setQuery(e.target.value),
122
+ placeholder: searchPlaceholder,
123
+ className: "h-8 pl-8 text-sm",
124
+ "aria-label": searchPlaceholder,
125
+ onKeyDown: (e) => {
126
+ if (e.key === "Escape") {
127
+ setQuery("");
128
+ }
129
+ }
130
+ }
131
+ )
132
+ ] })
133
+ ] }),
134
+ /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Separator, { className: "my-1 h-px bg-border/60" }),
135
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-[min(60vh,24rem)] overflow-y-auto py-1", children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyText }) : filtered.map((product) => /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Item, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
136
+ "a",
137
+ {
138
+ href: product.websiteUrl,
139
+ target: "_blank",
140
+ rel: "noopener noreferrer",
141
+ className: cn(
142
+ "flex items-center gap-3 rounded-md px-2 py-2 text-sm outline-none",
143
+ "text-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground"
144
+ ),
145
+ children: [
146
+ product.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx(
147
+ "img",
148
+ {
149
+ src: product.logoUrl,
150
+ alt: "",
151
+ "aria-hidden": "true",
152
+ className: "h-5 w-5 shrink-0 rounded object-contain"
153
+ }
154
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded bg-primary/10 text-[10px] font-bold text-primary", children: product.name.charAt(0) }),
155
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: product.name })
156
+ ]
157
+ }
158
+ ) }, product.slug)) }),
159
+ /* @__PURE__ */ jsxRuntime.jsx(DropdownMenu__namespace.Arrow, { className: "fill-popover" })
160
+ ]
161
+ }
162
+ ) })
163
+ ] });
164
+ }
165
+
166
+ exports.WebsiteSwitcher = WebsiteSwitcher;
167
+ //# sourceMappingURL=website-switcher.js.map
168
+ //# sourceMappingURL=website-switcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/ui/input.tsx","../../src/components/website-switcher.tsx"],"names":["twMerge","clsx","React","jsx","useState","useMemo","jsxs","DropdownMenu","Globe","ChevronDown","Search"],"mappings":"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","file":"website-switcher.js","sourcesContent":["/**\n * Utility function to merge Tailwind CSS classes\n * Combines clsx and tailwind-merge for optimal class handling\n */\n\nimport { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge multiple class names intelligently\n * - Handles conditional classes via clsx\n * - Deduplicates and resolves Tailwind conflicts via tailwind-merge\n *\n * @example\n * ```ts\n * cn('px-4 py-2', 'bg-blue-500', { 'text-white': isActive })\n * // => 'px-4 py-2 bg-blue-500 text-white' (if isActive is true)\n * ```\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\n\nimport { cn } from \"../utils/cn\";\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n","\"use client\";\n\n/**\n * WebsiteSwitcher Component\n *\n * A dropdown menu for navigating between Burdenoff ecosystem product websites.\n * Includes a search filter and renders each product with its logo and name.\n *\n * @example\n * ```tsx\n * import { WebsiteSwitcher } from '@burdenoff/website-sdk'\n *\n * <WebsiteSwitcher\n * products={[\n * { slug: 'vibecontrols', name: 'VibeControls', logoUrl: '/vibe.svg', websiteUrl: 'https://vibecontrols.com' },\n * { slug: 'fluidgrids', name: 'FluidGrids', logoUrl: '/fg.svg', websiteUrl: 'https://fluidgrids.com' },\n * ]}\n * />\n * ```\n */\n\nimport { useMemo, useState } from \"react\";\n\nimport * as DropdownMenu from \"@radix-ui/react-dropdown-menu\";\nimport { ChevronDown, Globe, Search } from \"lucide-react\";\n\nimport { Input } from \"../ui/input\";\nimport { cn } from \"../utils/cn\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface WebsiteSwitcherProduct {\n /** Stable identifier for the product. */\n slug: string;\n /** Human-readable product name. */\n name: string;\n /** URL to the product logo (absolute or relative). */\n logoUrl?: string | null;\n /** Product marketing site URL. */\n websiteUrl: string;\n}\n\nexport interface WebsiteSwitcherProps {\n /** All Burdenoff ecosystem products to display. */\n products: WebsiteSwitcherProduct[];\n /** Accessible label for the trigger. */\n triggerLabel?: string;\n /** Placeholder text for the search input. */\n searchPlaceholder?: string;\n /** Empty-state text when filtering returns no results. */\n emptyText?: string;\n /** Optional additional CSS class for the trigger. */\n className?: string;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction normalizeSearch(value: string): string {\n return value.toLowerCase().trim();\n}\n\nfunction filterProducts(\n products: WebsiteSwitcherProduct[],\n query: string,\n): WebsiteSwitcherProduct[] {\n const normalized = normalizeSearch(query);\n if (!normalized) return products;\n return products.filter((p) => normalizeSearch(p.name).includes(normalized));\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function WebsiteSwitcher({\n products,\n triggerLabel = \"Explore products\",\n searchPlaceholder = \"Find a product…\",\n emptyText = \"No products match your search.\",\n className,\n}: WebsiteSwitcherProps) {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n\n const filtered = useMemo(\n () => filterProducts(products, query),\n [products, query],\n );\n\n return (\n <DropdownMenu.Root open={open} onOpenChange={setOpen}>\n <DropdownMenu.Trigger\n className={cn(\n \"inline-flex items-center gap-1.5 text-sm font-medium transition-colors\",\n \"text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-md\",\n className,\n )}\n aria-label={triggerLabel}\n >\n <Globe className=\"h-4 w-4\" aria-hidden=\"true\" />\n <span className=\"hidden sm:inline\">{triggerLabel}</span>\n <ChevronDown\n className={cn(\n \"h-3.5 w-3.5 transition-transform duration-200\",\n open && \"rotate-180\",\n )}\n aria-hidden=\"true\"\n />\n </DropdownMenu.Trigger>\n\n <DropdownMenu.Portal>\n <DropdownMenu.Content\n className=\"z-50 min-w-[16rem] max-w-[20rem] rounded-lg border border-border/60 bg-popover p-2 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2\"\n sideOffset={8}\n align=\"end\"\n avoidCollisions\n >\n <div className=\"px-2 pb-2 pt-1\">\n <p className=\"text-xs font-medium text-muted-foreground mb-2\">\n Burdenoff ecosystem\n </p>\n <div className=\"relative\">\n <Search\n className=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={searchPlaceholder}\n className=\"h-8 pl-8 text-sm\"\n aria-label={searchPlaceholder}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n setQuery(\"\");\n }\n }}\n />\n </div>\n </div>\n\n <DropdownMenu.Separator className=\"my-1 h-px bg-border/60\" />\n\n <div className=\"max-h-[min(60vh,24rem)] overflow-y-auto py-1\">\n {filtered.length === 0 ? (\n <div className=\"px-3 py-4 text-center text-sm text-muted-foreground\">\n {emptyText}\n </div>\n ) : (\n filtered.map((product) => (\n <DropdownMenu.Item key={product.slug} asChild>\n <a\n href={product.websiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={cn(\n \"flex items-center gap-3 rounded-md px-2 py-2 text-sm outline-none\",\n \"text-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\",\n )}\n >\n {product.logoUrl ? (\n <img\n src={product.logoUrl}\n alt=\"\"\n aria-hidden=\"true\"\n className=\"h-5 w-5 shrink-0 rounded object-contain\"\n />\n ) : (\n <span className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded bg-primary/10 text-[10px] font-bold text-primary\">\n {product.name.charAt(0)}\n </span>\n )}\n <span className=\"truncate\">{product.name}</span>\n </a>\n </DropdownMenu.Item>\n ))\n )}\n </div>\n\n <DropdownMenu.Arrow className=\"fill-popover\" />\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n"]}