Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,31 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors",
"inline-flex items-center border px-2.5 py-0.5 text-xs font-semibold",
{
variants: {
variant: {
default: "border-slate-200 bg-slate-100 text-slate-700",
accent: "border-emerald-200 bg-emerald-100 text-emerald-800",
danger: "border-red-200 bg-red-100 text-red-700"
default: "border-transparent bg-muted text-muted-foreground",
accent: "border-transparent bg-accent text-accent-foreground",
danger: "border-transparent bg-destructive/10 text-destructive"
},
shape: {
pill: "rounded-full",
square: "rounded-md"
}
},
defaultVariants: {
variant: "default"
variant: "default",
shape: "pill"
}
}
);

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps): JSX.Element {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
function Badge({ className, variant, shape, ...props }: BadgeProps): JSX.Element {
return <div className={cn(badgeVariants({ variant, shape }), className)} {...props} />;
}

export { Badge, badgeVariants };
17 changes: 10 additions & 7 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-600/50 disabled:pointer-events-none disabled:opacity-50",
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-semibold transition-[transform,background-color,box-shadow] duration-200 ease-swift focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-[0.98] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-emerald-700 text-emerald-50 hover:bg-emerald-800",
secondary: "bg-slate-100 text-slate-900 hover:bg-slate-200 border border-slate-200",
ghost: "text-slate-700 hover:bg-slate-100",
destructive: "bg-red-600 text-white hover:bg-red-700"
default:
"bg-primary text-primary-foreground shadow-[inset_0_1px_0_0_hsl(0_0%_100%/0.12)] hover:bg-primary-hover",
secondary:
"border border-border bg-secondary text-secondary-foreground hover:bg-secondary-hover",
ghost: "text-secondary-foreground hover:bg-secondary",
destructive:
"bg-destructive text-destructive-foreground shadow-[inset_0_1px_0_0_hsl(0_0%_100%/0.15)] hover:bg-destructive-hover"
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3",
lg: "h-11 px-6"
sm: "h-9 px-3 text-[13px]",
lg: "h-11 px-6 text-base"
}
},
defaultVariants: {
Expand Down
12 changes: 8 additions & 4 deletions src/components/ui/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): JS
return (
<div
className={cn(
"rounded-xl border border-slate-200 bg-white text-slate-950 shadow-[0_8px_24px_rgba(15,23,42,0.06)]",
"rounded-lg border border-border bg-card text-card-foreground shadow-[0_1px_0_0_hsl(0_0%_100%/0.6)_inset,0_8px_24px_hsl(222_47%_11%/0.06)]",
className
)}
{...props}
Expand All @@ -17,15 +17,19 @@ function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement
return <div className={cn("flex flex-col space-y-1.5 p-4", className)} {...props} />;
}

function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>): JSX.Element {
return <h3 className={cn("text-lg font-extrabold tracking-tight", className)} {...props} />;
type CardTitleProps = React.HTMLAttributes<HTMLHeadingElement> & {
as?: "h1" | "h2" | "h3" | "h4";
};

function CardTitle({ className, as: Tag = "h3", ...props }: CardTitleProps): JSX.Element {
return <Tag className={cn("text-lg font-bold tracking-tight", className)} {...props} />;
}

function CardDescription({
className,
...props
}: React.HTMLAttributes<HTMLParagraphElement>): JSX.Element {
return <p className={cn("text-sm text-slate-600", className)} {...props} />;
return <p className={cn("text-sm text-muted-foreground", className)} {...props} />;
}

function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): JSX.Element {
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-600/50 disabled:cursor-not-allowed disabled:opacity-50",
"flex h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:ring-destructive",
className
)}
ref={ref}
Expand Down
30 changes: 22 additions & 8 deletions src/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,28 @@ export type SelectProps = React.SelectHTMLAttributes<HTMLSelectElement>;

const Select = React.forwardRef<HTMLSelectElement, SelectProps>(({ className, ...props }, ref) => {
return (
<select
className={cn(
"flex h-10 w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-600/50 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
<div className="relative">
<select
className={cn(
"flex h-10 w-full appearance-none rounded-md border border-input bg-card px-3 py-2 pr-9 text-sm text-foreground shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:ring-destructive",
className
)}
ref={ref}
{...props}
/>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"
>
<path d="m6 9 6 6 6-6" />
</svg>
</div>
);
});
Select.displayName = "Select";
Expand Down
10 changes: 8 additions & 2 deletions src/components/ui/separator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import { cn } from "@/lib/utils";

interface SeparatorProps extends React.HTMLAttributes<HTMLDivElement> {
orientation?: "horizontal" | "vertical";
decorative?: boolean;
}

function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: SeparatorProps): JSX.Element {
return (
<div
role={decorative ? "none" : "separator"}
aria-orientation={decorative ? undefined : orientation}
className={cn(
"shrink-0 bg-slate-200",
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
"shrink-0",
orientation === "horizontal"
? "h-px w-full bg-gradient-to-r from-transparent via-border to-transparent"
: "h-full w-px bg-gradient-to-b from-transparent via-border to-transparent",
className
)}
{...props}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 shadow-sm placeholder:text-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-600/50 disabled:cursor-not-allowed disabled:opacity-50",
"flex min-h-[80px] w-full rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 aria-[invalid=true]:border-destructive aria-[invalid=true]:focus-visible:ring-destructive",
className
)}
ref={ref}
Expand Down
14 changes: 10 additions & 4 deletions src/editor/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ function EditorApp(): JSX.Element {
<Card className="lg:max-h-[calc(100vh-2.5rem)] lg:overflow-auto">
<CardHeader className="space-y-3">
<div className="flex items-center justify-between">
<CardTitle>Shotback Editor</CardTitle>
<CardTitle as="h1">Shotback Editor</CardTitle>
<Badge variant="accent">{annotations.length} notes</Badge>
</div>
<Button disabled={isBusy} onClick={() => void takeScreenshot()}>
Expand Down Expand Up @@ -618,7 +618,13 @@ function EditorApp(): JSX.Element {
<span className="text-xs font-semibold uppercase tracking-wide text-slate-500">
Color
</span>
<Input type="color" value={color} onChange={(event) => setColor(event.target.value)} />
<Input
type="color"
aria-label="Annotation color"
value={color}
onChange={(event) => setColor(event.target.value)}
className="h-10 cursor-pointer p-1"
/>
</label>

<label className="block space-y-1.5">
Expand Down Expand Up @@ -688,7 +694,7 @@ function EditorApp(): JSX.Element {
<Separator />
<section className="space-y-2">
<div className="flex items-center justify-between">
<h4 className="m-0 text-sm font-semibold">Comment Timeline</h4>
<h2 className="m-0 text-sm font-semibold">Comment Timeline</h2>
<Badge>{timelineItems.length}</Badge>
</div>
{timelineItems.length === 0 ? (
Expand Down Expand Up @@ -754,7 +760,7 @@ function EditorApp(): JSX.Element {
<Separator />
<section className="space-y-2">
<div className="flex items-center justify-between">
<h4 className="m-0 text-sm font-semibold">Saved Shares</h4>
<h2 className="m-0 text-sm font-semibold">Saved Shares</h2>
<div className="flex items-center gap-2">
<Badge>{savedShares.length}</Badge>
{savedShares.length > 0 ? (
Expand Down
6 changes: 4 additions & 2 deletions src/popup/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ function PopupApp(): JSX.Element {
<Card>
<CardHeader className="space-y-2 pb-3">
<div className="flex items-center justify-between gap-2">
<CardTitle className="text-3xl leading-none">Shotback</CardTitle>
<Badge variant="accent" className="shrink-0">
<CardTitle as="h1" className="text-3xl leading-none">
Shotback
</CardTitle>
<Badge variant="accent" shape="square" className="shrink-0">
New
</Badge>
</div>
Expand Down
63 changes: 60 additions & 3 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,33 @@
:root {
--background: 210 40% 98%;
--foreground: 222 47% 11%;

--card: 0 0% 100%;
--card-foreground: 222 47% 11%;
--border: 214 32% 91%;
--ring: 160 84% 39%;
--radius: 0.8rem;

--primary: 163 94% 24%; /* emerald-700 */
--primary-hover: 162 88% 20%; /* emerald-800 */
--primary-foreground: 152 81% 96%; /* emerald-50 */

--secondary: 210 40% 96%; /* slate-100 */
--secondary-hover: 214 32% 91%; /* slate-200 */
--secondary-foreground: 222 47% 11%; /* slate-900 */

--muted: 210 40% 96%; /* slate-100 */
--muted-foreground: 215 16% 47%; /* slate-500 */

--accent: 149 80% 90%; /* emerald-100 */
--accent-foreground: 162 88% 20%; /* emerald-800 */

--destructive: 0 72% 51%; /* red-600 */
--destructive-hover: 0 74% 42%; /* red-700 */
--destructive-foreground: 0 0% 100%;

--border: 214 32% 91%; /* slate-200 — structural hairlines */
--input: 213 27% 84%; /* slate-300 — form-control borders */
--ring: 161 94% 30%; /* emerald-600 — focus ring */

--radius: 0.75rem;
}

* {
Expand All @@ -28,3 +50,38 @@
hsl(var(--background));
}
}

/*
* Dark theme tokens. Kept OUTSIDE @layer base so Tailwind does not tree-shake
* the `.dark` selector (no `dark` reference exists in content yet). Opt-in:
* add `class="dark"` to <html>. Light remains the default.
*/
.dark {
--background: 222 47% 6%;
--foreground: 210 40% 96%;

--card: 222 44% 9%;
--card-foreground: 210 40% 96%;

--primary: 161 94% 30%; /* emerald-600 */
--primary-hover: 161 94% 36%;
--primary-foreground: 222 47% 6%;

--secondary: 215 25% 17%; /* slate-800 */
--secondary-hover: 215 19% 22%;
--secondary-foreground: 210 40% 96%;

--muted: 215 25% 17%;
--muted-foreground: 215 20% 65%; /* slate-400 */

--accent: 162 88% 16%;
--accent-foreground: 152 76% 80%; /* emerald-200 */

--destructive: 0 72% 51%;
--destructive-hover: 0 74% 58%;
--destructive-foreground: 0 0% 100%;

--border: 215 25% 17%;
--input: 215 25% 20%;
--ring: 161 94% 36%;
}
6 changes: 3 additions & 3 deletions src/viewer/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function ViewerApp(): JSX.Element {
<main className="mx-auto min-h-screen w-full max-w-5xl p-4 md:p-6">
<Card>
<CardHeader>
<CardTitle>Shotback Share</CardTitle>
<CardTitle as="h1">Shotback Share</CardTitle>
<CardDescription>{status}</CardDescription>
</CardHeader>
</Card>
Expand All @@ -63,7 +63,7 @@ function ViewerApp(): JSX.Element {
<Card>
<CardHeader className="space-y-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<CardTitle>Shotback Share</CardTitle>
<CardTitle as="h1">Shotback Share</CardTitle>
<Badge variant="accent">Local</Badge>
</div>
<CardDescription>
Expand Down Expand Up @@ -93,7 +93,7 @@ function ViewerApp(): JSX.Element {
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between gap-3">
<CardTitle className="text-base">Annotated Image</CardTitle>
<CardTitle as="h2" className="text-base">Annotated Image</CardTitle>
<Button
variant="secondary"
size="sm"
Expand Down
34 changes: 32 additions & 2 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: "class",
content: ["./popup.html", "./editor.html", "./viewer.html", "./src/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: "hsl(var(--card))",
"card-foreground": "hsl(var(--card-foreground))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))"
},
primary: {
DEFAULT: "hsl(var(--primary))",
hover: "hsl(var(--primary-hover))",
foreground: "hsl(var(--primary-foreground))"
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
hover: "hsl(var(--secondary-hover))",
foreground: "hsl(var(--secondary-foreground))"
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))"
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))"
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
hover: "hsl(var(--destructive-hover))",
foreground: "hsl(var(--destructive-foreground))"
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))"
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)"
},
transitionTimingFunction: {
swift: "cubic-bezier(0.32, 0.72, 0, 1)"
}
}
},
Expand Down
Loading