mirror of https://github.com/buster-so/buster.git
Merge pull request #554 from buster-so/big-nate/bus-1424-default-color-palette-in-workspace-settings
default color palette in workspace settings
This commit is contained in:
commit
0c345cb00e
|
@ -1,10 +1,9 @@
|
|||
import React, { useRef, type PropsWithChildren } from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { ThemeList, type IColorPalette } from '../ThemeList';
|
||||
import { Button } from '@/components/ui/buttons';
|
||||
import { Plus } from '../../../ui/icons';
|
||||
import { NewThemePopup } from './NewThemePopup';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { Popover } from '../../../ui/popover';
|
||||
import { EditCustomThemeMenu } from './EditCustomThemeMenu';
|
||||
import { AddThemeProviderWrapper, useAddTheme } from './AddThemeProviderWrapper';
|
||||
|
||||
|
@ -59,25 +58,16 @@ const AddCustomThemeButton: React.FC = React.memo(({}) => {
|
|||
const { createCustomTheme } = useAddTheme();
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const closePopover = useMemoizedFn(() => {
|
||||
buttonRef.current?.click();
|
||||
});
|
||||
|
||||
const onSave = useMemoizedFn(async (theme: IColorPalette) => {
|
||||
await createCustomTheme(theme);
|
||||
closePopover();
|
||||
});
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={<NewThemePopup selectedTheme={undefined} onSave={onSave} onDelete={undefined} />}
|
||||
trigger="click"
|
||||
className="max-w-[320px] p-0"
|
||||
sideOffset={12}>
|
||||
<NewThemePopup
|
||||
onSave={createCustomTheme}
|
||||
selectedTheme={undefined}
|
||||
onDelete={undefined}
|
||||
onUpdate={undefined}>
|
||||
<Button ref={buttonRef} variant={'ghost'} size={'tall'} prefix={<Plus />}>
|
||||
Add a custom theme
|
||||
</Button>
|
||||
</Popover>
|
||||
</NewThemePopup>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -27,5 +27,11 @@ export const AddThemeProviderWrapper: React.FC<PropsWithChildren<AddThemeProps>>
|
|||
};
|
||||
|
||||
export const useAddTheme = () => {
|
||||
return React.useContext(AddThemeProvider);
|
||||
const context = React.useContext(AddThemeProvider);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useAddTheme must be used within an AddThemeProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
import React from 'react';
|
||||
import React, { type PropsWithChildren } from 'react';
|
||||
import type { IColorPalette } from '../ThemeList';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { NewThemePopup } from './NewThemePopup';
|
||||
import { useAddTheme } from './AddThemeProviderWrapper';
|
||||
|
||||
export const EditCustomThemeMenu: React.FC<{ theme: IColorPalette }> = React.memo(({ theme }) => {
|
||||
export const EditCustomThemeMenu: React.FC<PropsWithChildren<{ theme: IColorPalette }>> =
|
||||
React.memo(({ theme, children }) => {
|
||||
const { deleteCustomTheme, modifyCustomTheme } = useAddTheme();
|
||||
|
||||
const onSave = useMemoizedFn(async (theme: IColorPalette) => {
|
||||
await modifyCustomTheme(theme.id, theme);
|
||||
const onSave = useMemoizedFn((theme: IColorPalette) => {
|
||||
return modifyCustomTheme(theme.id, theme);
|
||||
});
|
||||
|
||||
const onDelete = useMemoizedFn(async (themeId: string) => {
|
||||
await deleteCustomTheme(themeId);
|
||||
const onDelete = useMemoizedFn((themeId: string) => {
|
||||
return deleteCustomTheme(themeId);
|
||||
});
|
||||
|
||||
const onUpdate = useMemoizedFn(async (theme: IColorPalette) => {
|
||||
await modifyCustomTheme(theme.id, theme);
|
||||
const onUpdate = useMemoizedFn((theme: IColorPalette) => {
|
||||
return modifyCustomTheme(theme.id, theme);
|
||||
});
|
||||
|
||||
return (
|
||||
<NewThemePopup selectedTheme={theme} onSave={onSave} onDelete={onDelete} onUpdate={onUpdate} />
|
||||
<NewThemePopup selectedTheme={theme} onSave={onSave} onDelete={onDelete} onUpdate={onUpdate}>
|
||||
{children}
|
||||
</NewThemePopup>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { Button } from '@/components/ui/buttons';
|
||||
import { Input } from '@/components/ui/inputs';
|
||||
import { Text } from '@/components/ui/typography';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import type { IColorPalette } from '../ThemeList';
|
||||
import { useMemoizedFn } from '@/hooks';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Plus, Trash } from '../../../ui/icons';
|
||||
import { Plus, Trash, FloppyDisk } from '../../../ui/icons';
|
||||
import { ColorPickButton } from './DraggableColorPicker';
|
||||
import { inputHasText } from '@/lib/text';
|
||||
import { DEFAULT_CHART_THEME } from '@buster/server-shared/metrics';
|
||||
import { Popover } from '../../../ui/popover';
|
||||
|
||||
interface NewThemePopupProps {
|
||||
selectedTheme?: IColorPalette;
|
||||
|
@ -17,8 +18,16 @@ interface NewThemePopupProps {
|
|||
onUpdate?: (theme: IColorPalette) => Promise<void>;
|
||||
}
|
||||
|
||||
export const NewThemePopup = React.memo(
|
||||
({ selectedTheme, onDelete, onUpdate, onSave }: NewThemePopupProps) => {
|
||||
const NewThemePopupContent = React.memo(
|
||||
({
|
||||
selectedTheme,
|
||||
onDelete,
|
||||
onUpdate,
|
||||
onSave,
|
||||
triggerRef
|
||||
}: NewThemePopupProps & {
|
||||
triggerRef: React.RefObject<HTMLSpanElement>;
|
||||
}) => {
|
||||
const [title, setTitle] = useState('');
|
||||
const [colors, setColors] = useState<string[]>(DEFAULT_CHART_THEME);
|
||||
const [id, setId] = useState(uuidv4());
|
||||
|
@ -32,8 +41,13 @@ export const NewThemePopup = React.memo(
|
|||
setId(uuidv4());
|
||||
});
|
||||
|
||||
const closePopover = useMemoizedFn(() => {
|
||||
triggerRef.current?.click();
|
||||
});
|
||||
|
||||
const onDeleteClick = useMemoizedFn(async () => {
|
||||
if (selectedTheme) await onDelete?.(id);
|
||||
closePopover();
|
||||
setTimeout(() => {
|
||||
reset();
|
||||
}, 350);
|
||||
|
@ -41,6 +55,7 @@ export const NewThemePopup = React.memo(
|
|||
|
||||
const onSaveClick = useMemoizedFn(async () => {
|
||||
await onSave({ id, name: title, colors });
|
||||
closePopover();
|
||||
setTimeout(() => {
|
||||
reset();
|
||||
}, 350);
|
||||
|
@ -48,6 +63,7 @@ export const NewThemePopup = React.memo(
|
|||
|
||||
const onUpdateClick = useMemoizedFn(async () => {
|
||||
await onUpdate?.({ id, name: title, colors });
|
||||
closePopover();
|
||||
setTimeout(() => {
|
||||
reset();
|
||||
}, 350);
|
||||
|
@ -89,7 +105,7 @@ export const NewThemePopup = React.memo(
|
|||
block
|
||||
disabled={disableCreateTheme}
|
||||
onClick={isNewTheme ? onSaveClick : onUpdateClick}
|
||||
prefix={<Plus />}>
|
||||
prefix={isNewTheme ? <Plus /> : <FloppyDisk />}>
|
||||
{isNewTheme || !onUpdate ? 'Create theme' : 'Update theme'}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -98,4 +114,22 @@ export const NewThemePopup = React.memo(
|
|||
}
|
||||
);
|
||||
|
||||
NewThemePopup.displayName = 'NewThemePopup';
|
||||
NewThemePopupContent.displayName = 'NewThemePopupContent';
|
||||
|
||||
export const NewThemePopup = ({
|
||||
children,
|
||||
...props
|
||||
}: NewThemePopupProps & { children: React.ReactNode }) => {
|
||||
const triggerRef = useRef<HTMLSpanElement>(null);
|
||||
return (
|
||||
<Popover
|
||||
content={<NewThemePopupContent {...props} triggerRef={triggerRef} />}
|
||||
trigger="click"
|
||||
className="max-w-[320px] p-0"
|
||||
sideOffset={12}>
|
||||
<span data-testid="new-theme-popup-trigger" ref={triggerRef}>
|
||||
{children}
|
||||
</span>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ export const ThemeList: React.FC<{
|
|||
themes: IColorPalette[];
|
||||
className?: string;
|
||||
onChangeColorTheme: (theme: IColorPalette) => void;
|
||||
themeThreeDotsMenu?: React.FC<{ theme: IColorPalette }>;
|
||||
themeThreeDotsMenu?: React.FC<{ theme: IColorPalette; children: React.ReactNode }>;
|
||||
}> = ({ themes, className, themeThreeDotsMenu, onChangeColorTheme }) => {
|
||||
return (
|
||||
<div
|
||||
|
@ -36,7 +36,7 @@ export const ThemeList: React.FC<{
|
|||
const ColorOption: React.FC<{
|
||||
theme: IColorPalette;
|
||||
selected: boolean | undefined;
|
||||
threeDotMenu?: React.FC<{ theme: IColorPalette }>;
|
||||
threeDotMenu?: React.FC<{ theme: IColorPalette; children: React.ReactNode }>;
|
||||
onChangeColorTheme: (theme: IColorPalette) => void;
|
||||
}> = React.memo(({ theme, selected = false, threeDotMenu, onChangeColorTheme }) => {
|
||||
const { name, colors } = theme;
|
||||
|
@ -65,17 +65,14 @@ const ColorOption: React.FC<{
|
|||
|
||||
{shouldShowMenu && (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Popover
|
||||
className="p-0"
|
||||
content={<ThreeDotMenuComponent theme={theme} />}
|
||||
trigger="click">
|
||||
<ThreeDotMenuComponent theme={theme}>
|
||||
<Button
|
||||
data-testid={`color-theme-three-dots-menu`}
|
||||
variant={'ghost'}
|
||||
size={'small'}
|
||||
prefix={<Dots />}
|
||||
/>
|
||||
</Popover>
|
||||
</ThreeDotMenuComponent>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -22,12 +22,12 @@ export const StylingAppColors: React.FC<{
|
|||
});
|
||||
|
||||
return (
|
||||
<div className="mt-3 flex flex-col space-y-2">
|
||||
<div className="flex flex-col space-y-2">
|
||||
{/* <div className={className}>
|
||||
<SelectColorApp selectedTab={selectedTab} onChange={setSelectedTab} />
|
||||
</div> */}
|
||||
|
||||
<div className={cn(className, 'mb-12')}>
|
||||
<div className={cn(className, 'mt-3 mb-12')}>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={selectedTab}
|
||||
|
|
Loading…
Reference in New Issue