diff --git a/site/next-env.d.ts b/site/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/site/next-env.d.ts +++ b/site/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/site/src/components/reactions.tsx b/site/src/components/reactions.tsx index e2839f7..d006f2b 100644 --- a/site/src/components/reactions.tsx +++ b/site/src/components/reactions.tsx @@ -12,36 +12,36 @@ import { ReactionsList, } from "./reactions.client"; -const liveblocks = new LiveblocksClient({ - secret: process.env.LIVEBLOCKS_SECRET_KEY!, -}); +// const liveblocks = new LiveblocksClient({ +// secret: process.env.LIVEBLOCKS_SECRET_KEY!, +// }); -async function ServerReactions() { - "use cache"; +// async function ServerReactions() { +// "use cache"; - cacheLife("seconds"); +// cacheLife("seconds"); - let reactions: ReactionsJson; +// let reactions: ReactionsJson; - try { - reactions = (await liveblocks.getStorageDocument(ROOM_ID, "json")) - .reactions; - } catch { - reactions = DEFAULT_REACTIONS; - } +// try { +// reactions = (await liveblocks.getStorageDocument(ROOM_ID, "json")) +// .reactions; +// } catch { +// reactions = DEFAULT_REACTIONS; +// } - if (!reactions || Object.keys(reactions).length === 0) { - reactions = DEFAULT_REACTIONS; - } +// if (!reactions || Object.keys(reactions).length === 0) { +// reactions = DEFAULT_REACTIONS; +// } - return ; -} +// return ; +// } export function Reactions(props: Omit, "children">) { return ( }> - + {/* */} ); diff --git a/site/src/examples/usage/usage.client.tsx b/site/src/examples/usage/usage.client.tsx index e1273ad..a0e78ac 100644 --- a/site/src/examples/usage/usage.client.tsx +++ b/site/src/examples/usage/usage.client.tsx @@ -19,6 +19,17 @@ function EmojiPicker({ className, columns, ...props }: EmojiPickerRootProps) { {...props} > + + {({ categories }) => ( +
+ {categories.map(({ category, scrollTo }) => ( + + ))} +
+ )} +
Loading… diff --git a/site/src/examples/usage/usage.tsx b/site/src/examples/usage/usage.tsx index 216a611..475631f 100644 --- a/site/src/examples/usage/usage.tsx +++ b/site/src/examples/usage/usage.tsx @@ -26,13 +26,24 @@ export function Usage({ children: ( {` "use client"; - + import { EmojiPicker } from "frimousse"; export function MyEmojiPicker() { return ( + + {({ categories }) => ( +
+ {categories.map(({ category, scrollTo }) => ( + + ))} +
+ )} +
Loading… diff --git a/src/components/emoji-picker.tsx b/src/components/emoji-picker.tsx index 6ca9eaa..ec09e4e 100644 --- a/src/components/emoji-picker.tsx +++ b/src/components/emoji-picker.tsx @@ -40,6 +40,7 @@ import type { EmojiData, EmojiPickerActiveEmojiProps, EmojiPickerCategory, + EmojiPickerCategoryNavProps, EmojiPickerDataCategory, EmojiPickerEmoji, EmojiPickerEmptyProps, @@ -1452,6 +1453,64 @@ function EmojiPickerSkinTone({ children, emoji }: EmojiPickerSkinToneProps) { return children({ skinTone, setSkinTone, skinToneVariations }); } +/** + * Exposes the emoji categories and provides scroll handlers to navigate + * to each category via a render callback. + * + * @example + * ```tsx + * + * {({ categories }) => ( + *
+ * {categories.map(({ category, scrollTo }) => ( + * + * ))} + *
+ * )} + *
+ * ``` + * + * This component allows building custom category navigation that can scroll + * to the corresponding section in the emoji list. + */ +function EmojiPickerCategoryNav({ + children, +}: EmojiPickerCategoryNavProps) { + const store = useEmojiPickerStore(); + const data = useSelectorKey(store, "data"); + const viewportRef = useSelectorKey(store, "viewportRef"); + const rowHeight = useSelectorKey(store, "rowHeight"); + const categoryHeaderHeight = useSelectorKey(store, "categoryHeaderHeight"); + + const categories = useMemo(() => { + if (!data?.categories || !viewportRef || !rowHeight || !categoryHeaderHeight) { + return []; + } + + return data.categories.map((category, index) => ({ + category: { label: category.label }, + scrollTo: () => { + const viewport = viewportRef.current; + + if (!viewport) { + return; + } + + const scrollTop = + index * categoryHeaderHeight + category.startRowIndex * rowHeight; + + viewport.scrollTo({ + top: scrollTop, + }); + }, + })); + }, [data?.categories, viewportRef, rowHeight, categoryHeaderHeight]); + + return children({ categories }); +} + EmojiPickerRoot.displayName = "EmojiPicker.Root"; EmojiPickerSearch.displayName = "EmojiPicker.Search"; EmojiPickerViewport.displayName = "EmojiPicker.Viewport"; @@ -1461,10 +1520,12 @@ EmojiPickerEmpty.displayName = "EmojiPicker.Empty"; EmojiPickerSkinToneSelector.displayName = "EmojiPicker.SkinToneSelector"; EmojiPickerActiveEmoji.displayName = "EmojiPicker.ActiveEmoji"; EmojiPickerSkinTone.displayName = "EmojiPicker.SkinTone"; +EmojiPickerCategoryNav.displayName = "EmojiPicker.CategoryNav"; export { EmojiPickerRoot as Root, // EmojiPickerSearch as Search, // + EmojiPickerCategoryNav as CategoryNav, // EmojiPickerViewport as Viewport, // EmojiPickerList as List, // EmojiPickerLoading as Loading, // diff --git a/src/index.ts b/src/index.ts index 43203f2..8fa7140 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export type { Category, Emoji, EmojiPickerActiveEmojiProps, + EmojiPickerCategoryNavProps, EmojiPickerEmptyProps, EmojiPickerListCategoryHeaderProps, EmojiPickerListComponents, diff --git a/src/types.ts b/src/types.ts index c8b9fa5..abcc484 100644 --- a/src/types.ts +++ b/src/types.ts @@ -283,3 +283,28 @@ export type EmojiPickerSkinToneProps = { */ children: (props: EmojiPickerSkinToneRenderProps) => ReactNode; }; + +export type EmojiPickerCategoryNavRenderProps = { + /** + * An array of categories with their labels and scroll handlers. + */ + categories: { + /** + * The category information. + */ + category: Category; + + /** + * A function to scroll the viewport to this category. + */ + scrollTo: () => void; + }[]; +}; + +export type EmojiPickerCategoryNavProps = { + /** + * A render callback which receives an array of categories with their + * labels and scroll handlers. + */ + children: (props: EmojiPickerCategoryNavRenderProps) => ReactNode; +};