HextaUI

BetaVercel UI SDK 0.1.0-beta.2 üzerine kurulu. API’si değişebilir.

Time input

Vercel UI SDK üzerinde parça parça yazılan bir saat alanı; 12 veya 24 saatlik saat, isteğe bağlı saniye, en küçük ve en büyük değer ve formlar için 24 saatlik değer.

Bağımlılıklar
  • @ui-sdk/core
  • @ui-sdk/react
import { IconClock } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import { TimeInput, TimeInputSegments } from "@/components/ui/ui-sdk-time-input"

function UiSdkTimeInputDemo() {
  return (
    <div className="flex w-full max-w-40 flex-col gap-2">
      <Label id="meeting-time">Meeting time</Label>
      <TimeInput aria-labelledby="meeting-time" defaultValue="09:30">
        <IconClock aria-hidden="true" />
        <TimeInputSegments />
      </TimeInput>
    </div>
  )
}

export { UiSdkTimeInputDemo }
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-time-input.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

UI SDK, ui-sdk adlı bir cascade katmanında kendi CSS’ini biraz ekler. Sınıflarınızın kazanması için bu katmanı genel stil dosyanızın en üstünde, Tailwind import’unun üzerinde tanımlayın.

@layer ui-sdk;

@import "tailwindcss";
import { TimeInput } from "@/components/ui/ui-sdk-time-input"
<Label id="meeting-time">Meeting time</Label>
<TimeInput aria-labelledby="meeting-time" defaultValue="09:30" />

Değer, alanın gösterdiği saat düzeninden bağımsız olarak yerel bir saat girişindeki gibi "14:30" biçiminde 24 saatlik bir zamandır. Her parça bir şey içerene kadar null kalır. Her parça kendi küçük girişidir: rakam yazınca ilerler, günün yarısı için A veya P'ye basın.

Alan bir gruptur, bu yüzden onu aria-labelledby veya aria-label ile adlandırın. Kutunun herhangi bir yerine tıklamak en yakın parçaya odaklanır.

TimeInput
└── TimeInputSegments
    └── TimeInputSegment

TimeInput parçalarını kendisi çizer. Yanlarına bir simge ya da başka içerik koymak için TimeInputSegments öğesini kendiniz ekleyin.

Alan yerel ayarın saat düzenini gösterir. hourCycle 12 veya 24 saati seçer, granularity saniye ekler ya da yalnızca saati gösterir. Hepsi 24 saatlik değer gönderir.

import { Label } from "@/components/ui/label"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

function UiSdkTimeInputClock() {
  return (
    <div className="flex w-full max-w-40 flex-col gap-4">
      <div className="flex flex-col gap-2">
        <Label id="clock-12">12-hour clock</Label>
        <TimeInput
          aria-labelledby="clock-12"
          hourCycle={12}
          defaultValue="18:45"
          locale="en-US"
        />
      </div>
      <div className="flex flex-col gap-2">
        <Label id="clock-24">24-hour clock</Label>
        <TimeInput
          aria-labelledby="clock-24"
          hourCycle={24}
          defaultValue="18:45"
        />
      </div>
      <div className="flex flex-col gap-2">
        <Label id="clock-seconds">With seconds</Label>
        <TimeInput
          aria-labelledby="clock-seconds"
          granularity="second"
          hourCycle={24}
          defaultValue="18:45:30"
        />
      </div>
      <div className="flex flex-col gap-2">
        <Label id="clock-hour">Hour only</Label>
        <TimeInput
          aria-labelledby="clock-hour"
          granularity="hour"
          hourCycle={24}
          defaultValue="18:00"
        />
      </div>
    </div>
  )
}

export { UiSdkTimeInputClock }

min öncesindeki veya max sonrasındaki bir saat alanı geçersiz işaretler ve formu gönderilmez.

"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

const opens = "09:00"
const closes = "17:30"

function UiSdkTimeInputMinMax() {
  const [time, setTime] = React.useState<string | null>("16:00")
  const outside = time !== null && (time < opens || time > closes)

  return (
    <div className="flex w-full max-w-40 flex-col gap-2">
      <Label id="pickup-time">Pickup</Label>
      <TimeInput
        aria-labelledby="pickup-time"
        aria-describedby="pickup-time-message"
        defaultValue="16:00"
        min={opens}
        max={closes}
        hourCycle={24}
        onValueChange={(value) => setTime(value)}
      />
      <p
        id="pickup-time-message"
        className={
          outside
            ? "m-0 text-xs text-destructive"
            : "m-0 text-xs text-muted-foreground"
        }
      >
        {outside ? "We’re closed then." : "Open 09:00 to 17:30."}
      </p>
    </div>
  )
}

export { UiSdkTimeInputMinMax }

invalid, başlangıçtan önceki bir bitiş saati gibi kendi denetimleriniz için alanı işaretler.

"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

function UiSdkTimeInputRange() {
  const [start, setStart] = React.useState<string | null>("13:00")
  const [end, setEnd] = React.useState<string | null>("12:30")
  const invalid = start !== null && end !== null && end <= start

  return (
    <div className="flex w-full max-w-72 flex-col gap-2">
      <div className="grid grid-cols-2 gap-2">
        <div className="flex flex-col gap-2">
          <Label id="shift-start">Starts</Label>
          <TimeInput
            aria-labelledby="shift-start"
            defaultValue="13:00"
            onValueChange={(value) => setStart(value)}
          />
        </div>
        <div className="flex flex-col gap-2">
          <Label id="shift-end">Ends</Label>
          <TimeInput
            aria-labelledby="shift-end"
            aria-describedby="shift-message"
            defaultValue="12:30"
            invalid={invalid}
            onValueChange={(value) => setEnd(value)}
          />
        </div>
      </div>
      <p
        id="shift-message"
        className={
          invalid
            ? "m-0 text-xs text-destructive"
            : "m-0 text-xs text-muted-foreground"
        }
      >
        {invalid ? "The shift has to end after it starts." : "One shift a day."}
      </p>
    </div>
  )
}

export { UiSdkTimeInputRange }

locale sırayı, ayırıcıları ve günün yarısının adlarını belirler. Olmadığında alan etkileşimli hale gelince sayfanın dilini izler.

import { Label } from "@/components/ui/label"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

const locales = [
  { locale: "en-US", label: "Time (en-US)" },
  { locale: "de-DE", label: "Uhrzeit (de-DE)" },
  { locale: "ja-JP", label: "時刻 (ja-JP)" },
  { locale: "ko-KR", label: "시간 (ko-KR)" },
]

function UiSdkTimeInputLocale() {
  return (
    <div className="flex w-full max-w-40 flex-col gap-4">
      {locales.map(({ locale, label }) => (
        <div key={locale} lang={locale} className="flex flex-col gap-2">
          <Label id={`time-${locale}`}>{label}</Label>
          <TimeInput
            aria-labelledby={`time-${locale}`}
            locale={locale}
            defaultValue="18:45"
          />
        </div>
      ))}
    </div>
  )
}

export { UiSdkTimeInputLocale }

Gizli bir giriş saati name altında gönderir. required boş veya yarım yazılmış bir saati durdurur, sıfırlama varsayılanı geri getirir.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

function UiSdkTimeInputForm() {
  const [submitted, setSubmitted] = React.useState<string | null>(null)

  return (
    <form
      className="flex w-full max-w-40 flex-col gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        setSubmitted(String(data.get("reminder")))
      }}
    >
      <div className="flex flex-col gap-2">
        <Label id="reminder-time">Remind me at</Label>
        <TimeInput
          aria-labelledby="reminder-time"
          name="reminder"
          defaultValue="08:15"
          required
        />
      </div>
      <div className="flex items-center gap-3">
        <Button type="submit" size="sm">
          Save
        </Button>
        <Button type="reset" size="sm" variant="ghost">
          Reset
        </Button>
      </div>
      <p className="m-0 text-sm text-muted-foreground tabular-nums">
        {submitted ? `Submitted ${submitted}` : "Nothing submitted yet."}
      </p>
    </form>
  )
}

export { UiSdkTimeInputForm }

createTimeInputController() alanı her yerden ayarlar, temizler veya odaklar. Bir parçaya "minute" gibi birimiyle odaklanın.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import {
  TimeInput,
  createTimeInputController,
} from "@/components/ui/ui-sdk-time-input"

function now() {
  const date = new Date()
  return `${String(date.getHours()).padStart(2, "0")}:${String(
    date.getMinutes()
  ).padStart(2, "0")}`
}

function UiSdkTimeInputController() {
  const [controller] = React.useState(createTimeInputController)
  const [value, setValue] = React.useState<string | null>(null)

  return (
    <div className="flex w-full max-w-56 flex-col gap-2">
      <Label id="log-time">Logged at</Label>
      <TimeInput
        aria-labelledby="log-time"
        controller={controller}
        onValueChange={(next) => setValue(next)}
      />
      <div className="flex items-center gap-2 pt-1">
        <Button
          size="sm"
          variant="outline"
          onClick={() => controller.setValue(now())}
        >
          Now
        </Button>
        <Button
          size="sm"
          variant="ghost"
          onClick={() => controller.focus("minute")}
        >
          Edit minutes
        </Button>
        <Button size="sm" variant="ghost" onClick={() => controller.clear()}>
          Clear
        </Button>
      </div>
      <p className="m-0 text-xs text-muted-foreground tabular-nums">
        {value ? `Value ${value}` : "Value null"}
      </p>
    </div>
  )
}

export { UiSdkTimeInputController }

Devre dışı bir alan odakta atlanır ve gönderilmez.

import { Label } from "@/components/ui/label"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

function UiSdkTimeInputDisabled() {
  return (
    <div className="flex w-full max-w-40 flex-col gap-2">
      <Label id="closed-time">Last entry</Label>
      <TimeInput aria-labelledby="closed-time" defaultValue="22:00" disabled />
    </div>
  )
}

export { UiSdkTimeInputDisabled }

Salt okunur bir alan odak alır ve gönderilir, ancak tuşlar ve yazma onu değiştirmez.

import { Label } from "@/components/ui/label"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

function UiSdkTimeInputReadOnly() {
  return (
    <div className="flex w-full max-w-40 flex-col gap-2">
      <Label id="checkout-time">Checkout</Label>
      <TimeInput
        aria-labelledby="checkout-time"
        defaultValue="11:00"
        readOnly
      />
    </div>
  )
}

export { UiSdkTimeInputReadOnly }

Parçalar yerel ayarın sırasını korur ve ok tuşları ekrandaki sıralarını izler.

import { IconClock } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import { TimeInput, TimeInputSegments } from "@/components/ui/ui-sdk-time-input"

function UiSdkTimeInputRtl() {
  return (
    <div dir="rtl" lang="ar" className="flex w-full max-w-40 flex-col gap-2">
      <Label id="rtl-time">وقت الاجتماع</Label>
      <TimeInput aria-labelledby="rtl-time" locale="ar-EG" defaultValue="18:45">
        <IconClock aria-hidden="true" />
        <TimeInputSegments />
      </TimeInput>
    </div>
  )
}

export { UiSdkTimeInputRtl }
TuşAction
0–9Parçayı doldurur, sonra bir sonrakine geçer.
↑↓Parçayı birer birer değiştirir ve başa döner. Boş bir parça şu anki saatten başlar.
PageUpPageDownDaha büyük adımlarla değiştirir, örneğin 15 dakika.
HomeEndParçanın ilk veya son değerine gider.
←→Önceki veya sonraki parçaya geçer.
BackspaceSon rakamı siler, sonra parçayı boşaltır, ardından geri gider.
AP12 saatlik düzende ÖÖ veya ÖS'yi seçer.
EnterFormu yazılanla gönderir.

Kök bir gruptur. Her parça, “hour” gibi birimiyle adlandırılmış, değeri ve aralığı olan bir spinbutton'dır; boş bir parça “Empty” olarak okunur. Parçalar arasındaki metin ekran okuyuculardan gizlenir.

Parçalar gerçek girişlerdir; bu yüzden telefonlar sayı tuş takımı gösterir ve sayfanın betiği çalışmadan yazılabilir. Telefonlar yakınlaştırmasın diye dokunmatik ekranlarda 16px'e büyürler ve odaktaki parça imleç yerine vurgulanır.

Her parça aynı adlı UI SDK parçasını sarar ve render ile parçanın durumuna bağlı bir fonksiyon olabilen className dahil tüm prop’larını kabul eder.

PropTürVarsayılan
value
string | null–
defaultValue
string | null–
onValueChangeHer parça bir şey içerdiğinde, bir parça boşaltıldığında da null ile çalışır.
(value: string | null, details) => void–
hourCycleVarsayılan olarak yerel ayarın saat düzeni.
12 | 24–
granularity
"hour" | "minute" | "second""minute"
min
string–
max
string–
localeVarsayılan olarak sayfanın dili.
string–
invalid
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
required
booleanfalse
name
string–
form
string–
controllercreateTimeInputController() ile oluşturulur.
TimeInputController–
childrenVarsayılan olarak TimeInputSegments.
ReactNode | (state) => ReactNode–
render
ReactElement | (props, state) => ReactElement–
ÖznitelikAçıklama
data-slot="time-input"CSS'te saat alanını hedeflemek için.
data-invalidSaat min ile max dışında olduğunda veya invalid ayarlandığında bulunur.
data-emptyTüm parçalar boşken bulunur.
data-disabledDevre dışıyken bulunur.
data-readonlySalt okunur olduğunda bulunur.
data-requiredZorunlu olduğunda bulunur.

Her parça için yerel ayarın sırasıyla, aralarındaki metinle birlikte bir TimeInputSegment çizer.

PropTürVarsayılan
segmentKök durumundaki segments öğelerinden biri.
TimeInputSegmentData–
render
ReactElement | (props, state) => ReactElement–
ÖznitelikAçıklama
data-slot="time-input-segment"CSS'te bir parçayı hedeflemek için.
data-typehour, minute, second, dayPeriod veya parçalar arasındaki metin için literal.
data-emptyParça boşken bulunur.