nuxt / ui

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

[Docs] Examples #297

Closed benjamincanac closed 7 months ago

benjamincanac commented 1 year ago

Feel free to comment here if you have some examples you'd like to see on the documentation: https://ui.nuxtlabs.com/getting-started/examples

Haythamasalama commented 1 year ago

✨ I'm preparing a PR for an advanced example table that is used in something similar to my own component in a real-life project. I'm currently working on it and have rebuilt it using Nuxt Lab UI.

The table features:

What do you think about providing this as an example? Everyone should be able to wrap it or turn it into single components.

Currently, I've coded 452 lines, and I'm not finished yet!

I have a question regarding mocking or using the API service for this example to simulate these operations. What is your opinion?"

https://github.com/nuxtlabs/ui/assets/37311945/085bef46-7387-465b-9f10-fb0f7ee5302d

benjamincanac commented 1 year ago

It would be indeed really nice to have a full data-table example like on https://ui.shadcn.com/examples/tasks. I'd keep the example as simple as possible without any API mocking by hard-coding the data though.

Feel free to submit a PR :)

Haythamasalama commented 1 year ago

To be honest, the table example with API is missing in the docs, and this is always used with the filter form API and reset pagination, filitering, etc., which are needed in real life. I will make it as simple as possible.

ddahan commented 1 year ago

I would love to have an example for form validation using Nuxt Labs UI form components and a library like VeeValidate.

benjamincanac commented 1 year ago

439 is coming soon for this!

ddahan commented 1 year ago

I was wondering if it makes sense to add an example with SortableJS using VueUse?

I mean, the code itself would have no interest because it's very easy to implement. However, the presence of such an example in the doc would be a simple way to remind people it exists, and hence they don't need to use another UI library that would implement it from scratch (kind of the same logic the the date picker example, but in simpler).

ddahan commented 1 year ago

Another interesting example could be a file upload mechanism using https://vueuse.org/core/useFileDialog/#usefiledialog

rahul37865 commented 1 year ago

I would love to have an example for form validation using Nuxt Labs UI form components and a library like VeeValidate.

Have you implemented VeeValidate + Yup with NuxtLabs UI ? It would be very helpful if you could write an example.

oritwoen commented 1 year ago

I would love to have an example for form validation using Nuxt Labs UI form components and a library like VeeValidate.

Have you implemented VeeValidate + Yup with NuxtLabs UI ? It would be very helpful if you could write an example.

It's already available: https://ui.nuxtlabs.com/forms/form

geekyshow1 commented 1 year ago

Recently VeeValidate official have created an example for vee-validate + yup + nuxtlabs UI Click Here However there is minor issue with this example which i have reported to VeeValidate official. I would request Nuxtlabs UI kindly add above mentioned example on your website https://ui.nuxtlabs.com/forms/form#other-libraries

geekyshow1 commented 1 year ago

Hi, As There are many Form Component which are still on development or at present not available I have created a basic Form which i am sharing feel free to use it in your project or NuxtLabs UI Team can add it on their Example Doc. suggestions and enhancements are welcome.

<script setup>
import { object, string } from 'yup';
const form = ref()
const selectedImage = ref('');
const imageUploadError = ref('');
const documentUploadError = ref(null);
const previewUrl = ref('');
const allStates = ['Jharkhand', 'Bihar', 'West Bengal']
const allLocations = ['Delhi', 'Banglore', 'Kolkata', 'Ranchi', 'Bokaro']
const allGenders = [{
  name: 'male',
  value: 'male',
  label: 'Male'
}, {
  name: 'female',
  value: 'female',
  label: 'Female'
}]
const formData = ref({
  name: '',
  email: '',
  dob: '',
  state: '',
  gender: 'male',
  location: [],
  pimage: undefined,
  rdoc: undefined,
});

// Using Yup to validate the form. You can add other fields too except pimage and rdoc
// We will manually validate pimage and rdoc
const schema = object({
  name: string().required('Name is required'),
  email: string().email().required('Email is required'),
})

const handleImageUpload = (event) => {
  const selectedFile = event.target.files[0];

  // Manually Validating pimage field on change
  // Check if the selected file is an image and meets the size criteria
  if (selectedFile) {
    if (selectedFile.type.startsWith('image/') && selectedFile.size <= 300 * 1024) {
      // Valid image: update selectedImage, formData.pimage, and previewUrl
      selectedImage.value = selectedFile;
      formData.pimage = selectedFile;

      // Clear any previous error message
      imageUploadError.value = null;

      // To Preview Selected Image
      const reader = new FileReader();
      reader.onload = (event) => {
        previewUrl.value = event.target.result;
      };
      reader.readAsDataURL(selectedFile);
    } else {
      // Invalid image: clear selectedImage and formData.pimage
      selectedImage.value = null;
      formData.pimage = null;

      // Clear previewUrl
      previewUrl.value = '';

      // Set the error message
      imageUploadError.value = 'Invalid image. Please select a valid image (JPEG, JPG, or PNG) with a size <= 300KB.';
    }
  }
};
const handleDocumentUpload = (event) => {
  const selectedFile = event.target.files[0];

  // Manually Validating rdoc field on change
  // Check if the selected file is a valid document and meets the size criteria
  if (selectedFile) {
    const allowedFormats = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
    const maxSize = 2 * 1024 * 1024; // 2MB

    if (allowedFormats.includes(selectedFile.type) && selectedFile.size <= maxSize) {
      // Valid document: update formData.rdoc
      formData.rdoc = selectedFile;

      // Clear any previous error message
      documentUploadError.value = null;
    } else {
      // Invalid document: clear formData.rdoc
      formData.rdoc = null;

      // Set the error message
      documentUploadError.value = 'Invalid document. Please select a valid document (PDF, DOC, or DOCX) with a size <= 2MB.';
    }
  }
};

const submitForm = async () => {
  await form.value.validate()

  // checking image and file validation error on form submit
  if (imageUploadError.value || documentUploadError.value) {
      console.error('Form validation failed');
      return; // Exit the function if there are validation errors
    }
   // Change apiURL according to your project requirement 
  const apiUrl = 'http://127.0.0.1:8000/api/resume/';

  // Create a FormData object and append form data
  const data = new FormData();
  data.append('name', formData.value.name);
  data.append('email', formData.value.email);
  data.append('dob', formData.value.dob);
  data.append('state', formData.value.state);
  data.append('gender', formData.value.gender);
  data.append('location', formData.value.location);
  data.append('pimage', formData.pimage);
  data.append('rdoc', formData.rdoc);
  try {
    const {data:user, error} = await useFetch(apiUrl, {
      method: 'POST',
      body: data
     // Do not include Headers. Let it handle by useFetch. Including headers e.g. multipart may cause error.
    });
    if (user.value) {
      // Handle a successful API response here
      console.log('Form data submitted successfully');
    }
    if (error.value) {
      // Handle a error API response here
      console.log('There is something wrong', error.value);
    }
  } catch (error) {
    console.error('Error:', error);
  }
};
</script>

<template>
  <UContainer>
    <UForm ref="form" :schema="schema" :state="formData" @submit.prevent="submitForm" class="max-w-2xl shadow-md p-10 shadow-orange-300">
      <UFormGroup label="Name" name="name" class="mb-4">
        <UInput v-model="formData.name" class="px-3 py-2 border rounded" />
      </UFormGroup>
      <UFormGroup label="Email" name="email" class="mb-4">
        <UInput v-model="formData.email" class="px-3 py-2 border rounded" />
      </UFormGroup>
      <UFormGroup label="Date of Birth" name="dob" class="mb-4">
        <input type="date" id="dob" v-model="formData.dob" class="w-full px-3 py-2 dark:bg-gray-800 dark:border-0 border-2 rounded"/>
      </UFormGroup>
      <UFormGroup label="State" name="state" class="mb-4">
        <USelect v-model="formData.state" :options="allStates" class="px-3 py-2 border rounded" />
      </UFormGroup>
      <UFormGroup label="Gender" name="gender" class="mb-4">
        <div class="flex space-x-4">
          <URadio v-for="gdr of allGenders" :key="gdr.name" v-model="formData.gender" v-bind="gdr" class="mr-2" />
        </div>
      </UFormGroup>
      <UFormGroup label="Job Location" name="location" class="mb-4">
        <USelectMenu id="location" v-model="formData.location" :options="allLocations" multiple placeholder="Select location" />
      </UFormGroup>
      <UFormGroup label="Profile Image (JPEG/JPG/PNG - less than 300KB)" name="pimage" class="mb-4">
        <div class="flex items-center space-x-6">
          <div class="shrink-0" v-if="selectedImage">
            <img class="h-16 w-16 object-cover rounded-full" :src="previewUrl" alt="Current profile photo" />
          </div>
          <label class="block">
            <span class="sr-only">Choose profile photo</span>
            <input type="file" id="pimage" name="pimage" class="text-sm text-slate-500 dark:text-gray-100
              file:mr-4 file:py-1 file:px-3
              file:rounded-full file:border-0
              file:text-xs 
              file:bg-violet-50 file:text-violet-700
              hover:file:bg-violet-100"
              accept="image/jpeg, image/png, image/jpg" 
              @change="handleImageUpload"
            />
          </label>
        </div>
        <!-- Display an error message if imageUploadError is not null -->
        <p v-if="imageUploadError" class="text-red-500 text-sm mt-2">{{ imageUploadError }}</p>
      </UFormGroup>
      <UFormGroup label="Resume (Doc/Docx/PDF - less than 2MB)" name="rdoc" class="mb-4">
        <span class="sr-only">Choose Resume File</span>
        <input type="file" id="rdoc" name="rdoc" class="text-sm text-slate-500  dark:text-gray-100
          file:mr-4 file:py-1 file:px-3
          file:rounded-full file:border-0
          file:text-xs 
          file:bg-violet-50 file:text-violet-700
          hover:file:bg-violet-100"
          accept=".pdf,.doc,.docx" 
          @change="handleDocumentUpload"
        />
        <!-- Display an error message if documentUploadError is not null -->
        <p v-if="documentUploadError" class="text-red-500 text-sm mt-2">{{ documentUploadError }}</p>
      </UFormGroup>
      <UButton type="submit">Submit</UButton>
    </UForm>
  </UContainer>
</template>

<style scoped>
</style>

Note - I have used yup so you need to install yup using npm i yup

madebyfabian commented 1 year ago

Wanted to share this implementation of the pagination, to make the prev/next buttons to be <a> elements, to improve SEO.

If you have an idea how I can implement it so that the page (1, 2, 3, ...) buttons also render as <a> elements, let me know!

<template>
  <div class="container">
    <div class="flex justify-center mt-8 md:mt-12">
      <UPagination
        v-model="page"
        :page-count="pageCount"
        :total="state.storiesTotal"
        :prevButton="prevPageProps"
        :nextButton="nextPageProps" />
    </div>
  </div>
</template>
<script setup lang="ts">
  import type { Button } from '@nuxt/ui/dist/runtime/types'

  // Amount of articles per page
  const pageCount = ref(3)

  // Current page count
  // Custom composable that sets a query parameter.
  const { searchQuery } = useRouteSearchQuery({ name: 'page', defaultValue: '1' })
  const page = computed<number>({
    get: () => Number(searchQuery.value),
    set: value => {
        searchQuery.value = String(value)
    },
  })

  // Pagination UI props, used to render the prev/next buttons as `<a>` (due to the `to` prop)
  const nextPageProps = computed<Partial<Button> | undefined>(() => {
    const canGoNext = page.value * pageCount.value < state.value.storiesTotal
    return canGoNext ? { to: { query: { page: page.value + 1 } } } : undefined
  })
  const prevPageProps = computed<Partial<Button> | undefined>(() => {
    const canGoPrev = page.value > 1
    return canGoPrev ? { to: { query: { page: page.value - 1 } } } : undefined
  })

  // Data fetch
  // Custom data fetching composable, not important for the example
  const { state, fetchStories } = await useStoryList({
    key: 'ratgeber-alle-artikel',
    init: {
        ...defaultOptions.value,
        page: page.value,
    },
  })
  watch(page, () => {
    fetchStories({
        ...defaultOptions.value,
        page: page.value,
    })
  })
</script>
clopezpro commented 11 months ago

✨ I'm preparing a PR for an advanced example table that is used in something similar to my own component in a real-life project. I'm currently working on it and have rebuilt it using Nuxt Lab UI.

The table features:

  • Auto-generated filter forms (text, radio, select, datepicker).
  • Columns management: sorting, show/hide, and action to save it (either to your backend or local storage).
  • Pagination and metadata for the number of rows.
  • Menu for selecting the number of rows per page.
  • Quick action buttons (e.g., Add).
  • Clear filter and reset action with logic to reset all operations, such as pagination.
  • Loading and empty states.
  • API and server-side integration.

What do you think about providing this as an example? Everyone should be able to wrap it or turn it into single components.

Currently, I've coded 452 lines, and I'm not finished yet!

I have a question regarding mocking or using the API service for this example to simulate these operations. What is your opinion?"

table.mp4

I would love to see this example

aastrum commented 9 months ago

Plese make range from - to, input autocomplete with hints. And it will be also good if you make float label in control fields.