diff --git a/src/App.tsx b/src/App.tsx
index a466f14..ae998d6 100644
--- a/src/App.tsx
+++ b/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: , children: [{ index: true, element: }] },
+ {
+ path: '/',
+ element: ,
+ children: [
+ { index: true, element: },
+ { path: 'schemes', element: },
+ ],
+ },
]);
export function App() {
diff --git a/src/page-components/schemes/SchemeList.module.css b/src/page-components/schemes/SchemeList.module.css
new file mode 100644
index 0000000..a0589d1
--- /dev/null
+++ b/src/page-components/schemes/SchemeList.module.css
@@ -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);
+ }
+ }
+}
diff --git a/src/page-components/schemes/SchemeList.tsx b/src/page-components/schemes/SchemeList.tsx
new file mode 100644
index 0000000..ab60db0
--- /dev/null
+++ b/src/page-components/schemes/SchemeList.tsx
@@ -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 (
+
+
+ New Scheme
+
+
+ );
+}
+
+type SchemeItemProps = {
+ item: ReturnType[number];
+};
+
+function SchemeItem({ item }: SchemeItemProps) {
+ const activedScheme = useAtomValue(activeSchemeAtom);
+ const isActived = useMemo(() => isEqual(activedScheme, item.id), [activedScheme, item.id]);
+
+ return (
+
+
{item.name}
+
+
created at {dayjs(item.createdAt).format('YYYY-MM-DD')}
+
{isActived && }
+
+
+ );
+}
+
+function SchemesItems() {
+ const schemes = useSchemeList();
+
+ return (
+
+ {isEmpty(schemes) &&
Create a scheme first.
}
+ {schemes.map((item) => (
+
+ ))}
+
+ );
+}
+
+export function SchemeList() {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/pages/Schemes.module.css b/src/pages/Schemes.module.css
new file mode 100644
index 0000000..5ef54b2
--- /dev/null
+++ b/src/pages/Schemes.module.css
@@ -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;
+ }
+}
diff --git a/src/pages/Schemes.tsx b/src/pages/Schemes.tsx
new file mode 100644
index 0000000..1161be0
--- /dev/null
+++ b/src/pages/Schemes.tsx
@@ -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 (
+
+ );
+}
diff --git a/src/stores/schemes.ts b/src/stores/schemes.ts
new file mode 100644
index 0000000..ff81ff1
--- /dev/null
+++ b/src/stores/schemes.ts
@@ -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('schemes', []);
+export const activeSchemeAtom = atomWithStorage('activeScheme', null);
+
+export function useSchemeList(): Pick[] {
+ 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;
+}