diff --git a/app/features/components-showcase/components-showcase.module.css b/app/features/components-showcase/components-showcase.module.css
new file mode 100644
index 000000000..e51b5aa80
--- /dev/null
+++ b/app/features/components-showcase/components-showcase.module.css
@@ -0,0 +1,28 @@
+.sectionTitle {
+ font-size: var(--fonts-xl);
+ border-bottom: 2px solid var(--color-bg-high);
+ margin-bottom: var(--s-4);
+}
+
+.componentRow {
+ display: grid;
+ align-items: center;
+ grid-template-columns: 200px 1fr;
+ gap: var(--s-4);
+}
+
+.componentRow:last-child {
+ border-bottom: none;
+}
+
+.componentLabel {
+ color: var(--color-text-high);
+ font-size: var(--fonts-sm);
+}
+
+.componentContent {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-sm);
+ align-items: center;
+}
diff --git a/app/features/components-showcase/routes/components.tsx b/app/features/components-showcase/routes/components.tsx
new file mode 100644
index 000000000..b4be83a2d
--- /dev/null
+++ b/app/features/components-showcase/routes/components.tsx
@@ -0,0 +1,729 @@
+import { useState } from "react";
+import { Alert } from "~/components/Alert";
+import { Divider } from "~/components/Divider";
+import { LinkButton, SendouButton } from "~/components/elements/Button";
+import { SendouDialog } from "~/components/elements/Dialog";
+import { SendouMenu, SendouMenuItem } from "~/components/elements/Menu";
+import { SendouPopover } from "~/components/elements/Popover";
+import { SendouSelect, SendouSelectItem } from "~/components/elements/Select";
+import { SendouSwitch } from "~/components/elements/Switch";
+import {
+ SendouTab,
+ SendouTabList,
+ SendouTabPanel,
+ SendouTabs,
+} from "~/components/elements/Tabs";
+import { toastQueue } from "~/components/elements/Toast";
+import { InfoPopover } from "~/components/InfoPopover";
+import { Input } from "~/components/Input";
+import { CheckmarkIcon } from "~/components/icons/Checkmark";
+import { EditIcon } from "~/components/icons/Edit";
+import { PlusIcon } from "~/components/icons/Plus";
+import { SearchIcon } from "~/components/icons/Search";
+import { TrashIcon } from "~/components/icons/Trash";
+import { Label } from "~/components/Label";
+import { Main } from "~/components/Main";
+import { Section } from "~/components/Section";
+import { SubmitButton } from "~/components/SubmitButton";
+import styles from "../components-showcase.module.css";
+
+export default function ComponentsShowcasePage() {
+ return (
+
+ Components
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function SectionTitle({ children }: { children: React.ReactNode }) {
+ return
{children}
;
+}
+
+function ComponentRow({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
+
+function ButtonsSection() {
+ return (
+
+ Buttons
+
+
+
+ Primary Button
+
+
+
+ Success Button
+
+
+
+ Outlined Button
+
+
+
+
+ Outlined Success
+
+
+
+
+ Destructive Button
+
+
+
+ Minimal Button
+
+
+
+ Minimal Success
+
+
+
+
+ Minimal Destructive
+
+
+
+
Sizes
+
+
+ Miniscule
+
+
+
+ Small
+
+
+
+ Medium
+
+
+
+ Big
+
+
+
With Icons
+
+
+ }>Add Item
+
+
+
+ } />
+
+
+
+ }>
+ Delete
+
+
+
+
States
+
+
+ Disabled Button
+
+
+
Link Buttons
+
+
+ Go to Home
+
+
+
+
+ GitHub
+
+
+
+
Submit Button
+
+
+ Submit Form
+
+
+
+ );
+}
+
+function AlertsSection() {
+ return (
+
+ Alerts
+
+
+
+ This is an informational alert message.
+
+
+
+
+ This is a warning alert. Please be careful!
+
+
+
+
+
+ This is an error alert. Something went wrong.
+
+
+
+
+
+ This is a success alert. Operation completed!
+
+
+
+
+ This is a tiny alert message.
+
+
+
+ );
+}
+
+function InputsSection() {
+ return (
+
+ );
+}
+
+const SELECT_ITEMS = [
+ { id: "1", name: "Option 1" },
+ { id: "2", name: "Option 2" },
+ { id: "3", name: "Option 3" },
+ { id: "4", name: "Option 4" },
+ { id: "5", name: "Option 5" },
+];
+
+function SelectSection() {
+ return (
+
+ Select
+
+
+
+
+ {(item) => (
+
+ {item.name}
+
+ )}
+
+
+
+
+
+ {(item) => (
+
+ {item.name}
+
+ )}
+
+
+
+
+
+ {(item) => (
+
+ {item.name}
+
+ )}
+
+
+
+
+
+ {(item) => (
+
+ {item.name}
+
+ )}
+
+
+
+
+
+ {(item) => (
+
+ {item.name}
+
+ )}
+
+
+
+
+
+ {(item) => (
+
+ {item.name}
+
+ )}
+
+
+
+
+ );
+}
+
+function SwitchSection() {
+ const [isOn, setIsOn] = useState(false);
+ const [isSmallOn, setIsSmallOn] = useState(true);
+
+ return (
+
+ Switch
+
+
+
+
+ Toggle me
+
+
+
+
+
+ Small switch
+
+
+
+
+
+
+
+
+ Disabled switch
+
+
+
+ );
+}
+
+function TabsSection() {
+ return (
+
+ Tabs
+
+
+
+
+
+ First Tab
+ Second Tab
+ Third Tab
+
+
+ Content for the first tab.
+
+
+ Content for the second tab.
+
+
+ Content for the third tab.
+
+
+
+
+
+
+
+ }>
+ Search
+
+ }>
+ Edit
+
+ }>
+ Review
+
+
+ Search content here.
+ Edit content here.
+ Review content here.
+
+
+
+
+
+
+
+ Pending
+
+
+ Approved
+
+
+ Rejected
+
+
+ 5 pending items.
+ 12 approved items.
+ No rejected items.
+
+
+
+
+ );
+}
+
+function DialogSection() {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+ Dialog
+
+
+
+ Open Dialog}
+ heading="Dialog Title"
+ >
+ This is the content of the dialog.
+
+ You can put any content here, including forms, lists, or other
+ components.
+
+
+
+
+
+
+
setIsOpen(true)}>
+ Open Controlled Dialog
+
+
setIsOpen(false)}
+ heading="Controlled Dialog"
+ showCloseButton
+ >
+ This dialog is controlled via state.
+ setIsOpen(false)}>
+ Close
+
+
+
+
+
+
+ );
+}
+
+function PopoverSection() {
+ return (
+
+ Popover
+
+
+
+ Click for Popover}
+ >
+ This is popover content!
+
+
+
+
+
+ Some text with help
+
+ This is additional information shown in a popover.
+
+
+
+
+
+
+ Compact help icon
+ Tiny popover with less padding.
+
+
+
+
+ );
+}
+
+function MenuSection() {
+ return (
+
+ Menu
+
+
+
+ Open Menu}>
+ {}}>Menu Item 1
+ {}}>Menu Item 2
+ {}}>Menu Item 3
+
+
+
+
+ Menu with Icons}>
+ } onAction={() => {}}>
+ Edit
+
+ } onAction={() => {}}>
+ Add New
+
+ } onAction={() => {}}>
+ Delete
+
+
+
+
+
+ Menu with Active}>
+ {}}>
+ Active Item
+
+ {}}>Normal Item
+ {}}>Another Item
+
+
+
+
+ Scrolling Menu}
+ >
+ {Array.from({ length: 10 }, (_, i) => (
+ {}}>
+ Item {i + 1}
+
+ ))}
+
+
+
+
+ );
+}
+
+function ToastSection() {
+ return (
+
+ Toast
+
+
+
+
+ toastQueue.add({
+ message: "Operation completed successfully!",
+ variant: "success",
+ })
+ }
+ >
+ Show Success Toast
+
+
+
+
+
+ toastQueue.add({
+ message: "Something went wrong. Please try again.",
+ variant: "error",
+ })
+ }
+ >
+ Show Error Toast
+
+
+
+
+
+ toastQueue.add({
+ message: "Here is some information for you.",
+ variant: "info",
+ })
+ }
+ >
+ Show Info Toast
+
+
+
+
+ );
+}
+
+function DividerSection() {
+ return (
+
+ Divider
+
+
+
+
+
Content above
+
+
Content below
+
+
+
+
+
+
Section 1
+
OR
+
Section 2
+
+
+
+
+
+
Above
+
small text divider
+
Below
+
+
+
+
+ );
+}
+
+function MiscSection() {
+ return (
+
+ Miscellaneous
+
+
+
+
+ This is content inside a Section component.
+ It provides consistent styling for content blocks.
+
+
+
+
+ );
+}
diff --git a/app/routes.ts b/app/routes.ts
index 48bf783a7..f97714608 100644
--- a/app/routes.ts
+++ b/app/routes.ts
@@ -12,6 +12,10 @@ const devOnlyRoutes =
"/admin/generate-images",
"features/admin/routes/generate-images.tsx",
),
+ route(
+ "/components",
+ "features/components-showcase/routes/components.tsx",
+ ),
] satisfies RouteConfig)
: [];