Soft UI
GitHub

Soft UI: A Neumorphism and Glassmorphism based UI library.

v1.0

Simple.

Smooth.

Clean.

Flexible

Comprehensive neumorphic components for all needs.

Customizable

Adjust colors, shadows, and borders easily.

Responsive

Optimized for all screen sizes.

Utility-First

Apply neumorphic styles efficiently with utilities.

Optimized

Fast and lightweight components for smooth performance.

Clean

Toggle inner and outer shadows effortlessly.

Components

Soft UI provides different components. These are as follows:

LED Lights

onoff
LED Lights

Code

"use client";
import React, { useEffect, useState } from "react";
import clsx from "clsx";

type LEDLightProps = {
  size?: number;
  color?: string;
  className?: string;
  enabled?: boolean;
};

const LEDLight: React.FC<LEDLightProps> = ({
  size = 100,
  color = "red",
  className = "",
  enabled = true,
}) => {
  const [ledColor, setLedColor] = useState("inherit");
  const [shadowColor, setShadowColor] = useState("inherit");

  useEffect(() => {
    if (enabled) {
      setLedColor(color);
      setShadowColor(`${color}, ${color}66`); 
    } else {
      setLedColor("inherit");
      setShadowColor("inherit, inherit"); 
    }
  }, [color, enabled]);

  return (
    <div
      className={clsx(
        "relative flex items-center justify-center rounded-full bg-gray-200",
        "shadow-[inset_3px_3px_6px_rgba(0,0,0,0.2),inset_-3px_-3px_6px_rgba(255,255,255,0.6)]",
        className
      )}
      style={{ width: size, height: size }}
    >
      <div
        className={clsx(
          "absolute rounded-full",
          "shadow-[0px_0px_10px_rgba(0,0,0,0.2)] transition-all duration-300"
        )}
        style={{
          width: size / 1.55,
          height: size / 1.55,
          backgroundColor: ledColor,
          boxShadow: `0 0 10px ${shadowColor}, 0 0 20px ${shadowColor}`,
          filter: "blur(0.8px)",
          transition: "background-color 0.5s ease, box-shadow 0.5s ease",
        }}
      />
    </div>
  );
};

export default LEDLight;

Usage

"use client";
import LEDLight from "@/components/LEDLight";
import Switch from "@/components/Switch";
import { useState } from "react";
import FeatureContainer from "./FeatureContainer";

export default function LEDDemo() {
  const [ledEnabled, setLedEnabled] = useState(true);

  return (
    <FeatureContainer title="LED Lights" id="LED">
      <div className="flex gap-4 items-center">
        <div className="flex items-center gap-4">
          <LEDLight color="#ef4444" size={20} enabled={ledEnabled} />
          <LEDLight color="#fcd34d" size={20} enabled={ledEnabled} />
          <LEDLight color="#4ade80" size={20} enabled={ledEnabled} />
        </div>
        <div>
          <Switch
            size="large"
            enabled={ledEnabled}
            onToggle={() => setLedEnabled(!ledEnabled)}
          />
        </div>
      </div>
    </FeatureContainer>
  );
}

Switch Buttons

onoff
Switch Buttons

Code

"use client";
import React from "react";
import clsx from "clsx";

type SwitchProps = {
  enabled: boolean;
  onToggle: (param: boolean) => void;
  size?: "small" | "medium" | "large";
  className?: string;
};

const Switch: React.FC<SwitchProps> = ({
  enabled,
  onToggle,
  size = "medium",
  className,
}) => {
    
  const sizeStyles = {
    small: {
      switch: "w-10 h-5 rounded-lg",
      circle: "w-3 h-3 rounded-md",
      translate: "translate-x-6",
      text: "text-xs",
    },
    medium: {
      switch: "w-14 h-7 rounded-xl",
      circle: "w-5 h-5 rounded-lg",
      translate: "translate-x-8",
      text: "text-sm",
    },
    large: {
      switch: "w-20 h-10 rounded-2xl",
      circle: "w-8 h-8 rounded-xl",
      translate: "translate-x-11",
      text: "text-base",
    },
  };

  return (
    <div
      className={clsx(
        "relative inline-flex items-center cursor-pointer transition-all duration-300",
        className
      )}
      onClick={() => onToggle(!enabled)}
    >
      <div
        className={clsx(
          "flex items-center justify-between px-1 bg-gradient-to-r from-background-card to-background-card-secondary border border-muted/20 shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.4)]",
          sizeStyles[size].switch
        )}
      >
        <span
          className={clsx(
            sizeStyles[size].text,
            "text-muted font-semibold transition-opacity duration-300 text-sm mx-2",
            enabled ? "opacity-100" : "opacity-0",
            size !== "large" && "hidden"
          )}
        >
          on
        </span>
        <span
          className={clsx(
            sizeStyles[size].text,
            "text-muted font-semibold transition-opacity duration-300 text-sm mx-2",
            !enabled ? "opacity-100" : "opacity-0",
            size !== "large" && "hidden"
          )}
        >
          off
        </span>
      </div>
      <div
        className={clsx(
          "flex items-center justify-center absolute top-1 bg-white shadow-md transition-transform duration-300",
          sizeStyles[size].circle,
          enabled ? sizeStyles[size].translate : "translate-x-1"
        )}
      >
        <div className="h-[50%] rounded-full w-[3px] bg-background-card shadow-sm"></div>
      </div>
    </div>
  );
};

export default Switch;

Usage

"use client";
import Switch from "@/components/Switch";
import { useState } from "react";
import FeatureContainer from "./FeatureContainer";

export default function SwitchDemo() {
  const [switchAEnabled, setSwitchAEnabled] = useState(true);
  const [switchBEnabled, setSwitchBEnabled] = useState(true);
  const [switchCEnabled, setSwitchCEnabled] = useState(true);

  return (
    <FeatureContainer title="Switch Buttons" id="Switch">
      <div>
        <div className="flex items-center gap-4">
          <Switch
            enabled={switchAEnabled}
            onToggle={(val) => setSwitchAEnabled(val)}
            size="large"
          />
          <Switch
            enabled={switchBEnabled}
            onToggle={(val) => setSwitchBEnabled(val)}
            size="medium"
          />
          <Switch
            enabled={switchCEnabled}
            onToggle={(val) => setSwitchCEnabled(val)}
            size="small"
          />
        </div>
      </div>
    </FeatureContainer>
  );
}

LCD Screens

00:00:00
20°C
LCD Screens

Code

import { lcdFont } from "@/lib/fonts";
import Card from "./Card";
import clsx from "clsx";
import { ReactNode } from "react";

type Elevation = "none" | "outside" | "inside" | "mix";

type LCDScreenProps = {
  children: ReactNode;
  className?: string;
  elevation?: Elevation;
  showGrid?: boolean;
};

export default function LCDScreen({
  children,
  className,
  elevation = "inside",
  showGrid = false,
}: LCDScreenProps) {
  return (
    <Card
      variant="lcd"
      className={clsx(
        "relative text-4xl p-3 rounded-md text-center",
        lcdFont.className,
        className
      )}
      elevation={elevation}
    >
      {showGrid && (
        <div
          className="absolute w-full h-full inset-0"
          style={{
            backgroundImage: `
          linear-gradient(to right, rgba(0,0,0,0.1) 1px, transparent 1px),
          linear-gradient(to bottom, rgba(0,0,0,0.1) 1px, transparent 1px)
        `,
            backgroundSize: "10px 10px",
            pointerEvents: "none",
          }}
        ></div>
      )}
      <div>{children}</div>
    </Card>
  );
}

Usage

"use client";
import { useEffect, useRef, useState } from "react";
import FeatureContainer from "./FeatureContainer";
import LCDScreen from "@/components/LCDScreen";

export default function LCDScreenDemo() {
  // CLOCK
  const [hours, setHours] = useState<string>("00");
  const [minutes, setMinutes] = useState<string>("00");
  const [seconds, setSeconds] = useState<string>("00");

  useEffect(() => {
    const timerId = setInterval(() => {
      const currentTime = new Date();

      setHours(addLeadingZero(currentTime.getHours()));
      setMinutes(addLeadingZero(currentTime.getMinutes()));
      setSeconds(addLeadingZero(currentTime.getSeconds()));
    }, 1000);

    return () => clearInterval(timerId);
  }, []);

  const addLeadingZero = (num: number) =>
    num < 10 ? `0${num}` : num.toString();

  // Temperature
  const [temperature, setTemperature] = useState(20);

  useEffect(() => {
    const temperatureId = setInterval(() => {
      setTemperature((prevTemperature) => {
        const newTemperature = prevTemperature + 1;
        if (newTemperature >= 25) {
          return 16;
        }
        return newTemperature;
      });
    }, 1000);

    return () => clearInterval(temperatureId);
  }, []);

  // Wave
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    const width = canvas.width;
    const height = canvas.height;
    const amplitude = 50; // Height of the wave
    const frequency = 0.1; // Frequency of the wave

    let offset = 0;

    const drawWave = () => {
      ctx.clearRect(0, 0, width, height); // Clear the canvas

      ctx.beginPath();
      ctx.moveTo(0, height / 2);

      for (let x = 0; x < width; x++) {
        const y = amplitude * Math.sin(frequency * x + offset) + height / 2;
        ctx.lineTo(x, y);
      }

      ctx.strokeStyle = "#555555";
      ctx.lineWidth = 2;
      ctx.stroke();
    };

    const animate = () => {
      drawWave();
      offset -= 0.05; // Adjust this value to control the speed of the wave
      requestAnimationFrame(animate);
    };

    animate(); // Start the animation
  }, []);

  // Inline styles for the canvas to handle animation
  const canvasStyle: React.CSSProperties = {
    display: "block",
    animation: "moveCanvas 10s linear infinite",
    backgroundColor: "transparent",
  };

  return (
    <FeatureContainer title="LCD Screens" id="LCD">
      <div className="space-y-3">
        <div className="flex gap-4 items-center flex-wrap">
          {/* CLOCK */}
          <LCDScreen className="min-w-[160px]">
            {`${hours}:${minutes}:${seconds}`}
          </LCDScreen>
          {/* TEMPERATURE */}
          <LCDScreen className="min-w-[160px]">{`${temperature}°C`}</LCDScreen>
        </div>
        {/* WAVE */}
        <div>
          <LCDScreen showGrid={true}>
            <canvas
              ref={canvasRef}
              width={200}
              height={120}
              style={canvasStyle}
            />
          </LCDScreen>
        </div>
      </div>
    </FeatureContainer>
  );
}

Sliders

Sliders

Code

import React from "react";
import clsx from "clsx";

type SliderProps = {
  value: number;
  min?: number;
  max?: number;
  step?: number;
  onChange: (value: number) => void;
  size?: "small" | "medium" | "large";
  className?: string;
  progressColor?: string;
};

const Slider: React.FC<SliderProps> = ({
  value,
  min = 0,
  max = 100,
  step = 1,
  onChange,
  size = "medium",
  className,
  progressColor = "red",
}) => {
  const sizeStyles = {
    small: {
      trackHeight: "h-2",
      thumbSize: "w-10 h-8",
      thumbOffset: 20,
    },
    medium: {
      trackHeight: "h-3",
      thumbSize: "w-12 h-10",
      thumbOffset: 20,
    },
    large: {
      trackHeight: "h-4",
      thumbSize: "w-14 h-12",
      thumbOffset: 24,
    },
  };

  const percentage = ((value - min) / (max - min)) * 100;

  const thumbOffset =
    percentage === 0
      ? 0
      : percentage === 100
      ? sizeStyles[size].thumbOffset * 2
      : sizeStyles[size].thumbOffset;

  return (
    <div
      className={clsx(
        "relative flex items-center bg-background-variant px-3 py-2 rounded-full",
        "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.6)]",
        className
      )}
    >
      <div
        className={clsx(
          "relative rounded-full bg-background-variant",
          "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.6)]",
          sizeStyles[size].trackHeight,
          "w-full"
        )}
      >
        <div
          className={clsx("absolute top-0 left-0 rounded-full")}
          style={{
            background: progressColor,
            height: "100%",
            width: `${percentage}%`,
          }}
        />
      </div>

      <div
        className={clsx(
          "absolute top-1/2 transform -translate-y-1/2 rounded-lg border border-white bg-background-variant flex items-center justify-center",
          "shadow-[2px_2px_8px_rgba(0,0,0,0.1),-2px_-2px_8px_rgba(255,255,255,0.7)]",
          sizeStyles[size].thumbSize
        )}
        style={{ left: `calc(${percentage}% - ${thumbOffset}px)` }} // Adjust thumb positioning
      >
        <div className="h-[50%] rounded-full w-[3px] bg-background-card shadow-sm"></div>
      </div>

      <input
        type="range"
        min={min}
        max={max}
        step={step}
        value={value}
        onChange={(e) => onChange(Number(e.target.value))}
        className="absolute w-full h-full opacity-0 cursor-pointer"
      />
    </div>
  );
};

export default Slider;

Usage

"use client";
import { useState } from "react";
import FeatureContainer from "./FeatureContainer";
import Slider from "@/components/Slider";

export default function SliderDemo() {
  const [value1, setValue1] = useState(20);
  const [value2, setValue2] = useState(60);
  const [value3, setValue3] = useState(15);

  return (
    <FeatureContainer title="Sliders" id="Slider">
      <div className="mt-4 space-y-8">
        <Slider
          size="small"
          progressColor="green"
          value={value1}
          onChange={(val) => setValue1(val)}
        />
        <Slider
          size="medium"
          progressColor="red"
          value={value2}
          onChange={(val) => setValue2(val)}
        />
        <Slider
          size="large"
          progressColor="blue"
          value={value3}
          onChange={(val) => setValue3(val)}
        />
      </div>
    </FeatureContainer>
  );
}

Form Fields

Form Fields

Code

import React from "react";
import clsx from "clsx";

type SizeTypes = "small" | "medium" | "large";

interface TextFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
  fieldSize?: SizeTypes;
  startIcon?: React.ReactNode;
  endIcon?: React.ReactNode;
  className?: string;
}

const TextField: React.FC<TextFieldProps> = ({
  fieldSize = "medium",
  startIcon,
  endIcon,
  className = "",
  ...inputProps
}) => {
  const sizeStyles: Record<SizeTypes, string> = {
    small: "text-sm py-1 px-2",
    medium: "text-base py-2 px-3",
    large: "text-lg py-3 px-4",
  };

  const sizeClass = sizeStyles[fieldSize] || sizeStyles.medium;

  return (
    <div
      className={clsx(
        "flex items-center rounded-lg bg-background-variant",
        "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.1),inset_-2px_-2px_5px_rgba(255,255,255,0.7)]",
        sizeClass,
        className
      )}
    >
      {startIcon && <div className="mr-2 text-muted">{startIcon}</div>}
      <input
        className={clsx(
          "flex-1 bg-transparent outline-none",
          startIcon && "pl-1",
          endIcon && "pr-1"
        )}
        {...inputProps}
      />
      {endIcon && <div className="ml-2 text-muted">{endIcon}</div>}
    </div>
  );
};

export default TextField;

import React from "react";
import clsx from "clsx";

type SizeTypes = "small" | "medium" | "large";

interface TextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
  size?: SizeTypes;
  startIcon?: React.ReactNode;
  endIcon?: React.ReactNode;
  className?: string;
}

const TextArea: React.FC<TextAreaProps> = ({
  size = "medium",
  startIcon,
  endIcon,
  className = "",
  ...textareaProps
}) => {
  const sizeStyles: Record<SizeTypes, string> = {
    small: "text-sm py-1 px-2",
    medium: "text-base py-2 px-3",
    large: "text-lg py-3 px-4",
  };

  const sizeClass = sizeStyles[size] || sizeStyles.medium;

  return (
    <div
      className={clsx(
        "flex items-center rounded-lg bg-background-variant",
        "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.1),inset_-2px_-2px_5px_rgba(255,255,255,0.7)]",
        className
      )}
    >
      {startIcon && <div className="mr-2">{startIcon}</div>}
      <textarea
        className={clsx("resize-none flex-1 bg-transparent outline-none", sizeClass)}
        {...textareaProps}
      />
      {endIcon && <div className="ml-2">{endIcon}</div>}
    </div>
  );
};

export default TextArea;

import React from "react";
import clsx from "clsx";

type CheckboxProps = React.InputHTMLAttributes<HTMLInputElement> & {
  label?: string;
  size?: "small" | "medium" | "large";
  className?: string;
};

const Checkbox: React.FC<CheckboxProps> = ({
  checked,
  onChange,
  label,
  size = "medium",
  className = "",
  disabled = false,
  ...rest
}) => {
  const sizeStyles: Record<string, string> = {
    small: "w-4 h-4",
    medium: "w-6 h-6",
    large: "w-8 h-8",
  };

  const checkboxSize = sizeStyles[size] || sizeStyles.medium;

  return (
    <label
      className={clsx(
        "flex items-center cursor-pointer space-x-2",
        className
      )}
    >
      <div
        className={clsx(
          "relative flex items-center justify-center rounded-sm",
          "bg-background-variant shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.6)]",
          checkboxSize,
          {
            "bg-gray-300": disabled,
            "bg-blue-500": checked && !disabled,
          }
        )}
      >
        <input
          type="checkbox"
          checked={checked}
          onChange={(e) => onChange && onChange(e)}
          disabled={disabled}
          className="absolute opacity-0 cursor-pointer"
          {...rest}
        />
        <div
          className={clsx(
            "absolute rounded-sm transition-colors duration-300",
            checked ? "bg-blue-500" : "bg-gray-300",
            checkboxSize
          )}
        >
          {checked && !disabled && (
            <svg
              className="w-full h-full text-white"
              viewBox="0 0 24 24"
              fill="none"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                d="M4 12l4 4 8-8"
                stroke="currentColor"
                strokeWidth="2"
                strokeLinecap="round"
                strokeLinejoin="round"
              />
            </svg>
          )}
        </div>
      </div>
      {label && <span className={clsx("text-base", disabled && "text-gray-500")}>{label}</span>}
    </label>
  );
};

export default Checkbox;

"use client";
import React from "react";
import clsx from "clsx";

type SizeTypes = "small" | "medium" | "large";

type SelectOption = {
  value: string | number;
  label: string;
};

type SelectProps = React.SelectHTMLAttributes<HTMLSelectElement> & {
  options: SelectOption[];
  fieldSize?: SizeTypes;
  className?: string;
};

// Explicitly typing the sizeStyles keys
const sizeStyles: Record<SizeTypes, string> = {
  small: "px-2 py-1 text-sm",
  medium: "px-3 py-2 text-base",
  large: "px-4 py-3 text-lg",
};

const Select: React.FC<SelectProps> = ({
  options,
  fieldSize = "medium",
  disabled = false,
  className,
  ...rest
}) => {
  return (
    <div
      className={clsx(
        "relative inline-block rounded-full",
        "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.6)]",
        className
      )}
    >
      <select
        {...rest}
        disabled={disabled}
        className={clsx(
          "appearance-none rounded-full focus:outline-none w-full",
          sizeStyles[fieldSize], // Use the correct size styles
          "bg-background-variant text-gray-700 cursor-pointer",
          "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.6)]", // Inner shadow
          disabled ? "bg-gray-300 cursor-not-allowed text-gray-500" : ""
        )}
      >
        {options.map((option) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>

      {/* Arrow Icon */}
      <div
        className={clsx(
          "absolute top-1/2 right-3 transform -translate-y-1/2",
          "text-gray-500",
          "pointer-events-none"
        )}
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          className="w-4 h-4"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
        >
          <path d="M6 9l6 6 6-6" />
        </svg>
      </div>
    </div>
  );
};

export default Select;

Usage

"use client";
import { useState } from "react";
import FeatureContainer from "./FeatureContainer";
import TextField from "@/components/TextField";
import { BiLock, BiUser } from "react-icons/bi";
import TextArea from "@/components/TextArea";
import Checkbox from "@/components/CheckBox";
import DatePicker from "@/components/DatePicker";
import TimePicker from "@/components/TimePicker";
import Select from "@/components/Select";

export default function FormFieldsDemo() {
  const [value1, setValue1] = useState("");
  const [value2, setValue2] = useState("");
  const [value3, setValue3] = useState("");

  const [checked, setChecked] = useState(false);

  const [selectedValue1, setSelectedValue1] = useState("option1");
  const [selectedValue2, setSelectedValue2] = useState("option1");
  const [selectedValue3, setSelectedValue3] = useState("option1");

  const options = [
    { value: "option1", label: "Option 1" },
    { value: "option2", label: "Option 2" },
    { value: "option3", label: "Option 3" },
  ];

  return (
    <FeatureContainer title="Form Fields" id="Form">
      <div className="space-y-4">
        <TextField
          startIcon={<BiUser />}
          value={value1}
          onChange={(e) => setValue1(e.target.value)}
          placeholder="Username"
        />
        <TextField
          startIcon={<BiLock />}
          value={value2}
          onChange={(e) => setValue2(e.target.value)}
          placeholder="Password"
          type="password"
        />
        <TextArea
          value={value3}
          onChange={(e) => setValue3(e.target.value)}
          placeholder="Remarks"
          rows={6}
        />
        <Checkbox
          label="I agree with terms and conditions"
          checked={checked}
          onChange={(e) => setChecked(e.target.checked)}
        />
        <div>
          <Select
            value={selectedValue1}
            onChange={(e) => setSelectedValue1(e.target.value)}
            options={options}
            fieldSize="small"
            className="w-64"
          />
        </div>
        <div>
          <Select
            value={selectedValue2}
            onChange={(e) => setSelectedValue2(e.target.value)}
            options={options}
            fieldSize="medium"
            className="w-64"
          />
        </div>
        <div>
          <Select
            value={selectedValue3}
            onChange={(e) => setSelectedValue3(e.target.value)}
            options={options}
            fieldSize="large"
            className="w-64"
          />
        </div>
      </div>
    </FeatureContainer>
  );
}

Date and Time Pickers

Date and Time Pickers

Code

import React from "react";
import clsx from "clsx";

type DatePickerProps = {
  value: Date;
  onChange: (value: Date) => void;
  label?: string;
  disabled?: boolean;
  className?: string;
};

const formatDate = (date: Date): string => {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
};

const parseDate = (dateString: string): Date => {
  const [year, month, day] = dateString.split("-").map(Number);
  return new Date(year, month - 1, day);
};

const DatePicker: React.FC<DatePickerProps> = ({
  value,
  onChange,
  label,
  disabled = false,
  className,
}) => {
  return (
    <div className={clsx("inline-flex flex-col space-y-2", className)}>
      {label && (
        <label
          className={clsx(
            "text-sm font-medium",
            disabled ? "text-gray-500" : "text-gray-700"
          )}
        >
          {label}
        </label>
      )}
      <div
        className={clsx(
          "relative flex items-center px-4 py-2 rounded-lg",
          "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.1),inset_-2px_-2px_5px_rgba(255,255,255,0.7)]",
          disabled
            ? "bg-gray-100 cursor-not-allowed border-gray-300"
            : "bg-background-variant"
        )}
      >
        <input
          type="date"
          value={formatDate(value)}
          onChange={(e) => onChange(parseDate(e.target.value))}
          disabled={disabled}
          className={clsx(
            "w-full bg-transparent outline-none text-gray-700",
            disabled ? "text-gray-500" : "text-gray-700",
            "appearance-none focus:outline-none"
          )}
        />
        <div className="absolute inset-0 rounded-lg bg-transparent pointer-events-none" />
      </div>
    </div>
  );
};

export default DatePicker;

import React from "react";
import clsx from "clsx";

type TimePickerProps = {
  value: Date;
  onChange: (value: Date) => void;
  label?: string;
  disabled?: boolean;
  className?: string;
};

const formatTime = (date: Date): string => {
  const hours = String(date.getHours()).padStart(2, "0");
  const minutes = String(date.getMinutes()).padStart(2, "0");
  return `${hours}:${minutes}`;
};

const parseTime = (timeString: string, currentDate: Date): Date => {
  const [hours, minutes] = timeString.split(":").map(Number);
  const newDate = new Date(currentDate);
  newDate.setHours(hours);
  newDate.setMinutes(minutes);
  return newDate;
};

const TimePicker: React.FC<TimePickerProps> = ({
  value,
  onChange,
  label,
  disabled = false,
  className,
}) => {
  return (
    <div className={clsx("inline-flex flex-col space-y-2", className)}>
      {label && (
        <label
          className={clsx(
            "text-sm font-medium",
            disabled ? "text-gray-500" : "text-gray-700"
          )}
        >
          {label}
        </label>
      )}
      <div
        className={clsx(
          "relative flex items-center px-4 py-2 rounded-lg",
          "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.1),inset_-2px_-2px_5px_rgba(255,255,255,0.7)]",
          disabled
            ? "bg-gray-100 cursor-not-allowed border-gray-300"
            : "bg-background-variant"
        )}
      >
        <input
          type="time"
          value={formatTime(value)}
          onChange={(e) => onChange(parseTime(e.target.value, value))}
          disabled={disabled}
          className={clsx(
            "w-full bg-transparent outline-none text-gray-700",
            disabled ? "text-gray-500" : "text-gray-700",
            "appearance-none focus:outline-none"
          )}
        />
        <div className="absolute inset-0 rounded-lg bg-transparent pointer-events-none" />
      </div>
    </div>
  );
};

export default TimePicker;

Usage

"use client";
import { useState } from "react";
import FeatureContainer from "./FeatureContainer";
import DatePicker from "@/components/DatePicker";
import TimePicker from "@/components/TimePicker";

export default function DateTimePickerDemo() {
  const [date, setDate] = useState(new Date());
  const [time, setTime] = useState(new Date());

  return (
    <FeatureContainer title="Date and Time Pickers" id="DateTime">
      <div className="space-y-4">
        <div>
          <DatePicker value={date} onChange={(val) => setDate(val)} />
        </div>
        <div>
          <TimePicker value={time} onChange={(val) => setTime(val)} />
        </div>
      </div>
    </FeatureContainer>
  );
}

Cards

Default
Default (Inside)
Default (Outside)
Default (Mix)
Air
Air (Inside)
Air (Outside)
Air (Mix)
LCD
LCD (Inside)
LCD (Outside)
LCD (Mix)
Cards

Code

// components/Card.tsx
import React, { ReactNode } from "react";
import clsx from "clsx";

type Elevation = "none" | "outside" | "inside" | "mix";
type Variant = "default" | "air" | "lcd";

type CardProps = {
  children: ReactNode;
  className?: string;
  elevation?: Elevation;
  variant?: Variant;
  rounded?: boolean;
};

const Card: React.FC<CardProps> = ({
  children,
  className = "",
  elevation = "none",
  variant = "default",
  rounded = true,
}) => {
  const elevationClasses = {
    none: "",
    outside:
      "shadow-[2px_2px_8px_rgba(0,0,0,0.15),-2px_-2px_8px_rgba(255,255,255,0.7)]",
    inside:
      "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.6)]",
    mix: "shadow-[0_4px_6px_rgba(0,0,0,0.1),0_1px_3px_rgba(0,0,0,0.1),inset_1px_1px_3px_rgba(255,255,255,0.7),inset_-1px_-1px_3px_rgba(0,0,0,0.1)]",
  };

  const variantClasses = {
    default:
      "bg-gradient-to-r from-background-card to-background-card-secondary border border-muted/20",
    air: "bg-gradient-to-r from-background-card-secondary to-background-card border border-muted/20",
    lcd: "bg-background-card-lcd border border-muted-dark",
  };

  return (
    <div
      className={clsx(
        "p-6",
        elevationClasses[elevation],
        variantClasses[variant],
        rounded ? "rounded-2xl" : "",
        "inline-block",
        className
      )}
    >
      {children}
    </div>
  );
};

export default Card;

Usage

"use client";
import FeatureContainer from "./FeatureContainer";
import Card from "@/components/Card";

export default function CardsDemo() {
  return (
    <FeatureContainer title="Cards" id="Card">
      <div className="gap-4 text-sm font-medium flex flex-wrap text-center">
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="default"
            elevation="none"
          >
            Default
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="default"
            elevation="inside"
          >
            Default (Inside)
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="default"
            elevation="outside"
          >
            Default (Outside)
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="default"
            elevation="mix"
          >
            Default (Mix)
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="air"
            elevation="none"
          >
            Air
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="default"
            elevation="inside"
          >
            Air (Inside)
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="air"
            elevation="outside"
          >
            Air (Outside)
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="air"
            elevation="mix"
          >
            Air (Mix)
          </Card>
        </div>

        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="lcd"
            elevation="none"
          >
            LCD
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="lcd"
            elevation="inside"
          >
            LCD (Inside)
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="lcd"
            elevation="outside"
          >
            LCD (Outside)
          </Card>
        </div>
        <div>
          <Card
            className="w-28 h-28 flex items-center justify-center"
            variant="lcd"
            elevation="mix"
          >
            LCD (Mix)
          </Card>
        </div>
      </div>
    </FeatureContainer>
  );
}

Chips

Default
Inside
Outside
Mix
Default
Inside
Outside
Mix
Chips

Code

import React, { ReactNode } from "react";
import clsx from "clsx";

type Elevation = "none" | "outside" | "inside" | "mix";

type ChipProps = {
  children: ReactNode;
  className?: string;
  elevation?: Elevation;
  rounded?: boolean;
};

const Chip: React.FC<ChipProps> = ({
  children,
  className = "",
  elevation = "none",
  rounded = true,
}) => {
  const elevationClasses = {
    none: "",
    outside:
      "shadow-[2px_2px_8px_rgba(0,0,0,0.15),-2px_-2px_8px_rgba(255,255,255,0.7)]",
    inside:
      "shadow-[inset_2px_2px_5px_rgba(0,0,0,0.15),inset_-2px_-2px_5px_rgba(255,255,255,0.6)]",
    mix: "shadow-[0_4px_6px_rgba(0,0,0,0.1),0_1px_3px_rgba(0,0,0,0.1),inset_1px_1px_3px_rgba(255,255,255,0.7),inset_-1px_-1px_3px_rgba(0,0,0,0.1)]",
  };

  return (
    <div
      className={clsx(
        "px-6 py-2",
        elevationClasses[elevation],
        "bg-gradient-to-r from-background-card to-background-card-secondary border border-muted/20",
        rounded ? "rounded-full" : "rounded-sm",
        "inline-block",
        className
      )}
    >
      {children}
    </div>
  );
};

export default Chip;

Usage

import Chip from "@/components/Chip";
import FeatureContainer from "./FeatureContainer";

export default function ChipsDemo() {
  return (
    <FeatureContainer title="Chips" id="Chip">
      <div className="space-y-4">
        <div className="gap-4 font-medium flex flex-wrap text-center text-sm text-muted">
          <Chip>Default</Chip>
          <Chip elevation="inside">Inside</Chip>
          <Chip elevation="outside">Outside</Chip>
          <Chip elevation="mix">Mix</Chip>
        </div>
        <div className="gap-4 font-medium flex flex-wrap text-center text-sm text-muted">
          <Chip rounded={false}>Default</Chip>
          <Chip rounded={false} elevation="inside">
            Inside
          </Chip>
          <Chip rounded={false} elevation="outside">
            Outside
          </Chip>
          <Chip rounded={false} elevation="mix">
            Mix
          </Chip>
        </div>
      </div>
    </FeatureContainer>
  );
}

Icons

Icons

Code

"use client";
import React from "react";

type IconProps = {
  icon: React.ReactElement;
  size?: number;
  className?: string;
  onClick?: () => void;
};

const Icon: React.FC<IconProps> = ({
  icon,
  size = 24,
  className = "",
  onClick,
}) => {
  const IconElement = React.cloneElement(icon, {
    size,
    className: `inline-block ${className}`,
    style: {
      filter:
        "drop-shadow(1px 1px 2px rgba(255, 255, 255, 0.7)) drop-shadow(-1px -1px 2px rgba(0, 0, 0, 0.2))",
    },
  });

  return (
    <div
      className="inline-flex items-center justify-center"
      style={{ width: size, height: size }}
      onClick={() => onClick?.()}
    >
      {IconElement}
    </div>
  );
};

export default Icon;

Usage

import Icon from "@/components/Icon";
import FeatureContainer from "./FeatureContainer";
import { BiBell, BiChat, BiUser } from "react-icons/bi";

export default function IconsDemo() {
  return (
    <FeatureContainer title="Icons" id="Icon">
      <div className="space-y-4">
        <div className="gap-4 font-medium flex flex-wrap text-muted">
          <Icon icon={<BiBell />} size={70} />
          <Icon icon={<BiUser />} size={70} />
          <Icon icon={<BiChat />} size={70} />
        </div>
        <div className="gap-4 font-medium flex flex-wrap text-muted">
          <Icon icon={<BiBell />} size={50} />
          <Icon icon={<BiUser />} size={50} />
          <Icon icon={<BiChat />} size={50} />
        </div>
        <div className="gap-4 font-medium flex flex-wrap text-muted">
          <Icon icon={<BiBell />} size={30} />
          <Icon icon={<BiUser />} size={30} />
          <Icon icon={<BiChat />} size={30} />
        </div>
      </div>
    </FeatureContainer>
  );
}

Typography

Heading 1

Heading 2

Heading 3

Body 1

Body 2

Caption

Typography

Code

import React, { ReactNode } from "react";
import clsx from "clsx";

type TypographyProps = {
  variant?: "h1" | "h2" | "h3" | "body1" | "body2" | "caption";
  children: ReactNode;
  className?: string;
  noShadow?: boolean;
};

const Typography: React.FC<TypographyProps> = ({
  variant = "body1",
  children,
  className = "",
  noShadow = false,
}) => {
  const baseStyles = {
    h1: "text-2xl sm:text-5xl font-medium text-text",
    h2: "text-xl sm:text-3xl font-medium text-text",
    h3: "text-lg sm:text-2xl font-medium text-text",
    body1: "text-base font-normal text-text",
    body2: "text-sm font-light text-muted",
    caption: "text-xs font-light text-gray-500 text-muted",
  };

  const shadowClass = noShadow ? "" : "text-shadow";

  const Component =
    variant === "h1" || variant === "h2" || variant === "h3" ? variant : "p";

  return (
    <Component className={clsx(baseStyles[variant], shadowClass, className)}>
      {children}
    </Component>
  );
};

export default Typography;

Usage

import FeatureContainer from "./FeatureContainer";
import Typography from "@/components/Typography";

export default function TypographyDemo() {
  return (
    <FeatureContainer title="Typography" id="Typography">
      <div className="space-y-4">
        <Typography variant="h1">Heading 1</Typography>
        <Typography variant="h2">Heading 2</Typography>
        <Typography variant="h3">Heading 3</Typography>
        <Typography variant="body1">Body 1</Typography>
        <Typography variant="body2">Body 2</Typography>
        <Typography variant="caption">Caption</Typography>
      </div>
    </FeatureContainer>
  );
}

Buttons

Buttons

Code

import React from "react";
import clsx from "clsx";

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  label: string;
  size?: "small" | "medium" | "large";
  loading?: boolean;
  disabled?: boolean;
  className?: string;
};

// Size styles
const sizeStyles = {
  small: "px-4 py-2 text-sm",
  medium: "px-6 py-3 text-base",
  large: "px-8 py-4 text-lg",
};

const Button: React.FC<ButtonProps> = ({
  label,
  size = "medium",
  loading = false,
  disabled = false,
  className,
  ...rest
}) => {
  return (
    <button
      {...rest}
      disabled={disabled || loading}
      className={clsx(
        "rounded-md focus:outline-none transition-all duration-300",
        sizeStyles[size],
        "relative inline-flex justify-center items-center",
        "shadow-[2px_2px_8px_rgba(0,0,0,0.1),-2px_-2px_8px_rgba(255,255,255,0.7)]", // Neumorphism shadow
        disabled || loading
          ? "bg-gray-300 text-gray-500 cursor-not-allowed"
          : "bg-background-variant text-gray-700 hover:shadow-md",
        className
      )}
    >
      {loading ? (
        <div className="w-5 h-5 border-4 border-t-transparent border-gray-400 rounded-full animate-spin"></div> // Loading spinner
      ) : (
        <span>{label}</span>
      )}
    </button>
  );
};

export default Button;

Usage

"use client";
import Button from "@/components/Button";
import FeatureContainer from "./FeatureContainer";
import { useState } from "react";

export default function ButtonDemo() {
  const [loading, setLoading] = useState(false);

  const handleClick = () => {
    setLoading(true);
    setTimeout(() => setLoading(false), 2000); // Simulate a loading state for 2 seconds
  };

  return (
    <FeatureContainer title="Buttons" id="Button">
      <div className="space-y-4">
        <div>
          <Button
            label="Submit"
            size="medium"
            loading={loading}
            onClick={handleClick}
            className="w-32"
          />
        </div>
        <div>
          <Button label="Cancel" size="small" className="w-24" />
        </div>
        <div>
          <Button label="Large Button" size="large" className="w-fit" />
        </div>
      </div>
    </FeatureContainer>
  );
}
Made by Ishant, contact developerishant710@gmail.com