Skip to content
Welcome to vote for your favorite component! You can also tell me anything you want to say! (*´∀`)~♥

Ninja Button button

A button that creates shadow clones when disabled ((-∀(-∀-)∀-))

Usage Examples

Basic Usage

Fake! They're all fake! ((((;゚Д゚)))

View example source code
vue
<template>
  <div class="w-full flex flex-col gap-4">
    <base-checkbox
      v-model="disabled"
      :label="t('disableButton')"
      class="border rounded p-4"
    />

    <div class="h-[50vh] flex items-center justify-center">
      <btn-ninja
        :label="t('submit')"
        :disabled
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import BtnNinja from '../btn-ninja.vue'

const { t } = useI18n()

const disabled = ref(false)
</script>

Careful Confirmation

Eliminate all shadow clones before you can click "Confirm". ( •̀ ω •́ )✧

Please confirm your action!You are about to enable the "Auto-delete all Monday morning meetings" feature. This will:
  • Permanently delete all Monday morning meetings from the calendar
  • Cancel all related meeting room reservations
  • Automatically send an "I hate Mondays" email to all participants
Cancel
🎉 Operation completed!
View example source code
vue
<template>
  <div class="relative flex flex-col gap-3 border border-red-300 rounded-lg bg-red-100/50 p-6">
    <span class="text-xl text-red-800 font-bold">
      {{ t('title') }}
    </span>

    <span class="mt-2 text-red-700">
      {{ t('description') }}
    </span>

    <ul class="list-disc list-inside text-sm text-red-400 !m-0">
      <li>{{ t('action1') }}</li>
      <li>{{ t('action2') }}</li>
      <li>{{ t('action3') }}</li>
    </ul>

    <div class="flex justify-end gap-4">
      <btn-ninja>
        <div class="cursor-pointer p-2 px-4 text-gray-800">
          {{ t('cancel') }}
        </div>
      </btn-ninja>

      <btn-ninja
        v-slot="{ isHidden }"
        :disabled
        @show="disabled = false"
      >
        <base-btn
          :label="t('confirm')"
          class="active:scale-95 !border-red-600"
          :class="{
            'text-red-600 !border-dashed': disabled || isHidden,
            '!bg-red-600/50 !text-white': !disabled,
          }"
          @click="handleClick"
        />
      </btn-ninja>
    </div>

    <div
      class="pointer-events-none absolute inset-0 flex items-center justify-center bg-black/90 duration-500"
      :class="done ? ' opacity-100' : 'opacity-0'"
    >
      <span class="text-2xl text-white font-bold">
        {{ t('done') }}
      </span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseBtn from '../../base-btn.vue'
import BtnNinja from '../btn-ninja.vue'

const { t } = useI18n()

const disabled = ref(true)

const done = ref(false)
function handleClick() {
  if (disabled.value) {
    return
  }

  done.value = true
  setTimeout(() => {
    done.value = false
    disabled.value = true
  }, 2000)
}
</script>

How It Works

Anyone with some Vue experience probably knows that Vue components can use slots to let users insert custom template content.

One day, a thought suddenly popped up: what happens if the same slot is used multiple times? ヾ(◍'౪`◍)ノ゙

And that's how this component was born. ᕕ( ゚ ∀。)ᕗ

Source Code

API

Props

interface Props {
  label?: string;
  /** 分身數量 */
  count?: number;
  disabled?: boolean;
}

Emits

interface Emits {
  click: [];
  hidden: [];
  show: [];
}

Slots

interface Slots {
  default?: (props: { isHidden: boolean }) => unknown;
}

v0.60.0