or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

date-field-state.mddate-picker-state.mddate-range-picker-state.mdindex.mdtime-field-state.md

time-field-state.mddocs/

0

# Time Field State

1

2

State management for time field components that allow users to enter and edit time values. Each part of a time value (hour, minute, second, AM/PM) is displayed in individually editable segments, providing precise keyboard-based time input.

3

4

## Capabilities

5

6

### useTimeFieldState Hook

7

8

Creates a state object for managing time field component state. Extends date field functionality specifically for time-only input scenarios.

9

10

```typescript { .api }

11

/**

12

* Provides state management for a time field component.

13

* A time field allows users to enter and edit time values using a keyboard.

14

* Each part of a time value is displayed in an individually editable segment.

15

* @param props - Configuration options including locale

16

* @returns TimeFieldState object extending DateFieldState with time-specific functionality

17

*/

18

function useTimeFieldState<T extends TimeValue = TimeValue>(

19

props: TimeFieldStateOptions<T>

20

): TimeFieldState;

21

22

interface TimeFieldStateOptions<T extends TimeValue = TimeValue> extends TimePickerProps<T> {

23

/** The locale to display and edit the value according to. */

24

locale: string;

25

}

26

27

interface TimeFieldState extends DateFieldState {

28

/** The current time value as a Time object. */

29

timeValue: Time;

30

}

31

```

32

33

**Usage Examples:**

34

35

```typescript

36

import { useTimeFieldState } from "@react-stately/datepicker";

37

import { Time } from "@internationalized/date";

38

39

// Basic time field

40

function BasicTimeField() {

41

const state = useTimeFieldState({

42

locale: 'en-US',

43

defaultValue: new Time(14, 30), // 2:30 PM

44

onChange: (time) => console.log("Selected time:", time?.toString())

45

});

46

47

return (

48

<div>

49

{state.segments.map((segment, index) => (

50

<span

51

key={index}

52

style={{

53

padding: '2px 4px',

54

margin: '0 1px',

55

backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',

56

border: segment.isEditable ? '1px solid #ccc' : 'none',

57

borderRadius: '2px'

58

}}

59

>

60

{segment.text}

61

</span>

62

))}

63

<div>

64

Current time: {state.timeValue.toString()}

65

({state.timeValue.hour}:{state.timeValue.minute.toString().padStart(2, '0')})

66

</div>

67

</div>

68

);

69

}

70

71

// 12-hour time field with AM/PM

72

function TwelveHourTimeField() {

73

const state = useTimeFieldState({

74

locale: 'en-US',

75

granularity: 'minute',

76

hourCycle: 12,

77

onChange: (time) => {

78

if (time) {

79

const hours = time.hour;

80

const minutes = time.minute;

81

const period = hours >= 12 ? 'PM' : 'AM';

82

const displayHour = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;

83

console.log(`${displayHour}:${minutes.toString().padStart(2, '0')} ${period}`);

84

}

85

}

86

});

87

88

return (

89

<div>

90

<div style={{ display: 'flex', alignItems: 'center' }}>

91

{state.segments.map((segment, index) => (

92

<input

93

key={index}

94

value={segment.text}

95

placeholder={segment.placeholder}

96

readOnly={!segment.isEditable}

97

onChange={(e) => {

98

if (segment.type === 'dayPeriod') {

99

// Handle AM/PM toggle

100

const currentHour = state.timeValue.hour;

101

const newHour = e.target.value.toUpperCase() === 'PM' && currentHour < 12

102

? currentHour + 12

103

: e.target.value.toUpperCase() === 'AM' && currentHour >= 12

104

? currentHour - 12

105

: currentHour;

106

state.setSegment('hour', newHour);

107

} else {

108

const num = parseInt(e.target.value);

109

if (!isNaN(num)) {

110

state.setSegment(segment.type, num);

111

}

112

}

113

}}

114

style={{

115

width: `${Math.max(segment.text.length, 2)}ch`,

116

border: segment.isEditable ? '1px solid #ccc' : 'none',

117

textAlign: 'center',

118

backgroundColor: segment.isPlaceholder ? '#f8f8f8' : 'white'

119

}}

120

/>

121

))}

122

</div>

123

124

<div style={{ marginTop: '8px' }}>

125

<button onClick={() => state.increment('hour')}>Hour +</button>

126

<button onClick={() => state.decrement('hour')}>Hour -</button>

127

<button onClick={() => state.increment('minute')}>Min +</button>

128

<button onClick={() => state.decrement('minute')}>Min -</button>

129

</div>

130

</div>

131

);

132

}

133

134

// Time field with seconds and validation

135

function PreciseTimeField() {

136

const state = useTimeFieldState({

137

locale: 'en-US',

138

granularity: 'second',

139

minValue: new Time(9, 0, 0), // 9:00:00 AM

140

maxValue: new Time(17, 0, 0), // 5:00:00 PM

141

isRequired: true,

142

validate: (time) => {

143

if (!time) return "Time is required";

144

145

// Custom validation: no times ending in 13 seconds

146

if (time.second === 13) {

147

return "Unlucky second not allowed";

148

}

149

150

return null;

151

},

152

onChange: (time) => {

153

if (time) {

154

console.log(`${time.hour}:${time.minute}:${time.second}`);

155

}

156

}

157

});

158

159

return (

160

<div>

161

<div style={{

162

padding: '8px',

163

border: state.isInvalid ? '2px solid red' : '1px solid #ccc',

164

borderRadius: '4px'

165

}}>

166

{state.segments.map((segment, index) => (

167

<span

168

key={index}

169

tabIndex={segment.isEditable ? 0 : -1}

170

onKeyDown={(e) => {

171

if (segment.isEditable) {

172

switch (e.key) {

173

case 'ArrowUp':

174

e.preventDefault();

175

state.increment(segment.type);

176

break;

177

case 'ArrowDown':

178

e.preventDefault();

179

state.decrement(segment.type);

180

break;

181

case 'PageUp':

182

e.preventDefault();

183

state.incrementPage(segment.type);

184

break;

185

case 'PageDown':

186

e.preventDefault();

187

state.decrementPage(segment.type);

188

break;

189

}

190

}

191

}}

192

style={{

193

padding: '2px 4px',

194

margin: '0 1px',

195

backgroundColor: segment.isPlaceholder ? '#f0f0f0' : 'white',

196

border: segment.isEditable ? '1px solid #ddd' : 'none',

197

borderRadius: '2px',

198

outline: 'none',

199

cursor: segment.isEditable ? 'text' : 'default'

200

}}

201

>

202

{segment.text}

203

</span>

204

))}

205

</div>

206

207

{state.isInvalid && (

208

<div style={{ color: 'red', fontSize: '12px', marginTop: '4px' }}>

209

Please enter a valid time between 9:00:00 AM and 5:00:00 PM

210

</div>

211

)}

212

213

<div style={{ marginTop: '8px' }}>

214

<button onClick={() => state.setValue(new Time(12, 0, 0))}>

215

Set to Noon

216

</button>

217

<button onClick={() => state.confirmPlaceholder()}>

218

Confirm Current

219

</button>

220

<button onClick={() => state.setValue(null)}>

221

Clear

222

</button>

223

</div>

224

</div>

225

);

226

}

227

```

228

229

### Time Value Management

230

231

The time field state manages time values specifically, converting between different time representations.

232

233

```typescript { .api }

234

interface TimeFieldState extends DateFieldState {

235

/** The current time value as a Time object from @internationalized/date */

236

timeValue: Time;

237

}

238

```

239

240

The `timeValue` property provides direct access to the time portion regardless of whether the underlying value includes date information.

241

242

### Time-specific Features

243

244

Unlike date fields, time fields have specific behavior for time-only scenarios:

245

246

- **Hour Cycle Support**: Automatic 12/24 hour format handling

247

- **AM/PM Management**: Intelligent day period segment handling

248

- **Time Zone Handling**: Supports time-only values and datetime values with time zones

249

- **Placeholder Time**: Uses sensible defaults (midnight) when no time is specified

250

251

### Integration with Date Values

252

253

Time fields can work with different value types:

254

255

```typescript { .api }

256

interface TimeFieldStateOptions<T extends TimeValue> {

257

/** Current time value - can be Time, CalendarDateTime, or ZonedDateTime */

258

value?: T | null;

259

/** Default time value */

260

defaultValue?: T | null;

261

/** Placeholder value that affects formatting */

262

placeholderValue?: Time;

263

}

264

```

265

266

**Value Type Handling:**

267

268

```typescript

269

// Time-only value

270

const timeOnlyState = useTimeFieldState({

271

locale: 'en-US',

272

value: new Time(14, 30, 0), // 2:30:00 PM

273

});

274

275

// DateTime value (time portion will be extracted)

276

const dateTimeState = useTimeFieldState({

277

locale: 'en-US',

278

value: new CalendarDateTime(2023, 6, 15, 14, 30, 0),

279

});

280

281

// Zoned DateTime value (time portion with timezone)

282

const zonedState = useTimeFieldState({

283

locale: 'en-US',

284

value: toZoned(new CalendarDateTime(2023, 6, 15, 14, 30), 'America/New_York'),

285

});

286

```

287

288

### Granularity Control

289

290

Control which time segments are displayed and editable:

291

292

```typescript { .api }

293

interface TimeFieldStateOptions<T> {

294

/** The smallest time unit to display @default 'minute' */

295

granularity?: 'hour' | 'minute' | 'second';

296

/** Whether to display in 12 or 24 hour format */

297

hourCycle?: 12 | 24;

298

}

299

```

300

301

**Granularity Examples:**

302

- `granularity: 'hour'` - Shows only hour and AM/PM

303

- `granularity: 'minute'` - Shows hour, minute, and AM/PM

304

- `granularity: 'second'` - Shows hour, minute, second, and AM/PM

305

306

### Time Validation

307

308

Time fields support validation with time-specific constraints:

309

310

```typescript { .api }

311

interface TimeFieldStateOptions<T> {

312

/** Minimum allowed time */

313

minValue?: TimeValue;

314

/** Maximum allowed time */

315

maxValue?: TimeValue;

316

/** Custom validation function */

317

validate?: (value: MappedTimeValue<T> | null) => ValidationError | true | null | undefined;

318

/** Whether the time field is required */

319

isRequired?: boolean;

320

}

321

```

322

323

**Common Validation Scenarios:**

324

```typescript

325

// Business hours validation

326

const businessHoursState = useTimeFieldState({

327

locale: 'en-US',

328

minValue: new Time(9, 0), // 9:00 AM

329

maxValue: new Time(17, 0), // 5:00 PM

330

validate: (time) => {

331

if (time && (time.minute % 15 !== 0)) {

332

return "Please select times in 15-minute intervals";

333

}

334

return null;

335

}

336

});

337

338

// No midnight allowed

339

const noMidnightState = useTimeFieldState({

340

locale: 'en-US',

341

validate: (time) => {

342

if (time && time.hour === 0 && time.minute === 0) {

343

return "Midnight is not allowed";

344

}

345

return null;

346

}

347

});

348

```

349

350

### Inherited DateFieldState Functionality

351

352

TimeFieldState extends DateFieldState, providing all segment manipulation capabilities:

353

354

```typescript { .api }

355

// All DateFieldState methods are available:

356

state.increment('hour');

357

state.decrement('minute');

358

state.incrementPage('hour'); // +2 hours

359

state.decrementPage('minute'); // -15 minutes

360

state.setSegment('hour', 14);

361

state.clearSegment('minute');

362

state.confirmPlaceholder();

363

```