From 6c6ef1d2233206094c03c324efc981c3a4dab969 Mon Sep 17 00:00:00 2001 From: luiqor Date: Thu, 12 Dec 2024 18:15:26 +0200 Subject: [PATCH 01/41] add basic Menu component --- src/design-system/components/menu/Menu.scss | 35 +++++++++++++++++++++ src/design-system/components/menu/Menu.tsx | 35 +++++++++++++++++++++ src/design-system/stories/Menu.stories.tsx | 20 ++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 src/design-system/components/menu/Menu.scss create mode 100644 src/design-system/components/menu/Menu.tsx create mode 100644 src/design-system/stories/Menu.stories.tsx diff --git a/src/design-system/components/menu/Menu.scss b/src/design-system/components/menu/Menu.scss new file mode 100644 index 000000000..752249d1c --- /dev/null +++ b/src/design-system/components/menu/Menu.scss @@ -0,0 +1,35 @@ +@use '~scss/utilities' as *; + +.#{$prefix}menu { + background-color: $neutral-0; + border-radius: 6px; + box-shadow: + 0 0 8px 0 rgba(0, 0, 0, 0.08), + 0 10px 12px 0 rgba(0, 0, 0, 0.1); + + &__list-item { + background-color: $neutral-0; + width: 100%; + color: $blue-gray-700; + + &:disabled { + color: $blue-gray-700; + } + + &:hover { + background-color: $blue-gray-100; + } + } + + &--density-1 &__list-item { + padding: 16px; + height: 48px; + min-width: 200px; + } + + &--density-2 &__list-item { + padding: 16px 16px 8px; + height: 64px; + min-width: 256px; + } +} diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx new file mode 100644 index 000000000..e2286be3a --- /dev/null +++ b/src/design-system/components/menu/Menu.tsx @@ -0,0 +1,35 @@ +import { FC } from 'react' +import { + MenuItem, + Menu as MuiMenu, + MenuProps as MuiMenuProps +} from '@mui/material' + +import '~scss-components/menu/Menu.scss' + +interface MenuItemProps { + title: string +} + +interface MenuProps extends MuiMenuProps { + menuItems: MenuItemProps[] + density?: 'density-1' | 'density-2' +} + +const Menu: FC = ({ + menuItems, + open, + density = 'density-1' +}: MenuProps) => { + return ( + + {menuItems.map((menuItem) => ( + + {menuItem.title} + + ))} + + ) +} + +export default Menu diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx new file mode 100644 index 000000000..3a2e08ee4 --- /dev/null +++ b/src/design-system/stories/Menu.stories.tsx @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from '@storybook/react' + +import Menu from '~scss-components/menu/Menu' + +const meta: Meta = { + title: 'Components/Menu', + component: Menu, + tags: ['autodocs'] +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + open: true, + menuItems: [{ title: 'Lesson' }, { title: 'Quiz' }, { title: 'Attachment' }] + } +} From 3207989a854ef57854a1a41f0d4378afcc3348f7 Mon Sep 17 00:00:00 2001 From: luiqor Date: Thu, 12 Dec 2024 20:47:42 +0200 Subject: [PATCH 02/41] enhance MenuItem --- src/design-system/components/menu/Menu.scss | 17 ++++++++++++++--- src/design-system/components/menu/Menu.tsx | 20 +++++++++++--------- src/design-system/stories/Menu.stories.tsx | 8 +++++++- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/design-system/components/menu/Menu.scss b/src/design-system/components/menu/Menu.scss index 752249d1c..92e99f079 100644 --- a/src/design-system/components/menu/Menu.scss +++ b/src/design-system/components/menu/Menu.scss @@ -1,7 +1,6 @@ @use '~scss/utilities' as *; .#{$prefix}menu { - background-color: $neutral-0; border-radius: 6px; box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.08), @@ -21,15 +20,27 @@ } } + &__item-graphics { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + } + + &__item-title { + margin-left: 12px; + } + &--density-1 &__list-item { - padding: 16px; + padding: 16px 8px; height: 48px; min-width: 200px; } &--density-2 &__list-item { - padding: 16px 16px 8px; height: 64px; min-width: 256px; + font-weight: 500; } } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index e2286be3a..71e6bb4d7 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, ReactNode } from 'react' import { MenuItem, Menu as MuiMenu, @@ -9,23 +9,25 @@ import '~scss-components/menu/Menu.scss' interface MenuItemProps { title: string + additionalInfo?: string + nestedMenuItems?: MenuItemProps[] + graphics?: ReactNode } interface MenuProps extends MuiMenuProps { menuItems: MenuItemProps[] - density?: 'density-1' | 'density-2' + density?: 1 | 2 } -const Menu: FC = ({ - menuItems, - open, - density = 'density-1' -}: MenuProps) => { +const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { return ( - + {menuItems.map((menuItem) => ( - {menuItem.title} + {menuItem.graphics && ( +
{menuItem.graphics}
+ )} +

{menuItem.title}

))}
diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 3a2e08ee4..6e04b4bae 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -1,6 +1,8 @@ import type { Meta, StoryObj } from '@storybook/react' import Menu from '~scss-components/menu/Menu' +import { Book } from '@mui/icons-material' +import { Checkbox } from '@mui/material' const meta: Meta = { title: 'Components/Menu', @@ -15,6 +17,10 @@ type Story = StoryObj export const Default: Story = { args: { open: true, - menuItems: [{ title: 'Lesson' }, { title: 'Quiz' }, { title: 'Attachment' }] + menuItems: [ + { title: 'Lesson', graphics: }, + { title: 'Quiz', graphics: }, + { title: 'Attachment' } + ] } } From 3cc4ccb7ca194af46369df9925c4a2bc3f684ece Mon Sep 17 00:00:00 2001 From: luiqor Date: Thu, 12 Dec 2024 21:28:58 +0200 Subject: [PATCH 03/41] enhance styles for density 2 --- src/design-system/components/menu/Menu.scss | 40 ++++++++++++++------- src/design-system/components/menu/Menu.tsx | 9 +++-- src/design-system/stories/Menu.stories.tsx | 24 +++++++++++++ 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/design-system/components/menu/Menu.scss b/src/design-system/components/menu/Menu.scss index 92e99f079..923f0111b 100644 --- a/src/design-system/components/menu/Menu.scss +++ b/src/design-system/components/menu/Menu.scss @@ -6,10 +6,11 @@ 0 0 8px 0 rgba(0, 0, 0, 0.08), 0 10px 12px 0 rgba(0, 0, 0, 0.1); - &__list-item { + &__item { background-color: $neutral-0; width: 100%; color: $blue-gray-700; + padding: 16px 8px; &:disabled { color: $blue-gray-700; @@ -20,6 +21,25 @@ } } + &--density-1 &__item { + height: 48px; + min-width: 200px; + } + + &--density-2 &__item { + height: 64px; + min-width: 256px; + padding-top: 30px; + } + + &--density-2 &__item-title { + font-weight: $font-weight-medium; + } + + &--density-2 &__item-additional-info { + line-height: $line-height-sm; + } + &__item-graphics { width: 40px; height: 40px; @@ -28,19 +48,15 @@ justify-content: center; } - &__item-title { + &__item-text-box { margin-left: 12px; + display: flex; + flex-direction: column; + padding-right: 8px; } - &--density-1 &__list-item { - padding: 16px 8px; - height: 48px; - min-width: 200px; - } - - &--density-2 &__list-item { - height: 64px; - min-width: 256px; - font-weight: 500; + &__item-additional-info { + font-size: $font-size-xs; + color: $blue-gray-500; } } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 71e6bb4d7..cacfdd394 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -23,11 +23,16 @@ const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { return ( {menuItems.map((menuItem) => ( - + {menuItem.graphics && (
{menuItem.graphics}
)} -

{menuItem.title}

+
+ + {menuItem.additionalInfo} + + {menuItem.title} +
))}
diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 6e04b4bae..cdf121815 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -24,3 +24,27 @@ export const Default: Story = { ] } } + +export const WithAdditionalInfo: Story = { + args: { + open: true, + density: 2, + menuItems: [ + { + title: 'Lesson', + additionalInfo: 'Explore comprehensive lessons on various topics', + graphics: + }, + { + title: 'Quiz', + additionalInfo: 'Test your knowledge with engaging quizzes', + graphics: + }, + { + title: 'Attachment', + additionalInfo: 'Access all your important files and documents', + graphics: + } + ] + } +} From eb17c6798d546f511ba0347acc25ee886eab8bc6 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 12:39:35 +0200 Subject: [PATCH 04/41] add nested menu items --- .../components/menu-item/MenuItem.scss | 63 +++++++++++++++++++ .../components/menu-item/MenuItem.tsx | 29 +++++++++ .../components/menu-item/menu-item.types.ts | 9 +++ src/design-system/components/menu/Menu.scss | 54 ---------------- src/design-system/components/menu/Menu.tsx | 43 ++++++------- src/design-system/stories/Menu.stories.tsx | 35 +++++++++++ 6 files changed, 156 insertions(+), 77 deletions(-) create mode 100644 src/design-system/components/menu-item/MenuItem.scss create mode 100644 src/design-system/components/menu-item/MenuItem.tsx create mode 100644 src/design-system/components/menu-item/menu-item.types.ts diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss new file mode 100644 index 000000000..aaa8324b5 --- /dev/null +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -0,0 +1,63 @@ +@use '~scss/utilities' as *; + +.#{$prefix}item { + background-color: $neutral-0; + width: 100%; + color: $blue-gray-700; + padding: 16px 8px; + + &:disabled { + color: $blue-gray-700; + } + + &:hover { + background-color: $blue-gray-100; + } + + &.selected { + background-color: $blue-gray-50; + } + + &--density-1 { + height: 48px; + min-width: 200px; + } + + &--density-2 { + height: 64px; + min-width: 256px; + padding-top: 30px; + } + + &--density-2 &-title { + font-weight: $font-weight-medium; + } + + &--density-2 &-additional-info { + line-height: $line-height-sm; + } + + &--variant-nested { + background-color: $neutral-50; + } + + &-graphics { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + } + + &-text-box { + margin-left: 12px; + display: flex; + flex-direction: column; + padding-right: 8px; + } + + &-additional-info { + font-size: $font-size-xs; + color: $blue-gray-500; + } +} diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx new file mode 100644 index 000000000..58d897e38 --- /dev/null +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react' +import { MenuItem as MuiMenuItem } from '@mui/material' + +import { MenuItemProps } from './menu-item.types' + +import '~scss-components/menu-item/MenuItem.scss' + +const MenuItem: FC = ({ + title, + additionalInfo, + density = 1, + graphics, + variant = 'default' +}) => { + return ( + + {graphics &&
{graphics}
} +
+ {additionalInfo} + {title} +
+
+ ) +} + +export default MenuItem diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts new file mode 100644 index 000000000..802b61416 --- /dev/null +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -0,0 +1,9 @@ +import { ReactNode } from 'react' + +export interface MenuItemProps { + title: string + additionalInfo?: string + density?: 1 | 2 + graphics?: ReactNode + variant?: 'default' | 'nested' +} diff --git a/src/design-system/components/menu/Menu.scss b/src/design-system/components/menu/Menu.scss index 923f0111b..627bd3494 100644 --- a/src/design-system/components/menu/Menu.scss +++ b/src/design-system/components/menu/Menu.scss @@ -5,58 +5,4 @@ box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.08), 0 10px 12px 0 rgba(0, 0, 0, 0.1); - - &__item { - background-color: $neutral-0; - width: 100%; - color: $blue-gray-700; - padding: 16px 8px; - - &:disabled { - color: $blue-gray-700; - } - - &:hover { - background-color: $blue-gray-100; - } - } - - &--density-1 &__item { - height: 48px; - min-width: 200px; - } - - &--density-2 &__item { - height: 64px; - min-width: 256px; - padding-top: 30px; - } - - &--density-2 &__item-title { - font-weight: $font-weight-medium; - } - - &--density-2 &__item-additional-info { - line-height: $line-height-sm; - } - - &__item-graphics { - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - } - - &__item-text-box { - margin-left: 12px; - display: flex; - flex-direction: column; - padding-right: 8px; - } - - &__item-additional-info { - font-size: $font-size-xs; - color: $blue-gray-500; - } } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index cacfdd394..a61744a0b 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,17 +1,13 @@ -import { FC, ReactNode } from 'react' -import { - MenuItem, - Menu as MuiMenu, - MenuProps as MuiMenuProps -} from '@mui/material' +import { FC, Fragment } from 'react' +import { Menu as MuiMenu, MenuProps as MuiMenuProps } from '@mui/material' + +import { MenuItemProps as NestedMenuItemProps } from '~scss-components/menu-item/menu-item.types' +import MenuItem from '../menu-item/MenuItem' import '~scss-components/menu/Menu.scss' -interface MenuItemProps { - title: string - additionalInfo?: string - nestedMenuItems?: MenuItemProps[] - graphics?: ReactNode +interface MenuItemProps extends NestedMenuItemProps { + nestedMenuItems?: NestedMenuItemProps[] } interface MenuProps extends MuiMenuProps { @@ -22,18 +18,19 @@ interface MenuProps extends MuiMenuProps { const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { return ( - {menuItems.map((menuItem) => ( - - {menuItem.graphics && ( -
{menuItem.graphics}
- )} -
- - {menuItem.additionalInfo} - - {menuItem.title} -
-
+ {menuItems.map(({ nestedMenuItems, ...menuItemProps }) => ( + + + {nestedMenuItems && + nestedMenuItems.map((nestedMenuItemProps) => ( + + ))} + ))}
) diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index cdf121815..25b104bc4 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -48,3 +48,38 @@ export const WithAdditionalInfo: Story = { ] } } + +export const WithNestedMenuItems: Story = { + args: { + open: true, + menuItems: [ + { + title: 'Lesson', + graphics: , + nestedMenuItems: [ + { title: 'Math', graphics: }, + { title: 'Science', graphics: }, + { title: 'History', graphics: } + ] + }, + { + title: 'Quiz', + graphics: , + nestedMenuItems: [ + { title: 'Math', graphics: }, + { title: 'Science', graphics: }, + { title: 'History', graphics: } + ] + }, + { + title: 'Attachment', + graphics: , + nestedMenuItems: [ + { title: 'Math', graphics: }, + { title: 'Science', graphics: }, + { title: 'History', graphics: } + ] + } + ] + } +} From 7dbdaadfccab1f7110286f642e9e0d321c5f0bda Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 13:07:29 +0200 Subject: [PATCH 05/41] add isDropdown item --- .../components/menu-item/MenuItem.scss | 13 ++++++++++--- .../components/menu-item/MenuItem.tsx | 17 +++++++++++++---- .../components/menu-item/menu-item.types.ts | 1 + src/design-system/components/menu/Menu.tsx | 6 +++++- src/design-system/stories/Menu.stories.tsx | 4 ++++ 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index aaa8324b5..69c469654 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -5,6 +5,8 @@ width: 100%; color: $blue-gray-700; padding: 16px 8px; + display: flex; + justify-content: space-between; &:disabled { color: $blue-gray-700; @@ -41,7 +43,7 @@ background-color: $neutral-50; } - &-graphics { + &__graphics { width: 40px; height: 40px; display: flex; @@ -49,15 +51,20 @@ justify-content: center; } - &-text-box { + &__text-box { margin-left: 12px; display: flex; flex-direction: column; padding-right: 8px; } - &-additional-info { + &__additional-info { font-size: $font-size-xs; color: $blue-gray-500; } + + &__main-info-box { + display: flex; + align-items: center; + } } diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 58d897e38..11e1e0aa6 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -1,5 +1,6 @@ import { FC } from 'react' import { MenuItem as MuiMenuItem } from '@mui/material' +import ArrowDropDown from '@mui/icons-material/ArrowDropDown' import { MenuItemProps } from './menu-item.types' @@ -9,6 +10,7 @@ const MenuItem: FC = ({ title, additionalInfo, density = 1, + isDropDown = false, graphics, variant = 'default' }) => { @@ -17,11 +19,18 @@ const MenuItem: FC = ({ className={`s2s-item s2s-item--density-${density} s2s-item--variant-${variant}`} key={title} > - {graphics &&
{graphics}
} -
- {additionalInfo} - {title} +
+ {graphics &&
{graphics}
} +
+ {additionalInfo} + {title} +
+ {isDropDown && ( +
+ +
+ )} ) } diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts index 802b61416..0b1e1d561 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -4,6 +4,7 @@ export interface MenuItemProps { title: string additionalInfo?: string density?: 1 | 2 + isDropDown?: boolean graphics?: ReactNode variant?: 'default' | 'nested' } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index a61744a0b..8f3d138d8 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -20,7 +20,11 @@ const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { {menuItems.map(({ nestedMenuItems, ...menuItemProps }) => ( - + {nestedMenuItems && nestedMenuItems.map((nestedMenuItemProps) => ( , nestedMenuItems: [ { title: 'Math', graphics: }, @@ -64,6 +66,7 @@ export const WithNestedMenuItems: Story = { }, { title: 'Quiz', + additionalInfo: 'Test your knowledge with engaging quizzes', graphics: , nestedMenuItems: [ { title: 'Math', graphics: }, @@ -73,6 +76,7 @@ export const WithNestedMenuItems: Story = { }, { title: 'Attachment', + additionalInfo: 'Files and documents', graphics: , nestedMenuItems: [ { title: 'Math', graphics: }, From f46a4b72d7ebc99ae4c541578607a9d5b0897103 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 15:22:11 +0200 Subject: [PATCH 06/41] replace isDropDown with dropDownIconVariant --- src/design-system/components/menu-item/MenuItem.tsx | 8 ++++---- src/design-system/components/menu-item/menu-item.types.ts | 2 +- src/design-system/components/menu/Menu.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 11e1e0aa6..2574e23fe 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' import { MenuItem as MuiMenuItem } from '@mui/material' -import ArrowDropDown from '@mui/icons-material/ArrowDropDown' +import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material' import { MenuItemProps } from './menu-item.types' @@ -10,7 +10,7 @@ const MenuItem: FC = ({ title, additionalInfo, density = 1, - isDropDown = false, + dropDownIconVariant, graphics, variant = 'default' }) => { @@ -26,9 +26,9 @@ const MenuItem: FC = ({ {title}
- {isDropDown && ( + {dropDownIconVariant && (
- + {dropDownIconVariant === 'down' ? : }
)} diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts index 0b1e1d561..9eebb396f 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -4,7 +4,7 @@ export interface MenuItemProps { title: string additionalInfo?: string density?: 1 | 2 - isDropDown?: boolean + dropDownIconVariant?: 'up' | 'down' graphics?: ReactNode variant?: 'default' | 'nested' } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 8f3d138d8..535b8a5c4 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -23,7 +23,7 @@ const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { {nestedMenuItems && nestedMenuItems.map((nestedMenuItemProps) => ( From c738a7a7ca7dd4c0ef7744b1a4d937b62a2e80c0 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 16:43:34 +0200 Subject: [PATCH 07/41] add state logic --- .../components/menu-item/MenuItem.scss | 4 +++ .../components/menu-item/MenuItem.tsx | 27 ++++++++++---- .../components/menu-item/menu-item.types.ts | 4 +-- src/design-system/components/menu/Menu.tsx | 36 ++++++++++++++----- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index 69c469654..40fa6161a 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -43,6 +43,10 @@ background-color: $neutral-50; } + &--toggled { + background-color: $blue-gray-50; + } + &__graphics { width: 40px; height: 40px; diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 2574e23fe..370c71cbe 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -1,23 +1,36 @@ -import { FC } from 'react' +import { FC, useState } from 'react' import { MenuItem as MuiMenuItem } from '@mui/material' import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material' -import { MenuItemProps } from './menu-item.types' +import { MenuItemProps as CommonMenuItemProps } from './menu-item.types' import '~scss-components/menu-item/MenuItem.scss' +interface MenuItemProps extends CommonMenuItemProps { + density?: 1 | 2 +} + const MenuItem: FC = ({ title, additionalInfo, density = 1, - dropDownIconVariant, + isDropdown = false, graphics, - variant = 'default' + variant = 'default', + onClick = () => {} }) => { + const [isToggled, setIsToggled] = useState(false) + + const handleClick = () => { + setIsToggled((previous) => !previous) + onClick() + } + return (
{graphics &&
{graphics}
} @@ -26,9 +39,9 @@ const MenuItem: FC = ({ {title}
- {dropDownIconVariant && ( + {isDropdown && (
- {dropDownIconVariant === 'down' ? : } + {isToggled ? : }
)}
diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts index 9eebb396f..58f6dfb2b 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -3,8 +3,8 @@ import { ReactNode } from 'react' export interface MenuItemProps { title: string additionalInfo?: string - density?: 1 | 2 - dropDownIconVariant?: 'up' | 'down' + isDropdown?: boolean graphics?: ReactNode variant?: 'default' | 'nested' + onClick?: () => void } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 535b8a5c4..baaacecc2 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,4 +1,4 @@ -import { FC, Fragment } from 'react' +import { FC, Fragment, useState } from 'react' import { Menu as MuiMenu, MenuProps as MuiMenuProps } from '@mui/material' import { MenuItemProps as NestedMenuItemProps } from '~scss-components/menu-item/menu-item.types' @@ -10,27 +10,47 @@ interface MenuItemProps extends NestedMenuItemProps { nestedMenuItems?: NestedMenuItemProps[] } -interface MenuProps extends MuiMenuProps { +interface MenuProps extends Omit { menuItems: MenuItemProps[] density?: 1 | 2 } const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { + const [expandedNestedItem, setExpandedNestedItem] = useState( + null + ) + + const handleTopLevelItemClick = ({ + nestedMenuItems, + title, + onClick + }: MenuItemProps) => { + nestedMenuItems && + setExpandedNestedItem((previousTitle) => + previousTitle === title ? null : title + ) + + onClick && onClick() + } + return ( - {menuItems.map(({ nestedMenuItems, ...menuItemProps }) => ( - + {menuItems.map((item) => ( + handleTopLevelItemClick(item)} /> - {nestedMenuItems && - nestedMenuItems.map((nestedMenuItemProps) => ( + {item.nestedMenuItems && + expandedNestedItem === item.title && + item.nestedMenuItems.map((nestedMenuItemProps) => ( nestedMenuItemProps.onClick} variant='nested' /> ))} From fde760bf932b4db9607106fe6f09e38cf46822ca Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 16:47:27 +0200 Subject: [PATCH 08/41] replace Fragment with flatMap to fix MUI error --- src/design-system/components/menu/Menu.tsx | 29 +++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index baaacecc2..fa263c7f6 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,4 +1,4 @@ -import { FC, Fragment, useState } from 'react' +import { FC, useState } from 'react' import { Menu as MuiMenu, MenuProps as MuiMenuProps } from '@mui/material' import { MenuItemProps as NestedMenuItemProps } from '~scss-components/menu-item/menu-item.types' @@ -35,17 +35,16 @@ const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { return ( - {menuItems.map((item) => ( - - handleTopLevelItemClick(item)} - /> - {item.nestedMenuItems && - expandedNestedItem === item.title && - item.nestedMenuItems.map((nestedMenuItemProps) => ( + {menuItems.flatMap((item) => [ + handleTopLevelItemClick(item)} + />, + ...(item.nestedMenuItems && expandedNestedItem === item.title + ? item.nestedMenuItems.map((nestedMenuItemProps) => ( = ({ menuItems, open, density = 1 }: MenuProps) => { onClick={() => nestedMenuItemProps.onClick} variant='nested' /> - ))} - - ))} + )) + : []) + ])} ) } From eaa88a2d46b36bf92004c78e76765bdb5a9f62d0 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 16:55:50 +0200 Subject: [PATCH 09/41] replace isToggled handled in parent --- .../components/menu-item/MenuItem.tsx | 7 ++-- src/design-system/components/menu/Menu.tsx | 32 ++++++++----------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 370c71cbe..a46b2e1bb 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react' +import { FC } from 'react' import { MenuItem as MuiMenuItem } from '@mui/material' import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material' @@ -8,6 +8,7 @@ import '~scss-components/menu-item/MenuItem.scss' interface MenuItemProps extends CommonMenuItemProps { density?: 1 | 2 + isToggled?: boolean } const MenuItem: FC = ({ @@ -15,14 +16,12 @@ const MenuItem: FC = ({ additionalInfo, density = 1, isDropdown = false, + isToggled = false, graphics, variant = 'default', onClick = () => {} }) => { - const [isToggled, setIsToggled] = useState(false) - const handleClick = () => { - setIsToggled((previous) => !previous) onClick() } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index fa263c7f6..c277dfaee 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -16,21 +16,14 @@ interface MenuProps extends Omit { } const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { - const [expandedNestedItem, setExpandedNestedItem] = useState( - null - ) + const [toggledItem, setToggledItem] = useState(null) + + const handleTopLevelItemClick = ({ title, onClick }: MenuItemProps) => { + setToggledItem((previousTitle) => (previousTitle === title ? null : title)) - const handleTopLevelItemClick = ({ - nestedMenuItems, - title, - onClick - }: MenuItemProps) => { - nestedMenuItems && - setExpandedNestedItem((previousTitle) => - previousTitle === title ? null : title - ) - - onClick && onClick() + if (onClick) { + onClick() + } } return ( @@ -40,16 +33,17 @@ const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { {...item} density={density} isDropdown={Boolean(item.nestedMenuItems)} + isToggled={toggledItem === item.title} key={item.title} onClick={() => handleTopLevelItemClick(item)} />, - ...(item.nestedMenuItems && expandedNestedItem === item.title - ? item.nestedMenuItems.map((nestedMenuItemProps) => ( + ...(item.nestedMenuItems && toggledItem === item.title + ? item.nestedMenuItems.map((nestedMenuItem) => ( nestedMenuItemProps.onClick} + key={nestedMenuItem.title} + onClick={() => handleTopLevelItemClick(nestedMenuItem)} variant='nested' /> )) From e3d25a16556688c02bed766e1aca68581e1a5516 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 17:05:53 +0200 Subject: [PATCH 10/41] add onClick to stories --- src/design-system/stories/Menu.stories.tsx | 77 +++++++++++++++++----- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 0a17e88de..787a77618 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -18,9 +18,17 @@ export const Default: Story = { args: { open: true, menuItems: [ - { title: 'Lesson', graphics: }, - { title: 'Quiz', graphics: }, - { title: 'Attachment' } + { + title: 'Lesson', + graphics: , + onClick: () => alert('Lesson clicked') + }, + { + title: 'Quiz', + graphics: , + onClick: () => alert('Quiz clicked') + }, + { title: 'Attachment', onClick: () => alert('Attachment clicked') } ] } } @@ -33,17 +41,20 @@ export const WithAdditionalInfo: Story = { { title: 'Lesson', additionalInfo: 'Explore comprehensive lessons on various topics', - graphics: + graphics: , + onClick: () => alert('Lesson clicked') }, { title: 'Quiz', additionalInfo: 'Test your knowledge with engaging quizzes', - graphics: + graphics: , + onClick: () => alert('Quiz clicked') }, { title: 'Attachment', additionalInfo: 'Access all your important files and documents', - graphics: + graphics: , + onClick: () => alert('Attachment clicked') } ] } @@ -59,9 +70,21 @@ export const WithNestedMenuItems: Story = { additionalInfo: 'Explore comprehensive lessons on various topics', graphics: , nestedMenuItems: [ - { title: 'Math', graphics: }, - { title: 'Science', graphics: }, - { title: 'History', graphics: } + { + title: 'Math', + graphics: , + onClick: () => alert('Math clicked') + }, + { + title: 'Science', + graphics: , + onClick: () => alert('Science clicked') + }, + { + title: 'History', + graphics: , + onClick: () => alert('History clicked') + } ] }, { @@ -69,9 +92,21 @@ export const WithNestedMenuItems: Story = { additionalInfo: 'Test your knowledge with engaging quizzes', graphics: , nestedMenuItems: [ - { title: 'Math', graphics: }, - { title: 'Science', graphics: }, - { title: 'History', graphics: } + { + title: 'Geometry', + graphics: , + onClick: () => alert('Geometry clicked') + }, + { + title: 'Chemistry', + graphics: , + onClick: () => alert('Chemistry clicked') + }, + { + title: 'Modern History', + graphics: , + onClick: () => alert('Modern History clicked') + } ] }, { @@ -79,9 +114,21 @@ export const WithNestedMenuItems: Story = { additionalInfo: 'Files and documents', graphics: , nestedMenuItems: [ - { title: 'Math', graphics: }, - { title: 'Science', graphics: }, - { title: 'History', graphics: } + { + title: 'Solid of revolution', + graphics: , + onClick: () => alert('Solid of revolution clicked') + }, + { + title: 'Molecule', + graphics: , + onClick: () => alert('Molecule clicked') + }, + { + title: 'Modern world map', + graphics: , + onClick: () => alert('Modern world map clicked') + } ] } ] From 82497b8f1abfbdf55fbd9fc8079996c36353e795 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sat, 14 Dec 2024 17:19:19 +0200 Subject: [PATCH 11/41] refactor types for MenuItem --- src/design-system/components/menu-item/MenuItem.scss | 4 ++++ src/design-system/components/menu-item/MenuItem.tsx | 6 +++++- src/design-system/components/menu-item/menu-item.types.ts | 4 +--- src/design-system/components/menu/Menu.tsx | 3 ++- src/design-system/stories/Menu.stories.tsx | 6 +++++- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index 40fa6161a..f81770b3f 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -47,6 +47,10 @@ background-color: $blue-gray-50; } + &--bottom-border { + border-top: 1px solid $blue-gray-100; + } + &__graphics { width: 40px; height: 40px; diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index a46b2e1bb..43ba6e51b 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -7,8 +7,11 @@ import { MenuItemProps as CommonMenuItemProps } from './menu-item.types' import '~scss-components/menu-item/MenuItem.scss' interface MenuItemProps extends CommonMenuItemProps { + additionalInfo?: string + isDropdown?: boolean density?: 1 | 2 isToggled?: boolean + variant?: 'default' | 'nested' } const MenuItem: FC = ({ @@ -17,6 +20,7 @@ const MenuItem: FC = ({ density = 1, isDropdown = false, isToggled = false, + isBottomBorder = false, graphics, variant = 'default', onClick = () => {} @@ -27,7 +31,7 @@ const MenuItem: FC = ({ return ( diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts index 58f6dfb2b..ca85d2544 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -2,9 +2,7 @@ import { ReactNode } from 'react' export interface MenuItemProps { title: string - additionalInfo?: string - isDropdown?: boolean graphics?: ReactNode - variant?: 'default' | 'nested' + isBottomBorder?: boolean onClick?: () => void } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index c277dfaee..967cea4af 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -7,6 +7,7 @@ import MenuItem from '../menu-item/MenuItem' import '~scss-components/menu/Menu.scss' interface MenuItemProps extends NestedMenuItemProps { + additionalInfo?: string nestedMenuItems?: NestedMenuItemProps[] } @@ -41,7 +42,7 @@ const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { ? item.nestedMenuItems.map((nestedMenuItem) => ( handleTopLevelItemClick(nestedMenuItem)} variant='nested' diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 787a77618..d48125f81 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -28,7 +28,11 @@ export const Default: Story = { graphics: , onClick: () => alert('Quiz clicked') }, - { title: 'Attachment', onClick: () => alert('Attachment clicked') } + { + title: 'Attachment', + isBottomBorder: true, + onClick: () => alert('Attachment clicked') + } ] } } From 86f3f6e27cd865e0b98197a40d381cb5d1cd1839 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 12:27:31 +0200 Subject: [PATCH 12/41] add decorator with anchorEl to storybook --- src/design-system/components/menu/Menu.tsx | 31 ++++++++++++++++----- src/design-system/stories/Menu.stories.tsx | 32 +++++++++++++++++++--- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 967cea4af..ef46b8bce 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,5 +1,5 @@ import { FC, useState } from 'react' -import { Menu as MuiMenu, MenuProps as MuiMenuProps } from '@mui/material' +import { Menu as MuiMenu } from '@mui/material' import { MenuItemProps as NestedMenuItemProps } from '~scss-components/menu-item/menu-item.types' import MenuItem from '../menu-item/MenuItem' @@ -11,24 +11,41 @@ interface MenuItemProps extends NestedMenuItemProps { nestedMenuItems?: NestedMenuItemProps[] } -interface MenuProps extends Omit { +interface MenuProps { + anchorEl: HTMLElement | null + setAnchorEl: (anchorEl: HTMLElement | null) => void menuItems: MenuItemProps[] density?: 1 | 2 } -const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { +const Menu: FC = ({ + anchorEl, + setAnchorEl, + menuItems, + density = 1 +}: MenuProps) => { const [toggledItem, setToggledItem] = useState(null) - const handleTopLevelItemClick = ({ title, onClick }: MenuItemProps) => { + const handleItemClick = ({ title, onClick }: MenuItemProps) => { setToggledItem((previousTitle) => (previousTitle === title ? null : title)) if (onClick) { onClick() + handleMenuClose() } } + const handleMenuClose = () => { + setAnchorEl(null) + } + return ( - + {menuItems.flatMap((item) => [ = ({ menuItems, open, density = 1 }: MenuProps) => { isDropdown={Boolean(item.nestedMenuItems)} isToggled={toggledItem === item.title} key={item.title} - onClick={() => handleTopLevelItemClick(item)} + onClick={() => handleItemClick(item)} />, ...(item.nestedMenuItems && toggledItem === item.title ? item.nestedMenuItems.map((nestedMenuItem) => ( @@ -44,7 +61,7 @@ const Menu: FC = ({ menuItems, open, density = 1 }: MenuProps) => { {...nestedMenuItem} density={1} key={nestedMenuItem.title} - onClick={() => handleTopLevelItemClick(nestedMenuItem)} + onClick={() => handleItemClick(nestedMenuItem)} variant='nested' /> )) diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index d48125f81..6ad310df7 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -1,13 +1,40 @@ +import { useState } from 'react' import type { Meta, StoryObj } from '@storybook/react' import Menu from '~scss-components/menu/Menu' +import Button from '~scss-components/button/Button' import { Book } from '@mui/icons-material' import { Checkbox } from '@mui/material' const meta: Meta = { title: 'Components/Menu', component: Menu, - tags: ['autodocs'] + tags: ['autodocs'], + decorators: [ + (Story, context) => { + const [anchorEl, setAnchorEl] = useState(null) + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + return ( +
+ + + +
+ ) + } + ] } export default meta @@ -16,7 +43,6 @@ type Story = StoryObj export const Default: Story = { args: { - open: true, menuItems: [ { title: 'Lesson', @@ -39,7 +65,6 @@ export const Default: Story = { export const WithAdditionalInfo: Story = { args: { - open: true, density: 2, menuItems: [ { @@ -66,7 +91,6 @@ export const WithAdditionalInfo: Story = { export const WithNestedMenuItems: Story = { args: { - open: true, density: 2, menuItems: [ { From d9846f26cd63460b0788cf5fdd3acc6bc6fc114c Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 12:47:36 +0200 Subject: [PATCH 13/41] add Menu props --- src/design-system/components/menu/Menu.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index ef46b8bce..165260020 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,5 +1,5 @@ import { FC, useState } from 'react' -import { Menu as MuiMenu } from '@mui/material' +import { Menu as MuiMenu, PopoverOrigin } from '@mui/material' import { MenuItemProps as NestedMenuItemProps } from '~scss-components/menu-item/menu-item.types' import MenuItem from '../menu-item/MenuItem' @@ -15,14 +15,18 @@ interface MenuProps { anchorEl: HTMLElement | null setAnchorEl: (anchorEl: HTMLElement | null) => void menuItems: MenuItemProps[] + anchorOrigin?: PopoverOrigin density?: 1 | 2 + transformOrigin?: PopoverOrigin + slotProps?: { paper: { style: { maxHeight: number } } } } const Menu: FC = ({ anchorEl, setAnchorEl, menuItems, - density = 1 + density = 1, + ...menuProps }: MenuProps) => { const [toggledItem, setToggledItem] = useState(null) @@ -45,6 +49,7 @@ const Menu: FC = ({ className={`s2s-menu s2s-menu--density-${density}`} onClose={handleMenuClose} open={Boolean(anchorEl)} + {...menuProps} > {menuItems.flatMap((item) => [ Date: Sun, 15 Dec 2024 14:18:32 +0200 Subject: [PATCH 14/41] add defaultOnItemClick --- .../components/menu-item/menu-item.types.ts | 2 + src/design-system/components/menu/Menu.tsx | 55 +++++++++++++++---- src/design-system/stories/Menu.stories.tsx | 9 ++- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts index ca85d2544..028b6998e 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -1,5 +1,7 @@ import { ReactNode } from 'react' +export type OnItemClickArgs = Record + export interface MenuItemProps { title: string graphics?: ReactNode diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 165260020..069f75704 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,11 +1,18 @@ -import { FC, useState } from 'react' +import { FC, useCallback, useState } from 'react' import { Menu as MuiMenu, PopoverOrigin } from '@mui/material' -import { MenuItemProps as NestedMenuItemProps } from '~scss-components/menu-item/menu-item.types' +import { + MenuItemProps as CommonMenuItemProps, + OnItemClickArgs +} from '~scss-components/menu-item/menu-item.types' import MenuItem from '../menu-item/MenuItem' import '~scss-components/menu/Menu.scss' +interface NestedMenuItemProps extends CommonMenuItemProps { + defaultOnItemClickArgs?: OnItemClickArgs +} + interface MenuItemProps extends NestedMenuItemProps { additionalInfo?: string nestedMenuItems?: NestedMenuItemProps[] @@ -17,6 +24,7 @@ interface MenuProps { menuItems: MenuItemProps[] anchorOrigin?: PopoverOrigin density?: 1 | 2 + defaultOnItemClick?: (args: OnItemClickArgs) => void transformOrigin?: PopoverOrigin slotProps?: { paper: { style: { maxHeight: number } } } } @@ -25,23 +33,46 @@ const Menu: FC = ({ anchorEl, setAnchorEl, menuItems, + defaultOnItemClick, density = 1, ...menuProps }: MenuProps) => { const [toggledItem, setToggledItem] = useState(null) - const handleItemClick = ({ title, onClick }: MenuItemProps) => { - setToggledItem((previousTitle) => (previousTitle === title ? null : title)) + const handleMenuClose = useCallback(() => { + setAnchorEl(null) + }, [setAnchorEl]) - if (onClick) { - onClick() - handleMenuClose() - } - } + const handleItemClick = useCallback( + ({ + title, + defaultOnItemClickArgs, + onClick: customOnClick + }: MenuItemProps) => { + if (!customOnClick && !defaultOnItemClick) { + setToggledItem((previousTitle) => + previousTitle === title ? null : title + ) + return + } - const handleMenuClose = () => { - setAnchorEl(null) - } + if (customOnClick) { + customOnClick() + handleMenuClose() + } else if (defaultOnItemClick) { + const args: OnItemClickArgs = + defaultOnItemClickArgs === undefined + ? { title } + : { title, ...defaultOnItemClickArgs } + + defaultOnItemClick(args) + } + + setToggledItem(null) + handleMenuClose() + }, + [defaultOnItemClick, handleMenuClose] + ) return ( export const Default: Story = { args: { + defaultOnItemClick(args) { + alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + }, menuItems: [ { title: 'Lesson', graphics: , - onClick: () => alert('Lesson clicked') + defaultOnItemClickArgs: { path: '/lesson' } }, { title: 'Quiz', graphics: , - onClick: () => alert('Quiz clicked') + defaultOnItemClickArgs: { path: '/quiz' } }, { title: 'Attachment', isBottomBorder: true, - onClick: () => alert('Attachment clicked') + defaultOnItemClickArgs: { path: '/attachment' } } ] } From b42a3b6a5d32e4c42bfe306f266f2f09b798b695 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 14:31:26 +0200 Subject: [PATCH 15/41] add stories --- src/design-system/components/menu/Menu.tsx | 3 +- src/design-system/stories/Menu.stories.tsx | 124 ++++++++++++++++----- 2 files changed, 97 insertions(+), 30 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 069f75704..42205fd8d 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -47,9 +47,10 @@ const Menu: FC = ({ ({ title, defaultOnItemClickArgs, + nestedMenuItems, onClick: customOnClick }: MenuItemProps) => { - if (!customOnClick && !defaultOnItemClick) { + if ((!customOnClick && !defaultOnItemClick) || nestedMenuItems) { setToggledItem((previousTitle) => previousTitle === title ? null : title ) diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index ff2bb4d97..eec17e18e 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react' import Menu from '~scss-components/menu/Menu' import Button from '~scss-components/button/Button' -import { Book } from '@mui/icons-material' +import { EditRounded } from '@mui/icons-material' import { Checkbox } from '@mui/material' const meta: Meta = { @@ -49,7 +49,79 @@ export const Default: Story = { menuItems: [ { title: 'Lesson', - graphics: , + defaultOnItemClickArgs: { path: '/lesson' } + }, + { + title: 'Quiz', + defaultOnItemClickArgs: { path: '/quiz' } + }, + { + title: 'Attachment', + defaultOnItemClickArgs: { path: '/attachment' } + } + ] + } +} + +export const WithIcon: Story = { + args: { + defaultOnItemClick(args) { + alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + }, + menuItems: [ + { + title: 'Lesson', + graphics: , + defaultOnItemClickArgs: { path: '/lesson' } + }, + { + title: 'Quiz', + graphics: , + defaultOnItemClickArgs: { path: '/quiz' } + }, + { + title: 'Attachment', + graphics: , + defaultOnItemClickArgs: { path: '/attachment' } + } + ] + } +} + +export const WithCheckbox: Story = { + args: { + defaultOnItemClick(args) { + alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + }, + menuItems: [ + { + title: 'Lesson', + graphics: , + defaultOnItemClickArgs: { path: '/lesson' } + }, + { + title: 'Quiz', + graphics: , + defaultOnItemClickArgs: { path: '/quiz' } + }, + { + title: 'Attachment', + graphics: , + defaultOnItemClickArgs: { path: '/attachment' } + } + ] + } +} + +export const Mixed: Story = { + args: { + defaultOnItemClick(args) { + alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + }, + menuItems: [ + { + title: 'Lesson', + graphics: , defaultOnItemClickArgs: { path: '/lesson' } }, { @@ -69,24 +141,24 @@ export const Default: Story = { export const WithAdditionalInfo: Story = { args: { density: 2, + defaultOnItemClick(args) { + alert(`${JSON.stringify(args.title)} clicked`) + }, menuItems: [ { title: 'Lesson', additionalInfo: 'Explore comprehensive lessons on various topics', - graphics: , - onClick: () => alert('Lesson clicked') + graphics: }, { title: 'Quiz', additionalInfo: 'Test your knowledge with engaging quizzes', - graphics: , - onClick: () => alert('Quiz clicked') + graphics: }, { title: 'Attachment', additionalInfo: 'Access all your important files and documents', - graphics: , - onClick: () => alert('Attachment clicked') + graphics: } ] } @@ -95,70 +167,64 @@ export const WithAdditionalInfo: Story = { export const WithNestedMenuItems: Story = { args: { density: 2, + defaultOnItemClick(args) { + alert(`${JSON.stringify(args.title)} clicked`) + }, menuItems: [ { title: 'Lesson', additionalInfo: 'Explore comprehensive lessons on various topics', - graphics: , + graphics: , nestedMenuItems: [ { title: 'Math', - graphics: , - onClick: () => alert('Math clicked') + graphics: }, { title: 'Science', - graphics: , - onClick: () => alert('Science clicked') + graphics: }, { title: 'History', - graphics: , - onClick: () => alert('History clicked') + graphics: } ] }, { title: 'Quiz', additionalInfo: 'Test your knowledge with engaging quizzes', - graphics: , + graphics: , nestedMenuItems: [ { title: 'Geometry', - graphics: , - onClick: () => alert('Geometry clicked') + graphics: }, { title: 'Chemistry', - graphics: , - onClick: () => alert('Chemistry clicked') + graphics: }, { title: 'Modern History', - graphics: , - onClick: () => alert('Modern History clicked') + graphics: } ] }, { title: 'Attachment', additionalInfo: 'Files and documents', - graphics: , + graphics: , nestedMenuItems: [ { title: 'Solid of revolution', - graphics: , - onClick: () => alert('Solid of revolution clicked') + graphics: }, { title: 'Molecule', - graphics: , - onClick: () => alert('Molecule clicked') + graphics: }, { title: 'Modern world map', - graphics: , - onClick: () => alert('Modern world map clicked') + graphics: } ] } From a959490a12ab5620119f019c5efd2020588c1a23 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 14:36:16 +0200 Subject: [PATCH 16/41] update stories onClicks --- src/design-system/stories/Menu.stories.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index eec17e18e..9c3276e84 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -44,7 +44,7 @@ type Story = StoryObj export const Default: Story = { args: { defaultOnItemClick(args) { - alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + alert(`defaultOnItemClick args: ${JSON.stringify(args)}.`) }, menuItems: [ { @@ -66,7 +66,7 @@ export const Default: Story = { export const WithIcon: Story = { args: { defaultOnItemClick(args) { - alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + alert(`defaultOnItemClick args: ${JSON.stringify(args)}.`) }, menuItems: [ { @@ -91,7 +91,7 @@ export const WithIcon: Story = { export const WithCheckbox: Story = { args: { defaultOnItemClick(args) { - alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + alert(`defaultOnItemClick args: ${JSON.stringify(args)}.`) }, menuItems: [ { @@ -116,7 +116,7 @@ export const WithCheckbox: Story = { export const Mixed: Story = { args: { defaultOnItemClick(args) { - alert(`defaultOnItemClick args: ${JSON.stringify(args)}`) + alert(`defaultOnItemClick args: ${JSON.stringify(args)}.`) }, menuItems: [ { @@ -127,7 +127,12 @@ export const Mixed: Story = { { title: 'Quiz', graphics: , - defaultOnItemClickArgs: { path: '/quiz' } + defaultOnItemClickArgs: { path: '/quiz' }, + onClick: () => { + alert( + 'Quiz onClick called. Imagine there is custom checkbox filter logic here.' + ) + } }, { title: 'Attachment', @@ -142,7 +147,7 @@ export const WithAdditionalInfo: Story = { args: { density: 2, defaultOnItemClick(args) { - alert(`${JSON.stringify(args.title)} clicked`) + alert(`${JSON.stringify(args.title)} clicked.`) }, menuItems: [ { @@ -168,7 +173,7 @@ export const WithNestedMenuItems: Story = { args: { density: 2, defaultOnItemClick(args) { - alert(`${JSON.stringify(args.title)} clicked`) + alert(`${JSON.stringify(args.title)} clicked.`) }, menuItems: [ { From 3e6988ac6bdc8f32d8fbdaaf0ad86bd2e9f23fd4 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 14:58:56 +0200 Subject: [PATCH 17/41] enhance style variants for menu item --- .../components/menu-item/MenuItem.scss | 13 ++++++++++++- .../components/menu-item/MenuItem.tsx | 14 +++++++++++++- .../components/menu-item/menu-item.types.ts | 2 ++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index f81770b3f..0f82751e5 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -3,7 +3,6 @@ .#{$prefix}item { background-color: $neutral-0; width: 100%; - color: $blue-gray-700; padding: 16px 8px; display: flex; justify-content: space-between; @@ -47,6 +46,18 @@ background-color: $blue-gray-50; } + &--default { + color: $blue-gray-700; + } + + &--danger { + color: $red-500; + } + + &--disabled { + color: $blue-gray-700; + } + &--bottom-border { border-top: 1px solid $blue-gray-100; } diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 43ba6e51b..d7c8e7630 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -5,6 +5,7 @@ import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material' import { MenuItemProps as CommonMenuItemProps } from './menu-item.types' import '~scss-components/menu-item/MenuItem.scss' +import { cn } from '~/utils/cn' interface MenuItemProps extends CommonMenuItemProps { additionalInfo?: string @@ -17,10 +18,12 @@ interface MenuItemProps extends CommonMenuItemProps { const MenuItem: FC = ({ title, additionalInfo, + colorVariant = 'default', density = 1, isDropdown = false, isToggled = false, isBottomBorder = false, + isDisabled = false, graphics, variant = 'default', onClick = () => {} @@ -31,7 +34,16 @@ const MenuItem: FC = ({ return ( diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts index 028b6998e..e95646f76 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -4,6 +4,8 @@ export type OnItemClickArgs = Record export interface MenuItemProps { title: string + colorVariant?: 'default' | 'danger' + isDisabled?: boolean graphics?: ReactNode isBottomBorder?: boolean onClick?: () => void From 0c3dfa005a4c933a2297852ad242178d6371d686 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 15:10:35 +0200 Subject: [PATCH 18/41] remove min width --- src/design-system/components/menu-item/MenuItem.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index 0f82751e5..caa2e0dd4 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -21,12 +21,10 @@ &--density-1 { height: 48px; - min-width: 200px; } &--density-2 { height: 64px; - min-width: 256px; padding-top: 30px; } From 6868eb71ce2be8c229f02761d1beec1dce6d1f79 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 15:49:48 +0200 Subject: [PATCH 19/41] add custom menu positioning stories --- .../components/menu-item/MenuItem.tsx | 2 +- src/design-system/components/menu/Menu.tsx | 8 +++- src/design-system/stories/Menu.stories.tsx | 42 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index d7c8e7630..9daaec7ad 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -18,13 +18,13 @@ interface MenuItemProps extends CommonMenuItemProps { const MenuItem: FC = ({ title, additionalInfo, + graphics, colorVariant = 'default', density = 1, isDropdown = false, isToggled = false, isBottomBorder = false, isDisabled = false, - graphics, variant = 'default', onClick = () => {} }) => { diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 42205fd8d..0a10cb6df 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -25,8 +25,8 @@ interface MenuProps { anchorOrigin?: PopoverOrigin density?: 1 | 2 defaultOnItemClick?: (args: OnItemClickArgs) => void + maxHeight?: number transformOrigin?: PopoverOrigin - slotProps?: { paper: { style: { maxHeight: number } } } } const Menu: FC = ({ @@ -34,6 +34,7 @@ const Menu: FC = ({ setAnchorEl, menuItems, defaultOnItemClick, + maxHeight, density = 1, ...menuProps }: MenuProps) => { @@ -81,6 +82,11 @@ const Menu: FC = ({ className={`s2s-menu s2s-menu--density-${density}`} onClose={handleMenuClose} open={Boolean(anchorEl)} + slotProps={{ + paper: { + style: { maxHeight: maxHeight } + } + }} {...menuProps} > {menuItems.flatMap((item) => [ diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 9c3276e84..cc310e7f1 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -236,3 +236,45 @@ export const WithNestedMenuItems: Story = { ] } } + +export const CustomAnchorOrigin: Story = { + args: { + maxHeight: 100, + anchorOrigin: { + vertical: 'bottom', + horizontal: 'center' + }, + menuItems: [ + { + title: 'Lesson' + }, + { + title: 'Quiz' + }, + { + title: 'Attachment' + } + ] + } +} + +export const CustomTransformOrigin: Story = { + args: { + maxHeight: 100, + transformOrigin: { + vertical: 'bottom', + horizontal: 'left' + }, + menuItems: [ + { + title: 'Lesson' + }, + { + title: 'Quiz' + }, + { + title: 'Attachment' + } + ] + } +} From a3acc961d05082fa05db829b5bddb9307129d045 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 16:55:15 +0200 Subject: [PATCH 20/41] add ability to remove items --- .../components/menu-item/MenuItem.scss | 4 +++ .../components/menu-item/MenuItem.tsx | 9 +++++- src/design-system/components/menu/Menu.tsx | 29 +++++++++++++++++-- src/design-system/stories/Menu.stories.tsx | 19 ++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index caa2e0dd4..77f1b85cb 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -84,4 +84,8 @@ display: flex; align-items: center; } + + &__close { + font-size: $font-size-sm; + } } diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 9daaec7ad..54a27f53f 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' import { MenuItem as MuiMenuItem } from '@mui/material' -import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material' +import { ArrowDropDown, ArrowDropUp, CloseRounded } from '@mui/icons-material' import { MenuItemProps as CommonMenuItemProps } from './menu-item.types' @@ -12,6 +12,7 @@ interface MenuItemProps extends CommonMenuItemProps { isDropdown?: boolean density?: 1 | 2 isToggled?: boolean + onRemove?: () => void variant?: 'default' | 'nested' } @@ -19,6 +20,7 @@ const MenuItem: FC = ({ title, additionalInfo, graphics, + onRemove, colorVariant = 'default', density = 1, isDropdown = false, @@ -59,6 +61,11 @@ const MenuItem: FC = ({ {isToggled ? : } )} + {onRemove && !isDropdown && !isDisabled && ( +
+ +
+ )}
) } diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 0a10cb6df..cf4ca4d19 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -25,7 +25,10 @@ interface MenuProps { anchorOrigin?: PopoverOrigin density?: 1 | 2 defaultOnItemClick?: (args: OnItemClickArgs) => void + isItemsRemovalEnabled?: boolean + noItemsMessage?: string maxHeight?: number + minWidth?: number transformOrigin?: PopoverOrigin } @@ -35,9 +38,13 @@ const Menu: FC = ({ menuItems, defaultOnItemClick, maxHeight, + minWidth, + noItemsMessage, density = 1, + isItemsRemovalEnabled = false, ...menuProps }: MenuProps) => { + const [items, setItems] = useState(menuItems) const [toggledItem, setToggledItem] = useState(null) const handleMenuClose = useCallback(() => { @@ -76,6 +83,19 @@ const Menu: FC = ({ [defaultOnItemClick, handleMenuClose] ) + const handleItemRemoval = (title: string) => { + setItems((previousItems) => + previousItems.filter((item) => item.title !== title) + ) + } + + if (items.length < 1) { + items.push({ + title: noItemsMessage ?? 'No items', + isDisabled: true + }) + } + return ( = ({ open={Boolean(anchorEl)} slotProps={{ paper: { - style: { maxHeight: maxHeight } + style: { maxHeight: maxHeight, minWidth: minWidth } } }} {...menuProps} > - {menuItems.flatMap((item) => [ + {items.flatMap((item) => [ = ({ isToggled={toggledItem === item.title} key={item.title} onClick={() => handleItemClick(item)} + onRemove={ + isItemsRemovalEnabled + ? () => handleItemRemoval(item.title) + : undefined + } />, ...(item.nestedMenuItems && toggledItem === item.title ? item.nestedMenuItems.map((nestedMenuItem) => ( diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index cc310e7f1..228761de9 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -278,3 +278,22 @@ export const CustomTransformOrigin: Story = { ] } } + +export const RemovableItems: Story = { + args: { + isItemsRemovalEnabled: true, + noItemsMessage: 'No notifications yet.', + minWidth: 350, + menuItems: [ + { + title: 'Your cooperation was accepted' + }, + { + title: 'Your cooperation was declined' + }, + { + title: 'You have a new cooperation' + } + ] + } +} From 836be19b370676ed10d53246583af263f844c63f Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 17:31:14 +0200 Subject: [PATCH 21/41] enhance remove items styles and logic --- .../components/menu-item/MenuItem.scss | 10 +++++++++- .../components/menu-item/MenuItem.tsx | 2 ++ .../components/menu-item/menu-item.types.ts | 3 ++- src/design-system/components/menu/Menu.tsx | 18 +++++++++++------- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index 77f1b85cb..903a93475 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -19,6 +19,10 @@ background-color: $blue-gray-50; } + &--align-center { + justify-content: center; + } + &--density-1 { height: 48px; } @@ -48,10 +52,14 @@ color: $blue-gray-700; } - &--danger { + &--color-danger { color: $red-500; } + &--color-secondary { + color: $blue-gray-500; + } + &--disabled { color: $blue-gray-700; } diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 54a27f53f..9c18f73a2 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -21,6 +21,7 @@ const MenuItem: FC = ({ additionalInfo, graphics, onRemove, + alignVariant = 'left', colorVariant = 'default', density = 1, isDropdown = false, @@ -41,6 +42,7 @@ const MenuItem: FC = ({ `s2s-item--density-${density}`, `s2s-item--variant-${variant}`, `s2s-item--color-${colorVariant}`, + `s2s-item--align-${alignVariant}`, isToggled && 's2s-item--toggled', isBottomBorder && 's2s-item--bottom-border', isDisabled && 's2s-item--disabled' diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/menu-item.types.ts index e95646f76..82c035822 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/menu-item.types.ts @@ -4,7 +4,8 @@ export type OnItemClickArgs = Record export interface MenuItemProps { title: string - colorVariant?: 'default' | 'danger' + alignVariant?: 'left' | 'right' | 'center' + colorVariant?: 'default' | 'danger' | 'secondary' isDisabled?: boolean graphics?: ReactNode isBottomBorder?: boolean diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index cf4ca4d19..f7acfd226 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -89,13 +89,6 @@ const Menu: FC = ({ ) } - if (items.length < 1) { - items.push({ - title: noItemsMessage ?? 'No items', - isDisabled: true - }) - } - return ( = ({ )) : []) ])} + {isItemsRemovalEnabled && + (items.length >= 1 ? ( + setItems([])} + title='Clear all' + /> + ) : ( + + ))} ) } From bee008fa21d085c331b38bc766c0318d54b8e9ae Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 18:10:57 +0200 Subject: [PATCH 22/41] enhance stories docs to include arguments and description --- src/design-system/stories/Menu.stories.tsx | 144 +++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 228761de9..2a6a78669 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -10,6 +10,80 @@ const meta: Meta = { title: 'Components/Menu', component: Menu, tags: ['autodocs'], + parameters: { + docs: { + description: { + component: ` +The Menu component is a dropdown menu that displays a list of items. + +#### Key features: +- **Menu Items:** Display a list of items in the menu. Each item can have a title, additional information, and an icon. +- **Items Removal:** Enable or disable the removal of items from the menu. +- **Default On Item Click:** Define the default function to call when a menu item is clicked. +- **Customization:** Customize the appearance of the menu items using the provided props. + ` + } + } + }, + argTypes: { + anchorEl: { + description: + 'The element that serves as the anchor for the menu. This element is used to position the menu and open it.' + }, + setAnchorEl: { + description: 'Function to set the anchor element.' + }, + menuItems: { + description: 'The list of menu items to be displayed.', + control: { type: 'object' } + }, + anchorOrigin: { + description: 'The anchor origin point of the menu.', + control: { type: 'object' } + }, + density: { + description: 'The density of the menu items.', + control: { type: 'radio' } + }, + defaultOnItemClick: { + description: 'The default function to call when a menu item is clicked.' + }, + isItemsRemovalEnabled: { + description: 'Determines whether the items can be removed from the menu.', + control: 'boolean' + }, + noItemsMessage: { + description: + 'The message to display when there are no items in the menu.', + control: { type: 'text' } + }, + maxHeight: { + description: 'The maximum height of the menu.', + control: { type: 'number' } + }, + minWidth: { + description: 'The minimum width of the menu.', + control: { type: 'number' } + }, + transformOrigin: { + description: 'The transform origin point of the menu.', + control: { type: 'object' } + } + }, + args: { + anchorOrigin: { + vertical: 'bottom', + horizontal: 'left' + }, + isItemsRemovalEnabled: false, + noItemsMessage: 'No items to display.', + maxHeight: 200, + minWidth: 200, + transformOrigin: { + vertical: 'top', + horizontal: 'left' + } + }, decorators: [ (Story, context) => { const [anchorEl, setAnchorEl] = useState(null) @@ -60,6 +134,13 @@ export const Default: Story = { defaultOnItemClickArgs: { path: '/attachment' } } ] + }, + parameters: { + docs: { + description: { + story: 'The most basic dropdown menu. Click on the button to open.' + } + } } } @@ -85,6 +166,14 @@ export const WithIcon: Story = { defaultOnItemClickArgs: { path: '/attachment' } } ] + }, + parameters: { + docs: { + description: { + story: + 'The dropdown menu with custom icons. Click on the button to open.' + } + } } } @@ -110,6 +199,13 @@ export const WithCheckbox: Story = { defaultOnItemClickArgs: { path: '/attachment' } } ] + }, + parameters: { + docs: { + description: { + story: 'The dropdown menu with checkboxes. Click on the button to open.' + } + } } } @@ -140,6 +236,14 @@ export const Mixed: Story = { defaultOnItemClickArgs: { path: '/attachment' } } ] + }, + parameters: { + docs: { + description: { + story: + 'The menu can have a mix of icons, checkboxes, and custom onClick functions. Click on the button to open.' + } + } } } @@ -166,6 +270,14 @@ export const WithAdditionalInfo: Story = { graphics: } ] + }, + parameters: { + docs: { + description: { + story: + 'The dropdown menu with additional information and density value 2. Click on the button to open.' + } + } } } @@ -234,6 +346,14 @@ export const WithNestedMenuItems: Story = { ] } ] + }, + parameters: { + docs: { + description: { + story: + 'The dropdown menu with nested menu items and density value 2. A nested menu item is a menu item that contains another list of menu items. Click on the button to open.' + } + } } } @@ -255,6 +375,14 @@ export const CustomAnchorOrigin: Story = { title: 'Attachment' } ] + }, + parameters: { + docs: { + description: { + story: + 'The dropdown menu with a custom anchor origin point. Click on the button to open.' + } + } } } @@ -276,6 +404,14 @@ export const CustomTransformOrigin: Story = { title: 'Attachment' } ] + }, + parameters: { + docs: { + description: { + story: + 'The dropdown menu with a custom transform origin point. Click on the button to open.' + } + } } } @@ -295,5 +431,13 @@ export const RemovableItems: Story = { title: 'You have a new cooperation' } ] + }, + parameters: { + docs: { + description: { + story: + 'The dropdown menu with removable items. All top-level non-dropdown menu items can be removed. It is possible to set a custom message when there are no items in the menu. Click on the button to open.' + } + } } } From d0d0126b46837d91be17a50089d29e4708f9ec32 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 18:30:24 +0200 Subject: [PATCH 23/41] add MenuItem enums --- .../components/menu-item/MenuItem.constants.ts | 10 ++++++++++ src/design-system/components/menu-item/MenuItem.tsx | 11 ++++++----- .../{menu-item.types.ts => MenuItem.types.ts} | 5 +++-- src/design-system/components/menu/Menu.tsx | 10 +++++++--- 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 src/design-system/components/menu-item/MenuItem.constants.ts rename src/design-system/components/menu-item/{menu-item.types.ts => MenuItem.types.ts} (63%) diff --git a/src/design-system/components/menu-item/MenuItem.constants.ts b/src/design-system/components/menu-item/MenuItem.constants.ts new file mode 100644 index 000000000..322d51b8e --- /dev/null +++ b/src/design-system/components/menu-item/MenuItem.constants.ts @@ -0,0 +1,10 @@ +export enum MenuItemColorVariant { + Default = 'default', + Danger = 'danger', + Secondary = 'secondary' +} + +export enum MenuItemVariant { + Default = 'default', + Nested = 'nested' +} diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 9c18f73a2..2f8346d52 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -2,10 +2,11 @@ import { FC } from 'react' import { MenuItem as MuiMenuItem } from '@mui/material' import { ArrowDropDown, ArrowDropUp, CloseRounded } from '@mui/icons-material' -import { MenuItemProps as CommonMenuItemProps } from './menu-item.types' +import { MenuItemProps as CommonMenuItemProps } from './MenuItem.types' +import { MenuItemColorVariant, MenuItemVariant } from './MenuItem.constants' +import { cn } from '~/utils/cn' import '~scss-components/menu-item/MenuItem.scss' -import { cn } from '~/utils/cn' interface MenuItemProps extends CommonMenuItemProps { additionalInfo?: string @@ -13,7 +14,7 @@ interface MenuItemProps extends CommonMenuItemProps { density?: 1 | 2 isToggled?: boolean onRemove?: () => void - variant?: 'default' | 'nested' + variant?: MenuItemVariant } const MenuItem: FC = ({ @@ -22,13 +23,13 @@ const MenuItem: FC = ({ graphics, onRemove, alignVariant = 'left', - colorVariant = 'default', + colorVariant = MenuItemColorVariant.Default, density = 1, isDropdown = false, isToggled = false, isBottomBorder = false, isDisabled = false, - variant = 'default', + variant = MenuItemVariant.Default, onClick = () => {} }) => { const handleClick = () => { diff --git a/src/design-system/components/menu-item/menu-item.types.ts b/src/design-system/components/menu-item/MenuItem.types.ts similarity index 63% rename from src/design-system/components/menu-item/menu-item.types.ts rename to src/design-system/components/menu-item/MenuItem.types.ts index 82c035822..c51d9240a 100644 --- a/src/design-system/components/menu-item/menu-item.types.ts +++ b/src/design-system/components/menu-item/MenuItem.types.ts @@ -1,11 +1,12 @@ import { ReactNode } from 'react' +import { MenuItemColorVariant } from './MenuItem.constants' export type OnItemClickArgs = Record export interface MenuItemProps { title: string - alignVariant?: 'left' | 'right' | 'center' - colorVariant?: 'default' | 'danger' | 'secondary' + alignVariant?: 'left' | 'center' | 'right' + colorVariant?: MenuItemColorVariant isDisabled?: boolean graphics?: ReactNode isBottomBorder?: boolean diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index f7acfd226..f3cf7c2dc 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -4,8 +4,12 @@ import { Menu as MuiMenu, PopoverOrigin } from '@mui/material' import { MenuItemProps as CommonMenuItemProps, OnItemClickArgs -} from '~scss-components/menu-item/menu-item.types' +} from '~/design-system/components/menu-item/MenuItem.types' import MenuItem from '../menu-item/MenuItem' +import { + MenuItemColorVariant, + MenuItemVariant +} from '../menu-item/MenuItem.constants' import '~scss-components/menu/Menu.scss' @@ -123,7 +127,7 @@ const Menu: FC = ({ density={1} key={nestedMenuItem.title} onClick={() => handleItemClick(nestedMenuItem)} - variant='nested' + variant={MenuItemVariant.Nested} /> )) : []) @@ -132,7 +136,7 @@ const Menu: FC = ({ (items.length >= 1 ? ( setItems([])} title='Clear all' /> From b3bdc35bfa23f5003d98615028b05f38948e3c17 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 18:36:34 +0200 Subject: [PATCH 24/41] add constants to Menu --- src/design-system/components/menu/Menu.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index f3cf7c2dc..0ac4f7726 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -13,6 +13,9 @@ import { import '~scss-components/menu/Menu.scss' +const removeAllItemsTitle = 'Clear all' +const defaultNoItemsMessage = 'No items.' + interface NestedMenuItemProps extends CommonMenuItemProps { defaultOnItemClickArgs?: OnItemClickArgs } @@ -138,10 +141,13 @@ const Menu: FC = ({ alignVariant='center' colorVariant={MenuItemColorVariant.Secondary} onClick={() => setItems([])} - title='Clear all' + title={removeAllItemsTitle} /> ) : ( - + ))} ) From 091d3ba0811f6f6e50b8b2f770b5648f3151a54e Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 21:18:55 +0200 Subject: [PATCH 25/41] add MenuItem to storybook --- .../components/menu-item/MenuItem.scss | 2 +- .../components/menu-item/MenuItem.tsx | 8 +- src/design-system/stories/Menu.stories.tsx | 4 +- .../stories/MenuItem.stories.tsx | 95 +++++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/design-system/stories/MenuItem.stories.tsx diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index 903a93475..f7f15538f 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -65,7 +65,7 @@ } &--bottom-border { - border-top: 1px solid $blue-gray-100; + border-bottom: 1px solid $blue-gray-100; } &__graphics { diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 2f8346d52..bcff05940 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -65,7 +65,13 @@ const MenuItem: FC = ({ )} {onRemove && !isDropdown && !isDisabled && ( -
+
{ + event.stopPropagation() + onRemove() + }} + >
)} diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 2a6a78669..b512430e9 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -1,10 +1,10 @@ import { useState } from 'react' import type { Meta, StoryObj } from '@storybook/react' +import { EditRounded } from '@mui/icons-material' +import { Checkbox } from '@mui/material' import Menu from '~scss-components/menu/Menu' import Button from '~scss-components/button/Button' -import { EditRounded } from '@mui/icons-material' -import { Checkbox } from '@mui/material' const meta: Meta = { title: 'Components/Menu', diff --git a/src/design-system/stories/MenuItem.stories.tsx b/src/design-system/stories/MenuItem.stories.tsx new file mode 100644 index 000000000..a7c89eb08 --- /dev/null +++ b/src/design-system/stories/MenuItem.stories.tsx @@ -0,0 +1,95 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { EditRounded } from '@mui/icons-material' + +import MenuItem from '~scss-components/menu-item/MenuItem' +import { MenuItemColorVariant } from '~scss-components/menu-item/MenuItem.constants' + +const meta: Meta = { + title: 'Components/MenuItem', + component: MenuItem, + tags: ['autodocs'] +} + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + title: 'Assigment', + onClick: () => alert('Item "Default" was clicked.') + } +} + +export const WithGraphics: Story = { + args: { + title: 'Assigment', + graphics: , + onClick: () => alert('Item "With Graphics" was clicked.') + } +} + +export const WithAdditionalInfo: Story = { + args: { + title: 'Assigment', + graphics: , + additionalInfo: 'Due in 2 days', + density: 2, + onClick: () => alert('Item "With Additional Info" was clicked.') + } +} + +export const WithDropdown: Story = { + args: { + title: 'Assigment', + graphics: , + isDropdown: true, + onClick: () => alert('Imagine dropdown was expanded.') + } +} + +export const Toggled: Story = { + args: { + title: 'Assigment', + graphics: , + isToggled: true, + onClick: () => alert('Item "Toggled" was clicked.') + } +} + +export const WithBottomBorder: Story = { + args: { + title: 'Assigment', + graphics: , + isBottomBorder: true, + onClick: () => alert('Item "With Bottom Border" was clicked.') + } +} + +export const Disabled: Story = { + args: { + title: 'Assigment', + graphics: , + isDisabled: true, + onClick: () => alert('Item "Disabled" was clicked.') + } +} + +export const RemovableMenuItem: Story = { + args: { + title: 'Assigment', + graphics: , + onClick: () => alert('Item "With Remove" was clicked.'), + onRemove: () => alert('Imagine this item was removed') + } +} + +export const SecondaryColorAndCentered: Story = { + args: { + title: 'Assigment', + graphics: , + alignVariant: 'center', + colorVariant: MenuItemColorVariant.Secondary, + onClick: () => alert('Item "Colored Menu Item" was clicked.') + } +} From bcb6af31df9aeb5b036c76baeeed64138833dec1 Mon Sep 17 00:00:00 2001 From: luiqor Date: Sun, 15 Dec 2024 21:55:00 +0200 Subject: [PATCH 26/41] enhance MenuItem documentation --- .../components/menu-item/MenuItem.scss | 4 + .../stories/MenuItem.stories.tsx | 176 +++++++++++++++--- 2 files changed, 151 insertions(+), 29 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index f7f15538f..75f941585 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -23,6 +23,10 @@ justify-content: center; } + &--align-right { + justify-content: flex-end; + } + &--density-1 { height: 48px; } diff --git a/src/design-system/stories/MenuItem.stories.tsx b/src/design-system/stories/MenuItem.stories.tsx index a7c89eb08..bd63f803f 100644 --- a/src/design-system/stories/MenuItem.stories.tsx +++ b/src/design-system/stories/MenuItem.stories.tsx @@ -2,12 +2,118 @@ import type { Meta, StoryObj } from '@storybook/react' import { EditRounded } from '@mui/icons-material' import MenuItem from '~scss-components/menu-item/MenuItem' -import { MenuItemColorVariant } from '~scss-components/menu-item/MenuItem.constants' +import { + MenuItemColorVariant, + MenuItemVariant +} from '~scss-components/menu-item/MenuItem.constants' const meta: Meta = { title: 'Components/MenuItem', component: MenuItem, - tags: ['autodocs'] + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: ` +The MenuItem component can be used in a dropdown menu, a list of items. + +#### Key features: +- **Graphics**: An icon or image to be displayed next to the title. +- **Is dropdown**: Whether the menu item is a dropdown. If selected, onRemove will be ignored. +- **On click**: The function to be called when the menu item is clicked. +- **On remove**: The function to be called when the menu item is removed. +- **Customization**: The color, alignment, and density of the menu item can be customized. +` + } + } + }, + argTypes: { + title: { + description: 'The title of the menu item.', + control: { + type: 'text' + } + }, + additionalInfo: { + description: 'Additional information to be displayed next to the title.', + control: { + type: 'text' + } + }, + graphics: { + description: 'An icon or image to be displayed next to the title.', + control: false + }, + alignVariant: { + description: 'The alignment of the menu item.', + control: { + type: 'select', + options: ['left', 'center', 'right'] + } + }, + colorVariant: { + description: 'The color of the menu item.', + control: { + type: 'select', + options: Object.values(MenuItemColorVariant) + } + }, + density: { + description: 'The density of the menu item.', + control: { + type: 'radio' + } + }, + isDropdown: { + description: + 'Whether the menu item is a dropdown. If selected, onRemove will be ignored.', + control: { + type: 'boolean' + } + }, + isToggled: { + description: + 'Indicates whether the menu item is toggled. When selected, the background color will override other colors.', + control: { + type: 'boolean' + } + }, + isBottomBorder: { + description: + 'Whether the menu item has a bottom border. Useful for separating items.', + control: { + type: 'boolean' + } + }, + isDisabled: { + description: 'Whether the menu item is disabled.', + control: { + type: 'boolean' + } + }, + variant: { + description: 'The variant of the menu item.', + control: { + type: 'select', + options: Object.values(MenuItemVariant) + } + }, + onClick: { + description: 'The function to be called when the menu item is clicked.' + } + }, + args: { + title: 'Assigment', + onClick: () => alert('Item was clicked.'), + density: 1, + alignVariant: 'left', + colorVariant: MenuItemColorVariant.Default, + isDropdown: false, + isToggled: false, + isBottomBorder: false, + isDisabled: false, + variant: MenuItemVariant.Default + } } export default meta @@ -18,6 +124,13 @@ export const Default: Story = { args: { title: 'Assigment', onClick: () => alert('Item "Default" was clicked.') + }, + parameters: { + docs: { + description: { + story: 'The most simple menu item with onClick.' + } + } } } @@ -26,6 +139,14 @@ export const WithGraphics: Story = { title: 'Assigment', graphics: , onClick: () => alert('Item "With Graphics" was clicked.') + }, + parameters: { + docs: { + description: { + story: + 'The menu item with an icon. It is possible to use text, icons, checkboxes, other nodes instead of the icon.' + } + } } } @@ -36,6 +157,14 @@ export const WithAdditionalInfo: Story = { additionalInfo: 'Due in 2 days', density: 2, onClick: () => alert('Item "With Additional Info" was clicked.') + }, + parameters: { + docs: { + description: { + story: + 'The menu item with additional information. It is recommended to use the density prop with a value of 2 to enhance the visibility of the additional information.' + } + } } } @@ -45,33 +174,14 @@ export const WithDropdown: Story = { graphics: , isDropdown: true, onClick: () => alert('Imagine dropdown was expanded.') - } -} - -export const Toggled: Story = { - args: { - title: 'Assigment', - graphics: , - isToggled: true, - onClick: () => alert('Item "Toggled" was clicked.') - } -} - -export const WithBottomBorder: Story = { - args: { - title: 'Assigment', - graphics: , - isBottomBorder: true, - onClick: () => alert('Item "With Bottom Border" was clicked.') - } -} - -export const Disabled: Story = { - args: { - title: 'Assigment', - graphics: , - isDisabled: true, - onClick: () => alert('Item "Disabled" was clicked.') + }, + parameters: { + docs: { + description: { + story: + 'The menu item with a dropdown icon. Set onClick to handle the dropdown expansion.' + } + } } } @@ -81,6 +191,14 @@ export const RemovableMenuItem: Story = { graphics: , onClick: () => alert('Item "With Remove" was clicked.'), onRemove: () => alert('Imagine this item was removed') + }, + parameters: { + docs: { + description: { + story: + 'A menu item with a defined onRemove handler. Click the close icon in the right corner to remove the item. The onRemove handler will be ignored when isDropdown is true.' + } + } } } From b61c66671d92d1cff7671861d2f4ce4d622940f5 Mon Sep 17 00:00:00 2001 From: luiqor Date: Mon, 16 Dec 2024 11:24:12 +0200 Subject: [PATCH 27/41] fix MenuItem checkbox --- .../components/menu-item/MenuItem.tsx | 31 +++++++++++++++++-- .../stories/MenuItem.stories.tsx | 17 ++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index bcff05940..298ebc385 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { FC, useRef } from 'react' import { MenuItem as MuiMenuItem } from '@mui/material' import { ArrowDropDown, ArrowDropUp, CloseRounded } from '@mui/icons-material' @@ -32,7 +32,26 @@ const MenuItem: FC = ({ variant = MenuItemVariant.Default, onClick = () => {} }) => { + const graphicsRef = useRef(null) + const handleClick = () => { + if (graphicsRef.current) { + const interactiveElement = + graphicsRef.current.querySelector( + 'input[type="radio"], input[type="checkbox"], [role="radio"], [role="checkbox"]' + ) + + if (interactiveElement) { + interactiveElement.click() + return + } + } + + onClick() + } + + const handleGraphicsClick = (event: React.MouseEvent) => { + event.stopPropagation() onClick() } @@ -53,7 +72,15 @@ const MenuItem: FC = ({ onClick={handleClick} >
- {graphics &&
{graphics}
} + {graphics && ( +
+ {graphics} +
+ )}
{additionalInfo} {title} diff --git a/src/design-system/stories/MenuItem.stories.tsx b/src/design-system/stories/MenuItem.stories.tsx index bd63f803f..5eee574d0 100644 --- a/src/design-system/stories/MenuItem.stories.tsx +++ b/src/design-system/stories/MenuItem.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react' +import { Checkbox } from '@mui/material' import { EditRounded } from '@mui/icons-material' import MenuItem from '~scss-components/menu-item/MenuItem' @@ -150,6 +151,22 @@ export const WithGraphics: Story = { } } +export const WithCheckbox: Story = { + args: { + title: 'Assigment', + graphics: , + onClick: () => alert('Item "With Graphics" was clicked.') + }, + parameters: { + docs: { + description: { + story: + 'The menu item with a checkbox. The checkbox is interactive and can be toggled.' + } + } + } +} + export const WithAdditionalInfo: Story = { args: { title: 'Assigment', From bc6b9b7931b93cd3bcd9cc6f6aa11990d44b1c9a Mon Sep 17 00:00:00 2001 From: luiqor Date: Mon, 16 Dec 2024 13:23:15 +0200 Subject: [PATCH 28/41] add initialy toggled functionallity --- src/design-system/components/menu/Menu.tsx | 38 +++++++++++++++++----- src/design-system/stories/Menu.stories.tsx | 17 +++++----- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 0ac4f7726..ea058aa0d 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -18,6 +18,7 @@ const defaultNoItemsMessage = 'No items.' interface NestedMenuItemProps extends CommonMenuItemProps { defaultOnItemClickArgs?: OnItemClickArgs + isInitiallyToggled?: boolean } interface MenuItemProps extends NestedMenuItemProps { @@ -29,6 +30,7 @@ interface MenuProps { anchorEl: HTMLElement | null setAnchorEl: (anchorEl: HTMLElement | null) => void menuItems: MenuItemProps[] + allowToggleMultipleItems?: boolean anchorOrigin?: PopoverOrigin density?: 1 | 2 defaultOnItemClick?: (args: OnItemClickArgs) => void @@ -37,6 +39,7 @@ interface MenuProps { maxHeight?: number minWidth?: number transformOrigin?: PopoverOrigin + toggledItemsTitles: string[] } const Menu: FC = ({ @@ -48,13 +51,29 @@ const Menu: FC = ({ minWidth, noItemsMessage, density = 1, + allowToggleMultipleItems = false, isItemsRemovalEnabled = false, ...menuProps }: MenuProps) => { const [items, setItems] = useState(menuItems) - const [toggledItem, setToggledItem] = useState(null) + const [toggledItemsTitles, setToggledItemsTitles] = useState( + allowToggleMultipleItems + ? menuItems + .filter((item) => item.isInitiallyToggled) + .map((item) => item.title) + : [] + ) + + const toggleItem = (itemTitle: string) => { + setToggledItemsTitles((previousItems) => + previousItems.includes(itemTitle) + ? previousItems.filter((i) => i !== itemTitle) + : [...previousItems, itemTitle] + ) + } const handleMenuClose = useCallback(() => { + setToggledItemsTitles([]) setAnchorEl(null) }, [setAnchorEl]) @@ -65,10 +84,9 @@ const Menu: FC = ({ nestedMenuItems, onClick: customOnClick }: MenuItemProps) => { + toggleItem(title) + if ((!customOnClick && !defaultOnItemClick) || nestedMenuItems) { - setToggledItem((previousTitle) => - previousTitle === title ? null : title - ) return } @@ -84,10 +102,14 @@ const Menu: FC = ({ defaultOnItemClick(args) } - setToggledItem(null) + if (allowToggleMultipleItems) { + return + } + handleMenuClose() }, - [defaultOnItemClick, handleMenuClose] + + [defaultOnItemClick, handleMenuClose, allowToggleMultipleItems] ) const handleItemRemoval = (title: string) => { @@ -114,7 +136,7 @@ const Menu: FC = ({ {...item} density={density} isDropdown={Boolean(item.nestedMenuItems)} - isToggled={toggledItem === item.title} + isToggled={toggledItemsTitles.includes(item.title)} key={item.title} onClick={() => handleItemClick(item)} onRemove={ @@ -123,7 +145,7 @@ const Menu: FC = ({ : undefined } />, - ...(item.nestedMenuItems && toggledItem === item.title + ...(item.nestedMenuItems && toggledItemsTitles.includes(item.title) ? item.nestedMenuItems.map((nestedMenuItem) => ( , - defaultOnItemClickArgs: { path: '/lesson' } + graphics: }, { title: 'Quiz', - graphics: , - defaultOnItemClickArgs: { path: '/quiz' } + graphics: }, { title: 'Attachment', - graphics: , - defaultOnItemClickArgs: { path: '/attachment' } + graphics: } ] }, parameters: { docs: { description: { - story: 'The dropdown menu with checkboxes. Click on the button to open.' + story: + 'The dropdown menu with checkboxes. Click on the button to open the menu.' } } } From accdc9a5c85249baa6048946aade323fad0cd32f Mon Sep 17 00:00:00 2001 From: luiqor Date: Mon, 16 Dec 2024 13:30:31 +0200 Subject: [PATCH 29/41] add toggleAsSingleItem --- src/design-system/components/menu/Menu.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index ea058aa0d..5e9bdbbb9 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -64,7 +64,13 @@ const Menu: FC = ({ : [] ) - const toggleItem = (itemTitle: string) => { + const toggleAsSingleItem = (itemTitle: string) => { + setToggledItemsTitles((previousItems) => + previousItems.includes(itemTitle) ? [] : [itemTitle] + ) + } + + const toggleAsOneOfMultipleItems = (itemTitle: string) => { setToggledItemsTitles((previousItems) => previousItems.includes(itemTitle) ? previousItems.filter((i) => i !== itemTitle) @@ -84,7 +90,9 @@ const Menu: FC = ({ nestedMenuItems, onClick: customOnClick }: MenuItemProps) => { - toggleItem(title) + allowToggleMultipleItems + ? toggleAsOneOfMultipleItems(title) + : toggleAsSingleItem(title) if ((!customOnClick && !defaultOnItemClick) || nestedMenuItems) { return From 032f6044bab0f239613daf5ecf557ade5d675be7 Mon Sep 17 00:00:00 2001 From: luiqor Date: Mon, 16 Dec 2024 14:09:08 +0200 Subject: [PATCH 30/41] add menu items removal tests --- src/design-system/components/menu/Menu.tsx | 1 - .../components/menu/Menu.spec.jsx | 61 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/unit/design-system/components/menu/Menu.spec.jsx diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 5e9bdbbb9..a4a1d1aec 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -39,7 +39,6 @@ interface MenuProps { maxHeight?: number minWidth?: number transformOrigin?: PopoverOrigin - toggledItemsTitles: string[] } const Menu: FC = ({ diff --git a/tests/unit/design-system/components/menu/Menu.spec.jsx b/tests/unit/design-system/components/menu/Menu.spec.jsx new file mode 100644 index 000000000..39ee32eba --- /dev/null +++ b/tests/unit/design-system/components/menu/Menu.spec.jsx @@ -0,0 +1,61 @@ +import { render, fireEvent, screen, waitFor } from '@testing-library/react' +import Menu from '~scss-components/menu/Menu' + +const resourcesMenuItems = [ + { title: 'Lesson' }, + { title: 'Quiz' }, + { title: 'Attachment' } +] + +const noItemsCustomMessage = 'No items available.' + +describe('Menu Component', () => { + test('should render menu items', () => { + render( + {}} + menuItems={resourcesMenuItems} + toggledItemsTitles={[]} + /> + ) + + expect(screen.getByText('Lesson')).toBeInTheDocument() + expect(screen.getByText('Quiz')).toBeInTheDocument() + expect(screen.getByText('Attachment')).toBeInTheDocument() + }) + + test('should remove item when removal is enabled', async () => { + const { getByText } = render( + {}} + menuItems={resourcesMenuItems} + isItemsRemovalEnabled={true} + /> + ) + + const removeButton = getByText('Clear all') + fireEvent.click(removeButton) + + await waitFor(() => { + expect(screen.queryByText('Lesson')).not.toBeInTheDocument() + expect(screen.queryByText('Quiz')).not.toBeInTheDocument() + expect(screen.queryByText('Attachment')).not.toBeInTheDocument() + }) + }) + + test('should show no items message when no items left and removal is enabled', () => { + render( + {}} + menuItems={[]} + noItemsMessage={noItemsCustomMessage} + isItemsRemovalEnabled={true} + /> + ) + + expect(screen.getByText(noItemsCustomMessage)).toBeInTheDocument() + }) +}) From 83faba9c5cc135f5f75bdca45b262d571661921b Mon Sep 17 00:00:00 2001 From: luiqor Date: Mon, 16 Dec 2024 20:34:14 +0200 Subject: [PATCH 31/41] add Menu component tests --- .../components/menu/Menu.spec.jsx | 157 +++++++++++++++++- 1 file changed, 150 insertions(+), 7 deletions(-) diff --git a/tests/unit/design-system/components/menu/Menu.spec.jsx b/tests/unit/design-system/components/menu/Menu.spec.jsx index 39ee32eba..35773880c 100644 --- a/tests/unit/design-system/components/menu/Menu.spec.jsx +++ b/tests/unit/design-system/components/menu/Menu.spec.jsx @@ -7,6 +7,31 @@ const resourcesMenuItems = [ { title: 'Attachment' } ] +const resourcesMenuItemsWithCustomArgs = [ + { title: 'Lesson', defaultOnItemClickArgs: { path: '/lesson' } } +] + +const resourcesMenuItemsWithNestedItems = [ + { title: 'Lesson', nestedMenuItems: [{ title: 'Art' }, { title: 'Math' }] } +] + +const resourcesMenuItemsWithAdditionalInfo = [ + { title: 'Lesson', additionalInfo: 'This is a lesson' } +] + +const circleIcon = ( + + + +) + +const resourcesMenuItemsWithIcon = [{ title: 'Lesson', graphics: circleIcon }] + const noItemsCustomMessage = 'No items available.' describe('Menu Component', () => { @@ -16,7 +41,6 @@ describe('Menu Component', () => { anchorEl={document.createElement('div')} setAnchorEl={() => {}} menuItems={resourcesMenuItems} - toggledItemsTitles={[]} /> ) @@ -25,7 +49,7 @@ describe('Menu Component', () => { expect(screen.getByText('Attachment')).toBeInTheDocument() }) - test('should remove item when removal is enabled', async () => { + test('should remove item when removal is enabled', () => { const { getByText } = render( { const removeButton = getByText('Clear all') fireEvent.click(removeButton) - await waitFor(() => { - expect(screen.queryByText('Lesson')).not.toBeInTheDocument() - expect(screen.queryByText('Quiz')).not.toBeInTheDocument() - expect(screen.queryByText('Attachment')).not.toBeInTheDocument() - }) + expect(screen.queryByText('Lesson')).toBeNull() + expect(screen.queryByText('Quiz')).toBeNull() + expect(screen.queryByText('Attachment')).toBeNull() }) test('should show no items message when no items left and removal is enabled', () => { @@ -58,4 +80,125 @@ describe('Menu Component', () => { expect(screen.getByText(noItemsCustomMessage)).toBeInTheDocument() }) + + test('should set anchor element when clicking the button', () => { + const setAnchorEl = vi.fn() + const handleClick = vi.fn((event) => setAnchorEl(event.currentTarget)) + + render( +
+ + +
+ ) + + const button = screen.getByText('Open Menu') + fireEvent.click(button) + expect(handleClick).toHaveBeenCalled() + expect(setAnchorEl).toHaveBeenCalledWith(button) + }) + + test('should trigger defaultOnItemClick with default arguments when a menu item is clicked', () => { + const defaultOnItemClick = vi.fn() + render( + {}} + menuItems={resourcesMenuItems} + defaultOnItemClick={defaultOnItemClick} + /> + ) + + const lesson = screen.getByText('Lesson') + fireEvent.click(lesson) + expect(defaultOnItemClick).toHaveBeenCalledWith({ title: 'Lesson' }) + }) + + test('should trigger defaultOnItemClick with custom arguments when a menu item is clicked', () => { + const defaultOnItemClick = vi.fn() + render( + {}} + menuItems={resourcesMenuItemsWithCustomArgs} + defaultOnItemClick={defaultOnItemClick} + /> + ) + + const lesson = screen.getByText('Lesson') + fireEvent.click(lesson) + expect(defaultOnItemClick).toHaveBeenCalledWith({ + title: 'Lesson', + path: '/lesson' + }) + }) + + test('should trigger defaultOnItemClick default arguments when a nested menu item is clicked', () => { + const defaultOnItemClick = vi.fn() + render( + {}} + menuItems={resourcesMenuItemsWithNestedItems} + defaultOnItemClick={defaultOnItemClick} + /> + ) + + const lesson = screen.getByText('Lesson') + fireEvent.click(lesson) + + const art = screen.getByText('Art') + fireEvent.click(art) + expect(defaultOnItemClick).toHaveBeenCalledWith({ title: 'Art' }) + }) + + test('should show nested items when a menu item is clicked', () => { + render( + {}} + menuItems={resourcesMenuItemsWithNestedItems} + /> + ) + + const lesson = screen.getByText('Lesson') + fireEvent.click(lesson) + + expect(screen.getByText('Art')).toBeInTheDocument() + expect(screen.getByText('Math')).toBeInTheDocument() + }) + + test('should display additional info when it is provided', () => { + render( + {}} + menuItems={resourcesMenuItemsWithAdditionalInfo} + /> + ) + + const lesson = screen.getByText('Lesson') + fireEvent.click(lesson) + + expect(screen.getByText('This is a lesson')).toBeInTheDocument() + }) + + test('should display icon when it is provided', () => { + render( + {}} + menuItems={resourcesMenuItemsWithIcon} + /> + ) + + const lesson = screen.getByText('Lesson') + fireEvent.click(lesson) + + expect(screen.getByTestId('lesson-icon')).toBeInTheDocument() + }) }) From 6323fe7f4c639f78c80e9b9225722a6fbddee30b Mon Sep 17 00:00:00 2001 From: luiqor Date: Mon, 16 Dec 2024 21:30:05 +0200 Subject: [PATCH 32/41] add menu item tests --- .../components/menu-item/MenuItem.spec.jsx | 74 +++++++++++++++++++ .../components/menu/Menu.spec.jsx | 17 +---- 2 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 tests/unit/design-system/components/menu-item/MenuItem.spec.jsx diff --git a/tests/unit/design-system/components/menu-item/MenuItem.spec.jsx b/tests/unit/design-system/components/menu-item/MenuItem.spec.jsx new file mode 100644 index 000000000..5d2368c89 --- /dev/null +++ b/tests/unit/design-system/components/menu-item/MenuItem.spec.jsx @@ -0,0 +1,74 @@ +import { render, fireEvent, screen } from '@testing-library/react' +import MenuItem from '~scss-components/menu-item/MenuItem' + +const resourceMenuItemTitle = 'Lesson' + +const icon = + +const checkbox = + +const additionalInfo = 'This is a lesson' + +describe('MenuItem Component', () => { + test('should render menu item', () => { + render() + expect(screen.getByText(resourceMenuItemTitle)).toBeInTheDocument() + }) + + test('should call onClick when menu item is clicked', () => { + const onClick = vi.fn() + render() + fireEvent.click(screen.getByText(resourceMenuItemTitle)) + expect(onClick).toHaveBeenCalled() + }) + + test('should render svg graphics', () => { + render() + expect(screen.getByTestId('lesson-icon')).toBeInTheDocument() + }) + + test('should render checkbox', () => { + render() + expect(screen.getByRole('checkbox')).toBeInTheDocument() + }) + + test('should check checkbox when menu item clicked', () => { + render() + fireEvent.click(screen.getByText(resourceMenuItemTitle)) + expect(screen.getByRole('checkbox')).toBeChecked() + }) + + test('should display close button when removal is enabled', () => { + const { container } = render( + {}} /> + ) + const closeButton = container.querySelector('.s2s-item__close') + + expect(closeButton).toBeInTheDocument() + }) + + test('should not display close button when dropdown is enabled', () => { + const { container } = render( + {}} /> + ) + const closeButton = container.querySelector('.s2s-item__close') + + expect(closeButton).not.toBeInTheDocument() + }) + + test('should call onRemove when close button is clicked', () => { + const onRemove = vi.fn() + const { container } = render( + + ) + fireEvent.click(container.querySelector('.s2s-item__close')) + expect(onRemove).toHaveBeenCalled() + }) + + test('should render additional info', () => { + render( + + ) + expect(screen.getByText(additionalInfo)).toBeInTheDocument() + }) +}) diff --git a/tests/unit/design-system/components/menu/Menu.spec.jsx b/tests/unit/design-system/components/menu/Menu.spec.jsx index 35773880c..2db05eb6e 100644 --- a/tests/unit/design-system/components/menu/Menu.spec.jsx +++ b/tests/unit/design-system/components/menu/Menu.spec.jsx @@ -1,4 +1,4 @@ -import { render, fireEvent, screen, waitFor } from '@testing-library/react' +import { render, fireEvent, screen } from '@testing-library/react' import Menu from '~scss-components/menu/Menu' const resourcesMenuItems = [ @@ -19,18 +19,9 @@ const resourcesMenuItemsWithAdditionalInfo = [ { title: 'Lesson', additionalInfo: 'This is a lesson' } ] -const circleIcon = ( - - - -) - -const resourcesMenuItemsWithIcon = [{ title: 'Lesson', graphics: circleIcon }] +const lessonIcon = + +const resourcesMenuItemsWithIcon = [{ title: 'Lesson', graphics: lessonIcon }] const noItemsCustomMessage = 'No items available.' From 2518256e4d685bd586b6a41452c00d271c903890 Mon Sep 17 00:00:00 2001 From: luiqor Date: Mon, 16 Dec 2024 22:49:56 +0200 Subject: [PATCH 33/41] add missing argTypes to storybook docs --- .../components/menu-item/MenuItem.tsx | 1 - .../components/menu-item/MenuItem.types.ts | 1 + src/design-system/components/menu/Menu.tsx | 1 - src/design-system/stories/Menu.stories.tsx | 24 +++++++++++++++++-- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 298ebc385..b9d154824 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -9,7 +9,6 @@ import { cn } from '~/utils/cn' import '~scss-components/menu-item/MenuItem.scss' interface MenuItemProps extends CommonMenuItemProps { - additionalInfo?: string isDropdown?: boolean density?: 1 | 2 isToggled?: boolean diff --git a/src/design-system/components/menu-item/MenuItem.types.ts b/src/design-system/components/menu-item/MenuItem.types.ts index c51d9240a..588ae14a6 100644 --- a/src/design-system/components/menu-item/MenuItem.types.ts +++ b/src/design-system/components/menu-item/MenuItem.types.ts @@ -5,6 +5,7 @@ export type OnItemClickArgs = Record export interface MenuItemProps { title: string + additionalInfo?: string alignVariant?: 'left' | 'center' | 'right' colorVariant?: MenuItemColorVariant isDisabled?: boolean diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index a4a1d1aec..55eded02e 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -22,7 +22,6 @@ interface NestedMenuItemProps extends CommonMenuItemProps { } interface MenuItemProps extends NestedMenuItemProps { - additionalInfo?: string nestedMenuItems?: NestedMenuItemProps[] } diff --git a/src/design-system/stories/Menu.stories.tsx b/src/design-system/stories/Menu.stories.tsx index 7ae7f110e..3d99c0cc1 100644 --- a/src/design-system/stories/Menu.stories.tsx +++ b/src/design-system/stories/Menu.stories.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import type { Meta, StoryObj } from '@storybook/react' +import { type Meta, type StoryObj } from '@storybook/react' import { EditRounded } from '@mui/icons-material' import { Checkbox } from '@mui/material' @@ -34,9 +34,29 @@ The Menu component is a dropdown menu that displays a list of items. description: 'Function to set the anchor element.' }, menuItems: { - description: 'The list of menu items to be displayed.', + description: ` +The list of menu items to be displayed. Each menu item can have the following properties: +- **title***: \`string\` +- **additionalInfo**: \`string\` +- **graphics**: \`ReactNode\` +- **alignVariant**: \`'left' | 'center' | 'right'\` +- **colorVariant**: \`MenuItemColorVariant\` +- **defaultOnItemClickArgs**: \`OnItemClickArgs\` +- **isBottomBorder**: \`boolean\` +- **isDisabled**: \`boolean\` +- **isInitiallyToggled**: \`boolean\` +- **nestedMenuItems**: \`MenuItemProps[]\` +- **onClick**: \`function\` + +Nested menu items can have all the same properties except \`nestedMenuItems\`. + `, control: { type: 'object' } }, + allowToggleMultipleItems: { + description: + 'Determines whether multiple items can be toggled on and off.', + control: 'boolean' + }, anchorOrigin: { description: 'The anchor origin point of the menu.', control: { type: 'object' } From 45347c27d5cf370d1fbdde95f3d84cefb3386b27 Mon Sep 17 00:00:00 2001 From: luiqor Date: Tue, 17 Dec 2024 10:38:55 +0200 Subject: [PATCH 34/41] fix elements with click handlers must have at least one keyboard listener --- .../components/menu-item/MenuItem.tsx | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index b9d154824..dcf7f73bc 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -19,17 +19,17 @@ interface MenuItemProps extends CommonMenuItemProps { const MenuItem: FC = ({ title, additionalInfo, - graphics, - onRemove, alignVariant = 'left', colorVariant = MenuItemColorVariant.Default, density = 1, + graphics, isDropdown = false, isToggled = false, isBottomBorder = false, isDisabled = false, - variant = MenuItemVariant.Default, - onClick = () => {} + onClick, + onRemove, + variant = MenuItemVariant.Default }) => { const graphicsRef = useRef(null) @@ -46,12 +46,28 @@ const MenuItem: FC = ({ } } - onClick() + onClick?.() + } + + const handleGraphicsClick = ( + event: React.MouseEvent | React.KeyboardEvent + ) => { + event.stopPropagation() + onClick?.() + } + + const handleEnterOrSpaceKeyDown = ( + event: React.KeyboardEvent, + handleAction: (event: React.KeyboardEvent) => void + ) => { + if (event.key === 'Enter' || event.key === ' ') { + handleAction(event) + } } - const handleGraphicsClick = (event: React.MouseEvent) => { + const handleRemoveItem = (event: React.MouseEvent | React.KeyboardEvent) => { event.stopPropagation() - onClick() + onRemove?.() } return ( @@ -75,7 +91,10 @@ const MenuItem: FC = ({
{graphics}
@@ -93,10 +112,12 @@ const MenuItem: FC = ({ {onRemove && !isDropdown && !isDisabled && (
{ - event.stopPropagation() - onRemove() + onClick={handleRemoveItem} + onKeyDown={(event) => { + handleEnterOrSpaceKeyDown(event, handleRemoveItem) }} + role='button' + tabIndex={0} >
From 5fb9b1459167c5d3a7d09da2e9d21aa45ac8bcc6 Mon Sep 17 00:00:00 2001 From: luiqor Date: Tue, 17 Dec 2024 11:00:17 +0200 Subject: [PATCH 35/41] fix additional info line height --- src/design-system/components/menu-item/MenuItem.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index 75f941585..bf195a1c5 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -90,6 +90,7 @@ &__additional-info { font-size: $font-size-xs; color: $blue-gray-500; + line-height: $line-height-sm; } &__main-info-box { From f3ff7033963a727b22ba17b7d60d7590a133e422 Mon Sep 17 00:00:00 2001 From: luiqor Date: Tue, 17 Dec 2024 11:15:09 +0200 Subject: [PATCH 36/41] fix role button sonar issues --- src/design-system/components/menu-item/MenuItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index dcf7f73bc..96d6ce351 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -93,7 +93,6 @@ const MenuItem: FC = ({ onClick={handleGraphicsClick} onKeyDown={handleClick} ref={graphicsRef} - role='button' tabIndex={0} > {graphics} @@ -116,7 +115,6 @@ const MenuItem: FC = ({ onKeyDown={(event) => { handleEnterOrSpaceKeyDown(event, handleRemoveItem) }} - role='button' tabIndex={0} > From 5675a2668c84c42bb86037da3b8f967897cdde78 Mon Sep 17 00:00:00 2001 From: luiqor Date: Tue, 17 Dec 2024 11:22:08 +0200 Subject: [PATCH 37/41] fix tabIndex on graphics divs sonar issues --- src/design-system/components/menu-item/MenuItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 96d6ce351..4c45a3628 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -93,7 +93,6 @@ const MenuItem: FC = ({ onClick={handleGraphicsClick} onKeyDown={handleClick} ref={graphicsRef} - tabIndex={0} > {graphics}
@@ -115,7 +114,6 @@ const MenuItem: FC = ({ onKeyDown={(event) => { handleEnterOrSpaceKeyDown(event, handleRemoveItem) }} - tabIndex={0} >
From 0782dc2de0b42c3c567f14059f964a12163841d4 Mon Sep 17 00:00:00 2001 From: luiqor Date: Tue, 17 Dec 2024 11:33:25 +0200 Subject: [PATCH 38/41] refactor divs to buttons to fix sonar issues --- .../components/menu-item/MenuItem.scss | 2 ++ .../components/menu-item/MenuItem.tsx | 32 ++++--------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.scss b/src/design-system/components/menu-item/MenuItem.scss index bf195a1c5..ccceb6c02 100644 --- a/src/design-system/components/menu-item/MenuItem.scss +++ b/src/design-system/components/menu-item/MenuItem.scss @@ -78,6 +78,8 @@ display: flex; align-items: center; justify-content: center; + background: none; + border: none; } &__text-box { diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index 4c45a3628..c205e6f83 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -31,7 +31,7 @@ const MenuItem: FC = ({ onRemove, variant = MenuItemVariant.Default }) => { - const graphicsRef = useRef(null) + const graphicsRef = useRef(null) const handleClick = () => { if (graphicsRef.current) { @@ -49,23 +49,12 @@ const MenuItem: FC = ({ onClick?.() } - const handleGraphicsClick = ( - event: React.MouseEvent | React.KeyboardEvent - ) => { + const handleGraphicsClick = (event: React.MouseEvent) => { event.stopPropagation() onClick?.() } - const handleEnterOrSpaceKeyDown = ( - event: React.KeyboardEvent, - handleAction: (event: React.KeyboardEvent) => void - ) => { - if (event.key === 'Enter' || event.key === ' ') { - handleAction(event) - } - } - - const handleRemoveItem = (event: React.MouseEvent | React.KeyboardEvent) => { + const handleRemoveItem = (event: React.MouseEvent) => { event.stopPropagation() onRemove?.() } @@ -88,14 +77,13 @@ const MenuItem: FC = ({ >
{graphics && ( -
{graphics} -
+ )}
{additionalInfo} @@ -108,15 +96,9 @@ const MenuItem: FC = ({
)} {onRemove && !isDropdown && !isDisabled && ( -
{ - handleEnterOrSpaceKeyDown(event, handleRemoveItem) - }} - > +
+ )} ) From e2cf5e4a31421830b4e2b8c2cf6c49d5cd76858d Mon Sep 17 00:00:00 2001 From: luiqor Date: Tue, 17 Dec 2024 11:41:57 +0200 Subject: [PATCH 39/41] refactor defaultNoItemsMessage --- src/design-system/components/menu/Menu.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 55eded02e..477faf0b3 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -47,7 +47,7 @@ const Menu: FC = ({ defaultOnItemClick, maxHeight, minWidth, - noItemsMessage, + noItemsMessage = defaultNoItemsMessage, density = 1, allowToggleMultipleItems = false, isItemsRemovalEnabled = false, @@ -172,10 +172,7 @@ const Menu: FC = ({ title={removeAllItemsTitle} /> ) : ( - + ))} ) From d54af8e1514cd271b059c6d16cac1f6e9db097b8 Mon Sep 17 00:00:00 2001 From: luiqor Date: Wed, 18 Dec 2024 10:59:50 +0200 Subject: [PATCH 40/41] refactor small code issues --- .../components/menu-item/MenuItem.tsx | 1 - src/design-system/components/menu/Menu.tsx | 4 +-- .../components/menu/Menu.spec.jsx | 31 ++++++++++++------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/design-system/components/menu-item/MenuItem.tsx b/src/design-system/components/menu-item/MenuItem.tsx index c205e6f83..bdb91f0f4 100644 --- a/src/design-system/components/menu-item/MenuItem.tsx +++ b/src/design-system/components/menu-item/MenuItem.tsx @@ -72,7 +72,6 @@ const MenuItem: FC = ({ isDisabled && 's2s-item--disabled' )} disabled={isDisabled} - key={title} onClick={handleClick} >
diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 477faf0b3..51e902ce6 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -53,7 +53,7 @@ const Menu: FC = ({ isItemsRemovalEnabled = false, ...menuProps }: MenuProps) => { - const [items, setItems] = useState(menuItems) + const [items, setItems] = useState(menuItems) const [toggledItemsTitles, setToggledItemsTitles] = useState( allowToggleMultipleItems ? menuItems @@ -151,7 +151,7 @@ const Menu: FC = ({ : undefined } />, - ...(item.nestedMenuItems && toggledItemsTitles.includes(item.title) + ...(toggledItemsTitles.includes(item.title) && item.nestedMenuItems ? item.nestedMenuItems.map((nestedMenuItem) => ( { + let anchor + + beforeEach(() => { + render( -
+ ) const button = screen.getByText('Open Menu') @@ -97,7 +104,7 @@ describe('Menu Component', () => { const defaultOnItemClick = vi.fn() render( {}} menuItems={resourcesMenuItems} defaultOnItemClick={defaultOnItemClick} @@ -113,7 +120,7 @@ describe('Menu Component', () => { const defaultOnItemClick = vi.fn() render( {}} menuItems={resourcesMenuItemsWithCustomArgs} defaultOnItemClick={defaultOnItemClick} @@ -132,7 +139,7 @@ describe('Menu Component', () => { const defaultOnItemClick = vi.fn() render( {}} menuItems={resourcesMenuItemsWithNestedItems} defaultOnItemClick={defaultOnItemClick} @@ -150,7 +157,7 @@ describe('Menu Component', () => { test('should show nested items when a menu item is clicked', () => { render( {}} menuItems={resourcesMenuItemsWithNestedItems} /> @@ -166,7 +173,7 @@ describe('Menu Component', () => { test('should display additional info when it is provided', () => { render( {}} menuItems={resourcesMenuItemsWithAdditionalInfo} /> @@ -181,7 +188,7 @@ describe('Menu Component', () => { test('should display icon when it is provided', () => { render( {}} menuItems={resourcesMenuItemsWithIcon} /> From 7d4eedbc671257e2a02e29bb713229c63084e850 Mon Sep 17 00:00:00 2001 From: luiqor Date: Wed, 18 Dec 2024 17:53:12 +0200 Subject: [PATCH 41/41] remove redundant usage of useCallbacks --- src/design-system/components/menu/Menu.tsx | 70 ++++++++++------------ 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/src/design-system/components/menu/Menu.tsx b/src/design-system/components/menu/Menu.tsx index 51e902ce6..63cce10ad 100644 --- a/src/design-system/components/menu/Menu.tsx +++ b/src/design-system/components/menu/Menu.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useState } from 'react' +import { FC, useState } from 'react' import { Menu as MuiMenu, PopoverOrigin } from '@mui/material' import { @@ -76,47 +76,43 @@ const Menu: FC = ({ ) } - const handleMenuClose = useCallback(() => { + const handleMenuClose = () => { setToggledItemsTitles([]) setAnchorEl(null) - }, [setAnchorEl]) - - const handleItemClick = useCallback( - ({ - title, - defaultOnItemClickArgs, - nestedMenuItems, - onClick: customOnClick - }: MenuItemProps) => { - allowToggleMultipleItems - ? toggleAsOneOfMultipleItems(title) - : toggleAsSingleItem(title) - - if ((!customOnClick && !defaultOnItemClick) || nestedMenuItems) { - return - } - - if (customOnClick) { - customOnClick() - handleMenuClose() - } else if (defaultOnItemClick) { - const args: OnItemClickArgs = - defaultOnItemClickArgs === undefined - ? { title } - : { title, ...defaultOnItemClickArgs } - - defaultOnItemClick(args) - } - - if (allowToggleMultipleItems) { - return - } + } + + const handleItemClick = ({ + title, + defaultOnItemClickArgs, + nestedMenuItems, + onClick: customOnClick + }: MenuItemProps) => { + allowToggleMultipleItems + ? toggleAsOneOfMultipleItems(title) + : toggleAsSingleItem(title) + + if ((!customOnClick && !defaultOnItemClick) || nestedMenuItems) { + return + } + if (customOnClick) { + customOnClick() handleMenuClose() - }, + } else if (defaultOnItemClick) { + const args: OnItemClickArgs = + defaultOnItemClickArgs === undefined + ? { title } + : { title, ...defaultOnItemClickArgs } - [defaultOnItemClick, handleMenuClose, allowToggleMultipleItems] - ) + defaultOnItemClick(args) + } + + if (allowToggleMultipleItems) { + return + } + + handleMenuClose() + } const handleItemRemoval = (title: string) => { setItems((previousItems) =>