Skip to content

Commit

Permalink
🧹: Liveblocks configuration
Browse files Browse the repository at this point in the history
Going to revamp my portfolio a bit. So it meets next14 standards, and best practices.
  • Loading branch information
WomB0ComB0 committed Dec 18, 2023
1 parent b3fdf89 commit ef2524c
Show file tree
Hide file tree
Showing 11 changed files with 1,736 additions and 1,515 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ yarn-error.log*
next-env.d.ts

.vercel
.dccache
.vscode
44 changes: 44 additions & 0 deletions liveblocks.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createClient } from "@liveblocks/client";
import { createRoomContext } from "@liveblocks/react";

const client = createClient({
authEndpoint: "/api/liveblocks-auth",
});

// Presence represents the properties that will exist on every User in the Room
// and that will automatically be kept in sync. Accessible through the
// `user.presence` property. Must be JSON-serializable.
type Presence = {
cursor: { x: number; y: number } | null;
};

// Optionally, Storage represents the shared document that persists in the
// Room, even after all Users leave. Fields under Storage typically are
// LiveList, LiveMap, LiveObject instances, for which updates are
// automatically persisted and synced to all connected clients.
type Storage = {
// animals: LiveList<string>,
// ...
};

// Optionally, UserMeta represents static/readonly metadata on each User, as
// provided by your own custom auth backend (if used). Useful for data that
// will not change during a session, like a User's name or avatar.
type UserMeta = {
info: {
name: string;
color: [string, string];
avatar: string;
};
};

// Optionally, the type of custom events broadcasted and listened for in this
// room. Must be JSON-serializable.
// type RoomEvent = {};

export const {
RoomProvider,
useMyPresence,
useUpdateMyPresence,
useOthersMapped,
} = createRoomContext<Presence, Storage, UserMeta /* RoomEvent */>(client);
2,655 changes: 1,141 additions & 1,514 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
},
"dependencies": {
"@heroicons/react": "^2.0.18",
"@liveblocks/client": "^1.8.0",
"@liveblocks/node": "^1.8.0",
"@liveblocks/react": "^1.8.0",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/node": "20.10.4",
"@types/react": "18.2.38",
Expand Down
42 changes: 41 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import './global.scss'
import React, { useState, useEffect } from 'react';
import { Loader } from '@/components';
import Head from 'next/head';
// import { RoomProvider, useMyPresence } from '../../liveblocks.config';
// import LiveCursors from '@/components/cursor/LiveCursors';
// import { useRouter } from 'next/router';
// import { useMemo, useRef } from 'react';
import { Analytics } from '@vercel/analytics/react';


const RootLayout = ({ children }: { children: React.ReactNode }) => {
const [isLoading, setIsLoading] = useState(true);
// const roomId = useOverrideRoomId("nextjs-live-cursors-advanced");
useEffect(() => {
const timeout = setTimeout(() => {
setIsLoading(false);
Expand All @@ -32,11 +39,44 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => {
<Loader />
) : (
<>
{/* <RoomProvider
id={roomId}
Initialize the cursor position to null when joining the room
initialPresence={{
cursor: null,
}}
> */}
{children}
<Analytics />
{/* </RoomProvider> */}
</>
)}
</>
);
};
export default RootLayout;
export default RootLayout;

// function Example() {
// const cursorPanel = useRef(null);
// const [{ cursor }] = useMyPresence();

// return (
// <main ref={cursorPanel} className={styles.main}>
// <LiveCursors cursorPanel={cursorPanel} />
// <div className={styles.text}>
// {cursor
// ? `${cursor.x} × ${cursor.y}`
// : "Move your cursor to broadcast its position to other people in the room."}
// </div>
// </main>
// );
// }

// function useOverrideRoomId(roomId: string) {
// const { query } = useRouter();
// const overrideRoomId = useMemo(() => {
// return query?.roomId ? `${roomId}-${query.roomId}` : roomId;
// }, [query, roomId]);

// return overrideRoomId;
// }
51 changes: 51 additions & 0 deletions src/components/cursor/Cursor.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.cursor {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
user-select: none;
}

.nameWrapper,
.avatarWrapper {
position: relative;
}

.cursorSvg {
position: absolute;
top: 0;
left: 0;
}

.namePill {
position: absolute;
overflow: hidden;
top: 1rem;
left: 1rem;
padding-top: 0.375rem;
padding-bottom: 0.375rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 500;
white-space: nowrap;
border-radius: 0.5rem;
}

.namePillName {
z-index: 10;
position: relative;
}

.avatar {
display: block;
position: absolute;
top: 16px;
left: 16px;
overflow: hidden;
border-radius: 9999px;
outline-width: 2px;
outline-style: solid;
outline-offset: 2px;
}
161 changes: 161 additions & 0 deletions src/components/cursor/Cursor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React, { useMemo } from "react";
import Image from "next/image";
import { motion } from "framer-motion";
import { getContrastingColor } from "@/utils/getContrastingColor";
import styles from "./Cursor.module.css";

type AllProps = {
variant?: "basic" | "name" | "avatar";
x: number;
y: number;
color: [string, string];
};

type BasicCursorProps = AllProps & {
variant?: "basic";
name?: never;
avatar?: never;
size?: never;
};

type NameCursorProps = AllProps & {
variant: "name";
name: string;
avatar?: never;
size?: never;
};

type AvatarCursorProps = AllProps & {
variant: "avatar";
avatar: string;
name?: never;
size?: number;
};

type CursorProps = BasicCursorProps | NameCursorProps | AvatarCursorProps;

export default function Cursor({
variant = "basic",
x,
y,
color = ["", ""],
name = "",
avatar = "",
size = 36,
}: CursorProps) {
return (
<motion.div
className={styles.cursor}
initial={{ x, y }}
animate={{ x, y }}
transition={{
type: "spring",
bounce: 0.6,
damping: 30,
mass: 0.8,
stiffness: 350,
restSpeed: 0.01,
}}
>
{variant === "basic" ? <BasicCursor color={color} /> : null}
{variant === "name" ? <NameCursor color={color} name={name} /> : null}
{variant === "avatar" ? (
<AvatarCursor color={color} avatar={avatar} size={size} />
) : null}
</motion.div>
);
}

function BasicCursor({ color }: Pick<BasicCursorProps, "color">) {
return (
<svg width="32" height="44" viewBox="0 0 24 36" fill="none">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="500%" y2="0%">
<stop offset="0%" stopColor={color[0]} />
<stop offset="100%" stopColor={color[1]} />
</linearGradient>
</defs>
<path
fill="url(#gradient)"
d="M0.928548 2.18278C0.619075 1.37094 1.42087 0.577818 2.2293 0.896107L14.3863 5.68247C15.2271 6.0135 15.2325 7.20148 14.3947 7.54008L9.85984 9.373C9.61167 9.47331 9.41408 9.66891 9.31127 9.91604L7.43907 14.4165C7.09186 15.2511 5.90335 15.2333 5.58136 14.3886L0.928548 2.18278Z"
/>
</svg>
);
}

function NameCursor({ color, name }: Pick<NameCursorProps, "color" | "name">) {
const textColor = useMemo(
() => (color ? getContrastingColor(color[1]) : undefined),
[color]
);
return (
<div className={styles.nameWrapper}>
<svg
className={styles.cursorSvg}
width="32"
height="44"
viewBox="0 0 24 36"
fill="none"
>
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="500%" y2="0%">
<stop offset="0%" stopColor={color[0]} />
<stop offset="100%" stopColor={color[1]} />
</linearGradient>
</defs>
<path
fill="url(#gradient)"
d="M0.928548 2.18278C0.619075 1.37094 1.42087 0.577818 2.2293 0.896107L14.3863 5.68247C15.2271 6.0135 15.2325 7.20148 14.3947 7.54008L9.85984 9.373C9.61167 9.47331 9.41408 9.66891 9.31127 9.91604L7.43907 14.4165C7.09186 15.2511 5.90335 15.2333 5.58136 14.3886L0.928548 2.18278Z"
/>
</svg>
<div
className={styles.namePill}
style={{
backgroundImage: `linear-gradient(to bottom right, ${color[0]}, ${color[1]})`,
color: textColor,
}}
>
<div className={styles.namePillName}>{name}</div>
</div>
</div>
);
}

function AvatarCursor({
color,
avatar,
size,
}: Pick<AvatarCursorProps, "color" | "avatar" | "size">) {
return (
<div className={styles.avatarWrapper}>
<svg
className={styles.cursorSvg}
width="32"
height="44"
viewBox="0 0 24 36"
fill="none"
>
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="500%" y2="0%">
<stop offset="0%" stopColor={color[0]} />
<stop offset="100%" stopColor={color[1]} />
</linearGradient>
</defs>
<path
fill="url(#gradient)"
d="M0.928548 2.18278C0.619075 1.37094 1.42087 0.577818 2.2293 0.896107L14.3863 5.68247C15.2271 6.0135 15.2325 7.20148 14.3947 7.54008L9.85984 9.373C9.61167 9.47331 9.41408 9.66891 9.31127 9.91604L7.43907 14.4165C7.09186 15.2511 5.90335 15.2333 5.58136 14.3886L0.928548 2.18278Z"
/>
</svg>
<div
className={styles.avatar}
style={{
outlineColor: color[0],
width: size + "px",
height: size + "px",
}}
>
<Image src={avatar} height={size} width={size} alt="" />
</div>
</div>
);
}
Loading

1 comment on commit ef2524c

@vercel
Copy link

@vercel vercel bot commented on ef2524c Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.