增加方案列表的布局。

This commit is contained in:
徐涛 2024-12-25 15:35:35 +08:00
parent d5ba52d660
commit 6f3e051654
6 changed files with 218 additions and 1 deletions

View File

@ -2,9 +2,17 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Notifications } from './components/Notifications';
import { Home } from './pages/Home';
import { MainLayout } from './pages/MainLayout';
import { Schemes } from './pages/Schemes';
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() {

View 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);
}
}
}

View 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>
);
}

View 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
View 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
View 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;
}