Skip to content

Commit 6f4fd8a

Browse files
committed
feat: improve animation and special domains mapping
1 parent f917def commit 6f4fd8a

File tree

15 files changed

+283
-86
lines changed

15 files changed

+283
-86
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "next-whois-ui",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
@@ -18,6 +18,7 @@
1818
"@radix-ui/react-slot": "^1.1.0",
1919
"class-variance-authority": "^0.7.0",
2020
"clsx": "^2.1.0",
21+
"framer-motion": "^11.3.21",
2122
"html-to-image": "^1.11.11",
2223
"lucide-react": "^0.414.0",
2324
"moment": "^2.30.1",

pnpm-lock.yaml

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

src/components/items/rich-textarea.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,26 @@ export default function RichTextarea({
3636
<Button
3737
variant={`outline`}
3838
size={`icon-sm`}
39-
className={cn(`mr-1`, disableCopyAction && `hidden`)}
39+
className={cn(
40+
`mr-1 text-muted-foreground transition hover:text-primary`,
41+
disableCopyAction && `hidden`,
42+
)}
4043
onClick={() => copy(displayValue)}
44+
tapEnabled
4145
>
42-
<CopyIcon className={`w-3 h-3`} />
46+
<CopyIcon className={`w-3.5 h-3.5`} />
4347
</Button>
4448
<Button
4549
variant={`outline`}
4650
size={`icon-sm`}
4751
onClick={() => save(saveFileName || "text.txt", displayValue)}
48-
className={cn(disableSaveAction && `hidden`)}
52+
className={cn(
53+
`text-muted-foreground transition hover:text-primary`,
54+
disableSaveAction && `hidden`,
55+
)}
56+
tapEnabled
4957
>
50-
<DownloadIcon className={`w-3 h-3`} />
58+
<DownloadIcon className={`w-3.5 h-3.5`} />
5159
</Button>
5260
</div>
5361
<TextArea rows={10} readOnly={true} value={displayValue} />

src/components/motion/clickable.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from "react";
2+
import { motion } from "framer-motion";
3+
4+
export interface ClickableProps
5+
extends React.HTMLAttributes<HTMLDivElement>,
6+
React.PropsWithChildren<{}> {
7+
tapScale?: number;
8+
tapDuration?: number;
9+
hoverScale?: number;
10+
}
11+
12+
const Clickable: React.FC<ClickableProps> = ({
13+
children,
14+
className,
15+
tapScale = 0.95,
16+
tapDuration = 0.1,
17+
hoverScale,
18+
}) => {
19+
return (
20+
<motion.div
21+
className={className}
22+
whileTap={{
23+
scale: tapScale,
24+
transition: { duration: tapDuration },
25+
}}
26+
whileHover={hoverScale ? { scale: hoverScale } : {}}
27+
whileFocus={hoverScale ? { scale: hoverScale } : {}}
28+
>
29+
{children}
30+
</motion.div>
31+
);
32+
};
33+
34+
Clickable.displayName = "Clickable";
35+
export default Clickable;

src/components/theme-switch.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Inter } from "next/font/google";
1515

1616
const inter = Inter({ subsets: ["latin"] });
1717

18-
export function ModeToggle() {
18+
export function ThemeToggle() {
1919
const { setTheme } = useTheme();
2020

2121
return (

src/components/ui/button.tsx

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as React from "react";
22
import { Slot } from "@radix-ui/react-slot";
33
import { cva, type VariantProps } from "class-variance-authority";
4-
54
import { cn } from "@/lib/utils";
5+
import Clickable from "@/components/motion/clickable";
66

77
const buttonVariants = cva(
88
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50",
@@ -39,18 +39,51 @@ export interface ButtonProps
3939
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
4040
VariantProps<typeof buttonVariants> {
4141
asChild?: boolean;
42+
tapScale?: number;
43+
tapDuration?: number;
44+
tapClassName?: string;
45+
tapEnabled?: boolean;
4246
}
4347

4448
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
45-
({ className, variant, size, asChild = false, ...props }, ref) => {
49+
(
50+
{
51+
className,
52+
variant,
53+
size,
54+
tapEnabled,
55+
tapClassName,
56+
tapScale,
57+
tapDuration,
58+
asChild = false,
59+
...props
60+
},
61+
ref,
62+
) => {
4663
const Comp = asChild ? Slot : "button";
47-
return (
64+
const Btn = (
4865
<Comp
4966
className={cn(buttonVariants({ variant, size, className }))}
5067
ref={ref}
5168
{...props}
5269
/>
5370
);
71+
72+
const isTap =
73+
tapEnabled || (tapClassName !== undefined && tapScale !== null);
74+
if (isTap) {
75+
return (
76+
<Clickable
77+
className={cn(tapClassName)}
78+
tapScale={tapScale}
79+
tapDuration={tapDuration}
80+
>
81+
{Btn}
82+
</Clickable>
83+
);
84+
}
85+
86+
return Btn;
5487
},
5588
);
5689
Button.displayName = "Button";

src/components/ui/dropdown-menu.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
33
import { Check, ChevronRight, Circle } from "lucide-react";
44

55
import { cn } from "@/lib/utils";
6+
import Clickable from "@/components/motion/clickable";
67

78
const DropdownMenu = DropdownMenuPrimitive.Root;
89

@@ -78,15 +79,17 @@ const DropdownMenuItem = React.forwardRef<
7879
inset?: boolean;
7980
}
8081
>(({ className, inset, ...props }, ref) => (
81-
<DropdownMenuPrimitive.Item
82-
ref={ref}
83-
className={cn(
84-
"relative flex flex-row cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
85-
inset && "pl-8",
86-
className,
87-
)}
88-
{...props}
89-
/>
82+
<Clickable>
83+
<DropdownMenuPrimitive.Item
84+
ref={ref}
85+
className={cn(
86+
"relative flex flex-row cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
87+
inset && "pl-8",
88+
className,
89+
)}
90+
{...props}
91+
/>
92+
</Clickable>
9093
));
9194
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
9295

src/lib/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const VERSION = "0.1.0";
1+
export const VERSION = "0.2.0";
22

33
export const HISTORY_LIMIT: number = intEnv("NEXT_PUBLIC_HISTORY_LIMIT", 6);
44
// The maximum number of history items to keep in the local storage

src/lib/utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from "react";
44
import { toast } from "sonner";
55
import { getDomain } from "tldjs";
66
import { ParsedUrlQuery } from "node:querystring";
7+
import { getSpecialDomain } from "@/lib/whois/lib";
78

89
export function cn(...inputs: ClassValue[]) {
910
return twMerge(clsx(inputs));
@@ -109,8 +110,16 @@ export function countDuration(startTime: number, _endTime?: number): number {
109110
return (endTime - startTime) / 1000; // seconds
110111
}
111112

113+
export function extractDomain(url: string): string | null {
114+
try {
115+
return getDomain(getSpecialDomain(url));
116+
} catch {
117+
return null;
118+
}
119+
}
120+
112121
export function cleanDomain(domain: string): string {
113-
const hostname = getDomain(domain);
122+
const hostname = extractDomain(domain);
114123
if (hostname) {
115124
return hostname;
116125
}

src/lib/whois/lib.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ export type DomainRegex = {
1717
unknownTLD?: boolean;
1818
};
1919

20+
const specialDomains: Record<string, string> = {
21+
"gov.cn": "www.gov.cn",
22+
"cn.com": "www.cn.com",
23+
"com.cn": "www.com.cn",
24+
"org.cn": "www.org.cn",
25+
"net.cn": "www.net.cn",
26+
"edu.cn": "www.edu.cn",
27+
"mil.cn": "www.mil.cn",
28+
};
29+
2030
const defaultRegex: DomainRegex = {
2131
domainName: "Domain Name: *([^\\s]+)",
2232
registrar: "Registrar: *(.+)",
@@ -395,3 +405,7 @@ export function getDomainRegex(domain: string): DomainRegex {
395405
return defaultRegex;
396406
}
397407
}
408+
409+
export function getSpecialDomain(domain: string): string {
410+
return specialDomains[domain.toLowerCase()] ?? domain;
411+
}

src/lib/whois/lookup.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import { MAX_IP_WHOIS_FOLLOW, MAX_WHOIS_FOLLOW } from "@/lib/env";
22
import whois from "whois-raw";
33
import { WhoisResult } from "@/lib/whois/types";
44
import { parseWhoisData } from "@/lib/whois/tld_parser";
5-
import { countDuration, toErrorMessage } from "@/lib/utils";
6-
import { getDomain } from "tldjs";
5+
import { countDuration, extractDomain, toErrorMessage } from "@/lib/utils";
76

87
export function getLookupOptions(domain: string) {
9-
const isDomain = !!getDomain(domain);
8+
const isDomain = !!extractDomain(domain);
109
return {
1110
follow: isDomain ? MAX_WHOIS_FOLLOW : MAX_IP_WHOIS_FOLLOW,
1211
};

0 commit comments

Comments
 (0)