@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.
Files changed (52) hide show
  1. package/package.json +8 -7
  2. package/src/Element/component.tsx +211 -0
  3. package/src/Element/constants.ts +96 -0
  4. package/src/Element/index.ts +6 -0
  5. package/src/Element/types.ts +168 -0
  6. package/src/Element/utils.ts +15 -0
  7. package/src/List/component.tsx +57 -0
  8. package/src/List/index.ts +5 -0
  9. package/src/Overlay/component.tsx +131 -0
  10. package/src/Overlay/context.tsx +37 -0
  11. package/src/Overlay/index.ts +7 -0
  12. package/src/Overlay/useOverlay.tsx +616 -0
  13. package/src/Portal/component.tsx +41 -0
  14. package/src/Portal/index.ts +5 -0
  15. package/src/Text/component.tsx +65 -0
  16. package/src/Text/index.ts +5 -0
  17. package/src/Text/styled.ts +30 -0
  18. package/src/Util/component.tsx +43 -0
  19. package/src/Util/index.ts +5 -0
  20. package/src/__tests__/Content.test.tsx +115 -0
  21. package/src/__tests__/Element.test.ts +604 -0
  22. package/src/__tests__/Iterator.test.ts +483 -0
  23. package/src/__tests__/List.test.ts +199 -0
  24. package/src/__tests__/Overlay.test.ts +485 -0
  25. package/src/__tests__/Portal.test.ts +82 -0
  26. package/src/__tests__/Text.test.ts +274 -0
  27. package/src/__tests__/Util.test.ts +63 -0
  28. package/src/__tests__/Wrapper.test.tsx +152 -0
  29. package/src/__tests__/equalBeforeAfter.test.ts +122 -0
  30. package/src/__tests__/helpers.test.ts +65 -0
  31. package/src/__tests__/overlayContext.test.tsx +78 -0
  32. package/src/__tests__/responsiveProps.test.ts +298 -0
  33. package/src/__tests__/useOverlay.test.ts +1330 -0
  34. package/src/__tests__/utils.test.ts +69 -0
  35. package/src/constants.ts +1 -0
  36. package/src/helpers/Content/component.tsx +51 -0
  37. package/src/helpers/Content/index.ts +3 -0
  38. package/src/helpers/Content/styled.ts +105 -0
  39. package/src/helpers/Content/types.ts +49 -0
  40. package/src/helpers/Iterator/component.tsx +252 -0
  41. package/src/helpers/Iterator/index.ts +13 -0
  42. package/src/helpers/Iterator/types.ts +79 -0
  43. package/src/helpers/Wrapper/component.tsx +78 -0
  44. package/src/helpers/Wrapper/constants.ts +10 -0
  45. package/src/helpers/Wrapper/index.ts +3 -0
  46. package/src/helpers/Wrapper/styled.ts +69 -0
  47. package/src/helpers/Wrapper/types.ts +56 -0
  48. package/src/helpers/Wrapper/utils.ts +7 -0
  49. package/src/helpers/index.ts +4 -0
  50. package/src/index.ts +37 -0
  51. package/src/types.ts +81 -0
  52. 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 }