├── .github
└── FUNDING.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── components
├── AnimatedCounter.jsx
├── docs
│ ├── DocPager.jsx
│ ├── DocsLayout.jsx
│ └── TableOfContents.jsx
├── headerIcons.jsx
├── home
│ ├── HeroSection.jsx
│ ├── StargazersSection.jsx
│ └── TabsSection.jsx
├── layout
│ ├── Footer.jsx
│ ├── Header.jsx
│ └── Layout.jsx
├── loader.jsx
├── mdx-components.jsx
└── ui
│ ├── Accordions.jsx
│ ├── Avatars.jsx
│ ├── Backgrounds.jsx
│ ├── Buttons.jsx
│ ├── Cards.jsx
│ ├── Carousels.jsx
│ ├── Docks.jsx
│ ├── Grids.jsx
│ ├── Loaders.jsx
│ ├── Marquees.jsx
│ ├── Paginations.jsx
│ ├── Pointers.jsx
│ ├── Separators.jsx
│ ├── Tables.jsx
│ ├── Tabs.jsx
│ └── Texts.jsx
├── content
└── docs
│ ├── accordions.mdx
│ ├── avatars.mdx
│ ├── backgrounds.mdx
│ ├── buttons.mdx
│ ├── cards.mdx
│ ├── carousels.mdx
│ ├── changelog.mdx
│ ├── docks.mdx
│ ├── grids.mdx
│ ├── index.mdx
│ ├── loaders.mdx
│ ├── marquees.mdx
│ ├── paginations.mdx
│ ├── pointers.mdx
│ ├── separators.mdx
│ ├── tables.mdx
│ ├── tabs.mdx
│ └── texts.mdx
├── context
├── GithubContext.js
└── ThemeContext.js
├── jsconfig.json
├── lib
└── docs.js
├── next.config.mjs
├── package-lock.json
├── package.json
├── pages
├── 404.js
├── _app.js
├── _document.js
├── docs
│ └── [[...slug]].jsx
├── index.js
└── templates
│ └── index.jsx
├── public
├── apple-touch-icon.png
├── default-og-image.png
├── favicon.ico
├── logo.png
├── next.svg
├── vercel.svg
└── videos
│ ├── Portfolio-Template.mov
│ ├── SaaS-Template.mp4
│ ├── Startup-Template.mov
│ └── nature.mp4
├── styles
└── globals.css
├── theme
└── index.js
└── utils
└── constants.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [AbhiVarde]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node modules
2 | node_modules/
3 |
4 | # Env files
5 | .env
6 | .env.*
7 |
8 | # Logs
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | pnpm-debug.log*
13 |
14 | # OS-related
15 | .DS_Store
16 | Thumbs.db
17 |
18 | # Build output
19 | dist/
20 | .next/
21 | out/
22 | build/
23 |
24 | # IDEs/editors
25 | .vscode/
26 | .idea/
27 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | abhivarde.contact@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Sync UI
2 |
3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
4 |
5 | - Reporting a bug
6 | - Discussing the current state of the code
7 | - Submitting a fix
8 | - Proposing new features
9 |
10 | ## We Develop with Github
11 | We use github to host code, to track issues and feature requests, as well as accept pull requests.
12 |
13 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html)
14 | Pull requests are the best way to propose changes to the codebase.
15 |
16 | 1. Fork the repo and create your branch from `main`.
17 | 2. If you've added code that should be tested, add tests.
18 | 3. If you've changed APIs, update the documentation.
19 | 4. Ensure the test suite passes.
20 | 5. Make sure your code lints.
21 | 6. Issue that pull request!
22 |
23 | ## Any contributions you make will be under the MIT Software License
24 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project.
25 |
26 | ## Report bugs using Github's [issues](https://github.com/AbhiVarde/syncui/issues)
27 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/AbhiVarde/syncui/issues/new); it's that easy!
28 |
29 | ## Write bug reports with detail, background, and sample code
30 |
31 | **Great Bug Reports** tend to have:
32 |
33 | - A quick summary and/or background
34 | - Steps to reproduce
35 | - Be specific!
36 | - Give sample code if you can.
37 | - What you expected would happen
38 | - What actually happens
39 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
40 |
41 | ## Use a Consistent Coding Style
42 |
43 | * 2 spaces for indentation rather than tabs
44 | * You can try running `npm run lint` for style unification
45 |
46 | ## License
47 | By contributing, you agree that your contributions will be licensed under its [MIT License](http://choosealicense.com/licenses/mit/).
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Sync UI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
🚀 Introducing Sync UI ✨
3 |
A sleek UI library for Design Engineers, offering beautifully designed components built with MUI and Framer Motion.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | [](https://vercel.com/new/clone?repository-url=https://github.com/AbhiVarde/abhivarde.in)
12 |
13 | # syncui.design
14 |
15 | - **Library**: [React](https://react.dev/)
16 | - **Framework**: [Next.js](https://nextjs.org/)
17 | - **Styling**: [Material UI](https://mui.com/)
18 | - **Animation**: [Framer Motion](https://www.framer.com/motion/)
19 | - **Analytics**: [Vercel Analytics](https://vercel.com/analytics)
20 | - **Deployment**: [Vercel](https://vercel.com)
21 |
22 | ## Features
23 |
24 | - 🎨 Beautifully designed components
25 | - 📁 Ready-made templates for faster builds
26 | - 🛠️ Built with MUI and Framer Motion
27 | - ♿ Accessible and customizable
28 | - 🆓 100% Free and Open Source.
29 | - 🚀 Perfect for your next project
30 |
31 | ## Templates
32 |
33 | Explore our fresh, production-ready templates 🚀
34 |
35 | - 🚀 [Startup Template – $29](https://abhivarde.gumroad.com/l/startup-template-syncui)
36 | - 🧩 [SaaS Template – $29](https://abhivarde.gumroad.com/l/saas-template-syncui)
37 | - 🎯 [Portfolio Template – $29](https://abhivarde.gumroad.com/l/portfolio-template-syncui)
38 | - 🧃 [Bundle (All 3 Templates) – $79](https://abhivarde.gumroad.com/l/syncui-templates-bundle)
39 |
40 | → [See all templates](https://syncui.design/templates)
41 |
42 | *Tip: Right-click on links and select "Open in new tab" to keep this page open while browsing templates.*
43 |
44 | ## Documentation
45 |
46 | Visit [https://syncui.design/docs](https://syncui.design/docs) to view the full documentation and get started with Sync UI.
47 |
48 | ---
49 |
50 | 💖 **Love Sync UI? Support with a one-time gift!**
51 | From ☕ **$9** to keep development rolling, to 🚀 **$999** for a full promo of your work.
52 | [Become a Sponsor →](https://github.com/sponsors/AbhiVarde)
53 |
54 | ---
55 |
56 | ## Contributing
57 |
58 | We truly ❤️ pull requests! If you wish to help, you can learn more about how you can contribute to this project in the [contribution guide](https://github.com/AbhiVarde/syncui/blob/main/CONTRIBUTING.md).
59 |
60 | ## Let's talk
61 |
62 | - [Twitter](https://x.com/syncuidesign)
63 |
64 | ## License
65 |
66 | Sync UI is licensed under the [MIT License](http://choosealicense.com/licenses/mit/). All rights reserved.
67 |
68 | ## Authors
69 |
70 | Sync UI is created and maintained by [Abhi Varde](https://www.abhivarde.in/).
71 |
--------------------------------------------------------------------------------
/components/AnimatedCounter.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, memo, useRef } from "react";
2 |
3 | const AnimatedCounter = memo(
4 | ({
5 | value,
6 | duration = 2,
7 | className = "",
8 | formatter = (val) => val.toLocaleString(),
9 | delay = 0,
10 | }) => {
11 | const [counter, setCounter] = useState(0);
12 | const previousValueRef = useRef(0);
13 | const isFirstRender = useRef(true);
14 |
15 | useEffect(() => {
16 | if (isFirstRender.current) {
17 | setCounter(value);
18 | previousValueRef.current = value;
19 | isFirstRender.current = false;
20 | return;
21 | }
22 |
23 | if (value === previousValueRef.current) {
24 | return;
25 | }
26 |
27 | // Animate from previous value to new value
28 | const startValue = previousValueRef.current;
29 | const endValue = value;
30 | const difference = endValue - startValue;
31 |
32 | if (difference === 0) return;
33 |
34 | const steps = Math.abs(difference);
35 | const incrementValue = difference / steps;
36 | const incrementTime = (duration * 1000) / steps;
37 |
38 | const delayTimer = setTimeout(() => {
39 | let current = startValue;
40 |
41 | setCounter(current);
42 |
43 | const timer = setInterval(() => {
44 | current += incrementValue;
45 |
46 | if (
47 | (incrementValue > 0 && current >= endValue) ||
48 | (incrementValue < 0 && current <= endValue)
49 | ) {
50 | clearInterval(timer);
51 | setCounter(endValue);
52 | previousValueRef.current = endValue;
53 | } else {
54 | setCounter(current);
55 | }
56 | }, incrementTime);
57 |
58 | return () => clearInterval(timer);
59 | }, delay * 1000);
60 |
61 | return () => clearTimeout(delayTimer);
62 | }, [value, duration, delay]);
63 |
64 | return {formatter(Math.round(counter))} ;
65 | }
66 | );
67 |
68 | export default AnimatedCounter;
69 |
--------------------------------------------------------------------------------
/components/docs/DocPager.jsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from "react";
2 | import {
3 | Box,
4 | ButtonBase,
5 | Typography,
6 | useMediaQuery,
7 | useTheme,
8 | } from "@mui/material";
9 | import { RxChevronLeft, RxChevronRight } from "react-icons/rx";
10 | import Link from "next/link";
11 |
12 | const PagerButton = ({ direction, page }) => {
13 | const theme = useTheme();
14 | const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
15 |
16 | return (
17 |
18 |
36 |
43 | {isMobile ? (
44 |
54 | {direction === "prev" && }
55 | {direction === "prev" ? "Previous" : "Next"}
56 | {direction === "next" && }
57 |
58 | ) : (
59 |
66 |
76 | {direction === "prev" && }
77 | {direction === "prev" ? "Previous" : "Next"}
78 | {direction === "next" && }
79 |
80 |
81 | {page ? page.title : "No page"}
82 |
83 |
84 | )}
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | const findAdjacentPages = (docsTree, currentSlug) => {
92 | const flatTree = flattenTree(docsTree);
93 | const currentIndex = flatTree.findIndex(
94 | (item) => item.url === `/docs${currentSlug ? `/${currentSlug}` : ""}`
95 | );
96 |
97 | let prevPage = null;
98 | let nextPage = null;
99 |
100 | if (currentIndex > 0) {
101 | prevPage = flatTree[currentIndex - 1];
102 | }
103 |
104 | if (currentIndex < flatTree.length - 1) {
105 | nextPage = flatTree[currentIndex + 1];
106 | }
107 |
108 | return { prevPage, nextPage };
109 | };
110 |
111 | const flattenTree = (tree) => {
112 | const flattenedTree = [];
113 |
114 | // Add Setup page
115 | const setupPage = tree.find((node) => node.title === "Setup");
116 | if (setupPage) {
117 | flattenedTree.push({ title: "Setup", url: "/docs" });
118 | }
119 |
120 | // Add Changelog page
121 | const changelogPage = tree.find((node) => node.title === "Changelog");
122 | if (changelogPage) {
123 | flattenedTree.push({ title: "Changelog", url: "/docs/changelog" });
124 | }
125 |
126 | // Add all other pages
127 | tree.forEach((node) => {
128 | if (node.title !== "Setup" && node.title !== "Changelog") {
129 | if (!node.children) {
130 | flattenedTree.push({
131 | title: node.title,
132 | url: `/docs/${
133 | node.slug || node.title.toLowerCase().replace(/\s+/g, "-")
134 | }`,
135 | });
136 | } else {
137 | node.children.forEach((child) => {
138 | flattenedTree.push({
139 | title: child.title,
140 | url: `/docs/${
141 | child.slug || child.title.toLowerCase().replace(/\s+/g, "-")
142 | }`,
143 | });
144 | });
145 | }
146 | }
147 | });
148 |
149 | return flattenedTree;
150 | };
151 |
152 | export const DocPager = ({ slug, docsTree }) => {
153 | const { prevPage, nextPage } = useMemo(
154 | () => findAdjacentPages(docsTree, slug),
155 | [docsTree, slug]
156 | );
157 |
158 | return (
159 |
166 |
167 | {prevPage && }
168 |
169 |
170 | {nextPage && }
171 |
172 |
173 | );
174 | };
175 |
--------------------------------------------------------------------------------
/components/docs/DocsLayout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, Fragment } from "react";
2 | import { Box, Typography, Breadcrumbs } from "@mui/material";
3 | import Link from "next/link";
4 | import { TableOfContents } from "./TableOfContents";
5 | import { useRouter } from "next/router";
6 | import { RxChevronRight, RxTextAlignLeft } from "react-icons/rx";
7 |
8 | const DocsLayout = ({ children, toc, docsTree }) => {
9 | const router = useRouter();
10 | const [activeId, setActiveId] = useState("");
11 |
12 | useEffect(() => {
13 | const observer = new IntersectionObserver(
14 | (entries) => {
15 | entries.forEach((entry) => {
16 | if (entry.isIntersecting) {
17 | setActiveId(entry.target.id);
18 | }
19 | });
20 | },
21 | { rootMargin: "-20% 0px -60% 0px", threshold: 0.1 }
22 | );
23 |
24 | toc.forEach((item) => {
25 | const element = document.getElementById(item.id);
26 | if (element) observer.observe(element);
27 | });
28 |
29 | return () => observer.disconnect();
30 | }, [toc]);
31 |
32 | const renderBreadcrumbs = () => {
33 | const activeTocItem = toc.find((item) => item.id === activeId);
34 | const activeText = activeTocItem ? activeTocItem.text : "";
35 |
36 | return (
37 | }
39 | aria-label="breadcrumb"
40 | sx={{ mb: 2, display: { xs: "none", md: "flex", lg: "none" } }}
41 | >
42 |
53 |
54 | On this page
55 |
56 | {activeText && (
57 |
63 | {activeText}
64 |
65 | )}
66 |
67 | );
68 | };
69 |
70 | return (
71 |
77 |
89 | `1px solid ${theme.palette.divider}`,
94 | }}
95 | >
96 | {/* Scrollable content container */}
97 |
104 |
105 | {Object.entries(groupDocsTree(docsTree)).map(
106 | ([category, items]) => (
107 |
108 |
117 | {category}
118 |
119 |
120 | {items.map((item) => {
121 | const isActive =
122 | (item.title === "Setup" &&
123 | router.asPath === "/docs") ||
124 | (item.title === "Changelog" &&
125 | router.asPath === "/docs/changelog") ||
126 | (item.title === "Templates" &&
127 | router.asPath === "/templates") ||
128 | router.asPath === item.url;
129 | return (
130 |
136 |
160 | `0 0 0 1px ${theme.palette.divider}`,
161 | }),
162 | }}
163 | >
164 |
165 | {item.title === "Setup" ? "Setup" : item.title}
166 |
167 | {(item.title === "Accordions" ||
168 | item.title === "Backgrounds" ||
169 | item.title === "Pointers" ||
170 | item.title === "Grids" ||
171 | item.title === "Templates") && (
172 |
190 | New
191 |
192 | )}
193 |
194 |
195 | );
196 | })}
197 |
198 |
199 | )
200 | )}
201 |
202 |
203 | {/* Fixed footer */}
204 | `1px solid ${theme.palette.divider}`,
211 | p: 1.5,
212 | bgcolor: "background.default",
213 | }}
214 | >
215 |
216 | Brought to you by{" "}
217 |
228 | abhivarde.in
229 |
230 | .
231 |
232 |
233 |
234 |
235 |
246 | {renderBreadcrumbs()}
247 | {children}
248 |
249 |
261 |
262 |
263 |
264 | );
265 | };
266 |
267 | const groupDocsTree = (docsTree) => {
268 | const grouped = {
269 | "Getting Started": [],
270 | };
271 |
272 | // Add Templates to the Getting Started section
273 | grouped["Getting Started"].push({
274 | title: "Templates",
275 | url: "/templates",
276 | slug: "templates",
277 | });
278 |
279 | docsTree.forEach((item) => {
280 | if (item.title === "Setup" || item.title === "Changelog") {
281 | grouped["Getting Started"].push(item);
282 | } else {
283 | if (!grouped["Components"]) {
284 | grouped["Components"] = [];
285 | }
286 | grouped["Components"].push(item);
287 | }
288 | });
289 |
290 | grouped["Getting Started"].sort((a, b) => {
291 | const order = { Setup: 1, Changelog: 2, Templates: 3 };
292 | return (order[a.title] || 99) - (order[b.title] || 99);
293 | });
294 |
295 | return grouped;
296 | };
297 |
298 | export default DocsLayout;
299 |
--------------------------------------------------------------------------------
/components/docs/TableOfContents.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Box, Typography, List, ListItem } from "@mui/material";
3 | import { RxTextAlignLeft, RxStar, RxStarFilled } from "react-icons/rx";
4 | import { GITHUB_URL, SPONSOR_URL } from "../../utils/constants";
5 | import { useGitHub } from "@/context/GithubContext";
6 | import { RiGithubFill, RiHeartFill, RiHeartLine } from "react-icons/ri";
7 | import AnimatedCounter from "../AnimatedCounter";
8 | import { AnimatePresence, motion } from "framer-motion";
9 |
10 | const buttonStyles = {
11 | mt: 1,
12 | mx: 1,
13 | display: "flex",
14 | alignItems: "center",
15 | justifyContent: "center",
16 | gap: 1.5,
17 | width: "100%",
18 | height: 40,
19 | borderRadius: "10px",
20 | color: "inherit",
21 | textDecoration: "none",
22 | bgcolor: (theme) => theme.palette.action.hover,
23 | border: "1px solid",
24 | borderColor: "divider",
25 | transition: "all 0.2s ease-in-out",
26 | "&:hover": {
27 | bgcolor: (theme) => theme.palette.action.selected,
28 | transform: "translateY(-2px)",
29 | boxShadow: (theme) => `0 4px 8px ${theme.palette.divider}`,
30 | },
31 | };
32 |
33 | export const TableOfContents = ({ toc }) => {
34 | const { stars, loading } = useGitHub();
35 | const [activeId, setActiveId] = useState("");
36 | const [isHeartHovered, setIsHeartHovered] = useState(false);
37 | const [isStarHovered, setIsStarHovered] = useState(false);
38 |
39 | useEffect(() => {
40 | const observer = new IntersectionObserver(
41 | (entries) => {
42 | entries.forEach((entry) => {
43 | if (entry.isIntersecting) {
44 | setActiveId(entry.target.id);
45 | }
46 | });
47 | },
48 | { rootMargin: "-20% 0px -60% 0px", threshold: 0.1 }
49 | );
50 |
51 | toc.forEach((item) => {
52 | const element = document.getElementById(item.id);
53 | if (element) observer.observe(element);
54 | });
55 |
56 | return () => observer.disconnect();
57 | }, [toc]);
58 |
59 | const handleClick = (e, id) => {
60 | e.preventDefault();
61 | const element = document.getElementById(id);
62 | if (element) {
63 | element.scrollIntoView({ behavior: "smooth", block: "start" });
64 | setActiveId(id);
65 | }
66 | };
67 |
68 | return (
69 |
70 |
81 | On this page
82 |
83 |
92 | {toc.map((item) => (
93 | handleClick(e, item.id)}
98 | sx={{
99 | borderLeft: 1,
100 | borderColor: activeId === item.id ? "" : "divider",
101 | pl: item.level > 1 ? (item.level - 1) * 0.5 : 0.5,
102 | transition: "all 0.3s ease-in-out",
103 | "&:hover": {
104 | backgroundColor: "transparent",
105 | },
106 | }}
107 | >
108 |
118 | {item.text}
119 |
120 |
121 | ))}
122 |
123 |
124 | setIsHeartHovered(true)}
130 | onMouseLeave={() => setIsHeartHovered(false)}
131 | sx={buttonStyles}
132 | >
133 |
134 |
135 | {isHeartHovered ? (
136 |
144 |
145 |
146 | ) : (
147 |
155 |
156 |
157 | )}
158 |
159 |
160 |
161 | Support Sync UI
162 |
163 |
164 |
165 | setIsStarHovered(true)}
171 | onMouseLeave={() => setIsStarHovered(false)}
172 | sx={buttonStyles}
173 | >
174 | {!loading ? (
175 |
176 | {/* Left side: GitHub icon + Star label */}
177 |
178 |
179 |
189 | Star
190 |
191 |
192 |
193 | {/* Right side: Counter + Star animation */}
194 |
202 |
203 |
204 |
205 |
206 |
207 |
220 | {isStarHovered ? (
221 |
222 | ) : (
223 |
224 | )}
225 |
226 |
227 |
228 |
229 | ) : (
230 |
231 |
232 |
233 | Star
234 |
235 |
236 | )}
237 |
238 |
239 | );
240 | };
241 |
--------------------------------------------------------------------------------
/components/headerIcons.jsx:
--------------------------------------------------------------------------------
1 | import { RiGithubFill, RiTwitterXLine } from "react-icons/ri";
2 | import { RxMoon, RxSun, RxDotsVertical } from "react-icons/rx";
3 | import { Box, IconButton, Typography } from "@mui/material";
4 | import { GITHUB_URL, TWITTER_URL } from "@/utils/constants";
5 | import AnimatedCounter from "./AnimatedCounter";
6 |
7 | const ICON_SIZE = 20;
8 | const CONTAINER_SIZE = 36;
9 |
10 | const IconContainer = ({ component = "a", children, ...props }) => (
11 | theme.palette.action.hover,
21 | height: CONTAINER_SIZE,
22 | minWidth: CONTAINER_SIZE,
23 | borderRadius: "8px",
24 | transition: "all 0.2s ease-in-out",
25 | border: "1px solid",
26 | borderColor: "divider",
27 | gap: 1,
28 | px: 1.5,
29 | "&:hover": {
30 | bgcolor: (theme) => theme.palette.action.selected,
31 | transform: "translateY(-1px)",
32 | },
33 | }}
34 | {...props}
35 | >
36 | {children}
37 |
38 | );
39 |
40 | const HeaderIcons = ({
41 | stars,
42 | loading,
43 | isDarkMode,
44 | toggleTheme,
45 | isMediumUp,
46 | handleToggle,
47 | anchorRef,
48 | menuOpen,
49 | }) => {
50 | return (
51 | <>
52 | {(typeof stars === "number" || loading) && (
53 |
58 |
59 | {!loading && stars > 0 && (
60 |
61 | val.toLocaleString()}
65 | delay={0.2}
66 | />
67 |
68 | )}
69 |
70 | )}
71 |
72 |
77 |
78 |
79 |
80 |
85 | {isDarkMode ? : }
86 |
87 |
88 | {!isMediumUp && (
89 |
99 |
100 |
101 | )}
102 | >
103 | );
104 | };
105 |
106 | export default HeaderIcons;
107 |
--------------------------------------------------------------------------------
/components/home/HeroSection.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import {
3 | Typography,
4 | Button,
5 | Box,
6 | Container,
7 | Tooltip,
8 | useTheme,
9 | } from "@mui/material";
10 | import { motion, AnimatePresence } from "framer-motion";
11 | import { RxArrowRight } from "react-icons/rx";
12 | import Link from "next/link";
13 | import {
14 | SiReact,
15 | SiNextdotjs,
16 | SiFramer,
17 | SiJavascript,
18 | SiMui,
19 | } from "react-icons/si";
20 | import { IoSparkles } from "react-icons/io5";
21 |
22 | const MotionButton = motion(Button);
23 |
24 | const HeroSection = () => {
25 | const theme = useTheme();
26 | const [isHovered, setIsHovered] = useState(false);
27 |
28 | return (
29 |
30 |
37 | {/* Introducing Button */}
38 |
43 |
44 |
65 |
66 |
67 | Introducing Templates
68 |
69 |
70 |
71 |
72 |
73 | {/* Main Heading */}
74 |
75 |
80 |
88 |
94 | Beautifully designed components
95 |
96 |
97 |
103 | built with MUI and Framer Motion
104 |
105 |
106 |
107 |
108 |
113 |
118 | Accessible and customizable components that you can copy and paste
119 | into your apps. Built with MUI and Framer Motion for seamless
120 | integration and stunning animations. Free. Open Source. Ready for
121 | your next project.
122 |
123 |
124 |
125 |
126 | {/* Tech Stack Icons */}
127 |
132 |
141 | {[
142 | { Icon: SiMui, title: "MUI", color: "#007FFF" },
143 | { Icon: SiJavascript, title: "JavaScript", color: "#F7DF1E" },
144 | { Icon: SiReact, title: "React", color: "#61DAFB" },
145 | {
146 | Icon: SiNextdotjs,
147 | title: "Next.js",
148 | color: theme.palette.mode === "dark" ? "#FFFFFF" : "#000000",
149 | },
150 | { Icon: SiFramer, title: "Framer Motion", color: "#FF0050" },
151 | ].map(({ Icon, title, color }) => (
152 |
153 |
163 |
164 |
165 |
166 | ))}
167 |
168 |
169 |
170 | {/* Modified Button Section with Peerlist Badge */}
171 |
176 |
185 | {/* What's New Button */}
186 |
187 | setIsHovered(true)}
192 | onMouseLeave={() => setIsHovered(false)}
193 | sx={{
194 | display: "flex",
195 | alignItems: "center",
196 | transition: "0.3s",
197 | flexShrink: 0,
198 | }}
199 | >
200 |
201 |
202 | {isHovered && (
203 |
210 |
211 |
212 | )}
213 |
214 | Get Started
215 |
216 | {!isHovered && (
217 |
224 |
225 |
226 | )}
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | );
236 | };
237 |
238 | export default HeroSection;
239 |
--------------------------------------------------------------------------------
/components/layout/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Box,
4 | Container,
5 | Typography,
6 | useTheme,
7 | useMediaQuery,
8 | } from "@mui/material";
9 | import { RiGithubFill, RiTwitterXLine } from "react-icons/ri";
10 | import { GITHUB_URL, TWITTER_URL } from "../../utils/constants";
11 |
12 | const Footer = () => {
13 | const theme = useTheme();
14 | const isMediumUp = useMediaQuery(theme.breakpoints.up("md"));
15 |
16 | const socialLinkStyle = {
17 | display: "inline-flex",
18 | alignItems: "center",
19 | gap: 0.8,
20 | color: "inherit",
21 | textDecoration: "none",
22 | bgcolor: (theme) => theme.palette.action.hover,
23 | px: 1.4, // Increased padding-x slightly for better balance
24 | py: 0.8, // Increased padding-y slightly for better balance
25 | borderRadius: "8px",
26 | transition: "all 0.2s ease-in-out",
27 | border: "1px solid",
28 | borderColor: "divider",
29 | height: "32px", // Fixed height for consistency
30 | "&:hover": {
31 | bgcolor: (theme) => theme.palette.action.selected,
32 | transform: "translateY(-1px)",
33 | },
34 | };
35 |
36 | return (
37 |
44 |
51 |
57 |
58 |
59 | Brought to you by{" "}
60 |
71 | abhivarde.in
72 |
73 | .
74 |
75 |
76 |
77 |
84 |
85 |
86 |
93 |
94 |
102 | Follow
103 |
104 |
105 |
106 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default Footer;
113 |
--------------------------------------------------------------------------------
/components/layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Box } from "@mui/material";
3 | import Header from "./Header";
4 | import Footer from "./Footer";
5 | import { useRouter } from "next/router";
6 |
7 | const Layout = ({ children, toggleTheme, isDarkMode, docsTree, toc }) => {
8 | const router = useRouter();
9 | const isDocsPage = router.pathname.startsWith("/docs");
10 | const is404Page = router.pathname === "/404";
11 |
12 | return (
13 |
14 | {!is404Page && (
15 |
22 | )}
23 |
33 | {children}
34 |
35 | {!isDocsPage && !is404Page && }
36 |
37 | );
38 | };
39 |
40 | export default Layout;
41 |
--------------------------------------------------------------------------------
/components/loader.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Box, Typography, useTheme } from "@mui/material";
3 |
4 | const Loader = () => {
5 | const theme = useTheme();
6 | const [timeoutActive, setTimeoutActive] = useState(true);
7 |
8 | useEffect(() => {
9 | const timeoutId = setTimeout(() => {
10 | setTimeoutActive(false);
11 | }, 5000);
12 | return () => clearTimeout(timeoutId);
13 | }, []);
14 |
15 | return (
16 |
28 | {timeoutActive ? (
29 | <>
30 |
31 | Loading...⏳
32 |
33 |
34 | Hang tight, we’re almost there! 😅
35 |
36 | >
37 | ) : (
38 |
39 | 😅 Oops! Taking longer than expected. Please try again later.
40 |
41 | )}
42 |
43 | );
44 | };
45 |
46 | export default Loader;
47 |
--------------------------------------------------------------------------------
/components/mdx-components.jsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, useState, useEffect } from "react";
2 | import dynamic from "next/dynamic";
3 | import {
4 | Typography,
5 | Link as MuiLink,
6 | Box,
7 | Table,
8 | TableBody,
9 | TableCell,
10 | TableHead,
11 | TableRow,
12 | Paper,
13 | Tabs,
14 | Tab,
15 | IconButton,
16 | Tooltip,
17 | styled,
18 | useTheme,
19 | useMediaQuery,
20 | } from "@mui/material";
21 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
22 | import { RxCheck, RxCopy } from "react-icons/rx";
23 | import { atomDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
24 | import { GoEye, GoTerminal } from "react-icons/go";
25 |
26 | import CardVariants from "./ui/Cards";
27 | import ButtonVariants from "./ui/Buttons";
28 | import TextVariants from "./ui/Texts";
29 | import LoaderVariants from "./ui/Loaders";
30 | import SeparatorVariants from "./ui/Separators";
31 | import BackgroundVariants from "./ui/Backgrounds";
32 | import AvatarVariants from "./ui/Avatars";
33 | import MarqueeVariants from "./ui/Marquees";
34 | import TabVariants from "./ui/Tabs";
35 | import PaginationVariants from "./ui/Paginations";
36 | import CarouselVariants from "./ui/Carousels";
37 | import TableVariants from "./ui/Tables";
38 | import DockVariants from "./ui/Docks";
39 | import PointerVariants from "./ui/Pointers";
40 | import GridVariants from "./ui/Grids";
41 | import AccordionVariants from "./ui/Accordions";
42 |
43 | const createHeading = (variant) => {
44 | return forwardRef(({ children, ...props }, ref) => {
45 | const id =
46 | typeof children === "string"
47 | ? children.toLowerCase().replace(/[^\w]+/g, "-")
48 | : undefined;
49 | return (
50 |
69 | {children}
70 |
71 | );
72 | });
73 | };
74 |
75 | const CodeBlock = ({ className, children }) => {
76 | const [copied, setCopied] = useState(false);
77 | const [isMounted, setIsMounted] = useState(false);
78 | const theme = useTheme();
79 | const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
80 |
81 | useEffect(() => {
82 | setIsMounted(true);
83 | }, []);
84 |
85 | const language = className ? className.replace(/language-/, "") : "jsx";
86 | const codeString =
87 | typeof children === "string" ? children.trim() : String(children).trim();
88 |
89 | const handleCopy = () => {
90 | if (typeof window !== "undefined") {
91 | navigator.clipboard.writeText(codeString);
92 | setCopied(true);
93 | setTimeout(() => setCopied(false), 2000);
94 | }
95 | };
96 |
97 | if (!isMounted) {
98 | return null;
99 | }
100 |
101 | return (
102 |
109 |
110 |
136 | {copied ? : }
137 |
138 |
139 |
155 |
167 | {codeString}
168 |
169 |
170 |
171 | );
172 | };
173 |
174 | const DynamicCodeBlock = dynamic(() => Promise.resolve(CodeBlock), {
175 | ssr: false,
176 | });
177 |
178 | const Preview = ({ children }) => {children} ;
179 |
180 | const CodePreview = ({ preview, code }) => {
181 | const StyledTabs = styled(Tabs)(({ theme }) => ({
182 | borderBottom: `1px solid ${
183 | theme.palette.mode === "dark"
184 | ? "rgba(255, 255, 255, 0.2)"
185 | : "rgba(0, 0, 0, 0.2)"
186 | }`,
187 | "& .MuiTabs-indicator": {
188 | backgroundColor: theme.palette.mode === "dark" ? "white" : "black",
189 | transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
190 | },
191 | }));
192 |
193 | const StyledTab = styled(Tab)(({ theme }) => ({
194 | color:
195 | theme.palette.mode === "dark"
196 | ? "rgba(255, 255, 255, 0.7)"
197 | : "rgba(0, 0, 0, 0.7)",
198 | "&.Mui-selected": {
199 | color: theme.palette.mode === "dark" ? "white" : "black",
200 | },
201 | "&:hover": {
202 | color: theme.palette.mode === "dark" ? "white" : "black",
203 | opacity: 1,
204 | },
205 | minHeight: "36px",
206 | fontWeight: 500,
207 | fontSize: theme.typography.pxToRem(14),
208 | textTransform: "none",
209 | display: "flex",
210 | alignItems: "center",
211 | gap: "4px",
212 | transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
213 | }));
214 |
215 | const [tab, setTab] = useState(0);
216 |
217 | return (
218 |
227 | setTab(newValue)}>
228 | }
230 | label="Preview"
231 | iconPosition="start"
232 | />
233 | }
235 | label="Code"
236 | iconPosition="start"
237 | />
238 |
239 |
240 | {tab === 0 ? (
241 | {preview}
242 | ) : (
243 | {code}
244 | )}
245 |
246 |
247 | );
248 | };
249 |
250 | export const MDXComponents = {
251 | ButtonVariants: ButtonVariants,
252 | CardVariants: CardVariants,
253 | LoaderVariants: LoaderVariants,
254 | TextVariants: TextVariants,
255 | SeparatorVariants: SeparatorVariants,
256 | BackgroundVariants: BackgroundVariants,
257 | AvatarVariants: AvatarVariants,
258 | MarqueeVariants: MarqueeVariants,
259 | TabVariants: TabVariants,
260 | PaginationVariants: PaginationVariants,
261 | CarouselVariants: CarouselVariants,
262 | TableVariants: TableVariants,
263 | DockVariants: DockVariants,
264 | PointerVariants: PointerVariants,
265 | GridVariants: GridVariants,
266 | AccordionVariants: AccordionVariants,
267 | h1: createHeading("h1"),
268 | h2: createHeading("h2"),
269 | h3: createHeading("h3"),
270 | h4: createHeading("h4"),
271 | h5: createHeading("h5"),
272 | h6: createHeading("h6"),
273 | p: (props) => {
274 | if (
275 | typeof props.children === "object" &&
276 | props.children.type === DynamicCodeBlock
277 | ) {
278 | return props.children;
279 | }
280 | return (
281 |
292 | );
293 | },
294 | a: (props) => ,
295 | code: DynamicCodeBlock,
296 | pre: (props) =>
,
297 | img: (props) => (
298 |
299 |
300 |
301 | ),
302 | blockquote: (props) => (
303 |
317 | ),
318 | CodePreview,
319 | table: (props) => (
320 |
321 |
328 |
329 | ),
330 | thead: (props) => (
331 |
332 | ),
333 | tbody: TableBody,
334 | tr: TableRow,
335 | td: (props) => (
336 |
340 | ),
341 | th: (props) => (
342 |
349 | ),
350 | };
351 |
--------------------------------------------------------------------------------
/components/ui/Avatars.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import {
3 | Box,
4 | Tooltip,
5 | Typography,
6 | Avatar,
7 | Paper,
8 | useTheme,
9 | useMediaQuery,
10 | Skeleton,
11 | } from "@mui/material";
12 | import { motion, AnimatePresence } from "framer-motion";
13 |
14 | const AvatarVariants = ({ variant, totalUsers = 60 }) => {
15 | const theme = useTheme();
16 | const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
17 | const [loadedImages, setLoadedImages] = useState({});
18 |
19 | const avatarUrls = [
20 | "https://images.unsplash.com/photo-1570295999919-56ceb5ecca61",
21 | "https://images.unsplash.com/photo-1607746882042-944635dfe10e",
22 | "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
23 | "https://images.unsplash.com/photo-1500648767791-00dcc994a43e",
24 | "https://images.unsplash.com/photo-1599566150163-29194dcaad36",
25 | "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde",
26 | "https://images.unsplash.com/photo-1527980965255-d3b416303d12",
27 | "https://images.unsplash.com/photo-1544005313-94ddf0286df2",
28 | "https://images.unsplash.com/photo-1517841905240-472988babdf9",
29 | "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d",
30 | "https://images.unsplash.com/photo-1519345182560-3f2917c472ef",
31 | "https://images.unsplash.com/photo-1531427186611-ecfd6d936c79",
32 | ];
33 |
34 | const avatarSize = isMobile ? 35 : 50;
35 | const visibleAvatars = isMobile ? 5 : 8;
36 | const gridColumns = isMobile ? 3 : 4;
37 |
38 | // Preload images
39 | useEffect(() => {
40 | avatarUrls.forEach((url, index) => {
41 | const img = new Image();
42 | img.src = url;
43 | img.onload = () => {
44 | setLoadedImages((prev) => ({
45 | ...prev,
46 | [index]: true,
47 | }));
48 | };
49 | img.onerror = () => {
50 | setLoadedImages((prev) => ({
51 | ...prev,
52 | [index]: "error",
53 | }));
54 | };
55 | });
56 | }, []);
57 |
58 | const AvatarWithFallback = ({ url, index, ...props }) => {
59 | if (!loadedImages[index]) {
60 | return (
61 |
66 | );
67 | }
68 |
69 | return (
70 |
74 | );
75 | };
76 |
77 | const renderVariant = () => {
78 | switch (variant) {
79 | case "overlappingCircles":
80 | return (
81 |
82 | {avatarUrls.slice(0, visibleAvatars).map((url, index) => (
83 |
89 |
103 | 0 ? -2 : 0,
111 | boxShadow: "0 4px 8px rgba(0,0,0,0.1)",
112 | cursor: "pointer",
113 | }}
114 | />
115 |
116 |
117 | ))}
118 |
119 | );
120 |
121 | case "minimalGrid":
122 | return (
123 |
131 | {avatarUrls.slice(0, gridColumns * 3).map((url, index) => (
132 |
139 |
150 |
151 | ))}
152 |
153 | );
154 |
155 | case "floatingCards":
156 | return (
157 |
166 |
167 | {avatarUrls.slice(0, isMobile ? 6 : 10).map((url, index) => (
168 |
186 |
194 |
205 |
206 |
207 | ))}
208 |
209 |
210 | );
211 |
212 | case "modernShowcase":
213 | return (
214 |
215 |
225 | {avatarUrls.slice(0, isMobile ? 6 : 8).map((url, index) => (
226 |
241 |
253 |
254 | ))}
255 |
256 |
261 |
265 | {totalUsers}+ Contributors
266 |
267 |
268 |
269 | );
270 |
271 | default:
272 | return null;
273 | }
274 | };
275 |
276 | return (
277 |
285 | {renderVariant()}
286 |
287 | );
288 | };
289 |
290 | export default AvatarVariants;
291 |
--------------------------------------------------------------------------------
/components/ui/Loaders.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Box, Typography, useTheme } from "@mui/material";
3 | import { motion, AnimatePresence } from "framer-motion";
4 | import { useRouter } from "next/router";
5 |
6 | const LoaderVariants = ({ variant }) => {
7 | const theme = useTheme();
8 | const router = useRouter();
9 | const { asPath } = router;
10 | const loaderColor = theme.palette.mode === "dark" ? "#FFFFFF" : "#000000";
11 | const [isLoaded, setIsLoaded] = useState(false);
12 |
13 | useEffect(() => {
14 | const timer = setTimeout(() => setIsLoaded(true), 100);
15 | return () => clearTimeout(timer);
16 | }, []);
17 |
18 | const loaderStyle = {
19 | width: 160,
20 | height: asPath === "/docs/loaders" ? 60 : 120,
21 | display: "flex",
22 | flexDirection: "column",
23 | alignItems: "center",
24 | justifyContent: asPath === "/docs/loaders" ? "center" : "space-between",
25 | padding: asPath === "/docs/loaders" ? "0px !important" : "16px 0",
26 | };
27 |
28 | const typographyStyle = {
29 | fontWeight: 400,
30 | marginBottom: 2,
31 | textAlign: "center",
32 | };
33 |
34 | const renderLoader = () => {
35 | switch (variant) {
36 | case "pulsatingDots":
37 | return (
38 |
39 | {[0, 1, 2].map((index) => (
40 |
62 | ))}
63 |
64 | );
65 |
66 | case "morphingCube":
67 | return (
68 |
84 | );
85 |
86 | case "pulsatingRing":
87 | return (
88 |
106 | );
107 |
108 | case "circularSweep":
109 | return (
110 |
111 |
129 |
130 | );
131 |
132 | case "fadingSquares":
133 | return (
134 |
135 | {[0, 1, 2, 3].map((index) => (
136 |
156 | ))}
157 |
158 | );
159 |
160 | case "orbitalSpin":
161 | return (
162 |
181 | );
182 |
183 | case "triadicOrbit":
184 | return (
185 |
201 | {[0, 1, 2].map((index) => (
202 |
223 | ))}
224 |
225 | );
226 |
227 | case "barWave":
228 | return (
229 |
238 | {[0, 1, 2, 3, 4].map((i) => (
239 |
256 | ))}
257 |
258 | );
259 |
260 | default:
261 | return null;
262 | }
263 | };
264 |
265 | return (
266 |
267 | {isLoaded && (
268 |
274 |
275 | {asPath !== "/docs/loaders" && (
276 |
277 | {variant.charAt(0).toUpperCase() + variant.slice(1)}
278 |
279 | )}
280 | {renderLoader()}
281 |
282 |
283 | )}
284 |
285 | );
286 | };
287 |
288 | export default LoaderVariants;
289 |
--------------------------------------------------------------------------------
/components/ui/Pointers.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Box, Typography } from "@mui/material";
3 | import { motion, useMotionValue, useSpring } from "framer-motion";
4 | import { useRouter } from "next/router";
5 |
6 | const PointerVariants = ({ variant = "glowingDot" }) => {
7 | const router = useRouter();
8 | const isHome = router.pathname === "/";
9 | const [isHovering, setIsHovering] = useState(false);
10 | const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
11 |
12 | const getGradientColors = () => {
13 | switch (variant) {
14 | case "glowingDot":
15 | return {
16 | start: "#FF5F6D",
17 | end: "#FFC371",
18 | };
19 | case "followingRing":
20 | return { start: "#4E65FF", end: "#92EFFD" };
21 | case "magneticArrow":
22 | return { start: "#00C853", end: "#B2FF59" };
23 | case "trailingDots":
24 | return { start: "#9D50BB", end: "#6E48AA" };
25 | case "emojiFollower":
26 | return { start: "#FF4B2B", end: "#FF416C" };
27 | case "gradientBlob":
28 | return { start: "#36D1DC", end: "#5B86E5" };
29 | default:
30 | return { start: "#4E65FF", end: "#92EFFD" };
31 | }
32 | };
33 |
34 | const { start, end } = getGradientColors();
35 |
36 | const handleMouseEnter = () => {
37 | setIsHovering(true);
38 | };
39 |
40 | const handleMouseLeave = () => {
41 | setIsHovering(false);
42 | };
43 |
44 | const handleMouseMove = (e) => {
45 | const rect = e.currentTarget.getBoundingClientRect();
46 | setMousePosition({
47 | x: e.clientX - rect.left,
48 | y: e.clientY - rect.top,
49 | });
50 | };
51 |
52 | const renderPointer = () => {
53 | if (!isHovering) return null;
54 |
55 | switch (variant) {
56 | case "glowingDot":
57 | return ;
58 | case "followingRing":
59 | return ;
60 | case "magneticArrow":
61 | return ;
62 | case "trailingDots":
63 | return ;
64 | case "emojiFollower":
65 | return ;
66 | case "gradientBlob":
67 | return ;
68 | default:
69 | return null;
70 | }
71 | };
72 |
73 | return (
74 |
94 |
95 | Move your cursor here
96 |
97 |
98 | {renderPointer()}
99 |
100 | );
101 | };
102 |
103 | // Pointer variant components
104 | const GlowingDot = ({ position }) => {
105 | return (
106 |
124 | );
125 | };
126 |
127 | const FollowingRing = ({ position }) => {
128 | // Create spring for smoother movement
129 | const springConfig = { damping: 25, stiffness: 200 };
130 | const x = useMotionValue(position.x);
131 | const y = useMotionValue(position.y);
132 | const xSpring = useSpring(x, springConfig);
133 | const ySpring = useSpring(y, springConfig);
134 |
135 | useEffect(() => {
136 | x.set(position.x);
137 | y.set(position.y);
138 | }, [position, x, y]);
139 |
140 | return (
141 |
160 | );
161 | };
162 |
163 | const MagneticArrow = ({ position }) => {
164 | return (
165 |
178 |
179 |
189 |
190 |
191 | );
192 | };
193 |
194 | const TrailingDots = ({ position }) => {
195 | const [trail, setTrail] = useState([]);
196 |
197 | useEffect(() => {
198 | // Add position to trail
199 | setTrail((prevTrail) => {
200 | const newTrail = [
201 | ...prevTrail,
202 | { x: position.x, y: position.y, id: Date.now() },
203 | ];
204 | // Keep only the last 5 positions
205 | return newTrail.slice(-5);
206 | });
207 | }, [position]);
208 |
209 | return (
210 | <>
211 | {trail.map((dot, index) => {
212 | const size = 12 - index * 2;
213 | const opacity = 1 - index * 0.2;
214 |
215 | return (
216 |
235 | );
236 | })}
237 | >
238 | );
239 | };
240 |
241 | const EmojiFollower = ({ position }) => {
242 | const emojis = ["✨", "🎯", "👆", "👀", "🔥"];
243 | const [currentEmoji, setCurrentEmoji] = useState(emojis[0]);
244 |
245 | useEffect(() => {
246 | // Change emoji every second
247 | const interval = setInterval(() => {
248 | const randomIndex = Math.floor(Math.random() * emojis.length);
249 | setCurrentEmoji(emojis[randomIndex]);
250 | }, 1000);
251 |
252 | return () => clearInterval(interval);
253 | }, []);
254 |
255 | return (
256 |
273 | {currentEmoji}
274 |
275 | );
276 | };
277 |
278 | const GradientBlob = ({ position }) => {
279 | // Create spring for smoother movement
280 | const springConfig = { damping: 25, stiffness: 200 };
281 | const x = useMotionValue(position.x);
282 | const y = useMotionValue(position.y);
283 | const xSpring = useSpring(x, springConfig);
284 | const ySpring = useSpring(y, springConfig);
285 |
286 | useEffect(() => {
287 | x.set(position.x);
288 | y.set(position.y);
289 | }, [position, x, y]);
290 |
291 | return (
292 |
317 | );
318 | };
319 |
320 | export default PointerVariants;
321 |
--------------------------------------------------------------------------------
/components/ui/Separators.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Divider, Box, useTheme } from "@mui/material";
3 | import { styled, keyframes } from "@mui/system";
4 | import { RxStar, RxStarFilled, RxPlus } from "react-icons/rx";
5 |
6 | const StyledDivider = styled(Divider)(({ theme, variant }) => ({
7 | width: "100%",
8 | ...(variant === "dashed" && {
9 | borderStyle: "dashed",
10 | borderWidth: "1.5px",
11 | }),
12 | }));
13 |
14 | const IconWrapper = styled(Box)(({ theme }) => ({
15 | display: "flex",
16 | alignItems: "center",
17 | justifyContent: "center",
18 | padding: theme.spacing(1, 2),
19 | border: `1px solid ${theme.palette.divider}`,
20 | borderRadius: "9999px",
21 | }));
22 |
23 | const shimmer = keyframes`
24 | 0% { background-position: -1000px 0; }
25 | 100% { background-position: 1000px 0; }
26 | `;
27 |
28 | const SeparatorVariants = ({ variant, label, ...props }) => {
29 | const theme = useTheme();
30 |
31 | const [isStarFilled, setIsStarFilled] = useState(false);
32 |
33 | const handleStarClick = () => {
34 | setIsStarFilled(!isStarFilled);
35 | };
36 |
37 | const renderSeparator = () => {
38 | switch (variant) {
39 | case "dashed":
40 | return ;
41 | case "icon":
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | );
51 | case "zigzag":
52 | return (
53 |
61 | );
62 | case "gradient":
63 | return (
64 |
71 | );
72 | case "shimmer":
73 | return (
74 |
83 | );
84 | case "dotted":
85 | return (
86 |
94 | );
95 | case "starry":
96 | return (
97 |
98 |
99 |
100 | {isStarFilled ? : }
101 |
102 |
103 |
104 | );
105 | default:
106 | return ;
107 | }
108 | };
109 |
110 | return {renderSeparator()} ;
111 | };
112 |
113 | export default SeparatorVariants;
114 |
--------------------------------------------------------------------------------
/components/ui/Tabs.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
3 | import { motion, AnimatePresence } from "framer-motion";
4 | import { useRouter } from "next/router";
5 |
6 | const TabVariants = ({ variant }) => {
7 | const theme = useTheme();
8 | const router = useRouter();
9 | const { asPath } = router;
10 | const [activeTab, setActiveTab] = useState(0);
11 | const tabs = ["Home", "Profile", "Settings", "Contact"];
12 |
13 | const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
14 |
15 | const getTabStyle = (index) => ({
16 | padding: isMobile ? "8px 12px" : "10px 20px",
17 | cursor: "pointer",
18 | position: "relative",
19 | color: theme.palette.mode === "dark" ? "#fff" : "#000",
20 | transition: "color 0.3s ease",
21 | fontSize: isMobile ? "0.875rem" : "1rem",
22 | });
23 |
24 | const renderTabs = () => {
25 | switch (variant) {
26 | case "slidingUnderline":
27 | return (
28 |
40 | {tabs.map((tab, index) => (
41 | setActiveTab(index)}
45 | >
46 |
49 | {tab}
50 |
51 | {activeTab === index && (
52 |
65 | )}
66 |
67 | ))}
68 |
69 | );
70 |
71 | case "growingBackground":
72 | return (
73 |
84 | {tabs.map((tab, index) => (
85 | setActiveTab(index)}
93 | >
94 |
101 | {tab}
102 |
103 |
104 | {activeTab === index && (
105 |
122 | )}
123 |
124 |
125 | ))}
126 |
127 | );
128 |
129 | case "elevatedCards":
130 | return (
131 |
142 | {tabs.map((tab, index) => (
143 | setActiveTab(index)}
164 | >
165 |
178 | {tab}
179 |
180 |
181 | ))}
182 |
183 | );
184 |
185 | case "floatingBackground":
186 | return (
187 |
200 |
226 | {tabs.map((tab, index) => (
227 | setActiveTab(index)}
240 | >
241 |
247 | {tab}
248 |
249 |
250 | {activeTab === index && (
251 |
269 | )}
270 |
271 |
272 | ))}
273 |
274 | );
275 |
276 | default:
277 | return null;
278 | }
279 | };
280 |
281 | return (
282 |
294 | {renderTabs()}
295 |
296 | );
297 | };
298 |
299 | export default TabVariants;
300 |
--------------------------------------------------------------------------------
/content/docs/avatars.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Avatars
3 | description: Sync UI offers a variety of avatar styles to enhance your user interface.
4 | ---
5 |
6 | Sync UI offers a variety of avatar styles to enhance your user interface.
7 |
8 | #### Modern Showcase
9 |
10 | A modern layout with animated entrance and hover effects.
11 |
12 | }
14 | code={`
15 | import React from "react";
16 | import { Box, Typography, Avatar } from "@mui/material";
17 | import { motion } from "framer-motion";
18 |
19 | const ModernShowcase = ({ avatarUrls, totalUsers }) => (
20 |
21 |
22 |
32 | {avatarUrls.slice(0, 8).map((url, index) => (
33 |
48 |
58 |
59 | ))}
60 |
61 |
66 |
67 | {totalUsers}+ Contributors
68 |
69 |
70 |
71 | );
72 |
73 | export default ModernShowcase;
74 | `}
75 | />
76 |
77 |
78 | #### Floating Cards
79 |
80 | Animated avatar cards with hover effects.
81 |
82 | }
84 | code={`
85 | import React from "react";
86 | import { Box, Avatar, Paper } from "@mui/material";
87 | import { motion, AnimatePresence } from "framer-motion";
88 |
89 | const FloatingCards = ({ avatarUrls }) => (
90 |
91 | {" "}
92 |
101 |
102 | {avatarUrls.slice(0, 10).map((url, index) => (
103 |
121 |
129 |
138 |
139 |
140 | ))}
141 |
142 |
143 | );
144 |
145 | export default FloatingCards;
146 | `}
147 | />
148 |
149 | #### Minimal Grid
150 |
151 | A grid layout of avatars with hover animations.
152 |
153 | }
155 | code={`
156 | import React from "react";
157 | import { Box, Avatar } from "@mui/material";
158 | import { motion } from "framer-motion";
159 |
160 | const MinimalGrid = ({ avatarUrls }) => (
161 |
162 |
170 | {avatarUrls.map((url, index) => (
171 |
178 |
187 |
188 | ))}
189 |
190 | );
191 |
192 | export default MinimalGrid;
193 | `}
194 | />
195 |
196 | #### Overlapping Circles
197 |
198 | A set of overlapping avatar circles, ideal for displaying group members or collaborators.
199 |
200 | }
202 | code={`
203 | import React from "react";
204 | import { Box, Tooltip, Avatar } from "@mui/material";
205 | import { motion } from "framer-motion";
206 |
207 | const OverlappingCircles = ({ avatarUrls }) => (
208 |
209 |
210 | {avatarUrls.map((url, index) => (
211 |
217 |
231 | 0 ? -2 : 0,
238 | boxShadow: "0 4px 8px rgba(0,0,0,0.1)",
239 | }}
240 | />
241 |
242 |
243 | ))}
244 |
245 | );
246 |
247 | export default OverlappingCircles;
248 | `}
249 | />
250 |
251 |
--------------------------------------------------------------------------------
/content/docs/changelog.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Changelog
3 | description: Latest updates and announcements for Sync UI
4 | ---
5 |
6 | #### 🧪 Jun '25 – Startup Template & Bundle Offer
7 |
8 | **New in Templates:**
9 | • [Startup Template](/templates) – Built for modern startups: smooth, focused, and ready to launch 🚀
10 | • 💼 **Bundle Deal:** Get all 3 templates – SaaS, Portfolio & Startup – for **$79** _(Save $8)_
11 | → [Grab the Bundle](https://abhivarde.gumroad.com/l/syncui-templates-bundle)
12 | • Or pick your favorite for **$29** each ✨
13 |
14 | ---
15 |
16 | #### 🔲 May '25 – Grid Systems Unleashed
17 |
18 | **Structural Foundations:**
19 | • [Grids](/docs/grids) – Introduced 5 flexible layout systems including Masonry and Bento styles
20 | • [Accordions](docs/accordions) – Versatile collapsible content organizers with elegant transitions and interactive states
21 |
22 | ---
23 |
24 | #### ✳️ Apr '25 – Pointers & New Text Variant
25 |
26 | **New Additions:**
27 | • [Backgrounds](/docs/backgrounds) – New magnetic and grid variants, with refreshed colors and animations
28 | • [Pointers](/docs/pointers) – Guide users visually with directional cues
29 | • [Texts](/docs/texts) – Introduced "Video Text": text with video content visible through the text mask
30 |
31 | ---
32 |
33 | #### 🧩 Mar '25 – Introducing Templates
34 |
35 | **Fresh Launches:**
36 | • [SaaS Template](/templates) – Ready-to-use UI for SaaS apps
37 | • [Portfolio Template](/templates) – Clean, modern layouts to showcase your work
38 |
39 | ---
40 |
41 | #### 🔧 Feb '25 – Component Upgrades
42 |
43 | **Enhanced Experiences:**
44 | • [Cards](/docs/cards) – Added Lens Card variant with Twitter embed support
45 | • [Marquees](/docs/marquees) – Smoother scroll animations and refined layout behavior
46 | • [Paginations](/docs/paginations) – New numeric and progress bar styles
47 | • [Texts](/docs/texts) – More typography presets and motion flexibility
48 |
49 | ---
50 |
51 | #### 🧱 Jan '25 – New Components Drop
52 |
53 | **Now Available:**
54 | • [Avatars](/docs/avatars) – User profile system with dynamic badges
55 | • [Docks](/docs/docks) – Context-aware navigation sidebars
56 | • [Tables](/docs/tables) – Responsive, sortable data tables for any screen
57 |
58 | ---
59 |
60 | #### 🌀 Nov '24 – New Layers of Functionality
61 |
62 | **Just Added:**
63 | • [Carousels](/docs/carousels) – Swipeable, touch-friendly content sliders
64 | • [Content Cards](/docs/cards) – Pre-built card containers for flexible layouts
65 | • [Dynamic Overlays](/docs/cards) – Modal overlays triggered by context
66 |
67 | ---
68 |
69 | #### 🚀 Oct '24 – Sync UI Is Live!
70 |
71 | Say hello to **Sync UI** — an open-source UI library powered by **MUI** and **Framer Motion**, built for developers who love performance, polish, and flexibility.
72 |
73 | **Core Components:**
74 | • [Avatars](/docs/avatars)
75 | • [Backgrounds](/docs/backgrounds)
76 | • [Buttons](/docs/buttons)
77 | • [Cards](/docs/cards)
78 | • [Loaders](/docs/loaders)
79 | • [Marquees](/docs/marquees)
80 | • [Paginations](/docs/paginations)
81 | • [Separators](/docs/separators)
82 | • [Tabs](/docs/tabs)
83 | • [Texts](/docs/texts)
84 |
85 | **Highlights:**
86 | • Sleek, modern UI building blocks
87 | • Built with MUI & Framer Motion
88 | • Fully accessible & responsive
89 | • Open-source (MIT)
90 | • Community-first approach
91 |
92 | ---
93 |
94 | #### 💖 Love Sync UI? Support with a One-Time Gift
95 |
96 | From a ☕ $9 coffee to keep development rolling, to a 🚀 $999 shoutout promoting your work —
97 | every bit of support helps us build better, faster.
98 |
99 | [Become a Sponsor →](https://github.com/sponsors/AbhiVarde)
100 |
101 | ---
102 |
103 | #### 💬 Let's Build It Together
104 |
105 | Explore components, copy the code, and make them yours.
106 | Have feedback or want to contribute? Visit us on [GitHub](https://github.com/AbhiVarde/syncui) — your ideas help shape what's next.
107 |
108 | ---
109 |
110 | Stay tuned, stay creative – Sync UI grows with you. 💚
111 |
--------------------------------------------------------------------------------
/content/docs/grids.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Grids
3 | description: Sync UI offers a variety of grid layout styles to enhance your content presentation.
4 | ---
5 |
6 | Sync UI provides multiple grid layout variants to beautifully organize your content with smooth animations.
7 |
8 | #### Masonry Grid
9 | A cascading grid layout with items of varying heights.
10 |
11 | }
13 | code={`
14 | import React from "react";
15 | import { Typography, useTheme, Box } from "@mui/material";
16 | import { motion, AnimatePresence } from "framer-motion";
17 |
18 | const MotionBox = motion(Box);
19 |
20 | const MasonryGrid = () => {
21 | const theme = useTheme();
22 |
23 | const unsplashImages = [
24 | "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&q=80&w=2940",
25 | "https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&q=80&w=2940",
26 | "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&q=80&w=2940",
27 | ];
28 |
29 | const items = Array.from({ length: 9 }, (_, i) => ({
30 | id: i + 1,
31 | imageUrl: unsplashImages[i % unsplashImages.length],
32 | }));
33 |
34 | const getMasonryHeight = (index) => {
35 | const baseHeights = [280, 350, 320, 400, 360, 430, 300, 380, 340];
36 | const baseHeight = baseHeights[index % baseHeights.length];
37 | const variation = ((index * 7) % 60) - 30;
38 | return Math.max(250, baseHeight + variation);
39 | };
40 |
41 | const getColumnCount = () => {
42 | if (typeof window !== "undefined") {
43 | if (window.innerWidth < 600) return 1;
44 | if (window.innerWidth < 900) return 2;
45 | return 3;
46 | }
47 | return 3;
48 | };
49 |
50 | const columnCount = getColumnCount();
51 | const gap = 16;
52 |
53 | const columns = Array.from({ length: columnCount }, () => []);
54 | const columnHeights = Array(columnCount).fill(0);
55 |
56 | items.forEach((item, index) => {
57 | const height = getMasonryHeight(index);
58 | const shortestColumnIndex = columnHeights.reduce(
59 | (minIndex, height, currentIndex) =>
60 | height < columnHeights[minIndex] ? currentIndex : minIndex,
61 | 0
62 | );
63 | columns[shortestColumnIndex].push({
64 | ...item,
65 | originalIndex: index,
66 | height,
67 | });
68 | columnHeights[shortestColumnIndex] += height + gap;
69 | });
70 |
71 | return (
72 |
81 | {columns.map((column, columnIndex) => (
82 |
91 | {column.map((item, itemIndex) => (
92 |
113 |
122 |
123 | ))}
124 |
125 | ))}
126 |
127 | );
128 | };
129 | `}
130 | />
131 |
132 | #### Bento Grid
133 | A Japanese-style compartmentalized grid with featured areas.
134 |
135 | }
137 | code={`
138 | import React from "react";
139 | import { Box, useTheme } from "@mui/material";
140 | import { motion } from "framer-motion";
141 |
142 | const MotionBox = motion(Box);
143 |
144 | const BentoGrid = () => {
145 | const theme = useTheme();
146 | const unsplashImages = [
147 | "https://images.unsplash.com/photo-1506905925346-21bda4d32df4",
148 | "https://images.unsplash.com/photo-1507525428034-b723cf961d3e",
149 | "https://images.unsplash.com/photo-1441974231531-c6227db76b6e",
150 | "https://images.unsplash.com/photo-1469474968028-56623f02e42e",
151 | "https://images.unsplash.com/photo-1439066615861-d1af74d74000",
152 | ];
153 |
154 | return (
155 |
165 |
176 |
177 |
188 |
189 |
200 |
201 | {[3, 4].map((imageIndex, index) => (
202 |
214 | ))}
215 |
216 | );
217 | };
218 | `}
219 | />
220 |
221 | #### Glassmorphism Grid
222 | A grid with frosted glass effect cards.
223 |
224 | }
226 | code={`
227 | import React from "react";
228 | import { Box, useTheme } from "@mui/material";
229 | import { motion } from "framer-motion";
230 |
231 | const MotionBox = motion(Box);
232 |
233 | const GlassmorphismGrid = () => {
234 | const theme = useTheme();
235 | const unsplashImages = [
236 | "https://images.unsplash.com/photo-1506905925346-21bda4d32df4",
237 | "https://images.unsplash.com/photo-1507525428034-b723cf961d3e",
238 | "https://images.unsplash.com/photo-1441974231531-c6227db76b6e",
239 | "https://images.unsplash.com/photo-1469474968028-56623f02e42e",
240 | ];
241 |
242 | return (
243 |
255 | {Array.from({ length: 6 }).map((_, index) => (
256 |
271 |
280 |
281 | ))}
282 |
283 | );
284 | };
285 | `}
286 | />
287 |
288 | #### Organic Shapes Grid
289 | A grid with items featuring organic, irregular shapes.
290 |
291 | }
293 | code={`
294 | import React from "react";
295 | import { Box, useTheme } from "@mui/material";
296 | import { motion } from "framer-motion";
297 |
298 | const MotionBox = motion(Box);
299 |
300 | const OrganicShapesGrid = () => {
301 | const theme = useTheme();
302 | const unsplashImages = [
303 | "https://images.unsplash.com/photo-1506905925346-21bda4d32df4",
304 | "https://images.unsplash.com/photo-1507525428034-b723cf961d3e",
305 | "https://images.unsplash.com/photo-1441974231531-c6227db76b6e",
306 | ];
307 |
308 | return (
309 |
321 | {Array.from({ length: 6 }).map((_, index) => {
322 | const topLeft = Math.floor(Math.random() * 30) + 20;
323 | const topRight = Math.floor(Math.random() * 30) + 20;
324 | const bottomRight = Math.floor(Math.random() * 30) + 20;
325 | const bottomLeft = Math.floor(Math.random() * 30) + 20;
326 |
327 | return (
328 |
338 | );
339 | })}
340 |
341 | );
342 | };
343 | `}
344 | />
345 |
346 | #### Minimal Cards Grid
347 | A clean grid with subtle shadows and rounded corners.
348 |
349 | }
351 | code={`
352 | import React from "react";
353 | import { Box, useTheme } from "@mui/material";
354 | import { motion } from "framer-motion";
355 |
356 | const MotionBox = motion(Box);
357 |
358 | const MinimalCardsGrid = () => {
359 | const theme = useTheme();
360 | const unsplashImages = [
361 | "https://images.unsplash.com/photo-1506905925346-21bda4d32df4",
362 | "https://images.unsplash.com/photo-1507525428034-b723cf961d3e",
363 | "https://images.unsplash.com/photo-1441974231531-c6227db76b6e",
364 | ];
365 |
366 | return (
367 |
379 | {Array.from({ length: 6 }).map((_, index) => (
380 |
392 | ))}
393 |
394 | );
395 | };
396 | `}
397 | />
--------------------------------------------------------------------------------
/content/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Setup
3 | description: Learn how to get started with Sync UI
4 | ---
5 |
6 | #### Introduction
7 |
8 | Sync UI empowers you to efficiently design modern and interactive React applications. This toolkit provides a rich library of pre-built, customizable components, allowing you to focus on crafting exceptional user experiences.
9 |
10 | To use Sync UI, you need to set up a React or Next.js project and install MUI and Framer Motion. Follow the instructions below to get started.
11 |
12 | #### Setting up your project
13 |
14 | First, you'll need to set up a React.js or Next.js project. Choose the option that best fits your needs:
15 |
16 | ##### Option 1: Create a new React.js project
17 |
18 | React is a JavaScript library for building user interfaces, particularly single-page applications.
19 |
20 | **Learn more about React:** [https://reactjs.org/](https://reactjs.org/)
21 |
22 | ```bash
23 | npx create-react-app my-sync-ui-app
24 | cd my-sync-ui-app
25 | ```
26 |
27 | To start your React development server:
28 |
29 | ```bash
30 | npm start
31 | ```
32 |
33 | ##### Option 2: Create a new Next.js project
34 |
35 | Next.js is a React framework that enables functionality such as server-side rendering and generating static websites.
36 |
37 | **Explore Next.js:** [https://nextjs.org/](https://nextjs.org/)
38 |
39 | ```bash
40 | npx create-next-app@latest my-sync-ui-app
41 | cd my-sync-ui-app
42 | ```
43 |
44 | To start your Next.js development server:
45 |
46 | ```bash
47 | npm run dev
48 | ```
49 |
50 | #### What is MUI?
51 |
52 | MUI (Material UI) is a React UI framework offering a robust set of customizable components. This library simplifies the creation of consistent, accessible, and responsive interfaces.
53 |
54 | **Dive deeper:** [https://mui.com/](https://mui.com/)
55 |
56 | #### What is Framer Motion?
57 |
58 | Framer Motion is a powerful animation library for React that streamlines the process of integrating smooth and engaging animations into your applications.
59 |
60 | **Elevate your UI:** [https://www.framer.com/motion](https://www.framer.com/motion/)
61 |
62 | #### Install the core dependencies
63 |
64 | After setting up your React.js or Next.js project, install the required dependencies for Sync UI:
65 |
66 | ```bash
67 | npm install @mui/material @emotion/react @emotion/styled framer-motion
68 | ```
69 |
70 | #### Next steps
71 |
72 | With your project set up and dependencies installed, you're ready to start using Sync UI components in your application. Check out our component documentation to learn how to implement various UI elements and bring your designs to life.
73 |
--------------------------------------------------------------------------------
/content/docs/loaders.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Loaders
3 | description: Sync UI offers a variety of loader animation styles to enhance your user interface.
4 | ---
5 |
6 | Sync UI offers a variety of loader animation styles to enhance your user interface and provide visual feedback during loading states.
7 |
8 | #### Pulsating Dots
9 |
10 | Three dots that pulsate in sequence.
11 |
12 | }
14 | code={`
15 | import React from "react";
16 | import { Box } from "@mui/material";
17 | import { motion } from "framer-motion";
18 |
19 | const PulsatingDots = () => (
20 |
21 |
22 | {[0, 1, 2].map((index) => (
23 |
45 | ))}
46 |
47 | );
48 |
49 | export default PulsatingDots;
50 | `}
51 | />
52 |
53 | #### Morphing Cube
54 |
55 | A square that rotates and morphs into a circle.
56 |
57 | }
59 | code={`
60 | import React from "react";
61 | import { motion } from "framer-motion";
62 |
63 | const MorphingCube = () => (
64 |
65 |
81 | );
82 |
83 | export default MorphingCube;
84 | `}
85 | />
86 |
87 | #### Pulsating Ring
88 |
89 | A circle that pulsates and rotates.
90 |
91 | }
93 | code={`
94 | import React from "react";
95 | import { motion } from "framer-motion";
96 |
97 | const PulsatingRing = () => (
98 |
99 |
117 | );
118 |
119 | export default PulsatingRing;
120 | `}
121 | />
122 |
123 | #### Circular Sweep
124 |
125 | A circular progress indicator that sweeps around.
126 |
127 | }
129 | code={`
130 | import React from "react";
131 | import { motion } from "framer-motion";
132 |
133 | const CircularSweep = () => (
134 |
135 |
136 |
154 |
155 | );
156 |
157 | export default CircularSweep;
158 | `}
159 | />
160 |
161 | #### Fading Squares
162 |
163 | Four squares that fade in and out in sequence.
164 |
165 | }
167 | code={`
168 | import React from "react";
169 | import { Box } from "@mui/material";
170 | import { motion } from "framer-motion";
171 |
172 | const FadingSquares = () => (
173 |
174 |
175 | {[0, 1, 2, 3].map((index) => (
176 |
196 | ))}
197 |
198 | );
199 |
200 | export default FadingSquares;
201 | `}
202 | />
203 |
204 | #### Orbital Spin
205 |
206 | A circular border that spins around its center.
207 |
208 | }
210 | code={`
211 | import React from "react";
212 | import { motion } from "framer-motion";
213 |
214 | const OrbitalSpin = () => (
215 |
216 |
235 |
236 | );
237 |
238 | export default OrbitalSpin;
239 | `}
240 | />
241 |
242 | #### Triadic Orbit
243 |
244 | Three circles orbiting around a central point.
245 |
246 | }
248 | code={`
249 | import React from "react";
250 | import { motion } from "framer-motion";
251 |
252 | const TriadicOrbit = () => (
253 |
254 |
270 | {[0, 1, 2].map((index) => (
271 |
292 | ))}
293 |
294 | );
295 |
296 | export default TriadicOrbit;
297 | `}
298 | />
299 |
300 | #### Bar Wave
301 |
302 | A series of bars that move up and down in a wave pattern.
303 |
304 | }
306 | code={`
307 | import React from "react";
308 | import { Box } from "@mui/material";
309 | import { motion } from "framer-motion";
310 |
311 | const BarWave = () => (
312 |
313 |
322 | {[0, 1, 2, 3, 4].map((i) => (
323 |
340 | ))}
341 |
342 | );
343 |
344 | export default BarWave;
345 | `}
346 | />
347 |
--------------------------------------------------------------------------------
/content/docs/marquees.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Marquees
3 | description: Sync UI offers various marquee animation styles to add dynamic content to your user interface.
4 | ---
5 |
6 | Sync UI provides several marquee animation styles to display dynamic, scrolling content with smooth animations and hover interactions.
7 |
8 | #### Horizontal Marquee
9 |
10 | A horizontally scrolling carousel with edge gradients and hover pause.
11 |
12 | }
14 | code={`
15 | import React, { useState, useEffect } from "react";
16 | import { Box, Card, Stack, Avatar, Typography, IconButton, useTheme } from "@mui/material";
17 | import { motion, useAnimation } from "framer-motion";
18 | import { RxTwitterLogo } from "react-icons/rx";
19 |
20 | const componentTestimonials = [
21 | {
22 | name: "Emma Rodriguez",
23 | handle: "@emma_frontend",
24 | avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
25 | text: "SyncUI + Framer Motion = dev time cut in half! 🚀",
26 | verified: true,
27 | },
28 | // Add more testimonials like these
29 | ]
30 |
31 | const HorizontalMarquee = () => {
32 | const theme = useTheme();
33 | const controls = useAnimation();
34 | const [isPaused, setIsPaused] = useState(false);
35 |
36 | useEffect(() => {
37 | if (!isPaused) {
38 | controls.start({
39 | x: "-50%",
40 | transition: {
41 | duration: 20,
42 | ease: "linear",
43 | repeat: Infinity,
44 | repeatType: "loop"
45 | }
46 | });
47 | }
48 | return () => controls.stop();
49 | }, [isPaused]);
50 |
51 | return (
52 |
70 | setIsPaused(true)}
74 | onHoverEnd={() => setIsPaused(false)}
75 | >
76 | {[...Array(3)].map((_, i) =>
77 | componentTestimonials.map((tweet, index) => (
78 |
89 |
90 |
91 |
100 |
101 |
102 | {tweet.name}
103 |
104 |
105 | {tweet.handle}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | {tweet.text}
114 |
115 |
116 |
117 | ))
118 | )}
119 |
120 |
121 | );
122 | };
123 | `}
124 | />
125 |
126 | #### Vertical Marquee
127 |
128 | A vertically scrolling carousel with top/bottom gradients and hover pause.
129 |
130 | }
132 | code={`
133 | import React, { useState, useEffect } from "react";
134 | import { Box, Card, Stack, Avatar, Typography, IconButton, useTheme } from "@mui/material";
135 | import { motion, useAnimation } from "framer-motion";
136 | import { RxTwitterLogo } from "react-icons/rx";
137 |
138 | const componentTestimonials = [
139 | {
140 | name: "Emma Rodriguez",
141 | handle: "@emma_frontend",
142 | avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
143 | text: "SyncUI + Framer Motion = dev time cut in half! 🚀",
144 | verified: true,
145 | },
146 | // Add more testimonials like these
147 | ]
148 |
149 | const VerticalMarquee = () => {
150 | const theme = useTheme();
151 | const controls = useAnimation();
152 | const [isPaused, setIsPaused] = useState(false);
153 |
154 | useEffect(() => {
155 | if (!isPaused) {
156 | controls.start({
157 | y: "-50%",
158 | transition: {
159 | duration: 20,
160 | ease: "linear",
161 | repeat: Infinity,
162 | repeatType: "loop"
163 | }
164 | });
165 | }
166 | return () => controls.stop();
167 | }, [isPaused]);
168 |
169 | return (
170 |
188 | setIsPaused(true)}
192 | onHoverEnd={() => setIsPaused(false)}
193 | >
194 | {[...Array(3)].map((_, i) =>
195 | componentTestimonials.map((tweet, index) => (
196 |
207 |
208 |
209 |
218 |
219 |
220 | {tweet.name}
221 |
222 |
223 | {tweet.handle}
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 | {tweet.text}
232 |
233 |
234 |
235 | ))
236 | )}
237 |
238 |
239 | );
240 | };
241 | `}
242 | />
243 |
244 | #### Diagonal Marquee
245 |
246 | A diagonal scrolling grid with dynamic rotations and hover effects.
247 |
248 | }
250 | code={`
251 | import React, { useState, useEffect } from "react";
252 | import { Box, Card, Stack, Avatar, Typography, IconButton, useTheme } from "@mui/material";
253 | import { motion, useAnimation } from "framer-motion";
254 | import { RxTwitterLogo } from "react-icons/rx";
255 |
256 | const componentTestimonials = [
257 | {
258 | name: "Emma Rodriguez",
259 | handle: "@emma_frontend",
260 | avatar: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
261 | text: "SyncUI + Framer Motion = dev time cut in half! 🚀",
262 | verified: true,
263 | },
264 | // Add more testimonials like these
265 | ]
266 |
267 | const DiagonalMarquee = () => {
268 | const theme = useTheme();
269 | const controls = useAnimation();
270 | const [isPaused, setIsPaused] = useState(false);
271 |
272 | useEffect(() => {
273 | if (!isPaused) {
274 | controls.start({
275 | x: "-50%",
276 | y: "-50%",
277 | transition: {
278 | duration: 20,
279 | ease: "linear",
280 | repeat: Infinity,
281 | repeatType: "loop"
282 | }
283 | });
284 | }
285 | return () => controls.stop();
286 | }, [isPaused]);
287 |
288 | return (
289 |
295 |
307 | {[...Array(3)].map((_, i) =>
308 | componentTestimonials.map((tweet, index) => (
309 |
314 |
325 |
326 |
327 |
336 |
337 |
338 | {tweet.name}
339 |
340 |
341 | {tweet.handle}
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 | {tweet.text}
350 |
351 |
352 |
353 |
354 | ))
355 | )}
356 |
357 |
358 | );
359 | };
360 | `}
361 | />
362 |
--------------------------------------------------------------------------------
/content/docs/separators.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Separators
3 | description: Sync UI offers a variety of separator styles to enhance your user interface.
4 | ---
5 |
6 | Sync UI offers a variety of separator styles to enhance your user interface.
7 |
8 | #### Dashed Separator
9 |
10 | A dashed line separator with improved visibility.
11 |
12 | }
14 | code={`
15 | import React from 'react';
16 | import { Divider, Typography, Box } from '@mui/material';
17 | import { styled } from '@mui/system';
18 |
19 | const StyledDivider = styled(Divider)(({ theme }) => ({
20 | width: '100%',
21 | borderStyle: 'dashed',
22 | borderWidth: '1.5px',
23 | }));
24 |
25 | const DashedSeparator = ({ label }) => (
26 |
27 |
28 | {label && (
29 |
34 | {label}
35 |
36 | )}
37 |
38 |
39 | );
40 |
41 | export default DashedSeparator;
42 | `}
43 | />
44 |
45 | #### Icon Separator
46 |
47 | A separator with an icon in the center.
48 |
49 | }
51 | code={`
52 | import React from 'react';
53 | import { Divider, Typography, Box } from '@mui/material';
54 | import { styled } from '@mui/system';
55 | import { RxPlus } from 'react-icons/rx';
56 |
57 | const StyledDivider = styled(Divider)(({ theme }) => ({
58 | width: '100%',
59 | }));
60 |
61 | const IconWrapper = styled(Box)(({ theme }) => ({
62 | display: 'flex',
63 | alignItems: 'center',
64 | justifyContent: 'center',
65 | padding: theme.spacing(1, 2),
66 | border: \`1px solid \${theme.palette.divider}\`,
67 | borderRadius: '9999px',
68 | }));
69 |
70 | const IconSeparator = ({ label }) => (
71 |
72 |
73 | {label && (
74 |
79 | {label}
80 |
81 | )}
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | );
91 |
92 | export default IconSeparator;
93 | `}
94 | />
95 |
96 | #### Zigzag Separator
97 |
98 | A zigzag pattern separator.
99 |
100 | }
102 | code={`
103 | import React from 'react';
104 | import { Typography, Box, useTheme } from '@mui/material';
105 |
106 | const ZigzagSeparator = ({ label }) => {
107 | const theme = useTheme();
108 |
109 | return (
110 |
111 |
112 | {label && (
113 |
118 | {label}
119 |
120 | )}
121 |
129 |
130 | );
131 | };
132 |
133 | export default ZigzagSeparator;
134 | `}
135 | />
136 |
137 | #### Gradient Separator
138 |
139 | A separator with a smooth gradient transition.
140 |
141 | }
143 | code={`
144 | import React from 'react';
145 | import { Typography, Box, useTheme } from '@mui/material';
146 |
147 | const GradientSeparator = ({ label }) => {
148 | const theme = useTheme();
149 |
150 | return (
151 |
152 |
153 | {label && (
154 |
159 | {label}
160 |
161 | )}
162 |
169 |
170 | );
171 | };
172 |
173 | export default GradientSeparator;
174 | `}
175 | />
176 |
177 | #### Shimmer Separator
178 |
179 | A separator with a shimmering animation effect.
180 |
181 | }
183 | code={`
184 | import React from 'react';
185 | import { Typography, Box, useTheme } from '@mui/material';
186 | import { keyframes } from '@mui/system';
187 |
188 | const shimmer = keyframes\`
189 | 0% { background-position: -1000px 0; }
190 | 100% { background-position: 1000px 0; }
191 | \`;
192 |
193 | const ShimmerSeparator = ({ label }) => {
194 | const theme = useTheme();
195 |
196 | return (
197 |
198 |
199 | {label && (
200 |
205 | {label}
206 |
207 | )}
208 |
216 |
217 | );
218 | };
219 |
220 | export default ShimmerSeparator;
221 | `}
222 | />
223 |
224 | #### Dotted Separator
225 |
226 | A separator with a dotted pattern.
227 |
228 | }
230 | code={`
231 | import React from 'react';
232 | import { Typography, Box, useTheme } from '@mui/material';
233 |
234 | const DottedSeparator = ({ label }) => {
235 | const theme = useTheme();
236 |
237 | return (
238 |
239 |
240 | {label && (
241 |
246 | {label}
247 |
248 | )}
249 |
257 |
258 | );
259 | };
260 |
261 | export default DottedSeparator;
262 | `}
263 | />
264 |
265 | #### Starry Separator
266 |
267 | A separator with star icons.
268 |
269 | }
271 | code={`
272 | import React, { useState } from "react";
273 | import { Divider, Typography, Box, useTheme } from '@mui/material';
274 | import { RxStarFilled, RxStar } from 'react-icons/rx';
275 |
276 | const StarrySeparator = ({ label }) => {
277 |
278 | const theme = useTheme();
279 | const [isStarFilled, setIsStarFilled] = useState(false);
280 |
281 | const handleStarClick = () => {
282 | setIsStarFilled(!isStarFilled);
283 | };
284 |
285 | return (
286 |
287 |
288 | {label && (
289 |
294 | {label}
295 |
296 | )}
297 |
298 |
299 |
300 | {isStarFilled ? : }
301 |
302 |
303 |
304 |
305 | );
306 | };
307 |
308 | export default StarrySeparator;
309 | `}
310 | />
311 |
--------------------------------------------------------------------------------
/content/docs/tabs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Tabs
3 | description: Sync UI offers a variety of responsive tab animation styles to enhance your user interface across all device sizes.
4 | ---
5 |
6 | Sync UI offers a variety of tab animation styles to enhance your user interface and provide visual feedback during tab transitions. These animations are now fully responsive and work seamlessly across all device sizes.
7 |
8 | #### Sliding Underline
9 |
10 | A smooth underline that slides to the active tab, adapting to screen size.
11 |
12 | }
14 | code={`
15 | import React, { useState } from "react";
16 | import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
17 | import { motion } from "framer-motion";
18 |
19 | const SlidingUnderlineTabs = () => {
20 | const theme = useTheme();
21 | const [activeTab, setActiveTab] = useState(0);
22 | const tabs = ["Home", "Profile", "Settings", "Contact"];
23 | const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
24 |
25 | return (
26 |
38 | {tabs.map((tab, index) => (
39 | setActiveTab(index)}
50 | >
51 |
52 | {tab}
53 |
54 | {activeTab === index && (
55 |
67 | )}
68 |
69 | ))}
70 |
71 | );
72 | };
73 |
74 | export default SlidingUnderlineTabs;
75 | `}
76 | />
77 |
78 | #### Floating Background
79 |
80 | A dynamic background that floats behind the active tab, now responsive to screen size.
81 |
82 | }
84 | code={`
85 | import React, { useState } from "react";
86 | import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
87 | import { motion, AnimatePresence } from "framer-motion";
88 |
89 | const FloatingBackgroundTabs = () => {
90 | const theme = useTheme();
91 | const [activeTab, setActiveTab] = useState(0);
92 | const tabs = ["Home", "Profile", "Settings", "Contact"];
93 | const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
94 |
95 | return (
96 |
109 |
135 | {tabs.map((tab, index) => (
136 | setActiveTab(index)}
149 | >
150 |
157 | {tab}
158 |
159 |
160 | {activeTab === index && (
161 |
178 | )}
179 |
180 |
181 | ))}
182 |
183 | );
184 | };
185 |
186 | export default FloatingBackgroundTabs;
187 | `}
188 | />
189 |
190 | #### Elevated Cards
191 |
192 | Tabs that elevate when active, giving a 3D effect, now responsive to different screen sizes.
193 |
194 | }
196 | code={`
197 | import React, { useState } from "react";
198 | import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
199 | import { motion } from "framer-motion";
200 |
201 | const ElevatedCardsTabs = () => {
202 | const theme = useTheme();
203 | const [activeTab, setActiveTab] = useState(0);
204 | const tabs = ["Home", "Profile", "Settings", "Contact"];
205 | const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
206 |
207 | return (
208 |
219 | {tabs.map((tab, index) => (
220 | setActiveTab(index)}
240 | >
241 |
255 | {tab}
256 |
257 |
258 | ))}
259 |
260 | );
261 | };
262 |
263 | export default ElevatedCardsTabs;
264 | `}
265 | />
266 |
267 | #### Growing Background
268 |
269 | A background that grows to highlight the active tab, now fully responsive.
270 |
271 | }
273 | code={`
274 | import React, { useState } from "react";
275 | import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
276 | import { motion, AnimatePresence } from "framer-motion";
277 |
278 | const GrowingBackgroundTabs = () => {
279 | const theme = useTheme();
280 | const [activeTab, setActiveTab] = useState(0);
281 | const tabs = ["Home", "Profile", "Settings", "Contact"];
282 | const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
283 |
284 | return (
285 |
296 | {tabs.map((tab, index) => (
297 | setActiveTab(index)}
308 | >
309 |
317 | {tab}
318 |
319 |
320 | {activeTab === index && (
321 |
337 | )}
338 |
339 |
340 | ))}
341 |
342 | );
343 | };
344 |
345 | export default GrowingBackgroundTabs;
346 | `}
347 | />
--------------------------------------------------------------------------------
/context/GithubContext.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useContext,
4 | useState,
5 | useEffect,
6 | useRef,
7 | } from "react";
8 | import axios from "axios";
9 |
10 | const GitHubContext = createContext();
11 |
12 | export function GitHubProvider({ children }) {
13 | const [gitHubData, setGitHubData] = useState({
14 | stars: 0,
15 | stargazers: [],
16 | loading: true,
17 | error: null,
18 | });
19 |
20 | const isInitialLoad = useRef(true);
21 | const previousStargazersRef = useRef([]);
22 |
23 | const GITHUB_TOKEN = process.env.NEXT_PUBLIC_GITHUB_TOKEN;
24 |
25 | const fetchGitHubData = async () => {
26 | try {
27 | const headers = {
28 | Accept: "application/vnd.github.v3+json",
29 | };
30 |
31 | if (GITHUB_TOKEN) {
32 | headers.Authorization = `token ${GITHUB_TOKEN}`;
33 | }
34 |
35 | const repoResponse = await axios.get(
36 | "https://api.github.com/repos/AbhiVarde/syncui",
37 | { headers }
38 | );
39 |
40 | const stargazersResponse = await axios.get(
41 | "https://api.github.com/repos/AbhiVarde/syncui/stargazers",
42 | {
43 | headers,
44 | params: {
45 | per_page: 100,
46 | },
47 | }
48 | );
49 |
50 | const newStargazers = stargazersResponse.data;
51 | const newStarsCount = repoResponse.data.stargazers_count;
52 |
53 | if (isInitialLoad.current) {
54 | setGitHubData({
55 | stars: newStarsCount,
56 | stargazers: newStargazers,
57 | loading: false,
58 | error: null,
59 | });
60 | isInitialLoad.current = false;
61 | previousStargazersRef.current = newStargazers;
62 | } else {
63 | if (newStarsCount !== gitHubData.stars) {
64 | setGitHubData({
65 | stars: newStarsCount,
66 | stargazers: newStargazers,
67 | loading: false,
68 | error: null,
69 | });
70 | previousStargazersRef.current = newStargazers;
71 | }
72 | }
73 |
74 | if (process.env.NODE_ENV === "development") {
75 | const rateLimit = repoResponse.headers["x-ratelimit-remaining"];
76 | console.log(`GitHub API rate limit remaining: ${rateLimit}`);
77 | }
78 | } catch (error) {
79 | console.error("GitHub API error:", error);
80 |
81 | setGitHubData((prevData) => ({
82 | ...prevData,
83 | stars: prevData.stars || 0,
84 | loading: false,
85 | error: error.response?.data?.message || "Failed to fetch GitHub data",
86 | }));
87 | }
88 | };
89 |
90 | useEffect(() => {
91 | fetchGitHubData();
92 |
93 | const intervalId = setInterval(() => {
94 | fetchGitHubData();
95 | }, 60000);
96 |
97 | return () => clearInterval(intervalId);
98 | }, []);
99 |
100 | return (
101 |
102 | {children}
103 |
104 | );
105 | }
106 |
107 | export function useGitHub() {
108 | const context = useContext(GitHubContext);
109 | if (context === undefined) {
110 | throw new Error("useGitHub must be used within a GitHubProvider");
111 | }
112 | return context;
113 | }
114 |
--------------------------------------------------------------------------------
/context/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useState, useEffect } from "react";
2 |
3 | const ThemeContext = createContext({
4 | isDarkMode: false,
5 | toggleTheme: () => {},
6 | });
7 |
8 | export const ThemeProvider = ({ children }) => {
9 | const [isDarkMode, setIsDarkMode] = useState(false);
10 |
11 | useEffect(() => {
12 | const savedTheme = localStorage.getItem("theme");
13 | if (savedTheme) {
14 | setIsDarkMode(savedTheme === "dark");
15 | }
16 | }, []);
17 |
18 | const toggleTheme = () => {
19 | const newTheme = !isDarkMode;
20 | setIsDarkMode(newTheme);
21 | localStorage.setItem("theme", newTheme ? "dark" : "light");
22 | };
23 |
24 | return (
25 |
26 | {children}
27 |
28 | );
29 | };
30 |
31 | export const useTheme = () => useContext(ThemeContext);
32 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/docs.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import path from "path";
3 | import matter from "gray-matter";
4 | import { bundleMDX } from "mdx-bundler";
5 |
6 | const docsDirectory = path.join(process.cwd(), "content/docs");
7 |
8 | export async function getAllDocsSlugs() {
9 | const files = fs.readdirSync(docsDirectory);
10 | return files
11 | .filter((file) => file.endsWith(".mdx"))
12 | .map((file) => {
13 | const slug = file.replace(/\.mdx$/, "");
14 | const fullPath = path.join(docsDirectory, file);
15 | const fileContents = fs.readFileSync(fullPath, "utf8");
16 | const { data } = matter(fileContents);
17 |
18 | return {
19 | slug: slug === "index" ? "" : slug,
20 | title: data.title || slug,
21 | url: `/docs/${slug === "index" ? "" : slug}`,
22 | };
23 | });
24 | }
25 |
26 | export async function getDocBySlug(slug) {
27 | const fileName = slug === "" ? "index.mdx" : `${slug}.mdx`;
28 | const fullPath = path.join(docsDirectory, fileName);
29 |
30 | if (!fs.existsSync(fullPath)) {
31 | return null;
32 | }
33 |
34 | const fileContents = fs.readFileSync(fullPath, "utf8");
35 |
36 | const { data, content } = matter(fileContents);
37 |
38 | try {
39 | const { code, frontmatter } = await bundleMDX({
40 | source: content,
41 | cwd: docsDirectory,
42 | });
43 |
44 | const toc = extractTOC(content);
45 |
46 | return {
47 | code,
48 | frontmatter: {
49 | ...frontmatter,
50 | ...data,
51 | },
52 | toc,
53 | };
54 | } catch (error) {
55 | console.error("Error in bundleMDX:", error);
56 | return null;
57 | }
58 | }
59 |
60 | function extractTOC(content) {
61 | if (typeof content !== "string") {
62 | console.error("extractTOC received non-string content:", content);
63 | return [];
64 | }
65 |
66 | const headingRegex = /^(#{2,4})\s+(.+)$/gm;
67 | const toc = [];
68 | let match;
69 |
70 | try {
71 | while ((match = headingRegex.exec(content)) !== null) {
72 | toc.push({
73 | level: match[1].length,
74 | text: match[2],
75 | id: match[2].toLowerCase().replace(/[^\w]+/g, "-"),
76 | });
77 | }
78 | } catch (error) {
79 | console.error("Error in extractTOC:", error);
80 | }
81 |
82 | return toc;
83 | }
84 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | import remarkGfm from "remark-gfm";
2 |
3 | const nextConfig = {
4 | webpack: async (config, options) => {
5 | config.module.rules.push({
6 | test: /\.mdx?$/,
7 | use: [
8 | options.defaultLoaders.babel,
9 | {
10 | loader: "@mdx-js/loader",
11 | options: {
12 | remarkPlugins: [remarkGfm],
13 | },
14 | },
15 | ],
16 | });
17 |
18 | return config;
19 | },
20 | pageExtensions: ["js", "jsx", "md", "mdx"],
21 | };
22 |
23 | export default nextConfig;
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sync-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@emotion/react": "^11.13.0",
13 | "@emotion/styled": "^11.13.0",
14 | "@giscus/react": "^3.0.0",
15 | "@mdx-js/loader": "^3.0.1",
16 | "@mdx-js/react": "^3.0.1",
17 | "@mui/material": "^5.16.7",
18 | "@vercel/analytics": "^1.3.1",
19 | "axios": "^1.7.9",
20 | "framer-motion": "^11.3.24",
21 | "gray-matter": "^4.0.3",
22 | "mdx-bundler": "^10.0.2",
23 | "next": "14.2.5",
24 | "next-mdx-remote": "^5.0.0",
25 | "next-seo": "^6.5.0",
26 | "react": "^18",
27 | "react-dom": "^18",
28 | "react-icons": "^5.2.1",
29 | "react-syntax-highlighter": "^15.5.0",
30 | "recharts": "^2.15.0",
31 | "remark-gfm": "^4.0.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/pages/404.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { motion } from "framer-motion";
3 | import { Box, Typography, Button, useTheme } from "@mui/material";
4 | import Head from "next/head";
5 | import { useRouter } from "next/router";
6 | import { LiaTelegramPlane } from "react-icons/lia";
7 |
8 | const getRandomInt = (max) => Math.floor(Math.random() * max);
9 |
10 | const DEFAULT_CHARACTER_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
11 | const NUMBER_CHARACTER_SET = "0123456789".split("");
12 |
13 | const ScrambleText = ({
14 | text,
15 | duration = 800, // Reduced duration for subtler animation
16 | delay = 0,
17 | characterSet = DEFAULT_CHARACTER_SET,
18 | monospace = false, // Added monospace option
19 | animate = true, // Option to disable animation
20 | }) => {
21 | const [displayText, setDisplayText] = useState(() => text.split(""));
22 | const iterationCount = useRef(0);
23 | const [isAnimating, setIsAnimating] = useState(false);
24 |
25 | useEffect(() => {
26 | // Skip animation setup if animate is false
27 | if (!animate) {
28 | setDisplayText(text.split(""));
29 | return;
30 | }
31 |
32 | const startTimeout = setTimeout(() => {
33 | setIsAnimating(true);
34 | }, delay);
35 | return () => clearTimeout(startTimeout);
36 | }, [delay, animate, text]);
37 |
38 | useEffect(() => {
39 | if (!isAnimating || !animate) return;
40 |
41 | const maxIterations = text.length;
42 | const startTime = performance.now();
43 | let animationFrameId;
44 |
45 | const animateFrame = (currentTime) => {
46 | const elapsed = currentTime - startTime;
47 | const progress = Math.min(elapsed / duration, 1);
48 |
49 | // Smoother easing function for more subtle animation
50 | const easedProgress = Math.pow(progress, 1.5);
51 |
52 | iterationCount.current = easedProgress * maxIterations;
53 |
54 | setDisplayText((currentText) =>
55 | currentText.map((letter, index) =>
56 | letter === " "
57 | ? letter
58 | : index <= iterationCount.current
59 | ? text[index]
60 | : characterSet[getRandomInt(characterSet.length)]
61 | )
62 | );
63 |
64 | if (progress < 1) {
65 | animationFrameId = requestAnimationFrame(animateFrame);
66 | }
67 | };
68 |
69 | animationFrameId = requestAnimationFrame(animateFrame);
70 |
71 | return () => cancelAnimationFrame(animationFrameId);
72 | }, [text, duration, isAnimating, characterSet, animate]);
73 |
74 | return (
75 |
82 | {displayText.map((letter, index) => (
83 |
90 | {letter === " " ? "\u00A0" : letter}
91 |
92 | ))}
93 |
94 | );
95 | };
96 |
97 | const ScrambleNumber = ({ number, duration = 1000, delay = 100 }) => {
98 | return (
99 |
106 | );
107 | };
108 |
109 | const NotFound = () => {
110 | const theme = useTheme();
111 | const router = useRouter();
112 |
113 | const containerVariants = {
114 | hidden: { opacity: 0 },
115 | visible: {
116 | opacity: 1,
117 | transition: {
118 | staggerChildren: 0.2,
119 | delayChildren: 0.3,
120 | },
121 | },
122 | };
123 |
124 | const itemVariants = {
125 | hidden: { y: 10, opacity: 0 },
126 | visible: {
127 | y: 0,
128 | opacity: 1,
129 | transition: {
130 | type: "spring",
131 | stiffness: 70,
132 | damping: 12,
133 | duration: 0.6,
134 | },
135 | },
136 | };
137 |
138 | const planeVariants = {
139 | hidden: { y: -10, opacity: 0 }, // Reduced from y: -20
140 | visible: {
141 | y: 0,
142 | opacity: 1,
143 | transition: {
144 | duration: 0.8, // Reduced from 1
145 | ease: "easeOut",
146 | },
147 | },
148 | animate: {
149 | y: [-3, 3, -3], // Reduced from [-5, 5, -5]
150 | rotate: [-2, 2, -2], // Reduced from [-3, 3, -3]
151 | scale: [1, 1.02, 1], // Reduced scale change from 1.03
152 | transition: {
153 | duration: 6, // Slower for subtler movement
154 | repeat: Infinity,
155 | ease: "easeInOut",
156 | },
157 | },
158 | };
159 |
160 | return (
161 | <>
162 |
163 | Page Not Found // Sync UI
164 |
168 | {/* Add Google Fonts link for Roboto Mono */}
169 |
173 |
174 |
175 |
189 |
201 |
206 |
210 |
211 |
212 | {/* 404 Heading */}
213 |
214 |
224 |
225 |
226 |
227 |
228 | {/* Subheading with NO scramble animation */}
229 |
230 |
241 | {/* Removed animation for this text */}
242 | Oops! Page took flight!
243 |
244 |
245 |
246 | {/* Description */}
247 |
248 |
257 | Looks like this page decided to go on vacation. Maybe try our
258 | homepage instead?
259 |
260 |
261 |
262 | {/* Action buttons */}
263 |
264 |
272 | router.push("/")}
275 | sx={{
276 | px: 3,
277 | py: 1,
278 | borderRadius: "4px",
279 | backgroundColor:
280 | theme.palette.mode === "dark" ? "#ffffff" : "#000000",
281 | color: theme.palette.mode === "dark" ? "#000000" : "#ffffff",
282 | fontWeight: 500,
283 | textTransform: "none",
284 | "&:hover": {
285 | backgroundColor:
286 | theme.palette.mode === "dark" ? "#e0e0e0" : "#333333",
287 | transform: "translateY(-2px)",
288 | },
289 | transition: "all 0.3s ease",
290 | }}
291 | >
292 | Return Home
293 |
294 |
295 | window.history.back()}
298 | sx={{
299 | px: 3,
300 | py: 1,
301 | borderRadius: "4px",
302 | fontWeight: 500,
303 | textTransform: "none",
304 | borderColor:
305 | theme.palette.mode === "dark" ? "#ffffff" : "#000000",
306 | color: theme.palette.mode === "dark" ? "#ffffff" : "#000000",
307 | "&:hover": {
308 | transform: "translateY(-2px)",
309 | borderColor:
310 | theme.palette.mode === "dark" ? "#e0e0e0" : "#333333",
311 | backgroundColor: "transparent",
312 | },
313 | transition: "all 0.3s ease",
314 | }}
315 | >
316 | Go Back
317 |
318 |
319 |
320 |
321 |
322 | >
323 | );
324 | };
325 |
326 | export default NotFound;
327 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { ThemeProvider as MuiThemeProvider, CssBaseline } from "@mui/material";
3 | import { AnimatePresence } from "framer-motion";
4 | import Router from "next/router";
5 | import { Analytics } from "@vercel/analytics/react";
6 | import Layout from "../components/layout/Layout";
7 | import Loader from "@/components/loader";
8 | import { lightTheme, darkTheme } from "../theme";
9 | import { ThemeProvider, useTheme } from "../context/ThemeContext";
10 | import "../styles/globals.css";
11 | import { DefaultSeo } from "next-seo";
12 | import { GitHubProvider } from "@/context/GithubContext";
13 |
14 | function MyApp({ Component, pageProps }) {
15 | return (
16 | <>
17 |
67 |
68 |
69 |
70 |
71 |
72 | >
73 | );
74 | }
75 |
76 | function AppContent({ Component, pageProps }) {
77 | const { isDarkMode, toggleTheme } = useTheme();
78 | const [loading, setLoading] = useState(false);
79 | const [currentRoute, setCurrentRoute] = useState("");
80 | const [isMounted, setIsMounted] = useState(false);
81 | const theme = isDarkMode ? darkTheme : lightTheme;
82 |
83 | useEffect(() => {
84 | setIsMounted(true);
85 | setCurrentRoute(window.location.pathname.split("/")[1]);
86 | const handleRouteChange = (url) => {
87 | const newRoute = url.split("/")[1];
88 | if (newRoute !== currentRoute) {
89 | setLoading(true);
90 | }
91 | };
92 | const handleRouteComplete = (url) => {
93 | setLoading(false);
94 | setCurrentRoute(url.split("/")[1]);
95 | };
96 | Router.events.on("routeChangeStart", handleRouteChange);
97 | Router.events.on("routeChangeComplete", handleRouteComplete);
98 | Router.events.on("routeChangeError", () => setLoading(false));
99 | return () => {
100 | Router.events.off("routeChangeStart", handleRouteChange);
101 | Router.events.off("routeChangeComplete", handleRouteComplete);
102 | Router.events.off("routeChangeError", () => setLoading(false));
103 | };
104 | }, [currentRoute]);
105 |
106 | if (!isMounted) {
107 | return null;
108 | }
109 |
110 | return (
111 |
112 |
113 |
114 | {loading ? (
115 |
116 | ) : (
117 |
123 |
124 |
129 |
130 |
131 | )}
132 |
133 | );
134 | }
135 |
136 | export default MyApp;
137 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from "next/document";
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/pages/docs/[[...slug]].jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getMDXComponent } from "mdx-bundler/client";
3 | import { getAllDocsSlugs, getDocBySlug } from "../../lib/docs";
4 | import DocsLayout from "../../components/docs/DocsLayout";
5 | import { MDXComponents } from "../../components/mdx-components";
6 | import { DocPager } from "../../components/docs/DocPager";
7 | import Head from "next/head";
8 | import { MDXProvider } from "@mdx-js/react";
9 |
10 | export default function DocPage({ code, frontmatter, toc, docsTree, slug }) {
11 | const Component = React.useMemo(() => {
12 | if (code) {
13 | try {
14 | return getMDXComponent(code);
15 | } catch (error) {
16 | console.error("Error in getMDXComponent:", error);
17 | return null;
18 | }
19 | }
20 | return null;
21 | }, [code]);
22 |
23 | if (!Component) {
24 | return Error: Could not load the document.
;
25 | }
26 |
27 | return (
28 | <>
29 |
30 | {frontmatter.title} // Sync UI
31 |
32 |
33 |
34 | {frontmatter.title}
35 |
36 |
37 |
38 |
39 |
40 |
41 | >
42 | );
43 | }
44 |
45 | export async function getStaticProps({ params }) {
46 | const slug = params.slug?.join("/") || "";
47 |
48 | const docData = await getDocBySlug(slug);
49 |
50 | if (!docData) {
51 | return {
52 | notFound: true,
53 | };
54 | }
55 |
56 | const { code, frontmatter, toc } = docData;
57 | const docsTree = await getAllDocsSlugs();
58 |
59 | return {
60 | props: {
61 | code,
62 | frontmatter,
63 | toc,
64 | docsTree,
65 | slug,
66 | },
67 | };
68 | }
69 |
70 | export async function getStaticPaths() {
71 | const slugs = await getAllDocsSlugs();
72 |
73 | const paths = slugs.map((item) => ({
74 | params: { slug: item.slug === "" ? [] : item.slug.split("/") },
75 | }));
76 |
77 | return {
78 | paths,
79 | fallback: false,
80 | };
81 | }
82 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import HeroSection from "../components/home/HeroSection";
3 | import TabsSection from "@/components/home/TabsSection";
4 | import StargazersSection from "@/components/home/StargazersSection";
5 | import { motion } from "framer-motion";
6 |
7 | const Home = () => {
8 | return (
9 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default Home;
23 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/default-og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/default-og-image.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/logo.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/videos/Portfolio-Template.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/videos/Portfolio-Template.mov
--------------------------------------------------------------------------------
/public/videos/SaaS-Template.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/videos/SaaS-Template.mp4
--------------------------------------------------------------------------------
/public/videos/Startup-Template.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/videos/Startup-Template.mov
--------------------------------------------------------------------------------
/public/videos/nature.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AbhiVarde/syncui/e1feef5b490af7b7151dc673393298cfed24d152/public/videos/nature.mp4
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;700&display=swap");
2 |
3 | :root {
4 | --max-width: 1100px;
5 | --border-radius: 12px;
6 | --light-selection-bg: rgba(39, 39, 42, 0.99);
7 | --light-selection-color: rgb(244, 244, 245);
8 | --dark-selection-bg: rgba(244, 244, 245, 0.99);
9 | --dark-selection-color: rgb(39, 39, 42);
10 | }
11 |
12 | * {
13 | box-sizing: border-box;
14 | padding: 0;
15 | margin: 0;
16 | scrollbar-width: none;
17 | /* Firefox */
18 | -ms-overflow-style: none;
19 | /* IE and Edge */
20 | }
21 |
22 | *::-webkit-scrollbar {
23 | display: none;
24 | /* Chrome, Safari, Edge (WebKit) */
25 | }
26 |
27 | html,
28 | body {
29 | max-width: 100vw;
30 | overflow-x: hidden;
31 | font-family: "Montserrat", sans-serif;
32 | }
33 |
34 | a {
35 | color: inherit;
36 | text-decoration: none;
37 | }
38 |
39 | ::selection {
40 | background-color: var(--light-selection-bg);
41 | color: var(--light-selection-color);
42 | }
43 |
44 | [data-theme="dark"] ::selection {
45 | background-color: var(--dark-selection-bg);
46 | color: var(--dark-selection-color);
47 | }
48 |
--------------------------------------------------------------------------------
/theme/index.js:
--------------------------------------------------------------------------------
1 | import { createTheme, responsiveFontSizes } from "@mui/material/styles";
2 |
3 | const pxToRem = (px) => `${px / 16}rem`;
4 |
5 | function customResponsiveFontSizes({ sm, md, lg }) {
6 | return {
7 | "@media (min-width:600px)": {
8 | fontSize: pxToRem(sm),
9 | },
10 | "@media (min-width:900px)": {
11 | fontSize: pxToRem(md),
12 | },
13 | "@media (min-width:1200px)": {
14 | fontSize: pxToRem(lg),
15 | },
16 | };
17 | }
18 |
19 | const baseTheme = {
20 | typography: {
21 | fontFamily: '"Montserrat", "Helvetica", "Arial", sans-serif',
22 | h1: {
23 | fontWeight: 700,
24 | lineHeight: 1.25,
25 | fontSize: pxToRem(42),
26 | ...customResponsiveFontSizes({ sm: 52, md: 58, lg: 64 }),
27 | },
28 | h2: {
29 | fontWeight: 700,
30 | lineHeight: 1.33,
31 | fontSize: pxToRem(34),
32 | ...customResponsiveFontSizes({ sm: 40, md: 44, lg: 48 }),
33 | },
34 | h3: {
35 | fontWeight: 600,
36 | lineHeight: 1.5,
37 | fontSize: pxToRem(26),
38 | ...customResponsiveFontSizes({ sm: 32, md: 36, lg: 40 }),
39 | },
40 | h4: {
41 | fontWeight: 600,
42 | lineHeight: 1.5,
43 | fontSize: pxToRem(22),
44 | ...customResponsiveFontSizes({ sm: 24, md: 28, lg: 32 }),
45 | },
46 | h5: {
47 | fontWeight: 500,
48 | lineHeight: 1.5,
49 | fontSize: pxToRem(20),
50 | ...customResponsiveFontSizes({ sm: 20, md: 24, lg: 28 }),
51 | },
52 | h6: {
53 | fontWeight: 500,
54 | lineHeight: 1.56,
55 | fontSize: pxToRem(18),
56 | ...customResponsiveFontSizes({ sm: 18, md: 20, lg: 22 }),
57 | },
58 | subtitle1: {
59 | fontWeight: 600,
60 | lineHeight: 1.5,
61 | fontSize: pxToRem(16),
62 | ...customResponsiveFontSizes({ sm: 16, md: 18, lg: 20 }),
63 | },
64 | subtitle2: {
65 | fontWeight: 500,
66 | lineHeight: 1.57,
67 | fontSize: pxToRem(14),
68 | ...customResponsiveFontSizes({ sm: 14, md: 16, lg: 18 }),
69 | },
70 | body1: {
71 | fontWeight: 400,
72 | lineHeight: 1.5,
73 | fontSize: pxToRem(16),
74 | ...customResponsiveFontSizes({ sm: 16, md: 16, lg: 18 }),
75 | },
76 | body2: {
77 | fontWeight: 400,
78 | lineHeight: 1.57,
79 | fontSize: pxToRem(14),
80 | ...customResponsiveFontSizes({ sm: 14, md: 14, lg: 16 }),
81 | },
82 | button: {
83 | fontWeight: 600,
84 | lineHeight: 1.71,
85 | fontSize: pxToRem(14),
86 | textTransform: "capitalize",
87 | ...customResponsiveFontSizes({ sm: 14, md: 15, lg: 16 }),
88 | },
89 | caption: {
90 | lineHeight: 1.5,
91 | fontSize: pxToRem(12),
92 | ...customResponsiveFontSizes({ sm: 12, md: 13, lg: 14 }),
93 | },
94 | overline: {
95 | fontWeight: 700,
96 | lineHeight: 1.5,
97 | fontSize: pxToRem(12),
98 | textTransform: "uppercase",
99 | ...customResponsiveFontSizes({ sm: 12, md: 13, lg: 14 }),
100 | },
101 | },
102 | shape: {
103 | borderRadius: 8,
104 | },
105 | components: {
106 | MuiButton: {
107 | styleOverrides: {
108 | root: {
109 | textTransform: "none",
110 | fontWeight: 500,
111 | borderRadius: 8,
112 | transition: "all 0.3s ease-in-out",
113 | padding: "8px 20px",
114 | fontSize: "16px",
115 | },
116 | },
117 | variants: [
118 | {
119 | props: { variant: "outlined" },
120 | style: ({ theme }) => ({
121 | border: `1px solid`,
122 | backgroundColor: theme.palette.background.default,
123 | color: theme.palette.text.primary,
124 | "&:hover": {
125 | border: `1px solid`,
126 | backgroundColor: theme.palette.background.default,
127 | },
128 | }),
129 | },
130 | {
131 | props: { variant: "contained" },
132 | style: ({ theme }) => ({
133 | border: `1px solid ${theme.palette.text.primary}`,
134 | backgroundColor: theme.palette.text.primary,
135 | color: theme.palette.background.default,
136 | "&:hover": {
137 | border: `1px solid ${theme.palette.text.primary}`,
138 | backgroundColor: theme.palette.text.primary,
139 | },
140 | }),
141 | },
142 | ],
143 | },
144 | MuiTooltip: {
145 | styleOverrides: {
146 | tooltip: {
147 | backgroundColor: "black",
148 | color: "white",
149 | fontSize: "12px",
150 | padding: "6px",
151 | borderRadius: "4px",
152 | fontWeight: 500,
153 | boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
154 | },
155 | arrow: {
156 | color: "black",
157 | },
158 | },
159 | },
160 | },
161 | };
162 |
163 | const createResponsiveTheme = (palette) => {
164 | const theme = createTheme({
165 | ...baseTheme,
166 | palette,
167 | components: {
168 | ...baseTheme.components,
169 | MuiTooltip: {
170 | ...baseTheme.components.MuiTooltip,
171 | styleOverrides: {
172 | ...baseTheme.components.MuiTooltip.styleOverrides,
173 | tooltip: {
174 | ...baseTheme.components.MuiTooltip.styleOverrides.tooltip,
175 | backgroundColor: palette.mode === "dark" ? "white" : "black",
176 | color: palette.mode === "dark" ? "black" : "white",
177 | },
178 | arrow: {
179 | color: palette.mode === "dark" ? "white" : "black",
180 | },
181 | },
182 | },
183 | },
184 | });
185 | return responsiveFontSizes(theme);
186 | };
187 |
188 | export const lightTheme = createResponsiveTheme({
189 | mode: "light",
190 | background: {
191 | default: "#FFFFFF",
192 | paper: "#F8F8F8",
193 | },
194 | text: {
195 | primary: "#1A1A1A",
196 | secondary: "#585858",
197 | },
198 | primary: {
199 | main: "#3366FF",
200 | light: "#6690FF",
201 | dark: "#0040FF",
202 | },
203 | secondary: {
204 | main: "#FF3366",
205 | light: "#FF6690",
206 | dark: "#FF0040",
207 | },
208 | divider: "rgba(0, 0, 0, 0.12)",
209 | });
210 |
211 | export const darkTheme = createResponsiveTheme({
212 | mode: "dark",
213 | background: {
214 | default: "#000000",
215 | paper: "#111111",
216 | },
217 | text: {
218 | primary: "#FFFFFF",
219 | secondary: "#B0B0B0",
220 | },
221 | primary: {
222 | main: "#66A3FF",
223 | light: "#99C2FF",
224 | dark: "#3385FF",
225 | },
226 | secondary: {
227 | main: "#FF6699",
228 | light: "#FF99BB",
229 | dark: "#FF3377",
230 | },
231 | divider: "rgba(255, 255, 255, 0.12)",
232 | });
233 |
--------------------------------------------------------------------------------
/utils/constants.js:
--------------------------------------------------------------------------------
1 | export const SPONSOR_URL = "https://github.com/sponsors/AbhiVarde";
2 | export const GITHUB_URL = "https://github.com/AbhiVarde/syncui";
3 | export const TWITTER_URL = "https://x.com/syncuidesign";
4 |
--------------------------------------------------------------------------------