From 5e9bda7fab1b12334951ea5e2b03efc8597a30b8 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Fri, 1 May 2026 06:18:03 +0200 Subject: [PATCH] chore: tailwind linting (#28165) chore: tailwind cannonical classes --- .vscode/settings.json | 3 +- .../ui/specs/timeline/timeline.e2e-spec.ts | 2 +- pnpm-lock.yaml | 85 ++++++++++++++++++- web/eslint.config.js | 14 ++- web/package.json | 5 +- web/src/app.css | 61 ++++++------- web/src/lib/components/AdaptiveImage.svelte | 8 +- web/src/lib/components/AdminCard.svelte | 2 +- .../components/ApiKeyPermissionsPicker.svelte | 2 +- .../components/BreadcrumbActionPage.svelte | 4 +- web/src/lib/components/ImageLayer.svelte | 2 +- .../components/SharedLinkExpiration.svelte | 2 +- .../components/SharedLinkFormFields.svelte | 2 +- .../StorageTemplateSettings.svelte | 12 +-- .../SupportedDatetimePanel.svelte | 2 +- .../SupportedVariablesPanel.svelte | 2 +- .../components/album-page/AlbumCard.svelte | 8 +- .../album-page/AlbumCardGroup.svelte | 6 +- .../album-page/AlbumSharedLink.svelte | 2 +- .../components/album-page/AlbumViewer.svelte | 8 +- .../components/album-page/AlbumsTable.svelte | 12 +-- .../album-page/AlbumsTableRow.svelte | 16 ++-- .../asset-viewer/ActivityStatus.svelte | 2 +- .../asset-viewer/ActivityViewer.svelte | 41 +++++---- .../asset-viewer/AlbumListItem.svelte | 14 ++- .../asset-viewer/AssetViewer.svelte | 26 +++--- .../asset-viewer/AssetViewerNavBar.svelte | 4 +- .../asset-viewer/DetailPanel.svelte | 26 +++--- .../asset-viewer/DetailPanelDate.svelte | 4 +- .../DetailPanelDescription.svelte | 8 +- .../asset-viewer/DetailPanelLocation.svelte | 6 +- .../asset-viewer/DetailPanelPeople.svelte | 4 +- .../asset-viewer/DetailPanelTags.svelte | 4 +- .../asset-viewer/ImagePanoramaViewer.svelte | 2 +- .../asset-viewer/NavigationArea.svelte | 2 +- .../asset-viewer/OcrBoundingBox.svelte | 10 +-- .../components/asset-viewer/OcrButton.svelte | 2 +- .../PhotoSphereViewerAdapter.svelte | 2 +- .../asset-viewer/PhotoViewer.svelte | 12 +-- .../asset-viewer/SlideshowBar.svelte | 2 +- .../asset-viewer/VideoNativeViewer.svelte | 4 +- .../asset-viewer/VideoPanoramaViewer.svelte | 2 +- .../asset-viewer/VideoRemoteViewer.svelte | 6 +- .../asset-viewer/editor/EditorPanel.svelte | 6 +- .../editor/transform-tool/CropArea.svelte | 10 +-- .../transform-tool/TransformTool.svelte | 12 +-- .../face-editor/FaceEditor.svelte | 12 +-- .../lib/components/assets/BrokenAsset.svelte | 2 +- .../assets/thumbnail/ImageThumbnail.svelte | 4 +- .../assets/thumbnail/Thumbnail.svelte | 49 +++++------ .../assets/thumbnail/VideoThumbnail.svelte | 6 +- .../faces-page/AssignFaceSidePanel.svelte | 10 +-- .../faces-page/PersonSidePanel.svelte | 20 ++--- .../components/layouts/AdminPageLayout.svelte | 6 +- .../components/layouts/AuthPageLayout.svelte | 10 +-- .../components/layouts/UserPageLayout.svelte | 4 +- .../maintenance/MaintenanceBackupEntry.svelte | 10 +-- .../maintenance/MaintenanceBackupsList.svelte | 6 +- .../pages/SharedLinkErrorPage.svelte | 2 +- .../components/pages/SharedLinkPage.svelte | 4 +- .../ServerStatisticsCard.svelte | 10 +-- .../share-page/IndividualSharedViewer.svelte | 4 +- .../shared-components/Combobox.svelte | 24 +++--- .../shared-components/ControlAppBar.svelte | 6 +- .../shared-components/EmptyPlaceholder.svelte | 4 +- .../shared-components/Qrcode.svelte | 2 +- .../shared-components/TagPill.svelte | 6 +- .../shared-components/UserAvatar.svelte | 6 +- .../album-selection/NewAlbumListItem.svelte | 4 +- .../context-menu/ContextMenu.svelte | 2 +- .../context-menu/MenuOption.svelte | 4 +- .../context-menu/RightClickContextMenu.svelte | 2 +- .../gallery-viewer/GalleryViewer.svelte | 2 +- .../shared-components/map/Map.svelte | 6 +- .../navigation-bar/AccountInfoPanel.svelte | 14 +-- .../navigation-bar/NavigationBar.svelte | 12 +-- .../navigation-bar/NotificationItem.svelte | 8 +- .../navigation-bar/NotificationPanel.svelte | 6 +- .../progress-bar/ProgressBar.svelte | 2 +- .../IndividualPurchaseOptionCard.svelte | 12 +-- .../PurchaseActivationSuccess.svelte | 8 +- .../purchasing/PurchaseContent.svelte | 2 +- .../ServerPurchaseOptionCard.svelte | 12 +-- .../search-bar/SearchBar.svelte | 24 +++--- .../search-bar/SearchCameraSection.svelte | 2 +- .../search-bar/SearchDisplaySection.svelte | 2 +- .../search-bar/SearchHistoryBox.svelte | 8 +- .../search-bar/SearchLocationSection.svelte | 2 +- .../search-bar/SearchMediaSection.svelte | 2 +- .../search-bar/SearchPeopleSection.svelte | 16 ++-- .../search-bar/SearchTagsSection.svelte | 2 +- .../search-bar/SearchTextSection.svelte | 2 +- .../settings/SettingAccordion.svelte | 8 +- .../settings/SettingDropdown.svelte | 2 +- .../settings/SettingInputField.svelte | 10 +-- .../settings/SettingSwitch.svelte | 2 +- .../side-bar/BottomInfo.svelte | 2 +- .../side-bar/PurchaseInfo.svelte | 22 ++--- .../side-bar/RecentAlbums.svelte | 6 +- .../side-bar/ServerStatus.svelte | 18 ++-- .../side-bar/StorageSpace.svelte | 4 +- .../shared-components/tree/Breadcrumbs.svelte | 10 +-- .../shared-components/tree/Tree.svelte | 6 +- .../tree/TreeItemThumbnails.svelte | 6 +- .../shared-components/tree/TreeItems.svelte | 2 +- web/src/lib/components/sidebar/Sidebar.svelte | 4 +- web/src/lib/components/timeline/Month.svelte | 4 +- .../lib/components/timeline/Scrubber.svelte | 20 ++--- .../lib/components/timeline/Timeline.svelte | 2 +- .../PinCodeCreateForm.svelte | 4 +- .../user-settings-page/UserApiKeyGrid.svelte | 8 +- web/src/lib/elements/Badge.svelte | 2 +- web/src/lib/elements/Dropdown.svelte | 6 +- web/src/lib/elements/DurationInput.svelte | 2 +- web/src/lib/elements/GroupTab.svelte | 4 +- web/src/lib/elements/SearchBar.svelte | 2 +- web/src/lib/elements/Skeleton.svelte | 4 +- web/src/lib/elements/SkipLink.svelte | 2 +- web/src/lib/elements/StarRating.svelte | 2 +- .../lib/modals/AddWorkflowStepModal.svelte | 2 +- web/src/lib/modals/AlbumAddUsersModal.svelte | 4 +- web/src/lib/modals/AlbumEditModal.svelte | 6 +- web/src/lib/modals/AlbumOptionsModal.svelte | 6 +- web/src/lib/modals/AlbumPickerModal.svelte | 14 +-- web/src/lib/modals/AppDownloadModal.svelte | 8 +- .../lib/modals/AssetChangeDateModal.svelte | 4 +- .../lib/modals/AssetDeleteConfirmModal.svelte | 2 +- .../AssetSelectionChangeDateModal.svelte | 8 +- web/src/lib/modals/AssetTagModal.svelte | 2 +- web/src/lib/modals/AvatarEditModal.svelte | 2 +- web/src/lib/modals/CreateFaceModal.svelte | 4 +- .../modals/EmailTemplatePreviewModal.svelte | 4 +- .../modals/GeolocationPointPickerModal.svelte | 16 ++-- .../lib/modals/HelpAndFeedbackModal.svelte | 8 +- web/src/lib/modals/MapModal.svelte | 4 +- web/src/lib/modals/MapSettingsModal.svelte | 4 +- web/src/lib/modals/NavigateToDateModal.svelte | 2 +- .../lib/modals/ObtainiumConfigModal.svelte | 8 +- .../lib/modals/PartnerSelectionModal.svelte | 6 +- .../modals/PasswordResetSuccessModal.svelte | 2 +- web/src/lib/modals/PeoplePickerModal.svelte | 2 +- .../modals/PersonMergeSuggestionModal.svelte | 10 +-- web/src/lib/modals/QrCodeModal.svelte | 2 +- web/src/lib/modals/SearchFilterModal.svelte | 2 +- web/src/lib/modals/ServerAboutModal.svelte | 4 +- web/src/lib/modals/ShortcutsModal.svelte | 4 +- .../(user)/DragAndDropUploadOverlay.svelte | 2 +- web/src/routes/(user)/albums/+page.svelte | 4 +- .../(user)/albums/AlbumsControls.svelte | 6 +- .../[[assetId=id]]/+page.svelte | 12 +-- .../[[assetId=id]]/AlbumDescription.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 2 +- .../routes/(user)/buy/SupporterBadge.svelte | 2 +- web/src/routes/(user)/explore/+page.svelte | 20 ++--- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 4 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 6 +- .../[[assetId=id]]/MapTimelinePanel.svelte | 4 +- .../[[assetId=id]]/MemoryPhotoViewer.svelte | 4 +- .../[[assetId=id]]/MemoryVideoViewer.svelte | 4 +- .../[[assetId=id]]/MemoryViewer.svelte | 48 +++++------ .../[[assetId=id]]/+page.svelte | 2 +- web/src/routes/(user)/people/+page.svelte | 12 +-- .../people/ManagePeopleVisibility.svelte | 8 +- .../routes/(user)/people/PeopleCard.svelte | 6 +- .../(user)/people/PeopleInfiniteScroll.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 16 ++-- .../[[assetId=id]]/EditNameInput.svelte | 4 +- .../[[assetId=id]]/FaceThumbnail.svelte | 8 +- .../[[assetId=id]]/MergeFaceSelector.svelte | 4 +- .../[[assetId=id]]/PeopleList.svelte | 4 +- .../[[assetId=id]]/UnmergeFaceSelector.svelte | 2 +- .../(user)/photos/[[assetId=id]]/+page.svelte | 2 +- .../(user)/places/PlacesCardGroup.svelte | 10 +-- .../(user)/places/PlacesControls.svelte | 4 +- .../[[assetId=id]]/+page.svelte | 14 +-- .../(user)/shared-links/(list)/+layout.svelte | 4 +- .../shared-links/(list)/ShareCover.svelte | 2 +- .../shared-links/(list)/SharedLinkCard.svelte | 8 +- web/src/routes/(user)/sharing/+page.svelte | 6 +- .../[[assetId=id]]/+page.svelte | 4 +- .../[[assetId=id]]/+page.svelte | 4 +- .../(user)/user-settings/AppSettings.svelte | 2 +- .../ChangePasswordSettings.svelte | 2 +- .../(user)/user-settings/DeviceList.svelte | 2 +- .../user-settings/DownloadSettings.svelte | 2 +- .../user-settings/FeatureSettings.svelte | 20 ++--- .../NotificationsSettings.svelte | 4 +- .../(user)/user-settings/OauthSettings.svelte | 2 +- .../user-settings/PartnerSettings.svelte | 12 +-- .../user-settings/PinCodeChangeForm.svelte | 4 +- .../user-settings/SettingCombobox.svelte | 2 +- .../user-settings/UserApiKeyList.svelte | 4 +- .../user-settings/UserProfileSettings.svelte | 2 +- .../user-settings/UserPurchaseSettings.svelte | 16 ++-- web/src/routes/(user)/utilities/+page.svelte | 2 +- .../(user)/utilities/UtilitiesMenu.svelte | 10 +-- .../[[assetId=id]]/+page.svelte | 16 ++-- .../[[assetId=id]]/DuplicateAsset.svelte | 20 ++--- .../DuplicatesCompareControl.svelte | 8 +- .../[[assetId=id]]/InfoRow.svelte | 6 +- .../(user)/utilities/geolocation/+page.svelte | 20 ++--- .../[[assetId=id]]/+page.svelte | 4 +- .../[[assetId=id]]/LargeAssetData.svelte | 10 +-- .../(user)/utilities/workflows/+page.svelte | 19 ++--- .../workflows/[workflowId]/+page.svelte | 18 ++-- .../[workflowId]/SchemaFormFields.svelte | 2 +- .../[workflowId]/WorkflowCardConnector.svelte | 10 +-- .../[workflowId]/WorkflowJsonEditor.svelte | 2 +- .../WorkflowPickerItemCard.svelte | 6 +- .../[workflowId]/WorkflowSummary.svelte | 34 ++++---- .../[workflowId]/WorkflowTriggerCard.svelte | 6 +- web/src/routes/+page.svelte | 2 +- web/src/routes/DownloadPanel.svelte | 6 +- web/src/routes/ErrorLayout.svelte | 6 +- web/src/routes/NavigationLoadingBar.svelte | 2 +- web/src/routes/UploadAssetPreview.svelte | 6 +- web/src/routes/UploadPanel.svelte | 18 ++-- .../library-management/(list)/+layout.svelte | 2 +- .../library-management/[id]/+layout.svelte | 8 +- web/src/routes/admin/queues/QueueCard.svelte | 4 +- .../routes/admin/queues/QueueCardBadge.svelte | 2 +- .../admin/queues/QueueCardButton.svelte | 2 +- web/src/routes/admin/queues/QueuePanel.svelte | 2 +- .../routes/admin/queues/[name]/+page.svelte | 4 +- .../ServerStatisticsPanel.svelte | 16 ++-- .../routes/admin/system-settings/+page.svelte | 2 +- .../admin/system-settings/AuthSettings.svelte | 2 +- .../MachineLearningSettings.svelte | 4 +- .../NotificationSettings.svelte | 2 +- .../system-settings/ServerSettings.svelte | 2 +- .../system-settings/SettingCheckboxes.svelte | 6 +- .../system-settings/SettingSelect.svelte | 8 +- .../system-settings/SettingTextarea.svelte | 4 +- .../admin/users/(list)/new/+page.svelte | 2 +- .../routes/admin/users/[id]/+layout.svelte | 6 +- .../admin/users/[id]/FeatureSetting.svelte | 2 +- web/src/routes/auth/login/+page.svelte | 4 +- web/src/routes/auth/onboarding/+page.svelte | 10 +-- .../auth/onboarding/OnboardingBackup.svelte | 14 +-- .../auth/onboarding/OnboardingCard.svelte | 10 +-- .../auth/onboarding/OnboardingHello.svelte | 4 +- .../auth/onboarding/OnboardingTheme.svelte | 14 ++- web/src/routes/auth/pin-prompt/+page.svelte | 4 +- web/src/routes/maintenance/+page.svelte | 2 +- .../RestoreFlowDetectInstall.svelte | 4 +- .../RestoreFlowSelectBackup.svelte | 2 +- 248 files changed, 963 insertions(+), 880 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index eeb80649ba..dbf9688b9b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,7 +26,8 @@ }, "[svelte]": { "editor.defaultFormatter": "svelte.svelte-vscode", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "tailwindCSS.lint.suggestCanonicalClasses": "ignore" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode", diff --git a/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts b/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts index 5069a46a91..c2a3b8e724 100644 --- a/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts +++ b/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts @@ -304,7 +304,7 @@ test.describe('Timeline', () => { await page.keyboard.down('Shift'); await thumbnailUtils.withAssetId(page, assets[2].id).hover(); await expect( - thumbnailUtils.locator(page).locator('.absolute.top-0.h-full.w-full.bg-immich-primary.opacity-40'), + thumbnailUtils.locator(page).locator('.absolute.top-0.size-full.bg-immich-primary.opacity-40'), ).toHaveCount(3); await thumbnailUtils.selectButton(page, assets[2].id).click(); await page.keyboard.up('Shift'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6de45bf746..f7aa8f33f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -875,7 +875,7 @@ importers: specifier: 7.0.0 version: 7.0.0(svelte@5.55.2)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@tailwindcss/vite': - specifier: ^4.2.2 + specifier: ^4.2.4 version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@testing-library/jest-dom': specifier: ^6.4.2 @@ -919,6 +919,9 @@ importers: eslint-config-prettier: specifier: ^10.1.8 version: 10.1.8(eslint@10.2.1(jiti@2.6.1)) + eslint-plugin-better-tailwindcss: + specifier: ^4.5.0 + version: 4.5.0(eslint@10.2.1(jiti@2.6.1))(tailwindcss@4.2.4)(typescript@6.0.3) eslint-plugin-compat: specifier: ^7.0.0 version: 7.0.1(eslint@10.2.1(jiti@2.6.1)) @@ -956,7 +959,7 @@ importers: specifier: ^1.3.3 version: 1.6.0(svelte@5.55.2) tailwindcss: - specifier: ^4.2.2 + specifier: ^4.2.4 version: 4.2.4 typescript: specifier: ^6.0.0 @@ -2730,6 +2733,10 @@ packages: resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/css-tree@4.0.2': + resolution: {integrity: sha512-eqSkC3mka2tiqOuPZKqvxNJoRzpxMss3Np3Yqi4sW7nTTRCpTKB2hzrY4JRsi0ZP3QbVfp23sgEm7VCoOjesmw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@eslint/js@10.0.1': resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} @@ -5468,6 +5475,11 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@valibot/to-json-schema@1.6.0': + resolution: {integrity: sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A==} + peerDependencies: + valibot: ^1.3.0 + '@vercel/oidc@3.0.5': resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} engines: {node: '>= 20'} @@ -7329,6 +7341,19 @@ packages: peerDependencies: eslint: '>=7.0.0' + eslint-plugin-better-tailwindcss@4.5.0: + resolution: {integrity: sha512-EBNTx6OJYaWv7uUxHWTy1fhiNz2rZVkoeOHZzAJFwWaEPideBf04CMshrJ7YntG0KQzadlbRhHKYr32q5aBX4w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + oxlint: ^1.35.0 + tailwindcss: ^3.3.0 || ^4.1.17 + peerDependenciesMeta: + eslint: + optional: true + oxlint: + optional: true + eslint-plugin-compat@7.0.1: resolution: {integrity: sha512-wDID2fVIAfxV9R1uSkCn5HscnNu8yMxDF1IaQGyD1C6XuWwJbuaDgMOSkVgOom0LzY8z0fXXXCy7AQQTERQUvQ==} engines: {node: '>=18.x'} @@ -9052,6 +9077,9 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -11553,6 +11581,15 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + tailwind-csstree@0.3.1: + resolution: {integrity: sha512-v147gLOR+E+9H4dNaP9rBeS/S/CTQJMRItlX9jLOXjdBGfSRauLwiz7LBCViaQmn6URXIlOdN6iMzSzOaeoUUw==} + engines: {node: '>=18.18'} + peerDependencies: + '@eslint/css': '>=1.0.0' + peerDependenciesMeta: + '@eslint/css': + optional: true + tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} @@ -12099,6 +12136,14 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + valibot@1.3.1: + resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + validator@13.15.35: resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} @@ -15090,6 +15135,11 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@eslint/css-tree@4.0.2': + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + '@eslint/js@10.0.1(eslint@10.2.1(jiti@2.6.1))': optionalDependencies: eslint: 10.2.1(jiti@2.6.1) @@ -17887,6 +17937,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@valibot/to-json-schema@1.6.0(valibot@1.3.1(typescript@6.0.3))': + dependencies: + valibot: 1.3.1(typescript@6.0.3) + '@vercel/oidc@3.0.5': {} '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.2)(happy-dom@20.9.0)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': @@ -17920,7 +17974,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.5)(happy-dom@20.9.0)(jsdom@26.1.0(canvas@2.11.2))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/expect@3.2.4': dependencies: @@ -19977,6 +20031,23 @@ snapshots: dependencies: eslint: 10.2.1(jiti@2.6.1) + eslint-plugin-better-tailwindcss@4.5.0(eslint@10.2.1(jiti@2.6.1))(tailwindcss@4.2.4)(typescript@6.0.3): + dependencies: + '@eslint/css-tree': 4.0.2 + '@valibot/to-json-schema': 1.6.0(valibot@1.3.1(typescript@6.0.3)) + enhanced-resolve: 5.21.0 + jiti: 2.6.1 + synckit: 0.11.12 + tailwind-csstree: 0.3.1 + tailwindcss: 4.2.4 + tsconfig-paths-webpack-plugin: 4.2.0 + valibot: 1.3.1(typescript@6.0.3) + optionalDependencies: + eslint: 10.2.1(jiti@2.6.1) + transitivePeerDependencies: + - '@eslint/css' + - typescript + eslint-plugin-compat@7.0.1(eslint@10.2.1(jiti@2.6.1)): dependencies: '@mdn/browser-compat-data': 6.1.5 @@ -22094,6 +22165,8 @@ snapshots: mdn-data@2.0.30: {} + mdn-data@2.27.1: {} + media-typer@0.3.0: {} media-typer@1.1.0: {} @@ -25150,6 +25223,8 @@ snapshots: tagged-tag@1.0.0: {} + tailwind-csstree@0.3.1: {} + tailwind-merge@3.5.0: {} tailwind-variants@3.2.2(tailwind-merge@3.5.0)(tailwindcss@4.2.4): @@ -25749,6 +25824,10 @@ snapshots: uuid@8.3.2: {} + valibot@1.3.1(typescript@6.0.3): + optionalDependencies: + typescript: 6.0.3 + validator@13.15.35: {} value-equal@1.0.1: {} diff --git a/web/eslint.config.js b/web/eslint.config.js index a75aa9ed05..e457be29ba 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -1,6 +1,7 @@ import js from '@eslint/js'; import tslintPluginCompat from '@koddsson/eslint-plugin-tscompat'; import prettier from 'eslint-config-prettier'; +import eslintPluginBetterTailwindcss from 'eslint-plugin-better-tailwindcss'; import eslintPluginCompat from 'eslint-plugin-compat'; import eslintPluginSvelte from 'eslint-plugin-svelte'; import eslintPluginUnicorn from 'eslint-plugin-unicorn'; @@ -18,7 +19,6 @@ export default typescriptEslint.config( ...eslintPluginSvelte.configs.recommended, eslintPluginUnicorn.configs.recommended, js.configs.recommended, - prettier, { plugins: { tscompat: tslintPluginCompat, @@ -134,6 +134,18 @@ export default typescriptEslint.config( }, }, { + extends: [eslintPluginBetterTailwindcss.configs.recommended], + settings: { + 'better-tailwindcss': { + entryPoint: 'src/app.css', + }, + }, + + rules: { + 'better-tailwindcss/enforce-consistent-line-wrapping': 'off', + 'better-tailwindcss/no-unknown-classes': 'off', + }, + files: ['**/*.svelte'], languageOptions: { diff --git a/web/package.json b/web/package.json index 32b44a4645..1b4f1c46ed 100644 --- a/web/package.json +++ b/web/package.json @@ -76,7 +76,7 @@ "@sveltejs/enhanced-img": "^0.10.4", "@sveltejs/kit": "^2.56.1", "@sveltejs/vite-plugin-svelte": "7.0.0", - "@tailwindcss/vite": "^4.2.2", + "@tailwindcss/vite": "^4.2.4", "@testing-library/jest-dom": "^6.4.2", "@testing-library/svelte": "^5.2.8", "@testing-library/user-event": "^14.5.2", @@ -91,6 +91,7 @@ "dotenv": "^17.0.0", "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", + "eslint-plugin-better-tailwindcss": "^4.5.0", "eslint-plugin-compat": "^7.0.0", "eslint-plugin-svelte": "^3.12.4", "eslint-plugin-unicorn": "^64.0.0", @@ -104,7 +105,7 @@ "svelte": "5.55.2", "svelte-check": "^4.4.6", "svelte-eslint-parser": "^1.3.3", - "tailwindcss": "^4.2.2", + "tailwindcss": "^4.2.4", "typescript": "^6.0.0", "typescript-eslint": "^8.45.0", "vite": "^8.0.0", diff --git a/web/src/app.css b/web/src/app.css index 0a0187f9fd..07226be41f 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -1,8 +1,38 @@ @import 'tailwindcss'; @import '@immich/ui/theme/default.css'; + @source "../node_modules/@immich/ui"; /* @import '../../../ui/packages/ui/dist/theme/default.css'; */ +@custom-variant dark (&:where(.dark, .dark *):not(.light)); + +@theme inline { + --color-immich-primary: rgb(var(--immich-primary)); + --color-immich-bg: rgb(var(--immich-bg)); + --color-immich-fg: rgb(var(--immich-fg)); + --color-immich-gray: rgb(var(--immich-gray)); + + --color-immich-dark-primary: rgb(var(--immich-dark-primary)); + --color-immich-dark-bg: rgb(var(--immich-dark-bg)); + --color-immich-dark-fg: rgb(var(--immich-dark-fg)); + --color-immich-dark-gray: rgb(var(--immich-dark-gray)); +} + +@theme { + --font-sans: 'GoogleSans', sans-serif; + --font-mono: 'GoogleSansCode', monospace; + + --spacing-18: 4.5rem; + + --breakpoint-tall: 800px; + --breakpoint-2xl: 1535px; + --breakpoint-xl: 1279px; + --breakpoint-lg: 1023px; + --breakpoint-md: 767px; + --breakpoint-sm: 639px; + --breakpoint-sidebar: 850px; +} + @utility immich-form-input { @apply bg-gray-100 ring-1 ring-gray-200 transition outline-none focus-within:ring-1 disabled:cursor-not-allowed dark:bg-gray-800 dark:ring-neutral-900 flex w-full items-center rounded-lg disabled:bg-gray-300 disabled:text-dark dark:disabled:bg-gray-900 dark:disabled:text-gray-200 flex-1 py-2.5 text-base pl-4 pr-4; } @@ -34,35 +64,6 @@ grid-template-columns: repeat(auto-fill, minmax(min(calc(var(--spacing) * --value(number)), 100%), 1fr)); } -@custom-variant dark (&:where(.dark, .dark *):not(.light)); - -@theme inline { - --color-immich-primary: rgb(var(--immich-primary)); - --color-immich-bg: rgb(var(--immich-bg)); - --color-immich-fg: rgb(var(--immich-fg)); - --color-immich-gray: rgb(var(--immich-gray)); - - --color-immich-dark-primary: rgb(var(--immich-dark-primary)); - --color-immich-dark-bg: rgb(var(--immich-dark-bg)); - --color-immich-dark-fg: rgb(var(--immich-dark-fg)); - --color-immich-dark-gray: rgb(var(--immich-dark-gray)); -} - -@theme { - --font-sans: 'GoogleSans', sans-serif; - --font-mono: 'GoogleSansCode', monospace; - - --spacing-18: 4.5rem; - - --breakpoint-tall: 800px; - --breakpoint-2xl: 1535px; - --breakpoint-xl: 1279px; - --breakpoint-lg: 1023px; - --breakpoint-md: 767px; - --breakpoint-sm: 639px; - --breakpoint-sidebar: 850px; -} - @layer base { :root { /* light */ @@ -168,7 +169,7 @@ .maplibregl-popup { .maplibregl-popup-tip { - @apply border-t-subtle! translate-y-[-1px]; + @apply border-t-subtle! -translate-y-px; } .maplibregl-popup-content { diff --git a/web/src/lib/components/AdaptiveImage.svelte b/web/src/lib/components/AdaptiveImage.svelte index a61fb13029..39bc4516b7 100644 --- a/web/src/lib/components/AdaptiveImage.svelte +++ b/web/src/lib/components/AdaptiveImage.svelte @@ -148,11 +148,11 @@ }); -
+
{@render backdrop?.()}
- + {:else if show.spinner} {/if} @@ -185,7 +185,7 @@ {/if} {#if show.brokenAsset} - + {/if} {#if show.preview} diff --git a/web/src/lib/components/AdminCard.svelte b/web/src/lib/components/AdminCard.svelte index 4aaf890ca4..02f1195ddb 100644 --- a/web/src/lib/components/AdminCard.svelte +++ b/web/src/lib/components/AdminCard.svelte @@ -15,7 +15,7 @@ -
+
{title} diff --git a/web/src/lib/components/ApiKeyPermissionsPicker.svelte b/web/src/lib/components/ApiKeyPermissionsPicker.svelte index 62283bcf74..859c20da80 100644 --- a/web/src/lib/components/ApiKeyPermissionsPicker.svelte +++ b/web/src/lib/components/ApiKeyPermissionsPicker.svelte @@ -50,7 +50,7 @@