feat: add Frutiger font, enhance UI with glass effect and shadows, and improve component styling
This commit is contained in:
BIN
thoughts-frontend/app/frutiger-bold.woff
Normal file
BIN
thoughts-frontend/app/frutiger-bold.woff
Normal file
Binary file not shown.
BIN
thoughts-frontend/app/frutiger.woff
Normal file
BIN
thoughts-frontend/app/frutiger.woff
Normal file
Binary file not shown.
@@ -42,6 +42,9 @@
|
|||||||
--radius-lg: var(--radius);
|
--radius-lg: var(--radius);
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
|
||||||
|
--background-start: var(--color-sky-blue);
|
||||||
|
--background-end: var(--color-lime-300);
|
||||||
|
|
||||||
/* Frutiger Aero Gradients */
|
/* Frutiger Aero Gradients */
|
||||||
--gradient-fa-blue: 135deg, hsl(217 91% 60%) 0%, hsl(200 90% 70%) 100%;
|
--gradient-fa-blue: 135deg, hsl(217 91% 60%) 0%, hsl(200 90% 70%) 100%;
|
||||||
--gradient-fa-green: 135deg, hsl(155 70% 55%) 0%, hsl(170 80% 65%) 100%;
|
--gradient-fa-green: 135deg, hsl(155 70% 55%) 0%, hsl(170 80% 65%) 100%;
|
||||||
@@ -59,6 +62,8 @@
|
|||||||
--text-shadow-sm: 0 1px 0px rgba(255, 255, 255, 0.4);
|
--text-shadow-sm: 0 1px 0px rgba(255, 255, 255, 0.4);
|
||||||
--text-shadow-md: 0 2px 2px rgba(0, 0, 0, 0.2);
|
--text-shadow-md: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||||
--text-shadow-lg: 0 4px 4px rgba(0, 0, 0, 0.2);
|
--text-shadow-lg: 0 4px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
--font-display: var(--font-frutiger), "Arial", "Helvetica", "sans-serif";
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -178,12 +183,11 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
background: linear-gradient(
|
background-image: url("/background.jpeg");
|
||||||
135deg,
|
background-size: cover;
|
||||||
hsl(var(--background)) 0%,
|
background-position: center;
|
||||||
hsl(var(--background)) 70%,
|
background-attachment: fixed;
|
||||||
hsl(var(--primary) / 0.1) 100%
|
background-repeat: no-repeat;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.glossy-effect::before {
|
.glossy-effect::before {
|
||||||
@@ -263,3 +267,48 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.shadow-fa-sm {
|
||||||
|
box-shadow: var(--shadow-fa-sm), var(--fa-inner);
|
||||||
|
}
|
||||||
|
.shadow-fa-md {
|
||||||
|
box-shadow: var(--shadow-fa-md), var(--fa-inner);
|
||||||
|
}
|
||||||
|
.shadow-fa-lg {
|
||||||
|
box-shadow: var(--shadow-fa-lg), var(--fa-inner);
|
||||||
|
}
|
||||||
|
.text-shadow-default {
|
||||||
|
text-shadow: var(--text-shadow-default);
|
||||||
|
}
|
||||||
|
.text-shadow-sm {
|
||||||
|
text-shadow: var(--text-shadow-sm);
|
||||||
|
}
|
||||||
|
.text-shadow-md {
|
||||||
|
text-shadow: var(--text-shadow-md);
|
||||||
|
}
|
||||||
|
.text-shadow-lg {
|
||||||
|
text-shadow: var(--text-shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-effect {
|
||||||
|
@apply bg-card/70 backdrop-blur-lg border border-white/20 shadow-fa-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gloss-highlight::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60%;
|
||||||
|
border-radius: inherit; /* This is key for matching the parent's border radius */
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(255, 255, 255, 0.5) 0%,
|
||||||
|
rgba(255, 255, 255, 0) 100%
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -4,22 +4,29 @@ import "./globals.css";
|
|||||||
import { AuthProvider } from "@/hooks/use-auth";
|
import { AuthProvider } from "@/hooks/use-auth";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { Header } from "@/components/header";
|
import { Header } from "@/components/header";
|
||||||
|
import localFont from "next/font/local";
|
||||||
const geistSans = Geist({
|
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Thoughts",
|
title: "Thoughts",
|
||||||
description: "A social network for sharing thoughts",
|
description: "A social network for sharing thoughts",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const frutiger = localFont({
|
||||||
|
src: [
|
||||||
|
{
|
||||||
|
path: "./frutiger.woff",
|
||||||
|
weight: "normal",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "./frutiger-bold.woff",
|
||||||
|
weight: "bold",
|
||||||
|
style: "normal",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variable: "--font-frutiger",
|
||||||
|
});
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
@@ -27,9 +34,7 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body className={`${frutiger.className} antialiased`}>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
||||||
>
|
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex-1">{children}</main>
|
<main className="flex-1">{children}</main>
|
||||||
|
@@ -51,7 +51,7 @@ async function FeedPage({ token }: { token: string }) {
|
|||||||
|
|
||||||
<main className="col-span-1 lg:col-span-2 space-y-6">
|
<main className="col-span-1 lg:col-span-2 space-y-6">
|
||||||
<header className="mb-6">
|
<header className="mb-6">
|
||||||
<h1 className="text-3xl font-bold">Your Feed</h1>
|
<h1 className="text-3xl font-bold text-shadow-sm">Your Feed</h1>
|
||||||
</header>
|
</header>
|
||||||
<PostThoughtForm />
|
<PostThoughtForm />
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
@@ -76,26 +76,36 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
authorDetails.set(user.username, { avatarUrl: user.avatarUrl });
|
authorDetails.set(user.username, { avatarUrl: user.avatarUrl });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div id={`profile-page-${user.username}`}>
|
||||||
{user.customCss && (
|
{user.customCss && (
|
||||||
<style dangerouslySetInnerHTML={{ __html: user.customCss }} />
|
<style dangerouslySetInnerHTML={{ __html: user.customCss }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
id="profile-header"
|
||||||
className="h-48 bg-gray-200 bg-cover bg-center profile-header"
|
className="h-48 bg-gray-200 bg-cover bg-center profile-header"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: user.headerUrl ? `url(${user.headerUrl})` : "none",
|
backgroundImage: user.headerUrl ? `url(${user.headerUrl})` : "none",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<main className="container mx-auto max-w-6xl p-4 -mt-16 grid grid-cols-1 lg:grid-cols-4 gap-8">
|
<main
|
||||||
|
id="main-container"
|
||||||
|
className="container mx-auto max-w-6xl p-4 -mt-16 grid grid-cols-1 lg:grid-cols-4 gap-8"
|
||||||
|
>
|
||||||
{/* Left Sidebar (Profile Card & Top Friends) */}
|
{/* Left Sidebar (Profile Card & Top Friends) */}
|
||||||
<aside className="col-span-1 lg:col-span-1 space-y-6">
|
<aside id="left-sidebar" className="col-span-1 lg:col-span-1 space-y-6">
|
||||||
<div className="sticky top-20 space-y-6">
|
<div id="left-sidebar__inner" className="sticky top-20 space-y-6">
|
||||||
<Card className="p-6 bg-card/80 backdrop-blur-lg">
|
<Card id="profile-card" className="p-6 bg-card/80 backdrop-blur-lg">
|
||||||
<div className="flex justify-between items-start">
|
<div
|
||||||
<div className="flex items-end gap-4">
|
id="profile-card__inner"
|
||||||
<div className="w-24 h-24 rounded-full border-4 border-background shrink-0">
|
className="flex justify-between items-start"
|
||||||
|
>
|
||||||
|
<div id="profile-card__avatar" className="flex items-end gap-4">
|
||||||
|
<div
|
||||||
|
id="profile-card__avatar-image"
|
||||||
|
className="w-24 h-24 rounded-full border-4 border-background shrink-0"
|
||||||
|
>
|
||||||
<UserAvatar
|
<UserAvatar
|
||||||
src={user.avatarUrl}
|
src={user.avatarUrl}
|
||||||
alt={user.displayName}
|
alt={user.displayName}
|
||||||
@@ -104,7 +114,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Action Button */}
|
{/* Action Button */}
|
||||||
<div>
|
<div id="profile-card__action">
|
||||||
{isOwnProfile ? (
|
{isOwnProfile ? (
|
||||||
<Button asChild variant="outline" size="sm">
|
<Button asChild variant="outline" size="sm">
|
||||||
<Link href="/settings/profile">
|
<Link href="/settings/profile">
|
||||||
@@ -120,19 +130,30 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div id="profile-card__info" className="mt-4">
|
||||||
<h1 className="text-2xl font-bold">
|
<h1 id="profile-card__name" className="text-2xl font-bold">
|
||||||
{user.displayName || user.username}
|
{user.displayName || user.username}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p
|
||||||
|
id="profile-card__username"
|
||||||
|
className="text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
@{user.username}
|
@{user.username}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-4 text-sm whitespace-pre-wrap">{user.bio}</p>
|
<p
|
||||||
|
id="profile-card__bio"
|
||||||
|
className="mt-4 text-sm whitespace-pre-wrap"
|
||||||
|
>
|
||||||
|
{user.bio}
|
||||||
|
</p>
|
||||||
|
|
||||||
{isOwnProfile && (
|
{isOwnProfile && (
|
||||||
<div className="flex items-center gap-4 mt-4 text-sm">
|
<div
|
||||||
|
id="profile-card__stats"
|
||||||
|
className="flex items-center gap-4 mt-4 text-sm"
|
||||||
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`/users/${user.username}/following`}
|
href={`/users/${user.username}/following`}
|
||||||
className="hover:underline"
|
className="hover:underline"
|
||||||
@@ -154,7 +175,10 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2 mt-4 text-sm text-muted-foreground">
|
<div
|
||||||
|
id="profile-card__joined"
|
||||||
|
className="flex items-center gap-2 mt-4 text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
<Calendar className="h-4 w-4" />
|
<Calendar className="h-4 w-4" />
|
||||||
<span>
|
<span>
|
||||||
Joined {new Date(user.joinedAt).toLocaleDateString()}
|
Joined {new Date(user.joinedAt).toLocaleDateString()}
|
||||||
@@ -166,7 +190,10 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div className="col-span-1 lg:col-span-3 space-y-4">
|
<div
|
||||||
|
id="profile-card__thoughts"
|
||||||
|
className="col-span-1 lg:col-span-3 space-y-4"
|
||||||
|
>
|
||||||
{topLevelThoughts.map((thought) => (
|
{topLevelThoughts.map((thought) => (
|
||||||
<ThoughtThread
|
<ThoughtThread
|
||||||
key={thought.id}
|
key={thought.id}
|
||||||
@@ -177,7 +204,10 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{topLevelThoughts.length === 0 && (
|
{topLevelThoughts.length === 0 && (
|
||||||
<Card className="flex items-center justify-center h-48">
|
<Card
|
||||||
|
id="profile-card__no-thoughts"
|
||||||
|
className="flex items-center justify-center h-48"
|
||||||
|
>
|
||||||
<p className="text-center text-muted-foreground">
|
<p className="text-center text-muted-foreground">
|
||||||
This user hasn't posted any public thoughts yet.
|
This user hasn't posted any public thoughts yet.
|
||||||
</p>
|
</p>
|
||||||
|
@@ -50,6 +50,7 @@ export function FollowButton({
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
variant={isFollowing ? "secondary" : "default"}
|
variant={isFollowing ? "secondary" : "default"}
|
||||||
|
data-following={isFollowing}
|
||||||
>
|
>
|
||||||
{isFollowing ? (
|
{isFollowing ? (
|
||||||
<>
|
<>
|
||||||
|
@@ -10,7 +10,7 @@ export function Header() {
|
|||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 flex justify-center w-full border-b border-primary/20 bg-background/80 backdrop-blur-xl supports-[backdrop-filter]:bg-background/60 glossy-effect bottom">
|
<header className="sticky top-0 z-50 flex justify-center w-full border-b border-primary/20 bg-background/80 glass-effect glossy-effect bottom rounded-none">
|
||||||
<div className="container flex h-14 items-center px-2">
|
<div className="container flex h-14 items-center px-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Link href="/" className="flex items-center gap-1">
|
<Link href="/" className="flex items-center gap-1">
|
||||||
|
@@ -55,7 +55,7 @@ export function ReplyForm({ parentThoughtId, onReplySuccess }: ReplyFormProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2 pt-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2 p-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="content"
|
name="content"
|
||||||
@@ -64,7 +64,7 @@ export function ReplyForm({ parentThoughtId, onReplySuccess }: ReplyFormProps) {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Post your reply..."
|
placeholder="Post your reply..."
|
||||||
className="resize-none"
|
className="resize-none bg-white glass-effect glossy-efect bottom shadow-fa-sm"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@@ -85,8 +85,10 @@ export function ThoughtCard({
|
|||||||
<div
|
<div
|
||||||
id={thought.id}
|
id={thought.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-card/70 backdrop-blur-lg shadow-fa-md rounded-xl overflow-hidden glossy-effect bottom",
|
"bg-transparent backdrop-blur-lg shadow-fa-md rounded-xl overflow-hidden glossy-effect bottom",
|
||||||
isReply ? "backdrop-blur-sm shadow-fa-sm p-2" : ""
|
isReply
|
||||||
|
? "bg-white/80 glass-effect glossy-effect bottom shadow-fa-sm p-2"
|
||||||
|
: ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{thought.replyToId && isReply && (
|
{thought.replyToId && isReply && (
|
||||||
@@ -163,7 +165,7 @@ export function ThoughtCard({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isReplyOpen && (
|
{isReplyOpen && (
|
||||||
<div className="border-t p-4 border-border/50 bg-background/50 backdrop-blur-sm">
|
<div className="border-t m-4 rounded-2xl border-border/50 bg-secondary/20 ">
|
||||||
<ReplyForm
|
<ReplyForm
|
||||||
parentThoughtId={thought.id}
|
parentThoughtId={thought.id}
|
||||||
onReplySuccess={() => setIsReplyOpen(false)}
|
onReplySuccess={() => setIsReplyOpen(false)}
|
||||||
|
@@ -25,7 +25,7 @@ export function ThoughtThread({
|
|||||||
const directReplies = repliesByParentId.get(thought.id) || [];
|
const directReplies = repliesByParentId.get(thought.id) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-0">
|
<div id={`thought-thread-${thought.id}`} className="flex flex-col gap-0">
|
||||||
<ThoughtCard
|
<ThoughtCard
|
||||||
thought={thought}
|
thought={thought}
|
||||||
author={author}
|
author={author}
|
||||||
@@ -34,7 +34,10 @@ export function ThoughtThread({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{directReplies.length > 0 && (
|
{directReplies.length > 0 && (
|
||||||
<div className="pl-6 border-l-2 border-primary/30 border-dashed ml-6 flex flex-col gap-4 pt-4">
|
<div
|
||||||
|
id={`thought-thread-${thought.id}__replies`}
|
||||||
|
className="pl-6 border-l-2 border-primary border-dashed ml-6 flex flex-col gap-4 pt-4"
|
||||||
|
>
|
||||||
{directReplies.map((reply) => (
|
{directReplies.map((reply) => (
|
||||||
<ThoughtThread // RECURSIVE CALL
|
<ThoughtThread // RECURSIVE CALL
|
||||||
key={reply.id}
|
key={reply.id}
|
||||||
|
@@ -40,19 +40,25 @@ export async function TopFriends({ usernames }: TopFriendsProps) {
|
|||||||
.map((result) => result.value);
|
.map((result) => result.value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-4">
|
<Card id="top-friends" className="p-4">
|
||||||
<CardHeader className="p-0 pb-2">
|
<CardHeader id="top-friends__header" className="p-0 pb-2">
|
||||||
<CardTitle className="text-lg text-shadow-md">Top Friends</CardTitle>
|
<CardTitle id="top-friends__title" className="text-lg text-shadow-md">
|
||||||
|
Top Friends
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-0">
|
<CardContent id="top-friends__content" className="p-0">
|
||||||
{friends.map((friend) => (
|
{friends.map((friend) => (
|
||||||
<Link
|
<Link
|
||||||
|
id={`top-friends__link-${friend.id}`}
|
||||||
href={`/users/${friend.username}`}
|
href={`/users/${friend.username}`}
|
||||||
key={friend.id}
|
key={friend.id}
|
||||||
className="flex items-center gap-3 py-2 px-2 -mx-2 rounded-lg hover:bg-accent/50 transition-colors"
|
className="flex items-center gap-3 py-2 px-2 -mx-2 rounded-lg hover:bg-accent/50 transition-colors"
|
||||||
>
|
>
|
||||||
<UserAvatar src={friend.avatarUrl} alt={friend.username} />
|
<UserAvatar src={friend.avatarUrl} alt={friend.username} />
|
||||||
<span className="text-xs truncate w-full group-hover:underline font-medium text-shadow-sm">
|
<span
|
||||||
|
id={`top-friends__name-${friend.id}`}
|
||||||
|
className="text-xs truncate w-full group-hover:underline font-medium text-shadow-sm"
|
||||||
|
>
|
||||||
{friend.displayName || friend.username}
|
{friend.displayName || friend.username}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
@@ -11,12 +11,13 @@ const buttonVariants = cva(
|
|||||||
variant: {
|
variant: {
|
||||||
// Default button gets blue gradient, gloss, and shadows
|
// Default button gets blue gradient, gloss, and shadows
|
||||||
default:
|
default:
|
||||||
"fa-gradient-blue text-primary-foreground shadow-fa-md hover:bg-primary/90 active:shadow-fa-inner transition-transform active:scale-[0.98] glossy-effect",
|
"glass-effect fa-gradient-blue text-primary-foreground shadow-fa-md hover:bg-primary/90 active:shadow-fa-inner transition-transform active:scale-[0.98] glossy-effect",
|
||||||
// Secondary gets green gradient
|
// Secondary gets green gradient
|
||||||
secondary:
|
secondary:
|
||||||
"fa-gradient-green text-secondary-foreground shadow-fa-md hover:bg-secondary/90 active:shadow-fa-inner transition-transform active:scale-[0.98] glossy-effect",
|
"glass-effect fa-gradient-green text-secondary-foreground shadow-fa-md hover:bg-secondary/90 active:shadow-fa-inner transition-transform active:scale-[0.98] glossy-effect",
|
||||||
// Ghost and Link should be more subtle
|
// Ghost and Link should be more subtle
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground rounded-lg", // Keep them simple, maybe a slight blur/gloss on hover
|
ghost:
|
||||||
|
"glass-effect hover:bg-accent hover:text-accent-foreground rounded-lg", // Keep them simple, maybe a slight blur/gloss on hover
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
// Outline button for a transparent-ish, glassy feel
|
// Outline button for a transparent-ish, glassy feel
|
||||||
outline:
|
outline:
|
||||||
|
@@ -7,11 +7,11 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
<div
|
<div
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative rounded-xl border bg-card/70 backdrop-blur-lg shadow-fa-lg overflow-hidden glossy-effect bottom text-card-foreground flex flex-col gap-6 py-6",
|
"relative rounded-xl border bg-card/70 backdrop-blur-lg shadow-fa-lg overflow-hidden glass-effect glossy-effect bottom text-card-foreground flex flex-col gap-6 py-6",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
></div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,27 +1,27 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Select({
|
function Select({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectGroup({
|
function SelectGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectValue({
|
function SelectValue({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectTrigger({
|
function SelectTrigger({
|
||||||
@@ -30,14 +30,14 @@ function SelectTrigger({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
size?: "sm" | "default"
|
size?: "sm" | "default";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"bg-transparent glass-effect glossy-effect bottom shadow-fa-md border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -47,7 +47,7 @@ function SelectTrigger({
|
|||||||
<ChevronDownIcon className="size-4 opacity-50" />
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectContent({
|
function SelectContent({
|
||||||
@@ -61,7 +61,7 @@ function SelectContent({
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
"bg-popover/90 glass-effect shadow-fa-sm bottom text-popover-foreground 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className
|
className
|
||||||
@@ -82,7 +82,7 @@ function SelectContent({
|
|||||||
<SelectScrollDownButton />
|
<SelectScrollDownButton />
|
||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Content>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectLabel({
|
function SelectLabel({
|
||||||
@@ -95,7 +95,7 @@ function SelectLabel({
|
|||||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectItem({
|
function SelectItem({
|
||||||
@@ -107,7 +107,7 @@ function SelectItem({
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"bg-transparent focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -119,7 +119,7 @@ function SelectItem({
|
|||||||
</span>
|
</span>
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectSeparator({
|
function SelectSeparator({
|
||||||
@@ -132,7 +132,7 @@ function SelectSeparator({
|
|||||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollUpButton({
|
function SelectScrollUpButton({
|
||||||
@@ -150,7 +150,7 @@ function SelectScrollUpButton({
|
|||||||
>
|
>
|
||||||
<ChevronUpIcon className="size-4" />
|
<ChevronUpIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollUpButton>
|
</SelectPrimitive.ScrollUpButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollDownButton({
|
function SelectScrollDownButton({
|
||||||
@@ -168,7 +168,7 @@ function SelectScrollDownButton({
|
|||||||
>
|
>
|
||||||
<ChevronDownIcon className="size-4" />
|
<ChevronDownIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownButton>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -182,4 +182,4 @@ export {
|
|||||||
SelectSeparator,
|
SelectSeparator,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
}
|
};
|
||||||
|
@@ -8,7 +8,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||||||
data-slot="textarea"
|
data-slot="textarea"
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
"shadow-fa-inner transition-shadow",
|
"shadow-fa-inner transition-shadow glass-effect glossy-effect bottom",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
Reference in New Issue
Block a user