@handamade/psi-react 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @handamade/psi-react
2
2
 
3
- 15 React 19 components consuming @handamade/psi-tokens. Zero runtime dependencies, CSS Modules, full TypeScript support.
3
+ 18 React 19 components consuming @handamade/psi-tokens. Zero runtime dependencies, CSS Modules, full TypeScript support.
4
4
 
5
5
  ## Installation
6
6
 
@@ -0,0 +1,57 @@
1
+ import type { ReactElement, ReactNode, Ref } from "react";
2
+ export type MenuPlacement = "bottom-start" | "bottom-end" | "top-start" | "top-end";
3
+ export interface MenuContextValue {
4
+ close: (reason: "item-select") => void;
5
+ }
6
+ export declare const MenuContext: import("react").Context<MenuContextValue>;
7
+ export interface MenuProps {
8
+ /** Controlled open state; syncs to showPopover()/hidePopover(). */
9
+ open: boolean;
10
+ /**
11
+ * Called when a dismissal is *requested*; the consumer flips `open`.
12
+ *
13
+ * Menu never closes itself on `"esc"` or `"item-select"` — the popover stays
14
+ * open until `open` becomes false (D50). `"outside"` is the one exception:
15
+ * light dismiss is performed by the browser before it tells us, so by the
16
+ * time that reason is reported the popover is already closed. Flipping
17
+ * `open` to false is still required, to keep React's state in step.
18
+ */
19
+ onClose: (reason: "esc" | "outside" | "item-select") => void;
20
+ /**
21
+ * The trigger element. Must be a single React element that spreads unknown
22
+ * props onto a focusable node (a Psi `Button`, say): Menu clones it to add
23
+ * `aria-haspopup="menu"` and `aria-expanded`, so assistive tech associates
24
+ * the menu with the control users actually focus.
25
+ */
26
+ trigger: ReactElement<Record<string, unknown>>;
27
+ /** Placement relative to the trigger. @default "bottom-start" */
28
+ placement?: MenuPlacement;
29
+ /** Accessible name for the menu when there is no visible label. */
30
+ "aria-label"?: string;
31
+ children: ReactNode;
32
+ className?: string;
33
+ /** Forwarded ref to the popover element. */
34
+ ref?: Ref<HTMLDivElement>;
35
+ }
36
+ /** Action menu on the native Popover API top layer: the top layer and light
37
+ * dismiss come from the platform; Esc, roving keyboard, placement and
38
+ * dismissal reasons are Psi's (D53). Controlled-only, like Dialog (D50).
39
+ *
40
+ * Controlled-only means Menu does not change its own visibility. Esc and
41
+ * item-select *report* a dismissal via onClose(reason) and leave the popover
42
+ * open; only the consumer flipping `open` to false actually closes it. Esc
43
+ * calls preventDefault() to suppress the platform's own popover dismissal,
44
+ * which is what makes that possible.
45
+ *
46
+ * Light dismiss (outside click) is the one asymmetry, and it is forced by the
47
+ * platform: the browser hides the popover itself and the hide-side
48
+ * `beforetoggle` is not cancelable, so the popover is already closed by the
49
+ * time the resulting `toggle` lets us report onClose("outside"). The consumer
50
+ * must still flip `open` to false so React's state matches the DOM.
51
+ *
52
+ * `toggle` therefore reports only that one reason. A close driven by the
53
+ * consumer (`open` true -> false) runs through the sync effect's own
54
+ * hidePopover(), which raises a `toggle` too — that one is suppressed, because
55
+ * a programmatic close is not a dismissal and must not call onClose. */
56
+ export declare function Menu({ open, onClose, trigger, placement, children, className, ref, ...rest }: MenuProps): import("react").JSX.Element;
57
+ //# sourceMappingURL=Menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Menu.d.ts","sourceRoot":"","sources":["../../src/Menu/Menu.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAK1D,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;CACxC;AAED,eAAO,MAAM,WAAW,2CAAuD,CAAC;AAEhF,MAAM,WAAW,SAAS;IACxB,mEAAmE;IACnE,IAAI,EAAE,OAAO,CAAC;IACd;;;;;;;;OAQG;IACH,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,GAAG,SAAS,GAAG,aAAa,KAAK,IAAI,CAAC;IAC7D;;;;;OAKG;IACH,OAAO,EAAE,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,iEAAiE;IACjE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CAC3B;AAMD;;;;;;;;;;;;;;;;;;;wEAmBwE;AACxE,wBAAgB,IAAI,CAAC,EACnB,IAAI,EACJ,OAAO,EACP,OAAO,EACP,SAA0B,EAC1B,QAAQ,EACR,SAAS,EACT,GAAG,EACH,GAAG,IAAI,EACR,EAAE,SAAS,+BAyGX"}
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from "react";
2
+ export interface MenuItemProps {
3
+ children: ReactNode;
4
+ /** Fires on activation; Menu then reports the dismissal via onClose("item-select") (D50). */
5
+ onSelect: () => void;
6
+ /** danger is for destructive actions only (house rule). @default "neutral" */
7
+ variant?: "neutral" | "danger";
8
+ disabled?: boolean;
9
+ }
10
+ /** One action in a Menu. Renders a real <button> so activation, Enter and
11
+ * Space come from the platform; the roving tabindex is applied by Menu's
12
+ * keyboard hook, which finds items via [data-psi-menu-item] (D53). */
13
+ export declare function MenuItem({ children, onSelect, variant, disabled }: MenuItemProps): import("react").JSX.Element;
14
+ //# sourceMappingURL=MenuItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuItem.d.ts","sourceRoot":"","sources":["../../src/Menu/MenuItem.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,SAAS,CAAC;IACpB,6FAA6F;IAC7F,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;sEAEsE;AACtE,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAmB,EAAE,QAAgB,EAAE,EAAE,aAAa,+BAqBpG"}
@@ -0,0 +1,3 @@
1
+ /** Hairline rule between Menu item groups. No props by design (D53). */
2
+ export declare function MenuSeparator(): import("react").JSX.Element;
3
+ //# sourceMappingURL=MenuSeparator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuSeparator.d.ts","sourceRoot":"","sources":["../../src/Menu/MenuSeparator.tsx"],"names":[],"mappings":"AAEA,wEAAwE;AACxE,wBAAgB,aAAa,gCAE5B"}
@@ -0,0 +1,15 @@
1
+ import type { KeyboardEvent, RefObject } from "react";
2
+ interface Options {
3
+ popoverRef: RefObject<HTMLDivElement | null>;
4
+ triggerRef: RefObject<HTMLDivElement | null>;
5
+ open: boolean;
6
+ onEsc: () => void;
7
+ }
8
+ /** Roving-tabindex keyboard navigation for Menu (D53): Up/Down with wrap,
9
+ * Home/End, character typeahead, Esc, and focus return on close. Disabled
10
+ * items are skipped everywhere. Psi's only keyboard-navigation JS — a
11
+ * deliberate departure from D52, which refused role="toolbar" precisely to
12
+ * avoid it. */
13
+ export declare function useMenuKeyboard({ popoverRef, triggerRef, open, onEsc }: Options): (event: KeyboardEvent<HTMLDivElement>) => void;
14
+ export {};
15
+ //# sourceMappingURL=useMenuKeyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMenuKeyboard.d.ts","sourceRoot":"","sources":["../../src/Menu/useMenuKeyboard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKtD,UAAU,OAAO;IACf,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;eAIe;AACf,wBAAgB,eAAe,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,WAsDpE,aAAa,CAAC,cAAc,CAAC,UAoExC"}
@@ -0,0 +1,18 @@
1
+ import type { RefObject } from "react";
2
+ import type { MenuPlacement } from "./Menu.js";
3
+ interface Options {
4
+ popoverRef: RefObject<HTMLDivElement | null>;
5
+ triggerRef: RefObject<HTMLDivElement | null>;
6
+ open: boolean;
7
+ placement: MenuPlacement;
8
+ anchorName: string;
9
+ }
10
+ /** Placement for Menu (D53). Above the anchor floor this hook only declares
11
+ * anchor-name / position-anchor and lets CSS position the popover. Below it,
12
+ * a top-layer element's containing block is the viewport — there is no
13
+ * declarative way to place it near its trigger — so this sets `inset` from
14
+ * the trigger rect and keeps it current. That branch is dead code above the
15
+ * anchor floor and is deletable outright once the floor rises. */
16
+ export declare function useMenuPlacement({ popoverRef, triggerRef, open, placement, anchorName }: Options): void;
17
+ export {};
18
+ //# sourceMappingURL=useMenuPlacement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMenuPlacement.d.ts","sourceRoot":"","sources":["../../src/Menu/useMenuPlacement.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAQ/C,UAAU,OAAO;IACf,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,UAAU,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC7C,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,aAAa,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;kEAKkE;AAClE,wBAAgB,gBAAgB,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,OAAO,QA2ChG"}
@@ -1 +1 @@
1
- {"version":3,"file":"a11y-meta.d.ts","sourceRoot":"","sources":["../src/a11y-meta.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CA0E9C,CAAC"}
1
+ {"version":3,"file":"a11y-meta.d.ts","sourceRoot":"","sources":["../src/a11y-meta.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CA8F9C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -28,5 +28,10 @@ export { Tooltip } from "./Tooltip/Tooltip.js";
28
28
  export type { TooltipProps } from "./Tooltip/Tooltip.js";
29
29
  export { Toolbar } from "./Toolbar/Toolbar.js";
30
30
  export type { ToolbarProps } from "./Toolbar/Toolbar.js";
31
+ export { Menu, MenuContext } from "./Menu/Menu.js";
32
+ export type { MenuProps, MenuPlacement, MenuContextValue } from "./Menu/Menu.js";
33
+ export { MenuItem } from "./Menu/MenuItem.js";
34
+ export type { MenuItemProps } from "./Menu/MenuItem.js";
35
+ export { MenuSeparator } from "./Menu/MenuSeparator.js";
31
36
  export { IconPlus, IconMinus, IconClose, IconCheck, IconChevronDown, IconChevronRight, IconSearch, IconSettings, IconUser, IconEdit, IconTrash, IconCopy, IconExternalLink, IconEye, IconEyeOff, IconLoader, IconArrowDown, IconArrowUpRight, IconLinkedIn, IconGitHub, IconX, IconInstagram, } from "./icons/index.js";
32
37
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EACL,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,OAAO,EACP,UAAU,EACV,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,KAAK,EACL,aAAa,GACd,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EACL,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,OAAO,EACP,UAAU,EACV,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,KAAK,EACL,aAAa,GACd,MAAM,kBAAkB,CAAC"}