diff --git a/.changeset/lazy-coins-camp.md b/.changeset/lazy-coins-camp.md new file mode 100644 index 00000000..7847afc9 --- /dev/null +++ b/.changeset/lazy-coins-camp.md @@ -0,0 +1,5 @@ +--- +'@smile/haring-react': minor +--- + +Creating a floating menu diff --git a/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.mock.tsx b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.mock.tsx new file mode 100644 index 00000000..310eabb8 --- /dev/null +++ b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.mock.tsx @@ -0,0 +1,34 @@ +export const floatingMenuLabelMock = { + children: [ + { + hasModal: true, + href: '', + id: 0, + text: 'Document', + }, + { + hasModal: false, + href: 'https://mantine.dev#1', + id: 1, + text: 'Favoris ', + }, + { + hasModal: true, + href: '', + id: 2, + text: 'Archives', + }, + { + hasModal: false, + href: 'https://mantine.dev#2', + id: 3, + text: 'Contrats', + }, + { + hasModal: true, + href: '', + id: 4, + text: 'Aide', + }, + ], +}; diff --git a/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.module.css b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.module.css new file mode 100644 index 00000000..79a34e0f --- /dev/null +++ b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.module.css @@ -0,0 +1,175 @@ +.isOpened { + position: relative; + justify-content: center; + align-items: center; + color: white; + background-color: var(--mantine-color-cyan-9); + + .floatingMenuLabel { + display: block; + color: #fff; + } + + .floatingMenuButton { + color: #fff; + } + /* noinspection CssInvalidAtRule */ + @mixin larger-than $mantine-breakpoint-xs { + align-items: flex-start; + border-radius: 28px 0px 0px 28px; + + svg { + flex: 1 0 25px; + } + } +} +.floatingMenuContainer { + position: fixed; + bottom: 0; + justify-content: space-around; + align-items: self-start; + flex-direction: row; + background-color: white; + width: 100%; + z-index: 190; + + /*noinspection CssInvalidAtRule*/ + @mixin larger-than $mantine-breakpoint-xs { + justify-content: space-evenly; + flex-direction: column; + top: 50%; + width: 56px; + height: max-content; + box-shadow: -6px 6px 7px 3px #00000008; + transform: translateY(-50%); + gap: 5px; + } +} +@mixin larger-than $mantine-breakpoint-xs { + .left { + left: 0; + border-radius: 0px 8px 8px 0px; + .floatingMenuItem { + left: calc(-138px + 56px); + transition: left 0.3s ease; + } + + .floatingMenuItem { + justify-content: flex-end; + align-items: center; + + &:hover, + &.isOpened { + justify-content: flex-start; + border-radius: 0px 28px 28px 0px; + left: 0; + + .floatingMenuLabel { + display: block; + color: #fff; + } + + .floatingMenuButton { + color: #fff; + } + } + } + + .floatingMenuButton { + span { + justify-content: flex-start; + flex-direction: row-reverse; + } + } + } + + .right { + right: 0; + border-radius: 8px 0px 0px 8px; + + .floatingMenuItem { + right: 0; + transition: right 0.3s ease; + + &:hover, + &.isOpened { + flex-direction: column; + border-radius: 28px 0px 0px 28px; + right: calc(138px - 56px); + + .floatingMenuLabel { + display: block; + color: #fff; + } + + .floatingMenuButton { + color: #fff; + } + } + } + } +} +.floatingMenuItem { + position: relative; + padding: 10px 5px; + justify-content: space-between; + height: 100%; + + span { + flex-direction: column; + max-width: 84px; + justify-content: flex-start; + align-items: center; + text-align: center; + gap: 5px; + } + + &:hover { + position: relative; + justify-content: center; + align-items: center; + color: white; + background-color: var(--mantine-color-cyan-9); + + /*noinspection CssInvalidAtRule*/ + @mixin larger-than $mantine-breakpoint-xs { + align-items: flex-start; + border-radius: 28px 0px 0px 28px; + + svg { + flex: 1 0 25px; + } + } + } + /*noinspection CssInvalidAtRule*/ + @mixin larger-than $mantine-breakpoint-xs { + width: 138px; + padding: 0 15px; + height: 56px; + + span { + flex-direction: row; + max-width: 100%; + gap: 0; + } + } +} + +.floatingMenuButton { + width: 100%; + height: 100%; + border-radius: 0; + color: var(--mantine-color-dark-6); + + @mixin larger-than $mantine-breakpoint-xs { + height: 59px; + align-items: flex-start; + border-radius: 28px 0px 0px 28px; + } +} + +.floatingMenuLabel { + @mixin larger-than $mantine-breakpoint-xs { + display: none; + } +} diff --git a/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.stories.tsx b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.stories.tsx new file mode 100644 index 00000000..c82d1357 --- /dev/null +++ b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.stories.tsx @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { FloatingMenu as Cmp } from './FloatingMenu'; +import { floatingMenuLabelMock } from './FloatingMenu.mock'; + +const meta = { + component: Cmp, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + tags: ['autodocs'], + title: '3-custom/Components/FloatingMenu', +} satisfies Meta; + +export default meta; +type IStory = StoryObj; + +export const FloatingMenu: IStory = { + args: { + items: floatingMenuLabelMock.children, + position: 'right', + }, +}; diff --git a/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.tsx b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.tsx new file mode 100644 index 00000000..40445a26 --- /dev/null +++ b/packages/haring-react/src/Components/FloatingMenu/FloatingMenu.tsx @@ -0,0 +1,103 @@ +'use client'; +import type { ReactElement } from 'react'; + +import { ActionIcon, Flex, Group, Modal, Text } from '@mantine/core'; +import { + CalendarBlank, + FileText, + Newspaper, + Question, + Star, +} from '@phosphor-icons/react'; +import { useState } from 'react'; + +import classes from './FloatingMenu.module.css'; + +type IMenuPosition = 'left' | 'right'; + +export interface IMenuItems { + hasModal: boolean; + href: string; + id: number; + text: string; +} + +export interface IFloatingMenuProps { + items?: IMenuItems[]; + position?: IMenuPosition; +} + +const icons = [FileText, Star, Newspaper, CalendarBlank, Question]; + +export function FloatingMenu(props: IFloatingMenuProps): ReactElement { + const { position = 'right', items } = props; + + const [openedItem, setOpenedItem] = useState(null); + + const handleOpen = (index: number) => () => { + setOpenedItem(openedItem === index ? null : index); + }; + + const handleClose: () => void = () => { + setOpenedItem(null); + }; + + const positionClass = position === 'right' ? classes.right : classes.left; + return ( + + {items?.map((item, index) => { + const Icon = icons[index]; + + return ( + + + + + {item.text} + + + + {item.hasModal ? ( + + {item.text} + + ) : ( + '' + )} + + ); + })} + + ); +} diff --git a/packages/haring-react/src/index.tsx b/packages/haring-react/src/index.tsx index 72ea1b7f..7fc10ed0 100644 --- a/packages/haring-react/src/index.tsx +++ b/packages/haring-react/src/index.tsx @@ -28,6 +28,8 @@ export type { IButtonListProps } from './Components/ButtonList/ButtonList'; export { ButtonList } from './Components/ButtonList/ButtonList'; export { ActionBar } from './Components/ActionBar/ActionBar'; export type { IActionBarProps } from './Components/ActionBar/ActionBar'; +export { FloatingMenu } from './Components/FloatingMenu/FloatingMenu'; +export type { IFloatingMenuProps } from './Components/FloatingMenu/FloatingMenu'; export { ActionList } from './Components/ActionList/ActionList'; export type { IActionListProps,