Maintenance: Use eslint-plugin-better-tailwindcss for tailwind class linting in vue templates

Approved-by: Dusan Vuckovic <dv@zammad.com>
This commit is contained in:
Benjamin Scharf
2026-09-28 15:43:23 +02:00
parent 619ef87bb2
commit 33e72ba0f4
119 changed files with 344 additions and 209 deletions

View File

@@ -2,24 +2,7 @@
"semi": false,
"singleQuote": true,
"printWidth": 100,
"sortTailwindcss": {
"stylesheet": "./app/frontend/apps/desktop/styles/main.css"
},
"sortPackageJson": false,
"overrides": [
{
"files": ["./app/frontend/apps/desktop/**"],
"options": {
"sortTailwindcss": { "stylesheet": "./app/frontend/apps/desktop/styles/main.css" }
}
},
{
"files": ["./app/frontend/apps/mobile/**"],
"options": {
"sortTailwindcss": { "stylesheet": "./app/frontend/apps/mobile/styles/main.css" }
}
}
],
"ignorePatterns": [
"app/frontend/**/graphql/**/*.ts",
"!app/frontend/tests/graphql/**/*.ts",

View File

@@ -2,7 +2,7 @@
<template>
<div
class="before:motion-reduce:animation-none relative size-8 rounded-full before:absolute before:inset-0 before:-z-1 before:size-full before:w-full before:animate-spin before:rounded-full before:bg-linear-to-r before:from-blue-800 before:to-pink-200 before:[animation-duration:2s]"
class="relative size-8 rounded-full before:absolute before:inset-0 before:-z-1 before:size-full before:w-full before:animate-spin before:rounded-full before:bg-linear-to-r before:from-blue-800 before:to-pink-200 before:[animation-duration:2s] motion-reduce:before:animate-none"
>
<div
class="flex h-full rounded-[inherit] border border-transparent bg-neutral-50 bg-clip-padding p-1 dark:bg-gray-500"

View File

@@ -125,7 +125,7 @@ const { transitions } = useTransitionConfig()
:id="dialogId"
tag="div"
disable-teleport
class="absolute top-[50%] z-50 size-full translate-y-[-50%] ltr:left-[50%] ltr:translate-x-[-50%] rtl:right-[50%] rtl:-translate-x-[-50%]"
class="absolute top-[50%] z-50 size-full translate-y-[-50%] ltr:left-[50%] ltr:-translate-x-1/2 rtl:right-[50%] rtl:translate-x-1/2"
:class="{ 'z-40': isFullscreen, hidden: !isActive }"
role="dialog"
backdrop-class="z-40"
@@ -143,9 +143,7 @@ const { transitions } = useTransitionConfig()
>
<div class="flex items-center justify-between bg-neutral-50 dark:bg-gray-500">
<slot name="header">
<div
class="flex items-center gap-2 text-xl leading-snug text-gray-100 dark:text-neutral-400"
>
<div class="flex items-center gap-2 text-xl/snug text-gray-100 dark:text-neutral-400">
<CommonIcon v-if="headerIcon" size="small" :name="headerIcon" />
<h3 :id="`${dialogId}-title`">{{ $t(headerTitle) }}</h3>
</div>

View File

@@ -28,7 +28,7 @@ const errorImage = computed(() => {
<template>
<img class="max-h-96 w-135" :alt="$t('Error')" :src="errorImage" />
<h1 class="text-center text-xl leading-snug text-black dark:text-white">
<h1 class="text-center text-xl/snug text-black dark:text-white">
{{ $t(options?.title) }}
</h1>
<CommonLabel class="mx-auto max-w-prose text-center" tag="p">

View File

@@ -162,7 +162,7 @@ const onPrimaryClick = () => {
</div>
<div
class="flex min-w-0 flex-1 flex-col text-sm leading-snug text-black dark:text-white"
class="flex min-w-0 flex-1 flex-col text-sm/snug text-black dark:text-white"
:class="{ 'group-active:text-white': canPreview }"
>
<div class="flex">

View File

@@ -324,7 +324,7 @@ const { transitions } = useTransitionConfig()
{ 'transition-all': !isResizing, hidden: !isActive },
isSmallScreen
? 'inset-6 w-auto overflow-hidden rounded-xl border print:inset-0'
: 'overflow-clip-x inset-y-0 inset-e-0 w-full rounded-s-xl border-y border-s lg:w-(--flyout-container-width) print:w-full',
: 'inset-y-0 inset-e-0 w-full overflow-x-clip rounded-s-xl border-y border-s lg:w-(--flyout-container-width) print:w-full',
]"
:fullscreen="isFullscreen"
:aria-labelledby="`${flyoutId}-title`"

View File

@@ -44,7 +44,7 @@ const descriptionOutput = computed(() => {
<CommonLabel
v-if="event.details"
class="max-w-md cursor-text rounded bg-neutral-200 px-0.5 font-mono break-word text-black dark:bg-gray-400 dark:text-white"
class="max-w-md cursor-text rounded-sm bg-neutral-200 px-0.5 font-mono break-word text-black dark:bg-gray-400 dark:text-white"
:class="{
'me-1': event.showSeparator || event.additionalDetails,
'ms-1': descriptionOutput,
@@ -63,7 +63,7 @@ const descriptionOutput = computed(() => {
<CommonLabel
v-if="event.additionalDetails"
class="cursor-text rounded bg-neutral-200 px-0.5 font-mono break-word text-black dark:bg-gray-400 dark:text-white"
class="cursor-text rounded-sm bg-neutral-200 px-0.5 font-mono break-word text-black dark:bg-gray-400 dark:text-white"
>{{ event.additionalDetails }}</CommonLabel
>
</div>

View File

@@ -15,7 +15,7 @@ const { event } = defineProps<Props>()
<template>
<span>
<CommonTranslateRenderer
class="text-sm leading-snug text-gray-100 dark:text-neutral-400"
class="text-sm/snug text-gray-100 dark:text-neutral-400"
:source="__('Email sent to %s')"
:placeholders="[
{

View File

@@ -20,7 +20,7 @@ const actionName2Source: Record<string, string> = {
<template>
<span>
<CommonTranslateRenderer
class="text-sm leading-snug text-gray-100 dark:text-neutral-400"
class="text-sm/snug text-gray-100 dark:text-neutral-400"
:source="actionName2Source[event.actionName]"
:placeholders="[
{

View File

@@ -56,7 +56,7 @@ const actionName2Placeholder: Record<string, RenderPlaceholder[]> = {
<template>
<span>
<CommonTranslateRenderer
class="text-sm leading-snug text-gray-100 dark:text-neutral-400"
class="text-sm/snug text-gray-100 dark:text-neutral-400"
:source="actionName2Source[event.actionName]"
:placeholders="actionName2Placeholder[event.actionName]"
/>

View File

@@ -16,7 +16,7 @@ const { createdAt } = defineProps<Props>()
class="flex-initial rounded-t-lg border border-b-0 border-neutral-100 bg-blue-200 dark:border-gray-700 dark:bg-gray-700 print:border-black"
>
<CommonLabel
class="m-1 rounded p-1 text-black dark:text-white"
class="m-1 rounded-sm p-1 text-black dark:text-white"
prefix-icon="calendar-date-time"
size="medium"
>

View File

@@ -195,7 +195,7 @@ const vFocus = (el: HTMLElement) => {
// Styling
const focusClasses = computed(() => {
let classes =
'group-focus-within:before:absolute group-focus-within:before:-left-[5px] group-focus-within:before:top-1/2 group-focus-within:before:z-0 group-focus-within:before:h-[calc(100%+10px)] group-focus-within:before:w-[calc(100%+10px)] group-focus-within:before:-translate-y-1/2 group-focus-within:before:rounded-md group-focus-visible-within group-focus-visible:before:outline-1 group-focus-visible:before:outline-blue-800'
'group-focus-within:before:absolute group-focus-within:before:top-1/2 group-focus-within:before:left-[-5px] group-focus-within:before:z-0 group-focus-within:before:size-[calc(100%+10px)] group-focus-within:before:-translate-y-1/2 group-focus-within:before:rounded-md group-focus-visible:before:outline-1 group-focus-visible:before:outline-blue-800'
if (props.alternativeBackground) {
classes += ' group-focus-within:before:bg-neutral-50 dark:group-focus-within:before:bg-gray-500'
@@ -237,7 +237,7 @@ const editBackgroundClass = computed(() =>
const hoverClasses = computed(() => {
let classes =
'before:absolute before:-left-[5px] before:top-1/2 before:-translate-y-1/2 before:-z-10 before:h-[calc(100%+10px)] before:w-[calc(100%+10px)] before:rounded-md'
'before:absolute before:top-1/2 before:left-[-5px] before:-z-10 before:size-[calc(100%+10px)] before:-translate-y-1/2 before:rounded-md'
if (props.alternativeBackground) {
classes +=
@@ -305,7 +305,7 @@ defineExpose({
<div
v-else
class="flex max-w-full items-center gap-2 before:absolute before:top-1/2 before:-left-1.25 before:z-0 before:h-[calc(100%+10px)] before:w-[calc(100%+10px)] before:-translate-y-1/2 before:rounded-md focus-within:before:outline-1 focus-within:before:outline-blue-800"
class="flex max-w-full items-center gap-2 before:absolute before:top-1/2 before:-left-1.25 before:z-0 before:size-[calc(100%+10px)] before:-translate-y-1/2 before:rounded-md focus-within:before:outline-1 focus-within:before:outline-blue-800"
:class="[{ 'w-full': block }, editBackgroundClass, fontSizeClassMap[size]]"
>
<div class="relative z-10 w-full ltr:pr-14 rtl:pl-14">

View File

@@ -37,7 +37,7 @@ defineEmits<{
:to="fullscreen ? '#app' : teleportTo"
>
<div
class="absolute top-0 right-0 bottom-0 left-0 z-30 h-full w-full bg-alpha-900 print:hidden"
class="absolute inset-0 z-30 size-full bg-alpha-900 print:hidden"
:class="backdropClass"
role="presentation"
tabindex="-1"

View File

@@ -531,7 +531,7 @@ const goToChildPage = ({ option, noFocus }: { option: AutoCompleteOption; noFocu
/>
</div>
<div v-else-if="isLoading" class="px-2.5 py-2.5">
<div v-else-if="isLoading" class="p-2.5">
<CommonLoader class="w-full" size="small" loading />
</div>
<CommonSelectItem

View File

@@ -546,7 +546,7 @@ watch(
class="relative"
:inert="isSorting"
:class="{
'opacity-50 before:absolute before:z-20 before:h-full before:w-full': isSorting,
'opacity-50 before:absolute before:z-20 before:size-full': isSorting,
}"
>
<template v-for="(item, index) in loadedItems" :key="`${index}-${item.id}`">

View File

@@ -10,7 +10,7 @@ defineProps<Props>()
<template>
<caption
class="mb-2 text-left text-sm leading-snug text-gray-100 dark:text-neutral-400"
class="mb-2 text-left text-sm/snug text-gray-100 dark:text-neutral-400"
:class="{ 'sr-only absolute': !show }"
>
<slot />

View File

@@ -47,7 +47,7 @@ const { getTicketNumberWithTitle } = useTicketNumberAndTitle()
:is="component"
v-else
v-tooltip="!noLink ? getTicketNumberWithTitle(ticket?.number, ticket?.title) : undefined"
class="flex! grow gap-2 rounded-md break-words group-hover/tab:bg-blue-600 hover:no-underline! focus-visible:rounded-md focus-visible:outline-hidden group-hover/tab:dark:bg-blue-900"
class="flex! grow gap-2 rounded-md wrap-break-word group-hover/tab:bg-blue-600 hover:no-underline! focus-visible:rounded-md focus-visible:outline-hidden group-hover/tab:dark:bg-blue-900"
:class="{
group: !noLink,
'items-start': !noWrap,

View File

@@ -8,7 +8,7 @@ withDefaults(defineProps<{ label?: string }>(), {
<template>
<div
class="indicator absolute -top-[3px] h-1.5 w-1.5 animate-pulse rounded-full bg-pink-500 ltr:-right-[3px] rtl:-left-[3px]"
class="indicator absolute top-[-3px] size-1.5 animate-pulse rounded-full bg-pink-500 ltr:right-[-3px] rtl:left-[-3px]"
role="status"
aria-live="polite"
:aria-label="$t(label)"

View File

@@ -575,7 +575,7 @@ useFormBlock(
role="listitem"
>
<div
class="inline-flex items-center gap-1 rounded bg-white px-1.5 py-0.5 text-xs text-black dark:bg-gray-200 dark:text-white formkit-alternative-background:bg-neutral-100 dark:formkit-alternative-background:bg-gray-200"
class="inline-flex items-center gap-1 rounded-sm bg-white px-1.5 py-0.5 text-xs/snug text-black dark:bg-gray-200 dark:text-white formkit-alternative-background:bg-neutral-100 dark:formkit-alternative-background:bg-gray-200"
>
<CommonIcon
v-if="getSelectedAutocompleteOptionIcon(selectedValue)"

View File

@@ -48,7 +48,7 @@ defineExpose({
ref="button"
v-tooltip="$t(action.label || action.name)"
type="button"
class="transition-color relative flex items-center gap-1 rounded-lg p-1.5 focus-visible-app-default hover:bg-blue-600 hover:text-black active:bg-blue-800! active:text-white aria-expanded:text-white dark:hover:bg-blue-900 dark:hover:text-white"
class="relative flex items-center gap-1 rounded-lg p-1.5 focus-visible-app-default transition-colors hover:bg-blue-600 hover:text-black active:bg-blue-800! active:text-white aria-expanded:text-white dark:hover:bg-blue-900 dark:hover:text-white"
:class="[
action.class,
{
@@ -70,7 +70,7 @@ defineExpose({
/>
<div
v-if="action.name === 'textColor'"
class="color-indicator absolute start-1/2 bottom-[0.4rem] box-content h-1.5 w-1.5 rounded-[1px] border border-blue-50 ltr:-translate-x-[0.25rem] rtl:translate-x-[0.25rem] dark:border-gray-800"
class="color-indicator absolute inset-s-1/2 bottom-[0.4rem] box-content size-1.5 rounded-[1px] border border-blue-50 ltr:-translate-x-1 rtl:translate-x-1 dark:border-gray-800"
:style="{
backgroundColor: props.editor?.getAttributes('textStyle')?.color
? props.editor.getAttributes('textStyle').color

View File

@@ -94,7 +94,7 @@ const { isOverDropZone } = useDropZone(dropZoneElement, {
>
<div
v-if="isOverDropZone"
class="w-full rounded text-center outline-1 outline-blue-800 outline-dashed"
class="w-full rounded-sm text-center outline-1 outline-blue-800 outline-dashed"
>
<CommonLabel class="py-2 text-blue-800!" prefix-icon="upload">
{{ $t('Drop image file here') }}

View File

@@ -93,7 +93,7 @@ const { collapseEnter, collapseAfterEnter, collapseLeave } = useTransitionCollap
<template>
<output
:id="context.id"
class="block rounded-lg bg-blue-200 focus:outline focus:outline-1 focus:outline-offset-1 focus:outline-blue-800 hover:focus:outline-blue-800 dark:bg-gray-700"
class="block rounded-lg bg-blue-200 focus:outline-1 focus:outline-offset-1 focus:outline-blue-800 hover:focus:outline-blue-800 dark:bg-gray-700"
role="tree"
:class="context.classes.input"
:name="context.node.name"
@@ -175,7 +175,7 @@ const { collapseEnter, collapseAfterEnter, collapseLeave } = useTransitionCollap
/>
<CommonIcon
v-if="option.children && !valueLookup[option.value]"
class="shrink-0 fill-stone-200 hover:fill-black focus:outline-hidden focus-visible:rounded-xs focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-blue-800 dark:fill-neutral-500 dark:hover:fill-white"
class="shrink-0 fill-stone-200 hover:fill-black focus:outline-hidden focus-visible:rounded-xs focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-blue-800 dark:fill-neutral-500 dark:hover:fill-white"
:aria-label="i18n.t('Toggle group')"
:name="collapseLookup[option.value] ? 'chevron-up' : 'chevron-down'"
size="xs"

View File

@@ -247,7 +247,7 @@ setupMissingOrDisabledOptionHandling()
role="listitem"
>
<div
class="inline-flex items-center gap-1 rounded bg-white px-1.5 py-0.5 text-xs text-black dark:bg-gray-200 dark:text-white formkit-alternative-background:bg-neutral-100 dark:formkit-alternative-background:bg-gray-200"
class="inline-flex items-center gap-1 rounded-sm bg-white px-1.5 py-0.5 text-xs/snug text-black dark:bg-gray-200 dark:text-white formkit-alternative-background:bg-neutral-100 dark:formkit-alternative-background:bg-gray-200"
>
<CommonIcon
v-if="getSelectedOptionIcon(selectedValue)"

View File

@@ -313,7 +313,7 @@ setupMissingOrDisabledOptionHandling()
role="listitem"
>
<div
class="inline-flex cursor-default items-center gap-1 rounded bg-white px-1.5 py-0.5 text-xs text-black dark:bg-gray-200 dark:text-white formkit-alternative-background:bg-neutral-100 dark:formkit-alternative-background:bg-gray-200"
class="inline-flex cursor-default items-center gap-1 rounded-sm bg-white px-1.5 py-0.5 text-xs/snug text-black dark:bg-gray-200 dark:text-white formkit-alternative-background:bg-neutral-100 dark:formkit-alternative-background:bg-gray-200"
>
<CommonIcon
v-if="getSelectedOptionIcon(selectedValue)"

View File

@@ -29,7 +29,7 @@ const { debouncedLoading } = useDebouncedLoading({
<div v-if="loading || debouncedLoading" class="p-3" :class="{ invisible: !debouncedLoading }">
<!-- Visibility icon and title. -->
<div class="mb-3 flex items-center gap-1.25">
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="h-4 w-4 shrink-0" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="size-4 shrink-0" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="h-5 w-2/3" />
</div>

View File

@@ -90,11 +90,7 @@ const allFilteredEntries = computed<NavigationMenuEntry[]>(() => {
>{{ __('No results found') }}
</CommonLabel>
<ul v-else>
<li
v-for="category in categories"
:key="category.label"
class="bg-neutral-00 relative z-0 mb-1"
>
<li v-for="category in categories" :key="category.label" class="relative z-0 mb-1">
<CommonSectionCollapse
v-if="permittedEntries[category.label].length > 0"
:id="category.id"

View File

@@ -10,7 +10,7 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
<!-- :TODO label for screen reader? or to many translation strings -->
<CommonSkeleton
:style="{ 'animation-delay': `${0.1}s` }"
class="row-span-2 h-12.5 w-12.5 space-y-2"
class="row-span-2 size-12.5 space-y-2"
rounded
/>
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="h-6 w-full" />

View File

@@ -180,7 +180,7 @@ const { resetQuickSearchInputField } = useQuickSearchInput()
:class="{
'opacity-0 transition-opacity': !isTouchDevice,
}"
class="absolute end-2 top-3 justify-end group-hover/recent-search:opacity-100 focus:opacity-100"
class="absolute inset-e-2 top-3 justify-end group-hover/recent-search:opacity-100 focus:opacity-100"
icon="x-lg"
size="small"
variant="remove"

View File

@@ -82,7 +82,7 @@ const ariaChecked = computed(() => {
<button
type="button"
role="checkbox"
class="relative inline-flex flex-shrink-0 cursor-pointer items-center rounded-full bg-stone-200 ring-1 ring-neutral-100 focus-visible-app-default hover:outline-1 hover:outline-offset-2 hover:outline-blue-600 dark:bg-gray-500 dark:ring-gray-900"
class="relative inline-flex shrink-0 cursor-pointer items-center rounded-full bg-stone-200 ring-1 ring-neutral-100 focus-visible-app-default hover:outline-1 hover:outline-offset-2 hover:outline-blue-600 dark:bg-gray-500 dark:ring-gray-900"
:class="[
trackSizeClasses,
{

View File

@@ -28,7 +28,7 @@ onKeyDown('Escape', cancelBulkAction)
class="flex min-w-lg flex-col gap-3 rounded-xl border border-neutral-100 bg-neutral-50 p-3 focus:outline-none dark:border-gray-900 dark:bg-gray-500"
>
<div class="flex items-center justify-between bg-neutral-50 dark:bg-gray-500">
<div class="flex items-center gap-2 text-xl leading-snug text-gray-100 dark:text-neutral-400">
<div class="flex items-center gap-2 text-xl/snug text-gray-100 dark:text-neutral-400">
<CommonLabel size="xl" tag="h3">{{ $t('Confirm bulk action') }}</CommonLabel>
</div>
<CommonButton

View File

@@ -25,15 +25,15 @@ const { debouncedLoading } = useDebouncedLoading({
:class="{ invisible: !debouncedLoading }"
>
<div class="flex h-9 items-center gap-2">
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="h-5 w-5" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="size-5" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="h-4 w-4/5" />
</div>
<div class="flex h-9 items-center gap-2">
<CommonSkeleton :style="{ 'animation-delay': `${0.2}s` }" class="h-5 w-5" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.2}s` }" class="size-5" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.2}s` }" class="h-4 w-3/5" />
</div>
<div class="flex h-9 items-center gap-2">
<CommonSkeleton :style="{ 'animation-delay': `${0.3}s` }" class="h-5 w-5" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.3}s` }" class="size-5" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.3}s` }" class="h-4 w-full" />
</div>
</div>

View File

@@ -28,20 +28,12 @@ const { debouncedLoading } = useDebouncedLoading({
<div class="mb-2.5 space-y-2 border-b border-neutral-100 pb-2.5 dark:border-gray-900">
<CommonSkeleton :style="{ 'animation-delay': `${0.3}s` }" class="h-4 w-1/4" />
<div class="flex w-full items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.4}s` }"
class="h-6 w-6 shrink-0"
rounded
/>
<CommonSkeleton :style="{ 'animation-delay': `${0.4}s` }" class="size-6 shrink-0" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.4}s` }" class="h-6 w-1/2" />
</div>
<CommonSkeleton :style="{ 'animation-delay': `${0.5}s` }" class="h-4 w-1/3" />
<div class="flex w-full items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.6}s` }"
class="h-6 w-6 shrink-0"
rounded
/>
<CommonSkeleton :style="{ 'animation-delay': `${0.6}s` }" class="size-6 shrink-0" rounded />
<CommonSkeleton :style="{ 'animation-delay': `${0.6}s` }" class="h-6 w-3/4" />
</div>
</div>

View File

@@ -221,7 +221,7 @@ const { transitions } = useTransitionConfig()
</CommonLabel>
<ul class="list-disc ltr:pl-5 rtl:pr-5">
<li v-for="app in authenticatorApps" :key="app.key">
<CommonLink class="text-sm leading-snug" :link="app.link" open-in-new-tab>
<CommonLink class="text-sm/snug" :link="app.link" open-in-new-tab>
{{ $t(app.label) }}
</CommonLink>
</li>
@@ -252,7 +252,7 @@ const { transitions } = useTransitionConfig()
<div
v-show="showSecretOverlay"
id="qr-code-secret-overlay"
class="bg-opacity-90 absolute top-0 right-0 bottom-0 left-0 flex flex-col items-center justify-center gap-1.5 rounded-lg bg-black"
class="absolute inset-0 flex flex-col items-center justify-center gap-1.5 rounded-lg bg-black/90"
role="presentation"
>
<span

View File

@@ -68,7 +68,7 @@ const { organizationDisplayName } = useOrganizationEntity(organization)
<component
:is="nameComponent"
v-if="dense"
class="text-sm leading-snug"
class="text-sm/snug"
:class="{ group: !noLink }"
:link="!noLink ? `/users/${getIdFromGraphQLId(user.id!)}` : undefined"
>

View File

@@ -70,7 +70,7 @@ const overflowCount = computed(() => {
<template #default="slotProps">
<slot v-bind="slotProps">
<div
class="flex h-8 w-8 items-center justify-center rounded-full bg-green-200 text-sm text-gray-300 dark:bg-gray-600 dark:text-neutral-400"
class="flex size-8 items-center justify-center rounded-full bg-green-200 text-sm/snug text-gray-300 dark:bg-gray-600 dark:text-neutral-400"
>
{{ overflowCount }}
</div>

View File

@@ -10,7 +10,7 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
<!-- :TODO label for screen reader? or to many translation strings -->
<CommonSkeleton
:style="{ 'animation-delay': `${0.1}s` }"
class="row-span-2 h-12.5 w-12.5 space-y-2"
class="row-span-2 size-12.5 space-y-2"
rounded
/>
<CommonSkeleton :style="{ 'animation-delay': `${0.1}s` }" class="h-6 w-full" />

View File

@@ -79,7 +79,7 @@ const currentViewTitle = computed(
:link="taskbarTabLink"
:class="{
[activeBackgroundColor]: taskbarTabActive,
'group-hover/tab:bg-blue-60': collapsed,
'group-hover/tab:bg-blue-600': collapsed,
'rounded-lg!': !collapsed,
}"
internal

View File

@@ -69,7 +69,7 @@ const confirmRemoveUserTaskbarTab = async () => {
<CommonButton
v-tooltip="$t('Close this tab')"
:class="{ 'opacity-0 transition-opacity': !isTouchDevice }"
class="absolute end-2 top-3 group-hover/tab:opacity-100 focus:opacity-100"
class="absolute inset-e-2 top-3 group-hover/tab:opacity-100 focus:opacity-100"
icon="x-lg"
size="small"
variant="remove"

View File

@@ -312,7 +312,7 @@ const { isTouchDevice } = useTouchDevice()
:taskbar-tab-link="getTaskbarTabLink(tabEntityKey)"
:collapsed="collapsed"
:is-active="index === selectedItemIndex"
class="group/link peer-focus-visible:trl:pl-(--tab-remove-bar-button-width) focus-visible-app-default [--tab-remove-bar-button-width:2rem] group-hover/tab:ltr:pr-(--tab-remove-bar-button-width) peer-focus-visible:ltr:pr-(--tab-remove-bar-button-width) group-hover/tab:rtl:pl-(--tab-remove-bar-button-width)"
class="group/link focus-visible-app-default [--tab-remove-bar-button-width:2rem] group-hover/tab:ltr:pr-(--tab-remove-bar-button-width) peer-focus-visible:ltr:pr-(--tab-remove-bar-button-width) group-hover/tab:rtl:pl-(--tab-remove-bar-button-width) peer-focus-visible:rtl:pl-(--tab-remove-bar-button-width)"
:class="{
'rounded-none group-first/tab:rounded-t-[10px] group-last/tab:rounded-b-[10px] focus-visible:-outline-offset-1!':
collapsed,

View File

@@ -14,7 +14,7 @@ const labelWidths = ['w-3/4', 'w-1/2', 'w-2/3', 'w-5/6', 'w-1/2']
<template>
<div v-if="collapsed" class="flex justify-center py-1">
<CommonSkeleton class="h-9 w-9" />
<CommonSkeleton class="size-9" />
</div>
<div v-else class="flex flex-col gap-1.5 p-1">
<div
@@ -22,7 +22,7 @@ const labelWidths = ['w-3/4', 'w-1/2', 'w-2/3', 'w-5/6', 'w-1/2']
:key="index"
class="flex items-center gap-2 rounded-md px-2 py-3"
>
<CommonSkeleton :style="{ 'animation-delay': `${index * 0.1}s` }" class="h-4 w-4 shrink-0" />
<CommonSkeleton :style="{ 'animation-delay': `${index * 0.1}s` }" class="size-4 shrink-0" />
<CommonSkeleton
:style="{ 'animation-delay': `${index * 0.1 + 0.05}s` }"
:class="width"

View File

@@ -36,7 +36,7 @@ const backgroundVariantClasses = computed(() => {
<template>
<main
ref="scroll-container"
class="h-full w-full text-gray-100 dark:text-neutral-400"
class="size-full text-gray-100 dark:text-neutral-400"
:class="[
backgroundVariantClasses,
{

View File

@@ -52,7 +52,7 @@ const isTicketAgent = computed(() => hasPermission('ticket.agent') ?? false)
:class="{ 'ltr:ml-auto rtl:mr-auto': !collapsed }"
>
<!-- :TODO Add custom branding -->
<CommonIcon name="logo" class="z-10 block h-9 w-9" />
<CommonIcon name="logo" class="z-10 block size-9" />
</component>
</header>
</template>

View File

@@ -67,7 +67,7 @@ const handleLinkClick = (notification: OnlineNotification) => {
<CommonLabel
:id="`notification-${notification.id}`"
tag="p"
class="inline! text-lg leading-5 wrap-anywhere text-black dark:text-white"
class="inline! text-lg/snug wrap-anywhere text-black dark:text-white"
:class="{ 'group-hover/link:underline': notification.createdBy }"
v-html="highlightedMessage"
/>

View File

@@ -93,5 +93,5 @@ provideCurrentTaskbarTab({
<CommonError :options="pageError" authenticated />
</LayoutMain>
<slot v-else-if="showContent && pageError !== undefined" />
<div v-else class="flex h-full w-full grow flex-col bg-blue-50 dark:bg-gray-800" />
<div v-else class="flex size-full grow flex-col bg-blue-50 dark:bg-gray-800" />
</template>

View File

@@ -23,7 +23,7 @@ defineProps<Props>()
<CommonThirdPartyAuthenticationButton
v-for="provider of providers"
:key="provider.name"
class="grow basis-[calc(50%-theme(spacing.2))]"
class="grow basis-[calc(50%-(--spacing(2)))]"
:url="provider.url"
:button-prefix-icon="provider.icon"
button-size="large"

View File

@@ -298,7 +298,7 @@ const { switchValue, toggleBetaUiSwitch } = useBetaUi()
<CommonLabel
v-if="loginFlow.state === '2fa-select'"
class="mt-3 mb-3 text-stone-200 dark:text-neutral-500"
class="my-3 text-stone-200 dark:text-neutral-500"
>
{{ $t('Contact the administrator if you have any problems logging in.') }}
</CommonLabel>

View File

@@ -12,6 +12,5 @@ const session = useSessionStore()
<LayoutMain>
Hello, {{ session.user?.fullname }}!
<!-- TODO: content -->
<div class="bg-test h-20">TEST</div>
</LayoutMain>
</template>

View File

@@ -1606,7 +1606,7 @@ const { openFeedbackDialog } = useFeedbackDialog()
<CommonSkeleton class="h-8 w-full" />
Avatar example
<CommonSkeleton rounded class="h-8 w-8" />
<CommonSkeleton rounded class="size-8" />
Table Skeleton
<CommonTableSkeleton />
@@ -2203,7 +2203,7 @@ const { openFeedbackDialog } = useFeedbackDialog()
<div class="mb-4 w-96">
<CommonTabGroup v-model="activeTab" :tabs="playgroundTabItems" select-first-by-default />
</div>
<pre class="mb-6 rounded bg-blue-100 px-3 py-1 font-mono text-xs dark:bg-gray-700">
<pre class="mb-6 rounded-sm bg-blue-100 px-3 py-1 font-mono text-xs/snug dark:bg-gray-700">
active: {{ activeTab }}</pre>
<h3 class="mb-2">2. Tab Group — Multi Select (scroll, no marker)</h3>
@@ -2219,7 +2219,7 @@ active: {{ activeTab }}</pre>
multiple
/>
</div>
<pre class="mb-6 rounded bg-blue-100 px-3 py-1 font-mono text-xs dark:bg-gray-700">
<pre class="mb-6 rounded-sm bg-blue-100 px-3 py-1 font-mono text-xs/snug dark:bg-gray-700">
active: {{ activeFilters }}</pre>
<h3 class="mb-2">3. Tab Group — Single Select (overflow menu)</h3>
@@ -2235,7 +2235,7 @@ active: {{ activeFilters }}</pre>
select-first-by-default
/>
</div>
<pre class="mb-6 rounded bg-blue-100 px-3 py-1 font-mono text-xs dark:bg-gray-700">
<pre class="mb-6 rounded-sm bg-blue-100 px-3 py-1 font-mono text-xs/snug dark:bg-gray-700">
active: {{ activeTabOverflow }}</pre>
<h3 class="mb-2">4. Tab Group — Multi Select (overflow menu)</h3>
@@ -2252,7 +2252,7 @@ active: {{ activeTabOverflow }}</pre>
mode="overflow"
/>
</div>
<pre class="mb-6 rounded bg-blue-100 px-3 py-1 font-mono text-xs dark:bg-gray-700">
<pre class="mb-6 rounded-sm bg-blue-100 px-3 py-1 font-mono text-xs/snug dark:bg-gray-700">
active: {{ activeFiltersOverflow }}</pre>
<h3 class="mb-2">5. Navigation Tabs — Scroll Mode</h3>
@@ -2267,7 +2267,7 @@ active: {{ activeFiltersOverflow }}</pre>
mode="scroll"
/>
</div>
<pre class="mb-6 rounded bg-blue-100 px-3 py-1 font-mono text-xs dark:bg-gray-700">
<pre class="mb-6 rounded-sm bg-blue-100 px-3 py-1 font-mono text-xs/snug dark:bg-gray-700">
active: {{ activeNavTabScroll }}</pre>
<h3 class="mb-2">6. Navigation Tabs — Overflow Menu</h3>
@@ -2282,7 +2282,7 @@ active: {{ activeNavTabScroll }}</pre>
mode="overflow"
/>
</div>
<pre class="mb-6 rounded bg-blue-100 px-3 py-1 font-mono text-xs dark:bg-gray-700">
<pre class="mb-6 rounded-sm bg-blue-100 px-3 py-1 font-mono text-xs/snug dark:bg-gray-700">
active: {{ activeNavTabOverflow }}</pre>
</section>

View File

@@ -301,7 +301,7 @@ defineExpose({ addAnswer })
<ol
v-if="!showsSortingEmptyState"
ref="dnd-parent"
class="group focus-visible:outline-offset-0.5! flex flex-col gap-4 rounded-xl focus-visible-app-default"
class="group flex flex-col gap-4 rounded-xl focus-visible-app-default focus-visible:outline-offset-0!"
:class="{ 'mt-4': !isSorting }"
:tabindex="isDraggable ? 0 : undefined"
:aria-label="isDraggable ? $t('Answer order list') : undefined"

View File

@@ -16,7 +16,7 @@ const locale = useLocaleStore()
or two; a truncated segment reveals its full title as a tooltip on hover. -->
<div
v-if="categoryPath.length"
class="flex min-w-0 items-center gap-1 text-xs leading-snug text-stone-200 dark:text-neutral-500"
class="flex min-w-0 items-center gap-1 text-xs/snug text-stone-200 dark:text-neutral-500"
>
<template v-for="(segment, index) in categoryPath" :key="segment.id">
<CommonIcon

View File

@@ -534,7 +534,7 @@ watch(browsedPage, () => {
<ol
v-if="!showsSortingEmptyState"
ref="dnd-parent"
class="group focus-visible:outline-offset-0.5! rounded-xl focus-visible-app-default"
class="group rounded-xl focus-visible-app-default focus-visible:outline-offset-0!"
:class="CATEGORY_GRID_CLASSES"
:tabindex="isRearranging ? 0 : undefined"
:aria-label="isRearranging ? $t('Category order list') : undefined"
@@ -638,7 +638,10 @@ watch(browsedPage, () => {
<CommonIndicator v-model="isReachingBottom" />
<!-- Not while sorting: the bottom bar takes over there, and adding is not on offer. -->
<div v-if="!isSortingArmed" class="pointer-none sticky bottom-3 h-0 w-full print:hidden">
<div
v-if="!isSortingArmed"
class="pointer-events-none sticky bottom-3 h-0 w-full print:hidden"
>
<CommonFloatingToolbar
:label="$t('Knowledge base actions')"
:is-reaching-bottom="isReachingBottom"

View File

@@ -73,7 +73,7 @@ useStickyTopCalculator(headerWithHiddenDetailsHeight, { offset: 7 })
<template>
<TopBarHeaderCompact
ref="header-with-hidden-details"
class="absolute top-0 right-0 left-0 z-30 bg-neutral-50/80 backdrop-blur-2xs dark:bg-gray-500/80"
class="absolute inset-x-0 top-0 z-30 bg-neutral-50/80 backdrop-blur-2xs dark:bg-gray-500/80"
:inert="!isCompactHeaderVisible"
:organization="organization"
:organization-display-name="organizationDisplayName"

View File

@@ -28,7 +28,7 @@ const { debouncedLoading } = useDebouncedLoading({
<div class="mb-2 flex h-9 items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.1}s` }"
class="h-5 w-5 -translate-y-2"
class="size-5 -translate-y-2"
rounded
/>
<div class="flex grow flex-col gap-1">
@@ -47,7 +47,7 @@ const { debouncedLoading } = useDebouncedLoading({
<div class="mb-2 flex h-9 items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.2}s` }"
class="h-5 w-5 -translate-y-2"
class="size-5 -translate-y-2"
rounded
alternative-background
/>
@@ -67,7 +67,7 @@ const { debouncedLoading } = useDebouncedLoading({
<div class="flex h-9 items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.3}s` }"
class="h-5 w-5 -translate-y-2"
class="size-5 -translate-y-2"
rounded
alternative-background
/>

View File

@@ -125,13 +125,13 @@ const captureImage = () => {
<div class="flex flex-col items-center gap-6 pt-12 pb-10">
<canvas
v-show="image"
class="h-64 min-h-64 w-64 min-w-64 rounded-full border border-black dark:border-white"
class="size-64 min-h-64 min-w-64 rounded-full border border-black dark:border-white"
>
</canvas>
<div
v-if="!image"
class="relative h-64 min-h-64 w-64 min-w-64 overflow-hidden rounded-full border border-black bg-blue-200 text-stone-200 dark:border-white dark:bg-gray-700 dark:text-neutral-500"
class="relative size-64 min-h-64 min-w-64 overflow-hidden rounded-full border border-black bg-blue-200 text-stone-200 dark:border-white dark:bg-gray-700 dark:text-neutral-500"
>
<CommonIcon
:name="cameraIcon"
@@ -141,7 +141,7 @@ const captureImage = () => {
<!-- eslint-disable vuejs-accessibility/media-has-caption -->
<video
v-show="!cameraIsDisabled"
class="h-full w-full object-cover"
class="size-full object-cover"
:aria-label="$t('Use the camera to take a photo for the avatar.')"
:srcObject="stream"
autoplay

View File

@@ -71,7 +71,7 @@ const discardImage = () => {
},
}"
:transitions="false"
class="cropper !max-h-[340px] !max-w-[476px]"
class="cropper max-h-[340px]! max-w-[476px]!"
background-class="cropper-background"
image-class="cropper__image"
@change="imageCropped"

View File

@@ -229,7 +229,7 @@ dark:hover:outline-blue-900 has-[input:focus]:outline-1 has-[input:focus]:outlin
<CommonButton
v-if="selectedEntityHasFiltersEnabled"
:id="advancedFiltersButtonId"
class="relative z-20 h-auto! w-full! bg-blue-200! -outline-offset-1! transition-[border-radius]! before:absolute before:right-0 before:bottom-0 before:left-0 before:hidden before:h-3 before:w-full before:translate-y-full before:bg-blue-200 before:opacity-0 before:transition-opacity before:duration-0 before:ease-in-out active:scale-none! @lg:w-auto! @lg:before:block dark:bg-gray-700! dark:before:bg-gray-700"
class="relative z-20 h-auto! w-full! bg-blue-200! -outline-offset-1! transition-[border-radius]! before:absolute before:inset-x-0 before:bottom-0 before:hidden before:h-3 before:w-full before:translate-y-full before:bg-blue-200 before:opacity-0 before:transition-opacity before:duration-0 before:ease-in-out active:scale-none! @lg:w-auto! @lg:before:block dark:bg-gray-700! dark:before:bg-gray-700"
:class="{
'rounded-b-none! before:h-3 before:opacity-100 @lg:rounded-b-none!': isFilterPanelsOpen,
'before:delay-200 before:duration-25': !isFilterPanelsOpen,

View File

@@ -27,7 +27,7 @@ const metaAddress = computed(() => {
<template>
<div
class="flex max-w-full flex-wrap items-center gap-1 overflow-hidden whitespace-nowrap *:not-last:after:text-sm *:not-last:after:leading-snug *:not-last:after:content-[',']"
class="flex max-w-full flex-wrap items-center gap-1 overflow-hidden whitespace-nowrap *:not-last:after:text-sm/snug *:not-last:after:content-[',']"
>
<template v-if="metaAddress?.parsed?.length">
<template v-for="meta in metaAddress.parsed" :key="`${meta.name}-${meta.emailAddress}`">

View File

@@ -115,7 +115,7 @@ onMounted(() => {
</h2>
<div class="flex h-full min-h-0 grow flex-col">
<div
class="mx-auto flex h-full w-full max-w-4xl grow flex-col px-12"
class="mx-auto flex size-full max-w-4xl grow flex-col px-12"
:class="{ 'py-3': isPinned }"
>
<div class="flex h-full grow flex-col" data-test-id="article-reply-stripes-panel">

View File

@@ -10,7 +10,7 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
>
<div class="flex items-center gap-1.5">
<CommonSkeleton class="h-3 w-12" />
<CommonSkeleton class="h-3 w-3" />
<CommonSkeleton class="size-3" />
<CommonSkeleton class="h-3 w-20" />
</div>

View File

@@ -83,7 +83,7 @@ const translationStore = useArticleTranslationStore()
<!-- 48rem for the middle grid to align with the content area -->
<!-- 1.5 rem some extra spacing => 46.5rem-->
<div class="justify-start-center w-full max-w-186">
<div class="w-full max-w-186">
<div class="flex flex-col justify-center">
<CommonInlineEdit
v-model:editing="isUpdatingTitle"

View File

@@ -7,7 +7,7 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
<template>
<div class="flex flex-col rounded-lg bg-blue-200 p-1 dark:bg-gray-700">
<div v-for="i in 3" :key="i" class="flex w-full items-center gap-2 px-2 py-1">
<CommonSkeleton alternative-background class="h-9 w-9 shrink-0 rounded" />
<CommonSkeleton alternative-background class="size-9 shrink-0 rounded-sm" />
<div class="flex flex-1 flex-col gap-1">
<CommonSkeleton alternative-background class="h-4 w-36" />
<CommonSkeleton alternative-background class="h-3 w-16" />

View File

@@ -69,7 +69,7 @@ const badgeColor = computed(() => {
<CommonLabel
v-if="badge"
size="xs"
class="pointer-events-none absolute -bottom-2 block! max-w-10 min-w-4 truncate rounded-full border-2 border-white px-0.5 py-0.5 text-center font-bold text-white ltr:-right-1.5 rtl:-left-1.5 dark:border-gray-500"
class="pointer-events-none absolute -bottom-2 block! max-w-10 min-w-4 truncate rounded-full border-2 border-white p-0.5 text-center font-bold text-white ltr:-right-1.5 rtl:-left-1.5 dark:border-gray-500"
:class="[badgeColor]"
:aria-label="$t(badge.label)"
role="status"

View File

@@ -20,6 +20,6 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
</li>
</ul>
<CommonSkeleton class="col-start-2 h-8 w-8 justify-self-end" />
<CommonSkeleton class="col-start-2 size-8 justify-self-end" />
</div>
</template>

View File

@@ -57,7 +57,7 @@ const issueStateName = computed(() => {
<div class="group flex gap-2">
<CommonIcon
role="status"
class="flex-shrink-0"
class="shrink-0"
:class="issueStateColor"
:label="__('Issue status')"
:aria-roledescription="$t('issue status: %s', $t(issue.state))"
@@ -79,7 +79,7 @@ const issueStateName = computed(() => {
<ExternalReferenceContent v-if="issue.issueType" :label="$t('Type')">
<CommonBadge
:style="issueTypeStyle"
class="max-w-full self-start truncate !rounded-full border-neutral-100 ltr:border rtl:border dark:border-gray-900"
class="max-w-full self-start truncate rounded-full! border-neutral-100 ltr:border rtl:border dark:border-gray-900"
>
{{ issue.issueType.name }}
</CommonBadge>

View File

@@ -17,7 +17,7 @@ defineProps<Props>()
:key="label.title"
:style="{ backgroundColor: label.color, color: label.textColor }"
role="listitem"
class="!rounded-full border-neutral-100 ltr:border rtl:border dark:border-gray-900"
class="rounded-full! border-neutral-100 ltr:border rtl:border dark:border-gray-900"
>
{{ label.title }}
</CommonBadge>

View File

@@ -17,7 +17,7 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
<div class="flex flex-col gap-0.5">
<CommonSkeleton class="h-3 w-10" />
<CommonSkeleton class="h-5 w-16 !rounded-full" />
<CommonSkeleton class="h-5 w-16 rounded-full!" />
</div>
<div class="flex flex-col gap-0.5">
@@ -28,8 +28,8 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
<div v-if="i === 1" class="flex flex-col gap-0.5">
<CommonSkeleton class="h-3 w-12" />
<div class="flex gap-1">
<CommonSkeleton class="h-5 w-14 !rounded-full" />
<CommonSkeleton class="h-5 w-10 !rounded-full" />
<CommonSkeleton class="h-5 w-14 rounded-full!" />
<CommonSkeleton class="h-5 w-10 rounded-full!" />
</div>
</div>
</div>

View File

@@ -84,7 +84,7 @@ const showDivider = computed(() => shownUnits.value.length > 1) // If more than
<CommonButton
v-if="shouldDisplayShowButton"
class="mt-1 hover:outline-transparent! ltr:float-right ltr:-mr-2 ltr:-ml-2 rtl:float-left rtl:-mr-2 rtl:-ml-2"
class="mt-1 hover:outline-transparent! ltr:float-right ltr:-mx-2 rtl:float-left rtl:-mx-2"
variant="secondary"
@click="showAll = true"
>{{ $t('Show %s more', remainingUnitsCount) }}&hellip;</CommonButton

View File

@@ -28,6 +28,6 @@ import CommonSkeleton from '#desktop/components/CommonSkeleton/CommonSkeleton.vu
</div>
</div>
<CommonSkeleton class="h-8 w-8 self-end" />
<CommonSkeleton class="size-8 self-end" />
</div>
</template>

View File

@@ -24,7 +24,7 @@ defineProps<Props>()
<li
v-for="content in summary"
:key="content"
class="grid grid-cols-[min-content_1fr] gap-x-2 gap-y-1 ps-2 before:col-start-1 before:mt-2 before:h-[3px] before:w-[3px] before:shrink-0 before:rounded-full before:bg-current"
class="grid grid-cols-[min-content_1fr] gap-x-2 gap-y-1 ps-2 before:col-start-1 before:mt-2 before:size-[3px] before:shrink-0 before:rounded-full before:bg-current"
>
<!-- eslint-disable-next-line vue/no-v-text-v-html-on-component, vue/no-v-html -->
<CommonLabel class="col-2 wrap-anywhere" tag="p" v-html="textToHtml(content)" />

View File

@@ -73,7 +73,7 @@ useStickyTopCalculator(headerWithHiddenDetailsHeight, { offset: 7 })
<template>
<TopBarHeaderCompact
ref="header-with-hidden-details"
class="absolute top-0 right-0 left-0 z-30 bg-neutral-50/80 backdrop-blur-2xs dark:bg-gray-500/80"
class="absolute inset-x-0 top-0 z-30 bg-neutral-50/80 backdrop-blur-2xs dark:bg-gray-500/80"
:inert="!isCompactHeaderVisible"
:user="user"
:user-display-name="userDisplayName"
@@ -86,7 +86,7 @@ useStickyTopCalculator(headerWithHiddenDetailsHeight, { offset: 7 })
<TopBarHeaderFull
ref="header-with-details"
class="sticky top-0 right-0 left-0 z-20 w-full min-w-xs bg-neutral-50/80 backdrop-blur-2xs dark:bg-gray-500/80"
class="sticky inset-x-0 top-0 z-20 w-full min-w-xs bg-neutral-50/80 backdrop-blur-2xs dark:bg-gray-500/80"
:inert="isCompactHeaderVisible"
:user="user"
:user-display-name="userDisplayName"

View File

@@ -28,7 +28,7 @@ const { debouncedLoading } = useDebouncedLoading({
<div class="mb-2 flex h-9 items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.2}s` }"
class="h-5 w-5 -translate-y-2"
class="size-5 -translate-y-2"
rounded
alternative-background
/>
@@ -48,7 +48,7 @@ const { debouncedLoading } = useDebouncedLoading({
<div class="mb-2 flex h-9 items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.2}s` }"
class="h-5 w-5 -translate-y-2"
class="size-5 -translate-y-2"
rounded
alternative-background
/>
@@ -68,7 +68,7 @@ const { debouncedLoading } = useDebouncedLoading({
<div class="flex h-9 items-center gap-2">
<CommonSkeleton
:style="{ 'animation-delay': `${0.3}s` }"
class="h-5 w-5 -translate-y-2"
class="size-5 -translate-y-2"
rounded
alternative-background
/>

View File

@@ -28,13 +28,13 @@ const sizeClass = computed(() => {
<template>
<div
:class="sizeClass"
class="relative rounded-full before:absolute before:inset-0 before:z-0 before:size-full before:animate-spin before:rounded-full before:bg-gradient-to-r before:from-blue before:to-pink before:[animation-duration:2s]"
class="relative rounded-full before:absolute before:inset-0 before:z-0 before:size-full before:animate-spin before:rounded-full before:bg-linear-to-r before:from-blue before:to-pink before:[animation-duration:2s]"
>
<div
:aria-label="$t('AI agent')"
class="relative z-1 flex rounded-full border border-transparent bg-gray-600/90 bg-clip-padding p-1"
>
<CommonIcon class="h-full w-full text-blue" size="tiny" name="ai-agent" />
<CommonIcon class="size-full text-blue" size="tiny" name="ai-agent" />
</div>
</div>
</template>

View File

@@ -61,7 +61,7 @@ const iconSizeClass = computed(() => {
:type="type"
:form="form"
:disabled="disabled"
class="inline-flex flex-shrink-0 flex-nowrap items-center justify-center gap-x-1 border-0"
class="inline-flex shrink-0 flex-nowrap items-center justify-center gap-x-1 border-0"
:class="[
...transparentBackgroundClasses,
...variantClasses,

View File

@@ -70,7 +70,7 @@ const isTabs = computed(() => props.as === 'tabs')
option.class,
{
'opacity-50': option.disabled,
'!bg-gray-200': option.selected || (option.value != null && modelValue === option.value),
'bg-gray-200!': option.selected || (option.value != null && modelValue === option.value),
'flex-1 py-2': mode === 'full',
'py-1': mode === 'compressed',
},

View File

@@ -40,7 +40,7 @@ describe('buttons group', () => {
const button = view.getByRole('button', { name: 'button' })
expect(button).toBeEnabled()
expect(button, 'selected button has a class').toHaveClass('!bg-gray-200')
expect(button, 'selected button has a class').toHaveClass('bg-gray-200!')
await view.events.click(button)

View File

@@ -60,7 +60,7 @@ const heading = computed(() => {
>
<template #header>
<div
class="flex min-h-[3.5rem] items-center justify-center border-b border-gray-300 p-3 text-center text-white"
class="flex min-h-14 items-center justify-center border-b border-gray-300 p-3 text-center text-white"
>
{{
$t(

View File

@@ -117,9 +117,7 @@ export default {
<div
class="relative flex h-16 shrink-0 items-center justify-center rounded-t-xl bg-gray-600/80 select-none"
>
<div
class="absolute top-0 bottom-0 flex items-center ltr:left-0 ltr:pl-4 rtl:right-0 rtl:pr-4"
>
<div class="absolute inset-y-0 flex items-center ltr:left-0 ltr:pl-4 rtl:right-0 rtl:pr-4">
<slot name="before-label" />
</div>
<div
@@ -129,9 +127,7 @@ export default {
{{ $t(label) }}
</slot>
</div>
<div
class="absolute top-0 bottom-0 flex items-center ltr:right-0 ltr:pr-4 rtl:left-0 rtl:pl-4"
>
<div class="absolute inset-y-0 flex items-center ltr:right-0 ltr:pr-4 rtl:left-0 rtl:pl-4">
<slot name="after-label">
<CommonButton
class="grow"

View File

@@ -86,7 +86,7 @@ const { isTouchDevice } = useTouchDevice()
<button
v-if="canPreview"
v-tooltip="$t('Preview %s', props.file.name)"
class="flex h-9 w-9 shrink-0 items-center justify-center rounded"
class="flex size-9 shrink-0 items-center justify-center rounded-sm"
:class="{ border: canPreview !== 'image' }"
@click="onPreviewClick"
@keydown.delete.prevent="$emit('remove')"
@@ -95,7 +95,7 @@ const { isTouchDevice } = useTouchDevice()
<template v-if="canPreview">
<img
v-if="canPreview === 'image'"
class="h-9 w-9 rounded border object-cover"
class="size-9 rounded-sm border object-cover"
:src="previewUrl"
:alt="$t('Image of %s', file.name)"
@error="imageFailed = true"
@@ -118,7 +118,7 @@ const { isTouchDevice } = useTouchDevice()
>
<div
v-if="!canPreview"
class="flex h-9 w-9 items-center justify-center rounded border"
class="flex size-9 items-center justify-center rounded-sm border"
:class="iconClass || 'border-gray-300'"
>
<CommonIcon

View File

@@ -43,7 +43,7 @@ const iconProps = computed<IconProps | null>(() => {
<div class="flex min-h-[54px] items-center">
<div
v-if="iconProps || $slots.icon"
class="flex h-8 w-8 items-center justify-center ltr:mr-2 rtl:ml-2"
class="flex size-8 items-center justify-center ltr:mr-2 rtl:ml-2"
data-test-id="wrapper-icon"
:class="{
[`rounded-lg ${iconBg}`]: iconBg,

View File

@@ -133,7 +133,7 @@ const getClassesByType = (type: PopupItemDescriptor['type']) => {
<div
v-if="persistent || localState"
v-show="persistent ? localState : true"
class="window fixed top-0 bottom-0 flex w-screen flex-col justify-end px-4 pb-safe-4 text-white ltr:left-0 rtl:right-0"
class="window fixed inset-y-0 flex w-screen flex-col justify-end px-4 pb-safe-4 text-white ltr:left-0 rtl:right-0"
:class="{ 'z-20': !zIndex }"
:style="{ zIndex }"
role="presentation"

View File

@@ -183,7 +183,7 @@ const duration = VITE_TEST_MODE ? undefined : { enter: 300, leave: 200 }
>
<!-- empty @click is needed for https://stackoverflow.com/a/39712411 -->
<div
class="select-overlay fixed inset-0 flex h-full w-full bg-gray-500 opacity-60"
class="select-overlay fixed inset-0 flex size-full bg-gray-500 opacity-60"
data-test-id="dialog-overlay"
role="presentation"
@click="void 0"

View File

@@ -52,7 +52,7 @@ const label = computed(() => {
<CommonIcon
v-if="multiple"
:class="{
'!text-white': selected,
'text-white!': selected,
'opacity-30': option.disabled,
}"
size="base"

View File

@@ -22,7 +22,7 @@ const classes = computed(() => {
<template>
<button
class="flex h-6 w-6 grow-0 items-center justify-center rounded-full"
class="flex size-6 grow-0 items-center justify-center rounded-full"
:disabled="disabled"
:class="classes"
type="button"
@@ -32,7 +32,7 @@ const classes = computed(() => {
role="status"
:aria-label="$t('Invalid values in step %s', label)"
aria-live="assertive"
class="absolute mb-3 h-4 min-w-[1rem] rounded-full bg-red px-1 text-center text-xs text-black ltr:ml-6 rtl:mr-6"
class="absolute mb-3 h-4 min-w-4 rounded-full bg-red px-1 text-center text-xs/snug text-black ltr:ml-6 rtl:mr-6"
>
{{ errorCount }}
</div>

View File

@@ -22,7 +22,7 @@ const escalationState = useEscalationState(toRef(() => props.escalationAt))
'bg-red-dark text-red-bright': escalationState === EscalationState.Escalated,
'bg-yellow-highlight text-yellow': escalationState === EscalationState.Warning,
}"
class="flex items-center rounded bg-gray-100 py-1 text-black select-none ltr:pr-1.5 ltr:pl-1 rtl:pr-1 rtl:pl-1.5"
class="flex items-center rounded-sm bg-gray-100 py-1 text-black select-none ltr:pr-1.5 ltr:pl-1 rtl:pr-1 rtl:pl-1.5"
role="alert"
>
<CommonIcon name="ticket-escalating" size="tiny" decorative />

View File

@@ -34,7 +34,7 @@ const priorityText = computed(() => {
<div
v-if="priorityText"
:class="priorityClass"
class="rounded px-2 py-1 text-xs leading-2 whitespace-nowrap uppercase select-none"
class="rounded-sm px-2 py-1 text-xs/2 whitespace-nowrap uppercase select-none"
>
{{ $t(priorityText) }}
</div>

View File

@@ -52,7 +52,7 @@ const backgroundClass = computed(() => {
textClass,
backgroundClass,
{
'rounded py-1 ltr:pr-1.5 ltr:pl-1 rtl:pr-1 rtl:pl-1.5': pill,
'rounded-sm py-1 ltr:pr-1.5 ltr:pl-1 rtl:pr-1 rtl:pl-1.5': pill,
},
]"
class="flex items-center select-none"

View File

@@ -306,7 +306,7 @@ useTraverseOptions(autocompleteList)
<CommonIcon
v-if="context.multiple"
:class="{
'!text-white': isCurrentValue(option.value),
'text-white!': isCurrentValue(option.value),
'opacity-30': option.disabled,
}"
:name="isCurrentValue(option.value) ? 'check-box-yes' : 'check-box-no'"

View File

@@ -125,7 +125,7 @@ whenever(
<button
:title="$t(action.label || action.name)"
type="button"
class="relative flex items-center gap-1 rounded bg-black p-2 lg:hover:bg-gray-300"
class="relative flex items-center gap-1 rounded-sm bg-black p-2 lg:hover:bg-gray-300"
:class="[
action.class,
{
@@ -147,7 +147,7 @@ whenever(
<CommonIcon v-if="action.subMenu" name="caret" size="xs" decorative />
<div
v-if="action.name === 'textColor'"
class="color-indicator absolute start-1/2 bottom-[0.6rem] box-content h-1 w-1 rounded-xs border border-gray-400 ltr:-translate-x-1/2 rtl:translate-x-1/2"
class="color-indicator absolute inset-s-1/2 bottom-[0.6rem] box-content size-1 rounded-xs border border-gray-400 ltr:-translate-x-1/2 rtl:translate-x-1/2"
:style="{
backgroundColor: props.editor?.getAttributes('textStyle')?.color
? props.editor.getAttributes('textStyle').color

View File

@@ -62,7 +62,7 @@ const emptyMessage = computed(() => {
<template>
<ul
:id="listboxId"
class="z-10 max-h-64 overflow-auto rounded bg-gray-300 text-white"
class="z-10 max-h-64 overflow-auto bg-gray-300 leading-snug text-white"
:data-test-id="`mention-${type}`"
role="listbox"
:aria-label="$t(label)"
@@ -93,7 +93,7 @@ const emptyMessage = computed(() => {
<div>{{ (item as MentionTextItem).name }}</div>
<div
v-if="(item as MentionTextItem).keywords"
class="rounded border border-solid border-gray-150 px-1 text-sm"
class="rounded-sm border border-solid border-gray-150 px-1 text-sm"
>
{{ (item as MentionTextItem).keywords }}
</div>

View File

@@ -186,10 +186,10 @@ const processSearchKeydown = (event: KeyboardEvent) => {
>
<CommonIcon
:class="{
'!text-white': isCurrentValue(option),
'text-white! opacity-100': isCurrentValue(option),
}"
:name="isCurrentValue(option) ? 'check-box-yes' : 'check-box-no'"
class="text-white/50 ltr:mr-3 rtl:ml-3"
class="text-white opacity-50 ltr:mr-3 rtl:ml-3"
size="base"
decorative
/>

View File

@@ -79,7 +79,7 @@ useFormBlock(reactiveContext, onInputClick)
<div
v-for="tag of selectedTagsList"
:key="tag"
class="rounded-xs bg-gray/20 px-[4px] py-[2px] text-base leading-4 text-gray uppercase"
class="rounded-xs bg-gray/20 px-[4px] py-[2px] text-base/4 text-gray uppercase"
role="listitem"
>
{{ tag }}

View File

@@ -240,7 +240,7 @@ const getCurrentIndex = (option: FlatSelectOption) => {
<CommonIcon
v-if="context.multiple"
:class="{
'!text-white': isCurrentValue(option.value),
'text-white!': isCurrentValue(option.value),
'opacity-30': option.disabled,
}"
:name="isCurrentValue(option.value) ? 'check-box-yes' : 'check-box-no'"
@@ -261,7 +261,7 @@ const getCurrentIndex = (option: FlatSelectOption) => {
v-else-if="option.icon"
:name="option.icon"
:class="{
'!text-white': isCurrentValue(option.value),
'text-white!': isCurrentValue(option.value),
'opacity-30': option.disabled,
}"
size="small"

View File

@@ -55,7 +55,7 @@ const users = computed(() => {
{{ users }}
</template>
</span>
<span class="mb-1 line-clamp-3 text-lg leading-5 font-bold whitespace-normal">
<span class="mb-1 line-clamp-3 text-lg/5 font-bold whitespace-normal">
<slot> {{ entity.name }} </slot>
</span>
<div v-if="stringUpdated" data-test-id="stringUpdated" class="truncate text-gray">

View File

@@ -19,7 +19,7 @@ defineProps<{
no-autofocus
>
<div class="w-full p-4">
<p class="mb-3 break-words whitespace-pre-wrap">
<p class="mb-3 wrap-break-word whitespace-pre-wrap">
{{ $c.ticket_duplicate_detection_body }}
</p>
<CommonLink v-for="[id, number, title] in tickets" :key="id" :link="`/tickets/${id}`">
@@ -27,9 +27,9 @@ defineProps<{
<div
class="flex flex-1 items-center gap-1 overflow-hidden border-b border-white/10 py-3 text-gray-100 ltr:pr-2 rtl:pl-2"
>
<div class="flex-1 truncate text-sm">
<div class="flex-1 truncate text-sm/snug">
<span>#{{ number }}</span>
<span class="mb-1 line-clamp-3 text-lg leading-5 font-bold whitespace-normal">
<span class="mb-1 line-clamp-3 text-lg/5 font-bold whitespace-normal">
{{ title }}
</span>
</div>

View File

@@ -53,7 +53,7 @@ const customer = computed(() => {
{{ customer }}
</template>
</span>
<span class="mb-1 line-clamp-3 text-lg leading-5 font-bold whitespace-normal">
<span class="mb-1 line-clamp-3 text-lg/5 font-bold whitespace-normal">
<slot>
{{ entity.title }}
</slot>

View File

@@ -35,7 +35,7 @@ const { stringUpdated } = useEditedBy(toRef(props, 'entity'))
{{ entity.organization.name }}
</template>
</span>
<span class="mb-1 line-clamp-3 text-lg leading-5 font-bold whitespace-normal">
<span class="mb-1 line-clamp-3 text-lg/5 font-bold whitespace-normal">
<slot> {{ entity.firstname }} {{ entity.lastname }} </slot>
</span>
<div v-if="stringUpdated" class="truncate" data-test-id="stringUpdated">

View File

@@ -38,7 +38,7 @@ const notificationCount = computed(() => {
v-if="notificationCount"
role="status"
:aria-label="$t('Unread notifications')"
class="absolute h-4 min-w-[1rem] rounded-full bg-blue px-1 text-center text-xs text-black ltr:ml-4 rtl:mr-4"
class="absolute h-4 min-w-4 rounded-full bg-blue px-1 text-center text-xs/snug text-black ltr:ml-4 rtl:mr-4"
>
{{ notificationCount }}
</div>

View File

@@ -250,7 +250,7 @@ const actions = computed<CommonButtonOption[]>(() => [
<div v-if="avatarImage" class="flex w-full flex-col items-center justify-center">
<Cropper
class="mt-4 mb-4 !max-h-[250px] !max-w-[400px]"
class="my-4 max-h-[250px]! max-w-[400px]!"
:src="avatarImage.content"
:stencil-props="{
aspectRatio: 1,

View File

@@ -17,7 +17,7 @@ const application = useApplicationStore()
open-in-new-tab
class="ltr:mr-1 rtl:ml-1"
>
<img :src="'/assets/images/icons/logo.svg'" :alt="$t('Logo')" class="h-6 w-6" />
<img :src="'/assets/images/icons/logo.svg'" :alt="$t('Logo')" class="size-6" />
</CommonLink>
<span class="ltr:mr-1 rtl:ml-1">{{ $t('Powered by') }}</span>
<CommonLink link="https://zammad.org" external open-in-new-tab class="font-medium">

View File

@@ -36,7 +36,7 @@ const csrfToken = getCSRFToken()
decorative
class="shrink-0 ltr:mr-2.5 rtl:ml-2.5"
/>
<span class="truncate text-xl leading-7">
<span class="truncate text-xl/7">
{{ $t(provider.label) }}
</span>
</button>

Some files were not shown because too many files have changed in this diff Show More