Skip to content

Instantly share code, notes, and snippets.

@VashonG
Created September 25, 2022 12:23
Show Gist options
  • Save VashonG/0fa1dbd89d9e92b09f0a1ee15d5cbd80 to your computer and use it in GitHub Desktop.
Save VashonG/0fa1dbd89d9e92b09f0a1ee15d5cbd80 to your computer and use it in GitHub Desktop.
Radial Nav Menu
<div id="root"></div>
interface IMenuOptionProps {
icon: string;
index: number;
label: string;
url: string;
type: "quick" | "full";
}
const MenuOption: React.FC<IMenuOptionProps> = (props: IMenuOptionProps) => {
const { toggled } = React.useContext(AppContext);
const className: string = `menu-${props.type}-option`,
delay: number = toggled ? 200 : 0;
const styles: React.CSSProperties = {
transitionDelay: `${delay + (50 * props.index)}ms`
}
return (
<a href={props.url} target="_blank" className={className} disabled={!toggled} style={styles}>
<i className={props.icon} />
<h3 className={props.type === "quick" ? "tooltip" : "label"}>{props.label}</h3>
</a>
);
}
const Menu: React.FC = () => {
const { toggled } = React.useContext(AppContext);
const profileImage: string = "https://images.unsplash.com/photo-1614027164847-1b28cfe1df60?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8bGlvbnxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=500&q=60";
const getOptions = (options: IMenuOptionProps[], type: "quick" | "full"): JSX.Element[] => {
return options.map((option: IMenuOptionProps, index: number) => (
<MenuOption
key={option.label}
icon={option.icon}
index={index}
label={option.label}
url={option.url}
type={type}
/>
));
}
const getQuickOptions = (): JSX.Element[] => {
return getOptions([{
icon: "fa-solid fa-bell", label: "Notifications", url: "https://codepen.io/Hyperplexed"
}, {
icon: "fa-solid fa-gear", label: "Settings", url: "https://codepen.io/Hyperplexed"
}, {
icon: "fa-solid fa-moon", label: "Theme", url: "https://codepen.io/Hyperplexed"
}], "quick");
}
const getFullOptions = (): JSX.Element[] => {
return getOptions([{
icon: "fa-solid fa-house", label: "Home", url: "https://codepen.io/Hyperplexed"
}, {
icon: "fa-solid fa-user", label: "Profile", url: "https://codepen.io/Hyperplexed"
}, {
icon: "fa-solid fa-chart-line", label: "Dashboard", url: "https://codepen.io/Hyperplexed"
}, {
icon: "fa-solid fa-heart", label: "Subscriptions", url: "https://codepen.io/Hyperplexed"
}, {
icon: "fa-solid fa-wallet", label: "Wallet", url: "https://codepen.io/Hyperplexed"
}], "full");
}
return (
<div id="menu" className={classNames({ toggled })}>
<div id="menu-background-wrapper">
<div id="menu-background" />
</div>
<img id="menu-profile-image" src={profileImage} />
<div id="menu-quick-options">
{getQuickOptions()}
</div>
<div id="menu-full-options">
{getFullOptions()}
</div>
</div>
);
}
const AppContext = React.createContext(null);
const App: React.FC = () => {
const [toggled, setToggledTo] = React.useState<boolean>(false);
React.useEffect(() => {
setTimeout(() => setToggledTo(true), 1000);
}, []);
const handleOnClick = (): void => setToggledTo(!toggled);
return(
<AppContext.Provider value={{ toggled }}>
<div id="app">
<Menu />
<button id="menu-toggle" type="button" onClick={handleOnClick}>
<i className={ toggled ? "fa-solid fa-xmark-large" : "fa-solid fa-bars-staggered" } />
</button>
</div>
</AppContext.Provider>
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@types/react@17.0.44/index.d.ts"></script>
<script src="https://unpkg.com/@types/react-dom@17.0.15/index.d.ts"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.3.1/index.min.js"></script>
<script src="https://codepen.io/Hyperplexed/pen/xxYJYjM/54407644e24173ad6019b766443bf2a6.js"></script>
@function gray($color){
@return rgb($color, $color, $color);
}
$red: rgb(237, 66, 69);
$blue: rgb(100, 181, 246);
$darkBlue: rgb(21, 101, 192);
$background: rgb(45, 50, 64);
$theme: $blue;
$themeDark: $darkBlue;
$smallShadow: 0px 0px 10px 4px rgba(black, 0.08);
$largeShadow: 0px 0px 20px 4px rgba(black, 0.15);
body{
margin: 0px;
overflow: hidden;
padding: 0px;
input, h1, h2, h3, a {
font-family: "Rubik", sans-serif;
font-weight: 400;
margin: 0px;
padding: 0px;
}
}
#app {
background-color: $background;
height: 100vh;
overflow: hidden;
width: 100vw;
#menu-toggle {
background-color: white;
border: none;
border-radius: 8vh;
bottom: 0px;
box-shadow: $smallShadow;
cursor: pointer;
height: 8vh;
left: 0px;
margin: 2vh;
position: fixed;
width: 8vh;
z-index: 3;
&:hover {
background-color: gray(240);
}
i {
color: $theme;
font-size: 2vh;
height: 3vh;
line-height: 3vh;
text-align: center;
width: 3vh;
}
}
#menu {
height: 100vh;
opacity: 0;
position: absolute;
transform: translateX(-50%);
transition: opacity 250ms, transform 250ms;
transition-delay: 300ms;
width: 40vh;
&.toggled {
opacity: 1;
transform: translateX(0%);
transition-delay: 0ms;
#menu-quick-options {
.menu-quick-option {
opacity: 1;
transform: translateX(0%);
&:first-of-type,
&:last-of-type {
transform: translateX(-50%);
}
}
}
#menu-full-options {
.menu-full-option {
opacity: 1;
transform: translateX(-0.5vh);
&:first-of-type,
&:last-of-type {
transform: translateX(-4vh);
}
&:nth-of-type(2),
&:nth-of-type(4) {
transform: translateX(-2vh);
}
}
}
#menu-background-wrapper {
#menu-background {
&:before {
transform: translate(-50%, -50%);
transition-delay: 200ms;
}
}
}
}
#menu-background-wrapper {
height: 100vh;
left: 0px;
position: absolute;
top: 0px;
width: 40vh;
z-index: 1;
#menu-background {
background-color: $theme;
border-bottom-right-radius: 100%;
border-top-right-radius: 100%;
box-shadow: $largeShadow;
height: 200vh;
position: absolute;
right: 0px;
top: -50vh;
width: 200vh;
&:before {
background-color: rgba($theme, 0.05);
border: 1px solid rgba($theme, 0.2);
border-bottom-right-radius: 100%;
border-top-right-radius: 100%;
content: "";
height: 120%;
left: 50%;
position: absolute;
top: 50%;
transform: translate(-100%, -50%);
transition: transform 250ms;
width: 120%;
z-index: -1;
}
}
}
#menu-profile-image {
border-radius: 500px;
box-shadow: $largeShadow;
display: block;
height: 40vh;
left: 0px;
object-fit: cover;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 40vh;
z-index: 2;
}
#menu-quick-options {
align-items: center;
display: flex;
flex-direction: column;
height: 40vh;
justify-content: space-around;
position: absolute;
top: 50%;
transform: translate(100%, -50%);
width: 20vh;
z-index: 3;
.menu-quick-option {
align-items: center;
background-color: white;
border: none;
border-radius: 8vh;
box-shadow: $smallShadow;
cursor: pointer;
display: flex;
height: 8vh;
justify-content: center;
opacity: 0;
padding: 0px;
text-decoration: none;
transform: translateX(-30%) scale(0.25);
transition: opacity 150ms, transform 150ms;
width: 8vh;
&:hover {
background-color: gray(240);
.tooltip {
opacity: 1;
transform: translateX(100%);
}
}
&:first-of-type {
transform: translate(-80%, 30%) scale(0.5);
}
&:last-of-type {
transform: translate(-80%, -30%) scale(0.5);
}
i {
color: $theme;
font-size: 2vh;
height: 3vh;
line-height: 3vh;
text-align: center;
width: 3vh;
}
.tooltip {
background-color: gray(30);
border-radius: 0.5vh;
box-shadow: 0px 0px 1vh 0.25vh rgba(black, 0.08);
color: white;
font-size: 1em;
opacity: 0;
padding: 1vh;
pointer-events: none;
position: absolute;
right: -1vh;
transform: translateX(90%);
transition: opacity 250ms, transform 250ms;
}
}
}
#menu-full-options {
align-items: flex-start;
display: flex;
flex-direction: column;
height: 60vh;
justify-content: space-around;
left: 44vh;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 20vh;
z-index: 2;
.menu-full-option {
align-items: center;
background-color: transparent;
border: none;
border-radius: 0.5vh;
cursor: pointer;
display: flex;
gap: 1vh;
opacity: 0;
padding: 1vh;
text-decoration: none;
transform: translateX(-2vh);
transition: opacity 150ms, transform 150ms;
&:hover {
background-color: rgba(white, 0.05);
}
&:first-of-type,
&:last-of-type {
transform: translateX(-6vh);
}
&:nth-of-type(2),
&:nth-of-type(4) {
transform: translateX(-4vh);
}
i, h3 {
color: white;
font-size: 1.5vh;
height: 2vh;
line-height: 2vh;
}
}
}
}
}
@media(max-width: 1200px) {
#app {
#menu {
width: 30vh;
#menu-background-wrapper {
width: 30vh;
}
#menu-profile-image {
height: 30vh;
width: 30vh;
}
#menu-quick-options {
width: 15vh;
}
#menu-full-options {
left: 34vh;
}
}
}
}
@media(max-width: 800px) {
#app {
#menu {
width: 25vh;
#menu-background-wrapper {
width: 25vh;
}
#menu-profile-image {
height: 25vh;
width: 25vh;
}
#menu-quick-options {
width: 12.5vh;
.menu-quick-option {
height: 7vh;
width: 7vh;
}
}
#menu-full-options {
left: 29vh;
}
}
}
}
@media (prefers-reduced-motion) {
#app {
#menu {
transition: none;
#menu-quick-options {
.menu-quick-option {
transition: none;
}
}
#menu-full-options {
.menu-full-option {
transition: none;
}
}
#menu-background-wrapper {
#menu-background {
&:before {
transition: none;
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment