-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #48 from code0-tech/menu
Menu component
- Loading branch information
Showing
23 changed files
with
451 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,6 @@ const preview: Preview = { | |
}, | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
export default preview; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,5 @@ | |
.badge--#{$name} { | ||
@include opacityBox(false, $color); | ||
font-size: $tertiaryFontSize; | ||
border: none; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import {AriaMenuProps, useMenu, useMenuItem, useMenuSection} from "react-aria"; | ||
import {Node, useTreeState} from "react-stately"; | ||
import React from "react"; | ||
import {TreeState} from "@react-stately/tree"; | ||
import {IconCheck} from "@tabler/icons-react"; | ||
import "./Menu.style.scss" | ||
import {MenuItemType, MenuSectionType} from "./Menu"; | ||
|
||
export function InternalMenu<T extends object>(props: AriaMenuProps<T>) { | ||
|
||
const dummyState = useTreeState(props); | ||
const disabledKeys = [...dummyState.collection.getKeys()].map(key => dummyState.collection.getItem(key)).filter(item => { | ||
return item?.props.disabled || item?.props.unselectable | ||
}).map(item => item?.key ?? "") | ||
|
||
const state = useTreeState({ | ||
...props, | ||
disabledKeys | ||
}); | ||
|
||
// Get props for the menu element | ||
const ref = React.useRef(null); | ||
const {menuProps} = useMenu({ | ||
...props, | ||
disabledKeys | ||
}, state, ref); | ||
|
||
return ( | ||
<ul {...menuProps} ref={ref} className={"menu"}> | ||
{[...state.collection].map((item) => ( | ||
item.type === 'section' ? <MenuSection key={item.key} section={item} state={state}/> | ||
: <InternalMenuItem key={item.key} item={item} state={state}/> | ||
))} | ||
</ul> | ||
); | ||
} | ||
|
||
function InternalMenuItem<T>({item, state}: {item: Node<T>, state: TreeState<T>}) { | ||
|
||
const {variant = "secondary", disabled = false, unselectable = false} = item.props as MenuItemType | ||
|
||
// Get props for the menu item element | ||
const ref = React.useRef(null); | ||
const {menuItemProps, isSelected} = useMenuItem( | ||
{key: item.key}, | ||
state, | ||
ref | ||
) | ||
|
||
return ( | ||
<li {...(!disabled ? {...menuItemProps} : {})} ref={ref} className={`menu__item menu__item--${variant} ${disabled && "menu__item--disabled"} ${unselectable && "menu__item--unselectable"}`}> | ||
|
||
<div>{item.rendered}</div> | ||
{isSelected && !unselectable ? <IconCheck size={16} style={{marginLeft: ".5rem"}}/> : menuItemProps.role != "menuitem" ? <IconCheck size={16} style={{marginLeft: ".5rem", opacity: 0}}/> : null} | ||
</li> | ||
) | ||
} | ||
|
||
function MenuSection<T>({section, state}: {section: Node<T>, state: TreeState<T>}) { | ||
|
||
const {title} = section.props as MenuSectionType | ||
// Get props for the menu item element | ||
const ref = React.useRef(null); | ||
const { itemProps, headingProps, groupProps } = useMenuSection({ | ||
heading: section.rendered, | ||
'aria-label': section['aria-label'] | ||
}); | ||
|
||
/**const children = [...state.collection.getKeys()].map((value) => { | ||
return state.collection.getItem(value) | ||
}).filter(item => item?.parentKey == section.key) as Node<any>[]**/ | ||
|
||
return <ul {...groupProps} className={"menu__section"}> | ||
{title && <span className={"menu__section-title"}>{title}</span>} | ||
{[...section.childNodes].map((node) => ( | ||
<InternalMenuItem | ||
key={node.key} | ||
item={node} | ||
state={state} | ||
/> | ||
))} | ||
</ul> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import {Meta, StoryObj} from "@storybook/react"; | ||
import React from "react"; | ||
import Button from "../button/Button"; | ||
import {Placement} from "react-aria"; | ||
import Menu from "./Menu"; | ||
import {IconLogout, IconUserCancel, IconUserEdit} from "@tabler/icons-react"; | ||
import Badge from "../badge/Badge"; | ||
|
||
const meta: Meta = { | ||
title: "Menu", | ||
component: Menu, | ||
argTypes: { | ||
placement: { | ||
options: ['left start', 'left end', 'bottom start', 'bottom end', 'top start', 'top end', 'right start', 'right end'], | ||
control: {type: 'radio'}, | ||
} | ||
} | ||
} | ||
|
||
export default meta; | ||
|
||
type MenuStory = StoryObj<{ placement: Placement }> | ||
|
||
export const MenuAccount: MenuStory = { | ||
render: (args) => { | ||
|
||
const {placement} = args | ||
|
||
return <> | ||
<Menu placement={placement} defaultOpen> | ||
<Menu.Trigger> | ||
<Button>Click me</Button> | ||
</Menu.Trigger> | ||
<Menu.Content> | ||
<Menu.Section> | ||
<Menu.Item variant={"info"} unselectable key={"ssd"}> | ||
Storage almost full. You can <br/> | ||
manage your storage in Settings → | ||
</Menu.Item> | ||
</Menu.Section> | ||
<Menu.Section title={"Account Settings"}> | ||
<Menu.Item key={"update-account"}><Menu.Icon><IconUserEdit/></Menu.Icon> Update | ||
Account</Menu.Item> | ||
<Menu.Item variant={"error"} | ||
key={"delete-account"}><Menu.Icon><IconUserCancel/></Menu.Icon> Delete | ||
Account</Menu.Item> | ||
</Menu.Section> | ||
<Menu.Item variant={"warning"} | ||
key="logout"><Menu.Icon><IconLogout/></Menu.Icon> Logout <Menu.Shortcut>⌘Q</Menu.Shortcut></Menu.Item> | ||
</Menu.Content> | ||
</Menu> | ||
|
||
</> | ||
} | ||
} | ||
|
||
export const MenuAccountList: MenuStory = { | ||
render: (args) => { | ||
|
||
const {placement} = args | ||
|
||
return <> | ||
<Menu placement={placement} defaultOpen selectionMode={"multiple"} | ||
defaultSelectedKeys={["[email protected]"]}> | ||
<Menu.Trigger> | ||
<Button>Click me</Button> | ||
</Menu.Trigger> | ||
<Menu.Content> | ||
{ | ||
[{ | ||
mail: "[email protected]", | ||
name: "Nico Sammito" | ||
}, { | ||
mail: "[email protected]", | ||
name: "Niklas van Schrick" | ||
}, { | ||
mail: "[email protected]", | ||
name: "Raphael Götz" | ||
}, { | ||
mail: "[email protected]", | ||
name: "Maximillian Städler" | ||
}].map(item => ( | ||
<Menu.Item key={item.mail}>{item.name} <Badge | ||
style={{marginLeft: ".5rem"}}>{item.mail}</Badge></Menu.Item> | ||
)) | ||
} | ||
</Menu.Content> | ||
</Menu> | ||
|
||
</> | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
@import "src/styles/helpers"; | ||
|
||
.menu { | ||
|
||
list-style: none; | ||
margin: -.25rem 0; | ||
padding: 0; | ||
outline: none; | ||
|
||
> *:first-child.menu__section { | ||
border-top: none; | ||
margin-top: 0; | ||
padding-top: 0; | ||
} | ||
|
||
> *:last-child.menu__section { | ||
border-bottom: none; | ||
margin-bottom: 0; | ||
padding-bottom: 0; | ||
} | ||
|
||
&__item { | ||
@include disabled(); | ||
border: none !important; | ||
margin: 0 -.25rem; | ||
border-radius: .5rem; | ||
padding: .5rem; | ||
cursor: pointer; | ||
position: relative; | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
|
||
> div { | ||
position: relative; | ||
display: flex; | ||
width: 100%; | ||
align-items: center; | ||
} | ||
|
||
} | ||
|
||
&__section { | ||
border-top: 1px solid borderColor(); | ||
border-bottom: 1px solid borderColor(); | ||
list-style: none; | ||
margin: .25rem -.5rem; | ||
padding: .25rem .5rem; | ||
outline: none; | ||
|
||
+ .menu__section { | ||
border-top: none; | ||
margin-top: -.25rem; | ||
} | ||
} | ||
|
||
&__section-title { | ||
font-size: $tertiaryFontSize; | ||
color: rgba($white, .25); | ||
display: block; | ||
margin: .25rem 0 .25rem .25rem; | ||
} | ||
|
||
&__icon { | ||
margin-right: .5rem; | ||
} | ||
|
||
&__shortcut { | ||
margin-left: auto; | ||
padding-left: .5rem; | ||
} | ||
|
||
} | ||
|
||
@each $name, $color in $variants { | ||
.menu__item--#{$name} { | ||
@include hoverAndActiveContent { | ||
background: rgba($color, .2); | ||
} | ||
|
||
.menu__icon { | ||
color: rgba($color, .5); | ||
} | ||
} | ||
.menu__item--unselectable { | ||
background: transparent !important; | ||
pointer-events: none; | ||
} | ||
} |
Oops, something went wrong.