PluginProbe
The Innovative Form Builder – IvyForms / 0.9
The Innovative Form Builder – IvyForms v0.9
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / _components / timepicker / IvyTimePicker.vue

IvyTimePicker.vue in The Innovative Form Builder – IvyForms 0.9, at frontend/src/views/_components/timepicker/IvyTimePicker.vue

252 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="ivyforms-time-picker" :class="{ 'is-secondary': isSecondary }">
3 <ElTimePicker
4 v-model="localModelValue"
5 class="ivyforms-input"
6 :class="{ 'has-value': !!localModelValue }"
7 :editable="editable"
8 :clearable="clearable"
9 :format="displayFormat"
10 :value-format="valueFormat"
11 :popper-class="`ivyforms-time-popper ivyforms-time-popper--${popperBackground}`"
12 :popper-options="{ placement: 'bottom-start' }"
13 v-bind="restAttrs"
14 :disabled-hours="safeDisabledHours"
15 :disabled-minutes="safeDisabledMinutes"
16 @change="onChange"
17 />
18 </div>
19 </template>
20
21 <script setup lang="ts">
22 import { computed, useAttrs, inject } from 'vue'
23 import type { TimeFormat } from '@/types/time/time-type'
24
25 interface Props {
26 modelValue?: string | null
27 format?: string
28 timeFormat?: TimeFormat
29 valueFormat?: string
30 clearable?: boolean
31 editable?: boolean
32 popperBackground?: 'level-2-foreground' | 'level-1-foreground'
33 secondary?: boolean
34 disabledHours?: () => number[]
35 disabledMinutes?: (hour: number) => number[]
36 }
37
38 const props = withDefaults(defineProps<Props>(), {
39 modelValue: null,
40 timeFormat: '24h',
41 valueFormat: 'HH:mm',
42 clearable: true,
43 editable: false,
44 popperBackground: 'level-2-foreground',
45 format: undefined,
46 secondary: false,
47 disabledHours: undefined,
48 disabledMinutes: undefined,
49 })
50
51 // Prioritize prop over inject
52 const isSecondary = computed(() => props.secondary || inject('isSecondary', false))
53
54 const emit = defineEmits<{
55 (e: 'update:modelValue', value: string | null): void
56 (e: 'change', value: string | null): void
57 }>()
58
59 const attrs = useAttrs()
60
61 // Separate attrs so we keep explicit control of a few props while forwarding the rest
62 const restAttrs = computed(() => {
63 // Only exclude keys, do not assign them to variables
64 const excludedKeys = [
65 'modelValue',
66 'format',
67 'timeFormat',
68 'valueFormat',
69 'clearable',
70 'editable',
71 'popperBackground',
72 'secondary',
73 ]
74 return Object.fromEntries(Object.entries(attrs).filter(([key]) => !excludedKeys.includes(key)))
75 })
76
77 const onChange = (val: string | null) => {
78 emit('change', val)
79 }
80
81 const localModelValue = computed({
82 get: () => props.modelValue ?? null,
83 set: (val: string | null) => emit('update:modelValue', val),
84 })
85
86 const displayFormat = computed(() => {
87 if (props.format) return props.format
88 return props.timeFormat === 'ampm' ? 'hh:mm A' : 'HH:mm'
89 })
90
91 const safeDisabledHours = props.disabledHours ?? (() => [])
92 const safeDisabledMinutes = props.disabledMinutes ?? (() => [])
93 </script>
94
95 <style lang="scss">
96 // Root wrapper
97 .ivyforms-time-picker {
98 width: 100%;
99 height: 40px;
100
101 input::selection {
102 background-color: var(--map-base-dusk-o20);
103 color: var(--map-base-text-0);
104 }
105
106 // El time editor behaves like date editor under the hood
107 .el-date-editor {
108 &.el-input {
109 width: 100%;
110 height: 40px;
111 }
112 }
113
114 .ivyforms-input {
115 width: 100%;
116 height: 40px;
117
118 .el-input__prefix {
119 display: none;
120 }
121
122 .el-input__wrapper,
123 &.el-input__wrapper {
124 display: flex;
125 align-items: center;
126 gap: 4px;
127 padding: 0 12px;
128 border-radius: var(--Radius-radius-md, 8px);
129 border: 1px solid var(--map-base-dusk-stroke--2);
130 background: var(--map-base-dusk-o05);
131 box-shadow: none;
132
133 &:hover {
134 border: 1px solid var(--map-base-dusk-stroke-0);
135 }
136 &:focus,
137 &.is-focus,
138 &:active,
139 &.is-active {
140 border: 1px solid var(--map-base-brand-stroke-0);
141 box-shadow: none;
142 background: var(--map-base-dusk-o05);
143 }
144
145 input.el-input__inner {
146 padding: 0;
147 border: none;
148 background: transparent;
149 box-shadow: none;
150 font-size: 16px;
151 line-height: 22px;
152 color: var(--map-base-text-0);
153
154 &:focus {
155 outline: none;
156 }
157 }
158 }
159 }
160
161 // Secondary styling
162 &.is-secondary {
163 .ivyforms-input {
164 .el-input__wrapper,
165 &.el-input__wrapper {
166 &:focus,
167 &.is-focus,
168 &:active,
169 &.is-active {
170 border: 1px solid var(--map-base-purple-stroke-0);
171 }
172 }
173 }
174 }
175 }
176
177 // Popper styling
178 .ivyforms-time-popper {
179 --el-color-primary: var(--map-base-brand-0);
180
181 &.el-popper {
182 display: flex;
183 flex-direction: column;
184 align-items: center;
185 border: none !important;
186 box-shadow:
187 0px 12px 32px 4px rgba(0, 0, 0, 0.04),
188 0px 8px 20px 0px rgba(0, 0, 0, 0.08) !important;
189 border-radius: 4px;
190 background: var(--map-ground-level-2-foreground);
191
192 &.ivyforms-time-popper--level-1-foreground {
193 background: var(--map-ground-level-1-foreground);
194 }
195
196 &.ivyforms-time-popper--level-2-foreground {
197 background: var(--map-ground-level-2-foreground);
198 }
199
200 .el-time-spinner__item {
201 margin-bottom: 0 !important;
202 }
203 }
204
205 .el-time-spinner__item {
206 color: var(--map-base-text-0) !important;
207
208 &:hover:not(.is-disabled):not(.is-active) {
209 background: var(--map-hover) !important;
210 }
211 }
212
213 .el-popper__arrow {
214 left: unset !important;
215
216 &::before {
217 background: var(--map-ground-level-2-foreground) !important;
218 border: 1px solid var(--map-ground-level-2-foreground) !important;
219 }
220 }
221
222 .el-time-panel {
223 width: 165px;
224
225 &__footer {
226 padding: 8px 12px;
227 height: 46px;
228 }
229
230 &__btn.cancel {
231 font-weight: 500;
232 padding: 0 8px;
233 margin: 0;
234 color: var(--map-base-text-0);
235 }
236
237 &__btn.confirm {
238 background: var(--map-base-dusk-fill-1);
239 border-radius: 4px;
240 color: $primitive-white;
241 text-transform: lowercase;
242 padding: 0 8px;
243 margin: 0 0 0 8px;
244
245 &:first-letter {
246 text-transform: uppercase;
247 }
248 }
249 }
250 }
251 </style>
252