Todos os componentes

Settings Preferences

Settings Preferences é um componente para React e Tailwind CSS, da biblioteca HextaUI, com licença MIT. Copia e cola no teu projeto.

HextaUI MIT other

O que é

Settings Preferences é um componente para React e Tailwind CSS, da biblioteca HextaUI, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Não precisa de pacotes além do que o projeto já tem

Licença

Este componente vem de HextaUI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Settings Preferences

registry/new-york/blocks/settings/settings-preferences.tsx
"use client";

import {
  Calendar,
  Clock,
  Eye,
  Globe,
  Hash,
  Keyboard,
  Languages,
  Layout,
  Loader2,
  Moon,
  Palette,
  Save,
  Sun,
  Type,
  Volume2,
} from "lucide-react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/registry/new-york/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/registry/new-york/ui/card";
import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/registry/new-york/ui/field";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/registry/new-york/ui/select";
import { Separator } from "@/registry/new-york/ui/separator";
import { Switch } from "@/registry/new-york/ui/switch";

export interface PreferencesData {
  theme: "light" | "dark" | "system";
  accentColor?: string;
  fontSize: "small" | "medium" | "large";
  language: string;
  timezone: string;
  dateFormat: string;
  timeFormat: "12h" | "24h";
  numberFormat: string;
  density: "compact" | "comfortable" | "spacious";
  animations: boolean;
  reducedMotion: boolean;
  highContrast: boolean;
  screenReaderAnnouncements: boolean;
  keyboardShortcuts: boolean;
}

export interface SettingsPreferencesProps {
  preferences?: PreferencesData;
  onSave?: (data: PreferencesData) => Promise<void>;
  className?: string;
}

const accentColors = [
  { value: "blue", label: "Blue", color: "bg-blue-500" },
  { value: "green", label: "Green", color: "bg-green-500" },
  { value: "purple", label: "Purple", color: "bg-purple-500" },
  { value: "orange", label: "Orange", color: "bg-orange-500" },
  { value: "red", label: "Red", color: "bg-red-500" },
  { value: "pink", label: "Pink", color: "bg-pink-500" },
];

const languages = [
  { value: "en", label: "English" },
  { value: "es", label: "Spanish" },
  { value: "fr", label: "French" },
  { value: "de", label: "German" },
  { value: "ja", label: "Japanese" },
  { value: "zh", label: "Chinese" },
];

const timezones = [
  { value: "UTC", label: "UTC" },
  { value: "America/New_York", label: "Eastern Time (ET)" },
  { value: "America/Chicago", label: "Central Time (CT)" },
  { value: "America/Denver", label: "Mountain Time (MT)" },
  { value: "America/Los_Angeles", label: "Pacific Time (PT)" },
  { value: "Europe/London", label: "London (GMT)" },
  { value: "Europe/Paris", label: "Paris (CET)" },
  { value: "Asia/Tokyo", label: "Tokyo (JST)" },
];

const dateFormats = [
  { value: "MM/DD/YYYY", label: "MM/DD/YYYY" },
  { value: "DD/MM/YYYY", label: "DD/MM/YYYY" },
  { value: "YYYY-MM-DD", label: "YYYY-MM-DD" },
  { value: "DD MMM YYYY", label: "DD MMM YYYY" },
];

const numberFormats = [
  { value: "1,234.56", label: "1,234.56 (US)" },
  { value: "1.234,56", label: "1.234,56 (EU)" },
  { value: "1 234,56", label: "1 234,56 (FR)" },
];

const defaultPreferences: PreferencesData = {
  theme: "system",
  accentColor: "blue",
  fontSize: "medium",
  language: "en",
  timezone: "UTC",
  dateFormat: "MM/DD/YYYY",
  timeFormat: "12h",
  numberFormat: "1,234.56",
  density: "comfortable",
  animations: true,
  reducedMotion: false,
  highContrast: false,
  screenReaderAnnouncements: true,
  keyboardShortcuts: true,
};

export default function SettingsPreferences({
  preferences = defaultPreferences,
  onSave,
  className,
}: SettingsPreferencesProps) {
  const [isSaving, setIsSaving] = useState(false);
  const [localPreferences, setLocalPreferences] =
    useState<PreferencesData>(preferences);

  const handleSave = async () => {
    setIsSaving(true);
    try {
      await onSave?.(localPreferences);
    } finally {
      setIsSaving(false);
    }
  };

  const updatePreference = <K extends keyof PreferencesData>(
    key: K,
    value: PreferencesData[K]
  ) => {
    setLocalPreferences((prev) => ({ ...prev, [key]: value }));
  };

  return (
    <Card className={cn("w-full shadow-xs", className)}>
      <CardHeader>
        <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
          <div className="flex min-w-0 flex-1 flex-col gap-2">
            <CardTitle className="wrap-break-word">Preferences</CardTitle>
            <CardDescription className="wrap-break-word">
              Customize your app experience and appearance
            </CardDescription>
          </div>
          <div className="flex shrink-0 gap-2">
            <Button
              className="w-full sm:w-auto"
              disabled={isSaving}
              onClick={handleSave}
              type="button"
            >
              {isSaving ? (
                <>
                  <Loader2 className="size-4 animate-spin" />
                  <span className="whitespace-nowrap">Saving…</span>
                </>
              ) : (
                <>
                  <Save className="size-4" />
                  <span className="whitespace-nowrap">Save Changes</span>
                </>
              )}
            </Button>
          </div>
        </div>
      </CardHeader>
      <CardContent>
        <div className="grid gap-6 md:grid-cols-2">
          {/* Appearance Section */}
          <div className="flex flex-col gap-4">
            <div className="flex items-center gap-2">
              <Palette className="size-5 text-muted-foreground" />
              <h3 className="font-semibold text-base">Appearance</h3>
            </div>

            <div className="grid gap-4 md:grid-cols-2">
              {/* Theme Selection */}
              <div className="flex flex-col gap-4 rounded-lg border p-4 md:col-span-2">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Sun className="size-4" />
                  </div>
                  <FieldLabel className="mb-0">Theme</FieldLabel>
                </div>
                <div className="grid grid-cols-3 gap-2">
                  {[
                    { value: "light", label: "Light", icon: Sun },
                    { value: "dark", label: "Dark", icon: Moon },
                    { value: "system", label: "System", icon: Layout },
                  ].map((theme) => {
                    const Icon = theme.icon;
                    return (
                      <button
                        aria-label={`Select ${theme.label} theme`}
                        className={cn(
                          "flex flex-col items-center gap-2 rounded-lg border-2 p-3 transition-all",
                          localPreferences.theme === theme.value
                            ? "border-primary bg-primary/5"
                            : "border-muted hover:border-primary/50"
                        )}
                        key={theme.value}
                        onClick={() =>
                          updatePreference(
                            "theme",
                            theme.value as "light" | "dark" | "system"
                          )
                        }
                        type="button"
                      >
                        <Icon className="size-5" />
                        <span className="font-medium text-xs">
                          {theme.label}
                        </span>
                      </button>
                    );
                  })}
                </div>
              </div>

              {/* Accent Color */}
              <div className="flex flex-col gap-4 rounded-lg border p-4">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Palette className="size-4" />
                  </div>
                  <FieldLabel className="mb-0">Accent Color</FieldLabel>
                </div>
                <div className="flex flex-wrap gap-2">
                  {accentColors.map((color) => (
                    <button
                      aria-label={`Select ${color.label} accent color`}
                      className={cn(
                        "flex size-9 items-center justify-center rounded-full border-2 transition-all",
                        localPreferences.accentColor === color.value
                          ? "scale-110 border-primary ring-2 ring-primary/20"
                          : "border-muted hover:border-primary/50"
                      )}
                      key={color.value}
                      onClick={() =>
                        updatePreference("accentColor", color.value)
                      }
                      type="button"
                    >
                      <div className={cn("size-5 rounded-full", color.color)} />
                    </button>
                  ))}
                </div>
              </div>

              {/* Font Size */}
              <div className="flex flex-col gap-4 rounded-lg border p-4">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Type className="size-4" />
                  </div>
                  <FieldLabel className="mb-0" htmlFor="font-size">
                    Font Size
                  </FieldLabel>
                </div>
                <Select
                  onValueChange={(value: "small" | "medium" | "large") =>
                    updatePreference("fontSize", value)
                  }
                  value={localPreferences.fontSize}
                >
                  <SelectTrigger className="w-full" id="font-size">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="small">Small</SelectItem>
                    <SelectItem value="medium">Medium</SelectItem>
                    <SelectItem value="large">Large</SelectItem>
                  </SelectContent>
                </Select>
              </div>

              {/* Density */}
              <div className="flex flex-col gap-4 rounded-lg border p-4">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Layout className="size-4" />
                  </div>
                  <FieldLabel className="mb-0" htmlFor="density">
                    Display Density
                  </FieldLabel>
                </div>
                <Select
                  onValueChange={(
                    value: "compact" | "comfortable" | "spacious"
                  ) => updatePreference("density", value)}
                  value={localPreferences.density}
                >
                  <SelectTrigger className="w-full" id="density">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="compact">Compact</SelectItem>
                    <SelectItem value="comfortable">Comfortable</SelectItem>
                    <SelectItem value="spacious">Spacious</SelectItem>
                  </SelectContent>
                </Select>
              </div>
            </div>
          </div>

          {/* Language & Region Section */}
          <div className="flex flex-col gap-4">
            <div className="flex items-center gap-2">
              <Globe className="size-5 text-muted-foreground" />
              <h3 className="font-semibold text-base">Language & Region</h3>
            </div>

            <div className="grid gap-4 md:grid-cols-2">
              {/* Language */}
              <div className="flex flex-col gap-4 rounded-lg border p-4">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Languages className="size-4" />
                  </div>
                  <FieldLabel className="mb-0" htmlFor="language">
                    Language
                  </FieldLabel>
                </div>
                <Select
                  onValueChange={(value) => updatePreference("language", value)}
                  value={localPreferences.language}
                >
                  <SelectTrigger className="w-full" id="language">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {languages.map((lang) => (
                      <SelectItem key={lang.value} value={lang.value}>
                        {lang.label}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>

              {/* Timezone */}
              <div className="flex flex-col gap-4 rounded-lg border p-4">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Clock className="size-4" />
                  </div>
                  <FieldLabel className="mb-0" htmlFor="timezone">
                    Timezone
                  </FieldLabel>
                </div>
                <Select
                  onValueChange={(value) => updatePreference("timezone", value)}
                  value={localPreferences.timezone}
                >
                  <SelectTrigger className="w-full" id="timezone">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {timezones.map((tz) => (
                      <SelectItem key={tz.value} value={tz.value}>
                        {tz.label}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>

              {/* Date Format */}
              <div className="flex flex-col gap-4 rounded-lg border p-4">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Calendar className="size-4" />
                  </div>
                  <FieldLabel className="mb-0" htmlFor="date-format">
                    Date Format
                  </FieldLabel>
                </div>
                <Select
                  onValueChange={(value) =>
                    updatePreference("dateFormat", value)
                  }
                  value={localPreferences.dateFormat}
                >
                  <SelectTrigger className="w-full" id="date-format">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {dateFormats.map((format) => (
                      <SelectItem key={format.value} value={format.value}>
                        {format.label}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>

              {/* Time Format */}
              <div className="flex flex-col gap-4 rounded-lg border p-4">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Clock className="size-4" />
                  </div>
                  <FieldLabel className="mb-0" htmlFor="time-format">
                    Time Format
                  </FieldLabel>
                </div>
                <Select
                  onValueChange={(value: "12h" | "24h") =>
                    updatePreference("timeFormat", value)
                  }
                  value={localPreferences.timeFormat}
                >
                  <SelectTrigger className="w-full" id="time-format">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="12h">12-hour (AM/PM)</SelectItem>
                    <SelectItem value="24h">24-hour</SelectItem>
                  </SelectContent>
                </Select>
              </div>

              {/* Number Format */}
              <div className="flex flex-col gap-4 rounded-lg border p-4 md:col-span-2">
                <div className="flex items-center gap-2">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Hash className="size-4" />
                  </div>
                  <FieldLabel className="mb-0" htmlFor="number-format">
                    Number Format
                  </FieldLabel>
                </div>
                <Select
                  onValueChange={(value) =>
                    updatePreference("numberFormat", value)
                  }
                  value={localPreferences.numberFormat}
                >
                  <SelectTrigger className="w-full" id="number-format">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    {numberFormats.map((format) => (
                      <SelectItem key={format.value} value={format.value}>
                        {format.label}
                      </SelectItem>
                    ))}
                  </SelectContent>
                </Select>
              </div>
            </div>
          </div>
        </div>

        <Separator className="my-6" />

        {/* Accessibility Section */}
        <div className="flex flex-col gap-4">
          <div className="flex items-center gap-2">
            <Eye className="size-5 text-muted-foreground" />
            <h3 className="font-semibold text-base">Accessibility</h3>
          </div>

          <div className="grid gap-3 md:grid-cols-2">
            <Field>
              <div className="flex items-center justify-between rounded-lg border p-3">
                <div className="flex items-center gap-3">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Volume2 className="size-4" />
                  </div>
                  <div className="flex flex-col gap-0.5">
                    <FieldLabel className="mb-0" htmlFor="animations">
                      Animations
                    </FieldLabel>
                    <FieldDescription className="text-xs">
                      Smooth transitions
                    </FieldDescription>
                  </div>
                </div>
                <Switch
                  checked={localPreferences.animations}
                  id="animations"
                  onCheckedChange={(checked) =>
                    updatePreference("animations", checked)
                  }
                />
              </div>
            </Field>

            <Field>
              <div className="flex items-center justify-between rounded-lg border p-3">
                <div className="flex items-center gap-3">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Eye className="size-4" />
                  </div>
                  <div className="flex flex-col gap-0.5">
                    <FieldLabel className="mb-0" htmlFor="reduced-motion">
                      Reduced Motion
                    </FieldLabel>
                    <FieldDescription className="text-xs">
                      Respect system preference
                    </FieldDescription>
                  </div>
                </div>
                <Switch
                  checked={localPreferences.reducedMotion}
                  id="reduced-motion"
                  onCheckedChange={(checked) =>
                    updatePreference("reducedMotion", checked)
                  }
                />
              </div>
            </Field>

            <Field>
              <div className="flex items-center justify-between rounded-lg border p-3">
                <div className="flex items-center gap-3">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Eye className="size-4" />
                  </div>
                  <div className="flex flex-col gap-0.5">
                    <FieldLabel className="mb-0" htmlFor="high-contrast">
                      High Contrast
                    </FieldLabel>
                    <FieldDescription className="text-xs">
                      Better visibility
                    </FieldDescription>
                  </div>
                </div>
                <Switch
                  checked={localPreferences.highContrast}
                  id="high-contrast"
                  onCheckedChange={(checked) =>
                    updatePreference("highContrast", checked)
                  }
                />
              </div>
            </Field>

            <Field>
              <div className="flex items-center justify-between rounded-lg border p-3">
                <div className="flex items-center gap-3">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Volume2 className="size-4" />
                  </div>
                  <div className="flex flex-col gap-0.5">
                    <FieldLabel className="mb-0" htmlFor="screen-reader">
                      Screen Reader
                    </FieldLabel>
                    <FieldDescription className="text-xs">
                      Enable announcements
                    </FieldDescription>
                  </div>
                </div>
                <Switch
                  checked={localPreferences.screenReaderAnnouncements}
                  id="screen-reader"
                  onCheckedChange={(checked) =>
                    updatePreference("screenReaderAnnouncements", checked)
                  }
                />
              </div>
            </Field>

            <Field>
              <div className="flex items-center justify-between rounded-lg border p-3">
                <div className="flex items-center gap-3">
                  <div className="flex size-8 items-center justify-center rounded-lg bg-muted">
                    <Keyboard className="size-4" />
                  </div>
                  <div className="flex flex-col gap-0.5">
                    <FieldLabel className="mb-0" htmlFor="keyboard-shortcuts">
                      Keyboard Shortcuts
                    </FieldLabel>
                    <FieldDescription className="text-xs">
                      Faster navigation
                    </FieldDescription>
                  </div>
                </div>
                <Switch
                  checked={localPreferences.keyboardShortcuts}
                  id="keyboard-shortcuts"
                  onCheckedChange={(checked) =>
                    updatePreference("keyboardShortcuts", checked)
                  }
                />
              </div>
            </Field>
          </div>
        </div>
      </CardContent>
    </Card>
  );
}

Componentes parecidos