index.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. import {Fragment, useContext, useEffect} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import {hideSidebar, showSidebar} from 'sentry/actionCreators/preferences';
  6. import Feature from 'sentry/components/acl/feature';
  7. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  8. import {OnboardingContext} from 'sentry/components/onboarding/onboardingContext';
  9. import {getMergedTasks} from 'sentry/components/onboardingWizard/taskConfig';
  10. import PerformanceOnboardingSidebar from 'sentry/components/performanceOnboarding/sidebar';
  11. import ReplaysOnboardingSidebar from 'sentry/components/replaysOnboarding/sidebar';
  12. import {isDone} from 'sentry/components/sidebar/utils';
  13. import {
  14. IconChevron,
  15. IconDashboard,
  16. IconGraph,
  17. IconIssues,
  18. IconLightning,
  19. IconPlay,
  20. IconProfiling,
  21. IconProject,
  22. IconReleases,
  23. IconSettings,
  24. IconSiren,
  25. IconStar,
  26. IconStats,
  27. IconSupport,
  28. IconTelescope,
  29. IconTimer,
  30. IconUser,
  31. } from 'sentry/icons';
  32. import {t} from 'sentry/locale';
  33. import ConfigStore from 'sentry/stores/configStore';
  34. import DemoWalkthroughStore from 'sentry/stores/demoWalkthroughStore';
  35. import HookStore from 'sentry/stores/hookStore';
  36. import PreferencesStore from 'sentry/stores/preferencesStore';
  37. import SidebarPanelStore from 'sentry/stores/sidebarPanelStore';
  38. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  39. import {space} from 'sentry/styles/space';
  40. import {Organization} from 'sentry/types';
  41. import {isDemoWalkthrough} from 'sentry/utils/demoMode';
  42. import {getDiscoverLandingUrl} from 'sentry/utils/discover/urls';
  43. import theme from 'sentry/utils/theme';
  44. import {useLocation} from 'sentry/utils/useLocation';
  45. import useMedia from 'sentry/utils/useMedia';
  46. import useProjects from 'sentry/utils/useProjects';
  47. import {RELEASE_LEVEL} from 'sentry/views/performance/database/settings';
  48. import {ProfilingOnboardingSidebar} from '../profiling/ProfilingOnboarding/profilingOnboardingSidebar';
  49. import Broadcasts from './broadcasts';
  50. import SidebarHelp from './help';
  51. import OnboardingStatus from './onboardingStatus';
  52. import ServiceIncidents from './serviceIncidents';
  53. import {SidebarAccordion} from './sidebarAccordion';
  54. import SidebarDropdown from './sidebarDropdown';
  55. import SidebarItem from './sidebarItem';
  56. import {SidebarOrientation, SidebarPanelKey} from './types';
  57. type Props = {
  58. location?: Location;
  59. organization?: Organization;
  60. };
  61. function activatePanel(panel: SidebarPanelKey) {
  62. SidebarPanelStore.activatePanel(panel);
  63. }
  64. function togglePanel(panel: SidebarPanelKey) {
  65. SidebarPanelStore.togglePanel(panel);
  66. }
  67. function hidePanel() {
  68. SidebarPanelStore.hidePanel();
  69. }
  70. function useOpenOnboardingSidebar(organization?: Organization) {
  71. const onboardingContext = useContext(OnboardingContext);
  72. const {projects: project} = useProjects();
  73. const location = useLocation();
  74. const openOnboardingSidebar = (() => {
  75. if (location?.hash === '#welcome') {
  76. if (organization && !ConfigStore.get('demoMode')) {
  77. const tasks = getMergedTasks({
  78. organization,
  79. projects: project,
  80. onboardingContext,
  81. });
  82. const allDisplayedTasks = tasks
  83. .filter(task => task.display)
  84. .filter(task => !task.renderCard);
  85. const doneTasks = allDisplayedTasks.filter(isDone);
  86. return !(doneTasks.length >= allDisplayedTasks.length);
  87. }
  88. return true;
  89. }
  90. return false;
  91. })();
  92. useEffect(() => {
  93. if (openOnboardingSidebar) {
  94. activatePanel(SidebarPanelKey.ONBOARDING_WIZARD);
  95. }
  96. }, [openOnboardingSidebar]);
  97. }
  98. function Sidebar({location, organization}: Props) {
  99. const config = useLegacyStore(ConfigStore);
  100. const preferences = useLegacyStore(PreferencesStore);
  101. const activePanel = useLegacyStore(SidebarPanelStore);
  102. const collapsed = !!preferences.collapsed;
  103. const horizontal = useMedia(`(max-width: ${theme.breakpoints.medium})`);
  104. useOpenOnboardingSidebar();
  105. const toggleCollapse = () => {
  106. const action = collapsed ? showSidebar : hideSidebar;
  107. action();
  108. };
  109. // Close panel on any navigation
  110. useEffect(() => void hidePanel(), [location?.pathname]);
  111. // Add classname to body
  112. useEffect(() => {
  113. const bcl = document.body.classList;
  114. bcl.add('body-sidebar');
  115. return () => bcl.remove('body-sidebar');
  116. }, []);
  117. useEffect(() => {
  118. Object.values(SidebarPanelKey).forEach(key => {
  119. if (location?.hash === `#sidebar-${key}`) {
  120. togglePanel(key);
  121. }
  122. });
  123. }, [location?.hash]);
  124. // Add sidebar collapse classname to body
  125. useEffect(() => {
  126. const bcl = document.body.classList;
  127. if (collapsed) {
  128. bcl.add('collapsed');
  129. } else {
  130. bcl.remove('collapsed');
  131. }
  132. return () => bcl.remove('collapsed');
  133. }, [collapsed]);
  134. const hasPanel = !!activePanel;
  135. const hasOrganization = !!organization;
  136. const orientation: SidebarOrientation = horizontal ? 'top' : 'left';
  137. const sidebarItemProps = {
  138. orientation,
  139. collapsed,
  140. hasPanel,
  141. organization,
  142. };
  143. const sidebarAnchor = isDemoWalkthrough() ? (
  144. <GuideAnchor target="projects" disabled={!DemoWalkthroughStore.get('sidebar')}>
  145. {t('Projects')}
  146. </GuideAnchor>
  147. ) : (
  148. <GuideAnchor target="projects">{t('Projects')}</GuideAnchor>
  149. );
  150. const projects = hasOrganization && (
  151. <SidebarItem
  152. {...sidebarItemProps}
  153. index
  154. icon={<IconProject />}
  155. label={sidebarAnchor}
  156. to={`/organizations/${organization.slug}/projects/`}
  157. id="projects"
  158. />
  159. );
  160. const issues = hasOrganization && (
  161. <SidebarItem
  162. {...sidebarItemProps}
  163. icon={<IconIssues />}
  164. label={<GuideAnchor target="issues">{t('Issues')}</GuideAnchor>}
  165. to={`/organizations/${organization.slug}/issues/?referrer=sidebar`}
  166. id="issues"
  167. />
  168. );
  169. const discover2 = hasOrganization && (
  170. <Feature
  171. hookName="feature-disabled:discover2-sidebar-item"
  172. features={['discover-basic']}
  173. organization={organization}
  174. >
  175. <SidebarItem
  176. {...sidebarItemProps}
  177. icon={<IconTelescope />}
  178. label={<GuideAnchor target="discover">{t('Discover')}</GuideAnchor>}
  179. to={getDiscoverLandingUrl(organization)}
  180. id="discover-v2"
  181. />
  182. </Feature>
  183. );
  184. const performance = hasOrganization && (
  185. <Feature
  186. hookName="feature-disabled:performance-sidebar-item"
  187. features={['performance-view']}
  188. organization={organization}
  189. >
  190. {(() => {
  191. // If Database View is enabled, show a Performance accordion with a Database sub-item
  192. if (organization.features.includes('performance-database-view')) {
  193. return (
  194. <SidebarAccordion
  195. {...sidebarItemProps}
  196. icon={<IconLightning />}
  197. label={<GuideAnchor target="performance">{t('Performance')}</GuideAnchor>}
  198. to={`/organizations/${organization.slug}/performance/`}
  199. id="performance"
  200. >
  201. <SidebarItem
  202. {...sidebarItemProps}
  203. isAlpha={RELEASE_LEVEL === 'alpha'}
  204. isBeta={RELEASE_LEVEL === 'beta'}
  205. isNew={RELEASE_LEVEL === 'new'}
  206. label={
  207. <GuideAnchor target="performance-database">
  208. {t('Query Insights')}
  209. </GuideAnchor>
  210. }
  211. to={`/organizations/${organization.slug}/performance/database/`}
  212. id="performance-database"
  213. icon={<SubitemDot collapsed={collapsed} />}
  214. />
  215. </SidebarAccordion>
  216. );
  217. }
  218. // Otherwise, show a regular sidebar link to the Performance landing page
  219. return (
  220. <SidebarItem
  221. {...sidebarItemProps}
  222. icon={<IconLightning />}
  223. label={<GuideAnchor target="performance">{t('Performance')}</GuideAnchor>}
  224. to={`/organizations/${organization.slug}/performance/`}
  225. id="performance"
  226. />
  227. );
  228. })()}
  229. </Feature>
  230. );
  231. const starfish = hasOrganization && (
  232. <Feature
  233. hookName="feature-disabled:starfish-view"
  234. features={['starfish-view']}
  235. organization={organization}
  236. >
  237. <SidebarAccordion
  238. {...sidebarItemProps}
  239. icon={<IconStar />}
  240. aria-label={t('Starfish')}
  241. label={<GuideAnchor target="starfish">{t('Starfish')}</GuideAnchor>}
  242. to={`/organizations/${organization.slug}/starfish/`}
  243. id="starfish"
  244. exact
  245. >
  246. <SidebarItem
  247. {...sidebarItemProps}
  248. label={<GuideAnchor target="starfish">{t('Database')}</GuideAnchor>}
  249. to={`/organizations/${organization.slug}/performance/database/`}
  250. id="performance-database"
  251. icon={<SubitemDot collapsed={collapsed} />}
  252. />
  253. <SidebarItem
  254. {...sidebarItemProps}
  255. label={<GuideAnchor target="starfish">{t('Interactions')}</GuideAnchor>}
  256. to={`/organizations/${organization.slug}/performance/browser/interactions`}
  257. id="performance-browser-interactions"
  258. icon={<SubitemDot collapsed={collapsed} />}
  259. />
  260. <SidebarItem
  261. {...sidebarItemProps}
  262. label={<GuideAnchor target="starfish">{t('Page Loads')}</GuideAnchor>}
  263. to={`/organizations/${organization.slug}/performance/browser/pageloads`}
  264. id="performance-browser-page-loads"
  265. icon={<SubitemDot collapsed={collapsed} />}
  266. />
  267. </SidebarAccordion>
  268. </Feature>
  269. );
  270. const releases = hasOrganization && (
  271. <SidebarItem
  272. {...sidebarItemProps}
  273. icon={<IconReleases />}
  274. label={<GuideAnchor target="releases">{t('Releases')}</GuideAnchor>}
  275. to={`/organizations/${organization.slug}/releases/`}
  276. id="releases"
  277. />
  278. );
  279. const userFeedback = hasOrganization && (
  280. <SidebarItem
  281. {...sidebarItemProps}
  282. icon={<IconSupport />}
  283. label={t('User Feedback')}
  284. to={`/organizations/${organization.slug}/user-feedback/`}
  285. id="user-feedback"
  286. />
  287. );
  288. const feedback = hasOrganization && (
  289. <Feature features={['user-feedback-ui']} organization={organization}>
  290. <SidebarItem
  291. {...sidebarItemProps}
  292. icon={<IconUser />}
  293. label={t('Feedback')}
  294. to={`/organizations/${organization.slug}/feedback/`}
  295. id="feedback"
  296. isAlpha
  297. />
  298. </Feature>
  299. );
  300. const alerts = hasOrganization && (
  301. <SidebarItem
  302. {...sidebarItemProps}
  303. icon={<IconSiren />}
  304. label={t('Alerts')}
  305. to={`/organizations/${organization.slug}/alerts/rules/`}
  306. id="alerts"
  307. />
  308. );
  309. const monitors = hasOrganization && (
  310. <Feature features={['monitors']} organization={organization}>
  311. <SidebarItem
  312. {...sidebarItemProps}
  313. icon={<IconTimer />}
  314. label={t('Crons')}
  315. to={`/organizations/${organization.slug}/crons/`}
  316. id="crons"
  317. isBeta
  318. />
  319. </Feature>
  320. );
  321. const replays = hasOrganization && (
  322. <Feature
  323. hookName="feature-disabled:replay-sidebar-item"
  324. features={['session-replay-ui']}
  325. organization={organization}
  326. requireAll={false}
  327. >
  328. <SidebarItem
  329. {...sidebarItemProps}
  330. icon={<IconPlay />}
  331. label={t('Replays')}
  332. to={`/organizations/${organization.slug}/replays/`}
  333. id="replays"
  334. />
  335. </Feature>
  336. );
  337. const ddm = hasOrganization && (
  338. <Feature
  339. features={['ddm-ui', 'custom-metrics']}
  340. organization={organization}
  341. requireAll
  342. >
  343. <SidebarItem
  344. {...sidebarItemProps}
  345. icon={<IconGraph />}
  346. label={t('DDM')}
  347. to={`/organizations/${organization.slug}/ddm/`}
  348. id="ddm"
  349. isAlpha
  350. />
  351. </Feature>
  352. );
  353. const dashboards = hasOrganization && (
  354. <Feature
  355. hookName="feature-disabled:dashboards-sidebar-item"
  356. features={['discover', 'discover-query', 'dashboards-basic', 'dashboards-edit']}
  357. organization={organization}
  358. requireAll={false}
  359. >
  360. <SidebarItem
  361. {...sidebarItemProps}
  362. index
  363. icon={<IconDashboard />}
  364. label={t('Dashboards')}
  365. to={`/organizations/${organization.slug}/dashboards/`}
  366. id="customizable-dashboards"
  367. />
  368. </Feature>
  369. );
  370. const profiling = hasOrganization && (
  371. <Feature
  372. hookName="feature-disabled:profiling-sidebar-item"
  373. features={['profiling']}
  374. organization={organization}
  375. requireAll={false}
  376. >
  377. <SidebarItem
  378. {...sidebarItemProps}
  379. index
  380. icon={<IconProfiling />}
  381. label={t('Profiling')}
  382. to={`/organizations/${organization.slug}/profiling/`}
  383. id="profiling"
  384. />
  385. </Feature>
  386. );
  387. const stats = hasOrganization && (
  388. <SidebarItem
  389. {...sidebarItemProps}
  390. icon={<IconStats />}
  391. label={t('Stats')}
  392. to={`/organizations/${organization.slug}/stats/`}
  393. id="stats"
  394. />
  395. );
  396. const settings = hasOrganization && (
  397. <SidebarItem
  398. {...sidebarItemProps}
  399. icon={<IconSettings />}
  400. label={t('Settings')}
  401. to={`/settings/${organization.slug}/`}
  402. id="settings"
  403. />
  404. );
  405. return (
  406. <SidebarWrapper aria-label={t('Primary Navigation')} collapsed={collapsed}>
  407. <SidebarSectionGroupPrimary>
  408. <SidebarSection>
  409. <SidebarDropdown
  410. orientation={orientation}
  411. collapsed={collapsed}
  412. org={organization}
  413. user={config.user}
  414. config={config}
  415. />
  416. </SidebarSection>
  417. <PrimaryItems>
  418. {hasOrganization && (
  419. <Fragment>
  420. <SidebarSection>
  421. {issues}
  422. {projects}
  423. </SidebarSection>
  424. <SidebarSection>
  425. {performance}
  426. {starfish}
  427. {profiling}
  428. {replays}
  429. {monitors}
  430. {alerts}
  431. </SidebarSection>
  432. <SidebarSection>
  433. {discover2}
  434. {ddm}
  435. {dashboards}
  436. {releases}
  437. {userFeedback}
  438. {feedback}
  439. </SidebarSection>
  440. <SidebarSection>
  441. {stats}
  442. {settings}
  443. </SidebarSection>
  444. </Fragment>
  445. )}
  446. </PrimaryItems>
  447. </SidebarSectionGroupPrimary>
  448. {hasOrganization && (
  449. <SidebarSectionGroup>
  450. <PerformanceOnboardingSidebar
  451. currentPanel={activePanel}
  452. onShowPanel={() => togglePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING)}
  453. hidePanel={hidePanel}
  454. {...sidebarItemProps}
  455. />
  456. <ReplaysOnboardingSidebar
  457. currentPanel={activePanel}
  458. onShowPanel={() => togglePanel(SidebarPanelKey.REPLAYS_ONBOARDING)}
  459. hidePanel={hidePanel}
  460. {...sidebarItemProps}
  461. />
  462. <ProfilingOnboardingSidebar
  463. currentPanel={activePanel}
  464. onShowPanel={() => togglePanel(SidebarPanelKey.REPLAYS_ONBOARDING)}
  465. hidePanel={hidePanel}
  466. {...sidebarItemProps}
  467. />
  468. <SidebarSection noMargin noPadding>
  469. <OnboardingStatus
  470. org={organization}
  471. currentPanel={activePanel}
  472. onShowPanel={() => togglePanel(SidebarPanelKey.ONBOARDING_WIZARD)}
  473. hidePanel={hidePanel}
  474. {...sidebarItemProps}
  475. />
  476. </SidebarSection>
  477. <SidebarSection>
  478. {HookStore.get('sidebar:bottom-items').length > 0 &&
  479. HookStore.get('sidebar:bottom-items')[0]({
  480. orientation,
  481. collapsed,
  482. hasPanel,
  483. organization,
  484. })}
  485. <SidebarHelp
  486. orientation={orientation}
  487. collapsed={collapsed}
  488. hidePanel={hidePanel}
  489. organization={organization}
  490. />
  491. <Broadcasts
  492. orientation={orientation}
  493. collapsed={collapsed}
  494. currentPanel={activePanel}
  495. onShowPanel={() => togglePanel(SidebarPanelKey.BROADCASTS)}
  496. hidePanel={hidePanel}
  497. organization={organization}
  498. />
  499. <ServiceIncidents
  500. orientation={orientation}
  501. collapsed={collapsed}
  502. currentPanel={activePanel}
  503. onShowPanel={() => togglePanel(SidebarPanelKey.SERVICE_INCIDENTS)}
  504. hidePanel={hidePanel}
  505. />
  506. </SidebarSection>
  507. {!horizontal && (
  508. <SidebarSection>
  509. <SidebarCollapseItem
  510. id="collapse"
  511. data-test-id="sidebar-collapse"
  512. {...sidebarItemProps}
  513. icon={<IconChevron direction={collapsed ? 'right' : 'left'} size="sm" />}
  514. label={collapsed ? t('Expand') : t('Collapse')}
  515. onClick={toggleCollapse}
  516. />
  517. </SidebarSection>
  518. )}
  519. </SidebarSectionGroup>
  520. )}
  521. </SidebarWrapper>
  522. );
  523. }
  524. export default Sidebar;
  525. const responsiveFlex = css`
  526. display: flex;
  527. flex-direction: column;
  528. @media (max-width: ${theme.breakpoints.medium}) {
  529. flex-direction: row;
  530. }
  531. `;
  532. export const SidebarWrapper = styled('nav')<{collapsed: boolean}>`
  533. background: ${p => p.theme.sidebarGradient};
  534. color: ${p => p.theme.sidebar.color};
  535. line-height: 1;
  536. padding: 12px 0 2px; /* Allows for 32px avatars */
  537. width: ${p => p.theme.sidebar[p.collapsed ? 'collapsedWidth' : 'expandedWidth']};
  538. position: fixed;
  539. top: ${p => (ConfigStore.get('demoMode') ? p.theme.demo.headerSize : 0)};
  540. left: 0;
  541. bottom: 0;
  542. justify-content: space-between;
  543. z-index: ${p => p.theme.zIndex.sidebar};
  544. border-right: solid 1px ${p => p.theme.sidebarBorder};
  545. ${responsiveFlex};
  546. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  547. top: 0;
  548. left: 0;
  549. right: 0;
  550. height: ${p => p.theme.sidebar.mobileHeight};
  551. bottom: auto;
  552. width: auto;
  553. padding: 0 ${space(1)};
  554. align-items: center;
  555. border-right: none;
  556. border-bottom: solid 1px ${p => p.theme.sidebarBorder};
  557. }
  558. `;
  559. const SidebarSectionGroup = styled('div')`
  560. ${responsiveFlex};
  561. flex-shrink: 0; /* prevents shrinking on Safari */
  562. gap: 1px;
  563. `;
  564. const SidebarSectionGroupPrimary = styled('div')`
  565. ${responsiveFlex};
  566. /* necessary for child flexing on msedge and ff */
  567. min-height: 0;
  568. min-width: 0;
  569. flex: 1;
  570. /* expand to fill the entire height on mobile */
  571. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  572. height: 100%;
  573. align-items: center;
  574. }
  575. `;
  576. const PrimaryItems = styled('div')`
  577. overflow: auto;
  578. flex: 1;
  579. display: flex;
  580. flex-direction: column;
  581. gap: 1px;
  582. -ms-overflow-style: -ms-autohiding-scrollbar;
  583. @media (max-height: 675px) and (min-width: ${p => p.theme.breakpoints.medium}) {
  584. border-bottom: 1px solid ${p => p.theme.gray400};
  585. padding-bottom: ${space(1)};
  586. box-shadow: rgba(0, 0, 0, 0.15) 0px -10px 10px inset;
  587. &::-webkit-scrollbar {
  588. background-color: transparent;
  589. width: 8px;
  590. }
  591. &::-webkit-scrollbar-thumb {
  592. background: ${p => p.theme.gray400};
  593. border-radius: 8px;
  594. }
  595. }
  596. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  597. overflow-y: visible;
  598. flex-direction: row;
  599. height: 100%;
  600. align-items: center;
  601. border-right: 1px solid ${p => p.theme.gray400};
  602. padding-right: ${space(1)};
  603. margin-right: ${space(0.5)};
  604. box-shadow: rgba(0, 0, 0, 0.15) -10px 0px 10px inset;
  605. ::-webkit-scrollbar {
  606. display: none;
  607. }
  608. }
  609. `;
  610. const SubitemDot = styled('div')<{collapsed: boolean}>`
  611. width: 3px;
  612. height: 3px;
  613. background: currentcolor;
  614. border-radius: 50%;
  615. opacity: ${p => (p.collapsed ? 1 : 0)};
  616. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  617. opacity: 1;
  618. }
  619. `;
  620. const SidebarSection = styled(SidebarSectionGroup)<{
  621. noMargin?: boolean;
  622. noPadding?: boolean;
  623. }>`
  624. ${p => !p.noMargin && `margin: ${space(1)} 0`};
  625. ${p => !p.noPadding && `padding: 0 ${space(2)}`};
  626. @media (max-width: ${p => p.theme.breakpoints.small}) {
  627. margin: 0;
  628. padding: 0;
  629. }
  630. &:empty {
  631. display: none;
  632. }
  633. `;
  634. const SidebarCollapseItem = styled(SidebarItem)`
  635. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  636. display: none;
  637. }
  638. `;