mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-27 18:54:27 +00:00
Compare commits
10 Commits
update-PR-
...
SIG-8604
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a3fd7259e | ||
|
|
a53f4e584a | ||
|
|
98fa1a42d0 | ||
|
|
c67d022175 | ||
|
|
a8f7d43b9b | ||
|
|
2953a18a84 | ||
|
|
77404d17fa | ||
|
|
359a018b96 | ||
|
|
8bef7d9be4 | ||
|
|
824da0af0c |
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user