Skip to content

Commit 7cba90c

Browse files
authored
Merge pull request #1584 from visualize-admin/feat/tab-labels
Add labels to tabs
2 parents 33f0628 + 3c3adf0 commit 7cba90c

2 files changed

Lines changed: 150 additions & 28 deletions

File tree

app/components/chart-selection-tabs.tsx

Lines changed: 148 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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";
1516
import { makeStyles } from "@mui/styles";
1617
import { PUBLISHED_STATE } from "@prisma/client";
18+
import clsx from "clsx";
1719
import { useSession } from "next-auth/react";
1820
import Link from "next/link";
1921
import { useRouter } from "next/router";
@@ -50,7 +52,7 @@ import { useUserConfig } from "@/domain/user-configs";
5052
import { useFlag } from "@/flags";
5153
import { useDataCubesComponentsQuery } from "@/graphql/hooks";
5254
import { Icon, IconName } from "@/icons";
53-
import { useLocale } from "@/src";
55+
import { defaultLocale, useLocale } from "@/locales";
5456
import { createConfig, updateConfig } from "@/utils/chart-config/api";
5557
import { createChartId } from "@/utils/create-chart-id";
5658
import { 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

352362
type 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+
621690
const 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"

app/locales/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { defaultLocale, i18n, locales, parseLocaleString } from "./locales";
2+
export { LocaleProvider, useLocale } from "./use-locale";

0 commit comments

Comments
 (0)