mirror of
https://github.com/penpot/penpot.git
synced 2026-04-28 04:38:14 +00:00
✨ Add RawSvg component to @penpot/ui
This commit is contained in:
parent
425a140a44
commit
828dcb3a96
@ -32,11 +32,15 @@ frontend/packages/ui/
|
||||
│ │ └── IconButton.spec.tsx
|
||||
│ ├── example/ # Example component (reference)
|
||||
│ ├── foundations/
|
||||
│ │ ├── assets/ # Icon component
|
||||
│ │ ├── assets/ # Icon, RawSvg components
|
||||
│ │ │ ├── Icon.tsx
|
||||
│ │ │ ├── Icon.module.scss
|
||||
│ │ │ ├── Icon.stories.tsx
|
||||
│ │ │ └── Icon.spec.tsx
|
||||
│ │ │ ├── Icon.spec.tsx
|
||||
│ │ │ ├── RawSvg.tsx
|
||||
│ │ │ ├── RawSvg.module.scss
|
||||
│ │ │ ├── RawSvg.stories.tsx
|
||||
│ │ │ └── RawSvg.spec.tsx
|
||||
│ │ └── typography/ # Text, Heading components + shared utilities
|
||||
│ └── product/ # Product-level components (e.g. Cta)
|
||||
├── eslint.config.mjs # ESLint 9 flat config (TypeScript + React)
|
||||
@ -56,6 +60,7 @@ Components are organised to mirror the CLJS source tree
|
||||
| `ds/foundations/typography/text.cljs` | `src/lib/foundations/typography/Text.tsx` |
|
||||
| `ds/foundations/typography/heading.cljs` | `src/lib/foundations/typography/Heading.tsx` |
|
||||
| `ds/foundations/assets/icon.cljs` | `src/lib/foundations/assets/Icon.tsx` |
|
||||
| `ds/foundations/assets/raw_svg.cljs` | `src/lib/foundations/assets/RawSvg.tsx` |
|
||||
| `ds/product/cta.cljs` | `src/lib/product/Cta.tsx` |
|
||||
| `ds/buttons/button.cljs` | `src/lib/buttons/Button.tsx` |
|
||||
| `ds/buttons/icon_button.cljs` | `src/lib/buttons/IconButton.tsx` |
|
||||
|
||||
@ -13,6 +13,8 @@ export { Cta } from "./lib/product/Cta";
|
||||
export type { CtaProps } from "./lib/product/Cta";
|
||||
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";
|
||||
export type { RawSvgId, RawSvgProps } from "./lib/foundations/assets/RawSvg";
|
||||
export { Button } from "./lib/buttons/Button";
|
||||
export type { ButtonProps, ButtonVariant } from "./lib/buttons/Button";
|
||||
export { IconButton } from "./lib/buttons/IconButton";
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
// 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
|
||||
|
||||
// RawSvg has no component-specific styles; all sizing and colour is
|
||||
// controlled by the consumer via props (width, height, style, etc.).
|
||||
@ -0,0 +1,63 @@
|
||||
// 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 { RawSvg, rawSvgIds } from "./RawSvg";
|
||||
|
||||
describe("RawSvg", () => {
|
||||
it("should render successfully", () => {
|
||||
const { baseElement } = render(<RawSvg id="penpot-logo" />);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders an <svg> element", () => {
|
||||
const { container } = render(<RawSvg id="penpot-logo" />);
|
||||
expect(container.querySelector("svg")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("renders a <use> element referencing the correct asset href", () => {
|
||||
const { container } = render(<RawSvg id="brand-github" />);
|
||||
const use = container.querySelector("use");
|
||||
expect(use?.getAttribute("href")).toBe("#asset-brand-github");
|
||||
});
|
||||
|
||||
it("forwards width and height props to the svg element", () => {
|
||||
const { container } = render(
|
||||
<RawSvg id="penpot-logo" width={200} height={48} />,
|
||||
);
|
||||
const svg = container.querySelector("svg");
|
||||
expect(svg?.getAttribute("width")).toBe("200");
|
||||
expect(svg?.getAttribute("height")).toBe("48");
|
||||
});
|
||||
|
||||
it("forwards className to the svg element", () => {
|
||||
const { container } = render(
|
||||
<RawSvg id="penpot-logo" className="my-svg" />,
|
||||
);
|
||||
expect(container.querySelector("svg")?.getAttribute("class")).toContain(
|
||||
"my-svg",
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards arbitrary svg props", () => {
|
||||
const { container } = render(
|
||||
<RawSvg id="penpot-logo" data-testid="raw-svg" aria-label="logo" />,
|
||||
);
|
||||
const svg = container.querySelector("svg");
|
||||
expect(svg?.getAttribute("data-testid")).toBe("raw-svg");
|
||||
expect(svg?.getAttribute("aria-label")).toBe("logo");
|
||||
});
|
||||
|
||||
it("exports a non-empty rawSvgIds array", () => {
|
||||
expect(rawSvgIds.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("includes expected asset IDs in rawSvgIds", () => {
|
||||
expect(rawSvgIds).toContain("penpot-logo");
|
||||
expect(rawSvgIds).toContain("brand-github");
|
||||
expect(rawSvgIds).toContain("loader");
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,36 @@
|
||||
// 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 { rawSvgIds, RawSvg } from "./RawSvg";
|
||||
|
||||
const meta = {
|
||||
title: "Foundations/Assets/RawSvg",
|
||||
component: RawSvg,
|
||||
args: {
|
||||
id: "brand-gitlab",
|
||||
width: 200,
|
||||
},
|
||||
argTypes: {
|
||||
id: {
|
||||
options: rawSvgIds,
|
||||
control: { type: "select" },
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof RawSvg>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const PenpotLogo: Story = {
|
||||
args: { id: "penpot-logo", width: 200, height: 48 },
|
||||
};
|
||||
|
||||
export const PenpotLogoIcon: Story = {
|
||||
args: { id: "penpot-logo-icon", width: 48, height: 48 },
|
||||
};
|
||||
57
frontend/packages/ui/src/lib/foundations/assets/RawSvg.tsx
Normal file
57
frontend/packages/ui/src/lib/foundations/assets/RawSvg.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
// 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";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Raw SVG asset ID catalogue
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const rawSvgIds = [
|
||||
"brand-openid",
|
||||
"brand-github",
|
||||
"brand-gitlab",
|
||||
"brand-google",
|
||||
"loader",
|
||||
"logo-error-screen",
|
||||
"logo-subscription",
|
||||
"logo-subscription-light",
|
||||
"nitrate-welcome",
|
||||
"marketing-arrows",
|
||||
"marketing-exchange",
|
||||
"marketing-file",
|
||||
"marketing-layers",
|
||||
"penpot-logo",
|
||||
"penpot-logo-icon",
|
||||
"empty-placeholder-1-left",
|
||||
"empty-placeholder-1-right",
|
||||
"empty-placeholder-2-left",
|
||||
"empty-placeholder-2-right",
|
||||
] as const;
|
||||
|
||||
export type RawSvgId = (typeof rawSvgIds)[number];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Component
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface RawSvgProps extends Omit<
|
||||
ComponentPropsWithRef<"svg">,
|
||||
"children"
|
||||
> {
|
||||
/** Raw SVG asset identifier — must be one of the registered asset IDs. */
|
||||
id: RawSvgId;
|
||||
}
|
||||
|
||||
function RawSvgInner({ id, ...rest }: RawSvgProps) {
|
||||
return (
|
||||
<svg {...rest}>
|
||||
<use href={`#asset-${id}`} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export const RawSvg = memo(RawSvgInner);
|
||||
Loading…
x
Reference in New Issue
Block a user