diff --git a/.gitignore b/.gitignore index ae783cf..90c2af7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ appmap.log /vsc/node_modules /vsc/dist node_modules/ +/frontend/web/dist/ +/frontend/common/dist/ diff --git a/frontend/common/components.json b/frontend/common/components.json new file mode 100644 index 0000000..ac17545 --- /dev/null +++ b/frontend/common/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles/globals.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "@ra-aid/common/src/components", + "utils": "@ra-aid/common/src/lib/utils" + } +} \ No newline at end of file diff --git a/frontend/common/package.json b/frontend/common/package.json index 2371bed..cd5c9c2 100644 --- a/frontend/common/package.json +++ b/frontend/common/package.json @@ -8,7 +8,23 @@ "build": "tsc", "dev": "tsc --watch" }, + "dependencies": { + "@radix-ui/react-toggle": "^1.1.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "lucide-react": "^0.344.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "tailwind-merge": "^2.2.1", + "tailwindcss-animate": "^1.0.7" + }, "devDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", "typescript": "^5.0.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" } } diff --git a/frontend/common/postcss.config.js b/frontend/common/postcss.config.js new file mode 100644 index 0000000..dc655aa --- /dev/null +++ b/frontend/common/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/frontend/common/src/components/ShadcnDemo.tsx b/frontend/common/src/components/ShadcnDemo.tsx new file mode 100644 index 0000000..2b29520 --- /dev/null +++ b/frontend/common/src/components/ShadcnDemo.tsx @@ -0,0 +1,197 @@ +import React from "react"; +import { + ThemeProvider, + ThemeToggle, + Button, + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent +} from "../index"; + +/** + * ShadcnDemo Component + * + * Demonstrates various shadcn UI components including: + * - Cards with different content + * - Buttons with different variants and sizes + * - Theme toggling between light and dark modes + * - Responsive layout using Tailwind CSS + */ +export function ShadcnDemo() { + return ( + +
+
+
+

Shadcn UI Demo

+ +
+ +
+

Button Variants

+
+ + + + + + +
+
+ +
+

Button Sizes

+
+ + + + +
+
+ +
+

Cards

+
+ {/* Feature Card */} + + + Feature Highlight + Showcasing a key product feature + + +

This card demonstrates a clean way to highlight important features or content in your application.

+
+ + + +
+ + {/* Stats Card */} + + + Statistics + Key metrics and data points + + +
+ Total Users + 1,234 +
+
+ Active Sessions + 567 +
+
+ Conversion Rate + 12.5% +
+
+ + + +
+ + {/* CTA Card */} + + + Get Started + Begin your journey today + + +

Sign up now and get access to all premium features for the first 30 days.

+
+ + + +
+
+
+ +
+

Responsive Demo

+ + +
+
+

Mobile View

+

Default

+
+
+

Tablet View

+

sm:block

+
+
+

Desktop View

+

lg:block

+
+
+

Desktop View

+

lg:block

+
+
+
+ + + + +
+
+ +
+

Interactive States

+
+ + + Button States + + + + + + + + + + + Interactive Example + + +

Toggle between these options:

+
+ + + +
+
+ + + +
+
+
+
+
+
+ ); +} + +export default ShadcnDemo; \ No newline at end of file diff --git a/frontend/common/src/components/theme/theme-provider.tsx b/frontend/common/src/components/theme/theme-provider.tsx new file mode 100644 index 0000000..9695667 --- /dev/null +++ b/frontend/common/src/components/theme/theme-provider.tsx @@ -0,0 +1,74 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; + +type Theme = "dark" | "light" | "system"; + +type ThemeProviderProps = { + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; + +type ThemeProviderState = { + theme: Theme; + setTheme: (theme: Theme) => void; +}; + +const initialState: ThemeProviderState = { + theme: "system", + setTheme: () => null, +}; + +const ThemeProviderContext = createContext(initialState); + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey = "ra-aid-ui-theme", + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme + ); + + useEffect(() => { + const root = window.document.documentElement; + + root.classList.remove("light", "dark"); + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + + root.classList.add(systemTheme); + return; + } + + root.classList.add(theme); + }, [theme]); + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme); + setTheme(theme); + }, + }; + + return ( + + {children} + + ); +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext); + + if (context === undefined) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + + return context; +} \ No newline at end of file diff --git a/frontend/common/src/components/theme/theme-toggle.tsx b/frontend/common/src/components/theme/theme-toggle.tsx new file mode 100644 index 0000000..8276051 --- /dev/null +++ b/frontend/common/src/components/theme/theme-toggle.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { useTheme } from "./theme-provider"; +import { cn } from "../../lib/utils"; + +export function ThemeToggle({ className }: { className?: string }) { + const { theme, setTheme } = useTheme(); + + return ( + + ); +} \ No newline at end of file diff --git a/frontend/common/src/components/ui/button.tsx b/frontend/common/src/components/ui/button.tsx new file mode 100644 index 0000000..3676aa7 --- /dev/null +++ b/frontend/common/src/components/ui/button.tsx @@ -0,0 +1,55 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "../../lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + return ( +