{"_id":"@beatsphere/expo-klipy","name":"@beatsphere/expo-klipy","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@beatsphere/expo-klipy","version":"0.1.0","description":"Klipy GIF API client + drop-in <KlipyPicker /> for React Native and Expo. Tenor migration in 30 seconds.","main":"build/index.js","types":"build/index.d.ts","scripts":{"build":"tsc","prepublishOnly":"tsc","clean":"rm -rf build"},"keywords":["klipy","gif","gifs","tenor","tenor-replacement","giphy","react-native","expo","stickers","clips","memes","zod","typescript","picker"],"author":{"name":"BeatSphere","email":"hello@beatsphere.live"},"license":"MIT","homepage":"https://github.com/Beatsphere/expo-klipy#readme","repository":{"type":"git","url":"git+https://github.com/Beatsphere/expo-klipy.git"},"bugs":{"url":"https://github.com/Beatsphere/expo-klipy/issues"},"dependencies":{"axios":"^1.6.0","zod":"^3.22.0"},"peerDependencies":{"@expo/vector-icons":">=14.0.0","expo-image":">=2.0.0","react":">=18.0.0","react-native":">=0.73.0","react-native-safe-area-context":">=4.0.0"},"devDependencies":{"@expo/vector-icons":"^14.0.0","@types/react":"^18.2.0","expo-image":"^2.0.0","react":"^18.2.0","react-native":"^0.76.0","react-native-safe-area-context":"^4.0.0","typescript":"^5.0.0"},"gitHead":"7d566584192d0f72501dd11e007d8535385628e1","_id":"@beatsphere/expo-klipy@0.1.0","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-Kr9abQyy/mhEDzAi/yg5cK4cTFJrCq/D/R/acMLGh2xLRN3b1Io2L+yz2dwXT4JfLzN19TrgF637FzNKLjSw3g==","shasum":"b41c85cae9559ff5fcdbcfe4f2b348e8808b9542","tarball":"https://registry.npmjs.org/@beatsphere/expo-klipy/-/expo-klipy-0.1.0.tgz","fileCount":33,"unpackedSize":186600,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC6offrRcN2OQKMcqQIOLdrto04AKyaqK1qDJUx5eiD3wIgbDErIfF5NRwVYHRj/1EYe3ICNfBtegDl1QaMUxs+uZY="}]},"_npmUser":{"name":"ayushh2k","email":"rustichouse042@gmail.com"},"directories":{},"maintainers":[{"name":"ayushh2k","email":"rustichouse042@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/expo-klipy_0.1.0_1777530673907_0.7901742734935175"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-30T06:31:13.795Z","0.1.0":"2026-04-30T06:31:14.047Z","modified":"2026-04-30T06:31:14.463Z"},"maintainers":[{"name":"ayushh2k","email":"rustichouse042@gmail.com"}],"description":"Klipy GIF API client + drop-in <KlipyPicker /> for React Native and Expo. Tenor migration in 30 seconds.","homepage":"https://github.com/Beatsphere/expo-klipy#readme","keywords":["klipy","gif","gifs","tenor","tenor-replacement","giphy","react-native","expo","stickers","clips","memes","zod","typescript","picker"],"repository":{"type":"git","url":"git+https://github.com/Beatsphere/expo-klipy.git"},"author":{"name":"BeatSphere","email":"hello@beatsphere.live"},"bugs":{"url":"https://github.com/Beatsphere/expo-klipy/issues"},"license":"MIT","readme":"# @beatsphere/expo-klipy\n\nKlipy GIF API client + drop-in `<KlipyPicker />` for React Native and Expo. Designed as a 30-second migration path off Tenor (which Google is shutting down on June 30, 2026).\n\n- Typed REST client with Zod runtime validation\n- Themable picker component — search, trending, attribution, safe-area handling\n- Per-platform API key resolution (Klipy issues separate keys for iOS / Android / Web)\n- Works with `gifs`, `stickers`, `clips`, and `memes` endpoints\n\nBattle-tested in [BeatSphere](https://beatsphere.live).\n\n## Install\n\n```bash\nnpm install @beatsphere/expo-klipy\n```\n\nPeer dependencies (already present in any modern Expo app):\n\n- `react`, `react-native`\n- `expo-image`\n- `react-native-safe-area-context`\n- `@expo/vector-icons`\n\n## Get API keys\n\nRegister at [partner.klipy.com](https://partner.klipy.com) and click **Add Platform**. Klipy issues a separate key per platform — generate one for iOS and one for Android (and one for Web if applicable).\n\nTest keys allow up to 100 requests/minute for development. Request production access from the same panel when you're ready to ship.\n\n## Quick Start — `<KlipyPicker />`\n\n```tsx\nimport React, { useState } from 'react';\nimport { Button, View } from 'react-native';\nimport { KlipyPicker, resolvePlatformApiKey, type KlipyItem } from '@beatsphere/expo-klipy';\n\nconst apiKey = resolvePlatformApiKey({\n  ios: process.env.EXPO_PUBLIC_KLIPY_API_KEY_IOS,\n  android: process.env.EXPO_PUBLIC_KLIPY_API_KEY_ANDROID,\n});\n\nexport function ChatInput() {\n  const [pickerOpen, setPickerOpen] = useState(false);\n\n  const handleSelect = (item: KlipyItem) => {\n    // `md.gif.url` is a sensible default for sending in chat (~300×300).\n    // Use `hd` for higher quality, `sm` for a thumbnail, `mp4` to send video.\n    const gifUrl = item.file.md.gif.url;\n    sendChatMessage(gifUrl);\n    setPickerOpen(false);\n  };\n\n  return (\n    <View>\n      <Button title=\"GIF\" onPress={() => setPickerOpen(true)} />\n      <KlipyPicker\n        visible={pickerOpen}\n        onClose={() => setPickerOpen(false)}\n        onSelect={handleSelect}\n        apiKey={apiKey}\n      />\n    </View>\n  );\n}\n```\n\nThat's it. The picker handles search debouncing, trending fallback, loading/empty states, swipe-to-dismiss, and the required Klipy attribution.\n\n## Quick Start — REST client only\n\nIf you're building a custom UI, use the client functions directly:\n\n```ts\nimport { search, trending, type KlipyItem } from '@beatsphere/expo-klipy';\n\nconst trendingPage = await trending({ apiKey: 'your_key', perPage: 24 });\nconst searchPage = await search({ apiKey: 'your_key', q: 'cats', perPage: 24, page: 1 });\n\ntrendingPage.items.forEach((gif: KlipyItem) => {\n  console.log(gif.title, gif.file.md.gif.url);\n});\n\nif (searchPage.hasNext) {\n  const next = await search({ apiKey: 'your_key', q: 'cats', page: 2 });\n}\n```\n\n## Migrating from Tenor\n\nTenor's response shape | Klipy equivalent\n--- | ---\n`media_formats.gif.url` (full size) | `file.md.gif.url`\n`media_formats.tinygif.url` (thumbnail) | `file.sm.gif.url`\n`media_formats.mediumgif.url` | `file.hd.gif.url`\n`results[]` | `data.data[]` (or `.items` from this client)\n`tenor.googleapis.com/v2/search?q=...` | `api.klipy.com/api/v1/{KEY}/gifs/search?q=...`\n`tenor.googleapis.com/v2/featured` | `api.klipy.com/api/v1/{KEY}/gifs/trending`\nAPI key as `?key=` query param | API key in URL path\n\n## API\n\n### `<KlipyPicker />`\n\nDrop-in modal picker. Renders as a `pageSheet` modal on iOS (~85% height, swipe-to-dismiss) and full-screen on Android.\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `visible` | `boolean` | — | Show / hide |\n| `onClose` | `() => void` | — | Called on dismissal |\n| `onSelect` | `(item: KlipyItem) => void` | — | Called when the user picks an item |\n| `apiKey` | `string` | — | Klipy API key |\n| `contentType` | `'gifs' \\| 'stickers' \\| 'clips' \\| 'memes'` | `'gifs'` | Content category |\n| `locale` | `string` | — | `xx_XX` locale code, e.g. `'us_US'` |\n| `rating` | `'g' \\| 'pg' \\| 'pg-13' \\| 'r'` | — | Content rating filter |\n| `perPage` | `number` | `24` | Results per request (8–50) |\n| `searchPlaceholder` | `string` | `'Search Klipy'` | Search input placeholder |\n| `title` | `string` | `'GIFs'` | Header title |\n| `theme` | `KlipyPickerTheme` | dark | Color overrides |\n| `style` | `StyleProp<ViewStyle>` | — | Container style |\n| `titleStyle` | `StyleProp<TextStyle>` | — | Title text style |\n| `numColumns` | `number` | `2` | Grid columns |\n| `hideAttribution` | `boolean` | `false` | Hide \"Powered by KLIPY\". **Klipy's terms require attribution** — only set this if you render `<KlipyAttribution />` elsewhere. |\n\n### `<KlipyAttribution />`\n\nStandalone attribution mark. Use this if you build a custom picker UI.\n\n```tsx\n<KlipyAttribution textStyle={{ fontFamily: 'Inter-Bold' }} />\n```\n\n### Functions\n\n| Function | Description |\n|----------|-------------|\n| `search({ apiKey, q, ... })` | Keyword search |\n| `trending({ apiKey, ... })` | Trending content |\n| `reportShare({ apiKey, itemId })` | Notify Klipy that a piece of content was shared (improves their trending data — best-effort, silent on failure) |\n| `resolvePlatformApiKey({ ios, android, web, fallback })` | Return the right key for the active platform |\n\nAll client functions resolve to a `KlipyResultPage`:\n\n```ts\n{\n  items: KlipyItem[];\n  currentPage: number;\n  perPage: number;\n  hasNext: boolean;\n}\n```\n\n### `KlipyItem` shape\n\n```ts\n{\n  id: number;\n  slug: string;\n  title: string;\n  type: string;\n  file: {\n    hd: { gif, webp?, jpg?, mp4?, webm? };  // 300×300, large\n    md: { gif, webp?, jpg?, mp4?, webm? };  // 300×300, smaller file\n    sm: { gif, webp?, jpg?, mp4?, webm? };  // 220×220, thumbnail\n    xs: { gif, webp?, jpg?, mp4?, webm? };  // 90×90, tiny preview\n  };\n  tags?: string[];\n  blur_preview?: string;  // base64 placeholder\n}\n```\n\nEach format object has `url`, `width`, `height`, `size`.\n\n## Theming\n\n```tsx\n<KlipyPicker\n  // ...\n  theme={{\n    background: '#FFFFFF',\n    surface: '#F2F2F7',\n    text: '#000000',\n    mutedText: '#8E8E93',\n    divider: '#C6C6C8',\n    tilePlaceholder: '#E5E5EA',\n    attributionText: '#999999',\n  }}\n/>\n```\n\n## Attribution\n\nKlipy's terms require visible attribution wherever their content is displayed. The picker renders a \"POWERED BY KLIPY\" footer by default. If you build a custom UI, render `<KlipyAttribution />` somewhere visible.\n\n## License\n\nMIT © BeatSphere\n","readmeFilename":"README.md","_rev":"1-6279b378f220ffbf317b6e34dce92ceb"}