DEV Community

Cover image for Using Nextjs, Aceternity UI and Shadcn-UI all together
ManashAnand
ManashAnand

Posted on

Using Nextjs, Aceternity UI and Shadcn-UI all together

Hey Folks,
I am Manash Anand .
Let’s came directly to the topic
Here is my webapp hosted :- AI-Carnival

Here is github link :- Github

So , I was wondering how to make a cool looking webapp with next
Two things came in my mind

The best part
Both are made on top of tailwind which is compatible with Nextjs

So, first lets start with shadcn ui
Here are the commands
( I’m using npm for my perference , you can use any npm ,yarn, pnpm ,bun also use command from official sites for better copy pasting here there are some restrictions)

npx create-next-app@latest my-app — typescript — tailwind — eslint

npx shadcn-ui@latest init
Enter fullscreen mode Exit fullscreen mode

It will ask you some question , you decide whatever suits you better

Now let’s go for Aceternity UI

step 1:

npm i framer-motion clsx tailwind-merge
Enter fullscreen mode Exit fullscreen mode

( this will install framer-motion clsx and tailwind-merge )

  • Framer Motion provides smooth animations
  • clsx generates dynamic CSS class names
  • tailwind-merge simplifies merging Tailwind CSS classes

step 2:

Now some more dependencies

npm i three three-globe @react-three/fiber @react-three/drei
Enter fullscreen mode Exit fullscreen mode

( this packages are used to render different important package )

step 3:

Make a file in root level
utils/cn.ts

step 4:

put this code into the cn.ts

import { ClassValue, clsx } from “clsx”;
import { twMerge } from “tailwind-merge”;

export function cn(…inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Enter fullscreen mode Exit fullscreen mode

step 5:

copy this json file ( hover over it )

step 5: copy the source code in components/ui/globe.tsx ( at root level )

the source code is below

“use client”;
import { useEffect, useRef, useState } from “react”;
import { Color, Scene, Fog, PerspectiveCamera, Vector3 } from “three”;
import ThreeGlobe from “three-globe”;
import { useThree, Object3DNode, Canvas, extend } from “@react-three/fiber”;
import { OrbitControls } from “@react-three/drei”;
import countries from “@/data/globe.json”;
declare module “@react-three/fiber” {
interface ThreeElements {
threeGlobe: Object3DNode<ThreeGlobe, typeof ThreeGlobe>;
}
}

extend({ ThreeGlobe });

const RING_PROPAGATION_SPEED = 3;
const aspect = 1.2;
const cameraZ = 300;

type Position = {
order: number;
startLat: number;
startLng: number;
endLat: number;
endLng: number;
arcAlt: number;
color: string;
};

export type GlobeConfig = {
pointSize?: number;
globeColor?: string;
showAtmosphere?: boolean;
atmosphereColor?: string;
atmosphereAltitude?: number;
emissive?: string;
emissiveIntensity?: number;
shininess?: number;
polygonColor?: string;
ambientLight?: string;
directionalLeftLight?: string;
directionalTopLight?: string;
pointLight?: string;
arcTime?: number;
arcLength?: number;
rings?: number;
maxRings?: number;
initialPosition?: {
lat: number;
lng: number;
};
autoRotate?: boolean;
autoRotateSpeed?: number;
};

interface WorldProps {
globeConfig: GlobeConfig;
data: Position[];
}

let numbersOfRings = [0];

export function Globe({ globeConfig, data }: WorldProps) {
const [globeData, setGlobeData] = useState<
| {
size: number;
order: number;
color: (t: number) => string;
lat: number;
lng: number;
}[]
| null
>(null);

const globeRef = useRef<ThreeGlobe | null>(null);

const defaultProps = {
pointSize: 1,
atmosphereColor: “#ffffff”,
showAtmosphere: true,
atmosphereAltitude: 0.1,
polygonColor: “rgba(255,255,255,0.7)”,
globeColor: “#1d072e”,
emissive: “#000000”,
emissiveIntensity: 0.1,
shininess: 0.9,
arcTime: 2000,
arcLength: 0.9,
rings: 1,
maxRings: 3,
…globeConfig,
};

useEffect(() => {
if (globeRef.current) {
_buildData();
_buildMaterial();
}
}, [globeRef.current]);

const _buildMaterial = () => {
if (!globeRef.current) return;

const globeMaterial = globeRef.current.globeMaterial() as unknown as {
color: Color;
emissive: Color;
emissiveIntensity: number;
shininess: number;
};
globeMaterial.color = new Color(globeConfig.globeColor);
globeMaterial.emissive = new Color(globeConfig.emissive);
globeMaterial.emissiveIntensity = globeConfig.emissiveIntensity || 0.1;
globeMaterial.shininess = globeConfig.shininess || 0.9;
};

const _buildData = () => {
const arcs = data;
let points = [];
for (let i = 0; i < arcs.length; i++) {
const arc = arcs[i];
const rgb = hexToRgb(arc.color) as { r: number; g: number; b: number };
points.push({
size: defaultProps.pointSize,
order: arc.order,
color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 — t})`,
lat: arc.startLat,
lng: arc.startLng,
});
points.push({
size: defaultProps.pointSize,
order: arc.order,
color: (t: number) => `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${1 — t})`,
lat: arc.endLat,
lng: arc.endLng,
});
}

// remove duplicates for same lat and lng
const filteredPoints = points.filter(
(v, i, a) =>
a.findIndex((v2) =>
[“lat”, “lng”].every(
(k) => v2[k as “lat” | “lng”] === v[k as “lat” | “lng”]
)
) === i
);

setGlobeData(filteredPoints);
};

useEffect(() => {
if (globeRef.current && globeData) {
globeRef.current
.hexPolygonsData(countries.features)
.hexPolygonResolution(3)
.hexPolygonMargin(0.7)
.showAtmosphere(defaultProps.showAtmosphere)
.atmosphereColor(defaultProps.atmosphereColor)
.atmosphereAltitude(defaultProps.atmosphereAltitude)
.hexPolygonColor((e) => {
return defaultProps.polygonColor;
});
startAnimation();
}
}, [globeData]);

const startAnimation = () => {
if (!globeRef.current || !globeData) return;

globeRef.current
.arcsData(data)
.arcStartLat((d) => (d as { startLat: number }).startLat * 1)
.arcStartLng((d) => (d as { startLng: number }).startLng * 1)
.arcEndLat((d) => (d as { endLat: number }).endLat * 1)
.arcEndLng((d) => (d as { endLng: number }).endLng * 1)
.arcColor((e: any) => (e as { color: string }).color)
.arcAltitude((e) => {
return (e as { arcAlt: number }).arcAlt * 1;
})
.arcStroke((e) => {
return [0.32, 0.28, 0.3][Math.round(Math.random() * 2)];
})
.arcDashLength(defaultProps.arcLength)
.arcDashInitialGap((e) => (e as { order: number }).order * 1)
.arcDashGap(15)
.arcDashAnimateTime((e) => defaultProps.arcTime);

globeRef.current
.pointsData(data)
.pointColor((e) => (e as { color: string }).color)
.pointsMerge(true)
.pointAltitude(0.0)
.pointRadius(2);

globeRef.current
.ringsData([])
.ringColor((e: any) => (t: any) => e.color(t))
.ringMaxRadius(defaultProps.maxRings)
.ringPropagationSpeed(RING_PROPAGATION_SPEED)
.ringRepeatPeriod(
(defaultProps.arcTime * defaultProps.arcLength) / defaultProps.rings
);
};

useEffect(() => {
if (!globeRef.current || !globeData) return;

const interval = setInterval(() => {
if (!globeRef.current || !globeData) return;
numbersOfRings = genRandomNumbers(
0,
data.length,
Math.floor((data.length * 4) / 5)
);

globeRef.current.ringsData(
globeData.filter((d, i) => numbersOfRings.includes(i))
);
}, 2000);

return () => {
clearInterval(interval);
};
}, [globeRef.current, globeData]);

return (
<>
<threeGlobe ref={globeRef} />
</>
);
}

export function WebGLRendererConfig() {
const { gl, size } = useThree();

useEffect(() => {
gl.setPixelRatio(window.devicePixelRatio);
gl.setSize(size.width, size.height);
gl.setClearColor(0xffaaff, 0);
}, []);

return null;
}

export function World(props: WorldProps) {
const { globeConfig } = props;
const scene = new Scene();
scene.fog = new Fog(0xffffff, 400, 2000);
return (
<Canvas scene={scene} camera={new PerspectiveCamera(50, aspect, 180, 1800)}>
<WebGLRendererConfig />
<ambientLight color={globeConfig.ambientLight} intensity={0.6} />
<directionalLight
color={globeConfig.directionalLeftLight}
position={new Vector3(-400, 100, 400)}
/>
<directionalLight
color={globeConfig.directionalTopLight}
position={new Vector3(-200, 500, 200)}
/>
<pointLight
color={globeConfig.pointLight}
position={new Vector3(-200, 500, 200)}
intensity={0.8}
/>
<Globe {…props} />
<OrbitControls
enablePan={false}
enableZoom={false}
minDistance={cameraZ}
maxDistance={cameraZ}
autoRotateSpeed={1}
autoRotate={true}
minPolarAngle={Math.PI / 3.5}
maxPolarAngle={Math.PI — Math.PI / 3}
/>
</Canvas>
);
}

export function hexToRgb(hex: string) {
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});

var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}

export function genRandomNumbers(min: number, max: number, count: number) {
const arr = [];
while (arr.length < count) {
const r = Math.floor(Math.random() * (max — min)) + min;
if (arr.indexOf(r) === -1) arr.push(r);
}

return arr;
}
Enter fullscreen mode Exit fullscreen mode

And done everything work fine

If you find something interesting and helpfull
give my repo a star REPO_LINK
You can follow me for interesting stuff
Linkedin

Twitter

Portfolio

Github

Mail: anandmanash321@gmail.com

I am also a
Client ( who can provide jobs/Intern/Gigs )

Freelancer ( who will solve your problem )

Looking for full time job role ( as I am a student 😄)

Thanks for reading …

Top comments (0)