change font to inter, scale down fonts, improve rounding/filter elements

This commit is contained in:
Gregor Vostrak
2025-07-17 16:33:31 +02:00
committed by Constantin Graf
parent e3cfc155b8
commit 38f38790d5
33 changed files with 4228 additions and 92 deletions

View File

@@ -15,7 +15,6 @@ use App\Models\Task;
use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Korridor\LaravelModelValidationRules\Rules\ExistsEloquent;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -160,30 +160,17 @@ body {
}
/* Inter Variable Font with browser compatibility considerations */
@font-face {
font-family: 'Outfit';
src: url('/fonts/Outfit-Regular.ttf');
font-weight: 400;
}
@font-face {
font-family: 'Outfit';
src: url('/fonts/Outfit-Medium.ttf');
font-weight: 500;
}
@font-face {
font-family: 'Outfit';
src: url('/fonts/Outfit-SemiBold.ttf');
font-weight: 600;
}
@font-face {
font-family: 'Outfit';
src: url('/fonts/Outfit-Bold.ttf');
font-weight: 700;
}
@font-face {
font-family: 'Outfit';
src: url('/fonts/Outfit-ExtraBold.ttf');
font-weight: 800;
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2 supports variations'),
url('/fonts/Inter-Variable.woff2') format('woff2-variations'),
url('/fonts/Inter-Variable.ttf') format('truetype supports variations'),
url('/fonts/Inter-Variable.ttf') format('truetype-variations');
font-weight: 100 900;
font-style: normal;
font-display: swap;
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
}
@layer base {

View File

@@ -127,7 +127,7 @@ const option = computed(() => ({
fontWeight: 600,
color: labelColor.value,
margin: 16,
fontFamily: 'Outfit, sans-serif',
fontFamily: 'Inter, sans-serif',
},
axisTick: {
lineStyle: {
@@ -139,7 +139,7 @@ const option = computed(() => ({
type: 'value',
axisLabel: {
color: labelColor.value,
fontFamily: 'Outfit, sans-serif',
fontFamily: 'Inter, sans-serif',
},
splitLine: {
lineStyle: {

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import Badge from '@/packages/ui/src/Badge.vue';
import { Button } from '@/Components/ui/button';
const props = defineProps<{
icon: Component;
@@ -12,32 +12,39 @@ import { twMerge } from 'tailwind-merge';
const activeClass = computed(() => {
if (props.active) {
return 'border-accent-300/50 bg-accent-300/10 hover:bg-accent-300/20';
return 'border-accent-300/50 bg-accent-50 hover:bg-accent-100 dark:border-accent-300/50 dark:bg-accent-300/5 dark:hover:bg-accent-300/10';
}
return '';
});
const iconClass = computed(() => {
return twMerge(
'-ml-0.5 h-4 w-4',
props.active ? 'dark:text-accent-300/80 text-accent-400/80' : 'text-text-quaternary'
);
});
</script>
<template>
<Badge
size="large"
tag="button"
<Button
variant="outline"
size="sm"
:class="
twMerge(
'cursor-pointer bg-input-background hover:bg-card-background transition flex',
activeClass
)
">
<component
:is="icon"
class="-ml-0.5 h-4 w-4 text-text-quaternary"></component>
:class="iconClass"
></component>
<span class="text-nowrap"> {{ title }} </span>
<div
v-if="count"
class="bg-accent-300/20 w-5 h-5 font-medium rounded flex items-center transition justify-center">
{{ count }}
</div>
</Badge>
</Button>
</template>
<style scoped></style>

View File

@@ -319,7 +319,7 @@ const tableData = computed(() => {
<div class="py-2.5 w-full border-b border-default-background-separator">
<MainContainer class="sm:flex space-y-4 sm:space-y-0 justify-between">
<div
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4">
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-3">
<div class="text-sm font-medium">Filters</div>
<MemberMultiselectDropdown
v-model="selectedMembers"

View File

@@ -19,6 +19,7 @@ import {
} from '@/Components/ui/number-field';
import { ArrowsUpDownIcon } from '@heroicons/vue/20/solid';
import { computed, ref, watch } from 'vue';
import { twMerge } from 'tailwind-merge';
// TimeEntryRoundingType definition
const TimeEntryRoundingType = {
Up: 'up' as const,
@@ -120,6 +121,21 @@ watch(() => props.minutes, (newMinutes) => {
watch(currentInterval, () => {
initializeSelectedInterval();
});
// Active styling similar to ReportingFilterBadge
const activeClass = computed(() => {
if (props.enabled) {
return 'border-accent-300/50 bg-accent-50 hover:bg-accent-100 dark:border-accent-300/50 dark:bg-accent-300/5 dark:hover:bg-accent-300/10';
}
return '';
});
const iconClass = computed(() => {
return twMerge(
'w-4 h-4',
props.enabled ? 'dark:text-accent-300/80 text-accent-400/80' : 'text-muted-foreground opacity-50'
);
});
</script>
<template>
@@ -128,14 +144,15 @@ watch(currentInterval, () => {
<Button
variant="outline"
size="sm"
class="text-sm">
<ArrowsUpDownIcon class="w-4 h-4" :class="enabled ? 'text-primary' : 'text-muted-foreground opacity-50'" />
:class="twMerge(activeClass)">
<ArrowsUpDownIcon :class="iconClass" />
Rounding {{ enabled ? 'on' : 'off' }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-72 p-4">
<div class="space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center justify-between">
<InputLabel for="enable-rounding" value="Enable Rounding" />
<Switch
id="enable-rounding"
@@ -143,6 +160,11 @@ watch(currentInterval, () => {
class="data-[state=checked]:bg-accent-500"
@update:model-value="updateEnabled" />
</div>
<div class="mb-3 pb-2 pt-1 text-xs text-muted-foreground border-b border-border-secondary text-text-tertiary">
Rounding is applied to each individual time entry, not to the accumulated total.
</div>
</div>
<div>
<InputLabel for="rounding-type" value="Rounding Type" class="mb-2" />

View File

@@ -43,7 +43,7 @@ const isRunningInDifferentOrganization = computed(() => {
</div>
</div>
<div>
<div class="text-text-secondary font-extrabold text-xs">
<div class="text-text-secondary font-medium text-xs">
Current Timer
</div>
<div class="text-text-primary font-medium text-lg">

View File

@@ -49,7 +49,7 @@ const option = computed(() => ({
fontSize: 16,
fontWeight: 600,
margin: 24,
fontFamily: 'Outfit, sans-serif',
fontFamily: 'Inter, sans-serif',
},
axisTick: {
lineStyle: {

View File

@@ -18,9 +18,9 @@ defineProps<{
<template>
<div
class="px-3.5 py-2 flex justify-between @container border-b border-card-background-separator">
class="px-3.5 py-2 flex justify-between @container border-b border-b-background-separator">
<div class="flex items-center min-w-[70px]">
<p class="font-semibold text-sm text-text-primary">
<p class="font-medium text-sm text-text-primary">
{{ formatHumanReadableDate(date) }}
</p>
</div>
@@ -28,7 +28,7 @@ defineProps<{
<DayOverviewCardChart :history="history"></DayOverviewCardChart>
</div>
<div
class="flex text-sm items-center justify-center text-text-secondary min-w-[65px] font-semibold">
class="flex text-sm items-center justify-center text-text-secondary min-w-[65px] font-medium">
{{
formatHumanReadableDuration(
duration,

View File

@@ -47,7 +47,7 @@ async function startTaskTimer() {
<template>
<div
class="px-3.5 py-2 grid grid-cols-5 border-b border-b-card-background-separator">
class="px-3.5 py-2 grid grid-cols-5 border-b border-b-background-separator">
<div class="col-span-4">
<p class="font-medium text-text-primary text-sm pb-1 truncate">
<span v-if="timeEntry.description"> {{ timeEntry.description }}</span>

View File

@@ -7,7 +7,7 @@ defineProps<{
</script>
<template>
<div class="px-4 py-2 2xl:py-3 border-b border-card-background-separator">
<div class="px-4 py-2 2xl:py-3 border-b border-b-background-separator">
<div class="col-span-2">
<div class="flex justify-between">
<p class="font-semibold text-sm text-text-primary">

View File

@@ -202,7 +202,7 @@ const option = computed(() => {
fontSize: 16,
fontWeight: 600,
margin: 24,
fontFamily: 'Outfit, sans-serif',
fontFamily: 'Inter, sans-serif',
color: labelColor.value,
},
axisTick: {
@@ -215,7 +215,7 @@ const option = computed(() => {
type: 'value',
axisLabel: {
color: labelColor.value,
fontFamily: 'Outfit, sans-serif',
fontFamily: 'Inter, sans-serif',
},
splitLine: {
lineStyle: {

View File

@@ -32,7 +32,7 @@ const open = useSessionStorage('nav-collapse-state-' + props.title, true);
<CollapsibleRoot v-else v-model:open="open"
><CollapsibleTrigger class="w-full group py-0.5">
<div
class="text-text-secondary group-hover:text-text-primary group-hover:bg-menu-active group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center justify-between">
class="text-text-secondary group-hover:text-text-primary group-hover:bg-menu-active group flex gap-x-2 rounded-md transition leading-6 py-0.5 px-2 font-medium text-sm items-center justify-between">
<div class="flex items-center gap-x-2">
<component
:is="icon"
@@ -41,7 +41,7 @@ const open = useSessionStorage('nav-collapse-state-' + props.title, true);
current
? 'text-icon-active'
: 'text-icon-default group-hover:text-icon-active',
'transition h-5 w-5 shrink-0',
'transition h-4 w-4 shrink-0',
]"
aria-hidden="true" />
<span>

View File

@@ -16,7 +16,7 @@ defineProps<{
current
? 'bg-menu-active text-text-primary'
: 'text-text-secondary group-hover:text-text-primary group-hover:bg-menu-active ',
'group flex gap-x-2 rounded-md transition leading-6 py-1 px-2 font-medium text-sm items-center',
'group flex gap-x-2 rounded-md transition leading-6 py-0.5 px-2 font-medium text-sm items-center',
]">
<component
:is="icon"
@@ -25,7 +25,7 @@ defineProps<{
current
? 'text-icon-active'
: 'text-icon-default group-hover:text-icon-active',
'transition h-5 w-5 shrink-0',
'transition h-4 w-4 shrink-0',
]"
aria-hidden="true" />
{{ title }}

View File

@@ -3,7 +3,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
export { default as Button } from './Button.vue'
export const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
@@ -11,7 +11,7 @@ export const buttonVariants = cva(
destructive:
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border shadow-xs hover:text-accent-foreground border-input dark:border-input hover:bg-white/15',
'border shadow-xs hover:text-text-primary bg-card-background dark:bg-transparent border-input dark:border-input hover:bg-white/5',
secondary:
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',

View File

@@ -14,7 +14,7 @@ const delegatedProps = computed(() => {
const forwardedProps = useForwardProps(delegatedProps)
const sizeClasses = computed(() => {
return props.size === 'small' ? 'h-[34px]' : 'h-[42px]'
return props.size === 'small' ? 'h-[34px] text-sm' : 'h-[42px]'
})
</script>

View File

@@ -170,7 +170,7 @@ const page = usePage<{
</nav>
<div
class="text-text-tertiary text-sm font-semibold pt-5 pb-1.5">
class="text-text-tertiary text-xs font-semibold pt-5 pb-1.5">
Manage
</div>
@@ -218,7 +218,7 @@ const page = usePage<{
</nav>
<div
v-if="canUpdateOrganization()"
class="text-text-tertiary text-sm font-semibold pt-5 pb-1.5">
class="text-text-tertiary text-xs font-semibold pt-5 pb-1.5">
Admin
</div>

View File

@@ -282,7 +282,7 @@ async function downloadExport(format: ExportFormat) {
<MainContainer
class="sm:flex space-y-4 sm:space-y-0 justify-between">
<div
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-4">
class="flex flex-wrap items-center space-y-2 sm:space-y-0 space-x-3">
<div class="text-sm font-medium">Filters</div>
<MemberMultiselectDropdown
v-model="selectedMembers"
@@ -372,14 +372,13 @@ async function downloadExport(format: ExportFormat) {
:icon="BillableIcon"></ReportingFilterBadge>
</template>
</SelectDropdown>
</div>
<div class="flex items-center space-x-3">
<ReportingRoundingControls
v-model:enabled="roundingEnabled"
v-model:type="roundingType"
v-model:minutes="roundingMinutes"
@change="updateFilteredTimeEntries" />
</div>
<div>
<DateRangePicker
v-model:start="startDate"
v-model:end="endDate"

View File

@@ -47,7 +47,7 @@ const tagClasses = computed(() => {
tagClasses,
badgeClasses[size],
borderClasses,
'rounded transition inline-flex items-center font-semibold text-text-primary disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
'rounded transition inline-flex items-center font-medium text-text-primary disabled:text-text-quaternary outline-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
props.class
)
">

View File

@@ -8,13 +8,13 @@ defineProps<{
</script>
<template>
<div class="flex w-full items-center justify-between pb-2.5 lg:pb-4">
<div class="flex w-full items-center justify-between pb-2.5 lg:pb-3">
<h3
class="text-text-primary font-semibold text-sm lg:text-base flex items-center space-x-2 lg:space-x-2.5">
class="text-text-primary font-medium text-sm lg:text-base flex items-center space-x-1.5 lg:space-x-2">
<component
:is="icon"
v-if="icon"
class="w-5 lg:w-6 text-icon-default"></component>
class="w-4 lg:w-4 text-icon-default"></component>
<span>
{{ title }}
</span>

View File

@@ -4,6 +4,7 @@ import {
PopoverContent,
PopoverTrigger,
} from '@/Components/ui/popover';
import { Button } from '@/Components/ui/button';
import { RangeCalendar } from '@/Components/ui/range-calendar';
import { CalendarDate } from '@internationalized/date';
import { CalendarIcon } from 'lucide-vue-next';
@@ -208,14 +209,15 @@ watch(open, (value) => {
<template>
<Popover v-model:open="open">
<PopoverTrigger as-child>
<button
<Button
variant="outline"
:class="
twMerge(
'flex w-full items-center justify-between whitespace-nowrap rounded-md border border-input-border bg-input-background px-3 h-[34px] shadow-sm data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
'flex w-full items-center justify-between whitespace-nowrap h-[34px] text-start',
!modelValue && 'text-muted-foreground'
)
">
<CalendarIcon class="mr-2 h-4 w-4" />
<CalendarIcon class="-ml-0.5 text-text-quaternary h-4 w-4" />
<template v-if="modelValue.start">
<template v-if="modelValue.end">
{{
@@ -242,23 +244,23 @@ watch(open, (value) => {
</template>
</template>
<template v-else> Pick a date </template>
</button>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<div class="flex divide-x divide-border-secondary">
<div
class="text-text-primary text-sm flex flex-col space-y-0.5 items-start py-2 px-2 [&_button:hover]:bg-tertiary [&_button]:rounded [&_button]:px-2 [&_button]:py-1">
<button @click="setToday">Today</button>
<button @click="setThisWeek">This Week</button>
<button @click="setLastWeek">Last Week</button>
<button @click="setLast14Days">Last 14 days</button>
<button @click="setThisMonth">This Month</button>
<button @click="setLastMonth">Last Month</button>
<button @click="setLast30Days">Last 30 days</button>
<button @click="setLast90Days">Last 90 days</button>
<button @click="setLast12Months">Last 12 months</button>
<button @click="setThisYear">This year</button>
<button @click="setLastYear">Last year</button>
class="text-text-primary text-sm flex flex-col space-y-0.5 items-start py-2 px-2">
<Button variant="ghost" size="sm" class="justify-start" @click="setToday">Today</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setThisWeek">This Week</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setLastWeek">Last Week</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setLast14Days">Last 14 days</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setThisMonth">This Month</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setLastMonth">Last Month</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setLast30Days">Last 30 days</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setLast90Days">Last 90 days</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setLast12Months">Last 12 months</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setThisYear">This year</Button>
<Button variant="ghost" size="sm" class="justify-start" @click="setLastYear">Last year</Button>
</div>
<div class="pl-2">
<RangeCalendar

View File

@@ -43,7 +43,7 @@ const organization = inject<ComputedRef<Organization>>('organization');
showDate
? 'text-xs py-1.5 font-semibold'
: 'text-sm py-1.5 font-medium',
organization?.time_format === '12-hours' ? 'w-[160px]' : 'w-[110px]',
organization?.time_format === '12-hours' ? 'w-[170px]' : 'w-[120px]',
open && 'border-card-border bg-card-background'
)
">

View File

@@ -38,7 +38,7 @@ function selectUnselectAll(value: boolean) {
<div class="flex items-center space-x-2">
<div class="w-5">
<svg
class="w-4 sm:w-5 text-icon-default group-hover:hidden block"
class="w-3 sm:w-4 text-icon-default group-hover:hidden block"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none">
@@ -54,15 +54,15 @@ function selectUnselectAll(value: boolean) {
class="group-hover:block hidden"
@update:checked="selectUnselectAll"></Checkbox>
</div>
<span class="font-semibold text-text-primary">
<span class="font-medium text-text-primary">
{{ formatWeekday(date) }}
</span>
<span class="font-semibold text-text-secondary">
<span class="font-medium text-text-secondary">
{{ formatDate(date, organization?.date_format) }}
</span>
</div>
<div class="text-text-secondary pr-[90px] lg:pr-[92px]">
<span class="font-semibold">
<span class="font-medium">
{{
formatHumanReadableDuration(
duration,

View File

@@ -211,7 +211,7 @@ useSelectEvents(filteredRecentlyTrackedTimeEntries,
v-model="tempDescription"
placeholder="What are you working on?"
data-testid="time_entry_description"
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3.5 border-b border-b-card-background-separator @2xl:px-4 text-base @4xl:text-lg text-text-primary font-medium bg-transparent border-none placeholder-muted focus:ring-0 transition"
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3.5 border-b border-b-card-background-separator @2xl:px-4 text-base @4xl:text-lg text-text-primary bg-transparent border-none placeholder-text-secondary font-medium focus:ring-0 transition"
type="text"
@keydown.enter="startTimerIfNotActive"
@keydown.esc="showDropdown = false"

View File

@@ -1,14 +1,14 @@
<x-filament-widgets::widget>
<x-filament::section>
<div>
<span class="text-gray-950 font-bold">Version</span>
<span class="text-gray-950 font-bold dark:text-white">Version</span>
@if($version !== null)
<span>v{{ $version }}</span>
@else
<span>-</span>
@endif
<br>
<span class="text-gray-950 font-bold">Build</span>
<span class="text-gray-950 font-bold dark:text-white">Build</span>
@if($build !== null)
<span>{{ $build }}</span>
@else

View File

@@ -4,14 +4,15 @@ import typography from "@tailwindcss/typography";
/** @type {import("tailwindcss").Config} */
export default {
darkMode: ["selector", "class"],
darkMode: ["selector", ".dark"],
content: [
"./extensions/Invoicing/resources/js/**/*.vue",
"./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
"./vendor/laravel/jetstream/**/*.blade.php",
"./storage/framework/views/*.php",
"./resources/views/**/*.blade.php",
"./resources/js/**/*.vue"
"./resources/js/**/*.vue",
"./resources/js/**/*.ts"
],
theme: {
extend: {
@@ -24,10 +25,25 @@ export default {
},
fontFamily: {
sans: [
"Outfit",
"Inter",
...defaultTheme.fontFamily.sans
]
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.8125rem', { lineHeight: '1.125rem' }],
base: ['0.875rem', { lineHeight: '1.25rem' }],
lg: ['1rem', { lineHeight: '1.5rem' }],
xl: ['1.125rem', { lineHeight: '1.75rem' }],
'2xl': ['1.25rem', { lineHeight: '1.75rem' }],
'3xl': ['1.5rem', { lineHeight: '2rem' }],
'4xl': ['1.75rem', { lineHeight: '2.25rem' }],
'5xl': ['2rem', { lineHeight: '1' }],
'6xl': ['2.25rem', { lineHeight: '1' }],
'7xl': ['2.5rem', { lineHeight: '1' }],
'8xl': ['3rem', { lineHeight: '1' }],
'9xl': ['3.5rem', { lineHeight: '1' }]
},
colors: {
ring: "var(--ring)",
primary: {