Refactor imports, update dependencies, add Jenkinsfile
Some checks failed
armco-org/components-viewer/pipeline/head There was a failure building this commit

This commit is contained in:
2026-01-03 20:44:24 +05:30
commit ff8a8956bd
26 changed files with 10680 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Dependencies
node_modules
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Swap the comments on the following lines if you don't wish to use zero-installs
# Documentation here: https://yarnpkg.com/features/zero-installs
!.yarn/cache
#.pnp.*
# Testing
coverage
# Production
build
# Miscellaneous
*.local
.DS_Store
analyticsrc.json

3
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,3 @@
@Library('jenkins-shared@main') _
kanikoPipeline(repoName: 'components-viewer', branch: env.BRANCH_NAME ?: 'main')

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Armco Template for the tech stack: React, TS, Dart Sass, Redux Tookkit, react-redux, react browser routing, TS based plop generator

36
build-tools/build.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Get the directory of the current script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Default values
DEV_FLAG=""
# Parse arguments
for arg in "$@"
do
case $arg in
--dev)
DEV_FLAG="--dev"
shift # Remove --dev from processing
;;
esac
done
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
echo "[BUILD:SH] Removing build if exists"
rm -rf build
echo "[BUILD:SH] Checking TS Types"
npx tsc
echo "[BUILD:SH] Initiating build..."
# Conditionally use vite-dev.config.ts if --dev flag is present
if [ "$DEV_FLAG" == "--dev" ]; then
vite build --config vite-dev.config.ts
else
vite build
fi
echo "[BUILD:SH] Running post processor scripts..."
# Run Post processors: Update style imports in .js files, create component modules
node "$SCRIPT_DIR/post-processor.js" build/cjs $DEV_FLAG
node "$SCRIPT_DIR/post-processor.js" build/es $DEV_FLAG

View File

@@ -0,0 +1,35 @@
import { promises as fs } from "fs"
import { dirname, resolve } from "path"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
async function generateModule(fileName, isDev) {
if (
fileName.endsWith(".js") &&
fileName.indexOf("-chunk") === -1 &&
fileName !== "index.js"
) {
const dir = fileName.slice(0, -3)
const name = `@armco/layout/${dir}`
const packageJsonContent = {
name,
main: `../${isDev ? "build/" : ""}cjs/${dir}.js`,
module: `../${isDev ? "build/" : ""}es/${dir}.js`,
types: `../${isDev ? "build/" : ""}types/${dir}.d.ts`,
}
const dirPath = resolve(__dirname, `../${isDev ? "" : "build/"}${dir}`)
try {
await fs.mkdir(dirPath, { recursive: true })
await fs.writeFile(
resolve(dirPath, "package.json"),
JSON.stringify(packageJsonContent, null, 2),
)
} catch (error) {
console.error(`Error processing directory ${dirPath}:`, error)
}
}
}
export default generateModule

View File

@@ -0,0 +1,24 @@
import { readdir } from "fs/promises"
import generateModule from "./generate-module.js"
async function postProcessor(dir, isDev) {
try {
const files = await readdir(dir)
await Promise.all(
files.map(async (file) => {
await generateModule(file, isDev)
}),
)
} catch (error) {
console.error(`Error processing directory ${dir}:`, error)
}
}
const targetDir = process.argv[2]
if (targetDir) {
postProcessor(targetDir, process.argv.includes("--dev"))
} else {
console.error("Please provide the build directory to run post processor on.")
process.exit(1)
}

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Redux App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

8185
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

137
package.json Normal file
View File

@@ -0,0 +1,137 @@
{
"name": "@armco/components-viewer",
"description": "Components Viewer/Tester for React Components Library",
"version": "0.0.49",
"type": "module",
"author": "Armco (@restruct-corporate-advantage)",
"types": "./build/types/ComponentsViewer.d.ts",
"main": "./build/cjs/ComponentsViewer.js",
"module": "./build/es/ComponentsViewer.js",
"scripts": {
"dev": "vite --config vite-dev.config.ts",
"start": "NODE_ENV=production vite",
"build": "rm -rf 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": "NODE_ENV=development vitest",
"format": "prettier --write .",
"lint": "eslint .",
"type-check": "tsc",
"publish:dry": "npm publish --dry-run",
"publish:local": "./scripts/publish-local.sh",
"publish:public": "./scripts/publish.sh",
"publish:sh": "./publish.sh"
},
"dependencies": {
"@lottiefiles/react-lottie-player": "^3.5.3",
"@tanstack/react-table": "^8.21.2",
"d3": "^7.9.0",
"highcharts": "^11.2.0",
"highcharts-react-official": "^3.2.1",
"highlight.js": "^11.8.0",
"lottie-react": "^2.4.0",
"lottie-web": "^5.12.2",
"moment": "^2.29.4"
},
"peerDependencies": {
"@armco/components": "^0.0.60",
"@armco/configs": "^0.0.11",
"@armco/types": "^0.0.18",
"@armco/utils": "^0.0.29",
"@reduxjs/toolkit": "^1.8.1",
"react": "^18.2.0",
"react-app-polyfill": "^3.0.0",
"react-bootstrap": "^2.7.4",
"react-dev-utils": "^12.0.1",
"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-resizable": "^3.0.5",
"react-router-dom": "^6.13.0",
"resize-observer-polyfill": "^1.5.1"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest",
"plugin:storybook/recommended"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "error",
"react/jsx-no-target-blank": "off"
}
},
"prettier": "prettier-config-nick",
"repository": {
"type": "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/components-viewer/issues"
},
"homepage": "https://github.com/ReStruct-Corporate-Advantage/components-viewer#readme",
"devDependencies": {
"@armco/avatar": "^0.0.6",
"@armco/calendar": "^0.0.8",
"@armco/components": "^0.0.60",
"@armco/configs": "^0.0.15",
"@armco/imageeditor": "^0.0.8",
"@armco/sam-editor": "^0.0.5",
"@armco/shared-components": "^0.0.61",
"@armco/types": "^0.0.18",
"@armco/uploader": "^0.0.6",
"@armco/utils": "^0.0.31",
"@reduxjs/toolkit": "^2.11.2",
"@types/node": "^24.0.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^5.1.0",
"ajv": "^8.12.0",
"glob": "^11.0.3",
"react": "^19.2.3",
"react-bootstrap": "^2.10.10",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "^19.2.3",
"react-draggable": "^4.5.0",
"react-redux": "^9.2.0",
"react-resizable": "^3.1.3",
"react-router-dom": "^7.11.0",
"resize-observer-polyfill": "^1.5.1",
"sass-embedded": "^1.89.2",
"typescript": "^5.9.3",
"uuid": "^13.0.0",
"vite": "^7.3.0",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-externalize-deps": "^0.10.0",
"vite-plugin-lib-inject-css": "^2.2.2",
"vitest": "^4.0.8"
}
}

34
publish.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/sh
semver=${1:-patch}
set -e
npm --no-git-tag-version version ${semver}
npm run build
cp package.json build/
# Use Node.js for portable package.json normalization
PKG_PATH="$(pwd)/build/package.json" node - <<'EOF'
const fs = require('fs');
const path = process.env.PKG_PATH;
const pkg = JSON.parse(fs.readFileSync(path, 'utf8'));
pkg.private = false;
delete pkg.scripts;
delete pkg.devDependencies;
if (!pkg.files) pkg.files = ['*'];
else pkg.files = pkg.files.map(x => x === 'build' ? '*' : x);
['main','module','types'].forEach(k => {
if (pkg[k]) {
pkg[k] = pkg[k]
.replace(/^\.?\/build\//, '')
.replace(/^build\//, '');
}
});
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
EOF
cd build
npm publish --access public --loglevel verbose

132
src/ComponentList.tsx Executable file
View File

@@ -0,0 +1,132 @@
import { ReactNode, useEffect, useState } from "react"
import { useNavigate } from "react-router-dom"
import { TreeListData } from "./types"
import { stringifyUrl } from "@armco/utils/network"
import { useTheme } from "@armco/utils/hooks"
import { ErrorBoundary, TreeList, TextInput } from "@armco/shared-components"
import * as components from "@armco/shared-components"
import COMPONENTS from "./components"
import { adaptToTreeFromComponentConfig, loadScript } from "./helper"
const Components: any = { ...components }
const LIBS: { [key: string]: () => Promise<any> } = {
"@armco/components": () => import("@armco/components"),
antd: () =>
loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/antd/5.21.4/antd.min.js",
"andtd",
),
material: () =>
loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/material-ui/4.12.4/index.js",
"MaterialUI",
),
"ng-bootstrap": () =>
loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/ng-bootstrap/10.0.0/ng-bootstrap.umd.min.js",
"",
),
"react-bootstrap": () =>
loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/react-bootstrap/2.10.5/react-bootstrap.min.js",
"",
),
}
interface ComponentListProps {
acceptLibInput?: boolean
}
let formattedTreeData: Array<TreeListData> =
adaptToTreeFromComponentConfig(COMPONENTS)
const ComponentList = (props: ComponentListProps): ReactNode => {
const { acceptLibInput } = props
const navigate = useNavigate()
const { theme } = useTheme()
const [components, setComponents] = useState<Array<string>>()
// TODO: Test code
useEffect(() => loadComponents("@armco/components"), [])
const handleComponentSelect = (treeNode: TreeListData) => {
const params = treeNode.data?.props
treeNode.data?.component &&
navigate(stringifyUrl(`/components/${treeNode.data?.component}`, params))
}
const loadComponents = (lib: string) => {
if (LIBS[lib])
LIBS[lib]()?.then((module) => {
const importedComponents = Object.keys(module)
importedComponents.sort()
formattedTreeData = adaptToTreeFromComponentConfig(
COMPONENTS,
importedComponents,
)
setComponents(importedComponents)
})
}
return (
<div className="ar-ComponentList h-100 w-100 px-2">
{!components && acceptLibInput ? (
<span className="h-100 flex-center flex-column">
<div className="flex-center flex-column">
<div className="mb-4 fs-6 lh-base">
Provide a library of which you want to view the components.
</div>
<div className="mb-4">
If you've installed your library and can see it in node_modules,
just provide the name of the library or provide URL of library if
it's hosted on a CDN.
</div>
</div>
<div>
<form
onSubmit={(e) => {
e.preventDefault()
const lib = (
(e.target as HTMLFormElement).elements[
"ar-component-id" as any
] as HTMLInputElement
).value
loadComponents(lib)
}}
>
<TextInput
id="ar-component-id"
placeholder="Load a library..."
action={loadComponents}
/>
</form>
</div>
</span>
) : (
<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

37
src/ComponentsViewer.tsx Normal file
View File

@@ -0,0 +1,37 @@
import { ReactNode } from "react"
import { DndProvider } from "react-dnd"
import { HTML5Backend } from "react-dnd-html5-backend"
import { TouchBackend } from "react-dnd-touch-backend"
import { ErrorBoundary, Main, Modal } from "@armco/shared-components"
import { isMobile as checkMobile } from "@armco/utils/helper"
import { useModalState, usePanelContent } from "@armco/utils/hooks"
import ComponentList from "./ComponentList"
import Editor from "./Editor"
const isMobile = checkMobile()
const ComponentsViewer = (): ReactNode => {
const { panelContent: rightPanelContent } = usePanelContent(false)
const { modalState } = useModalState()
const backend = isMobile ? TouchBackend : HTML5Backend
return (
<div className="ar-ComponentsViewer d-flex flex-column h-100">
<DndProvider backend={backend}>
<Main
contentClasses="p-2 h-100"
drawerContent={<ComponentList acceptLibInput />}
mainContent={
<ErrorBoundary>
<Editor />
</ErrorBoundary>
}
rightPanelContent={rightPanelContent}
/>
<Modal {...modalState} />
</DndProvider>
</div>
)
}
export default ComponentsViewer

41
src/Editor.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%;
}
}

250
src/Editor.tsx Executable file
View File

@@ -0,0 +1,250 @@
import { useEffect, useRef, useState, ReactNode, ComponentType } from "react"
import { useLocation, useNavigate } from "react-router-dom"
import {
ArDropdownVariants,
ArSizes,
ArThemes,
FrameContentDefinition,
FrameContentProps,
PRIMITIVES,
} from "./types"
import * as components from "@armco/shared-components"
import { Toggle, Dropdown } from "@armco/shared-components"
import { stringifyUrl } from "@armco/utils/network"
import { toReadable } from "@armco/utils/helper"
import { useTheme } from "@armco/utils/hooks"
import COMPONENTS from "./components"
import { findComponentDescription } from "./helper"
import { DUMMY_CHILDREN, DUMMIES } from "./dummies"
import "./Editor.scss"
const Components: { [key: string]: any } = { ...components }
const FrameContent = (
props: FrameContentProps,
): ReactNode => {
const { contentDefinition, contentRef } = props
const { component, props: componentProps, data } = contentDefinition || {}
const background: string = data?.test?.background
const SelectedComponent = component as ComponentType<any> | undefined
let childRenders: Array<ReactNode> = []
if (data?.type === "HOC") {
childRenders = data.children.map(
(name: string) => DUMMY_CHILDREN[name as keyof object],
)
}
const parsedComponentprops: typeof componentProps = {}
componentProps &&
Object.keys(componentProps).forEach((key) => {
try {
parsedComponentprops[key] = JSON.parse(componentProps[key] as string)
} catch {
parsedComponentprops[key] = componentProps[key]
}
})
return SelectedComponent ? (
<div
className={`ar-Editor__frame__main h-100 w-100 overflow-auto p-5 d-flex flex-column position-relative${background ? " background" : ""
}`}
style={{
// @ts-ignore
backgroundImage: background ? `url(${background})` : "",
}}
ref={contentRef}
id="root"
>
{data && data.type === "HOC" ? (
<SelectedComponent {...parsedComponentprops}>
{childRenders}
</SelectedComponent>
) : (
<SelectedComponent {...parsedComponentprops} />
)}
</div>
) : (
<div className="flex-center h-100 fs-3">
Please use the left panel to view content here
</div>
)
}
const Editor = (): ReactNode => {
const contentRef = useRef<HTMLDivElement | null>(null)
const [selectedComponentDefinition, setSelectedComponentDefinition] =
useState<FrameContentDefinition | undefined>(undefined)
const { theme, setTheme } = useTheme()
const location = useLocation()
const navigate = useNavigate()
useEffect(() => {
if (theme) {
document.getElementsByTagName("html")[0].setAttribute("ar-theme", theme)
if (selectedComponentDefinition) {
if (!selectedComponentDefinition.props) {
selectedComponentDefinition.props = {}
}
selectedComponentDefinition.props.theme = theme
setSelectedComponentDefinition({ ...selectedComponentDefinition })
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [theme])
useEffect(() => {
const locationPathArr = location.pathname
.split("/")
.filter((p: string) => p)
const componentName =
locationPathArr.length > 1 && locationPathArr[locationPathArr.length - 1]
if (componentName) {
const { selectedItem } = findComponentDescription(
componentName,
COMPONENTS,
)
if (selectedItem) {
let props: { [key: string]: string | number | boolean | undefined } = {
demo: true,
theme,
}
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
}
})
}
}
const dummyProps = DUMMIES[componentName]
dummyProps && Object.assign(props, dummyProps)
if (
!selectedComponentDefinition ||
selectedComponentDefinition.componentName !== componentName ||
JSON.stringify(selectedComponentDefinition.props) !==
JSON.stringify(props)
) {
const SelectedComponent = Components[componentName]
setSelectedComponentDefinition({
componentName,
component: SelectedComponent,
props,
data: selectedItem,
})
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location])
const updateProps = (selectedComponentDefinition: FrameContentDefinition) => {
if (selectedComponentDefinition) {
const { demo, theme, ...selectedProps } =
selectedComponentDefinition.props || {}
const nonObjectProps: { [key: string]: any } = {}
Object.keys(selectedProps).forEach(
(p) =>
typeof selectedProps[p] !== "object" &&
(nonObjectProps[p] = selectedProps[p]),
)
const newUrl = stringifyUrl("/", nonObjectProps).slice(1)
newUrl !== location.search && navigate(newUrl)
}
}
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={toReadable(prop)}
onChange={(isChecked: boolean) =>
selectedComponentDefinition?.componentName &&
updateProps({
...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 &&
updateProps({
...selectedComponentDefinition,
props: {
...selectedComponentDefinition.props,
[prop]: obj.value,
},
})
}}
variant={ArDropdownVariants.SELECTIONSASPILLS}
placeholder={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}
contentRef={contentRef}
/>
</div>
)
}
export default Editor

1166
src/components.ts Normal file

File diff suppressed because it is too large Load Diff

134
src/dummies.tsx Normal file
View File

@@ -0,0 +1,134 @@
import { v4 as uuid } from "uuid"
import { Button } from "@armco/shared-components"
import { ArPlacement } from "./types"
// Mock navigator data for Tools & Services demo
interface AppNavigatorDataType {
label: string
items: Array<{ label: string; icon: string; url?: string; description?: string[] }>
icon?: string
color?: string
}
const tnsNavigator: AppNavigatorDataType | undefined = {
label: "Tools & Services",
items: [
{ label: "Analytics", icon: "analytics", url: "/analytics" },
{ label: "Settings", icon: "settings", url: "/settings" },
],
}
export const DUMMIES: { [key: string]: { [key: string]: any } } = {
Accordion: {
data: [
{
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: {
data: [
{ 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" },
],
},
AppAndToolsSelector: {
data: navigator,
},
ProductDescriptionTile: {
name: "Icons & Fonts",
description: [
"Dive into a world of creativity with our diverse icon assets and fonts.",
],
image: "",
imagePlacement: ArPlacement.RIGHT,
},
Tiles: {
data: tnsNavigator,
},
}
export const DUMMY_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" />,
}

90
src/helper.ts Normal file
View File

@@ -0,0 +1,90 @@
import {
ComponentDescription,
ComponentList,
RecusionConditionTypes,
TreeListData,
} from "./types"
import { injectIds } from "@armco/utils/recursionHelper"
export function findComponentDescription(
componentName: string,
components: ComponentList,
) {
let selectedItem = components.find(
(item: ComponentDescription) =>
(typeof item === "string" ? item : item.name) === componentName,
)
return {
selectedItem: selectedItem
? (JSON.parse(JSON.stringify(selectedItem)) as ComponentDescription)
: { name: componentName },
component: componentName,
}
}
export function adaptToTreeFromComponentConfig(
componentConfig: Array<ComponentDescription>,
module?: Array<string>,
): Array<TreeListData> {
const returnTreeList: Array<TreeListData> = []
let components
if (module) {
components = module
} else {
components = componentConfig
components.sort((c1, c2) => (c1.name > c2.name ? 1 : -1))
}
components.forEach((item: any) => {
const config =
typeof item === "string" &&
componentConfig.find((config) => config.name === item)
if (config) {
item = config
}
const treeItem: TreeListData = {
label: typeof item === "string" ? item : item.name,
data: {
component: typeof item === "string" ? item : item.name,
item: typeof item === "string" ? { name: item } : item,
},
}
const children: Array<any> = []
treeItem.children = children
if (item.variants && Object.keys(item.variants.length > 0)) {
Object.keys(item.variants).forEach((variantKey) => {
const variants = item.variants[variantKey]
treeItem.children &&
treeItem.children.push({
label: variantKey,
data: { name: typeof item === "string" ? item : item.name },
children: variants.map((v: string) => ({
label: v,
data: {
component: typeof item === "string" ? item : item.name,
props: { [variantKey]: v },
},
})),
})
})
}
returnTreeList.push(treeItem)
})
injectIds({
data: returnTreeList,
condition: { type: RecusionConditionTypes.KEY_EXISTS, key: "label" },
iterateOn: "children",
})
return returnTreeList
}
export const loadScript = (src: string, globalVar: string): Promise<any> => {
return new Promise((resolve, reject) => {
const script = document.createElement("script")
script.src = src
script.async = true
script.onload = () => resolve(window[globalVar as any])
script.onerror = () => reject(new Error(`Failed to load script: ${src}`))
document.head.appendChild(script)
})
}

17
src/index.tsx Normal file
View File

@@ -0,0 +1,17 @@
import React from "react"
import ReactDOM from "react-dom/client"
import { BrowserRouter } from "react-router-dom"
import { ArProvider } from "@armco/utils/providers"
import ComponentsViewer from "./ComponentsViewer"
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
root.render(
<React.StrictMode>
<BrowserRouter>
<ArProvider>
<ComponentsViewer />
</ArProvider>
</BrowserRouter>
</React.StrictMode>,
)

71
src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1,71 @@
/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />
declare namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: "development" | "production"
readonly PUBLIC_URL: string
}
}
declare module "*.avif" {
const src: string
export default src
}
declare module "*.bmp" {
const src: string
export default src
}
declare module "*.gif" {
const src: string
export default src
}
declare module "*.jpg" {
const src: string
export default src
}
declare module "*.jpeg" {
const src: string
export default src
}
declare module "*.png" {
const src: string
export default src
}
declare module "*.webp" {
const src: string
export default src
}
declare module "*.svg" {
import * as React from "react"
export const ReactComponent: React.FunctionComponent<
React.SVGProps<SVGSVGElement> & { title?: string }
>
const src: string
export default src
}
declare module "*.module.css" {
const classes: { readonly [key: string]: string }
export default classes
}
declare module "*.module.scss" {
const classes: { readonly [key: string]: string }
export default classes
}
declare module "*.module.sass" {
const classes: { readonly [key: string]: string }
export default classes
}

28
src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
interface FrameContentDefinition {
componentName: string
component: ReactNode
props?: {
theme?: string
demo?: boolean
[key: string]:
| string
| number
| boolean
| { [key: string]: any }
| undefined
}
data: { [key: string]: any }
}
export interface ComponentDescription {
name: string
type?: string
children?: Array<string>
props?: Array<any>
propValues?: { [key: string]: string }
variants?: { [key: string]: Array<string | boolean | number> }
test?: { [key: string]: any }
uses?: Array<{ [key: string]: string }>
}
export type ComponentList = Array<ComponentDescription>

69
src/types.ts Normal file
View File

@@ -0,0 +1,69 @@
import { ReactNode } from "react"
// Re-exports from @armco/types
export type { ObjectType, FunctionType, ArrayType, PRIMITIVES } from "@armco/types"
// Re-exports from @armco/shared-components
export type { TreeListData } from "@armco/shared-components/entity"
export {
ArButtonVariants,
ArDropdownVariants,
ArLabelValueVariants,
ArLoaderTypes,
ArPlacement,
ArSizes,
ArWebPageLayout,
RecusionConditionTypes,
} from "@armco/shared-components/enums"
// Local enums (not exported from shared packages)
export enum ArThemes {
LIGHT1 = "th-light-1",
DARK1 = "th-dark-1",
}
export enum ArDateMasks {
DATE = "YYYY-MM-DD",
DATETIME = "YYYY-MM-DD HH:mm:ss",
TIME = "HH:mm:ss",
SHORT_DATE = "MM/DD/YYYY",
LONG_DATE = "MMMM D, YYYY",
}
// ─────────────────────────────────────────────────────────────
// components-viewer local types
// ─────────────────────────────────────────────────────────────
export interface FrameContentDefinition {
componentName?: string
component?: ReactNode | React.ComponentType<any>
props?: {
theme?: string
demo?: boolean
[key: string]:
| string
| number
| boolean
| { [key: string]: any }
| undefined
}
data?: { [key: string]: any }
}
export interface FrameContentProps {
contentDefinition?: FrameContentDefinition
contentRef?: React.RefObject<HTMLDivElement | null>
}
export interface ComponentDescription {
name: string
type?: string
children?: Array<string>
props?: Array<any>
propValues?: { [key: string]: string }
variants?: { [key: string]: Array<string | boolean | number> }
test?: { [key: string]: any }
uses?: Array<{ [key: string]: string }>
}
export type ComponentList = Array<ComponentDescription>

55
src/utils.ts Normal file
View File

@@ -0,0 +1,55 @@
import { injectIds } from "@armco/utils/recursionHelper"
import { RecusionConditionTypes, TreeListData, ComponentDescription } from "./types"
export function adaptToTreeFromComponentConfig(data: any): Array<TreeListData> {
const returnTreeList: Array<TreeListData> = []
Object.keys(data).forEach((key) => {
const groupConfig = data[key as keyof any]
const components = groupConfig.components
components.sort((c1: ComponentDescription, c2: ComponentDescription) =>
(c1.name as any) > (c2.name as any) ? 1 : -1,
)
const obj: TreeListData = {
label: groupConfig.label,
children: [],
data: { ...groupConfig },
}
components.forEach((item: any) => {
const treeItem: TreeListData = {
label: typeof item === "string" ? item : item.name,
data: {
component: typeof item === "string" ? item : item.name,
hierarchy: key,
},
}
const children: Array<any> = []
treeItem.children = children
if (item.variants && Object.keys(item.variants.length > 0)) {
Object.keys(item.variants).forEach((variantKey) => {
const variants = item.variants[variantKey]
treeItem.children &&
treeItem.children.push({
label: variantKey,
data: { name: typeof item === "string" ? item : item.name },
children: variants.map((v: string) => ({
label: v,
data: {
component: typeof item === "string" ? item : item.name,
props: { [variantKey]: v },
hierarchy: key,
},
})),
})
})
}
obj.children && obj.children.push(treeItem)
})
returnTreeList.push(obj)
})
injectIds({
data: returnTreeList,
condition: { type: RecusionConditionTypes.KEY_EXISTS, key: "label" },
iterateOn: "children",
})
return returnTreeList
}

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

26
tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

20
vite-dev.config.ts Normal file
View File

@@ -0,0 +1,20 @@
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
open: true,
},
build: {
outDir: "build",
sourcemap: true,
},
test: {
globals: true,
environment: "jsdom",
setupFiles: "src/setupTests",
mockReset: true,
},
})

41
vite.config.ts Normal file
View File

@@ -0,0 +1,41 @@
import { resolve } from "path"
import { glob } from "glob"
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import dts from "vite-plugin-dts"
import { libInjectCss } from "vite-plugin-lib-inject-css"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
build: {
outDir: "build",
lib: {
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|index).{ts,tsx}")),
},
rollupOptions: {
treeshake: true,
external: [
new RegExp("react*"),
new RegExp("highcharts*"),
new RegExp("@armco/*"),
"d3",
"uuid",
],
output: [
{
format: "es",
dir: "build/es",
entryFileNames: "[name].js",
chunkFileNames: "[name]-chunk.js",
},
{
format: "cjs",
dir: "build/cjs",
entryFileNames: "[name].js",
chunkFileNames: "[name]-chunk.js",
},
],
},
},
})