PXD

Reactions

Opens an emoji picker beside its content and keeps the reaction list in sync through v-model; the chips are rendered by the parent, through the methods exposed below.

Default

The component inherits from popover and supports all the properties of popover. For details, See: Popover.

Shipped the new onboarding flow
<script setup>
import { ref, shallowRef } from 'vue'

const reactions = ref([
  { emoji: '๐Ÿ‘', count: 3 },
  { emoji: '๐Ÿš€', count: 1 },
])

const reactionsRef = shallowRef()

function onChip(emoji) {
  reactionsRef.value.toggle(emoji)
}
</script>

<template>
  <PReactions ref="reactionsRef" v-model="reactions">
    <div class="gap-1 flex flex-col items-start">
      <span class="px-3 py-2 rounded-lg bg-gray-100 text-sm">Shipped the new onboarding flow</span>

      <div data-popover-ignore class="gap-1 flex items-center">
        <button
          v-for="item in reactions"
          :key="item.emoji"
          type="button"
          :data-mine="item.mine || undefined"
          :aria-pressed="item.mine ? 'true' : 'false'"
          class="ps-1.5 pe-2 h-6 gap-1 text-xs inline-flex shrink-0 cursor-pointer appearance-none items-center rounded-full border bg-background-100 text-foreground-secondary self-focus-ring outline-none hover:bg-background-hover data-[mine=true]:border-primary data-[mine=true]:bg-gray-200 data-[mine=true]:text-foreground motion-safe:transition-colors"
          @click="onChip(item.emoji)"
        >
          <span aria-hidden="true">{{ item.emoji }}</span>
          <span class="tabular-nums">{{ item.count }}</span>
        </button>
      </div>
    </div>
  </PReactions>
</template>

Incoming reaction

Shipped the new onboarding flow
<script setup>
import { ref, shallowRef } from 'vue'

const reactions = ref([{ emoji: '๐Ÿš€', count: 1, mine: true }])
const reactionsRef = shallowRef()

const others = ['๐Ÿ‘', '๐ŸŽ‰', '๐Ÿ‘€', 'โค๏ธ']
let step = 0

function onChip(emoji) {
  reactionsRef.value.toggle(emoji)
}

function someoneReacts() {
  const emoji = others[step++ % others.length]
  const existing = reactions.value.find(item => item.emoji === emoji)

  reactions.value = existing
    ? reactions.value.map(item => (item.emoji === emoji ? { ...item, count: item.count + 1 } : item))
    : [...reactions.value, { emoji, count: 1 }]
}
</script>

<template>
  <div class="gap-1 flex flex-col items-start">
    <PReactions ref="reactionsRef" v-model="reactions">
      <span class="px-3 py-2 rounded-lg bg-gray-100 text-sm">Shipped the new onboarding flow</span>
    </PReactions>

    <div data-popover-ignore class="gap-1 flex items-center">
      <button
        v-for="item in reactions"
        :key="item.emoji"
        type="button"
        :data-mine="item.mine || undefined"
        :aria-pressed="item.mine ? 'true' : 'false'"
        class="ps-1.5 pe-2 h-6 gap-1 text-xs inline-flex shrink-0 cursor-pointer appearance-none items-center rounded-full border bg-background-100 text-foreground-secondary self-focus-ring outline-none hover:bg-background-hover data-[mine=true]:border-primary data-[mine=true]:bg-gray-200 data-[mine=true]:text-foreground motion-safe:transition-colors"
        @click="onChip(item.emoji)"
      >
        <span aria-hidden="true">{{ item.emoji }}</span>
        <span class="tabular-nums">{{ item.count }}</span>
      </button>
    </div>

    <PButton size="sm" variant="ghost" @click="someoneReacts">Someone else reacts</PButton>
  </div>
</template>

Custom emoji

Love it
<script setup>
import { ref } from 'vue'

const reactions = ref([])

const emojis = ['โค๏ธ', '๐Ÿงก', '๐Ÿ’›', '๐Ÿ’š', '๐Ÿ’™', '๐Ÿ’œ']
</script>

<template>
  <PReactions v-model="reactions" :emojis="emojis">
    <span class="px-3 py-2 rounded-lg bg-gray-100 text-sm">Love it</span>
  </PReactions>
</template>

Hover trigger

Hover me to react
<script setup>
import { ref } from 'vue'

const reactions = ref([{ emoji: '๐Ÿ”ฅ', count: 2, mine: true }])
</script>

<template>
  <PReactions v-model="reactions" trigger="hover">
    <span class="px-3 py-2 rounded-lg bg-gray-100 text-sm">Hover me to react</span>
  </PReactions>
</template>

Props

NameTypeDefaultDescription
model-valueReaction[][]Current reactions, each with an emoji, a count and whether it is mine
emojisstring[]['๐Ÿ‘', 'โค๏ธ', '๐Ÿ˜‚', '๐Ÿ˜ฎ', '๐Ÿ˜ข', '๐Ÿ™']Emoji offered in the picker
triggerPopoverTrigger | PopoverTrigger[]'click'How the picker opens: click, hover, contextmenu or manual
positionComponentPosition'top-start'Placement of the picker relative to the content
disabledbooleanfalseDisable the picker and make the exposed methods no-ops

Events

NameTypeDescription
add(emoji: string) => voidEmitted when the reader adds their reaction.
remove(emoji: string) => voidEmitted when the reader takes their reaction off.
change(reactions: Reaction[]) => voidEmitted when the reaction list changes.
update:model-value(reactions: Reaction[]) => voidEmitted when the reaction list changes.

Slots

NameDescription
defaultContent the picker is anchored to. Mark the parts that handle their own clicks with data-popover-ignore.

Methods

NameTypeDescription
pick(emoji: string) => voidAdd the emoji and close the picker.
toggle(emoji: string) => voidAdd the emoji, or take the reader off it when it is already theirs.

Source

Source