@pyreon/elements 0.11.1 → 0.11.3
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/package.json +8 -7
- package/src/Element/component.tsx +211 -0
- package/src/Element/constants.ts +96 -0
- package/src/Element/index.ts +6 -0
- package/src/Element/types.ts +168 -0
- package/src/Element/utils.ts +15 -0
- package/src/List/component.tsx +57 -0
- package/src/List/index.ts +5 -0
- package/src/Overlay/component.tsx +131 -0
- package/src/Overlay/context.tsx +37 -0
- package/src/Overlay/index.ts +7 -0
- package/src/Overlay/useOverlay.tsx +616 -0
- package/src/Portal/component.tsx +41 -0
- package/src/Portal/index.ts +5 -0
- package/src/Text/component.tsx +65 -0
- package/src/Text/index.ts +5 -0
- package/src/Text/styled.ts +30 -0
- package/src/Util/component.tsx +43 -0
- package/src/Util/index.ts +5 -0
- package/src/__tests__/Content.test.tsx +115 -0
- package/src/__tests__/Element.test.ts +604 -0
- package/src/__tests__/Iterator.test.ts +483 -0
- package/src/__tests__/List.test.ts +199 -0
- package/src/__tests__/Overlay.test.ts +485 -0
- package/src/__tests__/Portal.test.ts +82 -0
- package/src/__tests__/Text.test.ts +274 -0
- package/src/__tests__/Util.test.ts +63 -0
- package/src/__tests__/Wrapper.test.tsx +152 -0
- package/src/__tests__/equalBeforeAfter.test.ts +122 -0
- package/src/__tests__/helpers.test.ts +65 -0
- package/src/__tests__/overlayContext.test.tsx +78 -0
- package/src/__tests__/responsiveProps.test.ts +298 -0
- package/src/__tests__/useOverlay.test.ts +1330 -0
- package/src/__tests__/utils.test.ts +69 -0
- package/src/constants.ts +1 -0
- package/src/helpers/Content/component.tsx +51 -0
- package/src/helpers/Content/index.ts +3 -0
- package/src/helpers/Content/styled.ts +105 -0
- package/src/helpers/Content/types.ts +49 -0
- package/src/helpers/Iterator/component.tsx +252 -0
- package/src/helpers/Iterator/index.ts +13 -0
- package/src/helpers/Iterator/types.ts +79 -0
- package/src/helpers/Wrapper/component.tsx +78 -0
- package/src/helpers/Wrapper/constants.ts +10 -0
- package/src/helpers/Wrapper/index.ts +3 -0
- package/src/helpers/Wrapper/styled.ts +69 -0
- package/src/helpers/Wrapper/types.ts +56 -0
- package/src/helpers/Wrapper/utils.ts +7 -0
- package/src/helpers/index.ts +4 -0
- package/src/index.ts +37 -0
- package/src/types.ts +81 -0
- package/src/utils.ts +1 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay component that renders a trigger element and conditionally shows
|
|
3
|
+
* content via a Portal. The trigger receives a ref and optional show/hide
|
|
4
|
+
* callbacks; the content is positioned and managed by the useOverlay hook.
|
|
5
|
+
* A context Provider wraps the content to support nested overlays (e.g.,
|
|
6
|
+
* a dropdown inside another dropdown) via blocked-state propagation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
10
|
+
import { onMount, Portal } from "@pyreon/core"
|
|
11
|
+
import { render } from "@pyreon/ui-core"
|
|
12
|
+
import { PKG_NAME } from "../constants"
|
|
13
|
+
import type { Content, PyreonComponent } from "../types"
|
|
14
|
+
import useOverlay, { type UseOverlayProps } from "./useOverlay"
|
|
15
|
+
|
|
16
|
+
const IS_BROWSER = typeof window !== "undefined"
|
|
17
|
+
|
|
18
|
+
type Align = "bottom" | "top" | "left" | "right"
|
|
19
|
+
type AlignX = "left" | "center" | "right"
|
|
20
|
+
type AlignY = "bottom" | "top" | "center"
|
|
21
|
+
|
|
22
|
+
type TriggerRenderer = (
|
|
23
|
+
props: Partial<{
|
|
24
|
+
active: boolean
|
|
25
|
+
showContent: () => void
|
|
26
|
+
hideContent: () => void
|
|
27
|
+
}>,
|
|
28
|
+
) => VNodeChild
|
|
29
|
+
|
|
30
|
+
type ContentRenderer = (
|
|
31
|
+
props: Partial<{
|
|
32
|
+
active: boolean
|
|
33
|
+
showContent: () => void
|
|
34
|
+
hideContent: () => void
|
|
35
|
+
align: Align
|
|
36
|
+
alignX: AlignX
|
|
37
|
+
alignY: AlignY
|
|
38
|
+
}>,
|
|
39
|
+
) => VNodeChild
|
|
40
|
+
|
|
41
|
+
export type Props = {
|
|
42
|
+
children: ContentRenderer | Content
|
|
43
|
+
trigger: TriggerRenderer | Content
|
|
44
|
+
DOMLocation?: HTMLElement
|
|
45
|
+
triggerRefName?: string
|
|
46
|
+
contentRefName?: string
|
|
47
|
+
} & UseOverlayProps
|
|
48
|
+
|
|
49
|
+
const Component: PyreonComponent<Props> = ({
|
|
50
|
+
children,
|
|
51
|
+
trigger,
|
|
52
|
+
DOMLocation,
|
|
53
|
+
triggerRefName = "ref",
|
|
54
|
+
contentRefName = "ref",
|
|
55
|
+
...props
|
|
56
|
+
}) => {
|
|
57
|
+
const {
|
|
58
|
+
active,
|
|
59
|
+
triggerRef,
|
|
60
|
+
contentRef,
|
|
61
|
+
showContent,
|
|
62
|
+
hideContent,
|
|
63
|
+
align,
|
|
64
|
+
alignX,
|
|
65
|
+
alignY,
|
|
66
|
+
setupListeners,
|
|
67
|
+
Provider,
|
|
68
|
+
...ctx
|
|
69
|
+
} = useOverlay(props)
|
|
70
|
+
|
|
71
|
+
const { openOn, closeOn, type } = props
|
|
72
|
+
|
|
73
|
+
const passHandlers =
|
|
74
|
+
openOn === "manual" || closeOn === "manual" || closeOn === "clickOutsideContent"
|
|
75
|
+
|
|
76
|
+
const ariaHasPopup = (() => {
|
|
77
|
+
switch (type) {
|
|
78
|
+
case "modal":
|
|
79
|
+
return "dialog" as const
|
|
80
|
+
case "tooltip":
|
|
81
|
+
return "true" as const
|
|
82
|
+
default:
|
|
83
|
+
return "menu" as const
|
|
84
|
+
}
|
|
85
|
+
})()
|
|
86
|
+
|
|
87
|
+
// Set up event listeners on mount
|
|
88
|
+
onMount(() => {
|
|
89
|
+
const cleanup = setupListeners()
|
|
90
|
+
return cleanup
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
{render(trigger, {
|
|
96
|
+
[triggerRefName]: triggerRef,
|
|
97
|
+
active: active(),
|
|
98
|
+
"aria-expanded": active(),
|
|
99
|
+
"aria-haspopup": ariaHasPopup,
|
|
100
|
+
...(passHandlers ? { showContent, hideContent } : {}),
|
|
101
|
+
})}
|
|
102
|
+
|
|
103
|
+
{() =>
|
|
104
|
+
IS_BROWSER && active() ? (
|
|
105
|
+
<Portal target={DOMLocation ?? document.body}>
|
|
106
|
+
<Provider {...ctx}>
|
|
107
|
+
{render(children, {
|
|
108
|
+
[contentRefName]: contentRef,
|
|
109
|
+
role: type === "modal" ? "dialog" : undefined,
|
|
110
|
+
"aria-modal": type === "modal" ? true : undefined,
|
|
111
|
+
active: active(),
|
|
112
|
+
align,
|
|
113
|
+
alignX: alignX(),
|
|
114
|
+
alignY: alignY(),
|
|
115
|
+
...(passHandlers ? { showContent, hideContent } : {}),
|
|
116
|
+
})}
|
|
117
|
+
</Provider>
|
|
118
|
+
</Portal>
|
|
119
|
+
) : null
|
|
120
|
+
}
|
|
121
|
+
</>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const name = `${PKG_NAME}/Overlay` as const
|
|
126
|
+
|
|
127
|
+
Component.displayName = name
|
|
128
|
+
Component.pkgName = PKG_NAME
|
|
129
|
+
Component.PYREON__COMPONENT = name
|
|
130
|
+
|
|
131
|
+
export default Component
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context for nested overlay coordination. When a child overlay opens, it
|
|
3
|
+
* sets the parent's blocked state to true, preventing the parent from
|
|
4
|
+
* closing in response to click/hover events that belong to the child.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { VNodeChild } from "@pyreon/core"
|
|
8
|
+
import { createContext, provide, useContext } from "@pyreon/core"
|
|
9
|
+
|
|
10
|
+
export interface OverlayContext {
|
|
11
|
+
blocked: boolean | (() => boolean)
|
|
12
|
+
setBlocked: () => void
|
|
13
|
+
setUnblocked: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const context = createContext<OverlayContext>({} as OverlayContext)
|
|
17
|
+
|
|
18
|
+
export const useOverlayContext = () => useContext(context)
|
|
19
|
+
|
|
20
|
+
const Component = ({
|
|
21
|
+
children,
|
|
22
|
+
blocked,
|
|
23
|
+
setBlocked,
|
|
24
|
+
setUnblocked,
|
|
25
|
+
}: OverlayContext & { children: VNodeChild }) => {
|
|
26
|
+
const ctx = {
|
|
27
|
+
blocked,
|
|
28
|
+
setBlocked,
|
|
29
|
+
setUnblocked,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
provide(context, ctx)
|
|
33
|
+
|
|
34
|
+
return <>{children}</>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default Component
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import component, { type Props } from "./component"
|
|
2
|
+
import OverlayProvider from "./context"
|
|
3
|
+
import useOverlay, { type UseOverlayProps } from "./useOverlay"
|
|
4
|
+
|
|
5
|
+
export type { Props as OverlayProps, UseOverlayProps }
|
|
6
|
+
|
|
7
|
+
export { component as Overlay, OverlayProvider, useOverlay }
|