feat: sidebar enhancement (#10157)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run

* feat: sidebar enhancement (#9748)

* fix: sidebar enhancement

* fix: new source btn changes

* fix: shortcut order changes

* fix: changes in more section collapse behaviour

* fix: sidebar shortcut changes, consistency, cleanup in collapse mode

* fix: sidebar pin, tooltip and other changes

* feat: updated alignment issues

* fix: sidenav enhancement - fixes

* fix: code fix

* fix: sidenav enhancement

* feat: addressed comments and feedback

* feat: fix default shortcut empty issue

* feat: code clean and improvements

* feat: refactor and cleanup

* feat: refactor and addressed comment

* feat: removed isscrolled

* feat: corrected the ref intialization
This commit is contained in:
SagarRajput-7
2026-02-03 13:28:21 +05:30
committed by GitHub
parent 5b3f121431
commit c9cd974dca
11 changed files with 595 additions and 188 deletions

View File

@@ -48,7 +48,7 @@
} }
.app-content { .app-content {
width: calc(100% - 64px); // width of the sidebar width: calc(100% - 54px); // width of the sidebar
z-index: 0; z-index: 0;
margin: 0 auto; margin: 0 auto;

View File

@@ -168,7 +168,7 @@
.ant-pagination { .ant-pagination {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: calc(100% - 64px); width: calc(100% - 54px);
background: rgb(18, 19, 23); background: rgb(18, 19, 23);
padding: 16px; padding: 16px;
margin: 0; margin: 0;

View File

@@ -442,7 +442,7 @@
.ant-pagination { .ant-pagination {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: calc(100% - 64px); width: calc(100% - 54px);
background: var(--bg-ink-500); background: var(--bg-ink-500);
padding: 16px; padding: 16px;
margin: 0; margin: 0;

View File

@@ -58,7 +58,7 @@
overflow-y: auto; overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
height: calc(100% - 64px); height: calc(100% - 54px);
&::-webkit-scrollbar { &::-webkit-scrollbar {
height: 1rem; height: 1rem;

View File

@@ -194,7 +194,7 @@
.ant-pagination { .ant-pagination {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: calc(100% - 64px); width: calc(100% - 54px);
background: var(--bg-ink-500); background: var(--bg-ink-500);
padding: 16px; padding: 16px;
margin: 0; margin: 0;

View File

@@ -13,6 +13,12 @@
.nav-item-active-marker { .nav-item-active-marker {
background: #4e74f8; background: #4e74f8;
} }
.nav-item-data {
.nav-item-label {
color: var(--bg-vanilla-100, #fff);
}
}
} }
&.disabled { &.disabled {
@@ -27,14 +33,14 @@
.nav-item-data { .nav-item-data {
color: white; color: white;
background: var(--Slate-500, #161922); background: var(--bg-slate-500, #161922);
} }
} }
&.active { &.active {
.nav-item-data { .nav-item-data {
color: white; color: white;
background: var(--Slate-500, #161922); background: var(--bg-slate-500, #161922);
// color: #3f5ecc; // color: #3f5ecc;
} }
} }
@@ -50,9 +56,9 @@
.nav-item-data { .nav-item-data {
flex-grow: 1; flex-grow: 1;
max-width: calc(100% - 24px); max-width: calc(100% - 20px);
display: flex; display: flex;
margin: 0px 8px; margin: 0px 0px 0px 6px;
padding: 2px 8px; padding: 2px 8px;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
@@ -68,7 +74,7 @@
background: transparent; background: transparent;
transition: 0.2s all linear; transition: 0.08s all ease;
border-radius: 3px; border-radius: 3px;
@@ -100,7 +106,7 @@
&:hover { &:hover {
.nav-item-label { .nav-item-label {
color: var(--Vanilla-100, #fff); color: var(--bg-vanilla-100, #fff);
} }
.nav-item-pin-icon { .nav-item-pin-icon {
@@ -120,6 +126,12 @@
.nav-item-active-marker { .nav-item-active-marker {
background: #4e74f8; background: #4e74f8;
} }
.nav-item-data {
.nav-item-label {
color: var(--bg-slate-500);
}
}
} }
&:hover { &:hover {

View File

@@ -1,11 +1,12 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/click-events-have-key-events */
import { Tag } from 'antd'; import { Tag, Tooltip } from 'antd';
import cx from 'classnames'; import cx from 'classnames';
import { Pin, PinOff } from 'lucide-react'; import { Pin, PinOff } from 'lucide-react';
import { SidebarItem } from '../sideNav.types'; import { SidebarItem } from '../sideNav.types';
import './NavItem.styles.scss';
import './NavItem.styles.scss'; import './NavItem.styles.scss';
export default function NavItem({ export default function NavItem({
@@ -74,21 +75,25 @@ export default function NavItem({
)} )}
{onTogglePin && !isPinned && ( {onTogglePin && !isPinned && (
<Pin <Tooltip title="Add to shortcuts" placement="right">
size={12} <Pin
className="nav-item-pin-icon" size={12}
onClick={handleTogglePinClick} className="nav-item-pin-icon"
color="var(--Vanilla-400, #c0c1c3)" onClick={handleTogglePinClick}
/> color="var(--Vanilla-400, #c0c1c3)"
/>
</Tooltip>
)} )}
{onTogglePin && isPinned && ( {onTogglePin && isPinned && (
<PinOff <Tooltip title="Remove from shortcuts" placement="right">
size={12} <PinOff
className="nav-item-pin-icon" size={12}
onClick={handleTogglePinClick} className="nav-item-pin-icon"
color="var(--Vanilla-400, #c0c1c3)" onClick={handleTogglePinClick}
/> color="var(--Vanilla-400, #c0c1c3)"
/>
</Tooltip>
)} )}
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
.sidenav-container { .sidenav-container {
width: 64px; width: 54px;
height: 100%; height: 100%;
position: relative; position: relative;
z-index: 1; z-index: 1;
@@ -10,47 +10,60 @@
} }
.sideNav { .sideNav {
flex: 0 0 64px; flex: 0 0 54px;
height: 100%; height: 100%;
max-width: 64px; max-width: 54px;
min-width: 64px; min-width: 54px;
width: 64px; width: 54px;
border-right: 1px solid var(--Slate-500, #161922); border-right: 1px solid var(--bg-slate-500, #161922);
background: var(--Ink-500, #0b0c0e); background: var(--bg-ink-500, #0b0c0e);
padding-bottom: 48px; padding-bottom: 48px;
transition: all 0.2s, background 0s, border 0s; transition: all 0.08s ease, background 0s, border 0s;
.brand-container { .brand-container {
padding: 8px 18px; padding: 8px 15px;
max-width: 100%; max-width: 100%;
background: transparent;
} }
.brand-company-meta { .brand-company-meta {
display: flex; display: flex;
gap: 8px; align-items: center;
gap: 6px;
flex-shrink: 0;
width: 100%;
justify-content: center;
} }
.brand { .brand {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
max-width: 100%; max-width: 100%;
overflow: hidden; overflow: hidden;
gap: 32px; gap: 32px;
height: 32px; height: 32px;
width: 100%;
.brand-logo { .brand-logo {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
gap: 8px; gap: 8px;
flex-shrink: 0;
width: 20px;
height: 16px;
position: relative;
cursor: pointer; cursor: pointer;
img { img {
height: 16px; height: 16px;
width: auto;
display: block;
} }
.brand-logo-name { .brand-logo-name {
@@ -66,6 +79,10 @@
.brand-title-section { .brand-title-section {
display: none; display: none;
flex-shrink: 0;
align-items: center;
gap: 0;
position: relative;
.license-type { .license-type {
display: flex; display: flex;
@@ -76,7 +93,7 @@
color: var(--bg-vanilla-100); color: var(--bg-vanilla-100);
border-radius: 4px 0px 0px 4px; border-radius: 4px 0px 0px 4px;
background: var(--Slate-400, #1d212d); background: var(--bg-slate-400, #1d212d);
text-align: center; text-align: center;
font-family: Inter; font-family: Inter;
@@ -98,11 +115,11 @@
gap: 6px; gap: 6px;
border-radius: 0px 4px 4px 0px; border-radius: 0px 4px 4px 0px;
background: var(--Slate-300, #242834); background: var(--bg-slate-300, #242834);
} }
.version { .version {
color: var(--Vanilla-400, #c0c1c3); color: var(--bg-vanilla-400, #c0c1c3);
text-align: center; text-align: center;
font-variant-numeric: lining-nums tabular-nums slashed-zero; font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-feature-settings: 'salt' on; font-feature-settings: 'salt' on;
@@ -156,24 +173,48 @@
.get-started-nav-items { .get-started-nav-items {
display: flex; display: flex;
margin: 4px 13px 12px 10px; margin: 4px 10px 12px 8px;
.get-started-btn { .get-started-btn {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 8px; padding: 8px;
margin-left: 2px;
gap: 8px; gap: 8px;
width: 100%; width: 100%;
height: 32px; height: 32px;
border-radius: 3px; border-radius: 3px;
border: 1px solid var(--Slate-400, #1d212d); border: 1px solid var(--bg-slate-400, #1d212d);
background: var(--Slate-500, #161922); background: var(--bg-slate-500, #161922);
box-shadow: none !important; box-shadow: none !important;
color: var(--bg-vanilla-400, #c0c1c3);
svg {
color: var(--bg-vanilla-400, #c0c1c3);
}
.nav-item-label {
color: var(--bg-vanilla-400, #c0c1c3);
}
&:hover:not(:disabled) {
background: var(--bg-slate-400, #1d212d);
border-color: var(--bg-slate-400, #1d212d);
color: var(--bg-vanilla-100, #fff);
svg {
color: var(--bg-vanilla-100, #fff);
}
.nav-item-label {
color: var(--bg-vanilla-100, #fff);
}
}
} }
} }
@@ -192,6 +233,10 @@
width: 100%; width: 100%;
} }
.nav-item {
margin-bottom: 6px;
}
.nav-top-section { .nav-top-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -227,7 +272,7 @@
} }
.nav-section-title { .nav-section-title {
color: var(--Slate-50, #62687c); color: var(--bg-slate-50, #62687c);
font-family: Inter; font-family: Inter;
font-size: 11px; font-size: 11px;
font-style: normal; font-style: normal;
@@ -241,7 +286,7 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 0 20px; padding: 0 17px;
.nav-section-title-text { .nav-section-title-text {
display: none; display: none;
@@ -250,11 +295,17 @@
.nav-section-title-icon { .nav-section-title-icon {
display: flex; display: flex;
align-items: center; align-items: center;
transition: opacity 0.08s ease, transform 0.08s ease;
&.reorder { &.reorder {
display: none; display: none;
cursor: pointer; cursor: pointer;
margin-left: auto; margin-left: auto;
transition: color 0.2s;
&:hover {
color: var(--bg-vanilla-100, #fff);
}
} }
} }
@@ -268,7 +319,7 @@
} }
.nav-section-subtitle { .nav-section-subtitle {
color: var(--Vanilla-400, #c0c1c3); color: var(--bg-vanilla-400, #c0c1c3);
font-family: Inter; font-family: Inter;
font-size: 11px; font-size: 11px;
font-style: normal; font-style: normal;
@@ -276,20 +327,20 @@
line-height: 14px; /* 150% */ line-height: 14px; /* 150% */
letter-spacing: 0.4px; letter-spacing: 0.4px;
padding: 0 20px; padding: 6px 20px;
opacity: 0.6; opacity: 0.6;
display: none; display: none;
transition: all 0.3s, background 0s, border 0s; transition: all 0.08s ease, background 0s, border 0s;
transition-delay: 0.1s; transition-delay: 0.03s;
} }
.nav-items-section { .nav-items-section {
margin-top: 8px; margin-top: 8px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; transition: all 0.08s ease;
} }
} }
@@ -302,7 +353,7 @@
.nav-items-section { .nav-items-section {
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-10px);
transition: all 0.4s ease; transition: all 0.1s ease;
overflow: hidden; overflow: hidden;
height: 0; height: 0;
} }
@@ -312,11 +363,34 @@
.nav-items-section { .nav-items-section {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
transition: all 0.4s ease; transition: all 0.1s ease;
overflow: hidden; overflow: hidden;
height: auto; height: auto;
} }
} }
&.sidebar-collapsed {
.nav-title-section {
display: none;
}
.nav-items-section {
margin-top: 0;
opacity: 1;
transform: translateY(0);
transition: all 0.08s ease;
height: auto;
overflow: visible;
}
}
}
.shortcut-nav-items {
&.sidebar-collapsed {
.nav-items-section {
margin-top: 0;
}
}
} }
.scroll-for-more-container { .scroll-for-more-container {
@@ -326,7 +400,7 @@
width: 100%; width: 100%;
bottom: 12px; bottom: 12px;
bottom: 8px; bottom: 8px;
margin-left: 50px; margin-left: 43px;
.scroll-for-more { .scroll-for-more {
display: flex; display: flex;
@@ -370,8 +444,6 @@
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
padding-top: 12px;
.secondary-nav-items { .secondary-nav-items {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -381,10 +453,10 @@
overflow-x: hidden; overflow-x: hidden;
padding: 8px 0; padding: 8px 0;
max-width: 100%; max-width: 100%;
width: 64px; width: 54px;
// width: 100%; // temp // width: 100%; // temp
transition: all 0.2s, background 0s, border 0s; transition: all 0.08s ease, background 0s, border 0s;
background: linear-gradient(180deg, rgba(11, 12, 14, 0) 0%, #0b0c0e 27.11%); background: linear-gradient(180deg, rgba(11, 12, 14, 0) 0%, #0b0c0e 27.11%);
@@ -413,7 +485,7 @@
&.scroll-available { &.scroll-available {
.nav-bottom-section { .nav-bottom-section {
border-top: 1px solid var(--Slate-500, #161922); border-top: 1px solid var(--bg-slate-500, #161922);
} }
} }
} }
@@ -424,24 +496,53 @@
} }
&.collapsed { &.collapsed {
flex: 0 0 64px; flex: 0 0 54px;
max-width: 64px; max-width: 54px;
min-width: 64px; min-width: 54px;
width: 64px; width: 54px;
.nav-wrapper { .nav-wrapper {
.nav-top-section { .nav-top-section {
.shortcut-nav-items { .shortcut-nav-items {
.nav-section-title, .nav-section-title {
display: none;
}
.nav-section-subtitle { .nav-section-subtitle {
display: none; display: none;
} }
.nav-items-section {
display: flex;
margin-top: 0;
}
.nav-title-section {
margin-top: 0;
margin-bottom: 0;
gap: 0;
}
}
.more-nav-items {
.nav-section-title {
display: none;
}
.nav-items-section {
display: flex;
margin-top: 0;
}
.nav-title-section {
display: none;
}
} }
} }
.nav-bottom-section { .nav-bottom-section {
.secondary-nav-items { .secondary-nav-items {
width: 64px; width: 54px;
} }
} }
} }
@@ -466,7 +567,7 @@
border-radius: 12px; border-radius: 12px;
background: var(--Robin-500, #4e74f8); background: var(--Robin-500, #4e74f8);
color: var(--Vanilla-100, #fff); color: var(--bg-vanilla-100, #fff);
font-variant-numeric: lining-nums tabular-nums slashed-zero; font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-feature-settings: 'case' on, 'cpsp' on, 'dlig' on, 'salt' on; font-feature-settings: 'case' on, 'cpsp' on, 'dlig' on, 'salt' on;
font-family: Inter; font-family: Inter;
@@ -479,7 +580,7 @@
} }
.sidenav-beta-tag { .sidenav-beta-tag {
color: var(--Vanilla-100, #fff); color: var(--bg-vanilla-100, #fff);
font-variant-numeric: lining-nums tabular-nums slashed-zero; font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-feature-settings: 'case' on, 'cpsp' on, 'dlig' on, 'salt' on; font-feature-settings: 'case' on, 'cpsp' on, 'dlig' on, 'salt' on;
font-family: Inter; font-family: Inter;
@@ -494,7 +595,47 @@
background: var(--bg-slate-300); background: var(--bg-slate-300);
} }
&:hover { &:not(.pinned) {
.nav-item {
.nav-item-data {
justify-content: center;
}
}
.shortcut-nav-items,
.more-nav-items {
.nav-section-title {
padding: 0 17px;
.nav-section-title-icon {
display: none;
}
}
}
&.dropdown-open {
.nav-item {
.nav-item-data {
flex-grow: 1;
justify-content: flex-start;
}
}
.shortcut-nav-items,
.more-nav-items {
.nav-section-title {
padding: 0 17px;
.nav-section-title-icon {
display: flex;
}
}
}
}
}
&:not(.pinned):hover,
&.dropdown-open {
flex: 0 0 240px; flex: 0 0 240px;
max-width: 240px; max-width: 240px;
min-width: 240px; min-width: 240px;
@@ -505,8 +646,17 @@
z-index: 10; z-index: 10;
background: #0b0c0e; background: #0b0c0e;
.brand-container {
padding: 8px 15px;
}
.brand { .brand {
justify-content: space-between; justify-content: flex-start;
.brand-company-meta {
justify-content: flex-start;
width: 100%;
}
.brand-title-section { .brand-title-section {
display: flex; display: flex;
@@ -533,6 +683,11 @@
.nav-section-title-icon { .nav-section-title-icon {
&.reorder { &.reorder {
display: flex; display: flex;
transition: color 0.2s;
&:hover {
color: var(--bg-vanilla-100, #fff);
}
} }
} }
} }
@@ -574,7 +729,7 @@
flex-direction: row; flex-direction: row;
gap: 3px; gap: 3px;
border-radius: 20px; border-radius: 20px;
background: var(--Slate-400, #1d212d); background: var(--bg-slate-400, #1d212d);
/* Drop Shadow */ /* Drop Shadow */
box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01), box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01),
@@ -590,7 +745,7 @@
width: 140px; width: 140px;
.scroll-for-more-label { .scroll-for-more-label {
color: var(--Vanilla-400, #c0c1c3); color: var(--bg-vanilla-400, #c0c1c3);
font-family: Inter; font-family: Inter;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
@@ -631,6 +786,13 @@
align-items: flex-start; align-items: flex-start;
} }
} }
.nav-item {
.nav-item-data {
flex-grow: 1;
justify-content: flex-start;
}
}
} }
.get-started-nav-items { .get-started-nav-items {
@@ -664,8 +826,17 @@
z-index: 10; z-index: 10;
background: #0b0c0e; background: #0b0c0e;
.brand-container {
padding: 8px 15px;
}
.brand { .brand {
justify-content: space-between; justify-content: flex-start;
.brand-company-meta {
justify-content: flex-start;
width: 100%;
}
.brand-title-section { .brand-title-section {
display: flex; display: flex;
@@ -692,6 +863,11 @@
.nav-section-title-icon { .nav-section-title-icon {
&.reorder { &.reorder {
display: flex; display: flex;
transition: color 0.2s;
&:hover {
color: var(--bg-vanilla-100, #fff);
}
} }
} }
} }
@@ -733,7 +909,7 @@
flex-direction: row; flex-direction: row;
gap: 3px; gap: 3px;
border-radius: 20px; border-radius: 20px;
background: var(--Slate-400, #1d212d); background: var(--bg-slate-400, #1d212d);
/* Drop Shadow */ /* Drop Shadow */
box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01), box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01),
@@ -751,7 +927,7 @@
.scroll-for-more-label { .scroll-for-more-label {
display: block; display: block;
color: var(--Vanilla-400, #c0c1c3); color: var(--bg-vanilla-400, #c0c1c3);
font-family: Inter; font-family: Inter;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
@@ -856,7 +1032,7 @@
.ant-dropdown-menu-item { .ant-dropdown-menu-item {
.ant-dropdown-menu-title-content { .ant-dropdown-menu-title-content {
color: var(--Vanilla-400, #c0c1c3); color: var(--bg-vanilla-400, #c0c1c3);
font-family: Inter; font-family: Inter;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
@@ -864,6 +1040,12 @@
line-height: normal; line-height: normal;
letter-spacing: 0.14px; letter-spacing: 0.14px;
} }
&:hover:not(.ant-dropdown-menu-item-disabled) {
.ant-dropdown-menu-title-content {
color: var(--bg-vanilla-100, #fff);
}
}
} }
} }
} }
@@ -875,7 +1057,7 @@
gap: 8px; gap: 8px;
.user-settings-dropdown-label-text { .user-settings-dropdown-label-text {
color: var(--Slate-50, #62687c); color: var(--bg-slate-50, #62687c);
font-family: Inter; font-family: Inter;
font-size: 10px; font-size: 10px;
font-family: Inter; font-family: Inter;
@@ -887,7 +1069,7 @@
} }
.user-settings-dropdown-label-email { .user-settings-dropdown-label-email {
color: var(--Vanilla-400, #c0c1c3); color: var(--bg-vanilla-400, #c0c1c3);
font-family: Inter; font-family: Inter;
font-size: 12px; font-size: 12px;
font-style: normal; font-style: normal;
@@ -897,12 +1079,16 @@
} }
.ant-dropdown-menu-item-divider { .ant-dropdown-menu-item-divider {
background-color: var(--Slate-500, #161922) !important; background-color: var(--bg-slate-500, #161922) !important;
} }
.ant-dropdown-menu-item-disabled { .ant-dropdown-menu-item-disabled {
opacity: 0.7; opacity: 0.7;
} }
.ant-dropdown-menu {
width: 100% !important;
}
} }
.settings-dropdown, .settings-dropdown,
@@ -912,6 +1098,27 @@
} }
} }
.secondary-nav-items {
.nav-item {
position: relative;
.nav-item-active-marker {
position: absolute;
left: -5px;
top: 50%;
transform: translateY(-50%);
margin: 0;
width: 8px;
height: 24px;
z-index: 1;
}
}
.nav-item-data {
margin-left: 8px !important;
}
}
.reorder-shortcut-nav-items-modal { .reorder-shortcut-nav-items-modal {
width: 384px !important; width: 384px !important;
@@ -1028,7 +1235,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
border-radius: 2px; border-radius: 2px;
border-radius: 2px;
background: var(--Robin-500, #4e74f8) !important; background: var(--Robin-500, #4e74f8) !important;
color: var(--bg-vanilla-100) !important; color: var(--bg-vanilla-100) !important;
font-family: Inter; font-family: Inter;
@@ -1038,10 +1244,10 @@
line-height: 24px; line-height: 24px;
&.secondary-btn { &.secondary-btn {
background-color: var(--Slate-500, #161922) !important; background-color: var(--bg-slate-500, #161922) !important;
border: 1px solid var(--bg-slate-500) !important; border: 1px solid var(--bg-slate-500) !important;
color: var(--Vanilla-400, #c0c1c3) !important; color: var(--bg-vanilla-400, #c0c1c3) !important;
/* button/ small */ /* button/ small */
font-family: Inter; font-family: Inter;
@@ -1064,6 +1270,10 @@
} }
} }
.help-support-dropdown li.ant-dropdown-menu-item-divider {
background-color: var(--bg-slate-500, #161922) !important;
}
.lightMode { .lightMode {
.sideNav { .sideNav {
background: var(--bg-vanilla-100); background: var(--bg-vanilla-100);
@@ -1095,8 +1305,32 @@
.get-started-nav-items { .get-started-nav-items {
.get-started-btn { .get-started-btn {
border: 1px solid var(--bg-vanilla-300); border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100); background: var(--bg-vanilla-200);
color: var(--bg-ink-400); color: var(--bg-slate-50, #62687c);
svg {
color: var(--bg-slate-50, #62687c);
}
.nav-item-label {
color: var(--bg-ink-400, #62687c);
}
// Hover state (light mode)
&:hover:not(:disabled) {
background: var(--bg-vanilla-300);
border-color: var(--bg-vanilla-300);
color: var(--bg-slate-500, #161922);
svg {
color: var(--bg-slate-500, #161922);
}
.nav-item-label {
color: var(--bg-slate-500, #161922);
}
}
} }
} }
@@ -1108,7 +1342,25 @@
} }
} }
.brand-container {
background: transparent;
}
.nav-wrapper { .nav-wrapper {
.nav-top-section {
.shortcut-nav-items {
.nav-section-title {
.nav-section-title-icon {
&.reorder {
&:hover {
color: var(--bg-slate-400, #1d212d);
}
}
}
}
}
}
.secondary-nav-items { .secondary-nav-items {
border-top: 1px solid var(--bg-vanilla-300); border-top: 1px solid var(--bg-vanilla-300);
@@ -1123,8 +1375,43 @@
} }
} }
&:hover { &.pinned {
.nav-wrapper {
.nav-top-section {
.shortcut-nav-items {
.nav-section-title {
.nav-section-title-icon {
&.reorder {
&:hover {
color: var(--bg-slate-400, #1d212d);
}
}
}
}
}
}
}
}
&:not(.pinned):hover,
&.dropdown-open {
background: var(--bg-vanilla-100); background: var(--bg-vanilla-100);
.nav-wrapper {
.nav-top-section {
.shortcut-nav-items {
.nav-section-title {
.nav-section-title-icon {
&.reorder {
&:hover {
color: var(--bg-slate-400, #1d212d);
}
}
}
}
}
}
}
} }
} }
@@ -1134,6 +1421,12 @@
.ant-dropdown-menu-title-content { .ant-dropdown-menu-title-content {
color: var(--bg-ink-400); color: var(--bg-ink-400);
} }
&:hover:not(.ant-dropdown-menu-item-disabled) {
.ant-dropdown-menu-title-content {
color: var(--bg-ink-500);
}
}
} }
} }
} }
@@ -1210,6 +1503,10 @@
color: var(--bg-ink-400); color: var(--bg-ink-400);
} }
} }
.help-support-dropdown li.ant-dropdown-menu-item-divider {
background-color: var(--bg-vanilla-300) !important;
}
} }
.version-tooltip-overlay { .version-tooltip-overlay {
@@ -1222,7 +1519,7 @@
border-radius: 2px; border-radius: 2px;
border: 1px solid var(--bg-slate-500); border: 1px solid var(--bg-slate-500);
color: var(--Vanilla-100, #fff); color: var(--bg-vanilla-100, #fff);
font-family: Inter; font-family: Inter;
font-size: 11px; font-size: 11px;
font-style: normal; font-style: normal;
@@ -1237,7 +1534,7 @@
gap: 4px; gap: 4px;
.version-update-notification-tooltip-title { .version-update-notification-tooltip-title {
color: var(--Vanilla-100, #fff); color: var(--bg-vanilla-100, #fff);
font-family: Inter; font-family: Inter;
font-size: 11px; font-size: 11px;
font-style: normal; font-style: normal;
@@ -1247,7 +1544,7 @@
} }
.version-update-notification-tooltip-content { .version-update-notification-tooltip-content {
color: var(--Vanilla-100, #fff); color: var(--bg-vanilla-100, #fff);
font-family: Inter; font-family: Inter;
font-size: 10px; font-size: 10px;
font-style: normal; font-style: normal;

View File

@@ -157,18 +157,27 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
DefaultHelpSupportDropdownMenuItems, DefaultHelpSupportDropdownMenuItems,
); );
const [pinnedMenuItems, setPinnedMenuItems] = useState<SidebarItem[]>([]);
const [tempPinnedMenuItems, setTempPinnedMenuItems] = useState<SidebarItem[]>( const [tempPinnedMenuItems, setTempPinnedMenuItems] = useState<SidebarItem[]>(
[], [],
); );
const [secondaryMenuItems, setSecondaryMenuItems] = useState<SidebarItem[]>(
[],
);
const [hasScroll, setHasScroll] = useState(false); const [hasScroll, setHasScroll] = useState(false);
const navTopSectionRef = useRef<HTMLDivElement>(null); const navTopSectionRef = useRef<HTMLDivElement>(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const [pinnedMenuItems, setPinnedMenuItems] = useState<SidebarItem[]>([]);
const [secondaryMenuItems, setSecondaryMenuItems] = useState<SidebarItem[]>(
[],
);
const handleMouseEnter = useCallback(() => {
setIsHovered(true);
}, []);
const handleMouseLeave = useCallback(() => {
setIsHovered(false);
}, []);
const checkScroll = useCallback((): void => { const checkScroll = useCallback((): void => {
if (navTopSectionRef.current) { if (navTopSectionRef.current) {
@@ -217,63 +226,68 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const isAdmin = user.role === USER_ROLES.ADMIN; const isAdmin = user.role === USER_ROLES.ADMIN;
const isEditor = user.role === USER_ROLES.EDITOR; const isEditor = user.role === USER_ROLES.EDITOR;
useEffect(() => { // Compute initial pinned items and secondary menu items synchronously to avoid flash
const navShortcuts = (userPreferences?.find( const computedPinnedMenuItems = useMemo(() => {
const navShortcutsPreference = userPreferences?.find(
(preference) => preference.name === USER_PREFERENCES.NAV_SHORTCUTS, (preference) => preference.name === USER_PREFERENCES.NAV_SHORTCUTS,
)?.value as unknown) as string[]; );
const navShortcuts = (navShortcutsPreference?.value as unknown) as
| string[]
| undefined;
const shouldShowIntegrations = // If userPreferences not loaded yet, return empty to avoid showing defaults before preferences load
(isCloudUser || isEnterpriseSelfHostedUser) && (isAdmin || isEditor); if (userPreferences === null) {
return [];
}
if (navShortcuts && isArray(navShortcuts) && navShortcuts.length > 0) { // If preference exists with non-empty array, use stored shortcuts
// nav shortcuts is array of strings if (isArray(navShortcuts) && navShortcuts.length > 0) {
const pinnedItems = navShortcuts return navShortcuts
.map((shortcut) => .map((shortcut) =>
defaultMoreMenuItems.find((item) => item.itemKey === shortcut), defaultMoreMenuItems.find((item) => item.itemKey === shortcut),
) )
.filter((item): item is SidebarItem => item !== undefined); .filter((item): item is SidebarItem => item !== undefined);
// Set pinned items in the order they were stored
setPinnedMenuItems(pinnedItems);
setSecondaryMenuItems(
defaultMoreMenuItems.map((item) => ({
...item,
isPinned: pinnedItems.some((pinned) => pinned.itemKey === item.itemKey),
isEnabled:
item.key === ROUTES.INTEGRATIONS
? shouldShowIntegrations
: item.isEnabled,
})),
);
} else {
// Set default pinned items
const defaultPinnedItems = defaultMoreMenuItems.filter(
(item) => item.isPinned,
);
setPinnedMenuItems(defaultPinnedItems);
setSecondaryMenuItems(
defaultMoreMenuItems.map((item) => ({
...item,
isPinned: defaultPinnedItems.some(
(pinned) => pinned.itemKey === item.itemKey,
),
isEnabled:
item.key === ROUTES.INTEGRATIONS
? shouldShowIntegrations
: item.isEnabled,
})),
);
} }
// No preference, or empty array → use defaults
return defaultMoreMenuItems.filter((item) => item.isPinned);
}, [userPreferences]);
const computedSecondaryMenuItems = useMemo(() => {
const shouldShowIntegrationsValue =
(isCloudUser || isEnterpriseSelfHostedUser) && (isAdmin || isEditor);
return defaultMoreMenuItems.map((item) => ({
...item,
isPinned: computedPinnedMenuItems.some(
(pinned) => pinned.itemKey === item.itemKey,
),
isEnabled:
item.key === ROUTES.INTEGRATIONS
? shouldShowIntegrationsValue
: item.isEnabled,
}));
}, [ }, [
userPreferences, computedPinnedMenuItems,
isCloudUser, isCloudUser,
isEnterpriseSelfHostedUser, isEnterpriseSelfHostedUser,
isAdmin, isAdmin,
isEditor, isEditor,
]); ]);
// Track if we've done the initial sync (to avoid overwriting user actions during session)
const hasInitializedRef = useRef(false);
// Sync state only on initial load when userPreferences first becomes available
useEffect(() => {
// Only sync once: when userPreferences loads for the first time
if (!hasInitializedRef.current && userPreferences !== null) {
setPinnedMenuItems(computedPinnedMenuItems);
setSecondaryMenuItems(computedSecondaryMenuItems);
hasInitializedRef.current = true;
}
}, [computedPinnedMenuItems, computedSecondaryMenuItems, userPreferences]);
const isOnboardingV3Enabled = featureFlags?.find( const isOnboardingV3Enabled = featureFlags?.find(
(flag) => flag.name === FeatureKeys.ONBOARDING_V3, (flag) => flag.name === FeatureKeys.ONBOARDING_V3,
)?.active; )?.active;
@@ -327,6 +341,17 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
.map((item) => item.itemKey) .map((item) => item.itemKey)
.filter(Boolean) as string[]; .filter(Boolean) as string[];
// Update context immediately (optimistically) so computed values reflect the change
updateUserPreferenceInContext({
name: USER_PREFERENCES.NAV_SHORTCUTS,
description: USER_PREFERENCES.NAV_SHORTCUTS,
valueType: 'array',
defaultValue: false,
allowedValues: [],
allowedScopes: ['user'],
value: navShortcuts,
});
updateUserPreferenceMutation( updateUserPreferenceMutation(
{ {
name: USER_PREFERENCES.NAV_SHORTCUTS, name: USER_PREFERENCES.NAV_SHORTCUTS,
@@ -335,6 +360,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
{ {
onSuccess: (response) => { onSuccess: (response) => {
if (response.data) { if (response.data) {
// Update context again on success to ensure consistency
updateUserPreferenceInContext({ updateUserPreferenceInContext({
name: USER_PREFERENCES.NAV_SHORTCUTS, name: USER_PREFERENCES.NAV_SHORTCUTS,
description: USER_PREFERENCES.NAV_SHORTCUTS, description: USER_PREFERENCES.NAV_SHORTCUTS,
@@ -368,13 +394,13 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
if (isCurrentlyPinned) { if (isCurrentlyPinned) {
return prevItems.filter((i) => i.key !== item.key); return prevItems.filter((i) => i.key !== item.key);
} }
return [item, ...prevItems]; return [...prevItems, item];
}); });
// Get the updated pinned menu items for preference update // Get the updated pinned menu items for preference update
const updatedPinnedItems = pinnedMenuItems.some((i) => i.key === item.key) const updatedPinnedItems = pinnedMenuItems.some((i) => i.key === item.key)
? pinnedMenuItems.filter((i) => i.key !== item.key) ? pinnedMenuItems.filter((i) => i.key !== item.key)
: [item, ...pinnedMenuItems]; : [...pinnedMenuItems, item];
// Update user preference with the ordered list of item keys // Update user preference with the ordered list of item keys
updateNavShortcutsPreference(updatedPinnedItems); updateNavShortcutsPreference(updatedPinnedItems);
@@ -455,6 +481,10 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
pathname, pathname,
]); ]);
const isSettingsPage = useMemo(() => pathname.startsWith(ROUTES.SETTINGS), [
pathname,
]);
const userSettingsDropdownMenuItems: MenuProps['items'] = useMemo( const userSettingsDropdownMenuItems: MenuProps['items'] = useMemo(
() => () =>
[ [
@@ -594,7 +624,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
}, },
{ {
type: 'group', type: 'group',
label: "WHAT's NEW", label: "WHAT'S NEW",
}, },
...dropdownItems, ...dropdownItems,
{ {
@@ -750,6 +780,15 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
[secondaryMenuItems], [secondaryMenuItems],
); );
// Get active "More" items that should be visible in collapsed state
const activeMoreMenuItems = useMemo(
() => moreMenuItems.filter((item) => activeMenuKey === item.key),
[moreMenuItems, activeMenuKey],
);
// Check if sidebar is collapsed (not pinned, not hovered, and no dropdown open)
const isCollapsed = !isPinned && !isHovered && !isDropdownOpen;
const renderNavItems = ( const renderNavItems = (
items: SidebarItem[], items: SidebarItem[],
allowPin?: boolean, allowPin?: boolean,
@@ -901,7 +940,15 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
return ( return (
<div className={cx('sidenav-container', isPinned && 'pinned')}> <div className={cx('sidenav-container', isPinned && 'pinned')}>
<div className={cx('sideNav', isPinned && 'pinned')}> <div
className={cx(
'sideNav',
isPinned && 'pinned',
isDropdownOpen && 'dropdown-open',
)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className="brand-container"> <div className="brand-container">
<div className="brand"> <div className="brand">
<div className="brand-company-meta"> <div className="brand-company-meta">
@@ -999,35 +1046,43 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
{renderNavItems(primaryMenuItems)} {renderNavItems(primaryMenuItems)}
</div> </div>
<div className="shortcut-nav-items"> {(pinnedMenuItems.length > 0 || !isCollapsed) && (
<div className="nav-title-section"> <div
<div className="nav-section-title"> className={cx('shortcut-nav-items', isCollapsed && 'sidebar-collapsed')}
<div className="nav-section-title-icon"> >
<MousePointerClick size={16} /> {!isCollapsed && (
</div> <div className="nav-title-section">
<div className="nav-section-title">
<div className="nav-section-title-icon">
<MousePointerClick size={16} />
</div>
<div className="nav-section-title-text">SHORTCUTS</div> <div className="nav-section-title-text">SHORTCUTS</div>
{pinnedMenuItems.length > 1 && ( {pinnedMenuItems.length > 1 && (
<div <Tooltip title="Manage shortcuts" placement="right">
className="nav-section-title-icon reorder" <div
onClick={(): void => { className="nav-section-title-icon reorder"
logEvent('Sidebar V2: Manage shortcuts clicked', {}); onClick={(): void => {
setIsReorderShortcutNavItemsModalOpen(true); logEvent('Sidebar V2: Manage shortcuts clicked', {});
}} setIsReorderShortcutNavItemsModalOpen(true);
> }}
<Logs size={16} /> >
<Logs size={16} />
</div>
</Tooltip>
)}
</div> </div>
)}
</div>
{pinnedMenuItems.length === 0 && ( {pinnedMenuItems.length === 0 && (
<div className="nav-section-subtitle"> <div className="nav-section-subtitle">
You have not added any shortcuts yet. You have not added any shortcuts yet.
</div>
)}
</div> </div>
)} )}
{pinnedMenuItems.length > 0 && ( {(pinnedMenuItems.length > 0 || isCollapsed) && (
<div className="nav-items-section"> <div className="nav-items-section">
{renderNavItems( {renderNavItems(
pinnedMenuItems.filter((item) => item.isEnabled), pinnedMenuItems.filter((item) => item.isEnabled),
@@ -1036,46 +1091,60 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
</div> </div>
)} )}
</div> </div>
</div> )}
{moreMenuItems.length > 0 && ( {moreMenuItems.length > 0 && (
<div <div
className={cx( className={cx(
'more-nav-items', 'more-nav-items',
isMoreMenuCollapsed ? 'collapsed' : 'expanded', isMoreMenuCollapsed ? 'collapsed' : 'expanded',
isCollapsed && 'sidebar-collapsed',
)} )}
> >
<div className="nav-title-section"> {!isCollapsed && (
<div <div className="nav-title-section">
className="nav-section-title" <div
onClick={(): void => { className="nav-section-title"
logEvent('Sidebar V2: More menu clicked', { onClick={(): void => {
action: isMoreMenuCollapsed ? 'expand' : 'collapse', // Only allow toggling when sidebar is open (pinned, hovered, or dropdown open)
}); if (isCollapsed) {
setIsMoreMenuCollapsed(!isMoreMenuCollapsed); return;
}} }
> const newCollapsedState = !isMoreMenuCollapsed;
<div className="nav-section-title-icon"> logEvent('Sidebar V2: More menu clicked', {
<Ellipsis size={16} /> action: isMoreMenuCollapsed ? 'expand' : 'collapse',
</div> });
setIsMoreMenuCollapsed(newCollapsedState);
}}
>
<div className="nav-section-title-icon">
<Ellipsis size={16} />
</div>
<div className="nav-section-title-text">MORE</div> <div className="nav-section-title-text">MORE</div>
<div className="collapse-expand-section-icon"> <div className="collapse-expand-section-icon">
{isMoreMenuCollapsed ? ( {isMoreMenuCollapsed ? (
<ChevronDown size={16} /> <ChevronDown size={16} />
) : ( ) : (
<ChevronUp size={16} /> <ChevronUp size={16} />
)} )}
</div>
</div> </div>
</div> </div>
</div> )}
<div className="nav-items-section"> <div className="nav-items-section">
{renderNavItems( {/* Show all items when expanded, only active items when collapsed */}
moreMenuItems.filter((item) => item.isEnabled), {isCollapsed
true, ? renderNavItems(
)} activeMoreMenuItems.filter((item) => item.isEnabled),
true,
)
: renderNavItems(
moreMenuItems.filter((item) => item.isEnabled),
true,
)}
</div> </div>
</div> </div>
)} )}
@@ -1102,6 +1171,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
placement="topLeft" placement="topLeft"
overlayClassName="nav-dropdown-overlay help-support-dropdown" overlayClassName="nav-dropdown-overlay help-support-dropdown"
trigger={['click']} trigger={['click']}
onOpenChange={(open): void => setIsDropdownOpen(open)}
> >
<div className="nav-item"> <div className="nav-item">
<div className="nav-item-data" data-testid="help-support-nav-item"> <div className="nav-item-data" data-testid="help-support-nav-item">
@@ -1122,8 +1192,10 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
placement="topLeft" placement="topLeft"
overlayClassName="nav-dropdown-overlay settings-dropdown" overlayClassName="nav-dropdown-overlay settings-dropdown"
trigger={['click']} trigger={['click']}
onOpenChange={(open): void => setIsDropdownOpen(open)}
> >
<div className="nav-item"> <div className={cx('nav-item', isSettingsPage && 'active')}>
<div className="nav-item-active-marker" />
<div className="nav-item-data" data-testid="settings-nav-item"> <div className="nav-item-data" data-testid="settings-nav-item">
<div className="nav-item-icon">{userSettingsMenuItem.icon}</div> <div className="nav-item-icon">{userSettingsMenuItem.icon}</div>

View File

@@ -33,6 +33,19 @@
height: calc(100vh - 48px); height: calc(100vh - 48px);
border-right: 1px solid var(--Slate-500, #161922); border-right: 1px solid var(--Slate-500, #161922);
background: var(--Ink-500, #0b0c0e); background: var(--Ink-500, #0b0c0e);
margin-top: 4px;
.nav-item {
.nav-item-data {
margin: 0px 8px 0px 4px;
}
&.active {
.nav-item-data .nav-item-label {
color: var(--bg-vanilla-100, #fff);
}
}
}
} }
.settings-page-content { .settings-page-content {
@@ -81,6 +94,14 @@
.settings-page-sidenav { .settings-page-sidenav {
border-right: 1px solid var(--bg-vanilla-300); border-right: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100); background: var(--bg-vanilla-100);
.nav-item {
&.active {
.nav-item-data .nav-item-label {
color: var(--bg-ink-500);
}
}
}
} }
.settings-page-content { .settings-page-content {

View File

@@ -13,7 +13,7 @@ import { SidebarItem } from 'container/SideNav/sideNav.types';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense'; import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history'; import history from 'lib/history';
import { Wrench } from 'lucide-react'; import { Cog } from 'lucide-react';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import { USER_ROLES } from 'types/roles'; import { USER_ROLES } from 'types/roles';
@@ -236,7 +236,7 @@ function SettingsPage(): JSX.Element {
className="settings-page-header-title" className="settings-page-header-title"
data-testid="settings-page-title" data-testid="settings-page-title"
> >
<Wrench size={16} /> <Cog size={16} />
Settings Settings
</div> </div>
</header> </header>