55 KeyboardArrowDownIcon ,
66 KeyboardArrowLeftIcon ,
77} from " @bcc-code/icons-vue" ;
8- import { computed , ref , watch } from " vue" ;
8+ import { computed , nextTick , onMounted , onUnmounted , ref , watch } from " vue" ;
99import BccReactEmoji from " ./BccReactEmoji.vue" ;
1010import type { BccReactInfo } from " ./types" ;
1111
@@ -24,11 +24,14 @@ const emit = defineEmits<{
2424}>();
2525
2626const MAX_VISIBLE_EMOJIS = 7 ;
27+ const SELECTOR_HEIGHT = 36 ;
2728
2829const show = ref (false );
2930const showMore = ref (false );
3031const clickedId = ref <string | null >(null );
3132const selector = ref <HTMLElement | null >(null );
33+ const toggleButton = ref <HTMLElement | null >(null );
34+ const shouldOpenUpwards = ref (false );
3235
3336const activeEmojis = computed (() => {
3437 const active = props .emojis .filter ((emoji ) => emoji .count && emoji .count > 0 );
@@ -52,19 +55,72 @@ function selectEmoji(emoji: BccReactInfo) {
5255 }, 250 );
5356}
5457
55- watch (show , (newVal ) => {
58+ function checkOpenDirection() {
59+ if (! selector .value ) return ;
60+ const rect = selector .value .getBoundingClientRect ();
61+ const viewportHeight = window .innerHeight || document .documentElement .clientHeight ;
62+
63+ shouldOpenUpwards .value = rect .bottom + 200 > viewportHeight ;
64+ }
65+
66+ watch (show , async (newVal ) => {
5667 if (newVal ) {
5768 visibleEmojis .value = unselectedEmojis .value .slice (0 , MAX_VISIBLE_EMOJIS );
5869 hiddenEmojis .value = unselectedEmojis .value .slice (MAX_VISIBLE_EMOJIS );
70+ await nextTick (checkOpenDirection );
71+ }
72+ });
73+
74+ watch (showMore , async (newVal ) => {
75+ await nextTick (checkOpenDirection );
76+ if (! selector .value ) return ;
77+
78+ const el = selector .value ;
79+ el .style .transition = " height 0.3s ease, transform 0.3s ease" ;
80+
81+ if (newVal ) {
82+ el .style .height = ` ${SELECTOR_HEIGHT }px ` ;
83+ el .style .transform = " translateY(0)" ;
84+ void el .offsetHeight ;
85+ el .style .height = el .scrollHeight + " px" ;
86+ if (shouldOpenUpwards .value ) {
87+ el .style .transform = ` translateY(-${el .scrollHeight - SELECTOR_HEIGHT }px) ` ;
88+ }
89+ } else {
90+ void el .offsetHeight ;
91+ el .style .height = el .scrollHeight + " px" ;
92+ el .style .height = ` ${SELECTOR_HEIGHT }px ` ;
93+ if (shouldOpenUpwards .value ) {
94+ el .style .transform = ` translateY(0) ` ;
95+ }
5996 }
6097});
98+
99+ onMounted (() => {
100+ function onClickOutside(event : MouseEvent ) {
101+ const target = event .target as Node ;
102+
103+ if (! show .value || selector .value ?.contains (target ) || toggleButton .value ?.contains (target )) {
104+ return ;
105+ }
106+
107+ show .value = false ;
108+ }
109+
110+ document .addEventListener (" click" , onClickOutside , true );
111+
112+ onUnmounted (() => {
113+ document .removeEventListener (" click" , onClickOutside , true );
114+ });
115+ });
61116 </script >
62117
63118<template >
64119 <div class =" bcc-react" >
65120 <TransitionGroup name =" bcc-fade" >
66121 <button
67122 key =" toggle"
123+ ref =" toggleButton"
68124 @click =" show = !show"
69125 v-if =" show || emojis.some((e) => !e.selected)"
70126 class =" bcc-react-toggle"
@@ -95,17 +151,7 @@ watch(show, (newVal) => {
95151 class =" bcc-react-selector-container"
96152 :class =" { 'bcc-react-selector-container--top': props.top }"
97153 >
98- <div
99- ref =" selector"
100- class =" bcc-react-selector"
101- :class ="
102- showMore
103- ? `bcc-react-selector--expanded bcc-react-selector-rows--${Math.ceil(
104- hiddenEmojis.length / 8
105- )}`
106- : ''
107- "
108- >
154+ <div ref =" selector" class =" bcc-react-selector" >
109155 <div class =" bcc-react-selector-emojis-container" >
110156 <template v-for =" emoji in visibleEmojis " :key =" emoji .id " >
111157 <button
0 commit comments