From 7ddcb5fe6b7de9df3e0424598d1836d51c3f80eb Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Mon, 22 Apr 2024 18:28:41 +0100 Subject: ui(extension): Use shadcn and better dark mode support --- apps/browser-extension/src/Layout.tsx | 13 +-- apps/browser-extension/src/Logo.tsx | 8 +- apps/browser-extension/src/NotConfiguredPage.tsx | 8 +- apps/browser-extension/src/OptionsPage.tsx | 9 +- apps/browser-extension/src/SignInPage.tsx | 14 ++- .../browser-extension/src/components/ui/button.tsx | 57 ++++++++++ .../browser-extension/src/components/ui/dialog.tsx | 120 +++++++++++++++++++++ apps/browser-extension/src/components/ui/input.tsx | 24 +++++ apps/browser-extension/src/index.css | 73 +------------ apps/browser-extension/src/utils/ThemeProvider.tsx | 73 +++++++++++++ apps/browser-extension/src/utils/css.ts | 7 ++ apps/browser-extension/src/utils/providers.tsx | 9 +- 12 files changed, 320 insertions(+), 95 deletions(-) create mode 100644 apps/browser-extension/src/components/ui/button.tsx create mode 100644 apps/browser-extension/src/components/ui/dialog.tsx create mode 100644 apps/browser-extension/src/components/ui/input.tsx create mode 100644 apps/browser-extension/src/utils/ThemeProvider.tsx create mode 100644 apps/browser-extension/src/utils/css.ts (limited to 'apps/browser-extension/src') diff --git a/apps/browser-extension/src/Layout.tsx b/apps/browser-extension/src/Layout.tsx index bf760362..0431f550 100644 --- a/apps/browser-extension/src/Layout.tsx +++ b/apps/browser-extension/src/Layout.tsx @@ -1,6 +1,7 @@ import { Home, RefreshCw, Settings, X } from "lucide-react"; import { Outlet, useNavigate } from "react-router-dom"; +import { Button } from "./components/ui/button"; import usePluginSettings from "./utils/settings"; export default function Layout() { @@ -35,16 +36,16 @@ export default function Layout() {
{process.env.NODE_ENV == "development" && ( - + )} - - + +
diff --git a/apps/browser-extension/src/Logo.tsx b/apps/browser-extension/src/Logo.tsx index 462bbef0..3e2e6444 100644 --- a/apps/browser-extension/src/Logo.tsx +++ b/apps/browser-extension/src/Logo.tsx @@ -1,9 +1,15 @@ +import logoImgWhite from "../public/logo-full-white.png"; import logoImg from "../public/logo-full.png"; export default function Logo() { return ( - hoarder logo + hoarder logo + hoarder logo ); } diff --git a/apps/browser-extension/src/NotConfiguredPage.tsx b/apps/browser-extension/src/NotConfiguredPage.tsx index 298e9f5e..31d45d6a 100644 --- a/apps/browser-extension/src/NotConfiguredPage.tsx +++ b/apps/browser-extension/src/NotConfiguredPage.tsx @@ -1,6 +1,8 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; +import { Button } from "./components/ui/button"; +import { Input } from "./components/ui/input"; import Logo from "./Logo"; import usePluginSettings from "./utils/settings"; @@ -33,16 +35,14 @@ export default function NotConfiguredPage() {

{error}

- setServerAddress(e.target.value)} />
- + ); } diff --git a/apps/browser-extension/src/OptionsPage.tsx b/apps/browser-extension/src/OptionsPage.tsx index 24785857..41b72178 100644 --- a/apps/browser-extension/src/OptionsPage.tsx +++ b/apps/browser-extension/src/OptionsPage.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from "react"; import { useNavigate } from "react-router-dom"; +import { Button } from "./components/ui/button"; import Logo from "./Logo"; import Spinner from "./Spinner"; import usePluginSettings from "./utils/settings"; @@ -54,13 +55,15 @@ export default function OptionsPage() { Settings
+
+ Server Address: + {settings.address} +
Logged in as: {loggedInMessage}
- + ); } diff --git a/apps/browser-extension/src/SignInPage.tsx b/apps/browser-extension/src/SignInPage.tsx index aa8699ae..4e846070 100644 --- a/apps/browser-extension/src/SignInPage.tsx +++ b/apps/browser-extension/src/SignInPage.tsx @@ -1,6 +1,8 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; +import { Button } from "./components/ui/button"; +import { Input } from "./components/ui/input"; import Logo from "./Logo"; import usePluginSettings from "./utils/settings"; import { api } from "./utils/trpc"; @@ -51,7 +53,7 @@ export default function SignInPage() {
- setFormData((f) => ({ ...f, email: e.target.value })) @@ -63,7 +65,7 @@ export default function SignInPage() {
- setFormData((f) => ({ @@ -76,13 +78,9 @@ export default function SignInPage() { className="h-8 flex-1 rounded-lg border border-gray-300 p-2" />
- +
); diff --git a/apps/browser-extension/src/components/ui/button.tsx b/apps/browser-extension/src/components/ui/button.tsx new file mode 100644 index 00000000..1bd3802d --- /dev/null +++ b/apps/browser-extension/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import type { VariantProps } from "class-variance-authority"; +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva } from "class-variance-authority"; + +import { cn } from "../../utils/css"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + 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) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/apps/browser-extension/src/components/ui/dialog.tsx b/apps/browser-extension/src/components/ui/dialog.tsx new file mode 100644 index 00000000..997230bb --- /dev/null +++ b/apps/browser-extension/src/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "../../utils/css"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/apps/browser-extension/src/components/ui/input.tsx b/apps/browser-extension/src/components/ui/input.tsx new file mode 100644 index 00000000..5751b004 --- /dev/null +++ b/apps/browser-extension/src/components/ui/input.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { cn } from "../../utils/css"; + +export type InputProps = React.InputHTMLAttributes; + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/apps/browser-extension/src/index.css b/apps/browser-extension/src/index.css index e7d4bb2f..d5c92220 100644 --- a/apps/browser-extension/src/index.css +++ b/apps/browser-extension/src/index.css @@ -1,72 +1 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} +@import "@hoarder/tailwind-config/globals.css"; diff --git a/apps/browser-extension/src/utils/ThemeProvider.tsx b/apps/browser-extension/src/utils/ThemeProvider.tsx new file mode 100644 index 00000000..79a0b32f --- /dev/null +++ b/apps/browser-extension/src/utils/ThemeProvider.tsx @@ -0,0 +1,73 @@ +import { createContext, useContext, useEffect, useState } from "react"; + +type Theme = "dark" | "light" | "system"; + +interface ThemeProviderProps { + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +} + +interface 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 = "vite-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; +}; diff --git a/apps/browser-extension/src/utils/css.ts b/apps/browser-extension/src/utils/css.ts new file mode 100644 index 00000000..88283f01 --- /dev/null +++ b/apps/browser-extension/src/utils/css.ts @@ -0,0 +1,7 @@ +import type { ClassValue } from "clsx"; +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/apps/browser-extension/src/utils/providers.tsx b/apps/browser-extension/src/utils/providers.tsx index 4ca17016..4b571254 100644 --- a/apps/browser-extension/src/utils/providers.tsx +++ b/apps/browser-extension/src/utils/providers.tsx @@ -1,9 +1,16 @@ import { TRPCProvider } from "@hoarder/shared-react/providers/trpc-provider"; import usePluginSettings from "./settings"; +import { ThemeProvider } from "./ThemeProvider"; export function Providers({ children }: { children: React.ReactNode }) { const { settings } = usePluginSettings(); - return {children}; + return ( + + + {children} + + + ); } -- cgit v1.2.3-70-g09d2