Skip to content

Commit a045428

Browse files
committed
twik: some ui tweks
1 parent 1ede003 commit a045428

12 files changed

Lines changed: 1533 additions & 1309 deletions

File tree

src/App.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const App = () => (
3434
</Routes>
3535
</ErrorBoundary>
3636
</BrowserRouter>
37-
<ToastContainer />
37+
<ToastContainer position="bottom-right" theme="dark" autoClose={2400} />
3838
{/* // vercel */}
3939
{process.env.NODE_ENV === "production" && <Analytics />}
4040
</>

src/Home.jsx

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { useState, useEffect } from "react";
22
import { NavLink } from "react-router-dom";
3-
import { Zap, ExternalLink, Sparkles, Wifi, Wrench, Eye } from "lucide-react";
3+
import {
4+
Zap,
5+
ExternalLink,
6+
Sparkles,
7+
Wifi,
8+
Wrench,
9+
Eye,
10+
SquarePlus,
11+
} from "lucide-react";
412

513
import earthIcon from "@/public/earth.svg";
614
import cn from "@/utils/cn";
@@ -34,6 +42,12 @@ const features = [
3442
},
3543
];
3644

45+
const formatNumber = (num) =>
46+
new Intl.NumberFormat("en-US", {
47+
notation: "compact",
48+
maximumFractionDigits: 1,
49+
}).format(num);
50+
3751
const HomePage = () => (
3852
<>
3953
<HeroSection />
@@ -46,37 +60,30 @@ const HeroSection = () => {
4660
const [stars, setStars] = useState("30+");
4761

4862
useEffect(() => {
49-
const getStarsCount = async () => {
63+
(async () => {
5064
try {
51-
const res = await fetch(
52-
"https://api.github.com/repos/sabeerbikba/dev.tools"
65+
const resp = await fetch(
66+
"https://api.github.com/repos/sabeerbikba/dev.tools",
67+
{
68+
headers: { Accept: "application/vnd.github+json" },
69+
}
70+
);
71+
if (!resp.ok) throw new Error(`GitHub API error: ${resp.status}`);
72+
const data = await resp.json();
73+
const rawCount = parseInt(data?.stargazers_count);
74+
setStars(
75+
rawCount > 1000
76+
? `${formatNumber(rawCount)}+`
77+
: formatNumber(rawCount)
5378
);
54-
if (!res.ok) throw new Error(`GitHub API error: ${res.status}`);
55-
56-
const data = await res.json();
57-
const rawCount = data?.stargazers_count;
58-
59-
const formatNumber = (num) =>
60-
new Intl.NumberFormat("en-US", {
61-
notation: "compact",
62-
maximumFractionDigits: 1,
63-
}).format(num);
64-
65-
if (rawCount > 1000) {
66-
setStars(formatNumber(rawCount) + "+");
67-
} else {
68-
setStars(formatNumber(rawCount));
69-
}
7079
} catch (err) {
7180
console.error("Failed to fetch stars:", err);
7281
}
73-
};
74-
75-
getStarsCount();
82+
})();
7683
}, []);
7784

7885
return (
79-
<section className="relative !py-32 !px-4 text-center overflow-hidden">
86+
<section className="relative py-28 xl:py-32 max-sm:py-20 !px-4 text-center overflow-hidden">
8087
<div className="absolute inset-0 -z-10">
8188
<div className="absolute top-20 left-1/4 w-72 h-72 bg-primary/5 rounded-full blur-3xl" />
8289
<div className="absolute top-40 right-1/4 w-96 h-96 bg-blue-500/5 rounded-full blur-3xl" />
@@ -135,28 +142,28 @@ const HeroSection = () => {
135142
};
136143

137144
const Features = () => (
138-
<section className="py-16 px-4 text-white">
145+
<section className="py-16 xl:py-14 max-sm:py-12 px-4 text-white">
139146
<div className="max-w-7xl mx-auto">
140-
<div className="text-center mb-16">
147+
<div className="text-center mb-16 max-sm:mb-12">
141148
<div className="text-white bg-[#4446a6]/15 inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 text-primary text-sm mb-4">
142149
<Zap className="w-4 h-4" />
143150
Features
144151
</div>
145-
<h2 className="text-4xl font-semibold mb-4">
152+
<h2 className="text-4xl max-sm:text-3xl font-semibold mb-4">
146153
Everything you need for
147154
<span className="block bg-gradient-to-r from-[#4446a6] to-white bg-clip-text text-transparent">
148155
productive development
149156
</span>
150157
</h2>
151158
</div>
152159

153-
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
160+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
154161
{features.map((feature, index) => {
155162
const IconComponent = feature.icon;
156163
return (
157164
<div
158165
key={index}
159-
className="group relative p-6 rounded-xl border bg-card hover:shadow-lg transition-all duration-300 hover:-translate-y-1 border border-gray-500 hover:border-gray-600 bg-[#374151]/20 hover:bg-[#374151]/30"
166+
className="group max-md:max-w-[510px] max-md:mx-auto relative p-6 rounded-xl border bg-card hover:shadow-lg transition-all duration-300 hover:-translate-y-1 border border-gray-500 hover:border-gray-600 bg-[#374151]/20 hover:bg-[#374151]/30"
160167
>
161168
<div className="mb-4">
162169
<div className="w-12 h-12 rounded-lg bg-[#4446a6]/10 flex items-center justify-center group-hover:bg-[#4446a6]/20 transition-colors">
@@ -181,7 +188,10 @@ const Features = () => (
181188
);
182189

183190
const Tools = () => (
184-
<section id="tools" className="!py-16 pt-[120px]! !px-4 relative">
191+
<section
192+
id="tools"
193+
className="py-16 pt-[90px] xl:pt-[120px] max-sm:pt-[80px] px-4 relative"
194+
>
185195
<div className="absolute inset-0 -z-10">
186196
<div className="absolute top-0 left-1/4 w-64 h-64 bg-purple-500/5 rounded-full blur-3xl" />
187197
<div className="absolute bottom-0 right-1/4 w-80 h-80 bg-blue-500/5 rounded-full blur-3xl" />
@@ -197,7 +207,7 @@ const Tools = () => (
197207
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center">
198208
{routes
199209
.slice(1, -1)
200-
.map(({ name, description, path, category, icon, isPopular }, i) => (
210+
.map(({ description, path, category, icon, isPopular, isNew }, i) => (
201211
<ToolCard
202212
key={i}
203213
name={formatToolName(path).titleCase}
@@ -206,6 +216,7 @@ const Tools = () => (
206216
category={category}
207217
icon={icon}
208218
isPopular={isPopular}
219+
isNew={isNew}
209220
/>
210221
))}
211222
</div>
@@ -253,7 +264,15 @@ const CardContent = ({ className, ...props }) => (
253264
/>
254265
);
255266

256-
const ToolCard = ({ name, description, path, category, icon, isPopular }) => (
267+
const ToolCard = ({
268+
name,
269+
description,
270+
path,
271+
category,
272+
icon,
273+
isPopular,
274+
isNew,
275+
}) => (
257276
<Card className="group max-w-[410px] hover:shadow-xl hover:shadow-primary/5 transition-all duration-300 hover:-translate-y-1 relative overflow-hidden">
258277
{isPopular && (
259278
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-blue-500/5 pointer-events-none" />
@@ -269,12 +288,25 @@ const ToolCard = ({ name, description, path, category, icon, isPopular }) => (
269288
<CardTitle className="group-hover:text-[#e6e6e6] transition-colors">
270289
{name}
271290
</CardTitle>
272-
{isPopular && (
291+
{(isPopular || isNew) && (
273292
<div className="flex items-center gap-1 !mt-1">
274-
<Sparkles className="w-3 h-3 fill-yellow-500 text-yellow-500" />
275-
<span className="text-xs font-medium text-yellow-600">
276-
Popular
277-
</span>
293+
{isPopular && (
294+
<>
295+
<Sparkles className="w-3 h-3 fill-yellow-500 text-yellow-500" />
296+
<span className="text-xs font-medium text-yellow-600">
297+
Popular
298+
</span>
299+
</>
300+
)}
301+
{isNew && (
302+
<>
303+
{" "}
304+
<SquarePlus className="w-3 h-3 text-red-400" />
305+
<span className="text-xs font-medium text-red-500">
306+
New
307+
</span>
308+
</>
309+
)}
278310
</div>
279311
)}
280312
</div>
@@ -286,9 +318,7 @@ const ToolCard = ({ name, description, path, category, icon, isPopular }) => (
286318
</CardHeader>
287319

288320
<CardContent>
289-
<p className="!mb-4 leading-relaxed text-sm">
290-
{description}
291-
</p>
321+
<p className="!mb-4 leading-relaxed text-sm">{description}</p>
292322
<NavLink
293323
className="inline-flex-center group-hover:bg-[#4446a6] whitespace-nowrap text-sm font-medium border h-8 rounded-md !px-3 w-full gap-2 group-hover:bg-primary group-hover:text-[#4446a6]-foreground border-gray-200 group-hover:border-gray-300 transition-all duration-200"
294324
to={path}

src/common/BasicBtn.jsx

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
1-
import PropTypes from 'prop-types';
1+
import PropTypes from "prop-types";
2+
import cn from "@/utils/cn";
23

34
const BasicBtn = ({
4-
onClick,
55
btnText,
6-
classNames = '',
6+
onClick,
7+
classNames = "",
78
styles = {},
89
btnDisabled,
9-
}) => {
10-
// Nothing fancy just added styles
11-
12-
return (
13-
<button
14-
onClick={onClick}
15-
className={classNames + ' btn'} // Some Tailwind class names require the !important modifier due to the btn class
16-
style={styles}
17-
disabled={btnDisabled}
18-
>{btnText}</button>
19-
);
20-
};
10+
}) => (
11+
<button
12+
onClick={onClick}
13+
className={cn(classNames, "btn cursor-pointer capitalize")} // Some Tailwind class names require the !important modifier due to the btn class
14+
style={styles}
15+
disabled={btnDisabled}
16+
>
17+
{btnText}
18+
</button>
19+
);
2120

2221
BasicBtn.propTypes = {
23-
onClick: PropTypes.func.isRequired,
2422
btnText: PropTypes.string.isRequired,
23+
onClick: PropTypes.func.isRequired,
2524
className: PropTypes.string,
2625
styles: PropTypes.object,
2726
btnDisabled: PropTypes.bool,
28-
}
27+
};
2928

30-
export default BasicBtn;
29+
export default BasicBtn;

src/common/CopyBtn.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default function CopyBtn({
99
svg,
1010
setCopyBtnDisabled,
1111
disabled = false,
12-
className,
12+
className = "",
1313
}) {
1414
const [isCopying, setIsCopying] = useState(false);
1515

@@ -18,8 +18,6 @@ export default function CopyBtn({
1818
setIsCopying(true);
1919
await navigator.clipboard.writeText(copyText.toString());
2020
toast.success('text-copied', {
21-
position: 'bottom-right',
22-
theme: 'dark',
2321
autoClose: 1700,
2422
onClose: () => {
2523
setIsCopying(false);

src/common/ToggleSwitch.jsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import PropTypes from "prop-types";
2+
import cn from "@/utils/cn";
3+
4+
const ToggleSwitch = ({
5+
enabled,
6+
onClick,
7+
falseText,
8+
trueText,
9+
className,
10+
smallVersion,
11+
}) => (
12+
<div
13+
className={cn((trueText || falseText) && "flex items-center", className)}
14+
onClick={onClick}
15+
>
16+
{falseText && <span>Editor</span>}
17+
<div
18+
className={cn(
19+
"flex items-center bg-gray-300 rounded-full p-1 cursor-pointer transition-colors duration-300 mx-2",
20+
smallVersion
21+
? "w-7 h-4 relative -z-20"
22+
: "w-14 h-8 ",
23+
enabled ? "bg-indigo-500" : "bg-gray-400"
24+
)}
25+
>
26+
<div
27+
className={cn(
28+
"bg-white rounded-full shadow-md transform transition-transform duration-300",
29+
smallVersion ? "size-3 -z-10" : "size-6",
30+
enabled
31+
? smallVersion
32+
? "translate-x-3"
33+
: "translate-x-6"
34+
: "translate-x-0"
35+
)}
36+
/>
37+
</div>
38+
{trueText && <span>Preview</span>}
39+
</div>
40+
);
41+
42+
ToggleSwitch.propTypes = {
43+
enabled: PropTypes.bool.isRequired,
44+
onClick: PropTypes.func.isRequired,
45+
falseText: PropTypes.string,
46+
trueText: PropTypes.string,
47+
className: PropTypes.string,
48+
smallVersion: PropTypes.bool,
49+
};
50+
51+
export default ToggleSwitch;

src/components/EditorSplitViewLayout.jsx

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import PropTypes from "prop-types";
33

44
import cn from "@/utils/cn";
55
import useMediaQuery from "@/hooks/useMediaQuery";
6+
import ToggleSwitch from "@/common/ToggleSwitch";
67

78
const EditorSplitViewLayout = ({ editorBtns, editor, preview }) => {
89
const [enabledEditor, setEnabledEditor] = useState(true);
9-
const isMobile = useMediaQuery("max-width: 640px");
10+
const isMobile = useMediaQuery("max-width: 640px");
1011

1112
const btnsBaseClass =
1213
"flex justify-around h-[5%] [&>button]:w-[30%] [&>button]:h-[92%] [&>button]:rounded-sm [&>button]:cursor-pointer";
@@ -16,7 +17,7 @@ const EditorSplitViewLayout = ({ editorBtns, editor, preview }) => {
1617
<div
1718
className={cn(
1819
"sm:w-1/2 max-sm:w-full sm:h-full max-sm:h-[80%] flex flex-col bg-[#808080cc]",
19-
enabledEditor || isMobile && "hidden h-0"
20+
enabledEditor || (isMobile && "hidden h-0")
2021
)}
2122
>
2223
<div className={cn(btnsBaseClass, "max-sm:hidden")}>{editorBtns}</div>
@@ -27,31 +28,23 @@ const EditorSplitViewLayout = ({ editorBtns, editor, preview }) => {
2728
<div
2829
className={cn(
2930
"sm:w-1/2 max-sm:w-full sm:h-full max-sm:h-[80%] text-white max-sm:border-b max-sm:border-white",
30-
!enabledEditor || isMobile && "hidden"
31+
!enabledEditor || (isMobile && "hidden")
3132
)}
3233
>
3334
{preview}
3435
</div>
3536
<div className={cn(btnsBaseClass, "sm:hidden mt-1")}>{editorBtns}</div>
36-
<div className="flex justify-end items-center h-10 pt-2 mr-8 sm:hidden">
37-
<span>Editor</span>
38-
<div
39-
onClick={() => setEnabledEditor((prev) => !prev)}
40-
className={`mx-2 w-14 h-8 flex items-center bg-gray-300 rounded-full p-1 cursor-pointer transition-colors duration-300 ${
41-
!enabledEditor ? "bg-indigo-500" : "bg-gray-400"
42-
}`}
43-
>
44-
<div
45-
className={`bg-white w-6 h-6 rounded-full shadow-md transform transition-transform duration-300 ${
46-
!enabledEditor ? "translate-x-6" : "translate-x-0"
47-
}`}
48-
/>
49-
</div>
50-
<span>Preview</span>
51-
</div>
37+
<ToggleSwitch
38+
falseText="Editor"
39+
trueText="Preview"
40+
enabled={!enabledEditor}
41+
onClick={() => setEnabledEditor((prev) => !prev)}
42+
className="h-10 pt-2 mr-8 sm:hidden justify-end"
43+
/>
5244
</div>
5345
);
5446
};
47+
5548
EditorSplitViewLayout.PropTypes = {
5649
editorBtns: PropTypes.element.isRequired,
5750
editor: PropTypes.element.isRequired,

0 commit comments

Comments
 (0)