diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/func.tsx b/apps/frontend/ui/src/navigation/partials/OptionConcepts/func.tsx
new file mode 100644
index 0000000..499db14
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/func.tsx
@@ -0,0 +1,41 @@
+import type { JSX } from "react";
+
+import { MenuList } from "@lib-components";
+
+import { OptionLayoutTemplate } from "@app-ui/navigation/templates";
+import useOptionConceptsClasses from "@app-ui/navigation/partials/OptionConcepts/styles";
+import { MenuItemRadioTemplate } from "@app-ui/navigation/partials/OptionConcepts/template";
+import { useSelectionState } from "@app-ui/navigation/partials/OptionConcepts/hooks";
+
+type TProps = {
+ concepts: string[];
+ onSearch: (value: string) => void;
+};
+
+export default function OptionConcepts({
+ concepts,
+ onSearch,
+}: TProps): JSX.Element {
+ const classes = useOptionConceptsClasses();
+ const { checkedValues, onChange } = useSelectionState();
+ return (
+ {
+ onSearch(checkedValues.concept[0]);
+ }}
+ disabledSearch={checkedValues.concept.length === 0}
+ >
+
+ {concepts.map((concept) => (
+
+ ))}
+
+
+ );
+}
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/hooks.ts b/apps/frontend/ui/src/navigation/partials/OptionConcepts/hooks.ts
new file mode 100644
index 0000000..3d0dd7a
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/hooks.ts
@@ -0,0 +1,18 @@
+import { useState } from "react";
+import type { MenuProps as TMenuProps } from "@fluentui/react-components";
+
+function useSelectionState() {
+ const [checkedValues, setCheckedValues] = useState>({
+ concept: [],
+ });
+ const onChange: TMenuProps["onCheckedValueChange"] = (
+ _,
+ { name, checkedItems },
+ ) => {
+ setCheckedValues((s) => ({ ...s, [name]: checkedItems }));
+ };
+
+ return { checkedValues, onChange };
+}
+
+export { useSelectionState };
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/index.ts b/apps/frontend/ui/src/navigation/partials/OptionConcepts/index.ts
new file mode 100644
index 0000000..602f93e
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/index.ts
@@ -0,0 +1,3 @@
+import OptionConcepts from "@app-ui/navigation/partials/OptionConcepts/func";
+
+export default OptionConcepts;
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/stories.ts b/apps/frontend/ui/src/navigation/partials/OptionConcepts/stories.ts
new file mode 100644
index 0000000..28cd5bd
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/stories.ts
@@ -0,0 +1,34 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import OptionConcepts from "@app-ui/navigation/partials/OptionConcepts";
+
+const meta: Meta = {
+ title: "App/UI/Navigation/Partials/OptionConcepts",
+ component: OptionConcepts,
+ args: {
+ concepts: ["Partiality", "Signaling", "Connectivity", "Transformativity"],
+ onSearch: () => {},
+ },
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Index: Story = {};
+
+export const WithOverflow: Story = {
+ args: {
+ concepts: [
+ "Partiality",
+ "Signaling",
+ "Connectivity",
+ "Transformativity",
+ "Adaptability",
+ "Inclusivity",
+ "Interactivity",
+ "Reactivity",
+ "Sustainability",
+ ],
+ },
+};
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/styles.ts b/apps/frontend/ui/src/navigation/partials/OptionConcepts/styles.ts
new file mode 100644
index 0000000..bc7f86e
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/styles.ts
@@ -0,0 +1,13 @@
+import { makeStyles, EThemeDimensions, tokens } from "@lib-theme";
+
+const useOptionConceptsClasses = makeStyles({
+ list: {
+ width: "100%",
+ height: EThemeDimensions.M4,
+ overflow: "auto",
+ backgroundColor: tokens.colorNeutralBackground1,
+ borderRadius: tokens.borderRadiusMedium,
+ },
+});
+
+export default useOptionConceptsClasses;
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/MenuItemRadio/func.tsx b/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/MenuItemRadio/func.tsx
new file mode 100644
index 0000000..be548fc
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/MenuItemRadio/func.tsx
@@ -0,0 +1,15 @@
+import type { JSX } from "react";
+
+import { MenuItemRadio as MenuItemRadioOrigin } from "@lib-components";
+
+type TProps = {
+ value: string;
+};
+
+export default function MenuItemRadio({ value }: TProps): JSX.Element {
+ return (
+
+ {value}
+
+ );
+}
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/MenuItemRadio/index.ts b/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/MenuItemRadio/index.ts
new file mode 100644
index 0000000..40f575b
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/MenuItemRadio/index.ts
@@ -0,0 +1,3 @@
+import MenuItemRadio from "@app-ui/navigation/partials/OptionConcepts/template/MenuItemRadio/func";
+
+export default MenuItemRadio;
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/index.ts b/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/index.ts
new file mode 100644
index 0000000..9e7ebbd
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/template/index.ts
@@ -0,0 +1 @@
+export { default as MenuItemRadioTemplate } from "@app-ui/navigation/partials/OptionConcepts/template/MenuItemRadio";
diff --git a/apps/frontend/ui/src/navigation/partials/OptionConcepts/tests.tsx b/apps/frontend/ui/src/navigation/partials/OptionConcepts/tests.tsx
new file mode 100644
index 0000000..6257313
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/partials/OptionConcepts/tests.tsx
@@ -0,0 +1,65 @@
+import { render, screen, fireEvent } from "@tests-unit-browser";
+import "@testing-library/jest-dom";
+
+import OptionConcepts from "@app-ui/navigation/partials/OptionConcepts";
+
+describe("OptionConcepts", () => {
+ it("should render with given concepts", () => {
+ const onSearch = jest.fn((value: string) => value);
+
+ render(
+ ,
+ );
+
+ const menuList = screen.getByRole("menu");
+ expect(menuList).toBeInTheDocument();
+
+ const concept1 = screen.getByText("concept1");
+ expect(concept1).toBeInTheDocument();
+ const concept2 = screen.getByText("concept2");
+ expect(concept2).toBeInTheDocument();
+ const concept3 = screen.getByText("concept3");
+ expect(concept3).toBeInTheDocument();
+ });
+
+ it("should be able to select different concepts", () => {
+ const onSearch = jest.fn((value: string) => value);
+
+ render(
+ ,
+ );
+
+ const concept1 = screen.getByText("concept1");
+ expect(concept1).toBeInTheDocument();
+ const concept2 = screen.getByText("concept2");
+ expect(concept2).toBeInTheDocument();
+ const concept3 = screen.getByText("concept3");
+ expect(concept3).toBeInTheDocument();
+
+ const searchButton = screen.getByRole("button", { name: "Search" });
+ expect(searchButton).toBeInTheDocument();
+ expect(searchButton).toBeDisabled();
+
+ fireEvent.click(concept1);
+ expect(searchButton).toBeEnabled();
+
+ fireEvent.click(searchButton);
+ expect(onSearch).toHaveBeenCalledWith("concept1");
+
+ fireEvent.click(concept2);
+ fireEvent.click(searchButton);
+ expect(onSearch).toHaveBeenCalledWith("concept2");
+
+ fireEvent.click(concept3);
+ fireEvent.click(searchButton);
+ expect(onSearch).toHaveBeenCalledWith("concept3");
+
+ expect(onSearch).toHaveBeenCalledTimes(3);
+ });
+});
diff --git a/apps/frontend/ui/src/navigation/templates/OptionLayout/func.tsx b/apps/frontend/ui/src/navigation/templates/OptionLayout/func.tsx
new file mode 100644
index 0000000..2ed218b
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/templates/OptionLayout/func.tsx
@@ -0,0 +1,36 @@
+import type { JSX, ReactNode } from "react";
+
+import { Flex, Button } from "@lib-components";
+import { Subtitle2, Caption2 } from "@lib-theme";
+
+import useOptionLayoutClasses from "@app-ui/navigation/templates/OptionLayout/styles";
+
+type TProps = {
+ header: string;
+ subtitle: string;
+ children?: ReactNode;
+ onSearch: () => void;
+ disabledSearch: boolean;
+};
+
+export default function OptionLayout({
+ header,
+ subtitle,
+ children = undefined,
+ onSearch,
+ disabledSearch,
+}: TProps): JSX.Element {
+ const classes = useOptionLayoutClasses();
+ return (
+
+
+ {header}
+ {subtitle}
+
+ {children}
+
+
+ );
+}
diff --git a/apps/frontend/ui/src/navigation/templates/OptionLayout/index.ts b/apps/frontend/ui/src/navigation/templates/OptionLayout/index.ts
new file mode 100644
index 0000000..22f5e55
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/templates/OptionLayout/index.ts
@@ -0,0 +1,3 @@
+import OptionLayout from "@app-ui/navigation/templates/OptionLayout/func";
+
+export default OptionLayout;
diff --git a/apps/frontend/ui/src/navigation/templates/OptionLayout/styles.ts b/apps/frontend/ui/src/navigation/templates/OptionLayout/styles.ts
new file mode 100644
index 0000000..dcca2f4
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/templates/OptionLayout/styles.ts
@@ -0,0 +1,11 @@
+import { makeStyles, tokens, EThemeDimensions } from "@lib-theme";
+
+const useOptionLayoutClasses = makeStyles({
+ root: {
+ width: EThemeDimensions.L8,
+ backgroundColor: tokens.colorNeutralBackground2,
+ borderRadius: tokens.borderRadiusLarge,
+ },
+});
+
+export default useOptionLayoutClasses;
diff --git a/apps/frontend/ui/src/navigation/templates/index.ts b/apps/frontend/ui/src/navigation/templates/index.ts
new file mode 100644
index 0000000..0773614
--- /dev/null
+++ b/apps/frontend/ui/src/navigation/templates/index.ts
@@ -0,0 +1 @@
+export { default as OptionLayoutTemplate } from "@app-ui/navigation/templates/OptionLayout";
diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts
index 442c269..295896e 100644
--- a/libs/components/src/index.ts
+++ b/libs/components/src/index.ts
@@ -2,6 +2,8 @@ export { Divider } from "@fluentui/react-components";
export { Tab } from "@fluentui/react-components";
export { TabList } from "@fluentui/react-components";
export { Button } from "@fluentui/react-components";
+export { MenuList } from "@fluentui/react-components";
+export { MenuItemRadio } from "@fluentui/react-components";
export { Collapse } from "@fluentui/react-motion-components-preview";
diff --git a/package-lock.json b/package-lock.json
index cb1af74..c84b38e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"@fluentui/react-motion-components-preview": "^0.3.1",
"@griffel/react": "^1.5.25",
"@reduxjs/toolkit": "^2.2.8",
- "fluentui-helpers": "0.2.1",
+ "fluentui-helpers": "0.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2"
@@ -9951,9 +9951,9 @@
"dev": true
},
"node_modules/fluentui-helpers": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/fluentui-helpers/-/fluentui-helpers-0.2.1.tgz",
- "integrity": "sha512-yVTr0yz2ZQLwGX3bgph1pOt4zP7W3b30iNEyNFlWyOPuqUlxMl2JDEGeg/8QOcObsyPPY2D27svFoxz7QT4aag==",
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/fluentui-helpers/-/fluentui-helpers-0.3.0.tgz",
+ "integrity": "sha512-syhmFb/HbGzCL9T/dkeHbQ3CTeeBJuJfgitfK2Jxj7T+JseLztIZDgwdLmakJusqkbBLXdEuTO6KHhHyI1ociQ==",
"license": "MIT",
"peerDependencies": {
"@fluentui/react-components": "^9",
diff --git a/package.json b/package.json
index 84ac324..7deff09 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"@fluentui/react-motion-components-preview": "^0.3.1",
"@griffel/react": "^1.5.25",
"@reduxjs/toolkit": "^2.2.8",
- "fluentui-helpers": "0.2.1",
+ "fluentui-helpers": "0.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.1.2"