-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'release' of https://github.com/appsmithorg/appsmith int…
…o chore/eject-cra-retry-3
- Loading branch information
Showing
9 changed files
with
352 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
55 changes: 55 additions & 0 deletions
55
app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
`; |
114 changes: 114 additions & 0 deletions
114
app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
10 changes: 10 additions & 0 deletions
10
app/client/packages/design-system/ads/src/DismissibleTab/DismissibleTabBar.types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
2 changes: 2 additions & 0 deletions
2
app/client/packages/design-system/ads/src/DismissibleTab/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; |
Oops, something went wrong.