tangled
alpha
login
or
join now
isuggest.selfce.st
/
strand
3
fork
atom
alternative tangled frontend (extremely wip)
3
fork
atom
overview
issues
pulls
pipelines
feat: abstract button
serenity
2 weeks ago
f3cb8085
6f91a304
+94
-8
2 changed files
expand all
collapse all
unified
split
src
components
Animated
Button.tsx
Auth
SignOutButton.tsx
+75
src/components/Animated/Button.tsx
···
1
1
+
import { motion, Transition, Variants } from "motion/react";
2
2
+
import { MouseEventHandler, ReactNode, useState } from "react";
3
3
+
4
4
+
export const Button = ({
5
5
+
className,
6
6
+
onClick,
7
7
+
icon,
8
8
+
iconVariants = {
9
9
+
rest: { rotate: 0 },
10
10
+
active: { rotate: [0, -10, 10, -10, 10, 0, 0, 0] },
11
11
+
},
12
12
+
iconTransitions = { duration: 0.3, ease: "easeInOut" },
13
13
+
iconPosition = "left",
14
14
+
iconClassName = "text-accent",
15
15
+
label,
16
16
+
labelClassName = "text-accent",
17
17
+
underlineClassName = "bg-accent",
18
18
+
disabled = false,
19
19
+
}: {
20
20
+
className?: string;
21
21
+
onClick?: MouseEventHandler<HTMLButtonElement>;
22
22
+
iconVariants?: Variants;
23
23
+
iconTransitions?: Transition;
24
24
+
iconClassName?: string;
25
25
+
icon?: ReactNode;
26
26
+
iconPosition?: "left" | "right";
27
27
+
labelClassName?: string;
28
28
+
label: string;
29
29
+
underlineClassName?: string;
30
30
+
disabled?: boolean;
31
31
+
}) => {
32
32
+
const [isIconWiggle, setIsIconWiggle] = useState(false);
33
33
+
34
34
+
const iconElement = icon && (
35
35
+
<motion.div
36
36
+
variants={iconVariants}
37
37
+
initial="rest"
38
38
+
transition={iconTransitions}
39
39
+
className={iconClassName}
40
40
+
animate={isIconWiggle ? "active" : "rest"}
41
41
+
>
42
42
+
{icon}
43
43
+
</motion.div>
44
44
+
);
45
45
+
46
46
+
return (
47
47
+
<motion.button
48
48
+
onClick={onClick}
49
49
+
className={`flex items-center gap-2 pl-2 ${className}`}
50
50
+
initial="initial"
51
51
+
whileHover="hover"
52
52
+
onHoverStart={() => setIsIconWiggle(true)}
53
53
+
onAnimationComplete={() => setIsIconWiggle(false)}
54
54
+
disabled={disabled}
55
55
+
>
56
56
+
{iconPosition === "left" && iconElement}
57
57
+
<motion.div className="relative inline-block">
58
58
+
<p className={labelClassName}>{label}</p>
59
59
+
<motion.span
60
60
+
className={`absolute bottom-1 left-0 h-0.25 w-full origin-center ${underlineClassName}`}
61
61
+
variants={{
62
62
+
initial: { scaleX: 0 },
63
63
+
hover: { scaleX: 1 },
64
64
+
}}
65
65
+
transition={{
66
66
+
type: "spring",
67
67
+
duration: 0.2,
68
68
+
bounce: 0.3,
69
69
+
}}
70
70
+
/>
71
71
+
</motion.div>
72
72
+
{iconPosition === "right" && iconElement}
73
73
+
</motion.button>
74
74
+
);
75
75
+
};
+19
-8
src/components/Auth/SignOutButton.tsx
···
1
1
+
import { Button } from "@/components/Animated/Button";
1
2
import { LucideLogOut } from "@/components/Icons/LucideLogOut";
2
3
import { useOAuth } from "@/lib/oauth";
3
3
-
import { useNavigate, useRouter } from "@tanstack/react-router";
4
4
+
import { useNavigate } from "@tanstack/react-router";
4
5
5
6
export const SignOutButton = () => {
6
7
const { signOut } = useOAuth();
7
8
const navigate = useNavigate();
8
8
-
const router = useRouter();
9
9
10
10
const handleSignOut = () => {
11
11
signOut();
12
12
navigate({ to: "/" });
13
13
};
14
14
15
15
+
const icon = <LucideLogOut />;
16
16
+
15
17
return (
16
16
-
<button
17
17
-
className="flex cursor-pointer items-center gap-2 pl-2"
18
18
+
<Button
19
19
+
icon={icon}
20
20
+
label="Sign Out"
21
21
+
className="cursor-pointer"
22
22
+
labelClassName="text-sm text-negative"
18
23
onClick={handleSignOut}
19
19
-
>
20
20
-
<LucideLogOut />
21
21
-
<p className="text-sm">Sign Out</p>
22
22
-
</button>
24
24
+
iconTransitions={{ duration: 0.2, ease: "easeInOut" }}
25
25
+
iconVariants={{
26
26
+
active: {
27
27
+
x: [0, 3, -3, 0],
28
28
+
opacity: [1, 0, 0, 1],
29
29
+
},
30
30
+
}}
31
31
+
iconClassName="text-negative"
32
32
+
underlineClassName="bg-negative"
33
33
+
/>
23
34
);
24
35
};