Compare commits
9 Commits
cb9a01109e
...
ddfc2fff15
Author | SHA1 | Date | |
---|---|---|---|
|
ddfc2fff15 | ||
|
ba8991d1b5 | ||
|
25a3cf0fce | ||
|
56ba55a4ca | ||
|
036b9fead6 | ||
|
1db89e57cc | ||
|
367117d8aa | ||
|
e32eed405f | ||
|
6643eae433 |
|
@ -1,15 +1,16 @@
|
|||
import { Icon, IconProps } from '@iconify/react/dist/iconify.js';
|
||||
import cx from 'clsx';
|
||||
import { MouseEvent, MouseEventHandler, useCallback } from 'react';
|
||||
import { MouseEvent, MouseEventHandler, RefObject, useCallback } from 'react';
|
||||
import styles from './ActionIcon.module.css';
|
||||
|
||||
type ActionIconProps = {
|
||||
icon: IconProps['icon'];
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
extendClassName?: HTMLButtonElement['className'];
|
||||
ref: RefObject<HTMLButtonElement>;
|
||||
};
|
||||
|
||||
export function ActionIcon({ icon, onClick, extendClassName }: ActionIconProps) {
|
||||
export function ActionIcon({ icon, onClick, extendClassName, ref }: ActionIconProps) {
|
||||
const handleClick = useCallback(
|
||||
(event: MouseEvent<HTMLButtonElement>) => {
|
||||
onClick?.(event);
|
||||
|
@ -18,7 +19,11 @@ export function ActionIcon({ icon, onClick, extendClassName }: ActionIconProps)
|
|||
);
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleClick} className={cx(styles.action_icon, extendClassName)}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={cx(styles.action_icon, extendClassName)}
|
||||
ref={ref}>
|
||||
<Icon icon={icon} className={styles.icon} />
|
||||
</button>
|
||||
);
|
||||
|
|
42
src/components/ContextMenu.module.css
Normal file
42
src/components/ContextMenu.module.css
Normal file
|
@ -0,0 +1,42 @@
|
|||
@layer components {
|
||||
.context_menu_locationer {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
.action_icon {
|
||||
background-color: transparent;
|
||||
&:hover {
|
||||
background-color: var(--color-neutral-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-neutral-active);
|
||||
}
|
||||
}
|
||||
.menu_body {
|
||||
position: absolute;
|
||||
width: max-content;
|
||||
background-color: var(--color-wumeizi);
|
||||
color: var(--color-yudubai);
|
||||
border: 1px solid var(--color-xuanqing);
|
||||
border-radius: var(--border-radius-xs);
|
||||
padding-block: var(--spacing-xs);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 300;
|
||||
.menu_item {
|
||||
width: 100%;
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
&:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-primary-active);
|
||||
}
|
||||
}
|
||||
hr {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
390
src/components/ContextMenu.tsx
Normal file
390
src/components/ContextMenu.tsx
Normal file
|
@ -0,0 +1,390 @@
|
|||
import { SwatchEntry } from 'color-module';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { capitalize, size } from 'lodash-es';
|
||||
import {
|
||||
FC,
|
||||
MouseEvent,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { MaterialDesign2SchemeSource } from '../material-2-scheme';
|
||||
import {
|
||||
MaterialDesign3DynamicSchemeSource,
|
||||
MaterialDesign3SchemeSource,
|
||||
} from '../material-3-scheme';
|
||||
import { QSchemeSource } from '../q-scheme';
|
||||
import { currentPickedColor } from '../stores/colors';
|
||||
import { activeSchemeAtom, useActiveScheme, useUpdateScheme } from '../stores/schemes';
|
||||
import { SwatchSchemeSource } from '../swatch_scheme';
|
||||
import { ActionIcon } from './ActionIcon';
|
||||
import styles from './ContextMenu.module.css';
|
||||
import { NotificationType, useNotification } from './Notifications';
|
||||
|
||||
interface ContextMenuItemProps {
|
||||
color: string;
|
||||
afterClick?: () => void;
|
||||
}
|
||||
|
||||
const SetPickerMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const setCurrentPicker = useSetAtom(currentPickedColor);
|
||||
const handleClickAction = useCallback(() => {
|
||||
setCurrentPicker(color);
|
||||
afterClick?.();
|
||||
}, [afterClick, color]);
|
||||
|
||||
return (
|
||||
<div className={styles.menu_item} onClick={handleClickAction}>
|
||||
Set to default Picker
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const QSchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const updateSchemeContent = useCallback(
|
||||
(content: keyof QSchemeSource) => {
|
||||
updateScheme((prev) => {
|
||||
prev.schemeStorage.source[content] = color;
|
||||
return prev;
|
||||
});
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`${capitalize(content)} color in active scheme updated.`,
|
||||
'tabler:settings-up',
|
||||
3000,
|
||||
);
|
||||
afterClick?.();
|
||||
},
|
||||
[color, activeSchemeId, updateScheme],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('primary')}>
|
||||
Set as Primary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('secondary')}>
|
||||
Set as Secondary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('tertiary')}>
|
||||
Set as Tertiary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('accent')}>
|
||||
Set as Accent color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('danger')}>
|
||||
Set as Danger color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('success')}>
|
||||
Set as Success color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('warning')}>
|
||||
Set as Warn color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('info')}>
|
||||
Set as Info color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('foreground')}>
|
||||
Set as Foreground color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('background')}>
|
||||
Set as Background color
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SwatchSchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const addColorEntry = useCallback(() => {
|
||||
updateScheme((prev) => {
|
||||
const source = prev.schemeStorage.source as SwatchSchemeSource;
|
||||
|
||||
const colorAmount = source.colors.length;
|
||||
const newEntry = new SwatchEntry(`Custom Color ${colorAmount + 1}`, color);
|
||||
source.colors.push(newEntry.toJsValue());
|
||||
|
||||
return prev;
|
||||
});
|
||||
|
||||
showToast(NotificationType.SUCCESS, 'New color entry added.', 'tabler:settings-up', 3000);
|
||||
|
||||
afterClick?.();
|
||||
}, [color, activeSchemeId, updateScheme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={addColorEntry}>
|
||||
Add to swatch color
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Material2SchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const updateSchemeColor = useCallback(
|
||||
(content: keyof MaterialDesign2SchemeSource) => {
|
||||
updateScheme((prev) => {
|
||||
prev.schemeStorage.source[content] = color;
|
||||
return prev;
|
||||
});
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`${capitalize(content)} color in active scheme updated.`,
|
||||
'tabler:settings-up',
|
||||
3000,
|
||||
);
|
||||
afterClick?.();
|
||||
},
|
||||
[color, activeSchemeId, updateScheme],
|
||||
);
|
||||
const addToCustomColor = useCallback(() => {
|
||||
updateScheme((prev) => {
|
||||
const source = prev.schemeStorage.source as MaterialDesign2SchemeSource;
|
||||
const colorAmount = size(source.custom_colors);
|
||||
source.custom_colors[`Custom Color ${colorAmount + 1}`] = color;
|
||||
return prev;
|
||||
});
|
||||
showToast(NotificationType.SUCCESS, `New color entry added.`, 'tabler:settings-up', 3000);
|
||||
afterClick?.();
|
||||
}, [color, activeSchemeId, updateScheme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeColor('primary')}>
|
||||
Set as Primary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeColor('secondary')}>
|
||||
Set as Secondary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeColor('error')}>
|
||||
Set as Error color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => addToCustomColor()}>
|
||||
Add to Custom colors
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Material3SchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const updateSchemeContent = useCallback(
|
||||
(content: keyof MaterialDesign3SchemeSource) => {
|
||||
updateScheme((prev) => {
|
||||
prev.schemeStorage.source[content] = color;
|
||||
return prev;
|
||||
});
|
||||
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`${capitalize(content)} color in active scheme updated.`,
|
||||
'tabler:settings-up',
|
||||
3000,
|
||||
);
|
||||
|
||||
afterClick?.();
|
||||
},
|
||||
[color, activeSchemeId, updateScheme],
|
||||
);
|
||||
const addCustomColor = useCallback(() => {
|
||||
updateScheme((prev) => {
|
||||
const source = prev.schemeStorage.source as
|
||||
| MaterialDesign3DynamicSchemeSource
|
||||
| MaterialDesign3SchemeSource;
|
||||
const colorAmount = size(source.custom_colors);
|
||||
source.custom_colors[`Custom Color ${colorAmount + 1}`] = color;
|
||||
return prev;
|
||||
});
|
||||
|
||||
showToast(NotificationType.SUCCESS, `New color entry added.`, 'tabler:settings-up', 3000);
|
||||
|
||||
afterClick?.();
|
||||
}, [color, activeSchemeId, updateScheme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('source')}>
|
||||
Set as Source color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('error')}>
|
||||
Set as Error color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={addCustomColor}>
|
||||
Add to Custom colors
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface ContextMenuBodyProps {
|
||||
color: string;
|
||||
afterClick?: () => void;
|
||||
x?: number;
|
||||
y?: number;
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const ContextMenuBody: FC<ContextMenuBodyProps> = ({ color, afterClick, x, y, ref }) => {
|
||||
const activeScheme = useActiveScheme();
|
||||
|
||||
const schemeMenu = useMemo(() => {
|
||||
const sharedProps: ContextMenuItemProps = {
|
||||
color,
|
||||
afterClick,
|
||||
};
|
||||
switch (activeScheme?.type) {
|
||||
case 'q_scheme':
|
||||
return <QSchemeMenu {...sharedProps} />;
|
||||
case 'swatch_scheme':
|
||||
return <SwatchSchemeMenu {...sharedProps} />;
|
||||
case 'material_2':
|
||||
return <Material2SchemeMenu {...sharedProps} />;
|
||||
case 'material_3':
|
||||
case 'material_3_dynamic':
|
||||
return <Material3SchemeMenu {...sharedProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [activeScheme, color, afterClick]);
|
||||
|
||||
return (
|
||||
<div className={styles.menu_body} ref={ref} style={{ top: y, left: x }}>
|
||||
<SetPickerMenu color={color} afterClick={afterClick} />
|
||||
{schemeMenu}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface ContextMenuProps {
|
||||
color: string;
|
||||
}
|
||||
|
||||
const ContextMenu: FC<ContextMenuProps> = ({ color }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [initialPosition, setInitialPosition] = useState({ x: 0, y: 0 });
|
||||
const [renderPosition, setRenderPosition] = useState({ x: 0, y: 0 });
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleOpenMenu = useCallback(() => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false);
|
||||
return;
|
||||
}
|
||||
if (triggerRef.current && containerRef.current) {
|
||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
|
||||
const x = triggerRect.left - containerRect.left;
|
||||
const y = triggerRect.bottom - containerRect.top;
|
||||
|
||||
setInitialPosition({ x, y });
|
||||
setRenderPosition({ x, y });
|
||||
setIsOpen(true);
|
||||
}
|
||||
}, [isOpen]);
|
||||
const handleCloseMenu = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
const handleLeaveClose = useCallback(
|
||||
(evt: MouseEvent<HTMLDivElement>) => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
const relatedTarget = evt.relatedTarget as Node | null;
|
||||
|
||||
if (menuRef.current && menuRef.current.contains(relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (triggerRef.current && triggerRef.current.contains(relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleCloseMenu();
|
||||
},
|
||||
[handleCloseMenu, isOpen],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && menuRef.current && containerRef.current && triggerRef.current) {
|
||||
const menuElemenet = menuRef.current;
|
||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
|
||||
const menuHeight = menuElemenet.offsetHeight;
|
||||
const menuWidth = menuElemenet.offsetWidth;
|
||||
|
||||
const viewportHeight = window.innerHeight;
|
||||
const viewportWidth = window.innerWidth;
|
||||
|
||||
const viewportX = containerRect.left + initialPosition.x;
|
||||
const viewportY = containerRect.top + initialPosition.y;
|
||||
|
||||
let adjustedX = initialPosition.x;
|
||||
let adjustedY = initialPosition.y;
|
||||
|
||||
if (viewportX + menuWidth > viewportWidth) {
|
||||
adjustedX = initialPosition.x - menuWidth + triggerRect.width;
|
||||
if (containerRect.left + adjustedX < 0) {
|
||||
adjustedX = -containerRect.left + 5; // 留5px边距
|
||||
}
|
||||
}
|
||||
|
||||
if (viewportY + menuHeight > viewportHeight) {
|
||||
adjustedY = initialPosition.y - menuHeight - triggerRect.height;
|
||||
if (containerRect.top + adjustedY < 0) {
|
||||
adjustedY = -containerRect.top + 5; // 留5px边距
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustedX !== renderPosition.x || adjustedY !== renderPosition.y) {
|
||||
setRenderPosition({ x: adjustedX, y: adjustedY });
|
||||
}
|
||||
}
|
||||
}, [isOpen, initialPosition, renderPosition.x, renderPosition.y]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.context_menu_locationer}
|
||||
ref={containerRef}
|
||||
onMouseLeave={handleLeaveClose}>
|
||||
<ActionIcon
|
||||
icon="tabler:dots-vertical"
|
||||
extendClassName={styles.action_icon}
|
||||
onClick={handleOpenMenu}
|
||||
ref={triggerRef}
|
||||
/>
|
||||
{isOpen && (
|
||||
<ContextMenuBody
|
||||
color={color}
|
||||
afterClick={handleCloseMenu}
|
||||
x={renderPosition.x}
|
||||
y={renderPosition.y}
|
||||
ref={menuRef}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
|
@ -11,12 +11,19 @@
|
|||
.color_block {
|
||||
flex: 1 0;
|
||||
}
|
||||
.color_value {
|
||||
.operate_row {
|
||||
padding: var(--spacing-xxs) var(--spacing-xs);
|
||||
font-size: var(--font-size-xs);
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
.color_value {
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useColorFunction } from '../ColorFunctionContext';
|
||||
import { useCopyColor } from '../hooks/useCopyColor';
|
||||
import ContextMenu from './ContextMenu';
|
||||
import styles from './FlexColorStand.module.css';
|
||||
|
||||
type FlexColorStandProps = {
|
||||
|
@ -51,8 +52,11 @@ export function FlexColorStand({ color, valueMode = 'hex' }: FlexColorStandProps
|
|||
return (
|
||||
<div className={styles.color_stand}>
|
||||
<div className={styles.color_block} style={{ backgroundColor: bgColor }} />
|
||||
<div className={styles.color_value} onClick={() => copyToClipboard(colorValue)}>
|
||||
{bgColor}
|
||||
<div className={styles.operate_row}>
|
||||
<div className={styles.color_value} onClick={() => copyToClipboard(colorValue)}>
|
||||
{bgColor}
|
||||
</div>
|
||||
<ContextMenu color={color} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
line-height: var(--font-size-xxs);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-xs);
|
||||
cursor: pointer;
|
||||
}
|
||||
.color_block {
|
||||
width: 100%;
|
||||
|
@ -19,6 +18,7 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
|
@ -38,5 +38,6 @@
|
|||
}
|
||||
.color_value {
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { capitalize, isEmpty } from 'lodash-es';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import ContextMenu from '../../components/ContextMenu';
|
||||
import { useCopyColor } from '../../hooks/useCopyColor';
|
||||
import { ColorDescription } from '../../models';
|
||||
import styles from './ColorCard.module.css';
|
||||
|
@ -57,7 +58,7 @@ export function ColorCard({ color, copyMode }: ColorCardProps) {
|
|||
}, [copytToClipboard, color, copyMode, colorHex]);
|
||||
|
||||
return (
|
||||
<div className={styles.card} onClick={handleCopy}>
|
||||
<div className={styles.card}>
|
||||
<div
|
||||
className={styles.color_block}
|
||||
style={{ backgroundColor: `rgb(${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]})` }}
|
||||
|
@ -69,7 +70,10 @@ export function ColorCard({ color, copyMode }: ColorCardProps) {
|
|||
<span className={styles.en_name}>{color.pinyin.map(capitalize).join(' ')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.color_value}>#{colorHex}</div>
|
||||
<div className={styles.color_value} onClick={handleCopy}>
|
||||
#{colorHex}
|
||||
</div>
|
||||
<ContextMenu color={colorHex} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@layer pages {
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 var(--spacing-m);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -9,6 +10,7 @@
|
|||
font-size: var(--font-size-m);
|
||||
}
|
||||
.color_blocks {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
@ -16,6 +18,7 @@
|
|||
flex-wrap: nowrap;
|
||||
gap: var(--spacing-s);
|
||||
.color_block {
|
||||
max-height: 23em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
@ -42,14 +45,25 @@
|
|||
padding-inline: var(--spacing-s);
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
.color_code {
|
||||
.color_code_row {
|
||||
height: 1.5em;
|
||||
padding-inline: var(--spacing-s);
|
||||
font-size: var(--font-size-s);
|
||||
text-transform: uppercase;
|
||||
text-align: right;
|
||||
> span {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
overflow: visible;
|
||||
.color_code {
|
||||
height: 1.5em;
|
||||
padding-inline: var(--spacing-s);
|
||||
font-size: var(--font-size-s);
|
||||
text-transform: uppercase;
|
||||
text-align: right;
|
||||
> span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import cx from 'clsx';
|
||||
import { constant, flatten, isEqual, take, times } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import ContextMenu from '../../components/ContextMenu';
|
||||
import { useCopyColor } from '../../hooks/useCopyColor';
|
||||
import { HarmonyColor } from '../../models';
|
||||
import styles from './HarmonyPreview.module.css';
|
||||
|
@ -33,8 +34,11 @@ export function HarmonyPreview({ colors = [] }: HarmonyPreviewProps) {
|
|||
style={{ flexGrow: ratio }}>
|
||||
<div className={styles.color_ratio}>{ratio > 0 && `Ratio: ${ratio}`}</div>
|
||||
<div className={styles.color_square} style={{ backgroundColor: `#${color}` }}></div>
|
||||
<div className={styles.color_code}>
|
||||
{ratio > 0 && <span onClick={() => copyColor(color)}>#{color}</span>}
|
||||
<div className={styles.color_code_row}>
|
||||
<div className={styles.color_code}>
|
||||
{ratio > 0 && <span onClick={() => copyColor(color)}>#{color}</span>}
|
||||
</div>
|
||||
<ContextMenu color={color} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -31,7 +31,7 @@ function SchemeItem({ item }: SchemeItemProps) {
|
|||
const isActived = useMemo(() => isEqual(activedScheme, item.id), [activedScheme, item.id]);
|
||||
const isSelected = useMemo(() => isEqual(navParams['id'], item.id), [navParams, item.id]);
|
||||
const handleActiveScheme = useCallback(() => {
|
||||
setActiveScheme((prev) => (prev ? null : item.id));
|
||||
setActiveScheme((prev) => (prev === item.id ? null : item.id));
|
||||
}, [item]);
|
||||
const handleRemoveScheme = useCallback(() => {
|
||||
removeScheme();
|
||||
|
|
|
@ -70,7 +70,7 @@ export function useScheme(id?: string | null): SchemeContent<SchemeStorage> | nu
|
|||
|
||||
export function useActiveScheme(): SchemeContent<SchemeStorage> | null {
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const activeScheme = useScheme(activeSchemeId ?? 'UNEXISTS');
|
||||
const activeScheme = useScheme(activeSchemeId ?? null);
|
||||
return activeScheme;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user