feat(store): add shared Zustand store package with counter example

- Create packages/store with @multica/store package
- Add zustand to pnpm catalog for version consistency
- Add counter store as cross-platform state example
- Integrate counter into ComponentExample for verification
- Add tsconfig path mappings for web and desktop
- Add @multica/store to Next.js transpilePackages
- Add @multica/store dependency to packages/ui

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing 2026-01-30 10:52:54 +08:00
parent eb5c388a80
commit d7d2861a79
11 changed files with 133 additions and 3 deletions

View file

@ -16,7 +16,8 @@
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@multica/ui/*": ["../../packages/ui/src/*"]
"@multica/ui/*": ["../../packages/ui/src/*"],
"@multica/store/*": ["../../packages/store/src/*"]
},
/* Linting */

View file

@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
transpilePackages: ["@multica/ui"],
transpilePackages: ["@multica/ui", "@multica/store"],
};
export default nextConfig;

View file

@ -20,7 +20,8 @@
],
"paths": {
"@/*": ["./*"],
"@multica/ui/*": ["../../packages/ui/src/*"]
"@multica/ui/*": ["../../packages/ui/src/*"],
"@multica/store/*": ["../../packages/store/src/*"]
}
},
"include": [

View file

@ -0,0 +1,15 @@
{
"name": "@multica/store",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
"./*": "./src/*.ts"
},
"dependencies": {
"zustand": "catalog:"
},
"devDependencies": {
"typescript": "catalog:"
}
}

View file

@ -0,0 +1,15 @@
import { create } from 'zustand'
interface CounterState {
count: number
increment: () => void
decrement: () => void
reset: () => void
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}))

View file

@ -0,0 +1 @@
export { useCounterStore } from './counter'

View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@multica/store/*": ["./src/*"]
}
},
"include": ["src"]
}

View file

@ -12,6 +12,7 @@
"./hooks/*": "./src/hooks/*.ts"
},
"dependencies": {
"@multica/store": "workspace:*",
"@base-ui/react": "^1.1.0",
"@hugeicons/core-free-icons": "^3.1.1",
"@hugeicons/react": "^1.1.4",

View file

@ -65,16 +65,52 @@ import {
import { Textarea } from "@multica/ui/components/ui/textarea"
import { HugeiconsIcon } from "@hugeicons/react"
import { PlusSignIcon, BluetoothIcon, MoreVerticalCircle01Icon, FileIcon, FolderIcon, FolderOpenIcon, CodeIcon, MoreHorizontalCircle01Icon, SearchIcon, FloppyDiskIcon, DownloadIcon, EyeIcon, LayoutIcon, PaintBoardIcon, SunIcon, MoonIcon, ComputerIcon, UserIcon, CreditCardIcon, SettingsIcon, KeyboardIcon, LanguageCircleIcon, NotificationIcon, MailIcon, ShieldIcon, HelpCircleIcon, File01Icon, LogoutIcon } from "@hugeicons/core-free-icons"
import { useCounterStore } from "@multica/store/counter"
export function ComponentExample() {
return (
<ExampleWrapper>
<CounterExample />
<CardExample />
<FormExample />
</ExampleWrapper>
)
}
function CounterExample() {
const { count, increment, decrement, reset } = useCounterStore()
return (
<Example title="Counter (Zustand Store)">
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Shared Counter</CardTitle>
<CardDescription>
This counter uses Zustand from @multica/store, shared across web and desktop.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center gap-4">
<Button variant="outline" size="icon" onClick={decrement}>
-
</Button>
<span className="text-4xl font-bold tabular-nums">{count}</span>
<Button variant="outline" size="icon" onClick={increment}>
+
</Button>
</div>
</CardContent>
<CardFooter className="justify-between">
<Button variant="ghost" onClick={reset}>
Reset
</Button>
<Badge variant="secondary">Count: {count}</Badge>
</CardFooter>
</Card>
</Example>
)
}
function CardExample() {
return (
<Example title="Card" className="items-center justify-center">

40
pnpm-lock.yaml generated
View file

@ -24,6 +24,9 @@ catalogs:
typescript:
specifier: ^5.9.3
version: 5.9.3
zustand:
specifier: ^5.0.0
version: 5.0.10
importers:
@ -241,6 +244,16 @@ importers:
specifier: ^5.9.3
version: 5.9.3
packages/store:
dependencies:
zustand:
specifier: 'catalog:'
version: 5.0.10(@types/react@19.2.10)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
devDependencies:
typescript:
specifier: 'catalog:'
version: 5.9.3
packages/ui:
dependencies:
'@base-ui/react':
@ -252,6 +265,9 @@ importers:
'@hugeicons/react':
specifier: ^1.1.4
version: 1.1.4(react@19.2.3)
'@multica/store':
specifier: workspace:*
version: link:../store
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@ -5712,6 +5728,24 @@ packages:
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
zustand@5.0.10:
resolution: {integrity: sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=18.0.0'
immer: '>=9.0.6'
react: '>=18.0.0'
use-sync-external-store: '>=1.2.0'
peerDependenciesMeta:
'@types/react':
optional: true
immer:
optional: true
react:
optional: true
use-sync-external-store:
optional: true
snapshots:
7zip-bin@5.2.0: {}
@ -12059,3 +12093,9 @@ snapshots:
zod@3.25.76: {}
zod@4.3.6: {}
zustand@5.0.10(@types/react@19.2.10)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)):
optionalDependencies:
'@types/react': 19.2.10
react: 19.2.3
use-sync-external-store: 1.6.0(react@19.2.3)

View file

@ -9,3 +9,4 @@ catalog:
"@types/react-dom": "^19"
"@types/node": "^25.0.10"
typescript: "^5.9.3"
zustand: "^5.0.0"