Ayarlar

Cursor ve Claude gibi düzenlenmiş, bir yapay zeka ürünü için ayarlar. Arama, gruplar ve harici bağlantılı dolu bir kenar çubuğu, sakin seçiciler ve iç içe seçenekler içeren satır kartları, yalnızca bir şey değiştiğinde yükselen koyu bir kaydetme adası, kaydetmek için ⌘S, kontrollerinizden ya da sunucunuzdan gelen alan hataları ve içeriğin şeklini alan yükleme durumları.

Cursor, Claude ve Codex aynı ayarlar sayfasında anlaştı: arama ve bir avuç gruplanmış bölüm içeren dolu bir kenar çubuğu, sağda ise solda etiket ve açıklama, sağda sakin bir kontrol bulunan satır kartları. Settings bu sayfadır. Bölümlerinizi tutar ve her ayarlar sayfasının yanlış yaptığı kısımları ele alır: düzenlemelerin kaybolması, iki kez kaydetme ve masaüstündeki kenar çubuğundan telefondaki bir listeye geçiş.

Satırlar herhangi bir kontrolü alır. SettingsSelect, bu uygulamaların kullandığı kompakt değer seçicidir; bir seçenek menüsü açan küçük çerçeveli bir düğme. SettingsNumber ise basılı tutarak tekrar ettirebileceğiniz bir artırıcıdır. İkisi de satırıyla adlandırılır; böylece ekran okuyucular "Chat font, Serif" duyar. SettingsLink, başka bir şey açan bir satırdır; uygulamadan çıkan bağlantılar için şeron ya da ok içerir. SettingsNested, bir anahtarın altında bağımlı seçenekleri kaydırarak açar, örneğin Run code altında ağ erişimi. Arama, kenar çubuğunu etikete, açıklamaya ve anahtar kelimelere göre filtreler ve Enter ilk eşleşmeyi açar. SettingsChoice bir seçimi resimli kartlara çevirir; böylece insanlar bir temayı ya da yoğunluğu nasıl göründüğüne göre seçer.

Siz söyleyene kadar hiçbir şey kaydedilmez. Bir değer kayıtlı olandan farklılaşır farklılaşmaz, alttan Discard ve Save içeren koyu bir ada yükselir ve bölümün kenar çubuğundaki girdisi bir nokta alır. Geri değiştirirseniz çubuk kaybolur. Başka bir bölüm açmayı, telefonda geri gitmeyi ya da sekmeyi kapatmayı denerseniz geçiş engellenir: çubuk sallanır ve önce kaydet ya da vazgeç der, tarayıcı da sekme kapanmadan önce sorar. ⌘S veya Ctrl+S her yerden kaydeder.

Kaydetme ilerlemesini düğmede gösterir, ardından ada bir Saved işaretine küçülür ve kayarak gider. Kontrolleriniz başarısız olursa alanlar hatalarını gösterir, odak ilkine geçer ve çubuk kaç tanesinin düzeltilmesi gerektiğini söyler. Sunucu hayır derse alanlar için hata döndürün ya da hata fırlatın; taslak tam yazıldığı gibi kalır. Kaydederken yazmaya devam ederseniz çubuk daha yeni düzenlemeler için açık kalır.

Telefonda kenar çubuğu, açıklamalar ve şeronlarla gruplanmış bir listeye dönüşür. Bir bölüme dokunmak onu geri düğmesiyle listenin üzerine kaydırır ve odak başlığına geçer. Bir bölümün verisi yüklenirken anahtar satırları şeklinde bir skeleton ya da skeleton prop'uyla kendi skeleton'unuz gösterilir; yükleme başarısız olursa Try again ile bir hata gösterilir.

  1. Pro registry'yi components.json dosyasına ekleyin

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token'ınızı ekleyin

    Hesap sayfanızda bir token oluşturun ve .env.local dosyasına HEXTAUI_PRO_TOKEN olarak ekleyin.

  3. Bloğu ekleyin

    pnpm dlx shadcn@latest add @hextaui-pro/settings

Bir bölümü API'nize bağlayın

useSettingsForm, ilettiğiniz değerlerin bir taslağını tutar. Alan hatalarını alanın altında göstermek için onSave'den döndürün ya da mesajı kaydetme çubuğunda göstermek için hata fırlatın. Her iki durumda da taslak korunur.

"use client"

import { IconAdjustmentsHorizontal, IconUserCircle } from "@tabler/icons-react"

import { Input } from "@/components/ui/input"
import { Switch } from "@/components/ui/switch"

import {
  SettingsGroup,
  SettingsRow,
  SettingsSection,
  SettingsShell,
  useSettingsForm,
  type SettingsSectionItem,
} from "@/components/blocks/settings/settings"

const sections: SettingsSectionItem[] = [
  { id: "profile", label: "Profile", icon: <IconUserCircle />, group: "Account" },
  { id: "preferences", label: "Preferences", icon: <IconAdjustmentsHorizontal />, group: "Account" },
]

type Profile = { name: string; digest: boolean }

function ProfileForm({ profile }: { profile: Profile }) {
  const form = useSettingsForm({
    values: profile,
    validate: (values) => (values.name.trim() ? {} : { name: "Enter your name." }),
    onSave: async (values) => {
      const response = await fetch("/api/profile", {
        method: "PATCH",
        body: JSON.stringify(values),
      })
      if (response.status === 409) return { name: "That name is taken." }
      if (!response.ok) throw new Error("Check your connection and try again.")
    },
  })

  return (
    <SettingsGroup>
      <SettingsRow label="Name" error={form.errors.name}>
        <Input
          value={form.values.name}
          onChange={(event) => form.setValue("name", event.target.value)}
          className="@md/field-group:w-64"
        />
      </SettingsRow>
      <SettingsRow label="Weekly digest" description="A summary every Monday." layout="inline">
        <Switch
          checked={form.values.digest}
          onCheckedChange={(checked) => form.setValue("digest", checked)}
        />
      </SettingsRow>
    </SettingsGroup>
  )
}

export function SettingsPage({ profile }: { profile: Profile }) {
  return (
    <SettingsShell sections={sections} className="h-svh">
      <SettingsSection id="profile">
        <ProfileForm profile={profile} />
      </SettingsSection>
      <SettingsSection id="preferences">{null}</SettingsSection>
    </SettingsShell>
  )
}

Bölüm başına bir route

Her bölüme kendi URL'sini vermek için etkin bölümü value ve onValueChange ile kontrol edin. Kabuk, bir şey kaydedilmemişken geçişi yine engeller; bu yüzden onValueChange yalnızca ayrılmak güvenliyken tetiklenir.

"use client"

import { usePathname, useRouter } from "next/navigation"

import {
  SettingsSection,
  SettingsShell,
  type SettingsSectionItem,
} from "@/components/blocks/settings/settings"

const sections: SettingsSectionItem[] = [
  { id: "profile", label: "Profile", group: "Account" },
  { id: "billing", label: "Billing", group: "Workspace" },
]

export function SettingsLayout({ children }: { children: React.ReactNode }) {
  const router = useRouter()
  const section = usePathname().split("/").at(-1) ?? "profile"

  return (
    <SettingsShell
      sections={sections}
      value={section}
      onValueChange={(id) => router.push(`/settings/${id}`)}
      className="h-svh"
    >
      <SettingsSection id={section}>{children}</SettingsSection>
    </SettingsShell>
  )
}

Yükleme ve hatalar

Bir bölümün verisi yüklenirken status iletin. Skeleton 150ms bekler; böylece hızlı yüklemeler asla yanıp sönmez ve hata durumu onRetry ile Try again sunar.

"use client"

import * as React from "react"

import { Switch } from "@/components/ui/switch"

import { SettingsGroup, SettingsRow, SettingsSection, useSettingsForm } from "@/components/blocks/settings/settings"

type Alerts = { invoices: boolean; overage: boolean }

function AlertsForm({ alerts }: { alerts: Alerts }) {
  const form = useSettingsForm({
    values: alerts,
    onSave: async (values) => {
      const response = await fetch("/api/alerts", { method: "PUT", body: JSON.stringify(values) })
      if (!response.ok) throw new Error("Try again in a moment.")
    },
  })

  return (
    <SettingsGroup>
      <SettingsRow label="Invoices" layout="inline">
        <Switch
          checked={form.values.invoices}
          onCheckedChange={(checked) => form.setValue("invoices", checked)}
        />
      </SettingsRow>
      <SettingsRow label="Usage over 80%" layout="inline">
        <Switch
          checked={form.values.overage}
          onCheckedChange={(checked) => form.setValue("overage", checked)}
        />
      </SettingsRow>
    </SettingsGroup>
  )
}

export function AlertsSection() {
  const [alerts, setAlerts] = React.useState<Alerts | null>(null)
  const [failed, setFailed] = React.useState(false)

  const load = React.useCallback(() => {
    setFailed(false)
    fetch("/api/alerts")
      .then((response) => (response.ok ? response.json() : Promise.reject(response)))
      .then(setAlerts)
      .catch(() => setFailed(true))
  }, [])

  React.useEffect(load, [load])

  return (
    <SettingsSection
      id="alerts"
      status={failed ? "error" : alerts ? "ready" : "loading"}
      onRetry={load}
    >
      {alerts ? <AlertsForm alerts={alerts} /> : null}
    </SettingsSection>
  )
}

Yapı

Dıştan içe doğru birleştirdiğiniz parçalar.

ParçaAçıklama
SettingsShellSayfa: bölüm gezintisi, içerik sütunu, kaydetme çubuğu ve kaydedilmemiş değişikliklerle ayrılmaya karşı koruma.
SettingsSectionBir bölüm. Yalnızca açıkken, başlığı, isteğe bağlı eylemleri ve yükleme veya hata durumlarıyla render edilir.
SettingsGroupSatırlardan oluşan, grup hakkında bir not için isteğe bağlı alt bilgisi olan başlıklı bir kart.
SettingsRowEkran okuyucular için birbirine bağlı bir etiket, açıklama ve kontrol; altında alan hatası.
SettingsSelectKısa bir listeden tek bir değer için sakin bir seçici.
SettingsLinkBir sayfayı, iletişim kutusunu ya da harici bir bağlantıyı açan satır.
SettingsNestedÜst anahtar açıkken kayarak açılan bağımlı seçenekler.
SettingsNumberBasılı tutulduğunda tekrar eden − ve + içeren, Base UI'nin Number Field'ı üzerine kurulu bir sayı artırıcı.
SettingsChoiceTema ya da yoğunluk gibi tek bir seçeneği seçmek için, radio semantiğine sahip resimli kartlar.
SettingsSkeletonGrup başına satır sayısı ve kontrol şekliyle yapılandırılabilen yükleme yer tutucusu.
useSettingsFormBir bölümün taslağı. Neyin değiştiğini izler, doğrular, kaydeder ve bölümü kaydetme çubuğuna bağlar.
useSettingsNavigateİçerikten bir bölüm açar, kenar çubuğu gibi korunur.

SettingsShell

Ayrıca her div prop'unu kabul eder.

PropTürVarsayılan
sections{ id, label, description?, icon?, group?, keywords?, href? }. Aynı group değerine sahip ardışık öğeler bir başlığı paylaşır. keywords aramanın bir bölümü bulmasına yardım eder ve href öğeyi harici bir bağlantı yapar.
SettingsSectionItem[]–
valueSiz kontrol ettiğinizde açık olan bölüm.
string–
defaultValueBaşlangıçta açık olan bölüm.
stringfirst section
onValueChangeBiri başka bir bölüm açtığında çağrılır. Bir şey kaydedilmemişken ya da kaydedilirken asla çağrılmaz.
(value: string) => void–
titleGezintinin üstündeki sayfa başlığı ve telefonlarda geri düğmesinin etiketi.
ReactNode"Settings"
descriptionBaşlığın altında bir satır.
ReactNode–
navHeaderKenar çubuğunun üstündeki içerik, uygulamaya dönen bir Back bağlantısı gibi.
ReactNode–
searchableBölümlerin üstüne bir arama alanı ekler.
booleanfalse
navFooterKenar çubuğunun altına sabitlenmiş içerik, oturum açmış kullanıcı gibi.
ReactNode–
groupLabelsHer grubun adını üstünde gösterir. Grupları yalnızca boşlukla ayırmak için kapatın; adlar yine de ekran okuyucular için grupları etiketler.
booleantrue

SettingsSection

Ayrıca her section prop'unu kabul eder.

PropTürVarsayılan
idsections içindeki bir id ile eşleşir.
string–
titleBaşlık.
ReactNodethe section's label
descriptionBaşlığın altındaki satır.
ReactNodethe section's description
actionsBaşlığın yanındaki düğmeler.
ReactNode–
statuschildren yerine bir skeleton ya da hata gösterir.
"ready" | "loading" | "error""ready"
skeletonstatus loading iken ne gösterileceği.
ReactNode<SettingsSkeleton />
errorHata durumu için mesaj.
ReactNode–
onRetryHata durumuna Try again ekler.
() => void–
PropTürVarsayılan
titleKartın üstündeki başlık.
ReactNode–
descriptionBaşlığın altında, grubun ne hakkında olduğunu anlatan soluk bir satır.
ReactNode–
footerKartın altında, bir değişikliğin neyi etkilediği gibi notlar için soluk bir şerit.
ReactNode–
PropTürVarsayılan
labelSatırın içindeki kontrolü etiketler.
ReactNode–
descriptionKontrolle birlikte okunan yardım metni.
ReactNode–
errorKontrolü geçersiz olarak işaretler ve mesajı satırın altında gösterir.
string–
layoutauto, kart genişken kontrolü etiketin yanına, darken altına koyar. inline, anahtarlar için kontrolü etiketin yanında tutar. stacked, metin alanları için her zaman altına koyar.
"auto" | "inline" | "stacked""auto"
disabledSatırın alanını devre dışı bırakır.
booleanfalse

SettingsSelect

Ayrıca her Button prop'unu kabul eder.

PropTürVarsayılan
valueSeçilen değer.
string–
onValueChangeYeni değerle çağrılır.
(value: string) => void–
optionsSeçenekler, sırayla.
{ value, label }[]–

SettingsChoice

Bir radio group; böylece ok tuşları kartlar arasında gezinir. Ayrıca her Base UI RadioGroup prop'unu kabul eder.

PropTürVarsayılan
valueSeçilen seçenek.
string–
onValueChangeYeni seçenekle çağrılır.
(value: string) => void–
optionsHer kartın resmi ve altındaki ad.
{ value, label, preview }[]–
columnsSatır başına kart. Satır dar olduğunda 4, 2'ye düşer.
2 | 3 | 43
ratio16:10 önizlemeler, daha kısa olanlar için 2:1.
"card" | "wide""card"

SettingsNumber

Ayrıca format ve smallStep gibi her Base UI NumberField.Root prop'unu kabul eder.

PropTürVarsayılan
valueGeçerli sayı.
number | null–
onValueChangeSayı değiştikçe çağrılır.
(value: number | null) => void–
minEn düşük değer. − düğmesi orada devre dışı kalır.
number–
maxEn yüksek değer. + düğmesi orada devre dışı kalır.
number–
stepHer basış ya da ok tuşunun değeri ne kadar değiştirdiği.
number1

Ayrıca her anchor prop'unu kabul eder. href yoksa bir düğme render eder.

PropTürVarsayılan
labelSatırın başlığı.
ReactNode–
descriptionBaşlığın altında bir satır.
ReactNode–
externalhref'i yeni sekmede açar ve şeron yerine ok gösterir.
booleanfalse
PropTürVarsayılan
openSeçenekleri gösterir. Genellikle üst anahtarın değeri.
boolean–
PropTürVarsayılan
groupsHer yer tutucu grubunun kaç satırı olduğu.
number[][3, 2]
controlHer satırın sağındaki şekil.
"switch" | "select" | "input""switch"

useSettingsForm

{ values, setValue, errors, dirty, status, save, discard } döndürür.

PropTürVarsayılan
valuesŞu an kayıtlı olan. Değiştiğinde ve düzenleme yoksa taslak onu izler.
Values–
onSaveTaslağı kaydeder. Alan hatalarını göstermek için { field: message } döndürün ya da mesajı kaydetme çubuğunda göstermek için hata fırlatın.
(values) => void | errors | Promise<void | errors>–
validateonSave'den önce çalışır. Herhangi bir hata kaydetmeyi durdurur ve ilk geçersiz alana odaklanır.
(values) => errors | undefined–

useSettingsNavigate

Kabuğun içinde herhangi bir yerden bir bölüm açan bir fonksiyon döndürür, örneğin bir banner'ın Open düğmesi. Kaydedilmemiş değişikliklere kenar çubuğuyla aynı şekilde saygı gösterir.

PropTürVarsayılan
navigateBölümü açar ya da bir şey kaydedilmemişse kaydetme çubuğunu sallar.
(id: string) => void–
TuşAction
TabGezinti, ardından bölüm, ardından açıksa kaydetme çubuğu arasında gezinir.
EnterOdaktaki bölümü açar.
↑↓Bir artırıcıda sayıyı bir adım değiştirir. Shift on adım değiştirir.
EnterArama alanında ilk eşleşen bölümü açar. Escape aramayı temizler.
⌘SKaydedilmemiş bir şey varken kaydeder. Windows ve Linux'ta Ctrl+S.
  • Gezinti bir landmark'tır ve açık bölüm geçerli sayfa olarak işaretlenir.
  • Her bölüm, başlığıyla adlandırılmış bir bölgedir. Telefonlarda bir bölüm açıldığında odak başlığa, geri gidildiğinde satırına döner.
  • Satırlar Field kullanır; böylece etiketler, açıklamalar ve hatalar kontrole bağlanır.
  • Engellenen gezinme polite olarak duyurulur ve başarısız bir kaydetme alert olarak duyurulur.
  • Kaydetme çubuğu ve gizli paneller etkisizdir; bu yüzden sekme sırasının dışındadır ve ekran okuyuculardan gizlenir.
  • Azaltılmış harekette paneller kaymak yerine solar ve kaydetme çubuğunun sallanması bir halkaya dönüşür.

Şunlarla geliştirildi

Settings bloğunun yapıldığı ücretsiz HextaUI bileşenleri. Her biri tek başına yüklenir.

Kod

6 dosya, components/blocks/settings konumuna eklendi.