diff --git a/app/client/cypress/e2e/Regression/ClientSide/IDE/Focus_retentions_inputs_spec.js b/app/client/cypress/e2e/Regression/ClientSide/IDE/Focus_retentions_inputs_spec.js index dd2eaa491f..15920c5dd5 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/IDE/Focus_retentions_inputs_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/IDE/Focus_retentions_inputs_spec.js @@ -110,7 +110,7 @@ describe("Focus Retention of Inputs", { tags: ["@tag.IDE"] }, function () { cy.setQueryTimeout(10000); EditorNavigation.SelectEntityByName("SQL_Query", EntityType.Query); - cy.get(locators._queryName).should("contain.text", "SQL_Query"); + cy.get(locators._activeEntityTab).should("contain.text", "SQL_Query"); pluginActionForm.toolbar.toggleSettings(); agHelper.GetElement(dataSources._usePreparedStatement).should("be.focused"); EditorNavigation.SelectEntityByName("S3_Query", EntityType.Query); diff --git a/app/client/cypress/e2e/Regression/ClientSide/IDE/Tabs_Navigation_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/IDE/Tabs_Navigation_spec.ts index 1e56f501e1..d1635779a4 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/IDE/Tabs_Navigation_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/IDE/Tabs_Navigation_spec.ts @@ -53,13 +53,13 @@ describe("Tabs Navigation", { tags: ["@tag.IDE"] }, () => { agHelper.GetNClick(editorTabSelector("page1_query1")); agHelper - .GetElement(locators._queryName) + .GetElement(locators._activeEntityTab) .should("have.text", "Page1_Query1"); agHelper.GetNClick(editorTabSelector("page1_query2")); agHelper - .GetElement(locators._queryName) + .GetElement(locators._activeEntityTab) .should("have.text", "Page1_Query2"); }); @@ -93,13 +93,13 @@ describe("Tabs Navigation", { tags: ["@tag.IDE"] }, () => { agHelper.GetNClick(editorTabSelector("page2_query1")); agHelper - .GetElement(locators._queryName) + .GetElement(locators._activeEntityTab) .should("have.text", "Page2_Query1"); agHelper.GetNClick(editorTabSelector("page2_query2")); agHelper - .GetElement(locators._queryName) + .GetElement(locators._activeEntityTab) .should("have.text", "Page2_Query2"); }); @@ -108,13 +108,13 @@ describe("Tabs Navigation", { tags: ["@tag.IDE"] }, () => { agHelper.GetNClick(editorTabSelector("page1_query1")); agHelper - .GetElement(locators._queryName) + .GetElement(locators._activeEntityTab) .should("have.text", "Page1_Query1"); agHelper.GetNClick(editorTabSelector("page1_query2")); agHelper - .GetElement(locators._queryName) + .GetElement(locators._activeEntityTab) .should("have.text", "Page1_Query2"); PageLeftPane.switchSegment(PagePaneSegment.JS); diff --git a/app/client/cypress/e2e/Regression/ClientSide/JSObject/RenameJSObjectBug38207_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/JSObject/RenameJSObjectBug38207_spec.ts index bb3018186f..232aa96c93 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/JSObject/RenameJSObjectBug38207_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/JSObject/RenameJSObjectBug38207_spec.ts @@ -12,7 +12,7 @@ describe( jsEditor.RenameJSObjectFromContextMenu("ChangedName1"); // Validate the new name of the JS Object - cy.get(jsEditor.listOfJsObjects).eq(0).contains("ChangedName1"); + cy.get(jsEditor.listOfJsDismissibleTabs).eq(0).contains("ChangedName1"); // Create second JS file jsEditor.CreateJSObject("", { prettify: false, toRun: false }); @@ -24,7 +24,7 @@ describe( jsEditor.RenameJSObjectFromContextMenu("ChangedName3"); // Validate the new name of the 3rd JS Objcte - cy.get(jsEditor.listOfJsObjects).eq(2).contains("ChangedName3"); + cy.get(jsEditor.listOfJsDismissibleTabs).eq(2).contains("ChangedName3"); }); }, ); diff --git a/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts b/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts index 5cb3cfb1b0..42bd6a552d 100644 --- a/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts +++ b/app/client/cypress/e2e/Regression/ServerSide/Datasources/Oracle_Spec.ts @@ -446,7 +446,7 @@ describe( subAction: "Page1", toastToValidate: "copied to page", }); - agHelper.GetNAssertContains(locators._queryName, "Query1Copy"); + agHelper.GetNAssertContains(locators._activeEntityTab, "Query1Copy"); dataSources.runQueryAndVerifyResponseViews({ count: 2 }); PageList.AddNewPage(); EditorNavigation.SelectEntityByName("Page1", EntityType.Page); @@ -456,7 +456,7 @@ describe( toastToValidate: "moved to page", }); agHelper.WaitUntilAllToastsDisappear(); - agHelper.GetNAssertContains(locators._queryName, "Query1Copy"); + agHelper.GetNAssertContains(locators._activeEntityTab, "Query1Copy"); dataSources.runQueryAndVerifyResponseViews({ count: 2 }); agHelper.ActionContextMenuWithInPane({ action: "Delete", diff --git a/app/client/cypress/support/ApiCommands.js b/app/client/cypress/support/ApiCommands.js index 3b77c7eea3..6136d917e7 100644 --- a/app/client/cypress/support/ApiCommands.js +++ b/app/client/cypress/support/ApiCommands.js @@ -181,9 +181,9 @@ Cypress.Commands.add("createAndFillApi", (url, parameters) => { dataSources.NavigateToDSCreateNew(); cy.testCreateApiButton(); cy.get("@createNewApi").then((response) => { - cy.get(locator._queryName).should("be.visible"); + cy.get(locator._activeEntityTab).should("be.visible"); expect(response.response.body.responseMeta.success).to.eq(true); - cy.get(locator._queryName) + cy.get(locator._activeEntityTab) .invoke("text") .then((text) => { const someText = text; diff --git a/app/client/cypress/support/Objects/CommonLocators.ts b/app/client/cypress/support/Objects/CommonLocators.ts index 2c2d7c6da4..f10c896d3b 100644 --- a/app/client/cypress/support/Objects/CommonLocators.ts +++ b/app/client/cypress/support/Objects/CommonLocators.ts @@ -10,8 +10,8 @@ export class CommonLocators { _link = ".ads-v2-link"; _btnSpinner = ".ads-v2-spinner"; _sidebar = ".t--sidebar"; - _queryName = ".editor-tab.active > .ads-v2-text"; - _queryNameTxt = ".editor-tab.active > .ads-v2-text input"; + _activeEntityTab = ".editor-tab.active .ads-v2-text"; + _activeEntityTabInput = ".editor-tab.active .ads-v2-text input"; _editIcon = ".t--action-name-edit-icon"; _emptyCanvasCta = "[data-testid='canvas-ctas']"; _dsName = ".t--edit-datasource-name span"; diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 970192e1d9..e91fc6fd52 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -270,8 +270,8 @@ export class AggregateHelper { public RenameQuery(renameVal: string, willFailError?: string) { this.rename({ - nameLocator: this.locator._queryName, - textInputLocator: this.locator._queryNameTxt, + nameLocator: this.locator._activeEntityTab, + textInputLocator: this.locator._activeEntityTabInput, renameVal, dblClick: true, willFailError, @@ -1162,7 +1162,7 @@ export class AggregateHelper { public GetObjectName() { //cy.get(this.locator._queryName).invoke("text").then((text) => cy.wrap(text).as("queryName")); or below syntax - return cy.get(this.locator._queryName).invoke("text"); + return cy.get(this.locator._activeEntityTab).invoke("text"); } public GetElementLength(selector: string) { diff --git a/app/client/cypress/support/Pages/ApiPage.ts b/app/client/cypress/support/Pages/ApiPage.ts index 2ee47618bd..cc35866559 100644 --- a/app/client/cypress/support/Pages/ApiPage.ts +++ b/app/client/cypress/support/Pages/ApiPage.ts @@ -105,7 +105,7 @@ export class ApiPage { public settingsTriggerLocator = "[data-testid='t--js-settings-trigger']"; public splitPaneContextMenuTrigger = ".entity-context-menu"; public moreActionsTrigger = "[data-testid='t--more-action-trigger']"; - private apiNameInput = ".editor-tab.active > .ads-v2-text input"; + private apiNameInput = this.locator._activeEntityTabInput; public pageList = ".ads-v2-sub-menu > .ads-v2-menu__menu-item"; CreateApi( diff --git a/app/client/cypress/support/Pages/JSEditor.ts b/app/client/cypress/support/Pages/JSEditor.ts index d66172fe03..88d268b233 100644 --- a/app/client/cypress/support/Pages/JSEditor.ts +++ b/app/client/cypress/support/Pages/JSEditor.ts @@ -36,7 +36,8 @@ export class JSEditor { public settingsTriggerLocator = "[data-testid='t--js-settings-trigger']"; public contextMenuTriggerLocator = "[data-testid='t--more-action-trigger']"; public runFunctionSelectLocator = "[data-testid='t--js-function-run']"; - public listOfJsObjects = "[data-testid='t--tabs-container']>div>span"; + public listOfJsDismissibleTabs = + "[data-testid='t--tabs-container'] .editor-tab"; public toolbar = new PluginEditorToolbar( this.runButtonLocator, @@ -55,8 +56,8 @@ export class JSEditor { private _onPageLoadSwitchStatus = (functionName: string) => `//div[contains(@class, '${functionName}-on-page-load-setting')]//label/input`; - private _jsObjName = ".editor-tab.active > .ads-v2-text"; - public _jsObjTxt = ".editor-tab.active > .ads-v2-text input"; + private _jsObjName = this.locator._activeEntityTab; + public _jsObjTxt = this.locator._activeEntityTabInput; public _newJSobj = "span:contains('New JS object')"; private _bindingsClose = ".t--entity-property-close"; public _propertyList = ".binding"; diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.stories.tsx b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.stories.tsx index 0327ddedc1..f8907bfb45 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.stories.tsx +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.stories.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import type { Meta, StoryObj } from "@storybook/react"; -import { DismissibleTab } from "."; +import { DismissibleTab } from "./DismissibleTab"; const meta: Meta = { title: "ADS/Components/Dismissible Tab", diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.styles.ts b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.styles.ts index 42abfbe698..4cf1d86d0b 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.styles.ts +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.styles.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; -import { Button as ADSButton } from ".."; +import { Button as ADSButton } from "../Button"; export const Tab = styled.div` position: relative; @@ -9,7 +9,8 @@ export const Tab = styled.div` justify-content: center; flex-shrink: 0; gap: var(--ads-v2-spaces-2); - height: 100%; + min-height: 32px; + max-height: 32px; font-size: 12px; color: var(--ads-v2-color-fg); cursor: pointer; @@ -20,8 +21,7 @@ export const Tab = styled.div` border-right: 1px solid transparent; border-top: 3px solid transparent; - padding: var(--ads-v2-spaces-3); - padding-top: 6px; + padding: 0 var(--ads-v2-spaces-3) 2px var(--ads-v2-spaces-3); &.active { background: var(--ads-v2-colors-control-field-default-bg); diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.tsx b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.tsx index f424baddb8..ebff3e91d9 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.tsx +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.tsx @@ -2,7 +2,7 @@ import React, { useCallback } from "react"; import clsx from "classnames"; -import { Icon } from ".."; +import { Icon } from "../Icon"; import * as Styled from "./DismissibleTab.styles"; import { DATA_TEST_ID } from "./constants"; diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.types.ts b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.types.ts index 3a88343855..dbd084a397 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.types.ts +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTab.types.ts @@ -1,10 +1,18 @@ import type React from "react"; export interface DismissibleTabProps { + /** The content of the tab. */ children: React.ReactNode; + /** Used for custom styling, necessary for styled-components. */ + className?: string; + /** Used for passing data-testid. */ dataTestId?: string; + /** Applies active styling. */ isActive?: boolean; + /** Callback when the tab is clicked. */ onClick: () => void; + /** Callback when tab is closed. */ onClose: (e: React.MouseEvent) => void; + /** Callback when tab is double clicked. */ onDoubleClick?: () => void; } diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.stories.tsx b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.stories.tsx index 96a4ddd2ec..224ba1e553 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.stories.tsx +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.stories.tsx @@ -2,11 +2,9 @@ import React, { useRef, useState } from "react"; import type { Meta, StoryObj } from "@storybook/react"; -import { - DismissibleTab, - DismissibleTabBar, - type DismissibleTabBarProps, -} from "."; +import { DismissibleTab } from "./DismissibleTab"; +import { DismissibleTabBar } from "./DismissibleTabBar"; +import { type DismissibleTabBarProps } from "./DismissibleTabBar.types"; const meta: Meta = { title: "ADS/Components/Dismissible Tab Bar", diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.styles.ts b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.styles.ts index 92109d5c3a..59ee5aa0ae 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.styles.ts +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.styles.ts @@ -1,6 +1,6 @@ import styled, { css } from "styled-components"; -import { Button } from ".."; +import { Button } from "../Button"; export const animatedLeftBorder = (showLeftBorder: boolean) => css` transition: border-color 0.5s ease; @@ -16,10 +16,11 @@ export const Root = styled.div<{ }>` display: flex; align-items: center; - overflow: hidden; + overflow-x: hidden; white-space: nowrap; position: relative; - height: 32px; + max-height: 32px; + min-height: 32px; ${({ $showLeftBorder }) => animatedLeftBorder($showLeftBorder ?? false)}; `; @@ -28,7 +29,6 @@ export const TabsContainer = styled.div` display: flex; flex: 1 0 auto; align-items: center; - gap: var(--ads-v2-spaces-2); height: 100%; `; @@ -41,11 +41,11 @@ export const PlusButtonContainer = styled.div<{ $showLeftBorder?: boolean }>` position: sticky; right: 0; border: none; - min-width: 32px; - height: 100%; + min-width: 28px; + min-height: 32px; display: flex; align-items: center; - justify-content: center; + justify-content: end; ${({ $showLeftBorder }) => animatedLeftBorder($showLeftBorder ?? false)}; `; diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.tsx b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.tsx index 08e1dfc145..dd975200a8 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.tsx +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.tsx @@ -1,6 +1,8 @@ import React, { useEffect, useRef, useState } from "react"; import { noop } from "lodash"; +import clsx from "clsx"; +import { Spinner } from "../Spinner"; import { ScrollArea } from "../ScrollArea"; import * as Styled from "./DismissibleTabBar.styles"; @@ -14,13 +16,15 @@ export const SCROLL_AREA_OPTIONS = { } as const; const SCROLL_AREA_STYLE = { - height: 34, - top: 1, + height: 32, }; export const DismissibleTabBar = ({ children, + className, disableAdd = false, + hideAdd = false, + isAddingNewTab, onTabAdd, }: DismissibleTabBarProps) => { const [isLeftIntersecting, setIsLeftIntersecting] = useState(false); @@ -85,7 +89,10 @@ export const DismissibleTabBar = ({ ); return ( - + - - - + {!hideAdd && ( + + {isAddingNewTab ? ( + + ) : ( + + )} + + )} ); }; diff --git a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.types.ts b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.types.ts index 990400b595..e936acbdf1 100644 --- a/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.types.ts +++ b/app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.types.ts @@ -2,9 +2,19 @@ import type React from "react"; import type { DismissibleTabProps } from "./DismissibleTab.types"; export interface DismissibleTabBarProps { + /** The content of the tab bar. */ children: | React.ReactElement - | React.ReactElement[]; - onTabAdd: () => void; + | React.ReactElement[] + | React.ReactNode; + /** Used for custom styling, necessary for styled-components. */ + className?: string; + /** Button is visible, but disabled & not clickable. */ disableAdd?: boolean; + /** Hides add button completely. */ + hideAdd?: boolean; + /** Will display a loader in place of add button. */ + isAddingNewTab?: boolean; + /** Callback tab is added. */ + onTabAdd: () => void; } diff --git a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.stories.tsx b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.stories.tsx index 7fbcf68792..ecfdf11ce9 100644 --- a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.stories.tsx +++ b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.stories.tsx @@ -2,8 +2,8 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; -import { EditableDismissibleTab } from "."; -import { Icon } from "../.."; +import { EditableDismissibleTab } from "./EditableDismissibleTab"; +import { Icon } from "../../Icon"; const meta: Meta = { title: "ADS/Templates/Editable Dismissible Tab", @@ -24,7 +24,7 @@ export const Basic: Story = { dataTestId: "t--dismissible-tab", icon: JSIcon(), name: "Hello", - canEdit: true, + isEditable: true, onNameSave: console.log, validateName: (name: string) => diff --git a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx index 6ba119f092..06b9709ab5 100644 --- a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx +++ b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.tsx @@ -2,33 +2,38 @@ import React from "react"; import { noop } from "lodash"; import { useBoolean } from "usehooks-ts"; -import { DismissibleTab } from "../.."; -import { EditableEntityName } from ".."; +import { DismissibleTab } from "../../DismissibleTab"; +import { EditableEntityName } from "../EditableEntityName"; import type { EditableDismissibleTabProps } from "./EditableDismissibleTab.types"; export const EditableDismissibleTab = (props: EditableDismissibleTabProps) => { const { - canEdit, dataTestId, icon, isActive, isEditable = true, + isEditing: propIsEditing, isLoading, name, onClick, onClose, + onEnterEditMode: propOnEnterEditMode, + onExitEditMode: propOnExitEditMode, onNameSave, validateName, } = props; const { - setFalse: exitEditMode, - setTrue: enterEditMode, - value: isEditing, + setFalse: localOnExitEditMode, + setTrue: localOnEnterEditMode, + value: localIsEditing, } = useBoolean(false); - const handleDoubleClick = isEditable ? enterEditMode : noop; + const isEditing = propIsEditing ?? localIsEditing; + const handleEnterEditMode = propOnEnterEditMode ?? localOnEnterEditMode; + const handleExitEditMode = propOnExitEditMode ?? localOnExitEditMode; + const handleDoubleClick = isEditable ? handleEnterEditMode : noop; return ( { onDoubleClick={handleDoubleClick} > diff --git a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.types.ts b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.types.ts index 37cd5a8081..f2cdbb8db2 100644 --- a/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.types.ts +++ b/app/client/packages/design-system/ads/src/Templates/EditableDismissibleTab/EditableDismissibleTab.types.ts @@ -1,15 +1,31 @@ import type React from "react"; export interface EditableDismissibleTabProps { + /** Used for passing data-testid. */ dataTestId?: string; + /** Icon component to be displayed in the tab. */ icon: React.ReactNode; + /** Passed to tab component, applies active state/styles. */ isActive: boolean; + /** Controls if tab can be edited. */ isEditable?: boolean; + /** Can be passed to control editing state externally. */ + isEditing?: boolean; + /** Shows loading indicator in place of an icon. */ isLoading: boolean; + /** The name of the tab. */ name: string; + + /** Callback when the tab is clicked. */ onClick: () => void; + /** Callback when tab is closed. */ onClose: () => void; + /** Callback when tab enters edit mode. */ + onEnterEditMode?: () => void; + /** Callback when tab exits edit mode. */ + onExitEditMode?: () => void; + /** Callback when tab name is saved. */ onNameSave: (name: string) => void; + /** Function to validate the name. */ validateName: (name: string) => string | null; - canEdit: boolean; } diff --git a/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.tsx b/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.tsx index 576503b71e..7037649ca1 100644 --- a/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.tsx +++ b/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.tsx @@ -45,7 +45,7 @@ export const EditableEntityName = (props: EditableEntityNameProps) => { } return icon; - }, [isLoading, icon]); + }, [isLoading, icon, size]); const inputProps = useMemo( () => ({ diff --git a/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.types.ts b/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.types.ts index 6f8ac33ab4..e51d4350ff 100644 --- a/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.types.ts +++ b/app/client/packages/design-system/ads/src/Templates/EditableEntityName/EditableEntityName.types.ts @@ -1,15 +1,26 @@ import type React from "react"; export interface EditableEntityNameProps { - icon: React.ReactNode; - inputTestId?: string; - isEditing: boolean; - isLoading?: boolean; - name: string; - onExitEditing: () => void; - onNameSave: (name: string) => void; - validateName: (name: string) => string | null; + /** Controls if name can be edited. */ canEdit: boolean; + /** Icon component. */ + icon: React.ReactNode; + /** Used for passing data-testid to input. */ + inputTestId?: string; + /** Toggles editing mode. */ + isEditing: boolean; + /** Controls if name is fixed width. */ isFixedWidth?: boolean; + /** Shows loading indicator in place of an icon. */ + isLoading?: boolean; + /** The name of the entity. */ + name: string; + /** Size of the icon & input. */ size?: "small" | "medium"; + /** Callback when editing is exited. */ + onExitEditing: () => void; + /** Callback when name is saved. */ + onNameSave: (name: string) => void; + /** Function to validate the name. */ + validateName: (name: string) => string | null; } diff --git a/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.stories.tsx b/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.stories.tsx index 0bba13528b..b773469b44 100644 --- a/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.stories.tsx +++ b/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.stories.tsx @@ -3,9 +3,14 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { useBoolean } from "usehooks-ts"; -import { DismissibleTab, DismissibleTabBar } from "../.."; +import { DismissibleTab } from "../../DismissibleTab"; -import { EntityTabsHeader, EntityListButton, ToggleScreenModeButton } from "."; +import { + EntityTabBar, + EntityTabsHeader, + EntityListButton, + ToggleScreenModeButton, +} from "./EntityTabsHeader"; const meta: Meta = { title: "ADS/Templates/Entity Tabs Header", @@ -25,7 +30,7 @@ const Template = ({ width }: Args) => {
- + One @@ -41,7 +46,7 @@ const Template = ({ width }: Args) => { Five - + { return ; @@ -28,6 +29,10 @@ export const ToggleScreenModeButton = (props: ToggleScreenModeButtonProps) => { ); }; +export const EntityTabBar = (props: DismissibleTabBarProps) => { + return ; +}; + export function EntityTabsHeader({ children }: EntityTabsHeaderProps) { return {children}; } diff --git a/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.types.ts b/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.types.ts index ac9fb814d0..26297f96fc 100644 --- a/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.types.ts +++ b/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/EntityTabsHeader.types.ts @@ -1,9 +1,8 @@ import type { ReactElement, ReactNode } from "react"; -import type { - ToggleButtonProps, - ButtonProps, - DismissibleTabBarProps, -} from "../.."; + +import type { DismissibleTabBarProps } from "../../DismissibleTab"; +import type { ToggleButtonProps } from "../../ToggleButton"; +import type { ButtonProps } from "../../Button"; export type EntityListButtonProps = Omit; @@ -17,11 +16,15 @@ export type ToggleScreenModeButtonProps = Omit< type DismissibleTabBarType = ReactElement; type EntityListButtonType = ReactElement; +/** Required for optional/conditional children. */ +type OptionalChild = T | null | false; + export interface EntityTabsHeaderProps { children: | DismissibleTabBarType - | [DismissibleTabBarType] - | [EntityListButtonType, DismissibleTabBarType] - | [DismissibleTabBarType, ReactNode] - | [EntityListButtonType, DismissibleTabBarType, ReactNode]; + | [ + OptionalChild, + DismissibleTabBarType, + OptionalChild, + ]; } diff --git a/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/index.ts b/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/index.ts index 8c4033e0d6..0949d4a41d 100644 --- a/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/index.ts +++ b/app/client/packages/design-system/ads/src/Templates/EntityTabsHeader/index.ts @@ -1,5 +1,6 @@ export { EntityTabsHeader, EntityListButton, + EntityTabBar, ToggleScreenModeButton, } from "./EntityTabsHeader"; diff --git a/app/client/packages/design-system/ads/src/Templates/index.ts b/app/client/packages/design-system/ads/src/Templates/index.ts index 4b7b197c82..119bfd669a 100644 --- a/app/client/packages/design-system/ads/src/Templates/index.ts +++ b/app/client/packages/design-system/ads/src/Templates/index.ts @@ -3,3 +3,4 @@ export * from "./EntityExplorer"; export * from "./Sidebar"; export * from "./EditableEntityName"; export * from "./EditableDismissibleTab"; +export * from "./EntityTabsHeader"; diff --git a/app/client/src/IDE/Components/FileTab/FileTab.test.tsx b/app/client/src/IDE/Components/FileTab/FileTab.test.tsx deleted file mode 100644 index 6650b2161a..0000000000 --- a/app/client/src/IDE/Components/FileTab/FileTab.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable react-perf/jsx-no-new-object-as-prop */ -/* eslint-disable react-perf/jsx-no-jsx-as-prop */ -import "@testing-library/jest-dom"; -import React from "react"; -import { render, fireEvent } from "@testing-library/react"; - -import { Icon } from "@appsmith/ads"; -import { FileTab } from "./FileTab"; -import { DATA_TEST_ID } from "./constants"; - -describe("FileTab", () => { - const mockOnClick = jest.fn(); - const mockOnClose = jest.fn(); - const mockOnDoubleClick = jest.fn(); - - const TITLE = "test_file"; - const TabIcon = () => ; - - const setup = () => { - const utils = render( - - - {TITLE} - , - ); - const tabElement = utils.getByText(TITLE); - - return { - tabElement, - ...utils, - }; - }; - - test("renders component", () => { - const { getByTestId, tabElement } = setup(); - - fireEvent.click(tabElement); - expect(mockOnClick).toHaveBeenCalled(); - - const closeButton = getByTestId(DATA_TEST_ID.CLOSE_BUTTON); - - fireEvent.click(closeButton); - expect(mockOnClose).toHaveBeenCalled(); - }); - - test("double click event is fired", () => { - const { getByTestId, tabElement } = setup(); - - fireEvent.doubleClick(tabElement); - - expect(mockOnDoubleClick).toHaveBeenCalled(); - - const closeButton = getByTestId(DATA_TEST_ID.CLOSE_BUTTON); - - fireEvent.click(closeButton); - expect(mockOnClose).toHaveBeenCalled(); - }); -}); diff --git a/app/client/src/IDE/Components/FileTab/FileTab.tsx b/app/client/src/IDE/Components/FileTab/FileTab.tsx deleted file mode 100644 index 193b5323b5..0000000000 --- a/app/client/src/IDE/Components/FileTab/FileTab.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; - -import clsx from "classnames"; - -import { Icon } from "@appsmith/ads"; -import { sanitizeString } from "utils/URLUtils"; - -import * as Styled from "./styles"; -import { DATA_TEST_ID } from "./constants"; - -export interface FileTabProps { - isActive: boolean; - title: string; - onClick: () => void; - onClose: (e: React.MouseEvent) => void; - children: React.ReactNode; - onDoubleClick?: () => void; -} - -export const FileTab = ({ - children, - isActive, - onClick, - onClose, - onDoubleClick, - title, -}: FileTabProps) => { - return ( - - {children} - - - - - ); -}; diff --git a/app/client/src/IDE/Components/FileTab/constants.ts b/app/client/src/IDE/Components/FileTab/constants.ts deleted file mode 100644 index 4408a5b065..0000000000 --- a/app/client/src/IDE/Components/FileTab/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const DATA_TEST_ID = { - INPUT: "t--ide-tab-editable-input", - CLOSE_BUTTON: "t--tab-close-btn", - SPINNER: "t--ide-tab-spinner", -}; diff --git a/app/client/src/IDE/Components/FileTab/index.ts b/app/client/src/IDE/Components/FileTab/index.ts deleted file mode 100644 index 3a8ba9834e..0000000000 --- a/app/client/src/IDE/Components/FileTab/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { FileTab } from "./FileTab"; -export type { FileTabProps } from "./FileTab"; diff --git a/app/client/src/IDE/Components/FileTab/styles.tsx b/app/client/src/IDE/Components/FileTab/styles.tsx deleted file mode 100644 index 79b2e1b258..0000000000 --- a/app/client/src/IDE/Components/FileTab/styles.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import styled from "styled-components"; - -import { Text as ADSText } from "@appsmith/ads"; - -export const Tab = styled.div` - display: flex; - height: 100%; - position: relative; - font-size: 12px; - color: var(--ads-v2-colors-text-default); - cursor: pointer; - gap: var(--ads-v2-spaces-2); - border-top-left-radius: var(--ads-v2-border-radius); - border-top-right-radius: var(--ads-v2-border-radius); - align-items: center; - justify-content: center; - padding: var(--ads-v2-spaces-3); - padding-top: 6px; // to accommodate border and make icons align correctly - border-left: 1px solid transparent; - border-right: 1px solid transparent; - border-top: 3px solid transparent; - flex-shrink: 0; - - &.active { - background: var(--ads-v2-colors-control-field-default-bg); - border-top-color: var(--ads-v2-color-bg-brand); - border-left-color: var(--ads-v2-color-border-muted); - border-right-color: var(--ads-v2-color-border-muted); - - span { - font-weight: var(--ads-v2-font-weight-bold); - } - } - - & > .tab-close { - position: relative; - right: -2px; - visibility: hidden; - } - - &:hover > .tab-close, - &.active > .tab-close { - visibility: visible; - } -`; - -export const IconContainer = styled.div` - height: 12px; - width: 12px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - - img { - width: 12px; - } -`; - -export const Text = styled(ADSText)` - min-width: 3ch; - padding: 0 var(--ads-v2-spaces-1); -`; - -export const CloseButton = styled.button` - border-radius: var(--ads-v2-border-radius); - cursor: pointer; - padding: var(--ads-v2-spaces-1); - - &:hover { - background: var(--ads-v2-colors-action-tertiary-surface-hover-bg); - } -`; diff --git a/app/client/src/pages/Editor/IDE/EditorTabs/AddButton.tsx b/app/client/src/pages/Editor/IDE/EditorTabs/AddButton.tsx deleted file mode 100644 index b6cefda063..0000000000 --- a/app/client/src/pages/Editor/IDE/EditorTabs/AddButton.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import { Flex, Spinner, Button } from "@appsmith/ads"; -import { useCurrentEditorState, useIDETabClickHandlers } from "../hooks"; -import { useIsJSAddLoading } from "ee/pages/Editor/IDE/EditorPane/JS/hooks"; -import { EditorEntityTabState } from "ee/entities/IDE/constants"; - -const AddButton = () => { - const { addClickHandler } = useIDETabClickHandlers(); - const isJSLoading = useIsJSAddLoading(); - const { segmentMode } = useCurrentEditorState(); - - if (segmentMode === EditorEntityTabState.Add) { - return null; - } - - if (isJSLoading) { - return ( - - - - ); - } - - return ( -