diff --git a/.eslintignore b/.eslintignore index d9eb23ba..bbbe62b6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,5 @@ build/* node_modules/* src/react-app-env.d.ts src/declarations.d.ts -src/components/Atomic/Icon/components/* \ No newline at end of file +src/components/Atomic/Icon/components/* +!.storybook \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 72fbf269..c506005a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -93,7 +93,18 @@ module.exports = { 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'warn' } - }], + }, + { + // or whatever matches stories specified in .storybook/main.js + "files": ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)'], + "rules": { + // example of overriding a rule + 'storybook/hierarchy-separator': 'error', + // example of disabling a rule + 'storybook/default-exports': 'off', + } + } + ], rules: { // http://eslint.org/docs/rules/ 'array-callback-return': 'warn', diff --git a/.gitignore b/.gitignore index 3c76345c..5f572149 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +*storybook.log +test-report.xml \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..92c7fff5 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname "$0")/_/husky.sh" + +npm run test \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts index c307562e..aaa1848c 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,54 +1,45 @@ -import { dirname, join } from "path"; -import type { StorybookConfig } from '@storybook/react-webpack5'; +import type { StorybookConfig } from '@storybook/react-webpack5' +import { join, dirname } from 'path' + +/** + * This function is used to resolve the absolute path of a package. + * It is needed in projects that use Yarn PnP or are set up within a monorepo. + */ +function getAbsolutePath(value: string): any { + return dirname(require.resolve(join(value, 'package.json'))) +} const config: StorybookConfig = { - stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: [ - getAbsolutePath("@storybook/preset-scss"), - getAbsolutePath("@storybook/addon-links"), - getAbsolutePath("@storybook/addon-essentials"), - getAbsolutePath("@storybook/addon-interactions"), - { - name: '@storybook/addon-styling', - options: { - sass: { - // Require your Sass preprocessor here - implementation: require('sass'), + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: [ + getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), + getAbsolutePath('@storybook/addon-onboarding'), + getAbsolutePath('@storybook/addon-links'), + getAbsolutePath('@storybook/addon-essentials'), + getAbsolutePath('@chromatic-com/storybook'), + getAbsolutePath('@storybook/addon-interactions'), + getAbsolutePath('@storybook/addon-themes'), + ], + framework: { + name: getAbsolutePath('@storybook/react-webpack5'), + options: {}, + }, + babel: async (options:any) => { + options.plugins.push('babel-plugin-inline-react-svg') + return options; + }, + swc: () => ({ + jsc: { + transform: { + react: { + runtime: 'automatic', + }, + }, }, - }, + }), + staticDirs: ['../public'], + docs: { + autodocs: false, }, - getAbsolutePath("storybook-addon-themes") - ], - framework: { - name: getAbsolutePath("@storybook/react-webpack5"), - - options: { - builder: { - fsCache: true, - lazyCompilation: true - } - } - }, - babel: async (options:any) => { - options.plugins.push('babel-plugin-inline-react-svg') - return options; - }, - // webpackFinal: async config => { - // config.resolve.alias = { - // ...(config.resolve.alias || []) - // }; - // config.resolve.extensions.push('.ts', '.tsx') - // return config - // }, - core: {}, - staticDirs: ['../public'], - docs: { - autodocs: false - } } - export default config - -function getAbsolutePath(value: string): any { - return dirname(require.resolve(join(value, "package.json"))); -} \ No newline at end of file diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html index 50296b87..e69de29b 100644 --- a/.storybook/preview-body.html +++ b/.storybook/preview-body.html @@ -1,3 +0,0 @@ -
- - \ No newline at end of file diff --git a/.storybook/preview.js b/.storybook/preview.js deleted file mode 100644 index def506f0..00000000 --- a/.storybook/preview.js +++ /dev/null @@ -1,63 +0,0 @@ -import { ThemeProvider } from '@emotion/react' -import { addons } from '@storybook/addons' -import { useEffect, useState } from 'react' -import { CHANGE } from 'storybook-addon-themes/src/constants' - -import plgd from '../src/components/Atomic/_theme/plgd' -import siemens from '../src/components/Atomic/_theme/siemens' - -import 'bootstrap/dist/css/bootstrap.min.css' -import 'bootstrap/dist/js/bootstrap.bundle' -// @import '@/assets/fonts/fontawesome/css/all.min.css'; -import '../../../src/assets/fonts/fontawesome/css/all.min.css'; - -import '../src/common/styles/colors.scss' -import '../src/common/styles/default.scss' -import '../src/common/styles/animations.scss' -import '../src/common/styles/form.scss' - - -export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - themes: { - default: 'plgd', - list: [ - { name: 'plgd', class: ['theme-plgd', 'plgd-mode'], color: '#00aced' }, - // { name: 'dark', class: ['theme-fb', 'dark-mode'], color: '#3b5998' }, - { name: 'siemens', class: ['theme-siemens', 'siemens-mode'], color: '#000028' }, - ], - }, -} - -const channel = addons.getChannel() - -const withTheme = (StoryFn, context) => { - const { themes } = context.parameters - - const [themeName, setThemeName] = useState(() => { - const lastValue = channel.last(CHANGE); - return (lastValue && lastValue[0]) || themes.default - }) - - const getThemeByKey = (themeName) => themeName === 'plgd' ? plgd : siemens - - useEffect(() => { - channel.on(CHANGE, setThemeName); - return () => channel.removeListener(CHANGE, setThemeName) - }, [setThemeName]) - - return ( - - - - ) -} - -// export all decorators that should be globally applied in an array -export const decorators = [withTheme] \ No newline at end of file diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 00000000..f9c3ae47 --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import type { Preview } from '@storybook/react' +import {ThemeProvider} from "@emotion/react"; +import { withThemeByClassName } from '@storybook/addon-themes' + +import plgd from '../src/components/Atomic/_theme/plgd' +import siemens from "../src/components/Atomic/_theme/siemens"; +import Aoo from "../src/components/Atomic/App/App"; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +} + +export default preview + + +const withTheme = (StoryFn: any, context: any) => { + const getThemeByKey = (themeName: string) => themeName === 'plgd' ? plgd : siemens + + return ( + + + + + + + + ) +} + +export const decorators = [withTheme, withThemeByClassName({ + themes: { + plgd: 'plgd-theme', + siemens: 'siemens-theme', + }, + defaultTheme: 'plgd', +})] \ No newline at end of file diff --git a/.storybook/webpack.config.ts b/.storybook/webpack.config.ts new file mode 100644 index 00000000..43b15e7d --- /dev/null +++ b/.storybook/webpack.config.ts @@ -0,0 +1,14 @@ +const path = require("path"); + +module.exports = function({ config }: any) { + config.module.rules.push({ + test: /\.(ts|tsx)$/, + loader: require.resolve("babel-loader"), + options: { + presets: [["react-app", { flow: false, typescript: true }], require.resolve("@emotion/babel-preset-css-prop")], + }, + }); + + config.resolve.extensions.push(".ts", ".tsx"); + return config; +}; \ No newline at end of file diff --git a/package.json b/package.json index d9feead0..a5e2c405 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,13 @@ ":lint:eslint": "eslint --ext .js,.jsx,.ts,.tsx -c .eslintrc.js --max-warnings 0 --fix --format=pretty ./src", ":lint:prettier": "prettier ./src --check", "prebuild": "rimraf build", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build", - "deploy-storybook": "storybook-to-ghpages --dry-run", "icons:create": "npx @svgr/cli --config-file ./config/.svgrrc.js -d ./src/components/Atomic/Icon/components ./src/components/Atomic/Icon/assets", ":generate:theme:build": "node ./scripts/build.theme.js", ":generate:theme:clean": "rimraf ./build/lib", - ":generate:theme": "npm-run-all :generate:theme:build :generate:theme:clean" + ":generate:theme": "npm-run-all :generate:theme:build :generate:theme:clean", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", + "deploy-storybook": "storybook-to-ghpages --dry-run" }, "dependencies": { "@babel/core": "^7.23.3", @@ -58,7 +58,6 @@ "@opentelemetry/instrumentation-xml-http-request": "^0.45.1", "@opentelemetry/sdk-trace-base": "^1.18.1", "@opentelemetry/sdk-trace-web": "^1.18.1", - "@storybook/addons": "^7.6.13", "@types/convert-units": "^2.3.9", "@types/dompurify": "^3.0.2", "@types/lodash": "^4.14.202", @@ -119,17 +118,18 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-transform-runtime": "^7.23.7", "@babel/runtime": "^7.23.8", + "@chromatic-com/storybook": "^1.3.2", "@emotion/jest": "^11.11.0", - "@storybook/addon-actions": "^7.6.13", - "@storybook/addon-essentials": "^7.6.13", - "@storybook/addon-interactions": "^7.6.13", - "@storybook/addon-links": "^7.6.13", - "@storybook/addon-styling": "^1.3.7", - "@storybook/preset-scss": "^1.0.3", - "@storybook/react": "^7.6.13", - "@storybook/react-webpack5": "^7.6.13", - "@storybook/storybook-deployer": "^2.8.16", - "@storybook/testing-library": "^0.2.2", + "@storybook/addon-essentials": "^8.0.8", + "@storybook/addon-interactions": "^8.0.8", + "@storybook/addon-links": "^8.0.8", + "@storybook/addon-onboarding": "^8.0.8", + "@storybook/addon-themes": "^8.0.8", + "@storybook/addon-webpack5-compiler-swc": "^1.0.2", + "@storybook/blocks": "^8.0.8", + "@storybook/react": "^8.0.8", + "@storybook/react-webpack5": "^8.0.8", + "@storybook/test": "^8.0.8", "@svgr/cli": "^8.1.0", "@svgr/webpack": "^8.1.0", "@testing-library/jest-dom": "^6.1.4", @@ -152,7 +152,7 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-storybook": "^0.6.15", + "eslint-plugin-storybook": "^0.8.0", "eslint-plugin-testing-library": "^6.0.2", "fast-glob": "^3.3.2", "fs-extra": "^11.1.1", @@ -171,8 +171,7 @@ "rimraf": "^5.0.5", "sass": "^1.69.5", "sass-loader": "^13.3.2", - "storybook": "^7.6.13", - "storybook-addon-themes": "^6.1.0", + "storybook": "^8.0.8", "style-loader": "^3.3.3", "ts-jest": "^29.1.1", "util": "^0.12.5", diff --git a/src/components/Atomic/CodeEditor/CodeEditor.test.tsx b/src/components/Atomic/CodeEditor/CodeEditor.test.tsx new file mode 100644 index 00000000..c1cd74c7 --- /dev/null +++ b/src/components/Atomic/CodeEditor/CodeEditor.test.tsx @@ -0,0 +1,23 @@ +import { render } from '@testing-library/react' +import CodeEditor from './CodeEditor' + +describe('', () => { + it('renders without crashing', () => { + const { container, asFragment } = render() + expect(container).toBeInTheDocument() + + expect(asFragment()).toMatchSnapshot() + }) + + it('displays placeholder text when value is empty', () => { + const { getByText, asFragment } = render() + expect(getByText('Placeholder')).toBeInTheDocument() + + expect(asFragment()).toMatchSnapshot() + }) + + it('does not display placeholder text when value is not empty', () => { + const { queryByText } = render() + expect(queryByText('Placeholder')).not.toBeInTheDocument() + }) +}) diff --git a/src/components/Atomic/CodeEditor/CodeEditor.tsx b/src/components/Atomic/CodeEditor/CodeEditor.tsx index e9f10223..dcb5ffc5 100644 --- a/src/components/Atomic/CodeEditor/CodeEditor.tsx +++ b/src/components/Atomic/CodeEditor/CodeEditor.tsx @@ -76,6 +76,7 @@ const CodeEditor: FC = (props) => { extensions={extensions} height={getSizeInPx(height!)} onChange={(v) => handleChange(v)} + readOnly={disabled === true} theme={theme} value={value} /> @@ -83,7 +84,7 @@ const CodeEditor: FC = (props) => {
-

{placeholderText}

+ {showPlaceholder &&

{placeholderText}

}
)} diff --git a/src/components/Atomic/CodeEditor/__snapshots__/CodeEditor.test.tsx.snap b/src/components/Atomic/CodeEditor/__snapshots__/CodeEditor.test.tsx.snap new file mode 100644 index 00000000..c9769bff --- /dev/null +++ b/src/components/Atomic/CodeEditor/__snapshots__/CodeEditor.test.tsx.snap @@ -0,0 +1,345 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` displays placeholder text when value is empty 1`] = ` + + .emotion-0 { + position: relative; +} + +.emotion-1 { + border: 1px solid; + border-radius: 8px; + overflow: hidden; + position: relative; + z-index: 2; +} + +.emotion-1 .cm-editor { + background: transparent; + -webkit-transition: all 0.3s; + transition: all 0.3s; +} + +.emotion-1 .cm-gutters { + border: 0; +} + +.emotion-1 .cm-lineNumbers .cm-gutterElement { + text-align: center; + padding: 0 16px 0 16px; +} + +.emotion-1 .cm-gutterElement { + color: !important; +} + +.emotion-1 .cm-content { + padding: 16px 0; +} + +.emotion-1 .cm-line { + padding: 0 16px; + line-height: 1.6; +} + +.emotion-2 { + position: absolute; + border-radius: 8px; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + z-index: 1; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; +} + +.emotion-3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-transition: all 0.3s; + transition: all 0.3s; + opacity: 1; +} + +.emotion-4 { + font-weight: 400; + font-size: 14px; + line-height: 22px; +} + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + + + + + + + + +

+ Placeholder +

+
+
+
+ +`; + +exports[` renders without crashing 1`] = ` + + .emotion-0 { + position: relative; +} + +.emotion-1 { + border: 1px solid; + border-radius: 8px; + overflow: hidden; + position: relative; + z-index: 2; +} + +.emotion-1 .cm-editor { + background: transparent; + -webkit-transition: all 0.3s; + transition: all 0.3s; +} + +.emotion-1 .cm-gutters { + border: 0; +} + +.emotion-1 .cm-lineNumbers .cm-gutterElement { + text-align: center; + padding: 0 16px 0 16px; +} + +.emotion-1 .cm-gutterElement { + color: !important; +} + +.emotion-1 .cm-content { + padding: 16px 0; +} + +.emotion-1 .cm-line { + padding: 0 16px; + line-height: 1.6; +} + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+ +`; diff --git a/src/stories/Alert.stories.jsx b/src/stories/Alert.stories.jsx index bea285f4..09db61f8 100644 --- a/src/stories/Alert.stories.jsx +++ b/src/stories/Alert.stories.jsx @@ -25,6 +25,8 @@ const Template = (args) => ( Alert text +
+
Before adding a remote client, verify their TLS certificate for security. To proceed, open the URL in your browser, verify and accept the diff --git a/src/stories/CaPool.stories.jsx b/src/stories/CaPool.stories.jsx deleted file mode 100644 index b4a95ce8..00000000 --- a/src/stories/CaPool.stories.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import CaPool from '../components/Organisms/CaPool' - -export default { - title: 'Example/CaPool', - component: CaPool, - argTypes: {}, -} - -const Template = (args) => ( -
- -
-) - -export const Default = Template.bind({}) -Default.args = {} diff --git a/src/stories/CaPool.stories.tsx b/src/stories/CaPool.stories.tsx new file mode 100644 index 00000000..ca671fe9 --- /dev/null +++ b/src/stories/CaPool.stories.tsx @@ -0,0 +1,63 @@ +import React, { useCallback, useState } from 'react' +import cloneDeep from 'lodash/cloneDeep' +import { StoryFn } from '@storybook/react' + +import CaPool from '../components/Organisms/CaPool' + +export default { + title: 'Example/CaPool', + component: CaPool, + argTypes: {}, +} + +const i18n = { + title: 'title', + download: 'download', + edit: 'edit', + delete: 'delete', + search: 'search', + view: 'view', + showMore: 'showMore', + update: 'update', +} + +const Template = (args: any) => { + const [data, setData] = useState([ + { id: 0, name: 'ca-item-0', data: {} }, + { id: 1, name: 'ca-item-1', data: {} }, + { id: 2, name: 'ca-item-2', data: {} }, + { id: 3, name: 'ca-item-3', data: {} }, + { id: 4, name: 'ca-item-4', data: {} }, + ]) + + const handleDeleteCaItem = useCallback( + (id: string) => { + const newData = cloneDeep(data) + newData.splice(parseInt(id, 10), 1) + setData(newData) + }, + [data] + ) + + return ( +
+ { + alert('handle add modal') + setData([...data, { id: data.length, name: `ca-item-${data.length}`, data: {} }]) + }} + onDelete={handleDeleteCaItem} + onDownload={() => alert('handle download')} + onEdit={() => alert('handle edit')} + onView={() => alert('handle view')} + /> +
+ ) +} + +export const Default: StoryFn = Template.bind({}) +Default.args = {} diff --git a/src/stories/CodeEditor.stories.jsx b/src/stories/CodeEditor.stories.jsx new file mode 100644 index 00000000..46df1ea3 --- /dev/null +++ b/src/stories/CodeEditor.stories.jsx @@ -0,0 +1,17 @@ +import React from 'react' +import CodeEditor from '../components/Atomic/CodeEditor' + +export default { + title: 'Example/CodeEditor', + component: CodeEditor, + argTypes: {}, +} + +const Template = (args) => ( +
+ +
+) + +export const Default = Template.bind({}) +Default.args = {} diff --git a/src/stories/header.css b/src/stories/header.css deleted file mode 100644 index d13ec050..00000000 --- a/src/stories/header.css +++ /dev/null @@ -1,32 +0,0 @@ -.wrapper { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -svg { - /*display: inline-block;*/ - /*vertical-align: top;*/ -} - -h1 { - font-weight: 900; - font-size: 20px; - line-height: 1; - margin: 6px 0 6px 10px; - display: inline-block; - vertical-align: top; -} - -button + button { - margin-left: 10px; -} - -.welcome { - color: #333; - font-size: 14px; - margin-right: 10px; -} diff --git a/test-report.xml b/test-report.xml index 6c0f571a..4d8d615d 100644 --- a/test-report.xml +++ b/test-report.xml @@ -1,113 +1,118 @@ - - - + + - - - - + - + - + - - - + + + + - - + + - - + + - + - - + + - - + + - - - - + + + + - - + + - - - - + + - - + + - - + + + + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - + + + - - + + - - + + - - + + - - - - + - + - - + + - - + + + + + + + + + + + + - - + + \ No newline at end of file