Skip to content
Open
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
7 changes: 4 additions & 3 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Header } from "@/components/home/header";
import { Hero } from "@/components/home/hero";
import { HowItWorks } from "@/components/home/how-it-works";
import { Roadmap } from "@/components/home/roadmap";
import { Testimonials } from "@/components/home/testimonials";
import { ThemeHotKeyHandler } from "@/components/home/theme-hotkey-handler";
import { ThemePresetSelector } from "@/components/home/theme-preset-selector";
import { useEffect, useState } from "react";

Expand All @@ -19,7 +19,7 @@ export default function Home() {

useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 10) {
if (window.scrollY > 10){
setIsScrolled(true);
} else {
setIsScrolled(false);
Expand All @@ -32,6 +32,7 @@ export default function Home() {

return (
<div className="bg-background text-foreground flex min-h-[100dvh] flex-col items-center justify-items-center">
<ThemeHotKeyHandler>
<Header
isScrolled={isScrolled}
mobileMenuOpen={mobileMenuOpen}
Expand All @@ -40,7 +41,6 @@ export default function Home() {
<main className="w-full flex-1">
<Hero />
<ThemePresetSelector />
<Testimonials />
<Features />
<AIGenerationCTA />
<HowItWorks />
Expand All @@ -49,6 +49,7 @@ export default function Home() {
<CTA />
</main>
<Footer />
</ThemeHotKeyHandler>
</div>
);
}
180 changes: 180 additions & 0 deletions components/editor/keyboard-shortcut-overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"use client";

import React, { useState, useEffect, useRef, useCallback } from 'react';
import { X } from 'lucide-react';
import { cn } from '@/lib/utils';
import { ScrollArea } from '@/components/ui/scroll-area';

interface KeyboardShortcutsOverlayProps {
children: React.ReactNode;
}

const KeyboardShortcutsOverlay: React.FC<KeyboardShortcutsOverlayProps> = ({ children }) => {
const [isVisible, setIsVisible] = useState(false);
const overlayRef = useRef<HTMLDivElement>(null);

const shortcuts = [
{
category: "EDITING",
items: [
{ action: "Apply random theme", keys: ["Space"] },
{ action: "Undo", keys: ["Ctrl", "Z"] },
{ action: "Redo", keys: ["Ctrl", "Y"] },
{ action: "Reset to current preset", keys: ["Ctrl", "R"] },
{ action: "Save theme", keys: ["Ctrl", "S"] },
]
},
{
category: "NAVIGATION",
items: [
{ action: "Next theme", keys: ["Ctrl", "→"] },
{ action: "Previous theme", keys: ["Ctrl", "←"] },
{ action: "Open AI tab", fun: () => console.log("Open AI Tab (Ctrl+Shift+O)"), keys: ["Ctrl", "Shift", "O"] },
{ action: "Toggle code panel", keys: ["Ctrl", "B"] },
]
},
{
category: "COPY",
items: [
{ action: "Copy theme CSS", keys: ["Ctrl", "Shift", "C"] },
{ action: "Copy registry command", keys: ["Ctrl", "Alt", "C"] },
]
},
{
category: "HELP",
items: [
{ action: "Show/hide shortcuts", keys: ["Ctrl", "/"] },
]
}
];

const handleClose = useCallback(() => {
setIsVisible(false);
}, []);

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.ctrlKey && event.code === 'Slash') {
event.preventDefault();
setIsVisible(prev => !prev);
return;
}


if (event.code === 'Escape' && isVisible) {
event.preventDefault();
handleClose();
return;
}
};

const handleClickOutside = (event: MouseEvent) => {
if (isVisible && overlayRef.current && !overlayRef.current.contains(event.target as Node)) {
handleClose();
}
};

window.addEventListener('keydown', handleKeyDown);
document.addEventListener('mousedown', handleClickOutside);

return () => {
window.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isVisible, handleClose]);

useEffect(() => {
if (isVisible) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isVisible]);

const KeyBadge: React.FC<{ keyName: string; className?: string }> = ({ keyName, className }) => (
<kbd
className={cn(
"inline-flex items-center justify-center min-w-[24px] h-6 px-2 text-xs font-semibold bg-secondary text-secondary-foreground border border-secondary-foreground/20 rounded-md shadow-sm",
"select-none",
className
)}
>
{keyName}
</kbd>
);

return (
<>
{children}
{isVisible && (
<div
className="fixed inset-0 bg-background/80 backdrop-blur-sm flex items-center justify-center z-[1000] p-4 animate-in fade-in-0 duration-200"
aria-modal="true"
role="dialog"
>
<div
ref={overlayRef}
className="bg-card rounded-lg shadow-2xl max-w-2xl w-full max-h-[80vh] flex flex-col border border-border
transform transition-all animate-in zoom-in-95 data-[state=open]:slide-in-from-bottom-2 duration-300 ease-out"
>
<div className="flex items-center justify-between p-6 border-b border-border">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-primary/10 text-primary rounded-md flex items-center justify-center font-bold text-lg">
</div>
<h2 className="text-xl font-semibold text-foreground">Keyboard Shortcuts</h2>
</div>
<button
onClick={handleClose}
className="text-muted-foreground hover:text-foreground transition-colors p-2 rounded-md hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
aria-label="Close"
>
<X size={20} />
</button>
</div>

<ScrollArea className="flex rounded-md flex-col h-full p-6">
<p className="text-muted-foreground text-sm mb-6">
Speed up your theme editing workflow with these keyboard shortcuts.
</p>

<div className="space-y-8">
{shortcuts.map((category, categoryIndex) => (
<div key={categoryIndex}>
<h3 className="text-sm font-medium text-muted-foreground uppercase tracking-wider mb-4">
{category.category}
</h3>
<div className="space-y-3">
{category.items.map((shortcut, index) => (
<div key={index} className="flex items-center justify-between group">
<span className="text-foreground text-sm font-medium">
{shortcut.action}
</span>
<div className="flex items-center gap-1">
{shortcut.keys.map((key, keyIndex) => (
<React.Fragment key={keyIndex}>
<KeyBadge keyName={key} />
{keyIndex < shortcut.keys.length - 1 && (
<span className="text-muted-foreground text-xs font-semibold mx-0.5">+</span>
)}
</React.Fragment>
))}
</div>
</div>
))}
</div>
</div>
))}
</div>
</ScrollArea>
</div>
</div>
)}
</>
);
};

export default KeyboardShortcutsOverlay;
60 changes: 60 additions & 0 deletions components/home/theme-hotkey-handler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client"
import { useClient } from "@/hooks/use-client"
import { useEditorStore } from "@/store/editor-store"
import { defaultPresets } from "@/utils/theme-presets"
import { useEffect, useMemo } from "react"


export const ThemeHotKeyHandler = ({children}:{children:React.ReactNode}) => {

const { themeState, applyThemePreset } = useEditorStore()

const availableThemes = useMemo(() => Object.keys(defaultPresets),[])
const isClient = useClient()

const applyRandomTheme = () => {

if(!isClient) return;

const currentTheme = themeState.preset
const otherThemes = availableThemes.filter(theme => theme != currentTheme)

if(otherThemes.length > 0){
const randomIndex = Math.floor(Math.random()* otherThemes.length)
const randomTheme = otherThemes[randomIndex]
applyThemePreset(randomTheme)
}
}

useEffect(() => {

if(!isClient) return;

const handleKeySpaceStroke = (event: KeyboardEvent) => {
if(!event.target || !(event.target instanceof HTMLElement)) return;

if(event.code === "Space" &&
event.target.tagName !== "INPUT" &&
event.target.tagName !== "TEXTAREA" &&
!event.target.isContentEditable &&
!event.target.closest('[contenteditable="true"]')) {

event.preventDefault();
applyRandomTheme();
}
};

window.addEventListener('keydown', handleKeySpaceStroke);


return () => {
window.removeEventListener('keydown', handleKeySpaceStroke);
};
}, [isClient, themeState.preset]);

return (
<div>
{children}
</div>
)
}
15 changes: 15 additions & 0 deletions hooks/use-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client"

import { useEffect, useState } from "react";

export function useClient(){

const [isClient,setIsClient] = useState(false)

useEffect(() => {

setIsClient(true)
},[])

return isClient;
}
Loading