nuxt / ui

A UI Library for Modern Web Apps, powered by Vue & Tailwind CSS.
https://ui.nuxt.com
MIT License
3.84k stars 472 forks source link

Number Input component #958

Closed hoanghadu closed 10 months ago

hoanghadu commented 10 months ago

Description

It would be great if there is a number input with buttons to increase and decrease value.

Props would be: min, max, step, autofocus, disabled, etc

for reference: https://github.com/marcocesarato/react-native-input-spinner

Additional context

No response

benjamincanac commented 10 months ago

You can use the UInput component and set the type to number and use all the other props, it will work.

hoanghadu commented 10 months ago

The increase/decrease buttons of <UInput type="number" /> is tiny, and they are not possible to click in mobile screen.

I also tried to make a component for number input with props and emit, but it it not beautiful as other components of Nuxt UI.

benjamincanac commented 10 months ago

If you want a custom component like in your reference, you can use the ButtonGroup and put a button with a - icon, an input with the .number modifier and another button with the + icon.

hoanghadu commented 10 months ago

I wrote a custom NumberInput component with increase/decrease buttons for Nuxt UI.

Here is the code, someone might need it.

<template>
  <UButtonGroup :size="props.size" orientation="horizontal">
    <UButton @click="decrement" icon="i-heroicons-minus-20-solid" color="gray" :disabled="props.min == value"/>
    <input type="number"
      :value="value"
      @input="handleInput"
      :min="min"
      :max="max"
      :step="step"
      class="w-20 text-center item-center border-solid border-2 border-gray-300"
      />
    <UButton @click="increment" icon="i-heroicons-plus-20-solid" color="gray" :disabled="props.max == value"/>
  </UButtonGroup>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue';

// Define props
const props = defineProps({
  modelValue: {
    type: Number,
    default: 0
  },
  min: {
    type: Number,
    default: -Infinity
  },
  max: {
    type: Number,
    default: Infinity
  },
  step: {
    type: Number,
    default: 1
  },
  size: {
    type: String as PropType<"md" | "sm" | "xs" | "2xs" | "lg" | "xl" | undefined>,
    default: 'md'
  }
});

// Reactive value reference
const value = ref(props.modelValue);

// Watch for external modelValue changes
watch(() => props.modelValue, (newVal) => {
  value.value = newVal;
});

// Emit function
const emit = defineEmits(['update:modelValue']);

// Increment function
const increment = () => {
  const newValue = Math.min(value.value + props.step, props.max);
  value.value = newValue;
  emit('update:modelValue', newValue);
};

// Decrement function
const decrement = () => {
  const newValue = Math.max(value.value - props.step, props.min);
  value.value = newValue;
  emit('update:modelValue', newValue);
};

// Handle input change
const handleInput = (event: Event) => {
  const target = event.target as HTMLInputElement;
  let newValue = parseFloat(target.value);
  newValue = Math.max(Math.min(newValue, props.max), props.min);
  value.value = newValue;
  emit('update:modelValue', newValue);
};
</script>

<style scoped>
  /* Hide Arrows From Input Number */
  /* Chrome, Safari, Edge, Opera */
  input::-webkit-outer-spin-button,
  input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }

  /* Firefox */
  input[type=number] {
    -moz-appearance: textfield;
  }
</style>

I am still a junior developer, if you find fine any part of the code that could be improved, feel free to comment. Cheers,

hoanghadu commented 10 months ago

The code above has a bug. The prop "size" does not work.

For instance, if we call <NumberInput v-model="val" :size="xs" /> , the console will show a warning: Vue warn]: Property "xs" was accessed during render but is not defined on instance.