Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c9a9d8a
feat: add a dismissible tab component to ads
alex-golovanov Jan 17, 2025
aca2541
feat: move useEditableText to hooks
alex-golovanov Jan 20, 2025
a2d7849
feat: add editable entity name template
alex-golovanov Jan 20, 2025
777b8c7
feat: revert to icon component as a prop & fix positioning
alex-golovanov Jan 21, 2025
1a43e3f
feat: add editable dismissible tab template
alex-golovanov Jan 21, 2025
6d3e7ea
chore: accessibility tweaks
alex-golovanov Jan 21, 2025
7c4a5be
chore: remove unnecessary callback
alex-golovanov Jan 22, 2025
d299e5b
chore: change all ads imports to relative paths
alex-golovanov Jan 22, 2025
3d320f1
Merge branch 'release' into feat/37649-entity-tab-component
alex-golovanov Jan 22, 2025
2bec5e8
fix: change path to prevent unit test failure
alex-golovanov Jan 22, 2025
2498569
chore: move types to separate files
alex-golovanov Jan 23, 2025
23c8fbe
chore: move types to separate files
alex-golovanov Jan 23, 2025
b676070
Merge branch 'feat/37649-entity-tab-component' into feat/37692-scroll…
alex-golovanov Jan 23, 2025
2e48d37
perf: add optional ref forwarding
alex-golovanov Jan 27, 2025
5f44034
perf: prevent event bubbling on tab close
alex-golovanov Jan 27, 2025
a011e73
feat: add scrollable tab list
alex-golovanov Jan 27, 2025
cf0324e
Merge branch 'release' into feat/37692-scrollable-tabs-list
alex-golovanov Jan 27, 2025
e4cbb72
Update app/client/packages/design-system/ads/src/DismissibleTab/Dismi…
alex-golovanov Jan 27, 2025
2e82109
feat: added scroll to new tab & added container border omn overflow
alex-golovanov Jan 28, 2025
3c6c283
chore: renaming isStuck to isIntersecting
alex-golovanov Jan 29, 2025
2007e08
perf: scroll to active tab instead of last added
alex-golovanov Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-console */
import React, { useEffect, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";

import {
Expand Down Expand Up @@ -72,20 +72,6 @@ const Template = (props: StoryProps) => {
setActiveTabId(tabId);
};

useEffect(
function scrollAddedTabIntoView() {
const activeTabElement = document.querySelector(".editor-tab.active");

if (activeTabElement) {
activeTabElement.scrollIntoView({
inline: "nearest",
behavior: "smooth",
});
}
},
[activeTabId],
);

return (
<div style={{ width: containerWidth }}>
<DismissibleTabBar disableAdd={disableAdd} onTabAdd={handleTabAdd}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import styled from "styled-components";
import styled, { css } from "styled-components";

import { Button } from "..";

export const Root = styled.div`
export const animatedLeftBorder = (showLeftBorder: boolean) => css`
transition: border-color 0.5s ease;
border-left: 1px solid transparent;

border-left-color: ${showLeftBorder
? "var(--ads-v2-color-border-muted)"
: "transparent"};
`;

export const Root = styled.div<{
$showLeftBorder?: boolean;
}>`
display: flex;
align-items: center;
overflow: hidden;
white-space: nowrap;
position: relative;
height: 32px;

${({ $showLeftBorder }) => animatedLeftBorder($showLeftBorder ?? false)};
`;

export const TabsContainer = styled.div`
Expand All @@ -21,9 +34,10 @@ export const TabsContainer = styled.div`

export const StickySentinel = styled.div`
width: 1px;
height: 100%;
`;

export const PlusButtonContainer = styled.div<{ $isStuck?: boolean }>`
export const PlusButtonContainer = styled.div<{ $showLeftBorder?: boolean }>`
position: sticky;
right: 0;
border: none;
Expand All @@ -33,11 +47,7 @@ export const PlusButtonContainer = styled.div<{ $isStuck?: boolean }>`
align-items: center;
justify-content: center;

${({ $isStuck }) =>
$isStuck &&
`
border-left: 1px solid var(--ads-v2-color-border-muted);
`}
${({ $showLeftBorder }) => animatedLeftBorder($showLeftBorder ?? false)};
`;

export const PlusButton = styled(Button)`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { noop } from "lodash";
import { usePrevious } from "@mantine/hooks";

import { ScrollArea } from "../ScrollArea";

Expand All @@ -23,37 +24,66 @@ export const DismissibleTabBar = ({
disableAdd = false,
onTabAdd,
}: DismissibleTabBarProps) => {
const [isStuck, setIsStuck] = useState(false);
const [isLeftStuck, setIsLeftStuck] = useState(false);
const [isRightStuck, setIsRightStuck] = useState(false);

const containerRef = useRef<HTMLDivElement | null>(null);
const sentinelRef = useRef<HTMLDivElement | null>(null);
const sentinelLeftRef = useRef<HTMLDivElement | null>(null);
const sentinelRightRef = useRef<HTMLDivElement | null>(null);

const totalChildren = React.Children.count(children);
const prevTotalChildren = usePrevious(totalChildren) ?? totalChildren;

const handleAdd = disableAdd ? noop : onTabAdd;

useEffect(function observeSticky() {
if (!containerRef.current || !sentinelRef.current) return;
if (
!containerRef.current ||
!sentinelLeftRef.current ||
!sentinelRightRef.current
)
return;

const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsStuck(false);
} else {
setIsStuck(true);
}
(entries) => {
entries.forEach((entry) => {
if (entry.target === sentinelLeftRef.current) {
setIsLeftStuck(!entry.isIntersecting);
}

if (entry.target === sentinelRightRef.current) {
setIsRightStuck(!entry.isIntersecting);
}
});
},
{
root: containerRef.current,
threshold: 1.0,
},
);

observer.observe(sentinelRef.current);
observer.observe(sentinelLeftRef.current);
observer.observe(sentinelRightRef.current);

return () => observer.disconnect();
}, []);

useEffect(
function scrollAddedTabIntoView() {
const element = sentinelRightRef.current;

if (element && totalChildren > prevTotalChildren) {
element.scrollIntoView({
inline: "center",
behavior: "smooth",
});
}
},
[totalChildren, prevTotalChildren],
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the same as activeTab. A previously added tab, could get active outside of a tabs selection and that would be needed a scroll into view

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's not meant to be active, we only need to scroll to a newly added tab, which will be marked active.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the consumer handles the scroll to an active tab that is not new?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored.


return (
<Styled.Root>
<Styled.Root $showLeftBorder={isLeftStuck}>
<ScrollArea
data-testid="t--editor-tabs"
options={SCROLL_AREA_OPTIONS}
Expand All @@ -62,13 +92,14 @@ export const DismissibleTabBar = ({
style={SCROLL_AREA_STYLE}
>
<Styled.TabsContainer data-testid="t--tabs-container" role="tablist">
<Styled.StickySentinel ref={sentinelLeftRef} />
{children}
<Styled.StickySentinel ref={sentinelRef} />
<Styled.StickySentinel ref={sentinelRightRef} />
</Styled.TabsContainer>
</ScrollArea>
<Styled.PlusButtonContainer $isStuck={isStuck}>
<Styled.PlusButtonContainer $showLeftBorder={isRightStuck}>
<Styled.PlusButton
disabled={disableAdd}
isDisabled={disableAdd}
isIconButton
kind="tertiary"
onClick={handleAdd}
Expand Down
Loading