chore(root): prepare workspace for alpha (#276)

This commit is contained in:
rahim
2025-12-21 20:26:13 +11:00
committed by GitHub
parent 5ddf02278d
commit b89fef56dd
247 changed files with 1197 additions and 446 deletions
@@ -0,0 +1,6 @@
// A (very crude) utility to merge class names
// Usually I'd use something like `clsx` or `classnames` but this is ok for our simple use case.
// It just makes the billions of Tailwind classes a little easier to read.
export function cn(...classes: (string | undefined)[]): string {
return classes.filter(Boolean).join(' ');
}
@@ -0,0 +1,140 @@
import type { ReactElement } from 'react';
import { createContext, forwardRef, useCallback, useContext, useEffect, useRef, useSyncExternalStore } from 'react';
export type StateHookFn<TProps = any, TState = any> = (props: TProps) => TState;
export type PropsHookFn<TProps = any, TState = any, TResultProps = any> = (
props: TProps,
state: TState
) => TResultProps;
export type RenderFn<TProps = any, TState = any> = (props: TProps, state: TState) => ReactElement;
const Context = createContext<any>(null);
/**
* Generic factory function to create connected components following the hooks pattern
* inspired by Adobe React Spectrum and Base UI architectures.
*
* @param useStateHook - Hook that provides component state
* @param usePropsHook - Hook that enhances props with state-derived values
* @param defaultRender - Default render function for the component
* @param displayName - Display name for React DevTools
* @returns Connected component with customizable render prop
*/
export function toConnectedComponent<
TProps extends Record<string, any>,
TState,
TResultProps extends Record<string, any>,
TRenderFn extends RenderFn<TResultProps, TState>,
>(
useStateHook: StateHookFn<TProps, TState>,
usePropsHook: PropsHookFn<TProps, TState, TResultProps>,
defaultRender: TRenderFn,
displayName: string,
): ConnectedComponent<TProps, TRenderFn> {
const ConnectedComponent = forwardRef<HTMLElement, TProps & { render?: TRenderFn }>(
({ render = defaultRender, ...props }, ref) => {
const propsWithRef = ref ? { ...props, ref } : props;
const connectedState = useStateHook(propsWithRef as unknown as TProps);
const connectedProps = usePropsHook(propsWithRef as unknown as TProps, connectedState);
return <Context.Provider value={connectedState}>{render(connectedProps, connectedState)}</Context.Provider>;
},
);
ConnectedComponent.displayName = displayName;
return ConnectedComponent;
}
/**
* Type helper to infer the component type from the factory
*/
export type ConnectedComponent<TProps extends Record<string, any>, TRenderFn extends RenderFn<any, any>> = React.ForwardRefExoticComponent<
React.PropsWithoutRef<TProps & { render?: TRenderFn }> & React.RefAttributes<HTMLElement>
>;
/**
* Factory function to create context-based components that don't use toConnectedComponent
* These components rely on context provided by a parent component.
*
* @param usePropsHook - Hook that enhances props with context-derived values
* @param defaultRender - Default render function for the component
* @param displayName - Display name for React DevTools
* @returns Context-based component with customizable render prop
*/
export function toContextComponent<
TProps extends Record<string, any>,
TResultProps extends Record<string, any>,
TRenderFn extends (props: TResultProps, context: any) => ReactElement,
>(
usePropsHook: (props: TProps, context: ReturnType<StateHookFn<TProps>>) => TResultProps,
defaultRender: TRenderFn,
displayName: string,
): ContextComponent<TProps, TRenderFn> {
const ContextComponent = forwardRef<HTMLElement, TProps & { render?: TRenderFn }>(
({ render = defaultRender, ...props }, ref) => {
const context = useContext(Context);
const propsWithRef = ref ? { ...props, ref } : props;
const contextProps = usePropsHook(propsWithRef as unknown as TProps, context);
return render(contextProps, context);
},
);
ContextComponent.displayName = displayName;
return ContextComponent;
}
/**
* Type helper to infer the context component type from the factory
*/
export type ContextComponent<
TProps extends Record<string, any>,
TRenderFn extends (props: any, context: any) => ReactElement,
> = React.ForwardRefExoticComponent<
React.PropsWithoutRef<TProps & { render?: TRenderFn }> & React.RefAttributes<any>
>;
/**
* Hook that manages a CoreClass instance and triggers re-renders when state changes.
* Uses useSyncExternalStore for optimal performance with external state subscriptions.
*/
export function useCore<
T extends {
subscribe: (callback: (state: any) => void) => () => void;
getState: () => any;
setState: (state: any) => void;
},
>(CoreClass: new () => T, state: any): ReturnType<T['getState']> {
const coreRef = useRef<T | null>(null);
const snapshotRef = useRef<any>(null);
// Initialize the core instance
if (!coreRef.current) {
coreRef.current = new CoreClass();
snapshotRef.current = coreRef.current.getState();
}
useEffect(() => {
coreRef.current?.setState(state);
}, [...Object.values(state)]);
// Use useSyncExternalStore to subscribe to state changes
useSyncExternalStore(
useCallback((onStoreChange) => {
if (!coreRef.current) return () => {};
return coreRef.current.subscribe((newState) => {
snapshotRef.current = newState;
onStoreChange();
});
}, []),
useCallback(() => {
return snapshotRef.current;
}, []),
() => null, // server snapshot
);
return coreRef.current.getState();
}
@@ -0,0 +1,60 @@
import * as React from 'react';
type PossibleRef<T> = React.Ref<T> | undefined;
/**
* Set a given ref to a given value
* This utility takes care of different types of refs: callback refs and RefObject(s)
*/
function setRef<T>(ref: PossibleRef<T>, value: T): (() => void) | void | undefined {
if (typeof ref === 'function') {
return ref(value);
} else if (ref !== null && ref !== undefined) {
(ref as React.MutableRefObject<T>).current = value;
}
}
/**
* A utility to compose multiple refs together
* Accepts callback refs and RefObject(s)
*/
function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
return (node) => {
let hasCleanup = false;
const cleanups = refs.map((ref) => {
const cleanup = setRef(ref, node);
if (!hasCleanup && typeof cleanup == 'function') {
hasCleanup = true;
}
return cleanup;
});
// React <19 will log an error to the console if a callback ref returns a
// value. We don't use ref cleanups internally so this will only happen if a
// user's ref callback returns a value, which we only expect if they are
// using the cleanup functionality added in React 19.
if (hasCleanup) {
return () => {
for (let i = 0; i < cleanups.length; i++) {
const cleanup = cleanups[i];
if (typeof cleanup == 'function') {
cleanup();
} else {
setRef(refs[i], null);
}
}
};
}
};
}
/**
* A custom hook that composes multiple refs
* Accepts callback refs and RefObject(s)
*/
function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
// eslint-disable-next-line react-hooks/exhaustive-deps
return React.useCallback(composeRefs(...refs), refs);
}
export { composeRefs, useComposedRefs };
@@ -0,0 +1,43 @@
import type { RefObject } from 'react';
import { useEffect, useRef } from 'react';
/**
* Custom React hook for observing DOM mutations using MutationObserver
*
* @param target - Element to observe (ref or element)
* @param callback - Function called when mutations occur
* @param options - MutationObserver options
*/
export function useMutationObserver<T extends Element = Element>(
target: RefObject<T> | T | null | undefined,
callback: MutationCallback,
options: MutationObserverInit = {
attributes: true,
childList: true,
subtree: true,
},
): void {
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
useEffect(() => {
const element = target && 'current' in target ? target.current : target;
if (!element || !(element instanceof Element)) {
return;
}
const observer = new MutationObserver((mutations, obs) => {
callbackRef.current(mutations, obs);
});
observer.observe(element, options);
return () => {
observer.disconnect();
};
}, [target, options]);
}