Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1,21 @@
/* eslint-disable no-console */
import type { Meta, StoryObj } from "@storybook/react";

import { DismissibleTab } from ".";

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

export default meta;

type Story = StoryObj<typeof DismissibleTab>;

export const Basic: Story = {
args: {
isActive: true,
dataTestId: "t--dismissible-tab",
children: "Dismissible tab",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import styled from "styled-components";

import { Button as ADSButton } from "..";

export const Tab = styled.div`
position: relative;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
gap: var(--ads-v2-spaces-2);
height: 100%;
font-size: 12px;
color: var(--ads-v2-color-fg);
cursor: pointer;

border-top-left-radius: var(--ads-v2-border-radius);
border-top-right-radius: var(--ads-v2-border-radius);
border-left: 1px solid transparent;
border-right: 1px solid transparent;
border-top: 3px solid transparent;

padding: var(--ads-v2-spaces-3);
padding-top: 6px;

&.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 {
opacity: 0;
transition: opacity 0.2s ease;
}

&:hover > .tab-close,
&:focus-within > .tab-close,
&.active > .tab-close {
opacity: 1;
}
`;

export const CloseButton = styled(ADSButton)`
border-radius: 2px;
cursor: pointer;
padding: var(--ads-v2-spaces-1);
max-width: 16px;
max-height: 16px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";

import clsx from "classnames";

import { Icon } from "..";

import * as Styled from "./DismissibleTab.styles";
import { DATA_TEST_ID } from "./constants";

import type { DismissibleTabProps } from "./DismissibleTab.types";

export const DismissibleTab = ({
children,
dataTestId,
isActive,
onClick,
onClose,
onDoubleClick,
}: DismissibleTabProps) => {
return (
<Styled.Tab
className={clsx("editor-tab", isActive && "active")}
data-testid={dataTestId}
onClick={onClick}
onDoubleClick={onDoubleClick}
>
{children}
<Styled.CloseButton
aria-label="Close tab"
className="tab-close"
data-testid={DATA_TEST_ID.CLOSE_BUTTON}
isIconButton
kind="tertiary"
onClick={onClose}
role="tab"
size="sm"
tabIndex={0}
>
<Icon name="close-line" />
</Styled.CloseButton>
</Styled.Tab>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type React from "react";

export interface DismissibleTabProps {
children: React.ReactNode;
dataTestId?: string;
isActive: boolean;
onClick: () => void;
onClose: (e: React.MouseEvent) => void;
onDoubleClick?: () => void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const DATA_TEST_ID = {
CLOSE_BUTTON: "t--tab-close-btn",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DismissibleTab } from "./DismissibleTab";
export type { DismissibleTabProps } from "./DismissibleTab.types";
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* eslint-disable no-console */
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";

import { EditableDismissibleTab } from ".";
import styled from "styled-components";

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

const meta: Meta<typeof EditableDismissibleTab> = {
title: "ADS/Templates/Editable Dismissible Tab",
component: EditableDismissibleTab,
};

const EntityIcon = styled.div`
height: 18px;
width: 18px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;

svg,
img {
height: 100%;
width: 100%;
}
`;

const JSIcon = () => {
return (
<EntityIcon>
<Icon name="js-yellow" size="md" />
</EntityIcon>
);
};

export default meta;

type Story = StoryObj<typeof EditableDismissibleTab>;

export const Basic: Story = {
args: {
isActive: true,
dataTestId: "t--dismissible-tab",
icon: JSIcon(),
name: "Hello",

onNameSave: console.log,
validateName: (name: string) =>
name.length < 3 ? "Name must be at least 3 characters" : null,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from "react";
import { noop } from "lodash";
import { useBoolean } from "usehooks-ts";

import { DismissibleTab } from "../..";
import { EditableEntityName } from "..";

import type { EditableDismissibleTabProps } from "./EditableDismissibleTab.types";

export const EditableDismissibleTab = (props: EditableDismissibleTabProps) => {
const {
dataTestId,
icon,
isActive,
isEditable = true,
isLoading,
name,
onClick,
onClose,
onNameSave,
validateName,
} = props;

const {
setFalse: exitEditMode,
setTrue: enterEditMode,
value: isEditing,
} = useBoolean(false);

const handleDoubleClick = isEditable ? enterEditMode : noop;

return (
<DismissibleTab
dataTestId={dataTestId}
isActive={isActive}
onClick={onClick}
onClose={onClose}
onDoubleClick={handleDoubleClick}
>
<EditableEntityName
icon={icon}
isEditing={isEditing}
isLoading={isLoading}
name={name}
onExitEditing={exitEditMode}
onNameSave={onNameSave}
validateName={validateName}
/>
</DismissibleTab>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type React from "react";

export interface EditableDismissibleTabProps {
dataTestId?: string;
icon: React.ReactNode;
isActive: boolean;
isEditable?: boolean;
isLoading: boolean;
name: string;
onClick: () => void;
onClose: () => void;
onNameSave: (name: string) => void;
validateName: (name: string) => string | null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EditableDismissibleTab } from "./EditableDismissibleTab";
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* eslint-disable no-console */
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import styled from "styled-components";

import { Icon } from "../..";
import { EditableEntityName } from ".";

const EntityIcon = styled.div`
height: 18px;
width: 18px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;

svg,
img {
height: 100%;
width: 100%;
}
`;

const JSIcon = () => {
return (
<EntityIcon>
<Icon name="js-yellow" size="md" />
</EntityIcon>
);
};

const meta: Meta<typeof EditableEntityName> = {
title: "ADS/Templates/Editable Entity Name",
component: EditableEntityName,
};

export default meta;

type Story = StoryObj<typeof EditableEntityName>;

export const Basic: Story = {
args: {
name: "Hello",
onNameSave: console.log,
onExitEditing: console.log,
icon: JSIcon(),
inputTestId: "t--editable-name",
isEditing: true,
isLoading: false,
validateName: (name: string) =>
name.length < 3 ? "Name must be at least 3 characters" : null,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import styled from "styled-components";
import { Text as ADSText } from "../../Text";

export const Root = styled.div`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: start;
align-items: center;
gap: var(--ads-v2-spaces-2);
`;

export const Text = styled(ADSText)`
min-width: 3ch;
bottom: -0.5px;
`;

export const IconContainer = styled.div`
height: 12px;
width: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;

img {
width: 12px;
}
`;
Loading