Skip to content

Commit 1c15399

Browse files
covaci-edvin“Edvin
andauthored
feat: add open direction to the BccReact selector dropdown (#338)
* feat: add open direction to the BccReact selector dropdown * refactor: address comments * feat: close dropdown on click outside --------- Co-authored-by: “Edvin <“[email protected]”>
1 parent f449013 commit 1c15399

File tree

2 files changed

+60
-33
lines changed

2 files changed

+60
-33
lines changed

design-library/src/components/BccReact/BccReact.css

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
}
2323

2424
.bcc-react-selector {
25-
@apply flex max-h-9 flex-col overflow-hidden bg-neutral-100 px-0 drop-shadow-md transition-all duration-300 ease-out;
25+
@apply flex h-9 flex-col overflow-hidden bg-neutral-100 px-0 drop-shadow-md;
2626
border-radius: 18px;
2727
}
2828

@@ -45,25 +45,6 @@
4545
@apply bottom-0 top-auto pb-0;
4646
}
4747

48-
.bcc-react-selector--expanded {
49-
@apply max-h-56 translate-y-0 overflow-auto;
50-
}
51-
.bcc-react-selector--expanded.bcc-react-selector-rows--1 {
52-
@apply max-h-20;
53-
}
54-
.bcc-react-selector--expanded.bcc-react-selector-rows--2 {
55-
@apply max-h-28;
56-
}
57-
.bcc-react-selector--expanded.bcc-react-selector-rows--3 {
58-
@apply max-h-36;
59-
}
60-
.bcc-react-selector--expanded.bcc-react-selector-rows--4 {
61-
@apply max-h-48;
62-
}
63-
.bcc-react-dropdown-container--top .bcc-react-selector--expanded {
64-
@apply -translate-y-full;
65-
}
66-
6748
.bcc-react-dropdown {
6849
@apply -z-10 flex w-full flex-wrap overflow-hidden px-1;
6950
}

design-library/src/components/BccReact/BccReact.vue

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
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";
99
import BccReactEmoji from "./BccReactEmoji.vue";
1010
import type { BccReactInfo } from "./types";
1111
@@ -24,11 +24,14 @@ const emit = defineEmits<{
2424
}>();
2525
2626
const MAX_VISIBLE_EMOJIS = 7;
27+
const SELECTOR_HEIGHT = 36;
2728
2829
const show = ref(false);
2930
const showMore = ref(false);
3031
const clickedId = ref<string | null>(null);
3132
const selector = ref<HTMLElement | null>(null);
33+
const toggleButton = ref<HTMLElement | null>(null);
34+
const shouldOpenUpwards = ref(false);
3235
3336
const 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

Comments
 (0)