Skip to content
Closed
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
16 changes: 16 additions & 0 deletions apps/web/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/App.css",
"baseColor": "zinc",
"cssVariables": false
},
"aliases": {
"components": "./src/components",
"ui": "./src/components/ui"
}
}
2 changes: 1 addition & 1 deletion apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<!-- PWA Meta Tags -->
<meta name="description" content="생산적인 시간 관리를 위한 시간 기록 앱" />
<meta name="theme-color" content="#6366f1" />
<meta name="theme-color" content="#050705" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
Expand Down
165 changes: 165 additions & 0 deletions apps/web/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,161 @@
@tailwind components;
@tailwind utilities;

:root {
color-scheme: dark;
background: #050705;
}

html,
body,
#root {
height: 100%;
margin: 0;
overflow: hidden;
}

body {
background: #050705;
}

::selection {
background: rgb(201 255 61 / 25%);
color: #f5f8f5;
}

.focus-command-deck {
isolation: isolate;
position: relative;
background-color: #050705;
background-image:
linear-gradient(rgb(201 255 61 / 2%) 1px, transparent 1px),
linear-gradient(90deg, rgb(201 255 61 / 2%) 1px, transparent 1px),
radial-gradient(circle at 50% -20%, rgb(201 255 61 / 7%), transparent 38%);
background-size: 32px 32px, 32px 32px, 100% 100%;
}

.focus-command-deck::after {
position: absolute;
z-index: 0;
inset: 0;
pointer-events: none;
content: '';
opacity: 0.25;
background: repeating-linear-gradient(
to bottom,
transparent 0,
transparent 3px,
rgb(255 255 255 / 1.5%) 4px
);
}

.deck-key {
display: inline-flex;
min-width: 22px;
height: 20px;
align-items: center;
justify-content: center;
border: 1px solid #3a423b;
background: #111512;
padding: 0 6px;
color: #aeb7af;
font-family: 'SFMono-Regular', 'Cascadia Code', ui-monospace, monospace;
font-size: 8px;
font-weight: 700;
line-height: 1;
letter-spacing: 0.05em;
box-shadow: inset 0 -1px 0 rgb(255 255 255 / 5%);
}

.deck-key-dark {
height: 17px;
border-color: #2f3630;
background: #070907;
padding-inline: 4px;
color: #788179;
}

.deck-toolbar .btn {
min-height: 2.25rem;
height: 2.25rem;
border: 1px solid #2d342e;
border-radius: 0;
background: #0b0e0c;
color: #8e978f;
font-family: 'SFMono-Regular', 'Cascadia Code', ui-monospace, monospace;
font-size: 10px;
box-shadow: none;
}

.deck-toolbar .btn:hover {
border-color: rgb(201 255 61 / 55%);
background: #111612;
color: #c9ff3d;
}

.deck-toolbar .btn-primary {
border-color: #c9ff3d;
background: #c9ff3d;
color: #080a08;
}

.deck-toolbar .btn-primary:hover {
background: #dbff78;
color: #080a08;
}

.deck-toolbar .btn-circle {
width: 2.25rem;
padding: 0;
}

.deck-toolbar .dropdown-content {
border-color: #343b35;
border-radius: 0;
background: #0b0e0c;
color: #dfe6e0;
}

.focus-command-deck .modal-box {
border: 1px solid #353c36;
border-radius: 0;
background: #0b0e0c;
color: #e8eee9;
box-shadow: 0 30px 90px rgb(0 0 0 / 60%);
}

.focus-command-deck .modal-box .input,
.focus-command-deck .modal-box .textarea,
.focus-command-deck .modal-box .select {
border-color: #343b35;
border-radius: 0;
background: #080b09;
color: #e8eee9;
}

.focus-command-deck main > * {
animation: deck-panel-in 420ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

.focus-command-deck main > :nth-child(2) {
animation-delay: 60ms;
}

.focus-command-deck main > :nth-child(3) {
animation-delay: 120ms;
}

@keyframes deck-panel-in {
from {
opacity: 0;
transform: translateY(7px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes shake {
0% {
transform: translateX(0);
Expand Down Expand Up @@ -32,3 +187,13 @@
.shake-animation {
animation: shake 600ms ease-in-out;
}

@media (prefers-reduced-motion: reduce) {
.focus-command-deck main > * {
animation: none;
}

.shake-animation {
animation-duration: 1ms;
}
}
62 changes: 55 additions & 7 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@ import { useEffect, useMemo } from 'react';
import { Command, CommandPalette } from './components/CommandPalette';
import { useCommandPalette } from './hooks/useCommandPalette';
import { Routes } from './Routes';
import { focusActivityInput } from './utils/commandEvents';
import {
dispatchAddConsumptionStart,
dispatchAddProductionStart,
focusActivityInput,
} from './utils/commandEvents';

function isEditableTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) return false;

return (
target.isContentEditable ||
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
target instanceof HTMLSelectElement
);
}

export const App = () => {
const { isOpen, close } = useCommandPalette();
Expand All @@ -15,6 +30,43 @@ export const App = () => {
console.log(`[build version] ${__BUILD_VERSION__}`);
}, []);

useEffect(() => {
const handleOperationalShortcuts = (event: KeyboardEvent) => {
const hasOpenDialog = document.querySelector('.modal-open') !== null;
if (isOpen || hasOpenDialog || event.isComposing) {
return;
}

if (
event.key === '/' &&
!event.altKey &&
!event.ctrlKey &&
!event.metaKey &&
!isEditableTarget(event.target)
) {
event.preventDefault();
focusActivityInput();
return;
}

if (event.altKey && !event.ctrlKey && !event.metaKey) {
if (event.code === 'Digit1') {
event.preventDefault();
dispatchAddProductionStart();
}

if (event.code === 'Digit2') {
event.preventDefault();
dispatchAddConsumptionStart();
}
}
};

window.addEventListener('keydown', handleOperationalShortcuts);
return () =>
window.removeEventListener('keydown', handleOperationalShortcuts);
}, [isOpen]);

const commands: Command[] = useMemo(
() => [
{
Expand All @@ -31,9 +83,7 @@ export const App = () => {
description: '현재 시각으로 생산 활동을 시작합니다',
icon: <PlusCircle size={18} />,
action: () => {
import('./utils/commandEvents').then((module) => {
module.dispatchAddProductionStart();
});
dispatchAddProductionStart();
},
keywords: ['생산', 'production', 'start', '시작'],
},
Expand All @@ -43,9 +93,7 @@ export const App = () => {
description: '현재 시각으로 소비 활동을 시작합니다',
icon: <MinusCircle size={18} />,
action: () => {
import('./utils/commandEvents').then((module) => {
module.dispatchAddConsumptionStart();
});
dispatchAddConsumptionStart();
},
keywords: ['소비', 'consumption', 'start', '시작'],
},
Expand Down
Loading
Loading