Skip to content

Commit 31f5b9c

Browse files
committed
feat: Add @radix-ui/react-hover-card package for signup ui update
1 parent bdb5d30 commit 31f5b9c

File tree

4 files changed

+157
-40
lines changed

4 files changed

+157
-40
lines changed

client/package-lock.json

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@radix-ui/react-collapsible": "^1.1.1",
1717
"@radix-ui/react-dialog": "^1.1.2",
1818
"@radix-ui/react-dropdown-menu": "^2.1.2",
19+
"@radix-ui/react-hover-card": "^1.1.2",
1920
"@radix-ui/react-icons": "^1.3.0",
2021
"@radix-ui/react-label": "^2.1.0",
2122
"@radix-ui/react-navigation-menu": "^1.2.0",

client/src/components/Auth/user-auth-form-signup.tsx

Lines changed: 98 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { Icons } from "@/components/ui/icons";
99
import { Button } from "@/components/ui/button";
1010
import { Input } from "@/components/ui/input";
1111
import { Label } from "@/components/ui/label";
12+
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
13+
import { Check, X } from "lucide-react";
1214

1315
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> { }
1416
interface PasswordRules {
@@ -31,6 +33,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
3133
const [usernameError, setUsernameError] = useState<string>("");
3234
const [passwordRules, setPasswordRules] = useState<PasswordRules | null>(null);
3335
const [isPasswordValid, setIsPasswordValid] = useState<boolean>(false);
36+
const allRulesPassed = passwordRules && Object.values(passwordRules).every((passed) => passed === true);
3437
const navigate = useNavigate();
3538

3639
// Username availability check with debounce
@@ -157,26 +160,57 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
157160
<Label className="sr-only" htmlFor="username">
158161
Username
159162
</Label>
160-
<Input
161-
id="username"
162-
placeholder="Username"
163-
type="text"
164-
autoCapitalize="none"
165-
autoComplete="username"
166-
autoCorrect="off"
167-
disabled={isLoading}
168-
value={username}
169-
onChange={(e) => setUsername(e.target.value)}
170-
className='dark:bg-zinc-900'
171-
required
172-
/>
173-
{isUsernameAvailable === false && (
174-
<p className="text-red-500">Username is not available</p>
175-
)}
176-
{isUsernameAvailable === true && (
177-
<p className="text-green-500">Username is available</p>
178-
)}
179-
{usernameError && <p className="text-red-500">{usernameError}</p>}
163+
164+
{/* Container for input and icon */}
165+
<div className="relative flex items-center">
166+
<Input
167+
id="username"
168+
placeholder="Username"
169+
type="text"
170+
autoCapitalize="none"
171+
autoComplete="username"
172+
autoCorrect="off"
173+
disabled={isLoading}
174+
value={username}
175+
onChange={(e) => setUsername(e.target.value)}
176+
className="dark:bg-zinc-900 pr-10" // Add padding to the right for the icon
177+
required
178+
/>
179+
180+
{/* HoverCard for the green tick */}
181+
{isUsernameAvailable && (
182+
<HoverCard>
183+
<HoverCardTrigger asChild>
184+
<Check className="absolute right-3 text-green-500" />
185+
</HoverCardTrigger>
186+
<HoverCardContent className="text-green-500 text-sm bg-zinc-900">
187+
Username is available
188+
</HoverCardContent>
189+
</HoverCard>
190+
)}
191+
{isUsernameAvailable === false && (
192+
<HoverCard>
193+
<HoverCardTrigger asChild>
194+
<X className="absolute right-3 text-red-500" />
195+
</HoverCardTrigger>
196+
<HoverCardContent className="text-red-500 text-sm bg-zinc-900">
197+
Username is not available
198+
Username is not available
199+
</HoverCardContent>
200+
</HoverCard>
201+
)}
202+
203+
{usernameError && (
204+
<HoverCard>
205+
<HoverCardTrigger asChild>
206+
<X className="absolute right-3 text-red-500" />
207+
</HoverCardTrigger>
208+
<HoverCardContent className="text-red-500 text-sm bg-zinc-900">
209+
{usernameError}
210+
</HoverCardContent>
211+
</HoverCard>
212+
)}
213+
</div>
180214
</div>
181215
<div className="grid gap-1">
182216
<Label className="sr-only" htmlFor="email">
@@ -200,26 +234,50 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
200234
<Label className="sr-only" htmlFor="password">
201235
Password
202236
</Label>
203-
<Input
204-
id="password"
205-
placeholder="Password"
206-
type="password"
207-
autoComplete="current-password"
208-
disabled={isLoading}
209-
value={password}
210-
onChange={handlePasswordChange}
211-
className='dark:bg-zinc-900'
212-
required
213-
/>
214-
{passwordRules &&
215-
Object.entries(passwordRules).map(([rule, passed]) => (
216-
<p
217-
key={rule}
218-
className={passed ? "text-green-500" : "text-red-500"}
219-
>
220-
{passwordRuleMessages[rule as keyof PasswordRules]}
221-
</p>
222-
))}
237+
<div className="relative flex items-center">
238+
<Input
239+
id="password"
240+
placeholder="Password"
241+
type="password"
242+
autoComplete="current-password"
243+
disabled={isLoading}
244+
value={password}
245+
onChange={handlePasswordChange}
246+
className="dark:bg-zinc-900 pr-10"
247+
required
248+
/>
249+
{allRulesPassed && (
250+
<HoverCard>
251+
<HoverCardTrigger asChild>
252+
<Check className="absolute right-3 text-green-500" />
253+
</HoverCardTrigger>
254+
<HoverCardContent className="text-green-500 text-sm bg-zinc-900">
255+
{passwordRules &&
256+
Object.entries(passwordRules).map(([rule, passed]) => (
257+
<p key={rule} className={passed ? "text-green-500" : "text-red-500"}>
258+
{passwordRuleMessages[rule as keyof typeof passwordRuleMessages]}
259+
</p>
260+
))}
261+
</HoverCardContent>
262+
</HoverCard>
263+
)}
264+
{!allRulesPassed && (
265+
<HoverCard>
266+
<HoverCardTrigger asChild>
267+
<X className="absolute right-3 text-red-500" />
268+
</HoverCardTrigger>
269+
<HoverCardContent className="text-red-500 text-sm bg-zinc-900">
270+
{passwordRules &&
271+
Object.entries(passwordRules).map(([rule, passed]) => (
272+
<p key={rule} className={passed ? "text-green-500" : "text-red-500"}>
273+
{passwordRuleMessages[rule as keyof typeof passwordRuleMessages]}
274+
</p>
275+
))}
276+
</HoverCardContent>
277+
</HoverCard>
278+
)}
279+
</div>
280+
223281
</div>
224282
<Button
225283
disabled={
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from "react"
2+
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3+
4+
import { cn } from "@/lib/utils"
5+
6+
const HoverCard = HoverCardPrimitive.Root
7+
8+
const HoverCardTrigger = HoverCardPrimitive.Trigger
9+
10+
const HoverCardContent = React.forwardRef<
11+
React.ElementRef<typeof HoverCardPrimitive.Content>,
12+
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
13+
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14+
<HoverCardPrimitive.Content
15+
ref={ref}
16+
align={align}
17+
sideOffset={sideOffset}
18+
className={cn(
19+
"z-50 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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",
20+
className
21+
)}
22+
{...props}
23+
/>
24+
))
25+
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
26+
27+
export { HoverCard, HoverCardTrigger, HoverCardContent }

0 commit comments

Comments
 (0)