Skip to content

Commit f153a8e

Browse files
ok
1 parent 4124eb8 commit f153a8e

561 files changed

Lines changed: 22170 additions & 65647 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/web/cli.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"uiLibrary": "base-ui"
3+
}

apps/web/components.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://ui.shadcn.com/schema.json",
3-
"style": "new-york",
3+
"style": "base-lyra",
44
"rsc": true,
55
"tsx": true,
66
"tailwind": {
@@ -18,6 +18,8 @@
1818
"lib": "@/lib",
1919
"hooks": "@/hooks"
2020
},
21+
"menuColor": "default",
22+
"menuAccent": "subtle",
2123
"registries": {
2224
"@kibo-ui": "https://www.kibo-ui.com/r/{name}.json"
2325
}

apps/web/package.json

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,13 @@
1111
"generate-schema": "bun scripts/generate-schema.ts"
1212
},
1313
"dependencies": {
14+
"@base-ui/react": "^1.0.0",
1415
"@better-t-stack/backend": "workspace:*",
1516
"@better-t-stack/template-generator": "workspace:*",
1617
"@better-t-stack/types": "workspace:*",
1718
"@erquhart/convex-oss-stats": "^0.8.2",
1819
"@number-flow/react": "^0.5.10",
1920
"@orama/orama": "^3.1.18",
20-
"@radix-ui/react-dialog": "^1.1.15",
21-
"@radix-ui/react-dropdown-menu": "^2.1.16",
22-
"@radix-ui/react-hover-card": "^1.1.15",
23-
"@radix-ui/react-scroll-area": "^1.2.10",
24-
"@radix-ui/react-select": "^2.2.6",
25-
"@radix-ui/react-slot": "^1.2.4",
26-
"@radix-ui/react-switch": "^1.2.6",
27-
"@radix-ui/react-toggle": "^1.1.10",
28-
"@radix-ui/react-use-controllable-state": "^1.2.2",
2921
"@shikijs/transformers": "^3.20.0",
3022
"babel-plugin-react-compiler": "^1.0.0",
3123
"class-variance-authority": "^0.7.1",
@@ -37,7 +29,7 @@
3729
"date-fns": "^4.1.0",
3830
"fumadocs-core": "16.3.2",
3931
"fumadocs-mdx": "14.2.2",
40-
"fumadocs-ui": "16.3.2",
32+
"fumadocs-ui": "npm:@fumadocs/base-ui@latest",
4133
"lucide-react": "^0.562.0",
4234
"motion": "^12.23.26",
4335
"next": "^16.1.0",

apps/web/src/app/(home)/_components/command-section.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,17 @@ export default function CommandSection() {
3838
<span className="font-semibold text-sm">CLI_COMMAND</span>
3939
</div>
4040
<DropdownMenu>
41-
<DropdownMenuTrigger asChild>
42-
<button
43-
type="button"
44-
className="flex items-center gap-2 rounded border border-border px-3 py-1.5 text-xs transition-colors hover:bg-muted/10"
45-
>
46-
<PackageIcon pm={selectedPM} className="h-3 w-3" />
47-
<span>{selectedPM.toUpperCase()}</span>
48-
<ChevronDown className="h-3 w-3" />
49-
</button>
41+
<DropdownMenuTrigger
42+
render={
43+
<button
44+
type="button"
45+
className="flex items-center gap-2 rounded border border-border px-3 py-1.5 text-xs transition-colors hover:bg-muted/10"
46+
/>
47+
}
48+
>
49+
<PackageIcon pm={selectedPM} className="h-3 w-3" />
50+
<span>{selectedPM.toUpperCase()}</span>
51+
<ChevronDown className="h-3 w-3" />
5052
</DropdownMenuTrigger>
5153
<DropdownMenuContent align="end">
5254
{(["bun", "pnpm", "npm"] as const).map((pm) => (

apps/web/src/app/(home)/new/_components/preset-dropdown.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ interface PresetDropdownProps {
1717
export function PresetDropdown({ onApplyPreset }: PresetDropdownProps) {
1818
return (
1919
<DropdownMenu>
20-
<DropdownMenuTrigger asChild>
21-
<button
22-
type="button"
23-
className="flex flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-fd-background px-2 py-1.5 font-medium text-muted-foreground text-xs transition-all hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground"
24-
>
25-
<Zap className="h-3 w-3" />
26-
Presets
27-
<ChevronDown className="ml-auto h-3 w-3" />
28-
</button>
20+
<DropdownMenuTrigger
21+
render={
22+
<button
23+
type="button"
24+
className="flex flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-fd-background px-2 py-1.5 font-medium text-muted-foreground text-xs transition-all hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground"
25+
/>
26+
}
27+
>
28+
<Zap className="h-3 w-3" />
29+
Presets
30+
<ChevronDown className="ml-auto h-3 w-3" />
2931
</DropdownMenuTrigger>
3032
<DropdownMenuContent align="end" className="w-64 bg-fd-background">
3133
{PRESET_TEMPLATES.map((preset) => (

apps/web/src/app/(home)/new/_components/stack-builder.tsx

Lines changed: 138 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,21 @@ const StackBuilder = () => {
5555
const [, setLastChanges] = useState<Array<{ category: string; message: string }>>([]);
5656

5757
const sectionRefs = useRef<Record<string, HTMLElement | null>>({});
58-
const contentRef = useRef<HTMLDivElement>(null);
58+
const contentRef = useRef<HTMLDivElement | null>(null);
59+
const scrollAreaRef = useRef<HTMLDivElement | null>(null);
5960
const lastAppliedStackString = useRef<string>("");
6061

62+
useEffect(() => {
63+
if (scrollAreaRef.current) {
64+
const viewport = scrollAreaRef.current.querySelector<HTMLDivElement>(
65+
'[data-slot="scroll-area-viewport"]',
66+
);
67+
if (viewport) {
68+
contentRef.current = viewport;
69+
}
70+
}
71+
}, [viewMode]);
72+
6173
const compatibilityAnalysis = analyzeStackCompatibility(stack);
6274

6375
const projectNameError = validateProjectName(stack.projectName || "");
@@ -459,15 +471,17 @@ const StackBuilder = () => {
459471
<PresetDropdown onApplyPreset={applyPreset} />
460472

461473
<DropdownMenu>
462-
<DropdownMenuTrigger asChild>
463-
<button
464-
type="button"
465-
className="flex flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-fd-background px-2 py-1.5 font-medium text-muted-foreground text-xs transition-all hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground"
466-
>
467-
<Settings className="h-3 w-3" />
468-
Settings
469-
<ChevronDown className="ml-auto h-3 w-3" />
470-
</button>
474+
<DropdownMenuTrigger
475+
render={
476+
<button
477+
type="button"
478+
className="flex flex-1 items-center justify-center gap-1.5 rounded-md border border-border bg-fd-background px-2 py-1.5 font-medium text-muted-foreground text-xs transition-all hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground"
479+
/>
480+
}
481+
>
482+
<Settings className="h-3 w-3" />
483+
Settings
484+
<ChevronDown className="ml-auto h-3 w-3" />
471485
</DropdownMenuTrigger>
472486
<DropdownMenuContent align="end" className="w-64 bg-fd-background">
473487
<YoloToggle stack={stack} onToggle={(yolo) => setStack({ yolo })} />
@@ -526,97 +540,103 @@ const StackBuilder = () => {
526540
onSelectFile={setSelectedFile}
527541
/>
528542
) : (
529-
<ScrollArea ref={contentRef} className="flex-1 overflow-hidden scroll-smooth">
530-
<main className="p-3 sm:p-4">
531-
{CATEGORY_ORDER.map((categoryKey) => {
532-
const categoryOptions =
533-
TECH_OPTIONS[categoryKey as keyof typeof TECH_OPTIONS] || [];
534-
const categoryDisplayName = getCategoryDisplayName(categoryKey);
535-
536-
const filteredOptions = categoryOptions;
537-
538-
if (filteredOptions.length === 0) return null;
539-
540-
return (
541-
<section
542-
ref={(el) => {
543-
sectionRefs.current[categoryKey] = el;
544-
}}
545-
key={categoryKey}
546-
id={`section-${categoryKey}`}
547-
className="mb-6 scroll-mt-4 sm:mb-8"
548-
>
549-
<div className="mb-3 flex items-center border-border border-b pb-2 text-muted-foreground">
550-
<Terminal className="mr-2 h-4 w-4 shrink-0 sm:h-5 sm:w-5" />
551-
<h2 className="font-semibold text-foreground text-sm sm:text-base">
552-
{categoryDisplayName}
553-
</h2>
554-
{compatibilityAnalysis.notes[categoryKey]?.hasIssue && (
555-
<Tooltip delayDuration={100}>
556-
<TooltipTrigger asChild>
557-
<InfoIcon className="ml-2 h-4 w-4 shrink-0 cursor-help text-muted-foreground" />
558-
</TooltipTrigger>
559-
<TooltipContent side="top" align="start">
560-
<ul className="list-disc space-y-1 pl-4 text-xs">
561-
{compatibilityAnalysis.notes[categoryKey].notes.map((note) => (
562-
<li key={note}>{note}</li>
563-
))}
564-
</ul>
565-
</TooltipContent>
566-
</Tooltip>
567-
)}
568-
</div>
569-
570-
<div className="grid grid-cols-1 xs:grid-cols-2 gap-2 sm:gap-3 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4">
571-
{filteredOptions.map((tech) => {
572-
let isSelected = false;
573-
const category = categoryKey as keyof StackState;
574-
const currentValue = stack[category];
575-
576-
if (
577-
category === "addons" ||
578-
category === "examples" ||
579-
category === "webFrontend" ||
580-
category === "nativeFrontend"
581-
) {
582-
isSelected = ((currentValue as string[]) || []).includes(tech.id);
583-
} else {
584-
isSelected = currentValue === tech.id;
585-
}
586-
587-
const isDisabled = !isOptionCompatible(
588-
stack,
589-
categoryKey as keyof typeof TECH_OPTIONS,
590-
tech.id,
591-
);
592-
593-
const disabledReason = isDisabled
594-
? getDisabledReason(
595-
stack,
596-
categoryKey as keyof typeof TECH_OPTIONS,
597-
tech.id,
598-
)
599-
: null;
600-
601-
return (
602-
<Tooltip key={tech.id} delayDuration={100}>
603-
<TooltipTrigger asChild>
604-
<motion.div
605-
className={cn(
606-
"relative cursor-pointer rounded border p-2 transition-all sm:p-3",
607-
isSelected
608-
? "border-primary bg-primary/10"
609-
: isDisabled
610-
? "border-destructive/30 bg-destructive/5 opacity-50 hover:opacity-75"
611-
: "border-border hover:border-muted hover:bg-muted",
612-
)}
613-
whileHover={{ scale: 1.02 }}
614-
whileTap={{ scale: 0.98 }}
615-
onClick={() =>
616-
handleTechSelect(
617-
categoryKey as keyof typeof TECH_OPTIONS,
618-
tech.id,
619-
)
543+
<div ref={scrollAreaRef} className="flex-1">
544+
<ScrollArea className="h-full overflow-hidden scroll-smooth">
545+
<main className="p-3 sm:p-4">
546+
{CATEGORY_ORDER.map((categoryKey) => {
547+
const categoryOptions =
548+
TECH_OPTIONS[categoryKey as keyof typeof TECH_OPTIONS] || [];
549+
const categoryDisplayName = getCategoryDisplayName(categoryKey);
550+
551+
const filteredOptions = categoryOptions;
552+
553+
if (filteredOptions.length === 0) return null;
554+
555+
return (
556+
<section
557+
ref={(el) => {
558+
sectionRefs.current[categoryKey] = el;
559+
}}
560+
key={categoryKey}
561+
id={`section-${categoryKey}`}
562+
className="mb-6 scroll-mt-4 sm:mb-8"
563+
>
564+
<div className="mb-3 flex items-center border-border border-b pb-2 text-muted-foreground">
565+
<Terminal className="mr-2 h-4 w-4 shrink-0 sm:h-5 sm:w-5" />
566+
<h2 className="font-semibold text-foreground text-sm sm:text-base">
567+
{categoryDisplayName}
568+
</h2>
569+
{compatibilityAnalysis.notes[categoryKey]?.hasIssue && (
570+
<Tooltip delay={100}>
571+
<TooltipTrigger
572+
render={
573+
<InfoIcon className="ml-2 h-4 w-4 shrink-0 cursor-help text-muted-foreground" />
574+
}
575+
/>
576+
<TooltipContent side="top" align="start">
577+
<ul className="list-disc space-y-1 pl-4 text-xs">
578+
{compatibilityAnalysis.notes[categoryKey].notes.map((note) => (
579+
<li key={note}>{note}</li>
580+
))}
581+
</ul>
582+
</TooltipContent>
583+
</Tooltip>
584+
)}
585+
</div>
586+
587+
<div className="grid grid-cols-1 xs:grid-cols-2 gap-2 sm:gap-3 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4">
588+
{filteredOptions.map((tech) => {
589+
let isSelected = false;
590+
const category = categoryKey as keyof StackState;
591+
const currentValue = stack[category];
592+
593+
if (
594+
category === "addons" ||
595+
category === "examples" ||
596+
category === "webFrontend" ||
597+
category === "nativeFrontend"
598+
) {
599+
isSelected = ((currentValue as string[]) || []).includes(tech.id);
600+
} else {
601+
isSelected = currentValue === tech.id;
602+
}
603+
604+
const isDisabled = !isOptionCompatible(
605+
stack,
606+
categoryKey as keyof typeof TECH_OPTIONS,
607+
tech.id,
608+
);
609+
610+
const disabledReason = isDisabled
611+
? getDisabledReason(
612+
stack,
613+
categoryKey as keyof typeof TECH_OPTIONS,
614+
tech.id,
615+
)
616+
: null;
617+
618+
return (
619+
<Tooltip key={tech.id} delay={100}>
620+
<TooltipTrigger
621+
render={
622+
<motion.div
623+
className={cn(
624+
"relative cursor-pointer rounded border p-2 transition-all sm:p-3",
625+
isSelected
626+
? "border-primary bg-primary/10"
627+
: isDisabled
628+
? "border-destructive/30 bg-destructive/5 opacity-50 hover:opacity-75"
629+
: "border-border hover:border-muted hover:bg-muted",
630+
)}
631+
whileHover={{ scale: 1.02 }}
632+
whileTap={{ scale: 0.98 }}
633+
onClick={() =>
634+
handleTechSelect(
635+
categoryKey as keyof typeof TECH_OPTIONS,
636+
tech.id,
637+
)
638+
}
639+
/>
620640
}
621641
>
622642
<div className="flex items-start">
@@ -653,23 +673,23 @@ const StackBuilder = () => {
653673
Default
654674
</span>
655675
)}
656-
</motion.div>
657-
</TooltipTrigger>
658-
{disabledReason && (
659-
<TooltipContent side="top" align="center" className="max-w-xs">
660-
<p className="text-xs">{disabledReason}</p>
661-
</TooltipContent>
662-
)}
663-
</Tooltip>
664-
);
665-
})}
666-
</div>
667-
</section>
668-
);
669-
})}
670-
<div className="h-10" />
671-
</main>
672-
</ScrollArea>
676+
</TooltipTrigger>
677+
{disabledReason && (
678+
<TooltipContent side="top" align="center" className="max-w-xs">
679+
<p className="text-xs">{disabledReason}</p>
680+
</TooltipContent>
681+
)}
682+
</Tooltip>
683+
);
684+
})}
685+
</div>
686+
</section>
687+
);
688+
})}
689+
<div className="h-10" />
690+
</main>
691+
</ScrollArea>
692+
</div>
673693
)}
674694
</div>
675695
</div>

0 commit comments

Comments
 (0)