Skip to content

Commit

Permalink
feat: update ui
Browse files Browse the repository at this point in the history
  • Loading branch information
xnmeet committed Jan 5, 2025
1 parent 12ce4c3 commit e773167
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 265 deletions.
4 changes: 2 additions & 2 deletions apps/web/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Concert+One&family=Jersey+15&display=swap"
href="https://fonts.googleapis.com/css2?family=Concert+One&display=swap"
rel="stylesheet" />
<title>Rico Thrift Converter</title>
<title>Rico</title>
</head>
<body>
<div id="root"></div>
Expand Down
15 changes: 12 additions & 3 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ParserService } from './lib/parser/service';
import { initRicoWasm } from './lib/rico';
import { EXAMPLE_THRIFT, EXAMPLE_JSON } from './lib/examples';
import { Header } from './components/layout/Header';
import { Footer } from './components/layout/Footer';
import { ArrowRight } from './components/icons/ArrowRight';
import {
CONVERSION_MODES,
type ConversionMode,
Expand Down Expand Up @@ -159,8 +161,8 @@ function App() {
: 'thrift';

return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 p-6 dark:from-gray-900 dark:to-gray-800">
<div className="mx-auto flex max-w-[1600px] flex-col gap-6">
<div className="flex h-screen flex-col bg-gradient-to-br from-gray-50 to-gray-100 p-3 dark:from-[#242938] dark:to-[#1e2230]">
<div className="mx-auto flex h-full w-full max-w-[1600px] flex-col gap-3">
<Header
isThriftToJson={isThriftToJson}
isDisabled={isDisabled}
Expand All @@ -170,14 +172,19 @@ function App() {
parserMetrics={parserMetrics}
/>

<main className="grid h-[calc(100vh-12rem)] grid-cols-2 gap-6">
<main className="flex-1 grid grid-cols-2 gap-3 min-h-0 relative">
<ThriftEditor
value={input}
onChange={setInput}
title={getEditorTitle(true)}
subtitle={getEditorSubtitle(true)}
language={getEditorLanguage(true)}
/>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/80 dark:bg-[#2b3245]/80 shadow-lg flex items-center justify-center z-10">
<div className="text-gray-400 dark:text-gray-500">
<ArrowRight />
</div>
</div>
<ThriftEditor
value={output}
editable={false}
Expand All @@ -186,6 +193,8 @@ function App() {
language={getEditorLanguage(false)}
/>
</main>

<Footer />
</div>
</div>
);
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/assets/icons/crates.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions apps/web/src/assets/icons/github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions apps/web/src/assets/icons/npm.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/web/src/assets/icons/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 47 additions & 84 deletions apps/web/src/components/converter/ConversionToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Button } from '../ui/button';
import { ArrowLeftRight } from 'lucide-react';
import { cn } from '../../lib/utils';
import { RefreshCw } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';

interface ConversionToggleProps {
isThriftToJson: boolean;
isDisabled?: boolean;
isDisabled: boolean;
onToggle: () => void;
}

Expand All @@ -15,91 +13,56 @@ export function ConversionToggle({
onToggle
}: ConversionToggleProps) {
return (
<Button
<button
onClick={onToggle}
variant="outline"
className={cn(
'relative flex items-center gap-2 rounded-full border-gray-200/60 px-4 py-2 text-base shadow-sm transition-colors',
isDisabled && 'opacity-50 cursor-not-allowed',
!isDisabled && 'hover:border-gray-300 hover:bg-gray-50'
)}
disabled={isDisabled}>
<div className="relative w-[4.5rem]">
<AnimatePresence mode="wait">
<motion.div
key={isThriftToJson ? 'thrift' : 'json'}
initial={{
y: isThriftToJson ? 20 : -20,
opacity: 0
}}
animate={{
y: 0,
opacity: 1
}}
exit={{
y: isThriftToJson ? -20 : 20,
opacity: 0
}}
transition={{
duration: 0.15,
ease: 'easeInOut'
}}
className="absolute inset-0 flex items-center justify-center">
<span
className={cn(
'font-display tracking-tight whitespace-nowrap text-base',
isThriftToJson ? 'text-blue-600 font-semibold' : 'text-gray-500'
)}>
{isThriftToJson ? 'Thrift' : 'JSON'}
</span>
</motion.div>
</AnimatePresence>
</div>

<motion.div
animate={{ rotate: isThriftToJson ? 0 : 180 }}
transition={{
type: 'spring',
stiffness: 200,
damping: 30
}}
className="mx-0.5">
<ArrowLeftRight className="h-4 w-4 text-gray-400" />
</motion.div>
disabled={isDisabled}
className="group relative flex h-8 items-center rounded-lg bg-white/80 px-1 shadow-sm ring-1 ring-gray-200/50 transition hover:bg-white/90 disabled:opacity-50 dark:bg-[#1a1b26]/90 dark:ring-[#2c2e3f] dark:hover:bg-[#1a1b26]">
<div className="relative flex items-center gap-3 px-3">
<div className="w-14 text-center">
<AnimatePresence mode="wait">
<motion.div
key={isThriftToJson ? 'thrift' : 'json'}
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -10, opacity: 0 }}
transition={{ duration: 0.15 }}
className="text-gray-900 dark:text-[#c0caf5]">
<span className="text-xs font-semibold tracking-wide">
{isThriftToJson ? 'THRIFT' : 'JSON'}
</span>
</motion.div>
</AnimatePresence>
</div>

<div className="relative w-[4.5rem]">
<AnimatePresence mode="wait">
<div className="relative flex h-5 w-5 items-center justify-center">
<div className="absolute inset-0 rounded-full bg-gray-900/[0.03] shadow-sm ring-1 ring-gray-900/[0.05] transition group-hover:bg-gray-900/[0.05] dark:bg-[#2c2e3f] dark:ring-[#414868] dark:group-hover:bg-[#414868]" />
<motion.div
key={isThriftToJson ? 'json' : 'thrift'}
initial={{
y: isThriftToJson ? -20 : 20,
opacity: 0
}}
animate={{
y: 0,
opacity: 1
}}
exit={{
y: isThriftToJson ? 20 : -20,
opacity: 0
}}
animate={{ rotate: isThriftToJson ? 360 : 0 }}
transition={{
duration: 0.15,
ease: 'easeInOut'
}}
className="absolute inset-0 flex items-center justify-center">
<span
className={cn(
'font-display tracking-tight whitespace-nowrap text-base',
!isThriftToJson
? 'text-blue-600 font-semibold'
: 'text-gray-500'
)}>
{isThriftToJson ? 'JSON' : 'Thrift'}
</span>
type: 'spring',
stiffness: 200,
damping: 10
}}>
<RefreshCw className="relative h-3 w-3 text-gray-500 transition-colors group-hover:text-gray-900 dark:text-[#7aa2f7] dark:group-hover:text-[#89b4f7]" />
</motion.div>
</AnimatePresence>
</div>

<div className="w-14 text-center">
<AnimatePresence mode="wait">
<motion.div
key={isThriftToJson ? 'json' : 'thrift'}
initial={{ y: -10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 10, opacity: 0 }}
transition={{ duration: 0.15 }}
className="text-gray-400 dark:text-[#565f89]">
<span className="text-xs font-semibold tracking-wide">
{isThriftToJson ? 'JSON' : 'THRIFT'}
</span>
</motion.div>
</AnimatePresence>
</div>
</div>
</Button>
</button>
);
}
6 changes: 3 additions & 3 deletions apps/web/src/components/editor/ThriftEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ export function ThriftEditor({
<div
ref={ref}
className={cn(
'flex h-full flex-col overflow-hidden rounded-lg bg-white shadow-sm dark:bg-gray-800',
'flex h-full flex-col overflow-hidden rounded-lg bg-white shadow-sm dark:bg-[#2b3245]/90',
className
)}>
<div className="flex items-center justify-between border-b border-gray-100 px-4 py-2 dark:border-gray-700">
<div className="flex items-center justify-between border-b border-gray-100 px-4 py-2 dark:border-[#363d52]/50">
<div className="flex items-center gap-2">
<TitleIcon className="h-4 w-4 text-blue-500/70" strokeWidth={2.5} />
<div>
Expand Down Expand Up @@ -95,7 +95,7 @@ export function ThriftEditor({
readOnly={!editable}
extensions={[json()]}
className={cn(
'h-full overflow-auto [&_.cm-gutters]:border-none [&_.cm-gutters]:bg-white [&_.cm-focused]:outline-none dark:[&_.cm-gutters]:bg-gray-800',
'h-full overflow-auto [&_.cm-gutters]:border-none [&_.cm-gutters]:bg-white [&_.cm-focused]:outline-none dark:[&_.cm-gutters]:bg-[#0d1117]',
!editable && 'cursor-text select-text'
)}
basicSetup={{
Expand Down
21 changes: 21 additions & 0 deletions apps/web/src/components/icons/ArrowRight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { cn } from '../../lib/utils';

interface ArrowRightProps {
className?: string;
}

export function ArrowRight({ className }: ArrowRightProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={cn('w-6 h-6', className)}>
<path d="M5 12h14m-7-7 7 7-7 7" />
</svg>
);
}
56 changes: 56 additions & 0 deletions apps/web/src/components/layout/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import githubIcon from '../../assets/icons/github.svg';
import npmIcon from '../../assets/icons/npm.svg';
import cratesIcon from '../../assets/icons/crates.svg';
import vercelIcon from '../../assets/icons/vercel.svg';

const linkClass =
'group flex items-center gap-1.5 text-gray-600/90 transition-all hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200';
const dividerClass = 'h-4 w-px bg-gray-200/50 dark:bg-gray-700/30';
const imageClass =
'h-4 w-4 transition-colors [filter:invert(0.4)_sepia(0)_saturate(0)] group-hover:[filter:invert(0)_sepia(0)_saturate(1)] dark:[filter:invert(0.7)_sepia(0)_saturate(0)] dark:group-hover:[filter:invert(0.9)_sepia(0)_saturate(0)]';
const textClass = 'text-xs font-medium tracking-wide';

export const Footer = () => {
return (
<footer className="flex justify-center gap-3 py-2">
<a
href="https://github.com/xnmeet/rico"
target="_blank"
rel="noopener noreferrer"
className={linkClass}>
<img src={githubIcon} alt="GitHub" className={imageClass} />
<span className={textClass}>GitHub</span>
</a>
<div className={dividerClass} />
<a
href="https://www.npmjs.com/package/@rico-core/parser"
target="_blank"
rel="noopener noreferrer"
className={linkClass}>
<img
src={npmIcon}
alt="NPM"
className="h-3 w-[42px] transition-colors [filter:invert(0.4)_sepia(0)_saturate(0)] group-hover:[filter:invert(0)_sepia(0)_saturate(1)] dark:[filter:invert(0.7)_sepia(0)_saturate(0)] dark:group-hover:[filter:invert(0.9)_sepia(0)_saturate(0)]"
/>
</a>
<div className={dividerClass} />
<a
href="https://crates.io/crates/rico"
target="_blank"
rel="noopener noreferrer"
className={linkClass}>
<img src={cratesIcon} alt="Crates.io" className={imageClass} />
<span className={textClass}>Crates.io</span>
</a>
<div className={dividerClass} />
<a
href="https://vercel.com"
target="_blank"
rel="noopener noreferrer"
className={linkClass}>
<img src={vercelIcon} alt="Vercel" className={imageClass} />
<span className={textClass}>Vercel</span>
</a>
</footer>
);
};
22 changes: 9 additions & 13 deletions apps/web/src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RicoLogo } from '../logo/RicoLogo';
import { ConversionToggle } from '../converter/ConversionToggle';
import { ParserStatus } from '../status/ParserStatus';
import { StatusDisplay } from '../status/StatusDisplay';
import { ThemeToggle } from '../theme/ThemeToggle';
import type {
WasmStatus,
Expand All @@ -26,19 +26,15 @@ export function Header({
parserMetrics
}: HeaderProps) {
return (
<header className="relative flex items-center justify-between overflow-hidden rounded-2xl border border-gray-200/20 bg-white/10 px-6 py-4 shadow-[0_8px_16px_-6px_rgb(0_0_0/0.05)] backdrop-blur-xl dark:border-white/10 dark:bg-gray-800/10 dark:shadow-[0_8px_16px_-6px_rgb(0_0_0/0.15)]">
<div className="absolute inset-0 bg-gradient-to-r from-white/50 via-white/30 to-white/50 dark:from-white/[0.03] dark:via-white/[0.02] dark:to-white/[0.03]" />
<header className="relative flex items-center justify-between overflow-hidden rounded-2xl border border-gray-200/20 bg-white/10 px-5 py-3 shadow-[0_8px_16px_-6px_rgb(0_0_0/0.05)] backdrop-blur-xl dark:border-[#363d52]/30 dark:bg-[#2b3245]/40 dark:shadow-[0_8px_16px_-6px_rgb(0_0_0/0.2)]">
<div className="absolute inset-0 bg-gradient-to-r from-white/50 via-white/30 to-white/50 dark:from-[#363d52]/20 dark:via-[#2f354a]/15 dark:to-[#363d52]/20" />
<div className="relative flex items-center gap-3">
<RicoLogo />
<RicoLogo className="h-8 w-8" />
<div>
<h1 className="font-display text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
<h1 className="font-display text-xl font-bold tracking-tight text-gray-900 dark:text-white">
Rico
<span className="font-display font-medium text-gray-600 dark:text-gray-400">
{' '}
Thrift Converter
</span>
</h1>
<p className="font-display text-sm font-medium tracking-tight text-gray-500 dark:text-gray-400">
<p className="font-display text-xs font-normal tracking-tight text-gray-500 dark:text-gray-400">
Convert between Thrift IDL and JSON with ease
</p>
</div>
Expand All @@ -52,13 +48,13 @@ export function Header({
/>
</div>

<div className="relative flex items-center space-x-4">
<ParserStatus
<div className="relative flex items-center gap-3">
<StatusDisplay
wasmStatus={wasmStatus}
parserStatus={parserStatus}
metrics={parserMetrics}
/>
<div className="ml-2 h-4 w-px bg-gray-200/50 dark:bg-white/10" />
<div className="h-4 w-px bg-gray-200/50 dark:bg-white/10" />
<ThemeToggle />
</div>
</header>
Expand Down
Loading

0 comments on commit e773167

Please sign in to comment.