增加方案列表的布局。
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 { 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() {
|
||||
|
|
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