diff --git a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/CategoryMenu.tsx b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/CategoryMenu.tsx
index 0da133118..249d5171d 100644
--- a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/CategoryMenu.tsx
+++ b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/CategoryMenu.tsx
@@ -4,11 +4,10 @@ import {
faSquareXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import * as Checkbox from "@radix-ui/react-checkbox";
import styles from "./FilterMenu.module.css";
import { CategorySelection, CATEGORY_STATES as STATES } from "../types";
-import { NewIcon } from "@thunderstore/cyberstorm";
+import { CycleButton, NewIcon } from "@thunderstore/cyberstorm";
import { classnames } from "@thunderstore/cyberstorm/src/utils/utils";
interface Props {
@@ -42,28 +41,24 @@ export const CategoryMenu = (props: Props) => {
))}
diff --git a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/OthersMenu.tsx b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/OthersMenu.tsx
index a254c6a2c..4537f48d7 100644
--- a/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/OthersMenu.tsx
+++ b/apps/cyberstorm-remix/app/commonComponents/PackageSearch/FilterMenus/OthersMenu.tsx
@@ -1,9 +1,8 @@
import { faSquare, faSquareCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import * as Checkbox from "@radix-ui/react-checkbox";
import styles from "./FilterMenu.module.css";
-import { NewIcon } from "@thunderstore/cyberstorm";
+import { CycleButton, NewIcon } from "@thunderstore/cyberstorm";
import { classnames } from "@thunderstore/cyberstorm/src/utils/utils";
interface Props {
@@ -33,15 +32,16 @@ export const OthersMenu = (props: Props) => {
)}
>
{label}
- setChecked(!checked)}
- className={styles.checkbox}
+ setChecked(!checked)}
+ rootClasses={styles.checkbox}
+ value={checked ? "on" : "off"}
+ noState
>
-
+
))}
diff --git a/packages/cyberstorm/src/index.ts b/packages/cyberstorm/src/index.ts
index cd240ab5b..1b7b1c573 100644
--- a/packages/cyberstorm/src/index.ts
+++ b/packages/cyberstorm/src/index.ts
@@ -105,6 +105,7 @@ export { CardCommunity } from "./newComponents/Card/CardCommunity/CardCommunity"
export { CardPackage } from "./newComponents/Card/CardPackage/CardPackage";
export { Link as NewLink } from "./newComponents/Link/Link/Link";
export { Button as NewButton } from "./newComponents/Button/Button";
+export { CycleButton } from "./newComponents/CycleButton/CycleButton";
export { BreadCrumbs as NewBreadCrumbs } from "./newComponents/BreadCrumbs/BreadCrumbs";
export { TextInput as NewTextInput } from "./newComponents/TextInput/TextInput";
export {
diff --git a/packages/cyberstorm/src/newComponents/CycleButton/CycleButton.css b/packages/cyberstorm/src/newComponents/CycleButton/CycleButton.css
new file mode 100644
index 000000000..a25d48993
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/CycleButton/CycleButton.css
@@ -0,0 +1,6 @@
+@layer components {
+ .ts-cyclebutton {
+ display: inline-flex;
+ align-items: center;
+ }
+}
diff --git a/packages/cyberstorm/src/newComponents/CycleButton/CycleButton.tsx b/packages/cyberstorm/src/newComponents/CycleButton/CycleButton.tsx
new file mode 100644
index 000000000..5a5a2f568
--- /dev/null
+++ b/packages/cyberstorm/src/newComponents/CycleButton/CycleButton.tsx
@@ -0,0 +1,85 @@
+import "./CycleButton.css";
+import React, { useState } from "react";
+import { classnames } from "../../utils/utils";
+import {
+ Actionable,
+ ActionableButtonProps,
+} from "../../primitiveComponents/Actionable/Actionable";
+
+interface CycleButtonProps
+ extends Omit {
+ noState?: boolean;
+ value?: string;
+ onInterract?: () => void;
+ options?: string[];
+ onValueChange?: (value: string) => void;
+}
+
+/**
+ * Button for cycling through values
+ * If you want to handle state outside of this component use the "noState" param
+ * Notes for handling state inside this component:
+ * - First value of the list will be the initial value
+ * - You can access the value from onValueChange. It's called each time value changes.
+ */
+export const CycleButton = React.forwardRef<
+ HTMLButtonElement,
+ CycleButtonProps
+>((props: CycleButtonProps, forwardedRef) => {
+ const {
+ children,
+ rootClasses,
+ noState = false,
+ onInterract,
+ options,
+ onValueChange,
+ ...forwardedProps
+ } = props;
+
+ if (noState) {
+ return (
+ onInterract() : undefined}
+ >
+ {children}
+
+ );
+ }
+
+ const initialValue = options && options.length > 0 ? options[0] : "";
+ const [currentValue, setCurrentValue] = useState(initialValue);
+
+ return (
+ {
+ if (options && options.length > 0) {
+ const currentValueIndex = options?.indexOf(currentValue);
+ if (
+ currentValueIndex === -1 ||
+ currentValueIndex === options.length - 1
+ ) {
+ setCurrentValue(options[0]);
+ } else {
+ setCurrentValue(options[currentValueIndex + 1]);
+ }
+ }
+ if (onInterract) {
+ onInterract();
+ }
+ }}
+ onChange={onValueChange ? () => onValueChange(currentValue) : undefined}
+ >
+ {children}
+
+ );
+});
+
+CycleButton.displayName = "CycleButton";