draggableTabBar.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import 'intersection-observer'; // polyfill
  2. import {useState} from 'react';
  3. import styled from '@emotion/styled';
  4. import type {Key, Node} from '@react-types/shared';
  5. import Badge from 'sentry/components/badge/badge';
  6. import {DraggableTabList} from 'sentry/components/draggableTabs/draggableTabList';
  7. import type {DraggableTabListItemProps} from 'sentry/components/draggableTabs/item';
  8. import type {MenuItemProps} from 'sentry/components/dropdownMenu';
  9. import QueryCount from 'sentry/components/queryCount';
  10. import {TabPanels, Tabs} from 'sentry/components/tabs';
  11. import {space} from 'sentry/styles/space';
  12. import {defined} from 'sentry/utils';
  13. import {DraggableTabMenuButton} from 'sentry/views/issueList/draggableTabMenuButton';
  14. export interface Tab {
  15. content: React.ReactNode;
  16. key: Key;
  17. label: string;
  18. hasUnsavedChanges?: boolean;
  19. queryCount?: number;
  20. }
  21. export interface DraggableTabBarProps {
  22. tabs: Tab[];
  23. onDelete?: (key: MenuItemProps['key']) => void;
  24. onDiscard?: (key: MenuItemProps['key']) => void;
  25. onDuplicate?: (key: MenuItemProps['key']) => void;
  26. onRename?: (key: MenuItemProps['key']) => void;
  27. onSave?: (key: MenuItemProps['key']) => void;
  28. }
  29. export function DraggableTabBar(props: DraggableTabBarProps) {
  30. const [tabs, setTabs] = useState<Tab[]>(props.tabs);
  31. const [selectedTabKey, setSelectedTabKey] = useState<Key>(props.tabs[0].key);
  32. const onReorder: (newOrder: Node<DraggableTabListItemProps>[]) => void = newOrder => {
  33. setTabs(
  34. newOrder
  35. .map(node => {
  36. const foundTab = tabs.find(tab => tab.key === node.key);
  37. return foundTab?.key === node.key ? foundTab : null;
  38. })
  39. .filter(defined)
  40. );
  41. };
  42. return (
  43. <Tabs>
  44. <DraggableTabList
  45. onReorder={onReorder}
  46. onSelectionChange={setSelectedTabKey}
  47. orientation="horizontal"
  48. >
  49. {tabs.map(tab => (
  50. <DraggableTabList.Item key={tab.key}>
  51. <TabContentWrap>
  52. {tab.label}
  53. <StyledBadge>
  54. <QueryCount hideParens count={tab.queryCount} max={1000} />
  55. </StyledBadge>
  56. {selectedTabKey === tab.key && (
  57. <DraggableTabMenuButton
  58. hasUnsavedChanges={tab.hasUnsavedChanges}
  59. onDelete={key => props.onDelete?.(key)}
  60. onDiscard={key => props.onDiscard?.(key)}
  61. onDuplicate={key => props.onDuplicate?.(key)}
  62. onRename={key => props.onRename?.(key)}
  63. onSave={key => props.onSave?.(key)}
  64. />
  65. )}
  66. </TabContentWrap>
  67. </DraggableTabList.Item>
  68. ))}
  69. </DraggableTabList>
  70. <TabPanels>
  71. {tabs.map(tab => (
  72. <TabPanels.Item key={tab.key}>{tab.content}</TabPanels.Item>
  73. ))}
  74. </TabPanels>
  75. </Tabs>
  76. );
  77. }
  78. const TabContentWrap = styled('span')`
  79. white-space: nowrap;
  80. display: flex;
  81. align-items: center;
  82. flex-direction: row;
  83. padding: ${space(0)} ${space(0)};
  84. gap: 6px;
  85. `;
  86. const StyledBadge = styled(Badge)`
  87. display: flex;
  88. height: 16px;
  89. align-items: center;
  90. justify-content: center;
  91. border-radius: 10px;
  92. background: transparent;
  93. border: 1px solid ${p => p.theme.gray200};
  94. color: ${p => p.theme.gray300};
  95. margin-left: ${space(0)};
  96. `;