Skip to content

Commit

Permalink
chore:Menu component
Browse files Browse the repository at this point in the history
  • Loading branch information
lerte committed Oct 16, 2024
1 parent 6dc8b55 commit 39e5ac4
Show file tree
Hide file tree
Showing 22 changed files with 1,248 additions and 99 deletions.
6 changes: 3 additions & 3 deletions apps/docs/src/components/ThemeChanger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Icon,
IconButton,
Label,
Menu,
PopoverMenu,
SegmentedButton,
SegmentedButtonSet,
Slider
Expand Down Expand Up @@ -94,7 +94,7 @@ export const ThemeChanger = () => {
>
<Icon>palette</Icon>
</IconButton>
<Menu style={{ right: '0px' }} open={open} setOpen={setOpen}>
<PopoverMenu style={{ right: '0px' }} open={open} setOpen={setOpen}>
<div className="flex flex-col w-56 my-3 mx-4 *:[margin-block-end:16px]">
<section className="flex relative">
<h2 className="text-2xl tracking-tighter leading-none">
Expand Down Expand Up @@ -222,7 +222,7 @@ export const ThemeChanger = () => {
</SegmentedButtonSet>
</section>
</div>
</Menu>
</PopoverMenu>
</div>
)
}
64 changes: 30 additions & 34 deletions apps/docs/src/usages/menus.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,39 @@
import { Button, Icon, Menu, MenuItem } from 'actify'
import { Icon, IconButton, Menu, MenuItem, MenuItems } from 'actify'

import React from 'react'

export default () => {
const [open, setOpen] = React.useState(false)
return (
<div className="relative w-fit">
<Button
id="usage-anchor"
onPress={() => {
setOpen(!open)
}}
<div className="flex gap-8">
<Menu
className="p-2"
label="Open with label"
onAction={(key) => alert(key)}
>
Open Menu
</Button>
<Menu open={open} setOpen={setOpen}>
<MenuItem start={<Icon>home</Icon>}>React</MenuItem>
<MenuItem
start={
<Icon>
<svg
width="33.455"
height="36.987"
fill="#fff"
viewBox="0 0 33.455 36.987"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeWidth="2.5"
stroke="currentColor"
transform="translate(-28.272 365)"
d="M55.047-328.513l-5.238-13.822-14.323,5.317-3.243,8.5H29L42.821-364.5h4.359L61-328.513Zm-6.067-15.969.829,2.147-.829-2.147-5.308-13.745-7.123,18.445"
/>
</svg>
</Icon>
}
>
Actify
</MenuItem>
<MenuItems>
<MenuItem key="edit">Edit…</MenuItem>
<MenuItem key="duplicate">Duplicate</MenuItem>
</MenuItems>
<MenuItems>
<MenuItem key="move">Move…</MenuItem>
<MenuItem key="rename">Rename…</MenuItem>
</MenuItems>
<MenuItems>
<MenuItem key="archive">Archive</MenuItem>
<MenuItem key="delete">Delete…</MenuItem>
</MenuItems>
</Menu>
<Menu
activator={(ref, menuTriggerProps) => (
<IconButton ref={ref} {...menuTriggerProps}>
<Icon>More_Horiz</Icon>
</IconButton>
)}
onAction={(key) => alert(key)}
>
<MenuItem key="copy">Copy</MenuItem>
<MenuItem key="cut">Cut</MenuItem>
<MenuItem key="paste">Paste</MenuItem>
</Menu>
</div>
)
Expand Down
5 changes: 3 additions & 2 deletions packages/actify/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type ButtonProps = {
const Button = (props: ButtonProps) => {
const {
id,
ref,
style,
ripple = true,
color = 'primary',
Expand All @@ -38,7 +39,7 @@ const Button = (props: ButtonProps) => {
children
} = props

const buttonRef = React.useRef(null)
const buttonRef = React.useRef<HTMLButtonElement>(null)
const { buttonProps } = useButton(props, buttonRef)

const buttonId = id || `actify-button${useId()}`
Expand All @@ -61,8 +62,8 @@ const Button = (props: ButtonProps) => {
{ripple && <Ripple id={buttonId} disabled={disabled} />}
<button
id={buttonId}
ref={buttonRef}
disabled={disabled}
ref={ref || buttonRef}
className={buttons['button']}
{...mergeProps(buttonProps, focusProps)}
>
Expand Down
3 changes: 2 additions & 1 deletion packages/actify/src/components/Button/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type IconButtonProps = {
const IconButton = (props: IconButtonProps) => {
const {
id,
ref,
disabled,
children,
className,
Expand All @@ -62,7 +63,7 @@ const IconButton = (props: IconButtonProps) => {

return (
<button
ref={buttonRef}
ref={ref || buttonRef}
id={iconButtonId}
disabled={disabled}
{...mergeProps(buttonProps, focusProps)}
Expand Down
2 changes: 1 addition & 1 deletion packages/actify/src/components/Item/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import styles from './actify.module.css'
import styles from './item.module.css'

export interface ItemProps extends React.ComponentProps<'div'> {
container?: React.ReactNode
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
.item {
color: var(--md-sys-color-on-surface, #1d1b20);
font-family: var(
--md-sys-typescale-body-large-font,
var(--md-ref-typeface-plain, Roboto)
);
font-size: var(--md-sys-typescale-body-large-size, 1rem);
font-weight: var(
--md-sys-typescale-body-large-weight,
var(--md-ref-typeface-weight-regular, 400)
);
line-height: var(--md-sys-typescale-body-large-line-height, 1.5rem);
align-items: center;
box-sizing: border-box;
display: flex;
gap: 16px;
min-height: 56px;
overflow: hidden;
padding: 12px 16px;
position: relative;
text-overflow: ellipsis;

border-radius: inherit;
flex: 1;
color: var(
--md-menu-item-label-text-color,
var(--md-sys-color-on-surface, #1d1b20)
);
font-family: var(
--md-menu-item-label-text-font,
var(
--md-sys-typescale-body-large-font,
var(--md-ref-typeface-plain, Roboto)
)
);
font-size: var(
--md-menu-item-label-text-size,
var(--md-sys-typescale-body-large-size, 1rem)
);
line-height: var(
--md-menu-item-label-text-line-height,
var(--md-sys-typescale-body-large-line-height, 1.5rem)
);
font-weight: var(
--md-menu-item-label-text-weight,
var(
--md-sys-typescale-body-large-weight,
var(--md-ref-typeface-weight-regular, 400)
)
);
min-height: var(--md-menu-item-one-line-container-height, 56px);
padding-top: var(--md-menu-item-top-space, 12px);
padding-bottom: var(--md-menu-item-bottom-space, 12px);
padding-inline-start: var(--md-menu-item-leading-space, 16px);
padding-inline-end: var(--md-menu-item-trailing-space, 16px);
}
.item {
color: var(--md-sys-color-on-surface, #1d1b20);
font-family: var(
--md-sys-typescale-body-large-font,
var(--md-ref-typeface-plain, Roboto)
);
font-size: var(--md-sys-typescale-body-large-size, 1rem);
font-weight: var(
--md-sys-typescale-body-large-weight,
var(--md-ref-typeface-weight-regular, 400)
);
line-height: var(--md-sys-typescale-body-large-line-height, 1.5rem);
align-items: center;
box-sizing: border-box;
display: flex;
gap: 16px;
min-height: 56px;
overflow: hidden;
padding: 12px 16px;
position: relative;
text-overflow: ellipsis;

border-radius: inherit;
flex: 1;
color: var(
--md-menu-item-label-text-color,
var(--md-sys-color-on-surface, #1d1b20)
);
font-family: var(
--md-menu-item-label-text-font,
var(
--md-sys-typescale-body-large-font,
var(--md-ref-typeface-plain, Roboto)
)
);
font-size: var(
--md-menu-item-label-text-size,
var(--md-sys-typescale-body-large-size, 1rem)
);
line-height: var(
--md-menu-item-label-text-line-height,
var(--md-sys-typescale-body-large-line-height, 1.5rem)
);
font-weight: var(
--md-menu-item-label-text-weight,
var(
--md-sys-typescale-body-large-weight,
var(--md-ref-typeface-weight-regular, 400)
)
);
min-height: var(--md-menu-item-one-line-container-height, 56px);
padding-top: var(--md-menu-item-top-space, 12px);
padding-bottom: var(--md-menu-item-bottom-space, 12px);
padding-inline-start: var(--md-menu-item-leading-space, 16px);
padding-inline-end: var(--md-menu-item-trailing-space, 16px);
}
98 changes: 98 additions & 0 deletions packages/actify/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { AriaButtonProps, Placement, useMenu, useMenuTrigger } from 'react-aria'
import type { AriaMenuProps, MenuTriggerProps } from '@react-types/menu'
import { useMenuTriggerState, useTreeState } from 'react-stately'

import { Button } from './../Button'
import { MenuItem } from './MenuItem'
import { MenuItems } from './MenuItems'
import { Popover } from './../Popover'
import React from 'react'
import clsx from 'clsx'
import styles from './menu.module.css'

interface MenuButtonProps<T extends object>
extends AriaMenuProps<T>,
MenuTriggerProps {
style?: React.CSSProperties
className?: string
label?: string
placement?: Placement
activator?: (
ref: React.RefObject<null>,
menuTriggerProps: AriaButtonProps<'button'>
) => React.JSX.Element
}

export function Menu<T extends object>(props: MenuButtonProps<T>) {
// Create state based on the incoming props
const state = useMenuTriggerState(props)

// Get props for the menu trigger and menu elements
const ref = React.useRef(null)
const { menuTriggerProps, menuProps } = useMenuTrigger<T>({}, state, ref)

return (
<>
{props.label && (
<Button {...menuTriggerProps} ref={ref}>
{props.label}
</Button>
)}
{props?.activator?.(ref, menuTriggerProps)}
{state.isOpen && (
<Popover state={state} triggerRef={ref} placement={props.placement}>
<MenuRoot
{...props}
{...menuProps}
autoFocus={state.focusStrategy || true}
onClose={() => state.close()}
/>
</Popover>
)}
</>
)
}

interface MenuProps<T extends object> extends AriaMenuProps<T> {
onClose: () => void
style?: React.CSSProperties
className?: string
}

function MenuRoot<T extends object>(props: MenuProps<T>) {
// Create state based on the incoming props
const state = useTreeState(props)

// Get props for the menu element
const ref = React.useRef(null)
const { menuProps } = useMenu(props, state, ref)

return (
<ul
ref={ref}
{...menuProps}
style={props.style}
className={clsx(styles['menu-item'], props.className)}
>
{[...state.collection].map((item) =>
item.type == 'section' ? (
<MenuItems
key={item.key}
section={item}
state={state}
onClose={props.onClose}
onAction={() => props.onAction}
/>
) : (
<MenuItem
key={item.key}
item={item}
state={state}
onClose={props.onClose}
onAction={() => props.onAction}
/>
)
)}
</ul>
)
}
Loading

0 comments on commit 39e5ac4

Please sign in to comment.