mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-24 08:22:10 -04:00
videos in explorer section, update search video, and spacing
This commit is contained in:
Binary file not shown.
BIN
apps/landing/public/videos/Spacedrive_search.webm
Normal file
BIN
apps/landing/public/videos/Spacedrive_search.webm
Normal file
Binary file not shown.
BIN
apps/landing/public/videos/Spacedrive_tagmode.webm
Normal file
BIN
apps/landing/public/videos/Spacedrive_tagmode.webm
Normal file
Binary file not shown.
BIN
apps/landing/public/videos/Spacedrive_tags.webm
Normal file
BIN
apps/landing/public/videos/Spacedrive_tags.webm
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,51 +1,65 @@
|
||||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import libraryArt from '~/assets/bento/library.svg?url';
|
||||
import lockArt from '~/assets/bento/lock.svg?url';
|
||||
import tagsArt from '~/assets/bento/tags.svg?url';
|
||||
import { BentoBox } from '~/components/bento-box';
|
||||
'use client';
|
||||
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
import { SelectedVideo, Video } from '~/components/video';
|
||||
|
||||
const videos: {
|
||||
title: string;
|
||||
src: string;
|
||||
description: string;
|
||||
}[] = [
|
||||
{
|
||||
title: 'Drag and Drop',
|
||||
src: '/videos/Spacedrive_DragAndDrop.webm',
|
||||
description: 'Easily drag and drop files or folders.'
|
||||
},
|
||||
{
|
||||
title: 'Tabs',
|
||||
src: '/videos/Spacedrive_Tabs.webm',
|
||||
description: 'Browse seamlessly with multiple tabs.'
|
||||
},
|
||||
{
|
||||
title: 'Quick Preview',
|
||||
src: '/videos/Spacedrive_QuickPreview.webm',
|
||||
description: 'Instantly preview files and object data.'
|
||||
}
|
||||
];
|
||||
|
||||
export const Explorer = () => {
|
||||
const [selectedVideo, setSelectedVideo] = useState<null | string>(null);
|
||||
return (
|
||||
<div className="container mx-auto flex flex-col flex-wrap items-center gap-10 p-4">
|
||||
<h1 className="flex-1 self-start text-2xl font-semibold leading-8 md:text-3xl md:leading-10 lg:self-start">
|
||||
Explorer.{' '}
|
||||
<span className="bg-gradient-to-r from-zinc-400 to-zinc-600 bg-clip-text text-transparent">
|
||||
{/* Some controlled line breaks here based on breakpoint to make sure the breaks looks nice always :) */}
|
||||
<br className="lg:hidden" />
|
||||
Browse and manage your data
|
||||
<br className="sm:hidden" /> like never before.
|
||||
</span>
|
||||
</h1>
|
||||
<div
|
||||
className={twMerge(
|
||||
'grid w-full grid-cols-3 grid-rows-2 gap-5 max-lg:grid-cols-1 lg:grid-rows-1'
|
||||
)}
|
||||
>
|
||||
<BentoBox
|
||||
className="bento-border-top lg:bento-border-left"
|
||||
imageSrc={libraryArt}
|
||||
imageAlt="Library"
|
||||
title="Seamless Sync & Access"
|
||||
titleColor="#63C3F3"
|
||||
description="Whether online or offline, instantly access your data anytime, anywhere. Keeping everything updated and available across your devices."
|
||||
/>
|
||||
<BentoBox
|
||||
imageSrc={lockArt}
|
||||
imageAlt="Lock"
|
||||
title="Privacy & Control"
|
||||
titleColor="#6368F3"
|
||||
description="Your data is yours. With Spacedrive’s top-notch security, only you can access your information — no third parties, no exceptions."
|
||||
/>
|
||||
<BentoBox
|
||||
className="bento-border-bottom lg:bento-border-right"
|
||||
imageSrc={tagsArt}
|
||||
imageAlt="Tags"
|
||||
title="Effortless Organization"
|
||||
titleColor="#DF63F3"
|
||||
description="Keep your digital life organized with automatic categorization and smart structuring, making it easy to find what you need instantly."
|
||||
/>
|
||||
<>
|
||||
{selectedVideo ? (
|
||||
<AnimatePresence>
|
||||
<SelectedVideo src={selectedVideo} />
|
||||
</AnimatePresence>
|
||||
) : null}
|
||||
<div className="container mx-auto flex flex-col flex-wrap items-center gap-10 p-4">
|
||||
<h1 className="flex-1 self-start text-2xl font-semibold leading-8 md:text-3xl md:leading-10 lg:self-start">
|
||||
Explorer.{' '}
|
||||
<span className="bg-gradient-to-r from-zinc-400 to-zinc-600 bg-clip-text text-transparent">
|
||||
{/* Some controlled line breaks here based on breakpoint to make sure the breaks looks nice always :) */}
|
||||
<br className="lg:hidden" />
|
||||
Browse and manage your data
|
||||
<br className="sm:hidden" /> like never before.
|
||||
</span>
|
||||
</h1>
|
||||
<div className="grid w-full grid-cols-1 gap-10 md:grid-cols-3 md:gap-4">
|
||||
{videos.map((video) => (
|
||||
<div className="h-fit" key={video.src}>
|
||||
<Video
|
||||
setSelectedVideo={setSelectedVideo}
|
||||
layoutId={`video-${video.src}`}
|
||||
onClick={() => setSelectedVideo(video.src)}
|
||||
{...video}
|
||||
/>
|
||||
<h2 className="mt-5 text-lg font-bold text-white">{video.title}</h2>
|
||||
<p className="text-md text-ink-dull">{video.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './explorer';
|
||||
export * from './github';
|
||||
export * from './features';
|
||||
export * from './search';
|
||||
export * from './tags';
|
||||
|
||||
@@ -20,7 +20,7 @@ export const Search = () => {
|
||||
muted
|
||||
controls={false}
|
||||
loop
|
||||
src="/videos/search.mp4"
|
||||
src="/videos/Spacedrive_search.webm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
61
apps/landing/src/app/page-sections/tags.tsx
Normal file
61
apps/landing/src/app/page-sections/tags.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import React from 'react';
|
||||
import { SelectedVideo, Video } from '~/components/video';
|
||||
|
||||
const videos: {
|
||||
title: string;
|
||||
src: string;
|
||||
description: string;
|
||||
}[] = [
|
||||
{
|
||||
title: 'Tag Assignment mode',
|
||||
src: '/videos/Spacedrive_tagmode.webm',
|
||||
description: 'Assign tags to files and folders quickly and easily'
|
||||
},
|
||||
{
|
||||
title: 'Contextual Tagging',
|
||||
src: '/videos/Spacedrive_tags.webm',
|
||||
description: 'Tag files and folders directly from the right-click menu'
|
||||
}
|
||||
];
|
||||
|
||||
const Tags = () => {
|
||||
const [selectedVideo, setSelectedVideo] = React.useState<null | string>(null);
|
||||
return (
|
||||
<>
|
||||
{selectedVideo ? (
|
||||
<AnimatePresence>
|
||||
<SelectedVideo src={selectedVideo} />
|
||||
</AnimatePresence>
|
||||
) : null}
|
||||
<div className="container mx-auto flex flex-col flex-wrap items-center gap-10 p-4">
|
||||
<h1 className="flex-1 self-start text-2xl font-semibold leading-8 md:text-3xl md:leading-10 lg:self-start">
|
||||
Multiple ways to set tags.{' '}
|
||||
<span className="bg-gradient-to-r from-zinc-400 to-zinc-600 bg-clip-text text-transparent">
|
||||
{/* Some controlled line breaks here based on breakpoint to make sure the breaks looks nice always :) */}
|
||||
<br className="lg:hidden" />
|
||||
quickly organize your files.
|
||||
</span>
|
||||
</h1>
|
||||
<div className="grid w-full grid-cols-1 gap-10 md:grid-cols-2 md:gap-4">
|
||||
{videos.map((video) => (
|
||||
<div className="h-fit" key={video.src}>
|
||||
<Video
|
||||
setSelectedVideo={setSelectedVideo}
|
||||
layoutId={`video-${video.src}`}
|
||||
onClick={() => setSelectedVideo(video.src)}
|
||||
{...video}
|
||||
/>
|
||||
<h2 className="mt-5 text-lg font-bold text-white">{video.title}</h2>
|
||||
<p className="text-md text-ink-dull">{video.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tags;
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Assistant, Explorer, Features, Github, Header, Search } from '~/app/page-sections';
|
||||
|
||||
import Tags from './page-sections/tags';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Spacedrive — A file manager from the future.',
|
||||
description:
|
||||
@@ -14,10 +16,11 @@ export const metadata = {
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6 md:gap-[200px]">
|
||||
<div className="flex flex-col gap-6 md:gap-[150px]">
|
||||
<Header />
|
||||
<Explorer />
|
||||
<Features />
|
||||
<Explorer />
|
||||
<Tags />
|
||||
<Search />
|
||||
<Assistant />
|
||||
<Github />
|
||||
|
||||
66
apps/landing/src/components/video.tsx
Normal file
66
apps/landing/src/components/video.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import clsx from 'clsx';
|
||||
import { motion, MotionProps } from 'framer-motion';
|
||||
import { ComponentProps, useRef } from 'react';
|
||||
import { useClickOutside } from '~/hooks/useClickOutside';
|
||||
|
||||
type Props = ComponentProps<'video'> &
|
||||
MotionProps & {
|
||||
containerClassName?: string;
|
||||
setSelectedVideo: (src: string | null) => void;
|
||||
};
|
||||
|
||||
const Video = ({ containerClassName, setSelectedVideo, ...rest }: Props) => {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
useClickOutside(videoRef, () => setSelectedVideo(null));
|
||||
return (
|
||||
<div className={clsx(containerClassName)}>
|
||||
<motion.video
|
||||
style={{
|
||||
borderRadius: 12 // for framer-motion
|
||||
}}
|
||||
ref={videoRef}
|
||||
whileHover={{
|
||||
scale: 1.05
|
||||
}}
|
||||
whileTap={{ scale: 1 }}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="size-full cursor-pointer object-cover"
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectedVideo = ({ src }: { src: string }) => {
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
layout
|
||||
exit={{ opacity: 0 }}
|
||||
className="bg-opacity/50 fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-md"
|
||||
/>
|
||||
<div className="fixed inset-0 z-[60] flex items-center justify-center p-5 md:p-0">
|
||||
<motion.video
|
||||
src={src}
|
||||
style={{
|
||||
borderRadius: 12
|
||||
}}
|
||||
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||
autoPlay
|
||||
layoutId={`video-${src}`}
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className="object-cover md:size-3/5"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { SelectedVideo, Video };
|
||||
25
apps/landing/src/hooks/useClickOutside.ts
Normal file
25
apps/landing/src/hooks/useClickOutside.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
|
||||
|
||||
export const useClickOutside = (
|
||||
ref: React.RefObject<HTMLElement>,
|
||||
handler: (event: MouseEvent | TouchEvent) => void
|
||||
): void => {
|
||||
useEffect(() => {
|
||||
const listener = (event: MouseEvent | TouchEvent) => {
|
||||
if (!ref.current || ref.current.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
handler(event);
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', listener);
|
||||
document.addEventListener('touchstart', listener);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', listener);
|
||||
document.removeEventListener('touchstart', listener);
|
||||
};
|
||||
}, [ref, handler]);
|
||||
}
|
||||
@@ -12,8 +12,8 @@ const SUPPORTED_ICONS = ['Document', 'Code', 'Text', 'Config'];
|
||||
|
||||
const positionConfig: Record<string, string> = {
|
||||
Text: 'flex h-full w-full items-center justify-center',
|
||||
Code: 'flex h-full w-full items-center justify-center',
|
||||
Config: 'flex h-full w-full items-center justify-center'
|
||||
Code: 'flex h-full w-full items-center justify-center pt-[18px]',
|
||||
Config: 'flex h-full w-full items-center justify-center pt-[18px]'
|
||||
};
|
||||
|
||||
const LayeredFileIcon = forwardRef<HTMLImageElement, LayeredFileIconProps>(
|
||||
@@ -38,7 +38,7 @@ const LayeredFileIcon = forwardRef<HTMLImageElement, LayeredFileIconProps>(
|
||||
className={clsx('pointer-events-none absolute bottom-0 right-0', positionClass)}
|
||||
>
|
||||
<Suspense>
|
||||
<IconComponent viewBox="0 0 16 16" height="40%" width="40%" />
|
||||
<IconComponent viewBox="0 0 16 16" height="50%" width="50%" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user