Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/theme builder import theme #3023

Closed
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
37 changes: 37 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@docusaurus/theme-live-codeblock": "^3.5.2",
"@easyops-cn/docusaurus-search-local": "^0.44.5",
"@mdx-js/react": "^3.0.0",
"@monaco-editor/react": "^4.6.0",
"axios": "^1.7.7",
"clsx": "^2.0.0",
"date-fns": "^3.6.0",
Expand All @@ -41,13 +42,13 @@
"@docusaurus/module-type-aliases": "^3.5.2",
"@docusaurus/tsconfig": "^3.5.2",
"@docusaurus/types": "^3.5.2",
"@heroicons/react": "^2.2.0",
"@types/react": "^18.0.0",
"autoprefixer": "^10.4.20",
"d3-array": "^2.4.0",
"d3-scale": "^3.2.1",
"d3-time": "^1.1.0",
"find-cache-dir": "5.0.0",
"@heroicons/react": "^2.2.0",
"mdast-util-from-markdown": "^2.0.1",
"postcss": "^8.4.38",
"prismjs": "^1.29.0",
Expand Down
97 changes: 87 additions & 10 deletions website/src/pages/themes/_components/base-theme-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,79 @@
import React from "react";
import React, { useEffect, useState } from "react";
import Select from "./select";
import { themes, useTheme } from "../_providers/themeProvider";
import { CUSTOM_THEME, themes, useTheme } from "../_providers/themeProvider";
import { usePreviewOptions } from "../_providers/previewOptionsProvider";
import PanelHeader from "./panel-header";
import Editor from "@monaco-editor/react";
import { Button } from "@site/src/components/button";
import { stringifyWithoutQuotes } from "../_utils";

const themeOptions = themes.map((theme) => ({
label: theme.name,
value: theme.name,
}));
const EDITOR_OPTIONS = {
minimap: { enabled: false },
fontSize: 12,
};

const themeOptions = [
...themes.map((theme) => ({
label: theme.name,
value: theme.name,
})),
];

const BaseThemePanel = () => {
const { baseTheme, onBaseThemeSelect } = useTheme();
const { baseTheme, onBaseThemeSelect, customThemeConfig } = useTheme();
const { resetPreviewOptions } = usePreviewOptions();
const [customTheme, setCustomTheme] = useState<string>(() =>
stringifyWithoutQuotes(customThemeConfig),
);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
setCustomTheme(stringifyWithoutQuotes(customThemeConfig));
}, [customThemeConfig]);

const handleThemeSelect = (themeName?: string) => {
onBaseThemeSelect(themeName);
const theme = themes.find((t) => t.name === themeName);
if (!theme) return;
onBaseThemeSelect(theme);
resetPreviewOptions();
};

const handleCustomThemeChange = (value: string | undefined) => {
setCustomTheme(value || "");
setError(null);
};

const applyCustomTheme = () => {
try {
const parsedTheme = new Function(`return (${customTheme.trim()});`)();
if (typeof parsedTheme !== "object" || Array.isArray(parsedTheme)) {
throw new Error("Invalid theme structure. Must be an object.");
}

onBaseThemeSelect({
...CUSTOM_THEME,
config: parsedTheme,
});
setError(null);
} catch {
setError(
"Invalid JavaScript object. Please check your theme configuration.",
);
}
};

const handleEditorMount = (_, monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: false,
enableSchemaRequest: false,
schemas: [],
});
};

const isCustomTheme = baseTheme?.name === CUSTOM_THEME.name;

return (
<>
<div className="flex flex-col h-full">
<PanelHeader
title="Base Theme"
description="Select a theme to begin customizing."
Expand All @@ -31,7 +85,30 @@ const BaseThemePanel = () => {
options={themeOptions}
label="Theme"
/>
</>
{isCustomTheme && (
<div className="mt-4 flex-1 flex flex-col justify-start">
<p className="text-sm text-grayscale-400 mb-2">
Extend the theme object below to apply your own theme configuration.
</p>
<div className="w-full flex-1 border rounded">
<Editor
height="100%"
defaultLanguage="json"
theme="vs-dark"
value={customTheme}
onChange={handleCustomThemeChange}
onMount={handleEditorMount}
options={EDITOR_OPTIONS}
/>
</div>
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
<Button onClick={applyCustomTheme} className="mt-4">
Apply Custom Theme
</Button>
</div>
)}
</div>
);
};

export default BaseThemePanel;
35 changes: 30 additions & 5 deletions website/src/pages/themes/_components/color-palette-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ColorScalePropType, VictoryThemeDefinition } from "victory";
import { ColorChangeArgs } from "./control";
import clsx from "clsx";
import { usePreviewOptions } from "../_providers/previewOptionsProvider";
import { useTheme } from "../_providers/themeProvider";
import { TiPlus } from "react-icons/ti";

type ColorPaletteSelectorProps = {
label: string;
Expand All @@ -23,22 +25,39 @@ const ColorPaletteSelector = ({
className,
}: ColorPaletteSelectorProps) => {
const { colorScale, updateColorScale } = usePreviewOptions();
const { updateCustomThemeConfig } = useTheme();

const handleRadioChange = () => {
updateColorScale(value);
};

const handleColorChange = (newColor, i, cScale) => {
onColorChange({
newColor,
index: i,
colorScale: cScale,
});
if (newColor === undefined) {
// Remove color if undefined
const updatedColors = palette?.[cScale]?.filter(
(_, index) => index !== i,
);
updateCustomThemeConfig(`palette.${cScale}`, updatedColors);
} else {
onColorChange({
newColor,
index: i,
colorScale: cScale,
});
}
if (colorScale !== cScale) {
updateColorScale(cScale);
}
};

const handleAddColor = () => {
const updatedColors = [
...(palette?.[colorScaleType as string] || []),
"#000000",
];
updateCustomThemeConfig(`palette.${colorScaleType}`, updatedColors);
};

const isSelected = colorScale === value;

return (
Expand Down Expand Up @@ -69,6 +88,12 @@ const ColorPaletteSelector = ({
}
/>
))}
<button
onClick={handleAddColor}
className="font-medium flex w-[35px] h-[35px] rounded-full cursor-pointer justify-center items-center border-2 border-grayscale-300"
>
<TiPlus className="text-lg" />
</button>
</div>
)}
</div>
Expand Down
34 changes: 23 additions & 11 deletions website/src/pages/themes/_components/color-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useId } from "react";
import { TiPencil } from "react-icons/ti";
import { TiMinus, TiPencil } from "react-icons/ti";
import clsx from "clsx";
import Select from "./select";
import { useTheme } from "../_providers/themeProvider";

type ColorPickerProps = {
label?: string;
Expand All @@ -25,6 +26,7 @@ const ColorPicker = ({
showColorName = false,
className,
}: ColorPickerProps) => {
const { updateCustomThemeConfig } = useTheme();
const [isPickerOpen, setIsPickerOpen] = React.useState(false);
const [colorOption, setColorOption] = React.useState<string | undefined>(
() => {
Expand All @@ -50,9 +52,11 @@ const ColorPicker = ({
};

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (onColorChange) {
onColorChange(event.target.value);
}
onColorChange(event.target.value);
};

const handleRemoveColor = () => {
onColorChange(undefined);
};

const id = useId();
Expand Down Expand Up @@ -106,13 +110,21 @@ const ColorPicker = ({
}}
/>
{!showColorName && (
<div
className={`absolute top-0 left-0 w-full h-full text-white flex justify-center items-center text-xl rounded-full opacity-0 group-hover/swatch:opacity-100 ${
isPickerOpen ? "opacity-100" : ""
}`}
>
<TiPencil />
</div>
<>
<div
className={`absolute top-0 left-0 w-full h-full text-white flex justify-center items-center text-xl rounded-full opacity-0 group-hover/swatch:opacity-100 ${
isPickerOpen ? "opacity-100" : ""
}`}
>
<TiPencil />
</div>
<button
onClick={handleRemoveColor}
className="absolute -top-1.5 -right-1.5 w-5 h-5 text-white bg-red-500 flex justify-center items-center text-xl rounded-full opacity-0 group-hover/swatch:opacity-100 z-20 cursor-pointer"
>
<TiMinus />
</button>
</>
)}
</div>
{showColorName && (
Expand Down
Loading