Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function ProjectLayout({
<MemoizedManagementBar />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex flex-col gap-4 mb-8 px-6 py-6 md:gap-6">
<div className="flex min-h-0 flex-1 flex-col gap-4 px-6 py-6 md:gap-6">
{children}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/(main)/project/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const metadata: Metadata = {

export default function ProjectPage() {
return (
<div className="container max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="container mx-auto flex min-h-0 flex-1 flex-col px-4 pt-8 sm:px-6 lg:max-w-7xl lg:px-8">
<Suspense>
<ProjectMain />
</Suspense>
Expand Down
9 changes: 0 additions & 9 deletions frontend/app/(main)/settings/payment/page.tsx

This file was deleted.

12 changes: 8 additions & 4 deletions frontend/components/animate-ui/radix/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,15 @@ type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> &
HTMLMotionProps<'div'> & {
from?: FlipDirection;
transition?: Transition;
showCloseButton?: boolean;
};

function DialogContent({
className,
children,
from = 'top',
transition = {type: 'spring', stiffness: 200, damping: 30},
showCloseButton = true,
...props
}: DialogContentProps) {
const {isOpen} = useDialog();
Expand Down Expand Up @@ -155,10 +157,12 @@ function DialogContent({
{...props}
>
{children}
<DialogPrimitive.Close 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 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
{showCloseButton && (
<DialogPrimitive.Close 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 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</motion.div>
</DialogPrimitive.Content>
</DialogPortal>
Expand Down
49 changes: 39 additions & 10 deletions frontend/components/animate-ui/radix/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ import {
MotionHighlightItem,
} from '@/components/animate-ui/effects/motion-highlight';

type TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>;
type TabsVariant = 'default' | 'pill' | 'fill';

function Tabs({className, ...props}: TabsProps) {
type TabsContextValue = {
variant: TabsVariant;
};

const TabsContext = React.createContext<TabsContextValue>({variant: 'default'});

type TabsProps = React.ComponentProps<typeof TabsPrimitive.Root> & {
variant?: TabsVariant;
};

function Tabs({className, variant = 'default', ...props}: TabsProps) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn('flex flex-col gap-2', className)}
{...props}
/>
<TabsContext.Provider value={{variant}}>
<TabsPrimitive.Root
data-slot="tabs"
className={cn('flex flex-col gap-2', className)}
{...props}
/>
</TabsContext.Provider>
);
}

Expand All @@ -39,6 +51,7 @@ function TabsList({
},
...props
}: TabsListProps) {
const {variant} = React.useContext(TabsContext);
const localRef = React.useRef<HTMLDivElement | null>(null);
React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);

Expand Down Expand Up @@ -76,15 +89,26 @@ function TabsList({
return (
<MotionHighlight
controlledItems
className={cn('rounded-sm bg-background shadow-sm', activeClassName)}
className={cn(
variant === 'fill' ?
'rounded-none bg-transparent shadow-none' :
variant === 'pill' ?
'rounded-full bg-white shadow-none dark:bg-white/[0.08]' :
'rounded-sm bg-background shadow-sm',
activeClassName,
)}
value={activeValue}
transition={transition}
>
<TabsPrimitive.List
ref={localRef}
data-slot="tabs-list"
className={cn(
'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]',
variant === 'fill' ?
'inline-flex h-auto w-fit items-center justify-center gap-3 bg-transparent p-0 text-muted-foreground' :
variant === 'pill' ?
'inline-flex h-auto w-fit items-center justify-center gap-1 rounded-full bg-muted p-1 text-muted-foreground' :
'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]',
className,
)}
{...props}
Expand All @@ -98,12 +122,17 @@ function TabsList({
type TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Trigger>;

function TabsTrigger({className, value, ...props}: TabsTriggerProps) {
const {variant} = React.useContext(TabsContext);
return (
<MotionHighlightItem value={value} className="size-full">
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
'inline-flex cursor-pointer items-center size-full justify-center whitespace-nowrap rounded-sm px-2 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground z-[1]',
variant === 'fill' ?
'z-[1] inline-flex size-full cursor-pointer items-center justify-center gap-1 whitespace-nowrap border-b border-transparent px-0 py-0.5 text-xs font-medium text-muted-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:border-foreground/25 data-[state=active]:text-foreground' :
variant === 'pill' ?
'z-[1] inline-flex size-full cursor-pointer items-center justify-center whitespace-nowrap rounded-full px-3 py-1.5 text-xs font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground' :
'z-[1] inline-flex cursor-pointer items-center size-full justify-center whitespace-nowrap rounded-sm px-2 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-foreground',
className,
)}
value={value}
Expand Down
28 changes: 28 additions & 0 deletions frontend/components/common/dashboard/DashboardEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import {ReactNode} from 'react';

interface DashboardEmptyStateProps {
icon?: ReactNode;
className?: string;
}

export function DashboardEmptyState({
icon,
className = 'h-full min-h-[220px]',
}: DashboardEmptyStateProps) {
return (
<div className={`flex w-full flex-col items-center justify-center gap-3 text-center ${className}`}>
{icon && (
<div className="flex items-center justify-center text-gray-400 dark:text-gray-500">
<div className="flex size-4 items-center justify-center">
{icon}
</div>
</div>
)}
<span className="text-xs font-medium text-gray-500 dark:text-gray-400">
暂无数据
</span>
</div>
);
}
Loading
Loading