Skip to content

Commit 35b005c

Browse files
Merge pull request #147 from linked-planet/dev
work on shadow dom support
2 parents 635c251 + f638b7b commit 35b005c

24 files changed

+3173
-431
lines changed

bundler_plugins/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
// the .js extension is necessary because the bundler plugins are not typescript files
22
// and we need to use the .js extension to import them as esm modules
33
import viteAppendCssPlugin from "./vite-append-css-plugin.js"
4-
import { postcssClassPrefixerPlugin } from "./postcss_prefix_classname-plugin.js"
54
import rollupClassPrefixerPlugin from "./rollup_class_prefixer-plugin.js"
65

7-
export {
8-
viteAppendCssPlugin,
9-
postcssClassPrefixerPlugin,
10-
rollupClassPrefixerPlugin,
11-
}
6+
export { viteAppendCssPlugin, rollupClassPrefixerPlugin }

bundler_plugins/postcss_prefix_classname-plugin.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

library/src/components/Blanket.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import ReactDOM from "react-dom"
22
import type { ComponentPropsWithoutRef } from "react"
33
import { twMerge } from "tailwind-merge"
4-
import { getPortal } from "../utils"
5-
4+
import { usePortalContainer } from "../utils"
65
type BlanketProps = ComponentPropsWithoutRef<"div"> & {
7-
usePortal?: boolean
8-
portalContainer?: HTMLElement | null
6+
usePortal?: boolean | ShadowRoot
97
}
108

119
export function Blanket({
@@ -14,9 +12,10 @@ export function Blanket({
1412
"aria-label": ariaLabel,
1513
role,
1614
usePortal = true,
17-
portalContainer = getPortal("uikts-blanket"),
1815
...props
1916
}: BlanketProps) {
17+
const portalContainer = usePortalContainer(usePortal, "uikts-blanket", null)
18+
2019
const ele = (
2120
<div
2221
className={twMerge(
@@ -38,7 +37,7 @@ export function Blanket({
3837
</div>
3938
</div>
4039
)
41-
if (!usePortal || portalContainer === null) {
40+
if (!portalContainer) {
4241
return ele
4342
}
4443
return ReactDOM.createPortal(ele, portalContainer)

library/src/components/DropdownMenu.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import * as RDd from "@radix-ui/react-dropdown-menu"
2-
import { forwardRef, useMemo, useRef, type ForwardedRef } from "react"
2+
import {
3+
forwardRef,
4+
useImperativeHandle,
5+
useMemo,
6+
useRef,
7+
type ForwardedRef,
8+
} from "react"
39
import { twMerge } from "tailwind-merge"
4-
import { getPortal } from "../utils"
10+
import { usePortalContainer } from "../utils"
511
import { Button, type ButtonProps } from "./Button"
612
import { overlayBaseStyle } from "./styleHelper"
713
import { IconSizeHelper } from "./IconSizeHelper"
@@ -425,7 +431,7 @@ export type DropdownMenuProps = {
425431
trigger?: React.ReactNode
426432
triggerComponent?: React.ReactElement<DropdownTriggerProps>
427433
children: React.ReactNode
428-
usePortal?: boolean
434+
usePortal?: boolean | ShadowRoot
429435
testId?: string
430436
hideChevron?: boolean
431437
modal?: boolean
@@ -590,6 +596,9 @@ const Menu = forwardRef<HTMLButtonElement, DropdownMenuProps>(
590596
contentStyle,
591597
],
592598
)
599+
const triggerRef = useRef<HTMLButtonElement>(null)
600+
// biome-ignore lint/style/noNonNullAssertion: save here
601+
useImperativeHandle(ref, () => triggerRef.current!)
593602

594603
const _trigger = triggerComponent ?? (
595604
<Trigger
@@ -601,12 +610,18 @@ const Menu = forwardRef<HTMLButtonElement, DropdownMenuProps>(
601610
chevronStyle={chevronStyle}
602611
style={triggerStyle}
603612
{...props}
604-
ref={ref}
613+
ref={triggerRef}
605614
>
606615
{trigger ?? "trigger"}
607616
</Trigger>
608617
)
609618

619+
const portalContainer = usePortalContainer(
620+
usePortal,
621+
"uikts-dropdown",
622+
triggerRef.current,
623+
)
624+
610625
return (
611626
<RDd.Root
612627
open={open}
@@ -623,7 +638,7 @@ const Menu = forwardRef<HTMLButtonElement, DropdownMenuProps>(
623638
{_trigger}
624639
</RDd.Trigger>
625640
{usePortal ? (
626-
<RDd.Portal container={getPortal(portalDivId)}>
641+
<RDd.Portal container={portalContainer}>
627642
{content}
628643
</RDd.Portal>
629644
) : (

library/src/components/Modal.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import React, {
88
useEffect,
99
useState,
1010
useCallback,
11+
useImperativeHandle,
1112
} from "react"
1213
import { twJoin, twMerge } from "tailwind-merge"
13-
import { getPortal } from "../utils/getPortal"
14+
1415
import { overlayBaseStyle } from "./styleHelper"
1516
import { VisuallyHidden } from "@radix-ui/react-visually-hidden"
17+
import { Button } from "./Button"
18+
import usePortalContainer from "../utils/usePortalContainer"
1619

1720
type ModalDialogProps = {
1821
open?: boolean
@@ -25,7 +28,7 @@ type ModalDialogProps = {
2528
style?: CSSProperties
2629
shouldCloseOnEscapePress?: boolean
2730
shouldCloseOnOverlayClick?: boolean
28-
usePortal?: boolean
31+
usePortal?: boolean | ShadowRoot
2932
useModal?: boolean
3033
id?: string
3134
triggerId?: string
@@ -35,11 +38,14 @@ type ModalDialogProps = {
3538
accessibleDialogDescription: string
3639
role?: RDialog.DialogContentProps["role"]
3740
tabIndex?: RDialog.DialogContentProps["tabIndex"]
41+
ref?: React.Ref<HTMLButtonElement>
3842
}
3943

4044
const blanketStyles =
4145
"fixed inset-0 bg-blanket ease-out transition-opacity duration-200 animate-fade-in"
4246

47+
const portalContainerId = "uikts-modal" as const
48+
4349
function Container({
4450
shouldCloseOnEscapePress = true,
4551
shouldCloseOnOverlayClick = true,
@@ -60,7 +66,18 @@ function Container({
6066
role = "dialog",
6167
accessibleDialogDescription,
6268
tabIndex = undefined,
69+
ref,
6370
}: ModalDialogProps) {
71+
const triggerRef = useRef<HTMLButtonElement>(null)
72+
// biome-ignore lint/style/noNonNullAssertion: safe if the trigger is used
73+
useImperativeHandle(ref, () => triggerRef.current!)
74+
75+
const portalContainer: HTMLElement | null = usePortalContainer(
76+
usePortal,
77+
portalContainerId,
78+
triggerRef.current,
79+
)
80+
6481
const content = useMemo(
6582
() => (
6683
<>
@@ -126,6 +143,8 @@ function Container({
126143
],
127144
)
128145

146+
console.log("portalContainer", portalContainer?.parentElement?.parentNode)
147+
129148
return (
130149
<RDialog.Root
131150
open={open}
@@ -134,13 +153,18 @@ function Container({
134153
modal={useModal}
135154
>
136155
{trigger && (
137-
<RDialog.Trigger id={triggerId} data-testid={triggerTestId}>
138-
{trigger}
156+
<RDialog.Trigger
157+
id={triggerId}
158+
data-testid={triggerTestId}
159+
asChild
160+
ref={triggerRef}
161+
>
162+
<Button>{"Open Modal"}</Button>
139163
</RDialog.Trigger>
140164
)}
141165

142166
{usePortal ? (
143-
<RDialog.Portal container={getPortal("uikts-modal")}>
167+
<RDialog.Portal container={portalContainer}>
144168
{content}
145169
</RDialog.Portal>
146170
) : (

library/src/components/Popover.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as RPo from "@radix-ui/react-popover"
22
import { forwardRef, useMemo, useRef } from "react"
33
import { twMerge } from "tailwind-merge"
4-
import { getPortal } from "../utils"
4+
import { usePortalContainer } from "../utils"
55
import { Button, type ButtonProps } from "./Button"
66
import { overlayBaseStyle } from "./styleHelper"
77
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
@@ -70,7 +70,7 @@ const Trigger = forwardRef<HTMLButtonElement, TriggerProps>(
7070
)
7171

7272
export type PopoverProps = RPo.PopoverProps & {
73-
usePortal?: boolean
73+
usePortal?: boolean | ShadowRoot
7474
/* trigger replaces the content of the trigger button */
7575
trigger?: React.ReactNode
7676
/* triggerComponent replaces the the trigger button component */
@@ -203,6 +203,13 @@ function Root({
203203
</Trigger>
204204
)
205205

206+
const triggerRef = useRef<HTMLButtonElement>(null)
207+
const portalContainer = usePortalContainer(
208+
usePortal,
209+
"uikts-popover",
210+
triggerRef.current,
211+
)
212+
206213
return (
207214
<RPo.Root
208215
open={open}
@@ -211,11 +218,11 @@ function Root({
211218
onOpenChange={onOpenChange}
212219
data-testid={testId}
213220
>
214-
<RPo.Trigger asChild={triggerAsChild}>{_trigger}</RPo.Trigger>
221+
<RPo.Trigger asChild ref={triggerRef}>
222+
{_trigger}
223+
</RPo.Trigger>
215224
{usePortal ? (
216-
<RPo.Portal container={getPortal(portalDivId)}>
217-
{content}
218-
</RPo.Portal>
225+
<RPo.Portal container={portalContainer}>{content}</RPo.Portal>
219226
) : (
220227
content
221228
)}

library/src/components/ToastFlag.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ const toastProxyMap = (() => {
327327
const value = Reflect.get(target, prop, receiver)
328328
// If the property is a method, bind it to the target Map
329329
if (typeof value === "function") {
330-
return (...args: any[]) => {
330+
return (...args: unknown[]) => {
331331
const result = value.apply(target, args)
332332
mapSnapshot = new Map(target)
333333
if (

library/src/components/Tooltip.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import * as RTTp from "@radix-ui/react-tooltip"
22
import type React from "react"
3-
import { type CSSProperties, useMemo } from "react"
3+
import { type CSSProperties, useMemo, useRef } from "react"
44
import { twMerge } from "tailwind-merge"
5-
import { getPortal } from "../utils"
5+
import { usePortalContainer } from "../utils"
66

77
const portalDivId = "uikts-tooltip" as const
88

99
export type TooltipProps = {
1010
tooltipContent?: React.ReactNode
1111
tooltipHTMLContent?: string
12-
usePortal?: boolean
12+
usePortal?: boolean | ShadowRoot
1313
className?: string
1414
style?: CSSProperties
1515
tooltipClassName?: string
@@ -79,6 +79,14 @@ export function Tooltip({
7979
tooltipStyle,
8080
])
8181

82+
const triggerRef = useRef<HTMLButtonElement>(null)
83+
84+
const portalContainer = usePortalContainer(
85+
usePortal,
86+
"uikts-tooltip",
87+
triggerRef.current,
88+
)
89+
8290
return (
8391
<RTTp.Root
8492
open={open}
@@ -92,13 +100,12 @@ export function Tooltip({
92100
asChild
93101
data-testid={triggerTestId}
94102
id={triggerId}
103+
ref={triggerRef}
95104
>
96105
{children}
97106
</RTTp.Trigger>
98107
{usePortal ? (
99-
<RTTp.Portal container={getPortal(portalDivId)}>
100-
{content}
101-
</RTTp.Portal>
108+
<RTTp.Portal container={portalContainer}>{content}</RTTp.Portal>
102109
) : (
103110
content
104111
)}

library/src/components/form/elements/SelectSingleFormField.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export interface SelectSingleFormFieldProps<
1010
options: Array<{ label: string; value: A }>
1111
onChange?: (value: A) => void
1212
placeholder?: string
13+
usePortal?: boolean
14+
menuIsOpen?: boolean
1315
}
1416

1517
export function SelectSingleFormField<
@@ -24,6 +26,8 @@ export function SelectSingleFormField<
2426
required,
2527
options,
2628
placeholder,
29+
usePortal,
30+
menuIsOpen,
2731
}: SelectSingleFormFieldProps<T, A>) {
2832
const fieldValue = formProps.watch(name)
2933
const onChangeCB = useRef(onChange)
@@ -55,6 +59,8 @@ export function SelectSingleFormField<
5559
required={required}
5660
disabled={formProps.readonly}
5761
placeholder={placeholder}
62+
usePortal={usePortal}
63+
menuIsOpen={menuIsOpen}
5864
/>
5965
</div>
6066
)

0 commit comments

Comments
 (0)