Compare commits
No commits in common. "9606106c4566cc83458e818d13bede866f15b31e" and "e0d35d279f703dfae5bf15d8fe7b6799fbbdea47" have entirely different histories.
9606106c45
...
e0d35d279f
@ -29,7 +29,6 @@ const routes = createBrowserRouter([
|
|||||||
path: 'schemes',
|
path: 'schemes',
|
||||||
element: <Schemes />,
|
element: <Schemes />,
|
||||||
children: [
|
children: [
|
||||||
{ index: true, element: <div /> },
|
|
||||||
{ path: 'new', element: <NewScheme /> },
|
{ path: 'new', element: <NewScheme /> },
|
||||||
{ path: 'not-found', element: <SchemeNotFound /> },
|
{ path: 'not-found', element: <SchemeNotFound /> },
|
||||||
{ path: ':id', element: <SchemeDetail /> },
|
{ path: ':id', element: <SchemeDetail /> },
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
@layer components {
|
|
||||||
.action_icon {
|
|
||||||
padding: var(--spacing-xs);
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--border-radius-xxs);
|
|
||||||
line-height: 1em;
|
|
||||||
.icon {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Icon, IconProps } from '@iconify/react/dist/iconify.js';
|
|
||||||
import cx from 'clsx';
|
|
||||||
import { MouseEventHandler, useCallback } from 'react';
|
|
||||||
import styles from './ActionIcon.module.css';
|
|
||||||
|
|
||||||
type ActionIconProps = {
|
|
||||||
icon: IconProps['icon'];
|
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
||||||
extendClassName?: HTMLButtonElement['className'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ActionIcon({ icon, onClick, extendClassName }: ActionIconProps) {
|
|
||||||
const handleClick = useCallback(
|
|
||||||
(event: MouseEvent<HTMLButtonElement>) => {
|
|
||||||
onClick?.(event);
|
|
||||||
},
|
|
||||||
[onClick],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button onClick={handleClick} className={cx(styles.action_icon, extendClassName)}>
|
|
||||||
<Icon icon={icon} className={styles.icon} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
@layer components {
|
|
||||||
.badge {
|
|
||||||
padding: var(--spacing-xxs) var(--spacing-xs);
|
|
||||||
border-radius: var(--border-radius-xxs);
|
|
||||||
flex: 0 0 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 1.2em;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import cx from 'clsx';
|
|
||||||
import { ReactNode } from 'react';
|
|
||||||
import styles from './Badge.module.css';
|
|
||||||
|
|
||||||
type BadgeProps = {
|
|
||||||
extendClassName?: HTMLDivElement['className'];
|
|
||||||
children?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function Badge({ extendClassName, children }: BadgeProps) {
|
|
||||||
return <div className={cx(styles.badge, extendClassName)}>{children}</div>;
|
|
||||||
}
|
|
@ -8,15 +8,9 @@ type HSegmentedControlProps = {
|
|||||||
options?: Option[];
|
options?: Option[];
|
||||||
value?: Option['value'];
|
value?: Option['value'];
|
||||||
onChange?: (value: Option['value']) => void;
|
onChange?: (value: Option['value']) => void;
|
||||||
extendClassName?: HTMLDivElement['className'];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function HSegmentedControl({
|
export function HSegmentedControl({ options = [], value, onChange }: HSegmentedControlProps) {
|
||||||
options = [],
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
extendClassName,
|
|
||||||
}: HSegmentedControlProps) {
|
|
||||||
const [selected, setSelected] = useState(value ?? options[0].value ?? null);
|
const [selected, setSelected] = useState(value ?? options[0].value ?? null);
|
||||||
const [sliderPosition, setSliderPosition] = useState(0);
|
const [sliderPosition, setSliderPosition] = useState(0);
|
||||||
const [sliderWidth, setSliderWidth] = useState(0);
|
const [sliderWidth, setSliderWidth] = useState(0);
|
||||||
@ -34,7 +28,7 @@ export function HSegmentedControl({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.segmented_control, extendClassName)}>
|
<div className={styles.segmented_control}>
|
||||||
<div className={styles.options}>
|
<div className={styles.options}>
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<div
|
<div
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
@layer components {
|
|
||||||
.badge {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
color: var(--color-yuebai);
|
|
||||||
background-color: var(--color-mose);
|
|
||||||
&.q {
|
|
||||||
background-color: var(--color-mantianxingzi);
|
|
||||||
}
|
|
||||||
&.swatch {
|
|
||||||
background-color: var(--color-pinlan);
|
|
||||||
}
|
|
||||||
&.m2 {
|
|
||||||
background-color: #03dac6;
|
|
||||||
color: var(--color-qihei);
|
|
||||||
}
|
|
||||||
&.m3 {
|
|
||||||
background-color: #a78fff;
|
|
||||||
color: var(--color-qihei);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
import cx from 'clsx';
|
|
||||||
import { isNil } from 'lodash-es';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { schemeType, SchemeType } from '../models';
|
|
||||||
import { Badge } from './Badge';
|
|
||||||
import styles from './SchemeSign.module.css';
|
|
||||||
|
|
||||||
type SchemeSignProps = {
|
|
||||||
scheme?: SchemeType;
|
|
||||||
short?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SchemeSign({ scheme, short = false }: SchemeSignProps) {
|
|
||||||
const schemeName = schemeType(scheme, short);
|
|
||||||
const signColorStyles = useMemo(() => {
|
|
||||||
switch (scheme) {
|
|
||||||
case 'q_scheme':
|
|
||||||
return styles.q;
|
|
||||||
case 'swatch_scheme':
|
|
||||||
return styles.swatch;
|
|
||||||
case 'material_2':
|
|
||||||
return styles.m2;
|
|
||||||
case 'material_3':
|
|
||||||
return styles.m3;
|
|
||||||
}
|
|
||||||
}, [scheme]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
!isNil(scheme) && (
|
|
||||||
<Badge extendClassName={cx(styles.badge, signColorStyles)}>{schemeName}</Badge>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
@ -8,15 +8,9 @@ type VSegmentedControlProps = {
|
|||||||
options?: Option[];
|
options?: Option[];
|
||||||
value?: Option['value'];
|
value?: Option['value'];
|
||||||
onChange?: (value: Option['value']) => void;
|
onChange?: (value: Option['value']) => void;
|
||||||
extendClassName?: HTMLDivElement['className'];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function VSegmentedControl({
|
export function VSegmentedControl({ options = [], value, onChange }: VSegmentedControlProps) {
|
||||||
options = [],
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
extendClassName,
|
|
||||||
}: VSegmentedControlProps) {
|
|
||||||
const [selected, setSelected] = useState(value ?? options[0].value ?? null);
|
const [selected, setSelected] = useState(value ?? options[0].value ?? null);
|
||||||
const [sliderPosition, setSliderPosition] = useState(0);
|
const [sliderPosition, setSliderPosition] = useState(0);
|
||||||
const [sliderHeight, setSliderHeight] = useState(0);
|
const [sliderHeight, setSliderHeight] = useState(0);
|
||||||
@ -34,7 +28,7 @@ export function VSegmentedControl({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.segmented_control, extendClassName)}>
|
<div className={styles.segmented_control}>
|
||||||
<div className={styles.options}>
|
<div className={styles.options}>
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<div
|
<div
|
||||||
|
@ -28,7 +28,7 @@ export type MaterialDesign2SchemeSource = {
|
|||||||
custom_colors: Record<string, string>;
|
custom_colors: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MaterialDesign2SchemeStorage = {
|
export type Materialdesign2SchemeStorage = {
|
||||||
source: MaterialDesign2SchemeSource;
|
source: MaterialDesign2SchemeSource;
|
||||||
scheme: MaterialDesign2Scheme;
|
scheme: MaterialDesign2Scheme;
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
import { find, isNil } from 'lodash-es';
|
|
||||||
import { MaterialDesign2SchemeStorage } from './material-2-scheme';
|
|
||||||
import { MaterialDesign3SchemeStorage } from './material-3-scheme';
|
|
||||||
import { QSchemeStorage } from './q-scheme';
|
|
||||||
import { SwatchSchemeStorage } from './swatch_scheme';
|
|
||||||
|
|
||||||
export type Option = {
|
export type Option = {
|
||||||
label: string;
|
label: string;
|
||||||
value: string | number | null;
|
value: string | number | null;
|
||||||
@ -28,30 +22,6 @@ export type ColorDescription = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SchemeType = 'q_scheme' | 'swatch_scheme' | 'material_2' | 'material_3';
|
export type SchemeType = 'q_scheme' | 'swatch_scheme' | 'material_2' | 'material_3';
|
||||||
export type SchemeTypeOption = {
|
|
||||||
label: string;
|
|
||||||
short: string;
|
|
||||||
value: SchemeType;
|
|
||||||
};
|
|
||||||
export const SchemeTypeOptions: SchemeTypeOption[] = [
|
|
||||||
{ label: 'Q Scheme', short: 'Q', value: 'q_scheme' },
|
|
||||||
{ label: 'Swatch Scheme', short: 'Swatch', value: 'swatch_scheme' },
|
|
||||||
{ label: 'Material Design 2 Scheme', short: 'M2', value: 'material_2' },
|
|
||||||
{ label: 'Material Design 3 Scheme', short: 'M3', value: 'material_3' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function schemeType(
|
|
||||||
value?: SchemeTypeOption['value'] | null,
|
|
||||||
short?: boolean,
|
|
||||||
): string | null {
|
|
||||||
const useShort = short ?? false;
|
|
||||||
const foundType = find(SchemeTypeOptions, { value }) as SchemeTypeOption | undefined;
|
|
||||||
if (isNil(foundType)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return useShort ? foundType.short : foundType.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SchemeContent<SchemeStorage> = {
|
export type SchemeContent<SchemeStorage> = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -65,9 +35,3 @@ export type ColorShifting = {
|
|||||||
chroma: number;
|
chroma: number;
|
||||||
lightness: number;
|
lightness: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemeStorage =
|
|
||||||
| QSchemeStorage
|
|
||||||
| SwatchSchemeStorage
|
|
||||||
| MaterialDesign2SchemeStorage
|
|
||||||
| MaterialDesign3SchemeStorage;
|
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: var(--spacing-s);
|
|
||||||
.scheme_item {
|
.scheme_item {
|
||||||
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 8);
|
padding: calc(var(--spacing) * 2) calc(var(--spacing) * 8);
|
||||||
&.selected {
|
&.selected {
|
||||||
@ -54,42 +53,6 @@
|
|||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
.delete_btn {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
color: var(--color-yuebai);
|
|
||||||
background-color: oklch(from var(--color-danger) l c h / 0.25);
|
|
||||||
&:hover {
|
|
||||||
background-color: oklch(from var(--color-danger-hover) l c h / 0.65);
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
background-color: oklch(from var(--color-danger-active) l c h / 0.65);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.active_btn {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
color: var(--color-yuebai);
|
|
||||||
background-color: oklch(from var(--color-info) l c h / 0.25);
|
|
||||||
&:hover {
|
|
||||||
background-color: oklch(from var(--color-info-hover) l c h / 0.65);
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
background-color: oklch(from var(--color-info-active) l c h / 0.65);
|
|
||||||
}
|
|
||||||
&.deactive {
|
|
||||||
background-color: oklch(from var(--color-warn) l c h / 0.25);
|
|
||||||
&:hover {
|
|
||||||
background-color: oklch(from var(--color-warn-hover) l c h / 0.65);
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
background-color: oklch(from var(--color-warn-active) l c h / 0.65);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.active_badge {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
color: var(--color-yuebai);
|
|
||||||
background-color: var(--color-success);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.empty_prompt {
|
.empty_prompt {
|
||||||
padding-inline: calc(var(--spacing) * 6);
|
padding-inline: calc(var(--spacing) * 6);
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import { useAtom } from 'jotai';
|
import dayjs from 'dayjs';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import { isEmpty, isEqual } from 'lodash-es';
|
import { isEmpty, isEqual } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Link, useNavigate, useParams } from 'react-router-dom';
|
import { Link, useNavigate, useParams } from 'react-router-dom';
|
||||||
import { ActionIcon } from '../../components/ActionIcon';
|
import { activeSchemeAtom, useSchemeList } from '../../stores/schemes';
|
||||||
import { Badge } from '../../components/Badge';
|
|
||||||
import { SchemeSign } from '../../components/SchemeSign';
|
|
||||||
import { activeSchemeAtom, useRemoveScheme, useSchemeList } from '../../stores/schemes';
|
|
||||||
import styles from './SchemeList.module.css';
|
import styles from './SchemeList.module.css';
|
||||||
|
|
||||||
function OperateButtons() {
|
function OperateButtons() {
|
||||||
@ -26,20 +25,9 @@ type SchemeItemProps = {
|
|||||||
function SchemeItem({ item }: SchemeItemProps) {
|
function SchemeItem({ item }: SchemeItemProps) {
|
||||||
const navParams = useParams();
|
const navParams = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [activedScheme, setActiveScheme] = useAtom(activeSchemeAtom);
|
const activedScheme = useAtomValue(activeSchemeAtom);
|
||||||
const removeScheme = useRemoveScheme(item.id);
|
|
||||||
const isActived = useMemo(() => isEqual(activedScheme, item.id), [activedScheme, item.id]);
|
const isActived = useMemo(() => isEqual(activedScheme, item.id), [activedScheme, item.id]);
|
||||||
const isSelected = useMemo(() => isEqual(navParams['id'], item.id), [navParams, item.id]);
|
const isSelected = useMemo(() => isEqual(navParams['id'], item.id), [navParams, item.id]);
|
||||||
const handleActiveScheme = useCallback(() => {
|
|
||||||
setActiveScheme((prev) => (prev ? null : item.id));
|
|
||||||
}, [item]);
|
|
||||||
const handleRemoveScheme = useCallback(() => {
|
|
||||||
removeScheme();
|
|
||||||
if (isActived) {
|
|
||||||
setActiveScheme(null);
|
|
||||||
}
|
|
||||||
navigate(-1);
|
|
||||||
}, [item, isActived]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -47,23 +35,11 @@ function SchemeItem({ item }: SchemeItemProps) {
|
|||||||
onClick={() => navigate(item.id)}>
|
onClick={() => navigate(item.id)}>
|
||||||
<div className={styles.name}>{item.name}</div>
|
<div className={styles.name}>{item.name}</div>
|
||||||
<div className={styles.status}>
|
<div className={styles.status}>
|
||||||
<SchemeSign scheme={item.type} short />
|
<div className={styles.create_time}>
|
||||||
{isActived && <Badge extendClassName={styles.active_badge}>ACTIVE</Badge>}
|
created at {dayjs(item.createdAt).format('YYYY-MM-DD')}
|
||||||
|
</div>
|
||||||
<div className="spacer"></div>
|
<div className="spacer"></div>
|
||||||
{isSelected && (
|
{isActived && <Icon icon="tabler:check" className={styles.active_icon} />}
|
||||||
<>
|
|
||||||
<ActionIcon
|
|
||||||
icon="tabler:trash"
|
|
||||||
extendClassName={styles.delete_btn}
|
|
||||||
onClick={handleRemoveScheme}
|
|
||||||
/>
|
|
||||||
<ActionIcon
|
|
||||||
icon="tabler:checkbox"
|
|
||||||
extendClassName={cx(styles.active_btn, isActived && styles.deactive)}
|
|
||||||
onClick={handleActiveScheme}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -25,7 +25,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.custom_segment {
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import { isEmpty, isNil } from 'lodash-es';
|
import { isEmpty, isNil } from 'lodash-es';
|
||||||
import { useActionState, useState } from 'react';
|
import { useActionState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { VSegmentedControl } from '../components/VSegmentedControl';
|
|
||||||
import { SchemeTypeOption, SchemeTypeOptions } from '../models';
|
|
||||||
import { useCreateScheme } from '../stores/schemes';
|
import { useCreateScheme } from '../stores/schemes';
|
||||||
import styles from './NewScheme.module.css';
|
import styles from './NewScheme.module.css';
|
||||||
|
|
||||||
export function NewScheme() {
|
export function NewScheme() {
|
||||||
const createScheme = useCreateScheme();
|
const createScheme = useCreateScheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [schemeType, setSchemeType] = useState<SchemeTypeOption['value']>('q_scheme');
|
|
||||||
const [errors, formAction] = useActionState((prevState, formData) => {
|
const [errors, formAction] = useActionState((prevState, formData) => {
|
||||||
try {
|
try {
|
||||||
const name = formData.get('name') as string;
|
const name = formData.get('name') as string;
|
||||||
@ -18,8 +15,7 @@ export function NewScheme() {
|
|||||||
throw { name: 'Name is required' };
|
throw { name: 'Name is required' };
|
||||||
}
|
}
|
||||||
const description = (formData.get('description') ?? null) as string | null;
|
const description = (formData.get('description') ?? null) as string | null;
|
||||||
const schemeType = (formData.get('type') ?? 'q_scheme') as SchemeTypeOption['value'];
|
const newId = createScheme(name, description);
|
||||||
const newId = createScheme(name, schemeType, description);
|
|
||||||
navigate(`../${newId}`);
|
navigate(`../${newId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
@ -29,18 +25,6 @@ export function NewScheme() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form action={formAction} className={styles.create_scheme_form_layout}>
|
<form action={formAction} className={styles.create_scheme_form_layout}>
|
||||||
<div className={styles.form_row}>
|
|
||||||
<label>
|
|
||||||
Scheme Type <span>*</span>
|
|
||||||
</label>
|
|
||||||
<VSegmentedControl
|
|
||||||
options={SchemeTypeOptions}
|
|
||||||
extendClassName={styles.custom_segment}
|
|
||||||
value={schemeType}
|
|
||||||
onChange={setSchemeType}
|
|
||||||
/>
|
|
||||||
<input type="hidden" name="type" value={schemeType} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.form_row}>
|
<div className={styles.form_row}>
|
||||||
<label>
|
<label>
|
||||||
Name <span>*</span>
|
Name <span>*</span>
|
||||||
|
@ -7,16 +7,5 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.badge_layout {
|
|
||||||
padding: var(--spacing-xs) var(--spacing-m);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
.create_time {
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
color: var(--color-neutral);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import dayjs from 'dayjs';
|
|
||||||
import { isNil, set } from 'lodash-es';
|
import { isNil, set } from 'lodash-es';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { EditableDescription } from '../components/EditableDescription';
|
import { EditableDescription } from '../components/EditableDescription';
|
||||||
import { EditableTitle } from '../components/EditableTitle';
|
import { EditableTitle } from '../components/EditableTitle';
|
||||||
import { SchemeSign } from '../components/SchemeSign';
|
import { Tab } from '../components/Tab';
|
||||||
|
import { SchemeView } from '../page-components/scheme/SchemeView';
|
||||||
import { useScheme, useUpdateScheme } from '../stores/schemes';
|
import { useScheme, useUpdateScheme } from '../stores/schemes';
|
||||||
import styles from './SchemeDetail.module.css';
|
import styles from './SchemeDetail.module.css';
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ export function SchemeDetail() {
|
|||||||
const scheme = useScheme(id);
|
const scheme = useScheme(id);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const updateScheme = useUpdateScheme(id);
|
const updateScheme = useUpdateScheme(id);
|
||||||
|
const [showScheme, setShowScheme] = useState<'light' | 'dark'>('light');
|
||||||
|
|
||||||
const updateTitle = useCallback(
|
const updateTitle = useCallback(
|
||||||
(newTitle: string) => {
|
(newTitle: string) => {
|
||||||
@ -42,13 +43,15 @@ export function SchemeDetail() {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.scheme_detail_layout}>
|
<div className={styles.scheme_detail_layout}>
|
||||||
<EditableTitle title={scheme?.name} onChange={updateTitle} />
|
<EditableTitle title={scheme?.name} onChange={updateTitle} />
|
||||||
<div className={styles.badge_layout}>
|
|
||||||
<SchemeSign scheme={scheme?.type} />
|
|
||||||
<div className={styles.create_time}>
|
|
||||||
created at {dayjs(scheme?.createdAt).format('YYYY-MM-DD')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<EditableDescription content={scheme?.description} onChange={updateDescription} />
|
<EditableDescription content={scheme?.description} onChange={updateDescription} />
|
||||||
|
<Tab
|
||||||
|
tabs={[
|
||||||
|
{ title: 'Light Scheme', id: 'light' },
|
||||||
|
{ title: 'Dark Scheme', id: 'dark' },
|
||||||
|
]}
|
||||||
|
onActive={(tabId) => setShowScheme(tabId as 'light' | 'dark')}
|
||||||
|
/>
|
||||||
|
<SchemeView scheme={scheme?.[`${showScheme}Scheme`]} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { atomWithStorage } from 'jotai/utils';
|
|||||||
import { isEqual, reduce } from 'lodash-es';
|
import { isEqual, reduce } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { SchemeContent, SchemeStorage, SchemeType } from '../models';
|
|
||||||
|
|
||||||
type ColorSet = {
|
type ColorSet = {
|
||||||
normal?: string | null;
|
normal?: string | null;
|
||||||
@ -39,42 +38,38 @@ export type SchemeSet = {
|
|||||||
darkScheme: Scheme;
|
darkScheme: Scheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
const schemesAtom = atomWithStorage<SchemeContent<SchemeStorage>[]>('schemes', []);
|
const schemesAtom = atomWithStorage<SchemeSet[]>('schemes', []);
|
||||||
export const activeSchemeAtom = atomWithStorage<string | null>('activeScheme', null);
|
export const activeSchemeAtom = atomWithStorage<string | null>('activeScheme', null);
|
||||||
|
|
||||||
export function useSchemeList(): Pick<SchemeContent<SchemeStorage>, 'id' | 'name' | 'createdAt'>[] {
|
export function useSchemeList(): Pick<SchemeSet, 'id' | 'name' | 'createdAt'>[] {
|
||||||
const schemes = useAtomValue(schemesAtom);
|
const schemes = useAtomValue(schemesAtom);
|
||||||
const sortedSchemes = useMemo(
|
const sortedSchemes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
schemes
|
schemes
|
||||||
.sort((a, b) => dayjs(b.createdAt).diff(dayjs(a.createdAt)))
|
.sort((a, b) => dayjs(b.createdAt).diff(dayjs(a.createdAt)))
|
||||||
.map(({ id, name, createdAt, type }) => ({ id, name, createdAt, type })),
|
.map(({ id, name, createdAt }) => ({ id, name, createdAt })),
|
||||||
[schemes],
|
[schemes],
|
||||||
);
|
);
|
||||||
|
|
||||||
return sortedSchemes;
|
return sortedSchemes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useScheme(id: string): SchemeContent<SchemeStorage> | null {
|
export function useScheme(id: string): SchemeSet | null {
|
||||||
const schemes = useAtomValue(schemesAtom);
|
const schemes = useAtomValue(schemesAtom);
|
||||||
const scheme = useMemo(() => schemes.find((s) => isEqual(id, s.id)) ?? null, [schemes, id]);
|
const scheme = useMemo(() => schemes.find((s) => isEqual(id, s.id)) ?? null, [schemes, id]);
|
||||||
return scheme;
|
return scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useActiveScheme(): SchemeContent<SchemeStorage> | null {
|
export function useActiveScheme(): SchemeSet | null {
|
||||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||||
const activeScheme = useScheme(activeSchemeId ?? 'UNEXISTS');
|
const activeScheme = useScheme(activeSchemeId ?? 'UNEXISTS');
|
||||||
return activeScheme;
|
return activeScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateScheme(): (
|
export function useCreateScheme(): (name: string, description?: string) => string {
|
||||||
name: string,
|
|
||||||
type: SchemeType,
|
|
||||||
description?: string,
|
|
||||||
) => string {
|
|
||||||
const updateSchemes = useSetAtom(schemesAtom);
|
const updateSchemes = useSetAtom(schemesAtom);
|
||||||
const createSchemeAction = useCallback(
|
const createSchemeAction = useCallback(
|
||||||
(name: string, type: SchemeType, description?: string) => {
|
(name: string, description?: string) => {
|
||||||
const newId = v4();
|
const newId = v4();
|
||||||
updateSchemes((prev) => [
|
updateSchemes((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
@ -83,8 +78,8 @@ export function useCreateScheme(): (
|
|||||||
name,
|
name,
|
||||||
createdAt: dayjs().toISOString(),
|
createdAt: dayjs().toISOString(),
|
||||||
description: description ?? null,
|
description: description ?? null,
|
||||||
type,
|
lightScheme: {},
|
||||||
schemeStorage: {},
|
darkScheme: {},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
return newId;
|
return newId;
|
||||||
@ -95,12 +90,10 @@ export function useCreateScheme(): (
|
|||||||
return createSchemeAction;
|
return createSchemeAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUpdateScheme(
|
export function useUpdateScheme(id: string): (updater: (prev: SchemeSet) => SchemeSet) => void {
|
||||||
id: string,
|
|
||||||
): (updater: (prev: SchemeContent<SchemeStorage>) => SchemeContent<SchemeStorage>) => void {
|
|
||||||
const updateSchemes = useSetAtom(schemesAtom);
|
const updateSchemes = useSetAtom(schemesAtom);
|
||||||
const updateAction = useCallback(
|
const updateAction = useCallback(
|
||||||
(updater: (prev: SchemeContent<SchemeStorage>) => SchemeContent<SchemeStorage>) => {
|
(updater: (prev: SchemeSet) => SchemeSet) => {
|
||||||
updateSchemes((prev) =>
|
updateSchemes((prev) =>
|
||||||
reduce(
|
reduce(
|
||||||
prev,
|
prev,
|
||||||
@ -112,7 +105,7 @@ export function useUpdateScheme(
|
|||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
[] as SchemeContent<SchemeStorage>[],
|
[] as SchemeSet[],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user