-
Thunderstore Mod Manager
-
- You are prepared. Download Thunderstore Mod Manager
-
- for desktop and enter a world of Thunder{" "}
-
+
+ Thunderstore Mod Manager
+
+
+ You are prepared. Download Thunderstore Mod Manager for
+ desktop and enter a world of Thunder{" "}
+
-
-
-
+
+
-
- Get Manager
-
-
+ Get Manager
+
-
-
+
+
diff --git a/packages/cyberstorm/src/components/Icon/Icon.tsx b/packages/cyberstorm/src/components/Icon/Icon.tsx
index 213695a9f..a7696b0f6 100644
--- a/packages/cyberstorm/src/components/Icon/Icon.tsx
+++ b/packages/cyberstorm/src/components/Icon/Icon.tsx
@@ -10,6 +10,8 @@ interface IconProps {
wrapperClasses?: string;
}
+// TODO: Needs to be converted to the new system in a way that it handles
+// csSize to width or something like that
export function Icon(props: IconProps) {
const {
children,
diff --git a/packages/cyberstorm/src/components/ImageWithFallback/ImageWithFallback.module.css b/packages/cyberstorm/src/components/ImageWithFallback/ImageWithFallback.module.css
index 3b7e577c5..0c58a1025 100644
--- a/packages/cyberstorm/src/components/ImageWithFallback/ImageWithFallback.module.css
+++ b/packages/cyberstorm/src/components/ImageWithFallback/ImageWithFallback.module.css
@@ -18,9 +18,9 @@
justify-content: center;
border-radius: var(--border-radius--8);
overflow: hidden;
- color: var(--color-purple--9);
- background-color: var(--color-purple--4);
- transition: var(--animation-length-l);
+ color: var(--old--color-purple--9);
+ background-color: var(--old--color-purple--4);
+ transition: var(--animation-length-s) ease-out;
aspect-ratio: var(--aspect-ratio);
}
@@ -30,7 +30,7 @@
justify-content: center;
width: var(--image-width);
aspect-ratio: 1;
- transition: var(--animation-length-l);
+ transition: var(--animation-length-s) ease-out;
}
.image {
diff --git a/packages/cyberstorm/src/components/Links/LinkingProvider.tsx b/packages/cyberstorm/src/components/Links/LinkingProvider.tsx
index 907c6c69c..b55cac1eb 100644
--- a/packages/cyberstorm/src/components/Links/LinkingProvider.tsx
+++ b/packages/cyberstorm/src/components/Links/LinkingProvider.tsx
@@ -23,6 +23,7 @@ export interface ThunderstoreLinkProps {
community?: string;
namespace?: string;
package?: string;
+ version?: string;
team?: string;
user?: string;
}
@@ -37,6 +38,7 @@ export const thunderstoreLinkProps: ThunderstoreLinkProps = {
community: "",
namespace: "",
package: "",
+ version: "",
team: "",
user: "",
};
@@ -52,7 +54,7 @@ export interface AnyProps {
type NoRequiredProps = (props: AnyProps) => RE | null;
type PackageProps = { community: string; namespace: string; package: string };
-type PackageVersionProps = {
+export type PackageVersionProps = {
community: string;
namespace: string;
package: string;
diff --git a/packages/cyberstorm/src/components/Links/Links.tsx b/packages/cyberstorm/src/components/Links/Links.tsx
index 1938d8ebd..e6f4553ab 100644
--- a/packages/cyberstorm/src/components/Links/Links.tsx
+++ b/packages/cyberstorm/src/components/Links/Links.tsx
@@ -6,32 +6,7 @@
* wishes.
*/
import React, { PropsWithChildren } from "react";
-import {
- LinkingContext,
- LinkLibrary,
- ThunderstoreLinkProps,
-} from "./LinkingProvider";
-
-const wrap =
(
- key: T,
- className?: string,
- ref?: React.ForwardedRef,
- forwardedProps?: object
-): LinkLibrary[T] => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, react/display-name
- return (props: any) => {
- const Links = React.useContext(LinkingContext);
- const Link = Links[key];
- return (
-
- );
- };
-};
+import { LinkingContext, ThunderstoreLinkProps } from "./LinkingProvider";
interface typeWorkaroundProps extends PropsWithChildren {
community?: string;
@@ -73,31 +48,57 @@ export type CyberstormLinkIds =
| "User";
interface CyberstormLinkProps
- extends ThunderstoreLinkProps,
+ extends React.AnchorHTMLAttributes,
+ ThunderstoreLinkProps,
typeWorkaroundProps {
linkId: CyberstormLinkIds;
className?: string;
+ "data-color"?: string;
+ "data-size"?: string;
+ "data-variant"?: string;
forwardedProps?: object;
- ref?: React.ForwardedRef;
}
-// This exists for typescripts type checking.
-// And partly because I couldn't be bothered to figure out how to correctly structure the types
-function typeWorkaround(props: typeWorkaroundProps) {
- return {
- children: props.children,
- url: props.url ? props.url : "",
- community: props.community ? props.community : "",
- namespace: props.namespace ? props.namespace : "",
- package: props.package ? props.package : "",
- version: props.version ? props.version : "",
- team: props.team ? props.team : "",
- user: props.user ? props.user : "",
- };
-}
+// TODO: Move to primitives
+export const CyberstormLink = React.forwardRef<
+ HTMLAnchorElement,
+ CyberstormLinkProps
+>((props: CyberstormLinkProps, forwardedRef) => {
+ const {
+ linkId,
+ className,
+ children,
+ url = "",
+ community = "",
+ namespace = "",
+ version = "",
+ team = "",
+ user = "",
+ ...forwardedProps
+ } = props;
+ const fProps =
+ forwardedProps as React.AnchorHTMLAttributes;
+ const Links = React.useContext(LinkingContext);
+ const Link = Links[linkId];
+ return (
+
+ {children}
+
+ );
+});
-export function CyberstormLink(props: CyberstormLinkProps) {
- const { linkId, className, forwardedProps, ref, ...remainingProps } = props;
- const LinkGen = wrap(linkId, className, ref, forwardedProps);
- return LinkGen(typeWorkaround(remainingProps));
-}
+CyberstormLink.displayName = "CyberstormLink";
diff --git a/packages/cyberstorm/src/components/Markdown/Markdown.module.css b/packages/cyberstorm/src/components/Markdown/Markdown.module.css
index b6f5f44ba..8023e919c 100644
--- a/packages/cyberstorm/src/components/Markdown/Markdown.module.css
+++ b/packages/cyberstorm/src/components/Markdown/Markdown.module.css
@@ -100,7 +100,7 @@
color: #f5f5f6;
}
-.root a { color: var(--color-cyber-green--50); }
+.root a { color: var(--old--color-cyber-green--50); }
.root blockquote {
display: flex;
diff --git a/packages/cyberstorm/src/components/MultiSelectSearch/MultiSelectSearch.module.css b/packages/cyberstorm/src/components/MultiSelectSearch/MultiSelectSearch.module.css
index c03ece881..4156c413b 100644
--- a/packages/cyberstorm/src/components/MultiSelectSearch/MultiSelectSearch.module.css
+++ b/packages/cyberstorm/src/components/MultiSelectSearch/MultiSelectSearch.module.css
@@ -64,19 +64,19 @@
}
.inputContainer[data-color="red"] {
- --border-color: var(--color-red--5);
+ --border-color: var(--old--color-red--5);
}
.inputContainer[data-color="red"]:hover {
- --border-color: var(--color-red--3);
+ --border-color: var(--old--color-red--3);
}
.inputContainer[data-color="green"] {
- --border-color: var(--color-cyber-green--50);
+ --border-color: var(--old--color-cyber-green--50);
}
.inputContainer[data-color="green"]:hover {
- --border-color: var(--color-cyber-green--80);
+ --border-color: var(--old--color-cyber-green--80);
}
.clearSearch {
diff --git a/packages/cyberstorm/src/components/NewTabs/Tabs.module.css b/packages/cyberstorm/src/components/NewTabs/Tabs.module.css
index 5e4036fe1..913bfebda 100644
--- a/packages/cyberstorm/src/components/NewTabs/Tabs.module.css
+++ b/packages/cyberstorm/src/components/NewTabs/Tabs.module.css
@@ -7,7 +7,7 @@
.buttons {
display: flex;
- border-bottom: 3px solid var(--color-purple--5);
+ border-bottom: 3px solid var(--old--color-purple--5);
}
.button {
@@ -20,7 +20,7 @@
margin-bottom: -3px;
padding: var(--space--12) var(--space--16);
- border-bottom: 3px solid var(--color-purple--5);
+ border-bottom: 3px solid var(--old--color-purple--5);
color: var(--tab-color);
background-color: transparent;
@@ -30,7 +30,7 @@
.button.active {
border-color: var(--tab-color);
- --tab-color: var(--color-green--5);
+ --tab-color: var(--old--color-green--5);
}
.button:disabled {
diff --git a/packages/cyberstorm/src/components/Popover/Popover.tsx b/packages/cyberstorm/src/components/Popover/Popover.tsx
deleted file mode 100644
index 398cd3992..000000000
--- a/packages/cyberstorm/src/components/Popover/Popover.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { PropsWithChildren, ReactNode } from "react";
-
-interface Props extends PropsWithChildren {
- trigger: ReactNode;
- popoverId: string;
- popoverRootClasses?: string;
- popoverWrapperClasses?: string;
- noWrapper?: boolean;
-}
-
-/**
- * Centralized component for using HTML popovers.
- * The trigger should already have popover attributes set
- * as it is just inserted as a child.
- */
-export function Popover(props: Props) {
- return (
- <>
- {props.trigger}
-
- {props.noWrapper ? (
- props.children
- ) : (
-
{props.children}
- )}
-
- >
- );
-}
-
-Popover.displayName = "Popover";
diff --git a/packages/cyberstorm/src/components/SelectSearch/SelectSearch.module.css b/packages/cyberstorm/src/components/SelectSearch/SelectSearch.module.css
index c03ece881..4156c413b 100644
--- a/packages/cyberstorm/src/components/SelectSearch/SelectSearch.module.css
+++ b/packages/cyberstorm/src/components/SelectSearch/SelectSearch.module.css
@@ -64,19 +64,19 @@
}
.inputContainer[data-color="red"] {
- --border-color: var(--color-red--5);
+ --border-color: var(--old--color-red--5);
}
.inputContainer[data-color="red"]:hover {
- --border-color: var(--color-red--3);
+ --border-color: var(--old--color-red--3);
}
.inputContainer[data-color="green"] {
- --border-color: var(--color-cyber-green--50);
+ --border-color: var(--old--color-cyber-green--50);
}
.inputContainer[data-color="green"]:hover {
- --border-color: var(--color-cyber-green--80);
+ --border-color: var(--old--color-cyber-green--80);
}
.clearSearch {
diff --git a/packages/cyberstorm/src/components/Tag/Tag.module.css b/packages/cyberstorm/src/components/Tag/Tag.module.css
index 48944d1de..5fd86632f 100644
--- a/packages/cyberstorm/src/components/Tag/Tag.module.css
+++ b/packages/cyberstorm/src/components/Tag/Tag.module.css
@@ -111,20 +111,20 @@
}
.tag__success {
- --bg-color: var(--color-green--10);
+ --bg-color: var(--old--color-green--10);
--border-color: transparent;
- --text-color: var(--color-green--5);
+ --text-color: var(--old--color-green--5);
}
.tag__success:hover {
- --bg-color: var(--color-green--10);
- --border-color: var(--color-green--5);
- --text-color: var(--color-green--5);
+ --bg-color: var(--old--color-green--10);
+ --border-color: var(--old--color-green--5);
+ --text-color: var(--old--color-green--5);
}
.tag__info {
--bg-color: var(--color-info-background);
- --border-color: var(--color-blue--20);
+ --border-color: var(--old--color-blue--20);
--text-color: var(--color-info);
--icon-color: var(--color-info);
diff --git a/packages/cyberstorm/src/components/TextInput/TextInput.module.css b/packages/cyberstorm/src/components/TextInput/TextInput.module.css
index b44484dc6..6741a7c2c 100644
--- a/packages/cyberstorm/src/components/TextInput/TextInput.module.css
+++ b/packages/cyberstorm/src/components/TextInput/TextInput.module.css
@@ -46,19 +46,19 @@
}
.input[data-color="red"] {
- --border-color: var(--color-red--5);
+ --border-color: var(--old--color-red--5);
}
.input[data-color="red"]:hover {
- --border-color: var(--color-red--3);
+ --border-color: var(--old--color-red--3);
}
.input[data-color="green"] {
- --border-color: var(--color-cyber-green--50);
+ --border-color: var(--old--color-cyber-green--50);
}
.input[data-color="green"]:hover {
- --border-color: var(--color-cyber-green--80);
+ --border-color: var(--old--color-cyber-green--80);
}
.hasLeftIcon {
diff --git a/packages/cyberstorm/src/components/Tooltip/Tooltip.tsx b/packages/cyberstorm/src/components/Tooltip/Tooltip.tsx
index 28e6b2f76..0949a3d4e 100644
--- a/packages/cyberstorm/src/components/Tooltip/Tooltip.tsx
+++ b/packages/cyberstorm/src/components/Tooltip/Tooltip.tsx
@@ -18,16 +18,17 @@ export interface TooltipProps {
/**
* Cyberstorm Tooltip Component
*/
-export function Tooltip({
- content,
- sideOffset = 5,
- side = "right",
- collisionPadding = 20,
- sticky = "always",
- avoidCollisions = true,
- children,
- open,
-}: TooltipProps) {
+export function Tooltip(props: TooltipProps) {
+ const {
+ content,
+ sideOffset = 5,
+ side = "right",
+ collisionPadding = 20,
+ sticky = "always",
+ avoidCollisions = true,
+ children,
+ open,
+ } = props;
return (
{children}
diff --git a/packages/cyberstorm/src/index.ts b/packages/cyberstorm/src/index.ts
index 1cc615cad..ad818419c 100644
--- a/packages/cyberstorm/src/index.ts
+++ b/packages/cyberstorm/src/index.ts
@@ -53,7 +53,6 @@ export {
export { CyberstormProviders } from "./components/Providers";
export { Select, type SelectProps } from "./components/Select/Select";
export { Tabs, type TabsProps } from "./components/Tabs/Tabs";
-export { Popover } from "./components/Popover/Popover";
export { Tag, type TagProps } from "./components/Tag/Tag";
export {
TextInput,
@@ -73,8 +72,65 @@ export { CommunityCard } from "./components/CommunityCard/CommunityCard";
export { CommunityCardSkeleton } from "./components/CommunityCard/CommunityCardSkeleton";
export { range } from "./utils/utils";
export { SettingItem } from "./components/SettingItem/SettingItem";
-export { AdContainer } from "./components/AdContainer/AdContainer";
export { ImageWithFallback } from "./components/ImageWithFallback/ImageWithFallback";
export { CollapsibleText } from "./components/CollapsibleText/CollapsibleText";
export { SkeletonBox } from "./components/SkeletonBox/SkeletonBox";
export { isNode, isRecord, isStringArray } from "./utils/type_guards";
+
+// primitiveComponents
+export {
+ Actionable,
+ type ActionableButtonProps,
+ type ActionableLinkProps,
+ type ActionableCyberstormLinkProps,
+} from "./primitiveComponents/Actionable/Actionable";
+export {
+ Frame,
+ type FrameDisplayProps,
+ type FrameFloaterProps,
+ type FrameHeadingProps,
+ type FrameIconProps,
+ type FrameListItemProps,
+ type FrameListProps,
+ type FrameModalProps,
+ type FramePopoverProps,
+ type FrameTextProps,
+ type FrameWindowProps,
+} from "./primitiveComponents/Frame/Frame";
+export {
+ Input,
+ type InputTextInputProps,
+} from "./primitiveComponents/Input/Input";
+export {
+ type variants,
+ type colors,
+ type sizes,
+ type TextStyles,
+ type PrimitiveComponentDefaultProps,
+} from "./primitiveComponents/utils/utils";
+
+// newComponents
+export { Menu } from "./newComponents/Menu/Menu";
+export { Modal } from "./newComponents/Modal/Modal";
+export { Heading } from "./newComponents/Heading/Heading";
+export { CardCommunity } from "./newComponents/Card/CardCommunity/CardCommunity";
+export { Link as NewLink } from "./newComponents/Link/Link/Link";
+export { LinkButton } from "./newComponents/Link/LinkButton/LinkButton";
+export { Button as NewButton } from "./newComponents/Button/Button";
+export { BreadCrumbs as NewBreadCrumbs } from "./newComponents/BreadCrumbs/BreadCrumbs";
+export { Container } from "./newComponents/Container/Container";
+export { TextInput as NewTextInput } from "./newComponents/TextInput/TextInput";
+export {
+ Select as NewSelect,
+ type SelectProps as NewSelectProps,
+} from "./newComponents/Select/Select";
+export { Icon as NewIcon } from "./newComponents/Icon/Icon";
+export { Tag as NewTag } from "./newComponents/Tag/Tag";
+export {
+ DropDown as NewDropDown,
+ DropDownItem as NewDropDownItem,
+ DropDownDivider as NewDropDownDivider,
+} from "./newComponents/DropDown/DropDown";
+export { Image } from "./newComponents/Image/Image";
+export * as List from "./newComponents/List";
+export { AdContainer } from "./newComponents/AdContainer/AdContainer";
diff --git a/packages/cyberstorm/src/newComponents/AdContainer/AdContainer.module.css b/packages/cyberstorm/src/newComponents/AdContainer/AdContainer.module.css
new file mode 100644
index 000000000..5906bffcf
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/AdContainer/AdContainer.module.css
@@ -0,0 +1,46 @@
+.root {
+ position: relative;
+ display: inline-flex;
+ flex-direction: column;
+ align-items: flex-start;
+ border: 1px solid #1f1f42;
+}
+
+.fallback {
+ display: flex;
+ flex-flow: wrap;
+ gap: var(--gap--8);
+ align-content: center;
+ align-items: center;
+ justify-content: center;
+ width: 18.75rem;
+ height: 15.625rem;
+ padding: var(--space--24);
+ color: var(--color-tertiary);
+ background: var(--color-3);
+}
+
+.icon {
+ width: var(--space--16);
+ animation: heart-beat 2s cubic-bezier(0, -0.76, 0.89, 1.64) infinite;
+}
+
+.content {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+@keyframes heart-beat {
+ 0% {
+ transform: scale(1);
+ }
+
+ 50% {
+ transform: scale(0.9);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
diff --git a/packages/cyberstorm/src/newComponents/AdContainer/AdContainer.tsx b/packages/cyberstorm/src/newComponents/AdContainer/AdContainer.tsx
new file mode 100644
index 000000000..6605d6e60
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/AdContainer/AdContainer.tsx
@@ -0,0 +1,48 @@
+import styles from "./AdContainer.module.css";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faHeart } from "@fortawesome/free-solid-svg-icons";
+import { Container, NewIcon } from "../..";
+
+interface AdContainerProps {
+ containerId: string;
+}
+
+export function AdContainer(props: AdContainerProps) {
+ const { containerId } = props;
+
+ return (
+
+
+ Thunderstore development is made possible with ads.
+
+
+
+ Thanks
+
+
+
+
+
+
+ );
+}
+
+AdContainer.displayName = "AdContainer";
diff --git a/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.module.css b/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.module.css
new file mode 100644
index 000000000..dcd4dae5d
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.module.css
@@ -0,0 +1,90 @@
+.root {
+ display: flex;
+ gap: var(--space--4);
+ padding-left: var(--space--16);
+}
+
+.outer {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space--8) var(--space--16);
+ background-color: var(--breadcrumb-bg-color);
+
+ /* skew the container */
+ transform: skew(-30deg);
+
+ --breadcrumb-bg-color: var(--color-4);
+}
+
+.outer:hover {
+ --breadcrumb-bg-color: var(--color-7);
+}
+
+.outerHome {
+ z-index: 1;
+}
+
+.outer__start {
+ padding-left: 0;
+}
+
+.outer__end {
+ padding-right: 0;
+
+ /* --breadcrumb-bg-color: var(--color-7); */
+}
+
+.outer__start::before,
+.outer__end::after {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ z-index: -1;
+ width: var(--space--32);
+ background-color: var(--breadcrumb-bg-color);
+ transform: skew(30deg);
+ content: "\0020";
+}
+
+.outer__start::before {
+ left: -1rem;
+ border-radius: 0.5rem 0 0 0.5rem;
+}
+
+.outer__end::after {
+ right: -1rem;
+ border-radius: 0 0.5rem 0.5rem 0;
+}
+
+.inner {
+ /* gap: var(--space--14);
+ align-items: center; */
+ font-weight: var(--font-weight);
+ font-size: var(--font-size);
+ line-height: var(--line-height);
+
+ /* unskew the content */
+ transform: skew(30deg);
+}
+
+.innerHome {
+ width: 1em;
+ height: 1em;
+}
+
+/* .inner > a {
+ display: block;
+ margin: calc(-1 * var(--space--8)) calc(-1 * var(--space--16));
+ padding: var(--space--8) var(--space--16);
+} */
+
+.inner__end {
+ color: var(--color-primary);
+}
+
+.home {
+ font-weight: var(--font-weight-regular);
+ text-decoration: none;
+}
diff --git a/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.tsx b/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.tsx
new file mode 100644
index 000000000..74a178bb0
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/BreadCrumbs/BreadCrumbs.tsx
@@ -0,0 +1,90 @@
+import React, { PropsWithChildren } from "react";
+
+import { faHouse } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import styles from "./BreadCrumbs.module.css";
+import { classnames } from "../../utils/utils";
+import { NewIcon, NewLink } from "../..";
+import { Frame } from "../../primitiveComponents/Frame/Frame";
+
+type BreadCrumbsProps = PropsWithChildren<{
+ excludeHome?: boolean;
+ rootClasses?: string;
+}>;
+
+// TODO: This component is not complete and probably is in need of redesign
+// TODO: Bug: when excludeHome is true, last element's style is wrong
+export function BreadCrumbs(props: BreadCrumbsProps) {
+ const children = React.Children.toArray(props.children);
+
+ const nodes = children.map((node, index) => {
+ const homifiedIndex = props.excludeHome ? index - 1 : index;
+ const isLast = homifiedIndex == children.length - 1;
+ return (
+
+
+ {node}
+
+
+ );
+ });
+
+ const home = (
+
+
+
+
+
+ );
+
+ return (
+
+ {props.excludeHome ? null : home}
+ {nodes}
+
+ );
+}
+
+export function DefaultHomeCrumb() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/cyberstorm/src/newComponents/Button/Button.tsx b/packages/cyberstorm/src/newComponents/Button/Button.tsx
new file mode 100644
index 000000000..4a7f962aa
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Button/Button.tsx
@@ -0,0 +1,54 @@
+import styles from "../../sharedComponentStyles/ButtonStyles/Button.module.css";
+import React from "react";
+import { classnames } from "../../utils/utils";
+import {
+ ActionableButtonProps,
+ Actionable,
+} from "../../primitiveComponents/Actionable/Actionable";
+
+export const Button = React.forwardRef<
+ HTMLButtonElement,
+ Omit
+>((props: Omit, forwardedRef) => {
+ const {
+ children,
+ rootClasses,
+ csVariant = "default",
+ csColor = "purple",
+ csSize = "m",
+ csTextStyles,
+ ...forwardedProps
+ } = props;
+
+ // TODO: Turn into a proper resolver function
+ // Same logic is in LinkButton too
+ const fontStyles = (size: typeof csSize) => {
+ if (size === "xs") {
+ return ["fontSizeXS", "fontWeightBold", "lineHeightAuto"];
+ } else if (size === "s") {
+ return ["fontSizeS", "fontWeightBold", "lineHeightAuto"];
+ } else {
+ return ["fontSizeM", "fontWeightBold", "lineHeightAuto"];
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+});
+
+Button.displayName = "Button";
diff --git a/packages/cyberstorm/src/newComponents/Card/CardCommunity/CardCommunity.module.css b/packages/cyberstorm/src/newComponents/Card/CardCommunity/CardCommunity.module.css
new file mode 100644
index 000000000..e7031e2bd
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Card/CardCommunity/CardCommunity.module.css
@@ -0,0 +1,59 @@
+.root {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: var(--space--12);
+}
+
+.root:hover .imageWrapper {
+ filter: brightness(1.15);
+}
+
+.root:focus-within {
+ border-radius: var(--border-radius--8);
+ outline: 0.2rem solid var(--color-cyber-green--5);
+ outline-offset: 0.5rem;
+}
+
+.root .imageWrapper > div {
+ transform-origin: bottom;
+}
+
+.root:hover .imageWrapper > div,
+.root:focus-within .imageWrapper > div {
+ transform: scale(1.05);
+}
+
+.title {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.title:focus-visible {
+ outline: none !important;
+}
+
+.metaItemList {
+ display: flex;
+ flex-direction: row;
+ gap: var(--gap--16);
+ margin-top: auto;
+}
+
+.metaItem {
+ display: flex;
+ gap: var(--gap--8);
+ align-items: center;
+}
+
+.tag {
+ position: absolute;
+ right: 0;
+ z-index: 1;
+ display: flex;
+ flex-flow: row-reverse;
+ flex-wrap: wrap;
+ gap: var(--space--8);
+ margin: var(--space--8);
+}
diff --git a/packages/cyberstorm/src/newComponents/Card/CardCommunity/CardCommunity.tsx b/packages/cyberstorm/src/newComponents/Card/CardCommunity/CardCommunity.tsx
new file mode 100644
index 000000000..ca33e2983
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Card/CardCommunity/CardCommunity.tsx
@@ -0,0 +1,104 @@
+import {
+ faBoxOpen,
+ faDownload,
+ faHandSparkles,
+ faFire,
+} from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Community } from "@thunderstore/dapper/types";
+
+import styles from "./CardCommunity.module.css";
+import { numberWithSpaces, formatInteger } from "../../../utils/utils";
+import { NewLink, NewIcon, Image, NewTag } from "../../..";
+import { Container } from "../../Container/Container";
+
+interface Props {
+ community: Community;
+ isPopular?: boolean;
+ isNew?: boolean;
+}
+
+export function CardCommunity(props: Props) {
+ const { community, isPopular, isNew } = props;
+
+ return (
+
+
+ {isPopular ? (
+
+
+
+
+ Popular
+
+ ) : null}
+ {isNew ? (
+
+
+
+
+ New
+
+ ) : null}
+
+
+
+
+
+ {community.name}
+
+
+
+
+
+
+ {formatInteger(community.total_package_count)}
+
+
+
+
+
+ {formatInteger(community.total_download_count)}
+
+
+
+ );
+}
+
+CardCommunity.displayName = "CardCommunity";
diff --git a/packages/cyberstorm/src/newComponents/Container/Container.module.css b/packages/cyberstorm/src/newComponents/Container/Container.module.css
new file mode 100644
index 000000000..16566337d
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Container/Container.module.css
@@ -0,0 +1,25 @@
+.container {
+ color: var(--container-color);
+
+ --container-color: var(--color-primary);
+}
+
+.container[data-variant="default"] {
+ --container-color: var(--color-5);
+}
+
+.container[data-variant="primary"] {
+ --container-color: var(--color-primary);
+}
+
+.container[data-variant="secondary"] {
+ --container-color: var(--color-secondary);
+}
+
+.container[data-variant="tertiary"] {
+ --container-color: var(--color-tertiary);
+}
+
+.container[data-variant="accent"] {
+ --container-color: var(--color-accent);
+}
diff --git a/packages/cyberstorm/src/newComponents/Container/Container.tsx b/packages/cyberstorm/src/newComponents/Container/Container.tsx
new file mode 100644
index 000000000..f42e4f14a
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Container/Container.tsx
@@ -0,0 +1,32 @@
+import styles from "./Container.module.css";
+import React from "react";
+import { Frame, FrameWindowProps } from "../../primitiveComponents/Frame/Frame";
+import { classnames } from "../../utils/utils";
+
+export const Container = React.forwardRef<
+ HTMLDivElement,
+ Omit
+>((props: Omit, forwardedRef) => {
+ const {
+ children,
+ rootClasses,
+ csColor = "purple",
+ csVariant = "primary",
+ ...forwardedProps
+ } = props;
+ const fProps = forwardedProps as FrameWindowProps;
+ return (
+
+ {children}
+
+ );
+});
+
+Container.displayName = "Container";
diff --git a/packages/cyberstorm/src/newComponents/DropDown/DropDown.module.css b/packages/cyberstorm/src/newComponents/DropDown/DropDown.module.css
new file mode 100644
index 000000000..d54a94156
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/DropDown/DropDown.module.css
@@ -0,0 +1,111 @@
+.dropdown {
+ z-index: 2;
+ display: flex;
+ flex-direction: column;
+ padding: var(--space--8) 0;
+ border: var(--space--px) var(--dropdown-border-color) solid;
+ border-radius: var(--border-radius--8);
+ overflow: hidden;
+ color: var(--dropdown-color);
+ background-color: var(--dropdown-background-color);
+ box-shadow: var(--box-shadow-default);
+ transform-origin: var(--radix-dropdown-menu-content-transform-origin);
+ animation: fade-in var(--animation-length-m) ease;
+
+ --dropdown-color: var(--color-primary);
+ --dropdown-background-color: var(--color-secondary);
+ --dropdown-border-color: var(--color-tertiary);
+}
+
+.dropdown[data-variant="default"] {
+ --dropdown-color: var(--color-5);
+ --dropdown-background-color: var(--color-2);
+ --dropdown-border-color: var(--color-6);
+}
+
+.dropdown[data-variant="primary"] {
+ --dropdown-color: var(--color-primary);
+ --dropdown-background-color: var(--color-secondary);
+ --dropdown-border-color: var(--color-tertiary);
+}
+
+.dropdown[data-variant="secondary"] {
+ --dropdown-color: var(--color-secondary);
+ --dropdown-background-color: var(--color-primary);
+ --dropdown-border-color: var(--color-accent);
+}
+
+.dropdown[data-variant="tertiary"] {
+ --dropdown-color: var(--color-tertiary);
+ --dropdown-background-color: var(--color-accent);
+ --dropdown-border-color: var(--color-secondary);
+}
+
+.dropdown[data-variant="accent"] {
+ --dropdown-color: var(--color-accent);
+ --dropdown-background-color: var(--color-tertiary);
+ --dropdown-border-color: var(--color-secondary);
+}
+
+.dropdownItem {
+ padding: var(--space--12) var(--space--16);
+ overflow: hidden;
+ color: var(--dropdown-item-color, var(--dropdown-color));
+ background-color: var(--dropdown-item-background-color, var(--dropdown-background-color));
+ outline: none;
+
+ --dropdown-item-color: var(--color-tertiary);
+ --dropdown-item-background-color: var(--color-2);
+ --dropdown-item-highlighted-background-color: var(--color-6);
+}
+
+.dropdownItem[data-highlighted] {
+ z-index: 999;
+ background-color: var(--dropdown-item-highlighted-background-color, var(--color-primary));
+}
+
+.dropdownItem[data-variant="default"] {
+ --dropdown-item-color: var(--color-8);
+ --dropdown-item-background-color: var(--color-2);
+ --dropdown-item-highlighted-background-color: var(--color-6);
+}
+
+.dropdownItem[data-variant="primary"] {
+ --dropdown-item-color: var(--color-primary);
+ --dropdown-item-background-color: var(--color-secondary);
+ --dropdown-item-highlighted-background-color: var(--color-tertiary);
+}
+
+.dropdownItem[data-variant="secondary"] {
+ --dropdown-item-color: var(--color-secondary);
+ --dropdown-item-background-color: var(--color-primary);
+ --dropdown-item-highlighted-background-color: var(--color-tertiary);
+}
+
+.dropdownItem[data-variant="tertiary"] {
+ --dropdown-item-color: var(--color-tertiary);
+ --dropdown-item-background-color: var(--color-accent);
+ --dropdown-item-highlighted-background-color: var(--color-accent);
+}
+
+.dropdownItem[data-variant="accent"] {
+ --dropdown-item-color: var(--color-accent);
+ --dropdown-item-background-color: var(--color-tertiary);
+ --dropdown-item-highlighted-background-color: var(--color-secondary);
+}
+
+.divider {
+ height: var(--space--px);
+ margin: var(--space--8) 0;
+ background-color: var(--color-6, var(--color-primary, red));
+}
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
diff --git a/packages/cyberstorm/src/newComponents/DropDown/DropDown.tsx b/packages/cyberstorm/src/newComponents/DropDown/DropDown.tsx
new file mode 100644
index 000000000..574a2db28
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/DropDown/DropDown.tsx
@@ -0,0 +1,80 @@
+import { ReactNode, ReactElement } from "react";
+import styles from "./DropDown.module.css";
+
+import * as RadixDropDown from "@radix-ui/react-dropdown-menu";
+import { Container } from "../Container/Container";
+import { classnames } from "../../utils/utils";
+import { PrimitiveComponentDefaultProps } from "../../primitiveComponents/utils/utils";
+
+interface DropDownProps extends PrimitiveComponentDefaultProps {
+ defaultOpen?: boolean;
+ contentAlignment?: "start" | "center" | "end";
+ trigger: ReactNode | ReactElement;
+}
+
+export function DropDown(props: DropDownProps) {
+ const {
+ children,
+ rootClasses,
+ csColor,
+ csVariant,
+ csSize,
+ csTextStyles,
+ defaultOpen = false,
+ contentAlignment = "start",
+ trigger,
+ } = props;
+
+ return (
+
+
+ {trigger}
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+export function DropDownItem(props: PrimitiveComponentDefaultProps) {
+ const { children, rootClasses, csTextStyles, csColor, csVariant, csSize } =
+ props;
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function DropDownDivider() {
+ return ;
+}
+
+DropDown.displayName = "DropDown";
+DropDownItem.displayName = "DropDownItem";
+DropDownDivider.displayName = "DropDownDivider";
diff --git a/packages/cyberstorm/src/newComponents/Heading/Heading.module.css b/packages/cyberstorm/src/newComponents/Heading/Heading.module.css
new file mode 100644
index 000000000..73b77ac89
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Heading/Heading.module.css
@@ -0,0 +1,21 @@
+.heading {
+ color: var(--heading-color);
+
+ --heading-color: var(--color-primary);
+}
+
+.heading[data-variant="primary"] {
+ --heading-color: var(--color-primary);
+}
+
+.heading[data-variant="secondary"] {
+ --heading-color: var(--color-secondary);
+}
+
+.heading[data-variant="tertiary"] {
+ --heading-color: var(--color-tertiary);
+}
+
+.heading[data-variant="accent"] {
+ --heading-color: var(--color-accent);
+}
diff --git a/packages/cyberstorm/src/newComponents/Heading/Heading.tsx b/packages/cyberstorm/src/newComponents/Heading/Heading.tsx
new file mode 100644
index 000000000..0093ac1fd
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Heading/Heading.tsx
@@ -0,0 +1,42 @@
+import { PropsWithChildren } from "react";
+import styles from "./Heading.module.css";
+import React from "react";
+import { Frame } from "../../primitiveComponents/Frame/Frame";
+
+interface DefaultProps
+ extends React.HTMLAttributes,
+ PropsWithChildren {
+ level?: "1" | "2" | "3" | "4";
+ styleLevel?: "1" | "2" | "3" | "4";
+ variant?: "primary" | "secondary" | "tertiary" | "accent";
+ mode?: "heading" | "display";
+}
+
+export const Heading = React.forwardRef(
+ (props: DefaultProps, forwardedRef) => {
+ const {
+ children,
+ variant = "primary",
+ level = "1",
+ styleLevel,
+ mode = "heading",
+ ...forwardedProps
+ } = props;
+ const fProps = forwardedProps as DefaultProps;
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+Heading.displayName = "Heading";
diff --git a/packages/cyberstorm/src/newComponents/Icon/Icon.module.css b/packages/cyberstorm/src/newComponents/Icon/Icon.module.css
new file mode 100644
index 000000000..126960a68
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Icon/Icon.module.css
@@ -0,0 +1,23 @@
+.icon {
+ color: var(--icon-color, "currentColor");
+}
+
+.icon[data-variant="default"] {
+ --icon-color: var(--color-7);
+}
+
+.icon[data-variant="primary"] {
+ --icon-color: var(--color-primary);
+}
+
+.icon[data-variant="secondary"] {
+ --icon-color: var(--color-secondary);
+}
+
+.icon[data-variant="tertiary"] {
+ --icon-color: var(--color-tertiary);
+}
+
+.icon[data-variant="accent"] {
+ --icon-color: var(--color-accent);
+}
diff --git a/packages/cyberstorm/src/newComponents/Icon/Icon.tsx b/packages/cyberstorm/src/newComponents/Icon/Icon.tsx
new file mode 100644
index 000000000..c4e777f3b
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Icon/Icon.tsx
@@ -0,0 +1,21 @@
+import { Frame, FrameIconProps } from "../../primitiveComponents/Frame/Frame";
+import React from "react";
+import styles from "./Icon.module.css";
+import { classnames } from "../../utils/utils";
+
+export const Icon = React.forwardRef<
+ HTMLDivElement | HTMLSpanElement | SVGElement,
+ Omit
+>((props: Omit, forwardedRef) => {
+ const { rootClasses, ...forwardedProps } = props;
+ return (
+
+ );
+});
+
+Icon.displayName = "Icon";
diff --git a/packages/cyberstorm/src/newComponents/Image/Image.module.css b/packages/cyberstorm/src/newComponents/Image/Image.module.css
new file mode 100644
index 000000000..392c239d2
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Image/Image.module.css
@@ -0,0 +1,67 @@
+.isSquare {
+ --aspect-ratio: 1;
+ --image-width: 50%;
+}
+
+.is3By4 {
+ --aspect-ratio: 3 / 4;
+ --image-width: 30%;
+}
+
+.fullWidth {
+ --image-width: 100%;
+}
+
+.imageWrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--border-radius--8);
+ overflow: hidden;
+ color: var(--image-wrapper-color);
+ background-color: var(--image-wrapper-bg-color);
+ transition: var(--animation-length-s) ease-out;
+ aspect-ratio: var(--aspect-ratio);
+
+ --image-wrapper-color: var(--color-primary);
+ --image-wrapper-bg-color: var(--color-tertiary);
+}
+
+.imageContent {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--image-width);
+ aspect-ratio: 1;
+ transition: var(--animation-length-s) ease-out;
+}
+
+.image {
+ object-fit: cover;
+ aspect-ratio: var(--aspect-ratio);
+}
+
+.imageWrapper[data-variant="default"] {
+ --image-wrapper-color: var(--color-9);
+ --image-wrapper-bg-color: var(--color-4);
+}
+
+.imageWrapper[data-variant="primary"] {
+ --image-wrapper-color: var(--color-primary);
+ --image-wrapper-bg-color: var(--color-tertiary);
+}
+
+.imageWrapper[data-variant="secondary"] {
+ --image-wrapper-color: var(--color-secondary);
+ --image-wrapper-bg-color: var(--color-tertiary);
+}
+
+.imageWrapper[data-variant="tertiary"] {
+ --image-wrapper-color: var(--color-tertiary);
+ --image-wrapper-bg-color: var(--color-primary);
+}
+
+.imageWrapper[data-variant="accent"] {
+ --image-wrapper-color: var(--color-accent);
+ --image-wrapper-bg-color: var(--color-primary);
+}
diff --git a/packages/cyberstorm/src/newComponents/Image/Image.tsx b/packages/cyberstorm/src/newComponents/Image/Image.tsx
new file mode 100644
index 000000000..0248c4a1c
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Image/Image.tsx
@@ -0,0 +1,74 @@
+import { faBan, faGamepad } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import styles from "./Image.module.css";
+import { classnames } from "../../utils/utils";
+import { Frame, FrameWindowProps } from "../../primitiveComponents/Frame/Frame";
+import { Icon } from "../..";
+import React from "react";
+
+interface ImageProps extends Omit {
+ src: string | null;
+ /** Type of the image defines the icon used as the fallback. */
+ cardType: "community" | "package";
+ /** Alt text for the image. Leave empty for decorative images. */
+ alt?: string;
+ /** Force 1:1 aspect ratio */
+ square?: boolean;
+}
+
+// TODO: Needs a storybook story
+/**
+ * Show the image, or use predefined icon as the fallback.
+ */
+export const Image = React.forwardRef(
+ (props: ImageProps, forwardedRef) => {
+ const {
+ src,
+ cardType,
+ alt = "",
+ rootClasses,
+ square = false,
+ csColor = "surface",
+ csVariant = "default",
+ ...forwardedProps
+ } = props;
+ const fProps = forwardedProps as ImageProps;
+
+ return (
+
+ {src ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+ }
+);
+
+Image.displayName = "Image";
+
+const getIcon = (type: ImageProps["cardType"] = "community") =>
+ ({
+ community: faGamepad,
+ package: faBan,
+ }[type]);
diff --git a/packages/cyberstorm/src/newComponents/Link/Link/Link.module.css b/packages/cyberstorm/src/newComponents/Link/Link/Link.module.css
new file mode 100644
index 000000000..5cc237a41
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Link/Link/Link.module.css
@@ -0,0 +1,78 @@
+/* Common Link styles */
+.link {
+ color: var(--link-color);
+
+ --link-color: var(--text-color);
+}
+
+.link:hover {
+ --link-color: var(--text-color);
+}
+
+.link:active {
+ --link-color: var(--text-color);
+}
+
+.link[data-variant="default"] {
+ --link-color: var(--color-7);
+}
+
+.link[data-variant="default"]:hover {
+ --link-color: var(--color-9);
+}
+
+.link[data-variant="default"]:active {
+ --link-color: var(--color-9);
+}
+
+.link[data-variant="primary"] {
+ --link-color: var(--color-primary);
+}
+
+.link[data-variant="primary"]:hover {
+ --link-color: var(--color-primary);
+}
+
+.link[data-variant="primary"]:active {
+ --link-color: var(--color-primary);
+}
+
+.link[data-variant="secondary"] {
+ --link-color: var(--color-secondary);
+}
+
+.link[data-variant="secondary"]:hover {
+ --link-color: var(--color-primary);
+}
+
+.link[data-variant="secondary"]:active {
+ --link-color: var(--color-primary);
+}
+
+.link[data-variant="accent"] {
+ --link-color: var(--color-accent);
+}
+
+.link[data-variant="accent"]:hover {
+ --link-color: var(--color-primary);
+}
+
+.link[data-variant="accent"]:active {
+ --link-color: var(--color-primary);
+}
+
+/* Link variants */
+
+/* auto */
+
+.link[data-variant="auto"] {
+ display: inline-flex;
+ align-items: flex-start;
+}
+
+/* body */
+
+.link[data-mode="body"] {
+ display: flex;
+ align-items: flex-start;
+}
diff --git a/packages/cyberstorm/src/newComponents/Link/Link/Link.tsx b/packages/cyberstorm/src/newComponents/Link/Link/Link.tsx
new file mode 100644
index 000000000..979bb7bde
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Link/Link/Link.tsx
@@ -0,0 +1,32 @@
+import {
+ Actionable,
+ ActionableCyberstormLinkProps,
+ ActionableLinkProps,
+} from "../../../primitiveComponents/Actionable/Actionable";
+import styles from "./Link.module.css";
+import React from "react";
+import { classnames } from "../../../utils/utils";
+
+export const Link = React.forwardRef<
+ HTMLAnchorElement,
+ ActionableLinkProps | ActionableCyberstormLinkProps
+>(
+ (
+ props: ActionableLinkProps | ActionableCyberstormLinkProps,
+ forwardedRef
+ ) => {
+ const { children, rootClasses, csVariant, ...forwardedProps } = props;
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+Link.displayName = "Link";
diff --git a/packages/cyberstorm/src/newComponents/Link/LinkButton/LinkButton.tsx b/packages/cyberstorm/src/newComponents/Link/LinkButton/LinkButton.tsx
new file mode 100644
index 000000000..4bf3eca71
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Link/LinkButton/LinkButton.tsx
@@ -0,0 +1,59 @@
+import {
+ Actionable,
+ ActionableCyberstormLinkProps,
+ ActionableLinkProps,
+} from "../../../primitiveComponents/Actionable/Actionable";
+import styles from "../../../sharedComponentStyles/ButtonStyles/Button.module.css";
+import React from "react";
+import { classnames } from "../../../utils/utils";
+
+export const LinkButton = React.forwardRef<
+ HTMLAnchorElement,
+ ActionableLinkProps | ActionableCyberstormLinkProps
+>(
+ (
+ props: ActionableLinkProps | ActionableCyberstormLinkProps,
+ forwardedRef
+ ) => {
+ const {
+ children,
+ rootClasses,
+ csVariant = "default",
+ csColor = "purple",
+ csSize = "m",
+ csTextStyles,
+ ...forwardedProps
+ } = props;
+
+ // TODO: Turn into a proper resolver function
+ // Same logic is in LinkButton too
+ const fontStyles = (size: typeof csSize) => {
+ if (size === "xs") {
+ return ["fontSizeXS", "fontWeightBold", "lineHeightAuto"];
+ } else if (size === "s") {
+ return ["fontSizeS", "fontWeightBold", "lineHeightAuto"];
+ } else {
+ return ["fontSizeM", "fontWeightBold", "lineHeightAuto"];
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+LinkButton.displayName = "LinkButton";
diff --git a/packages/cyberstorm/src/newComponents/List/List/List.module.css b/packages/cyberstorm/src/newComponents/List/List/List.module.css
new file mode 100644
index 000000000..d1663ef58
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/List/List/List.module.css
@@ -0,0 +1,29 @@
+.list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap--8);
+ color: var(--list-color);
+ list-style-type: none;
+
+ --list-color: var(--color-accent);
+}
+
+.list[data-variant="default"] {
+ --list-color: var(--color-5);
+}
+
+.list[data-variant="primary"] {
+ --list-color: var(--color-primary);
+}
+
+.list[data-variant="secondary"] {
+ --list-color: var(--color-secondary);
+}
+
+.list[data-variant="tertiary"] {
+ --list-color: var(--color-tertiary);
+}
+
+.list[data-variant="accent"] {
+ --list-color: var(--color-accent);
+}
diff --git a/packages/cyberstorm/src/newComponents/List/List/List.tsx b/packages/cyberstorm/src/newComponents/List/List/List.tsx
new file mode 100644
index 000000000..3bd4e2be3
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/List/List/List.tsx
@@ -0,0 +1,35 @@
+import styles from "./List.module.css";
+import React from "react";
+import {
+ Frame,
+ FrameListProps,
+} from "../../../primitiveComponents/Frame/Frame";
+import { classnames } from "../../../utils/utils";
+
+export const List = React.forwardRef<
+ HTMLUListElement,
+ Omit
+>((props: Omit, forwardedRef) => {
+ const {
+ children,
+ rootClasses,
+ csColor = "purple",
+ csVariant = "accent",
+ ...forwardedProps
+ } = props;
+ const fProps = forwardedProps as FrameListProps;
+ return (
+
+ {children}
+
+ );
+});
+
+List.displayName = "List";
diff --git a/packages/cyberstorm/src/newComponents/List/ListItem/ListItem.module.css b/packages/cyberstorm/src/newComponents/List/ListItem/ListItem.module.css
new file mode 100644
index 000000000..d5268e01f
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/List/ListItem/ListItem.module.css
@@ -0,0 +1,23 @@
+.listItem {
+ color: var(--list-item-color);
+}
+
+.list[data-variant="default"] {
+ --list-item-color: var(--color-5);
+}
+
+.list[data-variant="primary"] {
+ --list-item-color: var(--color-primary);
+}
+
+.list[data-variant="secondary"] {
+ --list-item-color: var(--color-secondary);
+}
+
+.list[data-variant="tertiary"] {
+ --list-item-color: var(--color-tertiary);
+}
+
+.list[data-variant="accent"] {
+ --list-item-color: var(--color-accent);
+}
diff --git a/packages/cyberstorm/src/newComponents/List/ListItem/ListItem.tsx b/packages/cyberstorm/src/newComponents/List/ListItem/ListItem.tsx
new file mode 100644
index 000000000..22a113725
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/List/ListItem/ListItem.tsx
@@ -0,0 +1,27 @@
+import styles from "./ListItem.module.css";
+import React from "react";
+import {
+ Frame,
+ FrameListItemProps,
+} from "../../../primitiveComponents/Frame/Frame";
+import { classnames } from "../../../utils/utils";
+
+export const ListItem = React.forwardRef<
+ HTMLLIElement,
+ Omit
+>((props: Omit, forwardedRef) => {
+ const { children, rootClasses, ...forwardedProps } = props;
+ const fProps = forwardedProps as FrameListItemProps;
+ return (
+
+ {children}
+
+ );
+});
+
+ListItem.displayName = "ListItem";
diff --git a/packages/cyberstorm/src/newComponents/List/index.ts b/packages/cyberstorm/src/newComponents/List/index.ts
new file mode 100644
index 000000000..ce6b38cd1
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/List/index.ts
@@ -0,0 +1,2 @@
+export { List as Root } from "./List/List";
+export { ListItem } from "./ListItem/ListItem";
diff --git a/packages/cyberstorm/src/newComponents/Menu/Menu.module.css b/packages/cyberstorm/src/newComponents/Menu/Menu.module.css
new file mode 100644
index 000000000..d6ca52bd4
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Menu/Menu.module.css
@@ -0,0 +1,24 @@
+.menuRoot {
+ width: 80%;
+ height: 100%;
+}
+
+.menuWrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ align-items: flex-start;
+ width: 100%;
+ height: 100%;
+ padding: 1.5rem;
+ padding-top: 7rem;
+ background: #15152d;
+}
+
+.menuCloseButton {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ width: 2.75rem;
+ height: 2.75rem;
+}
diff --git a/packages/cyberstorm/src/newComponents/Menu/Menu.tsx b/packages/cyberstorm/src/newComponents/Menu/Menu.tsx
new file mode 100644
index 000000000..6a8972321
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Menu/Menu.tsx
@@ -0,0 +1,55 @@
+import { ReactNode } from "react";
+
+import styles from "./Menu.module.css";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faXmark } from "@fortawesome/free-solid-svg-icons";
+import { NewButton, NewIcon } from "../..";
+import {
+ FramePopoverProps,
+ Frame,
+} from "../../primitiveComponents/Frame/Frame";
+
+interface Props extends Omit {
+ trigger: ReactNode;
+ controls?: ReactNode;
+}
+
+// TODO: Add storybook story
+// TODO: Add support for color, size and text systems
+export function Menu(props: Props) {
+ return (
+ <>
+ {props.trigger}
+
+ {props.controls ? (
+ props.controls
+ ) : (
+
+
+
+
+
+ )}
+
+ {props.children}
+
+ >
+ );
+}
+
+Menu.displayName = "Menu";
diff --git a/packages/cyberstorm/src/newComponents/Modal/Modal.module.css b/packages/cyberstorm/src/newComponents/Modal/Modal.module.css
new file mode 100644
index 000000000..673e2afa8
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Modal/Modal.module.css
@@ -0,0 +1,28 @@
+.modalRoot {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ border: 1px solid var(--color-6);
+ border-radius: var(--frame-modal-border-radius);
+ background: var(--color-2);
+ transform: translate(-50%, -50%);
+}
+
+.modalWrapper {
+ display: flex;
+ flex-direction: column;
+ gap: var(--frame-modal-gap);
+ align-items: flex-start;
+ align-self: stretch;
+ justify-content: center;
+ padding: var(--frame-modal-padding);
+ background: transparent;
+}
+
+.modalCloseButton {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ width: 2.75rem;
+ height: 2.75rem;
+}
diff --git a/packages/cyberstorm/src/newComponents/Modal/Modal.tsx b/packages/cyberstorm/src/newComponents/Modal/Modal.tsx
new file mode 100644
index 000000000..c788bf54c
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Modal/Modal.tsx
@@ -0,0 +1,44 @@
+import { ReactNode } from "react";
+import { Frame, FrameModalProps } from "../../primitiveComponents/Frame/Frame";
+import styles from "./Modal.module.css";
+import { NewButton, NewIcon } from "../..";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faXmark } from "@fortawesome/free-solid-svg-icons";
+
+interface Props extends Omit {
+ trigger: ReactNode;
+}
+
+// TODO: Add storybook story
+export function Modal(props: Props) {
+ return (
+ <>
+ {props.trigger}
+
+
+
+
+
+
+ {props.children}
+
+ >
+ );
+}
+
+Modal.displayName = "Modal";
diff --git a/packages/cyberstorm/src/newComponents/Select/Select.module.css b/packages/cyberstorm/src/newComponents/Select/Select.module.css
new file mode 100644
index 000000000..38a86810a
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Select/Select.module.css
@@ -0,0 +1,60 @@
+.label {
+ font-weight: var(--font-weight-regular);
+}
+
+.content {
+ z-index: 999;
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap--2);
+ min-width: var(--radix-popper-anchor-width);
+ max-width: max-content;
+ padding: var(--space--8) 0;
+ border: var(--space--px) var(--color-surface--6) solid;
+ border-radius: var(--border-radius--8);
+ overflow: hidden;
+ color: var(--text-color);
+ background-color: var(--color-surface--2);
+ box-shadow: var(--box-shadow-default);
+ animation: fade-in var(--animation-length-l) ease;
+}
+
+.item {
+ display: flex;
+ flex-direction: row;
+
+ gap: var(--gap--12);
+ align-items: center;
+ padding: var(--space--12) var(--space--16);
+ outline: none;
+ cursor: pointer;
+}
+
+.item:hover,
+.item:focus {
+ background-color: var(--color-surface--6, var(--color-primary, red));
+}
+
+.content__wide {
+ justify-content: space-between;
+}
+
+.trigger {
+ justify-content: space-between;
+}
+
+.iconAndLabel {
+ display: flex;
+ gap: var(--gap--16);
+ align-items: center;
+}
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
diff --git a/packages/cyberstorm/src/newComponents/Select/Select.tsx b/packages/cyberstorm/src/newComponents/Select/Select.tsx
new file mode 100644
index 000000000..8fbf578db
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Select/Select.tsx
@@ -0,0 +1,121 @@
+import React from "react";
+import styles from "./Select.module.css";
+import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import {
+ Root,
+ Content,
+ Item,
+ Portal,
+ Trigger,
+ Viewport,
+} from "@radix-ui/react-select";
+import { classnames } from "../../utils/utils";
+import { Container, NewButton, NewIcon } from "../..";
+
+type SelectOption = {
+ value: T;
+ label?: string;
+ leftIcon?: JSX.Element;
+};
+
+type _SelectProps = {
+ variant?: "default" | "accent" | "wide";
+ defaultOpen?: boolean;
+ icon?: JSX.Element;
+ onChange?: (val: T) => void;
+ options: SelectOption[];
+ placeholder?: string;
+ value?: string;
+ rootClasses?: string;
+};
+
+export type SelectProps = _SelectProps &
+ Omit, keyof _SelectProps>;
+
+// TODO: COLOR SYSTEM IMPLEMENTATION IS MISSING IN CSS
+// TODO: Rework to use regular select preferrably
+// or atleast ensure a11y stuff works
+export function Select(props: SelectProps) {
+ const {
+ defaultOpen = false,
+ icon = (
+
+
+
+ ),
+ options,
+ onChange,
+ placeholder = "Select",
+ value,
+ ...forwardedProps
+ } = props;
+
+ const selectItemElements = options ? mapSelectData(options) : null;
+ const selectedOption = options?.find((o) => o.value === value);
+
+ return (
+
+
+
+
+ {selectedOption?.leftIcon ? (
+
+ {selectedOption?.leftIcon}
+
+ ) : null}
+ {selectedOption?.label ?? placeholder}
+ {!selectedOption?.label && !selectedOption?.leftIcon
+ ? selectedOption?.value
+ : null}
+
+ {icon}
+
+
+
+
+
+ {selectItemElements}
+
+
+
+ );
+}
+
+Select.displayName = "Select";
+
+const mapSelectData = (options: SelectOption[]) => {
+ return options.map((option, index) => (
+ -
+
+
+ {option.leftIcon}
+
+ {option.label}
+ {!option.label && !option.leftIcon ? option.value : null}
+
+
+ ));
+};
diff --git a/packages/cyberstorm/src/newComponents/Tag/Tag.tsx b/packages/cyberstorm/src/newComponents/Tag/Tag.tsx
new file mode 100644
index 000000000..ffd25aa82
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/Tag/Tag.tsx
@@ -0,0 +1,52 @@
+import styles from "../../sharedComponentStyles/TagStyles/Tag.module.css";
+import React from "react";
+import { Frame, FrameWindowProps } from "../../primitiveComponents/Frame/Frame";
+import { classnames } from "../../utils/utils";
+
+interface TagProps extends Omit {
+ dark?: boolean;
+ hoverable?: boolean;
+}
+
+export const Tag = React.forwardRef(
+ (props: TagProps, forwardedRef) => {
+ const {
+ children,
+ rootClasses,
+ csSize = "m",
+ csColor = "surface",
+ // Consume this prop since tag doesn't have other variants
+ csVariant,
+ dark,
+ hoverable,
+ ...forwardedProps
+ } = props;
+ const fProps = forwardedProps as FrameWindowProps;
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+Tag.displayName = "Tag";
diff --git a/packages/cyberstorm/src/newComponents/TextInput/TextInput.module.css b/packages/cyberstorm/src/newComponents/TextInput/TextInput.module.css
new file mode 100644
index 000000000..ee466e35d
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/TextInput/TextInput.module.css
@@ -0,0 +1,126 @@
+.wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: auto;
+ color: var(--input-wrapper-text-color);
+
+ --input-wrapper-text-color: var(--color-primary);
+}
+
+.wrapper[data-variant="default"] {
+ --input-wrapper-text-color: var(--color-5);
+}
+
+.wrapper[data-variant="primary"] {
+ --input-wrapper-text-color: var(--color-primary);
+}
+
+.wrapper[data-variant="secondary"] {
+ --input-wrapper-text-color: var(--color-secondary);
+}
+
+.wrapper[data-variant="tertiary"] {
+ --input-wrapper-text-color: var(--color-tertiary);
+}
+
+.wrapper[data-variant="accent"] {
+ --input-wrapper-text-color: var(--color-accent);
+}
+
+.wrapper > input {
+ outline: none;
+}
+
+.textInput {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ width: 100%;
+ padding: var(--space--8) var(--space--14);
+ border: var(--border-width--2) solid var(--border-color);
+ border-radius: var(--border-radius--8);
+ color: var(--input-text-color);
+ background-color: var(--color-surface--4);
+ cursor: text;
+ transition: ease-out var(--animation-length-xs);
+
+ --border-color: transparent;
+ --input-text-color: var(--color-primary);
+}
+
+.textInput:hover {
+ --border-color: var(--color-border--highlight);
+}
+
+.textInput:focus-within {
+ color: var(--color-text--default);
+ background-color: var(--color-black);
+
+ --border-color: var(--color-7);
+}
+
+.wrapper[data-variant="default"] > .textInput {
+ --input-text-color: var(--color-5);
+}
+
+.wrapper[data-variant="primary"] > .textInput {
+ --input-text-color: var(--color-primary);
+}
+
+.wrapper[data-variant="secondary"] > .textInput {
+ --input-text-color: var(--color-secondary);
+}
+
+.wrapper[data-variant="tertiary"] > .textInput {
+ --input-text-color: var(--color-tertiary);
+}
+
+.wrapper[data-variant="accent"] > .textInput {
+ --input-text-color: var(--color-accent);
+}
+
+/* DESIGN SYSTEM TODO: Add support for other variants and colors */
+
+.textInput::placeholder {
+ color: var(--color-text--tertiary);
+}
+
+.textInput[data-size="m"] {
+ height: var(--space--44);
+ padding: var(--space--12) calc(var(--space--16) + var(--right-padding-bonus, 0px)) var(--space--12) calc(var(--space--16) + var(--left-padding-bonus, 0px));
+}
+
+.textInput[data-size="s"] {
+ height: var(--space--36);
+ padding: var(--space--10) calc(var(--space--16) + var(--right-padding-bonus, 0px)) var(--space--10) calc(var(--space--16) + var(--left-padding-bonus, 0px));
+}
+
+.hasLeftIcon {
+ --left-padding-bonus: var(--space--24);
+}
+
+.hasClearValue {
+ --right-padding-bonus: var(--space--16);
+}
+
+.leftIcon {
+ position: absolute;
+ margin: var(--space--10) var(--space--16);
+ color: var(--color-accent);
+ pointer-events: none;
+}
+
+.rightIcon {
+ position: absolute;
+ padding: var(--space--14) var(--space--16);
+}
+
+.clearValueButton {
+ position: absolute;
+ right: var(--space--16);
+ display: flex;
+ color: var(--color-accent);
+ background: inherit;
+ opacity: 0.5;
+}
diff --git a/packages/cyberstorm/src/newComponents/TextInput/TextInput.tsx b/packages/cyberstorm/src/newComponents/TextInput/TextInput.tsx
new file mode 100644
index 000000000..d502aacbf
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/TextInput/TextInput.tsx
@@ -0,0 +1,103 @@
+import styles from "./TextInput.module.css";
+import React from "react";
+import {
+ Input,
+ InputTextInputProps,
+} from "../../primitiveComponents/Input/Input";
+import { classnames } from "../../utils/utils";
+import { Frame } from "../../primitiveComponents/Frame/Frame";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faCircleXmark } from "@fortawesome/free-solid-svg-icons";
+import { Actionable } from "../../primitiveComponents/Actionable/Actionable";
+import { colors, variants } from "../../primitiveComponents/utils/utils";
+import { NewIcon } from "../..";
+
+interface TextInputProps extends Omit {
+ leftIcon?: JSX.Element;
+ rightIcon?: JSX.Element;
+ enterHook?: (value: string | number | readonly string[]) => string | void;
+ clearValue?: () => void;
+ wrapperColor?: colors;
+ wrapperVariant?: variants;
+}
+
+// TODO: Finish the styles conversion to new system
+export const TextInput = React.forwardRef(
+ (props: TextInputProps, forwardedRef) => {
+ const {
+ children,
+ leftIcon,
+ rightIcon,
+ clearValue,
+ enterHook,
+ wrapperColor = "purple",
+ wrapperVariant = "tertiary",
+ rootClasses,
+ csColor = "purple",
+ csSize = "m",
+ csVariant = "primary",
+ ...forwardedProps
+ } = props;
+ const fProps = forwardedProps as InputTextInputProps;
+ const onEnter = (e: React.KeyboardEvent) => {
+ if (fProps.value && enterHook && e.key === "Enter") {
+ enterHook(fProps.value);
+ }
+ };
+ return (
+
+ {leftIcon ? (
+
+ {leftIcon}
+
+ ) : null}
+
+ {children}
+
+ {clearValue && fProps.value !== "" ? (
+ clearValue()}
+ rootClasses={styles.clearValueButton}
+ tooltipText="Clear"
+ tooltipSide="left"
+ csColor={csColor}
+ csVariant={"default"}
+ aria-label="Clear search input"
+ >
+
+
+
+
+ ) : null}
+
+ );
+ }
+);
+
+TextInput.displayName = "TextInput";
diff --git a/packages/cyberstorm/src/primitiveComponents/Actionable/Actionable.tsx b/packages/cyberstorm/src/primitiveComponents/Actionable/Actionable.tsx
new file mode 100644
index 000000000..86717d9ae
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Actionable/Actionable.tsx
@@ -0,0 +1,125 @@
+import {
+ CyberstormLink,
+ CyberstormLinkIds,
+} from "../../components/Links/Links";
+import React from "react";
+import { ThunderstoreLinkProps } from "../../components/Links/LinkingProvider";
+import { PrimitiveComponentDefaultProps, TooltipWrapper } from "../utils/utils";
+import { classnames } from "../../utils/utils";
+
+export interface ActionableButtonProps
+ extends React.ButtonHTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "button";
+}
+
+export interface ActionableLinkProps
+ extends React.AnchorHTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "link";
+ href: string;
+}
+
+export interface ActionableCyberstormLinkProps
+ extends React.AnchorHTMLAttributes,
+ PrimitiveComponentDefaultProps,
+ ThunderstoreLinkProps {
+ primitiveType: "cyberstormLink";
+ linkId: CyberstormLinkIds;
+}
+
+export const Actionable = React.forwardRef<
+ HTMLButtonElement | HTMLAnchorElement,
+ ActionableButtonProps | ActionableLinkProps | ActionableCyberstormLinkProps
+>(
+ (
+ props:
+ | ActionableButtonProps
+ | ActionableLinkProps
+ | ActionableCyberstormLinkProps,
+ forwardedRef
+ ) => {
+ const {
+ children,
+ primitiveType,
+ rootClasses,
+ csColor,
+ csVariant,
+ csSize,
+ csTextStyles,
+ tooltipText,
+ tooltipSide,
+ ...forwardedProps
+ } = props;
+
+ if (primitiveType === "button") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const fProps = forwardedProps as ActionableButtonProps;
+
+ return (
+
+
+
+ );
+ }
+ if (primitiveType === "link") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const fProps = forwardedProps as ActionableLinkProps;
+ return (
+
+
+ {children}
+
+
+ );
+ }
+ if (primitiveType === "cyberstormLink") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const { linkId, ...strippedForwardedProps } =
+ forwardedProps as ActionableCyberstormLinkProps;
+ return (
+
+
+ {children}
+
+
+ );
+ }
+ return Errored actionable
;
+ }
+);
+
+Actionable.displayName = "Actionable";
diff --git a/packages/cyberstorm/src/primitiveComponents/Frame/Display.module.css b/packages/cyberstorm/src/primitiveComponents/Frame/Display.module.css
new file mode 100644
index 000000000..6c001498a
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Frame/Display.module.css
@@ -0,0 +1,37 @@
+.display {
+ font-weight: 700;
+ font-family: inter, sans-serif;
+ font-style: normal;
+ line-height: normal;
+}
+
+.display[data-stylelevel="1"] {
+ font-weight: var(--font-weight-heavy);
+ font-size: calc(var(--space--54) * var(--scaling, 1));
+ font-family: Hubot-Sans, sans-serif;
+ line-height: 115%; /* 62.1px */
+}
+
+.display[data-stylelevel="2"] {
+ font-weight: var(--font-weight-heavy);
+ font-size: calc(var(--space--51) * var(--scaling, 1));
+ font-family: Hubot-Sans, sans-serif;
+}
+
+.display[data-stylelevel="3"] {
+ font-weight: 700;
+ font-size: calc(var(--space--38) * var(--scaling, 1));
+ font-family: Hubot-Sans, sans-serif;
+}
+
+.display[data-stylelevel="4"] {
+ font-size: calc(var(--space--16) * var(--scaling, 1));
+}
+
+.display[data-stylelevel="5"] {
+ font-size: calc(var(--space--14) * var(--scaling, 1));
+}
+
+.display[data-stylelevel="6"] {
+ font-size: calc(var(--space--12) * var(--scaling, 1));
+}
diff --git a/packages/cyberstorm/src/primitiveComponents/Frame/Frame.tsx b/packages/cyberstorm/src/primitiveComponents/Frame/Frame.tsx
new file mode 100644
index 000000000..79d0a8d29
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Frame/Frame.tsx
@@ -0,0 +1,490 @@
+import popooverStyles from "./Popover.module.css";
+import modalStyles from "./Modal.module.css";
+import headingStyles from "./Heading.module.css";
+import displayStyles from "./Display.module.css";
+import iconStyles from "./Icon.module.css";
+import { classnames } from "./../../utils/utils";
+import React from "react";
+import { PrimitiveComponentDefaultProps, TooltipWrapper } from "../utils/utils";
+import { Children, cloneElement } from "react";
+
+export interface FrameWindowProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "window";
+}
+
+export interface FrameListProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "list";
+}
+
+export interface FrameListItemProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "listItem";
+}
+
+// TODO: Turn this into the new "attachable window", used for dropdowns etc
+export interface FrameFloaterProps extends PrimitiveComponentDefaultProps {
+ primitiveType: "floater";
+}
+
+export interface FrameHeadingProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "heading";
+ csStyleLevel: "1" | "2" | "3" | "4" | "5" | "6";
+ csLevel: "1" | "2" | "3" | "4" | "5" | "6";
+}
+
+export interface FrameDisplayProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "display";
+ csStyleLevel: "1" | "2" | "3" | "4" | "5" | "6";
+ csLevel: "1" | "2" | "3" | "4" | "5" | "6";
+}
+
+export interface FrameTextProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "text";
+}
+
+export interface FramePopoverProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "popover";
+ popoverId: string;
+ wrapperClasses?: string;
+ noWrapper?: boolean;
+}
+
+export interface FrameModalProps
+ extends React.HTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "modal";
+ popoverId: string;
+ wrapperClasses?: string;
+ noWrapper?: boolean;
+}
+
+export interface FrameIconProps
+ extends React.HTMLAttributes,
+ Omit {
+ children?: JSX.Element | JSX.Element[];
+ primitiveType: "icon";
+ wrapperClasses?: string;
+ noWrapper?: boolean;
+ csMode?: "default" | "inline";
+}
+
+export const Frame = React.forwardRef<
+ | HTMLDivElement
+ | HTMLUListElement
+ | HTMLLIElement
+ | HTMLHeadingElement
+ | HTMLParagraphElement
+ | HTMLSpanElement
+ | SVGElement,
+ | FrameWindowProps
+ | FrameListProps
+ | FrameListItemProps
+ | FrameFloaterProps
+ | FrameHeadingProps
+ | FrameDisplayProps
+ | FrameTextProps
+ | FramePopoverProps
+ | FrameModalProps
+ | FrameIconProps
+>(
+ (
+ props:
+ | FrameWindowProps
+ | FrameListProps
+ | FrameListItemProps
+ | FrameFloaterProps
+ | FrameHeadingProps
+ | FrameDisplayProps
+ | FrameTextProps
+ | FramePopoverProps
+ | FrameModalProps
+ | FrameIconProps,
+ forwardedRef
+ ) => {
+ const {
+ children,
+ primitiveType,
+ csTextStyles,
+ csColor,
+ csVariant,
+ csSize,
+ rootClasses,
+ tooltipText,
+ tooltipSide,
+ ...forwardedProps
+ } = props;
+ if (primitiveType === "window") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const fProps = forwardedProps as FrameWindowProps;
+ return (
+
+
+ {children}
+
+
+ );
+ }
+ if (primitiveType === "list") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const fProps = forwardedProps as FrameListProps;
+ return (
+
+
+
+ );
+ }
+ if (primitiveType === "listItem") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const fProps = forwardedProps as FrameListItemProps;
+ return (
+
+
+ {children}
+
+
+ );
+ }
+ if (primitiveType === "heading" || primitiveType == "display") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const { csLevel, csStyleLevel, ...strippedForwardedProps } =
+ forwardedProps as FrameHeadingProps | FrameDisplayProps;
+ const primitiveStyles =
+ primitiveType === "heading"
+ ? headingStyles.heading
+ : displayStyles.display;
+ let element;
+ if (csLevel === "1") {
+ element = (
+
+ {children}
+
+ );
+ } else if (csLevel === "2") {
+ element = (
+
+ {children}
+
+ );
+ } else if (csLevel === "3") {
+ element = (
+
+ {children}
+
+ );
+ } else if (csLevel === "4") {
+ element = (
+
+ {children}
+
+ );
+ } else if (csLevel === "5") {
+ element = (
+
+ {children}
+
+ );
+ } else if (csLevel === "6") {
+ element = (
+
+ {children}
+
+ );
+ }
+ return (
+
+ {element}
+
+ );
+ }
+ if (primitiveType === "text") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const textProps = forwardedProps as FrameTextProps;
+ return (
+
+
+ {children}
+
+
+ );
+ }
+ if (primitiveType === "popover") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const {
+ wrapperClasses,
+ popoverId,
+ noWrapper,
+ ...strippedForwardedProps
+ } = forwardedProps as FramePopoverProps;
+ return (
+
+
+ {noWrapper ? (
+ children
+ ) : (
+
{children}
+ )}
+
+
+ );
+ }
+ if (primitiveType === "modal") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const {
+ wrapperClasses,
+ popoverId,
+ noWrapper,
+ ...strippedForwardedProps
+ } = forwardedProps as FrameModalProps;
+ return (
+
+
+ {noWrapper ? (
+ children
+ ) : (
+
+ )}
+
+
+ );
+ }
+ if (primitiveType === "icon") {
+ if (!children) {
+ return null;
+ }
+
+ const { wrapperClasses, noWrapper, csMode, ...strippedForwardedProps } =
+ forwardedProps as FrameIconProps;
+
+ let svgFProps: (Partial & React.Attributes) | null | undefined =
+ null;
+ if (noWrapper) {
+ svgFProps = { ...strippedForwardedProps };
+ }
+
+ const svgIconRef = forwardedRef as React.ForwardedRef;
+
+ const clones = Children.map(children, (child) =>
+ cloneElement(child, {
+ className: classnames(
+ child.props.className,
+ iconStyles.icon,
+ noWrapper && csMode === "inline" ? iconStyles.inline : null,
+ ...(csTextStyles ? csTextStyles : []),
+ rootClasses
+ ),
+ ref: noWrapper ? svgIconRef : null,
+ "data-color": csColor ? csColor : null,
+ "data-variant": csVariant ? csVariant : null,
+ "data-size": csSize ? csSize : null,
+ ...svgFProps,
+ })
+ );
+
+ let content = null;
+
+ if (noWrapper) {
+ content = <>{clones}>;
+ } else if (csMode === "inline") {
+ const spanIconRef = forwardedRef as React.ForwardedRef;
+ content = (
+
+ {clones}
+
+ );
+ } else {
+ const divIconRef = forwardedRef as React.ForwardedRef;
+ content = (
+
+ {clones}
+
+ );
+ }
+
+ return (
+
+ {content}
+
+ );
+ }
+ return Errored frame
;
+ }
+);
+
+Frame.displayName = "Frame";
diff --git a/packages/cyberstorm/src/primitiveComponents/Frame/Heading.module.css b/packages/cyberstorm/src/primitiveComponents/Frame/Heading.module.css
new file mode 100644
index 000000000..c2879194d
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Frame/Heading.module.css
@@ -0,0 +1,34 @@
+.heading {
+ font-weight: 700;
+ font-family: inter, sans-serif;
+ font-style: normal;
+ line-height: normal;
+}
+
+.heading[data-stylelevel="1"] {
+ font-weight: var(--font-weight-heavy);
+ font-size: var(--space--54);
+ font-family: Hubot-Sans, sans-serif;
+ line-height: 115%; /* 62.1px */
+}
+
+.heading[data-stylelevel="2"] {
+ font-size: var(--space--32);
+ line-height: 140%; /* 44.8px */
+}
+
+.heading[data-stylelevel="3"] {
+ font-size: var(--space--20);
+}
+
+.heading[data-stylelevel="4"] {
+ font-size: var(--space--16);
+}
+
+.heading[data-stylelevel="5"] {
+ font-size: var(--space--14);
+}
+
+.heading[data-stylelevel="6"] {
+ font-size: var(--space--12);
+}
diff --git a/packages/cyberstorm/src/primitiveComponents/Frame/Icon.module.css b/packages/cyberstorm/src/primitiveComponents/Frame/Icon.module.css
new file mode 100644
index 000000000..a1c870b71
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Frame/Icon.module.css
@@ -0,0 +1,26 @@
+/* Wrapper, not necessary */
+.iconWrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.iconWrapper svg,
+.icon {
+ display: flex;
+
+ flex-shrink: 0;
+ fill: currentcolor;
+ stroke: currentcolor;
+ block-size: 100%;
+ inline-size: auto;
+ min-inline-size: 100%;
+
+ stroke-width: 0;
+}
+
+.inline {
+ display: inline-flex;
+ height: 1em;
+ min-inline-size: 1em;
+}
diff --git a/packages/cyberstorm/src/primitiveComponents/Frame/Modal.module.css b/packages/cyberstorm/src/primitiveComponents/Frame/Modal.module.css
new file mode 100644
index 000000000..153dadbb9
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Frame/Modal.module.css
@@ -0,0 +1,18 @@
+.modal {
+ width: 100%;
+ height: 100%;
+ background: rgba(0 0 0 / 0.6);
+ backdrop-filter: blur(10px);
+}
+
+.modal[data-size="s"] {
+ --frame-modal-gap: 1rem;
+ --frame-modal-padding: 1.5rem 2.25rem;
+ --frame-modal-border-radius: 0.25rem;
+}
+
+.modal[data-size="m"] {
+ --frame-modal-gap: 2rem;
+ --frame-modal-padding: 3rem 5.5rem;
+ --frame-modal-border-radius: 0.5rem;
+}
diff --git a/packages/cyberstorm/src/primitiveComponents/Frame/Popover.module.css b/packages/cyberstorm/src/primitiveComponents/Frame/Popover.module.css
new file mode 100644
index 000000000..f53e14f40
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Frame/Popover.module.css
@@ -0,0 +1,11 @@
+.popover[data-size="s"] {
+ --frame-popover-gap: 1rem;
+ --frame-popover-padding: 1.5rem 2.25rem;
+ --frame-popover-border-radius: 0.25rem;
+}
+
+.popover[data-size="m"] {
+ --frame-popover-gap: 2rem;
+ --frame-popover-padding: 3rem 5.5rem;
+ --frame-popover-border-radius: 0.5rem;
+}
diff --git a/packages/cyberstorm/src/primitiveComponents/Input/Input.tsx b/packages/cyberstorm/src/primitiveComponents/Input/Input.tsx
new file mode 100644
index 000000000..9228ff19f
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/Input/Input.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+import { PrimitiveComponentDefaultProps, TooltipWrapper } from "../utils/utils";
+import { classnames } from "../../utils/utils";
+
+export interface InputTextInputProps
+ extends React.InputHTMLAttributes,
+ PrimitiveComponentDefaultProps {
+ primitiveType: "textInput";
+}
+
+export const Input = React.forwardRef(
+ (props: InputTextInputProps, forwardedRef) => {
+ const {
+ children,
+ primitiveType,
+ rootClasses,
+ csTextStyles,
+ csColor,
+ csVariant,
+ csSize,
+ tooltipText,
+ tooltipSide,
+ ...forwardedProps
+ } = props;
+
+ if (primitiveType === "textInput") {
+ const fRef = forwardedRef as React.ForwardedRef;
+ const fProps = forwardedProps as InputTextInputProps;
+
+ return (
+
+
+ {children}
+
+
+ );
+ }
+ return Errored Input
;
+ }
+);
+
+Input.displayName = "Input";
diff --git a/packages/cyberstorm/src/primitiveComponents/utils/utils.tsx b/packages/cyberstorm/src/primitiveComponents/utils/utils.tsx
new file mode 100644
index 000000000..4233eb6df
--- /dev/null
+++ b/packages/cyberstorm/src/primitiveComponents/utils/utils.tsx
@@ -0,0 +1,65 @@
+import { PropsWithChildren } from "react";
+import { Tooltip } from "../..";
+
+export type variants =
+ | "default"
+ | "primary"
+ | "secondary"
+ | "tertiary"
+ | "accent"
+ | "special";
+export type colors =
+ | "surface"
+ | "surface-alpha"
+ | "blue"
+ | "pink"
+ | "red"
+ | "orange"
+ | "green"
+ | "yellow"
+ | "purple"
+ | "cyber-green";
+export type sizes = "xxs" | "xs" | "s" | "m" | "l";
+
+const textStyleOptions = [
+ "lineHeightAuto",
+ "lineHeightBody",
+ "fontSizeXXS",
+ "fontSizeXS",
+ "fontSizeS",
+ "fontSizeM",
+ "fontSizeL",
+ "fontWeightRegular",
+ "fontWeightMedium",
+ "fontWeightSemiBold",
+ "fontWeightBold",
+ "fontFamilyInter",
+ "fontFamilyHubot",
+] as const;
+
+// eslint-disable-next-line prettier/prettier
+export type TextStyles = typeof textStyleOptions[number];
+
+interface TooltipWrapperProps extends PropsWithChildren {
+ tooltipText?: string;
+ tooltipSide?: "bottom" | "left" | "right" | "top";
+}
+
+export interface PrimitiveComponentDefaultProps
+ extends PropsWithChildren,
+ TooltipWrapperProps {
+ csColor?: colors;
+ csVariant?: variants;
+ csSize?: sizes;
+ csTextStyles?: TextStyles[];
+ rootClasses?: string;
+}
+
+export const TooltipWrapper = (props: TooltipWrapperProps) =>
+ props.tooltipText ? (
+
+ {props.children}
+
+ ) : (
+ <>{props.children}>
+ );
diff --git a/packages/cyberstorm/src/sharedComponentStyles/ButtonStyles/Button.module.css b/packages/cyberstorm/src/sharedComponentStyles/ButtonStyles/Button.module.css
new file mode 100644
index 000000000..482be211c
--- /dev/null
+++ b/packages/cyberstorm/src/sharedComponentStyles/ButtonStyles/Button.module.css
@@ -0,0 +1,187 @@
+/* Common Button styles */
+.button {
+ display: flex;
+ flex: none;
+ flex-direction: row;
+
+ gap: var(--button-gap);
+ align-items: center;
+
+ height: var(--button-height);
+
+ padding: var(--button-padding);
+ border: var(--button-border);
+
+ border-radius: var(--button-border-radius);
+ color: var(--button-color);
+
+ text-align: center;
+ background-color: var(--button-bg-color);
+ user-select: none;
+}
+
+.button:where(:not(:focus)) {
+ transition: var(--button-animation-length);
+
+ --button-animation-length: ease-in-out var(--animation-length-s);
+}
+
+/* Button variants */
+
+/* default */
+
+.button[data-variant="default"] {
+ --button-color: var(--color-11);
+ --button-bg-color: var(--color-4);
+}
+
+.button[data-variant="default"]:hover {
+ --button-bg-color: var(--color-3);
+}
+
+.button[data-variant="default"]:active {
+ --button-bg-color: var(--color-2);
+}
+
+/* primary */
+
+.button[data-variant="primary"] {
+ --button-color: var(--color-text--default);
+ --button-bg-color: var(--color-surface--10);
+}
+
+.button[data-variant="primary"]:hover {
+ --button-bg-color: var(--color-surface--10--hover);
+}
+
+.button[data-variant="primary"]:active {
+ background-color: var(--button-bg-color-active);
+}
+
+/* secondary */
+
+.button[data-variant="secondary"] {
+ --button-color: var(--color-text--default);
+ --button-bg-color: var(--color-6);
+}
+
+.button[data-variant="secondary"]:hover {
+ --button-bg-color: var(--color-7);
+}
+
+.button[data-variant="secondary"]:active {
+ --button-bg-color: var(--color-5);
+}
+
+/* tertiary */
+
+.button[data-variant="tertiary"] {
+ --button-color: var(--color-text--default);
+ --button-bg-color: transparent;
+}
+
+.button[data-variant="tertiary"]:hover {
+ --button-bg-color: var(--color-6);
+}
+
+.button[data-variant="tertiary"]:active {
+ --button-bg-color: var(--color-5);
+}
+
+/* accent */
+
+.button[data-variant="accent"] {
+ --button-color: var(--color-2);
+ --button-bg-color: var(--color-7);
+}
+
+.button[data-variant="accent"]:hover {
+ --button-bg-color: var(--color-9);
+}
+
+.button[data-variant="accent"]:active {
+ --button-bg-color: var(--color-6);
+}
+
+/* special */
+
+.button[data-variant="special"] {
+ border: 2px solid transparent;
+ background:
+ linear-gradient(
+ 284deg,
+ hsl(275deg 64% 11% / 1) 0%,
+ hsl(156deg 46% 12% / 1) 100%
+ ) padding-box,
+ linear-gradient(
+ 284deg,
+ hsl(276deg 77% 54% / 1) 0%,
+ hsl(158deg 100% 57% / 1) 100%
+ ) border-box;
+}
+
+.button[data-variant="special"]:hover {
+ background:
+ linear-gradient(
+ 284deg,
+ hsl(203deg 80% 21% / 1) 0%,
+ hsl(155deg 46% 23% / 1) 100%
+ ) padding-box,
+ linear-gradient(
+ 284deg,
+ hsl(154deg 65% 67% / 1) 0%,
+ hsl(203deg 92% 63% / 1) 100%
+ ) border-box;
+}
+
+.button[data-variant="special"]:active {
+ background:
+ linear-gradient(
+ 284deg,
+ hsl(203deg 80% 21% / 1) 0%,
+ hsl(155deg 46% 23% / 1) 100%
+ ) padding-box,
+ linear-gradient(
+ 284deg,
+ hsl(154deg 65% 67% / 1) 0%,
+ hsl(203deg 92% 63% / 1) 100%
+ ) border-box;
+}
+
+/* Button sizes */
+
+/* xs */
+
+.button[data-size="xs"] {
+ --button-height: var(--space--30);
+ --button-padding: var(--space--12);
+ --button-gap: var(--gap--16);
+ --button-border-radius: var(--border-radius--8);
+}
+
+/* s */
+
+.button[data-size="s"] {
+ --button-height: var(--space--36);
+ --button-padding: var(--space--12);
+ --button-gap: var(--gap--16);
+ --button-border-radius: var(--border-radius--8);
+}
+
+/* m */
+
+.button[data-size="m"] {
+ --button-height: var(--space--44);
+ --button-padding: var(--space--12) var(--space--16);
+ --button-gap: var(--gap--16);
+ --button-border-radius: var(--border-radius--8);
+}
+
+/* l */
+
+.button[data-size="l"] {
+ --button-height: var(--space--44);
+ --button-padding: var(--space--12) var(--space--16);
+ --button-gap: var(--gap--16);
+ --button-border-radius: var(--border-radius--8);
+}
diff --git a/packages/cyberstorm/src/sharedComponentStyles/TagStyles/Tag.module.css b/packages/cyberstorm/src/sharedComponentStyles/TagStyles/Tag.module.css
new file mode 100644
index 000000000..47f196a42
--- /dev/null
+++ b/packages/cyberstorm/src/sharedComponentStyles/TagStyles/Tag.module.css
@@ -0,0 +1,60 @@
+.tag {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--tag-color);
+ background: var(--tag-background-color);
+
+ --tag-color: var(--color-primary);
+ --tag-background-color: var(--color-primary);
+}
+
+.tag[data-variant="default"] {
+ --tag-color: var(--color-1);
+ --tag-background-color: var(--color-7);
+}
+
+.tag[data-variant="default"].hoverable:hover {
+ --tag-background-color: var(--color-8);
+}
+
+.tag[data-variant="default"].dark {
+ --tag-color: var(--color-9);
+ --tag-background-color: var(--color-2);
+}
+
+.tag[data-variant="default"].dark.hoverable:hover {
+ --tag-background-color: var(--color-3);
+}
+
+/* As surface color has different text color, override here */
+
+.tag[data-variant="default"][data-color="surface"] {
+ --tag-color: var(--color-primary);
+ --tag-background-color: var(--color-9);
+}
+
+.tag[data-variant="default"][data-color="surface"].hoverable:hover {
+ --tag-background-color: hsl(240deg 44% 55% / 1);
+}
+
+.tag[data-variant="default"][data-color="surface"].dark {
+ --tag-color: var(--color-primary);
+ --tag-background-color: var(--color-surface-alpha--8);
+}
+
+.tag[data-variant="default"][data-color="surface"].dark.hoverable:hover {
+ --tag-background-color: var(--color-surface-alpha--9);
+}
+
+.tag[data-size="m"] {
+ gap: var(--space--8);
+ padding: var(--space--6) var(--border-radius--8);
+ border-radius: var(--border-radius--8);
+}
+
+.tag[data-size="s"] {
+ gap: var(--gap--4);
+ padding: var(--space--4) var(--space--6);
+ border-radius: var(--border-radius--6);
+}
diff --git a/packages/cyberstorm/src/utils/utils.ts b/packages/cyberstorm/src/utils/utils.ts
index dd0130244..5d832ab40 100644
--- a/packages/cyberstorm/src/utils/utils.ts
+++ b/packages/cyberstorm/src/utils/utils.ts
@@ -3,8 +3,11 @@ export const range = (start: number, end: number) => {
return Array.from({ length }, (_, idx) => idx + start);
};
-export const formatInteger = (inputNumber: number) => {
- return Intl.NumberFormat("en", { notation: "compact" }).format(inputNumber);
+export const formatInteger = (
+ inputNumber: number,
+ notation: "standard" | "scientific" | "engineering" | "compact" = "compact"
+) => {
+ return Intl.NumberFormat("en", { notation: notation }).format(inputNumber);
};
export const numberWithSpaces = (x: number) => {