diff --git a/front/src/AppNavbar.tsx b/front/src/AppNavbar.tsx index eae338d8c..218d941da 100644 --- a/front/src/AppNavbar.tsx +++ b/front/src/AppNavbar.tsx @@ -10,24 +10,23 @@ import { IconTargetArrow, IconUser, } from '@/ui/icons/index'; -import NavItemsContainer from '@/ui/layout/navbar/NavItemsContainer'; +import { useIsSubNavbarDisplayed } from '@/ui/layout/hooks/useIsSubNavbarDisplayed'; +import MainNavbar from '@/ui/layout/navbar/MainNavbar'; import NavItem from './modules/ui/layout/navbar/NavItem'; import NavTitle from './modules/ui/layout/navbar/NavTitle'; -import NavWorkspaceButton from './modules/ui/layout/navbar/NavWorkspaceButton'; export function AppNavbar() { const theme = useTheme(); const currentPath = useLocation().pathname; - const shouldDiplaySubNavBar = currentPath.match(/\/settings\//g) !== null; + const isSubNavbarDisplayed = useIsSubNavbarDisplayed(); return ( <> - {!shouldDiplaySubNavBar ? ( - <> - - + {!isSubNavbarDisplayed ? ( + + <> } active={currentPath === '/opportunities'} /> - - + + ) : ( )} diff --git a/front/src/modules/comments/components/timeline/Timeline.tsx b/front/src/modules/comments/components/timeline/Timeline.tsx index 67bf9276a..146529dd4 100644 --- a/front/src/modules/comments/components/timeline/Timeline.tsx +++ b/front/src/modules/comments/components/timeline/Timeline.tsx @@ -6,7 +6,7 @@ import { useOpenCommentThreadRightDrawer } from '@/comments/hooks/useOpenComment import { useOpenCreateCommentThreadDrawer } from '@/comments/hooks/useOpenCreateCommentThreadDrawer'; import { CommentableEntity } from '@/comments/types/CommentableEntity'; import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer'; -import { IconCirclePlus, IconNotes } from '@/ui/icons/index'; +import { IconNotes } from '@/ui/icons/index'; import { beautifyExactDate, beautifyPastDateRelativeToNow, @@ -221,10 +221,6 @@ export function Timeline({ entity }: { entity: CommentableEntity }) { - - - - openCreateCommandThread(entity)} /> diff --git a/front/src/modules/settings/components/SettingsNavbar.tsx b/front/src/modules/settings/components/SettingsNavbar.tsx index bcf87bb99..0fb34152a 100644 --- a/front/src/modules/settings/components/SettingsNavbar.tsx +++ b/front/src/modules/settings/components/SettingsNavbar.tsx @@ -11,9 +11,8 @@ import { IconUsers, } from '@/ui/icons/index'; import NavItem from '@/ui/layout/navbar/NavItem'; -import NavItemsContainer from '@/ui/layout/navbar/NavItemsContainer'; import NavTitle from '@/ui/layout/navbar/NavTitle'; -import SubNavbarContainer from '@/ui/layout/navbar/sub-navbar/SubNavBarContainer'; +import SubNavbar from '@/ui/layout/navbar/sub-navbar/SubNavbar'; export function SettingsNavbar() { const theme = useTheme(); @@ -25,8 +24,8 @@ export function SettingsNavbar() { }, [logout]); return ( - - + + <> } danger={true} /> - - + + ); } diff --git a/front/src/modules/ui/components/buttons/IconButton.tsx b/front/src/modules/ui/components/buttons/IconButton.tsx index 70b79c90d..334187c28 100644 --- a/front/src/modules/ui/components/buttons/IconButton.tsx +++ b/front/src/modules/ui/components/buttons/IconButton.tsx @@ -1,33 +1,118 @@ +import React from 'react'; import styled from '@emotion/styled'; -const StyledIconButton = styled.button` +export type IconButtonVariant = 'transparent' | 'border' | 'shadow' | 'white'; + +export type IconButtonSize = 'large' | 'medium' | 'small'; + +export type ButtonProps = { + icon?: React.ReactNode; + variant?: IconButtonVariant; + size?: IconButtonSize; +} & React.ComponentProps<'button'>; + +const StyledIconButton = styled.button>` align-items: center; - background: ${({ theme }) => theme.color.blue}; - border: none; + background: ${({ theme, variant, disabled }) => { + switch (variant) { + case 'shadow': + case 'white': + return theme.background.transparent.lighter; + case 'transparent': + case 'border': + default: + return 'transparent'; + } + }}; + border-color: ${({ theme, variant }) => { + switch (variant) { + case 'border': + return theme.border.color.medium; + case 'shadow': + case 'white': + case 'transparent': + default: + return 'none'; + } + }}; + transition: background 0.1s ease; + border-radius: ${({ theme }) => { + return theme.border.radius.sm; + }}; + border-width: ${({ variant }) => { + switch (variant) { + case 'border': + return '1px'; + case 'shadow': + case 'white': + case 'transparent': + default: + return 0; + } + }}; + color: ${({ theme, disabled }) => { + if (disabled) { + return theme.font.color.extraLight; + } - border-radius: 50%; - color: ${({ theme }) => theme.font.color.inverted}; - - cursor: pointer; + return theme.font.color.tertiary; + }}; + border-style: solid; + cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; + height: ${({ size }) => { + switch (size) { + case 'large': + return '32px'; + case 'medium': + return '24px'; + case 'small': + default: + return '20px'; + } + }}; display: flex; - height: 20px; - justify-content: center; - padding: 0; - transition: color 0.1s ease-in-out, background 0.1s ease-in-out; - width: 20px; - - &:disabled { - background: ${({ theme }) => theme.background.quaternary}; - color: ${({ theme }) => theme.font.color.tertiary}; - cursor: default; + width: ${({ size }) => { + switch (size) { + case 'large': + return '32px'; + case 'medium': + return '24px'; + case 'small': + default: + return '20px'; + } + }}; + flex-shrink: 0; + &:hover { + background: ${({ theme, disabled }) => { + return disabled ? 'auto' : theme.background.transparent.light; + }}; } + user-select: none; + &:active { + background: ${({ theme, disabled }) => { + return disabled ? 'auto' : theme.background.transparent.medium; + }}; `; export function IconButton({ icon, + title, + variant = 'transparent', + size = 'medium', + disabled = false, ...props -}: { icon: React.ReactNode } & React.ButtonHTMLAttributes) { - return {icon}; +}: ButtonProps) { + return ( + + {icon} + + ); } diff --git a/front/src/modules/ui/components/buttons/RoundedIconButton.tsx b/front/src/modules/ui/components/buttons/RoundedIconButton.tsx new file mode 100644 index 000000000..ae49fffd7 --- /dev/null +++ b/front/src/modules/ui/components/buttons/RoundedIconButton.tsx @@ -0,0 +1,33 @@ +import styled from '@emotion/styled'; + +const StyledIconButton = styled.button` + align-items: center; + background: ${({ theme }) => theme.color.blue}; + border: none; + + border-radius: 50%; + color: ${({ theme }) => theme.font.color.inverted}; + + cursor: pointer; + display: flex; + height: 20px; + + justify-content: center; + + padding: 0; + transition: color 0.1s ease-in-out, background 0.1s ease-in-out; + width: 20px; + + &:disabled { + background: ${({ theme }) => theme.background.quaternary}; + color: ${({ theme }) => theme.font.color.tertiary}; + cursor: default; + } +`; + +export function RoundedIconButton({ + icon, + ...props +}: { icon: React.ReactNode } & React.ButtonHTMLAttributes) { + return {icon}; +} diff --git a/front/src/modules/ui/components/buttons/__stories__/IconButton.stories.tsx b/front/src/modules/ui/components/buttons/__stories__/IconButton.stories.tsx index b875f901e..4a96c6f1e 100644 --- a/front/src/modules/ui/components/buttons/__stories__/IconButton.stories.tsx +++ b/front/src/modules/ui/components/buttons/__stories__/IconButton.stories.tsx @@ -1,33 +1,153 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { withKnobs } from '@storybook/addon-knobs'; import { expect, jest } from '@storybook/jest'; import type { Meta, StoryObj } from '@storybook/react'; import { userEvent, within } from '@storybook/testing-library'; -import { IconArrowRight } from '@/ui/icons'; +import { IconUser } from '@/ui/icons'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; import { IconButton } from '../IconButton'; +type IconButtonProps = React.ComponentProps; + +const StyledContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + width: 800px; + > * + * { + margin-top: ${({ theme }) => theme.spacing(4)}; + } +`; + +const StyledTitle = styled.h1` + font-size: ${({ theme }) => theme.font.size.lg}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(2)}; + margin-top: ${({ theme }) => theme.spacing(3)}; +`; + +const StyledDescription = styled.span` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; + text-align: center; + text-transform: uppercase; +`; + +const StyledLine = styled.div` + display: flex; + flex: 1; + flex-direction: row; +`; + +const StyledIconButtonContainer = styled.div` + align-items: center; + display: flex; + flex-direction: column; + padding: ${({ theme }) => theme.spacing(2)}; + width: 50px; +`; + const meta: Meta = { title: 'UI/Buttons/IconButton', component: IconButton, + decorators: [withKnobs], }; export default meta; type Story = StoryObj; +const variants: IconButtonProps['variant'][] = [ + 'transparent', + 'border', + 'shadow', + 'white', +]; + const clickJestFn = jest.fn(); -export const Default: Story = { +const states = { + default: { + description: 'Default', + extraProps: (variant: string) => ({ + 'data-testid': `${variant}-button-default`, + onClick: clickJestFn, + }), + }, + hover: { + description: 'Hover', + extraProps: (variant: string) => ({ + id: `${variant}-button-hover`, + 'data-testid': `${variant}-button-hover`, + }), + }, + pressed: { + description: 'Pressed', + extraProps: (variant: string) => ({ + id: `${variant}-button-pressed`, + 'data-testid': `${variant}-button-pressed`, + }), + }, + disabled: { + description: 'Disabled', + extraProps: (variant: string) => ({ + 'data-testid': `${variant}-button-disabled`, + disabled: true, + }), + }, +}; + +function IconButtonRow({ variant, size, ...props }: IconButtonProps) { + const iconSize = size === 'small' ? 14 : 16; + return ( + <> + {Object.entries(states).map(([state, { description, extraProps }]) => ( + + {description} + } + /> + + ))} + + ); +} + +const generateStory = ( + size: IconButtonProps['size'], + LineComponent: React.ComponentType, +): Story => ({ render: getRenderWrapperForComponent( - } />, + + {variants.map((variant) => ( +
+ {variant} + + + +
+ ))} +
, ), play: async ({ canvasElement }) => { const canvas = within(canvasElement); - expect(clickJestFn).toHaveBeenCalledTimes(0); - const button = canvas.getByRole('button'); - await userEvent.click(button); + const button = canvas.getByTestId(`transparent-button-default`); - expect(clickJestFn).toHaveBeenCalledTimes(1); + const numberOfClicks = clickJestFn.mock.calls.length; + await userEvent.click(button); + expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1); }, -}; +}); + +export const LargeSize = generateStory('large', IconButtonRow); +export const MediumSize = generateStory('medium', IconButtonRow); +export const SmallSize = generateStory('small', IconButtonRow); diff --git a/front/src/modules/ui/components/buttons/__stories__/RoundedIconButton.stories.tsx b/front/src/modules/ui/components/buttons/__stories__/RoundedIconButton.stories.tsx new file mode 100644 index 000000000..647282852 --- /dev/null +++ b/front/src/modules/ui/components/buttons/__stories__/RoundedIconButton.stories.tsx @@ -0,0 +1,36 @@ +import { expect, jest } from '@storybook/jest'; +import type { Meta, StoryObj } from '@storybook/react'; +import { userEvent, within } from '@storybook/testing-library'; + +import { IconArrowRight } from '@/ui/icons'; +import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; + +import { RoundedIconButton } from '../RoundedIconButton'; + +const meta: Meta = { + title: 'UI/Buttons/RoundedIconButton', + component: RoundedIconButton, +}; + +export default meta; +type Story = StoryObj; + +const clickJestFn = jest.fn(); + +export const Default: Story = { + render: getRenderWrapperForComponent( + } + />, + ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + expect(clickJestFn).toHaveBeenCalledTimes(0); + const button = canvas.getByRole('button'); + await userEvent.click(button); + + expect(clickJestFn).toHaveBeenCalledTimes(1); + }, +}; diff --git a/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx b/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx index 7ce375878..28c5d48d0 100644 --- a/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx +++ b/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx @@ -4,7 +4,7 @@ import { HotkeysEvent } from 'react-hotkeys-hook/dist/types'; import TextareaAutosize from 'react-textarea-autosize'; import styled from '@emotion/styled'; -import { IconButton } from '@/ui/components/buttons/IconButton'; +import { RoundedIconButton } from '@/ui/components/buttons/RoundedIconButton'; import { IconArrowRight } from '@/ui/icons/index'; const MAX_ROWS = 5; @@ -47,7 +47,7 @@ const StyledTextArea = styled(TextareaAutosize)` `; // TODO: this messes with the layout, fix it -const StyledBottomRightIconButton = styled.div` +const StyledBottomRightRoundedIconButton = styled.div` height: 0; position: relative; right: 26px; @@ -129,13 +129,13 @@ export function AutosizeTextInput({ onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} /> - - + } disabled={isSendButtonDisabled} /> - + ); diff --git a/front/src/modules/ui/components/table/EntityTableBody.tsx b/front/src/modules/ui/components/table/EntityTableBody.tsx index 2f749007b..b4e88a135 100644 --- a/front/src/modules/ui/components/table/EntityTableBody.tsx +++ b/front/src/modules/ui/components/table/EntityTableBody.tsx @@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil'; import { TableColumn } from '@/people/table/components/peopleColumns'; import { RecoilScope } from '@/recoil-scope/components/RecoilScope'; +import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState'; import { isFetchingEntityTableDataState } from '@/ui/tables/states/isFetchingEntityTableDataState'; import { RowContext } from '@/ui/tables/states/RowContext'; import { tableRowIdsState } from '@/ui/tables/states/tableRowIdsState'; @@ -11,13 +12,15 @@ import { EntityTableRow } from './EntityTableRow'; export function EntityTableBody({ columns }: { columns: Array }) { const rowIds = useRecoilValue(tableRowIdsState); + const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState); + const isFetchingEntityTableData = useRecoilValue( isFetchingEntityTableDataState, ); return ( - {!isFetchingEntityTableData + {!isFetchingEntityTableData && !isNavbarSwitchingSize ? rowIds.map((rowId, index) => ( diff --git a/front/src/modules/ui/layout/DefaultLayout.tsx b/front/src/modules/ui/layout/DefaultLayout.tsx index db490dca8..57d58ba64 100644 --- a/front/src/modules/ui/layout/DefaultLayout.tsx +++ b/front/src/modules/ui/layout/DefaultLayout.tsx @@ -7,7 +7,7 @@ import { AppNavbar } from '~/AppNavbar'; import { MOBILE_VIEWPORT } from '../themes/themes'; -import { NavbarContainer } from './navbar/NavbarContainer'; +import { NavbarAnimatedContainer } from './navbar/NavbarAnimatedContainer'; import { isNavbarOpenedState } from './states/isNavbarOpenedState'; const StyledLayout = styled.div` @@ -47,9 +47,9 @@ export function DefaultLayout({ children }: OwnProps) { {userIsAuthenticated ? ( <> - + - + {children} ) : ( diff --git a/front/src/modules/ui/layout/hooks/useIsSubNavbarDisplayed.ts b/front/src/modules/ui/layout/hooks/useIsSubNavbarDisplayed.ts new file mode 100644 index 000000000..fbc9bff39 --- /dev/null +++ b/front/src/modules/ui/layout/hooks/useIsSubNavbarDisplayed.ts @@ -0,0 +1,6 @@ +import { useLocation } from 'react-router-dom'; + +export function useIsSubNavbarDisplayed() { + const currentPath = useLocation().pathname; + return currentPath.match(/\/settings\//g) !== null; +} diff --git a/front/src/modules/ui/layout/navbar/MainNavbar.tsx b/front/src/modules/ui/layout/navbar/MainNavbar.tsx new file mode 100644 index 000000000..3eef39791 --- /dev/null +++ b/front/src/modules/ui/layout/navbar/MainNavbar.tsx @@ -0,0 +1,21 @@ +import styled from '@emotion/styled'; + +import NavItemsContainer from './NavItemsContainer'; +import NavWorkspaceButton from './NavWorkspaceButton'; + +type OwnProps = { + children: JSX.Element; +}; + +const StyledContainer = styled.div` + width: 220px; +`; + +export default function MainNavbar({ children }: OwnProps) { + return ( + + + {children} + + ); +} diff --git a/front/src/modules/ui/layout/navbar/NavItemsContainer.tsx b/front/src/modules/ui/layout/navbar/NavItemsContainer.tsx index 56e1356ac..452ec3b28 100644 --- a/front/src/modules/ui/layout/navbar/NavItemsContainer.tsx +++ b/front/src/modules/ui/layout/navbar/NavItemsContainer.tsx @@ -8,7 +8,6 @@ const StyledNavItemsContainer = styled.div` display: flex; flex-direction: column; margin-top: 40px; - min-width: 220px; `; function NavItemsContainer({ children }: OwnProps) { diff --git a/front/src/modules/ui/layout/navbar/NavbarAnimatedContainer.tsx b/front/src/modules/ui/layout/navbar/NavbarAnimatedContainer.tsx new file mode 100644 index 000000000..4ac742e53 --- /dev/null +++ b/front/src/modules/ui/layout/navbar/NavbarAnimatedContainer.tsx @@ -0,0 +1,57 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { motion } from 'framer-motion'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +import { MOBILE_VIEWPORT } from '@/ui/themes/themes'; + +import { useIsSubNavbarDisplayed } from '../hooks/useIsSubNavbarDisplayed'; +import { isNavbarOpenedState } from '../states/isNavbarOpenedState'; +import { isNavbarSwitchingSizeState } from '../states/isNavbarSwitchingSizeState'; + +const StyledNavbarContainer = styled(motion.div)` + align-items: end; + display: flex; + flex-direction: column; + flex-shrink: 0; + overflow: hidden; + padding: ${({ theme }) => theme.spacing(2)}; + + @media (max-width: ${MOBILE_VIEWPORT}px) { + width: ${(props) => + useRecoilValue(isNavbarOpenedState) + ? `calc(100% - ` + props.theme.spacing(4) + `)` + : '0'}; + } +`; + +type NavbarProps = { + children: React.ReactNode; + layout?: string; +}; + +export function NavbarAnimatedContainer({ children, layout }: NavbarProps) { + const isMenuOpened = useRecoilValue(isNavbarOpenedState); + const [, setIsNavbarSwitchingSize] = useRecoilState( + isNavbarSwitchingSizeState, + ); + const isSubNavbarDisplayed = useIsSubNavbarDisplayed(); + const theme = useTheme(); + + return ( + { + setIsNavbarSwitchingSize(false); + }} + animate={{ + width: isMenuOpened ? (isSubNavbarDisplayed ? '520px' : '220px') : '0', + opacity: isMenuOpened ? 1 : 0, + }} + transition={{ + duration: theme.animation.duration.visible, + }} + > + {children} + + ); +} diff --git a/front/src/modules/ui/layout/navbar/NavbarContainer.tsx b/front/src/modules/ui/layout/navbar/NavbarContainer.tsx deleted file mode 100644 index 81b1d20c1..000000000 --- a/front/src/modules/ui/layout/navbar/NavbarContainer.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; - -import { MOBILE_VIEWPORT } from '@/ui/themes/themes'; - -import { isNavbarOpenedState } from '../states/isNavbarOpenedState'; - -const StyledNavbarContainer = styled.div` - flex-direction: column; - flex-shrink: 0; - overflow: hidden; - padding: ${({ theme }) => theme.spacing(2)}; - width: ${(props) => (useRecoilValue(isNavbarOpenedState) ? 'auto' : '0')}; - - @media (max-width: ${MOBILE_VIEWPORT}px) { - width: ${(props) => - useRecoilValue(isNavbarOpenedState) - ? `calc(100% - ` + props.theme.spacing(4) + `)` - : '0'}; - } -`; - -const NavbarContent = styled.div` - display: ${() => (useRecoilValue(isNavbarOpenedState) ? 'block' : 'none')}; -`; - -type NavbarProps = { - children: React.ReactNode; - layout?: string; -}; - -export function NavbarContainer({ children, layout }: NavbarProps) { - return ( - - {children} - - ); -} diff --git a/front/src/modules/ui/layout/navbar/sub-navbar/NavBackButton.tsx b/front/src/modules/ui/layout/navbar/sub-navbar/NavBackButton.tsx index da97abcbb..177baa16c 100644 --- a/front/src/modules/ui/layout/navbar/sub-navbar/NavBackButton.tsx +++ b/front/src/modules/ui/layout/navbar/sub-navbar/NavBackButton.tsx @@ -1,8 +1,10 @@ import { useNavigate } from 'react-router-dom'; import styled from '@emotion/styled'; +import { useRecoilState } from 'recoil'; import { IconChevronLeft } from '@/ui/icons/index'; +import { isNavbarSwitchingSizeState } from '../../states/isNavbarSwitchingSizeState'; import NavCollapseButton from '../NavCollapseButton'; type OwnProps = { @@ -32,12 +34,18 @@ const StyledContainer = styled.div` export default function NavBackButton({ title }: OwnProps) { const navigate = useNavigate(); + const [, setIsNavbarSwitchingSize] = useRecoilState( + isNavbarSwitchingSizeState, + ); return ( <> navigate('/', { replace: true })} + onClick={() => { + setIsNavbarSwitchingSize(true); + navigate('/', { replace: true }); + }} > {title} diff --git a/front/src/modules/ui/layout/navbar/sub-navbar/SubNavBarContainer.tsx b/front/src/modules/ui/layout/navbar/sub-navbar/SubNavbar.tsx similarity index 59% rename from front/src/modules/ui/layout/navbar/sub-navbar/SubNavBarContainer.tsx rename to front/src/modules/ui/layout/navbar/sub-navbar/SubNavbar.tsx index 9f7e19e94..9a7585170 100644 --- a/front/src/modules/ui/layout/navbar/sub-navbar/SubNavBarContainer.tsx +++ b/front/src/modules/ui/layout/navbar/sub-navbar/SubNavbar.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled'; +import NavItemsContainer from '../NavItemsContainer'; + import NavBackButton from './NavBackButton'; type OwnProps = { @@ -10,23 +12,15 @@ type OwnProps = { const StyledContainer = styled.div` display: flex; flex-direction: column; - padding-left: 300px; padding-top: ${({ theme }) => theme.spacing(6)}; + width: 220px; `; -const StyledNavItemsContainer = styled.div` - display: flex; - flex-direction: column; -`; - -export default function SubNavbarContainer({ - children, - backButtonTitle, -}: OwnProps) { +export default function SubNavbar({ children, backButtonTitle }: OwnProps) { return ( - {children} + {children} ); } diff --git a/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx b/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx index 77443ad5b..c43d8ddec 100644 --- a/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx +++ b/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx @@ -1,5 +1,7 @@ import { useRef } from 'react'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { motion } from 'framer-motion'; import { useRecoilState } from 'recoil'; import { @@ -13,16 +15,15 @@ import { rightDrawerPageState } from '../states/rightDrawerPageState'; import { RightDrawerRouter } from './RightDrawerRouter'; -const StyledContainer = styled.div` +const StyledContainer = styled(motion.div)` background: ${({ theme }) => theme.background.primary}; box-shadow: ${({ theme }) => theme.boxShadow.strong}; height: 100%; overflow-x: hidden; position: fixed; + right: 0; top: 0; - transition: width 0.5s; - width: ${({ theme }) => theme.rightDrawerWidth}; z-index: 2; `; @@ -45,17 +46,23 @@ export function RightDrawer() { callback: () => setIsRightDrawerOpen(false), mode: OutsideClickAlerterMode.absolute, }); - if (!isRightDrawerOpen || !isDefined(rightDrawerPage)) { + const theme = useTheme(); + if (!isDefined(rightDrawerPage)) { return <>; } return ( - <> - - - - - - + + + + + ); } diff --git a/front/src/modules/ui/layout/states/isNavbarSwitchingSizeState.ts b/front/src/modules/ui/layout/states/isNavbarSwitchingSizeState.ts new file mode 100644 index 000000000..c9a524484 --- /dev/null +++ b/front/src/modules/ui/layout/states/isNavbarSwitchingSizeState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const isNavbarSwitchingSizeState = atom({ + key: 'ui/isNavbarSwitchingSizeState', + default: true, +}); diff --git a/front/src/modules/ui/layout/top-bar/TopBar.tsx b/front/src/modules/ui/layout/top-bar/TopBar.tsx index 3b3fd85db..deb625c45 100644 --- a/front/src/modules/ui/layout/top-bar/TopBar.tsx +++ b/front/src/modules/ui/layout/top-bar/TopBar.tsx @@ -1,6 +1,7 @@ import { ReactNode } from 'react'; import styled from '@emotion/styled'; +import { IconButton } from '@/ui/components/buttons/IconButton'; import { IconPlus } from '@/ui/icons/index'; import NavCollapseButton from '../navbar/NavCollapseButton'; @@ -16,6 +17,7 @@ const TopBarContainer = styled.div` font-size: 14px; min-height: ${TOP_BAR_MIN_HEIGHT}px; padding: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(3)}; `; const TitleContainer = styled.div` @@ -26,22 +28,6 @@ const TitleContainer = styled.div` width: 100%; `; -const AddButtonContainer = styled.div` - align-items: center; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.sm}; - color: ${({ theme }) => theme.font.color.tertiary}; - cursor: pointer; - display: flex; - flex-shrink: 0; - height: 28px; - justify-content: center; - justify-self: flex-end; - margin-right: ${({ theme }) => theme.spacing(1)}; - user-select: none; - width: 28px; -`; - type OwnProps = { title: string; icon: ReactNode; @@ -56,12 +42,13 @@ export function TopBar({ title, icon, onAddButtonClick }: OwnProps) { {icon} {title} {onAddButtonClick && ( - } + size="large" data-testid="add-button" onClick={onAddButtonClick} - > - - + variant="border" + /> )} diff --git a/front/src/modules/ui/themes/animation.ts b/front/src/modules/ui/themes/animation.ts new file mode 100644 index 000000000..422a1ad91 --- /dev/null +++ b/front/src/modules/ui/themes/animation.ts @@ -0,0 +1,6 @@ +export const animation = { + duration: { + instant: 0.1, + visible: 0.3, + }, +}; diff --git a/front/src/modules/ui/themes/themes.ts b/front/src/modules/ui/themes/themes.ts index 85bff0f12..3bfd0ad31 100644 --- a/front/src/modules/ui/themes/themes.ts +++ b/front/src/modules/ui/themes/themes.ts @@ -1,3 +1,4 @@ +import { animation } from './animation'; import { backgroundDark, backgroundLight } from './background'; import { blur } from './blur'; import { borderDark, borderLight } from './border'; @@ -13,6 +14,7 @@ const common = { icon: icon, text: text, blur: blur, + animation: animation, snackBar: { success: { background: '#16A26B',