Compare commits

...

10 Commits

Author SHA1 Message Date
SagarRajput-7
0a3fd7259e Merge branch 'main' into SIG-8604 2025-12-22 06:28:20 +07:00
SagarRajput-7
a53f4e584a feat: updated alignment issues 2025-12-06 05:07:34 +05:30
SagarRajput-7
98fa1a42d0 Merge branch 'main' into SIG-8604 2025-12-04 10:21:27 +05:30
SagarRajput-7
c67d022175 fix: sidebar pin, tooltip and other changes 2025-12-03 10:11:53 +05:30
SagarRajput-7
a8f7d43b9b fix: sidebar shortcut changes, consistency, cleanup in collapse mode 2025-12-03 09:44:49 +05:30
SagarRajput-7
2953a18a84 fix: changes in more section collapse behaviour 2025-12-02 23:15:09 +05:30
SagarRajput-7
77404d17fa fix: shortcut order changes 2025-12-02 14:46:16 +05:30
SagarRajput-7
359a018b96 fix: new source btn changes 2025-12-02 14:32:25 +05:30
SagarRajput-7
8bef7d9be4 Merge branch 'main' into SIG-8604 2025-12-02 12:24:11 +05:30
SagarRajput-7
824da0af0c fix: sidebar enhancement 2025-11-12 18:35:55 +05:30
6 changed files with 408 additions and 109 deletions

View File

@@ -13,6 +13,12 @@
.nav-item-active-marker {
background: #4e74f8;
}
.nav-item-data {
.nav-item-label {
color: var(--Vanilla-100, #fff);
}
}
}
&.disabled {
@@ -120,6 +126,12 @@
.nav-item-active-marker {
background: #4e74f8;
}
.nav-item-data {
.nav-item-label {
color: var(--bg-slate-500);
}
}
}
&:hover {

View File

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

View File

@@ -24,6 +24,7 @@
.brand-container {
padding: 8px 18px;
max-width: 100%;
background: linear-gradient(0deg, rgba(11, 12, 14, 0) 0%, #0b0c0e 27%);
}
.brand-company-meta {
@@ -34,6 +35,7 @@
.brand {
display: flex;
align-items: center;
justify-content: center;
max-width: 100%;
overflow: hidden;
@@ -163,7 +165,6 @@
align-items: center;
justify-content: center;
padding: 8px;
margin-left: 2px;
gap: 8px;
width: 100%;
@@ -174,6 +175,31 @@
background: var(--Slate-500, #161922);
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);
}
}
}
}
@@ -241,7 +267,7 @@
align-items: center;
gap: 8px;
padding: 0 20px;
padding: 0 18px;
.nav-section-title-text {
display: none;
@@ -255,6 +281,11 @@
display: none;
cursor: pointer;
margin-left: auto;
transition: color 0.2s;
&:hover {
color: var(--bg-vanilla-100, #fff);
}
}
}
@@ -276,7 +307,7 @@
line-height: 14px; /* 150% */
letter-spacing: 0.4px;
padding: 0 20px;
padding: 6px 20px;
opacity: 0.6;
display: none;
@@ -432,10 +463,33 @@
.nav-wrapper {
.nav-top-section {
.shortcut-nav-items {
.nav-section-title,
.nav-section-title {
display: flex;
justify-content: center;
padding: 0;
.nav-section-title-text {
display: none;
}
.nav-section-title-icon.reorder {
display: none;
}
}
.nav-section-subtitle {
display: none;
}
.nav-items-section {
display: none;
}
.nav-title-section {
margin-top: 0;
margin-bottom: 0;
gap: 0;
}
}
}
@@ -494,7 +548,40 @@
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 22px !important;
}
}
:hover,
&.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 18px !important;
}
}
}
}
&:not(.pinned):hover,
&.dropdown-open {
flex: 0 0 240px;
max-width: 240px;
min-width: 240px;
@@ -533,6 +620,11 @@
.nav-section-title-icon {
&.reorder {
display: flex;
transition: color 0.2s;
&:hover {
color: var(--bg-vanilla-100, #fff);
}
}
}
}
@@ -692,6 +784,11 @@
.nav-section-title-icon {
&.reorder {
display: flex;
transition: color 0.2s;
&:hover {
color: var(--bg-vanilla-100, #fff);
}
}
}
}
@@ -864,6 +961,12 @@
line-height: normal;
letter-spacing: 0.14px;
}
&:hover:not(.ant-dropdown-menu-item-disabled) {
.ant-dropdown-menu-title-content {
color: var(--bg-vanilla-100, #fff);
}
}
}
}
}
@@ -903,6 +1006,10 @@
.ant-dropdown-menu-item-disabled {
opacity: 0.7;
}
.ant-dropdown-menu {
width: 100% !important; // todo:sagar check with shuvam once
}
}
.settings-dropdown,
@@ -912,6 +1019,12 @@
}
}
.secondary-nav-items {
.nav-item-data {
margin-left: 10px !important;
}
}
.reorder-shortcut-nav-items-modal {
width: 384px !important;
@@ -1028,7 +1141,6 @@
display: flex;
align-items: center;
border-radius: 2px;
border-radius: 2px;
background: var(--Robin-500, #4e74f8) !important;
color: var(--bg-vanilla-100) !important;
font-family: Inter;
@@ -1064,6 +1176,10 @@
}
}
.help-support-dropdown li.ant-dropdown-menu-item-divider {
background-color: var(--Slate-500, #161922) !important;
}
.lightMode {
.sideNav {
background: var(--bg-vanilla-100);
@@ -1095,8 +1211,32 @@
.get-started-nav-items {
.get-started-btn {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
color: var(--bg-ink-400);
background: var(--bg-vanilla-200);
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 +1248,25 @@
}
}
.brand-container {
background: linear-gradient(0deg, rgba(255, 255, 255, 0) 0%, #fff 27%);
}
.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 {
border-top: 1px solid var(--bg-vanilla-300);
@@ -1123,8 +1281,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);
.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 +1327,12 @@
.ant-dropdown-menu-title-content {
color: var(--bg-ink-400);
}
&:hover:not(.ant-dropdown-menu-item-disabled) {
.ant-dropdown-menu-title-content {
color: var(--bg-ink-500);
}
}
}
}
}
@@ -1210,6 +1409,10 @@
color: var(--bg-ink-400);
}
}
.help-support-dropdown li.ant-dropdown-menu-item-divider {
background-color: var(--bg-vanilla-300) !important;
}
}
.version-tooltip-overlay {

View File

@@ -157,18 +157,15 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
DefaultHelpSupportDropdownMenuItems,
);
const [pinnedMenuItems, setPinnedMenuItems] = useState<SidebarItem[]>([]);
const [tempPinnedMenuItems, setTempPinnedMenuItems] = useState<SidebarItem[]>(
[],
);
const [secondaryMenuItems, setSecondaryMenuItems] = useState<SidebarItem[]>(
[],
);
const [hasScroll, setHasScroll] = useState(false);
const navTopSectionRef = useRef<HTMLDivElement>(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const prevSidebarOpenRef = useRef<boolean>(isPinned);
const userManuallyCollapsedRef = useRef<boolean>(false);
const checkScroll = useCallback((): void => {
if (navTopSectionRef.current) {
@@ -217,63 +214,72 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const isAdmin = user.role === USER_ROLES.ADMIN;
const isEditor = user.role === USER_ROLES.EDITOR;
useEffect(() => {
const navShortcuts = (userPreferences?.find(
// Compute initial pinned items and secondary menu items synchronously to avoid flash
const computedPinnedMenuItems = useMemo(() => {
const navShortcutsPreference = userPreferences?.find(
(preference) => preference.name === USER_PREFERENCES.NAV_SHORTCUTS,
)?.value as unknown) as string[];
);
const navShortcuts = (navShortcutsPreference?.value as unknown) as
| string[]
| undefined;
const shouldShowIntegrations =
(isCloudUser || isEnterpriseSelfHostedUser) && (isAdmin || isEditor);
// Check if preference exists (user has set it before, even if empty)
const preferenceExists = navShortcutsPreference !== undefined;
if (navShortcuts && isArray(navShortcuts) && navShortcuts.length > 0) {
// nav shortcuts is array of strings
const pinnedItems = navShortcuts
if (preferenceExists && isArray(navShortcuts)) {
return navShortcuts
.map((shortcut) =>
defaultMoreMenuItems.find((item) => item.itemKey === shortcut),
)
.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,
})),
);
}
// Preference doesn't exist or userPreferences not loaded yet
// If userPreferences is null, return empty to avoid showing defaults before preferences load
if (userPreferences === null) {
return [];
}
// Preference doesn't exist - use defaults for first-time users
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,
isEnterpriseSelfHostedUser,
isAdmin,
isEditor,
]);
const [pinnedMenuItems, setPinnedMenuItems] = useState<SidebarItem[]>(
computedPinnedMenuItems,
);
const [secondaryMenuItems, setSecondaryMenuItems] = useState<SidebarItem[]>(
computedSecondaryMenuItems,
);
// Sync state when computed values change (when userPreferences loads or updates)
// This ensures we respect user preferences without showing defaults first
useEffect(() => {
setPinnedMenuItems(computedPinnedMenuItems);
setSecondaryMenuItems(computedSecondaryMenuItems);
}, [computedPinnedMenuItems, computedSecondaryMenuItems]);
const isOnboardingV3Enabled = featureFlags?.find(
(flag) => flag.name === FeatureKeys.ONBOARDING_V3,
)?.active;
@@ -301,7 +307,8 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
setShowVersionUpdateNotification,
] = useState(false);
const [isMoreMenuCollapsed, setIsMoreMenuCollapsed] = useState(false);
const [isMoreMenuCollapsed, setIsMoreMenuCollapsed] = useState(!isPinned);
const [isHovered, setIsHovered] = useState(false);
const [
isReorderShortcutNavItemsModalOpen,
@@ -327,6 +334,17 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
.map((item) => item.itemKey)
.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(
{
name: USER_PREFERENCES.NAV_SHORTCUTS,
@@ -335,6 +353,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
{
onSuccess: (response) => {
if (response.data) {
// Update context again on success to ensure consistency
updateUserPreferenceInContext({
name: USER_PREFERENCES.NAV_SHORTCUTS,
description: USER_PREFERENCES.NAV_SHORTCUTS,
@@ -368,13 +387,13 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
if (isCurrentlyPinned) {
return prevItems.filter((i) => i.key !== item.key);
}
return [item, ...prevItems];
return [...prevItems, item];
});
// Get the updated pinned menu items for preference update
const updatedPinnedItems = pinnedMenuItems.some((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
updateNavShortcutsPreference(updatedPinnedItems);
@@ -406,6 +425,21 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
}
}, [isReorderShortcutNavItemsModalOpen, pinnedMenuItems]);
useEffect(() => {
const isSidebarOpen = isPinned || isHovered;
const wasSidebarOpen = prevSidebarOpenRef.current;
if (!isSidebarOpen) {
// Sidebar is collapsed - always collapse more menu and reset manual collapse flag
setIsMoreMenuCollapsed(true);
userManuallyCollapsedRef.current = false;
} else if (!wasSidebarOpen && !userManuallyCollapsedRef.current) {
// Sidebar just opened (transitioned from collapsed) - auto-expand only if user didn't manually collapse
setIsMoreMenuCollapsed(false);
}
prevSidebarOpenRef.current = isSidebarOpen;
}, [isPinned, isHovered]);
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
@@ -594,7 +628,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
},
{
type: 'group',
label: "WHAT's NEW",
label: "WHAT'S NEW",
},
...dropdownItems,
{
@@ -854,7 +888,15 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
return (
<div className={cx('sidenav-container', isPinned && 'pinned')}>
<div className={cx('sideNav', isPinned && 'pinned')}>
<div
className={cx(
'sideNav',
isPinned && 'pinned',
isDropdownOpen && 'dropdown-open',
)}
onMouseEnter={(): void => setIsHovered(true)}
onMouseLeave={(): void => setIsHovered(false)}
>
<div className="brand-container">
<div className="brand">
<div className="brand-company-meta">
@@ -952,44 +994,48 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
{renderNavItems(primaryMenuItems)}
</div>
<div className="shortcut-nav-items">
<div className="nav-title-section">
<div className="nav-section-title">
<div className="nav-section-title-icon">
<MousePointerClick size={16} />
{(pinnedMenuItems.length > 0 || isPinned || isHovered) && (
<div className="shortcut-nav-items">
<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>
{pinnedMenuItems.length > 1 && (
<Tooltip title="Manage shortcuts" placement="right">
<div
className="nav-section-title-icon reorder"
onClick={(): void => {
logEvent('Sidebar V2: Manage shortcuts clicked', {});
setIsReorderShortcutNavItemsModalOpen(true);
}}
>
<Logs size={16} />
</div>
</Tooltip>
)}
</div>
<div className="nav-section-title-text">SHORTCUTS</div>
{pinnedMenuItems.length === 0 && (
<div className="nav-section-subtitle">
You have not added any shortcuts yet.
</div>
)}
{pinnedMenuItems.length > 1 && (
<div
className="nav-section-title-icon reorder"
onClick={(): void => {
logEvent('Sidebar V2: Manage shortcuts clicked', {});
setIsReorderShortcutNavItemsModalOpen(true);
}}
>
<Logs size={16} />
{pinnedMenuItems.length > 0 && (
<div className="nav-items-section">
{renderNavItems(
pinnedMenuItems.filter((item) => item.isEnabled),
true,
)}
</div>
)}
</div>
{pinnedMenuItems.length === 0 && (
<div className="nav-section-subtitle">
You have not added any shortcuts yet.
</div>
)}
{pinnedMenuItems.length > 0 && (
<div className="nav-items-section">
{renderNavItems(
pinnedMenuItems.filter((item) => item.isEnabled),
true,
)}
</div>
)}
</div>
</div>
)}
{moreMenuItems.length > 0 && (
<div
@@ -1002,10 +1048,21 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
<div
className="nav-section-title"
onClick={(): void => {
// Only allow toggling when sidebar is pinned or hovered
if (!isPinned && !isHovered) {
return;
}
const newCollapsedState = !isMoreMenuCollapsed;
logEvent('Sidebar V2: More menu clicked', {
action: isMoreMenuCollapsed ? 'expand' : 'collapse',
});
setIsMoreMenuCollapsed(!isMoreMenuCollapsed);
setIsMoreMenuCollapsed(newCollapsedState);
// Track if user manually collapsed it
if (newCollapsedState) {
userManuallyCollapsedRef.current = true;
} else {
userManuallyCollapsedRef.current = false;
}
}}
>
<div className="nav-section-title-icon">
@@ -1055,6 +1112,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
placement="topLeft"
overlayClassName="nav-dropdown-overlay help-support-dropdown"
trigger={['click']}
onOpenChange={(open): void => setIsDropdownOpen(open)}
>
<div className="nav-item">
<div className="nav-item-data" data-testid="help-support-nav-item">
@@ -1075,6 +1133,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
placement="topLeft"
overlayClassName="nav-dropdown-overlay settings-dropdown"
trigger={['click']}
onOpenChange={(open): void => setIsDropdownOpen(open)}
>
<div className="nav-item">
<div className="nav-item-data" data-testid="settings-nav-item">

View File

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

View File

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