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