Skip to content

Commit

Permalink
feat(medium): adding react and vue examples
Browse files Browse the repository at this point in the history
  • Loading branch information
manoncarbonnel authored and dpellier committed Nov 10, 2023
1 parent 5cf52ad commit cff4418
Show file tree
Hide file tree
Showing 12 changed files with 714 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable */
/* tslint:disable */
/* auto-generated react proxies */
import { createReactComponent } from './react-component-lib';

import type { JSX } from '@ovhcloud/ods-component-medium/custom-elements';

import { defineCustomElement as defineOsdsMedium } from '@ovhcloud/ods-component-medium/custom-elements/osds-medium.js';

export const OsdsMedium = /*@__PURE__*/createReactComponent<JSX.OsdsMedium, HTMLOsdsMediumElement>('osds-medium', undefined, undefined, defineOsdsMedium);
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { createElement } from 'react';

import { attachProps, camelToDashCase, createForwardRef, dashToPascalCase, isCoveredByReact, mergeRefs } from './utils';

export interface HTMLStencilElement extends HTMLElement {
componentOnReady(): Promise<this>;
}

interface StencilReactInternalProps<ElementType> extends React.HTMLAttributes<ElementType> {
forwardedRef: React.RefObject<ElementType>;
ref?: React.Ref<any>;
}

export const createReactComponent = <
PropType,
ElementType extends HTMLStencilElement,
ContextStateType = {},
ExpandedPropsTypes = {}
>(
tagName: string,
ReactComponentContext?: React.Context<ContextStateType>,
manipulatePropsFunction?: (
originalProps: StencilReactInternalProps<ElementType>,
propsToPass: any
) => ExpandedPropsTypes,
defineCustomElement?: () => void
) => {
if (defineCustomElement !== undefined) {
defineCustomElement();
}

const displayName = dashToPascalCase(tagName);
const ReactComponent = class extends React.Component<StencilReactInternalProps<ElementType>> {
componentEl!: ElementType;

setComponentElRef = (element: ElementType) => {
this.componentEl = element;
};

constructor(props: StencilReactInternalProps<ElementType>) {
super(props);
}

componentDidMount() {
this.componentDidUpdate(this.props);
}

componentDidUpdate(prevProps: StencilReactInternalProps<ElementType>) {
attachProps(this.componentEl, this.props, prevProps);
}

render() {
const { children, forwardedRef, style, className, ref, ...cProps } = this.props;

let propsToPass = Object.keys(cProps).reduce((acc: any, name) => {
const value = (cProps as any)[name];

if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2).toLowerCase();
if (typeof document !== 'undefined' && isCoveredByReact(eventName)) {
acc[name] = value;
}
} else {
// we should only render strings, booleans, and numbers as attrs in html.
// objects, functions, arrays etc get synced via properties on mount.
const type = typeof value;

if (type === 'string' || type === 'boolean' || type === 'number') {
acc[camelToDashCase(name)] = value;
}
}
return acc;
}, {} as ExpandedPropsTypes);

if (manipulatePropsFunction) {
propsToPass = manipulatePropsFunction(this.props, propsToPass);
}

const newProps: Omit<StencilReactInternalProps<ElementType>, 'forwardedRef'> = {
...propsToPass,
ref: mergeRefs(forwardedRef, this.setComponentElRef),
style,
};

/**
* We use createElement here instead of
* React.createElement to work around a
* bug in Vite (https://github.com/vitejs/vite/issues/6104).
* React.createElement causes all elements to be rendered
* as <tagname> instead of the actual Web Component.
*/
return createElement(tagName, newProps, children);
}

static get displayName() {
return displayName;
}
};

// If context was passed to createReactComponent then conditionally add it to the Component Class
if (ReactComponentContext) {
ReactComponent.contextType = ReactComponentContext;
}

return createForwardRef<PropType, ElementType>(ReactComponent, displayName);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React from 'react';
import ReactDOM from 'react-dom';

import { OverlayEventDetail } from './interfaces';
import { StencilReactForwardedRef, attachProps, dashToPascalCase, defineCustomElement, setRef } from './utils';

interface OverlayElement extends HTMLElement {
present: () => Promise<void>;
dismiss: (data?: any, role?: string | undefined) => Promise<boolean>;
}

export interface ReactOverlayProps {
children?: React.ReactNode;
isOpen: boolean;
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onDidPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
onWillPresent?: (event: CustomEvent<OverlayEventDetail>) => void;
}

export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>(
tagName: string,
controller: { create: (options: any) => Promise<OverlayType> },
customElement?: any
) => {
defineCustomElement(tagName, customElement);

const displayName = dashToPascalCase(tagName);
const didDismissEventName = `on${displayName}DidDismiss`;
const didPresentEventName = `on${displayName}DidPresent`;
const willDismissEventName = `on${displayName}WillDismiss`;
const willPresentEventName = `on${displayName}WillPresent`;

type Props = OverlayComponent &
ReactOverlayProps & {
forwardedRef?: StencilReactForwardedRef<OverlayType>;
};

let isDismissing = false;

class Overlay extends React.Component<Props> {
overlay?: OverlayType;
el!: HTMLDivElement;

constructor(props: Props) {
super(props);
if (typeof document !== 'undefined') {
this.el = document.createElement('div');
}
this.handleDismiss = this.handleDismiss.bind(this);
}

static get displayName() {
return displayName;
}

componentDidMount() {
if (this.props.isOpen) {
this.present();
}
}

componentWillUnmount() {
if (this.overlay) {
this.overlay.dismiss();
}
}

handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
setRef(this.props.forwardedRef, null);
}

shouldComponentUpdate(nextProps: Props) {
// Check if the overlay component is about to dismiss
if (this.overlay && nextProps.isOpen !== this.props.isOpen && nextProps.isOpen === false) {
isDismissing = true;
}

return true;
}

async componentDidUpdate(prevProps: Props) {
if (this.overlay) {
attachProps(this.overlay, this.props, prevProps);
}

if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps);
}
if (this.overlay && prevProps.isOpen !== this.props.isOpen && this.props.isOpen === false) {
await this.overlay.dismiss();
isDismissing = false;

/**
* Now that the overlay is dismissed
* we need to render again so that any
* inner components will be unmounted
*/
this.forceUpdate();
}
}

async present(prevProps?: Props) {
const { children, isOpen, onDidDismiss, onDidPresent, onWillDismiss, onWillPresent, ...cProps } = this.props;
const elementProps = {
...cProps,
ref: this.props.forwardedRef,
[didDismissEventName]: this.handleDismiss,
[didPresentEventName]: (e: CustomEvent) => this.props.onDidPresent && this.props.onDidPresent(e),
[willDismissEventName]: (e: CustomEvent) => this.props.onWillDismiss && this.props.onWillDismiss(e),
[willPresentEventName]: (e: CustomEvent) => this.props.onWillPresent && this.props.onWillPresent(e),
};

this.overlay = await controller.create({
...elementProps,
component: this.el,
componentProps: {},
});

setRef(this.props.forwardedRef, this.overlay);
attachProps(this.overlay, elementProps, prevProps);

await this.overlay.present();
}

render() {
/**
* Continue to render the component even when
* overlay is dismissing otherwise component
* will be hidden before animation is done.
*/
return ReactDOM.createPortal(this.props.isOpen || isDismissing ? this.props.children : null, this.el);
}
}

return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />;
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { createReactComponent } from './createComponent';
export { createOverlayComponent } from './createOverlayComponent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// General types important to applications using stencil built components
export interface EventEmitter<T = any> {
emit: (data?: T) => CustomEvent<T>;
}

export interface StyleReactProps {
class?: string;
className?: string;
style?: { [key: string]: any };
}

export interface OverlayEventDetail<T = any> {
data?: T;
role?: string;
}

export interface OverlayInterface {
el: HTMLElement;
animated: boolean;
keyboardClose: boolean;
overlayIndex: number;
presented: boolean;

enterAnimation?: any;
leaveAnimation?: any;

didPresent: EventEmitter<void>;
willPresent: EventEmitter<void>;
willDismiss: EventEmitter<OverlayEventDetail>;
didDismiss: EventEmitter<OverlayEventDetail>;

present(): Promise<void>;
dismiss(data?: any, role?: string): Promise<boolean>;
}
Loading

0 comments on commit cff4418

Please sign in to comment.