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/components.json | 17 +++ apps/browser-extension/index.html | 2 +- apps/browser-extension/package.json | 17 ++- apps/browser-extension/public/logo-full-white.png | Bin 0 -> 9041 bytes 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 +- apps/browser-extension/tailwind.config.js | 1 + pnpm-lock.yaml | 44 ++++++++ 18 files changed, 396 insertions(+), 100 deletions(-) create mode 100644 apps/browser-extension/components.json create mode 100644 apps/browser-extension/public/logo-full-white.png 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 diff --git a/apps/browser-extension/components.json b/apps/browser-extension/components.json new file mode 100644 index 00000000..67eaccb0 --- /dev/null +++ b/apps/browser-extension/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "src/components", + "utils": "src/utils/css" + } +} diff --git a/apps/browser-extension/index.html b/apps/browser-extension/index.html index e4b78eae..3ba58dda 100644 --- a/apps/browser-extension/index.html +++ b/apps/browser-extension/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + Hoarder
diff --git a/apps/browser-extension/package.json b/apps/browser-extension/package.json index 20cc843c..9b204519 100644 --- a/apps/browser-extension/package.json +++ b/apps/browser-extension/package.json @@ -12,30 +12,39 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@hoarder/trpc": "workspace:^0.1.0", "@hoarder/shared-react": "workspace:^0.1.0", + "@hoarder/trpc": "workspace:^0.1.0", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^5.24.8", "@trpc/client": "11.0.0-next-beta.308", "@trpc/next": "11.0.0-next-beta.308", "@trpc/react-query": "11.0.0-next-beta.308", "@trpc/server": "11.0.0-next-beta.308", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "cmdk": "^1.0.0", "lucide-react": "^0.330.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.0", - "use-chrome-storage": "^1.2.2", "superjson": "^2.2.1", + "tailwind-merge": "^2.2.1", + "tailwindcss-animate": "^1.0.7", + "use-chrome-storage": "^1.2.2", "zod": "^3.22.4" }, "devDependencies": { + "@crxjs/vite-plugin": "^1.0.14", "@hoarder/eslint-config": "workspace:^0.2.0", "@hoarder/prettier-config": "workspace:^0.1.0", "@hoarder/tailwind-config": "workspace:^0.1.0", "@hoarder/tsconfig": "workspace:^0.1.0", - "@crxjs/vite-plugin": "^1.0.14", + "@types/chrome": "^0.0.260", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", - "@types/chrome": "^0.0.260", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.17", "eslint": "^8.57.0", diff --git a/apps/browser-extension/public/logo-full-white.png b/apps/browser-extension/public/logo-full-white.png new file mode 100644 index 00000000..8d3067c6 Binary files /dev/null and b/apps/browser-extension/public/logo-full-white.png differ 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} + + + ); } diff --git a/apps/browser-extension/tailwind.config.js b/apps/browser-extension/tailwind.config.js index e8f2d639..7300649a 100644 --- a/apps/browser-extension/tailwind.config.js +++ b/apps/browser-extension/tailwind.config.js @@ -1,6 +1,7 @@ import web from "@hoarder/tailwind-config/web"; const config = { + darkMode: "media", content: web.content, presets: [web], }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e64f565..e5b7aec5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,6 +49,18 @@ importers: '@hoarder/trpc': specifier: workspace:^0.1.0 version: link:../../packages/trpc + '@radix-ui/react-dialog': + specifier: ^1.0.5 + version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-popover': + specifier: ^1.0.7 + version: 1.0.7(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-select': + specifier: ^2.0.0 + version: 2.0.0(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.0.2(@types/react@18.2.58)(react@18.2.0) '@tanstack/react-query': specifier: ^5.24.8 version: 5.24.8(react@18.2.0) @@ -64,6 +76,15 @@ importers: '@trpc/server': specifier: 11.0.0-next-beta.308 version: 11.0.0-next-beta.308 + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + clsx: + specifier: ^2.1.0 + version: 2.1.0 + cmdk: + specifier: ^1.0.0 + version: 1.0.0(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) lucide-react: specifier: ^0.330.0 version: 0.330.0(react@18.2.0) @@ -79,6 +100,12 @@ importers: superjson: specifier: ^2.2.1 version: 2.2.1 + tailwind-merge: + specifier: ^2.2.1 + version: 2.2.1 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.1) use-chrome-storage: specifier: ^1.2.2 version: 1.3.0(react@18.2.0) @@ -5261,6 +5288,12 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + cmdk@1.0.0: + resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -19589,6 +19622,17 @@ snapshots: cluster-key-slot@1.1.2: dev: false + cmdk@1.0.0(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + dependencies: + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + dev: false + collapse-white-space@2.1.0: {} color-convert@1.9.3: -- cgit v1.2.3-70-g09d2