mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-11 23:02:56 +01:00
Updated project to React 19
This commit is contained in:
@@ -1,23 +1,24 @@
|
||||
const path = require('path');
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
import path from 'path';
|
||||
|
||||
const editorDir = path.join(__dirname, '../../noodl-editor');
|
||||
const coreLibDir = path.join(__dirname, '../');
|
||||
|
||||
module.exports = {
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'@storybook/preset-create-react-app',
|
||||
'@storybook/addon-measure'
|
||||
],
|
||||
framework: '@storybook/react',
|
||||
core: {
|
||||
builder: '@storybook/builder-webpack5'
|
||||
framework: {
|
||||
name: '@storybook/react-webpack5',
|
||||
options: {}
|
||||
},
|
||||
webpackFinal: (config) => {
|
||||
const destinationPath = path.resolve(__dirname, '../../noodl-editor');
|
||||
const addExternalPath = (rules) => {
|
||||
const addExternalPath = (rules: any[]) => {
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const rule = rules[i];
|
||||
if (rule.test && RegExp(rule.test).test('.tsx')) {
|
||||
@@ -32,17 +33,20 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
addExternalPath(config.module.rules);
|
||||
if (config.module?.rules) {
|
||||
addExternalPath(config.module.rules as any[]);
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.ts$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('ts-loader')
|
||||
}
|
||||
]
|
||||
});
|
||||
config.module.rules.push({
|
||||
test: /\.ts$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('ts-loader')
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
config.resolve = config.resolve || {};
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
'@noodl-core-ui': path.join(coreLibDir, 'src'),
|
||||
@@ -56,5 +60,10 @@ module.exports = {
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
typescript: {
|
||||
reactDocgen: 'react-docgen-typescript'
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"name": "@noodl/noodl-core-ui",
|
||||
"version": "2.7.0",
|
||||
"scripts": {
|
||||
"start": "start-storybook -p 6006 -s public",
|
||||
"build": "build-storybook -s public"
|
||||
"start": "storybook dev -p 6006",
|
||||
"build": "storybook build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -42,14 +42,20 @@
|
||||
"react-dom": "19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^8.6.14",
|
||||
"@storybook/addon-interactions": "^8.6.14",
|
||||
"@storybook/addon-links": "^8.6.14",
|
||||
"@storybook/addon-measure": "^8.6.14",
|
||||
"@storybook/react": "^8.6.14",
|
||||
"@storybook/react-webpack5": "^8.6.14",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.42",
|
||||
"@types/react": "19.0.0",
|
||||
"@types/react-dom": "19.0.0",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"babel-plugin-named-exports-order": "^0.0.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"sass": "^1.90.0",
|
||||
"storybook": "^9.1.3",
|
||||
"storybook": "^8.6.14",
|
||||
"ts-loader": "^9.5.4",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^3.5.2",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { ChangeEventHandler, cloneElement, FocusEventHandler, MouseEventHandler } from 'react';
|
||||
import React, { ChangeEventHandler, cloneElement, FocusEventHandler, isValidElement, MouseEventHandler, ReactElement } from 'react';
|
||||
|
||||
import { InputNotification } from '@noodl-types/globalInputTypes';
|
||||
|
||||
@@ -113,7 +113,7 @@ export function Checkbox({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{children && <div className={css['ChildContainer']}>{cloneElement(children, { isChecked })}</div>}
|
||||
{children && isValidElement(children) && <div className={css['ChildContainer']}>{cloneElement(children as ReactElement<{ isChecked?: boolean }>, { isChecked })}</div>}
|
||||
{label && <InputLabelSection label={label} />}
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -89,7 +89,7 @@ export function CoreBaseDialog({
|
||||
}, 50);
|
||||
}, [isVisible]);
|
||||
|
||||
const dialogRef = useRef<HTMLDivElement>();
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
const [dialogPosition, setDialogPosition] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
@@ -45,7 +45,7 @@ export function Carousel({ activeIndex, items, indicator }: CarouselProps) {
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
<HStack UNSAFE_style={{ width: items.length * 100 + '%' }}>
|
||||
{items.map((item, index) => (
|
||||
<VStack key={index} ref={(ref) => (sliderRefs.current[index] = ref)} UNSAFE_style={{ width: '100%' }}>
|
||||
<VStack key={index} ref={(ref) => { sliderRefs.current[index] = ref; }} UNSAFE_style={{ width: '100%' }}>
|
||||
{item.slot}
|
||||
</VStack>
|
||||
))}
|
||||
|
||||
@@ -91,6 +91,10 @@ export function Columns({
|
||||
}}
|
||||
>
|
||||
{toArray(children).map((child, i) => {
|
||||
// Skip non-element children (null, undefined, boolean)
|
||||
if (!React.isValidElement(child)) {
|
||||
return child;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="column-item"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { cloneElement, MouseEventHandler } from 'react';
|
||||
import React, { cloneElement, isValidElement, MouseEventHandler, ReactElement } from 'react';
|
||||
|
||||
import { Icon, IconName, IconSize } from '@noodl-core-ui/components/common/Icon';
|
||||
import { BaseDialog, BaseDialogProps } from '@noodl-core-ui/components/layout/BaseDialog';
|
||||
@@ -100,7 +100,10 @@ export function MenuDialog({
|
||||
{item.label}
|
||||
</Label>
|
||||
</div>
|
||||
{item.component && cloneElement(item.component(() => onClose()))}
|
||||
{item.component && (() => {
|
||||
const component = item.component(() => onClose());
|
||||
return isValidElement(component) ? cloneElement(component as ReactElement) : component;
|
||||
})()}
|
||||
<div className={css['EndSlot']}>
|
||||
{item.endSlot && typeof item.endSlot === 'string' && <Text>{item.endSlot}</Text>}
|
||||
{item.endSlot && typeof item.endSlot !== 'string' && item.endSlot}
|
||||
|
||||
@@ -1,92 +1,91 @@
|
||||
import { Slot } from '@noodl-core-ui/types/global';
|
||||
import classNames from 'classnames';
|
||||
import React, { CSSProperties, useLayoutEffect, useRef, useState } from 'react';
|
||||
import css from './PopupSection.module.scss';
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Remove style prop and replace with "isInactive" prop
|
||||
*/
|
||||
|
||||
export interface PopupSectionProps {
|
||||
children?: Slot;
|
||||
title?: string;
|
||||
className?: string;
|
||||
maxContentHeight?: number;
|
||||
|
||||
hasBottomBorder?: boolean;
|
||||
hasYPadding?: boolean;
|
||||
isCenteringChildren?: boolean;
|
||||
|
||||
style?: CSSProperties;
|
||||
contentContainerStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export function PopupSection({
|
||||
children,
|
||||
title,
|
||||
className,
|
||||
maxContentHeight,
|
||||
|
||||
hasBottomBorder,
|
||||
hasYPadding,
|
||||
isCenteringChildren,
|
||||
|
||||
style,
|
||||
contentContainerStyle
|
||||
}: PopupSectionProps) {
|
||||
const contentRef = useRef<HTMLDivElement>();
|
||||
const [shouldScroll, setShouldScroll] = useState(false);
|
||||
const [hasBeenCalculated, setHasBeenCalculated] = useState(false);
|
||||
|
||||
function checkIfShouldScroll() {
|
||||
if (!contentRef.current || !maxContentHeight) return false;
|
||||
if (contentRef.current.getBoundingClientRect().height >= maxContentHeight) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setShouldScroll(checkIfShouldScroll());
|
||||
setHasBeenCalculated(true);
|
||||
}
|
||||
}, [contentRef, children]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames([css['Root'], hasBottomBorder && css['has-bottom-border'], className])}
|
||||
style={{
|
||||
...style,
|
||||
overflowY: hasBeenCalculated ? undefined : 'hidden',
|
||||
height: hasBeenCalculated ? undefined : 0
|
||||
}}
|
||||
>
|
||||
{title && (
|
||||
<header className={css['Header']}>
|
||||
<h2 className={css['Title']}>{title}</h2>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{children ? (
|
||||
<div
|
||||
className={classNames([
|
||||
css['Content'],
|
||||
hasYPadding && css['has-y-padding'],
|
||||
isCenteringChildren && css['is-centering-children']
|
||||
])}
|
||||
ref={contentRef}
|
||||
style={{
|
||||
...contentContainerStyle,
|
||||
height: shouldScroll ? maxContentHeight : undefined,
|
||||
// @ts-expect-error
|
||||
overflowY: shouldScroll ? 'overlay' : undefined
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<div ref={contentRef} />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
import { Slot } from '@noodl-core-ui/types/global';
|
||||
import classNames from 'classnames';
|
||||
import React, { CSSProperties, useLayoutEffect, useRef, useState } from 'react';
|
||||
import css from './PopupSection.module.scss';
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Remove style prop and replace with "isInactive" prop
|
||||
*/
|
||||
|
||||
export interface PopupSectionProps {
|
||||
children?: Slot;
|
||||
title?: string;
|
||||
className?: string;
|
||||
maxContentHeight?: number;
|
||||
|
||||
hasBottomBorder?: boolean;
|
||||
hasYPadding?: boolean;
|
||||
isCenteringChildren?: boolean;
|
||||
|
||||
style?: CSSProperties;
|
||||
contentContainerStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export function PopupSection({
|
||||
children,
|
||||
title,
|
||||
className,
|
||||
maxContentHeight,
|
||||
|
||||
hasBottomBorder,
|
||||
hasYPadding,
|
||||
isCenteringChildren,
|
||||
|
||||
style,
|
||||
contentContainerStyle
|
||||
}: PopupSectionProps) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [shouldScroll, setShouldScroll] = useState(false);
|
||||
const [hasBeenCalculated, setHasBeenCalculated] = useState(false);
|
||||
|
||||
function checkIfShouldScroll() {
|
||||
if (!contentRef.current || !maxContentHeight) return false;
|
||||
if (contentRef.current.getBoundingClientRect().height >= maxContentHeight) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (contentRef.current) {
|
||||
setShouldScroll(checkIfShouldScroll());
|
||||
setHasBeenCalculated(true);
|
||||
}
|
||||
}, [contentRef, children]);
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames([css['Root'], hasBottomBorder && css['has-bottom-border'], className])}
|
||||
style={{
|
||||
...style,
|
||||
overflowY: hasBeenCalculated ? undefined : 'hidden',
|
||||
height: hasBeenCalculated ? undefined : 0
|
||||
}}
|
||||
>
|
||||
{title && (
|
||||
<header className={css['Header']}>
|
||||
<h2 className={css['Title']}>{title}</h2>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{children ? (
|
||||
<div
|
||||
className={classNames([
|
||||
css['Content'],
|
||||
hasYPadding && css['has-y-padding'],
|
||||
isCenteringChildren && css['is-centering-children']
|
||||
])}
|
||||
ref={contentRef}
|
||||
style={{
|
||||
...contentContainerStyle,
|
||||
height: shouldScroll ? maxContentHeight : undefined,
|
||||
overflowY: shouldScroll ? 'overlay' : undefined
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<div ref={contentRef} />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function PropertyPanelSelectInput({
|
||||
hasSmallText
|
||||
}: PropertyPanelSelectInputProps) {
|
||||
const [isSelectCollapsed, setIsSelectCollapsed] = useState(true);
|
||||
const rootRef = useRef<HTMLDivElement>();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const displayValue = properties?.options.find((option) => option.value === value)?.label;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export function PropertyPanelSliderInput({
|
||||
}
|
||||
|
||||
const thumbPercentage = useMemo(
|
||||
() => linearMap(parseInt(value.toString()), properties.min, properties.max, 0, 100),
|
||||
() => linearMap(parseInt(value.toString()), Number(properties.min), Number(properties.max), 0, 100),
|
||||
[value, properties]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import React, {
|
||||
JSXElementConstructor,
|
||||
ReactChild,
|
||||
ReactElement,
|
||||
ReactFragment,
|
||||
ReactPortal,
|
||||
ReactText
|
||||
} from 'react';
|
||||
|
||||
export interface UnsafeStyleProps {
|
||||
UNSAFE_className?: string;
|
||||
UNSAFE_style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// FIXME: add generics to be able to specify what exact components are allowed?
|
||||
export type SingleSlot =
|
||||
| ReactElement<TSFixme, TSFixme>
|
||||
| ReactFragment
|
||||
| ReactPortal
|
||||
| boolean
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type Slot = SingleSlot | SingleSlot[];
|
||||
import React, { ReactElement, ReactPortal } from 'react';
|
||||
|
||||
export interface UnsafeStyleProps {
|
||||
UNSAFE_className?: string;
|
||||
UNSAFE_style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// FIXME: add generics to be able to specify what exact components are allowed?
|
||||
// Note: ReactFragment removed in React 19, using React.ReactNode for fragments
|
||||
export type SingleSlot =
|
||||
| ReactElement<TSFixme, TSFixme>
|
||||
| Iterable<React.ReactNode>
|
||||
| ReactPortal
|
||||
| boolean
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type Slot = SingleSlot | SingleSlot[];
|
||||
|
||||
Reference in New Issue
Block a user