Skip to content

Commit

Permalink
Merge branch 'release' of https://github.com/appsmithorg/appsmith int…
Browse files Browse the repository at this point in the history
…o chore/eject-cra-retry-3
  • Loading branch information
riodeuno committed Jan 30, 2025
2 parents f538f4b + 3ab237d commit 2853c1f
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "../../../../support/Objects/ObjectsCore";
import { EntityItems } from "../../../../support/Pages/AssertHelper";

describe("Slug URLs", () => {
describe("Slug URLs", { tags: ["@tag.AppUrl"] }, () => {
let applicationName;
let applicationId;

Expand Down Expand Up @@ -145,10 +145,9 @@ describe("Slug URLs", () => {
it("4. Checks redirect url", () => {
cy.url().then((url) => {
homePage.Signout(true);
agHelper.VisitNAssert(url + "?embed=true&a=b"); //removing 'getConsolidatedData' api check due to its flakyness
agHelper.AssertURL(
`?redirectUrl=${encodeURIComponent(url + "?embed=true&a=b")}`,
);
const redirectUrl = `${url}?embed=true&a=b`;
agHelper.VisitNAssert(redirectUrl);
agHelper.AssertURL(`?redirectUrl=${encodeURIComponent(redirectUrl)}`);
});
});
});
1 change: 1 addition & 0 deletions app/client/cypress/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
"@tag.Anvil",
"@tag.Audio",
"@tag.Auditlogs",
"@tag.AppUrl",
"@tag.Authentication",
"@tag.AutoHeight",
"@tag.Binding",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useCallback } from "react";

import clsx from "classnames";

Expand All @@ -17,6 +17,15 @@ export const DismissibleTab = ({
onClose,
onDoubleClick,
}: DismissibleTabProps) => {
const handleClose = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
onClose(e);
},
[onClose],
);

return (
<Styled.Tab
className={clsx("editor-tab", isActive && "active")}
Expand All @@ -31,7 +40,7 @@ export const DismissibleTab = ({
data-testid={DATA_TEST_ID.CLOSE_BUTTON}
isIconButton
kind="tertiary"
onClick={onClose}
onClick={handleClose}
role="tab"
size="sm"
tabIndex={0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* eslint-disable no-console */
import React, { useRef, useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";

import {
DismissibleTab,
DismissibleTabBar,
type DismissibleTabBarProps,
} from ".";

const meta: Meta<typeof DismissibleTabBar> = {
title: "ADS/Components/Dismissible Tab Bar",
component: DismissibleTabBar,
};

export default meta;

const createTabs = (count: number) => {
const tabs = [];

for (let i = 1; i <= count; i++) {
tabs.push({ id: `tab${i}`, label: `Tab ${i}` });
}

return tabs;
};

const INITIAL_TAB_COUNT = 5;
const initialTabs = createTabs(INITIAL_TAB_COUNT);

interface StoryProps extends DismissibleTabBarProps {
containerWidth: number;
}

const Template = (props: StoryProps) => {
const { containerWidth, disableAdd } = props;

const tabCountRef = useRef<number>(INITIAL_TAB_COUNT);
const [tabs, setTabs] = useState(initialTabs);
const [activeTabId, setActiveTabId] = useState(initialTabs[0].id);

const handleClose = (tabId: string) => () => {
const closedTabIndex = tabs.findIndex((tab) => tab.id === tabId);
const filteredTabs = tabs.filter((tab) => tab.id !== tabId);

setTabs(filteredTabs);

if (activeTabId === tabId && filteredTabs.length) {
if (closedTabIndex >= filteredTabs.length) {
const nextIndex = Math.max(0, closedTabIndex - 1);
const nextTab = filteredTabs[nextIndex];

setActiveTabId(nextTab.id);
} else {
const nextTab = filteredTabs[closedTabIndex];

setActiveTabId(nextTab.id);
}
}
};

const handleClick = (tabId: string) => () => {
setActiveTabId(tabId);
};

const handleTabAdd = () => {
const tabNumber = ++tabCountRef.current;
const tabId = `tab${tabNumber}`;
const nextTabs = [...tabs, { id: tabId, label: `Tab ${tabNumber}` }];

setTabs(nextTabs);
setActiveTabId(tabId);
};

return (
<div style={{ width: containerWidth }}>
<DismissibleTabBar disableAdd={disableAdd} onTabAdd={handleTabAdd}>
{tabs.map((tab) => (
<DismissibleTab
isActive={tab.id === activeTabId}
key={tab.id}
onClick={handleClick(tab.id)}
onClose={handleClose(tab.id)}
>
{tab.label}
</DismissibleTab>
))}
</DismissibleTabBar>
</div>
);
};

export const Basic = Template.bind({}) as StoryObj;

Basic.argTypes = {
containerWidth: {
control: { type: "range", min: 200, max: 600, step: 10 },
},
};

Basic.args = {
disableAdd: false,
containerWidth: 450,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import styled, { css } from "styled-components";

import { Button } from "..";

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`
display: flex;
flex: 1 0 auto;
align-items: center;
gap: var(--ads-v2-spaces-2);
height: 100%;
`;

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

export const PlusButtonContainer = styled.div<{ $showLeftBorder?: boolean }>`
position: sticky;
right: 0;
border: none;
min-width: 32px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
${({ $showLeftBorder }) => animatedLeftBorder($showLeftBorder ?? false)};
`;

export const PlusButton = styled(Button)`
min-width: 24px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useEffect, useRef, useState } from "react";
import { noop } from "lodash";

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

import * as Styled from "./DismissibleTabBar.styles";
import type { DismissibleTabBarProps } from "./DismissibleTabBar.types";

export const SCROLL_AREA_OPTIONS = {
overflow: {
x: "scroll",
y: "hidden",
},
} as const;

const SCROLL_AREA_STYLE = {
height: 34,
top: 1,
};

export const DismissibleTabBar = ({
children,
disableAdd = false,
onTabAdd,
}: DismissibleTabBarProps) => {
const [isLeftIntersecting, setIsLeftIntersecting] = useState(false);
const [isRightIntersecting, setIsRightIntersecting] = useState(false);

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

const handleAdd = disableAdd ? noop : onTabAdd;

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

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.target === sentinelLeftRef.current) {
setIsLeftIntersecting(!entry.isIntersecting);
}

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

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

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

useEffect(
function debouncedScrollActiveTabIntoView() {
const timerId = setTimeout(() => {
// accessing active tab with a document query is a bit hacky, but it's more performant than keeping a map of refs and cloning children
const activeTab = document.querySelector(".editor-tab.active");

if (activeTab) {
activeTab.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center",
});
}
}, 100);

return () => clearTimeout(timerId);
},
[children],
);

return (
<Styled.Root $showLeftBorder={isLeftIntersecting}>
<ScrollArea
data-testid="t--editor-tabs"
options={SCROLL_AREA_OPTIONS}
ref={containerRef}
size="sm"
style={SCROLL_AREA_STYLE}
>
<Styled.TabsContainer data-testid="t--tabs-container" role="tablist">
<Styled.StickySentinel ref={sentinelLeftRef} />
{children}
<Styled.StickySentinel ref={sentinelRightRef} />
</Styled.TabsContainer>
</ScrollArea>
<Styled.PlusButtonContainer $showLeftBorder={isRightIntersecting}>
<Styled.PlusButton
isDisabled={disableAdd}
isIconButton
kind="tertiary"
onClick={handleAdd}
startIcon="add-line"
title="Add new tab"
/>
</Styled.PlusButtonContainer>
</Styled.Root>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type React from "react";
import type { DismissibleTabProps } from "./DismissibleTab.types";

export interface DismissibleTabBarProps {
children:
| React.ReactElement<DismissibleTabProps>
| React.ReactElement<DismissibleTabProps>[];
onTabAdd: () => void;
disableAdd?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { DismissibleTab } from "./DismissibleTab";
export type { DismissibleTabProps } from "./DismissibleTab.types";
export { DismissibleTabBar } from "./DismissibleTabBar";
export type { DismissibleTabBarProps } from "./DismissibleTabBar.types";
Loading

0 comments on commit 2853c1f

Please sign in to comment.