Skip to content

Commit

Permalink
SVG comp
Browse files Browse the repository at this point in the history
  • Loading branch information
moransantiago committed Dec 5, 2024
1 parent 44946c9 commit 85e7bd1
Show file tree
Hide file tree
Showing 9 changed files with 5,118 additions and 4,380 deletions.
4 changes: 4 additions & 0 deletions packages/basehub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"src/next/toolbar",
"src/react/pump",
"src/events",
"react-svg.js",
"react-svg.d.ts",
"react-rich-text.js",
"react-rich-text.d.ts",
"react-form.js",
Expand Down Expand Up @@ -68,6 +70,7 @@
"shiki": "^1.17.7",
"sonner": "1.5.0",
"typesense": "1.8.2",
"xmldom": "^0.6.0",
"zod": "3.22.1"
},
"devDependencies": {
Expand All @@ -76,6 +79,7 @@
"@types/node": "18.13.0",
"@types/react": "18.2.20",
"@types/react-dom": "18.2.7",
"@types/xmldom": "^0.1.34",
"esbuild-scss-modules-plugin": "^1.1.1",
"next": "^13.5.3",
"pkg-pr-new": "^0.0.30",
Expand Down
1 change: 1 addition & 0 deletions packages/basehub/react-svg.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./dist/react-svg";
1 change: 1 addition & 0 deletions packages/basehub/react-svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./dist/react-svg";
10 changes: 3 additions & 7 deletions packages/basehub/src/bin/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,7 @@ R extends Omit<MutationGenqlSelection, "transaction" | "transactionAwaitable"> &
}

await esbuild.build({
entryPoints: [
path.join(basehubModulePath, "src", "events", "index.ts"),
],
entryPoints: [path.join(basehubModulePath, "src", "events", "index.ts")],
bundle: true,
outdir: analyticsOutDir,
minify: false,
Expand Down Expand Up @@ -423,6 +421,7 @@ R extends Omit<MutationGenqlSelection, "transaction" | "transactionAwaitable"> &
if (output !== "node_modules") {
// alias react-rich-text and other packages to the generated client for better import experience
[
"react-svg",
"react-rich-text",
"react-form",
"react-code-block/index",
Expand Down Expand Up @@ -479,10 +478,7 @@ R extends Omit<MutationGenqlSelection, "transaction" | "transactionAwaitable"> &
"next-toolbar.d.ts"
);
const analyticsIndexJsPath = path.join(basehubModulePath, "events.js");
const analyticsIndexDtsPath = path.join(
basehubModulePath,
"events.d.ts"
);
const analyticsIndexDtsPath = path.join(basehubModulePath, "events.d.ts");
fs.writeFileSync(
indexJsPath,
ensureCrossPlatformTsImport(
Expand Down
1 change: 1 addition & 0 deletions packages/basehub/src/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export {
type CustomBlocksBase,
type HandlerProps as RichTextHandlerProps,
} from "./rich-text/primitive";
export { SVG } from "./svg/primitive";
1 change: 1 addition & 0 deletions packages/basehub/src/react/svg/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./primitive";
113 changes: 113 additions & 0 deletions packages/basehub/src/react/svg/primitive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as React from "react";
import * as z from "zod";

import { DOMParser } from "xmldom";

const svgComponentSchema = z.union([
z.literal("svg"),
z.literal("path"),
z.literal("circle"),
z.literal("rect"),
z.literal("g"),
z.literal("line"),
z.literal("polyline"),
z.literal("polygon"),
z.literal("text"),
]);
type SvgComponent = z.infer<typeof svgComponentSchema>;

type ComponentsOverride = {
[K in SvgComponent]: (props: JSX.IntrinsicElements[K]) => React.ReactNode;
};

const DEFAULT_COMPONENTS: ComponentsOverride = {
svg: (props) => <svg {...props} />,
path: (props) => <path {...props} />,
circle: (props) => <circle {...props} />,
rect: (props) => <rect {...props} />,
g: (props) => <g {...props} />,
line: (props) => <line {...props} />,
polyline: (props) => <polyline {...props} />,
polygon: (props) => <polygon {...props} />,
text: (props) => <text {...props} />,
};

export const SVG = ({
children,
components = DEFAULT_COMPONENTS,
}: {
children: string;
components?: Partial<ComponentsOverride>;
}) => {
// Merge default components with custom ones
const finalComponents = { ...DEFAULT_COMPONENTS, ...components };

const parseAndRenderSVG = (svgString: string) => {
try {
// Create a DOM parser
const parser =
typeof window !== "undefined"
? new DOMParser()
: new (require("xmldom").DOMParser)();
const doc = parser.parseFromString(svgString, "image/svg+xml");
const svgElement = doc.documentElement;

// Recursive function to convert DOM nodes to React elements
const convertNode = (node: Element): React.ReactNode => {
// Skip text nodes that only contain whitespace
if (node.nodeType === 3 && !node.nodeValue?.trim()) {
return null;
}

// For text nodes, return the text content
if (node.nodeType === 3) {
return node.nodeValue;
}

// Get the tag name and convert to lowercase
const tagName = node.tagName?.toLowerCase();

// Skip if not a valid tag
if (!tagName) return null;
const parsedTagName = svgComponentSchema.safeParse(tagName);

// Get the component for this tag
const tag = parsedTagName.success
? parsedTagName.data
: (tagName as SvgComponent);
const Component = finalComponents[tag];

// Convert attributes to props
const props: Record<string, JSX.IntrinsicElements[typeof tag]> = {};
Array.from(node.attributes || []).forEach((attr) => {
// Convert kebab-case to camelCase for React
const name = attr.name.replace(/-([a-z])/g, (g) =>
(g?.[1] as string).toUpperCase()
);
props[name] = attr.value as JSX.IntrinsicElements[typeof tag];
});

// Convert children
const _children = Array.from(node.childNodes)
.map((child) => convertNode(child as Element))
.filter(Boolean);

// Return the React element
if (_children.length === 0) return Component(props);

return Component({ ...props, children: _children });
};

return convertNode(svgElement);
} catch (error) {
console.error("Error parsing SVG:", error);
return null;
}
};

// Return the parsed and rendered SVG
const renderedSvg = parseAndRenderSVG(children);
if (!renderedSvg) return null;

return renderedSvg;
};
1 change: 1 addition & 0 deletions packages/basehub/tsup-client.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default defineConfig((_options: Options) => {
minify: false,
dts: true,
entry: {
"react-svg": "./src/react/svg/index.ts",
"react-rich-text": "./src/react/rich-text/index.ts",
"react-form": "./src/react/form/index.ts",
"react-code-block/index": "./src/react/code-block/index.ts",
Expand Down
Loading

0 comments on commit 85e7bd1

Please sign in to comment.