added config viewer, tasker
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
#!/bin/zsh
|
||||
|
||||
declare -a arr=("Alert", "Badge" "Breadcrumb" "BrowserIncompatibility" "Button" "Checkbox" "ColorPicker" "ContextMenu" "DateInput" "DatePicker" "DateRange" "Calendar" "PickerRange" "DetailsPanel" "Dialog" "FacetedFilter" "Icon" "InlineMenu" "LabelValue" "LearnLink" "Link" "List" "Modal" "Notification" "NumericStepper" "Pagination" "Picklist" "Pill" "Pillbox" "Popover" "ProgressIndicator" "ProgressStepper" "Radio" "SearchField" "SecondaryNavigation" "SegmentedControl" "Select" "Slider" "Splitter" "Tab" "TabBar" "Table" "Tag" "TextArea" "TextInput" "Mask" "TimeEntry" "Toggle" "Toolbar" "Tooltip" "TypeAhead" "Uploader" "Widget")
|
||||
declare -a arr=("Alert", "Badge" "Breadcrumb" "BrowserIncompatibility" "Button" "Checkbox" "ColorPicker" "ContextMenu" "DateInput" "DatePicker" "Date" "Calendar" "PickerRange" "DetailsPanel" "Dialog" "FacetedFilter" "Icon" "InlineMenu" "LabelValue" "LearnLink" "Link" "List" "Modal" "Notification" "NumericStepper" "Pagination" "Picklist" "Pill" "Pillbox" "Popover" "ProgressIndicator" "ProgressStepper" "Radio" "SearchField" "SecondaryNavigation" "SegmentedControl" "Select" "Slider" "Splitter" "Tab" "TabBar" "Table" "Tag" "TextArea" "TextInput" "Mask" "TimeEntry" "Toggle" "Toolbar" "Tooltip" "TypeAhead" "Uploader" "Widget")
|
||||
for i in "${arr[@]}"
|
||||
do
|
||||
npm run component "$i"
|
||||
done
|
||||
|
||||
declare -a arr=("AboutUs" "Application" "Banner" "Benefits" "Blog" "Brands" "Breadcrumbs" "CTA" "Card" "Careers" "Contact" "Content" "Cookies" "Dashboard" "Download" "Ecomm_Orders" "Ecomm_Products" "Empty" "FAQ" "Features" "Footer" "Form" "Gallery" "GraphTiles" "Graph" "HTTPCode" "Hero" "HowItWorks" "InstaPhotos" "Integrations" "LogoClouds" "Newsletter" "Notifications" "Portfolio" "Pricing" "ProductInfo" "Projects" "Reviews" "RichText" "Search" "Services" "SignInUp" "Snackbar" "Stats" "Steps" "Team" "Testimonials" "Toast" "Users")
|
||||
declare -a arr=("AboutUs" "Application" "Carousel" "Banner" "Benefits" "Blog" "Brands" "Breadcrumbs" "CTA" "Card" "Careers" "Contact" "Content" "Cookies" "Dashboard" "Download" "Ecomm_Orders" "Ecomm_Products" "Empty" "FAQ" "Features" "Footer" "Form" "Gallery" "GraphTiles" "Graph" "HTTPCode" "Hero" "HowItWorks" "InstaPhotos" "Integrations" "LogoClouds" "Newsletter" "Notifications" "Portfolio" "Pricing" "ProductInfo" "Projects" "Reviews" "RichText" "Search" "Services" "SignInUp" "Snackbar" "Stats" "Steps" "Swiper" "Team" "Testimonials" "Toast" "Users")
|
||||
for i in "${arr[@]}"
|
||||
do
|
||||
npm run molecule "$i"
|
||||
|
||||
2453
package-lock.json
generated
2453
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -34,12 +34,18 @@
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"@storybook/cli": "^7.0.23",
|
||||
"bootstrap": "^5.3.0",
|
||||
"classnames": "^2.3.2",
|
||||
"d3": "^7.8.5",
|
||||
"highlight.js": "^11.8.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"moment": "^2.29.4",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-bootstrap": "^2.7.4",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"react-redux": "^8.0.1",
|
||||
"react-render-to-json": "^0.0.6",
|
||||
"react-router-dom": "^6.13.0",
|
||||
"svgpath": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vite-plugin-svgr": "^3.2.0"
|
||||
},
|
||||
@@ -56,6 +62,7 @@
|
||||
"@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",
|
||||
|
||||
@@ -1,35 +1,78 @@
|
||||
import { Suspense, useEffect } from "react"
|
||||
import { useRoutes } from "react-router-dom"
|
||||
import Cookies from "js-cookie"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { AlertProps } from "./types/components.interface"
|
||||
import Header from "./components/Header"
|
||||
import { useAppDispatch, useAppSelector } from "./hooks"
|
||||
import { notification, setLoggedIn } from "./store"
|
||||
import {
|
||||
notification,
|
||||
notify,
|
||||
setLoggedIn,
|
||||
setRightPanelContent,
|
||||
setUser,
|
||||
} from "./store"
|
||||
import { Alert } from "./components"
|
||||
import { ArAlertType } from "./types/enums"
|
||||
import Helper from "./utils/helper"
|
||||
import { Network } from "./utils"
|
||||
import ROUTES from "./routes"
|
||||
import { SESSION_COOKIE_NAME } from "./config/constants"
|
||||
import { ENDPOINTS } from "./config/constants"
|
||||
import API_CONFIG from "./config/api-config"
|
||||
|
||||
Helper.populatePagesInRoutes(ROUTES)
|
||||
|
||||
interface RouterProps {}
|
||||
|
||||
const Router = (props: RouterProps) => {
|
||||
const alertInfo = useAppSelector<AlertProps>(notification)
|
||||
const alertInfo = useAppSelector<AlertProps | undefined>(notification)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
document
|
||||
.getElementsByTagName("html")[0]
|
||||
.setAttribute("ar-theme", "th-light-1")
|
||||
dispatch(setLoggedIn(!!Cookies.get(SESSION_COOKIE_NAME)))
|
||||
Network.get(
|
||||
API_CONFIG.IAM[process.env.NODE_ENV] +
|
||||
ENDPOINTS.USERS.ROOT +
|
||||
ENDPOINTS.USERS.CHECK,
|
||||
)
|
||||
.then((response) => {
|
||||
if (response && response.status === 200) {
|
||||
dispatch(setLoggedIn(true))
|
||||
if (response.body) {
|
||||
dispatch(setUser(response.body))
|
||||
}
|
||||
} else {
|
||||
dispatch(setLoggedIn(false))
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.status === 403) {
|
||||
dispatch(setLoggedIn(false))
|
||||
}
|
||||
})
|
||||
window.onmessage = (e) => {
|
||||
if (e.data && e.data.event === "LOGGED_IN") {
|
||||
dispatch(setLoggedIn(true))
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Logged in successfully!",
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
dispatch(setRightPanelContent(""))
|
||||
dispatch(setUser(e.data.data.body.user))
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Header />
|
||||
{useRoutes(ROUTES)}
|
||||
<Alert {...alertInfo} />
|
||||
{alertInfo && <Alert {...alertInfo} />}
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
3
src/app/components/ConfigRowItem/ConfigRowItem.component.scss
Executable file
3
src/app/components/ConfigRowItem/ConfigRowItem.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-ConfigRowItem {
|
||||
|
||||
}
|
||||
8
src/app/components/ConfigRowItem/ConfigRowItem.test.ts
Executable file
8
src/app/components/ConfigRowItem/ConfigRowItem.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigRowItem from "./ConfigRowItem"
|
||||
|
||||
describe("ConfigRowItem", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
90
src/app/components/ConfigRowItem/ConfigRowItem.tsx
Executable file
90
src/app/components/ConfigRowItem/ConfigRowItem.tsx
Executable file
@@ -0,0 +1,90 @@
|
||||
import { ChangeEvent, useState } from "react"
|
||||
import { ConfigRowItemProps } from "../../types/components.interface"
|
||||
import { Button, LoadableIcon, TextInput } from ".."
|
||||
import "./ConfigRowItem.component.scss"
|
||||
import { ArButtonVariants } from "../../types/enums"
|
||||
|
||||
const ConfigRowItem = (props: ConfigRowItemProps): JSX.Element => {
|
||||
const { config, disabled, isNew, onAdd, onUpdate, onDelete } = props
|
||||
const [key, setKey] = useState<string>(config?.key || "")
|
||||
const [value, setValue] = useState<string>(config?.value || "")
|
||||
const [edited, setEdited] = useState<boolean>()
|
||||
|
||||
const configIsSubmittable = (isNew || edited) && key && value
|
||||
return (
|
||||
<div className="ar-ConfigRowItem row">
|
||||
<div className="col-3">
|
||||
<TextInput
|
||||
value={key}
|
||||
disabled={disabled && !edited}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setKey(e.target.value)
|
||||
}
|
||||
placeholder="Config key"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<TextInput
|
||||
placeholder="Enter a value, text/json etc."
|
||||
disabled={disabled && !edited}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(e.target.value)
|
||||
}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
{isNew ? (
|
||||
<div className="col-1 flex-v-center">
|
||||
<Button
|
||||
content="Add"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
preIcon="io/IoMdAdd"
|
||||
onClick={() => {
|
||||
configIsSubmittable && onAdd && onAdd(key, value)
|
||||
setKey("")
|
||||
setValue("")
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="col-2 flex-v-center">
|
||||
<span
|
||||
className={`me-3${
|
||||
configIsSubmittable ? " cursor-pointer" : " pe-none"
|
||||
}`}
|
||||
>
|
||||
<LoadableIcon
|
||||
icon="fa/FaCheck"
|
||||
color={configIsSubmittable ? "green" : "rgba(0, 128, 0, 0.3)"}
|
||||
onClick={() => {
|
||||
config?._id &&
|
||||
configIsSubmittable &&
|
||||
onUpdate &&
|
||||
onUpdate(config?._id, key, value)
|
||||
setKey("")
|
||||
setValue("")
|
||||
}}
|
||||
hoverShadow
|
||||
/>
|
||||
</span>
|
||||
<span className={`me-3${edited ? " pe-none" : " cursor-pointer"}`}>
|
||||
<LoadableIcon
|
||||
icon={edited ? "rx/RxCross2" : "md/MdModeEditOutline"}
|
||||
color={isNew || edited ? "rgba(165, 42, 42, 0.3)" : "brown"}
|
||||
onClick={() => (edited ? setEdited(false) : setEdited(true))}
|
||||
/>
|
||||
</span>
|
||||
<span className={edited ? "pe-none" : "cursor-pointer"}>
|
||||
<LoadableIcon
|
||||
icon="ri/RiDeleteBin6Line"
|
||||
color={isNew || edited ? "rgba(128, 0, 0, 0.3)" : "red"}
|
||||
onClick={() => onDelete && onDelete(config?._id)}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigRowItem
|
||||
3
src/app/components/ConfigRowItem/index.ts
Executable file
3
src/app/components/ConfigRowItem/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigRowItem from "./ConfigRowItem"
|
||||
|
||||
export default ConfigRowItem
|
||||
3
src/app/components/ConfigurationList/ConfigurationList.component.scss
Executable file
3
src/app/components/ConfigurationList/ConfigurationList.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-ConfigurationList {
|
||||
|
||||
}
|
||||
8
src/app/components/ConfigurationList/ConfigurationList.test.ts
Executable file
8
src/app/components/ConfigurationList/ConfigurationList.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationList from "./ConfigurationList"
|
||||
|
||||
describe("ConfigurationList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
30
src/app/components/ConfigurationList/ConfigurationList.tsx
Executable file
30
src/app/components/ConfigurationList/ConfigurationList.tsx
Executable file
@@ -0,0 +1,30 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { TreeList } from ".."
|
||||
import { ConfigurationListProps } from "../../types/components.interface"
|
||||
import { TreeListData } from "../../types/entity.interface"
|
||||
import { Network } from "../../utils"
|
||||
import "./ConfigurationList.component.scss"
|
||||
|
||||
const ConfigurationList = (props: ConfigurationListProps): JSX.Element => {
|
||||
const { list } = props
|
||||
const navigate = useNavigate()
|
||||
const handleComponentSelect = (treeNode: TreeListData) => {
|
||||
const params = treeNode.data?.props
|
||||
treeNode.data?.component &&
|
||||
navigate(
|
||||
Network.stringifyUrl(`/components/${treeNode.data?.component}`, params),
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="ar-ConfigurationList w-100 p-2">
|
||||
<TreeList
|
||||
onItemSelect={handleComponentSelect}
|
||||
data={list || []}
|
||||
title="Configurations"
|
||||
firstExpanded={true}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationList
|
||||
3
src/app/components/ConfigurationList/index.ts
Executable file
3
src/app/components/ConfigurationList/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationList from "./ConfigurationList"
|
||||
|
||||
export default ConfigurationList
|
||||
@@ -0,0 +1,10 @@
|
||||
.ar-ConfigurationLoginPrompt {
|
||||
width: 50vw;
|
||||
background-color: var(--ar-bg);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
small {
|
||||
color: var(--ar-color-secondary);
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationLoginPrompt from "./ConfigurationLoginPrompt"
|
||||
|
||||
describe("ConfigurationLoginPrompt", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
51
src/app/components/ConfigurationLoginPrompt/ConfigurationLoginPrompt.tsx
Executable file
51
src/app/components/ConfigurationLoginPrompt/ConfigurationLoginPrompt.tsx
Executable file
@@ -0,0 +1,51 @@
|
||||
import { setRightPanelContent } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Button, LoadableIcon } from ".."
|
||||
import { ConfigurationLoginPromptProps } from "../../types/components.interface"
|
||||
import { ArButtonVariants } from "../../types/enums"
|
||||
import "./ConfigurationLoginPrompt.component.scss"
|
||||
|
||||
const ConfigurationLoginPrompt = (
|
||||
props: ConfigurationLoginPromptProps,
|
||||
): JSX.Element => {
|
||||
const dispatch = useAppDispatch()
|
||||
return (
|
||||
<div className="ar-ConfigurationLoginPrompt p-3 border">
|
||||
<div className="flex-h-center flex-column">
|
||||
<h6 className="flex-v-center" style={{ color: "#ffbf00" }}>
|
||||
<LoadableIcon
|
||||
classes="me-2"
|
||||
icon="fa/FaExclamationTriangle"
|
||||
color="#ffbf00"
|
||||
/>
|
||||
Please login to continue
|
||||
</h6>
|
||||
<small>
|
||||
This is the configurations feature where you can create and save
|
||||
configurations as key-value pairs.
|
||||
</small>
|
||||
<small>
|
||||
These key-value pairs can then be accessed using an endpoint inside
|
||||
your application.
|
||||
</small>
|
||||
<small className="mb-3">
|
||||
<strong>
|
||||
In order to be able to save and secure these configurations, you
|
||||
need to create an account, if one doesn't exist already and login
|
||||
using the same. Please use the button below to continue.
|
||||
</strong>
|
||||
</small>
|
||||
</div>
|
||||
<Button
|
||||
content="Login"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
postIcon="io5/IoArrowForwardCircle"
|
||||
onClick={() => {
|
||||
dispatch(setRightPanelContent("LoginProvider"))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationLoginPrompt
|
||||
3
src/app/components/ConfigurationLoginPrompt/index.ts
Executable file
3
src/app/components/ConfigurationLoginPrompt/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationLoginPrompt from "./ConfigurationLoginPrompt"
|
||||
|
||||
export default ConfigurationLoginPrompt
|
||||
@@ -0,0 +1,3 @@
|
||||
.ar-ConfigurationNoConfigPrompt {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationNoConfigPrompt from "./ConfigurationNoConfigPrompt"
|
||||
|
||||
describe("ConfigurationNoConfigPrompt", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { ReactNode, useState } from "react"
|
||||
import { setModalState } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Button, LoadableIcon, NamespaceOrgForm, Popover } from ".."
|
||||
import { ConfigurationNoConfigPromptProps } from "../../types/components.interface"
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
} from "../../types/enums"
|
||||
import "./ConfigurationNoConfigPrompt.component.scss"
|
||||
|
||||
const ConfigurationNoConfigPrompt = (
|
||||
props: ConfigurationNoConfigPromptProps,
|
||||
): ReactNode => {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return (
|
||||
<div className="ar-ConfigurationNoConfigPrompt flex-center flex-column border px-3 py-5 w-50">
|
||||
<LoadableIcon
|
||||
classes="mb-4"
|
||||
icon="gr/GrConfigure"
|
||||
size="7rem"
|
||||
color="lightgrey"
|
||||
/>
|
||||
<p className="ar-ConfigurationNoConfigPrompt__message mx-5">
|
||||
You don't have any configurations created yet, you can start by
|
||||
selecting one of the options below
|
||||
</p>
|
||||
<div className="ar-ConfigurationNoConfigPrompt__buttons d-flex">
|
||||
<Popover trigger={ArPopoverTriggers.HOVER}>
|
||||
<Button
|
||||
classes="me-3"
|
||||
content="+ Space"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceOrgForm context="namespace" />,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
className="text-wrap"
|
||||
style={{ maxWidth: "10rem" }}
|
||||
>
|
||||
<p>This is where you keep your configurations.</p>
|
||||
<p className="mb-3">
|
||||
Use namespaces in your project/s to avoid fetching all the
|
||||
configurations for multiple projects.
|
||||
</p>
|
||||
<b>Namespace is required to create a configuration</b>
|
||||
</div>
|
||||
</Popover>
|
||||
<Popover trigger={ArPopoverTriggers.HOVER}>
|
||||
<Button
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
content="+ Organization"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceOrgForm context="org" />,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
className="text-wrap"
|
||||
style={{ maxWidth: "10rem" }}
|
||||
>
|
||||
<p>
|
||||
You may skip creating an organization as one will be created for
|
||||
you if you don't.
|
||||
</p>
|
||||
<p>
|
||||
Or you may choose to create one. This will allow you to control
|
||||
severl aspects of an organization at creation time itself
|
||||
</p>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationNoConfigPrompt
|
||||
3
src/app/components/ConfigurationNoConfigPrompt/index.ts
Executable file
3
src/app/components/ConfigurationNoConfigPrompt/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationNoConfigPrompt from "./ConfigurationNoConfigPrompt"
|
||||
|
||||
export default ConfigurationNoConfigPrompt
|
||||
13
src/app/components/ConfigurationViewer/ConfigurationViewer.component.scss
Executable file
13
src/app/components/ConfigurationViewer/ConfigurationViewer.component.scss
Executable file
@@ -0,0 +1,13 @@
|
||||
.ar-ConfigurationViewer {
|
||||
background-color: var(--ar-bg);
|
||||
.ar-ConfigurationViewer__header {
|
||||
background-color: var(--ar-bg-base);
|
||||
border-bottom: 1px solid var(--ar-color-layout-border);
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
8
src/app/components/ConfigurationViewer/ConfigurationViewer.test.ts
Executable file
8
src/app/components/ConfigurationViewer/ConfigurationViewer.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationViewer from "./ConfigurationViewer"
|
||||
|
||||
describe("ConfigurationViewer", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
205
src/app/components/ConfigurationViewer/ConfigurationViewer.tsx
Executable file
205
src/app/components/ConfigurationViewer/ConfigurationViewer.tsx
Executable file
@@ -0,0 +1,205 @@
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { notify, setModalState } from "../../store"
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
ConfigRowItem,
|
||||
InlineMenu,
|
||||
LoadableIcon,
|
||||
NamespaceInfoBox,
|
||||
NamespaceOrgForm,
|
||||
Popover,
|
||||
} from ".."
|
||||
import {
|
||||
ArAlertType,
|
||||
ArButtonVariants,
|
||||
ArPopoverPositions,
|
||||
ArPopoverSlots,
|
||||
ArSizes,
|
||||
} from "../../types/enums"
|
||||
import { ObjectType } from "../../types/types"
|
||||
import { ConfigurationViewerProps } from "../../types/components.interface"
|
||||
import { Network } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "./ConfigurationViewer.component.scss"
|
||||
|
||||
const ConfigurationViewer = (props: ConfigurationViewerProps): JSX.Element => {
|
||||
const { namespace, fetchNamespaces } = props
|
||||
const configurations = namespace?.configs
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const addConfig = (key: string, value: string, _id?: string) => {
|
||||
const payload: ObjectType = {
|
||||
key,
|
||||
value: JSON.parse(value),
|
||||
namespace: namespace._id,
|
||||
version: "v1",
|
||||
}
|
||||
_id && (payload._id = _id)
|
||||
Network.post(
|
||||
API_CONFIG.CONFIG[process.env.NODE_ENV] +
|
||||
ENDPOINTS.CONFIG.ROOT +
|
||||
ENDPOINTS.CONFIG.SAVE +
|
||||
namespace._id,
|
||||
payload,
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
fetchNamespaces()
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Added a new config successfully",
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Failed to add new config",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const deleteConfig = (_id?: string) => {
|
||||
Network.get(
|
||||
API_CONFIG.CONFIG[process.env.NODE_ENV] +
|
||||
ENDPOINTS.CONFIG.ROOT +
|
||||
ENDPOINTS.CONFIG.DELETE +
|
||||
"/" +
|
||||
namespace._id +
|
||||
"/" +
|
||||
_id,
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
fetchNamespaces()
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Removed config successfully",
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Failed to delete config",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="ar-ConfigurationViewer d-flex flex-column w-100 h-100">
|
||||
<div className="ar-ConfigurationViewer__header px-3 py-2 w-100">
|
||||
<Breadcrumb
|
||||
classes="d-inline-block"
|
||||
data={[{ label: namespace.name, route: `/config/${namespace.name}` }]}
|
||||
/>
|
||||
<Button
|
||||
classes="float-end"
|
||||
content="Edit"
|
||||
color="brown"
|
||||
preIcon="fa/FaRegEdit"
|
||||
variant={ArButtonVariants.LINK}
|
||||
size={ArSizes.SMALL}
|
||||
/>
|
||||
<Popover
|
||||
classes="float-end"
|
||||
// trigger={ArPopoverTriggers.HOVER}
|
||||
position={ArPopoverPositions.BOTTOMLEFT}
|
||||
>
|
||||
<Button
|
||||
content="Add"
|
||||
color="green"
|
||||
preIcon="md/MdAddCircleOutline"
|
||||
variant={ArButtonVariants.LINK}
|
||||
size={ArSizes.SMALL}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
/>
|
||||
<InlineMenu
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
data={{
|
||||
Org: {},
|
||||
Namespace: {
|
||||
onClick: () =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceOrgForm context="namespace" />,
|
||||
}),
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
<LoadableIcon
|
||||
classes="cursor-pointer"
|
||||
icon="io/IoMdInformationCircle"
|
||||
color="#0d6efd"
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceInfoBox namespace={namespace} />,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="ar-ConfigurationViewer__content flex-1 d-flex px-3 py-2 w-100">
|
||||
<div className="d-flex flex-1 flex-column">
|
||||
<ConfigRowItem onAdd={addConfig} isNew />
|
||||
<div className="my-3 border" />
|
||||
{configurations && configurations.length > 0 ? (
|
||||
configurations.map((configuration) => {
|
||||
return (
|
||||
<ConfigRowItem
|
||||
onUpdate={(_id: string, key: string, value: string) =>
|
||||
addConfig(key, value, _id)
|
||||
}
|
||||
onDelete={deleteConfig}
|
||||
config={configuration}
|
||||
disabled
|
||||
/>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<div className="row flex-1">
|
||||
<div className="col flex-center fw-bold">
|
||||
Nothing here, start by adding configurations above
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ar-ConfigurationViewer__footer px-3 py-2 w-100">
|
||||
<Button
|
||||
classes="float-end"
|
||||
content="Submit"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
size={ArSizes.SMALL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationViewer
|
||||
3
src/app/components/ConfigurationViewer/index.ts
Executable file
3
src/app/components/ConfigurationViewer/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationViewer from "./ConfigurationViewer"
|
||||
|
||||
export default ConfigurationViewer
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ReactNode } from "react"
|
||||
import "./Content.component.scss"
|
||||
|
||||
interface ContentProps {
|
||||
children?: JSX.Element | Array<JSX.Element>
|
||||
children?: ReactNode
|
||||
classes?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ComponentList from "../ComponentList/ComponentList"
|
||||
import { ReactNode } from "react"
|
||||
import "./Drawer.component.scss"
|
||||
|
||||
interface DrawerProps {
|
||||
children?: JSX.Element | Array<JSX.Element>
|
||||
children?: ReactNode
|
||||
classes?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { ReactNode, useEffect, useState } from "react"
|
||||
import { useLocation } from "react-router-dom"
|
||||
import { createPortal } from "react-dom"
|
||||
import { Alert, Button, Toggle } from ".."
|
||||
import { Alert, Button, Dropdown, List, Popover, Toggle } from ".."
|
||||
import { FrameContentDefinition } from "../../types/entity.interface"
|
||||
import { FrameContentProps } from "../../types/components.interface"
|
||||
import Helper from "../../utils/helper"
|
||||
import COMPONENTS from "../../config/components"
|
||||
import * as images from "../../static/images"
|
||||
import "./Editor.component.scss"
|
||||
import { ArButtonVariants, ArPopoverSlots } from "../../types/enums"
|
||||
|
||||
const children = {
|
||||
bg: (
|
||||
@@ -208,18 +209,42 @@ const Editor = (): JSX.Element | string => {
|
||||
}
|
||||
}, [selectedComponentDefinition, mountNode, notificationProps, theme])
|
||||
|
||||
const currentItemProps = selectedComponentDefinition?.props || []
|
||||
return (
|
||||
<div className="ar-Editor w-100 h-100 d-flex flex-column">
|
||||
<div className="ar-Editor__tools px-3 py-2">
|
||||
<span className="float-end">
|
||||
<Toggle
|
||||
toggleOnName="Dark"
|
||||
toggleOffName="Light"
|
||||
onChange={(checked: boolean) =>
|
||||
setTheme(checked ? "th-light-1" : "th-dark-1")
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<div className="row justify-content-end">
|
||||
<div className="col flex-v-center justify-content-end">
|
||||
{(Array.isArray(currentItemProps)
|
||||
? currentItemProps
|
||||
: Object.keys(currentItemProps)
|
||||
)
|
||||
.filter((prop: string) => prop !== "demo")
|
||||
.map((prop: any, index: number, arr) => (
|
||||
<Popover
|
||||
classes={`${
|
||||
index === arr.length - 1 ? "me-3 border-right" : ""
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
variant={ArButtonVariants.LINK}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
content={prop}
|
||||
/>
|
||||
<List slot={ArPopoverSlots.POPOVER} data={[]} />
|
||||
</Popover>
|
||||
))}
|
||||
<span className="float-end">
|
||||
<Toggle
|
||||
toggleOnName="Dark"
|
||||
toggleOffName="Light"
|
||||
onChange={(checked: boolean) =>
|
||||
setTheme(checked ? "th-light-1" : "th-dark-1")
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<iframe
|
||||
className="ar-Editor__frame w-100 h-100"
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.shrink {
|
||||
max-width: 4rem;
|
||||
}
|
||||
|
||||
.ar-FavoriteItem {
|
||||
width: 0;
|
||||
transition: width 0.3s linear;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useAppSelector } from "../../hooks"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import { getFavorites } from "../../pages/IconsPage/IconsPage.slice"
|
||||
import { getLoggedIn } from "../../store"
|
||||
import { getLoggedIn, setRightPanelContent } from "../../store"
|
||||
import { Button, IconTile, LoadableIcon, Popover } from ".."
|
||||
import {
|
||||
FavoritesItemProps,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
IconTileProps,
|
||||
} from "../../types/components.interface"
|
||||
import {
|
||||
ArButtonTypes,
|
||||
ArButtonVariants,
|
||||
ArPopoverPositions,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
@@ -40,15 +40,20 @@ const FavoriteItem = (props: FavoritesItemProps): JSX.Element | null => {
|
||||
const FavoritesList = (props: FavoritesListProps): JSX.Element => {
|
||||
const favorites = useAppSelector(getFavorites)
|
||||
const isLoggedIn = useAppSelector<boolean | undefined>(getLoggedIn)
|
||||
const dispatch = useAppDispatch()
|
||||
const icons =
|
||||
favorites &&
|
||||
favorites.map((favorite: IconTileProps, index: number) => (
|
||||
<FavoriteItem index={index} favorite={favorite} />
|
||||
))
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-FavoritesList h-100${icons?.length > 0 ? " show" : ""}`}
|
||||
className={`ar-FavoritesList h-100${icons?.length > 0 ? " show" : ""}${
|
||||
isLoggedIn ? " shrink" : ""
|
||||
}`}
|
||||
>
|
||||
<LoadableIcon icon="tb/TbLayoutSidebarRightCollapse" />
|
||||
{!isLoggedIn ? (
|
||||
<Popover
|
||||
trigger={ArPopoverTriggers.HOVER}
|
||||
@@ -71,12 +76,22 @@ const FavoritesList = (props: FavoritesListProps): JSX.Element => {
|
||||
</div>
|
||||
</Popover>
|
||||
) : (
|
||||
<span
|
||||
className="ar-FavoritesList__header loggedIn p-2 border-bottom d-flex justify-content-center w-100"
|
||||
slot="anchor"
|
||||
<Popover
|
||||
trigger={ArPopoverTriggers.HOVER}
|
||||
position={ArPopoverPositions.LEFTBOTTOM}
|
||||
>
|
||||
<LoadableIcon icon="io/IoIosCheckmarkCircleOutline" color="white" />
|
||||
</span>
|
||||
<span
|
||||
className="ar-FavoritesList__header loggedIn p-2 border-bottom d-flex justify-content-center w-100"
|
||||
slot="anchor"
|
||||
>
|
||||
<LoadableIcon icon="io/IoIosCheckmarkCircleOutline" color="white" />
|
||||
</span>
|
||||
<div slot="popover" className="p-2">
|
||||
<strong className="mb-2 d-inline-block">
|
||||
Your favorites are being synchronized with your account!
|
||||
</strong>
|
||||
</div>
|
||||
</Popover>
|
||||
)}
|
||||
<div
|
||||
className={`ar-FavoritesList__favorites flex-v-center flex-column mt-3${
|
||||
@@ -88,10 +103,13 @@ const FavoritesList = (props: FavoritesListProps): JSX.Element => {
|
||||
{!isLoggedIn && (
|
||||
<Button
|
||||
classes="mx-2"
|
||||
variant={ArButtonTypes.WARNING}
|
||||
variant={ArButtonVariants.WARNING}
|
||||
size={ArSizes.SMALL}
|
||||
content="Login"
|
||||
icon="ci/CiWarning"
|
||||
preIcon="ci/CiWarning"
|
||||
onClick={() => {
|
||||
dispatch(setRightPanelContent("LoginProvider"))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,13 @@ const tabs = [
|
||||
route: "/config",
|
||||
icon: "fc.FcDataConfiguration",
|
||||
},
|
||||
{
|
||||
label: "Tasks",
|
||||
id: uuid(),
|
||||
isActive: false,
|
||||
route: "/tasks",
|
||||
icon: "ri.RiPlayList2Fill",
|
||||
},
|
||||
]
|
||||
|
||||
const userOptions = [
|
||||
@@ -57,7 +64,7 @@ const Header = (): JSX.Element => {
|
||||
}
|
||||
}, [location])
|
||||
|
||||
const separator = <span className="ar-Header__separator h-100 mx-2" />
|
||||
const separator = <span className="ar-Header__separator h-100 me-2" />
|
||||
|
||||
return (
|
||||
<header className="ar-Header w-100 flex-v-center px-2">
|
||||
|
||||
9
src/app/components/IconController/IconController.component.scss
Executable file
9
src/app/components/IconController/IconController.component.scss
Executable file
@@ -0,0 +1,9 @@
|
||||
.ar-IconController {
|
||||
.ar-IconController__main, .ar-IconController__header {
|
||||
background-color: var(--ar-bg-base);
|
||||
}
|
||||
|
||||
.ar-IconController__controls-form {
|
||||
background-color: var(--ar-bg);
|
||||
}
|
||||
}
|
||||
8
src/app/components/IconController/IconController.test.ts
Executable file
8
src/app/components/IconController/IconController.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import IconController from "./IconController"
|
||||
|
||||
describe("IconController", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
181
src/app/components/IconController/IconController.tsx
Executable file
181
src/app/components/IconController/IconController.tsx
Executable file
@@ -0,0 +1,181 @@
|
||||
import { useState } from "react"
|
||||
import { IconControllerProps } from "../../types/components.interface"
|
||||
import {
|
||||
Breadcrumb,
|
||||
ColorSelector,
|
||||
Dropdown,
|
||||
LoadableIcon,
|
||||
Suggestions,
|
||||
Tags,
|
||||
TextInput,
|
||||
} from ".."
|
||||
import { ObjectType } from "../../types/types"
|
||||
import "./IconController.component.scss"
|
||||
|
||||
const complement = (hex?: string) => {
|
||||
if (hex) {
|
||||
const hexParts = hex.substring(1).toLowerCase().split("")
|
||||
const hexMap = { a: 10, b: 11, c: 12, d: 13, e: 14, f: 15 }
|
||||
const reverseMap = { 10: "a", 11: "b", 12: "c", 13: "d", 14: "e", 15: "f" }
|
||||
let complementValue = "#"
|
||||
hexParts.forEach((part: string) => {
|
||||
let num = +part
|
||||
if (isNaN(num)) {
|
||||
num = hexMap[part as keyof object]
|
||||
}
|
||||
const complementNumeric = 15 - num
|
||||
complementValue +=
|
||||
complementNumeric > 9
|
||||
? reverseMap[complementNumeric as keyof object]
|
||||
: complementNumeric
|
||||
})
|
||||
return complementValue
|
||||
}
|
||||
return "#fff"
|
||||
}
|
||||
|
||||
const getSuggestions = (group, name, icons) => {
|
||||
const response: {
|
||||
group?: Array<ObjectType>
|
||||
[key: string]: Array<ObjectType> | undefined
|
||||
} = {}
|
||||
}
|
||||
const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
const { group, name } = props
|
||||
const [iconColor, setIconColor] = useState<string>()
|
||||
const [backgroundColor, setBackGroundColor] = useState<string>()
|
||||
const [size, setSize] = useState<string>()
|
||||
const [unit, setUnit] = useState<string>("rem")
|
||||
const fontColor = complement(backgroundColor || "white")
|
||||
|
||||
// const suggestions = getSuggestions(group, name)
|
||||
|
||||
return (
|
||||
<div className="ar-IconController container h-100 overflow-auto">
|
||||
{group && name && (
|
||||
<Breadcrumb
|
||||
data={[
|
||||
{ label: group, route: `/icon/${group}` },
|
||||
{ label: name, route: `/icon/${group}/${name}` },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<div className="ar-IconController__header mb-3 p-3 border">
|
||||
<h6 className="mb-0 fw-bold">
|
||||
{group}.{name}
|
||||
</h6>
|
||||
</div>
|
||||
<div className="ar-IconController__main mb-3 p-3 border">
|
||||
<div className="row">
|
||||
<div
|
||||
className="ar-IconController__icon-sizes col"
|
||||
style={{ backgroundColor: backgroundColor }}
|
||||
>
|
||||
{group && name && (
|
||||
<div className="row h-100 border-right">
|
||||
<div className="col flex-center flex-column border-right">
|
||||
<div className="h-50 flex-center flex-column border-bottom w-100">
|
||||
<LoadableIcon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
size="3rem"
|
||||
color={iconColor || "black"}
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
style={{ color: fontColor || "black" }}
|
||||
>
|
||||
3rem
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-50 flex-center flex-column w-100">
|
||||
<LoadableIcon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
size="7rem"
|
||||
color={iconColor || "black"}
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
style={{ color: fontColor || "black" }}
|
||||
>
|
||||
7rem
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col flex-center flex-column">
|
||||
<LoadableIcon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
size={`${size}${unit}` || "20rem"}
|
||||
color={iconColor || "black"}
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
style={{ color: fontColor || "black" }}
|
||||
>
|
||||
{size && unit ? size + unit : "20rem"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ar-IconController__controls col d-flex">
|
||||
<div className="row w-100">
|
||||
<div className="col-2 flex-h-center">
|
||||
<ColorSelector onChange={setIconColor} label="Color" />
|
||||
</div>
|
||||
<div className="col-2 flex-h-center">
|
||||
<ColorSelector
|
||||
onChange={setBackGroundColor}
|
||||
label="Background"
|
||||
/>
|
||||
</div>
|
||||
<div className="ar-IconController__controls-form col-8 border">
|
||||
<div className="row h-100 py-3">
|
||||
<div className="ar-IconController__controls-form-container col-12 h-25">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<TextInput
|
||||
type="slider"
|
||||
label="Icon Size"
|
||||
min="1"
|
||||
max="30"
|
||||
onChange={(e) => setSize(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Dropdown
|
||||
label="Unit"
|
||||
onSelectionChanged={(e: { value: string }) =>
|
||||
setUnit(e.value)
|
||||
}
|
||||
options={[
|
||||
{ label: "rem", value: "rem" },
|
||||
{ label: "px", value: "px" },
|
||||
{ label: "vh", value: "vh" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tags classes="col-12 h-75" label={name} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Suggestions
|
||||
classes="border mb-3"
|
||||
suggestions={[{ group: "ci", name: "CiAlarmOn" }]}
|
||||
/>
|
||||
<Suggestions
|
||||
classes="border"
|
||||
suggestions={[{ group: "ci", name: "CiAlarmOn" }]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconController
|
||||
3
src/app/components/IconController/index.ts
Executable file
3
src/app/components/IconController/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import IconController from "./IconController"
|
||||
|
||||
export default IconController
|
||||
@@ -13,16 +13,23 @@
|
||||
.ar-IconTile {
|
||||
cursor: pointer;
|
||||
width: 7rem;
|
||||
height: 8.5rem;
|
||||
height: 7rem;
|
||||
transition: width 0.3s, padding 0.3s;
|
||||
|
||||
&.compact {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
&.list {
|
||||
width: 8rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ar-bg-hover);
|
||||
}
|
||||
|
||||
.ar-IconTile__image {
|
||||
height: 6.5rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
height: 1.9rem;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import React from "react"
|
||||
import IconsList from "./IconsList"
|
||||
|
||||
describe("IconsList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
it("renders without error", () => {})
|
||||
})
|
||||
|
||||
@@ -1,22 +1,48 @@
|
||||
import { ChangeEvent } from "react"
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import { notify } from "../../store"
|
||||
import { notify, setRightPanelContent } from "../../store"
|
||||
import {
|
||||
getFavorites,
|
||||
removeFavorite,
|
||||
setFavorites,
|
||||
} from "../../pages/IconsPage/IconsPage.slice"
|
||||
import { Button, IconTile, Loader, Pagination, Search } from ".."
|
||||
import { ArButtonTypes, ArLoaderTypes, ArSizes } from "../../types/enums"
|
||||
import {
|
||||
Button,
|
||||
IconTile,
|
||||
Loader,
|
||||
Pagination,
|
||||
Popover,
|
||||
Search,
|
||||
SegmentedControl,
|
||||
} from ".."
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArIconTileTypes,
|
||||
ArLoaderTypes,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
} from "../../types/enums"
|
||||
import { IconTileProps, IconsListProps } from "../../types/components.interface"
|
||||
import Helper from "../../utils/helper"
|
||||
import "./IconsList.component.scss"
|
||||
import { SegmentType } from "../../types/types"
|
||||
|
||||
const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
const { icons, onSearchChanged } = props
|
||||
const [view, setView] = useState<SegmentType>()
|
||||
const dispatch = useAppDispatch()
|
||||
const favorites = useAppSelector(getFavorites)
|
||||
|
||||
useEffect(() => {
|
||||
if (favorites) {
|
||||
dispatch(
|
||||
setRightPanelContent(favorites.length > 0 ? "FavoritesList" : ""),
|
||||
)
|
||||
}
|
||||
}, [favorites, dispatch])
|
||||
|
||||
const slices =
|
||||
icons &&
|
||||
icons.length > 0 &&
|
||||
@@ -24,7 +50,11 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
|
||||
const onIconTileClick = (iconProps: IconResponse) => {
|
||||
dispatch(
|
||||
notify({ show: true, message: `Icon link for ${iconProps.name} copied` }),
|
||||
notify({
|
||||
show: true,
|
||||
message: `Icon link for ${iconProps.name} copied`,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,27 +63,69 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
{!icons && (
|
||||
<Loader label="Loading Icons..." type={ArLoaderTypes.SHAPES} />
|
||||
)}
|
||||
<div className="ar-IconsList__header-pagination-search-upload py-2 px-3 mb-3 border">
|
||||
<div className="ar-IconsList__header-pagination-search-upload py-2 px-3 mb-2 border">
|
||||
<div className="row">
|
||||
<div className="col-7 d-flex align-items-center">
|
||||
<div className="col-4 d-flex align-items-center">
|
||||
<h6 className="mb-0 h-100 flex-center px-3 border-right">
|
||||
Icon Repository
|
||||
<Button
|
||||
variant={ArButtonVariants.LINK}
|
||||
content="Categories"
|
||||
size={ArSizes.SMALL}
|
||||
splitOptions={[
|
||||
{
|
||||
label: "All",
|
||||
},
|
||||
{
|
||||
label: "Business",
|
||||
},
|
||||
{
|
||||
label: "Medical",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</h6>
|
||||
{slices && (
|
||||
<Pagination classes="ps-3" data={slices} maxPillsToShow={5} />
|
||||
)}
|
||||
</div>
|
||||
<span className="col-2">
|
||||
<span className="col-4 d-none d-md-flex flex-v-center justify-content-end">
|
||||
<Button
|
||||
classes="h-100 float-end"
|
||||
classes="h-100 float-end me-3"
|
||||
content="Create"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonVariants.LINK}
|
||||
preIcon="io5/IoCreateOutline"
|
||||
/>
|
||||
<Button
|
||||
classes="h-100 float-end me-3"
|
||||
content="Upload"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonTypes.SUCCESS}
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
/>
|
||||
<SegmentedControl
|
||||
segments={[
|
||||
{
|
||||
isIcon: true,
|
||||
name: "comfy",
|
||||
icon: "md/MdViewComfy",
|
||||
tooltip: "View Comfortable",
|
||||
},
|
||||
{
|
||||
isIcon: true,
|
||||
name: "compact",
|
||||
icon: "md/MdViewCompact",
|
||||
tooltip: "View Compact",
|
||||
},
|
||||
{
|
||||
isIcon: true,
|
||||
name: "list",
|
||||
icon: "io5/IoList",
|
||||
tooltip: "Quick Peak",
|
||||
},
|
||||
]}
|
||||
onChange={(view) => setView(view as SegmentType)}
|
||||
/>
|
||||
</span>
|
||||
<div className="col-3">
|
||||
<div className="col-4 offset-4 offset-md-0 flex-v-center">
|
||||
<Search
|
||||
classes="bg-white h-100"
|
||||
classes="bg-white"
|
||||
placeholder="Search by name, tags, description"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onSearchChanged(event.target.value)
|
||||
@@ -75,67 +147,92 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
{icons && (
|
||||
<div className="ar-IconsList__icon-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap">
|
||||
{icons.slice(0, 100).map((icon, index) => (
|
||||
<IconTile
|
||||
key={index}
|
||||
icon={icon}
|
||||
onClick={onIconTileClick}
|
||||
tools={[
|
||||
{
|
||||
iconProps: {
|
||||
icon: "io.IoIosShareAlt",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
<Popover trigger={ArPopoverTriggers.HOVER}>
|
||||
{view && view.name !== ArIconTileTypes.LIST ? (
|
||||
<span slot={ArPopoverSlots.POPOVER}>{icon.name}</span>
|
||||
) : null}
|
||||
<IconTile
|
||||
type={
|
||||
view && view.name
|
||||
? (view.name as ArIconTileTypes)
|
||||
: ArIconTileTypes.COMFY
|
||||
}
|
||||
classes={view?.name}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
key={index}
|
||||
icon={icon}
|
||||
onClick={onIconTileClick}
|
||||
tools={[
|
||||
{
|
||||
iconProps: {
|
||||
icon: "io.IoIosShareAlt",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
},
|
||||
name: "share",
|
||||
onClick: () => {},
|
||||
},
|
||||
name: "share",
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
iconProps: {
|
||||
icon: "ai/AiFillLike",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
{
|
||||
iconProps: {
|
||||
icon: "ai/AiFillLike",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
},
|
||||
name: "like",
|
||||
onClick: () => {},
|
||||
},
|
||||
name: "like",
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
iconProps: {
|
||||
icon: "md/MdFavorite",
|
||||
hoverColor: "lightblue",
|
||||
toggleColor: "red",
|
||||
color: "white",
|
||||
toggled: false,
|
||||
},
|
||||
name: "favorite",
|
||||
onClick: () => {
|
||||
const favorite = favorites.find(
|
||||
(searchedIcon: IconTileProps) =>
|
||||
icon.name === searchedIcon.icon.name,
|
||||
)
|
||||
if (!favorites || !favorite) {
|
||||
dispatch(setFavorites({ icon }))
|
||||
dispatch(
|
||||
notify({
|
||||
message: "Icon Added to your favorites",
|
||||
show: true,
|
||||
}),
|
||||
{
|
||||
iconProps: {
|
||||
icon: "md/MdFavorite",
|
||||
hoverColor: "lightblue",
|
||||
toggleColor: "red",
|
||||
color: "white",
|
||||
toggled: false,
|
||||
},
|
||||
name: "favorite",
|
||||
onClick: () => {
|
||||
const favorite = favorites.find(
|
||||
(searchedIcon: IconTileProps) =>
|
||||
icon.name === searchedIcon.icon.name,
|
||||
)
|
||||
} else {
|
||||
dispatch(removeFavorite({ icon }))
|
||||
dispatch(
|
||||
notify({
|
||||
message: "Icon removed from your favorites",
|
||||
show: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
if (!favorites || !favorite) {
|
||||
dispatch(setFavorites({ icon }))
|
||||
dispatch(
|
||||
notify({
|
||||
message: "Icon Added to your favorites",
|
||||
show: true,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
dispatch(removeFavorite({ icon }))
|
||||
dispatch(
|
||||
notify({
|
||||
message: "Icon removed from your favorites",
|
||||
show: true,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
]}
|
||||
hideFooter={
|
||||
(view && (view.name as ArIconTileTypes)) !==
|
||||
ArIconTileTypes.LIST
|
||||
}
|
||||
/>
|
||||
</Popover>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{slices && (
|
||||
<Pagination
|
||||
classes="my-3 flex-center"
|
||||
data={slices}
|
||||
maxPillsToShow={5}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.ar-LoginProvider {
|
||||
|
||||
width: 30rem;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import "./LoginProvider.component.scss"
|
||||
const LoginProvider = (props: LoginProviderProps): JSX.Element => {
|
||||
return (
|
||||
<iframe
|
||||
src="https://iam.notabuck.com"
|
||||
// src="https://iam.notabuck.com"
|
||||
src="http://localhost:3001"
|
||||
title="IAM"
|
||||
className="ar-LoginProvider h-100"
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.ar-Main {
|
||||
.ar-Drawer {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.ar-Content {
|
||||
width: 85%;
|
||||
& + .ar-Content {
|
||||
width: 85%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,13 @@ import Content from "../Content"
|
||||
import Drawer from "../Drawer"
|
||||
import { ErrorBoundary, SidePanel } from ".."
|
||||
import { ArPlacement } from "../../types/enums"
|
||||
import { MainProps } from "../../types/components.interface"
|
||||
import "./Main.component.scss"
|
||||
import { ReactNode } from "react"
|
||||
|
||||
interface MainProps {
|
||||
drawerContent?: JSX.Element | Array<JSX.Element>
|
||||
mainContent?: JSX.Element | Array<JSX.Element>
|
||||
rightPanelContent?: JSX.Element | Array<JSX.Element>
|
||||
leftPanelContent?: JSX.Element | Array<JSX.Element>
|
||||
rightPanelHeader?: ReactNode
|
||||
leftPanelHeader?: ReactNode
|
||||
}
|
||||
|
||||
const Main = (props: MainProps): JSX.Element => {
|
||||
const {
|
||||
contentClasses,
|
||||
drawerContent,
|
||||
mainContent,
|
||||
rightPanelContent,
|
||||
@@ -25,18 +18,25 @@ const Main = (props: MainProps): JSX.Element => {
|
||||
} = props
|
||||
return (
|
||||
<main className="ar-Main d-flex flex-grow-1 w-100">
|
||||
<Drawer classes="d-flex h-100">{drawerContent}</Drawer>
|
||||
{drawerContent && <Drawer classes="d-flex h-100">{drawerContent}</Drawer>}
|
||||
<SidePanel
|
||||
header={leftPanelHeader || "Header Name"}
|
||||
placement={ArPlacement.LEFT}
|
||||
content={leftPanelContent}
|
||||
componentName={leftPanelContent as string}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<Content classes="flex-center p-3 position-relative">
|
||||
<Content
|
||||
classes={`flex-center flex-grow-1 position-relative${
|
||||
contentClasses ? " " + contentClasses : ""
|
||||
}`}
|
||||
>
|
||||
{mainContent}
|
||||
</Content>
|
||||
</ErrorBoundary>
|
||||
<SidePanel header={rightPanelHeader} content={rightPanelContent} />
|
||||
<SidePanel
|
||||
header={rightPanelHeader}
|
||||
componentName={rightPanelContent as string}
|
||||
/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
9
src/app/components/NamespaceInfoBox/NamespaceInfoBox.component.scss
Executable file
9
src/app/components/NamespaceInfoBox/NamespaceInfoBox.component.scss
Executable file
@@ -0,0 +1,9 @@
|
||||
.ar-NamespaceInfoBox {
|
||||
.ar-NamespaceInfoBox__namespace-link {
|
||||
border: var(--ar-border);
|
||||
background-color: var(--ar-bg-hover);
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
8
src/app/components/NamespaceInfoBox/NamespaceInfoBox.test.ts
Executable file
8
src/app/components/NamespaceInfoBox/NamespaceInfoBox.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import NamespaceInfoBox from "./NamespaceInfoBox"
|
||||
|
||||
describe("NamespaceInfoBox", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
114
src/app/components/NamespaceInfoBox/NamespaceInfoBox.tsx
Executable file
114
src/app/components/NamespaceInfoBox/NamespaceInfoBox.tsx
Executable file
@@ -0,0 +1,114 @@
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import hljs from "highlight.js/lib/core"
|
||||
import javascript from "highlight.js/lib/languages/javascript"
|
||||
import { NamespaceInfoBoxProps } from "../../types/components.interface"
|
||||
import { Alert, Link, LoadableIcon } from ".."
|
||||
import { Helper } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "highlight.js/styles/github.css"
|
||||
import "./NamespaceInfoBox.component.scss"
|
||||
|
||||
hljs.registerLanguage("javascript", javascript)
|
||||
|
||||
const NamespaceInfoBox = (props: NamespaceInfoBoxProps): JSX.Element => {
|
||||
const { namespace } = props
|
||||
const codeRef = useRef<HTMLPreElement>(null)
|
||||
const [displayed, show] = useState<{ displayed: boolean }>()
|
||||
const [message, setMessage] = useState<string>()
|
||||
const helpText =
|
||||
"Now that your namespace has been created you can integrate it with your project using the below endpoint"
|
||||
const nsLink =
|
||||
API_CONFIG.CONFIG[process.env.NODE_ENV] +
|
||||
ENDPOINTS.NAMESPACE.ROOT +
|
||||
ENDPOINTS.NAMESPACE.FETCH +
|
||||
namespace._id
|
||||
const nsAllLink =
|
||||
API_CONFIG.CONFIG[process.env.NODE_ENV] +
|
||||
ENDPOINTS.NAMESPACE.ROOT +
|
||||
ENDPOINTS.NAMESPACE.FETCHALL
|
||||
const codeStr = `
|
||||
// API to fetch configurations for the namespace ${namespace.name}
|
||||
fetch("${nsLink}")
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
console.log(res.body.namespace);
|
||||
}
|
||||
})
|
||||
`
|
||||
const code = hljs.highlight("javascript", codeStr).value
|
||||
|
||||
return (
|
||||
<div className="ar-NamespaceInfoBox w-100">
|
||||
<div className="ar-NamespaceInfoBox__content d-flex flex-1 flex-column h-100">
|
||||
<header className="ar-NamespaceInfoBox__header w-100 border-bottom px-3 py-2 fw-bold f4 flex-v-center">
|
||||
{namespace.name}
|
||||
</header>
|
||||
<main className="ar-NamespaceInfoBox__body flex-1 overflow-auto">
|
||||
<div className="ar-NameSpaceModal__body p-3 h-100 flex-center flex-column">
|
||||
<small className="ar-help-text mb-3">{helpText}</small>
|
||||
<Link
|
||||
to={nsLink}
|
||||
label={nsLink}
|
||||
classes="ar-NamespaceInfoBox__namespace-link px-3 py-2 mb-3"
|
||||
onClick={() => {
|
||||
Helper.copyOrPrompt(nsLink, () => {
|
||||
show({ displayed: true })
|
||||
setMessage("Link Copied!")
|
||||
})
|
||||
}}
|
||||
preemptNavigation
|
||||
/>
|
||||
<small className="ar-help-text mb-3">
|
||||
Or to fetch all of your namespaces
|
||||
</small>
|
||||
<Link
|
||||
to={nsAllLink}
|
||||
label={nsAllLink}
|
||||
classes="ar-NamespaceInfoBox__namespace-link px-3 py-2 mb-3"
|
||||
onClick={() => {
|
||||
Helper.copyOrPrompt(nsAllLink, () => {
|
||||
show({ displayed: true })
|
||||
setMessage("Link Copied!")
|
||||
})
|
||||
}}
|
||||
preemptNavigation
|
||||
/>
|
||||
<div className="ar-NamespaceInfoBox__sample-code border p-2 w-100 overflow-auto position-relative">
|
||||
<LoadableIcon
|
||||
classes="ar-NamespaceInfoBox__copy-button position-absolute"
|
||||
icon="md/MdContentCopy"
|
||||
color="grey"
|
||||
hoverColor="black"
|
||||
onClick={() => {
|
||||
const code = codeRef.current?.innerText
|
||||
Helper.copyOrPrompt(code, () => {
|
||||
show({ displayed: true })
|
||||
setMessage(
|
||||
"Copied code for integrating your configurations",
|
||||
)
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<pre>
|
||||
<code
|
||||
dangerouslySetInnerHTML={{ __html: code }}
|
||||
ref={codeRef}
|
||||
/>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Alert
|
||||
show={displayed?.displayed}
|
||||
key={uuid()}
|
||||
message={message || "Link Copied!"}
|
||||
closeable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NamespaceInfoBox
|
||||
3
src/app/components/NamespaceInfoBox/index.ts
Executable file
3
src/app/components/NamespaceInfoBox/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import NamespaceInfoBox from "./NamespaceInfoBox"
|
||||
|
||||
export default NamespaceInfoBox
|
||||
13
src/app/components/NamespaceOrgForm/NamespaceOrgForm.component.scss
Executable file
13
src/app/components/NamespaceOrgForm/NamespaceOrgForm.component.scss
Executable file
@@ -0,0 +1,13 @@
|
||||
.ar-NamespaceOrgForm {
|
||||
header {
|
||||
min-height: var(--ar-height-header);
|
||||
height: unset;
|
||||
background-color: var(--ar-bg-base);
|
||||
}
|
||||
|
||||
footer {
|
||||
min-height: var(--ar-height-footer);
|
||||
height: unset;
|
||||
background-color: var(--ar-bg-base);
|
||||
}
|
||||
}
|
||||
5
src/app/components/NamespaceOrgForm/NamespaceOrgForm.test.ts
Executable file
5
src/app/components/NamespaceOrgForm/NamespaceOrgForm.test.ts
Executable file
@@ -0,0 +1,5 @@
|
||||
import NamespaceOrgForm from "./NamespaceOrgForm"
|
||||
|
||||
describe("NamespaceOrgForm", () => {
|
||||
it("renders without error", () => {})
|
||||
})
|
||||
110
src/app/components/NamespaceOrgForm/NamespaceOrgForm.tsx
Executable file
110
src/app/components/NamespaceOrgForm/NamespaceOrgForm.tsx
Executable file
@@ -0,0 +1,110 @@
|
||||
import { ChangeEvent, useState } from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { notify, setModalState } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Button, Loader, NamespaceInfoBox, TextInput } from ".."
|
||||
import { NamespaceFormProps } from "../../types/components.interface"
|
||||
import { ArAlertType, ArButtonVariants, ArSizes } from "../../types/enums"
|
||||
import { FunctionType } from "../../types/types"
|
||||
import { Network } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "./NamespaceOrgForm.component.scss"
|
||||
|
||||
const NamespaceOrgForm = (props: NamespaceFormProps): JSX.Element => {
|
||||
const { context } = props
|
||||
const [namespace, setNamespace] = useState<string>()
|
||||
const [org, setOrg] = useState<string>()
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const nsURL =
|
||||
API_CONFIG.CONFIG[process.env.NODE_ENV] +
|
||||
ENDPOINTS.NAMESPACE.ROOT +
|
||||
ENDPOINTS.NAMESPACE.CREATE
|
||||
|
||||
const helpText =
|
||||
context === "org"
|
||||
? "You may think of an organization as a way to organize your configuration spaces. A typical use case could be managing configurations for multiple projects, and maintaining configuration for each project in it's own organization. An organization may contain one or more organizations and/or namespaces"
|
||||
: "You may enter a name that indicates the purpose of the configuration held under this space, for eg. an environment name - test, pre-prod, prod; or a space for form field configurations, eg. field_definitions, etc."
|
||||
|
||||
return (
|
||||
<div className="ar-NamespaceOrgForm">
|
||||
{loading && <Loader />}
|
||||
<div className="ar-Modal__content d-flex flex-1 flex-column">
|
||||
<header className="ar-Modal__header w-100 border-bottom px-3 py-2 fw-bold f4 flex-v-center">
|
||||
{context === "org" ? "Create Organization" : "Create a Space"}
|
||||
</header>
|
||||
<main className="ar-Modal__body flex-1">
|
||||
<div className="ar-NameSpaceModal__body p-3 h-100 flex-center flex-column">
|
||||
<TextInput
|
||||
containerClasses="mb-3"
|
||||
classes="w-100"
|
||||
label={context === "org" ? "Organization Name" : "Namespace"}
|
||||
onChange={
|
||||
((event: ChangeEvent<HTMLInputElement>) =>
|
||||
(context === "org" ? setOrg : setNamespace)(
|
||||
event.target.value,
|
||||
)) as FunctionType
|
||||
}
|
||||
/>
|
||||
<small className="ar-help-text">{helpText}</small>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="ar-Modal__footer w-100 border-top px-3 py-2">
|
||||
<Button
|
||||
classes="float-end"
|
||||
variant={ArButtonVariants.PRIMARY}
|
||||
size={ArSizes.SMALL}
|
||||
content="Submit"
|
||||
onClick={() => {
|
||||
setLoading(true)
|
||||
Network.post(nsURL, { org, namespace })
|
||||
.then((res) => {
|
||||
console.log(res)
|
||||
setLoading(false)
|
||||
if (res.status === 200) {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Namespace created successfully!",
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
dispatch(
|
||||
setModalState({
|
||||
content: namespace ? (
|
||||
<NamespaceInfoBox namespace={res.body.namespace} />
|
||||
) : null,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false)
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Failed to create namespace/org",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
classes="float-end me-3"
|
||||
variant={ArButtonVariants.SECONDARY}
|
||||
size={ArSizes.SMALL}
|
||||
content="Cancel"
|
||||
onClick={() => dispatch(setModalState({ show: false }))}
|
||||
/>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NamespaceOrgForm
|
||||
3
src/app/components/NamespaceOrgForm/index.ts
Executable file
3
src/app/components/NamespaceOrgForm/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import NamespaceOrgForm from "./NamespaceOrgForm"
|
||||
|
||||
export default NamespaceOrgForm
|
||||
@@ -1,11 +1,11 @@
|
||||
.ar-SidePanel {
|
||||
transition: max-width 0.5s;
|
||||
.ar-SidePanel__header {
|
||||
height: 3rem;
|
||||
border-bottom: var(--ar-border);
|
||||
}
|
||||
|
||||
max-width: 30rem;
|
||||
width: 30rem;
|
||||
// width: 30rem;
|
||||
&.floating {
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { Suspense, lazy } from "react"
|
||||
import { SidePanelProps } from "../../types/components.interface"
|
||||
import { ArPlacement } from "../../types/enums"
|
||||
import { ComponentImport } from "../../types/entity.interface"
|
||||
import "./SidePanel.component.scss"
|
||||
|
||||
const componentImport = {
|
||||
LoginProvider: () => import("../LoginProvider"),
|
||||
FavoritesList: () => import("../FavoritesList"),
|
||||
TaskViewer: () => import("../TaskViewer"),
|
||||
// Add more components and keys as needed
|
||||
}
|
||||
|
||||
const SidePanel = (props: SidePanelProps): JSX.Element | null => {
|
||||
const { isFloating, content, header, placement } = props
|
||||
const { isFloating, componentName, componentProps, header, placement } = props
|
||||
const SelectedComponent =
|
||||
componentName &&
|
||||
lazy(() => (componentImport as ComponentImport)[componentName]())
|
||||
const placementClass = isFloating
|
||||
? !placement || placement === ArPlacement.RIGHT
|
||||
? " right right-0"
|
||||
@@ -11,21 +23,21 @@ const SidePanel = (props: SidePanelProps): JSX.Element | null => {
|
||||
: !placement || placement === ArPlacement.RIGHT
|
||||
? "right"
|
||||
: "left"
|
||||
return content ? (
|
||||
<div
|
||||
className={`ar-SidePanel h-100 d-flex flex-column${
|
||||
isFloating ? " position-absolute top-0" : ""
|
||||
}${" " + placementClass}${isFloating ? " floating" : ""}${
|
||||
placement ? " " + placement : ""
|
||||
}`}
|
||||
>
|
||||
{header && (
|
||||
<div className="ar-SidePanel__header row h6 p-3 flex-v-center">
|
||||
<div className="col">{header}</div>
|
||||
</div>
|
||||
)}
|
||||
{content}
|
||||
</div>
|
||||
return SelectedComponent ? (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<div
|
||||
className={`ar-SidePanel h-100 d-flex flex-column${
|
||||
isFloating ? " floating position-absolute top-0" : ""
|
||||
}${" " + placementClass}${placement ? " " + placement : ""}`}
|
||||
>
|
||||
{header && (
|
||||
<div className="ar-SidePanel__header row h6 p-3 flex-v-center">
|
||||
<div className="col">{header}</div>
|
||||
</div>
|
||||
)}
|
||||
<SelectedComponent {...componentProps} />
|
||||
</div>
|
||||
</Suspense>
|
||||
) : null
|
||||
}
|
||||
|
||||
|
||||
3
src/app/components/TaskList/TaskList.component.scss
Executable file
3
src/app/components/TaskList/TaskList.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-TaskList {
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react"
|
||||
import DateRange from "./DateRange"
|
||||
import TaskList from "./TaskList"
|
||||
|
||||
describe("DateRange", () => {
|
||||
describe("TaskList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
103
src/app/components/TaskList/TaskList.tsx
Executable file
103
src/app/components/TaskList/TaskList.tsx
Executable file
@@ -0,0 +1,103 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { Button, Link, SidePanel } from ".."
|
||||
import { TaskListProps } from "../../types/components.interface"
|
||||
import { Task } from "../../types/entity.interface"
|
||||
import { ArButtonVariants, ArPlacement, ArSizes } from "../../types/enums"
|
||||
import { FunctionType } from "../../types/types"
|
||||
import "./TaskList.component.scss"
|
||||
|
||||
interface TaskItemProps {
|
||||
setSelectedTask: FunctionType
|
||||
task: Task
|
||||
}
|
||||
|
||||
const TaskItem = (props: TaskItemProps) => {
|
||||
const { setSelectedTask, task } = props
|
||||
return (
|
||||
<Button
|
||||
variant={ArButtonVariants.LINK}
|
||||
onClick={() => setSelectedTask(task)}
|
||||
content={task.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const TaskList = (props: TaskListProps): JSX.Element => {
|
||||
const { list, taskFetcher } = props
|
||||
const [selectedTask, setSelectedTask] = useState<Task>()
|
||||
const [taskViewerDisplayed, displayTaskViewer] = useState<boolean>()
|
||||
|
||||
const listRenders =
|
||||
list &&
|
||||
list.map((task) => {
|
||||
return <TaskItem task={task} setSelectedTask={setSelectedTask} />
|
||||
})
|
||||
const hasListItems = list && list.length > 0
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTask) {
|
||||
displayTaskViewer(true)
|
||||
}
|
||||
}, [selectedTask])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-TaskList h-100 container d-flex px-0 w-100
|
||||
${!hasListItems ? " border bg" : ""}`}
|
||||
>
|
||||
{hasListItems ? (
|
||||
<div
|
||||
className={`d-flex h-100 flex-column flex-1${
|
||||
taskViewerDisplayed ? " me-3" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="px-3 py-2 mb-3 bg border">
|
||||
<Button
|
||||
classes="float-end"
|
||||
content="Create"
|
||||
preIcon="md/MdAddCircleOutline"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
size={ArSizes.SMALL}
|
||||
onClick={() => displayTaskViewer(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ar-TaskList__main border p-3 flex-1 bg">
|
||||
{listRenders}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="ar-TaskList__no-tasks-prompt m-3 flex-grow-1">
|
||||
<small className="ar-TaskList__no-tasks-help-text d-inline-block mb-3">
|
||||
You have not created any tasks yet, create your first one now.
|
||||
</small>
|
||||
<Button
|
||||
content="Create"
|
||||
preIcon="md/MdAddCircleOutline"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
size={ArSizes.SMALL}
|
||||
onClick={() => displayTaskViewer(true)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{taskViewerDisplayed && (
|
||||
<SidePanel
|
||||
placement={ArPlacement.RIGHT}
|
||||
componentName="TaskViewer"
|
||||
componentProps={{
|
||||
hideSelf: () => {
|
||||
setSelectedTask(undefined)
|
||||
displayTaskViewer(false)
|
||||
},
|
||||
isNew: !selectedTask,
|
||||
selectedTask,
|
||||
taskFetcher,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TaskList
|
||||
3
src/app/components/TaskList/index.ts
Executable file
3
src/app/components/TaskList/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import TaskList from "./TaskList"
|
||||
|
||||
export default TaskList
|
||||
3
src/app/components/TaskLoginPrompt/TaskLoginPrompt.component.scss
Executable file
3
src/app/components/TaskLoginPrompt/TaskLoginPrompt.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-TaskLoginPrompt {
|
||||
background-color: var(--ar-bg);
|
||||
}
|
||||
8
src/app/components/TaskLoginPrompt/TaskLoginPrompt.test.ts
Executable file
8
src/app/components/TaskLoginPrompt/TaskLoginPrompt.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import TaskLoginPrompt from "./TaskLoginPrompt"
|
||||
|
||||
describe("TaskLoginPrompt", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
64
src/app/components/TaskLoginPrompt/TaskLoginPrompt.tsx
Executable file
64
src/app/components/TaskLoginPrompt/TaskLoginPrompt.tsx
Executable file
@@ -0,0 +1,64 @@
|
||||
import { setRightPanelContent } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Button, LoadableIcon } from ".."
|
||||
import { TaskLoginPromptProps } from "../../types/components.interface"
|
||||
import { ArButtonVariants } from "../../types/enums"
|
||||
import "./TaskLoginPrompt.component.scss"
|
||||
|
||||
const TaskLoginPrompt = (props: TaskLoginPromptProps): JSX.Element => {
|
||||
const dispatch = useAppDispatch()
|
||||
return (
|
||||
<div className="ar-TaskLoginPrompt p-3 border container">
|
||||
<div className="flex-h-center flex-column">
|
||||
<h6 className="flex-v-center" style={{ color: "#ffbf00" }}>
|
||||
<LoadableIcon
|
||||
classes="me-2"
|
||||
icon="fa/FaExclamationTriangle"
|
||||
color="#ffbf00"
|
||||
/>
|
||||
Please login to continue
|
||||
</h6>
|
||||
<small>
|
||||
This is the tasker where you can create and schedule simple tasks,
|
||||
represented by an endpoint.
|
||||
</small>
|
||||
<small>
|
||||
Created tasks can be run by configuring a crontab format, or can be
|
||||
run manually.
|
||||
</small>
|
||||
<small>
|
||||
Typical use cases:{" "}
|
||||
<span className="fw-bold">
|
||||
API health check, cloud configuration refresh, cache refresh, etc.
|
||||
</span>
|
||||
</small>
|
||||
<small>
|
||||
Understand that this feature only calls an endpoint at provided
|
||||
intervals, maintaining and managing these APIs is entirely your
|
||||
responsibility.
|
||||
</small>
|
||||
<small className="mb-3 fw-bold">
|
||||
It is necessary that this feature is used responsibly. Please only
|
||||
provide endpoints that you own or administer.
|
||||
</small>
|
||||
<small className="mb-3">
|
||||
<strong>
|
||||
In order to be able to save and schedule these tasks, you need to
|
||||
create an account, if one doesn't exist already and login using the
|
||||
same. Please use the button below to continue.
|
||||
</strong>
|
||||
</small>
|
||||
</div>
|
||||
<Button
|
||||
content="Login"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
postIcon="io5/IoArrowForwardCircle"
|
||||
onClick={() => {
|
||||
dispatch(setRightPanelContent("LoginProvider"))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TaskLoginPrompt
|
||||
3
src/app/components/TaskLoginPrompt/index.ts
Executable file
3
src/app/components/TaskLoginPrompt/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import TaskLoginPrompt from "./TaskLoginPrompt"
|
||||
|
||||
export default TaskLoginPrompt
|
||||
12
src/app/components/TaskViewer/TaskViewer.component.scss
Executable file
12
src/app/components/TaskViewer/TaskViewer.component.scss
Executable file
@@ -0,0 +1,12 @@
|
||||
.ar-TaskViewer {
|
||||
min-width: 20rem;
|
||||
background-color: var(--ar-bg-base);
|
||||
header {
|
||||
height: unset;
|
||||
}
|
||||
.ar-TaskViewer__close-button {
|
||||
cursor: pointer;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
8
src/app/components/TaskViewer/TaskViewer.test.ts
Executable file
8
src/app/components/TaskViewer/TaskViewer.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import TaskViewer from "./TaskViewer"
|
||||
|
||||
describe("TaskViewer", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
252
src/app/components/TaskViewer/TaskViewer.tsx
Executable file
252
src/app/components/TaskViewer/TaskViewer.tsx
Executable file
@@ -0,0 +1,252 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { getUser, notify } from "../../store"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
CronTab,
|
||||
DatePicker,
|
||||
Dropdown,
|
||||
LoadableIcon,
|
||||
TextArea,
|
||||
TextInput,
|
||||
} from ".."
|
||||
import { TaskViewerProps } from "../../types/components.interface"
|
||||
import {
|
||||
ArAlertType,
|
||||
ArButtonVariants,
|
||||
ArSizes,
|
||||
JobStatus,
|
||||
} from "../../types/enums"
|
||||
import { CronType, Task, User } from "../../types/entity.interface"
|
||||
import { ObjectType } from "../../types/types"
|
||||
import { Helper, Network, Validator } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "./TaskViewer.component.scss"
|
||||
|
||||
const emptyTask = (user?: User) => ({
|
||||
owner: user?.email || "",
|
||||
schedule: { cron: "" },
|
||||
target: { endpoint: "" },
|
||||
name: "",
|
||||
status: JobStatus.STOPPED,
|
||||
})
|
||||
|
||||
const TaskViewer = (props: TaskViewerProps): JSX.Element => {
|
||||
const { classes, hideSelf, isNew, selectedTask, taskFetcher } = props
|
||||
const user = useAppSelector<User | undefined>(getUser)
|
||||
const [formData, setFormData] = useState<
|
||||
| {
|
||||
[key: string]: string | number | boolean | CronType
|
||||
}
|
||||
| Task
|
||||
>({})
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTask) {
|
||||
setFormData(selectedTask)
|
||||
}
|
||||
}, [selectedTask])
|
||||
|
||||
const change = (field: string, value: string | number | boolean) => {
|
||||
setFormData({ ...formData, [field]: value })
|
||||
}
|
||||
|
||||
const submitTaskDetails = () => {
|
||||
const payload: Task = formData
|
||||
? (JSON.parse(JSON.stringify(formData)) as Task)
|
||||
: emptyTask(user)
|
||||
let cron: CronType | string = formData.cron as CronType
|
||||
if (cron) {
|
||||
if (!payload.schedule) {
|
||||
payload.schedule = { cron: "" }
|
||||
}
|
||||
payload.schedule.cron =
|
||||
(cron.min || "*") +
|
||||
(cron.hour || "*") +
|
||||
(cron.day || "*") +
|
||||
(cron.week || "*") +
|
||||
(cron.month || "*")
|
||||
}
|
||||
payload.name = formData.name as string
|
||||
payload.client = formData.client as string
|
||||
payload.description = formData.description as string
|
||||
payload.target = {
|
||||
endpoint: (typeof formData.target === "string"
|
||||
? formData.target
|
||||
: (formData as Task).target.endpoint) as string,
|
||||
}
|
||||
const operation = isNew ? Network.post : Network.put
|
||||
if (Validator.validateTask(payload)) {
|
||||
operation(
|
||||
API_CONFIG.TASKER[process.env.NODE_ENV] +
|
||||
ENDPOINTS.TASKER.ROOT +
|
||||
ENDPOINTS.TASKER.SAVE,
|
||||
{ job: payload },
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === (isNew ? 201 : 200)) {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: `${
|
||||
isNew ? "Registered" : "Updated"
|
||||
} cron task successfully`,
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
taskFetcher()
|
||||
hideSelf()
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e.status === 409) {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Job with give name already exists!",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Failed to register the task",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Please review and fix the task details",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-TaskViewer p-3 overflow-auto h-100 position-relative${
|
||||
classes ? " " + classes : ""
|
||||
}`}
|
||||
>
|
||||
<LoadableIcon
|
||||
classes="ar-TaskViewer__close-button position-absolute"
|
||||
icon="io5/IoClose"
|
||||
onClick={hideSelf}
|
||||
/>
|
||||
<header className="mb-3 fw-bold h6">Task Details</header>
|
||||
<Card>
|
||||
<TextInput
|
||||
classes="mb-3 w-100"
|
||||
label="Name"
|
||||
onChange={(e) => change("name", e.target.value)}
|
||||
value={formData.name as string}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
classes="mb-3 w-100"
|
||||
label="Client"
|
||||
onChange={(e) => change("client", e.target.value)}
|
||||
value={formData.client as string}
|
||||
required
|
||||
/>
|
||||
<TextArea
|
||||
classes="mb-3"
|
||||
label="Description"
|
||||
onChange={(e: ChangeEvent<unknown>) =>
|
||||
change("description", (e.target as HTMLTextAreaElement).value)
|
||||
}
|
||||
value={formData.description as string}
|
||||
/>
|
||||
<TextInput
|
||||
classes="w-100"
|
||||
label="Target"
|
||||
type="url"
|
||||
onChange={(e) => change("target", e.target.value)}
|
||||
required
|
||||
value={
|
||||
formData.target
|
||||
? ((typeof formData.target === "string"
|
||||
? formData.target
|
||||
: (formData as Task).target.endpoint) as string)
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
<div className="my-3 border" />
|
||||
<Card classes="mb-3">
|
||||
<strong className="d-inline-block mb-3">
|
||||
Provide scheduling details
|
||||
</strong>
|
||||
{/* <Date classes="mb-3 w-100" isRangeSelector /> */}
|
||||
<div className="flex-v-center mb-3">
|
||||
<span className="me-3 fw-bold">Every</span>
|
||||
<div className="d-inline-block me-3">
|
||||
<TextInput
|
||||
type="number"
|
||||
min={1}
|
||||
onChange={(e) => change("frequency", e.target.value)}
|
||||
value={formData.frequency as string}
|
||||
/>
|
||||
</div>
|
||||
<div className="d-inline-block">
|
||||
<Dropdown
|
||||
options={[
|
||||
{ label: "minutes" },
|
||||
{ label: "hours" },
|
||||
{ label: "days" },
|
||||
{ label: "weeks" },
|
||||
{ label: "months" },
|
||||
]}
|
||||
onSelectionChanged={(input: {
|
||||
value: string
|
||||
data: ObjectType
|
||||
}) => change("name", input.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-v-center mb-3">
|
||||
<span className="me-3 fw-bold">Starting</span>
|
||||
<DatePicker />
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<strong className="d-inline-block mb-3">
|
||||
Or provide a crontab in the format * * * * 3 * (takes precedence)
|
||||
</strong>
|
||||
{/* <TextInput classes="w-100" label="CronTab" /> */}
|
||||
<CronTab
|
||||
onChange={(cronState) => change("cron", cronState)}
|
||||
value={formData.schedule as { cron: string }}
|
||||
/>
|
||||
</Card>
|
||||
<div className="my-3 border" />
|
||||
<div className="d-flex justify-content-between">
|
||||
<Checkbox label="Start Immediately?" onChange={() => {}} />
|
||||
<Button
|
||||
classes="float-end"
|
||||
content="Submit"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
size={ArSizes.SMALL}
|
||||
onClick={submitTaskDetails}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TaskViewer
|
||||
3
src/app/components/TaskViewer/index.ts
Executable file
3
src/app/components/TaskViewer/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import TaskViewer from "./TaskViewer"
|
||||
|
||||
export default TaskViewer
|
||||
@@ -1,5 +1,5 @@
|
||||
.ar-Alert {
|
||||
width: 15rem;
|
||||
width: 20rem;
|
||||
transition: all 0.3s;
|
||||
border-radius: 0.2rem;
|
||||
opacity: 0;
|
||||
@@ -78,7 +78,7 @@
|
||||
max-width: calc(100% - 2rem);
|
||||
overflow: auto;
|
||||
line-height: 1rem;
|
||||
font-size: 0.85rem;
|
||||
// font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.ar-Alert__close-icon {
|
||||
|
||||
@@ -17,9 +17,23 @@ const Alert = (props: AlertProps): JSX.Element => {
|
||||
theme,
|
||||
type,
|
||||
timeout,
|
||||
uid,
|
||||
} = props
|
||||
const [showClass, show] = useState<string>()
|
||||
|
||||
// useEffect(() => {
|
||||
// if (externalShow || demo) {
|
||||
// setTimeout(() => show("show"), 0)
|
||||
// if (!demo || timeout) {
|
||||
// clearTimeout(timeoutRef)
|
||||
// timeoutRef = setTimeout(() => {
|
||||
// show("")
|
||||
// window.top?.postMessage({ ar: null }, "*")
|
||||
// }, timeout || 5000)
|
||||
// }
|
||||
// }
|
||||
// }, [position, type, timeout, externalShow, message, demo])
|
||||
|
||||
useEffect(() => {
|
||||
if (externalShow || demo) {
|
||||
setTimeout(() => show("show"), 0)
|
||||
@@ -31,7 +45,7 @@ const Alert = (props: AlertProps): JSX.Element => {
|
||||
}, timeout || 5000)
|
||||
}
|
||||
}
|
||||
}, [position, type, timeout, externalShow, message, demo])
|
||||
}, [demo, externalShow, timeout, uid])
|
||||
|
||||
let icon
|
||||
switch (type) {
|
||||
|
||||
3
src/app/components/atoms/ArViz/ArViz.component.scss
Executable file
3
src/app/components/atoms/ArViz/ArViz.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-ArViz {
|
||||
|
||||
}
|
||||
8
src/app/components/atoms/ArViz/ArViz.test.ts
Executable file
8
src/app/components/atoms/ArViz/ArViz.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ArViz from "./ArViz"
|
||||
|
||||
describe("ArViz", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
49
src/app/components/atoms/ArViz/ArViz.tsx
Executable file
49
src/app/components/atoms/ArViz/ArViz.tsx
Executable file
@@ -0,0 +1,49 @@
|
||||
import { useEffect, useRef } from "react"
|
||||
import * as d3 from "d3"
|
||||
import { ArVizProps } from "../../../types/components.interface"
|
||||
import { ArVisualizationTypes } from "../../../types/enums"
|
||||
import { generateBubbleChart } from "../../../utils/chartGenerators"
|
||||
import "./ArViz.component.scss"
|
||||
|
||||
const dataDummy = [
|
||||
{ source: "Item 1", x: 100, y: 60, val: 1350, color: "#C9D6DF" },
|
||||
{ source: "Item 2", x: 30, y: 80, val: 2500, color: "#F7EECF" },
|
||||
{ source: "Item 3", x: 50, y: 40, val: 5700, color: "#E3E1B2" },
|
||||
{ source: "Item 4", x: 190, y: 100, val: 30000, color: "#F9CAC8" },
|
||||
{ source: "Item 5", x: 80, y: 170, val: 47500, color: "#D1C2E0" },
|
||||
]
|
||||
|
||||
const ArViz = (props: ArVizProps): JSX.Element => {
|
||||
const { type, data, demo } = props
|
||||
const svgContainerRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (data && svgContainerRef.current) {
|
||||
switch (type) {
|
||||
case ArVisualizationTypes.BUBBLE:
|
||||
// generateBubbleChart(data || (demo && dataDummy))
|
||||
generateBubbleChart(dataDummy)
|
||||
}
|
||||
const svg = svgContainerRef.current as SVGSVGElement
|
||||
const boundingBox = svg.getBBox()
|
||||
const svgToUpdate = d3.select("#ar-ArViz__chart-container")
|
||||
svgToUpdate.attr(
|
||||
"viewBox",
|
||||
`${boundingBox.x} ${boundingBox.y} ${boundingBox.width} ${boundingBox.height}`,
|
||||
)
|
||||
}
|
||||
}, [svgContainerRef, data])
|
||||
|
||||
return (
|
||||
<div className="ar-ArViz h-100 w-100 overflow-auto p-3">
|
||||
<svg
|
||||
id="ar-ArViz__chart-container"
|
||||
className="h-100 w-100"
|
||||
ref={svgContainerRef}
|
||||
viewBox=""
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArViz
|
||||
3
src/app/components/atoms/ArViz/index.ts
Executable file
3
src/app/components/atoms/ArViz/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ArViz from "./ArViz"
|
||||
|
||||
export default ArViz
|
||||
@@ -1,17 +1,10 @@
|
||||
import { Icon } from "../.."
|
||||
import { ICON_ROOT } from "../../../config/constants"
|
||||
import { BadgeProps } from "../../../types/components.interface"
|
||||
import "./Badge.component.scss"
|
||||
|
||||
interface BadgeProps {
|
||||
icon?: string
|
||||
size?: string
|
||||
shadow?: boolean
|
||||
text?: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
const Badge = (props: BadgeProps): JSX.Element => {
|
||||
const { icon, size, shadow, text, type } = props
|
||||
const { classes, icon, size, shadow, text, type } = props
|
||||
if (icon?.indexOf("/") === -1) {
|
||||
console.warn(
|
||||
"Not sufficient information to render icon, needed format <icon_family>/<icon_name>",
|
||||
@@ -21,7 +14,7 @@ const Badge = (props: BadgeProps): JSX.Element => {
|
||||
<span
|
||||
className={`ar-Badge${size ? " " + size : " medium"}${
|
||||
type ? " " + type : " inprogress"
|
||||
}${shadow ? " shadow" : ""}`}
|
||||
}${shadow ? " shadow" : ""}${classes ? " " + classes : ""}`}
|
||||
>
|
||||
{icon && size !== "small" && <Icon src={`${ICON_ROOT}/${icon}`} />}
|
||||
{size !== "small" && (text || "+2")}
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
import { MouseEventHandler } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import {
|
||||
BreadCrumbData,
|
||||
BreadcrumbProps,
|
||||
} from "../../../types/components.interface"
|
||||
import "./Breadcrumb.component.scss"
|
||||
|
||||
interface BaseBreadCrumbData {
|
||||
label: string
|
||||
data?: any
|
||||
}
|
||||
interface BreadcrumbDataWithClick extends BaseBreadCrumbData {
|
||||
onClick: Function
|
||||
}
|
||||
|
||||
interface BreadcrumbDataWithRoute extends BaseBreadCrumbData {
|
||||
route: string
|
||||
}
|
||||
|
||||
type BreadCrumbData = BreadcrumbDataWithClick | BreadcrumbDataWithRoute
|
||||
|
||||
interface BreadcrumbProps {
|
||||
data: Array<BreadCrumbData>
|
||||
separator?: string
|
||||
onClick?: Function
|
||||
}
|
||||
import Button from "../Button"
|
||||
import { ArButtonVariants, ArSizes } from "../../../types/enums"
|
||||
|
||||
const Breadcrumb = (props: BreadcrumbProps): JSX.Element => {
|
||||
const { data, separator, onClick } = props
|
||||
const { classes, data, separator, size, onClick } = props
|
||||
const navigate = useNavigate()
|
||||
|
||||
const demoDummy = [
|
||||
@@ -40,11 +25,14 @@ const Breadcrumb = (props: BreadcrumbProps): JSX.Element => {
|
||||
const nodes = data || demoDummy
|
||||
|
||||
return (
|
||||
<div className="ar-Breadcrumb">
|
||||
<div className={`ar-Breadcrumb${classes ? " " + classes : ""}`}>
|
||||
{nodes.map((node: BreadCrumbData, index: number) => (
|
||||
<>
|
||||
<span
|
||||
className="btn btn-link p-2"
|
||||
<Button
|
||||
classes="d-inline-block"
|
||||
content={node.label}
|
||||
size={size || ArSizes.SMALL}
|
||||
variant={ArButtonVariants.LINKNATIVE}
|
||||
onClick={
|
||||
"onClick" in node
|
||||
? (e) => node.onClick(e, node.data)
|
||||
@@ -52,9 +40,7 @@ const Breadcrumb = (props: BreadcrumbProps): JSX.Element => {
|
||||
? (e) => onClick(e, node.data)
|
||||
: () => navigate("route" in node ? node.route : "")
|
||||
}
|
||||
>
|
||||
{node.label}
|
||||
</span>
|
||||
/>
|
||||
{index < nodes.length - 1 && (separator || "/")}
|
||||
</>
|
||||
))}
|
||||
|
||||
3
src/app/components/atoms/BubbleViz/BubbleViz.component.scss
Executable file
3
src/app/components/atoms/BubbleViz/BubbleViz.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-BubbleViz {
|
||||
|
||||
}
|
||||
8
src/app/components/atoms/BubbleViz/BubbleViz.test.ts
Executable file
8
src/app/components/atoms/BubbleViz/BubbleViz.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import BubbleViz from "./BubbleViz"
|
||||
|
||||
describe("BubbleViz", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
21
src/app/components/atoms/BubbleViz/BubbleViz.tsx
Executable file
21
src/app/components/atoms/BubbleViz/BubbleViz.tsx
Executable file
@@ -0,0 +1,21 @@
|
||||
import { useEffect } from "react"
|
||||
import d3 from "d3"
|
||||
import { BubbleVizProps } from "../../../types/components.interface"
|
||||
import "./BubbleViz.component.scss"
|
||||
|
||||
const data = [
|
||||
{ source: "Item 1", x: 100, y: 60, val: 1350, color: "#C9D6DF" },
|
||||
{ source: "Item 2", x: 30, y: 80, val: 2500, color: "#F7EECF" },
|
||||
{ source: "Item 3", x: 50, y: 40, val: 5700, color: "#E3E1B2" },
|
||||
{ source: "Item 4", x: 190, y: 100, val: 30000, color: "#F9CAC8" },
|
||||
{ source: "Item 5", x: 80, y: 170, val: 47500, color: "#D1C2E0" },
|
||||
]
|
||||
|
||||
const BubbleViz = (props: BubbleVizProps): JSX.Element => {
|
||||
// const { classes, data } = props
|
||||
const { classes } = props
|
||||
|
||||
return <div className={`ar-BubbleViz${classes ? " " + classes : ""}`}></div>
|
||||
}
|
||||
|
||||
export default BubbleViz
|
||||
3
src/app/components/atoms/BubbleViz/index.ts
Executable file
3
src/app/components/atoms/BubbleViz/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import BubbleViz from "./BubbleViz"
|
||||
|
||||
export default BubbleViz
|
||||
@@ -48,6 +48,59 @@
|
||||
|
||||
}
|
||||
|
||||
&.has-split-button {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.ar-Button__split-button {
|
||||
transition: all 0.5s;
|
||||
width: 2rem;
|
||||
&.ar-xsmall {
|
||||
width: 2rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&.ar-small {
|
||||
width: 2.275rem;
|
||||
padding: 0.3rem 0.7rem;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
&.ar-large {
|
||||
width: 13rem;
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 0.3rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&+.ar-Popover {
|
||||
.ar-Button__split-button {
|
||||
width: 2rem;
|
||||
transition: all 0.5s;
|
||||
border-top-right-radius: 0.2rem;
|
||||
border-bottom-right-radius: 0.2rem;
|
||||
background-color: var(--ar-color-primary-faded);
|
||||
border: 1px solid var(--ar-color-primary);
|
||||
&.ar-link {
|
||||
width: auto;
|
||||
font-size: 0.8rem;
|
||||
background-color: transparent;
|
||||
color: var(--ar-color-font-link);
|
||||
border: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.ar-secondary {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-color-primary);
|
||||
@@ -111,4 +164,39 @@
|
||||
background-color: var(--ar-color-font-light);
|
||||
}
|
||||
}
|
||||
|
||||
&.ar-link {
|
||||
width: auto;
|
||||
font-size: 0.8rem;
|
||||
background-color: transparent;
|
||||
color: var(--ar-color-font);
|
||||
border: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.ar-link-hover {
|
||||
width: auto;
|
||||
font-size: 0.8rem;
|
||||
background-color: transparent;
|
||||
color: var(--ar-color-font);
|
||||
border: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
&.ar-link-native {
|
||||
width: auto;
|
||||
font-size: 0.7rem;
|
||||
background-color: transparent;
|
||||
color: var(--ar-color-font-link);
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,87 @@
|
||||
import Icon from "../Icon"
|
||||
import { List, Popover, LoadableIcon } from "../.."
|
||||
import { ButtonProps } from "../../../types/components.interface"
|
||||
import { ICON_ROOT } from "../../../config/constants"
|
||||
import {
|
||||
ArButtonTypes,
|
||||
ArButtonVariants,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
} from "../../../types/enums"
|
||||
import "./Button.component.scss"
|
||||
|
||||
const Button = (props: ButtonProps) => {
|
||||
const { classes, content, icon, onClick, size, variant, ...rest } = props
|
||||
const {
|
||||
classes,
|
||||
color,
|
||||
content,
|
||||
preIcon,
|
||||
postIcon,
|
||||
onClick,
|
||||
size,
|
||||
splitOptions,
|
||||
splitTrigger,
|
||||
type = ArButtonTypes.BUTTON,
|
||||
variant,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
let setColor = ""
|
||||
const btnStyles: { color?: string } = {}
|
||||
if (color) {
|
||||
setColor = color
|
||||
btnStyles.color = color
|
||||
} else {
|
||||
if (variant === ArButtonVariants.LINKNATIVE) {
|
||||
setColor = "#0d6efd"
|
||||
} else if (
|
||||
variant === ArButtonVariants.LINK ||
|
||||
variant === ArButtonVariants.LINKHOVEREFFECT
|
||||
) {
|
||||
setColor = "black"
|
||||
} else {
|
||||
setColor = "white"
|
||||
}
|
||||
}
|
||||
return (
|
||||
<button
|
||||
className={`ar-Button fw-bold${classes ? " " + classes : ""}${
|
||||
variant ? " ar-" + variant : ""
|
||||
}${size ? " ar-" + size : ""}`}
|
||||
onClick={onClick}
|
||||
{...rest}
|
||||
>
|
||||
{icon && (
|
||||
<span className="ar-Button__icon me-3">
|
||||
<Icon src={`${ICON_ROOT}/${icon}/white`} />
|
||||
</span>
|
||||
<>
|
||||
<button
|
||||
type={type}
|
||||
className={`ar-Button fw-bold flex-center${
|
||||
classes ? " " + classes : ""
|
||||
}${variant ? " ar-" + variant : ""}${size ? " ar-" + size : ""}${
|
||||
splitOptions ? " has-split-button" : ""
|
||||
}`}
|
||||
onClick={onClick}
|
||||
style={btnStyles}
|
||||
{...rest}
|
||||
>
|
||||
{preIcon && (
|
||||
<span className="ar-Button__icon me-2">
|
||||
<LoadableIcon icon={preIcon} color={setColor} />
|
||||
</span>
|
||||
)}
|
||||
{content || (!preIcon && !postIcon ? "Button" : "")}
|
||||
{postIcon && (
|
||||
<span className="ar-Button__icon ms-2">
|
||||
<LoadableIcon icon={postIcon} color={setColor} />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{splitOptions && (
|
||||
<Popover trigger={splitTrigger || ArPopoverTriggers.CLICK}>
|
||||
<button
|
||||
className={`px-1 py-2 ar-Button__split-button${
|
||||
variant ? " ar-" + variant : ""
|
||||
}${size ? " ar-" + size : ""}`}
|
||||
type="button"
|
||||
title="Extra Button Options"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
>
|
||||
<LoadableIcon icon="io/IoIosArrowDown" />
|
||||
</button>
|
||||
<List data={splitOptions} slot={ArPopoverSlots.POPOVER} />
|
||||
</Popover>
|
||||
)}
|
||||
{content || "Button"}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,7 @@ import "./Calendar.component.scss"
|
||||
interface CalendarProps {}
|
||||
|
||||
const Calendar = (props: any): JSX.Element => {
|
||||
return (
|
||||
<div className="ar-Calendar">
|
||||
In Component Calendar
|
||||
</div>
|
||||
)
|
||||
return <div className="ar-Calendar">In Component Calendar</div>
|
||||
}
|
||||
|
||||
export default Calendar
|
||||
export default Calendar
|
||||
|
||||
@@ -17,9 +17,13 @@ const Checkbox = (props: CheckboxProps): JSX.Element => {
|
||||
return type === "TOGGLE" ? (
|
||||
<Toggle {...toggleProps} />
|
||||
) : (
|
||||
<div className="ar-Checkbox">
|
||||
{label && <label htmlFor={id || "ar-checkbox-1"}>{label}</label>}
|
||||
<span className="ar-Checkbox__state me-2">{checked ? "On" : "Off"}</span>
|
||||
<div className="ar-Checkbox flex-v-center">
|
||||
{label && (
|
||||
<label className="fw-bold me-2" htmlFor={id || "ar-checkbox-1"}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
{/* <span className="ar-Checkbox__state me-2">{checked ? "On" : "Off"}</span> */}
|
||||
<input
|
||||
id={id || "ar-checkbox-1"}
|
||||
type={type || "checkbox"}
|
||||
|
||||
38
src/app/components/atoms/ColorSelector/ColorSelector.component.scss
Executable file
38
src/app/components/atoms/ColorSelector/ColorSelector.component.scss
Executable file
@@ -0,0 +1,38 @@
|
||||
.ar-ColorSelector {
|
||||
width: 2rem;
|
||||
|
||||
.ar-ColorSelector__item {
|
||||
flex: 1;
|
||||
min-height: 1.5rem;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
|
||||
background-size: 400% 400%;
|
||||
animation: gradient 15s ease infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.ar-ColorSelector__picker {
|
||||
flex: 1;
|
||||
min-height: 1.5rem;
|
||||
transition: all 0.3s;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
|
||||
@keyframes gradient {
|
||||
0% {background-position: 0% 50%;}
|
||||
50% {background-position: 100% 50%;}
|
||||
100% {background-position: 0% 50%;}
|
||||
}
|
||||
}
|
||||
8
src/app/components/atoms/ColorSelector/ColorSelector.test.ts
Executable file
8
src/app/components/atoms/ColorSelector/ColorSelector.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ColorSelector from "./ColorSelector"
|
||||
|
||||
describe("ColorSelector", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
62
src/app/components/atoms/ColorSelector/ColorSelector.tsx
Executable file
62
src/app/components/atoms/ColorSelector/ColorSelector.tsx
Executable file
@@ -0,0 +1,62 @@
|
||||
import { useState } from "react"
|
||||
import { ColorSelectorProps } from "../../../types/components.interface"
|
||||
import "./ColorSelector.component.scss"
|
||||
import TextInput from "../TextInput"
|
||||
|
||||
const ColorSelector = (props: ColorSelectorProps): JSX.Element => {
|
||||
const { classes, label, onChange, withSample } = props
|
||||
const [iconColor, setIconColor] = useState<string>()
|
||||
|
||||
const onColorChange = (color: string) => {
|
||||
setIconColor(color)
|
||||
onChange && onChange(color)
|
||||
}
|
||||
const selectors = [
|
||||
"#9400D3",
|
||||
"pink",
|
||||
"aquamarine",
|
||||
"olive",
|
||||
"brown",
|
||||
"lightblue",
|
||||
"#4B0082",
|
||||
"#0000FF",
|
||||
"#00FF00",
|
||||
"#FFFF00",
|
||||
"#FF7F00",
|
||||
"#FF0000",
|
||||
].map((color: string) => {
|
||||
return (
|
||||
<li
|
||||
className="ar-ColorSelector__item"
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => onColorChange(color)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div className="ar-ColorSelector w-100">
|
||||
{label && <div className="mb-3 fw-bold text-center">{label}</div>}
|
||||
<ul
|
||||
className={`ar-ColorSelector__list d-flex flex-column list-unstyled${
|
||||
classes ? " " + classes : ""
|
||||
}`}
|
||||
style={{ height: label ? "calc(100% - 2rem)" : "100%" }}
|
||||
>
|
||||
{selectors}
|
||||
<TextInput
|
||||
classes="ar-ColorSelector__picker"
|
||||
type="color"
|
||||
onChange={(e) => onColorChange(e.target.value)}
|
||||
/>
|
||||
</ul>
|
||||
{withSample && (
|
||||
<div
|
||||
className="ar-ColorSelector__sample"
|
||||
style={{ backgroundColor: iconColor, width: "1rem", height: "1rem" }}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColorSelector
|
||||
3
src/app/components/atoms/ColorSelector/index.ts
Executable file
3
src/app/components/atoms/ColorSelector/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ColorSelector from "./ColorSelector"
|
||||
|
||||
export default ColorSelector
|
||||
55
src/app/components/atoms/CronTab/CronTab.component.scss
Executable file
55
src/app/components/atoms/CronTab/CronTab.component.scss
Executable file
@@ -0,0 +1,55 @@
|
||||
.ar-CronTab {
|
||||
.ar-CronTab__input {
|
||||
width: 2rem;
|
||||
font-size: 1.5rem;
|
||||
input {
|
||||
outline: none;
|
||||
border: none;
|
||||
text-align: center;
|
||||
&::placeholder {
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
color: lightgrey;
|
||||
}
|
||||
|
||||
&::-ms-input-placeholder {
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
color: lightgrey;
|
||||
}
|
||||
|
||||
&.form-component {
|
||||
border: none;
|
||||
&:focus-visible {
|
||||
border-bottom: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ar-CronTab__help-text {
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--ar-bg-mild);
|
||||
|
||||
header {
|
||||
height: auto;
|
||||
}
|
||||
section {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
&.expanded {
|
||||
max-height: 30vh;
|
||||
}
|
||||
}
|
||||
ol {
|
||||
padding-left: 0.75rem;
|
||||
li {
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/app/components/atoms/CronTab/CronTab.test.ts
Executable file
8
src/app/components/atoms/CronTab/CronTab.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import CronTab from "./CronTab"
|
||||
|
||||
describe("CronTab", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
92
src/app/components/atoms/CronTab/CronTab.tsx
Executable file
92
src/app/components/atoms/CronTab/CronTab.tsx
Executable file
@@ -0,0 +1,92 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { CronTabProps } from "../../../types/components.interface"
|
||||
import TextInput from "../TextInput"
|
||||
import LoadableIcon from "../LoadableIcon"
|
||||
import "./CronTab.component.scss"
|
||||
|
||||
const cronFields = ["min", "hour", "day", "week", "month"]
|
||||
const CronTab = (props: CronTabProps): JSX.Element => {
|
||||
const { onChange, value } = props
|
||||
const [cronTab, setCronTab] = useState<{ [key: string]: string }>({})
|
||||
const [helpExpanded, toggleHelpExpanded] = useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
const actualValue = typeof value === "object" ? value.cron : value
|
||||
const values = (actualValue as string).split("")
|
||||
const cronTabState: { [key: string]: string } = {}
|
||||
values.forEach((value: string, index) => {
|
||||
cronTabState[cronFields[index]] = value
|
||||
})
|
||||
setCronTab(cronTabState)
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const handleChange = (e: ChangeEvent, type: string) => {
|
||||
const currentValue = cronTab[type]
|
||||
const newValue = (e.target as HTMLInputElement).value
|
||||
const lastChar = newValue.charAt(newValue.length - 1)
|
||||
if (newValue.length === 1 || currentValue !== lastChar) {
|
||||
const newCronState = { ...cronTab, [type]: lastChar || "" }
|
||||
setCronTab(newCronState)
|
||||
onChange && onChange(newCronState)
|
||||
}
|
||||
}
|
||||
|
||||
const cronFieldRenders = cronFields.map((cronField, index) => {
|
||||
return (
|
||||
<TextInput
|
||||
key={"crontabtextinput-" + index}
|
||||
placeholder="* / n"
|
||||
containerClasses="ar-CronTab__input"
|
||||
onChange={(e: ChangeEvent) => handleChange(e, cronField)}
|
||||
value={cronTab[cronField]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div className="ar-CronTab w-100">
|
||||
<div className="ar-CronTab__fields w-100 d-flex border mb-3">
|
||||
{cronFieldRenders}
|
||||
</div>
|
||||
<div className="ar-CronTab__help-text border">
|
||||
<header
|
||||
className="p-2 border-bottom d-flex justify-content-between cursor-pointer"
|
||||
onClick={() => toggleHelpExpanded(!helpExpanded)}
|
||||
>
|
||||
<strong>Examples</strong>
|
||||
<LoadableIcon
|
||||
classes="ar-CronTab__acc-icon"
|
||||
icon={
|
||||
helpExpanded ? "md/MdKeyboardArrowUp" : "md/MdKeyboardArrowDown"
|
||||
}
|
||||
/>
|
||||
</header>
|
||||
<section className={helpExpanded ? " expanded p-2" : ""}>
|
||||
<small>
|
||||
<ol className="mb-0">
|
||||
<li>
|
||||
<strong>0 0 * * *</strong>: Every day at midnight.
|
||||
</li>
|
||||
<li>
|
||||
<strong>0 * * * *</strong>: Every hour.
|
||||
</li>
|
||||
<li>
|
||||
<strong>0 9 * * </strong>1-5: Every weekday at 9 AM.
|
||||
</li>
|
||||
<li>
|
||||
<strong>*/15 * * </strong>* *: Every 15 minutes.
|
||||
</li>
|
||||
<li>
|
||||
<strong>0 0 1 * *</strong>: On the 1st day of every month at
|
||||
midnight.
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CronTab
|
||||
3
src/app/components/atoms/CronTab/index.ts
Executable file
3
src/app/components/atoms/CronTab/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import CronTab from "./CronTab"
|
||||
|
||||
export default CronTab
|
||||
24
src/app/components/atoms/Date/CalTime.tsx
Normal file
24
src/app/components/atoms/Date/CalTime.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from "react"
|
||||
import Calendar from "./Calendar" // Import your Calendar component
|
||||
import TimePicker from "./TimePicker" // Import your TimePicker component
|
||||
|
||||
interface CalTimeProps {
|
||||
month: number
|
||||
showTimePicker?: boolean
|
||||
year: number
|
||||
// Add other props as needed
|
||||
}
|
||||
|
||||
const CalTime: React.FC<CalTimeProps> = ({ showTimePicker, ...otherProps }) => {
|
||||
return (
|
||||
<div className="ar-CalTime">
|
||||
{/* Render the Calendar component */}
|
||||
<Calendar {...otherProps} />
|
||||
|
||||
{/* Render the TimePicker component if showTimePicker is true */}
|
||||
{showTimePicker && <TimePicker />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CalTime
|
||||
103
src/app/components/atoms/Date/Calendar.tsx
Normal file
103
src/app/components/atoms/Date/Calendar.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React from "react"
|
||||
import moment from "moment"
|
||||
|
||||
interface CalendarProps {
|
||||
year: number
|
||||
month: number
|
||||
}
|
||||
|
||||
const Calendar: React.FC<CalendarProps> = ({ year, month }) => {
|
||||
const firstDayOfMonth = moment(`${year}-${month}-01`)
|
||||
const lastDayOfMonth = firstDayOfMonth.clone().endOf("month")
|
||||
const firstDayOfWeek = firstDayOfMonth.day()
|
||||
const numberOfDays = lastDayOfMonth.date()
|
||||
|
||||
interface Weekday {
|
||||
abbr: string
|
||||
ariaLabel: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const getWeekdayNames = (): Weekday[] => {
|
||||
const weekdays = moment.weekdaysShort()
|
||||
return weekdays.map((weekday) => ({
|
||||
abbr: weekday,
|
||||
ariaLabel: `Select ${weekday}`,
|
||||
value: weekday.substr(0, 2),
|
||||
}))
|
||||
}
|
||||
|
||||
const renderCalendarGrid = () => {
|
||||
const daysArray = Array.from(
|
||||
{ length: numberOfDays },
|
||||
(_, index) => index + 1,
|
||||
)
|
||||
const weekdays = getWeekdayNames()
|
||||
|
||||
return (
|
||||
<table
|
||||
className="ar-Calendar w-100 h-100"
|
||||
role="grid"
|
||||
aria-labelledby="calendar-heading"
|
||||
>
|
||||
<caption id="calendar-heading">
|
||||
{firstDayOfMonth.format("MMMM YYYY")}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
{weekdays.map((weekday) => (
|
||||
<th
|
||||
key={weekday.abbr}
|
||||
abbr={weekday.abbr}
|
||||
role="columnheader"
|
||||
aria-label={weekday.ariaLabel}
|
||||
>
|
||||
{weekday.value}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.from(
|
||||
{ length: Math.ceil((numberOfDays + firstDayOfWeek) / 7) },
|
||||
(_, row) => (
|
||||
<tr key={row}>
|
||||
{Array.from({ length: 7 }, (_, col) => {
|
||||
const day = row * 7 + col + 1 - firstDayOfWeek
|
||||
return day > 0 && day <= numberOfDays ? (
|
||||
<td
|
||||
key={day}
|
||||
role="gridcell"
|
||||
className="calendar-day cursor-pointer"
|
||||
aria-label={firstDayOfMonth
|
||||
.date(day)
|
||||
.format("D MMMM YYYY")}
|
||||
onClick={() => handleDayClick(day)}
|
||||
>
|
||||
{day}
|
||||
</td>
|
||||
) : (
|
||||
<td
|
||||
key={`empty-${col}`}
|
||||
role="gridcell"
|
||||
aria-hidden="true"
|
||||
></td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
),
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
const handleDayClick = (day: number) => {
|
||||
// Handle day click event here
|
||||
console.log(`Selected day: ${day}`)
|
||||
}
|
||||
|
||||
return renderCalendarGrid()
|
||||
}
|
||||
|
||||
export default Calendar
|
||||
531
src/app/components/atoms/Date/Date.component.scss
Executable file
531
src/app/components/atoms/Date/Date.component.scss
Executable file
@@ -0,0 +1,531 @@
|
||||
$color_1: inherit;
|
||||
$color_2: #fff;
|
||||
$color_3: #ccc;
|
||||
$color_4: #999;
|
||||
$color_5: #000;
|
||||
$font-family_1: arial;
|
||||
$background-color_1: #fff;
|
||||
$background-color_2: #eee;
|
||||
$background-color_3: #ebf4f8;
|
||||
$background-color_4: #357ebd;
|
||||
$background-color_5: #08c;
|
||||
$border-color_1: transparent;
|
||||
$border-bottom-color_1: rgba(0, 0, 0, 0.2);
|
||||
|
||||
/* Larger Screen Styling */
|
||||
// .ar-DateRange {
|
||||
// // position: absolute;
|
||||
// color: $color_1;
|
||||
// background-color: $background-color_1;
|
||||
// border-radius: 4px;
|
||||
// border: 1px solid #ddd;
|
||||
// width: 278px;
|
||||
// max-width: none;
|
||||
// padding: 0;
|
||||
// margin-top: 7px;
|
||||
// top: 100px;
|
||||
// left: 20px;
|
||||
// z-index: 3001;
|
||||
// // display: none;
|
||||
// font-family: $font-family_1;
|
||||
// font-size: 15px;
|
||||
// line-height: 1em;
|
||||
// &:before {
|
||||
// position: absolute;
|
||||
// display: inline-block;
|
||||
// border-bottom-color: $border-bottom-color_1;
|
||||
// content: '';
|
||||
// top: -7px;
|
||||
// border-right: 7px solid transparent;
|
||||
// border-left: 7px solid transparent;
|
||||
// border-bottom: 7px solid #ccc;
|
||||
// }
|
||||
// &:after {
|
||||
// position: absolute;
|
||||
// display: inline-block;
|
||||
// border-bottom-color: $border-bottom-color_1;
|
||||
// content: '';
|
||||
// top: -6px;
|
||||
// border-right: 6px solid transparent;
|
||||
// border-bottom: 6px solid #fff;
|
||||
// border-left: 6px solid transparent;
|
||||
// }
|
||||
// .drp-calendar {
|
||||
// // display: none;
|
||||
// max-width: 270px;
|
||||
// }
|
||||
// .drp-calendar.left {
|
||||
// padding: 8px 0 8px 8px;
|
||||
// }
|
||||
// .drp-calendar.right {
|
||||
// padding: 8px;
|
||||
// }
|
||||
// .drp-calendar.single {
|
||||
// .calendar-table {
|
||||
// border: none;
|
||||
// }
|
||||
// }
|
||||
// .calendar-table {
|
||||
// .next {
|
||||
// span {
|
||||
// color: $color_2;
|
||||
// border: solid black;
|
||||
// border-width: 0 2px 2px 0;
|
||||
// border-radius: 0;
|
||||
// display: inline-block;
|
||||
// padding: 3px;
|
||||
// -webkit-transform: rotate(-45deg);
|
||||
// transform: rotate(-45deg);
|
||||
// }
|
||||
// }
|
||||
// .prev {
|
||||
// span {
|
||||
// color: $color_2;
|
||||
// border: solid black;
|
||||
// border-width: 0 2px 2px 0;
|
||||
// border-radius: 0;
|
||||
// display: inline-block;
|
||||
// padding: 3px;
|
||||
// -webkit-transform: rotate(135deg);
|
||||
// transform: rotate(135deg);
|
||||
// }
|
||||
// }
|
||||
// th {
|
||||
// white-space: nowrap;
|
||||
// text-align: center;
|
||||
// vertical-align: middle;
|
||||
// min-width: 32px;
|
||||
// width: 32px;
|
||||
// height: 24px;
|
||||
// line-height: 24px;
|
||||
// font-size: 12px;
|
||||
// border-radius: 4px;
|
||||
// border: 1px solid transparent;
|
||||
// white-space: nowrap;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
// td {
|
||||
// white-space: nowrap;
|
||||
// text-align: center;
|
||||
// vertical-align: middle;
|
||||
// min-width: 32px;
|
||||
// width: 32px;
|
||||
// height: 24px;
|
||||
// line-height: 24px;
|
||||
// font-size: 12px;
|
||||
// border-radius: 4px;
|
||||
// border: 1px solid transparent;
|
||||
// white-space: nowrap;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
// border: 1px solid #fff;
|
||||
// border-radius: 4px;
|
||||
// background-color: $background-color_1;
|
||||
// table {
|
||||
// width: 100%;
|
||||
// margin: 0;
|
||||
// border-spacing: 0;
|
||||
// border-collapse: collapse;
|
||||
// }
|
||||
// }
|
||||
// td.available {
|
||||
// &:hover {
|
||||
// background-color: $background-color_2;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_1;
|
||||
// }
|
||||
// }
|
||||
// th.available {
|
||||
// &:hover {
|
||||
// background-color: $background-color_2;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_1;
|
||||
// }
|
||||
// }
|
||||
// td.week {
|
||||
// font-size: 80%;
|
||||
// color: $color_3;
|
||||
// }
|
||||
// th.week {
|
||||
// font-size: 80%;
|
||||
// color: $color_3;
|
||||
// }
|
||||
// td.off {
|
||||
// background-color: $background-color_1;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_4;
|
||||
// }
|
||||
// td.off.in-range {
|
||||
// background-color: $background-color_1;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_4;
|
||||
// }
|
||||
// td.off.start-date {
|
||||
// background-color: $background-color_1;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_4;
|
||||
// }
|
||||
// td.off.end-date {
|
||||
// background-color: $background-color_1;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_4;
|
||||
// }
|
||||
// td.in-range {
|
||||
// background-color: $background-color_3;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_5;
|
||||
// border-radius: 0;
|
||||
// }
|
||||
// td.start-date {
|
||||
// border-radius: 4px 0 0 4px;
|
||||
// }
|
||||
// td.end-date {
|
||||
// border-radius: 0 4px 4px 0;
|
||||
// }
|
||||
// td.start-date.end-date {
|
||||
// border-radius: 4px;
|
||||
// }
|
||||
// td.active {
|
||||
// background-color: $background-color_4;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_2;
|
||||
// &:hover {
|
||||
// background-color: $background-color_4;
|
||||
// border-color: $border-color_1;
|
||||
// color: $color_2;
|
||||
// }
|
||||
// }
|
||||
// th.month {
|
||||
// width: auto;
|
||||
// }
|
||||
// td.disabled {
|
||||
// color: $color_4;
|
||||
// cursor: not-allowed;
|
||||
// text-decoration: line-through;
|
||||
// }
|
||||
// option.disabled {
|
||||
// color: $color_4;
|
||||
// cursor: not-allowed;
|
||||
// text-decoration: line-through;
|
||||
// }
|
||||
// select.monthselect {
|
||||
// font-size: 12px;
|
||||
// padding: 1px;
|
||||
// height: auto;
|
||||
// margin: 0;
|
||||
// cursor: default;
|
||||
// margin-right: 2%;
|
||||
// width: 56%;
|
||||
// }
|
||||
// select.yearselect {
|
||||
// font-size: 12px;
|
||||
// padding: 1px;
|
||||
// height: auto;
|
||||
// margin: 0;
|
||||
// cursor: default;
|
||||
// width: 40%;
|
||||
// }
|
||||
// select.hourselect {
|
||||
// width: 50px;
|
||||
// margin: 0 auto;
|
||||
// background: #eee;
|
||||
// border: 1px solid #eee;
|
||||
// padding: 2px;
|
||||
// outline: 0;
|
||||
// font-size: 12px;
|
||||
// }
|
||||
// select.minuteselect {
|
||||
// width: 50px;
|
||||
// margin: 0 auto;
|
||||
// background: #eee;
|
||||
// border: 1px solid #eee;
|
||||
// padding: 2px;
|
||||
// outline: 0;
|
||||
// font-size: 12px;
|
||||
// }
|
||||
// select.secondselect {
|
||||
// width: 50px;
|
||||
// margin: 0 auto;
|
||||
// background: #eee;
|
||||
// border: 1px solid #eee;
|
||||
// padding: 2px;
|
||||
// outline: 0;
|
||||
// font-size: 12px;
|
||||
// }
|
||||
// select.ampmselect {
|
||||
// width: 50px;
|
||||
// margin: 0 auto;
|
||||
// background: #eee;
|
||||
// border: 1px solid #eee;
|
||||
// padding: 2px;
|
||||
// outline: 0;
|
||||
// font-size: 12px;
|
||||
// }
|
||||
// .calendar-time {
|
||||
// text-align: center;
|
||||
// margin: 4px auto 0 auto;
|
||||
// line-height: 30px;
|
||||
// position: relative;
|
||||
// select.disabled {
|
||||
// color: $color_3;
|
||||
// cursor: not-allowed;
|
||||
// }
|
||||
// }
|
||||
// .drp-buttons {
|
||||
// clear: both;
|
||||
// text-align: right;
|
||||
// padding: 8px;
|
||||
// border-top: 1px solid #ddd;
|
||||
// // display: none;
|
||||
// line-height: 12px;
|
||||
// vertical-align: middle;
|
||||
// .btn {
|
||||
// margin-left: 8px;
|
||||
// font-size: 12px;
|
||||
// font-weight: bold;
|
||||
// padding: 4px 8px;
|
||||
// }
|
||||
// }
|
||||
// .drp-selected {
|
||||
// display: inline-block;
|
||||
// font-size: 12px;
|
||||
// padding-right: 8px;
|
||||
// }
|
||||
// .ranges {
|
||||
// float: none;
|
||||
// text-align: left;
|
||||
// margin: 0;
|
||||
// ul {
|
||||
// list-style: none;
|
||||
// margin: 0 auto;
|
||||
// padding: 0;
|
||||
// width: 100%;
|
||||
// }
|
||||
// li {
|
||||
// font-size: 12px;
|
||||
// padding: 8px 12px;
|
||||
// cursor: pointer;
|
||||
// &:hover {
|
||||
// background-color: $background-color_2;
|
||||
// }
|
||||
// }
|
||||
// li.active {
|
||||
// background-color: $background-color_5;
|
||||
// color: $color_2;
|
||||
// }
|
||||
// }
|
||||
// &.opensleft {
|
||||
// &:before {
|
||||
// right: 9px;
|
||||
// }
|
||||
// &:after {
|
||||
// right: 10px;
|
||||
// }
|
||||
// }
|
||||
// &.openscenter {
|
||||
// &:before {
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// width: 0;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
// }
|
||||
// &:after {
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// width: 0;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
// }
|
||||
// }
|
||||
// &.opensright {
|
||||
// &:before {
|
||||
// left: 9px;
|
||||
// }
|
||||
// &:after {
|
||||
// left: 10px;
|
||||
// }
|
||||
// }
|
||||
// &.drop-up {
|
||||
// margin-top: -7px;
|
||||
// &:before {
|
||||
// top: initial;
|
||||
// bottom: -7px;
|
||||
// border-bottom: initial;
|
||||
// border-top: 7px solid #ccc;
|
||||
// }
|
||||
// &:after {
|
||||
// top: initial;
|
||||
// bottom: -6px;
|
||||
// border-bottom: initial;
|
||||
// border-top: 6px solid #fff;
|
||||
// }
|
||||
// }
|
||||
// &.single {
|
||||
// .ar-DateRange {
|
||||
// .ranges {
|
||||
// float: none;
|
||||
// }
|
||||
// }
|
||||
// .drp-calendar {
|
||||
// float: none;
|
||||
// }
|
||||
// .drp-selected {
|
||||
// // display: none;
|
||||
// }
|
||||
// }
|
||||
// &.show-calendar {
|
||||
// .drp-calendar {
|
||||
// display: block;
|
||||
// }
|
||||
// .drp-buttons {
|
||||
// display: block;
|
||||
// }
|
||||
// .ranges {
|
||||
// margin-top: 8px;
|
||||
// }
|
||||
// }
|
||||
// &.auto-apply {
|
||||
// .drp-buttons {
|
||||
// // display: none;
|
||||
// }
|
||||
// }
|
||||
// &.show-ranges.single.rtl {
|
||||
// .drp-calendar.left {
|
||||
// border-right: 1px solid #ddd;
|
||||
// }
|
||||
// }
|
||||
// &.show-ranges.single.ltr {
|
||||
// .drp-calendar.left {
|
||||
// border-left: 1px solid #ddd;
|
||||
// }
|
||||
// }
|
||||
// &.show-ranges.rtl {
|
||||
// .drp-calendar.right {
|
||||
// border-right: 1px solid #ddd;
|
||||
// }
|
||||
// }
|
||||
// &.show-ranges.ltr {
|
||||
// .drp-calendar.left {
|
||||
// border-left: 1px solid #ddd;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// @media (min-width: 564px) {
|
||||
// .ar-DateRange {
|
||||
// width: auto;
|
||||
// direction: ltr;
|
||||
// text-align: left;
|
||||
// .ranges {
|
||||
// ul {
|
||||
// width: 140px;
|
||||
// }
|
||||
// float: left;
|
||||
// }
|
||||
// .drp-calendar.left {
|
||||
// clear: left;
|
||||
// margin-right: 0;
|
||||
// .calendar-table {
|
||||
// border-right: none;
|
||||
// border-top-right-radius: 0;
|
||||
// border-bottom-right-radius: 0;
|
||||
// padding-right: 8px;
|
||||
// }
|
||||
// }
|
||||
// .drp-calendar.right {
|
||||
// margin-left: 0;
|
||||
// .calendar-table {
|
||||
// border-left: none;
|
||||
// border-top-left-radius: 0;
|
||||
// border-bottom-left-radius: 0;
|
||||
// }
|
||||
// }
|
||||
// .drp-calendar {
|
||||
// float: left;
|
||||
// }
|
||||
// &.single {
|
||||
// .ranges {
|
||||
// ul {
|
||||
// width: 100%;
|
||||
// }
|
||||
// float: left;
|
||||
// }
|
||||
// .drp-calendar.left {
|
||||
// clear: none;
|
||||
// }
|
||||
// .drp-calendar {
|
||||
// float: left;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// @media (min-width: 730px) {
|
||||
// .ar-DateRange {
|
||||
// .ranges {
|
||||
// width: auto;
|
||||
// float: left;
|
||||
// }
|
||||
// .drp-calendar.left {
|
||||
// clear: none !important;
|
||||
// }
|
||||
// &.rtl {
|
||||
// .ranges {
|
||||
// float: right;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
.ar-Date {
|
||||
/* Styles for the Date component */
|
||||
background-color: #fff; /* Background color for the Date component */
|
||||
padding: 16px; /* Padding for the Date component */
|
||||
border: 1px solid #ddd; /* Border for the Date component */
|
||||
font-size: 0.75rem;
|
||||
|
||||
&__header {
|
||||
/* Styles for the header section of the Date component */
|
||||
font-size: 1.25rem; /* Font size for the header */
|
||||
font-weight: bold; /* Bold font for the header */
|
||||
margin-bottom: 16px; /* Margin at the bottom of the header */
|
||||
}
|
||||
|
||||
/* Add more styles for child elements of Date component */
|
||||
}
|
||||
|
||||
/* RangeSelector component styles */
|
||||
.ar-RangeSelector {
|
||||
/* Styles for the RangeSelector component */
|
||||
/* Add RangeSelector-specific styles here */
|
||||
}
|
||||
|
||||
.ar-Calendar {
|
||||
|
||||
}
|
||||
|
||||
.ar-TimePicker {
|
||||
|
||||
}
|
||||
|
||||
.ar-Slider {
|
||||
|
||||
.ar-Slider__items-container {
|
||||
scroll-snap-type: x mandatory;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
.ar-Slider__item {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.ar-CalTime {
|
||||
width: 12rem;
|
||||
height: 10rem;
|
||||
/* Styles for the CalTime component within CalView */
|
||||
/* Add CalTime-specific styles here */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add more styles for other classes as needed */
|
||||
6
src/app/components/atoms/Date/Date.test.ts
Executable file
6
src/app/components/atoms/Date/Date.test.ts
Executable file
@@ -0,0 +1,6 @@
|
||||
import React from "react"
|
||||
import Date from "./Date"
|
||||
|
||||
describe("Date", () => {
|
||||
it("renders without error", () => {})
|
||||
})
|
||||
114
src/app/components/atoms/Date/Date.tsx
Executable file
114
src/app/components/atoms/Date/Date.tsx
Executable file
@@ -0,0 +1,114 @@
|
||||
import { useState } from "react"
|
||||
import { DateProps } from "../../../types/components.interface"
|
||||
import { ArButtonVariants, ArSizes } from "../../../types/enums"
|
||||
import RangeSelector from "./RangeSelector"
|
||||
import Slider from "./Slider"
|
||||
import Button from "../Button"
|
||||
import CalTime from "./CalTime"
|
||||
import "./Date.component.scss"
|
||||
|
||||
const Date: React.FC<DateProps> = (props) => {
|
||||
const {
|
||||
minDate,
|
||||
maxDate,
|
||||
startDate,
|
||||
endDate,
|
||||
isRangeSelector,
|
||||
showTimePicker,
|
||||
timePickerSeconds,
|
||||
is12HourFormat,
|
||||
showQuickSelectors,
|
||||
customSelectors,
|
||||
showTodayInFooter,
|
||||
showDropdowns,
|
||||
} = props
|
||||
// State for current month and year
|
||||
const [currentMonth, setCurrentMonth] = useState<number>(
|
||||
(startDate?.getMonth() || 0) + 1,
|
||||
)
|
||||
const [currentYear, setCurrentYear] = useState<number>(
|
||||
startDate?.getFullYear() as number,
|
||||
)
|
||||
|
||||
// Function to handle month change
|
||||
const handleMonthChange = (newMonth: number) => {
|
||||
// Update the current month state
|
||||
setCurrentMonth(newMonth)
|
||||
}
|
||||
|
||||
// Function to handle year change
|
||||
const handleYearChange = (newYear: number) => {
|
||||
// Update the current year state
|
||||
setCurrentYear(newYear)
|
||||
}
|
||||
|
||||
const numberOfViews = isRangeSelector ? 4 : 3
|
||||
|
||||
const items = Array.from({ length: numberOfViews }).map((_, index) => (
|
||||
<div key={index} className="ar-CalView__instance">
|
||||
<CalTime month={currentMonth} year={currentYear} {...props} />
|
||||
</div>
|
||||
))
|
||||
|
||||
return (
|
||||
<div className="ar-Date container">
|
||||
{/* Add responsive classes for mobile */}
|
||||
<div className="row">
|
||||
{showQuickSelectors && (
|
||||
<div className="col-lg-3">
|
||||
<RangeSelector customSelectors={customSelectors} />
|
||||
</div>
|
||||
)}
|
||||
{/* Second Column */}
|
||||
<div className="col-lg-9">
|
||||
{/* Use Slider component */}
|
||||
<Slider month={currentMonth} year={currentYear}>
|
||||
{/* Render your calendar instances and time pickers here */}
|
||||
{/* Example for rendering calendar instances */}
|
||||
{items}
|
||||
</Slider>
|
||||
</div>
|
||||
</div>
|
||||
{/* Second Row */}
|
||||
<div className="row d-sm-block">
|
||||
<div className="col-lg-12">
|
||||
<div className="date-picker-footer">
|
||||
{showTodayInFooter && (
|
||||
<Button
|
||||
size={ArSizes.XSMALL}
|
||||
variant={ArButtonVariants.SECONDARY}
|
||||
content="Today"
|
||||
onClick={() => {
|
||||
/* Handle "Today" button click */
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="date-picker-selected">
|
||||
{/* Display selected date/time */}
|
||||
{/* ... */}
|
||||
</div>
|
||||
<div className="date-picker-buttons">
|
||||
<Button
|
||||
size={ArSizes.XSMALL}
|
||||
variant={ArButtonVariants.SECONDARY}
|
||||
content="Cancel"
|
||||
onClick={() => {
|
||||
/* Handle "Today" button click */
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size={ArSizes.XSMALL}
|
||||
content="Apply"
|
||||
onClick={() => {
|
||||
/* Handle "Today" button click */
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Date
|
||||
43
src/app/components/atoms/Date/RangeSelector.tsx
Normal file
43
src/app/components/atoms/Date/RangeSelector.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from "react"
|
||||
import { List } from "../.." // Import your pre-existing List component
|
||||
import { ListItemProps } from "../../../types/components.interface"
|
||||
|
||||
interface RangeSelectorProps {
|
||||
customSelectors?: Array<ListItemProps>
|
||||
isSingle?: boolean
|
||||
}
|
||||
|
||||
const RangeSelector: React.FC<RangeSelectorProps> = ({
|
||||
isSingle,
|
||||
customSelectors,
|
||||
}) => {
|
||||
const defaultSingleDatePickerRanges: Array<ListItemProps> = [
|
||||
{ label: "Today", onClick: () => {} },
|
||||
{ label: "Yesterday", onClick: () => {} },
|
||||
{ label: "Tomorrow", onClick: () => {} },
|
||||
{ label: "Minus 7 Days", onClick: () => {} },
|
||||
{ label: "Minus 30 Days", onClick: () => {} },
|
||||
{ label: "Minus 365 Days", onClick: () => {} },
|
||||
]
|
||||
|
||||
const defaultDateRangeRanges: Array<ListItemProps> = [
|
||||
{ label: "Last 3 Days", onClick: () => {} },
|
||||
{ label: "Last Week", onClick: () => {} },
|
||||
{ label: "Last Month", onClick: () => {} },
|
||||
{ label: "Last 6 Months", onClick: () => {} },
|
||||
{ label: "Custom", onClick: () => {} },
|
||||
]
|
||||
|
||||
const ranges = customSelectors
|
||||
? customSelectors
|
||||
: isSingle
|
||||
? defaultSingleDatePickerRanges
|
||||
: defaultDateRangeRanges
|
||||
return (
|
||||
<div className="ar-RangeSelector">
|
||||
<List data={ranges} itemClasses="cursor-pointer" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RangeSelector
|
||||
43
src/app/components/atoms/Date/Slider.component.scss
Normal file
43
src/app/components/atoms/Date/Slider.component.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
.slider-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 300px; /* Adjust container width as needed */
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.slide {
|
||||
flex: 0 0 100%;
|
||||
height: 100px; /* Adjust slide height as needed */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
background-color: #e0e0e0;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.slide.active {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.prev-button,
|
||||
.next-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 5px 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.prev-button {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.next-button {
|
||||
right: 0;
|
||||
}
|
||||
160
src/app/components/atoms/Date/Slider.tsx
Normal file
160
src/app/components/atoms/Date/Slider.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React, { useState, ReactNode, useRef, MutableRefObject } from "react"
|
||||
import LoadableIcon from "../LoadableIcon"
|
||||
import { formatDate } from "./dateRangeFunctions"
|
||||
import { Helper } from "../../../utils"
|
||||
import Carousel from "../../molecules/Carousel"
|
||||
|
||||
interface SliderProps {
|
||||
children: ReactNode[]
|
||||
swipeThreshold?: number
|
||||
month: number
|
||||
year: number
|
||||
}
|
||||
|
||||
// function scrollToNext(
|
||||
// currentIndex: number,
|
||||
// items: Array<ReactNode>,
|
||||
// container: MutableRefObject<null>,
|
||||
// ) {
|
||||
// if (currentIndex < items.length - 1) {
|
||||
// currentIndex++
|
||||
// const nextItem = items[currentIndex]
|
||||
// ;(container.current as unknown as JSX.Element)?.scroll({
|
||||
// left: nextItem?.offsetLeft,
|
||||
// behavior: "smooth", // Use smooth behavior for smooth scrolling
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
function scrollToNext(
|
||||
currentIndex: number,
|
||||
container: MutableRefObject<HTMLElement | null>,
|
||||
) {
|
||||
if (
|
||||
container.current?.children &&
|
||||
Array.isArray(container.current?.children) &&
|
||||
currentIndex < container.current?.children.length - 1
|
||||
) {
|
||||
currentIndex++
|
||||
const nextItem = container.current?.children[currentIndex]
|
||||
nextItem?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "start",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const Slider: React.FC<SliderProps> = ({
|
||||
children,
|
||||
month,
|
||||
year,
|
||||
swipeThreshold = 25,
|
||||
}) => {
|
||||
const sliderRef = useRef(null)
|
||||
const itemsContainerRef = useRef(null)
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0)
|
||||
const [startX, setStartX] = useState<number | null>(null)
|
||||
|
||||
const numberOfViews = children.length
|
||||
|
||||
const slideToNext = () => {
|
||||
const nextIndex = (currentIndex + 1) % children.length
|
||||
setCurrentIndex(nextIndex)
|
||||
}
|
||||
|
||||
const slideToPrevious = () => {
|
||||
const previousIndex = (currentIndex - 1 + children.length) % children.length
|
||||
setCurrentIndex(previousIndex)
|
||||
}
|
||||
|
||||
const handleTouchStart = (e: React.TouchEvent) => {
|
||||
setStartX(e.touches[0].clientX)
|
||||
}
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent) => {
|
||||
if (startX !== null) {
|
||||
const deltaX = e.touches[0].clientX - startX
|
||||
if (deltaX > swipeThreshold) {
|
||||
// Swipe right, move to the previous slide
|
||||
slideToPrevious()
|
||||
} else if (deltaX < -swipeThreshold) {
|
||||
// Swipe left, move to the next slide
|
||||
slideToNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
setStartX(null)
|
||||
}
|
||||
|
||||
// Function to handle changing to the previous calendar view
|
||||
const handlePrev = () => {
|
||||
// Update the currentIndex to navigate to the previous view
|
||||
setCurrentIndex((prevIndex) => Math.max(prevIndex - 1, 0))
|
||||
}
|
||||
|
||||
// Function to handle changing to the next calendar view
|
||||
const handleNext = () => {
|
||||
scrollToNext(
|
||||
Math.min(currentIndex + 1, numberOfViews - 1),
|
||||
itemsContainerRef,
|
||||
)
|
||||
// Update the currentIndex to navigate to the next view
|
||||
// setCurrentIndex((prevIndex) => Math.min(prevIndex + 1, numberOfViews - 1))
|
||||
}
|
||||
|
||||
return (
|
||||
// Inside Slider component's return statement
|
||||
<div className="ar-Slider" ref={sliderRef}>
|
||||
{/* <div className="cal-view-controls d-flex justify-content-between">
|
||||
<LoadableIcon
|
||||
icon="md/MdKeyboardArrowLeft"
|
||||
onClick={handlePrev}
|
||||
classes="prev-button cursor-pointer"
|
||||
aria-label="Previous Calendar"
|
||||
/>
|
||||
<div className="d-inline-block">{formatDate(month, year)}</div>
|
||||
<LoadableIcon
|
||||
icon="md/MdKeyboardArrowRight"
|
||||
onClick={handleNext}
|
||||
classes="next-button cursor-pointer"
|
||||
aria-label="Next Calendar"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="ar-Slider__items-container d-flex"
|
||||
ref={itemsContainerRef}
|
||||
>
|
||||
{children.map((child, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`ar-Slider__item ${
|
||||
index === currentIndex ? "active" : ""
|
||||
}`}
|
||||
style={{
|
||||
scrollSnapAlign: index === currentIndex ? "start" : "none",
|
||||
}}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
role="tabpanel" // Use role="tabpanel" for each slide
|
||||
tabIndex={index === currentIndex ? 0 : -1} // Allow keyboard focus on active slide
|
||||
>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div> */}
|
||||
<Carousel
|
||||
classes="ar-Slider__items-container"
|
||||
infiniteLoop={true}
|
||||
noMarkers
|
||||
>
|
||||
{children}
|
||||
</Carousel>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Slider
|
||||
70
src/app/components/atoms/Date/TimePicker.tsx
Normal file
70
src/app/components/atoms/Date/TimePicker.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from "react"
|
||||
import { Dropdown } from "../.." // Import your Dropdown component
|
||||
|
||||
interface TimePickerProps {
|
||||
is12HourFormat?: boolean
|
||||
showSeconds?: boolean
|
||||
}
|
||||
|
||||
const TimePicker: React.FC<TimePickerProps> = ({
|
||||
is12HourFormat,
|
||||
showSeconds,
|
||||
}) => {
|
||||
const getOptions = (type: "hour" | "minute" | "second") => {
|
||||
const options = []
|
||||
const max = type === "hour" ? (is12HourFormat ? 12 : 24) : 60
|
||||
const format = type === "hour" ? 1 : 2
|
||||
|
||||
for (let i = 0; i < max; i++) {
|
||||
const label = String(i).padStart(format, "0")
|
||||
options.push({ label, value: i })
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
const hourOptions = getOptions("hour")
|
||||
const minuteOptions = getOptions("minute")
|
||||
const secondOptions = showSeconds ? getOptions("second") : []
|
||||
const amPmOptions = is12HourFormat
|
||||
? [
|
||||
{ label: "AM", value: "AM" },
|
||||
{ label: "PM", value: "PM" },
|
||||
]
|
||||
: []
|
||||
|
||||
return (
|
||||
<div className="ar-TimePicker">
|
||||
<Dropdown
|
||||
options={hourOptions}
|
||||
ariaLabel="Hour"
|
||||
onSelectionChanged={() => {}}
|
||||
/>
|
||||
<span>:</span>
|
||||
<Dropdown
|
||||
options={minuteOptions}
|
||||
ariaLabel="Minute"
|
||||
onSelectionChanged={() => {}}
|
||||
/>
|
||||
{showSeconds && (
|
||||
<>
|
||||
<span>:</span>
|
||||
<Dropdown
|
||||
options={secondOptions}
|
||||
ariaLabel="Second"
|
||||
onSelectionChanged={() => {}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{is12HourFormat && (
|
||||
<Dropdown
|
||||
options={amPmOptions}
|
||||
ariaLabel="AM/PM"
|
||||
onSelectionChanged={() => {}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimePicker
|
||||
1680
src/app/components/atoms/Date/dateRangeFunctions.ts
Normal file
1680
src/app/components/atoms/Date/dateRangeFunctions.ts
Normal file
File diff suppressed because it is too large
Load Diff
3
src/app/components/atoms/Date/index.ts
Executable file
3
src/app/components/atoms/Date/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import Date from "./Date"
|
||||
|
||||
export default Date
|
||||
@@ -1,3 +1,17 @@
|
||||
.ar-DatePicker {
|
||||
border-top-left-radius: 0.125rem;
|
||||
border-bottom-left-radius: 0.125rem;
|
||||
.ar-DatePicker__picker {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ar-DatePicker__clear-button {
|
||||
visibility: hidden;
|
||||
padding: 0 0.75rem;
|
||||
|
||||
&.has-value {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,51 @@
|
||||
import React from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { DatePickerProps } from "../../../types/components.interface"
|
||||
import "./DatePicker.component.scss"
|
||||
|
||||
interface DatePickerProps {}
|
||||
const DatePicker = (props: DatePickerProps): JSX.Element => {
|
||||
const {
|
||||
classes,
|
||||
containerClasses,
|
||||
id,
|
||||
label,
|
||||
labelClasses,
|
||||
placeholder,
|
||||
value,
|
||||
} = props
|
||||
const [localValue, setLocalValue] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
setLocalValue(value as string)
|
||||
}, [value])
|
||||
|
||||
const DatePicker = (props: any): JSX.Element => {
|
||||
return (
|
||||
<div className="ar-DatePicker">
|
||||
In Component DatePicker
|
||||
<div
|
||||
className={`ar-DatePicker${
|
||||
containerClasses ? " " + containerClasses : ""
|
||||
}`}
|
||||
>
|
||||
{label && (
|
||||
<label
|
||||
className={`ar-DatePicker__label${
|
||||
labelClasses ? " " + labelClasses : ""
|
||||
}`}
|
||||
htmlFor={id}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
className={`ar-DatePicker__picker form-component p-2${
|
||||
classes ? " " + classes : ""
|
||||
}`}
|
||||
id={id}
|
||||
onChange={(e) => setLocalValue(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
type="date"
|
||||
value={localValue}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DatePicker
|
||||
export default DatePicker
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user