Skip to content
Snippets Groups Projects
Unverified Commit 6f8484eb authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Modal tab styling updates (#23620)

* Modal tab scroll and styling updates
parent ef0d02d3
No related branches found
No related tags found
No related merge requests found
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
import Icon from "metabase/components/Icon";
import Ellipsified from "../Ellipsified";
export interface TabProps {
isSelected?: boolean;
......@@ -35,8 +36,8 @@ export const TabIcon = styled(Icon)`
margin-right: 0.25rem;
`;
export const TabLabel = styled.span`
export const TabLabel = styled(Ellipsified)`
font-size: 1rem;
line-height: 1rem;
font-weight: bold;
max-width: 16rem;
`;
......@@ -9,15 +9,37 @@ export default {
title: "Core/TabList",
component: TabList,
};
const sampleStyle = {
maxWidth: "400px",
padding: "10px",
border: "1px solid #ccc",
};
const Template: ComponentStory<typeof TabList> = args => {
const [{ value }, updateArgs] = useArgs();
const handleChange = (value: unknown) => updateArgs({ value });
return (
<TabList {...args} value={value} onChange={handleChange}>
<Tab value={1}>Tab 1</Tab>
<Tab value={2}>Tab 2</Tab>
</TabList>
<div style={sampleStyle}>
<TabList {...args} value={value} onChange={handleChange}>
<Tab value={1}>Tab 1</Tab>
<Tab value={2}>Tab 2</Tab>
<Tab value={3}>Tab3supercalifragilisticexpialidocious</Tab>
<Tab value={4}>
Tab 4 With a Very Long Name that may cause this component to wrap
</Tab>
<Tab value={5}>
Tab 5 With a Very Long Name that may cause this component to wrap
</Tab>
<Tab value={6}>
Tab 6 With a Very Long Name that may cause this component to wrap
</Tab>
<Tab value={7}>
Tab 7 With a Very Long Name that may cause this component to wrap
</Tab>
</TabList>
</div>
);
};
......
import styled from "@emotion/styled";
import { alpha, color } from "metabase/lib/colors";
import { space } from "metabase/styled-components/theme";
export const TabListRoot = styled.div`
overflow-x: auto;
position: relative;
display: flex;
align-items: center;
`;
export const TabListContent = styled.div`
overflow-x: hidden;
display: flex;
gap: 1rem;
align-items: flex-start;
gap: 2.5rem;
scroll-behavior: smooth;
`;
interface ScrollButtonProps {
directionIcon: "left" | "right";
}
export const ScrollButton = styled.button<ScrollButtonProps>`
position: absolute;
cursor: pointer;
height: 100%;
width: 3rem;
padding-bottom: ${space(2)};
text-align: ${props => props.directionIcon};
color: ${color("text-light")};
&:hover {
color: ${color("brand")};
}
${props => props.directionIcon}: 0;
background: linear-gradient(
to ${props => props.directionIcon},
${alpha("white", 0.1)},
${alpha("white", 0.5)},
30%,
${color("white")}
);
`;
......@@ -5,10 +5,16 @@ import React, {
Ref,
useContext,
useMemo,
useState,
useEffect,
useRef,
} from "react";
import Icon from "metabase/components/Icon";
import { useUniqueId } from "metabase/hooks/use-unique-id";
import { TabContext, TabContextType } from "../Tab";
import { TabListContent, TabListRoot } from "./TabList.styled";
import { TabListContent, TabListRoot, ScrollButton } from "./TabList.styled";
const UNDERSCROLL_PIXELS = 32;
export interface TabListProps<T>
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
......@@ -23,20 +29,73 @@ const TabList = forwardRef(function TabGroup<T>(
) {
const idPrefix = useUniqueId();
const outerContext = useContext(TabContext);
const [scrollPosition, setScrollPosition] = useState(0);
const [showScrollRight, setShowScrollRight] = useState(false);
const tabListContentRef = useRef(null);
const innerContext = useMemo(() => {
return { value, idPrefix, onChange };
}, [value, idPrefix, onChange]);
const activeContext = outerContext.isDefault ? innerContext : outerContext;
const scroll = (direction: string) => {
if (tabListContentRef.current) {
const container = tabListContentRef.current as HTMLDivElement;
const scrollDistance =
(container.offsetWidth - UNDERSCROLL_PIXELS) *
(direction === "left" ? -1 : 1);
container.scrollBy(scrollDistance, 0);
setScrollPosition(container.scrollLeft + scrollDistance);
}
};
const showScrollLeft = scrollPosition > 0;
useEffect(() => {
if (!tabListContentRef.current) {
return;
}
const container = tabListContentRef.current as HTMLDivElement;
setShowScrollRight(
scrollPosition + container.offsetWidth < container.scrollWidth,
);
}, [scrollPosition]);
return (
<TabListRoot {...props} ref={ref} role="tablist">
<TabListContent>
<TabListContent ref={tabListContentRef}>
<TabContext.Provider value={activeContext as TabContextType}>
{children}
</TabContext.Provider>
</TabListContent>
{showScrollLeft && (
<ScrollArrow direction="left" onClick={() => scroll("left")} />
)}
{showScrollRight && (
<ScrollArrow direction="right" onClick={() => scroll("right")} />
)}
</TabListRoot>
);
});
interface ScrollArrowProps {
direction: "left" | "right";
onClick: () => void;
}
const ScrollArrow = ({ direction, onClick }: ScrollArrowProps) => (
<ScrollButton
onClick={onClick}
directionIcon={direction}
aria-label={`scroll-${direction}-button`}
>
<Icon name={`chevron${direction}`} color="brand" />
</ScrollButton>
);
export default TabList;
......@@ -38,7 +38,7 @@ export const ModalTitle = styled(Ellipsified)`
`;
export const ModalTabList = styled(TabList)`
padding: 0 2rem;
margin: 0 2rem;
flex-shrink: 0;
`;
......
......@@ -174,7 +174,7 @@ const BulkFilterModalSectionList = ({
<TabContent value={tab} onChange={setTab}>
<ModalTabList>
{sections.map((section, index) => (
<Tab key={index} value={index} icon={section.icon}>
<Tab key={index} value={index}>
{section.name}
</Tab>
))}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment