-
- Current Tab
-
-
- {tabInfo.favIconUrl ? (
-

- ) : (
-
{getInitial(tabInfo.title)}
- )}
-
-
{tabInfo.title}
-
{tabInfo.url}
-
-
-
+
+
)}
-
-
-
-
-
+
=>
+ openWebPage("/Options/options.html")
+ }
+ onGitHub={(): Promise =>
+ openWebPage("https://github.com/abhijithvijayan/web-extension-starter")
+ }
+ onSupport={(): Promise =>
+ openWebPage("https://www.buymeacoffee.com/abhijithvijayan")
+ }
+ />
);
};
diff --git a/source/Popup/components/FooterActions/FooterActions.module.scss b/source/Popup/components/FooterActions/FooterActions.module.scss
new file mode 100644
index 0000000..8946421
--- /dev/null
+++ b/source/Popup/components/FooterActions/FooterActions.module.scss
@@ -0,0 +1,10 @@
+@use "../../../styles/variables";
+
+.footer {
+ display: flex;
+ gap: 10px;
+}
+
+.button {
+ flex: 1;
+}
diff --git a/source/Popup/components/FooterActions/FooterActions.tsx b/source/Popup/components/FooterActions/FooterActions.tsx
new file mode 100644
index 0000000..fcfa362
--- /dev/null
+++ b/source/Popup/components/FooterActions/FooterActions.tsx
@@ -0,0 +1,51 @@
+import * as React from "react";
+import type { FC } from "react";
+import { Button } from "../../../components/Button/Button";
+import { SettingsIcon } from "../../../components/icons/SettingsIcon";
+import { GitHubIcon } from "../../../components/icons/GitHubIcon";
+import { HeartIcon } from "../../../components/icons/HeartIcon";
+import styles from "./FooterActions.module.scss";
+
+interface FooterActionsProps {
+ onSettings: () => void;
+ onGitHub: () => void;
+ onSupport: () => void;
+}
+
+export const FooterActions: FC = ({
+ onSettings,
+ onGitHub,
+ onSupport,
+}) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/source/Popup/components/TabInfo/TabInfo.module.scss b/source/Popup/components/TabInfo/TabInfo.module.scss
new file mode 100644
index 0000000..eda6134
--- /dev/null
+++ b/source/Popup/components/TabInfo/TabInfo.module.scss
@@ -0,0 +1,56 @@
+@use "../../../styles/variables";
+
+.content {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ margin-bottom: 16px;
+}
+
+.favicon {
+ width: 44px;
+ height: 44px;
+ border-radius: variables.$radiusMd;
+ flex-shrink: 0;
+ background: variables.$greyWhite;
+ object-fit: cover;
+ border: 1px solid variables.$border;
+}
+
+.faviconPlaceholder {
+ width: 44px;
+ height: 44px;
+ border-radius: variables.$radiusMd;
+ background: linear-gradient(135deg, variables.$primary 0%, #8b5cf6 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: variables.$white;
+ font-size: 18px;
+ font-weight: variables.$bold;
+ flex-shrink: 0;
+}
+
+.details {
+ flex: 1;
+ min-width: 0;
+}
+
+.title {
+ font-size: 14px;
+ font-weight: variables.$semibold;
+ color: variables.$black;
+ margin-bottom: 4px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ line-height: 1.3;
+}
+
+.url {
+ font-size: 12px;
+ color: variables.$skyBlue;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/source/Popup/components/TabInfo/TabInfo.tsx b/source/Popup/components/TabInfo/TabInfo.tsx
new file mode 100644
index 0000000..0eeec3a
--- /dev/null
+++ b/source/Popup/components/TabInfo/TabInfo.tsx
@@ -0,0 +1,42 @@
+import * as React from "react";
+import type { FC } from "react";
+import { Card } from "../../../components/Card/Card";
+import { Button } from "../../../components/Button/Button";
+import styles from "./TabInfo.module.scss";
+
+interface TabInfoProps {
+ title: string;
+ url: string;
+ favIconUrl?: string;
+ onReload: () => void;
+}
+
+export const TabInfo: FC = ({
+ title,
+ url,
+ favIconUrl,
+ onReload,
+}) => {
+ const getInitial = (text: string): string => {
+ return text.charAt(0).toUpperCase();
+ };
+
+ return (
+
+
+ {favIconUrl ? (
+

+ ) : (
+
{getInitial(title)}
+ )}
+
+
+
+
+ );
+};
diff --git a/source/Popup/index.tsx b/source/Popup/index.tsx
index 9164558..d51b986 100644
--- a/source/Popup/index.tsx
+++ b/source/Popup/index.tsx
@@ -1,10 +1,8 @@
-import React from 'react';
+import * as React from 'react';
import ReactDOM from 'react-dom/client';
import Popup from './Popup';
-import './styles.scss';
-
const container = document.getElementById('popup-root');
if (!container) {
diff --git a/source/Popup/styles.scss b/source/Popup/styles.scss
deleted file mode 100644
index 6b4701e..0000000
--- a/source/Popup/styles.scss
+++ /dev/null
@@ -1,198 +0,0 @@
-@use "../styles/fonts";
-@use "../styles/reset";
-@use "../styles/variables";
-
-body {
- color: variables.$black;
- background: linear-gradient(180deg, variables.$greyWhite 0%, #eef2f7 100%);
- width: 100%;
- font-family: variables.$fontFamily;
-}
-
-.popup {
- width: 380px;
- padding: 20px;
-
- &__header {
- text-align: center;
- margin-bottom: 20px;
- padding-bottom: 16px;
- border-bottom: 1px solid variables.$border;
- }
-
- &__title {
- font-size: 18px;
- font-weight: variables.$bold;
- letter-spacing: -0.3px;
- color: variables.$black;
- background: linear-gradient(135deg, variables.$primary 0%, #8b5cf6 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- }
-
- &__greeting {
- font-size: 13px;
- color: variables.$skyBlue;
- margin-top: 6px;
- font-weight: variables.$medium;
- }
-
- &__card {
- background: variables.$cardBg;
- border-radius: variables.$radiusLg;
- padding: 18px;
- margin-bottom: 14px;
- box-shadow: variables.$shadowMd;
- border: 1px solid variables.$border;
- }
-
- &__card-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 14px;
- }
-
- &__card-title {
- font-size: 11px;
- font-weight: variables.$bold;
- text-transform: uppercase;
- letter-spacing: 0.8px;
- color: variables.$skyBlue;
- }
-
- &__tab-content {
- display: flex;
- align-items: center;
- gap: 14px;
- margin-bottom: 16px;
- }
-
- &__favicon {
- width: 44px;
- height: 44px;
- border-radius: variables.$radiusMd;
- flex-shrink: 0;
- background: variables.$greyWhite;
- object-fit: cover;
- border: 1px solid variables.$border;
- }
-
- &__favicon-placeholder {
- width: 44px;
- height: 44px;
- border-radius: variables.$radiusMd;
- background: linear-gradient(135deg, variables.$primary 0%, #8b5cf6 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- color: variables.$white;
- font-size: 18px;
- font-weight: variables.$bold;
- flex-shrink: 0;
- }
-
- &__tab-details {
- flex: 1;
- min-width: 0;
- }
-
- &__tab-title {
- font-size: 14px;
- font-weight: variables.$semibold;
- color: variables.$black;
- margin-bottom: 4px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- line-height: 1.3;
- }
-
- &__tab-url {
- font-size: 12px;
- color: variables.$skyBlue;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- &__btn {
- width: 100%;
- padding: 11px 16px;
- font-size: 13px;
- font-weight: variables.$semibold;
- border: none;
- border-radius: variables.$radiusMd;
- cursor: pointer;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
-
- &:hover {
- transform: translateY(-2px);
- box-shadow: variables.$shadowMd;
- }
-
- &:active {
- transform: translateY(0);
- }
-
- &--primary {
- background: linear-gradient(135deg, variables.$primary 0%, variables.$primaryDark 100%);
- color: variables.$white;
- }
-
- &--secondary {
- background: variables.$greyWhite;
- color: variables.$black;
- border: 1px solid variables.$border;
-
- &:hover {
- background: #eef2f7;
- }
- }
- }
-
- &__footer {
- display: flex;
- gap: 10px;
- }
-
- &__footer-btn {
- flex: 1;
- padding: 12px 10px;
- font-size: 12px;
- font-weight: variables.$semibold;
- border: none;
- border-radius: variables.$radiusMd;
- cursor: pointer;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 6px;
-
- &:hover {
- transform: translateY(-2px);
- box-shadow: variables.$shadowMd;
- }
-
- &--settings {
- background: linear-gradient(135deg, variables.$primary 0%, #8b5cf6 100%);
- color: variables.$white;
- }
-
- &--github {
- background: linear-gradient(135deg, #24292e 0%, #1a1a1a 100%);
- color: variables.$white;
- }
-
- &--support {
- background: linear-gradient(135deg, #f59e0b 0%, #ea580c 100%);
- color: variables.$white;
- }
- }
-}
diff --git a/source/components/Button/Button.module.scss b/source/components/Button/Button.module.scss
new file mode 100644
index 0000000..fdcba25
--- /dev/null
+++ b/source/components/Button/Button.module.scss
@@ -0,0 +1,74 @@
+@use "../../styles/variables";
+
+.button {
+ padding: 11px 16px;
+ font-size: 13px;
+ font-weight: variables.$semibold;
+ border: none;
+ border-radius: variables.$radiusMd;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: variables.$shadowMd;
+ }
+
+ &:active {
+ transform: translateY(0);
+ }
+}
+
+.fullWidth {
+ width: 100%;
+}
+
+.primary {
+ background: linear-gradient(135deg, variables.$primary 0%, variables.$primaryDark 100%);
+ color: variables.$white;
+}
+
+.secondary {
+ background: variables.$greyWhite;
+ color: variables.$black;
+ border: 1px solid variables.$border;
+
+ &:hover {
+ background: #eef2f7;
+ }
+}
+
+.settings {
+ background: linear-gradient(135deg, variables.$primary 0%, #8b5cf6 100%);
+ color: variables.$white;
+}
+
+.github {
+ background: linear-gradient(135deg, #24292e 0%, #1a1a1a 100%);
+ color: variables.$white;
+}
+
+.support {
+ background: linear-gradient(135deg, #f59e0b 0%, #ea580c 100%);
+ color: variables.$white;
+}
+
+.small {
+ padding: 12px 10px;
+ font-size: 12px;
+ gap: 6px;
+}
+
+.medium {
+ padding: 11px 16px;
+ font-size: 13px;
+}
+
+.large {
+ padding: 14px 28px;
+ font-size: 14px;
+}
diff --git a/source/components/Button/Button.tsx b/source/components/Button/Button.tsx
new file mode 100644
index 0000000..e494e8b
--- /dev/null
+++ b/source/components/Button/Button.tsx
@@ -0,0 +1,38 @@
+import * as React from "react";
+import type { FC, ReactNode, ButtonHTMLAttributes } from "react";
+import styles from "./Button.module.scss";
+
+type ButtonVariant = "primary" | "secondary" | "settings" | "github" | "support";
+type ButtonSize = "small" | "medium" | "large";
+
+interface ButtonProps extends ButtonHTMLAttributes {
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ fullWidth?: boolean;
+ children: ReactNode;
+}
+
+export const Button: FC = ({
+ variant = "primary",
+ size = "medium",
+ fullWidth = false,
+ children,
+ className,
+ ...props
+}) => {
+ const classNames = [
+ styles.button,
+ styles[variant],
+ styles[size],
+ fullWidth && styles.fullWidth,
+ className,
+ ]
+ .filter(Boolean)
+ .join(" ");
+
+ return (
+
+ );
+};
diff --git a/source/components/Card/Card.module.scss b/source/components/Card/Card.module.scss
new file mode 100644
index 0000000..526d70c
--- /dev/null
+++ b/source/components/Card/Card.module.scss
@@ -0,0 +1,29 @@
+@use "../../styles/variables";
+
+.card {
+ background: variables.$cardBg;
+ border-radius: variables.$radiusLg;
+ padding: 18px;
+ box-shadow: variables.$shadowMd;
+ border: 1px solid variables.$border;
+}
+
+.large {
+ padding: 28px;
+ box-shadow: variables.$shadowLg;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 14px;
+}
+
+.title {
+ font-size: 11px;
+ font-weight: variables.$bold;
+ text-transform: uppercase;
+ letter-spacing: 0.8px;
+ color: variables.$skyBlue;
+}
diff --git a/source/components/Card/Card.tsx b/source/components/Card/Card.tsx
new file mode 100644
index 0000000..f084c69
--- /dev/null
+++ b/source/components/Card/Card.tsx
@@ -0,0 +1,36 @@
+import * as React from "react";
+import type { FC, ReactNode } from "react";
+import styles from "./Card.module.scss";
+
+interface CardProps {
+ title?: string;
+ size?: "default" | "large";
+ children: ReactNode;
+ className?: string;
+}
+
+export const Card: FC = ({
+ title,
+ size = "default",
+ children,
+ className,
+}) => {
+ const classNames = [
+ styles.card,
+ size === "large" && styles.large,
+ className,
+ ]
+ .filter(Boolean)
+ .join(" ");
+
+ return (
+
+ {title && (
+
+ {title}
+
+ )}
+ {children}
+
+ );
+};
diff --git a/source/components/Checkbox/Checkbox.module.scss b/source/components/Checkbox/Checkbox.module.scss
new file mode 100644
index 0000000..5af8abd
--- /dev/null
+++ b/source/components/Checkbox/Checkbox.module.scss
@@ -0,0 +1,33 @@
+@use "../../styles/variables";
+
+.wrapper {
+ display: flex;
+ align-items: flex-start;
+ gap: 14px;
+ padding: 16px;
+ background: variables.$greyWhite;
+ border-radius: variables.$radiusMd;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border: 2px solid transparent;
+
+ &:hover {
+ background: #eef2f7;
+ border-color: variables.$border;
+ }
+}
+
+.checkbox {
+ width: 20px;
+ height: 20px;
+ margin-top: 2px;
+ cursor: pointer;
+ accent-color: variables.$primary;
+}
+
+.text {
+ font-size: 14px;
+ line-height: 1.5;
+ color: variables.$skyBlue;
+ font-weight: variables.$medium;
+}
diff --git a/source/components/Checkbox/Checkbox.tsx b/source/components/Checkbox/Checkbox.tsx
new file mode 100644
index 0000000..91dd745
--- /dev/null
+++ b/source/components/Checkbox/Checkbox.tsx
@@ -0,0 +1,38 @@
+import * as React from "react";
+import type { FC, InputHTMLAttributes } from "react";
+import styles from "./Checkbox.module.scss";
+
+interface CheckboxProps extends Omit, "type"> {
+ label: string;
+}
+
+export const Checkbox: FC = ({
+ label,
+ id,
+ checked,
+ onChange,
+ ...props
+}) => {
+ const handleWrapperClick = (e: React.MouseEvent): void => {
+ if ((e.target as HTMLElement).tagName !== "INPUT") {
+ const input = e.currentTarget.querySelector("input");
+ if (input) {
+ input.click();
+ }
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/source/components/Input/Input.module.scss b/source/components/Input/Input.module.scss
new file mode 100644
index 0000000..869fcf6
--- /dev/null
+++ b/source/components/Input/Input.module.scss
@@ -0,0 +1,41 @@
+@use "../../styles/variables";
+
+.wrapper {
+ margin-bottom: 24px;
+}
+
+.label {
+ display: block;
+ font-size: 13px;
+ font-weight: variables.$semibold;
+ margin-bottom: 10px;
+ color: variables.$black;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.input {
+ width: 100%;
+ padding: 14px 16px;
+ font-size: 15px;
+ background-color: variables.$white;
+ color: variables.$black;
+ border: 2px solid variables.$border;
+ border-radius: variables.$radiusMd;
+ transition: all 0.2s ease;
+ box-sizing: border-box;
+
+ &:hover {
+ border-color: #cbd5e1;
+ }
+
+ &:focus {
+ outline: none;
+ border-color: variables.$primary;
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.15);
+ }
+
+ &::placeholder {
+ color: #94a3b8;
+ }
+}
diff --git a/source/components/Input/Input.tsx b/source/components/Input/Input.tsx
new file mode 100644
index 0000000..a720fe5
--- /dev/null
+++ b/source/components/Input/Input.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+import type { FC, InputHTMLAttributes } from "react";
+import styles from "./Input.module.scss";
+
+interface InputProps extends InputHTMLAttributes {
+ label?: string;
+}
+
+export const Input: FC = ({ label, id, className, ...props }) => {
+ return (
+
+ {label && (
+
+ )}
+
+
+ );
+};
diff --git a/source/components/icons/GitHubIcon.tsx b/source/components/icons/GitHubIcon.tsx
new file mode 100644
index 0000000..0ec8e6c
--- /dev/null
+++ b/source/components/icons/GitHubIcon.tsx
@@ -0,0 +1,12 @@
+import * as React from "react";
+import type { FC } from "react";
+
+interface IconProps {
+ size?: number;
+}
+
+export const GitHubIcon: FC = ({ size = 14 }) => (
+
+);
diff --git a/source/components/icons/HeartIcon.tsx b/source/components/icons/HeartIcon.tsx
new file mode 100644
index 0000000..deb5a9b
--- /dev/null
+++ b/source/components/icons/HeartIcon.tsx
@@ -0,0 +1,12 @@
+import * as React from "react";
+import type { FC } from "react";
+
+interface IconProps {
+ size?: number;
+}
+
+export const HeartIcon: FC = ({ size = 14 }) => (
+
+);
diff --git a/source/components/icons/SettingsIcon.tsx b/source/components/icons/SettingsIcon.tsx
new file mode 100644
index 0000000..7335994
--- /dev/null
+++ b/source/components/icons/SettingsIcon.tsx
@@ -0,0 +1,20 @@
+import * as React from "react";
+import type { FC } from "react";
+
+interface IconProps {
+ size?: number;
+}
+
+export const SettingsIcon: FC = ({ size = 14 }) => (
+
+);
diff --git a/source/globals.d.ts b/source/globals.d.ts
index 4c27915..2574e6c 100644
--- a/source/globals.d.ts
+++ b/source/globals.d.ts
@@ -1,2 +1,5 @@
// https://www.typescriptlang.org/tsconfig/#noUncheckedSideEffectImports
-declare module "*.scss" {}
\ No newline at end of file
+declare module "*.scss" {
+ const content: { [className: string]: string };
+ export default content;
+}
\ No newline at end of file