💄(front) improve menu and select ui
We want to introduce two variant for this lists, so it was needed to create a slot recipe.
This commit is contained in:
@@ -10,7 +10,6 @@ import { JoinMeetingDialog } from '../components/JoinMeetingDialog'
|
|||||||
import { ProConnectButton } from '@/components/ProConnectButton'
|
import { ProConnectButton } from '@/components/ProConnectButton'
|
||||||
import { useCreateRoom } from '@/features/rooms'
|
import { useCreateRoom } from '@/features/rooms'
|
||||||
import { usePersistentUserChoices } from '@livekit/components-react'
|
import { usePersistentUserChoices } from '@livekit/components-react'
|
||||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe'
|
|
||||||
import { RiAddLine, RiLink } from '@remixicon/react'
|
import { RiAddLine, RiLink } from '@remixicon/react'
|
||||||
import { LaterMeetingDialog } from '@/features/home/components/LaterMeetingDialog'
|
import { LaterMeetingDialog } from '@/features/home/components/LaterMeetingDialog'
|
||||||
import { IntroSlider } from '@/features/home/components/IntroSlider'
|
import { IntroSlider } from '@/features/home/components/IntroSlider'
|
||||||
@@ -18,6 +17,7 @@ import { MoreLink } from '@/features/home/components/MoreLink'
|
|||||||
import { ReactNode, useState } from 'react'
|
import { ReactNode, useState } from 'react'
|
||||||
|
|
||||||
import { css } from '@/styled-system/css'
|
import { css } from '@/styled-system/css'
|
||||||
|
import { menuRecipe } from '@/primitives/menuRecipe.ts';
|
||||||
|
|
||||||
const Columns = ({ children }: { children?: ReactNode }) => {
|
const Columns = ({ children }: { children?: ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
@@ -173,7 +173,7 @@ export const Home = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
<RACMenu>
|
<RACMenu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className={menuItemRecipe({ icon: true })}
|
className={menuRecipe({icon: true, variant: 'light'}).item}
|
||||||
onAction={async () => {
|
onAction={async () => {
|
||||||
const slug = generateRoomId()
|
const slug = generateRoomId()
|
||||||
createRoom({ slug, username }).then((data) =>
|
createRoom({ slug, username }).then((data) =>
|
||||||
@@ -188,7 +188,7 @@ export const Home = () => {
|
|||||||
{t('createMenu.instantOption')}
|
{t('createMenu.instantOption')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className={menuItemRecipe({ icon: true })}
|
className={menuRecipe({icon: true, variant: 'light'}).item}
|
||||||
onAction={() => {
|
onAction={() => {
|
||||||
const slug = generateRoomId()
|
const slug = generateRoomId()
|
||||||
createRoom({ slug, username }).then((data) =>
|
createRoom({ slug, username }).then((data) =>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Text, text } from '@/primitives/Text'
|
|||||||
import {
|
import {
|
||||||
RiCheckLine,
|
RiCheckLine,
|
||||||
RiCloseLine,
|
RiCloseLine,
|
||||||
RiFileCopyLine, RiLink,
|
RiFileCopyLine,
|
||||||
RiSpam2Fill,
|
RiSpam2Fill,
|
||||||
} from '@remixicon/react';
|
} from '@remixicon/react';
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@@ -49,8 +49,6 @@ export const InviteDialog = ({
|
|||||||
}
|
}
|
||||||
}, [isCopied])
|
}, [isCopied])
|
||||||
|
|
||||||
const [isHovered, setIsHovered] = useState(false)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRACDialog {...dialogProps}>
|
<StyledRACDialog {...dialogProps}>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { menuItemRecipe } from '@/primitives/menuItemRecipe'
|
|
||||||
import {
|
import {
|
||||||
RiAccountBoxLine,
|
RiAccountBoxLine,
|
||||||
RiMegaphoneLine,
|
RiMegaphoneLine,
|
||||||
@@ -10,6 +9,7 @@ import { Dispatch, SetStateAction } from 'react'
|
|||||||
import { DialogState } from './OptionsButton'
|
import { DialogState } from './OptionsButton'
|
||||||
import { Separator } from '@/primitives/Separator'
|
import { Separator } from '@/primitives/Separator'
|
||||||
import { useSidePanel } from '../../../hooks/useSidePanel'
|
import { useSidePanel } from '../../../hooks/useSidePanel'
|
||||||
|
import { menuRecipe } from '@/primitives/menuRecipe.ts';
|
||||||
|
|
||||||
// @todo try refactoring it to use MenuList component
|
// @todo try refactoring it to use MenuList component
|
||||||
export const OptionsMenuItems = ({
|
export const OptionsMenuItems = ({
|
||||||
@@ -29,7 +29,7 @@ export const OptionsMenuItems = ({
|
|||||||
<Section>
|
<Section>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onAction={() => toggleEffects()}
|
onAction={() => toggleEffects()}
|
||||||
className={menuItemRecipe({ icon: true })}
|
className={menuRecipe({ icon: true }).item}
|
||||||
>
|
>
|
||||||
<RiAccountBoxLine size={20} />
|
<RiAccountBoxLine size={20} />
|
||||||
{t('effects')}
|
{t('effects')}
|
||||||
@@ -40,13 +40,13 @@ export const OptionsMenuItems = ({
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
href="https://grist.incubateur.net/o/docs/forms/1YrfNP1QSSy8p2gCxMFnSf/4"
|
href="https://grist.incubateur.net/o/docs/forms/1YrfNP1QSSy8p2gCxMFnSf/4"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className={menuItemRecipe({ icon: true })}
|
className={menuRecipe({ icon: true }).item}
|
||||||
>
|
>
|
||||||
<RiMegaphoneLine size={20} />
|
<RiMegaphoneLine size={20} />
|
||||||
{t('feedbacks')}
|
{t('feedbacks')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className={menuItemRecipe({ icon: true })}
|
className={menuRecipe({ icon: true }).item}
|
||||||
onAction={() => onOpenDialog('settings')}
|
onAction={() => onOpenDialog('settings')}
|
||||||
>
|
>
|
||||||
<RiSettings3Line size={20} />
|
<RiSettings3Line size={20} />
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const Header = () => {
|
|||||||
<Menu>
|
<Menu>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
invisible
|
variant="greyscale"
|
||||||
tooltip={t('loggedInUserTooltip')}
|
tooltip={t('loggedInUserTooltip')}
|
||||||
tooltipType="delayed"
|
tooltipType="delayed"
|
||||||
>
|
>
|
||||||
@@ -83,6 +83,7 @@ export const Header = () => {
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
<MenuList
|
<MenuList
|
||||||
|
variant={"light"}
|
||||||
items={[{ value: 'logout', label: t('logout') }]}
|
items={[{ value: 'logout', label: t('logout') }]}
|
||||||
onAction={(value) => {
|
onAction={(value) => {
|
||||||
if (value === 'logout') {
|
if (value === 'logout') {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { Menu, MenuProps, MenuItem } from 'react-aria-components'
|
import { Menu, MenuProps, MenuItem } from 'react-aria-components'
|
||||||
import { menuItemRecipe } from './menuItemRecipe'
|
import { menuRecipe } from '@/primitives/menuRecipe.ts';
|
||||||
|
import type { RecipeVariantProps } from '@/styled-system/types';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* render a Button primitive that shows a popover showing a list of pressable items
|
* render a Button primitive that shows a popover showing a list of pressable items
|
||||||
@@ -14,11 +19,14 @@ export const MenuList = <T extends string | number = string>({
|
|||||||
onAction: (key: T) => void
|
onAction: (key: T) => void
|
||||||
selectedItem?: T
|
selectedItem?: T
|
||||||
items: Array<string | { value: T; label: ReactNode }>
|
items: Array<string | { value: T; label: ReactNode }>
|
||||||
} & MenuProps<unknown>) => {
|
} & MenuProps<unknown> & RecipeVariantProps<typeof menuRecipe>) => {
|
||||||
|
const [variantProps] = menuRecipe.splitVariantProps(menuProps)
|
||||||
|
const classes = menuRecipe({extraPadding: true, ...variantProps})
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
selectionMode={selectedItem !== undefined ? 'single' : undefined}
|
selectionMode={selectedItem !== undefined ? 'single' : undefined}
|
||||||
selectedKeys={selectedItem !== undefined ? [selectedItem] : undefined}
|
selectedKeys={selectedItem !== undefined ? [selectedItem] : undefined}
|
||||||
|
className={classes.root}
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
>
|
>
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
@@ -26,7 +34,7 @@ export const MenuList = <T extends string | number = string>({
|
|||||||
const label = typeof item === 'string' ? item : item.label
|
const label = typeof item === 'string' ? item : item.label
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className={menuItemRecipe({ extraPadding: true })}
|
className={classes.item}
|
||||||
key={value}
|
key={value}
|
||||||
id={value as string}
|
id={value as string}
|
||||||
onAction={() => {
|
onAction={() => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from 'react-aria-components'
|
} from 'react-aria-components'
|
||||||
import { Box } from './Box'
|
import { Box } from './Box'
|
||||||
import { StyledPopover } from './Popover'
|
import { StyledPopover } from './Popover'
|
||||||
import { menuItemRecipe } from './menuItemRecipe'
|
import { menuRecipe } from '@/primitives/menuRecipe.ts';
|
||||||
|
|
||||||
const StyledButton = styled(Button, {
|
const StyledButton = styled(Button, {
|
||||||
base: {
|
base: {
|
||||||
@@ -75,7 +75,7 @@ export const Select = <T extends string | number>({
|
|||||||
<ListBox>
|
<ListBox>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<ListBoxItem
|
<ListBoxItem
|
||||||
className={menuItemRecipe({ extraPadding: true })}
|
className={menuRecipe({extraPadding: true, variant: 'light'}).item}
|
||||||
id={item.value}
|
id={item.value}
|
||||||
key={item.value}
|
key={item.value}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const StyledTab = styled(RACTab, {
|
|||||||
color: 'box.text',
|
color: 'box.text',
|
||||||
},
|
},
|
||||||
'&[data-selected]': {
|
'&[data-selected]': {
|
||||||
backgroundColor: 'primaryDark.50',
|
backgroundColor: 'primary.800',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import { cva } from '@/styled-system/css'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reusable styles for a menu item, select item, etc… to be used with panda `css()` or `styled()`
|
|
||||||
*
|
|
||||||
* these are in their own files because react hot refresh doesn't like exporting stuff
|
|
||||||
* that aren't components in component files
|
|
||||||
*/
|
|
||||||
export const menuItemRecipe = cva({
|
|
||||||
base: {
|
|
||||||
paddingY: 0.125,
|
|
||||||
paddingX: 0.5,
|
|
||||||
textAlign: 'left',
|
|
||||||
width: 'full',
|
|
||||||
borderRadius: 4,
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'box.text',
|
|
||||||
border: '1px solid transparent',
|
|
||||||
position: 'relative',
|
|
||||||
'&[data-selected]': {
|
|
||||||
'&::before': {
|
|
||||||
content: '"✓"',
|
|
||||||
position: 'absolute',
|
|
||||||
top: '2px',
|
|
||||||
left: '6px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'&[data-focused]': {
|
|
||||||
color: 'primary.text',
|
|
||||||
backgroundColor: 'primaryDark.50',
|
|
||||||
outline: 'none!',
|
|
||||||
},
|
|
||||||
'&[data-hovered]': {
|
|
||||||
color: 'primary.text',
|
|
||||||
backgroundColor: 'primaryDark.50',
|
|
||||||
outline: 'none!',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
variants: {
|
|
||||||
icon: {
|
|
||||||
true: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '1rem',
|
|
||||||
paddingY: '0.4rem',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extraPadding: {
|
|
||||||
true: {
|
|
||||||
paddingLeft: 1.5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
74
src/frontend/src/primitives/menuRecipe.ts
Normal file
74
src/frontend/src/primitives/menuRecipe.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { sva } from '@/styled-system/css';
|
||||||
|
|
||||||
|
export const menuRecipe = sva({
|
||||||
|
slots: ['root', 'item'],
|
||||||
|
base: {
|
||||||
|
root: { },
|
||||||
|
item: {
|
||||||
|
paddingY: 0.125,
|
||||||
|
paddingX: 0.5,
|
||||||
|
textAlign: 'left',
|
||||||
|
width: 'full',
|
||||||
|
borderRadius: 4,
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: 'box.text',
|
||||||
|
border: '1px solid transparent',
|
||||||
|
position: 'relative',
|
||||||
|
'&[data-selected]': {
|
||||||
|
'&::before': {
|
||||||
|
content: '"✓"',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '2px',
|
||||||
|
left: '6px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'&[data-focused]': {
|
||||||
|
color: 'primary.text',
|
||||||
|
backgroundColor: 'primaryDark.100',
|
||||||
|
outline: 'none!',
|
||||||
|
},
|
||||||
|
'&[data-hovered]': {
|
||||||
|
color: 'primary.text',
|
||||||
|
backgroundColor: 'primaryDark.100',
|
||||||
|
outline: 'none!',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
light: {
|
||||||
|
item: {
|
||||||
|
'&[data-focused]': {
|
||||||
|
backgroundColor: 'primary.800',
|
||||||
|
},
|
||||||
|
'&[data-hovered]': {
|
||||||
|
backgroundColor: 'primary.800',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extraPadding: {
|
||||||
|
true: {
|
||||||
|
item: {
|
||||||
|
paddingLeft: 1.5,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
true: {
|
||||||
|
item: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '1rem',
|
||||||
|
paddingY: '0.4rem',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'dark'
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user