@@ -7,13 +7,15 @@ import {
77 Grow ,
88 Popover ,
99 Stack ,
10+ tabClasses ,
1011 Theme ,
1112 Tooltip ,
1213 Typography ,
1314 useEventCallback ,
1415} from "@mui/material" ;
1516import { makeStyles } from "@mui/styles" ;
1617import { PUBLISHED_STATE } from "@prisma/client" ;
18+ import clsx from "clsx" ;
1719import { useSession } from "next-auth/react" ;
1820import Link from "next/link" ;
1921import { useRouter } from "next/router" ;
@@ -50,7 +52,7 @@ import { useUserConfig } from "@/domain/user-configs";
5052import { useFlag } from "@/flags" ;
5153import { useDataCubesComponentsQuery } from "@/graphql/hooks" ;
5254import { Icon , IconName } from "@/icons" ;
53- import { useLocale } from "@/src " ;
55+ import { defaultLocale , useLocale } from "@/locales " ;
5456import { createConfig , updateConfig } from "@/utils/chart-config/api" ;
5557import { createChartId } from "@/utils/create-chart-id" ;
5658import { getRouterChartId } from "@/utils/router/helpers" ;
@@ -104,6 +106,7 @@ export const ChartSelectionTabs = () => {
104106 const [ state ] = useConfiguratorState ( hasChartConfigs ) ;
105107 const editable =
106108 isConfiguring ( state ) || isLayouting ( state ) || isPublishing ( state ) ;
109+ const locale = useLocale ( ) ;
107110
108111 if ( ! editable && state . chartConfigs . length === 1 ) {
109112 return null ;
@@ -115,6 +118,12 @@ export const ChartSelectionTabs = () => {
115118 key : d . key ,
116119 chartType : d . chartType ,
117120 active : d . key === chartConfig . key ,
121+ label :
122+ d . meta . title [ locale ] !== ""
123+ ? d . meta . title [ locale ]
124+ : d . meta . title [ defaultLocale ] !== ""
125+ ? d . meta . title [ defaultLocale ]
126+ : t ( { id : "annotation.add.title" } ) ,
118127 } ;
119128 } ) ;
120129
@@ -347,6 +356,7 @@ type TabDatum = {
347356 key : string ;
348357 chartType : ChartType ;
349358 active : boolean ;
359+ label : string ;
350360} ;
351361
352362type TabsFixedProps = {
@@ -618,7 +628,67 @@ const PassthroughTab = ({
618628 return < > { children } </ > ;
619629} ;
620630
631+ const useTabsInnerStyles = makeStyles < Theme > ( ( theme ) => ( {
632+ root : {
633+ display : "flex" ,
634+ alignItems : "center" ,
635+ justifyContent : "space-between" ,
636+ gap : 1 ,
637+ } ,
638+ tab : {
639+ zIndex : 1 ,
640+ maxWidth : "auto" ,
641+ "&:first-child" : {
642+ // We need to add a negative margin to the first tab so that its left margin
643+ // goes "under" the border of the tab list.
644+ marginLeft : - 1 ,
645+ } ,
646+ } ,
647+ // :last-child does not work when the tabs are not scrollable
648+ // MUI seems to add an empty element at the end, thus we cannot use :last-child
649+ lastTab : {
650+ // We need to add a negative margin to the last tab so that its right margin
651+ // goes "under" the border of the tab list.
652+ marginRight : - 1 ,
653+ } ,
654+ tabList : {
655+ top : 1 ,
656+ border : "1px solid" ,
657+ borderColor : theme . palette . divider ,
658+ borderTop : 0 ,
659+ borderBottom : 0 ,
660+ position : "relative" ,
661+ "--bg" : theme . palette . background . paper ,
662+ "--cut-off-width" : "1rem" ,
663+
664+ [ `& .${ tabClasses . root } ` ] : {
665+ maxWidth : "max-content" ,
666+ } ,
667+
668+ "&:before, &:after" : {
669+ top : 1 ,
670+ content : '""' ,
671+ bottom : 1 ,
672+ position : "absolute" ,
673+ width : "var(--cut-off-width)" ,
674+ zIndex : 10 ,
675+ } ,
676+ "&:before" : {
677+ borderBottom : 0 ,
678+ left : 0 ,
679+ right : "auto" ,
680+ backgroundImage : "linear-gradient(to left, transparent, var(--bg))" ,
681+ } ,
682+ "&:after" : {
683+ left : "auto" ,
684+ right : 0 ,
685+ backgroundImage : "linear-gradient(to right, transparent, var(--bg))" ,
686+ } ,
687+ } ,
688+ } ) ) ;
689+
621690const TabsInner = ( props : TabsInnerProps ) => {
691+ const classes = useTabsInnerStyles ( ) ;
622692 const {
623693 data,
624694 addable,
@@ -636,14 +706,7 @@ const TabsInner = (props: TabsInnerProps) => {
636706 const activeTabIndex = data . findIndex ( ( x ) => x . active ) ;
637707
638708 return (
639- < Box
640- sx = { {
641- display : "flex" ,
642- alignItems : "flex-start" ,
643- justifyContent : "space-between" ,
644- gap : 5 ,
645- } }
646- >
709+ < div className = { classes . root } >
647710 < TabContext value = { `${ activeTabIndex } ` } >
648711 < DragDropContext
649712 onDragEnd = { ( d ) => {
@@ -664,7 +727,7 @@ const TabsInner = (props: TabsInnerProps) => {
664727 ref = { provided . innerRef }
665728 variant = "scrollable"
666729 scrollButtons = { false }
667- sx = { { top : 1 } }
730+ className = { classes . tabList }
668731 >
669732 { data . map ( ( d , i ) => (
670733 < PassthroughTab key = { d . key } value = { `${ i } ` } >
@@ -684,14 +747,18 @@ const TabsInner = (props: TabsInnerProps) => {
684747 component = "div"
685748 key = { d . key }
686749 value = { `${ i } ` }
687- className = { `${
750+ className = { clsx (
751+ classes . tab ,
688752 // We need to add the "selected" class ourselves since we are wrapping
689753 // the tabs by Draggable.
690- i === activeTabIndex ? "Mui-selected" : ""
691- } `}
754+ i === activeTabIndex ? "Mui-selected" : "" ,
755+ i === data . length - 1 ? classes . lastTab : ""
756+ ) }
692757 sx = { {
693758 px : 0 ,
694- minWidth : "fit-content" ,
759+ flexShrink : 1 ,
760+ minWidth : 180 ,
761+ flexBasis : "100%" ,
695762 } }
696763 label = {
697764 < TabContent
@@ -700,6 +767,7 @@ const TabsInner = (props: TabsInnerProps) => {
700767 editable = { editable }
701768 draggable = { draggable }
702769 active = { d . active }
770+ label = { d . label }
703771 dragging = { snapshot . isDragging }
704772 onChevronDownClick = { ( e ) => {
705773 onChartEdit ?.( e , d . key ) ;
@@ -718,25 +786,32 @@ const TabsInner = (props: TabsInnerProps) => {
718786 < PassthroughTab >
719787 < div style = { { opacity : 0 } } > { provided . placeholder } </ div >
720788 </ PassthroughTab >
721-
722- { addable && (
723- < VisualizeTab
724- component = "div"
725- sx = { {
726- px : 0 ,
727- minWidth : "fit-content" ,
728- } }
729- onClick = { onChartAdd }
730- label = { < TabContent iconName = "add" chartKey = "" /> }
731- />
732- ) }
733789 </ VisualizeTabList >
734790 ) }
735791 </ Droppable >
736792 </ DragDropContext >
737793 </ TabContext >
738794
739- < Box gap = "0.5rem" display = "flex" >
795+ { addable && (
796+ < VisualizeTab
797+ component = "div"
798+ className = { classes . tab }
799+ sx = { {
800+ px : 0 ,
801+ pt : "2px" ,
802+ top : 1 ,
803+ margin : "0 1rem" ,
804+ height : "100%" ,
805+ border : "1px solid" ,
806+ borderColor : "divider" ,
807+ minWidth : "fit-content" ,
808+ } }
809+ onClick = { onChartAdd }
810+ label = { < TabContent iconName = "add" chartKey = "" /> }
811+ />
812+ ) }
813+ < Box flexGrow = { 1 } />
814+ < Box gap = "0.5rem" display = "flex" flexShrink = { 0 } >
740815 { isConfiguring ( state ) ? < SaveDraftButton chartId = { chartId } /> : null }
741816 { editable &&
742817 isConfiguring ( state ) &&
@@ -746,7 +821,7 @@ const TabsInner = (props: TabsInnerProps) => {
746821 < PublishChartButton chartId = { chartId } />
747822 ) ) }
748823 </ Box >
749- </ Box >
824+ </ div >
750825 ) ;
751826} ;
752827
@@ -755,11 +830,44 @@ export const useIconStyles = makeStyles<
755830 { active : boolean | undefined ; dragging : boolean | undefined }
756831> ( ( { palette, spacing } ) => ( {
757832 root : {
833+ "--bg" : palette . background . paper ,
834+ backgroundColor : "var(--bg)" ,
758835 alignItems : "center" ,
759836 padding : spacing ( 2 ) ,
760837 borderTopLeftRadius : spacing ( 1 ) ,
761838 borderTopRightRadius : spacing ( 1 ) ,
762839 cursor : "default" ,
840+ textTransform : "none" ,
841+ width : "100%" ,
842+ justifyContent : "stretch" ,
843+ } ,
844+ label : {
845+ flexShrink : 1 ,
846+ flexGrow : 1 ,
847+ marginLeft : "-0.5rem" ,
848+ color : "inherit" ,
849+ flexWrap : "nowrap" ,
850+ whiteSpace : "nowrap" ,
851+ minWidth : 0 ,
852+ overflow : "hidden" ,
853+ minHeight : "100%" ,
854+ position : "relative" ,
855+ display : "flex" ,
856+ cursor : "pointer" ,
857+ alignItems : "center" ,
858+ marginRight : "0.15rem" ,
859+
860+ "&:after" : {
861+ content : '" "' ,
862+ position : "absolute" ,
863+ right : 0 ,
864+ top : 0 ,
865+ bottom : 0 ,
866+ height : "auto" ,
867+ width : "10px" ,
868+ backgroundImage :
869+ "linear-gradient(to right, rgba(255, 255, 255, 0), var(--bg))" ,
870+ } ,
763871 } ,
764872 chartIconWrapper : {
765873 minWidth : "fit-content" ,
@@ -795,6 +903,7 @@ type TabContentProps = {
795903 draggable ?: boolean ;
796904 active ?: boolean ;
797905 dragging ?: boolean ;
906+ label ?: string ;
798907 onChevronDownClick ?: (
799908 e : React . MouseEvent < HTMLElement > ,
800909 activeChartKey : string
@@ -809,6 +918,7 @@ const TabContent = (props: TabContentProps) => {
809918 editable,
810919 draggable,
811920 active,
921+ label,
812922 dragging,
813923 onChevronDownClick,
814924 onSwitchClick,
@@ -825,6 +935,16 @@ const TabContent = (props: TabContentProps) => {
825935 < Icon name = { iconName } />
826936 </ Button >
827937
938+ { label ? (
939+ < Button
940+ variant = "text"
941+ color = "primary"
942+ className = { classes . label }
943+ onClick = { onSwitchClick }
944+ >
945+ { label }
946+ </ Button >
947+ ) : null }
828948 { editable && (
829949 < Button
830950 variant = "text"
0 commit comments