diff --git a/apps/business/src/components/BrandModal.module.css b/apps/business/src/components/BrandModal.module.css index d89a231..bbdd25d 100644 --- a/apps/business/src/components/BrandModal.module.css +++ b/apps/business/src/components/BrandModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.25); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,6 +10,19 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .modal { width: 100%; max-width: 520px; @@ -135,14 +147,6 @@ box-shadow: 0 6px 16px rgba(var(--primary-rgb) / 0.35); } -.overlayIn { - animation: overlayFadeIn 0.22s ease forwards; -} - -.overlayOut { - animation: overlayFadeOut 0.22s ease forwards; -} - .modalIn { animation: modalFadeIn 0.25s ease forwards; } @@ -150,17 +154,6 @@ .modalOut { animation: modalFadeOut 0.22s ease forwards; } - -@keyframes overlayFadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes overlayFadeOut { - from { opacity: 1; } - to { opacity: 0; } -} - @keyframes modalFadeIn { from { opacity: 0; diff --git a/apps/business/src/components/CategoryModal.module.css b/apps/business/src/components/CategoryModal.module.css index 947f1be..1de6162 100644 --- a/apps/business/src/components/CategoryModal.module.css +++ b/apps/business/src/components/CategoryModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.25); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,6 +10,19 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .modal { width: 100%; max-width: 480px; @@ -147,14 +159,6 @@ box-shadow: 0 6px 16px rgba(var(--primary-rgb) / 0.35); } -.overlayIn { - animation: overlayFadeIn 0.22s ease forwards; -} - -.overlayOut { - animation: overlayFadeOut 0.22s ease forwards; -} - .modalIn { animation: modalFadeIn 0.25s ease forwards; } @@ -162,17 +166,6 @@ .modalOut { animation: modalFadeOut 0.22s ease forwards; } - -@keyframes overlayFadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes overlayFadeOut { - from { opacity: 1; } - to { opacity: 0; } -} - @keyframes modalFadeIn { from { opacity: 0; diff --git a/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css b/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css index 8691260..a97e78d 100644 --- a/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css +++ b/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.28); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,6 +10,19 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .modal { position: relative; width: 100%; @@ -174,13 +186,8 @@ cursor: not-allowed; } -.overlayIn { animation: overlayFadeIn 0.22s ease forwards; } -.overlayOut { animation: overlayFadeOut 0.22s ease forwards; } .modalIn { animation: modalFadeIn 0.25s ease forwards; } .modalOut { animation: modalFadeOut 0.22s ease forwards; } - -@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/apps/business/src/components/ConfirmDeleteModal.module.css b/apps/business/src/components/ConfirmDeleteModal.module.css index bf6b8bb..de84732 100644 --- a/apps/business/src/components/ConfirmDeleteModal.module.css +++ b/apps/business/src/components/ConfirmDeleteModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(127, 29, 29, 0.2); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,6 +10,19 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: rgba(127, 29, 29, 0.28); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .modal { position: relative; width: 100%; @@ -105,13 +117,8 @@ color: #ef4444; } -.overlayIn { animation: overlayFadeIn 0.22s ease forwards; } -.overlayOut { animation: overlayFadeOut 0.22s ease forwards; } .modalIn { animation: modalFadeIn 0.25s ease forwards; } .modalOut { animation: modalFadeOut 0.22s ease forwards; } - -@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/apps/business/src/components/ImageLightbox.module.css b/apps/business/src/components/ImageLightbox.module.css index f984611..107dc5d 100644 --- a/apps/business/src/components/ImageLightbox.module.css +++ b/apps/business/src/components/ImageLightbox.module.css @@ -1,6 +1,7 @@ .overlay { position: fixed; inset: 0; + isolation: isolate; z-index: 300; display: flex; flex-direction: column; @@ -8,18 +9,20 @@ justify-content: center; gap: 16px; padding: 24px; + background: transparent; +} + +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; background: rgba(15, 23, 42, 0.82); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); } -.overlayIn { - animation: fadeIn 0.2s ease forwards; -} - -.overlayOut { - animation: fadeOut 0.2s ease forwards; -} .closeBtn { position: absolute; diff --git a/apps/business/src/components/ProductCommentsModal.module.css b/apps/business/src/components/ProductCommentsModal.module.css index 2fa01e3..40b68c9 100644 --- a/apps/business/src/components/ProductCommentsModal.module.css +++ b/apps/business/src/components/ProductCommentsModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.25); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,8 +10,18 @@ padding: 20px; } -.overlayIn { animation: overlayFadeIn 0.22s ease forwards; } -.overlayOut { animation: overlayFadeOut 0.22s ease forwards; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + .modal { width: 100%; @@ -206,9 +215,6 @@ margin-top: 0; padding-top: 0; } - -@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/apps/business/src/components/VariationsModal.module.css b/apps/business/src/components/VariationsModal.module.css index 291521f..3ab2875 100644 --- a/apps/business/src/components/VariationsModal.module.css +++ b/apps/business/src/components/VariationsModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.35); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,13 +10,23 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .overlayNested { z-index: 1010; } -.overlayIn { animation: overlayFadeIn 0.22s ease forwards; } -.overlayOut { animation: overlayFadeOut 0.22s ease forwards; } - .modal { width: 100%; max-width: 480px; @@ -420,9 +429,6 @@ color: #ef4444; margin-bottom: 12px; } - -@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/apps/business/src/i18n/messages.ts b/apps/business/src/i18n/messages.ts index 0a7aa67..1db0af9 100644 --- a/apps/business/src/i18n/messages.ts +++ b/apps/business/src/i18n/messages.ts @@ -393,6 +393,8 @@ const en = { 'storeItems.loading': 'Loading store items...', 'storeItems.empty': 'No store items yet. Use the + button to add products from your catalog.', 'storeItems.emptyFiltered': 'No store items match your filters.', + 'storeItems.pagination': + 'Page {page} / {totalPages} · {perPage} per page · {total} total', 'storeItems.errorLoad': 'Unable to load store items.', 'storeItems.errorRemove': 'Unable to remove store item.', 'storeItems.add': 'Add store items', @@ -1490,6 +1492,8 @@ const fa: Record = { 'storeItems.loading': 'در حال بارگذاری اقلام فروشگاه...', 'storeItems.empty': 'هنوز قلمی در فروشگاه نیست. با دکمه + از کاتالوگ اضافه کنید.', 'storeItems.emptyFiltered': 'هیچ قلمی با فیلترهای شما مطابقت ندارد.', + 'storeItems.pagination': + 'صفحه {page} / {totalPages} · {perPage} در هر صفحه · {total} مجموع', 'storeItems.errorLoad': 'بارگذاری اقلام فروشگاه ممکن نشد.', 'storeItems.errorRemove': 'حذف قلم از فروشگاه ممکن نشد.', 'storeItems.add': 'افزودن اقلام فروشگاه', @@ -1504,7 +1508,7 @@ const fa: Record = { 'storeItems.card.festival': 'جشنواره', 'storeItems.card.variantOne': '۱ تنوع', 'storeItems.card.variantMany': '{count} تنوع', - 'storeItems.card.edit': 'ویرایش اقلام فروشگاه', + 'storeItems.card.edit': 'ویرایش محصول فروشگاه', 'storeItems.card.discount': 'تنظیم تخفیف', 'storeItems.card.festivalPoints': 'امتیاز جشنواره', 'storeItems.card.remove': 'حذف از فروشگاه', @@ -1543,7 +1547,7 @@ const fa: Record = { 'storeItems.create.errorVariations': 'بارگذاری تنوع‌های محصول ممکن نشد.', 'storeItems.create.errorCreate': 'ایجاد اقلام فروشگاه ممکن نشد.', 'storeItems.price.contact': 'برای قیمت، تماس بگیرید', - 'storeItems.edit.title': 'ویرایش اقلام فروشگاه', + 'storeItems.edit.title': 'ویرایش محصول فروشگاه', 'storeItems.edit.save': 'ذخیره تغییرات', 'storeItems.edit.saving': 'در حال ذخیره…', 'storeItems.edit.errorSave': 'ذخیره اقلام فروشگاه ممکن نشد.', diff --git a/apps/business/src/index.css b/apps/business/src/index.css index 2856a09..526b263 100644 --- a/apps/business/src/index.css +++ b/apps/business/src/index.css @@ -24,6 +24,8 @@ --glass-border: rgba(255, 255, 255, 0.7); --glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.08); --blur-glass: 28px; + --modal-overlay-bg: rgba(15, 23, 42, 0.4); + --modal-overlay-blur: 12px; --text-primary: #1e293b; --text-secondary: #64748b; --text-muted: #94a3b8; diff --git a/apps/business/src/pages/StoreItemsPage.module.css b/apps/business/src/pages/StoreItemsPage.module.css index aef67f8..68fe921 100644 --- a/apps/business/src/pages/StoreItemsPage.module.css +++ b/apps/business/src/pages/StoreItemsPage.module.css @@ -24,6 +24,39 @@ border-radius: var(--radius-sm); } +.pagination { + margin-top: 20px; + padding: 0 4px; + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + min-height: 38px; + color: var(--text-secondary); + font-weight: 500; + font-size: 12px; +} + +.paginationMeta { + grid-column: 1; + justify-self: start; + display: inline-flex; + align-items: center; + height: 38px; + margin: 0; + padding: 0; + line-height: 38px; + font-family: var(--font-ui); + transform: translateY(2px); +} + +.pagination > nav { + grid-column: 2; + justify-self: center; + height: 38px; + margin: 0; + padding: 0; +} + .fabDock { position: fixed; inset-inline-end: 32px; diff --git a/apps/business/src/pages/StoreItemsPage.tsx b/apps/business/src/pages/StoreItemsPage.tsx index f4aa45f..56681ae 100644 --- a/apps/business/src/pages/StoreItemsPage.tsx +++ b/apps/business/src/pages/StoreItemsPage.tsx @@ -1,9 +1,11 @@ import { useEffect, useMemo, useState } from 'react' +import { useSearchParams } from 'react-router-dom' import { Plus, RotateCcw, Search } from 'lucide-react' import { Breadcrumbs } from '../components/Breadcrumbs' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { CreateStoreItemsModal } from '../components/CreateStoreItemsModal' import { EditStoreItemsModal } from '../components/EditStoreItemsModal' +import { Pagination } from '../components/Pagination' import { PickStoreVariantModal } from '../components/PickStoreVariantModal' import { ShoppingCartModal } from '../components/ShoppingCartModal' import { StoreItemCard } from '../components/StoreItemCard' @@ -15,7 +17,8 @@ import { useToast } from '../context/ToastContext' import { ApiError } from '../lib/api' import { deleteStoreItemsByProduct, - listStoreItems, + listAllStoreItems, + STORE_ITEMS_PER_PAGE, type StoreItem, } from '../services/storeItemService' import type { ShoppingCard } from '../services/shoppingCardService' @@ -37,6 +40,11 @@ import filterStyles from '../components/ListFiltersPanel.module.css' import pageStyles from '../components/PageContent.module.css' import styles from './StoreItemsPage.module.css' +function readPage(searchParams: URLSearchParams) { + const pageRaw = Number(searchParams.get('page')) + return Number.isFinite(pageRaw) && pageRaw >= 1 ? Math.floor(pageRaw) : 1 +} + export function StoreItemsPage() { return } @@ -44,6 +52,8 @@ export function StoreItemsPage() { function StoreItemsPageContent() { const t = useT() const { locale } = useLocale() + const [searchParams, setSearchParams] = useSearchParams() + const currentPage = readPage(searchParams) const { itemCount, hasItems, addVariant, loadShoppingCard } = useDraftCart() const { showToast } = useToast() const [items, setItems] = useState([]) @@ -97,6 +107,20 @@ function StoreItemsPageContent() { [listings, appliedFilters], ) + const totalListings = filteredListings.length + const totalPages = Math.max(1, Math.ceil(totalListings / STORE_ITEMS_PER_PAGE)) + const safePage = Math.min(currentPage, totalPages) + const pagedListings = useMemo(() => { + const start = (safePage - 1) * STORE_ITEMS_PER_PAGE + return filteredListings.slice(start, start + STORE_ITEMS_PER_PAGE) + }, [filteredListings, safePage]) + + useEffect(() => { + if (!isLoading && currentPage > totalPages) { + writePage(totalPages) + } + }, [isLoading, currentPage, totalPages]) + const productItems = useMemo(() => { const target = editTarget ?? discountTarget ?? festivalTarget if (!target) return [] @@ -108,6 +132,13 @@ function StoreItemsPageContent() { [listings], ) + function writePage(page: number) { + const next = new URLSearchParams(searchParams) + if (page > 1) next.set('page', String(page)) + else next.delete('page') + setSearchParams(next, { replace: true }) + } + function applyFilters() { setAppliedFilters({ name: draftName.trim(), @@ -115,6 +146,7 @@ function StoreItemsPageContent() { maxPrice: parseIrtInput(draftMaxPrice), onlyDiscounted: draftOnlyDiscounted, }) + writePage(1) } function clearFilters() { @@ -123,6 +155,12 @@ function StoreItemsPageContent() { setDraftMaxPrice('') setDraftOnlyDiscounted(false) setAppliedFilters(EMPTY_STORE_LISTING_FILTERS) + writePage(1) + } + + function handlePageChange(page: number) { + writePage(page) + window.scrollTo({ top: 0, behavior: 'smooth' }) } useEffect(() => { @@ -136,8 +174,8 @@ function StoreItemsPageContent() { setError('') try { - const data = await listStoreItems(1, 50, signal) - setItems(data.items) + const data = await listAllStoreItems(signal) + setItems(data) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return if (err instanceof ApiError) { @@ -215,6 +253,12 @@ function StoreItemsPageContent() { await deleteStoreItemsByProduct(deleteTarget.productId) setItems((prev) => prev.filter((item) => item.productId !== deleteTarget.productId)) setDeleteTarget(null) + + const nextTotal = Math.max(0, totalListings - 1) + const nextTotalPages = Math.max(1, Math.ceil(nextTotal / STORE_ITEMS_PER_PAGE)) + if (safePage > nextTotalPages) { + writePage(nextTotalPages) + } } catch (err) { if (err instanceof ApiError) { setError(err.message) @@ -329,20 +373,40 @@ function StoreItemsPageContent() { ) : filteredListings.length === 0 ? (

{t('storeItems.emptyFiltered')}

) : ( -
- {filteredListings.map((listing) => ( - +
+ {pagedListings.map((listing) => ( + + ))} +
+ +
+
+ {t('storeItems.pagination', { + page: safePage, + totalPages, + perPage: STORE_ITEMS_PER_PAGE, + total: totalListings, + })} +
+ - ))} -
+
+ )}
diff --git a/apps/business/src/services/storeItemService.ts b/apps/business/src/services/storeItemService.ts index 4accffd..4b16139 100644 --- a/apps/business/src/services/storeItemService.ts +++ b/apps/business/src/services/storeItemService.ts @@ -1,6 +1,9 @@ import { apiRequest } from '../lib/api' import { getActiveBusinessId } from '../lib/businessContext' +/** Product cards per page on My Store Items (grid). */ +export const STORE_ITEMS_PER_PAGE = 24 + export interface StoreItemSelection { variationId: string variationName: string diff --git a/apps/customer/src/components/VariationsModal.module.css b/apps/customer/src/components/VariationsModal.module.css index 3c0fc46..f1db1fa 100644 --- a/apps/customer/src/components/VariationsModal.module.css +++ b/apps/customer/src/components/VariationsModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.25); - backdrop-filter: blur(6px); - -webkit-backdrop-filter: blur(6px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,13 +10,23 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .overlayNested { z-index: 210; } -.overlayIn { animation: overlayFadeIn 0.22s ease forwards; } -.overlayOut { animation: overlayFadeOut 0.22s ease forwards; } - .modal { width: 100%; max-width: 480px; @@ -412,9 +421,6 @@ color: #ef4444; margin-bottom: 12px; } - -@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/apps/super-admin/src/components/ConfirmDeleteModal.module.css b/apps/super-admin/src/components/ConfirmDeleteModal.module.css index a10ae73..de84732 100644 --- a/apps/super-admin/src/components/ConfirmDeleteModal.module.css +++ b/apps/super-admin/src/components/ConfirmDeleteModal.module.css @@ -1,16 +1,28 @@ .overlay { position: fixed; inset: 0; - background: rgba(127, 29, 29, 0.28); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; - z-index: 300; + z-index: 200; padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: rgba(127, 29, 29, 0.28); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .modal { position: relative; width: 100%; @@ -105,13 +117,8 @@ color: #ef4444; } -.overlayIn { animation: overlayFadeIn 0.22s ease forwards; } -.overlayOut { animation: overlayFadeOut 0.22s ease forwards; } .modalIn { animation: modalFadeIn 0.25s ease forwards; } .modalOut { animation: modalFadeOut 0.22s ease forwards; } - -@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/apps/super-admin/src/components/Modal.module.css b/apps/super-admin/src/components/Modal.module.css index f84dd4d..0fc4766 100644 --- a/apps/super-admin/src/components/Modal.module.css +++ b/apps/super-admin/src/components/Modal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.32); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,6 +10,19 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .modal { width: 100%; max-width: 520px; diff --git a/apps/super-admin/src/index.css b/apps/super-admin/src/index.css index 67eecd9..5c95814 100644 --- a/apps/super-admin/src/index.css +++ b/apps/super-admin/src/index.css @@ -29,6 +29,8 @@ --radius: 16px; --radius-sm: 12px; --blur-glass: 28px; + --modal-overlay-bg: rgba(15, 23, 42, 0.4); + --modal-overlay-blur: 12px; --select-arrow-size: 16px; --select-arrow-offset: 12px; --select-padding-end: 2.5rem; diff --git a/packages/dashboard-core/src/styles/tokens.css b/packages/dashboard-core/src/styles/tokens.css index 8ebabd7..5f0d8da 100644 --- a/packages/dashboard-core/src/styles/tokens.css +++ b/packages/dashboard-core/src/styles/tokens.css @@ -24,6 +24,8 @@ --glass-border: rgba(255, 255, 255, 0.7); --glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.08); --blur-glass: 28px; + --modal-overlay-bg: rgba(15, 23, 42, 0.4); + --modal-overlay-blur: 12px; --text-primary: #1e293b; --text-secondary: #64748b; --text-muted: #94a3b8; diff --git a/packages/dashboard-ui/src/components/PasswordResetModal.module.css b/packages/dashboard-ui/src/components/PasswordResetModal.module.css index fdf2012..1e467a7 100644 --- a/packages/dashboard-ui/src/components/PasswordResetModal.module.css +++ b/packages/dashboard-ui/src/components/PasswordResetModal.module.css @@ -1,9 +1,8 @@ .overlay { position: fixed; inset: 0; - background: rgba(15, 23, 42, 0.25); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); + isolation: isolate; + background: transparent; display: flex; align-items: center; justify-content: center; @@ -11,6 +10,19 @@ padding: 20px; } +/* Tint + blur on ::before so opacity animations on .overlay cannot disable backdrop-filter. */ +.overlay::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: var(--modal-overlay-bg); + backdrop-filter: blur(var(--modal-overlay-blur)); + -webkit-backdrop-filter: blur(var(--modal-overlay-blur)); +} + + .modal { position: relative; width: 100%; @@ -245,13 +257,8 @@ color: var(--primary); } -.overlayIn { animation: overlayFadeIn 0.22s ease forwards; } -.overlayOut { animation: overlayFadeOut 0.22s ease forwards; } .modalIn { animation: modalFadeIn 0.25s ease forwards; } .modalOut { animation: modalFadeOut 0.22s ease forwards; } - -@keyframes overlayFadeIn { from { opacity: 0; } to { opacity: 1; } } -@keyframes overlayFadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes modalFadeIn { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); }