Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BccReact component #281

Merged
merged 3 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading