diff --git a/.changeset/poor-pants-tell.md b/.changeset/poor-pants-tell.md
new file mode 100644
index 0000000..c0dc48b
--- /dev/null
+++ b/.changeset/poor-pants-tell.md
@@ -0,0 +1,5 @@
+---
+"@zenml-io/react-component-library": minor
+---
+
+Initialize Sidebar Component
diff --git a/.storybook/assets/Appshell.tsx b/.storybook/assets/Appshell.tsx
new file mode 100644
index 0000000..37ff9cf
--- /dev/null
+++ b/.storybook/assets/Appshell.tsx
@@ -0,0 +1,12 @@
+import React, { ReactNode } from "react";
+
+export function AppShell({ children }: { children: ReactNode }) {
+ return (
+
+ );
+}
diff --git a/.storybook/assets/CPU.tsx b/.storybook/assets/CPU.tsx
new file mode 100644
index 0000000..c5f958e
--- /dev/null
+++ b/.storybook/assets/CPU.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+export default function CPU({ className = "" }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/.storybook/assets/icons.tsx b/.storybook/assets/icons.tsx
new file mode 100644
index 0000000..92f6fb3
--- /dev/null
+++ b/.storybook/assets/icons.tsx
@@ -0,0 +1,44 @@
+import React from "react";
+export function CPU({ className = "" }: { className?: string }) {
+ return (
+
+ );
+}
+
+export function CloseButton({ className = "" }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index cd70a32..4be460a 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -7,6 +7,13 @@ import "../src/index.css";
const preview: Preview = {
parameters: {
+ backgrounds: {
+ default: "light",
+ values: [
+ { name: "light", value: "#F9FAFB" },
+ { name: "dark", value: "#1F2937" }
+ ]
+ },
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
diff --git a/src/components/Sidebar/Sidebar.stories.tsx b/src/components/Sidebar/Sidebar.stories.tsx
new file mode 100644
index 0000000..28481ab
--- /dev/null
+++ b/src/components/Sidebar/Sidebar.stories.tsx
@@ -0,0 +1,83 @@
+import { Meta } from "@storybook/react";
+import React from "react";
+import {
+ Sidebar,
+ SidebarHeader,
+ SidebarItem,
+ SidebarItemContent,
+ SidebarBody,
+ SidebarHeaderImage,
+ SidebarHeaderTitle,
+ SidebarList
+} from "./index";
+import { CPU, CloseButton } from "../../../.storybook/assets/icons";
+import { StoryObj } from "@storybook/react";
+import { AppShell } from "../../../.storybook/assets/Appshell";
+
+const meta = {
+ title: "UI/Sidebar",
+ component: Sidebar,
+ parameters: {
+ layout: "fullscreen"
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ )
+ ],
+ tags: ["autodocs"]
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const defaultStory: Story = {
+ name: "Sidebar",
+ args: {
+ children: (
+ <>
+ } title="ZenML Tenant">
+
+
+
+ My Tenant
+
+
+
+
+
+
+ } label="Models" />
+
+
+
+
+
+
+ } label="Models" />
+
+
+
+
+
+
+ } label="Models" />
+
+
+
+
+
+
+
+ } label="Models" />
+
+
+
+
+ >
+ )
+ }
+};
diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx
new file mode 100644
index 0000000..de1141c
--- /dev/null
+++ b/src/components/Sidebar/Sidebar.tsx
@@ -0,0 +1,146 @@
+import React, {
+ HTMLAttributes,
+ HTMLProps,
+ PropsWithChildren,
+ ReactElement,
+ ReactNode,
+ cloneElement,
+ forwardRef,
+ isValidElement
+} from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cn } from "../../utilities/index";
+
+export const Sidebar = forwardRef>(
+ ({ className, children, ...rest }, ref) => {
+ return (
+
+ );
+ }
+);
+
+Sidebar.displayName = "Sidebar";
+
+export function SidebarHeaderImage({ children }: PropsWithChildren) {
+ return {children};
+}
+
+export const SidebarHeaderTitle = forwardRef<
+ HTMLParagraphElement,
+ HTMLAttributes
+>(({ children, className, ...rest }, ref) => (
+
+ {children}
+
+));
+
+SidebarHeaderTitle.displayName = "SidebarHeaderTitle";
+
+export type SidebarHeaderProps = HTMLAttributes & {
+ title: string;
+ icon?: ReactNode;
+};
+
+export const SidebarHeader = forwardRef(
+ ({ title, icon, children, className, ...rest }, ref) => {
+ const existingIconClasses = isValidElement(icon) ? icon.props.className || "" : "";
+
+ const iconClasses = cn(
+ existingIconClasses,
+ "w-6 ml-auto shrink-0 h-6 opacity-0 group-hover:opacity-100 duration-300 transition-all"
+ );
+
+ return (
+
+ {children}
+
+ {icon && cloneElement(icon as ReactElement, { className: iconClasses })}
+
+ );
+ }
+);
+
+SidebarHeader.displayName = "SidebarHeader";
+
+export const SidebarBody = forwardRef>(
+ ({ className, ...rest }, ref) => {
+ return (
+
+ );
+ }
+);
+export const SidebarList = forwardRef>(
+ ({ className, ...rest }, ref) => {
+ return (
+
+ );
+ }
+);
+
+SidebarList.displayName = "SidebarList";
+
+export type SidebarItemProps = HTMLAttributes & {
+ isActive?: boolean;
+};
+
+export function SidebarItem({
+ isActive = false,
+ ...rest
+}: PropsWithChildren<{ isActive?: boolean }>) {
+ return (
+
+ );
+}
+
+type SidebarItemContentProps = {
+ icon: ReactNode;
+ label: string;
+ isActive?: boolean;
+};
+
+export function SidebarItemContent({ icon, label, isActive }: SidebarItemContentProps) {
+ const existingIconClasses = isValidElement(icon) ? icon.props.className || "" : "";
+
+ const iconClasses = cn(
+ existingIconClasses,
+ `w-5 h-5 shrink-0 ${isActive ? "stroke-primary-400" : "stroke-theme-text-tertiary"} `
+ );
+ return (
+ <>
+ {cloneElement(icon as ReactElement, { className: iconClasses })}
+ {label}
+ >
+ );
+}
diff --git a/src/components/Sidebar/SidebarHeader.stories.tsx b/src/components/Sidebar/SidebarHeader.stories.tsx
new file mode 100644
index 0000000..0e5df48
--- /dev/null
+++ b/src/components/Sidebar/SidebarHeader.stories.tsx
@@ -0,0 +1,35 @@
+import { Meta } from "@storybook/react";
+import React from "react";
+import { SidebarHeader, SidebarHeaderImage } from "./Sidebar";
+import { CloseButton } from "../../../.storybook/assets/icons";
+import { StoryObj } from "@storybook/react";
+
+const meta = {
+ title: "UI/Sidebar",
+ component: SidebarHeader,
+ decorators: [
+ (Story) => (
+
+
+
+ )
+ ],
+ tags: ["autodocs"]
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const sidebarHeader: Story = {
+ name: "Sidebar Header",
+ args: {
+ title: "ZenML Tenant",
+ icon: ,
+ children: (
+
+
+
+ )
+ }
+};
diff --git a/src/components/Sidebar/SidebarItem.stories.tsx b/src/components/Sidebar/SidebarItem.stories.tsx
new file mode 100644
index 0000000..c440112
--- /dev/null
+++ b/src/components/Sidebar/SidebarItem.stories.tsx
@@ -0,0 +1,46 @@
+import { Meta } from "@storybook/react";
+import React from "react";
+import { SidebarItem, SidebarItemContent } from "./Sidebar";
+import { CPU } from "../../../.storybook/assets/icons";
+import { StoryObj } from "@storybook/react";
+
+const meta = {
+ title: "UI/Sidebar",
+ component: SidebarItem,
+ decorators: [
+ (Story) => (
+
+ )
+ ],
+ tags: ["autodocs"]
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const inactive: Story = {
+ name: "Inactive Sidebar Item",
+ args: {
+ isActive: false,
+ children: (
+
+ } label="Models" />
+
+ )
+ }
+};
+
+export const active: Story = {
+ name: "Active Sidebar Item",
+ args: {
+ isActive: true,
+ children: (
+
+ } label="Models" />
+
+ )
+ }
+};
diff --git a/src/components/Sidebar/index.tsx b/src/components/Sidebar/index.tsx
new file mode 100644
index 0000000..d678998
--- /dev/null
+++ b/src/components/Sidebar/index.tsx
@@ -0,0 +1 @@
+export * from "./Sidebar";
diff --git a/src/components/index.ts b/src/components/index.ts
index e22c29a..f063219 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1 +1,2 @@
export * from "./Button";
+export * from "./Sidebar";
diff --git a/src/tailwind/index.ts b/src/tailwind/index.ts
index 3d684b8..d5583c4 100644
--- a/src/tailwind/index.ts
+++ b/src/tailwind/index.ts
@@ -73,7 +73,7 @@ export const zenmlPlugin = plugin(
"--color-blue-025": "208 88% 90%",
"--color-text-primary": "var(--color-primary-900)",
"--color-text-secondary": "var(--color-neutral-500)",
- "--color-text-tertiary": "var(--color-neutral-300)",
+ "--color-text-tertiary": "var(--color-neutral-400)",
"--color-text-negative": "var(--color-neutral-000)",
"--color-text-brand": "var(--color-primary-500)",
"--color-text-error": "var(--color-error-500)",