feat: copy connectorkit ui-base components
This commit is contained in:
43
app/src/components/ui-base/button.tsx
Normal file
43
app/src/components/ui-base/button.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Button as BaseButton } from '@base-ui/react/button';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors select-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90 active:bg-primary/80',
|
||||||
|
destructive: 'bg-destructive text-white shadow-sm hover:bg-destructive/90 active:bg-destructive/80',
|
||||||
|
outline:
|
||||||
|
'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground active:bg-accent/80',
|
||||||
|
secondary:
|
||||||
|
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 active:bg-secondary/70',
|
||||||
|
ghost: 'hover:bg-accent hover:text-accent-foreground active:bg-accent/80',
|
||||||
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: 'h-9 px-4 py-2 rounded-md',
|
||||||
|
sm: 'h-8 rounded-md px-3 text-xs',
|
||||||
|
lg: 'h-10 rounded-md px-8',
|
||||||
|
icon: 'h-9 w-9 rounded-md',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, ...props }, ref) => {
|
||||||
|
return <BaseButton ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} />;
|
||||||
|
});
|
||||||
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
export { Button, buttonVariants };
|
||||||
39
app/src/components/ui-base/collapsible.tsx
Normal file
39
app/src/components/ui-base/collapsible.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Collapsible as BaseCollapsible } from '@base-ui/react/collapsible';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const Collapsible = BaseCollapsible.Root;
|
||||||
|
|
||||||
|
const CollapsibleTrigger = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
React.ComponentPropsWithoutRef<typeof BaseCollapsible.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<BaseCollapsible.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex w-full items-center justify-between py-2 text-sm font-medium transition-all hover:underline [&[data-panel-open]>svg]:rotate-180',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CollapsibleTrigger.displayName = 'CollapsibleTrigger';
|
||||||
|
|
||||||
|
const CollapsibleContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentPropsWithoutRef<typeof BaseCollapsible.Panel>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<BaseCollapsible.Panel
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'h-[var(--collapsible-panel-height)] overflow-hidden transition-all data-[ending-style]:h-0 data-[starting-style]:h-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
CollapsibleContent.displayName = 'CollapsibleContent';
|
||||||
|
|
||||||
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||||
99
app/src/components/ui-base/dialog.tsx
Normal file
99
app/src/components/ui-base/dialog.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Dialog as BaseDialog } from '@base-ui/react/dialog';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
|
const Dialog = BaseDialog.Root;
|
||||||
|
|
||||||
|
const DialogTrigger = BaseDialog.Trigger;
|
||||||
|
|
||||||
|
const DialogPortal = BaseDialog.Portal;
|
||||||
|
|
||||||
|
const DialogClose = BaseDialog.Close;
|
||||||
|
|
||||||
|
const DialogBackdrop = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseDialog.Backdrop>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<BaseDialog.Backdrop
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed inset-0 min-h-dvh z-50 bg-black/50 transition-opacity duration-150 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 dark:bg-black/80 supports-[-webkit-touch-callout:none]:absolute',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
DialogBackdrop.displayName = 'DialogBackdrop';
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentPropsWithoutRef<typeof BaseDialog.Popup> & {
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
}
|
||||||
|
>(({ className, children, showCloseButton = true, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogBackdrop />
|
||||||
|
<BaseDialog.Popup
|
||||||
|
ref={ref}
|
||||||
|
aria-describedby={undefined}
|
||||||
|
className={cn(
|
||||||
|
'fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg outline-none transition-all duration-150 data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0 z-[9999]',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogClose className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 data-[disabled]:pointer-events-none">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogClose>
|
||||||
|
)}
|
||||||
|
</BaseDialog.Popup>
|
||||||
|
</DialogPortal>
|
||||||
|
));
|
||||||
|
DialogContent.displayName = 'DialogContent';
|
||||||
|
|
||||||
|
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
|
||||||
|
);
|
||||||
|
DialogHeader.displayName = 'DialogHeader';
|
||||||
|
|
||||||
|
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
|
||||||
|
);
|
||||||
|
DialogFooter.displayName = 'DialogFooter';
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<HTMLHeadingElement, React.ComponentPropsWithoutRef<typeof BaseDialog.Title>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<BaseDialog.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
DialogTitle.displayName = 'DialogTitle';
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.ComponentPropsWithoutRef<typeof BaseDialog.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<BaseDialog.Description ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
|
||||||
|
));
|
||||||
|
DialogDescription.displayName = 'DialogDescription';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogPortal,
|
||||||
|
DialogBackdrop,
|
||||||
|
DialogClose,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
};
|
||||||
81
app/src/components/ui-base/menu.tsx
Normal file
81
app/src/components/ui-base/menu.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Menu as BaseMenu } from '@base-ui/react/menu';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const Menu = BaseMenu.Root;
|
||||||
|
|
||||||
|
const MenuTrigger = React.forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<typeof BaseMenu.Trigger>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<BaseMenu.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors select-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 focus-visible:-outline-offset-1 border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground cursor-pointer',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
MenuTrigger.displayName = 'MenuTrigger';
|
||||||
|
|
||||||
|
const MenuPortal = BaseMenu.Portal;
|
||||||
|
|
||||||
|
const MenuPositioner = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseMenu.Positioner>>(
|
||||||
|
({ className, sideOffset = 8, ...props }, ref) => (
|
||||||
|
<BaseMenu.Positioner
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn('outline-none select-none z-50', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
MenuPositioner.displayName = 'MenuPositioner';
|
||||||
|
|
||||||
|
const MenuPopup = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseMenu.Popup>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<BaseMenu.Popup
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'origin-[var(--transform-origin)] rounded-md bg-popover p-1 text-popover-foreground shadow-md outline outline-1 outline-border transition-[transform,scale,opacity] duration-150 data-[ending-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[starting-style]:opacity-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
MenuPopup.displayName = 'MenuPopup';
|
||||||
|
|
||||||
|
const MenuItem = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseMenu.Item>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<BaseMenu.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
MenuItem.displayName = 'MenuItem';
|
||||||
|
|
||||||
|
const MenuSeparator = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseMenu.Separator>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<BaseMenu.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
|
||||||
|
),
|
||||||
|
);
|
||||||
|
MenuSeparator.displayName = 'MenuSeparator';
|
||||||
|
|
||||||
|
const MenuGroup = BaseMenu.Group;
|
||||||
|
|
||||||
|
const MenuGroupLabel = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<typeof BaseMenu.GroupLabel>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<BaseMenu.GroupLabel ref={ref} className={cn('px-2 py-1.5 text-sm font-semibold', className)} {...props} />
|
||||||
|
),
|
||||||
|
);
|
||||||
|
MenuGroupLabel.displayName = 'MenuGroupLabel';
|
||||||
|
|
||||||
|
export { Menu, MenuTrigger, MenuPortal, MenuPositioner, MenuPopup, MenuItem, MenuSeparator, MenuGroup, MenuGroupLabel };
|
||||||
Reference in New Issue
Block a user