增加方案列表的布局。
This commit is contained in:
		
							
								
								
									
										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; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user