Next.js Supabase V3 (#463)

Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
This commit is contained in:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View File

@@ -120,9 +120,7 @@ export function AlertDialogStory() {
const generateCode = () => {
let code = `<AlertDialog>\n`;
code += ` <AlertDialogTrigger asChild>\n`;
code += ` <Button variant="${controls.triggerVariant}">${controls.triggerText}</Button>\n`;
code += ` </AlertDialogTrigger>\n`;
code += ` <AlertDialogTrigger render={<Button variant="${controls.triggerVariant}">${controls.triggerText}</Button>} />\n`;
code += ` <AlertDialogContent>\n`;
code += ` <AlertDialogHeader>\n`;
@@ -179,11 +177,14 @@ export function AlertDialogStory() {
const renderPreview = () => {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={controls.triggerVariant}>
{controls.triggerText}
</Button>
</AlertDialogTrigger>
<AlertDialogTrigger
render={
<Button variant={controls.triggerVariant}>
{controls.triggerText}
</Button>
}
/>
<AlertDialogContent>
<AlertDialogHeader>
{controls.withIcon ? (
@@ -341,11 +342,11 @@ export function AlertDialogStory() {
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-3">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm">
<Trash2 className="mr-2 h-4 w-4" />
Delete Item
</Button>
<AlertDialogTrigger
render={<Button variant="destructive" size="sm" />}
>
<Trash2 className="mr-2 h-4 w-4" />
Delete Item
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -370,11 +371,9 @@ export function AlertDialogStory() {
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">
<LogOut className="mr-2 h-4 w-4" />
Sign Out
</Button>
<AlertDialogTrigger render={<Button variant="outline" />}>
<LogOut className="mr-2 h-4 w-4" />
Sign Out
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -397,11 +396,9 @@ export function AlertDialogStory() {
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">
<UserX className="mr-2 h-4 w-4" />
Remove User
</Button>
<AlertDialogTrigger render={<Button variant="outline" />}>
<UserX className="mr-2 h-4 w-4" />
Remove User
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -438,11 +435,9 @@ export function AlertDialogStory() {
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-3">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">
<Archive className="mr-2 h-4 w-4" />
Archive Project
</Button>
<AlertDialogTrigger render={<Button variant="outline" />}>
<Archive className="mr-2 h-4 w-4" />
Archive Project
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -465,11 +460,9 @@ export function AlertDialogStory() {
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button>
<Download className="mr-2 h-4 w-4" />
Export Data
</Button>
<AlertDialogTrigger render={<Button />}>
<Download className="mr-2 h-4 w-4" />
Export Data
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -493,11 +486,9 @@ export function AlertDialogStory() {
</AlertDialog>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">
<RefreshCw className="mr-2 h-4 w-4" />
Reset Settings
</Button>
<AlertDialogTrigger render={<Button variant="outline" />}>
<RefreshCw className="mr-2 h-4 w-4" />
Reset Settings
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -535,11 +526,11 @@ export function AlertDialogStory() {
<div className="space-y-3">
<h4 className="text-sm font-semibold">Error/Destructive</h4>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm">
<Trash2 className="mr-2 h-4 w-4" />
Delete Forever
</Button>
<AlertDialogTrigger
render={<Button variant="destructive" size="sm" />}
>
<Trash2 className="mr-2 h-4 w-4" />
Delete Forever
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -567,11 +558,11 @@ export function AlertDialogStory() {
<div className="space-y-3">
<h4 className="text-sm font-semibold">Warning</h4>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline" size="sm">
<AlertTriangle className="mr-2 h-4 w-4" />
Unsaved Changes
</Button>
<AlertDialogTrigger
render={<Button variant="outline" size="sm" />}
>
<AlertTriangle className="mr-2 h-4 w-4" />
Unsaved Changes
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -597,11 +588,11 @@ export function AlertDialogStory() {
<div className="space-y-3">
<h4 className="text-sm font-semibold">Info</h4>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline" size="sm">
<Share className="mr-2 h-4 w-4" />
Share Publicly
</Button>
<AlertDialogTrigger
render={<Button variant="outline" size="sm" />}
>
<Share className="mr-2 h-4 w-4" />
Share Publicly
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -627,11 +618,9 @@ export function AlertDialogStory() {
<div className="space-y-3">
<h4 className="text-sm font-semibold">Success</h4>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm">
<Download className="mr-2 h-4 w-4" />
Complete Setup
</Button>
<AlertDialogTrigger render={<Button size="sm" />}>
<Download className="mr-2 h-4 w-4" />
Complete Setup
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
@@ -850,10 +839,8 @@ export function AlertDialogStory() {
<h4 className="text-sm font-semibold">Focus Management</h4>
<p className="text-muted-foreground text-sm">
Focus moves to Cancel button by default
<br />
Tab navigation between Cancel and Action
<br />
Escape key activates Cancel action
<br /> Tab navigation between Cancel and Action
<br /> Escape key activates Cancel action
<br /> Enter key activates Action button when focused
</p>
</div>
@@ -861,10 +848,8 @@ export function AlertDialogStory() {
<h4 className="text-sm font-semibold">Content Guidelines</h4>
<p className="text-muted-foreground text-sm">
Use clear, specific titles and descriptions
<br />
Explain consequences of the action
<br />
Use action-specific button labels
<br /> Explain consequences of the action
<br /> Use action-specific button labels
<br /> Always provide a way to cancel
</p>
</div>
@@ -872,8 +857,7 @@ export function AlertDialogStory() {
<h4 className="text-sm font-semibold">Visual Design</h4>
<p className="text-muted-foreground text-sm">
Use appropriate icons and colors for severity
<br />
Make destructive actions visually distinct
<br /> Make destructive actions visually distinct
<br /> Ensure sufficient contrast for all text
</p>
</div>
@@ -892,8 +876,7 @@ export function AlertDialogStory() {
<h4 className="text-sm font-semibold">Title Guidelines</h4>
<p className="text-muted-foreground text-sm">
Be specific about the action (not just "Are you sure?")
<br />
Use active voice ("Delete account" not "Account deletion")
<br /> Use active voice ("Delete account" not "Account deletion")
<br /> Keep it concise but descriptive
</p>
</div>
@@ -901,10 +884,8 @@ export function AlertDialogStory() {
<h4 className="text-sm font-semibold">Description Guidelines</h4>
<p className="text-muted-foreground text-sm">
Explain what will happen
<br />
Mention if the action is irreversible
<br />
Provide context about consequences
<br /> Mention if the action is irreversible
<br /> Provide context about consequences
<br /> Use plain, non-technical language
</p>
</div>
@@ -912,10 +893,8 @@ export function AlertDialogStory() {
<h4 className="text-sm font-semibold">Button Labels</h4>
<p className="text-muted-foreground text-sm">
Use specific verbs ("Delete", "Save", "Continue")
<br />
Match the action being performed
<br />
Avoid generic labels when possible
<br /> Match the action being performed
<br /> Avoid generic labels when possible
<br /> Make the primary action clear
</p>
</div>

View File

@@ -33,7 +33,6 @@ interface ButtonControls {
loading: boolean;
withIcon: boolean;
fullWidth: boolean;
asChild: boolean;
}
const variantOptions = [
@@ -68,7 +67,6 @@ export function ButtonStory() {
loading: false,
withIcon: false,
fullWidth: false,
asChild: false,
});
const generateCode = () => {
@@ -77,14 +75,12 @@ export function ButtonStory() {
variant: controls.variant,
size: controls.size,
disabled: controls.disabled,
asChild: controls.asChild,
className: controls.fullWidth ? 'w-full' : '',
},
{
variant: 'default',
size: 'default',
disabled: false,
asChild: false,
className: '',
},
);
@@ -194,15 +190,6 @@ export function ButtonStory() {
onCheckedChange={(checked) => updateControl('fullWidth', checked)}
/>
</div>
<div className="flex items-center justify-between">
<Label htmlFor="asChild">As Child</Label>
<Switch
id="asChild"
checked={controls.asChild}
onCheckedChange={(checked) => updateControl('asChild', checked)}
/>
</div>
</>
);

View File

@@ -276,11 +276,11 @@ export default function CalendarStory() {
<Card>
<CardContent className="flex justify-center pt-6">
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="justify-start">
<CalendarIcon className="mr-2 h-4 w-4" />
Pick a date
</Button>
<PopoverTrigger
render={<Button variant="outline" className="justify-start" />}
>
<CalendarIcon className="mr-2 h-4 w-4" />
Pick a date
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar

View File

@@ -320,10 +320,12 @@ export function CardButtonStory() {
</thead>
<tbody>
<tr className="border-b">
<td className="p-3 font-mono text-sm">asChild</td>
<td className="p-3 font-mono text-sm">boolean</td>
<td className="p-3 font-mono text-sm">false</td>
<td className="p-3">Render as child element</td>
<td className="p-3 font-mono text-sm">render</td>
<td className="p-3 font-mono text-sm">
React.ReactElement
</td>
<td className="p-3 font-mono text-sm">-</td>
<td className="p-3">Compose with a custom element</td>
</tr>
<tr className="border-b">
<td className="p-3 font-mono text-sm">className</td>

View File

@@ -139,8 +139,8 @@ export function DialogStory() {
});
let code = `<Dialog>\n`;
code += ` <DialogTrigger asChild>\n`;
code += ` <Button variant="${controls.triggerVariant}">${controls.triggerText}</Button>\n`;
code += ` <DialogTrigger render={<Button variant="${controls.triggerVariant}" />}>\n`;
code += ` ${controls.triggerText}\n`;
code += ` </DialogTrigger>\n`;
code += ` <DialogContent${contentPropsString}>\n`;
code += ` <DialogHeader>\n`;
@@ -182,8 +182,8 @@ export function DialogStory() {
if (controls.withFooter) {
code += ` <DialogFooter>\n`;
code += ` <DialogClose asChild>\n`;
code += ` <Button variant="outline">Cancel</Button>\n`;
code += ` <DialogClose render={<Button variant="outline" />}>\n`;
code += ` Cancel\n`;
code += ` </DialogClose>\n`;
code += ` <Button>Save Changes</Button>\n`;
code += ` </DialogFooter>\n`;
@@ -198,10 +198,8 @@ export function DialogStory() {
const renderPreview = () => {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant={controls.triggerVariant}>
{controls.triggerText}
</Button>
<DialogTrigger render={<Button variant={controls.triggerVariant} />}>
{controls.triggerText}
</DialogTrigger>
<DialogContent
className={cn(
@@ -271,8 +269,8 @@ export function DialogStory() {
{controls.withFooter && (
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button>Save Changes</Button>
</DialogFooter>
@@ -391,11 +389,9 @@ export function DialogStory() {
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">
<Info className="mr-2 h-4 w-4" />
Info Dialog
</Button>
<DialogTrigger render={<Button variant="outline" />}>
<Info className="mr-2 h-4 w-4" />
Info Dialog
</DialogTrigger>
<DialogContent>
<DialogHeader>
@@ -412,19 +408,15 @@ export function DialogStory() {
</p>
</div>
<DialogFooter>
<DialogClose asChild>
<Button>Got it</Button>
</DialogClose>
<DialogClose render={<Button />}>Got it</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button>
<Edit className="mr-2 h-4 w-4" />
Edit Profile
</Button>
<DialogTrigger render={<Button />}>
<Edit className="mr-2 h-4 w-4" />
Edit Profile
</DialogTrigger>
<DialogContent>
<DialogHeader>
@@ -456,8 +448,8 @@ export function DialogStory() {
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button>Save Changes</Button>
</DialogFooter>
@@ -465,11 +457,9 @@ export function DialogStory() {
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">
<Settings className="mr-2 h-4 w-4" />
Settings
</Button>
<DialogTrigger render={<Button variant="secondary" />}>
<Settings className="mr-2 h-4 w-4" />
Settings
</DialogTrigger>
<DialogContent>
<DialogHeader>
@@ -499,8 +489,8 @@ export function DialogStory() {
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button>Save</Button>
</DialogFooter>
@@ -518,10 +508,8 @@ export function DialogStory() {
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
Small Dialog
</Button>
<DialogTrigger render={<Button variant="outline" size="sm" />}>
Small Dialog
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
@@ -536,16 +524,14 @@ export function DialogStory() {
</p>
</div>
<DialogFooter>
<DialogClose asChild>
<Button>Close</Button>
</DialogClose>
<DialogClose render={<Button />}>Close</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Large Dialog</Button>
<DialogTrigger render={<Button variant="outline" />}>
Large Dialog
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
@@ -571,8 +557,8 @@ export function DialogStory() {
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button>Save</Button>
</DialogFooter>
@@ -590,11 +576,9 @@ export function DialogStory() {
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">
<Image className="mr-2 h-4 w-4" />
Image Gallery
</Button>
<DialogTrigger render={<Button variant="outline" />}>
<Image className="mr-2 h-4 w-4" />
Image Gallery
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
@@ -627,11 +611,9 @@ export function DialogStory() {
</Dialog>
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">
<MessageSquare className="mr-2 h-4 w-4" />
Feedback
</Button>
<DialogTrigger render={<Button variant="outline" />}>
<MessageSquare className="mr-2 h-4 w-4" />
Feedback
</DialogTrigger>
<DialogContent>
<DialogHeader>
@@ -668,8 +650,8 @@ export function DialogStory() {
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button>
<MessageSquare className="mr-2 h-4 w-4" />
@@ -736,8 +718,8 @@ export function DialogStory() {
<div>
<h4 className="mb-3 text-lg font-semibold">DialogTrigger</h4>
<p className="text-muted-foreground mb-3 text-sm">
The element that opens the dialog. Use asChild prop to render as
child element.
The element that opens the dialog. Use the render prop to compose
with a custom element.
</p>
</div>
@@ -840,10 +822,8 @@ export function DialogStory() {
<h4 className="text-sm font-semibold">Focus Management</h4>
<p className="text-muted-foreground text-sm">
Focus moves to dialog when opened
<br />
Focus returns to trigger when closed
<br />
Tab navigation stays within dialog
<br /> Focus returns to trigger when closed
<br /> Tab navigation stays within dialog
<br /> Escape key closes the dialog
</p>
</div>

View File

@@ -2,7 +2,7 @@
import { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import { Code2, FileText, Search } from 'lucide-react';
@@ -35,6 +35,7 @@ export function DocsSidebar({
selectedCategory,
}: DocsSidebarProps) {
const [searchQuery, setSearchQuery] = useState('');
const searchParams = useSearchParams();
const router = useRouter();
const filteredComponents = COMPONENTS_REGISTRY.filter((c) =>
@@ -50,21 +51,21 @@ export function DocsSidebar({
.sort((a, b) => a.name.localeCompare(b.name));
const onCategorySelect = (category: string | null) => {
const searchParams = new URLSearchParams(window.location.search);
searchParams.set('category', category || '');
router.push(`/components?${searchParams.toString()}`);
const sp = new URLSearchParams(searchParams);
sp.set('category', category || '');
router.push(`/components?${sp.toString()}`);
};
const onComponentSelect = (component: ComponentInfo) => {
const searchParams = new URLSearchParams(window.location.search);
searchParams.set('component', component.name);
router.push(`/components?${searchParams.toString()}`);
const sp = new URLSearchParams(searchParams);
sp.set('component', component.name);
router.push(`/components?${sp.toString()}`);
};
return (
<div className="bg-muted/30 flex h-screen w-80 flex-col overflow-hidden border-r">
{/* Header */}
<div className="flex-shrink-0 border-b p-4">
<div className="shrink-0 border-b p-4">
<div className="mb-2 flex items-center gap-2">
<Code2 className="text-primary h-6 w-6" />
@@ -77,13 +78,14 @@ export function DocsSidebar({
</div>
{/* Controls */}
<div className="flex-shrink-0 space-y-2 border-b p-4">
<div className="shrink-0 space-y-2 border-b p-4">
{/* Category Select */}
<div className="space-y-2">
<Select
value={selectedCategory || 'all'}
defaultValue={selectedCategory || 'all'}
onValueChange={(value) => {
const category = value === 'all' ? null : value;
onCategorySelect(category);
// Select first component in the filtered results
@@ -96,8 +98,12 @@ export function DocsSidebar({
}
}}
>
<SelectTrigger>
<SelectValue placeholder={'Select a category'} />
<SelectTrigger className="w-full">
<SelectValue>
{(category) => {
return category === 'all' ? 'All Categories' : category;
}}
</SelectValue>
</SelectTrigger>
<SelectContent>
@@ -154,7 +160,7 @@ export function DocsSidebar({
{/* Components List - Scrollable */}
<div className="flex flex-1 flex-col overflow-y-auto">
<div className="flex-shrink-0 p-4 pb-2">
<div className="shrink-0 p-4 pb-2">
<h3 className="flex items-center gap-2 text-sm font-semibold">
<FileText className="h-4 w-4" />
Components

View File

@@ -101,13 +101,18 @@ const examples = [
return (
<div className="flex min-h-32 items-center justify-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarImage src="/avatars/01.png" alt="@username" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
</Button>
<DropdownMenuTrigger
render={
<Button
variant="ghost"
className="relative h-8 w-8 rounded-full"
/>
}
>
<Avatar className="h-8 w-8">
<AvatarImage src="/avatars/01.png" alt="@username" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
@@ -185,11 +190,11 @@ const examples = [
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
<DropdownMenuTrigger
render={<Button variant="ghost" className="h-8 w-8 p-0" />}
>
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuItem onClick={() => setSelectedAction('open')}>
@@ -275,11 +280,9 @@ const examples = [
return (
<div className="flex min-h-48 items-center justify-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Plus className="mr-2 h-4 w-4" />
Create New
</Button>
<DropdownMenuTrigger render={<Button variant="outline" />}>
<Plus className="mr-2 h-4 w-4" />
Create New
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Create Content</DropdownMenuLabel>
@@ -393,11 +396,11 @@ const examples = [
<span className="text-sm">Appearance & Layout</span>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
<Settings className="mr-2 h-4 w-4" />
Configure
</Button>
<DropdownMenuTrigger
render={<Button variant="outline" size="sm" />}
>
<Settings className="mr-2 h-4 w-4" />
Configure
</DropdownMenuTrigger>
<DropdownMenuContent className="w-64" align="end">
<DropdownMenuLabel>View Options</DropdownMenuLabel>
@@ -547,10 +550,10 @@ const examples = [
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
<DropdownMenuTrigger
render={<Button variant="ghost" className="h-8 w-8 p-0" />}
>
<MoreHorizontal className="h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuItem
@@ -863,7 +866,7 @@ export default function DropdownMenuStory() {
modal: controls.modal ? true : undefined,
});
const dropdownStructure = `<DropdownMenu${rootProps}>\n <DropdownMenuTrigger asChild>\n <Button variant="outline">Open Menu</Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent${contentProps}>\n <DropdownMenuItem>\n <User className="mr-2 h-4 w-4" />\n <span>Profile</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Settings className="mr-2 h-4 w-4" />\n <span>Settings</span>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <LogOut className="mr-2 h-4 w-4" />\n <span>Log out</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>`;
const dropdownStructure = `<DropdownMenu${rootProps}>\n <DropdownMenuTrigger render={<Button variant="outline" />}>\n Open Menu\n </DropdownMenuTrigger>\n <DropdownMenuContent${contentProps}>\n <DropdownMenuItem>\n <User className="mr-2 h-4 w-4" />\n <span>Profile</span>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Settings className="mr-2 h-4 w-4" />\n <span>Settings</span>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <LogOut className="mr-2 h-4 w-4" />\n <span>Log out</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>`;
return `${importStatement}\n${buttonImport}\n${iconImport}\n\n${dropdownStructure}`;
};
@@ -971,8 +974,8 @@ export default function DropdownMenuStory() {
const previewContent = (
<div className="flex justify-center p-6">
<DropdownMenu modal={controls.modal}>
<DropdownMenuTrigger asChild>
<Button variant="outline">Open Menu</Button>
<DropdownMenuTrigger render={<Button variant="outline" />}>
Open Menu
</DropdownMenuTrigger>
<DropdownMenuContent
side={controls.side}

View File

@@ -4,7 +4,7 @@ import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import * as z from 'zod';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
@@ -119,7 +119,7 @@ export default function FormStory() {
const formImport = generateImportStatement(formComponents, '@kit/ui/form');
const inputImport = generateImportStatement(['Input'], '@kit/ui/input');
const buttonImport = generateImportStatement(['Button'], '@kit/ui/button');
const hookFormImports = `import { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { z } from 'zod';`;
const hookFormImports = `import { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport * as z from 'zod';`;
let schemaCode = '';
let formFieldsCode = '';
@@ -130,19 +130,19 @@ export default function FormStory() {
formFieldsCode = ` <FormField\n control={form.control}\n name="username"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Username</FormLabel>\n <FormControl>\n <Input ${controls.disabled ? 'disabled ' : ''}placeholder="Enter username" {...field} />\n </FormControl>${controls.showDescriptions ? '\n <FormDescription>\n Your public display name.\n </FormDescription>' : ''}${controls.showValidation ? '\n <FormMessage />' : ''}\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name="email"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Email</FormLabel>\n <FormControl>\n <Input ${controls.disabled ? 'disabled ' : ''}type="email" placeholder="Enter email" {...field} />\n </FormControl>${controls.showDescriptions ? "\n <FormDescription>\n We'll never share your email.\n </FormDescription>" : ''}${controls.showValidation ? '\n <FormMessage />' : ''}\n </FormItem>\n )}\n />`;
onSubmitCode = ` function onSubmit(values: z.infer<typeof formSchema>) {\n console.log('Form submitted:', values);\n }`;
onSubmitCode = ` function onSubmit(values: z.output<typeof formSchema>) {\n console.log('Form submitted:', values);\n }`;
} else if (controls.formType === 'advanced') {
schemaCode = `const formSchema = z.object({\n firstName: z.string().min(1, 'First name is required.'),\n lastName: z.string().min(1, 'Last name is required.'),\n email: z.string().email('Please enter a valid email address.'),\n});`;
formFieldsCode = ` <FormField\n control={form.control}\n name="firstName"\n render={({ field }) => (\n <FormItem>\n <FormLabel>First Name</FormLabel>\n <FormControl>\n <Input ${controls.disabled ? 'disabled ' : ''}placeholder="John" {...field} />\n </FormControl>${controls.showValidation ? '\n <FormMessage />' : ''}\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name="lastName"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Last Name</FormLabel>\n <FormControl>\n <Input ${controls.disabled ? 'disabled ' : ''}placeholder="Doe" {...field} />\n </FormControl>${controls.showValidation ? '\n <FormMessage />' : ''}\n </FormItem>\n )}\n />`;
onSubmitCode = ` function onSubmit(values: z.infer<typeof formSchema>) {\n console.log('Advanced form submitted:', values);\n }`;
onSubmitCode = ` function onSubmit(values: z.output<typeof formSchema>) {\n console.log('Advanced form submitted:', values);\n }`;
} else {
schemaCode = `const formSchema = z.object({\n password: z.string().min(8, 'Password must be at least 8 characters.'),\n confirmPassword: z.string(),\n}).refine((data) => data.password === data.confirmPassword, {\n message: 'Passwords do not match.',\n path: ['confirmPassword'],\n});`;
formFieldsCode = ` <FormField\n control={form.control}\n name="password"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Password</FormLabel>\n <FormControl>\n <Input ${controls.disabled ? 'disabled ' : ''}type="password" {...field} />\n </FormControl>${controls.showValidation ? '\n <FormMessage />' : ''}\n </FormItem>\n )}\n />\n <FormField\n control={form.control}\n name="confirmPassword"\n render={({ field }) => (\n <FormItem>\n <FormLabel>Confirm Password</FormLabel>\n <FormControl>\n <Input ${controls.disabled ? 'disabled ' : ''}type="password" {...field} />\n </FormControl>${controls.showValidation ? '\n <FormMessage />' : ''}\n </FormItem>\n )}\n />`;
onSubmitCode = ` function onSubmit(values: z.infer<typeof formSchema>) {\n console.log('Validation form submitted:', values);\n }`;
onSubmitCode = ` function onSubmit(values: z.output<typeof formSchema>) {\n console.log('Validation form submitted:', values);\n }`;
}
const defaultValuesCode =
@@ -152,13 +152,13 @@ export default function FormStory() {
? ` defaultValues: {\n firstName: '',\n lastName: '',\n email: '',\n },`
: ` defaultValues: {\n password: '',\n confirmPassword: '',\n },`;
const fullFormCode = `${hookFormImports}\n${formImport}\n${inputImport}\n${buttonImport}\n\n${schemaCode}\n\nfunction MyForm() {\n const form = useForm<z.infer<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n${defaultValuesCode}\n });\n\n${onSubmitCode}\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">\n${formFieldsCode}\n <Button type="submit"${controls.disabled ? ' disabled' : ''}>Submit</Button>\n </form>\n </Form>\n );\n}`;
const fullFormCode = `${hookFormImports}\n${formImport}\n${inputImport}\n${buttonImport}\n\n${schemaCode}\n\nfunction MyForm() {\n const form = useForm<z.output<typeof formSchema>>({\n resolver: zodResolver(formSchema),\n${defaultValuesCode}\n });\n\n${onSubmitCode}\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">\n${formFieldsCode}\n <Button type="submit"${controls.disabled ? ' disabled' : ''}>Submit</Button>\n </form>\n </Form>\n );\n}`;
return fullFormCode;
};
// Basic form
const basicForm = useForm<z.infer<typeof basicFormSchema>>({
const basicForm = useForm<z.output<typeof basicFormSchema>>({
resolver: zodResolver(basicFormSchema),
defaultValues: {
username: '',
@@ -169,7 +169,7 @@ export default function FormStory() {
});
// Advanced form
const advancedForm = useForm<z.infer<typeof advancedFormSchema>>({
const advancedForm = useForm<z.output<typeof advancedFormSchema>>({
resolver: zodResolver(advancedFormSchema),
defaultValues: {
firstName: '',
@@ -183,7 +183,7 @@ export default function FormStory() {
});
// Validation form
const validationForm = useForm<z.infer<typeof validationFormSchema>>({
const validationForm = useForm<z.output<typeof validationFormSchema>>({
resolver: zodResolver(validationFormSchema),
defaultValues: {
password: '',
@@ -1056,7 +1056,7 @@ export default function FormStory() {
<pre className="overflow-x-auto text-sm">
{`import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import * as z from 'zod';
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@kit/ui/form';
const formSchema = z.object({
@@ -1065,7 +1065,7 @@ const formSchema = z.object({
});
function MyForm() {
const form = useForm<z.infer<typeof formSchema>>({
const form = useForm<z.output<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: '',
@@ -1073,7 +1073,7 @@ function MyForm() {
},
});
function onSubmit(values: z.infer<typeof formSchema>) {
function onSubmit(values: z.output<typeof formSchema>) {
console.log(values);
}

View File

@@ -99,7 +99,7 @@ export function KbdStory() {
let snippet = groupLines.join('\n');
if (controls.showTooltip) {
snippet = `<TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button variant="outline">Command palette</Button>\n </TooltipTrigger>\n <TooltipContent className="flex items-center gap-2">\n <span>Press</span>\n ${groupLines.join('\n ')}\n </TooltipContent>\n </Tooltip>\n</TooltipProvider>`;
snippet = `<TooltipProvider>\n <Tooltip>\n <TooltipTrigger render={<Button variant="outline" />}>\n Command palette\n </TooltipTrigger>\n <TooltipContent className="flex items-center gap-2">\n <span>Press</span>\n ${groupLines.join('\n ')}\n </TooltipContent>\n </Tooltip>\n</TooltipProvider>`;
}
return formatCodeBlock(snippet, [
@@ -115,11 +115,11 @@ export function KbdStory() {
{controls.showTooltip ? (
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" className="gap-2">
<Command className="h-4 w-4" />
Command palette
</Button>
<TooltipTrigger
render={<Button variant="outline" className="gap-2" />}
>
<Command className="h-4 w-4" />
Command palette
</TooltipTrigger>
<TooltipContent className="flex items-center gap-2">
<span>Press</span>

View File

@@ -763,10 +763,8 @@ export function SelectStory() {
<h4 className="text-sm font-semibold">Keyboard Navigation</h4>
<p className="text-muted-foreground text-sm">
Space/Enter opens the select
<br />
Arrow keys navigate options
<br />
Escape closes the dropdown
<br /> Arrow keys navigate options
<br /> Escape closes the dropdown
<br /> Type to search/filter options
</p>
</div>

View File

@@ -136,11 +136,13 @@ export function SimpleDataTableStory() {
{controls.showActions && (
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
<DropdownMenuTrigger
render={
<Button variant="ghost" className="h-8 w-8 p-0" />
}
>
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem

View File

@@ -100,7 +100,7 @@ export function SwitchStory() {
className: cn(
controls.size === 'sm' && 'h-4 w-7',
controls.size === 'lg' && 'h-6 w-11',
controls.error && 'data-[state=checked]:bg-destructive',
controls.error && 'data-checked:bg-destructive',
),
};
@@ -200,7 +200,7 @@ export function SwitchStory() {
className={cn(
controls.size === 'sm' && 'h-4 w-7',
controls.size === 'lg' && 'h-6 w-11',
controls.error && 'data-[state=checked]:bg-destructive',
controls.error && 'data-checked:bg-destructive',
)}
/>
);
@@ -616,7 +616,7 @@ export function SwitchStory() {
</Label>
<Switch
id="error-switch"
className="data-[state=checked]:bg-destructive"
className="data-checked:bg-destructive"
/>
</div>
<p className="text-destructive text-sm">
@@ -642,7 +642,7 @@ export function SwitchStory() {
<div>
<h4 className="mb-3 text-lg font-semibold">Switch</h4>
<p className="text-muted-foreground mb-3 text-sm">
A toggle switch component for boolean states. Built on Radix UI
A toggle switch component for boolean states. Built on Base UI
Switch primitive.
</p>
<div className="overflow-x-auto">
@@ -792,8 +792,7 @@ export function SwitchStory() {
<h4 className="text-sm font-semibold">Keyboard Navigation</h4>
<p className="text-muted-foreground text-sm">
Tab to focus the switch
<br />
Space or Enter to toggle state
<br /> Space or Enter to toggle state
<br /> Arrow keys when part of a radio group
</p>
</div>

View File

@@ -62,9 +62,9 @@ interface TabsControlsProps {
const variantClasses = {
default: '',
pills:
'[&>div]:bg-background [&>div]:border [&>div]:rounded-lg [&>div]:p-1 [&_button]:rounded-md [&_button[data-state=active]]:bg-primary [&_button[data-state=active]]:text-primary-foreground',
'[&>div]:bg-background [&>div]:border [&>div]:rounded-lg [&>div]:p-1 [&_button]:rounded-md [&_button[data-active]]:bg-primary [&_button[data-active]]:text-primary-foreground',
underline:
'[&>div]:bg-transparent [&>div]:border-b [&>div]:rounded-none [&_button]:rounded-none [&_button]:border-b-2 [&_button]:border-transparent [&_button[data-state=active]]:border-primary [&_button[data-state=active]]:bg-transparent',
'[&>div]:bg-transparent [&>div]:border-b [&>div]:rounded-none [&_button]:rounded-none [&_button]:border-b-2 [&_button]:border-transparent [&_button[data-active]]:border-primary [&_button[data-active]]:bg-transparent',
};
const sizeClasses = {
@@ -683,28 +683,28 @@ function App() {
<TabsList className="h-auto rounded-none border-b bg-transparent p-0">
<TabsTrigger
value="overview"
className="data-[state=active]:border-primary rounded-none border-b-2 border-transparent data-[state=active]:bg-transparent"
className="data-active:border-primary rounded-none border-b-2 border-transparent data-active:bg-transparent"
>
<BarChart3 className="mr-2 h-4 w-4" />
Overview
</TabsTrigger>
<TabsTrigger
value="users"
className="data-[state=active]:border-primary rounded-none border-b-2 border-transparent data-[state=active]:bg-transparent"
className="data-active:border-primary rounded-none border-b-2 border-transparent data-active:bg-transparent"
>
<User className="mr-2 h-4 w-4" />
Users
</TabsTrigger>
<TabsTrigger
value="revenue"
className="data-[state=active]:border-primary rounded-none border-b-2 border-transparent data-[state=active]:bg-transparent"
className="data-active:border-primary rounded-none border-b-2 border-transparent data-active:bg-transparent"
>
<CreditCard className="mr-2 h-4 w-4" />
Revenue
</TabsTrigger>
<TabsTrigger
value="reports"
className="data-[state=active]:border-primary rounded-none border-b-2 border-transparent data-[state=active]:bg-transparent"
className="data-active:border-primary rounded-none border-b-2 border-transparent data-active:bg-transparent"
>
<FileText className="mr-2 h-4 w-4" />
Reports
@@ -905,8 +905,7 @@ const apiReference = {
{
name: '...props',
type: 'React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>',
description:
'All props from Radix UI Tabs.Root component including asChild, id, etc.',
description: 'All additional props from Base UI Tabs.Root component.',
},
],
examples: [

View File

@@ -144,22 +144,23 @@ function TooltipStory() {
let code = `<TooltipProvider${providerPropsString}>\n`;
code += ` <Tooltip>\n`;
code += ` <TooltipTrigger asChild>\n`;
if (controls.triggerType === 'button') {
code += ` <Button variant="${controls.triggerVariant}">Hover me</Button>\n`;
code += ` <TooltipTrigger render={<Button variant="${controls.triggerVariant}" />}>\n`;
code += ` Hover me\n`;
} else if (controls.triggerType === 'icon') {
code += ` <Button variant="${controls.triggerVariant}" size="icon">\n`;
const iconName = selectedIconData?.icon.name || 'Info';
code += ` <${iconName} className="h-4 w-4" />\n`;
code += ` </Button>\n`;
code += ` <TooltipTrigger render={<Button variant="${controls.triggerVariant}" size="icon" />}>\n`;
code += ` <${iconName} className="h-4 w-4" />\n`;
} else if (controls.triggerType === 'text') {
code += ` <span className="cursor-help underline decoration-dotted">Hover me</span>\n`;
code += ` <TooltipTrigger render={<span className="cursor-help underline decoration-dotted" />}>\n`;
code += ` Hover me\n`;
} else if (controls.triggerType === 'input') {
code += ` <Input placeholder="Hover over this input" />\n`;
code += ` <TooltipTrigger render={<Input placeholder="Hover over this input" />} />\n`;
}
code += ` </TooltipTrigger>\n`;
if (controls.triggerType !== 'input') {
code += ` </TooltipTrigger>\n`;
}
code += ` <TooltipContent${contentPropsString}>\n`;
code += ` <p>${controls.content}</p>\n`;
code += ` </TooltipContent>\n`;
@@ -170,28 +171,50 @@ function TooltipStory() {
};
const renderPreview = () => {
const trigger = (() => {
const renderTrigger = () => {
switch (controls.triggerType) {
case 'button':
return <Button variant={controls.triggerVariant}>Hover me</Button>;
return (
<TooltipTrigger
render={<Button variant={controls.triggerVariant} />}
>
Hover me
</TooltipTrigger>
);
case 'icon':
return (
<Button variant={controls.triggerVariant} size="icon">
<TooltipTrigger
render={<Button variant={controls.triggerVariant} size="icon" />}
>
<IconComponent className="h-4 w-4" />
</Button>
</TooltipTrigger>
);
case 'text':
return (
<span className="cursor-help underline decoration-dotted">
<TooltipTrigger
render={
<span className="cursor-help underline decoration-dotted" />
}
>
Hover me
</span>
</TooltipTrigger>
);
case 'input':
return <Input placeholder="Hover over this input" />;
return (
<TooltipTrigger
render={<Input placeholder="Hover over this input" />}
/>
);
default:
return <Button variant={controls.triggerVariant}>Hover me</Button>;
return (
<TooltipTrigger
render={<Button variant={controls.triggerVariant} />}
>
Hover me
</TooltipTrigger>
);
}
})();
};
return (
<div className="flex min-h-[200px] items-center justify-center">
@@ -201,7 +224,7 @@ function TooltipStory() {
disableHoverableContent={controls.disableHoverableContent}
>
<Tooltip>
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
{renderTrigger()}
<TooltipContent
side={controls.side}
align={controls.align}
@@ -376,11 +399,9 @@ function TooltipStory() {
<TooltipProvider>
<div className="flex flex-wrap gap-4">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">
<Info className="mr-2 h-4 w-4" />
Info Button
</Button>
<TooltipTrigger render={<Button variant="outline" />}>
<Info className="mr-2 h-4 w-4" />
Info Button
</TooltipTrigger>
<TooltipContent>
<p>This provides additional information</p>
@@ -388,10 +409,8 @@ function TooltipStory() {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<HelpCircle className="h-4 w-4" />
</Button>
<TooltipTrigger render={<Button variant="ghost" size="icon" />}>
<HelpCircle className="h-4 w-4" />
</TooltipTrigger>
<TooltipContent>
<p>Click for help documentation</p>
@@ -399,10 +418,12 @@ function TooltipStory() {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-help underline decoration-dotted">
Hover for explanation
</span>
<TooltipTrigger
render={
<span className="cursor-help underline decoration-dotted" />
}
>
Hover for explanation
</TooltipTrigger>
<TooltipContent>
<p>This term needs clarification for better understanding</p>
@@ -410,9 +431,9 @@ function TooltipStory() {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Input placeholder="Hover me" className="w-48" />
</TooltipTrigger>
<TooltipTrigger
render={<Input placeholder="Hover me" className="w-48" />}
/>
<TooltipContent>
<p>Enter your email address here</p>
</TooltipContent>
@@ -434,10 +455,10 @@ function TooltipStory() {
{/* Top Row */}
<div></div>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="sm">
Top
</Button>
<TooltipTrigger
render={<Button variant="outline" size="sm" />}
>
Top
</TooltipTrigger>
<TooltipContent side="top">
<p>Tooltip on top</p>
@@ -447,10 +468,10 @@ function TooltipStory() {
{/* Middle Row */}
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="sm">
Left
</Button>
<TooltipTrigger
render={<Button variant="outline" size="sm" />}
>
Left
</TooltipTrigger>
<TooltipContent side="left">
<p>Tooltip on left</p>
@@ -460,10 +481,10 @@ function TooltipStory() {
<span className="text-muted-foreground text-sm">Center</span>
</div>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="sm">
Right
</Button>
<TooltipTrigger
render={<Button variant="outline" size="sm" />}
>
Right
</TooltipTrigger>
<TooltipContent side="right">
<p>Tooltip on right</p>
@@ -473,10 +494,10 @@ function TooltipStory() {
{/* Bottom Row */}
<div></div>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="sm">
Bottom
</Button>
<TooltipTrigger
render={<Button variant="outline" size="sm" />}
>
Bottom
</TooltipTrigger>
<TooltipContent side="bottom">
<p>Tooltip on bottom</p>
@@ -498,11 +519,9 @@ function TooltipStory() {
<TooltipProvider>
<div className="flex flex-wrap gap-4">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">
<Star className="mr-2 h-4 w-4" />
Premium Feature
</Button>
<TooltipTrigger render={<Button variant="outline" />}>
<Star className="mr-2 h-4 w-4" />
Premium Feature
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<div className="space-y-1">
@@ -516,11 +535,9 @@ function TooltipStory() {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline">
<Settings className="mr-2 h-4 w-4" />
Advanced Settings
</Button>
<TooltipTrigger render={<Button variant="outline" />}>
<Settings className="mr-2 h-4 w-4" />
Advanced Settings
</TooltipTrigger>
<TooltipContent>
<div className="space-y-1">
@@ -537,11 +554,9 @@ function TooltipStory() {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="destructive">
<AlertCircle className="mr-2 h-4 w-4" />
Delete Account
</Button>
<TooltipTrigger render={<Button variant="destructive" />}>
<AlertCircle className="mr-2 h-4 w-4" />
Delete Account
</TooltipTrigger>
<TooltipContent className="border-destructive bg-destructive text-destructive-foreground max-w-xs">
<div className="space-y-1">
@@ -568,10 +583,10 @@ function TooltipStory() {
<div className="space-y-4">
<div className="flex items-center gap-4">
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost">
<Copy className="h-4 w-4" />
</Button>
<TooltipTrigger
render={<Button size="icon" variant="ghost" />}
>
<Copy className="h-4 w-4" />
</TooltipTrigger>
<TooltipContent>
<p>Copy to clipboard</p>
@@ -579,10 +594,10 @@ function TooltipStory() {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost">
<Download className="h-4 w-4" />
</Button>
<TooltipTrigger
render={<Button size="icon" variant="ghost" />}
>
<Download className="h-4 w-4" />
</TooltipTrigger>
<TooltipContent>
<p>Download file</p>
@@ -590,10 +605,10 @@ function TooltipStory() {
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="ghost">
<Share className="h-4 w-4" />
</Button>
<TooltipTrigger
render={<Button size="icon" variant="ghost" />}
>
<Share className="h-4 w-4" />
</TooltipTrigger>
<TooltipContent>
<p>Share with others</p>
@@ -605,9 +620,11 @@ function TooltipStory() {
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Tooltip>
<TooltipTrigger asChild>
<Input id="username" placeholder="Enter username" />
</TooltipTrigger>
<TooltipTrigger
render={
<Input id="username" placeholder="Enter username" />
}
/>
<TooltipContent>
<p>Must be 3-20 characters, letters and numbers only</p>
</TooltipContent>
@@ -616,9 +633,7 @@ function TooltipStory() {
<div className="flex items-center space-x-2">
<Tooltip>
<TooltipTrigger asChild>
<Checkbox id="terms" />
</TooltipTrigger>
<TooltipTrigger render={<Checkbox id="terms" />} />
<TooltipContent className="max-w-xs">
<p>
By checking this, you agree to our Terms of Service and
@@ -751,7 +766,7 @@ function TooltipStory() {
</li>
<li>
<strong>TooltipTrigger:</strong> Element that triggers the
tooltip (use asChild prop)
tooltip (use render prop)
</li>
</ul>
</div>
@@ -856,8 +871,7 @@ function TooltipStory() {
<h4 className="text-sm font-semibold">Keyboard Support</h4>
<p className="text-muted-foreground text-sm">
Tooltips appear on focus and disappear on blur
<br />
Escape key dismisses tooltips
<br /> Escape key dismisses tooltips
<br /> Tooltips don't trap focus or interfere with navigation
</p>
</div>

View File

@@ -492,7 +492,7 @@ export const COMPONENTS_REGISTRY: ComponentInfo[] = [
status: 'stable',
component: CardButtonStory,
sourceFile: '@kit/ui/card-button',
props: ['asChild', 'className', 'children', 'onClick', 'disabled'],
props: ['className', 'children', 'onClick', 'disabled'],
icon: MousePointer,
},
@@ -950,7 +950,7 @@ export const COMPONENTS_REGISTRY: ComponentInfo[] = [
status: 'stable',
component: ItemStory,
sourceFile: '@kit/ui/item',
props: ['variant', 'size', 'asChild', 'className'],
props: ['variant', 'size', 'className'],
icon: Layers,
},
@@ -1004,7 +1004,7 @@ export const COMPONENTS_REGISTRY: ComponentInfo[] = [
status: 'stable',
component: BreadcrumbStory,
sourceFile: '@kit/ui/breadcrumb',
props: ['separator', 'asChild', 'href', 'className'],
props: ['separator', 'href', 'className'],
icon: ChevronRight,
},

View File

@@ -1,4 +1,3 @@
import { withI18n } from '../../lib/i18n/with-i18n';
import { DocsContent } from './components/docs-content';
import { DocsHeader } from './components/docs-header';
import { DocsSidebar } from './components/docs-sidebar';
@@ -29,4 +28,4 @@ async function ComponentDocsPage(props: ComponentDocsPageProps) {
);
}
export default withI18n(ComponentDocsPage);
export default ComponentDocsPage;

View File

@@ -1,9 +1,8 @@
import 'server-only';
import { DatabaseTool } from '@kit/mcp-server/database';
import { relative } from 'path';
import { DatabaseTool } from '@kit/mcp-server/database';
export interface DatabaseTable {
name: string;
schema: string;

View File

@@ -1,9 +1,9 @@
'use server';
import { relative } from 'path';
import { DatabaseTool } from '@kit/mcp-server/database';
import { relative } from 'path';
export async function getTableDetailsAction(
tableName: string,
schema = 'public',

View File

@@ -2,8 +2,6 @@
import Link from 'next/link';
import { EmailTesterFormSchema } from '@/app/emails/lib/email-tester-form-schema';
import { sendEmailAction } from '@/app/emails/lib/server-actions';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
@@ -19,6 +17,9 @@ import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { Switch } from '@kit/ui/switch';
import { EmailTesterFormSchema } from '@/app/emails/lib/email-tester-form-schema';
import { sendEmailAction } from '@/app/emails/lib/server-actions';
export function EmailTesterForm(props: {
template: string;
settings: {

View File

@@ -1,7 +1,3 @@
import { EmailTesterForm } from '@/app/emails/[id]/components/email-tester-form';
import { EnvModeSelector } from '@/components/env-mode-selector';
import { IFrame } from '@/components/iframe';
import {
createKitEmailsDeps,
createKitEmailsService,
@@ -17,6 +13,10 @@ import {
} from '@kit/ui/dialog';
import { Page, PageBody, PageHeader } from '@kit/ui/page';
import { EmailTesterForm } from '@/app/emails/[id]/components/email-tester-form';
import { EnvModeSelector } from '@/components/env-mode-selector';
import { IFrame } from '@/components/iframe';
type EnvMode = 'development' | 'production';
type EmailPageProps = React.PropsWithChildren<{
@@ -67,10 +67,10 @@ export default async function EmailPage(props: EmailPageProps) {
Remember that the below is an approximation of the email. Always test
it in your inbox.{' '}
<Dialog>
<DialogTrigger asChild>
<Button variant={'link'} className="p-0 underline">
Test Email
</Button>
<DialogTrigger
render={<Button variant={'link'} className="p-0 underline" />}
>
Test Email
</DialogTrigger>
<DialogContent>

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
export const EmailTesterFormSchema = z.object({
username: z.string().min(1),

View File

@@ -49,13 +49,16 @@ export default async function EmailsPage() {
<div className={'grid grid-cols-1 gap-4 md:grid-cols-4'}>
{categoryTemplates.map((template) => (
<CardButton key={template.id} asChild>
<Link href={`/emails/${template.id}`}>
<CardButtonHeader>
<CardButtonTitle>{template.name}</CardButtonTitle>
</CardButtonHeader>
</Link>
</CardButton>
<CardButton
key={template.id}
render={
<Link href={`/emails/${template.id}`}>
<CardButtonHeader>
<CardButtonTitle>{template.name}</CardButtonTitle>
</CardButtonHeader>
</Link>
}
/>
))}
</div>
</div>

View File

@@ -1,8 +1,9 @@
import type { Metadata } from 'next';
import { getMessages } from 'next-intl/server';
import { DevToolLayout } from '@/components/app-layout';
import { RootProviders } from '@/components/root-providers';
import '../styles/globals.css';
export const metadata: Metadata = {
@@ -10,15 +11,17 @@ export const metadata: Metadata = {
description: 'The dev tool for Makerkit',
};
export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const messages = await getMessages();
return (
<html lang="en">
<body>
<RootProviders>
<RootProviders messages={messages}>
<DevToolLayout>{children}</DevToolLayout>
</RootProviders>
</body>

View File

@@ -1,13 +1,13 @@
import { execFile } from 'node:child_process';
import { access, readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { promisify } from 'node:util';
import {
type KitPrerequisitesDeps,
createKitPrerequisitesService,
} from '@kit/mcp-server/prerequisites';
import { execFile } from 'node:child_process';
import { access, readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
export async function loadDashboardKitPrerequisites() {

View File

@@ -1,14 +1,14 @@
import {
type KitStatusDeps,
createKitStatusService,
} from '@kit/mcp-server/status';
import { execFile } from 'node:child_process';
import { access, readFile, stat } from 'node:fs/promises';
import { Socket } from 'node:net';
import { join } from 'node:path';
import { promisify } from 'node:util';
import {
type KitStatusDeps,
createKitStatusService,
} from '@kit/mcp-server/status';
const execFileAsync = promisify(execFile);
export async function loadDashboardKitStatus() {

View File

@@ -1,11 +1,11 @@
import { ServiceCard } from '@/components/status-tile';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Page, PageBody, PageHeader } from '@kit/ui/page';
import { loadDashboardKitPrerequisites } from './lib/prerequisites-dashboard.loader';
import { loadDashboardKitStatus } from './lib/status-dashboard.loader';
import { ServiceCard } from '@/components/status-tile';
export default async function DashboardPage() {
const [status, prerequisites] = await Promise.all([
loadDashboardKitStatus(),
@@ -37,7 +37,6 @@ export default async function DashboardPage() {
return (
<Page style={'custom'}>
<PageHeader
displaySidebarTrigger={false}
title={'Dev Tool'}
description={'Kit MCP status for this workspace'}
/>

View File

@@ -1,5 +1,4 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { loadPRDPageData } from '../_lib/server/prd-page.loader';

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
export const CreatePRDSchema = z.object({
title: z
@@ -32,4 +32,4 @@ export const CreatePRDSchema = z.object({
.min(1, 'At least one success metric is required'),
});
export type CreatePRDData = z.infer<typeof CreatePRDSchema>;
export type CreatePRDData = z.output<typeof CreatePRDSchema>;

View File

@@ -1,7 +1,7 @@
import { relative } from 'node:path';
import { PRDManager } from '@kit/mcp-server/prd-manager';
import { relative } from 'node:path';
interface PRDSummary {
filename: string;
title: string;

View File

@@ -1,9 +1,8 @@
import 'server-only';
import { PRDManager } from '@kit/mcp-server/prd-manager';
import { relative } from 'node:path';
import { PRDManager } from '@kit/mcp-server/prd-manager';
export interface CustomPhase {
id: string;
name: string;

View File

@@ -131,12 +131,14 @@ export function TranslationsComparison({
<If condition={locales.length > 1}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Select Languages
<ChevronDownIcon className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuTrigger
render={
<Button variant="outline" className="ml-auto">
Select Languages
<ChevronDownIcon className="ml-2 h-4 w-4" />
</Button>
}
/>
<DropdownMenuContent align="end" className="w-[200px]">
{locales.map((locale) => (

View File

@@ -2,7 +2,7 @@
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
import * as z from 'zod';
import { findWorkspaceRoot } from '@kit/mcp-server/env';
import {

View File

@@ -5,9 +5,6 @@ import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { envVariables } from '@/app/variables/lib/env-variables-model';
import { updateEnvironmentVariableAction } from '@/app/variables/lib/server-actions';
import { EnvModeSelector } from '@/components/env-mode-selector';
import {
ChevronsUpDownIcon,
Copy,
@@ -44,6 +41,10 @@ import { cn } from '@kit/ui/utils';
import { AppEnvState, EnvVariableState } from '../lib/types';
import { DynamicFormInput } from './dynamic-form-input';
import { envVariables } from '@/app/variables/lib/env-variables-model';
import { updateEnvironmentVariableAction } from '@/app/variables/lib/server-actions';
import { EnvModeSelector } from '@/components/env-mode-selector';
export function AppEnvironmentVariablesManager({
state,
}: React.PropsWithChildren<{
@@ -731,13 +732,15 @@ function FilterSwitcher(props: {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="font-normal">
{buttonLabel()}
<DropdownMenuTrigger
render={
<Button variant="outline" className="font-normal">
{buttonLabel()}
<ChevronsUpDownIcon className="text-muted-foreground ml-1 h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<ChevronsUpDownIcon className="text-muted-foreground ml-1 h-3 w-3" />
</Button>
}
/>
<DropdownMenuContent>
<DropdownMenuCheckboxItem
@@ -886,38 +889,41 @@ function Summary({ appState }: { appState: AppEnvState }) {
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size={'sm'}
onClick={() => {
let data = '';
<TooltipTrigger
render={
<Button
variant="outline"
size={'sm'}
onClick={() => {
let data = '';
const groups = getGroups(appState, () => true);
const groups = getGroups(appState, () => true);
groups.forEach((group) => {
data += `# ${group.category}\n`;
groups.forEach((group) => {
data += `# ${group.category}\n`;
group.variables.forEach((variable) => {
data += `${variable.key}=${variable.effectiveValue}\n`;
group.variables.forEach((variable) => {
data += `${variable.key}=${variable.effectiveValue}\n`;
});
data += '\n';
});
data += '\n';
});
const promise = copyToClipboard(data);
const promise = copyToClipboard(data);
toast.promise(promise, {
loading: 'Copying environment variables...',
success: 'Environment variables copied to clipboard.',
error: 'Failed to copy environment variables to clipboard',
});
}}
>
<CopyIcon className={'mr-2 h-4 w-4'} />
<span>Copy env file to clipboard</span>
</Button>
</TooltipTrigger>
toast.promise(promise, {
loading: 'Copying environment variables...',
success: 'Environment variables copied to clipboard.',
error:
'Failed to copy environment variables to clipboard',
});
}}
>
<CopyIcon className={'mr-2 h-4 w-4'} />
<span>Copy env file to clipboard</span>
</Button>
}
/>
<TooltipContent>
Copy environment variables to clipboard. You can place it in your

View File

@@ -2,7 +2,7 @@
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
import * as z from 'zod';
import {
createKitEnvDeps,

View File

@@ -1,7 +1,5 @@
import { use } from 'react';
import { EnvMode } from '@/app/variables/lib/types';
import {
createKitEnvDeps,
createKitEnvService,
@@ -11,6 +9,8 @@ import { Page, PageBody, PageHeader } from '@kit/ui/page';
import { AppEnvironmentVariablesManager } from './components/app-environment-variables-manager';
import { EnvMode } from '@/app/variables/lib/types';
type VariablesPageProps = {
searchParams: Promise<{ mode?: EnvMode }>;
};

View File

@@ -1,13 +1,13 @@
import { DevToolSidebar } from '@/components/app-sidebar';
import { SidebarInset, SidebarProvider } from '@kit/ui/sidebar';
import { SidebarInset, SidebarProvider } from '@kit/ui/shadcn-sidebar';
import { DevToolSidebar } from '@/components/app-sidebar';
export function DevToolLayout(props: React.PropsWithChildren) {
return (
<SidebarProvider>
<DevToolSidebar />
<SidebarInset>{props.children}</SidebarInset>
<SidebarInset className="px-4">{props.children}</SidebarInset>
</SidebarProvider>
);
}

View File

@@ -24,7 +24,7 @@ import {
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from '@kit/ui/shadcn-sidebar';
} from '@kit/ui/sidebar';
import { isRouteActive } from '@kit/ui/utils';
const routes = [
@@ -92,14 +92,14 @@ export function DevToolSidebar({
{route.children.map((child) => (
<SidebarMenuSubItem key={child.path}>
<SidebarMenuSubButton
asChild
render={
<Link href={child.path}>
<child.Icon className="h-4 w-4" />
<span>{child.label}</span>
</Link>
}
isActive={isRouteActive(child.path, pathname, false)}
>
<Link href={child.path}>
<child.Icon className="h-4 w-4" />
<span>{child.label}</span>
</Link>
</SidebarMenuSubButton>
/>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
@@ -107,13 +107,13 @@ export function DevToolSidebar({
) : (
<SidebarMenuButton
isActive={isRouteActive(route.path, pathname, false)}
asChild
>
<Link href={route.path}>
<route.Icon className="h-4 w-4" />
<span>{route.label}</span>
</Link>
</SidebarMenuButton>
render={
<Link href={route.path}>
<route.Icon className="h-4 w-4" />
<span>{route.label}</span>
</Link>
}
/>
)}
</SidebarMenuItem>
))}

View File

@@ -2,8 +2,6 @@
import { useRouter } from 'next/navigation';
import { EnvMode } from '@/app/variables/lib/types';
import {
Select,
SelectContent,
@@ -12,6 +10,8 @@ import {
SelectValue,
} from '@kit/ui/select';
import { EnvMode } from '@/app/variables/lib/types';
export function EnvModeSelector({ mode }: { mode: EnvMode }) {
const router = useRouter();

View File

@@ -1,7 +1,6 @@
'use client';
import { useState } from 'react';
import { createPortal } from 'react-dom';
export const IFrame: React.FC<

View File

@@ -3,18 +3,18 @@
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { AbstractIntlMessages } from 'next-intl';
import { I18nProvider } from '@kit/i18n/provider';
import { I18nClientProvider } from '@kit/i18n/provider';
import { Toaster } from '@kit/ui/sonner';
import { i18nResolver } from '../lib/i18n/i18n.resolver';
import { getI18nSettings } from '../lib/i18n/i18n.settings';
export function RootProviders(props: React.PropsWithChildren) {
export function RootProviders(
props: React.PropsWithChildren<{ messages: AbstractIntlMessages }>,
) {
return (
<I18nProvider settings={getI18nSettings('en')} resolver={i18nResolver}>
<I18nClientProvider locale="en" messages={props.messages}>
<ReactQueryProvider>{props.children}</ReactQueryProvider>
</I18nProvider>
</I18nClientProvider>
);
}

View File

@@ -33,7 +33,7 @@ interface ServiceCardProps {
export const ServiceCard = ({ name, status }: ServiceCardProps) => {
return (
<Card className="w-full max-w-2xl">
<CardContent className="p-4">
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">

View File

@@ -0,0 +1,26 @@
import { getRequestConfig } from 'next-intl/server';
import account from '../../web/i18n/messages/en/account.json';
import auth from '../../web/i18n/messages/en/auth.json';
import billing from '../../web/i18n/messages/en/billing.json';
import common from '../../web/i18n/messages/en/common.json';
import marketing from '../../web/i18n/messages/en/marketing.json';
import teams from '../../web/i18n/messages/en/teams.json';
export default getRequestConfig(async () => {
return {
locale: 'en',
messages: {
common,
auth,
account,
teams,
billing,
marketing,
},
timeZone: 'UTC',
getMessageFallback(info) {
return info.key;
},
};
});

View File

@@ -1,13 +0,0 @@
import { createI18nServerInstance } from './i18n.server';
type LayoutOrPageComponent<Params> = React.ComponentType<Params>;
export function withI18n<Params extends object>(
Component: LayoutOrPageComponent<Params>,
) {
return async function I18nServerComponentWrapper(params: Params) {
await createI18nServerInstance();
return <Component {...params} />;
};
}

View File

@@ -1,8 +1,12 @@
import type { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./i18n/request.ts');
const nextConfig: NextConfig = {
reactStrictMode: true,
transpilePackages: ['@kit/ui', '@kit/shared'],
transpilePackages: ['@kit/ui', '@kit/shared', '@kit/i18n'],
reactCompiler: true,
devIndicators: {
position: 'bottom-right',
@@ -14,4 +18,4 @@ const nextConfig: NextConfig = {
},
};
export default nextConfig;
export default withNextIntl(nextConfig);

View File

@@ -4,44 +4,41 @@
"private": true,
"scripts": {
"clean": "git clean -xdf .next .turbo node_modules",
"dev": "next dev --port=3010 | pino-pretty -c",
"format": "prettier --check --write \"**/*.{ts,tsx}\" --ignore-path=\"../../.prettierignore\""
"dev": "next dev --port=3010 | pino-pretty -c"
},
"dependencies": {
"@faker-js/faker": "^10.2.0",
"@hookform/resolvers": "^5.2.2",
"@faker-js/faker": "catalog:",
"@hookform/resolvers": "catalog:",
"@tanstack/react-query": "catalog:",
"lucide-react": "catalog:",
"next": "catalog:",
"next-intl": "catalog:",
"nodemailer": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"rxjs": "^7.8.2"
"rxjs": "catalog:"
},
"devDependencies": {
"@kit/email-templates": "workspace:*",
"@kit/i18n": "workspace:*",
"@kit/mcp-server": "workspace:*",
"@kit/next": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@tailwindcss/postcss": "^4.2.1",
"@types/node": "catalog:",
"@tailwindcss/postcss": "catalog:",
"@types/nodemailer": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"babel-plugin-react-compiler": "1.0.0",
"pino-pretty": "13.0.0",
"babel-plugin-react-compiler": "catalog:",
"pino-pretty": "catalog:",
"react-hook-form": "catalog:",
"recharts": "2.15.3",
"recharts": "catalog:",
"tailwindcss": "catalog:",
"tw-animate-css": "catalog:",
"typescript": "^5.9.3",
"typescript": "catalog:",
"zod": "catalog:"
},
"prettier": "@kit/prettier-config",
"browserslist": [
"last 1 versions",
"> 0.7%",

View File

@@ -66,26 +66,6 @@
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
@keyframes fade-up {
0% {
opacity: 0;