@lattice-ui/popover 0.1.1

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.
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { PopoverAnchorProps } from "./types";
3
+ export declare function PopoverAnchor(props: PopoverAnchorProps): React.JSX.Element;
@@ -0,0 +1,36 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local usePopoverContext = TS.import(script, script.Parent, "context").usePopoverContext
7
+ local function toGuiObject(instance)
8
+ if not instance or not instance:IsA("GuiObject") then
9
+ return nil
10
+ end
11
+ return instance
12
+ end
13
+ local function PopoverAnchor(props)
14
+ local popoverContext = usePopoverContext()
15
+ local setAnchorRef = React.useCallback(function(instance)
16
+ popoverContext.anchorRef.current = toGuiObject(instance)
17
+ end, { popoverContext.anchorRef })
18
+ if props.asChild then
19
+ local child = props.children
20
+ if not child then
21
+ error("[PopoverAnchor] `asChild` requires a child element.")
22
+ end
23
+ return React.createElement(Slot, {
24
+ ref = setAnchorRef,
25
+ }, child)
26
+ end
27
+ return React.createElement("frame", {
28
+ BackgroundTransparency = 1,
29
+ BorderSizePixel = 0,
30
+ Size = UDim2.fromOffset(0, 0),
31
+ ref = setAnchorRef,
32
+ })
33
+ end
34
+ return {
35
+ PopoverAnchor = PopoverAnchor,
36
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { PopoverCloseProps } from "./types";
3
+ export declare function PopoverClose(props: PopoverCloseProps): React.JSX.Element;
@@ -0,0 +1,38 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local usePopoverContext = TS.import(script, script.Parent, "context").usePopoverContext
7
+ local function PopoverClose(props)
8
+ local popoverContext = usePopoverContext()
9
+ local handleActivated = React.useCallback(function()
10
+ popoverContext.setOpen(false)
11
+ end, { popoverContext.setOpen })
12
+ if props.asChild then
13
+ local child = props.children
14
+ if not child then
15
+ error("[PopoverClose] `asChild` requires a child element.")
16
+ end
17
+ return React.createElement(Slot, {
18
+ Event = {
19
+ Activated = handleActivated,
20
+ },
21
+ }, child)
22
+ end
23
+ return React.createElement("textbutton", {
24
+ AutoButtonColor = false,
25
+ BackgroundTransparency = 1,
26
+ BorderSizePixel = 0,
27
+ Event = {
28
+ Activated = handleActivated,
29
+ },
30
+ Size = UDim2.fromOffset(110, 34),
31
+ Text = "Close",
32
+ TextColor3 = Color3.fromRGB(240, 244, 250),
33
+ TextSize = 16,
34
+ }, props.children)
35
+ end
36
+ return {
37
+ PopoverClose = PopoverClose,
38
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { PopoverContentProps } from "./types";
3
+ export declare function PopoverContent(props: PopoverContentProps): React.JSX.Element | undefined;
@@ -0,0 +1,112 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local _layer = TS.import(script, TS.getModule(script, "@lattice-ui", "layer").out)
7
+ local DismissableLayer = _layer.DismissableLayer
8
+ local Presence = _layer.Presence
9
+ local usePopper = TS.import(script, TS.getModule(script, "@lattice-ui", "popper").out).usePopper
10
+ local usePopoverContext = TS.import(script, script.Parent, "context").usePopoverContext
11
+ local function toGuiObject(instance)
12
+ if not instance or not instance:IsA("GuiObject") then
13
+ return nil
14
+ end
15
+ return instance
16
+ end
17
+ local function PopoverContentImpl(props)
18
+ local popoverContext = usePopoverContext()
19
+ local anchorRef = if popoverContext.anchorRef.current then popoverContext.anchorRef else popoverContext.triggerRef
20
+ local popper = usePopper({
21
+ anchorRef = anchorRef,
22
+ contentRef = popoverContext.contentRef,
23
+ placement = props.placement,
24
+ offset = props.offset,
25
+ padding = props.padding,
26
+ enabled = props.enabled,
27
+ })
28
+ local setContentRef = React.useCallback(function(instance)
29
+ popoverContext.contentRef.current = toGuiObject(instance)
30
+ end, { popoverContext.contentRef })
31
+ if props.asChild then
32
+ local child = props.children
33
+ if not React.isValidElement(child) then
34
+ error("[PopoverContent] `asChild` requires a child element.")
35
+ end
36
+ return React.createElement(DismissableLayer, {
37
+ enabled = props.enabled,
38
+ modal = popoverContext.modal,
39
+ onDismiss = props.onDismiss,
40
+ onEscapeKeyDown = props.onEscapeKeyDown,
41
+ onInteractOutside = props.onInteractOutside,
42
+ onPointerDownOutside = props.onPointerDownOutside,
43
+ }, React.createElement(Slot, {
44
+ AnchorPoint = popper.anchorPoint,
45
+ Position = popper.position,
46
+ Visible = props.visible,
47
+ ref = setContentRef,
48
+ }, child))
49
+ end
50
+ return React.createElement(DismissableLayer, {
51
+ enabled = props.enabled,
52
+ modal = popoverContext.modal,
53
+ onDismiss = props.onDismiss,
54
+ onEscapeKeyDown = props.onEscapeKeyDown,
55
+ onInteractOutside = props.onInteractOutside,
56
+ onPointerDownOutside = props.onPointerDownOutside,
57
+ }, React.createElement("frame", {
58
+ AnchorPoint = popper.anchorPoint,
59
+ BackgroundTransparency = 1,
60
+ BorderSizePixel = 0,
61
+ Position = popper.position,
62
+ Size = UDim2.fromOffset(0, 0),
63
+ Visible = props.visible,
64
+ ref = setContentRef,
65
+ }, props.children))
66
+ end
67
+ local function PopoverContent(props)
68
+ local popoverContext = usePopoverContext()
69
+ local open = popoverContext.open
70
+ local forceMount = props.forceMount == true
71
+ local handleDismiss = React.useCallback(function()
72
+ popoverContext.setOpen(false)
73
+ end, { popoverContext.setOpen })
74
+ if not open and not forceMount then
75
+ return nil
76
+ end
77
+ if forceMount then
78
+ return React.createElement(PopoverContentImpl, {
79
+ asChild = props.asChild,
80
+ enabled = open,
81
+ offset = props.offset,
82
+ onDismiss = handleDismiss,
83
+ onEscapeKeyDown = props.onEscapeKeyDown,
84
+ onInteractOutside = props.onInteractOutside,
85
+ onPointerDownOutside = props.onPointerDownOutside,
86
+ padding = props.padding,
87
+ placement = props.placement,
88
+ visible = open,
89
+ }, props.children)
90
+ end
91
+ return React.createElement(Presence, {
92
+ exitFallbackMs = 0,
93
+ present = open,
94
+ render = function(state)
95
+ return React.createElement(PopoverContentImpl, {
96
+ asChild = props.asChild,
97
+ enabled = state.isPresent,
98
+ offset = props.offset,
99
+ onDismiss = handleDismiss,
100
+ onEscapeKeyDown = props.onEscapeKeyDown,
101
+ onInteractOutside = props.onInteractOutside,
102
+ onPointerDownOutside = props.onPointerDownOutside,
103
+ padding = props.padding,
104
+ placement = props.placement,
105
+ visible = state.isPresent,
106
+ }, props.children)
107
+ end,
108
+ })
109
+ end
110
+ return {
111
+ PopoverContent = PopoverContent,
112
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { PopoverPortalProps } from "./types";
3
+ export declare function PopoverPortal(props: PopoverPortalProps): React.JSX.Element;
@@ -0,0 +1,33 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local React = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out).React
4
+ local _layer = TS.import(script, TS.getModule(script, "@lattice-ui", "layer").out)
5
+ local Portal = _layer.Portal
6
+ local PortalProvider = _layer.PortalProvider
7
+ local usePortalContext = _layer.usePortalContext
8
+ local function PopoverPortalWithOverrides(props)
9
+ local portalContext = usePortalContext()
10
+ local container = props.container or portalContext.container
11
+ local _condition = props.displayOrderBase
12
+ if _condition == nil then
13
+ _condition = portalContext.displayOrderBase
14
+ end
15
+ local displayOrderBase = _condition
16
+ return React.createElement(PortalProvider, {
17
+ container = container,
18
+ displayOrderBase = displayOrderBase,
19
+ }, React.createElement(Portal, nil, props.children))
20
+ end
21
+ local function PopoverPortal(props)
22
+ local hasOverrides = props.container ~= nil or props.displayOrderBase ~= nil
23
+ if hasOverrides then
24
+ return React.createElement(PopoverPortalWithOverrides, {
25
+ container = props.container,
26
+ displayOrderBase = props.displayOrderBase,
27
+ }, props.children)
28
+ end
29
+ return React.createElement(Portal, nil, props.children)
30
+ end
31
+ return {
32
+ PopoverPortal = PopoverPortal,
33
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { PopoverProps } from "./types";
3
+ export declare function Popover(props: PopoverProps): React.JSX.Element;
@@ -0,0 +1,48 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local useControllableState = _core.useControllableState
6
+ local PopoverContextProvider = TS.import(script, script.Parent, "context").PopoverContextProvider
7
+ local function Popover(props)
8
+ local _object = {
9
+ value = props.open,
10
+ }
11
+ local _left = "defaultValue"
12
+ local _condition = props.defaultOpen
13
+ if _condition == nil then
14
+ _condition = false
15
+ end
16
+ _object[_left] = _condition
17
+ _object.onChange = props.onOpenChange
18
+ local _binding = useControllableState(_object)
19
+ local open = _binding[1]
20
+ local setOpenState = _binding[2]
21
+ local _condition_1 = props.modal
22
+ if _condition_1 == nil then
23
+ _condition_1 = false
24
+ end
25
+ local modal = _condition_1
26
+ local triggerRef = React.useRef()
27
+ local anchorRef = React.useRef()
28
+ local contentRef = React.useRef()
29
+ local setOpen = React.useCallback(function(nextOpen)
30
+ setOpenState(nextOpen)
31
+ end, { setOpenState })
32
+ local contextValue = React.useMemo(function()
33
+ return {
34
+ open = open,
35
+ setOpen = setOpen,
36
+ modal = modal,
37
+ triggerRef = triggerRef,
38
+ anchorRef = anchorRef,
39
+ contentRef = contentRef,
40
+ }
41
+ end, { modal, open, setOpen })
42
+ return React.createElement(PopoverContextProvider, {
43
+ value = contextValue,
44
+ }, props.children)
45
+ end
46
+ return {
47
+ Popover = Popover,
48
+ }
@@ -0,0 +1,3 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import type { PopoverTriggerProps } from "./types";
3
+ export declare function PopoverTrigger(props: PopoverTriggerProps): React.JSX.Element;
@@ -0,0 +1,54 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _core = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out)
4
+ local React = _core.React
5
+ local Slot = _core.Slot
6
+ local usePopoverContext = TS.import(script, script.Parent, "context").usePopoverContext
7
+ local function toGuiObject(instance)
8
+ if not instance or not instance:IsA("GuiObject") then
9
+ return nil
10
+ end
11
+ return instance
12
+ end
13
+ local function PopoverTrigger(props)
14
+ local popoverContext = usePopoverContext()
15
+ local setTriggerRef = React.useCallback(function(instance)
16
+ popoverContext.triggerRef.current = toGuiObject(instance)
17
+ end, { popoverContext.triggerRef })
18
+ local handleActivated = React.useCallback(function()
19
+ if props.disabled then
20
+ return nil
21
+ end
22
+ popoverContext.setOpen(not popoverContext.open)
23
+ end, { popoverContext.open, popoverContext.setOpen, props.disabled })
24
+ if props.asChild then
25
+ local child = props.children
26
+ if not child then
27
+ error("[PopoverTrigger] `asChild` requires a child element.")
28
+ end
29
+ return React.createElement(Slot, {
30
+ Event = {
31
+ Activated = handleActivated,
32
+ },
33
+ ref = setTriggerRef,
34
+ }, child)
35
+ end
36
+ return React.createElement("textbutton", {
37
+ Active = props.disabled ~= true,
38
+ AutoButtonColor = false,
39
+ BackgroundTransparency = 1,
40
+ BorderSizePixel = 0,
41
+ Event = {
42
+ Activated = handleActivated,
43
+ },
44
+ Selectable = props.disabled ~= true,
45
+ Size = UDim2.fromOffset(150, 38),
46
+ Text = "Toggle Popover",
47
+ TextColor3 = Color3.fromRGB(240, 244, 250),
48
+ TextSize = 16,
49
+ ref = setTriggerRef,
50
+ }, props.children)
51
+ end
52
+ return {
53
+ PopoverTrigger = PopoverTrigger,
54
+ }
@@ -0,0 +1,3 @@
1
+ import type { PopoverContextValue } from "./types";
2
+ declare const PopoverContextProvider: import("@rbxts/react").Provider<PopoverContextValue | undefined>, usePopoverContext: () => PopoverContextValue;
3
+ export { PopoverContextProvider, usePopoverContext };
@@ -0,0 +1,10 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local createStrictContext = TS.import(script, TS.getModule(script, "@lattice-ui", "core").out).createStrictContext
4
+ local _binding = createStrictContext("Popover")
5
+ local PopoverContextProvider = _binding[1]
6
+ local usePopoverContext = _binding[2]
7
+ return {
8
+ PopoverContextProvider = PopoverContextProvider,
9
+ usePopoverContext = usePopoverContext,
10
+ }
@@ -0,0 +1,48 @@
1
+ import type { LayerInteractEvent } from "@lattice-ui/layer";
2
+ import type { PopperPlacement } from "@lattice-ui/popper";
3
+ import type React from "@rbxts/react";
4
+ export type PopoverSetOpen = (open: boolean) => void;
5
+ export type PopoverContextValue = {
6
+ open: boolean;
7
+ setOpen: PopoverSetOpen;
8
+ modal: boolean;
9
+ triggerRef: React.MutableRefObject<GuiObject | undefined>;
10
+ anchorRef: React.MutableRefObject<GuiObject | undefined>;
11
+ contentRef: React.MutableRefObject<GuiObject | undefined>;
12
+ };
13
+ export type PopoverProps = {
14
+ open?: boolean;
15
+ defaultOpen?: boolean;
16
+ onOpenChange?: (open: boolean) => void;
17
+ modal?: boolean;
18
+ children?: React.ReactNode;
19
+ };
20
+ export type PopoverTriggerProps = {
21
+ asChild?: boolean;
22
+ disabled?: boolean;
23
+ children?: React.ReactElement;
24
+ };
25
+ export type PopoverPortalProps = {
26
+ container?: BasePlayerGui;
27
+ displayOrderBase?: number;
28
+ children?: React.ReactNode;
29
+ };
30
+ export type PopoverContentProps = {
31
+ asChild?: boolean;
32
+ forceMount?: boolean;
33
+ placement?: PopperPlacement;
34
+ offset?: Vector2;
35
+ padding?: number;
36
+ onEscapeKeyDown?: (event: LayerInteractEvent) => void;
37
+ onPointerDownOutside?: (event: LayerInteractEvent) => void;
38
+ onInteractOutside?: (event: LayerInteractEvent) => void;
39
+ children?: React.ReactNode;
40
+ };
41
+ export type PopoverAnchorProps = {
42
+ asChild?: boolean;
43
+ children?: React.ReactElement;
44
+ };
45
+ export type PopoverCloseProps = {
46
+ asChild?: boolean;
47
+ children?: React.ReactElement;
48
+ };
@@ -0,0 +1,2 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ return nil
package/out/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { PopoverAnchor } from "./Popover/PopoverAnchor";
2
+ export { PopoverClose } from "./Popover/PopoverClose";
3
+ export { PopoverContent } from "./Popover/PopoverContent";
4
+ export { PopoverPortal } from "./Popover/PopoverPortal";
5
+ export { Popover } from "./Popover/PopoverRoot";
6
+ export { PopoverTrigger } from "./Popover/PopoverTrigger";
7
+ export type { PopoverAnchorProps, PopoverCloseProps, PopoverContentProps, PopoverPortalProps, PopoverProps, PopoverTriggerProps, } from "./Popover/types";
package/out/init.luau ADDED
@@ -0,0 +1,10 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ exports.PopoverAnchor = TS.import(script, script, "Popover", "PopoverAnchor").PopoverAnchor
5
+ exports.PopoverClose = TS.import(script, script, "Popover", "PopoverClose").PopoverClose
6
+ exports.PopoverContent = TS.import(script, script, "Popover", "PopoverContent").PopoverContent
7
+ exports.PopoverPortal = TS.import(script, script, "Popover", "PopoverPortal").PopoverPortal
8
+ exports.Popover = TS.import(script, script, "Popover", "PopoverRoot").Popover
9
+ exports.PopoverTrigger = TS.import(script, script, "Popover", "PopoverTrigger").PopoverTrigger
10
+ return exports
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@lattice-ui/popover",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "main": "out/init.luau",
6
+ "types": "out/index.d.ts",
7
+ "dependencies": {
8
+ "@lattice-ui/layer": "0.1.1",
9
+ "@lattice-ui/popper": "0.1.1",
10
+ "@lattice-ui/core": "0.1.1"
11
+ },
12
+ "devDependencies": {
13
+ "@rbxts/react": "17.3.7-ts.1",
14
+ "@rbxts/react-roblox": "17.3.7-ts.1"
15
+ },
16
+ "peerDependencies": {
17
+ "@rbxts/react": "^17",
18
+ "@rbxts/react-roblox": "^17"
19
+ },
20
+ "scripts": {
21
+ "build": "rbxtsc -p tsconfig.json",
22
+ "watch": "rbxtsc -p tsconfig.json -w",
23
+ "typecheck": "tsc -p tsconfig.typecheck.json"
24
+ }
25
+ }
@@ -0,0 +1,33 @@
1
+ import { React, Slot } from "@lattice-ui/core";
2
+ import { usePopoverContext } from "./context";
3
+ import type { PopoverAnchorProps } from "./types";
4
+
5
+ function toGuiObject(instance: Instance | undefined) {
6
+ if (!instance || !instance.IsA("GuiObject")) {
7
+ return undefined;
8
+ }
9
+
10
+ return instance;
11
+ }
12
+
13
+ export function PopoverAnchor(props: PopoverAnchorProps) {
14
+ const popoverContext = usePopoverContext();
15
+
16
+ const setAnchorRef = React.useCallback(
17
+ (instance: Instance | undefined) => {
18
+ popoverContext.anchorRef.current = toGuiObject(instance);
19
+ },
20
+ [popoverContext.anchorRef],
21
+ );
22
+
23
+ if (props.asChild) {
24
+ const child = props.children;
25
+ if (!child) {
26
+ error("[PopoverAnchor] `asChild` requires a child element.");
27
+ }
28
+
29
+ return <Slot ref={setAnchorRef}>{child}</Slot>;
30
+ }
31
+
32
+ return <frame BackgroundTransparency={1} BorderSizePixel={0} Size={UDim2.fromOffset(0, 0)} ref={setAnchorRef} />;
33
+ }
@@ -0,0 +1,35 @@
1
+ import { React, Slot } from "@lattice-ui/core";
2
+ import { usePopoverContext } from "./context";
3
+ import type { PopoverCloseProps } from "./types";
4
+
5
+ export function PopoverClose(props: PopoverCloseProps) {
6
+ const popoverContext = usePopoverContext();
7
+
8
+ const handleActivated = React.useCallback(() => {
9
+ popoverContext.setOpen(false);
10
+ }, [popoverContext.setOpen]);
11
+
12
+ if (props.asChild) {
13
+ const child = props.children;
14
+ if (!child) {
15
+ error("[PopoverClose] `asChild` requires a child element.");
16
+ }
17
+
18
+ return <Slot Event={{ Activated: handleActivated }}>{child}</Slot>;
19
+ }
20
+
21
+ return (
22
+ <textbutton
23
+ AutoButtonColor={false}
24
+ BackgroundTransparency={1}
25
+ BorderSizePixel={0}
26
+ Event={{ Activated: handleActivated }}
27
+ Size={UDim2.fromOffset(110, 34)}
28
+ Text="Close"
29
+ TextColor3={Color3.fromRGB(240, 244, 250)}
30
+ TextSize={16}
31
+ >
32
+ {props.children}
33
+ </textbutton>
34
+ );
35
+ }
@@ -0,0 +1,145 @@
1
+ import { React, Slot } from "@lattice-ui/core";
2
+ import { DismissableLayer, Presence } from "@lattice-ui/layer";
3
+ import { usePopper } from "@lattice-ui/popper";
4
+ import { usePopoverContext } from "./context";
5
+ import type { PopoverContentProps } from "./types";
6
+
7
+ type PopoverContentImplProps = {
8
+ enabled: boolean;
9
+ visible: boolean;
10
+ onDismiss: () => void;
11
+ asChild?: boolean;
12
+ placement?: PopoverContentProps["placement"];
13
+ offset?: PopoverContentProps["offset"];
14
+ padding?: PopoverContentProps["padding"];
15
+ } & Pick<PopoverContentProps, "children" | "onEscapeKeyDown" | "onInteractOutside" | "onPointerDownOutside">;
16
+
17
+ function toGuiObject(instance: Instance | undefined) {
18
+ if (!instance || !instance.IsA("GuiObject")) {
19
+ return undefined;
20
+ }
21
+
22
+ return instance;
23
+ }
24
+
25
+ function PopoverContentImpl(props: PopoverContentImplProps) {
26
+ const popoverContext = usePopoverContext();
27
+ const anchorRef = popoverContext.anchorRef.current ? popoverContext.anchorRef : popoverContext.triggerRef;
28
+
29
+ const popper = usePopper({
30
+ anchorRef,
31
+ contentRef: popoverContext.contentRef,
32
+ placement: props.placement,
33
+ offset: props.offset,
34
+ padding: props.padding,
35
+ enabled: props.enabled,
36
+ });
37
+
38
+ const setContentRef = React.useCallback(
39
+ (instance: Instance | undefined) => {
40
+ popoverContext.contentRef.current = toGuiObject(instance);
41
+ },
42
+ [popoverContext.contentRef],
43
+ );
44
+
45
+ if (props.asChild) {
46
+ const child = props.children;
47
+ if (!React.isValidElement(child)) {
48
+ error("[PopoverContent] `asChild` requires a child element.");
49
+ }
50
+
51
+ return (
52
+ <DismissableLayer
53
+ enabled={props.enabled}
54
+ modal={popoverContext.modal}
55
+ onDismiss={props.onDismiss}
56
+ onEscapeKeyDown={props.onEscapeKeyDown}
57
+ onInteractOutside={props.onInteractOutside}
58
+ onPointerDownOutside={props.onPointerDownOutside}
59
+ >
60
+ <Slot AnchorPoint={popper.anchorPoint} Position={popper.position} Visible={props.visible} ref={setContentRef}>
61
+ {child}
62
+ </Slot>
63
+ </DismissableLayer>
64
+ );
65
+ }
66
+
67
+ return (
68
+ <DismissableLayer
69
+ enabled={props.enabled}
70
+ modal={popoverContext.modal}
71
+ onDismiss={props.onDismiss}
72
+ onEscapeKeyDown={props.onEscapeKeyDown}
73
+ onInteractOutside={props.onInteractOutside}
74
+ onPointerDownOutside={props.onPointerDownOutside}
75
+ >
76
+ <frame
77
+ AnchorPoint={popper.anchorPoint}
78
+ BackgroundTransparency={1}
79
+ BorderSizePixel={0}
80
+ Position={popper.position}
81
+ Size={UDim2.fromOffset(0, 0)}
82
+ Visible={props.visible}
83
+ ref={setContentRef}
84
+ >
85
+ {props.children}
86
+ </frame>
87
+ </DismissableLayer>
88
+ );
89
+ }
90
+
91
+ export function PopoverContent(props: PopoverContentProps) {
92
+ const popoverContext = usePopoverContext();
93
+ const open = popoverContext.open;
94
+ const forceMount = props.forceMount === true;
95
+
96
+ const handleDismiss = React.useCallback(() => {
97
+ popoverContext.setOpen(false);
98
+ }, [popoverContext.setOpen]);
99
+
100
+ if (!open && !forceMount) {
101
+ return undefined;
102
+ }
103
+
104
+ if (forceMount) {
105
+ return (
106
+ <PopoverContentImpl
107
+ asChild={props.asChild}
108
+ enabled={open}
109
+ offset={props.offset}
110
+ onDismiss={handleDismiss}
111
+ onEscapeKeyDown={props.onEscapeKeyDown}
112
+ onInteractOutside={props.onInteractOutside}
113
+ onPointerDownOutside={props.onPointerDownOutside}
114
+ padding={props.padding}
115
+ placement={props.placement}
116
+ visible={open}
117
+ >
118
+ {props.children}
119
+ </PopoverContentImpl>
120
+ );
121
+ }
122
+
123
+ return (
124
+ <Presence
125
+ exitFallbackMs={0}
126
+ present={open}
127
+ render={(state) => (
128
+ <PopoverContentImpl
129
+ asChild={props.asChild}
130
+ enabled={state.isPresent}
131
+ offset={props.offset}
132
+ onDismiss={handleDismiss}
133
+ onEscapeKeyDown={props.onEscapeKeyDown}
134
+ onInteractOutside={props.onInteractOutside}
135
+ onPointerDownOutside={props.onPointerDownOutside}
136
+ padding={props.padding}
137
+ placement={props.placement}
138
+ visible={state.isPresent}
139
+ >
140
+ {props.children}
141
+ </PopoverContentImpl>
142
+ )}
143
+ />
144
+ );
145
+ }
@@ -0,0 +1,28 @@
1
+ import { React } from "@lattice-ui/core";
2
+ import { Portal, PortalProvider, usePortalContext } from "@lattice-ui/layer";
3
+ import type { PopoverPortalProps } from "./types";
4
+
5
+ function PopoverPortalWithOverrides(props: PopoverPortalProps) {
6
+ const portalContext = usePortalContext();
7
+ const container = props.container ?? portalContext.container;
8
+ const displayOrderBase = props.displayOrderBase ?? portalContext.displayOrderBase;
9
+
10
+ return (
11
+ <PortalProvider container={container} displayOrderBase={displayOrderBase}>
12
+ <Portal>{props.children}</Portal>
13
+ </PortalProvider>
14
+ );
15
+ }
16
+
17
+ export function PopoverPortal(props: PopoverPortalProps) {
18
+ const hasOverrides = props.container !== undefined || props.displayOrderBase !== undefined;
19
+ if (hasOverrides) {
20
+ return (
21
+ <PopoverPortalWithOverrides container={props.container} displayOrderBase={props.displayOrderBase}>
22
+ {props.children}
23
+ </PopoverPortalWithOverrides>
24
+ );
25
+ }
26
+
27
+ return <Portal>{props.children}</Portal>;
28
+ }
@@ -0,0 +1,37 @@
1
+ import { React, useControllableState } from "@lattice-ui/core";
2
+ import { PopoverContextProvider } from "./context";
3
+ import type { PopoverProps } from "./types";
4
+
5
+ export function Popover(props: PopoverProps) {
6
+ const [open, setOpenState] = useControllableState<boolean>({
7
+ value: props.open,
8
+ defaultValue: props.defaultOpen ?? false,
9
+ onChange: props.onOpenChange,
10
+ });
11
+ const modal = props.modal ?? false;
12
+
13
+ const triggerRef = React.useRef<GuiObject>();
14
+ const anchorRef = React.useRef<GuiObject>();
15
+ const contentRef = React.useRef<GuiObject>();
16
+
17
+ const setOpen = React.useCallback(
18
+ (nextOpen: boolean) => {
19
+ setOpenState(nextOpen);
20
+ },
21
+ [setOpenState],
22
+ );
23
+
24
+ const contextValue = React.useMemo(
25
+ () => ({
26
+ open,
27
+ setOpen,
28
+ modal,
29
+ triggerRef,
30
+ anchorRef,
31
+ contentRef,
32
+ }),
33
+ [modal, open, setOpen],
34
+ );
35
+
36
+ return <PopoverContextProvider value={contextValue}>{props.children}</PopoverContextProvider>;
37
+ }
@@ -0,0 +1,61 @@
1
+ import { React, Slot } from "@lattice-ui/core";
2
+ import { usePopoverContext } from "./context";
3
+ import type { PopoverTriggerProps } from "./types";
4
+
5
+ function toGuiObject(instance: Instance | undefined) {
6
+ if (!instance || !instance.IsA("GuiObject")) {
7
+ return undefined;
8
+ }
9
+
10
+ return instance;
11
+ }
12
+
13
+ export function PopoverTrigger(props: PopoverTriggerProps) {
14
+ const popoverContext = usePopoverContext();
15
+
16
+ const setTriggerRef = React.useCallback(
17
+ (instance: Instance | undefined) => {
18
+ popoverContext.triggerRef.current = toGuiObject(instance);
19
+ },
20
+ [popoverContext.triggerRef],
21
+ );
22
+
23
+ const handleActivated = React.useCallback(() => {
24
+ if (props.disabled) {
25
+ return;
26
+ }
27
+
28
+ popoverContext.setOpen(!popoverContext.open);
29
+ }, [popoverContext.open, popoverContext.setOpen, props.disabled]);
30
+
31
+ if (props.asChild) {
32
+ const child = props.children;
33
+ if (!child) {
34
+ error("[PopoverTrigger] `asChild` requires a child element.");
35
+ }
36
+
37
+ return (
38
+ <Slot Event={{ Activated: handleActivated }} ref={setTriggerRef}>
39
+ {child}
40
+ </Slot>
41
+ );
42
+ }
43
+
44
+ return (
45
+ <textbutton
46
+ Active={props.disabled !== true}
47
+ AutoButtonColor={false}
48
+ BackgroundTransparency={1}
49
+ BorderSizePixel={0}
50
+ Event={{ Activated: handleActivated }}
51
+ Selectable={props.disabled !== true}
52
+ Size={UDim2.fromOffset(150, 38)}
53
+ Text="Toggle Popover"
54
+ TextColor3={Color3.fromRGB(240, 244, 250)}
55
+ TextSize={16}
56
+ ref={setTriggerRef}
57
+ >
58
+ {props.children}
59
+ </textbutton>
60
+ );
61
+ }
@@ -0,0 +1,6 @@
1
+ import { createStrictContext } from "@lattice-ui/core";
2
+ import type { PopoverContextValue } from "./types";
3
+
4
+ const [PopoverContextProvider, usePopoverContext] = createStrictContext<PopoverContextValue>("Popover");
5
+
6
+ export { PopoverContextProvider, usePopoverContext };
@@ -0,0 +1,56 @@
1
+ import type { LayerInteractEvent } from "@lattice-ui/layer";
2
+ import type { PopperPlacement } from "@lattice-ui/popper";
3
+ import type React from "@rbxts/react";
4
+
5
+ export type PopoverSetOpen = (open: boolean) => void;
6
+
7
+ export type PopoverContextValue = {
8
+ open: boolean;
9
+ setOpen: PopoverSetOpen;
10
+ modal: boolean;
11
+ triggerRef: React.MutableRefObject<GuiObject | undefined>;
12
+ anchorRef: React.MutableRefObject<GuiObject | undefined>;
13
+ contentRef: React.MutableRefObject<GuiObject | undefined>;
14
+ };
15
+
16
+ export type PopoverProps = {
17
+ open?: boolean;
18
+ defaultOpen?: boolean;
19
+ onOpenChange?: (open: boolean) => void;
20
+ modal?: boolean;
21
+ children?: React.ReactNode;
22
+ };
23
+
24
+ export type PopoverTriggerProps = {
25
+ asChild?: boolean;
26
+ disabled?: boolean;
27
+ children?: React.ReactElement;
28
+ };
29
+
30
+ export type PopoverPortalProps = {
31
+ container?: BasePlayerGui;
32
+ displayOrderBase?: number;
33
+ children?: React.ReactNode;
34
+ };
35
+
36
+ export type PopoverContentProps = {
37
+ asChild?: boolean;
38
+ forceMount?: boolean;
39
+ placement?: PopperPlacement;
40
+ offset?: Vector2;
41
+ padding?: number;
42
+ onEscapeKeyDown?: (event: LayerInteractEvent) => void;
43
+ onPointerDownOutside?: (event: LayerInteractEvent) => void;
44
+ onInteractOutside?: (event: LayerInteractEvent) => void;
45
+ children?: React.ReactNode;
46
+ };
47
+
48
+ export type PopoverAnchorProps = {
49
+ asChild?: boolean;
50
+ children?: React.ReactElement;
51
+ };
52
+
53
+ export type PopoverCloseProps = {
54
+ asChild?: boolean;
55
+ children?: React.ReactElement;
56
+ };
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ export { PopoverAnchor } from "./Popover/PopoverAnchor";
2
+ export { PopoverClose } from "./Popover/PopoverClose";
3
+ export { PopoverContent } from "./Popover/PopoverContent";
4
+ export { PopoverPortal } from "./Popover/PopoverPortal";
5
+ export { Popover } from "./Popover/PopoverRoot";
6
+ export { PopoverTrigger } from "./Popover/PopoverTrigger";
7
+ export type {
8
+ PopoverAnchorProps,
9
+ PopoverCloseProps,
10
+ PopoverContentProps,
11
+ PopoverPortalProps,
12
+ PopoverProps,
13
+ PopoverTriggerProps,
14
+ } from "./Popover/types";
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "out",
6
+ "declaration": true,
7
+ "typeRoots": [
8
+ "./node_modules/@rbxts",
9
+ "../../node_modules/@rbxts",
10
+ "./node_modules/@lattice-ui",
11
+ "../../node_modules/@lattice-ui"
12
+ ],
13
+ "types": ["types", "compiler-types"]
14
+ },
15
+ "include": ["src"]
16
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "baseUrl": "..",
6
+ "rootDir": "..",
7
+ "paths": {
8
+ "@lattice-ui/checkbox": ["checkbox/src/index.ts"],
9
+ "@lattice-ui/core": ["core/src/index.ts"],
10
+ "@lattice-ui/dialog": ["dialog/src/index.ts"],
11
+ "@lattice-ui/focus": ["focus/src/index.ts"],
12
+ "@lattice-ui/layer": ["layer/src/index.ts"],
13
+ "@lattice-ui/menu": ["menu/src/index.ts"],
14
+ "@lattice-ui/popover": ["popover/src/index.ts"],
15
+ "@lattice-ui/popper": ["popper/src/index.ts"],
16
+ "@lattice-ui/radio-group": ["radio-group/src/index.ts"],
17
+ "@lattice-ui/style": ["style/src/index.ts"],
18
+ "@lattice-ui/switch": ["switch/src/index.ts"],
19
+ "@lattice-ui/system": ["system/src/index.ts"],
20
+ "@lattice-ui/tabs": ["tabs/src/index.ts"],
21
+ "@lattice-ui/toggle-group": ["toggle-group/src/index.ts"],
22
+ "@lattice-ui/tooltip": ["tooltip/src/index.ts"]
23
+ }
24
+ }
25
+ }