Skip to content

Commit dce8077

Browse files
committed
feat(InputTime): enhance component with Time support and add InputTimeFormField example
1 parent e4c1e45 commit dce8077

File tree

4 files changed

+188
-12
lines changed

4 files changed

+188
-12
lines changed

docs/app/components/content/ComponentCode.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ChipProps } from '@nuxt/ui'
44
import json5 from 'json5'
55
import { upperFirst, camelCase, kebabCase } from 'scule'
66
import { hash } from 'ohash'
7-
import { CalendarDate } from '@internationalized/date'
7+
import { CalendarDate, Time } from '@internationalized/date'
88
import * as theme from '#build/ui'
99
import { get, set } from '#ui/utils'
1010
@@ -14,6 +14,7 @@ interface Cast {
1414
}
1515
1616
type CastDateValue = [number, number, number]
17+
type CastTimeValue = [number, number, number]
1718
1819
const castMap: Record<string, Cast> = {
1920
'DateValue': {
@@ -37,6 +38,12 @@ const castMap: Record<string, Cast> = {
3738
3839
return `{ start: new CalendarDate(${value.start.year}, ${value.start.month}, ${value.start.day}), end: new CalendarDate(${value.end.year}, ${value.end.month}, ${value.end.day}) }`
3940
}
41+
},
42+
'TimeValue': {
43+
get: (args: CastTimeValue) => new Time(...args),
44+
template: (value: Time) => {
45+
return value ? `new Time(${value.hour}, ${value.minute}, ${value.second})` : 'null'
46+
}
4047
}
4148
}
4249
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script setup lang="ts">
2+
import { Time } from '@internationalized/date'
3+
4+
const time = shallowRef(new Time(12, 30, 0))
5+
</script>
6+
7+
<template>
8+
<UFormField label="Time" help="Specify the time" required>
9+
<UInputTime v-model="time" />
10+
</UFormField>
11+
</template>

docs/content/docs/2.components/input-time.md

Lines changed: 160 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
---
22
title: InputTime
3-
description: ''
3+
description: 'An input for selecting a time.'
4+
category: form
45
links:
5-
- label: InputTime
6+
- label: TimeField
67
icon: i-custom-reka-ui
7-
to: https://reka-ui.com/docs/components/input-time
8+
to: https://reka-ui.com/docs/components/time-field
89
- label: GitHub
910
icon: i-simple-icons-github
1011
to: https://github.com/nuxt/ui/blob/v4/src/runtime/components/InputTime.vue
@@ -13,8 +14,164 @@ navigation.badge: Soon
1314

1415
## Usage
1516

17+
Use the `v-model` directive to control the selected date.
18+
19+
::component-code
20+
---
21+
cast:
22+
modelValue: TimeValue
23+
ignore:
24+
- modelValue
25+
external:
26+
- modelValue
27+
props:
28+
modelValue: [12, 30, 0]
29+
---
30+
::
31+
32+
Use the `default-value` prop to set the initial value when you do not need to control its state.
33+
34+
::component-code
35+
---
36+
cast:
37+
defaultValue: TimeValue
38+
ignore:
39+
- defaultValue
40+
external:
41+
- defaultValue
42+
props:
43+
defaultValue: [12, 30, 0]
44+
---
45+
::
46+
47+
::note
48+
This component relies on the [`@internationalized/date`](https://react-spectrum.adobe.com/internationalized/date/index.html) package which provides objects and functions for representing and manipulating dates and times in a locale-aware manner.
49+
::
50+
51+
### Placeholder
52+
53+
Use the `placeholder` prop to set a placeholder text.
54+
55+
::component-code
56+
---
57+
cast:
58+
placeholder: TimeValue
59+
ignore:
60+
- placeholder
61+
external:
62+
- placeholder
63+
props:
64+
placeholder: [12, 30, 0]
65+
---
66+
::
67+
68+
### Color
69+
70+
Use the `color` prop to change the color of the InputTime.
71+
72+
::component-code
73+
---
74+
props:
75+
color: neutral
76+
highlight: true
77+
---
78+
::
79+
80+
::note
81+
The `highlight` prop is used here to show the focus state. It's used internally when a validation error occurs.
82+
::
83+
84+
### Variant
85+
86+
Use the `variant` prop to change the variant of the InputTime.
87+
88+
::component-code
89+
---
90+
cast:
91+
defaultValue: TimeValue
92+
hide:
93+
- defaultValue
94+
props:
95+
variant: subtle
96+
defaultValue: [12, 30, 0]
97+
---
98+
::
99+
100+
### Size
101+
102+
Use the `size` prop to change the size of the InputTime.
103+
104+
::component-code
105+
---
106+
props:
107+
size: xl
108+
---
109+
::
110+
111+
### Icon
112+
113+
Use the `icon` prop to show an [Icon](/docs/components/icon) inside the InputTime.
114+
115+
::component-code
116+
---
117+
props:
118+
icon: 'i-lucide-clock'
119+
---
120+
::
121+
122+
::note
123+
Use the `leading` and `trailing` props to set the icon position or the `leading-icon` and `trailing-icon` props to set a different icon for each position.
124+
::
125+
126+
### Avatar
127+
128+
Use the `avatar` prop to show an [Avatar](/docs/components/avatar) inside the InputTime.
129+
130+
::component-code
131+
---
132+
prettier: true
133+
props:
134+
avatar:
135+
src: 'https://github.com/vuejs.png'
136+
size: md
137+
variant: outline
138+
---
139+
::
140+
141+
### Disabled
142+
143+
Use the `disabled` prop to disable the InputTime.
144+
145+
::component-code
146+
---
147+
props:
148+
disabled: true
149+
---
150+
::
151+
152+
### Hide Time Zone
153+
154+
Use the `hideTimeZone` prop to hide the time zone of the InputTime.
155+
156+
::component-code
157+
---
158+
props:
159+
hideTimeZone: true
160+
---
161+
::
162+
16163
## Examples
17164

165+
### Within a FormField
166+
167+
You can use the InputTime within a [FormField](/docs/components/form-field) component to display a label, help text, required indicator, etc.
168+
169+
::component-example
170+
---
171+
name: 'input-time-form-field-example'
172+
---
173+
::
174+
18175
## API
19176

20177
### Props

src/runtime/components/InputTime.vue

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import type { TimeFieldRootProps, TimeFieldRootEmits } from 'reka-ui'
33
import type { AppConfig } from '@nuxt/schema'
44
import theme from '#build/ui/input-time'
5+
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
6+
import type { AvatarProps } from '../types'
57
import type { ComponentConfig } from '../types/tv'
68
79
type InputTime = ComponentConfig<typeof theme, AppConfig, 'inputTime'>
810
9-
export interface InputTimeProps extends Omit<TimeFieldRootProps, 'as' | 'locale' | 'dir'> {
11+
export interface InputTimeProps extends Omit<TimeFieldRootProps, 'as' | 'locale' | 'dir'>, UseComponentIconsProps {
1012
/**
1113
* The element or component this component should render as.
1214
* @defaultValue 'div'
@@ -45,7 +47,7 @@ export interface InputTimeSlots {
4547
import type { ComponentPublicInstance } from 'vue'
4648
import { computed, onMounted, ref } from 'vue'
4749
import { TimeFieldRoot, TimeFieldInput, useForwardPropsEmits, Primitive } from 'reka-ui'
48-
import { reactivePick } from '@vueuse/core'
50+
import { reactiveOmit } from '@vueuse/core'
4951
import { useAppConfig } from '#imports'
5052
import { useFieldGroup } from '../composables/useFieldGroup'
5153
import { useComponentIcons } from '../composables/useComponentIcons'
@@ -62,7 +64,7 @@ const slots = defineSlots<InputTimeSlots>()
6264
const { code: codeLocale, dir } = useLocale()
6365
const appConfig = useAppConfig() as InputTime['AppConfig']
6466
65-
const rootProps = useForwardPropsEmits(reactivePick(props, 'disabled', 'id', 'name', 'required'), emits)
67+
const rootProps = useForwardPropsEmits(reactiveOmit(props, 'modelValue', 'defaultValue', 'color', 'variant', 'size', 'highlight', 'autofocus', 'autofocusDelay', 'locale', 'icon', 'avatar', 'class', 'ui'), emits)
6668
6769
const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size: formGroupSize, name, highlight, disabled, ariaAttrs } = useFormField<InputTimeProps>(props)
6870
const { orientation, size: fieldGroupSize } = useFieldGroup<InputTimeProps>(props)
@@ -76,6 +78,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.inputTime ||
7678
variant: props.variant,
7779
size: inputSize.value,
7880
highlight: highlight.value,
81+
leading: isLeading.value || !!props.avatar || !!slots.leading,
7982
trailing: isTrailing.value || !!slots.trailing,
8083
fieldGroup: orientation.value
8184
}))
@@ -126,12 +129,9 @@ defineExpose({
126129
v-slot="{ segments }"
127130
:model-value="modelValue"
128131
:default-value="defaultValue"
129-
:default-placeholder="defaultPlaceholder"
130-
:placeholder="placeholder"
131-
:required="required"
132+
:name="name"
132133
:disabled="disabled"
133134
:locale="locale"
134-
:name="name"
135135
:dir="dir"
136136
:class="ui.base({ class: [props.ui?.base] })"
137137
@update:model-value="onUpdate"
@@ -150,9 +150,10 @@ defineExpose({
150150

151151
<slot :ui="ui" />
152152

153-
<span v-if="isLeading || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
153+
<span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
154154
<slot name="leading" :ui="ui">
155155
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
156+
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
156157
</slot>
157158
</span>
158159

0 commit comments

Comments
 (0)