[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-93272":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":16,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":19,"hasPages":19,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":15,"starSnapshotCount":15,"syncStatus":25,"lastSyncTime":26,"discoverSource":27},93272,"expo-morphing-menu","rit3zh\u002Fexpo-morphing-menu","rit3zh","🗂️ A morphing, keyboard-aware composer + picker menu.","",null,"TypeScript",111,10,102,0,9,48.52,"MIT License",false,"main",[],"2026-07-22 04:02:08","# expo-morphing-menu\n\nA morphing, keyboard-aware **composer + picker menu** for React Native.\n\n## ✨ Features\n\n- 🫧 **Morphing presentation** — a single spring machine drives the composer's width, height, corner radius, and position together, so the input bar _grows into_ the menu surface instead of a separate sheet sliding up\n- ⌨️ **Keyboard-following** via `react-native-keyboard-controller` — the surface reads live keyboard height and stays glued above it as it opens and closes\n- 🧭 **Multi-panel navigation** — register `\u003CBottomInput.Item>` rows (Camera, Photos, Files, Plugins…) and matching `\u003CBottomInput.Content>` panels; `open(id)` morphs the surface to that panel and `back()` returns to the item list\n- 🖼️ **Single-image morph** — tapping a photo runs a shared-element morph from its grid cell into an attachment thumb, coordinated through the root via `reportFrame` \u002F `run`\n- 🧠 TypeScript-first, fully typed surface\n\n---\n\n## ⚙️ Installation\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Frit3zh\u002Fexpo-morphing-menu\ncd expo-morphing-menu\nbun install\nbun start -c\n```\n\nCore dependencies (already wired up in this template):\n\n```bash\nbun add react-native-reanimated react-native-gesture-handler \\\n  react-native-safe-area-context react-native-keyboard-controller \\\n  expo-symbols expo-glass-effect expo-image expo-document-picker\n```\n\n> **Expo SDK 57.** Read the exact versioned docs at\n> https:\u002F\u002Fdocs.expo.dev\u002Fversions\u002Fv57.0.0\u002F before changing native code.\n\n---\n\n## 🚀 Usage\n\nWrap your app once with `KeyboardProvider`, then compose a `\u003CBottomInput>` at the\nbottom of your screen.\n\n```tsx\n\u002F\u002F app\u002F_layout.tsx\nimport { KeyboardProvider } from \"react-native-keyboard-controller\";\nimport { Stack } from \"expo-router\";\n\nexport default function RootLayout() {\n  return (\n    \u003CKeyboardProvider enabled>\n      \u003CStack screenOptions={{ headerShown: false }} \u002F>\n    \u003C\u002FKeyboardProvider>\n  );\n}\n```\n\nThe simplest composer — a placeholder, one trigger, one menu item:\n\n```tsx\nimport { BottomInput } from \"@\u002Fcomponents\u002Fbottom-input\";\nimport { SymbolView } from \"expo-symbols\";\n\nexport function Composer() {\n  return (\n    \u003CBottomInput placeholder=\"Ask ChatGPT\">\n      \u003CBottomInput.Trigger>\n        \u003CSymbolView name=\"plus\" size={22} tintColor=\"#ececec\" \u002F>\n      \u003C\u002FBottomInput.Trigger>\n\n      \u003CBottomInput.Items>\n        \u003CBottomInput.Item\n          id=\"photos\"\n          label=\"Photos\"\n          icon={\u003CSymbolView name=\"photo\" size={22} tintColor=\"#ececec\" \u002F>}\n        \u002F>\n      \u003C\u002FBottomInput.Items>\n\n      \u003CBottomInput.Content id=\"photos\">\n        {\u002F* your panel here *\u002F}\n      \u003C\u002FBottomInput.Content>\n    \u003C\u002FBottomInput>\n  );\n}\n```\n\n## Preview\n\nhttps:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F15abbea3-644f-4680-8795-4d16182a70ec\n\n\n\n### Controlling open state\n\nPass `open` \u002F `onOpenChange` to drive the menu from outside (e.g. to prefetch the\nphoto library the moment it opens):\n\n```tsx\nconst [menuOpen, setMenuOpen] = useState(false);\n\n\u003CBottomInput\n  placeholder=\"Ask ChatGPT\"\n  open={menuOpen}\n  onOpenChange={setMenuOpen}\n  trailing={\u003CComposerTrailing \u002F>}\n>\n  {\u002F* … *\u002F}\n\u003C\u002FBottomInput>;\n```\n\n### Attachments\n\nRender selected photos and files above the field. The composer measures the strip\nand reflows to fit:\n\n```tsx\n\u003CBottomInput.Attachments>\n  {attachments.map((photo) => (\n    \u003CAttachmentThumb key={photo.id} photo={photo} onRemove={removeAttachment} \u002F>\n  ))}\n  {files.map((file) => (\n    \u003CFileAttachment key={file.id} file={file} onRemove={removeFile} \u002F>\n  ))}\n\u003C\u002FBottomInput.Attachments>\n```\n\n### Multiple panels\n\nRegister several items and a matching `Content` panel per id. Tapping an item\nmorphs the surface to that panel:\n\n```tsx\n\u003CBottomInput.Items>\n  \u003CBottomInput.Item id=\"camera\" label=\"Camera\" icon={\u002F* … *\u002F} \u002F>\n  \u003CBottomInput.Item id=\"photos\" label=\"Photos\" icon={\u002F* … *\u002F} \u002F>\n  \u003CBottomInput.Item id=\"files\" label=\"Files\" icon={\u002F* … *\u002F} \u002F>\n\u003C\u002FBottomInput.Items>\n\n\u003CBottomInput.Content id=\"camera\">\u003CCameraSurface \u002F>\u003C\u002FBottomInput.Content>\n\u003CBottomInput.Content id=\"photos\">\u003CPhotoGrid \u002F>\u003C\u002FBottomInput.Content>\n\u003CBottomInput.Content id=\"files\" height={800}>\u003CFilesPanel \u002F>\u003C\u002FBottomInput.Content>\n```\n\n> ⚠️ `Content.height` — a fixed height only works reliably when the keyboard is\n> **not** mounted. The panel is bottom-anchored just above the keyboard, so a\n> height taller than the space above it overflows off-screen. Omit `height` to use\n> the keyboard-safe default.\n\n---\n\n## 🧱 Component Anatomy\n\n```tsx\n\u003CBottomInput>\n  \u003CBottomInput.Attachments \u002F>\n  \u003CBottomInput.Trigger \u002F>\n  \u003CBottomInput.Items>\n    \u003CBottomInput.Item \u002F>\n  \u003C\u002FBottomInput.Items>\n  \u003CBottomInput.Content \u002F>\n\u003C\u002FBottomInput>\n```\n\n---\n\n## 🧩 API\n\n### `\u003CBottomInput>` (root)\n\n| Prop           | Type                      | Default | Description                                                |\n| -------------- | ------------------------- | ------- | ---------------------------------------------------------- |\n| `placeholder`  | `string`                  | —       | Placeholder for the composer text field.                   |\n| `open`         | `boolean`                 | —       | Controlled menu-open state.                                |\n| `onOpenChange` | `(open: boolean) => void` | —       | Fires when the menu opens or closes.                       |\n| `inputProps`   | `TextInputProps`          | —       | Props forwarded to the underlying `TextInput`.             |\n| `trailing`     | `ReactNode`               | —       | Content pinned to the trailing edge of the field.          |\n| `children`     | `ReactNode`               | —       | The `Attachments` \u002F `Trigger` \u002F `Items` \u002F `Content` slots. |\n\n### `\u003CBottomInput.Trigger>`\n\n| Prop       | Type        | Description                               |\n| ---------- | ----------- | ----------------------------------------- |\n| `children` | `ReactNode` | The pressable content (e.g. a ＋ symbol). |\n\n### `\u003CBottomInput.Items>` \u002F `\u003CBottomInput.Item>`\n\n`Items` wraps the menu rows. Each `Item`:\n\n| Prop    | Type        | Description                                      |\n| ------- | ----------- | ------------------------------------------------ |\n| `id`    | `string`    | Unique id linking this row to a `Content` panel. |\n| `label` | `string`    | Row label.                                       |\n| `icon`  | `ReactNode` | Leading icon.                                    |\n\n### `\u003CBottomInput.Content>`\n\n| Prop       | Type        | Description                                                         |\n| ---------- | ----------- | ------------------------------------------------------------------- |\n| `id`       | `string`    | Matches an `Item` id; the surface morphs to this panel when opened. |\n| `height`   | `number`    | Optional fixed panel height. See the keyboard caveat above.         |\n| `children` | `ReactNode` | The panel body.                                                     |\n\n### `\u003CBottomInput.Attachments>`\n\n| Prop       | Type        | Description                                 |\n| ---------- | ----------- | ------------------------------------------- |\n| `children` | `ReactNode` | Attachment thumbs rendered above the field. |\n\n### `useBottomInput()`\n\nAccess menu state and the morph controller from any descendant of `\u003CBottomInput>`.\n\n| Field               | Type                   | Description                                                                       |\n| ------------------- | ---------------------- | --------------------------------------------------------------------------------- |\n| `menuOpen`          | `boolean`              | Whether the menu is open.                                                         |\n| `toggleMenu()`      | `() => void`           | Toggle the menu.                                                                  |\n| `close()`           | `() => void`           | Close the menu.                                                                   |\n| `activeId`          | `string \\| null`       | The currently open panel id.                                                      |\n| `open(id)`          | `(id: string) => void` | Morph the surface to a panel.                                                     |\n| `back()`            | `() => void`           | Return from a panel to the item list.                                             |\n| `contentWidth`      | `number`               | Measured width of the extended surface.                                           |\n| `contentHeight`     | `number`               | Measured height of the extended surface.                                          |\n| `attachmentsHeight` | `SharedValue\u003Cnumber>`  | Live height of the attachment strip.                                              |\n| `morph`             | `IBottomInputMorph`    | Single-image morph controller (`reportFrame` \u002F `run`, `pendingId`, `revealedId`). |\n\n---\n\n## 🧱 Stack\n\n[Expo SDK 57](https:\u002F\u002Fexpo.dev\u002Fchangelog) · [React Native 0.86](https:\u002F\u002Freactnative.dev\u002F) · [Reanimated 4](https:\u002F\u002Fdocs.swmansion.com\u002Freact-native-reanimated\u002F) · [Gesture Handler 2](https:\u002F\u002Fdocs.swmansion.com\u002Freact-native-gesture-handler\u002F) · [Keyboard Controller](https:\u002F\u002Fkirillzyusko.github.io\u002Freact-native-keyboard-controller\u002F) · [expo-symbols](https:\u002F\u002Fdocs.expo.dev\u002Fversions\u002Flatest\u002Fsdk\u002Fsymbols\u002F) · [expo-glass-effect](https:\u002F\u002Fdocs.expo.dev\u002Fversions\u002Flatest\u002Fsdk\u002Fglass-effect\u002F) · [expo-image](https:\u002F\u002Fdocs.expo.dev\u002Fversions\u002Flatest\u002Fsdk\u002Fimage\u002F) · [Safe Area Context](https:\u002F\u002Fgithub.com\u002Fth3rdwave\u002Freact-native-safe-area-context) · [Expo Router](https:\u002F\u002Fdocs.expo.dev\u002Frouter\u002Fintroduction\u002F)\n\n---\n","这是一个为 React Native 应用设计的动态变形式底部输入菜单组件，支持键盘感知、多面板导航与共享元素过渡动画。核心特性包括：基于弹簧动画的统一形变（输入框平滑扩展为菜单面板）、实时响应键盘高度并自动贴附、通过 ID 驱动的多级内容切换（如照片\u002F文件\u002F插件面板），以及单图网格到附件缩略图的共享元素 morph 动画。适用于需要沉浸式、高交互性底部操作入口的移动应用，如聊天工具、AI 助手、富媒体编辑器等场景。",2,"2026-07-15 02:30:06","CREATED_QUERY"]