Skip to content

Commit

Permalink
Merge pull request #48 from BinaryStudioAcademy/task/OV-29-add-video-…
Browse files Browse the repository at this point in the history
…items-menu

OV-29: Add Video Items Menu
  • Loading branch information
nikita-remeslov authored Aug 30, 2024
2 parents 4dbbb89 + e30edef commit 5ac5142
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
Icon,
IconButton,
Input,
InputGroup,
InputRightElement,
ViewIcon,
ViewOffIcon,
} from '~/bundles/common/components/components.js';
import { useCallback, useState } from '~/bundles/common/hooks/hooks.js';
import { IconName } from '~/bundles/common/icons/icons.js';

type Properties = {
label: string;
Expand Down Expand Up @@ -44,7 +44,15 @@ const PasswordInput: React.FC<Properties> = ({
aria-label={
isPasswordVisible ? 'Hide password' : 'Show password'
}
icon={isPasswordVisible ? <ViewIcon /> : <ViewOffIcon />}
icon={
<Icon
as={
isPasswordVisible
? IconName.VIEW
: IconName.VIEW_OFF
}
/>
}
onClick={handlePasswordIconClick}
variant="ghostIcon"
/>
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/bundles/common/components/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ export { Overlay } from './overlay/overlay.js';
export { Player } from './player/player.js';
export { RouterProvider } from './router-provider/router-provider.js';
export { VideoModal } from './video-modal/video-modal.js';
export { DownloadIcon } from '@chakra-ui/icons';
export { ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
export {
Badge,
Box,
Center,
Circle,
CloseButton,
Collapse,
ChakraProvider as ComponentsProvider,
Flex,
FormControl,
Expand All @@ -28,6 +28,7 @@ export {
Text,
VStack,
} from '@chakra-ui/react';
export { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
export { FormikProvider as FormProvider } from 'formik';
export { Provider as StoreProvider } from 'react-redux';
export { Outlet as RouterOutlet } from 'react-router-dom';
21 changes: 19 additions & 2 deletions frontend/src/bundles/common/icons/icon-name.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import { faEllipsisVertical, faPen } from '@fortawesome/free-solid-svg-icons';
import { DownloadIcon, ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
import {
faCircleUser,
faCloudArrowUp,
faEllipsisVertical,
faFont,
faPen,
faT,
faTableColumns,
} from '@fortawesome/free-solid-svg-icons';

const IconName = {
OPTIONS_VERTICAL: faEllipsisVertical,
PEN: faPen,
OPTIONS_VERTICAL: faEllipsisVertical,
DOWNLOAD: DownloadIcon,
VIEW: ViewIcon,
VIEW_OFF: ViewOffIcon,
AVATAR: faCircleUser,
UPLOAD: faCloudArrowUp,
TEMPLATE: faTableColumns,
SCRIPT: faFont,
TEXT: faT,
} as const;

export { IconName };
5 changes: 3 additions & 2 deletions frontend/src/bundles/studio/pages/studio.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
Button,
DownloadIcon,
Header,
Icon,
IconButton,
} from '~/bundles/common/components/components.js';
import { IconName } from '~/bundles/common/icons/icons.js';

const Studio: React.FC = () => {
return (
Expand All @@ -20,7 +21,7 @@ const Studio: React.FC = () => {
<IconButton
variant="primaryOutlined"
aria-label="Download"
icon={<DownloadIcon />}
icon={<Icon as={IconName.DOWNLOAD} />}
/>
}
/>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/bundles/video-editor/components/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Menu } from './menu/menu.js';
export { MenuBody } from './menu-body/menu-body.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
Box,
CloseButton,
Flex,
Heading,
VStack,
} from '~/bundles/common/components/components.js';

type Properties = {
title: string | React.ReactNode;
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
};

const MenuBody: React.FC<Properties> = ({
title,
children,
isOpen,
onClose,
}) => {
return (
<>
{isOpen && (
<Box
sx={{
position: 'fixed',
top: '50%',
transform: 'translateY(-50%)',
left: '100px',
height: '72vh',
width: '335px',
p: 2,
zIndex: 1000,
borderRadius: '8px',
boxShadow: 'lg',
bg: 'background.900',
color: 'white',
}}
>
<Flex justifyContent="space-between" alignItems="center">
<Heading as="h3">{title}</Heading>
<CloseButton onClick={onClose} color="background.600" />
</Flex>
<VStack mt={4} spacing={4} align="start">
{children}
</VStack>
</Box>
)}
</>
);
};

export { MenuBody };
93 changes: 93 additions & 0 deletions frontend/src/bundles/video-editor/components/menu/menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Box, Flex, VStack } from '~/bundles/common/components/components.js';
import { useCallback } from '~/bundles/common/hooks/hooks.js';

import { type MenuItem } from '../../types/types.js';

type Properties = {
items: MenuItem[];
activeIndex: number | null;
onActiveIndexSet: (index: number) => void;
};

const Menu: React.FC<Properties> = ({
items,
activeIndex,
onActiveIndexSet,
}) => {
const handleClick = useCallback(
(index: number) => {
return () => {
if (!items || items.length === 0 || index >= items.length) {
return;
}

const item = items[index];
if (!item) {
return;
}

onActiveIndexSet(index);
item.onClick();
};
},
[onActiveIndexSet, items],
);
return (
<Box
sx={{
position: 'fixed',
left: 0,
top: '50%',
transform: 'translateY(-50%)',
h: 'auto',
w: '84px',
bg: 'background.900',
color: 'white',
borderTopRightRadius: 'xl',
borderBottomRightRadius: 'xl',
}}
>
<VStack spacing={1} align="stretch" p={2} w="100%">
{items.map((item, index) => (
<Flex
key={index}
onClick={handleClick(index)}
sx={{
flexDirection: 'column',
alignItems: 'center',
p: 2,
w: '66px',
gap: 1,
cursor: 'pointer',
borderRadius: '8px',
bg:
activeIndex === index
? 'background.600'
: 'transparent',
_hover: {
bg: 'background.600',
},
}}
>
<Box
sx={{
fontSize: 'xl',
}}
>
{item.icon}
</Box>
<Box
sx={{
fontSize: '12px',
}}
>
{item.label}
</Box>
</Flex>
))}
</VStack>
</Box>
);
};

export { Menu };
31 changes: 31 additions & 0 deletions frontend/src/bundles/video-editor/components/mock/menu-mock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
Flex,
FontAwesomeIcon,
Icon,
} from '~/bundles/common/components/components.js';
import { IconName } from '~/bundles/common/icons/icons.js';

const TemplatesContent: React.FC = () => (
<div>This is the Templates content.</div>
);
const AvatarsContent: React.FC = () => <div>This is the Avatars content.</div>;
const ScriptHeader: React.FC = () => (
<Flex justifyContent={'space-between'} w={'280px'}>
<div>Script</div>
<div>
<Icon as={FontAwesomeIcon} icon={IconName.UPLOAD} />
</div>
</Flex>
);
const ScriptContent: React.FC = () => <div>This is the Script content.</div>;
const TextContent: React.FC = () => <div>This is the Text content.</div>;
const AssetsContent: React.FC = () => <div>This is the Assets content.</div>;

export {
AssetsContent,
AvatarsContent,
ScriptContent,
ScriptHeader,
TemplatesContent,
TextContent,
};
89 changes: 89 additions & 0 deletions frontend/src/bundles/video-editor/pages/video-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
FontAwesomeIcon,
Icon,
} from '~/bundles/common/components/components.js';
import { useCallback, useState } from '~/bundles/common/hooks/hooks.js';
import { IconName } from '~/bundles/common/icons/icons.js';

import { Menu, MenuBody } from '../components/components.js';
import {
AssetsContent,
AvatarsContent,
ScriptContent,
ScriptHeader,
TemplatesContent,
TextContent,
} from '../components/mock/menu-mock.js';
import { type MenuItem } from '../types/menu-item.type.js';

const VideoEditor: React.FC = () => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const [activeContent, setActiveContent] = useState<React.ReactNode | null>(
null,
);
const [activeTitle, setActiveTitle] = useState<string | React.ReactNode>(
'',
);
const [isOpen, setIsOpen] = useState(false);

const handleMenuClick = (
header: string | React.ReactNode,
content: React.ReactNode,
): void => {
setActiveContent(content);
setActiveTitle(header);
setIsOpen(true);
};

const resetActiveItem = useCallback((): void => {
setIsOpen(false);
setActiveIndex(null);
}, []);

const menuItems: MenuItem[] = [
{
label: 'Templates',
icon: <Icon as={FontAwesomeIcon} icon={IconName.TEMPLATE} />,
onClick: () => handleMenuClick('Templates', <TemplatesContent />),
},
{
label: 'Avatars',
icon: <Icon as={FontAwesomeIcon} icon={IconName.AVATAR} />,
onClick: () => handleMenuClick('Avatars', <AvatarsContent />),
},
{
label: 'Script',
icon: <Icon as={FontAwesomeIcon} icon={IconName.SCRIPT} />,
onClick: () => handleMenuClick(<ScriptHeader />, <ScriptContent />),
},
{
label: 'Text',
icon: <Icon as={FontAwesomeIcon} icon={IconName.TEXT} />,
onClick: () => handleMenuClick('Text', <TextContent />),
},
{
label: 'Assets',
icon: <Icon as={FontAwesomeIcon} icon={IconName.UPLOAD} />,
onClick: () => handleMenuClick('Assets', <AssetsContent />),
},
];

return (
<>
<Menu
items={menuItems}
activeIndex={activeIndex}
onActiveIndexSet={setActiveIndex}
/>
<MenuBody
title={activeTitle}
isOpen={isOpen}
onClose={resetActiveItem}
>
{activeContent}
</MenuBody>
</>
);
};

export { VideoEditor };
7 changes: 7 additions & 0 deletions frontend/src/bundles/video-editor/types/menu-item.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type MenuItem = {
label: string;
icon: React.ReactNode;
onClick: () => void;
};

export { type MenuItem };
1 change: 1 addition & 0 deletions frontend/src/bundles/video-editor/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { type MenuItem } from './menu-item.type.js';

0 comments on commit 5ac5142

Please sign in to comment.