diff --git a/packages/ui/src/store/hooks.ts b/packages/ui/src/store/hooks.ts new file mode 100644 index 0000000..f21233f --- /dev/null +++ b/packages/ui/src/store/hooks.ts @@ -0,0 +1,26 @@ +import { useSyncExternalStore } from 'react'; +import { type FavolinkStoreObserver } from './store'; + +export function useFavolinkStore(store: FavolinkStoreObserver) { + const state = useSyncExternalStore( + store.subscribe, + store.getState, + store.getState, + ); + + return [state, store.setState] as const; +} + +export function useFavolinkStoreValue(store: FavolinkStoreObserver) { + const state = useSyncExternalStore( + store.subscribe, + store.getState, + store.getState, + ); + + return state; +} + +export function useSetFavolinkStore(store: FavolinkStoreObserver) { + return store.setState; +} diff --git a/packages/ui/src/store/index.ts b/packages/ui/src/store/index.ts new file mode 100644 index 0000000..a40f0a9 --- /dev/null +++ b/packages/ui/src/store/index.ts @@ -0,0 +1,11 @@ +/* eslint-disable @stylistic/padding-line-between-statements */ +export { + FavolinkStore, + type FavolinkStoreObserver, + createFavolinkStore, +} from './store'; +export { + useFavolinkStore, + useFavolinkStoreValue, + useSetFavolinkStore, +} from './hooks'; diff --git a/packages/ui/src/store/store.ts b/packages/ui/src/store/store.ts new file mode 100644 index 0000000..d75b23d --- /dev/null +++ b/packages/ui/src/store/store.ts @@ -0,0 +1,53 @@ +export interface FavolinkStoreObserver { + getState: () => T; + setState: (next: SetStateAction) => void; + subscribe: (listener: () => void) => () => void; + emitChange: () => void; +} + +type SetStateFn = (prevState: T) => T; + +type SetStateAction = SetStateFn | T; + +export class FavolinkStore implements FavolinkStoreObserver { + private state: T; + private listeners: (() => void)[] = []; + + constructor(initialState: T) { + this.state = initialState; + } + + getState = () => { + return this.state; + }; + + setState = (next: SetStateAction) => { + const setter = next as SetStateFn; + const nextValue = typeof next === 'function' ? setter(this.state) : next; + + if (this.state === nextValue) { + return; + } + + this.state = nextValue; + this.emitChange(); + }; + + subscribe = (listener: () => void) => { + this.listeners = [...this.listeners, listener]; + + return () => { + this.listeners = this.listeners.filter((l) => l !== listener); + }; + }; + + emitChange = () => { + this.listeners.forEach((listener) => { + listener(); + }); + }; +} + +export function createFavolinkStore(initialState: T) { + return new FavolinkStore(initialState); +}