Add POC menu abstraction

This commit is contained in:
Eric Bailey
2024-03-04 17:06:27 -06:00
parent 6c9d6f5b05
commit a496a380d3
8 changed files with 339 additions and 0 deletions
+1
View File
@@ -58,6 +58,7 @@
"@lingui/react": "^4.5.0",
"@mattermost/react-native-paste-input": "^0.6.4",
"@miblanchard/react-native-slider": "^2.3.1",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-native-camera-roll/camera-roll": "^5.2.2",
"@react-native-clipboard/clipboard": "^1.10.0",
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import type {ContextType} from '#/components/Menu/types'
export const Context = React.createContext<ContextType>({
// @ts-ignore
control: null,
})
+80
View File
@@ -0,0 +1,80 @@
import React from 'react'
import * as Dialog from '#/components/Dialog'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {ContextType, TriggerChildProps} from '#/components/Menu/types'
import {Context} from '#/components/Menu/context'
export {useDialogControl as useMenuControl} from '#/components/Dialog'
export function Root({
children,
control,
}: React.PropsWithChildren<{
control?: Dialog.DialogOuterProps['control']
}>) {
const defaultControl = Dialog.useDialogControl()
const context = React.useMemo<ContextType>(
() => ({
control: control || defaultControl,
}),
[control, defaultControl],
)
return <Context.Provider value={context}>{children}</Context.Provider>
}
export function Trigger({
children,
}: {
children(props: TriggerChildProps): React.ReactNode
}) {
const {control} = React.useContext(Context)
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
if (!control) {
throw new Error('Menu.Trigger must be used within a Menu.Root')
}
return children({
isNative: true,
control,
state: {
hovered: false,
focused,
pressed,
},
handlers: {
onPress: control.open,
onFocus,
onBlur,
onPressIn,
onPressOut,
},
})
}
export function Outer({children}: React.PropsWithChildren<{}>) {
const {control} = React.useContext(Context)
if (!control) {
throw new Error('Menu.Outer must be used within a Menu.Root')
}
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner label="Menu TODO">
{children}
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
export function Button({}: {}) {}
+101
View File
@@ -0,0 +1,101 @@
import React from 'react'
import {Pressable} from 'react-native'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import * as Dialog from '#/components/Dialog'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {flatten, web} from '#/alf'
import {ContextType, TriggerChildProps} from '#/components/Menu/types'
import {Context} from '#/components/Menu/context'
export function useMenuControl(): Dialog.DialogControlProps {
return {
id: '',
// @ts-ignore
ref: null,
open: () => {
throw new Error(`Menu controls are only available on native platforms`)
},
close: () => {
throw new Error(`Menu controls are only available on native platforms`)
},
}
}
export function Root({
children,
}: React.PropsWithChildren<{
control?: Dialog.DialogOuterProps['control']
}>) {
const context = React.useMemo<ContextType>(
() => ({
control: null,
}),
[],
)
return (
<Context.Provider value={context}>
<DropdownMenu.Root>{children}</DropdownMenu.Root>
</Context.Provider>
)
}
export function Trigger({
children,
}: {
children(props: TriggerChildProps): React.ReactNode
}) {
const {
state: hovered,
onIn: onMouseEnter,
onOut: onMouseLeave,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
return (
<DropdownMenu.Trigger asChild>
<Pressable
onPressIn={onPressIn}
onPressOut={onPressOut}
onFocus={onFocus}
onBlur={onBlur}
style={flatten([web({outline: 0})])}
{...web({
onMouseEnter,
onMouseLeave,
})}>
{children({
isNative: false,
control: null,
state: {
hovered,
focused,
pressed,
},
handlers: {},
})}
</Pressable>
</DropdownMenu.Trigger>
)
}
export function Outer(_props: React.PropsWithChildren<{}>) {
return (
<DropdownMenu.Portal>
<DropdownMenu.Content sideOffset={5}>
<DropdownMenu.Item>
New Tab <div className="RightSlot">+T</div>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
)
}
export function Button({}: {}) {}
+33
View File
@@ -0,0 +1,33 @@
import * as Dialog from '#/components/Dialog'
export type ContextType = {
control: Dialog.DialogOuterProps['control'] | null
}
export type TriggerChildProps =
| {
isNative: true
control: Dialog.DialogOuterProps['control']
state: {
hovered: false
focused: boolean
pressed: boolean
}
handlers: {
onPress: () => void
onFocus: () => void
onBlur: () => void
onPressIn: () => void
onPressOut: () => void
}
}
| {
isNative: false
control: null
state: {
hovered: boolean
focused: boolean
pressed: boolean
}
handlers: {}
}
+28
View File
@@ -0,0 +1,28 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
import * as Menu from '#/components/Menu'
// import {useDialogStateControlContext} from '#/state/dialogs'
export function Menus() {
// const {closeAllDialogs} = useDialogStateControlContext()
return (
<View style={[a.gap_md]}>
<Menu.Root>
<Menu.Trigger>
{({state, handlers}) => {
console.log(state)
return <Text {...handlers}>Open</Text>
}}
</Menu.Trigger>
<Menu.Outer>
<Text>Open</Text>
</Menu.Outer>
</Menu.Root>
</View>
)
}
+2
View File
@@ -16,6 +16,7 @@ import {Dialogs} from './Dialogs'
import {Breakpoints} from './Breakpoints'
import {Shadows} from './Shadows'
import {Icons} from './Icons'
import {Menus} from './Menus'
export function Storybook() {
const t = useTheme()
@@ -65,6 +66,7 @@ export function Storybook() {
Dark
</Button>
</View>
<Menus />
<ThemeProvider theme="light">
<Theming />
+86
View File
@@ -4467,6 +4467,18 @@
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-escape-keydown" "1.0.3"
"@radix-ui/react-dismissable-layer@1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4"
integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-escape-keydown" "1.0.3"
"@radix-ui/react-dropdown-menu@^2.0.1":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.5.tgz#19bf4de8ffa348b4eb6a86842f14eff93d741170"
@@ -4481,6 +4493,20 @@
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-dropdown-menu@^2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz#cdf13c956c5e263afe4e5f3587b3071a25755b63"
integrity sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-id" "1.0.1"
"@radix-ui/react-menu" "2.0.6"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-controllable-state" "1.0.1"
"@radix-ui/react-focus-guards@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad"
@@ -4498,6 +4524,16 @@
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-focus-scope@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525"
integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-id@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0"
@@ -4531,6 +4567,31 @@
aria-hidden "^1.1.1"
react-remove-scroll "2.5.5"
"@radix-ui/react-menu@2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e"
integrity sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.1"
"@radix-ui/react-collection" "1.0.3"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-direction" "1.0.1"
"@radix-ui/react-dismissable-layer" "1.0.5"
"@radix-ui/react-focus-guards" "1.0.1"
"@radix-ui/react-focus-scope" "1.0.4"
"@radix-ui/react-id" "1.0.1"
"@radix-ui/react-popper" "1.1.3"
"@radix-ui/react-portal" "1.0.4"
"@radix-ui/react-presence" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-roving-focus" "1.0.4"
"@radix-ui/react-slot" "1.0.2"
"@radix-ui/react-use-callback-ref" "1.0.1"
aria-hidden "^1.1.1"
react-remove-scroll "2.5.5"
"@radix-ui/react-popper@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.2.tgz#4c0b96fcd188dc1f334e02dba2d538973ad842e9"
@@ -4548,6 +4609,23 @@
"@radix-ui/react-use-size" "1.0.1"
"@radix-ui/rect" "1.0.1"
"@radix-ui/react-popper@1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==
dependencies:
"@babel/runtime" "^7.13.10"
"@floating-ui/react-dom" "^2.0.0"
"@radix-ui/react-arrow" "1.0.3"
"@radix-ui/react-compose-refs" "1.0.1"
"@radix-ui/react-context" "1.0.1"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-layout-effect" "1.0.1"
"@radix-ui/react-use-rect" "1.0.1"
"@radix-ui/react-use-size" "1.0.1"
"@radix-ui/rect" "1.0.1"
"@radix-ui/react-portal@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.3.tgz#ffb961244c8ed1b46f039e6c215a6c4d9989bda1"
@@ -4556,6 +4634,14 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-portal@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15"
integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.3"
"@radix-ui/react-presence@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba"