增加方案列表的布局。
This commit is contained in:
parent
d5ba52d660
commit
6f3e051654
10
src/App.tsx
10
src/App.tsx
|
@ -2,9 +2,17 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||||
import { Notifications } from './components/Notifications';
|
import { Notifications } from './components/Notifications';
|
||||||
import { Home } from './pages/Home';
|
import { Home } from './pages/Home';
|
||||||
import { MainLayout } from './pages/MainLayout';
|
import { MainLayout } from './pages/MainLayout';
|
||||||
|
import { Schemes } from './pages/Schemes';
|
||||||
|
|
||||||
const routes = createBrowserRouter([
|
const routes = createBrowserRouter([
|
||||||
{ path: '/', element: <MainLayout />, children: [{ index: true, element: <Home /> }] },
|
{
|
||||||
|
path: '/',
|
||||||
|
element: <MainLayout />,
|
||||||
|
children: [
|
||||||
|
{ index: true, element: <Home /> },
|
||||||
|
{ path: 'schemes', element: <Schemes /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
|
|
33
src/page-components/schemes/SchemeList.module.css
Normal file
33
src/page-components/schemes/SchemeList.module.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
@layer pages {
|
||||||
|
.scheme_list {
|
||||||
|
max-width: calc(var(--spacing) * 125);
|
||||||
|
flex: 1 1 calc(var(--spacing) * 125);
|
||||||
|
padding: calc(var(--spacing) * 4) 0;
|
||||||
|
box-shadow: 2px 0 8px oklch(from var(--color-black) l c h / 65%);
|
||||||
|
z-index: 40;
|
||||||
|
.operates_buttons {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(var(--spacing) * 2);
|
||||||
|
padding: calc(var(--spacing) * 4) calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
|
.scheme_items {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.empty_prompt {
|
||||||
|
padding-inline: calc(var(--spacing) * 6);
|
||||||
|
padding-block: calc(var(--spacing) * 4);
|
||||||
|
font-size: var(--font-size-s);
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-neutral-focus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
src/page-components/schemes/SchemeList.tsx
Normal file
59
src/page-components/schemes/SchemeList.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { isEmpty, isEqual } from 'lodash-es';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { activeSchemeAtom, useSchemeList } from '../../stores/schemes';
|
||||||
|
import styles from './SchemeList.module.css';
|
||||||
|
|
||||||
|
function OperateButtons() {
|
||||||
|
return (
|
||||||
|
<div className={styles.operates_buttons}>
|
||||||
|
<Link to="new" className="button">
|
||||||
|
New Scheme
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchemeItemProps = {
|
||||||
|
item: ReturnType<typeof useSchemeList>[number];
|
||||||
|
};
|
||||||
|
|
||||||
|
function SchemeItem({ item }: SchemeItemProps) {
|
||||||
|
const activedScheme = useAtomValue(activeSchemeAtom);
|
||||||
|
const isActived = useMemo(() => isEqual(activedScheme, item.id), [activedScheme, item.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>{item.name}</div>
|
||||||
|
<div>
|
||||||
|
<div>created at {dayjs(item.createdAt).format('YYYY-MM-DD')}</div>
|
||||||
|
<div>{isActived && <Icon icon="tabler:check" />}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SchemesItems() {
|
||||||
|
const schemes = useSchemeList();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.scheme_items}>
|
||||||
|
{isEmpty(schemes) && <div className={styles.empty_prompt}>Create a scheme first.</div>}
|
||||||
|
{schemes.map((item) => (
|
||||||
|
<SchemeItem key={item.id} item={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SchemeList() {
|
||||||
|
return (
|
||||||
|
<div className={styles.scheme_list}>
|
||||||
|
<OperateButtons />
|
||||||
|
<SchemesItems />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
13
src/pages/Schemes.module.css
Normal file
13
src/pages/Schemes.module.css
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@layer pages {
|
||||||
|
.schemes_workspace {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.scheme_operates {
|
||||||
|
flex: 1 0;
|
||||||
|
}
|
||||||
|
}
|
14
src/pages/Schemes.tsx
Normal file
14
src/pages/Schemes.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { SchemeList } from '../page-components/schemes/SchemeList';
|
||||||
|
import styles from './Schemes.module.css';
|
||||||
|
|
||||||
|
export function Schemes() {
|
||||||
|
return (
|
||||||
|
<div className={styles.schemes_workspace}>
|
||||||
|
<SchemeList />
|
||||||
|
<div className={styles.scheme_operates}>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
90
src/stores/schemes.ts
Normal file
90
src/stores/schemes.ts
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
|
import { atomWithStorage } from 'jotai/utils';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
type ColorSet = {
|
||||||
|
normal?: string | null;
|
||||||
|
hover?: string | null;
|
||||||
|
active?: string | null;
|
||||||
|
focus?: string | null;
|
||||||
|
disabled?: string | null;
|
||||||
|
lighten?: string | null;
|
||||||
|
darken?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Scheme = {
|
||||||
|
primary?: ColorSet | null;
|
||||||
|
secondary?: ColorSet | ColorSet[] | null;
|
||||||
|
accent?: ColorSet | null;
|
||||||
|
neutral?: ColorSet | null;
|
||||||
|
foreground?: ColorSet | null;
|
||||||
|
background?: ColorSet | null;
|
||||||
|
danger?: ColorSet | null;
|
||||||
|
warning?: ColorSet | null;
|
||||||
|
success?: ColorSet | null;
|
||||||
|
info?: ColorSet | null;
|
||||||
|
border?: ColorSet | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SchemeSet = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
createdAt: string;
|
||||||
|
description: string | null;
|
||||||
|
lightScheme: Scheme;
|
||||||
|
darkScheme: Scheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
const schemesAtom = atomWithStorage<SchemeSet[]>('schemes', []);
|
||||||
|
export const activeSchemeAtom = atomWithStorage<string | null>('activeScheme', null);
|
||||||
|
|
||||||
|
export function useSchemeList(): Pick<SchemeSet, 'id' | 'name' | 'createdAt'>[] {
|
||||||
|
const schemes = useAtomValue(schemesAtom);
|
||||||
|
const sortedSchemes = useMemo(
|
||||||
|
() =>
|
||||||
|
schemes
|
||||||
|
.sort((a, b) => dayjs(b.createdAt).diff(dayjs(a.createdAt)))
|
||||||
|
.map(({ id, name, createdAt }) => ({ id, name, createdAt })),
|
||||||
|
[schemes],
|
||||||
|
);
|
||||||
|
|
||||||
|
return sortedSchemes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useScheme(id: string): SchemeSet | null {
|
||||||
|
const schemes = useAtomValue(schemesAtom);
|
||||||
|
const scheme = useMemo(() => schemes.find((s) => s.id === id) ?? null, [schemes, id]);
|
||||||
|
return scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useActiveScheme(): SchemeSet | null {
|
||||||
|
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||||
|
const activeScheme = useScheme(activeSchemeId ?? 'UNEXISTS');
|
||||||
|
return activeScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreateScheme(): (name: string, description?: string) => string {
|
||||||
|
const updateSchemes = useSetAtom(schemesAtom);
|
||||||
|
const createSchemeAction = useCallback(
|
||||||
|
(name: string, description?: string) => {
|
||||||
|
const newId = v4();
|
||||||
|
updateSchemes((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
id: newId,
|
||||||
|
name,
|
||||||
|
createdAt: dayjs().toISOString(),
|
||||||
|
description: description ?? null,
|
||||||
|
lightScheme: {},
|
||||||
|
darkScheme: {},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return newId;
|
||||||
|
},
|
||||||
|
[updateSchemes],
|
||||||
|
);
|
||||||
|
|
||||||
|
return createSchemeAction;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user