├── .gitignore ├── .prettierrc ├── README.md ├── docker-compose.yaml ├── package.json ├── packages ├── app │ ├── .env.example │ ├── components │ │ ├── record │ │ │ ├── index.tsx │ │ │ ├── record.tsx │ │ │ └── skeleton.tsx │ │ └── records │ │ │ ├── index.tsx │ │ │ ├── records-list.tsx │ │ │ ├── records.tsx │ │ │ └── skeletons.tsx │ ├── config.ts │ ├── design-system │ │ ├── bottom-sheet │ │ │ └── index.tsx │ │ ├── menu │ │ │ └── index.tsx │ │ └── theme.ts │ ├── graphql-codegen.js │ ├── graphql │ │ ├── fragments │ │ │ ├── CurrentUser.fragment.generated.ts │ │ │ ├── CurrentUser.fragment.graphql │ │ │ ├── Record.fragment.generated.ts │ │ │ ├── Record.fragment.graphql │ │ │ ├── User.fragment.generated.ts │ │ │ └── User.fragment.graphql │ │ └── types.ts │ ├── hooks │ │ └── use-debounce.ts │ ├── index.js │ ├── package.json │ ├── screens │ │ ├── CrateScreen.tsx │ │ ├── HomeScreen.tsx │ │ ├── NotFoundScreen.tsx │ │ ├── PlaylistsScreen.tsx │ │ └── ProfileScreen.tsx │ └── utils │ │ ├── fetch.ts │ │ ├── init-apollo.ts │ │ ├── is-desktop.ts │ │ ├── is-mobile.ts │ │ ├── is-server.ts │ │ ├── open-key.ts │ │ └── type-policies.ts ├── expo-next │ ├── .env.local │ ├── .env.local.example │ ├── .expo-shared │ │ └── assets.json │ ├── .gitignore │ ├── App.tsx │ ├── app.config.js │ ├── assets │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── eas.json │ ├── index.js │ ├── metro.config.js │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── public │ │ ├── favicon.png │ │ └── fonts │ │ │ ├── Arial Rounded MT Std Bold.woff │ │ │ ├── Arial Rounded MT Std Bold.woff2 │ │ │ ├── Arial Rounded MT Std Extra Bold.woff │ │ │ ├── Arial Rounded MT Std Extra Bold.woff2 │ │ │ ├── Arial Rounded MT Std Light.woff │ │ │ ├── Arial Rounded MT Std Light.woff2 │ │ │ ├── Arial Rounded MT Std.woff │ │ │ ├── Arial Rounded MT Std.woff2 │ │ │ ├── ArialRoundedMTStd.ttf │ │ │ ├── ArialRoundedMTStdBold.ttf │ │ │ ├── ArialRoundedMTStdExtraBold.ttf │ │ │ └── ArialRoundedMTStdLight.ttf │ ├── src │ │ ├── navigation │ │ │ ├── BottomTabNavigator.tsx │ │ │ ├── LinkingConfiguration.ts │ │ │ └── index.tsx │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── crate.tsx │ │ │ ├── home.tsx │ │ │ ├── index.tsx │ │ │ ├── playlists.tsx │ │ │ ├── privacy.tsx │ │ │ ├── profile.tsx │ │ │ └── terms.tsx │ │ └── styles │ │ │ ├── fonts.scss │ │ │ └── global.scss │ ├── tsconfig.json │ └── yarn.lock └── radix │ ├── context-menu │ ├── README.md │ ├── dist │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ ├── index.js │ │ ├── index.js.map │ │ ├── index.module.js │ │ └── index.module.js.map │ ├── package.json │ └── src │ │ ├── ContextMenu.stories.tsx │ │ ├── ContextMenu.tsx │ │ └── index.ts │ ├── dropdown-menu │ ├── README.md │ ├── dist │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ ├── index.js │ │ ├── index.js.map │ │ ├── index.module.js │ │ └── index.module.js.map │ ├── package.json │ └── src │ │ ├── DropdownMenu.stories.tsx │ │ ├── DropdownMenu.tsx │ │ └── index.ts │ └── menu │ ├── README.md │ ├── dist │ ├── index.d.ts │ ├── index.d.ts.map │ ├── index.js │ ├── index.js.map │ ├── index.module.js │ └── index.module.js.map │ ├── package.json │ └── src │ ├── Menu.stories.tsx │ ├── Menu.tsx │ ├── index.ts │ └── useMenuTypeahead.tsx ├── patches ├── @expo+next-adapter+2.1.77.patch ├── @expo+webpack-config+0.12.76.patch ├── @gorhom+bottom-sheet+4.0.0-alpha.3.patch └── babel-preset-expo+8.3.0.patch └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | node_modules/ 4 | yarn-error.log 5 | *.hprof 6 | .vercel 7 | **/*.env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Record Pool Monorepo - Universal UI PoC 2 | 3 | This is a proof of concept using Radix UI primitives on iOS, Android and Web with 4 | Expo and Next.js 🎉 5 | 6 | Made this as a proposal to create universal UI primitives and components based 7 | on Radix UI that works with Expo by using React Native for Web. 8 | 9 | The app is based on code samples from [Record Pool](https://recordpool.app), 10 | a side project kickstarted during Expo Jam (a week of dogfooding at Expo). 11 | 12 | Bottom sheet menu on mobile: 13 | 14 | Screenshot 2021-06-03 at 07 41 16 15 | 16 | Dropdown menu on desktop: 17 | 18 | Screenshot 2021-06-03 at 07 42 23 19 | 20 | ## App 21 | 22 | > Code shared between iOS, Android and Web 23 | 24 | `cd packages/app` 25 | 26 | The interesting stuff is located at: 27 | 28 | `packages/app/components/record` 29 | `packages/app/design-system/menu` 30 | `packages/app/design-system/bottom-sheet` 31 | 32 | ## Expo + Next.js 33 | 34 | > App entrypoints and navigation / routing 35 | 36 | Expo entrypoint: `App.tsx` 37 | 38 | `cd packages/expo-next` 39 | 40 | `yarn start:expo` to start iOS and Android app with Expo 41 | 42 | Next.js entrypoint: `src/pages/_app.tsx` 43 | 44 | `cd packages/expo-next` 45 | 46 | `yarn dev` to start web app 47 | 48 | ## Radix 49 | 50 | > Custom Radix UI components using React Native for Web and React Native Bottom Sheet 51 | 52 | `cd packages/radix` 53 | 54 | ### Context Menu 55 | 56 | Trigger on desktop: 57 | 58 | - Right-click on record 59 | 60 | `packages/radix/context-menu/src/ContextMenu.tsx` 61 | 62 | ```diff 63 | diff --git a/packages/react/context-menu/src/ContextMenu.tsx b/packages/react/context-menu/src/ContextMenu.tsx 64 | index f5c83ab..674b0c8 100644 65 | --- a/packages/react/context-menu/src/ContextMenu.tsx 66 | +++ b/packages/react/context-menu/src/ContextMenu.tsx 67 | @@ -1,4 +1,5 @@ 68 | import * as React from 'react'; 69 | +import { Platform, View } from 'react-native'; 70 | import { composeEventHandlers } from '@radix-ui/primitive'; 71 | import { createContext } from '@radix-ui/react-context'; 72 | import { Primitive, extendPrimitive } from '@radix-ui/react-primitive'; 73 | @@ -53,7 +54,7 @@ ContextMenu.displayName = CONTEXT_MENU_NAME; 74 | * -----------------------------------------------------------------------------------------------*/ 75 | 76 | const TRIGGER_NAME = 'ContextMenuTrigger'; 77 | -const TRIGGER_DEFAULT_TAG = 'span'; 78 | +const TRIGGER_DEFAULT_TAG = Platform.OS === 'web' ? 'span' : View; 79 | 80 | type ContextMenuTriggerOwnProps = Polymorphic.OwnProps; 81 | type ContextMenuTriggerPrimitive = Polymorphic.ForwardRefComponent< 82 | ``` 83 | 84 | ### Dropdown Menu (+ Bottom Sheet Menu) 85 | 86 | Trigger on desktop: 87 | 88 | - Click on menu vertical 89 | 90 | Trigger on mobile: 91 | 92 | - Press on menu vertical 93 | - Long-press on record 94 | 95 | `packages/radix/dropdown-menu/src/DropdownMenu.tsx` 96 | 97 | ```diff 98 | diff --git a/packages/react/dropdown-menu/src/DropdownMenu.tsx b/packages/react/dropdown-menu/src/DropdownMenu.tsx 99 | index 47a2c55..ef20902 100644 100 | --- a/packages/react/dropdown-menu/src/DropdownMenu.tsx 101 | +++ b/packages/react/dropdown-menu/src/DropdownMenu.tsx 102 | @@ -1,4 +1,5 @@ 103 | import * as React from 'react'; 104 | +import { Platform, View, Modal } from 'react-native'; 105 | import { composeEventHandlers } from '@radix-ui/primitive'; 106 | import { useComposedRefs } from '@radix-ui/react-compose-refs'; 107 | import { createContext } from '@radix-ui/react-context'; 108 | @@ -6,6 +7,7 @@ import { useControllableState } from '@radix-ui/react-use-controllable-state'; 109 | import { extendPrimitive } from '@radix-ui/react-primitive'; 110 | import * as MenuPrimitive from '@radix-ui/react-menu'; 111 | import { useId } from '@radix-ui/react-id'; 112 | +import BottomSheet from '@gorhom/bottom-sheet'; 113 | 114 | import type * as Polymorphic from '@radix-ui/react-polymorphic'; 115 | 116 | @@ -64,7 +66,7 @@ DropdownMenu.displayName = DROPDOWN_MENU_NAME; 117 | * -----------------------------------------------------------------------------------------------*/ 118 | 119 | const TRIGGER_NAME = 'DropdownMenuTrigger'; 120 | -const TRIGGER_DEFAULT_TAG = 'button'; 121 | +const TRIGGER_DEFAULT_TAG = Platform.OS === 'web' ? 'button' : View; 122 | 123 | type DropdownMenuTriggerOwnProps = Omit< 124 | Polymorphic.OwnProps, 125 | @@ -134,35 +136,70 @@ const DropdownMenuContent = React.forwardRef((props, forwardedRef) => { 126 | ...contentProps 127 | } = props; 128 | const context = useDropdownMenuContext(CONTENT_NAME); 129 | + const bottomSheetRef = React.useRef(null); 130 | + const handleSheetChanges = React.useCallback( 131 | + (index: number) => { 132 | + if (index === -1) { 133 | + context.onOpenChange(false); 134 | + } 135 | + }, 136 | + [context] 137 | + ); 138 | + 139 | + if (Platform.OS === 'web') { 140 | + return ( 141 | + { 155 | + event.preventDefault(); 156 | + context.triggerRef.current?.focus(); 157 | + })} 158 | + onPointerDownOutside={composeEventHandlers( 159 | + props.onPointerDownOutside, 160 | + (event) => { 161 | + const targetIsTrigger = context.triggerRef.current?.contains( 162 | + event.target as HTMLElement 163 | + ); 164 | + // prevent dismissing when clicking the trigger 165 | + // as it's already setup to close, otherwise it would close and immediately open. 166 | + if (targetIsTrigger) event.preventDefault(); 167 | + }, 168 | + { checkForDefaultPrevented: false } 169 | + )} 170 | + /> 171 | + ); 172 | + } 173 | + 174 | return ( 175 | - { 189 | - event.preventDefault(); 190 | - context.triggerRef.current?.focus(); 191 | - })} 192 | - onPointerDownOutside={composeEventHandlers( 193 | - props.onPointerDownOutside, 194 | - (event) => { 195 | - const targetIsTrigger = context.triggerRef.current?.contains(event.target as HTMLElement); 196 | - // prevent dismissing when clicking the trigger 197 | - // as it's already setup to close, otherwise it would close and immediately open. 198 | - if (targetIsTrigger) event.preventDefault(); 199 | - }, 200 | - { checkForDefaultPrevented: false } 201 | - )} 202 | - /> 203 | + { 208 | + context.onOpenChange(!context.open); 209 | + }} 210 | + > 211 | + 218 | + 219 | ); 220 | }) as DropdownMenuContentPrimitive; 221 | ``` 222 | 223 | ### Menu 224 | 225 | `packages/radix/menu/src/Menu.tsx` 226 | 227 | ```diff 228 | diff --git a/packages/react/menu/src/Menu.tsx b/packages/react/menu/src/Menu.tsx 229 | index 005ba42..b3f60d0 100644 230 | --- a/packages/react/menu/src/Menu.tsx 231 | +++ b/packages/react/menu/src/Menu.tsx 232 | @@ -1,4 +1,5 @@ 233 | import * as React from 'react'; 234 | +import { Platform, View } from 'react-native'; 235 | import { RemoveScroll } from 'react-remove-scroll'; 236 | import { hideOthers } from 'aria-hidden'; 237 | import { composeEventHandlers } from '@radix-ui/primitive'; 238 | @@ -396,6 +397,18 @@ const MenuItem = React.forwardRef((props, forwardedRef) => { 239 | 240 | MenuItem.displayName = ITEM_NAME; 241 | 242 | +/* ------------------------------------------------------------------------------------------------- 243 | + * MenuItemNative 244 | + * -----------------------------------------------------------------------------------------------*/ 245 | + 246 | +const MenuItemNative = React.forwardRef((props, forwardedRef) => { 247 | + const { ...itemProps } = props; 248 | + 249 | + return ; 250 | +}) as MenuItemPrimitive; 251 | + 252 | +MenuItemNative.displayName = ITEM_NAME; 253 | + 254 | /* ------------------------------------------------------------------------------------------------- 255 | * MenuCheckboxItem 256 | * -----------------------------------------------------------------------------------------------*/ 257 | @@ -594,7 +607,7 @@ const Anchor = MenuAnchor; 258 | const Content = MenuContent; 259 | const Group = MenuGroup; 260 | const Label = MenuLabel; 261 | -const Item = MenuItem; 262 | +const Item = Platform.OS === 'web' ? MenuItem : MenuItemNative; 263 | const CheckboxItem = MenuCheckboxItem; 264 | const RadioGroup = MenuRadioGroup; 265 | const RadioItem = MenuRadioItem; 266 | ``` 267 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | volumes: 4 | db_data: 5 | redis_data: 6 | nest_node_modules: 7 | nest_dist: 8 | 9 | services: 10 | nest: 11 | container_name: record-pool-nest 12 | build: ./packages/nest 13 | depends_on: 14 | - postgres 15 | - redis 16 | volumes: 17 | - ./packages/nest:/usr/app 18 | - nest_node_modules:/usr/app/node_modules 19 | - nest_dist:/usr/app/dist 20 | ports: 21 | - 4000:4000 # nest 22 | - 9229:9229 # node --inspect 23 | command: yarn start:dev:docker 24 | 25 | postgres: 26 | container_name: record-pool-postgres 27 | image: postgres:12 28 | ports: 29 | - 5432:5432 30 | restart: always 31 | volumes: 32 | - db_data:/var/lib/postgresql/data 33 | environment: 34 | POSTGRES_PASSWORD: postgrespassword 35 | 36 | hasura: 37 | container_name: record-pool-hasura 38 | image: hasura/graphql-engine:v1.3.3.cli-migrations-v2 39 | ports: 40 | - 8080:8080 41 | depends_on: 42 | - postgres 43 | - nest 44 | restart: always 45 | env_file: ./packages/hasura/.env 46 | volumes: 47 | - ./packages/hasura/migrations:/hasura-migrations 48 | 49 | redis: 50 | container_name: record-pool-redis 51 | image: bitnami/redis:5.0.6 52 | restart: always 53 | ports: 54 | - 6379:6379 55 | environment: 56 | - REDIS_PASSWORD=redis 57 | volumes: 58 | - redis_data:/bitnami/redis/data 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recordpool", 3 | "private": true, 4 | "devDependencies": { 5 | "expo-yarn-workspaces": "^1.5.1", 6 | "patch-package": "^6.4.7" 7 | }, 8 | "workspaces": [ 9 | "packages/app", 10 | "packages/expo-next", 11 | "packages/radix/*" 12 | ], 13 | "scripts": { 14 | "start": "cd packages/expo-next && yarn start:expo", 15 | "dev": "cd packages/expo-next && yarn dev", 16 | "dev:hasura": "cd packages/hasura && yarn dev", 17 | "deploy": "vercel --prod --no-clipboard", 18 | "up": "./scripts/up.sh", 19 | "down": "docker-compose down --remove-orphans --volumes", 20 | "logs": "docker-compose logs -f --tail 100", 21 | "ssh": "./scripts/ssh.sh", 22 | "reset": "./scripts/reset.sh", 23 | "postinstall": "patch-package" 24 | }, 25 | "resolutions": { 26 | "html-webpack-plugin": "^5.3.1", 27 | "webpack": "^5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/app/.env.example: -------------------------------------------------------------------------------- 1 | STAGE=development 2 | HASURA_ADMIN_SECRET= -------------------------------------------------------------------------------- /packages/app/components/record/index.tsx: -------------------------------------------------------------------------------- 1 | import { Record, RECORD_HEIGHT } from 'app/components/record/record'; 2 | import { RecordSkeleton } from 'app/components/record/skeleton'; 3 | 4 | export { Record, RECORD_HEIGHT, RecordSkeleton }; 5 | -------------------------------------------------------------------------------- /packages/app/components/record/record.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { 3 | Image, 4 | useWindowDimensions, 5 | TouchableHighlight, 6 | Platform 7 | } from 'react-native'; 8 | import { styled, Text, View } from 'dripsy'; 9 | import useSWRNative from '@nandorojo/swr-react-native'; 10 | import useUnmountSignal from 'use-unmount-signal'; 11 | import { Feather } from '@expo/vector-icons'; 12 | 13 | import { getOpenKeyNotationColor } from 'app/utils/open-key'; 14 | import { fetchWithRecord } from 'app/utils/fetch'; 15 | import type { RecordFragment } from 'app/graphql/fragments/Record.fragment.generated'; 16 | import * as ContextMenu from '@radix-ui/react-context-menu'; 17 | import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; 18 | import { 19 | bottomSheetModalStyle, 20 | customHandle 21 | } from 'app/design-system/bottom-sheet'; 22 | import { Menu, menuStyle } from 'app/design-system/menu'; 23 | 24 | export const RECORD_HEIGHT = 70; 25 | 26 | const StyledTouchableHighlight = styled(TouchableHighlight)({}); 27 | 28 | type Props = { 29 | record: RecordFragment; 30 | openPlayer: (record: RecordFragment) => void; 31 | isMovable?: boolean; 32 | setIsMenuOpen?: (isMenuOpen: boolean) => void; 33 | }; 34 | 35 | function RecordContent({ 36 | record, 37 | openPlayer, 38 | isMovable = false, 39 | setIsMenuOpen 40 | }: Props) { 41 | const dimensions = useWindowDimensions(); 42 | const shouldFetch = !record?.id && !record?.bpm; 43 | const unmountSignal = useUnmountSignal(); 44 | const { data: metadataData, error: metadataError } = useSWRNative( 45 | ['/api/metadata', record, shouldFetch, unmountSignal], 46 | fetchWithRecord, 47 | { 48 | revalidateOnFocus: false 49 | } 50 | ); 51 | record = (metadataData as RecordFragment) ?? record; 52 | 53 | if (metadataError) console.error(metadataError); 54 | 55 | return ( 56 | openPlayer(record)} 58 | onLongPress={() => setIsMenuOpen(true)} 59 | > 60 | 70 | 78 | 79 | 84 | 94 | {record.title} 95 | 96 | 97 | 98 | {record.artist}{' '} 99 | {typeof record.duration === 'string' && 100 | record.duration?.includes(':') && <>- {record.duration} } 101 | {record.bpm && <>- {record.bpm} BPM} 102 | {record.openKey && ( 103 | <> 104 | {' '} 105 | -{' '} 106 | 111 | {record.openKey} 112 | 113 | 114 | )} 115 | 116 | 117 | 118 | {!isMovable && ( 119 | { 122 | setIsMenuOpen(true); 123 | }} 124 | > 125 | 126 | 127 | 128 | 129 | )} 130 | 131 | 132 | ); 133 | } 134 | 135 | export function Record(props: Props) { 136 | const [isMenuOpen, setIsMenuOpen] = useState(false); 137 | 138 | return ( 139 | 140 | 141 | 142 | 143 | 144 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | ); 159 | } 160 | -------------------------------------------------------------------------------- /packages/app/components/record/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View } from 'dripsy'; 3 | import { MotiView } from '@motify/components'; 4 | import { Skeleton } from '@motify/skeleton'; 5 | 6 | import { RECORD_HEIGHT } from './record'; 7 | 8 | const Spacer = ({ height = 16 }) => ; 9 | 10 | export function RecordSkeleton() { 11 | return ( 12 | 13 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /packages/app/components/records/index.tsx: -------------------------------------------------------------------------------- 1 | import { Records } from 'app/components/records/records'; 2 | import { RecordsList } from 'app/components/records/records-list'; 3 | import { RecordSkeletons } from 'app/components/records/skeletons'; 4 | 5 | export { Records, RecordsList, RecordSkeletons }; 6 | -------------------------------------------------------------------------------- /packages/app/components/records/records-list.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FlatList } from 'react-native'; 3 | 4 | import { Record } from 'app/components/record'; 5 | import type { RecordFragment } from 'app/graphql/fragments/Record.fragment.generated'; 6 | 7 | type Props = { 8 | records: RecordFragment[] | null; 9 | canDragToReorder?: boolean; 10 | openPlayer?: (record: RecordFragment) => void; 11 | isInModal?: boolean; 12 | }; 13 | 14 | export function RecordsList({ 15 | records, 16 | canDragToReorder = true, 17 | openPlayer, 18 | isInModal = false 19 | }: Props) { 20 | return ( 21 | `${item.id}-${index}`} 24 | renderItem={({ item: record }) => ( 25 | 30 | )} 31 | /> 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/app/components/records/records.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | 3 | import { RecordsList } from 'app/components/records/records-list'; 4 | import type { RecordFragment } from 'app/graphql/fragments/Record.fragment.generated'; 5 | import { RecordSkeletons } from 'app/components/records/skeletons'; 6 | 7 | type Props = { 8 | records: RecordFragment[] | null; 9 | canDragToReorder?: boolean; 10 | }; 11 | 12 | export function Records({ records, canDragToReorder }: Props) { 13 | const openPlayer = useCallback((record) => {}, []); 14 | 15 | return ( 16 | <> 17 | {!records ? ( 18 | 19 | ) : ( 20 | 25 | )} 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/app/components/records/skeletons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RecordSkeleton } from 'app/components/record'; 3 | 4 | export function RecordSkeletons() { 5 | return ( 6 | <> 7 | {[1, 2, 3].map((key) => { 8 | return ; 9 | })} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/app/config.ts: -------------------------------------------------------------------------------- 1 | import Constants from 'expo-constants'; 2 | 3 | export const STAGE = (Constants.manifest.extra.STAGE || 4 | process.env.STAGE || 5 | process.env.NEXT_PUBLIC_STAGE || 6 | 'development') as 'development' | 'staging' | 'production'; 7 | 8 | const envConfig = { 9 | shared: { 10 | stage: STAGE 11 | }, 12 | development: { 13 | website_url: 'http://localhost:3000', 14 | hasura_graphql_url: 'http://localhost:8080/v1/graphql', 15 | hasura_url: 'http://localhost:8080', 16 | hasura_hostname: 'localhost:8080', 17 | nest_graphql_url: 'http://localhost:4000/graphql', 18 | nest_url: 'http://localhost:4000', 19 | session_cookie: 'development_token', 20 | cookie_domain: 'localhost', 21 | scheme: 'com.pool.recordpool.development' 22 | }, 23 | staging: { 24 | website_url: `https://${ 25 | process.env.VERCEL_URL || 'staging.recordpool.app' 26 | }`, 27 | hasura_graphql_url: 'https://hasura.staging.recordpool.app/v1/graphql', 28 | hasura_url: 'https://hasura.staging.recordpool.app', 29 | hasura_hostname: 'hasura.staging.recordpool.app', 30 | nest_graphql_url: 'https://nest.staging.recordpool.app/graphql', 31 | nest_url: 'https://nest.staging.recordpool.app', 32 | session_cookie: 'staging_token', 33 | cookie_domain: '.staging.recordpool.app', 34 | scheme: 'com.pool.recordpool.staging' 35 | }, 36 | production: { 37 | website_url: `https://recordpool.app`, 38 | hasura_graphql_url: 'https://hasura.recordpool.app/v1/graphql', 39 | hasura_url: 'https://hasura.recordpool.app', 40 | hasura_hostname: 'hasura.recordpool.app', 41 | nest_graphql_url: 'https://nest.recordpool.app/graphql', 42 | nest_url: 'https://nest.recordpool.app', 43 | session_cookie: 'production_token', 44 | cookie_domain: '.recordpool.app', 45 | scheme: 'com.pool.recordpool' 46 | } 47 | }; 48 | 49 | export const config = { 50 | ...envConfig.shared, 51 | ...envConfig[STAGE] 52 | }; 53 | -------------------------------------------------------------------------------- /packages/app/design-system/bottom-sheet/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View } from 'dripsy'; 3 | 4 | export function customHandle() { 5 | return ( 6 | 17 | 25 | 26 | ); 27 | } 28 | 29 | export const bottomSheetModalStyle = { 30 | backgroundColor: 'black', 31 | shadowColor: 'black', 32 | shadowOffset: { 33 | width: 0, 34 | height: -12 35 | }, 36 | shadowOpacity: 0.58, 37 | shadowRadius: 16.0, 38 | elevation: 16 39 | }; 40 | -------------------------------------------------------------------------------- /packages/app/design-system/menu/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Platform } from 'react-native'; 3 | import { Text, View, Pressable } from 'dripsy'; 4 | 5 | import * as ContextMenu from '@radix-ui/react-context-menu'; 6 | import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; 7 | 8 | export function Menu({ 9 | setIsMenuOpen, 10 | isContextMenu = false 11 | }: { 12 | setIsMenuOpen: (isMenuOpen: boolean) => void; 13 | isContextMenu?: boolean; 14 | }) { 15 | return ( 16 | 22 | { 25 | console.log('Add to crate'); 26 | setIsMenuOpen(false); 27 | }} 28 | isContextMenu={isContextMenu} 29 | /> 30 | { 33 | console.log('Add to playlist'); 34 | setIsMenuOpen(false); 35 | }} 36 | isContextMenu={isContextMenu} 37 | /> 38 | { 41 | console.log('Share'); 42 | setIsMenuOpen(false); 43 | }} 44 | isContextMenu={isContextMenu} 45 | /> 46 | 47 | ); 48 | } 49 | 50 | function MenuItem({ action, title, isContextMenu = false }) { 51 | const Menu = isContextMenu ? ContextMenu : DropdownMenu; 52 | 53 | return ( 54 | 55 | {({ hovered }) => ( 56 | 67 | {title} 68 | 69 | )} 70 | 71 | ); 72 | } 73 | 74 | export const menuStyle = { 75 | minWidth: 130, 76 | backgroundColor: '#212121', 77 | borderRadius: 2, 78 | border: '1px solid rgba(255, 255, 255, 0.1)', 79 | paddingTop: 8, 80 | paddingBottom: 8 81 | }; 82 | -------------------------------------------------------------------------------- /packages/app/design-system/theme.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | const webFont = (font: string) => 4 | Platform.select({ 5 | web: `"${font}", Arial, Helvetica Neue, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`, 6 | default: font 7 | }); 8 | 9 | const theme = { 10 | colors: { 11 | text: '#fff', 12 | background: '#000' 13 | }, 14 | fonts: { 15 | root: 'Arial Rounded MT Std' 16 | }, 17 | customFonts: { 18 | 'Arial Rounded MT Std': { 19 | bold: webFont('Arial Rounded MT Std Bold'), 20 | default: webFont('Arial Rounded MT Std'), 21 | normal: webFont('Arial Rounded MT Std'), 22 | '400': webFont('Arial Rounded MT Std'), 23 | '500': webFont('Arial Rounded MT Std'), 24 | '600': webFont('Arial Rounded MT Std Bold'), 25 | '700': webFont('Arial Rounded MT Std Bold'), 26 | '800': webFont('Arial Rounded MT Std Bold'), 27 | '900': webFont('Arial Rounded MT Std Extra Bold') 28 | } 29 | }, 30 | space: [10, 12, 14], 31 | text: { 32 | thick: { 33 | fontFamily: 'root', 34 | fontWeight: 'black' 35 | } 36 | } 37 | }; 38 | 39 | export default theme; 40 | -------------------------------------------------------------------------------- /packages/app/graphql-codegen.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overwrite: true, 3 | schema: [ 4 | { 5 | 'http://localhost:8080/v1/graphql': { 6 | headers: { 7 | 'X-Hasura-Admin-Secret': process.env.HASURA_ADMIN_SECRET, 8 | 'X-Hasura-Role': 'user', 9 | 'X-Hasura-User-Id': '0cc65efe-d2e7-4529-ae88-265e126149e2' 10 | } 11 | } 12 | } 13 | ], 14 | documents: ['**/*.graphql'], 15 | generates: { 16 | 'graphql/types.ts': { 17 | plugins: ['typescript'] 18 | }, 19 | 'graphql/': { 20 | preset: 'near-operation-file', 21 | presetConfig: { 22 | baseTypesPath: 'types.ts' 23 | }, 24 | config: { 25 | withHooks: true, 26 | withComponent: false, 27 | withHOC: false, 28 | withMutationFn: false, 29 | withRefetchFn: false, 30 | addDocBlocks: false, 31 | reactApolloVersion: 3, 32 | preResolveTypes: true, 33 | dedupeOperationSuffix: true 34 | }, 35 | plugins: [ 36 | { 37 | add: { 38 | content: [ 39 | '// This file was generated using GraphQL Codegen. Command: `yarn generate-graphql-code`', 40 | '// For more info and docs, visit https://graphql-code-generator.com/' 41 | ] 42 | } 43 | }, 44 | 'typescript-operations', 45 | 'typescript-react-apollo' 46 | ] 47 | }, 48 | './graphql.schema.json': { 49 | plugins: ['introspection'] 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /packages/app/graphql/fragments/CurrentUser.fragment.generated.ts: -------------------------------------------------------------------------------- 1 | // This file was generated using GraphQL Codegen. Command: `yarn generate-graphql-code` 2 | // For more info and docs, visit https://graphql-code-generator.com/ 3 | import * as Types from '../types'; 4 | 5 | import { UserFragment } from './User.fragment.generated'; 6 | import { RecordFragment } from './Record.fragment.generated'; 7 | import { gql } from '@apollo/client'; 8 | import { UserFragmentDoc } from './User.fragment.generated'; 9 | import { RecordFragmentDoc } from './Record.fragment.generated'; 10 | 11 | 12 | export type CurrentUserFragment = ( 13 | { __typename?: 'users', email: string, records: Array<{ __typename?: 'users_records', position: number, record: ( 14 | { __typename?: 'records' } 15 | & RecordFragment 16 | ) }> } 17 | & UserFragment 18 | ); 19 | 20 | export const CurrentUserFragmentDoc = gql` 21 | fragment CurrentUser on users { 22 | ...User 23 | email 24 | records: users_records(order_by: {position: asc}) { 25 | record { 26 | ...Record 27 | } 28 | position 29 | } 30 | } 31 | ${UserFragmentDoc} 32 | ${RecordFragmentDoc}`; -------------------------------------------------------------------------------- /packages/app/graphql/fragments/CurrentUser.fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment CurrentUser on users { 2 | ...User 3 | email 4 | records: users_records(order_by: { position: asc }) { 5 | record { 6 | ...Record 7 | } 8 | position 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/app/graphql/fragments/Record.fragment.generated.ts: -------------------------------------------------------------------------------- 1 | // This file was generated using GraphQL Codegen. Command: `yarn generate-graphql-code` 2 | // For more info and docs, visit https://graphql-code-generator.com/ 3 | import * as Types from '../types'; 4 | 5 | import { gql } from '@apollo/client'; 6 | 7 | 8 | export type RecordFragment = { __typename?: 'records', id: any, album?: Types.Maybe, artist?: Types.Maybe, bpm?: Types.Maybe, duration?: Types.Maybe, genre?: Types.Maybe, key?: Types.Maybe, label?: Types.Maybe, mix?: Types.Maybe, name?: Types.Maybe, slug?: Types.Maybe, image?: Types.Maybe, title?: Types.Maybe, youtubeId?: Types.Maybe, beatportId?: Types.Maybe, durationSeconds?: Types.Maybe, openKey?: Types.Maybe, createdAt: any, updatedAt: any }; 9 | 10 | export const RecordFragmentDoc = gql` 11 | fragment Record on records { 12 | id 13 | youtubeId: youtube_id 14 | beatportId: beatport_id 15 | album 16 | artist 17 | bpm 18 | duration 19 | durationSeconds: duration_seconds 20 | genre 21 | key 22 | openKey: open_key 23 | label 24 | mix 25 | name 26 | slug 27 | image 28 | title 29 | createdAt: created_at 30 | updatedAt: updated_at 31 | } 32 | `; -------------------------------------------------------------------------------- /packages/app/graphql/fragments/Record.fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment Record on records { 2 | id 3 | youtubeId: youtube_id 4 | beatportId: beatport_id 5 | album 6 | artist 7 | bpm 8 | duration 9 | durationSeconds: duration_seconds 10 | genre 11 | key 12 | openKey: open_key 13 | label 14 | mix 15 | name 16 | slug 17 | image 18 | title 19 | createdAt: created_at 20 | updatedAt: updated_at 21 | } 22 | -------------------------------------------------------------------------------- /packages/app/graphql/fragments/User.fragment.generated.ts: -------------------------------------------------------------------------------- 1 | // This file was generated using GraphQL Codegen. Command: `yarn generate-graphql-code` 2 | // For more info and docs, visit https://graphql-code-generator.com/ 3 | import * as Types from '../types'; 4 | 5 | import { gql } from '@apollo/client'; 6 | 7 | 8 | export type UserFragment = { __typename?: 'users', id: any, picture?: Types.Maybe, locale?: Types.Maybe, firstName?: Types.Maybe, lastName?: Types.Maybe, lastSeenAt?: Types.Maybe }; 9 | 10 | export const UserFragmentDoc = gql` 11 | fragment User on users { 12 | id 13 | firstName: first_name 14 | lastName: last_name 15 | picture 16 | lastSeenAt: last_seen_at 17 | locale 18 | } 19 | `; -------------------------------------------------------------------------------- /packages/app/graphql/fragments/User.fragment.graphql: -------------------------------------------------------------------------------- 1 | fragment User on users { 2 | id 3 | firstName: first_name 4 | lastName: last_name 5 | picture 6 | lastSeenAt: last_seen_at 7 | locale 8 | } 9 | -------------------------------------------------------------------------------- /packages/app/hooks/use-debounce.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | export function useDebounce(value: any, delay: number) { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | 11 | return () => { 12 | clearTimeout(handler); 13 | }; 14 | }, [value, delay]); 15 | 16 | return debouncedValue; 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/index.js: -------------------------------------------------------------------------------- 1 | // We need this empty file for `next-transpile-modules` 2 | -------------------------------------------------------------------------------- /packages/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "types": "index.js", 6 | "scripts": { 7 | "dev": "yarn generate-graphql-code:watch", 8 | "generate-graphql-code": "graphql-codegen --config graphql-codegen.js -r dotenv/config .env", 9 | "generate-graphql-code:watch": "yarn generate-graphql-code -w" 10 | }, 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /packages/app/screens/CrateScreen.tsx: -------------------------------------------------------------------------------- 1 | export default function Screen() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/screens/HomeScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { TextInput, Platform } from 'react-native'; 3 | import { styled, View, Text } from 'dripsy'; 4 | import useSWRNative from '@nandorojo/swr-react-native'; 5 | import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; 6 | import useUnmountSignal from 'use-unmount-signal'; 7 | 8 | import { Records } from 'app/components/records'; 9 | import { useDebounce } from 'app/hooks/use-debounce'; 10 | import { fetchWithSearch } from 'app/utils/fetch'; 11 | 12 | const StyledTextInput = styled(TextInput)({ 13 | height: 40, 14 | color: 'white', 15 | backgroundColor: '#666666', 16 | borderRadius: 18, 17 | paddingX: 8, 18 | margin: 8, 19 | fontFamily: 'Arial Rounded MT Std', 20 | paddingTop: 4 21 | }); 22 | 23 | export default function HomeScreen() { 24 | const [search, setSearch] = useState('the man with the red face'); 25 | const debouncedSearch = useDebounce(search, 500); 26 | const unmountSignal = useUnmountSignal(); 27 | const { data, error } = useSWRNative( 28 | ['/api/search', debouncedSearch, unmountSignal], 29 | fetchWithSearch, 30 | { 31 | revalidateOnFocus: false, 32 | dedupingInterval: 2000 33 | } 34 | ); 35 | 36 | if (error) { 37 | console.error(error); 38 | 39 | return Error; 40 | } 41 | 42 | return ( 43 | 44 | 45 | setSearch(text)} 47 | value={search} 48 | autoCorrect={false} 49 | clearButtonMode="always" 50 | keyboardAppearance="dark" 51 | returnKeyType="search" 52 | /> 53 | 54 | 55 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /packages/app/screens/NotFoundScreen.tsx: -------------------------------------------------------------------------------- 1 | export default function Screen() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/screens/PlaylistsScreen.tsx: -------------------------------------------------------------------------------- 1 | export default function Screen() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/screens/ProfileScreen.tsx: -------------------------------------------------------------------------------- 1 | export default function Screen() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/utils/fetch.ts: -------------------------------------------------------------------------------- 1 | import type { RecordFragment } from 'app/graphql/fragments/Record.fragment.generated'; 2 | 3 | const API = 'https://recordpool.app'; 4 | 5 | export const fetchWithRecord = async ( 6 | url: string, 7 | record: RecordFragment, 8 | shouldFetch: boolean, 9 | unmountSignal: AbortSignal 10 | ) => { 11 | if (!shouldFetch) return null; 12 | 13 | if (record) { 14 | try { 15 | const response = await fetch(`${API}${url}`, { 16 | method: 'POST', 17 | headers: { 18 | Accept: 'application/json', 19 | 'Content-Type': 'application/json' 20 | }, 21 | body: JSON.stringify({ record }), 22 | signal: unmountSignal 23 | }); 24 | return await response.json(); 25 | } catch (error) {} 26 | } 27 | }; 28 | 29 | export const fetchWithSearch = async (url: string, search: string) => { 30 | if (search && search !== '') { 31 | try { 32 | const response = await fetch(`${API}${url}`, { 33 | method: 'POST', 34 | headers: { 35 | Accept: 'application/json', 36 | 'Content-Type': 'application/json' 37 | }, 38 | body: JSON.stringify({ search }) 39 | }); 40 | return await response.json(); 41 | } catch (error) { 42 | console.error(error); 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /packages/app/utils/init-apollo.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloClient, 3 | InMemoryCache, 4 | HttpLink, 5 | NormalizedCacheObject, 6 | ApolloLink, 7 | split 8 | } from '@apollo/client'; 9 | 10 | import { WebSocketLink } from '@apollo/client/link/ws'; 11 | import { setContext } from '@apollo/client/link/context'; 12 | import { RetryLink } from '@apollo/client/link/retry'; 13 | import { onError } from '@apollo/client/link/error'; 14 | import { getMainDefinition } from '@apollo/client/utilities'; 15 | import { createUploadLink } from 'apollo-upload-client'; 16 | 17 | import { config } from '../config'; 18 | import { typePolicies } from './type-policies'; 19 | import { isServer } from './is-server'; 20 | // import { getToken } from './token'; 21 | 22 | let globalApolloClient: ApolloClient | null = null; 23 | 24 | const scheme = (proto: string) => { 25 | return config.stage === 'development' ? proto : `${proto}s`; 26 | }; 27 | 28 | const wsurl = `${scheme('ws')}://${config.hasura_hostname}/v1/graphql`; 29 | const httpurl = `${scheme('http')}://${config.hasura_hostname}/v1/graphql`; 30 | 31 | interface CreateOptions { 32 | initialState?: NormalizedCacheObject; 33 | jwt?: string; 34 | } 35 | 36 | function createCache(initialState = {}) { 37 | return new InMemoryCache({ 38 | typePolicies 39 | }).restore(initialState); 40 | } 41 | 42 | function getAuthMiddleware(jwt?: string) { 43 | return setContext(async (req, { headers }) => { 44 | const token = jwt; // ?? (await getToken()); 45 | 46 | if (!token) return { headers }; 47 | 48 | // Add the authorization to the headers 49 | return { 50 | headers: { 51 | ...headers, 52 | Authorization: token ? `Bearer ${token}` : '' 53 | } 54 | }; 55 | }); 56 | } 57 | 58 | const errorLink = onError(({ graphQLErrors, networkError }) => { 59 | if (graphQLErrors) 60 | graphQLErrors.map(({ message, extensions }) => 61 | console.error( 62 | `[GraphQL error]: Message: ${message}, Code: ${extensions?.code}` 63 | ) 64 | ); 65 | 66 | if (networkError) 67 | console.error( 68 | `[Network error]: ${networkError.name} ${networkError.message}` 69 | ); 70 | }); 71 | 72 | function createClient({ initialState, jwt }: CreateOptions = {}) { 73 | // Create an http link 74 | const httpLink = new HttpLink({ 75 | uri: httpurl 76 | }); 77 | 78 | let link; 79 | 80 | if (!isServer) { 81 | // Create an upload link 82 | const multipartLink = createUploadLink({ 83 | uri: config.nest_graphql_url 84 | }); 85 | 86 | // Create a WebSocket link 87 | const wsLink = new WebSocketLink({ 88 | uri: wsurl, 89 | options: { 90 | lazy: true, 91 | reconnect: true, 92 | connectionParams: async () => { 93 | const token = jwt; // ?? (await getToken()); 94 | 95 | if (!token) return {}; 96 | 97 | return { 98 | headers: { 99 | Authorization: token ? `Bearer ${token}` : '' 100 | } 101 | }; 102 | } 103 | } 104 | }); 105 | 106 | const subscriptionOrQueryLink = split( 107 | // Split based on operation type 108 | ({ query }) => { 109 | const definition = getMainDefinition(query); 110 | return ( 111 | definition.kind === 'OperationDefinition' && 112 | definition.operation === 'subscription' 113 | ); 114 | }, 115 | wsLink, 116 | httpLink 117 | ); 118 | 119 | // We need send multipart GraphQL requests directly to API 120 | // see: https://github.com/hasura/graphql-engine/issues/2419 121 | link = split( 122 | ({ query }) => { 123 | const definition = getMainDefinition(query); 124 | 125 | const isSingleUpload = definition.variableDefinitions 126 | ? definition.variableDefinitions.some( 127 | ({ type }) => 128 | type.kind === 'NonNullType' && 129 | type.type.kind === 'NamedType' && 130 | type.type.name.value === 'Upload' 131 | ) 132 | : false; 133 | 134 | const isMultiUpload = definition.variableDefinitions 135 | ? definition.variableDefinitions.some( 136 | ({ type }) => 137 | type.kind === 'ListType' && 138 | type.type.kind === 'NonNullType' && 139 | type.type.type.kind === 'NamedType' && 140 | type.type.type.name.value === 'Upload' 141 | ) 142 | : false; 143 | 144 | return isSingleUpload || isMultiUpload; 145 | }, 146 | multipartLink, 147 | subscriptionOrQueryLink 148 | ); 149 | } 150 | 151 | const retryLink = new RetryLink(); 152 | const authMiddleware = getAuthMiddleware(jwt); 153 | const cache = createCache(initialState); 154 | 155 | return new ApolloClient({ 156 | ssrMode: isServer, 157 | link: ApolloLink.from([ 158 | errorLink, 159 | retryLink, 160 | authMiddleware, 161 | !isServer ? link : httpLink 162 | ]), 163 | cache 164 | }); 165 | } 166 | 167 | export default function initApollo({ initialState, jwt }: CreateOptions = {}) { 168 | const _apolloClient = 169 | globalApolloClient ?? createClient({ initialState, jwt }); 170 | 171 | // If your page has Next.js data fetching methods that use Apollo Client, 172 | // the initial state gets hydrated here 173 | if (initialState) { 174 | const previousState = _apolloClient.cache.extract() || {}; 175 | const nextState = { 176 | ...initialState, 177 | ...previousState, 178 | ROOT_QUERY: { 179 | ...(initialState.ROOT_QUERY || {}), 180 | ...(previousState.ROOT_QUERY || {}) 181 | } 182 | }; 183 | _apolloClient.cache.restore(nextState); 184 | } 185 | 186 | // For Next.js SSG and SSR always create a new Apollo Client 187 | if (isServer) return _apolloClient; 188 | 189 | // Create the Apollo Client once in the client 190 | if (!globalApolloClient) globalApolloClient = _apolloClient; 191 | 192 | return _apolloClient; 193 | } 194 | 195 | export function useApollo({ initialState, jwt }: CreateOptions = {}) { 196 | const client = jwt 197 | ? initApollo({ initialState, jwt }) 198 | : initApollo({ initialState }); 199 | 200 | return client; 201 | } 202 | -------------------------------------------------------------------------------- /packages/app/utils/is-desktop.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | import { isMobile } from './is-mobile'; 4 | 5 | export const isDesktop = Platform.OS === 'web' && !isMobile; 6 | -------------------------------------------------------------------------------- /packages/app/utils/is-mobile.ts: -------------------------------------------------------------------------------- 1 | import { isServer } from './is-server'; 2 | 3 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent 4 | export const isMobile = 5 | !isServer && 6 | (('maxTouchPoints' in navigator && navigator?.maxTouchPoints > 0) || 7 | ('msMaxTouchPoints' in navigator && navigator?.msMaxTouchPoints > 0) || 8 | (window?.matchMedia && 9 | matchMedia('(pointer:coarse)').media === '(pointer:coarse)') || 10 | /android/i.test(navigator?.userAgent) || 11 | /iPad|iPhone|iPod|ios/.test(navigator?.userAgent)); 12 | -------------------------------------------------------------------------------- /packages/app/utils/is-server.ts: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | export const isServer = Platform.OS === 'web' && typeof window === 'undefined'; 4 | -------------------------------------------------------------------------------- /packages/app/utils/open-key.ts: -------------------------------------------------------------------------------- 1 | export function getOpenKeyNotationColor(key: string) { 2 | const map = { 3 | '1d': '#FE3FEA', 4 | '1m': '#FE3FEA', 5 | '2d': '#AB64FD', 6 | '2m': '#AB64FD', 7 | '3d': '#3E8AFD', 8 | '3m': '#3E8AFD', 9 | '4d': '#00C9FE', 10 | '4m': '#00C9FE', 11 | '5d': '#00E7E7', 12 | '5m': '#00E7E7', 13 | '6d': '#00D58F', 14 | '6m': '#00D58F', 15 | '7d': '#3DEF3D', 16 | '7m': '#3DEF3D', 17 | '8d': '#97FD00', 18 | '8m': '#97FD00', 19 | '9d': '#FED600', 20 | '9m': '#FED600', 21 | '10d': '#F98C28', 22 | '10m': '#F98C28', 23 | '11d': '#FE642D', 24 | '11m': '#FE642D', 25 | '12d': '#FC4949', 26 | '12m': '#FC4949' 27 | }; 28 | 29 | return map[key]; 30 | } 31 | 32 | export function getOpenKeyNotation(key: string) { 33 | // In the Open Key Notation, a key X typically harmonizes with itself and the keys X±1 (with 0=12 and 1=13). 34 | // Additionally, every key can be in a mode, i.e. major or minor. 35 | // This is denoted by a d for Dur (major) and an m for Moll (minor). 36 | // As long as the number stays the same, you can switch between d and m. 37 | 38 | // Flat: ♭ 39 | // Sharp: ♯ 40 | // Minor: min / m 41 | // Major: maj / d 42 | 43 | const map = {}; 44 | 45 | [ 46 | 'A min', 47 | 'E min', 48 | 'B min', 49 | 'F♯ min', 50 | 'C♯ min', 51 | 'G♯ min', 52 | 'D♯ min', 53 | 'B♭ min', 54 | 'F min', 55 | 'C min', 56 | 'G min', 57 | 'D min' 58 | ].map((key, index) => (map[key] = `${index + 1}m`)); 59 | 60 | [ 61 | 'C maj', 62 | 'G maj', 63 | 'D maj', 64 | 'A maj', 65 | 'E maj', 66 | 'B maj', 67 | 'F♯ maj', 68 | 'D♭ maj', 69 | 'A♭ maj', 70 | 'E♭ maj', 71 | 'B♭ maj', 72 | 'F maj' 73 | ].map((key, index) => (map[key] = `${index + 1}d`)); 74 | 75 | map['E♭ min'] = map['D♯ min']; 76 | map['D♭ min'] = map['C♯ min']; 77 | map['A♭ min'] = map['G♯ min']; 78 | map['A♯ min'] = map['B♭ min']; 79 | 80 | map['G♭ maj'] = map['F♯ maj']; 81 | map['C♯ maj'] = map['D♭ maj']; 82 | map['D♯ maj'] = map['E♭ maj']; 83 | map['G♯ maj'] = map['A♭ maj']; 84 | map['A♯ maj'] = map['B♭ maj']; 85 | 86 | return map[key]; 87 | } 88 | -------------------------------------------------------------------------------- /packages/app/utils/type-policies.ts: -------------------------------------------------------------------------------- 1 | // Type policies for Apollo Cache 2 | // https://www.apollographql.com/docs/react/caching/cache-field-behavior/ 3 | 4 | export const typePolicies = {}; 5 | -------------------------------------------------------------------------------- /packages/expo-next/.env.local: -------------------------------------------------------------------------------- 1 | STAGE=production 2 | NEXT_PUBLIC_STAGE=production 3 | -------------------------------------------------------------------------------- /packages/expo-next/.env.local.example: -------------------------------------------------------------------------------- 1 | STAGE=production 2 | NEXT_PUBLIC_STAGE=production -------------------------------------------------------------------------------- /packages/expo-next/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /packages/expo-next/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | .next/* 4 | npm-debug.* 5 | yarn-error.* 6 | *.jks 7 | *.p8 8 | *.p12 9 | *.key 10 | *.mobileprovision 11 | *.orig.* 12 | web-build/ 13 | __generated__/ 14 | # @generated expo-cli sync-2138f1e3e130677ea10ea873f6d498e3890e677b 15 | # The following patterns were generated by expo-cli 16 | 17 | # OSX 18 | # 19 | .DS_Store 20 | 21 | # Xcode 22 | # 23 | build/ 24 | *.pbxuser 25 | !default.pbxuser 26 | *.mode1v3 27 | !default.mode1v3 28 | *.mode2v3 29 | !default.mode2v3 30 | *.perspectivev3 31 | !default.perspectivev3 32 | xcuserdata 33 | *.xccheckout 34 | *.moved-aside 35 | DerivedData 36 | *.hmap 37 | *.ipa 38 | *.xcuserstate 39 | project.xcworkspace 40 | 41 | # Android/IntelliJ 42 | # 43 | build/ 44 | .idea 45 | .gradle 46 | local.properties 47 | *.iml 48 | *.hprof 49 | 50 | # node.js 51 | # 52 | node_modules/ 53 | npm-debug.log 54 | yarn-error.log 55 | 56 | # BUCK 57 | buck-out/ 58 | \.buckd/ 59 | *.keystore 60 | !debug.keystore 61 | 62 | # Bundle artifacts 63 | *.jsbundle 64 | 65 | # CocoaPods 66 | /ios/Pods/ 67 | 68 | # Expo 69 | .expo/ 70 | web-build/ 71 | 72 | # @end expo-cli -------------------------------------------------------------------------------- /packages/expo-next/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as Updates from 'expo-updates'; 3 | import { enableScreens } from 'react-native-screens'; 4 | import { DripsyProvider } from 'dripsy'; 5 | import { useFonts } from 'expo-font'; 6 | import { StatusBar } from 'expo-status-bar'; 7 | import { SafeAreaProvider } from 'react-native-safe-area-context'; 8 | import { Feather } from '@expo/vector-icons'; 9 | import AppLoading from 'expo-app-loading'; 10 | import { ApolloProvider } from '@apollo/client'; 11 | import { IdProvider } from '@radix-ui/react-id'; 12 | 13 | enableScreens(); 14 | 15 | import Navigation from './src/navigation'; 16 | import theme from 'app/design-system/theme'; 17 | import { useApollo } from 'app/utils/init-apollo'; 18 | 19 | export default function App() { 20 | const apolloClient = useApollo(); 21 | const [fontsLoaded, error] = useFonts({ 22 | ...Feather.font, 23 | 'Arial Rounded MT Std': require('./public/fonts/ArialRoundedMTStd.ttf'), 24 | 'Arial Rounded MT Std Bold': require('./public/fonts/ArialRoundedMTStdBold.ttf'), 25 | 'Arial Rounded MT Std Extra Bold': require('./public/fonts/ArialRoundedMTStdExtraBold.ttf'), 26 | 'Arial Rounded MT Std Light': require('./public/fonts/ArialRoundedMTStdLight.ttf') 27 | }); 28 | 29 | if (error) { 30 | console.error(error); 31 | } 32 | 33 | if (!fontsLoaded) { 34 | return ; 35 | } 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /packages/expo-next/app.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Record Pool', 3 | description: 'Music discovery and playlists management', 4 | slug: 'recordpool', 5 | scheme: 'recordpool', 6 | icon: './assets/icon.png', 7 | sdkVersion: '41.0.0', 8 | version: '0.0.1', 9 | splash: { 10 | image: './assets/splash.png', 11 | resizeMode: 'contain', 12 | backgroundColor: '#000000' 13 | }, 14 | ios: { 15 | bundleIdentifier: '', 16 | supportsTablet: true, 17 | infoPlist: { 18 | UIBackgroundModes: ['audio'] 19 | } 20 | }, 21 | android: { 22 | package: '', 23 | versionCode: 1 24 | }, 25 | assetBundlePatterns: ['**/*'], 26 | updates: { 27 | fallbackToCacheTimeout: 0 28 | }, 29 | packagerOpts: { 30 | config: 'metro.config.js', 31 | sourceExts: [ 32 | 'expo.ts', 33 | 'expo.tsx', 34 | 'expo.js', 35 | 'expo.jsx', 36 | 'ts', 37 | 'tsx', 38 | 'js', 39 | 'jsx', 40 | 'json', 41 | 'wasm', 42 | 'svg' 43 | ] 44 | }, 45 | extra: { 46 | STAGE: process.env.STAGE 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /packages/expo-next/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/assets/icon.png -------------------------------------------------------------------------------- /packages/expo-next/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/assets/splash.png -------------------------------------------------------------------------------- /packages/expo-next/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@expo/next-adapter/babel'], 3 | // Note: If you load other babel plugins, the Reanimated plugin has to be listed last in the plugins array. 4 | plugins: [ 5 | [ 6 | '@babel/plugin-transform-react-jsx', 7 | { 8 | pragmaFrag: 'React.Fragment' 9 | } 10 | ], 11 | 'react-native-reanimated/plugin' 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /packages/expo-next/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": { 3 | "android": { 4 | "release": { 5 | "workflow": "managed", 6 | "releaseChannel": "production", 7 | "env": { 8 | "STAGE": "production" 9 | } 10 | }, 11 | "preview": { 12 | "workflow": "managed", 13 | "releaseChannel": "staging", 14 | "distribution": "internal", 15 | "env": { 16 | "STAGE": "staging" 17 | } 18 | }, 19 | "development": { 20 | "workflow": "managed", 21 | "distribution": "internal", 22 | "gradleCommand": ":app:assembleDebug" 23 | } 24 | }, 25 | "ios": { 26 | "release": { 27 | "workflow": "managed", 28 | "releaseChannel": "production", 29 | "env": { 30 | "STAGE": "production" 31 | } 32 | }, 33 | "preview": { 34 | "workflow": "managed", 35 | "releaseChannel": "staging", 36 | "distribution": "internal", 37 | "env": { 38 | "STAGE": "staging" 39 | } 40 | }, 41 | "development": { 42 | "workflow": "managed", 43 | "distribution": "internal", 44 | "schemeBuildConfiguration": "Debug" 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/expo-next/index.js: -------------------------------------------------------------------------------- 1 | import 'expo/build/Expo.fx'; 2 | import { activateKeepAwake } from 'expo-keep-awake'; 3 | import registerRootComponent from 'expo/build/launch/registerRootComponent'; 4 | 5 | import App from './App'; 6 | 7 | if (__DEV__) { 8 | activateKeepAwake(); 9 | } 10 | 11 | registerRootComponent(App); 12 | -------------------------------------------------------------------------------- /packages/expo-next/metro.config.js: -------------------------------------------------------------------------------- 1 | const { createMetroConfiguration } = require('expo-yarn-workspaces'); 2 | const { getDefaultConfig } = require('expo/metro-config'); 3 | 4 | const config = createMetroConfiguration(__dirname); 5 | 6 | module.exports = (async () => { 7 | const { 8 | resolver: { sourceExts, assetExts } 9 | } = await getDefaultConfig(__dirname); 10 | 11 | return { 12 | ...config, 13 | transformer: { 14 | ...config.transformer, 15 | babelTransformerPath: require.resolve('react-native-svg-transformer') 16 | }, 17 | resolver: { 18 | ...config.resolver, 19 | assetExts: assetExts.filter((ext) => ext !== 'svg'), 20 | sourceExts: [...sourceExts, 'svg'] 21 | } 22 | }; 23 | })(); 24 | -------------------------------------------------------------------------------- /packages/expo-next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/expo-next/next.config.js: -------------------------------------------------------------------------------- 1 | const { withExpo } = require('@expo/next-adapter'); 2 | const withFonts = require('next-fonts'); 3 | const withImages = require('next-images'); 4 | const withPlugins = require('next-compose-plugins'); 5 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 6 | enabled: process.env.ANALYZE === 'true' 7 | }); 8 | const withTM = require('next-transpile-modules')([ 9 | 'app', 10 | 'dripsy', 11 | '@dripsy/core', 12 | 'moti', 13 | '@motify/components', 14 | '@motify/core', 15 | '@motify/skeleton', 16 | '@gorhom/bottom-sheet', 17 | '@nandorojo/swr-react-native' 18 | ]); 19 | 20 | const nextConfig = { 21 | future: { 22 | webpack5: true 23 | }, 24 | typescript: { 25 | ignoreDevErrors: true, 26 | ignoreBuildErrors: true 27 | }, 28 | images: { 29 | domains: ['lh3.googleusercontent.com'] 30 | }, 31 | async headers() { 32 | const cacheHeaders = [ 33 | { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' } 34 | ]; 35 | return [ 36 | { source: '/_next/static/:static*', headers: cacheHeaders }, 37 | { source: '/fonts/:font*', headers: cacheHeaders } 38 | ]; 39 | } 40 | }; 41 | 42 | module.exports = withPlugins( 43 | [ 44 | [withTM, {}], 45 | withFonts, 46 | withImages, 47 | withBundleAnalyzer, 48 | [withExpo, { projectRoot: __dirname }] 49 | ], 50 | nextConfig 51 | ); 52 | -------------------------------------------------------------------------------- /packages/expo-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js", 3 | "name": "expo-next", 4 | "scripts": { 5 | "start": "react-native start", 6 | "start:expo": "expo start", 7 | "start:clear": "yarn start -c", 8 | "start:web": "NODE_ENV=production next start", 9 | "start:android": "expo --android", 10 | "start:ios": "expo --ios", 11 | "android": "react-native run-android", 12 | "ios": "react-native run-ios", 13 | "eject": "expo eject", 14 | "postinstall": "expo-yarn-workspaces postinstall && cd ../.. && patch-package", 15 | "dev": "next", 16 | "build": "next build", 17 | "analyze": "ANALYZE=true yarn build", 18 | "deploy": "yarn --cwd ../../ deploy", 19 | "publish:staging": "STAGE=production expo publish --release-channel=staging", 20 | "publish:production": "STAGE=production expo publish --release-channel=production", 21 | "build:preview": "STAGE=production eas build --profile preview --platform all", 22 | "build:preview:ios": "STAGE=production eas build --profile preview --platform ios", 23 | "build:preview:android": "STAGE=production eas build --profile preview --platform android", 24 | "generate-graphql-code": "yarn --cwd ../app/ generate-graphql-code", 25 | "generate-graphql-code:watch": "yarn --cwd ../app/ generate-graphql-code:watch" 26 | }, 27 | "dependencies": { 28 | "@apollo/client": "^3.3.16", 29 | "@expo/vector-icons": "^12.0.4", 30 | "@expo/webpack-config": "^0.12.76", 31 | "@gorhom/bottom-sheet": "4.0.0-alpha.3", 32 | "@motify/skeleton": "^0.10.0", 33 | "@nandorojo/swr-react-native": "^0.1.3", 34 | "@next/bundle-analyzer": "^10.2.0", 35 | "@radix-ui/react-context-menu": "workspace:*", 36 | "@radix-ui/react-dropdown-menu": "workspace:*", 37 | "@radix-ui/react-menu": "workspace:*", 38 | "@react-native-community/netinfo": "^6.0.0", 39 | "@react-navigation/bottom-tabs": "^5.11.10", 40 | "@react-navigation/native": "^5.9.4", 41 | "@react-navigation/stack": "^5.14.4", 42 | "apollo-upload-client": "^14.1.3", 43 | "app": "*", 44 | "cors": "^2.8.5", 45 | "dripsy": "^2.2.0", 46 | "expo": "^41.0.0", 47 | "expo-app-loading": "^1.0.3", 48 | "expo-application": "^3.1.2", 49 | "expo-av": "~9.1.2", 50 | "expo-blur": "^9.0.3", 51 | "expo-constants": "^10.1.3", 52 | "expo-font": "^9.1.0", 53 | "expo-haptics": "^10.0.0", 54 | "expo-image-picker": "~10.1.4", 55 | "expo-linear-gradient": "^9.1.0", 56 | "expo-linking": "^2.2.3", 57 | "expo-secure-store": "~10.1.0", 58 | "expo-splash-screen": "~0.10.2", 59 | "expo-status-bar": "~1.0.4", 60 | "expo-updates": "~0.5.4", 61 | "got": "^11.8.2", 62 | "graphql": "^15.5.0", 63 | "jwt-decode": "^3.1.2", 64 | "moti": "^0.10.1", 65 | "next": "^10.2.0", 66 | "next-fonts": "^1.5.1", 67 | "next-images": "^1.7.0", 68 | "next-transpile-modules": "^7.0.0", 69 | "node-youtube-music": "^0.3.5", 70 | "raf": "^3.4.1", 71 | "react": "16.13.1", 72 | "react-dom": "16.13.1", 73 | "react-hook-form": "^7.6.0", 74 | "react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz", 75 | "react-native-gesture-handler": "^1.10.3", 76 | "react-native-reanimated": "^2.2.0", 77 | "react-native-safe-area-context": "^3.2.0", 78 | "react-native-screens": "~3.0.0", 79 | "react-native-unimodules": "~0.13.3", 80 | "react-native-web": "^0.16.3", 81 | "react-native-web-webview": "^1.0.2", 82 | "react-native-webview": "11.2.3", 83 | "react-native-youtube-iframe": "^2.1.0", 84 | "resize-observer-polyfill": "^1.5.1", 85 | "sass": "^1.32.12", 86 | "setimmediate": "^1.0.5", 87 | "subscriptions-transport-ws": "^0.9.18", 88 | "swr": "^0.5.5", 89 | "unimodules-app-loader": "^2.1.0", 90 | "unimodules-constants-interface": "^6.1.0", 91 | "unimodules-file-system-interface": "^6.1.0", 92 | "unimodules-font-interface": "^6.1.0", 93 | "universal-cookie": "^4.0.4", 94 | "use-unmount-signal": "^1.0.0" 95 | }, 96 | "devDependencies": { 97 | "@babel/core": "^7.14.0", 98 | "@babel/plugin-transform-react-jsx": "^7.13.12", 99 | "@expo/next-adapter": "^2.1.77", 100 | "@graphql-codegen/add": "^2.0.2", 101 | "@graphql-codegen/cli": "1.15.0", 102 | "@graphql-codegen/introspection": "^1.18.2", 103 | "@graphql-codegen/near-operation-file-preset": "^1.18.0", 104 | "@graphql-codegen/typescript": "^1.22.0", 105 | "@graphql-codegen/typescript-operations": "^1.17.16", 106 | "@graphql-codegen/typescript-react-apollo": "^2.2.4", 107 | "@types/apollo-upload-client": "^14.1.0", 108 | "@types/cors": "^2.8.10", 109 | "@types/react": "~16.9.35", 110 | "@types/react-native": "~0.63.2", 111 | "css-minimizer-webpack-plugin": "^3.0.0", 112 | "expo-yarn-workspaces": "^1.5.1", 113 | "next-compose-plugins": "^2.2.0", 114 | "react-native-svg-transformer": "^0.14.3", 115 | "typescript": "^4.3.2" 116 | }, 117 | "resolutions": { 118 | "html-webpack-plugin": "^5.3.1", 119 | "webpack": "^5" 120 | }, 121 | "private": true, 122 | "version": "1.0.0", 123 | "expo-yarn-workspaces": { 124 | "symlinks": [ 125 | "@expo/vector-icons", 126 | "@expo/next-adapter", 127 | "react-native", 128 | "react-native-unimodules", 129 | "react-native-reanimated", 130 | "react-native-safe-area-context", 131 | "expo-app-loading", 132 | "expo-application", 133 | "expo-blur", 134 | "expo-constants", 135 | "expo-error-recovery", 136 | "expo-file-system", 137 | "expo-font", 138 | "expo-haptics", 139 | "expo-image-picker", 140 | "expo-keep-awake", 141 | "expo-linear-gradient", 142 | "expo-linking", 143 | "expo-permissions", 144 | "expo-secure-store", 145 | "expo-splash-screen", 146 | "expo-status-bar", 147 | "expo-structured-headers", 148 | "expo-updates", 149 | "unimodules-app-loader", 150 | "unimodules-barcode-scanner-interface", 151 | "unimodules-camera-interface", 152 | "unimodules-constants-interface", 153 | "@unimodules/core", 154 | "unimodules-face-detector-interface", 155 | "unimodules-file-system-interface", 156 | "unimodules-font-interface", 157 | "unimodules-image-loader-interface", 158 | "unimodules-permissions-interface", 159 | "@unimodules/react-native-adapter", 160 | "unimodules-sensors-interface", 161 | "unimodules-task-manager-interface" 162 | ] 163 | }, 164 | "license": "MIT" 165 | } 166 | -------------------------------------------------------------------------------- /packages/expo-next/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/favicon.png -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std Bold.woff -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std Bold.woff2 -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std Extra Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std Extra Bold.woff -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std Extra Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std Extra Bold.woff2 -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std Light.woff -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std Light.woff2 -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std.woff -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/Arial Rounded MT Std.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/Arial Rounded MT Std.woff2 -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/ArialRoundedMTStd.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/ArialRoundedMTStd.ttf -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/ArialRoundedMTStdBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/ArialRoundedMTStdBold.ttf -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/ArialRoundedMTStdExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/ArialRoundedMTStdExtraBold.ttf -------------------------------------------------------------------------------- /packages/expo-next/public/fonts/ArialRoundedMTStdLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axeldelafosse/recordpool-universal-ui-poc/c46dcab9bb6d7249f3240b725ebb1310f4affc1b/packages/expo-next/public/fonts/ArialRoundedMTStdLight.ttf -------------------------------------------------------------------------------- /packages/expo-next/src/navigation/BottomTabNavigator.tsx: -------------------------------------------------------------------------------- 1 | import { Feather } from '@expo/vector-icons'; 2 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; 3 | // import { createNativeStackNavigator } from 'react-native-screens/native-stack'; 4 | import { createStackNavigator } from '@react-navigation/stack'; 5 | import React from 'react'; 6 | import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; 7 | 8 | import HomeScreen from 'app/screens/HomeScreen'; 9 | import CrateScreen from 'app/screens/CrateScreen'; 10 | import PlaylistsScreen from 'app/screens/PlaylistsScreen'; 11 | import ProfileScreen from 'app/screens/ProfileScreen'; 12 | 13 | const BottomTab = createBottomTabNavigator(); 14 | 15 | export default function BottomTabNavigator() { 16 | return ( 17 | 18 | 22 | 27 | }} 28 | /> 29 | ( 34 | 35 | ) 36 | }} 37 | /> 38 | 44 | }} 45 | /> 46 | 51 | }} 52 | /> 53 | 54 | 55 | ); 56 | } 57 | 58 | function TabBarIcon(props: { 59 | name: React.ComponentProps['name']; 60 | color: string; 61 | }) { 62 | return ; 63 | } 64 | 65 | // Each tab has its own navigation stack, you can read more about this pattern here: 66 | // https://reactnavigation.org/docs/tab-based-navigation#a-stack-navigator-for-each-tab 67 | const HomeStack = createStackNavigator(); 68 | 69 | function HomeNavigator() { 70 | return ( 71 | 76 | 81 | 82 | ); 83 | } 84 | 85 | const CrateStack = createStackNavigator(); 86 | 87 | function CrateNavigator() { 88 | return ( 89 | 94 | 99 | 100 | ); 101 | } 102 | 103 | const PlaylistsStack = createStackNavigator(); 104 | 105 | function PlaylistsNavigator() { 106 | return ( 107 | 112 | 117 | 118 | ); 119 | } 120 | 121 | const ProfileStack = createStackNavigator(); 122 | 123 | function ProfileNavigator() { 124 | return ( 125 | 130 | 135 | 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /packages/expo-next/src/navigation/LinkingConfiguration.ts: -------------------------------------------------------------------------------- 1 | import * as Linking from 'expo-linking'; 2 | 3 | export default { 4 | prefixes: [Linking.makeUrl('/')], 5 | config: { 6 | screens: { 7 | Root: { 8 | initialRouteName: 'Home', 9 | screens: { 10 | Home: { 11 | screens: { 12 | Home: 'home' 13 | } 14 | }, 15 | Crate: { 16 | screens: { 17 | Crate: 'crate' 18 | } 19 | }, 20 | Playlists: { 21 | screens: { 22 | Playlists: 'playlists' 23 | } 24 | }, 25 | Profile: { 26 | screens: { 27 | Profile: 'profile' 28 | } 29 | } 30 | } 31 | }, 32 | NotFound: '*' 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /packages/expo-next/src/navigation/index.tsx: -------------------------------------------------------------------------------- 1 | import { NavigationContainer, DarkTheme } from '@react-navigation/native'; 2 | // import { createNativeStackNavigator } from 'react-native-screens/native-stack'; 3 | import { createStackNavigator } from '@react-navigation/stack'; 4 | import React from 'react'; 5 | import { ColorSchemeName } from 'react-native'; 6 | 7 | import NotFoundScreen from 'app/screens/NotFoundScreen'; 8 | import BottomTabNavigator from './BottomTabNavigator'; 9 | import LinkingConfiguration from './LinkingConfiguration'; 10 | 11 | // If you are not familiar with React Navigation, we recommend going through the 12 | // "Fundamentals" guide: https://reactnavigation.org/docs/getting-started 13 | export default function Navigation({ 14 | colorScheme = 'dark' 15 | }: { 16 | colorScheme?: ColorSchemeName; 17 | }) { 18 | return ( 19 | 23 | 24 | 25 | ); 26 | } 27 | 28 | // A root stack navigator is often used for displaying modals on top of all other content 29 | // Read more here: https://reactnavigation.org/docs/modal 30 | const Stack = createStackNavigator(); 31 | 32 | function RootNavigator() { 33 | return ( 34 | 35 | 36 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/fonts.scss'; 2 | import '../styles/global.scss'; 3 | 4 | import 'raf/polyfill'; 5 | // @ts-ignore 6 | global.setImmediate = requestAnimationFrame; 7 | import 'setimmediate'; 8 | 9 | import { ApolloProvider } from '@apollo/client'; 10 | import { DripsyProvider } from 'dripsy'; 11 | import { SafeAreaProvider } from 'react-native-safe-area-context'; 12 | import Head from 'next/head'; 13 | import { AppProps } from 'next/app'; 14 | 15 | import Navigation from '../navigation'; 16 | import theme from 'app/design-system/theme'; 17 | import { useApollo } from 'app/utils/init-apollo'; 18 | 19 | export default function App({ Component, pageProps }: AppProps) { 20 | const { initialApolloState } = pageProps; 21 | const apolloClient = useApollo({ initialState: initialApolloState }); 22 | 23 | return ( 24 | <> 25 | 26 | Record Pool 27 | 28 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { getInitialProps } from '@expo/next-adapter/document'; 2 | import NextDocument, { Html, Head, Main, NextScript } from 'next/document'; 3 | import React from 'react'; 4 | 5 | class Document extends NextDocument { 6 | render() { 7 | return ( 8 | 9 | 10 | 17 | 24 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | ); 39 | } 40 | } 41 | 42 | Document.getInitialProps = getInitialProps; 43 | 44 | export default Document; 45 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/crate.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/home.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/playlists.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/privacy.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/profile.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/expo-next/src/pages/terms.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /packages/expo-next/src/styles/fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Arial Rounded MT Std'; 3 | src: url('/fonts/Arial Rounded MT Std.woff2') format('woff2'), 4 | url('/fonts/Arial Rounded MT Std.woff') format('woff'); 5 | font-weight: 400; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'Arial Rounded MT Std Bold'; 11 | src: url('/fonts/Arial Rounded MT Std Bold.woff2') format('woff2'), 12 | url('/fonts/Arial Rounded MT Std Bold.woff') format('woff'); 13 | font-weight: 700; 14 | font-style: normal; 15 | } 16 | 17 | @font-face { 18 | font-family: 'Arial Rounded MT Std Extra Bold'; 19 | src: url('/fonts/Arial Rounded MT Std Extra Bold.woff2') format('woff2'), 20 | url('/fonts/Arial Rounded MT Std Extra Bold.woff') format('woff'); 21 | font-weight: 900; 22 | font-style: normal; 23 | } 24 | -------------------------------------------------------------------------------- /packages/expo-next/src/styles/global.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | html { 8 | scrollbar-color: light; 9 | scroll-behavior: smooth; 10 | -webkit-tap-highlight-color: transparent; 11 | } 12 | 13 | body { 14 | text-rendering: optimizeLegibility; 15 | overscroll-behavior-y: none; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | } 19 | 20 | html, 21 | body, 22 | input, 23 | textarea, 24 | select, 25 | button { 26 | font-family: 'Arial Rounded MT Std', Arial, Helvetica Neue, -apple-system, 27 | BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, sans-serif, 28 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 29 | } 30 | 31 | *, 32 | *::after, 33 | *::before { 34 | box-sizing: border-box; 35 | } 36 | 37 | p { 38 | margin-block-start: auto; 39 | margin-block-end: auto; 40 | } 41 | 42 | *:not(input):not(textarea):not([contenteditable]) { 43 | -webkit-user-select: none; 44 | } 45 | 46 | // -ms-overflow-style: none; /* Internet Explorer 10+ */ 47 | // scrollbar-width: none; /* Firefox */ 48 | ::-webkit-scrollbar { 49 | display: none; /* Safari and Chrome */ 50 | } 51 | 52 | ::-webkit-scrollbar-thumb { 53 | background-color: rgba(255, 255, 255, 0.1); 54 | } 55 | 56 | ::-webkit-scrollbar-track { 57 | background-color: rgba(255, 255, 255, 0.1); 58 | } 59 | -------------------------------------------------------------------------------- /packages/expo-next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "preserve", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true, 10 | "strict": false, 11 | "target": "esnext", 12 | "allowJs": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "isolatedModules": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"], 20 | "extends": "expo/tsconfig.base" 21 | } 22 | -------------------------------------------------------------------------------- /packages/radix/context-menu/README.md: -------------------------------------------------------------------------------- 1 | # `react-context-menu` 2 | 3 | ## Installation 4 | 5 | ```sh 6 | $ yarn add @radix-ui/react-context-menu 7 | # or 8 | $ npm install @radix-ui/react-context-menu 9 | ``` 10 | 11 | ## Usage 12 | 13 | View docs [here](https://radix-ui.com/primitives/docs/components/context-menu). 14 | -------------------------------------------------------------------------------- /packages/radix/context-menu/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Primitive } from "@radix-ui/react-primitive"; 3 | import * as MenuPrimitive from "@radix-ui/react-menu"; 4 | import * as Polymorphic from "@radix-ui/react-polymorphic"; 5 | export const ContextMenu: React.FC<{ 6 | onOpenChange?(open: boolean): void; 7 | }>; 8 | declare const TRIGGER_DEFAULT_TAG: any; 9 | type ContextMenuTriggerOwnProps = Polymorphic.OwnProps; 10 | export type ContextMenuTriggerPrimitive = Polymorphic.ForwardRefComponent; 11 | export const ContextMenuTrigger: ContextMenuTriggerPrimitive; 12 | type ContextMenuContentOwnProps = Polymorphic.Merge, 'trapFocus' | 'disableOutsideScroll' | 'portalled' | 'onOpenAutoFocus' | 'side' | 'sideOffset' | 'align' | 'alignOffset'>, { 13 | offset?: number; 14 | }>; 15 | export type ContextMenuContentPrimitive = Polymorphic.ForwardRefComponent, ContextMenuContentOwnProps>; 16 | export const ContextMenuContent: ContextMenuContentPrimitive; 17 | export const ContextMenuGroup: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 18 | export const ContextMenuLabel: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 19 | export const ContextMenuItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 20 | export const ContextMenuCheckboxItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 21 | export const ContextMenuRadioGroup: import("@radix-ui/react-primitive").ExtendedPrimitive; 22 | export const ContextMenuRadioItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 23 | export const ContextMenuItemIndicator: import("@radix-ui/react-primitive").ExtendedPrimitive; 24 | export const ContextMenuSeparator: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 25 | export const Root: React.FC<{ 26 | onOpenChange?(open: boolean): void; 27 | }>; 28 | export const Trigger: ContextMenuTriggerPrimitive; 29 | export const Content: ContextMenuContentPrimitive; 30 | export const Group: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 31 | export const Label: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 32 | export const Item: import("@radix-ui/react-primitive").ExtendedPrimitive; 33 | export const CheckboxItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 34 | export const RadioGroup: import("@radix-ui/react-primitive").ExtendedPrimitive; 35 | export const RadioItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 36 | export const ItemIndicator: import("@radix-ui/react-primitive").ExtendedPrimitive; 37 | export const Separator: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 38 | 39 | //# sourceMappingURL=index.d.ts.map 40 | -------------------------------------------------------------------------------- /packages/radix/context-menu/dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"mappings":"A;A;A;A;AA2BA,OAAA,MAAM,aAAa,MAAM,EAAE,CAAC;IAAE,YAAY,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAoBjE,CAAC;AASF,QAAA,MAAM,wBAA2D,CAAC;AAElE,kCAAkC,YAAY,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AACzE,0CAAmC,YAAY,mBAAmB,CAChE,0BAA0B,EAC1B,0BAA0B,CAC3B,CAAC;AAEF,OAAA,MAAM,+CAuB2B,CAAC;AAUlC,kCAAkC,YAAY,KAAK,CACjD,IAAI,CACF,YAAY,QAAQ,CAAC,OAAO,cAAc,OAAO,CAAC,EAChD,WAAW,GACX,sBAAsB,GACtB,WAAW,GACX,iBAAiB,GACjB,MAAM,GACN,YAAY,GACZ,OAAO,GACP,aAAa,CAChB,EACD;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CACpB,CAAC;AAEF,0CAAmC,YAAY,mBAAmB,CAChE,YAAY,gBAAgB,CAAC,OAAO,cAAc,OAAO,CAAC,EAC1D,0BAA0B,CAC3B,CAAC;AAEF,OAAA,MAAM,+CAsB2B,CAAC;AAMlC,OAAA,MAAM,oMAA4F,CAAC;AACnG,OAAA,MAAM,oMAA4F,CAAC;AACnG,OAAA,MAAM,8GAAyF,CAAC;AAChG,OAAA,MAAM,8HAEJ,CAAC;AACH,OAAA,MAAM,0HAEJ,CAAC;AACH,OAAA,MAAM,wHAEJ,CAAC;AACH,OAAA,MAAM,iIAEJ,CAAC;AACH,OAAA,MAAM,wMAEJ,CAAC;AAIH,OAAA,MAAM;wBA3I4C,OAAO,GAAG,IAAI;EA2IxC,CAAC;AACzB,OAAA,MAAM,oCAA4B,CAAC;AACnC,OAAA,MAAM,oCAA4B,CAAC;AACnC,OAAA,MAAM,yLAAwB,CAAC;AAC/B,OAAA,MAAM,yLAAwB,CAAC;AAC/B,OAAA,MAAM,mGAAsB,CAAC;AAC7B,OAAA,MAAM,mHAAsC,CAAC;AAC7C,OAAA,MAAM,+GAAkC,CAAC;AACzC,OAAA,MAAM,6GAAgC,CAAC;AACvC,OAAA,MAAM,sHAAwC,CAAC;AAC/C,OAAA,MAAM,6LAAgC,CAAC","sources":["./packages/react/context-menu/src/packages/react/context-menu/src/ContextMenu.tsx","./packages/react/context-menu/src/packages/react/context-menu/src/index.ts"],"sourcesContent":[null,null],"names":[],"version":3,"file":"index.d.ts.map"} -------------------------------------------------------------------------------- /packages/radix/context-menu/dist/index.js: -------------------------------------------------------------------------------- 1 | var e,t=require("@radix-ui/react-use-callback-ref").useCallbackRef,n=d({},require("@radix-ui/react-menu")),o=require("@radix-ui/react-primitive"),r=o.Primitive,a=o.extendPrimitive,s=require("@radix-ui/react-context").createContext,i=require("@radix-ui/primitive").composeEventHandlers,u=require("react-native"),c=u.Platform,x=u.View,p=d({},require("react")),l=(e=require("@babel/runtime/helpers/extends"))&&e.__esModule?e.default:e;function d(e,t){return Object.keys(t).forEach((function(n){"default"!==n&&"__esModule"!==n&&Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[n]}})})),e}const[m,C]=s("ContextMenu"),f=e=>{const{children:o,onOpenChange:r}=e,[a,s]=p.useState(!1),i=t(r),u=p.useCallback((e=>{s(e),i(e)}),[i]);/*#__PURE__*/return p.createElement(n.Root,{open:a,onOpenChange:u},/*#__PURE__*/p.createElement(m,{open:a,onOpenChange:u},o))};exports.ContextMenu=f;const M="web"===c.OS?"span":x,b=/*#__PURE__*/p.forwardRef(((e,t)=>{const{as:o=M,...a}=e,s=C("ContextMenuTrigger"),u=p.useRef({x:0,y:0}),c=p.useRef({getBoundingClientRect:()=>DOMRect.fromRect({width:0,height:0,...u.current})});/*#__PURE__*/return p.createElement(p.Fragment,null,/*#__PURE__*/p.createElement(n.Anchor,{virtualRef:c}),/*#__PURE__*/p.createElement(r,l({},a,{as:o,ref:t,onContextMenu:i(e.onContextMenu,(e=>{e.preventDefault(),u.current={x:e.clientX,y:e.clientY},s.onOpenChange(!0)}))})))}));exports.ContextMenuTrigger=b;const I=/*#__PURE__*/p.forwardRef(((e,t)=>{const{disableOutsidePointerEvents:o=!0,offset:r,...a}=e,s=C("ContextMenuContent");/*#__PURE__*/return p.createElement(n.Content,l({},a,{ref:t,disableOutsidePointerEvents:!!s.open&&o,style:{...e.style,"--radix-context-menu-content-transform-origin":"var(--radix-popper-transform-origin)"},trapFocus:!0,disableOutsideScroll:!0,portalled:!0,side:"bottom",sideOffset:r,align:"start",alignOffset:2}))}));exports.ContextMenuContent=I;const g=a(n.Group,{displayName:"ContextMenuGroup"});exports.ContextMenuGroup=g;const R=a(n.Label,{displayName:"ContextMenuLabel"});exports.ContextMenuLabel=R;const h=a(n.Item,{displayName:"ContextMenuItem"});exports.ContextMenuItem=h;const y=a(n.CheckboxItem,{displayName:"ContextMenuCheckboxItem"});exports.ContextMenuCheckboxItem=y;const O=a(n.RadioGroup,{displayName:"ContextMenuRadioGroup"});exports.ContextMenuRadioGroup=O;const v=a(n.RadioItem,{displayName:"ContextMenuRadioItem"});exports.ContextMenuRadioItem=v;const E=a(n.ItemIndicator,{displayName:"ContextMenuItemIndicator"});exports.ContextMenuItemIndicator=E;const k=a(n.Separator,{displayName:"ContextMenuSeparator"});exports.ContextMenuSeparator=k;const q=f;exports.Root=q;const G=b;exports.Trigger=G;const N=I;exports.Content=N;const S=g;exports.Group=S;const P=R;exports.Label=P;const w=h;exports.Item=w;const L=y;exports.CheckboxItem=L;const _=O;exports.RadioGroup=_;const T=v;exports.RadioItem=T;const j=E;exports.ItemIndicator=j;const D=k;exports.Separator=D; 2 | //# sourceMappingURL=index.js.map 3 | -------------------------------------------------------------------------------- /packages/radix/context-menu/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"mappings":"IAAgCA,qWAAAA,8CAClBA,EAAEC,WAAaD,EAAEE,QAAUF,EAWzC,SAASG,EAAuBC,EAAMC,GAcpC,OAbAC,OAAOC,KAAKF,GAAQG,SAAQ,SAASC,GACvB,YAARA,GAA6B,eAARA,GAIzBH,OAAOI,eAAeN,EAAMK,EAAK,CAC/BE,YAAY,EACZC,IAAK,WACH,OAAOP,EAAOI,SAKbL,ECVT,MAOOS,EAAqBC,GAAyBC,EAP3B,eAWpBC,EAAiEC,IACrE,MAAMC,SAAEA,EAAFC,aAAYA,GAAiBF,GAC5BG,EAAMC,GAAWC,EAAMC,UAAS,GACjCC,EAAuBC,EAAeN,GAEtCO,EAAmBJ,EAAMK,aAC5BP,IACCC,EAAQD,GACRI,EAAqBJ,KAEvB,CAACI,iBAGH,OACEI,EAAAC,cAACC,EAAcC,KAAf,CAAoBX,KAAMA,EAAMD,aAAcO,gBAC5CE,EAAAC,cAACG,EAAD,CAAqBZ,KAAMA,EAAMD,aAAcO,GAC5CR,2BAYT,MACMe,EAAsC,QAAhBC,EAASC,GAAe,OAASC,EAQvDC,eAAqBf,EAAMgB,YAAW,CAACrB,EAAOsB,KAClD,MAAMC,GAAEA,EAAKP,KAAwBQ,GAAiBxB,EAChDyB,EAAU5B,EAXG,sBAYb6B,EAAWrB,EAAMsB,OAAc,CAAEC,EAAG,EAAGC,EAAG,IAC1CC,EAAazB,EAAMsB,OAAO,CAC9BI,sBAAuB,IAAMC,QAAQC,SAAS,CAAEC,MAAO,EAAGC,OAAQ,KAAMT,EAASU,yBAGnF,OACEzB,EAAAC,cAAAD,EAAA0B,SAAA,kBACE1B,EAAAC,cAACC,EAAcyB,OAAf,CAAsBR,WAAYA,iBAClCnB,EAAAC,cAAC2B,EAADC,EAAA,GACMhB,EADN,CAEED,GAAIA,EACJkB,IAAKnB,EACLoB,cAAeC,EAAqB3C,EAAM0C,eAAgBE,IACxDA,EAAMC,iBACNnB,EAASU,QAAU,CAAER,EAAGgB,EAAME,QAASjB,EAAGe,EAAMG,SAChDtB,EAAQvB,cAAa,0CAa/B,MAsBM8C,eAAqB3C,EAAMgB,YAAW,CAACrB,EAAOsB,KAClD,MAAM2B,4BAAEA,GAA8B,EAAhCC,OAAsCA,KAAWC,GAAiBnD,EAClEyB,EAAU5B,EAxBG,mCAyBnB,OACEc,EAAAC,cAACC,EAAcuC,QAAfZ,EAAA,GACMW,EADN,CAEEV,IAAKnB,EACL2B,8BAA6BxB,EAAQtB,MAAO8C,EAC5CI,MAAO,IACFrD,EAAMqD,MAETC,gDAA0D,wCAE5DC,WAAS,EACTC,sBAAoB,EACpBC,WAAS,EACTC,KAAK,SACLC,WAAYT,EACZU,MAAM,QACNC,YAAa,qCASnB,MAAMC,EAAmBC,EAAgBC,EAAcC,MAAO,CAAEC,YAAa,gDAC7E,MAAMC,EAAmBJ,EAAgBC,EAAcI,MAAO,CAAEF,YAAa,gDAC7E,MAAMG,EAAkBN,EAAgBC,EAAcM,KAAM,CAAEJ,YAAa,8CAC3E,MAAMK,EAA0BR,EAAgBC,EAAcQ,aAAc,CAC1EN,YAAa,8DAEf,MAAMO,EAAwBV,EAAgBC,EAAcU,WAAY,CACtER,YAAa,0DAEf,MAAMS,EAAuBZ,EAAgBC,EAAcY,UAAW,CACpEV,YAAa,wDAEf,MAAMW,EAA2Bd,EAAgBC,EAAcc,cAAe,CAC5EZ,YAAa,gEAEf,MAAMa,EAAuBhB,EAAgBC,EAAcgB,UAAW,CACpEd,YAAa,wDAKf,MAAMpD,EAAOf,iBACb,MAAMkF,EAAU7D,oBAChB,MAAMgC,EAAUJ,oBAChB,MAAMiB,EAAQH,kBACd,MAAMM,EAAQD,kBACd,MAAMG,EAAOD,iBACb,MAAMG,EAAeD,yBACrB,MAAMG,EAAaD,uBACnB,MAAMG,EAAYD,sBAClB,MAAMG,EAAgBD,0BACtB,MAAMG,EAAYD","sources":["./node_modules/@parcel/scope-hoisting/lib/helpers.js","./packages/react/context-menu/src/ContextMenu.tsx"],"sourcesContent":["function $parcel$interopDefault(a) {\n return a && a.__esModule ? a.default : a;\n}\n\nfunction $parcel$defineInteropFlag(a) {\n Object.defineProperty(a, '__esModule', {value: true});\n}\n\nfunction $parcel$reexport(e, n, v) {\n Object.defineProperty(e, n, {get: v, enumerable: true});\n}\n\nfunction $parcel$exportWildcard(dest, source) {\n Object.keys(source).forEach(function(key) {\n if (key === 'default' || key === '__esModule') {\n return;\n }\n\n Object.defineProperty(dest, key, {\n enumerable: true,\n get: function get() {\n return source[key];\n },\n });\n });\n\n return dest;\n}\n\nfunction $parcel$missingModule(name) {\n var err = new Error(\"Cannot find module '\" + name + \"'\");\n err.code = 'MODULE_NOT_FOUND';\n throw err;\n}\n\nvar $parcel$global =\n typeof globalThis !== 'undefined'\n ? globalThis\n : typeof self !== 'undefined'\n ? self\n : typeof window !== 'undefined'\n ? window\n : typeof global !== 'undefined'\n ? global\n : {};\n","import * as React from 'react';\nimport { Platform, View } from 'react-native';\nimport { composeEventHandlers } from '@radix-ui/primitive';\nimport { createContext } from '@radix-ui/react-context';\nimport { Primitive, extendPrimitive } from '@radix-ui/react-primitive';\nimport * as MenuPrimitive from '@radix-ui/react-menu';\nimport { useCallbackRef } from '@radix-ui/react-use-callback-ref';\n\nimport type * as Polymorphic from '@radix-ui/react-polymorphic';\n\ntype Point = { x: number; y: number };\n\n/* -------------------------------------------------------------------------------------------------\n * ContextMenu\n * -----------------------------------------------------------------------------------------------*/\n\nconst CONTEXT_MENU_NAME = 'ContextMenu';\n\ntype ContextMenuContextValue = {\n open: boolean;\n onOpenChange(open: boolean): void;\n};\n\nconst [ContextMenuProvider, useContextMenuContext] = createContext(\n CONTEXT_MENU_NAME\n);\n\nconst ContextMenu: React.FC<{ onOpenChange?(open: boolean): void }> = (props) => {\n const { children, onOpenChange } = props;\n const [open, setOpen] = React.useState(false);\n const handleOpenChangeProp = useCallbackRef(onOpenChange);\n\n const handleOpenChange = React.useCallback(\n (open) => {\n setOpen(open);\n handleOpenChangeProp(open);\n },\n [handleOpenChangeProp]\n );\n\n return (\n \n \n {children}\n \n \n );\n};\n\nContextMenu.displayName = CONTEXT_MENU_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * ContextMenuTrigger\n * -----------------------------------------------------------------------------------------------*/\n\nconst TRIGGER_NAME = 'ContextMenuTrigger';\nconst TRIGGER_DEFAULT_TAG = Platform.OS === 'web' ? 'span' : View;\n\ntype ContextMenuTriggerOwnProps = Polymorphic.OwnProps;\ntype ContextMenuTriggerPrimitive = Polymorphic.ForwardRefComponent<\n typeof TRIGGER_DEFAULT_TAG,\n ContextMenuTriggerOwnProps\n>;\n\nconst ContextMenuTrigger = React.forwardRef((props, forwardedRef) => {\n const { as = TRIGGER_DEFAULT_TAG, ...triggerProps } = props;\n const context = useContextMenuContext(TRIGGER_NAME);\n const pointRef = React.useRef({ x: 0, y: 0 });\n const virtualRef = React.useRef({\n getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...pointRef.current }),\n });\n\n return (\n <>\n \n {\n event.preventDefault();\n pointRef.current = { x: event.clientX, y: event.clientY };\n context.onOpenChange(true);\n })}\n />\n \n );\n}) as ContextMenuTriggerPrimitive;\n\nContextMenuTrigger.displayName = TRIGGER_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * ContextMenuContent\n * -----------------------------------------------------------------------------------------------*/\n\nconst CONTENT_NAME = 'ContextMenuContent';\n\ntype ContextMenuContentOwnProps = Polymorphic.Merge<\n Omit<\n Polymorphic.OwnProps,\n | 'trapFocus'\n | 'disableOutsideScroll'\n | 'portalled'\n | 'onOpenAutoFocus'\n | 'side'\n | 'sideOffset'\n | 'align'\n | 'alignOffset'\n >,\n { offset?: number }\n>;\n\ntype ContextMenuContentPrimitive = Polymorphic.ForwardRefComponent<\n Polymorphic.IntrinsicElement,\n ContextMenuContentOwnProps\n>;\n\nconst ContextMenuContent = React.forwardRef((props, forwardedRef) => {\n const { disableOutsidePointerEvents = true, offset, ...contentProps } = props;\n const context = useContextMenuContext(CONTENT_NAME);\n return (\n \n );\n}) as ContextMenuContentPrimitive;\n\nContextMenuContent.displayName = CONTENT_NAME;\n\n/* ---------------------------------------------------------------------------------------------- */\n\nconst ContextMenuGroup = extendPrimitive(MenuPrimitive.Group, { displayName: 'ContextMenuGroup' });\nconst ContextMenuLabel = extendPrimitive(MenuPrimitive.Label, { displayName: 'ContextMenuLabel' });\nconst ContextMenuItem = extendPrimitive(MenuPrimitive.Item, { displayName: 'ContextMenuItem' });\nconst ContextMenuCheckboxItem = extendPrimitive(MenuPrimitive.CheckboxItem, {\n displayName: 'ContextMenuCheckboxItem',\n});\nconst ContextMenuRadioGroup = extendPrimitive(MenuPrimitive.RadioGroup, {\n displayName: 'ContextMenuRadioGroup',\n});\nconst ContextMenuRadioItem = extendPrimitive(MenuPrimitive.RadioItem, {\n displayName: 'ContextMenuRadioItem',\n});\nconst ContextMenuItemIndicator = extendPrimitive(MenuPrimitive.ItemIndicator, {\n displayName: 'ContextMenuItemIndicator',\n});\nconst ContextMenuSeparator = extendPrimitive(MenuPrimitive.Separator, {\n displayName: 'ContextMenuSeparator',\n});\n\n/* -----------------------------------------------------------------------------------------------*/\n\nconst Root = ContextMenu;\nconst Trigger = ContextMenuTrigger;\nconst Content = ContextMenuContent;\nconst Group = ContextMenuGroup;\nconst Label = ContextMenuLabel;\nconst Item = ContextMenuItem;\nconst CheckboxItem = ContextMenuCheckboxItem;\nconst RadioGroup = ContextMenuRadioGroup;\nconst RadioItem = ContextMenuRadioItem;\nconst ItemIndicator = ContextMenuItemIndicator;\nconst Separator = ContextMenuSeparator;\n\nexport {\n ContextMenu,\n ContextMenuTrigger,\n ContextMenuContent,\n ContextMenuGroup,\n ContextMenuLabel,\n ContextMenuItem,\n ContextMenuCheckboxItem,\n ContextMenuRadioGroup,\n ContextMenuRadioItem,\n ContextMenuItemIndicator,\n ContextMenuSeparator,\n //\n Root,\n Trigger,\n Content,\n Group,\n Label,\n Item,\n CheckboxItem,\n RadioGroup,\n RadioItem,\n ItemIndicator,\n Separator,\n};\nexport type { ContextMenuTriggerPrimitive, ContextMenuContentPrimitive };\n"],"names":["a","__esModule","default","$parcel$exportWildcard","dest","source","Object","keys","forEach","key","defineProperty","enumerable","get","ContextMenuProvider","useContextMenuContext","createContext","ContextMenu","props","children","onOpenChange","open","setOpen","React","useState","handleOpenChangeProp","useCallbackRef","handleOpenChange","useCallback","_react","createElement","_radixUiReactMenu","Root","$ffc34e43379ee114b0300eebc3d396c$var$ContextMenuProvider","TRIGGER_DEFAULT_TAG","Platform","OS","View","ContextMenuTrigger","forwardRef","forwardedRef","as","triggerProps","context","pointRef","useRef","x","y","virtualRef","getBoundingClientRect","DOMRect","fromRect","width","height","current","Fragment","Anchor","Primitive","_babelRuntimeHelpersExtends","ref","onContextMenu","composeEventHandlers","event","preventDefault","clientX","clientY","ContextMenuContent","disableOutsidePointerEvents","offset","contentProps","Content","style","--radix-context-menu-content-transform-origin","trapFocus","disableOutsideScroll","portalled","side","sideOffset","align","alignOffset","ContextMenuGroup","extendPrimitive","MenuPrimitive","Group","displayName","ContextMenuLabel","Label","ContextMenuItem","Item","ContextMenuCheckboxItem","CheckboxItem","ContextMenuRadioGroup","RadioGroup","ContextMenuRadioItem","RadioItem","ContextMenuItemIndicator","ItemIndicator","ContextMenuSeparator","Separator","Trigger"],"version":3,"file":"index.js.map"} -------------------------------------------------------------------------------- /packages/radix/context-menu/dist/index.module.js: -------------------------------------------------------------------------------- 1 | import{useCallbackRef as e}from"@radix-ui/react-use-callback-ref";import*as t from"@radix-ui/react-menu";import{Primitive as o,extendPrimitive as n}from"@radix-ui/react-primitive";import{createContext as r}from"@radix-ui/react-context";import{composeEventHandlers as a}from"@radix-ui/primitive";import{Platform as i,View as x}from"react-native";import*as u from"react";import p from"@babel/runtime/helpers/esm/extends";const[s,c]=r("ContextMenu");export const ContextMenu=o=>{const{children:n,onOpenChange:r}=o,[a,i]=u.useState(!1),x=e(r),p=u.useCallback((e=>{i(e),x(e)}),[x]);/*#__PURE__*/return u.createElement(t.Root,{open:a,onOpenChange:p},/*#__PURE__*/u.createElement(s,{open:a,onOpenChange:p},n))};/*#__PURE__*/const m="web"===i.OS?"span":x;export const ContextMenuTrigger=/*#__PURE__*/u.forwardRef(((e,n)=>{const{as:r=m,...i}=e,x=c("ContextMenuTrigger"),s=u.useRef({x:0,y:0}),C=u.useRef({getBoundingClientRect:()=>DOMRect.fromRect({width:0,height:0,...s.current})});/*#__PURE__*/return u.createElement(u.Fragment,null,/*#__PURE__*/u.createElement(t.Anchor,{virtualRef:C}),/*#__PURE__*/u.createElement(o,p({},i,{as:r,ref:n,onContextMenu:a(e.onContextMenu,(e=>{e.preventDefault(),s.current={x:e.clientX,y:e.clientY},x.onOpenChange(!0)}))})))}));/*#__PURE__*/export const ContextMenuContent=/*#__PURE__*/u.forwardRef(((e,o)=>{const{disableOutsidePointerEvents:n=!0,offset:r,...a}=e,i=c("ContextMenuContent");/*#__PURE__*/return u.createElement(t.Content,p({},a,{ref:o,disableOutsidePointerEvents:!!i.open&&n,style:{...e.style,"--radix-context-menu-content-transform-origin":"var(--radix-popper-transform-origin)"},trapFocus:!0,disableOutsideScroll:!0,portalled:!0,side:"bottom",sideOffset:r,align:"start",alignOffset:2}))}));/*#__PURE__*/export const ContextMenuGroup=n(t.Group,{displayName:"ContextMenuGroup"});export const ContextMenuLabel=n(t.Label,{displayName:"ContextMenuLabel"});export const ContextMenuItem=n(t.Item,{displayName:"ContextMenuItem"});export const ContextMenuCheckboxItem=n(t.CheckboxItem,{displayName:"ContextMenuCheckboxItem"});export const ContextMenuRadioGroup=n(t.RadioGroup,{displayName:"ContextMenuRadioGroup"});export const ContextMenuRadioItem=n(t.RadioItem,{displayName:"ContextMenuRadioItem"});export const ContextMenuItemIndicator=n(t.ItemIndicator,{displayName:"ContextMenuItemIndicator"});export const ContextMenuSeparator=n(t.Separator,{displayName:"ContextMenuSeparator"});export const Root=ContextMenu;export const Trigger=ContextMenuTrigger;export const Content=ContextMenuContent;export const Group=ContextMenuGroup;export const Label=ContextMenuLabel;export const Item=ContextMenuItem;export const CheckboxItem=ContextMenuCheckboxItem;export const RadioGroup=ContextMenuRadioGroup;export const RadioItem=ContextMenuRadioItem;export const ItemIndicator=ContextMenuItemIndicator;export const Separator=ContextMenuSeparator; 2 | //# sourceMappingURL=index.module.js.map 3 | -------------------------------------------------------------------------------- /packages/radix/context-menu/dist/index.module.js.map: -------------------------------------------------------------------------------- 1 | {"mappings":"maAgBA,MAOOA,EAAqBC,GAAyBC,EAP3B,sBAW1B,MAAMC,YAAiEC,IACrE,MAAMC,SAAEA,EAAFC,aAAYA,GAAiBF,GAC5BG,EAAMC,GAAWC,EAAMC,UAAS,GACjCC,EAAuBC,EAAeN,GAEtCO,EAAmBJ,EAAMK,aAC5BP,IACCC,EAAQD,GACRI,EAAqBJ,KAEvB,CAACI,iBAGH,OACEI,EAAAC,cAACC,EAAcC,KAAf,CAAoBX,KAAMA,EAAMD,aAAcO,gBAC5CE,EAAAC,cAACG,EAAD,CAAqBZ,KAAMA,EAAMD,aAAcO,GAC5CR,kBAYT,MACMe,EAAsC,QAAhBC,EAASC,GAAe,OAASC,SAQ7D,MAAMC,gCAAqBf,EAAMgB,YAAW,CAACrB,EAAOsB,KAClD,MAAMC,GAAEA,EAAKP,KAAwBQ,GAAiBxB,EAChDyB,EAAU5B,EAXG,sBAYb6B,EAAWrB,EAAMsB,OAAc,CAAEC,EAAG,EAAGC,EAAG,IAC1CC,EAAazB,EAAMsB,OAAO,CAC9BI,sBAAuB,IAAMC,QAAQC,SAAS,CAAEC,MAAO,EAAGC,OAAQ,KAAMT,EAASU,yBAGnF,OACEzB,EAAAC,cAAAD,EAAA0B,SAAA,kBACE1B,EAAAC,cAACC,EAAcyB,OAAf,CAAsBR,WAAYA,iBAClCnB,EAAAC,cAAC2B,EAADC,EAAA,GACMhB,EADN,CAEED,GAAIA,EACJkB,IAAKnB,EACLoB,cAAeC,EAAqB3C,EAAM0C,eAAgBE,IACxDA,EAAMC,iBACNnB,EAASU,QAAU,CAAER,EAAGgB,EAAME,QAASjB,EAAGe,EAAMG,SAChDtB,EAAQvB,cAAa,iCAmC/B,MAAM8C,gCAAqB3C,EAAMgB,YAAW,CAACrB,EAAOsB,KAClD,MAAM2B,4BAAEA,GAA8B,EAAhCC,OAAsCA,KAAWC,GAAiBnD,EAClEyB,EAAU5B,EAxBG,mCAyBnB,OACEc,EAAAC,cAACC,EAAcuC,QAAfZ,EAAA,GACMW,EADN,CAEEV,IAAKnB,EACL2B,8BAA6BxB,EAAQtB,MAAO8C,EAC5CI,MAAO,IACFrD,EAAMqD,MAETC,gDAA0D,wCAE5DC,WAAS,EACTC,sBAAoB,EACpBC,WAAS,EACTC,KAAK,SACLC,WAAYT,EACZU,MAAM,QACNC,YAAa,4BASnB,MAAMC,iBAAmBC,EAAgBC,EAAcC,MAAO,CAAEC,YAAa,4BAC7E,MAAMC,iBAAmBJ,EAAgBC,EAAcI,MAAO,CAAEF,YAAa,4BAC7E,MAAMG,gBAAkBN,EAAgBC,EAAcM,KAAM,CAAEJ,YAAa,2BAC3E,MAAMK,wBAA0BR,EAAgBC,EAAcQ,aAAc,CAC1EN,YAAa,mCAEf,MAAMO,sBAAwBV,EAAgBC,EAAcU,WAAY,CACtER,YAAa,iCAEf,MAAMS,qBAAuBZ,EAAgBC,EAAcY,UAAW,CACpEV,YAAa,gCAEf,MAAMW,yBAA2Bd,EAAgBC,EAAcc,cAAe,CAC5EZ,YAAa,oCAEf,MAAMa,qBAAuBhB,EAAgBC,EAAcgB,UAAW,CACpEd,YAAa,gCAKf,MAAMpD,KAAOf,mBACb,MAAMkF,QAAU7D,0BAChB,MAAMgC,QAAUJ,0BAChB,MAAMiB,MAAQH,wBACd,MAAMM,MAAQD,wBACd,MAAMG,KAAOD,uBACb,MAAMG,aAAeD,+BACrB,MAAMG,WAAaD,6BACnB,MAAMG,UAAYD,4BAClB,MAAMG,cAAgBD,gCACtB,MAAMG,UAAYD","sources":["./packages/react/context-menu/src/ContextMenu.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Platform, View } from 'react-native';\nimport { composeEventHandlers } from '@radix-ui/primitive';\nimport { createContext } from '@radix-ui/react-context';\nimport { Primitive, extendPrimitive } from '@radix-ui/react-primitive';\nimport * as MenuPrimitive from '@radix-ui/react-menu';\nimport { useCallbackRef } from '@radix-ui/react-use-callback-ref';\n\nimport type * as Polymorphic from '@radix-ui/react-polymorphic';\n\ntype Point = { x: number; y: number };\n\n/* -------------------------------------------------------------------------------------------------\n * ContextMenu\n * -----------------------------------------------------------------------------------------------*/\n\nconst CONTEXT_MENU_NAME = 'ContextMenu';\n\ntype ContextMenuContextValue = {\n open: boolean;\n onOpenChange(open: boolean): void;\n};\n\nconst [ContextMenuProvider, useContextMenuContext] = createContext(\n CONTEXT_MENU_NAME\n);\n\nconst ContextMenu: React.FC<{ onOpenChange?(open: boolean): void }> = (props) => {\n const { children, onOpenChange } = props;\n const [open, setOpen] = React.useState(false);\n const handleOpenChangeProp = useCallbackRef(onOpenChange);\n\n const handleOpenChange = React.useCallback(\n (open) => {\n setOpen(open);\n handleOpenChangeProp(open);\n },\n [handleOpenChangeProp]\n );\n\n return (\n \n \n {children}\n \n \n );\n};\n\nContextMenu.displayName = CONTEXT_MENU_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * ContextMenuTrigger\n * -----------------------------------------------------------------------------------------------*/\n\nconst TRIGGER_NAME = 'ContextMenuTrigger';\nconst TRIGGER_DEFAULT_TAG = Platform.OS === 'web' ? 'span' : View;\n\ntype ContextMenuTriggerOwnProps = Polymorphic.OwnProps;\ntype ContextMenuTriggerPrimitive = Polymorphic.ForwardRefComponent<\n typeof TRIGGER_DEFAULT_TAG,\n ContextMenuTriggerOwnProps\n>;\n\nconst ContextMenuTrigger = React.forwardRef((props, forwardedRef) => {\n const { as = TRIGGER_DEFAULT_TAG, ...triggerProps } = props;\n const context = useContextMenuContext(TRIGGER_NAME);\n const pointRef = React.useRef({ x: 0, y: 0 });\n const virtualRef = React.useRef({\n getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...pointRef.current }),\n });\n\n return (\n <>\n \n {\n event.preventDefault();\n pointRef.current = { x: event.clientX, y: event.clientY };\n context.onOpenChange(true);\n })}\n />\n \n );\n}) as ContextMenuTriggerPrimitive;\n\nContextMenuTrigger.displayName = TRIGGER_NAME;\n\n/* -------------------------------------------------------------------------------------------------\n * ContextMenuContent\n * -----------------------------------------------------------------------------------------------*/\n\nconst CONTENT_NAME = 'ContextMenuContent';\n\ntype ContextMenuContentOwnProps = Polymorphic.Merge<\n Omit<\n Polymorphic.OwnProps,\n | 'trapFocus'\n | 'disableOutsideScroll'\n | 'portalled'\n | 'onOpenAutoFocus'\n | 'side'\n | 'sideOffset'\n | 'align'\n | 'alignOffset'\n >,\n { offset?: number }\n>;\n\ntype ContextMenuContentPrimitive = Polymorphic.ForwardRefComponent<\n Polymorphic.IntrinsicElement,\n ContextMenuContentOwnProps\n>;\n\nconst ContextMenuContent = React.forwardRef((props, forwardedRef) => {\n const { disableOutsidePointerEvents = true, offset, ...contentProps } = props;\n const context = useContextMenuContext(CONTENT_NAME);\n return (\n \n );\n}) as ContextMenuContentPrimitive;\n\nContextMenuContent.displayName = CONTENT_NAME;\n\n/* ---------------------------------------------------------------------------------------------- */\n\nconst ContextMenuGroup = extendPrimitive(MenuPrimitive.Group, { displayName: 'ContextMenuGroup' });\nconst ContextMenuLabel = extendPrimitive(MenuPrimitive.Label, { displayName: 'ContextMenuLabel' });\nconst ContextMenuItem = extendPrimitive(MenuPrimitive.Item, { displayName: 'ContextMenuItem' });\nconst ContextMenuCheckboxItem = extendPrimitive(MenuPrimitive.CheckboxItem, {\n displayName: 'ContextMenuCheckboxItem',\n});\nconst ContextMenuRadioGroup = extendPrimitive(MenuPrimitive.RadioGroup, {\n displayName: 'ContextMenuRadioGroup',\n});\nconst ContextMenuRadioItem = extendPrimitive(MenuPrimitive.RadioItem, {\n displayName: 'ContextMenuRadioItem',\n});\nconst ContextMenuItemIndicator = extendPrimitive(MenuPrimitive.ItemIndicator, {\n displayName: 'ContextMenuItemIndicator',\n});\nconst ContextMenuSeparator = extendPrimitive(MenuPrimitive.Separator, {\n displayName: 'ContextMenuSeparator',\n});\n\n/* -----------------------------------------------------------------------------------------------*/\n\nconst Root = ContextMenu;\nconst Trigger = ContextMenuTrigger;\nconst Content = ContextMenuContent;\nconst Group = ContextMenuGroup;\nconst Label = ContextMenuLabel;\nconst Item = ContextMenuItem;\nconst CheckboxItem = ContextMenuCheckboxItem;\nconst RadioGroup = ContextMenuRadioGroup;\nconst RadioItem = ContextMenuRadioItem;\nconst ItemIndicator = ContextMenuItemIndicator;\nconst Separator = ContextMenuSeparator;\n\nexport {\n ContextMenu,\n ContextMenuTrigger,\n ContextMenuContent,\n ContextMenuGroup,\n ContextMenuLabel,\n ContextMenuItem,\n ContextMenuCheckboxItem,\n ContextMenuRadioGroup,\n ContextMenuRadioItem,\n ContextMenuItemIndicator,\n ContextMenuSeparator,\n //\n Root,\n Trigger,\n Content,\n Group,\n Label,\n Item,\n CheckboxItem,\n RadioGroup,\n RadioItem,\n ItemIndicator,\n Separator,\n};\nexport type { ContextMenuTriggerPrimitive, ContextMenuContentPrimitive };\n"],"names":["ContextMenuProvider","useContextMenuContext","createContext","ContextMenu","props","children","onOpenChange","open","setOpen","React","useState","handleOpenChangeProp","useCallbackRef","handleOpenChange","useCallback","_react","createElement","_radixUiReactMenu","Root","$b55f3d34bcc87b2d9b0bc87c90588a09$var$ContextMenuProvider","TRIGGER_DEFAULT_TAG","Platform","OS","View","ContextMenuTrigger","forwardRef","forwardedRef","as","triggerProps","context","pointRef","useRef","x","y","virtualRef","getBoundingClientRect","DOMRect","fromRect","width","height","current","Fragment","Anchor","Primitive","_babelRuntimeHelpersEsmExtends","ref","onContextMenu","composeEventHandlers","event","preventDefault","clientX","clientY","ContextMenuContent","disableOutsidePointerEvents","offset","contentProps","Content","style","--radix-context-menu-content-transform-origin","trapFocus","disableOutsideScroll","portalled","side","sideOffset","align","alignOffset","ContextMenuGroup","extendPrimitive","MenuPrimitive","Group","displayName","ContextMenuLabel","Label","ContextMenuItem","Item","ContextMenuCheckboxItem","CheckboxItem","ContextMenuRadioGroup","RadioGroup","ContextMenuRadioItem","RadioItem","ContextMenuItemIndicator","ItemIndicator","ContextMenuSeparator","Separator","Trigger"],"version":3,"file":"index.module.js.map"} -------------------------------------------------------------------------------- /packages/radix/context-menu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@radix-ui/react-context-menu", 3 | "version": "0.0.19", 4 | "license": "MIT", 5 | "source": "src/index.ts", 6 | "main": "dist/index.js", 7 | "module": "dist/index.module.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "README.md" 12 | ], 13 | "sideEffects": false, 14 | "scripts": { 15 | "clean": "rm -rf dist", 16 | "prepublish": "yarn clean", 17 | "version": "yarn version" 18 | }, 19 | "dependencies": { 20 | "@babel/runtime": "^7.13.10", 21 | "@radix-ui/primitive": "workspace:*", 22 | "@radix-ui/react-context": "workspace:*", 23 | "@radix-ui/react-menu": "workspace:*", 24 | "@radix-ui/react-polymorphic": "workspace:*", 25 | "@radix-ui/react-primitive": "workspace:*", 26 | "@radix-ui/react-use-callback-ref": "workspace:*" 27 | }, 28 | "peerDependencies": { 29 | "react": "^16.8 || ^17.0", 30 | "react-dom": "^16.8 || ^17.0" 31 | }, 32 | "homepage": "https://radix-ui.com/primitives", 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/radix-ui/primitives.git" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/radix-ui/primitives/issues" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/radix/context-menu/src/ContextMenu.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | ContextMenu, 4 | ContextMenuTrigger, 5 | ContextMenuContent, 6 | ContextMenuGroup, 7 | ContextMenuLabel, 8 | ContextMenuItem, 9 | ContextMenuCheckboxItem, 10 | ContextMenuRadioGroup, 11 | ContextMenuRadioItem, 12 | ContextMenuItemIndicator, 13 | ContextMenuSeparator, 14 | } from './ContextMenu'; 15 | import { css } from '../../../../stitches.config'; 16 | import { foodGroups } from '../../../../test-data/foods'; 17 | import { classes, TickIcon } from '../../menu/src/Menu.stories'; 18 | 19 | const { contentClass, itemClass, labelClass, separatorClass } = classes; 20 | 21 | export default { title: 'Components/ContextMenu' }; 22 | 23 | export const Styled = () => { 24 | const [open, setOpen] = React.useState(false); 25 | return ( 26 |
36 | 37 | 41 | 42 | console.log('undo')}> 43 | Undo 44 | 45 | console.log('redo')}> 46 | Redo 47 | 48 | 49 | console.log('cut')}> 50 | Cut 51 | 52 | console.log('copy')}> 53 | Copy 54 | 55 | console.log('paste')}> 56 | Paste 57 | 58 | 59 | 60 |
61 | ); 62 | }; 63 | 64 | export const WithLabels = () => ( 65 |
66 | 67 | 68 | 69 | {foodGroups.map((foodGroup, index) => ( 70 | 71 | {foodGroup.label && ( 72 | 73 | {foodGroup.label} 74 | 75 | )} 76 | {foodGroup.foods.map((food) => ( 77 | console.log(food.label)} 82 | > 83 | {food.label} 84 | 85 | ))} 86 | {index < foodGroups.length - 1 && } 87 | 88 | ))} 89 | 90 | 91 |
92 | ); 93 | 94 | export const CheckboxItems = () => { 95 | const checkboxItems = [ 96 | { label: 'Bold', state: React.useState(false) }, 97 | { label: 'Italic', state: React.useState(true) }, 98 | { label: 'Underline', state: React.useState(false) }, 99 | { label: 'Strikethrough', state: React.useState(false), disabled: true }, 100 | ]; 101 | 102 | return ( 103 |
104 | 105 | 106 | 107 | console.log('show')}> 108 | Show fonts 109 | 110 | console.log('bigger')}> 111 | Bigger 112 | 113 | console.log('smaller')}> 114 | Smaller 115 | 116 | 117 | {checkboxItems.map(({ label, state: [checked, setChecked], disabled }) => ( 118 | 125 | {label} 126 | 127 | 128 | 129 | 130 | ))} 131 | 132 | 133 |
134 | ); 135 | }; 136 | 137 | export const RadioItems = () => { 138 | const files = ['README.md', 'index.js', 'page.css']; 139 | const [file, setFile] = React.useState(files[1]); 140 | 141 | return ( 142 |
143 | 144 | 145 | 146 | console.log('minimize')}> 147 | Minimize window 148 | 149 | console.log('zoom')}> 150 | Zoom 151 | 152 | console.log('smaller')}> 153 | Smaller 154 | 155 | 156 | 157 | {files.map((file) => ( 158 | 159 | {file} 160 | 161 | 162 | 163 | 164 | ))} 165 | 166 | 167 | 168 |

Selected file: {file}

169 |
170 | ); 171 | }; 172 | 173 | export const PreventClosing = () => ( 174 |
175 | 176 | 177 | 178 | window.alert('action 1')}> 179 | I will close 180 | 181 | { 184 | event.preventDefault(); 185 | window.alert('action 1'); 186 | }} 187 | > 188 | I won't close 189 | 190 | 191 | 192 |
193 | ); 194 | 195 | export const Multiple = () => { 196 | const [customColors, setCustomColors] = React.useState<{ [index: number]: string }>({}); 197 | const [fadedIndexes, setFadedIndexes] = React.useState([]); 198 | return ( 199 |
event.preventDefault()} 202 | > 203 | {Array.from({ length: 100 }, (_, i) => { 204 | const customColor = customColors[i]; 205 | return ( 206 | 207 | 208 | Color 209 | setCustomColors((colors) => ({ ...colors, [i]: color }))} 212 | > 213 | 214 | Blue 215 | 216 | 217 | 218 | 219 | 220 | Red 221 | 222 | 223 | 224 | 225 | 226 | 227 | 231 | setFadedIndexes((indexes) => 232 | faded ? [...indexes, i] : indexes.filter((index) => index !== i) 233 | ) 234 | } 235 | > 236 | Fade 237 | 238 | 239 | 240 | 241 | 242 | 243 |
260 | {i + 1} 261 |
262 |
263 |
264 | ); 265 | })} 266 |
267 | ); 268 | }; 269 | 270 | export const Nested = () => ( 271 |
272 | 273 | 277 | 278 | {' '} 279 | 280 | Red box menu 281 | 282 | console.log('red action1')}> 283 | Red action 1 284 | 285 | console.log('red action2')}> 286 | Red action 2 287 | 288 | 289 | 290 | 291 | 292 | Blue box menu 293 | 294 | console.log('blue action1')}> 295 | Blue action 1 296 | 297 | console.log('blue action2')}> 298 | Blue action 2 299 | 300 | 301 | 302 |
303 | ); 304 | 305 | const triggerClass = css({ 306 | display: 'block', 307 | width: 100, 308 | height: 100, 309 | border: '1px solid $black', 310 | borderRadius: 6, 311 | backgroundColor: '$gray100', 312 | 313 | '&:focus': { 314 | outline: 'none', 315 | boxShadow: '0 0 0 2px rgba(0, 0, 0, 0.5)', 316 | }, 317 | }); 318 | 319 | const scaleIn = css.keyframes({ 320 | '0%': { transform: 'scale(0) rotateZ(-10deg)' }, 321 | '20%': { transform: 'scale(1.1)' }, 322 | '100%': { transform: 'scale(1)' }, 323 | }); 324 | 325 | const animatedContentClass = css(contentClass, { 326 | transformOrigin: 'var(--radix-context-menu-content-transform-origin)', 327 | '&[data-state="open"]': { animation: `${scaleIn} 0.6s cubic-bezier(0.16, 1, 0.3, 1)` }, 328 | }); 329 | -------------------------------------------------------------------------------- /packages/radix/context-menu/src/ContextMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Platform, View } from 'react-native'; 3 | import { composeEventHandlers } from '@radix-ui/primitive'; 4 | import { createContext } from '@radix-ui/react-context'; 5 | import { Primitive, extendPrimitive } from '@radix-ui/react-primitive'; 6 | import * as MenuPrimitive from '@radix-ui/react-menu'; 7 | import { useCallbackRef } from '@radix-ui/react-use-callback-ref'; 8 | 9 | import type * as Polymorphic from '@radix-ui/react-polymorphic'; 10 | 11 | type Point = { x: number; y: number }; 12 | 13 | /* ------------------------------------------------------------------------------------------------- 14 | * ContextMenu 15 | * -----------------------------------------------------------------------------------------------*/ 16 | 17 | const CONTEXT_MENU_NAME = 'ContextMenu'; 18 | 19 | type ContextMenuContextValue = { 20 | open: boolean; 21 | onOpenChange(open: boolean): void; 22 | }; 23 | 24 | const [ContextMenuProvider, useContextMenuContext] = createContext( 25 | CONTEXT_MENU_NAME 26 | ); 27 | 28 | const ContextMenu: React.FC<{ onOpenChange?(open: boolean): void }> = (props) => { 29 | const { children, onOpenChange } = props; 30 | const [open, setOpen] = React.useState(false); 31 | const handleOpenChangeProp = useCallbackRef(onOpenChange); 32 | 33 | const handleOpenChange = React.useCallback( 34 | (open) => { 35 | setOpen(open); 36 | handleOpenChangeProp(open); 37 | }, 38 | [handleOpenChangeProp] 39 | ); 40 | 41 | return ( 42 | 43 | 44 | {children} 45 | 46 | 47 | ); 48 | }; 49 | 50 | ContextMenu.displayName = CONTEXT_MENU_NAME; 51 | 52 | /* ------------------------------------------------------------------------------------------------- 53 | * ContextMenuTrigger 54 | * -----------------------------------------------------------------------------------------------*/ 55 | 56 | const TRIGGER_NAME = 'ContextMenuTrigger'; 57 | const TRIGGER_DEFAULT_TAG = Platform.OS === 'web' ? 'span' : View; 58 | 59 | type ContextMenuTriggerOwnProps = Polymorphic.OwnProps; 60 | type ContextMenuTriggerPrimitive = Polymorphic.ForwardRefComponent< 61 | typeof TRIGGER_DEFAULT_TAG, 62 | ContextMenuTriggerOwnProps 63 | >; 64 | 65 | const ContextMenuTrigger = React.forwardRef((props, forwardedRef) => { 66 | const { as = TRIGGER_DEFAULT_TAG, ...triggerProps } = props; 67 | const context = useContextMenuContext(TRIGGER_NAME); 68 | const pointRef = React.useRef({ x: 0, y: 0 }); 69 | const virtualRef = React.useRef({ 70 | getBoundingClientRect: () => DOMRect.fromRect({ width: 0, height: 0, ...pointRef.current }), 71 | }); 72 | 73 | return ( 74 | <> 75 | 76 | { 81 | event.preventDefault(); 82 | pointRef.current = { x: event.clientX, y: event.clientY }; 83 | context.onOpenChange(true); 84 | })} 85 | /> 86 | 87 | ); 88 | }) as ContextMenuTriggerPrimitive; 89 | 90 | ContextMenuTrigger.displayName = TRIGGER_NAME; 91 | 92 | /* ------------------------------------------------------------------------------------------------- 93 | * ContextMenuContent 94 | * -----------------------------------------------------------------------------------------------*/ 95 | 96 | const CONTENT_NAME = 'ContextMenuContent'; 97 | 98 | type ContextMenuContentOwnProps = Polymorphic.Merge< 99 | Omit< 100 | Polymorphic.OwnProps, 101 | | 'trapFocus' 102 | | 'disableOutsideScroll' 103 | | 'portalled' 104 | | 'onOpenAutoFocus' 105 | | 'side' 106 | | 'sideOffset' 107 | | 'align' 108 | | 'alignOffset' 109 | >, 110 | { offset?: number } 111 | >; 112 | 113 | type ContextMenuContentPrimitive = Polymorphic.ForwardRefComponent< 114 | Polymorphic.IntrinsicElement, 115 | ContextMenuContentOwnProps 116 | >; 117 | 118 | const ContextMenuContent = React.forwardRef((props, forwardedRef) => { 119 | const { disableOutsidePointerEvents = true, offset, ...contentProps } = props; 120 | const context = useContextMenuContext(CONTENT_NAME); 121 | return ( 122 | 139 | ); 140 | }) as ContextMenuContentPrimitive; 141 | 142 | ContextMenuContent.displayName = CONTENT_NAME; 143 | 144 | /* ---------------------------------------------------------------------------------------------- */ 145 | 146 | const ContextMenuGroup = extendPrimitive(MenuPrimitive.Group, { displayName: 'ContextMenuGroup' }); 147 | const ContextMenuLabel = extendPrimitive(MenuPrimitive.Label, { displayName: 'ContextMenuLabel' }); 148 | const ContextMenuItem = extendPrimitive(MenuPrimitive.Item, { displayName: 'ContextMenuItem' }); 149 | const ContextMenuCheckboxItem = extendPrimitive(MenuPrimitive.CheckboxItem, { 150 | displayName: 'ContextMenuCheckboxItem', 151 | }); 152 | const ContextMenuRadioGroup = extendPrimitive(MenuPrimitive.RadioGroup, { 153 | displayName: 'ContextMenuRadioGroup', 154 | }); 155 | const ContextMenuRadioItem = extendPrimitive(MenuPrimitive.RadioItem, { 156 | displayName: 'ContextMenuRadioItem', 157 | }); 158 | const ContextMenuItemIndicator = extendPrimitive(MenuPrimitive.ItemIndicator, { 159 | displayName: 'ContextMenuItemIndicator', 160 | }); 161 | const ContextMenuSeparator = extendPrimitive(MenuPrimitive.Separator, { 162 | displayName: 'ContextMenuSeparator', 163 | }); 164 | 165 | /* -----------------------------------------------------------------------------------------------*/ 166 | 167 | const Root = ContextMenu; 168 | const Trigger = ContextMenuTrigger; 169 | const Content = ContextMenuContent; 170 | const Group = ContextMenuGroup; 171 | const Label = ContextMenuLabel; 172 | const Item = ContextMenuItem; 173 | const CheckboxItem = ContextMenuCheckboxItem; 174 | const RadioGroup = ContextMenuRadioGroup; 175 | const RadioItem = ContextMenuRadioItem; 176 | const ItemIndicator = ContextMenuItemIndicator; 177 | const Separator = ContextMenuSeparator; 178 | 179 | export { 180 | ContextMenu, 181 | ContextMenuTrigger, 182 | ContextMenuContent, 183 | ContextMenuGroup, 184 | ContextMenuLabel, 185 | ContextMenuItem, 186 | ContextMenuCheckboxItem, 187 | ContextMenuRadioGroup, 188 | ContextMenuRadioItem, 189 | ContextMenuItemIndicator, 190 | ContextMenuSeparator, 191 | // 192 | Root, 193 | Trigger, 194 | Content, 195 | Group, 196 | Label, 197 | Item, 198 | CheckboxItem, 199 | RadioGroup, 200 | RadioItem, 201 | ItemIndicator, 202 | Separator, 203 | }; 204 | export type { ContextMenuTriggerPrimitive, ContextMenuContentPrimitive }; 205 | -------------------------------------------------------------------------------- /packages/radix/context-menu/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ContextMenu'; 2 | -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/README.md: -------------------------------------------------------------------------------- 1 | # `react-dropdown-menu` 2 | 3 | ## Installation 4 | 5 | ```sh 6 | $ yarn add @radix-ui/react-dropdown-menu 7 | # or 8 | $ npm install @radix-ui/react-dropdown-menu 9 | ``` 10 | 11 | ## Usage 12 | 13 | View docs [here](https://radix-ui.com/primitives/docs/components/dropdown-menu). 14 | -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as MenuPrimitive from "@radix-ui/react-menu"; 3 | import * as Polymorphic from "@radix-ui/react-polymorphic"; 4 | type DropdownMenuOwnProps = { 5 | open?: boolean; 6 | defaultOpen?: boolean; 7 | onOpenChange?(open: boolean): void; 8 | }; 9 | export const DropdownMenu: React.FC; 10 | declare const TRIGGER_DEFAULT_TAG: any; 11 | type DropdownMenuTriggerOwnProps = Omit, 'virtualRef'>; 12 | export type DropdownMenuTriggerPrimitive = Polymorphic.ForwardRefComponent; 13 | export const DropdownMenuTrigger: DropdownMenuTriggerPrimitive; 14 | type DropdownMenuContentOwnProps = Omit, 'trapFocus' | 'onOpenAutoFocus'>; 15 | export type DropdownMenuContentPrimitive = Polymorphic.ForwardRefComponent, DropdownMenuContentOwnProps>; 16 | export const DropdownMenuContent: DropdownMenuContentPrimitive; 17 | export const DropdownMenuGroup: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 18 | export const DropdownMenuLabel: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 19 | export const DropdownMenuItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 20 | export const DropdownMenuCheckboxItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 21 | export const DropdownMenuRadioGroup: import("@radix-ui/react-primitive").ExtendedPrimitive; 22 | export const DropdownMenuRadioItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 23 | export const DropdownMenuItemIndicator: import("@radix-ui/react-primitive").ExtendedPrimitive; 24 | export const DropdownMenuSeparator: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 25 | export const DropdownMenuArrow: import("@radix-ui/react-primitive").ExtendedPrimitive, "svg">; 26 | export const Root: React.FC; 27 | export const Trigger: DropdownMenuTriggerPrimitive; 28 | export const Content: DropdownMenuContentPrimitive; 29 | export const Group: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 30 | export const Label: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 31 | export const Item: import("@radix-ui/react-primitive").ExtendedPrimitive; 32 | export const CheckboxItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 33 | export const RadioGroup: import("@radix-ui/react-primitive").ExtendedPrimitive; 34 | export const RadioItem: import("@radix-ui/react-primitive").ExtendedPrimitive; 35 | export const ItemIndicator: import("@radix-ui/react-primitive").ExtendedPrimitive; 36 | export const Separator: import("@radix-ui/react-primitive").ExtendedPrimitive, "div">; 37 | export const Arrow: import("@radix-ui/react-primitive").ExtendedPrimitive, "svg">; 38 | 39 | //# sourceMappingURL=index.d.ts.map 40 | -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"mappings":"A;A;A;AA+BA,4BAA4B;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CACpC,CAAC;AAEF,OAAA,MAAM,cAAc,MAAM,EAAE,CAAC,oBAAoB,CAsBhD,CAAC;AASF,QAAA,MAAM,wBAA6D,CAAC;AAEpE,mCAAmC,IAAI,CACrC,YAAY,QAAQ,CAAC,OAAO,cAAc,MAAM,CAAC,EACjD,YAAY,CACb,CAAC;AACF,2CAAoC,YAAY,mBAAmB,CACjE,0BAA0B,EAC1B,2BAA2B,CAC5B,CAAC;AAEF,OAAA,MAAM,iDA8B4B,CAAC;AAUnC,mCAAmC,IAAI,CACrC,YAAY,QAAQ,CAAC,OAAO,cAAc,OAAO,CAAC,EAClD,WAAW,GAAG,iBAAiB,CAChC,CAAC;AAEF,2CAAoC,YAAY,mBAAmB,CACjE,YAAY,gBAAgB,CAAC,OAAO,cAAc,OAAO,CAAC,EAC1D,2BAA2B,CAC5B,CAAC;AAEF,OAAA,MAAM,iDAwE4B,CAAC;AAMnC,OAAA,MAAM,qMAEJ,CAAC;AACH,OAAA,MAAM,qMAEJ,CAAC;AACH,OAAA,MAAM,+GAA2F,CAAC;AAClG,OAAA,MAAM,+HAEJ,CAAC;AACH,OAAA,MAAM,2HAEJ,CAAC;AACH,OAAA,MAAM,yHAEJ,CAAC;AACH,OAAA,MAAM,kIAEJ,CAAC;AACH,OAAA,MAAM,yMAEJ,CAAC;AACH,OAAA,MAAM,oMAEJ,CAAC;AAIH,OAAA,MAAM,oCAAmB,CAAC;AAC1B,OAAA,MAAM,qCAA6B,CAAC;AACpC,OAAA,MAAM,qCAA6B,CAAC;AACpC,OAAA,MAAM,yLAAyB,CAAC;AAChC,OAAA,MAAM,yLAAyB,CAAC;AAChC,OAAA,MAAM,mGAAuB,CAAC;AAC9B,OAAA,MAAM,mHAAuC,CAAC;AAC9C,OAAA,MAAM,+GAAmC,CAAC;AAC1C,OAAA,MAAM,6GAAiC,CAAC;AACxC,OAAA,MAAM,sHAAyC,CAAC;AAChD,OAAA,MAAM,6LAAiC,CAAC;AACxC,OAAA,MAAM,wLAAyB,CAAC","sources":["./packages/react/dropdown-menu/src/packages/react/dropdown-menu/src/DropdownMenu.tsx","./packages/react/dropdown-menu/src/packages/react/dropdown-menu/src/index.ts"],"sourcesContent":[null,null],"names":[],"version":3,"file":"index.d.ts.map"} -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/dist/index.js: -------------------------------------------------------------------------------- 1 | var e=w(require("@gorhom/bottom-sheet")),o=require("@radix-ui/react-id").useId,n=x({},require("@radix-ui/react-menu")),r=require("@radix-ui/react-primitive").extendPrimitive,t=require("@radix-ui/react-use-controllable-state").useControllableState,a=require("@radix-ui/react-context").createContext,s=require("@radix-ui/react-compose-refs").useComposedRefs,p=require("@radix-ui/primitive").composeEventHandlers,u=require("react-native"),i=u.Platform,d=u.View,c=u.Modal,l=x({},require("react")),m=w(require("@babel/runtime/helpers/extends"));function w(e){return e&&e.__esModule?e.default:e}function x(e,o){return Object.keys(o).forEach((function(n){"default"!==n&&"__esModule"!==n&&Object.defineProperty(e,n,{enumerable:!0,get:function(){return o[n]}})})),e}const[D,f]=a("DropdownMenu"),g=e=>{const{children:r,open:a,defaultOpen:s,onOpenChange:p}=e,u=l.useRef(null),[i=!1,d]=t({prop:a,defaultProp:s,onChange:p});/*#__PURE__*/return l.createElement(n.Root,{open:i,onOpenChange:d},/*#__PURE__*/l.createElement(D,{triggerRef:u,contentId:o(),open:i,onOpenChange:d,onOpenToggle:l.useCallback((()=>d((e=>!e))),[d])},r))};exports.DropdownMenu=g;const M="web"===i.OS?"button":d,b=/*#__PURE__*/l.forwardRef(((e,o)=>{const{as:r=M,...t}=e,a=f("DropdownMenuTrigger"),u=s(o,a.triggerRef);/*#__PURE__*/return l.createElement(n.Anchor,m({type:"button","aria-haspopup":"menu","aria-expanded":!!a.open||void 0,"aria-controls":a.open?a.contentId:void 0,"data-state":a.open?"open":"closed"},t,{as:r,ref:u,onMouseDown:p(e.onMouseDown,(e=>{0===e.button&&!1===e.ctrlKey&&a.onOpenToggle()})),onKeyDown:p(e.onKeyDown,(e=>{[" ","Enter","ArrowUp","ArrowDown"].includes(e.key)&&(e.preventDefault(),a.onOpenChange(!0))}))}))}));exports.DropdownMenuTrigger=b;const C=/*#__PURE__*/l.forwardRef(((o,r)=>{const{onCloseAutoFocus:t,disableOutsidePointerEvents:a=!0,disableOutsideScroll:s=!0,portalled:u=!0,...d}=o,w=f("DropdownMenuContent"),x=l.useRef(null),D=l.useCallback((e=>{-1===e&&w.onOpenChange(!1)}),[w]);return"web"===i.OS?/*#__PURE__*/l.createElement(n.Content,m({id:w.contentId},d,{ref:r,disableOutsidePointerEvents:a,disableOutsideScroll:s,portalled:u,style:{...o.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)"},trapFocus:!0,onCloseAutoFocus:p(t,(e=>{var o;e.preventDefault(),null===(o=w.triggerRef.current)||void 0===o||o.focus()})),onPointerDownOutside:p(o.onPointerDownOutside,(e=>{var o;(null===(o=w.triggerRef.current)||void 0===o?void 0:o.contains(e.target))&&e.preventDefault()}),{checkForDefaultPrevented:!1})})):/*#__PURE__*/l.createElement(c,{animationType:"none",transparent:!0,visible:w.open,onRequestClose:()=>{w.onOpenChange(!w.open)}},/*#__PURE__*/l.createElement(e,m({},d,{ref:x,animateOnMount:!0,enablePanDownToClose:!0,onChange:D})))}));exports.DropdownMenuContent=C;const I=r(n.Group,{displayName:"DropdownMenuGroup"});exports.DropdownMenuGroup=I;const v=r(n.Label,{displayName:"DropdownMenuLabel"});exports.DropdownMenuLabel=v;const h=r(n.Item,{displayName:"DropdownMenuItem"});exports.DropdownMenuItem=h;const O=r(n.CheckboxItem,{displayName:"DropdownMenuCheckboxItem"});exports.DropdownMenuCheckboxItem=O;const R=r(n.RadioGroup,{displayName:"DropdownMenuRadioGroup"});exports.DropdownMenuRadioGroup=R;const y=r(n.RadioItem,{displayName:"DropdownMenuRadioItem"});exports.DropdownMenuRadioItem=y;const q=r(n.ItemIndicator,{displayName:"DropdownMenuItemIndicator"});exports.DropdownMenuItemIndicator=q;const E=r(n.Separator,{displayName:"DropdownMenuSeparator"});exports.DropdownMenuSeparator=E;const P=r(n.Arrow,{displayName:"DropdownMenuArrow"});exports.DropdownMenuArrow=P;const k=g;exports.Root=k;const A=b;exports.Trigger=A;const N=C;exports.Content=N;const S=I;exports.Group=S;const G=v;exports.Label=G;const T=h;exports.Item=T;const F=O;exports.CheckboxItem=F;const L=R;exports.RadioGroup=L;const _=y;exports.RadioItem=_;const K=q;exports.ItemIndicator=K;const j=E;exports.Separator=j;const H=P;exports.Arrow=H; 2 | //# sourceMappingURL=index.js.map 3 | -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/dist/index.module.js: -------------------------------------------------------------------------------- 1 | import o from"@gorhom/bottom-sheet";import{useId as e}from"@radix-ui/react-id";import*as n from"@radix-ui/react-menu";import{extendPrimitive as r}from"@radix-ui/react-primitive";import{useControllableState as t}from"@radix-ui/react-use-controllable-state";import{createContext as p}from"@radix-ui/react-context";import{useComposedRefs as a}from"@radix-ui/react-compose-refs";import{composeEventHandlers as d}from"@radix-ui/primitive";import{Platform as i,View as u,Modal as s}from"react-native";import*as c from"react";import m from"@babel/runtime/helpers/esm/extends";const[l,w]=p("DropdownMenu");export const DropdownMenu=o=>{const{children:r,open:p,defaultOpen:a,onOpenChange:d}=o,i=c.useRef(null),[u=!1,s]=t({prop:p,defaultProp:a,onChange:d});/*#__PURE__*/return c.createElement(n.Root,{open:u,onOpenChange:s},/*#__PURE__*/c.createElement(l,{triggerRef:i,contentId:e(),open:u,onOpenChange:s,onOpenToggle:c.useCallback((()=>s((o=>!o))),[s])},r))};/*#__PURE__*/const D="web"===i.OS?"button":u;export const DropdownMenuTrigger=/*#__PURE__*/c.forwardRef(((o,e)=>{const{as:r=D,...t}=o,p=w("DropdownMenuTrigger"),i=a(e,p.triggerRef);/*#__PURE__*/return c.createElement(n.Anchor,m({type:"button","aria-haspopup":"menu","aria-expanded":!!p.open||void 0,"aria-controls":p.open?p.contentId:void 0,"data-state":p.open?"open":"closed"},t,{as:r,ref:i,onMouseDown:d(o.onMouseDown,(o=>{0===o.button&&!1===o.ctrlKey&&p.onOpenToggle()})),onKeyDown:d(o.onKeyDown,(o=>{[" ","Enter","ArrowUp","ArrowDown"].includes(o.key)&&(o.preventDefault(),p.onOpenChange(!0))}))}))}));/*#__PURE__*/export const DropdownMenuContent=/*#__PURE__*/c.forwardRef(((e,r)=>{const{onCloseAutoFocus:t,disableOutsidePointerEvents:p=!0,disableOutsideScroll:a=!0,portalled:u=!0,...l}=e,D=w("DropdownMenuContent"),x=c.useRef(null),M=c.useCallback((o=>{-1===o&&D.onOpenChange(!1)}),[D]);return"web"===i.OS?/*#__PURE__*/c.createElement(n.Content,m({id:D.contentId},l,{ref:r,disableOutsidePointerEvents:p,disableOutsideScroll:a,portalled:u,style:{...e.style,"--radix-dropdown-menu-content-transform-origin":"var(--radix-popper-transform-origin)"},trapFocus:!0,onCloseAutoFocus:d(t,(o=>{var e;o.preventDefault(),null===(e=D.triggerRef.current)||void 0===e||e.focus()})),onPointerDownOutside:d(e.onPointerDownOutside,(o=>{var e;(null===(e=D.triggerRef.current)||void 0===e?void 0:e.contains(o.target))&&o.preventDefault()}),{checkForDefaultPrevented:!1})})):/*#__PURE__*/c.createElement(s,{animationType:"none",transparent:!0,visible:D.open,onRequestClose:()=>{D.onOpenChange(!D.open)}},/*#__PURE__*/c.createElement(o,m({},l,{ref:x,animateOnMount:!0,enablePanDownToClose:!0,onChange:M})))}));/*#__PURE__*/export const DropdownMenuGroup=r(n.Group,{displayName:"DropdownMenuGroup"});export const DropdownMenuLabel=r(n.Label,{displayName:"DropdownMenuLabel"});export const DropdownMenuItem=r(n.Item,{displayName:"DropdownMenuItem"});export const DropdownMenuCheckboxItem=r(n.CheckboxItem,{displayName:"DropdownMenuCheckboxItem"});export const DropdownMenuRadioGroup=r(n.RadioGroup,{displayName:"DropdownMenuRadioGroup"});export const DropdownMenuRadioItem=r(n.RadioItem,{displayName:"DropdownMenuRadioItem"});export const DropdownMenuItemIndicator=r(n.ItemIndicator,{displayName:"DropdownMenuItemIndicator"});export const DropdownMenuSeparator=r(n.Separator,{displayName:"DropdownMenuSeparator"});export const DropdownMenuArrow=r(n.Arrow,{displayName:"DropdownMenuArrow"});export const Root=DropdownMenu;export const Trigger=DropdownMenuTrigger;export const Content=DropdownMenuContent;export const Group=DropdownMenuGroup;export const Label=DropdownMenuLabel;export const Item=DropdownMenuItem;export const CheckboxItem=DropdownMenuCheckboxItem;export const RadioGroup=DropdownMenuRadioGroup;export const RadioItem=DropdownMenuRadioItem;export const ItemIndicator=DropdownMenuItemIndicator;export const Separator=DropdownMenuSeparator;export const Arrow=DropdownMenuArrow; 2 | //# sourceMappingURL=index.module.js.map 3 | -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@radix-ui/react-dropdown-menu", 3 | "version": "0.0.19", 4 | "license": "MIT", 5 | "source": "src/index.ts", 6 | "main": "dist/index.js", 7 | "module": "dist/index.module.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "README.md" 12 | ], 13 | "sideEffects": false, 14 | "scripts": { 15 | "clean": "rm -rf dist", 16 | "prepublish": "yarn clean", 17 | "version": "yarn version" 18 | }, 19 | "dependencies": { 20 | "@babel/runtime": "^7.13.10", 21 | "@gorhom/bottom-sheet": "4.0.0-alpha.3", 22 | "@radix-ui/primitive": "workspace:*", 23 | "@radix-ui/react-compose-refs": "workspace:*", 24 | "@radix-ui/react-context": "workspace:*", 25 | "@radix-ui/react-id": "workspace:*", 26 | "@radix-ui/react-menu": "workspace:*", 27 | "@radix-ui/react-polymorphic": "workspace:*", 28 | "@radix-ui/react-primitive": "workspace:*", 29 | "@radix-ui/react-use-controllable-state": "workspace:*" 30 | }, 31 | "peerDependencies": { 32 | "react": "^16.8 || ^17.0", 33 | "react-dom": "^16.8 || ^17.0" 34 | }, 35 | "homepage": "https://radix-ui.com/primitives", 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/radix-ui/primitives.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/radix-ui/primitives/issues" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/src/DropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Platform, View, Modal } from 'react-native'; 3 | import { composeEventHandlers } from '@radix-ui/primitive'; 4 | import { useComposedRefs } from '@radix-ui/react-compose-refs'; 5 | import { createContext } from '@radix-ui/react-context'; 6 | import { useControllableState } from '@radix-ui/react-use-controllable-state'; 7 | import { extendPrimitive } from '@radix-ui/react-primitive'; 8 | import * as MenuPrimitive from '@radix-ui/react-menu'; 9 | import { useId } from '@radix-ui/react-id'; 10 | import BottomSheet from '@gorhom/bottom-sheet'; 11 | 12 | import type * as Polymorphic from '@radix-ui/react-polymorphic'; 13 | 14 | /* ------------------------------------------------------------------------------------------------- 15 | * DropdownMenu 16 | * -----------------------------------------------------------------------------------------------*/ 17 | 18 | const DROPDOWN_MENU_NAME = 'DropdownMenu'; 19 | 20 | type DropdownMenuContextValue = { 21 | triggerRef: React.RefObject; 22 | contentId: string; 23 | open: boolean; 24 | onOpenChange(open: boolean): void; 25 | onOpenToggle(): void; 26 | }; 27 | 28 | const [DropdownMenuProvider, useDropdownMenuContext] = createContext( 29 | DROPDOWN_MENU_NAME 30 | ); 31 | 32 | type DropdownMenuOwnProps = { 33 | open?: boolean; 34 | defaultOpen?: boolean; 35 | onOpenChange?(open: boolean): void; 36 | }; 37 | 38 | const DropdownMenu: React.FC = (props) => { 39 | const { children, open: openProp, defaultOpen, onOpenChange } = props; 40 | const triggerRef = React.useRef(null); 41 | const [open = false, setOpen] = useControllableState({ 42 | prop: openProp, 43 | defaultProp: defaultOpen, 44 | onChange: onOpenChange, 45 | }); 46 | 47 | return ( 48 | 49 | setOpen((prevOpen) => !prevOpen), [setOpen])} 55 | > 56 | {children} 57 | 58 | 59 | ); 60 | }; 61 | 62 | DropdownMenu.displayName = DROPDOWN_MENU_NAME; 63 | 64 | /* ------------------------------------------------------------------------------------------------- 65 | * DropdownMenuTrigger 66 | * -----------------------------------------------------------------------------------------------*/ 67 | 68 | const TRIGGER_NAME = 'DropdownMenuTrigger'; 69 | const TRIGGER_DEFAULT_TAG = Platform.OS === 'web' ? 'button' : View; 70 | 71 | type DropdownMenuTriggerOwnProps = Omit< 72 | Polymorphic.OwnProps, 73 | 'virtualRef' 74 | >; 75 | type DropdownMenuTriggerPrimitive = Polymorphic.ForwardRefComponent< 76 | typeof TRIGGER_DEFAULT_TAG, 77 | DropdownMenuTriggerOwnProps 78 | >; 79 | 80 | const DropdownMenuTrigger = React.forwardRef((props, forwardedRef) => { 81 | const { as = TRIGGER_DEFAULT_TAG, ...triggerProps } = props; 82 | const context = useDropdownMenuContext(TRIGGER_NAME); 83 | const composedTriggerRef = useComposedRefs(forwardedRef, context.triggerRef); 84 | 85 | return ( 86 | { 96 | // only call handler if it's the left button (mousedown gets triggered by all mouse buttons) 97 | // but not when the control key is pressed (avoiding MacOS right click) 98 | if (event.button === 0 && event.ctrlKey === false) { 99 | context.onOpenToggle(); 100 | } 101 | })} 102 | onKeyDown={composeEventHandlers(props.onKeyDown, (event: React.KeyboardEvent) => { 103 | if ([' ', 'Enter', 'ArrowUp', 'ArrowDown'].includes(event.key)) { 104 | event.preventDefault(); 105 | context.onOpenChange(true); 106 | } 107 | })} 108 | /> 109 | ); 110 | }) as DropdownMenuTriggerPrimitive; 111 | 112 | DropdownMenuTrigger.displayName = TRIGGER_NAME; 113 | 114 | /* ------------------------------------------------------------------------------------------------- 115 | * DropdownMenuContent 116 | * -----------------------------------------------------------------------------------------------*/ 117 | 118 | const CONTENT_NAME = 'DropdownMenuContent'; 119 | 120 | type DropdownMenuContentOwnProps = Omit< 121 | Polymorphic.OwnProps, 122 | 'trapFocus' | 'onOpenAutoFocus' 123 | >; 124 | 125 | type DropdownMenuContentPrimitive = Polymorphic.ForwardRefComponent< 126 | Polymorphic.IntrinsicElement, 127 | DropdownMenuContentOwnProps 128 | >; 129 | 130 | const DropdownMenuContent = React.forwardRef((props, forwardedRef) => { 131 | const { 132 | onCloseAutoFocus, 133 | disableOutsidePointerEvents = true, 134 | disableOutsideScroll = true, 135 | portalled = true, 136 | ...contentProps 137 | } = props; 138 | const context = useDropdownMenuContext(CONTENT_NAME); 139 | const bottomSheetRef = React.useRef(null); 140 | const handleSheetChanges = React.useCallback( 141 | (index: number) => { 142 | if (index === -1) { 143 | context.onOpenChange(false); 144 | } 145 | }, 146 | [context] 147 | ); 148 | 149 | if (Platform.OS === 'web') { 150 | return ( 151 | { 165 | event.preventDefault(); 166 | context.triggerRef.current?.focus(); 167 | })} 168 | onPointerDownOutside={composeEventHandlers( 169 | props.onPointerDownOutside, 170 | (event) => { 171 | const targetIsTrigger = context.triggerRef.current?.contains( 172 | event.target as HTMLElement 173 | ); 174 | // prevent dismissing when clicking the trigger 175 | // as it's already setup to close, otherwise it would close and immediately open. 176 | if (targetIsTrigger) event.preventDefault(); 177 | }, 178 | { checkForDefaultPrevented: false } 179 | )} 180 | /> 181 | ); 182 | } 183 | 184 | return ( 185 | { 190 | context.onOpenChange(!context.open); 191 | }} 192 | > 193 | 200 | 201 | ); 202 | }) as DropdownMenuContentPrimitive; 203 | 204 | DropdownMenuContent.displayName = CONTENT_NAME; 205 | 206 | /* ---------------------------------------------------------------------------------------------- */ 207 | 208 | const DropdownMenuGroup = extendPrimitive(MenuPrimitive.Group, { 209 | displayName: 'DropdownMenuGroup', 210 | }); 211 | const DropdownMenuLabel = extendPrimitive(MenuPrimitive.Label, { 212 | displayName: 'DropdownMenuLabel', 213 | }); 214 | const DropdownMenuItem = extendPrimitive(MenuPrimitive.Item, { displayName: 'DropdownMenuItem' }); 215 | const DropdownMenuCheckboxItem = extendPrimitive(MenuPrimitive.CheckboxItem, { 216 | displayName: 'DropdownMenuCheckboxItem', 217 | }); 218 | const DropdownMenuRadioGroup = extendPrimitive(MenuPrimitive.RadioGroup, { 219 | displayName: 'DropdownMenuRadioGroup', 220 | }); 221 | const DropdownMenuRadioItem = extendPrimitive(MenuPrimitive.RadioItem, { 222 | displayName: 'DropdownMenuRadioItem', 223 | }); 224 | const DropdownMenuItemIndicator = extendPrimitive(MenuPrimitive.ItemIndicator, { 225 | displayName: 'DropdownMenuItemIndicator', 226 | }); 227 | const DropdownMenuSeparator = extendPrimitive(MenuPrimitive.Separator, { 228 | displayName: 'DropdownMenuSeparator', 229 | }); 230 | const DropdownMenuArrow = extendPrimitive(MenuPrimitive.Arrow, { 231 | displayName: 'DropdownMenuArrow', 232 | }); 233 | 234 | /* -----------------------------------------------------------------------------------------------*/ 235 | 236 | const Root = DropdownMenu; 237 | const Trigger = DropdownMenuTrigger; 238 | const Content = DropdownMenuContent; 239 | const Group = DropdownMenuGroup; 240 | const Label = DropdownMenuLabel; 241 | const Item = DropdownMenuItem; 242 | const CheckboxItem = DropdownMenuCheckboxItem; 243 | const RadioGroup = DropdownMenuRadioGroup; 244 | const RadioItem = DropdownMenuRadioItem; 245 | const ItemIndicator = DropdownMenuItemIndicator; 246 | const Separator = DropdownMenuSeparator; 247 | const Arrow = DropdownMenuArrow; 248 | 249 | export { 250 | DropdownMenu, 251 | DropdownMenuTrigger, 252 | DropdownMenuContent, 253 | DropdownMenuGroup, 254 | DropdownMenuLabel, 255 | DropdownMenuItem, 256 | DropdownMenuCheckboxItem, 257 | DropdownMenuRadioGroup, 258 | DropdownMenuRadioItem, 259 | DropdownMenuItemIndicator, 260 | DropdownMenuSeparator, 261 | DropdownMenuArrow, 262 | // 263 | Root, 264 | Trigger, 265 | Content, 266 | Group, 267 | Label, 268 | Item, 269 | CheckboxItem, 270 | RadioGroup, 271 | RadioItem, 272 | ItemIndicator, 273 | Separator, 274 | Arrow, 275 | }; 276 | export type { DropdownMenuTriggerPrimitive, DropdownMenuContentPrimitive }; 277 | -------------------------------------------------------------------------------- /packages/radix/dropdown-menu/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DropdownMenu'; 2 | -------------------------------------------------------------------------------- /packages/radix/menu/README.md: -------------------------------------------------------------------------------- 1 | # `react-menu` 2 | 3 | ## Installation 4 | 5 | ```sh 6 | $ yarn add @radix-ui/react-menu 7 | # or 8 | $ npm install @radix-ui/react-menu 9 | ``` 10 | 11 | ## Usage 12 | 13 | This is an internal utility, not intended for public usage. 14 | -------------------------------------------------------------------------------- /packages/radix/menu/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { DismissableLayer } from "@radix-ui/react-dismissable-layer"; 3 | import { FocusScope } from "@radix-ui/react-focus-scope"; 4 | import { Primitive } from "@radix-ui/react-primitive"; 5 | import * as PopperPrimitive from "@radix-ui/react-popper"; 6 | import { RovingFocusGroup, RovingFocusItem } from "@radix-ui/react-roving-focus"; 7 | import * as Polymorphic from "@radix-ui/react-polymorphic"; 8 | type MenuOwnProps = { 9 | open?: boolean; 10 | onOpenChange?(open: boolean): void; 11 | }; 12 | export const Menu: React.FC; 13 | type MenuContentOwnProps = Polymorphic.Merge, { 14 | /** 15 | * Used to force mounting when more control is needed. Useful when 16 | * controlling animation with React animation libraries. 17 | */ 18 | forceMount?: true; 19 | }>; 20 | export type MenuContentPrimitive = Polymorphic.ForwardRefComponent, MenuContentOwnProps>; 21 | export const MenuContent: MenuContentPrimitive; 22 | type FocusScopeOwnProps = Polymorphic.OwnProps; 23 | type DismissableLayerOwnProps = Polymorphic.OwnProps; 24 | type RovingFocusGroupOwnProps = Polymorphic.OwnProps; 25 | type MenuContentImplOwnProps = Polymorphic.Merge, Omit & { 26 | /** 27 | * Whether focus should be trapped within the `MenuContent` 28 | * (default: false) 29 | */ 30 | trapFocus?: FocusScopeOwnProps['trapped']; 31 | /** 32 | * Event handler called when auto-focusing on open. 33 | * Can be prevented. 34 | */ 35 | onOpenAutoFocus?: FocusScopeOwnProps['onMountAutoFocus']; 36 | /** 37 | * Event handler called when auto-focusing on close. 38 | * Can be prevented. 39 | */ 40 | onCloseAutoFocus?: FocusScopeOwnProps['onUnmountAutoFocus']; 41 | /** 42 | * Whether scrolling outside the `MenuContent` should be prevented 43 | * (default: `false`) 44 | */ 45 | disableOutsideScroll?: boolean; 46 | /** 47 | * The direction of navigation between menu items. 48 | * @defaultValue ltr 49 | */ 50 | dir?: RovingFocusGroupOwnProps['dir']; 51 | /** 52 | * Whether keyboard navigation should loop around 53 | * @defaultValue false 54 | */ 55 | loop?: RovingFocusGroupOwnProps['loop']; 56 | /** 57 | * Whether the `MenuContent` should render in a `Portal` 58 | * (default: `true`) 59 | */ 60 | portalled?: boolean; 61 | }>; 62 | type MenuContentImplPrimitive = Polymorphic.ForwardRefComponent, MenuContentImplOwnProps>; 63 | declare const MenuContentImpl: MenuContentImplPrimitive; 64 | declare const ITEM_DEFAULT_TAG = "div"; 65 | type MenuItemOwnProps = Polymorphic.Merge, 'focusable' | 'active'>, { 66 | disabled?: boolean; 67 | textValue?: string; 68 | onSelect?: (event: Event) => void; 69 | }>; 70 | export type MenuItemPrimitive = Polymorphic.ForwardRefComponent; 71 | export const MenuItem: MenuItemPrimitive; 72 | type MenuCheckboxItemOwnProps = Polymorphic.Merge, { 73 | checked?: boolean; 74 | onCheckedChange?: (checked: boolean) => void; 75 | }>; 76 | export type MenuCheckboxItemPrimitive = Polymorphic.ForwardRefComponent, MenuCheckboxItemOwnProps>; 77 | export const MenuCheckboxItem: MenuCheckboxItemPrimitive; 78 | type MenuRadioGroupOwnProps = Polymorphic.Merge, { 79 | value?: string; 80 | onValueChange?: (value: string) => void; 81 | }>; 82 | export type MenuRadioGroupPrimitive = Polymorphic.ForwardRefComponent, MenuRadioGroupOwnProps>; 83 | export const MenuRadioGroup: MenuRadioGroupPrimitive; 84 | type MenuRadioItemOwnProps = Polymorphic.Merge, { 85 | value: string; 86 | }>; 87 | export type MenuRadioItemPrimitive = Polymorphic.ForwardRefComponent, MenuRadioItemOwnProps>; 88 | export const MenuRadioItem: MenuRadioItemPrimitive; 89 | declare const ITEM_INDICATOR_DEFAULT_TAG = "span"; 90 | type MenuItemIndicatorOwnProps = Polymorphic.Merge, { 91 | /** 92 | * Used to force mounting when more control is needed. Useful when 93 | * controlling animation with React animation libraries. 94 | */ 95 | forceMount?: true; 96 | }>; 97 | export type MenuItemIndicatorPrimitive = Polymorphic.ForwardRefComponent; 98 | export const MenuItemIndicator: MenuItemIndicatorPrimitive; 99 | export const MenuAnchor: import("@radix-ui/react-primitive").ExtendedPrimitive; 100 | export const MenuGroup: import("@radix-ui/react-primitive").ExtendedPrimitive; 101 | export const MenuLabel: import("@radix-ui/react-primitive").ExtendedPrimitive; 102 | export const MenuSeparator: import("@radix-ui/react-primitive").ExtendedPrimitive; 103 | export const MenuArrow: import("@radix-ui/react-primitive").ExtendedPrimitive; 104 | export const Root: React.FC; 105 | export const Anchor: import("@radix-ui/react-primitive").ExtendedPrimitive; 106 | export const Content: MenuContentPrimitive; 107 | export const Group: import("@radix-ui/react-primitive").ExtendedPrimitive; 108 | export const Label: import("@radix-ui/react-primitive").ExtendedPrimitive; 109 | export const Item: MenuItemPrimitive; 110 | export const CheckboxItem: MenuCheckboxItemPrimitive; 111 | export const RadioGroup: MenuRadioGroupPrimitive; 112 | export const RadioItem: MenuRadioItemPrimitive; 113 | export const ItemIndicator: MenuItemIndicatorPrimitive; 114 | export const Separator: import("@radix-ui/react-primitive").ExtendedPrimitive; 115 | export const Arrow: import("@radix-ui/react-primitive").ExtendedPrimitive; 116 | 117 | //# sourceMappingURL=index.d.ts.map 118 | -------------------------------------------------------------------------------- /packages/radix/menu/dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"mappings":"A;A;A;A;A;A;A;ACuCA,oBAAoB;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;CACpC,CAAC;AAEF,OAAA,MAAM,MAAM,MAAM,EAAE,CAAC,YAAY,CAUhC,CAAC;AAqBF,2BAA2B,YAAY,KAAK,CAC1C,YAAY,QAAQ,CAAC,sBAAsB,CAAC,EAC5C;IACE;A;A;OAGG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB,CACF,CAAC;AAEF,mCAA4B,YAAY,mBAAmB,CACzD,YAAY,gBAAgB,CAAC,sBAAsB,CAAC,EACpD,mBAAmB,CACpB,CAAC;AAEF,OAAA,MAAM,iCAcoB,CAAC;AAE3B,0BAA0B,YAAY,QAAQ,CAAC,iBAAiB,CAAC,CAAC;AAClE,gCAAgC,YAAY,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AAC9E,gCAAgC,YAAY,QAAQ,CAAC,uBAAuB,CAAC,CAAC;AAE9E,+BAA+B,YAAY,KAAK,CAC9C,YAAY,QAAQ,CAAC,OAAO,gBAAgB,OAAO,CAAC,EACpD,IAAI,CAAC,wBAAwB,EAAE,WAAW,CAAC,GAAG;IAC5C;A;A;OAGG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAE1C;A;A;OAGG;IACH,eAAe,CAAC,EAAE,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;IAEzD;A;A;OAGG;IACH,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;IAE5D;A;A;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;A;A;OAGG;IACH,GAAG,CAAC,EAAE,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAEtC;A;A;OAGG;IACH,IAAI,CAAC,EAAE,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAExC;A;A;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CACF,CAAC;AAEF,gCAAgC,YAAY,mBAAmB,CAC7D,YAAY,gBAAgB,CAAC,OAAO,gBAAgB,OAAO,CAAC,EAC5D,uBAAuB,CACxB,CAAC;AAEF,QAAA,MAAM,yCA8HwB,CAAC;AAS/B,QAAA,MAAM,wBAAwB,CAAC;AAG/B,wBAAwB,YAAY,KAAK,CACvC,IAAI,CAAC,YAAY,QAAQ,CAAC,sBAAsB,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAC,EAC1E;IACE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACnC,CACF,CAAC;AAEF,gCAAyB,YAAY,mBAAmB,CAAC,uBAAuB,EAAE,gBAAgB,CAAC,CAAC;AAEpG,OAAA,MAAM,2BAmFiB,CAAC;AAsBxB,gCAAgC,YAAY,KAAK,CAC/C,YAAY,QAAQ,CAAC,eAAe,CAAC,EACrC;IACE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9C,CACF,CAAC;AAEF,wCAAiC,YAAY,mBAAmB,CAC9D,YAAY,gBAAgB,CAAC,eAAe,CAAC,EAC7C,wBAAwB,CACzB,CAAC;AAEF,OAAA,MAAM,2CAkByB,CAAC;AAYhC,8BAA8B,YAAY,KAAK,CAC7C,YAAY,QAAQ,CAAC,gBAAgB,CAAC,EACtC;IACE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC,CACF,CAAC;AAEF,sCAA+B,YAAY,mBAAmB,CAC5D,YAAY,gBAAgB,CAAC,gBAAgB,CAAC,EAC9C,sBAAsB,CACvB,CAAC;AAEF,OAAA,MAAM,uCAYuB,CAAC;AAU9B,6BAA6B,YAAY,KAAK,CAC5C,YAAY,QAAQ,CAAC,eAAe,CAAC,EACrC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAClB,CAAC;AACF,qCAA8B,YAAY,mBAAmB,CAC3D,YAAY,gBAAgB,CAAC,eAAe,CAAC,EAC7C,qBAAqB,CACtB,CAAC;AAEF,OAAA,MAAM,qCAoBsB,CAAC;AAS7B,QAAA,MAAM,mCAAmC,CAAC;AAI1C,iCAAiC,YAAY,KAAK,CAChD,YAAY,QAAQ,CAAC,gBAAgB,CAAC,EACtC;IACE;A;A;OAGG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC;CACnB,CACF,CAAC;AAEF,yCAAkC,YAAY,mBAAmB,CAC/D,iCAAiC,EACjC,yBAAyB,CAC1B,CAAC;AAEF,OAAA,MAAM,6CAa0B,CAAC;AAMjC,OAAA,MAAM,+GAAmF,CAAC;AAC1F,OAAA,MAAM,+HAGJ,CAAC;AACH,OAAA,MAAM,+HAAoE,CAAC;AAC3E,OAAA,MAAM,mIAGJ,CAAC;AACH,OAAA,MAAM,6GAAgF,CAAC;AAsBvF,OAAA,MAAM,4BAAW,CAAC;AAClB,OAAA,MAAM,2GAAmB,CAAC;AAC1B,OAAA,MAAM,6BAAqB,CAAC;AAC5B,OAAA,MAAM,2HAAiB,CAAC;AACxB,OAAA,MAAM,2HAAiB,CAAC;AACxB,OAAA,MAAM,uBAAwD,CAAC;AAC/D,OAAA,MAAM,uCAA+B,CAAC;AACtC,OAAA,MAAM,mCAA2B,CAAC;AAClC,OAAA,MAAM,iCAAyB,CAAC;AAChC,OAAA,MAAM,yCAAiC,CAAC;AACxC,OAAA,MAAM,+HAAyB,CAAC;AAChC,OAAA,MAAM,yGAAiB,CAAC","sources":["./packages/react/menu/src/packages/react/menu/src/useMenuTypeahead.tsx","./packages/react/menu/src/packages/react/menu/src/Menu.tsx","./packages/react/menu/src/packages/react/menu/src/index.ts"],"sourcesContent":[null,null,null],"names":[],"version":3,"file":"index.d.ts.map"} -------------------------------------------------------------------------------- /packages/radix/menu/dist/index.js: -------------------------------------------------------------------------------- 1 | var e,t=require("@radix-ui/react-focus-guards").useFocusGuards,r=require("@radix-ui/react-use-callback-ref").useCallbackRef,n=require("@radix-ui/react-slot").Slot,o=require("@radix-ui/react-roving-focus"),a=o.RovingFocusGroup,u=o.RovingFocusItem,c=require("@radix-ui/react-portal").Portal,s=D({},require("@radix-ui/react-popper")),i=require("@radix-ui/react-primitive"),l=i.Primitive,d=i.extendPrimitive,p=require("@radix-ui/react-presence").Presence,m=require("@radix-ui/react-focus-scope").FocusScope,f=require("@radix-ui/react-dismissable-layer").DismissableLayer,v=require("@radix-ui/react-context").createContext,x=require("@radix-ui/react-compose-refs").useComposedRefs,h=require("@radix-ui/react-collection").createCollection,E=require("@radix-ui/primitive").composeEventHandlers,y=require("aria-hidden").hideOthers,M=require("react-remove-scroll").RemoveScroll,b=require("react-native"),w=b.Platform,C=b.View,g=D({},require("react")),k=(e=require("@babel/runtime/helpers/extends"))&&e.__esModule?e.default:e;function D(e,t){return Object.keys(t).forEach((function(r){"default"!==r&&"__esModule"!==r&&Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[r]}})})),e}function P(){const e=g.useRef(0),t=g.useRef(""),r=g.useCallback((n=>{t.current=n,window.clearTimeout(e.current),e.current=window.setTimeout((()=>r("")),1e3)}),[]);return{onKeyDownCapture:e=>{if(1===e.key.length&&!(e.ctrlKey||e.altKey||e.metaKey)){const n=e.currentTarget;r(t.current+e.key)," "!==e.key||t.current.startsWith(" ")||e.stopPropagation();const o=document.activeElement,a=o?R(o):void 0,u=function(e,t,r){const n=t.length>1&&Array.from(t).every((e=>e===t[0]))?t[0]:t,o=r?e.indexOf(r):-1;let a=(u=e,c=Math.max(o,0),u.map(((e,t)=>u[(c+t)%u.length])));var u,c;1===n.length&&(a=a.filter((e=>e!==r)));const s=a.find((e=>e.toLowerCase().startsWith(n.toLowerCase())));return s!==r?s:void 0}(Array.from(n.querySelectorAll(`[${S}]`)).map(R),t.current,a),c=n.querySelector(`[${S}="${u}"]`);c&&setTimeout((()=>c.focus()))}}}}const R=e=>{var t;return null!==(t=e.getAttribute(S))&&void 0!==t?t:""},S="data-radix-menu-typeahead-item";const q=["ArrowUp","PageDown","End"],I=["ArrowDown","PageUp","Home",...q],[O,A]=v("Menu"),F=e=>{const{open:t=!1,children:n,onOpenChange:o}=e,a=r(o);/*#__PURE__*/return g.createElement(s.Root,null,/*#__PURE__*/g.createElement(O,{open:t,onOpenChange:a},n))};exports.Menu=F;const[K,L,G]=h(),[T,V]=v("MenuContent"),N=/*#__PURE__*/g.forwardRef(((e,t)=>{const{forceMount:r,...n}=e,o=A("MenuContent");/*#__PURE__*/return g.createElement(p,{present:r||o.open},/*#__PURE__*/g.createElement(K,null,/*#__PURE__*/g.createElement(U,k({"data-state":(a=o.open,a?"open":"closed")},n,{ref:t}))));var a}));exports.MenuContent=N;const U=/*#__PURE__*/g.forwardRef(((e,r)=>{const{dir:o="ltr",loop:u=!1,trapFocus:i,onOpenAutoFocus:l,onCloseAutoFocus:d,disableOutsidePointerEvents:p,onEscapeKeyDown:v,onPointerDownOutside:h,onFocusOutside:b,onInteractOutside:w,disableOutsideScroll:C,portalled:D,...R}=e,S=A("MenuContent"),O=P(),{getItems:F}=G(),[K,L]=g.useState(null),[V,N]=g.useState(!1),U=g.useRef(null),_=x(r,U),$=D?c:g.Fragment,j=C?M:g.Fragment;return t(),g.useEffect((()=>{const e=U.current;if(e)return y(e)}),[]),/*#__PURE__*/g.createElement($,null,/*#__PURE__*/g.createElement(j,null,/*#__PURE__*/g.createElement(T,{onItemLeave:g.useCallback((()=>{var e;null===(e=U.current)||void 0===e||e.focus(),L(null)}),[])},/*#__PURE__*/g.createElement(m,{as:n,trapped:i&&S.open,onMountAutoFocus:l,onUnmountAutoFocus:e=>{V?e.preventDefault():null==d||d(e)}},/*#__PURE__*/g.createElement(f,{as:n,disableOutsidePointerEvents:p,onEscapeKeyDown:E(v,(()=>{N(!1)})),onPointerDownOutside:E(h,(e=>{const t=e.detail.originalEvent,r=0===t.button&&!1===t.ctrlKey;N(!p&&r)}),{checkForDefaultPrevented:!1}),onFocusOutside:E(b,(e=>{i&&e.preventDefault()}),{checkForDefaultPrevented:!1}),onInteractOutside:w,onDismiss:()=>S.onOpenChange(!1)},/*#__PURE__*/g.createElement(a,{as:n,dir:o,orientation:"vertical",loop:u,currentTabStopId:K,onCurrentTabStopIdChange:L,onEntryFocus:e=>e.preventDefault()},/*#__PURE__*/g.createElement(s.Content,k({role:"menu"},R,{ref:_,style:{outline:"none",...R.style},onKeyDownCapture:E(R.onKeyDownCapture,O.onKeyDownCapture),onKeyDown:E(R.onKeyDown,(e=>{const t=U.current;if(e.target!==t)return;if(!I.includes(e.key))return;e.preventDefault();const r=F().filter((e=>!e.disabled)).map((e=>e.ref.current));q.includes(e.key)&&r.reverse(),function(e){const t=document.activeElement;for(const r of e){if(r===t)return;if(r.focus(),document.activeElement!==t)return}}(r)}))}))))))))})),_="div",$=/*#__PURE__*/g.forwardRef(((e,t)=>{const{as:r=_,disabled:n=!1,textValue:o,onSelect:a,...c}=e,s=g.useRef(null),i=x(t,s),l=A("MenuItem"),d=V("MenuItem"),[p,m]=g.useState("");g.useEffect((()=>{const e=s.current;var t;e&&m((null!==(t=e.textContent)&&void 0!==t?t:"").trim())}),[c.children]);const f=function({textValue:e,disabled:t}){return{[S]:t?void 0:e}}({textValue:null!=o?o:p,disabled:n}),v=()=>{const e=s.current;if(!n&&e){var t;const r=new Event("menu.itemSelect",{bubbles:!0,cancelable:!0});if(e.dispatchEvent(r),r.defaultPrevented)return;null===(t=l.onOpenChange)||void 0===t||t.call(l,!1)}};return g.useEffect((()=>{const e=s.current;if(e){const t=e=>null==a?void 0:a(e);return e.addEventListener("menu.itemSelect",t),()=>e.removeEventListener("menu.itemSelect",t)}}),[a]),/*#__PURE__*/g.createElement(L,{disabled:n},/*#__PURE__*/g.createElement(u,k({role:"menuitem","aria-disabled":n||void 0,focusable:!n},c,f,{as:r,ref:i,"data-disabled":n?"":void 0,onKeyDown:E(e.onKeyDown,(e=>{n||"Enter"!==e.key&&" "!==e.key||(" "===e.key&&e.preventDefault(),v())})),onMouseUp:E(e.onMouseUp,v),onMouseMove:E(e.onMouseMove,(e=>{if(n)d.onItemLeave();else{e.currentTarget.focus()}})),onMouseLeave:E(e.onMouseLeave,(()=>d.onItemLeave()))})))}));/*#__PURE__*/exports.MenuItem=$;const j=/*#__PURE__*/g.forwardRef(((e,t)=>{const{...r}=e;/*#__PURE__*/return g.createElement(C,k({},r,{ref:t}))})),H=/*#__PURE__*/g.forwardRef(((e,t)=>{const{checked:r=!1,onCheckedChange:n,...o}=e;/*#__PURE__*/return g.createElement(Q.Provider,{value:r},/*#__PURE__*/g.createElement($,k({role:"menuitemcheckbox","aria-checked":r},o,{ref:t,"data-state":ne(r),onSelect:E(o.onSelect,(()=>null==n?void 0:n(!r)),{checkForDefaultPrevented:!1})})))}));/*#__PURE__*/exports.MenuCheckboxItem=H;const W=/*#__PURE__*/g.createContext({}),z=/*#__PURE__*/g.forwardRef(((e,t)=>{const{value:n,onValueChange:o,...a}=e,u=r(o),c=g.useMemo((()=>({value:n,onValueChange:u})),[n,u]);/*#__PURE__*/return g.createElement(W.Provider,{value:c},/*#__PURE__*/g.createElement(Z,k({},a,{ref:t})))}));exports.MenuRadioGroup=z;const B=/*#__PURE__*/g.forwardRef(((e,t)=>{const{value:r,...n}=e,o=g.useContext(W),a=r===o.value;/*#__PURE__*/return g.createElement(Q.Provider,{value:a},/*#__PURE__*/g.createElement($,k({role:"menuitemradio","aria-checked":a},n,{ref:t,"data-state":ne(a),onSelect:E(n.onSelect,(()=>{var e;return null===(e=o.onValueChange)||void 0===e?void 0:e.call(o,r)}),{checkForDefaultPrevented:!1})})))}));exports.MenuRadioItem=B;const J="span",Q=/*#__PURE__*/g.createContext(!1),X=/*#__PURE__*/g.forwardRef(((e,t)=>{const{as:r=J,forceMount:n,...o}=e,a=g.useContext(Q);/*#__PURE__*/return g.createElement(p,{present:n||a},/*#__PURE__*/g.createElement(l,k({},o,{as:r,ref:t,"data-state":ne(a)})))}));exports.MenuItemIndicator=X;const Y=d(s.Anchor,{displayName:"MenuAnchor"});exports.MenuAnchor=Y;const Z=d(l,{defaultProps:{role:"group"},displayName:"MenuGroup"});exports.MenuGroup=Z;const ee=d(l,{displayName:"MenuLabel"});exports.MenuLabel=ee;const te=d(l,{defaultProps:{role:"separator","aria-orientation":"horizontal"},displayName:"MenuSeparator "});exports.MenuSeparator=te;const re=d(s.Arrow,{displayName:"MenuArrow"});function ne(e){return e?"checked":"unchecked"}exports.MenuArrow=re;const oe=F;exports.Root=oe;const ae=Y;exports.Anchor=ae;const ue=N;exports.Content=ue;const ce=Z;exports.Group=ce;const se=ee;exports.Label=se;const ie="web"===w.OS?$:j;exports.Item=ie;const le=H;exports.CheckboxItem=le;const de=z;exports.RadioGroup=de;const pe=B;exports.RadioItem=pe;const me=X;exports.ItemIndicator=me;const fe=te;exports.Separator=fe;const ve=re;exports.Arrow=ve; 2 | //# sourceMappingURL=index.js.map 3 | -------------------------------------------------------------------------------- /packages/radix/menu/dist/index.module.js: -------------------------------------------------------------------------------- 1 | import{useFocusGuards as e}from"@radix-ui/react-focus-guards";import{useCallbackRef as t}from"@radix-ui/react-use-callback-ref";import{Slot as r}from"@radix-ui/react-slot";import{RovingFocusGroup as n,RovingFocusItem as o}from"@radix-ui/react-roving-focus";import{Portal as a}from"@radix-ui/react-portal";import*as u from"@radix-ui/react-popper";import{Primitive as c,extendPrimitive as i}from"@radix-ui/react-primitive";import{Presence as s}from"@radix-ui/react-presence";import{FocusScope as l}from"@radix-ui/react-focus-scope";import{DismissableLayer as m}from"@radix-ui/react-dismissable-layer";import{createContext as d}from"@radix-ui/react-context";import{useComposedRefs as p}from"@radix-ui/react-compose-refs";import{createCollection as f}from"@radix-ui/react-collection";import{composeEventHandlers as v}from"@radix-ui/primitive";import{hideOthers as x}from"aria-hidden";import{RemoveScroll as M}from"react-remove-scroll";import{Platform as h,View as E}from"react-native";import*as w from"react";import y from"@babel/runtime/helpers/esm/extends";function C(){const e=w.useRef(0),t=w.useRef(""),r=w.useCallback((n=>{t.current=n,window.clearTimeout(e.current),e.current=window.setTimeout((()=>r("")),1e3)}),[]);return{onKeyDownCapture:e=>{if(1===e.key.length&&!(e.ctrlKey||e.altKey||e.metaKey)){const n=e.currentTarget;r(t.current+e.key)," "!==e.key||t.current.startsWith(" ")||e.stopPropagation();const o=document.activeElement,a=o?b(o):void 0,u=function(e,t,r){const n=t.length>1&&Array.from(t).every((e=>e===t[0]))?t[0]:t,o=r?e.indexOf(r):-1;let a=(u=e,c=Math.max(o,0),u.map(((e,t)=>u[(c+t)%u.length])));var u,c;1===n.length&&(a=a.filter((e=>e!==r)));const i=a.find((e=>e.toLowerCase().startsWith(n.toLowerCase())));return i!==r?i:void 0}(Array.from(n.querySelectorAll(`[${g}]`)).map(b),t.current,a),c=n.querySelector(`[${g}="${u}"]`);c&&setTimeout((()=>c.focus()))}}}}const b=e=>{var t;return null!==(t=e.getAttribute(g))&&void 0!==t?t:""},g="data-radix-menu-typeahead-item";const I=["ArrowUp","PageDown","End"],k=["ArrowDown","PageUp","Home",...I],[D,S]=d("Menu");export const Menu=e=>{const{open:r=!1,children:n,onOpenChange:o}=e,a=t(o);/*#__PURE__*/return w.createElement(u.Root,null,/*#__PURE__*/w.createElement(D,{open:r,onOpenChange:a},n))};/*#__PURE__*/const[A,R,P]=f(),[O,F]=d("MenuContent");export const MenuContent=/*#__PURE__*/w.forwardRef(((e,t)=>{const{forceMount:r,...n}=e,o=S("MenuContent");/*#__PURE__*/return w.createElement(s,{present:r||o.open},/*#__PURE__*/w.createElement(A,null,/*#__PURE__*/w.createElement(K,y({"data-state":(a=o.open,a?"open":"closed")},n,{ref:t}))));var a}));const K=/*#__PURE__*/w.forwardRef(((t,o)=>{const{dir:c="ltr",loop:i=!1,trapFocus:s,onOpenAutoFocus:d,onCloseAutoFocus:f,disableOutsidePointerEvents:h,onEscapeKeyDown:E,onPointerDownOutside:b,onFocusOutside:g,onInteractOutside:D,disableOutsideScroll:A,portalled:R,...F}=t,K=S("MenuContent"),L=C(),{getItems:G}=P(),[T,V]=w.useState(null),[N,U]=w.useState(!1),$=w.useRef(null),q=p(o,$),W=R?a:w.Fragment,z=A?M:w.Fragment;return e(),w.useEffect((()=>{const e=$.current;if(e)return x(e)}),[]),/*#__PURE__*/w.createElement(W,null,/*#__PURE__*/w.createElement(z,null,/*#__PURE__*/w.createElement(O,{onItemLeave:w.useCallback((()=>{var e;null===(e=$.current)||void 0===e||e.focus(),V(null)}),[])},/*#__PURE__*/w.createElement(l,{as:r,trapped:s&&K.open,onMountAutoFocus:d,onUnmountAutoFocus:e=>{N?e.preventDefault():null==f||f(e)}},/*#__PURE__*/w.createElement(m,{as:r,disableOutsidePointerEvents:h,onEscapeKeyDown:v(E,(()=>{U(!1)})),onPointerDownOutside:v(b,(e=>{const t=e.detail.originalEvent,r=0===t.button&&!1===t.ctrlKey;U(!h&&r)}),{checkForDefaultPrevented:!1}),onFocusOutside:v(g,(e=>{s&&e.preventDefault()}),{checkForDefaultPrevented:!1}),onInteractOutside:D,onDismiss:()=>K.onOpenChange(!1)},/*#__PURE__*/w.createElement(n,{as:r,dir:c,orientation:"vertical",loop:i,currentTabStopId:T,onCurrentTabStopIdChange:V,onEntryFocus:e=>e.preventDefault()},/*#__PURE__*/w.createElement(u.Content,y({role:"menu"},F,{ref:q,style:{outline:"none",...F.style},onKeyDownCapture:v(F.onKeyDownCapture,L.onKeyDownCapture),onKeyDown:v(F.onKeyDown,(e=>{const t=$.current;if(e.target!==t)return;if(!k.includes(e.key))return;e.preventDefault();const r=G().filter((e=>!e.disabled)).map((e=>e.ref.current));I.includes(e.key)&&r.reverse(),function(e){const t=document.activeElement;for(const r of e){if(r===t)return;if(r.focus(),document.activeElement!==t)return}}(r)}))}))))))))})),L="div";/*#__PURE__*/export const MenuItem=/*#__PURE__*/w.forwardRef(((e,t)=>{const{as:r=L,disabled:n=!1,textValue:a,onSelect:u,...c}=e,i=w.useRef(null),s=p(t,i),l=S("MenuItem"),m=F("MenuItem"),[d,f]=w.useState("");w.useEffect((()=>{const e=i.current;var t;e&&f((null!==(t=e.textContent)&&void 0!==t?t:"").trim())}),[c.children]);const x=function({textValue:e,disabled:t}){return{[g]:t?void 0:e}}({textValue:null!=a?a:d,disabled:n}),M=()=>{const e=i.current;if(!n&&e){var t;const r=new Event("menu.itemSelect",{bubbles:!0,cancelable:!0});if(e.dispatchEvent(r),r.defaultPrevented)return;null===(t=l.onOpenChange)||void 0===t||t.call(l,!1)}};return w.useEffect((()=>{const e=i.current;if(e){const t=e=>null==u?void 0:u(e);return e.addEventListener("menu.itemSelect",t),()=>e.removeEventListener("menu.itemSelect",t)}}),[u]),/*#__PURE__*/w.createElement(R,{disabled:n},/*#__PURE__*/w.createElement(o,y({role:"menuitem","aria-disabled":n||void 0,focusable:!n},c,x,{as:r,ref:s,"data-disabled":n?"":void 0,onKeyDown:v(e.onKeyDown,(e=>{n||"Enter"!==e.key&&" "!==e.key||(" "===e.key&&e.preventDefault(),M())})),onMouseUp:v(e.onMouseUp,M),onMouseMove:v(e.onMouseMove,(e=>{if(n)m.onItemLeave();else{e.currentTarget.focus()}})),onMouseLeave:v(e.onMouseLeave,(()=>m.onItemLeave()))})))}));/*#__PURE__*/const G=/*#__PURE__*/w.forwardRef(((e,t)=>{const{...r}=e;/*#__PURE__*/return w.createElement(E,y({},r,{ref:t}))}));/*#__PURE__*/export const MenuCheckboxItem=/*#__PURE__*/w.forwardRef(((e,t)=>{const{checked:r=!1,onCheckedChange:n,...o}=e;/*#__PURE__*/return w.createElement(N.Provider,{value:r},/*#__PURE__*/w.createElement(MenuItem,y({role:"menuitemcheckbox","aria-checked":r},o,{ref:t,"data-state":U(r),onSelect:v(o.onSelect,(()=>null==n?void 0:n(!r)),{checkForDefaultPrevented:!1})})))}));/*#__PURE__*/const T=/*#__PURE__*/w.createContext({});export const MenuRadioGroup=/*#__PURE__*/w.forwardRef(((e,r)=>{const{value:n,onValueChange:o,...a}=e,u=t(o),c=w.useMemo((()=>({value:n,onValueChange:u})),[n,u]);/*#__PURE__*/return w.createElement(T.Provider,{value:c},/*#__PURE__*/w.createElement(MenuGroup,y({},a,{ref:r})))}));/*#__PURE__*/export const MenuRadioItem=/*#__PURE__*/w.forwardRef(((e,t)=>{const{value:r,...n}=e,o=w.useContext(T),a=r===o.value;/*#__PURE__*/return w.createElement(N.Provider,{value:a},/*#__PURE__*/w.createElement(MenuItem,y({role:"menuitemradio","aria-checked":a},n,{ref:t,"data-state":U(a),onSelect:v(n.onSelect,(()=>{var e;return null===(e=o.onValueChange)||void 0===e?void 0:e.call(o,r)}),{checkForDefaultPrevented:!1})})))}));/*#__PURE__*/const V="span",N=/*#__PURE__*/w.createContext(!1);export const MenuItemIndicator=/*#__PURE__*/w.forwardRef(((e,t)=>{const{as:r=V,forceMount:n,...o}=e,a=w.useContext(N);/*#__PURE__*/return w.createElement(s,{present:n||a},/*#__PURE__*/w.createElement(c,y({},o,{as:r,ref:t,"data-state":U(a)})))}));/*#__PURE__*/export const MenuAnchor=i(u.Anchor,{displayName:"MenuAnchor"});export const MenuGroup=i(c,{defaultProps:{role:"group"},displayName:"MenuGroup"});export const MenuLabel=i(c,{displayName:"MenuLabel"});export const MenuSeparator=i(c,{defaultProps:{role:"separator","aria-orientation":"horizontal"},displayName:"MenuSeparator "});export const MenuArrow=i(u.Arrow,{displayName:"MenuArrow"});function U(e){return e?"checked":"unchecked"}export const Root=Menu;export const Anchor=MenuAnchor;export const Content=MenuContent;export const Group=MenuGroup;export const Label=MenuLabel;export const Item="web"===h.OS?MenuItem:G;export const CheckboxItem=MenuCheckboxItem;export const RadioGroup=MenuRadioGroup;export const RadioItem=MenuRadioItem;export const ItemIndicator=MenuItemIndicator;export const Separator=MenuSeparator;export const Arrow=MenuArrow; 2 | //# sourceMappingURL=index.module.js.map 3 | -------------------------------------------------------------------------------- /packages/radix/menu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@radix-ui/react-menu", 3 | "version": "0.0.18", 4 | "license": "MIT", 5 | "source": "src/index.ts", 6 | "main": "dist/index.js", 7 | "module": "dist/index.module.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "README.md" 12 | ], 13 | "sideEffects": false, 14 | "scripts": { 15 | "clean": "rm -rf dist", 16 | "prepublish": "yarn clean", 17 | "version": "yarn version" 18 | }, 19 | "dependencies": { 20 | "@babel/runtime": "^7.13.10", 21 | "@radix-ui/primitive": "workspace:*", 22 | "@radix-ui/react-collection": "workspace:*", 23 | "@radix-ui/react-compose-refs": "workspace:*", 24 | "@radix-ui/react-context": "workspace:*", 25 | "@radix-ui/react-dismissable-layer": "workspace:*", 26 | "@radix-ui/react-focus-guards": "workspace:*", 27 | "@radix-ui/react-focus-scope": "workspace:*", 28 | "@radix-ui/react-polymorphic": "workspace:*", 29 | "@radix-ui/react-popper": "workspace:*", 30 | "@radix-ui/react-portal": "workspace:*", 31 | "@radix-ui/react-presence": "workspace:*", 32 | "@radix-ui/react-primitive": "workspace:*", 33 | "@radix-ui/react-roving-focus": "workspace:*", 34 | "@radix-ui/react-slot": "workspace:*", 35 | "@radix-ui/react-use-callback-ref": "workspace:*", 36 | "aria-hidden": "^1.1.1", 37 | "react-remove-scroll": "^2.4.0" 38 | }, 39 | "peerDependencies": { 40 | "react": "^16.8 || ^17.0", 41 | "react-dom": "^16.8 || ^17.0" 42 | }, 43 | "homepage": "https://radix-ui.com/primitives", 44 | "repository": { 45 | "type": "git", 46 | "url": "git+https://github.com/radix-ui/primitives.git" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/radix-ui/primitives/issues" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/radix/menu/src/Menu.stories.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { 3 | Menu as MenuPrimitive, 4 | MenuAnchor, 5 | MenuContent, 6 | MenuGroup, 7 | MenuLabel, 8 | MenuItem, 9 | MenuCheckboxItem, 10 | MenuRadioGroup, 11 | MenuRadioItem, 12 | MenuItemIndicator, 13 | MenuSeparator, 14 | } from './Menu'; 15 | import { css } from '../../../../stitches.config'; 16 | import { foodGroups } from '../../../../test-data/foods'; 17 | 18 | export default { 19 | title: 'Components/Menu', 20 | excludeStories: ['TickIcon', 'styledComponents', 'classes'], 21 | }; 22 | 23 | export const Styled = () => ( 24 | 25 | window.alert('undo')}> 26 | Undo 27 | 28 | window.alert('redo')}> 29 | Redo 30 | 31 | 32 | window.alert('cut')}> 33 | Cut 34 | 35 | window.alert('copy')}> 36 | Copy 37 | 38 | window.alert('paste')}> 39 | Paste 40 | 41 | 42 | ); 43 | 44 | export const WithLabels = () => ( 45 | 46 | {foodGroups.map((foodGroup, index) => ( 47 | 48 | {foodGroup.label && ( 49 | 50 | {foodGroup.label} 51 | 52 | )} 53 | {foodGroup.foods.map((food) => ( 54 | window.alert(food.label)} 59 | > 60 | {food.label} 61 | 62 | ))} 63 | {index < foodGroups.length - 1 && } 64 | 65 | ))} 66 | 67 | ); 68 | 69 | const suits = [ 70 | { emoji: '♥️', label: 'Hearts' }, 71 | { emoji: '♠️', label: 'Spades' }, 72 | { emoji: '♦️', label: 'Diamonds' }, 73 | { emoji: '♣️', label: 'Clubs' }, 74 | ]; 75 | 76 | export const Typeahead = () => ( 77 | <> 78 |

Testing ground for typeahead behaviour

79 | 80 |
81 |
82 |

Text labels

83 |
84 |

85 | For comparison 86 |
87 | try the closed select below 88 |

89 | 100 |
101 | 102 |
103 | 104 |
105 |

Complex children

106 |

(relying on `.textContent` — default)

107 | 108 | {suits.map((suit) => ( 109 | 110 | {suit.label} 111 | 112 | {suit.emoji} 113 | 114 | 115 | ))} 116 | 117 |
118 | 119 |
120 |

Complex children

121 |

(with explicit `textValue` prop)

122 | 123 | {suits.map((suit) => ( 124 | 125 | 126 | {suit.emoji} 127 | 128 | {suit.label} 129 | 130 | ))} 131 | 132 |
133 |
134 | 135 | ); 136 | 137 | export const CheckboxItems = () => { 138 | const checkboxItems = [ 139 | { label: 'Bold', state: React.useState(false) }, 140 | { label: 'Italic', state: React.useState(true) }, 141 | { label: 'Underline', state: React.useState(false) }, 142 | { label: 'Strikethrough', state: React.useState(false), disabled: true }, 143 | ]; 144 | 145 | return ( 146 | 147 | window.alert('show')}> 148 | Show fonts 149 | 150 | window.alert('bigger')}> 151 | Bigger 152 | 153 | window.alert('smaller')}> 154 | Smaller 155 | 156 | 157 | {checkboxItems.map(({ label, state: [checked, setChecked], disabled }) => ( 158 | 165 | {label} 166 | 167 | 168 | 169 | 170 | ))} 171 | 172 | ); 173 | }; 174 | 175 | export const RadioItems = () => { 176 | const files = ['README.md', 'index.js', 'page.css']; 177 | const [file, setFile] = React.useState(files[1]); 178 | 179 | return ( 180 | 181 | window.alert('minimize')}> 182 | Minimize window 183 | 184 | window.alert('zoom')}> 185 | Zoom 186 | 187 | window.alert('smaller')}> 188 | Smaller 189 | 190 | 191 | 192 | {files.map((file) => ( 193 | 194 | {file} 195 | 196 | 197 | 198 | 199 | ))} 200 | 201 | 202 | ); 203 | }; 204 | 205 | export const Animated = () => { 206 | const files = ['README.md', 'index.js', 'page.css']; 207 | const [file, setFile] = React.useState(files[1]); 208 | const [open, setOpen] = React.useState(true); 209 | const checkboxItems = [ 210 | { label: 'Bold', state: React.useState(false) }, 211 | { label: 'Italic', state: React.useState(true) }, 212 | { label: 'Underline', state: React.useState(false) }, 213 | { label: 'Strikethrough', state: React.useState(false), disabled: true }, 214 | ]; 215 | 216 | return ( 217 | <> 218 | 222 |
223 |
224 | 225 | {checkboxItems.map(({ label, state: [checked, setChecked], disabled }) => ( 226 | 233 | {label} 234 | 235 | 236 | 237 | 238 | ))} 239 | 240 | {files.map((file) => ( 241 | 242 | {file} 243 | 244 | 245 | 246 | 247 | ))} 248 | 249 | 250 | 251 | ); 252 | }; 253 | 254 | type MenuOwnProps = Omit< 255 | React.ComponentProps & React.ComponentProps, 256 | | 'onOpenChange' 257 | | 'portalled' 258 | | 'trapFocus' 259 | | 'onOpenAutoFocus' 260 | | 'onCloseAutoFocus' 261 | | 'disableOutsidePointerEvents' 262 | | 'disableOutsideScroll' 263 | >; 264 | 265 | const Menu: React.FC = (props) => { 266 | const { open = true, children, ...contentProps } = props; 267 | return ( 268 | {}}> 269 | 270 | event.preventDefault()} 275 | onCloseAutoFocus={(event) => event.preventDefault()} 276 | disableOutsidePointerEvents={false} 277 | disableOutsideScroll={false} 278 | align="start" 279 | {...contentProps} 280 | > 281 | {children} 282 | 283 | 284 | ); 285 | }; 286 | 287 | const contentClass = css({ 288 | display: 'inline-block', 289 | boxSizing: 'border-box', 290 | minWidth: 130, 291 | backgroundColor: '$white', 292 | border: '1px solid $gray100', 293 | borderRadius: 6, 294 | padding: 5, 295 | boxShadow: '0 5px 10px 0 rgba(0, 0, 0, 0.1)', 296 | fontFamily: 'apple-system, BlinkMacSystemFont, helvetica, arial, sans-serif', 297 | fontSize: 13, 298 | '&:focus-within': { 299 | borderColor: '$black', 300 | }, 301 | }); 302 | 303 | const itemStyles: any = { 304 | display: 'flex', 305 | alignItems: 'center', 306 | justifyContent: 'space-between', 307 | lineHeight: '1', 308 | cursor: 'default', 309 | userSelect: 'none', 310 | whiteSpace: 'nowrap', 311 | height: 25, 312 | padding: '0 10px', 313 | color: '$black', 314 | borderRadius: 3, 315 | }; 316 | 317 | const labelClass = css({ 318 | ...itemStyles, 319 | color: '$gray100', 320 | }); 321 | 322 | const itemClass = css({ 323 | ...itemStyles, 324 | 325 | '&:focus': { 326 | outline: 'none', 327 | backgroundColor: '$black', 328 | color: 'white', 329 | }, 330 | 331 | '&[data-disabled]': { 332 | color: '$gray100', 333 | }, 334 | }); 335 | 336 | const separatorClass = css({ 337 | height: 1, 338 | margin: '5px 10px', 339 | backgroundColor: '$gray100', 340 | }); 341 | 342 | const fadeIn = css.keyframes({ 343 | from: { opacity: 0 }, 344 | to: { opacity: 1 }, 345 | }); 346 | 347 | const fadeOut = css.keyframes({ 348 | from: { opacity: 1 }, 349 | to: { opacity: 0 }, 350 | }); 351 | 352 | const animatedRootClass = css(contentClass, { 353 | '&[data-state="open"]': { 354 | animation: `${fadeIn} 300ms ease-out`, 355 | }, 356 | '&[data-state="closed"]': { 357 | animation: `${fadeOut} 300ms ease-in`, 358 | }, 359 | }); 360 | 361 | const animatedItemIndicatorClass = css({ 362 | '&[data-state="checked"]': { 363 | animation: `${fadeIn} 300ms ease-out`, 364 | }, 365 | '&[data-state="unchecked"]': { 366 | animation: `${fadeOut} 300ms ease-in`, 367 | }, 368 | }); 369 | 370 | export const TickIcon = () => ( 371 | 382 | 383 | 384 | ); 385 | 386 | export const classes = { 387 | contentClass, 388 | labelClass, 389 | itemClass, 390 | separatorClass, 391 | }; 392 | -------------------------------------------------------------------------------- /packages/radix/menu/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Menu'; 2 | -------------------------------------------------------------------------------- /packages/radix/menu/src/useMenuTypeahead.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function useMenuTypeahead() { 4 | const timerRef = React.useRef(0); 5 | const searchRef = React.useRef(''); 6 | 7 | // Reset `searchRef` 1 second after it was last updated 8 | const setSearch = React.useCallback((search: string) => { 9 | searchRef.current = search; 10 | window.clearTimeout(timerRef.current); 11 | timerRef.current = window.setTimeout(() => setSearch(''), 1000); 12 | }, []); 13 | 14 | return { 15 | onKeyDownCapture: (event: React.KeyboardEvent) => { 16 | if (event.key.length === 1 && !(event.ctrlKey || event.altKey || event.metaKey)) { 17 | const container = event.currentTarget as HTMLElement; 18 | setSearch(searchRef.current + event.key); 19 | 20 | // Stop activating the item if we're still "searching", essentially preventing 21 | // the spacebar from selecting the item currently focused. 22 | // This is also why we use `onKeyDownCapture` rather than `onKeyDown` 23 | if (event.key === ' ' && !searchRef.current.startsWith(' ')) { 24 | event.stopPropagation(); 25 | } 26 | 27 | const currentItem = document.activeElement; 28 | const currentMatch = currentItem ? getValue(currentItem) : undefined; 29 | const values = Array.from(container.querySelectorAll(`[${ITEM_ATTR}]`)).map(getValue); 30 | const nextMatch = getNextMatch(values, searchRef.current, currentMatch); 31 | const newItem = container.querySelector(`[${ITEM_ATTR}="${nextMatch}"]`); 32 | 33 | if (newItem) { 34 | /** 35 | * Imperative focus during keydown is risky so we prevent React's batching updates 36 | * to avoid potential bugs. See: https://github.com/facebook/react/issues/20332 37 | */ 38 | setTimeout(() => (newItem as HTMLElement).focus()); 39 | } 40 | } 41 | }, 42 | }; 43 | } 44 | 45 | /** 46 | * This is the "meat" of the matching logic. It takes in all the values, 47 | * the search and the current match, and returns the next match (or `undefined`). 48 | * 49 | * We normalize the search because if a user has repeatedly pressed a character, 50 | * we want the exact same behavior as if we only had that one character 51 | * (ie. cycle through options starting with that character) 52 | * 53 | * We also reorder the values by wrapping the array around the current match. 54 | * This is so we always look forward from the current match, and picking the first 55 | * match will always be the correct one. 56 | * 57 | * Finally, if the normalized search is exactly one character, we exclude the 58 | * current match from the values because otherwise it would be the first to match always 59 | * and focus would never move. This is as opposed to the regular case, where we 60 | * don't want focus to move if the current match still matches. 61 | */ 62 | function getNextMatch(values: string[], search: string, currentMatch?: string) { 63 | const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]); 64 | const normalizedSearch = isRepeated ? search[0] : search; 65 | const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1; 66 | let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0)); 67 | const excludeCurrentMatch = normalizedSearch.length === 1; 68 | if (excludeCurrentMatch) wrappedValues = wrappedValues.filter((v) => v !== currentMatch); 69 | const nextMatch = wrappedValues.find((value) => 70 | value.toLowerCase().startsWith(normalizedSearch.toLowerCase()) 71 | ); 72 | return nextMatch !== currentMatch ? nextMatch : undefined; 73 | } 74 | 75 | const getValue = (element: Element) => element.getAttribute(ITEM_ATTR) ?? ''; 76 | 77 | const ITEM_ATTR = 'data-radix-menu-typeahead-item'; 78 | 79 | type UseMenuTypeaheadItemOptions = { textValue: string; disabled?: boolean }; 80 | 81 | function useMenuTypeaheadItem({ textValue, disabled }: UseMenuTypeaheadItemOptions) { 82 | return { [ITEM_ATTR]: disabled ? undefined : textValue }; 83 | } 84 | 85 | /** 86 | * Wraps an array around itself at a given start index 87 | * Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']` 88 | */ 89 | function wrapArray(array: T[], startIndex: number) { 90 | return array.map((_, index) => array[(startIndex + index) % array.length]); 91 | } 92 | 93 | export { useMenuTypeahead, useMenuTypeaheadItem }; 94 | -------------------------------------------------------------------------------- /patches/@expo+next-adapter+2.1.77.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@expo/next-adapter/build/babel.js b/node_modules/@expo/next-adapter/build/babel.js 2 | index ab1b808..36dbe97 100644 3 | --- a/node_modules/@expo/next-adapter/build/babel.js 4 | +++ b/node_modules/@expo/next-adapter/build/babel.js 5 | @@ -13,12 +13,16 @@ module.exports = function (api) { 6 | if (missingPackages.length) 7 | throw new Error(`[BABEL]: preset \`@expo/next-adapter/babel\` is missing peer dependencies: ${missingPackages.join(', ')}`); 8 | } 9 | + 10 | return { 11 | presets: [ 12 | require('babel-preset-expo'), 13 | // Only use next in the browser, it'll break your native project/ 14 | isWeb && require('next/babel'), 15 | ].filter(Boolean), 16 | + plugins: [ 17 | + isWeb && "@babel/plugin-proposal-class-properties", 18 | + ].filter(Boolean), 19 | }; 20 | }; 21 | function hasModule(name) { 22 | diff --git a/node_modules/@expo/next-adapter/build/withExpo.js b/node_modules/@expo/next-adapter/build/withExpo.js 23 | index 2e63eb4..fbea871 100644 24 | --- a/node_modules/@expo/next-adapter/build/withExpo.js 25 | +++ b/node_modules/@expo/next-adapter/build/withExpo.js 26 | @@ -7,8 +7,8 @@ function withExpo(nextConfig = {}) { 27 | // Prevent define plugin from overwriting Next.js environment. 28 | process.env.EXPO_WEBPACK_DEFINE_ENVIRONMENT_AS_KEYS = 'true'; 29 | const expoConfig = addons_1.withUnimodules(config, { 30 | - projectRoot: nextConfig.projectRoot || process.cwd(), 31 | - }, { supportsFontLoading: false }); 32 | + projectRoot: nextConfig.projectRoot || process.cwd() 33 | + }, { supportsFontLoading: false, webpack5: (options.config.future || {}).webpack5 }); 34 | // Use original public path 35 | (expoConfig.output || {}).publicPath = (config.output || {}).publicPath; 36 | // TODO: Bacon: use commonjs for RNW babel maybe... 37 | -------------------------------------------------------------------------------- /patches/@expo+webpack-config+0.12.76.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@expo/webpack-config/webpack/addons/withOptimizations.js b/node_modules/@expo/webpack-config/webpack/addons/withOptimizations.js 2 | index 8411aeb..6e91898 100644 3 | --- a/node_modules/@expo/webpack-config/webpack/addons/withOptimizations.js 4 | +++ b/node_modules/@expo/webpack-config/webpack/addons/withOptimizations.js 5 | @@ -7,6 +7,7 @@ exports.isDebugMode = void 0; 6 | const getenv_1 = require("getenv"); 7 | const is_wsl_1 = __importDefault(require("is-wsl")); 8 | const optimize_css_assets_webpack_plugin_1 = __importDefault(require("optimize-css-assets-webpack-plugin")); 9 | +const css_minimizer_webpack_plugin_1 = __importDefault(require("css-minimizer-webpack-plugin")); 10 | const postcss_safe_parser_1 = __importDefault(require("postcss-safe-parser")); 11 | const terser_webpack_plugin_1 = __importDefault(require("terser-webpack-plugin")); 12 | /** 13 | @@ -81,21 +82,7 @@ function withOptimizations(webpackConfig) { 14 | sourceMap: shouldUseSourceMap, 15 | }), 16 | // This is only used in production mode 17 | - new optimize_css_assets_webpack_plugin_1.default({ 18 | - cssProcessorOptions: { 19 | - parser: postcss_safe_parser_1.default, 20 | - map: shouldUseSourceMap 21 | - ? { 22 | - // `inline: false` forces the sourcemap to be output into a 23 | - // separate file 24 | - inline: false, 25 | - // `annotation: true` appends the sourceMappingURL to the end of 26 | - // the css file, helping the browser find the sourcemap 27 | - annotation: true, 28 | - } 29 | - : false, 30 | - }, 31 | - }), 32 | + new css_minimizer_webpack_plugin_1.default() 33 | ], 34 | // Automatically split vendor and commons 35 | // https://twitter.com/wSokra/status/969633336732905474 36 | diff --git a/node_modules/@expo/webpack-config/webpack/addons/withUnimodules.js b/node_modules/@expo/webpack-config/webpack/addons/withUnimodules.js 37 | index e293a98..5411c51 100644 38 | --- a/node_modules/@expo/webpack-config/webpack/addons/withUnimodules.js 39 | +++ b/node_modules/@expo/webpack-config/webpack/addons/withUnimodules.js 40 | @@ -39,6 +39,7 @@ function withUnimodules(webpackConfig = {}, env = {}, argv = {}) { 41 | webpackConfig.output = {}; 42 | // Attempt to use the input webpack config mode 43 | env.mode = env.mode || webpackConfig.mode; 44 | + const isWebpack5 = argv.webpack5 45 | const environment = env_1.validateEnvironment(env); 46 | let { supportsFontLoading } = argv; 47 | // If the args don't specify this then we'll check if the input already supports font loading. 48 | @@ -117,7 +118,7 @@ function withUnimodules(webpackConfig = {}, env = {}, argv = {}) { 49 | }; 50 | // Transpile and remove expo modules from Next.js externals. 51 | const includeFunc = babelLoader.include; 52 | - webpackConfig = ignoreExternalModules(webpackConfig, includeFunc); 53 | + webpackConfig = ignoreExternalModules(webpackConfig, includeFunc, isWebpack5); 54 | // Add a loose requirement on the ResizeObserver polyfill if it's installed... 55 | webpackConfig = withEntry_1.default(webpackConfig, env, { 56 | entryPath: 'resize-observer-polyfill/dist/ResizeObserver.global', 57 | @@ -137,7 +138,7 @@ exports.default = withUnimodules; 58 | * @param webpackConfig Config to be modified 59 | * @param shouldIncludeModule A method that returns a boolean when the input module should be transpiled and externed. 60 | */ 61 | -function ignoreExternalModules(webpackConfig, shouldIncludeModule) { 62 | +function ignoreExternalModules(webpackConfig, shouldIncludeModule, isWebpack5) { 63 | if (!webpackConfig.externals) { 64 | return webpackConfig; 65 | } 66 | @@ -148,6 +149,13 @@ function ignoreExternalModules(webpackConfig, shouldIncludeModule) { 67 | if (typeof external !== 'function') { 68 | return external; 69 | } 70 | + 71 | + if (isWebpack5) { 72 | + return ((ctx) => { 73 | + const relPath = path_1.default.join('node_modules', ctx.request); 74 | + return shouldIncludeModule(relPath) ? undefined : external(ctx); 75 | + }) 76 | + } 77 | return ((ctx, req, cb) => { 78 | const relPath = path_1.default.join('node_modules', req); 79 | return shouldIncludeModule(relPath) ? cb() : external(ctx, req, cb); 80 | -------------------------------------------------------------------------------- /patches/@gorhom+bottom-sheet+4.0.0-alpha.3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.android.js.map b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.android.js.map 2 | deleted file mode 100644 3 | index 1a8f11a..0000000 4 | --- a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.android.js.map 5 | +++ /dev/null 6 | @@ -1 +0,0 @@ 7 | -{"version":3,"sources":["Touchables.android.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA","sourcesContent":["export {\n TouchableOpacity,\n TouchableHighlight,\n TouchableWithoutFeedback,\n} from 'react-native-gesture-handler';\n"]} 8 | \ No newline at end of file 9 | diff --git a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.android.js b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.js 10 | similarity index 100% 11 | rename from node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.android.js 12 | rename to node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.js 13 | diff --git a/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.js.map b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.js.map 14 | new file mode 100644 15 | index 0000000..a16bc78 16 | --- /dev/null 17 | +++ b/node_modules/@gorhom/bottom-sheet/lib/commonjs/components/touchables/Touchables.js.map 18 | @@ -0,0 +1 @@ 19 | +{"version":3,"sources":["Touchables.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA","sourcesContent":["export {\n TouchableOpacity,\n TouchableHighlight,\n TouchableWithoutFeedback,\n} from 'react-native-gesture-handler';\n"]} 20 | \ No newline at end of file 21 | diff --git a/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.android.js.map b/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.android.js.map 22 | deleted file mode 100644 23 | index 3098e7d..0000000 24 | --- a/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.android.js.map 25 | +++ /dev/null 26 | @@ -1 +0,0 @@ 27 | -{"version":3,"sources":["Touchables.android.tsx"],"names":["TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback"],"mappings":"AAAA,SACEA,gBADF,EAEEC,kBAFF,EAGEC,wBAHF,QAIO,8BAJP","sourcesContent":["export {\n TouchableOpacity,\n TouchableHighlight,\n TouchableWithoutFeedback,\n} from 'react-native-gesture-handler';\n"]} 28 | \ No newline at end of file 29 | diff --git a/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.android.js b/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.js 30 | similarity index 70% 31 | rename from node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.android.js 32 | rename to node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.js 33 | index fc81621..b1edc97 100644 34 | --- a/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.android.js 35 | +++ b/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.js 36 | @@ -1,2 +1,2 @@ 37 | export { TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback } from 'react-native-gesture-handler'; 38 | -//# sourceMappingURL=Touchables.android.js.map 39 | \ No newline at end of file 40 | +//# sourceMappingURL=Touchables.js.map 41 | \ No newline at end of file 42 | diff --git a/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.js.map b/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.js.map 43 | new file mode 100644 44 | index 0000000..ac59a11 45 | --- /dev/null 46 | +++ b/node_modules/@gorhom/bottom-sheet/lib/module/components/touchables/Touchables.js.map 47 | @@ -0,0 +1 @@ 48 | +{"version":3,"sources":["Touchables.tsx"],"names":["TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback"],"mappings":"AAAA,SACEA,gBADF,EAEEC,kBAFF,EAGEC,wBAHF,QAIO,8BAJP","sourcesContent":["export {\n TouchableOpacity,\n TouchableHighlight,\n TouchableWithoutFeedback,\n} from 'react-native-gesture-handler';\n"]} 49 | \ No newline at end of file 50 | diff --git a/node_modules/@gorhom/bottom-sheet/src/components/touchables/Touchables.android.tsx b/node_modules/@gorhom/bottom-sheet/src/components/touchables/Touchables.tsx 51 | similarity index 100% 52 | rename from node_modules/@gorhom/bottom-sheet/src/components/touchables/Touchables.android.tsx 53 | rename to node_modules/@gorhom/bottom-sheet/src/components/touchables/Touchables.tsx 54 | -------------------------------------------------------------------------------- /patches/babel-preset-expo+8.3.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/babel-preset-expo/index.js b/node_modules/babel-preset-expo/index.js 2 | index 1022490..08c8182 100644 3 | --- a/node_modules/babel-preset-expo/index.js 4 | +++ b/node_modules/babel-preset-expo/index.js 5 | @@ -31,6 +31,8 @@ module.exports = function(api, options = {}) { 6 | // Reference: https://github.com/expo/expo/pull/4685#discussion_r307143920 7 | require('metro-react-native-babel-preset'), 8 | { 9 | + dev: true, 10 | + useTransformReactJsxExperimental: true, 11 | disableImportExportTransform: platformOptions.disableImportExportTransform, 12 | lazyImportExportTransform: 13 | lazyImportsOption === true 14 | --------------------------------------------------------------------------------