Add some menu items
This commit is contained in:
@@ -213,6 +213,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* NativeDropdown component */
|
/* NativeDropdown component */
|
||||||
|
.radix-dropdown-item:focus,
|
||||||
.nativeDropdown-item:focus {
|
.nativeDropdown-item:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {View, Pressable} from 'react-native'
|
||||||
|
|
||||||
|
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
import {ContextType, TriggerChildProps} from '#/components/Menu/types'
|
|
||||||
import {Context} from '#/components/Menu/context'
|
import {Context} from '#/components/Menu/context'
|
||||||
|
import {
|
||||||
|
ContextType,
|
||||||
|
TriggerChildProps,
|
||||||
|
ItemProps,
|
||||||
|
GroupProps,
|
||||||
|
ItemTextProps,
|
||||||
|
ItemIconProps,
|
||||||
|
} from '#/components/Menu/types'
|
||||||
|
|
||||||
export {useDialogControl as useMenuControl} from '#/components/Dialog'
|
export {useDialogControl as useMenuControl} from '#/components/Dialog'
|
||||||
|
|
||||||
@@ -27,7 +37,7 @@ export function Root({
|
|||||||
|
|
||||||
export function Trigger({
|
export function Trigger({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: ViewStyleProp & {
|
||||||
children(props: TriggerChildProps): React.ReactNode
|
children(props: TriggerChildProps): React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const {control} = React.useContext(Context)
|
const {control} = React.useContext(Context)
|
||||||
@@ -71,10 +81,99 @@ export function Outer({children}: React.PropsWithChildren<{}>) {
|
|||||||
<Dialog.Outer control={control}>
|
<Dialog.Outer control={control}>
|
||||||
<Dialog.Handle />
|
<Dialog.Handle />
|
||||||
<Dialog.ScrollableInner label="Menu TODO">
|
<Dialog.ScrollableInner label="Menu TODO">
|
||||||
{children}
|
<View style={[a.gap_lg]}>{children}</View>
|
||||||
|
<View style={{height: a.gap_lg.gap}} />
|
||||||
</Dialog.ScrollableInner>
|
</Dialog.ScrollableInner>
|
||||||
</Dialog.Outer>
|
</Dialog.Outer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Button({}: {}) {}
|
export function Item({children, label, style, onPress}: ItemProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||||
|
const {
|
||||||
|
state: pressed,
|
||||||
|
onIn: onPressIn,
|
||||||
|
onOut: onPressOut,
|
||||||
|
} = useInteractionState()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
accessibilityHint=""
|
||||||
|
accessibilityLabel={label}
|
||||||
|
onPress={onPress}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onPressIn={onPressIn}
|
||||||
|
onPressOut={onPressOut}
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
a.p_md,
|
||||||
|
a.rounded_md,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
{minHeight: 48},
|
||||||
|
style,
|
||||||
|
(focused || pressed) && [t.atoms.bg_contrast_50],
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ItemText({children, style}: ItemTextProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_md,
|
||||||
|
a.font_bold,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
{paddingTop: 2},
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ItemIcon({icon: Comp}: ItemIconProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
return <Comp size="lg" fill={t.atoms.text_contrast_medium.color} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Group({children, style}: GroupProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.rounded_md,
|
||||||
|
a.overflow_hidden,
|
||||||
|
a.border,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
style,
|
||||||
|
]}>
|
||||||
|
{React.Children.toArray(children).map((child, i) => {
|
||||||
|
// ignore null children, like Dividers
|
||||||
|
return React.isValidElement(child) && child.props.children ? (
|
||||||
|
<React.Fragment>
|
||||||
|
{i > 0 ? (
|
||||||
|
<View style={[a.border_b, t.atoms.border_contrast_low]} />
|
||||||
|
) : null}
|
||||||
|
{React.cloneElement(child, {
|
||||||
|
// @ts-ignore
|
||||||
|
style: {
|
||||||
|
borderRadius: 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
) : null
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Divider() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Pressable} from 'react-native'
|
import {View, Pressable} from 'react-native'
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
|
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
import {flatten, web} from '#/alf'
|
import {atoms as a, useTheme, flatten, web, ViewStyleProp} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
import {ContextType, TriggerChildProps} from '#/components/Menu/types'
|
import {
|
||||||
|
ContextType,
|
||||||
|
TriggerChildProps,
|
||||||
|
ItemProps,
|
||||||
|
GroupProps,
|
||||||
|
ItemTextProps,
|
||||||
|
ItemIconProps,
|
||||||
|
} from '#/components/Menu/types'
|
||||||
import {Context} from '#/components/Menu/context'
|
import {Context} from '#/components/Menu/context'
|
||||||
|
|
||||||
export function useMenuControl(): Dialog.DialogControlProps {
|
export function useMenuControl(): Dialog.DialogControlProps {
|
||||||
@@ -44,7 +52,8 @@ export function Root({
|
|||||||
|
|
||||||
export function Trigger({
|
export function Trigger({
|
||||||
children,
|
children,
|
||||||
}: {
|
style,
|
||||||
|
}: ViewStyleProp & {
|
||||||
children(props: TriggerChildProps): React.ReactNode
|
children(props: TriggerChildProps): React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
@@ -59,7 +68,7 @@ export function Trigger({
|
|||||||
<Pressable
|
<Pressable
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
style={flatten([web({outline: 0})])}
|
style={flatten([style, web({outline: 0})])}
|
||||||
{...web({
|
{...web({
|
||||||
onMouseEnter,
|
onMouseEnter,
|
||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
@@ -79,16 +88,105 @@ export function Trigger({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Outer(_props: React.PropsWithChildren<{}>) {
|
export function Outer({children}: React.PropsWithChildren<{}>) {
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
<DropdownMenu.Content sideOffset={5}>
|
<DropdownMenu.Content sideOffset={5} loop>
|
||||||
<DropdownMenu.Item>
|
<View style={[a.rounded_sm, t.atoms.bg_contrast_50, {padding: 6}]}>
|
||||||
New Tab <div className="RightSlot">⌘+T</div>
|
{children}
|
||||||
</DropdownMenu.Item>
|
</View>
|
||||||
|
|
||||||
|
<DropdownMenu.Arrow
|
||||||
|
className="DropdownMenuArrow"
|
||||||
|
fill={t.atoms.bg_contrast_50.backgroundColor}
|
||||||
|
/>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Portal>
|
</DropdownMenu.Portal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Button({}: {}) {}
|
export function Item({children, label, onPress}: ItemProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {
|
||||||
|
state: hovered,
|
||||||
|
onIn: onMouseEnter,
|
||||||
|
onOut: onMouseLeave,
|
||||||
|
} = useInteractionState()
|
||||||
|
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Item asChild onSelect={onPress}>
|
||||||
|
<Pressable
|
||||||
|
className="radix-dropdown-item"
|
||||||
|
accessibilityHint=""
|
||||||
|
accessibilityLabel={label}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
style={flatten([
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
a.py_sm,
|
||||||
|
a.px_md,
|
||||||
|
a.rounded_xs,
|
||||||
|
{minHeight: 36},
|
||||||
|
web({outline: 0}),
|
||||||
|
(hovered || focused) && [
|
||||||
|
web({outline: '0 !important'}),
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
],
|
||||||
|
])}
|
||||||
|
{...web({
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave,
|
||||||
|
})}>
|
||||||
|
{children}
|
||||||
|
</Pressable>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ItemText({children, style}: ItemTextProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<Text style={[a.font_bold, t.atoms.text_contrast_high, style]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ItemIcon({icon: Comp, position = 'left'}: ItemIconProps) {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
size="md"
|
||||||
|
fill={t.atoms.text_contrast_medium.color}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
marginLeft: position === 'left' ? -2 : 0,
|
||||||
|
marginRight: position === 'right' ? -2 : 0,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Group({children}: GroupProps) {
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Divider() {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Separator
|
||||||
|
style={{
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: t.atoms.bg_contrast_100.backgroundColor,
|
||||||
|
marginTop: 6,
|
||||||
|
marginBottom: 6,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||||
|
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {TextStyleProp, ViewStyleProp} from '#/alf'
|
||||||
|
|
||||||
export type ContextType = {
|
export type ContextType = {
|
||||||
control: Dialog.DialogOuterProps['control'] | null
|
control: Dialog.DialogOuterProps['control'] | null
|
||||||
@@ -37,3 +41,18 @@ export type TriggerChildProps =
|
|||||||
}
|
}
|
||||||
handlers: {}
|
handlers: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ItemProps = React.PropsWithChildren<
|
||||||
|
ViewStyleProp & {
|
||||||
|
label: string
|
||||||
|
onPress: () => void
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
export type ItemTextProps = React.PropsWithChildren<TextStyleProp & {}>
|
||||||
|
export type ItemIconProps = React.PropsWithChildren<{
|
||||||
|
icon: React.ComponentType<SVGIconProps>
|
||||||
|
position?: 'left' | 'right'
|
||||||
|
}>
|
||||||
|
|
||||||
|
export type GroupProps = React.PropsWithChildren<ViewStyleProp & {}>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {View} from 'react-native'
|
|||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
|
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||||
// import {useDialogStateControlContext} from '#/state/dialogs'
|
// import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
|
|
||||||
export function Menus() {
|
export function Menus() {
|
||||||
@@ -11,18 +12,30 @@ export function Menus() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.gap_md]}>
|
<View style={[a.gap_md]}>
|
||||||
<Menu.Root>
|
<View style={[a.flex_row, a.align_start]}>
|
||||||
<Menu.Trigger>
|
<Menu.Root>
|
||||||
{({state, handlers}) => {
|
<Menu.Trigger style={[a.flex_1]}>
|
||||||
console.log(state.hovered, state.focused, state.pressed)
|
{({handlers}) => {
|
||||||
return <Text {...handlers}>Open</Text>
|
return <Text {...handlers}>Open</Text>
|
||||||
}}
|
}}
|
||||||
</Menu.Trigger>
|
</Menu.Trigger>
|
||||||
|
|
||||||
<Menu.Outer>
|
<Menu.Outer>
|
||||||
<Text>Open</Text>
|
<Menu.Group>
|
||||||
</Menu.Outer>
|
<Menu.Item label="Click me" onPress={() => {}}>
|
||||||
</Menu.Root>
|
<Menu.ItemIcon icon={Search} />
|
||||||
|
<Menu.ItemText>Click me</Menu.ItemText>
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
|
<Menu.Divider />
|
||||||
|
|
||||||
|
<Menu.Item label="Another item" onPress={() => {}}>
|
||||||
|
<Menu.ItemText>Another item</Menu.ItemText>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Group>
|
||||||
|
</Menu.Outer>
|
||||||
|
</Menu.Root>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* NativeDropdown component */
|
/* NativeDropdown component */
|
||||||
|
.radix-dropdown-item:focus,
|
||||||
.nativeDropdown-item:focus {
|
.nativeDropdown-item:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user