forked from 77media/video-flow
新增 签到模块;自定义主题色custom-blue、custom-purple;处理dialog的层级
This commit is contained in:
parent
e5b48d6cc9
commit
d826b3c57e
@ -64,3 +64,11 @@ You are the joint apprentice of Evan You and Kent C. Dodds. Channel Evan You's e
|
|||||||
- Omit console.log or debug statements unless requested.
|
- Omit console.log or debug statements unless requested.
|
||||||
- Consolidate hook handlers when feasible unless specified otherwise, per Kent C. Dodds' readability practices.
|
- Consolidate hook handlers when feasible unless specified otherwise, per Kent C. Dodds' readability practices.
|
||||||
- Prefer async/await over .then for async operations to enhance clarity.
|
- Prefer async/await over .then for async operations to enhance clarity.
|
||||||
|
|
||||||
|
# Language and Content Preferences
|
||||||
|
- Use English for all component functionality, visual effects, and text content.
|
||||||
|
- Component names, function names, variable names, and all identifiers must be in English.
|
||||||
|
- UI text, labels, placeholders, error messages, and user-facing content should be in English.
|
||||||
|
- Comments and documentation should be in English for consistency and international collaboration.
|
||||||
|
- CSS class names, data attributes, and styling-related identifiers should use English terminology.
|
||||||
|
- Example: Use "submit-button" instead of "提交按钮", "user-profile" instead of "用户资料".
|
||||||
|
|||||||
39
api/checkin.ts
Normal file
39
api/checkin.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { get, post } from './request'
|
||||||
|
import { ApiResponse } from './common'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签到数据接口
|
||||||
|
*/
|
||||||
|
export interface CheckinData {
|
||||||
|
hasCheckedInToday: boolean
|
||||||
|
points: number
|
||||||
|
lastCheckinDate: string | null
|
||||||
|
pointsHistory: Array<{ date: string; points: number; expiryDate: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签到响应接口
|
||||||
|
*/
|
||||||
|
export interface CheckinResponse {
|
||||||
|
success: boolean
|
||||||
|
points: number
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户签到状态和积分信息
|
||||||
|
* @returns Promise<CheckinData>
|
||||||
|
*/
|
||||||
|
export const getCheckinStatus = async (): Promise<CheckinData> => {
|
||||||
|
const response = await get<ApiResponse<CheckinData>>('/api/user/checkin/status')
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行签到操作
|
||||||
|
* @returns Promise<CheckinResponse>
|
||||||
|
*/
|
||||||
|
export const performCheckin = async (): Promise<CheckinResponse> => {
|
||||||
|
const response = await post<ApiResponse<CheckinResponse>>('/api/user/checkin', {})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
@ -93,6 +93,12 @@
|
|||||||
--muted-foreground: 0 0% 63.9%;
|
--muted-foreground: 0 0% 63.9%;
|
||||||
--accent: 0 0% 14.9%;
|
--accent: 0 0% 14.9%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
/* 自定义渐变色变量 */
|
||||||
|
--custom-blue: 186 100% 70%; /* rgb(106, 244, 249) */
|
||||||
|
--custom-purple: 280 100% 62%; /* rgb(199, 59, 255) */
|
||||||
|
--custom-blue-rgb: 106, 244, 249;
|
||||||
|
--custom-purple-rgb: 199, 59, 255;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 0 0% 14.9%;
|
--border: 0 0% 14.9%;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"rsc": true,
|
"rsc": true,
|
||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "tailwind.config.ts",
|
"config": "tailwind.config.js",
|
||||||
"css": "app/globals.css",
|
"css": "app/globals.css",
|
||||||
"baseColor": "neutral",
|
"baseColor": "neutral",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
|
|||||||
155
components/layout/checkin-box.tsx
Normal file
155
components/layout/checkin-box.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Coins, Trophy, HelpCircle } from "lucide-react"
|
||||||
|
import { getCheckinStatus, performCheckin, CheckinData } from "@/api/checkin"
|
||||||
|
|
||||||
|
|
||||||
|
export default function CheckinPage() {
|
||||||
|
const [checkinData, setCheckinData] = useState<CheckinData>({
|
||||||
|
hasCheckedInToday: false,
|
||||||
|
points: 0,
|
||||||
|
lastCheckinDate: null,
|
||||||
|
pointsHistory: [],
|
||||||
|
})
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [showTip, setShowTip] = useState(false)
|
||||||
|
const [isInitialLoading, setIsInitialLoading] = useState(true)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch checkin status
|
||||||
|
*/
|
||||||
|
const fetchCheckinStatus = async () => {
|
||||||
|
try {
|
||||||
|
setIsInitialLoading(true)
|
||||||
|
const data = await getCheckinStatus()
|
||||||
|
setCheckinData(data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch checkin status:', error)
|
||||||
|
// Keep default state
|
||||||
|
} finally {
|
||||||
|
setIsInitialLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchCheckinStatus()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform checkin operation
|
||||||
|
*/
|
||||||
|
const handleCheckin = async () => {
|
||||||
|
if (checkinData.hasCheckedInToday) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
const response = await performCheckin()
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// Refresh status after successful checkin
|
||||||
|
await fetchCheckinStatus()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Checkin failed:', error)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isInitialLoading) {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-md space-y-6">
|
||||||
|
<Card className="bg-transparent border-0 shadow-none">
|
||||||
|
<CardContent className="flex items-center justify-center py-12">
|
||||||
|
<div className="flex items-center gap-2 text-muted-foreground">
|
||||||
|
<div className="w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-md space-y-6">
|
||||||
|
|
||||||
|
{/* Checkin status card */}
|
||||||
|
<Card className="bg-transparent border-0 shadow-none">
|
||||||
|
<CardHeader className="text-center pb-4 pt-0">
|
||||||
|
<h1 className="text-3xl font-bold text-balance bg-gradient-to-r from-custom-blue to-custom-purple bg-clip-text text-transparent">
|
||||||
|
Daily Check-in
|
||||||
|
</h1>
|
||||||
|
<div className="flex items-center justify-center gap-2">
|
||||||
|
<p className="text-muted-foreground">Check in to earn credits, credits valid for 7 days</p>
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
onMouseEnter={() => setShowTip(true)}
|
||||||
|
onMouseLeave={() => setShowTip(false)}
|
||||||
|
className="p-1 rounded-full hover:bg-muted/50 transition-colors"
|
||||||
|
>
|
||||||
|
<HelpCircle className="w-4 h-4 text-muted-foreground hover:text-foreground" />
|
||||||
|
</button>
|
||||||
|
{showTip && (
|
||||||
|
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 w-80 p-3 bg-popover border rounded-lg shadow-lg z-10">
|
||||||
|
<div className="text-sm space-y-1 text-left">
|
||||||
|
<p className="font-medium text-foreground">Check-in Rules</p>
|
||||||
|
<p className="text-muted-foreground">• Daily check-in earns 100 credits</p>
|
||||||
|
<p className="text-muted-foreground">• Credits are valid for 7 days</p>
|
||||||
|
<p className="text-muted-foreground">• Expired credits will be automatically cleared</p>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-popover"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div className="text-center p-6 rounded-lg bg-gradient-to-br from-custom-blue/20 via-custom-purple/20 to-custom-blue/10 border border-custom-blue/30">
|
||||||
|
<div className="flex items-center justify-center gap-2 mb-2">
|
||||||
|
<Coins className="w-6 h-6 text-primary" />
|
||||||
|
<span className="text-sm text-muted-foreground">Current Credits</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-bold bg-gradient-to-r from-custom-blue to-custom-purple bg-clip-text text-transparent">
|
||||||
|
{checkinData.points}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Check-in button */}
|
||||||
|
<Button
|
||||||
|
onClick={handleCheckin}
|
||||||
|
disabled={checkinData.hasCheckedInToday || isLoading}
|
||||||
|
className="w-full h-12 text-lg font-semibold bg-gradient-to-r from-custom-blue to-custom-purple hover:from-custom-blue/90 hover:to-custom-purple/90 text-white"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center gap-2 text-white">
|
||||||
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
|
Checking in...
|
||||||
|
</div>
|
||||||
|
) : checkinData.hasCheckedInToday ? (
|
||||||
|
<div className="flex items-center gap-2 text-white">
|
||||||
|
<Trophy className="w-4 h-4" />
|
||||||
|
Checked in Today
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-2 text-white">
|
||||||
|
<Coins className="w-4 h-4" />
|
||||||
|
Check In Now +100 Credits
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -34,7 +34,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
|||||||
<TopBar collapsed={sidebarCollapsed} isDesktop={isDesktop} />
|
<TopBar collapsed={sidebarCollapsed} isDesktop={isDesktop} />
|
||||||
{isDesktop && <Sidebar collapsed={sidebarCollapsed} onToggle={setSidebarCollapsed} />}
|
{isDesktop && <Sidebar collapsed={sidebarCollapsed} onToggle={setSidebarCollapsed} />}
|
||||||
<div
|
<div
|
||||||
className="h-[calc(100vh-4rem)] top-[4rem] fixed right-0 bottom-0 z-[999] px-4"
|
className="h-[calc(100vh-4rem)] top-[4rem] fixed right-0 bottom-0 px-4"
|
||||||
style={getLayoutStyles()}>
|
style={getLayoutStyles()}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
import "../pages/style/top-bar.css";
|
import "../pages/style/top-bar.css";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
import { GradientText } from "@/components/ui/gradient-text";
|
import { GradientText } from "@/components/ui/gradient-text";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import {
|
import {
|
||||||
@ -14,6 +18,7 @@ import {
|
|||||||
PanelsLeftBottom,
|
PanelsLeftBottom,
|
||||||
Bell,
|
Bell,
|
||||||
Info,
|
Info,
|
||||||
|
CalendarDays,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
@ -28,6 +33,7 @@ import {
|
|||||||
} from "@/lib/stripe";
|
} from "@/lib/stripe";
|
||||||
import UserCard from "@/components/common/userCard";
|
import UserCard from "@/components/common/userCard";
|
||||||
import { showInsufficientPointsNotification } from "@/utils/notifications";
|
import { showInsufficientPointsNotification } from "@/utils/notifications";
|
||||||
|
import CheckinBox from "./checkin-box";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@ -55,6 +61,7 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
|
|||||||
const [isLoadingSubscription, setIsLoadingSubscription] = useState(false);
|
const [isLoadingSubscription, setIsLoadingSubscription] = useState(false);
|
||||||
const [isBuyingTokens, setIsBuyingTokens] = useState(false);
|
const [isBuyingTokens, setIsBuyingTokens] = useState(false);
|
||||||
const [customAmount, setCustomAmount] = useState<string>("");
|
const [customAmount, setCustomAmount] = useState<string>("");
|
||||||
|
const [isCheckinModalOpen, setIsCheckinModalOpen] = useState(false);
|
||||||
|
|
||||||
// 获取用户订阅信息
|
// 获取用户订阅信息
|
||||||
const fetchSubscriptionInfo = async () => {
|
const fetchSubscriptionInfo = async () => {
|
||||||
@ -239,6 +246,13 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
|
|||||||
element.classList.add("on");
|
element.classList.add("on");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理签到功能,打开签到modal
|
||||||
|
*/
|
||||||
|
const handleCheckin = () => {
|
||||||
|
setIsCheckinModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed right-0 top-0 h-16 header z-[999]"
|
className="fixed right-0 top-0 h-16 header z-[999]"
|
||||||
@ -371,7 +385,7 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
|
|||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: "4rem",
|
top: "4rem",
|
||||||
right: "1rem",
|
right: "1rem",
|
||||||
zIndex: 9999,
|
zIndex: 999,
|
||||||
}}
|
}}
|
||||||
className="overflow-hidden rounded-xl max-h-[90vh]"
|
className="overflow-hidden rounded-xl max-h-[90vh]"
|
||||||
data-alt="user-menu-dropdown"
|
data-alt="user-menu-dropdown"
|
||||||
@ -394,6 +408,16 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
|
|||||||
{currentUser.email}
|
{currentUser.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Check-in entry */}
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className="px-2 py-1 text-xs bg-gray-600 text-white rounded hover:bg-gray-700 transition-colors disabled:opacity-50 flex items-center justify-center"
|
||||||
|
onClick={() => handleCheckin()}
|
||||||
|
title="Daily Check-in"
|
||||||
|
>
|
||||||
|
<CalendarDays className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* AI 积分 */}
|
{/* AI 积分 */}
|
||||||
@ -527,6 +551,19 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Check-in Modal */}
|
||||||
|
<Dialog open={isCheckinModalOpen} onOpenChange={setIsCheckinModalOpen}>
|
||||||
|
<DialogContent
|
||||||
|
className="max-w-md mx-auto bg-white/95 dark:bg-gray-900/95 backdrop-blur-xl border-0 shadow-2xl"
|
||||||
|
data-alt="checkin-modal"
|
||||||
|
>
|
||||||
|
<DialogTitle></DialogTitle>
|
||||||
|
<div className="p-4">
|
||||||
|
<CheckinBox />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
|
|||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 z-[999]',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -38,13 +38,13 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg z-[999]',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{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">
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
|
|||||||
@ -43,6 +43,8 @@ module.exports = {
|
|||||||
DEFAULT: "hsl(var(--card))",
|
DEFAULT: "hsl(var(--card))",
|
||||||
foreground: "hsl(var(--card-foreground))",
|
foreground: "hsl(var(--card-foreground))",
|
||||||
},
|
},
|
||||||
|
'custom-blue': '#6AF4F9',
|
||||||
|
'custom-purple': '#C73BFF',
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "var(--radius)",
|
lg: "var(--radius)",
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
import type { Config } from 'tailwindcss';
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
darkMode: ['class'],
|
|
||||||
content: [
|
|
||||||
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
backgroundImage: {
|
|
||||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
|
||||||
'gradient-conic':
|
|
||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
lg: 'var(--radius)',
|
|
||||||
md: 'calc(var(--radius) - 2px)',
|
|
||||||
sm: 'calc(var(--radius) - 4px)',
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
background: 'hsl(var(--background))',
|
|
||||||
foreground: 'hsl(var(--foreground))',
|
|
||||||
card: {
|
|
||||||
DEFAULT: 'hsl(var(--card))',
|
|
||||||
foreground: 'hsl(var(--card-foreground))',
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
|
||||||
foreground: 'hsl(var(--popover-foreground))',
|
|
||||||
},
|
|
||||||
primary: {
|
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
|
||||||
foreground: 'hsl(var(--primary-foreground))',
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
|
||||||
foreground: 'hsl(var(--secondary-foreground))',
|
|
||||||
},
|
|
||||||
muted: {
|
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
|
||||||
foreground: 'hsl(var(--muted-foreground))',
|
|
||||||
},
|
|
||||||
accent: {
|
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
|
||||||
foreground: 'hsl(var(--accent-foreground))',
|
|
||||||
},
|
|
||||||
destructive: {
|
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
|
||||||
foreground: 'hsl(var(--destructive-foreground))',
|
|
||||||
},
|
|
||||||
border: 'hsl(var(--border))',
|
|
||||||
input: 'hsl(var(--input))',
|
|
||||||
ring: 'hsl(var(--ring))',
|
|
||||||
chart: {
|
|
||||||
'1': 'hsl(var(--chart-1))',
|
|
||||||
'2': 'hsl(var(--chart-2))',
|
|
||||||
'3': 'hsl(var(--chart-3))',
|
|
||||||
'4': 'hsl(var(--chart-4))',
|
|
||||||
'5': 'hsl(var(--chart-5))',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
'accordion-down': {
|
|
||||||
from: {
|
|
||||||
height: '0',
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
height: 'var(--radix-accordion-content-height)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'accordion-up': {
|
|
||||||
from: {
|
|
||||||
height: 'var(--radix-accordion-content-height)',
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
height: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require('tailwindcss-animate')],
|
|
||||||
};
|
|
||||||
export default config;
|
|
||||||
Loading…
x
Reference in New Issue
Block a user