mirror of
https://github.com/penpot/penpot.git
synced 2026-05-02 22:58:35 +00:00
✨ Add PanelTitle component to @penpot/ui
This commit is contained in:
parent
c22c45384b
commit
6436e18074
@ -65,6 +65,7 @@ Components are organised to mirror the CLJS source tree
|
||||
| `ds/product/cta.cljs` | `src/lib/product/Cta.tsx` |
|
||||
| `ds/product/loader.cljs` | `src/lib/product/Loader.tsx` |
|
||||
| `ds/product/avatar.cljs` | `src/lib/product/Avatar.tsx` |
|
||||
| `ds/product/panel_title.cljs` | `src/lib/product/PanelTitle.tsx` |
|
||||
| `ds/buttons/button.cljs` | `src/lib/buttons/Button.tsx` |
|
||||
| `ds/buttons/icon_button.cljs` | `src/lib/buttons/IconButton.tsx` |
|
||||
| `ds/utilities/swatch.cljs` | `src/lib/utilities/Swatch.tsx` |
|
||||
|
||||
@ -19,6 +19,8 @@ export type {
|
||||
AvatarProfile,
|
||||
AvatarVariant,
|
||||
} from "./lib/product/Avatar";
|
||||
export { PanelTitle } from "./lib/product/PanelTitle";
|
||||
export type { PanelTitleProps } from "./lib/product/PanelTitle";
|
||||
export { Icon, iconIds } from "./lib/foundations/assets/Icon";
|
||||
export type { IconId, IconProps } from "./lib/foundations/assets/Icon";
|
||||
export { RawSvg, rawSvgIds } from "./lib/foundations/assets/RawSvg";
|
||||
|
||||
26
frontend/packages/ui/src/lib/product/PanelTitle.module.scss
Normal file
26
frontend/packages/ui/src/lib/product/PanelTitle.module.scss
Normal file
@ -0,0 +1,26 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "../_ds/_sizes.scss" as *;
|
||||
@use "../_ds/_borders.scss" as *;
|
||||
@use "../_ds/typography.scss" as t;
|
||||
|
||||
.panel-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
block-size: $sz-32;
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.panel-title-text {
|
||||
@include t.use-typography("headline-small");
|
||||
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
76
frontend/packages/ui/src/lib/product/PanelTitle.spec.tsx
Normal file
76
frontend/packages/ui/src/lib/product/PanelTitle.spec.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
import { render } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { PanelTitle } from "./PanelTitle";
|
||||
|
||||
describe("PanelTitle", () => {
|
||||
it("should render successfully", () => {
|
||||
const { baseElement } = render(<PanelTitle text="My Panel" />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should render as a div", () => {
|
||||
const { container } = render(<PanelTitle text="My Panel" />);
|
||||
expect(container.firstElementChild?.tagName.toLowerCase()).toBe("div");
|
||||
});
|
||||
|
||||
it("should apply panel-title class to root element", () => {
|
||||
const { container } = render(<PanelTitle text="My Panel" />);
|
||||
expect(container.firstElementChild?.getAttribute("class")).toContain(
|
||||
"panel-title",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render the text prop in a span", () => {
|
||||
const { getByText } = render(<PanelTitle text="My Panel" />);
|
||||
const span = getByText("My Panel");
|
||||
expect(span.tagName.toLowerCase()).toBe("span");
|
||||
});
|
||||
|
||||
it("should not render a close button when onClose is not provided", () => {
|
||||
const { container } = render(<PanelTitle text="My Panel" />);
|
||||
const button = container.querySelector("button");
|
||||
expect(button).toBeNull();
|
||||
});
|
||||
|
||||
it("should render a close button when onClose is provided", () => {
|
||||
const { container } = render(
|
||||
<PanelTitle text="My Panel" onClose={() => {}} />,
|
||||
);
|
||||
const button = container.querySelector("button");
|
||||
expect(button).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should call onClose when close button is clicked", () => {
|
||||
const onClose = vi.fn();
|
||||
const { container } = render(
|
||||
<PanelTitle text="My Panel" onClose={onClose} />,
|
||||
);
|
||||
const button = container.querySelector("button") as HTMLButtonElement;
|
||||
button.click();
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should forward className to the root div", () => {
|
||||
const { container } = render(
|
||||
<PanelTitle text="My Panel" className="custom-cls" />,
|
||||
);
|
||||
expect(container.firstElementChild?.getAttribute("class")).toContain(
|
||||
"custom-cls",
|
||||
);
|
||||
});
|
||||
|
||||
it("should spread extra props onto the root div", () => {
|
||||
const { container } = render(
|
||||
<PanelTitle text="My Panel" data-testid="panel-header" />,
|
||||
);
|
||||
expect(container.firstElementChild?.getAttribute("data-testid")).toBe(
|
||||
"panel-header",
|
||||
);
|
||||
});
|
||||
});
|
||||
32
frontend/packages/ui/src/lib/product/PanelTitle.stories.tsx
Normal file
32
frontend/packages/ui/src/lib/product/PanelTitle.stories.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { PanelTitle } from "./PanelTitle";
|
||||
|
||||
const meta = {
|
||||
title: "Product/PanelTitle",
|
||||
component: PanelTitle,
|
||||
args: {
|
||||
text: "Lorem ipsum",
|
||||
},
|
||||
argTypes: {
|
||||
text: { control: "text" },
|
||||
},
|
||||
} satisfies Meta<typeof PanelTitle>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithCloseButton: Story = {
|
||||
args: {
|
||||
onClose: () => {
|
||||
console.warn("close");
|
||||
},
|
||||
},
|
||||
};
|
||||
40
frontend/packages/ui/src/lib/product/PanelTitle.tsx
Normal file
40
frontend/packages/ui/src/lib/product/PanelTitle.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
import { type ComponentPropsWithRef, memo } from "react";
|
||||
import clsx from "clsx";
|
||||
import { IconButton } from "../buttons/IconButton";
|
||||
import styles from "./PanelTitle.module.scss";
|
||||
|
||||
export interface PanelTitleProps extends ComponentPropsWithRef<"div"> {
|
||||
/** Title text displayed in the panel header */
|
||||
text: string;
|
||||
/** When provided, renders a close (×) button that calls this handler */
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function PanelTitleInner({
|
||||
text,
|
||||
onClose,
|
||||
className,
|
||||
...rest
|
||||
}: PanelTitleProps) {
|
||||
return (
|
||||
<div className={clsx(styles["panel-title"], className)} {...rest}>
|
||||
<span className={styles["panel-title-text"]}>{text}</span>
|
||||
{onClose != null && (
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
aria-label="Close"
|
||||
onClick={onClose}
|
||||
icon="close"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const PanelTitle = memo(PanelTitleInner);
|
||||
Loading…
x
Reference in New Issue
Block a user