Skip to content

Commit

Permalink
feat: added an empty state when searching
Browse files Browse the repository at this point in the history
- tweaks to the app image.
- slight tweaks to colors based upon the background gradient changes
  • Loading branch information
cecilia-sanare committed Mar 15, 2024
1 parent b7040af commit 6479cea
Show file tree
Hide file tree
Showing 20 changed files with 149 additions and 52 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@rain-cafe/react-utils": "^2.3.0",
"@tanstack/react-query": "^5.24.8",
"clsx": "^2.1.0",
"lucide-react": "^0.344.0",
"lucide-react": "^0.358.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.2",
Expand Down
10 changes: 5 additions & 5 deletions src/App.module.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
.background {
&:after {
&:before {
content: '';
width: 72.1875rem;
left: calc(50% - 30rem);
opacity: 0.2;
clip-path: polygon(
74.1% 44.1%,
Expand All @@ -22,8 +20,10 @@
76.1% 97.7%,
74.1% 44.1%
);
@apply absolute h-[30rem] blur-3xl rotate-[30deg] from-[#ff80b5] to-[#9089fc] -translate-x-1/2 aspect-[1155/678] bg-gradient-to-tr transform-gpu;
@apply absolute inset-x-0 min-h-[20%] from-[#ff80b5] to-[#9089fc] bg-gradient-to-tr transform-gpu animate-pulse;

animation-duration: 10s;
}

@apply absolute inset-x-0 blur-3xl -top-80 -z-50;
@apply fixed inset-x-0 min-h-dvh blur-3xl -z-50 max-w-5xl items-center flex flex-col mx-auto;
}
5 changes: 0 additions & 5 deletions src/components/AppHeader.module.css

This file was deleted.

1 change: 0 additions & 1 deletion src/components/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ArrowUp, Edit } from 'lucide-react';
import { cn } from '../utils/cn';
import { Button } from './Button';
import { Input } from './Input';
import * as styles from './AppHeader.module.css';

type Props = {
onChange?: (value: string) => void;
Expand Down
6 changes: 5 additions & 1 deletion src/components/AppImage.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
.appImage {
@apply relative rounded-md overflow-hidden;
@apply relative rounded-md;

> * {
border-radius: inherit;
}
}

a.appImage {
Expand Down
15 changes: 9 additions & 6 deletions src/components/AppImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ type Props = {
};

export const AppImage: FC<Props> = ({ className, id, to }) => {
const url = `https://steamcdn-a.akamaihd.net/steam/apps/${id}/header.jpg`;

return to ? (
<Link className={cn(styles.appImage, className)} to={to}>
<img src={`https://steamcdn-a.akamaihd.net/steam/apps/${id}/header.jpg`} />
<Link className={cn(styles.appImage, 'group', className)} to={to}>
<img src={url} />
<img className="blur-none group-hover:blur absolute inset-0 -z-10 transition-all" src={url} />
</Link>
) : (
<img
className={cn(styles.appImage, className)}
src={`https://steamcdn-a.akamaihd.net/steam/apps/${id}/header.jpg`}
/>
<div className={cn(styles.appImage, className)}>
<img src={url} />
<img className="blur-sm opacity-80 absolute inset-0 -z-10 transition-all" src={url} />
</div>
);
};
13 changes: 10 additions & 3 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type SharedProps = {
children?: ReactNode;
className?: string;
disabled?: boolean;
variant?: 'slim' | 'default';
};

type LinkProps = SharedProps & {
Expand All @@ -24,12 +25,18 @@ const isLink = (props: Props): props is LinkProps => {
return Object.hasOwn(props, 'to');
};

export const Button: FC<Props> = (props) => {
const variantsClassnames: Partial<Record<NonNullable<SharedProps['variant']>, string>> = {
default: 'min-h-14',
slim: 'min-h-10',
};

export const Button: FC<Props> = ({ className: externalClassname, variant = 'default', ...props }) => {
const className = cn(
styles.button,
'flex relative gap-2 items-center justify-center min-w-14 min-h-14 bg-secondary border border-transparent px-3 transition-all overflow-hidden rounded-md',
variantsClassnames[variant],
'flex relative gap-2 items-center justify-center min-w-14 bg-secondary border border-transparent px-3 transition-all overflow-hidden rounded-md',
props.disabled && 'text-white/20 pointer-events-none',
props.className
externalClassname
);
if (isLink(props)) {
if (props.to.startsWith('#')) {
Expand Down
6 changes: 1 addition & 5 deletions src/components/ButtonGroup.module.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
.buttons {
@apply grid grid-cols-[repeat(3,minmax(min-content,1fr))];
@apply grid grid-flow-col;
/* @apply flex gap-[1px] flex-wrap; */

> * {
@apply rounded-none flex-grow;
}

&.vertical {
@apply flex-col;
}
}
22 changes: 18 additions & 4 deletions src/components/ButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,27 @@ type Props = {
label: ReactNode;
children: ReactNode;
vertical?: boolean;
className?: string;
};

export const ButtonGroup: FC<Props> = ({ label, children, vertical }) => {
export const ButtonGroup: FC<Props> = ({ label, children, vertical, className }) => {
return (
<div className="flex flex-col rounded-md overflow-hidden bg-secondary w-full">
<div className="flex items-center justify-between gap-2 p-4 border-b border-b-white/10 font-bold">{label}</div>
<div className={cn(styles.buttons, vertical && styles.vertical)}>{children}</div>
<div
className={cn(
'flex rounded-md overflow-hidden bg-secondary w-full',
vertical ? 'flex-col' : 'flex-row',
className
)}
>
<div
className={cn(
'flex items-center justify-between gap-2 p-4 whitespace-nowrap font-bold',
vertical ? 'border-b border-b-white/10' : 'border-r border-r-white/10'
)}
>
{label}
</div>
<div className={cn(styles.buttons, 'flex-1')}>{children}</div>
</div>
);
};
2 changes: 1 addition & 1 deletion src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ type Props = {
};

export const Card: FC<Props> = ({ className, children }) => {
return <div className={cn('flex flex-col gap-4 rounded-md bg-black/20 p-4', className)}>{children}</div>;
return <div className={cn('flex flex-col gap-4 rounded-md bg-gray-950/40 p-4', className)}>{children}</div>;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
.input {
@apply relative;

.code {
&:not(.disabled) {
&:after {
content: '';
Expand Down
28 changes: 22 additions & 6 deletions src/components/Code.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { FC, ReactNode } from 'react';
import { useState, type FC, type ReactNode } from 'react';
import { cn } from '../utils/cn';
import { toast } from 'sonner';
import { delay } from '@rain-cafe/js-utils';
import { Clipboard } from 'lucide-react';
import { Clipboard, LoaderIcon } from 'lucide-react';
import * as styles from './Code.module.css';
import { Spinner } from './Spinner';

type Props = {
className?: string;
Expand All @@ -11,23 +13,37 @@ type Props = {
};

export const Code: FC<Props> = ({ className, children, shell = false }) => {
const [loading, setLoading] = useState(false);

return (
<div
className={cn(
'text-left relative select-none whitespace-pre-wrap flex flex-row items-start gap-2 rounded-md bg-white/10 px-4 py-2 hover:bg-white/20 transition-colors cursor-pointer pr-10',
'text-left relative select-none whitespace-pre-wrap flex flex-row items-start overflow-hidden gap-2 rounded-md bg-secondary px-4 py-2 transition-colors cursor-pointer pr-10',
styles.code,
shell && "before:content-['$']",
className
)}
onClick={(e) => {
toast.promise(delay(window.navigator.clipboard.writeText(e.currentTarget.innerText)), {
onClick={async (e) => {
setLoading(true);

const promise = delay(window.navigator.clipboard.writeText(e.currentTarget.innerText));
toast.promise(promise, {
loading: 'Copying...',
success: 'Copied to clipboard!',
error: 'Failed to copy to clipboard',
});

try {
await promise;
} finally {
setLoading(false);
}
}}
>
{children}
<Clipboard className="absolute right-2" />
<Spinner className="absolute right-2" loading={loading}>
<Clipboard className="absolute right-2" />
</Spinner>
</div>
);
};
33 changes: 26 additions & 7 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import { type FC, type ComponentProps } from 'react';
import { cn } from '../utils/cn';
import * as styles from './Input.module.css';
import { X } from 'lucide-react';
import { useCachedState } from '@rain-cafe/react-utils';

type Props = {
onChange?: (value: string) => void;
} & Pick<ComponentProps<'input'>, 'value' | 'placeholder' | 'disabled'>;

export const Input: FC<Props> = ({ onChange, ...props }) => {
export const Input: FC<Props> = ({ onChange: externalOnChange, value: externalValue, ...props }) => {
const [value, setValue] = useCachedState(() => externalValue, [externalValue]);

const onChange = (value: string) => {
setValue(value);
externalOnChange?.(value);
};

return (
<div
className={cn(styles.input, 'flex-1 bg-accent rounded-md min-h-12 text-xl', props.disabled && styles.disabled)}
>
<div className={'relative flex-1 min-h-12 bg-accent rounded-md overflow-hidden'}>
<input
{...props}
type="text"
className={cn('h-full w-full bg-transparent px-3 rounded-md')}
onChange={(e) => onChange?.(e.target.value)}
className={cn(
'h-full w-full bg-transparent px-3 text-xl transition-all',
props.disabled ? 'text-white/20 pointer-events-none' : 'hover:bg-white/10'
)}
value={value}
onChange={(e) => onChange(e.target.value)}
/>
<button
className={cn(
'absolute right-4 top-1/2 -translate-y-1/2 rounded-full bg-white/10 hover:bg-white/20 p-1 pointer-events-none opacity-0 transition-opacity',
value && 'pointer-events-auto opacity-100'
)}
onClick={() => onChange('')}
>
<X />
</button>
</div>
);
};
2 changes: 1 addition & 1 deletion src/components/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type Props = {
export const Label: FC<Props> = ({ label, children }) => {
return (
<div className="flex gap-4 items-start justify-between">
<div className="bg-white/20 p-2 rounded-md mr-auto">{label}</div>
<div className="bg-secondary p-2 rounded-md mr-auto">{label}</div>
<div className="flex flex-wrap gap-4 items-center justify-end h-full">{children}</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Pill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const Pill: FC<Props> = ({ children, variant }) => {
return (
<div
className={cn(
'rounded-full bg-white/20 px-3 min-h-10 items-center inline-flex',
'rounded-full bg-secondary px-3 min-h-10 items-center inline-flex',
variant && VariantClasses[variant]
)}
>
Expand Down
35 changes: 35 additions & 0 deletions src/components/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState, type FC, type ReactNode, useEffect } from 'react';
import { CircleCheck, LoaderIcon } from 'lucide-react';
import { cn } from '@/utils/cn';
import { delay } from '@rain-cafe/js-utils';

type Props = {
children: ReactNode;
loading: boolean;
className?: string;
};

enum LoadingState {
NOT_STARTED,
LOADING,
FINISHED,
}

export const Spinner: FC<Props> = ({ children, loading, className }) => {
const [state, setState] = useState(LoadingState.NOT_STARTED);

useEffect(() => {
if (loading) setState(LoadingState.LOADING);
else if (state === LoadingState.LOADING) {
setState(LoadingState.FINISHED);

setTimeout(() => {
setState(LoadingState.NOT_STARTED);
}, 500);
}
}, [loading]);

if (state === LoadingState.LOADING) return <LoaderIcon className={cn('text-white/20 animate-spin', className)} />;
else if (state === LoadingState.FINISHED) return <CircleCheck className={className} />;
return children;
};
2 changes: 2 additions & 0 deletions src/pages/SplashPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const Component: FC = () => {
<CodeIcon />
</>
}
vertical
>
<Button to="https://github.com/rain-cafe/protontweaks">
CLI
Expand All @@ -50,6 +51,7 @@ export const Component: FC = () => {
<LibraryBig />
</>
}
vertical
>
<Button to="https://github.com/rain-cafe/protontweaks-api-rs">
Rust
Expand Down
12 changes: 10 additions & 2 deletions src/pages/apps/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useLoaderData } from '@rain-cafe/react-utils/react-router';
import { useSearch } from '../../context/search';
import { AppImage } from '../../components/AppImage';
import { getApps } from '@/service/protontweaks';
import { Button } from '@/components/Button';
import { ButtonGroup } from '@/components/ButtonGroup';

export async function loader() {
return await getApps();
Expand All @@ -26,9 +28,15 @@ export const Component: FC = () => {
};
}, []);

return (
return filteredApps.length === 0 ? (
<ButtonGroup className="w-fit self-center" label="Can't find the game you're looking for?" vertical>
<Button variant="slim" to="https://github.com/rain-cafe/protontweaks-db/tree/main/apps">
Help us out and add it!~ ❤️
</Button>
</ButtonGroup>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 2xl:grid-cols-3 gap-5 mx-auto">
{filteredApps?.map((app) => (
{filteredApps.map((app) => (
<AppImage key={app.id} id={app.id} to={`/apps/${app.id}`} />
))}
</div>
Expand Down
1 change: 1 addition & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default defineConfig(async () => {
server: {
port: 3030,
hmr: true,
host: '0.0.0.0',
},
resolve: {
alias: {
Expand Down

0 comments on commit 6479cea

Please sign in to comment.