Skip to content

Commit

Permalink
Merge pull request #2 from c4tastic/feat/children-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
lbenie authored Aug 16, 2021
2 parents c702658 + abbfa1e commit 6d1ee6f
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 15 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,41 @@ import { WebComponentTag as tagName } from 'some/model/file'
export const ReactComponentWrapper = forwardRef<
WebComponentClazz | null,
WebComponentProps
>(({ children, ...props }, ref) =>
createWrapper<WebComponentClazz>({ tagName, children, props, ref })
)

// Alternatively, if your component has a default slot, you can pass `children` along

export const ReactComponentWithChildrenWrapper = forwardRef<
WebComponentClazz | null,
WebComponentProps
>((props, ref) => createWrapper<WebComponentClazz>({ tagName, props, ref }))
```

### Work with slotted elements

On top of the default slot, you can also forward `ReactElement` props to actual slot elements with the help of the `slottedNode` helper function.

```tsx
export const ReactComponentWrapper = forwardRef<
WebComponentClazz | null,
WebComponentProps
>(({ emoji, children, ...props }, ref) =>
createWrapper<WebComponentClazz>({
tagName,
props,
children: (
<>
{children}
{emoji && slottedNode(emoji, 'emoji')}
</>
),
ref,
})
)
```

## Try it

Simply clone this repository and run the `dev` command
Expand Down
31 changes: 19 additions & 12 deletions example/index.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="module" src="./lit/example/Example.lit.ts"></script>
<script type="module" src="./lit/example-with-custom-event/ExampleEvent.lit.ts"></script>
<script type="module" src="./index.tsx"></script>
</head>
<body>
<div id="root"></div>
</body>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script type="module" src="./lit/example/Example.lit.ts"></script>
<script
type="module"
src="./lit/example-with-custom-event/ExampleEvent.lit.ts"
></script>
<script
type="module"
src="./lit/example/ExampleWithChildren.lit.ts"
></script>
<script type="module" src="./index.tsx"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
1 change: 1 addition & 0 deletions example/lit/example/Example.models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const EXAMPLE = 'simple-example'
export const WITH_CHILDREN_TAG_NAME = 'example-with-children'

export interface SimpleExampleProps {
readonly msg: string
Expand Down
22 changes: 22 additions & 0 deletions example/lit/example/ExampleWithChildren.lit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { html, LitElement, customElement, css } from 'lit-element'
import { WITH_CHILDREN_TAG_NAME } from './Example.models'

@customElement(WITH_CHILDREN_TAG_NAME)
export class ExampleWithChildren extends LitElement {
static styles = css`
:host {
border: 1px solid #222;
padding: 0.5rem;
}
`
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
render() {
return html`<slot></slot> <slot name="emoji"></slot>`
}
}

declare global {
interface HTMLElementTagNameMap {
readonly [WITH_CHILDREN_TAG_NAME]: ExampleWithChildren
}
}
2 changes: 2 additions & 0 deletions example/react/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { FC } from 'react'
import { ExampleReact } from './example/Example.react'
import { ExampleReactEvent } from './example-with-event/ExampleEvent.react'
import { WithChildren } from './example-with-children/ExampleWithChildren.react'

export const App: FC = () => (
<div
Expand All @@ -12,6 +13,7 @@ export const App: FC = () => (
}}
>
<ExampleReact msg="World"></ExampleReact>
<WithChildren emoji={<span>👋</span>}>Hello there</WithChildren>
<ExampleReactEvent></ExampleReactEvent>
</div>
)
26 changes: 26 additions & 0 deletions example/react/example-with-children/ExampleWithChildren.react.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { HTMLAttributes, ReactElement } from 'react'
import { forwardRef } from 'react'
import { createWrapper, slottedNode } from '../../../src'
import type { ExampleWithChildren } from '../../lit/example/ExampleWithChildren.lit'
import { WITH_CHILDREN_TAG_NAME as tagName } from '../../lit/example/Example.models'

interface WithChildrenProps extends HTMLAttributes<ExampleWithChildren> {
readonly emoji?: ReactElement
}

export const WithChildren = forwardRef<
ExampleWithChildren | null,
WithChildrenProps
>(({ emoji, children, ...props }, ref) =>
createWrapper<ExampleWithChildren>({
tagName,
ref,
props,
children: (
<>
{children}
{emoji && slottedNode(emoji, 'emoji')}
</>
),
})
)
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { createWrapper } from './wrapper'
export { createWrapper, slottedNode } from './wrapper'
10 changes: 8 additions & 2 deletions src/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import type {
AllHTMLAttributes,
ForwardedRef,
MutableRefObject,
ReactElement,
ReactNode,
} from 'react'
import { createElement, useEffect } from 'react'
import { cloneElement, createElement, useEffect } from 'react'

interface WrapperProps<T> {
readonly tagName: string
Expand All @@ -14,6 +15,12 @@ interface WrapperProps<T> {
readonly events?: ReadonlyMap<string, EventListenerOrEventListenerObject>
}

export const slottedNode = (
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
node: ReactElement,
slot: string
): ReturnType<typeof cloneElement> => cloneElement(node, { slot })

const removeClassName = <T>(props?: WrapperProps<T>['props']) => ({
...props,
class: props?.className,
Expand All @@ -25,7 +32,6 @@ const isMutableRefObject = <T>(ref: any): ref is MutableRefObject<T> =>
'current' in ref

export const createWrapper = <T extends HTMLElement>(
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
wrapperProps: WrapperProps<T>
): ReturnType<typeof createElement> => {
const { tagName, ref, children, events, props } = wrapperProps
Expand Down

0 comments on commit 6d1ee6f

Please sign in to comment.