First source commit

This commit is contained in:
2024-09-18 17:55:49 +05:30
parent bc29f2b730
commit ba9f3d8602
27 changed files with 1924 additions and 284 deletions

4
.gitignore vendored
View File

@@ -28,4 +28,6 @@ build
# Miscellaneous
*.local
.DS_Store
.DS_Store
analyticsrc.json

View File

@@ -1,57 +1,131 @@
{
"name": "@armco/react-vite-rtk-template",
"private": true,
"version": "0.0.0",
"name": "@armco/components-viewer",
"description": "Components Viewer/Tester for React Components Library",
"version": "0.0.47",
"type": "module",
"author": "Armco (@restruct-corporate-advantage)",
"scripts": {
"dev": "vite",
"start": "vite",
"start": "NODE_ENV=production vite",
"build": "tsc && vite build",
"build:publish": "./scripts/build.sh",
"build:publish:compile": "tsc --p ./tsconfig-build.json && vite build --config vite-publish.config.ts",
"generate": "plop",
"atom": "plop atom",
"molecule": "plop molecule",
"component": "plop component",
"page": "plop page",
"preview": "vite preview",
"test": "vitest",
"test": "NODE_ENV=development vitest",
"format": "prettier --write .",
"lint": "eslint .",
"type-check": "tsc"
"type-check": "tsc",
"publish:dry": "npm publish --dry-run",
"publish:local": "./scripts/publish-local.sh",
"publish:public": "./scripts/publish.sh",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@armco/analytics": "^0.2.8",
"@lottiefiles/react-lottie-player": "^3.5.3",
"@popperjs/core": "^2.11.8",
"@reduxjs/toolkit": "^1.8.1",
"react": "^18.2.0",
"@storybook/cli": "^7.0.23",
"bootstrap": "^5.3.0",
"classnames": "^2.3.2",
"d3": "^7.9.0",
"highcharts": "^11.2.0",
"highcharts-react-official": "^3.2.1",
"highlight.js": "^11.8.0",
"js-cookie": "^3.0.5",
"lottie-react": "^2.4.0",
"lottie-web": "^5.12.2",
"moment": "^2.29.4",
"react-app-polyfill": "^3.0.0",
"react-bootstrap": "^2.7.4",
"react-dev-utils": "^12.0.1",
"react-dom": "^18.2.0",
"react-dnd": ">=16.0.0",
"react-dnd-html5-backend": ">=16.0.0",
"react-dnd-touch-backend": ">=16.0.0",
"react-draggable": "^4.4.6",
"react-redux": "^8.0.1",
"react-router-dom": "^6.13.0"
"react-resizable": "^3.0.5",
"react-router-dom": "^6.13.0",
"react-table": "^7.8.0",
"resize-observer-polyfill": "^1.5.1",
"svgpath": "^2.6.0",
"uuid": "^9.0.0",
"vite-plugin-svgr": "^3.2.0"
},
"devDependencies": {
"@armco/types": "^0.0.6",
"@babel/preset-env": "^7.24.5",
"@babel/preset-react": "^7.24.1",
"@babel/preset-typescript": "^7.24.1",
"@storybook/addon-essentials": "^7.0.23",
"@storybook/addon-interactions": "^7.0.23",
"@storybook/addon-links": "^7.0.23",
"@storybook/blocks": "^7.0.23",
"@storybook/react": "^7.0.23",
"@storybook/react-vite": "^7.0.23",
"@storybook/testing-library": "^0.0.14-next.2",
"@testing-library/dom": "^9.2.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.2.5",
"@types/bootstrap": "^5.2.6",
"@types/d3": "^7.4.0",
"@types/js-cookie": "^3.0.3",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-dom": "^18.2.18",
"@types/react-resizable": "^3.0.7",
"@types/react-table": "^7.7.19",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/uuid": "^9.0.2",
"@vitejs/plugin-react": "^4.0.0",
"chalk": "^5.3.0",
"cherry-pick": "^0.5.0",
"cpy-cli": "^5.0.0",
"eslint": "^8.0.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-storybook": "^0.6.12",
"execa": "^8.0.1",
"fs-extra": "^11.2.0",
"glob": "^10.3.10",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^21.1.0",
"plop": "^3.1.2",
"prettier": "^2.7.1",
"prettier-config-nick": "^1.0.2",
"prop-types": "^15.8.1",
"react": ">=16.8.0",
"react-dom": "^18.2.0",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.63.4",
"storybook": "^7.0.23",
"ts-jest": "^29.2.3",
"ts-node": "^10.9.2",
"typescript": "^5.0.2",
"vite": "^4.0.0",
"vite-plugin-dts": "^3.7.1",
"vitest": "^0.30.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dnd": ">=16.0.0",
"react-dnd-html5-backend": ">=16.0.0",
"react-dnd-touch-backend": ">=16.0.0",
"react-redux": "^8.0.1",
"react-router-dom": "^6.13.0"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
"react-app/jest",
"plugin:storybook/recommended"
],
"plugins": [
"prettier"
@@ -62,20 +136,29 @@
}
},
"prettier": "prettier-config-nick",
"main": "index.tsx",
"types": "./build/index.d.ts",
"main": "./build/index.js",
"module": "./build/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/ReStruct-Corporate-Advantage/.git"
"url": "git+https://github.com/ReStruct-Corporate-Advantage/components-viewer.git"
},
"keywords": [
"react",
"vite",
"reduxToolkit",
"sass",
"components",
"atomic",
"building-blocks",
"foundation"
],
"files": [
"build"
],
"license": "ISC",
"bugs": {
"url": "https://github.com/ReStruct-Corporate-Advantage/react-vite-rtk-template/issues"
"url": "https://github.com/ReStruct-Corporate-Advantage/components-viewer/issues"
},
"homepage": "https://github.com/ReStruct-Corporate-Advantage/react-vite-rtk-template#readme"
"homepage": "https://github.com/ReStruct-Corporate-Advantage/components-viewer#readme"
}

60
src/ComponentList.tsx Executable file
View File

@@ -0,0 +1,60 @@
import { useNavigate } from "react-router-dom"
import { ArThemes, ComponentRepository, TreeListData } from "@armco/types"
import {
Adapter,
COMPONENTS,
ErrorBoundary,
getCurrentTheme,
Network,
TreeList,
useAppSelector,
} from "."
import * as atoms from "../atoms"
import * as molecules from "../molecules"
import "./ComponentList.component.scss"
const Components: ComponentRepository = { ...atoms, ...molecules }
interface ComponentListProps {}
const formattedTreeData = Adapter.adaptToTreeFromComponentConfig(COMPONENTS)
const ComponentList = (props: ComponentListProps): JSX.Element => {
const navigate = useNavigate()
const theme = useAppSelector<ArThemes>(getCurrentTheme)
const handleComponentSelect = (treeNode: TreeListData) => {
const params = treeNode.data?.props
treeNode.data?.component &&
navigate(
Network.stringifyUrl(`/components/${treeNode.data?.component}`, params),
)
}
return (
<div className="ar-ComponentList h-100 w-100 px-2">
<TreeList
onItemSelect={handleComponentSelect}
data={formattedTreeData}
title="Component List"
firstExpanded={true}
theme={theme}
customTooltip={(item: TreeListData) => {
const Component =
typeof item.label === "string" && Components[item.label]
return Component ? (
<ErrorBoundary>
<Component demo />
</ErrorBoundary>
) : (
item.label
)
}}
isDraggable
showTooltip
/>
</div>
)
}
export default ComponentList

41
src/Editor.component.scss Executable file
View File

@@ -0,0 +1,41 @@
.ar-Editor {
border: 1px solid var(--ar-color-layout-border);
.ar-Editor__tools {
background-color: var(--ar-bg-base);
border-bottom: 1px solid var(--ar-color-layout-border);
}
.ar-Editor__prop-selector {
min-height: 2.5rem;
&::-webkit-scrollbar {
height: 2px;
background-color: #fefefe;
}
/* Track */
&::-webkit-scrollbar-track {
background: #cdcdcd;
width: 10px;
height: 10px;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: #ababab;
border-radius: 2px;
width: 3rem;
height: 10px;
&:hover {
background: #888;
}
}
}
}
#root.ar-Editor__frame__main {
background-color: var(--ar-bg-tertiary);
&.background {
background-repeat: no-repeat;
background-size: 100%;
}
}

359
src/Editor.tsx Executable file
View File

@@ -0,0 +1,359 @@
import { ReactNode, useEffect, useState } from "react"
import { useLocation, useNavigate } from "react-router-dom"
import { createPortal } from "react-dom"
import {
ArDropdownVariants,
ArSizes,
ArThemes,
ComponentDescription,
FrameContentDefinition,
FrameContentProps,
PRIMITIVES,
} from "@armco/types"
import {
Alert,
Button,
COMPONENTS,
Toggle,
Helper,
Network,
Dropdown,
} from "."
import StyleHelper from "./StyleHelper"
import * as images from "../../static/images"
import "./Editor.component.scss"
const children = {
bg: (
<div
key="dummy-bg"
className="ar-Editor__frame__dummy-child"
style={{ height: "100vh", width: "100vw" }}
/>
),
content: (
<span
key="dummy-content"
slot="content"
className="bg"
style={{ height: "10rem", width: "10rem" }}
>
Popover Content
</span>
),
popover: (
<div
key="dummy-popover"
slot="popover"
style={{ height: "10rem", width: "10rem" }}
>
Popover Content
</div>
),
list: (
<div
key="dummy-list"
className="d-grid"
style={{
gridTemplateColumns: "repeat(auto-fill, 5rem)",
gap: "1rem",
justifyContent: "space-around",
}}
>
{Array.from({ length: 40 }, (_, i) => (
<div
key={"dummy-list-item-" + i}
className="bg border border-radius-l2"
style={{ width: "5rem", height: "5rem" }}
/>
))}
</div>
),
anchor: <Button content="Click Me!" slot="anchor" />,
}
const FrameContent = (
props: FrameContentProps,
): JSX.Element | string | null => {
const { contentDefinition, notificationProps } = props
const { component, props: componentProps, data } = contentDefinition || {}
const background: string = data?.test?.background
const SelectedComponent = component
let childRenders: Array<JSX.Element> = []
if (data?.type === "HOC") {
childRenders = data.children.map(
(name: string) => children[name as keyof object],
)
}
return SelectedComponent ? (
<div
className={`ar-Editor__frame__main h-100 w-100 overflow-auto p-5 flex-column${
background ? " background" : ""
}`}
style={{
// @ts-ignore
backgroundImage: background ? `url(${images[background]})` : "",
}}
id="root"
>
{data && data.type === "HOC" ? (
<SelectedComponent {...componentProps}>
{childRenders}
</SelectedComponent>
) : (
<SelectedComponent {...componentProps} />
)}
{notificationProps && <Alert {...notificationProps} />}
</div>
) : (
"Select an item from the side panel to view here"
)
}
const Editor = (): JSX.Element | string => {
const [contentRef, setContentRef] = useState<HTMLIFrameElement | null>(null)
const [notificationProps, setNotificationProps] = useState<any>(null)
const [selectedComponentDefinition, setSelectedComponentDefinition] =
useState<FrameContentDefinition | null>(null)
// const [theme, setTheme] = useState<string>(ArThemes.LIGHT1)
const [theme, setTheme] = useState<string>(ArThemes.DARK1)
const [portal, setPortal] = useState<ReactNode>()
const [component, setComponent] = useState<string>()
const location = useLocation()
const navigate = useNavigate()
useEffect(() => {
window.onmessage = (e) => {
"ar" in e.data && setNotificationProps(e.data?.ar)
}
}, [])
useEffect(() => {
if (theme) {
const iframeHtml =
contentRef?.contentWindow?.document?.getElementsByTagName("html")[0]
iframeHtml && iframeHtml.setAttribute("ar-theme", theme)
const iframeBody = iframeHtml?.getElementsByTagName("body")[0]
iframeBody && (iframeBody.style.backgroundColor = "var(--ar-bg)")
}
}, [contentRef, theme])
useEffect(() => {
const locationPathArr = location.pathname
.split("/")
.filter((p: string) => p)
const componentName =
locationPathArr.length > 1 && locationPathArr[locationPathArr.length - 1]
if (componentName) {
setComponent(componentName)
const { selectedItem, hierarchy } = Helper.findComponentDescription(
componentName,
COMPONENTS,
)
if (selectedItem && hierarchy) {
let props: { [key: string]: string | number | boolean | undefined } = {
demo: true,
}
if (location.search) {
const params = location.search.substring(1)?.split("&")
if (params) {
params.forEach((prop) => {
const keyValue = prop.split("=")
const name = keyValue && keyValue[0]
let value: string | boolean = keyValue && keyValue[1]
value = value === "true" ? true : decodeURIComponent(value)
value =
value === "false" ? false : decodeURIComponent(value as string)
if (prop && value && selectedItem) {
props[name] = value
}
})
}
}
if (
!selectedComponentDefinition ||
selectedComponentDefinition.componentName !== componentName ||
JSON.stringify(selectedComponentDefinition.props) !==
JSON.stringify(props)
) {
const SelectedComponent = Helper.importComponent(
componentName,
hierarchy?.toLowerCase(),
)
setSelectedComponentDefinition({
componentName,
component: SelectedComponent,
props,
data: selectedItem,
hierarchy,
})
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location])
useEffect(() => {
if (
contentRef &&
selectedComponentDefinition &&
process.env.NODE_ENV !== "production"
) {
StyleHelper.injectComponentStyleInFrame(
selectedComponentDefinition.data as ComponentDescription,
selectedComponentDefinition.hierarchy,
contentRef,
)
}
}, [contentRef, selectedComponentDefinition])
useEffect(() => {
if (selectedComponentDefinition) {
const { demo, theme, ...selectedProps } =
selectedComponentDefinition.props || {}
const newUrl = Network.stringifyUrl("/", selectedProps).slice(1)
selectedProps && newUrl !== location.search && navigate(newUrl)
}
}, [selectedComponentDefinition])
useEffect(() => {
if (contentRef) {
const bsLink =
contentRef?.contentWindow?.document.getElementsByTagName("link")
if (!bsLink || bsLink.length === 0) {
if (process.env.NODE_ENV === "production") {
StyleHelper.injectLinkInFrame(
["index", "ComponentsViewerPage"],
contentRef,
)
} else {
StyleHelper.injectBootstrapInFrame(contentRef)
StyleHelper.injectVariablesInFrame(contentRef)
StyleHelper.injectAnimationInFrame(contentRef)
StyleHelper.injectGlobalInFrame(contentRef)
StyleHelper.injectEditorStyleInFrame(contentRef)
StyleHelper.injectComponentStyleInFrame(
{ name: "Alert" },
"ATOMS",
contentRef,
)
}
}
}
}, [contentRef])
const mountNode = contentRef?.contentWindow?.document?.body
useEffect(() => {
if (mountNode) {
if (selectedComponentDefinition && selectedComponentDefinition.props) {
selectedComponentDefinition.props.theme = theme
}
setPortal(
createPortal(
<FrameContent
contentDefinition={selectedComponentDefinition}
notificationProps={notificationProps}
/>,
mountNode,
),
)
}
}, [selectedComponentDefinition, mountNode, notificationProps, theme])
const currentItemVariants = selectedComponentDefinition?.data.variants || []
return (
<div className="ar-Editor w-100 h-100 d-flex flex-column">
<div className="ar-Editor__tools">
<div className="row justify-content-end">
<div className="col flex-v-center justify-content-end">
<div className="ar-Editor__prop-selector flex-v-center border-right me-3 overflow-auto py-2 px-3">
{Object.keys(currentItemVariants)
.filter((prop: string) => prop !== "demo")
.map((prop: any, index: number, arr) => {
const propsValues = currentItemVariants[prop]
const isBoolean =
propsValues.length === 2 &&
propsValues.indexOf(true) > -1 &&
propsValues.indexOf(false) > -1
return isBoolean ? (
<Toggle
key={"prop-toggle-" + index + "-" + prop}
classes="me-3"
label={Helper.toReadable(prop)}
onChange={(isChecked: boolean) =>
selectedComponentDefinition?.componentName &&
setSelectedComponentDefinition({
...selectedComponentDefinition,
props: {
...(selectedComponentDefinition.props || {}),
[prop]: isChecked,
},
})
}
isOn={Boolean(
selectedComponentDefinition?.props &&
selectedComponentDefinition?.props[prop],
)}
size={ArSizes.SMALL}
hideStatus
/>
) : (
<Dropdown
key={"prop-dropdown-" + index + "-" + prop}
classes="me-2"
contentMatchAnchorWidth={false}
options={propsValues.map((propValue: PRIMITIVES) => ({
name: propValue,
isSelected:
selectedComponentDefinition?.props &&
propValue + "" ===
selectedComponentDefinition?.props[prop],
}))}
onSelectionChanged={(obj) => {
selectedComponentDefinition?.componentName &&
setSelectedComponentDefinition({
...selectedComponentDefinition,
props: {
...(selectedComponentDefinition.props || {}),
[prop]: obj.value,
},
})
}}
variant={ArDropdownVariants.SELECTIONSASPILLS}
placeholder={Helper.toReadable(prop)}
version="v2"
/>
)
})}
</div>
<span className="float-end pe-3">
<Toggle
isOn={theme === ArThemes.DARK1}
toggleOnName="Dark"
toggleOffName="Light"
onChange={(checked: boolean) =>
setTheme(checked ? ArThemes.DARK1 : ArThemes.LIGHT1)
}
value={true}
/>
</span>
</div>
</div>
</div>
{/* <FrameContent
contentDefinition={selectedComponentDefinition}
notificationProps={notificationProps}
/> */}
<iframe
className="ar-Editor__frame w-100 h-100"
ref={setContentRef}
title="Armco Component Viewer"
>
{portal}
</iframe>
</div>
)
}
export default Editor

140
src/StyleHelper.ts Normal file
View File

@@ -0,0 +1,140 @@
import { ComponentDescription } from "@armco/types"
//// Editor Frame Style related helpers
class StyleHelper {
static injectGlobalInFrame(frame: HTMLIFrameElement | null) {
const clipCallback = (text: string) => {
return {
clipStart: text && text.indexOf("html {\\n font-size: 16px"),
}
}
StyleHelper.injectScssInFrame(
"/src/app/static/styles/_global.scss",
frame,
undefined,
clipCallback,
)
}
static injectBootstrapInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectLinkInFrame(
"/src/app/static/styles/bootstrap.min.css",
frame,
)
}
static injectVariablesInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectScssInFrame(
"/src/app/static/styles/_variables.scss",
frame,
)
}
static injectAnimationInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectScssInFrame(
"/src/app/static/styles/_animations.scss",
frame,
)
}
static injectEditorStyleInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectScssInFrame(
"/src/app/components/Editor/Editor.component.scss",
frame,
)
}
static injectComponentStyleInFrame(
selectedComponent: ComponentDescription,
hierarchy: string,
frame: HTMLIFrameElement | null,
) {
if (hierarchy && selectedComponent && selectedComponent.name) {
const componentStyleElement =
frame?.contentWindow?.document.querySelector("style.component-style")
componentStyleElement && componentStyleElement.remove()
const dependencyComponentsToInject = selectedComponent.uses
? [
{ name: selectedComponent.name, hierarchy },
...selectedComponent.uses,
]
: [{ name: selectedComponent.name, hierarchy }]
dependencyComponentsToInject.forEach(
(component: { [key: string]: string }) => {
const scssPath = `/src/app/components/${component.hierarchy.toLowerCase()}/${
component.name
}/${component.name}.component.scss`
StyleHelper.injectScssInFrame(scssPath, frame, component)
},
)
}
}
static injectScssInFrame(
scssPath: string,
frame: HTMLIFrameElement | null,
component?: { [key: string]: string },
clipCallback?: any,
) {
import("sass").then((module) => {
const { compileString } = module
fetch(scssPath)
.then((r) => r.text())
.then((text) => {
const { clipStart, clipEnd } = clipCallback
? clipCallback(text)
: () => ({})
text = text
.substring(
clipStart ||
text.indexOf("__vite__css = ") + '__vite__css = "'.length,
clipEnd || text.indexOf("__vite__updateStyle(") - 2,
)
.replace(/\\n/g, "")
.replace(/\\"/g, '"')
const css = compileString(text).css
StyleHelper.injectStyleInFrame(css, frame, component)
})
})
}
static injectStyleInFrame(
css: string,
frame: HTMLIFrameElement | null,
component?: { [key: string]: string },
) {
const style = document.createElement("style")
if (component && component.name) {
style.setAttribute("id", component.name)
style.setAttribute("class", "component-style")
}
style.appendChild(document.createTextNode(css))
frame?.contentWindow?.document?.head.appendChild(style)
}
static injectLinkInFrame(
link: string | Array<string>,
frame: HTMLIFrameElement | null,
) {
if (Array.isArray(link)) {
const links = document.querySelectorAll("link[rel='stylesheet']")
link.forEach((item: string) => {
Array.from(links).forEach((selectedLink) => {
const cssUrl = selectedLink.getAttribute("href")
if (cssUrl && cssUrl.startsWith(`/assets/${item}`)) {
const clone = selectedLink.cloneNode(true)
frame?.contentWindow?.document?.head.appendChild(clone)
}
})
})
} else {
const cssLink = document.createElement("link")
cssLink.href = link
cssLink.rel = "stylesheet"
cssLink.type = "text/css"
frame?.contentWindow?.document?.head.appendChild(cssLink)
}
}
}
export default StyleHelper

View File

@@ -1,3 +0,0 @@
.c-Home {
}

View File

@@ -1,18 +0,0 @@
import { createSlice } from "@reduxjs/toolkit"
export interface HomeState {}
const initialState: HomeState = {}
export const homeSlice = createSlice({
name: "home",
initialState,
reducers: {
increment: (state) => {},
},
extraReducers: (builder) => {},
})
export const { increment } = homeSlice.actions
export default homeSlice.reducer

View File

@@ -1,8 +0,0 @@
import React from "react"
import Home from "./Home"
describe("Home", () => {
it("renders without error", () => {
})
})

View File

@@ -1,14 +0,0 @@
import React from "react"
import "./Home.module.scss"
interface HomeProps {}
const Home = props => {
return (
<div className="c-Home">
In Page Home
</div>
)
}
export default Home

View File

@@ -1,3 +0,0 @@
import Home from "./Home.jsx"
export default Home

View File

@@ -1,7 +0,0 @@
/* PLOP_INJECT_IMPORT */
import Home from "./Home"
export {
/* PLOP_INJECT_EXPORT */
Home,
}

View File

@@ -1,6 +0,0 @@
interface RouteConfig {
path: String
class?: String
element: String | JSX.Element | null
children?: Array<RouteConfig>
}

View File

@@ -1,15 +0,0 @@
class Helper {
static populateComponentsInRoutes(routes: RouteConfig[], components: any) {
routes &&
routes.forEach((route) => {
const Component: JSX.ElementType =
components[route.element as keyof object]
route.element = <Component />
if (route.children) {
Helper.populateComponentsInRoutes(route.children, components)
}
})
}
}
export default Helper

1170
src/components.ts Normal file

File diff suppressed because it is too large Load Diff

50
src/dummies.tsx Normal file
View File

@@ -0,0 +1,50 @@
import React from "react"
import { v4 as uuid } from "uuid"
export const DUMMIES = {
Accordion: [
{
id: uuid(),
title: "Slide In",
content:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut laoreet tellus ante, et gravida massa egestas ac. Maecenas venenatis dui a eros maximus, vitae fringilla metus ultrices. Etiam at lacinia magna. Vestibulum sagittis felis sed diam ullamcorper, vel pharetra quam facilisis. Suspendisse bibendum ante id risus interdum, vel sodales felis egestas. Mauris vitae dui gravida, dictum sem id, accumsan tellus. Aliquam sodales quam efficitur, congue justo a, consectetur ante. Nunc euismod augue ac ante condimentum tincidunt. Quisque bibendum semper velit. Cras sit amet imperdiet dolor, ac aliquet mi. Morbi elementum magna eros, in venenatis nunc laoreet eget. Etiam at lorem suscipit, interdum augue id, scelerisque risus. Suspendisse at nisi lorem. Morbi libero erat, vestibulum at pulvinar at, ultrices ac turpis. Etiam ac leo gravida, semper quam in, tempus ipsum. Phasellus finibus rhoncus cursus. Maecenas dapibus ex ut congue eleifend. Sed luctus consectetur quam, at malesuada enim condimentum non.",
},
{
id: uuid(),
title: "Slide Out",
content: (
<input
type="text"
className="p-3 m-3"
title="dummy"
placeholder="Accordion content..."
/>
),
},
{
id: uuid(),
title: "Fade",
content:
"Donec varius lorem vel orci aliquam, nec venenatis turpis commodo. Ut ac diam nisi. Integer at lorem ac ligula vehicula porta. Proin tempor lorem a nunc auctor, rhoncus porta elit auctor. Duis imperdiet dictum luctus. Sed eget orci mattis, sagittis quam ac, congue arcu. Praesent blandit mattis sem, eget rutrum neque auctor id. Vestibulum eu augue ut nisl tempus iaculis. Nullam id tortor sed ante tincidunt consectetur et eu odio. Aliquam accumsan turpis ac dictum interdum.",
},
{
id: uuid(),
title: "Rotate",
content:
"Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec metus augue, tempus vitae neque in, accumsan faucibus velit. Quisque felis lorem, tristique nec finibus consequat, viverra quis nibh. Donec arcu mi, placerat eget pretium non, molestie vitae leo. Nunc cursus id mi quis malesuada.",
},
{
id: uuid(),
title: "Scale",
content:
"Integer dapibus aliquet bibendum. Quisque id lobortis dolor, in bibendum enim. Cras mollis iaculis metus et tincidunt. Maecenas fermentum porttitor faucibus. Suspendisse feugiat ac enim nec vulputate. Aliquam egestas ante ac tempor blandit. Etiam vehicula, dui sed facilisis rhoncus, quam tortor efficitur sapien, eu imperdiet nisi magna et dui. Nulla a justo odio.",
},
],
ArViz: [
{ source: "Item 1", val: 1350, color: "#C9D6DF" },
{ source: "Item 2", val: 2500, color: "#F7EECF" },
{ source: "Item 3", val: 5700, color: "#E3E1B2" },
{ source: "Item 4", val: 30000, color: "#F9CAC8" },
{ source: "Item 5", val: 47500, color: "#D1C2E0" },
],
}

View File

@@ -1,67 +0,0 @@
import { useState } from "react"
import { useAppSelector, useAppDispatch } from "../../app/hooks"
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
selectCount,
} from "./counterSlice"
export function Counter() {
const count = useAppSelector(selectCount)
const dispatch = useAppDispatch()
const [incrementAmount, setIncrementAmount] = useState("2")
const incrementValue = Number(incrementAmount) || 0
return (
<div>
<div className="row">
<button
className="button"
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span className="value">{count}</span>
<button
className="button"
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
<div className="row">
<input
className="textbox"
aria-label="Set increment amount"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
className="button"
onClick={() => dispatch(incrementByAmount(incrementValue))}
>
Add Amount
</button>
<button
className="asyncButton"
onClick={() => dispatch(incrementAsync(incrementValue))}
>
Add Async
</button>
<button
className="button"
onClick={() => dispatch(incrementIfOdd(incrementValue))}
>
Add If Odd
</button>
</div>
</div>
)
}

View File

@@ -1,6 +0,0 @@
// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
return new Promise<{ data: number }>((resolve) =>
setTimeout(() => resolve({ data: amount }), 500),
)
}

View File

@@ -1,34 +0,0 @@
import counterReducer, {
CounterState,
increment,
decrement,
incrementByAmount,
} from "./counterSlice"
describe("counter reducer", () => {
const initialState: CounterState = {
value: 3,
status: "idle",
}
it("should handle initial state", () => {
expect(counterReducer(undefined, { type: "unknown" })).toEqual({
value: 0,
status: "idle",
})
})
it("should handle increment", () => {
const actual = counterReducer(initialState, increment())
expect(actual.value).toEqual(4)
})
it("should handle decrement", () => {
const actual = counterReducer(initialState, decrement())
expect(actual.value).toEqual(2)
})
it("should handle incrementByAmount", () => {
const actual = counterReducer(initialState, incrementByAmount(2))
expect(actual.value).toEqual(5)
})
})

View File

@@ -1,84 +0,0 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"
import { RootState, AppThunk } from "../../app/store"
import { fetchCount } from "./counterAPI"
export interface CounterState {
value: number
status: "idle" | "loading" | "failed"
}
const initialState: CounterState = {
value: 0,
status: "idle",
}
// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
"counter/fetchCount",
async (amount: number) => {
const response = await fetchCount(amount)
// The value we return becomes the `fulfilled` action payload
return response.data
},
)
export const counterSlice = createSlice({
name: "counter",
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn"t actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state) => {
state.status = "loading"
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = "idle"
state.value += action.payload
})
.addCase(incrementAsync.rejected, (state) => {
state.status = "failed"
})
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they"re used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state: RootState) => state.counter.value
// We can also write thunks by hand, which may contain both sync and async logic.
// Here"s an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState())
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount))
}
}
export default counterSlice.reducer

View File

@@ -2,9 +2,9 @@ import React from "react"
import ReactDOM from "react-dom/client"
import { BrowserRouter } from "react-router-dom"
import { Provider } from "react-redux"
import { store } from "./app/store"
import Router from "./app/Router"
import "./app/static/styles/global.scss"
import { store } from "./store"
import Router from "./Router"
import "./static/styles/global.scss"
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)