Disclosure state primitive
Implements controlled/uncontrolled open-close state for disclosure components (Dialog, Popover, Tooltip, Collapsible, etc.) wrapping createControllableSignal with open/close/toggle convenience methods.
This commit is contained in:
parent
512eba474a
commit
ed13193430
37
packages/core/src/primitives/create-disclosure-state.ts
Normal file
37
packages/core/src/primitives/create-disclosure-state.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { Accessor } from "solid-js";
|
||||
import { createControllableSignal } from "./create-controllable-signal";
|
||||
|
||||
export interface CreateDisclosureStateOptions {
|
||||
open?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export interface DisclosureState {
|
||||
isOpen: Accessor<boolean>;
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
toggle: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared open/close state for all disclosure components (Dialog, Popover,
|
||||
* Tooltip, Collapsible, etc.). Wraps createControllableSignal with
|
||||
* convenience open/close/toggle methods.
|
||||
*/
|
||||
export function createDisclosureState(
|
||||
options: CreateDisclosureStateOptions,
|
||||
): DisclosureState {
|
||||
const [isOpen, setIsOpen] = createControllableSignal<boolean>({
|
||||
value: () => options.open,
|
||||
defaultValue: () => options.defaultOpen ?? false,
|
||||
onChange: options.onOpenChange,
|
||||
});
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
open: () => setIsOpen(true),
|
||||
close: () => setIsOpen(false),
|
||||
toggle: () => setIsOpen(!isOpen()),
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
import { createRoot } from "solid-js";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createDisclosureState } from "../../src/primitives/create-disclosure-state";
|
||||
|
||||
describe("createDisclosureState", () => {
|
||||
it("starts closed by default", () => {
|
||||
createRoot((dispose) => {
|
||||
const state = createDisclosureState({});
|
||||
expect(state.isOpen()).toBe(false);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it("respects defaultOpen", () => {
|
||||
createRoot((dispose) => {
|
||||
const state = createDisclosureState({ defaultOpen: true });
|
||||
expect(state.isOpen()).toBe(true);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it("open() sets state to true", () => {
|
||||
createRoot((dispose) => {
|
||||
const state = createDisclosureState({});
|
||||
state.open();
|
||||
expect(state.isOpen()).toBe(true);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it("close() sets state to false", () => {
|
||||
createRoot((dispose) => {
|
||||
const state = createDisclosureState({ defaultOpen: true });
|
||||
state.close();
|
||||
expect(state.isOpen()).toBe(false);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it("toggle() flips state", () => {
|
||||
createRoot((dispose) => {
|
||||
const state = createDisclosureState({});
|
||||
state.toggle();
|
||||
expect(state.isOpen()).toBe(true);
|
||||
state.toggle();
|
||||
expect(state.isOpen()).toBe(false);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it("calls onOpenChange when state changes", () => {
|
||||
createRoot((dispose) => {
|
||||
const onChange = vi.fn();
|
||||
const state = createDisclosureState({ onOpenChange: onChange });
|
||||
state.open();
|
||||
expect(onChange).toHaveBeenCalledWith(true);
|
||||
state.close();
|
||||
expect(onChange).toHaveBeenCalledWith(false);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
|
||||
it("respects controlled open prop", () => {
|
||||
createRoot((dispose) => {
|
||||
const state = createDisclosureState({ open: true });
|
||||
expect(state.isOpen()).toBe(true);
|
||||
// Calling close() fires onChange but does not change internal state
|
||||
const onChange = vi.fn();
|
||||
const controlled = createDisclosureState({ open: true, onOpenChange: onChange });
|
||||
controlled.close();
|
||||
expect(controlled.isOpen()).toBe(true); // still controlled
|
||||
expect(onChange).toHaveBeenCalledWith(false);
|
||||
dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user