added config viewer, tasker

This commit is contained in:
2023-10-05 01:45:55 +05:30
parent 69b3662365
commit 329229a21f
198 changed files with 11038 additions and 1598 deletions

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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>
)
}

View File

@@ -0,0 +1,3 @@
.ar-ConfigRowItem {
}

View File

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

View 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

View File

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

View File

@@ -0,0 +1,3 @@
.ar-ConfigurationList {
}

View File

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

View 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

View File

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

View File

@@ -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;
}
}

View File

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

View 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

View File

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

View File

@@ -0,0 +1,3 @@
.ar-ConfigurationNoConfigPrompt {
}

View File

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

View File

@@ -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

View File

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

View 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;
}
}

View File

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

View 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

View File

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

View File

@@ -1,7 +1,8 @@
import { ReactNode } from "react"
import "./Content.component.scss"
interface ContentProps {
children?: JSX.Element | Array<JSX.Element>
children?: ReactNode
classes?: string
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -13,6 +13,10 @@
}
}
&.shrink {
max-width: 4rem;
}
.ar-FavoriteItem {
width: 0;
transition: width 0.3s linear;

View File

@@ -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>

View File

@@ -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">

View 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);
}
}

View File

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

View 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

View File

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

View File

@@ -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);

View File

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

View File

@@ -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>
)
}

View File

@@ -1,3 +1,3 @@
.ar-LoginProvider {
width: 30rem;
}

View File

@@ -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"
/>

View File

@@ -1,9 +1,9 @@
.ar-Main {
.ar-Drawer {
width: 15%;
}
.ar-Content {
width: 85%;
& + .ar-Content {
width: 85%;
}
}
}

View File

@@ -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>
)
}

View 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;
}
}

View File

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

View 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

View File

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

View 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);
}
}

View File

@@ -0,0 +1,5 @@
import NamespaceOrgForm from "./NamespaceOrgForm"
describe("NamespaceOrgForm", () => {
it("renders without error", () => {})
})

View 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

View File

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

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -0,0 +1,3 @@
.ar-TaskList {
}

View File

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

View 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

View File

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

View File

@@ -0,0 +1,3 @@
.ar-TaskLoginPrompt {
background-color: var(--ar-bg);
}

View File

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

View 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

View File

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

View 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;
}
}

View File

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

View 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

View File

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

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -0,0 +1,3 @@
.ar-ArViz {
}

View File

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

View 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

View File

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

View File

@@ -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")}

View File

@@ -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 || "/")}
</>
))}

View File

@@ -0,0 +1,3 @@
.ar-BubbleViz {
}

View File

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

View 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

View File

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

View File

@@ -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;
}
}
}

View File

@@ -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>
</>
)
}

View File

@@ -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

View File

@@ -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"}

View 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%;}
}
}

View File

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

View 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

View File

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

View 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;
}
}
}
}

View File

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

View 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

View File

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

View 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

View 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

View 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 */

View File

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

View 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

View 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

View 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;
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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;
}
}
}

View File

@@ -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