Skip to content

Slider visualizationComponent custom #27

@AgneauAntoine

Description

@AgneauAntoine

Un petit template sympa qui peut être mis à dispo pour remplacer l'exemple Advanced Sliders sur formedible.dev

Image

RatingComponent.tsx :

"use client";
import React from 'react';
import { cn } from '@/lib/utils';

export type ColorScheme = 'dpe' | 'ges';

interface EnergyRatingComponentProps {
  displayValue: string | number;
  isActive: boolean;
  colorScheme?: ColorScheme; // Nouveau prop pour choisir le schéma de couleurs
}

export const EnergyRatingComponent: React.FC<EnergyRatingComponentProps> = ({
  displayValue,
  isActive,
  colorScheme = 'dpe', // Valeur par défaut
}) => {
  // Color mapping for each DPE rating
  const getColorClassesDpe = (rating: string | number) => {
    const ratingStr = rating.toString().toUpperCase();
    switch (ratingStr) {
      case 'A':
        return {
          bg: 'bg-green-500',
          text: 'text-white',
          border: 'border-green-600',
        };
      case 'B':
        return {
          bg: 'bg-lime-500',
          text: 'text-white',
          border: 'border-lime-600',
        };
      case 'C':
        return {
          bg: 'bg-yellow-500',
          text: 'text-white',
          border: 'border-yellow-600',
        };
      case 'D':
        return {
          bg: 'bg-orange-500',
          text: 'text-white',
          border: 'border-orange-600',
        };
      case 'E':
        return {
          bg: 'bg-red-500',
          text: 'text-white',
          border: 'border-red-600',
        };
      case 'F':
        return {
          bg: 'bg-red-700',
          text: 'text-white',
          border: 'border-red-800',
        };
      case 'G':
        return {
          bg: 'bg-red-900',
          text: 'text-white',
          border: 'border-red-1000',
        };
      default:
        return {
          bg: 'bg-gray-500',
          text: 'text-white',
          border: 'border-gray-600',
        };
    }
  };

  // Color mapping for each GES rating
  const getColorClassesGes = (rating: string | number) => {
    const ratingStr = rating.toString().toUpperCase();
    switch (ratingStr) {
      case 'A':
        return {
          bg: 'bg-purple-200',
          text: 'text-black',
          border: 'border-purple-200',
        };
      case 'B':
        return {
          bg: 'bg-purple-300',
          text: 'text-black',
          border: 'border-purple-300',
        };
      case 'C':
        return {
          bg: 'bg-purple-400',
          text: 'text-black',
          border: 'border-purple-400',
        };
      case 'D':
        return {
          bg: 'bg-purple-500',
          text: 'text-black',
          border: 'border-purple-500',
        };
      case 'E':
        return {
          bg: 'bg-purple-600',
          text: 'text-black',
          border: 'border-purple-600',
        };
      case 'F':
        return {
          bg: 'bg-purple-700',
          text: 'text-black',
          border: 'border-purple-700',
        };
      case 'G':
        return {
          bg: 'bg-purple-900',
          text: 'text-black',
          border: 'border-purple-900',
        };
      default:
        return {
          bg: 'bg-gray-500',
          text: 'text-black',
          border: 'border-gray-600',
        };
    }
  };

  // Sélection du schéma de couleurs basé sur le prop
  const colors = colorScheme === 'ges'
    ? getColorClassesGes(displayValue)
    : getColorClassesDpe(displayValue);

  const rating = displayValue.toString().toUpperCase();

  // Get energy efficiency description
  const getDescription = (rating: string) => {
    switch (rating) {
      case 'A':
        return 'Excellent';
      case 'B':
        return 'Très bien';
      case 'C':
        return 'Bien';
      case 'D':
        return 'Moyen';
      case 'E':
        return 'Médiocre';
      case 'F':
        return 'Mauvais';
      case 'G':
        return 'Très mauvais';
      default:
        return '';
    }
  };

  // Get energy consumption range (approximate kWh/m²/an for DPE or kg CO2/m²/an for GES)
  const getEnergyRange = (rating: string) => {
    if (colorScheme === 'ges') {
      // GES ranges in kg CO2/m²/an
      switch (rating) {
        case 'A':
          return '≤ 5';
        case 'B':
          return '6-10';
        case 'C':
          return '11-20';
        case 'D':
          return '21-35';
        case 'E':
          return '36-55';
        case 'F':
          return '56-80';
        case 'G':
          return '> 80';
        default:
          return '';
      }
    } else {
      // DPE ranges in kWh/m²/an
      switch (rating) {
        case 'A':
          return '≤ 50';
        case 'B':
          return '51-90';
        case 'C':
          return '91-150';
        case 'D':
          return '151-230';
        case 'E':
          return '231-330';
        case 'F':
          return '331-420';
        case 'G':
          return '> 420';
        default:
          return '';
      }
    }
  };

  // Get the unit based on color scheme
  const getUnit = () => {
    return colorScheme === 'ges' ? 'KGeqCO2/m²/an' : 'kWh/m²/an';
  };

  return (
    <div className="flex flex-col items-center space-y-2 transition-all duration-300">
      {/* Energy rating badge */}
      <div
        className={cn(
          'relative flex items-center justify-center',
          'w-12 h-12 rounded-lg border-2 font-bold text-lg',
          'transition-all duration-300 transform',
          colors.bg,
          colors.text,
          colors.border,
          isActive ? [
            'scale-125',
            'ring-2 ring-white ring-opacity-60',
            'z-10'
          ] : [
            'scale-100 hover:scale-110',
          ]
        )}
      >
        {/* Animated background pulse when active */}
        {isActive && (
          <div
            className={cn(
              'absolute inset-0 rounded-lg animate-pulse',
              colors.bg,
              'opacity-20'
            )}
          />
        )}

        {/* Energy icon */}
        <div className="absolute -top-1 -right-1">
          {rating === 'A' ? (
            // first place
            <div
              className={cn(
                  'w-5 h-5',
                )}>🤩</div>
          ) : rating === 'B' ? (
            // second place
            <div
              className={cn(
                  'w-5 h-5',
                )}>😁​</div>
          ) : rating === 'C' ? (
            // third place
            <div
              className={cn(
                  'w-5 h-5',
                )}>😅</div>
          ) : rating === 'F' ? (
            // Warning icon for poor ratings
            <div
              className={cn(
                  'w-5 h-5',
                )}>😨</div>
          ) : rating === 'G' ? (
            // Warning icon for poor ratings
            <div
              className={cn(
                  'w-5 h-5',
                )}>😰</div>
          ) : null}
        </div>

        {rating}
      </div>

      {/* Description and energy range when active */}
      <div
        className={cn(
          'text-center transition-all duration-300',
          isActive ? 'opacity-100 transform translate-y-0' : 'opacity-60 transform translate-y-1'
        )}
      >
        <div className={cn(
          'text-xs font-medium',
          isActive ? 'text-foreground' : 'text-muted-foreground'
        )}>
          {getDescription(rating)}
        </div>
        {isActive && (
          <div className="text-xs text-muted-foreground mt-1">
            {getEnergyRange(rating)} {getUnit()}
          </div>
        )}
      </div>

      {/* Connection line to slider */}
      <div
        className={cn(
          'w-0.5 h-4 transition-all duration-300',
          isActive ? colors.bg : 'bg-border'
        )}
      />
    </div>
  );
};

Page.tsx :

"use client"

import { useFormedible } from "@/hooks/use-formedible";
import React from 'react';
import { z } from 'zod';
import { Card, CardContent } from "@/components/ui/card";
import { EnergyRatingComponent, type ColorScheme } from '@/components/formedible/custom/RatingComponent';

const MySchema = z.object({
  dpe: z.number().min(1).max(7),
  ges: z.number().min(1).max(7),
});

type MyFormValues = z.infer<typeof MySchema>;

export default function MyForm() {
  const { Form } = useFormedible<MyFormValues>({
    schema: MySchema,

    fields: [
      // DPE (Diagnostic de Performance Énergétique)
      {
        section: { title: "🚥 Performance énergétique" },
        name: "dpe",
        type: "slider",
        label: "DPE (Diagnostic de Performance Énergétique) *",
        description: "Classe énergétique du logement",
        sliderConfig: {
          min: 1,
          max: 7,
          step: 1,
          valueMapping: [
            { sliderValue: 1, displayValue: "A" },
            { sliderValue: 2, displayValue: "B" },
            { sliderValue: 3, displayValue: "C" },
            { sliderValue: 4, displayValue: "D" },
            { sliderValue: 5, displayValue: "E" },
            { sliderValue: 6, displayValue: "F" },
            { sliderValue: 7, displayValue: "G" },
          ],
          visualizationComponent: (props) => (
            <EnergyRatingComponent {...props} colorScheme="dpe" />
          ),
          showValue: true,
          showTooltip: false,
          showTicks: false,
        },
        validation: z.number().min(1).max(7),
      },

      // GES (Gaz à Effet de Serre)
      {
        section: { title: "🌍 Émissions de GES" },
        name: "ges",
        type: "slider",
        label: "GES (Émissions de Gaz à Effet de Serre) *",
        description: "Classe d'émissions de CO2 du logement",
        sliderConfig: {
          min: 1,
          max: 7,
          step: 1,
          valueMapping: [
            { sliderValue: 1, displayValue: "A" },
            { sliderValue: 2, displayValue: "B" },
            { sliderValue: 3, displayValue: "C" },
            { sliderValue: 4, displayValue: "D" },
            { sliderValue: 5, displayValue: "E" },
            { sliderValue: 6, displayValue: "F" },
            { sliderValue: 7, displayValue: "G" },
          ],
          visualizationComponent: (props) => (
            <EnergyRatingComponent {...props} colorScheme="ges" />
          ),
          showValue: true,
          showTooltip: false,
          showTicks: false,
        },
        validation: z.number().min(1).max(7),
      },
    ],

    formOptions: {
      defaultValues: {
        dpe: 3,
        ges: 4,
      },

      onSubmit: async ({ value }) => {
        console.log("Données du formulaire soumises:", value);

        try {
          // Simulate API call
          await new Promise(resolve => setTimeout(resolve, 1000));

          // Success notification
          alert("Formulaire soumis avec succès ! Vos informations ont été enregistrées.");

          // Clear stored form data on success
          localStorage.removeItem("real-estate-form-draft");

        } catch (error) {
          console.error("Erreur lors de la soumission:", error);
          alert("Une erreur s'est produite. Veuillez réessayer.");
        }
      }
    },
  });

  return (
    <div className="min-h-screen bg-background p-6">
      <div className="max-w-6xl mx-auto">
        <h1 className="text-3xl font-bold text-center mb-8">Formulaire</h1>
        <Card
          className="bg-gradient-to-br from-chart-2 to-chart-3 border-0 shadow-2xl overflow-hidden hover:shadow-3xl transition-all duration-500 p-4"
        >
          <CardContent className="">
            <Form className="max-w-6xl mx-auto" />
          </CardContent>
        </Card>
      </div>
    </div>
  );
}

Enjoy :)

PS : Désolé je ne peux pas attacher directement mes fichiers à l'issue, il ne veut pas des .tsx !

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions