Skip to content

Commit

Permalink
Add BccReact component (#281)
Browse files Browse the repository at this point in the history
  • Loading branch information
u12206050 authored Jan 17, 2024
1 parent e6173da commit c71276f
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 1 deletion.
37 changes: 37 additions & 0 deletions design-library/src/components/BccReact/BccReact.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@layer components {
.bcc-react {
@apply relative flex w-full items-center overflow-visible;
}
.bcc-react-toggle {
@apply mr-1 flex shrink-0 cursor-pointer items-center justify-center rounded-full p-1 leading-tight transition;
}
.bcc-react-list {
@apply hide-scrollbar flex flex-1 items-center gap-1 overflow-x-auto overflow-y-hidden rounded-full p-1;
}
.bcc-react-empty {
@apply text-label flex items-center;
}

.bcc-react-selector {
@apply absolute -left-1 z-50 flex items-center gap-1 rounded-full bg-neutral-100 px-1 dark:bg-neutral-600;
/* Default --bottom */
@apply top-11 origin-top-left shadow-lg;
}

.bcc-react-selector--top {
@apply -top-10 origin-bottom-left shadow;
}

.bcc-react-selector-item {
@apply p-2 text-xl leading-none;
}

.bcc-react-emoji-list-item {
@apply flex cursor-pointer items-center justify-center rounded-full p-1 text-2xl leading-none shadow transition-all hover:scale-105;
/* Default --not-selected */
@apply text-black bg-neutral-100;
}
.bcc-react-emoji-list-item--selected {
@apply bg-neutral-800 text-white;
}
}
31 changes: 31 additions & 0 deletions design-library/src/components/BccReact/BccReact.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, it, expect } from "vitest";

import { mount } from "@vue/test-utils";
import BccReact from "./BccReact.vue";

describe("BccReact", () => {
it("renders reactions", () => {
expect(BccReact).toBeTruthy();

const wrapper = mount(BccReact, {
props: {
emojis: [
{
id: "thumbsup",
emoji: "👍",
count: 1,
},
{
id: "happy",
emoji: "😃",
count: 4,
},
],
},
});

expect(wrapper.text()).toContain("4");
expect(wrapper.text()).not.toContain("1");
expect(wrapper.html()).toMatchSnapshot();
});
});
165 changes: 165 additions & 0 deletions design-library/src/components/BccReact/BccReact.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import BccReact from "./BccReact.vue";

import type { Meta, StoryFn } from "@storybook/vue3";

export default {
title: "Components/BccReact",
component: BccReact,
argTypes: {},
} as Meta<typeof BccReact>;

const Template: StoryFn<typeof BccReact> = (args) => ({
components: { BccReact },
setup() {
return { args };
},
methods: {
onToggle(emojiId: string) {
const emoji = args.emojis.find((e) => e.id === emojiId);
if (!emoji) return;
if (emoji.count === undefined) emoji.count = 0;
emoji.count += emoji.selected ? -1 : 1;
emoji.selected = !emoji.selected;

// Enable for only allowing single reaction per user
/*
if (args.singleReaction && emoji.selected) {
args.emojis.forEach((e) => {
if (e.id !== emojiId && e.selected) {
e.selected = false;
e.count = (e.count || 1) - 1;
}
});
}
*/
},
},
template: `
<div class="h-16 flex items-center">
<BccReact v-bind="args" @toggle="onToggle" />
</div>
`,
});

export const Example = Template.bind({});
Example.args = {
top: false,
emojis: [
{
id: "thumbsup",
emoji: "👍",
count: 0,
},
{
id: "happy",
emoji: "😃",
count: 2,
selected: true,
},
{
id: "smile",
emoji: "😊",
count: 0,
},
{
id: "glasses",
emoji: "😎",
count: 0,
},
{
id: "love",
emoji: "😍",
count: 0,
},
{
id: "stars",
emoji: "🤩",
count: 0,
},
{
id: "rocket",
emoji: "🚀",
count: 93,
},
],
};

Example.parameters = {
docs: {
source: {
language: "html",
code: `
<--script-->
onToggle(emojiId: string) {
const emoji = args.emojis.find((e) => e.id === emojiId);
if (!emoji) return;
if (emoji.count === undefined) emoji.count = 0;
emoji.count += emoji.selected ? -1 : 1;
emoji.selected = !emoji.selected;
// Enable for only allowing single reaction per user
/*
if (args.singleReaction && emoji.selected) {
args.emojis.forEach((e) => {
if (e.id !== emojiId && e.selected) {
e.selected = false;
e.count = (e.count || 1) - 1;
}
});
}
*/
}
</-script-->
<template>
<div class="h-16 flex items-center">
<BccReact v-bind="args" @toggle="onToggle" />
</div>
</template>
`,
},
},
};

export const EmptyEmojis = Template.bind({});
EmptyEmojis.args = {
top: true,
placeholder: "No reactions yet",
emojis: [
{
id: "thumbsup",
emoji: "👍",
count: 0,
},
{
id: "happy",
emoji: "😃",
count: 0,
},
{
id: "smile",
emoji: "😊",
count: 0,
},
{
id: "glasses",
emoji: "😎",
count: 0,
},
{
id: "love",
emoji: "😍",
count: 0,
},
{
id: "stars",
emoji: "🤩",
count: 0,
},
{
id: "rocket",
emoji: "🚀",
count: 0,
},
],
};
88 changes: 88 additions & 0 deletions design-library/src/components/BccReact/BccReact.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { AddReactionFillIcon, AddReactionIcon, KeyboardArrowLeftIcon } from "@bcc-code/icons-vue";
import { computed, ref } from "vue";
import BccReactEmoji from "./BccReactEmoji.vue";
import type { EmojiStat } from "./BccReactEmoji.vue";
type Props = {
emojis: EmojiStat[];
top?: boolean;
placeholder?: string;
};
const props = withDefaults(defineProps<Props>(), {
top: false,
placeholder: "Be the first to react 😉",
});
const emit = defineEmits<{
(e: "toggle", id: string): void;
}>();
const activeEmojis = computed(() => {
const active = props.emojis.filter((emoji) => emoji.count && emoji.count > 0);
active.sort((a, b) => b.count! - a.count!);
return active;
});
const show = ref(false);
function selectEmoji(emoji: EmojiStat) {
emit("toggle", emoji.id);
setTimeout(() => {
show.value = false;
}, 250);
}
</script>

<template>
<div class="bcc-react">
<TransitionGroup name="bcc-fade">
<button
key="toggle"
@click="show = !show"
v-if="show || emojis.some((e) => !e.selected)"
class="bcc-react-toggle"
:class="[top ? 'rounded-b-full' : 'rounded-t-full']"
>
<AddReactionFillIcon v-if="show" class="w-6" />
<AddReactionIcon v-else class="w-6" />
</button>

<div class="bcc-react-list">
<template v-if="activeEmojis.length > 0">
<TransitionGroup name="bcc-explode" appear>
<template v-for="emoji in activeEmojis" :key="emoji.id">
<keep-alive>
<BccReactEmoji @click="selectEmoji(emoji)" v-bind="emoji" />
</keep-alive>
</template>
</TransitionGroup>
</template>
<p v-else-if="placeholder" class="bcc-react-empty">
<KeyboardArrowLeftIcon class="mr-1 w-4" /> {{ placeholder }}
</p>
</div>
</TransitionGroup>

<Transition name="bcc-scale-fast">
<div
key="list"
v-if="show"
class="bcc-react-selector"
:class="{ 'bcc-react-selector--top': top }"
>
<TransitionGroup name="bcc-explode">
<template v-for="emoji in emojis" :key="emoji.id">
<button
v-if="!emoji.selected"
@click="selectEmoji(emoji)"
class="bcc-react-selector-item"
>
{{ emoji.emoji }}
</button>
</template>
</TransitionGroup>
</div>
</Transition>
</div>
</template>
35 changes: 35 additions & 0 deletions design-library/src/components/BccReact/BccReactEmoji.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { ref, watch } from "vue";
export type EmojiStat = {
id: string;
emoji: string;
count?: number;
selected?: boolean;
};
const props = defineProps<EmojiStat>();
const animate = ref(false);
watch(
() => props.count,
() => {
animate.value = true;
setTimeout(() => {
animate.value = false;
}, 1000);
}
);
</script>

<template>
<button
class="bcc-react-emoji-list-item"
:class="{
'bcc-react-emoji-list-item--selected': selected,
'animate-wiggle': animate,
}"
>
<span>{{ emoji }}</span>
<span v-if="count && count > 1" class="mx-1 text-xs">{{ count }}</span>
</button>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`BccReact > renders reactions 1`] = `
"<div class=\\"bcc-react\\">
<transition-group-stub name=\\"bcc-fade\\" appear=\\"false\\" persisted=\\"false\\" css=\\"true\\"><button class=\\"bcc-react-toggle rounded-t-full\\"><svg aria-hidden=\\"true\\" viewBox=\\"0 -960 960 960\\" fill=\\"currentColor\\" class=\\"w-6\\">
<path d=\\"M480-480Zm0 400q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q43 0 83 8.5t77 24.5v90q-35-20-75.5-31.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160q133 0 226.5-93.5T800-480q0-32-6.5-62T776-600h86q9 29 13.5 58.5T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm320-680h-40q-17 0-28.5-11.5T720-800q0-17 11.5-28.5T760-840h40v-40q0-17 11.5-28.5T840-920q17 0 28.5 11.5T880-880v40h40q17 0 28.5 11.5T960-800q0 17-11.5 28.5T920-760h-40v40q0 17-11.5 28.5T840-680q-17 0-28.5-11.5T800-720v-40ZM620-520q25 0 42.5-17.5T680-580q0-25-17.5-42.5T620-640q-25 0-42.5 17.5T560-580q0 25 17.5 42.5T620-520Zm-280 0q25 0 42.5-17.5T400-580q0-25-17.5-42.5T340-640q-25 0-42.5 17.5T280-580q0 25 17.5 42.5T340-520Zm140 260q58 0 107-28t79-76q6-12-1-24t-21-12H316q-14 0-21 12t-1 24q30 48 79.5 76T480-260Z\\"></path>
</svg></button>
<div class=\\"bcc-react-list\\">
<transition-group-stub name=\\"bcc-explode\\" appear=\\"true\\" persisted=\\"false\\" css=\\"true\\"><button class=\\"bcc-react-emoji-list-item\\"><span>😃</span><span class=\\"mx-1 text-xs\\">4</span></button><button class=\\"bcc-react-emoji-list-item\\"><span>👍</span>
<!--v-if-->
</button></transition-group-stub>
</div>
</transition-group-stub>
<transition-stub name=\\"bcc-scale-fast\\" appear=\\"false\\" persisted=\\"false\\" css=\\"true\\">
<!--v-if-->
</transition-stub>
</div>"
`;
Loading

0 comments on commit c71276f

Please sign in to comment.