mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-13 10:42:16 +01:00
improve focus state styling
This commit is contained in:
@@ -35,7 +35,7 @@ const iconSizeClasses = computed(() => {
|
||||
});
|
||||
|
||||
const iconSizeWrapperClasses =
|
||||
props.size === 'small' ? 'w-6 sm:w-8 h-6 sm:h-8' : 'w-11 h-11';
|
||||
props.size === 'small' ? 'w-6 sm:w-8 h-6 sm:h-8' : 'w-10 h-10';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -45,7 +45,7 @@ const iconSizeWrapperClasses =
|
||||
twMerge(
|
||||
iconColorClasses,
|
||||
iconSizeWrapperClasses,
|
||||
'flex-shrink-0 ring-0 focus:outline-none focus:ring-0 transition focus:bg-card-background-separator hover:bg-card-background-separator rounded-full flex items-center justify-center'
|
||||
'flex-shrink-0 ring-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring transition focus:bg-card-background-separator hover:bg-card-background-separator rounded-full flex items-center justify-center'
|
||||
)
|
||||
">
|
||||
<BillableIcon :class="iconSizeClasses"></BillableIcon>
|
||||
|
||||
@@ -35,5 +35,5 @@ const proxyChecked = computed({
|
||||
type="checkbox"
|
||||
:id="id"
|
||||
:value="value"
|
||||
class="h-4 w-4 rounded bg-card-background border-input-border text-accent-500/80 focus:ring-accent-500/80" />
|
||||
class="h-4 w-4 rounded bg-card-background border-input-border text-accent-500/80 focus:outline-none focus:ring-ring/50 focus-visible:outline-none focus-visible:ring-ring/50" />
|
||||
</template>
|
||||
|
||||
@@ -24,7 +24,7 @@ const model = defineModel();
|
||||
ref="input"
|
||||
:class="
|
||||
twMerge(
|
||||
'border-input-border border bg-input-background text-white focus:ring-input-border-active focus:ring-0 focus-visible:border-input-border-active rounded-md shadow-sm',
|
||||
'border-input-border border bg-input-background text-white focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-transparent rounded-md shadow-sm',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
|
||||
119
resources/js/packages/ui/src/Input/TimePickerSimple.vue
Normal file
119
resources/js/packages/ui/src/Input/TimePickerSimple.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { getLocalizedDayJs } from '@/packages/ui/src/utils/time';
|
||||
import { useFocus } from '@vueuse/core';
|
||||
import { TextInput } from '@/packages/ui/src';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
// This has to be a localized timestamp, not UTC
|
||||
const model = defineModel<string | null>({
|
||||
default: null,
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
size: 'base' | 'large';
|
||||
focus: boolean;
|
||||
}>(),
|
||||
{
|
||||
size: 'base',
|
||||
focus: false,
|
||||
}
|
||||
);
|
||||
|
||||
function updateTime(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const newValue = target.value.trim();
|
||||
if (newValue.split(':').length === 2) {
|
||||
const [hours, minutes] = newValue.split(':');
|
||||
if (!isNaN(parseInt(hours)) && !isNaN(parseInt(minutes))) {
|
||||
model.value = getLocalizedDayJs(model.value)
|
||||
.set('hours', Math.min(parseInt(hours), 23))
|
||||
.set('minutes', Math.min(parseInt(minutes), 59))
|
||||
.format();
|
||||
emit('changed', model.value);
|
||||
}
|
||||
}
|
||||
// check if input is only numbers
|
||||
else if (/^\d+$/.test(newValue)) {
|
||||
if (newValue.length === 4) {
|
||||
// parse 1300 to 13:00
|
||||
const [hours, minutes] = [
|
||||
newValue.slice(0, 2),
|
||||
newValue.slice(2, 4),
|
||||
];
|
||||
model.value = getLocalizedDayJs(model.value)
|
||||
.set('hours', Math.min(parseInt(hours), 23))
|
||||
.set('minutes', Math.min(parseInt(minutes), 59))
|
||||
.format();
|
||||
emit('changed', model.value);
|
||||
} else if (newValue.length === 3) {
|
||||
// parse 130 to 01:30
|
||||
const [hours, minutes] = [
|
||||
newValue.slice(0, 1),
|
||||
newValue.slice(1, 3),
|
||||
];
|
||||
model.value = getLocalizedDayJs(model.value)
|
||||
.set('hours', Math.min(parseInt(hours), 23))
|
||||
.set('minutes', Math.min(parseInt(minutes), 59))
|
||||
.format();
|
||||
emit('changed', model.value);
|
||||
} else if (newValue.length === 2) {
|
||||
// parse 13 to 13:00
|
||||
model.value = getLocalizedDayJs(model.value)
|
||||
.set('hours', Math.min(parseInt(newValue), 23))
|
||||
.set('minutes', 0)
|
||||
.format();
|
||||
emit('changed', model.value);
|
||||
} else if (newValue.length === 1) {
|
||||
// parse 1 to 01:00
|
||||
model.value = getLocalizedDayJs(model.value)
|
||||
.set('hours', Math.min(parseInt(newValue), 23))
|
||||
.set('minutes', 0)
|
||||
.format();
|
||||
emit('changed', model.value);
|
||||
}
|
||||
}
|
||||
|
||||
inputValue.value = getLocalizedDayJs(model.value).format('HH:mm');
|
||||
}
|
||||
|
||||
watch(model, (value) => {
|
||||
inputValue.value = value ? getLocalizedDayJs(value).format('HH:mm') : null;
|
||||
});
|
||||
|
||||
const timeInput = ref<HTMLInputElement | null>(null);
|
||||
const emit = defineEmits(['changed']);
|
||||
|
||||
useFocus(timeInput, { initialValue: props.focus });
|
||||
|
||||
const inputValue = ref(
|
||||
model.value ? getLocalizedDayJs(model.value).format('HH:mm') : null
|
||||
);
|
||||
const open = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TextInput
|
||||
v-bind="$attrs"
|
||||
v-model="inputValue"
|
||||
ref="timeInput"
|
||||
:class="
|
||||
twMerge('text-center w-24 px-3 py-2', size === 'large' && 'w-28')
|
||||
"
|
||||
@blur="updateTime"
|
||||
@keydown.enter="
|
||||
updateTime($event);
|
||||
open = false;
|
||||
"
|
||||
@keydown.tab="open = false"
|
||||
@focus="($event.target as HTMLInputElement).select()"
|
||||
@mouseup="($event.target as HTMLInputElement).select()"
|
||||
@click="($event.target as HTMLInputElement).select()"
|
||||
@pointerup="($event.target as HTMLInputElement).select()"
|
||||
@focusin="open = true"
|
||||
data-testid="time_picker_input"
|
||||
type="text" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, ref, watch } from 'vue';
|
||||
import TimePicker from '@/packages/ui/src/Input/TimePicker.vue';
|
||||
import { useFocusWithin } from '@vueuse/core';
|
||||
import DatePicker from '@/packages/ui/src/Input/DatePicker.vue';
|
||||
import {
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
getLocalizedDayJs,
|
||||
} from '@/packages/ui/src/utils/time';
|
||||
import dayjs from 'dayjs';
|
||||
import TimePickerSimple from '@/packages/ui/src/Input/TimePickerSimple.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
start: string;
|
||||
@@ -16,7 +16,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
// The timestamps for the changed event are UTC
|
||||
const emit = defineEmits(['changed']);
|
||||
const emit = defineEmits(['changed', 'close']);
|
||||
|
||||
const tempStart = ref(
|
||||
props.start ? getLocalizedDayJs(props.start).format() : dayjs().format()
|
||||
@@ -58,24 +58,26 @@ watch(focused, (newValue, oldValue) => {
|
||||
<div class="px-2">
|
||||
<div class="font-bold text-white text-sm pb-2">Start</div>
|
||||
<div class="space-y-2">
|
||||
<TimePicker
|
||||
<TimePickerSimple
|
||||
data-testid="time_entry_range_start"
|
||||
tabindex="0"
|
||||
:focus
|
||||
@changed="updateTimeEntry"
|
||||
v-model="tempStart"></TimePicker>
|
||||
v-model="tempStart"></TimePickerSimple>
|
||||
<DatePicker
|
||||
class="text-xs text-text-tertiary max-w-24 px-1.5 py-1.5"
|
||||
@changed="updateTimeEntry"
|
||||
@blur.stop.prevent="emit('close')"
|
||||
v-model="tempStart"></DatePicker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-2">
|
||||
<div class="font-bold text-white text-sm pb-2">End</div>
|
||||
<div v-if="tempEnd !== null" class="space-y-2">
|
||||
<TimePicker
|
||||
<TimePickerSimple
|
||||
data-testid="time_entry_range_end"
|
||||
@changed="updateTimeEntry"
|
||||
v-model="tempEnd"></TimePicker>
|
||||
v-model="tempEnd"></TimePickerSimple>
|
||||
<DatePicker
|
||||
class="text-xs text-text-tertiary max-w-24 px-1.5 py-1.5"
|
||||
@changed="updateTimeEntry"
|
||||
|
||||
Reference in New Issue
Block a user